简体   繁体   English

如何在已连接的 Python asyncio stream 上启用 TLS?

[英]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_chainserver_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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM