deps: start working on ncrypto dep

Start moving src/crypto functionality out to a separate dep that
can be shared with other projects that need to emulate Node.js
crypto behavior.

PR-URL: https://github.com/nodejs/node/pull/53803
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
This commit is contained in:
James M Snell 2024-07-09 13:36:07 -07:00
parent c77424c990
commit efe5b81df9
18 changed files with 848 additions and 238 deletions

View File

@ -175,7 +175,8 @@ with-code-cache test-code-cache:
out/Makefile: config.gypi common.gypi node.gyp \ out/Makefile: config.gypi common.gypi node.gyp \
deps/uv/uv.gyp deps/llhttp/llhttp.gyp deps/zlib/zlib.gyp \ deps/uv/uv.gyp deps/llhttp/llhttp.gyp deps/zlib/zlib.gyp \
deps/simdutf/simdutf.gyp deps/ada/ada.gyp deps/nbytes/nbytes.gyp \ deps/simdutf/simdutf.gyp deps/ada/ada.gyp deps/nbytes/nbytes.gyp \
tools/v8_gypfiles/toolchain.gypi tools/v8_gypfiles/features.gypi \ tools/v8_gypfiles/toolchain.gypi \
tools/v8_gypfiles/features.gypi \
tools/v8_gypfiles/inspector.gypi tools/v8_gypfiles/v8.gyp tools/v8_gypfiles/inspector.gypi tools/v8_gypfiles/v8.gyp
$(PYTHON) tools/gyp_node.py -f make $(PYTHON) tools/gyp_node.py -f make

14
deps/ncrypto/BUILD.gn vendored Normal file
View File

@ -0,0 +1,14 @@
##############################################################################
# #
# DO NOT EDIT THIS FILE! #
# #
##############################################################################
# This file is used by GN for building, which is NOT the build system used for
# building official binaries.
# Please modify the gyp files if you are making changes to build system.
import("unofficial.gni")
ncrypto_gn_build("ncrypto") {
}

5
deps/ncrypto/README.md vendored Normal file
View File

@ -0,0 +1,5 @@
# Node.js crypto (ncrypto) library
The `ncrypto` library extracts the base internal implementation of Node.js crypto operations
that support both `node:crypto` and Web Crypto implementations and makes them available for
use in other projects that need to emulate Node.js' behavior.

92
deps/ncrypto/engine.cc vendored Normal file
View File

@ -0,0 +1,92 @@
#include "ncrypto.h"
namespace ncrypto {
// ============================================================================
// Engine
#ifndef OPENSSL_NO_ENGINE
EnginePointer::EnginePointer(ENGINE* engine_, bool finish_on_exit_)
: engine(engine_),
finish_on_exit(finish_on_exit_) {}
EnginePointer::EnginePointer(EnginePointer&& other) noexcept
: engine(other.engine),
finish_on_exit(other.finish_on_exit) {
other.release();
}
EnginePointer::~EnginePointer() { reset(); }
EnginePointer& EnginePointer::operator=(EnginePointer&& other) noexcept {
if (this == &other) return *this;
this->~EnginePointer();
return *new (this) EnginePointer(std::move(other));
}
void EnginePointer::reset(ENGINE* engine_, bool finish_on_exit_) {
if (engine != nullptr) {
if (finish_on_exit) {
// This also does the equivalent of ENGINE_free.
ENGINE_finish(engine);
} else {
ENGINE_free(engine);
}
}
engine = engine_;
finish_on_exit = finish_on_exit_;
}
ENGINE* EnginePointer::release() {
ENGINE* ret = engine;
engine = nullptr;
finish_on_exit = false;
return ret;
}
EnginePointer EnginePointer::getEngineByName(const std::string_view name,
CryptoErrorList* errors) {
MarkPopErrorOnReturn mark_pop_error_on_return(errors);
EnginePointer engine(ENGINE_by_id(name.data()));
if (!engine) {
// Engine not found, try loading dynamically.
engine = EnginePointer(ENGINE_by_id("dynamic"));
if (engine) {
if (!ENGINE_ctrl_cmd_string(engine.get(), "SO_PATH", name.data(), 0) ||
!ENGINE_ctrl_cmd_string(engine.get(), "LOAD", nullptr, 0)) {
engine.reset();
}
}
}
return std::move(engine);
}
bool EnginePointer::setAsDefault(uint32_t flags, CryptoErrorList* errors) {
if (engine == nullptr) return false;
ClearErrorOnReturn clear_error_on_return(errors);
return ENGINE_set_default(engine, flags) != 0;
}
bool EnginePointer::init(bool finish_on_exit) {
if (engine == nullptr) return false;
if (finish_on_exit) setFinishOnExit();
return ENGINE_init(engine) == 1;
}
EVPKeyPointer EnginePointer::loadPrivateKey(const std::string_view key_name) {
if (engine == nullptr) return EVPKeyPointer();
return EVPKeyPointer(ENGINE_load_private_key(engine, key_name.data(), nullptr, nullptr));
}
void EnginePointer::initEnginesOnce() {
static bool initialized = false;
if (!initialized) {
ENGINE_load_builtin_engines();
ENGINE_register_all_complete();
initialized = true;
}
}
#endif // OPENSSL_NO_ENGINE
} // namespace ncrypto

223
deps/ncrypto/ncrypto.cc vendored Normal file
View File

