Skip to content

Commit 2a4ee8a

Browse files
authored
bpo-32947: Fixes for TLS 1.3 and OpenSSL 1.1.1 (GH-8761)
Backport of TLS 1.3 related fixes from 3.7. Misc fixes and workarounds for compatibility with OpenSSL 1.1.1 from git master and TLS 1.3 support. With OpenSSL 1.1.1, Python negotiates TLS 1.3 by default. Some test cases only apply to TLS 1.2. OpenSSL 1.1.1 has added a new option OP_ENABLE_MIDDLEBOX_COMPAT for TLS 1.3. The feature is enabled by default for maximum compatibility with broken middle boxes. Users should be able to disable the hack and CPython's test suite needs it to verify default options Signed-off-by: Christian Heimes <[email protected]>
1 parent 4ee06b3 commit 2a4ee8a

File tree

5 files changed

+42
-7
lines changed

5 files changed

+42
-7
lines changed

Doc/library/ssl.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,15 @@ Constants
821821

822822
.. versionadded:: 3.3
823823

824+
.. data:: OP_ENABLE_MIDDLEBOX_COMPAT
825+
826+
Send dummy Change Cipher Spec (CCS) messages in TLS 1.3 handshake to make
827+
a TLS 1.3 connection look more like a TLS 1.2 connection.
828+
829+
This option is only available with OpenSSL 1.1.1 and later.
830+
831+
.. versionadded:: 3.6.7
832+
824833
.. data:: OP_NO_COMPRESSION
825834

826835
Disable compression on the SSL channel. This is useful if the application

Lib/test/test_asyncio/test_events.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1187,7 +1187,11 @@ def test_create_server_ssl_match_failed(self):
11871187
self.loop.run_until_complete(f_c)
11881188

11891189
# close connection
1190-
proto.transport.close()
1190+
# transport may be None with TLS 1.3, because connection is
1191+
# interrupted, server is unable to send session tickets, and
1192+
# transport is closed.
1193+
if proto.transport is not None:
1194+
proto.transport.close()
11911195
server.close()
11921196

11931197
def test_legacy_create_server_ssl_match_failed(self):

Lib/test/test_ssl.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ def data_file(*name):
8989
OP_SINGLE_DH_USE = getattr(ssl, "OP_SINGLE_DH_USE", 0)
9090
OP_SINGLE_ECDH_USE = getattr(ssl, "OP_SINGLE_ECDH_USE", 0)
9191
OP_CIPHER_SERVER_PREFERENCE = getattr(ssl, "OP_CIPHER_SERVER_PREFERENCE", 0)
92+
OP_ENABLE_MIDDLEBOX_COMPAT = getattr(ssl, "OP_ENABLE_MIDDLEBOX_COMPAT", 0)
9293

9394

9495
def handle_error(prefix):
@@ -181,8 +182,8 @@ def test_constants(self):
181182
ssl.OP_NO_TLSv1
182183
ssl.OP_NO_TLSv1_3
183184
if ssl.OPENSSL_VERSION_INFO >= (1, 0, 1):
184-
ssl.OP_NO_TLSv1_1
185-
ssl.OP_NO_TLSv1_2
185+
ssl.OP_NO_TLSv1_1
186+
ssl.OP_NO_TLSv1_2
186187

187188
def test_str_for_enums(self):
188189
# Make sure that the PROTOCOL_* constants have enum-like string
@@ -899,12 +900,13 @@ def test_get_ciphers(self):
899900

900901
@skip_if_broken_ubuntu_ssl
901902
def test_options(self):
902-
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
903+
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
903904
# OP_ALL | OP_NO_SSLv2 | OP_NO_SSLv3 is the default value
904905
default = (ssl.OP_ALL | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3)
905906
# SSLContext also enables these by default
906907
default |= (OP_NO_COMPRESSION | OP_CIPHER_SERVER_PREFERENCE |
907-
OP_SINGLE_DH_USE | OP_SINGLE_ECDH_USE)
908+
OP_SINGLE_DH_USE | OP_SINGLE_ECDH_USE |
909+
OP_ENABLE_MIDDLEBOX_COMPAT)
908910
self.assertEqual(default, ctx.options)
909911
ctx.options |= ssl.OP_NO_TLSv1
910912
self.assertEqual(default | ssl.OP_NO_TLSv1, ctx.options)
@@ -1857,11 +1859,23 @@ def wrap_conn(self):
18571859
self.sock, server_side=True)
18581860
self.server.selected_npn_protocols.append(self.sslconn.selected_npn_protocol())
18591861
self.server.selected_alpn_protocols.append(self.sslconn.selected_alpn_protocol())
1860-
except (ssl.SSLError, ConnectionResetError, OSError) as e:
1862+
except (ConnectionResetError, BrokenPipeError) as e:
18611863
# We treat ConnectionResetError as though it were an
18621864
# SSLError - OpenSSL on Ubuntu abruptly closes the
18631865
# connection when asked to use an unsupported protocol.
18641866
#
1867+
# BrokenPipeError is raised in TLS 1.3 mode, when OpenSSL
1868+
# tries to send session tickets after handshake.
1869+
# https://github.com/openssl/openssl/issues/6342
1870+
self.server.conn_errors.append(str(e))
1871+
if self.server.chatty:
1872+
handle_error(
1873+
"\n server: bad connection attempt from " + repr(
1874+
self.addr) + ":\n")
1875+
self.running = False
1876+
self.close()
1877+
return False
1878+
except (ssl.SSLError, OSError) as e:
18651879
# OSError may occur with wrong protocols, e.g. both
18661880
# sides use PROTOCOL_TLS_SERVER.
18671881
#
@@ -3042,14 +3056,16 @@ def serve():
30423056
# Block on the accept and wait on the connection to close.
30433057
evt.set()
30443058
remote, peer = server.accept()
3045-
remote.recv(1)
3059+
remote.send(remote.recv(4))
30463060

30473061
t = threading.Thread(target=serve)
30483062
t.start()
30493063
# Client wait until server setup and perform a connect.
30503064
evt.wait()
30513065
client = context.wrap_socket(socket.socket())
30523066
client.connect((host, port))
3067+
client.send(b'data')
3068+
client.recv()
30533069
client_addr = client.getsockname()
30543070
client.close()
30553071
t.join()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add OP_ENABLE_MIDDLEBOX_COMPAT and test workaround for TLSv1.3 for future
2+
compatibility with OpenSSL 1.1.1.

Modules/_ssl.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5486,6 +5486,10 @@ PyInit__ssl(void)
54865486
PyModule_AddIntConstant(m, "OP_NO_COMPRESSION",
54875487
SSL_OP_NO_COMPRESSION);
54885488
#endif
5489+
#ifdef SSL_OP_ENABLE_MIDDLEBOX_COMPAT
5490+
PyModule_AddIntConstant(m, "OP_ENABLE_MIDDLEBOX_COMPAT",
5491+
SSL_OP_ENABLE_MIDDLEBOX_COMPAT);
5492+
#endif
54895493

54905494
#if HAVE_SNI
54915495
r = Py_True;

0 commit comments

Comments
 (0)