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
sdoRead
或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