From 423e77d5c3126ce98f52263ca780ac78d3372ebc Mon Sep 17 00:00:00 2001 From: stelar7 Date: Fri, 15 Dec 2023 22:03:04 +0100 Subject: [PATCH] LibWeb: Implement `import_key` for PBKDF2 Co-Authored-By: Andrew Kaster --- .../Libraries/LibWeb/Crypto/SubtleCrypto.cpp | 130 ++++++++++++++++++ .../Libraries/LibWeb/Crypto/SubtleCrypto.h | 8 ++ .../Libraries/LibWeb/Crypto/SubtleCrypto.idl | 4 +- 3 files changed, 141 insertions(+), 1 deletion(-) diff --git a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp index 193a390a5a..7f726ba0bc 100644 --- a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp +++ b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp @@ -184,6 +184,88 @@ JS::NonnullGCPtr SubtleCrypto::digest(AlgorithmIdentifier const& al return verify_cast(*promise->promise()); } +// https://w3c.github.io/webcrypto/#SubtleCrypto-method-importKey +JS::ThrowCompletionOr> SubtleCrypto::import_key(Bindings::KeyFormat format, KeyDataType key_data, AlgorithmIdentifier algorithm, bool extractable, Vector key_usages) +{ + auto& realm = this->realm(); + + // 1. Let format, algorithm, extractable and usages, be the format, algorithm, extractable + // and key_usages parameters passed to the importKey() method, respectively. + + Variant real_key_data; + // 2. If format is equal to the string "raw", "pkcs8", or "spki": + if (format == Bindings::KeyFormat::Raw || format == Bindings::KeyFormat::Pkcs8 || format == Bindings::KeyFormat::Spki) { + // 1. If the keyData parameter passed to the importKey() method is a JsonWebKey dictionary, throw a TypeError. + if (key_data.has()) { + return realm.vm().throw_completion(JS::ErrorType::NotAnObjectOfType, "BufferSource"); + } + + // 2. Let keyData be the result of getting a copy of the bytes held by the keyData parameter passed to the importKey() method. + real_key_data = MUST(WebIDL::get_buffer_source_copy(*key_data.get>()->raw_object())); + } + + if (format == Bindings::KeyFormat::Jwk) { + // 1. If the keyData parameter passed to the importKey() method is not a JsonWebKey dictionary, throw a TypeError. + if (!key_data.has()) { + return realm.vm().throw_completion(JS::ErrorType::NotAnObjectOfType, "JsonWebKey"); + } + + // 2. Let keyData be the keyData parameter passed to the importKey() method. + real_key_data = key_data.get(); + } + + // NOTE: The spec jumps to 5 here for some reason? + // 5. Let normalizedAlgorithm be the result of normalizing an algorithm, with alg set to algorithm and op set to "importKey". + auto normalized_algorithm = normalize_an_algorithm(algorithm, "importKey"_string); + + // 6. If an error occurred, return a Promise rejected with normalizedAlgorithm. + if (normalized_algorithm.is_error()) { + auto promise = WebIDL::create_rejected_promise(realm, normalized_algorithm.release_error().release_value().value()); + return verify_cast(*promise->promise()); + } + + // 7. Let promise be a new Promise. + 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 { + 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); + 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; + } + auto result = maybe_result.release_value(); + + // 11. If the [[type]] internal slot of result is "secret" or "private" and usages is empty, then throw a SyntaxError. + if ((result->type() == Bindings::KeyType::Secret || result->type() == Bindings::KeyType::Private) && key_usages.is_empty()) { + WebIDL::reject_promise(realm, promise, WebIDL::SyntaxError::create(realm, "usages must not be empty"_fly_string)); + return; + } + + // 12. Set the [[extractable]] internal slot of result to extractable. + result->set_extractable(extractable); + + // 13. Set the [[usages]] internal slot of result to the normalized value of usages. + // FIXME: result->set_usages(key_usages); + + // 14. Resolve promise with result. + WebIDL::resolve_promise(realm, promise, result); + }); + + return verify_cast(*promise->promise()); +} + SubtleCrypto::SupportedAlgorithmsMap& SubtleCrypto::supported_algorithms_internal() { static SubtleCrypto::SupportedAlgorithmsMap s_supported_algorithms; @@ -227,6 +309,11 @@ SubtleCrypto::SupportedAlgorithmsMap SubtleCrypto::supported_algorithms() define_an_algorithm("digest"_string, "SHA-384"_string, ""_string); define_an_algorithm("digest"_string, "SHA-512"_string, ""_string); + // https://w3c.github.io/webcrypto/#pbkdf2 + define_an_algorithm("importKey"_string, "PBKDF2"_string, ""_string); + // FIXME: define_an_algorithm("deriveBits"_string, "PBKDF2"_string, "Pbkdf2Params"_string); + // FIXME: define_an_algorithm("get key length"_string, "PBKDF2"_string, ""_string); + return internal_object; } @@ -245,4 +332,47 @@ void SubtleCrypto::define_an_algorithm(String op, String algorithm, String type) 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 8a65fcbb87..dba5521077 100644 --- a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.h +++ b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2021-2022, Linus Groh + * Copyright (c) 2023, stelar7 * * SPDX-License-Identifier: BSD-2-Clause */ @@ -8,9 +9,12 @@ #include #include +#include #include #include +#include #include +#include namespace Web::Crypto { @@ -19,6 +23,7 @@ class SubtleCrypto final : public Bindings::PlatformObject { JS_DECLARE_ALLOCATOR(SubtleCrypto); using SupportedAlgorithmsMap = HashMap>; + using KeyDataType = Variant, Bindings::JsonWebKey>; using AlgorithmIdentifier = Variant, String>; public: @@ -27,6 +32,7 @@ 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); private: explicit SubtleCrypto(JS::Realm&); @@ -34,6 +40,8 @@ private: 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); + static SubtleCrypto::SupportedAlgorithmsMap& supported_algorithms_internal(); static SubtleCrypto::SupportedAlgorithmsMap supported_algorithms(); static void define_an_algorithm(String op, String algorithm, String type); diff --git a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.idl b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.idl index 600fc27038..8c44761545 100644 --- a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.idl +++ b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.idl @@ -1,3 +1,5 @@ +#import + typedef (object or DOMString) AlgorithmIdentifier; dictionary Algorithm { @@ -54,7 +56,7 @@ interface SubtleCrypto { // FIXME: Promise deriveKey(AlgorithmIdentifier algorithm, CryptoKey baseKey, AlgorithmIdentifier derivedKeyType, boolean extractable, sequence keyUsages ); // FIXME: Promise deriveBits(AlgorithmIdentifier algorithm, CryptoKey baseKey, unsigned long length); - // FIXME: Promise importKey(KeyFormat format, (BufferSource or JsonWebKey) keyData, AlgorithmIdentifier algorithm, boolean extractable, sequence keyUsages); + Promise importKey(KeyFormat format, (BufferSource or JsonWebKey) keyData, AlgorithmIdentifier algorithm, boolean extractable, sequence keyUsages); // FIXME: Promise exportKey(KeyFormat format, CryptoKey key); // FIXME: Promise wrapKey(KeyFormat format, CryptoKey key, CryptoKey wrappingKey, AlgorithmIdentifier wrapAlgorithm);