DKIM: Ed25519 signatures (GnuTLS 3.6.0 and later)
authorJeremy Harris <jgh146exb@wizmail.org>
Tue, 6 Feb 2018 14:24:23 +0000 (14:24 +0000)
committerJeremy Harris <jgh146exb@wizmail.org>
Tue, 6 Feb 2018 15:04:01 +0000 (15:04 +0000)
41 files changed:
doc/doc-docbook/spec.xfpt
doc/doc-txt/NewStuff
src/src/base64.c
src/src/dkim.c
src/src/pdkim/crypt_ver.h
src/src/pdkim/pdkim.c
src/src/pdkim/pdkim.h
src/src/pdkim/signing.c
src/src/pdkim/signing.h
test/Makefile.in
test/aux-fixed/dkim/dkim_ed25519.private [new file with mode: 0644]
test/configure
test/configure.ac
test/confs/4505 [new symlink]
test/confs/4520
test/confs/4525 [new symlink]
test/dnszones-src/db.test.ex
test/log/4502
test/log/4503
test/log/4504
test/log/4505 [new file with mode: 0644]
test/log/4506
test/log/4520
test/log/4525 [new file with mode: 0644]
test/mail/4530.y
test/mail/4530.z
test/runtest
test/scripts/4500-DKIM/4505 [new file with mode: 0644]
test/scripts/4500-DKIM/4525 [new file with mode: 0644]
test/scripts/4500-DKIM/4530
test/src/ed25519_privkey_pem_to_pubkey_raw_b64.c [new file with mode: 0644]
test/stderr/0021
test/stderr/0022
test/stderr/0303
test/stderr/0371
test/stderr/0386
test/stderr/0465
test/stderr/0487
test/stderr/0575
test/stderr/5410
test/stderr/5420

index 0eccce1..b5865e9 100644 (file)
@@ -38594,7 +38594,7 @@ There is no dot-stuffing (and no dot-termination).
 DKIM is a mechanism by which messages sent by some entity can be provably
 linked to a domain which that entity controls.  It permits reputation to
 be tracked on a per-domain basis, rather than merely upon source IP address.
-DKIM is documented in RFC 4871.
+DKIM is documented in RFC 6376.
 
 .new
 As DKIM relies on the message being unchanged in transit, messages handled
@@ -38656,6 +38656,12 @@ rsa-sha1 MUST NOT be used for signing or verifying.
 Signers MUST use RSA keys of at least 1024 bits for all keys.
 Signers SHOULD use RSA keys of at least 2048 bits.
 .endd
+
+Note also that the key content (the 'p=' field)
+in the DNS record is different between RSA and EC keys;
+for the former it is the base64 of the ASN.1 for the RSA public key
+(equivalent to the private-key .pem with the header/trailer stripped)
+but for EC keys it is the base64 of the pure key; no ASN.1 wrapping.
 .wen
 .wen
 
@@ -38685,10 +38691,14 @@ You can use the &%$dkim_domain%& and
 &%$dkim_selector%& expansion variables to determine the private key to use.
 The result can either
 .ilist
-be a valid RSA private key in ASCII armor, including line breaks.
+be a valid RSA private key in ASCII armor (.pem file), including line breaks
+.new
+.next
+with GnuTLS 3.6.0 or later, be a valid Ed25519 private key (same format as above)
+.wen
 .next
 start with a slash, in which case it is treated as a file that contains
-the private key.
+the private key
 .next
 be "0", "false" or the empty string, in which case the message will not
 be signed. This case will not result in an error, even if &%dkim_strict%&
@@ -38700,6 +38710,13 @@ Note that RFC 8301 says:
 .code
 Signers MUST use RSA keys of at least 1024 bits for all keys.
 Signers SHOULD use RSA keys of at least 2048 bits.
