blob: b1608a6f4ceb5fcb6fb804974115fcc33c9f3d97 [file] [log] [blame]
// Copyright 2021 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "libhwsec-foundation/crypto/elliptic_curve.h"
#include <optional>
#include "libhwsec-foundation/crypto/big_num_util.h"
#include "libhwsec-foundation/crypto/error_util.h"
#include <gtest/gtest.h>
#include <base/logging.h>
namespace hwsec_foundation {
namespace {
constexpr EllipticCurve::CurveType kCurve = EllipticCurve::CurveType::kPrime256;
constexpr int kScalarSizeInBytes = 32;
constexpr int kFieldElementSizeInBytes = 32;
// SPKI DER formatted (ecPublicKey, prime256v1) public point with valid point
// which is not on curve.
const char kSpkiDerPoint[] =
"3059301306072A8648CE3D020106082A8648CE3D030107034200040102030405060708090A"
"0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F"
"303132333435363738393A3B3C3D3E3F40";
brillo::SecureBlob CreateBogusPointSpkiDer() {
brillo::SecureBlob spki_der;
if (!brillo::SecureBlob::HexStringToSecureBlob(kSpkiDerPoint, &spki_der)) {
ADD_FAILURE() << "Failed to convert hex to SecureBlob";
return brillo::SecureBlob();
}
return spki_der;
}
// Returns a EC_KEY object with public key set to provided `point`. Unlike
// `EllipticCurve::PointToEccKey`, doesn't check if the key is valid.
crypto::ScopedEC_KEY PointToEccKey(const EC_POINT& point,
const EC_GROUP* group) {
crypto::ScopedEC_Key key(EC_KEY_new());
if (!key) {
ADD_FAILURE() << "Failed to allocate EC_KEY structure: "
<< GetOpenSSLErrors();
return nullptr;
}
if (EC_KEY_set_group(key.get(), group) != 1) {
ADD_FAILURE() << "Failed to set EC group: " << GetOpenSSLErrors();
return nullptr;
}
if (!EC_KEY_set_public_key(key.get(), &point)) {
ADD_FAILURE() << "Failed to set public key: " << GetOpenSSLErrors();
return nullptr;
}
return key;
}
} // namespace
class EllipticCurveTest : public testing::Test {
public:
void SetUp() override {
context_ = CreateBigNumContext();
ASSERT_TRUE(context_);
ec_ = EllipticCurve::Create(kCurve, context_.get());
ASSERT_TRUE(ec_);
}
// Creates point as generator multiplied by scalar_value.
// Returns nullptr if error occurred.
crypto::ScopedEC_POINT CreatePoint(BN_ULONG scalar_value) {
crypto::ScopedBIGNUM scalar = BigNumFromValue(scalar_value);
if (!scalar) {
LOG(ERROR) << "Failed to create BIGNUM structure";
return nullptr;
}
return ec_->MultiplyWithGenerator(*scalar, context_.get());
}
// Creates invalid point that is not on a curve.
// Returns nullptr if error occurred.
crypto::ScopedEC_POINT CreateInvalidPoint() {
crypto::ScopedEC_POINT point = ec_->PointAtInfinityForTesting();
if (!point) {
LOG(ERROR) << "Failed to create point at infinity";
return nullptr;
}
// Set point to some coordinates that are not on a curve.
crypto::ScopedBIGNUM x = BigNumFromValue(123u);
if (!x) {
LOG(ERROR) << "Failed to create BIGNUM structure";
return nullptr;
}
crypto::ScopedBIGNUM y = BigNumFromValue(321u);
if (!y) {
LOG(ERROR) << "Failed to create BIGNUM structure";
return nullptr;
}
// Set affine coordinates outside of the curve. Assume the method will fail,
// but it should still initialize the point.
if (EC_POINT_set_affine_coordinates(ec_->GetGroup(), point.get(), x.get(),
y.get(), context_.get()) == 1) {
LOG(ERROR) << "Failed to set affine coords for invalid point";
return nullptr;
}
// Capture OpenSSL error from error stack.
std::string error = GetOpenSSLErrors();
if (error.find("EC_POINT_set_affine_coordinates:point is not on curve") ==
std::string::npos) {
LOG(ERROR) << "Failed to create invalid point";
return nullptr;
}
// Verify that the point is not at infinity anymore, so it was indeed set,
// but it's not on a curve.
if (ec_->IsPointAtInfinity(*point) ||
ec_->IsPointValid(*point, context_.get())) {
LOG(ERROR) << "Failed to create invalid point";
return nullptr;
}
return point;
}
protected:
ScopedBN_CTX context_;
std::optional<EllipticCurve> ec_;
};
TEST_F(EllipticCurveTest, GetCurveType) {
std::optional<EllipticCurve> ec_256 = EllipticCurve::Create(
EllipticCurve::CurveType::kPrime256, context_.get());
ASSERT_TRUE(ec_256);
EXPECT_EQ(ec_256->GetCurveType(), EllipticCurve::CurveType::kPrime256);
std::optional<EllipticCurve> ec_384 = EllipticCurve::Create(
EllipticCurve::CurveType::kPrime384, context_.get());
ASSERT_TRUE(ec_384);
EXPECT_EQ(ec_384->GetCurveType(), EllipticCurve::CurveType::kPrime384);
std::optional<EllipticCurve> ec_521 = EllipticCurve::Create(
EllipticCurve::CurveType::kPrime521, context_.get());
ASSERT_TRUE(ec_521);
EXPECT_EQ(ec_521->GetCurveType(), EllipticCurve::CurveType::kPrime521);
}
TEST_F(EllipticCurveTest, ScalarAndAffineCoordinateSizeInBytes) {
EXPECT_EQ(ec_->ScalarSizeInBytes(), kScalarSizeInBytes);
EXPECT_EQ(ec_->AffineCoordinateSizeInBytes(), kFieldElementSizeInBytes);
}
TEST_F(EllipticCurveTest, PointAtInfinity) {
crypto::ScopedEC_POINT point = ec_->PointAtInfinityForTesting();
ASSERT_TRUE(point);
EXPECT_TRUE(ec_->IsPointValid(*point, context_.get()));
EXPECT_TRUE(ec_->IsPointAtInfinity(*point));
}
TEST_F(EllipticCurveTest, RandomNonZeroScalar) {
// Generates random secret. Note that this is non-deterministic,
// so we just check if the output is smaller than curve order
// and non-zero.
crypto::ScopedBIGNUM secret = ec_->RandomNonZeroScalar(context_.get());
ASSERT_TRUE(secret);
EXPECT_EQ(BN_cmp(secret.get(), ec_->GetOrderForTesting()), -1);
EXPECT_EQ(BN_is_zero(secret.get()), 0);
}
TEST_F(EllipticCurveTest, SubjectPublicKeyInfoConversions) {
crypto::ScopedEC_KEY key = ec_->GenerateKey(context_.get());
ASSERT_TRUE(key);
// Encode the public key:
brillo::SecureBlob spki_der_point_blob;
ASSERT_TRUE(ec_->EncodeToSpkiDer(key, &spki_der_point_blob, context_.get()));
// Decode the public key:
crypto::ScopedEC_POINT spki_der_decoded_key =
ec_->DecodeFromSpkiDer(spki_der_point_blob, context_.get());
// Compare the keys:
EXPECT_TRUE(ec_->AreEqual(*EC_KEY_get0_public_key(key.get()),
*spki_der_decoded_key, context_.get()));
// Test conversions of invalid or infinite points.
brillo::SecureBlob point_blob;
crypto::ScopedEC_POINT invalid_point = CreateInvalidPoint();
ASSERT_TRUE(invalid_point);
crypto::ScopedEC_KEY invalid_point_key =
PointToEccKey(*invalid_point, ec_->GetGroup());
ASSERT_TRUE(invalid_point_key);
EXPECT_FALSE(
ec_->EncodeToSpkiDer(invalid_point_key, &point_blob, context_.get()));
crypto::ScopedEC_POINT point_at_inf = ec_->PointAtInfinityForTesting();
ASSERT_TRUE(point_at_inf);
crypto::ScopedEC_KEY point_at_inf_key =
PointToEccKey(*point_at_inf, ec_->GetGroup());
ASSERT_TRUE(point_at_inf_key);
EXPECT_FALSE(
ec_->EncodeToSpkiDer(point_at_inf_key, &point_blob, context_.get()));
crypto::ScopedEC_POINT decoded_key =
ec_->DecodeFromSpkiDer(brillo::SecureBlob("not_a_point"), context_.get());
EXPECT_FALSE(decoded_key);
decoded_key =
ec_->DecodeFromSpkiDer(CreateBogusPointSpkiDer(), context_.get());
EXPECT_FALSE(decoded_key);
}
TEST_F(EllipticCurveTest, PointToEccKey) {
crypto::ScopedEC_KEY key = ec_->GenerateKey(context_.get());
ASSERT_TRUE(key);
const EC_POINT* public_key = EC_KEY_get0_public_key(key.get());
ASSERT_TRUE(public_key);
crypto::ScopedEC_KEY key_1 = ec_->PointToEccKey(*public_key);
ASSERT_TRUE(key_1);
const EC_POINT* public_key_1 = EC_KEY_get0_public_key(key_1.get());
ASSERT_TRUE(public_key_1);
EXPECT_TRUE(ec_->AreEqual(*public_key, *public_key_1, context_.get()));
// Test conversions of invalid or infinite points.
brillo::SecureBlob point_blob;
crypto::ScopedEC_POINT invalid_point = CreateInvalidPoint();
ASSERT_TRUE(invalid_point);
crypto::ScopedEC_KEY invalid_point_key = ec_->PointToEccKey(*invalid_point);
EXPECT_FALSE(invalid_point_key);
crypto::ScopedEC_POINT point_at_inf = ec_->PointAtInfinityForTesting();
ASSERT_TRUE(point_at_inf);
crypto::ScopedEC_KEY point_at_inf_key = ec_->PointToEccKey(*point_at_inf);
EXPECT_FALSE(point_at_inf_key);
}
TEST_F(EllipticCurveTest, PointToEccKeySubjectPublicKeyInfoConversions) {
crypto::ScopedEC_KEY key = ec_->GenerateKey(context_.get());
ASSERT_TRUE(key);
const EC_POINT* public_key = EC_KEY_get0_public_key(key.get());
ASSERT_TRUE(public_key);
crypto::ScopedEC_KEY key_1 = ec_->PointToEccKey(*public_key);
ASSERT_TRUE(key_1);
brillo::SecureBlob spki_der_public_key, spki_der_public_key_1;
ASSERT_TRUE(ec_->EncodeToSpkiDer(key, &spki_der_public_key, context_.get()));
ASSERT_TRUE(
ec_->EncodeToSpkiDer(key_1, &spki_der_public_key_1, context_.get()));
EXPECT_EQ(spki_der_public_key, spki_der_public_key_1);
}
TEST_F(EllipticCurveTest, Add) {
crypto::ScopedEC_POINT point1 = CreatePoint(1u);
ASSERT_TRUE(point1);
crypto::ScopedEC_POINT point2 = CreatePoint(2u);
ASSERT_TRUE(point2);
crypto::ScopedEC_POINT point3 = CreatePoint(3u);
ASSERT_TRUE(point3);
crypto::ScopedEC_POINT result = ec_->Add(*point1, *point2, context_.get());
ASSERT_TRUE(result);
EXPECT_TRUE(ec_->AreEqual(*result, *point3, context_.get()));
// Double the point.
result = ec_->Add(*point1, *point1, context_.get());
ASSERT_TRUE(result);
EXPECT_TRUE(ec_->AreEqual(*result, *point2, context_.get()));
// Add point to its inverse.
crypto::ScopedEC_POINT inv_point3 = CreatePoint(3u);
ASSERT_EQ(EC_POINT_invert(ec_->GetGroup(), inv_point3.get(), context_.get()),
1);
result = ec_->Add(*point3, *inv_point3, context_.get());
ASSERT_TRUE(result);
EXPECT_TRUE(ec_->IsPointAtInfinity(*result));
// Check if inverse of nG is (order-n)*G.
crypto::ScopedBIGNUM order_sub_3 = BigNumFromValue(3u);
ASSERT_TRUE(order_sub_3);
ASSERT_EQ(
BN_sub(order_sub_3.get(), ec_->GetOrderForTesting(), order_sub_3.get()),
1);
result = ec_->MultiplyWithGenerator(*order_sub_3, context_.get());
EXPECT_TRUE(ec_->AreEqual(*inv_point3, *result, context_.get()));
// Double point at infinity.
crypto::ScopedEC_POINT point_at_inf = ec_->PointAtInfinityForTesting();
result = ec_->Add(*point_at_inf, *point_at_inf, context_.get());
ASSERT_TRUE(result);
EXPECT_TRUE(ec_->IsPointAtInfinity(*point_at_inf));
}
TEST_F(EllipticCurveTest, MultiplicationWithGenerator) {
crypto::ScopedBIGNUM scalar1 = BigNumFromValue(123u);
ASSERT_TRUE(scalar1);
crypto::ScopedBIGNUM scalar2 = BigNumFromValue(321u);
ASSERT_TRUE(scalar2);
crypto::ScopedBIGNUM scalar_prod = CreateBigNum();
ASSERT_TRUE(scalar_prod);
ASSERT_EQ(
BN_mul(scalar_prod.get(), scalar1.get(), scalar2.get(), context_.get()),
1);
EXPECT_EQ(BN_get_word(scalar_prod.get()), 123u * 321u);
// Test if (G*scalar1)*scalar2 = G*(scalar1*scalar2).
crypto::ScopedEC_POINT point1 =
ec_->MultiplyWithGenerator(*scalar1, context_.get());
EXPECT_TRUE(ec_->IsPointValidAndFinite(*point1, context_.get()));
crypto::ScopedEC_POINT point2 =
ec_->Multiply(*point1, *scalar2, context_.get());
EXPECT_TRUE(ec_->IsPointValidAndFinite(*point2, context_.get()));
crypto::ScopedEC_POINT point_prod =
ec_->MultiplyWithGenerator(*scalar_prod, context_.get());
EXPECT_TRUE(ec_->IsPointValidAndFinite(*point_prod, context_.get()));
EXPECT_TRUE(ec_->AreEqual(*point2, *point_prod, context_.get()));
}
TEST_F(EllipticCurveTest, MultiplyWithGeneratorByBigScalars) {
// Get big scalars of curve order.
crypto::ScopedBIGNUM scalar1 = BigNumFromValue(123u);
ASSERT_TRUE(scalar1);
ASSERT_EQ(BN_sub(scalar1.get(), ec_->GetOrderForTesting(), scalar1.get()), 1);
crypto::ScopedBIGNUM scalar2 = BigNumFromValue(321u);
ASSERT_TRUE(scalar2);
ASSERT_EQ(BN_sub(scalar2.get(), ec_->GetOrderForTesting(), scalar2.get()), 1);
crypto::ScopedBIGNUM scalar_sum = CreateBigNum();
ASSERT_TRUE(scalar_sum);
ASSERT_EQ(BN_add(scalar_sum.get(), scalar1.get(), scalar2.get()), 1);
// Expect scalar_sum > order.
EXPECT_EQ(BN_cmp(scalar_sum.get(), ec_->GetOrderForTesting()), 1);
// Multiplication by scalar greater than order should fail.
EXPECT_FALSE(ec_->MultiplyWithGenerator(*scalar_sum, context_.get()));
crypto::ScopedBIGNUM scalar_mod_sum =
ec_->ModAdd(*scalar1, *scalar2, context_.get());
ASSERT_TRUE(scalar_mod_sum);
// Expect scalar_mod_sum < order.
EXPECT_EQ(BN_cmp(scalar_mod_sum.get(), ec_->GetOrderForTesting()), -1);
// Test if G*scalar1 + G*scalar2 = G*((scalar1 + scalar2) mod order).
crypto::ScopedEC_POINT point1 =
ec_->MultiplyWithGenerator(*scalar1, context_.get());
ASSERT_TRUE(point1);
EXPECT_TRUE(ec_->IsPointValidAndFinite(*point1, context_.get()));
crypto::ScopedEC_POINT point2 =
ec_->MultiplyWithGenerator(*scalar2, context_.get());
ASSERT_TRUE(point2);
EXPECT_TRUE(ec_->IsPointValidAndFinite(*point2, context_.get()));
crypto::ScopedEC_POINT point_sum1 =
ec_->MultiplyWithGenerator(*scalar_mod_sum, context_.get());
ASSERT_TRUE(point_sum1);
EXPECT_TRUE(ec_->IsPointValidAndFinite(*point_sum1, context_.get()));
crypto::ScopedEC_POINT point_sum2 =
ec_->Add(*point1, *point2, context_.get());
ASSERT_TRUE(point_sum2);
EXPECT_TRUE(ec_->IsPointValidAndFinite(*point_sum2, context_.get()));
EXPECT_TRUE(ec_->AreEqual(*point_sum1, *point_sum2, context_.get()));
}
TEST_F(EllipticCurveTest, MultiplyWithGeneratorByZero) {
crypto::ScopedBIGNUM scalar = BigNumFromValue(0);
crypto::ScopedEC_POINT point =
ec_->MultiplyWithGenerator(*scalar, context_.get());
EXPECT_TRUE(ec_->IsPointValid(*point, context_.get()));
EXPECT_TRUE(ec_->IsPointAtInfinity(*point));
}
TEST_F(EllipticCurveTest, MultiplyWithPointAtInfinity) {
crypto::ScopedBIGNUM scalar = BigNumFromValue(123u);
ASSERT_TRUE(scalar);
crypto::ScopedEC_POINT point = ec_->PointAtInfinityForTesting();
ASSERT_TRUE(point);
crypto::ScopedEC_POINT result =
ec_->Multiply(*point, *scalar, context_.get());
ASSERT_TRUE(result);
EXPECT_TRUE(ec_->IsPointAtInfinity(*result));
// Try 0 x point at infinity. The result should be also point at infinity.
scalar = BigNumFromValue(0u);
ASSERT_TRUE(scalar);
result = ec_->Multiply(*point, *scalar, context_.get());
ASSERT_TRUE(result);
EXPECT_TRUE(ec_->IsPointAtInfinity(*result));
}
TEST_F(EllipticCurveTest, MultiplyWithInvalidPoint) {
crypto::ScopedBIGNUM scalar = BigNumFromValue(1u);
ASSERT_TRUE(scalar);
crypto::ScopedEC_POINT point = CreateInvalidPoint();
ASSERT_TRUE(point);
// Verify that multiplication does not accept bogus point as the input.
crypto::ScopedEC_POINT result =
ec_->Multiply(*point, *scalar, context_.get());
EXPECT_FALSE(result);
}
TEST_F(EllipticCurveTest, MultiplyWithGeneratorByNegative) {
crypto::ScopedBIGNUM scalar1 = BigNumFromValue(123u);
ASSERT_TRUE(scalar1);
crypto::ScopedBIGNUM scalar2 = BigNumFromValue(321u);
ASSERT_TRUE(scalar2);
crypto::ScopedEC_POINT point1 =
ec_->MultiplyWithGenerator(*scalar1, context_.get());
crypto::ScopedEC_POINT point2 =
ec_->MultiplyWithGenerator(*scalar2, context_.get());
BN_set_negative(scalar1.get(), 1);
crypto::ScopedEC_POINT inverse_point1 =
ec_->MultiplyWithGenerator(*scalar1, context_.get());
crypto::ScopedEC_POINT point_sum_12 =
ec_->Add(*point1, *point2, context_.get());
crypto::ScopedEC_POINT point_sum_all =
ec_->Add(*point_sum_12, *inverse_point1, context_.get());
// Validates that after adding the inversion of point1 its contribution
// cancels out and we are left with point2.
ASSERT_TRUE(ec_->AreEqual(*point2, *point_sum_all, context_.get()));
}
TEST_F(EllipticCurveTest, GenerateKey) {
crypto::ScopedEC_KEY key = ec_->GenerateKey(context_.get());
ASSERT_TRUE(key);
const BIGNUM* private_key = EC_KEY_get0_private_key(key.get());
ASSERT_TRUE(private_key);
const EC_POINT* public_key = EC_KEY_get0_public_key(key.get());
ASSERT_TRUE(public_key);
// Validate that private_key * G = public_key.
crypto::ScopedEC_POINT expected_public_key =
ec_->MultiplyWithGenerator(*private_key, context_.get());
EXPECT_TRUE(ec_->AreEqual(*expected_public_key, *public_key, context_.get()));
}
TEST_F(EllipticCurveTest, GenerateKeysAsSecureBlobs) {
brillo::SecureBlob public_blob;
brillo::SecureBlob private_blob;
ASSERT_TRUE(ec_->GenerateKeysAsSecureBlobs(&public_blob, &private_blob,
context_.get()));
crypto::ScopedEC_POINT public_key =
ec_->DecodeFromSpkiDer(public_blob, context_.get());
ASSERT_TRUE(public_key);
crypto::ScopedBIGNUM private_key = SecureBlobToBigNum(private_blob);
ASSERT_TRUE(private_key);
// Validate that private_key * G = public_key.
crypto::ScopedEC_POINT expected_public_key =
ec_->MultiplyWithGenerator(*private_key, context_.get());
EXPECT_TRUE(ec_->AreEqual(*expected_public_key, *public_key, context_.get()));
}
TEST_F(EllipticCurveTest, InvertPoint) {
crypto::ScopedBIGNUM scalar = BigNumFromValue(123u);
ASSERT_TRUE(scalar);
crypto::ScopedEC_POINT point =
ec_->MultiplyWithGenerator(*scalar, context_.get());
BN_set_negative(scalar.get(), 1);
crypto::ScopedEC_POINT inverse_point =
ec_->MultiplyWithGenerator(*scalar, context_.get());
EXPECT_TRUE(ec_->InvertPoint(point.get(), context_.get()));
// Validates that the inverted point equals to inverse_point.
EXPECT_TRUE(ec_->AreEqual(*inverse_point, *point, context_.get()));
}
TEST_F(EllipticCurveTest, InversePointAddition) {
crypto::ScopedBIGNUM scalar1 = BigNumFromValue(123u);
ASSERT_TRUE(scalar1);
crypto::ScopedBIGNUM scalar2 = BigNumFromValue(321u);
ASSERT_TRUE(scalar2);
crypto::ScopedEC_POINT point1 =
ec_->MultiplyWithGenerator(*scalar1, context_.get());
ASSERT_TRUE(point1);
crypto::ScopedEC_POINT point2 =
ec_->MultiplyWithGenerator(*scalar2, context_.get());
ASSERT_TRUE(point2);
crypto::ScopedEC_POINT point_sum_12 =
ec_->Add(*point1, *point2, context_.get());
ASSERT_TRUE(point_sum_12);
ec_->InvertPoint(point1.get(), context_.get());
crypto::ScopedEC_POINT point_sum_all =
ec_->Add(*point_sum_12, *point1, context_.get());
ASSERT_TRUE(point_sum_all);
// Validates that after adding the inverted point1 its contribution
// cancels out and we are left with point2.
EXPECT_TRUE(ec_->AreEqual(*point2, *point_sum_all, context_.get()));
}
TEST_F(EllipticCurveTest, ScalarRangeCheck) {
std::string str_ff(32, static_cast<char>(0xff));
brillo::SecureBlob blob_ff(str_ff.begin(), str_ff.end());
crypto::ScopedBIGNUM num_ff = SecureBlobToBigNum(blob_ff);
EXPECT_FALSE(ec_->IsScalarValid(*num_ff));
std::string str_23(32, static_cast<char>(0x23));
brillo::SecureBlob blob_23(str_23.begin(), str_23.end());
crypto::ScopedBIGNUM num_23 = SecureBlobToBigNum(blob_23);
EXPECT_TRUE(ec_->IsScalarValid(*num_23));
std::string str_cc(32, static_cast<char>(0xcc));
brillo::SecureBlob blob_cc(str_cc.begin(), str_cc.end());
crypto::ScopedBIGNUM num_cc = SecureBlobToBigNum(blob_cc);
EXPECT_TRUE(ec_->IsScalarValid(*num_cc));
std::string str_00(32, static_cast<char>(0));
brillo::SecureBlob blob_00(str_00.begin(), str_00.end());
crypto::ScopedBIGNUM num_00 = SecureBlobToBigNum(blob_00);
EXPECT_TRUE(ec_->IsScalarValid(*num_00));
EXPECT_FALSE(ec_->IsScalarValid(*ec_->GetOrderForTesting()));
}
} // namespace hwsec_foundation