DKIM: support multiple hash methods
authorJeremy Harris <jgh146exb@wizmail.org>
Tue, 12 Sep 2017 16:37:48 +0000 (17:37 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Tue, 12 Sep 2017 19:01:05 +0000 (20:01 +0100)
22 files changed:
doc/doc-docbook/spec.xfpt
doc/doc-txt/NewStuff
src/src/dkim.c
src/src/expand.c
src/src/hash.c
src/src/hash.h
src/src/pdkim/crypt_ver.h
src/src/pdkim/pdkim.c
src/src/pdkim/pdkim.h
src/src/pdkim/pdkim_hash.h
src/src/pdkim/signing.c
src/src/pdkim/signing.h
src/src/structs.h
src/src/transports/smtp.c
test/confs/4503 [new symlink]
test/confs/4520
test/confs/4523 [new symlink]
test/log/4503 [new file with mode: 0644]
test/log/4523 [new file with mode: 0644]
test/runtest
test/scripts/4500-DKIM/4503 [new file with mode: 0644]
test/scripts/4500-DKIM/4523 [new file with mode: 0644]

index 862c8f9..61a6f0e 100644 (file)
@@ -23799,6 +23799,7 @@ of the message. Its value must not be zero. See also &%final_timeout%&.
 .option dkim_canon smtp string&!! unset
 .option dkim_strict smtp string&!! unset
 .option dkim_sign_headers smtp string&!! unset
+.option dkim_hash smtp string&!! sha256
 DKIM signing options.  For details see section &<<SECDKIMSIGN>>&.
 
 
@@ -38569,6 +38570,12 @@ list of header names. Headers with these names will be included in the message
 signature.
 When unspecified, the header names recommended in RFC4871 will be used.
 
+.new
+.option dkim_hash smtp string&!! sha256
+Can be set alternatively to &"sha1"& to use an alternate hash
+method.  Note that sha1 is now condidered insecure, and deprecated.
+.wen
+
 
 .section "Verifying DKIM signatures in incoming mail" "SECID514"
 .cindex "DKIM" "verification"
index 8ea1708..c10649e 100644 (file)
@@ -52,6 +52,8 @@ Version 4.90
     is opened with a TFO cookie.  Support varies between platforms
     (Linux does both. FreeBSD server only, others unknown).
 
+13. DKIM support for multiple hashes.
+
 
 Version 4.89
 ------------
index f7b9ee0..2b7f55a 100644 (file)
@@ -20,6 +20,12 @@ pdkim_signature *dkim_signatures = NULL;
 pdkim_signature *dkim_cur_sig = NULL;
 static const uschar * dkim_collect_error = NULL;
 
+
+
+/*XXX the caller only uses the first record if we return multiple.
+Could we hand back an allocated string?
+*/
+
 static int
 dkim_exim_query_dns_txt(char *name, char *answer)
 {
@@ -164,9 +170,7 @@ for (sig = dkim_signatures; sig; sig = sig->next)
   logmsg = string_append(logmsg, &size, &ptr, 7, 
        " c=", sig->canon_headers == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
        "/",   sig->canon_body    == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
-       " a=", sig->algo == PDKIM_ALGO_RSA_SHA256
-               ? "rsa-sha256"
-               : sig->algo == PDKIM_ALGO_RSA_SHA1 ? "rsa-sha1" : "err",
+       " a=", dkim_sig_to_a_tag(sig),
        string_sprintf(" b=%d",
                        (int)sig->sighash.len > -1 ? sig->sighash.len * 8 : 0));
   if ((s= sig->identity)) logmsg = string_append(logmsg, &size, &ptr, 2, " i=", s);
@@ -338,12 +342,7 @@ if (!dkim_verify_ctx || dkim_disable_verify || !dkim_cur_sig)
 switch (what)
   {
   case DKIM_ALGO:
-    switch (dkim_cur_sig->algo)
-      {
-      case PDKIM_ALGO_RSA_SHA1:        return US"rsa-sha1";
-      case PDKIM_ALGO_RSA_SHA256:
-      default:                 return US"rsa-sha256";
-      }
+    return dkim_sig_to_a_tag(dkim_cur_sig);
 
   case DKIM_BODYLENGTH:
     return dkim_cur_sig->bodylength >= 0
@@ -466,6 +465,7 @@ int seen_items_offset = 0;
 uschar *dkim_canon_expanded;
 uschar *dkim_sign_headers_expanded;
 uschar *dkim_private_key_expanded;
+uschar *dkim_hash_expanded;
 pdkim_ctx *ctx = NULL;
 uschar *rc = NULL;
 uschar *sigbuf = NULL;
@@ -608,15 +608,20 @@ 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 (!(dkim_hash_expanded = expand_string(dkim->dkim_hash)))
+    {
+    log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
+              "dkim_hash: %s", expand_string_message);
+    goto bad;
+    }
+
+/*XXX so we currently nail signing to RSA + given hash.
+Need to extract algo from privkey and check for disallowed combos. */
 
-  if (!(ctx = pdkim_init_sign(CS dkim_signing_domain,
-                       CS dkim_signing_selector,
-                       CS dkim_private_key_expanded,
-                       PDKIM_ALGO_RSA_SHA256,
+  if (!(ctx = pdkim_init_sign(dkim_signing_domain,
+                       dkim_signing_selector,
+                       dkim_private_key_expanded,
+                       dkim_hash_expanded,
                        dkim->dot_stuffed,
                        &dkim_exim_query_dns_txt,
                        errstr
index 83e519a..c51c1ff 100644 (file)
@@ -6461,7 +6461,7 @@ while (*s != 0)
          blob b;
          char st[3];
 
-         if (!exim_sha_init(&h, HASH_SHA256))
+         if (!exim_sha_init(&h, HASH_SHA2_256))
            {
            expand_string_message = US"unrecognised sha256 variant";
            goto EXPAND_FAILED;
index e239516..19ab1ef 100644 (file)
@@ -36,9 +36,11 @@ 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;
-  case HASH_SHA256: h->hashlen = 32; SHA256_Init(&h->u.sha2); break;
-  default:         h->hashlen = 0; return FALSE;
+  case HASH_SHA1:     h->hashlen = 20; SHA1_Init  (&h->u.sha1);     break;
+  case HASH_SHA2_256: h->hashlen = 32; SHA256_Init(&h->u.sha2_256); break;
+  case HASH_SHA2_384: h->hashlen = 48; SHA384_Init(&h->u.sha2_512); break;
+  case HASH_SHA2_512: h->hashlen = 64; SHA512_Init(&h->u.sha2_512); break;
+  default:           h->hashlen = 0; return FALSE;
   }
 return TRUE;
 }
@@ -49,8 +51,10 @@ exim_sha_update(hctx * h, const uschar * data, int len)
 {
 switch (h->method)
   {
-  case HASH_SHA1:   SHA1_Update  (&h->u.sha1, data, len); break;
-  case HASH_SHA256: SHA256_Update(&h->u.sha2, data, len); break;
+  case HASH_SHA1:     SHA1_Update  (&h->u.sha1,     data, len); break;
+  case HASH_SHA2_256: SHA256_Update(&h->u.sha2_256, data, len); break;
+  case HASH_SHA2_384: SHA384_Update(&h->u.sha2_512, data, len); break;
+  case HASH_SHA2_512: SHA512_Update(&h->u.sha2_512, data, len); break;
   /* should be blocked by init not handling these, but be explicit to
   guard against accidents later (and hush up clang -Wswitch) */
   default: assert(0);
@@ -64,8 +68,10 @@ exim_sha_finish(hctx * h, blob * b)
 b->data = store_get(b->len = h->hashlen);
 switch (h->method)
   {
-  case HASH_SHA1:   SHA1_Final  (b->data, &h->u.sha1); break;
-  case HASH_SHA256: SHA256_Final(b->data, &h->u.sha2); break;
+  case HASH_SHA1:     SHA1_Final  (b->data, &h->u.sha1);     break;
+  case HASH_SHA2_256: SHA256_Final(b->data, &h->u.sha2_256); break;
+  case HASH_SHA2_384: SHA384_Final(b->data, &h->u.sha2_512); break;
+  case HASH_SHA2_512: SHA512_Final(b->data, &h->u.sha2_512); break;
   default: assert(0);
   }
 }
@@ -81,10 +87,14 @@ 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;
-  case HASH_SHA256:   h->hashlen = 32; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA256); break;
+  case HASH_SHA1:     h->hashlen = 20; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA1);   break;
+  case HASH_SHA2_256: h->hashlen = 32; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA256); break;
+  case HASH_SHA2_384: h->hashlen = 48; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA384); break;
+  case HASH_SHA2_512: h->hashlen = 64; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA512); break;
 #ifdef EXIM_HAVE_SHA3
   case HASH_SHA3_256: h->hashlen = 32; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA3_256); break;
+  case HASH_SHA3_384: h->hashlen = 48; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA3_384); break;
+  case HASH_SHA3_512: h->hashlen = 64; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA3_512); break;
 #endif
   default: h->hashlen = 0; return FALSE;
   }