+
+Support for EC keys is being developed under
+&url(https://datatracker.ietf.org/doc/draft-ietf-dcrup-dkim-crypto/).
+They are considerably smaller than RSA keys for equivalent protection.
+As they are a recent development, users should consider dual-signing
+(by setting a list of selectors, and an expansion for this option)
+for some transition period.
 .endd
 .wen
 
@@ -38883,6 +38900,9 @@ The key record selector string.
 
 .vitem &%$dkim_algo%&
 The algorithm used. One of 'rsa-sha1' or 'rsa-sha256'.
+.new
+If running under GnuTLS 3.6.0 or later, may also be 'ed25519-sha256'.
+.wen
 
 .new
 Note that RFC 8301 says:
index c3f013e..ee40553 100644 (file)
@@ -33,6 +33,9 @@ Version 4.91
  8. Expansion item ${sha3:<string>} / ${sha3_<N>:<string>} now also supported
     under OpenSSL version 1.1.1 or later.
 
+ 9. DKIM operations can now use the Ed25519 algorithm in addition to RSA, under
+    GnuTLS 3.6.0 or later.
+
 
 Version 4.90
 ------------
index f6f187f..ae6874b 100644 (file)
@@ -245,7 +245,7 @@ uschar *p = code;
 
 while (len-- >0)
   {
-  register int x, y;
+  int x, y;
 
   x = *clear++;
   *p++ = enc64table[(x >> 2) & 63];
index 2a66b4a..c7bf641 100644 (file)
@@ -268,7 +268,7 @@ dkim_exim_verify_finish(void)
 pdkim_signature * sig;
 int rc;
 gstring * g = NULL;
-const uschar * errstr;
+const uschar * errstr = NULL;
 
 store_pool = POOL_PERM;
 
@@ -291,12 +291,8 @@ dkim_collect_input = FALSE;
 /* Finish DKIM operation and fetch link to signatures chain */
 
 rc = pdkim_feed_finish(dkim_verify_ctx, &dkim_signatures, &errstr);
-if (rc != PDKIM_OK)
-  {
-  log_write(0, LOG_MAIN, "DKIM: validation error: %.100s%s%s", pdkim_errstr(rc),
-           errstr ? ": " : "", errstr ? errstr : US"");
-  goto out;
-  }
+if (rc != PDKIM_OK && errstr)
+  log_write(0, LOG_MAIN, "DKIM: validation error: %s", errstr);
 
 /* Build a colon-separated list of signing domains (and identities, if present) in dkim_signers */
 
index bf62036..7b0ddf9 100644 (file)
 #ifdef USE_GNUTLS
 # include <gnutls/gnutls.h>
 
-# if GNUTLS_VERSION_NUMBER >= 0x30000
+# if GNUTLS_VERSION_NUMBER >= 0x030000
 #  define SIGN_GNUTLS
+#  if GNUTLS_VERSION_NUMBER >= 0x030600
+#   define SIGN_HAVE_ED25519
+#  endif
 # else
 #  define SIGN_GCRYPT
 # endif
index 365234f..eec1a9c 100644 (file)
@@ -82,13 +82,22 @@ static const pdkim_hashtype pdkim_hashes[] = {
 };
 
 const uschar * pdkim_keytypes[] = {
-  US"rsa"
+  [KEYTYPE_RSA] =      US"rsa",
+#ifdef SIGN_HAVE_ED25519
+  [KEYTYPE_ED25519] =  US"ed25519",            /* Works for 3.6.0 GnuTLS */
+#endif
+
+#ifdef notyet_EC_dkim_extensions       /* https://tools.ietf.org/html/draft-srose-dkim-ecc-00 */
+  US"eccp256",
+  US"eccp348",
+  US"ed448",
+#endif
 };
 
 typedef struct pdkim_combined_canon_entry {
-  const uschar * str;
-  int canon_headers;
-  int canon_body;
+  const uschar *       str;
+  int                  canon_headers;
+  int                  canon_body;
 } pdkim_combined_canon_entry;
 
 pdkim_combined_canon_entry pdkim_combined_canons[] = {
@@ -155,9 +164,9 @@ switch(status)
   {
   case PDKIM_OK:               return US"OK";
   case PDKIM_FAIL:             return US"FAIL";
-  case PDKIM_ERR_RSA_PRIVKEY:  return US"RSA_PRIVKEY";
-  case PDKIM_ERR_RSA_SIGNING:  return US"RSA SIGNING";
-  case PDKIM_ERR_LONG_LINE:    return US"RSA_LONG_LINE";
+  case PDKIM_ERR_RSA_PRIVKEY:  return US"PRIVKEY";
+  case PDKIM_ERR_RSA_SIGNING:  return US"SIGNING";
+  case PDKIM_ERR_LONG_LINE:    return US"LONG_LINE";
   case PDKIM_ERR_BUFFER_TOO_SMALL:     return US"BUFFER_TOO_SMALL";
   case PDKIM_SIGN_PRIVKEY_WRAP:        return US"PRIVKEY_WRAP";
   case PDKIM_SIGN_PRIVKEY_B64D:        return US"PRIVKEY_B64D";
@@ -504,7 +513,7 @@ for (p = raw_hdr; ; p++)
 
        switch (*cur_tag->s)
          {
-         case 'b':
+         case 'b':                             /* sig-data or body-hash */
            switch (cur_tag->s[1])
              {
              case '\0': pdkim_decode_base64(cur_val->s, &sig->sighash); break;
@@ -514,26 +523,35 @@ for (p = raw_hdr; ; p++)
              default:   break;
              }
            break;
-         case 'v':
+         case 'v':                                     /* version */
              /* We only support version 1, and that is currently the
                 only version there is. */
            sig->version =
              Ustrcmp(cur_val->s, PDKIM_SIGNATURE_VERSION) == 0 ? 1 : -1;
            break;
-         case 'a':
+         case 'a':                                     /* algorithm */
            {
            uschar * s = Ustrchr(cur_val->s, '-');
 
            for(i = 0; i < nelem(pdkim_keytypes); i++)
              if (Ustrncmp(cur_val->s, pdkim_keytypes[i], s - cur_val->s) == 0)
                { sig->keytype = i; break; }
+           if (sig->keytype < 0)
+             log_write(0, LOG_MAIN,
+               "DKIM: ignoring signature due to nonhandled keytype in a=%s",
+               cur_val->s);
+
            for (++s, i = 0; i < nelem(pdkim_hashes); i++)
              if (Ustrcmp(s, pdkim_hashes[i].dkim_hashname) == 0)
                { sig->hashtype = i; break; }
+           if (sig->hashtype < 0)
+             log_write(0, LOG_MAIN,
+               "DKIM: ignoring signature due to nonhandled hashtype in a=%s",
+               cur_val);
            break;
            }
 
-         case 'c':
+         case 'c':                                     /* canonicalization */
            for (i = 0; pdkim_combined_canons[i].str; i++)
              if (Ustrcmp(cur_val->s, pdkim_combined_canons[i].str) == 0)
                {
@@ -542,30 +560,32 @@ for (p = raw_hdr; ; p++)
                break;
                }
            break;
-         case 'q':
+         case 'q':                             /* Query method (for pubkey)*/
            for (i = 0; pdkim_querymethods[i]; i++)
              if (Ustrcmp(cur_val->s, pdkim_querymethods[i]) == 0)
                {
-               sig->querymethod = i;
+               sig->querymethod = i;   /* we never actually use this */
                break;
                }
            break;
-         case 's':
+         case 's':                                     /* Selector */
            sig->selector = string_copyn(cur_val->s, cur_val->ptr); break;
-         case 'd':
+         case 'd':                                     /* SDID */
            sig->domain = string_copyn(cur_val->s, cur_val->ptr); break;
-         case 'i':
+         case 'i':                                     /* AUID */
            sig->identity = pdkim_decode_qp(cur_val->s); break;
-         case 't':
+         case 't':                                     /* Timestamp */
            sig->created = strtoul(CS cur_val->s, NULL, 10); break;
-         case 'x':
+         case 'x':                                     /* Expiration */
            sig->expires = strtoul(CS cur_val->s, NULL, 10); break;
-         case 'l':
+         case 'l':                                     /* Body length count */
            sig->bodylength = strtol(CS cur_val->s, NULL, 10); break;
-         case 'h':
+         case 'h':                                     /* signed header fields */
            sig->headernames = string_copyn(cur_val->s, cur_val->ptr); break;
-         case 'z':
+         case 'z':                                     /* Copied headfields */
            sig->copiedheaders = pdkim_decode_qp(cur_val->s); break;
+/*XXX draft-ietf-dcrup-dkim-crypto-05 would need 'p' tag support
+for rsafp signatures.  But later discussion is dropping those. */
          default:
            DEBUG(D_acl) debug_printf(" Unknown tag encountered\n");
            break;
@@ -587,6 +607,9 @@ NEXT_CHAR:
     *q++ = c;
   }
 
+if (sig->keytype < 0 || sig->hashtype < 0)     /* Cannot verify this signature */
+  return NULL;
+
 *q = '\0';
 /* Chomp raw header. The final newline must not be added to the signature. */
 while (--q > sig->rawsig_no_b_val  && (*q == '\r' || *q == '\n'))
@@ -635,7 +658,7 @@ while ((ele = string_nextinlist(&raw_record, &sep, NULL, 0)))
       {
       case 'v': pub->version = val;                    break;
       case 'h': pub->hashes = val;                     break;
-      case 'k': break;
+      case 'k': pub->keytype = val;                    break;
       case 'g': pub->granularity = val;                        break;
       case 'n': pub->notes = pdkim_decode_qp(val);     break;
       case 'p': pdkim_decode_base64(val, &pub->key);   break;
@@ -658,9 +681,7 @@ else if (Ustrcmp(pub->version, PDKIM_PUB_RECORD_VERSION) != 0)
   }
 
 if (!pub->granularity) pub->granularity = US"*";
-/*
 if (!pub->keytype    ) pub->keytype     = US"rsa";
-*/
 if (!pub->srvtype    ) pub->srvtype     = US"*";
 
 /* p= is required */
@@ -1339,7 +1360,10 @@ DEBUG(D_acl) debug_printf(
       "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
 
 /* Import public key */
-if ((*errstr = exim_dkim_verify_init(&p->key, vctx)))
+
+if ((*errstr = exim_dkim_verify_init(&p->key,
+           sig->keytype == KEYTYPE_ED25519 ? KEYFMT_ED25519_BARE : KEYFMT_DER,
+           vctx)))
   {
   DEBUG(D_acl) debug_printf("verify_init: %s\n", *errstr);
   sig->verify_status =      PDKIM_VERIFY_INVALID;
@@ -1347,6 +1371,7 @@ if ((*errstr = exim_dkim_verify_init(&p->key, vctx)))
   return NULL;
   }
 
+vctx->keytype = sig->keytype;
 return p;
 }
 
@@ -1359,6 +1384,8 @@ pdkim_feed_finish(pdkim_ctx * ctx, pdkim_signature ** return_signatures,
 {
 pdkim_bodyhash * b;
 pdkim_signature * sig;
+BOOL verify_pass = FALSE;
+es_ctx sctx;
 
 /* Check if we must still flush a (partial) header. If that is the
    case, the message has no body, and we must compute a body hash
@@ -1379,6 +1406,12 @@ else
   DEBUG(D_acl) debug_printf(
       "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
 
+if (!ctx->sig)
+  {
+  DEBUG(D_acl) debug_printf("PDKIM: no signatures\n");
+  return PDKIM_OK;
+  }
+
 /* Build (and/or evaluate) body hash */
 pdkim_finish_bodyhash(ctx);
 
@@ -1388,11 +1421,33 @@ for (sig = ctx->sig; sig; sig = sig->next)
   uschar * sig_hdr = US"";
   blob hhash;
   gstring * hdata = NULL;
+  es_ctx sctx;
+
+  /*XXX The hash of the headers is needed for GCrypt (for which we can do RSA
+  suging only, as it happens) and for either GnuTLS and OpenSSL when we are
+  signing with EC (specifically, Ed25519).  The former is because the GCrypt
+  signing operation is pure (does not do its own hash) so we must hash.  The
+  latter is because we (stupidly, but this is what the IETF draft is saying)
+  must hash with the declared hash method, then pass the result to the library
+  hash-and-sign routine (because that's all the libraries are providing.  And
+  we're stuck with whatever that hidden hash method is, too).  We may as well
+  do this hash incrementally.
+  We don't need the hash we're calculating here for the GnuTLS and OpenSSL
+  cases of RSA signing, since those library routines can do hash-and-sign.
+  Some time in the future we could easily avoid doing the hash here for those
+  cases (which will be common for a long while.  We could also change from
+  the current copy-all-the-headers-into-one-block, then call the hash-and-sign
+  implementation  - to a proper incremental one.  Unfortunately, GnuTLS just
+  cannot do incremental - either signing or verification.  Unsure about GCrypt.
+  */
+
+  /*XXX The header hash is also used (so far) by the verify operation */
 
   if (!exim_sha_init(&hhash_ctx, pdkim_hashes[sig->hashtype].exim_hashmethod))
     {
-    DEBUG(D_acl)
-      debug_printf("PDKIM: hash setup error, possibly nonhandled hashtype\n");
+    log_write(0, LOG_MAIN|LOG_PANIC,
+      "PDKIM: hash setup error, possibly nonhandled hashtype");
     break;
     }
 
@@ -1420,9 +1475,19 @@ for (sig = ctx->sig; sig; sig = sig->next)
     uschar * s;
     int sep = 0;
 
-    sig->headernames = NULL;           /* Collected signed header names */
+    /* Import private key, including the keytype which we need for building
+    the signature header  */
+
+/*XXX extend for non-RSA algos */
+    if ((*err = exim_dkim_signing_init(US sig->privkey, &sctx)))
+      {
+      log_write(0, LOG_MAIN|LOG_PANIC, "signing_init: %s", *err);
+      return PDKIM_ERR_RSA_PRIVKEY;
+      }
+    sig->keytype = sctx.keytype;
 
-    for (p = sig->headers; p; p = p->next)
+    for (sig->headernames = NULL,              /* Collected signed header names */
+         p = sig->headers; p; p = p->next)
       {
       uschar * rh = p->value;
 
@@ -1438,6 +1503,7 @@ for (sig = ctx->sig; sig; sig = sig->next)
        exim_sha_update(&hhash_ctx, CUS rh, Ustrlen(rh));
 
        /* Remember headers block for signing (when the library cannot do incremental)  */
+       /*XXX we could avoid doing this for all but the GnuTLS/RSA case */
        hdata = exim_dkim_data_append(hdata, rh);
 
        DEBUG(D_acl) pdkim_quoteprint(rh, Ustrlen(rh));
@@ -1555,31 +1621,25 @@ for (sig = ctx->sig; sig; sig = sig->next)
   /* SIGNING ---------------------------------------------------------------- */
   if (ctx->flags & PDKIM_MODE_SIGN)
     {
-    es_ctx sctx;
+    hashmethod hm = sig->keytype == KEYTYPE_ED25519
+      ? HASH_SHA2_512 : pdkim_hashes[sig->hashtype].exim_hashmethod;
 
-    /* Import private key, including the keytype */
-/*XXX extend for non-RSA algos */
-    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;
-      }
-
-    /* Do signing.  With OpenSSL we are signing the hash of headers just
-    calculated, with GnuTLS we have to sign an entire block of headers
-    (due to available interfaces) and it recalculates the hash internally. */
+#ifdef SIGN_HAVE_ED25519
+    /* For GCrypt, and for EC, we pass the hash-of-headers to the signing
+    routine.  For anything else we just pass the headers. */
 
-#if defined(SIGN_GNUTLS)
-    hhash.data = hdata->s;
-    hhash.len =  hdata->ptr;
+    if (sig->keytype != KEYTYPE_ED25519)
 #endif
+      {
+      hhash.data = hdata->s;
+      hhash.len = hdata->ptr;
+      }
 
 /*XXX extend for non-RSA algos */
-    if ((*err = exim_dkim_sign(&sctx,
-                 pdkim_hashes[sig->hashtype].exim_hashmethod,
-                 &hhash, &sig->sighash)))
+/*- done for GnuTLS */
+    if ((*err = exim_dkim_sign(&sctx, hm, &hhash, &sig->sighash)))
       {
-      DEBUG(D_acl) debug_printf("signing: %s\n", *err);
+      log_write(0, LOG_MAIN|LOG_PANIC, "signing: %s", *err);
       return PDKIM_ERR_RSA_SIGNING;
       }
 
@@ -1636,7 +1696,12 @@ for (sig = ctx->sig; sig; sig = sig->next)
       }
 
     if (!(sig->pubkey = pdkim_key_from_dns(ctx, sig, &vctx, err)))
+      {
+      log_write(0, LOG_MAIN, "PDKIM: %s%s %s%s [failed key import]",
+       sig->domain   ? "d=" : "", sig->domain   ? sig->domain   : US"",
+       sig->selector ? "s=" : "", sig->selector ? sig->selector : US"");
       goto NEXT_VERIFY;
+      }
 
     /* If the pubkey limits to a list of specific hashes, ignore sigs that
     do not have the hash part of the sig algorithm matching */
@@ -1660,10 +1725,11 @@ for (sig = ctx->sig; sig; sig = sig->next)
       }
 
     /* Check the signature */
-/*XXX needs extension for non-RSA */
+/*XXX extend for non-RSA algos */
+/*- done for GnuTLS */
     if ((*err = exim_dkim_verify(&vctx,
-                 pdkim_hashes[sig->hashtype].exim_hashmethod,
-                 &hhash, &sig->sighash)))
+                               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;
@@ -1674,14 +1740,18 @@ for (sig = ctx->sig; sig; sig = sig->next)
 
     /* We have a winner! (if bodyhash was correct earlier) */
     if (sig->verify_status == PDKIM_VERIFY_NONE)
+      {
       sig->verify_status = PDKIM_VERIFY_PASS;
+      verify_pass = TRUE;
+      }
 
 NEXT_VERIFY:
 
     DEBUG(D_acl)
       {
-      debug_printf("PDKIM [%s] signature status: %s",
-             sig->domain, pdkim_verify_status_str(sig->verify_status));
+      debug_printf("PDKIM [%s] %s signature status: %s",
+             sig->domain, dkim_sig_to_a_tag(sig),
+             pdkim_verify_status_str(sig->verify_status));
       if (sig->verify_ext_status > 0)
        debug_printf(" (%s)\n",
                pdkim_verify_ext_status_str(sig->verify_ext_status));
@@ -1695,7 +1765,8 @@ NEXT_VERIFY:
 if (return_signatures)
   *return_signatures = ctx->sig;
 
-return PDKIM_OK;
+return ctx->flags & PDKIM_MODE_SIGN  ||  verify_pass
+  ? PDKIM_OK : PDKIM_FAIL;
 }
 
 
@@ -1719,8 +1790,6 @@ return ctx;
 
 /* -------------------------------------------------------------------------- */
 
-/*XXX ? needs extension to cover non-RSA algo?  */
-
 DLLEXPORT pdkim_signature *
 pdkim_init_sign(pdkim_ctx * ctx,
   uschar * domain, uschar * selector, uschar * privkey,
@@ -1742,15 +1811,15 @@ sig->bodylength = -1;
 sig->domain = string_copy(US domain);
 sig->selector = string_copy(US selector);
 sig->privkey = string_copy(US privkey);
-/*XXX no keytype yet; comes from privkey */
+sig->keytype = -1;
 
 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: unrecognised hashname '%s'\n", hashname);
+  log_write(0, LOG_MAIN|LOG_PANIC,
+    "PDKIM: unrecognised hashname '%s'", hashname);
   return NULL;
   }
 
index f467899..005249d 100644 (file)
@@ -72,6 +72,9 @@
 /* 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
 
@@ -103,10 +106,8 @@ typedef struct pdkim_pubkey {
   const uschar *granularity;      /* g=  */
 
   const uschar * hashes;          /* h=  */
-#ifdef notdef
-  uschar *keytype;                /* k=  */
-#endif
-  const uschar *srvtype;          /* s=  */
+  const uschar * keytype;         /* k=  */
+  const uschar * srvtype;         /* s=  */
   uschar *notes;                  /* n=  */
 
   blob  key;                      /* p=  */
@@ -139,6 +140,7 @@ typedef struct pdkim_signature {
   /* (v=) The version, as an integer. Currently, always "1" */
   int version;
 
+  /* (a=) The signature algorithm. Either PDKIM_ALGO_RSA_SHA256 */
   int keytype; /* pdkim_keytypes index */
   int hashtype;        /* pdkim_hashes index */
 
index 58edb4c..f73fa9c 100644 (file)
 
 /******************************************************************************/
 #ifdef SIGN_GNUTLS
+# define EXIM_GNUTLS_LIBRARY_LOG_LEVEL 3
+
+
+/* Logging function which can be registered with
+ *   gnutls_global_set_log_function()
+ *   gnutls_global_set_log_level() 0..9
+ */
+#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
+static void
+exim_gnutls_logger_cb(int level, const char *message)
+{
+size_t len = strlen(message);
+if (len < 1)
+  {
+  DEBUG(D_tls) debug_printf("GnuTLS<%d> empty debug message\n", level);
+  return;
+  }
+DEBUG(D_tls) debug_printf("GnuTLS<%d>: %s%s", level, message,
+    message[len-1] == '\n' ? "" : "\n");
+}
+#endif
+
+
 
 void
 exim_dkim_init(void)
 {
+#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
+DEBUG(D_tls)
+  {
+  gnutls_global_set_log_function(exim_gnutls_logger_cb);
+  /* arbitrarily chosen level; bump upto 9 for more */
+  gnutls_global_set_log_level(EXIM_GNUTLS_LIBRARY_LOG_LEVEL);
+  }
+#endif
 }
 
 
@@ -42,17 +73,27 @@ 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;
+gnutls_datum_t k = { .data = privkey_pem, .size = Ustrlen(privkey_pem) };
+gnutls_x509_privkey_t x509_key;
 int rc;
 
-k.data = privkey_pem;
-k.size = strlen(privkey_pem);
-
-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
+if (  (rc = gnutls_x509_privkey_init(&x509_key))
+   || (rc = gnutls_x509_privkey_import(x509_key, &k, GNUTLS_X509_FMT_PEM))
+   || (rc = gnutls_privkey_init(&sign_ctx->key))
+   || (rc = gnutls_privkey_import_x509(sign_ctx->key, x509_key, 0))
    )
-  return gnutls_strerror(rc);
+  return CUS gnutls_strerror(rc);
+
+switch (rc = gnutls_privkey_get_pk_algorithm(sign_ctx->key, NULL))
+  {
+  case GNUTLS_PK_RSA:          sign_ctx->keytype = KEYTYPE_RSA;     break;
+#ifdef SIGN_HAVE_ED25519
+  case GNUTLS_PK_EDDSA_ED25519:        sign_ctx->keytype = KEYTYPE_ED25519; break;
+#endif
+  default: return rc < 0
+    ? CUS gnutls_strerror(rc)
+    : string_sprintf("Unhandled key type: %d '%s'", rc, gnutls_pk_get_name(rc));
+  }
 
 return NULL;
 }
@@ -60,20 +101,17 @@ return NULL;
 
 
 /* allocate mem for signature (when signing) */
-/* sign data (gnutls_only)
-OR
-sign hash.
+/* hash & sign data.   No way to do incremental.
 
 Return: NULL for success, or an error string */
 
 const uschar *
 exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, blob * data, blob * sig)
 {
+gnutls_datum_t k_data = { .data = data->data, .size = data->len };
 gnutls_digest_algorithm_t dig;
-gnutls_datum_t k;
-size_t sigsize = 0;
+gnutls_datum_t k_sig;
 int rc;
-const uschar * ret = NULL;
 
 switch (hash)
   {
@@ -83,75 +121,87 @@ switch (hash)
   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->key, dig,
-  0, &k, NULL, &sigsize);
+if ((rc = gnutls_privkey_sign_data(sign_ctx->key, dig, 0, &k_data, &k_sig)))
+  return CUS gnutls_strerror(rc);
 
-sig->data = store_get(sigsize);
-sig->len = sigsize;
-
-/* Do signing */
-if ((rc = gnutls_x509_privkey_sign_data(sign_ctx->key, dig,
-           0, &k, sig->data, &sigsize)) != GNUTLS_E_SUCCESS
-   )
-  ret = gnutls_strerror(rc);
+/* Don't care about deinit for the key; shortlived process */
 
-gnutls_x509_privkey_deinit(sign_ctx->key);
-return ret;
+sig->data = k_sig.data;
+sig->len = k_sig.size;
+return NULL;
 }
 
 
 
-/* import public key (from DER in memory)
+/* import public key (from blob in memory)
 Return: NULL for success, or an error string */
 
 const uschar *
-exim_dkim_verify_init(blob * pubkey_der, ev_ctx * verify_ctx)
+exim_dkim_verify_init(blob * pubkey, keyformat fmt, ev_ctx * verify_ctx)
 {
 gnutls_datum_t k;
 int rc;
 const uschar * ret = NULL;
 
 gnutls_pubkey_init(&verify_ctx->key);
+k.data = pubkey->data;
+k.size = pubkey->len;
 
-k.data = pubkey_der->data;
-k.size = pubkey_der->len;
-
-if ((rc = gnutls_pubkey_import(verify_ctx->key, &k, GNUTLS_X509_FMT_DER))
-       != GNUTLS_E_SUCCESS)
-  ret = gnutls_strerror(rc);
+switch(fmt)
+  {
+  case KEYFMT_DER:
+    if ((rc = gnutls_pubkey_import(verify_ctx->key, &k, GNUTLS_X509_FMT_DER)))
+      ret = gnutls_strerror(rc);
+    break;
+#ifdef SIGN_HAVE_ED25519
+  case KEYFMT_ED25519_BARE:
+    if ((rc = gnutls_pubkey_import_ecc_raw(verify_ctx->key,
+                                         GNUTLS_ECC_CURVE_ED25519, &k, NULL)))
+      ret = gnutls_strerror(rc);
+    break;
+#endif
+  default:
+    ret = US"pubkey format not handled";
+    break;
+  }
 return ret;
 }
 
 
-/* verify signature (of hash)  (given pubkey & alleged sig)
+/* verify signature (of hash if RSA sig, of data if EC sig.  No way to do incremental)
+(given pubkey & alleged sig)
 Return: NULL for success, or an error string */
 
 const uschar *
 exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, blob * data_hash, blob * sig)
 {
-gnutls_sign_algorithm_t algo;
-gnutls_datum_t k, s;
+gnutls_datum_t k = { .data = data_hash->data, .size = data_hash->len };
+gnutls_datum_t s = { .data = sig->data,       .size = sig->len };
 int rc;
 const uschar * ret = NULL;
 
-/*XXX needs extension for non-rsa */
-switch (hash)
+#ifdef SIGN_HAVE_ED25519
+if (verify_ctx->keytype == KEYTYPE_ED25519)
   {
-  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";
+  if ((rc = gnutls_pubkey_verify_data2(verify_ctx->key,
+                                     GNUTLS_SIGN_EDDSA_ED25519, 0, &k, &s)) < 0)
+    ret = gnutls_strerror(rc);
   }
+else
+#endif
+  {
+  gnutls_sign_algorithm_t algo;
+  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->key, algo, 0, &k, &s)) < 0)
-  ret = gnutls_strerror(rc);
+  if ((rc = gnutls_pubkey_verify_hash2(verify_ctx->key, algo, 0, &k, &s)) < 0)
+    ret = gnutls_strerror(rc);
+  }
 
 gnutls_pubkey_deinit(verify_ctx->key);
 return ret;
@@ -177,8 +227,8 @@ 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]); */
+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)
@@ -208,6 +258,8 @@ long alen;
 int rc;
 gcry_error_t gerr;
 
