Merge lp:~morgan-s-reed/pyopenssl/mr-RSAadditions into lp:~exarkun/pyopenssl/trunk

Proposed by Morgan
Status: Work in progress
Proposed branch: lp:~morgan-s-reed/pyopenssl/mr-RSAadditions
Merge into: lp:~exarkun/pyopenssl/trunk
Diff against target: 967 lines (+736/-18)
8 files modified
PKG-INFO (+3/-3)
doc/pyOpenSSL.tex (+60/-3)
setup.py (+2/-2)
src/crypto/crypto.c (+71/-6)
src/crypto/crypto.h (+10/-1)
src/crypto/rsa.c (+400/-0)
src/crypto/rsa.h (+47/-0)
test/test_crypto.py (+143/-3)
To merge this branch: bzr merge lp:~morgan-s-reed/pyopenssl/mr-RSAadditions
Reviewer Review Type Date Requested Status
Jean-Paul Calderone Needs Fixing
Review via email: mp+15119@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Morgan (morgan-s-reed) wrote :

This branch is now ready for merging, all changes from trunk have been back-merged in, all tests succeed.

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

The first thing that strikes me about this change is the apparent redundancy that it introduces into the key APIs exposed by pyOpenSSL. It seems like adding sign, verify, encrypt, and decrypt methods to the `PKey` type would be the right way to expose this functionality. As I understand it, the EVP_Sign*, EVP_Verify*, EVP_Encrypt*, and EVP_Decrypt* functions will do this with the existing data structure wrapped by the `PKey` type.

review: Needs Fixing
Revision history for this message
Morgan (morgan-s-reed) wrote :

On Mon, Nov 23, 2009 at 03:04, Jean-Paul Calderone
<email address hidden> wrote:
> Review: Needs Fixing
> The first thing that strikes me about this change is the apparent redundancy that it introduces into the key APIs exposed by pyOpenSSL.  It seems like adding sign, verify, encrypt, and decrypt methods to the `PKey` type would be the right way to expose this functionality.  As I understand it, the EVP_Sign*, EVP_Verify*, EVP_Encrypt*, and EVP_Decrypt* functions will do this with the existing data structure wrapped by the `PKey` type.

Reviewing the documentation for the EVP module, you are correct that
the RSA_sign and RSA_verify methods are redundant. However it is not
(from what I can tell) possible to do direct RSA encryption/decryption
with the EVP module (this is what I implemented the RSA bindings for,
I was working with a licensing scheme that used a license key
encrypted directly by RSA), the API only allows you to "envelope"
symmetrically encrypted data in an EVP context (generate symmetric
keys/IVs encrypt "enveloped" data with those, encrypt those with
RSA/DSA). EVP_Decrypt* and EVP_Encrypt* methods are (as far as I can
tell) only for symmetric encryption.

That said the person who requested the RSA/AES stuff on the list
probably needs the EVP methods rather than the direct low-level
methods.

I will have a look into implementing the relevant EVP methods on PKey
but I'm not sure if I will have time to complete the implementation.

Revision history for this message
Morgan (morgan-s-reed) wrote :

On Mon, Nov 23, 2009 at 08:45, Morgan Reed <email address hidden> wrote:
> I will have a look into implementing the relevant EVP methods on PKey
> but I'm not sure if I will have time to complete the implementation.

Apologies for the double message, Gmail was having kittens for some
unknown reason.

Have we heard anything from Edward Tait recently? I note he was
working on a "branch with-EVP_sign+verify", though I've no idea what
the status of it is.

Revision history for this message
Morgan (morgan-s-reed) wrote :

On Mon, Nov 23, 2009 at 08:50, Morgan Reed <email address hidden> wrote:
> Apologies for the double message, Gmail was having kittens for some
> unknown reason.

Ahh, I see now, scratch that, launchpad is sending me copies of my responses...

Unmerged revisions

91. By intrinsic <intrinsic@athena>

Fixed RSA test case

90. By intrinsic <intrinsic@athena>

Cleaned the garbage out of crypto.c

89. By intrinsic <intrinsic@athena>

Fixed breakage from the merging trunk

88. By intrinsic <intrinsic@athena>

Trunk Merge Completed

87. By intrinsic <intrinsic@athena>

Incremental merge commit 5

86. By intrinsic <intrinsic@athena>

Incremental merge commit 4

85. By intrinsic <intrinsic@athena>

Incremental merge commit 3

84. By intrinsic <intrinsic@athena>

Incremental merge commit 2

83. By intrinsic <intrinsic@athena>

Incremental merge commit 1

82. By intrinsic <intrinsic@athena>

