Teach crypto/x509 how to verify an Ed25519 signature.

BUG=187

Change-Id: I5775ce0886041b0c12174a7d665f3af1e8bce511
Reviewed-on: https://boringssl-review.googlesource.com/14505
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/err/x509.errordata b/crypto/err/x509.errordata
index 5687c30..1a8c4f1 100644
--- a/crypto/err/x509.errordata
+++ b/crypto/err/x509.errordata
@@ -10,6 +10,7 @@
 X509,109,INVALID_BIT_STRING_BITS_LEFT
 X509,110,INVALID_DIRECTORY
 X509,111,INVALID_FIELD_NAME
+X509,136,INVALID_PARAMETER
 X509,112,INVALID_PSS_PARAMETERS
 X509,113,INVALID_TRUST
 X509,114,ISSUER_MISMATCH
diff --git a/crypto/obj/obj_xref.c b/crypto/obj/obj_xref.c
index 7b4ff12..1baed0e 100644
--- a/crypto/obj/obj_xref.c
+++ b/crypto/obj/obj_xref.c
@@ -85,10 +85,10 @@
     {NID_ecdsa_with_SHA256, NID_sha256, NID_X9_62_id_ecPublicKey},
     {NID_ecdsa_with_SHA384, NID_sha384, NID_X9_62_id_ecPublicKey},
     {NID_ecdsa_with_SHA512, NID_sha512, NID_X9_62_id_ecPublicKey},
-    /* For PSS the digest algorithm can vary and depends on the included
-     * AlgorithmIdentifier. The digest "undef" indicates the public key method
-     * should handle this explicitly. */
+    /* The following algorithms use more complex (or simpler) parameters. The
+     * digest "undef" indicates the caller should handle this explicitly. */
     {NID_rsassaPss, NID_undef, NID_rsaEncryption},
+    {NID_Ed25519, NID_undef, NID_Ed25519},
 };
 
 int OBJ_find_sigid_algs(int sign_nid, int *out_digest_nid, int *out_pkey_nid) {
diff --git a/crypto/x509/a_verify.c b/crypto/x509/a_verify.c
index 0af4197..400cdd1 100644
--- a/crypto/x509/a_verify.c
+++ b/crypto/x509/a_verify.c
@@ -73,9 +73,8 @@
 int ASN1_item_verify(const ASN1_ITEM *it, X509_ALGOR *a,
                      ASN1_BIT_STRING *signature, void *asn, EVP_PKEY *pkey)
 {
-    EVP_MD_CTX ctx;
     uint8_t *buf_in = NULL;
-    int ret = 0, inl;
+    int ret = 0, inl = 0;
 
     if (!pkey) {
         OPENSSL_PUT_ERROR(X509, ERR_R_PASSED_NULL_PARAMETER);
@@ -87,9 +86,9 @@
         return 0;
     }
 
-    EVP_MD_CTX_init(&ctx);
-
-    if (!x509_digest_verify_init(&ctx, a, pkey)) {
+    EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL);
+    if (ctx == NULL ||
+        !x509_digest_verify_init(ctx, a)) {
         goto err;
     }
 
@@ -100,28 +99,19 @@
         goto err;
     }
 
-    if (!EVP_DigestVerifyUpdate(&ctx, buf_in, inl)) {
+    if (!EVP_PKEY_verify_message(ctx, signature->data,
+                                 (size_t)signature->length, buf_in, inl)) {
+        OPENSSL_PUT_ERROR(X509, ERR_R_EVP_LIB);
+        goto err;
+    }
+
+    ret = 1;
+
+ err:
+    if (buf_in != NULL) {
         OPENSSL_cleanse(buf_in, (unsigned int)inl);
         OPENSSL_free(buf_in);
-        OPENSSL_PUT_ERROR(X509, ERR_R_EVP_LIB);
-        goto err;
     }
-
-    OPENSSL_cleanse(buf_in, (unsigned int)inl);
-    OPENSSL_free(buf_in);
-
-    if (EVP_DigestVerifyFinal(&ctx, signature->data,
-                              (size_t)signature->length) <= 0) {
-        OPENSSL_PUT_ERROR(X509, ERR_R_EVP_LIB);
-        goto err;
-    }
-    /*
-     * we don't need to zero the 'ctx' because we just checked public
-     * information
-     */
-    /* OPENSSL_memset(&ctx,0,sizeof(ctx)); */
-    ret = 1;
- err:
-    EVP_MD_CTX_cleanup(&ctx);
+    EVP_PKEY_CTX_free(ctx);
     return ret;
 }
diff --git a/crypto/x509/algorithm.c b/crypto/x509/algorithm.c
index 78ae882..6e0f3f2 100644
--- a/crypto/x509/algorithm.c
+++ b/crypto/x509/algorithm.c
@@ -101,8 +101,11 @@
   return 1;
 }
 
-int x509_digest_verify_init(EVP_MD_CTX *ctx, X509_ALGOR *sigalg,
-                            EVP_PKEY *pkey) {
+int x509_digest_verify_init(EVP_PKEY_CTX *ctx, X509_ALGOR *sigalg) {
+  if (!EVP_PKEY_verify_init(ctx)) {
+    return 0;
+  }
+
   /* Convert the signature OID into digest and public key OIDs. */
   int sigalg_nid = OBJ_obj2nid(sigalg->algorithm);
   int digest_nid, pkey_nid;
@@ -112,6 +115,7 @@
   }
 
   /* Check the public key OID matches the public key type. */
+  EVP_PKEY *pkey = EVP_PKEY_CTX_get0_pkey(ctx);
   if (pkey_nid != EVP_PKEY_id(pkey)) {
     OPENSSL_PUT_ERROR(ASN1, ASN1_R_WRONG_PUBLIC_KEY_TYPE);
     return 0;
@@ -119,11 +123,18 @@
 
   /* NID_undef signals that there are custom parameters to set. */
   if (digest_nid == NID_undef) {
-    if (sigalg_nid != NID_rsassaPss) {
-      OPENSSL_PUT_ERROR(ASN1, ASN1_R_UNKNOWN_SIGNATURE_ALGORITHM);
-      return 0;
+    if (sigalg_nid == NID_rsassaPss) {
+      return x509_rsa_pss_to_ctx(ctx, sigalg);
     }
-    return x509_rsa_pss_to_ctx(ctx, sigalg, pkey);
+    if (sigalg_nid == NID_Ed25519) {
+      if (sigalg->parameter != NULL) {
+        OPENSSL_PUT_ERROR(X509, X509_R_INVALID_PARAMETER);
+        return 0;
+      }
+      return 1; /* Nothing to configure. */
+    }
+    OPENSSL_PUT_ERROR(ASN1, ASN1_R_UNKNOWN_SIGNATURE_ALGORITHM);
+    return 0;
   }
 
   /* Otherwise, initialize with the digest from the OID. */
@@ -133,5 +144,5 @@
     return 0;
   }
 
-  return EVP_DigestVerifyInit(ctx, NULL, digest, NULL, pkey);
+  return EVP_PKEY_CTX_set_signature_md(ctx, digest);
 }
diff --git a/crypto/x509/internal.h b/crypto/x509/internal.h
index 4957c1e..f7101af 100644
--- a/crypto/x509/internal.h
+++ b/crypto/x509/internal.h
@@ -28,9 +28,8 @@
 
 /* x509_rsa_pss_to_ctx configures |ctx| for an RSA-PSS operation based on
  * signature algorithm parameters in |sigalg| (which must have type
- * |NID_rsassaPss|) and key |pkey|. It returns one on success and zero on
- * error. */
-int x509_rsa_pss_to_ctx(EVP_MD_CTX *ctx, X509_ALGOR *sigalg, EVP_PKEY *pkey);
+ * |NID_rsassaPss|). It returns one on success and zero on error. */
+int x509_rsa_pss_to_ctx(EVP_PKEY_CTX *ctx, X509_ALGOR *sigalg);
 
 /* x509_rsa_pss_to_ctx sets |algor| to the signature algorithm parameters for
  * |ctx|, which must have been configured for an RSA-PSS signing operation. It
@@ -52,11 +51,9 @@
 int x509_digest_sign_algorithm(EVP_MD_CTX *ctx, X509_ALGOR *algor);
 
 /* x509_digest_verify_init sets up |ctx| for a signature verification operation
- * with public key |pkey| and parameters from |algor|. The |ctx| argument must
- * have been initialised with |EVP_MD_CTX_init|. It returns one on success, or
- * zero on error. */
-int x509_digest_verify_init(EVP_MD_CTX *ctx, X509_ALGOR *sigalg,
-                            EVP_PKEY *pkey);
+ * with parameters from |algor|. The |ctx| argument must have been constructed
+ * with the public key. It returns one on success, or zero on error. */
+int x509_digest_verify_init(EVP_PKEY_CTX *ctx, X509_ALGOR *sigalg);
 
 
 #if defined(__cplusplus)
