Skip to content

Commit edf41e2

Browse files
tiranstratakis
authored andcommitted
00294: Define TLS cipher suite on build time
Define TLS cipher suite on build time depending on the OpenSSL default cipher suite selection. Fixed upstream on CPython's 3.7 branch: https://bugs.python.org/issue31429 See also: https://bugzilla.redhat.com/show_bug.cgi?id=1489816 Co-Authored-By: Christian Heimes <[email protected]> Co-Authored-By: Charalampos Stratakis <[email protected]>
1 parent d8bfae4 commit edf41e2

File tree

4 files changed

+87
-48
lines changed

4 files changed

+87
-48
lines changed

Lib/ssl.py

Lines changed: 2 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@
116116

117117

118118
from _ssl import HAS_SNI, HAS_ECDH, HAS_NPN, HAS_ALPN, HAS_TLSv1_3
119+
from _ssl import _DEFAULT_CIPHERS
119120
from _ssl import _OPENSSL_API_VERSION
120121

121122

@@ -174,48 +175,7 @@
174175
CHANNEL_BINDING_TYPES = []
175176

176177

177-
# Disable weak or insecure ciphers by default
178-
# (OpenSSL's default setting is 'DEFAULT:!aNULL:!eNULL')
179-
# Enable a better set of ciphers by default
180-
# This list has been explicitly chosen to:
181-
# * TLS 1.3 ChaCha20 and AES-GCM cipher suites
182-
# * Prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE)
183-
# * Prefer ECDHE over DHE for better performance
184-
# * Prefer AEAD over CBC for better performance and security
185-
# * Prefer AES-GCM over ChaCha20 because most platforms have AES-NI
186-
# (ChaCha20 needs OpenSSL 1.1.0 or patched 1.0.2)
187-
# * Prefer any AES-GCM and ChaCha20 over any AES-CBC for better
188-
# performance and security
189-
# * Then Use HIGH cipher suites as a fallback
190-
# * Disable NULL authentication, NULL encryption, 3DES and MD5 MACs
191-
# for security reasons
192-
_DEFAULT_CIPHERS = (
193-
'TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:'
194-
'TLS13-AES-128-GCM-SHA256:'
195-
'ECDH+AESGCM:ECDH+CHACHA20:DH+AESGCM:DH+CHACHA20:ECDH+AES256:DH+AES256:'
196-
'ECDH+AES128:DH+AES:ECDH+HIGH:DH+HIGH:RSA+AESGCM:RSA+AES:RSA+HIGH:'
197-
'!aNULL:!eNULL:!MD5:!3DES'
198-
)
199-
200-
# Restricted and more secure ciphers for the server side
201-
# This list has been explicitly chosen to:
202-
# * TLS 1.3 ChaCha20 and AES-GCM cipher suites
203-
# * Prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE)
204-
# * Prefer ECDHE over DHE for better performance
205-
# * Prefer AEAD over CBC for better performance and security
206-
# * Prefer AES-GCM over ChaCha20 because most platforms have AES-NI
207-
# * Prefer any AES-GCM and ChaCha20 over any AES-CBC for better
208-
# performance and security
209-
# * Then Use HIGH cipher suites as a fallback
210-
# * Disable NULL authentication, NULL encryption, MD5 MACs, DSS, RC4, and
211-
# 3DES for security reasons
212-
_RESTRICTED_SERVER_CIPHERS = (
213-
'TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:'
214-
'TLS13-AES-128-GCM-SHA256:'
215-
'ECDH+AESGCM:ECDH+CHACHA20:DH+AESGCM:DH+CHACHA20:ECDH+AES256:DH+AES256:'
216-
'ECDH+AES128:DH+AES:ECDH+HIGH:DH+HIGH:RSA+AESGCM:RSA+AES:RSA+HIGH:'
217-
'!aNULL:!eNULL:!MD5:!DSS:!RC4:!3DES'
218-
)
178+
_RESTRICTED_SERVER_CIPHERS = _DEFAULT_CIPHERS
219179

220180

221181
class CertificateError(ValueError):
@@ -389,8 +349,6 @@ class SSLContext(_SSLContext):
389349

