Merge lp:~shanemhansen/pyopenssl/dump_publickey into lp:~exarkun/pyopenssl/trunk

Proposed by Shane Hansen
Status: Needs review
Proposed branch: lp:~shanemhansen/pyopenssl/dump_publickey
Merge into: lp:~exarkun/pyopenssl/trunk
Diff against target: 180 lines (+119/-1)
3 files modified
OpenSSL/crypto/crypto.c (+56/-0)
OpenSSL/test/test_crypto.py (+54/-1)
doc/api/crypto.rst (+9/-0)
To merge this branch: bzr merge lp:~shanemhansen/pyopenssl/dump_publickey
Reviewer Review Type Date Requested Status
Jean-Paul Calderone Needs Fixing
Review via email: mp+125370@code.launchpad.net

Description of the change

This change implements the crypto.dump_publickey API. There are tests for ASN1 and PEM formatting.
There seem to be a number of api's for dumping public keys but I was able to match the PEM and ASN1 output with the openssl pkey tool which uses the PEM_write_bio_PUBKEY and i2d_PUBKEY_bio apis.

This is my first attempt at submitting something like this, so let me know on #pyopenssl if there's anything I can add. Thanks!

To post a comment you must log in.
Revision history for this message
Jean-Paul Calderone (exarkun) wrote :

Thanks. Some observations:

  1. The switch in crypto_dump_publickey has a default case that raises an exception with a message talking about FILETYPE_TEXT being a valid input, but there is no FILETYPE_TEXT case in the switch statement.
  2. There's a `print` statement left in test_dump_publickey_valid_filetype
  3. I'd like to see test_dump_publickey_valid_filetype split into two tests, one for FILETYPE_ASN1 and one for FILETYPE_PEM.
  4. There's a bit of trailing whitespace after test_dump_publickey_valid_filetype which should be deleted.
  5. All APIs should be documented in doc/api/ as well.

Otherwise this looks good. Thanks!

review: Needs Fixing
Revision history for this message
Shane Hansen (shanemhansen) wrote :

Thanks for the feedback, I've made the requested changes and repushed.
Let me know if there are any steps I've missed.

On Sat, Oct 20, 2012 at 6:25 PM, Jean-Paul Calderone <
<email address hidden>> wrote:

> The proposal to merge lp:~shanemhansen/pyopenssl/dump_publickey into
> lp:pyopenssl has been updated.
>
> Status: Needs review => Work in progress
>
> For more details, see:
>
> https://code.launchpad.net/~shanemhansen/pyopenssl/dump_publickey/+merge/125370
> --
>
> https://code.launchpad.net/~shanemhansen/pyopenssl/dump_publickey/+merge/125370
> You are the owner of lp:~shanemhansen/pyopenssl/dump_publickey.
>

Unmerged revisions

170. By Shane Hansen

Remove spurious print statement, incorrect exception statement,
and split up tests for dump_publickey.

169. By Space Monkey

