summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/ssl.rst9
-rw-r--r--Lib/ssl.py30
-rw-r--r--Lib/test/test_ssl.py62
-rw-r--r--Misc/NEWS4
-rw-r--r--Modules/_ssl.c31
5 files changed, 82 insertions, 54 deletions
diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst
index 2285237..98008fa 100644
--- a/Doc/library/ssl.rst
+++ b/Doc/library/ssl.rst
@@ -1191,7 +1191,14 @@ to speed up repeated connections from the same clients.
.. versionchanged:: 3.6
- :data:`PROTOCOL_TLS` is the default value.
+ The context is created with secure default values. The options
+ :data:`OP_NO_COMPRESSION`, :data:`OP_CIPHER_SERVER_PREFERENCE`,
+ :data:`OP_SINGLE_DH_USE`, :data:`OP_SINGLE_ECDH_USE`,
+ :data:`OP_NO_SSLv2` (except for :data:`PROTOCOL_SSLv2`),
+ and :data:`OP_NO_SSLv3` (except for :data:`PROTOCOL_SSLv3`) are
+ set by default. The initial cipher suite list contains only ``HIGH``
+ ciphers, no ``NULL`` ciphers and no ``MD5`` ciphers (except for
+ :data:`PROTOCOL_SSLv2`).
:class:`SSLContext` objects have the following methods and attributes:
diff --git a/Lib/ssl.py b/Lib/ssl.py
index 5f33849..3400b7f 100644
--- a/Lib/ssl.py
+++ b/Lib/ssl.py
@@ -488,32 +488,16 @@ def create_default_context(purpose=Purpose.SERVER_AUTH, *, cafile=None,
if not isinstance(purpose, _ASN1Object):
raise TypeError(purpose)
+ # SSLContext sets OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION,
+ # OP_CIPHER_SERVER_PREFERENCE, OP_SINGLE_DH_USE and OP_SINGLE_ECDH_USE
+ # by default.
context = SSLContext(PROTOCOL_TLS)
- # 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)
-
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:
@@ -539,12 +523,10 @@ def _create_unverified_context(protocol=PROTOCOL_TLS, *, cert_reqs=None,
if not isinstance(purpose, _ASN1Object):
raise TypeError(purpose)
+ # SSLContext sets OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION,
+ # OP_CIPHER_SERVER_PREFERENCE, OP_SINGLE_DH_USE and OP_SINGLE_ECDH_USE
+ # by default.
context = SSLContext(protocol)
- # 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
if cert_reqs is not None:
context.verify_mode = cert_reqs
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
index 0bdb86d..7488dc7 100644
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -80,6 +80,12 @@ NULLBYTECERT = data_file("nullbytecert.pem")
DHFILE = data_file("dh1024.pem")
BYTES_DHFILE = os.fsencode(DHFILE)
+# Not defined in all versions of OpenSSL
+OP_NO_COMPRESSION = getattr(ssl, "OP_NO_COMPRESSION", 0)
+OP_SINGLE_DH_USE = getattr(ssl, "OP_SINGLE_DH_USE", 0)
+OP_SINGLE_ECDH_USE = getattr(ssl, "OP_SINGLE_ECDH_USE", 0)
+OP_CIPHER_SERVER_PREFERENCE = getattr(ssl, "OP_CIPHER_SERVER_PREFERENCE", 0)
+
def handle_error(prefix):
exc_format = ' '.join(traceback.format_exception(*sys.exc_info()))
@@ -870,8 +876,9 @@ class ContextTests(unittest.TestCase):
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
# OP_ALL | OP_NO_SSLv2 | OP_NO_SSLv3 is the default value
default = (ssl.OP_ALL | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3)
- if not IS_LIBRESSL and ssl.OPENSSL_VERSION_INFO >= (1, 1, 0):
- default |= ssl.OP_NO_COMPRESSION
+ # SSLContext also enables these by default
+ default |= (OP_NO_COMPRESSION | OP_CIPHER_SERVER_PREFERENCE |
+ OP_SINGLE_DH_USE | OP_SINGLE_ECDH_USE)
self.assertEqual(default, ctx.options)
ctx.options |= ssl.OP_NO_TLSv1
self.assertEqual(default | ssl.OP_NO_TLSv1, ctx.options)
@@ -1236,16 +1243,29 @@ class ContextTests(unittest.TestCase):
stats["x509"] += 1
self.assertEqual(ctx.cert_store_stats(), stats)
+ def _assert_context_options(self, ctx):
+ self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
+ if OP_NO_COMPRESSION != 0:
+ self.assertEqual(ctx.options & OP_NO_COMPRESSION,
+ OP_NO_COMPRESSION)
+ if OP_SINGLE_DH_USE != 0:
+ self.assertEqual(ctx.options & OP_SINGLE_DH_USE,
+ OP_SINGLE_DH_USE)
+ if OP_SINGLE_ECDH_USE != 0:
+ self.assertEqual(ctx.options & OP_SINGLE_ECDH_USE,
+ OP_SINGLE_ECDH_USE)
+ if OP_CIPHER_SERVER_PREFERENCE != 0:
+ self.assertEqual(ctx.options & OP_CIPHER_SERVER_PREFERENCE,
+ OP_CIPHER_SERVER_PREFERENCE)
+
def test_create_default_context(self):
ctx = ssl.create_default_context()
+
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),
- )
+ self._assert_context_options(ctx)
+
with open(SIGNING_CA) as f:
cadata = f.read()
@@ -1253,40 +1273,24 @@ class ContextTests(unittest.TestCase):
cadata=cadata)
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),
- )
+ self._assert_context_options(ctx)
ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
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),
- )
+ self._assert_context_options(ctx)
def test__create_stdlib_context(self):
ctx = ssl._create_stdlib_context()
self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23)
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
self.assertFalse(ctx.check_hostname)
- self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
+ self._assert_context_options(ctx)
ctx = ssl._create_stdlib_context(ssl.PROTOCOL_TLSv1)
self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1)
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
- self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
+ self._assert_context_options(ctx)
ctx = ssl._create_stdlib_context(ssl.PROTOCOL_TLSv1,
cert_reqs=ssl.CERT_REQUIRED,
@@ -1294,12 +1298,12 @@ class ContextTests(unittest.TestCase):
self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1)
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._assert_context_options(ctx)
ctx = ssl._create_stdlib_context(purpose=ssl.Purpose.CLIENT_AUTH)
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._assert_context_options(ctx)
def test_check_hostname(self):
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
diff --git a/Misc/NEWS b/Misc/NEWS
index b70d2ed..7ea7a5b 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -138,6 +138,10 @@ Core and Builtins
Library
-------
+- Issue 28043: SSLContext has improved default settings: OP_NO_SSLv2,
+ OP_NO_SSLv3, OP_NO_COMPRESSION, OP_CIPHER_SERVER_PREFERENCE,
+ OP_SINGLE_DH_USE, OP_SINGLE_ECDH_USE and HIGH ciphers without MD5.
+
- Issue #24693: Changed some RuntimeError's in the zipfile module to more
appropriate types. Improved some error messages and debugging output.
diff --git a/Modules/_ssl.c b/Modules/_ssl.c
index b4fac44..d605405 100644
--- a/Modules/_ssl.c
+++ b/Modules/_ssl.c
@@ -2407,6 +2407,7 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version)
PySSLContext *self;
long options;
SSL_CTX *ctx = NULL;
+ int result;
#if defined(SSL_MODE_RELEASE_BUFFERS)
unsigned long libver;
#endif
@@ -2470,8 +2471,38 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version)
options |= SSL_OP_NO_SSLv2;
if (proto_version != PY_SSL_VERSION_SSL3)
options |= SSL_OP_NO_SSLv3;
+ /* Minimal security flags for server and client side context.
+ * Client sockets ignore server-side parameters. */
+#ifdef SSL_OP_NO_COMPRESSION
+ options |= SSL_OP_NO_COMPRESSION;
+#endif
+#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
+ options |= SSL_OP_CIPHER_SERVER_PREFERENCE;
+#endif
+#ifdef SSL_OP_SINGLE_DH_USE
+ options |= SSL_OP_SINGLE_DH_USE;
+#endif
+#ifdef SSL_OP_SINGLE_ECDH_USE
+ options |= SSL_OP_SINGLE_ECDH_USE;
+#endif
SSL_CTX_set_options(self->ctx, options);
+ /* A bare minimum cipher list without completly broken cipher suites.
+ * It's far from perfect but gives users a better head start. */
+ if (proto_version != PY_SSL_VERSION_SSL2) {
+ result = SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL:!eNULL:!MD5");
+ } else {
+ /* SSLv2 needs MD5 */
+ result = SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL:!eNULL");
+ }
+ if (result == 0) {
+ Py_DECREF(self);
+ ERR_clear_error();
+ PyErr_SetString(PySSLErrorObject,
+ "No cipher can be selected.");
+ return NULL;
+ }
+
#if defined(SSL_MODE_RELEASE_BUFFERS)
/* Set SSL_MODE_RELEASE_BUFFERS. This potentially greatly reduces memory
usage for no cost at all. However, don't do this for OpenSSL versions