[英]How do I enable TLS on an already connected Python asyncio stream?
I have a Python asyncio server written using the high level Streams API .我有一个使用高级Streams API编写的 Python 异步服务器。 I want to enable TLS on an already established connection, as in STARTTLS in the SMTP and IMAP protocols.我想在已建立的连接上启用 TLS,如 SMTP 和 IMAP 协议中的 STARTTLS。 The asyncio event loop has a start_tls() function (added in Python 3.7), but it takes a protocol and a transport rather than a stream. asyncio 事件循环有一个start_tls() function(在 Python 3.7 中添加),但它需要一个协议和一个传输而不是 ZF7B44CFAFD5C52223D7498196CA8。 The streams API does let you get the transport via StreamWriter.transport .流 API 确实允许您通过StreamWriter.transport获得传输。 But I don't see a way to change the transport, which would be required after calling start_tls().但是我看不到改变传输的方法,这在调用 start_tls() 之后是必需的。 Is it possible to use start_tls() with the streams API?是否可以将 start_tls() 与流 API 一起使用?
Looking at the code for the streams API you'll notice that StreamReader and StreamWriter both store their transport in an internal _transport
variable.查看流 API 的代码,您会注意到 StreamReader 和 StreamWriter 都将其传输存储在内部_transport
变量中。 It turns out that if you call start_tls() and then store the new transport in those variables it works just fine.事实证明,如果您调用 start_tls() 然后将新的传输存储在这些变量中,它就可以正常工作。 All the usual caveats with using an internal API apply of course.当然,使用内部 API 的所有常见警告都适用。 Here's what this looks like for a server.这是服务器的样子。 On a client I think you can just drop the load_cert_chain
and server_side
bits.在客户端上,我认为您可以删除load_cert_chain
和server_side
位。
transport = writer.transport
protocol = transport.get_protocol()
loop = asyncio.get_event_loop()
ssl_context = ssl.SSLContext()
ssl_context.load_cert_chain("/path/to/certchain", "/path/to/key")
new_transport = await loop.start_tls(
transport, protocol, ssl_context, server_side=True)
writer._transport = new_transport
reader._transport = new_transport
I needed to implement proxy support for asyncio streams of Python 3.8 and came up with following solution:我需要为 Python 3.8 的异步流实现代理支持,并提出以下解决方案:
import socket
import weakref
import asyncio
import typing as t
from ssl import create_default_context, Purpose, SSLContext
class TLSStreamReaderProtocol(asyncio.StreamReaderProtocol):
def upgrade_reader(self):
if self._stream_reader is not None:
self._stream_reader.set_exception(Exception('upgraded connection to TLS, this reader is obsolete now.'))
self._stream_reader_wr = weakref.ref(reader)
self._source_traceback = reader._source_traceback
async def open_tls_stream(host: str, port: int, ssl: t.Union[SSLContext, bool]=False):
# this does the same as loop.open_connection(), but TLS upgrade is done
# manually after connection be established.
loop = asyncio.get_running_loop()
reader = asyncio.StreamReader(limit=2**64, loop=loop)
protocol = TLSStreamReaderProtocol(reader, loop=loop)
transport, _ = await loop.create_connection(
lambda: protocol, host, port, family=socket.AF_INET
)
writer = asyncio.StreamWriter(transport, protocol, reader, loop)
# here you can use reader and writer for whatever you want, for example
# start a proxy connection and start TLS to target host later...
# now perform TLS upgrade
if ssl:
transport = await loop.start_tls(
transport,
protocol,
sslcontext=create_default_context(Purpose.SERVER_AUTH) if isinstance(ssl, bool) else ssl,
server_side=False,
server_hostname=host
)
reader = asyncio.StreamReader(limit=2**64, loop=loop)
protocol.upgrade_reader(reader) # update reader
protocol.connection_made(transport) # update transport
writer = asyncio.StreamWriter(transport, protocol, reader, loop) # update writer
return reader, writer
I'm using the following:我正在使用以下内容:
import asyncio
import ssl
async def tls_handshake(
reader: asyncio.StreamReader,
writer: asyncio.StreamWriter,
ssl_context: ssl.SSLContext,
):
"""
Upgrades the client connection to TLS/SSL.
Args:
reader: The reader of the client connection.
writer: The writer of the client connection.
ssl_context: The SSL context to use for the TLS/SSL handshake.
Notes:
For Python 3.6 to 3.9 you can use ``ssl.PROTOCOL_TLS`` for the SSL context. For
Python 3.10+ you need to either use ``ssl.PROTOCOL_TLS_CLIENT`` or
``ssl.PROTOCOL_TLS_SERVER`` depending on the role of the reader/writer.
"""
transport = writer.transport
protocol = transport.get_protocol()
loop = asyncio.get_event_loop()
new_transport = await loop.start_tls(
transport,
protocol,
ssl_context,
server_side=True,
)
reader._transport = new_transport
writer._transport = new_transport
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.