node:crypto: move Cipheriv and Decipheriv to native code (#18342)

This commit is contained in:
Dylan Conway
2025-03-21 02:52:52 -07:00
committed by GitHub
parent a3585ff961
commit 10665821c4
33 changed files with 1790 additions and 1918 deletions

View File

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

View File

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

View File

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

View File

@@ -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<Zig::GlobalObject*>(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<BufferEncodingType>(*lexicalGlobalObject, castedThis->m_encoding));
}

View File

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

View File

@@ -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<CustomGetterSetter>& 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);

View File

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

View File

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

View File

@@ -0,0 +1,46 @@
#include "JSCipher.h"
#include "JSCipherPrototype.h"
#include "JSCipherConstructor.h"
#include "DOMIsoSubspaces.h"
#include "ZigGlobalObject.h"
#include "ErrorCode.h"
#include <JavaScriptCore/JSCJSValueInlines.h>
#include <JavaScriptCore/VMTrapsInlines.h>
#include <JavaScriptCore/LazyClassStructureInlines.h>
#include <JavaScriptCore/FunctionPrototype.h>
#include <JavaScriptCore/ObjectPrototype.h>
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<typename Visitor>
void JSCipher::visitChildrenImpl(JSCell* cell, Visitor& visitor)
{
JSCipher* thisObject = jsCast<JSCipher*>(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

View File

@@ -0,0 +1,122 @@
#pragma once
#include "root.h"
#include <JavaScriptCore/JSDestructibleObject.h>
#include <JavaScriptCore/JSObject.h>
#include <JavaScriptCore/JSTypedArrays.h>
#include <wtf/text/WTFString.h>
#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<uint32_t> authTagLen, int32_t maxMessageSize)
{
JSCipher* instance = new (NotNull, JSC::allocateCell<JSCipher>(vm)) JSCipher(vm, structure, kind, WTFMove(ctx), authTagLen, maxMessageSize);
instance->finishCreation(vm, globalObject);
return instance;
}
template<typename, JSC::SubspaceAccess mode>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
return nullptr;
return WebCore::subspaceForImpl<JSCipher, WebCore::UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForJSCipher.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSCipher = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForJSCipher.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForJSCipher = std::forward<decltype(space)>(space); });
}
bool checkCCMMessageLength(int32_t messageLen) const
{
if (messageLen > m_maxMessageSize) {
return false;
}
return true;
}
bool maybePassAuthTagToOpenSSL()
{
if (m_authTagState == AuthTagState::AuthTagKnown) {
ncrypto::Buffer<const char> 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<uint32_t> 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<uint32_t> 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<JSCipher*>(cell)->~JSCipher(); }
};
void setupCipherClassStructure(JSC::LazyClassStructure::Initializer&);
} // namespace Bun

View File

@@ -0,0 +1,223 @@
#include "JSCipherConstructor.h"
#include "JSCipher.h"
#include "ErrorCode.h"
#include "JSBufferEncodingType.h"
#include "NodeValidator.h"
#include <JavaScriptCore/TypedArrayInlines.h>
#include <JavaScriptCore/JSCJSValueInlines.h>
#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<uint32_t>& 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<uint8_t> 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<uint32_t> 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<uint8_t*>(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

View File

@@ -0,0 +1,49 @@
#pragma once
#include "root.h"
#include <JavaScriptCore/InternalFunction.h>
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<JSCipherConstructor>(vm)) JSCipherConstructor(vm, structure);
constructor->finishCreation(vm, prototype);
return constructor;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
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

View File

@@ -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 <JavaScriptCore/TypedArrayInlines.h>
#include <JavaScriptCore/JSCJSValueInlines.h>
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<unsigned>(JSC::PropertyAttribute::Function), JSC::NoIntrinsic, { HashTableValue::NativeFunctionType, jsCipherUpdate, 2 } },
{ "final"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), JSC::NoIntrinsic, { HashTableValue::NativeFunctionType, jsCipherFinal, 0 } },
{ "setAutoPadding"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), JSC::NoIntrinsic, { HashTableValue::NativeFunctionType, jsCipherSetAutoPadding, 1 } },
{ "getAuthTag"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), JSC::NoIntrinsic, { HashTableValue::NativeFunctionType, jsCipherGetAuthTag, 0 } },
{ "setAuthTag"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), JSC::NoIntrinsic, { HashTableValue::NativeFunctionType, jsCipherSetAuthTag, 1 } },
{ "setAAD"_s, static_cast<unsigned>(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<JSCipher*>(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<const uint8_t> buf {
.data = reinterpret_cast<uint8_t*>(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<ArrayBuffer> outBuf = JSC::ArrayBuffer::tryCreateUninitialized(bufLen, 1);
if (!outBuf) {
throwOutOfMemoryError(lexicalGlobalObject, scope);
return JSValue::encode({});
}
buf = {
.data = reinterpret_cast<uint8_t*>(dataView->vector()),
.len = dataView->byteLength(),
};
bool res = cipher->m_ctx.update(buf, static_cast<unsigned char*>(outBuf->data()), &bufLen);
ASSERT(static_cast<size_t>(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<JSCipher*>(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<ArrayBuffer> 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<unsigned char*>(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<unsigned char*>(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<JSCipher*>(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<JSCipher*>(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<JSCipher*>(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<JSCipher*>(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<uint32_t> 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<int>::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<const unsigned char> buf {
.data = nullptr,
.len = static_cast<size_t>(*plaintextLength),
};
if (!cipher->m_ctx.update(buf, nullptr, &outlen)) {
return ERR::CRYPTO_INVALID_STATE(scope, globalObject, "setAAD"_s);
}
}
ncrypto::Buffer<const unsigned char> buf {
.data = reinterpret_cast<uint8_t*>(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());
}

View File

@@ -0,0 +1,45 @@
#pragma once
#include "root.h"
#include <JavaScriptCore/CallData.h>
#include <JavaScriptCore/ObjectConstructor.h>
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<JSCipherPrototype>(vm)) JSCipherPrototype(vm, structure);
prototype->finishCreation(vm);
return prototype;
}
template<typename, JSC::SubspaceAccess>
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

View File

@@ -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<JSDiffieHellman>(vm)) JSDiffieHellman(vm, structure, std::move(dh));
JSDiffieHellman* instance = new (NotNull, JSC::allocateCell<JSDiffieHellman>(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))
{
}

View File

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

View File

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

View File

@@ -931,6 +931,7 @@ public:
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSVerify;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSHmac;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSHash;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSCipher;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForServerRouteList;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForBunRequest;
};

View File

@@ -937,6 +937,7 @@ public:
std::unique_ptr<IsoSubspace> m_subspaceForJSDiffieHellman;
std::unique_ptr<IsoSubspace> m_subspaceForJSDiffieHellmanGroup;
std::unique_ptr<IsoSubspace> m_subspaceForJSECDH;
std::unique_ptr<IsoSubspace> m_subspaceForJSCipher;
};
} // namespace WebCore

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -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." ]`,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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);
*/

View File

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

View File

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