diff --git a/Makefile b/Makefile index ab62b2ca7a8..861b3410fb5 100644 --- a/Makefile +++ b/Makefile @@ -175,7 +175,8 @@ with-code-cache test-code-cache: out/Makefile: config.gypi common.gypi node.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 \ - 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 $(PYTHON) tools/gyp_node.py -f make diff --git a/deps/ncrypto/BUILD.gn b/deps/ncrypto/BUILD.gn new file mode 100644 index 00000000000..78e3702888a --- /dev/null +++ b/deps/ncrypto/BUILD.gn @@ -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") { +} diff --git a/deps/ncrypto/README.md b/deps/ncrypto/README.md new file mode 100644 index 00000000000..8881155719a --- /dev/null +++ b/deps/ncrypto/README.md @@ -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. diff --git a/deps/ncrypto/engine.cc b/deps/ncrypto/engine.cc new file mode 100644 index 00000000000..de708d1c13c --- /dev/null +++ b/deps/ncrypto/engine.cc @@ -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 diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc new file mode 100644 index 00000000000..b3e6a57edc1 --- /dev/null +++ b/deps/ncrypto/ncrypto.cc @@ -0,0 +1,223 @@ +#include "ncrypto.h" +#include +#include +#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 CryptoErrorList::pop_back() { + if (errors_.empty()) return std::nullopt; + std::string error = errors_.back(); + errors_.pop_back(); + return error; +} + +std::optional 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 BignumPointer::encode() { + return encodePadded(bn_.get(), byteLength()); +} + +std::vector BignumPointer::encodePadded(size_t size) { + return encodePadded(bn_.get(), size); +} + +std::vector BignumPointer::encode(const BIGNUM* bn) { + return encodePadded(bn, bn != nullptr ? BN_num_bytes(bn) : 0); +} + +std::vector BignumPointer::encodePadded(const BIGNUM* bn, size_t s) { + if (bn == nullptr) return std::vector(0); + size_t size = std::max(s, static_cast(BN_num_bytes(bn))); + std::vector 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(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(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(u); + if (passphrase != nullptr) { + size_t buflen = static_cast(size); + size_t len = passphrase->len; + if (buflen < len) + return -1; + memcpy(buf, reinterpret_cast(passphrase->data), len); + return len; + } + + return -1; +} + +} // namespace ncrypto diff --git a/deps/ncrypto/ncrypto.gyp b/deps/ncrypto/ncrypto.gyp new file mode 100644 index 00000000000..cf9b7c6cdb6 --- /dev/null +++ b/deps/ncrypto/ncrypto.gyp @@ -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' + ] + }], + ] + }, + ] +} diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h new file mode 100644 index 00000000000..6fb66fe5234 --- /dev/null +++ b/deps/ncrypto/ncrypto.h @@ -0,0 +1,298 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "openssl/bn.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef OPENSSL_NO_ENGINE +# include +#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 +#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 pop_back(); + std::optional pop_front(); + +private: + std::list 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 +struct FunctionDeleter { + void operator()(T* pointer) const { function(pointer); } + typedef std::unique_ptr Pointer; +}; + +template +using DeleteFnPtr = typename FunctionDeleter::Pointer; + +using BignumCtxPointer = DeleteFnPtr; +using BIOPointer = DeleteFnPtr; +using CipherCtxPointer = DeleteFnPtr; +using DHPointer = DeleteFnPtr; +using DSAPointer = DeleteFnPtr; +using DSASigPointer = DeleteFnPtr; +using ECDSASigPointer = DeleteFnPtr; +using ECPointer = DeleteFnPtr; +using ECGroupPointer = DeleteFnPtr; +using ECKeyPointer = DeleteFnPtr; +using ECPointPointer = DeleteFnPtr; +using EVPKeyCtxPointer = DeleteFnPtr; +using EVPKeyPointer = DeleteFnPtr; +using EVPMDCtxPointer = DeleteFnPtr; +using HMACCtxPointer = DeleteFnPtr; +using NetscapeSPKIPointer = DeleteFnPtr; +using PKCS8Pointer = DeleteFnPtr; +using RSAPointer = DeleteFnPtr; +using SSLCtxPointer = DeleteFnPtr; +using SSLPointer = DeleteFnPtr; +using SSLSessionPointer = DeleteFnPtr; +using X509Pointer = DeleteFnPtr; + +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 encode(); + std::vector encodePadded(size_t size); + + static std::vector encode(const BIGNUM* bn); + static std::vector encodePadded(const BIGNUM* bn, size_t size); + + private: + DeleteFnPtr 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 diff --git a/node.gyp b/node.gyp index 9617596760a..faf77c0f615 100644 --- a/node.gyp +++ b/node.gyp @@ -940,6 +940,9 @@ '<@(node_crypto_sources)', '<@(node_quic_sources)', ], + 'dependencies': [ + 'deps/ncrypto/ncrypto.gyp:ncrypto', + ], }], [ 'OS in "linux freebsd mac solaris" and ' 'target_arch=="x64" and ' @@ -1201,6 +1204,9 @@ 'defines': [ 'HAVE_OPENSSL=1', ], + 'dependencies': [ + 'deps/ncrypto/ncrypto.gyp:ncrypto', + ], 'sources': [ '<@(node_cctest_openssl_sources)' ], }], ['v8_enable_inspector==1', { @@ -1394,6 +1400,9 @@ 'defines': [ 'NODE_MKSNAPSHOT_USE_ARRAY_LITERALS=1' ], }], [ 'node_use_openssl=="true"', { + 'dependencies': [ + 'deps/ncrypto/ncrypto.gyp:ncrypto', + ], 'defines': [ 'HAVE_OPENSSL=1', ], diff --git a/src/crypto/crypto_context.cc b/src/crypto/crypto_context.cc index 7bf10371799..8206e941dc1 100644 --- a/src/crypto/crypto_context.cc +++ b/src/crypto/crypto_context.cc @@ -1,10 +1,11 @@ #include "crypto/crypto_context.h" +#include "base_object-inl.h" #include "crypto/crypto_bio.h" #include "crypto/crypto_common.h" #include "crypto/crypto_util.h" -#include "base_object-inl.h" #include "env-inl.h" #include "memory_tracker-inl.h" +#include "ncrypto.h" #include "node.h" #include "node_buffer.h" #include "node_options.h" @@ -655,26 +656,28 @@ void SecureContext::SetEngineKey(const FunctionCallbackInfo& args) { "experimental permission model is enabled"); } - CryptoErrorStore errors; + ncrypto::CryptoErrorList errors; Utf8Value engine_id(env->isolate(), args[1]); - EnginePointer engine = LoadEngineById(*engine_id, &errors); + auto engine = ncrypto::EnginePointer::getEngineByName( + engine_id.ToStringView(), &errors); if (!engine) { Local 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); return; } - if (!ENGINE_init(engine.get())) { + if (!engine.init(true /* finish on exit*/)) { return THROW_ERR_CRYPTO_OPERATION_FAILED( env, "Failure to initialize engine"); } - engine.finish_on_exit = true; - Utf8Value key_name(env->isolate(), args[0]); - EVPKeyPointer key(ENGINE_load_private_key(engine.get(), *key_name, - nullptr, nullptr)); + auto key = engine.loadPrivateKey(key_name.ToStringView()); if (!key) return ThrowCryptoError(env, ERR_get_error(), "ENGINE_load_private_key"); @@ -1143,12 +1146,17 @@ void SecureContext::SetClientCertEngine( "experimental permission model is enabled"); } - CryptoErrorStore errors; + ncrypto::CryptoErrorList errors; 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) { Local 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); return; } diff --git a/src/crypto/crypto_context.h b/src/crypto/crypto_context.h index 108ee0e2b2c..5d98e20c934 100644 --- a/src/crypto/crypto_context.h +++ b/src/crypto/crypto_context.h @@ -147,7 +147,7 @@ class SecureContext final : public BaseObject { X509Pointer issuer_; #ifndef OPENSSL_NO_ENGINE bool client_cert_engine_provided_ = false; - EnginePointer private_key_engine_; + ncrypto::EnginePointer private_key_engine_; #endif // !OPENSSL_NO_ENGINE unsigned char ticket_key_name_[16]; diff --git a/src/crypto/crypto_tls.cc b/src/crypto/crypto_tls.cc index 9fb567f89c1..0ad6fa28542 100644 --- a/src/crypto/crypto_tls.cc +++ b/src/crypto/crypto_tls.cc @@ -64,6 +64,26 @@ using v8::Value; namespace crypto { 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* s, const unsigned char* key, diff --git a/src/crypto/crypto_util.cc b/src/crypto/crypto_util.cc index 990638ec399..9c8f6d6f447 100644 --- a/src/crypto/crypto_util.cc +++ b/src/crypto/crypto_util.cc @@ -4,6 +4,7 @@ #include "crypto/crypto_keys.h" #include "env-inl.h" #include "memory_tracker-inl.h" +#include "ncrypto.h" #include "node_buffer.h" #include "node_options-inl.h" #include "string_bytes.h" @@ -11,6 +12,10 @@ #include "util-inl.h" #include "v8.h" +#ifndef OPENSSL_NO_ENGINE +#include +#endif // !OPENSSL_NO_ENGINE + #include "math.h" #if OPENSSL_VERSION_MAJOR >= 3 @@ -43,22 +48,6 @@ using v8::Uint8Array; using v8::Value; 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) { unsigned char* buf = static_cast(buffer); @@ -206,21 +195,14 @@ void InitCryptoOnce() { sk_SSL_COMP_zero(SSL_COMP_get_compression_methods()); #ifndef OPENSSL_NO_ENGINE - ERR_load_ENGINE_strings(); - ENGINE_load_builtin_engines(); + ncrypto::EnginePointer::initEnginesOnce(); #endif // !OPENSSL_NO_ENGINE } void GetFipsCrypto(const FunctionCallbackInfo& args) { Mutex::ScopedLock lock(per_process::cli_options_mutex); Mutex::ScopedLock fips_lock(fips_mutex); - -#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 + args.GetReturnValue().Set(ncrypto::isFipsEnabled() ? 1 : 0); } void SetFipsCrypto(const FunctionCallbackInfo& args) { @@ -232,43 +214,19 @@ void SetFipsCrypto(const FunctionCallbackInfo& args) { CHECK(env->owns_process_state()); bool enable = args[0]->BooleanValue(env->isolate()); -#if OPENSSL_VERSION_MAJOR >= 3 - if (enable == EVP_default_properties_is_fips_enabled(nullptr)) -#else - if (static_cast(enable) == FIPS_mode()) -#endif - 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); + ncrypto::CryptoErrorList errors; + if (!ncrypto::setFipsEnabled(enable, &errors)) { + Local exception; + if (cryptoErrorListToException(env, errors).ToLocal(&exception)) { + env->isolate()->ThrowException(exception); + } } } void TestFipsCrypto(const v8::FunctionCallbackInfo& args) { Mutex::ScopedLock lock(per_process::cli_options_mutex); Mutex::ScopedLock fips_lock(fips_mutex); - -#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); + args.GetReturnValue().Set(ncrypto::testFipsEnabled() ? 1 : 0); } void CryptoErrorStore::Capture() { @@ -285,6 +243,60 @@ bool CryptoErrorStore::Empty() const { return errors_.empty(); } +MaybeLocal 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 message; + if (!String::NewFromUtf8( + env->isolate(), last.data(), NewStringType::kNormal, last.size()) + .ToLocal(&message)) { + return {}; + } + + Local exception = Exception::Error(message); + CHECK(!exception.IsEmpty()); + + if (errors.size() > 1) { + CHECK(exception->IsObject()); + Local exception_obj = exception.As(); + std::vector> 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 error; + if (!ToV8Value(env->context(), *current).ToLocal(&error)) { + return {}; + } + stack.push_back(error); + ++current; + } + + Local 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 CryptoErrorStore::ToException( Environment* env, Local exception_string) const { @@ -591,54 +603,8 @@ void ThrowCryptoError(Environment* env, } #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& 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())) { return THROW_ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED( env, @@ -646,7 +612,16 @@ void SetEngine(const FunctionCallbackInfo& args) { "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 @@ -655,8 +630,8 @@ MaybeLocal EncodeBignum( const BIGNUM* bn, int size, Local* error) { - std::vector buf(size); - CHECK_EQ(BN_bn2binpad(bn, buf.data(), size), size); + std::vector buf = ncrypto::BignumPointer::encodePadded(bn, size); + CHECK_EQ(buf.size(), static_cast(size)); return StringBytes::Encode( env->isolate(), reinterpret_cast(buf.data()), diff --git a/src/crypto/crypto_util.h b/src/crypto/crypto_util.h index 4ba26101469..4d97af48edf 100644 --- a/src/crypto/crypto_util.h +++ b/src/crypto/crypto_util.h @@ -12,6 +12,8 @@ #include "util.h" #include "v8.h" +#include "ncrypto.h" + #include #include #include @@ -20,9 +22,7 @@ #include #include #include -#ifndef OPENSSL_NO_ENGINE -# include -#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 @@ -54,39 +54,33 @@ constexpr size_t kSizeOf_EVP_PKEY_CTX = 80; constexpr size_t kSizeOf_HMAC_CTX = 32; // Define smart pointers for the most commonly used OpenSSL types: -using X509Pointer = DeleteFnPtr; -using BIOPointer = DeleteFnPtr; -using SSLCtxPointer = DeleteFnPtr; -using SSLSessionPointer = DeleteFnPtr; -using SSLPointer = DeleteFnPtr; -using PKCS8Pointer = DeleteFnPtr; -using EVPKeyPointer = DeleteFnPtr; -using EVPKeyCtxPointer = DeleteFnPtr; -using EVPMDCtxPointer = DeleteFnPtr; -using RSAPointer = DeleteFnPtr; -using ECPointer = DeleteFnPtr; -using BignumPointer = DeleteFnPtr; -using BignumCtxPointer = DeleteFnPtr; -using NetscapeSPKIPointer = DeleteFnPtr; -using ECGroupPointer = DeleteFnPtr; -using ECPointPointer = DeleteFnPtr; -using ECKeyPointer = DeleteFnPtr; -using DHPointer = DeleteFnPtr; -using ECDSASigPointer = DeleteFnPtr; -using HMACCtxPointer = DeleteFnPtr; -using CipherCtxPointer = DeleteFnPtr; -using RsaPointer = DeleteFnPtr; -using DsaPointer = DeleteFnPtr; -using DsaSigPointer = DeleteFnPtr; +using X509Pointer = ncrypto::X509Pointer; +using BIOPointer = ncrypto::BIOPointer; +using SSLCtxPointer = ncrypto::SSLCtxPointer; +using SSLSessionPointer = ncrypto::SSLSessionPointer; +using SSLPointer = ncrypto::SSLPointer; +using PKCS8Pointer = ncrypto::PKCS8Pointer; +using EVPKeyPointer = ncrypto::EVPKeyPointer; +using EVPKeyCtxPointer = ncrypto::EVPKeyCtxPointer; +using EVPMDCtxPointer = ncrypto::EVPMDCtxPointer; +using RSAPointer = ncrypto::RSAPointer; +using ECPointer = ncrypto::ECPointer; +using BignumPointer = ncrypto::BignumPointer; +using BignumCtxPointer = ncrypto::BignumCtxPointer; +using NetscapeSPKIPointer = ncrypto::NetscapeSPKIPointer; +using ECGroupPointer = ncrypto::ECGroupPointer; +using ECPointPointer = ncrypto::ECPointPointer; +using ECKeyPointer = ncrypto::ECKeyPointer; +using DHPointer = ncrypto::DHPointer; +using ECDSASigPointer = ncrypto::ECDSASigPointer; +using HMACCtxPointer = ncrypto::HMACCtxPointer; +using CipherCtxPointer = ncrypto::CipherCtxPointer; +using RsaPointer = ncrypto::RSAPointer; +using DsaPointer = ncrypto::DSAPointer; +using DsaSigPointer = ncrypto::DSASigPointer; -// 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. -extern int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx); +using ClearErrorOnReturn = ncrypto::ClearErrorOnReturn; +using MarkPopErrorOnReturn = ncrypto::MarkPopErrorOnReturn; bool ProcessFipsOptions(); @@ -97,21 +91,6 @@ void InitCrypto(v8::Local target); 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 { const bool ok; MUST_USE_RESULT bool is_ok() const { return ok; } @@ -165,6 +144,21 @@ enum class NodeCryptoError { #undef V }; +template +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)...); +} + // Utility struct used to harvest error information from openssl's error stack struct CryptoErrorStore final : public MemoryRetainer { public: @@ -200,6 +194,9 @@ void CryptoErrorStore::Insert(const NodeCryptoError error, Args&&... args) { std::forward(args)...)); } +v8::MaybeLocal cryptoErrorListToException( + Environment* env, const ncrypto::CryptoErrorList& errors); + template T* MallocOpenSSL(size_t count) { void* mem = OPENSSL_malloc(MultiplyWithOverflowCheck(count, sizeof(T))); @@ -552,72 +549,6 @@ void ThrowCryptoError(Environment* env, unsigned long err, // NOLINT(runtime/int) 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& args); -#endif // !OPENSSL_NO_ENGINE - -void GetFipsCrypto(const v8::FunctionCallbackInfo& args); - -void SetFipsCrypto(const v8::FunctionCallbackInfo& args); - -void TestFipsCrypto(const v8::FunctionCallbackInfo& args); - class CipherPushContext { public: inline explicit CipherPushContext(Environment* env) : env_(env) {} diff --git a/src/node_metadata.cc b/src/node_metadata.cc index 937e415eb55..4627629484f 100644 --- a/src/node_metadata.cc +++ b/src/node_metadata.cc @@ -25,12 +25,13 @@ #if HAVE_OPENSSL #include +#include "ncrypto.h" #if NODE_OPENSSL_HAS_QUIC #include #endif #endif // HAVE_OPENSSL -#ifdef OPENSSL_INFO_QUIC +#ifdef NODE_OPENSSL_HAS_QUIC #include #include #endif @@ -118,6 +119,7 @@ Metadata::Versions::Versions() { #if HAVE_OPENSSL openssl = GetOpenSSLVersion(); + ncrypto = NCRYPTO_VERSION; #endif #ifdef NODE_HAVE_I18N_SUPPORT @@ -125,7 +127,7 @@ Metadata::Versions::Versions() { unicode = U_UNICODE_VERSION; #endif // NODE_HAVE_I18N_SUPPORT -#ifdef OPENSSL_INFO_QUIC +#ifdef NODE_OPENSSL_HAS_QUIC ngtcp2 = NGTCP2_VERSION; nghttp3 = NGHTTP3_VERSION; #endif diff --git a/src/node_metadata.h b/src/node_metadata.h index 90c7dbf22c8..87dcfd08f51 100644 --- a/src/node_metadata.h +++ b/src/node_metadata.h @@ -55,7 +55,7 @@ namespace node { V(cjs_module_lexer) #if HAVE_OPENSSL -#define NODE_VERSIONS_KEY_CRYPTO(V) V(openssl) +#define NODE_VERSIONS_KEY_CRYPTO(V) V(openssl) V(ncrypto) #else #define NODE_VERSIONS_KEY_CRYPTO(V) #endif diff --git a/test/parallel/test-crypto-prime.js b/test/parallel/test-crypto-prime.js index f0ec4efaf61..209c8251f78 100644 --- a/test/parallel/test-crypto-prime.js +++ b/test/parallel/test-crypto-prime.js @@ -1,3 +1,4 @@ +// Flags: --expose-internals 'use strict'; const common = require('../common'); diff --git a/test/parallel/test-process-versions.js b/test/parallel/test-process-versions.js index bab483a7e26..108ea9d67d7 100644 --- a/test/parallel/test-process-versions.js +++ b/test/parallel/test-process-versions.js @@ -34,6 +34,7 @@ if (hasUndici) { if (common.hasCrypto) { expected_keys.push('openssl'); + expected_keys.push('ncrypto'); } if (common.hasQuic) { @@ -78,6 +79,7 @@ assert.match(process.versions.modules, /^\d+$/); assert.match(process.versions.cjs_module_lexer, commonTemplate); if (common.hasCrypto) { + assert.match(process.versions.ncrypto, commonTemplate); if (process.config.variables.node_shared_openssl) { assert.ok(process.versions.openssl); } else { diff --git a/unofficial.gni b/unofficial.gni index 006ba413c68..c3b311e4a7f 100644 --- a/unofficial.gni +++ b/unofficial.gni @@ -178,6 +178,7 @@ template("node_gn_build") { deps += [ "//third_party/icu" ] } if (node_use_openssl) { + deps += [ "deps/ncrypto" ] public_deps += [ "deps/openssl" ] sources += gypi_values.node_crypto_sources } @@ -339,6 +340,7 @@ template("node_gn_build") { sources = gypi_values.node_cctest_sources if (node_use_openssl) { + deps += [ "deps/ncrypto" ] sources += gypi_values.node_cctest_openssl_sources } if (node_enable_inspector) {