@@ -117,8 +127,13 @@ 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;
-  case HASH_SHA256: h->hashlen = 32; gcry_md_open(&h->sha, GCRY_MD_SHA256, 0); break;
+  case HASH_SHA1:     h->hashlen = 20; gcry_md_open(&h->sha, GCRY_MD_SHA1, 0);   break;
+  case HASH_SHA2_256: h->hashlen = 32; gcry_md_open(&h->sha, GCRY_MD_SHA256, 0); break;
+  case HASH_SHA2_384: h->hashlen = 48; gcry_md_open(&h->sha, GCRY_MD_SHA384, 0); break;
+  case HASH_SHA2_512: h->hashlen = 64; gcry_md_open(&h->sha, GCRY_MD_SHA512, 0); break;
+  case HASH_SHA3_256: h->hashlen = 32; gcry_md_open(&h->sha, GCRY_MD_SHA3_256, 0); break;
+  case HASH_SHA3_384: h->hashlen = 48; gcry_md_open(&h->sha, GCRY_MD_SHA3_384, 0); break;
+  case HASH_SHA3_512: h->hashlen = 64; gcry_md_open(&h->sha, GCRY_MD_SHA3_512, 0); break;
   default:         h->hashlen = 0; return FALSE;
   }
 return TRUE;
@@ -152,7 +167,7 @@ exim_sha_init(hctx * h, hashmethod m)
 switch (h->method = m)
   {
   case HASH_SHA1:   h->hashlen = 20; sha1_starts(&h->u.sha1);    break;
-  case HASH_SHA256: h->hashlen = 32; sha2_starts(&h->u.sha2, 0); break;
+  case HASH_SHA2_256: h->hashlen = 32; sha2_starts(&h->u.sha2, 0); break;
   default:         h->hashlen = 0; return FALSE;
   }
 return TRUE;
@@ -165,7 +180,7 @@ exim_sha_update(hctx * h, const uschar * data, int len)
 switch (h->method)
   {
   case HASH_SHA1:   sha1_update(h->u.sha1, US data, len); break;
-  case HASH_SHA256: sha2_update(h->u.sha2, US data, len); break;
+  case HASH_SHA2_256: sha2_update(h->u.sha2, US data, len); break;
   }
 }
 
