Skip to content

Commit b8eaec6

Browse files
stratakisvstinner
authored andcommitted
[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 358cfd4)
1 parent c49f63c commit b8eaec6

File tree

5 files changed

+86
-54
lines changed

5 files changed

+86
-54
lines changed

Doc/library/ssl.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,6 +1058,17 @@ to speed up repeated connections from the same clients.
10581058
:func:`create_default_context` lets the :mod:`ssl` module choose
10591059
security settings for a given purpose.
10601060

1061+
.. versionchanged:: 2.7.16
1062+
1063+
The context is created with secure default values. The options
1064+
:data:`OP_NO_COMPRESSION`, :data:`OP_CIPHER_SERVER_PREFERENCE`,
1065+
:data:`OP_SINGLE_DH_USE`, :data:`OP_SINGLE_ECDH_USE`,
1066+
:data:`OP_NO_SSLv2` (except for :data:`PROTOCOL_SSLv2`),
1067+
and :data:`OP_NO_SSLv3` (except for :data:`PROTOCOL_SSLv3`) are
1068+
set by default. The initial cipher suite list contains only ``HIGH``
1069+
ciphers, no ``NULL`` ciphers and no ``MD5`` ciphers (except for
1070+
:data:`PROTOCOL_SSLv2`).
1071+
10611072

10621073
:class:`SSLContext` objects have the following methods and attributes:
10631074

Lib/ssl.py

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

427+
# SSLContext sets OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION,
428+
# OP_CIPHER_SERVER_PREFERENCE, OP_SINGLE_DH_USE and OP_SINGLE_ECDH_USE
429+
# by default.
427430
context = SSLContext(PROTOCOL_TLS)
428431

429-
# SSLv2 considered harmful.
430-
context.options |= OP_NO_SSLv2
431-
432-
# SSLv3 has problematic security and is only required for really old
433-
# clients such as IE6 on Windows XP
434-
context.options |= OP_NO_SSLv3
435-
436-
# disable compression to prevent CRIME attacks (OpenSSL 1.0+)
437-
context.options |= getattr(_ssl, "OP_NO_COMPRESSION", 0)
438-
439432
if purpose == Purpose.SERVER_AUTH:
440433
# verify certs and host name in client mode
441434
context.verify_mode = CERT_REQUIRED
442435
context.check_hostname = True
443436
elif purpose == Purpose.CLIENT_AUTH:
444-
# Prefer the server's ciphers by default so that we get stronger
445-
# encryption
446-
context.options |= getattr(_ssl, "OP_CIPHER_SERVER_PREFERENCE", 0)
447-
448-
# Use single use keys in order to improve forward secrecy
449-
context.options |= getattr(_ssl, "OP_SINGLE_DH_USE", 0)
450-
context.options |= getattr(_ssl, "OP_SINGLE_ECDH_USE", 0)
451-
452-
# disallow ciphers with known vulnerabilities
453437
context.set_ciphers(_RESTRICTED_SERVER_CIPHERS)
454438

