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

Proposed by Ziga Seilnacht
Status: Merged
Merged at revision: not available
Proposed branch: lp:~zseil/pyopenssl/client_CA
Merge into: lp:~exarkun/pyopenssl/trunk
Diff against target: 567 lines
5 files modified
doc/pyOpenSSL.tex (+33/-3)
src/ssl/connection.c (+53/-0)
src/ssl/context.c (+163/-43)
src/util.h (+5/-1)
test/test_ssl.py (+122/-3)
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+11040@code.launchpad.net

This proposal supersedes a proposal from 2009-09-01.

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

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 : Posted in a previous version of this proposal

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
Revision history for this message
Ziga Seilnacht (zseil) wrote : Posted in a previous version of this proposal

> 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
Revision history for this message
Jean-Paul Calderone (exarkun) wrote :

  1. In the tex docs for add_client_CA, two lines are added which each begin with "%" and repeat the word "Certificate" a number of times. What's this?
  2. From the tex docs, I can't tell what the argument to set_client_CA_list should be a list of. I would have guessed X509Names, but since add_client_CA takes an X509, I'm not sure (the Python docstring clarifies this; it'd be good to have the information in both places).
  3. Why does add_client_CA take an X509 instead of an X509Name? (I suppose it's because that's the how the OpenSSL API works - lame)
  4. I've been postponing looking at the cross-module type checking code that you replaced with "import_crypto_type" because it wasn't causing problems and I didn't want to think about it. However... now I've thought about it a little, and I think it should be simpler. If crypto_X509NameObj is defined, then crypto_X509Name_Type should be defined too, since they're defined in the same place. So the code should just use crypto_X509Name_Type (or crypto_PKey_Type or crypto_X509_Type). It doesn't need to do the import dance or check sizes or anything. There was probably a reason not to do it this way long, long ago, but I'm not very interested in it now.
  5. Likewise, crypto_X509Name_Check should be defined for ssl_Context_set_client_CA_list to use.

Thanks! I generally like the code and it doesn't seem to have any major problems. I'm looking forward to the next version of this.

review: Needs Fixing
Revision history for this message
Ziga Seilnacht (zseil) wrote :

Jean-Paul Calderone wrote:
> Review: Needs Fixing
> 1. In the tex docs for add_client_CA, two lines are added which each begin with "%" and repeat the word "Certificate" a number of times. What's this?

That was supposed to be a humorous reminder for myself that the text
should be clarified. It seems it failed in its purpose, I'll remove it
and try to clarify the text.

> 2. From the tex docs, I can't tell what the argument to set_client_CA_list should be a list of. I would have guessed X509Names, but since add_client_CA takes an X509, I'm not sure (the Python docstring clarifies this; it'd be good to have the information in both places).

Ok.

> 3. Why does add_client_CA take an X509 instead of an X509Name? (I suppose it's because that's the how the OpenSSL API works - lame)

Yes, the underlying OpenSSL API accepts certificates instead of
X509Names. Should I drop the whole add_client_CA method? It is
not strictly necessary.

Also, are the current method names ok? The preexisting method
load_client_ca_list has lowercase CA, should the new methods be
consistent with it rather than with the OpenSSL API?

> 4. I've been postponing looking at the cross-module type checking code that you replaced with "import_crypto_type" because it wasn't causing problems and I didn't want to think about it. However... now I've thought about it a little, and I think it should be simpler. If crypto_X509NameObj is defined, then crypto_X509Name_Type should be defined too, since they're defined in the same place. So the code should just use crypto_X509Name_Type (or crypto_PKey_Type or crypto_X509_Type). It doesn't need to do the import dance or check sizes or anything. There was probably a reason not to do it this way long, long ago, but I'm not very interested in it now.

crypto_XXX_Type's can't be used in the same way as crypto_XXXobj's. The
former are true runtime variables, while the latter are just struct
definitions. To use crypto_XXX_Types directly, the SSL module would have
  to be linked against the crypto module.

Other extension modules (Python's datetime for example) usually solve
this by providing the types in the CAPI struct pointer, and then using
some fairly ugly macros to make them look as normal variables. I can do
that, but we would loose ABI compatibility if we would reuse the current
crypto_XXX_Type names. I guess exposing them just with crypto_XXX_Check
macros would be enough?

> 5. Likewise, crypto_X509Name_Check should be defined for ssl_Context_set_client_CA_list to use.

I did try to use it, but that lead to linking errors and a few hours of
headscratching before I figured out what the difference between *Objs
and *Types was :)

>
> Thanks! I generally like the code and it doesn't seem to have any major problems. I'm looking forward to the next version of this.
>

Thank you for the review. I hope I got the workflow right, the status of
the merge request should be changed to resubmit when the fixes are made?
  Launchpad's interface is a bit confusing in this regard, I guess I
should report a bug...

Regards,
Ziga

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

>
> Thank you for the review. I hope I got the workflow right, the status of
> the merge request should be changed to resubmit when the fixes are made?
> Launchpad's interface is a bit confusing in this regard, I guess I
> should report a bug...
>

I respond to the rest later on, but for now I just wanted to comment on the workflow. I'm basically making this up as I go. pyOpenSSL is the only project I really seriously use Launchpad for. I'm sort of trying to implement Twisted's development workflow using Launchpad, but that doesn't tell me what state to put merge proposals into or other Launchpad-specific details like that. So, so far so good, I think, but if you have any suggestions (or just any feedback - what parts of the workflow are helpful, what parts are not), please let me know. :)

Thanks again for your work on this ticket.

Jean-Paul

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

Just wanted to ping you on this. Should I be re-reviewing this branch (it doesn't look like there have been any changes since last time), or providing more feedback? 0.10 might be coming up, and if this can be ready it'd be great to include it.

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.

Revision history for this message
Ziga Seilnacht (zseil) wrote :

> Just wanted to ping you on this. Should I be re-reviewing this branch (it
> doesn't look like there have been any changes since last time), or providing
> more feedback? 0.10 might be coming up, and if this can be ready it'd be
> great to include it.

Sorry for the long silence, I've been terribly busy at work lately.

I've updated the branch now with doc fixes, this should address 1) and 2).

Regarding 3), I can drop the whole method, but I think that it is useful
as it is, i.e. I expect that it would be used like this:

    store = context.get_cert_store()
    for cacert in cacerts:
        store.add_cert(cacert)
        context.add_client_ca(cacert)

I also lowercased the new methods, for consistency with the rest of
PyOpenSSL (e.g. SSL.Context.load_client_ca,
crypto.PKCS12.get_ca_certificates). This change was made in a single
revision, so it can be dropped if you think the old names are better.

Regarding 4) and 5), the needed changes would be quite intrusive and
I'm not sure that we could preserve binary compatibility. I'll try to
work on that over the weekend, but I would be more comfortable if that
was a separate branch, possibly merged after the release.

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

