diff --git a/Libraries/LibCrypto/CMakeLists.txt b/Libraries/LibCrypto/CMakeLists.txt index b4415216e5..cd80d442dc 100644 --- a/Libraries/LibCrypto/CMakeLists.txt +++ b/Libraries/LibCrypto/CMakeLists.txt @@ -14,6 +14,7 @@ set(SOURCES Curves/EdwardsCurve.cpp Curves/SECPxxxr1.cpp Hash/Argon2.cpp + Hash/SHAKE.cpp Hash/BLAKE2b.cpp Hash/HKDF.cpp Hash/MD5.cpp diff --git a/Libraries/LibCrypto/Hash/SHAKE.cpp b/Libraries/LibCrypto/Hash/SHAKE.cpp new file mode 100644 index 0000000000..3fe7668a31 --- /dev/null +++ b/Libraries/LibCrypto/Hash/SHAKE.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2025, mikiubo + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace Crypto::Hash { + +SHAKE::SHAKE(SHAKEKind kind) +{ + m_md = (kind == SHAKEKind::CSHAKE128) ? EVP_shake128() : EVP_shake256(); +} + +ErrorOr SHAKE::digest( + ReadonlyBytes data, + u32 length, + Optional customization, + Optional function_name) const +{ + bool wants_cshake = (customization.has_value() && !customization->is_empty()) || (function_name.has_value() && !function_name->is_empty()); + + if (wants_cshake) { + // FIXME: Implement cSHAKE with non-empty N or S + return Error::from_string_literal("cSHAKE with non-empty N or S is not supported yet (OpenSSL EVP limitation)"); + } + + if (length % 8 != 0) { + return Error::from_string_literal("SHAKE output length must be a multiple of 8 bits"); + } + + size_t output_bytes = length / 8; + auto buf = TRY(ByteBuffer::create_uninitialized(output_bytes)); + + auto ctx = TRY(OpenSSL_MD_CTX::wrap(EVP_MD_CTX_new())); + + OPENSSL_TRY(EVP_DigestInit_ex(ctx.ptr(), m_md, nullptr)); + + OPENSSL_TRY(EVP_DigestUpdate(ctx.ptr(), data.data(), data.size())); + + OPENSSL_TRY(EVP_DigestFinalXOF(ctx.ptr(), buf.data(), output_bytes)); + + return buf; +} + +} diff --git a/Libraries/LibCrypto/Hash/SHAKE.h b/Libraries/LibCrypto/Hash/SHAKE.h new file mode 100644 index 0000000000..14375b0755 --- /dev/null +++ b/Libraries/LibCrypto/Hash/SHAKE.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025, mikiubo + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace Crypto::Hash { + +enum class SHAKEKind { + CSHAKE128, + CSHAKE256 +}; + +class SHAKE { + AK_MAKE_NONCOPYABLE(SHAKE); + +public: + explicit SHAKE(SHAKEKind); + + ~SHAKE() = default; + + ErrorOr digest( + ReadonlyBytes data, + u32 length, + Optional customization, + Optional function_name) const; + +private: + EVP_MD const* m_md; +}; + +} diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index db926ec44b..877cc97c40 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -631,6 +632,19 @@ JS::ThrowCompletionOr> Ed448Params::from_value(JS return adopt_own(*new Ed448Params { maybe_context }); } +static inline JS::ThrowCompletionOr> get_optional_buffer_source(JS::VM& vm, JS::Object const& object, JS::PropertyKey const& name) +{ + if (!TRY(object.has_property(name))) + return OptionalNone {}; + + auto value = TRY(object.get(name)); + + if (!WebIDL::is_buffer_source_type(value)) + return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "BufferSource"); + + return TRY_OR_THROW_OOM(vm, WebIDL::get_buffer_source_copy(value.as_object())); +} + Argon2Params::~Argon2Params() = default; JS::ThrowCompletionOr> Argon2Params::from_value(JS::VM& vm, JS::Value value) @@ -664,23 +678,34 @@ JS::ThrowCompletionOr> Argon2Params::from_value(J maybe_version = TRY(WebIDL::convert_to_int(vm, version_value, WebIDL::EnforceRange::Yes, WebIDL::Clamp::No)); } - auto const extract_optional_buffer_source_value = [&](auto const& name) -> JS::ThrowCompletionOr> { - auto maybe_buffer = Optional {}; - if (MUST(object.has_property(name))) { - auto key_value = TRY(object.get(name)); - if (!key_value.is_object() || !(is(key_value.as_object()) || is(key_value.as_object()) || is(key_value.as_object()))) - return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "BufferSource"); - maybe_buffer = TRY_OR_THROW_OOM(vm, WebIDL::get_buffer_source_copy(key_value.as_object())); - } - return maybe_buffer; - }; - - auto const secret_value = TRY(extract_optional_buffer_source_value("secretValue"_utf16_fly_string)); - auto const associated_data = TRY(extract_optional_buffer_source_value("associatedData"_utf16_fly_string)); + auto const secret_value = TRY(get_optional_buffer_source(vm, object, "secretValue"_utf16_fly_string)); + auto const associated_data = TRY(get_optional_buffer_source(vm, object, "associatedData"_utf16_fly_string)); return adopt_own(*new Argon2Params { nonce, parallelism, memory, passes, maybe_version, secret_value, associated_data }); } +CShakeParams::~CShakeParams() = default; + +JS::ThrowCompletionOr> CShakeParams::from_value(JS::VM& vm, JS::Value value) +{ + VERIFY(value.is_object()); + auto& object = value.as_object(); + + if (!MUST(object.has_property("length"_utf16_fly_string))) { + return vm.throw_completion(JS::ErrorType::MissingRequiredProperty, "length"); + } + + auto const length_value = TRY(object.get("length"_utf16_fly_string)); + + auto const length = TRY(WebIDL::convert_to_int(vm, length_value, WebIDL::EnforceRange::Yes, WebIDL::Clamp::No)); + + auto const function_name = TRY(get_optional_buffer_source(vm, object, "functionName"_utf16_fly_string)); + + auto const customization = TRY(get_optional_buffer_source(vm, object, "customization"_utf16_fly_string)); + + return adopt_own(*new CShakeParams { length, function_name, customization }); +} + // https://w3c.github.io/webcrypto/#rsa-oaep-operations WebIDL::ExceptionOr> RSAOAEP::encrypt(AlgorithmParams const& params, GC::Ref key, ByteBuffer const& plaintext) { @@ -9634,4 +9659,47 @@ WebIDL::ExceptionOr Argon2::get_key_length(AlgorithmParams const&) return JS::js_null(); } +// https://wicg.github.io/webcrypto-modern-algos/#cshake-operations-digest +WebIDL::ExceptionOr> CShake::digest(AlgorithmParams const& params, ByteBuffer const& data) +{ + auto const& normalized_algorithm = static_cast(params); + + // 1. Let length be the length member of normalizedAlgorithm. + auto const& length = normalized_algorithm.length; + + // 2. Let functionName be the functionName member of normalizedAlgorithm if present or the empty octet string otherwise. + auto const& function_name = normalized_algorithm.function_name; + + // 3. Let customization be the customization member of normalizedAlgorithm if present or the empty octet string otherwise. + auto const& customization = normalized_algorithm.customization; + + auto const algorithm = [&]() { + // 4. If the name member of normalizedAlgorithm is a case-sensitive string match for "cSHAKE128": + if (normalized_algorithm.name == "cSHAKE128"sv) + return ::Crypto::Hash::SHAKE(::Crypto::Hash::SHAKEKind::CSHAKE128); + // 4. If the name member of normalizedAlgorithm is a case-sensitive string match for "cSHAKE256": + if (normalized_algorithm.name == "cSHAKE256"sv) + return ::Crypto::Hash::SHAKE(::Crypto::Hash::SHAKEKind::CSHAKE256); + VERIFY_NOT_REACHED(); + }(); + + // 4. Let result be the result of performing the cSHAKE128/cSHAKE256 function defined in Section 3 of [NIST-SP800-185] + // using message as the X input parameter, + // length as the L input parameter, + // functionName as the N input parameter, + // and customization as the S input parameter. + auto maybe_result = algorithm.digest( + data, + length, + customization.map([](auto const& value) { return value.span(); }), + function_name.map([](auto const& value) { return value.span(); })); + + // 5. If performing the operation results in an error, then throw an OperationError. + if (maybe_result.is_error()) + return WebIDL::OperationError::create(m_realm, Utf16String::formatted("Hash function failed: {}", maybe_result.error())); + + // 6. Return result. + return JS::ArrayBuffer::create(m_realm, maybe_result.release_value()); +} + } diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.h b/Libraries/LibWeb/Crypto/CryptoAlgorithms.h index fda43852ed..cc3e7ea40c 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.h +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.h @@ -739,6 +739,18 @@ private: } }; +class CShake : public AlgorithmMethods { +public: + virtual WebIDL::ExceptionOr> digest(AlgorithmParams const&, ByteBuffer const&) override; + static NonnullOwnPtr create(JS::Realm& realm) { return adopt_own(*new CShake(realm)); } + +private: + explicit CShake(JS::Realm& realm) + : AlgorithmMethods(realm) + { + } +}; + struct EcdhKeyDeriveParams : public AlgorithmParams { virtual ~EcdhKeyDeriveParams() override; @@ -808,6 +820,25 @@ struct Argon2Params : public AlgorithmParams { static JS::ThrowCompletionOr> from_value(JS::VM&, JS::Value); }; +// https://wicg.github.io/webcrypto-modern-algos/#cshake-params +struct CShakeParams : public AlgorithmParams { + virtual ~CShakeParams() override; + + CShakeParams(u32 length, Optional function_name, Optional customization) + : length(length) + , function_name(move(function_name)) + , customization(move(customization)) + + { + } + + u32 length; + Optional function_name; + Optional customization; + + static JS::ThrowCompletionOr> from_value(JS::VM&, JS::Value); +}; + ErrorOr base64_url_uint_encode(::Crypto::UnsignedBigInteger); WebIDL::ExceptionOr base64_url_bytes_decode(JS::Realm&, String const& base64_url_string); WebIDL::ExceptionOr<::Crypto::UnsignedBigInteger> base64_url_uint_decode(JS::Realm&, String const& base64_url_string); diff --git a/Libraries/LibWeb/Crypto/SubtleCrypto.cpp b/Libraries/LibWeb/Crypto/SubtleCrypto.cpp index 3652b66350..a46ae656b9 100644 --- a/Libraries/LibWeb/Crypto/SubtleCrypto.cpp +++ b/Libraries/LibWeb/Crypto/SubtleCrypto.cpp @@ -1547,6 +1547,10 @@ SupportedAlgorithmsMap const& supported_algorithms() define_an_algorithm("digest"_string, "SHA3-384"_string); define_an_algorithm("digest"_string, "SHA3-512"_string); + // https://wicg.github.io/webcrypto-modern-algos/#cshake-registration + define_an_algorithm("digest"_string, "cSHAKE128"_string); + define_an_algorithm("digest"_string, "cSHAKE256"_string); + // https://w3c.github.io/webcrypto/#hkdf-registration define_an_algorithm("deriveBits"_string, "HKDF"_string); define_an_algorithm("importKey"_string, "HKDF"_string); diff --git a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/digest/cshake.tentative.https.any.txt b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/digest/cshake.tentative.https.any.txt new file mode 100644 index 0000000000..f58e94aab6 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/digest/cshake.tentative.https.any.txt @@ -0,0 +1,53 @@ +Harness status: OK + +Found 48 tests + +48 Pass +Pass cSHAKE128 with 0 bit output and empty source data +Pass cSHAKE128 with 0 bit output and empty source data and altered buffer after call +Pass cSHAKE128 with 0 bit output and short source data +Pass cSHAKE128 with 0 bit output and short source data and altered buffer after call +Pass cSHAKE128 with 0 bit output and medium source data +Pass cSHAKE128 with 0 bit output and medium source data and altered buffer after call +Pass cSHAKE128 with 256 bit output and empty source data +Pass cSHAKE128 with 256 bit output and empty source data and altered buffer after call +Pass cSHAKE128 with 256 bit output and short source data +Pass cSHAKE128 with 256 bit output and short source data and altered buffer after call +Pass cSHAKE128 with 256 bit output and medium source data +Pass cSHAKE128 with 256 bit output and medium source data and altered buffer after call +Pass cSHAKE128 with 384 bit output and empty source data +Pass cSHAKE128 with 384 bit output and empty source data and altered buffer after call +Pass cSHAKE128 with 384 bit output and short source data +Pass cSHAKE128 with 384 bit output and short source data and altered buffer after call +Pass cSHAKE128 with 384 bit output and medium source data +Pass cSHAKE128 with 384 bit output and medium source data and altered buffer after call +Pass cSHAKE128 with 512 bit output and empty source data +Pass cSHAKE128 with 512 bit output and empty source data and altered buffer after call +Pass cSHAKE128 with 512 bit output and short source data +Pass cSHAKE128 with 512 bit output and short source data and altered buffer after call +Pass cSHAKE128 with 512 bit output and medium source data +Pass cSHAKE128 with 512 bit output and medium source data and altered buffer after call +Pass cSHAKE256 with 0 bit output and empty source data +Pass cSHAKE256 with 0 bit output and empty source data and altered buffer after call +Pass cSHAKE256 with 0 bit output and short source data +Pass cSHAKE256 with 0 bit output and short source data and altered buffer after call +Pass cSHAKE256 with 0 bit output and medium source data +Pass cSHAKE256 with 0 bit output and medium source data and altered buffer after call +Pass cSHAKE256 with 256 bit output and empty source data +Pass cSHAKE256 with 256 bit output and empty source data and altered buffer after call +Pass cSHAKE256 with 256 bit output and short source data +Pass cSHAKE256 with 256 bit output and short source data and altered buffer after call +Pass cSHAKE256 with 256 bit output and medium source data +Pass cSHAKE256 with 256 bit output and medium source data and altered buffer after call +Pass cSHAKE256 with 384 bit output and empty source data +Pass cSHAKE256 with 384 bit output and empty source data and altered buffer after call +Pass cSHAKE256 with 384 bit output and short source data +Pass cSHAKE256 with 384 bit output and short source data and altered buffer after call +Pass cSHAKE256 with 384 bit output and medium source data +Pass cSHAKE256 with 384 bit output and medium source data and altered buffer after call +Pass cSHAKE256 with 512 bit output and empty source data +Pass cSHAKE256 with 512 bit output and empty source data and altered buffer after call +Pass cSHAKE256 with 512 bit output and short source data +Pass cSHAKE256 with 512 bit output and short source data and altered buffer after call +Pass cSHAKE256 with 512 bit output and medium source data +Pass cSHAKE256 with 512 bit output and medium source data and altered buffer after call \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/digest/cshake.tentative.https.any.html b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/digest/cshake.tentative.https.any.html new file mode 100644 index 0000000000..1b6d26dd4a --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/digest/cshake.tentative.https.any.html @@ -0,0 +1,16 @@ + + +WebCryptoAPI: digest() cSHAKE algorithms + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/digest/cshake.tentative.https.any.js b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/digest/cshake.tentative.https.any.js new file mode 100644 index 0000000000..e793722d8b --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/digest/cshake.tentative.https.any.js @@ -0,0 +1,210 @@ +// META: title=WebCryptoAPI: digest() cSHAKE algorithms +// META: timeout=long + +var subtle = crypto.subtle; // Change to test prefixed implementations + +var sourceData = { + empty: new Uint8Array(0), + short: new Uint8Array([ + 21, 110, 234, 124, 193, 76, 86, 203, 148, 219, 3, 10, 74, 157, 149, 255, + ]), + medium: new Uint8Array([ + 182, 200, 249, 223, 100, 140, 208, 136, 183, 15, 56, 231, 65, 151, 177, 140, + 184, 30, 30, 67, 80, 213, 11, 204, 184, 251, 90, 115, 121, 200, 123, 178, + 227, 214, 237, 84, 97, 237, 30, 159, 54, 243, 64, 163, 150, 42, 68, 107, + 129, 91, 121, 75, 75, 212, 58, 68, 3, 80, 32, 119, 178, 37, 108, 200, 7, + 131, 127, 58, 172, 209, 24, 235, 75, 156, 43, 174, 184, 151, 6, 134, 37, + 171, 172, 161, 147, + ]), +}; + +// Test different output lengths for cSHAKE +var digestLengths = [0, 256, 384, 512]; + +var digestedData = { + cSHAKE128: { + 0: { + empty: new Uint8Array([]), + short: new Uint8Array([]), + medium: new Uint8Array([]), + }, + 256: { + empty: new Uint8Array([ + 127, 156, 43, 164, 232, 143, 130, 125, 97, 96, 69, 80, 118, 5, 133, 62, + 215, 59, 128, 147, 246, 239, 188, 136, 235, 26, 110, 172, 250, 102, 239, + 38, + ]), + short: new Uint8Array([ + 222, 166, 45, 115, 230, 181, 156, 247, 37, 208, 50, 13, 102, 0, 137, + 164, 71, 92, 187, 211, 184, 83, 158, 54, 105, 31, 21, 13, 71, 85, 103, + 148, + ]), + medium: new Uint8Array([ + 177, 172, 213, 58, 3, 231, 106, 34, 30, 82, 234, 87, 142, 4, 47, 104, + 106, 104, 195, 209, 201, 131, 42, 177, 130, 133, 207, 79, 48, 76, 163, + 45, + ]), + }, + 384: { + empty: new Uint8Array([ + 127, 156, 43, 164, 232, 143, 130, 125, 97, 96, 69, 80, 118, 5, 133, 62, + 215, 59, 128, 147, 246, 239, 188, 136, 235, 26, 110, 172, 250, 102, 239, + 38, 60, 177, 238, 169, 136, 0, 75, 147, 16, 60, 251, 10, 238, 253, 42, + 104, + ]), + short: new Uint8Array([ + 222, 166, 45, 115, 230, 181, 156, 247, 37, 208, 50, 13, 102, 0, 137, + 164, 71, 92, 187, 211, 184, 83, 158, 54, 105, 31, 21, 13, 71, 85, 103, + 148, 240, 55, 64, 1, 183, 136, 138, 188, 54, 152, 212, 11, 137, 174, 49, + 52, + ]), + medium: new Uint8Array([ + 177, 172, 213, 58, 3, 231, 106, 34, 30, 82, 234, 87, 142, 4, 47, 104, + 106, 104, 195, 209, 201, 131, 42, 177, 130, 133, 207, 79, 48, 76, 163, + 45, 63, 170, 9, 252, 130, 170, 225, 66, 211, 223, 205, 121, 5, 138, 93, + 92, + ]), + }, + 512: { + empty: new Uint8Array([ + 127, 156, 43, 164, 232, 143, 130, 125, 97, 96, 69, 80, 118, 5, 133, 62, + 215, 59, 128, 147, 246, 239, 188, 136, 235, 26, 110, 172, 250, 102, 239, + 38, 60, 177, 238, 169, 136, 0, 75, 147, 16, 60, 251, 10, 238, 253, 42, + 104, 110, 1, 250, 74, 88, 232, 163, 99, 156, 168, 161, 227, 249, 174, + 87, 226, + ]), + short: new Uint8Array([ + 222, 166, 45, 115, 230, 181, 156, 247, 37, 208, 50, 13, 102, 0, 137, + 164, 71, 92, 187, 211, 184, 83, 158, 54, 105, 31, 21, 13, 71, 85, 103, + 148, 240, 55, 64, 1, 183, 136, 138, 188, 54, 152, 212, 11, 137, 174, 49, + 52, 233, 51, 245, 26, 132, 202, 127, 218, 136, 12, 59, 253, 217, 220, + 58, 94, + ]), + medium: new Uint8Array([ + 177, 172, 213, 58, 3, 231, 106, 34, 30, 82, 234, 87, 142, 4, 47, 104, + 106, 104, 195, 209, 201, 131, 42, 177, 130, 133, 207, 79, 48, 76, 163, + 45, 63, 170, 9, 252, 130, 170, 225, 66, 211, 223, 205, 121, 5, 138, 93, + 92, 60, 17, 189, 45, 17, 195, 248, 169, 51, 31, 98, 172, 221, 186, 225, + 93, + ]), + }, + }, + cSHAKE256: { + 0: { + empty: new Uint8Array([]), + short: new Uint8Array([]), + medium: new Uint8Array([]), + }, + 256: { + empty: new Uint8Array([ + 70, 185, 221, 43, 11, 168, 141, 19, 35, 59, 63, 235, 116, 62, 235, 36, + 63, 205, 82, 234, 98, 184, 27, 130, 181, 12, 39, 100, 110, 213, 118, 47, + ]), + short: new Uint8Array([ + 23, 56, 17, 63, 90, 187, 62, 229, 50, 14, 225, 138, 162, 102, 195, 97, + 122, 116, 117, 219, 216, 237, 154, 152, 89, 148, 253, 221, 97, 18, 173, + 153, + ]), + medium: new Uint8Array([ + 65, 70, 193, 61, 134, 217, 188, 24, 107, 11, 48, 154, 182, 161, 36, 238, + 12, 116, 186, 38, 184, 198, 13, 204, 123, 62, 213, 5, 150, 154, 168, + 209, + ]), + }, + 384: { + empty: new Uint8Array([ + 70, 185, 221, 43, 11, 168, 141, 19, 35, 59, 63, 235, 116, 62, 235, 36, + 63, 205, 82, 234, 98, 184, 27, 130, 181, 12, 39, 100, 110, 213, 118, 47, + 215, 93, 196, 221, 216, 192, 242, 0, 203, 5, 1, 157, 103, 181, 146, 246, + ]), + short: new Uint8Array([ + 23, 56, 17, 63, 90, 187, 62, 229, 50, 14, 225, 138, 162, 102, 195, 97, + 122, 116, 117, 219, 216, 237, 154, 152, 89, 148, 253, 221, 97, 18, 173, + 153, 158, 200, 226, 235, 223, 234, 251, 150, 231, 111, 107, 179, 163, + 173, 186, 67, + ]), + medium: new Uint8Array([ + 65, 70, 193, 61, 134, 217, 188, 24, 107, 11, 48, 154, 182, 161, 36, 238, + 12, 116, 186, 38, 184, 198, 13, 204, 123, 62, 213, 5, 150, 154, 168, + 209, 144, 40, 198, 49, 121, 153, 160, 133, 177, 230, 182, 167, 133, 206, + 79, 246, + ]), + }, + 512: { + empty: new Uint8Array([ + 70, 185, 221, 43, 11, 168, 141, 19, 35, 59, 63, 235, 116, 62, 235, 36, + 63, 205, 82, 234, 98, 184, 27, 130, 181, 12, 39, 100, 110, 213, 118, 47, + 215, 93, 196, 221, 216, 192, 242, 0, 203, 5, 1, 157, 103, 181, 146, 246, + 252, 130, 28, 73, 71, 154, 180, 134, 64, 41, 46, 172, 179, 183, 196, + 190, + ]), + short: new Uint8Array([ + 23, 56, 17, 63, 90, 187, 62, 229, 50, 14, 225, 138, 162, 102, 195, 97, + 122, 116, 117, 219, 216, 237, 154, 152, 89, 148, 253, 221, 97, 18, 173, + 153, 158, 200, 226, 235, 223, 234, 251, 150, 231, 111, 107, 179, 163, + 173, 186, 67, 218, 96, 240, 12, 209, 36, 150, 223, 90, 243, 226, 138, + 230, 211, 222, 66, + ]), + medium: new Uint8Array([ + 65, 70, 193, 61, 134, 217, 188, 24, 107, 11, 48, 154, 182, 161, 36, 238, + 12, 116, 186, 38, 184, 198, 13, 204, 123, 62, 213, 5, 150, 154, 168, + 209, 144, 40, 198, 49, 121, 153, 160, 133, 177, 230, 182, 167, 133, 206, + 79, 246, 50, 174, 178, 116, 147, 34, 126, 68, 35, 47, 183, 179, 149, 33, + 65, 123, + ]), + }, + }, +}; + +// Test cSHAKE digest algorithms with variable output lengths +Object.keys(digestedData).forEach(function (alg) { + digestLengths.forEach(function (length) { + Object.keys(sourceData).forEach(function (size) { + promise_test(function (test) { + return crypto.subtle + .digest({ name: alg, length: length }, sourceData[size]) + .then(function (result) { + assert_true( + equalBuffers(result, digestedData[alg][length][size]), + 'digest matches expected' + ); + }); + }, alg + ' with ' + length + ' bit output and ' + size + ' source data'); + + promise_test(function (test) { + var buffer = new Uint8Array(sourceData[size]); + return crypto.subtle + .digest({ name: alg, length: length }, buffer) + .then(function (result) { + // Alter the buffer after calling digest + if (buffer.length > 0) { + buffer[0] = ~buffer[0]; + } + assert_true( + equalBuffers(result, digestedData[alg][length][size]), + 'digest matches expected' + ); + }); + }, alg + + ' with ' + + length + + ' bit output and ' + + size + + ' source data and altered buffer after call'); + }); + }); +}); + +function equalBuffers(a, b) { + if (a.byteLength !== b.byteLength) { + return false; + } + var aBytes = new Uint8Array(a); + var bBytes = new Uint8Array(b); + for (var i = 0; i < a.byteLength; i++) { + if (aBytes[i] !== bBytes[i]) { + return false; + } + } + return true; +}