Ticket #6024: tls_notify_6204_v4.patch

File tls_notify_6204_v4.patch, 10.4 KB (added by Andy Lutomirski, 7 years ago)

Further fixes -- applies on top of svn://svn.twistedmatrix.com/svn/Twisted/branches/tls-handshake-notification-6024@40726 bbbe8e31-12d6-0310-92fd-ac37d47ddeeb

  • doc/core/howto/ssl.xhtml

    diff --git doc/core/howto/ssl.xhtml doc/core/howto/ssl.xhtml
    index 3d6450a..f60e764 100644
    if __name__ == '__main__': 
    350350      certificate to the server for validation.
    351351    </p>
    352352
     353    <h2>Detecting handshake completion</h2>
     354
     355    <p>
     356      You can detect a completed handshake using the
     357      <code class="API">twisted.internet.interfaces.ISSLTransport.whenHandshakeDone</code>.
     358      This can be used to identify clients at the beginning of a connection,
     359      for example.
     360    </p>
     361
     362    <pre class="python">
     363from twisted.internet import ssl, reactor
     364from twisted.internet.interfaces import ISSLTransport
     365from twisted.internet.protocol import Factory, Protocol
     366
     367class WhoAreYou(Protocol):
     368    def connectionMade(self):
     369        if ISSLTransport.providedBy(self.transport):
     370            self.transport.whenHandshakeDone().addCallbacks(self._handshakeDone,
     371                                                            self._handshakeFail)
     372        else:
     373            print 'Client is not using SSL'
     374            self.transport.loseConnection()
     375
     376    def _handshakeDone(self, _):
     377        print 'Client handshake succeeded: %r' % \
     378            (self.transport.getPeerCertificate().get_subject())
     379        self.transport.loseConnection()
     380
     381    def _handshakeFail(self, reason):
     382        print 'Client handshake failed: %r' % reason.value
     383        self.transport.loseConnection()
     384
     385if __name__ == '__main__':
     386    factory = Factory()
     387    factory.protocol = WhoAreYou
     388
     389    with open("keys/ca.pem") as certAuthCertFile:
     390        certAuthCert = ssl.Certificate.loadPEM(certAuthCertFile.read())
     391
     392    with open("keys/server.key") as keyFile:
     393        with open("keys/server.crt") as certFile:
     394            serverCert = ssl.PrivateCertificate.loadPEM(
     395                keyFile.read() + certFile.read())
     396
     397    contextFactory = serverCert.options(certAuthCert)
     398
     399    reactor.listenTCP(8000, factory)
     400    reactor.listenSSL(8001, factory, contextFactory)
     401    reactor.run()
     402    </pre>
     403
    353404    <h2>Other facilities</h2>
    354405
    355406    <p><code class="API">twisted.protocols.amp</code> supports encrypted
  • twisted/protocols/test/test_tls.py

    diff --git twisted/protocols/test/test_tls.py twisted/protocols/test/test_tls.py
    index c5651d6..9a35400 100644
    class TLSMemoryBIOTests(TestCase): 
    334334                connectionDeferred])
    335335
    336336
     337    def test_handshakeInterrupted(self):
     338        """
     339        L{TLSMemoryBIOProtocol.whenHandshakeDone} should correctly handle
     340        a failure in the middle of a handshake.
     341        """
     342        clientConnectionLost = Deferred()
     343        clientFactory = ClientFactory()
     344        clientFactory.protocol = Protocol
     345
     346        clientContextFactory = TestContextFactory()
     347        wrapperFactory = TLSMemoryBIOFactory(
     348            clientContextFactory, True, clientFactory)
     349        sslClientProtocol = wrapperFactory.buildProtocol(None)
     350        handshakeDeferred = sslClientProtocol.whenHandshakeDone()
     351
     352        # The server will disconnect as soon as it receives some data.
     353        sslServerProtocol = AccumulatingProtocol(1)
     354
     355        loopbackAsync(sslServerProtocol, sslClientProtocol)
     356
     357        return self.assertFailure(handshakeDeferred, Error)
     358
     359
    337360    def test_notifyAfterSuccessfulHandshake(self):
    338361        """
    339362        Calling L{TLSMemoryBIOProtocol.whenHandshakeDone} after a
    class TLSMemoryBIOTests(TestCase): 
    357380        Calling L{TLSMemoryBIOProtocol.whenHandshakeDone} after a
    358381        failed handshake should work.
    359382        """
    360         clientConnectionLost = Deferred()
    361383        clientFactory = ClientFactory()
    362384        clientFactory.protocol = Protocol
    363385
    class TLSMemoryBIOTests(TestCase): 
    366388            clientContextFactory, True, clientFactory)
    367389        sslClientProtocol = wrapperFactory.buildProtocol(None)
    368390
    369         serverConnectionLost = Deferred()
    370391        serverFactory = ServerFactory()
    371392        serverFactory.protocol = Protocol
    372393
    class TLSMemoryBIOTests(TestCase): 
    427448        sslServerProtocol = wrapperFactory.buildProtocol(None)
    428449
    429450        connectionDeferred = loopbackAsync(sslServerProtocol, sslClientProtocol)
    430         result = Deferred()
    431451
    432452        def checkSide(side):
    433453            return self.assertFailure(side.whenHandshakeDone(), Error)
    class TLSMemoryBIOTests(TestCase): 
    532552        wrapperFactory = TLSMemoryBIOFactory(
    533553            clientContextFactory, True, clientFactory)
    534554        sslClientProtocol = wrapperFactory.buildProtocol(None)
    535         handshakeDeferred = sslClientProtocol.whenHandshakeDone()
    536555
    537556        serverProtocol = AccumulatingProtocol(len(bytes))
    538557        serverFactory = ServerFactory()
    class TLSMemoryBIOTests(TestCase): 
    801820        return handshakeDeferred
    802821
    803822
    804     def test_connectionLostOnlyAfterUnderlyingCloses(self):
     823    def _test_connectionLostOnlyAfterUnderlyingCloses(self, handshake_ok):
    805824        """
    806825        The user protocol's connectionLost is only called when transport
    807826        underlying TLS is disconnected.
     827
     828        @param handshake_ok: L{True} if the handshake should succeed; else
     829                             L{False}.
    808830        """
    809831        class LostProtocol(Protocol):
    810832            disconnected = None
    class TLSMemoryBIOTests(TestCase): 
    820842        # Pretend TLS shutdown finished cleanly; the underlying transport
    821843        # should be told to close, but the user protocol should not yet be
    822844        # notified:
    823         tlsProtocol._tlsShutdownFinished(None)
     845        if handshake_ok:
     846            errmsg = "ono"
     847            tlsProtocol._tlsShutdownFinished(None)
     848        else:
     849            errmsg = "handshake error"
     850            tlsProtocol._tlsShutdownFinished(Failure(ConnectionLost(errmsg)))
    824851        self.assertEqual(transport.disconnecting, True)
    825852        self.assertEqual(protocol.disconnected, None)
    826853
    class TLSMemoryBIOTests(TestCase): 
    828855        # notified with the given reason (since TLS closed cleanly):
    829856        tlsProtocol.connectionLost(Failure(ConnectionLost("ono")))
    830857        self.assertTrue(protocol.disconnected.check(ConnectionLost))
    831         self.assertEqual(protocol.disconnected.value.args, ("ono",))
     858        self.assertEqual(protocol.disconnected.value.args, (errmsg,))
     859
     860
     861    def test_connectionLostOnlyAfterUnderlyingClosesHandshakeOK(self):
     862        """
     863        If the handshake succeeds and then the underlying connection closes,
     864        the underlying failure should be propagated.
     865        """
     866        return self._test_connectionLostOnlyAfterUnderlyingCloses(True)
     867
     868
     869    def test_connectionLostOnlyAfterUnderlyingClosesHandshakeFailed(self):
     870        return self._test_connectionLostOnlyAfterUnderlyingCloses(False)
    832871
    833872
    834873    def test_loseConnectionTwice(self):
  • twisted/protocols/tls.py

    diff --git twisted/protocols/tls.py twisted/protocols/tls.py
    index 57a38b5..face561 100644
    class TLSMemoryBIOProtocol(ProtocolWrapper): 
    325325
    326326
    327327    def whenHandshakeDone(self):
     328        """
     329        See L{twisted.internet.interfaces.ISSLTransport.whenHandshakeDone}.
     330        """
    328331        d = defer.Deferred()
    329332        if self._handshakeDone:
    330333            if self._handshakeError is None:
    class TLSMemoryBIOProtocol(ProtocolWrapper): 
    351354            except WantWriteError:
    352355                self._flushSendBIO()
    353356                # And try again immediately
    354             except Error as e:
     357            except Error:
    355358                self._tlsShutdownFinished(Failure())
    356359                return
    357360            else:
    358                 self._handshakeSucceeded()
     361                self._handshakeFinished(None)
    359362                return
    360363
    361364
    362     def _handshakeSucceeded(self):
     365    def _handshakeFinished(self, error):
    363366        """
    364367        Mark the handshake done and notify everyone.  It's okay to call
    365368        this more than once.
     369
     370        @param error: A L{twisted.python.failure.Failure} object to indicate
     371                      failure or None to indicate success.
    366372        """
    367373        if not self._handshakeDone:
    368374            self._handshakeDone = True
     375            self._handshakeError = error
    369376            deferreds = self._handshakeDeferreds
    370377            self._handshakeDeferreds = None
    371378            for d in deferreds:
    372                 d.callback(None)
     379                if error is None:
     380                    d.callback(None)
     381                else:
     382                    d.errback(error)
    373383
    374384
    375385    def _flushSendBIO(self):
    class TLSMemoryBIOProtocol(ProtocolWrapper): 
    487497        Called when TLS connection has gone away; tell underlying transport to
    488498        disconnect.
    489499        """
    490         if not self._handshakeDone:
    491             # This is a handshake failure (either an explicit failure from
    492             # OpenSSL or an implicit failure due to a dropped transport
    493             # connection).
    494             #
    495             # Note: Some testcases evilly call _tlsShutdownFinished(None)
    496             # before the handshake finishes.  This can't happen in real life
    497             # (none of the call sites allow it), so it's okay that we'll
    498             # crash if there's actually anyone waiting for notification
    499             # of the handshake result.
    500             self._handshakeDone = True
    501             self._handshakeError = reason
    502 
    503             deferreds = self._handshakeDeferreds
    504             self._handshakeDeferreds = None
    505             for d in deferreds:
    506                 d.errback(reason)
    507 
     500        self._handshakeFinished(reason)
    508501        self._reason = reason
    509502        self._lostTLSConnection = True
    510503        # Using loseConnection causes the application protocol's
    class TLSMemoryBIOProtocol(ProtocolWrapper): 
    601594            else:
    602595                # SSL_write can transparently complete a handshake.  If we
    603596                # get here, then we're done handshaking.
    604                 self._handshakeSucceeded()
     597                self._handshakeFinished(None)
    605598                self._flushSendBIO()
    606599                alreadySent += sent
    607600
  • twisted/topfiles/6204.feature

    diff --git twisted/topfiles/6204.feature twisted/topfiles/6204.feature
    index 46cfabb..ec6dc14 100644
     
    1 twisted.internet.interfaces.ISSLTransport now has a whenHandshakeDone method to request notification when the handshake succeeds or fails.
     1twisted.internet.interfaces.ISSLTransport now has a whenHandshakeDone method to request notification when the SSL handshake succeeds or fails.