Merge lp:~zseil/pyopenssl/client_CA into lp:~exarkun/pyopenssl/trunk

Proposed by Ziga Seilnacht
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
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.

To post a comment you must log in.
Revision history for this message
Ziga Seilnacht (zseil) wrote :

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

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

review: Needs Fixing
lp:~zseil/pyopenssl/client_CA updated
131. By Ziga Seilnacht

Revert all changes unrelated to the new *client_CA* functionality.

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

review: Needs Resubmitting
lp:~zseil/pyopenssl/client_CA updated
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

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

Subscribers

People subscribed via source and target branches

to status/vote changes: