Merge lp:~jorton/pyopenssl/trust into lp:~exarkun/pyopenssl/trunk

Proposed by Joe Orton
Status: Needs review
Proposed branch: lp:~jorton/pyopenssl/trust
Merge into: lp:~exarkun/pyopenssl/trunk
Diff against target: 417 lines (+338/-0)
4 files modified
.bzrignore (+2/-0)
src/crypto/crypto.c (+117/-0)
src/crypto/x509.c (+169/-0)
test/test_crypto.py (+50/-0)
To merge this branch: bzr merge lp:~jorton/pyopenssl/trust
Reviewer Review Type Date Requested Status
Jean-Paul Calderone Pending
Review via email: mp+27232@code.launchpad.net

Description of the change

Add support for loading and dumping trusted certificates.

Expose trust attributes in the API.

To post a comment you must log in.

Unmerged revisions

131. By Joe Orton

Add support for simple trusted certificate handling, and manipulation
of a certificates trust bits:

* src/crypto/crypto.c (crypto_load_trusted_certificate,
  crypto_dump_trusted_certificate): New methods.
  (initcrypto): Add TRUST_* string constants.

* src/crypto/x509.c (crypto_X509_set_trusted_uses,
  crypto_X509_get_trusted_uses, crypto_X509_get_rejected_uses,
  crypto_X509_set_rejected_uses): New methods.
  (crypto_set_trust_or_reject, crypto_X509_stack_to_list):
  New utility function.

* test/test_crypto.py (FunctionTests.test_trusted_certificates):
  New test case.

130. By Joe Orton

* .bzrignore: Ignore test output.

129. By Joe Orton

* .bzrignore: New file; ignore build directory.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file '.bzrignore'
2--- .bzrignore 1970-01-01 00:00:00 +0000
3+++ .bzrignore 2010-06-10 08:46:24 +0000
4@@ -0,0 +1,2 @@
5+build
6+_trial_temp
7
8=== modified file 'src/crypto/crypto.c'
9--- src/crypto/crypto.c 2010-05-24 22:04:59 +0000
10+++ src/crypto/crypto.c 2010-06-10 08:46:24 +0000
11@@ -328,6 +328,113 @@
12 return buffer;
13 }
14
15+static char crypto_load_trusted_certificate_doc[] = "\n\
16+Load a trusted certificate from a buffer\n\
17+\n\
18+@param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1)\n\
19+ buffer - The buffer the certificate is stored in\n\
20+@return: The X509 object\n\
21+";
22+
23+static PyObject *
24+crypto_load_trusted_certificate(PyObject *spam, PyObject *args)
25+{
26+ crypto_X509Obj *crypto_X509_New(X509 *, int);
27+ int type, len;
28+ char *buffer;
29+ BIO *bio;
30+ X509 *cert;
31+
32+ if (!PyArg_ParseTuple(args, "is#:load_trusted_certificate", &type, &buffer, &len))
33+ return NULL;
34+
35+ bio = BIO_new_mem_buf(buffer, len);
36+ switch (type)
37+ {
38+ case X509_FILETYPE_PEM:
39+ cert = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL);
40+ break;
41+
42+#if 0 /* not implemented yet */
43+ case X509_FILETYPE_ASN1:
44+ cert = d2i_X509_AUX_bio(bio, NULL);
45+ break;
46+#endif
47+
48+ default:
49+ PyErr_SetString(PyExc_ValueError, "type argument must be FILETYPE_PEM");
50+ BIO_free(bio);
51+ return NULL;
52+ }
53+ BIO_free(bio);
54+
55+ if (cert == NULL)
56+ {
57+ exception_from_error_queue(crypto_Error);
58+ return NULL;
59+ }
60+
61+ return (PyObject *)crypto_X509_New(cert, 1);
62+}
63+
64+static char crypto_dump_trusted_certificate_doc[] = "\n\
65+Dump a trusted certificate to a buffer\n\
66+\n\
67+@param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1)\n\
68+@param cert: The certificate to dump\n\
69+@return: The buffer with the dumped certificate in\n\
70+";
71+
72+static PyObject *
73+crypto_dump_trusted_certificate(PyObject *spam, PyObject *args)
74+{
75+ int type, ret, buf_len;
76+ char *temp;
77+ PyObject *buffer;
78+ BIO *bio;
79+ crypto_X509Obj *cert;
80+
81+ if (!PyArg_ParseTuple(args, "iO!:dump_trusted_certificate", &type,
82+ &crypto_X509_Type, &cert))
83+ return NULL;
84+
85+ bio = BIO_new(BIO_s_mem());
86+ switch (type)
87+ {
88+ case X509_FILETYPE_PEM:
89+ ret = PEM_write_bio_X509_AUX(bio, cert->x509);
90+ break;
91+
92+#if 0 /* not implemented */
93+ case X509_FILETYPE_ASN1:
94+ ret = i2d_X509_bio(bio, cert->x509);
95+ break;
96+
97+ case X509_FILETYPE_TEXT:
98+ ret = X509_print_ex(bio, cert->x509, 0, 0);
99+ break;
100+#endif
101+
102+ default:
103+ PyErr_SetString(PyExc_ValueError, "type argument must be FILETYPE_PEM");
104+ BIO_free(bio);
105+ return NULL;
106+ }
107+
108+ if (ret == 0)
109+ {
110+ BIO_free(bio);
111+ exception_from_error_queue(crypto_Error);
112+ return NULL;
113+ }
114+
115+ buf_len = BIO_get_mem_data(bio, &temp);
116+ buffer = PyString_FromStringAndSize(temp, buf_len);
117+ BIO_free(bio);
118+
119+ return buffer;
120+}
121+
122 static char crypto_load_certificate_request_doc[] = "\n\
123 Load a certificate request from a buffer\n\
124 \n\
125@@ -598,6 +705,9 @@
126 { "dump_privatekey", (PyCFunction)crypto_dump_privatekey, METH_VARARGS, crypto_dump_privatekey_doc },
127 { "load_certificate", (PyCFunction)crypto_load_certificate, METH_VARARGS, crypto_load_certificate_doc },
128 { "dump_certificate", (PyCFunction)crypto_dump_certificate, METH_VARARGS, crypto_dump_certificate_doc },
129+
130+ { "load_trusted_certificate", (PyCFunction)crypto_load_trusted_certificate, METH_VARARGS, crypto_load_trusted_certificate_doc },
131+ { "dump_trusted_certificate", (PyCFunction)crypto_dump_trusted_certificate, METH_VARARGS, crypto_dump_trusted_certificate_doc },
132 { "load_certificate_request", (PyCFunction)crypto_load_certificate_request, METH_VARARGS, crypto_load_certificate_request_doc },
133 { "dump_certificate_request", (PyCFunction)crypto_dump_certificate_request, METH_VARARGS, crypto_dump_certificate_request_doc },
134 { "load_crl", (PyCFunction)crypto_load_crl, METH_VARARGS, crypto_load_crl_doc },
135@@ -713,6 +823,13 @@
136 if (PyModule_AddObject(module, "Error", crypto_Error) != 0)
137 goto error;
138
139+ PyModule_AddStringConstant(module, "TRUST_SSL_SERVER", SN_server_auth);
140+ PyModule_AddStringConstant(module, "TRUST_SSL_CLIENT", SN_client_auth);
141+ PyModule_AddStringConstant(module, "TRUST_OBJECT_SIGN", SN_code_sign);
142+ PyModule_AddStringConstant(module, "TRUST_EMAIL", SN_email_protect);
143+ PyModule_AddStringConstant(module, "TRUST_TSA", SN_time_stamp);
144+ PyModule_AddStringConstant(module, "TRUST_OCSP_SIGN", SN_OCSP_sign);
145+
146 PyModule_AddIntConstant(module, "FILETYPE_PEM", X509_FILETYPE_PEM);
147 PyModule_AddIntConstant(module, "FILETYPE_ASN1", X509_FILETYPE_ASN1);
148 PyModule_AddIntConstant(module, "FILETYPE_TEXT", X509_FILETYPE_TEXT);
149
150=== modified file 'src/crypto/x509.c'
151--- src/crypto/x509.c 2010-01-25 22:55:30 +0000
152+++ src/crypto/x509.c 2010-06-10 08:46:24 +0000
153@@ -3,6 +3,7 @@
154 *
155 * Copyright (C) AB Strakt 2001, All rights reserved
156 * Copyright (C) Jean-Paul Calderone 2008, All rights reserved
157+ * Copyright (C) Red Hat, Inc. 2010.
158 *
159 * Certificate (X.509) handling code, mostly thin wrappers around OpenSSL.
160 * See the file RATIONALE for a short explanation of why this module was written.
161@@ -688,6 +689,170 @@
162 return Py_None;
163 }
164
165+/* Return sorted list object with short names of objects in stack. */
166+static PyObject *
167+crypto_X509_stack_to_list(STACK_OF(ASN1_OBJECT *) stack)
168+{
169+ PyObject *list;
170+ int i, n;
171+
172+ n = sk_ASN1_OBJECT_num(stack);
173+ list = PyList_New(n);
174+
175+ for (i = 0; i < n; i++)
176+ {
177+ ASN1_OBJECT *obj = sk_ASN1_OBJECT_value(stack, i);
178+ int nid = OBJ_obj2nid(obj);
179+ const char *sn = OBJ_nid2sn(nid);
180+
181+ if (sn)
182+ {
183+ PyList_SetItem(list, i, PyString_FromString(sn));
184+ }
185+ else
186+ {
187+ PyErr_SetString(PyExc_RuntimeError, "Unknown name for nid");
188+ Py_DECREF(list);
189+ return NULL;
190+ }
191+ }
192+
193+ if (PyList_Sort(list))
194+ {
195+ PyErr_SetString(PyExc_RuntimeError, "Failed to sort list");
196+ Py_DECREF(list);
197+ return NULL;
198+ }
199+
200+ return list;
201+}
202+
203+static char crypto_X509_get_trusted_uses_doc[] = "\n\
204+Return a list of the trusted uses for the certificate.\n\
205+\n\
206+@return: A list of strings, or None if the cert contains no trust information.\n\
207+";
208+
209+static PyObject *
210+crypto_X509_get_trusted_uses(crypto_X509Obj *self, PyObject *args)
211+{
212+ if (!PyArg_ParseTuple(args, ":get_trusted_uses"))
213+ return NULL;
214+
215+ if (self->x509->aux && self->x509->aux->trust)
216+ {
217+ return crypto_X509_stack_to_list(self->x509->aux->trust);
218+ }
219+ else
220+ {
221+ Py_INCREF(Py_None);
222+ return Py_None;
223+ }
224+}
225+
226+static char crypto_X509_get_rejected_uses_doc[] = "\n\
227+Return a list of rejected uses for the certificate.\n\
228+\n\
229+@return: A list of strings, or None if the cert contains no trust information.\n\
230+";
231+static PyObject *
232+crypto_X509_get_rejected_uses(crypto_X509Obj *self, PyObject *args)
233+{
234+ if (!PyArg_ParseTuple(args, ":get_rejected_uses"))
235+ return NULL;
236+
237+ if (self->x509->aux && self->x509->aux->reject)
238+ {
239+ return crypto_X509_stack_to_list(self->x509->aux->reject);
240+ }
241+ else
242+ {
243+ Py_INCREF(Py_None);
244+ return Py_None;
245+ }
246+}
247+
248+static PyObject *
249+crypto_set_trust_or_reject(X509 *x509, PyObject *list, int trust)
250+{
251+ Py_ssize_t i, n;
252+ int *nids;
253+
254+ n = PyList_Size(list);
255+ nids = malloc(n * sizeof *nids);
256+
257+ for (i = 0; i < n; i++)
258+ {
259+ PyObject *o = PyList_GET_ITEM(list, i);
260+
261+ if (!PyString_Check(o))
262+ {
263+ free(nids);
264+ PyErr_SetString(PyExc_TypeError, "List must be of strings");
265+ return NULL;
266+ }
267+
268+ nids[i] = OBJ_sn2nid(PyString_AS_STRING(o));
269+ if (nids[i] == NID_undef)
270+ {
271+ free(nids);
272+ PyErr_SetString(PyExc_ValueError, "Trust type not known");
273+ return NULL;
274+ }
275+ }
276+
277+ X509_trust_clear(x509);
278+
279+ for (i = 0; i < n; i++)
280+ {
281+ ASN1_OBJECT *obj = OBJ_nid2obj(nids[i]);
282+
283+ if (trust)
284+ X509_add1_trust_object(x509, obj);
285+ else
286+ X509_add1_reject_object(x509, obj);
287+ }
288+
289+ free(nids);
290+
291+ Py_INCREF(Py_None);
292+ return Py_None;
293+}
294+
295+static char crypto_X509_set_trusted_uses_doc[] = "\n\
296+Set the list of trusted uses for the certificate.\n\
297+\n\
298+@return: None\
299+";
300+static PyObject *
301+crypto_X509_set_trusted_uses(crypto_X509Obj *self, PyObject *args)
302+{
303+ PyObject *list;
304+
305+ if (!PyArg_ParseTuple(args, "O!:set_trusted_uses",
306+ &PyList_Type, &list))
307+ return NULL;
308+
309+ return crypto_set_trust_or_reject(self->x509, list, 1);
310+}
311+
312+static char crypto_X509_set_rejected_uses_doc[] = "\n\
313+Set the list of rejected uses for the certificate.\n\
314+\n\
315+@return: None\
316+";
317+static PyObject *
318+crypto_X509_set_rejected_uses(crypto_X509Obj *self, PyObject *args)
319+{
320+ PyObject *list;
321+
322+ if (!PyArg_ParseTuple(args, "O!:set_rejected_uses",
323+ &PyList_Type, &list))
324+ return NULL;
325+
326+ return crypto_set_trust_or_reject(self->x509, list, 0);
327+}
328+
329 /*
330 * ADD_METHOD(name) expands to a correct PyMethodDef declaration
331 * { 'name', (PyCFunction)crypto_X509_name, METH_VARARGS }
332@@ -718,6 +883,10 @@
333 ADD_METHOD(subject_name_hash),
334 ADD_METHOD(digest),
335 ADD_METHOD(add_extensions),
336+ ADD_METHOD(get_trusted_uses),
337+ ADD_METHOD(get_rejected_uses),
338+ ADD_METHOD(set_trusted_uses),
339+ ADD_METHOD(set_rejected_uses),
340 { NULL, NULL }
341 };
342 #undef ADD_METHOD
343
344=== modified file 'test/test_crypto.py'
345--- test/test_crypto.py 2010-05-25 19:08:32 +0000
346+++ test/test_crypto.py 2010-06-10 08:46:24 +0000
347@@ -17,6 +17,9 @@
348 from OpenSSL.crypto import load_certificate, load_privatekey
349 from OpenSSL.crypto import FILETYPE_PEM, FILETYPE_ASN1, FILETYPE_TEXT
350 from OpenSSL.crypto import dump_certificate, load_certificate_request
351+from OpenSSL.crypto import load_trusted_certificate, dump_trusted_certificate
352+from OpenSSL.crypto import TRUST_SSL_CLIENT, TRUST_SSL_SERVER
353+from OpenSSL.crypto import TRUST_EMAIL, TRUST_OBJECT_SIGN
354 from OpenSSL.crypto import dump_certificate_request, dump_privatekey
355 from OpenSSL.crypto import PKCS7Type, load_pkcs7_data
356 from OpenSSL.crypto import PKCS12, PKCS12Type, load_pkcs12
357@@ -78,6 +81,23 @@
358 -----END CERTIFICATE-----
359 """
360
361+server_trusted_cert_pem = """-----BEGIN TRUSTED CERTIFICATE-----
362+MIICKDCCAZGgAwIBAgIJAJn/HpR21r/8MA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV
363+BAYTAlVTMQswCQYDVQQIEwJJTDEQMA4GA1UEBxMHQ2hpY2FnbzEQMA4GA1UEChMH
364+VGVzdGluZzEYMBYGA1UEAxMPVGVzdGluZyBSb290IENBMCIYDzIwMDkwMzI1MTIz
365+NzUzWhgPMjAxNzA2MTExMjM3NTNaMBgxFjAUBgNVBAMTDWxvdmVseSBzZXJ2ZXIw
366+gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAL6m+G653V0tpBC/OKl22VxOi2Cv
367+lK4TYu9LHSDP9uDVTe7V5D5Tl6qzFoRRx5pfmnkqT5B+W9byp2NU3FC5hLm5zSAr
368+b45meUhjEJ/ifkZgbNUjHdBIGP9MAQUHZa5WKdkGIJvGAvs8UzUqlr4TBWQIB24+
369+lJ+Ukk/CRgasrYwdAgMBAAGjNjA0MB0GA1UdDgQWBBS4kC7Ij0W1TZXZqXQFAM2e
370+gKEG2DATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQUFAAOBgQBh30Li
371+dJ+NlxIOx5343WqIBka3UbsOb2kxWrbkVCrvRapCMLCASO4FqiKWM+L0VDBprqIp
372+2mgpFQ6FHpoIENGvJhdEKpptQ5i7KaGhnDNTfdy3x1+h852G99f1iyj0RmbuFcM8
373+uzujnS8YXWvM7DM1Ilozk4MzPug8jzFp5uhKCTAiMBQGCCsGAQUFBwMCBggrBgEF
374+BQcDAaAKBggrBgEFBQcDAw==
375+-----END TRUSTED CERTIFICATE-----
376+"""
377+
378 server_key_pem = """-----BEGIN RSA PRIVATE KEY-----
379 MIICWwIBAAKBgQC+pvhuud1dLaQQvzipdtlcTotgr5SuE2LvSx0gz/bg1U3u1eQ+
380 U5eqsxaEUceaX5p5Kk+QflvW8qdjVNxQuYS5uc0gK2+OZnlIYxCf4n5GYGzVIx3Q
381@@ -1446,6 +1466,36 @@
382 good_text = _runopenssl(dumped_pem, "x509", "-noout", "-text")
383 self.assertEqual(dumped_text, good_text)
384
385+ def test_trusted_certificates(self):
386+ """
387+ L{load_trusted_certificate} test.
388+ """
389+ pemData = server_trusted_cert_pem
390+ cert = load_trusted_certificate(FILETYPE_PEM, pemData)
391+ self.assertTrue(isinstance(cert, X509Type))
392+ dumped_pem = dump_trusted_certificate(FILETYPE_PEM, cert)
393+ good_pem = _runopenssl(dumped_pem, "x509", "-outform", "PEM", "-trustout")
394+ self.assertEqual(dumped_pem, good_pem)
395+ self.assertEqual(dumped_pem, pemData)
396+ # Test equivalence of trusted cert with normal cert
397+ plaincert = load_certificate(FILETYPE_PEM, server_cert_pem)
398+ self.assertEqual(plaincert.get_subject(), cert.get_subject())
399+ trusted_uses = [TRUST_SSL_CLIENT, TRUST_SSL_SERVER]
400+ trusted_uses.sort()
401+ rejected_uses = [TRUST_OBJECT_SIGN]
402+ self.assertEqual(cert.get_trusted_uses(), trusted_uses)
403+ self.assertEqual(cert.get_rejected_uses(), rejected_uses)
404+ self.assertEqual(plaincert.get_trusted_uses(), None)
405+ self.assertEqual(plaincert.get_rejected_uses(), None)
406+
407+ trusted_uses = [TRUST_SSL_CLIENT]
408+ cert.set_trusted_uses(trusted_uses)
409+ self.assertEqual(cert.get_trusted_uses(), trusted_uses)
410+
411+ dump_pem2 = dump_trusted_certificate(FILETYPE_PEM, cert)
412+ cert2 = load_trusted_certificate(FILETYPE_PEM, dump_pem2)
413+
414+ self.assertEqual(cert.get_trusted_uses(), trusted_uses)
415
416 def test_dump_privatekey(self):
417 """

Subscribers

People subscribed via source and target branches

to status/vote changes: