Merge lp:~zseil/pyopenssl/client_CA into lp:~exarkun/pyopenssl/trunk
- client_CA
- Merge into trunk
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 | ||||
Related bugs: |
|
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.
Commit message
Description of the change
Ziga Seilnacht (zseil) wrote : Posted in a previous version of this proposal | # |
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.
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.
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_
5. Likewise, crypto_
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.
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_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_
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
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
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.
- 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.
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.
for cacert in cacerts:
I also lowercased the new methods, for consistency with the rest of
PyOpenSSL (e.g. SSL.Context.
crypto.
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.
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.
- 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
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.
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.
- 139. By Jean-Paul Calderone
-
Minimal tests for Context.
add_extra_ chain_cert
Preview Diff
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() |
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