[Python-checkins] cpython (3.4): Issue #21013: Enhance ssl.create_default_context() for server side contexts

donald.stufft python-checkins at python.org
Mon Mar 24 00:05:41 CET 2014


http://hg.python.org/cpython/rev/92efd86d1a38
changeset:   89941:92efd86d1a38
branch:      3.4
parent:      89939:f2dc5e60a293
user:        Donald Stufft <donald at stufft.io>
date:        Sun Mar 23 19:05:28 2014 -0400
summary:
  Issue #21013: Enhance ssl.create_default_context() for server side contexts

Closes #21013 by modfying ssl.create_default_context() to:

* Move the restricted ciphers to only apply when using
  ssl.Purpose.CLIENT_AUTH. The major difference between restricted and not
  is the lack of RC4 in the restricted. However there are servers that exist
  that only expose RC4 still.
* Switches the default protocol to ssl.PROTOCOL_SSLv23 so that the context
  will select TLS1.1 or TLS1.2 if it is available.
* Add ssl.OP_NO_SSLv3 by default to continue to block SSL3.0 sockets
* Add ssl.OP_SINGLE_DH_USE and ssl.OP_SINGLE_ECDG_USE to improve the security
  of the perfect forward secrecy
* Add ssl.OP_CIPHER_SERVER_PREFERENCE so that when used for a server side
  socket the context will prioritize our ciphers which have been carefully
  selected to maximize security and performance.
* Documents the failure conditions when a SSL3.0 connection is required so
  that end users can more easily determine if they need to unset
  ssl.OP_NO_SSLv3.

files:
  Doc/library/ssl.rst  |  27 ++++++++++++++++++++-------
  Lib/ssl.py           |  30 ++++++++++++++++++++++++------
  Lib/test/test_ssl.py |  26 +++++++++++++++++++++++---
  Misc/NEWS            |   3 +++
  4 files changed, 70 insertions(+), 16 deletions(-)


diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst
--- a/Doc/library/ssl.rst
+++ b/Doc/library/ssl.rst
@@ -250,13 +250,13 @@
    :const:`None`, this function can choose to trust the system's default
    CA certificates instead.
 
-   The settings in Python 3.4 are: :data:`PROTOCOL_TLSv1` with high encryption
-   cipher suites without RC4 and without unauthenticated cipher suites.
-   Passing :data:`~Purpose.SERVER_AUTH` as *purpose* sets
-   :data:`~SSLContext.verify_mode` to :data:`CERT_REQUIRED` and either
-   loads CA certificates (when at least one of *cafile*, *capath* or *cadata*
-   is given) or uses :meth:`SSLContext.load_default_certs` to load default
-   CA certificates.
+   The settings in Python 3.4 are: :data:`PROTOCOL_SSLv23`, :data:`OP_NO_SSLv2`,
+   and :data:`OP_NO_SSLv3` with high encryption cipher suites without RC4 and
+   without unauthenticated cipher suites. Passing :data:`~Purpose.SERVER_AUTH`
+   as *purpose* sets :data:`~SSLContext.verify_mode` to :data:`CERT_REQUIRED`
+   and either loads CA certificates (when at least one of *cafile*, *capath* or
+   *cadata* is given) or uses :meth:`SSLContext.load_default_certs` to load
+   default CA certificates.
 
    .. note::
       The protocol, options, cipher and other settings may change to more
@@ -266,6 +266,19 @@
       If your application needs specific settings, you should create a
       :class:`SSLContext` and apply the settings yourself.
 
+   .. note::
+      If you find that when certain older clients or servers attempt to connect
+      with a :class:`SSLContext` created by this function that they get an
+      error stating "Protocol or cipher suite mismatch", it may be that they
+      only support SSL3.0 which this function excludes using the
+      :data:`OP_NO_SSLv3`. SSL3.0 has problematic security due to a number of
+      poor implementations and it's reliance on MD5 within the protocol. If you
+      wish to continue to use this function but still allow SSL 3.0 connections
+      you can re-enable them using::
+
+         ctx = ssl.create_default_context(Purpose.CLIENT_AUTH)
+         ctx.options &= ~ssl.OP_NO_SSLv3
+
    .. versionadded:: 3.4
 
 
diff --git a/Lib/ssl.py b/Lib/ssl.py
--- a/Lib/ssl.py
+++ b/Lib/ssl.py
@@ -179,7 +179,7 @@
     'DH+RC4:RSA+RC4:!aNULL:!eNULL:!MD5'
 )
 
