From cb78c1a805d1e86dad86d8eb031eb0517a62ec20 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Sat, 17 Mar 2018 23:39:54 +0000 Subject: [PATCH] DKIM: Ed25519 signatures under OpenSSL (1.1.1 or later) OpenSSL 1.1.1 is not released yet, but operation has been checked against the current source --- doc/doc-docbook/spec.xfpt | 6 ++- doc/doc-txt/NewStuff | 2 +- doc/doc-txt/openssl.txt | 3 ++ src/src/hash.h | 1 + src/src/pdkim/crypt_ver.h | 6 ++- src/src/pdkim/pdkim.c | 43 ++++++++++++++---- src/src/pdkim/signing.c | 96 +++++++++++++++------------------------ 7 files changed, 85 insertions(+), 72 deletions(-) diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index aea31dd66..295cb15c1 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -38903,7 +38903,8 @@ The result can either 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) +with GnuTLS 3.6.0 or OpenSSL 1.1.1 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 @@ -39114,7 +39115,8 @@ 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'. +If running under GnuTLS 3.6.0 or OpenSSL 1.1.1 or later, +may also be 'ed25519-sha256'. The "_CRYPTO_SIGN_ED25519" macro will be defined if support is present for EC keys. .wen diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff index e4de435a9..58f3f2054 100644 --- a/doc/doc-txt/NewStuff +++ b/doc/doc-txt/NewStuff @@ -34,7 +34,7 @@ Version 4.91 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. + GnuTLS 3.6.0 or OpenSSL 1.1.1 or later. 10. Builtin feature-macros _CRYPTO_HASH_SHA3 and _CRYPTO_SIGN_ED25519, library version dependent. diff --git a/doc/doc-txt/openssl.txt b/doc/doc-txt/openssl.txt index e4f5d854c..93ca701a9 100644 --- a/doc/doc-txt/openssl.txt +++ b/doc/doc-txt/openssl.txt @@ -55,6 +55,8 @@ the relevant directory into the rpath stamped into the binary: USE_OPENSSL_PC=openssl LDFLAGS+=-ldl -Wl,-rpath,/opt/openssl/lib +[jgh: I've see /usr/local/lib used] + The -ldl is needed by OpenSSL 1.0.2+ on Linux and is not needed on most other platforms. The LDFLAGS is needed because `pkg-config` doesn't know how to emit information about RPATH-stamping, but we can still leverage @@ -94,6 +96,7 @@ is to run: readelf -d $(which exim) | grep RPATH +[jgh: I've seen that spelled RUNPATH] Very Advanced ------------- diff --git a/src/src/hash.h b/src/src/hash.h index a29d46531..5bd47acd1 100644 --- a/src/src/hash.h +++ b/src/src/hash.h @@ -30,6 +30,7 @@ typedef enum hashmethod { HASH_BADTYPE, + HASH_NULL, HASH_SHA1, HASH_SHA2_256, diff --git a/src/src/pdkim/crypt_ver.h b/src/src/pdkim/crypt_ver.h index e14ee5f4c..0982eb788 100644 --- a/src/src/pdkim/crypt_ver.h +++ b/src/src/pdkim/crypt_ver.h @@ -17,7 +17,7 @@ # if GNUTLS_VERSION_NUMBER >= 0x030000 # define SIGN_GNUTLS # if GNUTLS_VERSION_NUMBER >= 0x030600 -# define SIGN_HAVE_ED25519 /*MMMM*/ +# define SIGN_HAVE_ED25519 # endif # else # define SIGN_GCRYPT @@ -25,5 +25,9 @@ #else # define SIGN_OPENSSL +# if OPENSSL_VERSION_NUMBER >= 0x10101000L +# define SIGN_HAVE_ED25519 +# endif + #endif diff --git a/src/src/pdkim/pdkim.c b/src/src/pdkim/pdkim.c index 457d83efc..e291d9dd3 100644 --- a/src/src/pdkim/pdkim.c +++ b/src/src/pdkim/pdkim.c @@ -1390,6 +1390,24 @@ DEBUG(D_acl) debug_printf( /* Import public key */ +/* Normally we use the signature a= tag to tell us the pubkey format. +When signing under debug we do a test-import of the pubkey, and at that +time we do not have a signature so we must interpret the pubkey k= tag +instead. Assume writing on the sig is ok in that case. */ + +if (sig->keytype < 0) + { + int i; + for(i = 0; i < nelem(pdkim_keytypes); i++) + if (Ustrcmp(p->keytype, pdkim_keytypes[i]) == 0) + { sig->keytype = i; goto k_ok; } + DEBUG(D_acl) debug_printf("verify_init: unhandled keytype %s\n", p->keytype); + sig->verify_status = PDKIM_VERIFY_INVALID; + sig->verify_ext_status = PDKIM_VERIFY_INVALID_PUBKEY_IMPORT; + return NULL; + } +k_ok: + if ((*errstr = exim_dkim_verify_init(&p->key, sig->keytype == KEYTYPE_ED25519 ? KEYFMT_ED25519_BARE : KEYFMT_DER, vctx))) @@ -1662,7 +1680,12 @@ for (sig = ctx->sig; sig; sig = sig->next) if (ctx->flags & PDKIM_MODE_SIGN) { hashmethod hm = sig->keytype == KEYTYPE_ED25519 - ? HASH_SHA2_512 : pdkim_hashes[sig->hashtype].exim_hashmethod; +#if defined(SIGN_OPENSSL) + ? HASH_NULL +#else + ? HASH_SHA2_512 +#endif + : pdkim_hashes[sig->hashtype].exim_hashmethod; #ifdef SIGN_HAVE_ED25519 /* For GCrypt, and for EC, we pass the hash-of-headers to the signing @@ -1675,8 +1698,6 @@ for (sig = ctx->sig; sig; sig = sig->next) hhash.len = hdata->ptr; } -/*XXX extend for non-RSA algos */ -/*- done for GnuTLS */ if ((*err = exim_dkim_sign(&sctx, hm, &hhash, &sig->sighash))) { log_write(0, LOG_MAIN|LOG_PANIC, "signing: %s", *err); @@ -1696,6 +1717,7 @@ for (sig = ctx->sig; sig; sig = sig->next) else { ev_ctx vctx; + hashmethod hm; /* Make sure we have all required signature tags */ if (!( sig->domain && *sig->domain @@ -1764,12 +1786,17 @@ for (sig = ctx->sig; sig; sig = sig->next) } } + hm = sig->keytype == KEYTYPE_ED25519 +#if defined(SIGN_OPENSSL) + ? HASH_NULL +#else + ? HASH_SHA2_512 +#endif + : pdkim_hashes[sig->hashtype].exim_hashmethod; + /* Check the signature */ -/*XXX extend for non-RSA algos */ -/*- done for GnuTLS */ - if ((*err = exim_dkim_verify(&vctx, - pdkim_hashes[sig->hashtype].exim_hashmethod, - &hhash, &sig->sighash))) + + if ((*err = exim_dkim_verify(&vctx, hm, &hhash, &sig->sighash))) { DEBUG(D_acl) debug_printf("headers verify: %s\n", *err); sig->verify_status = PDKIM_VERIFY_FAIL; diff --git a/src/src/pdkim/signing.c b/src/src/pdkim/signing.c index b182c9a20..62e32234f 100644 --- a/src/src/pdkim/signing.c +++ b/src/src/pdkim/signing.c @@ -88,7 +88,7 @@ Return: NULL for success, or an error string */ const uschar * exim_dkim_signing_init(const uschar * privkey_pem, es_ctx * sign_ctx) { -gnutls_datum_t k = { .data = privkey_pem, .size = Ustrlen(privkey_pem) }; +gnutls_datum_t k = { .data = (void *)privkey_pem, .size = Ustrlen(privkey_pem) }; gnutls_x509_privkey_t x509_key; int rc; @@ -716,7 +716,7 @@ if (!(sign_ctx->key = PEM_read_bio_PrivateKey(bp, NULL, NULL, NULL))) sign_ctx->keytype = #ifdef SIGN_HAVE_ED25519 - EVP_PKEY_type(EVP_PKEY_id(sign_ctx->key)) == EVP_PKEY_EC + EVP_PKEY_type(EVP_PKEY_id(sign_ctx->key)) == EVP_PKEY_ED25519 ? KEYTYPE_ED25519 : KEYTYPE_RSA; #else KEYTYPE_RSA; @@ -727,7 +727,7 @@ return NULL; /* allocate mem for signature (when signing) */ -/* hash & sign data. Could be incremental +/* hash & sign data. Incremental not supported. Return: NULL for success with the signaature in the sig blob, or an error string */ @@ -740,31 +740,36 @@ size_t siglen; switch (hash) { + case HASH_NULL: md = NULL; break; /* Ed25519 signing */ case HASH_SHA1: md = EVP_sha1(); break; case HASH_SHA2_256: md = EVP_sha256(); break; case HASH_SHA2_512: md = EVP_sha512(); break; default: return US"nonhandled hash type"; } -/* 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 +#ifdef SIGN_HAVE_ED25519 +if ( (ctx = EVP_MD_CTX_new()) + && EVP_DigestSignInit(ctx, NULL, md, NULL, sign_ctx->key) > 0 + && EVP_DigestSign(ctx, NULL, &siglen, NULL, 0) > 0 + && (sig->data = store_get(siglen)) - /* Call update with the message */ + /* Obtain the signature (slen could change here!) */ + && EVP_DigestSign(ctx, sig->data, &siglen), data->data, data->len > 0 + ) + { + EVP_MD_CTX_destroy(ctx); + sig->len = siglen; + return NULL; + } +#else +/*XXX renamed to EVP_MD_CTX_new() in 1.1.0 */ +if ( (ctx = EVP_MD_CTX_create()) + && EVP_DigestSignInit(ctx, NULL, md, NULL, sign_ctx->key) > 0 && 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!) */ + + /* Obtain the signature (slen could change here!) */ && EVP_DigestSignFinal(ctx, sig->data, &siglen) > 0 ) { @@ -772,6 +777,7 @@ if( (ctx = EVP_MD_CTX_create()) sig->len = siglen; return NULL; } +#endif if (ctx) EVP_MD_CTX_destroy(ctx); return US ERR_error_string(ERR_get_error(), NULL); @@ -788,31 +794,18 @@ exim_dkim_verify_init(blob * pubkey, keyformat fmt, ev_ctx * verify_ctx) const uschar * s = pubkey->data; uschar * ret = NULL; -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) - ) + if (!(verify_ctx->key = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, + s, pubkey->len))) ret = US ERR_error_string(ERR_get_error(), NULL); - } break; #endif default: @@ -826,8 +819,7 @@ return ret; -/* verify signature (of hash) -(pre-EC coding; of data if "notyet" code, The latter could be incremental) +/* verify signature (of hash, except Ed25519 where of-data) (given pubkey & alleged sig) Return: NULL for success, or an error string */ @@ -836,45 +828,30 @@ exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, blob * data, blob * sig) { const EVP_MD * md; -/*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) { + case HASH_NULL: md = NULL; break; case HASH_SHA1: md = EVP_sha1(); break; case HASH_SHA2_256: md = EVP_sha256(); break; case HASH_SHA2_512: md = EVP_sha512(); break; default: return US"nonhandled hash type"; } -#ifdef notyet_SIGN_HAVE_ED25519 +#ifdef SIGN_HAVE_ED25519 +if (!md) { EVP_MD_CTX * ctx; - /*XXX renamed to EVP_MD_CTX_new() in 1.1.0 */ - if ( - (ctx = EVP_MD_CTX_create()) - - /* Initialize `key` with a public key */ + if ( (ctx = EVP_MD_CTX_new()) && 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_DigestVerify(ctx, sig->data, sig->len, data->data, data->len) > 0 ) - { - EVP_MD_CTX_destroy(ctx); /* renamed to _free in 1.1.0 */ - return NULL; - } + { EVP_MD_CTX_free(ctx); return NULL; } if (ctx) EVP_MD_CTX_free(ctx); - return US ERR_error_string(ERR_get_error(), NULL); } -#else +else +#endif { EVP_PKEY_CTX * ctx; @@ -888,9 +865,8 @@ switch (hash) { EVP_PKEY_CTX_free(ctx); return NULL; } if (ctx) EVP_PKEY_CTX_free(ctx); - return US ERR_error_string(ERR_get_error(), NULL); } -#endif +return US ERR_error_string(ERR_get_error(), NULL); } -- 2.25.1