From e6056ab959ed2670a9f2b05b19f4feba8fc4f5b7 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Tue, 26 Nov 2024 19:22:05 +0100 Subject: [PATCH] LibWeb: Implement ECDH.deriveBits --- Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp | 97 ++++++++++++++++++++ Libraries/LibWeb/Crypto/CryptoAlgorithms.h | 2 +- Libraries/LibWeb/Crypto/SubtleCrypto.cpp | 2 +- 3 files changed, 99 insertions(+), 2 deletions(-) diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index 5b0057ecfb9..822f048b378 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -2643,6 +2643,103 @@ WebIDL::ExceptionOr, GC::Ref>> ECDH::g // 20. Return the result of converting result to an ECMAScript Object, as defined by [WebIDL]. return Variant, GC::Ref> { CryptoKeyPair::create(m_realm, public_key, private_key) }; } + +// https://w3c.github.io/webcrypto/#ecdh-operations +WebIDL::ExceptionOr> ECDH::derive_bits(AlgorithmParams const& params, GC::Ref key, Optional length_optional) +{ + auto& realm = *m_realm; + auto const& normalized_algorithm = static_cast(params); + + // 1. If the [[type]] internal slot of key is not "private", then throw an InvalidAccessError. + if (key->type() != Bindings::KeyType::Private) + return WebIDL::InvalidAccessError::create(realm, "Key is not a private key"_string); + + // 2. Let publicKey be the public member of normalizedAlgorithm. + auto& public_key = normalized_algorithm.public_key; + + // 3. If the [[type]] internal slot of publicKey is not "public", then throw an InvalidAccessError. + if (public_key->type() != Bindings::KeyType::Public) + return WebIDL::InvalidAccessError::create(realm, "Public key is not a public key"_string); + + // 4. If the name attribute of the [[algorithm]] internal slot of publicKey is not equal to + // the name property of the [[algorithm]] internal slot of key, then throw an InvalidAccessError. + auto& internal_algorithm = static_cast(*key->algorithm()); + auto& public_internal_algorithm = static_cast(*public_key->algorithm()); + if (internal_algorithm.name() != public_internal_algorithm.name()) { + return WebIDL::InvalidAccessError::create(realm, "Algorithm mismatch"_string); + } + + // 5. If the namedCurve attribute of the [[algorithm]] internal slot of publicKey is not equal to + // the namedCurve property of the [[algorithm]] internal slot of key, then throw an InvalidAccessError. + if (internal_algorithm.named_curve() != public_internal_algorithm.named_curve()) + return WebIDL::InvalidAccessError::create(realm, "Curve mismatch"_string); + + ByteBuffer secret; + + // 6. If the namedCurve property of the [[algorithm]] internal slot of key is "P-256", "P-384" or "P-521": + // 7. If performing the operation results in an error, then throw a OperationError. + if (internal_algorithm.named_curve().is_one_of("P-256"sv, "P-384"sv, "P-521"sv)) { + // 1. Perform the ECDH primitive specified in [RFC6090] Section 4 + // with key as the EC private key d and the EC public key represented + // by the [[handle]] internal slot of publicKey as the EC public key. + // 2. Let secret be the result of applying the field element to octet string conversion + // defined in Section 6.2 of [RFC6090] to the output of the ECDH primitive. + auto private_key_data = key->handle().get<::Crypto::PK::ECPrivateKey<>>(); + auto public_key_data = public_key->handle().get<::Crypto::PK::ECPublicKey<>>(); + + Variant curve; + if (internal_algorithm.named_curve() == "P-256"sv) { + curve = ::Crypto::Curves::SECP256r1 {}; + } else if (internal_algorithm.named_curve() == "P-384"sv) { + curve = ::Crypto::Curves::SECP384r1 {}; + } else if (internal_algorithm.named_curve() == "P-521"sv) { + // TODO: Support P-521 + return WebIDL::NotSupportedError::create(m_realm, "'P-521' is not supported yet"_string); + } + + auto maybe_secret = curve.visit( + [](Empty const&) -> ErrorOr<::Crypto::Curves::SECPxxxr1Point> { return Error::from_string_literal("noop error"); }, + [&private_key_data, &public_key_data](auto instance) { return instance.compute_coordinate_point(private_key_data.d(), ::Crypto::Curves::SECPxxxr1Point { public_key_data.x(), public_key_data.y() }); }); + + if (maybe_secret.is_error()) { + auto message = TRY_OR_THROW_OOM(realm.vm(), String::formatted("Failed to compute secret: {}", maybe_secret.error())); + return WebIDL::OperationError::create(realm, message); + } + + auto maybe_secret_data = maybe_secret.release_value().to_uncompressed(); + if (maybe_secret_data.is_error()) + return WebIDL::OperationError::create(realm, "Failed to convert secret"_string); + + // NOTE: Remove the prefix from the uncompressed point + auto secret_data = maybe_secret_data.release_value(); + VERIFY(secret_data[0] == 0x04); + + secret = TRY_OR_THROW_OOM(realm.vm(), secret_data.slice(1, secret_data.size() - 1)); + } else { + // If the namedCurve property of the [[algorithm]] internal slot of key is a value specified + // in an applicable specification that specifies the use of that value with ECDH: + // Perform the ECDH derivation steps specified in that specification, + // passing in key and publicKey and resulting in secret. + // TODO: support 'applicable specification' + + return WebIDL::NotSupportedError::create(realm, "Only 'P-256', 'P-384' and 'P-521' is supported"_string); + } + + // 8. If length is null: Return secret + if (!length_optional.has_value()) { + auto result = TRY_OR_THROW_OOM(realm.vm(), ByteBuffer::copy(secret)); + return JS::ArrayBuffer::create(realm, move(secret)); + } + + // Otherwise: If the length of secret in bits is less than length: throw an OperationError. + auto length = length_optional.value(); + if (secret.size() * 8 < length) + return WebIDL::OperationError::create(realm, "Secret is too short"_string); + + // Otherwise: Return an octet string containing the first length bits of secret. + auto slice = TRY_OR_THROW_OOM(realm.vm(), secret.slice(0, length / 8)); + return JS::ArrayBuffer::create(realm, move(slice)); +} // https://wicg.github.io/webcrypto-secure-curves/#ed25519-operations WebIDL::ExceptionOr, GC::Ref>> ED25519::generate_key([[maybe_unused]] AlgorithmParams const& params, bool extractable, Vector const& key_usages) { diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.h b/Libraries/LibWeb/Crypto/CryptoAlgorithms.h index 538c9a556f1..dc005f1ec48 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.h +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.h @@ -496,9 +496,9 @@ private: class ECDH : public AlgorithmMethods { public: virtual WebIDL::ExceptionOr, GC::Ref>> generate_key(AlgorithmParams const&, bool, Vector const&) override; - // TODO: virtual WebIDL::ExceptionOr> derive_bits(AlgorithmParams const&, GC::Ref, Optional) override; // TODO: virtual WebIDL::ExceptionOr> import_key(AlgorithmParams const&, Bindings::KeyFormat, CryptoKey::InternalKeyData, bool, Vector const&) override; // TODO: virtual WebIDL::ExceptionOr> export_key(Bindings::KeyFormat, GC::Ref) override; + virtual WebIDL::ExceptionOr> derive_bits(AlgorithmParams const&, GC::Ref, Optional) override; static NonnullOwnPtr create(JS::Realm& realm) { return adopt_own(*new ECDH(realm)); } diff --git a/Libraries/LibWeb/Crypto/SubtleCrypto.cpp b/Libraries/LibWeb/Crypto/SubtleCrypto.cpp index 26cfb882db2..30f784cccaa 100644 --- a/Libraries/LibWeb/Crypto/SubtleCrypto.cpp +++ b/Libraries/LibWeb/Crypto/SubtleCrypto.cpp @@ -805,9 +805,9 @@ SupportedAlgorithmsMap const& supported_algorithms() // FIXME: define_an_algorithm("exportKey"_string, "ECDSA"_string); // https://w3c.github.io/webcrypto/#ecdh-registration - // FIXME: define_an_algorithm("deriveBits"_string, "ECDH"_string); // FIXME: define_an_algorithm("importKey"_string, "ECDH"_string); // FIXME: define_an_algorithm("exportKey"_string, "ECDH"_string); + define_an_algorithm("deriveBits"_string, "ECDH"_string); define_an_algorithm("generateKey"_string, "ECDH"_string); // https://w3c.github.io/webcrypto/#aes-ctr-registration