1 # Copyright (c) Twisted Matrix Laboratories.
2 # See LICENSE for details.
5 Tests for implementations of L{ITLSTransport}.
12 from zope.interface import implements
14 from twisted.internet.test.reactormixins import ReactorBuilder, EndpointCreator
15 from twisted.internet.protocol import ServerFactory, ClientFactory, Protocol
16 from twisted.internet.interfaces import (
17 IReactorSSL, ITLSTransport, IStreamClientEndpoint)
18 from twisted.internet.defer import Deferred, DeferredList
19 from twisted.internet.endpoints import (
20 SSL4ServerEndpoint, SSL4ClientEndpoint, TCP4ClientEndpoint)
21 from twisted.internet.error import ConnectionClosed
22 from twisted.internet.task import Cooperator
23 from twisted.trial.unittest import TestCase, SkipTest
24 from twisted.python.runtime import platform
26 from twisted.internet.test.test_core import ObjectModelIntegrationMixin
27 from twisted.internet.test.test_tcp import (
28 StreamTransportTestsMixin, AbortConnectionMixin)
29 from twisted.internet.test.connectionmixins import ConnectionTestsMixin
30 from twisted.internet.test.connectionmixins import BrokenContextFactory
33 from OpenSSL.crypto import FILETYPE_PEM
37 from twisted.internet.ssl import PrivateCertificate, KeyPair
38 from twisted.internet.ssl import ClientContextFactory
42 requiredInterfaces = [IReactorSSL]
44 if platform.isWindows():
46 "For some reason, these reactors don't deal with SSL "
47 "disconnection correctly on Windows. See #3371.")
49 "twisted.internet.glib2reactor.Glib2Reactor": msg,
50 "twisted.internet.gtk2reactor.Gtk2Reactor": msg}
53 class ContextGeneratingMixin(object):
55 "-----BEGIN CERTIFICATE-----\n"
56 "MIIDBjCCAm+gAwIBAgIBATANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzER\n"
57 "MA8GA1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQD\n"
58 "ExtNMkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5n\n"
59 "cHNAcG9zdDEuY29tMB4XDTAwMDkxMDA5NTEzMFoXDTAyMDkxMDA5NTEzMFowUzEL\n"
60 "MAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwlsb2NhbGhv\n"
61 "c3QxHTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tMFwwDQYJKoZIhvcNAQEB\n"
62 "BQADSwAwSAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh\n"
63 "5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAaOCAQQwggEAMAkGA1UdEwQC\n"
64 "MAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRl\n"
65 "MB0GA1UdDgQWBBTPhIKSvnsmYsBVNWjj0m3M2z0qVTCBpQYDVR0jBIGdMIGagBT7\n"
66 "hyNp65w6kxXlxb8pUU/+7Sg4AaF/pH0wezELMAkGA1UEBhMCU0cxETAPBgNVBAoT\n"
67 "CE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlw\n"
68 "dG8gQ2VydGlmaWNhdGUgTWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5uZ3BzQHBvc3Qx\n"
69 "LmNvbYIBADANBgkqhkiG9w0BAQQFAAOBgQA7/CqT6PoHycTdhEStWNZde7M/2Yc6\n"
70 "BoJuVwnW8YxGO8Sn6UJ4FeffZNcYZddSDKosw8LtPOeWoK3JINjAk5jiPQ2cww++\n"
71 "7QGG/g5NDjxFZNDJP1dGiLAxPW6JXwov4v0FmdzfLOZ01jDcgQQZqEpYlgpuI5JE\n"
73 "-----END CERTIFICATE-----\n")
76 "-----BEGIN RSA PRIVATE KEY-----\n"
77 "MIIBPAIBAAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh\n"
78 "5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAQJBAIqm/bz4NA1H++Vx5Ewx\n"
79 "OcKp3w19QSaZAwlGRtsUxrP7436QjnREM3Bm8ygU11BjkPVmtrKm6AayQfCHqJoT\n"
80 "ZIECIQDW0BoMoL0HOYM/mrTLhaykYAVqgIeJsPjvkEhTFXWBuQIhAM3deFAvWNu4\n"
81 "nklUQ37XsCT2c9tmNt1LAT+slG2JOTTRAiAuXDtC/m3NYVwyHfFm+zKHRzHkClk2\n"
82 "HjubeEgjpj32AQIhAJqMGTaZVOwevTXvvHwNEH+vRWsAYU/gbx+OQB+7VOcBAiEA\n"
83 "oolb6NMg/R3enNPvS1O4UU1H8wpaF77L4yiSWlE0p4w=\n"
84 "-----END RSA PRIVATE KEY-----\n")
87 def getServerContext(self):
89 Return a new SSL context suitable for use in a test server.
91 cert = PrivateCertificate.load(
92 self._certificateText,
93 KeyPair.load(self._privateKeyText, FILETYPE_PEM),
98 def getClientContext(self):
99 return ClientContextFactory()
103 class StartTLSClientEndpoint(object):
105 An endpoint which wraps another one and adds a TLS layer immediately when
106 connections are set up.
108 @ivar wrapped: A L{IStreamClientEndpoint} provider which will be used to
109 really set up connections.
111 @ivar contextFactory: A L{ContextFactory} to use to do TLS.
113 implements(IStreamClientEndpoint)
115 def __init__(self, wrapped, contextFactory):
116 self.wrapped = wrapped
117 self.contextFactory = contextFactory
120 def connect(self, factory):
122 Establish a connection using a protocol build by C{factory} and
123 immediately start TLS on it. Return a L{Deferred} which fires with the
126 # This would be cleaner when we have ITransport.switchProtocol, which
127 # will be added with ticket #3204:
128 class WrapperFactory(ServerFactory):
129 def buildProtocol(wrapperSelf, addr):
130 protocol = factory.buildProtocol(addr)
131 def connectionMade(orig=protocol.connectionMade):
132 protocol.transport.startTLS(self.contextFactory)
134 protocol.connectionMade = connectionMade
137 return self.wrapped.connect(WrapperFactory())
141 class StartTLSClientCreator(EndpointCreator, ContextGeneratingMixin):
143 Create L{ITLSTransport.startTLS} endpoint for the client, and normal SSL
144 for server just because it's easier.
146 def server(self, reactor):
148 Construct an SSL server endpoint. This should be be constructing a TCP
149 server endpoint which immediately calls C{startTLS} instead, but that
152 return SSL4ServerEndpoint(reactor, 0, self.getServerContext())
155 def client(self, reactor, serverAddress):
157 Construct a TCP client endpoint wrapped to immediately start TLS.
159 return StartTLSClientEndpoint(
161 reactor, '127.0.0.1', serverAddress.port),
162 ClientContextFactory())
166 class BadContextTestsMixin(object):
168 Mixin for L{ReactorBuilder} subclasses which defines a helper for testing
169 the handling of broken context factories.
171 def _testBadContext(self, useIt):
173 Assert that the exception raised by a broken context factory's
174 C{getContext} method is raised by some reactor method. If it is not, an
175 exception will be raised to fail the test.
177 @param useIt: A two-argument callable which will be called with a
178 reactor and a broken context factory and which is expected to raise
179 the same exception as the broken context factory's C{getContext}
182 reactor = self.buildReactor()
183 exc = self.assertRaises(
184 ValueError, useIt, reactor, BrokenContextFactory())
185 self.assertEqual(BrokenContextFactory.message, str(exc))
189 class StartTLSClientTestsMixin(TLSMixin, ReactorBuilder, ConnectionTestsMixin):
191 Tests for TLS connections established using L{ITLSTransport.startTLS} (as
192 opposed to L{IReactorSSL.connectSSL} or L{IReactorSSL.listenSSL}).
194 endpoints = StartTLSClientCreator()
198 class SSLCreator(EndpointCreator, ContextGeneratingMixin):
200 Create SSL endpoints.
202 def server(self, reactor):
204 Create an SSL server endpoint on a TCP/IP-stack allocated port.
206 return SSL4ServerEndpoint(reactor, 0, self.getServerContext())
209 def client(self, reactor, serverAddress):
211 Create an SSL client endpoint which will connect localhost on
212 the port given by C{serverAddress}.
214 @type serverAddress: L{IPv4Address}
216 return SSL4ClientEndpoint(
217 reactor, '127.0.0.1', serverAddress.port,
218 ClientContextFactory())
221 class SSLClientTestsMixin(TLSMixin, ReactorBuilder, ContextGeneratingMixin,
222 ConnectionTestsMixin, BadContextTestsMixin):
224 Mixin defining tests relating to L{ITLSTransport}.
226 endpoints = SSLCreator()
228 def test_badContext(self):
230 If the context factory passed to L{IReactorSSL.connectSSL} raises an
231 exception from its C{getContext} method, that exception is raised by
232 L{IReactorSSL.connectSSL}.
234 def useIt(reactor, contextFactory):
235 return reactor.connectSSL(
236 "127.0.0.1", 1234, ClientFactory(), contextFactory)
237 self._testBadContext(useIt)
240 def test_disconnectAfterWriteAfterStartTLS(self):
242 L{ITCPTransport.loseConnection} ends a connection which was set up with
243 L{ITLSTransport.startTLS} and which has recently been written to. This
244 is intended to verify that a socket send error masked by the TLS
245 implementation doesn't prevent the connection from being reported as
248 class ShortProtocol(Protocol):
249 def connectionMade(self):
250 if not ITLSTransport.providedBy(self.transport):
251 # Functionality isn't available to be tested.
252 finished = self.factory.finished
253 self.factory.finished = None
254 finished.errback(SkipTest("No ITLSTransport support"))
257 # Switch the transport to TLS.
258 self.transport.startTLS(self.factory.context)
259 # Force TLS to really get negotiated. If nobody talks, nothing
261 self.transport.write("x")
263 def dataReceived(self, data):
264 # Stuff some bytes into the socket. This mostly has the effect
265 # of causing the next write to fail with ENOTCONN or EPIPE.
266 # With the pyOpenSSL implementation of ITLSTransport, the error
267 # is swallowed outside of the control of Twisted.
268 self.transport.write("y")
269 # Now close the connection, which requires a TLS close alert to
271 self.transport.loseConnection()
273 def connectionLost(self, reason):
274 # This is the success case. The client and the server want to
276 finished = self.factory.finished
277 if finished is not None:
278 self.factory.finished = None
279 finished.callback(reason)
281 reactor = self.buildReactor()
283 serverFactory = ServerFactory()
284 serverFactory.finished = Deferred()
285 serverFactory.protocol = ShortProtocol
286 serverFactory.context = self.getServerContext()
288 clientFactory = ClientFactory()
289 clientFactory.finished = Deferred()
290 clientFactory.protocol = ShortProtocol
291 clientFactory.context = self.getClientContext()
292 clientFactory.context.method = serverFactory.context.method
294 lostConnectionResults = []
295 finished = DeferredList(
296 [serverFactory.finished, clientFactory.finished],
298 def cbFinished(results):
299 lostConnectionResults.extend([results[0][1], results[1][1]])
300 finished.addCallback(cbFinished)
302 port = reactor.listenTCP(0, serverFactory, interface='127.0.0.1')
303 self.addCleanup(port.stopListening)
305 connector = reactor.connectTCP(
306 port.getHost().host, port.getHost().port, clientFactory)
307 self.addCleanup(connector.disconnect)
309 finished.addCallback(lambda ign: reactor.stop())
310 self.runReactor(reactor)
311 lostConnectionResults[0].trap(ConnectionClosed)
312 lostConnectionResults[1].trap(ConnectionClosed)
316 class TLSPortTestsBuilder(TLSMixin, ContextGeneratingMixin,
317 ObjectModelIntegrationMixin, BadContextTestsMixin,
318 StreamTransportTestsMixin, ReactorBuilder):
320 Tests for L{IReactorSSL.listenSSL}
322 def getListeningPort(self, reactor, factory):
324 Get a TLS port from a reactor.
326 return reactor.listenSSL(0, factory, self.getServerContext())
329 def getExpectedStartListeningLogMessage(self, port, factory):
331 Get the message expected to be logged when a TLS port starts listening.
333 return "%s (TLS) starting on %d" % (factory, port.getHost().port)
336 def getExpectedConnectionLostLogMsg(self, port):
338 Get the expected connection lost message for a TLS port.
340 return "(TLS Port %s Closed)" % (port.getHost().port,)
343 def test_badContext(self):
345 If the context factory passed to L{IReactorSSL.listenSSL} raises an
346 exception from its C{getContext} method, that exception is raised by
347 L{IReactorSSL.listenSSL}.
349 def useIt(reactor, contextFactory):
350 return reactor.listenSSL(0, ServerFactory(), contextFactory)
351 self._testBadContext(useIt)
355 globals().update(SSLClientTestsMixin.makeTestCaseClasses())
356 globals().update(StartTLSClientTestsMixin.makeTestCaseClasses())
357 globals().update(TLSPortTestsBuilder().makeTestCaseClasses())
361 class AbortSSLConnectionTest(ReactorBuilder, AbortConnectionMixin, ContextGeneratingMixin):
363 C{abortConnection} tests using SSL.
365 requiredInterfaces = (IReactorSSL,)
366 endpoints = SSLCreator()
368 def buildReactor(self):
369 reactor = ReactorBuilder.buildReactor(self)
371 from twisted.protocols import tls
375 # Patch twisted.protocols.tls to use this reactor, until we get
376 # around to fixing #5206, or the TLS code uses an explicit reactor:
377 cooperator = Cooperator(
378 scheduler=lambda x: reactor.callLater(0.00001, x))
379 self.patch(tls, "cooperate", cooperator.cooperate)
384 if FILETYPE_PEM is None:
385 raise SkipTest("OpenSSL not available.")
387 globals().update(AbortSSLConnectionTest.makeTestCaseClasses())
389 class OldTLSDeprecationTest(TestCase):
391 Tests for the deprecation of L{twisted.internet._oldtls}, the implementation
392 module for L{IReactorSSL} used when only an old version of pyOpenSSL is
395 def test_warning(self):
397 The use of L{twisted.internet._oldtls} is deprecated, and emits a
398 L{DeprecationWarning}.
400 # Since _oldtls depends on OpenSSL, just skip this test if it isn't
401 # installed on the system. Faking it would be error prone.
405 raise SkipTest("OpenSSL not available.")
407 # Change the apparent version of OpenSSL to one support for which is
408 # deprecated. And have it change back again after the test.
409 self.patch(OpenSSL, '__version__', '0.5')
411 # If the module was already imported, the import statement below won't
412 # execute its top-level code. Take it out of sys.modules so the import
413 # system re-evaluates it. Arrange to put the original back afterwards.
414 # Also handle the case where it hasn't yet been imported.
416 oldtls = sys.modules['twisted.internet._oldtls']
418 self.addCleanup(sys.modules.pop, 'twisted.internet._oldtls')
420 del sys.modules['twisted.internet._oldtls']
422 operator.setitem, sys.modules, 'twisted.internet._oldtls',
426 import twisted.internet._oldtls
427 warnings = self.flushWarnings()
428 self.assertEqual(warnings[0]['category'], DeprecationWarning)
430 warnings[0]['message'],
431 "Support for pyOpenSSL 0.5 is deprecated. "
432 "Upgrade to pyOpenSSL 0.10 or newer.")