Blob Blame History Raw
From 8c544d9583c4172634f3180d6f90e6d4f37595ed Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcritten@redhat.com>
Date: Thu, 30 Oct 2014 11:52:14 -0400
Subject: [PATCH] Use NSS protocol range API to set available TLS protocols

Protocols are configured as an inclusive range from SSLv3 through
TLSv1.2. The allowed values in the range are ssl3, tls1.0,
tls1.1 and tls1.2.

This is overridable per client by setting tls_version_min and/or
tls_version_max.

https://fedorahosted.org/freeipa/ticket/4653

Reviewed-By: Jan Cholasta <jcholast@redhat.com>
---
 freeipa.spec.in     |  2 +-
 ipalib/constants.py |  4 ++++
 ipalib/rpc.py       |  5 ++++-
 ipapython/dogtag.py |  4 +++-
 ipapython/nsslib.py | 17 +++++++++++++++--
 5 files changed, 27 insertions(+), 5 deletions(-)

diff --git a/freeipa.spec.in b/freeipa.spec.in
index e29f77de0db89035d15008c6be2da0ae7e96158a..1c975b3912d0a7470a32f1b7e314cfde74446e85 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -271,7 +271,7 @@ Requires: gnupg
 Requires: iproute
 Requires: keyutils
 Requires: pyOpenSSL
-Requires: python-nss >= 0.15
+Requires: python-nss >= 0.16
 Requires: python-lxml
 Requires: python-netaddr
 Requires: libipa_hbac-python