+debug_printf_indent("%s\n", __FUNCTION__);
+
 /* integer; move past the header */
 if ((rc = as_tag(der, 0, ASN1_TAG_INTEGER, &alen)) != ASN1_SUCCESS)
   return US asn1_strerror(rc);
@@ -274,6 +326,7 @@ return g;   /*dummy*/
 
 
 /* import private key from PEM string in memory.
+Only handles RSA keys.
 Return: NULL for success, or an error string */
 
 const uschar *
@@ -285,7 +338,15 @@ long alen;
 int rc;
 
 /*XXX will need extension to _spot_ as well as handle a
-non-RSA key?  I think... */
+non-RSA key?  I think...
+So... this is not a PrivateKeyInfo - which would have a field
+identifying the keytype - PrivateKeyAlgorithmIdentifier -
+but a plain RSAPrivateKey (wrapped in PEM-headers.  Can we
+use those as a type tag?  What forms are there?  "BEGIN EC PRIVATE KEY" (cf. ec(1ssl))
+
+How does OpenSSL PEM_read_bio_PrivateKey() deal with it?
+gnutls_x509_privkey_import() ?
+*/
 
 /*
  *  RSAPrivateKey ::= SEQUENCE
@@ -299,6 +360,31 @@ non-RSA key?  I think... */
  *      exponent2         INTEGER,  -- d mod (q-1)
  *      coefficient       INTEGER,  -- (inverse of q) mod p
  *      otherPrimeInfos   OtherPrimeInfos OPTIONAL
+
+ * ECPrivateKey ::= SEQUENCE {
+ *     version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
+ *     privateKey     OCTET STRING,
+ *     parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
+ *     publicKey  [1] BIT STRING OPTIONAL
+ *   }
+ * Hmm, only 1 useful item, and not even an integer?  Wonder how we might use it...
+
+- actually, gnutls_x509_privkey_import() appears to require a curve name parameter
+       value for that is an OID? a local-only integer (it's an enum in GnuTLS)?
+
+
+Useful cmds:
+  ssh-keygen -t ecdsa -f foo.privkey
+  ssh-keygen -t ecdsa -b384 -f foo.privkey
+  ssh-keygen -t ecdsa -b521 -f foo.privkey
+  ssh-keygen -t ed25519 -f foo.privkey
+
+  < foo openssl pkcs8 -in /dev/stdin -inform PEM -nocrypt -topk8 -outform DER | od -x
+
+  openssl asn1parse -in foo -inform PEM -dump
+  openssl asn1parse -in foo -inform PEM -dump -stroffset 24    (??)
+(not good for ed25519)
+
  */
  
 if (  !(s1 = Ustrstr(CS privkey_pem, "-----BEGIN RSA PRIVATE KEY-----"))
@@ -357,6 +443,8 @@ DEBUG(D_acl) debug_printf_indent("rsa_signing_init:\n");
   debug_printf_indent(" QP: %s\n", s);
   }
 #endif
+
+sign_ctx->keytype = KEYTYPE_RSA;
 return NULL;
 
 asn_err: return US asn1_strerror(rc);
@@ -365,9 +453,7 @@ asn_err: return US asn1_strerror(rc);
 
 
 /* allocate mem for signature (when signing) */
-/* sign data (gnutls_only)
-OR
-sign hash.
+/* sign already-hashed data.
 
 Return: NULL for success, or an error string */
 
@@ -443,11 +529,11 @@ return NULL;
 }
 
 
-/* import public key (from DER in memory)
+/* import public key (from blob in memory)
 Return: NULL for success, or an error string */
 
 const uschar *
-exim_dkim_verify_init(blob * pubkey_der, ev_ctx * verify_ctx)
+exim_dkim_verify_init(blob * pubkey, keyformat fmt, ev_ctx * verify_ctx)
 {
 /*
 in code sequence per b81207d2bfa92 rsa_parse_public_key() and asn1_get_mpi()
@@ -460,6 +546,8 @@ uschar * errstr;
 gcry_error_t gerr;
 uschar * stage = US"S1";
 
+if (fmt != KEYFMT_DER) return US"pubkey format not handled";
+
 /*
 sequence
  sequence
@@ -476,33 +564,33 @@ openssl rsa -in aux-fixed/dkim/dkim.private -pubout | openssl asn1parse -dump -o
 */
 
 /* sequence; just move past the header */
-if ((rc = as_tag(pubkey_der, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL))
+if ((rc = as_tag(pubkey, 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))
+if ((rc = as_tag(pubkey, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, &alen))
    != ASN1_SUCCESS) goto asn_err;
-pubkey_der->data += alen; pubkey_der->len -= alen;
+pubkey->data += alen; pubkey->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)
+if ((rc = as_tag(pubkey, 0, ASN1_TAG_BIT_STRING, &alen)) != ASN1_SUCCESS)
   goto asn_err;
-pubkey_der->len = alen;
-pubkey_der->data++; pubkey_der->len--;
+pubkey->len = alen;
+pubkey->data++; pubkey->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))
+if ((rc = as_tag(pubkey, 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))
+if (  (errstr = as_mpi(pubkey, &verify_ctx->n))
+   || (errstr = as_mpi(pubkey, &verify_ctx->e))
    )
   return errstr;
 
@@ -525,7 +613,9 @@ DEBUG(D_acl) return string_sprintf("%s: %s", stage, asn1_strerror(rc));
 }
 
 
-/* verify signature (of hash)  (given pubkey & alleged sig)
+/* verify signature (of hash)
+XXX though we appear to be doing a hash, too!
+(given pubkey & alleged sig)
 Return: NULL for success, or an error string */
 
 const uschar *
@@ -589,11 +679,12 @@ ERR_load_crypto_strings();
 }
 
 