@@ -177,7 +192,7 @@ b->data = store_get(b->len = h->hashlen);
 switch (h->method)
   {
   case HASH_SHA1:   sha1_finish(h->u.sha1, b->data); break;
-  case HASH_SHA256: sha2_finish(h->u.sha2, b->data); break;
+  case HASH_SHA2_256: sha2_finish(h->u.sha2, b->data); break;
   }
 }
 
@@ -421,16 +436,6 @@ native_sha1_end(&h->sha1, NULL, 0, b->data);
 
 
 #endif
-/******************************************************************************/
-
-/* Common to all library versions */
-int
-exim_sha_hashlen(hctx * h)
-{
-return h->method == HASH_SHA1 ? 20
-     : h->method == HASH_SHA256 ? 32
-     : 0;
-}
 
 
 /******************************************************************************/
index 09b6594..b745e62 100644 (file)
 typedef enum hashmethod {
   HASH_BADTYPE,
   HASH_SHA1,
-  HASH_SHA256,
+
+  HASH_SHA2_256,
+  HASH_SHA2_384,
+  HASH_SHA2_512,
+
   HASH_SHA3_224,
   HASH_SHA3_256,
   HASH_SHA3_384,
@@ -46,7 +50,8 @@ typedef struct {
 #ifdef SHA_OPENSSL
   union {
     SHA_CTX      sha1;       /* SHA1 block                                */
-    SHA256_CTX   sha2;       /* SHA256 block                              */
+    SHA256_CTX   sha2_256;   /* SHA256 or 224 block                       */
+    SHA512_CTX   sha2_512;   /* SHA512 or 384 block                       */
   } u;
 
 #elif defined(SHA_GNUTLS)
@@ -70,7 +75,6 @@ typedef struct {
 extern BOOL     exim_sha_init(hctx *, hashmethod);
 extern void     exim_sha_update(hctx *, const uschar *a, int);
 extern void     exim_sha_finish(hctx *, blob *);
-extern int      exim_sha_hashlen(hctx *);
 
 #endif
 /* End of File */
index cd2171c..439d99b 100644 (file)
@@ -5,7 +5,7 @@
 /* Copyright (c) Jeremy Harris 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
-/* RSA and SHA routine selection for PDKIM */
+/* Signing and hashing routine selection for PDKIM */
 
 #include "../exim.h"
 #include "../sha_ver.h"
 # include <gnutls/gnutls.h>
 
 # if GNUTLS_VERSION_NUMBER >= 0x30000
-#  define RSA_GNUTLS
+#  define SIGN_GNUTLS
 # else
-#  define RSA_GCRYPT
+#  define SIGN_GCRYPT
 # endif
 
 #else
-# define RSA_OPENSSL
+# define SIGN_OPENSSL
 #endif
 
index 441f96c..bef6b6a 100644 (file)
 
 #include "crypt_ver.h"
 
-#ifdef RSA_OPENSSL
+#ifdef SIGN_OPENSSL
 # include <openssl/rsa.h>
 # include <openssl/ssl.h>
 # include <openssl/err.h>
-#elif defined(RSA_GNUTLS)
+#elif defined(SIGN_GNUTLS)
 # include <gnutls/gnutls.h>
 # include <gnutls/x509.h>
 #endif
@@ -73,26 +73,24 @@ const uschar * pdkim_querymethods[] = {
   US"dns/txt",
   NULL
 };
-const uschar * pdkim_algos[] = {
-  US"rsa-sha256",
-  US"rsa-sha1",
-  NULL
-};
 const uschar * pdkim_canons[] = {
   US"simple",
   US"relaxed",
   NULL
 };
-/*XXX currently unused */
-const uschar * pdkim_hashes[] = {
-  US"sha256",
-  US"sha1",
-  NULL
+
+typedef struct {
+  const uschar * dkim_hashname;
+  hashmethod    exim_hashmethod;
+} pdkim_hashtype;
+static const pdkim_hashtype pdkim_hashes[] = {
+  { US"sha1",   HASH_SHA1 },
+  { US"sha256", HASH_SHA2_256 },
+  { US"sha512", HASH_SHA2_512 }
 };
-/*XXX currently unused */
+
 const uschar * pdkim_keytypes[] = {
-  US"rsa",
-  NULL
+  US"rsa"
 };
 
 typedef struct pdkim_combined_canon_entry {
@@ -113,6 +111,17 @@ pdkim_combined_canon_entry pdkim_combined_canons[] = {
 
 
 /* -------------------------------------------------------------------------- */
+uschar *
+dkim_sig_to_a_tag(pdkim_signature * sig)
+{
+if (  sig->keytype < 0  || sig->keytype > nelem(pdkim_keytypes)
+   || sig->hashtype < 0 || sig->hashtype > nelem(pdkim_hashes))
+  return US"err";
+return string_sprintf("%s-%s",
+  pdkim_keytypes[sig->keytype], pdkim_hashes[sig->hashtype].dkim_hashname);
+}
+
+
 
 const char *
 pdkim_verify_status_str(int status)
@@ -433,8 +442,9 @@ memset(sig, 0, sizeof(pdkim_signature));
 sig->bodylength = -1;
 
 /* Set so invalid/missing data error display is accurate */
-sig->algo = -1;
 sig->version = 0;
+sig->keytype = -1;
+sig->hashtype = -1;
 
 q = sig->rawsig_no_b_val = store_get(Ustrlen(raw_hdr)+1);
 
@@ -507,14 +517,18 @@ 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)
-               {
-               sig->algo = i;
-               break;
-               }
+           {
+           uschar * s = Ustrchr(cur_val, '-');
+
+           for(i = 0; i < nelem(pdkim_keytypes); i++)
+             if (Ustrncmp(cur_val, pdkim_keytypes[i], s - cur_val) == 0)
+               { sig->keytype = i; break; }
+           for (++s, i = 0; i < nelem(pdkim_hashes); i++)
+             if (Ustrcmp(s, pdkim_hashes[i].dkim_hashname) == 0)
+               { sig->hashtype = i; break; }
            break;
+           }
+
          case 'c':
            for (i = 0; pdkim_combined_canons[i].str; i++)
              if (Ustrcmp(cur_val, pdkim_combined_canons[i].str) == 0)
@@ -588,9 +602,10 @@ DEBUG(D_acl)
 
 /*XXX hash method: extend for sha512 */
 if (!exim_sha_init(&sig->body_hash_ctx,
-              sig->algo == PDKIM_ALGO_RSA_SHA1 ? HASH_SHA1 : HASH_SHA256))
+              pdkim_hashes[sig->hashtype].exim_hashmethod))
   {
-  DEBUG(D_acl) debug_printf("PDKIM: hash init internal error\n");
+  DEBUG(D_acl)
+    debug_printf("PDKIM: hash init error, possibly nonhandled hashtype\n");
   return NULL;
   }
 return sig;
@@ -1189,8 +1204,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]);
+                   dkim_sig_to_a_tag(sig));
 hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"q=",
                    pdkim_querymethods[sig->querymethod]);
 hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"c=",
