diff --git a/src/bun.js/bindings/ErrorCode.cpp b/src/bun.js/bindings/ErrorCode.cpp index 03d91d6387..d74a1a3c05 100644 --- a/src/bun.js/bindings/ErrorCode.cpp +++ b/src/bun.js/bindings/ErrorCode.cpp @@ -1046,10 +1046,54 @@ JSC::EncodedJSValue CRYPTO_INVALID_KEYTYPE(JSC::ThrowScope& throwScope, JSC::JSG return {}; } +JSC::EncodedJSValue CRYPTO_UNKNOWN_CIPHER(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::StringView& cipherName) +{ + WTF::StringBuilder builder; + builder.append("Unknown cipher: "_s); + builder.append(cipherName); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_CRYPTO_UNKNOWN_CIPHER, builder.toString())); + return {}; +} + +JSC::EncodedJSValue CRYPTO_INVALID_AUTH_TAG(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& message) +{ + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_CRYPTO_INVALID_AUTH_TAG, message)); + return {}; +} + +JSC::EncodedJSValue CRYPTO_INVALID_IV(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject) +{ + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_CRYPTO_INVALID_IV, "Invalid initialization vector"_s)); + return {}; +} + +JSC::EncodedJSValue CRYPTO_UNSUPPORTED_OPERATION(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral message) +{ + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_CRYPTO_UNSUPPORTED_OPERATION, message)); + return {}; +} + JSC::EncodedJSValue CRYPTO_INVALID_KEYLEN(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject) { - auto message = "Invalid key length"_s; - throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_CRYPTO_INVALID_KEYLEN, message)); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_CRYPTO_INVALID_KEYLEN, "Invalid key length"_s)); + return {}; +} + +JSC::EncodedJSValue CRYPTO_INVALID_STATE(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral message) +{ + scope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_CRYPTO_INVALID_STATE, message)); + return {}; +} + +JSC::EncodedJSValue CRYPTO_INVALID_MESSAGELEN(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject) +{ + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_CRYPTO_INVALID_MESSAGELEN, "Invalid message length"_s)); + return {}; +} + +JSC::EncodedJSValue MISSING_ARGS(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral message) +{ + scope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_MISSING_ARGS, message)); return {}; } diff --git a/src/bun.js/bindings/ErrorCode.h b/src/bun.js/bindings/ErrorCode.h index ce090e748d..d0ec9ce166 100644 --- a/src/bun.js/bindings/ErrorCode.h +++ b/src/bun.js/bindings/ErrorCode.h @@ -85,6 +85,7 @@ JSC::EncodedJSValue INVALID_STATE(JSC::ThrowScope& throwScope, JSC::JSGlobalObje JSC::EncodedJSValue STRING_TOO_LONG(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject); JSC::EncodedJSValue BUFFER_OUT_OF_BOUNDS(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral name); JSC::EncodedJSValue UNKNOWN_SIGNAL(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue signal, bool triedUppercase = false); +JSC::EncodedJSValue MISSING_ARGS(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral message); JSC::EncodedJSValue SOCKET_BAD_PORT(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue name, JSC::JSValue port, bool allowZero); JSC::EncodedJSValue UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject); JSC::EncodedJSValue ASSERTION(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue msg); @@ -107,7 +108,13 @@ JSC::EncodedJSValue CRYPTO_TIMING_SAFE_EQUAL_LENGTH(JSC::ThrowScope&, JSC::JSGlo JSC::EncodedJSValue CRYPTO_UNKNOWN_DH_GROUP(JSC::ThrowScope&, JSC::JSGlobalObject*); JSC::EncodedJSValue CRYPTO_INVALID_KEYTYPE(JSC::ThrowScope&, JSC::JSGlobalObject*, WTF::ASCIILiteral message); JSC::EncodedJSValue CRYPTO_INVALID_KEYTYPE(JSC::ThrowScope&, JSC::JSGlobalObject*); +JSC::EncodedJSValue CRYPTO_UNKNOWN_CIPHER(JSC::ThrowScope&, JSC::JSGlobalObject*, const WTF::StringView& cipherName); +JSC::EncodedJSValue CRYPTO_INVALID_AUTH_TAG(JSC::ThrowScope&, JSC::JSGlobalObject*, const WTF::String& message); +JSC::EncodedJSValue CRYPTO_INVALID_IV(JSC::ThrowScope&, JSC::JSGlobalObject*); +JSC::EncodedJSValue CRYPTO_UNSUPPORTED_OPERATION(JSC::ThrowScope&, JSC::JSGlobalObject*, WTF::ASCIILiteral message); JSC::EncodedJSValue CRYPTO_INVALID_KEYLEN(JSC::ThrowScope&, JSC::JSGlobalObject*); +JSC::EncodedJSValue CRYPTO_INVALID_STATE(JSC::ThrowScope&, JSC::JSGlobalObject*, WTF::ASCIILiteral message); +JSC::EncodedJSValue CRYPTO_INVALID_MESSAGELEN(JSC::ThrowScope&, JSC::JSGlobalObject*); // URL diff --git a/src/bun.js/bindings/ErrorCode.ts b/src/bun.js/bindings/ErrorCode.ts index f8635d1ecd..1fd1f052ab 100644 --- a/src/bun.js/bindings/ErrorCode.ts +++ b/src/bun.js/bindings/ErrorCode.ts @@ -244,5 +244,6 @@ const errors: ErrorCodeMapping = [ ["ERR_WORKER_INIT_FAILED", Error], ["ERR_ZLIB_INITIALIZATION_FAILED", Error], ["MODULE_NOT_FOUND", Error], + ["ERR_INTERNAL_ASSERTION", Error], ]; export default errors; diff --git a/src/bun.js/bindings/JSStringDecoder.cpp b/src/bun.js/bindings/JSStringDecoder.cpp index 441f5582ac..7899c4bcfb 100644 --- a/src/bun.js/bindings/JSStringDecoder.cpp +++ b/src/bun.js/bindings/JSStringDecoder.cpp @@ -492,7 +492,7 @@ static JSC_DEFINE_CUSTOM_GETTER(jsStringDecoder_lastChar, (JSGlobalObject * lexi { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); - JSStringDecoder* castedThis = jsStringDecoderCast(lexicalGlobalObject, JSC::JSValue::decode(thisValue), "text"_s); + JSStringDecoder* castedThis = jsStringDecoderCast(lexicalGlobalObject, JSC::JSValue::decode(thisValue), "lastChar"_s); RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode({})); auto buffer = ArrayBuffer::create({ castedThis->m_lastChar, 4 }); auto* globalObject = reinterpret_cast(lexicalGlobalObject); @@ -520,7 +520,7 @@ static JSC_DEFINE_CUSTOM_GETTER(jsStringDecoder_encoding, (JSGlobalObject * lexi { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); - JSStringDecoder* castedThis = jsStringDecoderCast(lexicalGlobalObject, JSC::JSValue::decode(thisValue), "lastTotal"_s); + JSStringDecoder* castedThis = jsStringDecoderCast(lexicalGlobalObject, JSC::JSValue::decode(thisValue), "encoding"_s); RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode({})); return JSC::JSValue::encode(WebCore::convertEnumerationToJS(*lexicalGlobalObject, castedThis->m_encoding)); } diff --git a/src/bun.js/bindings/NodeValidator.h b/src/bun.js/bindings/NodeValidator.h index 3878ce6a88..d96e4646c1 100644 --- a/src/bun.js/bindings/NodeValidator.h +++ b/src/bun.js/bindings/NodeValidator.h @@ -9,25 +9,25 @@ namespace Bun { -JSC_DEFINE_HOST_FUNCTION(jsFunction_validateInteger, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); -JSC_DEFINE_HOST_FUNCTION(jsFunction_validateNumber, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); -JSC_DEFINE_HOST_FUNCTION(jsFunction_validateString, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); -JSC_DEFINE_HOST_FUNCTION(jsFunction_validateFiniteNumber, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); -JSC_DEFINE_HOST_FUNCTION(jsFunction_checkRangesOrGetDefault, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); -JSC_DEFINE_HOST_FUNCTION(jsFunction_validateFunction, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); -JSC_DEFINE_HOST_FUNCTION(jsFunction_validateBoolean, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); -JSC_DEFINE_HOST_FUNCTION(jsFunction_validatePort, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); -JSC_DEFINE_HOST_FUNCTION(jsFunction_validateAbortSignal, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); -JSC_DEFINE_HOST_FUNCTION(jsFunction_validateArray, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); -JSC_DEFINE_HOST_FUNCTION(jsFunction_validateInt32, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); -JSC_DEFINE_HOST_FUNCTION(jsFunction_validateUint32, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); -JSC_DEFINE_HOST_FUNCTION(jsFunction_validateSignalName, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); -JSC_DEFINE_HOST_FUNCTION(jsFunction_validateEncoding, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); -JSC_DEFINE_HOST_FUNCTION(jsFunction_validatePlainFunction, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); -JSC_DEFINE_HOST_FUNCTION(jsFunction_validateUndefined, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); -JSC_DEFINE_HOST_FUNCTION(jsFunction_validateBuffer, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); -JSC_DEFINE_HOST_FUNCTION(jsFunction_validateOneOf, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); -JSC_DEFINE_HOST_FUNCTION(jsFunction_validateObject, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); +JSC_DECLARE_HOST_FUNCTION(jsFunction_validateInteger); +JSC_DECLARE_HOST_FUNCTION(jsFunction_validateNumber); +JSC_DECLARE_HOST_FUNCTION(jsFunction_validateString); +JSC_DECLARE_HOST_FUNCTION(jsFunction_validateFiniteNumber); +JSC_DECLARE_HOST_FUNCTION(jsFunction_checkRangesOrGetDefault); +JSC_DECLARE_HOST_FUNCTION(jsFunction_validateFunction); +JSC_DECLARE_HOST_FUNCTION(jsFunction_validateBoolean); +JSC_DECLARE_HOST_FUNCTION(jsFunction_validatePort); +JSC_DECLARE_HOST_FUNCTION(jsFunction_validateAbortSignal); +JSC_DECLARE_HOST_FUNCTION(jsFunction_validateArray); +JSC_DECLARE_HOST_FUNCTION(jsFunction_validateInt32); +JSC_DECLARE_HOST_FUNCTION(jsFunction_validateUint32); +JSC_DECLARE_HOST_FUNCTION(jsFunction_validateSignalName); +JSC_DECLARE_HOST_FUNCTION(jsFunction_validateEncoding); +JSC_DECLARE_HOST_FUNCTION(jsFunction_validatePlainFunction); +JSC_DECLARE_HOST_FUNCTION(jsFunction_validateUndefined); +JSC_DECLARE_HOST_FUNCTION(jsFunction_validateBuffer); +JSC_DECLARE_HOST_FUNCTION(jsFunction_validateOneOf); +JSC_DECLARE_HOST_FUNCTION(jsFunction_validateObject); namespace V { diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index dd86b1f71e..8c3a701fc8 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -166,6 +166,7 @@ #include "JSDiffieHellman.h" #include "JSDiffieHellmanGroup.h" #include "JSECDH.h" +#include "JSCipher.h" #include "JSS3File.h" #include "S3Error.h" #include "ProcessBindingBuffer.h" @@ -2908,6 +2909,11 @@ void GlobalObject::finishCreation(VM& vm) setupJSHashClassStructure(init); }); + m_JSCipherClassStructure.initLater( + [](LazyClassStructure::Initializer& init) { + setupCipherClassStructure(init); + }); + m_lazyStackCustomGetterSetter.initLater( [](const Initializer& init) { init.set(CustomGetterSetter::create(init.vm, errorInstanceLazyStackCustomGetter, errorInstanceLazyStackCustomSetter)); @@ -4101,6 +4107,7 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) thisObject->m_JSECDHClassStructure.visit(visitor); thisObject->m_JSHmacClassStructure.visit(visitor); thisObject->m_JSHashClassStructure.visit(visitor); + thisObject->m_JSCipherClassStructure.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 bfc8d6eee0..768278f177 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -552,6 +552,7 @@ public: LazyClassStructure m_JSHmacClassStructure; LazyClassStructure m_JSHashClassStructure; LazyClassStructure m_JSECDHClassStructure; + LazyClassStructure m_JSCipherClassStructure; /** * 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 6ad25c8f1e..79fc665471 100644 --- a/src/bun.js/bindings/ncrypto.cpp +++ b/src/bun.js/bindings/ncrypto.cpp @@ -3118,6 +3118,14 @@ bool SSLCtxPointer::setCipherSuites(WTF::StringView ciphers) const Cipher Cipher::FromName(WTF::StringView name) { + + if (name.startsWithIgnoringASCIICase("aes"_s)) { + auto remain = name.substring(3); + if (remain == "128"_s) return Cipher::AES_128_CBC; + if (remain == "192"_s) return Cipher::AES_192_CBC; + if (remain == "256"_s) return Cipher::AES_256_CBC; + } + auto nameUtf8 = name.utf8(); return Cipher(EVP_get_cipherbyname(nameUtf8.data())); } diff --git a/src/bun.js/bindings/node/crypto/JSCipher.cpp b/src/bun.js/bindings/node/crypto/JSCipher.cpp new file mode 100644 index 0000000000..da79142eb5 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSCipher.cpp @@ -0,0 +1,46 @@ +#include "JSCipher.h" +#include "JSCipherPrototype.h" +#include "JSCipherConstructor.h" +#include "DOMIsoSubspaces.h" +#include "ZigGlobalObject.h" +#include "ErrorCode.h" +#include +#include +#include +#include +#include + +namespace Bun { + +const JSC::ClassInfo JSCipher::s_info = { "Cipher"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCipher) }; + +void JSCipher::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); +} + +template +void JSCipher::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + JSCipher* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); +} + +DEFINE_VISIT_CHILDREN(JSCipher); + +void setupCipherClassStructure(JSC::LazyClassStructure::Initializer& init) +{ + auto* prototypeStructure = JSCipherPrototype::createStructure(init.vm, init.global, init.global->objectPrototype()); + auto* prototype = JSCipherPrototype::create(init.vm, init.global, prototypeStructure); + + auto* constructorStructure = JSCipherConstructor::createStructure(init.vm, init.global, init.global->functionPrototype()); + auto* constructor = JSCipherConstructor::create(init.vm, constructorStructure, prototype); + + auto* structure = JSCipher::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/JSCipher.h b/src/bun.js/bindings/node/crypto/JSCipher.h new file mode 100644 index 0000000000..a87f7084e5 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSCipher.h @@ -0,0 +1,122 @@ +#pragma once + +#include "root.h" +#include +#include +#include +#include +#include "ncrypto.h" +#include "BunClientData.h" +#include "openssl/ssl.h" + +namespace Bun { + +enum class CipherKind { + Cipher, + Decipher, +}; + +enum class UpdateResult { + Success, + ErrorMessageSize, + ErrorState +}; + +enum class AuthTagState { + AuthTagUnknown, + AuthTagKnown, + AuthTagPassedToOpenSSL +}; + +class JSCipher final : public JSC::JSDestructibleObject { +public: + using Base = JSC::JSDestructibleObject; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + DECLARE_INFO; + DECLARE_VISIT_CHILDREN; + + 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()); + } + + static JSCipher* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSGlobalObject* globalObject, CipherKind kind, ncrypto::CipherCtxPointer&& ctx, std::optional authTagLen, int32_t maxMessageSize) + { + JSCipher* instance = new (NotNull, JSC::allocateCell(vm)) JSCipher(vm, structure, kind, WTFMove(ctx), authTagLen, maxMessageSize); + instance->finishCreation(vm, globalObject); + return instance; + } + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForJSCipher.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSCipher = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForJSCipher.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForJSCipher = std::forward(space); }); + } + + bool checkCCMMessageLength(int32_t messageLen) const + { + if (messageLen > m_maxMessageSize) { + return false; + } + + return true; + } + + bool maybePassAuthTagToOpenSSL() + { + if (m_authTagState == AuthTagState::AuthTagKnown) { + ncrypto::Buffer buf { + .data = m_authTag, + .len = m_authTagLen.value(), + }; + + if (!m_ctx.setAeadTag(buf)) { + return false; + } + + m_authTagState = AuthTagState::AuthTagPassedToOpenSSL; + } + + return true; + } + + bool isAuthenticatedMode() const + { + return ncrypto::Cipher::FromCtx(m_ctx).isSupportedAuthenticatedMode(); + } + + ncrypto::CipherCtxPointer m_ctx; + const CipherKind m_kind; + AuthTagState m_authTagState; + std::optional m_authTagLen; + char m_authTag[EVP_GCM_TLS_TAG_LEN]; + bool m_pendingAuthFailed; + int32_t m_maxMessageSize; + +private: + JSCipher(JSC::VM& vm, JSC::Structure* structure, CipherKind kind, ncrypto::CipherCtxPointer&& ctx, std::optional authTagLen, int32_t maxMessageSize) + : Base(vm, structure) + , m_kind(kind) + , m_authTagState(AuthTagState::AuthTagUnknown) + , m_authTagLen(authTagLen) + , m_pendingAuthFailed(false) + , m_maxMessageSize(maxMessageSize) + , m_ctx(WTFMove(ctx)) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); + static void destroy(JSC::JSCell* cell) { static_cast(cell)->~JSCipher(); } +}; + +void setupCipherClassStructure(JSC::LazyClassStructure::Initializer&); + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSCipherConstructor.cpp b/src/bun.js/bindings/node/crypto/JSCipherConstructor.cpp new file mode 100644 index 0000000000..26426974b7 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSCipherConstructor.cpp @@ -0,0 +1,223 @@ +#include "JSCipherConstructor.h" +#include "JSCipher.h" +#include "ErrorCode.h" +#include "JSBufferEncodingType.h" +#include "NodeValidator.h" +#include +#include +#include "CryptoUtil.h" +#include "openssl/dh.h" +#include "openssl/bn.h" +#include "openssl/err.h" +#include "ncrypto.h" + +using namespace JSC; +using namespace WebCore; +using namespace ncrypto; + +namespace Bun { + +const JSC::ClassInfo JSCipherConstructor::s_info = { "Cipher"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCipherConstructor) }; + +JSC_DEFINE_HOST_FUNCTION(callCipher, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + auto* constructor = globalObject->m_JSCipherClassStructure.constructor(globalObject); + + ArgList args = ArgList(callFrame); + auto callData = JSC::getConstructData(constructor); + JSC::JSValue result = JSC::construct(globalObject, constructor, callData, args); + return JSValue::encode(result); +} + +void initAuthenticated(JSGlobalObject* globalObject, ThrowScope& scope, CipherCtxPointer& ctx, const WTF::StringView& cipherString, CipherKind kind, int32_t ivLen, std::optional& authTagLen, int32_t& maxMessageSize) +{ + MarkPopErrorOnReturn popError; + + if (!ctx.setIvLength(ivLen)) { + ERR::CRYPTO_INVALID_IV(scope, globalObject); + return; + } + + if (ctx.isGcmMode()) { + if (authTagLen.has_value()) { + if (!Cipher::IsValidGCMTagLength(*authTagLen)) { + WTF::StringBuilder builder; + builder.append("Invalid authentication tag length: "_s); + builder.append(*authTagLen); + ERR::CRYPTO_INVALID_AUTH_TAG(scope, globalObject, builder.toString()); + return; + } + } + } else { + if (!authTagLen.has_value()) { + if (ctx.isChaCha20Poly1305()) { + authTagLen = 16; + } else { + WTF::StringBuilder builder; + builder.append("authTagLength required for: "_s); + builder.append(cipherString); + ERR::CRYPTO_INVALID_AUTH_TAG(scope, globalObject, builder.toString()); + return; + } + } + + if (ctx.isCcmMode() && kind == CipherKind::Decipher && ncrypto::isFipsEnabled()) { + ERR::CRYPTO_UNSUPPORTED_OPERATION(scope, globalObject, "CCM encryption not supported in FIPS mode"_s); + return; + } + + if (!ctx.setAeadTagLength(*authTagLen)) { + WTF::StringBuilder builder; + builder.append("Invalid authentication tag length: "_s); + builder.append(*authTagLen); + ERR::CRYPTO_INVALID_AUTH_TAG(scope, globalObject, builder.toString()); + return; + } + + if (ctx.isCcmMode()) { + if (ivLen == 12) + maxMessageSize = 16777215; + else if (ivLen == 13) + maxMessageSize = 65535; + else + maxMessageSize = INT_MAX; + } + } +} + +JSC_DEFINE_HOST_FUNCTION(constructCipher, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSValue isDecipherValue = callFrame->argument(0); + ASSERT(isDecipherValue.isBoolean()); + CipherKind cipherKind = isDecipherValue.toBoolean(globalObject) ? CipherKind::Decipher : CipherKind::Cipher; + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + JSValue cipherValue = callFrame->argument(1); + JSValue keyValue = callFrame->argument(2); + JSValue ivValue = callFrame->argument(3); + JSValue optionsValue = callFrame->argument(4); + + V::validateString(scope, globalObject, cipherValue, "cipher"_s); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + JSValue encodingValue = jsUndefined(); + if (optionsValue.pureToBoolean() != TriState::False) { + + encodingValue = optionsValue.get(globalObject, Identifier::fromString(vm, "encoding"_s)); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + if (encodingValue.isUndefinedOrNull()) { + encodingValue = jsUndefined(); + } else { + V::validateString(scope, globalObject, encodingValue, "options.encoding"_s); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + } + } + + WTF::Vector keyData; + prepareSecretKey(globalObject, scope, keyData, keyValue, encodingValue); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + JSArrayBufferView* ivView = nullptr; + if (!ivValue.isNull()) { + ivView = getArrayBufferOrView(globalObject, scope, ivValue, "iv"_s, jsUndefined()); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + } + + std::optional authTagLength = std::nullopt; + if (optionsValue.pureToBoolean() != TriState::False) { + JSValue authTagLengthValue = optionsValue.get(globalObject, Identifier::fromString(vm, "authTagLength"_s)); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + double authTagLengthNumber = authTagLengthValue.toNumber(globalObject); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + authTagLength = JSC::toInt32(authTagLengthNumber); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + if (authTagLengthNumber != authTagLength) { + return ERR::INVALID_ARG_VALUE(scope, globalObject, "options.authTagLength"_s, authTagLengthValue); + } + } + + WTF::String cipherString = cipherValue.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + if (UNLIKELY(keyData.size() > INT_MAX)) { + return ERR::OUT_OF_RANGE(scope, globalObject, "key is too big"_s, 0, INT_MAX, jsNumber(keyData.size())); + } + + int32_t ivLen = 0; + if (ivView) { + if (UNLIKELY(ivView->byteLength() > INT_MAX)) { + return ERR::OUT_OF_RANGE(scope, globalObject, "iv is too big"_s, 0, INT_MAX, jsNumber(ivView->byteLength())); + } + ivLen = ivView->byteLength(); + } + + MarkPopErrorOnReturn popError; + + Cipher cipher = Cipher::FromName(cipherString); + if (!cipher) { + return ERR::CRYPTO_UNKNOWN_CIPHER(scope, globalObject, cipherString); + } + + const int32_t expectedIvLen = cipher.getIvLength(); + + if (!ivView && expectedIvLen != 0) { + return ERR::CRYPTO_INVALID_IV(scope, globalObject); + } + + if (!cipher.isSupportedAuthenticatedMode() && ivView && ivView->byteLength() != expectedIvLen) { + return ERR::CRYPTO_INVALID_IV(scope, globalObject); + } + + if (cipher.isChaCha20Poly1305()) { + ASSERT(ivView); + + if (ivView->byteLength() > 12) { + return ERR::CRYPTO_INVALID_IV(scope, globalObject); + } + } + + CipherCtxPointer ctx = CipherCtxPointer::New(); + + if (cipher.isWrapMode()) { + ctx.setAllowWrap(); + } + + const bool encrypt = cipherKind == CipherKind::Cipher; + if (!ctx.init(cipher, encrypt)) { + throwCryptoError(globalObject, scope, ERR_get_error(), "Failed to initialize cipher"_s); + return JSValue::encode({}); + } + + int32_t maxMessageSize = 0; + if (cipher.isSupportedAuthenticatedMode()) { + initAuthenticated(globalObject, scope, ctx, cipherString, cipherKind, ivLen, authTagLength, maxMessageSize); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + } + + if (!ctx.setKeyLength(keyData.size())) { + ctx.reset(); + return ERR::CRYPTO_INVALID_KEYLEN(scope, globalObject); + } + + if (!ctx.init(Cipher(), encrypt, keyData.data(), ivView ? reinterpret_cast(ivView->vector()) : nullptr)) { + throwCryptoError(globalObject, scope, ERR_get_error(), "Failed to initialize cipher"_s); + } + + auto* zigGlobalObject = defaultGlobalObject(globalObject); + JSC::Structure* structure = zigGlobalObject->m_JSCipherClassStructure.get(zigGlobalObject); + + return JSC::JSValue::encode(JSCipher::create(vm, structure, globalObject, cipherKind, WTFMove(ctx), authTagLength, maxMessageSize)); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSCipherConstructor.h b/src/bun.js/bindings/node/crypto/JSCipherConstructor.h new file mode 100644 index 0000000000..237da0af07 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSCipherConstructor.h @@ -0,0 +1,49 @@ +#pragma once + +#include "root.h" +#include + +namespace Bun { + +JSC_DECLARE_HOST_FUNCTION(callCipher); +JSC_DECLARE_HOST_FUNCTION(constructCipher); + +class JSCipherConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSCipherConstructor* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSObject* prototype) + { + JSCipherConstructor* constructor = new (NotNull, JSC::allocateCell(vm)) JSCipherConstructor(vm, structure); + constructor->finishCreation(vm, prototype); + return constructor; + } + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.internalFunctionSpace(); + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info()); + } + +private: + JSCipherConstructor(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, callCipher, constructCipher) + { + } + + void finishCreation(JSC::VM& vm, JSC::JSObject* prototype) + { + Base::finishCreation(vm, 2, "Cipher"_s); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly); + } +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSCipherPrototype.cpp b/src/bun.js/bindings/node/crypto/JSCipherPrototype.cpp new file mode 100644 index 0000000000..79e4f85cc0 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSCipherPrototype.cpp @@ -0,0 +1,386 @@ +#include "JSCipherPrototype.h" +#include "JSCipher.h" +#include "ErrorCode.h" +#include "CryptoUtil.h" +#include "BunProcess.h" +#include "NodeValidator.h" +#include "JSBufferEncodingType.h" +#include +#include + +extern "C" bool Bun__Node__ProcessNoDeprecation; + +using namespace Bun; +using namespace JSC; +using namespace WebCore; +using namespace ncrypto; + +// Declare host function prototypes +JSC_DECLARE_HOST_FUNCTION(jsCipherUpdate); +JSC_DECLARE_HOST_FUNCTION(jsCipherFinal); +JSC_DECLARE_HOST_FUNCTION(jsCipherSetAutoPadding); +JSC_DECLARE_HOST_FUNCTION(jsCipherGetAuthTag); +JSC_DECLARE_HOST_FUNCTION(jsCipherSetAuthTag); +JSC_DECLARE_HOST_FUNCTION(jsCipherSetAAD); + +const JSC::ClassInfo JSCipherPrototype::s_info = { "Cipher"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCipherPrototype) }; + +static const JSC::HashTableValue JSCipherPrototypeTableValues[] = { + { "update"_s, static_cast(JSC::PropertyAttribute::Function), JSC::NoIntrinsic, { HashTableValue::NativeFunctionType, jsCipherUpdate, 2 } }, + { "final"_s, static_cast(JSC::PropertyAttribute::Function), JSC::NoIntrinsic, { HashTableValue::NativeFunctionType, jsCipherFinal, 0 } }, + { "setAutoPadding"_s, static_cast(JSC::PropertyAttribute::Function), JSC::NoIntrinsic, { HashTableValue::NativeFunctionType, jsCipherSetAutoPadding, 1 } }, + { "getAuthTag"_s, static_cast(JSC::PropertyAttribute::Function), JSC::NoIntrinsic, { HashTableValue::NativeFunctionType, jsCipherGetAuthTag, 0 } }, + { "setAuthTag"_s, static_cast(JSC::PropertyAttribute::Function), JSC::NoIntrinsic, { HashTableValue::NativeFunctionType, jsCipherSetAuthTag, 1 } }, + { "setAAD"_s, static_cast(JSC::PropertyAttribute::Function), JSC::NoIntrinsic, { HashTableValue::NativeFunctionType, jsCipherSetAAD, 2 } }, +}; + +void JSCipherPrototype::finishCreation(JSC::VM& vm) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSCipherPrototype::info(), JSCipherPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +JSC_DEFINE_HOST_FUNCTION(jsCipherUpdate, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + JSCipher* cipher = jsDynamicCast(callFrame->thisValue()); + if (!cipher) { + throwThisTypeError(*lexicalGlobalObject, scope, "Cipher"_s, "update"_s); + return JSValue::encode({}); + } + + JSValue dataValue = callFrame->argument(0); + JSValue encodingValue = callFrame->argument(1); + + WTF::String dataString = WTF::nullString(); + WTF::String encodingString = WTF::nullString(); + + JSArrayBufferView* dataView = getArrayBufferOrView(lexicalGlobalObject, scope, dataValue, "data"_s, encodingValue); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + MarkPopErrorOnReturn popError; + + if (dataView->byteLength() > INT_MAX) { + return ERR::OUT_OF_RANGE(scope, lexicalGlobalObject, "data is too big"_s, 0, INT_MAX, jsNumber(dataView->byteLength())); + } + + if (!cipher->m_ctx) { + throwCryptoError(lexicalGlobalObject, scope, popError.peekError(), "Trying to add data in unsupported state"); + return JSValue::encode({}); + } + + if (cipher->m_ctx.isCcmMode() && !cipher->checkCCMMessageLength(dataView->byteLength())) { + // return undefined + // https://github.com/nodejs/node/blob/6b4255434226491449b7d925038008439e5586b2/src/crypto/crypto_cipher.cc#L742 + return JSValue::encode(jsUndefined()); + } + + if (cipher->m_kind == CipherKind::Decipher && cipher->isAuthenticatedMode()) { + ASSERT(cipher->maybePassAuthTagToOpenSSL()); + } + + const int32_t blockSize = cipher->m_ctx.getBlockSize(); + if (dataView->byteLength() + blockSize > INT_MAX) { + throwCryptoError(lexicalGlobalObject, scope, popError.peekError(), "Trying to add data in unsupported state"); + return JSValue::encode({}); + } + int32_t bufLen = dataView->byteLength() + blockSize; + + ncrypto::Buffer buf { + .data = reinterpret_cast(dataView->vector()), + .len = dataView->byteLength(), + }; + + if (cipher->m_kind == CipherKind::Cipher && cipher->m_ctx.isWrapMode() && !cipher->m_ctx.update(buf, nullptr, &bufLen)) { + throwCryptoError(lexicalGlobalObject, scope, popError.peekError(), "Trying to add data in unsupported state"); + return JSValue::encode({}); + } + + RefPtr outBuf = JSC::ArrayBuffer::tryCreateUninitialized(bufLen, 1); + if (!outBuf) { + throwOutOfMemoryError(lexicalGlobalObject, scope); + return JSValue::encode({}); + } + + buf = { + .data = reinterpret_cast(dataView->vector()), + .len = dataView->byteLength(), + }; + + bool res = cipher->m_ctx.update(buf, static_cast(outBuf->data()), &bufLen); + ASSERT(static_cast(bufLen) <= outBuf->byteLength()); + + if (!res && cipher->m_kind == CipherKind::Decipher && cipher->m_ctx.isCcmMode()) { + cipher->m_pendingAuthFailed = true; + return JSValue::encode(JSUint8Array::create(lexicalGlobalObject, globalObject->JSBufferSubclassStructure(), WTFMove(outBuf), 0, bufLen)); + } + + if (res != 1) { + throwCryptoError(lexicalGlobalObject, scope, popError.peekError(), "Trying to add data in unsupported state"); + return JSValue::encode({}); + } + + return JSValue::encode(JSUint8Array::create(lexicalGlobalObject, globalObject->JSBufferSubclassStructure(), WTFMove(outBuf), 0, bufLen)); +} + +JSC_DEFINE_HOST_FUNCTION(jsCipherFinal, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + MarkPopErrorOnReturn popError; + + JSCipher* cipher = jsDynamicCast(callFrame->thisValue()); + if (!cipher) { + throwThisTypeError(*lexicalGlobalObject, scope, "Cipher"_s, "final"_s); + return JSValue::encode({}); + } + + if (!cipher->m_ctx) { + return ERR::CRYPTO_INVALID_STATE(scope, lexicalGlobalObject, "final"_s); + } + + const bool isAuthMode = cipher->isAuthenticatedMode(); + + auto throwCryptoErrorWithAuth = [isAuthMode, &popError](JSGlobalObject* globalObject, ThrowScope& scope) { + throwCryptoError(globalObject, scope, popError.peekError(), isAuthMode ? "Unsupported state or unable to authenticate data" : "Unsupported state"); + }; + + int32_t outLen = cipher->m_ctx.getBlockSize(); + RefPtr outBuf = ArrayBuffer::tryCreateUninitialized(outLen, 1); + if (!outBuf) { + throwOutOfMemoryError(lexicalGlobalObject, scope); + return JSValue::encode({}); + } + + if (cipher->m_kind == CipherKind::Decipher && Cipher::FromCtx(cipher->m_ctx).isSupportedAuthenticatedMode()) { + cipher->maybePassAuthTagToOpenSSL(); + } + + if (cipher->m_kind == CipherKind::Decipher && cipher->m_ctx.isChaCha20Poly1305() && cipher->m_authTagState != AuthTagState::AuthTagPassedToOpenSSL) { + throwCryptoErrorWithAuth(lexicalGlobalObject, scope); + return JSValue::encode({}); + } + + bool ok; + if (cipher->m_kind == CipherKind::Decipher && cipher->m_ctx.isCcmMode()) { + ok = !cipher->m_pendingAuthFailed; + outLen = 0; + } else { + ok = cipher->m_ctx.update({}, static_cast(outBuf->data()), &outLen, true); + ASSERT(outLen <= outBuf->byteLength()); + + if (ok && cipher->m_kind == CipherKind::Cipher && cipher->isAuthenticatedMode()) { + if (!cipher->m_authTagLen.has_value()) { + ASSERT(cipher->m_ctx.isGcmMode()); + cipher->m_authTagLen = sizeof(cipher->m_authTag); + } + + ok = cipher->m_ctx.getAeadTag(*cipher->m_authTagLen, reinterpret_cast(cipher->m_authTag)); + } + } + + cipher->m_ctx.reset(); + + if (!ok) { + throwCryptoErrorWithAuth(lexicalGlobalObject, scope); + return JSValue::encode({}); + } + + return JSValue::encode(JSUint8Array::create(lexicalGlobalObject, globalObject->JSBufferSubclassStructure(), WTFMove(outBuf), 0, outLen)); +} + +JSC_DEFINE_HOST_FUNCTION(jsCipherSetAutoPadding, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSCipher* cipher = jsDynamicCast(callFrame->thisValue()); + if (!cipher) { + throwThisTypeError(*globalObject, scope, "Cipher"_s, "setAutoPadding"_s); + return JSValue::encode({}); + } + + JSValue paddingValue = callFrame->argument(0); + + bool padding = paddingValue.toBoolean(globalObject); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + MarkPopErrorOnReturn popError; + if (!cipher->m_ctx.setPadding(padding)) { + return ERR::CRYPTO_INVALID_STATE(scope, globalObject, "setAutoPadding"_s); + } + + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsCipherGetAuthTag, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSCipher* cipher = jsDynamicCast(callFrame->thisValue()); + if (!cipher) { + throwThisTypeError(*lexicalGlobalObject, scope, "Cipher"_s, "getAuthTag"_s); + return JSValue::encode({}); + } + + if (cipher->m_ctx || cipher->m_kind != CipherKind::Cipher || !cipher->m_authTagLen) { + return ERR::CRYPTO_INVALID_STATE(scope, lexicalGlobalObject, "getAuthTag"_s); + } + + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + JSC::JSUint8Array* buf = JSC::JSUint8Array::createUninitialized(lexicalGlobalObject, globalObject->JSBufferSubclassStructure(), *cipher->m_authTagLen); + if (!buf) { + throwOutOfMemoryError(lexicalGlobalObject, scope); + return JSValue::encode({}); + } + + memcpy(buf->vector(), cipher->m_authTag, *cipher->m_authTagLen); + + return JSValue::encode(buf); +} + +JSC_DEFINE_HOST_FUNCTION(jsCipherSetAuthTag, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSCipher* cipher = jsDynamicCast(callFrame->thisValue()); + if (!cipher) { + throwThisTypeError(*globalObject, scope, "Cipher"_s, "setAuthTag"_s); + return JSValue::encode({}); + } + + JSValue authTagValue = callFrame->argument(0); + JSValue encodingValue = callFrame->argument(1); + JSArrayBufferView* authTag = getArrayBufferOrView(globalObject, scope, authTagValue, "buffer"_s, encodingValue); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + ASSERT(authTag); + + if (!cipher->m_ctx || !cipher->isAuthenticatedMode() || cipher->m_kind != CipherKind::Decipher || cipher->m_authTagState != AuthTagState::AuthTagUnknown) { + return ERR::CRYPTO_INVALID_STATE(scope, globalObject, "setAuthTag"_s); + } + + if (authTag->byteLength() > INT_MAX) { + return ERR::OUT_OF_RANGE(scope, globalObject, "buffer is too big"_s, 0, INT_MAX, jsNumber(authTag->byteLength())); + } + + uint32_t tagLen = authTag->byteLength(); + + bool isValid; + if (cipher->m_ctx.isGcmMode()) { + isValid = (!cipher->m_authTagLen.has_value() || *cipher->m_authTagLen == tagLen) && Cipher::IsValidGCMTagLength(tagLen); + } else { + ASSERT(Cipher::FromCtx(cipher->m_ctx).isSupportedAuthenticatedMode()); + ASSERT(cipher->m_authTagLen.has_value()); + isValid = *cipher->m_authTagLen == tagLen; + } + + if (!isValid) { + WTF::StringBuilder builder; + builder.append("Invalid authentication tag length: "_s); + builder.append(tagLen); + return ERR::CRYPTO_INVALID_AUTH_TAG(scope, globalObject, builder.toString()); + } + + if (cipher->m_ctx.isGcmMode() && !cipher->m_authTagLen.has_value() && tagLen != 16 && !Bun__Node__ProcessNoDeprecation) { + Bun::Process::emitWarning(globalObject, jsString(vm, makeString("Using AES-GCM authentication tags of less than 128 bits without specifying the authTagLength option when initializing decryption is deprecated."_s)), jsString(vm, makeString("DeprecationWarning"_s)), jsString(vm, makeString("DEP0182"_s)), jsUndefined()); + } + + cipher->m_authTagLen = tagLen; + cipher->m_authTagState = AuthTagState::AuthTagKnown; + + memset(cipher->m_authTag, 0, sizeof(cipher->m_authTag)); + memcpy(cipher->m_authTag, authTag->vector(), *cipher->m_authTagLen); + + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsCipherSetAAD, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSCipher* cipher = jsDynamicCast(callFrame->thisValue()); + if (!cipher) { + throwThisTypeError(*globalObject, scope, "Cipher"_s, "setAAD"_s); + return JSValue::encode({}); + } + + JSValue aadbufValue = callFrame->argument(0); + JSValue optionsValue = callFrame->argument(1); + + JSValue encodingValue = jsUndefined(); + std::optional plaintextLength = std::nullopt; + if (optionsValue.pureToBoolean() != TriState::False) { + encodingValue = optionsValue.get(globalObject, Identifier::fromString(vm, "encoding"_s)); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + V::validateString(scope, globalObject, encodingValue, "options.encoding"_s); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + JSValue plaintextLengthValue = optionsValue.get(globalObject, Identifier::fromString(vm, "plaintextLength"_s)); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + double plaintextLengthNumber = plaintextLengthValue.toNumber(globalObject); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + plaintextLength = JSC::toInt32(plaintextLengthNumber); + if (plaintextLengthNumber != plaintextLength) { + return ERR::INVALID_ARG_VALUE(scope, globalObject, "options.plaintextLength"_s, plaintextLengthValue); + } + } + + JSArrayBufferView* aadbuf = getArrayBufferOrView(globalObject, scope, aadbufValue, "aadbuf"_s, encodingValue); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + ASSERT(aadbuf); + + if (aadbuf->byteLength() > std::numeric_limits::max()) { + return ERR::OUT_OF_RANGE(scope, globalObject, "buffer is too big"_s, 0, INT_MAX, jsNumber(aadbuf->byteLength())); + } + + MarkPopErrorOnReturn popError; + + int32_t outlen; + + if (cipher->m_ctx.isCcmMode()) { + if (!plaintextLength.has_value()) { + return ERR::MISSING_ARGS(scope, globalObject, "options.plaintextLength required for CCM mode with AAD"_s); + } + + if (!cipher->checkCCMMessageLength(*plaintextLength)) { + return ERR::CRYPTO_INVALID_MESSAGELEN(scope, globalObject); + } + + if (cipher->m_kind == CipherKind::Decipher && !cipher->maybePassAuthTagToOpenSSL()) { + return ERR::CRYPTO_INVALID_STATE(scope, globalObject, "setAAD"_s); + } + + ncrypto::Buffer buf { + .data = nullptr, + .len = static_cast(*plaintextLength), + }; + + if (!cipher->m_ctx.update(buf, nullptr, &outlen)) { + return ERR::CRYPTO_INVALID_STATE(scope, globalObject, "setAAD"_s); + } + } + + ncrypto::Buffer buf { + .data = reinterpret_cast(aadbuf->vector()), + .len = aadbuf->byteLength(), + }; + + if (!cipher->m_ctx.update(buf, nullptr, &outlen)) { + return ERR::CRYPTO_INVALID_STATE(scope, globalObject, "setAAD"_s); + } + + return JSValue::encode(jsUndefined()); +} diff --git a/src/bun.js/bindings/node/crypto/JSCipherPrototype.h b/src/bun.js/bindings/node/crypto/JSCipherPrototype.h new file mode 100644 index 0000000000..3d4a9b4897 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSCipherPrototype.h @@ -0,0 +1,45 @@ +#pragma once + +#include "root.h" +#include +#include + +namespace Bun { + +class JSCipherPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSCipherPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSCipherPrototype* prototype = new (NotNull, JSC::allocateCell(vm)) JSCipherPrototype(vm, structure); + prototype->finishCreation(vm); + return prototype; + } + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.plainObjectSpace(); + } + + DECLARE_INFO; + + 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: + JSCipherPrototype(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&); +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSDiffieHellman.h b/src/bun.js/bindings/node/crypto/JSDiffieHellman.h index 2e99679530..3645d59084 100644 --- a/src/bun.js/bindings/node/crypto/JSDiffieHellman.h +++ b/src/bun.js/bindings/node/crypto/JSDiffieHellman.h @@ -24,7 +24,7 @@ public: static JSDiffieHellman* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSGlobalObject* globalObject, ncrypto::DHPointer&& dh) { - JSDiffieHellman* instance = new (NotNull, JSC::allocateCell(vm)) JSDiffieHellman(vm, structure, std::move(dh)); + JSDiffieHellman* instance = new (NotNull, JSC::allocateCell(vm)) JSDiffieHellman(vm, structure, WTFMove(dh)); instance->finishCreation(vm, globalObject); return instance; } @@ -48,7 +48,7 @@ public: private: JSDiffieHellman(JSC::VM& vm, JSC::Structure* structure, ncrypto::DHPointer&& dh) : Base(vm, structure) - , m_dh(std::move(dh)) + , m_dh(WTFMove(dh)) { } diff --git a/src/bun.js/bindings/node/crypto/JSHash.cpp b/src/bun.js/bindings/node/crypto/JSHash.cpp index bb3a3e89b9..2227ed3577 100644 --- a/src/bun.js/bindings/node/crypto/JSHash.cpp +++ b/src/bun.js/bindings/node/crypto/JSHash.cpp @@ -261,15 +261,22 @@ JSC_DEFINE_HOST_FUNCTION(jsHashProtoFuncDigest, (JSC::JSGlobalObject * lexicalGl // 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); + + const EVP_MD* md = hash->m_ctx.getDigest(); + uint32_t bufLen = len; + if (md == EVP_sha512_224()) { + // SHA-512/224 expects buffer length of length % 8. can be truncated afterwards + bufLen = SHA512_224_DIGEST_BUFFER_LENGTH; + } + + auto data = hash->m_ctx.digestFinal(bufLen); if (!data) { throwCryptoError(lexicalGlobalObject, scope, ERR_get_error(), "Failed to finalize digest"_s); return JSValue::encode({}); } - // Store the digest in the hash object + // Some hash algorithms don't support calling EVP_DigestFinal_ex more than once + // We need to cache the result for future calls hash->m_digest = ByteSource::allocated(data.release()); } diff --git a/src/bun.js/bindings/node/crypto/node_crypto_binding.cpp b/src/bun.js/bindings/node/crypto/node_crypto_binding.cpp index d762a34a48..d933d43696 100644 --- a/src/bun.js/bindings/node/crypto/node_crypto_binding.cpp +++ b/src/bun.js/bindings/node/crypto/node_crypto_binding.cpp @@ -45,6 +45,7 @@ #include "JSHmac.h" #include "JSHash.h" #include "CryptoPrimes.h" +#include "JSCipher.h" #include "CryptoHkdf.h" using namespace JSC; @@ -425,6 +426,9 @@ JSValue createNodeCryptoBinding(Zig::GlobalObject* globalObject) obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "checkPrimeSync"_s)), JSFunction::create(vm, globalObject, 2, "checkPrimeSync"_s, jsCheckPrimeSync, ImplementationVisibility::Public, NoIntrinsic), 0); + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "Cipher"_s)), + globalObject->m_JSCipherClassStructure.constructor(globalObject)); + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "hkdf"_s)), JSFunction::create(vm, globalObject, 6, "hkdf"_s, jsHkdf, ImplementationVisibility::Public, NoIntrinsic), 0); obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "hkdfSync"_s)), diff --git a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h index 3ba26026fd..74ae382bf8 100644 --- a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h @@ -931,6 +931,7 @@ public: std::unique_ptr m_clientSubspaceForJSVerify; std::unique_ptr m_clientSubspaceForJSHmac; std::unique_ptr m_clientSubspaceForJSHash; + std::unique_ptr m_clientSubspaceForJSCipher; 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 397854144d..8cbd7f7641 100644 --- a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h @@ -937,6 +937,7 @@ public: std::unique_ptr m_subspaceForJSDiffieHellman; std::unique_ptr m_subspaceForJSDiffieHellmanGroup; std::unique_ptr m_subspaceForJSECDH; + std::unique_ptr m_subspaceForJSCipher; }; } // namespace WebCore diff --git a/src/js/node/crypto.ts b/src/js/node/crypto.ts index b2d5e2c8de..b2d39dabc1 100644 --- a/src/js/node/crypto.ts +++ b/src/js/node/crypto.ts @@ -1,9 +1,6 @@ // Hardcoded module "node:crypto" -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; @@ -46,6 +43,7 @@ const { checkPrimeSync, generatePrime, generatePrimeSync, + Cipher, hkdf, hkdfSync, } = $cpp("node_crypto_binding.cpp", "createNodeCryptoBinding"); @@ -66,6 +64,8 @@ const { getHashes, } = $zig("node_crypto_binding.zig", "createNodeCryptoBindingZig"); +const normalizeEncoding = $newZigFunction("node_util_binding.zig", "normalizeEncoding", 1); + const { validateObject, validateString } = require("internal/validators"); const kHandle = Symbol("kHandle"); @@ -95,7 +95,6 @@ Certificate.exportPublicKey = exportPublicKey; Certificate.exportChallenge = exportChallenge; var Buffer = globalThis.Buffer; -const EMPTY_BUFFER = Buffer.alloc(0); const { isAnyArrayBuffer, isArrayBufferView } = require("node:util/types"); function getArrayBufferOrView(buffer, name, encoding?) { @@ -126,1860 +125,11 @@ function getArrayBufferOrView(buffer, name, encoding?) { } const crypto = globalThis.crypto; -const globalCrypto = crypto; -var __commonJS = (cb, mod: typeof module | undefined = undefined) => - function () { - return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; - }; +var crypto_exports: any = {}; -// node_modules/safe-buffer/index.js -var require_safe_buffer = __commonJS({ - "node_modules/safe-buffer/index.js"(exports, module) { - var buffer = BufferModule, - Buffer2 = buffer.Buffer; - function copyProps(src, dst) { - for (var key in src) dst[key] = src[key]; - } - Buffer2.from && Buffer2.alloc && Buffer2.allocUnsafe && Buffer2.allocUnsafeSlow - ? (module.exports = buffer) - : (copyProps(buffer, exports), (exports.Buffer = SafeBuffer)); - function SafeBuffer(arg, encodingOrOffset, length) { - return Buffer2(arg, encodingOrOffset, length); - } - SafeBuffer.prototype = Object.create(Buffer2.prototype); - copyProps(Buffer2, SafeBuffer); - SafeBuffer.from = function (arg, encodingOrOffset, length) { - if (typeof arg == "number") throw new TypeError("Argument must not be a number"); - return Buffer2(arg, encodingOrOffset, length); - }; - SafeBuffer.alloc = function (size, fill, encoding) { - if (typeof size != "number") throw new TypeError("Argument must be a number"); - var buf = Buffer2(size); - return ( - fill !== void 0 ? (typeof encoding == "string" ? buf.fill(fill, encoding) : buf.fill(fill)) : buf.fill(0), buf - ); - }; - SafeBuffer.allocUnsafe = function (size) { - if (typeof size != "number") throw new TypeError("Argument must be a number"); - return Buffer2(size); - }; - SafeBuffer.allocUnsafeSlow = function (size) { - if (typeof size != "number") throw new TypeError("Argument must be a number"); - return buffer.SlowBuffer(size); - }; - }, -}); - -// node_modules/inherits/inherits_browser.js -var require_inherits_browser = __commonJS({ - "node_modules/inherits/inherits_browser.js"(exports, module) { - module.exports = function (ctor, superCtor) { - superCtor && - ((ctor.super_ = superCtor), - (ctor.prototype = Object.create(superCtor.prototype, { - constructor: { - value: ctor, - enumerable: !1, - writable: !0, - configurable: !0, - }, - }))); - }; - }, -}); - -// node_modules/cipher-base/index.js -var require_cipher_base = __commonJS({ - "node_modules/cipher-base/index.js"(exports, module) { - var Buffer2 = require_safe_buffer().Buffer, - inherits = require_inherits_browser(); - function CipherBase(hashMode) { - StreamModule.Transform.$call(this), - (this.hashMode = typeof hashMode == "string"), - this.hashMode ? (this[hashMode] = this._finalOrDigest) : (this.final = this._finalOrDigest), - this._final && ((this.__final = this._final), (this._final = null)), - (this._decoder = null), - (this._encoding = null); - this._finalized = !1; - } - inherits(CipherBase, StreamModule.Transform); - CipherBase.prototype.update = function (data, inputEnc, outputEnc) { - if (outputEnc === "buffer") outputEnc = undefined; - typeof data == "string" && (data = Buffer2.from(data, inputEnc)); - var outData = this._update(data); - return this.hashMode ? this : (outputEnc && (outData = this._toString(outData, outputEnc)), outData); - }; - CipherBase.prototype.setAutoPadding = function () {}; - CipherBase.prototype.getAuthTag = function () { - throw new Error("trying to get auth tag in unsupported state"); - }; - CipherBase.prototype.setAuthTag = function () { - throw new Error("trying to set auth tag in unsupported state"); - }; - CipherBase.prototype.setAAD = function () { - throw new Error("trying to set aad in unsupported state"); - }; - CipherBase.prototype._transform = function (data, _, next) { - var err; - try { - this.hashMode ? this._update(data) : this.push(this._update(data)); - } catch (e) { - err = e; - } finally { - next(err); - } - }; - CipherBase.prototype._flush = function (done) { - var err; - try { - this.push(this.__final()); - } catch (e) { - err = e; - } - done(err); - }; - CipherBase.prototype._finalOrDigest = function (outputEnc) { - if (outputEnc === "buffer") outputEnc = undefined; - if (this._finalized) { - if (!this._encoding) return Buffer2.alloc(0); - return ""; - } - - this._finalized = !0; - var outData = this.__final() || Buffer2.alloc(0); - return outputEnc && (outData = this._toString(outData, outputEnc, !0)), outData; - }; - CipherBase.prototype._toString = function (value, enc, fin) { - if ((this._decoder || ((this._decoder = new StringDecoder(enc)), (this._encoding = enc)), this._encoding !== enc)) - throw new Error("can't switch encodings"); - var out = this._decoder.write(value); - return fin && (out += this._decoder.end()), out; - }; - module.exports = CipherBase; - }, -}); - -// node_modules/browserify-sign/browser/algorithms.json -var require_algorithms = __commonJS({ - "node_modules/browserify-sign/browser/algorithms.json"(exports, module) { - module.exports = { - sha224WithRSAEncryption: { - sign: "rsa", - hash: "sha224", - id: "302d300d06096086480165030402040500041c", - }, - "RSA-SHA224": { - sign: "ecdsa/rsa", - hash: "sha224", - id: "302d300d06096086480165030402040500041c", - }, - sha256WithRSAEncryption: { - sign: "rsa", - hash: "sha256", - id: "3031300d060960864801650304020105000420", - }, - "RSA-SHA256": { - sign: "ecdsa/rsa", - hash: "sha256", - id: "3031300d060960864801650304020105000420", - }, - sha384WithRSAEncryption: { - sign: "rsa", - hash: "sha384", - id: "3041300d060960864801650304020205000430", - }, - "RSA-SHA384": { - sign: "ecdsa/rsa", - hash: "sha384", - id: "3041300d060960864801650304020205000430", - }, - sha512WithRSAEncryption: { - sign: "rsa", - hash: "sha512", - id: "3051300d060960864801650304020305000440", - }, - "RSA-SHA512": { - sign: "ecdsa/rsa", - hash: "sha512", - id: "3051300d060960864801650304020305000440", - }, - "RSA-SHA1": { - sign: "rsa", - hash: "sha1", - id: "3021300906052b0e03021a05000414", - }, - "ecdsa-with-SHA1": { - sign: "ecdsa", - hash: "sha1", - id: "3021300906052b0e03021a05000414", - }, - sha1: { - sign: "ecdsa/rsa", - hash: "sha1", - id: "3021300906052b0e03021a05000414", - }, - sha256: { - sign: "ecdsa/rsa", - hash: "sha256", - id: "3031300d060960864801650304020105000420", - }, - sha224: { - sign: "ecdsa/rsa", - hash: "sha224", - id: "302d300d06096086480165030402040500041c", - }, - sha384: { - sign: "ecdsa/rsa", - hash: "sha384", - id: "3041300d060960864801650304020205000430", - }, - sha512: { - sign: "ecdsa/rsa", - hash: "sha512", - id: "3051300d060960864801650304020305000440", - }, - "DSA-SHA": { - sign: "dsa", - hash: "sha1", - id: "", - }, - "DSA-SHA1": { - sign: "dsa", - hash: "sha1", - id: "", - }, - DSA: { - sign: "dsa", - hash: "sha1", - id: "", - }, - "DSA-WITH-SHA224": { - sign: "dsa", - hash: "sha224", - id: "", - }, - "DSA-SHA224": { - sign: "dsa", - hash: "sha224", - id: "", - }, - "DSA-WITH-SHA256": { - sign: "dsa", - hash: "sha256", - id: "", - }, - "DSA-SHA256": { - sign: "dsa", - hash: "sha256", - id: "", - }, - "DSA-WITH-SHA384": { - sign: "dsa", - hash: "sha384", - id: "", - }, - "DSA-SHA384": { - sign: "dsa", - hash: "sha384", - id: "", - }, - "DSA-WITH-SHA512": { - sign: "dsa", - hash: "sha512", - id: "", - }, - "DSA-SHA512": { - sign: "dsa", - hash: "sha512", - id: "", - }, - "DSA-RIPEMD160": { - sign: "dsa", - hash: "rmd160", - id: "", - }, - ripemd160WithRSA: { - sign: "rsa", - hash: "rmd160", - id: "3021300906052b2403020105000414", - }, - "RSA-RIPEMD160": { - sign: "rsa", - hash: "rmd160", - id: "3021300906052b2403020105000414", - }, - md5WithRSAEncryption: { - sign: "rsa", - hash: "md5", - id: "3020300c06082a864886f70d020505000410", - }, - "RSA-MD5": { - sign: "rsa", - hash: "md5", - id: "3020300c06082a864886f70d020505000410", - }, - }; - }, -}); - -// node_modules/browserify-sign/algos.js -var require_algos = __commonJS({ - "node_modules/browserify-sign/algos.js"(exports, module) { - module.exports = require_algorithms(); - }, -}); - -// node_modules/des.js/lib/des/utils.js -var require_utils = __commonJS({ - "node_modules/des.js/lib/des/utils.js"(exports) { - "use strict"; - exports.readUInt32BE = function (bytes, off) { - var res = (bytes[0 + off] << 24) | (bytes[1 + off] << 16) | (bytes[2 + off] << 8) | bytes[3 + off]; - return res >>> 0; - }; - exports.writeUInt32BE = function (bytes, value, off) { - (bytes[0 + off] = value >>> 24), - (bytes[1 + off] = (value >>> 16) & 255), - (bytes[2 + off] = (value >>> 8) & 255), - (bytes[3 + off] = value & 255); - }; - exports.ip = function (inL, inR, out, off) { - for (var outL = 0, outR = 0, i = 6; i >= 0; i -= 2) { - for (var j = 0; j <= 24; j += 8) (outL <<= 1), (outL |= (inR >>> (j + i)) & 1); - for (var j = 0; j <= 24; j += 8) (outL <<= 1), (outL |= (inL >>> (j + i)) & 1); - } - for (var i = 6; i >= 0; i -= 2) { - for (var j = 1; j <= 25; j += 8) (outR <<= 1), (outR |= (inR >>> (j + i)) & 1); - for (var j = 1; j <= 25; j += 8) (outR <<= 1), (outR |= (inL >>> (j + i)) & 1); - } - (out[off + 0] = outL >>> 0), (out[off + 1] = outR >>> 0); - }; - exports.rip = function (inL, inR, out, off) { - for (var outL = 0, outR = 0, i = 0; i < 4; i++) - for (var j = 24; j >= 0; j -= 8) - (outL <<= 1), (outL |= (inR >>> (j + i)) & 1), (outL <<= 1), (outL |= (inL >>> (j + i)) & 1); - for (var i = 4; i < 8; i++) - for (var j = 24; j >= 0; j -= 8) - (outR <<= 1), (outR |= (inR >>> (j + i)) & 1), (outR <<= 1), (outR |= (inL >>> (j + i)) & 1); - (out[off + 0] = outL >>> 0), (out[off + 1] = outR >>> 0); - }; - exports.pc1 = function (inL, inR, out, off) { - for (var outL = 0, outR = 0, i = 7; i >= 5; i--) { - for (var j = 0; j <= 24; j += 8) (outL <<= 1), (outL |= (inR >> (j + i)) & 1); - for (var j = 0; j <= 24; j += 8) (outL <<= 1), (outL |= (inL >> (j + i)) & 1); - } - for (var j = 0; j <= 24; j += 8) (outL <<= 1), (outL |= (inR >> (j + i)) & 1); - for (var i = 1; i <= 3; i++) { - for (var j = 0; j <= 24; j += 8) (outR <<= 1), (outR |= (inR >> (j + i)) & 1); - for (var j = 0; j <= 24; j += 8) (outR <<= 1), (outR |= (inL >> (j + i)) & 1); - } - for (var j = 0; j <= 24; j += 8) (outR <<= 1), (outR |= (inL >> (j + i)) & 1); - (out[off + 0] = outL >>> 0), (out[off + 1] = outR >>> 0); - }; - exports.r28shl = function (num, shift) { - return ((num << shift) & 268435455) | (num >>> (28 - shift)); - }; - var pc2table = [ - 14, 11, 17, 4, 27, 23, 25, 0, 13, 22, 7, 18, 5, 9, 16, 24, 2, 20, 12, 21, 1, 8, 15, 26, 15, 4, 25, 19, 9, 1, 26, - 16, 5, 11, 23, 8, 12, 7, 17, 0, 22, 3, 10, 14, 6, 20, 27, 24, - ]; - exports.pc2 = function (inL, inR, out, off) { - for (var outL = 0, outR = 0, len = pc2table.length >>> 1, i = 0; i < len; i++) - (outL <<= 1), (outL |= (inL >>> pc2table[i]) & 1); - for (var i = len; i < pc2table.length; i++) (outR <<= 1), (outR |= (inR >>> pc2table[i]) & 1); - (out[off + 0] = outL >>> 0), (out[off + 1] = outR >>> 0); - }; - exports.expand = function (r, out, off) { - var outL = 0, - outR = 0; - outL = ((r & 1) << 5) | (r >>> 27); - for (var i = 23; i >= 15; i -= 4) (outL <<= 6), (outL |= (r >>> i) & 63); - for (var i = 11; i >= 3; i -= 4) (outR |= (r >>> i) & 63), (outR <<= 6); - (outR |= ((r & 31) << 1) | (r >>> 31)), (out[off + 0] = outL >>> 0), (out[off + 1] = outR >>> 0); - }; - var sTable = [ - 14, 0, 4, 15, 13, 7, 1, 4, 2, 14, 15, 2, 11, 13, 8, 1, 3, 10, 10, 6, 6, 12, 12, 11, 5, 9, 9, 5, 0, 3, 7, 8, 4, 15, - 1, 12, 14, 8, 8, 2, 13, 4, 6, 9, 2, 1, 11, 7, 15, 5, 12, 11, 9, 3, 7, 14, 3, 10, 10, 0, 5, 6, 0, 13, 15, 3, 1, 13, - 8, 4, 14, 7, 6, 15, 11, 2, 3, 8, 4, 14, 9, 12, 7, 0, 2, 1, 13, 10, 12, 6, 0, 9, 5, 11, 10, 5, 0, 13, 14, 8, 7, 10, - 11, 1, 10, 3, 4, 15, 13, 4, 1, 2, 5, 11, 8, 6, 12, 7, 6, 12, 9, 0, 3, 5, 2, 14, 15, 9, 10, 13, 0, 7, 9, 0, 14, 9, - 6, 3, 3, 4, 15, 6, 5, 10, 1, 2, 13, 8, 12, 5, 7, 14, 11, 12, 4, 11, 2, 15, 8, 1, 13, 1, 6, 10, 4, 13, 9, 0, 8, 6, - 15, 9, 3, 8, 0, 7, 11, 4, 1, 15, 2, 14, 12, 3, 5, 11, 10, 5, 14, 2, 7, 12, 7, 13, 13, 8, 14, 11, 3, 5, 0, 6, 6, - 15, 9, 0, 10, 3, 1, 4, 2, 7, 8, 2, 5, 12, 11, 1, 12, 10, 4, 14, 15, 9, 10, 3, 6, 15, 9, 0, 0, 6, 12, 10, 11, 1, 7, - 13, 13, 8, 15, 9, 1, 4, 3, 5, 14, 11, 5, 12, 2, 7, 8, 2, 4, 14, 2, 14, 12, 11, 4, 2, 1, 12, 7, 4, 10, 7, 11, 13, - 6, 1, 8, 5, 5, 0, 3, 15, 15, 10, 13, 3, 0, 9, 14, 8, 9, 6, 4, 11, 2, 8, 1, 12, 11, 7, 10, 1, 13, 14, 7, 2, 8, 13, - 15, 6, 9, 15, 12, 0, 5, 9, 6, 10, 3, 4, 0, 5, 14, 3, 12, 10, 1, 15, 10, 4, 15, 2, 9, 7, 2, 12, 6, 9, 8, 5, 0, 6, - 13, 1, 3, 13, 4, 14, 14, 0, 7, 11, 5, 3, 11, 8, 9, 4, 14, 3, 15, 2, 5, 12, 2, 9, 8, 5, 12, 15, 3, 10, 7, 11, 0, - 14, 4, 1, 10, 7, 1, 6, 13, 0, 11, 8, 6, 13, 4, 13, 11, 0, 2, 11, 14, 7, 15, 4, 0, 9, 8, 1, 13, 10, 3, 14, 12, 3, - 9, 5, 7, 12, 5, 2, 10, 15, 6, 8, 1, 6, 1, 6, 4, 11, 11, 13, 13, 8, 12, 1, 3, 4, 7, 10, 14, 7, 10, 9, 15, 5, 6, 0, - 8, 15, 0, 14, 5, 2, 9, 3, 2, 12, 13, 1, 2, 15, 8, 13, 4, 8, 6, 10, 15, 3, 11, 7, 1, 4, 10, 12, 9, 5, 3, 6, 14, 11, - 5, 0, 0, 14, 12, 9, 7, 2, 7, 2, 11, 1, 4, 14, 1, 7, 9, 4, 12, 10, 14, 8, 2, 13, 0, 15, 6, 12, 10, 9, 13, 0, 15, 3, - 3, 5, 5, 6, 8, 11, - ]; - exports.substitute = function (inL, inR) { - for (var out = 0, i = 0; i < 4; i++) { - var b = (inL >>> (18 - i * 6)) & 63, - sb = sTable[i * 64 + b]; - (out <<= 4), (out |= sb); - } - for (var i = 0; i < 4; i++) { - var b = (inR >>> (18 - i * 6)) & 63, - sb = sTable[4 * 64 + i * 64 + b]; - (out <<= 4), (out |= sb); - } - return out >>> 0; - }; - var permuteTable = [ - 16, 25, 12, 11, 3, 20, 4, 15, 31, 17, 9, 6, 27, 14, 1, 22, 30, 24, 8, 18, 0, 5, 29, 23, 13, 19, 2, 26, 10, 21, 28, - 7, - ]; - exports.permute = function (num) { - for (var out = 0, i = 0; i < permuteTable.length; i++) (out <<= 1), (out |= (num >>> permuteTable[i]) & 1); - return out >>> 0; - }; - exports.padSplit = function (num, size, group) { - for (var str = num.toString(2); str.length < size; ) str = "0" + str; - for (var out = [], i = 0; i < size; i += group) out.push(str.slice(i, i + group)); - return out.join(" "); - }; - }, -}); - -// node_modules/minimalistic-assert/index.js -var require_minimalistic_assert = __commonJS({ - "node_modules/minimalistic-assert/index.js"(exports, module) { - module.exports = assert; - function assert(val, msg) { - if (!val) throw new Error(msg || "Assertion failed"); - } - assert.equal = function (l, r, msg) { - if (l != r) throw new Error(msg || "Assertion failed: " + l + " != " + r); - }; - }, -}); - -// node_modules/des.js/lib/des/cipher.js -var require_cipher = __commonJS({ - "node_modules/des.js/lib/des/cipher.js"(exports, module) { - "use strict"; - var assert = require_minimalistic_assert(); - function Cipher(options) { - (this.options = options), - (this.type = this.options.type), - (this.blockSize = 8), - this._init(), - (this.buffer = new Array(this.blockSize)), - (this.bufferOff = 0); - } - Cipher.prototype = {}; - module.exports = Cipher; - Cipher.prototype._init = function () {}; - Cipher.prototype.update = function (data) { - return data.length === 0 ? [] : this.type === "decrypt" ? this._updateDecrypt(data) : this._updateEncrypt(data); - }; - Cipher.prototype._buffer = function (data, off) { - for (var min = Math.min(this.buffer.length - this.bufferOff, data.length - off), i = 0; i < min; i++) - this.buffer[this.bufferOff + i] = data[off + i]; - return (this.bufferOff += min), min; - }; - Cipher.prototype._flushBuffer = function (out, off) { - return this._update(this.buffer, 0, out, off), (this.bufferOff = 0), this.blockSize; - }; - Cipher.prototype._updateEncrypt = function (data) { - var inputOff = 0, - outputOff = 0, - count = ((this.bufferOff + data.length) / this.blockSize) | 0, - out = new Array(count * this.blockSize); - this.bufferOff !== 0 && - ((inputOff += this._buffer(data, inputOff)), - this.bufferOff === this.buffer.length && (outputOff += this._flushBuffer(out, outputOff))); - for ( - var max = data.length - ((data.length - inputOff) % this.blockSize); - inputOff < max; - inputOff += this.blockSize - ) - this._update(data, inputOff, out, outputOff), (outputOff += this.blockSize); - for (; inputOff < data.length; inputOff++, this.bufferOff++) this.buffer[this.bufferOff] = data[inputOff]; - return out; - }; - Cipher.prototype._updateDecrypt = function (data) { - for ( - var inputOff = 0, - outputOff = 0, - count = Math.ceil((this.bufferOff + data.length) / this.blockSize) - 1, - out = new Array(count * this.blockSize); - count > 0; - count-- - ) - (inputOff += this._buffer(data, inputOff)), (outputOff += this._flushBuffer(out, outputOff)); - return (inputOff += this._buffer(data, inputOff)), out; - }; - Cipher.prototype.final = function (buffer) { - var first; - buffer && (first = this.update(buffer)); - var last; - return ( - this.type === "encrypt" ? (last = this._finalEncrypt()) : (last = this._finalDecrypt()), - first ? first.concat(last) : last - ); - }; - Cipher.prototype._pad = function (buffer, off) { - if (off === 0) return !1; - for (; off < buffer.length; ) buffer[off++] = 0; - return !0; - }; - Cipher.prototype._finalEncrypt = function () { - if (!this._pad(this.buffer, this.bufferOff)) return []; - var out = new Array(this.blockSize); - return this._update(this.buffer, 0, out, 0), out; - }; - Cipher.prototype._unpad = function (buffer) { - return buffer; - }; - Cipher.prototype._finalDecrypt = function () { - assert.equal(this.bufferOff, this.blockSize, "Not enough data to decrypt"); - var out = new Array(this.blockSize); - return this._flushBuffer(out, 0), this._unpad(out); - }; - }, -}); - -// node_modules/des.js/lib/des/des.js -var require_des = __commonJS({ - "node_modules/des.js/lib/des/des.js"(exports, module) { - "use strict"; - var assert = require_minimalistic_assert(), - inherits = require_inherits_browser(), - utils = require_utils(), - Cipher = require_cipher(); - function DESState() { - (this.tmp = new Array(2)), (this.keys = null); - } - function DES(options) { - Cipher.$call(this, options); - var state = new DESState(); - (this._desState = state), this.deriveKeys(state, options.key); - } - inherits(DES, Cipher); - module.exports = DES; - DES.create = function (options) { - return new DES(options); - }; - var shiftTable = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]; - DES.prototype.deriveKeys = function (state, key) { - (state.keys = new Array(16 * 2)), assert.equal(key.length, this.blockSize, "Invalid key length"); - var kL = utils.readUInt32BE(key, 0), - kR = utils.readUInt32BE(key, 4); - utils.pc1(kL, kR, state.tmp, 0), (kL = state.tmp[0]), (kR = state.tmp[1]); - for (var i = 0; i < state.keys.length; i += 2) { - var shift = shiftTable[i >>> 1]; - (kL = utils.r28shl(kL, shift)), (kR = utils.r28shl(kR, shift)), utils.pc2(kL, kR, state.keys, i); - } - }; - DES.prototype._update = function (inp, inOff, out, outOff) { - var state = this._desState, - l = utils.readUInt32BE(inp, inOff), - r = utils.readUInt32BE(inp, inOff + 4); - utils.ip(l, r, state.tmp, 0), - (l = state.tmp[0]), - (r = state.tmp[1]), - this.type === "encrypt" ? this._encrypt(state, l, r, state.tmp, 0) : this._decrypt(state, l, r, state.tmp, 0), - (l = state.tmp[0]), - (r = state.tmp[1]), - utils.writeUInt32BE(out, l, outOff), - utils.writeUInt32BE(out, r, outOff + 4); - }; - DES.prototype._pad = function (buffer, off) { - for (var value = buffer.length - off, i = off; i < buffer.length; i++) buffer[i] = value; - return !0; - }; - DES.prototype._unpad = function (buffer) { - for (var pad = buffer[buffer.length - 1], i = buffer.length - pad; i < buffer.length; i++) - assert.equal(buffer[i], pad); - return buffer.slice(0, buffer.length - pad); - }; - DES.prototype._encrypt = function (state, lStart, rStart, out, off) { - for (var l = lStart, r = rStart, i = 0; i < state.keys.length; i += 2) { - var keyL = state.keys[i], - keyR = state.keys[i + 1]; - utils.expand(r, state.tmp, 0), (keyL ^= state.tmp[0]), (keyR ^= state.tmp[1]); - var s = utils.substitute(keyL, keyR), - f = utils.permute(s), - t = r; - (r = (l ^ f) >>> 0), (l = t); - } - utils.rip(r, l, out, off); - }; - DES.prototype._decrypt = function (state, lStart, rStart, out, off) { - for (var l = rStart, r = lStart, i = state.keys.length - 2; i >= 0; i -= 2) { - var keyL = state.keys[i], - keyR = state.keys[i + 1]; - utils.expand(l, state.tmp, 0), (keyL ^= state.tmp[0]), (keyR ^= state.tmp[1]); - var s = utils.substitute(keyL, keyR), - f = utils.permute(s), - t = l; - (l = (r ^ f) >>> 0), (r = t); - } - utils.rip(l, r, out, off); - }; - }, -}); - -// node_modules/des.js/lib/des/cbc.js -var require_cbc = __commonJS({ - "node_modules/des.js/lib/des/cbc.js"(exports) { - "use strict"; - var assert = require_minimalistic_assert(), - inherits = require_inherits_browser(), - proto = {}; - function CBCState(iv) { - assert.equal(iv.length, 8, "Invalid IV length"), (this.iv = new Array(8)); - for (var i = 0; i < this.iv.length; i++) this.iv[i] = iv[i]; - } - function instantiate(Base) { - function CBC(options) { - Base.$call(this, options), this._cbcInit(); - } - inherits(CBC, Base); - for (var keys = Object.keys(proto), i = 0; i < keys.length; i++) { - var key = keys[i]; - CBC.prototype[key] = proto[key]; - } - return ( - (CBC.create = function (options) { - return new CBC(options); - }), - CBC - ); - } - exports.instantiate = instantiate; - proto._cbcInit = function () { - var state = new CBCState(this.options.iv); - this._cbcState = state; - }; - proto._update = function (inp, inOff, out, outOff) { - var state = this._cbcState, - superProto = this.constructor.super_.prototype, - iv = state.iv; - if (this.type === "encrypt") { - for (var i = 0; i < this.blockSize; i++) iv[i] ^= inp[inOff + i]; - superProto._update.$call(this, iv, 0, out, outOff); - for (var i = 0; i < this.blockSize; i++) iv[i] = out[outOff + i]; - } else { - superProto._update.$call(this, inp, inOff, out, outOff); - for (var i = 0; i < this.blockSize; i++) out[outOff + i] ^= iv[i]; - for (var i = 0; i < this.blockSize; i++) iv[i] = inp[inOff + i]; - } - }; - }, -}); - -// node_modules/des.js/lib/des/ede.js -var require_ede = __commonJS({ - "node_modules/des.js/lib/des/ede.js"(exports, module) { - "use strict"; - var assert = require_minimalistic_assert(), - inherits = require_inherits_browser(), - Cipher = require_cipher(), - DES = require_des(); - function EDEState(type, key) { - assert.equal(key.length, 24, "Invalid key length"); - var k1 = key.slice(0, 8), - k2 = key.slice(8, 16), - k3 = key.slice(16, 24); - type === "encrypt" - ? (this.ciphers = [ - DES.create({ type: "encrypt", key: k1 }), - DES.create({ type: "decrypt", key: k2 }), - DES.create({ type: "encrypt", key: k3 }), - ]) - : (this.ciphers = [ - DES.create({ type: "decrypt", key: k3 }), - DES.create({ type: "encrypt", key: k2 }), - DES.create({ type: "decrypt", key: k1 }), - ]); - } - function EDE(options) { - Cipher.$call(this, options); - var state = new EDEState(this.type, this.options.key); - this._edeState = state; - } - inherits(EDE, Cipher); - module.exports = EDE; - EDE.create = function (options) { - return new EDE(options); - }; - EDE.prototype._update = function (inp, inOff, out, outOff) { - var state = this._edeState; - state.ciphers[0]._update(inp, inOff, out, outOff), - state.ciphers[1]._update(out, outOff, out, outOff), - state.ciphers[2]._update(out, outOff, out, outOff); - }; - EDE.prototype._pad = DES.prototype._pad; - EDE.prototype._unpad = DES.prototype._unpad; - }, -}); - -// node_modules/des.js/lib/des.js -var require_des2 = __commonJS({ - "node_modules/des.js/lib/des.js"(exports) { - "use strict"; - exports.utils = require_utils(); - exports.Cipher = require_cipher(); - exports.DES = require_des(); - exports.CBC = require_cbc(); - exports.EDE = require_ede(); - }, -}); - -// node_modules/browserify-des/index.js -var require_browserify_des = __commonJS({ - "node_modules/browserify-des/index.js"(exports, module) { - var CipherBase = require_cipher_base(), - des = require_des2(), - inherits = require_inherits_browser(), - Buffer2 = require_safe_buffer().Buffer, - modes = { - "des-ede3-cbc": des.CBC.instantiate(des.EDE), - "des-ede3": des.EDE, - "des-ede-cbc": des.CBC.instantiate(des.EDE), - "des-ede": des.EDE, - "des-cbc": des.CBC.instantiate(des.DES), - "des-ecb": des.DES, - }; - modes.des = modes["des-cbc"]; - modes.des3 = modes["des-ede3-cbc"]; - module.exports = DES; - inherits(DES, CipherBase); - function DES(opts) { - CipherBase.$call(this); - var modeName = opts.mode.toLowerCase(), - mode = modes[modeName], - type; - opts.decrypt ? (type = "decrypt") : (type = "encrypt"); - var key = opts.key; - Buffer2.isBuffer(key) || (key = Buffer2.from(key)), - (modeName === "des-ede" || modeName === "des-ede-cbc") && (key = Buffer2.concat([key, key.slice(0, 8)])); - var iv = opts.iv; - Buffer2.isBuffer(iv) || (iv = Buffer2.from(iv)), - (this._des = mode.create({ - key, - iv, - type, - })); - } - DES.prototype._update = function (data) { - return Buffer2.from(this._des.update(data)); - }; - DES.prototype._final = function () { - return Buffer2.from(this._des.final()); - }; - }, -}); - -// node_modules/browserify-aes/modes/ecb.js -var require_ecb = __commonJS({ - "node_modules/browserify-aes/modes/ecb.js"(exports) { - exports.encrypt = function (self2, block) { - return self2._cipher.encryptBlock(block); - }; - exports.decrypt = function (self2, block) { - return self2._cipher.decryptBlock(block); - }; - }, -}); - -// node_modules/buffer-xor/index.js -var require_buffer_xor = __commonJS({ - "node_modules/buffer-xor/index.js"(exports, module) { - module.exports = function (a, b) { - for (var length = Math.min(a.length, b.length), buffer = new Buffer(length), i = 0; i < length; ++i) - buffer[i] = a[i] ^ b[i]; - return buffer; - }; - }, -}); - -// node_modules/browserify-aes/modes/cbc.js -var require_cbc2 = __commonJS({ - "node_modules/browserify-aes/modes/cbc.js"(exports) { - var xor = require_buffer_xor(); - exports.encrypt = function (self2, block) { - var data = xor(block, self2._prev); - return (self2._prev = self2._cipher.encryptBlock(data)), self2._prev; - }; - exports.decrypt = function (self2, block) { - var pad = self2._prev; - self2._prev = block; - var out = self2._cipher.decryptBlock(block); - return xor(out, pad); - }; - }, -}); - -// node_modules/browserify-aes/modes/cfb.js -var require_cfb = __commonJS({ - "node_modules/browserify-aes/modes/cfb.js"(exports) { - var Buffer2 = require_safe_buffer().Buffer, - xor = require_buffer_xor(); - function encryptStart(self2, data, decrypt) { - var len = data.length, - out = xor(data, self2._cache); - return ( - (self2._cache = self2._cache.slice(len)), - (self2._prev = Buffer2.concat([self2._prev, decrypt ? data : out])), - out - ); - } - exports.encrypt = function (self2, data, decrypt) { - for (var out = Buffer2.allocUnsafe(0), len; data.length; ) - if ( - (self2._cache.length === 0 && - ((self2._cache = self2._cipher.encryptBlock(self2._prev)), (self2._prev = Buffer2.allocUnsafe(0))), - self2._cache.length <= data.length) - ) - (len = self2._cache.length), - (out = Buffer2.concat([out, encryptStart(self2, data.slice(0, len), decrypt)])), - (data = data.slice(len)); - else { - out = Buffer2.concat([out, encryptStart(self2, data, decrypt)]); - break; - } - return out; - }; - }, -}); - -// node_modules/browserify-aes/modes/cfb8.js -var require_cfb8 = __commonJS({ - "node_modules/browserify-aes/modes/cfb8.js"(exports) { - var Buffer2 = require_safe_buffer().Buffer; - function encryptByte(self2, byteParam, decrypt) { - var pad = self2._cipher.encryptBlock(self2._prev), - out = pad[0] ^ byteParam; - return (self2._prev = Buffer2.concat([self2._prev.slice(1), Buffer2.from([decrypt ? byteParam : out])])), out; - } - exports.encrypt = function (self2, chunk, decrypt) { - for (var len = chunk.length, out = Buffer2.allocUnsafe(len), i = -1; ++i < len; ) - out[i] = encryptByte(self2, chunk[i], decrypt); - return out; - }; - }, -}); - -// node_modules/browserify-aes/modes/cfb1.js -var require_cfb1 = __commonJS({ - "node_modules/browserify-aes/modes/cfb1.js"(exports) { - var Buffer2 = require_safe_buffer().Buffer; - function encryptByte(self2, byteParam, decrypt) { - for (var pad, i = -1, len = 8, out = 0, bit, value; ++i < len; ) - (pad = self2._cipher.encryptBlock(self2._prev)), - (bit = byteParam & (1 << (7 - i)) ? 128 : 0), - (value = pad[0] ^ bit), - (out += (value & 128) >> i % 8), - (self2._prev = shiftIn(self2._prev, decrypt ? bit : value)); - return out; - } - function shiftIn(buffer, value) { - var len = buffer.length, - i = -1, - out = Buffer2.allocUnsafe(buffer.length); - for (buffer = Buffer2.concat([buffer, Buffer2.from([value])]); ++i < len; ) - out[i] = (buffer[i] << 1) | (buffer[i + 1] >> 7); - return out; - } - exports.encrypt = function (self2, chunk, decrypt) { - for (var len = chunk.length, out = Buffer2.allocUnsafe(len), i = -1; ++i < len; ) - out[i] = encryptByte(self2, chunk[i], decrypt); - return out; - }; - }, -}); - -// node_modules/browserify-aes/modes/ofb.js -var require_ofb = __commonJS({ - "node_modules/browserify-aes/modes/ofb.js"(exports) { - var xor = require_buffer_xor(); - function getBlock(self2) { - return (self2._prev = self2._cipher.encryptBlock(self2._prev)), self2._prev; - } - exports.encrypt = function (self2, chunk) { - for (; self2._cache.length < chunk.length; ) self2._cache = Buffer.concat([self2._cache, getBlock(self2)]); - var pad = self2._cache.slice(0, chunk.length); - return (self2._cache = self2._cache.slice(chunk.length)), xor(chunk, pad); - }; - }, -}); - -// node_modules/browserify-aes/incr32.js -var require_incr32 = __commonJS({ - "node_modules/browserify-aes/incr32.js"(exports, module) { - function incr32(iv) { - for (var len = iv.length, item; len--; ) - if (((item = iv.readUInt8(len)), item === 255)) iv.writeUInt8(0, len); - else { - item++, iv.writeUInt8(item, len); - break; - } - } - module.exports = incr32; - }, -}); - -// node_modules/browserify-aes/modes/ctr.js -var require_ctr = __commonJS({ - "node_modules/browserify-aes/modes/ctr.js"(exports) { - var xor = require_buffer_xor(), - Buffer2 = require_safe_buffer().Buffer, - incr32 = require_incr32(); - function getBlock(self2) { - var out = self2._cipher.encryptBlockRaw(self2._prev); - return incr32(self2._prev), out; - } - var blockSize = 16; - exports.encrypt = function (self2, chunk) { - var chunkNum = Math.ceil(chunk.length / blockSize), - start = self2._cache.length; - self2._cache = Buffer2.concat([self2._cache, Buffer2.allocUnsafe(chunkNum * blockSize)]); - for (var i = 0; i < chunkNum; i++) { - var out = getBlock(self2), - offset = start + i * blockSize; - self2._cache.writeUInt32BE(out[0], offset + 0), - self2._cache.writeUInt32BE(out[1], offset + 4), - self2._cache.writeUInt32BE(out[2], offset + 8), - self2._cache.writeUInt32BE(out[3], offset + 12); - } - var pad = self2._cache.slice(0, chunk.length); - return (self2._cache = self2._cache.slice(chunk.length)), xor(chunk, pad); - }; - }, -}); - -// node_modules/browserify-aes/modes/list.json -var require_list = __commonJS({ - "node_modules/browserify-aes/modes/list.json"(exports, module) { - module.exports = { - "aes-128-ecb": { - cipher: "AES", - key: 128, - iv: 0, - mode: "ECB", - type: "block", - }, - "aes-192-ecb": { - cipher: "AES", - key: 192, - iv: 0, - mode: "ECB", - type: "block", - }, - "aes-256-ecb": { - cipher: "AES", - key: 256, - iv: 0, - mode: "ECB", - type: "block", - }, - "aes-128-cbc": { - cipher: "AES", - key: 128, - iv: 16, - mode: "CBC", - type: "block", - }, - "aes-192-cbc": { - cipher: "AES", - key: 192, - iv: 16, - mode: "CBC", - type: "block", - }, - "aes-256-cbc": { - cipher: "AES", - key: 256, - iv: 16, - mode: "CBC", - type: "block", - }, - aes128: { - cipher: "AES", - key: 128, - iv: 16, - mode: "CBC", - type: "block", - }, - aes192: { - cipher: "AES", - key: 192, - iv: 16, - mode: "CBC", - type: "block", - }, - aes256: { - cipher: "AES", - key: 256, - iv: 16, - mode: "CBC", - type: "block", - }, - "aes-128-cfb": { - cipher: "AES", - key: 128, - iv: 16, - mode: "CFB", - type: "stream", - }, - "aes-192-cfb": { - cipher: "AES", - key: 192, - iv: 16, - mode: "CFB", - type: "stream", - }, - "aes-256-cfb": { - cipher: "AES", - key: 256, - iv: 16, - mode: "CFB", - type: "stream", - }, - "aes-128-cfb8": { - cipher: "AES", - key: 128, - iv: 16, - mode: "CFB8", - type: "stream", - }, - "aes-192-cfb8": { - cipher: "AES", - key: 192, - iv: 16, - mode: "CFB8", - type: "stream", - }, - "aes-256-cfb8": { - cipher: "AES", - key: 256, - iv: 16, - mode: "CFB8", - type: "stream", - }, - "aes-128-cfb1": { - cipher: "AES", - key: 128, - iv: 16, - mode: "CFB1", - type: "stream", - }, - "aes-192-cfb1": { - cipher: "AES", - key: 192, - iv: 16, - mode: "CFB1", - type: "stream", - }, - "aes-256-cfb1": { - cipher: "AES", - key: 256, - iv: 16, - mode: "CFB1", - type: "stream", - }, - "aes-128-ofb": { - cipher: "AES", - key: 128, - iv: 16, - mode: "OFB", - type: "stream", - }, - "aes-192-ofb": { - cipher: "AES", - key: 192, - iv: 16, - mode: "OFB", - type: "stream", - }, - "aes-256-ofb": { - cipher: "AES", - key: 256, - iv: 16, - mode: "OFB", - type: "stream", - }, - "aes-128-ctr": { - cipher: "AES", - key: 128, - iv: 16, - mode: "CTR", - type: "stream", - }, - "aes-192-ctr": { - cipher: "AES", - key: 192, - iv: 16, - mode: "CTR", - type: "stream", - }, - "aes-256-ctr": { - cipher: "AES", - key: 256, - iv: 16, - mode: "CTR", - type: "stream", - }, - "aes-128-gcm": { - cipher: "AES", - key: 128, - iv: 12, - mode: "GCM", - type: "auth", - }, - "aes-192-gcm": { - cipher: "AES", - key: 192, - iv: 12, - mode: "GCM", - type: "auth", - }, - "aes-256-gcm": { - cipher: "AES", - key: 256, - iv: 12, - mode: "GCM", - type: "auth", - }, - }; - }, -}); - -// node_modules/browserify-aes/modes/index.js -var require_modes = __commonJS({ - "node_modules/browserify-aes/modes/index.js"(exports, module) { - var modeModules = { - ECB: require_ecb(), - CBC: require_cbc2(), - CFB: require_cfb(), - CFB8: require_cfb8(), - CFB1: require_cfb1(), - OFB: require_ofb(), - CTR: require_ctr(), - GCM: require_ctr(), - }, - modes = require_list(); - for (key in modes) modes[key].module = modeModules[modes[key].mode]; - var key; - module.exports = modes; - }, -}); - -// node_modules/browserify-aes/aes.js -var require_aes = __commonJS({ - "node_modules/browserify-aes/aes.js"(exports, module) { - var Buffer2 = require_safe_buffer().Buffer; - function asUInt32Array(buf) { - if (buf instanceof KeyObject) { - buf = buf.export(); - } else if (buf instanceof CryptoKey) { - buf = KeyObject.from(buf).export(); - } - Buffer2.isBuffer(buf) || (buf = Buffer2.from(buf)); - for (var len = (buf.length / 4) | 0, out = new Array(len), i = 0; i < len; i++) out[i] = buf.readUInt32BE(i * 4); - return out; - } - function scrubVec(v) { - for (var i = 0; i < v.length; v++) v[i] = 0; - } - function cryptBlock(M, keySchedule, SUB_MIX, SBOX, nRounds) { - for ( - var SUB_MIX0 = SUB_MIX[0], - SUB_MIX1 = SUB_MIX[1], - SUB_MIX2 = SUB_MIX[2], - SUB_MIX3 = SUB_MIX[3], - s0 = M[0] ^ keySchedule[0], - s1 = M[1] ^ keySchedule[1], - s2 = M[2] ^ keySchedule[2], - s3 = M[3] ^ keySchedule[3], - t0, - t1, - t2, - t3, - ksRow = 4, - round = 1; - round < nRounds; - round++ - ) - (t0 = - SUB_MIX0[s0 >>> 24] ^ - SUB_MIX1[(s1 >>> 16) & 255] ^ - SUB_MIX2[(s2 >>> 8) & 255] ^ - SUB_MIX3[s3 & 255] ^ - keySchedule[ksRow++]), - (t1 = - SUB_MIX0[s1 >>> 24] ^ - SUB_MIX1[(s2 >>> 16) & 255] ^ - SUB_MIX2[(s3 >>> 8) & 255] ^ - SUB_MIX3[s0 & 255] ^ - keySchedule[ksRow++]), - (t2 = - SUB_MIX0[s2 >>> 24] ^ - SUB_MIX1[(s3 >>> 16) & 255] ^ - SUB_MIX2[(s0 >>> 8) & 255] ^ - SUB_MIX3[s1 & 255] ^ - keySchedule[ksRow++]), - (t3 = - SUB_MIX0[s3 >>> 24] ^ - SUB_MIX1[(s0 >>> 16) & 255] ^ - SUB_MIX2[(s1 >>> 8) & 255] ^ - SUB_MIX3[s2 & 255] ^ - keySchedule[ksRow++]), - (s0 = t0), - (s1 = t1), - (s2 = t2), - (s3 = t3); - return ( - (t0 = - ((SBOX[s0 >>> 24] << 24) | (SBOX[(s1 >>> 16) & 255] << 16) | (SBOX[(s2 >>> 8) & 255] << 8) | SBOX[s3 & 255]) ^ - keySchedule[ksRow++]), - (t1 = - ((SBOX[s1 >>> 24] << 24) | (SBOX[(s2 >>> 16) & 255] << 16) | (SBOX[(s3 >>> 8) & 255] << 8) | SBOX[s0 & 255]) ^ - keySchedule[ksRow++]), - (t2 = - ((SBOX[s2 >>> 24] << 24) | (SBOX[(s3 >>> 16) & 255] << 16) | (SBOX[(s0 >>> 8) & 255] << 8) | SBOX[s1 & 255]) ^ - keySchedule[ksRow++]), - (t3 = - ((SBOX[s3 >>> 24] << 24) | (SBOX[(s0 >>> 16) & 255] << 16) | (SBOX[(s1 >>> 8) & 255] << 8) | SBOX[s2 & 255]) ^ - keySchedule[ksRow++]), - (t0 = t0 >>> 0), - (t1 = t1 >>> 0), - (t2 = t2 >>> 0), - (t3 = t3 >>> 0), - [t0, t1, t2, t3] - ); - } - var RCON = [0, 1, 2, 4, 8, 16, 32, 64, 128, 27, 54], - G = (function () { - for (var d = new Array(256), j = 0; j < 256; j++) j < 128 ? (d[j] = j << 1) : (d[j] = (j << 1) ^ 283); - for ( - var SBOX = [], - INV_SBOX = [], - SUB_MIX = [[], [], [], []], - INV_SUB_MIX = [[], [], [], []], - x = 0, - xi = 0, - i = 0; - i < 256; - ++i - ) { - var sx = xi ^ (xi << 1) ^ (xi << 2) ^ (xi << 3) ^ (xi << 4); - (sx = (sx >>> 8) ^ (sx & 255) ^ 99), (SBOX[x] = sx), (INV_SBOX[sx] = x); - var x2 = d[x], - x4 = d[x2], - x8 = d[x4], - t = (d[sx] * 257) ^ (sx * 16843008); - (SUB_MIX[0][x] = (t << 24) | (t >>> 8)), - (SUB_MIX[1][x] = (t << 16) | (t >>> 16)), - (SUB_MIX[2][x] = (t << 8) | (t >>> 24)), - (SUB_MIX[3][x] = t), - (t = (x8 * 16843009) ^ (x4 * 65537) ^ (x2 * 257) ^ (x * 16843008)), - (INV_SUB_MIX[0][sx] = (t << 24) | (t >>> 8)), - (INV_SUB_MIX[1][sx] = (t << 16) | (t >>> 16)), - (INV_SUB_MIX[2][sx] = (t << 8) | (t >>> 24)), - (INV_SUB_MIX[3][sx] = t), - x === 0 ? (x = xi = 1) : ((x = x2 ^ d[d[d[x8 ^ x2]]]), (xi ^= d[d[xi]])); - } - return { - SBOX, - INV_SBOX, - SUB_MIX, - INV_SUB_MIX, - }; - })(); - function AES(key) { - (this._key = asUInt32Array(key)), this._reset(); - } - AES.prototype = {}; - AES.blockSize = 4 * 4; - AES.keySize = 256 / 8; - AES.prototype.blockSize = AES.blockSize; - AES.prototype.keySize = AES.keySize; - AES.prototype._reset = function () { - for ( - var keyWords = this._key, - keySize = keyWords.length, - nRounds = keySize + 6, - ksRows = (nRounds + 1) * 4, - keySchedule = [], - k = 0; - k < keySize; - k++ - ) - keySchedule[k] = keyWords[k]; - for (k = keySize; k < ksRows; k++) { - var t = keySchedule[k - 1]; - k % keySize === 0 - ? ((t = (t << 8) | (t >>> 24)), - (t = - (G.SBOX[t >>> 24] << 24) | - (G.SBOX[(t >>> 16) & 255] << 16) | - (G.SBOX[(t >>> 8) & 255] << 8) | - G.SBOX[t & 255]), - (t ^= RCON[(k / keySize) | 0] << 24)) - : keySize > 6 && - k % keySize === 4 && - (t = - (G.SBOX[t >>> 24] << 24) | - (G.SBOX[(t >>> 16) & 255] << 16) | - (G.SBOX[(t >>> 8) & 255] << 8) | - G.SBOX[t & 255]), - (keySchedule[k] = keySchedule[k - keySize] ^ t); - } - for (var invKeySchedule = [], ik = 0; ik < ksRows; ik++) { - var ksR = ksRows - ik, - tt = keySchedule[ksR - (ik % 4 ? 0 : 4)]; - ik < 4 || ksR <= 4 - ? (invKeySchedule[ik] = tt) - : (invKeySchedule[ik] = - G.INV_SUB_MIX[0][G.SBOX[tt >>> 24]] ^ - G.INV_SUB_MIX[1][G.SBOX[(tt >>> 16) & 255]] ^ - G.INV_SUB_MIX[2][G.SBOX[(tt >>> 8) & 255]] ^ - G.INV_SUB_MIX[3][G.SBOX[tt & 255]]); - } - (this._nRounds = nRounds), (this._keySchedule = keySchedule), (this._invKeySchedule = invKeySchedule); - }; - AES.prototype.encryptBlockRaw = function (M) { - return (M = asUInt32Array(M)), cryptBlock(M, this._keySchedule, G.SUB_MIX, G.SBOX, this._nRounds); - }; - AES.prototype.encryptBlock = function (M) { - var out = this.encryptBlockRaw(M), - buf = Buffer2.allocUnsafe(16); - return ( - buf.writeUInt32BE(out[0], 0), - buf.writeUInt32BE(out[1], 4), - buf.writeUInt32BE(out[2], 8), - buf.writeUInt32BE(out[3], 12), - buf - ); - }; - AES.prototype.decryptBlock = function (M) { - M = asUInt32Array(M); - var m1 = M[1]; - (M[1] = M[3]), (M[3] = m1); - var out = cryptBlock(M, this._invKeySchedule, G.INV_SUB_MIX, G.INV_SBOX, this._nRounds), - buf = Buffer2.allocUnsafe(16); - return ( - buf.writeUInt32BE(out[0], 0), - buf.writeUInt32BE(out[3], 4), - buf.writeUInt32BE(out[2], 8), - buf.writeUInt32BE(out[1], 12), - buf - ); - }; - AES.prototype.scrub = function () { - scrubVec(this._keySchedule), scrubVec(this._invKeySchedule), scrubVec(this._key); - }; - module.exports.AES = AES; - }, -}); - -// node_modules/browserify-aes/ghash.js -var require_ghash = __commonJS({ - "node_modules/browserify-aes/ghash.js"(exports, module) { - var Buffer2 = require_safe_buffer().Buffer, - ZEROES = Buffer2.alloc(16, 0); - function toArray(buf) { - return [buf.readUInt32BE(0), buf.readUInt32BE(4), buf.readUInt32BE(8), buf.readUInt32BE(12)]; - } - function fromArray(out) { - var buf = Buffer2.allocUnsafe(16); - return ( - buf.writeUInt32BE(out[0] >>> 0, 0), - buf.writeUInt32BE(out[1] >>> 0, 4), - buf.writeUInt32BE(out[2] >>> 0, 8), - buf.writeUInt32BE(out[3] >>> 0, 12), - buf - ); - } - function GHASH(key) { - (this.h = key), (this.state = Buffer2.alloc(16, 0)), (this.cache = Buffer2.allocUnsafe(0)); - } - GHASH.prototype = {}; - GHASH.prototype.ghash = function (block) { - for (var i = -1; ++i < block.length; ) this.state[i] ^= block[i]; - this._multiply(); - }; - GHASH.prototype._multiply = function () { - for (var Vi = toArray(this.h), Zi = [0, 0, 0, 0], j, xi, lsbVi, i = -1; ++i < 128; ) { - for ( - xi = (this.state[~~(i / 8)] & (1 << (7 - (i % 8)))) !== 0, - xi && ((Zi[0] ^= Vi[0]), (Zi[1] ^= Vi[1]), (Zi[2] ^= Vi[2]), (Zi[3] ^= Vi[3])), - lsbVi = (Vi[3] & 1) !== 0, - j = 3; - j > 0; - j-- - ) - Vi[j] = (Vi[j] >>> 1) | ((Vi[j - 1] & 1) << 31); - (Vi[0] = Vi[0] >>> 1), lsbVi && (Vi[0] = Vi[0] ^ (225 << 24)); - } - this.state = fromArray(Zi); - }; - GHASH.prototype.update = function (buf) { - this.cache = Buffer2.concat([this.cache, buf]); - for (var chunk; this.cache.length >= 16; ) - (chunk = this.cache.slice(0, 16)), (this.cache = this.cache.slice(16)), this.ghash(chunk); - }; - GHASH.prototype.final = function (abl, bl) { - return ( - this.cache.length && this.ghash(Buffer2.concat([this.cache, ZEROES], 16)), - this.ghash(fromArray([0, abl, 0, bl])), - this.state - ); - }; - module.exports = GHASH; - }, -}); - -// node_modules/browserify-aes/authCipher.js -var require_authCipher = __commonJS({ - "node_modules/browserify-aes/authCipher.js"(exports, module) { - var aes = require_aes(), - Buffer2 = require_safe_buffer().Buffer, - Transform = require_cipher_base(), - inherits = require_inherits_browser(), - GHASH = require_ghash(), - xor = require_buffer_xor(), - incr32 = require_incr32(); - function xorTest(a, b) { - var out = 0; - a.length !== b.length && out++; - for (var len = Math.min(a.length, b.length), i = 0; i < len; ++i) out += a[i] ^ b[i]; - return out; - } - function calcIv(self2, iv, ck) { - if (iv.length === 12) - return ( - (self2._finID = Buffer2.concat([iv, Buffer2.from([0, 0, 0, 1])])), - Buffer2.concat([iv, Buffer2.from([0, 0, 0, 2])]) - ); - var ghash = new GHASH(ck), - len = iv.length, - toPad = len % 16; - ghash.update(iv), - toPad && ((toPad = 16 - toPad), ghash.update(Buffer2.alloc(toPad, 0))), - ghash.update(Buffer2.alloc(8, 0)); - var ivBits = len * 8, - tail = Buffer2.alloc(8); - tail.writeUIntBE(ivBits, 2, 6), ghash.update(tail), (self2._finID = ghash.state); - var out = Buffer2.from(self2._finID); - return incr32(out), out; - } - function StreamCipher(mode, key, iv, decrypt) { - Transform.$call(this); - var h = Buffer2.alloc(4, 0); - this._cipher = new aes.AES(key); - var ck = this._cipher.encryptBlock(h); - (this._ghash = new GHASH(ck)), - (iv = calcIv(this, iv, ck)), - (this._prev = Buffer2.from(iv)), - (this._cache = Buffer2.allocUnsafe(0)), - (this._secCache = Buffer2.allocUnsafe(0)), - (this._decrypt = decrypt), - (this._alen = 0), - (this._len = 0), - (this._mode = mode), - (this._authTag = null), - (this._called = !1); - } - inherits(StreamCipher, Transform); - StreamCipher.prototype._update = function (chunk) { - if (!this._called && this._alen) { - var rump = 16 - (this._alen % 16); - rump < 16 && ((rump = Buffer2.alloc(rump, 0)), this._ghash.update(rump)); - } - this._called = !0; - var out = this._mode.encrypt(this, chunk); - return this._decrypt ? this._ghash.update(chunk) : this._ghash.update(out), (this._len += chunk.length), out; - }; - StreamCipher.prototype._final = function () { - if (this._decrypt && !this._authTag) throw new Error("Unsupported state or unable to authenticate data"); - var tag = xor(this._ghash.final(this._alen * 8, this._len * 8), this._cipher.encryptBlock(this._finID)); - if (this._decrypt && xorTest(tag, this._authTag)) - throw new Error("Unsupported state or unable to authenticate data"); - (this._authTag = tag), this._cipher.scrub(); - }; - StreamCipher.prototype.getAuthTag = function () { - if (this._decrypt || !Buffer2.isBuffer(this._authTag)) - throw new Error("Attempting to get auth tag in unsupported state"); - return this._authTag; - }; - StreamCipher.prototype.setAuthTag = function (tag) { - if (!this._decrypt) throw new Error("Attempting to set auth tag in unsupported state"); - this._authTag = tag; - }; - StreamCipher.prototype.setAAD = function (buf) { - if (this._called) throw new Error("Attempting to set AAD in unsupported state"); - this._ghash.update(buf), (this._alen += buf.length); - }; - module.exports = StreamCipher; - }, -}); - -// node_modules/browserify-aes/streamCipher.js -var require_streamCipher = __commonJS({ - "node_modules/browserify-aes/streamCipher.js"(exports, module) { - var aes = require_aes(), - Buffer2 = require_safe_buffer().Buffer, - Transform = require_cipher_base(), - inherits = require_inherits_browser(); - function StreamCipher(mode, key, iv, decrypt) { - Transform.$call(this), - (this._cipher = new aes.AES(key)), - (this._prev = Buffer2.from(iv)), - (this._cache = Buffer2.allocUnsafe(0)), - (this._secCache = Buffer2.allocUnsafe(0)), - (this._decrypt = decrypt), - (this._mode = mode); - } - inherits(StreamCipher, Transform); - StreamCipher.prototype._update = function (chunk) { - return this._mode.encrypt(this, chunk, this._decrypt); - }; - StreamCipher.prototype._final = function () { - this._cipher.scrub(); - }; - module.exports = StreamCipher; - }, -}); - -// 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; - function EVP_BytesToKey(password, salt, keyBits, ivLen) { - if ( - (Buffer2.isBuffer(password) || (password = Buffer2.from(password, "binary")), - salt && (Buffer2.isBuffer(salt) || (salt = Buffer2.from(salt, "binary")), salt.length !== 8)) - ) - throw new RangeError("salt should be Buffer with 8 byte length"); - for ( - var keyLen = keyBits / 8, key = Buffer2.alloc(keyLen), iv = Buffer2.alloc(ivLen || 0), tmp = Buffer2.alloc(0); - keyLen > 0 || ivLen > 0; - - ) { - var hash = new Hash("md5"); - hash.update(tmp), hash.update(password), salt && hash.update(salt), (tmp = hash.digest()); - var used = 0; - if (keyLen > 0) { - var keyStart = key.length - keyLen; - (used = Math.min(keyLen, tmp.length)), tmp.copy(key, keyStart, 0, used), (keyLen -= used); - } - if (used < tmp.length && ivLen > 0) { - var ivStart = iv.length - ivLen, - length = Math.min(ivLen, tmp.length - used); - tmp.copy(iv, ivStart, used, used + length), (ivLen -= length); - } - } - return tmp.fill(0), { key, iv }; - } - module.exports = EVP_BytesToKey; - }, -}); - -// node_modules/browserify-aes/encrypter.js -var require_encrypter = __commonJS({ - "node_modules/browserify-aes/encrypter.js"(exports) { - var MODES = require_modes(), - AuthCipher = require_authCipher(), - Buffer2 = require_safe_buffer().Buffer, - StreamCipher = require_streamCipher(), - Transform = require_cipher_base(), - aes = require_aes(), - ebtk = require_evp_bytestokey(), - inherits = require_inherits_browser(); - function Cipher(mode, key, iv) { - Transform.$call(this), - (this._cache = new Splitter()), - (this._cipher = new aes.AES(key)), - (this._prev = Buffer2.from(iv)), - (this._mode = mode), - (this._autopadding = !0); - } - inherits(Cipher, Transform); - Cipher.prototype._update = function (data) { - this._cache.add(data); - for (var chunk, thing, out = []; (chunk = this._cache.get()); ) - (thing = this._mode.encrypt(this, chunk)), out.push(thing); - return Buffer2.concat(out); - }; - var PADDING = Buffer2.alloc(16, 16); - Cipher.prototype._final = function () { - var chunk = this._cache.flush(); - if (this._autopadding) return (chunk = this._mode.encrypt(this, chunk)), this._cipher.scrub(), chunk; - if (!chunk.equals(PADDING)) throw (this._cipher.scrub(), new Error("data not multiple of block length")); - }; - Cipher.prototype.setAutoPadding = function (setTo) { - return (this._autopadding = !!setTo), this; - }; - function Splitter() { - this.cache = Buffer2.allocUnsafe(0); - } - Splitter.prototype = {}; - Splitter.prototype.add = function (data) { - this.cache = Buffer2.concat([this.cache, data]); - }; - Splitter.prototype.get = function () { - if (this.cache.length > 15) { - var out = this.cache.slice(0, 16); - return (this.cache = this.cache.slice(16)), out; - } - return null; - }; - Splitter.prototype.flush = function () { - for (var len = 16 - this.cache.length, padBuff = Buffer2.allocUnsafe(len), i = -1; ++i < len; ) - padBuff.writeUInt8(len, i); - return Buffer2.concat([this.cache, padBuff]); - }; - function createCipheriv(suite, password, iv) { - var config = MODES[suite.toLowerCase()]; - if (!config) throw new TypeError("invalid suite type"); - password = getArrayBufferOrView(password, "password"); - const iv_length = iv?.length || 0; - const required_iv_length = config.iv || 0; - iv = iv === null ? EMPTY_BUFFER : getArrayBufferOrView(iv, "iv"); - - if (password?.length !== config.key / 8) { - var error = new RangeError("Invalid key length"); - error.code = "ERR_CRYPTO_INVALID_KEYLEN"; - throw error; - } - if (config.mode !== "GCM" && iv_length !== required_iv_length) { - var error = new RangeError("Invalid key length"); - error.code = "ERR_CRYPTO_INVALID_KEYLEN"; - throw error; - } - - return config.type === "stream" - ? new StreamCipher(config.module, password, iv) - : config.type === "auth" - ? new AuthCipher(config.module, password, iv) - : new Cipher(config.module, password, iv); - } - function createCipher(suite, password) { - var config = MODES[suite.toLowerCase()]; - if (!config) throw new TypeError("invalid suite type"); - var keys = ebtk(password, !1, config.key, config.iv); - return createCipheriv(suite, keys.key, keys.iv); - } - exports.createCipheriv = createCipheriv; - exports.createCipher = createCipher; - }, -}); - -// node_modules/browserify-aes/decrypter.js -var require_decrypter = __commonJS({ - "node_modules/browserify-aes/decrypter.js"(exports) { - var AuthCipher = require_authCipher(), - Buffer2 = require_safe_buffer().Buffer, - MODES = require_modes(), - StreamCipher = require_streamCipher(), - Transform = require_cipher_base(), - aes = require_aes(), - ebtk = require_evp_bytestokey(), - inherits = require_inherits_browser(); - function Decipher(mode, key, iv) { - Transform.$call(this), - (this._cache = new Splitter()), - (this._last = void 0), - (this._cipher = new aes.AES(key)), - (this._prev = Buffer2.from(iv)), - (this._mode = mode), - (this._autopadding = !0); - } - inherits(Decipher, Transform); - Decipher.prototype._update = function (data) { - this._cache.add(data); - for (var chunk, thing, out = []; (chunk = this._cache.get(this._autopadding)); ) - (thing = this._mode.decrypt(this, chunk)), out.push(thing); - return Buffer2.concat(out); - }; - Decipher.prototype._final = function () { - var chunk = this._cache.flush(); - if (this._autopadding) return unpad(this._mode.decrypt(this, chunk)); - if (chunk) throw new Error("data not multiple of block length"); - }; - Decipher.prototype.setAutoPadding = function (setTo) { - return (this._autopadding = !!setTo), this; - }; - function Splitter() { - this.cache = Buffer2.allocUnsafe(0); - } - Splitter.prototype = {}; - Splitter.prototype.add = function (data) { - this.cache = Buffer2.concat([this.cache, data]); - }; - Splitter.prototype.get = function (autoPadding) { - var out; - if (autoPadding) { - if (this.cache.length > 16) return (out = this.cache.slice(0, 16)), (this.cache = this.cache.slice(16)), out; - } else if (this.cache.length >= 16) - return (out = this.cache.slice(0, 16)), (this.cache = this.cache.slice(16)), out; - return null; - }; - Splitter.prototype.flush = function () { - if (this.cache.length) return this.cache; - }; - function unpad(last) { - var padded = last[15]; - if (padded < 1 || padded > 16) throw new Error("unable to decrypt data"); - for (var i = -1; ++i < padded; ) - if (last[i + (16 - padded)] !== padded) throw new Error("unable to decrypt data"); - if (padded !== 16) return last.slice(0, 16 - padded); - } - function createDecipheriv(suite, password, iv) { - var config = MODES[suite.toLowerCase()]; - if (!config) throw new TypeError("invalid suite type"); - - password = getArrayBufferOrView(password, "password"); - const iv_length = iv?.length || 0; - const required_iv_length = config.iv || 0; - iv = iv === null ? EMPTY_BUFFER : getArrayBufferOrView(iv, "iv"); - - if (config.mode !== "GCM" && iv_length !== required_iv_length) { - var error = new RangeError("Invalid key length"); - error.code = "ERR_CRYPTO_INVALID_KEYLEN"; - throw error; - } - if (password.length !== config.key / 8) { - var error = new RangeError("Invalid key length"); - error.code = "ERR_CRYPTO_INVALID_KEYLEN"; - throw error; - } - return config.type === "stream" - ? new StreamCipher(config.module, password, iv, !0) - : config.type === "auth" - ? new AuthCipher(config.module, password, iv, !0) - : new Decipher(config.module, password, iv); - } - function createDecipher(suite, password) { - var config = MODES[suite.toLowerCase()]; - if (!config) throw new TypeError("invalid suite type"); - var keys = ebtk(password, !1, config.key, config.iv); - return createDecipheriv(suite, keys.key, keys.iv); - } - exports.createDecipher = createDecipher; - exports.createDecipheriv = createDecipheriv; - }, -}); - -// node_modules/browserify-aes/browser.js -var require_browser5 = __commonJS({ - "node_modules/browserify-aes/browser.js"(exports) { - var ciphers = require_encrypter(), - deciphers = require_decrypter(); - exports.createCipher = exports.Cipher = ciphers.createCipher; - exports.createCipheriv = exports.Cipheriv = ciphers.createCipheriv; - exports.createDecipher = exports.Decipher = deciphers.createDecipher; - exports.createDecipheriv = exports.Decipheriv = deciphers.createDecipheriv; - exports.listCiphers = exports.getCiphers = getCiphers; - }, -}); - -// node_modules/browserify-des/modes.js -var require_modes2 = __commonJS({ - "node_modules/browserify-des/modes.js"(exports) { - exports["des-ecb"] = { - key: 8, - iv: 0, - }; - exports["des-cbc"] = exports.des = { - key: 8, - iv: 8, - }; - exports["des-ede3-cbc"] = exports.des3 = { - key: 24, - iv: 8, - }; - exports["des-ede3"] = { - key: 24, - iv: 0, - }; - exports["des-ede-cbc"] = { - key: 16, - iv: 8, - }; - exports["des-ede"] = { - key: 16, - iv: 0, - }; - }, -}); - -// node_modules/browserify-cipher/browser.js -var require_browser6 = __commonJS({ - "node_modules/browserify-cipher/browser.js"(exports) { - var DES = require_browserify_des(), - aes = require_browser5(), - aesModes = require_modes(), - desModes = require_modes2(), - ebtk = require_evp_bytestokey(); - function createCipher(suite, password) { - suite = suite.toLowerCase(); - var keyLen, ivLen; - if (aesModes[suite]) (keyLen = aesModes[suite].key), (ivLen = aesModes[suite].iv); - else if (desModes[suite]) (keyLen = desModes[suite].key * 8), (ivLen = desModes[suite].iv); - else throw new TypeError("invalid suite type"); - var keys = ebtk(password, !1, keyLen, ivLen); - return createCipheriv(suite, keys.key, keys.iv); - } - function createDecipher(suite, password) { - suite = suite.toLowerCase(); - var keyLen, ivLen; - if (aesModes[suite]) (keyLen = aesModes[suite].key), (ivLen = aesModes[suite].iv); - else if (desModes[suite]) (keyLen = desModes[suite].key * 8), (ivLen = desModes[suite].iv); - else throw new TypeError("invalid suite type"); - var keys = ebtk(password, !1, keyLen, ivLen); - return createDecipheriv(suite, keys.key, keys.iv); - } - function createCipheriv(suite, key, iv) { - if (((suite = suite.toLowerCase()), aesModes[suite])) return aes.createCipheriv(suite, key, iv); - if (desModes[suite]) return new DES({ key, iv, mode: suite }); - throw new TypeError("invalid suite type"); - } - function createDecipheriv(suite, key, iv) { - if (((suite = suite.toLowerCase()), aesModes[suite])) return aes.createDecipheriv(suite, key, iv); - if (desModes[suite]) return new DES({ key, iv, mode: suite, decrypt: !0 }); - throw new TypeError("invalid suite type"); - } - exports.createCipher = exports.Cipher = createCipher; - exports.createCipheriv = exports.Cipheriv = createCipheriv; - exports.createDecipher = exports.Decipher = createDecipher; - exports.createDecipheriv = exports.Decipheriv = createDecipheriv; - exports.listCiphers = exports.getCiphers = getCiphers; - }, -}); - -// node_modules/crypto-browserify/index.js -var require_crypto_browserify2 = __commonJS({ - "node_modules/crypto-browserify/index.js"(exports) { - "use strict"; - var aes = require_browser6(); - exports.Cipher = aes.Cipher; - exports.createCipher = aes.createCipher; - exports.Cipheriv = aes.Cipheriv; - exports.createCipheriv = aes.createCipheriv; - exports.Decipher = aes.Decipher; - exports.createDecipher = aes.createDecipher; - exports.Decipheriv = aes.Decipheriv; - exports.createDecipheriv = aes.createDecipheriv; - exports.getCiphers = getCiphers; - exports.listCiphers = aes.listCiphers; - - exports.getRandomValues = values => crypto.getRandomValues(values); - exports.constants = $processBindingConstants.crypto; - }, -}); - -// crypto.js -var crypto_exports = require_crypto_browserify2(); +crypto_exports.getRandomValues = value => crypto.getRandomValues(value); +crypto_exports.constants = $processBindingConstants.crypto; var scryptSync = "scryptSync" in crypto @@ -2592,3 +742,125 @@ crypto_exports.ECDH = ECDH; crypto_exports.createECDH = function createECDH(curve) { return new ECDH(curve); }; + +{ + function getDecoder(decoder, encoding) { + const normalizedEncoding = normalizeEncoding(encoding); + decoder ||= new StringDecoder(encoding); + if (decoder.encoding !== normalizedEncoding) { + if (normalizedEncoding === undefined) { + throw $ERR_UNKNOWN_ENCODING(encoding); + } + + // there's a test for this + // https://github.com/nodejs/node/blob/6b4255434226491449b7d925038008439e5586b2/lib/internal/crypto/cipher.js#L100 + // https://github.com/nodejs/node/blob/6b4255434226491449b7d925038008439e5586b2/test/parallel/test-crypto-encoding-validation-error.js#L31 + throw $ERR_INTERNAL_ASSERTION("Cannot change encoding"); + } + return decoder; + } + + function setAutoPadding(ap) { + this[kHandle].setAutoPadding(ap); + return this; + } + + function getAuthTag() { + return this[kHandle].getAuthTag(); + } + + function setAuthTag(tagbuf, encoding) { + this[kHandle].setAuthTag(tagbuf, encoding); + return this; + } + + function setAAD(aadbuf, options) { + this[kHandle].setAAD(aadbuf, options); + return this; + } + + function _transform(chunk, encoding, callback) { + this.push(this[kHandle].update(chunk, encoding)); + callback(); + } + + function _flush(callback) { + try { + this.push(this[kHandle].final()); + } catch (e) { + callback(e); + return; + } + callback(); + } + + function update(data, inputEncoding, outputEncoding) { + const res = this[kHandle].update(data, inputEncoding); + + if (outputEncoding && outputEncoding !== "buffer") { + this._decoder = getDecoder(this._decoder, outputEncoding); + return this._decoder.write(res); + } + + return res; + } + + function final(outputEncoding) { + const res = this[kHandle].final(); + + if (outputEncoding && outputEncoding !== "buffer") { + this._decoder = getDecoder(this._decoder, outputEncoding); + return this._decoder.end(res); + } + + return res; + } + + function Cipheriv(cipher, key, iv, options): void { + if (!new.target) { + return new Cipheriv(cipher, key, iv, options); + } + + this[kHandle] = new Cipher(false, cipher, key, iv, options); + LazyTransform.$apply(this, [options]); + this._decoder = null; + } + $toClass(Cipheriv, "Cipheriv", LazyTransform); + + Cipheriv.prototype.setAutoPadding = setAutoPadding; + Cipheriv.prototype.getAuthTag = getAuthTag; + Cipheriv.prototype.setAAD = setAAD; + Cipheriv.prototype._transform = _transform; + Cipheriv.prototype._flush = _flush; + Cipheriv.prototype.update = update; + Cipheriv.prototype.final = final; + + function Decipheriv(cipher, key, iv, options): void { + if (!new.target) { + return new Decipheriv(cipher, key, iv, options); + } + + this[kHandle] = new Cipher(true, cipher, key, iv, options); + LazyTransform.$apply(this, [options]); + this._decoder = null; + } + $toClass(Decipheriv, "Decipheriv", LazyTransform); + + Decipheriv.prototype.setAutoPadding = setAutoPadding; + Decipheriv.prototype.setAuthTag = setAuthTag; + Decipheriv.prototype.setAAD = setAAD; + Decipheriv.prototype._transform = _transform; + Decipheriv.prototype._flush = _flush; + Decipheriv.prototype.update = update; + Decipheriv.prototype.final = final; + + crypto_exports.Cipheriv = Cipheriv; + crypto_exports.Decipheriv = Decipheriv; + crypto_exports.createCipheriv = function createCipheriv(cipher, key, iv, options) { + return new Cipheriv(cipher, key, iv, options); + }; + crypto_exports.createDecipheriv = function createDecipheriv(cipher, key, iv, options) { + return new Decipheriv(cipher, key, iv, options); + }; + crypto_exports.getCiphers = getCiphers; +} diff --git a/test/js/bun/crypto/cipheriv-decipheriv.test.ts b/test/js/bun/crypto/cipheriv-decipheriv.test.ts index 01d94a7b0b..8b9f45ecb2 100644 --- a/test/js/bun/crypto/cipheriv-decipheriv.test.ts +++ b/test/js/bun/crypto/cipheriv-decipheriv.test.ts @@ -141,18 +141,21 @@ const references = { ciphertext: "8497dba3f7f3252e7f5f3cf2c49c5e16cd83da98a942532537a77283afb875ec5a865020ced4242615edb7ec2eaf7e6c", authTag: null, }, - "aes-128-cfb8": { - iv: "3021d44812302ae0312c9ef523f01bf5", - key: "20787258b5d2a166262ecc6e3e917a58", - ciphertext: "db4596b2f0d7a74bea91a1d715e1327ca149591f5bc64d19fde7138eacfa5dd0da503596dcc66bc771edcf14b6eb8f69", - authTag: null, - }, - "aes-128-cfb1": { - iv: "c91453a0182f1efeeb4525ed96b0aad3", - key: "26bfaea72f720475528cc5b2bfd5cf2e", - ciphertext: "5d3f5c646140be734f9283e67759f8b06340cc96a8bb21b591cfd43a48cc2941decdd9b4aea13b7c5c7a48d443c8d384", - authTag: null, - }, + + // BoringSSL does not support these modes + // "aes-128-cfb8": { + // iv: "3021d44812302ae0312c9ef523f01bf5", + // key: "20787258b5d2a166262ecc6e3e917a58", + // ciphertext: "db4596b2f0d7a74bea91a1d715e1327ca149591f5bc64d19fde7138eacfa5dd0da503596dcc66bc771edcf14b6eb8f69", + // authTag: null, + // }, + // "aes-128-cfb1": { + // iv: "c91453a0182f1efeeb4525ed96b0aad3", + // key: "26bfaea72f720475528cc5b2bfd5cf2e", + // ciphertext: "5d3f5c646140be734f9283e67759f8b06340cc96a8bb21b591cfd43a48cc2941decdd9b4aea13b7c5c7a48d443c8d384", + // authTag: null, + // }, + "aes-128-ofb": { iv: "ca6bf9503134e3a4bad0890a973d4189", key: "f4687e40072a015e25d927e13b7318c4", diff --git a/test/js/node/crypto/crypto.test.ts b/test/js/node/crypto/crypto.test.ts index 2a8fa50a9b..1fb6040c33 100644 --- a/test/js/node/crypto/crypto.test.ts +++ b/test/js/node/crypto/crypto.test.ts @@ -286,12 +286,12 @@ it("should send cipher events in the right order", async () => { // TODO: prefinish and readable (on both cipher and decipher) should be flipped // This seems like a bug in our crypto code, which expect(out.split("\n")).toEqual([ - `[ "cipher", "prefinish" ]`, `[ "cipher", "readable" ]`, + `[ "cipher", "prefinish" ]`, `[ "cipher", "data" ]`, `[ 1, "dfb6b7e029be3ad6b090349ed75931f28f991b52ca9a89f5bf6f82fa1c87aa2d624bd77701dcddfcceaf3add7d66ce06ced17aebca4cb35feffc4b8b9008b3c4" ]`, - `[ "decipher", "prefinish" ]`, `[ "decipher", "readable" ]`, + `[ "decipher", "prefinish" ]`, `[ "decipher", "data" ]`, `[ 2, "4f7574206f6620746865206d6f756e7461696e206f6620646573706169722c20612073746f6e65206f6620686f70652e" ]`, `[ 3, "Out of the mountain of despair, a stone of hope." ]`, diff --git a/test/js/node/test/parallel/test-crypto-cipheriv-decipheriv.js b/test/js/node/test/parallel/test-crypto-cipheriv-decipheriv.js new file mode 100644 index 0000000000..9a6440b63c --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-cipheriv-decipheriv.js @@ -0,0 +1,231 @@ +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); +const { hasOpenSSL3 } = require('../common/crypto'); +const isFipsEnabled = crypto.getFips(); + +function testCipher1(key, iv) { + // Test encryption and decryption with explicit key and iv + const plaintext = + '32|RmVZZkFUVmpRRkp0TmJaUm56ZU9qcnJkaXNNWVNpTTU*|iXmckfRWZBGWWELw' + + 'eCBsThSsfUHLeRe0KCsK8ooHgxie0zOINpXxfZi/oNG7uq9JWFVCk70gfzQH8ZUJ' + + 'jAfaFg**'; + const cipher = crypto.createCipheriv('des-ede3-cbc', key, iv); + let ciph = cipher.update(plaintext, 'utf8', 'hex'); + ciph += cipher.final('hex'); + + const decipher = crypto.createDecipheriv('des-ede3-cbc', key, iv); + let txt = decipher.update(ciph, 'hex', 'utf8'); + txt += decipher.final('utf8'); + + assert.strictEqual(txt, plaintext, + `encryption/decryption with key ${key} and iv ${iv}`); + + // Streaming cipher interface + // NB: In real life, it's not guaranteed that you can get all of it + // in a single read() like this. But in this case, we know it's + // quite small, so there's no harm. + const cStream = crypto.createCipheriv('des-ede3-cbc', key, iv); + cStream.end(plaintext); + ciph = cStream.read(); + + const dStream = crypto.createDecipheriv('des-ede3-cbc', key, iv); + dStream.end(ciph); + txt = dStream.read().toString('utf8'); + + assert.strictEqual(txt, plaintext, + `streaming cipher with key ${key} and iv ${iv}`); +} + + +function testCipher2(key, iv) { + // Test encryption and decryption with explicit key and iv + const plaintext = + '32|RmVZZkFUVmpRRkp0TmJaUm56ZU9qcnJkaXNNWVNpTTU*|iXmckfRWZBGWWELw' + + 'eCBsThSsfUHLeRe0KCsK8ooHgxie0zOINpXxfZi/oNG7uq9JWFVCk70gfzQH8ZUJ' + + 'jAfaFg**'; + const cipher = crypto.createCipheriv('des-ede3-cbc', key, iv); + let ciph = cipher.update(plaintext, 'utf8', 'buffer'); + ciph = Buffer.concat([ciph, cipher.final('buffer')]); + + const decipher = crypto.createDecipheriv('des-ede3-cbc', key, iv); + let txt = decipher.update(ciph, 'buffer', 'utf8'); + txt += decipher.final('utf8'); + + assert.strictEqual(txt, plaintext, + `encryption/decryption with key ${key} and iv ${iv}`); +} + + +function testCipher3(key, iv) { + if (!crypto.getCiphers().includes('id-aes128-wrap')) { + common.printSkipMessage('unsupported id-aes128-wrap test'); + return; + } + // Test encryption and decryption with explicit key and iv. + // AES Key Wrap test vector comes from RFC3394 + const plaintext = Buffer.from('00112233445566778899AABBCCDDEEFF', 'hex'); + + const cipher = crypto.createCipheriv('id-aes128-wrap', key, iv); + let ciph = cipher.update(plaintext, 'utf8', 'buffer'); + ciph = Buffer.concat([ciph, cipher.final('buffer')]); + const ciph2 = Buffer.from('1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5', + 'hex'); + assert(ciph.equals(ciph2)); + const decipher = crypto.createDecipheriv('id-aes128-wrap', key, iv); + let deciph = decipher.update(ciph, 'buffer'); + deciph = Buffer.concat([deciph, decipher.final()]); + + assert(deciph.equals(plaintext), + `encryption/decryption with key ${key} and iv ${iv}`); +} + +{ + const Cipheriv = crypto.Cipheriv; + const key = '123456789012345678901234'; + const iv = '12345678'; + + const instance = Cipheriv('des-ede3-cbc', key, iv); + assert(instance instanceof Cipheriv, 'Cipheriv is expected to return a new ' + + 'instance when called without `new`'); + + assert.throws( + () => crypto.createCipheriv(null), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "cipher" argument must be of type string. ' + + 'Received null' + }); + + assert.throws( + () => crypto.createCipheriv('des-ede3-cbc', null), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); + + assert.throws( + () => crypto.createCipheriv('des-ede3-cbc', key, 10), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); +} + +{ + const Decipheriv = crypto.Decipheriv; + const key = '123456789012345678901234'; + const iv = '12345678'; + + const instance = Decipheriv('des-ede3-cbc', key, iv); + assert(instance instanceof Decipheriv, 'Decipheriv expected to return a new' + + ' instance when called without `new`'); + + assert.throws( + () => crypto.createDecipheriv(null), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "cipher" argument must be of type string. ' + + 'Received null' + }); + + assert.throws( + () => crypto.createDecipheriv('des-ede3-cbc', null), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); + + assert.throws( + () => crypto.createDecipheriv('des-ede3-cbc', key, 10), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); +} + +testCipher1('0123456789abcd0123456789', '12345678'); +testCipher1('0123456789abcd0123456789', Buffer.from('12345678')); +testCipher1(Buffer.from('0123456789abcd0123456789'), '12345678'); +testCipher1(Buffer.from('0123456789abcd0123456789'), Buffer.from('12345678')); +testCipher2(Buffer.from('0123456789abcd0123456789'), Buffer.from('12345678')); + +if (!isFipsEnabled) { + testCipher3(Buffer.from('000102030405060708090A0B0C0D0E0F', 'hex'), + Buffer.from('A6A6A6A6A6A6A6A6', 'hex')); +} + +// Zero-sized IV or null should be accepted in ECB mode. +crypto.createCipheriv('aes-128-ecb', Buffer.alloc(16), Buffer.alloc(0)); +crypto.createCipheriv('aes-128-ecb', Buffer.alloc(16), null); + +const errMessage = /Invalid initialization vector/; + +// But non-empty IVs should be rejected. +for (let n = 1; n < 256; n += 1) { + assert.throws( + () => crypto.createCipheriv('aes-128-ecb', Buffer.alloc(16), + Buffer.alloc(n)), + errMessage); +} + +// Correctly sized IV should be accepted in CBC mode. +crypto.createCipheriv('aes-128-cbc', Buffer.alloc(16), Buffer.alloc(16)); + +// But all other IV lengths should be rejected. +for (let n = 0; n < 256; n += 1) { + if (n === 16) continue; + assert.throws( + () => crypto.createCipheriv('aes-128-cbc', Buffer.alloc(16), + Buffer.alloc(n)), + errMessage); +} + +// And so should null be. +assert.throws(() => { + crypto.createCipheriv('aes-128-cbc', Buffer.alloc(16), null); +}, /Invalid initialization vector/); + +// Zero-sized IV should be rejected in GCM mode. +assert.throws( + () => crypto.createCipheriv('aes-128-gcm', Buffer.alloc(16), + Buffer.alloc(0)), + errMessage); + +// But all other IV lengths should be accepted. +const minIvLength = hasOpenSSL3 ? 8 : 1; +const maxIvLength = hasOpenSSL3 ? 64 : 256; +for (let n = minIvLength; n < maxIvLength; n += 1) { + if (isFipsEnabled && n < 12) continue; + crypto.createCipheriv('aes-128-gcm', Buffer.alloc(16), Buffer.alloc(n)); +} + +{ + // Passing an invalid cipher name should throw. + assert.throws( + () => crypto.createCipheriv('aes-127', Buffer.alloc(16), null), + { + name: 'Error', + code: 'ERR_CRYPTO_UNKNOWN_CIPHER', + message: /Unknown cipher(: aes-127)?/ + }); + + // Passing a key with an invalid length should throw. + assert.throws( + () => crypto.createCipheriv('aes-128-ecb', Buffer.alloc(17), null), + /Invalid key length/); +} + +{ + // https://github.com/nodejs/node/issues/45757 + // eslint-disable-next-line no-restricted-syntax + assert.throws(() => + crypto.createCipheriv('aes-128-gcm', Buffer.alloc(16), Buffer.alloc(12)) + .update(Buffer.allocUnsafeSlow(2 ** 31 - 1))); +} diff --git a/test/js/node/test/parallel/test-crypto-classes.js b/test/js/node/test/parallel/test-crypto-classes.js new file mode 100644 index 0000000000..429bc91d44 --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-classes.js @@ -0,0 +1,30 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +if (!common.hasCrypto) { + common.skip('missing crypto'); +} +const crypto = require('crypto'); +const { hasOpenSSL3 } = require('../common/crypto'); + +// 'ClassName' : ['args', 'for', 'constructor'] +const TEST_CASES = { + 'Hash': ['sha1'], + 'Hmac': ['sha1', 'Node'], + 'Cipheriv': ['des-ede3-cbc', '0123456789abcd0123456789', '12345678'], + 'Decipheriv': ['des-ede3-cbc', '0123456789abcd0123456789', '12345678'], + 'Sign': ['RSA-SHA1'], + 'Verify': ['RSA-SHA1'], + 'DiffieHellman': [1024], + 'DiffieHellmanGroup': ['modp5'], + 'ECDH': ['prime256v1'], +}; + +if (!crypto.getFips()) { + TEST_CASES.DiffieHellman = [hasOpenSSL3 ? 1024 : 256]; +} + +for (const [clazz, args] of Object.entries(TEST_CASES)) { + assert(crypto[`create${clazz}`](...args) instanceof crypto[clazz]); +} diff --git a/test/js/node/test/parallel/test-crypto-dh.js b/test/js/node/test/parallel/test-crypto-dh.js index 7808bf5e9f..01833a2532 100644 --- a/test/js/node/test/parallel/test-crypto-dh.js +++ b/test/js/node/test/parallel/test-crypto-dh.js @@ -67,29 +67,26 @@ const { }; } else { wrongBlockLength = { - message: 'error:0606506D:digital envelope' + - ' routines:EVP_DecryptFinal_ex:wrong final block length', - code: 'ERR_OSSL_EVP_WRONG_FINAL_BLOCK_LENGTH', - library: 'digital envelope routines', - reason: 'wrong final block length' + message: /error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length|error:1e00007b:Cipher functions:OPENSSL_internal:WRONG_FINAL_BLOCK_LENGTH/, + code: /ERR_OSSL_(EVP_)?WRONG_FINAL_BLOCK_LENGTH/, + library: /digital envelope routines|Cipher functions/, + reason: /wrong final block length|WRONG_FINAL_BLOCK_LENGTH/ }; } // Run this one twice to make sure that the dh3 clears its error properly { - // TODO(dylan-conway): !!!!!!!!!!!!! - // const c = crypto.createDecipheriv('aes-128-ecb', crypto.randomBytes(16), ''); - // assert.throws(() => { - // c.final('utf8'); - // }, wrongBlockLength); + const c = crypto.createDecipheriv('aes-128-ecb', crypto.randomBytes(16), ''); + assert.throws(() => { + c.final('utf8'); + }, wrongBlockLength); } { - // TODO(dylan-conway): !!!!!!!!!!!!! - // const c = crypto.createDecipheriv('aes-128-ecb', crypto.randomBytes(16), ''); - // assert.throws(() => { - // c.final('utf8'); - // }, wrongBlockLength); + const c = crypto.createDecipheriv('aes-128-ecb', crypto.randomBytes(16), ''); + assert.throws(() => { + c.final('utf8'); + }, wrongBlockLength); } { diff --git a/test/js/node/test/parallel/test-crypto-encoding-validation-error.js b/test/js/node/test/parallel/test-crypto-encoding-validation-error.js new file mode 100644 index 0000000000..0e921ac286 --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-encoding-validation-error.js @@ -0,0 +1,52 @@ +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +// This test checks if error is thrown in case of wrong encoding provided into cipher. + +const assert = require('assert'); +const { createCipheriv, randomBytes } = require('crypto'); + +const createCipher = () => { + return createCipheriv('aes-256-cbc', randomBytes(32), randomBytes(16)); +}; + +{ + const cipher = createCipher(); + cipher.update('test', 'utf-8', 'utf-8'); + + assert.throws( + () => cipher.update('666f6f', 'hex', 'hex'), + { message: /Cannot change encoding/ } + ); +} + +{ + const cipher = createCipher(); + cipher.update('test', 'utf-8', 'utf-8'); + + assert.throws( + () => cipher.final('hex'), + { message: /Cannot change encoding/ } + ); +} + +{ + const cipher = createCipher(); + cipher.update('test', 'utf-8', 'utf-8'); + + assert.throws( + () => cipher.final('bad2'), + { message: /^Unknown encoding: bad2$/, code: 'ERR_UNKNOWN_ENCODING' } + ); +} + +{ + const cipher = createCipher(); + + assert.throws( + () => cipher.update('test', 'utf-8', 'bad3'), + { message: /^Unknown encoding: bad3$/, code: 'ERR_UNKNOWN_ENCODING' } + ); +} diff --git a/test/js/node/test/parallel/test-crypto-gcm-explicit-short-tag.js b/test/js/node/test/parallel/test-crypto-gcm-explicit-short-tag.js new file mode 100644 index 0000000000..ec0d70444c --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-gcm-explicit-short-tag.js @@ -0,0 +1,47 @@ +// Flags: --pending-deprecation +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { createDecipheriv, randomBytes } = require('crypto'); + +common.expectWarning({ + DeprecationWarning: [] +}); + +const key = randomBytes(32); +const iv = randomBytes(16); + +{ + // Full 128-bit tag. + + const tag = randomBytes(16); + createDecipheriv('aes-256-gcm', key, iv).setAuthTag(tag); +} + +{ + // Shortened tag with explicit length option. + + const tag = randomBytes(12); + createDecipheriv('aes-256-gcm', key, iv, { + authTagLength: tag.byteLength + }).setAuthTag(tag); +} + +{ + // Shortened tag with explicit but incorrect length option. + + const tag = randomBytes(12); + assert.throws(() => { + createDecipheriv('aes-256-gcm', key, iv, { + authTagLength: 14 + }).setAuthTag(tag); + }, { + name: 'TypeError', + message: 'Invalid authentication tag length: 12', + code: 'ERR_CRYPTO_INVALID_AUTH_TAG' + }); +} diff --git a/test/js/node/test/parallel/test-crypto-gcm-implicit-short-tag.js b/test/js/node/test/parallel/test-crypto-gcm-implicit-short-tag.js new file mode 100644 index 0000000000..79647f0085 --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-gcm-implicit-short-tag.js @@ -0,0 +1,20 @@ +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const { createDecipheriv, randomBytes } = require('crypto'); + +common.expectWarning({ + DeprecationWarning: [ + ['Using AES-GCM authentication tags of less than 128 bits without ' + + 'specifying the authTagLength option when initializing decryption is ' + + 'deprecated.', + 'DEP0182'], + ] +}); + +const key = randomBytes(32); +const iv = randomBytes(16); +const tag = randomBytes(12); +createDecipheriv('aes-256-gcm', key, iv).setAuthTag(tag); diff --git a/test/js/node/test/parallel/test-crypto-key-objects.js b/test/js/node/test/parallel/test-crypto-key-objects.js index b63b1c9054..a596cfd2b2 100644 --- a/test/js/node/test/parallel/test-crypto-key-objects.js +++ b/test/js/node/test/parallel/test-crypto-key-objects.js @@ -28,6 +28,8 @@ const { generateKeyPairSync, } = require('crypto'); +const { hasOpenSSL3 } = require('../common/crypto'); + const fixtures = require('../common/fixtures'); const publicPem = fixtures.readKey('rsa_public.pem', 'ascii'); @@ -301,7 +303,7 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', // This should not cause a crash: https://github.com/nodejs/node/issues/25247 assert.throws(() => { createPrivateKey({ key: '' }); - }, common.hasOpenSSL3 ? { + }, hasOpenSSL3 ? { message: 'error:1E08010C:DECODER routines::unsupported', } : { message: 'error:0909006C:PEM routines:get_name:no start line', @@ -327,7 +329,7 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', type: 'pkcs1' }); createPrivateKey({ key, format: 'der', type: 'pkcs1' }); - }, common.hasOpenSSL3 ? { + }, hasOpenSSL3 ? { message: /error:1E08010C:DECODER routines::unsupported/, library: 'DECODER routines' } : { @@ -514,7 +516,7 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', { // Reading an encrypted key without a passphrase should fail. - assert.throws(() => createPrivateKey(privateDsa), common.hasOpenSSL3 ? { + assert.throws(() => createPrivateKey(privateDsa), hasOpenSSL3 ? { name: 'Error', message: 'error:07880109:common libcrypto routines::interrupted or ' + 'cancelled', @@ -530,7 +532,7 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', key: privateDsa, format: 'pem', passphrase: Buffer.alloc(1025, 'a') - }), common.hasOpenSSL3 ? { name: 'Error' } : { + }), hasOpenSSL3 ? { name: 'Error' } : { code: 'ERR_OSSL_PEM_BAD_PASSWORD_READ', name: 'Error' }); diff --git a/test/js/node/test/parallel/test-crypto-oneshot-hash.js b/test/js/node/test/parallel/test-crypto-oneshot-hash.js new file mode 100644 index 0000000000..b0340a4189 --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-oneshot-hash.js @@ -0,0 +1,48 @@ +'use strict'; +// This tests crypto.hash() works. +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); +const fixtures = require('../common/fixtures'); +const { hasOpenSSL } = require('../common/crypto'); +const fs = require('fs'); + +// Test errors for invalid arguments. +[undefined, null, true, 1, () => {}, {}].forEach((invalid) => { + assert.throws(() => { crypto.hash(invalid, 'test'); }, { code: 'ERR_INVALID_ARG_TYPE' }); +}); + +[undefined, null, true, 1, () => {}, {}].forEach((invalid) => { + assert.throws(() => { crypto.hash('sha1', invalid); }, { code: 'ERR_INVALID_ARG_TYPE' }); +}); + +[null, true, 1, () => {}, {}].forEach((invalid) => { + assert.throws(() => { crypto.hash('sha1', 'test', invalid); }, { code: 'ERR_INVALID_ARG_TYPE' }); +}); + +assert.throws(() => { crypto.hash('sha1', 'test', 'not an encoding'); }, { code: 'ERR_INVALID_ARG_VALUE' }); + +// Test that the output of crypto.hash() is the same as crypto.createHash(). +const methods = crypto.getHashes(); + +const input = fs.readFileSync(fixtures.path('utf8_test_text.txt')); + +for (const method of methods) { + // Skip failing tests on OpenSSL 3.4.0 + if (method.startsWith('shake') && hasOpenSSL(3, 4)) + continue; + for (const outputEncoding of ['buffer', 'hex', 'base64', undefined]) { + if (method === 'SHA512-224') continue; + const oldDigest = crypto.createHash(method).update(input).digest(outputEncoding || 'hex'); + const digestFromBuffer = crypto.hash(method, input, outputEncoding); + assert.deepStrictEqual(digestFromBuffer, oldDigest, + `different result from ${method} with encoding ${outputEncoding}`); + const digestFromString = crypto.hash(method, input.toString(), outputEncoding); + assert.deepStrictEqual(digestFromString, oldDigest, + `different result from ${method} with encoding ${outputEncoding}`); + } +} diff --git a/test/js/node/test/parallel/test-crypto-padding-aes256.js b/test/js/node/test/parallel/test-crypto-padding-aes256.js index 14d853bdfd..4e25976f28 100644 --- a/test/js/node/test/parallel/test-crypto-padding-aes256.js +++ b/test/js/node/test/parallel/test-crypto-padding-aes256.js @@ -19,6 +19,10 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. +/* +Skipped test +https://github.com/electron/electron/blob/bf1d377e083380b4849c5f42aacf3762176eac07/script/node-disabled-tests.json#L25 + 'use strict'; const common = require('../common'); if (!common.hasCrypto) @@ -58,3 +62,5 @@ plaintext = '0123456789abcdef0123456789abcde'; // not a multiple encrypted = encrypt(plaintext, true); decrypted = decrypt(encrypted, true); assert.strictEqual(decrypted, plaintext); + +*/ \ No newline at end of file diff --git a/test/js/node/test/parallel/test-crypto-padding.js b/test/js/node/test/parallel/test-crypto-padding.js new file mode 100644 index 0000000000..025caf2592 --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-padding.js @@ -0,0 +1,125 @@ +// 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 { hasOpenSSL3 } = require('../common/crypto'); + +// Input data. +const ODD_LENGTH_PLAIN = 'Hello node world!'; +const EVEN_LENGTH_PLAIN = 'Hello node world!AbC09876dDeFgHi'; + +const KEY_PLAIN = 'S3c.r.e.t.K.e.Y!'; +const IV_PLAIN = 'blahFizz2011Buzz'; + +const CIPHER_NAME = 'aes-128-cbc'; + +// Expected result data. + +// echo -n 'Hello node world!' | \ +// openssl enc -aes-128-cbc -e -K 5333632e722e652e742e4b2e652e5921 \ +// -iv 626c616846697a7a3230313142757a7a | xxd -p -c256 +const ODD_LENGTH_ENCRYPTED = + '7f57859550d4d2fdb9806da2a750461a9fe77253cd1cbd4b07beee4e070d561f'; + +// echo -n 'Hello node world!AbC09876dDeFgHi' | \ +// openssl enc -aes-128-cbc -e -K 5333632e722e652e742e4b2e652e5921 \ +// -iv 626c616846697a7a3230313142757a7a | xxd -p -c256 +const EVEN_LENGTH_ENCRYPTED = + '7f57859550d4d2fdb9806da2a750461ab46e71b3d78ebe2d9684dfc87f7575b988' + + '6119866912cb8c7bcaf76c5ebc2378'; + +// echo -n 'Hello node world!AbC09876dDeFgHi' | \ +// openssl enc -aes-128-cbc -e -K 5333632e722e652e742e4b2e652e5921 \ +// -iv 626c616846697a7a3230313142757a7a -nopad | xxd -p -c256 +const EVEN_LENGTH_ENCRYPTED_NOPAD = + '7f57859550d4d2fdb9806da2a750461ab46e71b3d78ebe2d9684dfc87f7575b9'; + + +// Helper wrappers. +function enc(plain, pad) { + const encrypt = crypto.createCipheriv(CIPHER_NAME, KEY_PLAIN, IV_PLAIN); + encrypt.setAutoPadding(pad); + let hex = encrypt.update(plain, 'ascii', 'hex'); + hex += encrypt.final('hex'); + return hex; +} + +function dec(encd, pad) { + const decrypt = crypto.createDecipheriv(CIPHER_NAME, KEY_PLAIN, IV_PLAIN); + decrypt.setAutoPadding(pad); + let plain = decrypt.update(encd, 'hex'); + plain += decrypt.final('latin1'); + return plain; +} + +// Test encryption +assert.strictEqual(enc(ODD_LENGTH_PLAIN, true), ODD_LENGTH_ENCRYPTED); +assert.strictEqual(enc(EVEN_LENGTH_PLAIN, true), EVEN_LENGTH_ENCRYPTED); + +assert.throws(function() { + // Input must have block length %. + enc(ODD_LENGTH_PLAIN, false); +}, hasOpenSSL3 ? { + message: 'error:1C80006B:Provider routines::wrong final block length', + code: 'ERR_OSSL_WRONG_FINAL_BLOCK_LENGTH', + reason: 'wrong final block length', +} : { + message: /error:0607F08A:digital envelope routines:EVP_EncryptFinal_ex:data not multiple of block length|error:1e00006a:Cipher functions:OPENSSL_internal:DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH/, + code: /ERR_OSSL(_EVP)?_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH/, + reason: /data not multiple of block length|DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH/, +} +); + +assert.strictEqual( + enc(EVEN_LENGTH_PLAIN, false), EVEN_LENGTH_ENCRYPTED_NOPAD +); + +// Test decryption. +assert.strictEqual(dec(ODD_LENGTH_ENCRYPTED, true), ODD_LENGTH_PLAIN); +assert.strictEqual(dec(EVEN_LENGTH_ENCRYPTED, true), EVEN_LENGTH_PLAIN); + +// Returns including original padding. +assert.strictEqual(dec(ODD_LENGTH_ENCRYPTED, false).length, 32); +assert.strictEqual(dec(EVEN_LENGTH_ENCRYPTED, false).length, 48); + +assert.throws(function() { + // Must have at least 1 byte of padding (PKCS): + assert.strictEqual(dec(EVEN_LENGTH_ENCRYPTED_NOPAD, true), EVEN_LENGTH_PLAIN); +}, hasOpenSSL3 ? { + message: 'error:1C800064:Provider routines::bad decrypt', + reason: 'bad decrypt', + code: 'ERR_OSSL_BAD_DECRYPT', +} : { + message: /error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt|error:1e000065:Cipher functions:OPENSSL_internal:BAD_DECRYPT/, + reason: /bad decrypt|BAD_DECRYPT/, + code: /ERR_OSSL(_EVP)?_BAD_DECRYPT/, +}); + +// No-pad encrypted string should return the same: +assert.strictEqual( + dec(EVEN_LENGTH_ENCRYPTED_NOPAD, false), EVEN_LENGTH_PLAIN +); diff --git a/test/js/node/test/parallel/test-crypto-stream.js b/test/js/node/test/parallel/test-crypto-stream.js new file mode 100644 index 0000000000..3fb6cd833d --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-stream.js @@ -0,0 +1,87 @@ +// 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 stream = require('stream'); +const crypto = require('crypto'); +const { hasOpenSSL3 } = require('../common/crypto'); + +if (!crypto.getFips()) { + // Small stream to buffer converter + class Stream2buffer extends stream.Writable { + constructor(callback) { + super(); + + this._buffers = []; + this.once('finish', function() { + callback(null, Buffer.concat(this._buffers)); + }); + } + + _write(data, encoding, done) { + this._buffers.push(data); + return done(null); + } + } + + // Create an md5 hash of "Hallo world" + const hasher1 = crypto.createHash('md5'); + hasher1.pipe(new Stream2buffer(common.mustCall(function end(err, hash) { + assert.strictEqual(err, null); + assert.strictEqual( + hash.toString('hex'), '06460dadb35d3d503047ce750ceb2d07' + ); + }))); + hasher1.end('Hallo world'); + + // Simpler check for unpipe, setEncoding, pause and resume + crypto.createHash('md5').unpipe({}); + crypto.createHash('md5').setEncoding('utf8'); + crypto.createHash('md5').pause(); + crypto.createHash('md5').resume(); +} + +// Decipher._flush() should emit an error event, not an exception. +const key = Buffer.from('48fb56eb10ffeb13fc0ef551bbca3b1b', 'hex'); +const badkey = Buffer.from('12341234123412341234123412341234', 'hex'); +const iv = Buffer.from('6d358219d1f488f5f4eb12820a66d146', 'hex'); +const cipher = crypto.createCipheriv('aes-128-cbc', key, iv); +const decipher = crypto.createDecipheriv('aes-128-cbc', badkey, iv); + +cipher.pipe(decipher) + .on('error', common.expectsError(hasOpenSSL3 ? { + message: /bad decrypt/, + library: 'Provider routines', + reason: 'bad decrypt', + } : { + message: /bad decrypt|BAD_DECRYPT/, + function: /EVP_DecryptFinal_ex|OPENSSL_internal/, + library: /digital envelope routines|Cipher functions/, + reason: /bad decrypt|BAD_DECRYPT/, + })); + +cipher.end('Papaya!'); // Should not cause an unhandled exception.