Merge lp:~ppergame/pyopenssl/next-proto into lp:~exarkun/pyopenssl/trunk
- next-proto
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jean-Paul Calderone | Needs Fixing | ||
Review via email: mp+106293@code.launchpad.net |
Commit message
Description of the change
Next Protocol Negotiation support. I tried not to leak any memory at the OpenSSL API boundaries
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_
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.
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_negotiate d tests - 171. By Pavel Pergamenshchik
-
get0_next_
proto_negotiate d - 170. By Pavel Pergamenshchik
-
npn advertisement compiles
- 169. By Pavel Pergamenshchik <ppergame@ppergame-glaptop>
-
untested next_proto_
advertised_ cb
Preview Diff
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 |
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?