Merge lp:~ppergame/pyopenssl/next-proto into lp:~exarkun/pyopenssl/trunk

Proposed by Pavel Pergamenshchik
Status: Work in progress
Proposed branch: lp:~ppergame/pyopenssl/next-proto
Merge into: lp:~exarkun/pyopenssl/trunk
Diff against target: 751 lines (+641/-0)
5 files modified
OpenSSL/ssl/connection.c (+37/-0)
OpenSSL/ssl/connection.h (+2/-0)
OpenSSL/ssl/context.c (+374/-0)
OpenSSL/ssl/context.h (+4/-0)
OpenSSL/test/test_ssl.py (+224/-0)
To merge this branch: bzr merge lp:~ppergame/pyopenssl/next-proto
Reviewer Review Type Date Requested Status
Jean-Paul Calderone Needs Fixing
Review via email: mp+106293@code.launchpad.net

Description of the change

Next Protocol Negotiation support. I tried not to leak any memory at the OpenSSL API boundaries

To post a comment you must log in.
Revision history for this message
Jean-Paul Calderone (exarkun) wrote :

I just noticed that https://code.launchpad.net/~myers-1/pyopenssl/npn/+merge/92416 also exists. Which one of these should I choose to move forward on?

Revision history for this message
Jean-Paul Calderone (exarkun) wrote :

Looks like this one is more likely to see further activity, so I'll proceed here.

  1. There are some new XXXs in the code (copied from templates, I guess). It'd be nice if they were actually addressed instead. Since it's impractical to exercise malloc failures here, don't worry about unit testing. Just think really hard.
  2. Reference counting in ssl_Context_set_next_protos_advertised_cb is incorrect. I think it is possible for `self->next_protos_advertised_callback` to be the only thing keeping `self` alive, or perhaps for a weak reference callback or `__del__` on `self->next_protos_advertised_callback` to re-enter some part of ssl_Context_*. Since `self->next_protos_advertised_callback` is DECREFed while it is still in the `self` structure, this will probably lead to brokenness. I think this is what the Py_CLEAR macro (not heavily used throughout pyOpenSSL) is for. Same applies for next_protos_advertised_userdata, next_proto_select_callback, and next_proto_select_userdata. Maybe the (existing and new) code in `ssl_Context_clear` is fine though? I'm not clear on the re-entrancy issues of tp_clear, and I don't feel like digging them up.
  3. Some Python 3 issues in the Python code - except syntax (test_ssl.py, line 1125) and byte string literal syntax (b("foo") instead of "foo" to construct a byte string). I'm already set up to develop/test against Python 3.3 so I can take care of this if it's too much hassle for you, after the above points are addressed.

Thanks.

review: Needs Fixing

Unmerged revisions

178. By Pavel Pergamenshchik

rename get0 -> get, add proper test skipping

177. By Pavel Pergamenshchik

skip test if no NPN support in openssl

176. By Pavel Pergamenshchik

move the endif to the correct spot

175. By Pavel Pergamenshchik

pass the Connection to python next proto callbacks, use Connection threadstate

174. By Pavel Pergamenshchik

conditional compilation, more exceptions, more tests

173. By Pavel Pergamenshchik

test select returning none

172. By Pavel Pergamenshchik

get0_next_proto_negotiated tests

171. By Pavel Pergamenshchik

get0_next_proto_negotiated

170. By Pavel Pergamenshchik

npn advertisement compiles

169. By Pavel Pergamenshchik <ppergame@ppergame-glaptop>

