LibWeb: Implement import_key for PBKDF2

Co-Authored-By: Andrew Kaster <akaster@serenityos.org>
This commit is contained in:
stelar7 2023-12-15 22:03:04 +01:00 committed by Andrew Kaster
parent 7bc7e2a48f
commit 423e77d5c3
Notes: sideshowbarker 2024-07-17 02:21:14 +09:00
3 changed files with 141 additions and 1 deletions

View file

@ -184,6 +184,88 @@ JS::NonnullGCPtr<JS::Promise> SubtleCrypto::digest(AlgorithmIdentifier const& al
return verify_cast<JS::Promise>(*promise->promise());
}
// https://w3c.github.io/webcrypto/#SubtleCrypto-method-importKey
JS::ThrowCompletionOr<JS::NonnullGCPtr<JS::Promise>> SubtleCrypto::import_key(Bindings::KeyFormat format, KeyDataType key_data, AlgorithmIdentifier algorithm, bool extractable, Vector<Bindings::KeyUsage> 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<ByteBuffer, Bindings::JsonWebKey, Empty> 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<Bindings::JsonWebKey>()) {
return realm.vm().throw_completion<JS::TypeError>(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<JS::Handle<WebIDL::BufferSource>>()->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<Bindings::JsonWebKey>()) {
return realm.vm().throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "JsonWebKey");
}
// 2. Let keyData be the keyData parameter passed to the importKey() method.
real_key_data = key_data.get<Bindings::JsonWebKey>();
}
// 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<JS::Promise>(*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<JS::Promise>(*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<JS::NonnullGCPtr<CryptoKey>> SubtleCrypto::pbkdf2_import_key([[maybe_unused]] Variant<ByteBuffer, Bindings::JsonWebKey, Empty> key_data, [[maybe_unused]] AlgorithmIdentifier algorithm_parameter, Bindings::KeyFormat format, bool extractable, Vector<Bindings::KeyUsage> 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;
}
}

View file

@ -1,5 +1,6 @@
/*
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2023, stelar7 <dudedbz@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -8,9 +9,12 @@
#include <AK/HashMap.h>
#include <AK/String.h>
#include <AK/Vector.h>
#include <LibJS/Forward.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Bindings/SubtleCryptoPrototype.h>
#include <LibWeb/Crypto/CryptoBindings.h>
#include <LibWeb/Crypto/CryptoKey.h>
namespace Web::Crypto {
@ -19,6 +23,7 @@ class SubtleCrypto final : public Bindings::PlatformObject {
JS_DECLARE_ALLOCATOR(SubtleCrypto);
using SupportedAlgorithmsMap = HashMap<String, HashMap<String, String, AK::ASCIICaseInsensitiveStringTraits>>;
using KeyDataType = Variant<JS::Handle<WebIDL::BufferSource>, Bindings::JsonWebKey>;
using AlgorithmIdentifier = Variant<JS::Handle<JS::Object>, String>;
public:
@ -27,6 +32,7 @@ public:
virtual ~SubtleCrypto() override;
JS::NonnullGCPtr<JS::Promise> digest(AlgorithmIdentifier const& algorithm, JS::Handle<WebIDL::BufferSource> const& data);
JS::ThrowCompletionOr<JS::NonnullGCPtr<JS::Promise>> import_key(Bindings::KeyFormat format, KeyDataType keyData, AlgorithmIdentifier algorithm, bool extractable, Vector<Bindings::KeyUsage> keyUsages);
private:
explicit SubtleCrypto(JS::Realm&);
@ -34,6 +40,8 @@ private:
JS::ThrowCompletionOr<Bindings::Algorithm> normalize_an_algorithm(AlgorithmIdentifier const& algorithm, String operation);
WebIDL::ExceptionOr<JS::NonnullGCPtr<CryptoKey>> pbkdf2_import_key(Variant<ByteBuffer, Bindings::JsonWebKey, Empty> key_data, AlgorithmIdentifier algorithm, Bindings::KeyFormat format, bool extractable, Vector<Bindings::KeyUsage> usages);
static SubtleCrypto::SupportedAlgorithmsMap& supported_algorithms_internal();
static SubtleCrypto::SupportedAlgorithmsMap supported_algorithms();
static void define_an_algorithm(String op, String algorithm, String type);

View file

@ -1,3 +1,5 @@
#import <Crypto/CryptoKey.idl>
typedef (object or DOMString) AlgorithmIdentifier;
dictionary Algorithm {
@ -54,7 +56,7 @@ interface SubtleCrypto {
// FIXME: Promise<any> deriveKey(AlgorithmIdentifier algorithm, CryptoKey baseKey, AlgorithmIdentifier derivedKeyType, boolean extractable, sequence<KeyUsage> keyUsages );
// FIXME: Promise<ArrayBuffer> deriveBits(AlgorithmIdentifier algorithm, CryptoKey baseKey, unsigned long length);
// FIXME: Promise<CryptoKey> importKey(KeyFormat format, (BufferSource or JsonWebKey) keyData, AlgorithmIdentifier algorithm, boolean extractable, sequence<KeyUsage> keyUsages);
Promise<CryptoKey> importKey(KeyFormat format, (BufferSource or JsonWebKey) keyData, AlgorithmIdentifier algorithm, boolean extractable, sequence<KeyUsage> keyUsages);
// FIXME: Promise<any> exportKey(KeyFormat format, CryptoKey key);
// FIXME: Promise<any> wrapKey(KeyFormat format, CryptoKey key, CryptoKey wrappingKey, AlgorithmIdentifier wrapAlgorithm);