Merge lp:~andrefcruz/pyopenssl/pyopenssl into lp:~exarkun/pyopenssl/trunk
- pyopenssl
- Merge into trunk
Status: | Work in progress |
---|---|
Proposed branch: | lp:~andrefcruz/pyopenssl/pyopenssl |
Merge into: | lp:~exarkun/pyopenssl/trunk |
Diff against target: |
870 lines (+724/-27) 6 files modified
OpenSSL/ssl/context.c (+279/-3) OpenSSL/ssl/context.h (+2/-0) OpenSSL/test/test_ssl.py (+362/-24) OpenSSL/test/util.py (+4/-0) examples/psk/client.py (+36/-0) examples/psk/server.py (+41/-0) |
To merge this branch: | bzr merge lp:~andrefcruz/pyopenssl/pyopenssl |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
André Cruz (community) | Needs Resubmitting | ||
Jean-Paul Calderone | Needs Fixing | ||
Review via email: mp+105954@code.launchpad.net |
Commit message
Description of the change
Implemented the context-related functions of OpenSSL for TLS-PSK support:
void SSL_CTX_
void SSL_CTX_
int SSL_CTX_
Based on the tlsext servername support. This is my first experiment with embedding Python in C, so a proper review is needed, specially regarding the reference counts.
- 171. By André Cruz
-
Added tests for PSK functions.
- 172. By Jean-Paul Calderone
-
Python 3 compatibility
- 173. By Jean-Paul Calderone
-
Update the tests for PSK to use bytes instead of unicode
- 174. By Jean-Paul Calderone
-
Some reformatting, some comment adjustments, and some observations about issues
- 175. By Jean-Paul Calderone
-
Add a test for reference counting on the server psk callback too
- 176. By Jean-Paul Calderone
-
Add tests for exceptions raised by both psk callbacks and make them pass instead of segfaulting
- 177. By Jean-Paul Calderone
-
Handle non-tuple return value case of psk client callback
- 178. By Jean-Paul Calderone
-
Check non-bytes return value case of psk server callback and make it pass
- 179. By Jean-Paul Calderone
-
Use BYTESTRING_FMT instead of "s" for Python 3.3 compatibility - makes existing tests pass on Python 3.3.
- 180. By Jean-Paul Calderone
-
Check and require that bytes are returned in the tuple returned by the psk client callback
Jean-Paul Calderone (exarkun) wrote : | # |
Hi André,
I pushed a branch based on yours to <lp:~exarkun/pyopenssl/psk>. I added some more unit tests and fixed some bugs. There are some bugs remaining, I've marked the ones I noticed with XXX comments.
Thanks.
- 181. By André Cruz
-
Do something meaningful when the values exceed the maximum size.
- 182. By André Cruz
-
Account for the NULL byte when checking the maximum size.
- 183. By André Cruz
-
Don't swallow exceptions.
- 184. By André Cruz
-
Check length of psk returned from callback.
- 185. By André Cruz
-
Check for exception.
André Cruz (andrefcruz) wrote : | # |
I've been trying to find some time to fix the last XXX, the one about the name of the method, but it has not been easy. I think I know how to do it, I would fetch the 'name' attribute of the function object, but as I don't normally use the Python C API I would have to investigate. Is it really important?
Jean-Paul Calderone (exarkun) wrote : | # |
It's probably not *really* important. It would make the exceptions easier to understand. I would be happy enough to land this branch without that fix - just file a new bug report to keep track of the issue.
An unrelated comment though - make sure you also add unit tests for each of the cases represented by the other XXX comments (or make an existing, failing unit test pass - but iirc they were all passing).
André Cruz (andrefcruz) wrote : | # |
I'll file the new bug report when this is merged, otherwise I will be referring to code that is not in the project yet.
The other XXX comment was copied from already existing code. Do you want me to remove it?
Jean-Paul Calderone (exarkun) wrote : | # |
Thanks. I'm paying attention to this issue, but a little short on time to re-review the code. I will get back to you on these questions ASAP.
Dima Q (dimaqq) wrote : | # |
> Thanks. I'm paying attention to this issue, but a little short on time to re-
> review the code. I will get back to you on these questions ASAP.
and?
Jean-Paul Calderone (exarkun) wrote : | # |
Sorry. I was paying attention but then I forgot.
At this point the next step is to port the code to pyOpenSSL master@HEAD (now on github). Then I can re-review it and see what issues remaining, if any.
The good news is that the implementation of callbacks is much, much simplified in master@HEAD. Once the port is done these XXXs may just disappear.
Unmerged revisions
- 185. By André Cruz
-
Check for exception.
- 184. By André Cruz
-
Check length of psk returned from callback.
- 183. By André Cruz
-
Don't swallow exceptions.
- 182. By André Cruz
-
Account for the NULL byte when checking the maximum size.
- 181. By André Cruz
-
Do something meaningful when the values exceed the maximum size.
- 180. By Jean-Paul Calderone
-
Check and require that bytes are returned in the tuple returned by the psk client callback
- 179. By Jean-Paul Calderone
-
Use BYTESTRING_FMT instead of "s" for Python 3.3 compatibility - makes existing tests pass on Python 3.3.
- 178. By Jean-Paul Calderone
-
Check non-bytes return value case of psk server callback and make it pass
- 177. By Jean-Paul Calderone
-
Handle non-tuple return value case of psk client callback
- 176. By Jean-Paul Calderone
-
Add tests for exceptions raised by both psk callbacks and make them pass instead of segfaulting
Preview Diff
1 | === modified file 'OpenSSL/ssl/context.c' |
2 | --- OpenSSL/ssl/context.c 2012-03-09 23:04:04 +0000 |
3 | +++ OpenSSL/ssl/context.c 2012-10-21 08:20:25 +0000 |
4 | @@ -113,7 +113,7 @@ |
5 | * Returned "", or None, or something. Treat it as no passphrase. |
6 | */ |
7 | Py_DECREF(ret); |
8 | - goto out; |
9 | + goto out; |
10 | } |
11 | |
12 | if (!PyBytes_Check(ret)) { |
13 | @@ -267,8 +267,195 @@ |
14 | argv = Py_BuildValue("(O)", (PyObject *)conn); |
15 | ret = PyEval_CallObject(conn->context->tlsext_servername_callback, argv); |
16 | Py_DECREF(argv); |
17 | + |
18 | + if (ret == NULL) { |
19 | + /* |
20 | + * XXX - This should be reported somehow. -exarkun |
21 | + */ |
22 | + PyErr_Clear(); |
23 | + } else { |
24 | + Py_DECREF(ret); |
25 | + } |
26 | + |
27 | + /* |
28 | + * This function is returning into OpenSSL. Release the GIL again. |
29 | + */ |
30 | + MY_BEGIN_ALLOW_THREADS(conn->tstate); |
31 | + return result; |
32 | +} |
33 | + |
34 | +/* |
35 | + * Globally defined PSK client identity callback. This is called from OpenSSL |
36 | + * internally. The GIL will not be held when this function is invoked. It must |
37 | + * not be held when the function returns. |
38 | + * |
39 | + * ssl represents the connection this callback is for |
40 | + * |
41 | + * hint is a NULL-terminated PSK identity hint sent by the server |
42 | + * |
43 | + * identity where the the resulting NUL-terminated identity is to be stored |
44 | + * |
45 | + * max_identity_len maximum length of identity. Do not write more bytes to the |
46 | + * identity buffer than this (including trailing NUL). |
47 | + * |
48 | + * psk where the resulting pre-shared key is to be stored (not NUL terminated). |
49 | + * |
50 | + * max_psk_len maximum length of PSK. Do not write more bytes to the psk buffer |
51 | + * than this. |
52 | + * |
53 | + * On error, 0 is returned. Otherwise, the length of the pre-shared key is |
54 | + * returned. |
55 | + * |
56 | + */ |
57 | +static unsigned int |
58 | +global_psk_client_callback(SSL *ssl, const char *hint, char *identity, |
59 | + unsigned int max_identity_len, unsigned char *psk, |
60 | + unsigned int max_psk_len) { |
61 | + int result = 0; |
62 | + PyObject *argv, *ret; |
63 | + ssl_ConnectionObj *conn = (ssl_ConnectionObj *)SSL_get_app_data(ssl); |
64 | + char *ret_identity; |
65 | + char *ret_psk; |
66 | + Py_ssize_t ret_id_len, ret_psk_len; |
67 | + int parsed; |
68 | + |
69 | + /* |
70 | + * GIL isn't held yet. First things first - acquire it, or any Python API |
71 | + * we invoke might segfault or blow up the sun. The reverse will be done |
72 | + * before returning. |
73 | + */ |
74 | + MY_END_ALLOW_THREADS(conn->tstate); |
75 | + |
76 | + argv = Py_BuildValue("(O" BYTESTRING_FMT ")", (PyObject *)conn, hint); |
77 | + ret = PyEval_CallObject(conn->context->psk_client_callback, argv); |
78 | + Py_DECREF(argv); |
79 | + |
80 | + if (NULL == ret) { |
81 | + goto done; |
82 | + } |
83 | + |
84 | + /* XXX "psk_client_callback" should probably be the name of the actual |
85 | + * callback object, but that's probably super hard. */ |
86 | + |
87 | + if (!PyTuple_CheckExact(ret)) { |
88 | + PyErr_SetString( |
89 | + PyExc_TypeError, |
90 | + "psk_client_callback must return a two-tuple of bytes"); |
91 | + goto done; |
92 | + } |
93 | + |
94 | + if (PyTuple_Size(ret) != 2) { |
95 | + PyErr_SetString( |
96 | + PyExc_TypeError, |
97 | + "psk_client_callback must return a two-tuple of bytes"); |
98 | + goto done; |
99 | + } |
100 | + |
101 | + if (!PyBytes_CheckExact(PyTuple_GetItem(ret, 0))) { |
102 | + PyErr_SetString( |
103 | + PyExc_TypeError, |
104 | + "psk_client_callback must return a two-tuple of bytes"); |
105 | + goto done; |
106 | + } |
107 | + |
108 | + if (!PyBytes_CheckExact(PyTuple_GetItem(ret, 1))) { |
109 | + PyErr_SetString( |
110 | + PyExc_TypeError, |
111 | + "psk_client_callback must return a two-tuple of bytes"); |
112 | + goto done; |
113 | + } |
114 | + |
115 | + parsed = PyBytes_AsStringAndSize( |
116 | + PyTuple_GetItem(ret, 0), &ret_identity, &ret_id_len); |
117 | + if (-1 == parsed) { |
118 | + Py_DECREF(ret); |
119 | + goto done; |
120 | + } |
121 | + parsed = PyBytes_AsStringAndSize( |
122 | + PyTuple_GetItem(ret, 1), &ret_psk, &ret_psk_len); |
123 | + if (-1 == parsed) { |
124 | + Py_DECREF(ret); |
125 | + goto done; |
126 | + } |
127 | Py_DECREF(ret); |
128 | |
129 | + if (ret_id_len < max_identity_len && ret_psk_len <= max_psk_len) { |
130 | + strncpy(identity, ret_identity, ret_id_len); |
131 | + strncpy((char *)psk, (char *)ret_psk, ret_psk_len); |
132 | + result = ret_psk_len; |
133 | + } else { |
134 | + PyErr_SetString( |
135 | + PyExc_ValueError, |
136 | + "length of values to return exceeded maximum"); |
137 | + goto done; |
138 | + } |
139 | + |
140 | + |
141 | + done: |
142 | + /* |
143 | + * This function is returning into OpenSSL. Release the GIL again. |
144 | + */ |
145 | + MY_BEGIN_ALLOW_THREADS(conn->tstate); |
146 | + return result; |
147 | +} |
148 | + |
149 | +/* |
150 | + * Globally defined PSK server identity callback. This is called from |
151 | + * OpenSSL internally. The GIL will not be held when this function is invoked. |
152 | + * It must not be held when the function returns. |
153 | + * |
154 | + * ssl represents the connection this callback is for |
155 | + * |
156 | + * identity NUL-terminated PSK identity sent by the client |
157 | + * |
158 | + * psk where the resulting pre-shared key is to be stored (not NUL terminated) |
159 | + * |
160 | + * max_psk_len The maximum length of psk |
161 | + * |
162 | + */ |
163 | +static unsigned int |
164 | +global_psk_server_callback(SSL *ssl, const char *identity, unsigned char *psk, |
165 | + unsigned int max_psk_len) { |
166 | + int result = 0; |
167 | + char *str; |
168 | + PyObject *argv, *ret; |
169 | + ssl_ConnectionObj *conn = (ssl_ConnectionObj *)SSL_get_app_data(ssl); |
170 | + |
171 | + /* |
172 | + * GIL isn't held yet. First things first - acquire it, or any Python API |
173 | + * we invoke might segfault or blow up the sun. The reverse will be done |
174 | + * before returning. |
175 | + */ |
176 | + MY_END_ALLOW_THREADS(conn->tstate); |
177 | + |
178 | + argv = Py_BuildValue("(O" BYTESTRING_FMT ")", (PyObject *)conn, identity); |
179 | + ret = PyEval_CallObject(conn->context->psk_server_callback, argv); |
180 | + Py_DECREF(argv); |
181 | + |
182 | + if (NULL == ret) { |
183 | + goto done; |
184 | + } |
185 | + |
186 | + if (!PyBytes_CheckExact(ret)) { |
187 | + PyErr_SetString(PyExc_TypeError, "psk_server_callback must return bytes"); |
188 | + goto done; |
189 | + } |
190 | + |
191 | + str = PyBytes_AsString(ret); |
192 | + if (str) { |
193 | + if (strlen(str) > max_psk_len) { |
194 | + PyErr_SetString( |
195 | + PyExc_TypeError, |
196 | + "psk_server_callback returned a string too long"); |
197 | + goto done; |
198 | + } |
199 | + |
200 | + strncpy((char *)psk, str, max_psk_len); |
201 | + result = PyBytes_Size(ret); |
202 | + Py_XDECREF(ret); |
203 | + } |
204 | + |
205 | + done: |
206 | /* |
207 | * This function is returning into OpenSSL. Release the GIL again. |
208 | */ |
209 | @@ -562,7 +749,7 @@ |
210 | if (cert == NULL) { |
211 | return NULL; |
212 | } |
213 | - |
214 | + |
215 | if (!SSL_CTX_use_certificate(self->ctx, cert->x509)) |
216 | { |
217 | exception_from_error_queue(ssl_Error); |
218 | @@ -894,6 +1081,32 @@ |
219 | } |
220 | } |
221 | |
222 | +static char ssl_Context_use_psk_identity_hint_doc[] = "\n\ |
223 | +Set PSK identity hint to use\n\ |
224 | +\n\ |
225 | +:param hint: Identity hint to send\n\ |
226 | +:return: None\n\ |
227 | +"; |
228 | +static PyObject * |
229 | +ssl_Context_use_psk_identity_hint(ssl_ContextObj *self, PyObject *args) |
230 | +{ |
231 | + char *hint; |
232 | + |
233 | + if (!PyArg_ParseTuple(args, BYTESTRING_FMT ":use_psk_identity_hint", &hint)) |
234 | + return NULL; |
235 | + |
236 | + if (!SSL_CTX_use_psk_identity_hint(self->ctx, hint)) |
237 | + { |
238 | + exception_from_error_queue(ssl_Error); |
239 | + return NULL; |
240 | + } |
241 | + else |
242 | + { |
243 | + Py_INCREF(Py_None); |
244 | + return Py_None; |
245 | + } |
246 | +} |
247 | + |
248 | static char ssl_Context_set_client_ca_list_doc[] = "\n\ |
249 | Set the list of preferred client certificate signers for this server context.\n\ |
250 | \n\ |
251 | @@ -1186,6 +1399,67 @@ |
252 | return Py_None; |
253 | } |
254 | |
255 | +static char ssl_Context_set_psk_client_callback_doc[] = "\n\ |
256 | +Specify a callback function to be called when the client is sending\n\ |
257 | + the ClientKeyExchange message to the server.\n\ |
258 | +\n\ |
259 | +:param callback: The callback function. It will be invoked with\n\ |
260 | + the Connection instance, a NULL-terminated PSK identity hint sent\n\ |
261 | + by the server in parameter hint, a buffer identity of length\n\ |
262 | + max_identity_len bytes where the the resulting NULL-terminated\n\ |
263 | + identity is to be stored, and a buffer psk of length max_psk_len\n\ |
264 | + bytes where the resulting pre-shared key is to be stored.\n\ |
265 | +\n\ |
266 | +"; |
267 | +static PyObject * |
268 | +ssl_Context_set_psk_client_callback(ssl_ContextObj *self, PyObject *args) { |
269 | + PyObject *callback; |
270 | + PyObject *old; |
271 | + |
272 | + if (!PyArg_ParseTuple(args, "O:set_psk_client_callback", &callback)) { |
273 | + return NULL; |
274 | + } |
275 | + |
276 | + Py_INCREF(callback); |
277 | + old = self->psk_client_callback; |
278 | + self->psk_client_callback = callback; |
279 | + Py_XDECREF(old); |
280 | + |
281 | + SSL_CTX_set_psk_client_callback(self->ctx, global_psk_client_callback); |
282 | + |
283 | + Py_INCREF(Py_None); |
284 | + return Py_None; |
285 | +} |
286 | + |
287 | +static char ssl_Context_set_psk_server_callback_doc[] = "\n\ |
288 | +Specify a callback function to be called when the server receives\n\ |
289 | + the ClientKeyExchange message from the client.\n\ |
290 | +\n\ |
291 | +:param callback: The callback function. It will be invoked with\n\ |
292 | + the Connection instance, a NULL-terminated PSK identity sent\n\ |
293 | + by the client in parameter identity, and a buffer psk of length max_psk_len\n\ |
294 | + bytes where the resulting pre-shared key is to be stored.\n\ |
295 | +\n\ |
296 | +"; |
297 | +static PyObject * |
298 | +ssl_Context_set_psk_server_callback(ssl_ContextObj *self, PyObject *args) { |
299 | + PyObject *callback; |
300 | + PyObject *old; |
301 | + |
302 | + if (!PyArg_ParseTuple(args, "O:set_psk_server_callback", &callback)) { |
303 | + return NULL; |
304 | + } |
305 | + |
306 | + Py_INCREF(callback); |
307 | + old = self->psk_server_callback; |
308 | + self->psk_server_callback = callback; |
309 | + Py_XDECREF(old); |
310 | + |
311 | + SSL_CTX_set_psk_server_callback(self->ctx, global_psk_server_callback); |
312 | + |
313 | + Py_INCREF(Py_None); |
314 | + return Py_None; |
315 | +} |
316 | |
317 | /* |
318 | * Member methods in the Context object |
319 | @@ -1228,6 +1502,9 @@ |
320 | ADD_METHOD(set_options), |
321 | ADD_METHOD(set_mode), |
322 | ADD_METHOD(set_tlsext_servername_callback), |
323 | + ADD_METHOD(use_psk_identity_hint), |
324 | + ADD_METHOD(set_psk_client_callback), |
325 | + ADD_METHOD(set_psk_server_callback), |
326 | { NULL, NULL } |
327 | }; |
328 | #undef ADD_METHOD |
329 | @@ -1474,4 +1751,3 @@ |
330 | |
331 | return 1; |
332 | } |
333 | - |
334 | |
335 | === modified file 'OpenSSL/ssl/context.h' |
336 | --- OpenSSL/ssl/context.h 2011-05-26 22:47:00 +0000 |
337 | +++ OpenSSL/ssl/context.h 2012-10-21 08:20:25 +0000 |
338 | @@ -30,6 +30,8 @@ |
339 | *verify_callback, |
340 | *info_callback, |
341 | *tlsext_servername_callback, |
342 | + *psk_server_callback, |
343 | + *psk_client_callback, |
344 | *app_data; |
345 | PyThreadState *tstate; |
346 | } ssl_ContextObj; |
347 | |
348 | === modified file 'OpenSSL/test/test_ssl.py' |
349 | --- OpenSSL/test/test_ssl.py 2012-03-09 23:27:50 +0000 |
350 | +++ OpenSSL/test/test_ssl.py 2012-10-21 08:20:25 +0000 |
351 | @@ -37,7 +37,7 @@ |
352 | from OpenSSL.SSL import ( |
353 | Context, ContextType, Session, Connection, ConnectionType) |
354 | |
355 | -from OpenSSL.test.util import TestCase, bytes, b |
356 | +from OpenSSL.test.util import TestCase, bytes, b, u |
357 | from OpenSSL.test.test_crypto import ( |
358 | cleartextCertificatePEM, cleartextPrivateKeyPEM) |
359 | from OpenSSL.test.test_crypto import ( |
360 | @@ -221,6 +221,29 @@ |
361 | return server, client |
362 | |
363 | |
364 | + def _handshake_test(self, serverContext, clientContext): |
365 | + """ |
366 | + Verify that a client and server created with the given contexts can |
367 | + successfully handshake and communicate. |
368 | + """ |
369 | + serverSocket, clientSocket = socket_pair() |
370 | + |
371 | + server = Connection(serverContext, serverSocket) |
372 | + server.set_accept_state() |
373 | + |
374 | + client = Connection(clientContext, clientSocket) |
375 | + client.set_connect_state() |
376 | + |
377 | + # Make them talk to each other. |
378 | + # self._interactInMemory(client, server) |
379 | + for i in range(3): |
380 | + for s in [client, server]: |
381 | + try: |
382 | + s.do_handshake() |
383 | + except WantReadError: |
384 | + pass |
385 | + |
386 | + |
387 | def _interactInMemory(self, client_conn, server_conn): |
388 | """ |
389 | Try to read application bytes from each of the two :py:obj:`Connection` |
390 | @@ -756,29 +779,6 @@ |
391 | self.assertRaises(TypeError, context.add_extra_chain_cert, object(), object()) |
392 | |
393 | |
394 | - def _handshake_test(self, serverContext, clientContext): |
395 | - """ |
396 | - Verify that a client and server created with the given contexts can |
397 | - successfully handshake and communicate. |
398 | - """ |
399 | - serverSocket, clientSocket = socket_pair() |
400 | - |
401 | - server = Connection(serverContext, serverSocket) |
402 | - server.set_accept_state() |
403 | - |
404 | - client = Connection(clientContext, clientSocket) |
405 | - client.set_connect_state() |
406 | - |
407 | - # Make them talk to each other. |
408 | - # self._interactInMemory(client, server) |
409 | - for i in range(3): |
410 | - for s in [client, server]: |
411 | - try: |
412 | - s.do_handshake() |
413 | - except WantReadError: |
414 | - pass |
415 | - |
416 | - |
417 | def test_add_extra_chain_cert(self): |
418 | """ |
419 | :py:obj:`Context.add_extra_chain_cert` accepts an :py:obj:`X509` instance to add to |
420 | @@ -1060,6 +1060,344 @@ |
421 | self.assertEqual([(server, b("foo1.example.com"))], args) |
422 | |
423 | |
424 | +class PSKKeyCallbackTests(TestCase, _LoopbackMixin): |
425 | + """ |
426 | + Tests for :py:obj:`Context.set_psk_client_callback`, |
427 | + :py:obj:`Context.set_psk_server_callback` and |
428 | + :py:obj:`Context.use_psk_identity_hint`. |
429 | + """ |
430 | + def test_wrong_args(self): |
431 | + """ |
432 | + :py:obj:`Context.set_psk_client_callback` raises :py:obj:`TypeError` if called |
433 | + with other than one argument. |
434 | + """ |
435 | + context = Context(SSLv3_METHOD) |
436 | + self.assertRaises(TypeError, context.set_psk_client_callback) |
437 | + self.assertRaises( |
438 | + TypeError, context.set_psk_client_callback, 1, 2) |
439 | + |
440 | + def test_old_client_callback_forgotten(self): |
441 | + """ |
442 | + If :py:obj:`Context.set_psk_client_callback` is used to specify a new |
443 | + callback, the one it replaces is dereferenced. |
444 | + """ |
445 | + def callback(connection): |
446 | + pass |
447 | + |
448 | + def replacement(connection): |
449 | + pass |
450 | + |
451 | + context = Context(SSLv3_METHOD) |
452 | + context.set_psk_client_callback(callback) |
453 | + |
454 | + tracker = ref(callback) |
455 | + del callback |
456 | + |
457 | + context.set_psk_client_callback(replacement) |
458 | + collect() |
459 | + self.assertIdentical(None, tracker()) |
460 | + |
461 | + |
462 | + def test_old_server_callback_forgotten(self): |
463 | + """ |
464 | + If :py:obj:`Context.set_psk_server_callback` is used to specify a new |
465 | + callback, the one it replaces is dereferenced. |
466 | + """ |
467 | + def callback(connection): |
468 | + pass |
469 | + |
470 | + def replacement(connection): |
471 | + pass |
472 | + |
473 | + context = Context(SSLv3_METHOD) |
474 | + context.set_psk_server_callback(callback) |
475 | + |
476 | + tracker = ref(callback) |
477 | + del callback |
478 | + |
479 | + context.set_psk_server_callback(replacement) |
480 | + collect() |
481 | + self.assertIdentical(None, tracker()) |
482 | + |
483 | + |
484 | + def test_correct_parameters_and_key(self): |
485 | + """ |
486 | + All the psk callbacks receive the correct parameters and the SSL session |
487 | + is correctly initialized. |
488 | + """ |
489 | + client_args = [] |
490 | + def psk_client_callback(conn, hint): |
491 | + client_args.append((conn, hint)) |
492 | + return (b('client identity'), b('\xbe\xef')) |
493 | + client_context = Context(SSLv3_METHOD) |
494 | + client_context.set_psk_client_callback(psk_client_callback) |
495 | + |
496 | + # Lose our reference to it. The Context is responsible for keeping it |
497 | + # alive now. |
498 | + del psk_client_callback |
499 | + collect() |
500 | + |
501 | + server_args = [] |
502 | + def psk_server_callback(conn, identity): |
503 | + server_args.append((conn, identity)) |
504 | + return b('\xbe\xef') |
505 | + server_context = Context(SSLv3_METHOD) |
506 | + server_context.use_psk_identity_hint(b('This is the hint')) |
507 | + server_context.set_psk_server_callback(psk_server_callback) |
508 | + |
509 | + # Lose our reference to it. The Context is responsible for keeping it |
510 | + # alive now. |
511 | + del psk_server_callback |
512 | + collect() |
513 | + |
514 | + # Do a little connection to trigger the logic |
515 | + server = Connection(server_context, None) |
516 | + server.set_accept_state() |
517 | + |
518 | + client = Connection(client_context, None) |
519 | + client.set_connect_state() |
520 | + |
521 | + self._interactInMemory(server, client) |
522 | + |
523 | + self.assertEqual([(client, b('This is the hint'))], client_args) |
524 | + self.assertEqual([(server, b('client identity'))], server_args) |
525 | + |
526 | + |
527 | + def test_client_callback_exception(self): |
528 | + """ |
529 | + If the psk client callback function raises an exception, that exception |
530 | + is raised during the handshake (perhaps by L{Connection.do_handshake}, |
531 | + perhaps by L{Connection.recv}). |
532 | + """ |
533 | + class SomeException(Exception): |
534 | + pass |
535 | + |
536 | + def broken_psk_callback(connection, hint): |
537 | + raise SomeException() |
538 | + |
539 | + client_context = Context(SSLv3_METHOD) |
540 | + client_context.set_psk_client_callback(broken_psk_callback) |
541 | + |
542 | + def psk_server_callback(connection, identity): |
543 | + return b("foo") |
544 | + |
545 | + server_context = Context(SSLv3_METHOD) |
546 | + server_context.use_psk_identity_hint(b('This is the hint')) |
547 | + server_context.set_psk_server_callback(psk_server_callback) |
548 | + |
549 | + # Do a little connection to trigger the logic |
550 | + server = Connection(server_context, None) |
551 | + server.set_accept_state() |
552 | + |
553 | + client = Connection(client_context, None) |
554 | + client.set_connect_state() |
555 | + |
556 | + self.assertRaises( |
557 | + SomeException, self._interactInMemory, server, client) |
558 | + |
559 | + |
560 | + def test_server_callback_exception(self): |
561 | + """ |
562 | + If the psk server callback function raises an exception, that exception |
563 | + is raised during the handshake (perhaps by L{Connection.do_handshake}, |
564 | + perhaps by L{Connection.recv}). |
565 | + """ |
566 | + class SomeException(Exception): |
567 | + pass |
568 | + |
569 | + def psk_client_callback(connection, hint): |
570 | + return b('foo'), b('bar') |
571 | + |
572 | + client_context = Context(SSLv3_METHOD) |
573 | + client_context.set_psk_client_callback(psk_client_callback) |
574 | + |
575 | + def broken_psk_callback(connection, identity): |
576 | + raise SomeException() |
577 | + |
578 | + server_context = Context(SSLv3_METHOD) |
579 | + server_context.use_psk_identity_hint(b('This is the hint')) |
580 | + server_context.set_psk_server_callback(broken_psk_callback) |
581 | + |
582 | + # Do a little connection to trigger the logic |
583 | + server = Connection(server_context, None) |
584 | + server.set_accept_state() |
585 | + |
586 | + client = Connection(client_context, None) |
587 | + client.set_connect_state() |
588 | + |
589 | + self.assertRaises( |
590 | + SomeException, self._interactInMemory, server, client) |
591 | + |
592 | + |
593 | + def test_client_callback_non_tuple_result(self): |
594 | + """ |
595 | + If the psk client callback function returns an object that isn't a |
596 | + tuple, L{TypeError} is raised during the handshake (perhaps by |
597 | + L{Connection.do_handshake}, perhaps by L{Connection.recv}). |
598 | + """ |
599 | + def broken_client_callback(connection, hint): |
600 | + return 123 |
601 | + |
602 | + client_context = Context(SSLv3_METHOD) |
603 | + client_context.set_psk_client_callback(broken_client_callback) |
604 | + |
605 | + def psk_server_callback(connection, identity): |
606 | + return b"foo" |
607 | + |
608 | + server_context = Context(SSLv3_METHOD) |
609 | + server_context.use_psk_identity_hint(b('This is the hint')) |
610 | + server_context.set_psk_server_callback(psk_server_callback) |
611 | + |
612 | + # Do a little connection to trigger the logic |
613 | + server = Connection(server_context, None) |
614 | + server.set_accept_state() |
615 | + |
616 | + client = Connection(client_context, None) |
617 | + client.set_connect_state() |
618 | + |
619 | + exc = self.assertRaises( |
620 | + TypeError, self._interactInMemory, server, client) |
621 | + self.assertEqual( |
622 | + "psk_client_callback must return a two-tuple of bytes", str(exc)) |
623 | + |
624 | + |
625 | + def test_client_callback_non_bytes_result(self): |
626 | + """ |
627 | + If the psk client callback function returns a tuple containing non-bytes |
628 | + objects, L{TypeError} is raised during the handshake (perhaps by |
629 | + L{Connection.do_handshake}, perhaps by L{Connection.recv}). |
630 | + """ |
631 | + def broken_client_callback(connection, hint): |
632 | + return u("foo"), u("bar") |
633 | + |
634 | + client_context = Context(SSLv3_METHOD) |
635 | + client_context.set_psk_client_callback(broken_client_callback) |
636 | + |
637 | + def psk_server_callback(connection, identity): |
638 | + return b"bar" |
639 | + |
640 | + server_context = Context(SSLv3_METHOD) |
641 | + server_context.use_psk_identity_hint(b('This is the hint')) |
642 | + server_context.set_psk_server_callback(psk_server_callback) |
643 | + |
644 | + # Do a little connection to trigger the logic |
645 | + server = Connection(server_context, None) |
646 | + server.set_accept_state() |
647 | + |
648 | + client = Connection(client_context, None) |
649 | + client.set_connect_state() |
650 | + |
651 | + exc = self.assertRaises( |
652 | + TypeError, self._interactInMemory, server, client) |
653 | + self.assertEqual( |
654 | + "psk_client_callback must return a two-tuple of bytes", str(exc)) |
655 | + |
656 | + |
657 | + def test_server_callback_non_bytes_result(self): |
658 | + """ |
659 | + If the psk client callback function returns an object that isn't a |
660 | + tuple, L{TypeError} is raised during the handshake (perhaps by |
661 | + L{Connection.do_handshake}, perhaps by L{Connection.recv}). |
662 | + """ |
663 | + def psk_client_callback(connection, hint): |
664 | + return b("foo"), b("bar") |
665 | + |
666 | + client_context = Context(SSLv3_METHOD) |
667 | + client_context.set_psk_client_callback(psk_client_callback) |
668 | + |
669 | + def broken_server_callback(connection, identity): |
670 | + return 123 |
671 | + |
672 | + server_context = Context(SSLv3_METHOD) |
673 | + server_context.use_psk_identity_hint(b('This is the hint')) |
674 | + server_context.set_psk_server_callback(broken_server_callback) |
675 | + |
676 | + # Do a little connection to trigger the logic |
677 | + server = Connection(server_context, None) |
678 | + server.set_accept_state() |
679 | + |
680 | + client = Connection(client_context, None) |
681 | + client.set_connect_state() |
682 | + |
683 | + exc = self.assertRaises( |
684 | + TypeError, self._interactInMemory, server, client) |
685 | + self.assertEqual( |
686 | + "psk_server_callback must return bytes", str(exc)) |
687 | + |
688 | + |
689 | + def test_incorrect_key(self): |
690 | + """ |
691 | + The keys presented by both endpoints differ. |
692 | + """ |
693 | + def psk_client_callback(conn, hint): |
694 | + return (b('client identity'), b('123')) |
695 | + client_context = Context(SSLv3_METHOD) |
696 | + client_context.set_psk_client_callback(psk_client_callback) |
697 | + |
698 | + # Lose our reference to it. The Context is responsible for keeping it |
699 | + # alive now. |
700 | + del psk_client_callback |
701 | + collect() |
702 | + |
703 | + def psk_server_callback(conn, identity): |
704 | + return b('456') |
705 | + server_context = Context(SSLv3_METHOD) |
706 | + server_context.use_psk_identity_hint(b('This is the hint')) |
707 | + server_context.set_psk_server_callback(psk_server_callback) |
708 | + |
709 | + # Lose our reference to it. The Context is responsible for keeping it |
710 | + # alive now. |
711 | + del psk_server_callback |
712 | + collect() |
713 | + |
714 | + # Do a little connection to trigger the logic |
715 | + server = Connection(server_context, None) |
716 | + server.set_accept_state() |
717 | + |
718 | + client = Connection(client_context, None) |
719 | + client.set_connect_state() |
720 | + |
721 | + self.assertRaises(Error, self._interactInMemory, server, client) |
722 | + |
723 | + |
724 | + def test_callbacks_called(self): |
725 | + """ |
726 | + Both callbacks are called. |
727 | + """ |
728 | + cb_args = [] |
729 | + |
730 | + def psk_client_callback(*args): |
731 | + cb_args.append(args) |
732 | + return (b('client identity'), b('123')) |
733 | + client_context = Context(SSLv3_METHOD) |
734 | + client_context.set_psk_client_callback(psk_client_callback) |
735 | + |
736 | + # Lose our reference to it. The Context is responsible for keeping it |
737 | + # alive now. |
738 | + del psk_client_callback |
739 | + collect() |
740 | + |
741 | + def psk_server_callback(*args): |
742 | + cb_args.append(args) |
743 | + return b('456') |
744 | + server_context = Context(SSLv3_METHOD) |
745 | + server_context.use_psk_identity_hint(b('This is the hint')) |
746 | + server_context.set_psk_server_callback(psk_server_callback) |
747 | + |
748 | + # Lose our reference to it. The Context is responsible for keeping it |
749 | + # alive now. |
750 | + del psk_server_callback |
751 | + collect() |
752 | + |
753 | + # Do a little connection to trigger the logic |
754 | + server = Connection(server_context, None) |
755 | + server.set_accept_state() |
756 | + |
757 | + client = Connection(client_context, None) |
758 | + client.set_connect_state() |
759 | + |
760 | + self._interactInMemory(server, client) |
761 | + self.assertEquals(2, len(cb_args)) |
762 | |
763 | class SessionTests(TestCase): |
764 | """ |
765 | |
766 | === modified file 'OpenSSL/test/util.py' |
767 | --- OpenSSL/test/util.py 2011-07-16 05:14:58 +0000 |
768 | +++ OpenSSL/test/util.py 2012-10-21 08:20:25 +0000 |
769 | @@ -19,9 +19,13 @@ |
770 | def b(s): |
771 | return s |
772 | bytes = str |
773 | + def u(s): |
774 | + return s.decode("charmap") |
775 | else: |
776 | def b(s): |
777 | return s.encode("charmap") |
778 | + def u(s): |
779 | + return s |
780 | bytes = bytes |
781 | |
782 | |
783 | |
784 | === added directory 'examples/psk' |
785 | === added file 'examples/psk/client.py' |
786 | --- examples/psk/client.py 1970-01-01 00:00:00 +0000 |
787 | +++ examples/psk/client.py 2012-10-21 08:20:25 +0000 |
788 | @@ -0,0 +1,36 @@ |
789 | +# Copyright (C) Jean-Paul Calderone |
790 | +# See LICENSE for details. |
791 | + |
792 | +if __name__ == '__main__': |
793 | + import client |
794 | + raise SystemExit(client.main()) |
795 | + |
796 | +from sys import argv, stdout |
797 | +from socket import socket |
798 | + |
799 | +from OpenSSL.SSL import Context, Connection, SSLv3_METHOD |
800 | + |
801 | +def main(): |
802 | + """ |
803 | + Connect to a server and establish an SSL session based on a PSK. |
804 | + """ |
805 | + client = socket() |
806 | + |
807 | + print 'Connecting...', |
808 | + stdout.flush() |
809 | + client.connect(('127.0.0.1', 8443)) |
810 | + print 'connected', client.getpeername() |
811 | + |
812 | + ctx = Context(SSLv3_METHOD) |
813 | + ctx.set_psk_client_callback(callback) |
814 | + client_ssl = Connection(ctx, client) |
815 | + client_ssl.set_connect_state() |
816 | + client_ssl.do_handshake() |
817 | + client_ssl.write('DATA\n') |
818 | + data = client_ssl.read(1024) |
819 | + print 'Data received: {0}'.format(data) |
820 | + client_ssl.close() |
821 | + |
822 | +def callback(connection, hint): |
823 | + print 'GOT HINT {0}'.format(hint) |
824 | + return ("Client_identity", '\xbe\xef') |
825 | |
826 | === added file 'examples/psk/server.py' |
827 | --- examples/psk/server.py 1970-01-01 00:00:00 +0000 |
828 | +++ examples/psk/server.py 2012-10-21 08:20:25 +0000 |
829 | @@ -0,0 +1,41 @@ |
830 | +# Copyright (C) Jean-Paul Calderone |
831 | +# See LICENSE for details. |
832 | + |
833 | +if __name__ == '__main__': |
834 | + import server |
835 | + raise SystemExit(server.main()) |
836 | + |
837 | +from sys import stdout |
838 | +from socket import SOL_SOCKET, SO_REUSEADDR, socket |
839 | + |
840 | +from OpenSSL.SSL import SSLv3_METHOD, Context, Connection |
841 | + |
842 | +def main(): |
843 | + """ |
844 | + Run a server which selects the correct PSK. |
845 | + """ |
846 | + port = socket() |
847 | + port.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) |
848 | + port.bind(('', 8443)) |
849 | + port.listen(3) |
850 | + |
851 | + print 'Accepting...', |
852 | + stdout.flush() |
853 | + server, addr = port.accept() |
854 | + print 'accepted', addr |
855 | + |
856 | + server_context = Context(SSLv3_METHOD) |
857 | + server_context.use_psk_identity_hint('This is the hint') |
858 | + server_context.set_psk_server_callback(callback) |
859 | + |
860 | + server_ssl = Connection(server_context, server) |
861 | + server_ssl.set_accept_state() |
862 | + server_ssl.do_handshake() |
863 | + data = server_ssl.read(1024) |
864 | + print 'Data received: {0}'.format(data) |
865 | + server_ssl.send('ACK') |
866 | + server.close() |
867 | + |
868 | +def callback(connection, identity): |
869 | + print 'GOT IDENTITY {0}'.format(identity) |
870 | + return '\xbe\xef' |
Anything missing?