Skip to content

Commit 358cfd4

Browse files
committed
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).
1 parent 7036019 commit 358cfd4

File tree

5 files changed

+82
-54
lines changed

5 files changed

+82
-54
lines changed

‎Doc/library/ssl.rst‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1191,7 +1191,14 @@ to speed up repeated connections from the same clients.
11911191

11921192
.. versionchanged:: 3.6
11931193

1194-
:data:`PROTOCOL_TLS` is the default value.
1194+
The context is created with secure default values. The options
1195+
:data:`OP_NO_COMPRESSION`, :data:`OP_CIPHER_SERVER_PREFERENCE`,
1196+
:data:`OP_SINGLE_DH_USE`, :data:`OP_SINGLE_ECDH_USE`,
1197+
:data:`OP_NO_SSLv2` (except for :data:`PROTOCOL_SSLv2`),
1198+
and :data:`OP_NO_SSLv3` (except for :data:`PROTOCOL_SSLv3`) are
1199+
set by default. The initial cipher suite list contains only ``HIGH``
1200+
ciphers, no ``NULL`` ciphers and no ``MD5`` ciphers (except for
1201+
:data:`PROTOCOL_SSLv2`).
11951202

11961203

11971204
:class:`SSLContext` objects have the following methods and attributes:

‎Lib/ssl.py‎

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -488,32 +488,16 @@ def create_default_context(purpose=Purpose.SERVER_AUTH, *, cafile=None,
488488
if not isinstance(purpose, _ASN1Object):
489489
raise TypeError(purpose)
490490

491+
# SSLContext sets OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION,
492+
# OP_CIPHER_SERVER_PREFERENCE, OP_SINGLE_DH_USE and OP_SINGLE_ECDH_USE
493+
# by default.
491494
context = SSLContext(PROTOCOL_TLS)
492495

493-
# SSLv2 considered harmful.
494-
context.options |= OP_NO_SSLv2
495-
496-
# SSLv3 has problematic security and is only required for really old
497-
# clients such as IE6 on Windows XP
498-
context.options |= OP_NO_SSLv3
499-
500-
# disable compression to prevent CRIME attacks (OpenSSL 1.0+)
501-
context.options |= getattr(_ssl, "OP_NO_COMPRESSION", 0)
502-
503496
if purpose == Purpose.SERVER_AUTH:
504497
# verify certs and host name in client mode
505498
context.verify_mode = CERT_REQUIRED
506499
context.check_hostname = True
507500
elif purpose == Purpose.CLIENT_AUTH:
508-
# Prefer the server's ciphers by default so that we get stronger
509-
# encryption
510-
context.options |= getattr(_ssl, "OP_CIPHER_SERVER_PREFERENCE", 0)
511-
512-
# Use single use keys in order to improve forward secrecy
513-
context.options |= getattr(_ssl, "OP_SINGLE_DH_USE", 0)
514-
context.options |= getattr(_ssl, "OP_SINGLE_ECDH_USE", 0)
515-
516-
# disallow ciphers with known vulnerabilities
517501
context.set_ciphers(_RESTRICTED_SERVER_CIPHERS)
518502

519503
if cafile or capath or cadata:
@@ -539,12 +523,10 @@ def _create_unverified_context(protocol=PROTOCOL_TLS, *, cert_reqs=None,
539523
if not isinstance(purpose, _ASN1Object):
540524
raise TypeError(purpose)
541525

526+
# SSLContext sets OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION,
527+
# OP_CIPHER_SERVER_PREFERENCE, OP_SINGLE_DH_USE and OP_SINGLE_ECDH_USE
528+
# by default.
542529
context = SSLContext(protocol)
543-
# SSLv2 considered harmful.
544-
context.options |= OP_NO_SSLv2
545-
# SSLv3 has problematic security and is only required for really old
546-
# clients such as IE6 on Windows XP
547-
context.options |= OP_NO_SSLv3
548530

549531
if cert_reqs is not None:
550532
context.verify_mode = cert_reqs

‎Lib/test/test_ssl.py‎

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ def data_file(*name):
8080
DHFILE = data_file("dh1024.pem")
8181
BYTES_DHFILE = os.fsencode(DHFILE)
8282

83+
# Not defined in all versions of OpenSSL
84+
OP_NO_COMPRESSION = getattr(ssl, "OP_NO_COMPRESSION", 0)
85+
OP_SINGLE_DH_USE = getattr(ssl, "OP_SINGLE_DH_USE", 0)
86+
OP_SINGLE_ECDH_USE = getattr(ssl, "OP_SINGLE_ECDH_USE", 0)
87+
OP_CIPHER_SERVER_PREFERENCE = getattr(ssl, "OP_CIPHER_SERVER_PREFERENCE", 0)
88+
8389

8490
def handle_error(prefix):
8591
exc_format = ' '.join(traceback.format_exception(*sys.exc_info()))
@@ -870,8 +876,9 @@ def test_options(self):
870876
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
871877
# OP_ALL | OP_NO_SSLv2 | OP_NO_SSLv3 is the default value
872878
default = (ssl.OP_ALL | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3)
873-
if not IS_LIBRESSL and ssl.OPENSSL_VERSION_INFO >= (1, 1, 0):
874-
default |= ssl.OP_NO_COMPRESSION
879+
# SSLContext also enables these by default
880+
default |= (OP_NO_COMPRESSION | OP_CIPHER_SERVER_PREFERENCE |
881+
OP_SINGLE_DH_USE | OP_SINGLE_ECDH_USE)
875882
self.assertEqual(default, ctx.options)
876883
ctx.options |= ssl.OP_NO_TLSv1
877884
self.assertEqual(default | ssl.OP_NO_TLSv1, ctx.options)
@@ -1236,70 +1243,67 @@ def test_load_default_certs_env_windows(self):
12361243
stats["x509"] += 1
12371244
self.assertEqual(ctx.cert_store_stats(), stats)
12381245

1246+
def _assert_context_options(self, ctx):
1247+
self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
1248+
if OP_NO_COMPRESSION != 0:
1249+
self.assertEqual(ctx.options & OP_NO_COMPRESSION,
1250+
OP_NO_COMPRESSION)
1251+
if OP_SINGLE_DH_USE != 0:
1252+
self.assertEqual(ctx.options & OP_SINGLE_DH_USE,
1253+
OP_SINGLE_DH_USE)
1254+
if OP_SINGLE_ECDH_USE != 0:
1255+
self.assertEqual(ctx.options & OP_SINGLE_ECDH_USE,
1256+
OP_SINGLE_ECDH_USE)
1257+
if OP_CIPHER_SERVER_PREFERENCE != 0:
1258+
self.assertEqual(ctx.options & OP_CIPHER_SERVER_PREFERENCE,
1259+
OP_CIPHER_SERVER_PREFERENCE)
1260+
12391261
def test_create_default_context(self):
12401262
ctx = ssl.create_default_context()
1263+
12411264
self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23)
12421265
self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
12431266
self.assertTrue(ctx.check_hostname)
1244-
self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
1245-
self.assertEqual(
1246-
ctx.options & getattr(ssl, "OP_NO_COMPRESSION", 0),
1247-
getattr(ssl, "OP_NO_COMPRESSION", 0),
1248-
)
1267+
self._assert_context_options(ctx)
1268+
12491269

12501270
with open(SIGNING_CA) as f:
12511271
cadata = f.read()
12521272
ctx = ssl.create_default_context(cafile=SIGNING_CA, capath=CAPATH,
12531273
cadata=cadata)
12541274
self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23)
12551275
self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
1256-
self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
1257-
self.assertEqual(
1258-
ctx.options & getattr(ssl, "OP_NO_COMPRESSION", 0),
1259-
getattr(ssl, "OP_NO_COMPRESSION", 0),
1260-
)
1276+
self._assert_context_options(ctx)
12611277

