~justin-fathomdb/nova/justinsb-openstack-api-volumes

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/protocols/tls.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.protocols.test.test_tls,twisted.internet.test.test_tls,twisted.test.test_sslverify -*-
 
2
# Copyright (c) 2009 Twisted Matrix Laboratories.
 
3
# See LICENSE for details.
 
4
 
 
5
"""
 
6
Implementation of a TLS transport (L{ISSLTransport}) as an L{IProtocol}
 
7
layered on top of any L{ITransport} implementation, based on OpenSSL's
 
8
memory BIO features.
 
9
 
 
10
L{TLSMemoryBIOFactory} is a L{WrappingFactory} which wraps protocols created by
 
11
the factory it wraps with L{TLSMemoryBIOProtocol}.  L{TLSMemoryBIOProtocol}
 
12
intercedes between the underlying transport and the wrapped protocol to
 
13
implement SSL and TLS.  Typical usage of this module looks like this::
 
14
 
 
15
    from twisted.protocols.tls import TLSMemoryBIOFactory
 
16
    from twisted.internet.protocol import ServerFactory
 
17
    from twisted.internet.ssl import PrivateCertificate
 
18
    from twisted.internet import reactor
 
19
 
 
20
    from someapplication import ApplicationProtocol
 
21
 
 
22
    serverFactory = ServerFactory()
 
23
    serverFactory.protocol = ApplicationProtocol
 
24
    certificate = PrivateCertificate.loadPEM(certPEMData)
 
25
    contextFactory = certificate.options()
 
26
    tlsFactory = TLSMemoryBIOFactory(contextFactory, False, serverFactory)
 
27
    reactor.listenTCP(12345, tlsFactory)
 
28
    reactor.run()
 
29
 
 
30
Because the reactor's SSL and TLS APIs are likely implemented in a more
 
31
efficient way, it is more common to use them (see L{IReactorSSL} and
 
32
L{ITLSTransport}).  However, this API offers somewhat more flexibility; for
 
33
example, a L{TLSMemoryBIOProtocol} instance can use another instance of
 
34
L{TLSMemoryBIOProtocol} as its transport, yielding TLS over TLS - useful to
 
35
implement onion routing.  Or it can be used to run TLS over a UNIX socket,
 
36
or over stdio to a child process.
 
37
"""
 
38
 
 
39
 
 
40
from OpenSSL.SSL import Error, ZeroReturnError, WantReadError
 
41
from OpenSSL.SSL import TLSv1_METHOD, Context, Connection
 
42
 
 
43
try:
 
44
    Connection(Context(TLSv1_METHOD), None)
 
45
except TypeError, e:
 
46
    if str(e) != "argument must be an int, or have a fileno() method.":
 
47
        raise
 
48
    raise ImportError("twisted.protocols.tls requires pyOpenSSL 0.10 or newer.")
 
49
 
 
50
from zope.interface import implements
 
51
 
 
52
from twisted.python.failure import Failure
 
53
from twisted.internet.interfaces import ISystemHandle, ISSLTransport
 
54
from twisted.internet.main import CONNECTION_DONE, CONNECTION_LOST
 
55
from twisted.internet.protocol import Protocol
 
56
from twisted.protocols.policies import ProtocolWrapper, WrappingFactory
 
57
 
 
58
 
 
59
class TLSMemoryBIOProtocol(ProtocolWrapper):
 
