From 286b9d5fa4344de72fe6575fa089237fd7dbb56f Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Tue, 6 Feb 2018 14:24:23 +0000 Subject: [PATCH] DKIM: Ed25519 signatures (GnuTLS 3.6.0 and later) --- doc/doc-docbook/spec.xfpt | 26 +- doc/doc-txt/NewStuff | 3 + src/src/base64.c | 2 +- src/src/dkim.c | 10 +- src/src/pdkim/crypt_ver.h | 5 +- src/src/pdkim/pdkim.c | 187 ++++++--- src/src/pdkim/pdkim.h | 10 +- src/src/pdkim/signing.c | 394 +++++++++++++----- src/src/pdkim/signing.h | 32 +- test/Makefile.in | 8 +- test/aux-fixed/dkim/dkim_ed25519.private | 3 + test/configure | 14 + test/configure.ac | 2 + test/confs/4505 | 1 + test/confs/4520 | 8 +- test/confs/4525 | 1 + test/dnszones-src/db.test.ex | 8 + test/log/4502 | 1 + test/log/4503 | 1 + test/log/4504 | 1 + test/log/4505 | 11 + test/log/4506 | 4 +- test/log/4520 | 1 + test/log/4525 | 25 ++ test/mail/4530.y | 8 +- test/mail/4530.z | 8 +- test/runtest | 1 + test/scripts/4500-DKIM/4505 | 83 ++++ test/scripts/4500-DKIM/4525 | 24 ++ test/scripts/4500-DKIM/4530 | 2 +- .../ed25519_privkey_pem_to_pubkey_raw_b64.c | 139 ++++++ test/stderr/0021 | 1 + test/stderr/0022 | 3 + test/stderr/0303 | 2 + test/stderr/0371 | 1 + test/stderr/0386 | 2 + test/stderr/0465 | 2 + test/stderr/0487 | 1 + test/stderr/0575 | 1 + test/stderr/5410 | 3 + test/stderr/5420 | 3 + 41 files changed, 832 insertions(+), 210 deletions(-) create mode 100644 test/aux-fixed/dkim/dkim_ed25519.private create mode 120000 test/confs/4505 create mode 120000 test/confs/4525 create mode 100644 test/log/4505 create mode 100644 test/log/4525 create mode 100644 test/scripts/4500-DKIM/4505 create mode 100644 test/scripts/4500-DKIM/4525 create mode 100644 test/src/ed25519_privkey_pem_to_pubkey_raw_b64.c 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:} / ${sha3_:} 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 -# 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 # include -# include +# include #elif defined(SIGN_GCRYPT) -# include -# include +# include +# include #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: 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: 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: 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 ) 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 ) 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: +??? 250 +RCPT TO: +??? 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 ) + 1dtXln-0000YP-Hb + a@test.ex; Sun, 17 Sep 2017 12:29:51 +0100 +From: nobody@example.com +Message-Id: +Sender: CALLER_NAME +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: +??? 250 +RCPT TO: +??? 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: +Date: Mon, 01 Jan 2011 01:02:03 +0400 +From: Test User +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 +#include +#include + +#include +#include +#include +#include +#include +#include + +/* Unix includes */ + +typedef unsigned char uschar; + +#define CS (char *) +#define US (unsigned char *) + +#define FALSE 0 +#define TRUE 1 + + + +#ifdef HAVE_GNUTLS + + +#include +#include +#include + +#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 ) 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 ) 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 ) 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 ) 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 ) 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 -- 2.30.2