/
githubmirror
/
node
Обзор
Документация
Войти
/
githubmirror
/
node
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/crypto/crypto_util.cc
919 строк
31 KB
Filip Skokan
src: report why --enable-fips failed
05 авг 2026, 17:49
Не верифицирован
05 авг 2026, 17:49
b79dcdf
Код
Авторство
О чём код?
#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" #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::Array; using v8::ArrayBuffer; using v8::ArrayBufferView; using v8::BackingStore; using v8::BackingStoreInitializationMode; using v8::BackingStoreOnFailureMode; using v8::BigInt; using v8::Context; using v8::EscapableHandleScope; using v8::Exception; using v8::Function; 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; void MemoryRetainerTraits<crypto::ByteSource>::MemoryInfo( MemoryTracker* tracker, const crypto::ByteSource& value) { // Foreign ByteSources do not own the memory that they reference. if (value.allocated_data_ != nullptr) { tracker->TrackFieldWithSize("data", value.size_); } } const char* MemoryRetainerTraits<crypto::ByteSource>::MemoryInfoName( const crypto::ByteSource& value) { return "ByteSource"; } size_t MemoryRetainerTraits<crypto::ByteSource>::SelfSize( const crypto::ByteSource& value) { return sizeof(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; } std::optional<std::string> ProcessFipsOptions() { const bool enable_fips = per_process::cli_options->enable_fips_crypto; const bool force_fips = per_process::cli_options->force_fips_crypto; if (!enable_fips && !force_fips) return std::nullopt; #if OPENSSL_VERSION_MAJOR >= 3 // Whether FIPS-approved implementations are reachable is decided by the // OpenSSL configuration, not by Node.js. Refuse to start rather than // restrict the default property query to a provider that is not there, // which would leave every operation failing as unsupported. if (!ncrypto::testFipsEnabled()) { const std::string option = force_fips ? "--force-fips" : "--enable-fips"; return option + " requires an active OpenSSL provider named \"fips\". " "FIPS mode is configured through OpenSSL; see " "https://nodejs.org/api/crypto.html#fips-mode"; } #endif CryptoErrorList errors{CryptoErrorList::Option::NONE}; if (!ncrypto::setFipsEnabled(true, &errors)) { std::string error = "OpenSSL error when trying to enable FIPS"; if (!errors.empty()) error += ':'; for (const auto& openssl_error : errors) { error += '\n'; error += openssl_error; } return error; } return std::nullopt; } 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); #if OPENSSL_WITH_OPENSSL_PQC // Configure all loaded providers to prefer seed-only format for ML-KEM and // ML-DSA private keys in PKCS#8 export, falling back to priv-only when a // seed is not available. The provider encoder reads these parameters at // encoding time via ossl_prov_ctx_get_param(). OSSL_PROVIDER_do_all( nullptr, [](OSSL_PROVIDER* provider, void*) -> int { OSSL_PROVIDER_add_conf_parameter( provider, "ml-kem.output_formats", "seed-only,priv-only"); OSSL_PROVIDER_add_conf_parameter( provider, "ml-dsa.output_formats", "seed-only,priv-only"); return 1; }, nullptr); #endif 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(); primary_openssl_error_ = 0; while (const uint32_t err = ERR_get_error()) { if (primary_openssl_error_ == 0) primary_openssl_error_ = err; 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(env->ok_string()); } // 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; } namespace error { v8::Maybe<void> Decorate(Environment* env, v8::Local<v8::Object> obj, unsigned long err); // NOLINT(runtime/int) } // namespace error 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()); CHECK(exception_v->IsObject()); Local<Object> exception = exception_v.As<Object>(); if (!Empty()) { Local<Value> stack; if (!ToV8Value(env->context(), errors_).ToLocal(&stack) || exception->Set(env->context(), env->openssl_error_stack(), stack) .IsNothing()) { return MaybeLocal<Value>(); } } if (primary_openssl_error_ != 0) { if (error::Decorate(env, exception, primary_openssl_error_).IsNothing()) { return MaybeLocal<Value>(); } } else if (node_error_code_ != nullptr) { if (exception ->Set(env->context(), env->code_string(), OneByteString(env->isolate(), node_error_code_)) .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; } void TruncateToBitLength(size_t length_bits, ByteSource* bytes) { CHECK_NOT_NULL(bytes); const size_t length_bytes = NumBitsToBytes(length_bits); CHECK_LE(length_bytes, bytes->size()); if (bytes->allocated_data_ == nullptr || bytes->size() != length_bytes) { auto data = DataPointer::Alloc(length_bytes); if (length_bytes > 0) { CHECK_NOT_NULL(data.get()); memcpy(data.get(), bytes->data(), length_bytes); } *bytes = ByteSource::Allocated(data.release()); } const size_t remainder_bits = length_bits % CHAR_BIT; if (remainder_bits != 0) { auto* data = static_cast<unsigned char*>(bytes->allocated_data_); CHECK_NOT_NULL(data); const unsigned char mask = static_cast<unsigned char>(0xff << (CHAR_BIT - remainder_bits)); data[length_bytes - 1] &= mask; } } 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) { // JS normalizes secret KeyObject/CryptoKey inputs to a KeyObjectHandle. // Strings are converted here instead of 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); #if NCRYPTO_USE_OPENSSL3_PROVIDER const char* fs = nullptr; #else const char* fs = ERR_func_error_string(err); #endif 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); } #ifdef OPENSSL_IS_BORINGSSL #define OSSL_ERROR_CODES_MAP_OPENSSL_ONLY(V) #else #define OSSL_ERROR_CODES_MAP_OPENSSL_ONLY(V) \ V(PKCS12) \ V(DSO) \ V(OSSL_STORE) \ V(FIPS) \ V(TS) \ V(CT) \ V(ASYNC) \ V(KDF) \ V(SM2) #endif #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(RAND) \ V(ENGINE) \ V(OCSP) \ V(UI) \ V(COMP) \ V(ECDSA) \ V(ECDH) \ V(CMS) \ V(HMAC) \ V(USER) \ OSSL_ERROR_CODES_MAP_OPENSSL_ONLY(V) #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 #undef OSSL_ERROR_CODES_MAP_OPENSSL_ONLY // 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->DefineOwnProperty(env->context(), name, value).FromMaybe(false) ? JustVoid() : Nothing<void>(); } CryptoJobMode GetCryptoJobMode(v8::Local<v8::Value> args) { CHECK(args->IsUint32()); uint32_t mode = args.As<v8::Uint32>()->Value(); CHECK_LE(mode, kCryptoJobWebCrypto); return static_cast<CryptoJobMode>(mode); } bool IsCryptoJobAsync(CryptoJobMode mode) { return mode == kCryptoJobAsync || mode == kCryptoJobWebCrypto; } MaybeLocal<Value> CreateWebCryptoJobError(Environment* env, Local<Value> cause) { Isolate* isolate = env->isolate(); Local<Context> context = env->context(); Local<Object> per_context_bindings; Local<Value> domexception_ctor; if (!GetPerContextExports(context).ToLocal(&per_context_bindings) || !per_context_bindings ->Get(context, FIXED_ONE_BYTE_STRING(isolate, "DOMException")) .ToLocal(&domexception_ctor)) { return {}; } CHECK(domexception_ctor->IsFunction()); Local<Object> options = Object::New(isolate); if (options->Set(context, env->name_string(), env->operationerror_string()) .IsNothing() || options->Set(context, env->cause_string(), cause).IsNothing()) { return {}; } Local<Value> argv[] = { FIXED_ONE_BYTE_STRING(isolate, "The operation failed for an operation-specific " "reason"), options, }; return domexception_ctor.As<Function>()->NewInstance( context, arraysize(argv), argv); } MaybeLocal<Value> ToWebCryptoJobResult(Environment* env, Local<Value> value) { if (value->IsArrayBuffer()) { return value; } if (Buffer::HasInstance(value)) { return value.As<ArrayBufferView>()->Buffer(); } CHECK(value->IsBoolean() || (value->IsObject() && !value->IsArray() && !value->IsArrayBufferView())); return value; } 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); 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); NODE_DEFINE_CONSTANT(target, kCryptoJobWebCrypto); 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