Commit | Line | Data |
---|---|---|
420a0d19 | 1 | /* |
2813c06e | 2 | * PDKIM - a RFC4871 (DKIM) implementation |
420a0d19 | 3 | * |
2813c06e | 4 | * Copyright (C) 2016 Exim maintainers |
420a0d19 | 5 | * |
2813c06e | 6 | * RSA signing/verification interface |
420a0d19 CE |
7 | */ |
8 | ||
2813c06e CE |
9 | #include "../exim.h" |
10 | ||
11 | #ifndef DISABLE_DKIM /* entire file */ | |
12 | ||
13 | #ifndef SUPPORT_TLS | |
14 | # error Need SUPPORT_TLS for DKIM | |
15 | #endif | |
16 | ||
17 | #include "crypt_ver.h" | |
420a0d19 | 18 | #include "rsa.h" |
420a0d19 | 19 | |
420a0d19 | 20 | |
2813c06e CE |
21 | /******************************************************************************/ |
22 | #ifdef RSA_GNUTLS | |
23 | ||
24 | void | |
25 | exim_rsa_init(void) | |
420a0d19 | 26 | { |
420a0d19 CE |
27 | } |
28 | ||
2813c06e CE |
29 | |
30 | /* accumulate data (gnutls-only). String to be appended must be nul-terminated. */ | |
31 | blob * | |
32 | exim_rsa_data_append(blob * b, int * alloc, uschar * s) | |
420a0d19 | 33 | { |
2813c06e CE |
34 | int len = b->len; |
35 | b->data = string_append(b->data, alloc, &len, 1, s); | |
36 | b->len = len; | |
37 | return b; | |
38 | } | |
420a0d19 | 39 | |
420a0d19 | 40 | |
420a0d19 | 41 | |
2813c06e CE |
42 | /* import private key from PEM string in memory. |
43 | Return: NULL for success, or an error string */ | |
420a0d19 | 44 | |
2813c06e CE |
45 | const uschar * |
46 | exim_rsa_signing_init(uschar * privkey_pem, es_ctx * sign_ctx) | |
420a0d19 | 47 | { |
2813c06e CE |
48 | gnutls_datum_t k; |
49 | int rc; | |
420a0d19 | 50 | |
2813c06e CE |
51 | k.data = privkey_pem; |
52 | k.size = strlen(privkey_pem); | |
420a0d19 | 53 | |
2813c06e CE |
54 | if ( (rc = gnutls_x509_privkey_init(&sign_ctx->rsa)) != GNUTLS_E_SUCCESS |
55 | /*|| (rc = gnutls_x509_privkey_import(sign_ctx->rsa, &k, | |
56 | GNUTLS_X509_FMT_PEM)) != GNUTLS_E_SUCCESS */ | |
57 | ) | |
58 | return gnutls_strerror(rc); | |
420a0d19 | 59 | |
2813c06e CE |
60 | if ( /* (rc = gnutls_x509_privkey_init(&sign_ctx->rsa)) != GNUTLS_E_SUCCESS |
61 | ||*/ (rc = gnutls_x509_privkey_import(sign_ctx->rsa, &k, | |
62 | GNUTLS_X509_FMT_PEM)) != GNUTLS_E_SUCCESS | |
63 | ) | |
64 | return gnutls_strerror(rc); | |
420a0d19 | 65 | |
2813c06e | 66 | return NULL; |
420a0d19 CE |
67 | } |
68 | ||
420a0d19 | 69 | |
420a0d19 | 70 | |
2813c06e CE |
71 | /* allocate mem for signature (when signing) */ |
72 | /* sign data (gnutls_only) | |
73 | OR | |
74 | sign hash. | |
420a0d19 | 75 | |
2813c06e | 76 | Return: NULL for success, or an error string */ |
420a0d19 | 77 | |
2813c06e CE |
78 | const uschar * |
79 | exim_rsa_sign(es_ctx * sign_ctx, BOOL is_sha1, blob * data, blob * sig) | |
80 | { | |
81 | gnutls_datum_t k; | |
82 | size_t sigsize = 0; | |
83 | int rc; | |
84 | const uschar * ret = NULL; | |
85 | ||
86 | /* Allocate mem for signature */ | |
87 | k.data = data->data; | |
88 | k.size = data->len; | |
89 | (void) gnutls_x509_privkey_sign_data(sign_ctx->rsa, | |
90 | is_sha1 ? GNUTLS_DIG_SHA1 : GNUTLS_DIG_SHA256, | |
91 | 0, &k, NULL, &sigsize); | |
92 | ||
93 | sig->data = store_get(sigsize); | |
94 | sig->len = sigsize; | |
95 | ||
96 | /* Do signing */ | |
97 | if ((rc = gnutls_x509_privkey_sign_data(sign_ctx->rsa, | |
98 | is_sha1 ? GNUTLS_DIG_SHA1 : GNUTLS_DIG_SHA256, | |
99 | 0, &k, sig->data, &sigsize)) != GNUTLS_E_SUCCESS | |
100 | ) | |
101 | ret = gnutls_strerror(rc); | |
102 | ||
103 | gnutls_x509_privkey_deinit(sign_ctx->rsa); | |
104 | return ret; | |
420a0d19 | 105 | } |
420a0d19 CE |
106 | |
107 | ||
108 | ||
2813c06e CE |
109 | /* import public key (from DER in memory) |
110 | Return: NULL for success, or an error string */ | |
420a0d19 | 111 | |
2813c06e CE |
112 | const uschar * |
113 | exim_rsa_verify_init(blob * pubkey_der, ev_ctx * verify_ctx) | |
420a0d19 | 114 | { |
2813c06e CE |
115 | gnutls_datum_t k; |
116 | int rc; | |
117 | const uschar * ret = NULL; | |
420a0d19 | 118 | |
2813c06e | 119 | gnutls_pubkey_init(&verify_ctx->rsa); |
420a0d19 | 120 | |
2813c06e CE |
121 | k.data = pubkey_der->data; |
122 | k.size = pubkey_der->len; | |
420a0d19 | 123 | |
2813c06e CE |
124 | if ((rc = gnutls_pubkey_import(verify_ctx->rsa, &k, GNUTLS_X509_FMT_DER)) |
125 | != GNUTLS_E_SUCCESS) | |
126 | ret = gnutls_strerror(rc); | |
127 | return ret; | |
128 | } | |
420a0d19 | 129 | |
420a0d19 | 130 | |
2813c06e CE |
131 | /* verify signature (of hash) (given pubkey & alleged sig) |
132 | Return: NULL for success, or an error string */ | |
420a0d19 | 133 | |
2813c06e CE |
134 | const uschar * |
135 | exim_rsa_verify(ev_ctx * verify_ctx, BOOL is_sha1, blob * data_hash, blob * sig) | |
136 | { | |
137 | gnutls_datum_t k, s; | |
138 | int rc; | |
139 | const uschar * ret = NULL; | |
140 | ||
141 | k.data = data_hash->data; | |
142 | k.size = data_hash->len; | |
143 | s.data = sig->data; | |
144 | s.size = sig->len; | |
145 | if ((rc = gnutls_pubkey_verify_hash2(verify_ctx->rsa, | |
146 | is_sha1 ? GNUTLS_SIGN_RSA_SHA1 : GNUTLS_SIGN_RSA_SHA256, | |
147 | 0, &k, &s)) < 0) | |
148 | ret = gnutls_strerror(rc); | |
149 | ||
150 | gnutls_pubkey_deinit(verify_ctx->rsa); | |
151 | return ret; | |
152 | } | |
420a0d19 | 153 | |
420a0d19 | 154 | |
420a0d19 | 155 | |
420a0d19 | 156 | |
2813c06e CE |
157 | #elif defined(RSA_GCRYPT) |
158 | /******************************************************************************/ | |
420a0d19 | 159 | |
420a0d19 | 160 | |
2813c06e CE |
161 | /* Internal service routine: |
162 | Read and move past an asn.1 header, checking class & tag, | |
163 | optionally returning the data-length */ | |
420a0d19 | 164 | |
2813c06e CE |
165 | static int |
166 | as_tag(blob * der, uschar req_cls, long req_tag, long * alen) | |
167 | { | |
168 | int rc; | |
169 | uschar tag_class; | |
170 | int taglen; | |
171 | long tag, len; | |
172 | ||
173 | /* debug_printf_indent("as_tag: %02x %02x %02x %02x\n", | |
174 | der->data[0], der->data[1], der->data[2], der->data[3]); */ | |
420a0d19 | 175 | |
2813c06e CE |
176 | if ((rc = asn1_get_tag_der(der->data++, der->len--, &tag_class, &taglen, &tag)) |
177 | != ASN1_SUCCESS) | |
178 | return rc; | |
420a0d19 | 179 | |
2813c06e | 180 | if (tag_class != req_cls || tag != req_tag) return ASN1_ELEMENT_NOT_FOUND; |
420a0d19 | 181 | |
2813c06e CE |
182 | if ((len = asn1_get_length_der(der->data, der->len, &taglen)) < 0) |
183 | return ASN1_DER_ERROR; | |
184 | if (alen) *alen = len; | |
420a0d19 | 185 | |
2813c06e | 186 | /* debug_printf_indent("as_tag: tlen %d dlen %d\n", taglen, (int)len); */ |
420a0d19 | 187 | |
2813c06e CE |
188 | der->data += taglen; |
189 | der->len -= taglen; | |
190 | return rc; | |
420a0d19 CE |
191 | } |
192 | ||
2813c06e CE |
193 | /* Internal service routine: |
194 | Read and move over an asn.1 integer, setting an MPI to the value | |
195 | */ | |
420a0d19 | 196 | |
2813c06e CE |
197 | static uschar * |
198 | as_mpi(blob * der, gcry_mpi_t * mpi) | |
420a0d19 | 199 | { |
2813c06e CE |
200 | long alen; |
201 | int rc; | |
202 | gcry_error_t gerr; | |
420a0d19 | 203 | |
2813c06e CE |
204 | /* integer; move past the header */ |
205 | if ((rc = as_tag(der, 0, ASN1_TAG_INTEGER, &alen)) != ASN1_SUCCESS) | |
206 | return US asn1_strerror(rc); | |
420a0d19 | 207 | |
2813c06e CE |
208 | /* read to an MPI */ |
209 | if ((gerr = gcry_mpi_scan(mpi, GCRYMPI_FMT_STD, der->data, alen, NULL))) | |
210 | return US gcry_strerror(gerr); | |
420a0d19 | 211 | |
2813c06e CE |
212 | /* move over the data */ |
213 | der->data += alen; der->len -= alen; | |
214 | return NULL; | |
420a0d19 CE |
215 | } |
216 | ||
420a0d19 | 217 | |
420a0d19 | 218 | |
2813c06e CE |
219 | void |
220 | exim_rsa_init(void) | |
221 | { | |
222 | /* Version check should be the very first call because it | |
223 | makes sure that important subsystems are initialized. */ | |
224 | if (!gcry_check_version (GCRYPT_VERSION)) | |
225 | { | |
226 | fputs ("libgcrypt version mismatch\n", stderr); | |
227 | exit (2); | |
228 | } | |
420a0d19 | 229 | |
2813c06e CE |
230 | /* We don't want to see any warnings, e.g. because we have not yet |
231 | parsed program options which might be used to suppress such | |
232 | warnings. */ | |
233 | gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); | |
420a0d19 | 234 | |
2813c06e CE |
235 | /* ... If required, other initialization goes here. Note that the |
236 | process might still be running with increased privileges and that | |
237 | the secure memory has not been initialized. */ | |
420a0d19 | 238 | |
2813c06e CE |
239 | /* Allocate a pool of 16k secure memory. This make the secure memory |
240 | available and also drops privileges where needed. */ | |
241 | gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); | |
420a0d19 | 242 | |
2813c06e CE |
243 | /* It is now okay to let Libgcrypt complain when there was/is |
244 | a problem with the secure memory. */ | |
245 | gcry_control (GCRYCTL_RESUME_SECMEM_WARN); | |
420a0d19 | 246 | |
2813c06e | 247 | /* ... If required, other initialization goes here. */ |
420a0d19 | 248 | |
2813c06e CE |
249 | /* Tell Libgcrypt that initialization has completed. */ |
250 | gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); | |
420a0d19 | 251 | |
2813c06e | 252 | return; |
420a0d19 CE |
253 | } |
254 | ||
420a0d19 | 255 | |
420a0d19 | 256 | |
420a0d19 | 257 | |
2813c06e CE |
258 | /* Accumulate data (gnutls-only). |
259 | String to be appended must be nul-terminated. */ | |
420a0d19 | 260 | |
2813c06e CE |
261 | blob * |
262 | exim_rsa_data_append(blob * b, int * alloc, uschar * s) | |
263 | { | |
264 | return b; /*dummy*/ | |
420a0d19 CE |
265 | } |
266 | ||
420a0d19 | 267 | |
420a0d19 | 268 | |
2813c06e CE |
269 | /* import private key from PEM string in memory. |
270 | Return: NULL for success, or an error string */ | |
420a0d19 | 271 | |
2813c06e CE |
272 | const uschar * |
273 | exim_rsa_signing_init(uschar * privkey_pem, es_ctx * sign_ctx) | |
274 | { | |
275 | uschar * s1, * s2; | |
276 | blob der; | |
277 | long alen; | |
278 | int rc; | |
420a0d19 | 279 | |
2813c06e CE |
280 | /* |
281 | * RSAPrivateKey ::= SEQUENCE | |
282 | * version Version, | |
283 | * modulus INTEGER, -- n | |
284 | * publicExponent INTEGER, -- e | |
285 | * privateExponent INTEGER, -- d | |
286 | * prime1 INTEGER, -- p | |
287 | * prime2 INTEGER, -- q | |
288 | * exponent1 INTEGER, -- d mod (p-1) | |
289 | * exponent2 INTEGER, -- d mod (q-1) | |
290 | * coefficient INTEGER, -- (inverse of q) mod p | |
291 | * otherPrimeInfos OtherPrimeInfos OPTIONAL | |
292 | */ | |
293 | ||
294 | if ( !(s1 = Ustrstr(CS privkey_pem, "-----BEGIN RSA PRIVATE KEY-----")) | |
295 | || !(s2 = Ustrstr(CS (s1+=31), "-----END RSA PRIVATE KEY-----" )) | |
296 | ) | |
297 | return US"Bad PEM wrapper"; | |
298 | ||
299 | *s2 = '\0'; | |
300 | ||
301 | if ((der.len = b64decode(s1, &der.data)) < 0) | |
302 | return US"Bad PEM-DER b64 decode"; | |
303 | ||
304 | /* untangle asn.1 */ | |
305 | ||
306 | /* sequence; just move past the header */ | |
307 | if ((rc = as_tag(&der, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL)) | |
308 | != ASN1_SUCCESS) goto asn_err; | |
309 | ||
310 | /* integer version; move past the header, check is zero */ | |
311 | if ((rc = as_tag(&der, 0, ASN1_TAG_INTEGER, &alen)) != ASN1_SUCCESS) | |
312 | goto asn_err; | |
313 | if (alen != 1 || *der.data != 0) | |
314 | return US"Bad version number"; | |
315 | der.data++; der.len--; | |
316 | ||
317 | if ( (s1 = as_mpi(&der, &sign_ctx->n)) | |
318 | || (s1 = as_mpi(&der, &sign_ctx->e)) | |
319 | || (s1 = as_mpi(&der, &sign_ctx->d)) | |
320 | || (s1 = as_mpi(&der, &sign_ctx->p)) | |
321 | || (s1 = as_mpi(&der, &sign_ctx->q)) | |
322 | || (s1 = as_mpi(&der, &sign_ctx->dp)) | |
323 | || (s1 = as_mpi(&der, &sign_ctx->dq)) | |
324 | || (s1 = as_mpi(&der, &sign_ctx->qp)) | |
325 | ) | |
326 | return s1; | |
327 | ||
328 | DEBUG(D_acl) debug_printf_indent("rsa_signing_init:\n"); | |
329 | { | |
330 | uschar * s; | |
331 | gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->n); | |
332 | debug_printf_indent(" N : %s\n", s); | |
333 | gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->e); | |
334 | debug_printf_indent(" E : %s\n", s); | |
335 | gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->d); | |
336 | debug_printf_indent(" D : %s\n", s); | |
337 | gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->p); | |
338 | debug_printf_indent(" P : %s\n", s); | |
339 | gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->q); | |
340 | debug_printf_indent(" Q : %s\n", s); | |
341 | gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->dp); | |
342 | debug_printf_indent(" DP: %s\n", s); | |
343 | gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->dq); | |
344 | debug_printf_indent(" DQ: %s\n", s); | |
345 | gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->qp); | |
346 | debug_printf_indent(" QP: %s\n", s); | |
347 | } | |
348 | return NULL; | |
349 | ||
350 | asn_err: return US asn1_strerror(rc); | |
351 | } | |
420a0d19 | 352 | |
420a0d19 | 353 | |
420a0d19 | 354 | |
2813c06e CE |
355 | /* allocate mem for signature (when signing) */ |
356 | /* sign data (gnutls_only) | |
357 | OR | |
358 | sign hash. | |
420a0d19 | 359 | |
2813c06e | 360 | Return: NULL for success, or an error string */ |
420a0d19 | 361 | |
2813c06e CE |
362 | const uschar * |
363 | exim_rsa_sign(es_ctx * sign_ctx, BOOL is_sha1, blob * data, blob * sig) | |
364 | { | |
365 | gcry_sexp_t s_hash = NULL, s_key = NULL, s_sig = NULL; | |
366 | gcry_mpi_t m_sig; | |
367 | uschar * errstr; | |
368 | gcry_error_t gerr; | |
369 | ||
370 | #define SIGSPACE 128 | |
371 | sig->data = store_get(SIGSPACE); | |
372 | ||
373 | if (gcry_mpi_cmp (sign_ctx->p, sign_ctx->q) > 0) | |
374 | { | |
375 | gcry_mpi_swap (sign_ctx->p, sign_ctx->q); | |
376 | gcry_mpi_invm (sign_ctx->qp, sign_ctx->p, sign_ctx->q); | |
377 | } | |
378 | ||
379 | if ( (gerr = gcry_sexp_build (&s_key, NULL, | |
380 | "(private-key (rsa (n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))", | |
381 | sign_ctx->n, sign_ctx->e, | |
382 | sign_ctx->d, sign_ctx->p, | |
383 | sign_ctx->q, sign_ctx->qp)) | |
384 | || (gerr = gcry_sexp_build (&s_hash, NULL, | |
385 | is_sha1 | |
386 | ? "(data(flags pkcs1)(hash sha1 %b))" | |
387 | : "(data(flags pkcs1)(hash sha256 %b))", | |
388 | (int) data->len, CS data->data)) | |
389 | || (gerr = gcry_pk_sign (&s_sig, s_hash, s_key)) | |
390 | ) | |
391 | return US gcry_strerror(gerr); | |
392 | ||
393 | /* gcry_sexp_dump(s_sig); */ | |
394 | ||
395 | if ( !(s_sig = gcry_sexp_find_token(s_sig, "s", 0)) | |
396 | ) | |
397 | return US"no sig result"; | |
398 | ||
399 | m_sig = gcry_sexp_nth_mpi(s_sig, 1, GCRYMPI_FMT_USG); | |
400 | ||
401 | DEBUG(D_acl) | |
402 | { | |
403 | uschar * s; | |
404 | gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, m_sig); | |
405 | debug_printf_indent(" SG: %s\n", s); | |
406 | } | |
407 | ||
408 | gerr = gcry_mpi_print(GCRYMPI_FMT_USG, sig->data, SIGSPACE, &sig->len, m_sig); | |
409 | if (gerr) | |
410 | { | |
411 | debug_printf_indent("signature conversion from MPI to buffer failed\n"); | |
412 | return US gcry_strerror(gerr); | |
413 | } | |
414 | #undef SIGSPACE | |
415 | ||
416 | return NULL; | |
417 | } | |
420a0d19 | 418 | |
420a0d19 | 419 | |
2813c06e CE |
420 | /* import public key (from DER in memory) |
421 | Return: NULL for success, or an error string */ | |
420a0d19 | 422 | |
2813c06e CE |
423 | const uschar * |
424 | exim_rsa_verify_init(blob * pubkey_der, ev_ctx * verify_ctx) | |
425 | { | |
426 | /* | |
427 | in code sequence per b81207d2bfa92 rsa_parse_public_key() and asn1_get_mpi() | |
428 | */ | |
429 | uschar tag_class; | |
430 | int taglen; | |
431 | long alen; | |
432 | int rc; | |
433 | uschar * errstr; | |
434 | gcry_error_t gerr; | |
435 | uschar * stage = US"S1"; | |
420a0d19 CE |
436 | |
437 | /* | |
2813c06e CE |
438 | sequence |
439 | sequence | |
440 | OBJECT:rsaEncryption | |
441 | NULL | |
442 | BIT STRING:RSAPublicKey | |
443 | sequence | |
444 | INTEGER:Public modulus | |
445 | INTEGER:Public exponent | |
446 | ||
447 | openssl rsa -in aux-fixed/dkim/dkim.private -pubout -outform DER | od -t x1 | head; | |
448 | openssl rsa -in aux-fixed/dkim/dkim.private -pubout | openssl asn1parse -dump; | |
449 | openssl rsa -in aux-fixed/dkim/dkim.private -pubout | openssl asn1parse -dump -offset 22; | |
450 | */ | |
420a0d19 | 451 | |
2813c06e CE |
452 | /* sequence; just move past the header */ |
453 | if ((rc = as_tag(pubkey_der, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL)) | |
454 | != ASN1_SUCCESS) goto asn_err; | |
455 | ||
456 | /* sequence; skip the entire thing */ | |
457 | DEBUG(D_acl) stage = US"S2"; | |
458 | if ((rc = as_tag(pubkey_der, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, &alen)) | |
459 | != ASN1_SUCCESS) goto asn_err; | |
460 | pubkey_der->data += alen; pubkey_der->len -= alen; | |
461 | ||
462 | ||
463 | /* bitstring: limit range to size of bitstring; | |
464 | move over header + content wrapper */ | |
465 | DEBUG(D_acl) stage = US"BS"; | |
466 | if ((rc = as_tag(pubkey_der, 0, ASN1_TAG_BIT_STRING, &alen)) != ASN1_SUCCESS) | |
467 | goto asn_err; | |
468 | pubkey_der->len = alen; | |
469 | pubkey_der->data++; pubkey_der->len--; | |
470 | ||
471 | /* sequence; just move past the header */ | |
472 | DEBUG(D_acl) stage = US"S3"; | |
473 | if ((rc = as_tag(pubkey_der, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL)) | |
474 | != ASN1_SUCCESS) goto asn_err; | |
475 | ||
476 | /* read two integers */ | |
477 | DEBUG(D_acl) stage = US"MPI"; | |
478 | if ( (errstr = as_mpi(pubkey_der, &verify_ctx->n)) | |
479 | || (errstr = as_mpi(pubkey_der, &verify_ctx->e)) | |
480 | ) | |
481 | return errstr; | |
482 | ||
483 | DEBUG(D_acl) debug_printf_indent("rsa_verify_init:\n"); | |
484 | { | |
485 | uschar * s; | |
486 | gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, verify_ctx->n); | |
487 | debug_printf_indent(" N : %s\n", s); | |
488 | gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, verify_ctx->e); | |
489 | debug_printf_indent(" E : %s\n", s); | |
490 | } | |
491 | ||
492 | return NULL; | |
493 | ||
494 | asn_err: | |
495 | DEBUG(D_acl) return string_sprintf("%s: %s", stage, asn1_strerror(rc)); | |
496 | return US asn1_strerror(rc); | |
497 | } | |
420a0d19 | 498 | |
420a0d19 | 499 | |
2813c06e CE |
500 | /* verify signature (of hash) (given pubkey & alleged sig) |
501 | Return: NULL for success, or an error string */ | |
420a0d19 | 502 | |
2813c06e CE |
503 | const uschar * |
504 | exim_rsa_verify(ev_ctx * verify_ctx, BOOL is_sha1, blob * data_hash, blob * sig) | |
505 | { | |
506 | /* | |
507 | cf. libgnutls 2.8.5 _wrap_gcry_pk_verify() | |
508 | */ | |
509 | gcry_mpi_t m_sig; | |
510 | gcry_sexp_t s_sig = NULL, s_hash = NULL, s_pkey = NULL; | |
511 | gcry_error_t gerr; | |
512 | uschar * stage; | |
513 | ||
514 | if ( (stage = US"pkey sexp build", | |
515 | gerr = gcry_sexp_build (&s_pkey, NULL, "(public-key(rsa(n%m)(e%m)))", | |
516 | verify_ctx->n, verify_ctx->e)) | |
517 | || (stage = US"data sexp build", | |
518 | gerr = gcry_sexp_build (&s_hash, NULL, | |
519 | is_sha1 | |
520 | ? "(data(flags pkcs1)(hash sha1 %b))" | |
521 | : "(data(flags pkcs1)(hash sha256 %b))", | |
522 | (int) data_hash->len, CS data_hash->data)) | |
523 | || (stage = US"sig mpi scan", | |
524 | gerr = gcry_mpi_scan(&m_sig, GCRYMPI_FMT_USG, sig->data, sig->len, NULL)) | |
525 | || (stage = US"sig sexp build", | |
526 | gerr = gcry_sexp_build (&s_sig, NULL, "(sig-val(rsa(s%m)))", m_sig)) | |
527 | || (stage = US"verify", | |
528 | gerr = gcry_pk_verify (s_sig, s_hash, s_pkey)) | |
529 | ) | |
530 | { | |
531 | DEBUG(D_acl) debug_printf_indent("verify: error in stage '%s'\n", stage); | |
532 | return US gcry_strerror(gerr); | |
533 | } | |
534 | ||
535 | if (s_sig) gcry_sexp_release (s_sig); | |
536 | if (s_hash) gcry_sexp_release (s_hash); | |
537 | if (s_pkey) gcry_sexp_release (s_pkey); | |
538 | gcry_mpi_release (m_sig); | |
539 | gcry_mpi_release (verify_ctx->n); | |
540 | gcry_mpi_release (verify_ctx->e); | |
541 | ||
542 | return NULL; | |
543 | } | |
420a0d19 | 544 | |
420a0d19 | 545 | |
420a0d19 | 546 | |
420a0d19 | 547 | |
2813c06e CE |
548 | #elif defined(RSA_OPENSSL) |
549 | /******************************************************************************/ | |
420a0d19 | 550 | |
2813c06e CE |
551 | void |
552 | exim_rsa_init(void) | |
553 | { | |
554 | } | |
420a0d19 | 555 | |
420a0d19 | 556 | |
2813c06e CE |
557 | /* accumulate data (gnutls-only) */ |
558 | blob * | |
559 | exim_rsa_data_append(blob * b, int * alloc, uschar * s) | |
560 | { | |
561 | return b; /*dummy*/ | |
562 | } | |
420a0d19 | 563 | |
420a0d19 | 564 | |
2813c06e CE |
565 | /* import private key from PEM string in memory. |
566 | Return: NULL for success, or an error string */ | |
420a0d19 | 567 | |
2813c06e CE |
568 | const uschar * |
569 | exim_rsa_signing_init(uschar * privkey_pem, es_ctx * sign_ctx) | |
420a0d19 | 570 | { |
2813c06e CE |
571 | uschar * p, * q; |
572 | int len; | |
573 | ||
574 | /* Convert PEM to DER */ | |
575 | if ( !(p = Ustrstr(privkey_pem, "-----BEGIN RSA PRIVATE KEY-----")) | |
576 | || !(q = Ustrstr(p+=31, "-----END RSA PRIVATE KEY-----")) | |
577 | ) | |
578 | return US"Bad PEM wrapping"; | |
579 | ||
580 | *q = '\0'; | |
581 | if ((len = b64decode(p, &p)) < 0) | |
582 | return US"b64decode failed"; | |
583 | ||
584 | if (!(sign_ctx->rsa = d2i_RSAPrivateKey(NULL, CUSS &p, len))) | |
585 | { | |
586 | char ssl_errstring[256]; | |
587 | ERR_load_crypto_strings(); /*XXX move to a startup routine */ | |
588 | ERR_error_string(ERR_get_error(), ssl_errstring); | |
589 | return string_copy(US ssl_errstring); | |
590 | } | |
591 | ||
592 | return NULL; | |
420a0d19 CE |
593 | } |
594 | ||
420a0d19 | 595 | |
2813c06e CE |
596 | |
597 | /* allocate mem for signature (when signing) */ | |
598 | /* sign data (gnutls_only) | |
599 | OR | |
600 | sign hash. | |
601 | ||
602 | Return: NULL for success, or an error string */ | |
603 | ||
604 | const uschar * | |
605 | exim_rsa_sign(es_ctx * sign_ctx, BOOL is_sha1, blob * data, blob * sig) | |
420a0d19 | 606 | { |
2813c06e CE |
607 | uint len; |
608 | const uschar * ret = NULL; | |
609 | ||
610 | /* Allocate mem for signature */ | |
611 | len = RSA_size(sign_ctx->rsa); | |
612 | sig->data = store_get(len); | |
613 | sig->len = len; | |
614 | ||
615 | /* Do signing */ | |
616 | if (RSA_sign(is_sha1 ? NID_sha1 : NID_sha256, | |
617 | CUS data->data, data->len, | |
618 | US sig->data, &len, sign_ctx->rsa) != 1) | |
619 | { | |
620 | char ssl_errstring[256]; | |
621 | ERR_load_crypto_strings(); /*XXX move to a startup routine */ | |
622 | ERR_error_string(ERR_get_error(), ssl_errstring); | |
623 | ret = string_copy(US ssl_errstring); | |
624 | } | |
625 | ||
626 | RSA_free(sign_ctx->rsa); | |
627 | return ret;; | |
420a0d19 CE |
628 | } |
629 | ||
630 | ||
420a0d19 | 631 | |
2813c06e CE |
632 | /* import public key (from DER in memory) |
633 | Return: nULL for success, or an error string */ | |
634 | ||
635 | const uschar * | |
636 | exim_rsa_verify_init(blob * pubkey_der, ev_ctx * verify_ctx) | |
420a0d19 | 637 | { |
2813c06e CE |
638 | const uschar * p = CUS pubkey_der->data; |
639 | const uschar * ret = NULL; | |
640 | ||
641 | if (!(verify_ctx->rsa = d2i_RSA_PUBKEY(NULL, &p, (long) pubkey_der->len))) | |
642 | { | |
643 | char ssl_errstring[256]; | |
644 | ERR_load_crypto_strings(); /*XXX move to a startup routine */ | |
645 | ERR_error_string(ERR_get_error(), ssl_errstring); | |
646 | ret = string_copy(CUS ssl_errstring); | |
647 | } | |
648 | return ret; | |
420a0d19 CE |
649 | } |
650 | ||
2813c06e CE |
651 | |
652 | ||
653 | ||
654 | /* verify signature (of hash) (given pubkey & alleged sig) | |
655 | Return: NULL for success, or an error string */ | |
656 | ||
657 | const uschar * | |
658 | exim_rsa_verify(ev_ctx * verify_ctx, BOOL is_sha1, blob * data_hash, blob * sig) | |
420a0d19 | 659 | { |
2813c06e CE |
660 | const uschar * ret = NULL; |
661 | ||
662 | if (RSA_verify(is_sha1 ? NID_sha1 : NID_sha256, | |
663 | CUS data_hash->data, data_hash->len, | |
664 | US sig->data, (uint) sig->len, verify_ctx->rsa) != 1) | |
665 | { | |
666 | char ssl_errstring[256]; | |
667 | ERR_load_crypto_strings(); /*XXX move to a startup routine */ | |
668 | ERR_error_string(ERR_get_error(), ssl_errstring); | |
669 | ret = string_copy(US ssl_errstring); | |
670 | } | |
671 | return ret; | |
420a0d19 | 672 | } |
2813c06e CE |
673 | |
674 | ||
675 | #endif | |
676 | /******************************************************************************/ | |
677 | ||
678 | #endif /*DISABLE_DKIM*/ | |
679 | /* End of File */ |