-/* accumulate data (gnutls-only) */
+/* accumulate data (was gnutls-onl but now needed for OpenSSL non-EC too
+because now using hash-and-sign interface) */
 gstring *
 exim_dkim_data_append(gstring * g, uschar * s)
 {
-return g;      /*dummy*/
+return string_cat(g, s);
 }
 
 
@@ -607,15 +698,21 @@ BIO * bp = BIO_new_mem_buf(privkey_pem, -1);
 
 if (!(sign_ctx->key = PEM_read_bio_PrivateKey(bp, NULL, NULL, NULL)))
   return US ERR_error_string(ERR_get_error(), NULL);
+
+sign_ctx->keytype =
+#ifdef SIGN_HAVE_ED25519
+       EVP_PKEY_type(EVP_PKEY_id(sign_ctx->key)) == EVP_PKEY_EC
+         ? KEYTYPE_ED25519 : KEYTYPE_RSA;
+#else
+       KEYTYPE_RSA;
+#endif
 return NULL;
 }
 
 
 
 /* allocate mem for signature (when signing) */
-/* sign data (gnutls_only)
-OR
-sign hash.
+/* hash & sign data.  Could be incremental
 
 Return: NULL for success with the signaature in the sig blob, or an error string */
 
@@ -623,7 +720,7 @@ const uschar *
 exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, blob * data, blob * sig)
 {
 const EVP_MD * md;
-EVP_PKEY_CTX * ctx;
+EVP_MD_CTX * ctx;
 size_t siglen;
 
 switch (hash)
@@ -634,56 +731,98 @@ switch (hash)
   default:             return US"nonhandled hash type";
   }
 
-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
+/* Create the Message Digest Context */
+/*XXX renamed to EVP_MD_CTX_new() in 1.1.0 */
+if(  (ctx = EVP_MD_CTX_create())
+
+/* Initialise the DigestSign operation */
+  && EVP_DigestSignInit(ctx, NULL, md, NULL, sign_ctx->key) > 0
+ /* Call update with the message */
+   && EVP_DigestSignUpdate(ctx, data->data, data->len) > 0
+ /* Finalise the DigestSign operation */
+ /* First call EVP_DigestSignFinal with a NULL sig parameter to obtain the length of the
+  * signature. Length is returned in slen */
+   && EVP_DigestSignFinal(ctx, NULL, &siglen) > 0
+
+ /* Allocate memory for the signature based on size in slen */
+   && (sig->data = store_get(siglen))
+
+ /* Obtain the signature (slen could change here!) */
+   && EVP_DigestSignFinal(ctx, sig->data, &siglen) > 0
    )
   {
-  /* Allocate mem for signature */
-  sig->data = store_get(siglen);
-
-  if (EVP_PKEY_sign(ctx, sig->data, &siglen, data->data, data->len) > 0)
-    {
-    EVP_PKEY_CTX_free(ctx);
-    sig->len = siglen;
-    return NULL;
-    }
+  EVP_MD_CTX_destroy(ctx);
+  sig->len = siglen;
+  return NULL;
   }
 
