security: encode EC public key with named curve

refs #5037

Change-Id: I5d77782e347579fe6dffb188a92178f592bcd492
diff --git a/ndn-cxx/security/transform/private-key.cpp b/ndn-cxx/security/transform/private-key.cpp
index 659de11..f5c9360 100644
--- a/ndn-cxx/security/transform/private-key.cpp
+++ b/ndn-cxx/security/transform/private-key.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2019 Regents of the University of California.
+ * Copyright (c) 2013-2020 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -32,6 +32,7 @@
 #include "ndn-cxx/util/random.hpp"
 
 #include <boost/lexical_cast.hpp>
+#include <boost/scope_exit.hpp>
 #include <cstring>
 
 #define ENSURE_PRIVATE_KEY_LOADED(key) \
@@ -475,42 +476,44 @@
 unique_ptr<PrivateKey>
 PrivateKey::generateEcKey(uint32_t keySize)
 {
-  detail::EvpPkeyCtx pctx(EVP_PKEY_EC);
-
-  if (EVP_PKEY_paramgen_init(pctx) <= 0)
-    NDN_THROW(PrivateKey::Error("Failed to initialize EC paramgen context"));
-
-  int ret;
+  EC_KEY* eckey = nullptr;
   switch (keySize) {
   case 224:
-    ret = EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, NID_secp224r1);
+    eckey = EC_KEY_new_by_curve_name(NID_secp224r1);
     break;
   case 256:
-    ret = EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, NID_X9_62_prime256v1); // same as secp256r1
+    eckey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); // same as secp256r1
     break;
   case 384:
-    ret = EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, NID_secp384r1);
+    eckey = EC_KEY_new_by_curve_name(NID_secp384r1);
     break;
   case 521:
-    ret = EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, NID_secp521r1);
+    eckey = EC_KEY_new_by_curve_name(NID_secp521r1);
     break;
   default:
     NDN_THROW(std::invalid_argument("Unsupported EC key length " + to_string(keySize)));
   }
-  if (ret <= 0)
-    NDN_THROW(PrivateKey::Error("Failed to set EC curve"));
+  if (eckey == nullptr)
+    NDN_THROW(Error("Failed to set EC curve"));
 
-  Impl params;
-  if (EVP_PKEY_paramgen(pctx, &params.key) <= 0)
-    NDN_THROW(PrivateKey::Error("Failed to generate EC parameters"));
+  BOOST_SCOPE_EXIT(&eckey) {
+    EC_KEY_free(eckey);
+  } BOOST_SCOPE_EXIT_END
 
-  detail::EvpPkeyCtx kctx(params.key);
-  if (EVP_PKEY_keygen_init(kctx) <= 0)
-    NDN_THROW(PrivateKey::Error("Failed to initialize EC keygen context"));
+#if OPENSSL_VERSION_NUMBER < 0x1010000fL
+  EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE);
+#endif // OPENSSL_VERSION_NUMBER < 0x1010000fL
+
+  if (EC_KEY_generate_key(eckey) != 1) {
+    NDN_THROW(Error("Failed to generate EC key"));
+  }
 
   auto privateKey = make_unique<PrivateKey>();
-  if (EVP_PKEY_keygen(kctx, &privateKey->m_impl->key) <= 0)
-    NDN_THROW(PrivateKey::Error("Failed to generate EC key"));
+  privateKey->m_impl->key = EVP_PKEY_new();
+  if (privateKey->m_impl->key == nullptr)
+    NDN_THROW(Error("Failed to create EVP_PKEY"));
+  if (EVP_PKEY_set1_EC_KEY(privateKey->m_impl->key, eckey) != 1)
+    NDN_THROW(Error("Failed to assign EC key"));
 
   return privateKey;
 }
diff --git a/tests/unit/security/transform/private-key.t.cpp b/tests/unit/security/transform/private-key.t.cpp
index 2317479..738067a 100644
--- a/tests/unit/security/transform/private-key.t.cpp
+++ b/tests/unit/security/transform/private-key.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2019 Regents of the University of California.
+ * Copyright (c) 2013-2020 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -31,6 +31,7 @@
 #include "ndn-cxx/security/transform/signer-filter.hpp"
 #include "ndn-cxx/security/transform/stream-sink.hpp"
 #include "ndn-cxx/security/transform/verifier-filter.hpp"
+#include "ndn-cxx/util/string-helper.hpp"
 
 #include "tests/boost-test.hpp"
 
@@ -160,8 +161,11 @@
       "ywIDAQAB\n";
 };
 
-struct EcKeyTestData
+struct EcKeySpecificCurveTestData
 {
+  // EC keys are generated in named curve format only. However, old keys in specific curve format
+  // are still accepted. See https://redmine.named-data.net/issues/5037
+
   const size_t keySize = 256;
   const std::string privateKeyPkcs1 =
       "MIIBaAIBAQQgRxwcbzK9RV6AHYFsDcykI86o3M/a1KlJn0z8PcLMBZOggfowgfcC\n"
@@ -193,7 +197,31 @@
       "5EJTDxq6ls5FoYLfThp8HOjuwGSz0qw8ocMqyku1y0V5peQ4rEPd0bwcpZd9svA=\n";
 };
 
