Add an ECDH Wycheproof driver.

Unfortunately, this driver suffers a lot from Wycheproof's Java
heritgate, but so it goes. Their test formats bake in a lot of Java API
mistakes.

Change-Id: I3299e85efb58e99e4fa34841709c3bea6518968d
Reviewed-on: https://boringssl-review.googlesource.com/27865
Reviewed-by: Steven Valdez <svaldez@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
diff --git a/crypto/ecdh/ecdh_test.cc b/crypto/ecdh/ecdh_test.cc
index 6d48408..05dad00 100644
--- a/crypto/ecdh/ecdh_test.cc
+++ b/crypto/ecdh/ecdh_test.cc
@@ -20,15 +20,18 @@
 #include <gtest/gtest.h>
 
 #include <openssl/bn.h>
+#include <openssl/bytestring.h>
 #include <openssl/crypto.h>
 #include <openssl/ec.h>
 #include <openssl/ec_key.h>
 #include <openssl/ecdh.h>
 #include <openssl/err.h>
+#include <openssl/evp.h>
 #include <openssl/nid.h>
 
 #include "../test/file_test.h"
 #include "../test/test_util.h"
+#include "../test/wycheproof_util.h"
 
 
 static bssl::UniquePtr<EC_GROUP> GetCurve(FileTest *t, const char *key) {
@@ -115,6 +118,60 @@
   });
 }
 
+TEST(ECDHTest, Wycheproof) {
+  FileTestGTest("third_party/wycheproof/ecdh_test.txt", [](FileTest *t) {
+    t->IgnoreInstruction("curve");  // This is redundant with the per-test one.
+
+    bssl::UniquePtr<EC_GROUP> group = GetWycheproofCurve(t, "curve", false);
+    ASSERT_TRUE(group);
+    bssl::UniquePtr<BIGNUM> priv_key = GetWycheproofBIGNUM(t, "private", false);
+    ASSERT_TRUE(priv_key);
+    std::vector<uint8_t> peer_spki;
+    ASSERT_TRUE(t->GetBytes(&peer_spki, "public"));
+    WycheproofResult result;
+    ASSERT_TRUE(GetWycheproofResult(t, &result));
+    std::vector<uint8_t> shared;
+    ASSERT_TRUE(t->GetBytes(&shared, "shared"));
+
+    // Wycheproof stores the peer key in an SPKI to mimic a Java API mistake.
+    // This is non-standard and error-prone.
+    CBS cbs;
+    CBS_init(&cbs, peer_spki.data(), peer_spki.size());
+    bssl::UniquePtr<EVP_PKEY> peer_evp(EVP_parse_public_key(&cbs));
+    if (!peer_evp) {
+      // Note some of Wycheproof's "acceptable" entries are unsupported by
+      // BoringSSL because they test named curves (explicitly forbidden by RFC
+      // 5480), while others are supported because they used compressed
+      // coordinates. If the peer key fails to parse, we consider it to match
+      // "acceptable", but if the resulting shared secret matches below, it too
+      // matches "acceptable".
+      //
+      // TODO(davidben): Use the flags field to disambiguate these. Possibly
+      // first get the Wycheproof folks to use flags more consistently.
+      EXPECT_NE(WycheproofResult::kValid, result);
+      return;
+    }
+    EC_KEY *peer_ec = EVP_PKEY_get0_EC_KEY(peer_evp.get());
+    ASSERT_TRUE(peer_ec);
+
+    bssl::UniquePtr<EC_KEY> key(EC_KEY_new());
+    ASSERT_TRUE(key);
+    ASSERT_TRUE(EC_KEY_set_group(key.get(), group.get()));
+    ASSERT_TRUE(EC_KEY_set_private_key(key.get(), priv_key.get()));
+
+    std::vector<uint8_t> actual((EC_GROUP_get_degree(group.get()) + 7) / 8);
+    int ret =
+        ECDH_compute_key(actual.data(), actual.size(),
+                         EC_KEY_get0_public_key(peer_ec), key.get(), nullptr);
+    if (result == WycheproofResult::kInvalid) {
+      EXPECT_EQ(-1, ret);
+    } else {
+      EXPECT_EQ(static_cast<int>(actual.size()), ret);
+      EXPECT_EQ(Bytes(shared), Bytes(actual.data(), static_cast<size_t>(ret)));
+    }
+  });
+}
+
 // MakeCustomGroup returns an |EC_GROUP| containing a non-standard group. (P-256
 // with the wrong generator.)
 static bssl::UniquePtr<EC_GROUP> MakeCustomGroup() {
diff --git a/crypto/test/wycheproof_util.cc b/crypto/test/wycheproof_util.cc
index 3ff132f..8f7dfeb 100644
--- a/crypto/test/wycheproof_util.cc
+++ b/crypto/test/wycheproof_util.cc
@@ -14,6 +14,7 @@
 
 #include "./wycheproof_util.h"
 
+#include <openssl/bn.h>
 #include <openssl/digest.h>
 #include <openssl/ec.h>
 #include <openssl/nid.h>
@@ -89,3 +90,35 @@
   }
   return bssl::UniquePtr<EC_GROUP>(EC_GROUP_new_by_curve_name(nid));
 }
+
+bssl::UniquePtr<BIGNUM> GetWycheproofBIGNUM(FileTest *t, const char *key,
+                                            bool instruction) {
+  std::string value;
+  bool ok = instruction ? t->GetInstruction(&value, key)
+                        : t->GetAttribute(&value, key);
+  if (!ok) {
+    return nullptr;
+  }
+  BIGNUM *bn = nullptr;
+  if (BN_hex2bn(&bn, value.c_str()) != static_cast<int>(value.size())) {
+    BN_free(bn);
+    t->PrintLine("Could not decode value '%s'", value.c_str());
+    return nullptr;
+  }
+  bssl::UniquePtr<BIGNUM> ret(bn);
+  if (!value.empty()) {
+    // If the high bit is one, this is a negative number in Wycheproof.
+    // Wycheproof's tests generally mimic Java APIs, including all their
+    // mistakes. See
+    // https://github.com/google/wycheproof/blob/0329f5b751ef102bd6b7b7181b6e049522a887f5/java/com/google/security/wycheproof/JsonUtil.java#L62.
+    if ('0' > value[0] || value[0] > '7') {
+      bssl::UniquePtr<BIGNUM> tmp(BN_new());
+      if (!tmp ||
+          !BN_set_bit(tmp.get(), value.size() * 4) ||
+          !BN_sub(ret.get(), ret.get(), tmp.get())) {
+        return nullptr;
+      }
+    }
+  }
+  return ret;
+}
diff --git a/crypto/test/wycheproof_util.h b/crypto/test/wycheproof_util.h
index cf50b8e..4d8a14c 100644
--- a/crypto/test/wycheproof_util.h
+++ b/crypto/test/wycheproof_util.h
@@ -41,5 +41,10 @@
 bssl::UniquePtr<EC_GROUP> GetWycheproofCurve(FileTest *t, const char *key,
                                              bool instruction);
 
+// GetWycheproofBIGNUM returns a BIGNUM in the Wycheproof format, or nullptr on
+// error.
+bssl::UniquePtr<BIGNUM> GetWycheproofBIGNUM(FileTest *t, const char *key,
+                                            bool instruction);
+
 
 #endif  // OPENSSL_HEADER_CRYPTO_TEST_WYCHEPROOF_UTIL_H