Skip to content

Commit a526cc7

Browse files
committed
bpo-33618: Enable TLS 1.3 in tests
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 a526cc7

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)