DKIM: rename internal signing api
authorJeremy Harris <jgh146exb@wizmail.org>
Fri, 19 May 2017 12:32:53 +0000 (13:32 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Tue, 12 Sep 2017 16:08:45 +0000 (17:08 +0100)
src/scripts/MakeLinks
src/src/dkim.c
src/src/hash.c
src/src/pdkim/Makefile
src/src/pdkim/pdkim.c
src/src/pdkim/rsa.c [deleted file]
src/src/pdkim/rsa.h [deleted file]
src/src/pdkim/signing.c [new file with mode: 0644]
src/src/pdkim/signing.h [new file with mode: 0644]

index 44e3a4ebc0e5f8764ebf932d4c5cf9e1aabeeddc..22e5a4bd74b087572213995ea35b138bbc7e6f5b 100755 (executable)
@@ -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
index 2857e63980cc2eedfdf54d75b1819931a3dff253..f7b9ee0d18905d7cf9c8d213cdd69718b2c3eef6 100644 (file)
@@ -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,
index f49add29c74ff0949fce90cf861f75327128c619..e239516e1c380f3c5a5f02502271ebfaf58405d2 100644 (file)
@@ -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;
index c298568ead615987de814eb4b664b59fda00aed0..10631ceaada06b7fb83e9343dab7743fdb427e45 100644 (file)
@@ -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
index 2ce0ff6c9be4c193b6f2a50ea854b389e4b1c48f..441f96cb55b2217cac5c4b54fa8aedfad65e6d23 100644 (file)
@@ -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 (file)
index 950c617..0000000
+++ /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 (file)
index 6018eba..0000000
+++ /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 <openssl/rsa.h>
-# include <openssl/ssl.h>
-# include <openssl/err.h>
-#elif defined(RSA_GNUTLS)
-# include <gnutls/gnutls.h>
-# include <gnutls/x509.h>
-#  include <gnutls/abstract.h>
-#elif defined(RSA_GCRYPT)
-#  include <gcrypt.h>
-#  include <libtasn1.h>
-#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 (file)
index 0000000..bcd64fd
--- /dev/null
@@ -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 (file)
index 0000000..4e85808
--- /dev/null
@@ -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 <openssl/rsa.h>
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+#elif defined(RSA_GNUTLS)
+# include <gnutls/gnutls.h>
+# include <gnutls/x509.h>
+#  include <gnutls/abstract.h>
+#elif defined(RSA_GCRYPT)
+#  include <gcrypt.h>
+#  include <libtasn1.h>
+#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 */