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 <christian@python.org>
This commit is contained in:
Christian Heimes 2018-08-14 16:56:32 +02:00 committed by GitHub
parent 4ee06b3eef
commit 2a4ee8aa01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 42 additions and 7 deletions

View File

@ -821,6 +821,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

View File

@ -1187,7 +1187,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):

View File

@ -89,6 +89,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):
@ -181,8 +182,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
@ -899,12 +900,13 @@ class ContextTests(unittest.TestCase):
@skip_if_broken_ubuntu_ssl
def test_options(self):
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
# OP_ALL | OP_NO_SSLv2 | OP_NO_SSLv3 is the default value
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)
@ -1857,11 +1859,23 @@ if _have_threads:
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, OSError) 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.
#
@ -3042,7 +3056,7 @@ if _have_threads:
# 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()
@ -3050,6 +3064,8 @@ if _have_threads:
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()

View File

@ -0,0 +1,2 @@
Add OP_ENABLE_MIDDLEBOX_COMPAT and test workaround for TLSv1.3 for future
compatibility with OpenSSL 1.1.1.

View File

@ -5486,6 +5486,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;