390350
def __new__(cls, protocol=PROTOCOL_TLS, *args, **kwargs):
391351
self = _SSLContext.__new__(cls, protocol)
392-
if protocol != _SSLv2_IF_EXISTS:
393-
self.set_ciphers(_DEFAULT_CIPHERS)
394352
return self
395353

396354
def __init__(self, protocol=PROTOCOL_TLS):
@@ -505,8 +463,6 @@ def create_default_context(purpose=Purpose.SERVER_AUTH, *, cafile=None,
505463
# verify certs and host name in client mode
506464
context.verify_mode = CERT_REQUIRED
507465
context.check_hostname = True
508-
elif purpose == Purpose.CLIENT_AUTH:
509-
context.set_ciphers(_RESTRICTED_SERVER_CIPHERS)
510466

511467
if cafile or capath or cadata:
512468
context.load_verify_locations(cafile, capath, cadata)

Lib/test/test_ssl.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import platform
2020
import re
2121
import functools
22+
import sysconfig
2223
try:
2324
import ctypes
2425
except ImportError:
@@ -37,7 +38,7 @@
3738
HOST = support.HOST
3839
IS_LIBRESSL = ssl.OPENSSL_VERSION.startswith('LibreSSL')
3940
IS_OPENSSL_1_1 = not IS_LIBRESSL and ssl.OPENSSL_VERSION_INFO >= (1, 1, 0)
40-
41+
PY_SSL_DEFAULT_CIPHERS = sysconfig.get_config_var('PY_SSL_DEFAULT_CIPHERS')
4142

4243
def data_file(*name):
4344
return os.path.join(os.path.dirname(__file__), *name)
@@ -952,6 +953,19 @@ def test_ciphers(self):
952953
with self.assertRaisesRegex(ssl.SSLError, "No cipher can be selected"):
953954
ctx.set_ciphers("^$:,;?*'dorothyx")
954955

956+
@unittest.skipUnless(PY_SSL_DEFAULT_CIPHERS == 1,
957+
"Test applies only to Python default ciphers")
958+
def test_python_ciphers(self):
959+
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
960+
ciphers = ctx.get_ciphers()
961+
for suite in ciphers:
962+
name = suite['name']
963+
self.assertNotIn("PSK", name)
964+
self.assertNotIn("SRP", name)
965+
self.assertNotIn("MD5", name)
966+
self.assertNotIn("RC4", name)
967+
self.assertNotIn("3DES", name)
968+
955969
@unittest.skipIf(ssl.OPENSSL_VERSION_INFO < (1, 0, 2, 0, 0), 'OpenSSL too old')
956970
def test_get_ciphers(self):
957971
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)

Modules/_ssl.c

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,31 @@ SSL_SESSION_get_ticket_lifetime_hint(const SSL_SESSION *s)
238238

239239
#endif /* OpenSSL < 1.1.0 or LibreSSL < 2.7.0 */
240240

241+
/* Default cipher suites */
242+
#ifndef PY_SSL_DEFAULT_CIPHERS
243+
#define PY_SSL_DEFAULT_CIPHERS 1
244+
#endif
245+
246+
#if PY_SSL_DEFAULT_CIPHERS == 0
247+
#ifndef PY_SSL_DEFAULT_CIPHER_STRING
248+
#error "Py_SSL_DEFAULT_CIPHERS 0 needs Py_SSL_DEFAULT_CIPHER_STRING"
249+
#endif
250+
#elif PY_SSL_DEFAULT_CIPHERS == 1
251+
/* Python custom selection of sensible ciper suites
252+
* DEFAULT: OpenSSL's default cipher list. Since 1.0.2 the list is in sensible order.
253+
* !aNULL:!eNULL: really no NULL ciphers
254+
* !MD5:!3DES:!DES:!RC4:!IDEA:!SEED: no weak or broken algorithms on old OpenSSL versions.
255+
* !aDSS: no authentication with discrete logarithm DSA algorithm
256+
* !SRP:!PSK: no secure remote password or pre-shared key authentication
257+
*/
258+
#define PY_SSL_DEFAULT_CIPHER_STRING "DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK"
259+
#elif PY_SSL_DEFAULT_CIPHERS == 2
260+
/* Ignored in SSLContext constructor, only used to as _ssl.DEFAULT_CIPHER_STRING */
261+
#define PY_SSL_DEFAULT_CIPHER_STRING SSL_DEFAULT_CIPHER_LIST
262+
#else
263+
#error "Unsupported PY_SSL_DEFAULT_CIPHERS"
264+
#endif
265+
241266

