mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
node:crypto: move Cipheriv and Decipheriv to native code (#18342)
This commit is contained in:
@@ -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 {};
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
46
src/bun.js/bindings/node/crypto/JSCipher.cpp
Normal file
46
src/bun.js/bindings/node/crypto/JSCipher.cpp
Normal 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
|
||||
122
src/bun.js/bindings/node/crypto/JSCipher.h
Normal file
122
src/bun.js/bindings/node/crypto/JSCipher.h
Normal 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
|
||||
223
src/bun.js/bindings/node/crypto/JSCipherConstructor.cpp
Normal file
223
src/bun.js/bindings/node/crypto/JSCipherConstructor.cpp
Normal 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
|
||||
49
src/bun.js/bindings/node/crypto/JSCipherConstructor.h
Normal file
49
src/bun.js/bindings/node/crypto/JSCipherConstructor.h
Normal 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
|
||||
386
src/bun.js/bindings/node/crypto/JSCipherPrototype.cpp
Normal file
386
src/bun.js/bindings/node/crypto/JSCipherPrototype.cpp
Normal 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());
|
||||
}
|
||||
45
src/bun.js/bindings/node/crypto/JSCipherPrototype.h
Normal file
45
src/bun.js/bindings/node/crypto/JSCipherPrototype.h
Normal 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
|
||||
@@ -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))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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." ]`,
|
||||
|
||||
231
test/js/node/test/parallel/test-crypto-cipheriv-decipheriv.js
Normal file
231
test/js/node/test/parallel/test-crypto-cipheriv-decipheriv.js
Normal 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)));
|
||||
}
|
||||
30
test/js/node/test/parallel/test-crypto-classes.js
Normal file
30
test/js/node/test/parallel/test-crypto-classes.js
Normal 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]);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -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' }
|
||||
);
|
||||
}
|
||||
@@ -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'
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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'
|
||||
});
|
||||
|
||||
48
test/js/node/test/parallel/test-crypto-oneshot-hash.js
Normal file
48
test/js/node/test/parallel/test-crypto-oneshot-hash.js
Normal 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}`);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
*/
|
||||
125
test/js/node/test/parallel/test-crypto-padding.js
Normal file
125
test/js/node/test/parallel/test-crypto-padding.js
Normal 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
|
||||
);
|
||||
87
test/js/node/test/parallel/test-crypto-stream.js
Normal file
87
test/js/node/test/parallel/test-crypto-stream.js
Normal 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.
|
||||
Reference in New Issue
Block a user