60
    """
 
61
    L{TLSMemoryBIOProtocol} is a protocol wrapper which uses OpenSSL via a
 
62
    memory BIO to encrypt bytes written to it before sending them on to the
 
63
    underlying transport and decrypts bytes received from the underlying
 
64
    transport before delivering them to the wrapped protocol.
 
65
 
 
66
    @ivar _tlsConnection: The L{OpenSSL.SSL.Connection} instance which is
 
67
        encrypted and decrypting this connection.
 
68
 
 
69
    @ivar _lostConnection: A flag indicating whether connection loss has
 
70
        already been dealt with (C{True}) or not (C{False}).
 
71
 
 
72
    @ivar _writeBlockedOnRead: A flag indicating whether further writing must
 
73
        wait for data to be received (C{True}) or not (C{False}).
 
74
 
 
75
    @ivar _appSendBuffer: A C{list} of C{str} of application-level (cleartext)
 
76
        data which is waiting for C{_writeBlockedOnRead} to be reset to
 
77
        C{False} so it can be passed to and perhaps accepted by
 
78
        C{_tlsConnection.send}.
 
79
 
 
80
    @ivar _connectWrapped: A flag indicating whether or not to call
 
81
        C{makeConnection} on the wrapped protocol.  This is for the reactor's
 
82
        L{ITLSTransport.startTLS} implementation, since it has a protocol which
 
83
        it has already called C{makeConnection} on, and which has no interest
 
84
        in a new transport.  See #3821.
 
85
 
 
86
    @ivar _handshakeDone: A flag indicating whether or not the handshake is
 
87
        known to have completed successfully (C{True}) or not (C{False}).  This
 
88
        is used to control error reporting behavior.  If the handshake has not
 
89
        completed, the underlying L{OpenSSL.SSL.Error} will be passed to the
 
90
        application's C{connectionLost} method.  If it has completed, any
 
91
        unexpected L{OpenSSL.SSL.Error} will be turned into a
 
92
        L{ConnectionLost}.  This is weird; however, it is simply an attempt at
 
93
        a faithful re-implementation of the behavior provided by
 
94
        L{twisted.internet.ssl}.
 
95
 
 
96
    @ivar _reason: If an unexpected L{OpenSSL.SSL.Error} occurs which causes
 
97
        the connection to be lost, it is saved here.  If appropriate, this may
 
98
        be used as the reason passed to the application protocol's
 
99
        C{connectionLost} method.
 
100
    """
 
101
    implements(ISystemHandle, ISSLTransport)
 
102
 
 
103
    _reason = None
 
104
    _handshakeDone = False
 
105
    _lostConnection = False
 
106
    _writeBlockedOnRead = False
 
107
 
 
108
    def __init__(self, factory, wrappedProtocol, _connectWrapped=True):
 
109
        ProtocolWrapper.__init__(self, factory, wrappedProtocol)
 
110
        self._connectWrapped = _connectWrapped
 
111
 
 
112
 
 
113
    def getHandle(self):
 
114
        """
 
115
        Return the L{OpenSSL.SSL.Connection} object being used to encrypt and
 
116
        decrypt this connection.
 
117
 
 
118
        This is done for the benefit of L{twisted.internet.ssl.Certificate}'s
 
119
        C{peerFromTransport} and C{hostFromTransport} methods only.  A
 
120
        different system handle may be returned by future versions of this
 
121
        method.
 
122
        """
 
123
        return self._tlsConnection
 
124
 
 
125
 
 
126
    def makeConnection(self, transport):
 
127
        """
 
128
        Connect this wrapper to the given transport and initialize the
 
129
        necessary L{OpenSSL.SSL.Connection} with a memory BIO.
 
130
        """
 
131
        tlsContext = self.factory._contextFactory.getContext()
 
132
        self._tlsConnection = Connection(tlsContext, None)
 
133
        if self.factory._isClient:
 
134
            self._tlsConnection.set_connect_state()
 
135
        else:
 
136
            self._tlsConnection.set_accept_state()
 
137
        self._appSendBuffer = []
 
138
 
 
139
        # Intentionally skip ProtocolWrapper.makeConnection - it might call
 
140
        # wrappedProtocol.makeConnection, which we want to make conditional.
 
141
        Protocol.makeConnection(self, transport)
 
142
        self.factory.registerProtocol(self)
 
143
        if self._connectWrapped:
 
144
            # Now that the TLS layer is initialized, notify the application of
 
145
            # the connection.
 
146
            ProtocolWrapper.makeConnection(self, transport)
 
147
 
 
148
        # Now that we ourselves have a transport (initialized by the
 
149
        # ProtocolWrapper.makeConnection call above), kick off the TLS
 
150
        # handshake.
 
151
        try:
 
152
            self._tlsConnection.do_handshake()
 