242267
enum py_ssl_error {
243268
/* these mirror ssl.h */
@@ -2861,7 +2886,12 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version)
28612886
/* A bare minimum cipher list without completely broken cipher suites.
28622887
* It's far from perfect but gives users a better head start. */
28632888
if (proto_version != PY_SSL_VERSION_SSL2) {
2864-
result = SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL:!eNULL:!MD5");
2889+
#if PY_SSL_DEFAULT_CIPHERS == 2
2890+
/* stick to OpenSSL's default settings */
2891+
result = 1;
2892+
#else
2893+
result = SSL_CTX_set_cipher_list(ctx, PY_SSL_DEFAULT_CIPHER_STRING);
2894+
#endif
28652895
} else {
28662896
/* SSLv2 needs MD5 */
28672897
result = SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL:!eNULL");
@@ -5457,6 +5487,9 @@ PyInit__ssl(void)
54575487
(PyObject *)&PySSLSession_Type) != 0)
54585488
return NULL;
54595489

5490+
PyModule_AddStringConstant(m, "_DEFAULT_CIPHERS",
5491+
PY_SSL_DEFAULT_CIPHER_STRING);
5492+
54605493
PyModule_AddIntConstant(m, "SSL_ERROR_ZERO_RETURN",
54615494
PY_SSL_ERROR_ZERO_RETURN);
54625495
PyModule_AddIntConstant(m, "SSL_ERROR_WANT_READ",

configure.ac

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5663,6 +5663,42 @@ if test "$have_getrandom" = yes; then
56635663
[Define to 1 if the getrandom() function is available])
56645664
fi
56655665

5666+
# ssl module default cipher suite string
5667+
AH_TEMPLATE(PY_SSL_DEFAULT_CIPHERS,
5668+
[Default cipher suites list for ssl module.
5669+
1: Python's preferred selection, 2: leave OpenSSL defaults untouched, 0: custom string])
5670+
AH_TEMPLATE(PY_SSL_DEFAULT_CIPHER_STRING,
5671+
[Cipher suite string for PY_SSL_DEFAULT_CIPHERS=0]
5672+
)
5673+
AC_MSG_CHECKING(for --with-ssl-default-suites)
5674+
AC_ARG_WITH(ssl-default-suites,
5675+
AS_HELP_STRING([--with-ssl-default-suites=@<:@python|openssl|STRING@:>@],
5676+
[Override default cipher suites string,
5677+
python: use Python's preferred selection (default),
5678+
openssl: leave OpenSSL's defaults untouched,
5679+
STRING: use a custom string,
5680+
PROTOCOL_SSLv2 ignores the setting]),
5681+
[
5682+
AC_MSG_RESULT($withval)
5683+
case "$withval" in
5684+
python)
5685+
AC_DEFINE(PY_SSL_DEFAULT_CIPHERS, 1)
5686+
;;
5687+
openssl)
5688+
AC_DEFINE(PY_SSL_DEFAULT_CIPHERS, 2)
5689+
;;
5690+
*)
5691+
AC_DEFINE(PY_SSL_DEFAULT_CIPHERS, 0)
5692+
AC_DEFINE_UNQUOTED(PY_SSL_DEFAULT_CIPHER_STRING, "$withval")
5693+
;;
5694+
esac
5695+
],
5696+
[
5697+
AC_MSG_RESULT(python)
5698+
AC_DEFINE(PY_SSL_DEFAULT_CIPHERS, 1)
5699+
])
5700+
5701+
56665702
# generate output files
56675703
AC_CONFIG_FILES(Makefile.pre Modules/Setup.config Misc/python.pc Misc/python-config.sh)
56685704
AC_CONFIG_FILES([Modules/ld_so_aix], [chmod +x Modules/ld_so_aix])

0 commit comments

Comments
 (0)