-if (ctx) EVP_PKEY_CTX_free(ctx);
+if (ctx) EVP_MD_CTX_destroy(ctx);
 return US ERR_error_string(ERR_get_error(), NULL);
 }
 
 
 
-/* import public key (from DER in memory)
+/* import public key (from blob in memory)
 Return: NULL for success, or an error string */
 
 const uschar *
-exim_dkim_verify_init(blob * pubkey_der, ev_ctx * verify_ctx)
+exim_dkim_verify_init(blob * pubkey, keyformat fmt, ev_ctx * verify_ctx)
 {
-const uschar * s = pubkey_der->data;
+const uschar * s = pubkey->data;
+uschar * ret = NULL;
 
-/*XXX hmm, we never free this */
+if (fmt != KEYFMT_DER) return US"pubkey format not handled";
+switch(fmt)
+  {
+  case KEYFMT_DER:
+    /*XXX ok, this fails for EC:
+    error:0609E09C:digital envelope routines:pkey_set_type:unsupported algorithm
+    */
+
+    /*XXX hmm, we never free this */
+    if (!(verify_ctx->key = d2i_PUBKEY(NULL, &s, pubkey->len)))
+      ret = US ERR_error_string(ERR_get_error(), NULL);
+    break;
+#ifdef SIGN_HAVE_ED25519
+  case KEYFMT_ED25519_BARE:
+    {
+    BIGNUM * x;
+    EC_KEY * eck;
+    if (  !(x = BN_bin2bn(s, pubkey->len, NULL))
+       || !(eck = EC_KEY_new_by_curve_name(NID_ED25519))
+       || !EC_KEY_set_public_key_affine_coordinates(eck, x, NULL)
+       || !(verify_ctx->key = EVP_PKEY_new())
+       || !EVP_PKEY_assign_EC_KEY(verify_ctx->key, eck)
+       )
+      ret = US ERR_error_string(ERR_get_error(), NULL);
+    }
+    break;
+#endif
+  default:
+    ret = US"pubkey format not handled";
+    break;
+  }
 
-if ((verify_ctx->key = d2i_PUBKEY(NULL, &s, pubkey_der->len)))
-  return NULL;
-return US ERR_error_string(ERR_get_error(), NULL);
+return ret;
 }
 
 
 
 
-/* verify signature (of hash)  (given pubkey & alleged sig)
+/* verify signature (of hash)
+(pre-EC coding; of data if "notyet" code, The latter could be incremental)
+(given pubkey & alleged sig)
 Return: NULL for success, or an error string */
 
 const uschar *
-exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, blob * data_hash, blob * sig)
+exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, blob * data, blob * sig)
 {
 const EVP_MD * md;
-EVP_PKEY_CTX * ctx;
+
+/*XXX OpenSSL does not seem to have Ed25519 support yet. Reportedly BoringSSL does,
+but that's a nonstable API and not recommended (by its owner, Google) for external use. */
 
 switch (hash)
   {
@@ -693,17 +832,50 @@ switch (hash)
   default:             return US"nonhandled hash type";
   }
 
-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; }
+#ifdef notyet_SIGN_HAVE_ED25519
+  {
+  EVP_MD_CTX * ctx;
 
-if (ctx) EVP_PKEY_CTX_free(ctx);
-return US ERR_error_string(ERR_get_error(), NULL);
+  /*XXX renamed to EVP_MD_CTX_new() in 1.1.0 */
+  if (
+        (ctx = EVP_MD_CTX_create())
+
+  /* Initialize `key` with a public key */
+     && EVP_DigestVerifyInit(ctx, NULL, md, NULL, verify_ctx->key) > 0
+
+  /* add data to be hashed (call multiple times if needed) */
+
+     && EVP_DigestVerifyUpdate(ctx, data->data, data->len) > 0
+
+  /* finish off the hash and check the offered signature */
+
+     && EVP_DigestVerifyFinal(ctx, sig->data, sig->len) > 0
+     )
+    {
+    EVP_MD_CTX_destroy(ctx);   /* renamed to _free in 1.1.0 */
+    return NULL;
+    }
+
+  if (ctx) EVP_MD_CTX_free(ctx);
+  return US ERR_error_string(ERR_get_error(), NULL);
+  }
+#else
+  {
+  EVP_PKEY_CTX * ctx;
+
+  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->data, data->len) == 1
+     )
+    { EVP_PKEY_CTX_free(ctx); return NULL; }
+
+  if (ctx) EVP_PKEY_CTX_free(ctx);
+  return US ERR_error_string(ERR_get_error(), NULL);
+  }
+#endif
 }
 
 
index 61a1a0a..8d56981 100644 (file)
 #elif defined(SIGN_GNUTLS)
 # include <gnutls/gnutls.h>
 # include <gnutls/x509.h>
-#  include <gnutls/abstract.h>
+# include <gnutls/abstract.h>
 #elif defined(SIGN_GCRYPT)
-#  include <gcrypt.h>
-#  include <libtasn1.h>
+# include <gcrypt.h>
+# include <libtasn1.h>
 #endif
 
 #include "../blob.h"
 
