This is a follow up of this question: SSL handshake failures when no data was sent over Twisted TLSConnection
I have implemented a simple SSL server that closes the connection as soon as the client is connected.
I am testing it with openssl and I got this handshake failure:
$ openssl s_client -connect localhost:12345
CONNECTED(00000003) 2329:error:140790E5:SSL routines:SSL23_WRITE
:ssl handshake failure:s23_lib.c:188:
The problem is that TLS.Connection.loseConnection
does not wait for the ongoing handshake to be done and just disconnects the client.
A callback attached to OpenSSL.SSL.Connection.do_handshake
would have been great... but unfortunately I don't know if this can be done... or how to do it.
Any hints in how I could test that a TLS handshake was done is much appreciated. Many thanks!
Here is the code
class ApplicationProtocol(Protocol):
'''Protocol that closes the connection when connection is made.'''
def connectionMade(self):
self.transport.loseConnection()
# Here is a barebone TLS Server
serverFactory = ServerFactory()
serverFactory.protocol = ApplicationProtocol
server_cert_path = 'server.pem'
serverContextFactory = DefaultOpenSSLContextFactory(
privateKeyFileName = server_cert_path,
certificateFileName = server_cert_path,
sslmethod=SSL.SSLv23_METHOD)
tlsFactory = TLSMemoryBIOFactory(serverContextFactory, False, serverFactory)
reactor.listenTCP(12345, tlsFactory)
#reactor.listenSSL(12345, serverFactory, serverContextFactory)
For now I solve this really dirty and not 100% valid.
def tls_lose_connection(self):
"""
Monkey patching for TLSMemoryBIOProtocol to wait for handshake to end,
before closing the connection.
Send a TLS close alert and close the underlying connection.
"""
def close_connection():
self.disconnecting = True
if not self._writeBlockedOnRead:
self._tlsConnection.shutdown()
self._flushSendBIO()
self.transport.loseConnection()
# If we don't know if the handshake was done, we wait for a bit
# and the close the connection.
# This is done to avoid closing the connection in the middle of a
# handshake.
if not self._handshakeDone:
reactor.callLater(0.5, close_connection)
else:
close_connection()
TLSMemoryBIOProtocol.loseConnection = tls_lose_connection
I'm providing code that implements Jean-Paul's answer.
class ProxyClientTLSContextFactory(ssl.ClientContextFactory):
isClient = 1
def getContext(self):
ctx = SSL.Context(SSL.TLSv1_METHOD)
logger = logging.GetLogger()
def infoCallback(conn, where, ret):
# conn is a OpenSSL.SSL.Connection
# where is a set of flags telling where in the handshake we are
# See http://www.openssl.org/docs/ssl/SSL_CTX_set_info_callback.html
logger.debug("infoCallback %s %d %d" % (conn, where, ret))
if where & SSL.SSL_CB_HANDSHAKE_START:
logger.debug("Handshake started")
if where & SSL.SSL_CB_HANDSHAKE_DONE:
logger.debug("Handshake done")
ctx.set_info_callback(infoCallback)
return ctx
The problem I ran into inside of infoCallback() is that I have no idea how to get from the SSL.Connection back to the associated Twisted Protocol instance.
What I'd like to do is invoke a callback on my Protocol instance after the connection has been made and the TLS handshake has been completed so I can be sure the certificate validation is to my liking before I proceed.
The SSL context object can be configured with an "info callback" - Context.set_info_callback. This is a wrapper around SSL_CTX_set_info_callback. The slightly more convenient (in this case) SSL_set_info_callback for specifying a callback for a single connection is not exposed by pyOpenSSL, unfortunately.
Amongst other things, the info callback is invoked when the handshake completes. With a few acrobatics, you should be able to turn this notification into a Deferred or some other callback onto the protocol.
See the pyOpenSSL set_info_callback documentation and the OpenSSL SSL_CTX_set_info_callback documentation for details.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With