From e6ca20c7a7dee0e88baf581290afdb4116dfda12 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Tue, 14 Apr 2026 19:14:59 -0700 Subject: [PATCH 1/9] Update BoringSSL and add SHA3 support Bumps BoringSSL to oven-sh/boringssl@0c5fce43b (synced with upstream beafe3db1). Adds SHA3-256/384/512 to crypto.subtle.digest and HMAC, plus sha3-224/256/384/512 to node:crypto createHash/createHmac. Updates the Zig env_md_ctx_st struct to match the new EVP_MAX_MD_DATA_SIZE (240, was 208), which is required since the md_data buffer now holds the larger Keccak state. --- scripts/build/deps/boringssl.ts | 2 +- src/bun.js/api/crypto/EVP.zig | 4 + src/bun.js/bindings/AsymmetricKeyValue.cpp | 3 + .../webcore/SerializedScriptValue.cpp | 23 +++- .../webcrypto/CryptoAlgorithmHMAC.cpp | 8 ++ .../webcrypto/CryptoAlgorithmIdentifier.h | 3 + .../CryptoAlgorithmRegistryOpenSSL.cpp | 4 + .../webcrypto/CryptoAlgorithmSHA3.cpp | 90 +++++++++++++ .../bindings/webcrypto/CryptoAlgorithmSHA3.h | 72 +++++++++++ .../bindings/webcrypto/CryptoDigest.cpp | 42 ++++++ src/bun.js/bindings/webcrypto/CryptoDigest.h | 3 + .../bindings/webcrypto/CryptoKeyHMAC.cpp | 6 + .../bindings/webcrypto/OpenSSLUtilities.cpp | 6 + .../bindings/webcrypto/SubtleCrypto.cpp | 6 + src/deps/boringssl.translated.zig | 8 +- test/js/web/crypto/web-crypto-sha3.test.ts | 121 ++++++++++++++++++ 16 files changed, 397 insertions(+), 4 deletions(-) create mode 100644 src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3.cpp create mode 100644 src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3.h create mode 100644 test/js/web/crypto/web-crypto-sha3.test.ts diff --git a/scripts/build/deps/boringssl.ts b/scripts/build/deps/boringssl.ts index 0930d0ed07d..e1e12d494d7 100644 --- a/scripts/build/deps/boringssl.ts +++ b/scripts/build/deps/boringssl.ts @@ -5,7 +5,7 @@ import type { Dependency } from "../source.ts"; -const BORINGSSL_COMMIT = "4f4f5ef8ebc6e23cbf393428f0ab1b526773f7ac"; +const BORINGSSL_COMMIT = "0c5fce43b7ed5eb6001487ee48ac65766f5ddcd1"; export const boringssl: Dependency = { name: "boringssl", diff --git a/src/bun.js/api/crypto/EVP.zig b/src/bun.js/api/crypto/EVP.zig index 2d3f3b088fa..9e3de48a454 100644 --- a/src/bun.js/api/crypto/EVP.zig +++ b/src/bun.js/api/crypto/EVP.zig @@ -54,6 +54,10 @@ pub const Algorithm = enum { .sha512 => BoringSSL.EVP_sha512(), .@"sha512-224" => BoringSSL.EVP_sha512_224(), .@"sha512-256" => BoringSSL.EVP_sha512_256(), + .@"sha3-224" => BoringSSL.EVP_sha3_224(), + .@"sha3-256" => BoringSSL.EVP_sha3_256(), + .@"sha3-384" => BoringSSL.EVP_sha3_384(), + .@"sha3-512" => BoringSSL.EVP_sha3_512(), else => null, }; } diff --git a/src/bun.js/bindings/AsymmetricKeyValue.cpp b/src/bun.js/bindings/AsymmetricKeyValue.cpp index 0e7cb6744c4..d72f9abce0b 100644 --- a/src/bun.js/bindings/AsymmetricKeyValue.cpp +++ b/src/bun.js/bindings/AsymmetricKeyValue.cpp @@ -127,6 +127,9 @@ AsymmetricKeyValue::AsymmetricKeyValue(WebCore::CryptoKey& cryptoKey) case CryptoAlgorithmIdentifier::SHA_256: case CryptoAlgorithmIdentifier::SHA_384: case CryptoAlgorithmIdentifier::SHA_512: + case CryptoAlgorithmIdentifier::SHA3_256: + case CryptoAlgorithmIdentifier::SHA3_384: + case CryptoAlgorithmIdentifier::SHA3_512: case CryptoAlgorithmIdentifier::HKDF: case CryptoAlgorithmIdentifier::PBKDF2: case CryptoAlgorithmIdentifier::None: diff --git a/src/bun.js/bindings/webcore/SerializedScriptValue.cpp b/src/bun.js/bindings/webcore/SerializedScriptValue.cpp index 673ac650323..3125ee8411a 100644 --- a/src/bun.js/bindings/webcore/SerializedScriptValue.cpp +++ b/src/bun.js/bindings/webcore/SerializedScriptValue.cpp @@ -520,9 +520,12 @@ enum class CryptoAlgorithmIdentifierTag { PBKDF2 = 21, ED25519 = 22, X25519 = 23, + SHA3_256 = 24, + SHA3_384 = 25, + SHA3_512 = 26, }; -const uint8_t cryptoAlgorithmIdentifierTagMaximumValue = 22; +const uint8_t cryptoAlgorithmIdentifierTagMaximumValue = 26; static unsigned countUsages(CryptoKeyUsageBitmap usages) { @@ -2394,6 +2397,15 @@ class CloneSerializer : public CloneBase { case CryptoAlgorithmIdentifier::SHA_512: write(CryptoAlgorithmIdentifierTag::SHA_512); break; + case CryptoAlgorithmIdentifier::SHA3_256: + write(CryptoAlgorithmIdentifierTag::SHA3_256); + break; + case CryptoAlgorithmIdentifier::SHA3_384: + write(CryptoAlgorithmIdentifierTag::SHA3_384); + break; + case CryptoAlgorithmIdentifier::SHA3_512: + write(CryptoAlgorithmIdentifierTag::SHA3_512); + break; case CryptoAlgorithmIdentifier::HKDF: write(CryptoAlgorithmIdentifierTag::HKDF); break; @@ -3880,6 +3892,15 @@ class CloneDeserializer : public CloneBase { case CryptoAlgorithmIdentifierTag::SHA_512: result = CryptoAlgorithmIdentifier::SHA_512; break; + case CryptoAlgorithmIdentifierTag::SHA3_256: + result = CryptoAlgorithmIdentifier::SHA3_256; + break; + case CryptoAlgorithmIdentifierTag::SHA3_384: + result = CryptoAlgorithmIdentifier::SHA3_384; + break; + case CryptoAlgorithmIdentifierTag::SHA3_512: + result = CryptoAlgorithmIdentifier::SHA3_512; + break; case CryptoAlgorithmIdentifierTag::HKDF: result = CryptoAlgorithmIdentifier::HKDF; break; diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmHMAC.cpp b/src/bun.js/bindings/webcrypto/CryptoAlgorithmHMAC.cpp index f0c036ac8cd..2ffec58a851 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmHMAC.cpp +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmHMAC.cpp @@ -125,6 +125,10 @@ void CryptoAlgorithmHMAC::importKey(CryptoKeyFormat format, KeyData&& data, cons return alg.isNull() || alg == ALG384; case CryptoAlgorithmIdentifier::SHA_512: return alg.isNull() || alg == ALG512; + case CryptoAlgorithmIdentifier::SHA3_256: + case CryptoAlgorithmIdentifier::SHA3_384: + case CryptoAlgorithmIdentifier::SHA3_512: + return alg.isNull(); default: return false; } @@ -178,6 +182,10 @@ void CryptoAlgorithmHMAC::exportKey(CryptoKeyFormat format, Ref&& key case CryptoAlgorithmIdentifier::SHA_512: jwk.alg = String(ALG512); break; + case CryptoAlgorithmIdentifier::SHA3_256: + case CryptoAlgorithmIdentifier::SHA3_384: + case CryptoAlgorithmIdentifier::SHA3_512: + break; default: ASSERT_NOT_REACHED(); } diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmIdentifier.h b/src/bun.js/bindings/webcrypto/CryptoAlgorithmIdentifier.h index 8f0ef468e8e..75567bf2fe2 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmIdentifier.h +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmIdentifier.h @@ -48,6 +48,9 @@ enum class CryptoAlgorithmIdentifier : uint8_t { SHA_256, SHA_384, SHA_512, + SHA3_256, + SHA3_384, + SHA3_512, HKDF, PBKDF2, Ed25519, diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmRegistryOpenSSL.cpp b/src/bun.js/bindings/webcrypto/CryptoAlgorithmRegistryOpenSSL.cpp index b0d12c4970b..0ae50fe162e 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmRegistryOpenSSL.cpp +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmRegistryOpenSSL.cpp @@ -48,6 +48,7 @@ #include "CryptoAlgorithmSHA256.h" #include "CryptoAlgorithmSHA384.h" #include "CryptoAlgorithmSHA512.h" +#include "CryptoAlgorithmSHA3.h" #include "CryptoAlgorithmX25519.h" namespace WebCore { @@ -73,6 +74,9 @@ void CryptoAlgorithmRegistry::platformRegisterAlgorithms() registerAlgorithmWithAlternativeName(); registerAlgorithmWithAlternativeName(); registerAlgorithmWithAlternativeName(); + registerAlgorithm(); + registerAlgorithm(); + registerAlgorithm(); registerAlgorithm(); registerAlgorithm(); } diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3.cpp b/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3.cpp new file mode 100644 index 00000000000..5ca78400bcf --- /dev/null +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2024 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "CryptoAlgorithmSHA3.h" + +#if ENABLE(WEB_CRYPTO) + +#include "CryptoDigest.h" +#include "ScriptExecutionContext.h" + +namespace WebCore { + +static void dispatchDigest(PAL::CryptoDigest::Algorithm algorithm, + Vector&& message, CryptoAlgorithm::VectorCallback&& callback, + CryptoAlgorithm::ExceptionCallback&& exceptionCallback, + ScriptExecutionContext& context, WorkQueue& workQueue) +{ + auto digest = PAL::CryptoDigest::create(algorithm); + if (!digest) { + exceptionCallback(OperationError, ""_s); + return; + } + + if (message.size() < 64) { + auto moved = WTF::move(message); + digest->addBytes(moved.begin(), moved.size()); + auto result = digest->computeHash(); + ScriptExecutionContext::postTaskTo(context.identifier(), + [callback = WTF::move(callback), result = WTF::move(result)](auto&) { + callback(result); + }); + return; + } + + workQueue.dispatch(context.globalObject(), + [digest = WTF::move(digest), message = WTF::move(message), + callback = WTF::move(callback), + contextIdentifier = context.identifier()]() mutable { + digest->addBytes(message.begin(), message.size()); + auto result = digest->computeHash(); + ScriptExecutionContext::postTaskTo(contextIdentifier, + [callback = WTF::move(callback), result = WTF::move(result)](auto&) { + callback(result); + }); + }); +} + +#define DEFINE_SHA3(ClassName, DigestAlgo) \ + Ref ClassName::create() { return adoptRef(*new ClassName); } \ + CryptoAlgorithmIdentifier ClassName::identifier() const { return s_identifier; } \ + void ClassName::digest(Vector&& message, VectorCallback&& callback, \ + ExceptionCallback&& exceptionCallback, ScriptExecutionContext& context, \ + WorkQueue& workQueue) \ + { \ + dispatchDigest(DigestAlgo, WTF::move(message), WTF::move(callback), \ + WTF::move(exceptionCallback), context, workQueue); \ + } + +DEFINE_SHA3(CryptoAlgorithmSHA3_256, PAL::CryptoDigest::Algorithm::SHA3_256) +DEFINE_SHA3(CryptoAlgorithmSHA3_384, PAL::CryptoDigest::Algorithm::SHA3_384) +DEFINE_SHA3(CryptoAlgorithmSHA3_512, PAL::CryptoDigest::Algorithm::SHA3_512) + +#undef DEFINE_SHA3 + +} // namespace WebCore + +#endif // ENABLE(WEB_CRYPTO) diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3.h b/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3.h new file mode 100644 index 00000000000..549dc5f5794 --- /dev/null +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2024 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(WEB_CRYPTO) + +#include "CryptoAlgorithm.h" + +namespace WebCore { + +class CryptoAlgorithmSHA3_256 final : public CryptoAlgorithm { +public: + static constexpr ASCIILiteral s_name = "SHA3-256"_s; + static constexpr CryptoAlgorithmIdentifier s_identifier = CryptoAlgorithmIdentifier::SHA3_256; + static Ref create(); + +private: + CryptoAlgorithmSHA3_256() = default; + CryptoAlgorithmIdentifier identifier() const final; + void digest(Vector&&, VectorCallback&&, ExceptionCallback&&, ScriptExecutionContext&, WorkQueue&) final; +}; + +class CryptoAlgorithmSHA3_384 final : public CryptoAlgorithm { +public: + static constexpr ASCIILiteral s_name = "SHA3-384"_s; + static constexpr CryptoAlgorithmIdentifier s_identifier = CryptoAlgorithmIdentifier::SHA3_384; + static Ref create(); + +private: + CryptoAlgorithmSHA3_384() = default; + CryptoAlgorithmIdentifier identifier() const final; + void digest(Vector&&, VectorCallback&&, ExceptionCallback&&, ScriptExecutionContext&, WorkQueue&) final; +}; + +class CryptoAlgorithmSHA3_512 final : public CryptoAlgorithm { +public: + static constexpr ASCIILiteral s_name = "SHA3-512"_s; + static constexpr CryptoAlgorithmIdentifier s_identifier = CryptoAlgorithmIdentifier::SHA3_512; + static Ref create(); + +private: + CryptoAlgorithmSHA3_512() = default; + CryptoAlgorithmIdentifier identifier() const final; + void digest(Vector&&, VectorCallback&&, ExceptionCallback&&, ScriptExecutionContext&, WorkQueue&) final; +}; + +} // namespace WebCore + +#endif // ENABLE(WEB_CRYPTO) diff --git a/src/bun.js/bindings/webcrypto/CryptoDigest.cpp b/src/bun.js/bindings/webcrypto/CryptoDigest.cpp index 0c8f3aa5e8f..eed9b1315fe 100644 --- a/src/bun.js/bindings/webcrypto/CryptoDigest.cpp +++ b/src/bun.js/bindings/webcrypto/CryptoDigest.cpp @@ -26,6 +26,7 @@ #include "config.h" #include "CryptoDigest.h" +#include #include namespace { @@ -103,6 +104,38 @@ struct CryptoDigestContextImpl : public CryptoDigestContext { SHAContext m_context; }; +struct CryptoDigestContextEVP : public CryptoDigestContext { + WTF_DEPRECATED_MAKE_STRUCT_FAST_ALLOCATED(CryptoDigestContextEVP); + + static std::unique_ptr create(const EVP_MD* md) + { + return makeUnique(md); + } + + explicit CryptoDigestContextEVP(const EVP_MD* md) + { + EVP_MD_CTX_init(&m_context); + EVP_DigestInit_ex(&m_context, md, nullptr); + } + + ~CryptoDigestContextEVP() override { EVP_MD_CTX_cleanup(&m_context); } + + void addBytes(const void* input, size_t length) override + { + EVP_DigestUpdate(&m_context, input, length); + } + + Vector computeHash() override + { + Vector result(EVP_MD_CTX_size(&m_context)); + EVP_DigestFinal_ex(&m_context, result.begin(), nullptr); + return result; + } + +private: + EVP_MD_CTX m_context; +}; + CryptoDigest::CryptoDigest() { } @@ -131,6 +164,15 @@ std::unique_ptr CryptoDigest::create(CryptoDigest::Algorithm algor case CryptoDigest::Algorithm::SHA_512: digest->m_context = CryptoDigestContextImpl::create(); return digest; + case CryptoDigest::Algorithm::SHA3_256: + digest->m_context = CryptoDigestContextEVP::create(EVP_sha3_256()); + return digest; + case CryptoDigest::Algorithm::SHA3_384: + digest->m_context = CryptoDigestContextEVP::create(EVP_sha3_384()); + return digest; + case CryptoDigest::Algorithm::SHA3_512: + digest->m_context = CryptoDigestContextEVP::create(EVP_sha3_512()); + return digest; } return nullptr; diff --git a/src/bun.js/bindings/webcrypto/CryptoDigest.h b/src/bun.js/bindings/webcrypto/CryptoDigest.h index 5da849a1d33..12c3907e1a5 100644 --- a/src/bun.js/bindings/webcrypto/CryptoDigest.h +++ b/src/bun.js/bindings/webcrypto/CryptoDigest.h @@ -47,6 +47,9 @@ class CryptoDigest { SHA_256, SHA_384, SHA_512, + SHA3_256, + SHA3_384, + SHA3_512, }; PAL_EXPORT static std::unique_ptr create(Algorithm); PAL_EXPORT ~CryptoDigest(); diff --git a/src/bun.js/bindings/webcrypto/CryptoKeyHMAC.cpp b/src/bun.js/bindings/webcrypto/CryptoKeyHMAC.cpp index 5bde29b77e3..3ef96ae00fb 100644 --- a/src/bun.js/bindings/webcrypto/CryptoKeyHMAC.cpp +++ b/src/bun.js/bindings/webcrypto/CryptoKeyHMAC.cpp @@ -48,6 +48,12 @@ static size_t getKeyLengthFromHash(CryptoAlgorithmIdentifier hash) case CryptoAlgorithmIdentifier::SHA_384: case CryptoAlgorithmIdentifier::SHA_512: return 1024; + case CryptoAlgorithmIdentifier::SHA3_256: + return 1088; + case CryptoAlgorithmIdentifier::SHA3_384: + return 832; + case CryptoAlgorithmIdentifier::SHA3_512: + return 576; default: ASSERT_NOT_REACHED(); return 0; diff --git a/src/bun.js/bindings/webcrypto/OpenSSLUtilities.cpp b/src/bun.js/bindings/webcrypto/OpenSSLUtilities.cpp index 74bc0523241..c5be62c262f 100644 --- a/src/bun.js/bindings/webcrypto/OpenSSLUtilities.cpp +++ b/src/bun.js/bindings/webcrypto/OpenSSLUtilities.cpp @@ -45,6 +45,12 @@ const EVP_MD* digestAlgorithm(CryptoAlgorithmIdentifier hashFunction) return EVP_sha384(); case CryptoAlgorithmIdentifier::SHA_512: return EVP_sha512(); + case CryptoAlgorithmIdentifier::SHA3_256: + return EVP_sha3_256(); + case CryptoAlgorithmIdentifier::SHA3_384: + return EVP_sha3_384(); + case CryptoAlgorithmIdentifier::SHA3_512: + return EVP_sha3_512(); default: return nullptr; } diff --git a/src/bun.js/bindings/webcrypto/SubtleCrypto.cpp b/src/bun.js/bindings/webcrypto/SubtleCrypto.cpp index 5f57e71a2d4..cdbfc384d95 100644 --- a/src/bun.js/bindings/webcrypto/SubtleCrypto.cpp +++ b/src/bun.js/bindings/webcrypto/SubtleCrypto.cpp @@ -204,6 +204,9 @@ static ExceptionOr> normalizeCryptoAl case CryptoAlgorithmIdentifier::SHA_256: case CryptoAlgorithmIdentifier::SHA_384: case CryptoAlgorithmIdentifier::SHA_512: + case CryptoAlgorithmIdentifier::SHA3_256: + case CryptoAlgorithmIdentifier::SHA3_384: + case CryptoAlgorithmIdentifier::SHA3_512: result = makeUnique(params); break; default: @@ -381,6 +384,9 @@ static ExceptionOr> normalizeCryptoAl case CryptoAlgorithmIdentifier::SHA_256: case CryptoAlgorithmIdentifier::SHA_384: case CryptoAlgorithmIdentifier::SHA_512: + case CryptoAlgorithmIdentifier::SHA3_256: + case CryptoAlgorithmIdentifier::SHA3_384: + case CryptoAlgorithmIdentifier::SHA3_512: return Exception { NotSupportedError }; case CryptoAlgorithmIdentifier::None: return Exception { NotSupportedError }; diff --git a/src/deps/boringssl.translated.zig b/src/deps/boringssl.translated.zig index 156f2c203a7..b7e886318d1 100644 --- a/src/deps/boringssl.translated.zig +++ b/src/deps/boringssl.translated.zig @@ -343,7 +343,7 @@ pub const struct_evp_md_pctx_ops = opaque {}; pub const struct_env_md_ctx_st = extern struct { // md_data contains the hash-specific context md_data: extern union { - data: [208]u8, + data: [240]u8, alignment: u64, }, // digest is the underlying digest function, or NULL if not set @@ -6377,7 +6377,7 @@ pub const CIPHER_R_NO_DIRECTION_SET = @as(c_int, 124); pub const CIPHER_R_INVALID_NONCE = @as(c_int, 125); pub const OPENSSL_HEADER_DIGEST_H = ""; pub const EVP_MAX_MD_SIZE = @as(c_int, 64); -pub const EVP_MAX_MD_BLOCK_SIZE = @as(c_int, 128); +pub const EVP_MAX_MD_BLOCK_SIZE = @as(c_int, 144); pub const EVP_MD_FLAG_PKEY_DIGEST = @as(c_int, 1); pub const EVP_MD_FLAG_DIGALGID_ABSENT = @as(c_int, 2); pub const EVP_MD_FLAG_XOF = @as(c_int, 4); @@ -18756,6 +18756,10 @@ pub extern fn EVP_sha384() *const EVP_MD; pub extern fn EVP_sha512() *const EVP_MD; pub extern fn EVP_sha512_224() *const EVP_MD; pub extern fn EVP_sha512_256() *const EVP_MD; +pub extern fn EVP_sha3_224() *const EVP_MD; +pub extern fn EVP_sha3_256() *const EVP_MD; +pub extern fn EVP_sha3_384() *const EVP_MD; +pub extern fn EVP_sha3_512() *const EVP_MD; pub extern fn EVP_blake2b256() *const EVP_MD; pub extern fn EVP_blake2b512() *const EVP_MD; diff --git a/test/js/web/crypto/web-crypto-sha3.test.ts b/test/js/web/crypto/web-crypto-sha3.test.ts new file mode 100644 index 00000000000..0cb4beb54d7 --- /dev/null +++ b/test/js/web/crypto/web-crypto-sha3.test.ts @@ -0,0 +1,121 @@ +import { describe, expect, it } from "bun:test"; + +const hex = (buf: ArrayBuffer) => + [...new Uint8Array(buf)].map(b => b.toString(16).padStart(2, "0")).join(""); + +// NIST FIPS 202 test vectors +const vectors = [ + ["SHA3-256", "", "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a"], + ["SHA3-256", "abc", "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532"], + [ + "SHA3-384", + "", + "0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004", + ], + [ + "SHA3-384", + "abc", + "ec01498288516fc926459f58e2c6ad8df9b473cb0fc08c2596da7cf0e49be4b298d88cea927ac7f539f1edf228376d25", + ], + [ + "SHA3-512", + "", + "a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26", + ], + [ + "SHA3-512", + "abc", + "b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e10e116e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0", + ], +] as const; + +describe("crypto.subtle.digest SHA-3", () => { + for (const [alg, input, expected] of vectors) { + it(`${alg}(${JSON.stringify(input)})`, async () => { + const buf = await crypto.subtle.digest(alg, new TextEncoder().encode(input)); + expect(hex(buf)).toBe(expected); + }); + } + + it("SHA3-256 large input (>64 bytes, async path)", async () => { + const input = new TextEncoder().encode("a".repeat(1_000_000)); + const buf = await crypto.subtle.digest("SHA3-256", input); + expect(hex(buf)).toBe("5c8875ae474a3634ba4fd55ec85bffd661f32aca75c6d699d0cdcb6c115891c1"); + }); + + it("rejects unknown digest", async () => { + expect(crypto.subtle.digest("SHA3-1024" as any, new Uint8Array())).rejects.toThrow(); + }); +}); + +describe("HMAC with SHA-3", () => { + it("generateKey + sign + verify with SHA3-256", async () => { + const key = await crypto.subtle.generateKey( + { name: "HMAC", hash: "SHA3-256" }, + true, + ["sign", "verify"], + ); + const data = new TextEncoder().encode("hello world"); + const sig = await crypto.subtle.sign("HMAC", key, data); + expect(sig.byteLength).toBe(32); + expect(await crypto.subtle.verify("HMAC", key, sig, data)).toBe(true); + + const tampered = new Uint8Array(sig); + tampered[0] ^= 0xff; + expect(await crypto.subtle.verify("HMAC", key, tampered, data)).toBe(false); + }); + + it("HMAC-SHA3-256 against NIST vector", async () => { + const keyBytes = new Uint8Array(32).map((_, i) => i); + const key = await crypto.subtle.importKey( + "raw", + keyBytes, + { name: "HMAC", hash: "SHA3-256" }, + false, + ["sign"], + ); + const msg = new TextEncoder().encode("Sample message for keylen { + const key = await crypto.subtle.generateKey( + { name: "HMAC", hash: "SHA3-384" }, + true, + ["sign"], + ); + const raw = await crypto.subtle.exportKey("raw", key); + expect(raw.byteLength).toBe(104); + }); +}); + +describe("node:crypto SHA-3", () => { + it("createHash sha3-256", () => { + const { createHash } = require("node:crypto"); + expect(createHash("sha3-256").update("abc").digest("hex")).toBe( + "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532", + ); + }); + + it("createHash sha3-384", () => { + const { createHash } = require("node:crypto"); + expect(createHash("sha3-384").update("abc").digest("hex")).toBe( + "ec01498288516fc926459f58e2c6ad8df9b473cb0fc08c2596da7cf0e49be4b298d88cea927ac7f539f1edf228376d25", + ); + }); + + it("createHmac sha3-512", () => { + const { createHmac } = require("node:crypto"); + const out = createHmac("sha3-512", Buffer.from("key")).update("data").digest("hex"); + expect(out.length).toBe(128); + }); + + it("getHashes includes sha3", () => { + const { getHashes } = require("node:crypto"); + const hashes = getHashes(); + expect(hashes).toContain("sha3-256"); + expect(hashes).toContain("sha3-384"); + expect(hashes).toContain("sha3-512"); + }); +}); From 14bef0860245ca9beded6c38b5fb986a00cc3f88 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 02:16:48 +0000 Subject: [PATCH 2/9] [autofix.ci] apply automated fixes --- test/js/web/crypto/web-crypto-sha3.test.ts | 29 ++++------------------ 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/test/js/web/crypto/web-crypto-sha3.test.ts b/test/js/web/crypto/web-crypto-sha3.test.ts index 0cb4beb54d7..186faa5f2b1 100644 --- a/test/js/web/crypto/web-crypto-sha3.test.ts +++ b/test/js/web/crypto/web-crypto-sha3.test.ts @@ -1,17 +1,12 @@ import { describe, expect, it } from "bun:test"; -const hex = (buf: ArrayBuffer) => - [...new Uint8Array(buf)].map(b => b.toString(16).padStart(2, "0")).join(""); +const hex = (buf: ArrayBuffer) => [...new Uint8Array(buf)].map(b => b.toString(16).padStart(2, "0")).join(""); // NIST FIPS 202 test vectors const vectors = [ ["SHA3-256", "", "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a"], ["SHA3-256", "abc", "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532"], - [ - "SHA3-384", - "", - "0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004", - ], + ["SHA3-384", "", "0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004"], [ "SHA3-384", "abc", @@ -50,11 +45,7 @@ describe("crypto.subtle.digest SHA-3", () => { describe("HMAC with SHA-3", () => { it("generateKey + sign + verify with SHA3-256", async () => { - const key = await crypto.subtle.generateKey( - { name: "HMAC", hash: "SHA3-256" }, - true, - ["sign", "verify"], - ); + const key = await crypto.subtle.generateKey({ name: "HMAC", hash: "SHA3-256" }, true, ["sign", "verify"]); const data = new TextEncoder().encode("hello world"); const sig = await crypto.subtle.sign("HMAC", key, data); expect(sig.byteLength).toBe(32); @@ -67,24 +58,14 @@ describe("HMAC with SHA-3", () => { it("HMAC-SHA3-256 against NIST vector", async () => { const keyBytes = new Uint8Array(32).map((_, i) => i); - const key = await crypto.subtle.importKey( - "raw", - keyBytes, - { name: "HMAC", hash: "SHA3-256" }, - false, - ["sign"], - ); + const key = await crypto.subtle.importKey("raw", keyBytes, { name: "HMAC", hash: "SHA3-256" }, false, ["sign"]); const msg = new TextEncoder().encode("Sample message for keylen { - const key = await crypto.subtle.generateKey( - { name: "HMAC", hash: "SHA3-384" }, - true, - ["sign"], - ); + const key = await crypto.subtle.generateKey({ name: "HMAC", hash: "SHA3-384" }, true, ["sign"]); const raw = await crypto.subtle.exportKey("raw", key); expect(raw.byteLength).toBe(104); }); From 8ce301bc8e87ab3e6e1203835cda6cb1215817a9 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Tue, 14 Apr 2026 19:44:03 -0700 Subject: [PATCH 3/9] Update process.versions boringssl hash in test snapshot --- test/js/node/process/process.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/js/node/process/process.test.js b/test/js/node/process/process.test.js index 58ef76494d1..3e07fef8d5e 100644 --- a/test/js/node/process/process.test.js +++ b/test/js/node/process/process.test.js @@ -273,7 +273,7 @@ it("process.versions", () => { // These are the ACTUAL commits built into bun (not derived values, so // bumping a dep requires updating this test too). const expectedVersions = { - boringssl: "4f4f5ef8ebc6e23cbf393428f0ab1b526773f7ac", + boringssl: "0c5fce43b7ed5eb6001487ee48ac65766f5ddcd1", libarchive: "ded82291ab41d5e355831b96b0e1ff49e24d8939", mimalloc: "9a5e1f52cdf4662f9590b69de104a4469140796f", picohttpparser: "066d2b1e9ab820703db0837a7255d92d30f0c9f5", From 62a8a4d3df421a228ca45961e6fa08f032b0e8c5 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Tue, 14 Apr 2026 20:15:13 -0700 Subject: [PATCH 4/9] Update baseline allowlist for namespaced BoringSSL x25519 ADX symbols Upstream BoringSSL commit f7543e6db moved internal symbols into the bssl:: namespace. The x25519 ADX scalar multiply functions are runtime-gated by CRYPTO_is_BMI2_capable() && CRYPTO_is_ADX_capable() in crypto/curve25519/curve25519.cc. --- scripts/verify-baseline-static/allowlist-x64.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/verify-baseline-static/allowlist-x64.txt b/scripts/verify-baseline-static/allowlist-x64.txt index e9c20af9521..9cd64dde57c 100644 --- a/scripts/verify-baseline-static/allowlist-x64.txt +++ b/scripts/verify-baseline-static/allowlist-x64.txt @@ -584,11 +584,12 @@ rsaz_1024_sqr_avx2 [AVX, AVX2] # ---------------------------------------------------------------------------- # BoringSSL Curve25519. -# (3 symbols) +# (4 symbols) # ---------------------------------------------------------------------------- -fiat_curve25519_adx_mul [ADX, BMI2] -fiat_curve25519_adx_square [ADX, BMI2] -x25519_scalar_mult_adx [BMI2] +fiat_curve25519_adx_mul [ADX, BMI2] +fiat_curve25519_adx_square [ADX, BMI2] +_ZN4bssl22x25519_scalar_mult_adxEPhPKhS2_ [ADX, BMI2] +_ZN4bssl29x25519_ge_scalarmult_base_adxEPA32_hPKh [ADX, BMI2] # ---------------------------------------------------------------------------- From 22a55d1d1a43a0f8bd8f6dd8b0b93816290912b4 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Tue, 14 Apr 2026 20:47:44 -0700 Subject: [PATCH 5/9] Allow SHA3 digests in PBKDF2 now that BoringSSL supports them --- src/bun.js/api/crypto/PBKDF2.zig | 2 +- test/js/web/crypto/web-crypto-sha3.test.ts | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/bun.js/api/crypto/PBKDF2.zig b/src/bun.js/api/crypto/PBKDF2.zig index b3199307929..36b2fb9f708 100644 --- a/src/bun.js/api/crypto/PBKDF2.zig +++ b/src/bun.js/api/crypto/PBKDF2.zig @@ -163,7 +163,7 @@ pub fn fromJS(globalThis: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame, is_asy invalid: { switch (try EVP.Algorithm.map.fromJSCaseInsensitive(globalThis, arg4) orelse break :invalid) { - .shake128, .shake256, .@"sha3-224", .@"sha3-256", .@"sha3-384", .@"sha3-512" => break :invalid, + .shake128, .shake256 => break :invalid, else => |alg| break :brk alg, } } diff --git a/test/js/web/crypto/web-crypto-sha3.test.ts b/test/js/web/crypto/web-crypto-sha3.test.ts index 186faa5f2b1..84397208bfd 100644 --- a/test/js/web/crypto/web-crypto-sha3.test.ts +++ b/test/js/web/crypto/web-crypto-sha3.test.ts @@ -99,4 +99,11 @@ describe("node:crypto SHA-3", () => { expect(hashes).toContain("sha3-384"); expect(hashes).toContain("sha3-512"); }); + + it("pbkdf2Sync sha3-256", () => { + const { pbkdf2Sync } = require("node:crypto"); + expect(pbkdf2Sync("pw", "salt", 1000, 32, "sha3-256").toString("hex")).toBe( + "53b1bc246a311cbf8e2c907d96bcb209ddf95cd9f0a74fdcbab033b6ea82e30a", + ); + }); }); From 8d39b78cc629752cf1b02fb0bfad5b097707736d Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Tue, 14 Apr 2026 21:02:52 -0700 Subject: [PATCH 6/9] Address review: RSA+SHA3 JWK export, CryptoHasher HMAC, test fixes - Handle SHA3 in RSA-PSS/OAEP/RSASSA JWK import/export switches: omit alg field on export (no IETF-registered identifier exists), accept null alg on import. Prevents ASSERT_NOT_REACHED in debug builds when exporting RSA keys generated with SHA3 hash. - Remove stale ripemd160 HMAC rejection from CryptoHasher.zig - it works now that RIPEMD160 is in libcrypto. - Move sha3-* and ripemd160 from CryptoHasher's "unsupported" test list to the supported HMAC vectors (cross-validated against Python). - ASSERT on EVP_DigestInit_ex return in CryptoDigestContextEVP. - Fix missing await on rejects.toThrow(), use Buffer.alloc instead of string repeat, hoist node:crypto imports to module scope. --- src/bun.js/api/crypto/CryptoHasher.zig | 4 -- .../CryptoAlgorithmRSASSA_PKCS1_v1_5.cpp | 9 ++++ .../webcrypto/CryptoAlgorithmRSA_OAEP.cpp | 9 ++++ .../webcrypto/CryptoAlgorithmRSA_PSS.cpp | 9 ++++ .../bindings/webcrypto/CryptoDigest.cpp | 3 +- test/js/bun/util/bun-cryptohasher.test.ts | 17 ++++---- test/js/web/crypto/web-crypto-sha3.test.ts | 41 +++++++++++++++---- 7 files changed, 71 insertions(+), 21 deletions(-) diff --git a/src/bun.js/api/crypto/CryptoHasher.zig b/src/bun.js/api/crypto/CryptoHasher.zig index fd90be83ac0..53c93f885c4 100644 --- a/src/bun.js/api/crypto/CryptoHasher.zig +++ b/src/bun.js/api/crypto/CryptoHasher.zig @@ -273,10 +273,6 @@ pub const CryptoHasher = union(enum) { return CryptoHasher.new(brk: { if (hmac_key) |*key| { const chosen_algorithm = try algorithm_name.toEnumFromMap(globalThis, "algorithm", EVP.Algorithm, EVP.Algorithm.map); - if (chosen_algorithm == .ripemd160) { - // crashes at runtime. - return globalThis.throw("ripemd160 is not supported", .{}); - } break :brk .{ .hmac = HMAC.init(chosen_algorithm, key.slice()) orelse { diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmRSASSA_PKCS1_v1_5.cpp b/src/bun.js/bindings/webcrypto/CryptoAlgorithmRSASSA_PKCS1_v1_5.cpp index bc159ec11cb..8a6214ee598 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmRSASSA_PKCS1_v1_5.cpp +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmRSASSA_PKCS1_v1_5.cpp @@ -137,6 +137,11 @@ void CryptoAlgorithmRSASSA_PKCS1_v1_5::importKey(CryptoKeyFormat format, KeyData case CryptoAlgorithmIdentifier::SHA_512: isMatched = key.alg.isNull() || key.alg == ALG512; break; + case CryptoAlgorithmIdentifier::SHA3_256: + case CryptoAlgorithmIdentifier::SHA3_384: + case CryptoAlgorithmIdentifier::SHA3_512: + isMatched = key.alg.isNull(); + break; default: break; } @@ -208,6 +213,10 @@ void CryptoAlgorithmRSASSA_PKCS1_v1_5::exportKey(CryptoKeyFormat format, Ref&& case CryptoAlgorithmIdentifier::SHA_512: jwk.alg = String(ALG512); break; + case CryptoAlgorithmIdentifier::SHA3_256: + case CryptoAlgorithmIdentifier::SHA3_384: + case CryptoAlgorithmIdentifier::SHA3_512: + break; default: ASSERT_NOT_REACHED(); } diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmRSA_PSS.cpp b/src/bun.js/bindings/webcrypto/CryptoAlgorithmRSA_PSS.cpp index d6e585d6832..b858e896d32 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmRSA_PSS.cpp +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmRSA_PSS.cpp @@ -139,6 +139,11 @@ void CryptoAlgorithmRSA_PSS::importKey(CryptoKeyFormat format, KeyData&& data, c case CryptoAlgorithmIdentifier::SHA_512: isMatched = key.alg.isNull() || key.alg == ALG512; break; + case CryptoAlgorithmIdentifier::SHA3_256: + case CryptoAlgorithmIdentifier::SHA3_384: + case CryptoAlgorithmIdentifier::SHA3_512: + isMatched = key.alg.isNull(); + break; default: break; } @@ -210,6 +215,10 @@ void CryptoAlgorithmRSA_PSS::exportKey(CryptoKeyFormat format, Ref&& case CryptoAlgorithmIdentifier::SHA_512: jwk.alg = String(ALG512); break; + case CryptoAlgorithmIdentifier::SHA3_256: + case CryptoAlgorithmIdentifier::SHA3_384: + case CryptoAlgorithmIdentifier::SHA3_512: + break; default: ASSERT_NOT_REACHED(); } diff --git a/src/bun.js/bindings/webcrypto/CryptoDigest.cpp b/src/bun.js/bindings/webcrypto/CryptoDigest.cpp index eed9b1315fe..59c7b4e9bc7 100644 --- a/src/bun.js/bindings/webcrypto/CryptoDigest.cpp +++ b/src/bun.js/bindings/webcrypto/CryptoDigest.cpp @@ -115,7 +115,8 @@ struct CryptoDigestContextEVP : public CryptoDigestContext { explicit CryptoDigestContextEVP(const EVP_MD* md) { EVP_MD_CTX_init(&m_context); - EVP_DigestInit_ex(&m_context, md, nullptr); + int rc = EVP_DigestInit_ex(&m_context, md, nullptr); + ASSERT_UNUSED(rc, rc == 1); } ~CryptoDigestContextEVP() override { EVP_MD_CTX_cleanup(&m_context); } diff --git a/test/js/bun/util/bun-cryptohasher.test.ts b/test/js/bun/util/bun-cryptohasher.test.ts index 545c3f884e6..02ba0aa52ac 100644 --- a/test/js/bun/util/bun-cryptohasher.test.ts +++ b/test/js/bun/util/bun-cryptohasher.test.ts @@ -29,6 +29,13 @@ describe("HMAC", () => { "sha224": "d34c3a2647d4f82a4e6baeaa7d94379eafd931e0c16cbc44b4ba4d1e", "sha512-224": "af398c7f21f58e1377580227a89590d3ab8be52b31182fad9ec4d667", "sha512-256": "0ed15b2750a2a7281e96af006ab79e82ed54a7a2081bdb49e70a70d8c6bfeff0", + "sha3-224": "3dd0595758af01c6a9d662326acc3bc0c7e49b94573f74f800b6c114", + "sha3-256": "5b246f6c8b41fbd23b7aa3a73c0c93c6a35d4973bc727b24ad65f538d51ff3b6", + "sha3-384": + "f0af5d4479dc409e11c6e23014893c42a51fbd3435c93452f6154a87128174e2492a6b31994b1436ae681b3f1d838613", + "sha3-512": + "b15ed8373f1b493ccd417a7591745fdefbb4aa7b85c6937284de678e1a7b73b31e4da07561d358fefa30c6b1cf1a4b19a4c0d2f4f6e90ddfadc3a12367cb1a3c", + "ripemd160": "5291464ec22d15e61190b00b81b87c1a9dcb966f", }; for (let key of ["key", Buffer.from("key"), Buffer.from("key").buffer]) { test.each(Object.entries(hashes))("%s (key: " + key.constructor.name + ")", (algorithm, expected) => { @@ -58,15 +65,7 @@ describe("HMAC", () => { }); } - const unsupported = [ - ["sha3-224"], - ["sha3-256"], - ["sha3-384"], - ["sha3-512"], - ["shake128"], - ["shake256"], - ["ripemd160"], - ] as const; + const unsupported = [["shake128"], ["shake256"]] as const; test.each(unsupported)("%s is not supported", algorithm => { expect(() => new Bun.CryptoHasher(algorithm, "key")).toThrow(); expect(() => new Bun.CryptoHasher(algorithm)).not.toThrow(); diff --git a/test/js/web/crypto/web-crypto-sha3.test.ts b/test/js/web/crypto/web-crypto-sha3.test.ts index 84397208bfd..baf4e076447 100644 --- a/test/js/web/crypto/web-crypto-sha3.test.ts +++ b/test/js/web/crypto/web-crypto-sha3.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from "bun:test"; +import { createHash, createHmac, getHashes, pbkdf2Sync } from "node:crypto"; const hex = (buf: ArrayBuffer) => [...new Uint8Array(buf)].map(b => b.toString(16).padStart(2, "0")).join(""); @@ -33,13 +34,13 @@ describe("crypto.subtle.digest SHA-3", () => { } it("SHA3-256 large input (>64 bytes, async path)", async () => { - const input = new TextEncoder().encode("a".repeat(1_000_000)); + const input = Buffer.alloc(1_000_000, "a"); const buf = await crypto.subtle.digest("SHA3-256", input); expect(hex(buf)).toBe("5c8875ae474a3634ba4fd55ec85bffd661f32aca75c6d699d0cdcb6c115891c1"); }); it("rejects unknown digest", async () => { - expect(crypto.subtle.digest("SHA3-1024" as any, new Uint8Array())).rejects.toThrow(); + await expect(crypto.subtle.digest("SHA3-1024" as any, new Uint8Array())).rejects.toThrow(); }); }); @@ -71,29 +72,56 @@ describe("HMAC with SHA-3", () => { }); }); +describe("RSA with SHA-3 hash", () => { + it("RSA-PSS with SHA3-256: generate, sign, verify, JWK export", async () => { + const { publicKey, privateKey } = await crypto.subtle.generateKey( + { + name: "RSA-PSS", + modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]), + hash: "SHA3-256", + }, + true, + ["sign", "verify"], + ); + const data = new TextEncoder().encode("hello"); + const sig = await crypto.subtle.sign({ name: "RSA-PSS", saltLength: 32 }, privateKey, data); + expect(await crypto.subtle.verify({ name: "RSA-PSS", saltLength: 32 }, publicKey, sig, data)).toBe(true); + + const jwk = await crypto.subtle.exportKey("jwk", publicKey); + expect(jwk.kty).toBe("RSA"); + expect(jwk.alg).toBeUndefined(); + + const reimported = await crypto.subtle.importKey( + "jwk", + jwk, + { name: "RSA-PSS", hash: "SHA3-256" }, + true, + ["verify"], + ); + expect(await crypto.subtle.verify({ name: "RSA-PSS", saltLength: 32 }, reimported, sig, data)).toBe(true); + }); +}); + describe("node:crypto SHA-3", () => { it("createHash sha3-256", () => { - const { createHash } = require("node:crypto"); expect(createHash("sha3-256").update("abc").digest("hex")).toBe( "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532", ); }); it("createHash sha3-384", () => { - const { createHash } = require("node:crypto"); expect(createHash("sha3-384").update("abc").digest("hex")).toBe( "ec01498288516fc926459f58e2c6ad8df9b473cb0fc08c2596da7cf0e49be4b298d88cea927ac7f539f1edf228376d25", ); }); it("createHmac sha3-512", () => { - const { createHmac } = require("node:crypto"); const out = createHmac("sha3-512", Buffer.from("key")).update("data").digest("hex"); expect(out.length).toBe(128); }); it("getHashes includes sha3", () => { - const { getHashes } = require("node:crypto"); const hashes = getHashes(); expect(hashes).toContain("sha3-256"); expect(hashes).toContain("sha3-384"); @@ -101,7 +129,6 @@ describe("node:crypto SHA-3", () => { }); it("pbkdf2Sync sha3-256", () => { - const { pbkdf2Sync } = require("node:crypto"); expect(pbkdf2Sync("pw", "salt", 1000, 32, "sha3-256").toString("hex")).toBe( "53b1bc246a311cbf8e2c907d96bcb209ddf95cd9f0a74fdcbab033b6ea82e30a", ); From dc79dd864160e9aa39e48b9b8c12d78888a6b4b5 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 04:04:38 +0000 Subject: [PATCH 7/9] [autofix.ci] apply automated fixes --- test/js/bun/util/bun-cryptohasher.test.ts | 3 +-- test/js/web/crypto/web-crypto-sha3.test.ts | 10 +++------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/test/js/bun/util/bun-cryptohasher.test.ts b/test/js/bun/util/bun-cryptohasher.test.ts index 02ba0aa52ac..0b69b6f15a6 100644 --- a/test/js/bun/util/bun-cryptohasher.test.ts +++ b/test/js/bun/util/bun-cryptohasher.test.ts @@ -31,8 +31,7 @@ describe("HMAC", () => { "sha512-256": "0ed15b2750a2a7281e96af006ab79e82ed54a7a2081bdb49e70a70d8c6bfeff0", "sha3-224": "3dd0595758af01c6a9d662326acc3bc0c7e49b94573f74f800b6c114", "sha3-256": "5b246f6c8b41fbd23b7aa3a73c0c93c6a35d4973bc727b24ad65f538d51ff3b6", - "sha3-384": - "f0af5d4479dc409e11c6e23014893c42a51fbd3435c93452f6154a87128174e2492a6b31994b1436ae681b3f1d838613", + "sha3-384": "f0af5d4479dc409e11c6e23014893c42a51fbd3435c93452f6154a87128174e2492a6b31994b1436ae681b3f1d838613", "sha3-512": "b15ed8373f1b493ccd417a7591745fdefbb4aa7b85c6937284de678e1a7b73b31e4da07561d358fefa30c6b1cf1a4b19a4c0d2f4f6e90ddfadc3a12367cb1a3c", "ripemd160": "5291464ec22d15e61190b00b81b87c1a9dcb966f", diff --git a/test/js/web/crypto/web-crypto-sha3.test.ts b/test/js/web/crypto/web-crypto-sha3.test.ts index baf4e076447..c908aa5f01d 100644 --- a/test/js/web/crypto/web-crypto-sha3.test.ts +++ b/test/js/web/crypto/web-crypto-sha3.test.ts @@ -92,13 +92,9 @@ describe("RSA with SHA-3 hash", () => { expect(jwk.kty).toBe("RSA"); expect(jwk.alg).toBeUndefined(); - const reimported = await crypto.subtle.importKey( - "jwk", - jwk, - { name: "RSA-PSS", hash: "SHA3-256" }, - true, - ["verify"], - ); + const reimported = await crypto.subtle.importKey("jwk", jwk, { name: "RSA-PSS", hash: "SHA3-256" }, true, [ + "verify", + ]); expect(await crypto.subtle.verify({ name: "RSA-PSS", saltLength: 32 }, reimported, sig, data)).toBe(true); }); }); From 4514ceaa37cf9eeae0052f18a7710d2ba778b074 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Tue, 14 Apr 2026 21:06:56 -0700 Subject: [PATCH 8/9] Remove unused throw scope from SubtleCrypto::digest The scope was declared but never checked, tripping JSC's exception scope validation in ASAN builds. Matches the pattern in sign/verify/encrypt/decrypt which also call normalizeCryptoAlgorithmParameters without declaring a local scope (the IDL binding wrapper handles pending exceptions). --- src/bun.js/bindings/webcrypto/SubtleCrypto.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/bun.js/bindings/webcrypto/SubtleCrypto.cpp b/src/bun.js/bindings/webcrypto/SubtleCrypto.cpp index cdbfc384d95..4a26af69f4c 100644 --- a/src/bun.js/bindings/webcrypto/SubtleCrypto.cpp +++ b/src/bun.js/bindings/webcrypto/SubtleCrypto.cpp @@ -781,8 +781,6 @@ void SubtleCrypto::verify(JSC::JSGlobalObject& state, AlgorithmIdentifier&& algo void SubtleCrypto::digest(JSC::JSGlobalObject& state, AlgorithmIdentifier&& algorithmIdentifier, BufferSource&& dataBufferSource, Ref&& promise) { - auto& vm = state.vm(); - auto scope = DECLARE_THROW_SCOPE(vm); auto paramsOrException = normalizeCryptoAlgorithmParameters(state, WTF::move(algorithmIdentifier), Operations::Digest); if (paramsOrException.hasException()) { promise->reject(paramsOrException.releaseException().code(), "Unrecognized algorithm name"_s); From 4be6752d9d60735a73df9fe9d736d4a15916e7e8 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Tue, 14 Apr 2026 21:48:31 -0700 Subject: [PATCH 9/9] Properly check throw scope after normalizeCryptoAlgorithmParameters in digest RETURN_IF_EXCEPTION immediately after the call so the scope is satisfied on both the JS-exception path and the WebCore-exception path. Removing the scope (previous attempt) just moved the unchecked-exception assertion to JSDOMPromiseDeferred::reject. Also: ASSERT_UNUSED on EVP_DigestUpdate/Final return values, and replace HMAC-SHA3-512 length check with a Python-validated vector. --- src/bun.js/bindings/webcrypto/CryptoDigest.cpp | 6 ++++-- src/bun.js/bindings/webcrypto/SubtleCrypto.cpp | 3 +++ test/js/web/crypto/web-crypto-sha3.test.ts | 5 +++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/bun.js/bindings/webcrypto/CryptoDigest.cpp b/src/bun.js/bindings/webcrypto/CryptoDigest.cpp index 59c7b4e9bc7..f4ee9a71803 100644 --- a/src/bun.js/bindings/webcrypto/CryptoDigest.cpp +++ b/src/bun.js/bindings/webcrypto/CryptoDigest.cpp @@ -123,13 +123,15 @@ struct CryptoDigestContextEVP : public CryptoDigestContext { void addBytes(const void* input, size_t length) override { - EVP_DigestUpdate(&m_context, input, length); + int rc = EVP_DigestUpdate(&m_context, input, length); + ASSERT_UNUSED(rc, rc == 1); } Vector computeHash() override { Vector result(EVP_MD_CTX_size(&m_context)); - EVP_DigestFinal_ex(&m_context, result.begin(), nullptr); + int rc = EVP_DigestFinal_ex(&m_context, result.begin(), nullptr); + ASSERT_UNUSED(rc, rc == 1); return result; } diff --git a/src/bun.js/bindings/webcrypto/SubtleCrypto.cpp b/src/bun.js/bindings/webcrypto/SubtleCrypto.cpp index 4a26af69f4c..5c6f4df7d39 100644 --- a/src/bun.js/bindings/webcrypto/SubtleCrypto.cpp +++ b/src/bun.js/bindings/webcrypto/SubtleCrypto.cpp @@ -781,7 +781,10 @@ void SubtleCrypto::verify(JSC::JSGlobalObject& state, AlgorithmIdentifier&& algo void SubtleCrypto::digest(JSC::JSGlobalObject& state, AlgorithmIdentifier&& algorithmIdentifier, BufferSource&& dataBufferSource, Ref&& promise) { + auto& vm = state.vm(); + auto scope = DECLARE_THROW_SCOPE(vm); auto paramsOrException = normalizeCryptoAlgorithmParameters(state, WTF::move(algorithmIdentifier), Operations::Digest); + RETURN_IF_EXCEPTION(scope, void()); if (paramsOrException.hasException()) { promise->reject(paramsOrException.releaseException().code(), "Unrecognized algorithm name"_s); return; diff --git a/test/js/web/crypto/web-crypto-sha3.test.ts b/test/js/web/crypto/web-crypto-sha3.test.ts index c908aa5f01d..705abbf7ba9 100644 --- a/test/js/web/crypto/web-crypto-sha3.test.ts +++ b/test/js/web/crypto/web-crypto-sha3.test.ts @@ -113,8 +113,9 @@ describe("node:crypto SHA-3", () => { }); it("createHmac sha3-512", () => { - const out = createHmac("sha3-512", Buffer.from("key")).update("data").digest("hex"); - expect(out.length).toBe(128); + expect(createHmac("sha3-512", Buffer.from("key")).update("data").digest("hex")).toBe( + "752bf49d54115aaa670ea62bdf79eb95e6df787938bec5fabdfc4745cf49f7fe11b7c2f73989ad2e568f06ced3a2d99536b05a121f43647b98ea43f818f38b33", + ); }); it("getHashes includes sha3", () => {