blob: 3435e43b3baa0c188b984fa1905c6a8614c77de3 [file] [edit]
#include "crypto/crypto_util.h"
#include "async_wrap-inl.h"
#include "crypto/crypto_bio.h"
#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"
#include "threadpoolwork-inl.h"
#include "util-inl.h"
#include "v8.h"
#ifndef OPENSSL_NO_ENGINE
#include <openssl/engine.h>
#endif // !OPENSSL_NO_ENGINE
#include "math.h"
#if OPENSSL_VERSION_MAJOR >= 3
#include "openssl/provider.h"
#endif
namespace node {
using ncrypto::BignumPointer;
using ncrypto::BIOPointer;
using ncrypto::CryptoErrorList;
using ncrypto::DataPointer;
#ifndef OPENSSL_NO_ENGINE
using ncrypto::EnginePointer;
#endif // !OPENSSL_NO_ENGINE
using ncrypto::SSLPointer;
using v8::ArrayBuffer;
using v8::BackingStore;
using v8::BackingStoreInitializationMode;
using v8::BackingStoreOnFailureMode;
using v8::BigInt;
using v8::Context;
using v8::EscapableHandleScope;
using v8::Exception;
using v8::FunctionCallbackInfo;
using v8::HandleScope;
using v8::Isolate;
using v8::JustVoid;
using v8::Local;
using v8::LocalVector;
using v8::Maybe;
using v8::MaybeLocal;
using v8::NewStringType;
using v8::Nothing;
using v8::Object;
using v8::String;
using v8::TryCatch;
using v8::Uint32;
using v8::Uint8Array;
using v8::Value;
namespace crypto {
int PasswordCallback(char* buf, int size, int rwflag, void* u) {
const ByteSource* passphrase = *static_cast<const ByteSource**>(u);
if (passphrase != nullptr) {
size_t buflen = static_cast<size_t>(size);
size_t len = passphrase->size();
if (buflen < len)
return -1;
memcpy(buf, passphrase->data(), len);
return len;
}
return -1;
}
// 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 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) {
return 0;
}
bool ProcessFipsOptions() {
/* Override FIPS settings in configuration file, if needed. */
if (per_process::cli_options->enable_fips_crypto ||
per_process::cli_options->force_fips_crypto) {
#if OPENSSL_VERSION_MAJOR >= 3
if (!ncrypto::testFipsEnabled()) return false;
return ncrypto::setFipsEnabled(true, nullptr);
#else
// TODO(@jasnell): Remove this ifdef branch when openssl 1.1.1 is
// no longer supported.
if (FIPS_mode() == 0) return FIPS_mode_set(1);
#endif
}
return true;
}
bool InitCryptoOnce(Isolate* isolate) {
static uv_once_t init_once = UV_ONCE_INIT;
TryCatch try_catch{isolate};
uv_once(&init_once, InitCryptoOnce);
if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
try_catch.ReThrow();
return false;
}
return true;
}
// Protect accesses to FIPS state with a mutex. This should potentially
// be part of a larger mutex for global OpenSSL state.
static Mutex fips_mutex;
void InitCryptoOnce() {
Mutex::ScopedLock lock(per_process::cli_options_mutex);
Mutex::ScopedLock fips_lock(fips_mutex);
#ifndef OPENSSL_IS_BORINGSSL
OPENSSL_INIT_SETTINGS* settings = OPENSSL_INIT_new();
#if OPENSSL_VERSION_MAJOR < 3
// --openssl-config=...
if (!per_process::cli_options->openssl_config.empty()) {
const char* conf = per_process::cli_options->openssl_config.c_str();
OPENSSL_INIT_set_config_filename(settings, conf);
}
#endif
#if OPENSSL_VERSION_MAJOR >= 3
// --openssl-legacy-provider
if (per_process::cli_options->openssl_legacy_provider) {
OSSL_PROVIDER* legacy_provider = OSSL_PROVIDER_load(nullptr, "legacy");
if (legacy_provider == nullptr) {
fprintf(stderr, "Unable to load legacy provider.\n");
}
}
#endif
OPENSSL_init_ssl(0, settings);
OPENSSL_INIT_free(settings);
settings = nullptr;
#ifndef _WIN32
if (per_process::cli_options->secure_heap != 0) {
switch (DataPointer::TryInitSecureHeap(
per_process::cli_options->secure_heap,
per_process::cli_options->secure_heap_min)) {
case DataPointer::InitSecureHeapResult::FAILED:
fprintf(stderr, "Unable to initialize openssl secure heap.\n");
break;
case DataPointer::InitSecureHeapResult::UNABLE_TO_MEMORY_MAP:
// Not a fatal error but worthy of a warning.
fprintf(stderr, "Unable to memory map openssl secure heap.\n");
break;
case DataPointer::InitSecureHeapResult::OK:
// OK!
break;
}
}
#endif
#endif // OPENSSL_IS_BORINGSSL
// Turn off compression. Saves memory and protects against CRIME attacks.
// No-op with OPENSSL_NO_COMP builds of OpenSSL.
sk_SSL_COMP_zero(SSL_COMP_get_compression_methods());
#ifndef OPENSSL_NO_ENGINE
EnginePointer::initEnginesOnce();
#endif // !OPENSSL_NO_ENGINE
}
void GetFipsCrypto(const FunctionCallbackInfo<Value>& args) {
Mutex::ScopedLock lock(per_process::cli_options_mutex);
Mutex::ScopedLock fips_lock(fips_mutex);
args.GetReturnValue().Set(ncrypto::isFipsEnabled() ? 1 : 0);
}
void SetFipsCrypto(const FunctionCallbackInfo<Value>& args) {
Mutex::ScopedLock lock(per_process::cli_options_mutex);
Mutex::ScopedLock fips_lock(fips_mutex);
CHECK(!per_process::cli_options->force_fips_crypto);
Environment* env = Environment::GetCurrent(args);
CHECK(env->owns_process_state());
bool enable = args[0]->BooleanValue(env->isolate());
CryptoErrorList errors;
if (!ncrypto::setFipsEnabled(enable, &errors)) {
Local<Value> exception;
if (cryptoErrorListToException(env, errors).ToLocal(&exception)) {
env->isolate()->ThrowException(exception);
}
}
}
void TestFipsCrypto(const v8::FunctionCallbackInfo<v8::Value>& args) {
Mutex::ScopedLock lock(per_process::cli_options_mutex);
Mutex::ScopedLock fips_lock(fips_mutex);
args.GetReturnValue().Set(ncrypto::testFipsEnabled() ? 1 : 0);
}
void GetOpenSSLSecLevelCrypto(const FunctionCallbackInfo<Value>& args) {
ncrypto::ClearErrorOnReturn clear_error_on_return;
if (auto sec_level = SSLPointer::getSecurityLevel()) {
return args.GetReturnValue().Set(sec_level.value());
}
Environment* env = Environment::GetCurrent(args);
ThrowCryptoError(
env, clear_error_on_return.peekError(), "getOpenSSLSecLevel");
}
void CryptoErrorStore::Capture() {
errors_.clear();
while (const uint32_t err = ERR_get_error()) {
char buf[256];
ERR_error_string_n(err, buf, sizeof(buf));
errors_.emplace_back(buf);
}
std::ranges::reverse(errors_);
}
bool CryptoErrorStore::Empty() const {
return errors_.empty();
}
MaybeLocal<Value> cryptoErrorListToException(Environment* env,
const 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>();
LocalVector<Value> stack(env->isolate());
stack.reserve(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.data(), stack.size());
if (exception_obj
->Set(env->context(), env->openssl_error_stack(), stackArray)
.IsNothing()) {
return {};
}
}
return exception;
}
MaybeLocal<Value> CryptoErrorStore::ToException(
Environment* env,
Local<String> exception_string) const {
if (exception_string.IsEmpty()) {
CryptoErrorStore copy(*this);
if (copy.Empty()) {
// But possibly a bug...
copy.Insert(NodeCryptoError::OK);
}
// Use last element as the error message, everything else goes
// into the .opensslErrorStack property on the exception object.
const std::string& last_error_string = copy.errors_.back();
Local<Value> exception_string;
if (!ToV8Value(env->context(), last_error_string)
.ToLocal(&exception_string)) {
return MaybeLocal<Value>();
}
DCHECK(exception_string->IsString());
copy.errors_.pop_back();
return copy.ToException(env, exception_string.As<v8::String>());
}
Local<Value> exception_v = Exception::Error(exception_string);
CHECK(!exception_v.IsEmpty());
if (!Empty()) {
CHECK(exception_v->IsObject());
Local<Object> exception = exception_v.As<Object>();
Local<Value> stack;
if (!ToV8Value(env->context(), errors_).ToLocal(&stack) ||
exception->Set(env->context(), env->openssl_error_stack(), stack)
.IsNothing()) {
return MaybeLocal<Value>();
}
}
return exception_v;
}
ByteSource::ByteSource(ByteSource&& other) noexcept
: data_(other.data_),
allocated_data_(other.allocated_data_),
size_(other.size_) {
other.allocated_data_ = nullptr;
}
ByteSource::~ByteSource() {
OPENSSL_clear_free(allocated_data_, size_);
}
ByteSource& ByteSource::operator=(ByteSource&& other) noexcept {
if (&other != this) {
OPENSSL_clear_free(allocated_data_, size_);
data_ = other.data_;
allocated_data_ = other.allocated_data_;
other.allocated_data_ = nullptr;
size_ = other.size_;
}
return *this;
}
std::unique_ptr<BackingStore> ByteSource::ReleaseToBackingStore(
Environment* env) {
// It's ok for allocated_data_ to be nullptr but
// only if size_ is zero.
CHECK_IMPLIES(size_ > 0, allocated_data_ != nullptr);
#ifdef V8_ENABLE_SANDBOX
// If the v8 sandbox is enabled, then all array buffers must be allocated
// via the isolate. External buffers are not allowed. So, instead of wrapping
// the allocated data we'll copy it instead.
// TODO(@jasnell): It would be nice to use an abstracted utility to do this
// branch instead of duplicating the V8_ENABLE_SANDBOX check each time.
std::unique_ptr<BackingStore> ptr = ArrayBuffer::NewBackingStore(
env->isolate(),
size(),
BackingStoreInitializationMode::kUninitialized,
BackingStoreOnFailureMode::kReturnNull);
if (!ptr) {
THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
return nullptr;
}
memcpy(ptr->Data(), allocated_data_, size());
OPENSSL_clear_free(allocated_data_, size_);
#else
std::unique_ptr<BackingStore> ptr = ArrayBuffer::NewBackingStore(
allocated_data_,
size(),
[](void* data, size_t length, void* deleter_data) {
OPENSSL_clear_free(deleter_data, length);
}, allocated_data_);
#endif // V8_ENABLE_SANDBOX
CHECK(ptr);
allocated_data_ = nullptr;
data_ = nullptr;
size_ = 0;
return ptr;
}
Local<ArrayBuffer> ByteSource::ToArrayBuffer(Environment* env) {
std::unique_ptr<BackingStore> store = ReleaseToBackingStore(env);
return ArrayBuffer::New(env->isolate(), std::move(store));
}
MaybeLocal<Uint8Array> ByteSource::ToBuffer(Environment* env) {
Local<ArrayBuffer> ab = ToArrayBuffer(env);
return Buffer::New(env, ab, 0, ab->ByteLength());
}
ByteSource ByteSource::FromBIO(const BIOPointer& bio) {
CHECK(bio);
BUF_MEM* bptr = bio;
auto out = DataPointer::Alloc(bptr->length);
memcpy(out.get(), bptr->data, bptr->length);
return ByteSource::Allocated(out.release());
}
ByteSource ByteSource::FromEncodedString(Environment* env,
Local<String> key,
enum encoding enc) {
size_t length = 0;
ByteSource out;
if (StringBytes::Size(env->isolate(), key, enc).To(&length) && length > 0) {
auto buf = DataPointer::Alloc(length);
size_t actual = StringBytes::Write(
env->isolate(), static_cast<char*>(buf.get()), length, key, enc);
out = ByteSource::Allocated(buf.resize(actual).release());
}
return out;
}
ByteSource ByteSource::FromStringOrBuffer(Environment* env,
Local<Value> value) {
return IsAnyBufferSource(value) ? FromBuffer(value)
: FromString(env, value.As<String>());
}
ByteSource ByteSource::FromString(Environment* env, Local<String> str,
bool ntc) {
CHECK(str->IsString());
size_t size = str->Utf8LengthV2(env->isolate());
size_t alloc_size = ntc ? size + 1 : size;
auto out = DataPointer::Alloc(alloc_size);
int flags = String::WriteFlags::kNone;
if (ntc) flags |= String::WriteFlags::kNullTerminate;
str->WriteUtf8V2(
env->isolate(), static_cast<char*>(out.get()), alloc_size, flags);
return ByteSource::Allocated(out.release());
}
ByteSource ByteSource::FromBuffer(Local<Value> buffer, bool ntc) {
ArrayBufferOrViewContents<char> buf(buffer);
return ntc ? buf.ToNullTerminatedCopy() : buf.ToByteSource();
}
ByteSource ByteSource::FromSecretKeyBytes(
Environment* env,
Local<Value> value) {
// A key can be passed as a string, buffer or KeyObject with type 'secret'.
// If it is a string, we need to convert it to a buffer. We are not doing that
// in JS to avoid creating an unprotected copy on the heap.
return value->IsString() || IsAnyBufferSource(value)
? ByteSource::FromStringOrBuffer(env, value)
: ByteSource::FromSymmetricKeyObjectHandle(value);
}
ByteSource ByteSource::NullTerminatedCopy(Environment* env,
Local<Value> value) {
return Buffer::HasInstance(value) ? FromBuffer(value, true)
: FromString(env, value.As<String>(), true);
}
ByteSource ByteSource::FromSymmetricKeyObjectHandle(Local<Value> handle) {
CHECK(handle->IsObject());
KeyObjectHandle* key =
BaseObject::Unwrap<KeyObjectHandle>(handle.As<Object>());
CHECK_NOT_NULL(key);
return Foreign(key->Data().GetSymmetricKey(),
key->Data().GetSymmetricKeySize());
}
ByteSource ByteSource::Allocated(void* data, size_t size) {
return ByteSource(data, data, size);
}
ByteSource ByteSource::Foreign(const void* data, size_t size) {
return ByteSource(data, nullptr, size);
}
namespace error {
Maybe<void> Decorate(Environment* env,
Local<Object> obj,
unsigned long err) { // NOLINT(runtime/int)
if (err == 0) return JustVoid(); // No decoration necessary.
const char* ls = ERR_lib_error_string(err);
const char* fs = ERR_func_error_string(err);
const char* rs = ERR_reason_error_string(err);
Isolate* isolate = env->isolate();
Local<Context> context = isolate->GetCurrentContext();
if (ls != nullptr) {
if (obj->Set(context, env->library_string(),
OneByteString(isolate, ls)).IsNothing()) {
return Nothing<void>();
}
}
if (fs != nullptr) {
if (obj->Set(context, env->function_string(),
OneByteString(isolate, fs)).IsNothing()) {
return Nothing<void>();
}
}
if (rs != nullptr) {
if (obj->Set(context, env->reason_string(),
OneByteString(isolate, rs)).IsNothing()) {
return Nothing<void>();
}
// SSL has no API to recover the error name from the number, so we
// transform reason strings like "this error" to "ERR_SSL_THIS_ERROR",
// which ends up being close to the original error macro name.
std::string reason(rs);
for (auto& c : reason) {
if (c == ' ')
c = '_';
else
c = ToUpper(c);
}
#define OSSL_ERROR_CODES_MAP(V) \
V(SYS) \
V(BN) \
V(RSA) \
V(DH) \
V(EVP) \
V(BUF) \
V(OBJ) \
V(PEM) \
V(DSA) \
V(X509) \
V(ASN1) \
V(CONF) \
V(CRYPTO) \
V(EC) \
V(SSL) \
V(BIO) \
V(PKCS7) \
V(X509V3) \
V(PKCS12) \
V(RAND) \
V(DSO) \
V(ENGINE) \
V(OCSP) \
V(UI) \
V(COMP) \
V(ECDSA) \
V(ECDH) \
V(OSSL_STORE) \
V(FIPS) \
V(CMS) \
V(TS) \
V(HMAC) \
V(CT) \
V(ASYNC) \
V(KDF) \
V(SM2) \
V(USER) \
#define V(name) case ERR_LIB_##name: lib = #name "_"; break;
const char* lib = "";
const char* prefix = "OSSL_";
switch (ERR_GET_LIB(err)) { /* NOLINT(whitespace/newline) */
OSSL_ERROR_CODES_MAP(V)
}
#undef V
#undef OSSL_ERROR_CODES_MAP
// Don't generate codes like "ERR_OSSL_SSL_".
if (lib && strcmp(lib, "SSL_") == 0)
prefix = "";
// All OpenSSL reason strings fit in a single 80-column macro definition,
// all prefix lengths are <= 10, and ERR_OSSL_ is 9, so 128 is more than
// sufficient.
char code[128];
snprintf(code, sizeof(code), "ERR_%s%s%s", prefix, lib, reason.c_str());
if (obj->Set(env->isolate()->GetCurrentContext(),
env->code_string(),
OneByteString(env->isolate(), code)).IsNothing())
return Nothing<void>();
}
return JustVoid();
}
} // namespace error
void ThrowCryptoError(Environment* env,
unsigned long err, // NOLINT(runtime/int)
// Default, only used if there is no SSL `err` which can
// be used to create a long-style message string.
const char* message) {
char message_buffer[128] = {0};
if (err != 0 || message == nullptr) {
ERR_error_string_n(err, message_buffer, sizeof(message_buffer));
message = message_buffer;
}
HandleScope scope(env->isolate());
Local<String> exception_string;
Local<Value> exception;
Local<Object> obj;
if (!String::NewFromUtf8(env->isolate(), message).ToLocal(&exception_string))
return;
CryptoErrorStore errors;
errors.Capture();
if (!errors.ToException(env, exception_string).ToLocal(&exception) ||
!exception->ToObject(env->context()).ToLocal(&obj) ||
error::Decorate(env, obj, err).IsNothing()) {
return;
}
env->isolate()->ThrowException(exception);
}
#ifndef OPENSSL_NO_ENGINE
void SetEngine(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
if (env->permission()->enabled()) [[unlikely]] {
return THROW_ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED(
env,
"Programmatic selection of OpenSSL engines is unsupported while the "
"experimental permission model is enabled");
}
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(
EnginePointer::getEngineByName(*engine_id).setAsDefault(flags));
}
#endif // !OPENSSL_NO_ENGINE
MaybeLocal<Value> EncodeBignum(Environment* env, const BIGNUM* bn, int size) {
EscapableHandleScope scope(env->isolate());
auto buf = BignumPointer::EncodePadded(bn, size);
CHECK_EQ(buf.size(), static_cast<size_t>(size));
Local<Value> ret;
if (!StringBytes::Encode(env->isolate(),
reinterpret_cast<const char*>(buf.get()),
buf.size(),
BASE64URL)
.ToLocal(&ret)) {
return {};
}
return scope.Escape(ret);
}
Maybe<void> SetEncodedValue(Environment* env,
Local<Object> target,
Local<String> name,
const BIGNUM* bn,
int size) {
Local<Value> value;
CHECK_NOT_NULL(bn);
if (size == 0) size = BignumPointer::GetByteCount(bn);
if (!EncodeBignum(env, bn, size).ToLocal(&value)) {
return Nothing<void>();
}
return target->Set(env->context(), name, value).IsJust() ? JustVoid()
: Nothing<void>();
}
CryptoJobMode GetCryptoJobMode(v8::Local<v8::Value> args) {
CHECK(args->IsUint32());
uint32_t mode = args.As<v8::Uint32>()->Value();
CHECK_LE(mode, kCryptoJobSync);
return static_cast<CryptoJobMode>(mode);
}
namespace {
// SecureBuffer uses OpenSSL's secure heap feature to allocate a
// Uint8Array. Without --secure-heap, OpenSSL's secure heap is disabled,
// in which case this has the same semantics as
// using OPENSSL_malloc. However, if the secure heap is
// initialized, SecureBuffer will automatically use it.
void SecureBuffer(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
#ifdef V8_ENABLE_SANDBOX
// The v8 sandbox is enabled, so we cannot use the secure heap because
// the sandbox requires that all array buffers be allocated via the isolate.
// That is fundamentally incompatible with the secure heap which allocates
// in openssl's secure heap area. Instead we'll just throw an error here.
//
// That said, we really shouldn't get here in the first place since the
// option to enable the secure heap is only available when the sandbox
// is disabled.
UNREACHABLE();
#else
CHECK(args[0]->IsUint32());
uint32_t len = args[0].As<Uint32>()->Value();
auto data = DataPointer::SecureAlloc(len);
CHECK(data.isSecure());
if (!data) {
return THROW_ERR_OPERATION_FAILED(env, "Allocation failed");
}
auto released = data.release();
std::shared_ptr<BackingStore> store = ArrayBuffer::NewBackingStore(
released.data,
released.len,
[](void* data, size_t len, void* deleter_data) {
// The DataPointer takes ownership and will appropriately
// free the data when it gets reset.
DataPointer free_me(
ncrypto::Buffer<void>{
.data = data,
.len = len,
},
true);
},
nullptr);
Local<ArrayBuffer> buffer = ArrayBuffer::New(env->isolate(), store);
args.GetReturnValue().Set(Uint8Array::New(buffer, 0, len));
#endif // V8_ENABLE_SANDBOX
}
void SecureHeapUsed(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(
BigInt::New(args.GetIsolate(), DataPointer::GetSecureHeapUsed()));
}
} // namespace
namespace Util {
void Initialize(Environment* env, Local<Object> target) {
Local<Context> context = env->context();
#ifndef OPENSSL_NO_ENGINE
SetMethod(context, target, "setEngine", SetEngine);
#endif // !OPENSSL_NO_ENGINE
SetMethodNoSideEffect(context, target, "getFipsCrypto", GetFipsCrypto);
SetMethod(context, target, "setFipsCrypto", SetFipsCrypto);
SetMethodNoSideEffect(context, target, "testFipsCrypto", TestFipsCrypto);
NODE_DEFINE_CONSTANT(target, kCryptoJobAsync);
NODE_DEFINE_CONSTANT(target, kCryptoJobSync);
SetMethod(context, target, "secureBuffer", SecureBuffer);
SetMethodNoSideEffect(context, target, "secureHeapUsed", SecureHeapUsed);
SetMethodNoSideEffect(
context, target, "getOpenSSLSecLevelCrypto", GetOpenSSLSecLevelCrypto);
}
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
#ifndef OPENSSL_NO_ENGINE
registry->Register(SetEngine);
#endif // !OPENSSL_NO_ENGINE
registry->Register(GetFipsCrypto);
registry->Register(SetFipsCrypto);
registry->Register(TestFipsCrypto);
registry->Register(SecureBuffer);
registry->Register(SecureHeapUsed);
registry->Register(GetOpenSSLSecLevelCrypto);
}
} // namespace Util
} // namespace crypto
} // namespace node