Skip to content

Commit 529525f

Browse files
authored
bpo-33618: Enable TLS 1.3 in tests (GH-7079)
TLS 1.3 behaves slightly different than TLS 1.2. Session tickets and TLS client cert auth are now handled after the initialy handshake. Tests now either send/recv data to trigger session and client certs. Or tests ignore ConnectionResetError / BrokenPipeError on the server side to handle clients that force-close the socket fd. To test TLS 1.3, OpenSSL 1.1.1-pre7-dev (git master + OpenSSL PR openssl/openssl#6340) is required. Signed-off-by: Christian Heimes <[email protected]>
1 parent 28b9178 commit 529525f

File tree

9 files changed

+142
-46
lines changed

9 files changed

+142
-46
lines changed

Doc/library/ssl.rst

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2587,7 +2587,33 @@ successful call of :func:`~ssl.RAND_add`, :func:`~ssl.RAND_bytes` or
25872587
:func:`~ssl.RAND_pseudo_bytes` is sufficient.
25882588

25892589

2590-
.. ssl-libressl:
2590+
.. _ssl-tlsv1_3:
2591+
2592+
TLS 1.3
2593+
-------
2594+
2595+
.. versionadded:: 3.7
2596+
2597+
Python has provisional and experimental support for TLS 1.3 with OpenSSL
2598+
1.1.1. The new protocol behaves slightly differently than previous version
2599+
of TLS/SSL. Some new TLS 1.3 features are not yet available.
2600+
2601+
- TLS 1.3 uses a disjunct set of cipher suites. All AES-GCM and
2602+
ChaCha20 cipher suites are enabled by default. The method
2603+
:meth:`SSLContext.set_ciphers` cannot enable or disable any TLS 1.3
2604+
ciphers yet, but :meth:`SSLContext.get_cipers` returns them.
2605+
- Session tickets are no longer sent as part of the initial handshake and
2606+
are handled differently. :attr:`SSLSocket.session` and :class:`SSLSession`
2607+
are not compatible with TLS 1.3.
2608+
- Client-side certificates are also no longer verified during the initial
2609+
handshake. A server can request a certificate at any time. Clients
2610+
process certificate requests while they send or receive application data
2611+
from the server.
2612+
- TLS 1.3 features like early data, deferred TLS client cert request,
2613+
signature algorithm configuration, and rekeying are not supported yet.
2614+
2615+
2616+
.. _ssl-libressl:
25912617

25922618
LibreSSL support
25932619
----------------

Doc/whatsnew/3.7.rst

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1244,8 +1244,8 @@ Host name validation can be customized with
12441244
.. note::
12451245
The improved host name check requires a *libssl* implementation compatible
12461246
with OpenSSL 1.0.2 or 1.1. Consequently, OpenSSL 0.9.8 and 1.0.1 are no
1247-
longer supported and LibreSSL is temporarily not supported until it gains
1248-
the necessary OpenSSL 1.0.2 APIs.
1247+
longer supported. The ssl module is mostly compatible with LibreSSL 2.7.2
1248+
and newer.
12491249

12501250
The ``ssl`` module no longer sends IP addresses in SNI TLS extension.
12511251
(Contributed by Christian Heimes in :issue:`32185`.)
@@ -1270,8 +1270,12 @@ rather than the U-label form (``"pythön.org"``). (Contributed by
12701270
Nathaniel J. Smith and Christian Heimes in :issue:`28414`.)
12711271

12721272
The ``ssl`` module has preliminary and experimental support for TLS 1.3 and
1273-
OpenSSL 1.1.1. (Contributed by Christian Heimes in :issue:`32947`,
1274-
:issue:`20995`, :issue:`29136`, and :issue:`30622`)
1273+
OpenSSL 1.1.1. At the time of Python 3.7.0 release, OpenSSL 1.1.1 is still
1274+
under development and TLS 1.3 hasn't been finalized yet. The TLS 1.3
1275+
handshake and protocol behaves slightly differently than TLS 1.2 and earlier,
1276+
see :ref:`ssl-tlsv1_3`.
1277+
(Contributed by Christian Heimes in :issue:`32947`, :issue:`20995`,
1278+
:issue:`29136`, :issue:`30622` and :issue:`33618`)
12751279

12761280
:class:`~ssl.SSLSocket` and :class:`~ssl.SSLObject` no longer have a public
12771281
constructor. Direct instantiation was never a documented and supported

Lib/test/test_asyncio/test_sslproto.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,8 @@ def test_start_tls_server_1(self):
251251

252252
server_context = test_utils.simple_server_sslcontext()
253253
client_context = test_utils.simple_client_sslcontext()
254+
# TODO: fix TLSv1.3 support
255+
client_context.options |= ssl.OP_NO_TLSv1_3
254256

255257
def client(sock, addr):
256258
time.sleep(0.5)

Lib/test/test_asyncio/utils.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,6 @@ def simple_server_sslcontext():
7474
server_context.load_cert_chain(ONLYCERT, ONLYKEY)
7575
server_context.check_hostname = False
7676
server_context.verify_mode = ssl.CERT_NONE
77-
# TODO: fix TLSv1.3 support
78-
server_context.options |= ssl.OP_NO_TLSv1_3
7977
return server_context
8078

8179

Lib/test/test_ftplib.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ class DummyFTPServer(asyncore.dispatcher, threading.Thread):
257257
def __init__(self, address, af=socket.AF_INET):
258258
threading.Thread.__init__(self)
259259
asyncore.dispatcher.__init__(self)
260+
self.daemon = True
260261
self.create_socket(af, socket.SOCK_STREAM)
261262
self.bind(address)
262263
self.listen(5)
@@ -312,8 +313,6 @@ class SSLConnection(asyncore.dispatcher):
312313

313314
def secure_connection(self):
314315
context = ssl.SSLContext()
315-
# TODO: fix TLSv1.3 support
316-
context.options |= ssl.OP_NO_TLSv1_3
317316
context.load_cert_chain(CERTFILE)
318317
socket = context.wrap_socket(self.socket,
319318
suppress_ragged_eofs=False,
@@ -405,7 +404,7 @@ def handle_error(self):
405404

406405
def close(self):
407406
if (isinstance(self.socket, ssl.SSLSocket) and
408-
self.socket._sslobj is not None):
407+
self.socket._sslobj is not None):
409408
self._do_ssl_shutdown()
410409
else:
411410
super(SSLConnection, self).close()
@@ -910,8 +909,6 @@ def test_auth_issued_twice(self):
910909
def test_context(self):
911910
self.client.quit()
912911
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
913-
# TODO: fix TLSv1.3 support
914-
ctx.options |= ssl.OP_NO_TLSv1_3
915912
ctx.check_hostname = False
916913
ctx.verify_mode = ssl.CERT_NONE
917914
self.assertRaises(ValueError, ftplib.FTP_TLS, keyfile=CERTFILE,
@@ -944,8 +941,6 @@ def test_ccc(self):
944941
def test_check_hostname(self):
945942
self.client.quit()
946943
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
947-
# TODO: fix TLSv1.3 support
948-
ctx.options |= ssl.OP_NO_TLSv1_3
949944
self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
950945
self.assertEqual(ctx.check_hostname, True)
951946
ctx.load_verify_locations(CAFILE)
@@ -982,6 +977,7 @@ def setUp(self):
982977
self.sock.settimeout(20)
983978
self.port = support.bind_port(self.sock)
984979
self.server_thread = threading.Thread(target=self.server)
980+
self.server_thread.daemon = True
985981
self.server_thread.start()
986982
# Wait for the server to be ready.
987983
self.evt.wait()

Lib/test/test_poplib.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,6 @@ def cmd_stls(self, arg):
153153
if self.tls_active is False:
154154
self.push('+OK Begin TLS negotiation')
155155
context = ssl.SSLContext()
156-
# TODO: fix TLSv1.3 support
157-
context.options |= ssl.OP_NO_TLSv1_3
158156
context.load_cert_chain(CERTFILE)
159157
tls_sock = context.wrap_socket(self.socket,
160158
server_side=True,
@@ -206,6 +204,7 @@ class DummyPOP3Server(asyncore.dispatcher, threading.Thread):
206204
def __init__(self, address, af=socket.AF_INET):
207205
threading.Thread.__init__(self)
208206
asyncore.dispatcher.__init__(self)
207+
self.daemon = True
209208
self.create_socket(af, socket.SOCK_STREAM)
210209
self.bind(address)
211210
self.listen(5)
@@ -370,8 +369,6 @@ def test_stls(self):
370369
def test_stls_context(self):
371370
expected = b'+OK Begin TLS negotiation'
372371
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
373-
# TODO: fix TLSv1.3 support
374-
ctx.options |= ssl.OP_NO_TLSv1_3
375372
ctx.load_verify_locations(CAFILE)
376373
self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED)
377374
self.assertEqual(ctx.check_hostname, True)
@@ -412,8 +409,6 @@ def test__all__(self):
412409

413410
def test_context(self):
414411
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
415-
# TODO: fix TLSv1.3 support
416-
ctx.options |= ssl.OP_NO_TLSv1_3
417412
ctx.check_hostname = False
418413
ctx.verify_mode = ssl.CERT_NONE
419414
self.assertRaises(ValueError, poplib.POP3_SSL, self.server.host,
@@ -482,7 +477,7 @@ def setUp(self):
482477
self.sock.settimeout(60) # Safety net. Look issue 11812
483478
self.port = test_support.bind_port(self.sock)
484479
self.thread = threading.Thread(target=self.server, args=(self.evt,self.sock))
485-
self.thread.setDaemon(True)
480+
self.thread.daemon = True
486481
self.thread.start()
487482
self.evt.wait()
488483

Lib/test/test_ssl.py

Lines changed: 73 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1826,6 +1826,7 @@ def test_connect_capath(self):
18261826
s.connect(self.server_addr)
18271827
cert = s.getpeercert()
18281828
self.assertTrue(cert)
1829+
18291830
# Same with a bytes `capath` argument
18301831
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS)
18311832
ctx.verify_mode = ssl.CERT_REQUIRED
@@ -1841,8 +1842,6 @@ def test_connect_cadata(self):
18411842
der = ssl.PEM_cert_to_DER_cert(pem)
18421843
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS)
18431844
ctx.verify_mode = ssl.CERT_REQUIRED
1844-
# TODO: fix TLSv1.3 support
1845-
ctx.options |= ssl.OP_NO_TLSv1_3
18461845
ctx.load_verify_locations(cadata=pem)
18471846
with ctx.wrap_socket(socket.socket(socket.AF_INET)) as s:
18481847
s.connect(self.server_addr)
@@ -1852,8 +1851,6 @@ def test_connect_cadata(self):
18521851
# same with DER
18531852
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS)
18541853
ctx.verify_mode = ssl.CERT_REQUIRED
1855-
# TODO: fix TLSv1.3 support
1856-
ctx.options |= ssl.OP_NO_TLSv1_3
18571854
ctx.load_verify_locations(cadata=der)
18581855
with ctx.wrap_socket(socket.socket(socket.AF_INET)) as s:
18591856
s.connect(self.server_addr)
@@ -2109,11 +2106,21 @@ def wrap_conn(self):
21092106
self.sock, server_side=True)
21102107
self.server.selected_npn_protocols.append(self.sslconn.selected_npn_protocol())
21112108
self.server.selected_alpn_protocols.append(self.sslconn.selected_alpn_protocol())
2112-
except (ssl.SSLError, ConnectionResetError, OSError) as e:
2109+
except (ConnectionResetError, BrokenPipeError) as e:
21132110
# We treat ConnectionResetError as though it were an
21142111
# SSLError - OpenSSL on Ubuntu abruptly closes the
21152112
# connection when asked to use an unsupported protocol.
21162113
#
2114+
# BrokenPipeError is raised in TLS 1.3 mode, when OpenSSL
2115+
# tries to send session tickets after handshake.
2116+
# https://github.com/openssl/openssl/issues/6342
2117+
self.server.conn_errors.append(str(e))
2118+
if self.server.chatty:
2119+
handle_error("\n server: bad connection attempt from " + repr(self.addr) + ":\n")
2120+
self.running = False
2121+
self.close()
2122+
return False
2123+
except (ssl.SSLError, OSError) as e:
21172124
# OSError may occur with wrong protocols, e.g. both
21182125
# sides use PROTOCOL_TLS_SERVER.
21192126
#
@@ -2220,11 +2227,22 @@ def run(self):
22202227
sys.stdout.write(" server: read %r (%s), sending back %r (%s)...\n"
22212228
% (msg, ctype, msg.lower(), ctype))
22222229
self.write(msg.lower())
2230+
except ConnectionResetError:
2231+
# XXX: OpenSSL 1.1.1 sometimes raises ConnectionResetError
2232+
# when connection is not shut down gracefully.
2233+
if self.server.chatty and support.verbose:
2234+
sys.stdout.write(
2235+
" Connection reset by peer: {}\n".format(
2236+
self.addr)
2237+
)
2238+
self.close()
2239+
self.running = False
22232240
except OSError:
22242241
if self.server.chatty:
22252242
handle_error("Test server failure:\n")
22262243
self.close()
22272244
self.running = False
2245+
22282246
# normally, we'd just stop here, but for the test
22292247
# harness, we want to stop the server
22302248
self.server.stop()
@@ -2299,6 +2317,11 @@ def run(self):
22992317
pass
23002318
except KeyboardInterrupt:
23012319
self.stop()
2320+
except BaseException as e:
2321+
if support.verbose and self.chatty:
2322+
sys.stdout.write(
2323+
' connection handling failed: ' + repr(e) + '\n')
2324+
23022325
self.sock.close()
23032326