-using KeyTestDataSets = boost::mpl::vector<RsaKeyTestData, EcKeyTestData>;
+struct EcKeyNamedCurveTestData
+{
+  // openssl ecparam -name secp384r1 -genkey -out pvt1.pem
+  // openssl pkcs8 -topk8 -passout pass:password -in pvt1.pem -out pvt8.pem
+  // openssl ec -in pvt1.pem -pubout -out pub8.pem
+
+  const size_t keySize = 384;
+  const std::string privateKeyPkcs1 =
+      "MIGkAgEBBDCUsb7NymksCkQAjdLMjUilWhOEyeYmGi79sX1RbsmfnoF/8SesKBhO\n"
+      "or+TZ8g8/8igBwYFK4EEACKhZANiAARWGplLOGdQiXRFQcd0VLPeTt0zXEj5zvSv\n"
+      "aHx9MrzBy57wgz10wTAiR561wuLtFAYxmqL9Ikrzx/BaEg0+v2zQ05NCzMNN8v2c\n"
+      "7/FzOhD7fmZrlJsT6Q2aHGExW0Rj3GE=\n";
+  const std::string privateKeyPkcs8 =
+      "MIHgMBsGCSqGSIb3DQEFAzAOBAjniUjwJfWsMQICCAAEgcCakGTKa49csaPpmtzi\n"
+      "5sTJw+AH8ajUqcDbtp2pJP/Ni6M1p9fai9hOKPElf9uJuYh/S80FAU6WQmZBAxL4\n"
+      "bF598ncLPogpGvz21wLuSc1xnbD829zAsMmh0XZvMZpBWX2g0NnZJx7GOraskTSh\n"
+      "7qGu70B+uKzw+JxIzgBEeMcoBUg8mTEht5zghfLGYkQp6BpTPdpU64udpPAKzFNs\n"
+      "5X+BzBnT5Yy49/Lp4uYIji8qwJFF3VqTn8RFKunFYDDejRU=\n";
+  const std::string publicKeyPkcs8 =
+      "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEVhqZSzhnUIl0RUHHdFSz3k7dM1xI+c70\n"
+      "r2h8fTK8wcue8IM9dMEwIkeetcLi7RQGMZqi/SJK88fwWhINPr9s0NOTQszDTfL9\n"
+      "nO/xczoQ+35ma5SbE+kNmhxhMVtEY9xh\n";
+};
+
+using KeyTestDataSets = boost::mpl::vector<RsaKeyTestData, EcKeySpecificCurveTestData, EcKeyNamedCurveTestData>;
 
 static void
 checkPkcs8Encoding(ConstBufferPtr encoding, const std::string& password, ConstBufferPtr pkcs1)
@@ -410,25 +438,48 @@
   BOOST_CHECK_THROW(hmacKey->decrypt(os.buf()->data(), os.buf()->size()), PrivateKey::Error);
 }
 
-struct RsaKeyGenParams
+class RsaKeyGenParams
 {
+public:
   using Params = RsaKeyParams;
   using hasPublicKey = std::true_type;
   using canSavePkcs1 = std::true_type;
+
+  static void
+  checkPublicKey(const Buffer& bits)
+  {
+  }
 };
 
-struct EcKeyGenParams
+class EcKeyGenParams
 {
+public:
   using Params = EcKeyParams;
   using hasPublicKey = std::true_type;
   using canSavePkcs1 = std::true_type;
+
+  static void
+  checkPublicKey(const Buffer& bits)
+  {
+    // EC key generation should use named curve format. See https://redmine.named-data.net/issues/5037
+    // OBJECT IDENTIFIER 1.2.840.10045.3.1.7 prime256v1 (ANSI X9.62 named elliptic curve)
+    const uint8_t oid[] = {0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07};
+    BOOST_CHECK_MESSAGE(std::search(bits.begin(), bits.end(), oid, oid + sizeof(oid)) != bits.end(),
+                        "OID not found in " << toHex(bits));
+  }
 };
 
-struct HmacKeyGenParams
+class HmacKeyGenParams
 {
+public:
   using Params = HmacKeyParams;
   using hasPublicKey = std::false_type;
   using canSavePkcs1 = std::false_type;
+
+  static void
+  checkPublicKey(const Buffer& bits)
+  {
+  }
 };
 
 using KeyGenParams = boost::mpl::vector<RsaKeyGenParams,
@@ -452,6 +503,8 @@
   bool result = false;
   if (typename T::hasPublicKey()) {
     auto pKeyBits = sKey->derivePublicKey();
+    BOOST_REQUIRE(pKeyBits != nullptr);
+    T::checkPublicKey(*pKeyBits);
     PublicKey pKey;
     pKey.loadPkcs8(pKeyBits->data(), pKeyBits->size());
     BOOST_CHECK_NO_THROW(bufferSource(data, sizeof(data)) >>