-# Restricted and more secure ciphers
+# Restricted and more secure ciphers for the server side
 # This list has been explicitly chosen to:
 #   * Prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE)
 #   * Prefer ECDHE over DHE for better performance
@@ -188,7 +188,7 @@
 #   * Then Use 3DES as fallback which is secure but slow
 #   * Disable NULL authentication, NULL encryption, MD5 MACs, DSS, and RC4 for
 #     security reasons
-_RESTRICTED_CIPHERS = (
+_RESTRICTED_SERVER_CIPHERS = (
     'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+HIGH:'
     'DH+HIGH:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+HIGH:RSA+3DES:!aNULL:'
     '!eNULL:!MD5:!DSS:!RC4'
@@ -404,17 +404,35 @@
     """
     if not isinstance(purpose, _ASN1Object):
         raise TypeError(purpose)
-    context = SSLContext(PROTOCOL_TLSv1)
+
+    context = SSLContext(PROTOCOL_SSLv23)
+
     # SSLv2 considered harmful.
     context.options |= OP_NO_SSLv2
+
+    # SSLv3 has problematic security and is only required for really old
+    # clients such as IE6 on Windows XP
+    context.options |= OP_NO_SSLv3
+
     # disable compression to prevent CRIME attacks (OpenSSL 1.0+)
     context.options |= getattr(_ssl, "OP_NO_COMPRESSION", 0)
-    # disallow ciphers with known vulnerabilities
-    context.set_ciphers(_RESTRICTED_CIPHERS)
-    # verify certs and host name in client mode
+
     if purpose == Purpose.SERVER_AUTH:
+        # verify certs and host name in client mode
         context.verify_mode = CERT_REQUIRED
         context.check_hostname = True
+    elif purpose == Purpose.CLIENT_AUTH:
+        # Prefer the server's ciphers by default so that we get stronger
+        # encryption
+        context.options |= getattr(_ssl, "OP_CIPHER_SERVER_PREFERENCE", 0)
+
+        # Use single use keys in order to improve forward secrecy
+        context.options |= getattr(_ssl, "OP_SINGLE_DH_USE", 0)
+        context.options |= getattr(_ssl, "OP_SINGLE_ECDH_USE", 0)
+
+        # disallow ciphers with known vulnerabilities
+        context.set_ciphers(_RESTRICTED_SERVER_CIPHERS)
+
     if cafile or capath or cadata:
         context.load_verify_locations(cafile, capath, cadata)
     elif context.verify_mode != CERT_NONE:
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -1014,23 +1014,43 @@
 
     def test_create_default_context(self):
         ctx = ssl.create_default_context()
-        self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1)
+        self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23)
         self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
         self.assertTrue(ctx.check_hostname)
         self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
+        self.assertEqual(
+            ctx.options & getattr(ssl, "OP_NO_COMPRESSION", 0),
+            getattr(ssl, "OP_NO_COMPRESSION", 0),
+        )
 
         with open(SIGNING_CA) as f:
             cadata = f.read()
         ctx = ssl.create_default_context(cafile=SIGNING_CA, capath=CAPATH,
                                          cadata=cadata)
-        self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1)
+        self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23)
         self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
         self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
+        self.assertEqual(
+            ctx.options & getattr(ssl, "OP_NO_COMPRESSION", 0),
+            getattr(ssl, "OP_NO_COMPRESSION", 0),
+        )
 
         ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
-        self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1)
+        self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23)
         self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
         self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
+        self.assertEqual(
+            ctx.options & getattr(ssl, "OP_NO_COMPRESSION", 0),
+            getattr(ssl, "OP_NO_COMPRESSION", 0),
+        )
+        self.assertEqual(
+            ctx.options & getattr(ssl, "OP_SINGLE_DH_USE", 0),
+            getattr(ssl, "OP_SINGLE_DH_USE", 0),
+        )
+        self.assertEqual(
+            ctx.options & getattr(ssl, "OP_SINGLE_ECDH_USE", 0),
+            getattr(ssl, "OP_SINGLE_ECDH_USE", 0),
+        )
 
     def test__create_stdlib_context(self):
         ctx = ssl._create_stdlib_context()
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -24,6 +24,9 @@
 Library
 -------
 
+- Issue #21013: Enhance ssl.create_default_context() when used for server side
+  sockets to provide better security by default.
+
 - Issue #20633: Replace relative import by absolute import.
 
 - Issue #20980: Stop wrapping exception when using ThreadPool.

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list