diff options
author | stratakis <cstratak@redhat.com> | 2019-02-15 14:24:11 (GMT) |
---|---|---|
committer | Victor Stinner <vstinner@redhat.com> | 2019-02-15 14:24:11 (GMT) |
commit | b8eaec697a2b5d9d2def2950a0aa50e8ffcf1059 (patch) | |
tree | 3999b0fe5427d530a0a0aec871db684e45580cf4 | |
parent | c49f63c1761ce03df7850b9e0b31a18c432dac64 (diff) | |
download | cpython-b8eaec697a2b5d9d2def2950a0aa50e8ffcf1059.zip cpython-b8eaec697a2b5d9d2def2950a0aa50e8ffcf1059.tar.gz cpython-b8eaec697a2b5d9d2def2950a0aa50e8ffcf1059.tar.bz2 |
[2.7] bpo-28043: improved default settings for SSLContext (GH-10608)
The options OP_NO_COMPRESSION, OP_CIPHER_SERVER_PREFERENCE,
OP_SINGLE_DH_USE, OP_SINGLE_ECDH_USE, OP_NO_SSLv2 (except
for PROTOCOL_SSLv2), and OP_NO_SSLv3 (except for PROTOCOL_SSLv3)
are set by default. The initial cipher suite list contains only
HIGH ciphers, no NULL ciphers and MD5 ciphers (except for PROTOCOL_SSLv2).
(cherry picked from commit 358cfd426ccc0fcd6a7940d306602138e76420ae)
-rw-r--r-- | Doc/library/ssl.rst | 11 | ||||
-rw-r--r-- | Lib/ssl.py | 30 | ||||
-rw-r--r-- | Lib/test/test_ssl.py | 65 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Security/2018-11-20-16-50-03.bpo-28043.qOoOqW.rst | 3 | ||||
-rw-r--r-- | Modules/_ssl.c | 31 |
5 files changed, 86 insertions, 54 deletions
diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 7c7c85b..35c9548 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1058,6 +1058,17 @@ to speed up repeated connections from the same clients. :func:`create_default_context` lets the :mod:`ssl` module choose security settings for a given purpose. + .. versionchanged:: 2.7.16 + + 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: @@ -424,32 +424,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: @@ -475,12 +459,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 71f7777..00c8302 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -77,6 +77,12 @@ TALOS_INVALID_CRLDP = data_file("talos-2019-0758.pem") DHFILE = data_file("ffdh3072.pem") BYTES_DHFILE = DHFILE.encode(sys.getfilesystemencoding()) +# 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())) @@ -798,8 +804,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) @@ -1178,16 +1185,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().decode("ascii") @@ -1195,40 +1215,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, @@ -1236,12 +1240,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__https_verify_certificates(self): # Unit test to check the contect factory mapping @@ -2841,7 +2845,8 @@ else: ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_TLSv1_2 ) with ThreadedEchoServer(context=context) as server: - with context.wrap_socket(socket.socket()) as s: + s = context.wrap_socket(socket.socket()) + with closing(s): s.connect((HOST, server.port)) self.assertIn(s.cipher()[0], [ 'TLS_AES_256_GCM_SHA384', diff --git a/Misc/NEWS.d/next/Security/2018-11-20-16-50-03.bpo-28043.qOoOqW.rst b/Misc/NEWS.d/next/Security/2018-11-20-16-50-03.bpo-28043.qOoOqW.rst new file mode 100644 index 0000000..17eb1f6 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2018-11-20-16-50-03.bpo-28043.qOoOqW.rst @@ -0,0 +1,3 @@ +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. diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 19bb120..80078aa 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -2181,6 +2181,7 @@ context_new(PyTypeObject *type, PyObject *args, PyObject *kwds) int proto_version = PY_SSL_VERSION_TLS; long options; SSL_CTX *ctx = NULL; + int result; if (!PyArg_ParseTupleAndKeywords( args, kwds, "i:_SSLContext", kwlist, @@ -2245,8 +2246,38 @@ context_new(PyTypeObject *type, PyObject *args, PyObject *kwds) 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(OPENSSL_NO_ECDH) && !defined(OPENSSL_VERSION_1_1) /* Allow automatic ECDH curve selection (on OpenSSL 1.0.2+), or use prime256v1 by default. This is Apache mod_ssl's initialization |