Merge lp:~morgan-s-reed/pyopenssl/mr-RSAadditions into lp:~exarkun/pyopenssl/trunk
- mr-RSAadditions
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jean-Paul Calderone | Needs Fixing | ||
Review via email: mp+15119@code.launchpad.net |
Commit message
Description of the change
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.
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/
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.
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_
the status of it is.
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
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() |
This branch is now ready for merging, all changes from trunk have been back-merged in, all tests succeed.