23042327
def stop(self):
@@ -2745,8 +2768,6 @@ def test_check_hostname_idn(self):
27452768

27462769
server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
27472770
server_context.load_cert_chain(IDNSANSFILE)
2748-
# TODO: fix TLSv1.3 support
2749-
server_context.options |= ssl.OP_NO_TLSv1_3
27502771

27512772
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
27522773
context.verify_mode = ssl.CERT_REQUIRED
@@ -2797,7 +2818,7 @@ def test_check_hostname_idn(self):
27972818
with self.assertRaises(ssl.CertificateError):
27982819
s.connect((HOST, server.port))
27992820

2800-
def test_wrong_cert(self):
2821+
def test_wrong_cert_tls12(self):
28012822
"""Connecting when the server rejects the client's certificate
28022823
28032824
Launch a server with CERT_REQUIRED, and check that trying to
@@ -2808,9 +2829,8 @@ def test_wrong_cert(self):
28082829
client_context.load_cert_chain(WRONG_CERT)
28092830
# require TLS client authentication
28102831
server_context.verify_mode = ssl.CERT_REQUIRED
2811-
# TODO: fix TLSv1.3 support
2812-
# With TLS 1.3, test fails with exception in server thread
2813-
server_context.options |= ssl.OP_NO_TLSv1_3
2832+
# TLS 1.3 has different handshake
2833+
client_context.maximum_version = ssl.TLSVersion.TLSv1_2
28142834

28152835
server = ThreadedEchoServer(
28162836
context=server_context, chatty=True, connectionchatty=True,
@@ -2835,6 +2855,36 @@ def test_wrong_cert(self):
28352855
else:
28362856
self.fail("Use of invalid cert should have failed!")
28372857

2858+
@unittest.skipUnless(ssl.HAS_TLSv1_3, "Test needs TLS 1.3")
2859+
def test_wrong_cert_tls13(self):
2860+
client_context, server_context, hostname = testing_context()
2861+
client_context.load_cert_chain(WRONG_CERT)
2862+
server_context.verify_mode = ssl.CERT_REQUIRED
2863+
server_context.minimum_version = ssl.TLSVersion.TLSv1_3
2864+
client_context.minimum_version = ssl.TLSVersion.TLSv1_3
2865+
2866+
server = ThreadedEchoServer(
2867+
context=server_context, chatty=True, connectionchatty=True,
2868+
)
2869+
with server, \
2870+
client_context.wrap_socket(socket.socket(),
2871+
server_hostname=hostname) as s:
2872+
# TLS 1.3 perform client cert exchange after handshake
2873+
s.connect((HOST, server.port))
2874+
try:
2875+
s.write(b'data')
2876+
s.read(4)
2877+
except ssl.SSLError as e:
2878+
if support.verbose:
2879+
sys.stdout.write("\nSSLError is %r\n" % e)
2880+
except OSError as e:
2881+
if e.errno != errno.ECONNRESET:
2882+
raise
2883+
if support.verbose:
2884+
sys.stdout.write("\nsocket.error is %r\n" % e)
2885+
else:
2886+
self.fail("Use of invalid cert should have failed!")
2887+
28382888
def test_rude_shutdown(self):
28392889
"""A brutal shutdown of an SSL server should raise an OSError
28402890
in the client when attempting handshake.
@@ -3405,14 +3455,16 @@ def serve():
34053455
# Block on the accept and wait on the connection to close.
34063456
evt.set()
34073457
remote, peer = server.accept()
3408-
remote.recv(1)
3458+
remote.send(remote.recv(4))
34093459

