1 # -*- test-case-name: twisted.test.test_ssl -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
6 This module implements OpenSSL socket BIO based TLS support. It is only used if
7 memory BIO APIs are not available, which is when the version of pyOpenSSL
8 installed is older than 0.10 (when L{twisted.protocols.tls} is not importable).
9 This implementation is undesirable because of the complexity of working with
10 OpenSSL's non-blocking socket-based APIs (which this module probably does about
11 99% correctly, but see #4455 for an example of a problem with it).
13 Support for older versions of pyOpenSSL is now deprecated and will be removed
16 @see: L{twisted.internet._newtls}
22 from twisted.python.runtime import platformType
23 if platformType == 'win32':
24 from errno import WSAEINTR as EINTR
25 from errno import WSAEWOULDBLOCK as EWOULDBLOCK
26 from errno import WSAENOBUFS as ENOBUFS
28 from errno import EINTR
29 from errno import EWOULDBLOCK
30 from errno import ENOBUFS
32 from OpenSSL import SSL, __version__ as _sslversion
34 from zope.interface import implements
36 from twisted.python import log
37 from twisted.internet.interfaces import ITLSTransport, ISSLTransport
38 from twisted.internet.abstract import FileDescriptor
39 from twisted.internet.main import CONNECTION_DONE, CONNECTION_LOST
40 from twisted.internet._ssl import _TLSDelayed
43 "Support for pyOpenSSL %s is deprecated. "
44 "Upgrade to pyOpenSSL 0.10 or newer." % (_sslversion,),
45 category=DeprecationWarning,
49 _socketShutdownMethod = 'sock_shutdown'
51 writeBlockedOnRead = 0
52 readBlockedOnWrite = 0
53 _userWantRead = _userWantWrite = True
55 def getPeerCertificate(self):
56 return self.socket.get_peer_certificate()
60 # See the comment in the similar check in doWrite below.
61 # Additionally, in order for anything other than returning
62 # CONNECTION_DONE here to make sense, it will probably be necessary
63 # to implement a way to switch back to TCP from TLS (actually, if
64 # we did something other than return CONNECTION_DONE, that would be
65 # a big part of implementing that feature). In other words, the
66 # expectation is that doRead will be called when self.disconnected
67 # is True only when the connection has been lost. It's possible
68 # that the other end could stop speaking TLS and then send us some
69 # non-TLS data. We'll end up ignoring that data and dropping the
70 # connection. There's no unit tests for this check in the cases
71 # where it makes a difference. The test suite only hits this
72 # codepath when it would have otherwise hit the SSL.ZeroReturnError
73 # exception handler below, which has exactly the same behavior as
74 # this conditional. Maybe that's the only case that can ever be
75 # triggered, I'm not sure. -exarkun
76 return CONNECTION_DONE
77 if self.writeBlockedOnRead:
78 self.writeBlockedOnRead = 0
79 self._resetReadWrite()
81 return self._base.doRead(self)
82 except SSL.ZeroReturnError:
83 return CONNECTION_DONE
84 except SSL.WantReadError:
86 except SSL.WantWriteError:
87 self.readBlockedOnWrite = 1
88 self._base.startWriting(self)
89 self._base.stopReading(self)
91 except SSL.SysCallError, (retval, desc):
92 if ((retval == -1 and desc == 'Unexpected EOF')
94 return CONNECTION_LOST
96 return CONNECTION_LOST
101 # Retry disconnecting
102 if self.disconnected:
103 # This case is triggered when "disconnected" is set to True by a
104 # call to _postLoseConnection from FileDescriptor.doWrite (to which
105 # we upcall at the end of this overridden version of that API). It
106 # means that while, as far as any protocol connected to this
107 # transport is concerned, the connection no longer exists, the
108 # connection *does* actually still exist. Instead of closing the
109 # connection in the overridden _postLoseConnection, we probably
110 # tried (and failed) to send a TLS close alert. The TCP connection
111 # is still up and we're waiting for the socket to become writeable
112 # enough for the TLS close alert to actually be sendable. Only
113 # then will the connection actually be torn down. -exarkun
114 return self._postLoseConnection()
115 if self._writeDisconnected:
116 return self._closeWriteConnection()
118 if self.readBlockedOnWrite:
119 self.readBlockedOnWrite = 0
120 self._resetReadWrite()
121 return self._base.doWrite(self)
123 def writeSomeData(self, data):
125 return self._base.writeSomeData(self, data)
126 except SSL.WantWriteError:
128 except SSL.WantReadError:
129 self.writeBlockedOnRead = 1
130 self._base.stopWriting(self)
131 self._base.startReading(self)
133 except SSL.ZeroReturnError:
134 return CONNECTION_LOST
135 except SSL.SysCallError, e:
136 if e[0] == -1 and data == "":
137 # errors when writing empty strings are expected
141 return CONNECTION_LOST
146 def _postLoseConnection(self):
148 Gets called after loseConnection(), after buffered data is sent.
150 We try to send an SSL shutdown alert, but if it doesn't work, retry
151 when the socket is writable.
153 # Here, set "disconnected" to True to trick higher levels into thinking
154 # the connection is really gone. It's not, and we're not going to
155 # close it yet. Instead, we'll try to send a TLS close alert to shut
156 # down the TLS connection cleanly. Only after we actually get the
157 # close alert into the socket will we disconnect the underlying TCP
159 self.disconnected = True
160 if hasattr(self.socket, 'set_shutdown'):
161 # If possible, mark the state of the TLS connection as having
162 # already received a TLS close alert from the peer. Why do
164 self.socket.set_shutdown(SSL.RECEIVED_SHUTDOWN)
165 return self._sendCloseAlert()
168 def _sendCloseAlert(self):
169 # Okay, *THIS* is a bit complicated.
171 # Basically, the issue is, OpenSSL seems to not actually return
172 # errors from SSL_shutdown. Therefore, the only way to
173 # determine if the close notification has been sent is by
174 # SSL_shutdown returning "done". However, it will not claim it's
175 # done until it's both sent *and* received a shutdown notification.
177 # I don't actually want to wait for a received shutdown
178 # notification, though, so, I have to set RECEIVED_SHUTDOWN
179 # before calling shutdown. Then, it'll return True once it's
180 # *SENT* the shutdown.
182 # However, RECEIVED_SHUTDOWN can't be left set, because then
183 # reads will fail, breaking half close.
185 # Also, since shutdown doesn't report errors, an empty write call is
186 # done first, to try to detect if the connection has gone away.
187 # (*NOT* an SSL_write call, because that fails once you've called
190 os.write(self.socket.fileno(), '')
192 if se.args[0] in (EINTR, EWOULDBLOCK, ENOBUFS):
194 # Write error, socket gone
195 return CONNECTION_LOST
198 if hasattr(self.socket, 'set_shutdown'):
199 laststate = self.socket.get_shutdown()
200 self.socket.set_shutdown(laststate | SSL.RECEIVED_SHUTDOWN)
201 done = self.socket.shutdown()
202 if not (laststate & SSL.RECEIVED_SHUTDOWN):
203 self.socket.set_shutdown(SSL.SENT_SHUTDOWN)
205 #warnings.warn("SSL connection shutdown possibly unreliable, "
206 # "please upgrade to ver 0.XX", category=UserWarning)
207 self.socket.shutdown()
214 # Note that this is tested for by identity below.
215 return CONNECTION_DONE
217 # For some reason, the close alert wasn't sent. Start writing
218 # again so that we'll get another chance to send it.
220 # On Linux, select will sometimes not report a closed file
221 # descriptor in the write set (in particular, it seems that if a
222 # send() fails with EPIPE, the socket will not appear in the write
223 # set). The shutdown call above (which calls down to SSL_shutdown)
224 # may have swallowed a write error. Therefore, also start reading
225 # so that if the socket is closed we will notice. This doesn't
226 # seem to be a problem for poll (because poll reports errors
227 # separately) or with select on BSD (presumably because, unlike
228 # Linux, it doesn't implement select in terms of poll and then map
229 # POLLHUP to select's in fd_set).
233 def _closeWriteConnection(self):
234 result = self._sendCloseAlert()
236 if result is CONNECTION_DONE:
237 return self._base._closeWriteConnection(self)
241 def startReading(self):
242 self._userWantRead = True
243 if not self.readBlockedOnWrite:
244 return self._base.startReading(self)
247 def stopReading(self):
248 self._userWantRead = False
249 # If we've disconnected, preventing stopReading() from happening
250 # because we are blocked on a read is silly; the read will never
252 if self.disconnected or not self.writeBlockedOnRead:
253 return self._base.stopReading(self)
256 def startWriting(self):
257 self._userWantWrite = True
258 if not self.writeBlockedOnRead:
259 return self._base.startWriting(self)
262 def stopWriting(self):
263 self._userWantWrite = False
264 # If we've disconnected, preventing stopWriting() from happening
265 # because we are blocked on a write is silly; the write will never
267 if self.disconnected or not self.readBlockedOnWrite:
268 return self._base.stopWriting(self)
271 def _resetReadWrite(self):
272 # After changing readBlockedOnWrite or writeBlockedOnRead,
273 # call this to reset the state to what the user requested.
274 if self._userWantWrite:
279 if self._userWantRead:
286 def _getTLSClass(klass, _existing={}):
287 if klass not in _existing:
288 class TLSConnection(_TLSMixin, klass):
289 implements(ISSLTransport)
291 _existing[klass] = TLSConnection
292 return _existing[klass]
295 class ConnectionMixin(object):
297 Mixin for L{twisted.internet.tcp.Connection} to help implement
298 L{ITLSTransport} using pyOpenSSL to do crypto and I/O.
303 def startTLS(self, ctx, extra=True):
305 if self.dataBuffer or self._tempDataBuffer:
306 # pre-TLS bytes are still being written. Starting TLS now
307 # will do the wrong thing. Instead, mark that we're trying
308 # to go into the TLS state.
309 self._tlsWaiting = _TLSDelayed([], ctx, extra)
315 self.socket = SSL.Connection(ctx.getContext(), self.socket)
316 self.fileno = self.socket.fileno
323 self.__class__ = _getTLSClass(self.__class__)
326 def write(self, bytes):
327 if self._tlsWaiting is not None:
328 self._tlsWaiting.bufferedData.append(bytes)
330 FileDescriptor.write(self, bytes)
333 def writeSequence(self, iovec):
334 if self._tlsWaiting is not None:
335 self._tlsWaiting.bufferedData.extend(iovec)
337 FileDescriptor.writeSequence(self, iovec)
341 result = FileDescriptor.doWrite(self)
342 if self._tlsWaiting is not None:
343 if not self.dataBuffer and not self._tempDataBuffer:
344 waiting = self._tlsWaiting
345 self._tlsWaiting = None
346 self.startTLS(waiting.context, waiting.extra)
347 self.writeSequence(waiting.bufferedData)
352 class ClientMixin(object):
354 Mixin for L{twisted.internet.tcp.Client} to implement the client part of
357 implements(ITLSTransport)
359 def startTLS(self, ctx, client=1):
360 if self._base.startTLS(self, ctx, client):
362 self.socket.set_connect_state()
364 self.socket.set_accept_state()
368 class ServerMixin(object):
370 Mixin for L{twisted.internet.tcp.Client} to implement the server part of
373 implements(ITLSTransport)
375 def startTLS(self, ctx, server=1):
376 if self._base.startTLS(self, ctx, server):
378 self.socket.set_accept_state()
380 self.socket.set_connect_state()