#ifndef SRC_CRYPTO_CRYPTO_UTIL_H_ #define SRC_CRYPTO_CRYPTO_UTIL_H_ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include "async_wrap.h" #include "env.h" #include "node_errors.h" #include "node_external_reference.h" #include "node_internals.h" #include "string_bytes.h" #include "util.h" #include "v8.h" #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 #include #include #include #include #include #include #include namespace node { namespace crypto { // Currently known sizes of commonly used OpenSSL struct sizes. // OpenSSL considers it's various structs to be opaque and the // sizes may change from one version of OpenSSL to another, so // these values should not be trusted to remain static. These // are provided to allow for some close to reasonable memory // tracking. constexpr size_t kSizeOf_DH = 144; constexpr size_t kSizeOf_EC_KEY = 80; constexpr size_t kSizeOf_EVP_CIPHER_CTX = 168; constexpr size_t kSizeOf_EVP_MD_CTX = 48; constexpr size_t kSizeOf_EVP_PKEY = 72; 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 EVPMDPointer = 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; // 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); bool ProcessFipsOptions(); bool InitCryptoOnce(v8::Isolate* isolate); void InitCryptoOnce(); 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; } MUST_USE_RESULT bool is_err() const { return !ok; } }; // Either succeeds with exactly |length| bytes of cryptographically // strong pseudo-random data, or fails. This function may block. // Don't assume anything about the contents of |buffer| on error. // As a special case, |length == 0| can be used to check if the CSPRNG // is properly seeded without consuming entropy. MUST_USE_RESULT CSPRNGResult CSPRNG(void* buffer, size_t length); int PasswordCallback(char* buf, int size, int rwflag, void* u); int NoPasswordCallback(char* buf, int size, int rwflag, void* u); // Decode is used by the various stream-based crypto utilities to decode // string input. template void Decode(const v8::FunctionCallbackInfo& args, void (*callback)(T*, const v8::FunctionCallbackInfo&, const char*, size_t)) { T* ctx; ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder()); if (args[0]->IsString()) { StringBytes::InlineDecoder decoder; Environment* env = Environment::GetCurrent(args); enum encoding enc = ParseEncoding(env->isolate(), args[1], UTF8); if (decoder.Decode(env, args[0].As(), enc).IsNothing()) return; callback(ctx, args, decoder.out(), decoder.size()); } else { ArrayBufferViewContents buf(args[0]); callback(ctx, args, buf.data(), buf.length()); } } #define NODE_CRYPTO_ERROR_CODES_MAP(V) \ V(CIPHER_JOB_FAILED, "Cipher job failed") \ V(DERIVING_BITS_FAILED, "Deriving bits failed") \ V(ENGINE_NOT_FOUND, "Engine \"%s\" was not found") \ V(INVALID_KEY_TYPE, "Invalid key type") \ V(KEY_GENERATION_JOB_FAILED, "Key generation job failed") \ V(OK, "Ok") \ enum class NodeCryptoError { #define V(CODE, DESCRIPTION) CODE, NODE_CRYPTO_ERROR_CODES_MAP(V) #undef V }; // Utility struct used to harvest error information from openssl's error stack struct CryptoErrorStore final : public MemoryRetainer { public: void Capture(); bool Empty() const; template void Insert(const NodeCryptoError error, Args&&... args); v8::MaybeLocal ToException( Environment* env, v8::Local exception_string = v8::Local()) const; SET_NO_MEMORY_INFO() SET_MEMORY_INFO_NAME(CryptoErrorStore) SET_SELF_SIZE(CryptoErrorStore) private: std::vector errors_; }; template void CryptoErrorStore::Insert(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 } errors_.emplace_back(SPrintF(error_string, std::forward(args)...)); } template T* MallocOpenSSL(size_t count) { void* mem = OPENSSL_malloc(MultiplyWithOverflowCheck(count, sizeof(T))); CHECK_IMPLIES(mem == nullptr, count == 0); return static_cast(mem); } // A helper class representing a read-only byte array. When deallocated, its // contents are zeroed. class ByteSource { public: class Builder { public: // Allocates memory using OpenSSL's memory allocator. explicit Builder(size_t size) : data_(MallocOpenSSL(size)), size_(size) {} Builder(Builder&& other) = delete; Builder& operator=(Builder&& other) = delete; Builder(const Builder&) = delete; Builder& operator=(const Builder&) = delete; ~Builder() { OPENSSL_clear_free(data_, size_); } // Returns the underlying non-const pointer. template T* data() { return reinterpret_cast(data_); } // Returns the (allocated) size in bytes. size_t size() const { return size_; } // Finalizes the Builder and returns a read-only view that is optionally // truncated. ByteSource release(std::optional resize = std::nullopt) && { if (resize) { CHECK_LE(*resize, size_); if (*resize == 0) { OPENSSL_clear_free(data_, size_); data_ = nullptr; } size_ = *resize; } ByteSource out = ByteSource::Allocated(data_, size_); data_ = nullptr; size_ = 0; return out; } private: void* data_; size_t size_; }; ByteSource() = default; ByteSource(ByteSource&& other) noexcept; ~ByteSource(); ByteSource& operator=(ByteSource&& other) noexcept; ByteSource(const ByteSource&) = delete; ByteSource& operator=(const ByteSource&) = delete; template const T* data() const { return reinterpret_cast(data_); } size_t size() const { return size_; } operator bool() const { return data_ != nullptr; } BignumPointer ToBN() const { return BignumPointer(BN_bin2bn(data(), size(), nullptr)); } // Creates a v8::BackingStore that takes over responsibility for // any allocated data. The ByteSource will be reset with size = 0 // after being called. std::unique_ptr ReleaseToBackingStore(); v8::Local ToArrayBuffer(Environment* env); v8::MaybeLocal ToBuffer(Environment* env); static ByteSource Allocated(void* data, size_t size); static ByteSource Foreign(const void* data, size_t size); static ByteSource FromEncodedString(Environment* env, v8::Local value, enum encoding enc = BASE64); static ByteSource FromStringOrBuffer(Environment* env, v8::Local value); static ByteSource FromString(Environment* env, v8::Local str, bool ntc = false); static ByteSource FromBuffer(v8::Local buffer, bool ntc = false); static ByteSource FromBIO(const BIOPointer& bio); static ByteSource NullTerminatedCopy(Environment* env, v8::Local value); static ByteSource FromSymmetricKeyObjectHandle(v8::Local handle); static ByteSource FromSecretKeyBytes( Environment* env, v8::Local value); private: const void* data_ = nullptr; void* allocated_data_ = nullptr; size_t size_ = 0; ByteSource(const void* data, void* allocated_data, size_t size) : data_(data), allocated_data_(allocated_data), size_(size) {} }; enum CryptoJobMode { kCryptoJobAsync, kCryptoJobSync }; CryptoJobMode GetCryptoJobMode(v8::Local args); template class CryptoJob : public AsyncWrap, public ThreadPoolWork { public: using AdditionalParams = typename CryptoJobTraits::AdditionalParameters; explicit CryptoJob(Environment* env, v8::Local object, AsyncWrap::ProviderType type, CryptoJobMode mode, AdditionalParams&& params) : AsyncWrap(env, object, type), ThreadPoolWork(env, "crypto"), mode_(mode), params_(std::move(params)) { // If the CryptoJob is async, then the instance will be // cleaned up when AfterThreadPoolWork is called. if (mode == kCryptoJobSync) MakeWeak(); } bool IsNotIndicativeOfMemoryLeakAtExit() const override { // CryptoJobs run a work in the libuv thread pool and may still // exist when the event loop empties and starts to exit. return true; } void AfterThreadPoolWork(int status) override { Environment* env = AsyncWrap::env(); CHECK_EQ(mode_, kCryptoJobAsync); CHECK(status == 0 || status == UV_ECANCELED); std::unique_ptr ptr(this); // If the job was canceled do not execute the callback. // TODO(@jasnell): We should likely revisit skipping the // callback on cancel as that could leave the JS in a pending // state (e.g. unresolved promises...) if (status == UV_ECANCELED) return; v8::HandleScope handle_scope(env->isolate()); v8::Context::Scope context_scope(env->context()); // TODO(tniessen): Remove the exception handling logic here as soon as we // can verify that no code path in ToResult will ever throw an exception. v8::Local exception; v8::Local args[2]; { node::errors::TryCatchScope try_catch(env); v8::Maybe ret = ptr->ToResult(&args[0], &args[1]); if (!ret.IsJust()) { CHECK(try_catch.HasCaught()); exception = try_catch.Exception(); } else if (!ret.FromJust()) { return; } } if (exception.IsEmpty()) { ptr->MakeCallback(env->ondone_string(), arraysize(args), args); } else { ptr->MakeCallback(env->ondone_string(), 1, &exception); } } virtual v8::Maybe ToResult( v8::Local* err, v8::Local* result) = 0; CryptoJobMode mode() const { return mode_; } CryptoErrorStore* errors() { return &errors_; } AdditionalParams* params() { return ¶ms_; } const char* MemoryInfoName() const override { return CryptoJobTraits::JobName; } void MemoryInfo(MemoryTracker* tracker) const override { tracker->TrackField("params", params_); tracker->TrackField("errors", errors_); } static void Run(const v8::FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CryptoJob* job; ASSIGN_OR_RETURN_UNWRAP(&job, args.Holder()); if (job->mode() == kCryptoJobAsync) return job->ScheduleWork(); v8::Local ret[2]; env->PrintSyncTrace(); job->DoThreadPoolWork(); v8::Maybe result = job->ToResult(&ret[0], &ret[1]); if (result.IsJust() && result.FromJust()) { args.GetReturnValue().Set( v8::Array::New(env->isolate(), ret, arraysize(ret))); } } static void Initialize( v8::FunctionCallback new_fn, Environment* env, v8::Local target) { v8::Isolate* isolate = env->isolate(); v8::HandleScope scope(isolate); v8::Local context = env->context(); v8::Local job = NewFunctionTemplate(isolate, new_fn); job->Inherit(AsyncWrap::GetConstructorTemplate(env)); job->InstanceTemplate()->SetInternalFieldCount( AsyncWrap::kInternalFieldCount); SetProtoMethod(isolate, job, "run", Run); SetConstructorFunction(context, target, CryptoJobTraits::JobName, job); } static void RegisterExternalReferences(v8::FunctionCallback new_fn, ExternalReferenceRegistry* registry) { registry->Register(new_fn); registry->Register(Run); } private: const CryptoJobMode mode_; CryptoErrorStore errors_; AdditionalParams params_; }; template class DeriveBitsJob final : public CryptoJob { public: using AdditionalParams = typename DeriveBitsTraits::AdditionalParameters; static void New(const v8::FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CryptoJobMode mode = GetCryptoJobMode(args[0]); AdditionalParams params; if (DeriveBitsTraits::AdditionalConfig(mode, args, 1, ¶ms) .IsNothing()) { // The DeriveBitsTraits::AdditionalConfig is responsible for // calling an appropriate THROW_CRYPTO_* variant reporting // whatever error caused initialization to fail. return; } new DeriveBitsJob(env, args.This(), mode, std::move(params)); } static void Initialize( Environment* env, v8::Local target) { CryptoJob::Initialize(New, env, target); } static void RegisterExternalReferences(ExternalReferenceRegistry* registry) { CryptoJob::RegisterExternalReferences(New, registry); } DeriveBitsJob( Environment* env, v8::Local object, CryptoJobMode mode, AdditionalParams&& params) : CryptoJob( env, object, DeriveBitsTraits::Provider, mode, std::move(params)) {} void DoThreadPoolWork() override { if (!DeriveBitsTraits::DeriveBits( AsyncWrap::env(), *CryptoJob::params(), &out_)) { CryptoErrorStore* errors = CryptoJob::errors(); errors->Capture(); if (errors->Empty()) errors->Insert(NodeCryptoError::DERIVING_BITS_FAILED); return; } success_ = true; } v8::Maybe ToResult( v8::Local* err, v8::Local* result) override { Environment* env = AsyncWrap::env(); CryptoErrorStore* errors = CryptoJob::errors(); if (success_) { CHECK(errors->Empty()); *err = v8::Undefined(env->isolate()); return DeriveBitsTraits::EncodeOutput( env, *CryptoJob::params(), &out_, result); } if (errors->Empty()) errors->Capture(); CHECK(!errors->Empty()); *result = v8::Undefined(env->isolate()); return v8::Just(errors->ToException(env).ToLocal(err)); } SET_SELF_SIZE(DeriveBitsJob) void MemoryInfo(MemoryTracker* tracker) const override { tracker->TrackFieldWithSize("out", out_.size()); CryptoJob::MemoryInfo(tracker); } private: ByteSource out_; bool success_ = false; }; 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) {} inline void push_back(const char* str) { list_.emplace_back(OneByteString(env_->isolate(), str)); } inline v8::Local ToJSArray() { return v8::Array::New(env_->isolate(), list_.data(), list_.size()); } private: std::vector> list_; Environment* env_; }; #if OPENSSL_VERSION_MAJOR >= 3 template void array_push_back(const TypeName* evp_ref, const char* from, const char* to, void* arg) { if (!from) return; const TypeName* real_instance = getbyname(from); if (!real_instance) return; const char* real_name = getname(real_instance); if (!real_name) return; // EVP_*_fetch() does not support alias names, so we need to pass it the // real/original algorithm name. // We use EVP_*_fetch() as a filter here because it will only return an // instance if the algorithm is supported by the public OpenSSL APIs (some // algorithms are used internally by OpenSSL and are also passed to this // callback). TypeName* fetched = fetch_type(nullptr, real_name, nullptr); if (!fetched) return; free_type(fetched); static_cast(arg)->push_back(from); } #else template void array_push_back(const TypeName* evp_ref, const char* from, const char* to, void* arg) { if (!from) return; static_cast(arg)->push_back(from); } #endif inline bool IsAnyByteSource(v8::Local arg) { return arg->IsArrayBufferView() || arg->IsArrayBuffer() || arg->IsSharedArrayBuffer(); } template class ArrayBufferOrViewContents { public: ArrayBufferOrViewContents() = default; ArrayBufferOrViewContents(const ArrayBufferOrViewContents&) = delete; void operator=(const ArrayBufferOrViewContents&) = delete; inline explicit ArrayBufferOrViewContents(v8::Local buf) { if (buf.IsEmpty()) { return; } CHECK(IsAnyByteSource(buf)); if (buf->IsArrayBufferView()) { auto view = buf.As(); offset_ = view->ByteOffset(); length_ = view->ByteLength(); data_ = view->Buffer()->Data(); } else if (buf->IsArrayBuffer()) { auto ab = buf.As(); offset_ = 0; length_ = ab->ByteLength(); data_ = ab->Data(); } else { auto sab = buf.As(); offset_ = 0; length_ = sab->ByteLength(); data_ = sab->Data(); } } inline const T* data() const { // Ideally, these would return nullptr if IsEmpty() or length_ is zero, // but some of the openssl API react badly if given a nullptr even when // length is zero, so we have to return something. if (size() == 0) return &buf; return reinterpret_cast(data_) + offset_; } inline T* data() { // Ideally, these would return nullptr if IsEmpty() or length_ is zero, // but some of the openssl API react badly if given a nullptr even when // length is zero, so we have to return something. if (size() == 0) return &buf; return reinterpret_cast(data_) + offset_; } inline size_t size() const { return length_; } // In most cases, input buffer sizes passed in to openssl need to // be limited to <= INT_MAX. This utility method helps us check. inline bool CheckSizeInt32() { return size() <= INT_MAX; } inline ByteSource ToByteSource() const { return ByteSource::Foreign(data(), size()); } inline ByteSource ToCopy() const { if (size() == 0) return ByteSource(); ByteSource::Builder buf(size()); memcpy(buf.data(), data(), size()); return std::move(buf).release(); } inline ByteSource ToNullTerminatedCopy() const { if (size() == 0) return ByteSource(); ByteSource::Builder buf(size() + 1); memcpy(buf.data(), data(), size()); buf.data()[size()] = 0; return std::move(buf).release(size()); } template void CopyTo(M* dest, size_t len) const { static_assert(sizeof(M) == 1, "sizeof(M) must equal 1"); len = std::min(len, size()); if (len > 0 && data() != nullptr) memcpy(dest, data(), len); } private: T buf = 0; size_t offset_ = 0; size_t length_ = 0; void* data_ = nullptr; // Declaring operator new and delete as deleted is not spec compliant. // Therefore declare them private instead to disable dynamic alloc void* operator new(size_t); void* operator new[](size_t); void operator delete(void*); void operator delete[](void*); }; v8::MaybeLocal EncodeBignum( Environment* env, const BIGNUM* bn, int size, v8::Local* error); v8::Maybe SetEncodedValue( Environment* env, v8::Local target, v8::Local name, const BIGNUM* bn, int size = 0); bool SetRsaOaepLabel(const EVPKeyCtxPointer& rsa, const ByteSource& label); namespace Util { void Initialize(Environment* env, v8::Local target); void RegisterExternalReferences(ExternalReferenceRegistry* registry); } // namespace Util } // namespace crypto } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #endif // SRC_CRYPTO_CRYPTO_UTIL_H_