455439
if cafile or capath or cadata:
@@ -475,12 +459,10 @@ def _create_unverified_context(protocol=PROTOCOL_TLS, cert_reqs=None,
475459
if not isinstance(purpose, _ASN1Object):
476460
raise TypeError(purpose)
477461

462+
# SSLContext sets OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION,
463+
# OP_CIPHER_SERVER_PREFERENCE, OP_SINGLE_DH_USE and OP_SINGLE_ECDH_USE
464+
# by default.
478465
context = SSLContext(protocol)
479-
# SSLv2 considered harmful.
480-
context.options |= OP_NO_SSLv2
481-
# SSLv3 has problematic security and is only required for really old
482-
# clients such as IE6 on Windows XP
483-
context.options |= OP_NO_SSLv3
484466

485467
if cert_reqs is not None:
486468
context.verify_mode = cert_reqs

Lib/test/test_ssl.py

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ def data_file(*name):
7777
DHFILE = data_file("ffdh3072.pem")
7878
BYTES_DHFILE = DHFILE.encode(sys.getfilesystemencoding())
7979

80+
# Not defined in all versions of OpenSSL
81+
OP_NO_COMPRESSION = getattr(ssl, "OP_NO_COMPRESSION", 0)
82+
OP_SINGLE_DH_USE = getattr(ssl, "OP_SINGLE_DH_USE", 0)
83+
OP_SINGLE_ECDH_USE = getattr(ssl, "OP_SINGLE_ECDH_USE", 0)
84+
OP_CIPHER_SERVER_PREFERENCE = getattr(ssl, "OP_CIPHER_SERVER_PREFERENCE", 0)
85+
8086

8187
def handle_error(prefix):
8288
exc_format = ' '.join(traceback.format_exception(*sys.exc_info()))
@@ -798,8 +804,9 @@ def test_options(self):
798804
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
799805
# OP_ALL | OP_NO_SSLv2 | OP_NO_SSLv3 is the default value
800806
default = (ssl.OP_ALL | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3)
801-
if not IS_LIBRESSL and ssl.OPENSSL_VERSION_INFO >= (1, 1, 0):
802-
default |= ssl.OP_NO_COMPRESSION
807+
# SSLContext also enables these by default
808+
default |= (OP_NO_COMPRESSION | OP_CIPHER_SERVER_PREFERENCE |
809+
OP_SINGLE_DH_USE | OP_SINGLE_ECDH_USE)
803810
self.assertEqual(default, ctx.options)
804811
ctx.options |= ssl.OP_NO_TLSv1
805812
self.assertEqual(default | ssl.OP_NO_TLSv1, ctx.options)
@@ -1178,70 +1185,67 @@ def test_load_default_certs_env_windows(self):
11781185
stats["x509"] += 1
11791186
self.assertEqual(ctx.cert_store_stats(), stats)
11801187

1188+
def _assert_context_options(self, ctx):
1189+
self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
1190+
if OP_NO_COMPRESSION != 0:
1191+
self.assertEqual(ctx.options & OP_NO_COMPRESSION,
1192+
OP_NO_COMPRESSION)
1193+
if OP_SINGLE_DH_USE != 0:
1194+
self.assertEqual(ctx.options & OP_SINGLE_DH_USE,
1195+
OP_SINGLE_DH_USE)
1196+
if OP_SINGLE_ECDH_USE != 0:
1197+
self.assertEqual(ctx.options & OP_SINGLE_ECDH_USE,
1198+
OP_SINGLE_ECDH_USE)
1199+
if OP_CIPHER_SERVER_PREFERENCE != 0:
1200+
self.assertEqual(ctx.options & OP_CIPHER_SERVER_PREFERENCE,
1201+
OP_CIPHER_SERVER_PREFERENCE)
1202+
11811203
def test_create_default_context(self):
11821204
ctx = ssl.create_default_context()
1205+
11831206
self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23)
11841207
self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
11851208
self.assertTrue(ctx.check_hostname)
1186-
self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
1187-
self.assertEqual(
1188-
ctx.options & getattr(ssl, "OP_NO_COMPRESSION", 0),
1189-
getattr(ssl, "OP_NO_COMPRESSION", 0),
1190-
)
1209+
self._assert_context_options(ctx)
1210+
11911211

11921212
with open(SIGNING_CA) as f:
11931213
cadata = f.read().decode("ascii")
11941214
ctx = ssl.create_default_context(cafile=SIGNING_CA, capath=CAPATH,
11951215
cadata=cadata)
11961216
self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23)
11971217
self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
1198-
self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
1199-
self.assertEqual(
1200-
ctx.options & getattr(ssl, "OP_NO_COMPRESSION", 0),
1201-
getattr(ssl, "OP_NO_COMPRESSION", 0),
1202-
)
1218+
self._assert_context_options(ctx)
12031219

12041220
ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
12051221
self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23)
12061222
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
1207-
self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
1208-
self.assertEqual(
1209-
ctx.options & getattr(ssl, "OP_NO_COMPRESSION", 0),
1210-
getattr(ssl, "OP_NO_COMPRESSION", 0),
1211-
)
1212-
self.assertEqual(
1213-
ctx.options & getattr(ssl, "OP_SINGLE_DH_USE", 0),
1214-
getattr(ssl, "OP_SINGLE_DH_USE", 0),
1215-
)
1216-
self.assertEqual(
1217-
ctx.options & getattr(ssl, "OP_SINGLE_ECDH_USE", 0),
1218-
getattr(ssl, "OP_SINGLE_ECDH_USE", 0),
1219-
)
1223+
self._assert_context_options(ctx)
12201224