> > Just wanted to ping you on this. Should I be re-reviewing this branch (it
> > doesn't look like there have been any changes since last time), or providing
> > more feedback? 0.10 might be coming up, and if this can be ready it'd be
> > great to include it.
>
> Sorry for the long silence, I've been terribly busy at work lately.

No worries, I can understand that. :)

> Regarding 4) and 5), the needed changes would be quite intrusive and
> I'm not sure that we could preserve binary compatibility. I'll try to
> work on that over the weekend, but I would be more comfortable if that
> was a separate branch, possibly merged after the release.

I think you've probably put more thought into this area so far than I have. So I think I'm happy to worry about these points later on, if at all. I'm going to take a look at the branch again now.

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

Revision history for this message
Ziga Seilnacht (zseil) wrote :

>> Regarding 4) and 5), the needed changes would be quite intrusive and
>> I'm not sure that we could preserve binary compatibility. I'll try to
>> work on that over the weekend, but I would be more comfortable if that
>> was a separate branch, possibly merged after the release.
>
> I think you've probably put more thought into this area so far than I have.
> So I think I'm happy to worry about these points later on, if at all. I'm
> going to take a look at the branch again now.

Thanks for your work, I've seen your recent changes, it looks like I should
look deeper into this "unit" thing in unit testing :).