untested next_proto_advertised_cb

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'OpenSSL/ssl/connection.c'
2--- OpenSSL/ssl/connection.c 2012-03-11 01:18:02 +0000
3+++ OpenSSL/ssl/connection.c 2012-05-18 00:57:18 +0000
4@@ -1396,6 +1396,30 @@
5 return Py_None;
6 }
7
8+#if OPENSSL_VERSION_NUMBER >= 0x10001000L && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG)
9+static char ssl_Connection_get_next_proto_negotiated_doc[] = "\n\
10+Returns the next proto selected by the client\n\
11+\n\
12+:return: Non-empty next proto string, or None if there isn't one\n\
13+";
14+static PyObject *
15+ssl_Connection_get_next_proto_negotiated(ssl_ConnectionObj *self, PyObject *args)
16+{
17+ const char *data;
18+ unsigned len;
19+ if (!PyArg_ParseTuple(args, ":set_next_proto_select_cb"))
20+ return NULL;
21+
22+ SSL_get0_next_proto_negotiated(self->ssl, (const unsigned char **)&data, &len);
23+ if (!data) {
24+ Py_INCREF(Py_None);
25+ return Py_None;
26+ } else {
27+ return PyBytes_FromStringAndSize(data, len);
28+ }
29+}
30+#endif
31+
32 /*
33 * Member methods in the Connection object
34 * ADD_METHOD(name) expands to a correct PyMethodDef declaration
35@@ -1453,6 +1477,9 @@
36 ADD_METHOD(set_connect_state),
37 ADD_METHOD(get_session),
38 ADD_METHOD(set_session),
39+#if OPENSSL_VERSION_NUMBER >= 0x10001000L && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG)
40+ ADD_METHOD(get_next_proto_negotiated),
41+#endif
42 { NULL, NULL }
43 };
44 #undef ADD_ALIAS
45@@ -1518,6 +1545,10 @@
46 SSL_set_fd(self->ssl, (SOCKET_T)fd);
47 }
48 }
49+
50+ self->last_next_protos_advertised_str = NULL;
51+ self->last_next_proto_select_str = NULL;
52+
53 return self;
54
55 error:
56@@ -1632,6 +1663,12 @@
57 self->app_data = NULL;
58 self->into_ssl = NULL; /* was cleaned up by SSL_free() */
59 self->from_ssl = NULL; /* was cleaned up by SSL_free() */
60+
61+ PyMem_Free(self->last_next_protos_advertised_str);
62+ self->last_next_protos_advertised_str = NULL;
63+ PyMem_Free(self->last_next_proto_select_str);
64+ self->last_next_proto_select_str = NULL;
65+
66 return 0;
67 }
68
69
70=== modified file 'OpenSSL/ssl/connection.h'
71--- OpenSSL/ssl/connection.h 2011-03-03 00:26:20 +0000
72+++ OpenSSL/ssl/connection.h 2012-05-18 00:57:18 +0000
73@@ -45,6 +45,8 @@
74 PyThreadState *tstate; /* This field is no longer used. */
75 PyObject *app_data;
76 BIO *into_ssl, *from_ssl; /* for connections without file descriptors */
77+ unsigned char *last_next_protos_advertised_str;
78+ unsigned char *last_next_proto_select_str;
79 } ssl_ConnectionObj;
80
81
82
83=== modified file 'OpenSSL/ssl/context.c'
84--- OpenSSL/ssl/context.c 2012-03-09 23:04:04 +0000
85+++ OpenSSL/ssl/context.c 2012-05-18 00:57:18 +0000
86@@ -148,6 +148,276 @@
87 return len;
88 }
89
90+#if OPENSSL_VERSION_NUMBER >= 0x10001000L && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG)
91+/*
92+ * Globally defined next protocol advertisement callback. This is called from
93+ * OpenSSL internally. Return the encoded next proto list.
94+ *
95+ * Arguments: ssl - OpenSSL context
96+ * out - Pointer to the pointer to be set to the output string of
97+ * of the form '\003foo\004barf'
98+ * outlen - Pointer to the value to be set to length of output
99+ * string
100+ * arg - User data, always a Context object
101+ * Returns: SSL_TLSEXT_ERR_OK or SSL_TLSEXT_ERR_ALERT_FATAL
102+ */
103+static int
104+global_next_protos_advertised_callback(SSL *ssl, const unsigned char **out,
105+ unsigned int *outlen, void *arg)
106+{
107+ int ssl_ret = SSL_TLSEXT_ERR_ALERT_FATAL;
108+ char *out_str = NULL, *cur_pos, *raw_str;
109+ Py_ssize_t str_size, final_size = 0, num_protos, ctr;
110+ PyObject *argv, *protos = NULL, *proto;
111+ ssl_ConnectionObj *conn = (ssl_ConnectionObj *)SSL_get_app_data(ssl);
112+ ssl_ContextObj *ctx = conn->context;
113+
114+ /*
115+ * GIL isn't held yet. First things first - acquire it, or any Python API
116+ * we invoke might segfault or blow up the sun. The reverse will be done
117+ * before returning.
118+ */
119+ MY_END_ALLOW_THREADS(conn->tstate);
120+
121+ /*
122+ * Allocation model: OpenSSL promises to "keep a reference" to the output
123+ * string. Actually, OpenSSL makes a copy shortly after this callback
124+ * returns, so we have to keep a pointer to the string and deallocate
125+ * it later. There is a race condition between OpenSSL using the output
126+ * string and calling this callback again. Don't like it? Blame the
127+ * Chromium contributor who put this idiocy into OpenSSL.
128+ */
129+ PyMem_Free(conn->last_next_protos_advertised_str);
130+ conn->last_next_protos_advertised_str = NULL;
131+
132+ /* The Python callback is called with a (conn, userdata) tuple */
133+ argv = Py_BuildValue("(OO)", conn, ctx->next_protos_advertised_userdata);
134+
135+ /*
136+ * XXX Didn't check argv to see if it was NULL. -exarkun
137+ */
138+ protos = PyEval_CallObject(ctx->next_protos_advertised_callback, argv);
139+ Py_DECREF(argv);
140+
141+ if (protos == NULL) {
142+ /*
143+ * The callback raised an exception. It will be raised by whatever
144+ * Python API triggered this callback.
145+ */
146+ goto out;
147+ }
148+
149+ if (protos == Py_None) {
150+ /*
151+ * Don't advertise next proto at all
152+ */
153+ goto out;
154+ }
155+
156+ if (!PySequence_Check(protos) || PyBytes_Check(protos)) {
157+ /*
158+ * Got non-sequence or a string
159+ */
160+ PyErr_SetString(PyExc_TypeError, "next_protos_advertised_callback"
161+ "returned a value that is not a list of strings");
162+ goto out;
163+ }
164+
165+ num_protos = PySequence_Size(protos);
166+ if (num_protos == -1)
167+ {
168+ goto out;
169+ }
170+
171+ // first pass to find out how much memory to allocate
172+ for (ctr = 0; ctr < num_protos; ctr++) {
173+ proto = PySequence_GetItem(protos, ctr);
174+ if (!proto) {
175+ goto out;
176+ }
177+ if (!PyBytes_Check(proto)) {
178+ Py_DECREF(proto);
179+ PyErr_Format(PyExc_TypeError, "next_protos_advertised_callback"
180+ "return sequence contains a non-string");
181+ goto out;
182+ }
183+ str_size = PyBytes_Size(proto);
184+ if (str_size == -1) {
185+ Py_DECREF(proto);
186+ goto out;
187+ } else if (str_size < 1 || str_size > 255) {
188+ Py_DECREF(proto);
189+ PyErr_Format(PyExc_TypeError, "next_protos_advertised_callback"
190+ "return sequence contains an empty string or a string"
191+ "larger than 255 bytes");
192+ goto out;
193+ }
194+ final_size = final_size + 1 + str_size;
195+ Py_DECREF(proto);
196+ }
197+
198+ cur_pos = out_str = PyMem_Malloc(final_size);
199+
200+ for (ctr = 0; ctr < num_protos; ctr++) {
201+ proto = PySequence_GetItem(protos, ctr);
202+ if (PyBytes_AsStringAndSize(proto, &raw_str, &str_size) == -1) {
203+ PyMem_Free(out_str);
204+ goto out;
205+ }
206+
207+ *cur_pos = str_size;
208+ memcpy(cur_pos + 1, raw_str, str_size);
209+ cur_pos = cur_pos + 1 + str_size;
210+ Py_DECREF(proto);
211+ }
212+
213+ *out = conn->last_next_protos_advertised_str = (unsigned char *)out_str;
214+ *outlen = final_size;
215+ ssl_ret = SSL_TLSEXT_ERR_OK;
216+
217+out:
218+ Py_XDECREF(protos);
219+ /*
220+ * This function is returning into OpenSSL. Release the GIL again.
221+ */
222+ MY_BEGIN_ALLOW_THREADS(conn->tstate);
223+ return ssl_ret;
224+}
225+
226+/*
227+ * Globally defined next protocol selection callback. This is called from
228+ * OpenSSL internally. Return the selected next proto string.
229+ *
230+ * Arguments: ssl - OpenSSL context
231+ * out - Pointer to the pointer to be set to the next protocol
232+ * string
233+ * outlen - Pointer to the value to be set to length of output
234+ * string
235+ * in - Encoded string of next protos advertised by the server
236+ * inlen - Length of in
237+ * arg - User data, always a Context object
238+ * Returns: SSL_TLSEXT_ERR_OK or SSL_TLSEXT_ERR_ALERT_FATAL
239+ */
240+static int
241+global_next_proto_select_callback(SSL *ssl, unsigned char **out,
242+ unsigned char *outlen, const unsigned char *in, unsigned int inlen,
243+ void *arg)
244+{
245+ int ssl_ret = SSL_TLSEXT_ERR_ALERT_FATAL, ret;
246+ char *out_str, *raw_str;
247+ const unsigned char *cur_pos;
248+ unsigned char proto_size;
249+ PyObject *argv, *proto = NULL, *proto_list, *proto_bytes;
250+ Py_ssize_t str_size;
251+ ssl_ConnectionObj *conn = (ssl_ConnectionObj *)SSL_get_app_data(ssl);
252+ ssl_ContextObj *ctx = conn->context;
253+
254+ /*
255+ * GIL isn't held yet. First things first - acquire it, or any Python API
256+ * we invoke might segfault or blow up the sun. The reverse will be done
257+ * before returning.
258+ */
259+ MY_END_ALLOW_THREADS(ctx->tstate);
260+
261+ /*
262+ * Allocation model: OpenSSL promises to "keep a reference" to the output
263+ * string. Actually, OpenSSL makes a copy shortly after this callback
264+ * returns, so we have to keep a pointer to the string and deallocate
265+ * it later. There is a race condition between OpenSSL using the output
266+ * string and calling this callback again. Don't like it? Blame the
267+ * Chromium contributor who put this idiocy into OpenSSL.
268+ */
269+ PyMem_Free(conn->last_next_proto_select_str);
270+ conn->last_next_proto_select_str = NULL;
271+
272+ proto_list = PyList_New(0);
273+ if (!proto_list) {
274+ goto out;
275+ }
276+ cur_pos = in;
277+ while (in + inlen > cur_pos) {
278+ proto_size = *cur_pos++;
279+ if (in + inlen < cur_pos + proto_size) {
280+ Py_DECREF(proto_list);
281+ PyErr_Format(PyExc_RuntimeError, "next_proto_select_callback"
282+ "received improperly encoded proto list from OpenSSL");
283+ goto out;
284+ }
285+ proto_bytes = PyBytes_FromStringAndSize((char *)cur_pos, proto_size);
286+ if (!proto_bytes) {
287+ Py_DECREF(proto_list);
288+ goto out;
289+ }
290+ ret = PyList_Append(proto_list, proto_bytes);
291+ Py_DECREF(proto_bytes);
292+ if (ret) {
293+ Py_DECREF(proto_list);
294+ goto out;
295+ }
296+ cur_pos += proto_size;
297+ }
298+
299+ /* The Python callback is called with a (proto_list, conn, userdata) tuple */
300+ argv = Py_BuildValue("(OOO)", proto_list, conn,
301+ ctx->next_proto_select_userdata);
302+ Py_DECREF(proto_list);
303+
304+ /*
305+ * XXX Didn't check argv to see if it was NULL. -exarkun
306+ */
307+ proto = PyEval_CallObject(ctx->next_proto_select_callback, argv);
308+ Py_DECREF(argv);
309+
310+ if (proto == NULL) {
311+ /*
312+ * The callback raised an exception. It will be raised by whatever
313+ * Python API triggered this callback.
314+ */
315+ goto out;
316+ }
317+
318+ if (proto == Py_None) {
319+ /*
320+ * No proto to select results in connection loss
321+ */
322+ goto out;
323+ }
324+
325+ if (!PyBytes_Check(proto)) {
326+ /*
327+ * Not a string
328+ */
329+ PyErr_Format(PyExc_TypeError, "next_proto_select_callback"
330+ "returned neither string nor None");
331+ goto out;
332+ }
333+
334+ if (PyBytes_AsStringAndSize(proto, &raw_str, &str_size) == -1) {
335+ goto out;
336+ }
337+ if (str_size < 1 || str_size > 255) {
338+ PyErr_Format(PyExc_TypeError, "next_proto_select_callback"
339+ "returned empty string or string larger than 255 bytes");
340+ goto out;
341+ }
342+
343+ out_str = PyMem_Malloc(str_size);
344+ memcpy(out_str, raw_str, str_size);
345+
346+ *out = conn->last_next_proto_select_str = (unsigned char *)out_str;
347+ *outlen = str_size;
348+ ssl_ret = SSL_TLSEXT_ERR_OK;
349+
350+out:
351+ Py_XDECREF(proto);
352+ /*
353+ * This function is returning into OpenSSL. Release the GIL again.
354+ */
355+ MY_BEGIN_ALLOW_THREADS(conn->tstate);
356+ return ssl_ret;
357+}
358+#endif
359+
360 /*
361 * Globally defined verify callback
362 *
363@@ -387,6 +657,88 @@
364 return Py_None;
365 }
366
367+#if OPENSSL_VERSION_NUMBER >= 0x10001000L && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG)
368+static char ssl_Context_set_next_protos_advertised_cb_doc[] = "\n\
369+Set the callback that returns next protos advertised by this server\n\
370+\n\
371+:param callback: The Python callback to use. Takes a Connection and userdata.\n\
372+ Returns a sequence of strings, or None to suppress next proto\n\
373+ support\n\
374+:param userdata: (optional) A Python object which will be given as\n\
375+ argument to the callback\n\
376+:return: None\n\
377+";
378+static PyObject *
379+ssl_Context_set_next_protos_advertised_cb(ssl_ContextObj *self, PyObject *args)
380+{
381+ PyObject *callback = NULL, *userdata = Py_None;
382+
383+ if (!PyArg_ParseTuple(args, "O|O:set_next_protos_advertised_cb", &callback,
384+ &userdata))
385+ return NULL;
386+
387+ if (!PyCallable_Check(callback))
388+ {
389+ PyErr_SetString(PyExc_TypeError, "expected PyCallable");
390+ return NULL;
391+ }
392+
393+ Py_DECREF(self->next_protos_advertised_callback);
394+ Py_INCREF(callback);
395+ self->next_protos_advertised_callback = callback;
396+
397+ Py_DECREF(self->next_protos_advertised_userdata);
398+ Py_INCREF(userdata);
399+ self->next_protos_advertised_userdata = userdata;
400+
401+ SSL_CTX_set_next_protos_advertised_cb(self->ctx,
402+ global_next_protos_advertised_callback, NULL);
403+
404+ Py_INCREF(Py_None);
405+ return Py_None;
406+}
407+
408+static char ssl_Context_set_next_proto_select_cb_doc[] = "\n\
409+Set the callback that returns next proto selected by this client\n\
410+\n\
411+:param callback: The Python callback to use. Takes a list of next proto\n\
412+ strings, a Connection and userdata. Returns the next proto\n\
413+ string, or None to terminate the connection\n\
414+:param userdata: (optional) A Python object which will be given as\n\
415+ argument to the callback\n\
416+:return: None\n\
417+";
418+static PyObject *
419+ssl_Context_set_next_proto_select_cb(ssl_ContextObj *self, PyObject *args)
420+{
421+ PyObject *callback = NULL, *userdata = Py_None;
422+
423+ if (!PyArg_ParseTuple(args, "O|O:set_next_proto_select_cb", &callback,
424+ &userdata))
425+ return NULL;
426+
427+ if (!PyCallable_Check(callback))
428+ {
429+ PyErr_SetString(PyExc_TypeError, "expected PyCallable");
430+ return NULL;
431+ }
432+
433+ Py_DECREF(self->next_proto_select_callback);
434+ Py_INCREF(callback);
435+ self->next_proto_select_callback = callback;
436+
437+ Py_DECREF(self->next_proto_select_userdata);
438+ Py_INCREF(userdata);
439+ self->next_proto_select_userdata = userdata;
440+
441+ SSL_CTX_set_next_proto_select_cb(self->ctx,
442+ global_next_proto_select_callback, NULL);
443+
444+ Py_INCREF(Py_None);
445+ return Py_None;
446+}
447+#endif
448+
449 static PyTypeObject *
450 type_modified_error(const char *name) {
451 PyErr_Format(PyExc_RuntimeError,
452@@ -1199,6 +1551,10 @@
453 static PyMethodDef ssl_Context_methods[] = {
454 ADD_METHOD(load_verify_locations),
455 ADD_METHOD(set_passwd_cb),
456+#if OPENSSL_VERSION_NUMBER >= 0x10001000L && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG)
457+ ADD_METHOD(set_next_protos_advertised_cb),
458+ ADD_METHOD(set_next_proto_select_cb),
459+#endif
460 ADD_METHOD(set_default_verify_paths),
461 ADD_METHOD(use_certificate_chain_file),
462 ADD_METHOD(use_certificate_file),
463@@ -1276,6 +1632,10 @@
464 Py_INCREF(Py_None);
465 self->passphrase_callback = Py_None;
466 Py_INCREF(Py_None);
467+ self->next_protos_advertised_callback = Py_None;
468+ Py_INCREF(Py_None);
469+ self->next_proto_select_callback = Py_None;
470+ Py_INCREF(Py_None);
471 self->verify_callback = Py_None;
472 Py_INCREF(Py_None);
473 self->info_callback = Py_None;
474@@ -1287,6 +1647,12 @@
475 self->passphrase_userdata = Py_None;
476
477 Py_INCREF(Py_None);
478+ self->next_protos_advertised_userdata = Py_None;
479+
480+ Py_INCREF(Py_None);
481+ self->next_proto_select_userdata = Py_None;
482+
483+ Py_INCREF(Py_None);
484 self->app_data = Py_None;
485
486 /* Some initialization that's required to operate smoothly in Python */
487@@ -1378,6 +1744,14 @@
488 self->passphrase_callback = NULL;
489 Py_XDECREF(self->passphrase_userdata);
490 self->passphrase_userdata = NULL;
491+ Py_XDECREF(self->next_protos_advertised_callback);
492+ self->next_protos_advertised_callback = NULL;
493+ Py_XDECREF(self->next_protos_advertised_userdata);
494+ self->next_protos_advertised_userdata = NULL;
495+ Py_XDECREF(self->next_proto_select_callback);
496+ self->next_proto_select_callback = NULL;
497+ Py_XDECREF(self->next_proto_select_userdata);
498+ self->next_proto_select_userdata = NULL;
499 Py_XDECREF(self->verify_callback);
500 self->verify_callback = NULL;
501 Py_XDECREF(self->info_callback);
502
503=== modified file 'OpenSSL/ssl/context.h'
504--- OpenSSL/ssl/context.h 2011-05-26 22:47:00 +0000
505+++ OpenSSL/ssl/context.h 2012-05-18 00:57:18 +0000
506@@ -27,6 +27,10 @@
507 SSL_CTX *ctx;
508 PyObject *passphrase_callback,
509 *passphrase_userdata,
510+ *next_protos_advertised_callback,
511+ *next_protos_advertised_userdata,
512+ *next_proto_select_callback,
513+ *next_proto_select_userdata,
514 *verify_callback,
515 *info_callback,
516 *tlsext_servername_callback,
517
518=== modified file 'OpenSSL/test/test_ssl.py'
519--- OpenSSL/test/test_ssl.py 2012-03-09 23:27:50 +0000
520+++ OpenSSL/test/test_ssl.py 2012-05-18 00:57:18 +0000
521@@ -961,6 +961,230 @@
522
523
524
525+class NextProtocolNegotiationTests(TestCase, _LoopbackMixin):
526+ """
527+ Tests for :py:obj:`Context.set_next_protos_advertised_callback`,
528+ :py:obj:`Context.set_next_proto_select_callback` and
529+ :py:obj:`Connection.get_next_proto_negotiated`
530+ """
531+ if not hasattr(Connection, 'get_next_proto_negotiated'):
532+ 'Next proto negotiation is not supported by this OpenSSL version'
533+ else:
534+ def tearDown(self):
535+ self.serverContext = self.clientContext = self.server = self.client = None
536+
537+ def initNextProto(self, advertise = None, select = None):
538+ """
539+ Set up a client and a server :py:obj:`Connection`, and set their
540+ next proto negotiation callbacks
541+ """
542+
543+ serverContext = Context(TLSv1_METHOD)
544+ if advertise:
545+ serverContext.set_next_protos_advertised_cb(advertise)
546+ serverContext.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem))
547+ serverContext.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem))
548+
549+ server = Connection(serverContext, None)
550+ server.set_accept_state()
551+
552+ clientContext = Context(TLSv1_METHOD)
553+ if select:
554+ clientContext.set_next_proto_select_cb(select)
555+ client = Connection(clientContext, None)
556+ client.set_connect_state()
557+
558+ self.serverContext = serverContext
559+ self.clientContext = clientContext
560+ self.server = server
561+ self.client = client
562+
563+
564+ def test_select_no_advertise(self):
565+ """
566+ Next proto select callback isn't called if the server
567+ does not advertise
568+ """
569+
570+ select_calls = []
571+ def select(protos, conn, extra):
572+ select_calls.append((protos, conn, extra))
573+
574+ self.initNextProto(select = select)
575+
576+ self._interactInMemory(self.client, self.server)
577+
578+ self.assertEquals(select_calls, [])
579+
580+ self.assertEquals(self.client.get_next_proto_negotiated(), None)
581+ self.assertEquals(self.server.get_next_proto_negotiated(), None)
582+
583+
584+ def test_select_raises(self):
585+ """
586+ Exceptions in next proto select callback are propagated to the caller
587+ """
588+
589+ def advertise(conn, extra):
590+ return ['spdy/2.0', 'http/1.1']
591+ def select(protos, conn, extra):
592+ 1/0
593+
594+ self.initNextProto(advertise = advertise, select = select)
595+ self.assertRaises(ZeroDivisionError,
596+ self._interactInMemory, self.client, self.server)
597+
598+
599+ def test_select_bad(self):
600+ """
601+ TypeError is raised if select callback returns a bogus value
602+ """
603+
604+ bad_returns = [5, '', 'x' * 256]
605+
606+ def advertise(conn, extra):
607+ return ['spdy/2.0', 'http/1.1']
608+
609+ for bad in bad_returns:
610+ self.initNextProto(advertise = advertise, select = lambda protos, extra: bad)
611+ self.assertRaises(TypeError,
612+ self._interactInMemory, self.client, self.server)
613+
614+
615+ def test_advertise_raises(self):
616+ """
617+ Exceptions in next proto advertisement callback are propagated
618+ to the caller
619+ """
620+
621+ def advertise(conn, extra):
622+ 1/0
623+ def select(protos, conn, extra):
624+ return 'gopher'
625+
626+ self.initNextProto(advertise = advertise, select = select)
627+ self.assertRaises(ZeroDivisionError,
628+ self._interactInMemory, self.client, self.server)
629+
630+
631+ def test_advertise_bad(self):
632+ """
633+ TypeError is raised if select callback returns a bogus value
634+ """
635+
636+ bad_returns = ['nntp', ['imap4', 6], [''], ['x' * 256]]
637+ def select(protos, conn, extra):
638+ return 'gopher'
639+
640+ for bad in bad_returns:
641+ self.initNextProto(advertise = lambda extra: bad, select = select)
642+ self.assertRaises(TypeError,
643+ self._interactInMemory, self.client, self.server)
644+
645+
646+ def test_advertise_no_select(self):
647+ """
648+ Next proto advertisement callback isn't called if the client
649+ doesn't request next proto support
650+ """
651+
652+ advertise_calls = []
653+ def advertise(conn, extra):
654+ advertise_calls.append((conn, extra))
655+ return ['spdy/2.0', 'http/1.1']
656+
657+ self.initNextProto(advertise = advertise)
658+
659+ self._interactInMemory(self.client, self.server)
660+
661+ self.assertEquals(advertise_calls, [])
662+
663+ self.assertEquals(self.client.get_next_proto_negotiated(), None)
664+ self.assertEquals(self.server.get_next_proto_negotiated(), None)
665+
666+
667+ def test_select_none(self):
668+ """
669+ Connection is terminated if the client does not choose a next proto
670+ """
671+ advertise_calls = []
672+ def advertise(conn, extra):
673+ advertise_calls.append((conn, extra))
674+ return ['spdy/2.0', 'http/1.1']
675+
676+ select_calls = []
677+ def select(protos, conn, extra):
678+ select_calls.append((protos, conn, extra))
679+ # OpenSSL treats this as a fatal condition
680+ return None
681+
682+ self.initNextProto(advertise = advertise, select = select)
683+
684+ try:
685+ self._interactInMemory(self.client, self.server)
686+ except Error, e:
687+ self.assertEquals(e.args, ([('SSL routines', 'SSL3_GET_SERVER_HELLO', 'parse tlsext')],))
688+
689+ self.assertEquals(advertise_calls, [(self.server, None)])
690+ self.assertEquals(select_calls, [(['spdy/2.0', 'http/1.1'], self.client, None)])
691+
692+ self.assertEquals(self.client.get_next_proto_negotiated(), None)
693+ self.assertEquals(self.server.get_next_proto_negotiated(), None)
694+
695+
696+ def test_advertise_select(self):
697+ """
698+ Client and server negotiate next proto
699+ """
700+
701+ advertise_calls = []
702+ def advertise(conn, extra):
703+ advertise_calls.append((conn, extra))
704+ return ['spdy/2.0', 'http/1.1']
705+
706+ select_calls = []
707+ def select(protos, conn, extra):
708+ select_calls.append((protos, conn, extra))
709+ return 'gopher'
710+
711+ self.initNextProto(advertise = advertise, select = select)
712+
713+ self._interactInMemory(self.client, self.server)
714+
715+ self.assertEquals(advertise_calls, [(self.server, None)])
716+ self.assertEquals(select_calls, [(['spdy/2.0', 'http/1.1'], self.client, None)])
717+
718+ self.assertEquals(self.client.get_next_proto_negotiated(), 'gopher')
719+ self.assertEquals(self.server.get_next_proto_negotiated(), 'gopher')
720+
721+
722+ def test_advertise_empty(self):
723+ """
724+ Client receives empty next proto list sent by the server
725+ """
726+
727+ advertise_calls = []
728+ def advertise(conn, extra):
729+ advertise_calls.append((conn, extra))
730+ return []
731+
732+ select_calls = []
733+ def select(protos, conn, extra):
734+ select_calls.append((protos, conn, extra))
735+ return 'gopher'
736+
737+ self.initNextProto(advertise = advertise, select = select)
738+
739+ self._interactInMemory(self.client, self.server)
740+
741+ self.assertEquals(advertise_calls, [(self.server, None)])
742+ self.assertEquals(select_calls, [([], self.client, None)])
743+
744+ self.assertEquals(self.client.get_next_proto_negotiated(), 'gopher')
745+ self.assertEquals(self.server.get_next_proto_negotiated(), 'gopher')
746+
747+
748+
749 class ServerNameCallbackTests(TestCase, _LoopbackMixin):
750 """
751 Tests for :py:obj:`Context.set_tlsext_servername_callback` and its interaction with

Subscribers

People subscribed via source and target branches

to status/vote changes: