From 8b8c021a911dc4220ee9e5b09b25c704daa0787d Mon Sep 17 00:00:00 2001 From: Tete17 Date: Tue, 14 Apr 2026 00:00:47 +0200 Subject: [PATCH] LibCrypto: Support EC compressed point format (0x02/0x03) Add SECPxxxr1Point::from_compressed() to decompress SEC 1 compressed EC public keys using OpenSSL's EC_POINT_oct2point. Refactor read_ec_public_key() to delegate to SECPxxxr1Point::from_uncompressed and from_compressed, removing duplicated parsing logic. --- Libraries/LibCrypto/Curves/SECPxxxr1.cpp | 43 +++++++++++++ Libraries/LibCrypto/Curves/SECPxxxr1.h | 7 +- Libraries/LibCrypto/PK/EC.cpp | 48 ++------------ Tests/LibCrypto/TestCurves.cpp | 82 ++++++++++++++++++++++++ 4 files changed, 135 insertions(+), 45 deletions(-) diff --git a/Libraries/LibCrypto/Curves/SECPxxxr1.cpp b/Libraries/LibCrypto/Curves/SECPxxxr1.cpp index aa31a2a024..513ec1928a 100644 --- a/Libraries/LibCrypto/Curves/SECPxxxr1.cpp +++ b/Libraries/LibCrypto/Curves/SECPxxxr1.cpp @@ -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::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 SECPxxxr1::generate_private_key() { auto key = TRY(OpenSSL_PKEY::wrap(EVP_PKEY_Q_keygen(nullptr, nullptr, "EC", m_curve_name))); diff --git a/Libraries/LibCrypto/Curves/SECPxxxr1.h b/Libraries/LibCrypto/Curves/SECPxxxr1.h index c287b7f9c3..d7b741d967 100644 --- a/Libraries/LibCrypto/Curves/SECPxxxr1.h +++ b/Libraries/LibCrypto/Curves/SECPxxxr1.h @@ -44,10 +44,13 @@ struct SECPxxxr1Point { static ErrorOr 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)), @@ -55,6 +58,8 @@ struct SECPxxxr1Point { }; } + static ErrorOr from_compressed(ReadonlyBytes data); + ErrorOr x_bytes() const { return scalar_to_bytes(x, size); diff --git a/Libraries/LibCrypto/PK/EC.cpp b/Libraries/LibCrypto/PK/EC.cpp index 60ded0b647..5b7f11e4e2 100644 --- a/Libraries/LibCrypto/PK/EC.cpp +++ b/Libraries/LibCrypto/PK/EC.cpp @@ -45,39 +45,6 @@ ErrorOr ECPrivateKey::export_as_der() const return encoder.finish(); } -static ErrorOr decompress_ec_public_key(ReadonlyBytes compressed_bytes, Vector current_scope) -{ - auto coordinate_size = compressed_bytes.size() - 1; - char const* curve_name = nullptr; - if (coordinate_size == 32) - curve_name = "P-256"; - else if (coordinate_size == 48) - curve_name = "P-384"; - else if (coordinate_size == 66) - curve_name = "P-521"; - else - ERROR_WITH_SCOPE("Unsupported compressed point size"); - - auto* group = OPENSSL_TRY_PTR(EC_GROUP_new_by_curve_name(EC_curve_nist2nid(curve_name))); - 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, compressed_bytes.data(), compressed_bytes.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 ::Crypto::PK::ECPublicKey { - TRY(openssl_bignum_to_unsigned_big_integer(x)), - TRY(openssl_bignum_to_unsigned_big_integer(y)), - coordinate_size, - }; -} - static ErrorOr read_ec_public_key(ReadonlyBytes bytes, Vector current_scope) { // NOTE: Public keys do not have an ASN1 structure @@ -86,21 +53,14 @@ static ErrorOr read_ec_public_key(ReadonlyBytes bytes, Vector