I've looked into C API issues some more and have found a way that preserves
API as well as ABI compatibility, you can see the current progress at:

lp:~ziga-seilnacht/pyopenssl/bwcompat-c-api

The branch is based on client_CA branch and contains needed changes to the
crypto C API and the resulting simplification in SSL module.

Let me know if you think this is worth pursuing, in that case I'll open a
ticket and try to find some time to write tests and do the same for SSL
C API.

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

> >> Regarding 4) and 5), the needed changes would be quite intrusive and
> >> I'm not sure that we could preserve binary compatibility. I'll try to
> >> work on that over the weekend, but I would be more comfortable if that
> >> was a separate branch, possibly merged after the release.
> >
> > I think you've probably put more thought into this area so far than I have.
> > So I think I'm happy to worry about these points later on, if at all. I'm
> > going to take a look at the branch again now.
>
> Thanks for your work, I've seen your recent changes, it looks like I should
> look deeper into this "unit" thing in unit testing :).

It can definitely be helpful to have the smallest possible test method for each individual piece of behavior, since a single test method is the smallest unit of testing one can work with (for example, I can run one test method at a time with my test runner, but not less than that :).

Of course, with pyOpenSSL, I find that it becomes a bit challenging to decide what a sensible unit really is. Can't really test add_client_ca without also testing get_client_ca_list, for example. And writing a unit test that verifies some code doesn't leak memory is pretty hard. I'm just applying what I've learned about Python unit testing, hoping that at least some of it makes sense, and trying to learn from the experience. :)

> I've looked into C API issues some more and have found a way that preserves
> API as well as ABI compatibility, you can see the current progress at:
>
> lp:~ziga-seilnacht/pyopenssl/bwcompat-c-api
>
> The branch is based on client_CA branch and contains needed changes to the
> crypto C API and the resulting simplification in SSL module.
>
> Let me know if you think this is worth pursuing, in that case I'll open a
> ticket and try to find some time to write tests and do the same for SSL
> C API.

Cool! I think I'll have a chance to look at this tomorrow. Thanks.

lp:~zseil/pyopenssl/client_CA updated
139. By Jean-Paul Calderone