+typedef enum {
+  KEYTYPE_RSA,
+  KEYTYPE_ED25519
+} keytype;
+
+typedef enum {
+  KEYFMT_DER,          /* an asn.1 structure */
+  KEYFMT_ED25519_BARE  /* just the key */
+} keyformat;
+
 
 #ifdef SIGN_OPENSSL
 
 typedef struct {
-  EVP_PKEY * key;
+  keytype      keytype;
+  EVP_PKEY *   key;
 } es_ctx;
 
 typedef struct {
-  EVP_PKEY * key;
+  keytype      keytype;
+  EVP_PKEY *   key;
 } ev_ctx;
 
 #elif defined(SIGN_GNUTLS)
 
 typedef struct {
-  gnutls_x509_privkey_t key;
+  keytype      keytype;
+  gnutls_privkey_t key;
 } es_ctx;
 
 typedef struct {
+  keytype      keytype;
   gnutls_pubkey_t key;
 } ev_ctx;
 
 #elif defined(SIGN_GCRYPT)
 
 typedef struct {
-  int  keytype;
+  keytype      keytype;
   gcry_mpi_t n;
   gcry_mpi_t e;
   gcry_mpi_t d;
@@ -63,7 +77,7 @@ typedef struct {
 } es_ctx;
 
 typedef struct {
-  int  keytype;
+  keytype      keytype;
   gcry_mpi_t n;
   gcry_mpi_t e;
 } ev_ctx;
@@ -76,7 +90,7 @@ extern gstring * exim_dkim_data_append(gstring *, uschar *);
 
 extern const uschar * exim_dkim_signing_init(uschar *, es_ctx *);
 extern const uschar * exim_dkim_sign(es_ctx *, hashmethod, blob *, blob *);
-extern const uschar * exim_dkim_verify_init(blob *, ev_ctx *);
+extern const uschar * exim_dkim_verify_init(blob *, keyformat, ev_ctx *);
 extern const uschar * exim_dkim_verify(ev_ctx *, hashmethod, blob *, blob *);
 
 #endif /*DISABLE_DKIM*/
index edcc4ab..26631f3 100644 (file)
@@ -8,6 +8,7 @@ CFLAGS=@CFLAGS@ @BIND_8_COMPAT@ @DEFS@
 LDFLAGS=@LDFLAGS@
 CLIENT_SSL=@CLIENT_SSL@
 CLIENT_GNUTLS=@CLIENT_GNUTLS@
+B64_GNUTLS=@B64_GNUTLS@
 LOADED=@LOADED@
 LOADED_OPT=@LOADED_OPT@
 LIBS=@LIBS@
@@ -18,7 +19,8 @@ SRC = @srcdir@/src
 
 BINARIES =     bin/cf bin/client $(CLIENT_SSL) $(CLIENT_GNUTLS) \
                 bin/checkaccess bin/fakens bin/fd bin/iefbr14 $(LOADED) \
-                bin/mtpscript bin/server bin/showids bin/locate
+                bin/mtpscript bin/server bin/showids bin/locate \
+               $(B64_GNUTLS)
 
 # List of targets
 
@@ -85,6 +87,10 @@ bin/locate:     $(SRC)/locate.sh Makefile
                cp $(SRC)/locate.pl bin/locate
                chmod 0755 bin/locate
 
+bin/ed25519_privkey_pem_to_pubkey_raw_b64:     $(SRC)/ed25519_privkey_pem_to_pubkey_raw_b64.c Makefile
+               $(CC) $(CFLAGS) -DHAVE_GNUTLS $(LDFLAGS) -o bin/ed25519_privkey_pem_to_pubkey_raw_b64 \
+                       $(SRC)/ed25519_privkey_pem_to_pubkey_raw_b64.c  -lgnutls -lgcrypt $(LIBS)
+
 clean:;         rm -rf $(BINARIES) bin.sys
 
 FORCE:
diff --git a/test/aux-fixed/dkim/dkim_ed25519.private b/test/aux-fixed/dkim/dkim_ed25519.private
new file mode 100644 (file)
index 0000000..a532b8d
--- /dev/null
@@ -0,0 +1,3 @@
+-----BEGIN PRIVATE KEY-----
+MC4CAQAwBQYDK2VwBCIEIMCVDSGjt6hBzzc/Km1UBZ7nMcvLCZSqeiay3rhuQIqF
+-----END PRIVATE KEY-----
index 26489c6..78f7341 100755 (executable)
@@ -623,6 +623,7 @@ ac_subst_vars='LTLIBOBJS
 LIBOBJS
 LOADED_OPT
 LOADED
+B64_GNUTLS
 CLIENT_GNUTLS
 CLIENT_SSL
 BIND_8_COMPAT
@@ -3242,6 +3243,18 @@ fi
 
 done
 
+for ac_header in gnutls/gnutls.h
+do :
+  ac_fn_c_check_header_mongrel "$LINENO" "gnutls/gnutls.h" "ac_cv_header_gnutls_gnutls_h" "$ac_includes_default"
+if test "x$ac_cv_header_gnutls_gnutls_h" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_GNUTLS_GNUTLS_H 1
+_ACEOF
+ B64_GNUTLS=bin/ed25519_privkey_pem_to_pubkey_raw_b64
+fi
+
+done
+
 
 
 
@@ -3501,6 +3514,7 @@ fi
 
 
 
+
 ac_config_files="$ac_config_files Makefile"
 
 cat >confcache <<\_ACEOF
index 017d22d..858b8e3 100644 (file)
@@ -18,6 +18,7 @@ dnl Checks for header files.
 AC_CHECK_HEADERS(sys/socket.h)
 AC_CHECK_HEADERS(openssl/crypto.h,[CLIENT_SSL=bin/client-ssl])
 AC_CHECK_HEADERS(gnutls/gnutls.h,[CLIENT_GNUTLS=bin/client-gnutls])
+AC_CHECK_HEADERS(gnutls/gnutls.h,[B64_GNUTLS=bin/ed25519_privkey_pem_to_pubkey_raw_b64])
 
 dnl The check on dynamically loaded modules requires the building of
 dnl something to load. This seems to be something that varies between
@@ -61,6 +62,7 @@ dnl "Export" these variables
 AC_SUBST(BIND_8_COMPAT)
 AC_SUBST(CLIENT_SSL)
 AC_SUBST(CLIENT_GNUTLS)
+AC_SUBST(B64_GNUTLS)
 AC_SUBST(LOADED)
 AC_SUBST(LOADED_OPT)
 AC_SUBST(LIBS)
diff --git a/test/confs/4505 b/test/confs/4505
new file mode 120000 (symlink)
index 0000000..c4f73ba
--- /dev/null
@@ -0,0 +1 @@
+4500
\ No newline at end of file
index 9092c74..8332fa1 100644 (file)
@@ -50,9 +50,11 @@ send_to_server:
   dkim_selector =      sel
 .endif
 
-  dkim_private_key =   ${if match {$dkim_selector}{^ses}       {DDIR/dkim512.private} \
-                         {${if match {$dkim_selector}{^sel} {DDIR/dkim.private} \
-                         {}}}}
+  dkim_private_key =   ${extract {${length_3:$dkim_selector}} {\
+                               ses=dkim512.private \
+                               sel=dkim.private \
+                               sed=dkim_ed25519.private \
+                               }{DDIR/$value}}
 
 .ifndef HEADERS_MAXSIZE
   dkim_sign_headers =  OPT
diff --git a/test/confs/4525 b/test/confs/4525
new file mode 120000 (symlink)
index 0000000..072f5fa
--- /dev/null
@@ -0,0 +1 @@
+4520
\ No newline at end of file
index 9bd39df..08aadb9 100644 (file)
@@ -553,4 +553,12 @@ ses_sha256._domainkey TXT "v=DKIM1; h=sha256; p=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
 sel2._domainkey TXT "v=spf1 mx a include:spf.nl2go.com -all"
 sel2._domainkey TXT "v=DKIM1; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDXRFf+VhT+lCgFhhSkinZKcFNeRzjYdW8vT29Rbb3NadvTFwAd+cVLPFwZL8H5tUD/7JbUPqNTCPxmpgIL+V5T4tEZMorHatvvUM2qfcpQ45IfsZ+YdhbIiAslHCpy4xNxIR3zylgqRUF4+Dtsaqy3a5LhwMiKCLrnzhXk1F1hxwIDAQAB"
 
+; EC signing, using Ed25519
+; - needs GnuTLS 3.6.0 (fedora rawhide has that)
+;           certtool --generate-privkey --key-type=ed25519 --outfile=dkim_ed25519.private
+;           bin/ed25519_privkey_pem_to_pubkey_raw_b64 dkim_ed25519.private
+
+sed._domainkey TXT "v=DKIM1; k=ed25519; p=sPs07Vu29FpHT/80UXUcYHFOHifD4o2ZlP2+XUh9g6E="
+
+
 ; End
index efe78d2..dbbaa74 100644 (file)
@@ -10,6 +10,7 @@
 1999-03-02 09:44:33 10HmaZ-0005vi-00 signer: test.ex bits: 1024
 1999-03-02 09:44:33 10HmaZ-0005vi-00 DKIM: d=test.ex s=sel c=relaxed/simple a=rsa-sha1 b=1024 [verification succeeded]
 1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss DKIM=test.ex
+1999-03-02 09:44:33 10HmbA-0005vi-00 PDKIM: d=test.ex s=sel_bad [failed key import]
 1999-03-02 09:44:33 10HmbA-0005vi-00 signer: test.ex bits: 1024
 1999-03-02 09:44:33 10HmbA-0005vi-00 DKIM: d=test.ex s=sel_bad c=relaxed/relaxed a=rsa-sha1 b=1024 [invalid - syntax error in public key record]
 1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=564CFC9B.1040905@yahoo.com
index 55374fa..2693a94 100644 (file)
@@ -1,6 +1,7 @@
 
 ******** 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: validation error: Public key signature verification has failed.
 1999-03-02 09:44:33 10HmaX-0005vi-00 signer: test.ex bits: 1024
 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 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=qwerty1234@disco-zombie.net
index a4dee26..b678522 100644 (file)
@@ -1,6 +1,7 @@
 
 ******** 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: validation error: Public key signature verification has failed.
 1999-03-02 09:44:33 10HmaX-0005vi-00 signer: test.ex bits: 1024
 1999-03-02 09:44:33 10HmaX-0005vi-00 DKIM: d=test.ex s=sel2 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 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=qwerty1234@disco-zombie.net
diff --git a/test/log/4505 b/test/log/4505
new file mode 100644 (file)
index 0000000..388fcf5
--- /dev/null
@@ -0,0 +1,11 @@
+
+******** 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 signer: test.ex bits: 512
+1999-03-02 09:44:33 10HmaX-0005vi-00 DKIM: d=test.ex s=sed c=relaxed/relaxed a=ed25519-sha256 b=512 [verification succeeded]
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss DKIM=test.ex id=E10HmaY-0005vi-00@myhost.test.ex
+1999-03-02 09:44:33 10HmaZ-0005vi-00 signer: kitterman.org bits: 512
+1999-03-02 09:44:33 10HmaZ-0005vi-00 DKIM: d=kitterman.org s=ed25519 c=relaxed/simple a=ed25519-sha256 b=512 i=@kitterman.org t=1517847601 [verification succeeded]
+1999-03-02 09:44:33 10HmaZ-0005vi-00 signer: @kitterman.org bits: 512
+1999-03-02 09:44:33 10HmaZ-0005vi-00 DKIM: d=kitterman.org s=ed25519 c=relaxed/simple a=ed25519-sha256 b=512 i=@kitterman.org t=1517847601 [verification succeeded]
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss DKIM=kitterman.org id=example@example.com
index 1c39568..62cea9d 100644 (file)
@@ -10,8 +10,8 @@
 1999-03-02 09:44:33 10HmbA-0005vi-00 signer: test.ex bits: 1024
 1999-03-02 09:44:33 10HmbA-0005vi-00 DKIM: d=test.ex s=sel c=simple/simple a=rsa-sha1 b=1024 [verification failed - body hash mismatch (body probably modified in transit)]
 1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=qwerty1234@disco-zombie.net
-1999-03-02 09:44:33 10HmbB-0005vi-00 DKIM: validation error: RSA_LONG_LINE
-1999-03-02 09:44:33 10HmbB-0005vi-00 DKIM: Error during validation, disabling signature verification: RSA_LONG_LINE
+1999-03-02 09:44:33 10HmbB-0005vi-00 DKIM: validation error: LONG_LINE
+1999-03-02 09:44:33 10HmbB-0005vi-00 DKIM: Error during validation, disabling signature verification: LONG_LINE
 1999-03-02 09:44:33 10HmbB-0005vi-00 <= CALLER@bloggs.com H=(xxx) [127.0.0.1] P=smtp S=sss id=qwerty1234@disco-zombie.net
 1999-03-02 09:44:33 10HmbC-0005vi-00 signer: test.ex bits: 512
 1999-03-02 09:44:33 10HmbC-0005vi-00 DKIM: d=test.ex s=ses_sha256 c=simple/simple a=rsa-sha1 b=512 [verification failed - unspecified reason]
index 593cd66..44a1269 100644 (file)
@@ -80,6 +80,7 @@
 1999-03-02 09:44:33 10HmbK-0005vi-00 => :blackhole: <c@test.ex> R=server_dump
 1999-03-02 09:44:33 10HmbK-0005vi-00 Completed
 1999-03-02 09:44:33 rcpt acl: macro: From:Sender:Reply-To:Subject:Date:Message-ID: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 10HmbM-0005vi-00 PDKIM: d=test.ex s=sel_bad [failed key import]
 1999-03-02 09:44:33 10HmbM-0005vi-00 dkim_acl: signer: test.ex bits: 1024 h=From
 1999-03-02 09:44:33 10HmbM-0005vi-00 DKIM: d=test.ex s=sel_bad c=relaxed/relaxed a=rsa-sha256 b=1024 [invalid - syntax error in public key record]
 1999-03-02 09:44:33 10HmbM-0005vi-00 data acl: dkim status invalid
diff --git a/test/log/4525 b/test/log/4525
new file mode 100644 (file)
index 0000000..a2c5026
--- /dev/null
@@ -0,0 +1,25 @@
+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
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaZ-0005vi-00 => b@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] C="250 OK id=10HmbA-0005vi-00"
+1999-03-02 09:44:33 10HmaZ-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 rcpt acl: macro: From:Sender:Reply-To:Subject:Date:Message-ID: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 dkim_acl: signer: test.ex bits: 512 h=From:To:Subject
+1999-03-02 09:44:33 10HmaY-0005vi-00 DKIM: d=test.ex s=sed c=relaxed/relaxed a=ed25519-sha256 b=512 [verification succeeded]
+1999-03-02 09:44:33 10HmaY-0005vi-00 data acl: dkim status pass
+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
+1999-03-02 09:44:33 rcpt acl: macro: From:Sender:Reply-To:Subject:Date:Message-ID: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 10HmbA-0005vi-00 dkim_acl: signer: test.ex bits: 512 h=From
+1999-03-02 09:44:33 10HmbA-0005vi-00 DKIM: d=test.ex s=sed c=relaxed/relaxed a=ed25519-sha256 b=512 [verification succeeded]
+1999-03-02 09:44:33 10HmbA-0005vi-00 dkim_acl: signer: test.ex bits: 1024 h=From
+1999-03-02 09:44:33 10HmbA-0005vi-00 DKIM: d=test.ex s=sel c=relaxed/relaxed a=rsa-sha256 b=1024 [verification succeeded]
+1999-03-02 09:44:33 10HmbA-0005vi-00 data acl: dkim status pass:pass
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtp S=sss id=E10HmaZ-0005vi-00@myhost.test.ex
+1999-03-02 09:44:33 10HmbA-0005vi-00 => :blackhole: <b@test.ex> R=server_dump
+1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
index 445e41a..3554379 100644 (file)
@@ -5,10 +5,10 @@ Received: from localhost ([127.0.0.1] helo=testhost.test.ex)
        id 10HmaY-0005vi-00
        for y@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=test.ex;
