LibCrypto: Add SHAKE digest support

Introduce a new SHAKE hash wrapper in LibCrypto backed by OpenSSL.

Wire cSHAKE128 and cSHAKE256 into WebCrypto.

Note that cSHAKE with non-empty functionName or customization is
currently rejected due to OpenSSL EVP limitations.

This fixes WPT:
WebCryptoAPI/digest/cshake.tentative.https.any.html
This commit is contained in:
mikiubo 2026-01-16 23:52:12 +01:00 committed by Tim Flynn
parent a9bd0ca96c
commit cd8465a6b5
9 changed files with 485 additions and 13 deletions

View file

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

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2025, mikiubo <michele.uboldi@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ByteBuffer.h>
#include <LibCrypto/Hash/SHAKE.h>
#include <LibCrypto/OpenSSL.h>
#include <openssl/evp.h>
namespace Crypto::Hash {
SHAKE::SHAKE(SHAKEKind kind)
{
m_md = (kind == SHAKEKind::CSHAKE128) ? EVP_shake128() : EVP_shake256();
}
ErrorOr<ByteBuffer> SHAKE::digest(
ReadonlyBytes data,
u32 length,
Optional<ReadonlyBytes> customization,
Optional<ReadonlyBytes> 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;
}
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2025, mikiubo <michele.uboldi@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteBuffer.h>
#include <AK/Error.h>
#include <AK/Noncopyable.h>
#include <LibCrypto/OpenSSLForward.h>
namespace Crypto::Hash {
enum class SHAKEKind {
CSHAKE128,
CSHAKE256
};
class SHAKE {
AK_MAKE_NONCOPYABLE(SHAKE);
public:
explicit SHAKE(SHAKEKind);
~SHAKE() = default;
ErrorOr<ByteBuffer> digest(
ReadonlyBytes data,
u32 length,
Optional<ReadonlyBytes> customization,
Optional<ReadonlyBytes> function_name) const;
private:
EVP_MD const* m_md;
};
}

View file

@ -25,6 +25,7 @@
#include <LibCrypto/Hash/PBKDF2.h>
#include <LibCrypto/Hash/SHA1.h>
#include <LibCrypto/Hash/SHA2.h>
#include <LibCrypto/Hash/SHAKE.h>
#include <LibCrypto/PK/MLDSA.h>
#include <LibCrypto/PK/MLKEM.h>
#include <LibCrypto/PK/RSA.h>
@ -631,6 +632,19 @@ JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> Ed448Params::from_value(JS
return adopt_own<AlgorithmParams>(*new Ed448Params { maybe_context });
}
static inline JS::ThrowCompletionOr<Optional<ByteBuffer>> 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::TypeError>(JS::ErrorType::NotAnObjectOfType, "BufferSource");
return TRY_OR_THROW_OOM(vm, WebIDL::get_buffer_source_copy(value.as_object()));
}
Argon2Params::~Argon2Params() = default;
JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> Argon2Params::from_value(JS::VM& vm, JS::Value value)
@ -664,23 +678,34 @@ JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> Argon2Params::from_value(J
maybe_version = TRY(WebIDL::convert_to_int<WebIDL::Octet>(vm, version_value, WebIDL::EnforceRange::Yes, WebIDL::Clamp::No));
}
auto const extract_optional_buffer_source_value = [&](auto const& name) -> JS::ThrowCompletionOr<Optional<ByteBuffer>> {
auto maybe_buffer = Optional<ByteBuffer> {};
if (MUST(object.has_property(name))) {
auto key_value = TRY(object.get(name));
if (!key_value.is_object() || !(is<JS::TypedArrayBase>(key_value.as_object()) || is<JS::ArrayBuffer>(key_value.as_object()) || is<JS::DataView>(key_value.as_object())))
return vm.throw_completion<JS::TypeError>(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<AlgorithmParams>(*new Argon2Params { nonce, parallelism, memory, passes, maybe_version, secret_value, associated_data });
}
CShakeParams::~CShakeParams() = default;
JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> 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::TypeError>(JS::ErrorType::MissingRequiredProperty, "length");
}
auto const length_value = TRY(object.get("length"_utf16_fly_string));
auto const length = TRY(WebIDL::convert_to_int<WebIDL::UnsignedLong>(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<AlgorithmParams>(*new CShakeParams { length, function_name, customization });
}
// https://w3c.github.io/webcrypto/#rsa-oaep-operations
WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> RSAOAEP::encrypt(AlgorithmParams const& params, GC::Ref<CryptoKey> key, ByteBuffer const& plaintext)
{
@ -9634,4 +9659,47 @@ WebIDL::ExceptionOr<JS::Value> Argon2::get_key_length(AlgorithmParams const&)
return JS::js_null();
}
// https://wicg.github.io/webcrypto-modern-algos/#cshake-operations-digest
WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> CShake::digest(AlgorithmParams const& params, ByteBuffer const& data)
{
auto const& normalized_algorithm = static_cast<CShakeParams const&>(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());
}
}

View file

@ -739,6 +739,18 @@ private:
}
};
class CShake : public AlgorithmMethods {
public:
virtual WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> digest(AlgorithmParams const&, ByteBuffer const&) override;
static NonnullOwnPtr<AlgorithmMethods> 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<NonnullOwnPtr<AlgorithmParams>> 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<ByteBuffer> function_name, Optional<ByteBuffer> customization)
: length(length)
, function_name(move(function_name))
, customization(move(customization))
{
}
u32 length;
Optional<ByteBuffer> function_name;
Optional<ByteBuffer> customization;
static JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> from_value(JS::VM&, JS::Value);
};
ErrorOr<String> base64_url_uint_encode(::Crypto::UnsignedBigInteger);
WebIDL::ExceptionOr<ByteBuffer> 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);

View file

@ -1547,6 +1547,10 @@ SupportedAlgorithmsMap const& supported_algorithms()
define_an_algorithm<SHA>("digest"_string, "SHA3-384"_string);
define_an_algorithm<SHA>("digest"_string, "SHA3-512"_string);
// https://wicg.github.io/webcrypto-modern-algos/#cshake-registration
define_an_algorithm<CShake, CShakeParams>("digest"_string, "cSHAKE128"_string);
define_an_algorithm<CShake, CShakeParams>("digest"_string, "cSHAKE256"_string);
// https://w3c.github.io/webcrypto/#hkdf-registration
define_an_algorithm<HKDF, HKDFParams>("deriveBits"_string, "HKDF"_string);
define_an_algorithm<HKDF>("importKey"_string, "HKDF"_string);

View file

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

View file

@ -0,0 +1,16 @@
<!doctype html>
<meta charset=utf-8>
<title>WebCryptoAPI: digest() cSHAKE algorithms</title>
<meta name="timeout" content="long">
<script>
self.GLOBAL = {
isWindow: function() { return true; },
isWorker: function() { return false; },
isShadowRealm: function() { return false; },
};
</script>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<div id=log></div>
<script src="../../WebCryptoAPI/digest/cshake.tentative.https.any.js"></script>

View file

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