12211225
def test__create_stdlib_context(self):
12221226
ctx = ssl._create_stdlib_context()
12231227
self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23)
12241228
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
12251229
self.assertFalse(ctx.check_hostname)
1226-
self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
1230+
self._assert_context_options(ctx)
12271231

12281232
ctx = ssl._create_stdlib_context(ssl.PROTOCOL_TLSv1)
12291233
self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1)
12301234
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
1231-
self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
1235+
self._assert_context_options(ctx)
12321236

12331237
ctx = ssl._create_stdlib_context(ssl.PROTOCOL_TLSv1,
12341238
cert_reqs=ssl.CERT_REQUIRED,
12351239
check_hostname=True)
12361240
self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1)
12371241
self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
12381242
self.assertTrue(ctx.check_hostname)
1239-
self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
1243+
self._assert_context_options(ctx)
12401244

12411245
ctx = ssl._create_stdlib_context(purpose=ssl.Purpose.CLIENT_AUTH)
12421246
self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23)
12431247
self.assertEqual(ctx.verify_mode, ssl.CERT_NONE)
1244-
self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2)
1248+
self._assert_context_options(ctx)
12451249

12461250
def test__https_verify_certificates(self):
12471251
# Unit test to check the contect factory mapping
@@ -2841,7 +2845,8 @@ def test_tls1_3(self):
28412845
ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_TLSv1_2
28422846
)
28432847
with ThreadedEchoServer(context=context) as server:
2844-
with context.wrap_socket(socket.socket()) as s:
2848+
s = context.wrap_socket(socket.socket())
2849+
with closing(s):
28452850
s.connect((HOST, server.port))
28462851
self.assertIn(s.cipher()[0], [
28472852
'TLS_AES_256_GCM_SHA384',
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SSLContext has improved default settings: OP_NO_SSLv2, OP_NO_SSLv3,
2+
OP_NO_COMPRESSION, OP_CIPHER_SERVER_PREFERENCE, OP_SINGLE_DH_USE,
3+
OP_SINGLE_ECDH_USE and HIGH ciphers without MD5.

Modules/_ssl.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2181,6 +2181,7 @@ context_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
21812181
int proto_version = PY_SSL_VERSION_TLS;
21822182
long options;
21832183
SSL_CTX *ctx = NULL;
2184+
int result;
21842185

21852186
if (!PyArg_ParseTupleAndKeywords(
21862187
args, kwds, "i:_SSLContext", kwlist,
@@ -2245,8 +2246,38 @@ context_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
22452246
options |= SSL_OP_NO_SSLv2;
22462247
if (proto_version != PY_SSL_VERSION_SSL3)
22472248
options |= SSL_OP_NO_SSLv3;
2249+
/* Minimal security flags for server and client side context.
2250+
* Client sockets ignore server-side parameters. */
2251+
#ifdef SSL_OP_NO_COMPRESSION
2252+
options |= SSL_OP_NO_COMPRESSION;
2253+
#endif
2254+
#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
2255+
options |= SSL_OP_CIPHER_SERVER_PREFERENCE;
2256+
#endif
2257+
#ifdef SSL_OP_SINGLE_DH_USE
2258+
options |= SSL_OP_SINGLE_DH_USE;
2259+
#endif
2260+
#ifdef SSL_OP_SINGLE_ECDH_USE
2261+
options |= SSL_OP_SINGLE_ECDH_USE;
2262+
#endif
22482263
SSL_CTX_set_options(self->ctx, options);
22492264

2265+
/* A bare minimum cipher list without completly broken cipher suites.
2266+
* It's far from perfect but gives users a better head start. */
2267+
if (proto_version != PY_SSL_VERSION_SSL2) {
2268+
result = SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL:!eNULL:!MD5");
2269+
} else {
2270+
/* SSLv2 needs MD5 */
2271+
result = SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL:!eNULL");
2272+
}
2273+
if (result == 0) {
2274+
Py_DECREF(self);
2275+
ERR_clear_error();
2276+
PyErr_SetString(PySSLErrorObject,
2277+
"No cipher can be selected.");
2278+
return NULL;
2279+
}
2280+
22502281
#if !defined(OPENSSL_NO_ECDH) && !defined(OPENSSL_VERSION_1_1)
22512282
/* Allow automatic ECDH curve selection (on OpenSSL 1.0.2+), or use
22522283
prime256v1 by default. This is Apache mod_ssl's initialization

0 commit comments

Comments
 (0)