-       s=sel; h=LIST; bh=CVpkzY75tV/NCKk5pPx4GnM3NX83xwCiT0xVwo0G1Rs=; b=TIqPqpKM5qf
-       ZFlv2H8yio5RybWA3sLCtVmE6HmBhBKqW+uqLKG2grqJhVMJ3qXnvQQ3ixnMjMlJqfCpEBtxfsSR9
-       MGLPP9ZMdlrBNEL6XKlgE+X8bAra5zkuLZs8gy8H3/mtEfoKPs4ltB/ZK/j2FHG2+CEx+TDTIkh9E
-       wkAMrA=;
+       s=sel; h=Subject; bh=CVpkzY75tV/NCKk5pPx4GnM3NX83xwCiT0xVwo0G1Rs=; b=JTYpVY1D
+       sO37MibaZTC2CgpQAZlz/lRefFQv3Q7JM4D0aUfseT24Xg+kxv3xc5guSzKWQzycm3zie366tHape
+       lu70O4/5+Dyr0f/FKjmYxT+ALcIzuVN7Rty2JioBG07aryqJqmcR0xpmiggctb/h/2a/JGRKPcDWO
+       psj50XQNQ=;
 Received: from [127.0.0.1] (helo=xxx)
        by testhost.test.ex with esmtp (Exim x.yz)
        (envelope-from <CALLER@bloggs.com>)
index 1b47355..f81ae7b 100644 (file)
@@ -5,10 +5,10 @@ Received: from localhost ([127.0.0.1] helo=testhost.test.ex)
        id 10HmaX-0005vi-00
        for z@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=test.ex;
-       s=sel; h=LIST; bh=CVpkzY75tV/NCKk5pPx4GnM3NX83xwCiT0xVwo0G1Rs=; b=TIqPqpKM5qf
-       ZFlv2H8yio5RybWA3sLCtVmE6HmBhBKqW+uqLKG2grqJhVMJ3qXnvQQ3ixnMjMlJqfCpEBtxfsSR9
-       MGLPP9ZMdlrBNEL6XKlgE+X8bAra5zkuLZs8gy8H3/mtEfoKPs4ltB/ZK/j2FHG2+CEx+TDTIkh9E
-       wkAMrA=;
+       s=sel; h=Subject; bh=CVpkzY75tV/NCKk5pPx4GnM3NX83xwCiT0xVwo0G1Rs=; b=JTYpVY1D
+       sO37MibaZTC2CgpQAZlz/lRefFQv3Q7JM4D0aUfseT24Xg+kxv3xc5guSzKWQzycm3zie366tHape
+       lu70O4/5+Dyr0f/FKjmYxT+ALcIzuVN7Rty2JioBG07aryqJqmcR0xpmiggctb/h/2a/JGRKPcDWO
+       psj50XQNQ=;
 Received: from [127.0.0.1] (helo=xxx)
        by testhost.test.ex with esmtp (Exim x.yz)
        (envelope-from <CALLER@bloggs.com>)
index 4153160..035c56c 100755 (executable)
@@ -1194,6 +1194,7 @@ RESET_AFTER_EXTRA_LINE_READ:
     # openssl version variances
     s/(TLS error on connection [^:]*: error:)[0-9A-F]{8}(:system library):(?:fopen|func\(4095\)):(No such file or directory)$/$1xxxxxxxx$2:fopen:$3/;
     s/(DANE attempt failed.*error:)[0-9A-F]{8}(:SSL routines:)(ssl3_get_server_certificate|tls_process_server_certificate|CONNECT_CR_CERT)(?=:certificate verify failed$)/$1xxxxxxxx$2ssl3_get_server_certificate/;
+    s/(DKIM: validation error: )error:[0-9A-F]{8}:rsa routines:int_rsa_verify:bad signature$/$1Public key signature verification has failed./;
     }
 
   # ======== All files other than stderr ========
diff --git a/test/scripts/4500-DKIM/4505 b/test/scripts/4500-DKIM/4505
new file mode 100644 (file)
index 0000000..0be08ea
--- /dev/null
@@ -0,0 +1,83 @@
+# DKIM verify, ed25519
+#
+exim -DSERVER=server -bd -oX PORT_D
+****
+#
+# This should pass, only Mail::DKIM::Signer does not handle ed25519-sha256 yet
+#
+# Mail original (will be)in aux-fixed/4500.msg1.txt
+# Sig generated by: perl aux-fixed/dkim/sign.pl --algorithm=ed255190sha256 \
+#                      --method=simple/simple < aux-fixed/4500.msg1.txt
+#
+# TODO - until we have that we can only test internal consistency,
+# signing vs. verification.  For now, use a message we signed with
+# the Exim GnuTLS implementation (then we can test GnuTLS vs. others)
+#
+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=ed25519-sha256; q=dns/txt; c=relaxed/relaxed; d=test.ex
+       ; s=sed; h=From:To:Subject; bh=/Ab0giHZitYQbDhFszoqQRUkgqueaX9zatJttIU/plc=;
+        b=5fhyD3EILDrnL4DnkD4hDaeis7+GSzL9GMHrhIDZJjuJ00WD5iI8SQ1q9rDfzFL/Kdw0VIyB4R
+       Dq0a4H6HI+Bw==;
+Received: from jgh by myhost.test.ex with local (Exim x.yz)
+       envelope-from <jgh@myhost.test.ex>)
+        1dtXln-0000YP-Hb
+        a@test.ex; Sun, 17 Sep 2017 12:29:51 +0100
+From: nobody@example.com
+Message-Id: <E1dtXln-0000YP-Hb@myhost.test.ex>
+Sender: CALLER_NAME <jgh@myhost.test.ex>
+Date: Sun, 17 Sep 2017 12:29:51 +0100
+
+content
+.
+??? 250
+QUIT
+??? 221
+****
+#
+#
+# This should pass, an independently-generated sample from Scott Kitterman.
+# I don't want to retain this longterm as it hits an external DNS record,
+# not under the testsuite.
+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=ed25519-sha256; c=relaxed/simple; d=kitterman.org;
+ i=@kitterman.org; q=dns/txt; s=ed25519; t=1517847601;
+ h=message-id : date : from : to : subject : date : from :
+ subject; bh=wE7NXSkgnx9PGiavN4OZhJztvkqPDlemV3OGuEnLwNo=;
+ b=sEnnE99Xsjpcqa/cNf8k/KQCEgjJ/4tswIKoNvq2q0fFQL6XBORJ2fQb
+ Fvt34Tb4sOxlZtBYu01kEJlmGz4uCw==
+Authentication-Results: lists.example.org; arc=none; spf=pass smtp.mfrom=example.com; dmarc=pass
+Received: from localhost
+Message-ID: <example@example.com>
+Date: Mon, 01 Jan 2011 01:02:03 +0400
+From: Test User <test@example.com>
+To: somebody@example.com
+Subject: Testing
+
+This is a test message.
+.
+??? 250
+QUIT
+??? 221
+****
+#
+killdaemon
+no_stdout_check
+no_msglog_check
diff --git a/test/scripts/4500-DKIM/4525 b/test/scripts/4500-DKIM/4525
new file mode 100644 (file)
index 0000000..cc53a96
--- /dev/null
@@ -0,0 +1,24 @@
+# DKIM signing, ed25519
+#
+exim -bd -DSERVER=server -oX PORT_D
+****
+#
+# Privkey used here is:  aux-fixed/dkim/dkim_ed25519.private (set in the conf)
+#
+exim -DSELECTOR=sed -DOPT=From:To:Subject -odf a@test.ex
+From: nobody@example.com
+
+content
+****
+#
+# Multiple-signing test (rsa + ed25519)
+#
+exim -DSELECTOR=sed:sel -DOPT=From: -odf b@test.ex
+From: nobody@example.com
+
+content
+****
+#
+millisleep 500
+killdaemon
+no_msglog_check
index 1465d58..fb98e55 100644 (file)
@@ -1,6 +1,6 @@
 # DKIM, CHUNKING, wireformat-spoolfile
 #
-exim -bd -DSERVER=server -DOPT=dkim -oX PORT_S:PORT_D
+exim -bd -DSERVER=server -DOPT=dkim -DLIST=Subject -oX PORT_S:PORT_D
 ****
 #
 # 1: non-CHUNKING injection; will not be stored as wireformat therefore
