From 170a614904febd14ff6cfd7a75c9bccc114b3948 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 14 Aug 2018 16:56:32 +0200 Subject: [PATCH] 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 Upstream-Status: Backport [https://github.com/python/cpython/commit/2a4ee8aa01d61b6a9c8e9c65c211e61bdb471826] Signed-off-by: Anuj Mittal --- Doc/library/ssl.rst | 9 ++++++ Lib/test/test_asyncio/test_events.py | 6 +++- Lib/test/test_ssl.py | 29 +++++++++++++++---- .../2018-08-14-08-57-01.bpo-32947.mqStVW.rst | 2 ++ Modules/_ssl.c | 4 +++ 5 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2018-08-14-08-57-01.bpo-32947.mqStVW.rst diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 29c5e94cf6..f63a3deec5 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -757,6 +757,15 @@ Constants .. versionadded:: 3.3 +.. data:: OP_ENABLE_MIDDLEBOX_COMPAT + + Send dummy Change Cipher Spec (CCS) messages in TLS 1.3 handshake to make + a TLS 1.3 connection look more like a TLS 1.2 connection. + + This option is only available with OpenSSL 1.1.1 and later. + + .. versionadded:: 3.6.7 + .. data:: OP_NO_COMPRESSION Disable compression on the SSL channel. This is useful if the application diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py index 492a84a231..6f208474b9 100644 --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py @@ -1169,7 +1169,11 @@ class EventLoopTestsMixin: self.loop.run_until_complete(f_c) # close connection - proto.transport.close() + # transport may be None with TLS 1.3, because connection is + # interrupted, server is unable to send session tickets, and + # transport is closed. + if proto.transport is not None: + proto.transport.close() server.close() def test_legacy_create_server_ssl_match_failed(self): diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 1acc12ec2d..a2e1d32a62 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -78,6 +78,7 @@ OP_NO_COMPRESSION = getattr(ssl, "OP_NO_COMPRESSION", 0) OP_SINGLE_DH_USE = getattr(ssl, "OP_SINGLE_DH_USE", 0) OP_SINGLE_ECDH_USE = getattr(ssl, "OP_SINGLE_ECDH_USE", 0) OP_CIPHER_SERVER_PREFERENCE = getattr(ssl, "OP_CIPHER_SERVER_PREFERENCE", 0) +OP_ENABLE_MIDDLEBOX_COMPAT = getattr(ssl, "OP_ENABLE_MIDDLEBOX_COMPAT", 0) def handle_error(prefix): @@ -155,8 +156,8 @@ class BasicSocketTests(unittest.TestCase): ssl.OP_NO_TLSv1 ssl.OP_NO_TLSv1_3 if ssl.OPENSSL_VERSION_INFO >= (1, 0, 1): - ssl.OP_NO_TLSv1_1 - ssl.OP_NO_TLSv1_2 + ssl.OP_NO_TLSv1_1 + ssl.OP_NO_TLSv1_2 def test_str_for_enums(self): # Make sure that the PROTOCOL_* constants have enum-like string @@ -854,7 +855,8 @@ class ContextTests(unittest.TestCase): default = (ssl.OP_ALL | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3) # SSLContext also enables these by default default |= (OP_NO_COMPRESSION | OP_CIPHER_SERVER_PREFERENCE | - OP_SINGLE_DH_USE | OP_SINGLE_ECDH_USE) + OP_SINGLE_DH_USE | OP_SINGLE_ECDH_USE | + OP_ENABLE_MIDDLEBOX_COMPAT) self.assertEqual(default, ctx.options) ctx.options |= ssl.OP_NO_TLSv1 self.assertEqual(default | ssl.OP_NO_TLSv1, ctx.options) @@ -1860,11 +1862,26 @@ else: self.sock, server_side=True) self.server.selected_npn_protocols.append(self.sslconn.selected_npn_protocol()) self.server.selected_alpn_protocols.append(self.sslconn.selected_alpn_protocol()) - except (ssl.SSLError, ConnectionResetError) as e: + except (ConnectionResetError, BrokenPipeError) as e: # We treat ConnectionResetError as though it were an # SSLError - OpenSSL on Ubuntu abruptly closes the # connection when asked to use an unsupported protocol. # + # BrokenPipeError is raised in TLS 1.3 mode, when OpenSSL + # tries to send session tickets after handshake. + # https://github.com/openssl/openssl/issues/6342 + self.server.conn_errors.append(str(e)) + if self.server.chatty: + handle_error( + "\n server: bad connection attempt from " + repr( + self.addr) + ":\n") + self.running = False + self.close() + return False + except (ssl.SSLError, OSError) as e: + # OSError may occur with wrong protocols, e.g. both + # sides use PROTOCOL_TLS_SERVER. + # # XXX Various errors can have happened here, for example # a mismatching protocol version, an invalid certificate, # or a low-level bug. This should be made more discriminating. @@ -2974,7 +2991,7 @@ else: # Block on the accept and wait on the connection to close. evt.set() remote, peer = server.accept() - remote.recv(1) + remote.send(remote.recv(4)) t = threading.Thread(target=serve) t.start() @@ -2982,6 +2999,8 @@ else: evt.wait() client = context.wrap_socket(socket.socket()) client.connect((host, port)) + client.send(b'data') + client.recv() client_addr = client.getsockname() client.close() t.join() diff --git a/Misc/NEWS.d/next/Library/2018-08-14-08-57-01.bpo-32947.mqStVW.rst b/Misc/NEWS.d/next/Library/2018-08-14-08-57-01.bpo-32947.mqStVW.rst new file mode 100644 index 0000000000..28de360c36 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-08-14-08-57-01.bpo-32947.mqStVW.rst @@ -0,0 +1,2 @@ +Add OP_ENABLE_MIDDLEBOX_COMPAT and test workaround for TLSv1.3 for future +compatibility with OpenSSL 1.1.1. diff --git a/Modules/_ssl.c b/Modules/_ssl.c index c71d89607c..eb123a87ba 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -4858,6 +4858,10 @@ PyInit__ssl(void) PyModule_AddIntConstant(m, "OP_NO_COMPRESSION", SSL_OP_NO_COMPRESSION); #endif +#ifdef SSL_OP_ENABLE_MIDDLEBOX_COMPAT + PyModule_AddIntConstant(m, "OP_ENABLE_MIDDLEBOX_COMPAT", + SSL_OP_ENABLE_MIDDLEBOX_COMPAT); +#endif #if HAVE_SNI r = Py_True; -- 2.17.1