Skip to content

Commit

Permalink
deps: start working on ncrypto dep
Browse files Browse the repository at this point in the history
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: nodejs#53803
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
  • Loading branch information
jasnell authored and ehsankhfr committed Jul 18, 2024
1 parent 1b4fd2d commit c7b311d
Show file tree
Hide file tree
Showing 18 changed files with 849 additions and 239 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
14 changes: 14 additions & 0 deletions deps/ncrypto/BUILD.gn
Original file line number Diff line number Diff line change
@@ -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 changes: 5 additions & 0 deletions deps/ncrypto/README.md
Original file line number Diff line number Diff line change
@@ -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 changes: 92 additions & 0 deletions deps/ncrypto/engine.cc
Original file line number Diff line number Diff line change
@@ -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 changes: 223 additions & 0 deletions deps/ncrypto/ncrypto.cc
Original file line number Diff line number Diff line change
@@ -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 changes: 27 additions & 0 deletions deps/ncrypto/ncrypto.gyp
Original file line number Diff line number Diff line change
@@ -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'
]
}],
]
},
]
}
Loading

0 comments on commit c7b311d

Please sign in to comment.