e3ffab
From 8c544d9583c4172634f3180d6f90e6d4f37595ed Mon Sep 17 00:00:00 2001
e3ffab
From: Rob Crittenden <rcritten@redhat.com>
e3ffab
Date: Thu, 30 Oct 2014 11:52:14 -0400
e3ffab
Subject: [PATCH] Use NSS protocol range API to set available TLS protocols
e3ffab
e3ffab
Protocols are configured as an inclusive range from SSLv3 through
e3ffab
TLSv1.2. The allowed values in the range are ssl3, tls1.0,
e3ffab
tls1.1 and tls1.2.
e3ffab
e3ffab
This is overridable per client by setting tls_version_min and/or
e3ffab
tls_version_max.
e3ffab
e3ffab
https://fedorahosted.org/freeipa/ticket/4653
e3ffab
e3ffab
Reviewed-By: Jan Cholasta <jcholast@redhat.com>
e3ffab
---
e3ffab
 freeipa.spec.in     |  2 +-
e3ffab
 ipalib/constants.py |  4 ++++
e3ffab
 ipalib/rpc.py       |  5 ++++-
e3ffab
 ipapython/dogtag.py |  4 +++-
e3ffab
 ipapython/nsslib.py | 17 +++++++++++++++--
e3ffab
 5 files changed, 27 insertions(+), 5 deletions(-)
e3ffab
e3ffab
diff --git a/freeipa.spec.in b/freeipa.spec.in
e3ffab
index e29f77de0db89035d15008c6be2da0ae7e96158a..1c975b3912d0a7470a32f1b7e314cfde74446e85 100644
e3ffab
--- a/freeipa.spec.in
e3ffab
+++ b/freeipa.spec.in
e3ffab
@@ -271,7 +271,7 @@ Requires: gnupg
e3ffab
 Requires: iproute
e3ffab
 Requires: keyutils
e3ffab
 Requires: pyOpenSSL
e3ffab
-Requires: python-nss >= 0.15
e3ffab
+Requires: python-nss >= 0.16
e3ffab
 Requires: python-lxml
e3ffab
 Requires: python-netaddr
e3ffab
 Requires: libipa_hbac-python
