From cb1b319d7f1bcc7c1327f1eb52b286ef7a550429 Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Sun, 30 May 2021 12:27:15 +1000 Subject: [PATCH 1/3] Support asyncio loop.start_tls() for TLS in TLS --- Lib/asyncio/sslproto.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/asyncio/sslproto.py b/Lib/asyncio/sslproto.py index cad25b26539f5aa..24e9bb32022f4cd 100644 --- a/Lib/asyncio/sslproto.py +++ b/Lib/asyncio/sslproto.py @@ -283,6 +283,7 @@ def feed_appdata(self, data, offset=0): class _SSLProtocolTransport(transports._FlowControlMixin, transports.Transport): + _start_tls_compatible = True _sendfile_compatible = constants._SendfileMode.FALLBACK def __init__(self, loop, ssl_protocol): From 14791eb033ba760deac31fcf51721a2f44ce0431 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Fri, 11 Jun 2021 04:34:41 +0000 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Core and Builtins/2021-06-11-04-34-41.bpo-37179.7Gs2-B.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2021-06-11-04-34-41.bpo-37179.7Gs2-B.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-06-11-04-34-41.bpo-37179.7Gs2-B.rst b/Misc/NEWS.d/next/Core and Builtins/2021-06-11-04-34-41.bpo-37179.7Gs2-B.rst new file mode 100644 index 000000000000000..62136b816d010b0 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2021-06-11-04-34-41.bpo-37179.7Gs2-B.rst @@ -0,0 +1 @@ +Allow asyncio StartTLS in an existing TLS connection. \ No newline at end of file From 0aee4f89863a2bf47c567cdf21b93850d8f66f47 Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Wed, 13 Oct 2021 13:29:39 +1000 Subject: [PATCH 3/3] Added test for nested start_tls --- Lib/test/test_asyncio/test_sslproto.py | 107 +++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/Lib/test/test_asyncio/test_sslproto.py b/Lib/test/test_asyncio/test_sslproto.py index a47e43dd4cadb4b..bee165a50f804a6 100644 --- a/Lib/test/test_asyncio/test_sslproto.py +++ b/Lib/test/test_asyncio/test_sslproto.py @@ -281,6 +281,113 @@ async def client(addr): support.gc_collect() self.assertIsNone(client_context()) + def test_start_tls_client_nested(self): + HELLO_MSG = b'1' * self.PAYLOAD_SIZE + + server_context = test_utils.simple_server_sslcontext() + server_inner_context = test_utils.simple_server_sslcontext() + server_in = ssl.MemoryBIO() + server_out = ssl.MemoryBIO() + server_obj = server_inner_context.wrap_bio(server_in, server_out, + server_side=True) + client_context = test_utils.simple_client_sslcontext() + + def serve(sock): + sock.settimeout(self.TIMEOUT) + + sock.start_tls(server_context, server_side=True) + + want_read = True + + while True: + server_in.write(sock.recv(8192)) + + try: + server_obj.do_handshake() + except ssl.SSLWantReadError: + pass + else: + want_read = False + + sock.sendall(server_out.read()) + if not want_read: + break + + server_obj.write(b'O') + sock.sendall(server_out.read()) + + data = b"" + while len(data) < len(HELLO_MSG): + chunk = sock.recv(8192) + server_in.write(chunk) + + try: + while True: + data += server_obj.read() + except ssl.SSLWantReadError: + pass + + self.assertEqual(data, HELLO_MSG) + + with self.assertRaises(ssl.SSLWantReadError): + server_obj.unwrap() + sock.sendall(server_out.read()) + + server_in.write(sock.recv(8192)) + server_obj.unwrap() + + sock.shutdown(socket.SHUT_RDWR) + sock.close() + + class ClientProto(asyncio.Protocol): + def __init__(self, on_data, on_eof): + self.on_data = on_data + self.on_eof = on_eof + self.con_made_cnt = 0 + + def connection_made(proto, tr): + proto.con_made_cnt += 1 + # Ensure connection_made gets called only once. + self.assertEqual(proto.con_made_cnt, 1) + + def data_received(self, data): + self.on_data.set_result(data) + + def eof_received(self): + self.on_eof.set_result(True) + + async def client(addr): + await asyncio.sleep(0.5) + + tr, proto = await self.loop.create_connection(asyncio.Protocol, + *addr) + new_tr = await self.loop.start_tls(tr, proto, client_context) + + on_data = self.loop.create_future() + on_eof = self.loop.create_future() + inner_proto = ClientProto(on_data, on_eof) + inner_tr = await self.loop.start_tls(new_tr, inner_proto, + client_context) + + self.assertEqual(await on_data, b'O') + inner_tr.write(HELLO_MSG) + + inner_tr.close() + + await on_eof + + new_tr.close() + + with self.tcp_server(serve, timeout=self.TIMEOUT) as srv: + self.loop.run_until_complete( + asyncio.wait_for(client(srv.addr), + timeout=support.SHORT_TIMEOUT)) + + # No garbage is left if SSL is closed uncleanly + client_context = weakref.ref(client_context) + support.gc_collect() + self.assertIsNone(client_context()) + def test_create_connection_memory_leak(self): HELLO_MSG = b'1' * self.PAYLOAD_SIZE