@ -0,0 +1,223 @@
#include "ncrypto.h"
#include <algorithm>
#include <cstring>
#include "openssl/bn.h"
#if OPENSSL_VERSION_MAJOR >= 3
#include "openssl/provider.h"
#endif
namespace ncrypto {
// ============================================================================
ClearErrorOnReturn::ClearErrorOnReturn(CryptoErrorList* errors) : errors_(errors) {
ERR_clear_error();
}
ClearErrorOnReturn::~ClearErrorOnReturn() {
if (errors_ != nullptr) errors_->capture();
ERR_clear_error();
}
int ClearErrorOnReturn::peeKError() { return ERR_peek_error(); }
MarkPopErrorOnReturn::MarkPopErrorOnReturn(CryptoErrorList* errors) : errors_(errors) {
ERR_set_mark();
}
MarkPopErrorOnReturn::~MarkPopErrorOnReturn() {
if (errors_ != nullptr) errors_->capture();
ERR_pop_to_mark();
}
int MarkPopErrorOnReturn::peekError() { return ERR_peek_error(); }
CryptoErrorList::CryptoErrorList(CryptoErrorList::Option option) {
if (option == Option::CAPTURE_ON_CONSTRUCT) capture();
}
void CryptoErrorList::capture() {
errors_.clear();
while(const auto err = ERR_get_error()) {
char buf[256];
ERR_error_string_n(err, buf, sizeof(buf));
errors_.emplace_front(buf);
}
}
void CryptoErrorList::add(std::string error) {
errors_.push_back(error);
}
std::optional<std::string> CryptoErrorList::pop_back() {
if (errors_.empty()) return std::nullopt;
std::string error = errors_.back();
errors_.pop_back();
return error;
}
std::optional<std::string> CryptoErrorList::pop_front() {
if (errors_.empty()) return std::nullopt;
std::string error = errors_.front();
errors_.pop_front();
return error;
}
// ============================================================================
bool isFipsEnabled() {
#if OPENSSL_VERSION_MAJOR >= 3
return EVP_default_properties_is_fips_enabled(nullptr) == 1;
#else
return FIPS_mode() == 1;
#endif
}
bool setFipsEnabled(bool enable, CryptoErrorList* errors) {
if (isFipsEnabled() == enable) return true;
ClearErrorOnReturn clearErrorOnReturn(errors);
#if OPENSSL_VERSION_MAJOR >= 3
return EVP_default_properties_enable_fips(nullptr, enable ? 1 : 0) == 1;
#else
return FIPS_mode_set(enable ? 1 : 0) == 1;
#endif
}
bool testFipsEnabled() {
#if OPENSSL_VERSION_MAJOR >= 3
OSSL_PROVIDER* fips_provider = nullptr;
if (OSSL_PROVIDER_available(nullptr, "fips")) {
fips_provider = OSSL_PROVIDER_load(nullptr, "fips");
}
const auto enabled = fips_provider == nullptr ? 0 :
OSSL_PROVIDER_self_test(fips_provider) ? 1 : 0;
#else
#ifdef OPENSSL_FIPS
const auto enabled = FIPS_selftest() ? 1 : 0;
#else // OPENSSL_FIPS
const auto enabled = 0;
#endif // OPENSSL_FIPS
#endif
return enabled;
}
// ============================================================================
// Bignum
BignumPointer::BignumPointer(BIGNUM* bignum) : bn_(bignum) {}
BignumPointer::BignumPointer(BignumPointer&& other) noexcept
: bn_(other.release()) {}
BignumPointer& BignumPointer::operator=(BignumPointer&& other) noexcept {
if (this == &other) return *this;
this->~BignumPointer();
return *new (this) BignumPointer(std::move(other));
}
BignumPointer::~BignumPointer() { reset(); }
void BignumPointer::reset(BIGNUM* bn) {
bn_.reset(bn);
}
BIGNUM* BignumPointer::release() {
return bn_.release();
}
size_t BignumPointer::byteLength() {
if (bn_ == nullptr) return 0;
return BN_num_bytes(bn_.get());
}
std::vector<uint8_t> BignumPointer::encode() {
return encodePadded(bn_.get(), byteLength());
}
std::vector<uint8_t> BignumPointer::encodePadded(size_t size) {
return encodePadded(bn_.get(), size);
}
std::vector<uint8_t> BignumPointer::encode(const BIGNUM* bn) {
return encodePadded(bn, bn != nullptr ? BN_num_bytes(bn) : 0);
}
std::vector<uint8_t> BignumPointer::encodePadded(const BIGNUM* bn, size_t s) {
if (bn == nullptr) return std::vector<uint8_t>(0);
size_t size = std::max(s, static_cast<size_t>(BN_num_bytes(bn)));
std::vector<uint8_t> buf(size);
BN_bn2binpad(bn, buf.data(), size);
return buf;
}
bool BignumPointer::operator==(const BignumPointer& other) noexcept {
if (bn_ == nullptr && other.bn_ != nullptr) return false;
if (bn_ != nullptr && other.bn_ == nullptr) return false;
if (bn_ == nullptr && other.bn_ == nullptr) return true;
return BN_cmp(bn_.get(), other.bn_.get()) == 0;
}
bool BignumPointer::operator==(const BIGNUM* other) noexcept {
if (bn_ == nullptr && other != nullptr) return false;
if (bn_ != nullptr && other == nullptr) return false;
if (bn_ == nullptr && other == nullptr) return true;
return BN_cmp(bn_.get(), other) == 0;
}
// ============================================================================
// Utility methods
bool CSPRNG(void* buffer, size_t length) {
auto buf = reinterpret_cast<unsigned char*>(buffer);
do {
if (1 == RAND_status()) {
#if OPENSSL_VERSION_MAJOR >= 3
if (1 == RAND_bytes_ex(nullptr, buf, length, 0)) {
return true;
}
#else
while (length > INT_MAX && 1 == RAND_bytes(buf, INT_MAX)) {
buf += INT_MAX;
length -= INT_MAX;
}
if (length <= INT_MAX && 1 == RAND_bytes(buf, static_cast<int>(length)))
return true;
#endif
}
#if OPENSSL_VERSION_MAJOR >= 3
const auto code = ERR_peek_last_error();
// A misconfigured OpenSSL 3 installation may report 1 from RAND_poll()
// and RAND_status() but fail in RAND_bytes() if it cannot look up
// a matching algorithm for the CSPRNG.
if (ERR_GET_LIB(code) == ERR_LIB_RAND) {
const auto reason = ERR_GET_REASON(code);
if (reason == RAND_R_ERROR_INSTANTIATING_DRBG ||
reason == RAND_R_UNABLE_TO_FETCH_DRBG ||
reason == RAND_R_UNABLE_TO_CREATE_DRBG) {
return false;
}
}
#endif
} while (1 == RAND_poll());
return false;
}
int NoPasswordCallback(char* buf, int size, int rwflag, void* u) {
return 0;
}
int PasswordCallback(char* buf, int size, int rwflag, void* u) {
const Buffer* passphrase = static_cast<const Buffer*>(u);
if (passphrase != nullptr) {
size_t buflen = static_cast<size_t>(size);
size_t len = passphrase->len;
if (buflen < len)
return -1;
memcpy(buf, reinterpret_cast<const char*>(passphrase->data), len);
return len;
}
return -1;
}
} // namespace ncrypto

27
deps/ncrypto/ncrypto.gyp vendored Normal file
View File

@ -0,0 +1,27 @@
{
'variables': {
'ncrypto_sources': [
'engine.cc',
'ncrypto.cc',
'ncrypto.h',
],
},
'targets': [
{
'target_name': 'ncrypto',
'type': 'static_library',
'include_dirs': ['.'],
'direct_dependent_settings': {
'include_dirs': ['.'],
},
'sources': [ '<@(ncrypto_sources)' ],
'conditions': [
['node_shared_openssl=="false"', {
'dependencies': [
'../openssl/openssl.gyp:openssl'
]
}],
]
},
]
}

298
deps/ncrypto/ncrypto.h vendored Normal file
View File

@ -0,0 +1,298 @@
#pragma once
#include <list>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include "openssl/bn.h"
#include <openssl/x509.h>
#include <openssl/dh.h>
#include <openssl/dsa.h>
#include <openssl/ec.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/kdf.h>
#include <openssl/rsa.h>
#include <openssl/ssl.h>
#ifndef OPENSSL_NO_ENGINE
# include <openssl/engine.h>
#endif // !OPENSSL_NO_ENGINE
// The FIPS-related functions are only available
// when the OpenSSL itself was compiled with FIPS support.
#if defined(OPENSSL_FIPS) && OPENSSL_VERSION_MAJOR < 3
# include <openssl/fips.h>
#endif // OPENSSL_FIPS
#ifdef __GNUC__
#define NCRYPTO_MUST_USE_RESULT __attribute__((warn_unused_result))
#else
#define NCRYPTO_MUST_USE_RESULT
#endif
namespace ncrypto {
// ============================================================================
// Utility macros
#if NCRYPTO_DEVELOPMENT_CHECKS
#define NCRYPTO_STR(x) #x
#define NCRYPTO_REQUIRE(EXPR) \
{ \
if (!(EXPR) { abort(); }) }
#define NCRYPTO_FAIL(MESSAGE) \
do { \
std::cerr << "FAIL: " << (MESSAGE) << std::endl; \
abort(); \
} while (0);
#define NCRYPTO_ASSERT_EQUAL(LHS, RHS, MESSAGE) \
do { \
if (LHS != RHS) { \
std::cerr << "Mismatch: '" << LHS << "' - '" << RHS << "'" << std::endl; \
NCRYPTO_FAIL(MESSAGE); \
} \
} while (0);
#define NCRYPTO_ASSERT_TRUE(COND) \
do { \
if (!(COND)) { \
std::cerr << "Assert at line " << __LINE__ << " of file " << __FILE__ \
<< std::endl; \
NCRYPTO_FAIL(NCRYPTO_STR(COND)); \
} \
} while (0);
#else
#define NCRYPTO_FAIL(MESSAGE)
#define NCRYPTO_ASSERT_EQUAL(LHS, RHS, MESSAGE)
#define NCRYPTO_ASSERT_TRUE(COND)
#endif
#define NCRYPTO_DISALLOW_COPY(Name) \
Name(const Name&) = delete; \
Name& operator=(const Name&) = delete;
#define NCRYPTO_DISALLOW_MOVE(Name) \
Name(Name&&) = delete; \
Name& operator=(Name&&) = delete;
#define NCRYPTO_DISALLOW_COPY_AND_MOVE(Name) \
NCRYPTO_DISALLOW_COPY(Name) \
NCRYPTO_DISALLOW_MOVE(Name)
#define NCRYPTO_DISALLOW_NEW_DELETE() \
void* operator new(size_t) = delete; \
void operator delete(void*) = delete;
// ============================================================================
// Error handling utilities
// Capture the current OpenSSL Error Stack. The stack will be ordered such
// that the error currently at the top of the stack is at the end of the
// list and the error at the bottom of the stack is at the beginning.
class CryptoErrorList final {
public:
enum class Option {
NONE,
CAPTURE_ON_CONSTRUCT
};
CryptoErrorList(Option option = Option::CAPTURE_ON_CONSTRUCT);
void capture();
// Add an error message to the end of the stack.
void add(std::string message);
inline const std::string& peek_back() const { return errors_.back(); }
inline size_t size() const { return errors_.size(); }
inline bool empty() const { return errors_.empty(); }
inline auto begin() const noexcept { return errors_.begin(); }
inline auto end() const noexcept { return errors_.end(); }
inline auto rbegin() const noexcept { return errors_.rbegin(); }
inline auto rend() const noexcept { return errors_.rend(); }
std::optional<std::string> pop_back();
std::optional<std::string> pop_front();
private:
std::list<std::string> errors_;
};
// Forcibly clears the error stack on destruction. This stops stale errors
// from popping up later in the lifecycle of crypto operations where they
// would cause spurious failures. It is a rather blunt method, though, and
// ERR_clear_error() isn't necessarily cheap.
//
// If created with a pointer to a CryptoErrorList, the current OpenSSL error
// stack will be captured before clearing the error.
class ClearErrorOnReturn final {
public:
ClearErrorOnReturn(CryptoErrorList* errors = nullptr);
~ClearErrorOnReturn();
NCRYPTO_DISALLOW_COPY_AND_MOVE(ClearErrorOnReturn)
NCRYPTO_DISALLOW_NEW_DELETE()
int peeKError();
private:
CryptoErrorList* errors_;
};
// Pop errors from OpenSSL's error stack that were added between when this
// was constructed and destructed.
//
// If created with a pointer to a CryptoErrorList, the current OpenSSL error
// stack will be captured before resetting the error to the mark.
class MarkPopErrorOnReturn final {
public:
MarkPopErrorOnReturn(CryptoErrorList* errors = nullptr);
~MarkPopErrorOnReturn();
NCRYPTO_DISALLOW_COPY_AND_MOVE(MarkPopErrorOnReturn)
NCRYPTO_DISALLOW_NEW_DELETE()
int peekError();
private:
CryptoErrorList* errors_;
};
// ============================================================================
// Various smart pointer aliases for OpenSSL types.
template <typename T, void (*function)(T*)>
struct FunctionDeleter {
void operator()(T* pointer) const { function(pointer); }
typedef std::unique_ptr<T, FunctionDeleter> Pointer;
};
template <typename T, void (*function)(T*)>
using DeleteFnPtr = typename FunctionDeleter<T, function>::Pointer;
using BignumCtxPointer = DeleteFnPtr<BN_CTX, BN_CTX_free>;
using BIOPointer = DeleteFnPtr<BIO, BIO_free_all>;
using CipherCtxPointer = DeleteFnPtr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free>;
using DHPointer = DeleteFnPtr<DH, DH_free>;
using DSAPointer = DeleteFnPtr<DSA, DSA_free>;
using DSASigPointer = DeleteFnPtr<DSA_SIG, DSA_SIG_free>;
using ECDSASigPointer = DeleteFnPtr<ECDSA_SIG, ECDSA_SIG_free>;
using ECPointer = DeleteFnPtr<EC_KEY, EC_KEY_free>;
using ECGroupPointer = DeleteFnPtr<EC_GROUP, EC_GROUP_free>;
using ECKeyPointer = DeleteFnPtr<EC_KEY, EC_KEY_free>;
using ECPointPointer = DeleteFnPtr<EC_POINT, EC_POINT_free>;
using EVPKeyCtxPointer = DeleteFnPtr<EVP_PKEY_CTX, EVP_PKEY_CTX_free>;
using EVPKeyPointer = DeleteFnPtr<EVP_PKEY, EVP_PKEY_free>;
using EVPMDCtxPointer = DeleteFnPtr<EVP_MD_CTX, EVP_MD_CTX_free>;
using HMACCtxPointer = DeleteFnPtr<HMAC_CTX, HMAC_CTX_free>;
using NetscapeSPKIPointer = DeleteFnPtr<NETSCAPE_SPKI, NETSCAPE_SPKI_free>;
using PKCS8Pointer = DeleteFnPtr<PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO_free>;
using RSAPointer = DeleteFnPtr<RSA, RSA_free>;
using SSLCtxPointer = DeleteFnPtr<SSL_CTX, SSL_CTX_free>;
using SSLPointer = DeleteFnPtr<SSL, SSL_free>;
using SSLSessionPointer = DeleteFnPtr<SSL_SESSION, SSL_SESSION_free>;
using X509Pointer = DeleteFnPtr<X509, X509_free>;
class BignumPointer final {
public:
BignumPointer() = default;
explicit BignumPointer(BIGNUM* bignum);
BignumPointer(BignumPointer&& other) noexcept;
BignumPointer& operator=(BignumPointer&& other) noexcept;
NCRYPTO_DISALLOW_COPY(BignumPointer)
~BignumPointer();
bool operator==(const BignumPointer& other) noexcept;
bool operator==(const BIGNUM* other) noexcept;
inline bool operator==(std::nullptr_t) noexcept { return bn_ == nullptr; }
inline operator bool() const { return bn_ != nullptr; }
inline BIGNUM* get() const noexcept { return bn_.get(); }
void reset(BIGNUM* bn = nullptr);
BIGNUM* release();
size_t byteLength();
std::vector<uint8_t> encode();
std::vector<uint8_t> encodePadded(size_t size);
static std::vector<uint8_t> encode(const BIGNUM* bn);
static std::vector<uint8_t> encodePadded(const BIGNUM* bn, size_t size);
private:
DeleteFnPtr<BIGNUM, BN_clear_free> bn_;
};
#ifndef OPENSSL_NO_ENGINE
class EnginePointer final {
public:
EnginePointer() = default;
explicit EnginePointer(ENGINE* engine_, bool finish_on_exit = false);
EnginePointer(EnginePointer&& other) noexcept;
EnginePointer& operator=(EnginePointer&& other) noexcept;
NCRYPTO_DISALLOW_COPY(EnginePointer)
~EnginePointer();
inline operator bool() const { return engine != nullptr; }
inline ENGINE* get() { return engine; }
inline void setFinishOnExit() { finish_on_exit = true; }
void reset(ENGINE* engine_ = nullptr, bool finish_on_exit_ = false);
bool setAsDefault(uint32_t flags, CryptoErrorList* errors = nullptr);
bool init(bool finish_on_exit = false);
EVPKeyPointer loadPrivateKey(const std::string_view key_name);
// Release ownership of the ENGINE* pointer.
ENGINE* release();
// Retrieve an OpenSSL Engine instance by name. If the name does not
// identify a valid named engine, the returned EnginePointer will be
// empty.
static EnginePointer getEngineByName(const std::string_view name,
CryptoErrorList* errors = nullptr);
// Call once when initializing OpenSSL at startup for the process.
static void initEnginesOnce();
private:
ENGINE* engine = nullptr;
bool finish_on_exit = false;
};
#endif // !OPENSSL_NO_ENGINE
// ============================================================================
// FIPS
bool isFipsEnabled();
bool setFipsEnabled(bool enabled, CryptoErrorList* errors);
bool testFipsEnabled();
// ============================================================================
// Various utilities
struct Buffer {
const void* data;
size_t len;
};
bool CSPRNG(void* buffer, size_t length) NCRYPTO_MUST_USE_RESULT;
// This callback is used to avoid the default passphrase callback in OpenSSL
// which will typically prompt for the passphrase. The prompting is designed
// for the OpenSSL CLI, but works poorly for some environments like Node.js
// because it involves synchronous interaction with the controlling terminal,
// something we never want, and use this function to avoid it.
int NoPasswordCallback(char* buf, int size, int rwflag, void* u);
int PasswordCallback(char* buf, int size, int rwflag, void* u);
// ============================================================================
// Version metadata
#define NCRYPTO_VERSION "0.0.1"
enum {
NCRYPTO_VERSION_MAJOR = 0,
NCRYPTO_VERSION_MINOR = 0,
NCRYPTO_VERSION_REVISION = 1,
};
} // namespace ncrypto

View File

@ -940,6 +940,9 @@
'<@(node_crypto_sources)', '<@(node_crypto_sources)',
'<@(node_quic_sources)', '<@(node_quic_sources)',
], ],
'dependencies': [
'deps/ncrypto/ncrypto.gyp:ncrypto',
],
}], }],
[ 'OS in "linux freebsd mac solaris" and ' [ 'OS in "linux freebsd mac solaris" and '
'target_arch=="x64" and ' 'target_arch=="x64" and '
@ -1201,6 +1204,9 @@
'defines': [ 'defines': [
'HAVE_OPENSSL=1', 'HAVE_OPENSSL=1',
], ],
'dependencies': [
'deps/ncrypto/ncrypto.gyp:ncrypto',
],
'sources': [ '<@(node_cctest_openssl_sources)' ], 'sources': [ '<@(node_cctest_openssl_sources)' ],
}], }],
['v8_enable_inspector==1', { ['v8_enable_inspector==1', {
@ -1394,6 +1400,9 @@
'defines': [ 'NODE_MKSNAPSHOT_USE_ARRAY_LITERALS=1' ], 'defines': [ 'NODE_MKSNAPSHOT_USE_ARRAY_LITERALS=1' ],
}], }],
[ 'node_use_openssl=="true"', { [ 'node_use_openssl=="true"', {
'dependencies': [
'deps/ncrypto/ncrypto.gyp:ncrypto',
],
'defines': [ 'defines': [
'HAVE_OPENSSL=1', 'HAVE_OPENSSL=1',
], ],

View File

@ -1,10 +1,11 @@
#include "crypto/crypto_context.h" #include "crypto/crypto_context.h"
#include "base_object-inl.h"
#include "crypto/crypto_bio.h" #include "crypto/crypto_bio.h"
#include "crypto/crypto_common.h" #include "crypto/crypto_common.h"
#include "crypto/crypto_util.h" #include "crypto/crypto_util.h"
#include "base_object-inl.h"
#include "env-inl.h" #include "env-inl.h"
#include "memory_tracker-inl.h" #include "memory_tracker-inl.h"
#include "ncrypto.h"
#include "node.h" #include "node.h"
#include "node_buffer.h" #include "node_buffer.h"
#include "node_options.h" #include "node_options.h"
@ -655,26 +656,28 @@ void SecureContext::SetEngineKey(const FunctionCallbackInfo<Value>& args) {
"experimental permission model is enabled"); "experimental permission model is enabled");
} }
CryptoErrorStore errors; ncrypto::CryptoErrorList errors;
Utf8Value engine_id(env->isolate(), args[1]); Utf8Value engine_id(env->isolate(), args[1]);
EnginePointer engine = LoadEngineById(*engine_id, &errors); auto engine = ncrypto::EnginePointer::getEngineByName(
engine_id.ToStringView(), &errors);
if (!engine) { if (!engine) {
Local<Value> exception; Local<Value> exception;
if (errors.ToException(env).ToLocal(&exception)) if (errors.empty()) {
errors.add(getNodeCryptoErrorString(NodeCryptoError::ENGINE_NOT_FOUND,
*engine_id));
}
if (cryptoErrorListToException(env, errors).ToLocal(&exception))
env->isolate()->ThrowException(exception); env->isolate()->ThrowException(exception);
return; return;
} }
if (!ENGINE_init(engine.get())) { if (!engine.init(true /* finish on exit*/)) {
return THROW_ERR_CRYPTO_OPERATION_FAILED( return THROW_ERR_CRYPTO_OPERATION_FAILED(
env, "Failure to initialize engine"); env, "Failure to initialize engine");
} }
engine.finish_on_exit = true;
Utf8Value key_name(env->isolate(), args[0]); Utf8Value key_name(env->isolate(), args[0]);
EVPKeyPointer key(ENGINE_load_private_key(engine.get(), *key_name, auto key = engine.loadPrivateKey(key_name.ToStringView());
nullptr, nullptr));
if (!key) if (!key)
return ThrowCryptoError(env, ERR_get_error(), "ENGINE_load_private_key"); return ThrowCryptoError(env, ERR_get_error(), "ENGINE_load_private_key");
@ -1143,12 +1146,17 @@ void SecureContext::SetClientCertEngine(
"experimental permission model is enabled"); "experimental permission model is enabled");
} }
CryptoErrorStore errors; ncrypto::CryptoErrorList errors;
const Utf8Value engine_id(env->isolate(), args[0]); const Utf8Value engine_id(env->isolate(), args[0]);
EnginePointer engine = LoadEngineById(*engine_id, &errors); auto engine = ncrypto::EnginePointer::getEngineByName(
engine_id.ToStringView(), &errors);
if (!engine) { if (!engine) {
Local<Value> exception; Local<Value> exception;
if (errors.ToException(env).ToLocal(&exception)) if (errors.empty()) {
errors.add(getNodeCryptoErrorString(NodeCryptoError::ENGINE_NOT_FOUND,
*engine_id));
}
if (cryptoErrorListToException(env, errors).ToLocal(&exception))
env->isolate()->ThrowException(exception); env->isolate()->ThrowException(exception);
return; return;
} }