diff --git a/ipalib/constants.py b/ipalib/constants.py
index 1eed7ca6ad0e5920318dadc68ed36fff6cf889f2..111bafe5ed0c3d2df58a1b6839feedc58a14fcc4 100644
--- a/ipalib/constants.py
+++ b/ipalib/constants.py
@@ -122,6 +122,10 @@ DEFAULT_CONFIG = (
 
     ('rpc_protocol', 'jsonrpc'),
 
+    # Define an inclusive range of SSL/TLS version support
+    ('tls_version_min', 'tls1.0'),
+    ('tls_version_max', 'tls1.2'),
+
     # Time to wait for a service to start, in seconds
     ('startup_timeout', 300),
 
diff --git a/ipalib/rpc.py b/ipalib/rpc.py
index 5934f0c26e4b7c0a44adbab978c1f9b319d72e9f..806f6bb9adf004660c9cb285cf31b09a988afa93 100644
--- a/ipalib/rpc.py
+++ b/ipalib/rpc.py
@@ -68,6 +68,7 @@ from ipalib.krb_utils import KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN, KRB5KRB_AP_ERR_TKT
                              KRB5_FCC_PERM, KRB5_FCC_NOFILE, KRB5_CC_FORMAT, KRB5_REALM_CANT_RESOLVE
 from ipapython.dn import DN
 from ipalib.capabilities import VERSION_WITHOUT_CAPABILITIES
+from ipalib import api
 
 COOKIE_NAME = 'ipa_session'
 KEYRING_COOKIE_NAME = '%s_cookie:%%s' % COOKIE_NAME
@@ -488,7 +489,9 @@ class SSLTransport(LanguageAwareTransport):
         if sys.version_info < (2, 7):
             conn = NSSHTTPS(host, 443, dbdir=dbdir, no_init=no_init)
         else:
-            conn = NSSConnection(host, 443, dbdir=dbdir, no_init=no_init)
+            conn = NSSConnection(host, 443, dbdir=dbdir, no_init=no_init,
+                                 tls_version_min=api.env.tls_version_min,
+                                 tls_version_max=api.env.tls_version_max)
         self.dbdir=dbdir
 
         conn.connect()
diff --git a/ipapython/dogtag.py b/ipapython/dogtag.py
index 14824b99431e85dd73613befd72e500d370cfe2c..0e0aacca798377517244075ed6b07dff63e87358 100644
--- a/ipapython/dogtag.py
+++ b/ipapython/dogtag.py
@@ -234,7 +234,9 @@ def https_request(host, port, url, secdir, password, nickname, **kw):
     """
 
     def connection_factory(host, port):
-        conn = nsslib.NSSConnection(host, port, dbdir=secdir)
+        conn = nsslib.NSSConnection(host, port, dbdir=secdir,
+                                    tls_version_min=api.env.tls_version_min,
+                                    tls_version_max=api.env.tls_version_max)
         conn.set_debuglevel(0)
         conn.connect()
         conn.sock.set_client_auth_data_callback(
diff --git a/ipapython/nsslib.py b/ipapython/nsslib.py
index 93b0c56fcff4fc69841a6823aae8f694c1f76ff0..57fa3ff4fa5a044577f21fe43c2c0b0596c2e4f8 100644
--- a/ipapython/nsslib.py
+++ b/ipapython/nsslib.py
@@ -171,7 +171,8 @@ class NSSConnection(httplib.HTTPConnection, NSSAddressFamilyFallback):
     default_port = httplib.HTTPSConnection.default_port
 
     def __init__(self, host, port=None, strict=None,
-                 dbdir=None, family=socket.AF_UNSPEC, no_init=False):
+                 dbdir=None, family=socket.AF_UNSPEC, no_init=False,
+                 tls_version_min='tls1.1', tls_version_max='tls1.2'):
         """
         :param host: the server to connect to
         :param port: the port to use (default is set in HTTPConnection)
@@ -180,6 +181,8 @@ class NSSConnection(httplib.HTTPConnection, NSSAddressFamilyFallback):
         :param no_init: do not initialize the NSS database. This requires
                         that the database has already been initialized or
                         the request will fail.
+        :param tls_min_version: mininum version of SSL/TLS supported
+        :param tls_max_version: maximum version of SSL/TLS supported.
         """
         httplib.HTTPConnection.__init__(self, host, port, strict)
         NSSAddressFamilyFallback.__init__(self, family)
@@ -199,6 +202,8 @@ class NSSConnection(httplib.HTTPConnection, NSSAddressFamilyFallback):
         nss.nss_init(dbdir)
         ssl.set_domestic_policy()
         nss.set_password_callback(self.password_callback)
+        self.tls_version_min = str(tls_version_min)
+        self.tls_version_max = str(tls_version_max)
 
     def _create_socket(self):
         # TODO: remove the try block once python-nss is guaranteed to contain
@@ -218,6 +223,11 @@ class NSSConnection(httplib.HTTPConnection, NSSAddressFamilyFallback):
         self.sock = ssl.SSLSocket(family=self.family)
         self.sock.set_ssl_option(ssl.SSL_SECURITY, True)
         self.sock.set_ssl_option(ssl.SSL_HANDSHAKE_AS_CLIENT, True)
+        try:
+            self.sock.set_ssl_version_range(self.tls_version_min, self.tls_version_max)
+        except NSPRError, e:
+            root_logger.error('Failed to set TLS range to %s, %s' % (self.tls_version_min, self.tls_version_max))
+            raise
         self.sock.set_ssl_option(ssl_require_safe_negotiation, False)
         self.sock.set_ssl_option(ssl_enable_renegotiation, ssl_renegotiate_requires_xtn)
         # Provide a callback which notifies us when the SSL handshake is complete
@@ -236,8 +246,11 @@ class NSSConnection(httplib.HTTPConnection, NSSAddressFamilyFallback):
         """
         Verify callback. If we get here then the certificate is ok.
         """
+        channel = sock.get_ssl_channel_info()
+        suite = ssl.get_cipher_suite_info(channel.cipher_suite)
         root_logger.debug("handshake complete, peer = %s", sock.get_peer_name())
-        pass
+        root_logger.debug('Protocol: %s' % channel.protocol_version_str.upper())
+        root_logger.debug('Cipher: %s' % suite.cipher_suite_name)
 
     def connect(self):
         self.connect_socket(self.host, self.port)
-- 
2.1.0