Merge lp:~andrefcruz/pyopenssl/pyopenssl into lp:~exarkun/pyopenssl/trunk

Proposed by André Cruz
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
Reviewer Review Type Date Requested Status
André Cruz (community) Needs Resubmitting
Jean-Paul Calderone Needs Fixing
Review via email: mp+105954@code.launchpad.net

Description of the change

Implemented the context-related functions of OpenSSL for TLS-PSK support:

void SSL_CTX_set_psk_client_callback(SSL_CTX *ctx, unsigned int (*callback)(SSL *ssl, const char *hint, char *identity, unsigned int max_identity_len, unsigned char *psk, unsigned int max_psk_len));
void SSL_CTX_set_psk_server_callback(SSL_CTX *ctx, unsigned int (*callback)(SSL *ssl, const char *identity, unsigned char *psk, int max_psk_len));
int SSL_CTX_use_psk_identity_hint(SSL_CTX *ctx, const char *hint);

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.

To post a comment you must log in.
lp:~andrefcruz/pyopenssl/pyopenssl updated
171. By André Cruz

Added tests for PSK functions.

Revision history for this message
André Cruz (andrefcruz) wrote :

Anything missing?

lp:~andrefcruz/pyopenssl/pyopenssl updated
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

Revision history for this message
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.

review: Needs Fixing
lp:~andrefcruz/pyopenssl/pyopenssl updated
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.

Revision history for this message
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?

review: Needs Resubmitting
Revision history for this message
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).

Revision history for this message
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?

Revision history for this message
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.

Revision history for this message
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?

Revision history for this message
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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'

Subscribers

People subscribed via source and target branches

to status/vote changes: