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.
This commit is contained in:
Andrew Kaster 2024-03-06 16:53:50 -07:00 committed by Andrew Kaster
parent 644e764620
commit 2d59d6c98c
Notes: sideshowbarker 2024-07-16 23:51:07 +09:00
9 changed files with 294 additions and 136 deletions

View File

@ -4,6 +4,8 @@ source_set("Crypto") {
sources = [
"Crypto.cpp",
"Crypto.h",
"CryptoAlgorithms.cpp",
"CryptoAlgorithms.h",
"CryptoBindings.cpp",
"CryptoBindings.h",
"CryptoKey.cpp",

View File

@ -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

View File

@ -0,0 +1,128 @@
/*
* Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCrypto/Hash/HashManager.h>
#include <LibJS/Runtime/ArrayBuffer.h>
#include <LibJS/Runtime/DataView.h>
#include <LibJS/Runtime/TypedArray.h>
#include <LibWeb/Crypto/CryptoAlgorithms.h>
namespace Web::Crypto {
// Out of line to ensure this class has a key function
AlgorithmMethods::~AlgorithmMethods() = default;
JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> 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<NonnullOwnPtr<AlgorithmParams>> 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<WebIDL::BufferSource> salt;
if (!salt_value.is_object() || !(is<JS::TypedArrayBase>(salt_value.as_object()) || is<JS::ArrayBuffer>(salt_value.as_object()) || is<JS::DataView>(salt_value.as_object())))
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "BufferSource");
salt = JS::make_handle(vm.heap().allocate<WebIDL::BufferSource>(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, HashAlgorithmIdentifier> { 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<AlgorithmParams>(*new PBKDF2Params { { name }, salt, iterations, hash.downcast<HashAlgorithmIdentifier>() });
}
WebIDL::ExceptionOr<JS::NonnullGCPtr<CryptoKey>> PBKDF2::import_key(AlgorithmParams const&, Bindings::KeyFormat format, CryptoKey::InternalKeyData key_data, bool extractable, Vector<Bindings::KeyUsage> 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<JS::NonnullGCPtr<JS::ArrayBuffer>> 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());
}
}

View File

@ -0,0 +1,93 @@
/*
* Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/EnumBits.h>
#include <AK/String.h>
#include <LibJS/Forward.h>
#include <LibJS/Heap/GCPtr.h>
#include <LibWeb/Bindings/SubtleCryptoPrototype.h>
#include <LibWeb/Crypto/CryptoBindings.h>
#include <LibWeb/Crypto/CryptoKey.h>
#include <LibWeb/WebIDL/Buffers.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
namespace Web::Crypto {
using KeyDataType = Variant<JS::Handle<WebIDL::BufferSource>, Bindings::JsonWebKey>;
using AlgorithmIdentifier = Variant<JS::Handle<JS::Object>, String>;
using HashAlgorithmIdentifier = AlgorithmIdentifier;
// https://w3c.github.io/webcrypto/#algorithm-overview
struct AlgorithmParams {
String name;
static JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> from_value(JS::VM&, JS::Value);
};
// https://w3c.github.io/webcrypto/#pbkdf2-params
struct PBKDF2Params : public AlgorithmParams {
JS::Handle<WebIDL::BufferSource> salt;
u32 iterations;
HashAlgorithmIdentifier hash;
static JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> from_value(JS::VM&, JS::Value);
};
class AlgorithmMethods {
public:
virtual ~AlgorithmMethods();
virtual WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::ArrayBuffer>> digest(AlgorithmParams const&, ByteBuffer const&)
{
return WebIDL::NotSupportedError::create(m_realm, "digest is not supported"_fly_string);
}
virtual WebIDL::ExceptionOr<JS::NonnullGCPtr<CryptoKey>> import_key(AlgorithmParams const&, Bindings::KeyFormat, CryptoKey::InternalKeyData, bool, Vector<Bindings::KeyUsage> const&)
{
return WebIDL::NotSupportedError::create(m_realm, "importKey is not supported"_fly_string);
}
static NonnullOwnPtr<AlgorithmMethods> 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<JS::NonnullGCPtr<CryptoKey>> import_key(AlgorithmParams const&, Bindings::KeyFormat, CryptoKey::InternalKeyData, bool, Vector<Bindings::KeyUsage> const&) override;
static NonnullOwnPtr<AlgorithmMethods> 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<JS::NonnullGCPtr<JS::ArrayBuffer>> digest(AlgorithmParams const&, ByteBuffer const&) override;
static NonnullOwnPtr<AlgorithmMethods> create(JS::Realm& realm) { return adopt_own(*new SHA(realm)); }
private:
explicit SHA(JS::Realm& realm)
: AlgorithmMethods(realm)
{
}
};
}

View File

@ -6,6 +6,10 @@
#pragma once
#include <AK/Optional.h>
#include <AK/String.h>
#include <AK/Vector.h>
#include <LibJS/Runtime/Object.h>
#include <LibWeb/WebIDL/Buffers.h>
// FIXME: Generate these from IDL
@ -40,11 +44,6 @@ struct JsonWebKey {
Optional<String> 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<WebIDL::BufferSource> salt;
u32 iterations;
Variant<JS::Handle<JS::Object>, String> hash;
};
};

View File

@ -1,28 +1,36 @@
/*
* Copyright (c) 2023, stelar7 <dudedbz@gmail.com>
* Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Memory.h>
#include <LibWeb/Crypto/CryptoKey.h>
namespace Web::Crypto {
JS_DEFINE_ALLOCATOR(CryptoKey);
JS::NonnullGCPtr<CryptoKey> CryptoKey::create(JS::Realm& realm)
JS::NonnullGCPtr<CryptoKey> CryptoKey::create(JS::Realm& realm, InternalKeyData key_data)
{
return realm.heap().allocate<CryptoKey>(realm, realm);
return realm.heap().allocate<CryptoKey>(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<u8*>(&data), sizeof(data)); });
}
void CryptoKey::initialize(JS::Realm& realm)
{

View File

@ -11,6 +11,7 @@
#include <LibWeb/Bindings/CryptoKeyPrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Crypto/CryptoBindings.h>
namespace Web::Crypto {
@ -19,7 +20,9 @@ class CryptoKey final : public Bindings::PlatformObject {
JS_DECLARE_ALLOCATOR(CryptoKey);
public:
[[nodiscard]] static JS::NonnullGCPtr<CryptoKey> create(JS::Realm&);
using InternalKeyData = Variant<ByteBuffer, Bindings::JsonWebKey>;
[[nodiscard]] static JS::NonnullGCPtr<CryptoKey> create(JS::Realm&, InternalKeyData);
virtual ~CryptoKey() override;
@ -34,7 +37,7 @@ public:
void set_usages(JS::NonnullGCPtr<Object> 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<Object> m_algorithm;
JS::NonnullGCPtr<Object> m_usages;
InternalKeyData m_key_data;
};
}

View File

@ -41,17 +41,17 @@ void SubtleCrypto::initialize(JS::Realm& realm)
}
// https://w3c.github.io/webcrypto/#dfn-normalize-an-algorithm
JS::ThrowCompletionOr<Bindings::Algorithm> SubtleCrypto::normalize_an_algorithm(AlgorithmIdentifier const& algorithm, String operation)
WebIDL::ExceptionOr<SubtleCrypto::NormalizedAlgorithmAndParameter> 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<String>()) {
// 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<String>())));
TRY(dictionary->create_data_property("op", JS::PrimitiveString::create(realm.vm(), operation)));
TRY(dictionary->create_data_property("name", JS::PrimitiveString::create(vm, algorithm.get<String>())));
return normalize_an_algorithm(dictionary, operation);
}
@ -65,49 +65,38 @@ JS::ThrowCompletionOr<Bindings::Algorithm> 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<JS::Handle<JS::Object>>();
// 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::TypeError>(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<JS::Handle<JS::Object>>()->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::TypeError>(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<JS::Handle<JS::Object>>()));
auto normalized_algorithm = NormalizedAlgorithmAndParameter { move(methods), move(parameter) };
// 13. Return normalizedAlgorithm.
return normalized_algorithm;
@ -145,36 +134,15 @@ JS::NonnullGCPtr<JS::Promise> 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<JS::Promise>(*promise->promise());
@ -222,19 +190,14 @@ JS::ThrowCompletionOr<JS::NonnullGCPtr<JS::Promise>> 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<CryptoKey::InternalKeyData>(), 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<SHA>("digest"_string, "SHA-1"_string);
define_an_algorithm<SHA>("digest"_string, "SHA-256"_string);
define_an_algorithm<SHA>("digest"_string, "SHA-384"_string);
define_an_algorithm<SHA>("digest"_string, "SHA-512"_string);
// https://w3c.github.io/webcrypto/#pbkdf2
define_an_algorithm("importKey"_string, "PBKDF2"_string, ""_string);
define_an_algorithm<PBKDF2>("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<typename Methods, typename Param>
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<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

@ -13,6 +13,7 @@
#include <LibJS/Forward.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Bindings/SubtleCryptoPrototype.h>
#include <LibWeb/Crypto/CryptoAlgorithms.h>
#include <LibWeb/Crypto/CryptoBindings.h>
#include <LibWeb/Crypto/CryptoKey.h>
@ -22,9 +23,11 @@ class SubtleCrypto final : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(SubtleCrypto, 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>;
struct RegisteredAlgorithm {
NonnullOwnPtr<AlgorithmMethods> (*create_methods)(JS::Realm&) = nullptr;
JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> (*parameter_from_value)(JS::VM&, JS::Value) = nullptr;
};
using SupportedAlgorithmsMap = HashMap<String, HashMap<String, RegisteredAlgorithm, AK::ASCIICaseInsensitiveStringTraits>>;
public:
[[nodiscard]] static JS::NonnullGCPtr<SubtleCrypto> create(JS::Realm&);
@ -32,19 +35,24 @@ 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);
JS::ThrowCompletionOr<JS::NonnullGCPtr<JS::Promise>> import_key(Bindings::KeyFormat format, KeyDataType key_data, AlgorithmIdentifier algorithm, bool extractable, Vector<Bindings::KeyUsage> key_usages);
private:
explicit SubtleCrypto(JS::Realm&);
virtual void initialize(JS::Realm&) override;
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);
struct NormalizedAlgorithmAndParameter {
NonnullOwnPtr<AlgorithmMethods> methods;
NonnullOwnPtr<AlgorithmParams> parameter;
};
WebIDL::ExceptionOr<NormalizedAlgorithmAndParameter> 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<typename Methods, typename Param = AlgorithmParams>
static void define_an_algorithm(String op, String algorithm);
};
}