Commit 358cfd42 authored by Christian Heimes's avatar Christian Heimes

Issue 28043: SSLContext has improved default settings

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).
parent 70360194
...@@ -1191,7 +1191,14 @@ to speed up repeated connections from the same clients. ...@@ -1191,7 +1191,14 @@ to speed up repeated connections from the same clients.
.. versionchanged:: 3.6 .. 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: :class:`SSLContext` objects have the following methods and attributes:
......
...@@ -488,32 +488,16 @@ def create_default_context(purpose=Purpose.SERVER_AUTH, *, cafile=None, ...@@ -488,32 +488,16 @@ def create_default_context(purpose=Purpose.SERVER_AUTH, *, cafile=None,
if not isinstance(purpose, _ASN1Object): if not isinstance(purpose, _ASN1Object):
raise TypeError(purpose) 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) 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: if purpose == Purpose.SERVER_AUTH:
# verify certs and host name in client mode # verify certs and host name in client mode
context.verify_mode = CERT_REQUIRED context.verify_mode = CERT_REQUIRED
context.check_hostname = True context.check_hostname = True
elif purpose == Purpose.CLIENT_AUTH: 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) context.set_ciphers(_RESTRICTED_SERVER_CIPHERS)
if cafile or capath or cadata: if cafile or capath or cadata:
...@@ -539,12 +523,10 @@ def _create_unverified_context(protocol=PROTOCOL_TLS, *, cert_reqs=None, ...@@ -539,12 +523,10 @@ def _create_unverified_context(protocol=PROTOCOL_TLS, *, cert_reqs=None,
if not isinstance(purpose, _ASN1Object): if not isinstance(purpose, _ASN1Object):
raise TypeError(purpose) 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) 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: if cert_reqs is not None:
context.verify_mode = cert_reqs context.verify_mode = cert_reqs
......
...@@ -80,6 +80,12 @@ NULLBYTECERT = data_file("nullbytecert.pem") ...@@ -80,6 +80,12 @@ NULLBYTECERT = data_file("nullbytecert.pem")
DHFILE = data_file("dh1024.pem") DHFILE = data_file("dh1024.pem")
BYTES_DHFILE = os.fsencode(DHFILE) 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): def handle_error(prefix):
exc_format = ' '.join(traceback.format_exception(*sys.exc_info())) exc_format = ' '.join(traceback.format_exception(*sys.exc_info()))
...@@ -870,8 +876,9 @@ class ContextTests(unittest.TestCase): ...@@ -870,8 +876,9 @@ class ContextTests(unittest.TestCase):
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
# OP_ALL | OP_NO_SSLv2 | OP_NO_SSLv3 is the default value # OP_ALL | OP_NO_SSLv2 | OP_NO_SSLv3 is the default value
default = (ssl.OP_ALL | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3) default = (ssl.OP_ALL | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3)
if not IS_LIBRESSL and ssl.OPENSSL_VERSION_INFO >= (1, 1, 0): # SSLContext also enables these by default
default |= ssl.OP_NO_COMPRESSION default |= (OP_NO_COMPRESSION | OP_CIPHER_SERVER_PREFERENCE |
OP_SINGLE_DH_USE | OP_SINGLE_ECDH_USE)
self.assertEqual(default, ctx.options) self.assertEqual(default, ctx.options)
ctx.options |= ssl.OP_NO_TLSv1 ctx.options |= ssl.OP_NO_TLSv1
self.assertEqual(default | ssl.OP_NO_TLSv1, ctx.options) self.assertEqual(default | ssl.OP_NO_TLSv1, ctx.options)
...@@ -1236,16 +1243,29 @@ class ContextTests(unittest.TestCase): ...@@ -1236,16 +1243,29 @@ class ContextTests(unittest.TestCase):
stats["x509"] += 1 stats["x509"] += 1
self.assertEqual(ctx.cert_store_stats(), stats) 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): def test_create_default_context(self):
ctx = ssl.create_default_context() ctx = ssl.create_default_context()
self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23) self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23)
self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
self.assertTrue(ctx.check_hostname) self.assertTrue(ctx.check_hostname)
self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2) self._assert_context_options(ctx)
self.assertEqual(
ctx.options & getattr(ssl, "OP_NO_COMPRESSION", 0),
getattr(ssl, "OP_NO_COMPRESSION", 0),
)
with open(SIGNING_CA) as f: with open(SIGNING_CA) as f:
cadata = f.read() cadata = f.read()
...@@ -1253,40 +1273,24 @@ class ContextTests(unittest.TestCase): ...@@ -1253,40 +1273,24 @@ class ContextTests(unittest.TestCase):
cadata=cadata) cadata=cadata)
self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23) self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23)
self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2) self._assert_context_options(ctx)
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) ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23) self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23)
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) 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)
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): def test__create_stdlib_context(self):
ctx = ssl._create_stdlib_context() ctx = ssl._create_stdlib_context()
self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23) self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23)
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
self.assertFalse(ctx.check_hostname) 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) ctx = ssl._create_stdlib_context(ssl.PROTOCOL_TLSv1)
self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1) self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1)
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) 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, ctx = ssl._create_stdlib_context(ssl.PROTOCOL_TLSv1,
cert_reqs=ssl.CERT_REQUIRED, cert_reqs=ssl.CERT_REQUIRED,
...@@ -1294,12 +1298,12 @@ class ContextTests(unittest.TestCase): ...@@ -1294,12 +1298,12 @@ class ContextTests(unittest.TestCase):
self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1) self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1)
self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
self.assertTrue(ctx.check_hostname) 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) ctx = ssl._create_stdlib_context(purpose=ssl.Purpose.CLIENT_AUTH)
self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23) self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23)
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) 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): def test_check_hostname(self):
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
......
...@@ -138,6 +138,10 @@ Core and Builtins ...@@ -138,6 +138,10 @@ Core and Builtins
Library 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 - Issue #24693: Changed some RuntimeError's in the zipfile module to more
appropriate types. Improved some error messages and debugging output. appropriate types. Improved some error messages and debugging output.
......
...@@ -2407,6 +2407,7 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version) ...@@ -2407,6 +2407,7 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version)
PySSLContext *self; PySSLContext *self;
long options; long options;
SSL_CTX *ctx = NULL; SSL_CTX *ctx = NULL;
int result;
#if defined(SSL_MODE_RELEASE_BUFFERS) #if defined(SSL_MODE_RELEASE_BUFFERS)
unsigned long libver; unsigned long libver;
#endif #endif
...@@ -2470,8 +2471,38 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version) ...@@ -2470,8 +2471,38 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version)
options |= SSL_OP_NO_SSLv2; options |= SSL_OP_NO_SSLv2;
if (proto_version != PY_SSL_VERSION_SSL3) if (proto_version != PY_SSL_VERSION_SSL3)
options |= SSL_OP_NO_SSLv3; 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); 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) #if defined(SSL_MODE_RELEASE_BUFFERS)
/* Set SSL_MODE_RELEASE_BUFFERS. This potentially greatly reduces memory /* Set SSL_MODE_RELEASE_BUFFERS. This potentially greatly reduces memory
usage for no cost at all. However, don't do this for OpenSSL versions usage for no cost at all. However, don't do this for OpenSSL versions
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment