From: Jeremy Harris Date: Fri, 19 May 2017 12:32:53 +0000 (+0100) Subject: DKIM: rename internal signing api X-Git-Tag: exim-4_90_RC1~77 X-Git-Url: https://git.exim.org/exim.git/commitdiff_plain/9b2583c440ab9104070054dfa02e8611799f777b DKIM: rename internal signing api --- diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks index 44e3a4ebc..22e5a4bd7 100755 --- a/src/scripts/MakeLinks +++ b/src/scripts/MakeLinks @@ -85,7 +85,7 @@ cd .. mkdir pdkim cd pdkim for f in README Makefile crypt_ver.h pdkim.c \ - pdkim.h hash.c hash.h rsa.c rsa.h blob.h + pdkim.h hash.c hash.h signing.c signing.h blob.h do ln -s ../../src/pdkim/$f $f done diff --git a/src/src/dkim.c b/src/src/dkim.c index 2857e6398..f7b9ee0d1 100644 --- a/src/src/dkim.c +++ b/src/src/dkim.c @@ -608,6 +608,11 @@ while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep, NULL, 0))) dkim_private_key_expanded = big_buffer; } +/*XXX so we currently nail signing to RSA + SHA256. Need to extract algo +from privkey, and provide means for selecting hash-method. +Check for disallowed combos. +Will need new dkim_ transport option for hash. */ + if (!(ctx = pdkim_init_sign(CS dkim_signing_domain, CS dkim_signing_selector, CS dkim_private_key_expanded, diff --git a/src/src/hash.c b/src/src/hash.c index f49add29c..e239516e1 100644 --- a/src/src/hash.c +++ b/src/src/hash.c @@ -33,6 +33,7 @@ sha1; BOOL exim_sha_init(hctx * h, hashmethod m) { +/*XXX extend for sha512 */ switch (h->method = m) { case HASH_SHA1: h->hashlen = 20; SHA1_Init (&h->u.sha1); break; @@ -77,6 +78,7 @@ switch (h->method) BOOL exim_sha_init(hctx * h, hashmethod m) { +/*XXX extend for sha512 */ switch (h->method = m) { case HASH_SHA1: h->hashlen = 20; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA1); break; @@ -112,6 +114,7 @@ gnutls_hash_output(h->sha, b->data); BOOL exim_sha_init(hctx * h, hashmethod m) { +/*XXX extend for sha512 */ switch (h->method = m) { case HASH_SHA1: h->hashlen = 20; gcry_md_open(&h->sha, GCRY_MD_SHA1, 0); break; @@ -145,6 +148,7 @@ memcpy(b->data, gcry_md_read(h->sha, 0), h->hashlen); BOOL exim_sha_init(hctx * h, hashmethod m) { +/*XXX extend for sha512 */ switch (h->method = m) { case HASH_SHA1: h->hashlen = 20; sha1_starts(&h->u.sha1); break; diff --git a/src/src/pdkim/Makefile b/src/src/pdkim/Makefile index c298568ea..10631ceaa 100644 --- a/src/src/pdkim/Makefile +++ b/src/src/pdkim/Makefile @@ -1,6 +1,6 @@ # Make file for building the pdkim library. -OBJ = pdkim.o rsa.o +OBJ = pdkim.o signing.o pdkim.a: $(OBJ) @$(RM_COMMAND) -f pdkim.a @@ -13,6 +13,6 @@ pdkim.a: $(OBJ) $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) -I. $*.c pdkim.o: $(HDRS) crypt_ver.h pdkim.h pdkim.c -rsa.o: $(HDRS) crypt_ver.h rsa.h rsa.c +signing.o: $(HDRS) crypt_ver.h signing.h signing.c # End diff --git a/src/src/pdkim/pdkim.c b/src/src/pdkim/pdkim.c index 2ce0ff6c9..441f96cb5 100644 --- a/src/src/pdkim/pdkim.c +++ b/src/src/pdkim/pdkim.c @@ -42,7 +42,7 @@ #endif #include "pdkim.h" -#include "rsa.h" +#include "signing.h" #define PDKIM_SIGNATURE_VERSION "1" #define PDKIM_PUB_RECORD_VERSION US "DKIM1" @@ -83,11 +83,13 @@ const uschar * pdkim_canons[] = { US"relaxed", NULL }; +/*XXX currently unused */ const uschar * pdkim_hashes[] = { US"sha256", US"sha1", NULL }; +/*XXX currently unused */ const uschar * pdkim_keytypes[] = { US"rsa", NULL @@ -505,6 +507,7 @@ for (p = raw_hdr; ; p++) Ustrcmp(cur_val, PDKIM_SIGNATURE_VERSION) == 0 ? 1 : -1; break; case 'a': +/*XXX this searches a list of combined (algo + hash-method)s */ for (i = 0; pdkim_algos[i]; i++) if (Ustrcmp(cur_val, pdkim_algos[i]) == 0) { @@ -583,6 +586,7 @@ DEBUG(D_acl) "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); } +/*XXX hash method: extend for sha512 */ if (!exim_sha_init(&sig->body_hash_ctx, sig->algo == PDKIM_ALGO_RSA_SHA1 ? HASH_SHA1 : HASH_SHA256)) { @@ -1185,6 +1189,7 @@ col = hdr_len; /* Required and static bits */ hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"a=", +/*XXX this is a combo of algo and hash-method */ pdkim_algos[sig->algo]); hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"q=", pdkim_querymethods[sig->querymethod]); @@ -1332,7 +1337,7 @@ DEBUG(D_acl) debug_printf( "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); /* Import public key */ -if ((*errstr = exim_rsa_verify_init(&p->key, vctx))) +if ((*errstr = exim_dkim_verify_init(&p->key, vctx))) { DEBUG(D_acl) debug_printf("verify_init: %s\n", *errstr); sig->verify_status = PDKIM_VERIFY_INVALID; @@ -1370,6 +1375,7 @@ pdkim_finish_bodyhash(ctx); while (sig) { +/*XXX bool probably not enough */ BOOL is_sha1 = sig->algo == PDKIM_ALGO_RSA_SHA1; hctx hhash_ctx; uschar * sig_hdr = US""; @@ -1422,7 +1428,7 @@ while (sig) exim_sha_update(&hhash_ctx, CUS rh, Ustrlen(rh)); /* Remember headers block for signing (when the library cannot do incremental) */ - (void) exim_rsa_data_append(&hdata, &hdata_alloc, rh); + (void) exim_dkim_data_append(&hdata, &hdata_alloc, rh); DEBUG(D_acl) pdkim_quoteprint(rh, Ustrlen(rh)); } @@ -1522,8 +1528,9 @@ while (sig) } /* Remember headers block for signing (when the library cannot do incremental) */ +/*XXX is this assuing algo == RSA? */ if (ctx->flags & PDKIM_MODE_SIGN) - (void) exim_rsa_data_append(&hdata, &hdata_alloc, US sig_hdr); + (void) exim_dkim_data_append(&hdata, &hdata_alloc, US sig_hdr); /* SIGNING ---------------------------------------------------------------- */ if (ctx->flags & PDKIM_MODE_SIGN) @@ -1531,7 +1538,8 @@ while (sig) es_ctx sctx; /* Import private key */ - if ((*err = exim_rsa_signing_init(US sig->rsa_privkey, &sctx))) +/*XXX extend for non-RSA algos */ + if ((*err = exim_dkim_signing_init(US sig->rsa_privkey, &sctx))) { DEBUG(D_acl) debug_printf("signing_init: %s\n", *err); return PDKIM_ERR_RSA_PRIVKEY; @@ -1545,7 +1553,11 @@ while (sig) hdata = hhash; #endif - if ((*err = exim_rsa_sign(&sctx, is_sha1, &hdata, &sig->sighash))) +/*XXX extend for non-RSA algos */ +/*XXX oddly the dkim rfc does _not_ say what variant (sha1 or sha256) of +RSA signing should be done. We use the same variant as the hash-method. */ + + if ((*err = exim_dkim_sign(&sctx, is_sha1, &hdata, &sig->sighash))) { DEBUG(D_acl) debug_printf("signing: %s\n", *err); return PDKIM_ERR_RSA_SIGNING; @@ -1619,7 +1631,8 @@ while (sig) } /* Check the signature */ - if ((*err = exim_rsa_verify(&vctx, is_sha1, &hhash, &sig->sighash))) +/*XXX needs extension for non-RSA */ + if ((*err = exim_dkim_verify(&vctx, is_sha1, &hhash, &sig->sighash))) { DEBUG(D_acl) debug_printf("headers verify: %s\n", *err); sig->verify_status = PDKIM_VERIFY_FAIL; @@ -1677,6 +1690,9 @@ return ctx; /* -------------------------------------------------------------------------- */ +/*XXX ? needs extension to cover non-RSA algo? Currently the "algo" is actually +the combo of algo and hash-method */ + DLLEXPORT pdkim_ctx * pdkim_init_sign(char * domain, char * selector, char * rsa_privkey, int algo, BOOL dot_stuffed, int(*dns_txt_callback)(char *, char *), @@ -1707,6 +1723,7 @@ sig->selector = string_copy(US selector); sig->rsa_privkey = string_copy(US rsa_privkey); sig->algo = algo; +/*XXX extend for sha512 */ if (!exim_sha_init(&sig->body_hash_ctx, algo == PDKIM_ALGO_RSA_SHA1 ? HASH_SHA1 : HASH_SHA256)) { @@ -1761,7 +1778,7 @@ return PDKIM_OK; void pdkim_init(void) { -exim_rsa_init(); +exim_dkim_init(); } diff --git a/src/src/pdkim/rsa.c b/src/src/pdkim/rsa.c deleted file mode 100644 index 950c617c7..000000000 --- a/src/src/pdkim/rsa.c +++ /dev/null @@ -1,679 +0,0 @@ -/* - * PDKIM - a RFC4871 (DKIM) implementation - * - * Copyright (C) 2016 Exim maintainers - * - * RSA signing/verification interface - */ - -#include "../exim.h" - -#ifndef DISABLE_DKIM /* entire file */ - -#ifndef SUPPORT_TLS -# error Need SUPPORT_TLS for DKIM -#endif - -#include "crypt_ver.h" -#include "rsa.h" - - -/******************************************************************************/ -#ifdef RSA_GNUTLS - -void -exim_rsa_init(void) -{ -} - - -/* accumulate data (gnutls-only). String to be appended must be nul-terminated. */ -blob * -exim_rsa_data_append(blob * b, int * alloc, uschar * s) -{ -int len = b->len; -b->data = string_append(b->data, alloc, &len, 1, s); -b->len = len; -return b; -} - - - -/* import private key from PEM string in memory. -Return: NULL for success, or an error string */ - -const uschar * -exim_rsa_signing_init(uschar * privkey_pem, es_ctx * sign_ctx) -{ -gnutls_datum_t k; -int rc; - -k.data = privkey_pem; -k.size = strlen(privkey_pem); - -if ( (rc = gnutls_x509_privkey_init(&sign_ctx->rsa)) != GNUTLS_E_SUCCESS - /*|| (rc = gnutls_x509_privkey_import(sign_ctx->rsa, &k, - GNUTLS_X509_FMT_PEM)) != GNUTLS_E_SUCCESS */ - ) - return gnutls_strerror(rc); - -if ( /* (rc = gnutls_x509_privkey_init(&sign_ctx->rsa)) != GNUTLS_E_SUCCESS - ||*/ (rc = gnutls_x509_privkey_import(sign_ctx->rsa, &k, - GNUTLS_X509_FMT_PEM)) != GNUTLS_E_SUCCESS - ) - return gnutls_strerror(rc); - -return NULL; -} - - - -/* allocate mem for signature (when signing) */ -/* sign data (gnutls_only) -OR -sign hash. - -Return: NULL for success, or an error string */ - -const uschar * -exim_rsa_sign(es_ctx * sign_ctx, BOOL is_sha1, blob * data, blob * sig) -{ -gnutls_datum_t k; -size_t sigsize = 0; -int rc; -const uschar * ret = NULL; - -/* Allocate mem for signature */ -k.data = data->data; -k.size = data->len; -(void) gnutls_x509_privkey_sign_data(sign_ctx->rsa, - is_sha1 ? GNUTLS_DIG_SHA1 : GNUTLS_DIG_SHA256, - 0, &k, NULL, &sigsize); - -sig->data = store_get(sigsize); -sig->len = sigsize; - -/* Do signing */ -if ((rc = gnutls_x509_privkey_sign_data(sign_ctx->rsa, - is_sha1 ? GNUTLS_DIG_SHA1 : GNUTLS_DIG_SHA256, - 0, &k, sig->data, &sigsize)) != GNUTLS_E_SUCCESS - ) - ret = gnutls_strerror(rc); - -gnutls_x509_privkey_deinit(sign_ctx->rsa); -return ret; -} - - - -/* import public key (from DER in memory) -Return: NULL for success, or an error string */ - -const uschar * -exim_rsa_verify_init(blob * pubkey_der, ev_ctx * verify_ctx) -{ -gnutls_datum_t k; -int rc; -const uschar * ret = NULL; - -gnutls_pubkey_init(&verify_ctx->rsa); - -k.data = pubkey_der->data; -k.size = pubkey_der->len; - -if ((rc = gnutls_pubkey_import(verify_ctx->rsa, &k, GNUTLS_X509_FMT_DER)) - != GNUTLS_E_SUCCESS) - ret = gnutls_strerror(rc); -return ret; -} - - -/* verify signature (of hash) (given pubkey & alleged sig) -Return: NULL for success, or an error string */ - -const uschar * -exim_rsa_verify(ev_ctx * verify_ctx, BOOL is_sha1, blob * data_hash, blob * sig) -{ -gnutls_datum_t k, s; -int rc; -const uschar * ret = NULL; - -k.data = data_hash->data; -k.size = data_hash->len; -s.data = sig->data; -s.size = sig->len; -if ((rc = gnutls_pubkey_verify_hash2(verify_ctx->rsa, - is_sha1 ? GNUTLS_SIGN_RSA_SHA1 : GNUTLS_SIGN_RSA_SHA256, - 0, &k, &s)) < 0) - ret = gnutls_strerror(rc); - -gnutls_pubkey_deinit(verify_ctx->rsa); -return ret; -} - - - - -#elif defined(RSA_GCRYPT) -/******************************************************************************/ - - -/* Internal service routine: -Read and move past an asn.1 header, checking class & tag, -optionally returning the data-length */ - -static int -as_tag(blob * der, uschar req_cls, long req_tag, long * alen) -{ -int rc; -uschar tag_class; -int taglen; -long tag, len; - -/* debug_printf_indent("as_tag: %02x %02x %02x %02x\n", - der->data[0], der->data[1], der->data[2], der->data[3]); */ - -if ((rc = asn1_get_tag_der(der->data++, der->len--, &tag_class, &taglen, &tag)) - != ASN1_SUCCESS) - return rc; - -if (tag_class != req_cls || tag != req_tag) return ASN1_ELEMENT_NOT_FOUND; - -if ((len = asn1_get_length_der(der->data, der->len, &taglen)) < 0) - return ASN1_DER_ERROR; -if (alen) *alen = len; - -/* debug_printf_indent("as_tag: tlen %d dlen %d\n", taglen, (int)len); */ - -der->data += taglen; -der->len -= taglen; -return rc; -} - -/* Internal service routine: -Read and move over an asn.1 integer, setting an MPI to the value -*/ - -static uschar * -as_mpi(blob * der, gcry_mpi_t * mpi) -{ -long alen; -int rc; -gcry_error_t gerr; - -/* integer; move past the header */ -if ((rc = as_tag(der, 0, ASN1_TAG_INTEGER, &alen)) != ASN1_SUCCESS) - return US asn1_strerror(rc); - -/* read to an MPI */ -if ((gerr = gcry_mpi_scan(mpi, GCRYMPI_FMT_STD, der->data, alen, NULL))) - return US gcry_strerror(gerr); - -/* move over the data */ -der->data += alen; der->len -= alen; -return NULL; -} - - - -void -exim_rsa_init(void) -{ -/* Version check should be the very first call because it -makes sure that important subsystems are initialized. */ -if (!gcry_check_version (GCRYPT_VERSION)) - { - fputs ("libgcrypt version mismatch\n", stderr); - exit (2); - } - -/* We don't want to see any warnings, e.g. because we have not yet -parsed program options which might be used to suppress such -warnings. */ -gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); - -/* ... If required, other initialization goes here. Note that the -process might still be running with increased privileges and that -the secure memory has not been initialized. */ - -/* Allocate a pool of 16k secure memory. This make the secure memory -available and also drops privileges where needed. */ -gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); - -/* It is now okay to let Libgcrypt complain when there was/is -a problem with the secure memory. */ -gcry_control (GCRYCTL_RESUME_SECMEM_WARN); - -/* ... If required, other initialization goes here. */ - -/* Tell Libgcrypt that initialization has completed. */ -gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); - -return; -} - - - - -/* Accumulate data (gnutls-only). -String to be appended must be nul-terminated. */ - -blob * -exim_rsa_data_append(blob * b, int * alloc, uschar * s) -{ -return b; /*dummy*/ -} - - - -/* import private key from PEM string in memory. -Return: NULL for success, or an error string */ - -const uschar * -exim_rsa_signing_init(uschar * privkey_pem, es_ctx * sign_ctx) -{ -uschar * s1, * s2; -blob der; -long alen; -int rc; - -/* - * RSAPrivateKey ::= SEQUENCE - * version Version, - * modulus INTEGER, -- n - * publicExponent INTEGER, -- e - * privateExponent INTEGER, -- d - * prime1 INTEGER, -- p - * prime2 INTEGER, -- q - * exponent1 INTEGER, -- d mod (p-1) - * exponent2 INTEGER, -- d mod (q-1) - * coefficient INTEGER, -- (inverse of q) mod p - * otherPrimeInfos OtherPrimeInfos OPTIONAL - */ - -if ( !(s1 = Ustrstr(CS privkey_pem, "-----BEGIN RSA PRIVATE KEY-----")) - || !(s2 = Ustrstr(CS (s1+=31), "-----END RSA PRIVATE KEY-----" )) - ) - return US"Bad PEM wrapper"; - -*s2 = '\0'; - -if ((der.len = b64decode(s1, &der.data)) < 0) - return US"Bad PEM-DER b64 decode"; - -/* untangle asn.1 */ - -/* sequence; just move past the header */ -if ((rc = as_tag(&der, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL)) - != ASN1_SUCCESS) goto asn_err; - -/* integer version; move past the header, check is zero */ -if ((rc = as_tag(&der, 0, ASN1_TAG_INTEGER, &alen)) != ASN1_SUCCESS) - goto asn_err; -if (alen != 1 || *der.data != 0) - return US"Bad version number"; -der.data++; der.len--; - -if ( (s1 = as_mpi(&der, &sign_ctx->n)) - || (s1 = as_mpi(&der, &sign_ctx->e)) - || (s1 = as_mpi(&der, &sign_ctx->d)) - || (s1 = as_mpi(&der, &sign_ctx->p)) - || (s1 = as_mpi(&der, &sign_ctx->q)) - || (s1 = as_mpi(&der, &sign_ctx->dp)) - || (s1 = as_mpi(&der, &sign_ctx->dq)) - || (s1 = as_mpi(&der, &sign_ctx->qp)) - ) - return s1; - -DEBUG(D_acl) debug_printf_indent("rsa_signing_init:\n"); - { - uschar * s; - gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->n); - debug_printf_indent(" N : %s\n", s); - gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->e); - debug_printf_indent(" E : %s\n", s); - gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->d); - debug_printf_indent(" D : %s\n", s); - gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->p); - debug_printf_indent(" P : %s\n", s); - gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->q); - debug_printf_indent(" Q : %s\n", s); - gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->dp); - debug_printf_indent(" DP: %s\n", s); - gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->dq); - debug_printf_indent(" DQ: %s\n", s); - gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->qp); - debug_printf_indent(" QP: %s\n", s); - } -return NULL; - -asn_err: return US asn1_strerror(rc); -} - - - -/* allocate mem for signature (when signing) */ -/* sign data (gnutls_only) -OR -sign hash. - -Return: NULL for success, or an error string */ - -const uschar * -exim_rsa_sign(es_ctx * sign_ctx, BOOL is_sha1, blob * data, blob * sig) -{ -gcry_sexp_t s_hash = NULL, s_key = NULL, s_sig = NULL; -gcry_mpi_t m_sig; -uschar * errstr; -gcry_error_t gerr; - -#define SIGSPACE 128 -sig->data = store_get(SIGSPACE); - -if (gcry_mpi_cmp (sign_ctx->p, sign_ctx->q) > 0) - { - gcry_mpi_swap (sign_ctx->p, sign_ctx->q); - gcry_mpi_invm (sign_ctx->qp, sign_ctx->p, sign_ctx->q); - } - -if ( (gerr = gcry_sexp_build (&s_key, NULL, - "(private-key (rsa (n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))", - sign_ctx->n, sign_ctx->e, - sign_ctx->d, sign_ctx->p, - sign_ctx->q, sign_ctx->qp)) - || (gerr = gcry_sexp_build (&s_hash, NULL, - is_sha1 - ? "(data(flags pkcs1)(hash sha1 %b))" - : "(data(flags pkcs1)(hash sha256 %b))", - (int) data->len, CS data->data)) - || (gerr = gcry_pk_sign (&s_sig, s_hash, s_key)) - ) - return US gcry_strerror(gerr); - -/* gcry_sexp_dump(s_sig); */ - -if ( !(s_sig = gcry_sexp_find_token(s_sig, "s", 0)) - ) - return US"no sig result"; - -m_sig = gcry_sexp_nth_mpi(s_sig, 1, GCRYMPI_FMT_USG); - -DEBUG(D_acl) - { - uschar * s; - gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, m_sig); - debug_printf_indent(" SG: %s\n", s); - } - -gerr = gcry_mpi_print(GCRYMPI_FMT_USG, sig->data, SIGSPACE, &sig->len, m_sig); -if (gerr) - { - debug_printf_indent("signature conversion from MPI to buffer failed\n"); - return US gcry_strerror(gerr); - } -#undef SIGSPACE - -return NULL; -} - - -/* import public key (from DER in memory) -Return: NULL for success, or an error string */ - -const uschar * -exim_rsa_verify_init(blob * pubkey_der, ev_ctx * verify_ctx) -{ -/* -in code sequence per b81207d2bfa92 rsa_parse_public_key() and asn1_get_mpi() -*/ -uschar tag_class; -int taglen; -long alen; -int rc; -uschar * errstr; -gcry_error_t gerr; -uschar * stage = US"S1"; - -/* -sequence - sequence - OBJECT:rsaEncryption - NULL - BIT STRING:RSAPublicKey - sequence - INTEGER:Public modulus - INTEGER:Public exponent - -openssl rsa -in aux-fixed/dkim/dkim.private -pubout -outform DER | od -t x1 | head; -openssl rsa -in aux-fixed/dkim/dkim.private -pubout | openssl asn1parse -dump; -openssl rsa -in aux-fixed/dkim/dkim.private -pubout | openssl asn1parse -dump -offset 22; -*/ - -/* sequence; just move past the header */ -if ((rc = as_tag(pubkey_der, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL)) - != ASN1_SUCCESS) goto asn_err; - -/* sequence; skip the entire thing */ -DEBUG(D_acl) stage = US"S2"; -if ((rc = as_tag(pubkey_der, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, &alen)) - != ASN1_SUCCESS) goto asn_err; -pubkey_der->data += alen; pubkey_der->len -= alen; - - -/* bitstring: limit range to size of bitstring; -move over header + content wrapper */ -DEBUG(D_acl) stage = US"BS"; -if ((rc = as_tag(pubkey_der, 0, ASN1_TAG_BIT_STRING, &alen)) != ASN1_SUCCESS) - goto asn_err; -pubkey_der->len = alen; -pubkey_der->data++; pubkey_der->len--; - -/* sequence; just move past the header */ -DEBUG(D_acl) stage = US"S3"; -if ((rc = as_tag(pubkey_der, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL)) - != ASN1_SUCCESS) goto asn_err; - -/* read two integers */ -DEBUG(D_acl) stage = US"MPI"; -if ( (errstr = as_mpi(pubkey_der, &verify_ctx->n)) - || (errstr = as_mpi(pubkey_der, &verify_ctx->e)) - ) - return errstr; - -DEBUG(D_acl) debug_printf_indent("rsa_verify_init:\n"); - { - uschar * s; - gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, verify_ctx->n); - debug_printf_indent(" N : %s\n", s); - gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, verify_ctx->e); - debug_printf_indent(" E : %s\n", s); - } - -return NULL; - -asn_err: -DEBUG(D_acl) return string_sprintf("%s: %s", stage, asn1_strerror(rc)); - return US asn1_strerror(rc); -} - - -/* verify signature (of hash) (given pubkey & alleged sig) -Return: NULL for success, or an error string */ - -const uschar * -exim_rsa_verify(ev_ctx * verify_ctx, BOOL is_sha1, blob * data_hash, blob * sig) -{ -/* -cf. libgnutls 2.8.5 _wrap_gcry_pk_verify() -*/ -gcry_mpi_t m_sig; -gcry_sexp_t s_sig = NULL, s_hash = NULL, s_pkey = NULL; -gcry_error_t gerr; -uschar * stage; - -if ( (stage = US"pkey sexp build", - gerr = gcry_sexp_build (&s_pkey, NULL, "(public-key(rsa(n%m)(e%m)))", - verify_ctx->n, verify_ctx->e)) - || (stage = US"data sexp build", - gerr = gcry_sexp_build (&s_hash, NULL, - is_sha1 - ? "(data(flags pkcs1)(hash sha1 %b))" - : "(data(flags pkcs1)(hash sha256 %b))", - (int) data_hash->len, CS data_hash->data)) - || (stage = US"sig mpi scan", - gerr = gcry_mpi_scan(&m_sig, GCRYMPI_FMT_USG, sig->data, sig->len, NULL)) - || (stage = US"sig sexp build", - gerr = gcry_sexp_build (&s_sig, NULL, "(sig-val(rsa(s%m)))", m_sig)) - || (stage = US"verify", - gerr = gcry_pk_verify (s_sig, s_hash, s_pkey)) - ) - { - DEBUG(D_acl) debug_printf_indent("verify: error in stage '%s'\n", stage); - return US gcry_strerror(gerr); - } - -if (s_sig) gcry_sexp_release (s_sig); -if (s_hash) gcry_sexp_release (s_hash); -if (s_pkey) gcry_sexp_release (s_pkey); -gcry_mpi_release (m_sig); -gcry_mpi_release (verify_ctx->n); -gcry_mpi_release (verify_ctx->e); - -return NULL; -} - - - - -#elif defined(RSA_OPENSSL) -/******************************************************************************/ - -void -exim_rsa_init(void) -{ -} - - -/* accumulate data (gnutls-only) */ -blob * -exim_rsa_data_append(blob * b, int * alloc, uschar * s) -{ -return b; /*dummy*/ -} - - -/* import private key from PEM string in memory. -Return: NULL for success, or an error string */ - -const uschar * -exim_rsa_signing_init(uschar * privkey_pem, es_ctx * sign_ctx) -{ -uschar * p, * q; -int len; - -/* Convert PEM to DER */ -if ( !(p = Ustrstr(privkey_pem, "-----BEGIN RSA PRIVATE KEY-----")) - || !(q = Ustrstr(p+=31, "-----END RSA PRIVATE KEY-----")) - ) - return US"Bad PEM wrapping"; - -*q = '\0'; -if ((len = b64decode(p, &p)) < 0) - return US"b64decode failed"; - -if (!(sign_ctx->rsa = d2i_RSAPrivateKey(NULL, CUSS &p, len))) - { - char ssl_errstring[256]; - ERR_load_crypto_strings(); /*XXX move to a startup routine */ - ERR_error_string(ERR_get_error(), ssl_errstring); - return string_copy(US ssl_errstring); - } - -return NULL; -} - - - -/* allocate mem for signature (when signing) */ -/* sign data (gnutls_only) -OR -sign hash. - -Return: NULL for success, or an error string */ - -const uschar * -exim_rsa_sign(es_ctx * sign_ctx, BOOL is_sha1, blob * data, blob * sig) -{ -uint len; -const uschar * ret = NULL; - -/* Allocate mem for signature */ -len = RSA_size(sign_ctx->rsa); -sig->data = store_get(len); -sig->len = len; - -/* Do signing */ -if (RSA_sign(is_sha1 ? NID_sha1 : NID_sha256, - CUS data->data, data->len, - US sig->data, &len, sign_ctx->rsa) != 1) - { - char ssl_errstring[256]; - ERR_load_crypto_strings(); /*XXX move to a startup routine */ - ERR_error_string(ERR_get_error(), ssl_errstring); - ret = string_copy(US ssl_errstring); - } - -RSA_free(sign_ctx->rsa); -return ret;; -} - - - -/* import public key (from DER in memory) -Return: nULL for success, or an error string */ - -const uschar * -exim_rsa_verify_init(blob * pubkey_der, ev_ctx * verify_ctx) -{ -const uschar * p = CUS pubkey_der->data; -const uschar * ret = NULL; - -if (!(verify_ctx->rsa = d2i_RSA_PUBKEY(NULL, &p, (long) pubkey_der->len))) - { - char ssl_errstring[256]; - ERR_load_crypto_strings(); /*XXX move to a startup routine */ - ERR_error_string(ERR_get_error(), ssl_errstring); - ret = string_copy(CUS ssl_errstring); - } -return ret; -} - - - - -/* verify signature (of hash) (given pubkey & alleged sig) -Return: NULL for success, or an error string */ - -const uschar * -exim_rsa_verify(ev_ctx * verify_ctx, BOOL is_sha1, blob * data_hash, blob * sig) -{ -const uschar * ret = NULL; - -if (RSA_verify(is_sha1 ? NID_sha1 : NID_sha256, - CUS data_hash->data, data_hash->len, - US sig->data, (uint) sig->len, verify_ctx->rsa) != 1) - { - char ssl_errstring[256]; - ERR_load_crypto_strings(); /*XXX move to a startup routine */ - ERR_error_string(ERR_get_error(), ssl_errstring); - ret = string_copy(US ssl_errstring); - } -return ret; -} - - -#endif -/******************************************************************************/ - -#endif /*DISABLE_DKIM*/ -/* End of File */ diff --git a/src/src/pdkim/rsa.h b/src/src/pdkim/rsa.h deleted file mode 100644 index 6018eba64..000000000 --- a/src/src/pdkim/rsa.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * PDKIM - a RFC4871 (DKIM) implementation - * - * Copyright (C) 2016 Exim maintainers - * - * RSA signing/verification interface - */ - -#include "../exim.h" - -#ifndef DISABLE_DKIM /* entire file */ - -#include "crypt_ver.h" - -#ifdef RSA_OPENSSL -# include -# include -# include -#elif defined(RSA_GNUTLS) -# include -# include -# include -#elif defined(RSA_GCRYPT) -# include -# include -#endif - -#include "../blob.h" - - -#ifdef RSA_OPENSSL - -typedef struct { - RSA * rsa; -} es_ctx; - -typedef struct { - RSA * rsa; -} ev_ctx; - -#elif defined(RSA_GNUTLS) - -typedef struct { - gnutls_x509_privkey_t rsa; -} es_ctx; - -typedef struct { - gnutls_pubkey_t rsa; -} ev_ctx; - -#elif defined(RSA_GCRYPT) - -typedef struct { - gcry_mpi_t n; - gcry_mpi_t e; - gcry_mpi_t d; - gcry_mpi_t p; - gcry_mpi_t q; - gcry_mpi_t dp; - gcry_mpi_t dq; - gcry_mpi_t qp; -} es_ctx; - -typedef struct { - gcry_mpi_t n; - gcry_mpi_t e; -} ev_ctx; - -#endif - - -extern void exim_rsa_init(void); -extern blob * exim_rsa_data_append(blob *, int *, uschar *); - -extern const uschar * exim_rsa_signing_init(uschar *, es_ctx *); -extern const uschar * exim_rsa_sign(es_ctx *, BOOL, blob *, blob *); -extern const uschar * exim_rsa_verify_init(blob *, ev_ctx *); -extern const uschar * exim_rsa_verify(ev_ctx *, BOOL, blob *, blob *); - -#endif /*DISABLE_DKIM*/ -/* End of File */ diff --git a/src/src/pdkim/signing.c b/src/src/pdkim/signing.c new file mode 100644 index 000000000..bcd64fdc4 --- /dev/null +++ b/src/src/pdkim/signing.c @@ -0,0 +1,707 @@ +/* + * PDKIM - a RFC4871 (DKIM) implementation + * + * Copyright (C) 2016 Exim maintainers + * + * RSA signing/verification interface +XXX rename interfaces to cover all signature methods. +the method (algo) needs to be extracted from the supplied private-key +and not only stashed as needed in the sign- or verify- context, but +indicated to caller for protocol tag construction. + */ + +#include "../exim.h" + +#ifndef DISABLE_DKIM /* entire file */ + +#ifndef SUPPORT_TLS +# error Need SUPPORT_TLS for DKIM +#endif + +#include "crypt_ver.h" +#include "signing.h" + + +/******************************************************************************/ +#ifdef RSA_GNUTLS + +void +exim_dkim_init(void) +{ +} + + +/* accumulate data (gnutls-only). String to be appended must be nul-terminated. */ +blob * +exim_dkim_data_append(blob * b, int * alloc, uschar * s) +{ +int len = b->len; +b->data = string_append(b->data, alloc, &len, 1, s); +b->len = len; +return b; +} + + + +/* import private key from PEM string in memory. +Return: NULL for success, or an error string */ + +const uschar * +exim_dkim_signing_init(uschar * privkey_pem, es_ctx * sign_ctx) +{ +gnutls_datum_t k; +int rc; + +k.data = privkey_pem; +k.size = strlen(privkey_pem); + +if ( (rc = gnutls_x509_privkey_init(&sign_ctx->rsa)) != GNUTLS_E_SUCCESS + || (rc = gnutls_x509_privkey_import(sign_ctx->rsa, &k, + GNUTLS_X509_FMT_PEM)) != GNUTLS_E_SUCCESS + ) + return gnutls_strerror(rc); + +return NULL; +} + + + +/* allocate mem for signature (when signing) */ +/* sign data (gnutls_only) +OR +sign hash. + +Return: NULL for success, or an error string */ + +const uschar * +exim_dkim_sign(es_ctx * sign_ctx, BOOL is_sha1, blob * data, blob * sig) +{ +gnutls_datum_t k; +size_t sigsize = 0; +int rc; +const uschar * ret = NULL; + +/* Allocate mem for signature */ +k.data = data->data; +k.size = data->len; +(void) gnutls_x509_privkey_sign_data(sign_ctx->rsa, + is_sha1 ? GNUTLS_DIG_SHA1 : GNUTLS_DIG_SHA256, + 0, &k, NULL, &sigsize); + +sig->data = store_get(sigsize); +sig->len = sigsize; + +/* Do signing */ +/*XXX will need extension for hash type; looks ok for non-RSA algos +so long as the privkey_import stage got them. */ +if ((rc = gnutls_x509_privkey_sign_data(sign_ctx->rsa, + is_sha1 ? GNUTLS_DIG_SHA1 : GNUTLS_DIG_SHA256, + 0, &k, sig->data, &sigsize)) != GNUTLS_E_SUCCESS + ) + ret = gnutls_strerror(rc); + +gnutls_x509_privkey_deinit(sign_ctx->rsa); +return ret; +} + + + +/* import public key (from DER in memory) +Return: NULL for success, or an error string */ + +const uschar * +exim_dkim_verify_init(blob * pubkey_der, ev_ctx * verify_ctx) +{ +gnutls_datum_t k; +int rc; +const uschar * ret = NULL; + +gnutls_pubkey_init(&verify_ctx->rsa); + +k.data = pubkey_der->data; +k.size = pubkey_der->len; + +if ((rc = gnutls_pubkey_import(verify_ctx->rsa, &k, GNUTLS_X509_FMT_DER)) + != GNUTLS_E_SUCCESS) + ret = gnutls_strerror(rc); +return ret; +} + + +/* verify signature (of hash) (given pubkey & alleged sig) +Return: NULL for success, or an error string */ + +const uschar * +exim_dkim_verify(ev_ctx * verify_ctx, BOOL is_sha1, blob * data_hash, blob * sig) +{ +gnutls_datum_t k, s; +int rc; +const uschar * ret = NULL; + +k.data = data_hash->data; +k.size = data_hash->len; +s.data = sig->data; +s.size = sig->len; +if ((rc = gnutls_pubkey_verify_hash2(verify_ctx->rsa, +/*XXX needs extension for SHA512 */ + is_sha1 ? GNUTLS_SIGN_RSA_SHA1 : GNUTLS_SIGN_RSA_SHA256, + 0, &k, &s)) < 0) + ret = gnutls_strerror(rc); + +gnutls_pubkey_deinit(verify_ctx->rsa); +return ret; +} + + + + +#elif defined(RSA_GCRYPT) +/******************************************************************************/ + + +/* Internal service routine: +Read and move past an asn.1 header, checking class & tag, +optionally returning the data-length */ + +static int +as_tag(blob * der, uschar req_cls, long req_tag, long * alen) +{ +int rc; +uschar tag_class; +int taglen; +long tag, len; + +/* debug_printf_indent("as_tag: %02x %02x %02x %02x\n", + der->data[0], der->data[1], der->data[2], der->data[3]); */ + +if ((rc = asn1_get_tag_der(der->data++, der->len--, &tag_class, &taglen, &tag)) + != ASN1_SUCCESS) + return rc; + +if (tag_class != req_cls || tag != req_tag) return ASN1_ELEMENT_NOT_FOUND; + +if ((len = asn1_get_length_der(der->data, der->len, &taglen)) < 0) + return ASN1_DER_ERROR; +if (alen) *alen = len; + +/* debug_printf_indent("as_tag: tlen %d dlen %d\n", taglen, (int)len); */ + +der->data += taglen; +der->len -= taglen; +return rc; +} + +/* Internal service routine: +Read and move over an asn.1 integer, setting an MPI to the value +*/ + +static uschar * +as_mpi(blob * der, gcry_mpi_t * mpi) +{ +long alen; +int rc; +gcry_error_t gerr; + +/* integer; move past the header */ +if ((rc = as_tag(der, 0, ASN1_TAG_INTEGER, &alen)) != ASN1_SUCCESS) + return US asn1_strerror(rc); + +/* read to an MPI */ +if ((gerr = gcry_mpi_scan(mpi, GCRYMPI_FMT_STD, der->data, alen, NULL))) + return US gcry_strerror(gerr); + +/* move over the data */ +der->data += alen; der->len -= alen; +return NULL; +} + + + +void +exim_dkim_init(void) +{ +/* Version check should be the very first call because it +makes sure that important subsystems are initialized. */ +if (!gcry_check_version (GCRYPT_VERSION)) + { + fputs ("libgcrypt version mismatch\n", stderr); + exit (2); + } + +/* We don't want to see any warnings, e.g. because we have not yet +parsed program options which might be used to suppress such +warnings. */ +gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); + +/* ... If required, other initialization goes here. Note that the +process might still be running with increased privileges and that +the secure memory has not been initialized. */ + +/* Allocate a pool of 16k secure memory. This make the secure memory +available and also drops privileges where needed. */ +gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); + +/* It is now okay to let Libgcrypt complain when there was/is +a problem with the secure memory. */ +gcry_control (GCRYCTL_RESUME_SECMEM_WARN); + +/* ... If required, other initialization goes here. */ + +/* Tell Libgcrypt that initialization has completed. */ +gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); + +return; +} + + + + +/* Accumulate data (gnutls-only). +String to be appended must be nul-terminated. */ + +blob * +exim_dkim_data_append(blob * b, int * alloc, uschar * s) +{ +return b; /*dummy*/ +} + + + +/* import private key from PEM string in memory. +Return: NULL for success, or an error string */ + +const uschar * +exim_dkim_signing_init(uschar * privkey_pem, es_ctx * sign_ctx) +{ +uschar * s1, * s2; +blob der; +long alen; +int rc; + +/*XXX will need extension to _spot_ as well as handle a +non-RSA key? I think... */ + +/* + * RSAPrivateKey ::= SEQUENCE + * version Version, + * modulus INTEGER, -- n + * publicExponent INTEGER, -- e + * privateExponent INTEGER, -- d + * prime1 INTEGER, -- p + * prime2 INTEGER, -- q + * exponent1 INTEGER, -- d mod (p-1) + * exponent2 INTEGER, -- d mod (q-1) + * coefficient INTEGER, -- (inverse of q) mod p + * otherPrimeInfos OtherPrimeInfos OPTIONAL + */ + +if ( !(s1 = Ustrstr(CS privkey_pem, "-----BEGIN RSA PRIVATE KEY-----")) + || !(s2 = Ustrstr(CS (s1+=31), "-----END RSA PRIVATE KEY-----" )) + ) + return US"Bad PEM wrapper"; + +*s2 = '\0'; + +if ((der.len = b64decode(s1, &der.data)) < 0) + return US"Bad PEM-DER b64 decode"; + +/* untangle asn.1 */ + +/* sequence; just move past the header */ +if ((rc = as_tag(&der, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL)) + != ASN1_SUCCESS) goto asn_err; + +/* integer version; move past the header, check is zero */ +if ((rc = as_tag(&der, 0, ASN1_TAG_INTEGER, &alen)) != ASN1_SUCCESS) + goto asn_err; +if (alen != 1 || *der.data != 0) + return US"Bad version number"; +der.data++; der.len--; + +if ( (s1 = as_mpi(&der, &sign_ctx->n)) + || (s1 = as_mpi(&der, &sign_ctx->e)) + || (s1 = as_mpi(&der, &sign_ctx->d)) + || (s1 = as_mpi(&der, &sign_ctx->p)) + || (s1 = as_mpi(&der, &sign_ctx->q)) + || (s1 = as_mpi(&der, &sign_ctx->dp)) + || (s1 = as_mpi(&der, &sign_ctx->dq)) + || (s1 = as_mpi(&der, &sign_ctx->qp)) + ) + return s1; + +DEBUG(D_acl) debug_printf_indent("rsa_signing_init:\n"); + { + uschar * s; + gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->n); + debug_printf_indent(" N : %s\n", s); + gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->e); + debug_printf_indent(" E : %s\n", s); + gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->d); + debug_printf_indent(" D : %s\n", s); + gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->p); + debug_printf_indent(" P : %s\n", s); + gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->q); + debug_printf_indent(" Q : %s\n", s); + gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->dp); + debug_printf_indent(" DP: %s\n", s); + gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->dq); + debug_printf_indent(" DQ: %s\n", s); + gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->qp); + debug_printf_indent(" QP: %s\n", s); + } +return NULL; + +asn_err: return US asn1_strerror(rc); +} + + + +/* allocate mem for signature (when signing) */ +/* sign data (gnutls_only) +OR +sign hash. + +Return: NULL for success, or an error string */ + +const uschar * +exim_dkim_sign(es_ctx * sign_ctx, BOOL is_sha1, blob * data, blob * sig) +{ +gcry_sexp_t s_hash = NULL, s_key = NULL, s_sig = NULL; +gcry_mpi_t m_sig; +uschar * errstr; +gcry_error_t gerr; + +/*XXX will need extension for hash types (though, possibly, should +be re-specced to not rehash but take an already-hashed value? Actually +current impl looks WRONG - it _is_ given a has so should not be +re-hashing. Has this been tested? + +Will need extension for non-RSA sugning algos. */ + +#define SIGSPACE 128 +sig->data = store_get(SIGSPACE); + +if (gcry_mpi_cmp (sign_ctx->p, sign_ctx->q) > 0) + { + gcry_mpi_swap (sign_ctx->p, sign_ctx->q); + gcry_mpi_invm (sign_ctx->qp, sign_ctx->p, sign_ctx->q); + } + +if ( (gerr = gcry_sexp_build (&s_key, NULL, + "(private-key (rsa (n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))", + sign_ctx->n, sign_ctx->e, + sign_ctx->d, sign_ctx->p, + sign_ctx->q, sign_ctx->qp)) + || (gerr = gcry_sexp_build (&s_hash, NULL, + is_sha1 + ? "(data(flags pkcs1)(hash sha1 %b))" + : "(data(flags pkcs1)(hash sha256 %b))", + (int) data->len, CS data->data)) + || (gerr = gcry_pk_sign (&s_sig, s_hash, s_key)) + ) + return US gcry_strerror(gerr); + +/* gcry_sexp_dump(s_sig); */ + +if ( !(s_sig = gcry_sexp_find_token(s_sig, "s", 0)) + ) + return US"no sig result"; + +m_sig = gcry_sexp_nth_mpi(s_sig, 1, GCRYMPI_FMT_USG); + +DEBUG(D_acl) + { + uschar * s; + gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, m_sig); + debug_printf_indent(" SG: %s\n", s); + } + +gerr = gcry_mpi_print(GCRYMPI_FMT_USG, sig->data, SIGSPACE, &sig->len, m_sig); +if (gerr) + { + debug_printf_indent("signature conversion from MPI to buffer failed\n"); + return US gcry_strerror(gerr); + } +#undef SIGSPACE + +return NULL; +} + + +/* import public key (from DER in memory) +Return: NULL for success, or an error string */ + +const uschar * +exim_dkim_verify_init(blob * pubkey_der, ev_ctx * verify_ctx) +{ +/* +in code sequence per b81207d2bfa92 rsa_parse_public_key() and asn1_get_mpi() +*/ +uschar tag_class; +int taglen; +long alen; +int rc; +uschar * errstr; +gcry_error_t gerr; +uschar * stage = US"S1"; + +/* +sequence + sequence + OBJECT:rsaEncryption + NULL + BIT STRING:RSAPublicKey + sequence + INTEGER:Public modulus + INTEGER:Public exponent + +openssl rsa -in aux-fixed/dkim/dkim.private -pubout -outform DER | od -t x1 | head; +openssl rsa -in aux-fixed/dkim/dkim.private -pubout | openssl asn1parse -dump; +openssl rsa -in aux-fixed/dkim/dkim.private -pubout | openssl asn1parse -dump -offset 22; +*/ + +/* sequence; just move past the header */ +if ((rc = as_tag(pubkey_der, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL)) + != ASN1_SUCCESS) goto asn_err; + +/* sequence; skip the entire thing */ +DEBUG(D_acl) stage = US"S2"; +if ((rc = as_tag(pubkey_der, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, &alen)) + != ASN1_SUCCESS) goto asn_err; +pubkey_der->data += alen; pubkey_der->len -= alen; + + +/* bitstring: limit range to size of bitstring; +move over header + content wrapper */ +DEBUG(D_acl) stage = US"BS"; +if ((rc = as_tag(pubkey_der, 0, ASN1_TAG_BIT_STRING, &alen)) != ASN1_SUCCESS) + goto asn_err; +pubkey_der->len = alen; +pubkey_der->data++; pubkey_der->len--; + +/* sequence; just move past the header */ +DEBUG(D_acl) stage = US"S3"; +if ((rc = as_tag(pubkey_der, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL)) + != ASN1_SUCCESS) goto asn_err; + +/* read two integers */ +DEBUG(D_acl) stage = US"MPI"; +if ( (errstr = as_mpi(pubkey_der, &verify_ctx->n)) + || (errstr = as_mpi(pubkey_der, &verify_ctx->e)) + ) + return errstr; + +DEBUG(D_acl) debug_printf_indent("rsa_verify_init:\n"); + { + uschar * s; + gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, verify_ctx->n); + debug_printf_indent(" N : %s\n", s); + gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, verify_ctx->e); + debug_printf_indent(" E : %s\n", s); + } + +return NULL; + +asn_err: +DEBUG(D_acl) return string_sprintf("%s: %s", stage, asn1_strerror(rc)); + return US asn1_strerror(rc); +} + + +/* verify signature (of hash) (given pubkey & alleged sig) +Return: NULL for success, or an error string */ + +const uschar * +exim_dkim_verify(ev_ctx * verify_ctx, BOOL is_sha1, blob * data_hash, blob * sig) +{ +/* +cf. libgnutls 2.8.5 _wrap_gcry_pk_verify() +*/ +gcry_mpi_t m_sig; +gcry_sexp_t s_sig = NULL, s_hash = NULL, s_pkey = NULL; +gcry_error_t gerr; +uschar * stage; + +if ( (stage = US"pkey sexp build", + gerr = gcry_sexp_build (&s_pkey, NULL, "(public-key(rsa(n%m)(e%m)))", + verify_ctx->n, verify_ctx->e)) + || (stage = US"data sexp build", + gerr = gcry_sexp_build (&s_hash, NULL, +/*XXX needs extension for SHA512 */ + is_sha1 + ? "(data(flags pkcs1)(hash sha1 %b))" + : "(data(flags pkcs1)(hash sha256 %b))", + (int) data_hash->len, CS data_hash->data)) + || (stage = US"sig mpi scan", + gerr = gcry_mpi_scan(&m_sig, GCRYMPI_FMT_USG, sig->data, sig->len, NULL)) + || (stage = US"sig sexp build", + gerr = gcry_sexp_build (&s_sig, NULL, "(sig-val(rsa(s%m)))", m_sig)) + || (stage = US"verify", + gerr = gcry_pk_verify (s_sig, s_hash, s_pkey)) + ) + { + DEBUG(D_acl) debug_printf_indent("verify: error in stage '%s'\n", stage); + return US gcry_strerror(gerr); + } + +if (s_sig) gcry_sexp_release (s_sig); +if (s_hash) gcry_sexp_release (s_hash); +if (s_pkey) gcry_sexp_release (s_pkey); +gcry_mpi_release (m_sig); +gcry_mpi_release (verify_ctx->n); +gcry_mpi_release (verify_ctx->e); + +return NULL; +} + + + + +#elif defined(RSA_OPENSSL) +/******************************************************************************/ + +void +exim_dkim_init(void) +{ +} + + +/* accumulate data (gnutls-only) */ +blob * +exim_dkim_data_append(blob * b, int * alloc, uschar * s) +{ +return b; /*dummy*/ +} + + +/* import private key from PEM string in memory. +Return: NULL for success, or an error string */ + +const uschar * +exim_dkim_signing_init(uschar * privkey_pem, es_ctx * sign_ctx) +{ +uschar * p, * q; +int len; + +/*XXX maybe use PEM_read_bio_PrivateKey() ??? +The sign_ctx would need to have an EVP_PKEY* */ + +/* Convert PEM to DER */ +if ( !(p = Ustrstr(privkey_pem, "-----BEGIN RSA PRIVATE KEY-----")) + || !(q = Ustrstr(p+=31, "-----END RSA PRIVATE KEY-----")) + ) + return US"Bad PEM wrapping"; + +*q = '\0'; +if ((len = b64decode(p, &p)) < 0) + return US"b64decode failed"; + +if (!(sign_ctx->rsa = d2i_RSAPrivateKey(NULL, CUSS &p, len))) + { + char ssl_errstring[256]; + ERR_load_crypto_strings(); /*XXX move to a startup routine */ + ERR_error_string(ERR_get_error(), ssl_errstring); + return string_copy(US ssl_errstring); + } + +return NULL; +} + + + +/* allocate mem for signature (when signing) */ +/* sign data (gnutls_only) +OR +sign hash. + +Return: NULL for success, or an error string */ + +const uschar * +exim_dkim_sign(es_ctx * sign_ctx, BOOL is_sha1, blob * data, blob * sig) +{ +uint len; +const uschar * ret = NULL; + +/*XXX will need extension for non-RSA signing algo. Maybe use +https://www.openssl.org/docs/man1.0.2/crypto/EVP_PKEY_sign.html ??? */ + +/* Allocate mem for signature */ +len = RSA_size(sign_ctx->rsa); +sig->data = store_get(len); +sig->len = len; + +/* Do signing */ +if (RSA_sign(is_sha1 ? NID_sha1 : NID_sha256, + CUS data->data, data->len, + US sig->data, &len, sign_ctx->rsa) != 1) + { + char ssl_errstring[256]; + ERR_load_crypto_strings(); /*XXX move to a startup routine */ + ERR_error_string(ERR_get_error(), ssl_errstring); + ret = string_copy(US ssl_errstring); + } + +RSA_free(sign_ctx->rsa); +return ret;; +} + + + +/* import public key (from DER in memory) +Return: NULL for success, or an error string */ + +const uschar * +exim_dkim_verify_init(blob * pubkey_der, ev_ctx * verify_ctx) +{ +const uschar * p = CUS pubkey_der->data; +const uschar * ret = NULL; + +/*XXX d2i_X509_PUBKEY, X509_get_pubkey(), and an EVP_PKEY* in verify_ctx. */ + +if (!(verify_ctx->rsa = d2i_RSA_PUBKEY(NULL, &p, (long) pubkey_der->len))) + { + char ssl_errstring[256]; + ERR_load_crypto_strings(); /*XXX move to a startup routine */ + ERR_error_string(ERR_get_error(), ssl_errstring); + ret = string_copy(CUS ssl_errstring); + } +return ret; +} + + + + +/* verify signature (of hash) (given pubkey & alleged sig) +Return: NULL for success, or an error string */ + +const uschar * +exim_dkim_verify(ev_ctx * verify_ctx, BOOL is_sha1, blob * data_hash, blob * sig) +{ +const uschar * ret = NULL; + +/*XXX needs extension for SHA512, Possibly EVP_PKEY_verify() is all we need??? */ +/* with EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) +- see example code at https://www.openssl.org/docs/man1.0.2/crypto/EVP_PKEY_verify.html +and maybe also EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256()) though unclear +if that only sets a pad/type field byte value, or sets up for an actual hash operation... +Same on the signing side. +https://www.openssl.org/docs/manmaster/man3/EVP_PKEY_CTX_set_rsa_padding.html says it does what we want. */ + +if (RSA_verify(is_sha1 ? NID_sha1 : NID_sha256, + CUS data_hash->data, data_hash->len, + US sig->data, (uint) sig->len, verify_ctx->rsa) != 1) + { + char ssl_errstring[256]; + ERR_load_crypto_strings(); /*XXX move to a startup routine */ + ERR_error_string(ERR_get_error(), ssl_errstring); + ret = string_copy(US ssl_errstring); + } +return ret; +} + + +#endif +/******************************************************************************/ + +#endif /*DISABLE_DKIM*/ +/* End of File */ diff --git a/src/src/pdkim/signing.h b/src/src/pdkim/signing.h new file mode 100644 index 000000000..4e8580859 --- /dev/null +++ b/src/src/pdkim/signing.h @@ -0,0 +1,81 @@ +/* + * PDKIM - a RFC4871 (DKIM) implementation + * + * Copyright (C) 2016 Exim maintainers + * + * RSA signing/verification interface + */ + +#include "../exim.h" + +#ifndef DISABLE_DKIM /* entire file */ + +#include "crypt_ver.h" + +#ifdef RSA_OPENSSL +# include +# include +# include +#elif defined(RSA_GNUTLS) +# include +# include +# include +#elif defined(RSA_GCRYPT) +# include +# include +#endif + +#include "../blob.h" + + +#ifdef RSA_OPENSSL + +typedef struct { + RSA * rsa; +} es_ctx; + +typedef struct { + RSA * rsa; +} ev_ctx; + +#elif defined(RSA_GNUTLS) + +typedef struct { + gnutls_x509_privkey_t rsa; +} es_ctx; + +typedef struct { + gnutls_pubkey_t rsa; +} ev_ctx; + +#elif defined(RSA_GCRYPT) + +typedef struct { + gcry_mpi_t n; + gcry_mpi_t e; + gcry_mpi_t d; + gcry_mpi_t p; + gcry_mpi_t q; + gcry_mpi_t dp; + gcry_mpi_t dq; + gcry_mpi_t qp; +} es_ctx; + +typedef struct { + gcry_mpi_t n; + gcry_mpi_t e; +} ev_ctx; + +#endif + + +extern void exim_dkim_init(void); +extern blob * exim_dkim_data_append(blob *, int *, uschar *); + +extern const uschar * exim_dkim_signing_init(uschar *, es_ctx *); +extern const uschar * exim_dkim_sign(es_ctx *, BOOL, blob *, blob *); +extern const uschar * exim_dkim_verify_init(blob *, ev_ctx *); +extern const uschar * exim_dkim_verify(ev_ctx *, BOOL, blob *, blob *); + +#endif /*DISABLE_DKIM*/ +/* End of File */