Merge lp:~zseil/pyopenssl/client_CA into lp:~exarkun/pyopenssl/trunk
- client_CA
- Merge into trunk
Status: | Superseded | ||||
---|---|---|---|---|---|
Proposed branch: | lp:~zseil/pyopenssl/client_CA | ||||
Merge into: | lp:~exarkun/pyopenssl/trunk | ||||
Diff against target: | None lines | ||||
To merge this branch: | bzr merge lp:~zseil/pyopenssl/client_CA | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ziga Seilnacht (community) | Needs Resubmitting | ||
Jean-Paul Calderone | Needs Fixing | ||
Review via email: mp+10972@code.launchpad.net |
This proposal has been superseded by a proposal from 2009-09-02.
Commit message
Description of the change
Ziga Seilnacht (zseil) wrote : | # |
Jean-Paul Calderone (exarkun) wrote : | # |
Hi Žiga,
First, thanks for working on this. :)
It would be great to see the CA management API additions separated out from the other bug fixes. I think the new methods look like they should go in (I need to spend a little more time looking at the code and reading about the relevant OpenSSL APIs to be sure). Some of the other changes are a bit trickier though. It'd be great to get the CA stuff in without having to think about all the consequences of changing the type names of all the types in pyOpenSSL, for example. :) Similarly, I think some of the unrelated fixes (eg, for NULL return checks) should probably have test coverage added, but figuring out exactly how to test those may take some consideration.
Thanks again. Looking forward to the next version of the branch.
- 131. By Ziga Seilnacht
-
Revert all changes unrelated to the new *client_CA* functionality.
Ziga Seilnacht (zseil) wrote : | # |
> Hi Žiga,
>
> First, thanks for working on this. :)
>
> It would be great to see the CA management API additions separated out from
> the other bug fixes. I think the new methods look like they should go in (I
> need to spend a little more time looking at the code and reading about the
> relevant OpenSSL APIs to be sure). Some of the other changes are a bit
> trickier though. It'd be great to get the CA stuff in without having to think
> about all the consequences of changing the type names of all the types in
> pyOpenSSL, for example. :) Similarly, I think some of the unrelated fixes
> (eg, for NULL return checks) should probably have test coverage added, but
> figuring out exactly how to test those may take some consideration.
>
> Thanks again. Looking forward to the next version of the branch.
Hello Jean-Paul,
Thank you for reviewing :)
I reverted the unrelated changes now, I'll submit them as separate issues in the next few days.
- 132. By Ziga Seilnacht
-
Clarify documentation for SSL.Context.
set_client_ CA_list and remove silly comments. - 133. By Ziga Seilnacht
-
Lowercase *client_CA* methods for consistency with the rest of PyOpenSSL.
- 134. By Jean-Paul Calderone
-
Break up big set_client_ca_list test into a bunch of smaller ones
- 135. By Jean-Paul Calderone
-
Break up big add_client_ca test into a bunch of smaller ones
- 136. By Jean-Paul Calderone
-
doc formatting and wording tweak
- 137. By Jean-Paul Calderone
-
remove pre-2.5 warning with PyObject_
GetAttrString; reformat some braces - 138. By Jean-Paul Calderone
-
a few more brace re-arrangements
- 139. By Jean-Paul Calderone
-
Minimal tests for Context.
add_extra_ chain_cert - 140. By Ziga Seilnacht
-
Hack setup.py until MinGW builds without errors.
In addition, describe in detail what is needed for building on Windows.
Unmerged revisions
- 140. By Ziga Seilnacht
-
Hack setup.py until MinGW builds without errors.
In addition, describe in detail what is needed for building on Windows.
Preview Diff
1 | === modified file 'doc/pyOpenSSL.tex' |
2 | --- doc/pyOpenSSL.tex 2009-07-25 16:32:46 +0000 |
3 | +++ doc/pyOpenSSL.tex 2009-09-01 11:31:36 +0000 |
4 | @@ -189,8 +189,8 @@ |
5 | \end{datadesc} |
6 | |
7 | \begin{classdesc}{X509Extension}{typename, critical, value\optional{, subject}\optional{, issuer}} |
8 | -A class representing an X.509 v3 certificate extensions. |
9 | -See \url{http://openssl.org/docs/apps/x509v3_config.html\#STANDARD_EXTENSIONS} |
10 | +A class representing an X.509 v3 certificate extensions. |
11 | +See \url{http://openssl.org/docs/apps/x509v3_config.html\#STANDARD_EXTENSIONS} |
12 | for \var{typename} strings and their options. |
13 | Optional parameters \var{subject} and \var{issuer} must be X509 objects. |
14 | \end{classdesc} |
15 | @@ -673,7 +673,7 @@ |
16 | \end{funcdesc} |
17 | |
18 | \begin{excdesc}{Error} |
19 | -If the current RAND method supports any errors, this is raised when needed. |
20 | +If the current RAND method supports any errors, this is raised when needed. |
21 | The default method does not raise this when the entropy pool is depleted. |
22 | |
23 | Whenever this exception is raised directly, it has a list of error messages |
24 | @@ -853,6 +853,24 @@ |
25 | when requesting a client certificate. |
26 | \end{methoddesc} |
27 | |
28 | +\begin{methoddesc}[Context]{set_client_CA_list}{certificate_authorities} |
29 | +Replace the current list of \class{OpenSSL.crypto.X509Name} objects that |
30 | +would be sent to the client when requesting a client certificate with a |
31 | +new one. |
32 | + |
33 | +\versionadded{0.10} |
34 | +\end{methoddesc} |
35 | + |
36 | +\begin{methoddesc}[Context]{add_client_CA}{certificate_authority} |
37 | +Extract a \class{OpenSSL.crypto.X509Name} from the \var{certificate_authority} |
38 | +\class{OpenSSL.crypto.X509} certificate and add it to the list of preferred |
39 | +certificate signers sent to the client when requesting a client certificate. |
40 | +% Certificate certificate certificate. Certificate certificate. Certificate |
41 | +% certificate certificate certificate certificate certificate. Certificate. |
42 | + |
43 | +\versionadded{0.10} |
44 | +\end{methoddesc} |
45 | + |
46 | \begin{methoddesc}[Context]{load_verify_locations}{pemfile, capath} |
47 | Specify where CA certificates for verification purposes are located. These |
48 | are trusted certificates. Note that the certificates have to be in PEM |
49 | @@ -1026,6 +1044,20 @@ |
50 | but not it returns the entire list in one go. |
51 | \end{methoddesc} |
52 | |
53 | +\begin{methoddesc}[Connection]{get_client_CA_list}{} |
54 | +Retrieve the list of preferred client certificate issuers sent by the server |
55 | +as \class{OpenSSL.crypto.X509Name} objects. |
56 | + |
57 | +If this is a client \class{Connection}, the list will be empty until the |
58 | +connection with the server is established. |
59 | + |
60 | +If this is a server \class{Connection}, return the list of certificate |
61 | +authorities that will be sent or has been sent to the client, as controlled |
62 | +by this \class{Connection}'s \class{Context}. |
63 | + |
64 | +\versionadded{0.10} |
65 | +\end{methoddesc} |
66 | + |
67 | \begin{methoddesc}[Connection]{get_context}{} |
68 | Retrieve the Context object associated with this Connection. |
69 | \end{methoddesc} |
70 | |
71 | === modified file 'doc/tools/texinputs/howto.cls' |
72 | --- doc/tools/texinputs/howto.cls 2008-02-19 01:50:23 +0000 |
73 | +++ doc/tools/texinputs/howto.cls 2009-09-01 11:27:38 +0000 |
74 | @@ -6,6 +6,7 @@ |
75 | \ProvidesClass{howto} |
76 | [1998/02/25 Document class (Python HOWTO)] |
77 | |
78 | +\RequirePackage{ifpdf} |
79 | \RequirePackage{pypaper} |
80 | |
81 | % Change the options here to get a different set of basic options, This |
82 | @@ -23,7 +24,7 @@ |
83 | % distribution. |
84 | % |
85 | % The "fancyhdr" package makes nicer page footers reasonable to |
86 | -% implement, and is used to put the chapter and section information in |
87 | +% implement, and is used to put the chapter and section information in |
88 | % the footers. |
89 | % |
90 | \RequirePackage{fancyhdr}\typeout{Using fancier footers than usual.} |
91 | @@ -49,7 +50,8 @@ |
92 | % |
93 | \renewcommand{\maketitle}{ |
94 | \py@doHorizontalRule |
95 | - \@ifundefined{pdfinfo}{}{{ |
96 | + \ifpdf |
97 | + \begingroup |
98 | % This \def is required to deal with multi-line authors; it |
99 | % changes \\ to ', ' (comma-space), making it pass muster for |
100 | % generating document info in the PDF file. |
101 | @@ -58,7 +60,8 @@ |
102 | /Author (\@author) |
103 | /Title (\@title) |
104 | } |
105 | - }} |
106 | + \endgroup |
107 | + \fi |
108 | \begin{flushright} |
109 | {\rm\Huge\py@HeaderFamily \@title} \par |
110 | {\em\large\py@HeaderFamily \py@release} \par |
111 | @@ -84,7 +87,7 @@ |
112 | \py@doHorizontalRule |
113 | \vspace{12pt} |
114 | \py@doing@page@targetstrue |
115 | -} |
116 | +} |
117 | |
118 | % Fix the theindex environment to add an entry to the Table of |
119 | % Contents; this is much nicer than just having to jump to the end of |
120 | |
121 | === modified file 'doc/tools/texinputs/manual.cls' |
122 | --- doc/tools/texinputs/manual.cls 2008-02-19 01:50:23 +0000 |
123 | +++ doc/tools/texinputs/manual.cls 2009-09-01 11:27:38 +0000 |
124 | @@ -6,6 +6,7 @@ |
125 | \ProvidesClass{manual} |
126 | [1998/03/03 Document class (Python manual)] |
127 | |
128 | +\RequirePackage{ifpdf} |
129 | \RequirePackage{pypaper} |
130 | |
131 | % Change the options here to get a different set of basic options, but only |
132 | @@ -23,7 +24,7 @@ |
133 | % distribution. |
134 | % |
135 | % The "fancyhdr" package makes nicer page footers reasonable to |
136 | -% implement, and is used to put the chapter and section information in |
137 | +% implement, and is used to put the chapter and section information in |
138 | % the footers. |
139 | % |
140 | \RequirePackage{fancyhdr}\typeout{Using fancier footers than usual.} |
141 | @@ -63,7 +64,8 @@ |
142 | \let\footnotesize\small |
143 | \let\footnoterule\relax |
144 | \py@doHorizontalRule% |
145 | - \@ifundefined{pdfinfo}{}{{ |
146 | + \ifpdf |
147 | + \begingroup |
148 | % This \def is required to deal with multi-line authors; it |
149 | % changes \\ to ', ' (comma-space), making it pass muster for |
150 | % generating document info in the PDF file. |
151 | @@ -72,7 +74,8 @@ |
152 | /Author (\@author) |
153 | /Title (\@title) |
154 | } |
155 | - }} |
156 | + \endgroup |
157 | + \fi |
158 | \begin{flushright}% |
159 | {\rm\Huge\py@HeaderFamily \@title \par}% |
160 | {\em\LARGE\py@HeaderFamily \py@release \par} |
161 | |
162 | === modified file 'src/crypto/netscape_spki.c' |
163 | --- src/crypto/netscape_spki.c 2009-07-08 16:48:33 +0000 |
164 | +++ src/crypto/netscape_spki.c 2009-08-31 18:52:30 +0000 |
165 | @@ -243,7 +243,7 @@ |
166 | PyTypeObject crypto_NetscapeSPKI_Type = { |
167 | PyObject_HEAD_INIT(NULL) |
168 | 0, |
169 | - "NetscapeSPKI", |
170 | + "OpenSSL.crypto.NetscapeSPKI", |
171 | sizeof(crypto_NetscapeSPKIObj), |
172 | 0, |
173 | (destructor)crypto_NetscapeSPKI_dealloc, |
174 | |
175 | === modified file 'src/crypto/pkcs12.c' |
176 | --- src/crypto/pkcs12.c 2009-07-26 01:23:01 +0000 |
177 | +++ src/crypto/pkcs12.c 2009-08-31 19:36:23 +0000 |
178 | @@ -71,14 +71,14 @@ |
179 | \n\ |
180 | @returns: PKey object containing the private key\n\ |
181 | "; |
182 | -static crypto_PKeyObj * |
183 | +static PyObject * |
184 | crypto_PKCS12_get_privatekey(crypto_PKCS12Obj *self, PyObject *args) |
185 | { |
186 | if (!PyArg_ParseTuple(args, ":get_privatekey")) |
187 | return NULL; |
188 | |
189 | Py_INCREF(self->key); |
190 | - return (crypto_PKeyObj *) self->key; |
191 | + return self->key; |
192 | } |
193 | |
194 | static char crypto_PKCS12_set_privatekey_doc[] = "\n\ |
195 | @@ -514,7 +514,7 @@ |
196 | PyTypeObject crypto_PKCS12_Type = { |
197 | PyObject_HEAD_INIT(NULL) |
198 | 0, |
199 | - "PKCS12", |
200 | + "OpenSSL.crypto.PKCS12", |
201 | sizeof(crypto_PKCS12Obj), |
202 | 0, |
203 | (destructor)crypto_PKCS12_dealloc, |
204 | |
205 | === modified file 'src/crypto/pkcs7.c' |
206 | --- src/crypto/pkcs7.c 2009-06-27 18:32:07 +0000 |
207 | +++ src/crypto/pkcs7.c 2009-08-31 18:52:30 +0000 |
208 | @@ -177,7 +177,7 @@ |
209 | PyTypeObject crypto_PKCS7_Type = { |
210 | PyObject_HEAD_INIT(NULL) |
211 | 0, |
212 | - "PKCS7", |
213 | + "OpenSSL.crypto.PKCS7", |
214 | sizeof(crypto_PKCS7Obj), |
215 | 0, |
216 | (destructor)crypto_PKCS7_dealloc, |
217 | |
218 | === modified file 'src/crypto/x509.c' |
219 | --- src/crypto/x509.c 2009-07-08 16:48:33 +0000 |
220 | +++ src/crypto/x509.c 2009-08-31 18:52:30 +0000 |
221 | @@ -800,7 +800,7 @@ |
222 | PyTypeObject crypto_X509_Type = { |
223 | PyObject_HEAD_INIT(NULL) |
224 | 0, |
225 | - "X509", |
226 | + "OpenSSL.crypto.X509", |
227 | sizeof(crypto_X509Obj), |
228 | 0, |
229 | (destructor)crypto_X509_dealloc, |
230 | |
231 | === modified file 'src/crypto/x509ext.c' |
232 | --- src/crypto/x509ext.c 2009-07-17 20:06:12 +0000 |
233 | +++ src/crypto/x509ext.c 2009-08-31 18:52:30 +0000 |
234 | @@ -256,7 +256,7 @@ |
235 | PyTypeObject crypto_X509Extension_Type = { |
236 | PyObject_HEAD_INIT(NULL) |
237 | 0, |
238 | - "X509Extension", |
239 | + "OpenSSL.crypto.X509Extension", |
240 | sizeof(crypto_X509ExtensionObj), |
241 | 0, |
242 | (destructor)crypto_X509Extension_dealloc, |
243 | |
244 | === modified file 'src/crypto/x509name.c' |
245 | --- src/crypto/x509name.c 2009-07-16 20:25:19 +0000 |
246 | +++ src/crypto/x509name.c 2009-08-31 19:33:58 +0000 |
247 | @@ -55,12 +55,22 @@ |
248 | crypto_X509Name_new(PyTypeObject *subtype, PyObject *args, PyObject *kwargs) |
249 | { |
250 | crypto_X509NameObj *name; |
251 | + X509_NAME *sslname; |
252 | + PyObject *newname; |
253 | |
254 | if (!PyArg_ParseTuple(args, "O!:X509Name", &crypto_X509Name_Type, &name)) { |
255 | return NULL; |
256 | } |
257 | - |
258 | - return (PyObject *)crypto_X509Name_New(X509_NAME_dup(name->x509_name), 1); |
259 | + sslname = X509_NAME_dup(name->x509_name); |
260 | + if (sslname == NULL) { |
261 | + exception_from_error_queue(crypto_Error); |
262 | + return NULL; |
263 | + } |
264 | + newname = (PyObject *)crypto_X509Name_New(sslname, 1); |
265 | + if (newname == NULL) { |
266 | + X509_NAME_free(sslname); |
267 | + } |
268 | + return newname; |
269 | } |
270 | |
271 | |
272 | @@ -423,7 +433,7 @@ |
273 | PyTypeObject crypto_X509Name_Type = { |
274 | PyObject_HEAD_INIT(NULL) |
275 | 0, |
276 | - "X509Name", |
277 | + "OpenSSL.crypto.X509Name", |
278 | sizeof(crypto_X509NameObj), |
279 | 0, |
280 | (destructor)crypto_X509Name_dealloc, |
281 | |
282 | === modified file 'src/crypto/x509req.c' |
283 | --- src/crypto/x509req.c 2009-07-08 16:48:33 +0000 |
284 | +++ src/crypto/x509req.c 2009-08-31 18:52:30 +0000 |
285 | @@ -372,7 +372,7 @@ |
286 | PyTypeObject crypto_X509Req_Type = { |
287 | PyObject_HEAD_INIT(NULL) |
288 | 0, |
289 | - "X509Req", |
290 | + "OpenSSL.crypto.X509Req", |
291 | sizeof(crypto_X509ReqObj), |
292 | 0, |
293 | (destructor)crypto_X509Req_dealloc, |
294 | |
295 | === modified file 'src/crypto/x509store.c' |
296 | --- src/crypto/x509store.c 2009-07-08 16:48:33 +0000 |
297 | +++ src/crypto/x509store.c 2009-08-31 18:52:30 +0000 |
298 | @@ -109,7 +109,7 @@ |
299 | PyTypeObject crypto_X509Store_Type = { |
300 | PyObject_HEAD_INIT(NULL) |
301 | 0, |
302 | - "X509Store", |
303 | + "OpenSSL.crypto.X509Store", |
304 | sizeof(crypto_X509StoreObj), |
305 | 0, |
306 | (destructor)crypto_X509Store_dealloc, |
307 | |
308 | === modified file 'src/ssl/connection.c' |
309 | --- src/ssl/connection.c 2009-07-08 16:48:33 +0000 |
310 | +++ src/ssl/connection.c 2009-08-31 23:32:29 +0000 |
311 | @@ -819,16 +819,79 @@ |
312 | return NULL; |
313 | |
314 | lst = PyList_New(0); |
315 | + if (lst == NULL) { |
316 | + return NULL; |
317 | + } |
318 | while ((ret = SSL_get_cipher_list(self->ssl, idx)) != NULL) |
319 | { |
320 | item = PyString_FromString(ret); |
321 | - PyList_Append(lst, item); |
322 | + if (item == NULL) { |
323 | + Py_DECREF(lst); |
324 | + return NULL; |
325 | + } |
326 | + if (PyList_Append(lst, item)) { |
327 | + Py_DECREF(lst); |
328 | + Py_DECREF(item); |
329 | + return NULL; |
330 | + } |
331 | Py_DECREF(item); |
332 | idx++; |
333 | } |
334 | return lst; |
335 | } |
336 | |
337 | +static char ssl_Connection_get_client_CA_list_doc[] = "\n\ |
338 | +Get CAs whose certificates are suggested for client authentication.\n\ |
339 | +\n\ |
340 | +@return: A list of X509Names representing the acceptable CAs as set by\n\ |
341 | + ssl.Context.{set, add}_client_CA* if this is a server connection\n\ |
342 | + or as sent by the server if this is a client connection.\n\ |
343 | +"; |
344 | + |
345 | +static PyObject * |
346 | +ssl_Connection_get_client_CA_list(ssl_ConnectionObj *self, PyObject *args) |
347 | +{ |
348 | + STACK_OF(X509_NAME) *CANames; |
349 | + PyObject *CAList; |
350 | + int i, n; |
351 | + |
352 | + if (!PyArg_ParseTuple(args, ":get_client_CA_list")) { |
353 | + return NULL; |
354 | + } |
355 | + CANames = SSL_get_client_CA_list(self->ssl); |
356 | + if (CANames == NULL) { |
357 | + return PyList_New(0); |
358 | + } |
359 | + n = sk_X509_NAME_num(CANames); |
360 | + CAList = PyList_New(n); |
361 | + if (CAList == NULL) { |
362 | + return NULL; |
363 | + } |
364 | + for (i = 0; i < n; i++) { |
365 | + X509_NAME *CAName; |
366 | + PyObject *CA; |
367 | + |
368 | + CAName = X509_NAME_dup(sk_X509_NAME_value(CANames, i)); |
369 | + if (CAName == NULL) { |
370 | + Py_DECREF(CAList); |
371 | + exception_from_error_queue(ssl_Error); |
372 | + return NULL; |
373 | + } |
374 | + CA = (PyObject *)crypto_X509Name_New(CAName, 1); |
375 | + if (CA == NULL) { |
376 | + X509_NAME_free(CAName); |
377 | + Py_DECREF(CAList); |
378 | + return NULL; |
379 | + } |
380 | + if (PyList_SetItem(CAList, i, CA)) { |
381 | + Py_DECREF(CA); |
382 | + Py_DECREF(CAList); |
383 | + return NULL; |
384 | + } |
385 | + } |
386 | + return CAList; |
387 | +} |
388 | + |
389 | static char ssl_Connection_makefile_doc[] = "\n\ |
390 | The makefile() method is not implemented, since there is no dup semantics\n\ |
391 | for SSL connections\n\ |
392 | @@ -1087,6 +1150,7 @@ |
393 | ADD_METHOD(bio_shutdown), |
394 | ADD_METHOD(shutdown), |
395 | ADD_METHOD(get_cipher_list), |
396 | + ADD_METHOD(get_client_CA_list), |
397 | ADD_METHOD(makefile), |
398 | ADD_METHOD(get_app_data), |
399 | ADD_METHOD(set_app_data), |
400 | |
401 | === modified file 'src/ssl/context.c' |
402 | --- src/ssl/context.c 2009-07-08 16:48:33 +0000 |
403 | +++ src/ssl/context.c 2009-08-31 23:32:29 +0000 |
404 | @@ -335,36 +335,63 @@ |
405 | return Py_None; |
406 | } |
407 | |
408 | +static PyTypeObject * |
409 | +type_modified_error(const char *name) |
410 | +{ |
411 | + PyErr_Format(PyExc_RuntimeError, |
412 | + "OpenSSL.crypto's '%s' attribute has been modified", |
413 | + name); |
414 | + return NULL; |
415 | +} |
416 | + |
417 | +static PyTypeObject * |
418 | +import_crypto_type(const char *name, size_t objsize) |
419 | +{ |
420 | + PyObject *module, *type; |
421 | + PyTypeObject *res; |
422 | + char modname[] = "OpenSSL.crypto"; |
423 | + char buffer[256] = "OpenSSL.crypto."; |
424 | + |
425 | + if (strlen(buffer) + strlen(name) >= sizeof(buffer)) { |
426 | + PyErr_BadInternalCall(); |
427 | + return NULL; |
428 | + } |
429 | + strcat(buffer, name); |
430 | + module = PyImport_ImportModule(modname); |
431 | + if (module == NULL) { |
432 | + return NULL; |
433 | + } |
434 | + type = PyObject_GetAttrString(module, name); |
435 | + Py_DECREF(module); |
436 | + if (type == NULL) { |
437 | + return NULL; |
438 | + } |
439 | + if (!(PyType_Check(type))) { |
440 | + Py_DECREF(type); |
441 | + return type_modified_error(name); |
442 | + } |
443 | + res = (PyTypeObject *)type; |
444 | + if (strcmp(buffer, res->tp_name) != 0 || res->tp_basicsize != objsize) { |
445 | + Py_DECREF(type); |
446 | + return type_modified_error(name); |
447 | + } |
448 | + return res; |
449 | +} |
450 | + |
451 | static crypto_X509Obj * |
452 | -parse_certificate_argument(const char* format1, const char* format2, PyObject* args) |
453 | +parse_certificate_argument(const char* format, PyObject* args) |
454 | { |
455 | static PyTypeObject *crypto_X509_type = NULL; |
456 | crypto_X509Obj *cert; |
457 | |
458 | - /* We need to check that cert really is an X509 object before |
459 | - we deal with it. The problem is we can't just quickly verify |
460 | - the type (since that comes from another module). This should |
461 | - do the trick (reasonably well at least): Once we have one |
462 | - verified object, we use it's type object for future |
463 | - comparisons. */ |
464 | - |
465 | if (!crypto_X509_type) |
466 | { |
467 | - if (!PyArg_ParseTuple(args, (PYARG_PARSETUPLE_FORMAT *)format1, &cert)) |
468 | - return NULL; |
469 | - |
470 | - if (strcmp(cert->ob_type->tp_name, "X509") != 0 || |
471 | - cert->ob_type->tp_basicsize != sizeof(crypto_X509Obj)) |
472 | - { |
473 | - PyErr_SetString(PyExc_TypeError, "Expected an X509 object"); |
474 | - return NULL; |
475 | - } |
476 | - |
477 | - crypto_X509_type = cert->ob_type; |
478 | + crypto_X509_type = import_crypto_type("X509", sizeof(crypto_X509Obj)); |
479 | + if (!crypto_X509_type) |
480 | + return NULL; |
481 | } |
482 | - else |
483 | - if (!PyArg_ParseTuple(args, (PYARG_PARSETUPLE_FORMAT *)format2, crypto_X509_type, |
484 | - &cert)) |
485 | + if (!PyArg_ParseTuple(args, (PYARG_PARSETUPLE_FORMAT *)format, |
486 | + crypto_X509_type, &cert)) |
487 | return NULL; |
488 | return cert; |
489 | } |
490 | @@ -381,7 +408,7 @@ |
491 | { |
492 | X509* cert_original; |
493 | crypto_X509Obj *cert = parse_certificate_argument( |
494 | - "O:add_extra_chain_cert", "O!:add_extra_chain_cert", args); |
495 | + "O!:add_extra_chain_cert", args); |
496 | if (cert == NULL) |
497 | { |
498 | return NULL; |
499 | @@ -471,7 +498,7 @@ |
500 | ssl_Context_use_certificate(ssl_ContextObj *self, PyObject *args) |
501 | { |
502 | crypto_X509Obj *cert = parse_certificate_argument( |
503 | - "O:use_certificate", "O!:use_certificate", args); |
504 | + "O!:use_certificate", args); |
505 | if (cert == NULL) { |
506 | return NULL; |
507 | } |
508 | @@ -538,28 +565,12 @@ |
509 | static PyTypeObject *crypto_PKey_type = NULL; |
510 | crypto_PKeyObj *pkey; |
511 | |
512 | - /* We need to check that cert really is a PKey object before |
513 | - we deal with it. The problem is we can't just quickly verify |
514 | - the type (since that comes from another module). This should |
515 | - do the trick (reasonably well at least): Once we have one |
516 | - verified object, we use it's type object for future |
517 | - comparisons. */ |
518 | - |
519 | if (!crypto_PKey_type) |
520 | { |
521 | - if (!PyArg_ParseTuple(args, "O:use_privatekey", &pkey)) |
522 | - return NULL; |
523 | - |
524 | - if (strcmp(pkey->ob_type->tp_name, "OpenSSL.crypto.PKey") != 0 || |
525 | - pkey->ob_type->tp_basicsize != sizeof(crypto_PKeyObj)) |
526 | - { |
527 | - PyErr_SetString(PyExc_TypeError, "Expected a PKey object"); |
528 | - return NULL; |
529 | - } |
530 | - |
531 | - crypto_PKey_type = pkey->ob_type; |
532 | + crypto_PKey_type = import_crypto_type("PKey", sizeof(crypto_PKeyObj)); |
533 | + if (!crypto_PKey_type) |
534 | + return NULL; |
535 | } |
536 | - else |
537 | if (!PyArg_ParseTuple(args, "O!:use_privatekey", crypto_PKey_type, &pkey)) |
538 | return NULL; |
539 | |
540 | @@ -789,6 +800,111 @@ |
541 | } |
542 | } |
543 | |
544 | +static char ssl_Context_set_client_CA_list_doc[] = "\n\ |
545 | +Set the list of preferred client certificate signers for this server context.\n\ |
546 | +\n\ |
547 | +This list of certificate authorities will be sent to the client when the\n\ |
548 | +server requests a client certificate.\n\ |
549 | +\n\ |
550 | +@param certificate_authorities: a sequence of X509Names.\n\ |
551 | +@return: None\n\ |
552 | +"; |
553 | + |
554 | +static PyObject * |
555 | +ssl_Context_set_client_CA_list(ssl_ContextObj *self, PyObject *args) |
556 | +{ |
557 | + static PyTypeObject *X509NameType; |
558 | + PyObject *sequence, *tuple, *item; |
559 | + crypto_X509NameObj *name; |
560 | + X509_NAME *sslname; |
561 | + STACK_OF(X509_NAME) *CANames; |
562 | + Py_ssize_t length; |
563 | + int i; |
564 | + |
565 | + if (X509NameType == NULL) { |
566 | + X509NameType = import_crypto_type("X509Name", sizeof(crypto_X509NameObj)); |
567 | + if (X509NameType == NULL) { |
568 | + return NULL; |
569 | + } |
570 | + } |
571 | + if (!PyArg_ParseTuple(args, "O:set_client_CA_list", &sequence)) { |
572 | + return NULL; |
573 | + } |
574 | + tuple = PySequence_Tuple(sequence); |
575 | + if (tuple == NULL) { |
576 | + return NULL; |
577 | + } |
578 | + length = PyTuple_Size(tuple); |
579 | + if (length >= INT_MAX) { |
580 | + PyErr_SetString(PyExc_ValueError, "client CA list is too long"); |
581 | + Py_DECREF(tuple); |
582 | + return NULL; |
583 | + } |
584 | + CANames = sk_X509_NAME_new_null(); |
585 | + if (CANames == NULL) { |
586 | + Py_DECREF(tuple); |
587 | + exception_from_error_queue(ssl_Error); |
588 | + return NULL; |
589 | + } |
590 | + for (i = 0; i < length; i++) { |
591 | + item = PyTuple_GetItem(tuple, i); |
592 | + if (item->ob_type != X509NameType) { |
593 | + PyErr_Format(PyExc_TypeError, |
594 | + "client CAs must be X509Name objects, not %s objects", |
595 | + item->ob_type->tp_name); |
596 | + sk_X509_NAME_free(CANames); |
597 | + Py_DECREF(tuple); |
598 | + return NULL; |
599 | + } |
600 | + name = (crypto_X509NameObj *)item; |
601 | + sslname = X509_NAME_dup(name->x509_name); |
602 | + if (sslname == NULL) { |
603 | + sk_X509_NAME_free(CANames); |
604 | + Py_DECREF(tuple); |
605 | + exception_from_error_queue(ssl_Error); |
606 | + return NULL; |
607 | + } |
608 | + if (!sk_X509_NAME_push(CANames, sslname)) { |
609 | + X509_NAME_free(sslname); |
610 | + sk_X509_NAME_free(CANames); |
611 | + Py_DECREF(tuple); |
612 | + exception_from_error_queue(ssl_Error); |
613 | + return NULL; |
614 | + } |
615 | + } |
616 | + Py_DECREF(tuple); |
617 | + SSL_CTX_set_client_CA_list(self->ctx, CANames); |
618 | + Py_INCREF(Py_None); |
619 | + return Py_None; |
620 | +} |
621 | + |
622 | +static char ssl_Context_add_client_CA_doc[] = "\n\ |
623 | +Add the CA certificate to the list of preferred signers for this context.\n\ |
624 | +\n\ |
625 | +The list of certificate authorities will be sent to the client when the\n\ |
626 | +server requests a client certificate.\n\ |
627 | +\n\ |
628 | +@param certificate_authority: certificate authority's X509 certificate.\n\ |
629 | +@return: None\n\ |
630 | +"; |
631 | + |
632 | +static PyObject * |
633 | +ssl_Context_add_client_CA(ssl_ContextObj *self, PyObject *args) |
634 | +{ |
635 | + crypto_X509Obj *cert; |
636 | + |
637 | + cert = parse_certificate_argument("O!:add_client_CA", args); |
638 | + if (cert == NULL) { |
639 | + return NULL; |
640 | + } |
641 | + if (!SSL_CTX_add_client_CA(self->ctx, cert->x509)) { |
642 | + exception_from_error_queue(ssl_Error); |
643 | + return NULL; |
644 | + } |
645 | + Py_INCREF(Py_None); |
646 | + return Py_None; |
647 | +} |
648 | + |
649 | static char ssl_Context_set_timeout_doc[] = "\n\ |
650 | Set session timeout\n\ |
651 | \n\ |
652 | @@ -960,6 +1076,8 @@ |
653 | ADD_METHOD(get_verify_depth), |
654 | ADD_METHOD(load_tmp_dh), |
655 | ADD_METHOD(set_cipher_list), |
656 | + ADD_METHOD(set_client_CA_list), |
657 | + ADD_METHOD(add_client_CA), |
658 | ADD_METHOD(set_timeout), |
659 | ADD_METHOD(get_timeout), |
660 | ADD_METHOD(set_info_callback), |
661 | |
662 | === modified file 'src/util.c' |
663 | --- src/util.c 2009-07-16 22:47:27 +0000 |
664 | +++ src/util.c 2009-08-31 19:33:58 +0000 |
665 | @@ -22,26 +22,40 @@ |
666 | PyObject * |
667 | error_queue_to_list(void) { |
668 | PyObject *errlist, *tuple; |
669 | + int failed; |
670 | long err; |
671 | |
672 | errlist = PyList_New(0); |
673 | - |
674 | + if (errlist == NULL) { |
675 | + return NULL; |
676 | + } |
677 | while ((err = ERR_get_error()) != 0) { |
678 | - tuple = Py_BuildValue("(sss)", ERR_lib_error_string(err), |
679 | - ERR_func_error_string(err), |
680 | - ERR_reason_error_string(err)); |
681 | - PyList_Append(errlist, tuple); |
682 | + tuple = Py_BuildValue("(sss)", ERR_lib_error_string(err), |
683 | + ERR_func_error_string(err), |
684 | + ERR_reason_error_string(err)); |
685 | + if (tuple == NULL) { |
686 | + Py_DECREF(errlist); |
687 | + return NULL; |
688 | + } |
689 | + failed = PyList_Append(errlist, tuple); |
690 | Py_DECREF(tuple); |
691 | + if (failed) { |
692 | + Py_DECREF(errlist); |
693 | + return NULL; |
694 | + } |
695 | } |
696 | |
697 | return errlist; |
698 | } |
699 | |
700 | -void exception_from_error_queue(PyObject *the_Error) { |
701 | +void exception_from_error_queue(PyObject *the_Error) { |
702 | PyObject *errlist = error_queue_to_list(); |
703 | - PyErr_SetObject(the_Error, errlist); |
704 | - Py_DECREF(errlist); |
705 | -} |
706 | + |
707 | + if (errlist != NULL) { |
708 | + PyErr_SetObject(the_Error, errlist); |
709 | + Py_DECREF(errlist); |
710 | + } |
711 | +} |
712 | |
713 | /* |
714 | * Flush OpenSSL's error queue and ignore the result |
715 | @@ -57,5 +71,5 @@ |
716 | * very nasty things if we just invoked it with error_queue_to_list(). |
717 | */ |
718 | PyObject *list = error_queue_to_list(); |
719 | - Py_DECREF(list); |
720 | + Py_XDECREF(list); |
721 | } |
722 | |
723 | === modified file 'src/util.h' |
724 | --- src/util.h 2009-07-08 16:48:33 +0000 |
725 | +++ src/util.h 2009-08-31 23:32:29 +0000 |
726 | @@ -119,6 +119,10 @@ |
727 | } |
728 | #endif |
729 | |
730 | - |
731 | +#if !defined(PY_SSIZE_T_MIN) |
732 | +typedef int Py_ssize_t; |
733 | +#define PY_SSIZE_T_MAX INT_MAX |
734 | +#define PY_SSIZE_T_MIN INT_MIN |
735 | +#endif |
736 | |
737 | #endif |
738 | |
739 | === modified file 'test/test_crypto.py' |
740 | --- test/test_crypto.py 2009-08-27 16:47:55 +0000 |
741 | +++ test/test_crypto.py 2009-08-31 18:49:30 +0000 |
742 | @@ -10,6 +10,7 @@ |
743 | from os import popen2 |
744 | from datetime import datetime, timedelta |
745 | |
746 | +from OpenSSL import crypto |
747 | from OpenSSL.crypto import TYPE_RSA, TYPE_DSA, Error, PKey, PKeyType |
748 | from OpenSSL.crypto import X509, X509Type, X509Name, X509NameType |
749 | from OpenSSL.crypto import X509Req, X509ReqType |
750 | @@ -1533,5 +1534,20 @@ |
751 | |
752 | |
753 | |
754 | +class ModuleTests(TestCase): |
755 | + """ |
756 | + Tests for all objects in L{OpenSSL.crypto} module. |
757 | + """ |
758 | + |
759 | + def test_type_module_name(self): |
760 | + """ |
761 | + Test that all types have a sane C{__module__} attribute. |
762 | + """ |
763 | + for name, obj in vars(crypto).items(): |
764 | + if isinstance(obj, type): |
765 | + self.assertEqual(obj.__module__, "OpenSSL.crypto", name) |
766 | + |
767 | + |
768 | + |
769 | if __name__ == '__main__': |
770 | main() |
771 | |
772 | === modified file 'test/test_ssl.py' |
773 | --- test/test_ssl.py 2009-07-25 16:32:46 +0000 |
774 | +++ test/test_ssl.py 2009-09-01 11:32:34 +0000 |
775 | @@ -10,6 +10,7 @@ |
776 | from os.path import join |
777 | from unittest import main |
778 | |
779 | +from OpenSSL import SSL |
780 | from OpenSSL.crypto import TYPE_RSA, FILETYPE_PEM, PKey, dump_privatekey, load_certificate, load_privatekey |
781 | from OpenSSL.SSL import WantReadError, Context, ContextType, Connection, ConnectionType, Error |
782 | from OpenSSL.SSL import SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD |
783 | @@ -255,7 +256,7 @@ |
784 | context = Context(SSLv3_METHOD) |
785 | context.set_default_verify_paths() |
786 | context.set_verify( |
787 | - VERIFY_PEER, |
788 | + VERIFY_PEER, |
789 | lambda conn, cert, errno, depth, preverify_ok: preverify_ok) |
790 | |
791 | client = socket() |
792 | @@ -511,8 +512,8 @@ |
793 | established = True # assume the best |
794 | for ssl in client_conn, server_conn: |
795 | try: |
796 | - # Generally a recv() or send() could also work instead |
797 | - # of do_handshake(), and we would stop on the first |
798 | + # Generally a recv() or send() could also work instead |
799 | + # of do_handshake(), and we would stop on the first |
800 | # non-exception. |
801 | ssl.do_handshake() |
802 | except WantReadError: |
803 | @@ -583,6 +584,139 @@ |
804 | self.assertEquals(e.__class__, Error) |
805 | |
806 | |
807 | + def _check_client_CA_list(self, func): |
808 | + server = self._server(None) |
809 | + client = self._client(None) |
810 | + self.assertEqual(client.get_client_CA_list(), []) |
811 | + self.assertEqual(server.get_client_CA_list(), []) |
812 | + ctx = server.get_context() |
813 | + expected = func(ctx) |
814 | + self.assertEqual(client.get_client_CA_list(), []) |
815 | + self.assertEqual(server.get_client_CA_list(), expected) |
816 | + self._loopback(client, server) |
817 | + self.assertEqual(client.get_client_CA_list(), expected) |
818 | + self.assertEqual(server.get_client_CA_list(), expected) |
819 | + |
820 | + |
821 | + def test_set_client_CA_list_basic(self): |
822 | + """ |
823 | + Test paramater validation and return value for |
824 | + L{Context.set_client_CA_list}. |
825 | + """ |
826 | + ctx = Context(TLSv1_METHOD) |
827 | + self.assertRaises(TypeError, ctx.set_client_CA_list, "spam") |
828 | + self.assertRaises(TypeError, ctx.set_client_CA_list, ["spam"]) |
829 | + self.assertIdentical(ctx.set_client_CA_list([]), None) |
830 | + |
831 | + |
832 | + def test_set_client_CA_list_functional(self): |
833 | + """ |
834 | + The list of CAs set by L{Context.set_client_CA_list} and read by |
835 | + L{Connection.get_client_CA_list} should match on server and |
836 | + client side. |
837 | + """ |
838 | + cacert = load_certificate(FILETYPE_PEM, root_cert_pem) |
839 | + secert = load_certificate(FILETYPE_PEM, server_cert_pem) |
840 | + clcert = load_certificate(FILETYPE_PEM, server_cert_pem) |
841 | + |
842 | + cadesc = cacert.get_subject() |
843 | + sedesc = secert.get_subject() |
844 | + cldesc = clcert.get_subject() |
845 | + |
846 | + def single_CA(ctx): |
847 | + ctx.set_client_CA_list([cadesc]) |
848 | + return [cadesc] |
849 | + self._check_client_CA_list(single_CA) |
850 | + |
851 | + def no_CA(ctx): |
852 | + ctx.set_client_CA_list([]) |
853 | + return [] |
854 | + self._check_client_CA_list(no_CA) |
855 | + |
856 | + def multiple_CA(ctx): |
857 | + L = [cadesc, sedesc, cldesc] |
858 | + ctx.set_client_CA_list(L) |
859 | + return L |
860 | + self._check_client_CA_list(multiple_CA) |
861 | + |
862 | + def changed_CA(ctx): |
863 | + ctx.set_client_CA_list([sedesc, cldesc]) |
864 | + ctx.set_client_CA_list([cadesc]) |
865 | + return [cadesc] |
866 | + self._check_client_CA_list(changed_CA) |
867 | + |
868 | + def mutated_CA(ctx): |
869 | + L = [cadesc] |
870 | + ctx.set_client_CA_list([cadesc]) |
871 | + L.append(sedesc) |
872 | + return [cadesc] |
873 | + self._check_client_CA_list(mutated_CA) |
874 | + |
875 | + |
876 | + def test_add_client_CA_basic(self): |
877 | + """ |
878 | + Test paramater validation and return value for |
879 | + L{Context.add_client_CA}. |
880 | + """ |
881 | + ctx = Context(TLSv1_METHOD) |
882 | + cacert = load_certificate(FILETYPE_PEM, root_cert_pem) |
883 | + self.assertRaises(TypeError, ctx.add_client_CA, "spam") |
884 | + self.assertIdentical(ctx.add_client_CA(cacert), None) |
885 | + |
886 | + |
887 | + def test_add_client_CA_functional(self): |
888 | + """ |
889 | + The list of CAs set by L{Context.add_client_CA} and read by |
890 | + L{Connection.get_client_CA_list} should match on server and |
891 | + client side. |
892 | + """ |
893 | + cacert = load_certificate(FILETYPE_PEM, root_cert_pem) |
894 | + secert = load_certificate(FILETYPE_PEM, server_cert_pem) |
895 | + clcert = load_certificate(FILETYPE_PEM, server_cert_pem) |
896 | + |
897 | + cadesc = cacert.get_subject() |
898 | + sedesc = secert.get_subject() |
899 | + cldesc = clcert.get_subject() |
900 | + |
901 | + def single_CA(ctx): |
902 | + ctx.add_client_CA(cacert) |
903 | + return [cadesc] |
904 | + self._check_client_CA_list(single_CA) |
905 | + |
906 | + def multiple_CA(ctx): |
907 | + ctx.add_client_CA(cacert) |
908 | + ctx.add_client_CA(secert) |
909 | + return [cadesc, sedesc] |
910 | + self._check_client_CA_list(multiple_CA) |
911 | + |
912 | + def mixed_set_add_CA(ctx): |
913 | + ctx.set_client_CA_list([cadesc, sedesc]) |
914 | + ctx.add_client_CA(clcert) |
915 | + return [cadesc, sedesc, cldesc] |
916 | + self._check_client_CA_list(mixed_set_add_CA) |
917 | + |
918 | + def set_replaces_add_CA(ctx): |
919 | + ctx.add_client_CA(clcert) |
920 | + ctx.set_client_CA_list([cadesc]) |
921 | + ctx.add_client_CA(secert) |
922 | + return [cadesc, sedesc] |
923 | + self._check_client_CA_list(set_replaces_add_CA) |
924 | + |
925 | + |
926 | +class ModuleTests(TestCase): |
927 | + """ |
928 | + Tests for all objects in L{OpenSSL.crypto} module. |
929 | + """ |
930 | + |
931 | + def test_type_module_name(self): |
932 | + """ |
933 | + Test that all types have a sane C{__module__} attribute. |
934 | + """ |
935 | + for name, obj in vars(SSL).items(): |
936 | + if isinstance(obj, type): |
937 | + self.assertEqual(obj.__module__, "OpenSSL.SSL", name) |
938 | + |
939 | + |
940 | |
941 | if __name__ == '__main__': |
942 | main() |
Hello Jean-Paul,
This branch adds finer control over the list of preferred certificate authorities sent by the server when requesting a client certificate.
This functionality was requested in bug #364185 and in Twisted's bug #2061. The branch adds two new methods to SSL.Context, set_client_CA_list and add_client_CA and one new method to SSL.Connection, get_client_CA_list. The new methods are documented and tested.
More information can be found in commit messages and documentation. There are some unrelated changes in this branch, let me know if I should create a separate branch for those.
Regards,
Ziga