diff --git a/test/src/ed25519_privkey_pem_to_pubkey_raw_b64.c b/test/src/ed25519_privkey_pem_to_pubkey_raw_b64.c
new file mode 100644 (file)
index 0000000..f6639b7
--- /dev/null
@@ -0,0 +1,139 @@
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* Unix includes */
+
+typedef unsigned char uschar;
+
+#define CS   (char *)
+#define US   (unsigned char *)
+
+#define FALSE         0
+#define TRUE          1
+
+
+
+#ifdef HAVE_GNUTLS
+
+
+#include <gnutls/gnutls.h>
+#include <gnutls/abstract.h>
+#include <gnutls/x509.h>
+
+#if GNUTLS_VERSION_NUMBER >= 0x030600
+# define SIGN_HAVE_ED25519
+#endif
+
+
+
+static uschar *enc64table =
+  US"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+uschar *
+b64encode(uschar *clear, int len)
+{
+uschar *code = malloc(4*((len+2)/3) + 2);
+uschar *p = code;
+
+while (len-- >0)
+  {
+  int x, y;
+
+  x = *clear++;
+  *p++ = enc64table[(x >> 2) & 63];
+
+  if (len-- <= 0)
+    {
+    *p++ = enc64table[(x << 4) & 63];
+    *p++ = '=';
+    *p++ = '=';
+    break;
+    }
+
+  y = *clear++;
+  *p++ = enc64table[((x << 4) | ((y >> 4) & 15)) & 63];
+
+  if (len-- <= 0)
+    {
+    *p++ = enc64table[(y << 2) & 63];
+    *p++ = '=';
+    break;
+    }
+
+  x = *clear++;
+  *p++ = enc64table[((y << 2) | ((x >> 6) & 3)) & 63];
+
+  *p++ = enc64table[x & 63];
+  }
+
+*p = 0;
+
+return code;
+}
+
+/*************************************************
+*                 Main Program                   *
+*************************************************/
+
+
+int
+main(int argc, char **argv)
+{
+uschar * pemfile = argv[1];
+int fd;
+uschar buf[1024];
+int len, rc;
+gnutls_privkey_t privkey;
+gnutls_datum_t k;
+gnutls_pubkey_t pubkey;
+uschar * b64;
+
+#ifdef SIGN_HAVE_ED25519
+if ((fd = open(CS pemfile, O_RDONLY)) < 0)
+  exit(1);
+
+if ((len = read(fd, buf, sizeof(buf)-1)) < 0)
+  exit(2);
+
+k.data = buf;
+k.size = len;
+
+if (  (rc = gnutls_privkey_init(&privkey))
+   || (rc = gnutls_privkey_import_x509_raw(privkey, &k, GNUTLS_X509_FMT_PEM, NULL, GNUTLS_PKCS_PLAIN))
+   || (rc = gnutls_pubkey_init(&pubkey))
+   || (rc = gnutls_pubkey_import_privkey(pubkey, privkey, GNUTLS_KEY_DIGITAL_SIGNATURE, 0))
+   || (rc = gnutls_pubkey_export_ecc_raw2(pubkey, NULL, &k, NULL, GNUTLS_EXPORT_FLAG_NO_LZ))
+   )
+  fprintf(stderr, "%s\n", gnutls_strerror(rc));
+
+b64 = b64encode(k.data, k.size);
+
+printf("%s\n", b64);
+exit(0);
+
+#else
+fprintf(stderr, "No support for ed25519 signing in GnuTLS (version %s)\n", gnutls_check_version(NULL));
+exit(3);
+#endif
+}
+
+#endif
+
+#ifdef HAVE_OPENSSL
+int
+main(int argc, char **argv)
+{
+fprintf(stderr, "No support for ed25519 signing in OpenSSL\n");
+exit(3);
+}
+
+#endif
index 207889b..dd1cb8c 100644 (file)
@@ -184,6 +184,7 @@ end of ACL "rcpt": ACCEPT
 >>Headers added by MAIL or RCPT ACL:
   X-ACL-Warn: added header line
 >>
+PDKIM: no signatures
 LOG: MAIN
   <= ok@test3 H=[10.9.8.8] U=CALLER P=smtp S=sss
 Exim version x.yz ....
index c558b42..4b149a4 100644 (file)
@@ -53,6 +53,7 @@ P Received: from [V4NET.9.8.7]
        (envelope-from <x@y>)
        id 10HmbF-0005vi-00
        for warn_empty@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+PDKIM: no signatures
 calling local_scan(); timeout=300
 local_scan() returned 0 NULL
 LOG: MAIN
@@ -123,6 +124,7 @@ P Received: from [V4NET.9.8.7]
        (envelope-from <x@y>)
        id 10HmbG-0005vi-00
        for warn_log@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+PDKIM: no signatures
 calling local_scan(); timeout=300
 local_scan() returned 0 NULL
 LOG: MAIN
@@ -194,6 +196,7 @@ P Received: from [V4NET.9.8.7]
 >>Headers added by MAIL or RCPT ACL:
   X-ACL-Warn: warn user message
 >>
+PDKIM: no signatures
 calling local_scan(); timeout=300
 local_scan() returned 0 NULL
 LOG: MAIN
index edf35c1..5853432 100644 (file)
@@ -101,6 +101,7 @@ P Received: from [V4NET.2.3.4]
        by myhost.test.ex with esmtp (Exim x.yz)
        id 10HmaX-0005vi-00
        for x@y; Tue, 2 Mar 1999 09:44:33 +0000
+PDKIM: no signatures
 calling local_scan(); timeout=300
 local_scan() returned 0 NULL
 LOG: MAIN
@@ -177,6 +178,7 @@ P Received: from host.name.tld ([V4NET.2.3.4])
        by myhost.test.ex with esmtp (Exim x.yz)
        id 10HmaY-0005vi-00
        for x@y; Tue, 2 Mar 1999 09:44:33 +0000
+PDKIM: no signatures
 calling local_scan(); timeout=300
 local_scan() returned 0 NULL
 LOG: MAIN
index 9ff9306..9ecca77 100644 (file)
@@ -87,6 +87,7 @@ P Received: from [V4NET.0.0.0] (helo=something)
        (envelope-from <x@y>)
        id 10HmaX-0005vi-00
        for x@y; Tue, 2 Mar 1999 09:44:33 +0000
+PDKIM: no signatures
 using ACL "data"
 processing "accept"
 check set acl_m0 = $acl_m0; data
index 245137e..89f313b 100644 (file)
@@ -206,6 +206,7 @@ P Received: from [V4NET.11.12.13] (ident=CALLER)
   X-Warning: V4NET.11.12.13 is listed at rbl.test.ex
   X-Warning: This is a test blacklisting message
 >>
+PDKIM: no signatures
 calling local_scan(); timeout=300
 local_scan() returned 0 NULL
 Writing spool header file: TESTSUITE/spool//input//hdr.pppp
@@ -389,6 +390,7 @@ P Received: from [V4NET.11.12.13] (ident=CALLER)
   X-Warning: V4NET.11.12.13 is listed at rbl.test.ex
   X-Warning: This is a test blacklisting message
 >>
+PDKIM: no signatures
 calling local_scan(); timeout=300
 local_scan() returned 0 NULL
 Writing spool header file: TESTSUITE/spool//input//hdr.pppp
index 52dcbf3..6d5e595 100644 (file)
@@ -79,6 +79,7 @@ Data file written for message 10HmaY-0005vi-00
 P Received: from CALLER by myhost.test.ex with local-smtp (Exim x.yz)
        id 10HmaY-0005vi-00
        for abc@domain; Tue, 2 Mar 1999 09:44:33 +0000
+PDKIM: no signatures
 using ACL "check_data"
 processing "accept"
 check verify = header_syntax
@@ -152,6 +153,7 @@ Data file written for message 10HmaX-0005vi-00
 P Received: from CALLER by myhost.test.ex with local-smtp (Exim x.yz)
        id 10HmaX-0005vi-00
        for abc@xyz; Tue, 2 Mar 1999 09:44:33 +0000
+PDKIM: no signatures
 using ACL "check_data"
 processing "accept"
 check verify = header_syntax
index ad2daa2..e65c6a7 100644 (file)
@@ -59,6 +59,7 @@ P Received: from CALLER (helo=x.y)
        (envelope-from <x@y>)
        id 10HmaX-0005vi-00
        for userx@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+PDKIM: no signatures
 calling local_scan(); timeout=300
 local_scan() returned 0 NULL
 Writing spool header file: TESTSUITE/spool//input//hdr.pppp
index 7346788..c2df9c7 100644 (file)
@@ -48,6 +48,7 @@ P Received: from [V4NET.0.0.0]
        (envelope-from <x@y>)
        id 10HmaX-0005vi-00
        for x@y; Tue, 2 Mar 1999 09:44:33 +0000
+PDKIM: no signatures
 calling local_scan(); timeout=300
 local_scan() returned 0 NULL
 LOG: MAIN
index 946c48a..3f2c479 100644 (file)
@@ -227,6 +227,7 @@ end of inline ACL: ACCEPT
        for userx@domain.com
 ----------- start cutthrough headers send -----------
 ----------- done cutthrough headers send ------------
+PDKIM: no signatures
  ┌considering: ${tod_full}
  ├──expanding: ${tod_full}
  └─────result: Tue, 2 Mar 1999 09:44:33 +0000
@@ -441,6 +442,7 @@ end of inline ACL: ACCEPT
        for usery@domain.com
 ----------- start cutthrough headers send -----------
 ----------- done cutthrough headers send ------------
+PDKIM: no signatures
  ┌considering: ${tod_full}
  ├──expanding: ${tod_full}
  └─────result: Tue, 2 Mar 1999 09:44:33 +0000
@@ -655,6 +657,7 @@ end of inline ACL: ACCEPT
        for usery@domain.com
 ----------- start cutthrough headers send -----------
 ----------- done cutthrough headers send ------------
+PDKIM: no signatures
  ┌considering: ${tod_full}
  ├──expanding: ${tod_full}
  └─────result: Tue, 2 Mar 1999 09:44:33 +0000
index 97af80b..d2dc05d 100644 (file)
@@ -226,6 +226,7 @@ end of inline ACL: ACCEPT
        for userx@domain.com
 ----------- start cutthrough headers send -----------
 ----------- done cutthrough headers send ------------
+PDKIM: no signatures
  ┌considering: ${tod_full}
  ├──expanding: ${tod_full}
  └─────result: Tue, 2 Mar 1999 09:44:33 +0000
@@ -440,6 +441,7 @@ end of inline ACL: ACCEPT
        for usery@domain.com
 ----------- start cutthrough headers send -----------
 ----------- done cutthrough headers send ------------
+PDKIM: no signatures
  ┌considering: ${tod_full}
  ├──expanding: ${tod_full}
  └─────result: Tue, 2 Mar 1999 09:44:33 +0000
@@ -654,6 +656,7 @@ end of inline ACL: ACCEPT
        for usery@domain.com
 ----------- start cutthrough headers send -----------
 ----------- done cutthrough headers send ------------
+PDKIM: no signatures
  ┌considering: ${tod_full}
  ├──expanding: ${tod_full}
  └─────result: Tue, 2 Mar 1999 09:44:33 +0000