Committing local changes prior to back-merging trunk

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'PKG-INFO'
2--- PKG-INFO 2008-02-19 01:50:23 +0000
3+++ PKG-INFO 2009-11-21 12:40:29 +0000
4@@ -1,10 +1,10 @@
5 Metadata-Version: 1.0
6 Name: pyOpenSSL
7-Version: 0.6
8+Version: 0.8a1
9 Summary: Python wrapper module around the OpenSSL library
10 Home-page: http://pyopenssl.sourceforge.net/
11-Author: Martin Sjögren, AB Strakt
12-Author-email: msjogren@gmail.com
13+Author: Jean-Paul Calderone
14+Author-email: exarkun@twistedmatrix.com
15 License: LGPL
16 Description: High-level wrapper around a subset of the OpenSSL library, includes
17 * SSL.Connection objects, wrapping the methods of Python's portable
18
19=== modified file 'doc/pyOpenSSL.tex'
20--- doc/pyOpenSSL.tex 2009-11-13 14:16:32 +0000
21+++ doc/pyOpenSSL.tex 2009-11-21 12:40:29 +0000
22@@ -207,6 +207,14 @@
23 method.
24 \end{classdesc}
25
26+\begin{datadesc}{RSAType}
27+A Python type object representing the RSA object type.
28+\end{datadesc}
29+
30+\begin{funcdesc}{RSA}{}
31+Factory function that creates a RSA object.
32+\end{funcdesc}
33+
34 \begin{datadesc}{FILETYPE_PEM}
35 \dataline{FILETYPE_ASN1}
36 File type constants.
37@@ -612,10 +620,61 @@
38 Verify the NetscapeSPKI object using the given \var{key}.
39 \end{methoddesc}
40
41+\subsubsection{RSA objects \label{openssl-rsa}}
42+
43+RSA objects have the following methods:
44+
45+\begin{methoddesc}[RSA]{generate_key}{bits}
46+Generate a new RSA keypair of the given size (\var{bits}) and store within the
47+object.
48+\end{methoddesc}
49+
50+\begin{methoddesc}[RSA]{key_bits}{}
51+Returns the RSA_size of the contained keys
52+\end{methoddesc}
53+
54+\begin{methoddesc}[RSA]{public_encrypt}{message\optional{, padding}}
55+Returns the given \var{message} encrypted with the public key stored in the
56+object with the given \var{padding}, (defaults to RSA_PKCS1_PADDING, other valid
57+options are; RSA_PKCS1_OAEP_PADDING, RSA_SSLV23_PADDING and RSA_NO_PADDING),
58+throws an error if the input data is larger than the key stored in the object.
59+\end{methoddesc}
60+
61+\begin{methoddesc}[RSA]{private_encrypt}{message\optional{, padding}}
62+Returns the given \var{message} encrypted with the private key stored in the
63+object with the given \var{padding}, (defaults to RSA_PKCS1_PADDING, the only
64+other valid option at this time is RSA_NO_PADDING (these are the only supported
65+paddings in OpenSSL 0.9.8g)), throws an error if the input data is larger than
66+the key stored in the object.
67+\end{methoddesc}
68+
69+\begin{methoddesc}[RSA]{public_decrypt}{message\optional{, padding}}
70+Returns the plaintext decrypted from the given \var{message} using the public
71+key stored in the object, throws an error if the type of the padding does not
72+match the padding used to encrypt the message.
73+\end{methoddesc}
74+
75+\begin{methoddesc}[RSA]{private_decrypt}{message\optional{, padding}}
76+Returns the plaintext decrypted from the given \var{message} using the private
77+key stored in the object.
78+\end{methoddesc}
79+
80+\begin{methoddesc}[RSA]{sign}{algorithm, digest}
81+Signs the message whose digest as generated by \var{algorithm} (valid options
82+are NID_sha1, NID_ripemd160, NID_md5 and NID_md5_sha1) is \var{digest} with the
83+contained private key.
84+\end{methoddesc}
85+
86+\begin{methoddesc}[RSA]{verify}{algorithm, digest, signature}
87+Verifies the \var{signature} for the message whose digest as generated by
88+\var{algorithm} (valid options are NID_sha1, NID_ripemd160, NID_md5 and
89+NID_md5_sha1) is \var{digest} using the contained public key.
90+\end{methoddesc}
91
92 % % % rand module
93
94-\subsection{\module{rand} --- An interface to the OpenSSL pseudo random number generator \label{openssl-rand}}
95+\subsection{\module{rand} --- An interface to the OpenSSL pseudo random number
96+generator \label{openssl-rand}}
97
98 \declaremodule{extension}{rand}
99 \modulesynopsis{An interface to the OpenSSL pseudo random number generator}
100@@ -1191,8 +1250,6 @@
101 operation.
102 \end{methoddesc}
103
104-
105-
106 \section{Internals \label{internals}}
107
108 We ran into three main problems developing this: Exceptions, callbacks and
109
110=== modified file 'setup.py'
111--- setup.py 2009-11-11 15:51:56 +0000
112+++ setup.py 2009-11-21 12:40:29 +0000
113@@ -26,13 +26,13 @@
114 'src/crypto/x509store.c', 'src/crypto/x509req.c',
115 'src/crypto/x509ext.c', 'src/crypto/pkcs7.c',
116 'src/crypto/pkcs12.c', 'src/crypto/netscape_spki.c',
117- 'src/util.c']
118+ 'src/crypto/rsa.c', 'src/util.c']
119 crypto_dep = ['src/crypto/crypto.h', 'src/crypto/x509.h',
120 'src/crypto/x509name.h', 'src/crypto/pkey.h',
121 'src/crypto/x509store.h', 'src/crypto/x509req.h',
122 'src/crypto/x509ext.h', 'src/crypto/pkcs7.h',
123 'src/crypto/pkcs12.h', 'src/crypto/netscape_spki.h',
124- 'src/util.h']
125+ 'src/crypto/rsa.h', 'src/util.h']
126 rand_src = ['src/rand/rand.c', 'src/util.c']
127 rand_dep = ['src/util.h']
128 ssl_src = ['src/ssl/connection.c', 'src/ssl/context.c', 'src/ssl/ssl.c',
129
130=== modified file 'src/crypto/crypto.c'
131--- src/crypto/crypto.c 2009-07-17 17:50:12 +0000
132+++ src/crypto/crypto.c 2009-11-21 12:40:29 +0000
133@@ -150,7 +150,7 @@
134 crypto_PKeyObj *pkey;
135
136 if (!PyArg_ParseTuple(args, "iO!|sO:dump_privatekey", &type,
137- &crypto_PKey_Type, &pkey, &cipher_name, &pw))
138+ &crypto_PKey_Type, &pkey, &cipher_name, &pw))
139 return NULL;
140
141 if (cipher_name != NULL && pw == NULL)
142@@ -290,7 +290,7 @@
143 crypto_X509Obj *cert;
144
145 if (!PyArg_ParseTuple(args, "iO!:dump_certificate", &type,
146- &crypto_X509_Type, &cert))
147+ &crypto_X509_Type, &cert))
148 return NULL;
149
150 bio = BIO_new(BIO_s_mem());
151@@ -393,7 +393,7 @@
152 crypto_X509ReqObj *req;
153
154 if (!PyArg_ParseTuple(args, "iO!:dump_certificate_request", &type,
155- &crypto_X509Req_Type, &req))
156+ &crypto_X509Req_Type, &req))
157 return NULL;
158
159 bio = BIO_new(BIO_s_mem());
160@@ -514,6 +514,54 @@
161 return (PyObject *)crypto_PKCS12_New(p12, passphrase);
162 }
163
164+static char crypto_load_rsa_doc[] = "\n\
165+Load an RSA object from a buffer\n\
166+\n\
167+@param spam: Always NULL\n\
168+ args - The Python argument tuple, should be empty.\n\
169+@returns: The RSA object\n\
170+";
171+
172+static PyObject *
173+crypto_load_rsa(PyObject *spam, PyObject *args)
174+{
175+ crypto_RSAObj *crypto_RSA_New(RSA *, int);
176+ RSA *rsa;
177+
178+ if (!PyArg_ParseTuple(args, "s#|s:load_rsa"))
179+ return NULL;
180+
181+ if ((rsa = RSA_new()) == NULL) {
182+ exception_from_error_queue(crypto_Error);
183+ return NULL;
184+ }
185+
186+ return (PyObject *)crypto_RSA_New(rsa, 1);
187+}
188+
189+static char crypto_rsa_doc[] = "\n\
190+The factory function inserted in the module dictionary to create RSA\n\
191+objects\n\
192+\n\
193+Arguments: spam - Always NULL\n\
194+ args - The Python argument tuple, should be empty\n\
195+Returns: The RSA object\n\
196+";
197+
198+static PyObject *
199+crypto_rsa(PyObject *spam, PyObject *args)
200+{
201+ crypto_RSAObj *py_rsa;
202+
203+ if (!PyArg_ParseTuple(args, ":RSA"))
204+ return NULL;
205+
206+ py_rsa = crypto_RSA_New(RSA_new(), 1);
207+ if (py_rsa) {
208+ py_rsa->initialized = 0;
209+ }
210+ return (PyObject *)py_rsa;
211+}
212
213 static char crypto_X509_verify_cert_error_string_doc[] = "\n\
214 Get X509 verify certificate error string.\n\
215@@ -557,6 +605,9 @@
216 { "dump_certificate_request", (PyCFunction)crypto_dump_certificate_request, METH_VARARGS, crypto_dump_certificate_request_doc },
217 { "load_pkcs7_data", (PyCFunction)crypto_load_pkcs7_data, METH_VARARGS, crypto_load_pkcs7_data_doc },
218 { "load_pkcs12", (PyCFunction)crypto_load_pkcs12, METH_VARARGS, crypto_load_pkcs12_doc },
219+ { "load_rsa", (PyCFunction)crypto_load_rsa, METH_VARARGS, crypto_load_rsa_doc },
220+ /* Factory functions */
221+ { "RSA", (PyCFunction)crypto_rsa, METH_VARARGS, crypto_rsa_doc },
222 { "X509_verify_cert_error_string", (PyCFunction)crypto_X509_verify_cert_error_string, METH_VARARGS, crypto_X509_verify_cert_error_string_doc },
223 { "_exception_from_error_queue", (PyCFunction)crypto_exception_from_error_queue, METH_NOARGS, crypto_exception_from_error_queue_doc },
224 { NULL, NULL }
225@@ -654,9 +705,10 @@
226 crypto_API[crypto_X509Req_New_NUM] = (void *)crypto_X509Req_New;
227 crypto_API[crypto_X509Store_New_NUM] = (void *)crypto_X509Store_New;
228 crypto_API[crypto_PKey_New_NUM] = (void *)crypto_PKey_New;
229- crypto_API[crypto_X509Extension_New_NUM] = (void *)crypto_X509Extension_New;
230- crypto_API[crypto_PKCS7_New_NUM] = (void *)crypto_PKCS7_New;
231- crypto_API[crypto_NetscapeSPKI_New_NUM] = (void *)crypto_NetscapeSPKI_New;
232+ crypto_API[crypto_X509Extension_New_NUM]= (void *)crypto_X509Extension_New;
233+ crypto_API[crypto_PKCS7_New_NUM] = (void *)crypto_PKCS7_New;
234+ crypto_API[crypto_NetscapeSPKI_New_NUM] = (void *)crypto_NetscapeSPKI_New;
235+ crypto_API[crypto_RSA_New_NUM] = (void *)crypto_RSA_New;
236 c_api_object = PyCObject_FromVoidPtr((void *)crypto_API, NULL);
237 if (c_api_object != NULL)
238 PyModule_AddObject(module, "_C_API", c_api_object);
239@@ -674,6 +726,17 @@
240 PyModule_AddIntConstant(module, "TYPE_RSA", crypto_TYPE_RSA);
241 PyModule_AddIntConstant(module, "TYPE_DSA", crypto_TYPE_DSA);
242
243+ PyModule_AddIntConstant(module, "RSA_PKCS1_PADDING", crypto_RSA_PKCS1_PADDING);
244+ PyModule_AddIntConstant(module, "RSA_PKCS1_OAEP_PADDING", crypto_RSA_PKCS1_OAEP_PADDING);
245+ PyModule_AddIntConstant(module, "RSA_SSLV23_PADDING", crypto_RSA_SSLV23_PADDING);
246+ PyModule_AddIntConstant(module, "RSA_NO_PADDING", crypto_RSA_NO_PADDING);
247+
248+ PyModule_AddIntConstant(module, "NID_sha1", crypto_NID_sha1);
249+ PyModule_AddIntConstant(module, "NID_ripemd160", crypto_NID_ripemd160);
250+ PyModule_AddIntConstant(module, "NID_md5", crypto_NID_md5);
251+ PyModule_AddIntConstant(module, "NID_md5_sha1", crypto_NID_md5_sha1);
252+
253+
254 #ifdef WITH_THREAD
255 if (!init_openssl_threads())
256 goto error;
257@@ -696,6 +759,8 @@
258 goto error;
259 if (!init_crypto_netscape_spki(module))
260 goto error;
261+ if (!init_crypto_rsa(module))
262+ goto error;
263
264 error:
265 ;
266
267=== modified file 'src/crypto/crypto.h'
268--- src/crypto/crypto.h 2009-07-17 20:06:12 +0000
269+++ src/crypto/crypto.h 2009-11-21 12:40:29 +0000
270@@ -2,6 +2,7 @@
271 * crypto.h
272 *
273 * Copyright (C) AB Strakt 2001, All rights reserved
274+ * Copyright (C) Morgan Reed 2008, All rights reserved
275 *
276 * Exports from crypto.c.
277 * See the file RATIONALE for a short explanation of why this module was written.
278@@ -23,6 +24,7 @@
279 #include "x509ext.h"
280 #include "pkcs7.h"
281 #include "pkcs12.h"
282+#include "rsa.h"
283 #include "../util.h"
284
285 extern PyObject *crypto_Error;
286@@ -59,7 +61,11 @@
287 #define crypto_NetscapeSPKI_New_RETURN crypto_NetscapeSPKIObj *
288 #define crypto_NetscapeSPKI_New_PROTO (NETSCAPE_SPKI *, int)
289
290-#define crypto_API_pointers 8
291+#define crypto_RSA_New_NUM 8
292+#define crypto_RSA_New_RETURN crypto_RSAObj *
293+#define crypto_RSA_New_PROTO (RSA *, int)
294+
295+#define crypto_API_pointers 9
296
297 #ifdef crypto_MODULE
298
299@@ -71,6 +77,7 @@
300 extern crypto_X509Extension_New_RETURN crypto_X509Extension_New crypto_X509Extension_New_PROTO;
301 extern crypto_PKCS7_New_RETURN crypto_PKCS7_New crypto_PKCS7_New_PROTO;
302 extern crypto_NetscapeSPKI_New_RETURN crypto_NetscapeSPKI_New crypto_NetscapeSPKI_New_PROTO;
303+extern crypto_RSA_New_RETURN crypto_RSA_New crypto_RSA_New_PROTO;
304
305 #else /* crypto_MODULE */
306
307@@ -92,6 +99,8 @@
308 (*(crypto_PKCS7_New_RETURN (*)crypto_PKCS7_New_PROTO) crypto_API[crypto_PKCS7_New_NUM])
309 #define crypto_NetscapeSPKI_New \
310 (*(crypto_NetscapeSPKI_New_RETURN (*)crypto_NetscapeSPKI_New_PROTO) crypto_API[crypto_NetscapeSPKI_New_NUM])
311+#define crypto_RSA_New \
312+ (*(crypto_RSA_New_RETURN (*)crypto_RSA_New_PROTO) crypto_API[crypto_RSA_New_NUM])
313
314 #define import_crypto() \
315 { \
316
317=== added file 'src/crypto/rsa.c'
318--- src/crypto/rsa.c 1970-01-01 00:00:00 +0000
319+++ src/crypto/rsa.c 2009-11-21 12:40:29 +0000
320@@ -0,0 +1,400 @@
321+/*
322+ * rsa.c
323+ *
324+ * Copyright (C) Morgan Reed 2008, All rights reserved
325+ *
326+ * RSA encryption/decryption routines.
327+ *
328+ */
329+#include <Python.h>
330+#define crypto_MODULE
331+#include "crypto.h"
332+
333+#define DEBUG
334+
335+#define DEFAULT_PADDING RSA_PKCS1_PADDING
336+
337+/*
338+ * This is done every time something fails, so turning it into a macro is
339+ * really nice.
340+ *
341+ * Arguments: None
342+ * Returns: Doesn't return
343+ */
344+#define FAIL() \
345+do { \
346+ exception_from_error_queue(crypto_Error); \
347+ return NULL; \
348+} while (0)
349+
350+static char crypto_RSA_sign_doc[] = "\n\
351+Returns the passed message digest signed with the contained private key\n\
352+\n\
353+Arguments: self - The RSA object\n\
354+ args - The python argument tuple, should be:\n\
355+ algo - The algorithm used to generate the digest\n\
356+ digest - The message digest\n\
357+Returns: String containing the signature\n\
358+";
359+
360+static PyObject *
361+crypto_RSA_sign(crypto_RSAObj *self, PyObject *args)
362+{
363+ int bufsz = RSA_size(self->rsa);
364+ unsigned int algo, digestlen, siglen;
365+ unsigned char *digest;
366+ unsigned char *sig = (char *)malloc(bufsz);
367+
368+
369+ if(!PyArg_ParseTuple(args, "is#:sign", &algo, &digest, &digestlen))
370+ return NULL;
371+
372+ if(!RSA_sign(algo, digest, digestlen, sig, &siglen, self->rsa)) {
373+ ERR_print_errors_fp(stdout);
374+ FAIL();
375+ }
376+
377+ return PyString_FromStringAndSize(sig, siglen);
378+}
379+
380+static char crypto_RSA_verify_doc[] = "\n\
381+Tests the given signature against the contained public key\n\
382+\n\
383+Arguments: self - The RSA object\n\
384+ args - The python argument tuple, should be:\n\
385+ algo - The algorithm used to generate the digest\n\
386+ digest - The message digest\n\
387+ sig - The signature\n\
388+Returns: Boolean result of comparison\n\
389+";
390+
391+static PyObject *
392+crypto_RSA_verify(crypto_RSAObj *self, PyObject *args)
393+{
394+ unsigned long ret;
395+ unsigned int algo, digestlen, siglen;
396+ unsigned char *digest;
397+ unsigned char *sig;
398+
399+ if(!PyArg_ParseTuple(args, "is#s#:verify", &algo, &digest, &digestlen,
400+ &sig, &siglen))
401+ return NULL;
402+
403+ if(!(ret =RSA_verify(algo, digest, digestlen, sig, siglen, self->rsa)))
404+ ERR_print_errors_fp(stdout);
405+
406+ return PyLong_FromUnsignedLong(ret);
407+
408+}
409+
410+static char crypto_RSA_key_bits_doc[] = "\n\
411+Returns the length of the contained RSA key\n\
412+\n\
413+Arguments: self - The RSA object\n\
414+ args - The python argument tuple, should be empty\n\
415+Returns: Integer length of key\n\
416+";
417+
418+static PyObject *
419+crypto_RSA_key_bits(crypto_RSAObj *self, PyObject *args)
420+{
421+ unsigned long bits = (8 * RSA_size(self->rsa));
422+ return PyLong_FromUnsignedLong(bits);
423+}
424+
425+static char crypto_RSA_generate_key_doc[] = "\n\
426+Generates a new RSA keypair\n\
427+\n\
428+Arguments: self - The RSA object\n\
429+ args - The Python argument tuple, should be:\n\
430+ bits - Key bits\n\
431+Returns: None\n\
432+";
433+
434+static PyObject *
435+crypto_RSA_generate_key(crypto_RSAObj *self, PyObject *args)
436+{
437+ int bits;
438+ RSA *rsa;
439+
440+ if (!PyArg_ParseTuple(args, "i:generate_key", &bits))
441+ return NULL;
442+
443+ if (bits <= 0) {
444+ PyErr_SetString(PyExc_ValueError, "Invalid number of bits");
445+ return NULL;
446+ }
447+ if ((rsa = RSA_generate_key(bits, 0x10001, NULL, NULL)) == NULL)
448+ FAIL();
449+
450+ self->rsa = rsa;
451+
452+ self->initialized = 1;
453+ Py_INCREF(Py_None);
454+ return Py_None;
455+}
456+
457+
458+static char crypto_RSA_public_encrypt_doc[] = "\n\
459+Encrypts the input buffer with the associated public key\n\
460+\n\
461+Arguments: self - The RSA object\n\
462+ args - The Python argument tuple, should be:\n\
463+ ibuf - Cleartext input\n\
464+Returns: String containing encrypted data\n\
465+";
466+
467+static PyObject *
468+crypto_RSA_public_encrypt(crypto_RSAObj *self, PyObject *args)
469+{
470+ int bufsz = RSA_size(self->rsa);
471+ int sz, rsz;
472+ int padding = DEFAULT_PADDING;
473+ unsigned char *buf = (char*)malloc(bufsz);
474+ unsigned char *ibuf;
475+
476+ if (!self->rsa) {
477+ FAIL();
478+ }
479+
480+ if (!PyArg_ParseTuple(args, "s#|i:public_encrypt", &ibuf, &sz, &padding)) {
481+ FAIL();
482+ }
483+
484+ rsz = RSA_public_encrypt(sz, ibuf, buf, self->rsa, padding);
485+ if (rsz == -1) {
486+ ERR_print_errors_fp(stdout);
487+ FAIL();
488+ }
489+
490+ return PyString_FromStringAndSize(buf, rsz);
491+}
492+
493+static char crypto_RSA_public_decrypt_doc[] = "\n\
494+Decrypts the input buffer with the associated public key\n\
495+\n\
496+Arguments: self - The RSA object\n\
497+ args - The Python argument tuple, should be:\n\
498+ ibuf - Cyphertext input\n\
499+Returns: String containing encrypted data\n\
500+";
501+
502+static PyObject *
503+crypto_RSA_public_decrypt(crypto_RSAObj *self, PyObject *args)
504+{
505+ int bufsz = RSA_size(self->rsa);
506+ int sz, rsz;
507+ int padding = DEFAULT_PADDING;
508+ unsigned char *buf = (char*)malloc(bufsz);
509+ unsigned char *ibuf;
510+
511+ if (!self->rsa) {
512+ FAIL();
513+ }
514+
515+ if (!PyArg_ParseTuple(args, "s#|i:public_decrypt", &ibuf, &sz, &padding)) {
516+ FAIL();
517+ }
518+
519+ rsz = RSA_public_decrypt(sz, ibuf, buf, self->rsa, padding);
520+ if (rsz == -1) {
521+ ERR_print_errors_fp(stdout);
522+ FAIL();
523+ }
524+
525+ return PyString_FromStringAndSize(buf, rsz);
526+}
527+
528+static char crypto_RSA_private_encrypt_doc[] = "\n\
529+Encrypts the input buffer with the associated private key\n\
530+\n\
531+Arguments: self - The RSA object\n\
532+ args - The Python argument tuple, should be:\n\
533+ ibuf - Cleartext input\n\
534+Returns: String containing encrypted data\n\
535+";
536+
537+static PyObject *
538+crypto_RSA_private_encrypt(crypto_RSAObj *self, PyObject *args)
539+{
540+ int bufsz = RSA_size(self->rsa);
541+ int sz, rsz;
542+ int padding = DEFAULT_PADDING;
543+ unsigned char *buf = (char*)malloc(bufsz);
544+ unsigned char *ibuf;
545+
546+ if (!self->rsa) {
547+ FAIL();
548+ }
549+
550+ if (!PyArg_ParseTuple(args, "s#|i:private_decrypt", &ibuf, &sz, &padding)) {
551+ FAIL();
552+ }
553+
554+ rsz = RSA_private_encrypt(sz, ibuf, buf, self->rsa, padding);
555+ if (rsz == -1) {
556+ ERR_print_errors_fp(stdout);
557+ FAIL();
558+ }
559+
560+ return PyString_FromStringAndSize(buf, rsz);
561+}
562+
563+
564+static char crypto_RSA_private_decrypt_doc[] = "\n\
565+Decrypts the input buffer with the associated private key\n\
566+\n\
567+Arguments: self - The RSA object\n\
568+ args - The Python argument tuple, should be:\n\
569+ ibuf - Cyphertext input\n\
570+Returns: String containing encrypted data\n\
571+";
572+
573+static PyObject *
574+crypto_RSA_private_decrypt(crypto_RSAObj *self, PyObject *args)
575+{
576+ int bufsz = RSA_size(self->rsa);
577+ int sz, rsz;
578+ int padding = DEFAULT_PADDING;
579+ unsigned char *buf = (char*)malloc(bufsz);
580+ unsigned char *ibuf;
581+
582+ if (!self->rsa) {
583+ FAIL();
584+ }
585+
586+ if (!PyArg_ParseTuple(args, "s#|i:private_decrypt", &ibuf, &sz, &padding)){
587+ FAIL();
588+ }
589+
590+ rsz = RSA_private_decrypt(sz, ibuf, buf, self->rsa, padding);
591+ if (rsz == -1) {
592+ ERR_print_errors_fp(stdout);
593+ FAIL();
594+ }
595+
596+ return PyString_FromStringAndSize(buf, rsz);
597+}
598+
599+/*
600+ * ADD_METHOD(name) expands to a correct PyMethodDef declaration
601+ * { 'name', (PyCFunction)crypto_RSA_name, METH_VARARGS }
602+ * for convenience
603+ */
604+#define ADD_METHOD(name) \
605+ { #name, (PyCFunction)crypto_RSA_##name, METH_VARARGS, crypto_RSA_##name##_doc }
606+static PyMethodDef crypto_RSA_methods[] =
607+{
608+ ADD_METHOD(sign),
609+ ADD_METHOD(verify),
610+ ADD_METHOD(key_bits),
611+ ADD_METHOD(generate_key),
612+ ADD_METHOD(public_encrypt),
613+ ADD_METHOD(public_decrypt),
614+ ADD_METHOD(private_encrypt),
615+ ADD_METHOD(private_decrypt),
616+ { NULL, NULL }
617+};
618+#undef ADD_METHOD
619+
620+
621+/*
622+ * Constructor for RSA objects, never called by Python code directly
623+ *
624+ * Arguments: rsa - A "real" RSA object
625+ * dealloc - Boolean value to specify whether the destructor should
626+ * free the "real" RSA object
627+ * Returns: The newly created RSA object
628+ */
629+crypto_RSAObj *
630+crypto_RSA_New(RSA *rsa, int dealloc)
631+{
632+ crypto_RSAObj *self;
633+
634+ self = PyObject_New(crypto_RSAObj, &crypto_RSA_Type);
635+
636+ if (self == NULL)
637+ return NULL;
638+
639+ self->rsa = rsa;
640+ self->dealloc = dealloc;
641+
642+ /*
643+ * Heuristic. Most call-sites pass an initialized EVP_PKEY. Not
644+ * necessarily the case that they will, though. That's part of why this is
645+ * a hack. -exarkun
646+ */
647+ self->initialized = 1;
648+
649+ return self;
650+}
651+
652+/*
653+ * Deallocate the memory used by the RSA object
654+ *
655+ * Arguments: self - The RSA object
656+ * Returns: None
657+ */
658+static void
659+crypto_RSA_dealloc(crypto_RSAObj *self)
660+{
661+ /* Sometimes we don't have to dealloc the "real" RSA pointer ourselves */
662+ if (self->dealloc)
663+ RSA_free(self->rsa);
664+
665+ PyObject_Del(self);
666+}
667+
668+/*
669+ * Find attribute
670+ *
671+ * Arguments: self - The RSA object
672+ * name - The attribute name
673+ * Returns: A Python object for the attribute, or NULL if something went
674+ * wrong
675+ */
676+static PyObject *
677+crypto_RSA_getattr(crypto_RSAObj *self, char *name)
678+{
679+ return Py_FindMethod(crypto_RSA_methods, (PyObject *)self, name);
680+}
681+
682+PyTypeObject crypto_RSA_Type = {
683+ PyObject_HEAD_INIT(NULL)
684+ 0,
685+ "RSA",
686+ sizeof(crypto_RSAObj),
687+ 0,
688+ (destructor)crypto_RSA_dealloc,
689+ NULL, /* print */
690+ (getattrfunc)crypto_RSA_getattr,
691+ NULL, /* setattr */
692+ NULL, /* compare */
693+ NULL, /* repr */
694+ NULL, /* as_number */
695+ NULL, /* as_sequence */
696+ NULL, /* as_mapping */
697+ NULL, /* hash */
698+};
699+
700+
701+/*
702+ * Initialize the RSA part of the crypto sub module
703+ *
704+ * Arguments: dict - The crypto module dictionary
705+ * Returns: None
706+ */
707+int
708+init_crypto_rsa(PyObject *module)
709+{
710+ if (PyType_Ready(&crypto_RSA_Type) < 0) {
711+ return 0;
712+ }
713+
714+ if(PyModule_AddObject(module, "RSAType", (PyObject *)&crypto_RSA_Type) != 0) {
715+ return 0;
716+ }
717+
718+ return 1;
719+}
720+
721
722=== added file 'src/crypto/rsa.h'
723--- src/crypto/rsa.h 1970-01-01 00:00:00 +0000
724+++ src/crypto/rsa.h 2009-11-21 12:40:29 +0000
725@@ -0,0 +1,47 @@
726+/*
727+ * rsa.h
728+ *
729+ * Copyright (C) Morgan Reed 2008, All rights reserved
730+ *
731+ * Interface to RSA encryption/decryption routines
732+ *
733+ */
734+#ifndef PyOpenSSL_crypto_RSA_H_
735+#define PyOpenSSL_crypto_RSA_H_
736+
737+#include <Python.h>
738+#include <openssl/rsa.h>
739+#include <openssl/objects.h>
740+
741+extern int init_crypto_rsa (PyObject *);
742+
743+extern PyTypeObject crypto_RSA_Type;
744+
745+#define crypto_RSA_Check(v) ((v)->ob_type == &crypto_RSA_Type)
746+
747+typedef struct {
748+ PyObject_HEAD
749+
750+ /*
751+ * A pointer to the underlying OpenSSL structure.
752+ */
753+ RSA *rsa;
754+
755+ int initialized;
756+ /*
757+ * A flag indicating whether pkey will be freed when this object is freed.
758+ */
759+ int dealloc;
760+} crypto_RSAObj;
761+
762+#define crypto_RSA_PKCS1_PADDING RSA_PKCS1_PADDING
763+#define crypto_RSA_PKCS1_OAEP_PADDING RSA_PKCS1_OAEP_PADDING
764+#define crypto_RSA_SSLV23_PADDING RSA_SSLV23_PADDING
765+#define crypto_RSA_NO_PADDING RSA_NO_PADDING
766+
767+#define crypto_NID_sha1 NID_sha1
768+#define crypto_NID_ripemd160 NID_ripemd160
769+#define crypto_NID_md5 NID_md5
770+#define crypto_NID_md5_sha1 NID_md5_sha1
771+
772+#endif
773
774=== modified file 'test/test_crypto.py'
775--- test/test_crypto.py 2009-09-01 14:35:50 +0000
776+++ test/test_crypto.py 2009-11-21 12:40:29 +0000
777@@ -10,10 +10,15 @@
778 from os import popen2
779 from datetime import datetime, timedelta
780
781+from hashlib import sha1
782+
783 from OpenSSL.crypto import TYPE_RSA, TYPE_DSA, Error, PKey, PKeyType
784 from OpenSSL.crypto import X509, X509Type, X509Name, X509NameType
785 from OpenSSL.crypto import X509Req, X509ReqType
786 from OpenSSL.crypto import X509Extension, X509ExtensionType
787+from OpenSSL.crypto import RSA, RSAType, NID_sha1, NID_md5
788+from OpenSSL.crypto import RSA_PKCS1_OAEP_PADDING, RSA_PKCS1_PADDING
789+from OpenSSL.crypto import RSA_NO_PADDING, RSA_SSLV23_PADDING
790 from OpenSSL.crypto import load_certificate, load_privatekey
791 from OpenSSL.crypto import FILETYPE_PEM, FILETYPE_ASN1, FILETYPE_TEXT
792 from OpenSSL.crypto import dump_certificate, load_certificate_request
793@@ -197,6 +202,8 @@
794 -----END RSA PRIVATE KEY-----
795 """
796 encryptedPrivateKeyPEMPassphrase = "foobar"
797+# testPlaintext MUST be 128-bits long for the RSA no_padding tests
798+testPlaintext = "testing testing "
799
800 # Some PKCS#7 stuff. Generated with the openssl command line:
801 #
802@@ -735,7 +742,8 @@
803 """
804 def signable(self):
805 """
806- Return something with a C{set_pubkey}, C{set_pubkey}, and C{sign} method.
807+ Return something with a C{set_pubkey}, C{set_pubkey}, and C{sign}
808+ method.
809 """
810 raise NotImplementedError()
811
812@@ -790,7 +798,9 @@
813 request = X509Req()
814 self.assertTrue(
815 isinstance(request, X509ReqType),
816- "%r is of type %r, should be %r" % (request, type(request), X509ReqType))
817+ "%r is of type %r, should be %r" % (request,
818+ type(request),
819+ X509ReqType))
820
821
822 def test_version(self):
823@@ -817,7 +827,9 @@
824 subject = request.get_subject()
825 self.assertTrue(
826 isinstance(subject, X509NameType),
827- "%r is of type %r, should be %r" % (subject, type(subject), X509NameType))
828+ "%r is of type %r, should be %r" % (subject,
829+ type(subject),
830+ X509NameType))
831 subject.commonName = "foo"
832 self.assertEqual(request.get_subject().commonName, "foo")
833 del request
834@@ -1533,5 +1545,133 @@
835
836
837
838+class RSATests(TestCase):
839+
840+ def test_construction(self):
841+ """
842+ L{RSA} takes no arguments and returns an instance of L{RSAType}.
843+ """
844+ rsaobj = RSA()
845+ self.assertTrue(
846+ isinstance(rsaobj, RSAType),
847+ "%r is of type %r, should be %r" % (rsaobj,
848+ type(rsaobj),
849+ RSAType))
850+
851+ def test_generate_key(self):
852+ """
853+ L{generate_key} generates a new RSA key the given size and stores it in
854+ the internal structure
855+ """
856+ bits = 1024
857+ rsaobj = RSA()
858+ rsaobj.generate_key(bits)
859+ self.assertEqual(rsaobj.key_bits(), bits)
860+
861+ def test_regenerate_key(self):
862+ """
863+ L{generate_key} should generates a new key of the given size everytime
864+ it is called
865+ """
866+ bits = 512
867+ rsaobj = RSA()
868+ rsaobj.generate_key(bits)
869+ self.assertEqual(rsaobj.key_bits(), bits)
870+ rsaobj.generate_key(bits * 2)
871+ self.assertEqual(rsaobj.key_bits(), bits * 2)
872+
873+ def test_public_encrypt(self):
874+ """
875+ L{public_encrypt} generates a new key and encrypts a static value with
876+ the public key, then decrypts it with the private and compare the
877+ result
878+ """
879+ bits = 512
880+ rsaobj = RSA()
881+ rsaobj.generate_key(bits)
882+ crypt = rsaobj.public_encrypt(testPlaintext)
883+ decrypt = rsaobj.private_decrypt(crypt)
884+ self.assertEqual(decrypt, testPlaintext)
885+
886+ def test_private_encrypt(self):
887+ """
888+ L{private_encrypt} generates a new key and encrypts a static value with
889+ the private key, then decrypts it with the public and compares the
890+ result
891+ """
892+ bits = 512
893+ rsaobj = RSA()
894+ rsaobj.generate_key(bits)
895+ crypt = rsaobj.private_encrypt(testPlaintext)
896+ decrypt = rsaobj.public_decrypt(crypt)
897+ self.assertEqual(decrypt, testPlaintext)
898+
899+ def test_public_encrypt_oaep_padding(self):
900+ """
901+ L{public_encrypt} generates a new key and encrypts a static value with
902+ the public key, then decrypts it with the private and compare the
903+ result
904+ """
905+ bits = 512
906+ rsaobj = RSA()
907+ rsaobj.generate_key(bits)
908+ crypt = rsaobj.public_encrypt(testPlaintext, RSA_PKCS1_OAEP_PADDING)
909+ decrypt = rsaobj.private_decrypt(crypt, RSA_PKCS1_OAEP_PADDING)
910+ self.assertEqual(decrypt, testPlaintext)
911+
912+ def test_public_encrypt_no_padding(self):
913+ """
914+ L{private_encrypt} generates a new key and encrypts a static value with
915+ the private key, then decrypts it with the public and compares the
916+ result
917+ """
918+ bits = 128
919+ rsaobj = RSA()
920+ rsaobj.generate_key(bits)
921+ crypt = rsaobj.public_encrypt(testPlaintext, RSA_NO_PADDING)
922+ decrypt = rsaobj.private_decrypt(crypt, RSA_NO_PADDING)
923+ self.assertEqual(decrypt, testPlaintext)
924+
925+ def test_private_encrypt_no_padding(self):
926+ """
927+ L{private_encrypt} generates a new key and encrypts a static value with
928+ the private key, then decrypts it with the public and compares the
929+ result
930+ """
931+ bits = 128
932+ rsaobj = RSA()
933+ rsaobj.generate_key(bits)
934+ crypt = rsaobj.private_encrypt(testPlaintext, RSA_NO_PADDING)
935+ decrypt = rsaobj.public_decrypt(crypt, RSA_NO_PADDING)
936+ self.assertEqual(decrypt, testPlaintext)
937+
938+ def test_sign(self):
939+ """
940+ L{sign} generates a new key, signs a static value with it then verifies
941+ the generated signature
942+ """
943+ bits = 512
944+ rsaobj = RSA()
945+ rsaobj.generate_key(bits)
946+ digest = sha1()
947+ digest.update(testPlaintext)
948+ sig = rsaobj.sign(NID_sha1, digest.digest())
949+ self.assertTrue(rsaobj.verify(NID_sha1, digest.digest(), sig))
950+
951+ def test_verify(self):
952+ """
953+ L{verify} generates a new key, signs a static value with it then
954+ mangles the message and verifies, verify should fail
955+ """
956+ bits = 512
957+ rsaobj = RSA()
958+ rsaobj.generate_key(bits)
959+ digest = sha1()
960+ digest.update(testPlaintext)
961+ sig = rsaobj.sign(NID_sha1, digest.digest())
962+ digest.update(testPlaintext[::-1])
963+ self.assertFalse(rsaobj.verify(NID_sha1, digest.digest(), sig))
964+
965+
966 if __name__ == '__main__':
967 main()

Subscribers

People subscribed via source and target branches

to status/vote changes: