From ae19729a72398ec018ca234e312bf788bc0771f6 Mon Sep 17 00:00:00 2001 From: Dylan Conway <35280289+dylan-conway@users.noreply.github.com> Date: Thu, 6 Mar 2025 23:52:10 -0800 Subject: [PATCH] `node:crypto`: native `Hmac` and `Hash` (#17920) --- src/bun.js/api/BunObject.zig | 140 +- src/bun.js/bindings/ErrorCode.cpp | 14 +- src/bun.js/bindings/ErrorCode.h | 4 +- src/bun.js/bindings/ErrorCode.ts | 2 + src/bun.js/bindings/JSBuffer.cpp | 112 +- src/bun.js/bindings/JSBuffer.h | 1 + src/bun.js/bindings/JSBufferEncodingType.cpp | 9 +- src/bun.js/bindings/JSBufferEncodingType.h | 3 +- src/bun.js/bindings/KeyObject.cpp | 98 +- src/bun.js/bindings/KeyObject.h | 5 + src/bun.js/bindings/ZigGlobalObject.cpp | 14 + src/bun.js/bindings/ZigGlobalObject.h | 2 + src/bun.js/bindings/ncrypto.cpp | 57 +- src/bun.js/bindings/ncrypto.h | 3 +- src/bun.js/bindings/node/crypto/JSHash.cpp | 399 +++++ src/bun.js/bindings/node/crypto/JSHash.h | 132 ++ src/bun.js/bindings/node/crypto/JSHmac.cpp | 311 ++++ src/bun.js/bindings/node/crypto/JSHmac.h | 125 ++ src/bun.js/bindings/node/crypto/JSSign.cpp | 272 +--- src/bun.js/bindings/node/crypto/JSSign.h | 4 +- src/bun.js/bindings/node/crypto/JSVerify.cpp | 14 +- .../bindings/node/crypto/NodeCrypto.cpp | 8 + src/bun.js/bindings/node/crypto/util.cpp | 460 ++++++ src/bun.js/bindings/node/crypto/util.h | 85 ++ .../bindings/webcore/DOMClientIsoSubspaces.h | 2 + src/bun.js/bindings/webcore/DOMIsoSubspaces.h | 2 + .../webcore/JSDOMConvertEnumeration.h | 3 +- src/js/node/crypto.ts | 1331 ++--------------- test/js/node/crypto/crypto.hmac.test.ts | 34 +- test/js/node/crypto/node-crypto.test.js | 17 +- test/js/node/test/common/crypto.js | 48 +- .../parallel/test-crypto-hash-stream-pipe.js | 46 + .../js/node/test/parallel/test-crypto-hash.js | 292 ++++ .../js/node/test/parallel/test-crypto-hmac.js | 472 ++++++ 34 files changed, 2887 insertions(+), 1634 deletions(-) create mode 100644 src/bun.js/bindings/node/crypto/JSHash.cpp create mode 100644 src/bun.js/bindings/node/crypto/JSHash.h create mode 100644 src/bun.js/bindings/node/crypto/JSHmac.cpp create mode 100644 src/bun.js/bindings/node/crypto/JSHmac.h create mode 100644 test/js/node/test/parallel/test-crypto-hash-stream-pipe.js create mode 100644 test/js/node/test/parallel/test-crypto-hash.js create mode 100644 test/js/node/test/parallel/test-crypto-hmac.js diff --git a/src/bun.js/api/BunObject.zig b/src/bun.js/api/BunObject.zig index 7ee4acafd7..034fdc0de0 100644 --- a/src/bun.js/api/BunObject.zig +++ b/src/bun.js/api/BunObject.zig @@ -1528,7 +1528,7 @@ pub const Crypto = struct { _ = BoringSSL.EVP_DigestUpdate(&this.ctx, input.ptr, input.len); } - pub fn size(this: *EVP) u16 { + pub fn size(this: *const EVP) u16 { return @as(u16, @truncate(BoringSSL.EVP_MD_CTX_size(&this.ctx))); } @@ -2337,6 +2337,111 @@ pub const Crypto = struct { pub usingnamespace JSC.Codegen.JSCryptoHasher; usingnamespace bun.New(@This()); + // For using only CryptoHasherZig in c++ + pub const Extern = struct { + fn getByName(global: *JSGlobalObject, name_bytes: [*:0]const u8, name_len: usize) callconv(.C) ?*CryptoHasher { + const name = name_bytes[0..name_len]; + + if (CryptoHasherZig.init(name)) |inner| { + return CryptoHasher.new(.{ + .zig = inner, + }); + } + + const algorithm = EVP.Algorithm.map.get(name) orelse { + return null; + }; + + switch (algorithm) { + .ripemd160, + .blake2b256, + .blake2b512, + + .@"sha512-224", + => { + if (algorithm.md()) |md| { + return CryptoHasher.new(.{ + .evp = EVP.init(algorithm, md, global.bunVM().rareData().boringEngine()), + }); + } + }, + else => { + return null; + }, + } + + return null; + } + + fn getFromOther(global: *JSGlobalObject, other_handle: *CryptoHasher) callconv(.C) ?*CryptoHasher { + switch (other_handle.*) { + .zig => |other| { + const hasher = CryptoHasher.new(.{ + .zig = other.copy(), + }); + return hasher; + }, + .evp => |other| { + return CryptoHasher.new(.{ + .evp = other.copy(global.bunVM().rareData().boringEngine()) catch { + return null; + }, + }); + }, + else => { + return null; + }, + } + } + + fn destroy(handle: *CryptoHasher) callconv(.C) void { + handle.finalize(); + } + + fn update(handle: *CryptoHasher, input_bytes: [*]const u8, input_len: usize) callconv(.C) bool { + const input = input_bytes[0..input_len]; + + switch (handle.*) { + .zig => { + handle.zig.update(input); + return true; + }, + .evp => { + handle.evp.update(input); + return true; + }, + else => { + return false; + }, + } + } + + fn digest(handle: *CryptoHasher, global: *JSGlobalObject, buf: [*]u8, buf_len: usize) callconv(.C) u32 { + const digest_buf = buf[0..buf_len]; + switch (handle.*) { + .zig => { + const res = handle.zig.finalWithLen(digest_buf, buf_len); + return @intCast(res.len); + }, + .evp => { + const res = handle.evp.final(global.bunVM().rareData().boringEngine(), digest_buf); + return @intCast(res.len); + }, + else => { + return 0; + }, + } + } + + fn getDigestSize(handle: *CryptoHasher) callconv(.C) u32 { + return switch (handle.*) { + .zig => |inner| inner.digest_length, + .evp => |inner| inner.size(), + else => 0, + }; + } + }; + pub const digest = JSC.wrapInstanceMethod(CryptoHasher, "digest_", false); pub const hash = JSC.wrapStaticMethod(CryptoHasher, "hash_", false); @@ -2806,6 +2911,22 @@ pub const Crypto = struct { return null; } + pub fn init(algorithm: []const u8) ?CryptoHasherZig { + inline for (algo_map) |item| { + const name, const T = item; + if (bun.strings.eqlComptime(algorithm, name)) { + const handle: CryptoHasherZig = .{ + .algorithm = @field(EVP.Algorithm, name), + .state = bun.new(T, T.init(.{})), + .digest_length = digestLength(T), + }; + + return handle; + } + } + return null; + } + fn update(self: *CryptoHasherZig, bytes: []const u8) void { inline for (algo_map) |item| { if (self.algorithm == @field(EVP.Algorithm, item[0])) { @@ -2828,19 +2949,23 @@ pub const Crypto = struct { @panic("unreachable"); } - fn final(self: *CryptoHasherZig, output_digest_slice: []u8) []u8 { + fn finalWithLen(self: *CryptoHasherZig, output_digest_slice: []u8, res_len: usize) []u8 { inline for (algo_map) |pair| { const name, const T = pair; if (self.algorithm == @field(EVP.Algorithm, name)) { T.final(@ptrCast(@alignCast(self.state)), @ptrCast(output_digest_slice)); const reset: *T = @ptrCast(@alignCast(self.state)); reset.* = T.init(.{}); - return output_digest_slice[0..self.digest_length]; + return output_digest_slice[0..res_len]; } } @panic("unreachable"); } + fn final(self: *CryptoHasherZig, output_digest_slice: []u8) []u8 { + return self.finalWithLen(output_digest_slice, self.digest_length); + } + fn deinit(self: *CryptoHasherZig) void { inline for (algo_map) |item| { if (self.algorithm == @field(EVP.Algorithm, item[0])) { @@ -3069,6 +3194,15 @@ pub const Crypto = struct { pub const SHA512_256 = StaticCryptoHasher(Hashers.SHA512_256, "SHA512_256"); }; +comptime { + @export(&Crypto.CryptoHasher.Extern.getByName, .{ .name = "Bun__CryptoHasherExtern__getByName" }); + @export(&Crypto.CryptoHasher.Extern.getFromOther, .{ .name = "Bun__CryptoHasherExtern__getFromOther" }); + @export(&Crypto.CryptoHasher.Extern.destroy, .{ .name = "Bun__CryptoHasherExtern__destroy" }); + @export(&Crypto.CryptoHasher.Extern.update, .{ .name = "Bun__CryptoHasherExtern__update" }); + @export(&Crypto.CryptoHasher.Extern.digest, .{ .name = "Bun__CryptoHasherExtern__digest" }); + @export(&Crypto.CryptoHasher.Extern.getDigestSize, .{ .name = "Bun__CryptoHasherExtern__getDigestSize" }); +} + pub fn nanoseconds(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSC.JSValue { const ns = globalThis.bunVM().origin_timer.read(); return JSC.JSValue.jsNumberFromUint64(ns); diff --git a/src/bun.js/bindings/ErrorCode.cpp b/src/bun.js/bindings/ErrorCode.cpp index c5608ff95c..898b363ae4 100644 --- a/src/bun.js/bindings/ErrorCode.cpp +++ b/src/bun.js/bindings/ErrorCode.cpp @@ -1051,7 +1051,7 @@ JSC::EncodedJSValue CRYPTO_INCOMPATIBLE_KEY_OPTIONS(JSC::ThrowScope& throwScope, return {}; } -JSC::EncodedJSValue CRYPTO_INVALID_DIGEST(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& digest) +JSC::EncodedJSValue CRYPTO_INVALID_DIGEST(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::StringView& digest) { WTF::StringBuilder builder; builder.append("Invalid digest: "_s); @@ -1060,6 +1060,18 @@ JSC::EncodedJSValue CRYPTO_INVALID_DIGEST(JSC::ThrowScope& throwScope, JSC::JSGl return {}; } +JSC::EncodedJSValue CRYPTO_HASH_FINALIZED(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject) +{ + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_CRYPTO_HASH_FINALIZED, "Digest already called"_s)); + return {}; +} + +JSC::EncodedJSValue CRYPTO_HASH_UPDATE_FAILED(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject) +{ + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_CRYPTO_HASH_UPDATE_FAILED, "Hash update failed"_s)); + return {}; +} + JSC::EncodedJSValue MISSING_PASSPHRASE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral message) { throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_MISSING_PASSPHRASE, message)); diff --git a/src/bun.js/bindings/ErrorCode.h b/src/bun.js/bindings/ErrorCode.h index 619a476648..92b84685f8 100644 --- a/src/bun.js/bindings/ErrorCode.h +++ b/src/bun.js/bindings/ErrorCode.h @@ -94,7 +94,9 @@ JSC::EncodedJSValue CRYPTO_INVALID_JWK(JSC::ThrowScope& throwScope, JSC::JSGloba JSC::EncodedJSValue CRYPTO_SIGN_KEY_REQUIRED(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject); JSC::EncodedJSValue CRYPTO_INVALID_KEY_OBJECT_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSValue received, WTF::ASCIILiteral expected); JSC::EncodedJSValue CRYPTO_INCOMPATIBLE_KEY_OPTIONS(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& received, const WTF::String& expected); -JSC::EncodedJSValue CRYPTO_INVALID_DIGEST(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& digest); +JSC::EncodedJSValue CRYPTO_INVALID_DIGEST(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::StringView& digest); +JSC::EncodedJSValue CRYPTO_HASH_FINALIZED(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject); +JSC::EncodedJSValue CRYPTO_HASH_UPDATE_FAILED(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject); JSC::EncodedJSValue MISSING_PASSPHRASE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral message); // URL diff --git a/src/bun.js/bindings/ErrorCode.ts b/src/bun.js/bindings/ErrorCode.ts index 6b38d195c8..a161069547 100644 --- a/src/bun.js/bindings/ErrorCode.ts +++ b/src/bun.js/bindings/ErrorCode.ts @@ -33,6 +33,8 @@ const errors: ErrorCodeMapping = [ ["ERR_CRYPTO_SIGN_KEY_REQUIRED", Error], ["ERR_CRYPTO_INVALID_JWK", TypeError], ["ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS", Error], + ["ERR_CRYPTO_HASH_FINALIZED", Error], + ["ERR_CRYPTO_HASH_UPDATE_FAILED", Error], ["ERR_MISSING_PASSPHRASE", TypeError], ["ERR_DLOPEN_FAILED", Error], ["ERR_ENCODING_INVALID_ENCODED_DATA", TypeError], diff --git a/src/bun.js/bindings/JSBuffer.cpp b/src/bun.js/bindings/JSBuffer.cpp index 2cd3ce690a..58a7c5cd1f 100644 --- a/src/bun.js/bindings/JSBuffer.cpp +++ b/src/bun.js/bindings/JSBuffer.cpp @@ -283,7 +283,7 @@ static WebCore::BufferEncodingType parseEncoding(JSC::ThrowScope& scope, JSC::JS RETURN_IF_EXCEPTION(scope, {}); const auto& view = arg_->view(lexicalGlobalObject); - std::optional encoded = parseEnumeration2(*lexicalGlobalObject, view); + std::optional encoded = parseEnumerationFromView(view); if (UNLIKELY(!encoded)) { if (validateUnknown) { Bun::V::validateString(scope, lexicalGlobalObject, arg, "encoding"_s); @@ -1501,7 +1501,7 @@ static int64_t indexOf(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* if (!encodingValue.isUndefined()) { encodingString = encodingValue.toWTFString(lexicalGlobalObject); RETURN_IF_EXCEPTION(scope, {}); - encoding = parseEnumeration2(*lexicalGlobalObject, encodingString); + encoding = parseEnumerationFromString(encodingString); } else { encoding = BufferEncodingType::utf8; } @@ -1714,64 +1714,54 @@ static JSC::EncodedJSValue jsBufferPrototypeFunction_swap64Body(JSC::JSGlobalObj return JSC::JSValue::encode(castedThis); } -JSC::EncodedJSValue jsBufferToString(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSArrayBufferView* castedThis, size_t offset, size_t length, WebCore::BufferEncodingType encoding) +JSC::EncodedJSValue jsBufferToStringFromBytes(JSGlobalObject* lexicalGlobalObject, ThrowScope& scope, std::span bytes, BufferEncodingType encoding) { - auto scope = DECLARE_THROW_SCOPE(vm); + auto& vm = lexicalGlobalObject->vm(); - if (UNLIKELY(length == 0)) { - RELEASE_AND_RETURN(scope, JSC::JSValue::encode(JSC::jsEmptyString(vm))); + if (UNLIKELY(bytes.size() == 0)) { + RELEASE_AND_RETURN(scope, JSValue::encode(jsEmptyString(vm))); } - if (length > WTF::String::MaxLength) { + + if (bytes.size() > WTF::String::MaxLength) { return Bun::ERR::STRING_TOO_LONG(scope, lexicalGlobalObject); } - if (length > castedThis->byteLength()) { - length = castedThis->byteLength(); - } - - JSC::EncodedJSValue ret = 0; switch (encoding) { - case WebCore::BufferEncodingType::latin1: { + case BufferEncodingType::latin1: { std::span data; - auto str = String::tryCreateUninitialized(length, data); + auto str = String::tryCreateUninitialized(bytes.size(), data); if (UNLIKELY(str.isNull())) { throwOutOfMemoryError(lexicalGlobalObject, scope); return JSValue::encode({}); } - memcpy(data.data(), reinterpret_cast(castedThis->vector()) + offset, length); - return JSC::JSValue::encode(JSC::jsString(vm, WTFMove(str))); - } - case WebCore::BufferEncodingType::ucs2: - case WebCore::BufferEncodingType::utf16le: { + memcpy(data.data(), bytes.data(), bytes.size()); + return JSValue::encode(jsString(vm, WTFMove(str))); + } + case BufferEncodingType::ucs2: + case BufferEncodingType::utf16le: { std::span data; - size_t u16length = length / 2; + size_t u16length = bytes.size() / 2; if (u16length == 0) { - return JSC::JSValue::encode(JSC::jsEmptyString(vm)); - } else { - auto str = String::tryCreateUninitialized(u16length, data); - if (UNLIKELY(str.isNull())) { - throwOutOfMemoryError(lexicalGlobalObject, scope); - return JSValue::encode({}); - } - memcpy(reinterpret_cast(data.data()), reinterpret_cast(castedThis->vector()) + offset, u16length * 2); - return JSC::JSValue::encode(JSC::jsString(vm, str)); + return JSValue::encode(jsEmptyString(vm)); } - - break; - } - - case WebCore::BufferEncodingType::ascii: { - // ascii: we always know the length - // so we might as well allocate upfront - std::span data; - auto str = String::tryCreateUninitialized(length, data); + auto str = String::tryCreateUninitialized(u16length, data); if (UNLIKELY(str.isNull())) { throwOutOfMemoryError(lexicalGlobalObject, scope); return JSValue::encode({}); } - Bun__encoding__writeLatin1(reinterpret_cast(castedThis->vector()) + offset, length, data.data(), length, static_cast(encoding)); - return JSC::JSValue::encode(JSC::jsString(vm, WTFMove(str))); + memcpy(reinterpret_cast(data.data()), bytes.data(), u16length * 2); + return JSValue::encode(jsString(vm, WTFMove(str))); + } + case BufferEncodingType::ascii: { + std::span data; + auto str = String::tryCreateUninitialized(bytes.size(), data); + if (UNLIKELY(str.isNull())) { + throwOutOfMemoryError(lexicalGlobalObject, scope); + return JSValue::encode({}); + } + Bun__encoding__writeLatin1(bytes.data(), bytes.size(), data.data(), data.size(), static_cast(encoding)); + return JSValue::encode(jsString(vm, WTFMove(str))); } case WebCore::BufferEncodingType::buffer: @@ -1779,23 +1769,49 @@ JSC::EncodedJSValue jsBufferToString(JSC::VM& vm, JSC::JSGlobalObject* lexicalGl case WebCore::BufferEncodingType::base64: case WebCore::BufferEncodingType::base64url: case WebCore::BufferEncodingType::hex: { - ret = Bun__encoding__toString(reinterpret_cast(castedThis->vector()) + offset, length, lexicalGlobalObject, static_cast(encoding)); - RETURN_IF_EXCEPTION(scope, {}); - break; + EncodedJSValue res = Bun__encoding__toString(bytes.data(), bytes.size(), lexicalGlobalObject, static_cast(encoding)); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + JSValue stringValue = JSValue::decode(res); + if (UNLIKELY(!stringValue.isString())) { + scope.throwException(lexicalGlobalObject, stringValue); + return JSValue::encode({}); + } + + RELEASE_AND_RETURN(scope, JSValue::encode(stringValue)); } default: { throwTypeError(lexicalGlobalObject, scope, "Unsupported encoding? This shouldn't happen"_s); return {}; } } +} - JSC::JSValue retValue = JSC::JSValue::decode(ret); - if (UNLIKELY(!retValue.isString())) { - scope.throwException(lexicalGlobalObject, retValue); - return {}; +JSC::EncodedJSValue jsBufferToString(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSArrayBufferView* castedThis, size_t offset, size_t length, WebCore::BufferEncodingType encoding) +{ + auto scope = DECLARE_THROW_SCOPE(vm); + + if (UNLIKELY(!castedThis->byteLength())) { + RELEASE_AND_RETURN(scope, JSValue::encode(jsEmptyString(vm))); } - RELEASE_AND_RETURN(scope, JSC::JSValue::encode(retValue)); + ASSERT(offset < castedThis->byteLength()); + ASSERT(length <= castedThis->byteLength()); + ASSERT(offset + length <= castedThis->byteLength()); + + if (offset >= castedThis->byteLength()) { + offset = castedThis->byteLength(); + } + + if (length > castedThis->byteLength()) { + length = castedThis->byteLength(); + } + + if (offset + length > castedThis->byteLength()) { + length = castedThis->byteLength() - offset; + } + + return jsBufferToStringFromBytes(lexicalGlobalObject, scope, castedThis->span().subspan(offset, length), encoding); } // https://github.com/nodejs/node/blob/2eff28fb7a93d3f672f80b582f664a7c701569fb/src/node_buffer.cc#L208-L233 diff --git a/src/bun.js/bindings/JSBuffer.h b/src/bun.js/bindings/JSBuffer.h index b583e79816..65864bd860 100644 --- a/src/bun.js/bindings/JSBuffer.h +++ b/src/bun.js/bindings/JSBuffer.h @@ -64,6 +64,7 @@ JSC_DECLARE_HOST_FUNCTION(constructSlowBuffer); JSC::JSObject* createBufferPrototype(JSC::VM&, JSC::JSGlobalObject*); JSC::Structure* createBufferStructure(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue prototype); JSC::JSObject* createBufferConstructor(JSC::VM&, JSC::JSGlobalObject*, JSC::JSObject* bufferPrototype); +JSC::EncodedJSValue jsBufferToStringFromBytes(JSC::JSGlobalObject* lexicalGlobalObject, JSC::ThrowScope& scope, std::span bytes, BufferEncodingType encoding); JSC::EncodedJSValue jsBufferToString(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSArrayBufferView* castedThis, size_t offset, size_t length, WebCore::BufferEncodingType encoding); JSC::EncodedJSValue constructFromEncoding(JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSString* str, WebCore::BufferEncodingType encoding); diff --git a/src/bun.js/bindings/JSBufferEncodingType.cpp b/src/bun.js/bindings/JSBufferEncodingType.cpp index 88ba596fb2..5415d5509a 100644 --- a/src/bun.js/bindings/JSBufferEncodingType.cpp +++ b/src/bun.js/bindings/JSBufferEncodingType.cpp @@ -63,10 +63,15 @@ template<> std::optional parseEnumerationview(&lexicalGlobalObject); - return parseEnumeration2(lexicalGlobalObject, view); + return parseEnumerationFromView(view); } -std::optional parseEnumeration2(JSGlobalObject& lexicalGlobalObject, const WTF::StringView encoding) +template<> std::optional parseEnumerationFromString(const String& encoding) +{ + return parseEnumerationFromView(encoding); +} + +template<> std::optional parseEnumerationFromView(const StringView& encoding) { // caller must check if value is a string switch (encoding.length()) { diff --git a/src/bun.js/bindings/JSBufferEncodingType.h b/src/bun.js/bindings/JSBufferEncodingType.h index e468b09cd1..e65de54975 100644 --- a/src/bun.js/bindings/JSBufferEncodingType.h +++ b/src/bun.js/bindings/JSBufferEncodingType.h @@ -8,7 +8,8 @@ String convertEnumerationToString(BufferEncodingType); template<> JSC::JSString* convertEnumerationToJS(JSC::JSGlobalObject&, BufferEncodingType); template<> std::optional parseEnumeration(JSC::JSGlobalObject&, JSValue); -std::optional parseEnumeration2(JSC::JSGlobalObject&, const WTF::StringView); +template<> std::optional parseEnumerationFromString(const WTF::String&); +template<> std::optional parseEnumerationFromView(const WTF::StringView&); template<> WTF::ASCIILiteral expectedEnumerationValues(); } // namespace WebCore diff --git a/src/bun.js/bindings/KeyObject.cpp b/src/bun.js/bindings/KeyObject.cpp index eff2053941..5015ddc7af 100644 --- a/src/bun.js/bindings/KeyObject.cpp +++ b/src/bun.js/bindings/KeyObject.cpp @@ -2954,42 +2954,80 @@ JSC_DEFINE_HOST_FUNCTION(KeyObject__SymmetricKeySize, (JSC::JSGlobalObject * glo { if (auto* key = jsDynamicCast(callFrame->argument(0))) { auto& wrapped = key->wrapped(); - auto id = wrapped.keyClass(); - size_t size = 0; - switch (id) { - case CryptoKeyClass::HMAC: { - const auto& hmac = downcast(wrapped); - auto keyData = hmac.key(); - size = keyData.size(); - break; + auto size = getSymmetricKeySize(wrapped); + if (size.has_value() && size.value() > 0) { + return JSC::JSValue::encode(jsNumber(size.value())); } - case CryptoKeyClass::AES: { - const auto& aes = downcast(wrapped); - auto keyData = aes.key(); - size = keyData.size(); - break; - } - case CryptoKeyClass::Raw: { - const auto& raw = downcast(wrapped); - auto keyData = raw.key(); - size = keyData.size(); - break; - } - default: { - return JSC::JSValue::encode(JSC::jsUndefined()); - } - } - - if (!size) { - return JSC::JSValue::encode(JSC::jsUndefined()); - } - - return JSC::JSValue::encode(JSC::jsNumber(size)); } return JSC::JSValue::encode(JSC::jsUndefined()); } +std::optional getSymmetricKeySize(const WebCore::CryptoKey& key) +{ + auto id = key.keyClass(); + switch (id) { + case CryptoKeyClass::HMAC: { + const auto& hmac = downcast(key); + return hmac.key().size(); + } + case CryptoKeyClass::AES: { + const auto& aes = downcast(key); + return aes.key().size(); + } + case CryptoKeyClass::Raw: { + const auto& raw = downcast(key); + return raw.key().size(); + } + default: { + return std::nullopt; + } + } +} + +const uint8_t* getSymmetricKeyData(const WebCore::CryptoKey& key) +{ + auto id = key.keyClass(); + switch (id) { + case CryptoKeyClass::HMAC: { + const auto& hmac = downcast(key); + return hmac.key().data(); + } + case CryptoKeyClass::AES: { + const auto& aes = downcast(key); + return aes.key().data(); + } + case CryptoKeyClass::Raw: { + const auto& raw = downcast(key); + return raw.key().data(); + } + default: { + return nullptr; + } + } +} +std::optional> getSymmetricKey(const WebCore::CryptoKey& key) +{ + auto id = key.keyClass(); + switch (id) { + case CryptoKeyClass::HMAC: { + const auto& hmac = downcast(key); + return hmac.key().span(); + } + case CryptoKeyClass::AES: { + const auto& aes = downcast(key); + return aes.key().span(); + } + case CryptoKeyClass::Raw: { + const auto& raw = downcast(key); + return raw.key().span(); + } + default: { + return std::nullopt; + } + } +} + static EncodedJSValue doAsymmetricCipher(JSGlobalObject* globalObject, CallFrame* callFrame, bool encrypt) { auto count = callFrame->argumentCount(); diff --git a/src/bun.js/bindings/KeyObject.h b/src/bun.js/bindings/KeyObject.h index c34eec66df..9e878f7d8d 100644 --- a/src/bun.js/bindings/KeyObject.h +++ b/src/bun.js/bindings/KeyObject.h @@ -4,10 +4,15 @@ #include "root.h" #include "helpers.h" #include "ExceptionOr.h" +#include "CryptoKey.h" namespace WebCore { ExceptionOr> KeyObject__GetBuffer(JSC::JSValue bufferArg); JSC::JSValue createKeyObjectBinding(Zig::GlobalObject* globalObject); +std::optional getSymmetricKeySize(const CryptoKey& key); +const uint8_t* getSymmetricKeyData(const CryptoKey& key); +std::optional> getSymmetricKey(const CryptoKey& key); + } // namespace WebCore diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 84e6830de4..4716ff4814 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -162,6 +162,8 @@ #include "JSX509Certificate.h" #include "JSSign.h" #include "JSVerify.h" +#include "JSHmac.h" +#include "JSHash.h" #include "JSS3File.h" #include "S3Error.h" #include "ProcessBindingBuffer.h" @@ -2851,6 +2853,16 @@ void GlobalObject::finishCreation(VM& vm) setupJSVerifyClassStructure(init); }); + m_JSHmacClassStructure.initLater( + [](LazyClassStructure::Initializer& init) { + setupJSHmacClassStructure(init); + }); + + m_JSHashClassStructure.initLater( + [](LazyClassStructure::Initializer& init) { + setupJSHashClassStructure(init); + }); + m_lazyStackCustomGetterSetter.initLater( [](const Initializer& init) { init.set(CustomGetterSetter::create(init.vm, errorInstanceLazyStackCustomGetter, errorInstanceLazyStackCustomSetter)); @@ -4039,6 +4051,8 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) thisObject->m_JSX509CertificateClassStructure.visit(visitor); thisObject->m_JSSignClassStructure.visit(visitor); thisObject->m_JSVerifyClassStructure.visit(visitor); + thisObject->m_JSHmacClassStructure.visit(visitor); + thisObject->m_JSHashClassStructure.visit(visitor); thisObject->m_statValues.visit(visitor); thisObject->m_bigintStatValues.visit(visitor); thisObject->m_statFsValues.visit(visitor); diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index 68528927d5..55e647bc4a 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -535,6 +535,8 @@ public: LazyClassStructure m_JSX509CertificateClassStructure; LazyClassStructure m_JSSignClassStructure; LazyClassStructure m_JSVerifyClassStructure; + LazyClassStructure m_JSHmacClassStructure; + LazyClassStructure m_JSHashClassStructure; /** * WARNING: You must update visitChildrenImpl() if you add a new field. diff --git a/src/bun.js/bindings/ncrypto.cpp b/src/bun.js/bindings/ncrypto.cpp index 2a36671bd7..f6cf243173 100644 --- a/src/bun.js/bindings/ncrypto.cpp +++ b/src/bun.js/bindings/ncrypto.cpp @@ -1825,7 +1825,7 @@ DataPointer DHPointer::stateless(const EVPKeyPointer& ourKey, // ============================================================================ // KDF -const EVP_MD* getDigestByName(const WTF::StringView name) +const EVP_MD* getDigestByName(const WTF::StringView name, bool ignoreSHA512_224) { // Historically, "dss1" and "DSS1" were DSA aliases for SHA-1 // exposed through the public API. @@ -1833,6 +1833,61 @@ const EVP_MD* getDigestByName(const WTF::StringView name) return EVP_sha1(); } + if (WTF::equalIgnoringASCIICase(name, "md5"_s)) { + return EVP_md5(); + } + + if (WTF::startsWithIgnoringASCIICase(name, "sha"_s)) { + auto remain = name.substring(3); + if (remain.startsWith('-')) { + auto bits = remain.substring(1); + if (WTF::equalIgnoringASCIICase(bits, "1"_s)) { + return EVP_sha1(); + } + if (WTF::equalIgnoringASCIICase(bits, "224"_s)) { + return EVP_sha224(); + } + if (WTF::equalIgnoringASCIICase(bits, "256"_s)) { + return EVP_sha256(); + } + if (WTF::equalIgnoringASCIICase(bits, "384"_s)) { + return EVP_sha384(); + } + + if (WTF::startsWithIgnoringASCIICase(bits, "512"_s)) { + auto moreBits = bits.substring(3); + if (moreBits.isEmpty()) { + return EVP_sha512(); + } + if (WTF::equalIgnoringASCIICase(moreBits, "/224"_s)) { + if (ignoreSHA512_224) { + return nullptr; + } + return EVP_sha512_224(); + } + if (WTF::equalIgnoringASCIICase(moreBits, "/256"_s)) { + return EVP_sha512_256(); + } + + // backwards compatibility with what we supported before + // (not supported by node) + if (WTF::equalIgnoringASCIICase(moreBits, "256"_s) || WTF::equalIgnoringASCIICase(moreBits, "_256"_s)) { + return EVP_sha512_256(); + } + } + } + + // backwards compatibility with what we supported before + // (not supported by node) + if (WTF::equalIgnoringASCIICase(remain, "128"_s)) { + return EVP_sha1(); + } + } + + if (ignoreSHA512_224 && WTF::equalIgnoringASCIICase(name, "sha512-224"_s)) { + return nullptr; + } + // if (name == "ripemd160WithRSA"_s || name == "RSA-RIPEMD160"_s) { // return EVP_ripemd160(); // } diff --git a/src/bun.js/bindings/ncrypto.h b/src/bun.js/bindings/ncrypto.h index 2d519338a9..20a11a6110 100644 --- a/src/bun.js/bindings/ncrypto.h +++ b/src/bun.js/bindings/ncrypto.h @@ -513,6 +513,7 @@ public: return static_cast(data_); } + inline std::span span() const { return { get(), len_ }; } inline size_t size() const noexcept { return len_; } void reset(void* data = nullptr, size_t len = 0); void reset(const Buffer& buffer); @@ -1450,7 +1451,7 @@ DataPointer ExportChallenge(const Buffer& buf); // ============================================================================ // KDF -const EVP_MD* getDigestByName(const WTF::StringView name); +const EVP_MD* getDigestByName(const WTF::StringView name, bool ignoreSHA512_224 = false); const EVP_CIPHER* getCipherByName(const WTF::StringView name); // Verify that the specified HKDF output length is valid for the given digest. diff --git a/src/bun.js/bindings/node/crypto/JSHash.cpp b/src/bun.js/bindings/node/crypto/JSHash.cpp new file mode 100644 index 0000000000..4f06252f45 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSHash.cpp @@ -0,0 +1,399 @@ +#include "JSHash.h" +#include "util.h" +#include "BunClientData.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "NodeValidator.h" +#include + +namespace Bun { + +static const HashTableValue JSHashPrototypeTableValues[] = { + { "update"_s, static_cast(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsHashProtoFuncUpdate, 1 } }, + { "digest"_s, static_cast(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsHashProtoFuncDigest, 1 } }, +}; + +const ClassInfo JSHash::s_info = { "Hash"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSHash) }; +const ClassInfo JSHashPrototype::s_info = { "HashPrototype"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSHashPrototype) }; +const ClassInfo JSHashConstructor::s_info = { "HashConstructor"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSHashConstructor) }; + +JSHash::JSHash(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) +{ +} + +JSHash::~JSHash() +{ + if (m_zigHasher) { + ExternZigHash::destroy(m_zigHasher); + } +} + +void JSHash::finishCreation(JSC::VM& vm) +{ + Base::finishCreation(vm); +} + +template +JSC::GCClient::IsoSubspace* JSHash::subspaceFor(JSC::VM& vm) +{ + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForJSHash.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSHash = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForJSHash.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForJSHash = std::forward(space); }); +} + +JSHash* JSHash::create(JSC::VM& vm, JSC::Structure* structure) +{ + JSHash* instance = new (NotNull, JSC::allocateCell(vm)) JSHash(vm, structure); + instance->finishCreation(vm); + return instance; +} + +bool JSHash::init(JSC::JSGlobalObject* globalObject, ThrowScope& scope, const EVP_MD* md, std::optional xofLen) +{ + // Create the digest context + m_ctx = ncrypto::EVPMDCtxPointer::New(); + if (!m_ctx.digestInit(md)) { + m_ctx.reset(); + return false; + } + + // Set the digest length + m_mdLen = m_ctx.getDigestSize(); + + // Handle custom length for XOF hash functions (like SHAKE) + if (xofLen.has_value() && xofLen.value() != m_mdLen) { + // from node: + // https://github.com/nodejs/node/blob/2a6f90813f4802def79f2df1bfe20e95df279abf/src/crypto/crypto_hash.cc#L346 + // + // This is a little hack to cause createHash to fail when an incorrect + // hashSize option was passed for a non-XOF hash function. + if (!m_ctx.hasXofFlag()) { + EVPerr(EVP_F_EVP_DIGESTFINALXOF, EVP_R_NOT_XOF_OR_INVALID_LENGTH); + m_ctx.reset(); + return false; + } + m_mdLen = xofLen.value(); + } + + return true; +} + +bool JSHash::initZig(JSGlobalObject* globalObject, ThrowScope& scope, ExternZigHash::Hasher* hasher, std::optional xofLen) +{ + m_zigHasher = hasher; + m_mdLen = ExternZigHash::getDigestSize(hasher); + + if (m_mdLen == 0) { + return false; + } + + if (xofLen.has_value()) { + m_mdLen = xofLen.value(); + } + + return true; +} + +bool JSHash::update(std::span input) +{ + if (m_ctx) { + ncrypto::Buffer buffer { + .data = input.data(), + .len = input.size(), + }; + + return m_ctx.digestUpdate(buffer); + } + + if (m_zigHasher) { + return ExternZigHash::update(m_zigHasher, input); + } + + return false; +} + +void JSHashPrototype::finishCreation(JSC::VM& vm) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSHash::info(), JSHashPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +JSC_DEFINE_HOST_FUNCTION(jsHashProtoFuncUpdate, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + // Get the Hash instance + JSValue thisHash = callFrame->thisValue(); + JSHash* hash = jsDynamicCast(thisHash); + + JSValue hashWrapper = callFrame->argument(0); + + // Check if the Hash is already finalized + if (hash->m_finalized) { + return Bun::ERR::CRYPTO_HASH_FINALIZED(scope, globalObject); + } + + JSC::JSValue inputValue = callFrame->argument(1); + JSValue encodingValue = callFrame->argument(2); + + // Process the inputValue + if (inputValue.isString()) { + // Handle string inputValue + JSString* inputString = inputValue.toString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + + auto encoding = parseEnumeration(*globalObject, encodingValue).value_or(WebCore::BufferEncodingType::utf8); + RETURN_IF_EXCEPTION(scope, {}); + + // Validate encoding + if (encoding == WebCore::BufferEncodingType::hex && inputString->length() % 2 != 0) { + return Bun::ERR::INVALID_ARG_VALUE(scope, globalObject, "encoding"_s, encodingValue, makeString("is invalid for data of length "_s, inputString->length())); + } + + JSValue converted = JSValue::decode(WebCore::constructFromEncoding(globalObject, inputString, encoding)); + RETURN_IF_EXCEPTION(scope, {}); + + auto* convertedView = jsDynamicCast(converted); + + if (!hash->update(std::span { reinterpret_cast(convertedView->vector()), convertedView->byteLength() })) { + return Bun::ERR::CRYPTO_HASH_UPDATE_FAILED(scope, globalObject); + } + + return JSValue::encode(hashWrapper); + } else if (auto* view = JSC::jsDynamicCast(inputValue)) { + if (!hash->update(std::span { reinterpret_cast(view->vector()), view->byteLength() })) { + return Bun::ERR::CRYPTO_HASH_UPDATE_FAILED(scope, globalObject); + } + + return JSValue::encode(hashWrapper); + } + + return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "data"_s, "string or an instance of Buffer, TypedArray, or DataView"_s, inputValue); +} + +JSC_DEFINE_HOST_FUNCTION(jsHashProtoFuncDigest, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + // Get the Hash instance + JSHash* hash = jsDynamicCast(callFrame->thisValue()); + + // Check if already finalized + if (hash->m_finalized) { + return Bun::ERR::CRYPTO_HASH_FINALIZED(scope, globalObject); + } + + // Handle encoding if provided + JSC::JSValue encodingValue = callFrame->argument(0); + + BufferEncodingType encoding = BufferEncodingType::buffer; + if (encodingValue.pureToBoolean() != TriState::False) { + // this value needs to stringify if truthy + // https://github.com/nodejs/node/blob/2a6f90813f4802def79f2df1bfe20e95df279abf/lib/internal/crypto/hash.js#L130 + WTF::String encodingString = encodingValue.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + encoding = parseEnumerationFromString(encodingString).value_or(BufferEncodingType::buffer); + RETURN_IF_EXCEPTION(scope, {}); + } + + bool finalized = true; + JSValue setFinalizedValue = callFrame->argument(1); + if (setFinalizedValue.isBoolean()) { + finalized = setFinalizedValue.asBoolean(); + } + + uint32_t len = hash->m_mdLen; + + if (hash->m_zigHasher) { + if (hash->m_digestBuffer.size() > 0 || len == 0) { + return StringBytes::encode( + lexicalGlobalObject, + scope, + hash->m_digestBuffer.span().subspan(0, hash->m_mdLen), + encoding); + } + + uint32_t maxDigestLen = std::max((uint32_t)EVP_MAX_MD_SIZE, len); + hash->m_digestBuffer.resizeToFit(maxDigestLen); + auto totalDigestLen = ExternZigHash::digest(hash->m_zigHasher, globalObject, hash->m_digestBuffer.mutableSpan()); + if (!totalDigestLen) { + throwCryptoError(lexicalGlobalObject, scope, ERR_get_error(), "Failed to finalize digest"_s); + return JSValue::encode({}); + } + + hash->m_finalized = finalized; + hash->m_mdLen = std::min(len, totalDigestLen); + + auto result = StringBytes::encode( + lexicalGlobalObject, + scope, + hash->m_digestBuffer.span().subspan(0, hash->m_mdLen), + encoding); + + return result; + } + + // Only compute the digest if it hasn't been cached yet + if (!hash->m_digest && len > 0) { + // Some hash algorithms don't support calling EVP_DigestFinal_ex more than once + // We need to cache the result for future calls + auto data = hash->m_ctx.digestFinal(len); + if (!data) { + throwCryptoError(lexicalGlobalObject, scope, ERR_get_error(), "Failed to finalize digest"_s); + return JSValue::encode({}); + } + + // Store the digest in the hash object + hash->m_digest = ByteSource::allocated(data.release()); + } + + // Mark as finalized + hash->m_finalized = finalized; + + // Return the digest with the requested encoding + return StringBytes::encode( + lexicalGlobalObject, + scope, + std::span { reinterpret_cast(hash->m_digest.data()), len }, + encoding); +} + +JSC_DEFINE_HOST_FUNCTION(constructHash, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* zigGlobalObject = defaultGlobalObject(globalObject); + JSC::Structure* structure = zigGlobalObject->m_JSHashClassStructure.get(zigGlobalObject); + + // Handle new target + JSC::JSValue newTarget = callFrame->newTarget(); + if (UNLIKELY(zigGlobalObject->m_JSHashClassStructure.constructor(zigGlobalObject) != newTarget)) { + if (!newTarget) { + throwTypeError(globalObject, scope, "Class constructor Hash cannot be invoked without 'new'"_s); + return {}; + } + + auto* functionGlobalObject = defaultGlobalObject(getFunctionRealm(globalObject, newTarget.getObject())); + RETURN_IF_EXCEPTION(scope, {}); + structure = InternalFunction::createSubclassStructure( + globalObject, newTarget.getObject(), functionGlobalObject->m_JSHashClassStructure.get(functionGlobalObject)); + RETURN_IF_EXCEPTION(scope, {}); + } + + JSValue algorithmOrHashInstanceValue = callFrame->argument(0); + + // Because we aren't checking if m_finalized true in Hash.prototype.copy, we need to check here + // and make sure we validate arguments in the correct order. + // If clone, check m_finalized before anything else. + JSHash* original = nullptr; + const EVP_MD* md = nullptr; + ExternZigHash::Hasher* zigHasher = nullptr; + if (algorithmOrHashInstanceValue.inherits(JSHash::info())) { + original = jsDynamicCast(algorithmOrHashInstanceValue); + if (!original || original->m_finalized) { + return Bun::ERR::CRYPTO_HASH_FINALIZED(scope, globalObject); + } + + if (original->m_zigHasher) { + zigHasher = ExternZigHash::getFromOther(zigGlobalObject, original->m_zigHasher); + } else { + md = original->m_ctx.getDigest(); + } + } else { + Bun::V::validateString(scope, globalObject, algorithmOrHashInstanceValue, "algorithm"_s); + RETURN_IF_EXCEPTION(scope, {}); + + WTF::String algorithm = algorithmOrHashInstanceValue.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + + md = ncrypto::getDigestByName(algorithm, true); + if (!md) { + zigHasher = ExternZigHash::getByName(zigGlobalObject, algorithm); + } + } + + std::optional xofLen = std::nullopt; + JSValue optionsValue = callFrame->argument(1); + if (optionsValue.isObject()) { + JSValue outputLengthValue = optionsValue.get(globalObject, Identifier::fromString(vm, "outputLength"_s)); + RETURN_IF_EXCEPTION(scope, {}); + + if (!outputLengthValue.isUndefined()) { + Bun::V::validateUint32(scope, globalObject, outputLengthValue, "options.outputLength"_s, jsUndefined()); + RETURN_IF_EXCEPTION(scope, {}); + xofLen = outputLengthValue.toUInt32(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + } + } + + JSHash* hash = JSHash::create(vm, structure); + + if (zigHasher) { + if (!hash->initZig(globalObject, scope, zigHasher, xofLen)) { + throwCryptoError(globalObject, scope, 0, "Digest method not supported"_s); + return JSValue::encode({}); + } + return JSValue::encode(hash); + } + + if (md == nullptr || !hash->init(globalObject, scope, md, xofLen)) { + throwCryptoError(globalObject, scope, ERR_get_error(), "Digest method not supported"_s); + return JSValue::encode({}); + } + + if (original != nullptr && !original->m_ctx.copyTo(hash->m_ctx)) { + throwCryptoError(globalObject, scope, ERR_get_error(), "Digest copy error"_s); + return JSValue::encode({}); + } + + return JSC::JSValue::encode(hash); +} + +JSC_DEFINE_HOST_FUNCTION(callHash, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + throwTypeError(globalObject, scope, "Class constructor Hash cannot be invoked without 'new'"_s); + return JSC::encodedJSUndefined(); +} + +JSC::Structure* JSHashConstructor::createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) +{ + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info()); +} + +void setupJSHashClassStructure(JSC::LazyClassStructure::Initializer& init) +{ + auto* prototypeStructure = JSHashPrototype::createStructure(init.vm, init.global, init.global->objectPrototype()); + auto* prototype = JSHashPrototype::create(init.vm, init.global, prototypeStructure); + + auto* constructorStructure = JSHashConstructor::createStructure(init.vm, init.global, init.global->functionPrototype()); + auto* constructor = JSHashConstructor::create(init.vm, constructorStructure, prototype); + + auto* structure = JSHash::createStructure(init.vm, init.global, prototype); + init.setPrototype(prototype); + init.setStructure(structure); + init.setConstructor(constructor); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSHash.h b/src/bun.js/bindings/node/crypto/JSHash.h new file mode 100644 index 0000000000..8cb441dec5 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSHash.h @@ -0,0 +1,132 @@ +#pragma once + +#include "root.h" +#include "BunClientData.h" +#include +#include +#include +#include "ncrypto.h" +#include "util.h" +#include "JSBuffer.h" +#include "JSDOMConvertEnumeration.h" +#include +#include + +namespace Bun { + +JSC_DECLARE_HOST_FUNCTION(callHash); +JSC_DECLARE_HOST_FUNCTION(constructHash); + +class JSHash final : public JSC::JSDestructibleObject { +public: + using Base = JSC::JSDestructibleObject; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSHash* create(JSC::VM& vm, JSC::Structure* structure); + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + + JSHash(JSC::VM& vm, JSC::Structure* structure); + ~JSHash(); + + void finishCreation(JSC::VM& vm); + bool init(JSC::JSGlobalObject* globalObject, ThrowScope& scope, const EVP_MD* md, std::optional xofLen); + bool initZig(JSGlobalObject* globalObject, ThrowScope& scope, ExternZigHash::Hasher* hasher, std::optional xofLen); + bool update(std::span input); + + ncrypto::EVPMDCtxPointer m_ctx; + unsigned int m_mdLen { 0 }; + ByteSource m_digest; + bool m_finalized { false }; + + Vector m_digestBuffer; + + ExternZigHash::Hasher* m_zigHasher { nullptr }; +}; + +class JSHashPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSHashPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSHashPrototype* prototype = new (NotNull, JSC::allocateCell(vm)) JSHashPrototype(vm, structure); + prototype->finishCreation(vm); + return prototype; + } + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.plainObjectSpace(); + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + auto* structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + structure->setMayBePrototype(true); + return structure; + } + +private: + JSHashPrototype(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM& vm); +}; + +class JSHashConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSHashConstructor* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSObject* prototype) + { + JSHashConstructor* constructor = new (NotNull, JSC::allocateCell(vm)) JSHashConstructor(vm, structure); + constructor->finishCreation(vm, prototype); + return constructor; + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype); + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return &vm.internalFunctionSpace(); + } + +private: + JSHashConstructor(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, callHash, constructHash) + { + } + + void finishCreation(JSC::VM& vm, JSC::JSObject* prototype) + { + Base::finishCreation(vm, 2, "Hash"_s, PropertyAdditionMode::WithStructureTransition); + } +}; + +JSC_DECLARE_HOST_FUNCTION(jsHashProtoFuncUpdate); +JSC_DECLARE_HOST_FUNCTION(jsHashProtoFuncDigest); + +void setupJSHashClassStructure(JSC::LazyClassStructure::Initializer& init); + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSHmac.cpp b/src/bun.js/bindings/node/crypto/JSHmac.cpp new file mode 100644 index 0000000000..9df7691dd5 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSHmac.cpp @@ -0,0 +1,311 @@ +#include "JSHmac.h" +#include "util.h" +#include "BunClientData.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "NodeValidator.h" +#include + +namespace Bun { + +static const HashTableValue JSHmacPrototypeTableValues[] = { + { "update"_s, static_cast(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsHmacProtoFuncUpdate, 1 } }, + { "digest"_s, static_cast(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsHmacProtoFuncDigest, 1 } }, +}; + +const ClassInfo JSHmac::s_info = { "Hmac"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSHmac) }; +const ClassInfo JSHmacPrototype::s_info = { "HmacPrototype"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSHmacPrototype) }; +const ClassInfo JSHmacConstructor::s_info = { "HmacConstructor"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSHmacConstructor) }; + +JSHmac::JSHmac(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) +{ +} + +JSHmac::~JSHmac() +{ +} + +void JSHmac::finishCreation(JSC::VM& vm) +{ + Base::finishCreation(vm); +} + +template +JSC::GCClient::IsoSubspace* JSHmac::subspaceFor(JSC::VM& vm) +{ + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForJSHmac.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSHmac = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForJSHmac.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForJSHmac = std::forward(space); }); +} + +JSHmac* JSHmac::create(JSC::VM& vm, JSC::Structure* structure) +{ + JSHmac* instance = new (NotNull, JSC::allocateCell(vm)) JSHmac(vm, structure); + instance->finishCreation(vm); + return instance; +} + +void JSHmac::init(JSC::JSGlobalObject* globalObject, ThrowScope& scope, const StringView& algorithm, std::span keyData) +{ + // Get the digest algorithm from the algorithm name + const EVP_MD* md = ncrypto::getDigestByName(algorithm); + if (!md) { + Bun::ERR::CRYPTO_INVALID_DIGEST(scope, globalObject, algorithm); + return; + } + + // Create the HMAC context + m_ctx = ncrypto::HMACCtxPointer::New(); + + // Initialize HMAC with the key and algorithm + ncrypto::Buffer keyBuffer { + .data = keyData.data(), + .len = keyData.size(), + }; + + if (!m_ctx.init(keyBuffer, md)) { + m_ctx.reset(); + throwCryptoError(globalObject, scope, ERR_get_error(), "Failed to initialize HMAC context"_s); + return; + } +} + +bool JSHmac::update(std::span input) +{ + // Update the HMAC with the data + ncrypto::Buffer buffer { + .data = input.data(), + .len = input.size(), + }; + + return m_ctx.update(buffer); +} + +void JSHmacPrototype::finishCreation(JSC::VM& vm) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSHmac::info(), JSHmacPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +JSC_DEFINE_HOST_FUNCTION(jsHmacProtoFuncUpdate, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + // Get the HMAC instance + JSValue thisHmac = callFrame->thisValue(); + JSHmac* hmac = jsDynamicCast(thisHmac); + + // Check if the HMAC is already finalized + if (hmac->m_finalized) { + return Bun::ERR::CRYPTO_HASH_FINALIZED(scope, globalObject); + } + + JSValue wrappedHmac = callFrame->argument(0); + JSC::JSValue inputValue = callFrame->argument(1); + JSValue encodingValue = callFrame->argument(2); + + // Process the inputValue + if (inputValue.isString()) { + // Handle string inputValue + JSString* inputString = inputValue.toString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + + auto encoding = parseEnumeration(*globalObject, encodingValue).value_or(WebCore::BufferEncodingType::utf8); + RETURN_IF_EXCEPTION(scope, {}); + + // validateEncoding() + if (encoding == WebCore::BufferEncodingType::hex && inputString->length() % 2 != 0) { + return Bun::ERR::INVALID_ARG_VALUE(scope, globalObject, "encoding"_s, encodingValue, makeString("is invalid for data of length "_s, inputString->length())); + } + + JSValue converted = JSValue::decode(WebCore::constructFromEncoding(globalObject, inputString, encoding)); + RETURN_IF_EXCEPTION(scope, {}); + + auto* convertedView = jsDynamicCast(converted); + + if (!hmac->update(std::span { reinterpret_cast(convertedView->vector()), convertedView->byteLength() })) { + return Bun::ERR::CRYPTO_HASH_UPDATE_FAILED(scope, globalObject); + } + + return JSValue::encode(wrappedHmac); + } else if (auto* view = JSC::jsDynamicCast(inputValue)) { + if (!hmac->update(std::span { reinterpret_cast(view->vector()), view->byteLength() })) { + return Bun::ERR::CRYPTO_HASH_UPDATE_FAILED(scope, globalObject); + } + + return JSValue::encode(wrappedHmac); + } + + return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "data"_s, "string or an instance of Buffer, TypedArray, or DataView"_s, inputValue); +} + +JSC_DEFINE_HOST_FUNCTION(jsHmacProtoFuncDigest, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + // Get the HMAC instance + JSHmac* hmac = jsDynamicCast(callFrame->thisValue()); + + // Check if already finalized, return empty buffer if already finalized + if (hmac->m_finalized) { + auto* emptyBuffer = JSC::JSUint8Array::create(lexicalGlobalObject, globalObject->JSBufferSubclassStructure(), 0); + RETURN_IF_EXCEPTION(scope, {}); + + // Handle encoding if provided + JSC::JSValue encodingValue = callFrame->argument(0); + + auto encoding = parseEnumeration(*lexicalGlobalObject, encodingValue); + RETURN_IF_EXCEPTION(scope, {}); + + if (encoding.has_value() && encoding.value() != BufferEncodingType::buffer) { + return JSValue::encode(jsEmptyString(vm)); + } + + return JSValue::encode(emptyBuffer); + } + + // Handle encoding if provided + JSC::JSValue encodingValue = callFrame->argument(0); + + BufferEncodingType encoding = BufferEncodingType::buffer; + + if (encodingValue.pureToBoolean() != TriState::False) { + // this value must stringify + // https://github.com/nodejs/node/blob/db00f9401882297e7e2e85c9e3ef042888074eaf/lib/internal/crypto/hash.js#L166 + WTF::String encodingString = encodingValue.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + encoding = parseEnumerationFromString(encodingString).value_or(BufferEncodingType::buffer); + RETURN_IF_EXCEPTION(scope, {}); + } + + unsigned char mdValue[EVP_MAX_MD_SIZE]; + ncrypto::Buffer mdBuffer { + .data = mdValue, + .len = sizeof(mdValue), + }; + + if (hmac->m_ctx) { + if (!hmac->m_ctx.digestInto(&mdBuffer)) { + hmac->m_ctx.reset(); + throwCryptoError(lexicalGlobalObject, scope, ERR_get_error(), "Failed to digest HMAC"_s); + return {}; + } + hmac->m_ctx.reset(); + } + + // We shouldn't set finalized if coming from _flush, but this + // works because m_ctx is reset after digest + hmac->m_finalized = true; + + return StringBytes::encode( + lexicalGlobalObject, + scope, + std::span { reinterpret_cast(mdBuffer.data), mdBuffer.len }, + encoding); +} + +JSC_DEFINE_HOST_FUNCTION(constructHmac, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* zigGlobalObject = defaultGlobalObject(globalObject); + JSC::Structure* structure = zigGlobalObject->m_JSHmacClassStructure.get(zigGlobalObject); + + // Handle new target + JSC::JSValue newTarget = callFrame->newTarget(); + if (UNLIKELY(zigGlobalObject->m_JSHmacClassStructure.constructor(zigGlobalObject) != newTarget)) { + if (!newTarget) { + throwTypeError(globalObject, scope, "Class constructor Hmac cannot be invoked without 'new'"_s); + return {}; + } + + auto* functionGlobalObject = defaultGlobalObject(getFunctionRealm(globalObject, newTarget.getObject())); + RETURN_IF_EXCEPTION(scope, {}); + structure = InternalFunction::createSubclassStructure( + globalObject, newTarget.getObject(), functionGlobalObject->m_JSHmacClassStructure.get(functionGlobalObject)); + RETURN_IF_EXCEPTION(scope, {}); + } + + JSHmac* hmac = JSHmac::create(vm, structure); + + // Check if we have initialization arguments + JSValue algorithmValue = callFrame->argument(0); + Bun::V::validateString(scope, globalObject, algorithmValue, "hmac"_s); + RETURN_IF_EXCEPTION(scope, {}); + + // Get encoding next before stringifying algorithm + JSValue options = callFrame->argument(2); + JSValue encodingValue = jsUndefined(); + if (options.isObject()) { + encodingValue = options.get(globalObject, Identifier::fromString(vm, "encoding"_s)); + RETURN_IF_EXCEPTION(scope, {}); + + if (!encodingValue.isNull()) { + Bun::V::validateString(scope, globalObject, encodingValue, "options.encoding"_s); + RETURN_IF_EXCEPTION(scope, {}); + } + } + + WTF::String algorithm = algorithmValue.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + + JSValue key = callFrame->argument(1); + + Vector keyData; + prepareSecretKey(globalObject, scope, keyData, key, encodingValue); + RETURN_IF_EXCEPTION(scope, {}); + + hmac->init(globalObject, scope, algorithm, keyData.span()); + RETURN_IF_EXCEPTION(scope, {}); + + return JSC::JSValue::encode(hmac); +} + +JSC_DEFINE_HOST_FUNCTION(callHmac, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + throwTypeError(globalObject, scope, "Class constructor Hmac cannot be invoked without 'new'"_s); + return JSC::encodedJSUndefined(); +} + +JSC::Structure* JSHmacConstructor::createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) +{ + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info()); +} + +void setupJSHmacClassStructure(JSC::LazyClassStructure::Initializer& init) +{ + auto* prototypeStructure = JSHmacPrototype::createStructure(init.vm, init.global, init.global->objectPrototype()); + auto* prototype = JSHmacPrototype::create(init.vm, init.global, prototypeStructure); + + auto* constructorStructure = JSHmacConstructor::createStructure(init.vm, init.global, init.global->functionPrototype()); + auto* constructor = JSHmacConstructor::create(init.vm, constructorStructure, prototype); + + auto* structure = JSHmac::createStructure(init.vm, init.global, prototype); + init.setPrototype(prototype); + init.setStructure(structure); + init.setConstructor(constructor); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSHmac.h b/src/bun.js/bindings/node/crypto/JSHmac.h new file mode 100644 index 0000000000..920c2501a7 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSHmac.h @@ -0,0 +1,125 @@ +#pragma once + +#include "root.h" +#include "BunClientData.h" +#include +#include +#include +#include "ncrypto.h" +#include "util.h" +#include "JSBuffer.h" +#include "JSDOMConvertEnumeration.h" +#include +#include + +namespace Bun { + +JSC_DECLARE_HOST_FUNCTION(callHmac); +JSC_DECLARE_HOST_FUNCTION(constructHmac); + +class JSHmac final : public JSC::JSDestructibleObject { +public: + using Base = JSC::JSDestructibleObject; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSHmac* create(JSC::VM& vm, JSC::Structure* structure); + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + + JSHmac(JSC::VM& vm, JSC::Structure* structure); + ~JSHmac(); + + void finishCreation(JSC::VM& vm); + void init(JSC::JSGlobalObject* globalObject, ThrowScope& scope, const StringView& algorithm, std::span keyData); + bool update(std::span input); + + ncrypto::HMACCtxPointer m_ctx; + bool m_finalized { false }; +}; + +class JSHmacPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSHmacPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSHmacPrototype* prototype = new (NotNull, JSC::allocateCell(vm)) JSHmacPrototype(vm, structure); + prototype->finishCreation(vm); + return prototype; + } + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.plainObjectSpace(); + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + auto* structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + structure->setMayBePrototype(true); + return structure; + } + +private: + JSHmacPrototype(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM& vm); +}; + +class JSHmacConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSHmacConstructor* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSObject* prototype) + { + JSHmacConstructor* constructor = new (NotNull, JSC::allocateCell(vm)) JSHmacConstructor(vm, structure); + constructor->finishCreation(vm, prototype); + return constructor; + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype); + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return &vm.internalFunctionSpace(); + } + +private: + JSHmacConstructor(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, callHmac, constructHmac) + { + } + + void finishCreation(JSC::VM& vm, JSC::JSObject* prototype) + { + Base::finishCreation(vm, 2, "Hmac"_s, PropertyAdditionMode::WithStructureTransition); + } +}; + +JSC_DECLARE_HOST_FUNCTION(jsHmacProtoFuncUpdate); +JSC_DECLARE_HOST_FUNCTION(jsHmacProtoFuncDigest); + +void setupJSHmacClassStructure(JSC::LazyClassStructure::Initializer& init); + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSSign.cpp b/src/bun.js/bindings/node/crypto/JSSign.cpp index 66cc90bd9f..1908465d3a 100644 --- a/src/bun.js/bindings/node/crypto/JSSign.cpp +++ b/src/bun.js/bindings/node/crypto/JSSign.cpp @@ -50,15 +50,15 @@ JSSign::JSSign(JSC::VM& vm, JSC::Structure* structure) { } -void JSSign::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +void JSSign::finishCreation(JSC::VM& vm) { Base::finishCreation(vm); } -JSSign* JSSign::create(JSC::VM& vm, JSC::Structure* structure, JSC::JSGlobalObject* globalObject) +JSSign* JSSign::create(JSC::VM& vm, JSC::Structure* structure) { JSSign* sign = new (NotNull, JSC::allocateCell(vm)) JSSign(vm, structure); - sign->finishCreation(vm, globalObject); + sign->finishCreation(vm); return sign; } @@ -72,7 +72,7 @@ JSC::GCClient::IsoSubspace* JSSign::subspaceFor(JSC::VM& vm) { if constexpr (mode == JSC::SubspaceAccess::Concurrently) return nullptr; - return WebCore::subspaceForImpl( + return WebCore::subspaceForImpl( vm, [](auto& spaces) { return spaces.m_clientSubspaceForJSSign.get(); }, [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSSign = std::forward(space); }, @@ -228,21 +228,23 @@ JSC_DEFINE_HOST_FUNCTION(jsSignProtoFuncUpdate, (JSC::JSGlobalObject * globalObj return JSValue::encode({}); } + JSValue wrappedSign = callFrame->argument(0); + // Check that we have at least 1 argument (the data) - if (callFrame->argumentCount() < 1) { + if (callFrame->argumentCount() < 2) { throwVMError(globalObject, scope, "Sign.prototype.update requires at least 1 argument"_s); return JSValue::encode({}); } // Get the data argument - JSC::JSValue data = callFrame->argument(0); + JSC::JSValue data = callFrame->argument(1); // if it's a string, using encoding for decode. if it's a buffer, just use the buffer if (data.isString()) { JSString* dataString = data.toString(globalObject); RETURN_IF_EXCEPTION(scope, JSValue::encode({})); - JSValue encodingValue = callFrame->argument(1); + JSValue encodingValue = callFrame->argument(2); auto encoding = parseEnumeration(*globalObject, encodingValue).value_or(BufferEncodingType::utf8); RETURN_IF_EXCEPTION(scope, {}); @@ -258,7 +260,7 @@ JSC_DEFINE_HOST_FUNCTION(jsSignProtoFuncUpdate, (JSC::JSGlobalObject * globalObj updateWithBufferView(globalObject, thisObject, view); RETURN_IF_EXCEPTION(scope, JSValue::encode({})); - return JSValue::encode(callFrame->thisValue()); + return JSValue::encode(wrappedSign); } if (!data.isCell() || !JSC::isTypedArrayTypeIncludingDataView(data.asCell()->type())) { @@ -271,7 +273,7 @@ JSC_DEFINE_HOST_FUNCTION(jsSignProtoFuncUpdate, (JSC::JSGlobalObject * globalObj updateWithBufferView(globalObject, thisObject, view); RETURN_IF_EXCEPTION(scope, JSValue::encode({})); - return JSValue::encode(callFrame->thisValue()); + return JSValue::encode(wrappedSign); } return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "data"_s, "string or an instance of Buffer, TypedArray, or DataView"_s, data); @@ -372,256 +374,6 @@ JSUint8Array* signWithKey(JSC::JSGlobalObject* lexicalGlobalObject, JSSign* this return JSC::JSUint8Array::create(lexicalGlobalObject, globalObject->JSBufferSubclassStructure(), WTFMove(sigBuffer), 0, sigBuf.len); } -std::optional preparePrivateKey(JSGlobalObject* lexicalGlobalObject, JSC::ThrowScope& scope, JSValue maybeKey, std::optional algorithmIdentifier) -{ - ncrypto::ClearErrorOnReturn clearError; - - VM& vm = lexicalGlobalObject->vm(); - - if (!maybeKey.isCell()) { - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key"_s, "ArrayBuffer, Buffer, TypedArray, DataView, string, KeyObject, or CryptoKey"_s, maybeKey); - return std::nullopt; - } - - auto optionsCell = maybeKey.asCell(); - auto optionsType = optionsCell->type(); - - if (optionsCell->inherits()) { - auto* cryptoKey = jsCast(optionsCell); - - // convert it to a key object, then to EVPKeyPointer - auto& key = cryptoKey->wrapped(); - - if (algorithmIdentifier) { - switch (key.keyClass()) { - case CryptoKeyClass::RSA: { - const auto& rsa = downcast(key); - CryptoAlgorithmIdentifier restrictHash; - bool isRestricted = rsa.isRestrictedToHash(restrictHash); - if (isRestricted && algorithmIdentifier.value() != restrictHash) { - JSC::throwTypeError(lexicalGlobalObject, scope, "digest not allowed"_s); - return std::nullopt; - } - } - default: - break; - } - } - - AsymmetricKeyValue keyValue(key); - if (keyValue.key) { - EVP_PKEY_up_ref(keyValue.key); - ncrypto::EVPKeyPointer keyPtr(keyValue.key); - return keyPtr; - } - - throwCryptoOperationFailed(lexicalGlobalObject, scope); - return std::nullopt; - } else if (maybeKey.isObject()) { - JSObject* optionsObj = optionsCell->getObject(); - const auto& names = WebCore::builtinNames(vm); - - if (auto val = optionsObj->getIfPropertyExists(lexicalGlobalObject, names.bunNativePtrPrivateName())) { - if (val.isCell() && val.inherits()) { - auto* cryptoKey = jsCast(val.asCell()); - - auto& key = cryptoKey->wrapped(); - - if (algorithmIdentifier) { - switch (key.keyClass()) { - case CryptoKeyClass::RSA: { - const auto& rsa = downcast(key); - CryptoAlgorithmIdentifier restrictHash; - bool isRestricted = rsa.isRestrictedToHash(restrictHash); - if (isRestricted && algorithmIdentifier.value() != restrictHash) { - JSC::throwTypeError(lexicalGlobalObject, scope, "digest not allowed"_s); - return std::nullopt; - } - } - default: - break; - } - } - - AsymmetricKeyValue keyValue(key); - if (keyValue.key) { - EVP_PKEY_up_ref(keyValue.key); - ncrypto::EVPKeyPointer keyPtr(keyValue.key); - return keyPtr; - } - throwCryptoOperationFailed(lexicalGlobalObject, scope); - return std::nullopt; - } - } else if (optionsType >= Int8ArrayType && optionsType <= DataViewType) { - auto dataBuf = KeyObject__GetBuffer(maybeKey); - if (dataBuf.hasException()) { - return std::nullopt; - } - - ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig config; - config.format = ncrypto::EVPKeyPointer::PKFormatType::PEM; - - auto buffer = dataBuf.releaseReturnValue(); - ncrypto::Buffer ncryptoBuf { - .data = buffer.data(), - .len = buffer.size(), - }; - - auto res = ncrypto::EVPKeyPointer::TryParsePrivateKey(config, ncryptoBuf); - if (res) { - ncrypto::EVPKeyPointer keyPtr(WTFMove(res.value)); - return keyPtr; - } - - if (res.error.value() == ncrypto::EVPKeyPointer::PKParseError::NEED_PASSPHRASE) { - Bun::ERR::MISSING_PASSPHRASE(scope, lexicalGlobalObject, "Passphrase required for encrypted key"_s); - return std::nullopt; - } - - throwCryptoError(lexicalGlobalObject, scope, res.openssl_error.value_or(0), "Failed to read private key"_s); - return std::nullopt; - } - - JSValue key = optionsObj->get(lexicalGlobalObject, Identifier::fromString(vm, "key"_s)); - RETURN_IF_EXCEPTION(scope, {}); - JSValue encodingValue = optionsObj->get(lexicalGlobalObject, Identifier::fromString(vm, "encoding"_s)); - RETURN_IF_EXCEPTION(scope, {}); - JSValue passphrase = optionsObj->get(lexicalGlobalObject, Identifier::fromString(vm, "passphrase"_s)); - RETURN_IF_EXCEPTION(scope, {}); - JSValue formatValue = optionsObj->get(lexicalGlobalObject, Identifier::fromString(vm, "format"_s)); - RETURN_IF_EXCEPTION(scope, {}); - WTF::StringView formatStr = WTF::nullStringView(); - if (formatValue.isString()) { - auto str = formatValue.toString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, {}); - formatStr = str->view(lexicalGlobalObject); - } - - if (!key.isCell()) { - if (formatStr == "jwk"_s) { - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.key"_s, "object"_s, key); - } else { - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.key"_s, "ArrayBuffer, Buffer, TypedArray, DataView, string, KeyObject, or CryptoKey"_s, key); - } - return std::nullopt; - } - - String encodingString = encodingValue.toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, {}); - - auto keyCell = key.asCell(); - auto keyCellType = keyCell->type(); - if (keyCell->inherits()) { - auto* cryptoKey = jsCast(keyCell); - auto& key = cryptoKey->wrapped(); - - if (algorithmIdentifier) { - switch (key.keyClass()) { - case CryptoKeyClass::RSA: { - const auto& rsa = downcast(key); - CryptoAlgorithmIdentifier restrictHash; - bool isRestricted = rsa.isRestrictedToHash(restrictHash); - if (isRestricted && algorithmIdentifier.value() != restrictHash) { - JSC::throwTypeError(lexicalGlobalObject, scope, "digest not allowed"_s); - return std::nullopt; - } - } - default: - break; - } - } - - AsymmetricKeyValue keyValue(key); - if (keyValue.key) { - EVP_PKEY_up_ref(keyValue.key); - ncrypto::EVPKeyPointer keyPtr(keyValue.key); - return keyPtr; - } - throwCryptoOperationFailed(lexicalGlobalObject, scope); - return std::nullopt; - } else if (key.isObject()) { - JSObject* keyObj = key.getObject(); - if (auto keyVal = keyObj->getIfPropertyExists(lexicalGlobalObject, names.bunNativePtrPrivateName())) { - if (keyVal.isCell() && keyVal.inherits()) { - auto* cryptoKey = jsCast(keyVal.asCell()); - - auto& key = cryptoKey->wrapped(); - AsymmetricKeyValue keyValue(key); - if (keyValue.key) { - EVP_PKEY_up_ref(keyValue.key); - ncrypto::EVPKeyPointer keyPtr(WTFMove(keyValue.key)); - return keyPtr; - } - throwCryptoOperationFailed(lexicalGlobalObject, scope); - return std::nullopt; - } - } else if (keyCellType >= Int8ArrayType && keyCellType <= DataViewType) { - auto dataBuf = KeyObject__GetBuffer(key); - if (dataBuf.hasException()) { - return std::nullopt; - } - - ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig config; - config.format = parseKeyFormat(lexicalGlobalObject, formatValue, "options.format"_s, ncrypto::EVPKeyPointer::PKFormatType::PEM); - - config.passphrase = passphraseFromBufferSource(lexicalGlobalObject, scope, passphrase); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - // Get the type value from options - JSValue typeValue = optionsObj->get(lexicalGlobalObject, Identifier::fromString(vm, "type"_s)); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - // Parse key type for private key - auto keyType = parseKeyType(lexicalGlobalObject, typeValue, config.format == ncrypto::EVPKeyPointer::PKFormatType::DER, WTF::nullStringView(), false, "options.type"_s); - RETURN_IF_EXCEPTION(scope, std::nullopt); - config.type = keyType.value_or(ncrypto::EVPKeyPointer::PKEncodingType::PKCS1); - - auto buffer = dataBuf.releaseReturnValue(); - ncrypto::Buffer ncryptoBuf { - .data = buffer.data(), - .len = buffer.size(), - }; - - auto res = ncrypto::EVPKeyPointer::TryParsePrivateKey(config, ncryptoBuf); - if (!res) { - if (res.error.value() == ncrypto::EVPKeyPointer::PKParseError::NEED_PASSPHRASE) { - Bun::ERR::MISSING_PASSPHRASE(scope, lexicalGlobalObject, "Passphrase required for encrypted key"_s); - return std::nullopt; - } - - throwCryptoError(lexicalGlobalObject, scope, res.openssl_error.value_or(0), "Failed to read private key"_s); - return std::nullopt; - } - - ncrypto::EVPKeyPointer keyPtr(WTFMove(res.value)); - return keyPtr; - } else if (formatStr == "jwk"_s) { - bool isPublic = false; - return getKeyObjectHandleFromJwk(lexicalGlobalObject, scope, key, isPublic); - } - } else if (formatStr == "jwk"_s) { - bool isPublic = false; - return getKeyObjectHandleFromJwk(lexicalGlobalObject, scope, key, isPublic); - } else if (key.isString()) { - WTF::String keyStr = key.toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - return keyFromString(lexicalGlobalObject, scope, keyStr, passphrase); - } - - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.key"_s, "ArrayBuffer, Buffer, TypedArray, DataView, string, KeyObject, or CryptoKey"_s, key); - return std::nullopt; - } else if (maybeKey.isString()) { - WTF::String keyStr = maybeKey.toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - return keyFromString(lexicalGlobalObject, scope, keyStr, jsUndefined()); - } - - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key"_s, "ArrayBuffer, Buffer, TypedArray, DataView, string, KeyObject, or CryptoKey"_s, maybeKey); - return std::nullopt; -} - JSC_DEFINE_HOST_FUNCTION(jsSignProtoFuncSign, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { ncrypto::ClearErrorOnReturn clearError; @@ -720,7 +472,7 @@ JSC_DEFINE_HOST_FUNCTION(constructSign, (JSC::JSGlobalObject * globalObject, JSC RETURN_IF_EXCEPTION(scope, {}); } - return JSC::JSValue::encode(JSSign::create(vm, structure, globalObject)); + return JSC::JSValue::encode(JSSign::create(vm, structure)); } JSC_DEFINE_HOST_FUNCTION(jsSignOneShot, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) diff --git a/src/bun.js/bindings/node/crypto/JSSign.h b/src/bun.js/bindings/node/crypto/JSSign.h index bfe4e08411..2680e35f53 100644 --- a/src/bun.js/bindings/node/crypto/JSSign.h +++ b/src/bun.js/bindings/node/crypto/JSSign.h @@ -21,7 +21,7 @@ public: using Base = JSC::JSDestructibleObject; static constexpr unsigned StructureFlags = Base::StructureFlags; - static JSSign* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSGlobalObject* globalObject); + static JSSign* create(JSC::VM& vm, JSC::Structure* structure); static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype); template @@ -33,7 +33,7 @@ public: private: JSSign(JSC::VM& vm, JSC::Structure* structure); - void finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject); + void finishCreation(JSC::VM& vm); }; class JSSignPrototype final : public JSC::JSNonFinalObject { diff --git a/src/bun.js/bindings/node/crypto/JSVerify.cpp b/src/bun.js/bindings/node/crypto/JSVerify.cpp index d359848fb2..f52084187d 100644 --- a/src/bun.js/bindings/node/crypto/JSVerify.cpp +++ b/src/bun.js/bindings/node/crypto/JSVerify.cpp @@ -80,7 +80,7 @@ JSC::GCClient::IsoSubspace* JSVerify::subspaceFor(JSC::VM& vm) { if constexpr (mode == JSC::SubspaceAccess::Concurrently) return nullptr; - return WebCore::subspaceForImpl( + return WebCore::subspaceForImpl( vm, [](auto& spaces) { return spaces.m_clientSubspaceForJSVerify.get(); }, [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSVerify = std::forward(space); }, @@ -208,21 +208,23 @@ JSC_DEFINE_HOST_FUNCTION(jsVerifyProtoFuncUpdate, (JSGlobalObject * globalObject return JSValue::encode({}); } + JSValue wrappedVerify = callFrame->argument(0); + // Check that we have at least 1 argument (the data) - if (callFrame->argumentCount() < 1) { + if (callFrame->argumentCount() < 2) { throwVMError(globalObject, scope, "Verify.prototype.update requires at least 1 argument"_s); return JSValue::encode({}); } // Get the data argument - JSC::JSValue data = callFrame->argument(0); + JSC::JSValue data = callFrame->argument(1); // if it's a string, using encoding for decode. if it's a buffer, just use the buffer if (data.isString()) { JSString* dataString = data.toString(globalObject); RETURN_IF_EXCEPTION(scope, JSValue::encode({})); - JSValue encodingValue = callFrame->argument(1); + JSValue encodingValue = callFrame->argument(2); auto encoding = parseEnumeration(*globalObject, encodingValue).value_or(BufferEncodingType::utf8); RETURN_IF_EXCEPTION(scope, {}); @@ -257,7 +259,7 @@ JSC_DEFINE_HOST_FUNCTION(jsVerifyProtoFuncUpdate, (JSGlobalObject * globalObject return JSValue::encode({}); } - return JSValue::encode(callFrame->thisValue()); + return JSValue::encode(wrappedVerify); } if (!data.isCell() || !JSC::isTypedArrayTypeIncludingDataView(data.asCell()->type())) { @@ -287,7 +289,7 @@ JSC_DEFINE_HOST_FUNCTION(jsVerifyProtoFuncUpdate, (JSGlobalObject * globalObject return JSValue::encode({}); } - return JSValue::encode(callFrame->thisValue()); + return JSValue::encode(wrappedVerify); } return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "data"_s, "string or an instance of Buffer, TypedArray, or DataView"_s, data); diff --git a/src/bun.js/bindings/node/crypto/NodeCrypto.cpp b/src/bun.js/bindings/node/crypto/NodeCrypto.cpp index 3e05218d03..9465df8e17 100644 --- a/src/bun.js/bindings/node/crypto/NodeCrypto.cpp +++ b/src/bun.js/bindings/node/crypto/NodeCrypto.cpp @@ -42,6 +42,8 @@ #include "NodeValidator.h" #include "JSSign.h" #include "JSVerify.h" +#include "JSHmac.h" +#include "JSHash.h" using namespace JSC; using namespace Bun; @@ -468,6 +470,12 @@ JSValue createNodeCryptoBinding(Zig::GlobalObject* globalObject) obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "verify"_s)), JSFunction::create(vm, globalObject, 4, "verify"_s, jsVerifyOneShot, ImplementationVisibility::Public, NoIntrinsic), 0); + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "Hmac"_s)), + globalObject->m_JSHmacClassStructure.constructor(globalObject)); + + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "Hash"_s)), + globalObject->m_JSHashClassStructure.constructor(globalObject)); + return obj; } diff --git a/src/bun.js/bindings/node/crypto/util.cpp b/src/bun.js/bindings/node/crypto/util.cpp index 1b350603ea..09375256e8 100644 --- a/src/bun.js/bindings/node/crypto/util.cpp +++ b/src/bun.js/bindings/node/crypto/util.cpp @@ -6,11 +6,91 @@ #include "BunString.h" #include "JSBuffer.h" #include "JSDOMConvertEnumeration.h" +#include "JSCryptoKey.h" +#include "CryptoKeyRSA.h" +#include "AsymmetricKeyValue.h" +#include "KeyObject.h" +#include "JSVerify.h" +#include +#include "CryptoKeyRaw.h" namespace Bun { using namespace JSC; +namespace ExternZigHash { +struct Hasher; + +extern "C" Hasher* Bun__CryptoHasherExtern__getByName(Zig::GlobalObject* globalObject, const char* name, size_t nameLen); +Hasher* getByName(Zig::GlobalObject* globalObject, const StringView& name) +{ + auto utf8 = name.utf8(); + return Bun__CryptoHasherExtern__getByName(globalObject, utf8.data(), utf8.length()); +} + +extern "C" Hasher* Bun__CryptoHasherExtern__getFromOther(Zig::GlobalObject* global, Hasher* hasher); +Hasher* getFromOther(Zig::GlobalObject* globalObject, Hasher* hasher) +{ + return Bun__CryptoHasherExtern__getFromOther(globalObject, hasher); +} + +extern "C" void Bun__CryptoHasherExtern__destroy(Hasher* hasher); +void destroy(Hasher* hasher) +{ + Bun__CryptoHasherExtern__destroy(hasher); +} + +extern "C" bool Bun__CryptoHasherExtern__update(Hasher* hasher, const uint8_t* data, size_t len); +bool update(Hasher* hasher, std::span data) +{ + return Bun__CryptoHasherExtern__update(hasher, data.data(), data.size()); +} + +extern "C" uint32_t Bun__CryptoHasherExtern__digest(Hasher* hasher, Zig::GlobalObject* globalObject, uint8_t* out, size_t outLen); +uint32_t digest(Hasher* hasher, Zig::GlobalObject* globalObject, std::span out) +{ + return Bun__CryptoHasherExtern__digest(hasher, globalObject, out.data(), out.size()); +} + +extern "C" uint32_t Bun__CryptoHasherExtern__getDigestSize(Hasher* hasher); +uint32_t getDigestSize(Hasher* hasher) +{ + return Bun__CryptoHasherExtern__getDigestSize(hasher); +} + +}; // namespace ExternZigHash + +namespace StringBytes { + +// Identical to jsBufferToString, but `buffer` encoding will return an Buffer instead of a string +EncodedJSValue encode(JSGlobalObject* lexicalGlobalObject, ThrowScope& scope, std::span bytes, BufferEncodingType encoding) +{ + VM& vm = lexicalGlobalObject->vm(); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + if (UNLIKELY(!bytes.size() and encoding != BufferEncodingType::buffer)) { + return JSValue::encode(jsEmptyString(vm)); + } + + switch (encoding) { + case BufferEncodingType::buffer: { + auto buffer = JSC::ArrayBuffer::tryCreateUninitialized(bytes.size(), 1); + if (!buffer) { + throwOutOfMemoryError(lexicalGlobalObject, scope); + } + + memcpy(buffer->data(), bytes.data(), bytes.size()); + + return JSValue::encode(JSC::JSUint8Array::create(lexicalGlobalObject, globalObject->JSBufferSubclassStructure(), WTFMove(buffer), 0, bytes.size())); + } + default: { + return jsBufferToStringFromBytes(lexicalGlobalObject, scope, bytes, encoding); + } + } +} + +} + std::optional keyFromString(JSGlobalObject* lexicalGlobalObject, JSC::ThrowScope& scope, const WTF::StringView& keyView, JSValue passphraseValue) { ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig config; @@ -352,4 +432,384 @@ JSC::JSArrayBufferView* getArrayBufferOrView(JSGlobalObject* globalObject, Throw return view; } + +std::optional preparePrivateKey(JSGlobalObject* lexicalGlobalObject, JSC::ThrowScope& scope, JSValue maybeKey, std::optional algorithmIdentifier) +{ + ncrypto::ClearErrorOnReturn clearError; + + VM& vm = lexicalGlobalObject->vm(); + + if (!maybeKey.isCell()) { + Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key"_s, "ArrayBuffer, Buffer, TypedArray, DataView, string, KeyObject, or CryptoKey"_s, maybeKey); + return std::nullopt; + } + + auto optionsCell = maybeKey.asCell(); + auto optionsType = optionsCell->type(); + + if (optionsCell->inherits()) { + auto* cryptoKey = jsCast(optionsCell); + + // convert it to a key object, then to EVPKeyPointer + auto& key = cryptoKey->wrapped(); + + if (algorithmIdentifier) { + switch (key.keyClass()) { + case CryptoKeyClass::RSA: { + const auto& rsa = downcast(key); + CryptoAlgorithmIdentifier restrictHash; + bool isRestricted = rsa.isRestrictedToHash(restrictHash); + if (isRestricted && algorithmIdentifier.value() != restrictHash) { + JSC::throwTypeError(lexicalGlobalObject, scope, "digest not allowed"_s); + return std::nullopt; + } + } + default: + break; + } + } + + AsymmetricKeyValue keyValue(key); + if (keyValue.key) { + EVP_PKEY_up_ref(keyValue.key); + ncrypto::EVPKeyPointer keyPtr(keyValue.key); + return keyPtr; + } + + throwCryptoOperationFailed(lexicalGlobalObject, scope); + return std::nullopt; + } else if (maybeKey.isObject()) { + JSObject* optionsObj = optionsCell->getObject(); + const auto& names = WebCore::builtinNames(vm); + + if (auto val = optionsObj->getIfPropertyExists(lexicalGlobalObject, names.bunNativePtrPrivateName())) { + if (val.isCell() && val.inherits()) { + auto* cryptoKey = jsCast(val.asCell()); + + auto& key = cryptoKey->wrapped(); + + if (algorithmIdentifier) { + switch (key.keyClass()) { + case CryptoKeyClass::RSA: { + const auto& rsa = downcast(key); + CryptoAlgorithmIdentifier restrictHash; + bool isRestricted = rsa.isRestrictedToHash(restrictHash); + if (isRestricted && algorithmIdentifier.value() != restrictHash) { + JSC::throwTypeError(lexicalGlobalObject, scope, "digest not allowed"_s); + return std::nullopt; + } + } + default: + break; + } + } + + AsymmetricKeyValue keyValue(key); + if (keyValue.key) { + EVP_PKEY_up_ref(keyValue.key); + ncrypto::EVPKeyPointer keyPtr(keyValue.key); + return keyPtr; + } + throwCryptoOperationFailed(lexicalGlobalObject, scope); + return std::nullopt; + } + } else if (optionsType >= Int8ArrayType && optionsType <= DataViewType) { + auto dataBuf = KeyObject__GetBuffer(maybeKey); + if (dataBuf.hasException()) { + return std::nullopt; + } + + ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig config; + config.format = ncrypto::EVPKeyPointer::PKFormatType::PEM; + + auto buffer = dataBuf.releaseReturnValue(); + ncrypto::Buffer ncryptoBuf { + .data = buffer.data(), + .len = buffer.size(), + }; + + auto res = ncrypto::EVPKeyPointer::TryParsePrivateKey(config, ncryptoBuf); + if (res) { + ncrypto::EVPKeyPointer keyPtr(WTFMove(res.value)); + return keyPtr; + } + + if (res.error.value() == ncrypto::EVPKeyPointer::PKParseError::NEED_PASSPHRASE) { + Bun::ERR::MISSING_PASSPHRASE(scope, lexicalGlobalObject, "Passphrase required for encrypted key"_s); + return std::nullopt; + } + + throwCryptoError(lexicalGlobalObject, scope, res.openssl_error.value_or(0), "Failed to read private key"_s); + return std::nullopt; + } + + JSValue key = optionsObj->get(lexicalGlobalObject, Identifier::fromString(vm, "key"_s)); + RETURN_IF_EXCEPTION(scope, {}); + JSValue encodingValue = optionsObj->get(lexicalGlobalObject, Identifier::fromString(vm, "encoding"_s)); + RETURN_IF_EXCEPTION(scope, {}); + JSValue passphrase = optionsObj->get(lexicalGlobalObject, Identifier::fromString(vm, "passphrase"_s)); + RETURN_IF_EXCEPTION(scope, {}); + JSValue formatValue = optionsObj->get(lexicalGlobalObject, Identifier::fromString(vm, "format"_s)); + RETURN_IF_EXCEPTION(scope, {}); + WTF::StringView formatStr = WTF::nullStringView(); + if (formatValue.isString()) { + auto str = formatValue.toString(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, {}); + formatStr = str->view(lexicalGlobalObject); + } + + if (!key.isCell()) { + if (formatStr == "jwk"_s) { + Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.key"_s, "object"_s, key); + } else { + Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.key"_s, "ArrayBuffer, Buffer, TypedArray, DataView, string, KeyObject, or CryptoKey"_s, key); + } + return std::nullopt; + } + + String encodingString = encodingValue.toWTFString(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, {}); + + auto keyCell = key.asCell(); + auto keyCellType = keyCell->type(); + if (keyCell->inherits()) { + auto* cryptoKey = jsCast(keyCell); + auto& key = cryptoKey->wrapped(); + + if (algorithmIdentifier) { + switch (key.keyClass()) { + case CryptoKeyClass::RSA: { + const auto& rsa = downcast(key); + CryptoAlgorithmIdentifier restrictHash; + bool isRestricted = rsa.isRestrictedToHash(restrictHash); + if (isRestricted && algorithmIdentifier.value() != restrictHash) { + JSC::throwTypeError(lexicalGlobalObject, scope, "digest not allowed"_s); + return std::nullopt; + } + } + default: + break; + } + } + + AsymmetricKeyValue keyValue(key); + if (keyValue.key) { + EVP_PKEY_up_ref(keyValue.key); + ncrypto::EVPKeyPointer keyPtr(keyValue.key); + return keyPtr; + } + throwCryptoOperationFailed(lexicalGlobalObject, scope); + return std::nullopt; + } else if (key.isObject()) { + JSObject* keyObj = key.getObject(); + if (auto keyVal = keyObj->getIfPropertyExists(lexicalGlobalObject, names.bunNativePtrPrivateName())) { + if (keyVal.isCell() && keyVal.inherits()) { + auto* cryptoKey = jsCast(keyVal.asCell()); + + auto& key = cryptoKey->wrapped(); + AsymmetricKeyValue keyValue(key); + if (keyValue.key) { + EVP_PKEY_up_ref(keyValue.key); + ncrypto::EVPKeyPointer keyPtr(WTFMove(keyValue.key)); + return keyPtr; + } + throwCryptoOperationFailed(lexicalGlobalObject, scope); + return std::nullopt; + } + } else if (keyCellType >= Int8ArrayType && keyCellType <= DataViewType) { + auto dataBuf = KeyObject__GetBuffer(key); + if (dataBuf.hasException()) { + return std::nullopt; + } + + ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig config; + config.format = parseKeyFormat(lexicalGlobalObject, formatValue, "options.format"_s, ncrypto::EVPKeyPointer::PKFormatType::PEM); + + config.passphrase = passphraseFromBufferSource(lexicalGlobalObject, scope, passphrase); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + // Get the type value from options + JSValue typeValue = optionsObj->get(lexicalGlobalObject, Identifier::fromString(vm, "type"_s)); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + // Parse key type for private key + auto keyType = parseKeyType(lexicalGlobalObject, typeValue, config.format == ncrypto::EVPKeyPointer::PKFormatType::DER, WTF::nullStringView(), false, "options.type"_s); + RETURN_IF_EXCEPTION(scope, std::nullopt); + config.type = keyType.value_or(ncrypto::EVPKeyPointer::PKEncodingType::PKCS1); + + auto buffer = dataBuf.releaseReturnValue(); + ncrypto::Buffer ncryptoBuf { + .data = buffer.data(), + .len = buffer.size(), + }; + + auto res = ncrypto::EVPKeyPointer::TryParsePrivateKey(config, ncryptoBuf); + if (!res) { + if (res.error.value() == ncrypto::EVPKeyPointer::PKParseError::NEED_PASSPHRASE) { + Bun::ERR::MISSING_PASSPHRASE(scope, lexicalGlobalObject, "Passphrase required for encrypted key"_s); + return std::nullopt; + } + + throwCryptoError(lexicalGlobalObject, scope, res.openssl_error.value_or(0), "Failed to read private key"_s); + return std::nullopt; + } + + ncrypto::EVPKeyPointer keyPtr(WTFMove(res.value)); + return keyPtr; + } else if (formatStr == "jwk"_s) { + bool isPublic = false; + return getKeyObjectHandleFromJwk(lexicalGlobalObject, scope, key, isPublic); + } + } else if (formatStr == "jwk"_s) { + bool isPublic = false; + return getKeyObjectHandleFromJwk(lexicalGlobalObject, scope, key, isPublic); + } else if (key.isString()) { + WTF::String keyStr = key.toWTFString(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + return keyFromString(lexicalGlobalObject, scope, keyStr, passphrase); + } + + Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.key"_s, "ArrayBuffer, Buffer, TypedArray, DataView, string, KeyObject, or CryptoKey"_s, key); + return std::nullopt; + } else if (maybeKey.isString()) { + WTF::String keyStr = maybeKey.toWTFString(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + return keyFromString(lexicalGlobalObject, scope, keyStr, jsUndefined()); + } + + Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key"_s, "ArrayBuffer, Buffer, TypedArray, DataView, string, KeyObject, or CryptoKey"_s, maybeKey); + return std::nullopt; +} + +// takes a key value and encoding value +// - if key is string, returns the key as a vector of bytes, using encoding if !isUndefined +// - if key is isAnyArrayBuffer, return the bytes +// - if !bufferOnly: +// - if key is KeyObject with native crypto key, extract the key material +// - if key is CryptoKey, ensure `type` is secret, then extract the key material +// - none matched, throw error for INVALID_ARG_TYPE +void prepareSecretKey(JSGlobalObject* globalObject, ThrowScope& scope, Vector& out, JSValue key, JSValue encodingValue, bool bufferOnly) +{ + VM& vm = globalObject->vm(); + + // Handle KeyObject (if not bufferOnly) + if (!bufferOnly && key.isObject()) { + JSObject* obj = key.getObject(); + auto& names = WebCore::builtinNames(vm); + + // Check for BunNativePtr on the object + if (auto val = obj->getIfPropertyExists(globalObject, names.bunNativePtrPrivateName())) { + if (auto* cryptoKey = jsDynamicCast(val.asCell())) { + + JSValue typeValue = obj->get(globalObject, Identifier::fromString(vm, "type"_s)); + RETURN_IF_EXCEPTION(scope, ); + + auto wrappedKey = cryptoKey->protectedWrapped(); + + if (!typeValue.isString()) { + Bun::ERR::CRYPTO_INVALID_KEY_OBJECT_TYPE(scope, globalObject, typeValue, "secret"_s); + return; + } + + WTF::String typeString = typeValue.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, ); + + if (wrappedKey->type() != CryptoKeyType::Secret || typeString != "secret"_s) { + Bun::ERR::CRYPTO_INVALID_KEY_OBJECT_TYPE(scope, globalObject, typeValue, "secret"_s); + return; + } + + auto keyData = getSymmetricKey(wrappedKey); + + if (UNLIKELY(!keyData)) { + Bun::ERR::CRYPTO_INVALID_KEY_OBJECT_TYPE(scope, globalObject, typeValue, "secret"_s); + return; + } + + out.append(keyData.value()); + return; + } + } + } + + // Handle string or buffer + if (key.isString()) { + JSString* keyString = key.toString(globalObject); + RETURN_IF_EXCEPTION(scope, ); + + auto encoding = parseEnumeration(*globalObject, encodingValue).value_or(WebCore::BufferEncodingType::utf8); + RETURN_IF_EXCEPTION(scope, ); + if (encoding == WebCore::BufferEncodingType::buffer) { + encoding = WebCore::BufferEncodingType::utf8; + } + + // TODO(dylan-conway): add a way to do this with just the Vector. no need to create a buffer + JSValue buffer = JSValue::decode(WebCore::constructFromEncoding(globalObject, keyString, encoding)); + auto* view = jsDynamicCast(buffer); + out.append(std::span { reinterpret_cast(view->vector()), view->byteLength() }); + return; + } + + // Handle ArrayBuffer types + if (auto* view = jsDynamicCast(key)) { + out.append(std::span { reinterpret_cast(view->vector()), view->byteLength() }); + return; + } + + // If we got here, the key is not a valid type + WTF::String expectedTypes = bufferOnly ? "ArrayBuffer, Buffer, TypedArray, DataView, or a string"_s : "ArrayBuffer, Buffer, TypedArray, DataView, string, CryptoKey, or KeyObject"_s; + Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "key"_s, expectedTypes, key); +} + +ByteSource::ByteSource(ByteSource&& other) noexcept + : data_(other.data_) + , allocated_data_(other.allocated_data_) + , size_(other.size_) +{ + other.allocated_data_ = nullptr; +} + +ByteSource::~ByteSource() +{ + OPENSSL_clear_free(allocated_data_, size_); +} + +std::span ByteSource::span() const +{ + return { reinterpret_cast(data_), size_ }; +} + +ByteSource& ByteSource::operator=(ByteSource&& other) noexcept +{ + if (&other != this) { + OPENSSL_clear_free(allocated_data_, size_); + data_ = other.data_; + allocated_data_ = other.allocated_data_; + other.allocated_data_ = nullptr; + size_ = other.size_; + } + return *this; +} + +ByteSource ByteSource::fromBIO(const ncrypto::BIOPointer& bio) +{ + ASSERT(bio); + BUF_MEM* bptr = bio; + auto out = ncrypto::DataPointer::Alloc(bptr->length); + memcpy(out.get(), bptr->data, bptr->length); + return ByteSource::allocated(out.release()); +} + +ByteSource ByteSource::allocated(void* data, size_t size) +{ + return ByteSource(data, data, size); +} + +ByteSource ByteSource::foreign(const void* data, size_t size) +{ + return ByteSource(data, nullptr, size); +} + } diff --git a/src/bun.js/bindings/node/crypto/util.h b/src/bun.js/bindings/node/crypto/util.h index b588a47d3e..22cb4e8aba 100644 --- a/src/bun.js/bindings/node/crypto/util.h +++ b/src/bun.js/bindings/node/crypto/util.h @@ -4,6 +4,8 @@ #include "ncrypto.h" #include #include +#include "CryptoAlgorithmRegistry.h" +#include "JSBufferEncodingType.h" namespace Bun { @@ -18,6 +20,22 @@ enum class DSASigEnc { } +namespace ExternZigHash { +struct Hasher; + +Hasher* getByName(Zig::GlobalObject* globalObject, const StringView& name); +Hasher* getFromOther(Zig::GlobalObject* globalObject, Hasher* hasher); +void destroy(Hasher* hasher); +bool update(Hasher* hasher, std::span data); +uint32_t digest(Hasher* hasher, Zig::GlobalObject* globalObject, std::span out); +uint32_t getDigestSize(Hasher* hasher); + +}; // namespace ExternZigHash + +namespace StringBytes { +EncodedJSValue encode(JSGlobalObject* lexicalGlobalObject, ThrowScope& scope, std::span bytes, BufferEncodingType encoding); +}; + // void CheckThrow(JSC::JSGlobalObject* globalObject, SignBase::Error error); std::optional keyFromString(JSGlobalObject* lexicalGlobalObject, JSC::ThrowScope& scope, const WTF::StringView& keyView, JSValue passphraseValue); ncrypto::EVPKeyPointer::PKFormatType parseKeyFormat(JSC::JSGlobalObject* globalObject, JSValue formatValue, WTF::ASCIILiteral optionName, std::optional defaultFormat = std::nullopt); @@ -30,5 +48,72 @@ int32_t getPadding(JSC::JSGlobalObject* globalObject, JSValue options, const ncr std::optional getSaltLength(JSC::JSGlobalObject* globalObject, JSValue options); NodeCryptoKeys::DSASigEnc getDSASigEnc(JSC::JSGlobalObject* globalObject, JSValue options); JSC::JSArrayBufferView* getArrayBufferOrView(JSGlobalObject* globalObject, ThrowScope& scope, JSValue value, ASCIILiteral argName, JSValue encodingValue); +std::optional preparePrivateKey(JSGlobalObject* lexicalGlobalObject, JSC::ThrowScope& scope, JSValue maybeKey, std::optional algorithmIdentifier); +JSValue getStringOption(JSGlobalObject* globalObject, JSValue options, const WTF::ASCIILiteral& name); +void prepareSecretKey(JSGlobalObject* globalObject, ThrowScope& scope, Vector& out, JSValue key, JSValue encoding, bool bufferOnly = false); + +// Modified version of ByteSource from node +// +// https://github.com/nodejs/node/blob/2a6f90813f4802def79f2df1bfe20e95df279abf/src/crypto/crypto_util.h#L168 +// A helper class representing a read-only byte array. When deallocated, its +// contents are zeroed. +class ByteSource final { +public: + ByteSource() = default; + ByteSource(ByteSource&& other) noexcept; + ~ByteSource(); + + ByteSource& operator=(ByteSource&& other) noexcept; + + ByteSource(const ByteSource&) = delete; + ByteSource& operator=(const ByteSource&) = delete; + + std::span span() const; + + template + inline const T* data() const + { + return reinterpret_cast(data_); + } + + template + operator ncrypto::Buffer() const + { + return ncrypto::Buffer { + .data = data(), + .len = size(), + }; + } + + inline size_t size() const { return size_; } + + inline bool empty() const { return size_ == 0; } + + inline operator bool() const { return data_ != nullptr; } + + static ByteSource allocated(void* data, size_t size); + + template + static ByteSource allocated(const ncrypto::Buffer& buffer) + { + return allocated(buffer.data, buffer.len); + } + + static ByteSource foreign(const void* data, size_t size); + + static ByteSource fromBIO(const ncrypto::BIOPointer& bio); + +private: + const void* data_ = nullptr; + void* allocated_data_ = nullptr; + size_t size_ = 0; + + ByteSource(const void* data, void* allocated_data, size_t size) + : data_(data) + , allocated_data_(allocated_data) + , size_(size) + { + } +}; } diff --git a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h index da42ff975c..9f80bef3dc 100644 --- a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h @@ -922,6 +922,8 @@ public: std::unique_ptr m_clientSubspaceForEventEmitter; std::unique_ptr m_clientSubspaceForJSSign; std::unique_ptr m_clientSubspaceForJSVerify; + std::unique_ptr m_clientSubspaceForJSHmac; + std::unique_ptr m_clientSubspaceForJSHash; std::unique_ptr m_clientSubspaceForServerRouteList; std::unique_ptr m_clientSubspaceForBunRequest; }; diff --git a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h index 8a0bb923ec..26fb481b1d 100644 --- a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h @@ -925,6 +925,8 @@ public: std::unique_ptr m_subspaceForDOMURL; std::unique_ptr m_subspaceForJSSign; std::unique_ptr m_subspaceForJSVerify; + std::unique_ptr m_subspaceForJSHmac; + std::unique_ptr m_subspaceForJSHash; std::unique_ptr m_subspaceForServerRouteList; std::unique_ptr m_subspaceForBunRequest; }; diff --git a/src/bun.js/bindings/webcore/JSDOMConvertEnumeration.h b/src/bun.js/bindings/webcore/JSDOMConvertEnumeration.h index ce0c5d6904..14aa522c46 100644 --- a/src/bun.js/bindings/webcore/JSDOMConvertEnumeration.h +++ b/src/bun.js/bindings/webcore/JSDOMConvertEnumeration.h @@ -32,8 +32,9 @@ namespace WebCore { // Specialized by generated code for IDL enumeration conversion. -template std::optional parseEnumerationFromString(const String&); template std::optional parseEnumeration(JSC::JSGlobalObject&, JSC::JSValue); +template std::optional parseEnumerationFromView(const StringView&); +template std::optional parseEnumerationFromString(const String&); template ASCIILiteral expectedEnumerationValues(); // Specialized by generated code for IDL enumeration conversion. diff --git a/src/js/node/crypto.ts b/src/js/node/crypto.ts index 02ff363fd6..3ecd0e5535 100644 --- a/src/js/node/crypto.ts +++ b/src/js/node/crypto.ts @@ -1,10 +1,10 @@ // Hardcoded module "node:crypto" -var __defProp = Object.defineProperty; var __getOwnPropNames = Object.getOwnPropertyNames; const StreamModule = require("node:stream"); const BufferModule = require("node:buffer"); const StringDecoder = require("node:string_decoder").StringDecoder; const StringPrototypeToLowerCase = String.prototype.toLowerCase; +const LazyTransform = require("internal/streams/lazy_transform"); const { CryptoHasher } = Bun; const { @@ -38,6 +38,8 @@ const { sign, Verify: _Verify, verify, + Hmac: _Hmac, + Hash: _Hash, } = $cpp("NodeCrypto.cpp", "createNodeCryptoBinding"); const { POINT_CONVERSION_COMPRESSED, POINT_CONVERSION_HYBRID, POINT_CONVERSION_UNCOMPRESSED } = @@ -64,7 +66,7 @@ function exportChallenge(spkac, encoding) { } function Certificate(): void { - if (!(this instanceof Certificate)) { + if (!new.target) { return new Certificate(); } @@ -77,53 +79,6 @@ Certificate.verifySpkac = verifySpkac; Certificate.exportPublicKey = exportPublicKey; Certificate.exportChallenge = exportChallenge; -let bunAlgorithmMap: Map; -function normalizeAlgorithmName(alg: string) { - if (!bunAlgorithmMap) { - // This list should be kept roughly in sync with the ComptimeStringMap used - // in Bun.CryptoHasher - bunAlgorithmMap = new Map([ - ["blake2b256", "blake2b256"], - ["blake2b512", "blake2b512"], - - // this JS code expects rmd160 - // so we for now normalize to that instead. - ["ripemd160", "rmd160"], - ["rmd160", "rmd160"], - ["md4", "md4"], - ["md5", "md5"], - ["sha1", "sha1"], - ["sha128", "sha1"], - ["sha224", "sha224"], - ["sha256", "sha256"], - ["sha384", "sha384"], - ["sha512", "sha512"], - ["sha-1", "sha1"], - ["sha-224", "sha224"], - ["sha-256", "sha256"], - ["sha-384", "sha384"], - ["sha-512", "sha512"], - ["sha-512/224", "sha512-224"], - ["sha-512_224", "sha512-224"], - ["sha-512224", "sha512-224"], - ["sha512-224", "sha512-224"], - ["sha-512/256", "sha512-256"], - ["sha-512_256", "sha512-256"], - ["sha-512256", "sha512-256"], - ["sha512-256", "sha512-256"], - ["sha384", "sha384"], - ["sha3-224", "sha3-224"], - ["sha3-256", "sha3-256"], - ["sha3-384", "sha3-384"], - ["sha3-512", "sha3-512"], - ["shake128", "shake128"], - ["shake256", "shake256"], - ]); - } - - return bunAlgorithmMap.get((alg = alg?.toLowerCase?.())) || alg; -} - function getCipherInfo(nameOrNid, options) { if (typeof nameOrNid !== "string" && typeof nameOrNid !== "number") { throw $ERR_INVALID_ARG_TYPE("nameOrNid", ["string", "number"], nameOrNid); @@ -204,9 +159,6 @@ var __commonJS = (cb, mod: typeof module | undefined = undefined) => function () { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; -var __export = (target, all) => { - for (var name in all) __defProp(target, name, { get: all[name], enumerable: !0 }); -}; // node_modules/safe-buffer/index.js var require_safe_buffer = __commonJS({ @@ -294,928 +246,6 @@ var require_inherits_browser = __commonJS({ }, }); -// node_modules/hash-base/index.js -var require_hash_base = __commonJS({ - "node_modules/hash-base/index.js"(exports, module) { - "use strict"; - var Buffer2 = require_safe_buffer().Buffer, - inherits = require_inherits_browser(); - function throwIfNotStringOrBuffer(val, prefix) { - if (!Buffer2.isBuffer(val) && typeof val != "string") - throw new TypeError(prefix + " must be a string or a buffer"); - } - function HashBase(blockSize) { - StreamModule.Transform.$call(this), - (this._block = Buffer2.allocUnsafe(blockSize)), - (this._blockSize = blockSize), - (this._blockOffset = 0), - (this._length = [0, 0, 0, 0]), - (this._finalized = !1); - } - inherits(HashBase, StreamModule.Transform); - HashBase.prototype._transform = function (chunk, encoding, callback) { - var error = null; - try { - this.update(chunk, encoding); - } catch (err) { - error = err; - } - callback(error); - }; - HashBase.prototype._flush = function (callback) { - var error = null; - try { - this.push(this.digest()); - } catch (err) { - error = err; - } - callback(error); - }; - HashBase.prototype.update = function (data, encoding) { - if ((throwIfNotStringOrBuffer(data, "Data"), this._finalized)) throw new Error("Digest already called"); - Buffer2.isBuffer(data) || (data = Buffer2.from(data, encoding)); - for (var block = this._block, offset = 0; this._blockOffset + data.length - offset >= this._blockSize; ) { - for (var i = this._blockOffset; i < this._blockSize; ) block[i++] = data[offset++]; - this._update(), (this._blockOffset = 0); - } - for (; offset < data.length; ) block[this._blockOffset++] = data[offset++]; - for (var j = 0, carry = data.length * 8; carry > 0; ++j) - (this._length[j] += carry), - (carry = (this._length[j] / 4294967296) | 0), - carry > 0 && (this._length[j] -= 4294967296 * carry); - return this; - }; - HashBase.prototype._update = function () { - throw new Error("_update is not implemented"); - }; - HashBase.prototype.digest = function (encoding) { - if (this._finalized) throw new Error("Digest already called"); - this._finalized = !0; - var digest = this._digest(); - encoding !== void 0 && (digest = digest.toString(encoding)), this._block.fill(0), (this._blockOffset = 0); - for (var i = 0; i < 4; ++i) this._length[i] = 0; - return digest; - }; - HashBase.prototype._digest = function () { - throw new Error("_digest is not implemented"); - }; - module.exports = HashBase; - }, -}); - -// node_modules/md5.js/index.js -var require_md5 = __commonJS({ - "node_modules/md5.js/index.js"(exports, module) { - "use strict"; - var inherits = require_inherits_browser(), - HashBase = require_hash_base(), - Buffer2 = require_safe_buffer().Buffer, - ARRAY16 = new Array(16); - function MD5() { - HashBase.$call(this, 64), - (this._a = 1732584193), - (this._b = 4023233417), - (this._c = 2562383102), - (this._d = 271733878); - } - inherits(MD5, HashBase); - MD5.prototype._update = function () { - for (var M = ARRAY16, i = 0; i < 16; ++i) M[i] = this._block.readInt32LE(i * 4); - var a = this._a, - b = this._b, - c = this._c, - d = this._d; - (a = fnF(a, b, c, d, M[0], 3614090360, 7)), - (d = fnF(d, a, b, c, M[1], 3905402710, 12)), - (c = fnF(c, d, a, b, M[2], 606105819, 17)), - (b = fnF(b, c, d, a, M[3], 3250441966, 22)), - (a = fnF(a, b, c, d, M[4], 4118548399, 7)), - (d = fnF(d, a, b, c, M[5], 1200080426, 12)), - (c = fnF(c, d, a, b, M[6], 2821735955, 17)), - (b = fnF(b, c, d, a, M[7], 4249261313, 22)), - (a = fnF(a, b, c, d, M[8], 1770035416, 7)), - (d = fnF(d, a, b, c, M[9], 2336552879, 12)), - (c = fnF(c, d, a, b, M[10], 4294925233, 17)), - (b = fnF(b, c, d, a, M[11], 2304563134, 22)), - (a = fnF(a, b, c, d, M[12], 1804603682, 7)), - (d = fnF(d, a, b, c, M[13], 4254626195, 12)), - (c = fnF(c, d, a, b, M[14], 2792965006, 17)), - (b = fnF(b, c, d, a, M[15], 1236535329, 22)), - (a = fnG(a, b, c, d, M[1], 4129170786, 5)), - (d = fnG(d, a, b, c, M[6], 3225465664, 9)), - (c = fnG(c, d, a, b, M[11], 643717713, 14)), - (b = fnG(b, c, d, a, M[0], 3921069994, 20)), - (a = fnG(a, b, c, d, M[5], 3593408605, 5)), - (d = fnG(d, a, b, c, M[10], 38016083, 9)), - (c = fnG(c, d, a, b, M[15], 3634488961, 14)), - (b = fnG(b, c, d, a, M[4], 3889429448, 20)), - (a = fnG(a, b, c, d, M[9], 568446438, 5)), - (d = fnG(d, a, b, c, M[14], 3275163606, 9)), - (c = fnG(c, d, a, b, M[3], 4107603335, 14)), - (b = fnG(b, c, d, a, M[8], 1163531501, 20)), - (a = fnG(a, b, c, d, M[13], 2850285829, 5)), - (d = fnG(d, a, b, c, M[2], 4243563512, 9)), - (c = fnG(c, d, a, b, M[7], 1735328473, 14)), - (b = fnG(b, c, d, a, M[12], 2368359562, 20)), - (a = fnH(a, b, c, d, M[5], 4294588738, 4)), - (d = fnH(d, a, b, c, M[8], 2272392833, 11)), - (c = fnH(c, d, a, b, M[11], 1839030562, 16)), - (b = fnH(b, c, d, a, M[14], 4259657740, 23)), - (a = fnH(a, b, c, d, M[1], 2763975236, 4)), - (d = fnH(d, a, b, c, M[4], 1272893353, 11)), - (c = fnH(c, d, a, b, M[7], 4139469664, 16)), - (b = fnH(b, c, d, a, M[10], 3200236656, 23)), - (a = fnH(a, b, c, d, M[13], 681279174, 4)), - (d = fnH(d, a, b, c, M[0], 3936430074, 11)), - (c = fnH(c, d, a, b, M[3], 3572445317, 16)), - (b = fnH(b, c, d, a, M[6], 76029189, 23)), - (a = fnH(a, b, c, d, M[9], 3654602809, 4)), - (d = fnH(d, a, b, c, M[12], 3873151461, 11)), - (c = fnH(c, d, a, b, M[15], 530742520, 16)), - (b = fnH(b, c, d, a, M[2], 3299628645, 23)), - (a = fnI(a, b, c, d, M[0], 4096336452, 6)), - (d = fnI(d, a, b, c, M[7], 1126891415, 10)), - (c = fnI(c, d, a, b, M[14], 2878612391, 15)), - (b = fnI(b, c, d, a, M[5], 4237533241, 21)), - (a = fnI(a, b, c, d, M[12], 1700485571, 6)), - (d = fnI(d, a, b, c, M[3], 2399980690, 10)), - (c = fnI(c, d, a, b, M[10], 4293915773, 15)), - (b = fnI(b, c, d, a, M[1], 2240044497, 21)), - (a = fnI(a, b, c, d, M[8], 1873313359, 6)), - (d = fnI(d, a, b, c, M[15], 4264355552, 10)), - (c = fnI(c, d, a, b, M[6], 2734768916, 15)), - (b = fnI(b, c, d, a, M[13], 1309151649, 21)), - (a = fnI(a, b, c, d, M[4], 4149444226, 6)), - (d = fnI(d, a, b, c, M[11], 3174756917, 10)), - (c = fnI(c, d, a, b, M[2], 718787259, 15)), - (b = fnI(b, c, d, a, M[9], 3951481745, 21)), - (this._a = (this._a + a) | 0), - (this._b = (this._b + b) | 0), - (this._c = (this._c + c) | 0), - (this._d = (this._d + d) | 0); - }; - MD5.prototype._digest = function () { - (this._block[this._blockOffset++] = 128), - this._blockOffset > 56 && (this._block.fill(0, this._blockOffset, 64), this._update(), (this._blockOffset = 0)), - this._block.fill(0, this._blockOffset, 56), - this._block.writeUInt32LE(this._length[0], 56), - this._block.writeUInt32LE(this._length[1], 60), - this._update(); - var buffer = Buffer2.allocUnsafe(16); - return ( - buffer.writeInt32LE(this._a, 0), - buffer.writeInt32LE(this._b, 4), - buffer.writeInt32LE(this._c, 8), - buffer.writeInt32LE(this._d, 12), - buffer - ); - }; - function rotl(x, n) { - return (x << n) | (x >>> (32 - n)); - } - function fnF(a, b, c, d, m, k, s) { - return (rotl((a + ((b & c) | (~b & d)) + m + k) | 0, s) + b) | 0; - } - function fnG(a, b, c, d, m, k, s) { - return (rotl((a + ((b & d) | (c & ~d)) + m + k) | 0, s) + b) | 0; - } - function fnH(a, b, c, d, m, k, s) { - return (rotl((a + (b ^ c ^ d) + m + k) | 0, s) + b) | 0; - } - function fnI(a, b, c, d, m, k, s) { - return (rotl((a + (c ^ (b | ~d)) + m + k) | 0, s) + b) | 0; - } - module.exports = MD5; - }, -}); - -// node_modules/ripemd160/index.js -var require_ripemd160 = __commonJS({ - "node_modules/ripemd160/index.js"(exports, module) { - "use strict"; - var Buffer2 = Buffer, - inherits = require_inherits_browser(), - HashBase = require_hash_base(), - ARRAY16 = new Array(16), - zl = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, 3, - 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, 4, 0, - 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13, - ], - zr = [ - 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, 15, - 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, 12, 15, - 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11, - ], - sl = [ - 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, - 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, - 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6, - ], - sr = [ - 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, - 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, - 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11, - ], - hl = [0, 1518500249, 1859775393, 2400959708, 2840853838], - hr = [1352829926, 1548603684, 1836072691, 2053994217, 0]; - function RIPEMD160() { - HashBase.$call(this, 64), - (this._a = 1732584193), - (this._b = 4023233417), - (this._c = 2562383102), - (this._d = 271733878), - (this._e = 3285377520); - } - inherits(RIPEMD160, HashBase); - RIPEMD160.prototype._update = function () { - for (var words = ARRAY16, j = 0; j < 16; ++j) words[j] = this._block.readInt32LE(j * 4); - for ( - var al = this._a | 0, - bl = this._b | 0, - cl = this._c | 0, - dl = this._d | 0, - el = this._e | 0, - ar = this._a | 0, - br = this._b | 0, - cr = this._c | 0, - dr = this._d | 0, - er = this._e | 0, - i = 0; - i < 80; - i += 1 - ) { - var tl, tr; - i < 16 - ? ((tl = fn1(al, bl, cl, dl, el, words[zl[i]], hl[0], sl[i])), - (tr = fn5(ar, br, cr, dr, er, words[zr[i]], hr[0], sr[i]))) - : i < 32 - ? ((tl = fn2(al, bl, cl, dl, el, words[zl[i]], hl[1], sl[i])), - (tr = fn4(ar, br, cr, dr, er, words[zr[i]], hr[1], sr[i]))) - : i < 48 - ? ((tl = fn3(al, bl, cl, dl, el, words[zl[i]], hl[2], sl[i])), - (tr = fn3(ar, br, cr, dr, er, words[zr[i]], hr[2], sr[i]))) - : i < 64 - ? ((tl = fn4(al, bl, cl, dl, el, words[zl[i]], hl[3], sl[i])), - (tr = fn2(ar, br, cr, dr, er, words[zr[i]], hr[3], sr[i]))) - : ((tl = fn5(al, bl, cl, dl, el, words[zl[i]], hl[4], sl[i])), - (tr = fn1(ar, br, cr, dr, er, words[zr[i]], hr[4], sr[i]))), - (al = el), - (el = dl), - (dl = rotl(cl, 10)), - (cl = bl), - (bl = tl), - (ar = er), - (er = dr), - (dr = rotl(cr, 10)), - (cr = br), - (br = tr); - } - var t = (this._b + cl + dr) | 0; - (this._b = (this._c + dl + er) | 0), - (this._c = (this._d + el + ar) | 0), - (this._d = (this._e + al + br) | 0), - (this._e = (this._a + bl + cr) | 0), - (this._a = t); - }; - RIPEMD160.prototype._digest = function () { - (this._block[this._blockOffset++] = 128), - this._blockOffset > 56 && (this._block.fill(0, this._blockOffset, 64), this._update(), (this._blockOffset = 0)), - this._block.fill(0, this._blockOffset, 56), - this._block.writeUInt32LE(this._length[0], 56), - this._block.writeUInt32LE(this._length[1], 60), - this._update(); - var buffer = Buffer2.alloc ? Buffer2.alloc(20) : new Buffer2(20); - return ( - buffer.writeInt32LE(this._a, 0), - buffer.writeInt32LE(this._b, 4), - buffer.writeInt32LE(this._c, 8), - buffer.writeInt32LE(this._d, 12), - buffer.writeInt32LE(this._e, 16), - buffer - ); - }; - function rotl(x, n) { - return (x << n) | (x >>> (32 - n)); - } - function fn1(a, b, c, d, e, m, k, s) { - return (rotl((a + (b ^ c ^ d) + m + k) | 0, s) + e) | 0; - } - function fn2(a, b, c, d, e, m, k, s) { - return (rotl((a + ((b & c) | (~b & d)) + m + k) | 0, s) + e) | 0; - } - function fn3(a, b, c, d, e, m, k, s) { - return (rotl((a + ((b | ~c) ^ d) + m + k) | 0, s) + e) | 0; - } - function fn4(a, b, c, d, e, m, k, s) { - return (rotl((a + ((b & d) | (c & ~d)) + m + k) | 0, s) + e) | 0; - } - function fn5(a, b, c, d, e, m, k, s) { - return (rotl((a + (b ^ (c | ~d)) + m + k) | 0, s) + e) | 0; - } - module.exports = RIPEMD160; - }, -}); - -// node_modules/sha.js/hash.js -var require_hash = __commonJS({ - "node_modules/sha.js/hash.js"(exports, module) { - var Buffer2 = require_safe_buffer().Buffer; - function Hash(blockSize, finalSize) { - (this._block = Buffer2.alloc(blockSize)), - (this._finalSize = finalSize), - (this._blockSize = blockSize), - (this._len = 0); - } - Hash.prototype = {}; - Hash.prototype.update = function (data, enc) { - typeof data == "string" && ((enc = enc || "utf8"), (data = Buffer2.from(data, enc))); - for ( - var block = this._block, blockSize = this._blockSize, length = data.length, accum = this._len, offset = 0; - offset < length; - - ) { - for ( - var assigned = accum % blockSize, remainder = Math.min(length - offset, blockSize - assigned), i = 0; - i < remainder; - i++ - ) - block[assigned + i] = data[offset + i]; - (accum += remainder), (offset += remainder), accum % blockSize === 0 && this._update(block); - } - return (this._len += length), this; - }; - Hash.prototype.digest = function (enc) { - var rem = this._len % this._blockSize; - (this._block[rem] = 128), - this._block.fill(0, rem + 1), - rem >= this._finalSize && (this._update(this._block), this._block.fill(0)); - var bits = this._len * 8; - if (bits <= 4294967295) this._block.writeUInt32BE(bits, this._blockSize - 4); - else { - var lowBits = (bits & 4294967295) >>> 0, - highBits = (bits - lowBits) / 4294967296; - this._block.writeUInt32BE(highBits, this._blockSize - 8), - this._block.writeUInt32BE(lowBits, this._blockSize - 4); - } - this._update(this._block); - var hash = this._hash(); - return enc ? hash.toString(enc) : hash; - }; - Hash.prototype._update = function () { - throw new Error("_update must be implemented by subclass"); - }; - module.exports = Hash; - }, -}); - -// node_modules/sha.js/sha.js -var require_sha = __commonJS({ - "node_modules/sha.js/sha.js"(exports, module) { - var inherits = require_inherits_browser(), - Hash = require_hash(), - Buffer2 = require_safe_buffer().Buffer, - K = [1518500249, 1859775393, -1894007588, -899497514], - W = new Array(80); - function Sha() { - this.init(), (this._w = W), Hash.$call(this, 64, 56); - } - inherits(Sha, Hash); - Sha.prototype.init = function () { - return ( - (this._a = 1732584193), - (this._b = 4023233417), - (this._c = 2562383102), - (this._d = 271733878), - (this._e = 3285377520), - this - ); - }; - function rotl5(num) { - return (num << 5) | (num >>> 27); - } - function rotl30(num) { - return (num << 30) | (num >>> 2); - } - function ft(s, b, c, d) { - return s === 0 ? (b & c) | (~b & d) : s === 2 ? (b & c) | (b & d) | (c & d) : b ^ c ^ d; - } - Sha.prototype._update = function (M) { - for ( - var W2 = this._w, a = this._a | 0, b = this._b | 0, c = this._c | 0, d = this._d | 0, e = this._e | 0, i = 0; - i < 16; - ++i - ) - W2[i] = M.readInt32BE(i * 4); - for (; i < 80; ++i) W2[i] = W2[i - 3] ^ W2[i - 8] ^ W2[i - 14] ^ W2[i - 16]; - for (var j = 0; j < 80; ++j) { - var s = ~~(j / 20), - t = (rotl5(a) + ft(s, b, c, d) + e + W2[j] + K[s]) | 0; - (e = d), (d = c), (c = rotl30(b)), (b = a), (a = t); - } - (this._a = (a + this._a) | 0), - (this._b = (b + this._b) | 0), - (this._c = (c + this._c) | 0), - (this._d = (d + this._d) | 0), - (this._e = (e + this._e) | 0); - }; - Sha.prototype._hash = function () { - var H = Buffer2.allocUnsafe(20); - return ( - H.writeInt32BE(this._a | 0, 0), - H.writeInt32BE(this._b | 0, 4), - H.writeInt32BE(this._c | 0, 8), - H.writeInt32BE(this._d | 0, 12), - H.writeInt32BE(this._e | 0, 16), - H - ); - }; - module.exports = Sha; - }, -}); - -// node_modules/sha.js/sha1.js -var require_sha1 = __commonJS({ - "node_modules/sha.js/sha1.js"(exports, module) { - var inherits = require_inherits_browser(), - Hash = require_hash(), - Buffer2 = require_safe_buffer().Buffer, - K = [1518500249, 1859775393, -1894007588, -899497514], - W = new Array(80); - function Sha1() { - this.init(), (this._w = W), Hash.$call(this, 64, 56); - } - inherits(Sha1, Hash); - Sha1.prototype.init = function () { - return ( - (this._a = 1732584193), - (this._b = 4023233417), - (this._c = 2562383102), - (this._d = 271733878), - (this._e = 3285377520), - this - ); - }; - function rotl1(num) { - return (num << 1) | (num >>> 31); - } - function rotl5(num) { - return (num << 5) | (num >>> 27); - } - function rotl30(num) { - return (num << 30) | (num >>> 2); - } - function ft(s, b, c, d) { - return s === 0 ? (b & c) | (~b & d) : s === 2 ? (b & c) | (b & d) | (c & d) : b ^ c ^ d; - } - Sha1.prototype._update = function (M) { - for ( - var W2 = this._w, a = this._a | 0, b = this._b | 0, c = this._c | 0, d = this._d | 0, e = this._e | 0, i = 0; - i < 16; - ++i - ) - W2[i] = M.readInt32BE(i * 4); - for (; i < 80; ++i) W2[i] = rotl1(W2[i - 3] ^ W2[i - 8] ^ W2[i - 14] ^ W2[i - 16]); - for (var j = 0; j < 80; ++j) { - var s = ~~(j / 20), - t = (rotl5(a) + ft(s, b, c, d) + e + W2[j] + K[s]) | 0; - (e = d), (d = c), (c = rotl30(b)), (b = a), (a = t); - } - (this._a = (a + this._a) | 0), - (this._b = (b + this._b) | 0), - (this._c = (c + this._c) | 0), - (this._d = (d + this._d) | 0), - (this._e = (e + this._e) | 0); - }; - Sha1.prototype._hash = function () { - var H = Buffer2.allocUnsafe(20); - return ( - H.writeInt32BE(this._a | 0, 0), - H.writeInt32BE(this._b | 0, 4), - H.writeInt32BE(this._c | 0, 8), - H.writeInt32BE(this._d | 0, 12), - H.writeInt32BE(this._e | 0, 16), - H - ); - }; - module.exports = Sha1; - }, -}); - -// node_modules/sha.js/sha256.js -var require_sha256 = __commonJS({ - "node_modules/sha.js/sha256.js"(exports, module) { - var inherits = require_inherits_browser(), - Hash = require_hash(), - Buffer2 = require_safe_buffer().Buffer, - K = [ - 1116352408, 1899447441, 3049323471, 3921009573, 961987163, 1508970993, 2453635748, 2870763221, 3624381080, - 310598401, 607225278, 1426881987, 1925078388, 2162078206, 2614888103, 3248222580, 3835390401, 4022224774, - 264347078, 604807628, 770255983, 1249150122, 1555081692, 1996064986, 2554220882, 2821834349, 2952996808, - 3210313671, 3336571891, 3584528711, 113926993, 338241895, 666307205, 773529912, 1294757372, 1396182291, - 1695183700, 1986661051, 2177026350, 2456956037, 2730485921, 2820302411, 3259730800, 3345764771, 3516065817, - 3600352804, 4094571909, 275423344, 430227734, 506948616, 659060556, 883997877, 958139571, 1322822218, - 1537002063, 1747873779, 1955562222, 2024104815, 2227730452, 2361852424, 2428436474, 2756734187, 3204031479, - 3329325298, - ], - W = new Array(64); - function Sha256() { - this.init(), (this._w = W), Hash.$call(this, 64, 56); - } - inherits(Sha256, Hash); - Sha256.prototype.init = function () { - return ( - (this._a = 1779033703), - (this._b = 3144134277), - (this._c = 1013904242), - (this._d = 2773480762), - (this._e = 1359893119), - (this._f = 2600822924), - (this._g = 528734635), - (this._h = 1541459225), - this - ); - }; - function ch(x, y, z) { - return z ^ (x & (y ^ z)); - } - function maj(x, y, z) { - return (x & y) | (z & (x | y)); - } - function sigma0(x) { - return ((x >>> 2) | (x << 30)) ^ ((x >>> 13) | (x << 19)) ^ ((x >>> 22) | (x << 10)); - } - function sigma1(x) { - return ((x >>> 6) | (x << 26)) ^ ((x >>> 11) | (x << 21)) ^ ((x >>> 25) | (x << 7)); - } - function gamma0(x) { - return ((x >>> 7) | (x << 25)) ^ ((x >>> 18) | (x << 14)) ^ (x >>> 3); - } - function gamma1(x) { - return ((x >>> 17) | (x << 15)) ^ ((x >>> 19) | (x << 13)) ^ (x >>> 10); - } - Sha256.prototype._update = function (M) { - for ( - var W2 = this._w, - a = this._a | 0, - b = this._b | 0, - c = this._c | 0, - d = this._d | 0, - e = this._e | 0, - f = this._f | 0, - g = this._g | 0, - h = this._h | 0, - i = 0; - i < 16; - ++i - ) - W2[i] = M.readInt32BE(i * 4); - for (; i < 64; ++i) W2[i] = (gamma1(W2[i - 2]) + W2[i - 7] + gamma0(W2[i - 15]) + W2[i - 16]) | 0; - for (var j = 0; j < 64; ++j) { - var T1 = (h + sigma1(e) + ch(e, f, g) + K[j] + W2[j]) | 0, - T2 = (sigma0(a) + maj(a, b, c)) | 0; - (h = g), (g = f), (f = e), (e = (d + T1) | 0), (d = c), (c = b), (b = a), (a = (T1 + T2) | 0); - } - (this._a = (a + this._a) | 0), - (this._b = (b + this._b) | 0), - (this._c = (c + this._c) | 0), - (this._d = (d + this._d) | 0), - (this._e = (e + this._e) | 0), - (this._f = (f + this._f) | 0), - (this._g = (g + this._g) | 0), - (this._h = (h + this._h) | 0); - }; - Sha256.prototype._hash = function () { - var H = Buffer2.allocUnsafe(32); - return ( - H.writeInt32BE(this._a, 0), - H.writeInt32BE(this._b, 4), - H.writeInt32BE(this._c, 8), - H.writeInt32BE(this._d, 12), - H.writeInt32BE(this._e, 16), - H.writeInt32BE(this._f, 20), - H.writeInt32BE(this._g, 24), - H.writeInt32BE(this._h, 28), - H - ); - }; - module.exports = Sha256; - }, -}); - -// node_modules/sha.js/sha224.js -var require_sha224 = __commonJS({ - "node_modules/sha.js/sha224.js"(exports, module) { - var inherits = require_inherits_browser(), - Sha256 = require_sha256(), - Hash = require_hash(), - Buffer2 = require_safe_buffer().Buffer, - W = new Array(64); - function Sha224() { - this.init(), (this._w = W), Hash.$call(this, 64, 56); - } - inherits(Sha224, Sha256); - Sha224.prototype.init = function () { - return ( - (this._a = 3238371032), - (this._b = 914150663), - (this._c = 812702999), - (this._d = 4144912697), - (this._e = 4290775857), - (this._f = 1750603025), - (this._g = 1694076839), - (this._h = 3204075428), - this - ); - }; - Sha224.prototype._hash = function () { - var H = Buffer2.allocUnsafe(28); - return ( - H.writeInt32BE(this._a, 0), - H.writeInt32BE(this._b, 4), - H.writeInt32BE(this._c, 8), - H.writeInt32BE(this._d, 12), - H.writeInt32BE(this._e, 16), - H.writeInt32BE(this._f, 20), - H.writeInt32BE(this._g, 24), - H - ); - }; - module.exports = Sha224; - }, -}); - -// node_modules/sha.js/sha512.js -var require_sha512 = __commonJS({ - "node_modules/sha.js/sha512.js"(exports, module) { - var inherits = require_inherits_browser(), - Hash = require_hash(), - Buffer2 = require_safe_buffer().Buffer, - K = [ - 1116352408, 3609767458, 1899447441, 602891725, 3049323471, 3964484399, 3921009573, 2173295548, 961987163, - 4081628472, 1508970993, 3053834265, 2453635748, 2937671579, 2870763221, 3664609560, 3624381080, 2734883394, - 310598401, 1164996542, 607225278, 1323610764, 1426881987, 3590304994, 1925078388, 4068182383, 2162078206, - 991336113, 2614888103, 633803317, 3248222580, 3479774868, 3835390401, 2666613458, 4022224774, 944711139, - 264347078, 2341262773, 604807628, 2007800933, 770255983, 1495990901, 1249150122, 1856431235, 1555081692, - 3175218132, 1996064986, 2198950837, 2554220882, 3999719339, 2821834349, 766784016, 2952996808, 2566594879, - 3210313671, 3203337956, 3336571891, 1034457026, 3584528711, 2466948901, 113926993, 3758326383, 338241895, - 168717936, 666307205, 1188179964, 773529912, 1546045734, 1294757372, 1522805485, 1396182291, 2643833823, - 1695183700, 2343527390, 1986661051, 1014477480, 2177026350, 1206759142, 2456956037, 344077627, 2730485921, - 1290863460, 2820302411, 3158454273, 3259730800, 3505952657, 3345764771, 106217008, 3516065817, 3606008344, - 3600352804, 1432725776, 4094571909, 1467031594, 275423344, 851169720, 430227734, 3100823752, 506948616, - 1363258195, 659060556, 3750685593, 883997877, 3785050280, 958139571, 3318307427, 1322822218, 3812723403, - 1537002063, 2003034995, 1747873779, 3602036899, 1955562222, 1575990012, 2024104815, 1125592928, 2227730452, - 2716904306, 2361852424, 442776044, 2428436474, 593698344, 2756734187, 3733110249, 3204031479, 2999351573, - 3329325298, 3815920427, 3391569614, 3928383900, 3515267271, 566280711, 3940187606, 3454069534, 4118630271, - 4000239992, 116418474, 1914138554, 174292421, 2731055270, 289380356, 3203993006, 460393269, 320620315, - 685471733, 587496836, 852142971, 1086792851, 1017036298, 365543100, 1126000580, 2618297676, 1288033470, - 3409855158, 1501505948, 4234509866, 1607167915, 987167468, 1816402316, 1246189591, - ], - W = new Array(160); - function Sha512() { - this.init(), (this._w = W), Hash.$call(this, 128, 112); - } - inherits(Sha512, Hash); - Sha512.prototype.init = function () { - return ( - (this._ah = 1779033703), - (this._bh = 3144134277), - (this._ch = 1013904242), - (this._dh = 2773480762), - (this._eh = 1359893119), - (this._fh = 2600822924), - (this._gh = 528734635), - (this._hh = 1541459225), - (this._al = 4089235720), - (this._bl = 2227873595), - (this._cl = 4271175723), - (this._dl = 1595750129), - (this._el = 2917565137), - (this._fl = 725511199), - (this._gl = 4215389547), - (this._hl = 327033209), - this - ); - }; - function Ch(x, y, z) { - return z ^ (x & (y ^ z)); - } - function maj(x, y, z) { - return (x & y) | (z & (x | y)); - } - function sigma0(x, xl) { - return ((x >>> 28) | (xl << 4)) ^ ((xl >>> 2) | (x << 30)) ^ ((xl >>> 7) | (x << 25)); - } - function sigma1(x, xl) { - return ((x >>> 14) | (xl << 18)) ^ ((x >>> 18) | (xl << 14)) ^ ((xl >>> 9) | (x << 23)); - } - function Gamma0(x, xl) { - return ((x >>> 1) | (xl << 31)) ^ ((x >>> 8) | (xl << 24)) ^ (x >>> 7); - } - function Gamma0l(x, xl) { - return ((x >>> 1) | (xl << 31)) ^ ((x >>> 8) | (xl << 24)) ^ ((x >>> 7) | (xl << 25)); - } - function Gamma1(x, xl) { - return ((x >>> 19) | (xl << 13)) ^ ((xl >>> 29) | (x << 3)) ^ (x >>> 6); - } - function Gamma1l(x, xl) { - return ((x >>> 19) | (xl << 13)) ^ ((xl >>> 29) | (x << 3)) ^ ((x >>> 6) | (xl << 26)); - } - function getCarry(a, b) { - return a >>> 0 < b >>> 0 ? 1 : 0; - } - Sha512.prototype._update = function (M) { - for ( - var W2 = this._w, - ah = this._ah | 0, - bh = this._bh | 0, - ch = this._ch | 0, - dh = this._dh | 0, - eh = this._eh | 0, - fh = this._fh | 0, - gh = this._gh | 0, - hh = this._hh | 0, - al = this._al | 0, - bl = this._bl | 0, - cl = this._cl | 0, - dl = this._dl | 0, - el = this._el | 0, - fl = this._fl | 0, - gl = this._gl | 0, - hl = this._hl | 0, - i = 0; - i < 32; - i += 2 - ) - (W2[i] = M.readInt32BE(i * 4)), (W2[i + 1] = M.readInt32BE(i * 4 + 4)); - for (; i < 160; i += 2) { - var xh = W2[i - 30], - xl = W2[i - 15 * 2 + 1], - gamma0 = Gamma0(xh, xl), - gamma0l = Gamma0l(xl, xh); - (xh = W2[i - 2 * 2]), (xl = W2[i - 2 * 2 + 1]); - var gamma1 = Gamma1(xh, xl), - gamma1l = Gamma1l(xl, xh), - Wi7h = W2[i - 7 * 2], - Wi7l = W2[i - 7 * 2 + 1], - Wi16h = W2[i - 16 * 2], - Wi16l = W2[i - 16 * 2 + 1], - Wil = (gamma0l + Wi7l) | 0, - Wih = (gamma0 + Wi7h + getCarry(Wil, gamma0l)) | 0; - (Wil = (Wil + gamma1l) | 0), - (Wih = (Wih + gamma1 + getCarry(Wil, gamma1l)) | 0), - (Wil = (Wil + Wi16l) | 0), - (Wih = (Wih + Wi16h + getCarry(Wil, Wi16l)) | 0), - (W2[i] = Wih), - (W2[i + 1] = Wil); - } - for (var j = 0; j < 160; j += 2) { - (Wih = W2[j]), (Wil = W2[j + 1]); - var majh = maj(ah, bh, ch), - majl = maj(al, bl, cl), - sigma0h = sigma0(ah, al), - sigma0l = sigma0(al, ah), - sigma1h = sigma1(eh, el), - sigma1l = sigma1(el, eh), - Kih = K[j], - Kil = K[j + 1], - chh = Ch(eh, fh, gh), - chl = Ch(el, fl, gl), - t1l = (hl + sigma1l) | 0, - t1h = (hh + sigma1h + getCarry(t1l, hl)) | 0; - (t1l = (t1l + chl) | 0), - (t1h = (t1h + chh + getCarry(t1l, chl)) | 0), - (t1l = (t1l + Kil) | 0), - (t1h = (t1h + Kih + getCarry(t1l, Kil)) | 0), - (t1l = (t1l + Wil) | 0), - (t1h = (t1h + Wih + getCarry(t1l, Wil)) | 0); - var t2l = (sigma0l + majl) | 0, - t2h = (sigma0h + majh + getCarry(t2l, sigma0l)) | 0; - (hh = gh), - (hl = gl), - (gh = fh), - (gl = fl), - (fh = eh), - (fl = el), - (el = (dl + t1l) | 0), - (eh = (dh + t1h + getCarry(el, dl)) | 0), - (dh = ch), - (dl = cl), - (ch = bh), - (cl = bl), - (bh = ah), - (bl = al), - (al = (t1l + t2l) | 0), - (ah = (t1h + t2h + getCarry(al, t1l)) | 0); - } - (this._al = (this._al + al) | 0), - (this._bl = (this._bl + bl) | 0), - (this._cl = (this._cl + cl) | 0), - (this._dl = (this._dl + dl) | 0), - (this._el = (this._el + el) | 0), - (this._fl = (this._fl + fl) | 0), - (this._gl = (this._gl + gl) | 0), - (this._hl = (this._hl + hl) | 0), - (this._ah = (this._ah + ah + getCarry(this._al, al)) | 0), - (this._bh = (this._bh + bh + getCarry(this._bl, bl)) | 0), - (this._ch = (this._ch + ch + getCarry(this._cl, cl)) | 0), - (this._dh = (this._dh + dh + getCarry(this._dl, dl)) | 0), - (this._eh = (this._eh + eh + getCarry(this._el, el)) | 0), - (this._fh = (this._fh + fh + getCarry(this._fl, fl)) | 0), - (this._gh = (this._gh + gh + getCarry(this._gl, gl)) | 0), - (this._hh = (this._hh + hh + getCarry(this._hl, hl)) | 0); - }; - Sha512.prototype._hash = function () { - var H = Buffer2.allocUnsafe(64); - function writeInt64BE(h, l, offset) { - H.writeInt32BE(h, offset), H.writeInt32BE(l, offset + 4); - } - return ( - writeInt64BE(this._ah, this._al, 0), - writeInt64BE(this._bh, this._bl, 8), - writeInt64BE(this._ch, this._cl, 16), - writeInt64BE(this._dh, this._dl, 24), - writeInt64BE(this._eh, this._el, 32), - writeInt64BE(this._fh, this._fl, 40), - writeInt64BE(this._gh, this._gl, 48), - writeInt64BE(this._hh, this._hl, 56), - H - ); - }; - module.exports = Sha512; - }, -}); - -// node_modules/sha.js/sha384.js -var require_sha384 = __commonJS({ - "node_modules/sha.js/sha384.js"(exports, module) { - var inherits = require_inherits_browser(), - SHA512 = require_sha512(), - Hash = require_hash(), - Buffer2 = require_safe_buffer().Buffer, - W = new Array(160); - function Sha384() { - this.init(), (this._w = W), Hash.$call(this, 128, 112); - } - inherits(Sha384, SHA512); - Sha384.prototype.init = function () { - return ( - (this._ah = 3418070365), - (this._bh = 1654270250), - (this._ch = 2438529370), - (this._dh = 355462360), - (this._eh = 1731405415), - (this._fh = 2394180231), - (this._gh = 3675008525), - (this._hh = 1203062813), - (this._al = 3238371032), - (this._bl = 914150663), - (this._cl = 812702999), - (this._dl = 4144912697), - (this._el = 4290775857), - (this._fl = 1750603025), - (this._gl = 1694076839), - (this._hl = 3204075428), - this - ); - }; - Sha384.prototype._hash = function () { - var H = Buffer2.allocUnsafe(48); - function writeInt64BE(h, l, offset) { - H.writeInt32BE(h, offset), H.writeInt32BE(l, offset + 4); - } - return ( - writeInt64BE(this._ah, this._al, 0), - writeInt64BE(this._bh, this._bl, 8), - writeInt64BE(this._ch, this._cl, 16), - writeInt64BE(this._dh, this._dl, 24), - writeInt64BE(this._eh, this._el, 32), - writeInt64BE(this._fh, this._fl, 40), - H - ); - }; - module.exports = Sha384; - }, -}); - -// node_modules/sha.js/index.js -var require_sha2 = __commonJS({ - "node_modules/sha.js/index.js"(exports, module) { - var exports = (module.exports = function (algorithm) { - algorithm = algorithm.toLowerCase(); - var Algorithm = exports[algorithm]; - if (!Algorithm) throw new Error(algorithm + " is not supported (we accept pull requests)"); - return new Algorithm(); - }); - exports.sha = require_sha(); - exports.sha1 = require_sha1(); - exports.sha224 = require_sha224(); - exports.sha256 = require_sha256(); - exports.sha384 = require_sha384(); - exports.sha512 = require_sha512(); - }, -}); - // node_modules/cipher-base/index.js var require_cipher_base = __commonJS({ "node_modules/cipher-base/index.js"(exports, module) { @@ -1287,270 +317,6 @@ var require_cipher_base = __commonJS({ }, }); -// node_modules/create-hash/browser.js -var require_browser2 = __commonJS({ - "node_modules/create-hash/browser.js"(exports, module) { - "use strict"; - // does not become a node stream unless you create it into one - const LazyHash = function Hash(algorithm, options) { - this._options = options; - this._hasher = new CryptoHasher(algorithm); - this._finalized = false; - }; - LazyHash.prototype = Object.create(StreamModule.Transform.prototype); - LazyHash.prototype.update = function update(data, encoding) { - this._checkFinalized(); - this._hasher.update(data, encoding); - return this; - }; - LazyHash.prototype.digest = function digest(data, encoding) { - this._checkFinalized(); - this._finalized = true; - return this._hasher.digest(data, encoding); - }; - LazyHash.prototype._checkFinalized = function _checkFinalized() { - if (this._finalized) { - var err = new Error("Digest already called"); - err.code = "ERR_CRYPTO_HASH_FINALIZED"; - throw err; - } - }; - LazyHash.prototype.copy = function copy() { - const copy = Object.create(LazyHash.prototype); - copy._options = this._options; - copy._hasher = this._hasher.copy(); - copy._finalized = this._finalized; - return copy; - }; - - const lazyHashFullInitProto = { - __proto__: StreamModule.Transform.prototype, - ...LazyHash.prototype, - _transform(data, encoding, callback) { - this.update(data, encoding); - callback && callback(); - }, - _flush(callback) { - this.push(this.digest()); - callback(); - }, - }; - - const triggerMethods = [ - "_events", - "_eventsCount", - "_final", - "_maxListeners", - "_maxListeners", - "_read", - "_undestroy", - "_writableState", - "_write", - "_writev", - "addListener", - "asIndexedPairs", - "closed", - "compose", - "constructor", - "cork", - "destroy", - "destroyed", - "drop", - "emit", - "end", - "errored", - "eventNames", - "every", - "filter", - "find", - "flatMap", - "forEach", - "getMaxListeners", - "hasOwnProperty", - "isPaused", - "isPrototypeOf", - "iterator", - "listenerCount", - "listeners", - "map", - "off", - "on", - "once", - "pause", - "pipe", - "prependListener", - "prependOnceListener", - "propertyIsEnumerable", - "push", - "rawListeners", - "read", - "readable", - "readableAborted", - "readableBuffer", - "readableDidRead", - "readableEncoding", - "readableEnded", - "readableFlowing", - "readableHighWaterMark", - "readableLength", - "readableObjectMode", - "reduce", - "removeAllListeners", - "removeListener", - "resume", - "setDefaultEncoding", - "setEncoding", - "setMaxListeners", - "some", - "take", - "toArray", - "toLocaleString", - "toString", - "uncork", - "unpipe", - "unshift", - "valueOf", - "wrap", - "writable", - "writableBuffer", - "writableCorked", - "writableEnded", - "writableFinished", - "writableHighWaterMark", - "writableLength", - "writableNeedDrain", - "writableObjectMode", - "write", - ]; - for (const method of triggerMethods) { - Object.defineProperty(LazyHash.prototype, method, { - get() { - Object.setPrototypeOf(this, lazyHashFullInitProto); - StreamModule.Transform.$call(this, this._options); - return this[method]; - }, - enumerable: false, - configurable: true, - }); - } - - module.exports = function createHash(algorithm, options) { - return new LazyHash(algorithm, options); - }; - - module.exports.createHash = module.exports; - module.exports.Hash = LazyHash; - }, -}); - -// node_modules/create-hmac/legacy.js -var require_legacy = __commonJS({ - "node_modules/create-hmac/legacy.js"(exports, module) { - "use strict"; - var inherits = require_inherits_browser(), - Buffer2 = require_safe_buffer().Buffer, - Base = require_cipher_base(), - ZEROS = Buffer2.alloc(128), - blocksize = 64; - function Hmac(alg, key) { - key = exportIfKeyObject(key); - - Base.$call(this, "digest"), - typeof key == "string" && (key = Buffer2.from(key)), - (this._alg = alg), - (this._key = key), - key.length > blocksize - ? (key = alg(key)) - : key.length < blocksize && (key = Buffer2.concat([key, ZEROS], blocksize)); - for ( - var ipad = (this._ipad = Buffer2.allocUnsafe(blocksize)), - opad = (this._opad = Buffer2.allocUnsafe(blocksize)), - i = 0; - i < blocksize; - i++ - ) - (ipad[i] = key[i] ^ 54), (opad[i] = key[i] ^ 92); - this._hash = [ipad]; - } - Hmac.prototype = {}; - inherits(Hmac, Base); - Hmac.prototype._update = function (data) { - this._hash.push(data); - }; - Hmac.prototype._final = function () { - var h = this._alg(Buffer2.concat(this._hash)); - return this._alg(Buffer2.concat([this._opad, h])); - }; - module.exports = Hmac; - }, -}); - -// node_modules/create-hash/md5.js -var require_md52 = __commonJS({ - "node_modules/create-hash/md5.js"(exports, module) { - var MD5 = require_md5(); - module.exports = function (buffer) { - return new MD5().update(buffer).digest(); - }; - }, -}); - -// node_modules/create-hmac/browser.js -var require_browser3 = __commonJS({ - "node_modules/create-hmac/browser.js"(exports, module) { - "use strict"; - var inherits = require_inherits_browser(); - var Legacy = require_legacy(); - var Base = require_cipher_base(); - var Buffer2 = require_safe_buffer().Buffer; - var md5 = require_md52(); - var RIPEMD160 = require_ripemd160(); - var sha = require_sha2(); - var ZEROS = Buffer2.alloc(128); - function Hmac(alg, key) { - alg = normalizeAlgorithmName(alg); - key = exportIfKeyObject(key); - - Base.$call(this, "digest"), typeof key == "string" && (key = Buffer2.from(key)); - var blocksize = alg === "sha512" || alg === "sha384" ? 128 : 64; - if (((this._alg = alg), (this._key = key), key.length > blocksize)) { - var hash = alg === "rmd160" ? new RIPEMD160() : sha(alg); - key = hash.update(key).digest(); - } else key.length < blocksize && (key = Buffer2.concat([key, ZEROS], blocksize)); - for ( - var ipad = (this._ipad = Buffer2.allocUnsafe(blocksize)), - opad = (this._opad = Buffer2.allocUnsafe(blocksize)), - i = 0; - i < blocksize; - i++ - ) - (ipad[i] = key[i] ^ 54), (opad[i] = key[i] ^ 92); - (this._hash = alg === "rmd160" ? new RIPEMD160() : sha(alg)), this._hash.update(ipad); - } - inherits(Hmac, Base); - Hmac.prototype._update = function (data) { - this._hash.update(data); - }; - Hmac.prototype._final = function () { - var h = this._hash.digest(), - hash = this._alg === "rmd160" ? new RIPEMD160() : sha(this._alg); - - return hash.update(this._opad).update(h).digest(); - }; - module.exports = function (alg, key) { - key = exportIfKeyObject(key); - return ( - (alg = alg.toLowerCase()), - alg === "rmd160" || alg === "ripemd160" - ? new Hmac("rmd160", key) - : alg === "md5" - ? new Legacy(md5, key) - : new Hmac(alg, key) - ); - }; - }, -}); - // node_modules/browserify-sign/browser/algorithms.json var require_algorithms = __commonJS({ "node_modules/browserify-sign/browser/algorithms.json"(exports, module) { @@ -2965,8 +1731,7 @@ var require_streamCipher = __commonJS({ // node_modules/evp_bytestokey/index.js var require_evp_bytestokey = __commonJS({ "node_modules/evp_bytestokey/index.js"(exports, module) { - var Buffer2 = require_safe_buffer().Buffer, - MD5 = require_md5(); + var Buffer2 = require_safe_buffer().Buffer; function EVP_BytesToKey(password, salt, keyBits, ivLen) { if ( (Buffer2.isBuffer(password) || (password = Buffer2.from(password, "binary")), @@ -2978,7 +1743,7 @@ var require_evp_bytestokey = __commonJS({ keyLen > 0 || ivLen > 0; ) { - var hash = new MD5(); + var hash = new Hash("md5"); hash.update(tmp), hash.update(password), salt && hash.update(salt), (tmp = hash.digest()); var used = 0; if (keyLen > 0) { @@ -11386,9 +10151,6 @@ var require_crypto_browserify2 = __commonJS({ "node_modules/crypto-browserify/index.js"(exports) { "use strict"; exports.randomBytes = exports.rng = exports.pseudoRandomBytes = exports.prng = require_browser(); - exports.createHash = require_browser2(); - exports.Hash = exports.createHash.Hash; - exports.createHmac = exports.Hmac = require_browser3(); var algos = require_algos(), algoKeys = Object.keys(algos), hashes = ["sha1", "sha224", "sha256", "sha384", "sha512", "md5", "rmd160"].concat(algoKeys); @@ -11830,7 +10592,6 @@ function Sign(algorithm, options): void { StreamModule.Writable.$apply(this, [options]); } - $toClass(Sign, "Sign", StreamModule.Writable); Sign.prototype._write = function _write(chunk, encoding, callback) { @@ -11839,7 +10600,7 @@ Sign.prototype._write = function _write(chunk, encoding, callback) { }; Sign.prototype.update = function update(data, encoding) { - return this[kHandle].update(data, encoding); + return this[kHandle].update(this, data, encoding); }; Sign.prototype.sign = function sign(options, encoding) { @@ -11866,7 +10627,6 @@ function Verify(algorithm, options): void { StreamModule.Writable.$apply(this, [options]); } - $toClass(Verify, "Verify", StreamModule.Writable); Verify.prototype._write = Sign.prototype._write; @@ -11885,5 +10645,82 @@ function createVerify(algorithm, options?) { crypto_exports.createVerify = createVerify; +{ + function Hash(algorithm, options?): void { + if (!new.target) { + return new Hash(algorithm, options); + } + + const handle = new _Hash(algorithm, options); + this[kHandle] = handle; + + LazyTransform.$apply(this, [options]); + } + $toClass(Hash, "Hash", LazyTransform); + + Hash.prototype.copy = function copy(options) { + return new Hash(this[kHandle], options); + }; + + Hash.prototype._transform = function _transform(chunk, encoding, callback) { + this[kHandle].update(this, chunk, encoding); + callback(); + }; + + Hash.prototype._flush = function _flush(callback) { + this.push(this[kHandle].digest(null, false)); + callback(); + }; + + Hash.prototype.update = function update(data, encoding) { + return this[kHandle].update(this, data, encoding); + }; + + Hash.prototype.digest = function digest(outputEncoding) { + return this[kHandle].digest(outputEncoding); + }; + + crypto_exports.Hash = Hash; + crypto_exports.createHash = function createHash(algorithm, options) { + return new Hash(algorithm, options); + }; +} + +{ + function Hmac(hmac, key, options?): void { + if (!new.target) { + return new Hmac(hmac, key, options); + } + + const handle = new _Hmac(hmac, key, options); + this[kHandle] = handle; + + LazyTransform.$apply(this, [options]); + } + $toClass(Hmac, "Hmac", LazyTransform); + + Hmac.prototype.update = function update(data, encoding) { + return this[kHandle].update(this, data, encoding); + }; + + Hmac.prototype.digest = function digest(outputEncoding) { + return this[kHandle].digest(outputEncoding); + }; + + Hmac.prototype._transform = function _transform(chunk, encoding, callback) { + this[kHandle].update(this, chunk, encoding); + callback(); + }; + Hmac.prototype._flush = function _flush(callback) { + this.push(this[kHandle].digest()); + callback(); + }; + + crypto_exports.Hmac = Hmac; + crypto_exports.createHmac = function createHmac(hmac, key, options) { + return new Hmac(hmac, key, options); + }; +} + export default crypto_exports; /*! safe-buffer. MIT License. Feross Aboukhadijeh */ diff --git a/test/js/node/crypto/crypto.hmac.test.ts b/test/js/node/crypto/crypto.hmac.test.ts index 56421fbded..785dca9c9c 100644 --- a/test/js/node/crypto/crypto.hmac.test.ts +++ b/test/js/node/crypto/crypto.hmac.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from "bun:test"; import { Hmac, createHmac, createSecretKey } from "crypto"; +import { PassThrough } from "stream"; function testHmac(algo, key, data, expected) { if (!Array.isArray(data)) data = [data]; @@ -19,14 +20,39 @@ function testHmac(algo, key, data, expected) { } describe("crypto.Hmac", () => { - test.todo("Hmac is expected to return a new instance", async () => { + test("Hmac is expected to return a new instance", async () => { const instance = Hmac("sha256", "Node"); expect(instance instanceof Hmac).toBe(true); }); + test("_flush should not set finalized", async () => { + const s = new PassThrough(); + const h = createHmac("sha1", "hi"); + + const { resolve, promise } = Promise.withResolvers(); + + s.pipe(h) + .on("data", c => { + expect(c).toBe("f2a1c2327e7bf3297b3f494716666a30cf1ddf3f"); + + // _flush should not set finalized, but + // buffer shoule be empty because m_ctx.reset() + // was called + expect(h.digest("hex")).toBe(""); + resolve(); + }) + .setEncoding("hex"); + + s.end("hi"); + + await promise; + }); + test("createHmac should throw when using invalid options", async () => { - expect(() => createHmac(null)).toThrow("null is not an object"); - expect(() => createHmac("sha1", null)).toThrow("null is not an object"); + expect(() => createHmac(null)).toThrow('The "hmac" argument must be of type string. Received null'); + expect(() => createHmac("sha1", null)).toThrow( + 'The "key" argument must be of type ArrayBuffer, Buffer, TypedArray, DataView, string, CryptoKey, or KeyObject. Received null', + ); }); describe("test HMAC with multiple updates.", async () => { @@ -413,7 +439,7 @@ describe("crypto.Hmac", () => { } }); test("Invalid digest", async () => { - expect(() => createHmac("sha7", "key")).toThrow(/sha7 is not supported/); + expect(() => createHmac("sha7", "key")).toThrow("Invalid digest: sha7"); }); test("secret digest", async () => { diff --git a/test/js/node/crypto/node-crypto.test.js b/test/js/node/crypto/node-crypto.test.js index 681ef22910..b302647daa 100644 --- a/test/js/node/crypto/node-crypto.test.js +++ b/test/js/node/crypto/node-crypto.test.js @@ -260,7 +260,6 @@ describe("createHash", () => { "id-rsassa-pkcs1-v1_5-with-sha3-256", "id-rsassa-pkcs1-v1_5-with-sha3-384", "id-rsassa-pkcs1-v1_5-with-sha3-512", - "md5-sha1", "md5withrsaencryption", "ripemd", "ripemd160withrsa", @@ -302,11 +301,23 @@ describe("createHash", () => { const hash = crypto.createHash(name); hash.update("Hello World"); expect(hash.digest("hex")).toBe(nodeValues[name].value); - }).toThrow(Error(`Unsupported algorithm ${name}`)); + }).toThrow(Error(`Digest method not supported`)); } else { const hash = crypto.createHash(name); hash.update("Hello World"); + + // testing copy to be sure boringssl workarounds for blake2b256/512, + // ripemd160, sha3-, and shake128/256 are working. + const copy = hash.copy(); expect(hash.digest("hex")).toBe(nodeValues[name].value); + expect(copy.digest("hex")).toBe(nodeValues[name].value); + + expect(() => { + hash.copy(); + }).toThrow(Error(`Digest already called`)); + expect(() => { + copy.copy(); + }).toThrow(Error(`Digest already called`)); } }); @@ -316,7 +327,7 @@ describe("createHash", () => { const hash = crypto.createHash(name); hash.update("Hello World"); expect(hash.digest()).toEqual(Buffer.from(nodeValues[name].value, "hex")); - }).toThrow(Error(`Unsupported algorithm ${name}`)); + }).toThrow(Error(`Digest method not supported`)); } else { const hash = crypto.createHash(name); hash.update("Hello World"); diff --git a/test/js/node/test/common/crypto.js b/test/js/node/test/common/crypto.js index e859723e69..19dfb1388d 100644 --- a/test/js/node/test/common/crypto.js +++ b/test/js/node/test/common/crypto.js @@ -157,30 +157,32 @@ module.exports = { }, // opensslCli defined lazily to reduce overhead of spawnSync get opensslCli() { - if (opensslCli !== null) return opensslCli; + if (typeof Bun === "object") { + if (opensslCli !== null) return opensslCli; + + opensslCli = Bun.which('openssl'); + + return opensslCli; + } - opensslCli = Bun.which('openssl'); - + if (process.config.variables.node_shared_openssl) { + // Use external command + opensslCli = 'openssl'; + } else { + const path = require('path'); + // Use command built from sources included in Node.js repository + opensslCli = path.join(path.dirname(process.execPath), 'openssl-cli'); + } + + if (exports.isWindows) opensslCli += '.exe'; + + const { spawnSync } = require('child_process'); + + const opensslCmd = spawnSync(opensslCli, ['version']); + if (opensslCmd.status !== 0 || opensslCmd.error !== undefined) { + // OpenSSL command cannot be executed + opensslCli = false; + } return opensslCli; - - // if (process.config.variables.node_shared_openssl) { - // // Use external command - // opensslCli = 'openssl'; - // } else { - // const path = require('path'); - // // Use command built from sources included in Node.js repository - // opensslCli = path.join(path.dirname(process.execPath), 'openssl-cli'); - // } - - // if (exports.isWindows) opensslCli += '.exe'; - - // const { spawnSync } = require('child_process'); - - // const opensslCmd = spawnSync(opensslCli, ['version']); - // if (opensslCmd.status !== 0 || opensslCmd.error !== undefined) { - // // OpenSSL command cannot be executed - // opensslCli = false; - // } - // return opensslCli; }, }; diff --git a/test/js/node/test/parallel/test-crypto-hash-stream-pipe.js b/test/js/node/test/parallel/test-crypto-hash-stream-pipe.js new file mode 100644 index 0000000000..d22281abbd --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-hash-stream-pipe.js @@ -0,0 +1,46 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); + +const stream = require('stream'); +const s = new stream.PassThrough(); +const h = crypto.createHash('sha3-512'); +const expect = '36a38a2a35e698974d4e5791a3f05b05' + + '198235381e864f91a0e8cd6a26b677ec' + + 'dcde8e2b069bd7355fabd68abd6fc801' + + '19659f25e92f8efc961ee3a7c815c758'; + +s.pipe(h).on('data', common.mustCall(function(c) { + assert.strictEqual(c, expect); + // Calling digest() after piping into a stream with SHA3 should not cause + // a segmentation fault, see https://github.com/nodejs/node/issues/28245. + assert.strictEqual(h.digest('hex'), expect); +})).setEncoding('hex'); + +s.end('aoeu'); diff --git a/test/js/node/test/parallel/test-crypto-hash.js b/test/js/node/test/parallel/test-crypto-hash.js new file mode 100644 index 0000000000..ac704d2bf7 --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-hash.js @@ -0,0 +1,292 @@ +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) { + common.skip('missing crypto'); +} + +const assert = require('assert'); +const crypto = require('crypto'); +const fs = require('fs'); + +const { hasOpenSSL } = require('../common/crypto'); +const fixtures = require('../common/fixtures'); + +let cryptoType; +let digest; + +// Test hashing +const a1 = crypto.createHash('sha1').update('Test123').digest('hex'); +const a2 = crypto.createHash('sha256').update('Test123').digest('base64'); +const a3 = crypto.createHash('sha512').update('Test123').digest(); // buffer +const a4 = crypto.createHash('sha1').update('Test123').digest('buffer'); + +// stream interface +let a5 = crypto.createHash('sha512'); +a5.end('Test123'); +a5 = a5.read(); + +let a6 = crypto.createHash('sha512'); +a6.write('Te'); +a6.write('st'); +a6.write('123'); +a6.end(); +a6 = a6.read(); + +let a7 = crypto.createHash('sha512'); +a7.end(); +a7 = a7.read(); + +let a8 = crypto.createHash('sha512'); +a8.write(''); +a8.end(); +a8 = a8.read(); + +if (!crypto.getFips()) { + cryptoType = 'md5'; + digest = 'latin1'; + const a0 = crypto.createHash(cryptoType).update('Test123').digest(digest); + assert.strictEqual( + a0, + 'h\u00ea\u00cb\u0097\u00d8o\fF!\u00fa+\u000e\u0017\u00ca\u00bd\u008c', + `${cryptoType} with ${digest} digest failed to evaluate to expected hash` + ); +} +cryptoType = 'md5'; +digest = 'hex'; +assert.strictEqual( + a1, + '8308651804facb7b9af8ffc53a33a22d6a1c8ac2', + `${cryptoType} with ${digest} digest failed to evaluate to expected hash`); +cryptoType = 'sha256'; +digest = 'base64'; +assert.strictEqual( + a2, + '2bX1jws4GYKTlxhloUB09Z66PoJZW+y+hq5R8dnx9l4=', + `${cryptoType} with ${digest} digest failed to evaluate to expected hash`); +cryptoType = 'sha512'; +digest = 'latin1'; +assert.deepStrictEqual( + a3, + Buffer.from( + '\u00c1(4\u00f1\u0003\u001fd\u0097!O\'\u00d4C/&Qz\u00d4' + + '\u0094\u0015l\u00b8\u008dQ+\u00db\u001d\u00c4\u00b5}\u00b2' + + '\u00d6\u0092\u00a3\u00df\u00a2i\u00a1\u009b\n\n*\u000f' + + '\u00d7\u00d6\u00a2\u00a8\u0085\u00e3<\u0083\u009c\u0093' + + '\u00c2\u0006\u00da0\u00a1\u00879(G\u00ed\'', + 'latin1'), + `${cryptoType} with ${digest} digest failed to evaluate to expected hash`); +cryptoType = 'sha1'; +digest = 'hex'; +assert.deepStrictEqual( + a4, + Buffer.from('8308651804facb7b9af8ffc53a33a22d6a1c8ac2', 'hex'), + `${cryptoType} with ${digest} digest failed to evaluate to expected hash` +); + +// Stream interface should produce the same result. +assert.deepStrictEqual(a5, a3); +assert.deepStrictEqual(a6, a3); +assert.notStrictEqual(a7, undefined); +assert.notStrictEqual(a8, undefined); + +// Test multiple updates to same hash +const h1 = crypto.createHash('sha1').update('Test123').digest('hex'); +const h2 = crypto.createHash('sha1').update('Test').update('123').digest('hex'); +assert.strictEqual(h1, h2); + +// Test hashing for binary files +const fn = fixtures.path('sample.png'); +const sha1Hash = crypto.createHash('sha1'); +const fileStream = fs.createReadStream(fn); +fileStream.on('data', function(data) { + sha1Hash.update(data); +}); +fileStream.on('close', common.mustCall(function() { + // Test SHA1 of sample.png + assert.strictEqual(sha1Hash.digest('hex'), + '22723e553129a336ad96e10f6aecdf0f45e4149e'); +})); + +// Issue https://github.com/nodejs/node-v0.x-archive/issues/2227: unknown digest +// method should throw an error. +assert.throws(function() { + crypto.createHash('xyzzy'); +}, /Digest method not supported/); + +// Issue https://github.com/nodejs/node/issues/9819: throwing encoding used to +// segfault. +assert.throws( + () => crypto.createHash('sha256').digest({ + toString: () => { throw new Error('boom'); }, + }), + { + name: 'Error', + message: 'boom' + }); + +// Issue https://github.com/nodejs/node/issues/25487: error message for invalid +// arg type to update method should include all possible types +assert.throws( + () => crypto.createHash('sha256').update(), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); + +// Default UTF-8 encoding +const hutf8 = crypto.createHash('sha512').update('УТФ-8 text').digest('hex'); +assert.strictEqual( + hutf8, + '4b21bbd1a68e690a730ddcb5a8bc94ead9879ffe82580767ad7ec6fa8ba2dea6' + + '43a821af66afa9a45b6a78c712fecf0e56dc7f43aef4bcfc8eb5b4d8dca6ea5b'); + +assert.notStrictEqual( + hutf8, + crypto.createHash('sha512').update('УТФ-8 text', 'latin1').digest('hex')); + +const h3 = crypto.createHash('sha256'); +h3.digest(); + +assert.throws( + () => h3.digest(), + { + code: 'ERR_CRYPTO_HASH_FINALIZED', + name: 'Error' + }); + +assert.throws( + () => h3.update('foo'), + { + code: 'ERR_CRYPTO_HASH_FINALIZED', + name: 'Error' + }); + +assert.strictEqual( + crypto.createHash('sha256').update('test').digest('ucs2'), + crypto.createHash('sha256').update('test').digest().toString('ucs2')); + +assert.throws( + () => crypto.createHash(), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "algorithm" argument must be of type string. ' + + 'Received undefined' + } +); + +{ + const Hash = crypto.Hash; + const instance = crypto.Hash('sha256'); + assert(instance instanceof Hash, 'Hash is expected to return a new instance' + + ' when called without `new`'); +} + +// Test XOF hash functions and the outputLength option. +{ + // Default outputLengths. Since OpenSSL 3.4 an outputLength is mandatory + if (!hasOpenSSL(3, 4)) { + assert.strictEqual(crypto.createHash('shake128').digest('hex'), + '7f9c2ba4e88f827d616045507605853e'); + assert.strictEqual(crypto.createHash('shake128', null).digest('hex'), + '7f9c2ba4e88f827d616045507605853e'); + assert.strictEqual(crypto.createHash('shake256').digest('hex'), + '46b9dd2b0ba88d13233b3feb743eeb24' + + '3fcd52ea62b81b82b50c27646ed5762f'); + assert.strictEqual(crypto.createHash('shake256', { outputLength: 0 }) + .copy() // Default outputLength. + .digest('hex'), + '46b9dd2b0ba88d13233b3feb743eeb24' + + '3fcd52ea62b81b82b50c27646ed5762f'); + } + + // Short outputLengths. + assert.strictEqual(crypto.createHash('shake128', { outputLength: 0 }) + .digest('hex'), + ''); + assert.strictEqual(crypto.createHash('shake128', { outputLength: 5 }) + .copy({ outputLength: 0 }) + .digest('hex'), + ''); + assert.strictEqual(crypto.createHash('shake128', { outputLength: 5 }) + .digest('hex'), + '7f9c2ba4e8'); + assert.strictEqual(crypto.createHash('shake128', { outputLength: 0 }) + .copy({ outputLength: 5 }) + .digest('hex'), + '7f9c2ba4e8'); + assert.strictEqual(crypto.createHash('shake128', { outputLength: 15 }) + .digest('hex'), + '7f9c2ba4e88f827d61604550760585'); + assert.strictEqual(crypto.createHash('shake256', { outputLength: 16 }) + .digest('hex'), + '46b9dd2b0ba88d13233b3feb743eeb24'); + + // Large outputLengths. + assert.strictEqual(crypto.createHash('shake128', { outputLength: 128 }) + .digest('hex'), + '7f9c2ba4e88f827d616045507605853e' + + 'd73b8093f6efbc88eb1a6eacfa66ef26' + + '3cb1eea988004b93103cfb0aeefd2a68' + + '6e01fa4a58e8a3639ca8a1e3f9ae57e2' + + '35b8cc873c23dc62b8d260169afa2f75' + + 'ab916a58d974918835d25e6a435085b2' + + 'badfd6dfaac359a5efbb7bcc4b59d538' + + 'df9a04302e10c8bc1cbf1a0b3a5120ea'); + const superLongHash = crypto.createHash('shake256', { + outputLength: 1024 * 1024 + }).update('The message is shorter than the hash!') + .digest('hex'); + assert.strictEqual(superLongHash.length, 2 * 1024 * 1024); + assert.ok(superLongHash.endsWith('193414035ddba77bf7bba97981e656ec')); + assert.ok(superLongHash.startsWith('a2a28dbc49cfd6e5d6ceea3d03e77748')); + + // Non-XOF hash functions should accept valid outputLength options as well. + assert.strictEqual(crypto.createHash('sha224', { outputLength: 28 }) + .digest('hex'), + 'd14a028c2a3a2bc9476102bb288234c4' + + '15a2b01f828ea62ac5b3e42f'); + + // Passing invalid sizes should throw during creation. + assert.throws(() => { + crypto.createHash('sha256', { outputLength: 28 }); + }, { + code: common.openSSLIsBoringSSL ? 'ERR_OSSL_NOT_XOF_OR_INVALID_LENGTH' : 'ERR_OSSL_EVP_NOT_XOF_OR_INVALID_LENGTH' + }); + + for (const outputLength of [null, {}, 'foo', false]) { + assert.throws(() => crypto.createHash('sha256', { outputLength }), + { code: 'ERR_INVALID_ARG_TYPE' }); + } + + for (const outputLength of [-1, .5, Infinity, 2 ** 90]) { + assert.throws(() => crypto.createHash('sha256', { outputLength }), + { code: 'ERR_OUT_OF_RANGE' }); + } +} + +{ + const h = crypto.createHash('sha512'); + h.digest(); + assert.throws(() => h.copy(), { code: 'ERR_CRYPTO_HASH_FINALIZED' }); + assert.throws(() => h.digest(), { code: 'ERR_CRYPTO_HASH_FINALIZED' }); +} + +{ + const a = crypto.createHash('sha512').update('abc'); + const b = a.copy(); + const c = b.copy().update('def'); + const d = crypto.createHash('sha512').update('abcdef'); + assert.strictEqual(a.digest('hex'), b.digest('hex')); + assert.strictEqual(c.digest('hex'), d.digest('hex')); +} + +// { +// crypto.Hash('sha256'); +// common.expectWarning({ +// DeprecationWarning: [ +// ['crypto.Hash constructor is deprecated.', +// 'DEP0179'], +// ] +// }); +// } diff --git a/test/js/node/test/parallel/test-crypto-hmac.js b/test/js/node/test/parallel/test-crypto-hmac.js new file mode 100644 index 0000000000..dedbd778cc --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-hmac.js @@ -0,0 +1,472 @@ +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) { + common.skip('missing crypto'); +} + +const assert = require('assert'); +const crypto = require('crypto'); + +{ + const Hmac = crypto.Hmac; + const instance = crypto.Hmac('sha256', 'Node'); + assert(instance instanceof Hmac, 'Hmac is expected to return a new instance' + + ' when called without `new`'); +} + +assert.throws( + () => crypto.createHmac(null), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "hmac" argument must be of type string. Received null' + }); + +// This used to segfault. See: https://github.com/nodejs/node/issues/9819 +assert.throws( + () => crypto.createHmac('sha256', 'key').digest({ + toString: () => { throw new Error('boom'); }, + }), + { + name: 'Error', + message: 'boom' + }); + +assert.throws( + () => crypto.createHmac('sha1', null), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); + +function testHmac(algo, key, data, expected) { + // FIPS does not support MD5. + if (crypto.getFips() && algo === 'md5') + return; + + if (!Array.isArray(data)) + data = [data]; + + // If the key is a Buffer, test Hmac with a key object as well. + const keyWrappers = [ + (key) => key, + ...(typeof key === 'string' ? [] : [crypto.createSecretKey]), + ]; + + for (const keyWrapper of keyWrappers) { + const hmac = crypto.createHmac(algo, keyWrapper(key)); + for (const chunk of data) + hmac.update(chunk); + const actual = hmac.digest('hex'); + assert.strictEqual(actual, expected); + } +} + +{ + // Test HMAC with multiple updates. + testHmac('sha1', 'Node', ['some data', 'to hmac'], + '19fd6e1ba73d9ed2224dd5094a71babe85d9a892'); +} + +// Test HMAC (Wikipedia Test Cases) +const wikipedia = [ + { + key: 'key', data: 'The quick brown fox jumps over the lazy dog', + hmac: { // HMACs lifted from Wikipedia. + md5: '80070713463e7749b90c2dc24911e275', + sha1: 'de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9', + sha256: + 'f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc' + + '2d1a3cd8' + } + }, + { + key: 'key', data: '', + hmac: { // Intermediate test to help debugging. + md5: '63530468a04e386459855da0063b6596', + sha1: 'f42bb0eeb018ebbd4597ae7213711ec60760843f', + sha256: + '5d5d139563c95b5967b9bd9a8c9b233a9dedb45072794cd232dc1b74' + + '832607d0' + } + }, + { + key: '', data: 'The quick brown fox jumps over the lazy dog', + hmac: { // Intermediate test to help debugging. + md5: 'ad262969c53bc16032f160081c4a07a0', + sha1: '2ba7f707ad5f187c412de3106583c3111d668de8', + sha256: + 'fb011e6154a19b9a4c767373c305275a5a69e8b68b0b4c9200c383dc' + + 'ed19a416' + } + }, + { + key: '', data: '', + hmac: { // HMACs lifted from Wikipedia. + md5: '74e6f7298a9c2d168935f58c001bad88', + sha1: 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', + sha256: + 'b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c71214' + + '4292c5ad' + } + }, +]; + +for (const { key, data, hmac } of wikipedia) { + for (const hash in hmac) + testHmac(hash, key, data, hmac[hash]); +} + +// Test HMAC-SHA-* (rfc 4231 Test Cases) +const rfc4231 = [ + { + key: Buffer.from('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', 'hex'), + data: Buffer.from('4869205468657265', 'hex'), // 'Hi There' + hmac: { + sha224: '896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22', + sha256: + 'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c' + + '2e32cff7', + sha384: + 'afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c' + + '7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6', + sha512: + '87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b305' + + '45e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f170' + + '2e696c203a126854' + } + }, + { + key: Buffer.from('4a656665', 'hex'), // 'Jefe' + data: Buffer.from('7768617420646f2079612077616e7420666f72206e6f74686' + + '96e673f', 'hex'), // 'what do ya want for nothing?' + hmac: { + sha224: 'a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44', + sha256: + '5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b9' + + '64ec3843', + sha384: + 'af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec373' + + '6322445e8e2240ca5e69e2c78b3239ecfab21649', + sha512: + '164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7' + + 'ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b' + + '636e070a38bce737' + } + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'hex'), + data: Buffer.from('ddddddddddddddddddddddddddddddddddddddddddddddddd' + + 'ddddddddddddddddddddddddddddddddddddddddddddddddddd', + 'hex'), + hmac: { + sha224: '7fb3cb3588c6c1f6ffa9694d7d6ad2649365b0c1f65d69d1ec8333ea', + sha256: + '773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514' + + 'ced565fe', + sha384: + '88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e5' + + '5966144b2a5ab39dc13814b94e3ab6e101a34f27', + sha512: + 'fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33' + + 'b2279d39bf3e848279a722c806b485a47e67c807b946a337bee89426' + + '74278859e13292fb' + } + }, + { + key: Buffer.from('0102030405060708090a0b0c0d0e0f10111213141516171819', + 'hex'), + data: Buffer.from('cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc' + + 'dcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd', + 'hex'), + hmac: { + sha224: '6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a', + sha256: + '82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff4' + + '6729665b', + sha384: + '3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e' + + '1f573b4e6801dd23c4a7d679ccf8a386c674cffb', + sha512: + 'b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050' + + '361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2d' + + 'e2adebeb10a298dd' + } + }, + + { + key: Buffer.from('0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c', 'hex'), + // 'Test With Truncation' + data: Buffer.from('546573742057697468205472756e636174696f6e', 'hex'), + hmac: { + sha224: '0e2aea68a90c8d37c988bcdb9fca6fa8', + sha256: 'a3b6167473100ee06e0c796c2955552b', + sha384: '3abf34c3503b2a23a46efc619baef897', + sha512: '415fad6271580a531d4179bc891d87a6' + }, + truncate: true + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaa', 'hex'), + // 'Test Using Larger Than Block-Size Key - Hash Key First' + data: Buffer.from('54657374205573696e67204c6172676572205468616e20426' + + 'c6f636b2d53697a65204b6579202d2048617368204b657920' + + '4669727374', 'hex'), + hmac: { + sha224: '95e9a0db962095adaebe9b2d6f0dbce2d499f112f2d2b7273fa6870e', + sha256: + '60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f' + + '0ee37f54', + sha384: + '4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05' + + '033ac4c60c2ef6ab4030fe8296248df163f44952', + sha512: + '80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b0137' + + '83f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec' + + '8b915a985d786598' + } + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaa', 'hex'), + // 'This is a test using a larger than block-size key and a larger ' + + // 'than block-size data. The key needs to be hashed before being ' + + // 'used by the HMAC algorithm.' + data: Buffer.from('5468697320697320612074657374207573696e672061206c6' + + '172676572207468616e20626c6f636b2d73697a65206b6579' + + '20616e642061206c6172676572207468616e20626c6f636b2' + + 'd73697a6520646174612e20546865206b6579206e65656473' + + '20746f20626520686173686564206265666f7265206265696' + + 'e6720757365642062792074686520484d414320616c676f72' + + '6974686d2e', 'hex'), + hmac: { + sha224: '3a854166ac5d9f023f54d517d0b39dbd946770db9c2b95c9f6f565d1', + sha256: + '9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f5153' + + '5c3a35e2', + sha384: + '6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82' + + '461e99c5a678cc31e799176d3860e6110c46523e', + sha512: + 'e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d' + + '20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de04460' + + '65c97440fa8c6a58' + } + }, +]; + +for (let i = 0, l = rfc4231.length; i < l; i++) { + for (const hash in rfc4231[i].hmac) { + const str = crypto.createHmac(hash, rfc4231[i].key); + str.end(rfc4231[i].data); + let strRes = str.read().toString('hex'); + let actual = crypto.createHmac(hash, rfc4231[i].key) + .update(rfc4231[i].data) + .digest('hex'); + if (rfc4231[i].truncate) { + actual = actual.slice(0, 32); // first 128 bits == 32 hex chars + strRes = strRes.slice(0, 32); + } + const expected = rfc4231[i].hmac[hash]; + assert.strictEqual( + actual, + expected, + `Test HMAC-${hash} rfc 4231 case ${i + 1}: ${actual} must be ${expected}` + ); + assert.strictEqual( + actual, + strRes, + `Should get same result from stream (hash: ${hash} and case: ${i + 1})` + + ` => ${actual} must be ${strRes}` + ); + } +} + +// Test HMAC-MD5/SHA1 (rfc 2202 Test Cases) +const rfc2202_md5 = [ + { + key: Buffer.from('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', 'hex'), + data: 'Hi There', + hmac: '9294727a3638bb1c13f48ef8158bfc9d' + }, + { + key: 'Jefe', + data: 'what do ya want for nothing?', + hmac: '750c783e6ab0b503eaa86e310a5db738' + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'hex'), + data: Buffer.from('ddddddddddddddddddddddddddddddddddddddddddddddddd' + + 'ddddddddddddddddddddddddddddddddddddddddddddddddddd', + 'hex'), + hmac: '56be34521d144c88dbb8c733f0e8b3f6' + }, + { + key: Buffer.from('0102030405060708090a0b0c0d0e0f10111213141516171819', + 'hex'), + data: Buffer.from('cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc' + + 'dcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd' + + 'cdcdcdcdcd', + 'hex'), + hmac: '697eaf0aca3a3aea3a75164746ffaa79' + }, + { + key: Buffer.from('0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c', 'hex'), + data: 'Test With Truncation', + hmac: '56461ef2342edc00f9bab995690efd4c' + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaa', + 'hex'), + data: 'Test Using Larger Than Block-Size Key - Hash Key First', + hmac: '6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd' + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaa', + 'hex'), + data: + 'Test Using Larger Than Block-Size Key and Larger Than One ' + + 'Block-Size Data', + hmac: '6f630fad67cda0ee1fb1f562db3aa53e' + }, +]; + +for (const { key, data, hmac } of rfc2202_md5) + testHmac('md5', key, data, hmac); + +const rfc2202_sha1 = [ + { + key: Buffer.from('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', 'hex'), + data: 'Hi There', + hmac: 'b617318655057264e28bc0b6fb378c8ef146be00' + }, + { + key: 'Jefe', + data: 'what do ya want for nothing?', + hmac: 'effcdf6ae5eb2fa2d27416d5f184df9c259a7c79' + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'hex'), + data: Buffer.from('ddddddddddddddddddddddddddddddddddddddddddddd' + + 'ddddddddddddddddddddddddddddddddddddddddddddd' + + 'dddddddddd', + 'hex'), + hmac: '125d7342b9ac11cd91a39af48aa17b4f63f175d3' + }, + { + key: Buffer.from('0102030405060708090a0b0c0d0e0f10111213141516171819', + 'hex'), + data: Buffer.from('cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc' + + 'dcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd' + + 'cdcdcdcdcd', + 'hex'), + hmac: '4c9007f4026250c6bc8414f9bf50c86c2d7235da' + }, + { + key: Buffer.from('0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c', 'hex'), + data: 'Test With Truncation', + hmac: '4c1a03424b55e07fe7f27be1d58bb9324a9a5a04' + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaa', + 'hex'), + data: 'Test Using Larger Than Block-Size Key - Hash Key First', + hmac: 'aa4ae5e15272d00e95705637ce8a3b55ed402112' + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaa', + 'hex'), + data: + 'Test Using Larger Than Block-Size Key and Larger Than One ' + + 'Block-Size Data', + hmac: 'e8e99d0f45237d786d6bbaa7965c7808bbff1a91' + }, +]; + +for (const { key, data, hmac } of rfc2202_sha1) + testHmac('sha1', key, data, hmac); + +assert.strictEqual( + crypto.createHmac('sha256', 'w00t').digest('ucs2'), + crypto.createHmac('sha256', 'w00t').digest().toString('ucs2')); + +// Check initialized -> uninitialized state transition after calling digest(). +{ + const expected = + '\u0010\u0041\u0052\u00c5\u00bf\u00dc\u00a0\u007b\u00c6\u0033' + + '\u00ee\u00bd\u0046\u0019\u009f\u0002\u0055\u00c9\u00f4\u009d'; + { + const h = crypto.createHmac('sha1', 'key').update('data'); + assert.deepStrictEqual(h.digest('buffer'), Buffer.from(expected, 'latin1')); + assert.deepStrictEqual(h.digest('buffer'), Buffer.from('')); + } + { + const h = crypto.createHmac('sha1', 'key').update('data'); + assert.strictEqual(h.digest('latin1'), expected); + assert.strictEqual(h.digest('latin1'), ''); + } +} + +// Check initialized -> uninitialized state transition after calling digest(). +// Calls to update() omitted intentionally. +{ + const expected = + '\u00f4\u002b\u00b0\u00ee\u00b0\u0018\u00eb\u00bd\u0045\u0097' + + '\u00ae\u0072\u0013\u0071\u001e\u00c6\u0007\u0060\u0084\u003f'; + { + const h = crypto.createHmac('sha1', 'key'); + assert.deepStrictEqual(h.digest('buffer'), Buffer.from(expected, 'latin1')); + assert.deepStrictEqual(h.digest('buffer'), Buffer.from('')); + } + { + const h = crypto.createHmac('sha1', 'key'); + assert.strictEqual(h.digest('latin1'), expected); + assert.strictEqual(h.digest('latin1'), ''); + } +} + +{ + assert.throws( + () => crypto.createHmac('sha7', 'key'), + /Invalid digest/); +} + +{ + const buf = Buffer.alloc(0); + const keyObject = crypto.createSecretKey(Buffer.alloc(0)); + assert.deepStrictEqual( + crypto.createHmac('sha256', buf).update('foo').digest(), + crypto.createHmac('sha256', keyObject).update('foo').digest(), + ); +} + +if (typeof Bun === 'undefined') { + crypto.Hmac('sha256', 'Node'); + common.expectWarning({ + DeprecationWarning: [ + ['crypto.Hmac constructor is deprecated.', + 'DEP0181'], + ] + }); +}