Implement crypto.dump_publickey API (with tests)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'OpenSSL/crypto/crypto.c'
2--- OpenSSL/crypto/crypto.c 2009-12-22 16:02:04 +0000
3+++ OpenSSL/crypto/crypto.c 2012-10-21 20:06:21 +0000
4@@ -171,6 +171,60 @@
5 return (PyObject *)crypto_PKey_New(pkey, 1);
6 }
7
8+static char crypto_dump_publickey_doc[] = "\n\
9+Dump a public key to a buffer\n\
10+\n\
11+:param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1)\n\
12+:param pkey: The PKey to dump\n\
13+:return: The buffer with the dumped key in\n\
14+:rtype: :py:data:`str`\n\
15+";
16+
17+static PyObject *
18+crypto_dump_publickey(PyObject *spam, PyObject *args)
19+{
20+ int type, ret, buf_len;
21+ char *temp;
22+ PyObject *buffer;
23+ BIO *bio;
24+ crypto_PKeyObj *pkey;
25+
26+ if (!PyArg_ParseTuple(args, "iO!:dump_publickey", &type,
27+ &crypto_PKey_Type, &pkey)) {
28+ return NULL;
29+ }
30+ bio = BIO_new(BIO_s_mem());
31+ if (bio == NULL) {
32+ exception_from_error_queue(crypto_Error);
33+ return NULL;
34+ }
35+ switch (type) {
36+ case X509_FILETYPE_PEM:
37+ ret = PEM_write_bio_PUBKEY(bio, pkey->pkey);
38+ break;
39+
40+ case X509_FILETYPE_ASN1:
41+ ret = i2d_PUBKEY_bio(bio, pkey->pkey);
42+ break;
43+
44+ default:
45+ PyErr_SetString(PyExc_ValueError, "type argument must be FILETYPE_PEM, FILETYPE_ASN1");
46+ BIO_free(bio);
47+ return NULL;
48+ }
49+
50+ if (ret == 0) {
51+ BIO_free(bio);
52+ return raise_current_error();
53+ }
54+
55+ buf_len = BIO_get_mem_data(bio, &temp);
56+ buffer = PyBytes_FromStringAndSize(temp, buf_len);
57+ BIO_free(bio);
58+
59+ return buffer;
60+}
61+
62 static char crypto_dump_privatekey_doc[] = "\n\
63 Dump a private key to a buffer\n\
64 \n\
65@@ -185,6 +239,7 @@
66 :rtype: :py:data:`str`\n\
67 ";
68
69+
70 static PyObject *
71 crypto_dump_privatekey(PyObject *spam, PyObject *args)
72 {
73@@ -733,6 +788,7 @@
74 /* Module functions */
75 { "load_privatekey", (PyCFunction)crypto_load_privatekey, METH_VARARGS, crypto_load_privatekey_doc },
76 { "dump_privatekey", (PyCFunction)crypto_dump_privatekey, METH_VARARGS, crypto_dump_privatekey_doc },
77+ { "dump_publickey", (PyCFunction)crypto_dump_publickey, METH_VARARGS, crypto_dump_publickey_doc },
78 { "load_certificate", (PyCFunction)crypto_load_certificate, METH_VARARGS, crypto_load_certificate_doc },
79 { "dump_certificate", (PyCFunction)crypto_dump_certificate, METH_VARARGS, crypto_dump_certificate_doc },
80 { "load_certificate_request", (PyCFunction)crypto_load_certificate_request, METH_VARARGS, crypto_load_certificate_request_doc },
81
82=== modified file 'OpenSSL/test/test_crypto.py'
83--- OpenSSL/test/test_crypto.py 2012-03-09 22:58:00 +0000
84+++ OpenSSL/test/test_crypto.py 2012-10-21 20:06:21 +0000
85@@ -18,7 +18,8 @@
86 from OpenSSL.crypto import load_certificate, load_privatekey
87 from OpenSSL.crypto import FILETYPE_PEM, FILETYPE_ASN1, FILETYPE_TEXT
88 from OpenSSL.crypto import dump_certificate, load_certificate_request
89-from OpenSSL.crypto import dump_certificate_request, dump_privatekey
90+from OpenSSL.crypto import dump_certificate_request, dump_privatekey, dump_publickey
91+from OpenSSL.crypto import dump_publickey
92 from OpenSSL.crypto import PKCS7Type, load_pkcs7_data
93 from OpenSSL.crypto import PKCS12, PKCS12Type, load_pkcs12
94 from OpenSSL.crypto import CRL, Revoked, load_crl
95@@ -71,6 +72,24 @@
96 -----END RSA PRIVATE KEY-----
97 """)
98
99+#Generated from `openssl pkey -pubout` on root_key_pem
100+root_public_key_pem = b("""-----BEGIN PUBLIC KEY-----
101+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD5mkLpi7q6ROdu7khB3S9aanA0
102+Zls7vvfGOmB80/yeylhGpsjAjWen0VtSQke/NlEPGtO38tsV7CsuFnSmschvAnGr
103+cJl76b0UOOHUgDTIoRxC6QDU3claegwsrBA+sJEBbqx5RdXbIRGicPG/8qQ4Zm1S
104+KOgotcbwiaor2yxZ2wIDAQAB
105+-----END PUBLIC KEY-----
106+""")
107+#Generated from `openssl pkey -pubout -outform DER` on root_key_pem
108+root_public_key_asn1 = b("""0\x81\x9f0\r\x06\t*\x86H\x86\xf7\r\x01\
109+\x01\x01\x05\x00\x03\x81\x8d\x000\x81\x89\x02\x81\x81\x00\xf9\x9aB\
110+\xe9\x8b\xba\xbaD\xe7n\xeeHA\xdd/Zjp4f[;\xbe\xf7\xc6:`|\xd3\xfc\x9e\
111+\xcaXF\xa6\xc8\xc0\x8dg\xa7\xd1[RBG\xbf6Q\x0f\x1a\xd3\xb7\xf2\xdb\
112+\x15\xec+.\x16t\xa6\xb1\xc8o\x02q\xabp\x99{\xe9\xbd\x148\xe1\xd4\
113+\x804\xc8\xa1\x1cB\xe9\x00\xd4\xdd\xc9Zz\x0c,\xac\x10>\xb0\x91\x01n\
114+\xacyE\xd5\xdb!\x11\xa2p\xf1\xbf\xf2\xa48fmR(\xe8(\xb5\xc6\xf0\x89\
115+\xaa+\xdb,Y\xdb\x02\x03\x01\x00\x01""")
116+
117 server_cert_pem = b("""-----BEGIN CERTIFICATE-----
118 MIICKDCCAZGgAwIBAgIJAJn/HpR21r/8MA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV
119 BAYTAlVTMQswCQYDVQQIEwJJTDEQMA4GA1UEBxMHQ2hpY2FnbzEQMA4GA1UEChMH
120@@ -2069,6 +2088,40 @@
121 load_privatekey,
122 FILETYPE_PEM, encryptedPrivateKeyPEM, lambda *args: 3)
123
124+ def test_dump_publickey_wrong_args(self):
125+ """
126+ :py:obj:`dump_publickey` raises :py:obj:`TypeError` if called with the wrong number
127+ of arguments.
128+ """
129+ self.assertRaises(TypeError, dump_publickey)
130+
131+ def test_dump_publickey_invalid_filetype(self):
132+ """
133+ :py:obj:`dump_publickey` raises :py:obj:`ValueError` if called with an unrecognized
134+ filetype.
135+ """
136+ key = PKey()
137+ key.generate_key(TYPE_RSA, 512)
138+ self.assertRaises(ValueError, dump_publickey, 100, key)
139+
140+ def test_dump_publickey_valid_filetype_asn1(self):
141+ """
142+ :py:obj:`dump_publickey` can successfully dump an RSA public key
143+ in ASN1 format
144+ """
145+ key = load_privatekey(FILETYPE_PEM, root_key_pem)
146+ buf = dump_publickey(FILETYPE_ASN1, key)
147+ self.assertEqual(buf, root_public_key_asn1,
148+ "Public key matches that from openssl DER")
149+
150+ def test_dump_publickey_valid_filetype_PEM(self):
151+ """
152+ :py:obj:`dump_publickey` can successfully dump an RSA public key.
153+ """
154+ key = load_privatekey(FILETYPE_PEM, root_key_pem)
155+ buf = dump_publickey(FILETYPE_PEM, key)
156+ self.assertEqual(buf, root_public_key_pem,
157+ "Public key matches that from openssl PEM")
158
159 def test_dump_privatekey_wrong_args(self):
160 """
161
162=== modified file 'doc/api/crypto.rst'
163--- doc/api/crypto.rst 2012-09-12 14:49:07 +0000
164+++ doc/api/crypto.rst 2012-10-21 20:06:21 +0000
165@@ -141,6 +141,15 @@
166 pass phrase.
167
168
169+.. py:function:: dump_publickey(type, pkey)
170+
171+ Dump the publickey key *pkey* into a buffer string encoded with the type
172+ *type*.
173+
174+ *type* is one of (:py:const:`FILETYPE_PEM`, :py:const:`FILETYPE_ASN1`).
175+ *pkey* is a :py:class:`PKey` instance.
176+
177+
178 .. py:function:: load_certificate(type, buffer)
179
180 Load a certificate (X509) from the string *buffer* encoded with the

Subscribers

People subscribed via source and target branches

to status/vote changes: