Skip to content

Commit baebeb5

Browse files
committed
bpo-29136: Add TLS 1.3 support
TLS 1.3 introduces a new, distinct set of cipher suites. The TLS 1.3 cipher suites don't overlap with cipher suites from TLS 1.2 and earlier. Since Python sets its own set of permitted ciphers, TLS 1.3 handshake will fail as soon as OpenSSL 1.1.1 is released. Let's enable the common AES-GCM and ChaCha20 suites. Additionally the flag OP_NO_TLSv1_3 is added. It defaults to 0 (no op) with OpenSSL prior to 1.1.1. This allows applications to opt-out from TLS 1.3 now. Signed-off-by: Christian Heimes <christian@python.org>
1 parent a6a4dc8 commit baebeb5

File tree

5 files changed

+77
-3
lines changed

5 files changed

+77
-3
lines changed

Doc/library/ssl.rst

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,11 +193,11 @@ instead.
193193
.. table::
194194

195195
======================== ============ ============ ============= ========= =========== ===========
196-
*client* / **server** **SSLv2** **SSLv3** **TLS** **TLSv1** **TLSv1.1** **TLSv1.2**
196+
*client* / **server** **SSLv2** **SSLv3** **TLS** [3]_ **TLSv1** **TLSv1.1** **TLSv1.2**
197197
------------------------ ------------ ------------ ------------- --------- ----------- -----------
198198
*SSLv2* yes no no [1]_ no no no
199199
*SSLv3* no yes no [2]_ no no no
200-
*TLS* (*SSLv23*) no [1]_ no [2]_ yes yes yes yes
200+
*TLS* (*SSLv23*) [3]_ no [1]_ no [2]_ yes yes yes yes
201201
*TLSv1* no no yes yes no no
202202
*TLSv1.1* no no yes no yes no
203203
*TLSv1.2* no no yes no no yes
@@ -206,6 +206,8 @@ instead.
206206
.. rubric:: Footnotes
207207
.. [1] :class:`SSLContext` disables SSLv2 with :data:`OP_NO_SSLv2` by default.
208208
.. [2] :class:`SSLContext` disables SSLv3 with :data:`OP_NO_SSLv3` by default.
209+
.. [3] TLS 1.3 protocol is will be available with :data:`PROTOCOL_TLS` in
210+
OpenSSL >= 1.1.1. There is no dedicated PROTOCOL for just TLSv1.3.
209211
210212
.. note::
211213

@@ -294,6 +296,11 @@ purposes.
294296

295297
3DES was dropped from the default cipher string.
296298

299+
.. versionchanged:: 3.7
300+
301+
TLS 1.3 cipher suites TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384,
302+
and TLS_CHACHA20_POLY1305_SHA256 were added to the default cipher string.
303+
297304

298305
Random generation
299306
^^^^^^^^^^^^^^^^^
@@ -764,6 +771,15 @@ Constants
764771

765772
.. versionadded:: 3.4
766773

774+
.. data:: OP_NO_TLSv1_3
775+
776+
Prevents a TLSv1.3 connection. This option is only applicable in conjunction
777+
with :const:`PROTOCOL_TLS`. It prevents the peers from choosing TLSv1.3 as
778+
the protocol version. TLS 1.3 is available with OpenSSL 1.1.1 or later.
779+
With older versions, the flag defaults to *0*.
780+
781+
.. versionadded:: 3.7
782+
767783
.. data:: OP_CIPHER_SERVER_PREFERENCE
768784

769785
Use the server's cipher ordering preference, rather than the client's.
@@ -838,6 +854,12 @@ Constants
838854

839855
.. versionadded:: 3.3
840856

857+
.. data:: HAS_TLSv1_3
858+
859+
Whether the OpenSSL library has built-in support for the TLS 1.3 protocol.
860+
861+
.. versionadded:: 3.7
862+
841863
.. data:: CHANNEL_BINDING_TYPES
842864

843865
List of supported TLS channel binding types. Strings in this list

Lib/ssl.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@
115115
pass
116116

117117

118-
from _ssl import HAS_SNI, HAS_ECDH, HAS_NPN, HAS_ALPN
118+
from _ssl import HAS_SNI, HAS_ECDH, HAS_NPN, HAS_ALPN, HAS_TLSv1_3
119119
from _ssl import _OPENSSL_API_VERSION
120120

121121

@@ -178,6 +178,7 @@
178178
# (OpenSSL's default setting is 'DEFAULT:!aNULL:!eNULL')
179179
# Enable a better set of ciphers by default
180180
# This list has been explicitly chosen to:
181+
# * TLS 1.3 ChaCha20 and AES-GCM cipher suites
181182
# * Prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE)
182183
# * Prefer ECDHE over DHE for better performance
183184
# * Prefer AEAD over CBC for better performance and security
@@ -189,13 +190,16 @@
189190
# * Disable NULL authentication, NULL encryption, 3DES and MD5 MACs
190191
# for security reasons
191192
_DEFAULT_CIPHERS = (
193+
'TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:'
194+
'TLS13-AES-128-GCM-SHA256:'
192195
'ECDH+AESGCM:ECDH+CHACHA20:DH+AESGCM:DH+CHACHA20:ECDH+AES256:DH+AES256:'
193196
'ECDH+AES128:DH+AES:ECDH+HIGH:DH+HIGH:RSA+AESGCM:RSA+AES:RSA+HIGH:'
194197
'!aNULL:!eNULL:!MD5:!3DES'
195198
)
196199

