summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/doc-docbook/spec.xfpt26
-rw-r--r--doc/doc-txt/NewStuff3
-rw-r--r--src/src/base64.c2
-rw-r--r--src/src/dkim.c10
-rw-r--r--src/src/pdkim/crypt_ver.h5
-rw-r--r--src/src/pdkim/pdkim.c187
-rw-r--r--src/src/pdkim/pdkim.h10
-rw-r--r--src/src/pdkim/signing.c394
-rw-r--r--src/src/pdkim/signing.h32
-rw-r--r--test/Makefile.in8
-rw-r--r--test/aux-fixed/dkim/dkim_ed25519.private3
-rwxr-xr-xtest/configure14
-rw-r--r--test/configure.ac2
l---------test/confs/45051
-rw-r--r--test/confs/45208
l---------test/confs/45251
-rw-r--r--test/dnszones-src/db.test.ex8
-rw-r--r--test/log/45021
-rw-r--r--test/log/45031
-rw-r--r--test/log/45041
-rw-r--r--test/log/450511
-rw-r--r--test/log/45064
-rw-r--r--test/log/45201
-rw-r--r--test/log/452525
-rw-r--r--test/mail/4530.y8
-rw-r--r--test/mail/4530.z8
-rwxr-xr-xtest/runtest1
-rw-r--r--test/scripts/4500-DKIM/450583
-rw-r--r--test/scripts/4500-DKIM/452524
-rw-r--r--test/scripts/4500-DKIM/45302
-rw-r--r--test/src/ed25519_privkey_pem_to_pubkey_raw_b64.c139
-rw-r--r--test/stderr/00211
-rw-r--r--test/stderr/00223
-rw-r--r--test/stderr/03032
-rw-r--r--test/stderr/03711
-rw-r--r--test/stderr/03862
-rw-r--r--test/stderr/04652
-rw-r--r--test/stderr/04871
-rw-r--r--test/stderr/05751
-rw-r--r--test/stderr/54103
-rw-r--r--test/stderr/54203
41 files changed, 832 insertions, 210 deletions
diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt
index 0eccce1ec..b5865e966 100644
--- a/doc/doc-docbook/spec.xfpt
+++ b/doc/doc-docbook/spec.xfpt
@@ -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:
diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff
index c3f013ee8..ee40553a6 100644
--- a/doc/doc-txt/NewStuff
+++ b/doc/doc-txt/NewStuff
@@ -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
------------
diff --git a/src/src/base64.c b/src/src/base64.c
index f6f187f07..ae6874b8a 100644
--- a/src/src/base64.c
+++ b/src/src/base64.c
@@ -245,7 +245,7 @@ uschar *p = code;
while (len-- >0)
{
- register int x, y;
+ int x, y;
x = *clear++;
*p++ = enc64table[(x >> 2) & 63];
diff --git a/src/src/dkim.c b/src/src/dkim.c
index 2a66b4ac8..c7bf64152 100644
--- a/src/src/dkim.c
+++ b/src/src/dkim.c
@@ -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 */
diff --git a/src/src/pdkim/crypt_ver.h b/src/src/pdkim/crypt_ver.h
index bf620366f..7b0ddf92a 100644
--- a/src/src/pdkim/crypt_ver.h
+++ b/src/src/pdkim/crypt_ver.h
@@ -14,8 +14,11 @@
#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
diff --git a/src/src/pdkim/pdkim.c b/src/src/pdkim/pdkim.c
index 365234f4e..eec1a9c16 100644
--- a/src/src/pdkim/pdkim.c
+++ b/src/src/pdkim/pdkim.c
@@ -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;
}
diff --git a/src/src/pdkim/pdkim.h b/src/src/pdkim/pdkim.h
index f46789985..005249d15 100644
--- a/src/src/pdkim/pdkim.h
+++ b/src/src/pdkim/pdkim.h
@@ -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 */
diff --git a/src/src/pdkim/signing.c b/src/src/pdkim/signing.c
index 58edb4cdd..f73fa9cc8 100644
--- a/src/src/pdkim/signing.c
+++ b/src/src/pdkim/signing.c
@@ -20,10 +20,41 @@
/******************************************************************************/
#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
}
diff --git a/src/src/pdkim/signing.h b/src/src/pdkim/signing.h
index 61a1a0ad4..8d569812f 100644
--- a/src/src/pdkim/signing.h
+++ b/src/src/pdkim/signing.h
@@ -19,39 +19,53 @@
#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*/
diff --git a/test/Makefile.in b/test/Makefile.in
index edcc4ab78..26631f398 100644
--- a/test/Makefile.in
+++ b/test/Makefile.in
@@ -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
index 000000000..a532b8d74
--- /dev/null
+++ b/test/aux-fixed/dkim/dkim_ed25519.private
@@ -0,0 +1,3 @@
+-----BEGIN PRIVATE KEY-----
+MC4CAQAwBQYDK2VwBCIEIMCVDSGjt6hBzzc/Km1UBZ7nMcvLCZSqeiay3rhuQIqF
+-----END PRIVATE KEY-----
diff --git a/test/configure b/test/configure
index 26489c630..78f734198 100755
--- a/test/configure
+++ b/test/configure
@@ -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
diff --git a/test/configure.ac b/test/configure.ac
index 017d22d38..858b8e30f 100644
--- a/test/configure.ac
+++ b/test/configure.ac
@@ -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
index 000000000..c4f73bacd
--- /dev/null
+++ b/test/confs/4505
@@ -0,0 +1 @@
+4500 \ No newline at end of file
diff --git a/test/confs/4520 b/test/confs/4520
index 9092c74dc..8332fa1d1 100644
--- a/test/confs/4520
+++ b/test/confs/4520
@@ -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
index 000000000..072f5faf2
--- /dev/null
+++ b/test/confs/4525
@@ -0,0 +1 @@
+4520 \ No newline at end of file
diff --git a/test/dnszones-src/db.test.ex b/test/dnszones-src/db.test.ex
index 9bd39dfeb..08aadb963 100644
--- a/test/dnszones-src/db.test.ex
+++ b/test/dnszones-src/db.test.ex
@@ -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
diff --git a/test/log/4502 b/test/log/4502
index efe78d2f0..dbbaa7420 100644
--- a/test/log/4502
+++ b/test/log/4502
@@ -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
diff --git a/test/log/4503 b/test/log/4503
index 55374fa33..2693a947c 100644
--- a/test/log/4503
+++ b/test/log/4503
@@ -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
diff --git a/test/log/4504 b/test/log/4504
index a4dee26bc..b67852209 100644
--- a/test/log/4504
+++ b/test/log/4504
@@ -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
index 000000000..388fcf58e
--- /dev/null
+++ b/test/log/4505
@@ -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
diff --git a/test/log/4506 b/test/log/4506
index 1c39568c0..62cea9db4 100644
--- a/test/log/4506
+++ b/test/log/4506
@@ -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]
diff --git a/test/log/4520 b/test/log/4520
index 593cd6692..44a12694d 100644
--- a/test/log/4520
+++ b/test/log/4520
@@ -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
index 000000000..a2c502607
--- /dev/null
+++ b/test/log/4525
@@ -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
diff --git a/test/mail/4530.y b/test/mail/4530.y
index 445e41a95..35543799b 100644
--- a/test/mail/4530.y
+++ b/test/mail/4530.y
@@ -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>)
diff --git a/test/mail/4530.z b/test/mail/4530.z
index 1b4735507..f81ae7b01 100644
--- a/test/mail/4530.z
+++ b/test/mail/4530.z
@@ -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>)
diff --git a/test/runtest b/test/runtest
index 41531609e..035c56cdc 100755
--- a/test/runtest
+++ b/test/runtest
@@ -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
index 000000000..0be08ea31
--- /dev/null
+++ b/test/scripts/4500-DKIM/4505
@@ -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
index 000000000..cc53a96c0
--- /dev/null
+++ b/test/scripts/4500-DKIM/4525
@@ -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
diff --git a/test/scripts/4500-DKIM/4530 b/test/scripts/4500-DKIM/4530
index 1465d5896..fb98e5564 100644
--- a/test/scripts/4500-DKIM/4530
+++ b/test/scripts/4500-DKIM/4530
@@ -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
index 000000000..f6639b758
--- /dev/null
+++ b/test/src/ed25519_privkey_pem_to_pubkey_raw_b64.c
@@ -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
diff --git a/test/stderr/0021 b/test/stderr/0021
index 207889b0d..dd1cb8c7b 100644
--- a/test/stderr/0021
+++ b/test/stderr/0021
@@ -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 ....
diff --git a/test/stderr/0022 b/test/stderr/0022
index c558b4226..4b149a426 100644
--- a/test/stderr/0022
+++ b/test/stderr/0022
@@ -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
diff --git a/test/stderr/0303 b/test/stderr/0303
index edf35c14f..5853432ca 100644
--- a/test/stderr/0303
+++ b/test/stderr/0303
@@ -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
diff --git a/test/stderr/0371 b/test/stderr/0371
index 9ff930690..9ecca772c 100644
--- a/test/stderr/0371
+++ b/test/stderr/0371
@@ -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
diff --git a/test/stderr/0386 b/test/stderr/0386
index 245137ea2..89f313b93 100644
--- a/test/stderr/0386
+++ b/test/stderr/0386
@@ -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
diff --git a/test/stderr/0465 b/test/stderr/0465
index 52dcbf3d5..6d5e59593 100644
--- a/test/stderr/0465
+++ b/test/stderr/0465
@@ -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
diff --git a/test/stderr/0487 b/test/stderr/0487
index ad2daa2f8..e65c6a7eb 100644
--- a/test/stderr/0487
+++ b/test/stderr/0487
@@ -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
diff --git a/test/stderr/0575 b/test/stderr/0575
index 73467881a..c2df9c7da 100644
--- a/test/stderr/0575
+++ b/test/stderr/0575
@@ -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
diff --git a/test/stderr/5410 b/test/stderr/5410
index 946c48a2a..3f2c47994 100644
--- a/test/stderr/5410
+++ b/test/stderr/5410
@@ -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
diff --git a/test/stderr/5420 b/test/stderr/5420
index 97af80b4f..d2dc05d94 100644
--- a/test/stderr/5420
+++ b/test/stderr/5420
@@ -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