diff --git a/Libraries/LibCrypto/Curves/SECPxxxr1.cpp b/Libraries/LibCrypto/Curves/SECPxxxr1.cpp index 37e82c61c33..ad14a073f19 100644 --- a/Libraries/LibCrypto/Curves/SECPxxxr1.cpp +++ b/Libraries/LibCrypto/Curves/SECPxxxr1.cpp @@ -181,4 +181,32 @@ ErrorOr SECPxxxr1::sign(ReadonlyBytes hash, UnsignedBigInteg }; } +ErrorOr SECPxxxr1::is_valid_point(SECPxxxr1Point pubkey, Optional private_key) +{ + auto* group = OPENSSL_TRY_PTR(EC_GROUP_new_by_curve_name(EC_curve_nist2nid(m_curve_name))); + ScopeGuard const free_group = [&] { EC_GROUP_free(group); }; + + auto x = TRY(unsigned_big_integer_to_openssl_bignum(pubkey.x)); + auto y = TRY(unsigned_big_integer_to_openssl_bignum(pubkey.y)); + + auto* point = OPENSSL_TRY_PTR(EC_POINT_new(group)); + ScopeGuard const free_point = [&] { EC_POINT_free(point); }; + + // Check public point is on the curve, EC_POINT_set_affine_coordinates calls EC_POINT_is_on_curve internally + if (EC_POINT_set_affine_coordinates(group, point, x.ptr(), y.ptr(), nullptr) != 1) + return false; + + if (!private_key.has_value()) + return true; + + // Check that the public key is a valid point for the given private key + auto private_key_int = TRY(unsigned_big_integer_to_openssl_bignum(*private_key)); + + auto* expected_point = EC_POINT_new(group); + ScopeGuard const free_expected_point = [&] { EC_POINT_free(expected_point); }; + + OPENSSL_TRY(EC_POINT_mul(group, expected_point, private_key_int.ptr(), nullptr, nullptr, nullptr)); + return EC_POINT_cmp(group, expected_point, point, nullptr) == 0; +} + } diff --git a/Libraries/LibCrypto/Curves/SECPxxxr1.h b/Libraries/LibCrypto/Curves/SECPxxxr1.h index 9502a494773..a0188153236 100644 --- a/Libraries/LibCrypto/Curves/SECPxxxr1.h +++ b/Libraries/LibCrypto/Curves/SECPxxxr1.h @@ -165,6 +165,7 @@ public: ErrorOr compute_coordinate(UnsignedBigInteger scalar, SECPxxxr1Point point); ErrorOr verify(ReadonlyBytes hash, SECPxxxr1Point pubkey, SECPxxxr1Signature signature); ErrorOr sign(ReadonlyBytes hash, UnsignedBigInteger private_key); + ErrorOr is_valid_point(SECPxxxr1Point pubkey, Optional private_key = {}); protected: SECPxxxr1(char const* curve_name, size_t scalar_size) diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index 04144567b8b..c82d89f8d92 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -4049,8 +4049,29 @@ WebIDL::ExceptionOr> ECDSA::import_key(AlgorithmParams const& if (!named_curve.is_empty() && named_curve != normalized_algorithm.named_curve) return WebIDL::DataError::create(m_realm, "Invalid algorithm"_string); - // TODO: 12. If the public key value is not a valid point on the Elliptic Curve identified - // by the namedCurve member of normalizedAlgorithm throw a DataError. + // 12. If the public key value is not a valid point on the Elliptic Curve identified + // by the namedCurve member of normalizedAlgorithm throw a DataError. + auto curve = [&named_curve] -> Variant<::Crypto::Curves::SECP256r1, ::Crypto::Curves::SECP384r1, ::Crypto::Curves::SECP521r1> { + if (named_curve == "P-256") + return ::Crypto::Curves::SECP256r1 {}; + if (named_curve == "P-384") + return ::Crypto::Curves::SECP384r1 {}; + if (named_curve == "P-521") + return ::Crypto::Curves::SECP521r1 {}; + VERIFY_NOT_REACHED(); + }(); + TRY(curve.visit([&](auto& instance) -> WebIDL::ExceptionOr { + auto maybe_valid = key->handle().visit( + [](auto const&) -> ErrorOr { VERIFY_NOT_REACHED(); }, + [&](::Crypto::PK::ECPublicKey<> const& public_key) { + return instance.is_valid_point(public_key.to_secpxxxr1_point()); + }); + if (maybe_valid.is_error()) + return WebIDL::DataError::create(m_realm, "Failed to verify key"_string); + if (!maybe_valid.value()) + return WebIDL::DataError::create(m_realm, "Invalid key"_string); + return {}; + })); // 13. Set the [[type]] internal slot of key to "public" key->set_type(Bindings::KeyType::Public); @@ -4146,8 +4167,32 @@ WebIDL::ExceptionOr> ECDSA::import_key(AlgorithmParams const& if (!named_curve.is_empty() && named_curve != normalized_algorithm.named_curve) return WebIDL::DataError::create(m_realm, "Invalid algorithm"_string); - // TODO: 12. If the key value is not a valid point on the Elliptic Curve identified - // by the namedCurve member of normalizedAlgorithm throw a DataError. + // 12. If the key value is not a valid point on the Elliptic Curve identified + // by the namedCurve member of normalizedAlgorithm throw a DataError. + auto curve = [&named_curve] -> Variant<::Crypto::Curves::SECP256r1, ::Crypto::Curves::SECP384r1, ::Crypto::Curves::SECP521r1> { + if (named_curve == "P-256") + return ::Crypto::Curves::SECP256r1 {}; + if (named_curve == "P-384") + return ::Crypto::Curves::SECP384r1 {}; + if (named_curve == "P-521") + return ::Crypto::Curves::SECP521r1 {}; + VERIFY_NOT_REACHED(); + }(); + TRY(curve.visit([&](auto& instance) -> WebIDL::ExceptionOr { + auto maybe_valid = key->handle().visit( + [](auto const&) -> ErrorOr { VERIFY_NOT_REACHED(); }, + [&](::Crypto::PK::ECPrivateKey<> const& private_key) -> ErrorOr { + if (!private_key.public_key().has_value()) + return true; + + return instance.is_valid_point(private_key.public_key()->to_secpxxxr1_point(), private_key.d()); + }); + if (maybe_valid.is_error()) + return WebIDL::DataError::create(m_realm, "Failed to verify key"_string); + if (!maybe_valid.value()) + return WebIDL::DataError::create(m_realm, "Invalid key"_string); + return {}; + })); // 13. Set the [[type]] internal slot of key to "private". key->set_type(Bindings::KeyType::Private); @@ -4322,8 +4367,32 @@ WebIDL::ExceptionOr> ECDSA::import_key(AlgorithmParams const& return WebIDL::DataError::create(m_realm, "Invalid algorithm"_string); } - // TODO: 10. If the key value is not a valid point on the Elliptic Curve identified - // by the namedCurve member of normalizedAlgorithm throw a DataError. + // 10. If the key value is not a valid point on the Elliptic Curve identified + // by the namedCurve member of normalizedAlgorithm throw a DataError. + auto curve = [&named_curve] -> Variant<::Crypto::Curves::SECP256r1, ::Crypto::Curves::SECP384r1, ::Crypto::Curves::SECP521r1> { + if (named_curve == "P-256") + return ::Crypto::Curves::SECP256r1 {}; + if (named_curve == "P-384") + return ::Crypto::Curves::SECP384r1 {}; + if (named_curve == "P-521") + return ::Crypto::Curves::SECP521r1 {}; + VERIFY_NOT_REACHED(); + }(); + TRY(curve.visit([&](auto& instance) -> WebIDL::ExceptionOr { + auto maybe_valid = key->handle().visit( + [](auto const&) -> ErrorOr { VERIFY_NOT_REACHED(); }, + [&](::Crypto::PK::ECPublicKey<> const& public_key) { + return instance.is_valid_point(public_key.to_secpxxxr1_point()); + }, + [&](::Crypto::PK::ECPrivateKey<> const& private_key) { + return instance.is_valid_point(private_key.public_key()->to_secpxxxr1_point(), private_key.d()); + }); + if (maybe_valid.is_error()) + return WebIDL::DataError::create(m_realm, "Failed to verify key"_string); + if (!maybe_valid.value()) + return WebIDL::DataError::create(m_realm, "Invalid key"_string); + return {}; + })); // 11. Let algorithm be a new instance of an EcKeyAlgorithm object. auto algorithm = EcKeyAlgorithm::create(m_realm); @@ -4983,8 +5052,29 @@ WebIDL::ExceptionOr> ECDH::import_key(AlgorithmParams const& if (!named_curve.is_empty() && named_curve != normalized_algorithm.named_curve) return WebIDL::DataError::create(m_realm, "Invalid algorithm"_string); - // TODO: 12. If the key value is not a valid point on the Elliptic Curve identified - // by the namedCurve member of normalizedAlgorithm throw a DataError. + // 12. If the key value is not a valid point on the Elliptic Curve identified + // by the namedCurve member of normalizedAlgorithm throw a DataError. + auto curve = [&named_curve] -> Variant<::Crypto::Curves::SECP256r1, ::Crypto::Curves::SECP384r1, ::Crypto::Curves::SECP521r1> { + if (named_curve == "P-256") + return ::Crypto::Curves::SECP256r1 {}; + if (named_curve == "P-384") + return ::Crypto::Curves::SECP384r1 {}; + if (named_curve == "P-521") + return ::Crypto::Curves::SECP521r1 {}; + VERIFY_NOT_REACHED(); + }(); + TRY(curve.visit([&](auto& instance) -> WebIDL::ExceptionOr { + auto maybe_valid = key->handle().visit( + [](auto const&) -> ErrorOr { VERIFY_NOT_REACHED(); }, + [&](::Crypto::PK::ECPublicKey<> const& public_key) { + return instance.is_valid_point(public_key.to_secpxxxr1_point()); + }); + if (maybe_valid.is_error()) + return WebIDL::DataError::create(m_realm, "Failed to verify key"_string); + if (!maybe_valid.value()) + return WebIDL::DataError::create(m_realm, "Invalid key"_string); + return {}; + })); // 13. Set the [[type]] internal slot of key to "public" key->set_type(Bindings::KeyType::Public); @@ -5080,8 +5170,32 @@ WebIDL::ExceptionOr> ECDH::import_key(AlgorithmParams const& if (!named_curve.is_empty() && named_curve != normalized_algorithm.named_curve) return WebIDL::DataError::create(m_realm, "Invalid algorithm"_string); - // TODO: 12. If the key value is not a valid point on the Elliptic Curve identified - // by the namedCurve member of normalizedAlgorithm throw a DataError. + // 12. If the key value is not a valid point on the Elliptic Curve identified + // by the namedCurve member of normalizedAlgorithm throw a DataError. + auto curve = [&named_curve] -> Variant<::Crypto::Curves::SECP256r1, ::Crypto::Curves::SECP384r1, ::Crypto::Curves::SECP521r1> { + if (named_curve == "P-256") + return ::Crypto::Curves::SECP256r1 {}; + if (named_curve == "P-384") + return ::Crypto::Curves::SECP384r1 {}; + if (named_curve == "P-521") + return ::Crypto::Curves::SECP521r1 {}; + VERIFY_NOT_REACHED(); + }(); + TRY(curve.visit([&](auto& instance) -> WebIDL::ExceptionOr { + auto maybe_valid = key->handle().visit( + [](auto const&) -> ErrorOr { VERIFY_NOT_REACHED(); }, + [&](::Crypto::PK::ECPrivateKey<> const& private_key) -> ErrorOr { + if (!private_key.public_key().has_value()) + return true; + + return instance.is_valid_point(private_key.public_key()->to_secpxxxr1_point(), private_key.d()); + }); + if (maybe_valid.is_error()) + return WebIDL::DataError::create(m_realm, "Failed to verify key"_string); + if (!maybe_valid.value()) + return WebIDL::DataError::create(m_realm, "Invalid key"_string); + return {}; + })); // 13. Set the [[type]] internal slot of key to "private". key->set_type(Bindings::KeyType::Private); @@ -5225,8 +5339,32 @@ WebIDL::ExceptionOr> ECDH::import_key(AlgorithmParams const& return WebIDL::DataError::create(m_realm, "Invalid algorithm"_string); } - // TODO: 11. If the key value is not a valid point on the Elliptic Curve identified - // by the namedCurve member of normalizedAlgorithm throw a DataError. + // 11. If the key value is not a valid point on the Elliptic Curve identified + // by the namedCurve member of normalizedAlgorithm throw a DataError. + auto curve = [&named_curve] -> Variant<::Crypto::Curves::SECP256r1, ::Crypto::Curves::SECP384r1, ::Crypto::Curves::SECP521r1> { + if (named_curve == "P-256") + return ::Crypto::Curves::SECP256r1 {}; + if (named_curve == "P-384") + return ::Crypto::Curves::SECP384r1 {}; + if (named_curve == "P-521") + return ::Crypto::Curves::SECP521r1 {}; + VERIFY_NOT_REACHED(); + }(); + TRY(curve.visit([&](auto& instance) -> WebIDL::ExceptionOr { + auto maybe_valid = key->handle().visit( + [](auto const&) -> ErrorOr { VERIFY_NOT_REACHED(); }, + [&](::Crypto::PK::ECPublicKey<> const& public_key) { + return instance.is_valid_point(public_key.to_secpxxxr1_point()); + }, + [&](::Crypto::PK::ECPrivateKey<> const& private_key) { + return instance.is_valid_point(private_key.public_key()->to_secpxxxr1_point(), private_key.d()); + }); + if (maybe_valid.is_error()) + return WebIDL::DataError::create(m_realm, "Failed to verify key"_string); + if (!maybe_valid.value()) + return WebIDL::DataError::create(m_realm, "Invalid key"_string); + return {}; + })); // 12. Let algorithm be a new instance of an EcKeyAlgorithm object. auto algorithm = EcKeyAlgorithm::create(m_realm); diff --git a/Tests/LibWeb/Text/expected/Crypto/SubtleCrypto-ec-bad-import-key.txt b/Tests/LibWeb/Text/expected/Crypto/SubtleCrypto-ec-bad-import-key.txt new file mode 100644 index 00000000000..24089564c2a --- /dev/null +++ b/Tests/LibWeb/Text/expected/Crypto/SubtleCrypto-ec-bad-import-key.txt @@ -0,0 +1,30 @@ +ECDSA P-256 PUB - jwk OK: DataError: Invalid key +ECDSA P-256 PUB - spki OK: DataError: Invalid key +ECDSA P-256 PRIV - jwk OK: DataError: Invalid key +ECDSA P-256 PRIV - pkcs8 OK: DataError: Invalid key +ECDSA P-256 PRIV - pkcs8 OK: DataError: Invalid key +ECDSA P-384 PUB - jwk OK: DataError: Invalid key +ECDSA P-384 PUB - spki OK: DataError: Invalid key +ECDSA P-384 PRIV - jwk OK: DataError: Invalid key +ECDSA P-384 PRIV - pkcs8 OK: DataError: Invalid key +ECDSA P-384 PRIV - pkcs8 OK: DataError: Invalid key +ECDSA P-521 PUB - jwk OK: DataError: Invalid key +ECDSA P-521 PUB - spki OK: DataError: Invalid key +ECDSA P-521 PRIV - jwk OK: DataError: Invalid key +ECDSA P-521 PRIV - pkcs8 OK: DataError: Invalid key +ECDSA P-521 PRIV - pkcs8 OK: DataError: Invalid key +ECDH P-256 PUB - jwk OK: DataError: Invalid key +ECDH P-256 PUB - spki OK: DataError: Invalid key +ECDH P-256 PRIV - jwk OK: DataError: Invalid key +ECDH P-256 PRIV - pkcs8 OK: DataError: Invalid key +ECDH P-256 PRIV - pkcs8 OK: DataError: Invalid key +ECDH P-384 PUB - jwk OK: DataError: Invalid key +ECDH P-384 PUB - spki OK: DataError: Invalid key +ECDH P-384 PRIV - jwk OK: DataError: Invalid key +ECDH P-384 PRIV - pkcs8 OK: DataError: Invalid key +ECDH P-384 PRIV - pkcs8 OK: DataError: Invalid key +ECDH P-521 PUB - jwk OK: DataError: Invalid key +ECDH P-521 PUB - spki OK: DataError: Invalid key +ECDH P-521 PRIV - jwk OK: DataError: Invalid key +ECDH P-521 PRIV - pkcs8 OK: DataError: Invalid key +ECDH P-521 PRIV - pkcs8 OK: DataError: Invalid key diff --git a/Tests/LibWeb/Text/input/Crypto/SubtleCrypto-ec-bad-import-key.html b/Tests/LibWeb/Text/input/Crypto/SubtleCrypto-ec-bad-import-key.html new file mode 100644 index 00000000000..d2d47f707d0 --- /dev/null +++ b/Tests/LibWeb/Text/input/Crypto/SubtleCrypto-ec-bad-import-key.html @@ -0,0 +1,112 @@ + + +