Minimal tests for Context.add_extra_chain_cert

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-10-23 08:09:11 +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,22 @@
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 preferred certificate signers that would be
30+sent to the client when requesting a client certificate with the
31+\var{certificate_authorities} sequence of \class{OpenSSL.crypto.X509Name}s.
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+
41+\versionadded{0.10}
42+\end{methoddesc}
43+
44 \begin{methoddesc}[Context]{load_verify_locations}{pemfile, capath}
45 Specify where CA certificates for verification purposes are located. These
46 are trusted certificates. Note that the certificates have to be in PEM
47@@ -1026,6 +1042,20 @@
48 but not it returns the entire list in one go.
49 \end{methoddesc}
50
51+\begin{methoddesc}[Connection]{get_client_ca_list}{}
52+Retrieve the list of preferred client certificate issuers sent by the server
53+as \class{OpenSSL.crypto.X509Name} objects.
54+
55+If this is a client \class{Connection}, the list will be empty until the
56+connection with the server is established.
57+
58+If this is a server \class{Connection}, return the list of certificate
59+authorities that will be sent or has been sent to the client, as controlled
60+by this \class{Connection}'s \class{Context}.
61+
62+\versionadded{0.10}
63+\end{methoddesc}
64+
65 \begin{methoddesc}[Connection]{get_context}{}
66 Retrieve the Context object associated with this Connection.
67 \end{methoddesc}
68
69=== modified file 'src/ssl/connection.c'
70--- src/ssl/connection.c 2009-07-08 16:48:33 +0000
71+++ src/ssl/connection.c 2009-10-23 08:09:11 +0000
72@@ -829,6 +829,58 @@
73 return lst;
74 }
75
76+static char ssl_Connection_get_client_ca_list_doc[] = "\n\
77+Get CAs whose certificates are suggested for client authentication.\n\
78+\n\
79+@return: A list of X509Names representing the acceptable CAs as set by\n\
80+ ssl.Context.{set, add}_client_ca* if this is a server connection\n\
81+ or as sent by the server if this is a client connection.\n\
82+";
83+
84+static PyObject *
85+ssl_Connection_get_client_ca_list(ssl_ConnectionObj *self, PyObject *args)
86+{
87+ STACK_OF(X509_NAME) *CANames;
88+ PyObject *CAList;
89+ int i, n;
90+
91+ if (!PyArg_ParseTuple(args, ":get_client_ca_list")) {
92+ return NULL;
93+ }
94+ CANames = SSL_get_client_CA_list(self->ssl);
95+ if (CANames == NULL) {
96+ return PyList_New(0);
97+ }
98+ n = sk_X509_NAME_num(CANames);
99+ CAList = PyList_New(n);
100+ if (CAList == NULL) {
101+ return NULL;
102+ }
103+ for (i = 0; i < n; i++) {
104+ X509_NAME *CAName;
105+ PyObject *CA;
106+
107+ CAName = X509_NAME_dup(sk_X509_NAME_value(CANames, i));
108+ if (CAName == NULL) {
109+ Py_DECREF(CAList);
110+ exception_from_error_queue(ssl_Error);
111+ return NULL;
112+ }
113+ CA = (PyObject *)crypto_X509Name_New(CAName, 1);
114+ if (CA == NULL) {
115+ X509_NAME_free(CAName);
116+ Py_DECREF(CAList);
117+ return NULL;
118+ }
119+ if (PyList_SetItem(CAList, i, CA)) {
120+ Py_DECREF(CA);
121+ Py_DECREF(CAList);
122+ return NULL;
123+ }
124+ }
125+ return CAList;
126+}
127+
128 static char ssl_Connection_makefile_doc[] = "\n\
129 The makefile() method is not implemented, since there is no dup semantics\n\
130 for SSL connections\n\
131@@ -1087,6 +1139,7 @@
132 ADD_METHOD(bio_shutdown),
133 ADD_METHOD(shutdown),
134 ADD_METHOD(get_cipher_list),
135+ ADD_METHOD(get_client_ca_list),
136 ADD_METHOD(makefile),
137 ADD_METHOD(get_app_data),
138 ADD_METHOD(set_app_data),
139
140=== modified file 'src/ssl/context.c'
141--- src/ssl/context.c 2009-07-08 16:48:33 +0000
142+++ src/ssl/context.c 2009-10-23 08:09:11 +0000
143@@ -335,36 +335,65 @@
144 return Py_None;
145 }
146
147+static PyTypeObject *
148+type_modified_error(const char *name)
149+{
150+ PyErr_Format(PyExc_RuntimeError,
151+ "OpenSSL.crypto's '%s' attribute has been modified",
152+ name);
153+ return NULL;
154+}
155+
156+static PyTypeObject *
157+import_crypto_type(const char *name, size_t objsize)
158+{
159+ PyObject *module, *type, *name_attr;
160+ PyTypeObject *res;
161+ int right_name;
162+
163+ module = PyImport_ImportModule("OpenSSL.crypto");
164+ if (module == NULL) {
165+ return NULL;
166+ }
167+ type = PyObject_GetAttrString(module, name);
168+ Py_DECREF(module);
169+ if (type == NULL) {
170+ return NULL;
171+ }
172+ if (!(PyType_Check(type))) {
173+ Py_DECREF(type);
174+ return type_modified_error(name);
175+ }
176+ name_attr = PyObject_GetAttrString(type, "__name__");
177+ if (name_attr == NULL) {
178+ Py_DECREF(type);
179+ return NULL;
180+ }
181+ right_name = (PyString_CheckExact(name_attr) &&
182+ strcmp(name, PyString_AsString(name_attr)) == 0);
183+ Py_DECREF(name_attr);
184+ res = (PyTypeObject *)type;
185+ if (!right_name || res->tp_basicsize != objsize) {
186+ Py_DECREF(type);
187+ return type_modified_error(name);
188+ }
189+ return res;
190+}
191+
192 static crypto_X509Obj *
193-parse_certificate_argument(const char* format1, const char* format2, PyObject* args)
194+parse_certificate_argument(const char* format, PyObject* args)
195 {
196 static PyTypeObject *crypto_X509_type = NULL;
197 crypto_X509Obj *cert;
198
199- /* We need to check that cert really is an X509 object before
200- we deal with it. The problem is we can't just quickly verify
201- the type (since that comes from another module). This should
202- do the trick (reasonably well at least): Once we have one
203- verified object, we use it's type object for future
204- comparisons. */
205-
206 if (!crypto_X509_type)
207 {
208- if (!PyArg_ParseTuple(args, (PYARG_PARSETUPLE_FORMAT *)format1, &cert))
209- return NULL;
210-
211- if (strcmp(cert->ob_type->tp_name, "X509") != 0 ||
212- cert->ob_type->tp_basicsize != sizeof(crypto_X509Obj))
213- {
214- PyErr_SetString(PyExc_TypeError, "Expected an X509 object");
215- return NULL;
216- }
217-
218- crypto_X509_type = cert->ob_type;
219+ crypto_X509_type = import_crypto_type("X509", sizeof(crypto_X509Obj));
220+ if (!crypto_X509_type)
221+ return NULL;
222 }
223- else
224- if (!PyArg_ParseTuple(args, (PYARG_PARSETUPLE_FORMAT *)format2, crypto_X509_type,
225- &cert))
226+ if (!PyArg_ParseTuple(args, (PYARG_PARSETUPLE_FORMAT *)format,
227+ crypto_X509_type, &cert))
228 return NULL;
229 return cert;
230 }
231@@ -381,7 +410,7 @@
232 {
233 X509* cert_original;
234 crypto_X509Obj *cert = parse_certificate_argument(
235- "O:add_extra_chain_cert", "O!:add_extra_chain_cert", args);
236+ "O!:add_extra_chain_cert", args);
237 if (cert == NULL)
238 {
239 return NULL;
240@@ -471,7 +500,7 @@
241 ssl_Context_use_certificate(ssl_ContextObj *self, PyObject *args)
242 {
243 crypto_X509Obj *cert = parse_certificate_argument(
244- "O:use_certificate", "O!:use_certificate", args);
245+ "O!:use_certificate", args);
246 if (cert == NULL) {
247 return NULL;
248 }
249@@ -538,28 +567,12 @@
250 static PyTypeObject *crypto_PKey_type = NULL;
251 crypto_PKeyObj *pkey;
252
253- /* We need to check that cert really is a PKey object before
254- we deal with it. The problem is we can't just quickly verify
255- the type (since that comes from another module). This should
256- do the trick (reasonably well at least): Once we have one
257- verified object, we use it's type object for future
258- comparisons. */
259-
260 if (!crypto_PKey_type)
261 {
262- if (!PyArg_ParseTuple(args, "O:use_privatekey", &pkey))
263- return NULL;
264-
265- if (strcmp(pkey->ob_type->tp_name, "OpenSSL.crypto.PKey") != 0 ||
266- pkey->ob_type->tp_basicsize != sizeof(crypto_PKeyObj))
267- {
268- PyErr_SetString(PyExc_TypeError, "Expected a PKey object");
269- return NULL;
270- }
271-
272- crypto_PKey_type = pkey->ob_type;
273+ crypto_PKey_type = import_crypto_type("PKey", sizeof(crypto_PKeyObj));
274+ if (!crypto_PKey_type)
275+ return NULL;
276 }
277- else
278 if (!PyArg_ParseTuple(args, "O!:use_privatekey", crypto_PKey_type, &pkey))
279 return NULL;
280
281@@ -789,6 +802,111 @@
282 }
283 }
284
285+static char ssl_Context_set_client_ca_list_doc[] = "\n\
286+Set the list of preferred client certificate signers for this server context.\n\
287+\n\
288+This list of certificate authorities will be sent to the client when the\n\
289+server requests a client certificate.\n\
290+\n\
291+@param certificate_authorities: a sequence of X509Names.\n\
292+@return: None\n\
293+";
294+
295+static PyObject *
296+ssl_Context_set_client_ca_list(ssl_ContextObj *self, PyObject *args)
297+{
298+ static PyTypeObject *X509NameType;
299+ PyObject *sequence, *tuple, *item;
300+ crypto_X509NameObj *name;
301+ X509_NAME *sslname;
302+ STACK_OF(X509_NAME) *CANames;
303+ Py_ssize_t length;
304+ int i;
305+
306+ if (X509NameType == NULL) {
307+ X509NameType = import_crypto_type("X509Name", sizeof(crypto_X509NameObj));
308+ if (X509NameType == NULL) {
309+ return NULL;
310+ }
311+ }
312+ if (!PyArg_ParseTuple(args, "O:set_client_ca_list", &sequence)) {
313+ return NULL;
314+ }
315+ tuple = PySequence_Tuple(sequence);
316+ if (tuple == NULL) {
317+ return NULL;
318+ }
319+ length = PyTuple_Size(tuple);
320+ if (length >= INT_MAX) {
321+ PyErr_SetString(PyExc_ValueError, "client CA list is too long");
322+ Py_DECREF(tuple);
323+ return NULL;
324+ }
325+ CANames = sk_X509_NAME_new_null();
326+ if (CANames == NULL) {
327+ Py_DECREF(tuple);
328+ exception_from_error_queue(ssl_Error);
329+ return NULL;
330+ }
331+ for (i = 0; i < length; i++) {
332+ item = PyTuple_GetItem(tuple, i);
333+ if (item->ob_type != X509NameType) {
334+ PyErr_Format(PyExc_TypeError,
335+ "client CAs must be X509Name objects, not %s objects",
336+ item->ob_type->tp_name);
337+ sk_X509_NAME_free(CANames);
338+ Py_DECREF(tuple);
339+ return NULL;
340+ }
341+ name = (crypto_X509NameObj *)item;
342+ sslname = X509_NAME_dup(name->x509_name);
343+ if (sslname == NULL) {
344+ sk_X509_NAME_free(CANames);
345+ Py_DECREF(tuple);
346+ exception_from_error_queue(ssl_Error);
347+ return NULL;
348+ }
349+ if (!sk_X509_NAME_push(CANames, sslname)) {
350+ X509_NAME_free(sslname);
351+ sk_X509_NAME_free(CANames);
352+ Py_DECREF(tuple);
353+ exception_from_error_queue(ssl_Error);
354+ return NULL;
355+ }
356+ }
357+ Py_DECREF(tuple);
358+ SSL_CTX_set_client_CA_list(self->ctx, CANames);
359+ Py_INCREF(Py_None);
360+ return Py_None;
361+}
362+
363+static char ssl_Context_add_client_ca_doc[] = "\n\
364+Add the CA certificate to the list of preferred signers for this context.\n\
365+\n\
366+The list of certificate authorities will be sent to the client when the\n\
367+server requests a client certificate.\n\
368+\n\
369+@param certificate_authority: certificate authority's X509 certificate.\n\
370+@return: None\n\
371+";
372+
373+static PyObject *
374+ssl_Context_add_client_ca(ssl_ContextObj *self, PyObject *args)
375+{
376+ crypto_X509Obj *cert;
377+
378+ cert = parse_certificate_argument("O!:add_client_ca", args);
379+ if (cert == NULL) {
380+ return NULL;
381+ }
382+ if (!SSL_CTX_add_client_CA(self->ctx, cert->x509)) {
383+ exception_from_error_queue(ssl_Error);
384+ return NULL;
385+ }
386+ Py_INCREF(Py_None);
387+ return Py_None;
388+}
389+
390 static char ssl_Context_set_timeout_doc[] = "\n\
391 Set session timeout\n\
392 \n\
393@@ -960,6 +1078,8 @@
394 ADD_METHOD(get_verify_depth),
395 ADD_METHOD(load_tmp_dh),
396 ADD_METHOD(set_cipher_list),
397+ ADD_METHOD(set_client_ca_list),
398+ ADD_METHOD(add_client_ca),
399 ADD_METHOD(set_timeout),
400 ADD_METHOD(get_timeout),
401 ADD_METHOD(set_info_callback),
402
403=== modified file 'src/util.h'
404--- src/util.h 2009-07-08 16:48:33 +0000
405+++ src/util.h 2009-10-23 08:09:11 +0000
406@@ -119,6 +119,10 @@
407 }
408 #endif
409
410-
411+#if !defined(PY_SSIZE_T_MIN)
412+typedef int Py_ssize_t;
413+#define PY_SSIZE_T_MAX INT_MAX
414+#define PY_SSIZE_T_MIN INT_MIN
415+#endif
416
417 #endif
418
419=== modified file 'test/test_ssl.py'
420--- test/test_ssl.py 2009-07-25 16:32:46 +0000
421+++ test/test_ssl.py 2009-10-23 08:09:11 +0000
422@@ -255,7 +255,7 @@
423 context = Context(SSLv3_METHOD)
424 context.set_default_verify_paths()
425 context.set_verify(
426- VERIFY_PEER,
427+ VERIFY_PEER,
428 lambda conn, cert, errno, depth, preverify_ok: preverify_ok)
429
430 client = socket()
431@@ -511,8 +511,8 @@
432 established = True # assume the best
433 for ssl in client_conn, server_conn:
434 try:
435- # Generally a recv() or send() could also work instead
436- # of do_handshake(), and we would stop on the first
437+ # Generally a recv() or send() could also work instead
438+ # of do_handshake(), and we would stop on the first
439 # non-exception.
440 ssl.do_handshake()
441 except WantReadError:
442@@ -583,6 +583,125 @@
443 self.assertEquals(e.__class__, Error)
444
445
446+ def _check_client_ca_list(self, func):
447+ server = self._server(None)
448+ client = self._client(None)
449+ self.assertEqual(client.get_client_ca_list(), [])
450+ self.assertEqual(server.get_client_ca_list(), [])
451+ ctx = server.get_context()
452+ expected = func(ctx)
453+ self.assertEqual(client.get_client_ca_list(), [])
454+ self.assertEqual(server.get_client_ca_list(), expected)
455+ self._loopback(client, server)
456+ self.assertEqual(client.get_client_ca_list(), expected)
457+ self.assertEqual(server.get_client_ca_list(), expected)
458+
459+
460+ def test_set_client_ca_list_basic(self):
461+ """
462+ Test paramater validation and return value for
463+ L{Context.set_client_ca_list}.
464+ """
465+ ctx = Context(TLSv1_METHOD)
466+ self.assertRaises(TypeError, ctx.set_client_ca_list, "spam")
467+ self.assertRaises(TypeError, ctx.set_client_ca_list, ["spam"])
468+ self.assertIdentical(ctx.set_client_ca_list([]), None)
469+
470+
471+ def test_set_client_ca_list_functional(self):
472+ """
473+ The list of CAs set by L{Context.set_client_ca_list} and read by
474+ L{Connection.get_client_ca_list} should match on server and
475+ client side.
476+ """
477+ cacert = load_certificate(FILETYPE_PEM, root_cert_pem)
478+ secert = load_certificate(FILETYPE_PEM, server_cert_pem)
479+ clcert = load_certificate(FILETYPE_PEM, server_cert_pem)
480+
481+ cadesc = cacert.get_subject()
482+ sedesc = secert.get_subject()
483+ cldesc = clcert.get_subject()
484+
485+ def single_ca(ctx):
486+ ctx.set_client_ca_list([cadesc])
487+ return [cadesc]
488+ self._check_client_ca_list(single_ca)
489+
490+ def no_ca(ctx):
491+ ctx.set_client_ca_list([])
492+ return []
493+ self._check_client_ca_list(no_ca)
494+
495+ def multiple_ca(ctx):
496+ L = [cadesc, sedesc, cldesc]
497+ ctx.set_client_ca_list(L)
498+ return L
499+ self._check_client_ca_list(multiple_ca)
500+
501+ def changed_ca(ctx):
502+ ctx.set_client_ca_list([sedesc, cldesc])
503+ ctx.set_client_ca_list([cadesc])
504+ return [cadesc]
505+ self._check_client_ca_list(changed_ca)
506+
507+ def mutated_ca(ctx):
508+ L = [cadesc]
509+ ctx.set_client_ca_list([cadesc])
510+ L.append(sedesc)
511+ return [cadesc]
512+ self._check_client_ca_list(mutated_ca)
513+
514+
515+ def test_add_client_ca_basic(self):
516+ """
517+ Test paramater validation and return value for
518+ L{Context.add_client_ca}.
519+ """
520+ ctx = Context(TLSv1_METHOD)
521+ cacert = load_certificate(FILETYPE_PEM, root_cert_pem)
522+ self.assertRaises(TypeError, ctx.add_client_ca, "spam")
523+ self.assertIdentical(ctx.add_client_ca(cacert), None)
524+
525+
526+ def test_add_client_ca_functional(self):
527+ """
528+ The list of CAs set by L{Context.add_client_ca} and read by
529+ L{Connection.get_client_ca_list} should match on server and
530+ client side.
531+ """
532+ cacert = load_certificate(FILETYPE_PEM, root_cert_pem)
533+ secert = load_certificate(FILETYPE_PEM, server_cert_pem)
534+ clcert = load_certificate(FILETYPE_PEM, server_cert_pem)
535+
536+ cadesc = cacert.get_subject()
537+ sedesc = secert.get_subject()
538+ cldesc = clcert.get_subject()
539+
540+ def single_ca(ctx):
541+ ctx.add_client_ca(cacert)
542+ return [cadesc]
543+ self._check_client_ca_list(single_ca)
544+
545+ def multiple_ca(ctx):
546+ ctx.add_client_ca(cacert)
547+ ctx.add_client_ca(secert)
548+ return [cadesc, sedesc]
549+ self._check_client_ca_list(multiple_ca)
550+
551+ def mixed_set_add_ca(ctx):
552+ ctx.set_client_ca_list([cadesc, sedesc])
553+ ctx.add_client_ca(clcert)
554+ return [cadesc, sedesc, cldesc]
555+ self._check_client_ca_list(mixed_set_add_ca)
556+
557+ def set_replaces_add_ca(ctx):
558+ ctx.add_client_ca(clcert)
559+ ctx.set_client_ca_list([cadesc])
560+ ctx.add_client_ca(secert)
561+ return [cadesc, sedesc]
562+ self._check_client_ca_list(set_replaces_add_ca)
563+
564+
565
566 if __name__ == '__main__':
567 main()

Subscribers

People subscribed via source and target branches

to status/vote changes: