Openssl 使用此代码在TLS上运行TLS时,为什么会出现握手失败?

Openssl 使用此代码在TLS上运行TLS时,为什么会出现握手失败?,openssl,twisted,ssl,pyopenssl,Openssl,Twisted,Ssl,Pyopenssl,我尝试使用twisted.protocols.TLS实现一个协议,该协议可以在TLS上运行TLS,这是一个使用内存BIO的OpenSSL接口 我将其实现为一个协议包装器,它看起来很像一个常规的TCP传输,但是它有startTLS和stopTLS方法分别用于添加和删除一层TLS。这适用于TLS的第一层。如果我在“本机”Twisted TLS传输上运行它,它也可以正常工作。但是,如果我尝试使用此包装器提供的startTLS方法添加第二个TLS层,则会立即出现握手错误,并且连接最终处于未知的不可用状态

我尝试使用
twisted.protocols.TLS
实现一个协议,该协议可以在TLS上运行TLS,这是一个使用内存BIO的OpenSSL接口

我将其实现为一个协议包装器,它看起来很像一个常规的TCP传输,但是它有
startTLS
stopTLS
方法分别用于添加和删除一层TLS。这适用于TLS的第一层。如果我在“本机”Twisted TLS传输上运行它,它也可以正常工作。但是,如果我尝试使用此包装器提供的
startTLS
方法添加第二个TLS层,则会立即出现握手错误,并且连接最终处于未知的不可用状态

包装器和使其工作的两个助手如下所示:

from twisted.python.components import proxyForInterface
from twisted.internet.error import ConnectionDone
from twisted.internet.interfaces import ITCPTransport, IProtocol
from twisted.protocols.tls import TLSMemoryBIOFactory, TLSMemoryBIOProtocol
from twisted.protocols.policies import ProtocolWrapper, WrappingFactory

class TransportWithoutDisconnection(proxyForInterface(ITCPTransport)):
    """
    A proxy for a normal transport that disables actually closing the connection.
    This is necessary so that when TLSMemoryBIOProtocol notices the SSL EOF it
    doesn't actually close the underlying connection.

    All methods except loseConnection are proxied directly to the real transport.
    """
    def loseConnection(self):
        pass


class ProtocolWithoutConnectionLost(proxyForInterface(IProtocol)):
    """
    A proxy for a normal protocol which captures clean connection shutdown
    notification and sends it to the TLS stacking code instead of the protocol.
    When TLS is shutdown cleanly, this notification will arrive.  Instead of telling
    the protocol that the entire connection is gone, the notification is used to
    unstack the TLS code in OnionProtocol and hidden from the wrapped protocol.  Any
    other kind of connection shutdown (SSL handshake error, network hiccups, etc) are
    treated as real problems and propagated to the wrapped protocol.
    """
    def connectionLost(self, reason):
        if reason.check(ConnectionDone):
            self.onion._stopped()
        else:
            super(ProtocolWithoutConnectionLost, self).connectionLost(reason)


class OnionProtocol(ProtocolWrapper):
    """
    OnionProtocol is both a transport and a protocol.  As a protocol, it can run over
    any other ITransport.  As a transport, it implements stackable TLS.  That is,
    whatever application traffic is generated by the protocol running on top of
    OnionProtocol can be encapsulated in a TLS conversation.  Or, that TLS conversation
    can be encapsulated in another TLS conversation.  Or **that** TLS conversation can
    be encapsulated in yet *another* TLS conversation.

    Each layer of TLS can use different connection parameters, such as keys, ciphers,
    certificate requirements, etc.  At the remote end of this connection, each has to
    be decrypted separately, starting at the outermost and working in.  OnionProtocol
    can do this itself, of course, just as it can encrypt each layer starting with the
    innermost.
    """
    def makeConnection(self, transport):
        self._tlsStack = []
        ProtocolWrapper.makeConnection(self, transport)


    def startTLS(self, contextFactory, client, bytes=None):
        """
        Add a layer of TLS, with SSL parameters defined by the given contextFactory.

        If *client* is True, this side of the connection will be an SSL client.
        Otherwise it will be an SSL server.

        If extra bytes which may be (or almost certainly are) part of the SSL handshake
        were received by the protocol running on top of OnionProtocol, they must be
        passed here as the **bytes** parameter.
        """
        # First, create a wrapper around the application-level protocol
        # (wrappedProtocol) which can catch connectionLost and tell this OnionProtocol 
        # about it.  This is necessary to pop from _tlsStack when the outermost TLS
        # layer stops.
        connLost = ProtocolWithoutConnectionLost(self.wrappedProtocol)
        connLost.onion = self
        # Construct a new TLS layer, delivering events and application data to the
        # wrapper just created.
        tlsProtocol = TLSMemoryBIOProtocol(None, connLost, False)
        tlsProtocol.factory = TLSMemoryBIOFactory(contextFactory, client, None)

        # Push the previous transport and protocol onto the stack so they can be
        # retrieved when this new TLS layer stops.
        self._tlsStack.append((self.transport, self.wrappedProtocol))

        # Create a transport for the new TLS layer to talk to.  This is a passthrough
        # to the OnionProtocol's current transport, except for capturing loseConnection
        # to avoid really closing the underlying connection.
        transport = TransportWithoutDisconnection(self.transport)

        # Make the new TLS layer the current protocol and transport.
        self.wrappedProtocol = self.transport = tlsProtocol

        # And connect the new TLS layer to the previous outermost transport.
        self.transport.makeConnection(transport)

        # If the application accidentally got some bytes from the TLS handshake, deliver
        # them to the new TLS layer.
        if bytes is not None:
            self.wrappedProtocol.dataReceived(bytes)


    def stopTLS(self):
        """
        Remove a layer of TLS.
        """
        # Just tell the current TLS layer to shut down.  When it has done so, we'll get
        # notification in *_stopped*.
        self.transport.loseConnection()


    def _stopped(self):
        # A TLS layer has completely shut down.  Throw it away and move back to the
        # TLS layer it was wrapping (or possibly back to the original non-TLS
        # transport).
        self.transport, self.wrappedProtocol = self._tlsStack.pop()
我有一些简单的客户端和服务器程序,可以从launchpad(
bzr branch lp:~exarkun/+junk/onion
)获得。当我使用它两次调用上面的
startTLS
方法时,没有对
stopTLS
的中间调用,就会出现这个OpenSSL错误:

OpenSSL.SSL.Error: [('SSL routines', 'SSL23_GET_SERVER_HELLO', 'unknown protocol')]

为什么会出现问题?

如果第二层设备有能力,您可能需要在启动前通知远程设备您希望启动一个环境并为第二层分配资源。

如果两层使用相同的TLS参数,并且连接到同一主机,那么您可能对两层加密都使用相同的密钥对。尝试为嵌套层使用不同的密钥对,例如通过隧道传输到第三个主机/端口。i、 e:
localhost:30000
(客户端)->
localhost:8080
(TLS第1层使用密钥对A)->
localhost:8081
(TLS第2层使用密钥对B)。

OnOnInProtocol至少有两个问题:

  • 最里面的
    TLSMemoryBIOProtocol
    变成了
    wrappedProtocol
    ,而它应该是最外面的
  • ProtocolWithoutConnectionLost
    不会弹出任何
    TLSMemoryBIOProtocol
    ononinprotocol堆栈,因为
    connectionLost
    仅在
    FileDescriptor
    s
    doRead
    doWrite
    方法返回断开原因后调用
  • 如果不改变
    oninonprotocol
    管理其堆栈的方式,我们就无法解决第一个问题,而在找到新的堆栈实现之前,我们无法解决第二个问题。毫不奇怪,正确的设计是Twisted中数据如何流动的直接结果,因此我们将从一些数据流分析开始

    Twisted表示与或的实例建立的连接。由于我们程序中唯一的交互性发生了,所以我们只考虑数据流流向“代码>客户机< /Client >实例。

    让我们使用一个最小的
    LineReceiver
    客户端来预热,该客户端回显从端口9999上的本地服务器接收到的线路:

    from twisted.protocols import basic
    from twisted.internet import defer, endpoints, protocol, task
    
    class LineReceiver(basic.LineReceiver):
        def lineReceived(self, line):
            self.sendLine(line)
    
    def main(reactor):
        clientEndpoint = endpoints.clientFromString(
            reactor, "tcp:localhost:9999")
        connected = clientEndpoint.connect(
            protocol.ClientFactory.forProtocol(LineReceiver))
        def waitForever(_):
            return defer.Deferred()
        return connected.addCallback(waitForever)
    
    task.react(main)
    
    一旦建立了连接,
    客户端
    将成为我们的
    线路接收器
    协议的传输,并协调输入和输出:

    来自服务器的新数据导致反应器调用
    客户机的
    doRead
    方法,然后将接收到的数据传递给
    LineReceiver
    dataReceived
    方法。最后,
    LineReceiver.dataReceived
    在至少有一行可用时调用
    LineReceiver.lineReceived

    我们的应用程序通过调用
    LineReceiver.sendLine
    将一行数据发送回服务器。这将调用绑定到协议实例的传输上的
    write
    ,该实例与处理传入数据的
    客户端
    实例相同
    Client.write
    安排反应堆发送的数据,而
    Client.doWrite
    实际上通过套接字发送数据

    我们已经准备好查看从不调用
    startTLS
    的用户的行为:

    OnInclient
    s被包装在中,这是我们尝试嵌套TLS的关键。作为的一个子类,
    oninonprotocol
    的一个实例是一种协议传输三明治;它以协议的形式呈现给一个较低级别的传输,并以协议的传输形式呈现给一个协议,它通过一个在连接时由一个代理建立的伪装来包装

    现在,
    Client.doRead
    调用
    oninprotocol.dataReceived
    ,它将数据代理到
    oninclient
    。作为
    oninclient
    的传输,
    oninprotocol.write
    接受要从
    oninclient.sendLine
    发送的行,并将它们向下代理到
    客户端
    ,这是它自己的传输。这是
    ProtocolWrapper
    及其包装协议和自身传输之间的正常交互,因此数据自然地在每个协议之间流动,没有任何问题

    OnionProtocol.startTLS
    做了一些不同的事情。它试图在已建立的协议传输对之间插入一个新的
    协议包装器
    ,它恰好是一个。这似乎很容易:一个
    ProtocolWrapper
    将上层协议存储为它的,和
    startTLS
    应该能够注入一个新的
    TLSMemoryBIOProtocol
    ,通过在实例自身的
    wrappedProtocol
    transport
    上修补该实例,将
    OnIncluent
    封装到连接中:

    def startTLS(self):
        ...
        connLost = ProtocolWithoutConnectionLost(self.wrappedProtocol)
        connLost.onion = self
        # Construct a new TLS layer, delivering events and application data to the
        # wrapper just created.
        tlsProtocol = TLSMemoryBIOProtocol(None, connLost, False)
    
        # Push the previous transport and protocol onto the stack so they can be
        # retrieved when this new TLS layer stops.
        self._tlsStack.append((self.transport, self.wrappedProtocol))
        ...
        # Make the new TLS layer the current protocol and transport.
        self.wrappedProtocol = self.transport = tlsProtocol
    
    下面是第一次调用
    startTLS
    后的数据流:

    正如预期的那样,发送到OnInProtocol.dataReceived的新数据将路由到存储在
    \u tlsStack
    上的
    TLSMemoryBIOProtocol
    ,该协议将解密的明文传递给
    OnIncluent.dataReceived
    oninclient.sendLine
    还将其数据传递给
    TLSMemoryBIOProtocol.write
    ,后者对其进行加密,并将生成的密文发送给
    oninprotocol。
    
        self.wrappedProtocol = self.transport = tlsProtocol
    
    from twisted.python.components import proxyForInterface
    from twisted.internet.interfaces import ITCPTransport
    from twisted.protocols.tls import TLSMemoryBIOFactory, TLSMemoryBIOProtocol
    from twisted.protocols.policies import ProtocolWrapper, WrappingFactory
    
    
    class PopOnDisconnectTransport(proxyForInterface(ITCPTransport)):
        """
        L{TLSMemoryBIOProtocol.loseConnection} shuts down the TLS session
        and calls its own transport's C{loseConnection}.  A zero-length
        read also calls the transport's C{loseConnection}.  This proxy
        uses that behavior to invoke a C{pop} callback when a session has
        ended.  The callback is invoked exactly once because
        C{loseConnection} must be idempotent.
        """
        def __init__(self, pop, **kwargs):
            super(PopOnDisconnectTransport, self).__init__(**kwargs)
            self._pop = pop
    
        def loseConnection(self):
            self._pop()
            self._pop = lambda: None
    
    
    class OnionProtocol(ProtocolWrapper):
        """
        OnionProtocol is both a transport and a protocol.  As a protocol,
        it can run over any other ITransport.  As a transport, it
        implements stackable TLS.  That is, whatever application traffic
        is generated by the protocol running on top of OnionProtocol can
        be encapsulated in a TLS conversation.  Or, that TLS conversation
        can be encapsulated in another TLS conversation.  Or **that** TLS
        conversation can be encapsulated in yet *another* TLS
        conversation.
    
        Each layer of TLS can use different connection parameters, such as
        keys, ciphers, certificate requirements, etc.  At the remote end
        of this connection, each has to be decrypted separately, starting
        at the outermost and working in.  OnionProtocol can do this
        itself, of course, just as it can encrypt each layer starting with
        the innermost.
        """
    
        def __init__(self, *args, **kwargs):
            ProtocolWrapper.__init__(self, *args, **kwargs)
            # The application level protocol is the sentinel at the tail
            # of the linked list stack of protocol wrappers.  The stack
            # begins at this sentinel.
            self._tailProtocol = self._currentProtocol = self.wrappedProtocol
    
    
        def startTLS(self, contextFactory, client, bytes=None):
            """
            Add a layer of TLS, with SSL parameters defined by the given
            contextFactory.
    
            If *client* is True, this side of the connection will be an
            SSL client.  Otherwise it will be an SSL server.
    
            If extra bytes which may be (or almost certainly are) part of
            the SSL handshake were received by the protocol running on top
            of OnionProtocol, they must be passed here as the **bytes**
            parameter.
            """
            # The newest TLS session is spliced in between the previous
            # and the application protocol at the tail end of the list.
            tlsProtocol = TLSMemoryBIOProtocol(None, self._tailProtocol, False)
            tlsProtocol.factory = TLSMemoryBIOFactory(contextFactory, client, None)
    
            if self._currentProtocol is self._tailProtocol:
                # This is the first and thus outermost TLS session.  The
                # transport is the immutable sentinel that no startTLS or
                # stopTLS call will move within the linked list stack.
                # The wrappedProtocol will remain this outermost session
                # until it's terminated.
                self.wrappedProtocol = tlsProtocol
                nextTransport = PopOnDisconnectTransport(
                    original=self.transport,
                    pop=self._pop
                )
                # Store the proxied transport as the list's head sentinel
                # to enable an easy identity check in _pop.
                self._headTransport = nextTransport
            else:
                # This a later TLS session within the stack.  The previous
                # TLS session becomes its transport.
                nextTransport = PopOnDisconnectTransport(
                    original=self._currentProtocol,
                    pop=self._pop
                )
    
            # Splice the new TLS session into the linked list stack.
            # wrappedProtocol serves as the link, so the protocol at the
            # current position takes our new TLS session as its
            # wrappedProtocol.
            self._currentProtocol.wrappedProtocol = tlsProtocol
            # Move down one position in the linked list.
            self._currentProtocol = tlsProtocol
            # Expose the new, innermost TLS session as the transport to
            # the application protocol.
            self.transport = self._currentProtocol
            # Connect the new TLS session to the previous transport.  The
            # transport attribute also serves as the previous link.
            tlsProtocol.makeConnection(nextTransport)
    
            # Left over bytes are part of the latest handshake.  Pass them
            # on to the innermost TLS session.
            if bytes is not None:
                tlsProtocol.dataReceived(bytes)
    
    
        def stopTLS(self):
            self.transport.loseConnection()
    
    
        def _pop(self):
            pop = self._currentProtocol
            previous = pop.transport
            # If the previous link is the head sentinel, we've run out of
            # linked list.  Ensure that the application protocol, stored
            # as the tail sentinel, becomes the wrappedProtocol, and the
            # head sentinel, which is the underlying transport, becomes
            # the transport.
            if previous is self._headTransport:
                self._currentProtocol = self.wrappedProtocol = self._tailProtocol
                self.transport = previous
            else:
                # Splice out a protocol from the linked list stack.  The
                # previous transport is a PopOnDisconnectTransport proxy,
                # so first retrieve proxied object off its original
                # attribute.
                previousProtocol = previous.original
                # The previous protocol's next link becomes the popped
                # protocol's next link
                previousProtocol.wrappedProtocol = pop.wrappedProtocol
                # Move up one position in the linked list.
                self._currentProtocol = previousProtocol
                # Expose the new, innermost TLS session as the transport
                # to the application protocol.
                self.transport = self._currentProtocol
    
    
    
    class OnionFactory(WrappingFactory):
        """
        A L{WrappingFactory} that overrides
        L{WrappingFactory.registerProtocol} and
        L{WrappingFactory.unregisterProtocol}.  These methods store in and
        remove from a dictionary L{ProtocolWrapper} instances.  The
        C{transport} patching done as part of the linked-list management
        above causes the instances' hash to change, because the
        C{__hash__} is proxied through to the wrapped transport.  They're
        not essential to this program, so the easiest solution is to make
        them do nothing.
        """
        protocol = OnionProtocol
    
        def registerProtocol(self, protocol):
            pass
    
    
        def unregisterProtocol(self, protocol):
            pass