summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJeremy Harris <jgh146exb@wizmail.org>2018-02-06 14:24:23 +0000
committerJeremy Harris <jgh146exb@wizmail.org>2018-02-06 15:04:01 +0000
commit286b9d5fa4344de72fe6575fa089237fd7dbb56f (patch)
tree752fa289d3ebdf80e55bff1889cb2696a689e951 /src
parentd584cdcac04235b9323a34c049a1c5dc2cd2a309 (diff)
DKIM: Ed25519 signatures (GnuTLS 3.6.0 and later)
Diffstat (limited to 'src')
-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
7 files changed, 448 insertions, 192 deletions
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*/