197200
# Restricted and more secure ciphers for the server side
198201
# This list has been explicitly chosen to:
202+
# * TLS 1.3 ChaCha20 and AES-GCM cipher suites
199203
# * Prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE)
200204
# * Prefer ECDHE over DHE for better performance
201205
# * Prefer AEAD over CBC for better performance and security
@@ -206,6 +210,8 @@
206210
# * Disable NULL authentication, NULL encryption, MD5 MACs, DSS, RC4, and
207211
# 3DES for security reasons
208212
_RESTRICTED_SERVER_CIPHERS = (
213+
'TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:'
214+
'TLS13-AES-128-GCM-SHA256:'
209215
'ECDH+AESGCM:ECDH+CHACHA20:DH+AESGCM:DH+CHACHA20:ECDH+AES256:DH+AES256:'
210216
'ECDH+AES128:DH+AES:ECDH+HIGH:DH+HIGH:RSA+AESGCM:RSA+AES:RSA+HIGH:'
211217
'!aNULL:!eNULL:!MD5:!DSS:!RC4:!3DES'

Lib/test/test_ssl.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,13 @@ def test_constants(self):
166166
ssl.OP_NO_COMPRESSION
167167
self.assertIn(ssl.HAS_SNI, {True, False})
168168
self.assertIn(ssl.HAS_ECDH, {True, False})
169+
ssl.OP_NO_SSLv2
170+
ssl.OP_NO_SSLv3
171+
ssl.OP_NO_TLSv1
172+
ssl.OP_NO_TLSv1_3
173+
if ssl.OPENSSL_VERSION_INFO >= (1, 0, 1):
174+
ssl.OP_NO_TLSv1_1
175+
ssl.OP_NO_TLSv1_2
169176

170177
def test_str_for_enums(self):
171178
# Make sure that the PROTOCOL_* constants have enum-like string
@@ -3088,12 +3095,33 @@ def test_version_basic(self):
30883095
self.assertEqual(s.version(), 'TLSv1')
30893096
self.assertIs(s.version(), None)
30903097

3098+
@unittest.skipUnless(ssl.HAS_TLSv1_3,
3099+
"test requires TLSv1.3 enabled OpenSSL")
3100+
def test_tls1_3(self):
3101+
context = ssl.SSLContext(ssl.PROTOCOL_TLS)
3102+
context.load_cert_chain(CERTFILE)
3103+
# disable all but TLS 1.3
3104+
context.options |= (
3105+
ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_TLSv1_2
3106+
)
3107+
with ThreadedEchoServer(context=context) as server:
3108+
with context.wrap_socket(socket.socket()) as s:
3109+
s.connect((HOST, server.port))
3110+
self.assertIn(s.cipher()[0], [
3111+
'TLS13-AES-256-GCM-SHA384',
3112+
'TLS13-CHACHA20-POLY1305-SHA256',
3113+
'TLS13-AES-128-GCM-SHA256',
3114+
])
3115+
30913116
@unittest.skipUnless(ssl.HAS_ECDH, "test requires ECDH-enabled OpenSSL")
30923117
def test_default_ecdh_curve(self):
30933118
# Issue #21015: elliptic curve-based Diffie Hellman key exchange
30943119
# should be enabled by default on SSL contexts.
30953120
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
30963121
context.load_cert_chain(CERTFILE)
3122+
# TLSv1.3 defaults to PFS key agreement and no longer has KEA in
3123+
# cipher name.
3124+
context.options |= ssl.OP_NO_TLSv1_3
30973125
# Prior to OpenSSL 1.0.0, ECDH ciphers have to be enabled
30983126
# explicitly using the 'ECCdraft' cipher alias. Otherwise,
30993127
# our default cipher list should prefer ECDH-based ciphers
@@ -3522,6 +3550,10 @@ def test_session_handling(self):
35223550
context2.load_verify_locations(CERTFILE)
35233551
context2.load_cert_chain(CERTFILE)
35243552

3553+
# TODO: session reuse does not work with TLS 1.3
3554+
context.options |= ssl.OP_NO_TLSv1_3
3555+
context2.options |= ssl.OP_NO_TLSv1_3
3556+
35253557
server = ThreadedEchoServer(context=context, chatty=False)
35263558
with server:
35273559
with context.wrap_socket(socket.socket()) as s:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add TLS 1.3 cipher suites and OP_NO_TLSv1_3.

Modules/_ssl.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5350,6 +5350,11 @@ PyInit__ssl(void)
53505350
#if HAVE_TLSv1_2
53515351
PyModule_AddIntConstant(m, "OP_NO_TLSv1_1", SSL_OP_NO_TLSv1_1);
53525352
PyModule_AddIntConstant(m, "OP_NO_TLSv1_2", SSL_OP_NO_TLSv1_2);
5353+
#endif
5354+
#ifdef SSL_OP_NO_TLSv1_3
5355+
PyModule_AddIntConstant(m, "OP_NO_TLSv1_3", SSL_OP_NO_TLSv1_3);
5356+
#else
5357+
PyModule_AddIntConstant(m, "OP_NO_TLSv1_3", 0);
53535358
#endif
53545359
PyModule_AddIntConstant(m, "OP_CIPHER_SERVER_PREFERENCE",
53555360
SSL_OP_CIPHER_SERVER_PREFERENCE);
@@ -5399,6 +5404,14 @@ PyInit__ssl(void)
53995404
Py_INCREF(r);
54005405
PyModule_AddObject(m, "HAS_ALPN", r);
54015406

5407+
#if defined(TLS1_3_VERSION) && !defined(OPENSSL_NO_TLS1_3)
5408+
r = Py_True;
5409+
#else
5410+
r = Py_False;
5411+
#endif
5412+
Py_INCREF(r);
5413+
PyModule_AddObject(m, "HAS_TLSv1_3", r);
5414+
54025415
/* Mappings for error codes */
54035416
err_codes_to_names = PyDict_New();
54045417
err_names_to_codes = PyDict_New();

0 commit comments

Comments
 (0)