Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions Libraries/LibCrypto/Curves/SECPxxxr1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,49 @@

namespace Crypto::Curves {

static char const* curve_name_for_scalar_size(size_t scalar_size)
{
switch (scalar_size) {
case 32:
return "P-256";
case 48:
return "P-384";
case 66:
return "P-521";
default:
return nullptr;
}
}

ErrorOr<SECPxxxr1Point> SECPxxxr1Point::from_compressed(ReadonlyBytes data)
{
if (data.size() < 2 || (data[0] != 0x02 && data[0] != 0x03))
return Error::from_string_literal("Invalid compressed EC point");

auto scalar_size = data.size() - 1;
auto const* curve = curve_name_for_scalar_size(scalar_size);
if (!curve)
return Error::from_string_literal("Unsupported curve for compressed EC point");

auto* group = OPENSSL_TRY_PTR(EC_GROUP_new_by_curve_name(EC_curve_nist2nid(curve)));
ScopeGuard const free_group = [&] { EC_GROUP_free(group); };

auto* point = OPENSSL_TRY_PTR(EC_POINT_new(group));
ScopeGuard const free_point = [&] { EC_POINT_free(point); };

OPENSSL_TRY(EC_POINT_oct2point(group, point, data.data(), data.size(), nullptr));

auto x = TRY(OpenSSL_BN::create());
auto y = TRY(OpenSSL_BN::create());
OPENSSL_TRY(EC_POINT_get_affine_coordinates(group, point, x.ptr(), y.ptr(), nullptr));

return SECPxxxr1Point {
TRY(openssl_bignum_to_unsigned_big_integer(x)),
TRY(openssl_bignum_to_unsigned_big_integer(y)),
scalar_size,
};
}

ErrorOr<UnsignedBigInteger> SECPxxxr1::generate_private_key()
{
auto key = TRY(OpenSSL_PKEY::wrap(EVP_PKEY_Q_keygen(nullptr, nullptr, "EC", m_curve_name)));
Expand Down
7 changes: 6 additions & 1 deletion Libraries/LibCrypto/Curves/SECPxxxr1.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,22 @@ struct SECPxxxr1Point {

static ErrorOr<SECPxxxr1Point> from_uncompressed(ReadonlyBytes data)
{
if (data.size() < 1 || data[0] != 0x04)
if (data.size() < 3 || data[0] != 0x04)
return Error::from_string_literal("Invalid length or not an uncompressed SECPxxxr1 point");

auto half_size = (data.size() - 1) / 2;
if (1 + half_size * 2 != data.size())
return Error::from_string_literal("Invalid uncompressed SECPxxxr1 point length");

return SECPxxxr1Point {
UnsignedBigInteger::import_data(data.slice(1, half_size)),
UnsignedBigInteger::import_data(data.slice(1 + half_size, half_size)),
half_size,
};
}

static ErrorOr<SECPxxxr1Point> from_compressed(ReadonlyBytes data);

ErrorOr<ByteBuffer> x_bytes() const
{
return scalar_to_bytes(x, size);
Expand Down
15 changes: 6 additions & 9 deletions Libraries/LibCrypto/PK/EC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,13 @@ static ErrorOr<ECPublicKey> read_ec_public_key(ReadonlyBytes bytes, Vector<Strin
}

if (bytes[0] == 0x04) {
auto half_size = (bytes.size() - 1) / 2;
if (1 + half_size * 2 != bytes.size()) {
ERROR_WITH_SCOPE("Invalid public key length");
}
auto point = TRY(Curves::SECPxxxr1Point::from_uncompressed(bytes));
return ECPublicKey { move(point) };
}

return ::Crypto::PK::ECPublicKey {
UnsignedBigInteger::import_data(bytes.slice(1, half_size)),
UnsignedBigInteger::import_data(bytes.slice(1 + half_size, half_size)),
half_size,
};
if (bytes[0] == 0x02 || bytes[0] == 0x03) {
auto point = TRY(Curves::SECPxxxr1Point::from_compressed(bytes));
return ECPublicKey { move(point) };
}

if (bytes.size() % 2 == 0) {
Expand Down
30 changes: 15 additions & 15 deletions Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2738,7 +2738,7 @@ WebIDL::ExceptionOr<GC::Ref<JS::Object>> AesCbc::export_key(Bindings::KeyFormat
GC::Ptr<JS::Object> result = nullptr;

// 2. -> If format is "raw":
if (format == Bindings::KeyFormat::Raw) {
if (format == Bindings::KeyFormat::Raw || format == Bindings::KeyFormat::RawSecret) {
// 1. Let data be the raw octets of the key represented by [[handle]] internal slot of key.
auto data = handle.get<ByteBuffer>();

Expand Down Expand Up @@ -2939,7 +2939,7 @@ WebIDL::ExceptionOr<GC::Ref<JS::Object>> AesCtr::export_key(Bindings::KeyFormat
GC::Ptr<JS::Object> result = nullptr;

// 2. If format is "raw":
if (format == Bindings::KeyFormat::Raw) {
if (format == Bindings::KeyFormat::Raw || format == Bindings::KeyFormat::RawSecret) {
// 1. Let data be the raw octets of the key represented by [[handle]] internal slot of key.
auto data = key->handle().get<ByteBuffer>();

Expand Down Expand Up @@ -3259,7 +3259,7 @@ WebIDL::ExceptionOr<GC::Ref<JS::Object>> AesGcm::export_key(Bindings::KeyFormat
GC::Ptr<JS::Object> result = nullptr;

// 2. If format is "raw":
if (format == Bindings::KeyFormat::Raw) {
if (format == Bindings::KeyFormat::Raw || format == Bindings::KeyFormat::RawSecret) {
// 1. Let data be the raw octets of the key represented by [[handle]] internal slot of key.
auto data = key->handle().get<ByteBuffer>();

Expand Down Expand Up @@ -3607,7 +3607,7 @@ WebIDL::ExceptionOr<GC::Ref<JS::Object>> AesKw::export_key(Bindings::KeyFormat f
GC::Ptr<JS::Object> result = nullptr;

// 2. If format is "raw":
if (format == Bindings::KeyFormat::Raw) {
if (format == Bindings::KeyFormat::Raw || format == Bindings::KeyFormat::RawSecret) {
// 1. Let data be the raw octets of the key represented by [[handle]] internal slot of key.
auto data = key->handle().get<ByteBuffer>();

Expand Down Expand Up @@ -3775,7 +3775,7 @@ WebIDL::ExceptionOr<GC::Ref<CryptoKey>> HKDF::import_key(AlgorithmParams const&,
// (… see below …)
// Otherwise:
// throw a NotSupportedError.
if (format != Bindings::KeyFormat::Raw) {
if (format != Bindings::KeyFormat::Raw && format != Bindings::KeyFormat::RawSecret) {
return WebIDL::NotSupportedError::create(m_realm, "Only raw format is supported"_utf16);
}

Expand Down Expand Up @@ -4556,7 +4556,7 @@ WebIDL::ExceptionOr<GC::Ref<CryptoKey>> ECDSA::import_key(AlgorithmParams const&
}

// 2. If format is "raw":
else if (key_format == Bindings::KeyFormat::Raw) {
else if (key_format == Bindings::KeyFormat::Raw || key_format == Bindings::KeyFormat::RawPublic) {
// 1. If the namedCurve member of normalizedAlgorithm is not a named curve, then throw a DataError.
if (!normalized_algorithm.named_curve.is_one_of("P-256"sv, "P-384"sv, "P-521"sv))
return WebIDL::DataError::create(m_realm, "Invalid algorithm"_utf16);
Expand Down Expand Up @@ -4866,7 +4866,7 @@ WebIDL::ExceptionOr<GC::Ref<JS::Object>> ECDSA::export_key(Bindings::KeyFormat f
}

// 3. If format is "raw":
else if (format == Bindings::KeyFormat::Raw) {
else if (format == Bindings::KeyFormat::Raw || format == Bindings::KeyFormat::RawPublic) {
// 1. If the [[type]] internal slot of key is not "public", then throw an InvalidAccessError.
if (key->type() != Bindings::KeyType::Public)
return WebIDL::InvalidAccessError::create(m_realm, "Key is not a public key"_utf16);
Expand Down Expand Up @@ -5511,7 +5511,7 @@ WebIDL::ExceptionOr<GC::Ref<CryptoKey>> ECDH::import_key(AlgorithmParams const&
}

// 2. If format is "raw":
else if (key_format == Bindings::KeyFormat::Raw) {
else if (key_format == Bindings::KeyFormat::Raw || key_format == Bindings::KeyFormat::RawPublic) {
// 1. If the namedCurve member of normalizedAlgorithm is not a named curve, then throw a DataError.
if (!normalized_algorithm.named_curve.is_one_of("P-256"sv, "P-384"sv, "P-521"sv))
return WebIDL::DataError::create(m_realm, "Invalid algorithm"_utf16);
Expand Down Expand Up @@ -5812,7 +5812,7 @@ WebIDL::ExceptionOr<GC::Ref<JS::Object>> ECDH::export_key(Bindings::KeyFormat fo
}

// 3. If format is "raw":
else if (format == Bindings::KeyFormat::Raw) {
else if (format == Bindings::KeyFormat::Raw || format == Bindings::KeyFormat::RawPublic) {
// 1. If the [[type]] internal slot of key is not "public", then throw an InvalidAccessError.
if (key->type() != Bindings::KeyType::Public)
return WebIDL::InvalidAccessError::create(m_realm, "Key is not a public key"_utf16);
Expand Down Expand Up @@ -6145,7 +6145,7 @@ WebIDL::ExceptionOr<GC::Ref<CryptoKey>> ED25519::import_key(
}

// 2. If format is "raw":
else if (format == Bindings::KeyFormat::Raw) {
else if (format == Bindings::KeyFormat::Raw || format == Bindings::KeyFormat::RawPublic) {
// 1. If usages contains a value which is not "verify" then throw a SyntaxError.
for (auto const& usage : usages) {
if (usage != Bindings::KeyUsage::Verify) {
Expand Down Expand Up @@ -6283,7 +6283,7 @@ WebIDL::ExceptionOr<GC::Ref<JS::Object>> ED25519::export_key(Bindings::KeyFormat
}

// 2. If format is "raw":
if (format == Bindings::KeyFormat::Raw) {
if (format == Bindings::KeyFormat::Raw || format == Bindings::KeyFormat::RawPublic) {
// 1. If the [[type]] internal slot of key is not "public", then throw an InvalidAccessError.
if (key->type() != Bindings::KeyType::Public)
return WebIDL::InvalidAccessError::create(m_realm, "Key is not a public key"_utf16);
Expand Down Expand Up @@ -6997,7 +6997,7 @@ WebIDL::ExceptionOr<JS::Value> PBKDF2::get_key_length(AlgorithmParams const&)
WebIDL::ExceptionOr<GC::Ref<CryptoKey>> PBKDF2::import_key(AlgorithmParams const&, Bindings::KeyFormat format, CryptoKey::InternalKeyData key_data, bool extractable, Vector<Bindings::KeyUsage> const& key_usages)
{
// 1. If format is not "raw", throw a NotSupportedError
if (format != Bindings::KeyFormat::Raw)
if (format != Bindings::KeyFormat::Raw && format != Bindings::KeyFormat::RawSecret)
return WebIDL::NotSupportedError::create(m_realm, "Only raw format is supported"_utf16);

// 2. If usages contains a value that is not "deriveKey" or "deriveBits", then throw a SyntaxError.
Expand Down Expand Up @@ -7382,7 +7382,7 @@ WebIDL::ExceptionOr<GC::Ref<CryptoKey>> X25519::import_key([[maybe_unused]] Web:
}

// 2. If format is "raw":
else if (key_format == Bindings::KeyFormat::Raw) {
else if (key_format == Bindings::KeyFormat::Raw || key_format == Bindings::KeyFormat::RawPublic) {
// 1. If usages is not empty then throw a SyntaxError.
if (!usages.is_empty())
return WebIDL::SyntaxError::create(m_realm, "Usages must be empty"_utf16);
Expand Down Expand Up @@ -7518,7 +7518,7 @@ WebIDL::ExceptionOr<GC::Ref<JS::Object>> X25519::export_key(Bindings::KeyFormat
}

// 3. If format is "raw":
else if (format == Bindings::KeyFormat::Raw) {
else if (format == Bindings::KeyFormat::Raw || format == Bindings::KeyFormat::RawPublic) {
// 1. If the [[type]] internal slot of key is not "public", then throw an InvalidAccessError.
if (key->type() != Bindings::KeyType::Public)
return WebIDL::InvalidAccessError::create(m_realm, "Key is not a public key"_utf16);
Expand Down Expand Up @@ -8349,7 +8349,7 @@ WebIDL::ExceptionOr<GC::Ref<JS::Object>> HMAC::export_key(Bindings::KeyFormat fo

// 4. If format is "raw":
GC::Ptr<JS::Object> result;
if (format == Bindings::KeyFormat::Raw) {
if (format == Bindings::KeyFormat::Raw || format == Bindings::KeyFormat::RawSecret) {
// Let result be the result of creating an ArrayBuffer containing data.
result = JS::ArrayBuffer::create(m_realm, data);
}
Expand Down
82 changes: 82 additions & 0 deletions Tests/LibCrypto/TestCurves.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -439,3 +439,85 @@ TEST_CASE(test_secp521r1)
EXPECT_EQ(expected_public_key.x, generated_public.x);
EXPECT_EQ(expected_public_key.y, generated_public.y);
}

// Test vectors from WebCryptoAPI WPT ec_importKey.https.any.js
TEST_CASE(test_ec_compressed_point_p256)
{
// clang-format off
u8 raw_data[] {
0x04,
0xD2, 0x10, 0xB0, 0xA6, 0xF9, 0xD9, 0xF0, 0x12, 0x86, 0x80, 0x58, 0xB4, 0x3F, 0xA4, 0xF4, 0x71,
0x01, 0x85, 0x43, 0xBB, 0xA0, 0x0C, 0x92, 0x50, 0xDF, 0x92, 0x57, 0xC2, 0xAC, 0xAE, 0x5D, 0xD1,
0xCE, 0x03, 0x75, 0x52, 0xD4, 0x81, 0x45, 0x0C, 0xE3, 0x9B, 0x4D, 0x10, 0x95, 0x70, 0x1B, 0x17,
0x5B, 0xFA, 0xB3, 0x4B, 0x8E, 0x6C, 0x09, 0x9E, 0x18, 0xF1, 0xC1, 0x98, 0x35, 0x83, 0x61, 0xE8,
};
u8 compressed_data[] {
0x02,
0xD2, 0x10, 0xB0, 0xA6, 0xF9, 0xD9, 0xF0, 0x12, 0x86, 0x80, 0x58, 0xB4, 0x3F, 0xA4, 0xF4, 0x71,
0x01, 0x85, 0x43, 0xBB, 0xA0, 0x0C, 0x92, 0x50, 0xDF, 0x92, 0x57, 0xC2, 0xAC, 0xAE, 0x5D, 0xD1,
};

auto uncompressed = TRY_OR_FAIL(Crypto::Curves::SECPxxxr1Point::from_uncompressed({ raw_data, sizeof(raw_data) }));
auto compressed = TRY_OR_FAIL(Crypto::Curves::SECPxxxr1Point::from_compressed({ compressed_data, sizeof(compressed_data) }));

EXPECT_EQ(uncompressed.x, compressed.x);
EXPECT_EQ(uncompressed.y, compressed.y);
}

TEST_CASE(test_ec_compressed_point_p384)
{
// clang-format off
u8 raw_data[] {
0x04,
0x21, 0x9C, 0x14, 0xD6, 0x66, 0x17, 0xB3, 0x6E, 0xC6, 0xD8, 0x85, 0x6B, 0x38, 0x5B, 0x73, 0xA7,
0x4D, 0x34, 0x4F, 0xD8, 0xAE, 0x75, 0xEF, 0x04, 0x64, 0x35, 0xDD, 0xA5, 0x4E, 0x3B, 0x44, 0xBD,
0x5F, 0xBD, 0xEB, 0xD1, 0xD0, 0x8D, 0xD6, 0x9E, 0x2D, 0x7D, 0xC1, 0xDC, 0x21, 0x8C, 0xB4, 0x35,
0xBD, 0x28, 0x13, 0x8C, 0xC7, 0x78, 0x33, 0x7A, 0x84, 0x2F, 0x6B, 0xD6, 0x1B, 0x24, 0x0E, 0x74,
0x24, 0x9F, 0x24, 0x66, 0x7C, 0x2A, 0x58, 0x10, 0xA7, 0x6B, 0xFC, 0x28, 0xE0, 0x33, 0x5F, 0x88,
0xA6, 0x50, 0x1D, 0xEC, 0x01, 0x97, 0x6D, 0xA8, 0x5A, 0xFB, 0x00, 0x86, 0x9C, 0xB6, 0xAC, 0xE8,
};
u8 compressed_data[] {
0x02,
0x21, 0x9C, 0x14, 0xD6, 0x66, 0x17, 0xB3, 0x6E, 0xC6, 0xD8, 0x85, 0x6B, 0x38, 0x5B, 0x73, 0xA7,
0x4D, 0x34, 0x4F, 0xD8, 0xAE, 0x75, 0xEF, 0x04, 0x64, 0x35, 0xDD, 0xA5, 0x4E, 0x3B, 0x44, 0xBD,
0x5F, 0xBD, 0xEB, 0xD1, 0xD0, 0x8D, 0xD6, 0x9E, 0x2D, 0x7D, 0xC1, 0xDC, 0x21, 0x8C, 0xB4, 0x35,
};

auto uncompressed = TRY_OR_FAIL(Crypto::Curves::SECPxxxr1Point::from_uncompressed({ raw_data, sizeof(raw_data) }));
auto compressed = TRY_OR_FAIL(Crypto::Curves::SECPxxxr1Point::from_compressed({ compressed_data, sizeof(compressed_data) }));

EXPECT_EQ(uncompressed.x, compressed.x);
EXPECT_EQ(uncompressed.y, compressed.y);
}

TEST_CASE(test_ec_compressed_point_p521)
{
// clang-format off
u8 raw_data[] {
0x04,
0x01, 0x56, 0xF4, 0x79, 0xF8, 0xDF, 0x1E, 0x20, 0xA7, 0xFF, 0xC0, 0x4C, 0xE4, 0x20, 0xC3, 0xE1,
0x54, 0xAE, 0x25, 0x19, 0x96, 0xBE, 0xE4, 0x2F, 0x03, 0x4B, 0x84, 0xD4, 0x1B, 0x74, 0x3F, 0x34,
0xE4, 0x5F, 0x31, 0x1B, 0x81, 0x3A, 0x9C, 0xDE, 0xC8, 0xCD, 0xA5, 0x9B, 0xBB, 0xBD, 0x31, 0xD4,
0x60, 0xB3, 0x29, 0x25, 0x21, 0xE7, 0xC1, 0xB7, 0x22, 0xE5, 0x66, 0x7C, 0x03, 0xDB, 0x2F, 0xAE,
0x75, 0x3F,
0x01, 0x50, 0x17, 0x36, 0xCF, 0xE2, 0x47, 0x39, 0x43, 0x20, 0xD8, 0xE4, 0xAF, 0xC2, 0xFD, 0x39,
0xB5, 0xA9, 0x33, 0x10, 0x61, 0xB8, 0x1E, 0x22, 0x41, 0x28, 0x2B, 0x9E, 0x17, 0x89, 0x18, 0x22,
0xB5, 0xB7, 0x9E, 0x05, 0x2F, 0x45, 0x97, 0xB5, 0x96, 0x43, 0xFD, 0x39, 0x37, 0x9C, 0x51, 0xBD,
0x51, 0x25, 0xC4, 0xF4, 0x8B, 0xC3, 0xF0, 0x25, 0xCE, 0x3C, 0xD3, 0x69, 0x53, 0x28, 0x6C, 0xCB,
0x38, 0xFB,
};
u8 compressed_data[] {
0x03,
0x01, 0x56, 0xF4, 0x79, 0xF8, 0xDF, 0x1E, 0x20, 0xA7, 0xFF, 0xC0, 0x4C, 0xE4, 0x20, 0xC3, 0xE1,
0x54, 0xAE, 0x25, 0x19, 0x96, 0xBE, 0xE4, 0x2F, 0x03, 0x4B, 0x84, 0xD4, 0x1B, 0x74, 0x3F, 0x34,
0xE4, 0x5F, 0x31, 0x1B, 0x81, 0x3A, 0x9C, 0xDE, 0xC8, 0xCD, 0xA5, 0x9B, 0xBB, 0xBD, 0x31, 0xD4,
0x60, 0xB3, 0x29, 0x25, 0x21, 0xE7, 0xC1, 0xB7, 0x22, 0xE5, 0x66, 0x7C, 0x03, 0xDB, 0x2F, 0xAE,
0x75, 0x3F,
};

auto uncompressed = TRY_OR_FAIL(Crypto::Curves::SECPxxxr1Point::from_uncompressed({ raw_data, sizeof(raw_data) }));
auto compressed = TRY_OR_FAIL(Crypto::Curves::SECPxxxr1Point::from_compressed({ compressed_data, sizeof(compressed_data) }));

EXPECT_EQ(uncompressed.x, compressed.x);
EXPECT_EQ(uncompressed.y, compressed.y);
}
Loading
Loading