From 4daf07e69fe8ef641bcb39909f17df7cbfe88a01 Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Mon, 13 Dec 2021 22:09:55 +0000 Subject: [PATCH] LibWeb: Implement SubtleCrypto.digest() This is a simple implementation of SubtleCrypto.digest() using LibCrypto under the hood, so it supports all the required hash functions: SHA-1, SHA-256, SHA-384, SHA-512. Two FIXMEs remain: doing the hashing "in parallel", and supporting an object argument instead of a plain string. --- Userland/Libraries/LibWeb/CMakeLists.txt | 2 + .../Libraries/LibWeb/Crypto/SubtleCrypto.cpp | 83 +++++++++++++++++++ .../Libraries/LibWeb/Crypto/SubtleCrypto.h | 3 + .../Libraries/LibWeb/Crypto/SubtleCrypto.idl | 2 + 4 files changed, 90 insertions(+) create mode 100644 Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index fd7b198de69..cd33e36529a 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -15,6 +15,7 @@ set(SOURCES Bindings/WindowObject.cpp Bindings/Wrappable.cpp Crypto/Crypto.cpp + Crypto/SubtleCrypto.cpp CSS/Serialize.cpp CSS/CSSConditionRule.cpp CSS/CSSGroupingRule.cpp @@ -364,6 +365,7 @@ function(libweb_js_wrapper class) endfunction() libweb_js_wrapper(Crypto/Crypto) +libweb_js_wrapper(Crypto/SubtleCrypto) libweb_js_wrapper(CSS/CSSRule) libweb_js_wrapper(CSS/CSSRuleList) libweb_js_wrapper(CSS/CSSStyleDeclaration) diff --git a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp new file mode 100644 index 00000000000..a77ac50bce8 --- /dev/null +++ b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Web::Crypto { + +JS::Promise* SubtleCrypto::digest(String const& algorithm, JS::Handle const& data) +{ + auto& global_object = wrapper()->global_object(); + + // 1. Let algorithm be the algorithm parameter passed to the digest() method. + + // 2. Let data be the result of getting a copy of the bytes held by the data parameter passed to the digest() method. + auto data_buffer = Bindings::IDL::get_buffer_source_copy(*data.cell()); + if (!data_buffer.has_value()) { + auto* error = wrap(wrapper()->global_object(), DOM::OperationError::create("Failed to copy bytes from ArrayBuffer")); + auto* promise = JS::Promise::create(global_object); + promise->reject(error); + return promise; + } + + // 3. Let normalizedAlgorithm be the result of normalizing an algorithm, with alg set to algorithm and op set to "digest". + // FIXME: This is way more generic than it needs to be right now, so we simplify it. + ::Crypto::Hash::HashKind hash_kind; + if (algorithm.equals_ignoring_case("SHA-1"sv)) { + hash_kind = ::Crypto::Hash::HashKind::SHA1; + } else if (algorithm.equals_ignoring_case("SHA-256"sv)) { + hash_kind = ::Crypto::Hash::HashKind::SHA256; + } else if (algorithm.equals_ignoring_case("SHA-384"sv)) { + hash_kind = ::Crypto::Hash::HashKind::SHA384; + } else if (algorithm.equals_ignoring_case("SHA-512"sv)) { + hash_kind = ::Crypto::Hash::HashKind::SHA512; + } + // 4. If an error occurred, return a Promise rejected with normalizedAlgorithm. + else { + auto* error = wrap(wrapper()->global_object(), DOM::NotSupportedError::create(String::formatted("Invalid hash function '{}'", algorithm))); + auto* promise = JS::Promise::create(global_object); + promise->reject(error); + return promise; + } + + // 5. Let promise be a new Promise. + auto* promise = JS::Promise::create(global_object); + + // 6. Return promise and perform the remaining steps in parallel. + // FIXME: We don't have a good abstraction for this yet, so we do it in sync. + + // 7. If the following steps or referenced procedures say to throw an error, reject promise with the returned error and then terminate the algorithm. + + // 8. Let result be the result of performing the digest operation specified by normalizedAlgorithm using algorithm, with data as message. + ::Crypto::Hash::Manager hash; + hash.initialize(hash_kind); + hash.update(*data_buffer); + auto digest = hash.digest(); + auto const* digest_data = digest.immutable_data(); + auto result_buffer = ByteBuffer::create_zeroed(hash.digest_size()); + if (!result_buffer.has_value()) { + auto* error = wrap(wrapper()->global_object(), DOM::OperationError::create("Failed to create result buffer")); + promise->reject(error); + return promise; + } + for (size_t i = 0; i < hash.digest_size(); ++i) + (*result_buffer)[i] = digest_data[i]; + + auto* result = JS::ArrayBuffer::create(global_object, result_buffer.release_value()); + + // 9. Resolve promise with result. + promise->fulfill(result); + return promise; +} + +} diff --git a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.h b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.h index 7d3b0d7d319..24f762919c8 100644 --- a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.h +++ b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.h @@ -6,6 +6,7 @@ #pragma once +#include #include namespace Web::Crypto { @@ -21,6 +22,8 @@ public: return adopt_ref(*new SubtleCrypto()); } + JS::Promise* digest(String const& algorithm, JS::Handle const& data); + private: SubtleCrypto() = default; }; diff --git a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.idl b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.idl index e0e9d5489c7..99f867b2c63 100644 --- a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.idl +++ b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.idl @@ -1,3 +1,5 @@ [SecureContext,Exposed=(Window,Worker)] interface SubtleCrypto { + // FIXME: Add support for AlgorithmIdentifier ("typedef (object or DOMString)") + Promise digest(DOMString algorithm, BufferSource data); };