@@ -1375,8 +1389,6 @@ 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"";
   blob hhash;
@@ -1386,9 +1398,10 @@ while (sig)
   hdata.data = NULL;
   hdata.len = 0;
 
-  if (!exim_sha_init(&hhash_ctx, is_sha1 ? HASH_SHA1 : HASH_SHA256))
+  if (!exim_sha_init(&hhash_ctx, pdkim_hashes[sig->hashtype].exim_hashmethod))
     {
-    DEBUG(D_acl) debug_printf("PDKIM: hask setup internal error\n");
+    DEBUG(D_acl)
+      debug_printf("PDKIM: hash setup error, possibly nonhandled hashtype\n");
     break;
     }
 
@@ -1537,9 +1550,9 @@ while (sig)
     {
     es_ctx sctx;
 
-    /* Import private key */
+    /* Import private key, including the keytype */
 /*XXX extend for non-RSA algos */
-    if ((*err = exim_dkim_signing_init(US sig->rsa_privkey, &sctx)))
+    if ((*err = exim_dkim_signing_init(US sig->privkey, &sctx)))
       {
       DEBUG(D_acl) debug_printf("signing_init: %s\n", *err);
       return PDKIM_ERR_RSA_PRIVKEY;
@@ -1549,15 +1562,14 @@ while (sig)
     calculated, with GnuTLS we have to sign an entire block of headers
     (due to available interfaces) and it recalculates the hash internally. */
 
-#if defined(RSA_OPENSSL) || defined(RSA_GCRYPT)
+#if defined(SIGN_OPENSSL) || defined(SIGN_GCRYPT)
     hdata = hhash;
 #endif
 
 /*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)))
+    if ((*err = exim_dkim_sign(&sctx,
+                 pdkim_hashes[sig->hashtype].exim_hashmethod,
+                 &hdata, &sig->sighash)))
       {
       DEBUG(D_acl) debug_printf("signing: %s\n", *err);
       return PDKIM_ERR_RSA_SIGNING;
@@ -1583,7 +1595,8 @@ RSA signing should be done.  We use the same variant as the hash-method. */
         && sig->headernames   && *sig->headernames
         && sig->bodyhash.data
         && sig->sighash.data
-        && sig->algo > -1
+        && sig->keytype >= 0
+        && sig->hashtype >= 0
         && sig->version
        ) )
       {
@@ -1619,11 +1632,13 @@ RSA signing should be done.  We use the same variant as the hash-method. */
       const uschar * list = sig->pubkey->hashes, * ele;
       int sep = ':';
       while ((ele = string_nextinlist(&list, &sep, NULL, 0)))
-       if (Ustrcmp(ele, pdkim_algos[sig->algo] + 4) == 0) break;
+       if (Ustrcmp(ele, pdkim_hashes[sig->hashtype].dkim_hashname) == 0) break;
       if (!ele)
        {
-       DEBUG(D_acl) debug_printf("pubkey h=%s vs sig a=%s\n",
-                               sig->pubkey->hashes, pdkim_algos[sig->algo]);
+       DEBUG(D_acl) debug_printf("pubkey h=%s vs. sig a=%s_%s\n",
+         sig->pubkey->hashes,
+         pdkim_keytypes[sig->keytype],
+         pdkim_hashes[sig->hashtype].dkim_hashname);
        sig->verify_status =      PDKIM_VERIFY_FAIL;
        sig->verify_ext_status =  PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH;
        goto NEXT_VERIFY;
@@ -1632,7 +1647,9 @@ RSA signing should be done.  We use the same variant as the hash-method. */
 
     /* Check the signature */
 /*XXX needs extension for non-RSA */
-    if ((*err = exim_dkim_verify(&vctx, is_sha1, &hhash, &sig->sighash)))
+    if ((*err = exim_dkim_verify(&vctx,
+                 pdkim_hashes[sig->hashtype].exim_hashmethod,
+                 &hhash, &sig->sighash)))
       {
       DEBUG(D_acl) debug_printf("headers verify: %s\n", *err);
       sig->verify_status =      PDKIM_VERIFY_FAIL;
@@ -1690,18 +1707,18 @@ return ctx;
 
 /* -------------------------------------------------------------------------- */
 
-/*XXX ? needs extension to cover non-RSA algo?  Currently the "algo" is actually
-the combo of algo and hash-method */
+/*XXX ? needs extension to cover non-RSA algo?  */
 
 DLLEXPORT pdkim_ctx *
-pdkim_init_sign(char * domain, char * selector, char * rsa_privkey, int algo,
-  BOOL dot_stuffed, int(*dns_txt_callback)(char *, char *),
+pdkim_init_sign(uschar * domain, uschar * selector, uschar * privkey,
+  uschar * hashname, BOOL dot_stuffed, int(*dns_txt_callback)(char *, char *),
   const uschar ** errstr)
 {
+int hashtype;
 pdkim_ctx * ctx;
 pdkim_signature * sig;
 
-if (!domain || !selector || !rsa_privkey)
+if (!domain || !selector || !privkey)
   return NULL;
 
 ctx = store_get(sizeof(pdkim_ctx) + PDKIM_MAX_BODY_LINE_LEN + sizeof(pdkim_signature));
@@ -1720,14 +1737,23 @@ ctx->sig = sig;
 
 sig->domain = string_copy(US domain);
 sig->selector = string_copy(US selector);
-sig->rsa_privkey = string_copy(US rsa_privkey);
-sig->algo = algo;
+sig->privkey = string_copy(US privkey);
+/*XXX no keytype yet; comes from privkey */
 
-/*XXX extend for sha512 */
-if (!exim_sha_init(&sig->body_hash_ctx,
-              algo == PDKIM_ALGO_RSA_SHA1 ? HASH_SHA1 : HASH_SHA256))
+for (hashtype = 0; hashtype < nelem(pdkim_hashes); hashtype++)
+  if (Ustrcmp(hashname, pdkim_hashes[hashtype].dkim_hashname) == 0)
+  { sig->hashtype = hashtype; break; }
+if (hashtype >= nelem(pdkim_hashes))
   {
-  DEBUG(D_acl) debug_printf("PDKIM: hash setup internal error\n");
+  DEBUG(D_acl)
+    debug_printf("PDKIM: unrecognised hashname '%s'\n", hashname);
+  return NULL;
+  }
+
+if (!exim_sha_init(&sig->body_hash_ctx, pdkim_hashes[hashtype].exim_hashmethod))
+  {
+  DEBUG(D_acl)
+    debug_printf("PDKIM: hash setup error, possibly nonhandled hashtype\n");
   return NULL;
   }
 
index 176bc36..3c420ae 100644 (file)
 /* Some parameter values */
 #define PDKIM_QUERYMETHOD_DNS_TXT 0
 
-#define PDKIM_ALGO_RSA_SHA256     0
-#define PDKIM_ALGO_RSA_SHA1       1
-
 #define PDKIM_CANON_SIMPLE        0
 #define PDKIM_CANON_RELAXED       1
 
-#define PDKIM_HASH_SHA256         0
-#define PDKIM_HASH_SHA1           1
+/*XXX change to enums */
+#define PDKIM_HASH_SHA256         1
 
 #define PDKIM_KEYTYPE_RSA         0
 
@@ -122,9 +119,8 @@ typedef struct pdkim_signature {
   /* (v=) The version, as an integer. Currently, always "1" */
   int version;
 
-  /* (a=) The signature algorithm. Either PDKIM_ALGO_RSA_SHA256
-     or PDKIM_ALGO_RSA_SHA1 */
-  int algo;
+  int keytype; /* PDKIM_KEYTYPE_RSA */
+  int hashtype;        /* pdkim_hashes index */
 
   /* (c=x/) Header canonicalization method. Either PDKIM_CANON_SIMPLE
      or PDKIM_CANON_RELAXED */
@@ -239,7 +235,7 @@ typedef struct pdkim_signature {
   unsigned long signed_body_bytes; /* How many body bytes we hashed     */
   pdkim_stringlist *headers; /* Raw headers included in the sig         */
   /* Signing specific ------------------------------------------------- */
-  uschar * rsa_privkey;     /* Private RSA key                             */
+  uschar * privkey;         /* Private key                                 */
   uschar * sign_headers;    /* To-be-signed header names                   */
   uschar * rawsig_no_b_val; /* Original signature header w/o b= tag value. */
 } pdkim_signature;
@@ -287,7 +283,7 @@ extern "C" {
 void      pdkim_init         (void);
 
 DLLEXPORT
-pdkim_ctx *pdkim_init_sign    (char *, char *, char *, int,
+pdkim_ctx *pdkim_init_sign    (uschar *, uschar *, uschar *, uschar *,
                              BOOL, int(*)(char *, char *), const uschar **);
 
 DLLEXPORT
@@ -310,6 +306,8 @@ void       pdkim_free_ctx     (pdkim_ctx *);
 
 const uschar * pdkim_errstr(int);
 
+uschar *       dkim_sig_to_a_tag(pdkim_signature * sig);
+
 #ifdef __cplusplus
 }
 #endif
index 143cd19..008f277 100644 (file)
 #include "../blob.h"
 #include "../hash.h"
 
-#ifdef RSA_OPENSSL
+#ifdef SIGN_OPENSSL
 # include <openssl/rsa.h>
 # include <openssl/ssl.h>
 # include <openssl/err.h>
-#elif defined(RSA_GNUTLS)
+#elif defined(SIGN_GNUTLS)
 # include <gnutls/gnutls.h>
 # include <gnutls/x509.h>
 #endif
index bcd64fd..ec68414 100644 (file)
@@ -3,11 +3,7 @@
  *
  *  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.
+ *  signing/verification interface
  */
 
 #include "../exim.h"
@@ -23,7 +19,7 @@ indicated to caller for protocol tag construction.
 
 
 /******************************************************************************/
-#ifdef RSA_GNUTLS
+#ifdef SIGN_GNUTLS
 
 void
 exim_dkim_init(void)
@@ -55,8 +51,8 @@ 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,
+if (  (rc = gnutls_x509_privkey_init(&sign_ctx->key)) != GNUTLS_E_SUCCESS
+   || (rc = gnutls_x509_privkey_import(sign_ctx->key, &k,
          GNUTLS_X509_FMT_PEM)) != GNUTLS_E_SUCCESS
    )
   return gnutls_strerror(rc);
@@ -74,33 +70,38 @@ 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)
+exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, blob * data, blob * sig)
 {
+gnutls_digest_algorithm_t dig;
 gnutls_datum_t k;
 size_t sigsize = 0;
 int rc;
 const uschar * ret = NULL;
 
+switch (hash)
+  {
+  case HASH_SHA1:      dig = GNUTLS_DIG_SHA1; break;
+  case HASH_SHA2_256:  dig = GNUTLS_DIG_SHA256; break;
+  case HASH_SHA2_512:  dig = GNUTLS_DIG_SHA512; break;
+  default:             return US"nonhandled hash type";
+  }
+
 /* 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,
+(void) gnutls_x509_privkey_sign_data(sign_ctx->key, dig,
   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,
+if ((rc = gnutls_x509_privkey_sign_data(sign_ctx->key, dig,
            0, &k, sig->data, &sigsize)) != GNUTLS_E_SUCCESS
    )
   ret = gnutls_strerror(rc);
 
-gnutls_x509_privkey_deinit(sign_ctx->rsa);
+gnutls_x509_privkey_deinit(sign_ctx->key);
 return ret;
 }
 
@@ -116,12 +117,12 @@ gnutls_datum_t k;
 int rc;
 const uschar * ret = NULL;
 
-gnutls_pubkey_init(&verify_ctx->rsa);
+gnutls_pubkey_init(&verify_ctx->key);
 
 k.data = pubkey_der->data;
 k.size = pubkey_der->len;
 
-if ((rc = gnutls_pubkey_import(verify_ctx->rsa, &k, GNUTLS_X509_FMT_DER))
+if ((rc = gnutls_pubkey_import(verify_ctx->key, &k, GNUTLS_X509_FMT_DER))
        != GNUTLS_E_SUCCESS)
   ret = gnutls_strerror(rc);
 return ret;
@@ -132,31 +133,39 @@ return ret;
 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)
+exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, blob * data_hash, blob * sig)
 {
+gnutls_sign_algorithm_t algo;
 gnutls_datum_t k, s;
 int rc;
 const uschar * ret = NULL;
 
+/*XXX needs extension for non-rsa */
+switch (hash)
+  {
+  case HASH_SHA1:      algo = GNUTLS_SIGN_RSA_SHA1;   break;
+  case HASH_SHA2_256:  algo = GNUTLS_SIGN_RSA_SHA256; break;
+  case HASH_SHA2_512:  algo = GNUTLS_SIGN_RSA_SHA512; break;
+  default:             return US"nonhandled hash type";
+  }
+
 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)
+if ((rc = gnutls_pubkey_verify_hash2(verify_ctx->key, algo, 0, &k, &s)) < 0)
   ret = gnutls_strerror(rc);
 
-gnutls_pubkey_deinit(verify_ctx->rsa);
+gnutls_pubkey_deinit(verify_ctx->key);
 return ret;
 }
 
 
 
 
-#elif defined(RSA_GCRYPT)
+#elif defined(SIGN_GCRYPT)
 /******************************************************************************/
+/* This variant is used under pre-3.0.0 GnuTLS.  Only rsa-sha1 and rsa-sha256 */
 
 
 /* Internal service routine:
@@ -364,8 +373,9 @@ 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)
+exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, blob * data, blob * sig)
 {
+BOOL is_sha1;
 gcry_sexp_t s_hash = NULL, s_key = NULL, s_sig = NULL;
 gcry_mpi_t m_sig;
 uschar * errstr;
@@ -373,11 +383,18 @@ 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
+current impl looks WRONG - it _is_ given a hash so should not be
 re-hashing.  Has this been tested?
 
 Will need extension for non-RSA sugning algos. */
 
+switch (hash)
+  {
+  case HASH_SHA1:      is_sha1 = TRUE; break;
+  case HASH_SHA2_256:  is_sha1 = FALSE; break;
+  default:             return US"nonhandled hash type";
+  }
+
 #define SIGSPACE 128
 sig->data = store_get(SIGSPACE);
 
@@ -512,7 +529,7 @@ DEBUG(D_acl) return string_sprintf("%s: %s", stage, asn1_strerror(rc));
 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)
+exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, blob * data_hash, blob * sig)
 {
 /*
 cf. libgnutls 2.8.5 _wrap_gcry_pk_verify()
@@ -522,6 +539,13 @@ gcry_sexp_t s_sig = NULL, s_hash = NULL, s_pkey = NULL;
 gcry_error_t gerr;
 uschar * stage;
 
+switch (hash)
+  {
+  case HASH_SHA1:      is_sha1 = TRUE; break;
+  case HASH_SHA2_256:  is_sha1 = FALSE; break;
+  default:             return US"nonhandled hash type";
+  }
+
 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))
@@ -557,7 +581,7 @@ return NULL;
 
 
 
-#elif defined(RSA_OPENSSL)
+#elif defined(SIGN_OPENSSL)
 /******************************************************************************/
 
 void
@@ -580,30 +604,10 @@ 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);
-  }
+BIO * bp = BIO_new_mem_buf(privkey_pem, -1);
 
+if (!(sign_ctx->key = PEM_read_bio_PrivateKey(bp, NULL, NULL, NULL)))
+  return ERR_error_string(ERR_get_error(), NULL);
 return NULL;
 }
 
@@ -617,32 +621,37 @@ 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)
+exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, 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  ???  */
+const EVP_MD * md;
+EVP_PKEY_CTX * ctx;
+size_t siglen;
 
-/* Allocate mem for signature */
-len = RSA_size(sign_ctx->rsa);
-sig->data = store_get(len);
-sig->len = len;
+switch (hash)
+  {
+  case HASH_SHA1:      md = EVP_sha1();   break;
+  case HASH_SHA2_256:  md = EVP_sha256(); break;
+  case HASH_SHA2_512:  md = EVP_sha512(); break;
+  default:             return US"nonhandled hash type";
+  }
 
-/* Do signing */
-if (RSA_sign(is_sha1 ? NID_sha1 : NID_sha256,
-      CUS data->data, data->len,
-      US sig->data, &len, sign_ctx->rsa) != 1)
+if (  (ctx = EVP_PKEY_CTX_new(sign_ctx->key, NULL))
+   && EVP_PKEY_sign_init(ctx) > 0
+   && EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) > 0
+   && EVP_PKEY_CTX_set_signature_md(ctx, md) > 0
+   && EVP_PKEY_sign(ctx, NULL, &siglen, data->data, data->len) > 0
+   )
   {
-  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);
+  /* Allocate mem for signature */
+  sig->data = store_get(siglen);
+  sig->len = siglen;
+
+  if (EVP_PKEY_sign(ctx, sig->data, &siglen, data->data, data->len) > 0)
+    { EVP_PKEY_CTX_free(ctx); return NULL; }
   }
 
-RSA_free(sign_ctx->rsa);
-return ret;;
+if (ctx) EVP_PKEY_CTX_free(ctx);
+return ERR_error_string(ERR_get_error(), NULL);
 }
 
 
@@ -653,19 +662,13 @@ 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;
+const uschar * s = pubkey_der->data;
 
-/*XXX d2i_X509_PUBKEY, X509_get_pubkey(), and an EVP_PKEY* in verify_ctx. */
+/*XXX hmm, we never free this */
 
-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;
+if ((verify_ctx->key = d2i_PUBKEY(NULL, &s, pubkey_der->len)))
+  return NULL;
+return ERR_error_string(ERR_get_error(), NULL);
 }
 
 
@@ -675,31 +678,34 @@ return ret;
 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)
+exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, blob * data_hash, blob * sig)
 {
-const uschar * ret = NULL;
+const EVP_MD * md;
+EVP_PKEY_CTX * ctx;
 
-/*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)
+switch (hash)
   {
-  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);
+  case HASH_SHA1:      md = EVP_sha1();   break;
+  case HASH_SHA2_256:  md = EVP_sha256(); break;
+  case HASH_SHA2_512:  md = EVP_sha512(); break;
+  default:             return US"nonhandled hash type";
   }
-return ret;
+
+if (  (ctx = EVP_PKEY_CTX_new(verify_ctx->key, NULL))
+   && EVP_PKEY_verify_init(ctx) > 0
+   && EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) > 0
+   && EVP_PKEY_CTX_set_signature_md(ctx, md) > 0
+   && EVP_PKEY_verify(ctx, sig->data, sig->len,
+       data_hash->data, data_hash->len) == 1
+   )
+  { EVP_PKEY_CTX_free(ctx); return NULL; }
+
+if (ctx) EVP_PKEY_CTX_free(ctx);
+return ERR_error_string(ERR_get_error(), NULL);
 }
 
 
+
 #endif
 /******************************************************************************/
 
index 4e85808..0428810 100644 (file)
 
 #include "crypt_ver.h"
 
-#ifdef RSA_OPENSSL
+#ifdef SIGN_OPENSSL
 # include <openssl/rsa.h>
 # include <openssl/ssl.h>
 # include <openssl/err.h>
-#elif defined(RSA_GNUTLS)
+#elif defined(SIGN_GNUTLS)
 # include <gnutls/gnutls.h>
 # include <gnutls/x509.h>
 #  include <gnutls/abstract.h>
-#elif defined(RSA_GCRYPT)
+#elif defined(SIGN_GCRYPT)
 #  include <gcrypt.h>
 #  include <libtasn1.h>
 #endif
 #include "../blob.h"
 
 
-#ifdef RSA_OPENSSL
+#ifdef SIGN_OPENSSL
 
 typedef struct {
-  RSA * rsa;
+  EVP_PKEY * key;
 } es_ctx;
 
 typedef struct {
-  RSA * rsa;
+  EVP_PKEY * key;
 } ev_ctx;
 
-#elif defined(RSA_GNUTLS)
+#elif defined(SIGN_GNUTLS)
 
 typedef struct {
-  gnutls_x509_privkey_t rsa;
+  gnutls_x509_privkey_t key;
 } es_ctx;
 
 typedef struct {
-  gnutls_pubkey_t rsa;
+  gnutls_pubkey_t key;
 } ev_ctx;
 
-#elif defined(RSA_GCRYPT)
+#elif defined(SIGN_GCRYPT)
 
 typedef struct {
+  int  keytype;
   gcry_mpi_t n;
   gcry_mpi_t e;
   gcry_mpi_t d;
@@ -62,6 +63,7 @@ typedef struct {
 } es_ctx;
 
 typedef struct {
+  int  keytype;
   gcry_mpi_t n;
   gcry_mpi_t e;
 } ev_ctx;
index beea57f..06fcd41 100644 (file)
@@ -872,6 +872,7 @@ struct ob_dkim {
   uschar *dkim_canon;
   uschar *dkim_sign_headers;
   uschar *dkim_strict;
+  uschar *dkim_hash;
   BOOL    dot_stuffed;
 };
 
index d8bc596..147dfde 100644 (file)
@@ -43,6 +43,8 @@ optionlist smtp_transport_options[] = {
       (void *)offsetof(smtp_transport_options_block, dkim.dkim_canon) },
   { "dkim_domain", opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, dkim.dkim_domain) },
+  { "dkim_hash", opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, dkim.dkim_hash) },
   { "dkim_private_key", opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, dkim.dkim_private_key) },
   { "dkim_selector", opt_stringptr,
@@ -281,6 +283,7 @@ smtp_transport_options_block smtp_transport_option_defaults = {
     .dkim_canon =              NULL,
     .dkim_sign_headers =        NULL,
     .dkim_strict =             NULL,
+    .dkim_hash =               US"sha256",
     .dot_stuffed =             FALSE},
 #endif
 };