e3ffab
diff --git a/ipalib/constants.py b/ipalib/constants.py
e3ffab
index 1eed7ca6ad0e5920318dadc68ed36fff6cf889f2..111bafe5ed0c3d2df58a1b6839feedc58a14fcc4 100644
e3ffab
--- a/ipalib/constants.py
e3ffab
+++ b/ipalib/constants.py
e3ffab
@@ -122,6 +122,10 @@ DEFAULT_CONFIG = (
e3ffab
 
e3ffab
     ('rpc_protocol', 'jsonrpc'),
e3ffab
 
e3ffab
+    # Define an inclusive range of SSL/TLS version support
e3ffab
+    ('tls_version_min', 'tls1.0'),
e3ffab
+    ('tls_version_max', 'tls1.2'),
e3ffab
+
e3ffab
     # Time to wait for a service to start, in seconds
e3ffab
     ('startup_timeout', 300),
e3ffab
 
e3ffab
diff --git a/ipalib/rpc.py b/ipalib/rpc.py
e3ffab
index 5934f0c26e4b7c0a44adbab978c1f9b319d72e9f..806f6bb9adf004660c9cb285cf31b09a988afa93 100644
e3ffab
--- a/ipalib/rpc.py
e3ffab
+++ b/ipalib/rpc.py
e3ffab
@@ -68,6 +68,7 @@ from ipalib.krb_utils import KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN, KRB5KRB_AP_ERR_TKT
e3ffab
                              KRB5_FCC_PERM, KRB5_FCC_NOFILE, KRB5_CC_FORMAT, KRB5_REALM_CANT_RESOLVE
e3ffab
 from ipapython.dn import DN
e3ffab
 from ipalib.capabilities import VERSION_WITHOUT_CAPABILITIES
e3ffab
+from ipalib import api
e3ffab
 
e3ffab
 COOKIE_NAME = 'ipa_session'
e3ffab
 KEYRING_COOKIE_NAME = '%s_cookie:%%s' % COOKIE_NAME
e3ffab
@@ -488,7 +489,9 @@ class SSLTransport(LanguageAwareTransport):
e3ffab
         if sys.version_info < (2, 7):
e3ffab
             conn = NSSHTTPS(host, 443, dbdir=dbdir, no_init=no_init)
e3ffab
         else:
e3ffab
-            conn = NSSConnection(host, 443, dbdir=dbdir, no_init=no_init)
e3ffab
+            conn = NSSConnection(host, 443, dbdir=dbdir, no_init=no_init,
e3ffab
+                                 tls_version_min=api.env.tls_version_min,
e3ffab
+                                 tls_version_max=api.env.tls_version_max)
e3ffab
         self.dbdir=dbdir
e3ffab
 
e3ffab
         conn.connect()
e3ffab
diff --git a/ipapython/dogtag.py b/ipapython/dogtag.py
e3ffab
index 14824b99431e85dd73613befd72e500d370cfe2c..0e0aacca798377517244075ed6b07dff63e87358 100644
e3ffab
--- a/ipapython/dogtag.py
e3ffab
+++ b/ipapython/dogtag.py
e3ffab
@@ -234,7 +234,9 @@ def https_request(host, port, url, secdir, password, nickname, **kw):
e3ffab
     """
e3ffab
 
e3ffab
     def connection_factory(host, port):
e3ffab
-        conn = nsslib.NSSConnection(host, port, dbdir=secdir)
e3ffab
+        conn = nsslib.NSSConnection(host, port, dbdir=secdir,
e3ffab
+                                    tls_version_min=api.env.tls_version_min,
e3ffab
+                                    tls_version_max=api.env.tls_version_max)
e3ffab
         conn.set_debuglevel(0)
e3ffab
         conn.connect()
e3ffab
         conn.sock.set_client_auth_data_callback(
e3ffab
diff --git a/ipapython/nsslib.py b/ipapython/nsslib.py
e3ffab
index 93b0c56fcff4fc69841a6823aae8f694c1f76ff0..57fa3ff4fa5a044577f21fe43c2c0b0596c2e4f8 100644
e3ffab
--- a/ipapython/nsslib.py
e3ffab
+++ b/ipapython/nsslib.py
e3ffab
@@ -171,7 +171,8 @@ class NSSConnection(httplib.HTTPConnection, NSSAddressFamilyFallback):
e3ffab
     default_port = httplib.HTTPSConnection.default_port
e3ffab
 
e3ffab
     def __init__(self, host, port=None, strict=None,
e3ffab
-                 dbdir=None, family=socket.AF_UNSPEC, no_init=False):
e3ffab
+                 dbdir=None, family=socket.AF_UNSPEC, no_init=False,
e3ffab
+                 tls_version_min='tls1.1', tls_version_max='tls1.2'):
e3ffab
         """
e3ffab
         :param host: the server to connect to
e3ffab
         :param port: the port to use (default is set in HTTPConnection)
e3ffab
@@ -180,6 +181,8 @@ class NSSConnection(httplib.HTTPConnection, NSSAddressFamilyFallback):
e3ffab
         :param no_init: do not initialize the NSS database. This requires
e3ffab
                         that the database has already been initialized or
e3ffab
                         the request will fail.
e3ffab
+        :param tls_min_version: mininum version of SSL/TLS supported
e3ffab
+        :param tls_max_version: maximum version of SSL/TLS supported.
e3ffab
         """
e3ffab
         httplib.HTTPConnection.__init__(self, host, port, strict)
e3ffab
         NSSAddressFamilyFallback.__init__(self, family)
e3ffab
@@ -199,6 +202,8 @@ class NSSConnection(httplib.HTTPConnection, NSSAddressFamilyFallback):
e3ffab
         nss.nss_init(dbdir)
e3ffab
         ssl.set_domestic_policy()
e3ffab
         nss.set_password_callback(self.password_callback)
e3ffab
+        self.tls_version_min = str(tls_version_min)
e3ffab
+        self.tls_version_max = str(tls_version_max)
e3ffab
 
e3ffab
     def _create_socket(self):
e3ffab
         # TODO: remove the try block once python-nss is guaranteed to contain
e3ffab
@@ -218,6 +223,11 @@ class NSSConnection(httplib.HTTPConnection, NSSAddressFamilyFallback):
e3ffab
         self.sock = ssl.SSLSocket(family=self.family)
e3ffab
         self.sock.set_ssl_option(ssl.SSL_SECURITY, True)
e3ffab
         self.sock.set_ssl_option(ssl.SSL_HANDSHAKE_AS_CLIENT, True)
e3ffab
+        try:
e3ffab
+            self.sock.set_ssl_version_range(self.tls_version_min, self.tls_version_max)
e3ffab
+        except NSPRError, e:
e3ffab
+            root_logger.error('Failed to set TLS range to %s, %s' % (self.tls_version_min, self.tls_version_max))
e3ffab
+            raise
e3ffab
         self.sock.set_ssl_option(ssl_require_safe_negotiation, False)
e3ffab
         self.sock.set_ssl_option(ssl_enable_renegotiation, ssl_renegotiate_requires_xtn)
e3ffab
         # Provide a callback which notifies us when the SSL handshake is complete
e3ffab
@@ -236,8 +246,11 @@ class NSSConnection(httplib.HTTPConnection, NSSAddressFamilyFallback):
e3ffab
         """
e3ffab
         Verify callback. If we get here then the certificate is ok.
e3ffab
         """
e3ffab
+        channel = sock.get_ssl_channel_info()
e3ffab
+        suite = ssl.get_cipher_suite_info(channel.cipher_suite)
e3ffab
         root_logger.debug("handshake complete, peer = %s", sock.get_peer_name())
e3ffab
-        pass
e3ffab
+        root_logger.debug('Protocol: %s' % channel.protocol_version_str.upper())
e3ffab
+        root_logger.debug('Cipher: %s' % suite.cipher_suite_name)
e3ffab
 
e3ffab
     def connect(self):
e3ffab
         self.connect_socket(self.host, self.port)
e3ffab
-- 
e3ffab
2.1.0
e3ffab