12621278
ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
12631279
self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23)
12641280
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
1265-
self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
1266-
self.assertEqual(
1267-
ctx.options & getattr(ssl, "OP_NO_COMPRESSION", 0),
1268-
getattr(ssl, "OP_NO_COMPRESSION", 0),
1269-
)
1270-
self.assertEqual(
1271-
ctx.options & getattr(ssl, "OP_SINGLE_DH_USE", 0),
1272-
getattr(ssl, "OP_SINGLE_DH_USE", 0),
1273-
)
1274-
self.assertEqual(
1275-
ctx.options & getattr(ssl, "OP_SINGLE_ECDH_USE", 0),
1276-
getattr(ssl, "OP_SINGLE_ECDH_USE", 0),
1277-
)
1281+
self._assert_context_options(ctx)
12781282

12791283
def test__create_stdlib_context(self):
12801284
ctx = ssl._create_stdlib_context()
12811285
self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23)
12821286
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
12831287
self.assertFalse(ctx.check_hostname)
1284-
self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
1288+
self._assert_context_options(ctx)
12851289

12861290
ctx = ssl._create_stdlib_context(ssl.PROTOCOL_TLSv1)
12871291
self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1)
12881292
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
1289-
self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
1293+
self._assert_context_options(ctx)
12901294

12911295
ctx = ssl._create_stdlib_context(ssl.PROTOCOL_TLSv1,
12921296
cert_reqs=ssl.CERT_REQUIRED,
12931297
check_hostname=True)
12941298
self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1)
12951299
self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
12961300
self.assertTrue(ctx.check_hostname)
1297-
self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
1301+
self._assert_context_options(ctx)
12981302

