blob: 32d8f74a1f8dc10ab9f14a07a60bbc387db52b6e [file] [log] [blame]
Jean-Paul Calderone8b63d452008-03-21 18:31:12 -04001# Copyright (C) Jean-Paul Calderone 2008, All rights reserved
2
Jean-Paul Calderone30c09ea2008-03-21 17:04:05 -04003"""
4Unit tests for L{OpenSSL.SSL}.
5"""
6
Jean-Paul Calderone52f0d8b2009-03-07 09:10:19 -05007from sys import platform
Jean-Paul Calderone828c9cb2008-04-26 18:06:54 -04008from tempfile import mktemp
Jean-Paul Calderone5ef86512008-04-26 19:06:28 -04009from socket import socket
Jean-Paul Calderone1cb5d022008-09-07 20:58:50 -040010from os import makedirs, symlink
11from os.path import join
Jean-Paul Calderone30c09ea2008-03-21 17:04:05 -040012
Jean-Paul Calderone460cc1f2009-03-07 11:31:12 -050013try:
14 # Prefer Twisted's TestCase, since it supports things like skips.
15 from twisted.trial.unittest import TestCase
16except ImportError:
17 # Fall back to the stdlib TestCase though, since it kind of works.
Rick Dean5b7b6372009-04-01 11:34:06 -050018 from unittest import TestCase, main
Jean-Paul Calderone460cc1f2009-03-07 11:31:12 -050019
Jean-Paul Calderone5ef86512008-04-26 19:06:28 -040020from OpenSSL.crypto import TYPE_RSA, FILETYPE_PEM, PKey, dump_privatekey, load_certificate, load_privatekey
Jean-Paul Calderone5075fce2008-09-07 20:18:55 -040021from OpenSSL.SSL import WantReadError, Context, Connection, Error
Jean-Paul Calderone30c09ea2008-03-21 17:04:05 -040022from OpenSSL.SSL import SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD
Jean-Paul Calderone4bccf5e2008-12-28 22:50:42 -050023from OpenSSL.SSL import VERIFY_PEER
Jean-Paul Calderone65407752008-04-26 19:59:01 -040024from OpenSSL.test.test_crypto import _Python23TestCaseHelper, cleartextCertificatePEM, cleartextPrivateKeyPEM
Jean-Paul Calderone4bccf5e2008-12-28 22:50:42 -050025try:
26 from OpenSSL.SSL import OP_NO_QUERY_MTU
27except ImportError:
28 OP_NO_QUERY_MTU = None
29try:
30 from OpenSSL.SSL import OP_COOKIE_EXCHANGE
31except ImportError:
32 OP_COOKIE_EXCHANGE = None
33try:
34 from OpenSSL.SSL import OP_NO_TICKET
35except ImportError:
36 OP_NO_TICKET = None
Jean-Paul Calderone5ef86512008-04-26 19:06:28 -040037
Jean-Paul Calderone30c09ea2008-03-21 17:04:05 -040038
Jean-Paul Calderone65407752008-04-26 19:59:01 -040039class ContextTests(TestCase, _Python23TestCaseHelper):
Jean-Paul Calderone30c09ea2008-03-21 17:04:05 -040040 """
41 Unit tests for L{OpenSSL.SSL.Context}.
42 """
Jean-Paul Calderone828c9cb2008-04-26 18:06:54 -040043 def mktemp(self):
44 """
45 Pathetic substitute for twisted.trial.unittest.TestCase.mktemp.
46 """
47 return mktemp(dir=".")
48
49
Jean-Paul Calderone30c09ea2008-03-21 17:04:05 -040050 def test_method(self):
51 """
52 L{Context} can be instantiated with one of L{SSLv2_METHOD},
53 L{SSLv3_METHOD}, L{SSLv23_METHOD}, or L{TLSv1_METHOD}.
54 """
55 for meth in [SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD]:
56 Context(meth)
57 self.assertRaises(TypeError, Context, "")
58 self.assertRaises(ValueError, Context, 10)
59
60
61 def test_use_privatekey(self):
62 """
63 L{Context.use_privatekey} takes an L{OpenSSL.crypto.PKey} instance.
64 """
65 key = PKey()
66 key.generate_key(TYPE_RSA, 128)
67 ctx = Context(TLSv1_METHOD)
68 ctx.use_privatekey(key)
69 self.assertRaises(TypeError, ctx.use_privatekey, "")
Jean-Paul Calderone828c9cb2008-04-26 18:06:54 -040070
71
72 def test_set_passwd_cb(self):
73 """
74 L{Context.set_passwd_cb} accepts a callable which will be invoked when
75 a private key is loaded from an encrypted PEM.
76 """
77 key = PKey()
78 key.generate_key(TYPE_RSA, 128)
79 pemFile = self.mktemp()
80 fObj = file(pemFile, 'w')
81 passphrase = "foobar"
82 fObj.write(dump_privatekey(FILETYPE_PEM, key, "blowfish", passphrase))
83 fObj.close()
84
85 calledWith = []
86 def passphraseCallback(maxlen, verify, extra):
87 calledWith.append((maxlen, verify, extra))
88 return passphrase
89 context = Context(TLSv1_METHOD)
90 context.set_passwd_cb(passphraseCallback)
91 context.use_privatekey_file(pemFile)
92 self.assertTrue(len(calledWith), 1)
93 self.assertTrue(isinstance(calledWith[0][0], int))
94 self.assertTrue(isinstance(calledWith[0][1], int))
95 self.assertEqual(calledWith[0][2], None)
Jean-Paul Calderone5ef86512008-04-26 19:06:28 -040096
97
98 def test_set_info_callback(self):
99 """
100 L{Context.set_info_callback} accepts a callable which will be invoked
101 when certain information about an SSL connection is available.
102 """
103 port = socket()
104 port.bind(('', 0))
105 port.listen(1)
106
107 client = socket()
108 client.setblocking(False)
109 client.connect_ex(port.getsockname())
110
111 clientSSL = Connection(Context(TLSv1_METHOD), client)
112 clientSSL.set_connect_state()
113
114 called = []
115 def info(conn, where, ret):
116 called.append((conn, where, ret))
117 context = Context(TLSv1_METHOD)
118 context.set_info_callback(info)
119 context.use_certificate(
120 load_certificate(FILETYPE_PEM, cleartextCertificatePEM))
121 context.use_privatekey(
122 load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM))
123
124 server, ignored = port.accept()
125 server.setblocking(False)
126
127 serverSSL = Connection(context, server)
128 serverSSL.set_accept_state()
129
130 while not called:
131 for ssl in clientSSL, serverSSL:
132 try:
133 ssl.do_handshake()
134 except WantReadError:
135 pass
136
137 # Kind of lame. Just make sure it got called somehow.
138 self.assertTrue(called)
Jean-Paul Calderonee1bd4322008-09-07 20:17:17 -0400139
140
Jean-Paul Calderone1cb5d022008-09-07 20:58:50 -0400141 def _load_verify_locations_test(self, *args):
Jean-Paul Calderonee1bd4322008-09-07 20:17:17 -0400142 port = socket()
143 port.bind(('', 0))
144 port.listen(1)
145
146 client = socket()
147 client.setblocking(False)
148 client.connect_ex(port.getsockname())
149
Jean-Paul Calderonee1bd4322008-09-07 20:17:17 -0400150 clientContext = Context(TLSv1_METHOD)
Jean-Paul Calderone1cb5d022008-09-07 20:58:50 -0400151 clientContext.load_verify_locations(*args)
Jean-Paul Calderonee1bd4322008-09-07 20:17:17 -0400152 # Require that the server certificate verify properly or the
153 # connection will fail.
154 clientContext.set_verify(
155 VERIFY_PEER,
156 lambda conn, cert, errno, depth, preverify_ok: preverify_ok)
157
158 clientSSL = Connection(clientContext, client)
159 clientSSL.set_connect_state()
160
161 server, _ = port.accept()
162 server.setblocking(False)
163
164 serverContext = Context(TLSv1_METHOD)
165 serverContext.use_certificate(
166 load_certificate(FILETYPE_PEM, cleartextCertificatePEM))
167 serverContext.use_privatekey(
168 load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM))
169
170 serverSSL = Connection(serverContext, server)
171 serverSSL.set_accept_state()
172
173 for i in range(3):
174 for ssl in clientSSL, serverSSL:
175 try:
176 # Without load_verify_locations above, the handshake
177 # will fail:
178 # Error: [('SSL routines', 'SSL3_GET_SERVER_CERTIFICATE',
179 # 'certificate verify failed')]
180 ssl.do_handshake()
181 except WantReadError:
182 pass
183
184 cert = clientSSL.get_peer_certificate()
Jean-Paul Calderone20131f52009-04-01 12:05:45 -0400185 self.assertEqual(cert.get_subject().CN, 'Testing Root CA')
Jean-Paul Calderone5075fce2008-09-07 20:18:55 -0400186
Jean-Paul Calderone1cb5d022008-09-07 20:58:50 -0400187 def test_load_verify_file(self):
188 """
189 L{Context.load_verify_locations} accepts a file name and uses the
190 certificates within for verification purposes.
191 """
192 cafile = self.mktemp()
193 fObj = file(cafile, 'w')
194 fObj.write(cleartextCertificatePEM)
195 fObj.close()
196
197 self._load_verify_locations_test(cafile)
198
Jean-Paul Calderone5075fce2008-09-07 20:18:55 -0400199
200 def test_load_verify_invalid_file(self):
201 """
202 L{Context.load_verify_locations} raises L{Error} when passed a
203 non-existent cafile.
204 """
205 clientContext = Context(TLSv1_METHOD)
206 self.assertRaises(
207 Error, clientContext.load_verify_locations, self.mktemp())
Jean-Paul Calderone1cb5d022008-09-07 20:58:50 -0400208
209
210 def test_load_verify_directory(self):
211 """
212 L{Context.load_verify_locations} accepts a directory name and uses
213 the certificates within for verification purposes.
214 """
215 capath = self.mktemp()
216 makedirs(capath)
217 cafile = join(capath, 'cert.pem')
218 fObj = file(cafile, 'w')
219 fObj.write(cleartextCertificatePEM)
220 fObj.close()
221
222 # Hash value computed manually with c_rehash to avoid depending on
223 # c_rehash in the test suite.
Jean-Paul Calderone20131f52009-04-01 12:05:45 -0400224 symlink('cert.pem', join(capath, 'c7adac82.0'))
Jean-Paul Calderone1cb5d022008-09-07 20:58:50 -0400225
226 self._load_verify_locations_test(None, capath)
227
228
229 def test_set_default_verify_paths(self):
230 """
231 L{Context.set_default_verify_paths} causes the platform-specific CA
232 certificate locations to be used for verification purposes.
233 """
234 # Testing this requires a server with a certificate signed by one of
235 # the CAs in the platform CA location. Getting one of those costs
236 # money. Fortunately (or unfortunately, depending on your
237 # perspective), it's easy to think of a public server on the
238 # internet which has such a certificate. Connecting to the network
239 # in a unit test is bad, but it's the only way I can think of to
240 # really test this. -exarkun
241
242 # Arg, verisign.com doesn't speak TLSv1
243 context = Context(SSLv3_METHOD)
244 context.set_default_verify_paths()
245 context.set_verify(
246 VERIFY_PEER,
247 lambda conn, cert, errno, depth, preverify_ok: preverify_ok)
248
249 client = socket()
250 client.connect(('verisign.com', 443))
251 clientSSL = Connection(context, client)
252 clientSSL.set_connect_state()
253 clientSSL.do_handshake()
254 clientSSL.send('GET / HTTP/1.0\r\n\r\n')
255 self.assertTrue(clientSSL.recv(1024))
Jean-Paul Calderone52f0d8b2009-03-07 09:10:19 -0500256 if platform == "darwin":
Jean-Paul Calderone1d287e52009-03-07 09:09:07 -0500257 test_set_default_verify_paths.todo = (
258 "set_default_verify_paths appears not to work on OS X - a "
259 "problem with the supplied OpenSSL, perhaps?")
Jean-Paul Calderone9eadb962008-09-07 21:20:44 -0400260
261
262 def test_set_default_verify_paths_signature(self):
263 """
264 L{Context.set_default_verify_paths} takes no arguments and raises
265 L{TypeError} if given any.
266 """
267 context = Context(TLSv1_METHOD)
268 self.assertRaises(TypeError, context.set_default_verify_paths, None)
269 self.assertRaises(TypeError, context.set_default_verify_paths, 1)
270 self.assertRaises(TypeError, context.set_default_verify_paths, "")
Jean-Paul Calderone327d8f92008-12-28 21:55:56 -0500271
272
273
274class ConstantsTests(TestCase):
275 """
276 Tests for the values of constants exposed in L{OpenSSL.SSL}.
277
278 These are values defined by OpenSSL intended only to be used as flags to
279 OpenSSL APIs. The only assertions it seems can be made about them is
280 their values.
281 """
Jean-Paul Calderoned811b682008-12-28 22:59:15 -0500282 # unittest.TestCase has no skip mechanism
283 if OP_NO_QUERY_MTU is not None:
284 def test_op_no_query_mtu(self):
285 """
286 The value of L{OpenSSL.SSL.OP_NO_QUERY_MTU} is 0x1000, the value of
287 I{SSL_OP_NO_QUERY_MTU} defined by I{openssl/ssl.h}.
288 """
289 self.assertEqual(OP_NO_QUERY_MTU, 0x1000)
290 else:
291 "OP_NO_QUERY_MTU unavailable - OpenSSL version may be too old"
Jean-Paul Calderone327d8f92008-12-28 21:55:56 -0500292
293
Jean-Paul Calderoned811b682008-12-28 22:59:15 -0500294 if OP_COOKIE_EXCHANGE is not None:
295 def test_op_cookie_exchange(self):
296 """
297 The value of L{OpenSSL.SSL.OP_COOKIE_EXCHANGE} is 0x2000, the value
298 of I{SSL_OP_COOKIE_EXCHANGE} defined by I{openssl/ssl.h}.
299 """
300 self.assertEqual(OP_COOKIE_EXCHANGE, 0x2000)
301 else:
302 "OP_COOKIE_EXCHANGE unavailable - OpenSSL version may be too old"
Jean-Paul Calderone327d8f92008-12-28 21:55:56 -0500303
304
Jean-Paul Calderoned811b682008-12-28 22:59:15 -0500305 if OP_NO_TICKET is not None:
306 def test_op_no_ticket(self):
307 """
308 The value of L{OpenSSL.SSL.OP_NO_TICKET} is 0x4000, the value of
309 I{SSL_OP_NO_TICKET} defined by I{openssl/ssl.h}.
310 """
311 self.assertEqual(OP_NO_TICKET, 0x4000)
312 else:
313 "OP_NO_TICKET unavailable - OpenSSL version may be too old"
Rick Dean5b7b6372009-04-01 11:34:06 -0500314
315
316if __name__ == '__main__':
317 main()
318