diff --git a/test/confs/4503 b/test/confs/4503
new file mode 120000 (symlink)
index 0000000..c4f73ba
--- /dev/null
@@ -0,0 +1 @@
+4500
\ No newline at end of file
index 70454c3..4497028 100644 (file)
@@ -46,5 +46,8 @@ send_to_server:
 .ifndef HEADERS_MAXSIZE
   dkim_sign_headers =  OPT
 .endif
+.ifdef VALUE
+  dkim_hash =          VALUE
+.endif
 
 # End
diff --git a/test/confs/4523 b/test/confs/4523
new file mode 120000 (symlink)
index 0000000..072f5fa
--- /dev/null
@@ -0,0 +1 @@
+4520
\ No newline at end of file
diff --git a/test/log/4503 b/test/log/4503
new file mode 100644 (file)
index 0000000..7ec93a1
--- /dev/null
@@ -0,0 +1,6 @@
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 10HmaX-0005vi-00 DKIM: d=test.ex s=sel c=simple/simple a=rsa-sha512 b=1024 [verification failed - signature did not verify (headers probably modified in transit)]
+1999-03-02 09:44:33 10HmaX-0005vi-00 signer: test.ex bits: 1024
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=qwerty1234@disco-zombie.net
diff --git a/test/log/4523 b/test/log/4523
new file mode 100644 (file)
index 0000000..45eb89c
--- /dev/null
@@ -0,0 +1,11 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaX-0005vi-00 => a@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] C="250 OK id=10HmaY-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 10HmaY-0005vi-00 DKIM: d=test.ex s=sel c=relaxed/relaxed a=rsa-sha512 b=1024 [verification succeeded]
+1999-03-02 09:44:33 10HmaY-0005vi-00 signer: test.ex bits: 1024 h=Date:Sender:Message-Id:From:Reply-To:Subject:To:Cc:MIME-Version:Content-Type:Content-Transfer-Encoding:Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:List-Subscribe:List-Post:List-Owner:List-Archive
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtp S=sss id=E10HmaX-0005vi-00@myhost.test.ex
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <a@test.ex> R=server_dump
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
index 712ab79..35da493 100755 (executable)
@@ -1527,7 +1527,7 @@ $munges =
 
     'optional_config' =>
     { 'stdout' => '/^(
-                  dkim_(canon|domain|private_key|selector|sign_headers|strict)
+                  dkim_(canon|domain|private_key|selector|sign_headers|strict|hash)
                   |gnutls_require_(kx|mac|protocols)
                   |hosts_(requ(est|ire)|try)_(dane|ocsp)
                   |hosts_(avoid|nopass|require|verify_avoid)_tls
diff --git a/test/scripts/4500-DKIM/4503 b/test/scripts/4500-DKIM/4503
new file mode 100644 (file)
index 0000000..aca1958
--- /dev/null
@@ -0,0 +1,45 @@
+# DKIM verify, sha512
+#
+exim -DSERVER=server -bd -oX PORT_D
+****
+#
+# This should pass, only Mail::DKIM::Signer does not handle rsa-sha512.
+#  - sha512, 1024b
+# Mail original in aux-fixed/4500.msg1.txt
+# Sig generated by: perl aux-fixed/dkim/sign.pl --algorithm=rsa-sha512 \
+#                      --method=simple/simple < aux-fixed/4500.msg1.txt
+#
+# TODO - until we have that we can only test internal consistency,
+# signing vs. verification.
+#
+client 127.0.0.1 PORT_D
+??? 220
+HELO xxx
+??? 250
+MAIL FROM:<CALLER@bloggs.com>
+??? 250
+RCPT TO:<a@test.ex>
+??? 250
+DATA
+??? 354
+DKIM-Signature: v=1; a=rsa-sha512; c=simple/simple; d=test.ex; h=from:to
+       :date:message-id:subject; s=sel; bh=3UbbJTudPxmejzh7U1Zg33U3QT+1
+       6kfV2eOTvMeiEis=; b=xQSD/JMqz0C+xKf0A1NTkPTbkDuDdJbpBuyjjT9iYvyP
+       Zez+xl0TkoPobFGVa6EN8+ZeYV18zjifhtWYLSsNmPinUtcpKQLG1zxAKmmS0JEh
+       +qihlWbeGJ5+tK588ugUzXHPj+4JBW0H6kxHvdH0l2SlQE5xs/cdggnx5QX5USY=
+From: mrgus@text.ex
+To: bakawolf@yahoo.com
+Date: Thu, 19 Nov 2015 17:00:07 -0700
+Message-ID: <qwerty1234@disco-zombie.net>
+Subject: simple test
+
+This is a simple test.
+.
+??? 250
+QUIT
+??? 221
+****
+#
+killdaemon
+no_stdout_check
+no_msglog_check
diff --git a/test/scripts/4500-DKIM/4523 b/test/scripts/4500-DKIM/4523
new file mode 100644 (file)
index 0000000..b897fc2
--- /dev/null
@@ -0,0 +1,15 @@
+# DKIM signing, sha512
+#
+exim -bd -DSERVER=server -oX PORT_D
+****
+#
+# default header set
+exim -DHEADERS_MAXSIZE=y -DVALUE=sha512 -odf a@test.ex
+From: nobody@example.com
+
+content
+****
+#
+millisleep 500
+killdaemon
+no_msglog_check