From ae230c9150f66831d21987c9cdb236643326ee37 Mon Sep 17 00:00:00 2001 From: stelar7 Date: Wed, 27 Mar 2024 02:35:17 +0100 Subject: [PATCH] LibWeb: Implement most of ECDSA verify for SubtleCrypto --- .../expected/Crypto/SubtleCrypto-verify.txt | 2 + .../input/Crypto/SubtleCrypto-verify.html | 45 ++++++++ .../LibWeb/Crypto/CryptoAlgorithms.cpp | 106 ++++++++++++++++++ .../LibWeb/Crypto/CryptoAlgorithms.h | 3 +- .../Libraries/LibWeb/Crypto/SubtleCrypto.cpp | 1 + 5 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 Tests/LibWeb/Text/expected/Crypto/SubtleCrypto-verify.txt create mode 100644 Tests/LibWeb/Text/input/Crypto/SubtleCrypto-verify.html diff --git a/Tests/LibWeb/Text/expected/Crypto/SubtleCrypto-verify.txt b/Tests/LibWeb/Text/expected/Crypto/SubtleCrypto-verify.txt new file mode 100644 index 00000000000..ef7110fd5bd --- /dev/null +++ b/Tests/LibWeb/Text/expected/Crypto/SubtleCrypto-verify.txt @@ -0,0 +1,2 @@ +FIXME: This will fail as we dont support ECDSA sign() +FAIL: Verification not ok diff --git a/Tests/LibWeb/Text/input/Crypto/SubtleCrypto-verify.html b/Tests/LibWeb/Text/input/Crypto/SubtleCrypto-verify.html new file mode 100644 index 00000000000..d870c20172c --- /dev/null +++ b/Tests/LibWeb/Text/input/Crypto/SubtleCrypto-verify.html @@ -0,0 +1,45 @@ + + diff --git a/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index 0870ab38a66..7a5d239d240 100644 --- a/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -1104,4 +1104,110 @@ WebIDL::ExceptionOr> ECDSA::sign(AlgorithmPara return WebIDL::NotSupportedError::create(realm, "ECDSA signing is not supported yet"_fly_string); } +// https://w3c.github.io/webcrypto/#ecdsa-operations +WebIDL::ExceptionOr ECDSA::verify(AlgorithmParams const& params, JS::NonnullGCPtr key, ByteBuffer const& signature, ByteBuffer const& message) +{ + auto& realm = m_realm; + auto const& normalized_algorithm = static_cast(params); + + // 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(realm, "Key is not a public key"_fly_string); + + // 2. Let hashAlgorithm be the hash member of normalizedAlgorithm. + [[maybe_unused]] auto const& hash_algorithm = TRY(normalized_algorithm.hash.visit( + [](String const& name) -> JS::ThrowCompletionOr { return name; }, + [&](JS::Handle const& obj) -> JS::ThrowCompletionOr { + auto name_property = TRY(obj->get("name")); + return name_property.to_string(m_realm.vm()); })); + + // 3. Let M be the result of performing the digest operation specified by hashAlgorithm using message. + ::Crypto::Hash::HashKind hash_kind; + if (hash_algorithm.equals_ignoring_ascii_case("SHA-1"sv)) { + hash_kind = ::Crypto::Hash::HashKind::SHA1; + } else if (hash_algorithm.equals_ignoring_ascii_case("SHA-256"sv)) { + hash_kind = ::Crypto::Hash::HashKind::SHA256; + } else if (hash_algorithm.equals_ignoring_ascii_case("SHA-384"sv)) { + hash_kind = ::Crypto::Hash::HashKind::SHA384; + } else if (hash_algorithm.equals_ignoring_ascii_case("SHA-512"sv)) { + hash_kind = ::Crypto::Hash::HashKind::SHA512; + } else { + return WebIDL::NotSupportedError::create(m_realm, MUST(String::formatted("Invalid hash function '{}'", hash_algorithm))); + } + ::Crypto::Hash::Manager hash { hash_kind }; + hash.update(message); + auto digest = hash.digest(); + + auto result_buffer = ByteBuffer::copy(digest.immutable_data(), hash.digest_size()); + if (result_buffer.is_error()) + return WebIDL::OperationError::create(m_realm, "Failed to create result buffer"_fly_string); + + auto M = result_buffer.release_value(); + + // 4. Let Q be the ECDSA public key associated with key. + auto Q = key->handle().visit( + [](ByteBuffer data) -> ByteBuffer { + return data; + }, + [](auto) -> ByteBuffer { VERIFY_NOT_REACHED(); }); + + // FIXME: 5. Let params be the EC domain parameters associated with key. + + // 6. If the namedCurve attribute of the [[algorithm]] internal slot of key is "P-256", "P-384" or "P-521": + auto const& internal_algorithm = static_cast(*key->algorithm()); + auto const& named_curve = internal_algorithm.named_curve(); + + auto result = false; + + Variant curve; + if (named_curve.is_one_of("P-256"sv, "P-384"sv, "P-521"sv)) { + if (named_curve.equals_ignoring_ascii_case("P-256"sv)) + curve = ::Crypto::Curves::SECP256r1 {}; + + if (named_curve.equals_ignoring_ascii_case("P-384"sv)) + curve = ::Crypto::Curves::SECP384r1 {}; + + // FIXME: Support P-521 + if (named_curve.equals_ignoring_ascii_case("P-521"sv)) + return WebIDL::NotSupportedError::create(m_realm, "'P-521' is not supported yet"_fly_string); + + // Perform the ECDSA verifying process, as specified in [RFC6090], Section 5.3, + // with M as the received message, + // signature as the received signature + // and using params as the EC domain parameters, + // and Q as the public key. + + // NOTE: verify() takes the signature in X.509 format but JS uses IEEE P1363 format, so we need to convert it + // FIXME: Dont construct an ASN1 object here just to pass it to verify + auto half_size = signature.size() / 2; + auto r = ::Crypto::UnsignedBigInteger::import_data(signature.data(), half_size); + auto s = ::Crypto::UnsignedBigInteger::import_data(signature.data() + half_size, half_size); + + ::Crypto::ASN1::Encoder encoder; + (void)encoder.write_constructed(::Crypto::ASN1::Class::Universal, ::Crypto::ASN1::Kind::Sequence, [&] { + (void)encoder.write(r); + (void)encoder.write(s); + }); + auto encoded_signature = encoder.finish(); + + auto maybe_result = curve.visit( + [](Empty const&) -> ErrorOr { return Error::from_string_view("Failed to create valid crypto instance"sv); }, + [&](auto instance) { return instance.verify(M, Q, encoded_signature); }); + + if (maybe_result.is_error()) { + auto error_message = MUST(FlyString::from_utf8(maybe_result.error().string_literal())); + return WebIDL::OperationError::create(m_realm, error_message); + } + + result = maybe_result.release_value(); + } else { + // FIXME: Otherwise, the namedCurve attribute of the [[algorithm]] internal slot of key is a value specified in an applicable specification: + // FIXME: Perform the ECDSA verification steps specified in that specification passing in M, signature, params and Q and resulting in an indication of whether or not the purported signature is valid. + } + + // 9. Let result be a boolean with the value true if the signature is valid and the value false otherwise. + // 10. Return result. + return JS::Value(result); +} + } diff --git a/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h b/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h index 7819c740383..d298bc8a245 100644 --- a/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h +++ b/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h @@ -167,7 +167,7 @@ public: return WebIDL::NotSupportedError::create(m_realm, "sign is not supported"_fly_string); } - virtual WebIDL::ExceptionOr> verify(AlgorithmParams const&, JS::NonnullGCPtr, ByteBuffer const&, ByteBuffer const&) + virtual WebIDL::ExceptionOr verify(AlgorithmParams const&, JS::NonnullGCPtr, ByteBuffer const&, ByteBuffer const&) { return WebIDL::NotSupportedError::create(m_realm, "verify is not supported"_fly_string); } @@ -251,6 +251,7 @@ private: class ECDSA : public AlgorithmMethods { public: virtual WebIDL::ExceptionOr> sign(AlgorithmParams const&, JS::NonnullGCPtr, ByteBuffer const&) override; + virtual WebIDL::ExceptionOr verify(AlgorithmParams const&, JS::NonnullGCPtr, ByteBuffer const&, ByteBuffer const&) override; virtual WebIDL::ExceptionOr, JS::NonnullGCPtr>> generate_key(AlgorithmParams const&, bool, Vector const&) override; diff --git a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp index b9b5110065e..5421324d20d 100644 --- a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp +++ b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp @@ -641,6 +641,7 @@ SupportedAlgorithmsMap supported_algorithms() // https://w3c.github.io/webcrypto/#ecdsa define_an_algorithm("sign"_string, "ECDSA"_string); + define_an_algorithm("verify"_string, "ECDSA"_string); define_an_algorithm("generateKey"_string, "ECDSA"_string); return internal_object;