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 0eccce1ec3d6f0c188d62695fe2d4084db1b3c41..b5865e966fce06e1bf24e96309e5b7a378f54007 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 c3f013ee85a4784a9600fece8480cf3b5b7e6138..ee40553a613d15c8bb998c144960e892b6b0f1c9 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 f6f187f075a0f924b6915872af9900e09ba0d580..ae6874b8a8b255335116fd81ef4746d5c40ffdbe 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 2a66b4ac89e7c790753eabc95b0e23be3a3c9f94..c7bf641527822a7fecc97fe383ca7c548a5ea2f1 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 bf620366f02116ee3448448d69bffa010a75a8dc..7b0ddf92a97045df109b9e08a8ddcb4440b39a7c 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 365234f4e82d44308d7e81f9be4a081b43fa5eaf..eec1a9c1621268d94fbac86295b9547c3bea99d2 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 f46789985585f152d4730a0297085477749aac64..005249d154ff1690cf10282b695b8c7a73d98b26 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 58edb4cdd2b815e83a9d9ac7c7452ed673adad21..f73fa9cc8f00631f0cd158abd6a854877afd7ffb 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 61a1a0ad4950906371e46203e89e517c6a8beb8f..8d569812fdb2ed55984192b6ea4e69e7afc513c7 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 edcc4ab78c6eb416ce9a2cdb66f78641744e4cee..26631f39852b531239847dd2db959b5002b3030f 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 26489c630efe0cfb4130c222d8a65fa24ef4e718..78f734198055c5d15ac653cd1b2ff36e39a98eda 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 017d22d381c8d44019ccd0f81e4a51e798bcd601..858b8e30f8b02747171feaa03a71838bbba4b2e5 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 9092c74dc665b96b5d3836ca80493dd15008f9ab..8332fa1d1e7a7b5c73eeb09698b96a21e2a8aa27 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 9bd39dfeb553628f7387ce3eac57b10937b71cad..08aadb963f55daed084385fd230f580db44dbc4b 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 efe78d2f0370ad3560df29d7ef666c8cbbb543d3..dbbaa7420f19654d4968fba52e038e5760743c90 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 55374fa33c1580f76c11c3c265b4eacf90b7e366..2693a947c0a4a95659b52dcf76ec4a274bcb04bf 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 a4dee26bca68ebddb257383565fffd24f2ccb873..b67852209f435f73f899e0ad8ea79f1990719ea0 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 1c39568c00c8a30d7bf6bce526e32c2120a6dc9f..62cea9db417921f218dc837dc08438bc62be0bd9 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 593cd6692df4693aa15e62308365073073092614..44a12694d5086fe60cbd79b2a4aa7bef3d2b3aaa 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 445e41a95127bc6b09d1c18c1756408372b572e9..35543799b23855fb92133f00dac6ee01b7d0712e 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 1b4735507d0443d205157b7cf31f4a0aa68bee1c..f81ae7b01eeb21eae47c17c97d93ce24076d7729 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 41531609e3a9192c9962e64a26e5269388e7a360..035c56cdcaef9be987fc141d6f2cc6ba11f9646a 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 1465d5896fd0b7105f4447d1ba730923b833a1b6..fb98e5564f4fbb88a4df436cf22dbcc06d27b4ea 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 207889b0d0a58c17b8e484f14ea108fe904ef9fb..dd1cb8c7b2f4c63134ff23f58ebcdf46975ff6d2 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 c558b4226e1168698e0303621c044433a3c25026..4b149a426a8d869fd37f67ae6ebc9f47a6974e2e 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 edf35c14f2851e36de7357d5ba1575c8e8dc6f0f..5853432ca59b1d0821ee3dd412f99dd54c005ace 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 9ff9306902489ba1b76214f85b4475a636af6ca1..9ecca772c81516eb1c1ae8408c53f9b157814917 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 245137ea2a3fe85d974eba111fd71e2223cb7983..89f313b934e75212e420287bc8d288856a6b77b8 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 52dcbf3d50afa5ecc6d8ee23dd00f3bf6ed1af20..6d5e595937d7782941e0ab9fa4216289f10aebe2 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 ad2daa2f87f7698959474ad48d58eaeab72b40e0..e65c6a7eb3da6490ff02d469ce3681a3319a204e 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 73467881a97b354ec2c772226e6d4364525f044e..c2df9c7da6b5684e25a73275c6e3a576812d0b02 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 946c48a2a9a1977bd8f8b4b9521e4ed85c80c4b7..3f2c47994de833ca05630986512ffffca1ba411a 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 97af80b4f693aa27c549f4b982b0a3036bc89d52..d2dc05d945fe81b2e9cee68266ec86fa1752d3e1 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