Add some EVP APIs for low-level EC point and scalar formats

I haven't yet done ECPrivateKey. That one is a bit hairy since it is two
formats in one.

Bug: 42290408
Change-Id: Ie579392f5df34c221f79879fa468b930a692e196
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/98730
Reviewed-by: Lily Chen <chlily@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/crypto/evp/evp_test.cc b/crypto/evp/evp_test.cc
index ec2271f..cb23f77 100644
--- a/crypto/evp/evp_test.cc
+++ b/crypto/evp/evp_test.cc
@@ -112,9 +112,7 @@
 };
 
 const std::map<std::string, AlgorithmInfo> kAllAlgorithms = {
-    {"RSA",
-     {EVP_pkey_rsa(),
-      /*kem=*/nullptr, EVP_PKEY_RSA, true}},
+    {"RSA", {EVP_pkey_rsa(), /*kem=*/nullptr, EVP_PKEY_RSA, true}},
 
     {"RSA-PSS-SHA-256",
      {EVP_pkey_rsa_pss_sha256(), /*kem=*/nullptr, EVP_PKEY_RSA_PSS, false}},
@@ -307,7 +305,8 @@
                          EVP_PKEY *(*parse_func)(const EVP_PKEY_ALG *,
                                                  const uint8_t *, size_t)) {
   SCOPED_TRACE(attr);
-  if (!t->HasAttribute(attr)) {
+  if (!t->HasAttribute(attr) ||
+      t->HasAttribute("SkipImport" + std::string(attr))) {
     return true;  // Nothing to import.
   }
   std::vector<uint8_t> input;
@@ -521,6 +520,12 @@
                            alg_info, &EVP_PKEY_from_raw_private_key) ||
       !ImportWithAlgCommon(t, &key_set, KeyRole::kPrivate, "PrivateSeed",
                            alg_info, &EVP_PKEY_from_private_seed) ||
+      !ImportWithAlgCommon(t, &key_set, KeyRole::kPublic, "ECUncompressedPoint",
+                           alg_info, &EVP_PKEY_from_ec_uncompressed_point) ||
+      !ImportWithAlgCommon(t, &key_set, KeyRole::kPublic, "ECCompressedPoint",
+                           alg_info, &EVP_PKEY_from_ec_compressed_point) ||
+      !ImportWithAlgCommon(t, &key_set, KeyRole::kPrivate, "ECPrivateScalar",
+                           alg_info, &EVP_PKEY_from_ec_private_scalar) ||
       !ImportRSAPublicParams(t, &key_set, alg_info) ||
       !ImportRSAPrivateParams(t, &key_set, alg_info)) {
     return false;
@@ -605,7 +610,13 @@
         !CheckRawKey(t, "RawPublic", KeyRoleSelect::kAny, key,
                      EVP_PKEY_get_raw_public_key) ||
         !CheckRawKey(t, "PrivateSeed", KeyRoleSelect::kPrivate, key,
-                     EVP_PKEY_get_private_seed)) {
+                     EVP_PKEY_get_private_seed) ||
+        !CheckMarshalCBB(t, "ECUncompressedPoint", KeyRoleSelect::kAny, key,
+                         EVP_PKEY_marshal_ec_uncompressed_point) ||
+        !CheckMarshalCBB(t, "ECCompressedPoint", KeyRoleSelect::kAny, key,
+                         EVP_PKEY_marshal_ec_compressed_point) ||
+        !CheckMarshalCBB(t, "ECPrivateScalar", KeyRoleSelect::kPrivate, key,
+                         EVP_PKEY_marshal_ec_private_scalar)) {
       return false;
     }
   }
diff --git a/crypto/evp/p_ec.cc b/crypto/evp/p_ec.cc
index 06ef932..3e72432 100644
--- a/crypto/evp/p_ec.cc
+++ b/crypto/evp/p_ec.cc
@@ -575,6 +575,127 @@
   return ec_key;
 }
 