34103460
t = threading.Thread(target=serve)
34113461
t.start()
34123462
# Client wait until server setup and perform a connect.
34133463
evt.wait()
34143464
client = context.wrap_socket(socket.socket())
34153465
client.connect((host, port))
3466+
client.send(b'data')
3467+
client.recv()
34163468
client_addr = client.getsockname()
34173469
client.close()
34183470
t.join()
@@ -3465,7 +3517,7 @@ def test_version_basic(self):
34653517
self.assertIs(s.version(), None)
34663518
self.assertIs(s._sslobj, None)
34673519
s.connect((HOST, server.port))
3468-
if ssl.OPENSSL_VERSION_INFO >= (1, 1, 1):
3520+
if IS_OPENSSL_1_1_1 and ssl.HAS_TLSv1_3:
34693521
self.assertEqual(s.version(), 'TLSv1.3')
34703522
elif ssl.OPENSSL_VERSION_INFO >= (1, 0, 2):
34713523
self.assertEqual(s.version(), 'TLSv1.2')
@@ -3574,8 +3626,6 @@ def test_tls_unique_channel_binding(self):
35743626
sys.stdout.write("\n")
35753627

35763628
client_context, server_context, hostname = testing_context()
3577-
# TODO: fix TLSv1.3 support
3578-
client_context.options |= ssl.OP_NO_TLSv1_3
35793629

