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.
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
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::
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
20
from someapplication import ApplicationProtocol
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)
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.
40
from OpenSSL.SSL import Error, ZeroReturnError, WantReadError
41
from OpenSSL.SSL import TLSv1_METHOD, Context, Connection
44
Connection(Context(TLSv1_METHOD), None)
46
if str(e) != "argument must be an int, or have a fileno() method.":
48
raise ImportError("twisted.protocols.tls requires pyOpenSSL 0.10 or newer.")
50
from zope.interface import implements
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
59
class TLSMemoryBIOProtocol(ProtocolWrapper):
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.
66
@ivar _tlsConnection: The L{OpenSSL.SSL.Connection} instance which is
67
encrypted and decrypting this connection.
69
@ivar _lostConnection: A flag indicating whether connection loss has
70
already been dealt with (C{True}) or not (C{False}).
72
@ivar _writeBlockedOnRead: A flag indicating whether further writing must
73
wait for data to be received (C{True}) or not (C{False}).
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}.
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.
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}.
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.
101
implements(ISystemHandle, ISSLTransport)
104
_handshakeDone = False
105
_lostConnection = False
106
_writeBlockedOnRead = False
108
def __init__(self, factory, wrappedProtocol, _connectWrapped=True):
109
ProtocolWrapper.__init__(self, factory, wrappedProtocol)
110
self._connectWrapped = _connectWrapped
115
Return the L{OpenSSL.SSL.Connection} object being used to encrypt and
116
decrypt this connection.
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
123
return self._tlsConnection
126
def makeConnection(self, transport):
128
Connect this wrapper to the given transport and initialize the
129
necessary L{OpenSSL.SSL.Connection} with a memory BIO.
131
tlsContext = self.factory._contextFactory.getContext()
132
self._tlsConnection = Connection(tlsContext, None)
133
if self.factory._isClient:
134
self._tlsConnection.set_connect_state()
136
self._tlsConnection.set_accept_state()
137
self._appSendBuffer = []
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
146
ProtocolWrapper.makeConnection(self, transport)
148
# Now that we ourselves have a transport (initialized by the
149
# ProtocolWrapper.makeConnection call above), kick off the TLS
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
162
def _flushSendBIO(self):
164
Read any bytes out of the send BIO and write them to the underlying
168
bytes = self._tlsConnection.bio_read(2 ** 15)
169
except WantReadError:
170
# There may be nothing in the send BIO right now.
173
self.transport.write(bytes)
176
def _flushReceiveBIO(self):
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.
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:
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.
195
except ZeroReturnError:
196
# TLS has shut down and no more TLS data will be received over
198
self._lostConnection = True
199
self.transport.loseConnection()
200
if not self._handshakeDone and self._reason is not None:
201
failure = self._reason
203
failure = Failure(CONNECTION_DONE)
204
# Failure's are fat. Drop the reference.
206
ProtocolWrapper.connectionLost(self, failure)
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.
212
self._lostConnection = True
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)
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()
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)
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.
236
def dataReceived(self, bytes):
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
242
self._tlsConnection.bio_write(bytes)
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:
252
if not self._writeBlockedOnRead and self.disconnecting:
253
self.loseConnection()
255
self._flushReceiveBIO()
258
def connectionLost(self, reason):
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.
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()
271
def loseConnection(self):
273
Send a TLS close alert and close the underlying connection.
275
self.disconnecting = True
276
if not self._writeBlockedOnRead:
277
self._tlsConnection.shutdown()
279
self.transport.loseConnection()
282
def write(self, bytes):
284
Process the given application bytes and send any resulting TLS traffic
285
which arrives in the send BIO.
287
if self._lostConnection:
293
sent = self._tlsConnection.send(leftToSend)
294
except WantReadError:
295
self._writeBlockedOnRead = True
296
self._appSendBuffer.append(leftToSend)
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()
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
314
leftToSend = leftToSend[sent:]
317
def writeSequence(self, iovec):
319
Write a sequence of application bytes by joining them into one string
320
and passing them to L{write}.
322
self.write("".join(iovec))
325
def getPeerCertificate(self):
326
return self._tlsConnection.get_peer_certificate()
330
class TLSMemoryBIOFactory(WrappingFactory):
332
L{TLSMemoryBIOFactory} adds TLS to connections.
334
@ivar _contextFactory: The TLS context factory which will be used to define
335
certain TLS connection parameters.
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.
340
protocol = TLSMemoryBIOProtocol
342
def __init__(self, contextFactory, isClient, wrappedFactory):
343
WrappingFactory.__init__(self, wrappedFactory)
344
self._contextFactory = contextFactory
345
self._isClient = isClient