فهرست منبع

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.
Andrew Kaster 1 سال پیش
والد
کامیت
2d59d6c98c

+ 2 - 0
Meta/gn/secondary/Userland/Libraries/LibWeb/Crypto/BUILD.gn

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

+ 1 - 0
Userland/Libraries/LibWeb/CMakeLists.txt

@@ -25,6 +25,7 @@ set(SOURCES
     Bindings/PlatformObject.cpp
     Bindings/PlatformObject.cpp
     Clipboard/Clipboard.cpp
     Clipboard/Clipboard.cpp
     Crypto/Crypto.cpp
     Crypto/Crypto.cpp
+    Crypto/CryptoAlgorithms.cpp
     Crypto/CryptoBindings.cpp
     Crypto/CryptoBindings.cpp
     Crypto/CryptoKey.cpp
     Crypto/CryptoKey.cpp
     Crypto/SubtleCrypto.cpp
     Crypto/SubtleCrypto.cpp

+ 128 - 0
Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp

@@ -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());
+}
+
+}

+ 93 - 0
Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h

@@ -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)
+    {
+    }
+};
+
+}

+ 4 - 12
Userland/Libraries/LibWeb/Crypto/CryptoBindings.h

@@ -6,6 +6,10 @@
 
 
 #pragma once
 #pragma once
 
 
+#include <AK/Optional.h>
+#include <AK/String.h>
+#include <AK/Vector.h>
+#include <LibJS/Runtime/Object.h>
 #include <LibWeb/WebIDL/Buffers.h>
 #include <LibWeb/WebIDL/Buffers.h>
 
 
 // FIXME: Generate these from IDL
 // FIXME: Generate these from IDL
@@ -40,11 +44,6 @@ struct JsonWebKey {
     Optional<String> k;
     Optional<String> k;
 };
 };
 
 
-// https://w3c.github.io/webcrypto/#dfn-Algorithm
-struct Algorithm {
-    String name;
-};
-
 // https://w3c.github.io/webcrypto/#key-algorithm-dictionary
 // https://w3c.github.io/webcrypto/#key-algorithm-dictionary
 class KeyAlgorithm : public JS::Object {
 class KeyAlgorithm : public JS::Object {
     JS_OBJECT(KeyAlgorithm, Object);
     JS_OBJECT(KeyAlgorithm, Object);
@@ -66,11 +65,4 @@ private:
     String m_name;
     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;
-};
-
 };
 };

+ 12 - 4
Userland/Libraries/LibWeb/Crypto/CryptoKey.cpp

@@ -1,28 +1,36 @@
 /*
 /*
  * Copyright (c) 2023, stelar7 <dudedbz@gmail.com>
  * Copyright (c) 2023, stelar7 <dudedbz@gmail.com>
+ * Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
  *
  *
  * SPDX-License-Identifier: BSD-2-Clause
  * SPDX-License-Identifier: BSD-2-Clause
  */
  */
 
 
+#include <AK/Memory.h>
 #include <LibWeb/Crypto/CryptoKey.h>
 #include <LibWeb/Crypto/CryptoKey.h>
 
 
 namespace Web::Crypto {
 namespace Web::Crypto {
 
 
 JS_DEFINE_ALLOCATOR(CryptoKey);
 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)
     : PlatformObject(realm)
     , m_algorithm(Object::create(realm, nullptr))
     , m_algorithm(Object::create(realm, nullptr))
     , m_usages(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)
 void CryptoKey::initialize(JS::Realm& realm)
 {
 {

+ 7 - 2
Userland/Libraries/LibWeb/Crypto/CryptoKey.h

@@ -11,6 +11,7 @@
 #include <LibWeb/Bindings/CryptoKeyPrototype.h>
 #include <LibWeb/Bindings/CryptoKeyPrototype.h>
 #include <LibWeb/Bindings/Intrinsics.h>
 #include <LibWeb/Bindings/Intrinsics.h>
 #include <LibWeb/Bindings/PlatformObject.h>
 #include <LibWeb/Bindings/PlatformObject.h>
+#include <LibWeb/Crypto/CryptoBindings.h>
 
 
 namespace Web::Crypto {
 namespace Web::Crypto {
 
 
@@ -19,7 +20,9 @@ class CryptoKey final : public Bindings::PlatformObject {
     JS_DECLARE_ALLOCATOR(CryptoKey);
     JS_DECLARE_ALLOCATOR(CryptoKey);
 
 
 public:
 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;
     virtual ~CryptoKey() override;
 
 
@@ -34,7 +37,7 @@ public:
     void set_usages(JS::NonnullGCPtr<Object> usages) { m_usages = move(usages); }
     void set_usages(JS::NonnullGCPtr<Object> usages) { m_usages = move(usages); }
 
 
 private:
 private:
-    explicit CryptoKey(JS::Realm&);
+    CryptoKey(JS::Realm&, InternalKeyData);
     virtual void initialize(JS::Realm&) override;
     virtual void initialize(JS::Realm&) override;
     virtual void visit_edges(Visitor&) override;
     virtual void visit_edges(Visitor&) override;
 
 
@@ -42,6 +45,8 @@ private:
     bool m_extractable { false };
     bool m_extractable { false };
     JS::NonnullGCPtr<Object> m_algorithm;
     JS::NonnullGCPtr<Object> m_algorithm;
     JS::NonnullGCPtr<Object> m_usages;
     JS::NonnullGCPtr<Object> m_usages;
+
+    InternalKeyData m_key_data;
 };
 };
 
 
 }
 }

+ 31 - 110
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
 // 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& realm = this->realm();
+    auto& vm = this->vm();
 
 
     // If alg is an instance of a DOMString:
     // If alg is an instance of a DOMString:
     if (algorithm.has<String>()) {
     if (algorithm.has<String>()) {
         // Return the result of running the normalize an algorithm algorithm,
         // 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.
         // 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()));
         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);
         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
     // 2. Let initialAlg be the result of converting the ECMAScript object represented by alg to
     // the IDL dictionary type Algorithm, as defined by [WebIDL].
     // 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.
     // 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.
     // 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:
     // 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.
         // 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.
         // 2. Let desiredType be the IDL dictionary type stored at algName in registeredAlgorithms.
-        desired_type = (*it).value;
+        desired_type = it->value;
     } else {
     } else {
         // Otherwise:
         // Otherwise:
         // Return a new NotSupportedError and terminate this algorithm.
         // 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
     // 8. Let normalizedAlgorithm be the result of converting the ECMAScript object represented by alg
     // to the IDL dictionary type desiredType, as defined by [WebIDL].
     // 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.
     // 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.
     // 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.
     // 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.
     // 13. Return normalizedAlgorithm.
     return normalized_algorithm;
     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
         // 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.
         // 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;
-
-        ::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))));
-            return;
-        }
+        auto result = algorithm_object.methods->digest(*algorithm_object.parameter, data_buffer);
 
 
-        ::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));
+        if (result.is_exception()) {
+            WebIDL::reject_promise(realm, promise, Bindings::dom_exception_to_throw_completion(realm.vm(), result.release_error()).release_value().value());
             return;
             return;
         }
         }
 
 
-        auto result = JS::ArrayBuffer::create(realm, result_buffer.release_value());
-
         // 9. Resolve promise with result.
         // 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());
     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);
     auto promise = WebIDL::create_promise(realm);
 
 
     // 8. Return promise and perform the remaining steps in parallel.
     // 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);
         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.
         // 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
         // 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.
         // 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()) {
         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());
             WebIDL::reject_promise(realm, promise, Bindings::dom_exception_to_throw_completion(realm.vm(), maybe_result.release_error()).release_value().value());
             return;
             return;
@@ -298,13 +261,13 @@ SubtleCrypto::SupportedAlgorithmsMap SubtleCrypto::supported_algorithms()
 
 
     // https://w3c.github.io/webcrypto/#algorithm-conventions
     // https://w3c.github.io/webcrypto/#algorithm-conventions
     // https://w3c.github.io/webcrypto/#sha
     // 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
     // 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("deriveBits"_string, "PBKDF2"_string, "Pbkdf2Params"_string);
     // FIXME: define_an_algorithm("get key length"_string, "PBKDF2"_string, ""_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
 // 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();
     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();
     auto registered_algorithms = maybe_registered_algorithms.value();
 
 
     // 2. Set the alg key of registeredAlgorithms to the IDL dictionary type type.
     // 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);
     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;
-}
-
 }
 }

+ 16 - 8
Userland/Libraries/LibWeb/Crypto/SubtleCrypto.h

@@ -13,6 +13,7 @@
 #include <LibJS/Forward.h>
 #include <LibJS/Forward.h>
 #include <LibWeb/Bindings/PlatformObject.h>
 #include <LibWeb/Bindings/PlatformObject.h>
 #include <LibWeb/Bindings/SubtleCryptoPrototype.h>
 #include <LibWeb/Bindings/SubtleCryptoPrototype.h>
+#include <LibWeb/Crypto/CryptoAlgorithms.h>
 #include <LibWeb/Crypto/CryptoBindings.h>
 #include <LibWeb/Crypto/CryptoBindings.h>
 #include <LibWeb/Crypto/CryptoKey.h>
 #include <LibWeb/Crypto/CryptoKey.h>
 
 
@@ -22,9 +23,11 @@ class SubtleCrypto final : public Bindings::PlatformObject {
     WEB_PLATFORM_OBJECT(SubtleCrypto, Bindings::PlatformObject);
     WEB_PLATFORM_OBJECT(SubtleCrypto, Bindings::PlatformObject);
     JS_DECLARE_ALLOCATOR(SubtleCrypto);
     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:
 public:
     [[nodiscard]] static JS::NonnullGCPtr<SubtleCrypto> create(JS::Realm&);
     [[nodiscard]] static JS::NonnullGCPtr<SubtleCrypto> create(JS::Realm&);
@@ -32,19 +35,24 @@ public:
     virtual ~SubtleCrypto() override;
     virtual ~SubtleCrypto() override;
 
 
     JS::NonnullGCPtr<JS::Promise> digest(AlgorithmIdentifier const& algorithm, JS::Handle<WebIDL::BufferSource> const& data);
     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:
 private:
     explicit SubtleCrypto(JS::Realm&);
     explicit SubtleCrypto(JS::Realm&);
     virtual void initialize(JS::Realm&) override;
     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_internal();
     static SubtleCrypto::SupportedAlgorithmsMap supported_algorithms();
     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);
 };
 };
 
 
 }
 }