35803630
server = ThreadedEchoServer(context=server_context,
35813631
chatty=True,
@@ -3594,7 +3644,10 @@ def test_tls_unique_channel_binding(self):
35943644

35953645
# check if it is sane
35963646
self.assertIsNotNone(cb_data)
3597-
self.assertEqual(len(cb_data), 12) # True for TLSv1
3647+
if s.version() == 'TLSv1.3':
3648+
self.assertEqual(len(cb_data), 48)
3649+
else:
3650+
self.assertEqual(len(cb_data), 12) # True for TLSv1
35983651

35993652
# and compare with the peers version
36003653
s.write(b"CB tls-unique\n")
@@ -3616,7 +3669,10 @@ def test_tls_unique_channel_binding(self):
36163669
# is it really unique
36173670
self.assertNotEqual(cb_data, new_cb_data)
36183671
self.assertIsNotNone(cb_data)
3619-
self.assertEqual(len(cb_data), 12) # True for TLSv1
3672+
if s.version() == 'TLSv1.3':
3673+
self.assertEqual(len(cb_data), 48)
3674+
else:
3675+
self.assertEqual(len(cb_data), 12) # True for TLSv1
36203676
s.write(b"CB tls-unique\n")
36213677
peer_data_repr = s.read().strip()
36223678
self.assertEqual(peer_data_repr,
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Finalize and document preliminary and experimental TLS 1.3 support with
2+
OpenSSL 1.1.1

0 commit comments

Comments
 (0)