diff --git a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp index 09eda7db37..ace278bee6 100644 --- a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp +++ b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp @@ -35,6 +35,79 @@ void SubtleCrypto::initialize(JS::Realm& realm) set_prototype(&Bindings::ensure_web_prototype(realm, "SubtleCrypto"_fly_string)); } +// https://w3c.github.io/webcrypto/#dfn-normalize-an-algorithm +JS::ThrowCompletionOr SubtleCrypto::normalize_an_algorithm(Variant, String> const& algorithm, String operation) +{ + auto& realm = this->realm(); + + // 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))); + + return normalize_an_algorithm(dictionary, operation); + } + + // If alg is an object: + // 1. Let registeredAlgorithms be the associative container stored at the op key of supportedAlgorithms. + // NOTE: There should always be a container at the op key. + auto internal_object = supported_algorithms(); + auto maybe_registered_algorithms = internal_object.get(operation); + auto registered_algorithms = maybe_registered_algorithms.value(); + + // 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"); + } + + // 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())); + + String desired_type; + + // 5. If registeredAlgorithms contains a key that is a case-insensitive string match for algName: + if (registered_algorithms.contains(algorithm_name)) { + // 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; + } 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); + } + + // 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? + SubtleCrypto::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 + // and all of desiredType's inherited dictionaries, in order from least to most derived. + // FIXME: 12. For each dictionary dictionary in dictionaries: + + // 13. Return normalizedAlgorithm. + return normalized_algorithm; +} + // https://w3c.github.io/webcrypto/#dfn-SubtleCrypto-method-digest JS::NonnullGCPtr SubtleCrypto::digest(Variant, String> const& algorithm, JS::Handle const& data) { @@ -53,31 +126,14 @@ JS::NonnullGCPtr SubtleCrypto::digest(Variant()) { - auto promise = JS::Promise::create(realm); - auto error = WebIDL::OperationError::create(realm, "LibJS does not support non-string parameters to digest()"_fly_string); - promise->reject(error.ptr()); - return promise; - } + auto normalized_algorithm = normalize_an_algorithm(algorithm, "digest"_string); - ::Crypto::Hash::HashKind hash_kind; - auto algorithm_name = algorithm.get(); - auto algorithm_as_string_view = algorithm_name.bytes_as_string_view(); - if (algorithm_as_string_view.equals_ignoring_ascii_case("SHA-1"sv)) { - hash_kind = ::Crypto::Hash::HashKind::SHA1; - } else if (algorithm_as_string_view.equals_ignoring_ascii_case("SHA-256"sv)) { - hash_kind = ::Crypto::Hash::HashKind::SHA256; - } else if (algorithm_as_string_view.equals_ignoring_ascii_case("SHA-384"sv)) { - hash_kind = ::Crypto::Hash::HashKind::SHA384; - } else if (algorithm_as_string_view.equals_ignoring_ascii_case("SHA-512"sv)) { - hash_kind = ::Crypto::Hash::HashKind::SHA512; - } // 4. If an error occurred, return a Promise rejected with normalizedAlgorithm. - else { - auto error = WebIDL::NotSupportedError::create(realm, MUST(String::formatted("Invalid hash function '{}'", algorithm_name))); + if (normalized_algorithm.is_error()) { auto promise = JS::Promise::create(realm); - promise->reject(error.ptr()); + auto error = normalized_algorithm.release_error(); + auto error_value = error.value().value(); + promise->reject(error_value); return promise; } @@ -90,6 +146,24 @@ JS::NonnullGCPtr SubtleCrypto::digest(Variantreject(error.ptr()); + return promise; + } + ::Crypto::Hash::Manager hash { hash_kind }; hash.update(data_buffer); @@ -105,7 +179,69 @@ JS::NonnullGCPtr SubtleCrypto::digest(Variantfulfill(result); + return promise; } +SubtleCrypto::SupportedAlgorithmsMap& SubtleCrypto::supported_algorithms_internal() +{ + static SubtleCrypto::SupportedAlgorithmsMap s_supported_algorithms; + return s_supported_algorithms; +} + +// https://w3c.github.io/webcrypto/#algorithm-normalization-internal +SubtleCrypto::SupportedAlgorithmsMap SubtleCrypto::supported_algorithms() +{ + auto& internal_object = supported_algorithms_internal(); + + if (!internal_object.is_empty()) { + return internal_object; + } + + // 1. For each value, v in the List of supported operations, + // set the v key of the internal object supportedAlgorithms to a new associative container. + auto supported_operations = Vector { + "encrypt"_string, + "decrypt"_string, + "sign"_string, + "verify"_string, + "digest"_string, + "deriveBits"_string, + "wrapKey"_string, + "unwrapKey"_string, + "generateKey"_string, + "importKey"_string, + "exportKey"_string, + "get key length"_string, + }; + + for (auto& operation : supported_operations) { + internal_object.set(operation, {}); + } + + // https://w3c.github.io/webcrypto/#algorithm-conventions + // https://w3c.github.io/webcrypto/#sha + define_an_algorithm("digest"_string, "SHA1"_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); + + return internal_object; +} + +// https://w3c.github.io/webcrypto/#concept-define-an-algorithm +void SubtleCrypto::define_an_algorithm(String op, String algorithm, String type) +{ + auto& internal_object = supported_algorithms_internal(); + + // 1. Let registeredAlgorithms be the associative container stored at the op key of supportedAlgorithms. + // NOTE: There should always be a container at the op key. + auto maybe_registered_algorithms = internal_object.get(op); + 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); + internal_object.set(op, registered_algorithms); +} + } diff --git a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.h b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.h index cb2234a997..ded2071113 100644 --- a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.h +++ b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.h @@ -6,6 +6,8 @@ #pragma once +#include +#include #include #include @@ -15,7 +17,13 @@ class SubtleCrypto final : public Bindings::PlatformObject { WEB_PLATFORM_OBJECT(SubtleCrypto, Bindings::PlatformObject); JS_DECLARE_ALLOCATOR(SubtleCrypto); + using SupportedAlgorithmsMap = HashMap>; + public: + struct Algorithm { + String name; + }; + [[nodiscard]] static JS::NonnullGCPtr create(JS::Realm&); virtual ~SubtleCrypto() override; @@ -25,6 +33,12 @@ public: private: explicit SubtleCrypto(JS::Realm&); virtual void initialize(JS::Realm&) override; + + JS::ThrowCompletionOr normalize_an_algorithm(Variant, String> 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); }; }