DKIM: Ed25519 signatures under OpenSSL (1.1.1 or later)
authorJeremy Harris <jgh146exb@wizmail.org>
Sat, 17 Mar 2018 23:39:54 +0000 (23:39 +0000)
committerJeremy Harris <jgh146exb@wizmail.org>
Sat, 17 Mar 2018 23:39:54 +0000 (23:39 +0000)
OpenSSL 1.1.1 is not released yet, but operation has been checked against the current source

doc/doc-docbook/spec.xfpt
doc/doc-txt/NewStuff
doc/doc-txt/openssl.txt
src/src/hash.h
src/src/pdkim/crypt_ver.h
src/src/pdkim/pdkim.c
src/src/pdkim/signing.c

index aea31dd66e2000026b7b3f33435b0f1a3617947a..295cb15c19c5a4d856292f4cb2adca61bf87f635 100644 (file)
@@ -38903,7 +38903,8 @@ The result can either
 be a valid RSA private key in ASCII armor (.pem file), including line breaks
 .new
 .next
 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
 .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
 .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
 The "_CRYPTO_SIGN_ED25519" macro will be defined if support is present
 for EC keys.
 .wen
index e4de435a9267177b006f05aadf664c948fa10662..58f3f205495e91c68fb95461c185f52296f5a3e8 100644 (file)
@@ -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
     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.
 
 10. Builtin feature-macros _CRYPTO_HASH_SHA3 and _CRYPTO_SIGN_ED25519, library
     version dependent.
index e4f5d854cfb054b650520f4f935226f8fe7a5689..93ca701a97a0e1230bdbd3706bc87d0bc85c7d7b 100644 (file)
@@ -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
 
     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
 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
 
 
     readelf -d $(which exim) | grep RPATH
 
+[jgh: I've seen that spelled RUNPATH]
 
 Very Advanced
 -------------
 
 Very Advanced
 -------------
index a29d4653161f000bee90b8e13cd087a76ae6515a..5bd47acd1f14081ec64ecea688994ea869f42d8e 100644 (file)
@@ -30,6 +30,7 @@
 
 typedef enum hashmethod {
   HASH_BADTYPE,
 
 typedef enum hashmethod {
   HASH_BADTYPE,
+  HASH_NULL,
   HASH_SHA1,
 
   HASH_SHA2_256,
   HASH_SHA1,
 
   HASH_SHA2_256,
index e14ee5f4c83cc064f8a482f5f9c5dad0ac075d3e..0982eb788cbfc404a01ebe6c5d4bb935cee5e59d 100644 (file)
@@ -17,7 +17,7 @@
 # if GNUTLS_VERSION_NUMBER >= 0x030000
 #  define SIGN_GNUTLS
 #  if GNUTLS_VERSION_NUMBER >= 0x030600
 # 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
 #  endif
 # else
 #  define SIGN_GCRYPT
@@ -25,5 +25,9 @@
 
 #else
 # define SIGN_OPENSSL
 
 #else
 # define SIGN_OPENSSL
+#  if OPENSSL_VERSION_NUMBER >= 0x10101000L
+#   define SIGN_HAVE_ED25519
+#  endif
+
 #endif
 
 #endif
 
index 457d83efc7c6d2f362791bcbaedaf142cfb117d0..e291d9dd31de83fc42ae65f6d4e1c0d8381ab421 100644 (file)
@@ -1390,6 +1390,24 @@ DEBUG(D_acl) debug_printf(
 
 /* Import public key */
 
 
 /* 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)))
 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
   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
 
 #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;
       }
 
       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);
     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;
   else
     {
     ev_ctx vctx;
+    hashmethod hm;
 
     /* Make sure we have all required signature tags */
     if (!(  sig->domain        && *sig->domain
 
     /* 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 */
     /* 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;
       {
       DEBUG(D_acl) debug_printf("headers verify: %s\n", *err);
       sig->verify_status =      PDKIM_VERIFY_FAIL;
index b182c9a209c0d730869cbb69ebc10fcc65864c9d..62e32234f38cb8f62f7fbadc3846cbb9ac9693c1 100644 (file)
@@ -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)
 {
 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;
 
 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
 
 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;
          ? KEYTYPE_ED25519 : KEYTYPE_RSA;
 #else
        KEYTYPE_RSA;
@@ -727,7 +727,7 @@ return NULL;
 
 
 /* allocate mem for signature (when signing) */
 
 
 /* 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 */
 
 
 Return: NULL for success with the signaature in the sig blob, or an error string */
 
@@ -740,31 +740,36 @@ size_t siglen;
 
 switch (hash)
   {
 
 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";
   }
 
   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
    && 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
    && EVP_DigestSignFinal(ctx, NULL, &siglen) > 0
-
- /* Allocate memory for the signature based on size in slen */
    && (sig->data = store_get(siglen))
    && (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
    )
   {
    && EVP_DigestSignFinal(ctx, sig->data, &siglen) > 0
    )
   {
@@ -772,6 +777,7 @@ if(  (ctx = EVP_MD_CTX_create())
   sig->len = siglen;
   return NULL;
   }
   sig->len = siglen;
   return NULL;
   }
+#endif
 
 if (ctx) EVP_MD_CTX_destroy(ctx);
 return US ERR_error_string(ERR_get_error(), NULL);
 
 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;
 
 const uschar * s = pubkey->data;
 uschar * ret = NULL;
 
-if (fmt != KEYFMT_DER) return US"pubkey format not handled";
 switch(fmt)
   {
   case KEYFMT_DER:
 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:
     /*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);
       ret = US ERR_error_string(ERR_get_error(), NULL);
-    }
     break;
 #endif
   default:
     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 */
 
 (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;
 
 {
 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)
   {
 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";
   }
 
   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;
 
   {
   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
      && 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);
 
   if (ctx) EVP_MD_CTX_free(ctx);
-  return US ERR_error_string(ERR_get_error(), NULL);
   }
   }
-#else
+else
+#endif
   {
   EVP_PKEY_CTX * ctx;
 
   {
   EVP_PKEY_CTX * ctx;
 
@@ -888,9 +865,8 @@ switch (hash)
     { EVP_PKEY_CTX_free(ctx); return NULL; }
 
   if (ctx) EVP_PKEY_CTX_free(ctx);
     { 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);
 }
 
 
 }