Add the suite of EC_KEY and EC_POINT serializers.
OpenSSL added a bunch of these. oct2priv is a little weird (see
https://crbug.com/boringssl/534), but I've made it match OpenSSL and
set_private_key for now. But I think we should reduce the state-space a
bit.
EC_KEY_oct2priv behaves slightly differently from upstream OpenSSL in
one way: we reject inputs that aren't exactly the right size. This
matches the OpenSSL documentation (the OCTET STRING inside an
ECPrivateKey, per spec, is fixed-width), but not OpenSSL's behavior.
Update-note: see go/xshow when incorporating this change internally.
Change-Id: I33863d773ac4c7f3eabf4ffda157e8250c7fdbd9
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/55066
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/crypto/fipsmodule/ec/ec_key.c b/crypto/fipsmodule/ec/ec_key.c
index 3b3da24..31a097e 100644
--- a/crypto/fipsmodule/ec/ec_key.c
+++ b/crypto/fipsmodule/ec/ec_key.c
@@ -409,24 +409,70 @@
}
size_t EC_KEY_key2buf(const EC_KEY *key, point_conversion_form_t form,
- unsigned char **out_buf, BN_CTX *ctx) {
+ uint8_t **out_buf, BN_CTX *ctx) {
if (key == NULL || key->pub_key == NULL || key->group == NULL) {
+ OPENSSL_PUT_ERROR(EC, EC_R_MISSING_PARAMETERS);
return 0;
}
- const size_t len =
- EC_POINT_point2oct(key->group, key->pub_key, form, NULL, 0, ctx);
+ return EC_POINT_point2buf(key->group, key->pub_key, form, out_buf, ctx);
+}
+
+int EC_KEY_oct2priv(EC_KEY *key, const uint8_t *in, size_t len) {
+ if (key->group == NULL) {
+ OPENSSL_PUT_ERROR(EC, EC_R_MISSING_PARAMETERS);
+ return 0;
+ }
+
+ if (len != BN_num_bytes(EC_GROUP_get0_order(key->group))) {
+ OPENSSL_PUT_ERROR(EC, EC_R_DECODE_ERROR);
+ return 0;
+ }
+
+ BIGNUM *priv_key = BN_bin2bn(in, len, NULL);
+ int ok = priv_key != NULL && //
+ EC_KEY_set_private_key(key, priv_key);
+ BN_free(priv_key);
+ return ok;
+}
+
+size_t EC_KEY_priv2oct(const EC_KEY *key, uint8_t *out, size_t max_out) {
+ if (key->group == NULL || key->priv_key == NULL) {
+ OPENSSL_PUT_ERROR(EC, EC_R_MISSING_PARAMETERS);
+ return 0;
+ }
+
+ size_t len = BN_num_bytes(EC_GROUP_get0_order(key->group));
+ if (out == NULL) {
+ return len;
+ }
+
+ if (max_out < len) {
+ OPENSSL_PUT_ERROR(EC, EC_R_BUFFER_TOO_SMALL);
+ return 0;
+ }
+
+ size_t bytes_written;
+ ec_scalar_to_bytes(key->group, out, &bytes_written, &key->priv_key->scalar);
+ assert(bytes_written == len);
+ return len;
+}
+
+size_t EC_KEY_priv2buf(const EC_KEY *key, uint8_t **out_buf) {
+ *out_buf = NULL;
+ size_t len = EC_KEY_priv2oct(key, NULL, 0);
if (len == 0) {
return 0;
}
uint8_t *buf = OPENSSL_malloc(len);
if (buf == NULL) {
+ OPENSSL_PUT_ERROR(EC, ERR_R_MALLOC_FAILURE);
return 0;
}
- if (EC_POINT_point2oct(key->group, key->pub_key, form, buf, len, ctx) !=
- len) {
+ len = EC_KEY_priv2oct(key, buf, len);
+ if (len == 0) {
OPENSSL_free(buf);
return 0;
}
diff --git a/crypto/fipsmodule/ec/ec_test.cc b/crypto/fipsmodule/ec/ec_test.cc
index c54adb5..12430b8 100644
--- a/crypto/fipsmodule/ec/ec_test.cc
+++ b/crypto/fipsmodule/ec/ec_test.cc
@@ -105,6 +105,20 @@
0x37, 0xbf, 0x51, 0xf5,
};
+static const uint8_t kECKeyWithZerosPublic[] = {
+ 0x04, 0x6b, 0x17, 0xd1, 0xf2, 0xe1, 0x2c, 0x42, 0x47, 0xf8, 0xbc,
+ 0xe6, 0xe5, 0x63, 0xa4, 0x40, 0xf2, 0x77, 0x03, 0x7d, 0x81, 0x2d,
+ 0xeb, 0x33, 0xa0, 0xf4, 0xa1, 0x39, 0x45, 0xd8, 0x98, 0xc2, 0x96,
+ 0x4f, 0xe3, 0x42, 0xe2, 0xfe, 0x1a, 0x7f, 0x9b, 0x8e, 0xe7, 0xeb,
+ 0x4a, 0x7c, 0x0f, 0x9e, 0x16, 0x2b, 0xce, 0x33, 0x57, 0x6b, 0x31,
+ 0x5e, 0xce, 0xcb, 0xb6, 0x40, 0x68, 0x37, 0xbf, 0x51, 0xf5,
+};
+
+static const uint8_t kECKeyWithZerosRawPrivate[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
+
// DecodeECPrivateKey decodes |in| as an ECPrivateKey structure and returns the
// result or nullptr on error.
static bssl::UniquePtr<EC_KEY> DecodeECPrivateKey(const uint8_t *in,
@@ -191,11 +205,52 @@
EXPECT_TRUE(EncodeECPrivateKey(&out, key.get()));
EXPECT_EQ(Bytes(kECKeyWithZeros), Bytes(out.data(), out.size()));
+ // Check the private key encodes correctly, including with the leading zeros.
+ EXPECT_EQ(32u, EC_KEY_priv2oct(key.get(), nullptr, 0));
+ uint8_t buf[32];
+ ASSERT_EQ(32u, EC_KEY_priv2oct(key.get(), buf, sizeof(buf)));
+ EXPECT_EQ(Bytes(buf), Bytes(kECKeyWithZerosRawPrivate));
+
+ // Buffer too small.
+ EXPECT_EQ(0u, EC_KEY_priv2oct(key.get(), buf, sizeof(buf) - 1));
+
+ // Extra space in buffer.
+ uint8_t large_buf[33];
+ ASSERT_EQ(32u, EC_KEY_priv2oct(key.get(), large_buf, sizeof(large_buf)));
+ EXPECT_EQ(Bytes(buf), Bytes(kECKeyWithZerosRawPrivate));
+
+ // Allocating API.
+ uint8_t *buf_alloc;
+ size_t len = EC_KEY_priv2buf(key.get(), &buf_alloc);
+ ASSERT_GT(len, 0u);
+ bssl::UniquePtr<uint8_t> free_buf_alloc(buf_alloc);
+ EXPECT_EQ(Bytes(buf_alloc, len), Bytes(kECKeyWithZerosRawPrivate));
+
// Keys without leading zeros also parse, but they encode correctly.
key = DecodeECPrivateKey(kECKeyMissingZeros, sizeof(kECKeyMissingZeros));
ASSERT_TRUE(key);
EXPECT_TRUE(EncodeECPrivateKey(&out, key.get()));
EXPECT_EQ(Bytes(kECKeyWithZeros), Bytes(out.data(), out.size()));
+
+ // Test the key can be constructed with |EC_KEY_oct2*|.
+ key.reset(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
+ ASSERT_TRUE(key);
+ ASSERT_TRUE(EC_KEY_oct2key(key.get(), kECKeyWithZerosPublic,
+ sizeof(kECKeyWithZerosPublic), nullptr));
+ ASSERT_TRUE(EC_KEY_oct2priv(key.get(), kECKeyWithZerosRawPrivate,
+ sizeof(kECKeyWithZerosRawPrivate)));
+ EXPECT_TRUE(EncodeECPrivateKey(&out, key.get()));
+ EXPECT_EQ(Bytes(kECKeyWithZeros), Bytes(out.data(), out.size()));
+
+ // |EC_KEY_oct2priv|'s format is fixed-width and must match the group order.
+ key.reset(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
+ ASSERT_TRUE(key);
+ EXPECT_FALSE(EC_KEY_oct2priv(key.get(), kECKeyWithZerosRawPrivate + 1,
+ sizeof(kECKeyWithZerosRawPrivate) - 1));
+ uint8_t padded[sizeof(kECKeyWithZerosRawPrivate) + 1] = {0};
+ memcpy(padded + 1, kECKeyWithZerosRawPrivate,
+ sizeof(kECKeyWithZerosRawPrivate));
+ EXPECT_FALSE(EC_KEY_oct2priv(key.get(), padded, sizeof(padded)));
}
TEST(ECTest, SpecifiedCurve) {
diff --git a/crypto/fipsmodule/ec/oct.c b/crypto/fipsmodule/ec/oct.c
index 7032635..dd2e007 100644
--- a/crypto/fipsmodule/ec/oct.c
+++ b/crypto/fipsmodule/ec/oct.c
@@ -91,9 +91,9 @@
size_t ec_point_to_bytes(const EC_GROUP *group, const EC_AFFINE *point,
point_conversion_form_t form, uint8_t *buf,
- size_t len) {
+ size_t max_out) {
size_t output_len = ec_point_byte_len(group, form);
- if (len < output_len) {
+ if (max_out < output_len) {
OPENSSL_PUT_ERROR(EC, EC_R_BUFFER_TOO_SMALL);
return 0;
}
@@ -210,7 +210,7 @@
size_t EC_POINT_point2oct(const EC_GROUP *group, const EC_POINT *point,
point_conversion_form_t form, uint8_t *buf,
- size_t len, BN_CTX *ctx) {
+ size_t max_out, BN_CTX *ctx) {
if (EC_GROUP_cmp(group, point->group, NULL) != 0) {
OPENSSL_PUT_ERROR(EC, EC_R_INCOMPATIBLE_OBJECTS);
return 0;
@@ -228,7 +228,29 @@
if (!ec_jacobian_to_affine(group, &affine, &point->raw)) {
return 0;
}
- return ec_point_to_bytes(group, &affine, form, buf, len);
+ return ec_point_to_bytes(group, &affine, form, buf, max_out);
+}
+
+size_t EC_POINT_point2buf(const EC_GROUP *group, const EC_POINT *point,
+ point_conversion_form_t form, uint8_t **out_buf,
+ BN_CTX *ctx) {
+ *out_buf = NULL;
+ size_t len = EC_POINT_point2oct(group, point, form, NULL, 0, ctx);
+ if (len == 0) {
+ return 0;
+ }
+ uint8_t *buf = OPENSSL_malloc(len);
+ if (buf == NULL) {
+ OPENSSL_PUT_ERROR(EC, ERR_R_MALLOC_FAILURE);
+ return 0;
+ }
+ len = EC_POINT_point2oct(group, point, form, buf, len, ctx);
+ if (len == 0) {
+ OPENSSL_free(buf);
+ return 0;
+ }
+ *out_buf = buf;
+ return len;
}
int EC_POINT_set_compressed_coordinates_GFp(const EC_GROUP *group,
diff --git a/include/openssl/ec.h b/include/openssl/ec.h
index 8339bfb..63f0c6f 100644
--- a/include/openssl/ec.h
+++ b/include/openssl/ec.h
@@ -253,13 +253,23 @@
BN_CTX *ctx);
// EC_POINT_point2oct serialises |point| into the X9.62 form given by |form|
-// into, at most, |len| bytes at |buf|. It returns the number of bytes written
-// or zero on error if |buf| is non-NULL, else the number of bytes needed. The
-// |ctx| argument may be used if not NULL.
+// into, at most, |max_out| bytes at |buf|. It returns the number of bytes
+// written or zero on error if |buf| is non-NULL, else the number of bytes
+// needed. The |ctx| argument may be used if not NULL.
OPENSSL_EXPORT size_t EC_POINT_point2oct(const EC_GROUP *group,
const EC_POINT *point,
point_conversion_form_t form,
- uint8_t *buf, size_t len, BN_CTX *ctx);
+ uint8_t *buf, size_t max_out,
+ BN_CTX *ctx);
+
+// EC_POINT_point2buf serialises |point| into the X9.62 form given by |form| to
+// a newly-allocated buffer and sets |*out_buf| to point to it. It returns the
+// length of the result on success or zero on error. The caller must release
+// |*out_buf| with |OPENSSL_free| when done.
+OPENSSL_EXPORT size_t EC_POINT_point2buf(const EC_GROUP *group,
+ const EC_POINT *point,
+ point_conversion_form_t form,
+ uint8_t **out_buf, BN_CTX *ctx);
// EC_POINT_point2cbb behaves like |EC_POINT_point2oct| but appends the
// serialised point to |cbb|. It returns one on success and zero on error.
diff --git a/include/openssl/ec_key.h b/include/openssl/ec_key.h
index 3143290..ee9c9f0 100644
--- a/include/openssl/ec_key.h
+++ b/include/openssl/ec_key.h
@@ -186,12 +186,31 @@
OPENSSL_EXPORT int EC_KEY_oct2key(EC_KEY *key, const uint8_t *in, size_t len,
BN_CTX *ctx);
-// EC_KEY_key2buf encodes the public key in |key| to an allocated octet string
-// and sets |*out_buf| to point to it. It returns the length of the encoded
-// octet string or zero if an error occurred.
+// EC_KEY_key2buf behaves like |EC_POINT_point2buf|, except it encodes the
+// public key in |key|.
OPENSSL_EXPORT size_t EC_KEY_key2buf(const EC_KEY *key,
point_conversion_form_t form,
- unsigned char **out_buf, BN_CTX *ctx);
+ uint8_t **out_buf, BN_CTX *ctx);
+
+// EC_KEY_oct2priv decodes a big-endian, zero-padded integer from |len| bytes
+// from |in| and sets |key|'s private key to the result. It returns one on
+// success and zero on error. The input must be padded to the size of |key|'s
+// group order.
+OPENSSL_EXPORT int EC_KEY_oct2priv(EC_KEY *key, const uint8_t *in, size_t len);
+
+// EC_KEY_priv2oct serializes |key|'s private key as a big-endian integer,
+// zero-padded to the size of |key|'s group order and writes the result to at
+// most |max_out| bytes of |out|. It returns the number of bytes written on
+// success and zero on error. If |out| is NULL, it returns the number of bytes
+// needed without writing anything.
+OPENSSL_EXPORT size_t EC_KEY_priv2oct(const EC_KEY *key, uint8_t *out,
+ size_t max_out);
+
+// EC_KEY_priv2buf behaves like |EC_KEY_priv2oct| but sets |*out_buf| to a
+// newly-allocated buffer containing the result. It returns the size of the
+// result on success and zero on error. The caller must release |*out_buf| with
+// |OPENSSL_free| when done.
+OPENSSL_EXPORT size_t EC_KEY_priv2buf(const EC_KEY *key, uint8_t **out_buf);
// Key generation.