153
        except WantReadError:
 
154
            # This is the expected case - there's no data in the connection's
 
155
            # input buffer yet, so it won't be able to complete the whole
 
156
            # handshake now.  If this is the speak-first side of the
 
157
            # connection, then some bytes will be in the send buffer now; flush
 
158
            # them.
 
159
            self._flushSendBIO()
 
160
 
 
161
 
 
162
    def _flushSendBIO(self):
 
163
        """
 
164
        Read any bytes out of the send BIO and write them to the underlying
 
165
        transport.
 
166
        """
 
167
        try:
 
168
            bytes = self._tlsConnection.bio_read(2 ** 15)
 
169
        except WantReadError:
 
170
            # There may be nothing in the send BIO right now.
 
171
            pass
 
172
        else:
 
173
            self.transport.write(bytes)
 
174
 
 
175
 
 
176
    def _flushReceiveBIO(self):
 
177
        """
 
178
        Try to receive any application-level bytes which are now available
 
179
        because of a previous write into the receive BIO.  This will take
 
180
        care of delivering any application-level bytes which are received to
 
181
        the protocol, as well as handling of the various exceptions which
 
182
        can come from trying to get such bytes.
 
183
        """
 
184
        # Keep trying this until an error indicates we should stop or we
 
185
        # close the connection.  Looping is necessary to make sure we
 
186
        # process all of the data which was put into the receive BIO, as
 
187
        # there is no guarantee that a single recv call will do it all.
 
188
        while not self._lostConnection:
 
189
            try:
 
190
                bytes = self._tlsConnection.recv(2 ** 15)
 
191
            except WantReadError:
 
192
                # The newly received bytes might not have been enough to produce
 
193
                # any application data.
 
194
                break
 
195
            except ZeroReturnError:
 
196
                # TLS has shut down and no more TLS data will be received over
 
197
                # this connection.
 
198
                self._lostConnection = True
 
199
                self.transport.loseConnection()
 
200
                if not self._handshakeDone and self._reason is not None:
 
201
                    failure = self._reason
 
202
                else:
 
203
                    failure = Failure(CONNECTION_DONE)
 
204
                # Failure's are fat.  Drop the reference.
 
205
                self._reason = None
 
206
                ProtocolWrapper.connectionLost(self, failure)
 
207
            except Error, e:
 
208
                # Something went pretty wrong.  For example, this might be a
 
209
                # handshake failure (because there were no shared ciphers, because
 
210
                # a certificate failed to verify, etc).  TLS can no longer proceed.
 
211
                self._flushSendBIO()
 
212
                self._lostConnection = True
 
213
 
 
214
                # Squash EOF in violation of protocol into ConnectionLost
 
215
                if e.args[0] == -1 and e.args[1] == 'Unexpected EOF':
 
216
                    failure = Failure(CONNECTION_LOST)
 
217
                else:
 
218
                    failure = Failure()
 
219
                ProtocolWrapper.connectionLost(self, failure)
 
220
                # This loseConnection call is basically tested by
 
221
                # test_handshakeFailure.  At least one side will need to do it
 
222
                # or the test never finishes.
 
223
                self.transport.loseConnection()
 
224
            else:
 
225
                # If we got application bytes, the handshake must be done by
 
226
                # now.  Keep track of this to control error reporting later.
 
227
                self._handshakeDone = True
 
228
                ProtocolWrapper.dataReceived(self, bytes)
 
229
 
 
230
        # The received bytes might have generated a response which needs to be
 
231
        # sent now.  For example, the handshake involves several round-trip
 
232
        # exchanges without ever producing application-bytes.
 
233
        self._flushSendBIO()
 
234
 
 
235
 
 
236
    def dataReceived(self, bytes):
 
237
        """
 
238
        Deliver any received bytes to the receive BIO and then read and deliver
 
239
        to the application any application-level data which becomes available
 
240
        as a result of this.
 
241
        """
 
242
        self._tlsConnection.bio_write(bytes)
 
243
 
 
244
        if self._writeBlockedOnRead:
 
245
            # A read just happened, so we might not be blocked anymore.  Try to
 
246
            # flush all the pending application bytes.
 
247
            self._writeBlockedOnRead = False
 
248
            appSendBuffer = self._appSendBuffer
 
249
            self._appSendBuffer = []
 
250
            for bytes in appSendBuffer:
 
251
                self.write(bytes)
 
252
            if not self._writeBlockedOnRead and self.disconnecting:
 
253
                self.loseConnection()
 
254
 
 
255
        self._flushReceiveBIO()
 
256
 
 
257
 
 
258
    def connectionLost(self, reason):
 
259
        """
 
260
        Handle the possible repetition of calls to this method (due to either
 
261
        the underlying transport going away or due to an error at the TLS
 
262
        layer) and make sure the base implementation only gets invoked once.
 
263
        """
 
264
        if not self._lostConnection:
 
265
            # Tell the TLS connection that it's not going to get any more data
 
266
            # and give it a chance to finish reading.
 
267
            self._tlsConnection.bio_shutdown()
 
268
            self._flushReceiveBIO()
 
269
 
 
270
 
 
271
    def loseConnection(self):
 
272
        """
 
273
        Send a TLS close alert and close the underlying connection.
 
274
        """
 
275
        self.disconnecting = True
 
276
        if not self._writeBlockedOnRead:
 
277
            self._tlsConnection.shutdown()
 
278
            self._flushSendBIO()
 
279
            self.transport.loseConnection()
 
280
 
 
281
 
 
282
    def write(self, bytes):
 
283
        """
 
284
        Process the given application bytes and send any resulting TLS traffic
 
285
        which arrives in the send BIO.
 
286
        """
 
287
        if self._lostConnection:
 
288
            return
 
289
 
 
290
        leftToSend = bytes
 
291
        while leftToSend:
 
292
            try:
 
293
                sent = self._tlsConnection.send(leftToSend)
 
294
            except WantReadError:
 
295
                self._writeBlockedOnRead = True
 
296
                self._appSendBuffer.append(leftToSend)
 
297
                break
 
298
            except Error, e:
 
299
                # Just drop the connection.  This has two useful consequences.
 
300
                # First, for the application protocol's connectionLost method,
 
301
                # it will squash any error into connection lost.  We *could*
 
302
                # let the real exception propagate to application code, but the
 
303
                # other SSL implementation doesn't.  Second, it causes the
 
304
                # protocol's connectionLost method to be invoked
 
305
                # non-reentrantly, which is always a nice feature.
 
306
                self._reason = Failure()
 
307
                self.transport.loseConnection()
 
308
                break
 
309
            else:
 
310
                # If we sent some bytes, the handshake must be done.  Keep
 
311
                # track of this to control error reporting behavior.
 
312
                self._handshakeDone = True
 
313
                self._flushSendBIO()
 
314
                leftToSend = leftToSend[sent:]
 
315
 
 
316
 
 
317
    def writeSequence(self, iovec):
 
318
        """
 
319
        Write a sequence of application bytes by joining them into one string
 
320
        and passing them to L{write}.
 
321
        """
 
322
        self.write("".join(iovec))
 
323
 
 
324
 
 
325
    def getPeerCertificate(self):
 
326
        return self._tlsConnection.get_peer_certificate()
 
327
 
 
328
 
 
329
 
 
330
class TLSMemoryBIOFactory(WrappingFactory):
 
331
    """
 
332
    L{TLSMemoryBIOFactory} adds TLS to connections.
 
333
 
 
334
    @ivar _contextFactory: The TLS context factory which will be used to define
 
335
        certain TLS connection parameters.
 
336
 
 
337
    @ivar _isClient: A flag which is C{True} if this is a client TLS
 
338
        connection, C{False} if it is a server TLS connection.
 
339
    """
 
340
    protocol = TLSMemoryBIOProtocol
 
341
 
 
342
    def __init__(self, contextFactory, isClient, wrappedFactory):
 
343
        WrappingFactory.__init__(self, wrappedFactory)
 
344
        self._contextFactory = contextFactory
 
345
        self._isClient = isClient