diff --git a/crypto/x509/rsa_pss.c b/crypto/x509/rsa_pss.c
index 4913c3d..9c286c6 100644
--- a/crypto/x509/rsa_pss.c
+++ b/crypto/x509/rsa_pss.c
@@ -242,7 +242,7 @@
   return ret;
 }
 
-int x509_rsa_pss_to_ctx(EVP_MD_CTX *ctx, X509_ALGOR *sigalg, EVP_PKEY *pkey) {
+int x509_rsa_pss_to_ctx(EVP_PKEY_CTX *ctx, X509_ALGOR *sigalg) {
   assert(OBJ_obj2nid(sigalg->algorithm) == NID_rsassaPss);
 
   /* Decode PSS parameters */
@@ -279,11 +279,10 @@
     goto err;
   }
 
-  EVP_PKEY_CTX *pkctx;
-  if (!EVP_DigestVerifyInit(ctx, &pkctx, md, NULL, pkey) ||
-      !EVP_PKEY_CTX_set_rsa_padding(pkctx, RSA_PKCS1_PSS_PADDING) ||
-      !EVP_PKEY_CTX_set_rsa_pss_saltlen(pkctx, saltlen) ||
-      !EVP_PKEY_CTX_set_rsa_mgf1_md(pkctx, mgf1md)) {
+  if (!EVP_PKEY_CTX_set_signature_md(ctx, md) ||
+      !EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PSS_PADDING) ||
+      !EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, saltlen) ||
+      !EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, mgf1md)) {
     goto err;
   }
 
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc
index 4b80af8..6e23cb8 100644
--- a/crypto/x509/x509_test.cc
+++ b/crypto/x509/x509_test.cc
@@ -389,6 +389,35 @@
     "GKljn9weIYiMPV/BzGymwfv2EW0preLwtyJNJPaxbdin6Jc=\n"
     "-----END X509 CRL-----\n";
 
+// kEd25519Cert is a self-signed Ed25519 certificate.
+static const char kEd25519Cert[] =
+    "-----BEGIN CERTIFICATE-----\n"
+    "MIIBkTCCAUOgAwIBAgIJAJwooam0UCDmMAUGAytlcDBFMQswCQYDVQQGEwJBVTET\n"
+    "MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ\n"
+    "dHkgTHRkMB4XDTE0MDQyMzIzMjE1N1oXDTE0MDUyMzIzMjE1N1owRTELMAkGA1UE\n"
+    "BhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp\n"
+    "ZGdpdHMgUHR5IEx0ZDAqMAUGAytlcAMhANdamAGCsQq31Uv+08lkBzoO4XLz2qYj\n"
+    "Ja8CGmj3B1Eao1AwTjAdBgNVHQ4EFgQUoux7eV+fJK2v3ah6QPU/lj1/+7UwHwYD\n"
+    "VR0jBBgwFoAUoux7eV+fJK2v3ah6QPU/lj1/+7UwDAYDVR0TBAUwAwEB/zAFBgMr\n"
+    "ZXADQQBuCzqji8VP9xU8mHEMjXGChX7YP5J664UyVKHKH9Z1u4wEbB8dJ3ScaWSL\n"
+    "r+VHVKUhsrvcdCelnXRrrSD7xWAL\n"
+    "-----END CERTIFICATE-----\n";
+
+// kEd25519CertNull is an invalid self-signed Ed25519 with an explicit NULL in
+// the signature algorithm.
+static const char kEd25519CertNull[] =
+    "-----BEGIN CERTIFICATE-----\n"
+    "MIIBlTCCAUWgAwIBAgIJAJwooam0UCDmMAcGAytlcAUAMEUxCzAJBgNVBAYTAkFV\n"
+    "MRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRz\n"
+    "IFB0eSBMdGQwHhcNMTQwNDIzMjMyMTU3WhcNMTQwNTIzMjMyMTU3WjBFMQswCQYD\n"
+    "VQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQg\n"
+    "V2lkZ2l0cyBQdHkgTHRkMCowBQYDK2VwAyEA11qYAYKxCrfVS/7TyWQHOg7hcvPa\n"
+    "piMlrwIaaPcHURqjUDBOMB0GA1UdDgQWBBSi7Ht5X58kra/dqHpA9T+WPX/7tTAf\n"
+    "BgNVHSMEGDAWgBSi7Ht5X58kra/dqHpA9T+WPX/7tTAMBgNVHRMEBTADAQH/MAcG\n"
+    "AytlcAUAA0EA70uefNocdJohkKPNROKVyBuBD3LXMyvmdTklsaxSRY3PcZdOohlr\n"
+    "recgVPpVS7B+d9g4EwtZXIh4lodTBDHBBw==\n"
+    "-----END CERTIFICATE-----\n";
+
 // CertFromPEM parses the given, NUL-terminated pem block and returns an
 // |X509*|.
 static bssl::UniquePtr<X509> CertFromPEM(const char *pem) {
@@ -732,6 +761,49 @@
   return true;
 }
 
+static bool TestEd25519() {
+  bssl::UniquePtr<X509> cert(CertFromPEM(kEd25519Cert));
+  if (!cert) {
+    return false;
+  }
+
+  bssl::UniquePtr<EVP_PKEY> pkey(X509_get_pubkey(cert.get()));
+  if (!pkey) {
+    return false;
+  }
+
+  if (!X509_verify(cert.get(), pkey.get())) {
+    fprintf(stderr, "Could not verify certificate.\n");
+    return false;
+  }
+  return true;
+}
+
+static bool TestBadEd25519Parameters() {
+  bssl::UniquePtr<X509> cert(CertFromPEM(kEd25519CertNull));
+  if (!cert) {
+    return false;
+  }
+
+  bssl::UniquePtr<EVP_PKEY> pkey(X509_get_pubkey(cert.get()));
+  if (!pkey) {
+    return false;
+  }
+
+  if (X509_verify(cert.get(), pkey.get())) {
+    fprintf(stderr, "Unexpectedly verified bad certificate.\n");
+    return false;
+  }
+  uint32_t err = ERR_get_error();
+  if (ERR_GET_LIB(err) != ERR_LIB_X509 ||
+      ERR_GET_REASON(err) != X509_R_INVALID_PARAMETER) {
+    fprintf(stderr, "Did not get X509_R_INVALID_PARAMETER as expected.\n");
+    return false;
+  }
+  ERR_clear_error();
+  return true;
+}
+
 static bool SignatureRoundTrips(EVP_MD_CTX *md_ctx, EVP_PKEY *pkey) {
   // Make a certificate like signed with |md_ctx|'s settings.'
   bssl::UniquePtr<X509> cert(CertFromPEM(kLeafPEM));
@@ -1086,6 +1158,8 @@
       !TestCRL() ||
       !TestPSS() ||
       !TestBadPSSParameters() ||
+      !TestEd25519() ||
+      !TestBadEd25519Parameters() ||
       !TestSignCtx() ||
       !TestFromBuffer() ||
       !TestFromBufferTrailingData() ||
diff --git a/include/openssl/x509.h b/include/openssl/x509.h
index 88455dd..44b3b7b 100644
--- a/include/openssl/x509.h
+++ b/include/openssl/x509.h
@@ -1246,5 +1246,6 @@
 #define X509_R_WRONG_LOOKUP_TYPE 133
 #define X509_R_WRONG_TYPE 134
 #define X509_R_NAME_TOO_LONG 135
+#define X509_R_INVALID_PARAMETER 136
 
 #endif