From 2d59d6c98cb132d2749a3d537a36327ace05062d Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Wed, 6 Mar 2024 16:53:50 -0700 Subject: [PATCH] LibWeb: Refactor SubtleCrypto to allow adding more algorithms easier This patch throws away some of the spec suggestions for how to implement the normalize_algorithm AO and uses a new pattern that we can actually extend in our C++. Also update CryptoKey to store the key data. --- .../Userland/Libraries/LibWeb/Crypto/BUILD.gn | 2 + Userland/Libraries/LibWeb/CMakeLists.txt | 1 + .../LibWeb/Crypto/CryptoAlgorithms.cpp | 128 ++++++++++++++++ .../LibWeb/Crypto/CryptoAlgorithms.h | 93 ++++++++++++ .../Libraries/LibWeb/Crypto/CryptoBindings.h | 16 +- .../Libraries/LibWeb/Crypto/CryptoKey.cpp | 16 +- Userland/Libraries/LibWeb/Crypto/CryptoKey.h | 9 +- .../Libraries/LibWeb/Crypto/SubtleCrypto.cpp | 141 ++++-------------- .../Libraries/LibWeb/Crypto/SubtleCrypto.h | 24 ++- 9 files changed, 294 insertions(+), 136 deletions(-) create mode 100644 Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp create mode 100644 Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h diff --git a/Meta/gn/secondary/Userland/Libraries/LibWeb/Crypto/BUILD.gn b/Meta/gn/secondary/Userland/Libraries/LibWeb/Crypto/BUILD.gn index e20f1f060f..44dac6f956 100644 --- a/Meta/gn/secondary/Userland/Libraries/LibWeb/Crypto/BUILD.gn +++ b/Meta/gn/secondary/Userland/Libraries/LibWeb/Crypto/BUILD.gn @@ -4,6 +4,8 @@ source_set("Crypto") { sources = [ "Crypto.cpp", "Crypto.h", + "CryptoAlgorithms.cpp", + "CryptoAlgorithms.h", "CryptoBindings.cpp", "CryptoBindings.h", "CryptoKey.cpp", diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index f827a0b866..c39a2adff6 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -25,6 +25,7 @@ set(SOURCES Bindings/PlatformObject.cpp Clipboard/Clipboard.cpp Crypto/Crypto.cpp + Crypto/CryptoAlgorithms.cpp Crypto/CryptoBindings.cpp Crypto/CryptoKey.cpp Crypto/SubtleCrypto.cpp diff --git a/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp new file mode 100644 index 0000000000..d5f0a4de36 --- /dev/null +++ b/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2024, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +namespace Web::Crypto { + +// Out of line to ensure this class has a key function +AlgorithmMethods::~AlgorithmMethods() = default; + +JS::ThrowCompletionOr> AlgorithmParams::from_value(JS::VM& vm, JS::Value value) +{ + auto& object = value.as_object(); + + auto name = TRY(object.get("name")); + auto name_string = TRY(name.to_string(vm)); + + return adopt_own(*new AlgorithmParams { .name = name_string }); +} + +JS::ThrowCompletionOr> PBKDF2Params::from_value(JS::VM& vm, JS::Value value) +{ + auto& realm = *vm.current_realm(); + auto& object = value.as_object(); + + auto name_value = TRY(object.get("name")); + auto name = TRY(name_value.to_string(vm)); + + auto salt_value = TRY(object.get("salt")); + JS::Handle salt; + + if (!salt_value.is_object() || !(is(salt_value.as_object()) || is(salt_value.as_object()) || is(salt_value.as_object()))) + return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "BufferSource"); + + salt = JS::make_handle(vm.heap().allocate(realm, salt_value.as_object())); + + auto iterations_value = TRY(object.get("iterations")); + auto iterations = TRY(iterations_value.to_u32(vm)); + + auto hash_value = TRY(object.get("hash")); + auto hash = Variant { Empty {} }; + if (hash_value.is_string()) { + auto hash_string = TRY(hash_value.to_string(vm)); + hash = HashAlgorithmIdentifier { hash_string }; + } else { + auto hash_object = TRY(hash_value.to_object(vm)); + hash = HashAlgorithmIdentifier { hash_object }; + } + + return adopt_own(*new PBKDF2Params { { name }, salt, iterations, hash.downcast() }); +} + +WebIDL::ExceptionOr> PBKDF2::import_key(AlgorithmParams const&, Bindings::KeyFormat format, CryptoKey::InternalKeyData key_data, bool extractable, Vector const& key_usages) +{ + // 1. If format is not "raw", throw a NotSupportedError + if (format != Bindings::KeyFormat::Raw) { + return WebIDL::NotSupportedError::create(m_realm, "Only raw format is supported"_fly_string); + } + + // 2. If usages contains a value that is not "deriveKey" or "deriveBits", then throw a SyntaxError. + for (auto& usage : key_usages) { + if (usage != Bindings::KeyUsage::Derivekey && usage != Bindings::KeyUsage::Derivebits) { + return WebIDL::SyntaxError::create(m_realm, MUST(String::formatted("Invalid key usage '{}'", idl_enum_to_string(usage)))); + } + } + + // 3. If extractable is not false, then throw a SyntaxError. + if (extractable) + return WebIDL::SyntaxError::create(m_realm, "extractable must be false"_fly_string); + + // 4. Let key be a new CryptoKey representing keyData. + auto key = CryptoKey::create(m_realm, move(key_data)); + + // 5. Set the [[type]] internal slot of key to "secret". + key->set_type(Bindings::KeyType::Secret); + + // 6. Set the [[extractable]] internal slot of key to false. + key->set_extractable(false); + + // 7. Let algorithm be a new KeyAlgorithm object. + auto algorithm = Bindings::KeyAlgorithm::create(m_realm); + + // 8. Set the name attribute of algorithm to "PBKDF2". + algorithm->set_name("PBKDF2"_string); + + // 9. Set the [[algorithm]] internal slot of key to algorithm. + key->set_algorithm(algorithm); + + // 10. Return key. + return key; +} + +WebIDL::ExceptionOr> SHA::digest(AlgorithmParams const& algorithm, ByteBuffer const& data) +{ + auto& algorithm_name = algorithm.name; + + ::Crypto::Hash::HashKind hash_kind; + if (algorithm_name.equals_ignoring_ascii_case("SHA-1"sv)) { + hash_kind = ::Crypto::Hash::HashKind::SHA1; + } else if (algorithm_name.equals_ignoring_ascii_case("SHA-256"sv)) { + hash_kind = ::Crypto::Hash::HashKind::SHA256; + } else if (algorithm_name.equals_ignoring_ascii_case("SHA-384"sv)) { + hash_kind = ::Crypto::Hash::HashKind::SHA384; + } else if (algorithm_name.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 '{}'", algorithm_name))); + } + + ::Crypto::Hash::Manager hash { hash_kind }; + hash.update(data); + + 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); + + return JS::ArrayBuffer::create(m_realm, result_buffer.release_value()); +} + +} diff --git a/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h b/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h new file mode 100644 index 0000000000..adec48ffa9 --- /dev/null +++ b/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2024, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Web::Crypto { + +using KeyDataType = Variant, Bindings::JsonWebKey>; +using AlgorithmIdentifier = Variant, String>; +using HashAlgorithmIdentifier = AlgorithmIdentifier; + +// https://w3c.github.io/webcrypto/#algorithm-overview + +struct AlgorithmParams { + String name; + + static JS::ThrowCompletionOr> from_value(JS::VM&, JS::Value); +}; + +// https://w3c.github.io/webcrypto/#pbkdf2-params +struct PBKDF2Params : public AlgorithmParams { + JS::Handle salt; + u32 iterations; + HashAlgorithmIdentifier hash; + + static JS::ThrowCompletionOr> from_value(JS::VM&, JS::Value); +}; + +class AlgorithmMethods { +public: + virtual ~AlgorithmMethods(); + + virtual WebIDL::ExceptionOr> digest(AlgorithmParams const&, ByteBuffer const&) + { + return WebIDL::NotSupportedError::create(m_realm, "digest is not supported"_fly_string); + } + + virtual WebIDL::ExceptionOr> import_key(AlgorithmParams const&, Bindings::KeyFormat, CryptoKey::InternalKeyData, bool, Vector const&) + { + return WebIDL::NotSupportedError::create(m_realm, "importKey is not supported"_fly_string); + } + + static NonnullOwnPtr create(JS::Realm& realm) { return adopt_own(*new AlgorithmMethods(realm)); } + +protected: + explicit AlgorithmMethods(JS::Realm& realm) + : m_realm(realm) + { + } + + JS::Realm& m_realm; +}; + +class PBKDF2 : public AlgorithmMethods { +public: + virtual WebIDL::ExceptionOr> import_key(AlgorithmParams const&, Bindings::KeyFormat, CryptoKey::InternalKeyData, bool, Vector const&) override; + + static NonnullOwnPtr create(JS::Realm& realm) { return adopt_own(*new PBKDF2(realm)); } + +private: + explicit PBKDF2(JS::Realm& realm) + : AlgorithmMethods(realm) + { + } +}; + +class SHA : public AlgorithmMethods { +public: + virtual WebIDL::ExceptionOr> digest(AlgorithmParams const&, ByteBuffer const&) override; + + static NonnullOwnPtr create(JS::Realm& realm) { return adopt_own(*new SHA(realm)); } + +private: + explicit SHA(JS::Realm& realm) + : AlgorithmMethods(realm) + { + } +}; + +} diff --git a/Userland/Libraries/LibWeb/Crypto/CryptoBindings.h b/Userland/Libraries/LibWeb/Crypto/CryptoBindings.h index aed983e01c..edf766c6f0 100644 --- a/Userland/Libraries/LibWeb/Crypto/CryptoBindings.h +++ b/Userland/Libraries/LibWeb/Crypto/CryptoBindings.h @@ -6,6 +6,10 @@ #pragma once +#include +#include +#include +#include #include // FIXME: Generate these from IDL @@ -40,11 +44,6 @@ struct JsonWebKey { Optional k; }; -// https://w3c.github.io/webcrypto/#dfn-Algorithm -struct Algorithm { - String name; -}; - // https://w3c.github.io/webcrypto/#key-algorithm-dictionary class KeyAlgorithm : public JS::Object { JS_OBJECT(KeyAlgorithm, Object); @@ -66,11 +65,4 @@ private: String m_name; }; -// https://w3c.github.io/webcrypto/#pbkdf2-params -struct Pbkdf2Params { - JS::Handle salt; - u32 iterations; - Variant, String> hash; -}; - }; diff --git a/Userland/Libraries/LibWeb/Crypto/CryptoKey.cpp b/Userland/Libraries/LibWeb/Crypto/CryptoKey.cpp index 516346ec86..8431e4cedc 100644 --- a/Userland/Libraries/LibWeb/Crypto/CryptoKey.cpp +++ b/Userland/Libraries/LibWeb/Crypto/CryptoKey.cpp @@ -1,28 +1,36 @@ /* * Copyright (c) 2023, stelar7 + * Copyright (c) 2024, Andrew Kaster * * SPDX-License-Identifier: BSD-2-Clause */ +#include #include namespace Web::Crypto { JS_DEFINE_ALLOCATOR(CryptoKey); -JS::NonnullGCPtr CryptoKey::create(JS::Realm& realm) +JS::NonnullGCPtr CryptoKey::create(JS::Realm& realm, InternalKeyData key_data) { - return realm.heap().allocate(realm, realm); + return realm.heap().allocate(realm, realm, move(key_data)); } -CryptoKey::CryptoKey(JS::Realm& realm) +CryptoKey::CryptoKey(JS::Realm& realm, InternalKeyData key_data) : PlatformObject(realm) , m_algorithm(Object::create(realm, nullptr)) , m_usages(Object::create(realm, nullptr)) + , m_key_data(move(key_data)) { } -CryptoKey::~CryptoKey() = default; +CryptoKey::~CryptoKey() +{ + m_key_data.visit( + [](ByteBuffer& data) { secure_zero(data.data(), data.size()); }, + [](auto& data) { secure_zero(reinterpret_cast(&data), sizeof(data)); }); +} void CryptoKey::initialize(JS::Realm& realm) { diff --git a/Userland/Libraries/LibWeb/Crypto/CryptoKey.h b/Userland/Libraries/LibWeb/Crypto/CryptoKey.h index a36af57511..ba5ad753a2 100644 --- a/Userland/Libraries/LibWeb/Crypto/CryptoKey.h +++ b/Userland/Libraries/LibWeb/Crypto/CryptoKey.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace Web::Crypto { @@ -19,7 +20,9 @@ class CryptoKey final : public Bindings::PlatformObject { JS_DECLARE_ALLOCATOR(CryptoKey); public: - [[nodiscard]] static JS::NonnullGCPtr create(JS::Realm&); + using InternalKeyData = Variant; + + [[nodiscard]] static JS::NonnullGCPtr create(JS::Realm&, InternalKeyData); virtual ~CryptoKey() override; @@ -34,7 +37,7 @@ public: void set_usages(JS::NonnullGCPtr usages) { m_usages = move(usages); } private: - explicit CryptoKey(JS::Realm&); + CryptoKey(JS::Realm&, InternalKeyData); virtual void initialize(JS::Realm&) override; virtual void visit_edges(Visitor&) override; @@ -42,6 +45,8 @@ private: bool m_extractable { false }; JS::NonnullGCPtr m_algorithm; JS::NonnullGCPtr m_usages; + + InternalKeyData m_key_data; }; } diff --git a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp index fab1678be1..c1a67d2e0a 100644 --- a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp +++ b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp @@ -41,17 +41,17 @@ void SubtleCrypto::initialize(JS::Realm& realm) } // https://w3c.github.io/webcrypto/#dfn-normalize-an-algorithm -JS::ThrowCompletionOr SubtleCrypto::normalize_an_algorithm(AlgorithmIdentifier const& algorithm, String operation) +WebIDL::ExceptionOr SubtleCrypto::normalize_an_algorithm(AlgorithmIdentifier const& algorithm, String operation) { auto& realm = this->realm(); + auto& vm = this->vm(); // If alg is an instance of a DOMString: if (algorithm.has()) { // Return the result of running the normalize an algorithm algorithm, // with the alg set to a new Algorithm dictionary whose name attribute is alg, and with the op set to op. auto dictionary = JS::make_handle(JS::Object::create(realm, realm.intrinsics().object_prototype())); - TRY(dictionary->create_data_property("name", JS::PrimitiveString::create(realm.vm(), algorithm.get()))); - TRY(dictionary->create_data_property("op", JS::PrimitiveString::create(realm.vm(), operation))); + TRY(dictionary->create_data_property("name", JS::PrimitiveString::create(vm, algorithm.get()))); return normalize_an_algorithm(dictionary, operation); } @@ -65,49 +65,38 @@ JS::ThrowCompletionOr SubtleCrypto::normalize_an_algorithm( // 2. Let initialAlg be the result of converting the ECMAScript object represented by alg to // the IDL dictionary type Algorithm, as defined by [WebIDL]. - // FIXME: How do we turn this into an "Algorithm" in a nice way? - // NOTE: For now, we just use the object as-is. - auto initial_algorithm = algorithm.get>(); - // 3. If an error occurred, return the error and terminate this algorithm. - auto has_name = TRY(initial_algorithm->has_property("name")); - if (!has_name) { - return realm.vm().throw_completion(JS::ErrorType::NotAnObjectOfType, "Algorithm"); - } + // Note: We're not going to bother creating an Algorithm object, all we want is the name attribute so that we can + // fetch the actual algorithm factory from the registeredAlgorithms map. + auto initial_algorithm = TRY(algorithm.get>()->get("name")); // 4. Let algName be the value of the name attribute of initialAlg. - auto algorithm_name = TRY(TRY(initial_algorithm->get("name")).to_string(realm.vm())); + auto algorithm_name = TRY(initial_algorithm.to_string(vm)); - String desired_type; + RegisteredAlgorithm desired_type; // 5. If registeredAlgorithms contains a key that is a case-insensitive string match for algName: - if (registered_algorithms.contains(algorithm_name)) { + if (auto it = registered_algorithms.find(algorithm_name); it != registered_algorithms.end()) { // 1. Set algName to the value of the matching key. - auto it = registered_algorithms.find(algorithm_name); - algorithm_name = (*it).key; - // 2. Let desiredType be the IDL dictionary type stored at algName in registeredAlgorithms. - desired_type = (*it).value; + desired_type = it->value; } else { // Otherwise: // Return a new NotSupportedError and terminate this algorithm. - // FIXME: This should be a DOMException - return realm.vm().throw_completion(JS::ErrorType::NotImplemented, algorithm_name); + return WebIDL::NotSupportedError::create(realm, MUST(String::formatted("Algorithm '{}' is not supported", algorithm_name))); } // 8. Let normalizedAlgorithm be the result of converting the ECMAScript object represented by alg // to the IDL dictionary type desiredType, as defined by [WebIDL]. - // FIXME: Should IDL generate a struct for each of these? - Bindings::Algorithm normalized_algorithm; - // 9. Set the name attribute of normalizedAlgorithm to algName. - normalized_algorithm.name = algorithm_name; - // 10. If an error occurred, return the error and terminate this algorithm. - - // FIXME: 11. Let dictionaries be a list consisting of the IDL dictionary type desiredType + // 11. Let dictionaries be a list consisting of the IDL dictionary type desiredType // and all of desiredType's inherited dictionaries, in order from least to most derived. - // FIXME: 12. For each dictionary dictionary in dictionaries: + // 12. For each dictionary dictionary in dictionaries: + // Note: All of these steps are handled by the create_methods and parameter_from_value methods. + auto methods = desired_type.create_methods(realm); + auto parameter = TRY(desired_type.parameter_from_value(vm, algorithm.get>())); + auto normalized_algorithm = NormalizedAlgorithmAndParameter { move(methods), move(parameter) }; // 13. Return normalizedAlgorithm. return normalized_algorithm; @@ -145,36 +134,15 @@ JS::NonnullGCPtr SubtleCrypto::digest(AlgorithmIdentifier const& al // FIXME: Need spec reference to https://webidl.spec.whatwg.org/#reject // 8. Let result be the result of performing the digest operation specified by normalizedAlgorithm using algorithm, with data as message. - auto algorithm_name = algorithm_object.name; + auto result = algorithm_object.methods->digest(*algorithm_object.parameter, data_buffer); - ::Crypto::Hash::HashKind hash_kind; - if (algorithm_name.equals_ignoring_ascii_case("SHA-1"sv)) { - hash_kind = ::Crypto::Hash::HashKind::SHA1; - } else if (algorithm_name.equals_ignoring_ascii_case("SHA-256"sv)) { - hash_kind = ::Crypto::Hash::HashKind::SHA256; - } else if (algorithm_name.equals_ignoring_ascii_case("SHA-384"sv)) { - hash_kind = ::Crypto::Hash::HashKind::SHA384; - } else if (algorithm_name.equals_ignoring_ascii_case("SHA-512"sv)) { - hash_kind = ::Crypto::Hash::HashKind::SHA512; - } else { - WebIDL::reject_promise(realm, promise, WebIDL::NotSupportedError::create(realm, MUST(String::formatted("Invalid hash function '{}'", algorithm_name)))); + if (result.is_exception()) { + WebIDL::reject_promise(realm, promise, Bindings::dom_exception_to_throw_completion(realm.vm(), result.release_error()).release_value().value()); return; } - ::Crypto::Hash::Manager hash { hash_kind }; - hash.update(data_buffer); - - auto digest = hash.digest(); - auto result_buffer = ByteBuffer::copy(digest.immutable_data(), hash.digest_size()); - if (result_buffer.is_error()) { - WebIDL::reject_promise(realm, promise, WebIDL::OperationError::create(realm, "Failed to create result buffer"_fly_string)); - return; - } - - auto result = JS::ArrayBuffer::create(realm, result_buffer.release_value()); - // 9. Resolve promise with result. - WebIDL::resolve_promise(realm, promise, result); + WebIDL::resolve_promise(realm, promise, result.release_value()); }); return verify_cast(*promise->promise()); @@ -222,19 +190,14 @@ JS::ThrowCompletionOr> SubtleCrypto::import_key(Bi auto promise = WebIDL::create_promise(realm); // 8. Return promise and perform the remaining steps in parallel. - Platform::EventLoopPlugin::the().deferred_invoke([&realm, this, real_key_data = move(real_key_data), normalized_algorithm = normalized_algorithm.release_value(), promise, format, extractable, key_usages = move(key_usages), algorithm = move(algorithm)]() -> void { + Platform::EventLoopPlugin::the().deferred_invoke([&realm, real_key_data = move(real_key_data), normalized_algorithm = normalized_algorithm.release_value(), promise, format, extractable, key_usages = move(key_usages), algorithm = move(algorithm)]() -> void { HTML::TemporaryExecutionContext context(Bindings::host_defined_environment_settings_object(realm), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes); // 9. If the following steps or referenced procedures say to throw an error, reject promise with the returned error and then terminate the algorithm. // 10. Let result be the CryptoKey object that results from performing the import key operation // specified by normalizedAlgorithm using keyData, algorithm, format, extractable and usages. - if (normalized_algorithm.name != "PBKDF2"sv) { - WebIDL::reject_promise(realm, promise, WebIDL::NotSupportedError::create(realm, MUST(String::formatted("Invalid algorithm '{}'", normalized_algorithm.name)))); - return; - } - - auto maybe_result = pbkdf2_import_key(real_key_data, algorithm, format, extractable, key_usages); + auto maybe_result = normalized_algorithm.methods->import_key(*normalized_algorithm.parameter, format, real_key_data.downcast(), extractable, key_usages); if (maybe_result.is_error()) { WebIDL::reject_promise(realm, promise, Bindings::dom_exception_to_throw_completion(realm.vm(), maybe_result.release_error()).release_value().value()); return; @@ -298,13 +261,13 @@ SubtleCrypto::SupportedAlgorithmsMap SubtleCrypto::supported_algorithms() // https://w3c.github.io/webcrypto/#algorithm-conventions // https://w3c.github.io/webcrypto/#sha - define_an_algorithm("digest"_string, "SHA-1"_string, ""_string); - define_an_algorithm("digest"_string, "SHA-256"_string, ""_string); - define_an_algorithm("digest"_string, "SHA-384"_string, ""_string); - define_an_algorithm("digest"_string, "SHA-512"_string, ""_string); + define_an_algorithm("digest"_string, "SHA-1"_string); + define_an_algorithm("digest"_string, "SHA-256"_string); + define_an_algorithm("digest"_string, "SHA-384"_string); + define_an_algorithm("digest"_string, "SHA-512"_string); // https://w3c.github.io/webcrypto/#pbkdf2 - define_an_algorithm("importKey"_string, "PBKDF2"_string, ""_string); + define_an_algorithm("importKey"_string, "PBKDF2"_string); // FIXME: define_an_algorithm("deriveBits"_string, "PBKDF2"_string, "Pbkdf2Params"_string); // FIXME: define_an_algorithm("get key length"_string, "PBKDF2"_string, ""_string); @@ -312,7 +275,8 @@ SubtleCrypto::SupportedAlgorithmsMap SubtleCrypto::supported_algorithms() } // https://w3c.github.io/webcrypto/#concept-define-an-algorithm -void SubtleCrypto::define_an_algorithm(String op, String algorithm, String type) +template +void SubtleCrypto::define_an_algorithm(AK::String op, AK::String algorithm) { auto& internal_object = supported_algorithms_internal(); @@ -322,51 +286,8 @@ void SubtleCrypto::define_an_algorithm(String op, String algorithm, String type) auto registered_algorithms = maybe_registered_algorithms.value(); // 2. Set the alg key of registeredAlgorithms to the IDL dictionary type type. - registered_algorithms.set(algorithm, type); + registered_algorithms.set(algorithm, RegisteredAlgorithm { &Methods::create, &Param::from_value }); internal_object.set(op, registered_algorithms); } -// https://w3c.github.io/webcrypto/#pbkdf2-operations -WebIDL::ExceptionOr> SubtleCrypto::pbkdf2_import_key([[maybe_unused]] Variant key_data, [[maybe_unused]] AlgorithmIdentifier algorithm_parameter, Bindings::KeyFormat format, bool extractable, Vector key_usages) -{ - auto& realm = this->realm(); - - // 1. If format is not "raw", throw a NotSupportedError - if (format != Bindings::KeyFormat::Raw) { - return WebIDL::NotSupportedError::create(realm, "Only raw format is supported"_fly_string); - } - - // 2. If usages contains a value that is not "deriveKey" or "deriveBits", then throw a SyntaxError. - for (auto& usage : key_usages) { - if (usage != Bindings::KeyUsage::Derivekey && usage != Bindings::KeyUsage::Derivebits) { - return WebIDL::SyntaxError::create(realm, MUST(String::formatted("Invalid key usage '{}'", idl_enum_to_string(usage)))); - } - } - - // 3. If extractable is not false, then throw a SyntaxError. - if (extractable) - return WebIDL::SyntaxError::create(realm, "extractable must be false"_fly_string); - - // 4. Let key be a new CryptoKey representing keyData. - auto key = CryptoKey::create(realm); - - // 5. Set the [[type]] internal slot of key to "secret". - key->set_type(Bindings::KeyType::Secret); - - // 6. Set the [[extractable]] internal slot of key to false. - key->set_extractable(false); - - // 7. Let algorithm be a new KeyAlgorithm object. - auto algorithm = Bindings::KeyAlgorithm::create(realm); - - // 8. Set the name attribute of algorithm to "PBKDF2". - algorithm->set_name("PBKDF2"_string); - - // 9. Set the [[algorithm]] internal slot of key to algorithm. - key->set_algorithm(algorithm); - - // 10. Return key. - return key; -} - } diff --git a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.h b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.h index dba5521077..dabf8bcb4c 100644 --- a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.h +++ b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -22,9 +23,11 @@ class SubtleCrypto final : public Bindings::PlatformObject { WEB_PLATFORM_OBJECT(SubtleCrypto, Bindings::PlatformObject); JS_DECLARE_ALLOCATOR(SubtleCrypto); - using SupportedAlgorithmsMap = HashMap>; - using KeyDataType = Variant, Bindings::JsonWebKey>; - using AlgorithmIdentifier = Variant, String>; + struct RegisteredAlgorithm { + NonnullOwnPtr (*create_methods)(JS::Realm&) = nullptr; + JS::ThrowCompletionOr> (*parameter_from_value)(JS::VM&, JS::Value) = nullptr; + }; + using SupportedAlgorithmsMap = HashMap>; public: [[nodiscard]] static JS::NonnullGCPtr create(JS::Realm&); @@ -32,19 +35,24 @@ public: virtual ~SubtleCrypto() override; JS::NonnullGCPtr digest(AlgorithmIdentifier const& algorithm, JS::Handle const& data); - JS::ThrowCompletionOr> import_key(Bindings::KeyFormat format, KeyDataType keyData, AlgorithmIdentifier algorithm, bool extractable, Vector keyUsages); + + JS::ThrowCompletionOr> import_key(Bindings::KeyFormat format, KeyDataType key_data, AlgorithmIdentifier algorithm, bool extractable, Vector key_usages); private: explicit SubtleCrypto(JS::Realm&); virtual void initialize(JS::Realm&) override; - JS::ThrowCompletionOr normalize_an_algorithm(AlgorithmIdentifier const& algorithm, String operation); - - WebIDL::ExceptionOr> pbkdf2_import_key(Variant key_data, AlgorithmIdentifier algorithm, Bindings::KeyFormat format, bool extractable, Vector usages); + struct NormalizedAlgorithmAndParameter { + NonnullOwnPtr methods; + NonnullOwnPtr parameter; + }; + WebIDL::ExceptionOr normalize_an_algorithm(AlgorithmIdentifier const& algorithm, String operation); static SubtleCrypto::SupportedAlgorithmsMap& supported_algorithms_internal(); static SubtleCrypto::SupportedAlgorithmsMap supported_algorithms(); - static void define_an_algorithm(String op, String algorithm, String type); + + template + static void define_an_algorithm(String op, String algorithm); }; }