View File

@ -147,7 +147,7 @@ class SecureContext final : public BaseObject {
X509Pointer issuer_; X509Pointer issuer_;
#ifndef OPENSSL_NO_ENGINE #ifndef OPENSSL_NO_ENGINE
bool client_cert_engine_provided_ = false; bool client_cert_engine_provided_ = false;
EnginePointer private_key_engine_; ncrypto::EnginePointer private_key_engine_;
#endif // !OPENSSL_NO_ENGINE #endif // !OPENSSL_NO_ENGINE
unsigned char ticket_key_name_[16]; unsigned char ticket_key_name_[16];

View File

@ -64,6 +64,26 @@ using v8::Value;
namespace crypto { namespace crypto {
namespace { namespace {
// Our custom implementation of the certificate verify callback
// used when establishing a TLS handshake. Because we cannot perform
// I/O quickly enough with X509_STORE_CTX_ APIs in this callback,
// we ignore preverify_ok errors here and let the handshake continue.
// In other words, this VerifyCallback is a non-op. It is imperative
// that the user user Connection::VerifyError after the `secure`
// callback has been made.
int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx) {
// From https://www.openssl.org/docs/man1.1.1/man3/SSL_verify_cb:
//
// If VerifyCallback returns 1, the verification process is continued. If
// VerifyCallback always returns 1, the TLS/SSL handshake will not be
// terminated with respect to verification failures and the connection will
// be established. The calling process can however retrieve the error code
// of the last verification error using SSL_get_verify_result(3) or by
// maintaining its own error storage managed by VerifyCallback.
return 1;
}
SSL_SESSION* GetSessionCallback( SSL_SESSION* GetSessionCallback(
SSL* s, SSL* s,
const unsigned char* key, const unsigned char* key,

View File

@ -4,6 +4,7 @@
#include "crypto/crypto_keys.h" #include "crypto/crypto_keys.h"
#include "env-inl.h" #include "env-inl.h"
#include "memory_tracker-inl.h" #include "memory_tracker-inl.h"
#include "ncrypto.h"
#include "node_buffer.h" #include "node_buffer.h"
#include "node_options-inl.h" #include "node_options-inl.h"
#include "string_bytes.h" #include "string_bytes.h"
@ -11,6 +12,10 @@
#include "util-inl.h" #include "util-inl.h"
#include "v8.h" #include "v8.h"
#ifndef OPENSSL_NO_ENGINE
#include <openssl/engine.h>
#endif // !OPENSSL_NO_ENGINE
#include "math.h" #include "math.h"
#if OPENSSL_VERSION_MAJOR >= 3 #if OPENSSL_VERSION_MAJOR >= 3
@ -43,22 +48,6 @@ using v8::Uint8Array;
using v8::Value; using v8::Value;
namespace crypto { namespace crypto {
int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx) {
// From https://www.openssl.org/docs/man1.1.1/man3/SSL_verify_cb:
//
// If VerifyCallback returns 1, the verification process is continued. If
// VerifyCallback always returns 1, the TLS/SSL handshake will not be
// terminated with respect to verification failures and the connection will
// be established. The calling process can however retrieve the error code
// of the last verification error using SSL_get_verify_result(3) or by
// maintaining its own error storage managed by VerifyCallback.
//
// Since we cannot perform I/O quickly enough with X509_STORE_CTX_ APIs in
// this callback, we ignore all preverify_ok errors and let the handshake
// continue. It is imperative that the user use Connection::VerifyError after
// the 'secure' callback has been made.
return 1;
}
MUST_USE_RESULT CSPRNGResult CSPRNG(void* buffer, size_t length) { MUST_USE_RESULT CSPRNGResult CSPRNG(void* buffer, size_t length) {
unsigned char* buf = static_cast<unsigned char*>(buffer); unsigned char* buf = static_cast<unsigned char*>(buffer);
@ -206,21 +195,14 @@ void InitCryptoOnce() {
sk_SSL_COMP_zero(SSL_COMP_get_compression_methods()); sk_SSL_COMP_zero(SSL_COMP_get_compression_methods());
#ifndef OPENSSL_NO_ENGINE #ifndef OPENSSL_NO_ENGINE
ERR_load_ENGINE_strings(); ncrypto::EnginePointer::initEnginesOnce();
ENGINE_load_builtin_engines();
#endif // !OPENSSL_NO_ENGINE #endif // !OPENSSL_NO_ENGINE
} }
void GetFipsCrypto(const FunctionCallbackInfo<Value>& args) { void GetFipsCrypto(const FunctionCallbackInfo<Value>& args) {
Mutex::ScopedLock lock(per_process::cli_options_mutex); Mutex::ScopedLock lock(per_process::cli_options_mutex);
Mutex::ScopedLock fips_lock(fips_mutex); Mutex::ScopedLock fips_lock(fips_mutex);
args.GetReturnValue().Set(ncrypto::isFipsEnabled() ? 1 : 0);
#if OPENSSL_VERSION_MAJOR >= 3
args.GetReturnValue().Set(EVP_default_properties_is_fips_enabled(nullptr) ?
1 : 0);
#else
args.GetReturnValue().Set(FIPS_mode() ? 1 : 0);
#endif
} }
void SetFipsCrypto(const FunctionCallbackInfo<Value>& args) { void SetFipsCrypto(const FunctionCallbackInfo<Value>& args) {
@ -232,43 +214,19 @@ void SetFipsCrypto(const FunctionCallbackInfo<Value>& args) {
CHECK(env->owns_process_state()); CHECK(env->owns_process_state());
bool enable = args[0]->BooleanValue(env->isolate()); bool enable = args[0]->BooleanValue(env->isolate());
#if OPENSSL_VERSION_MAJOR >= 3 ncrypto::CryptoErrorList errors;
if (enable == EVP_default_properties_is_fips_enabled(nullptr)) if (!ncrypto::setFipsEnabled(enable, &errors)) {
#else Local<Value> exception;
if (static_cast<int>(enable) == FIPS_mode()) if (cryptoErrorListToException(env, errors).ToLocal(&exception)) {
#endif env->isolate()->ThrowException(exception);
return; // No action needed. }
#if OPENSSL_VERSION_MAJOR >= 3
if (!EVP_default_properties_enable_fips(nullptr, enable)) {
#else
if (!FIPS_mode_set(enable)) {
#endif
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
return ThrowCryptoError(env, err);
} }
} }
void TestFipsCrypto(const v8::FunctionCallbackInfo<v8::Value>& args) { void TestFipsCrypto(const v8::FunctionCallbackInfo<v8::Value>& args) {
Mutex::ScopedLock lock(per_process::cli_options_mutex); Mutex::ScopedLock lock(per_process::cli_options_mutex);
Mutex::ScopedLock fips_lock(fips_mutex); Mutex::ScopedLock fips_lock(fips_mutex);
args.GetReturnValue().Set(ncrypto::testFipsEnabled() ? 1 : 0);
#if OPENSSL_VERSION_MAJOR >= 3
OSSL_PROVIDER* fips_provider = nullptr;
if (OSSL_PROVIDER_available(nullptr, "fips")) {
fips_provider = OSSL_PROVIDER_load(nullptr, "fips");
}
const auto enabled = fips_provider == nullptr ? 0 :
OSSL_PROVIDER_self_test(fips_provider) ? 1 : 0;
#else
#ifdef OPENSSL_FIPS
const auto enabled = FIPS_selftest() ? 1 : 0;
#else // OPENSSL_FIPS
const auto enabled = 0;
#endif // OPENSSL_FIPS
#endif
args.GetReturnValue().Set(enabled);
} }
void CryptoErrorStore::Capture() { void CryptoErrorStore::Capture() {
@ -285,6 +243,60 @@ bool CryptoErrorStore::Empty() const {
return errors_.empty(); return errors_.empty();
} }
MaybeLocal<Value> cryptoErrorListToException(
Environment* env, const ncrypto::CryptoErrorList& errors) {
// The CryptoErrorList contains a listing of zero or more errors.
// If there are no errors, it is likely a bug but we will return
// an error anyway.
if (errors.empty()) {
return Exception::Error(FIXED_ONE_BYTE_STRING(env->isolate(), "Ok"));
}
// The last error in the list is the one that will be used as the
// error message. All other errors will be added to the .opensslErrorStack
// property. We know there has to be at least one error in the list at
// this point.
auto& last = errors.peek_back();
Local<String> message;
if (!String::NewFromUtf8(
env->isolate(), last.data(), NewStringType::kNormal, last.size())
.ToLocal(&message)) {
return {};
}
Local<Value> exception = Exception::Error(message);
CHECK(!exception.IsEmpty());
if (errors.size() > 1) {
CHECK(exception->IsObject());
Local<Object> exception_obj = exception.As<Object>();
std::vector<Local<Value>> stack(errors.size() - 1);
// Iterate over all but the last error in the list.
auto current = errors.begin();
auto last = errors.end();
last--;
while (current != last) {
Local<Value> error;
if (!ToV8Value(env->context(), *current).ToLocal(&error)) {
return {};
}
stack.push_back(error);
++current;
}
Local<v8::Array> stackArray =
v8::Array::New(env->isolate(), &stack[0], stack.size());
if (!exception_obj
->Set(env->context(), env->openssl_error_stack(), stackArray)
.IsNothing()) {
return {};
}
}
return exception;
}
MaybeLocal<Value> CryptoErrorStore::ToException( MaybeLocal<Value> CryptoErrorStore::ToException(
Environment* env, Environment* env,
Local<String> exception_string) const { Local<String> exception_string) const {
@ -591,54 +603,8 @@ void ThrowCryptoError(Environment* env,
} }
#ifndef OPENSSL_NO_ENGINE #ifndef OPENSSL_NO_ENGINE
EnginePointer LoadEngineById(const char* id, CryptoErrorStore* errors) {
MarkPopErrorOnReturn mark_pop_error_on_return;
EnginePointer engine(ENGINE_by_id(id));
if (!engine) {
// Engine not found, try loading dynamically.
engine = EnginePointer(ENGINE_by_id("dynamic"));
if (engine) {
if (!ENGINE_ctrl_cmd_string(engine.get(), "SO_PATH", id, 0) ||
!ENGINE_ctrl_cmd_string(engine.get(), "LOAD", nullptr, 0)) {
engine.reset();
}
}
}
if (!engine && errors != nullptr) {
errors->Capture();
if (errors->Empty()) {
errors->Insert(NodeCryptoError::ENGINE_NOT_FOUND, id);
}
}
return engine;
}
bool SetEngine(const char* id, uint32_t flags, CryptoErrorStore* errors) {
ClearErrorOnReturn clear_error_on_return;
EnginePointer engine = LoadEngineById(id, errors);
if (!engine)
return false;
if (!ENGINE_set_default(engine.get(), flags)) {
if (errors != nullptr)
errors->Capture();
return false;
}
return true;
}
void SetEngine(const FunctionCallbackInfo<Value>& args) { void SetEngine(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args); Environment* env = Environment::GetCurrent(args);
CHECK(args.Length() >= 2 && args[0]->IsString());
uint32_t flags;
if (!args[1]->Uint32Value(env->context()).To(&flags)) return;
const node::Utf8Value engine_id(env->isolate(), args[0]);
if (UNLIKELY(env->permission()->enabled())) { if (UNLIKELY(env->permission()->enabled())) {
return THROW_ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED( return THROW_ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED(
env, env,
@ -646,7 +612,16 @@ void SetEngine(const FunctionCallbackInfo<Value>& args) {
"experimental permission model is enabled"); "experimental permission model is enabled");
} }
args.GetReturnValue().Set(SetEngine(*engine_id, flags)); CHECK(args.Length() >= 2 && args[0]->IsString());
uint32_t flags;
if (!args[1]->Uint32Value(env->context()).To(&flags)) return;
const node::Utf8Value engine_id(env->isolate(), args[0]);
// If the engine name is not known, calling setAsDefault on the
// empty engine pointer will be non-op that always returns false.
args.GetReturnValue().Set(
ncrypto::EnginePointer::getEngineByName(engine_id.ToStringView())
.setAsDefault(flags));
} }
#endif // !OPENSSL_NO_ENGINE #endif // !OPENSSL_NO_ENGINE
@ -655,8 +630,8 @@ MaybeLocal<Value> EncodeBignum(
const BIGNUM* bn, const BIGNUM* bn,
int size, int size,
Local<Value>* error) { Local<Value>* error) {
std::vector<uint8_t> buf(size); std::vector<uint8_t> buf = ncrypto::BignumPointer::encodePadded(bn, size);
CHECK_EQ(BN_bn2binpad(bn, buf.data(), size), size); CHECK_EQ(buf.size(), static_cast<size_t>(size));
return StringBytes::Encode( return StringBytes::Encode(
env->isolate(), env->isolate(),
reinterpret_cast<const char*>(buf.data()), reinterpret_cast<const char*>(buf.data()),

View File

@ -12,6 +12,8 @@
#include "util.h" #include "util.h"
#include "v8.h" #include "v8.h"
#include "ncrypto.h"
#include <openssl/dsa.h> #include <openssl/dsa.h>
#include <openssl/ec.h> #include <openssl/ec.h>
#include <openssl/err.h> #include <openssl/err.h>
@ -20,9 +22,7 @@
#include <openssl/kdf.h> #include <openssl/kdf.h>
#include <openssl/rsa.h> #include <openssl/rsa.h>
#include <openssl/ssl.h> #include <openssl/ssl.h>
#ifndef OPENSSL_NO_ENGINE
# include <openssl/engine.h>
#endif // !OPENSSL_NO_ENGINE
// The FIPS-related functions are only available // The FIPS-related functions are only available
// when the OpenSSL itself was compiled with FIPS support. // when the OpenSSL itself was compiled with FIPS support.
#if defined(OPENSSL_FIPS) && OPENSSL_VERSION_MAJOR < 3 #if defined(OPENSSL_FIPS) && OPENSSL_VERSION_MAJOR < 3
@ -54,39 +54,33 @@ constexpr size_t kSizeOf_EVP_PKEY_CTX = 80;
constexpr size_t kSizeOf_HMAC_CTX = 32; constexpr size_t kSizeOf_HMAC_CTX = 32;
// Define smart pointers for the most commonly used OpenSSL types: // Define smart pointers for the most commonly used OpenSSL types:
using X509Pointer = DeleteFnPtr<X509, X509_free>; using X509Pointer = ncrypto::X509Pointer;
using BIOPointer = DeleteFnPtr<BIO, BIO_free_all>; using BIOPointer = ncrypto::BIOPointer;
using SSLCtxPointer = DeleteFnPtr<SSL_CTX, SSL_CTX_free>; using SSLCtxPointer = ncrypto::SSLCtxPointer;
using SSLSessionPointer = DeleteFnPtr<SSL_SESSION, SSL_SESSION_free>; using SSLSessionPointer = ncrypto::SSLSessionPointer;
using SSLPointer = DeleteFnPtr<SSL, SSL_free>; using SSLPointer = ncrypto::SSLPointer;
using PKCS8Pointer = DeleteFnPtr<PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO_free>; using PKCS8Pointer = ncrypto::PKCS8Pointer;
using EVPKeyPointer = DeleteFnPtr<EVP_PKEY, EVP_PKEY_free>; using EVPKeyPointer = ncrypto::EVPKeyPointer;
using EVPKeyCtxPointer = DeleteFnPtr<EVP_PKEY_CTX, EVP_PKEY_CTX_free>; using EVPKeyCtxPointer = ncrypto::EVPKeyCtxPointer;
using EVPMDCtxPointer = DeleteFnPtr<EVP_MD_CTX, EVP_MD_CTX_free>; using EVPMDCtxPointer = ncrypto::EVPMDCtxPointer;
using RSAPointer = DeleteFnPtr<RSA, RSA_free>; using RSAPointer = ncrypto::RSAPointer;
using ECPointer = DeleteFnPtr<EC_KEY, EC_KEY_free>; using ECPointer = ncrypto::ECPointer;
using BignumPointer = DeleteFnPtr<BIGNUM, BN_clear_free>; using BignumPointer = ncrypto::BignumPointer;
using BignumCtxPointer = DeleteFnPtr<BN_CTX, BN_CTX_free>; using BignumCtxPointer = ncrypto::BignumCtxPointer;
using NetscapeSPKIPointer = DeleteFnPtr<NETSCAPE_SPKI, NETSCAPE_SPKI_free>; using NetscapeSPKIPointer = ncrypto::NetscapeSPKIPointer;
using ECGroupPointer = DeleteFnPtr<EC_GROUP, EC_GROUP_free>; using ECGroupPointer = ncrypto::ECGroupPointer;
using ECPointPointer = DeleteFnPtr<EC_POINT, EC_POINT_free>; using ECPointPointer = ncrypto::ECPointPointer;
using ECKeyPointer = DeleteFnPtr<EC_KEY, EC_KEY_free>; using ECKeyPointer = ncrypto::ECKeyPointer;
using DHPointer = DeleteFnPtr<DH, DH_free>; using DHPointer = ncrypto::DHPointer;
using ECDSASigPointer = DeleteFnPtr<ECDSA_SIG, ECDSA_SIG_free>; using ECDSASigPointer = ncrypto::ECDSASigPointer;
using HMACCtxPointer = DeleteFnPtr<HMAC_CTX, HMAC_CTX_free>; using HMACCtxPointer = ncrypto::HMACCtxPointer;
using CipherCtxPointer = DeleteFnPtr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free>; using CipherCtxPointer = ncrypto::CipherCtxPointer;
using RsaPointer = DeleteFnPtr<RSA, RSA_free>; using RsaPointer = ncrypto::RSAPointer;
using DsaPointer = DeleteFnPtr<DSA, DSA_free>; using DsaPointer = ncrypto::DSAPointer;
using DsaSigPointer = DeleteFnPtr<DSA_SIG, DSA_SIG_free>; using DsaSigPointer = ncrypto::DSASigPointer;
// Our custom implementation of the certificate verify callback using ClearErrorOnReturn = ncrypto::ClearErrorOnReturn;
// used when establishing a TLS handshake. Because we cannot perform using MarkPopErrorOnReturn = ncrypto::MarkPopErrorOnReturn;
// I/O quickly enough with X509_STORE_CTX_ APIs in this callback,
// we ignore preverify_ok errors here and let the handshake continue.
// In other words, this VerifyCallback is a non-op. It is imperative
// that the user user Connection::VerifyError after the `secure`
// callback has been made.
extern int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx);
bool ProcessFipsOptions(); bool ProcessFipsOptions();
@ -97,21 +91,6 @@ void InitCrypto(v8::Local<v8::Object> target);
extern void UseExtraCaCerts(const std::string& file); extern void UseExtraCaCerts(const std::string& file);
// Forcibly clear OpenSSL's error stack on return. This stops stale errors
// from popping up later in the lifecycle of crypto operations where they
// would cause spurious failures. It's a rather blunt method, though.
// ERR_clear_error() isn't necessarily cheap either.
struct ClearErrorOnReturn {
~ClearErrorOnReturn() { ERR_clear_error(); }
};
// Pop errors from OpenSSL's error stack that were added
// between when this was constructed and destructed.
struct MarkPopErrorOnReturn {
MarkPopErrorOnReturn() { ERR_set_mark(); }
~MarkPopErrorOnReturn() { ERR_pop_to_mark(); }
};
struct CSPRNGResult { struct CSPRNGResult {
const bool ok; const bool ok;
MUST_USE_RESULT bool is_ok() const { return ok; } MUST_USE_RESULT bool is_ok() const { return ok; }
@ -165,6 +144,21 @@ enum class NodeCryptoError {
#undef V #undef V
}; };
template <typename... Args>
std::string getNodeCryptoErrorString(const NodeCryptoError error,
Args&&... args) {
const char* error_string = nullptr;
switch (error) {
#define V(CODE, DESCRIPTION) \
case NodeCryptoError::CODE: \
error_string = DESCRIPTION; \
break;
NODE_CRYPTO_ERROR_CODES_MAP(V)
#undef V
}
return SPrintF(error_string, std::forward<Args>(args)...);
}
// Utility struct used to harvest error information from openssl's error stack // Utility struct used to harvest error information from openssl's error stack
struct CryptoErrorStore final : public MemoryRetainer { struct CryptoErrorStore final : public MemoryRetainer {
public: public:
@ -200,6 +194,9 @@ void CryptoErrorStore::Insert(const NodeCryptoError error, Args&&... args) {
std::forward<Args>(args)...)); std::forward<Args>(args)...));
} }
v8::MaybeLocal<v8::Value> cryptoErrorListToException(
Environment* env, const ncrypto::CryptoErrorList& errors);
template <typename T> template <typename T>
T* MallocOpenSSL(size_t count) { T* MallocOpenSSL(size_t count) {
void* mem = OPENSSL_malloc(MultiplyWithOverflowCheck(count, sizeof(T))); void* mem = OPENSSL_malloc(MultiplyWithOverflowCheck(count, sizeof(T)));
@ -552,72 +549,6 @@ void ThrowCryptoError(Environment* env,
unsigned long err, // NOLINT(runtime/int) unsigned long err, // NOLINT(runtime/int)
const char* message = nullptr); const char* message = nullptr);
#ifndef OPENSSL_NO_ENGINE
struct EnginePointer {
ENGINE* engine = nullptr;
bool finish_on_exit = false;
inline EnginePointer() = default;
inline explicit EnginePointer(ENGINE* engine_, bool finish_on_exit_ = false)
: engine(engine_),
finish_on_exit(finish_on_exit_) {}
inline EnginePointer(EnginePointer&& other) noexcept
: engine(other.engine),
finish_on_exit(other.finish_on_exit) {
other.release();
}
inline ~EnginePointer() { reset(); }
inline EnginePointer& operator=(EnginePointer&& other) noexcept {
if (this == &other) return *this;
this->~EnginePointer();
return *new (this) EnginePointer(std::move(other));
}
inline operator bool() const { return engine != nullptr; }
inline ENGINE* get() { return engine; }
inline void reset(ENGINE* engine_ = nullptr, bool finish_on_exit_ = false) {
if (engine != nullptr) {
if (finish_on_exit) {
// This also does the equivalent of ENGINE_free.
CHECK_EQ(ENGINE_finish(engine), 1);
} else {
CHECK_EQ(ENGINE_free(engine), 1);
}
}
engine = engine_;
finish_on_exit = finish_on_exit_;
}
inline ENGINE* release() {
ENGINE* ret = engine;
engine = nullptr;
finish_on_exit = false;
return ret;
}
};
EnginePointer LoadEngineById(const char* id, CryptoErrorStore* errors);
bool SetEngine(
const char* id,
uint32_t flags,
CryptoErrorStore* errors = nullptr);
void SetEngine(const v8::FunctionCallbackInfo<v8::Value>& args);
#endif // !OPENSSL_NO_ENGINE
void GetFipsCrypto(const v8::FunctionCallbackInfo<v8::Value>& args);
void SetFipsCrypto(const v8::FunctionCallbackInfo<v8::Value>& args);
void TestFipsCrypto(const v8::FunctionCallbackInfo<v8::Value>& args);
class CipherPushContext { class CipherPushContext {
public: public:
inline explicit CipherPushContext(Environment* env) : env_(env) {} inline explicit CipherPushContext(Environment* env) : env_(env) {}

View File

@ -25,12 +25,13 @@
#if HAVE_OPENSSL #if HAVE_OPENSSL
#include <openssl/crypto.h> #include <openssl/crypto.h>
#include "ncrypto.h"
#if NODE_OPENSSL_HAS_QUIC #if NODE_OPENSSL_HAS_QUIC
#include <openssl/quic.h> #include <openssl/quic.h>
#endif #endif
#endif // HAVE_OPENSSL #endif // HAVE_OPENSSL
#ifdef OPENSSL_INFO_QUIC #ifdef NODE_OPENSSL_HAS_QUIC
#include <ngtcp2/version.h> #include <ngtcp2/version.h>
#include <nghttp3/version.h> #include <nghttp3/version.h>
#endif #endif
@ -118,6 +119,7 @@ Metadata::Versions::Versions() {
#if HAVE_OPENSSL #if HAVE_OPENSSL
openssl = GetOpenSSLVersion(); openssl = GetOpenSSLVersion();
ncrypto = NCRYPTO_VERSION;
#endif #endif
#ifdef NODE_HAVE_I18N_SUPPORT #ifdef NODE_HAVE_I18N_SUPPORT
@ -125,7 +127,7 @@ Metadata::Versions::Versions() {
unicode = U_UNICODE_VERSION; unicode = U_UNICODE_VERSION;
#endif // NODE_HAVE_I18N_SUPPORT #endif // NODE_HAVE_I18N_SUPPORT
#ifdef OPENSSL_INFO_QUIC #ifdef NODE_OPENSSL_HAS_QUIC
ngtcp2 = NGTCP2_VERSION; ngtcp2 = NGTCP2_VERSION;
nghttp3 = NGHTTP3_VERSION; nghttp3 = NGHTTP3_VERSION;
#endif #endif

View File

@ -55,7 +55,7 @@ namespace node {
V(cjs_module_lexer) V(cjs_module_lexer)
#if HAVE_OPENSSL #if HAVE_OPENSSL
#define NODE_VERSIONS_KEY_CRYPTO(V) V(openssl) #define NODE_VERSIONS_KEY_CRYPTO(V) V(openssl) V(ncrypto)
#else #else
#define NODE_VERSIONS_KEY_CRYPTO(V) #define NODE_VERSIONS_KEY_CRYPTO(V)
#endif #endif

View File

@ -1,3 +1,4 @@
// Flags: --expose-internals
'use strict'; 'use strict';
const common = require('../common'); const common = require('../common');

View File

@ -34,6 +34,7 @@ if (hasUndici) {
if (common.hasCrypto) { if (common.hasCrypto) {
expected_keys.push('openssl'); expected_keys.push('openssl');
expected_keys.push('ncrypto');
} }
if (common.hasQuic) { if (common.hasQuic) {
@ -78,6 +79,7 @@ assert.match(process.versions.modules, /^\d+$/);
assert.match(process.versions.cjs_module_lexer, commonTemplate); assert.match(process.versions.cjs_module_lexer, commonTemplate);
if (common.hasCrypto) { if (common.hasCrypto) {
assert.match(process.versions.ncrypto, commonTemplate);
if (process.config.variables.node_shared_openssl) { if (process.config.variables.node_shared_openssl) {
assert.ok(process.versions.openssl); assert.ok(process.versions.openssl);
} else { } else {

View File

@ -178,6 +178,7 @@ template("node_gn_build") {
deps += [ "//third_party/icu" ] deps += [ "//third_party/icu" ]
} }
if (node_use_openssl) { if (node_use_openssl) {
deps += [ "deps/ncrypto" ]
public_deps += [ "deps/openssl" ] public_deps += [ "deps/openssl" ]
sources += gypi_values.node_crypto_sources sources += gypi_values.node_crypto_sources
} }
@ -339,6 +340,7 @@ template("node_gn_build") {
sources = gypi_values.node_cctest_sources sources = gypi_values.node_cctest_sources
if (node_use_openssl) { if (node_use_openssl) {
deps += [ "deps/ncrypto" ]
sources += gypi_values.node_cctest_openssl_sources sources += gypi_values.node_cctest_openssl_sources
} }
if (node_enable_inspector) { if (node_enable_inspector) {