+static const EC_GROUP *alg_to_group(const EVP_PKEY_ALG *alg) {
+  if (alg->method->pkey_id != EVP_PKEY_EC) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_UNSUPPORTED_ALGORITHM);
+    return nullptr;
+  }
+  auto *alg_ec = static_cast<const EVP_PKEY_ALG_EC *>(alg);
+  if (alg_ec->ec_group == nullptr) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_UNSUPPORTED_ALGORITHM);
+    return nullptr;
+  }
+  return alg_ec->ec_group();
+}
+
+static EVP_PKEY *pkey_from_ec_point(const EC_GROUP *group, const uint8_t *in,
+                                    size_t len) {
+  UniquePtr<EVP_PKEY> pkey(EVP_PKEY_new());
+  UniquePtr<EC_KEY> ec_key(EC_KEY_new());
+  if (pkey == nullptr || ec_key == nullptr ||
+      !EC_KEY_set_group(ec_key.get(), group) ||
+      !EC_KEY_oct2key(ec_key.get(), in, len, nullptr)) {
+    return nullptr;
+  }
+  EVP_PKEY_assign_EC_KEY(pkey.get(), ec_key.release());
+  return pkey.release();
+}
+
+EVP_PKEY *EVP_PKEY_from_ec_uncompressed_point(
+    const EVP_PKEY_ALG *alg, const uint8_t *in, size_t len) {
+  const EC_GROUP *group = alg_to_group(alg);
+  if (group == nullptr) {
+    return nullptr;
+  }
+  // The `EC_KEY` APIs are not separated by uncompressed and compressed.
+  if (len == 0 || in[0] != 0x04) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
+    return nullptr;
+  }
+  return pkey_from_ec_point(group, in, len);
+}
+
+EVP_PKEY *EVP_PKEY_from_ec_compressed_point(const EVP_PKEY_ALG *alg,
+                                            const uint8_t *in, size_t len) {
+  const EC_GROUP *group = alg_to_group(alg);
+  if (group == nullptr) {
+    return nullptr;
+  }
+  // The `EC_KEY` APIs are not separated by uncompressed and compressed.
+  if (len == 0 || in[0] == 0x04) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
+    return nullptr;
+  }
+  return pkey_from_ec_point(group, in, len);
+}
+
+static int marshal_ec_point(CBB *cbb, const EVP_PKEY *key,
+                            point_conversion_form_t form) {
+  const EC_KEY *ec_key = EVP_PKEY_get0_EC_KEY(key);
+  if (ec_key == nullptr) {
+    return 0;
+  }
+  const EC_GROUP *group = EC_KEY_get0_group(ec_key);
+  const EC_POINT *pub = EC_KEY_get0_public_key(ec_key);
+  if (group == nullptr || pub == nullptr) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_MISSING_PUBLIC_KEY);
+    return 0;
+  }
+  return EC_POINT_point2cbb(cbb, group, pub, form, nullptr);
+}
+
+int EVP_PKEY_marshal_ec_uncompressed_point(CBB *cbb, const EVP_PKEY *key) {
+  return marshal_ec_point(cbb, key, POINT_CONVERSION_UNCOMPRESSED);
+}
+
+int EVP_PKEY_marshal_ec_compressed_point(CBB *cbb, const EVP_PKEY *key) {
+  return marshal_ec_point(cbb, key, POINT_CONVERSION_COMPRESSED);
+}
+
+EVP_PKEY *EVP_PKEY_from_ec_private_scalar(const EVP_PKEY_ALG *alg,
+                                          const uint8_t *in, size_t len) {
+  const EC_GROUP *group = alg_to_group(alg);
+  if (group == nullptr) {
+    return nullptr;
+  }
+  UniquePtr<EVP_PKEY> pkey(EVP_PKEY_new());
+  UniquePtr<EC_KEY> ec_key(EC_KEY_new());
+  if (pkey == nullptr || ec_key == nullptr ||
+      !EC_KEY_set_group(ec_key.get(), group) ||
+      !EC_KEY_oct2priv(ec_key.get(), in, len)) {
+    return nullptr;
+  }
+  // TODO(crbug.com/42290404): `EC_KEY_oct2priv` should fill in the public key
+  // internally.
+  UniquePtr<EC_POINT> pub(EC_POINT_new(group));
+  if (pub == nullptr ||
+      !EC_POINT_mul(group, pub.get(), EC_KEY_get0_private_key(ec_key.get()),
+                    nullptr, nullptr, nullptr) ||
+      !EC_KEY_set_public_key(ec_key.get(), pub.get())) {
+    return 0;
+  }
+
+  EVP_PKEY_assign_EC_KEY(pkey.get(), ec_key.release());
+  return pkey.release();
+}
+
+int EVP_PKEY_marshal_ec_private_scalar(CBB *cbb, const EVP_PKEY *key) {
+  const EC_KEY *ec_key = EVP_PKEY_get0_EC_KEY(key);
+  if (ec_key == nullptr) {
+    return 0;
+  }
+  size_t len = EC_KEY_priv2oct(ec_key, nullptr, 0);
+  if (len == 0) {
+    return 0;
+  }
+  uint8_t *out;
+  if (!CBB_reserve(cbb, &out, len)) {
+    return 0;
+  }
+  len = EC_KEY_priv2oct(ec_key, out, len);
+  return len != 0 && CBB_did_write(cbb, len);
+}
+
 int EVP_PKEY_get_ec_curve_nid(const EVP_PKEY *pkey) {
   const EC_KEY *ec_key = EVP_PKEY_get0_EC_KEY(pkey);
   if (ec_key == nullptr) {
diff --git a/crypto/evp/test/ec_tests.txt b/crypto/evp/test/ec_tests.txt
index 33ccff3..9810033 100644
--- a/crypto/evp/test/ec_tests.txt
+++ b/crypto/evp/test/ec_tests.txt
@@ -5,6 +5,9 @@
 Algorithm = EC-P-256
 Bits = 256
 ECCurve = prime256v1
+ECUncompressedPoint = 042c150f429ce70f216c252cf5e062ce1f639cd5d165c7f89424072c27197d78b33b920e95cdb664e990dcf0cfea0d94e2a8e6af9d0e58056e653104925b9fe6c9
+ECCompressedPoint = 032c150f429ce70f216c252cf5e062ce1f639cd5d165c7f89424072c27197d78b3
+ECPrivateScalar = 8a872fb62893c4d1ffc5b9f0f91758069f8352e08fa05a49f8db926cb5728725
 PKCS8 = 308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b02010104208a872fb62893c4d1ffc5b9f0f91758069f8352e08fa05a49f8db926cb5728725a144034200042c150f429ce70f216c252cf5e062ce1f639cd5d165c7f89424072c27197d78b33b920e95cdb664e990dcf0cfea0d94e2a8e6af9d0e58056e653104925b9fe6c9
 SPKI = 3059301306072a8648ce3d020106082a8648ce3d030107034200042c150f429ce70f216c252cf5e062ce1f639cd5d165c7f89424072c27197d78b33b920e95cdb664e990dcf0cfea0d94e2a8e6af9d0e58056e653104925b9fe6c9
 # The same key as above with redundant parameters.
@@ -15,17 +18,22 @@
 PKCS8NonCanonical = 308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b02010104208a872fb62893c4d1ffc5b9f0f91758069f8352e08fa05a49f8db926cb5728725a144034200042c150f429ce70f216c252cf5e062ce1f639cd5d165c7f89424072c27197d78b33b920e95cdb664e990dcf0cfea0d94e2a8e6af9d0e58056e653104925b9fe6c9
 
 # The same key as above with the optional public key omitted.
-#
-# TODO(crbug.com/503025567): Currently, we remember whether the public key was
-# omitted and roundtrip it back out, so this is a seprate key set. Pick a
-# canonical one instead?
 Key = P-256-MissingPublic
 Algorithm = EC-P-256
 Bits = 256
 ECCurve = prime256v1
+ECUncompressedPoint = 042c150f429ce70f216c252cf5e062ce1f639cd5d165c7f89424072c27197d78b33b920e95cdb664e990dcf0cfea0d94e2a8e6af9d0e58056e653104925b9fe6c9
+ECCompressedPoint = 032c150f429ce70f216c252cf5e062ce1f639cd5d165c7f89424072c27197d78b3
+ECPrivateScalar = 8a872fb62893c4d1ffc5b9f0f91758069f8352e08fa05a49f8db926cb5728725
 PKCS8 = 3041020100301306072a8648ce3d020106082a8648ce3d0301070427302502010104208a872fb62893c4d1ffc5b9f0f91758069f8352e08fa05a49f8db926cb5728725
 # Even with the public key omitted, we compute the public key.
 SPKI = 3059301306072a8648ce3d020106082a8648ce3d030107034200042c150f429ce70f216c252cf5e062ce1f639cd5d165c7f89424072c27197d78b33b920e95cdb664e990dcf0cfea0d94e2a8e6af9d0e58056e653104925b9fe6c9
+# TODO(crbug.com/503025567): Currently, we remember whether the public key was
+# omitted and roundtrip it back out, so this is a seprate key set. Pick a
+# canonical one instead? Remembering it also means that importing
+# ECPrivateScalar produces a slightly different object from importing PKCS8. To
+# avoid breaking the cross-format tests, skip importing ECPrivateScalar.
+SkipImportECPrivateScalar
 
 # The same key, but with the redundant parameters in the ECPrivateKey mismatched.
 Key = P-256-BadInnerParameters
@@ -68,6 +76,9 @@
 Algorithm = EC-P-224
 Bits = 224
 ECCurve = secp224r1
+ECUncompressedPoint = 041a95d8c8d08635ad7fa0facd3a9fe56f8f42451b58ea8a29f7d28b0fb214690b1149a69f016f644cdb3320b78a381027835d6091903f0513
+ECCompressedPoint = 031a95d8c8d08635ad7fa0facd3a9fe56f8f42451b58ea8a29f7d28b0f
+ECPrivateScalar = aa17cf0a0c8064bf6b509c212e48293d61e0d97acc85f8dea5c99166
 PKCS8 = 3078020100301006072a8648ce3d020106052b810400210461305f020101041caa17cf0a0c8064bf6b509c212e48293d61e0d97acc85f8dea5c99166a13c033a00041a95d8c8d08635ad7fa0facd3a9fe56f8f42451b58ea8a29f7d28b0fb214690b1149a69f016f644cdb3320b78a381027835d6091903f0513
 SPKI = 304e301006072a8648ce3d020106052b81040021033a00041a95d8c8d08635ad7fa0facd3a9fe56f8f42451b58ea8a29f7d28b0fb214690b1149a69f016f644cdb3320b78a381027835d6091903f0513
 
@@ -77,6 +88,9 @@
 Algorithm = EC-P-384
 Bits = 384
 ECCurve = secp384r1
+ECUncompressedPoint = 04826b3df548ad2e0b96436cb13508e88745a33b4b06cf485ad8350824b4dfe01ee66a5e1d1aaebfebcaa6337a1f33c338afc0d59b7ce7e389f73f66c9c4a44bbfcf570aec5cc52e7b6608c9061ab4d72de933448c39dd9238177917d398c22c5e
+ECCompressedPoint = 02826b3df548ad2e0b96436cb13508e88745a33b4b06cf485ad8350824b4dfe01ee66a5e1d1aaebfebcaa6337a1f33c338
+ECPrivateScalar = a29991b86091401328e62ec8caadd0482f887ff0936910e42a56c19f48cbe87331037a4e2b36f1091dd4a26ee2d2b01f
 PKCS8 = 3081b6020100301006072a8648ce3d020106052b8104002204819e30819b0201010430a29991b86091401328e62ec8caadd0482f887ff0936910e42a56c19f48cbe87331037a4e2b36f1091dd4a26ee2d2b01fa16403620004826b3df548ad2e0b96436cb13508e88745a33b4b06cf485ad8350824b4dfe01ee66a5e1d1aaebfebcaa6337a1f33c338afc0d59b7ce7e389f73f66c9c4a44bbfcf570aec5cc52e7b6608c9061ab4d72de933448c39dd9238177917d398c22c5e
 SPKI = 3076301006072a8648ce3d020106052b8104002203620004826b3df548ad2e0b96436cb13508e88745a33b4b06cf485ad8350824b4dfe01ee66a5e1d1aaebfebcaa6337a1f33c338afc0d59b7ce7e389f73f66c9c4a44bbfcf570aec5cc52e7b6608c9061ab4d72de933448c39dd9238177917d398c22c5e
 
@@ -86,9 +100,79 @@
 Algorithm = EC-P-521
 Bits = 521
 ECCurve = secp521r1
+ECUncompressedPoint = 0400f58adcbe5c07f6b500fadd3209487e38f9567f97c2204435a4eb140739905c201407e2530a6667216aad01fb849bcbefa3862b2f187f13c9a87923d378a0a184df017b4bb93f4766531785878458da6aaa525724e10dfb1f35cfbe55c56fc705714295ea74b6c3e714152ad78e929f5415683aed9bc7c68f0329934177829d715f03f2
+ECCompressedPoint = 0200f58adcbe5c07f6b500fadd3209487e38f9567f97c2204435a4eb140739905c201407e2530a6667216aad01fb849bcbefa3862b2f187f13c9a87923d378a0a184df
+ECPrivateScalar = 002eeee9c16b9b4d2dee606f443cf3b41d2899a4734ff7555b54c735afb34a6912f81c68a89ea9b427c69a1026d98ef1d7f9c683aec5c5103d9a4c21e403c638412f
 PKCS8 = 3081ee020100301006072a8648ce3d020106052b810400230481d63081d30201010442002eeee9c16b9b4d2dee606f443cf3b41d2899a4734ff7555b54c735afb34a6912f81c68a89ea9b427c69a1026d98ef1d7f9c683aec5c5103d9a4c21e403c638412fa18189038186000400f58adcbe5c07f6b500fadd3209487e38f9567f97c2204435a4eb140739905c201407e2530a6667216aad01fb849bcbefa3862b2f187f13c9a87923d378a0a184df017b4bb93f4766531785878458da6aaa525724e10dfb1f35cfbe55c56fc705714295ea74b6c3e714152ad78e929f5415683aed9bc7c68f0329934177829d715f03f2
 SPKI = 30819b301006072a8648ce3d020106052b81040023038186000400f58adcbe5c07f6b500fadd3209487e38f9567f97c2204435a4eb140739905c201407e2530a6667216aad01fb849bcbefa3862b2f187f13c9a87923d378a0a184df017b4bb93f4766531785878458da6aaa525724e10dfb1f35cfbe55c56fc705714295ea74b6c3e714152ad78e929f5415683aed9bc7c68f0329934177829d715f03f2
 
+# Compressed and uncompressed forms swapped
+Key = P-256-WrongForm-1
+Algorithm = EC-P-256
+ECUncompressedPoint = 032c150f429ce70f216c252cf5e062ce1f639cd5d165c7f89424072c27197d78b3
+Error = DECODE_ERROR
+
+Key = P-256-WrongForm-2
+Algorithm = EC-P-256
+ECCompressedPoint = 042c150f429ce70f216c252cf5e062ce1f639cd5d165c7f89424072c27197d78b33b920e95cdb664e990dcf0cfea0d94e2a8e6af9d0e58056e653104925b9fe6c9
+Error = DECODE_ERROR
+
+# Wrong length
+Key = P-256-Scalar-Too-Short
+Algorithm = EC-P-256
+ECPrivateScalar = 8a872fb62893c4d1ffc5b9f0f91758069f8352e08fa05a49f8db926cb57287
+Error = DECODE_ERROR
+
+Key = P-256-Scalar-Too-Long
+Algorithm = EC-P-256
+ECPrivateScalar = 008a872fb62893c4d1ffc5b9f0f91758069f8352e08fa05a49f8db926cb5728725
+Error = DECODE_ERROR
+
+# Scalar not reduced.
+Key = P-256-Scalar-NotReduced-1
+Algorithm = EC-P-256
+ECPrivateScalar = ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551
+Error = INVALID_SCALAR
+
+Key = P-256-Scalar-NotReduced-2
+Algorithm = EC-P-256
+ECPrivateScalar = ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552
+Error = INVALID_SCALAR
+
+# Truncated points
+Key = P-256-Truncated-Uncompressed
+Algorithm = EC-P-256
+ECUncompressedPoint = 042c150f429ce70f216c252cf5e062ce1f639cd5d165c7f89424072c27197d78b33b920e95cdb664e990dcf0cfea0d94e2a8e6af9d0e58056e653104925b9f
+Error = INVALID_ENCODING
+
+Key = P-256-Truncated-Compressed
+Algorithm = EC-P-256
+ECCompressedPoint = 032c150f429ce70f216c252cf5e062ce1f639cd5d165c7f89424072c27197d78
+Error = INVALID_ENCODING
+
+# First byte is wrong
+Key = P-256-Bad-First-Byte-Uncompressed
+Algorithm = EC-P-256
+ECUncompressedPoint = 052c150f429ce70f216c252cf5e062ce1f639cd5d165c7f89424072c27197d78b33b920e95cdb664e990dcf0cfea0d94e2a8e6af9d0e58056e653104925b9fe6c9
+Error = DECODE_ERROR
+
+Key = P-256-Bad-First-Byte-Compressed
+Algorithm = EC-P-256
+ECCompressedPoint = 052c150f429ce70f216c252cf5e062ce1f639cd5d165c7f89424072c27197d78b3
+Error = INVALID_ENCODING
+
+# Uncompressed point not on the curve
+Key = P-256-Not-On-Curve
+Algorithm = EC-P-256
+ECUncompressedPoint = 042c150f429ce70f216c252cf5e062ce1f639cd5d165c7f89424072c27197d78b33b920e95cdb664e990dcf0cfea0d94e2a8e6af9d0e58056e653104925b9fe6c8
+Error = POINT_IS_NOT_ON_CURVE
+
+# Compressed point has no square root
+Key = P-256-No-Square-Root-2
+Algorithm = EC-P-256
+ECCompressedPoint = 03ffffffff00000001000000000000000000000000fffffffffffffffffffffffe
+Error = INVALID_COMPRESSED_POINT
+
 
 # ECDSA tests
 
@@ -152,13 +236,18 @@
 Algorithm = EC-P-256
 Bits = 256
 ECCurve = prime256v1
-PKCS8 = 3041020100301306072a8648ce3d020106082a8648ce3d0301070427302502010104207d7dc5f71eb29ddaf80d6214632eeae03d9058af1fb6d22ed80badb62bc1a534
+ECUncompressedPoint = 04ead218590119e8876b29146ff89ca61770c4edbbf97d38ce385ed281d8a6b23028af61281fd35e2fa7002523acc85a429cb06ee6648325389f59edfce1405141
+ECCompressedPoint = 03ead218590119e8876b29146ff89ca61770c4edbbf97d38ce385ed281d8a6b230
+ECPrivateScalar = 7d7dc5f71eb29ddaf80d6214632eeae03d9058af1fb6d22ed80badb62bc1a534
+PKCS8 = 308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b02010104207d7dc5f71eb29ddaf80d6214632eeae03d9058af1fb6d22ed80badb62bc1a534a14403420004ead218590119e8876b29146ff89ca61770c4edbbf97d38ce385ed281d8a6b23028af61281fd35e2fa7002523acc85a429cb06ee6648325389f59edfce1405141
 SPKI = 3059301306072a8648ce3d020106082a8648ce3d03010703420004ead218590119e8876b29146ff89ca61770c4edbbf97d38ce385ed281d8a6b23028af61281fd35e2fa7002523acc85a429cb06ee6648325389f59edfce1405141
 
 Key = ECDH-P256-Peer
 Algorithm = EC-P-256
 Bits = 256
 ECCurve = prime256v1
+ECUncompressedPoint = 04700c48f77f56584c5cc632ca65640db91b6bacce3a4df6b42ce7cc838833d287db71e509e3fd9b060ddb20ba5c51dcc5948d46fbf640dfe0441782cab85fa4ac
+ECCompressedPoint = 02700c48f77f56584c5cc632ca65640db91b6bacce3a4df6b42ce7cc838833d287
 SPKI = 3059301306072a8648ce3d020106082a8648ce3d03010703420004700c48f77f56584c5cc632ca65640db91b6bacce3a4df6b42ce7cc838833d287db71e509e3fd9b060ddb20ba5c51dcc5948d46fbf640dfe0441782cab85fa4ac
 
 Derive = ECDH-P256-Ours/Private
diff --git a/include/openssl/evp.h b/include/openssl/evp.h
index 8f523ac..ee5cdae 100644
--- a/include/openssl/evp.h
+++ b/include/openssl/evp.h
@@ -1119,6 +1119,50 @@
 
 // EC specific control functions.
 
+// The following functions decode `len` bytes from `in` as an EC public key in
+// compressed or uncompressed X9.62 format, respectively. The EC curve is
+// determined by `alg`, which must be an EC algorithm (i.e. one of
+// `EVP_pkey_ec_*`).
+//
+// The functions return a newly-allocated `EVP_PKEY` on success or NULL on
+// error.
+//
+// To support both uncompressed and compressed forms, call
+// `EVP_PKEY_from_ec_uncompressed_point` when the input begins with 0x04 and
+// `EVP_PKEY_from_ec_compressed_point` otherwise. Uncompressed form is more
+// common.
+OPENSSL_EXPORT EVP_PKEY *EVP_PKEY_from_ec_uncompressed_point(
+    const EVP_PKEY_ALG *alg, const uint8_t *in, size_t len);
+OPENSSL_EXPORT EVP_PKEY *EVP_PKEY_from_ec_compressed_point(
+    const EVP_PKEY_ALG *alg, const uint8_t *in, size_t len);
+
+// The following functions marshal `key`'s EC public key in compressed or
+// uncompressed X9.62 format, respectively, and write the result to `cbb`. They
+// return one on success and zero on error. It is an error if `key` is not an EC
+// key.
+OPENSSL_EXPORT int EVP_PKEY_marshal_ec_uncompressed_point(CBB *cbb,
+                                                          const EVP_PKEY *key);
+OPENSSL_EXPORT int EVP_PKEY_marshal_ec_compressed_point(CBB *cbb,
+                                                        const EVP_PKEY *key);
+
+// EVP_PKEY_from_ec_private_scalar decodes `len` bytes from `in` as an EC
+// private key, encoded as a big-endian, zero-padded integer. The EC curve is
+// determined by `alg`, which must be an EC algorithm (i.e. one of
+// `EVP_pkey_ec_*`). The input must be fully reduced and padded to the size of
+// the group order (e.g. 32 bytes for P-256) or the import will fail.
+//
+// This function returns a newly-allocated `EVP_PKEY` on success or NULL on
+// error.
+OPENSSL_EXPORT EVP_PKEY *EVP_PKEY_from_ec_private_scalar(
+    const EVP_PKEY_ALG *alg, const uint8_t *in, size_t len);
+
+// EVP_PKEY_marshal_ec_private_scalar marshals `key`'s private key as a
+// big-endian, zero-padded integer EC scalar and writes the result to `cbb`. It
+// returns one on success or zero on error. It is an error if `key` is not an EC
+// key.
+OPENSSL_EXPORT int EVP_PKEY_marshal_ec_private_scalar(CBB *cbb,
+                                                      const EVP_PKEY *key);
+
 // EVP_PKEY_get_ec_curve_nid returns `pkey`'s curve as a NID constant, such as
 // `NID_X9_62_prime256v1`, or `NID_undef` if `pkey` is not an EC key.
 OPENSSL_EXPORT int EVP_PKEY_get_ec_curve_nid(const EVP_PKEY *pkey);
diff --git a/include/openssl/prefix_symbols.h b/include/openssl/prefix_symbols.h
index a2fec15..4cf8e44 100644
--- a/include/openssl/prefix_symbols.h
+++ b/include/openssl/prefix_symbols.h
@@ -1168,6 +1168,9 @@
 #pragma redefine_extname EVP_PKEY_encrypt_init BORINGSSL_ADD_USER_LABEL_AND_PREFIX(EVP_PKEY_encrypt_init)
 #pragma redefine_extname EVP_PKEY_eq BORINGSSL_ADD_USER_LABEL_AND_PREFIX(EVP_PKEY_eq)
 #pragma redefine_extname EVP_PKEY_free BORINGSSL_ADD_USER_LABEL_AND_PREFIX(EVP_PKEY_free)
+#pragma redefine_extname EVP_PKEY_from_ec_compressed_point BORINGSSL_ADD_USER_LABEL_AND_PREFIX(EVP_PKEY_from_ec_compressed_point)
+#pragma redefine_extname EVP_PKEY_from_ec_private_scalar BORINGSSL_ADD_USER_LABEL_AND_PREFIX(EVP_PKEY_from_ec_private_scalar)
+#pragma redefine_extname EVP_PKEY_from_ec_uncompressed_point BORINGSSL_ADD_USER_LABEL_AND_PREFIX(EVP_PKEY_from_ec_uncompressed_point)
 #pragma redefine_extname EVP_PKEY_from_private_key_info BORINGSSL_ADD_USER_LABEL_AND_PREFIX(EVP_PKEY_from_private_key_info)
 #pragma redefine_extname EVP_PKEY_from_private_seed BORINGSSL_ADD_USER_LABEL_AND_PREFIX(EVP_PKEY_from_private_seed)
 #pragma redefine_extname EVP_PKEY_from_raw_private_key BORINGSSL_ADD_USER_LABEL_AND_PREFIX(EVP_PKEY_from_raw_private_key)
@@ -1195,6 +1198,9 @@
 #pragma redefine_extname EVP_PKEY_is_opaque BORINGSSL_ADD_USER_LABEL_AND_PREFIX(EVP_PKEY_is_opaque)
 #pragma redefine_extname EVP_PKEY_keygen BORINGSSL_ADD_USER_LABEL_AND_PREFIX(EVP_PKEY_keygen)
 #pragma redefine_extname EVP_PKEY_keygen_init BORINGSSL_ADD_USER_LABEL_AND_PREFIX(EVP_PKEY_keygen_init)
+#pragma redefine_extname EVP_PKEY_marshal_ec_compressed_point BORINGSSL_ADD_USER_LABEL_AND_PREFIX(EVP_PKEY_marshal_ec_compressed_point)
+#pragma redefine_extname EVP_PKEY_marshal_ec_private_scalar BORINGSSL_ADD_USER_LABEL_AND_PREFIX(EVP_PKEY_marshal_ec_private_scalar)
+#pragma redefine_extname EVP_PKEY_marshal_ec_uncompressed_point BORINGSSL_ADD_USER_LABEL_AND_PREFIX(EVP_PKEY_marshal_ec_uncompressed_point)
 #pragma redefine_extname EVP_PKEY_missing_parameters BORINGSSL_ADD_USER_LABEL_AND_PREFIX(EVP_PKEY_missing_parameters)
 #pragma redefine_extname EVP_PKEY_new BORINGSSL_ADD_USER_LABEL_AND_PREFIX(EVP_PKEY_new)
 #pragma redefine_extname EVP_PKEY_new_raw_private_key BORINGSSL_ADD_USER_LABEL_AND_PREFIX(EVP_PKEY_new_raw_private_key)
@@ -4291,6 +4297,9 @@
 #define EVP_PKEY_encrypt_init BORINGSSL_ADD_PREFIX(EVP_PKEY_encrypt_init)
 #define EVP_PKEY_eq BORINGSSL_ADD_PREFIX(EVP_PKEY_eq)
 #define EVP_PKEY_free BORINGSSL_ADD_PREFIX(EVP_PKEY_free)
+#define EVP_PKEY_from_ec_compressed_point BORINGSSL_ADD_PREFIX(EVP_PKEY_from_ec_compressed_point)
+#define EVP_PKEY_from_ec_private_scalar BORINGSSL_ADD_PREFIX(EVP_PKEY_from_ec_private_scalar)
+#define EVP_PKEY_from_ec_uncompressed_point BORINGSSL_ADD_PREFIX(EVP_PKEY_from_ec_uncompressed_point)
 #define EVP_PKEY_from_private_key_info BORINGSSL_ADD_PREFIX(EVP_PKEY_from_private_key_info)
 #define EVP_PKEY_from_private_seed BORINGSSL_ADD_PREFIX(EVP_PKEY_from_private_seed)
 #define EVP_PKEY_from_raw_private_key BORINGSSL_ADD_PREFIX(EVP_PKEY_from_raw_private_key)
@@ -4318,6 +4327,9 @@
 #define EVP_PKEY_is_opaque BORINGSSL_ADD_PREFIX(EVP_PKEY_is_opaque)
 #define EVP_PKEY_keygen BORINGSSL_ADD_PREFIX(EVP_PKEY_keygen)
 #define EVP_PKEY_keygen_init BORINGSSL_ADD_PREFIX(EVP_PKEY_keygen_init)
+#define EVP_PKEY_marshal_ec_compressed_point BORINGSSL_ADD_PREFIX(EVP_PKEY_marshal_ec_compressed_point)
+#define EVP_PKEY_marshal_ec_private_scalar BORINGSSL_ADD_PREFIX(EVP_PKEY_marshal_ec_private_scalar)
+#define EVP_PKEY_marshal_ec_uncompressed_point BORINGSSL_ADD_PREFIX(EVP_PKEY_marshal_ec_uncompressed_point)
 #define EVP_PKEY_missing_parameters BORINGSSL_ADD_PREFIX(EVP_PKEY_missing_parameters)
 #define EVP_PKEY_new BORINGSSL_ADD_PREFIX(EVP_PKEY_new)
 #define EVP_PKEY_new_raw_private_key BORINGSSL_ADD_PREFIX(EVP_PKEY_new_raw_private_key)
diff --git a/rust/bssl-crypto/src/ec.rs b/rust/bssl-crypto/src/ec.rs
index 1c3fa1e..134993c 100644
--- a/rust/bssl-crypto/src/ec.rs
+++ b/rust/bssl-crypto/src/ec.rs
@@ -357,6 +357,9 @@
 
         // BoringSSL allows an `EC_KEY` to have a private scalar without a
         // public point, but `Key` is never exposed in that state.
+        //
+        // TODO(crbug.com/42290404): `EC_KEY_oct2priv` should fill in the public
+        // key.
 
         // Safety: `key.0` is valid by construction. The returned value is
         // still owned the `EC_KEY`.