// Copyright 2021 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // /////////////////////////////////////////////////////////////////////////////// #include "tink/internal/ec_util.h" #include #include #include #include #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/status/status.h" #include "absl/strings/escaping.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" #include "absl/types/span.h" #include "openssl/bn.h" #include "openssl/ec.h" #include "openssl/ecdsa.h" #include "openssl/evp.h" #include "include/rapidjson/document.h" #include "tink/internal/bn_util.h" #include "tink/internal/fips_utils.h" #include "tink/internal/ssl_unique_ptr.h" #include "tink/internal/ssl_util.h" #include "tink/subtle/common_enums.h" #include "tink/subtle/subtle_util.h" #include "tink/subtle/wycheproof_util.h" #include "tink/util/secret_data.h" #include "tink/util/status.h" #include "tink/util/statusor.h" #include "tink/util/test_matchers.h" namespace crypto { namespace tink { namespace internal { namespace { using ::crypto::tink::subtle::EcPointFormat; using ::crypto::tink::subtle::EllipticCurveType; using ::crypto::tink::subtle::WycheproofUtil; using ::crypto::tink::test::IsOk; using ::crypto::tink::test::IsOkAndHolds; using ::crypto::tink::test::StatusIs; using ::testing::AllOf; using ::testing::ElementsAreArray; using ::testing::Eq; using ::testing::Field; using ::testing::HasSubstr; using ::testing::IsEmpty; using ::testing::IsNull; using ::testing::Matcher; using ::testing::Not; using ::testing::SizeIs; using ::testing::TestParamInfo; using ::testing::TestWithParam; using ::testing::ValuesIn; // Use wycheproof test vectors to verify Ed25519 key generation from a seed (the // private key) results in the public/private key. TEST(EcUtilTest, NewEd25519KeyWithWycheproofTestVectors) { std::unique_ptr test_vectors = WycheproofUtil::ReadTestVectors("eddsa_test.json"); ASSERT_THAT(test_vectors, Not(IsNull())); // For this test we are only interested in Ed25519 keys. for (const auto& test_group : (*test_vectors)["testGroups"].GetArray()) { std::string private_key = WycheproofUtil::GetBytes(test_group["key"]["sk"]); std::string public_key = WycheproofUtil::GetBytes(test_group["key"]["pk"]); util::StatusOr> key = NewEd25519Key(util::SecretDataFromStringView(private_key)); ASSERT_THAT(key, IsOk()); EXPECT_EQ((*key)->public_key, public_key); EXPECT_EQ((*key)->private_key, private_key); } } TEST(EcUtilTest, NewEd25519KeyInvalidSeed) { std::string valid_seed = absl::HexStringToBytes( "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"); // Seed that is too small. for (int i = 0; i < 32; i++) { EXPECT_THAT( NewEd25519Key(util::SecretDataFromStringView(valid_seed.substr(0, i))) .status(), Not(IsOk())) << " with seed of length " << i; } // Seed that is too large. std::string large_seed = absl::StrCat(valid_seed, "a"); EXPECT_THAT( NewEd25519Key(util::SecretDataFromStringView(large_seed)).status(), Not(IsOk())) << " with seed of length " << large_seed.size(); } TEST(EcUtilTest, NewEcKeyReturnsWellFormedX25519Key) { util::StatusOr ec_key = NewEcKey(subtle::EllipticCurveType::CURVE25519); ASSERT_THAT(ec_key, IsOk()); EXPECT_THAT( *ec_key, AllOf(Field(&EcKey::curve, Eq(subtle::EllipticCurveType::CURVE25519)), Field(&EcKey::pub_x, SizeIs(X25519KeyPubKeySize())), Field(&EcKey::pub_y, IsEmpty()), Field(&EcKey::priv, SizeIs(X25519KeyPrivKeySize())))); } using EcUtilNewEcKeyWithSeed = TestWithParam; // Matcher for the equality of two EcKeys. Matcher EqualsEcKey(const EcKey& expected) { return AllOf(Field(&EcKey::priv, Eq(expected.priv)), Field(&EcKey::pub_x, Eq(expected.pub_x)), Field(&EcKey::pub_y, Eq(expected.pub_y)), Field(&EcKey::curve, Eq(expected.curve))); } TEST_P(EcUtilNewEcKeyWithSeed, KeysFromDifferentSeedAreDifferent) { if (IsFipsModeEnabled()) { GTEST_SKIP() << "Not supported in FIPS-only mode"; } if (!IsBoringSsl()) { GTEST_SKIP() << "NewEcKey with seed is not supported with OpenSSL"; } util::SecretData seed1 = util::SecretDataFromStringView( absl::HexStringToBytes("000102030405060708090a0b0c0d0e0f")); util::SecretData seed2 = util::SecretDataFromStringView( absl::HexStringToBytes("0f0e0d0c0b0a09080706050403020100")); subtle::EllipticCurveType curve = GetParam(); util::StatusOr keypair1 = NewEcKey(curve, seed1); ASSERT_THAT(keypair1, IsOk()); util::StatusOr keypair2 = NewEcKey(curve, seed2); ASSERT_THAT(keypair2, IsOk()); EXPECT_THAT(*keypair1, Not(EqualsEcKey(*keypair2))); } TEST_P(EcUtilNewEcKeyWithSeed, SameSeedGivesSameKey) { if (IsFipsModeEnabled()) { GTEST_SKIP() << "Not supported in FIPS-only mode"; } if (!IsBoringSsl()) { GTEST_SKIP() << "NewEcKey with seed is not supported with OpenSSL"; } util::SecretData seed1 = util::SecretDataFromStringView( absl::HexStringToBytes("000102030405060708090a0b0c0d0e0f")); subtle::EllipticCurveType curve = GetParam(); util::StatusOr keypair1 = NewEcKey(curve, seed1); ASSERT_THAT(keypair1, IsOk()); util::StatusOr keypair2 = NewEcKey(curve, seed1); ASSERT_THAT(keypair2, IsOk()); EXPECT_THAT(*keypair1, EqualsEcKey(*keypair2)); } INSTANTIATE_TEST_SUITE_P(EcUtilNewEcKeyWithSeeds, EcUtilNewEcKeyWithSeed, ValuesIn({subtle::NIST_P256, subtle::NIST_P384, subtle::NIST_P521})); TEST(EcUtilTest, GenerationWithSeedFailsWithWrongCurve) { if (IsFipsModeEnabled()) { GTEST_SKIP() << "Not supported in FIPS-only mode"; } if (!IsBoringSsl()) { GTEST_SKIP() << "NewEcKey with seed is not supported with OpenSSL"; } util::SecretData seed = util::SecretDataFromStringView( absl::HexStringToBytes("000102030405060708090a0b0c0d0e0f")); util::StatusOr keypair = NewEcKey(subtle::EllipticCurveType::CURVE25519, seed); EXPECT_THAT(keypair.status(), StatusIs(absl::StatusCode::kInternal)); } TEST(EcUtilTest, NewEcKeyFromSeedUnimplementedIfOpenSsl) { if (IsFipsModeEnabled()) { GTEST_SKIP() << "Not supported in FIPS-only mode"; } if (IsBoringSsl()) { GTEST_SKIP() << "OpenSSL-only test; skipping because BoringSSL is being used"; } util::SecretData seed = util::SecretDataFromStringView( absl::HexStringToBytes("000102030405060708090a0b0c0d0e0f")); util::StatusOr keypair = NewEcKey(subtle::EllipticCurveType::CURVE25519, seed); EXPECT_THAT(keypair.status(), StatusIs(absl::StatusCode::kUnimplemented)); } TEST(EcUtilTest, NewX25519KeyGeneratesNewKeyEveryTime) { util::StatusOr> keypair1 = NewX25519Key(); ASSERT_THAT(keypair1, IsOk()); util::StatusOr> keypair2 = NewX25519Key(); ASSERT_THAT(keypair2, IsOk()); auto priv_key1 = absl::MakeSpan((*keypair1)->private_key, X25519KeyPrivKeySize()); auto priv_key2 = absl::MakeSpan((*keypair2)->private_key, X25519KeyPrivKeySize()); auto pub_key1 = absl::MakeSpan((*keypair1)->public_value, X25519KeyPubKeySize()); auto pub_key2 = absl::MakeSpan((*keypair2)->public_value, X25519KeyPubKeySize()); EXPECT_THAT(priv_key1, Not(ElementsAreArray(priv_key2))); EXPECT_THAT(pub_key1, Not(ElementsAreArray(pub_key2))); } TEST(EcUtilTest, X25519KeyToEcKeyAndBack) { util::StatusOr> x25519_key = NewX25519Key(); ASSERT_THAT(x25519_key, IsOk()); EcKey ec_key = EcKeyFromX25519Key(x25519_key->get()); ASSERT_EQ(ec_key.curve, EllipticCurveType::CURVE25519); util::StatusOr> roundtrip_key = X25519KeyFromEcKey(ec_key); ASSERT_THAT(roundtrip_key, IsOk()); EXPECT_THAT( absl::MakeSpan((*x25519_key)->private_key, X25519KeyPrivKeySize()), ElementsAreArray(absl::MakeSpan((*roundtrip_key)->private_key, X25519KeyPrivKeySize()))); EXPECT_THAT( absl::MakeSpan((*x25519_key)->public_value, X25519KeyPubKeySize()), ElementsAreArray(absl::MakeSpan((*roundtrip_key)->public_value, X25519KeyPubKeySize()))); } TEST(EcUtilTest, X25519KeyFromRandomPrivateKey) { util::StatusOr> x25519_key = NewX25519Key(); ASSERT_THAT(x25519_key, IsOk()); absl::Span pkey_span = absl::MakeSpan((*x25519_key)->private_key, X25519KeyPrivKeySize()); util::StatusOr> roundtrip_key = X25519KeyFromPrivateKey({pkey_span.begin(), pkey_span.end()}); ASSERT_THAT(roundtrip_key, IsOk()); EXPECT_THAT( absl::MakeSpan((*x25519_key)->private_key, X25519KeyPrivKeySize()), ElementsAreArray(absl::MakeSpan((*roundtrip_key)->private_key, X25519KeyPrivKeySize()))); EXPECT_THAT( absl::MakeSpan((*x25519_key)->public_value, X25519KeyPubKeySize()), ElementsAreArray(absl::MakeSpan((*roundtrip_key)->public_value, X25519KeyPubKeySize()))); } struct X25519FunctionTestVector { std::string private_key; std::string expected_public_key; }; // Returns some X25519 test vectors taken from // https://datatracker.ietf.org/doc/html/rfc7748. std::vector GetX25519FunctionTestVectors() { return { // https://datatracker.ietf.org/doc/html/rfc7748#section-5.2 { /*private_key=*/ absl::HexStringToBytes("090000000000000000000000000000000000000000000" "0000000000000000000"), /*expected_public_key=*/ absl::HexStringToBytes("422c8e7a6227d7bca1350b3e2bb7279f7897b87bb6854" "b783c60e80311ae3079"), }, // https://datatracker.ietf.org/doc/html/rfc7748#section-6.1; Alice { /*private_key=*/ absl::HexStringToBytes("77076d0a7318a57d3c16c17251b26645df4c2f87ebc09" "92ab177fba51db92c2a"), /*expected_public_key=*/ absl::HexStringToBytes("8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381" "af4eba4a98eaa9b4e6a"), }, // https://datatracker.ietf.org/doc/html/rfc7748#section-6.1; Bob { /*private_key=*/ absl::HexStringToBytes("5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b" "6fd1c2f8b27ff88e0eb"), /*expected_public_key=*/ absl::HexStringToBytes("de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b786" "74dadfc7e146f882b4f"), }, // Locally made up test vector { /*private_key=*/ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", /*expected_public_key=*/ absl::HexStringToBytes("4049502db92ca2342c3f92dac5d6de7c85db5df5407a5" "b4996ce39f2efb7e827"), }, }; } using X25519FunctionTest = TestWithParam; TEST_P(X25519FunctionTest, ComputeX25519PublicKey) { X25519FunctionTestVector test_vector = GetParam(); util::StatusOr> key = X25519KeyFromPrivateKey( util::SecretDataFromStringView(test_vector.private_key)); ASSERT_THAT(key, IsOk()); EXPECT_THAT(absl::MakeSpan((*key)->public_value, X25519KeyPubKeySize()), ElementsAreArray(test_vector.expected_public_key)); } INSTANTIATE_TEST_SUITE_P(X25519SharedSecretTests, X25519FunctionTest, ValuesIn(GetX25519FunctionTestVectors())); struct X25519SharedSecretTestVector { std::string private_key; std::string public_key; std::string expected_shared_secret; }; // Returns some X25519 test vectors taken from // https://datatracker.ietf.org/doc/html/rfc7748#section-5.2. std::vector GetX25519SharedSecretTestVectors() { return { { /*private_key=*/ absl::HexStringToBytes("a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5" "a18506a2244ba449ac4"), /*public_key=*/ absl::HexStringToBytes("e6db6867583030db3594c1a424b15f7c726624ec26b33" "53b10a903a6d0ab1c4c"), /*expected_shared_secret=*/ absl::HexStringToBytes("c3da55379de9c6908e94ea4df28d084f32eccf03491c7" "1f754b4075577a28552"), }, { /*private_key=*/ absl::HexStringToBytes("4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea0" "1d42ca4169e7918ba0d"), /*public_key=*/ absl::HexStringToBytes("e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03" "c3efc4cd549c715a493"), /*expected_shared_secret=*/ absl::HexStringToBytes("95cbde9476e8907d7aade45cb4b873f88b595a68799fa" "152e6f8f7647aac7957"), }, }; } using X25519SharedSecretTest = TestWithParam; TEST_P(X25519SharedSecretTest, ComputeX25519SharedSecret) { X25519SharedSecretTestVector test_vector = GetParam(); // Generate the EVP_PKEYs. internal::SslUniquePtr ssl_priv_key(EVP_PKEY_new_raw_private_key( /*type=*/EVP_PKEY_X25519, /*unused=*/nullptr, /*in=*/reinterpret_cast(test_vector.private_key.data()), /*len=*/Ed25519KeyPrivKeySize())); ASSERT_THAT(ssl_priv_key, Not(IsNull())); internal::SslUniquePtr ssl_pub_key(EVP_PKEY_new_raw_public_key( /*type=*/EVP_PKEY_X25519, /*unused=*/nullptr, /*in=*/reinterpret_cast(test_vector.public_key.data()), /*len=*/Ed25519KeyPrivKeySize())); ASSERT_THAT(ssl_pub_key, Not(IsNull())); EXPECT_THAT(ComputeX25519SharedSecret(ssl_priv_key.get(), ssl_pub_key.get()), IsOkAndHolds(util::SecretDataFromStringView( test_vector.expected_shared_secret))); } INSTANTIATE_TEST_SUITE_P(X25519SharedSecretTests, X25519SharedSecretTest, ValuesIn(GetX25519SharedSecretTestVectors())); TEST(EcUtilTest, ComputeX25519SharedSecretInvalidKeyType) { // Key pair of an invalid type EVP_PKEY_ED25519. SslUniquePtr pctx(EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, /*e=*/nullptr)); ASSERT_THAT(pctx, Not(IsNull())); ASSERT_EQ(EVP_PKEY_keygen_init(pctx.get()), 1); EVP_PKEY* invalid_type_key_ptr = nullptr; ASSERT_EQ(EVP_PKEY_keygen(pctx.get(), &invalid_type_key_ptr), 1); SslUniquePtr invalid_type_key(invalid_type_key_ptr); // Private and public key with valid type. internal::SslUniquePtr ssl_priv_key(EVP_PKEY_new_raw_private_key( /*type=*/EVP_PKEY_X25519, /*unused=*/nullptr, /*in=*/ reinterpret_cast( absl::HexStringToBytes("a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5" "a18506a2244ba449ac4") .data()), /*len=*/Ed25519KeyPrivKeySize())); ASSERT_THAT(ssl_priv_key, Not(IsNull())); internal::SslUniquePtr ssl_pub_key(EVP_PKEY_new_raw_public_key( /*type=*/EVP_PKEY_X25519, /*unused=*/nullptr, /*in=*/ reinterpret_cast( absl::HexStringToBytes("e6db6867583030db3594c1a424b15f7c726624ec26b33" "53b10a903a6d0ab1c4c") .data()), /*len=*/Ed25519KeyPubKeySize())); ASSERT_THAT(ssl_pub_key, Not(IsNull())); EXPECT_THAT( ComputeX25519SharedSecret(ssl_priv_key.get(), invalid_type_key.get()) .status(), Not(IsOk())); EXPECT_THAT( ComputeX25519SharedSecret(invalid_type_key.get(), ssl_pub_key.get()) .status(), Not(IsOk())); } struct EncodingTestVector { EcPointFormat format; std::string x_hex; std::string y_hex; std::string encoded_hex; EllipticCurveType curve; }; std::vector GetEncodingTestVectors() { return { {EcPointFormat::UNCOMPRESSED, "00093057fb862f2ad2e82e581baeb3324e7b32946f2ba845a9beeed87d6995f54918ec6" "619b9931955d5a89d4d74adf1046bb362192f2ef6bd3e3d2d04dd1f87054a", "00aa3fb2448335f694e3cda4ae0cc71b1b2f2a206fa802d7262f19983c44674fe15327a" "caac1fa40424c395a6556cb8167312527fae5865ecffc14bbdc17da78cdcf", "0400093057fb862f2ad2e82e581baeb3324e7b32946f2ba845a9beeed87d6995f54918e" "c6619b9931955d5a89d4d74adf1046bb362192f2ef6bd3e3d2d04dd1f87054a00aa3fb2" "448335f694e3cda4ae0cc71b1b2f2a206fa802d7262f19983c44674fe15327acaac1fa4" "0424c395a6556cb8167312527fae5865ecffc14bbdc17da78cdcf", EllipticCurveType::NIST_P521}, {EcPointFormat::DO_NOT_USE_CRUNCHY_UNCOMPRESSED, "00093057fb862f2ad2e82e581baeb3324e7b32946f2ba845a9beeed87d6995f54918ec6" "619b9931955d5a89d4d74adf1046bb362192f2ef6bd3e3d2d04dd1f87054a", "00aa3fb2448335f694e3cda4ae0cc71b1b2f2a206fa802d7262f19983c44674fe15327a" "caac1fa40424c395a6556cb8167312527fae5865ecffc14bbdc17da78cdcf", "00093057fb862f2ad2e82e581baeb3324e7b32946f2ba845a9beeed87d6995f54918ec6" "619b9931955d5a89d4d74adf1046bb362192f2ef6bd3e3d2d04dd1f87054a00aa3fb244" "8335f694e3cda4ae0cc71b1b2f2a206fa802d7262f19983c44674fe15327acaac1fa404" "24c395a6556cb8167312527fae5865ecffc14bbdc17da78cdcf", EllipticCurveType::NIST_P521}, {EcPointFormat::COMPRESSED, "00093057fb862f2ad2e82e581baeb3324e7b32946f2ba845a9beeed87d6995f54918ec6" "619b9931955d5a89d4d74adf1046bb362192f2ef6bd3e3d2d04dd1f87054a", "00aa3fb2448335f694e3cda4ae0cc71b1b2f2a206fa802d7262f19983c44674fe15327a" "caac1fa40424c395a6556cb8167312527fae5865ecffc14bbdc17da78cdcf", "0300093057fb862f2ad2e82e581baeb3324e7b32946f2ba845a9beeed87d6995f54918e" "c6619b9931955d5a89d4d74adf1046bb362192f2ef6bd3e3d2d04dd1f87054a", EllipticCurveType::NIST_P521}}; } using EcUtilEncodeDecodePointTest = TestWithParam; TEST_P(EcUtilEncodeDecodePointTest, EcPointEncode) { const EncodingTestVector& test = GetParam(); util::StatusOr> point = GetEcPoint(test.curve, absl::HexStringToBytes(test.x_hex), absl::HexStringToBytes(test.y_hex)); ASSERT_THAT(point, IsOk()); util::StatusOr encoded_point = EcPointEncode(test.curve, test.format, point->get()); ASSERT_THAT(encoded_point, IsOk()); EXPECT_EQ(test.encoded_hex, absl::BytesToHexString(*encoded_point)); } TEST_P(EcUtilEncodeDecodePointTest, EcPointDecode) { const EncodingTestVector& test = GetParam(); // Get the test point and its encoded version. util::StatusOr> point = GetEcPoint(test.curve, absl::HexStringToBytes(test.x_hex), absl::HexStringToBytes(test.y_hex)); ASSERT_THAT(point, IsOk()); std::string encoded_str = absl::HexStringToBytes(test.encoded_hex); util::StatusOr> ec_group = EcGroupFromCurveType(test.curve); util::StatusOr> ec_point = EcPointDecode(test.curve, test.format, encoded_str); ASSERT_THAT(ec_point, IsOk()); EXPECT_EQ(EC_POINT_cmp(ec_group->get(), point->get(), ec_point->get(), /*ctx=*/nullptr), 0); // Modifying the 1st byte decoding fails. encoded_str[0] = '0'; util::StatusOr> ec_point2 = EcPointDecode(test.curve, test.format, encoded_str); EXPECT_THAT(ec_point2, Not(IsOk())); if (test.format == EcPointFormat::UNCOMPRESSED || test.format == EcPointFormat::COMPRESSED) { EXPECT_THAT(std::string(ec_point2.status().message()), HasSubstr("point should start with")); } } INSTANTIATE_TEST_SUITE_P( EcUtilEncodeDecodePointTests, EcUtilEncodeDecodePointTest, ValuesIn(GetEncodingTestVectors()), [](const TestParamInfo& info) { switch (info.param.format) { case EcPointFormat::UNCOMPRESSED: return "Uncompressed"; case EcPointFormat::DO_NOT_USE_CRUNCHY_UNCOMPRESSED: return "DoNotUseCrunchyUncompressed"; case EcPointFormat::COMPRESSED: return "Compressed"; default: return "Unknown"; } }); TEST(EcUtilTest, EcFieldSizeInBytes) { EXPECT_THAT(EcFieldSizeInBytes(EllipticCurveType::NIST_P256), IsOkAndHolds(256 / 8)); EXPECT_THAT(EcFieldSizeInBytes(EllipticCurveType::NIST_P384), IsOkAndHolds(384 / 8)); EXPECT_THAT(EcFieldSizeInBytes(EllipticCurveType::NIST_P521), IsOkAndHolds((521 + 7) / 8)); EXPECT_THAT(EcFieldSizeInBytes(EllipticCurveType::CURVE25519), IsOkAndHolds(256 / 8)); EXPECT_THAT(EcFieldSizeInBytes(EllipticCurveType::UNKNOWN_CURVE).status(), Not(IsOk())); } TEST(EcUtilTest, EcPointEncodingSizeInBytes) { EXPECT_THAT(EcPointEncodingSizeInBytes(EllipticCurveType::NIST_P256, EcPointFormat::UNCOMPRESSED), IsOkAndHolds(2 * (256 / 8) + 1)); EXPECT_THAT(EcPointEncodingSizeInBytes(EllipticCurveType::NIST_P256, EcPointFormat::COMPRESSED), IsOkAndHolds(256 / 8 + 1)); EXPECT_THAT(EcPointEncodingSizeInBytes(EllipticCurveType::NIST_P384, EcPointFormat::UNCOMPRESSED), IsOkAndHolds(2 * (384 / 8) + 1)); EXPECT_THAT(EcPointEncodingSizeInBytes(EllipticCurveType::NIST_P384, EcPointFormat::COMPRESSED), IsOkAndHolds(384 / 8 + 1)); EXPECT_THAT(EcPointEncodingSizeInBytes(EllipticCurveType::NIST_P521, EcPointFormat::UNCOMPRESSED), IsOkAndHolds(2 * ((521 + 7) / 8) + 1)); EXPECT_THAT(EcPointEncodingSizeInBytes(EllipticCurveType::NIST_P521, EcPointFormat::COMPRESSED), IsOkAndHolds((521 + 7) / 8 + 1)); EXPECT_THAT(EcPointEncodingSizeInBytes(EllipticCurveType::CURVE25519, EcPointFormat::COMPRESSED), IsOkAndHolds(256 / 8)); EXPECT_THAT(EcPointEncodingSizeInBytes(EllipticCurveType::NIST_P256, EcPointFormat::UNKNOWN_FORMAT) .status(), Not(IsOk())); } TEST(EcUtilTest, CurveTypeFromEcGroupSuccess) { EC_GROUP* p256_group = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1); EC_GROUP* p384_group = EC_GROUP_new_by_curve_name(NID_secp384r1); EC_GROUP* p521_group = EC_GROUP_new_by_curve_name(NID_secp521r1); util::StatusOr p256_curve = CurveTypeFromEcGroup(p256_group); util::StatusOr p384_curve = CurveTypeFromEcGroup(p384_group); util::StatusOr p521_curve = CurveTypeFromEcGroup(p521_group); ASSERT_THAT(p256_curve, IsOkAndHolds(EllipticCurveType::NIST_P256)); ASSERT_THAT(p384_curve, IsOkAndHolds(EllipticCurveType::NIST_P384)); ASSERT_THAT(p521_curve, IsOkAndHolds(EllipticCurveType::NIST_P521)); } TEST(EcUtilTest, CurveTypeFromEcGroupUnimplemented) { EXPECT_THAT( CurveTypeFromEcGroup(EC_GROUP_new_by_curve_name(NID_secp224r1)).status(), StatusIs(absl::StatusCode::kUnimplemented)); } TEST(EcUtilTest, EcGroupFromCurveTypeSuccess) { util::StatusOr> p256_curve = EcGroupFromCurveType(EllipticCurveType::NIST_P256); util::StatusOr> p384_curve = EcGroupFromCurveType(EllipticCurveType::NIST_P384); util::StatusOr> p521_curve = EcGroupFromCurveType(EllipticCurveType::NIST_P521); ASSERT_THAT(p256_curve, IsOk()); ASSERT_THAT(p384_curve, IsOk()); ASSERT_THAT(p521_curve, IsOk()); SslUniquePtr ssl_p256_group( EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); SslUniquePtr ssl_p384_group( EC_GROUP_new_by_curve_name(NID_secp384r1)); SslUniquePtr ssl_p521_group( EC_GROUP_new_by_curve_name(NID_secp521r1)); EXPECT_EQ( EC_GROUP_cmp(p256_curve->get(), ssl_p256_group.get(), /*ctx=*/nullptr), 0); EXPECT_EQ( EC_GROUP_cmp(p384_curve->get(), ssl_p384_group.get(), /*ctx=*/nullptr), 0); EXPECT_EQ( EC_GROUP_cmp(p521_curve->get(), ssl_p521_group.get(), /*ctx=*/nullptr), 0); } TEST(EcUtilTest, EcGroupFromCurveTypeUnimplemented) { EXPECT_THAT(EcGroupFromCurveType(EllipticCurveType::UNKNOWN_CURVE).status(), StatusIs(absl::StatusCode::kUnimplemented)); } TEST(EcUtilTest, GetEcPointReturnsAValidPoint) { SslUniquePtr group(EC_GROUP_new_by_curve_name(NID_secp521r1)); const unsigned int kCurveSizeInBytes = (EC_GROUP_get_degree(group.get()) + 7) / 8; constexpr absl::string_view kXCoordinateHex = "00093057fb862f2ad2e82e581baeb3324e7b32946f2ba845a9beeed87d6995f54918ec6" "619b9931955d5a89d4d74adf1046bb362192f2ef6bd3e3d2d04dd1f87054a"; constexpr absl::string_view kYCoordinateHex = "00aa3fb2448335f694e3cda4ae0cc71b1b2f2a206fa802d7262f19983c44674fe15327a" "caac1fa40424c395a6556cb8167312527fae5865ecffc14bbdc17da78cdcf"; util::StatusOr> point = GetEcPoint( EllipticCurveType::NIST_P521, absl::HexStringToBytes(kXCoordinateHex), absl::HexStringToBytes(kYCoordinateHex)); ASSERT_THAT(point, IsOk()); // We check that we can decode this point and the result is the same as the // original coordinates. std::string xy; subtle::ResizeStringUninitialized(&xy, 2 * kCurveSizeInBytes); SslUniquePtr x(BN_new()); SslUniquePtr y(BN_new()); ASSERT_EQ(EC_POINT_get_affine_coordinates(group.get(), point->get(), x.get(), y.get(), /*ctx=*/nullptr), 1); ASSERT_THAT( BignumToBinaryPadded(absl::MakeSpan(&xy[0], kCurveSizeInBytes), x.get()), IsOk()); ASSERT_THAT( BignumToBinaryPadded( absl::MakeSpan(&xy[kCurveSizeInBytes], kCurveSizeInBytes), y.get()), IsOk()); EXPECT_EQ(xy, absl::StrCat(absl::HexStringToBytes(kXCoordinateHex), absl::HexStringToBytes(kYCoordinateHex))); } TEST(EcUtilTest, EcSignatureIeeeToDer) { std::unique_ptr test_vectors = WycheproofUtil::ReadTestVectors("ecdsa_webcrypto_test.json"); ASSERT_THAT(test_vectors, Not(IsNull())); for (const auto& test_group : (*test_vectors)["testGroups"].GetArray()) { EllipticCurveType curve = WycheproofUtil::GetEllipticCurveType(test_group["key"]["curve"]); if (curve == EllipticCurveType::UNKNOWN_CURVE) { continue; } util::StatusOr> ec_group = EcGroupFromCurveType(curve); ASSERT_THAT(ec_group, IsOk()); // Read all the valid signatures. for (const auto& test : test_group["tests"].GetArray()) { std::string result = test["result"].GetString(); if (result != "valid") { continue; } std::string sig = WycheproofUtil::GetBytes(test["sig"]); util::StatusOr der_encoded = EcSignatureIeeeToDer(ec_group->get(), sig); ASSERT_THAT(der_encoded, IsOk()); // Make sure we can reconstruct the IEEE format: [ s || r ]. const uint8_t* der_sig_data_ptr = reinterpret_cast(der_encoded->data()); SslUniquePtr ecdsa_sig(d2i_ECDSA_SIG( /*out=*/nullptr, &der_sig_data_ptr, der_encoded->size())); ASSERT_THAT(ecdsa_sig, Not(IsNull())); // Owned by OpenSSL/BoringSSL. const BIGNUM* r; const BIGNUM* s; ECDSA_SIG_get0(ecdsa_sig.get(), &r, &s); ASSERT_THAT(r, Not(IsNull())); ASSERT_THAT(s, Not(IsNull())); util::StatusOr field_size = EcFieldSizeInBytes(curve); ASSERT_THAT(field_size, IsOk()); util::StatusOr r_str = BignumToString(r, *field_size); ASSERT_THAT(r_str, IsOk()); util::StatusOr s_str = BignumToString(s, *field_size); ASSERT_THAT(s_str, IsOk()); EXPECT_EQ(absl::StrCat(*r_str, *s_str), sig); } } } // ECDH test vector. struct EcdhWycheproofTestVector { std::string testcase_name; EllipticCurveType curve; std::string id; std::string comment; std::string pub_bytes; std::string priv_bytes; std::string expected_shared_bytes; std::string result; EcPointFormat format; }; // Utility function to look for a `value` inside an array of flags `flags`. bool HasFlag(const rapidjson::Value& flags, absl::string_view value) { if (!flags.IsArray()) { return false; } for (const rapidjson::Value& flag : flags.GetArray()) { if (std::string(flag.GetString()) == value) { return true; } } return false; } // Reads Wycheproof's ECDH test vectors from the given file `file_name`. std::vector ReadEcdhWycheproofTestVectors( absl::string_view file_name) { std::unique_ptr root = WycheproofUtil::ReadTestVectors(std::string(file_name)); std::vector test_vectors; for (const rapidjson::Value& test_group : (*root)["testGroups"].GetArray()) { // Tink only supports secp256r1, secp384r1 or secp521r1. EllipticCurveType curve = WycheproofUtil::GetEllipticCurveType(test_group["curve"]); if (curve == EllipticCurveType::UNKNOWN_CURVE) { continue; } for (const rapidjson::Value& test : test_group["tests"].GetArray()) { // Wycheproof's ECDH public key uses ASN encoding while Tink uses X9.62 // format point encoding. For the purpose of testing, we note the // followings: // + The prefix of ASN encoding contains curve name, so we can skip test // vector with "UnnamedCurve". // + The suffix of ASN encoding is X9.62 format point encoding. // TODO(quannguyen): Use X9.62 test vectors once it's available. if (HasFlag(test["flags"], /*value=*/"UnnamedCurve")) { continue; } // Get the format from "flags". EcPointFormat format = EcPointFormat::UNCOMPRESSED; if (HasFlag(test["flags"], /*value=*/"CompressedPoint")) { format = EcPointFormat::COMPRESSED; } // Testcase name is of the form: _tcid. std::vector file_name_tokens = absl::StrSplit(file_name, '.'); test_vectors.push_back({ absl::StrCat(file_name_tokens[0], "_tcid", test["tcId"].GetInt()), curve, absl::StrCat(test["tcId"].GetInt()), test["comment"].GetString(), WycheproofUtil::GetBytes(test["public"]), WycheproofUtil::GetBytes(test["private"]), WycheproofUtil::GetBytes(test["shared"]), test["result"].GetString(), format, }); } } return test_vectors; } using EcUtilComputeEcdhSharedSecretTest = TestWithParam; TEST_P(EcUtilComputeEcdhSharedSecretTest, ComputeEcdhSharedSecretWycheproof) { EcdhWycheproofTestVector params = GetParam(); util::StatusOr point_size = internal::EcPointEncodingSizeInBytes(params.curve, params.format); ASSERT_THAT(point_size, IsOk()); if (*point_size > params.pub_bytes.size()) { GTEST_SKIP(); } std::string pub_bytes = params.pub_bytes.substr( params.pub_bytes.size() - *point_size, *point_size); util::StatusOr> pub_key = EcPointDecode(params.curve, params.format, pub_bytes); if (!pub_key.ok()) { // Make sure we didn't fail decoding a valid point, then we can terminate // testing; ASSERT_NE(params.result, "valid"); return; } util::StatusOr> priv_key = StringToBignum(params.priv_bytes); ASSERT_THAT(priv_key, IsOk()); util::StatusOr shared_secret = ComputeEcdhSharedSecret(params.curve, priv_key->get(), pub_key->get()); if (params.result == "invalid") { EXPECT_THAT(shared_secret, Not(IsOk())); } else { EXPECT_THAT(shared_secret, IsOkAndHolds(util::SecretDataFromStringView( params.expected_shared_bytes))); } } std::vector GetEcUtilComputeEcdhSharedSecretParams() { std::vector test_vectors = ReadEcdhWycheproofTestVectors( /*file_name=*/"ecdh_secp256r1_test.json"); std::vector others = ReadEcdhWycheproofTestVectors( /*file_name=*/"ecdh_secp384r1_test.json"); test_vectors.insert(test_vectors.end(), others.begin(), others.end()); others = ReadEcdhWycheproofTestVectors( /*file_name=*/"ecdh_secp521r1_test.json"); test_vectors.insert(test_vectors.end(), others.begin(), others.end()); others = ReadEcdhWycheproofTestVectors( /*file_name=*/"ecdh_test.json"); test_vectors.insert(test_vectors.end(), others.begin(), others.end()); return test_vectors; } INSTANTIATE_TEST_SUITE_P( EcUtilComputeEcdhSharedSecretTests, EcUtilComputeEcdhSharedSecretTest, ValuesIn(GetEcUtilComputeEcdhSharedSecretParams()), [](const TestParamInfo& info) { return info.param.testcase_name; }); } // namespace } // namespace internal } // namespace tink } // namespace crypto