12991303
ctx = ssl._create_stdlib_context(purpose=ssl.Purpose.CLIENT_AUTH)
13001304
self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23)
13011305
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
1302-
self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
1306+
self._assert_context_options(ctx)
13031307

13041308
def test_check_hostname(self):
13051309
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)

‎Misc/NEWS‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ Core and Builtins
138138
Library
139139
-------
140140

141+
- Issue 28043: SSLContext has improved default settings: OP_NO_SSLv2,
142+
OP_NO_SSLv3, OP_NO_COMPRESSION, OP_CIPHER_SERVER_PREFERENCE,
143+
OP_SINGLE_DH_USE, OP_SINGLE_ECDH_USE and HIGH ciphers without MD5.
144+
141145
- Issue #24693: Changed some RuntimeError's in the zipfile module to more
142146
appropriate types. Improved some error messages and debugging output.
143147

‎Modules/_ssl.c‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2407,6 +2407,7 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version)
24072407
PySSLContext *self;
24082408
long options;
24092409
SSL_CTX *ctx = NULL;
2410+
int result;
24102411
#if defined(SSL_MODE_RELEASE_BUFFERS)
24112412
unsigned long libver;
24122413
#endif
@@ -2470,8 +2471,38 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version)
24702471
options |= SSL_OP_NO_SSLv2;
24712472
if (proto_version != PY_SSL_VERSION_SSL3)
24722473
options |= SSL_OP_NO_SSLv3;
2474+
/* Minimal security flags for server and client side context.
2475+
* Client sockets ignore server-side parameters. */
2476+
#ifdef SSL_OP_NO_COMPRESSION
2477+
options |= SSL_OP_NO_COMPRESSION;
2478+
#endif
2479+
#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
2480+
options |= SSL_OP_CIPHER_SERVER_PREFERENCE;
2481+
#endif
2482+
#ifdef SSL_OP_SINGLE_DH_USE
2483+
options |= SSL_OP_SINGLE_DH_USE;
2484+
#endif
2485+
#ifdef SSL_OP_SINGLE_ECDH_USE
2486+
options |= SSL_OP_SINGLE_ECDH_USE;
2487+
#endif
24732488
SSL_CTX_set_options(self->ctx, options);
24742489

2490+
/* A bare minimum cipher list without completly broken cipher suites.
2491+
* It's far from perfect but gives users a better head start. */
2492+
if (proto_version != PY_SSL_VERSION_SSL2) {
2493+
result = SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL:!eNULL:!MD5");
2494+
} else {
2495+
/* SSLv2 needs MD5 */
2496+
result = SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL:!eNULL");
2497+
}
2498+
if (result == 0) {
2499+
Py_DECREF(self);
2500+
ERR_clear_error();
2501+
PyErr_SetString(PySSLErrorObject,
2502+
"No cipher can be selected.");
2503+
return NULL;
2504+
}
2505+
24752506
#if defined(SSL_MODE_RELEASE_BUFFERS)
24762507
/* Set SSL_MODE_RELEASE_BUFFERS. This potentially greatly reduces memory
24772508
usage for no cost at all. However, don't do this for OpenSSL versions

0 commit comments

Comments
 (0)