Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

31
src/.clang-tidy Normal file
View File

@ -0,0 +1,31 @@
---
Checks: '-*,
# modernize-use-auto,
# modernize-use-equals-delete,
modernize-deprecated-headers,
modernize-make-unique,
modernize-make-shared,
modernize-raw-string-literal,
modernize-redundant-void-arg,
modernize-replace-random-shuffle,
modernize-shrink-to-fit,
modernize-use-default-member-init,
modernize-use-bool-literals,
modernize-use-emplace,
modernize-use-equals-default,
modernize-use-nullptr,
modernize-use-override,
modernize-use-starts-ends-with,
performance-faster-string-find,
performance-implicit-conversion-in-loop,
# performance-unnecessary-value-param, see https://github.com/nodejs/node/pull/26042
readability-delete-null-pointer, '
WarningsAsErrors: ''
HeaderFilterRegex: ''
AnalyzeTemporaryDtors: false
FormatStyle: none
User: nodejs/cpp
CheckOptions:
- key: google-readability-braces-around-statements.ShortStatementLines
value: 1
...

1517
src/README.md Normal file

File diff suppressed because it is too large Load Diff

6
src/acorn_version.h Normal file
View File

@ -0,0 +1,6 @@
// This is an auto generated file, please do not edit.
// Refer to tools/dep_updaters/update-acorn.sh
#ifndef SRC_ACORN_VERSION_H_
#define SRC_ACORN_VERSION_H_
#define ACORN_VERSION "8.14.1"
#endif // SRC_ACORN_VERSION_H_

244
src/aliased_buffer-inl.h Normal file
View File

@ -0,0 +1,244 @@
#ifndef SRC_ALIASED_BUFFER_INL_H_
#define SRC_ALIASED_BUFFER_INL_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "aliased_buffer.h"
#include "memory_tracker-inl.h"
#include "util-inl.h"
namespace node {
typedef size_t AliasedBufferIndex;
template <typename NativeT, typename V8T>
AliasedBufferBase<NativeT, V8T>::AliasedBufferBase(
v8::Isolate* isolate, const size_t count, const AliasedBufferIndex* index)
: isolate_(isolate), count_(count), byte_offset_(0), index_(index) {
CHECK_GT(count, 0);
if (index != nullptr) {
// Will be deserialized later.
return;
}
const v8::HandleScope handle_scope(isolate_);
const size_t size_in_bytes =
MultiplyWithOverflowCheck(sizeof(NativeT), count);
// allocate v8 ArrayBuffer
v8::Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate_, size_in_bytes);
buffer_ = static_cast<NativeT*>(ab->Data());
// allocate v8 TypedArray
v8::Local<V8T> js_array = V8T::New(ab, byte_offset_, count);
js_array_ = v8::Global<V8T>(isolate, js_array);
}
template <typename NativeT, typename V8T>
AliasedBufferBase<NativeT, V8T>::AliasedBufferBase(
v8::Isolate* isolate,
const size_t byte_offset,
const size_t count,
const AliasedBufferBase<uint8_t, v8::Uint8Array>& backing_buffer,
const AliasedBufferIndex* index)
: isolate_(isolate),
count_(count),
byte_offset_(byte_offset),
index_(index) {
if (index != nullptr) {
// Will be deserialized later.
return;
}
const v8::HandleScope handle_scope(isolate_);
v8::Local<v8::ArrayBuffer> ab = backing_buffer.GetArrayBuffer();
// validate that the byte_offset is aligned with sizeof(NativeT)
CHECK_EQ(byte_offset & (sizeof(NativeT) - 1), 0);
// validate this fits inside the backing buffer
CHECK_LE(MultiplyWithOverflowCheck(sizeof(NativeT), count),
ab->ByteLength() - byte_offset);
buffer_ = reinterpret_cast<NativeT*>(
const_cast<uint8_t*>(backing_buffer.GetNativeBuffer() + byte_offset));
v8::Local<V8T> js_array = V8T::New(ab, byte_offset, count);
js_array_ = v8::Global<V8T>(isolate, js_array);
}
template <typename NativeT, typename V8T>
AliasedBufferBase<NativeT, V8T>::AliasedBufferBase(
const AliasedBufferBase& that)
: isolate_(that.isolate_),
count_(that.count_),
byte_offset_(that.byte_offset_),
buffer_(that.buffer_) {
js_array_ = v8::Global<V8T>(that.isolate_, that.GetJSArray());
DCHECK(is_valid());
}
template <typename NativeT, typename V8T>
AliasedBufferIndex AliasedBufferBase<NativeT, V8T>::Serialize(
v8::Local<v8::Context> context, v8::SnapshotCreator* creator) {
DCHECK(is_valid());
return creator->AddData(context, GetJSArray());
}
template <typename NativeT, typename V8T>
inline void AliasedBufferBase<NativeT, V8T>::Deserialize(
v8::Local<v8::Context> context) {
DCHECK_NOT_NULL(index_);
v8::Local<V8T> arr =
context->GetDataFromSnapshotOnce<V8T>(*index_).ToLocalChecked();
// These may not hold true for AliasedBuffers that have grown, so should
// be removed when we expand the snapshot support.
DCHECK_EQ(count_, arr->Length());
DCHECK_EQ(byte_offset_, arr->ByteOffset());
uint8_t* raw = static_cast<uint8_t*>(arr->Buffer()->Data());
buffer_ = reinterpret_cast<NativeT*>(raw + byte_offset_);
js_array_.Reset(isolate_, arr);
index_ = nullptr;
}
template <typename NativeT, typename V8T>
AliasedBufferBase<NativeT, V8T>& AliasedBufferBase<NativeT, V8T>::operator=(
AliasedBufferBase<NativeT, V8T>&& that) noexcept {
DCHECK(is_valid());
this->~AliasedBufferBase();
isolate_ = that.isolate_;
count_ = that.count_;
byte_offset_ = that.byte_offset_;
buffer_ = that.buffer_;
js_array_.Reset(isolate_, that.js_array_.Get(isolate_));
that.buffer_ = nullptr;
that.js_array_.Reset();
return *this;
}
template <typename NativeT, typename V8T>
v8::Local<V8T> AliasedBufferBase<NativeT, V8T>::GetJSArray() const {
DCHECK(is_valid());
return js_array_.Get(isolate_);
}
template <typename NativeT, typename V8T>
void AliasedBufferBase<NativeT, V8T>::Release() {
DCHECK_NULL(index_);
js_array_.Reset();
}
template <typename NativeT, typename V8T>
inline void AliasedBufferBase<NativeT, V8T>::MakeWeak() {
DCHECK(is_valid());
js_array_.SetWeak();
}
template <typename NativeT, typename V8T>
v8::Local<v8::ArrayBuffer> AliasedBufferBase<NativeT, V8T>::GetArrayBuffer()
const {
return GetJSArray()->Buffer();
}
template <typename NativeT, typename V8T>
inline const NativeT* AliasedBufferBase<NativeT, V8T>::GetNativeBuffer() const {
DCHECK(is_valid());
return buffer_;
}
template <typename NativeT, typename V8T>
inline const NativeT* AliasedBufferBase<NativeT, V8T>::operator*() const {
return GetNativeBuffer();
}
template <typename NativeT, typename V8T>
inline void AliasedBufferBase<NativeT, V8T>::SetValue(const size_t index,
NativeT value) {
DCHECK_LT(index, count_);
DCHECK(is_valid());
buffer_[index] = value;
}
template <typename NativeT, typename V8T>
inline const NativeT AliasedBufferBase<NativeT, V8T>::GetValue(
const size_t index) const {
DCHECK(is_valid());
DCHECK_LT(index, count_);
return buffer_[index];
}
template <typename NativeT, typename V8T>
typename AliasedBufferBase<NativeT, V8T>::Reference
AliasedBufferBase<NativeT, V8T>::operator[](size_t index) {
DCHECK(is_valid());
return Reference(this, index);
}
template <typename NativeT, typename V8T>
NativeT AliasedBufferBase<NativeT, V8T>::operator[](size_t index) const {
return GetValue(index);
}
template <typename NativeT, typename V8T>
size_t AliasedBufferBase<NativeT, V8T>::Length() const {
return count_;
}
template <typename NativeT, typename V8T>
void AliasedBufferBase<NativeT, V8T>::reserve(size_t new_capacity) {
DCHECK(is_valid());
DCHECK_GE(new_capacity, count_);
DCHECK_EQ(byte_offset_, 0);
const v8::HandleScope handle_scope(isolate_);
const size_t old_size_in_bytes = sizeof(NativeT) * count_;
const size_t new_size_in_bytes =
MultiplyWithOverflowCheck(sizeof(NativeT), new_capacity);
// allocate v8 new ArrayBuffer
v8::Local<v8::ArrayBuffer> ab =
v8::ArrayBuffer::New(isolate_, new_size_in_bytes);
// allocate new native buffer
NativeT* new_buffer = static_cast<NativeT*>(ab->Data());
// copy old content
memcpy(new_buffer, buffer_, old_size_in_bytes);
// allocate v8 TypedArray
v8::Local<V8T> js_array = V8T::New(ab, byte_offset_, new_capacity);
// move over old v8 TypedArray
js_array_ = std::move(v8::Global<V8T>(isolate_, js_array));
buffer_ = new_buffer;
count_ = new_capacity;
}
template <typename NativeT, typename V8T>
inline bool AliasedBufferBase<NativeT, V8T>::is_valid() const {
return index_ == nullptr && !js_array_.IsEmpty();
}
template <typename NativeT, typename V8T>
inline size_t AliasedBufferBase<NativeT, V8T>::SelfSize() const {
return sizeof(*this);
}
#define VM(NativeT, V8T) \
template <> \
inline const char* AliasedBufferBase<NativeT, v8::V8T>::MemoryInfoName() \
const { \
return "Aliased" #V8T; \
} \
template <> \
inline void AliasedBufferBase<NativeT, v8::V8T>::MemoryInfo( \
node::MemoryTracker* tracker) const { \
tracker->TrackField("js_array", js_array_); \
}
ALIASED_BUFFER_LIST(VM)
#undef VM
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_ALIASED_BUFFER_INL_H_

206
src/aliased_buffer.h Normal file
View File

@ -0,0 +1,206 @@
#ifndef SRC_ALIASED_BUFFER_H_
#define SRC_ALIASED_BUFFER_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include <cinttypes>
#include "memory_tracker.h"
#include "v8.h"
namespace node {
typedef size_t AliasedBufferIndex;
/**
* Do not use this class directly when creating instances of it - use the
* Aliased*Array defined at the end of this file instead.
*
* This class encapsulates the technique of having a native buffer mapped to
* a JS object. Writes to the native buffer can happen efficiently without
* going through JS, and the data is then available to user's via the exposed
* JS object.
*
* While this technique is computationally efficient, it is effectively a
* write to JS program state w/out going through the standard
* (monitored) API. Thus, any VM capabilities to detect the modification are
* circumvented.
*
* The encapsulation herein provides a placeholder where such writes can be
* observed. Any notification APIs will be left as a future exercise.
*/
template <class NativeT, class V8T>
class AliasedBufferBase final : public MemoryRetainer {
public:
static_assert(std::is_scalar_v<NativeT>);
AliasedBufferBase(v8::Isolate* isolate,
size_t count,
const AliasedBufferIndex* index = nullptr);
/**
* Create an AliasedBufferBase over a sub-region of another aliased buffer.
* The two will share a v8::ArrayBuffer instance &
* a native buffer, but will each read/write to different sections of the
* native buffer.
*
* Note that byte_offset must be aligned by sizeof(NativeT).
*/
// TODO(refack): refactor into a non-owning `AliasedBufferBaseView`
AliasedBufferBase(
v8::Isolate* isolate,
size_t byte_offset,
size_t count,
const AliasedBufferBase<uint8_t, v8::Uint8Array>& backing_buffer,
const AliasedBufferIndex* index = nullptr);
AliasedBufferBase(const AliasedBufferBase& that);
AliasedBufferIndex Serialize(v8::Local<v8::Context> context,
v8::SnapshotCreator* creator);
void Deserialize(v8::Local<v8::Context> context);
AliasedBufferBase& operator=(AliasedBufferBase&& that) noexcept;
/**
* Helper class that is returned from operator[] to support assignment into
* a specified location.
*/
class Reference {
public:
Reference(AliasedBufferBase* aliased_buffer, const size_t index)
: aliased_buffer_(aliased_buffer), index_(index) {}
Reference(const Reference& that)
: aliased_buffer_(that.aliased_buffer_),
index_(that.index_) {
}
Reference& operator=(const NativeT& val) {
aliased_buffer_->SetValue(index_, val);
return *this;
}
Reference& operator=(const Reference& val) {
return *this = static_cast<NativeT>(val);
}
operator NativeT() const {
return aliased_buffer_->GetValue(index_);
}
Reference& operator+=(const NativeT& val) {
const NativeT current = aliased_buffer_->GetValue(index_);
aliased_buffer_->SetValue(index_, current + val);
return *this;
}
Reference& operator+=(const Reference& val) {
return this->operator+=(static_cast<NativeT>(val));
}
Reference& operator-=(const NativeT& val) {
const NativeT current = aliased_buffer_->GetValue(index_);
aliased_buffer_->SetValue(index_, current - val);
return *this;
}
private:
AliasedBufferBase* aliased_buffer_;
size_t index_;
};
/**
* Get the underlying v8 TypedArray overlaid on top of the native buffer
*/
v8::Local<V8T> GetJSArray() const;
void Release();
/**
* Make the global reference to the typed array weak. The caller must make
* sure that no operation can be done on the AliasedBuffer when the typed
* array becomes unreachable. Usually this means the caller must maintain
* a JS reference to the typed array from JS object.
*/
void MakeWeak();
/**
* Get the underlying v8::ArrayBuffer underlying the TypedArray and
* overlaying the native buffer
*/
v8::Local<v8::ArrayBuffer> GetArrayBuffer() const;
/**
* Get the underlying native buffer. Note that all reads/writes should occur
* through the GetValue/SetValue/operator[] methods
*/
const NativeT* GetNativeBuffer() const;
/**
* Synonym for GetBuffer()
*/
const NativeT* operator*() const;
/**
* Set position index to given value.
*/
void SetValue(size_t index, NativeT value);
/**
* Get value at position index
*/
const NativeT GetValue(size_t index) const;
/**
* Effectively, a synonym for GetValue/SetValue
*/
Reference operator[](size_t index);
NativeT operator[](size_t index) const;
size_t Length() const;
// Should only be used to extend the array.
// Should only be used on an owning array, not one created as a sub array of
// an owning `AliasedBufferBase`.
void reserve(size_t new_capacity);
size_t SelfSize() const override;
const char* MemoryInfoName() const override;
void MemoryInfo(MemoryTracker* tracker) const override;
private:
bool is_valid() const;
v8::Isolate* isolate_ = nullptr;
size_t count_ = 0;
size_t byte_offset_ = 0;
NativeT* buffer_ = nullptr;
v8::Global<V8T> js_array_;
// Deserialize data
const AliasedBufferIndex* index_ = nullptr;
};
#define ALIASED_BUFFER_LIST(V) \
V(int8_t, Int8Array) \
V(uint8_t, Uint8Array) \
V(int16_t, Int16Array) \
V(uint16_t, Uint16Array) \
V(int32_t, Int32Array) \
V(uint32_t, Uint32Array) \
V(float, Float32Array) \
V(double, Float64Array) \
V(int64_t, BigInt64Array)
#define V(NativeT, V8T) \
typedef AliasedBufferBase<NativeT, v8::V8T> Aliased##V8T;
ALIASED_BUFFER_LIST(V)
#undef V
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_ALIASED_BUFFER_H_

54
src/aliased_struct-inl.h Normal file
View File

@ -0,0 +1,54 @@
#ifndef SRC_ALIASED_STRUCT_INL_H_
#define SRC_ALIASED_STRUCT_INL_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "aliased_struct.h"
#include "v8.h"
#include <memory>
namespace node {
template <typename T>
template <typename... Args>
AliasedStruct<T>::AliasedStruct(v8::Isolate* isolate, Args&&... args)
: isolate_(isolate) {
const v8::HandleScope handle_scope(isolate);
store_ = v8::ArrayBuffer::NewBackingStore(isolate, sizeof(T));
ptr_ = new (store_->Data()) T(std::forward<Args>(args)...);
DCHECK_NOT_NULL(ptr_);
v8::Local<v8::ArrayBuffer> buffer = v8::ArrayBuffer::New(isolate, store_);
buffer_ = v8::Global<v8::ArrayBuffer>(isolate, buffer);
}
template <typename T>
AliasedStruct<T>::AliasedStruct(const AliasedStruct& that)
: AliasedStruct(that.isolate_, *that) {}
template <typename T>
AliasedStruct<T>& AliasedStruct<T>::operator=(
AliasedStruct<T>&& that) noexcept {
this->~AliasedStruct();
isolate_ = that.isolate_;
store_ = that.store_;
ptr_ = that.ptr_;
buffer_ = std::move(that.buffer_);
that.ptr_ = nullptr;
that.store_.reset();
return *this;
}
template <typename T>
AliasedStruct<T>::~AliasedStruct() {
if (ptr_ != nullptr) ptr_->~T();
}
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_ALIASED_STRUCT_INL_H_

63
src/aliased_struct.h Normal file
View File

@ -0,0 +1,63 @@
#ifndef SRC_ALIASED_STRUCT_H_
#define SRC_ALIASED_STRUCT_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "node_internals.h"
#include "v8.h"
#include <memory>
namespace node {
// AliasedStruct is a utility that allows uses a V8 Backing Store
// to be exposed to the C++/C side as a struct and to the
// JavaScript side as an ArrayBuffer to efficiently share
// data without marshalling. It is similar in nature to
// AliasedBuffer.
//
// struct Foo { int x; }
//
// AliasedStruct<Foo> foo;
// foo->x = 1;
//
// Local<ArrayBuffer> ab = foo.GetArrayBuffer();
template <typename T>
class AliasedStruct final {
public:
template <typename... Args>
explicit AliasedStruct(v8::Isolate* isolate, Args&&... args);
inline AliasedStruct(const AliasedStruct& that);
inline ~AliasedStruct();
inline AliasedStruct& operator=(AliasedStruct&& that) noexcept;
v8::Local<v8::ArrayBuffer> GetArrayBuffer() const {
return buffer_.Get(isolate_);
}
const T* Data() const { return ptr_; }
T* Data() { return ptr_; }
const T& operator*() const { return *ptr_; }
T& operator*() { return *ptr_; }
const T* operator->() const { return ptr_; }
T* operator->() { return ptr_; }
private:
v8::Isolate* isolate_;
std::shared_ptr<v8::BackingStore> store_;
T* ptr_;
v8::Global<v8::ArrayBuffer> buffer_;
};
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_ALIASED_STRUCT_H_

6
src/amaro_version.h Normal file
View File

@ -0,0 +1,6 @@
// This is an auto generated file, please do not edit.
// Refer to tools/dep_updaters/update-amaro.sh
#ifndef SRC_AMARO_VERSION_H_
#define SRC_AMARO_VERSION_H_
#define AMARO_VERSION "0.5.3"
#endif // SRC_AMARO_VERSION_H_

81
src/api/async_resource.cc Normal file
View File

@ -0,0 +1,81 @@
#include "async_context_frame.h"
#include "env-inl.h"
#include "node.h"
namespace node {
using v8::Function;
using v8::Isolate;
using v8::Local;
using v8::MaybeLocal;
using v8::Object;
using v8::String;
using v8::Value;
AsyncResource::AsyncResource(Isolate* isolate,
Local<Object> resource,
const char* name,
async_id trigger_async_id)
: env_(Environment::GetCurrent(isolate)),
resource_(isolate, resource),
context_frame_(isolate, async_context_frame::current(isolate)) {
CHECK_NOT_NULL(env_);
async_context_ = EmitAsyncInit(isolate, resource, name, trigger_async_id);
}
AsyncResource::~AsyncResource() {
CHECK_NOT_NULL(env_);
EmitAsyncDestroy(env_, async_context_);
}
MaybeLocal<Value> AsyncResource::MakeCallback(Local<Function> callback,
int argc,
Local<Value>* argv) {
auto isolate = env_->isolate();
async_context_frame::Scope async_context_frame_scope(
isolate, context_frame_.Get(isolate));
return node::MakeCallback(
isolate, get_resource(), callback, argc, argv, async_context_);
}
MaybeLocal<Value> AsyncResource::MakeCallback(const char* method,
int argc,
Local<Value>* argv) {
auto isolate = env_->isolate();
async_context_frame::Scope async_context_frame_scope(
isolate, context_frame_.Get(isolate));
return node::MakeCallback(
isolate, get_resource(), method, argc, argv, async_context_);
}
MaybeLocal<Value> AsyncResource::MakeCallback(Local<String> symbol,
int argc,
Local<Value>* argv) {
auto isolate = env_->isolate();
async_context_frame::Scope async_context_frame_scope(
isolate, context_frame_.Get(isolate));
return node::MakeCallback(
isolate, get_resource(), symbol, argc, argv, async_context_);
}
Local<Object> AsyncResource::get_resource() {
return resource_.Get(env_->isolate());
}
async_id AsyncResource::get_async_id() const {
return async_context_.async_id;
}
async_id AsyncResource::get_trigger_async_id() const {
return async_context_.trigger_async_id;
}
AsyncResource::CallbackScope::CallbackScope(AsyncResource* res)
: node::CallbackScope(res->env_,
res->resource_.Get(res->env_->isolate()),
res->async_context_) {}
} // namespace node

400
src/api/callback.cc Normal file
View File

@ -0,0 +1,400 @@
#include "async_context_frame.h"
#include "async_wrap-inl.h"
#include "env-inl.h"
#include "node.h"
#include "v8.h"
namespace node {
using v8::Context;
using v8::EscapableHandleScope;
using v8::Function;
using v8::HandleScope;
using v8::Isolate;
using v8::Local;
using v8::MaybeLocal;
using v8::Number;
using v8::Object;
using v8::String;
using v8::Undefined;
using v8::Value;
CallbackScope::CallbackScope(Isolate* isolate,
Local<Object> object,
async_context async_context)
: CallbackScope(Environment::GetCurrent(isolate), object, async_context) {}
CallbackScope::CallbackScope(Environment* env,
Local<Object> object,
async_context asyncContext)
: private_(new InternalCallbackScope(env,
object,
asyncContext)),
try_catch_(env->isolate()) {
try_catch_.SetVerbose(true);
}
CallbackScope::~CallbackScope() {
if (try_catch_.HasCaught())
private_->MarkAsFailed();
delete private_;
}
InternalCallbackScope::InternalCallbackScope(AsyncWrap* async_wrap, int flags)
: InternalCallbackScope(
async_wrap->env(),
async_wrap->object(),
{async_wrap->get_async_id(), async_wrap->get_trigger_async_id()},
flags,
async_wrap->context_frame()) {}
InternalCallbackScope::InternalCallbackScope(Environment* env,
Local<Object> object,
const async_context& asyncContext,
int flags,
Local<Value> context_frame)
: env_(env),
async_context_(asyncContext),
object_(object),
skip_hooks_(flags & kSkipAsyncHooks),
skip_task_queues_(flags & kSkipTaskQueues) {
CHECK_NOT_NULL(env);
env->PushAsyncCallbackScope();
if (!env->can_call_into_js()) {
failed_ = true;
return;
}
Isolate* isolate = env->isolate();
HandleScope handle_scope(isolate);
Local<Context> current_context = isolate->GetCurrentContext();
// If you hit this assertion, the caller forgot to enter the right Node.js
// Environment's v8::Context first.
// We first check `env->context() != current_context` because the contexts
// likely *are* the same, in which case we can skip the slightly more
// expensive Environment::GetCurrent() call.
if (env->context() != current_context) [[unlikely]] {
CHECK_EQ(Environment::GetCurrent(isolate), env);
}
isolate->SetIdle(false);
prior_context_frame_.Reset(
isolate, async_context_frame::exchange(isolate, context_frame));
env->async_hooks()->push_async_context(
async_context_.async_id, async_context_.trigger_async_id, object);
pushed_ids_ = true;
if (asyncContext.async_id != 0 && !skip_hooks_) {
// No need to check a return value because the application will exit if
// an exception occurs.
AsyncWrap::EmitBefore(env, asyncContext.async_id);
}
}
InternalCallbackScope::~InternalCallbackScope() {
Close();
env_->PopAsyncCallbackScope();
}
void InternalCallbackScope::Close() {
if (closed_) return;
closed_ = true;
// This function must ends up with either cleanup the
// async id stack or pop the topmost one from it
auto perform_stopping_check = [&]() {
if (env_->is_stopping()) {
MarkAsFailed();
env_->async_hooks()->clear_async_id_stack();
}
};
perform_stopping_check();
if (env_->is_stopping()) return;
Isolate* isolate = env_->isolate();
auto idle = OnScopeLeave([&]() { isolate->SetIdle(true); });
if (!failed_ && async_context_.async_id != 0 && !skip_hooks_) {
AsyncWrap::EmitAfter(env_, async_context_.async_id);
}
if (pushed_ids_) {
env_->async_hooks()->pop_async_context(async_context_.async_id);
async_context_frame::exchange(isolate, prior_context_frame_.Get(isolate));
}
if (failed_) return;
if (env_->async_callback_scope_depth() > 1 || skip_task_queues_) {
return;
}
TickInfo* tick_info = env_->tick_info();
if (!env_->can_call_into_js()) return;
auto weakref_cleanup = OnScopeLeave([&]() { env_->RunWeakRefCleanup(); });
Local<Context> context = env_->context();
if (!tick_info->has_tick_scheduled()) {
context->GetMicrotaskQueue()->PerformCheckpoint(isolate);
perform_stopping_check();
}
// Make sure the stack unwound properly. If there are nested MakeCallback's
// then it should return early and not reach this code.
if (env_->async_hooks()->fields()[AsyncHooks::kTotals]) {
CHECK_EQ(env_->execution_async_id(), 0);
CHECK_EQ(env_->trigger_async_id(), 0);
}
if (!tick_info->has_tick_scheduled() && !tick_info->has_rejection_to_warn()) {
return;
}
HandleScope handle_scope(isolate);
Local<Object> process = env_->process_object();
if (!env_->can_call_into_js()) return;
Local<Function> tick_callback = env_->tick_callback_function();
// The tick is triggered before JS land calls SetTickCallback
// to initializes the tick callback during bootstrap.
CHECK(!tick_callback.IsEmpty());
if (tick_callback->Call(context, process, 0, nullptr).IsEmpty()) {
failed_ = true;
}
perform_stopping_check();
}
MaybeLocal<Value> InternalMakeCallback(Environment* env,
Local<Object> resource,
Local<Object> recv,
const Local<Function> callback,
int argc,
Local<Value> argv[],
async_context asyncContext,
Local<Value> context_frame) {
CHECK(!recv.IsEmpty());
#ifdef DEBUG
for (int i = 0; i < argc; i++)
CHECK(!argv[i].IsEmpty());
#endif
Local<Function> hook_cb = env->async_hooks_callback_trampoline();
int flags = InternalCallbackScope::kNoFlags;
bool use_async_hooks_trampoline = false;
AsyncHooks* async_hooks = env->async_hooks();
if (!hook_cb.IsEmpty()) {
// Use the callback trampoline if there are any before or after hooks, or
// we can expect some kind of usage of async_hooks.executionAsyncResource().
flags = InternalCallbackScope::kSkipAsyncHooks;
use_async_hooks_trampoline =
async_hooks->fields()[AsyncHooks::kBefore] +
async_hooks->fields()[AsyncHooks::kAfter] +
async_hooks->fields()[AsyncHooks::kUsesExecutionAsyncResource] > 0;
}
InternalCallbackScope scope(
env, resource, asyncContext, flags, context_frame);
if (scope.Failed()) {
return MaybeLocal<Value>();
}
MaybeLocal<Value> ret;
Local<Context> context = env->context();
if (use_async_hooks_trampoline) {
MaybeStackBuffer<Local<Value>, 16> args(3 + argc);
args[0] = Number::New(env->isolate(), asyncContext.async_id);
args[1] = resource;
args[2] = callback;
for (int i = 0; i < argc; i++) {
args[i + 3] = argv[i];
}
ret = hook_cb->Call(context, recv, args.length(), &args[0]);
} else {
ret = callback->Call(context, recv, argc, argv);
}
if (ret.IsEmpty()) {
scope.MarkAsFailed();
return MaybeLocal<Value>();
}
scope.Close();
if (scope.Failed()) {
return MaybeLocal<Value>();
}
return ret;
}
// Public MakeCallback()s
MaybeLocal<Value> MakeCallback(Isolate* isolate,
Local<Object> recv,
const char* method,
int argc,
Local<Value> argv[],
async_context asyncContext) {
Local<String> method_string;
if (!String::NewFromUtf8(isolate, method).ToLocal(&method_string)) {
return {};
}
return MakeCallback(isolate, recv, method_string, argc, argv, asyncContext);
}
MaybeLocal<Value> MakeCallback(Isolate* isolate,
Local<Object> recv,
Local<String> symbol,
int argc,
Local<Value> argv[],
async_context asyncContext) {
// Check can_call_into_js() first because calling Get() might do so.
Local<Context> context;
if (!recv->GetCreationContext().ToLocal(&context)) {
return {};
}
Environment* env = Environment::GetCurrent(context);
CHECK_NOT_NULL(env);
if (!env->can_call_into_js()) return {};
Local<Value> callback_v;
if (!recv->Get(isolate->GetCurrentContext(), symbol).ToLocal(&callback_v)) {
return {};
}
if (!callback_v->IsFunction()) {
// This used to return an empty value, but Undefined() makes more sense
// since no exception is pending here.
return Undefined(isolate);
}
Local<Function> callback = callback_v.As<Function>();
return MakeCallback(isolate, recv, callback, argc, argv, asyncContext);
}
MaybeLocal<Value> MakeCallback(Isolate* isolate,
Local<Object> recv,
Local<Function> callback,
int argc,
Local<Value> argv[],
async_context asyncContext) {
return InternalMakeCallback(
isolate, recv, callback, argc, argv, asyncContext, Undefined(isolate));
}
MaybeLocal<Value> InternalMakeCallback(Isolate* isolate,
Local<Object> recv,
Local<Function> callback,
int argc,
Local<Value> argv[],
async_context asyncContext,
Local<Value> context_frame) {
// Observe the following two subtleties:
//
// 1. The environment is retrieved from the callback function's context.
// 2. The context to enter is retrieved from the environment.
//
// Because of the AssignToContext() call in src/node_contextify.cc,
// the two contexts need not be the same.
Local<Context> context;
if (!callback->GetCreationContext().ToLocal(&context)) {
return {};
}
Environment* env = Environment::GetCurrent(context);
CHECK_NOT_NULL(env);
Context::Scope context_scope(env->context());
MaybeLocal<Value> ret = InternalMakeCallback(
env, recv, recv, callback, argc, argv, asyncContext, context_frame);
if (ret.IsEmpty() && env->async_callback_scope_depth() == 0) {
// This is only for legacy compatibility and we may want to look into
// removing/adjusting it.
return Undefined(isolate);
}
return ret;
}
// Use this if you just want to safely invoke some JS callback and
// would like to retain the currently active async_context, if any.
// In case none is available, a fixed default context will be
// installed otherwise.
MaybeLocal<Value> MakeSyncCallback(Isolate* isolate,
Local<Object> recv,
Local<Function> callback,
int argc,
Local<Value> argv[]) {
Local<Context> context;
if (!callback->GetCreationContext().ToLocal(&context)) {
return {};
}
Environment* env = Environment::GetCurrent(context);
CHECK_NOT_NULL(env);
if (!env->can_call_into_js()) return {};
Context::Scope context_scope(context);
if (env->async_callback_scope_depth()) {
// There's another MakeCallback() on the stack, piggy back on it.
// In particular, retain the current async_context.
return callback->Call(context, recv, argc, argv);
}
// This is a toplevel invocation and the caller (intentionally)
// didn't provide any async_context to run in. Install a default context.
MaybeLocal<Value> ret = InternalMakeCallback(env,
env->process_object(),
recv,
callback,
argc,
argv,
async_context{0, 0},
Undefined(isolate));
return ret;
}
// Legacy MakeCallback()s
Local<Value> MakeCallback(Isolate* isolate,
Local<Object> recv,
const char* method,
int argc,
Local<Value>* argv) {
EscapableHandleScope handle_scope(isolate);
return handle_scope.Escape(
MakeCallback(isolate, recv, method, argc, argv, {0, 0})
.FromMaybe(Local<Value>()));
}
Local<Value> MakeCallback(Isolate* isolate,
Local<Object> recv,
Local<String> symbol,
int argc,
Local<Value>* argv) {
EscapableHandleScope handle_scope(isolate);
return handle_scope.Escape(
MakeCallback(isolate, recv, symbol, argc, argv, {0, 0})
.FromMaybe(Local<Value>()));
}
Local<Value> MakeCallback(Isolate* isolate,
Local<Object> recv,
Local<Function> callback,
int argc,
Local<Value>* argv) {
EscapableHandleScope handle_scope(isolate);
return handle_scope.Escape(
MakeCallback(isolate, recv, callback, argc, argv, {0, 0})
.FromMaybe(Local<Value>()));
}
} // namespace node

361
src/api/embed_helpers.cc Normal file
View File

@ -0,0 +1,361 @@
#include "debug_utils-inl.h"
#include "env-inl.h"
#include "node.h"
#include "node_snapshot_builder.h"
using v8::Context;
using v8::Function;
using v8::Global;
using v8::HandleScope;
using v8::Isolate;
using v8::Just;
using v8::Local;
using v8::Locker;
using v8::Maybe;
using v8::Nothing;
using v8::SealHandleScope;
using v8::SnapshotCreator;
using v8::TryCatch;
namespace node {
Maybe<ExitCode> SpinEventLoopInternal(Environment* env) {
CHECK_NOT_NULL(env);
MultiIsolatePlatform* platform = GetMultiIsolatePlatform(env);
CHECK_NOT_NULL(platform);
Isolate* isolate = env->isolate();
HandleScope handle_scope(isolate);
Context::Scope context_scope(env->context());
SealHandleScope seal(isolate);
if (env->is_stopping()) return Nothing<ExitCode>();
env->set_trace_sync_io(env->options()->trace_sync_io);
{
bool more;
env->performance_state()->Mark(
node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_START);
do {
if (env->is_stopping()) break;
uv_run(env->event_loop(), UV_RUN_DEFAULT);
if (env->is_stopping()) break;
platform->DrainTasks(isolate);
more = uv_loop_alive(env->event_loop());
if (more && !env->is_stopping()) continue;
if (EmitProcessBeforeExit(env).IsNothing())
break;
{
HandleScope handle_scope(isolate);
if (env->RunSnapshotSerializeCallback().IsEmpty()) {
break;
}
}
// Emit `beforeExit` if the loop became alive either after emitting
// event, or after running some callbacks.
more = uv_loop_alive(env->event_loop());
} while (more == true && !env->is_stopping());
env->performance_state()->Mark(
node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_EXIT);
}
if (env->is_stopping()) return Nothing<ExitCode>();
env->set_trace_sync_io(false);
// Clear the serialize callback even though the JS-land queue should
// be empty this point so that the deserialized instance won't
// attempt to call into JS again.
env->set_snapshot_serialize_callback(Local<Function>());
env->PrintInfoForSnapshotIfDebug();
env->ForEachRealm([](Realm* realm) { realm->VerifyNoStrongBaseObjects(); });
return EmitProcessExitInternal(env);
}
struct CommonEnvironmentSetup::Impl {
MultiIsolatePlatform* platform = nullptr;
uv_loop_t loop;
std::shared_ptr<ArrayBufferAllocator> allocator;
std::optional<SnapshotCreator> snapshot_creator;
Isolate* isolate = nullptr;
DeleteFnPtr<IsolateData, FreeIsolateData> isolate_data;
DeleteFnPtr<Environment, FreeEnvironment> env;
Global<Context> main_context;
};
CommonEnvironmentSetup::CommonEnvironmentSetup(
MultiIsolatePlatform* platform,
std::vector<std::string>* errors,
const EmbedderSnapshotData* snapshot_data,
uint32_t flags,
std::function<Environment*(const CommonEnvironmentSetup*)> make_env,
const SnapshotConfig* snapshot_config)
: impl_(new Impl()) {
CHECK_NOT_NULL(platform);
CHECK_NOT_NULL(errors);
impl_->platform = platform;
uv_loop_t* loop = &impl_->loop;
// Use `data` to tell the destructor whether the loop was initialized or not.
loop->data = nullptr;
int ret = uv_loop_init(loop);
if (ret != 0) {
errors->push_back(
SPrintF("Failed to initialize loop: %s", uv_err_name(ret)));
return;
}
loop->data = this;
impl_->allocator = ArrayBufferAllocator::Create();
const std::vector<intptr_t>& external_references =
SnapshotBuilder::CollectExternalReferences();
Isolate::CreateParams params;
params.array_buffer_allocator = impl_->allocator.get();
params.external_references = external_references.data();
params.external_references = external_references.data();
params.cpp_heap =
v8::CppHeap::Create(platform, v8::CppHeapCreateParams{{}}).release();
Isolate* isolate;
// Isolates created for snapshotting should be set up differently since
// it will be owned by the snapshot creator and needs to be cleaned up
// before serialization.
if (flags & Flags::kIsForSnapshotting) {
// The isolate must be registered before the SnapshotCreator initializes the
// isolate, so that the memory reducer can be initialized.
isolate = impl_->isolate = Isolate::Allocate();
platform->RegisterIsolate(isolate, loop);
impl_->snapshot_creator.emplace(isolate, params);
isolate->SetCaptureStackTraceForUncaughtExceptions(
true,
static_cast<int>(
per_process::cli_options->per_isolate->stack_trace_limit),
v8::StackTrace::StackTraceOptions::kDetailed);
SetIsolateMiscHandlers(isolate, {});
} else {
isolate = impl_->isolate =
NewIsolate(&params,
&impl_->loop,
platform,
SnapshotData::FromEmbedderWrapper(snapshot_data));
}
{
Locker locker(isolate);
Isolate::Scope isolate_scope(isolate);
HandleScope handle_scope(isolate);
TryCatch bootstrapCatch(isolate);
auto print_Exception = OnScopeLeave([&]() {
if (bootstrapCatch.HasCaught()) {
errors->push_back(FormatCaughtException(
isolate, isolate->GetCurrentContext(), bootstrapCatch));
}
});
impl_->isolate_data.reset(CreateIsolateData(
isolate, loop, platform, impl_->allocator.get(), snapshot_data));
impl_->isolate_data->set_snapshot_config(snapshot_config);
if (snapshot_data) {
impl_->env.reset(make_env(this));
if (impl_->env) {
impl_->main_context.Reset(isolate, impl_->env->context());
}
return;
}
Local<Context> context = NewContext(isolate);
impl_->main_context.Reset(isolate, context);
if (context.IsEmpty()) {
errors->push_back("Failed to initialize V8 Context");
return;
}
Context::Scope context_scope(context);
impl_->env.reset(make_env(this));
}
}
CommonEnvironmentSetup::CommonEnvironmentSetup(
MultiIsolatePlatform* platform,
std::vector<std::string>* errors,
std::function<Environment*(const CommonEnvironmentSetup*)> make_env)
: CommonEnvironmentSetup(platform, errors, nullptr, false, make_env) {}
std::unique_ptr<CommonEnvironmentSetup>
CommonEnvironmentSetup::CreateForSnapshotting(
MultiIsolatePlatform* platform,
std::vector<std::string>* errors,
const std::vector<std::string>& args,
const std::vector<std::string>& exec_args,
const SnapshotConfig& snapshot_config) {
// It's not guaranteed that a context that goes through
// v8_inspector::V8Inspector::contextCreated() is runtime-independent,
// so do not start the inspector on the main context when building
// the default snapshot.
uint64_t env_flags =
EnvironmentFlags::kDefaultFlags | EnvironmentFlags::kNoCreateInspector;
auto ret = std::unique_ptr<CommonEnvironmentSetup>(new CommonEnvironmentSetup(
platform,
errors,
nullptr,
true,
[&](const CommonEnvironmentSetup* setup) -> Environment* {
return CreateEnvironment(
setup->isolate_data(),
setup->context(),
args,
exec_args,
static_cast<EnvironmentFlags::Flags>(env_flags));
},
&snapshot_config));
if (!errors->empty()) ret.reset();
return ret;
}
CommonEnvironmentSetup::~CommonEnvironmentSetup() {
if (impl_->isolate != nullptr) {
Isolate* isolate = impl_->isolate;
{
Locker locker(isolate);
Isolate::Scope isolate_scope(isolate);
impl_->main_context.Reset();
impl_->env.reset();
impl_->isolate_data.reset();
}
bool platform_finished = false;
impl_->platform->AddIsolateFinishedCallback(
isolate,
[](void* data) {
bool* ptr = static_cast<bool*>(data);
*ptr = true;
},
&platform_finished);
if (impl_->snapshot_creator.has_value()) {
impl_->snapshot_creator.reset();
}
impl_->platform->DisposeIsolate(isolate);
// Wait until the platform has cleaned up all relevant resources.
while (!platform_finished)
uv_run(&impl_->loop, UV_RUN_ONCE);
}
if (impl_->isolate || impl_->loop.data != nullptr)
CheckedUvLoopClose(&impl_->loop);
delete impl_;
}
EmbedderSnapshotData::Pointer CommonEnvironmentSetup::CreateSnapshot() {
CHECK_NOT_NULL(snapshot_creator());
SnapshotData* snapshot_data = new SnapshotData();
EmbedderSnapshotData::Pointer result{
new EmbedderSnapshotData(snapshot_data, true)};
auto exit_code = SnapshotBuilder::CreateSnapshot(snapshot_data, this);
if (exit_code != ExitCode::kNoFailure) return {};
return result;
}
Maybe<int> SpinEventLoop(Environment* env) {
Maybe<ExitCode> result = SpinEventLoopInternal(env);
if (result.IsNothing()) {
return Nothing<int>();
}
return Just(static_cast<int>(result.FromJust()));
}
uv_loop_t* CommonEnvironmentSetup::event_loop() const {
return &impl_->loop;
}
std::shared_ptr<ArrayBufferAllocator>
CommonEnvironmentSetup::array_buffer_allocator() const {
return impl_->allocator;
}
Isolate* CommonEnvironmentSetup::isolate() const {
return impl_->isolate;
}
IsolateData* CommonEnvironmentSetup::isolate_data() const {
return impl_->isolate_data.get();
}
Environment* CommonEnvironmentSetup::env() const {
return impl_->env.get();
}
v8::Local<v8::Context> CommonEnvironmentSetup::context() const {
return impl_->main_context.Get(impl_->isolate);
}
v8::SnapshotCreator* CommonEnvironmentSetup::snapshot_creator() {
return impl_->snapshot_creator ? &impl_->snapshot_creator.value() : nullptr;
}
void EmbedderSnapshotData::DeleteSnapshotData::operator()(
const EmbedderSnapshotData* data) const {
CHECK_IMPLIES(data->owns_impl_, data->impl_);
if (data->owns_impl_ &&
data->impl_->data_ownership == SnapshotData::DataOwnership::kOwned) {
delete data->impl_;
}
delete data;
}
EmbedderSnapshotData::Pointer EmbedderSnapshotData::BuiltinSnapshotData() {
return EmbedderSnapshotData::Pointer{new EmbedderSnapshotData(
SnapshotBuilder::GetEmbeddedSnapshotData(), false)};
}
EmbedderSnapshotData::Pointer EmbedderSnapshotData::FromBlob(
const std::vector<char>& in) {
return FromBlob(std::string_view(in.data(), in.size()));
}
EmbedderSnapshotData::Pointer EmbedderSnapshotData::FromBlob(
std::string_view in) {
SnapshotData* snapshot_data = new SnapshotData();
CHECK_EQ(snapshot_data->data_ownership, SnapshotData::DataOwnership::kOwned);
EmbedderSnapshotData::Pointer result{
new EmbedderSnapshotData(snapshot_data, true)};
if (!SnapshotData::FromBlob(snapshot_data, in)) {
return {};
}
return result;
}
EmbedderSnapshotData::Pointer EmbedderSnapshotData::FromFile(FILE* in) {
return FromBlob(ReadFileSync(in));
}
std::vector<char> EmbedderSnapshotData::ToBlob() const {
return impl_->ToBlob();
}
void EmbedderSnapshotData::ToFile(FILE* out) const {
impl_->ToFile(out);
}
EmbedderSnapshotData::EmbedderSnapshotData(const SnapshotData* impl,
bool owns_impl)
: impl_(impl), owns_impl_(owns_impl) {}
bool EmbedderSnapshotData::CanUseCustomSnapshotPerIsolate() {
return false;
}
} // namespace node

182
src/api/encoding.cc Normal file
View File

@ -0,0 +1,182 @@
#include "node.h"
#include "string_bytes.h"
#include "util-inl.h"
#include "v8.h"
namespace node {
using v8::HandleScope;
using v8::Isolate;
using v8::Local;
using v8::MaybeLocal;
using v8::TryCatch;
using v8::Value;
enum encoding ParseEncoding(const char* encoding,
enum encoding default_encoding) {
switch (encoding[0]) {
case 'u':
case 'U':
// Note: the two first conditions are needed for performance reasons
// as "utf8"/"utf-8" is a common case.
// (same for other cases below)
// utf8, utf16le
if (encoding[1] == 't' && encoding[2] == 'f') {
// Skip `-`
const size_t skip = encoding[3] == '-' ? 4 : 3;
if (encoding[skip] == '8' && encoding[skip + 1] == '\0')
return UTF8;
if (strncmp(encoding + skip, "16le", 5) == 0)
return UCS2;
// ucs2
} else if (encoding[1] == 'c' && encoding[2] == 's') {
const size_t skip = encoding[3] == '-' ? 4 : 3;
if (encoding[skip] == '2' && encoding[skip + 1] == '\0')
return UCS2;
}
if (StringEqualNoCase(encoding, "utf8"))
return UTF8;
if (StringEqualNoCase(encoding, "utf-8"))
return UTF8;
if (StringEqualNoCase(encoding, "ucs2"))
return UCS2;
if (StringEqualNoCase(encoding, "ucs-2"))
return UCS2;
if (StringEqualNoCase(encoding, "utf16le"))
return UCS2;
if (StringEqualNoCase(encoding, "utf-16le"))
return UCS2;
break;
case 'l':
case 'L':
// latin1
if (encoding[1] == 'a') {
if (strncmp(encoding + 2, "tin1", 5) == 0)
return LATIN1;
}
if (StringEqualNoCase(encoding, "latin1"))
return LATIN1;
break;
case 'b':
case 'B':
// binary is a deprecated alias of latin1
if (encoding[1] == 'i') {
if (strncmp(encoding + 2, "nary", 5) == 0)
return LATIN1;
// buffer
} else if (encoding[1] == 'u') {
if (strncmp(encoding + 2, "ffer", 5) == 0)
return BUFFER;
// base64
} else if (encoding[1] == 'a') {
if (strncmp(encoding + 2, "se64", 5) == 0)
return BASE64;
if (strncmp(encoding + 2, "se64url", 8) == 0)
return BASE64URL;
}
if (StringEqualNoCase(encoding, "binary"))
return LATIN1; // BINARY is a deprecated alias of LATIN1.
if (StringEqualNoCase(encoding, "buffer"))
return BUFFER;
if (StringEqualNoCase(encoding, "base64"))
return BASE64;
if (StringEqualNoCase(encoding, "base64url"))
return BASE64URL;
break;
case 'a':
case 'A':
// ascii
if (encoding[1] == 's') {
if (strncmp(encoding + 2, "cii", 4) == 0)
return ASCII;
}
if (StringEqualNoCase(encoding, "ascii"))
return ASCII;
break;
case 'h':
case 'H':
// hex
if (encoding[1] == 'e')
if (encoding[2] == 'x' && encoding[3] == '\0')
return HEX;
if (StringEqualNoCase(encoding, "hex"))
return HEX;
break;
}
return default_encoding;
}
enum encoding ParseEncoding(Isolate* isolate,
Local<Value> encoding_v,
Local<Value> encoding_id,
enum encoding default_encoding) {
if (encoding_id->IsUint32()) {
return static_cast<enum encoding>(encoding_id.As<v8::Uint32>()->Value());
}
return ParseEncoding(isolate, encoding_v, default_encoding);
}
enum encoding ParseEncoding(Isolate* isolate,
Local<Value> encoding_v,
enum encoding default_encoding) {
CHECK(!encoding_v.IsEmpty());
if (!encoding_v->IsString())
return default_encoding;
Utf8Value encoding(isolate, encoding_v);
return ParseEncoding(*encoding, default_encoding);
}
MaybeLocal<Value> TryEncode(Isolate* isolate,
const char* buf,
size_t len,
enum encoding encoding) {
CHECK_NE(encoding, UCS2);
return StringBytes::Encode(isolate, buf, len, encoding);
}
MaybeLocal<Value> TryEncode(Isolate* isolate, const uint16_t* buf, size_t len) {
return StringBytes::Encode(isolate, buf, len);
}
Local<Value> Encode(Isolate* isolate,
const char* buf,
size_t len,
enum encoding encoding) {
CHECK_NE(encoding, UCS2);
TryCatch try_catch(isolate);
return StringBytes::Encode(isolate, buf, len, encoding).ToLocalChecked();
}
Local<Value> Encode(Isolate* isolate, const uint16_t* buf, size_t len) {
TryCatch try_catch(isolate);
return StringBytes::Encode(isolate, buf, len).ToLocalChecked();
}
// Returns -1 if the handle was not valid for decoding
ssize_t DecodeBytes(Isolate* isolate,
Local<Value> val,
enum encoding encoding) {
HandleScope scope(isolate);
return StringBytes::Size(isolate, val, encoding).FromMaybe(-1);
}
// Returns number of bytes written.
ssize_t DecodeWrite(Isolate* isolate,
char* buf,
size_t buflen,
Local<Value> val,
enum encoding encoding) {
return StringBytes::Write(isolate, buf, buflen, val, encoding);
}
} // namespace node

983
src/api/environment.cc Normal file
View File

@ -0,0 +1,983 @@
#include <cstdlib>
#include "env_properties.h"
#include "node.h"
#include "node_builtins.h"
#include "node_context_data.h"
#include "node_errors.h"
#include "node_exit_code.h"
#include "node_internals.h"
#include "node_options-inl.h"
#include "node_platform.h"
#include "node_realm-inl.h"
#include "node_shadow_realm.h"
#include "node_snapshot_builder.h"
#include "node_v8_platform-inl.h"
#include "node_wasm_web_api.h"
#include "uv.h"
#ifdef NODE_ENABLE_VTUNE_PROFILING
#include "../deps/v8/src/third_party/vtune/v8-vtune.h"
#endif
#if HAVE_INSPECTOR
#include "inspector/worker_inspector.h" // ParentInspectorHandle
#endif
#include "v8-cppgc.h"
namespace node {
using errors::TryCatchScope;
using v8::Array;
using v8::Boolean;
using v8::Context;
using v8::CppHeap;
using v8::CppHeapCreateParams;
using v8::EscapableHandleScope;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::HandleScope;
using v8::Isolate;
using v8::Just;
using v8::JustVoid;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Nothing;
using v8::Null;
using v8::Object;
using v8::ObjectTemplate;
using v8::Private;
using v8::PropertyDescriptor;
using v8::SealHandleScope;
using v8::String;
using v8::Value;
bool AllowWasmCodeGenerationCallback(Local<Context> context,
Local<String>) {
Local<Value> wasm_code_gen =
context->GetEmbedderData(ContextEmbedderIndex::kAllowWasmCodeGeneration);
return wasm_code_gen->IsUndefined() || wasm_code_gen->IsTrue();
}
bool ShouldAbortOnUncaughtException(Isolate* isolate) {
DebugSealHandleScope scope(isolate);
Environment* env = Environment::GetCurrent(isolate);
return env != nullptr &&
(env->is_main_thread() || !env->is_stopping()) &&
env->abort_on_uncaught_exception() &&
env->should_abort_on_uncaught_toggle()[0] &&
!env->inside_should_not_abort_on_uncaught_scope();
}
MaybeLocal<Value> PrepareStackTraceCallback(Local<Context> context,
Local<Value> exception,
Local<Array> trace) {
Environment* env = Environment::GetCurrent(context);
if (env == nullptr) {
return exception->ToString(context).FromMaybe(Local<Value>());
}
Realm* realm = Realm::GetCurrent(context);
Local<Function> prepare;
if (realm != nullptr) {
// If we are in a Realm, call the realm specific prepareStackTrace callback
// to avoid passing the JS objects (the exception and trace) across the
// realm boundary with the `Error.prepareStackTrace` override.
prepare = realm->prepare_stack_trace_callback();
} else {
// The context is created with ContextifyContext, call the principal
// realm's prepareStackTrace callback.
prepare = env->principal_realm()->prepare_stack_trace_callback();
}
if (prepare.IsEmpty()) {
return exception->ToString(context).FromMaybe(Local<Value>());
}
Local<Value> args[] = {
context->Global(),
exception,
trace,
};
// This TryCatch + Rethrow is required by V8 due to details around exception
// handling there. For C++ callbacks, V8 expects a scheduled exception (which
// is what ReThrow gives us). Just returning the empty MaybeLocal would leave
// us with a pending exception.
TryCatchScope try_catch(env);
MaybeLocal<Value> result =
prepare->Call(context, Undefined(env->isolate()), arraysize(args), args);
if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
try_catch.ReThrow();
}
return result;
}
void* NodeArrayBufferAllocator::Allocate(size_t size) {
void* ret;
if (zero_fill_field_ || per_process::cli_options->zero_fill_all_buffers)
ret = allocator_->Allocate(size);
else
ret = allocator_->AllocateUninitialized(size);
if (ret != nullptr) [[likely]] {
total_mem_usage_.fetch_add(size, std::memory_order_relaxed);
}
return ret;
}
void* NodeArrayBufferAllocator::AllocateUninitialized(size_t size) {
void* ret = allocator_->AllocateUninitialized(size);
if (ret != nullptr) [[likely]] {
total_mem_usage_.fetch_add(size, std::memory_order_relaxed);
}
return ret;
}
void NodeArrayBufferAllocator::Free(void* data, size_t size) {
total_mem_usage_.fetch_sub(size, std::memory_order_relaxed);
allocator_->Free(data, size);
}
DebuggingArrayBufferAllocator::~DebuggingArrayBufferAllocator() {
CHECK(allocations_.empty());
}
void* DebuggingArrayBufferAllocator::Allocate(size_t size) {
Mutex::ScopedLock lock(mutex_);
void* data = NodeArrayBufferAllocator::Allocate(size);
RegisterPointerInternal(data, size);
return data;
}
void* DebuggingArrayBufferAllocator::AllocateUninitialized(size_t size) {
Mutex::ScopedLock lock(mutex_);
void* data = NodeArrayBufferAllocator::AllocateUninitialized(size);
RegisterPointerInternal(data, size);
return data;
}
void DebuggingArrayBufferAllocator::Free(void* data, size_t size) {
Mutex::ScopedLock lock(mutex_);
UnregisterPointerInternal(data, size);
NodeArrayBufferAllocator::Free(data, size);
}
void DebuggingArrayBufferAllocator::RegisterPointer(void* data, size_t size) {
Mutex::ScopedLock lock(mutex_);
NodeArrayBufferAllocator::RegisterPointer(data, size);
RegisterPointerInternal(data, size);
}
void DebuggingArrayBufferAllocator::UnregisterPointer(void* data, size_t size) {
Mutex::ScopedLock lock(mutex_);
NodeArrayBufferAllocator::UnregisterPointer(data, size);
UnregisterPointerInternal(data, size);
}
void DebuggingArrayBufferAllocator::UnregisterPointerInternal(void* data,
size_t size) {
if (data == nullptr) return;
auto it = allocations_.find(data);
CHECK_NE(it, allocations_.end());
if (size > 0) {
// We allow allocations with size 1 for 0-length buffers to avoid having
// to deal with nullptr values.
CHECK_EQ(it->second, size);
}
allocations_.erase(it);
}
void DebuggingArrayBufferAllocator::RegisterPointerInternal(void* data,
size_t size) {
if (data == nullptr) return;
CHECK_EQ(allocations_.count(data), 0);
allocations_[data] = size;
}
std::unique_ptr<ArrayBufferAllocator> ArrayBufferAllocator::Create(bool debug) {
if (debug || per_process::cli_options->debug_arraybuffer_allocations)
return std::make_unique<DebuggingArrayBufferAllocator>();
else
return std::make_unique<NodeArrayBufferAllocator>();
}
ArrayBufferAllocator* CreateArrayBufferAllocator() {
return ArrayBufferAllocator::Create().release();
}
void FreeArrayBufferAllocator(ArrayBufferAllocator* allocator) {
delete allocator;
}
void SetIsolateCreateParamsForNode(Isolate::CreateParams* params) {
const uint64_t constrained_memory = uv_get_constrained_memory();
const uint64_t total_memory = constrained_memory > 0 ?
std::min(uv_get_total_memory(), constrained_memory) :
uv_get_total_memory();
if (total_memory > 0 &&
params->constraints.max_old_generation_size_in_bytes() == 0) {
// V8 defaults to 700MB or 1.4GB on 32 and 64 bit platforms respectively.
// This default is based on browser use-cases. Tell V8 to configure the
// heap based on the actual physical memory.
params->constraints.ConfigureDefaults(total_memory, 0);
}
params->embedder_wrapper_object_index = BaseObject::InternalFields::kSlot;
params->embedder_wrapper_type_index = std::numeric_limits<int>::max();
#ifdef NODE_ENABLE_VTUNE_PROFILING
params->code_event_handler = vTune::GetVtuneCodeEventHandler();
#endif
}
void SetIsolateErrorHandlers(v8::Isolate* isolate, const IsolateSettings& s) {
if (s.flags & MESSAGE_LISTENER_WITH_ERROR_LEVEL)
isolate->AddMessageListenerWithErrorLevel(
errors::PerIsolateMessageListener,
Isolate::MessageErrorLevel::kMessageError |
Isolate::MessageErrorLevel::kMessageWarning);
auto* abort_callback = s.should_abort_on_uncaught_exception_callback ?
s.should_abort_on_uncaught_exception_callback :
ShouldAbortOnUncaughtException;
isolate->SetAbortOnUncaughtExceptionCallback(abort_callback);
auto* fatal_error_cb = s.fatal_error_callback ?
s.fatal_error_callback : OnFatalError;
isolate->SetFatalErrorHandler(fatal_error_cb);
auto* oom_error_cb =
s.oom_error_callback ? s.oom_error_callback : OOMErrorHandler;
isolate->SetOOMErrorHandler(oom_error_cb);
if ((s.flags & SHOULD_NOT_SET_PREPARE_STACK_TRACE_CALLBACK) == 0) {
auto* prepare_stack_trace_cb = s.prepare_stack_trace_callback ?
s.prepare_stack_trace_callback : PrepareStackTraceCallback;
isolate->SetPrepareStackTraceCallback(prepare_stack_trace_cb);
}
}
void SetIsolateMiscHandlers(v8::Isolate* isolate, const IsolateSettings& s) {
isolate->SetMicrotasksPolicy(s.policy);
auto* allow_wasm_codegen_cb = s.allow_wasm_code_generation_callback ?
s.allow_wasm_code_generation_callback : AllowWasmCodeGenerationCallback;
isolate->SetAllowWasmCodeGenerationCallback(allow_wasm_codegen_cb);
auto* modify_code_generation_from_strings_callback =
ModifyCodeGenerationFromStrings;
if (s.modify_code_generation_from_strings_callback != nullptr) {
modify_code_generation_from_strings_callback =
s.modify_code_generation_from_strings_callback;
}
isolate->SetModifyCodeGenerationFromStringsCallback(
modify_code_generation_from_strings_callback);
Mutex::ScopedLock lock(node::per_process::cli_options_mutex);
if (per_process::cli_options->get_per_isolate_options()
->get_per_env_options()
->experimental_fetch) {
isolate->SetWasmStreamingCallback(wasm_web_api::StartStreamingCompilation);
}
if (per_process::cli_options->get_per_isolate_options()
->experimental_shadow_realm) {
isolate->SetHostCreateShadowRealmContextCallback(
shadow_realm::HostCreateShadowRealmContextCallback);
}
if ((s.flags & SHOULD_NOT_SET_PROMISE_REJECTION_CALLBACK) == 0) {
auto* promise_reject_cb = s.promise_reject_callback ?
s.promise_reject_callback : PromiseRejectCallback;
isolate->SetPromiseRejectCallback(promise_reject_cb);
}
if (s.flags & DETAILED_SOURCE_POSITIONS_FOR_PROFILING)
v8::CpuProfiler::UseDetailedSourcePositionsForProfiling(isolate);
}
void SetIsolateUpForNode(v8::Isolate* isolate,
const IsolateSettings& settings) {
Isolate::Scope isolate_scope(isolate);
SetIsolateErrorHandlers(isolate, settings);
SetIsolateMiscHandlers(isolate, settings);
}
void SetIsolateUpForNode(v8::Isolate* isolate) {
IsolateSettings settings;
SetIsolateUpForNode(isolate, settings);
}
// TODO(joyeecheung): we may want to expose this, but then we need to be
// careful about what we override in the params.
Isolate* NewIsolate(Isolate::CreateParams* params,
uv_loop_t* event_loop,
MultiIsolatePlatform* platform,
const SnapshotData* snapshot_data,
const IsolateSettings& settings) {
Isolate* isolate = Isolate::Allocate();
if (isolate == nullptr) return nullptr;
if (snapshot_data != nullptr) {
SnapshotBuilder::InitializeIsolateParams(snapshot_data, params);
}
{
// Because it uses a shared readonly-heap, V8 requires all snapshots used
// for creating Isolates to be identical. This isn't really memory-safe
// but also otherwise just doesn't work, and the only real alternative
// is disabling shared-readonly-heap mode altogether.
static Isolate::CreateParams first_params = *params;
params->snapshot_blob = first_params.snapshot_blob;
params->external_references = first_params.external_references;
}
// Register the isolate on the platform before the isolate gets initialized,
// so that the isolate can access the platform during initialization.
platform->RegisterIsolate(isolate, event_loop);
// Ensure that there is always a CppHeap.
if (settings.cpp_heap == nullptr) {
params->cpp_heap =
CppHeap::Create(platform, CppHeapCreateParams{{}}).release();
} else {
params->cpp_heap = settings.cpp_heap;
}
SetIsolateCreateParamsForNode(params);
Isolate::Initialize(isolate, *params);
Isolate::Scope isolate_scope(isolate);
if (snapshot_data == nullptr) {
// If in deserialize mode, delay until after the deserialization is
// complete.
SetIsolateUpForNode(isolate, settings);
} else {
SetIsolateMiscHandlers(isolate, settings);
}
return isolate;
}
Isolate* NewIsolate(ArrayBufferAllocator* allocator,
uv_loop_t* event_loop,
MultiIsolatePlatform* platform,
const EmbedderSnapshotData* snapshot_data,
const IsolateSettings& settings) {
Isolate::CreateParams params;
if (allocator != nullptr) params.array_buffer_allocator = allocator;
return NewIsolate(&params,
event_loop,
platform,
SnapshotData::FromEmbedderWrapper(snapshot_data),
settings);
}
Isolate* NewIsolate(std::shared_ptr<ArrayBufferAllocator> allocator,
uv_loop_t* event_loop,
MultiIsolatePlatform* platform,
const EmbedderSnapshotData* snapshot_data,
const IsolateSettings& settings) {
Isolate::CreateParams params;
if (allocator) params.array_buffer_allocator_shared = allocator;
return NewIsolate(&params,
event_loop,
platform,
SnapshotData::FromEmbedderWrapper(snapshot_data),
settings);
}
IsolateData* CreateIsolateData(
Isolate* isolate,
uv_loop_t* loop,
MultiIsolatePlatform* platform,
ArrayBufferAllocator* allocator,
const EmbedderSnapshotData* embedder_snapshot_data) {
return IsolateData::CreateIsolateData(
isolate, loop, platform, allocator, embedder_snapshot_data);
}
void FreeIsolateData(IsolateData* isolate_data) {
delete isolate_data;
}
// Hide the internal handle class from the public API.
#if HAVE_INSPECTOR
struct InspectorParentHandleImpl : public InspectorParentHandle {
std::unique_ptr<inspector::ParentInspectorHandle> impl;
explicit InspectorParentHandleImpl(
std::unique_ptr<inspector::ParentInspectorHandle>&& impl)
: impl(std::move(impl)) {}
};
#endif
Environment* CreateEnvironment(
IsolateData* isolate_data,
Local<Context> context,
const std::vector<std::string>& args,
const std::vector<std::string>& exec_args,
EnvironmentFlags::Flags flags,
ThreadId thread_id,
std::unique_ptr<InspectorParentHandle> inspector_parent_handle) {
Isolate* isolate = isolate_data->isolate();
Isolate::Scope isolate_scope(isolate);
HandleScope handle_scope(isolate);
const bool use_snapshot = context.IsEmpty();
const EnvSerializeInfo* env_snapshot_info = nullptr;
if (use_snapshot) {
CHECK_NOT_NULL(isolate_data->snapshot_data());
env_snapshot_info = &isolate_data->snapshot_data()->env_info;
}
// TODO(addaleax): This is a much better place for parsing per-Environment
// options than the global parse call.
Environment* env = new Environment(isolate_data,
isolate,
args,
exec_args,
env_snapshot_info,
flags,
thread_id);
CHECK_NOT_NULL(env);
if (use_snapshot) {
context = Context::FromSnapshot(isolate,
SnapshotData::kNodeMainContextIndex,
v8::DeserializeInternalFieldsCallback(
DeserializeNodeInternalFields, env),
nullptr,
MaybeLocal<Value>(),
nullptr,
v8::DeserializeContextDataCallback(
DeserializeNodeContextData, env))
.ToLocalChecked();
CHECK(!context.IsEmpty());
Context::Scope context_scope(context);
if (InitializeContextRuntime(context).IsNothing()) {
FreeEnvironment(env);
return nullptr;
}
SetIsolateErrorHandlers(isolate, {});
}
Context::Scope context_scope(context);
env->InitializeMainContext(context, env_snapshot_info);
#if HAVE_INSPECTOR
if (env->should_create_inspector()) {
if (inspector_parent_handle) {
env->InitializeInspector(std::move(
static_cast<InspectorParentHandleImpl*>(inspector_parent_handle.get())
->impl));
} else {
env->InitializeInspector({});
}
}
#endif
if (!use_snapshot && env->principal_realm()->RunBootstrapping().IsEmpty()) {
FreeEnvironment(env);
return nullptr;
}
return env;
}
void FreeEnvironment(Environment* env) {
Isolate* isolate = env->isolate();
Isolate::DisallowJavascriptExecutionScope disallow_js(isolate,
Isolate::DisallowJavascriptExecutionScope::THROW_ON_FAILURE);
{
HandleScope handle_scope(isolate); // For env->context().
Context::Scope context_scope(env->context());
SealHandleScope seal_handle_scope(isolate);
// Set the flag in accordance with the DisallowJavascriptExecutionScope
// above.
env->set_can_call_into_js(false);
env->set_stopping(true);
env->stop_sub_worker_contexts();
env->RunCleanup();
RunAtExit(env);
}
delete env;
}
NODE_EXTERN std::unique_ptr<InspectorParentHandle> GetInspectorParentHandle(
Environment* env,
ThreadId thread_id,
const char* url) {
return GetInspectorParentHandle(env, thread_id, url, "");
}
NODE_EXTERN std::unique_ptr<InspectorParentHandle> GetInspectorParentHandle(
Environment* env, ThreadId thread_id, const char* url, const char* name) {
CHECK_NOT_NULL(env);
if (name == nullptr) name = "";
CHECK_NE(thread_id.id, static_cast<uint64_t>(-1));
if (!env->should_create_inspector()) {
return nullptr;
}
#if HAVE_INSPECTOR
return std::make_unique<InspectorParentHandleImpl>(
env->inspector_agent()->GetParentHandle(thread_id.id, url, name));
#else
return {};
#endif
}
MaybeLocal<Value> LoadEnvironment(Environment* env,
StartExecutionCallback cb,
EmbedderPreloadCallback preload) {
env->InitializeLibuv();
env->InitializeDiagnostics();
if (preload) {
env->set_embedder_preload(std::move(preload));
}
env->InitializeCompileCache();
return StartExecution(env, cb);
}
MaybeLocal<Value> LoadEnvironment(Environment* env,
std::string_view main_script_source_utf8,
EmbedderPreloadCallback preload) {
// It could be empty when it's used by SEA to load an empty script.
CHECK_IMPLIES(main_script_source_utf8.size() > 0,
main_script_source_utf8.data());
return LoadEnvironment(
env,
[&](const StartExecutionCallbackInfo& info) -> MaybeLocal<Value> {
Local<Value> main_script;
if (!ToV8Value(env->context(), main_script_source_utf8)
.ToLocal(&main_script)) {
return {};
}
return info.run_cjs->Call(
env->context(), Null(env->isolate()), 1, &main_script);
},
std::move(preload));
}
Environment* GetCurrentEnvironment(Local<Context> context) {
return Environment::GetCurrent(context);
}
IsolateData* GetEnvironmentIsolateData(Environment* env) {
return env->isolate_data();
}
ArrayBufferAllocator* GetArrayBufferAllocator(IsolateData* isolate_data) {
return isolate_data->node_allocator();
}
Local<Context> GetMainContext(Environment* env) {
return env->context();
}
MultiIsolatePlatform* GetMultiIsolatePlatform(Environment* env) {
return GetMultiIsolatePlatform(env->isolate_data());
}
MultiIsolatePlatform* GetMultiIsolatePlatform(IsolateData* env) {
return env->platform();
}
MultiIsolatePlatform* CreatePlatform(
int thread_pool_size,
node::tracing::TracingController* tracing_controller) {
return CreatePlatform(
thread_pool_size,
static_cast<v8::TracingController*>(tracing_controller));
}
MultiIsolatePlatform* CreatePlatform(
int thread_pool_size,
v8::TracingController* tracing_controller) {
return MultiIsolatePlatform::Create(thread_pool_size,
tracing_controller)
.release();
}
void FreePlatform(MultiIsolatePlatform* platform) {
delete platform;
}
std::unique_ptr<MultiIsolatePlatform> MultiIsolatePlatform::Create(
int thread_pool_size,
v8::TracingController* tracing_controller,
v8::PageAllocator* page_allocator) {
return std::make_unique<NodePlatform>(thread_pool_size,
tracing_controller,
page_allocator);
}
MaybeLocal<Object> GetPerContextExports(Local<Context> context,
IsolateData* isolate_data) {
Isolate* isolate = context->GetIsolate();
EscapableHandleScope handle_scope(isolate);
Local<Object> global = context->Global();
Local<Private> key = Private::ForApi(isolate,
FIXED_ONE_BYTE_STRING(isolate, "node:per_context_binding_exports"));
Local<Value> existing_value;
if (!global->GetPrivate(context, key).ToLocal(&existing_value))
return MaybeLocal<Object>();
if (existing_value->IsObject())
return handle_scope.Escape(existing_value.As<Object>());
// To initialize the per-context binding exports, a non-nullptr isolate_data
// is needed
CHECK(isolate_data);
Local<Object> exports = Object::New(isolate);
if (context->Global()->SetPrivate(context, key, exports).IsNothing() ||
InitializePrimordials(context, isolate_data).IsNothing()) {
return MaybeLocal<Object>();
}
return handle_scope.Escape(exports);
}
// Any initialization logic should be performed in
// InitializeContext, because embedders don't necessarily
// call NewContext and so they will experience breakages.
Local<Context> NewContext(Isolate* isolate,
Local<ObjectTemplate> object_template) {
auto context = Context::New(isolate, nullptr, object_template);
if (context.IsEmpty()) return context;
if (InitializeContext(context).IsNothing()) {
return Local<Context>();
}
return context;
}
void ProtoThrower(const FunctionCallbackInfo<Value>& info) {
THROW_ERR_PROTO_ACCESS(info.GetIsolate());
}
// This runs at runtime, regardless of whether the context
// is created from a snapshot.
Maybe<void> InitializeContextRuntime(Local<Context> context) {
Isolate* isolate = context->GetIsolate();
HandleScope handle_scope(isolate);
// When `IsCodeGenerationFromStringsAllowed` is true, V8 takes the fast path
// and ignores the ModifyCodeGenerationFromStrings callback. Set it to false
// to delegate the code generation validation to
// node::ModifyCodeGenerationFromStrings.
// The `IsCodeGenerationFromStringsAllowed` can be refreshed by V8 according
// to the runtime flags, propagate the value to the embedder data.
bool is_code_generation_from_strings_allowed =
context->IsCodeGenerationFromStringsAllowed();
context->AllowCodeGenerationFromStrings(false);
context->SetEmbedderData(
ContextEmbedderIndex::kAllowCodeGenerationFromStrings,
Boolean::New(isolate, is_code_generation_from_strings_allowed));
if (per_process::cli_options->disable_proto == "") {
return JustVoid();
}
// Remove __proto__
// https://github.com/nodejs/node/issues/31951
Local<Object> prototype;
{
Local<String> object_string =
FIXED_ONE_BYTE_STRING(isolate, "Object");
Local<String> prototype_string =
FIXED_ONE_BYTE_STRING(isolate, "prototype");
Local<Value> object_v;
if (!context->Global()
->Get(context, object_string)
.ToLocal(&object_v)) {
return Nothing<void>();
}
Local<Value> prototype_v;
if (!object_v.As<Object>()
->Get(context, prototype_string)
.ToLocal(&prototype_v)) {
return Nothing<void>();
}
prototype = prototype_v.As<Object>();
}
Local<String> proto_string =
FIXED_ONE_BYTE_STRING(isolate, "__proto__");
if (per_process::cli_options->disable_proto == "delete") {
if (prototype
->Delete(context, proto_string)
.IsNothing()) {
return Nothing<void>();
}
} else if (per_process::cli_options->disable_proto == "throw") {
Local<Value> thrower;
if (!Function::New(context, ProtoThrower)
.ToLocal(&thrower)) {
return Nothing<void>();
}
PropertyDescriptor descriptor(thrower, thrower);
descriptor.set_enumerable(false);
descriptor.set_configurable(true);
if (prototype
->DefineProperty(context, proto_string, descriptor)
.IsNothing()) {
return Nothing<void>();
}
} else if (per_process::cli_options->disable_proto != "") {
// Validated in ProcessGlobalArgs
UNREACHABLE("invalid --disable-proto mode");
}
return JustVoid();
}
Maybe<void> InitializeBaseContextForSnapshot(Local<Context> context) {
Isolate* isolate = context->GetIsolate();
HandleScope handle_scope(isolate);
// Delete `Intl.v8BreakIterator`
// https://github.com/nodejs/node/issues/14909
{
Context::Scope context_scope(context);
Local<String> intl_string = FIXED_ONE_BYTE_STRING(isolate, "Intl");
Local<String> break_iter_string =
FIXED_ONE_BYTE_STRING(isolate, "v8BreakIterator");
Local<Value> intl_v;
if (!context->Global()->Get(context, intl_string).ToLocal(&intl_v)) {
return Nothing<void>();
}
if (intl_v->IsObject() &&
intl_v.As<Object>()->Delete(context, break_iter_string).IsNothing()) {
return Nothing<void>();
}
}
return JustVoid();
}
Maybe<void> InitializeMainContextForSnapshot(Local<Context> context) {
Isolate* isolate = context->GetIsolate();
HandleScope handle_scope(isolate);
// Initialize the default values.
context->SetEmbedderData(ContextEmbedderIndex::kAllowWasmCodeGeneration,
True(isolate));
context->SetEmbedderData(
ContextEmbedderIndex::kAllowCodeGenerationFromStrings, True(isolate));
if (InitializeBaseContextForSnapshot(context).IsNothing()) {
return Nothing<void>();
}
return JustVoid();
}
MaybeLocal<Object> InitializePrivateSymbols(Local<Context> context,
IsolateData* isolate_data) {
CHECK(isolate_data);
Isolate* isolate = context->GetIsolate();
EscapableHandleScope scope(isolate);
Context::Scope context_scope(context);
Local<ObjectTemplate> private_symbols = ObjectTemplate::New(isolate);
Local<Object> private_symbols_object;
#define V(PropertyName, _) \
private_symbols->Set(isolate, #PropertyName, isolate_data->PropertyName());
PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(V)
#undef V
if (!private_symbols->NewInstance(context).ToLocal(&private_symbols_object) ||
private_symbols_object->SetPrototypeV2(context, Null(isolate))
.IsNothing()) {
return MaybeLocal<Object>();
}
return scope.Escape(private_symbols_object);
}
Maybe<void> InitializePrimordials(Local<Context> context,
IsolateData* isolate_data) {
// Run per-context JS files.
Isolate* isolate = context->GetIsolate();
Context::Scope context_scope(context);
Local<Object> exports;
if (!GetPerContextExports(context).ToLocal(&exports)) {
return Nothing<void>();
}
Local<String> primordials_string =
FIXED_ONE_BYTE_STRING(isolate, "primordials");
// Ensure that `InitializePrimordials` is called exactly once on a given
// context.
CHECK(!exports->Has(context, primordials_string).FromJust());
Local<Object> primordials =
Object::New(isolate, Null(isolate), nullptr, nullptr, 0);
// Create primordials and make it available to per-context scripts.
if (exports->Set(context, primordials_string, primordials).IsNothing()) {
return Nothing<void>();
}
Local<Object> private_symbols;
if (!InitializePrivateSymbols(context, isolate_data)
.ToLocal(&private_symbols)) {
return Nothing<void>();
}
static const char* context_files[] = {"internal/per_context/primordials",
"internal/per_context/domexception",
"internal/per_context/messageport",
nullptr};
// We do not have access to a per-Environment BuiltinLoader instance
// at this point, because this code runs before an Environment exists
// in the first place. However, creating BuiltinLoader instances is
// relatively cheap and all the scripts that we may want to run at
// startup are always present in it.
thread_local builtins::BuiltinLoader builtin_loader;
// Primordials can always be just eagerly compiled.
builtin_loader.SetEagerCompile();
for (const char** module = context_files; *module != nullptr; module++) {
Local<Value> arguments[] = {exports, primordials, private_symbols};
if (builtin_loader
.CompileAndCall(
context, *module, arraysize(arguments), arguments, nullptr)
.IsEmpty()) {
// Execution failed during context creation.
return Nothing<void>();
}
}
return JustVoid();
}
// This initializes the main context (i.e. vm contexts are not included).
Maybe<bool> InitializeContext(Local<Context> context) {
if (InitializeMainContextForSnapshot(context).IsNothing()) {
return Nothing<bool>();
}
if (InitializeContextRuntime(context).IsNothing()) {
return Nothing<bool>();
}
return Just(true);
}
uv_loop_t* GetCurrentEventLoop(Isolate* isolate) {
HandleScope handle_scope(isolate);
Local<Context> context = isolate->GetCurrentContext();
if (context.IsEmpty()) return nullptr;
Environment* env = Environment::GetCurrent(context);
if (env == nullptr) return nullptr;
return env->event_loop();
}
void AddLinkedBinding(Environment* env, const node_module& mod) {
CHECK_NOT_NULL(env);
Mutex::ScopedLock lock(env->extra_linked_bindings_mutex());
node_module* prev_tail = env->extra_linked_bindings_tail();
env->extra_linked_bindings()->push_back(mod);
if (prev_tail != nullptr)
prev_tail->nm_link = &env->extra_linked_bindings()->back();
}
void AddLinkedBinding(Environment* env, const napi_module& mod) {
node_module node_mod = napi_module_to_node_module(&mod);
node_mod.nm_flags = NM_F_LINKED;
AddLinkedBinding(env, node_mod);
}
void AddLinkedBinding(Environment* env,
const char* name,
addon_context_register_func fn,
void* priv) {
node_module mod = {
NODE_MODULE_VERSION,
NM_F_LINKED,
nullptr, // nm_dso_handle
nullptr, // nm_filename
nullptr, // nm_register_func
fn,
name,
priv,
nullptr // nm_link
};
AddLinkedBinding(env, mod);
}
void AddLinkedBinding(Environment* env,
const char* name,
napi_addon_register_func fn,
int32_t module_api_version) {
node_module mod = {
-1, // nm_version for Node-API
NM_F_LINKED, // nm_flags
nullptr, // nm_dso_handle
nullptr, // nm_filename
nullptr, // nm_register_func
get_node_api_context_register_func(env, name, module_api_version),
name, // nm_modname
reinterpret_cast<void*>(fn), // nm_priv
nullptr // nm_link
};
AddLinkedBinding(env, mod);
}
static std::atomic<uint64_t> next_thread_id{0};
ThreadId AllocateEnvironmentThreadId() {
return ThreadId { next_thread_id++ };
}
[[noreturn]] void Exit(ExitCode exit_code) {
exit(static_cast<int>(exit_code));
}
void DefaultProcessExitHandlerInternal(Environment* env, ExitCode exit_code) {
env->set_stopping(true);
env->set_can_call_into_js(false);
env->stop_sub_worker_contexts();
env->isolate()->DumpAndResetStats();
// The tracing agent could be in the process of writing data using the
// threadpool. Stop it before shutting down libuv. The rest of the tracing
// agent disposal will be performed in DisposePlatform().
per_process::v8_platform.StopTracingAgent();
// When the process exits, the tasks in the thread pool may also need to
// access the data of V8Platform, such as trace agent, or a field
// added in the future. So make sure the thread pool exits first.
// And make sure V8Platform don not call into Libuv threadpool, see Dispose
// in node_v8_platform-inl.h
uv_library_shutdown();
DisposePlatform();
Exit(exit_code);
}
void DefaultProcessExitHandler(Environment* env, int exit_code) {
DefaultProcessExitHandlerInternal(env, static_cast<ExitCode>(exit_code));
}
void SetProcessExitHandler(
Environment* env, std::function<void(Environment*, ExitCode)>&& handler) {
env->set_process_exit_handler(std::move(handler));
}
void SetProcessExitHandler(Environment* env,
std::function<void(Environment*, int)>&& handler) {
auto movedHandler = std::move(handler);
env->set_process_exit_handler([=](Environment* env, ExitCode exit_code) {
movedHandler(env, static_cast<int>(exit_code));
});
}
} // namespace node

250
src/api/exceptions.cc Normal file
View File

@ -0,0 +1,250 @@
// This file contains implementation of error APIs exposed in node.h
#include "env-inl.h"
#include "node.h"
#include "node_errors.h"
#include "util-inl.h"
#include "uv.h"
#include "v8.h"
#include <cstring>
namespace node {
using v8::Context;
using v8::Exception;
using v8::Integer;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;
Local<Value> ErrnoException(Isolate* isolate,
int errorno,
const char* syscall,
const char* msg,
const char* path) {
Environment* env = Environment::GetCurrent(isolate);
CHECK_NOT_NULL(env);
Local<Value> e;
Local<String> estring = OneByteString(isolate, errors::errno_string(errorno));
if (msg == nullptr || msg[0] == '\0') {
msg = strerror(errorno);
}
Local<String> message = OneByteString(isolate, msg);
Local<String> cons =
String::Concat(isolate, estring, FIXED_ONE_BYTE_STRING(isolate, ", "));
cons = String::Concat(isolate, cons, message);
Local<String> path_string;
if (path != nullptr) {
// FIXME(bnoordhuis) It's questionable to interpret the file path as UTF-8.
path_string = String::NewFromUtf8(isolate, path).ToLocalChecked();
}
if (path_string.IsEmpty() == false) {
cons = String::Concat(isolate, cons, FIXED_ONE_BYTE_STRING(isolate, " '"));
cons = String::Concat(isolate, cons, path_string);
cons = String::Concat(isolate, cons, FIXED_ONE_BYTE_STRING(isolate, "'"));
}
e = Exception::Error(cons);
Local<Context> context = env->context();
Local<Object> obj = e.As<Object>();
obj->Set(context,
env->errno_string(),
Integer::New(isolate, errorno)).Check();
obj->Set(context, env->code_string(), estring).Check();
if (path_string.IsEmpty() == false) {
obj->Set(context, env->path_string(), path_string).Check();
}
if (syscall != nullptr) {
obj->Set(context,
env->syscall_string(),
OneByteString(isolate, syscall)).Check();
}
return e;
}
static Local<String> StringFromPath(Isolate* isolate, const char* path) {
#ifdef _WIN32
if (strncmp(path, "\\\\?\\UNC\\", 8) == 0) {
return String::Concat(
isolate,
FIXED_ONE_BYTE_STRING(isolate, "\\\\"),
String::NewFromUtf8(isolate, path + 8).ToLocalChecked());
} else if (strncmp(path, "\\\\?\\", 4) == 0) {
return String::NewFromUtf8(isolate, path + 4).ToLocalChecked();
}
#endif
return String::NewFromUtf8(isolate, path).ToLocalChecked();
}
Local<Value> UVException(Isolate* isolate,
int errorno,
const char* syscall,
const char* msg,
const char* path,
const char* dest) {
Environment* env = Environment::GetCurrent(isolate);
CHECK_NOT_NULL(env);
if (!msg || !msg[0])
msg = uv_strerror(errorno);
Local<String> js_code = OneByteString(isolate, uv_err_name(errorno));
Local<String> js_syscall = OneByteString(isolate, syscall);
Local<String> js_path;
Local<String> js_dest;
Local<String> js_msg = js_code;
js_msg =
String::Concat(isolate, js_msg, FIXED_ONE_BYTE_STRING(isolate, ": "));
js_msg = String::Concat(isolate, js_msg, OneByteString(isolate, msg));
js_msg =
String::Concat(isolate, js_msg, FIXED_ONE_BYTE_STRING(isolate, ", "));
js_msg = String::Concat(isolate, js_msg, js_syscall);
if (path != nullptr) {
js_path = StringFromPath(isolate, path);
js_msg =
String::Concat(isolate, js_msg, FIXED_ONE_BYTE_STRING(isolate, " '"));
js_msg = String::Concat(isolate, js_msg, js_path);
js_msg =
String::Concat(isolate, js_msg, FIXED_ONE_BYTE_STRING(isolate, "'"));
}
if (dest != nullptr) {
js_dest = StringFromPath(isolate, dest);
js_msg = String::Concat(
isolate, js_msg, FIXED_ONE_BYTE_STRING(isolate, " -> '"));
js_msg = String::Concat(isolate, js_msg, js_dest);
js_msg =
String::Concat(isolate, js_msg, FIXED_ONE_BYTE_STRING(isolate, "'"));
}
Local<Object> e =
Exception::Error(js_msg)->ToObject(isolate->GetCurrentContext())
.ToLocalChecked();
Local<Context> context = env->context();
e->Set(context,
env->errno_string(),
Integer::New(isolate, errorno)).Check();
e->Set(context, env->code_string(), js_code).Check();
e->Set(context, env->syscall_string(), js_syscall).Check();
if (!js_path.IsEmpty())
e->Set(context, env->path_string(), js_path).Check();
if (!js_dest.IsEmpty())
e->Set(context, env->dest_string(), js_dest).Check();
return e;
}
#ifdef _WIN32
// Does about the same as strerror(),
// but supports all windows error messages
static const char* winapi_strerror(const int errorno, bool* must_free) {
char* errmsg = nullptr;
FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr,
errorno,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPSTR>(&errmsg),
0,
nullptr);
if (errmsg) {
*must_free = true;
// Remove trailing newlines
for (int i = strlen(errmsg) - 1;
i >= 0 && (errmsg[i] == '\n' || errmsg[i] == '\r');
i--) {
errmsg[i] = '\0';
}
return errmsg;
} else {
// FormatMessage failed
*must_free = false;
return "Unknown error";
}
}
Local<Value> WinapiErrnoException(Isolate* isolate,
int errorno,
const char* syscall,
const char* msg,
const char* path) {
Environment* env = Environment::GetCurrent(isolate);
CHECK_NOT_NULL(env);
Local<Value> e;
bool must_free = false;
if (!msg || !msg[0]) {
msg = winapi_strerror(errorno, &must_free);
}
Local<String> message = OneByteString(isolate, msg);
if (path) {
Local<String> cons1 =
String::Concat(isolate, message, FIXED_ONE_BYTE_STRING(isolate, " '"));
Local<String> cons2 = String::Concat(
isolate,
cons1,
String::NewFromUtf8(isolate, path).ToLocalChecked());
Local<String> cons3 =
String::Concat(isolate, cons2, FIXED_ONE_BYTE_STRING(isolate, "'"));
e = Exception::Error(cons3);
} else {
e = Exception::Error(message);
}
Local<Context> context = env->context();
Local<Object> obj = e.As<Object>();
obj->Set(context, env->errno_string(), Integer::New(isolate, errorno))
.Check();
if (path != nullptr) {
obj->Set(context,
env->path_string(),
String::NewFromUtf8(isolate, path).ToLocalChecked())
.Check();
}
if (syscall != nullptr) {
obj->Set(context,
env->syscall_string(),
OneByteString(isolate, syscall))
.Check();
}
if (must_free) {
LocalFree(const_cast<char*>(msg));
}
return e;
}
#endif // _WIN32
// Implement the legacy name exposed in node.h. This has not been in fact
// fatal any more, as the user can handle the exception in the
// TryCatch by listening to `uncaughtException`.
// TODO(joyeecheung): deprecate it in favor of a more accurate name.
void FatalException(Isolate* isolate, const v8::TryCatch& try_catch) {
errors::TriggerUncaughtException(isolate, try_catch);
}
} // namespace node

255
src/api/hooks.cc Normal file
View File

@ -0,0 +1,255 @@
#include "env-inl.h"
#include "node_internals.h"
#include "node_process-inl.h"
#include "async_wrap.h"
namespace node {
using v8::Context;
using v8::HandleScope;
using v8::Integer;
using v8::Isolate;
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::NewStringType;
using v8::Nothing;
using v8::Object;
using v8::String;
void RunAtExit(Environment* env) {
env->RunAtExitCallbacks();
}
void AtExit(Environment* env, void (*cb)(void* arg), void* arg) {
CHECK_NOT_NULL(env);
env->AtExit(cb, arg);
}
void EmitBeforeExit(Environment* env) {
USE(EmitProcessBeforeExit(env));
}
Maybe<bool> EmitProcessBeforeExit(Environment* env) {
TRACE_EVENT0(TRACING_CATEGORY_NODE1(environment), "BeforeExit");
if (!env->destroy_async_id_list()->empty())
AsyncWrap::DestroyAsyncIdsCallback(env);
Isolate* isolate = env->isolate();
HandleScope handle_scope(isolate);
Context::Scope context_scope(env->context());
if (!env->can_call_into_js()) {
return Nothing<bool>();
}
Local<Integer> exit_code = Integer::New(
isolate, static_cast<int32_t>(env->exit_code(ExitCode::kNoFailure)));
return ProcessEmit(env, "beforeExit", exit_code).IsEmpty() ? Nothing<bool>()
: Just(true);
}
static ExitCode EmitExitInternal(Environment* env) {
return EmitProcessExitInternal(env).FromMaybe(ExitCode::kGenericUserError);
}
int EmitExit(Environment* env) {
return static_cast<int>(EmitExitInternal(env));
}
Maybe<ExitCode> EmitProcessExitInternal(Environment* env) {
// process.emit('exit')
Isolate* isolate = env->isolate();
HandleScope handle_scope(isolate);
Context::Scope context_scope(env->context());
env->set_exiting(true);
if (!env->can_call_into_js()) {
return Nothing<ExitCode>();
}
ExitCode exit_code = env->exit_code(ExitCode::kNoFailure);
// the exit code wasn't already set, so let's check for unsettled tlas
if (exit_code == ExitCode::kNoFailure) {
auto unsettled_tla = env->CheckUnsettledTopLevelAwait();
if (!unsettled_tla.FromJust()) {
exit_code = ExitCode::kUnsettledTopLevelAwait;
env->set_exit_code(exit_code);
}
}
Local<Integer> exit_code_int =
Integer::New(isolate, static_cast<int32_t>(exit_code));
if (ProcessEmit(env, "exit", exit_code_int).IsEmpty()) {
return Nothing<ExitCode>();
}
// Reload exit code, it may be changed by `emit('exit')`
return Just(env->exit_code(exit_code));
}
Maybe<int> EmitProcessExit(Environment* env) {
Maybe<ExitCode> result = EmitProcessExitInternal(env);
if (result.IsNothing()) {
return Nothing<int>();
}
return Just(static_cast<int>(result.FromJust()));
}
typedef void (*CleanupHook)(void* arg);
typedef void (*AsyncCleanupHook)(void* arg, void(*)(void*), void*);
struct AsyncCleanupHookInfo final {
Environment* env;
AsyncCleanupHook fun;
void* arg;
bool started = false;
// Use a self-reference to make sure the storage is kept alive while the
// cleanup hook is registered but not yet finished.
std::shared_ptr<AsyncCleanupHookInfo> self;
};
// Opaque type that is basically an alias for `shared_ptr<AsyncCleanupHookInfo>`
// (but not publicly so for easier ABI/API changes). In particular,
// std::shared_ptr does not generally maintain a consistent ABI even on a
// specific platform.
struct ACHHandle final {
std::shared_ptr<AsyncCleanupHookInfo> info;
};
// This is implemented as an operator on a struct because otherwise you can't
// default-initialize AsyncCleanupHookHandle, because in C++ for a
// std::unique_ptr to be default-initializable the deleter type also needs
// to be default-initializable; in particular, function types don't satisfy
// this.
void DeleteACHHandle::operator ()(ACHHandle* handle) const { delete handle; }
void AddEnvironmentCleanupHook(Isolate* isolate,
CleanupHook fun,
void* arg) {
Environment* env = Environment::GetCurrent(isolate);
CHECK_NOT_NULL(env);
env->AddCleanupHook(fun, arg);
}
void RemoveEnvironmentCleanupHook(Isolate* isolate,
CleanupHook fun,
void* arg) {
Environment* env = Environment::GetCurrent(isolate);
CHECK_NOT_NULL(env);
env->RemoveCleanupHook(fun, arg);
}
static void FinishAsyncCleanupHook(void* arg) {
AsyncCleanupHookInfo* info = static_cast<AsyncCleanupHookInfo*>(arg);
std::shared_ptr<AsyncCleanupHookInfo> keep_alive = info->self;
info->env->DecreaseWaitingRequestCounter();
info->self.reset();
}
static void RunAsyncCleanupHook(void* arg) {
AsyncCleanupHookInfo* info = static_cast<AsyncCleanupHookInfo*>(arg);
info->env->IncreaseWaitingRequestCounter();
info->started = true;
info->fun(info->arg, FinishAsyncCleanupHook, info);
}
ACHHandle* AddEnvironmentCleanupHookInternal(
Isolate* isolate,
AsyncCleanupHook fun,
void* arg) {
Environment* env = Environment::GetCurrent(isolate);
CHECK_NOT_NULL(env);
auto info = std::make_shared<AsyncCleanupHookInfo>();
info->env = env;
info->fun = fun;
info->arg = arg;
info->self = info;
env->AddCleanupHook(RunAsyncCleanupHook, info.get());
return new ACHHandle { info };
}
void RemoveEnvironmentCleanupHookInternal(
ACHHandle* handle) {
if (handle->info->started) return;
handle->info->self.reset();
handle->info->env->RemoveCleanupHook(RunAsyncCleanupHook, handle->info.get());
}
void RequestInterrupt(Environment* env, void (*fun)(void* arg), void* arg) {
env->RequestInterrupt([fun, arg](Environment* env) {
// Disallow JavaScript execution during interrupt.
Isolate::DisallowJavascriptExecutionScope scope(
env->isolate(),
Isolate::DisallowJavascriptExecutionScope::CRASH_ON_FAILURE);
fun(arg);
});
}
async_id AsyncHooksGetExecutionAsyncId(Isolate* isolate) {
Environment* env = Environment::GetCurrent(isolate);
if (env == nullptr) return -1;
return env->execution_async_id();
}
async_id AsyncHooksGetExecutionAsyncId(Local<Context> context) {
Environment* env = Environment::GetCurrent(context);
if (env == nullptr) return -1;
return env->execution_async_id();
}
async_id AsyncHooksGetTriggerAsyncId(Isolate* isolate) {
Environment* env = Environment::GetCurrent(isolate);
if (env == nullptr) return -1;
return env->trigger_async_id();
}
async_context EmitAsyncInit(Isolate* isolate,
Local<Object> resource,
const char* name,
async_id trigger_async_id) {
HandleScope handle_scope(isolate);
Local<String> type =
String::NewFromUtf8(isolate, name, NewStringType::kInternalized)
.ToLocalChecked();
return EmitAsyncInit(isolate, resource, type, trigger_async_id);
}
async_context EmitAsyncInit(Isolate* isolate,
Local<Object> resource,
Local<String> name,
async_id trigger_async_id) {
DebugSealHandleScope handle_scope(isolate);
Environment* env = Environment::GetCurrent(isolate);
CHECK_NOT_NULL(env);
// Initialize async context struct
if (trigger_async_id == -1)
trigger_async_id = env->get_default_trigger_async_id();
async_context context = {
env->new_async_id(), // async_id_
trigger_async_id // trigger_async_id_
};
// Run init hooks
AsyncWrap::EmitAsyncInit(env, resource, name, context.async_id,
context.trigger_async_id);
return context;
}
void EmitAsyncDestroy(Isolate* isolate, async_context asyncContext) {
EmitAsyncDestroy(Environment::GetCurrent(isolate), asyncContext);
}
void EmitAsyncDestroy(Environment* env, async_context asyncContext) {
AsyncWrap::EmitDestroy(env, asyncContext.async_id);
}
} // namespace node

169
src/api/utils.cc Normal file
View File

@ -0,0 +1,169 @@
#include "node.h"
#include <csignal>
namespace node {
const char* signo_string(int signo) {
#define SIGNO_CASE(e) \
case e: \
return #e;
switch (signo) {
#ifdef SIGHUP
SIGNO_CASE(SIGHUP);
#endif
#ifdef SIGINT
SIGNO_CASE(SIGINT);
#endif
#ifdef SIGQUIT
SIGNO_CASE(SIGQUIT);
#endif
#ifdef SIGILL
SIGNO_CASE(SIGILL);
#endif
#ifdef SIGTRAP
SIGNO_CASE(SIGTRAP);
#endif
#ifdef SIGABRT
SIGNO_CASE(SIGABRT);
#endif
#ifdef SIGIOT
#if SIGABRT != SIGIOT
SIGNO_CASE(SIGIOT);
#endif
#endif
#ifdef SIGBUS
SIGNO_CASE(SIGBUS);
#endif
#ifdef SIGFPE
SIGNO_CASE(SIGFPE);
#endif
#ifdef SIGKILL
SIGNO_CASE(SIGKILL);
#endif
#ifdef SIGUSR1
SIGNO_CASE(SIGUSR1);
#endif
#ifdef SIGSEGV
SIGNO_CASE(SIGSEGV);
#endif
#ifdef SIGUSR2
SIGNO_CASE(SIGUSR2);
#endif
#ifdef SIGPIPE
SIGNO_CASE(SIGPIPE);
#endif
#ifdef SIGALRM
SIGNO_CASE(SIGALRM);
#endif
SIGNO_CASE(SIGTERM);
#ifdef SIGCHLD
SIGNO_CASE(SIGCHLD);
#endif
#ifdef SIGSTKFLT
SIGNO_CASE(SIGSTKFLT);
#endif
#ifdef SIGCONT
SIGNO_CASE(SIGCONT);
#endif
#ifdef SIGSTOP
SIGNO_CASE(SIGSTOP);
#endif
#ifdef SIGTSTP
SIGNO_CASE(SIGTSTP);
#endif
#ifdef SIGBREAK
SIGNO_CASE(SIGBREAK);
#endif
#ifdef SIGTTIN
SIGNO_CASE(SIGTTIN);
#endif
#ifdef SIGTTOU
SIGNO_CASE(SIGTTOU);
#endif
#ifdef SIGURG
SIGNO_CASE(SIGURG);
#endif
#ifdef SIGXCPU
SIGNO_CASE(SIGXCPU);
#endif
#ifdef SIGXFSZ
SIGNO_CASE(SIGXFSZ);
#endif
#ifdef SIGVTALRM
SIGNO_CASE(SIGVTALRM);
#endif
#ifdef SIGPROF
SIGNO_CASE(SIGPROF);
#endif
#ifdef SIGWINCH
SIGNO_CASE(SIGWINCH);
#endif
#ifdef SIGIO
SIGNO_CASE(SIGIO);
#endif
#ifdef SIGPOLL
#if SIGPOLL != SIGIO
SIGNO_CASE(SIGPOLL);
#endif
#endif
#ifdef SIGLOST
#if SIGLOST != SIGABRT
SIGNO_CASE(SIGLOST);
#endif
#endif
#ifdef SIGPWR
#if SIGPWR != SIGLOST
SIGNO_CASE(SIGPWR);
#endif
#endif
#ifdef SIGINFO
#if !defined(SIGPWR) || SIGINFO != SIGPWR
SIGNO_CASE(SIGINFO);
#endif
#endif
#ifdef SIGSYS
SIGNO_CASE(SIGSYS);
#endif
default:
return "";
}
}
} // namespace node

View File

@ -0,0 +1,89 @@
#include "async_context_frame.h" // NOLINT(build/include_inline)
#include "env-inl.h"
#include "node_errors.h"
#include "node_external_reference.h"
#include "tracing/traced_value.h"
#include "util-inl.h"
#include "debug_utils-inl.h"
#include "v8.h"
using v8::Context;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;
namespace node {
namespace async_context_frame {
//
// Scope helper
//
Scope::Scope(Isolate* isolate, Local<Value> object) : isolate_(isolate) {
auto prior = exchange(isolate, object);
prior_.Reset(isolate, prior);
}
Scope::~Scope() {
auto value = prior_.Get(isolate_);
set(isolate_, value);
}
Local<Value> current(Isolate* isolate) {
return isolate->GetContinuationPreservedEmbedderData();
}
void set(Isolate* isolate, Local<Value> value) {
auto env = Environment::GetCurrent(isolate);
if (!env->options()->async_context_frame) {
return;
}
isolate->SetContinuationPreservedEmbedderData(value);
}
// NOTE: It's generally recommended to use async_context_frame::Scope
// but sometimes (such as enterWith) a direct exchange is needed.
Local<Value> exchange(Isolate* isolate, Local<Value> value) {
auto prior = current(isolate);
set(isolate, value);
return prior;
}
void CreatePerContextProperties(Local<Object> target,
Local<Value> unused,
Local<Context> context,
void* priv) {
Environment* env = Environment::GetCurrent(context);
Local<String> getContinuationPreservedEmbedderData = FIXED_ONE_BYTE_STRING(
env->isolate(), "getContinuationPreservedEmbedderData");
Local<String> setContinuationPreservedEmbedderData = FIXED_ONE_BYTE_STRING(
env->isolate(), "setContinuationPreservedEmbedderData");
// Grab the intrinsics from the binding object and expose those to our
// binding layer.
Local<Object> binding = context->GetExtrasBindingObject();
target
->Set(context,
getContinuationPreservedEmbedderData,
binding->Get(context, getContinuationPreservedEmbedderData)
.ToLocalChecked())
.Check();
target
->Set(context,
setContinuationPreservedEmbedderData,
binding->Get(context, setContinuationPreservedEmbedderData)
.ToLocalChecked())
.Check();
}
} // namespace async_context_frame
} // namespace node
NODE_BINDING_CONTEXT_AWARE_INTERNAL(
async_context_frame, node::async_context_frame::CreatePerContextProperties)

33
src/async_context_frame.h Normal file
View File

@ -0,0 +1,33 @@
#ifndef SRC_ASYNC_CONTEXT_FRAME_H_
#define SRC_ASYNC_CONTEXT_FRAME_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "base_object.h"
#include "v8.h"
#include <cstdint>
namespace node {
namespace async_context_frame {
class Scope {
public:
explicit Scope(v8::Isolate* isolate, v8::Local<v8::Value> object);
~Scope();
private:
v8::Isolate* isolate_;
v8::Global<v8::Value> prior_;
};
v8::Local<v8::Value> current(v8::Isolate* isolate);
void set(v8::Isolate* isolate, v8::Local<v8::Value> value);
v8::Local<v8::Value> exchange(v8::Isolate* isolate, v8::Local<v8::Value> value);
} // namespace async_context_frame
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_ASYNC_CONTEXT_FRAME_H_

96
src/async_wrap-inl.h Normal file
View File

@ -0,0 +1,96 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
#ifndef SRC_ASYNC_WRAP_INL_H_
#define SRC_ASYNC_WRAP_INL_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "async_wrap.h"
#include "base_object-inl.h"
#include "node_internals.h"
namespace node {
inline AsyncWrap::ProviderType AsyncWrap::provider_type() const {
return provider_type_;
}
inline AsyncWrap::ProviderType AsyncWrap::set_provider_type(
AsyncWrap::ProviderType provider) {
provider_type_ = provider;
return provider_type_;
}
inline double AsyncWrap::get_async_id() const {
return async_id_;
}
inline double AsyncWrap::get_trigger_async_id() const {
return trigger_async_id_;
}
inline v8::Local<v8::Value> AsyncWrap::context_frame() const {
return context_frame_.Get(env()->isolate());
}
inline v8::MaybeLocal<v8::Value> AsyncWrap::MakeCallback(
const v8::Local<v8::String> symbol,
int argc,
v8::Local<v8::Value>* argv) {
return MakeCallback(symbol.As<v8::Name>(), argc, argv);
}
inline v8::MaybeLocal<v8::Value> AsyncWrap::MakeCallback(
const v8::Local<v8::Symbol> symbol,
int argc,
v8::Local<v8::Value>* argv) {
return MakeCallback(symbol.As<v8::Name>(), argc, argv);
}
inline v8::MaybeLocal<v8::Value> AsyncWrap::MakeCallback(
const v8::Local<v8::Name> symbol,
int argc,
v8::Local<v8::Value>* argv) {
v8::Local<v8::Value> cb_v;
if (!object()->Get(env()->context(), symbol).ToLocal(&cb_v))
return v8::MaybeLocal<v8::Value>();
if (!cb_v->IsFunction()) {
v8::Isolate* isolate = env()->isolate();
return Undefined(isolate);
}
return MakeCallback(cb_v.As<v8::Function>(), argc, argv);
}
// static
inline v8::Local<v8::FunctionTemplate> AsyncWrap::GetConstructorTemplate(
Environment* env) {
return GetConstructorTemplate(env->isolate_data());
}
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_ASYNC_WRAP_INL_H_

729
src/async_wrap.cc Normal file
View File

@ -0,0 +1,729 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "async_wrap.h" // NOLINT(build/include_inline)
#include "async_context_frame.h"
#include "async_wrap-inl.h"
#include "env-inl.h"
#include "node_errors.h"
#include "node_external_reference.h"
#include "tracing/traced_value.h"
#include "util-inl.h"
#include "v8.h"
using v8::Context;
using v8::DontDelete;
using v8::EscapableHandleScope;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Global;
using v8::HandleScope;
using v8::Integer;
using v8::Isolate;
using v8::Local;
using v8::MaybeLocal;
using v8::Nothing;
using v8::Number;
using v8::Object;
using v8::ObjectTemplate;
using v8::PropertyAttribute;
using v8::ReadOnly;
using v8::String;
using v8::Undefined;
using v8::Value;
using v8::WeakCallbackInfo;
using v8::WeakCallbackType;
using TryCatchScope = node::errors::TryCatchScope;
namespace node {
static const char* const provider_names[] = {
#define V(PROVIDER) \
#PROVIDER,
NODE_ASYNC_PROVIDER_TYPES(V)
#undef V
};
void AsyncWrap::DestroyAsyncIdsCallback(Environment* env) {
Local<Function> fn = env->async_hooks_destroy_function();
TryCatchScope try_catch(env, TryCatchScope::CatchMode::kFatal);
do {
std::vector<double> destroy_async_id_list;
destroy_async_id_list.swap(*env->destroy_async_id_list());
if (!env->can_call_into_js()) return;
for (auto async_id : destroy_async_id_list) {
// Want each callback to be cleaned up after itself, instead of cleaning
// them all up after the while() loop completes.
HandleScope scope(env->isolate());
Local<Value> async_id_value = Number::New(env->isolate(), async_id);
MaybeLocal<Value> ret = fn->Call(
env->context(), Undefined(env->isolate()), 1, &async_id_value);
if (ret.IsEmpty())
return;
}
} while (!env->destroy_async_id_list()->empty());
}
void Emit(Environment* env, double async_id, AsyncHooks::Fields type,
Local<Function> fn) {
AsyncHooks* async_hooks = env->async_hooks();
if (async_hooks->fields()[type] == 0 || !env->can_call_into_js())
return;
HandleScope handle_scope(env->isolate());
Local<Value> async_id_value = Number::New(env->isolate(), async_id);
TryCatchScope try_catch(env, TryCatchScope::CatchMode::kFatal);
USE(fn->Call(env->context(), Undefined(env->isolate()), 1, &async_id_value));
}
void AsyncWrap::EmitPromiseResolve(Environment* env, double async_id) {
Emit(env, async_id, AsyncHooks::kPromiseResolve,
env->async_hooks_promise_resolve_function());
}
void AsyncWrap::EmitTraceEventBefore() {
switch (provider_type()) {
#define V(PROVIDER) \
case PROVIDER_ ## PROVIDER: \
TRACE_EVENT_NESTABLE_ASYNC_BEGIN0( \
TRACING_CATEGORY_NODE1(async_hooks), \
#PROVIDER "_CALLBACK", static_cast<int64_t>(get_async_id())); \
break;
NODE_ASYNC_PROVIDER_TYPES(V)
#undef V
default:
UNREACHABLE();
}
}
void AsyncWrap::EmitBefore(Environment* env, double async_id) {
Emit(env, async_id, AsyncHooks::kBefore,
env->async_hooks_before_function());
}
void AsyncWrap::EmitTraceEventAfter(ProviderType type, double async_id) {
switch (type) {
#define V(PROVIDER) \
case PROVIDER_ ## PROVIDER: \
TRACE_EVENT_NESTABLE_ASYNC_END0( \
TRACING_CATEGORY_NODE1(async_hooks), \
#PROVIDER "_CALLBACK", static_cast<int64_t>(async_id)); \
break;
NODE_ASYNC_PROVIDER_TYPES(V)
#undef V
default:
UNREACHABLE();
}
}
void AsyncWrap::EmitAfter(Environment* env, double async_id) {
// If the user's callback failed then the after() hooks will be called at the
// end of _fatalException().
Emit(env, async_id, AsyncHooks::kAfter,
env->async_hooks_after_function());
}
static void SetupHooks(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsObject());
// All of init, before, after, destroy, and promise_resolve are supplied by
// async_hooks internally, so this should only ever be called once. At which
// time all the functions should be set. Detect this by checking if
// init !IsEmpty().
CHECK(env->async_hooks_init_function().IsEmpty());
Local<Object> fn_obj = args[0].As<Object>();
#define SET_HOOK_FN(name) \
do { \
Local<Value> v = \
fn_obj->Get(env->context(), \
FIXED_ONE_BYTE_STRING(env->isolate(), #name)) \
.ToLocalChecked(); \
CHECK(v->IsFunction()); \
env->set_async_hooks_##name##_function(v.As<Function>()); \
} while (0)
SET_HOOK_FN(init);
SET_HOOK_FN(before);
SET_HOOK_FN(after);
SET_HOOK_FN(destroy);
SET_HOOK_FN(promise_resolve);
#undef SET_HOOK_FN
}
static void SetPromiseHooks(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
env->ResetPromiseHooks(
args[0]->IsFunction() ? args[0].As<Function>() : Local<Function>(),
args[1]->IsFunction() ? args[1].As<Function>() : Local<Function>(),
args[2]->IsFunction() ? args[2].As<Function>() : Local<Function>(),
args[3]->IsFunction() ? args[3].As<Function>() : Local<Function>());
}
static void GetPromiseHooks(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
args.GetReturnValue().Set(
env->async_hooks()->GetPromiseHooks(args.GetIsolate()));
}
class DestroyParam {
public:
double asyncId;
Environment* env;
Global<Object> target;
Global<Object> propBag;
};
static void DestroyParamCleanupHook(void* ptr) {
delete static_cast<DestroyParam*>(ptr);
}
void AsyncWrap::WeakCallback(const WeakCallbackInfo<DestroyParam>& info) {
HandleScope scope(info.GetIsolate());
std::unique_ptr<DestroyParam> p{info.GetParameter()};
Local<Object> prop_bag = PersistentToLocal::Default(info.GetIsolate(),
p->propBag);
Local<Value> val;
p->env->RemoveCleanupHook(DestroyParamCleanupHook, p.get());
if (!prop_bag.IsEmpty() &&
!prop_bag->Get(p->env->context(), p->env->destroyed_string())
.ToLocal(&val)) {
return;
}
if (val.IsEmpty() || val->IsFalse()) {
AsyncWrap::EmitDestroy(p->env, p->asyncId);
}
// unique_ptr goes out of scope here and pointer is deleted.
}
static void RegisterDestroyHook(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsObject());
CHECK(args[1]->IsNumber());
CHECK(args.Length() == 2 || args[2]->IsObject());
Isolate* isolate = args.GetIsolate();
DestroyParam* p = new DestroyParam();
p->asyncId = args[1].As<Number>()->Value();
p->env = Environment::GetCurrent(args);
p->target.Reset(isolate, args[0].As<Object>());
if (args.Length() > 2) {
p->propBag.Reset(isolate, args[2].As<Object>());
}
p->target.SetWeak(p, AsyncWrap::WeakCallback, WeakCallbackType::kParameter);
p->env->AddCleanupHook(DestroyParamCleanupHook, p);
}
void AsyncWrap::GetAsyncId(const FunctionCallbackInfo<Value>& args) {
AsyncWrap* wrap;
args.GetReturnValue().Set(kInvalidAsyncId);
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());
args.GetReturnValue().Set(wrap->get_async_id());
}
void AsyncWrap::PushAsyncContext(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
// No need for CHECK(IsNumber()) on args because if FromJust() doesn't fail
// then the checks in push_async_ids() and pop_async_id() will.
double async_id = args[0]->NumberValue(env->context()).FromJust();
double trigger_async_id = args[1]->NumberValue(env->context()).FromJust();
env->async_hooks()->push_async_context(async_id, trigger_async_id, {});
}
void AsyncWrap::PopAsyncContext(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
double async_id = args[0]->NumberValue(env->context()).FromJust();
args.GetReturnValue().Set(env->async_hooks()->pop_async_context(async_id));
}
void AsyncWrap::ExecutionAsyncResource(
const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
uint32_t index;
if (!args[0]->Uint32Value(env->context()).To(&index)) return;
args.GetReturnValue().Set(
env->async_hooks()->native_execution_async_resource(index));
}
void AsyncWrap::ClearAsyncIdStack(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
env->async_hooks()->clear_async_id_stack();
}
void AsyncWrap::AsyncReset(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsObject());
AsyncWrap* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());
Local<Object> resource = args[0].As<Object>();
double execution_async_id =
args[1]->IsNumber() ? args[1].As<Number>()->Value() : kInvalidAsyncId;
wrap->AsyncReset(resource, execution_async_id);
}
void AsyncWrap::GetProviderType(const FunctionCallbackInfo<Value>& args) {
AsyncWrap* wrap;
args.GetReturnValue().Set(AsyncWrap::PROVIDER_NONE);
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());
args.GetReturnValue().Set(wrap->provider_type());
}
void AsyncWrap::EmitDestroy(bool from_gc) {
AsyncWrap::EmitDestroy(env(), async_id_);
// Ensure no double destroy is emitted via AsyncReset().
async_id_ = kInvalidAsyncId;
if (!persistent().IsEmpty() && !from_gc) {
HandleScope handle_scope(env()->isolate());
USE(object()->Set(env()->context(), env()->resource_symbol(), object()));
}
}
void AsyncWrap::QueueDestroyAsyncId(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsNumber());
AsyncWrap::EmitDestroy(
Environment::GetCurrent(args),
args[0].As<Number>()->Value());
}
void AsyncWrap::SetCallbackTrampoline(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
if (args[0]->IsFunction()) {
env->set_async_hooks_callback_trampoline(args[0].As<Function>());
} else {
env->set_async_hooks_callback_trampoline(Local<Function>());
}
}
Local<FunctionTemplate> AsyncWrap::GetConstructorTemplate(
IsolateData* isolate_data) {
Local<FunctionTemplate> tmpl = isolate_data->async_wrap_ctor_template();
if (tmpl.IsEmpty()) {
Isolate* isolate = isolate_data->isolate();
tmpl = NewFunctionTemplate(isolate, nullptr);
tmpl->SetClassName(
FIXED_ONE_BYTE_STRING(isolate_data->isolate(), "AsyncWrap"));
SetProtoMethod(isolate, tmpl, "getAsyncId", AsyncWrap::GetAsyncId);
SetProtoMethod(isolate, tmpl, "asyncReset", AsyncWrap::AsyncReset);
SetProtoMethod(
isolate, tmpl, "getProviderType", AsyncWrap::GetProviderType);
isolate_data->set_async_wrap_ctor_template(tmpl);
}
return tmpl;
}
void AsyncWrap::CreatePerIsolateProperties(IsolateData* isolate_data,
Local<ObjectTemplate> target) {
Isolate* isolate = isolate_data->isolate();
SetMethod(isolate, target, "setupHooks", SetupHooks);
SetMethod(isolate, target, "setCallbackTrampoline", SetCallbackTrampoline);
SetMethod(isolate, target, "pushAsyncContext", PushAsyncContext);
SetMethod(isolate, target, "popAsyncContext", PopAsyncContext);
SetMethod(isolate, target, "executionAsyncResource", ExecutionAsyncResource);
SetMethod(isolate, target, "clearAsyncIdStack", ClearAsyncIdStack);
SetMethod(isolate, target, "queueDestroyAsyncId", QueueDestroyAsyncId);
SetMethod(isolate, target, "setPromiseHooks", SetPromiseHooks);
SetMethod(isolate, target, "getPromiseHooks", GetPromiseHooks);
SetMethod(isolate, target, "registerDestroyHook", RegisterDestroyHook);
AsyncWrap::GetConstructorTemplate(isolate_data);
}
void AsyncWrap::CreatePerContextProperties(Local<Object> target,
Local<Value> unused,
Local<Context> context,
void* priv) {
Realm* realm = Realm::GetCurrent(context);
Environment* env = realm->env();
Isolate* isolate = realm->isolate();
HandleScope scope(isolate);
PropertyAttribute ReadOnlyDontDelete =
static_cast<PropertyAttribute>(ReadOnly | DontDelete);
#define FORCE_SET_TARGET_FIELD(obj, str, field) \
(obj)->DefineOwnProperty(context, \
FIXED_ONE_BYTE_STRING(isolate, str), \
field, \
ReadOnlyDontDelete).FromJust()
// Attach the uint32_t[] where each slot contains the count of the number of
// callbacks waiting to be called on a particular event. It can then be
// incremented/decremented from JS quickly to communicate to C++ if there are
// any callbacks waiting to be called.
FORCE_SET_TARGET_FIELD(target,
"async_hook_fields",
env->async_hooks()->fields().GetJSArray());
// The following v8::Float64Array has 5 fields. These fields are shared in
// this way to allow JS and C++ to read/write each value as quickly as
// possible. The fields are represented as follows:
//
// kAsyncIdCounter: Maintains the state of the next unique id to be assigned.
//
// kDefaultTriggerAsyncId: Write the id of the resource responsible for a
// handle's creation just before calling the new handle's constructor.
// After the new handle is constructed kDefaultTriggerAsyncId is set back
// to kInvalidAsyncId.
FORCE_SET_TARGET_FIELD(target,
"async_id_fields",
env->async_hooks()->async_id_fields().GetJSArray());
FORCE_SET_TARGET_FIELD(target,
"execution_async_resources",
env->async_hooks()->js_execution_async_resources());
target->Set(context,
env->async_ids_stack_string(),
env->async_hooks()->async_ids_stack().GetJSArray()).Check();
Local<Object> constants = Object::New(isolate);
#define SET_HOOKS_CONSTANT(name) \
FORCE_SET_TARGET_FIELD( \
constants, #name, Integer::New(isolate, AsyncHooks::name))
SET_HOOKS_CONSTANT(kInit);
SET_HOOKS_CONSTANT(kBefore);
SET_HOOKS_CONSTANT(kAfter);
SET_HOOKS_CONSTANT(kDestroy);
SET_HOOKS_CONSTANT(kPromiseResolve);
SET_HOOKS_CONSTANT(kTotals);
SET_HOOKS_CONSTANT(kCheck);
SET_HOOKS_CONSTANT(kExecutionAsyncId);
SET_HOOKS_CONSTANT(kTriggerAsyncId);
SET_HOOKS_CONSTANT(kAsyncIdCounter);
SET_HOOKS_CONSTANT(kDefaultTriggerAsyncId);
SET_HOOKS_CONSTANT(kUsesExecutionAsyncResource);
SET_HOOKS_CONSTANT(kStackLength);
#undef SET_HOOKS_CONSTANT
FORCE_SET_TARGET_FIELD(target, "constants", constants);
Local<Object> async_providers = Object::New(isolate);
#define V(p) \
FORCE_SET_TARGET_FIELD( \
async_providers, #p, Integer::New(isolate, AsyncWrap::PROVIDER_ ## p));
NODE_ASYNC_PROVIDER_TYPES(V)
#undef V
FORCE_SET_TARGET_FIELD(target, "Providers", async_providers);
#undef FORCE_SET_TARGET_FIELD
// TODO(legendecas): async hook functions are not realm-aware yet.
// This simply avoid overriding principal realm's functions when a
// ShadowRealm initializes the binding.
realm->set_async_hooks_init_function(Local<Function>());
realm->set_async_hooks_before_function(Local<Function>());
realm->set_async_hooks_after_function(Local<Function>());
realm->set_async_hooks_destroy_function(Local<Function>());
realm->set_async_hooks_promise_resolve_function(Local<Function>());
realm->set_async_hooks_callback_trampoline(Local<Function>());
realm->set_async_hooks_binding(target);
}
void AsyncWrap::RegisterExternalReferences(
ExternalReferenceRegistry* registry) {
registry->Register(SetupHooks);
registry->Register(SetCallbackTrampoline);
registry->Register(PushAsyncContext);
registry->Register(PopAsyncContext);
registry->Register(ExecutionAsyncResource);
registry->Register(ClearAsyncIdStack);
registry->Register(QueueDestroyAsyncId);
registry->Register(SetPromiseHooks);
registry->Register(GetPromiseHooks);
registry->Register(RegisterDestroyHook);
registry->Register(AsyncWrap::GetAsyncId);
registry->Register(AsyncWrap::AsyncReset);
registry->Register(AsyncWrap::GetProviderType);
}
AsyncWrap::AsyncWrap(Environment* env,
Local<Object> object,
ProviderType provider,
double execution_async_id)
: AsyncWrap(env, object) {
CHECK_NE(provider, PROVIDER_NONE);
provider_type_ = provider;
// Use AsyncReset() call to execute the init() callbacks.
AsyncReset(object, execution_async_id);
init_hook_ran_ = true;
}
AsyncWrap::AsyncWrap(Environment* env, Local<Object> object)
: BaseObject(env, object),
context_frame_(env->isolate(),
async_context_frame::current(env->isolate())) {}
// This method is necessary to work around one specific problem:
// Before the init() hook runs, if there is one, the BaseObject() constructor
// registers this object with the Environment for finalization and debugging
// purposes.
// If the Environment decides to inspect this object for debugging, it tries to
// call virtual methods on this object that are only (meaningfully) implemented
// by the subclasses of AsyncWrap.
// This could, with bad luck, happen during the AsyncWrap() constructor,
// because we run JS code as part of it and that in turn can lead to a heapdump
// being taken, either through the inspector or our programmatic API for it.
// The object being initialized is not fully constructed at that point, and
// in particular its virtual function table points to the AsyncWrap one
// (as the subclass constructor has not yet begun execution at that point).
// This means that the functions that are used for heap dump memory tracking
// are not yet available, and trying to call them would crash the process.
// We use this particular `IsDoneInitializing()` method to tell the Environment
// that such debugging methods are not yet available.
// This may be somewhat unreliable when it comes to future changes, because
// at this point it *only* protects AsyncWrap subclasses, and *only* for cases
// where heap dumps are being taken while the init() hook is on the call stack.
// For now, it seems like the best solution, though.
bool AsyncWrap::IsDoneInitializing() const {
return init_hook_ran_;
}
AsyncWrap::~AsyncWrap() {
EmitTraceEventDestroy();
EmitDestroy(true /* from gc */);
}
void AsyncWrap::EmitTraceEventDestroy() {
switch (provider_type()) {
#define V(PROVIDER) \
case PROVIDER_ ## PROVIDER: \
TRACE_EVENT_NESTABLE_ASYNC_END0( \
TRACING_CATEGORY_NODE1(async_hooks), \
#PROVIDER, static_cast<int64_t>(get_async_id())); \
break;
NODE_ASYNC_PROVIDER_TYPES(V)
#undef V
default:
UNREACHABLE();
}
}
void AsyncWrap::EmitDestroy(Environment* env, double async_id) {
if (env->async_hooks()->fields()[AsyncHooks::kDestroy] == 0 ||
!env->can_call_into_js()) {
return;
}
if (env->destroy_async_id_list()->empty()) {
env->SetImmediate(&DestroyAsyncIdsCallback, CallbackFlags::kUnrefed);
}
// If the list gets very large empty it faster using a Microtask.
// Microtasks can't be added in GC context therefore we use an
// interrupt to get this Microtask scheduled as fast as possible.
if (env->destroy_async_id_list()->size() == 16384) {
env->RequestInterrupt([](Environment* env) {
env->context()->GetMicrotaskQueue()->EnqueueMicrotask(
env->isolate(),
[](void* arg) {
DestroyAsyncIdsCallback(static_cast<Environment*>(arg));
}, env);
});
}
env->destroy_async_id_list()->push_back(async_id);
}
// Generalized call for both the constructor and for handles that are pooled
// and reused over their lifetime. This way a new uid can be assigned when
// the resource is pulled out of the pool and put back into use.
void AsyncWrap::AsyncReset(Local<Object> resource, double execution_async_id) {
CHECK_NE(provider_type(), PROVIDER_NONE);
if (async_id_ != kInvalidAsyncId) {
// This instance was in use before, we have already emitted an init with
// its previous async_id and need to emit a matching destroy for that
// before generating a new async_id.
EmitDestroy();
}
// Now we can assign a new async_id_ to this instance.
async_id_ = execution_async_id == kInvalidAsyncId ? env()->new_async_id()
: execution_async_id;
trigger_async_id_ = env()->get_default_trigger_async_id();
Isolate* isolate = env()->isolate();
{
HandleScope handle_scope(isolate);
Local<Object> obj = object();
CHECK(!obj.IsEmpty());
if (resource != obj) {
USE(obj->Set(env()->context(), env()->resource_symbol(), resource));
}
}
switch (provider_type()) {
#define V(PROVIDER) \
case PROVIDER_ ## PROVIDER: \
if (*TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED( \
TRACING_CATEGORY_NODE1(async_hooks))) { \
auto data = tracing::TracedValue::Create(); \
data->SetInteger("executionAsyncId", \
static_cast<int64_t>(env()->execution_async_id())); \
data->SetInteger("triggerAsyncId", \
static_cast<int64_t>(get_trigger_async_id())); \
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1( \
TRACING_CATEGORY_NODE1(async_hooks), \
#PROVIDER, static_cast<int64_t>(get_async_id()), \
"data", std::move(data)); \
} \
break;
NODE_ASYNC_PROVIDER_TYPES(V)
#undef V
default:
UNREACHABLE();
}
context_frame_.Reset(isolate, async_context_frame::current(isolate));
EmitAsyncInit(env(), resource,
env()->async_hooks()->provider_string(provider_type()),
async_id_, trigger_async_id_);
}
void AsyncWrap::EmitAsyncInit(Environment* env,
Local<Object> object,
Local<String> type,
double async_id,
double trigger_async_id) {
CHECK(!object.IsEmpty());
CHECK(!type.IsEmpty());
AsyncHooks* async_hooks = env->async_hooks();
// Nothing to execute, so can continue normally.
if (async_hooks->fields()[AsyncHooks::kInit] == 0) {
return;
}
HandleScope scope(env->isolate());
Local<Function> init_fn = env->async_hooks_init_function();
Local<Value> argv[] = {
Number::New(env->isolate(), async_id),
type,
Number::New(env->isolate(), trigger_async_id),
object,
};
TryCatchScope try_catch(env, TryCatchScope::CatchMode::kFatal);
USE(init_fn->Call(env->context(), object, arraysize(argv), argv));
}
MaybeLocal<Value> AsyncWrap::MakeCallback(const Local<Function> cb,
int argc,
Local<Value>* argv) {
EmitTraceEventBefore();
ProviderType provider = provider_type();
async_context context { get_async_id(), get_trigger_async_id() };
MaybeLocal<Value> ret =
InternalMakeCallback(env(),
object(),
object(),
cb,
argc,
argv,
context,
context_frame_.Get(env()->isolate()));
// This is a static call with cached values because the `this` object may
// no longer be alive at this point.
EmitTraceEventAfter(provider, context.async_id);
return ret;
}
const char* AsyncWrap::MemoryInfoName() const {
return provider_names[provider_type()];
}
std::string AsyncWrap::diagnostic_name() const {
char buf[64];
snprintf(buf,
sizeof(buf),
"%s(%" PRIu64 ":%.0f)",
MemoryInfoName(),
env()->thread_id(),
async_id_);
return buf;
}
Local<Object> AsyncWrap::GetOwner() {
return GetOwner(env(), object());
}
Local<Object> AsyncWrap::GetOwner(Environment* env, Local<Object> obj) {
EscapableHandleScope handle_scope(env->isolate());
CHECK(!obj.IsEmpty());
TryCatchScope ignore_exceptions(env);
while (true) {
Local<Value> owner;
if (!obj->Get(env->context(),
env->owner_symbol()).ToLocal(&owner) ||
!owner->IsObject()) {
return handle_scope.Escape(obj);
}
obj = owner.As<Object>();
}
}
} // namespace node
NODE_BINDING_CONTEXT_AWARE_INTERNAL(async_wrap,
node::AsyncWrap::CreatePerContextProperties)
NODE_BINDING_PER_ISOLATE_INIT(async_wrap,
node::AsyncWrap::CreatePerIsolateProperties)
NODE_BINDING_EXTERNAL_REFERENCE(async_wrap,
node::AsyncWrap::RegisterExternalReferences)

240
src/async_wrap.h Normal file
View File

@ -0,0 +1,240 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
#ifndef SRC_ASYNC_WRAP_H_
#define SRC_ASYNC_WRAP_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "base_object.h"
#include "v8.h"
#include <cstdint>
namespace node {
#define NODE_ASYNC_NON_CRYPTO_PROVIDER_TYPES(V) \
V(NONE) \
V(DIRHANDLE) \
V(DNSCHANNEL) \
V(ELDHISTOGRAM) \
V(FILEHANDLE) \
V(FILEHANDLECLOSEREQ) \
V(BLOBREADER) \
V(FSEVENTWRAP) \
V(FSREQCALLBACK) \
V(FSREQPROMISE) \
V(GETADDRINFOREQWRAP) \
V(GETNAMEINFOREQWRAP) \
V(HEAPSNAPSHOT) \
V(HTTP2SESSION) \
V(HTTP2STREAM) \
V(HTTP2PING) \
V(HTTP2SETTINGS) \
V(HTTPINCOMINGMESSAGE) \
V(HTTPCLIENTREQUEST) \
V(JSSTREAM) \
V(JSUDPWRAP) \
V(MESSAGEPORT) \
V(PIPECONNECTWRAP) \
V(PIPESERVERWRAP) \
V(PIPEWRAP) \
V(PROCESSWRAP) \
V(PROMISE) \
V(QUERYWRAP) \
V(QUIC_ENDPOINT) \
V(QUIC_LOGSTREAM) \
V(QUIC_PACKET) \
V(QUIC_SESSION) \
V(QUIC_STREAM) \
V(QUIC_UDP) \
V(SHUTDOWNWRAP) \
V(SIGNALWRAP) \
V(STATWATCHER) \
V(STREAMPIPE) \
V(TCPCONNECTWRAP) \
V(TCPSERVERWRAP) \
V(TCPWRAP) \
V(TTYWRAP) \
V(UDPSENDWRAP) \
V(UDPWRAP) \
V(SIGINTWATCHDOG) \
V(WORKER) \
V(WORKERHEAPSNAPSHOT) \
V(WORKERHEAPSTATISTICS) \
V(WRITEWRAP) \
V(ZLIB)
#if HAVE_OPENSSL
#define NODE_ASYNC_CRYPTO_PROVIDER_TYPES(V) \
V(CHECKPRIMEREQUEST) \
V(PBKDF2REQUEST) \
V(KEYPAIRGENREQUEST) \
V(KEYGENREQUEST) \
V(KEYEXPORTREQUEST) \
V(CIPHERREQUEST) \
V(DERIVEBITSREQUEST) \
V(HASHREQUEST) \
V(RANDOMBYTESREQUEST) \
V(RANDOMPRIMEREQUEST) \
V(SCRYPTREQUEST) \
V(SIGNREQUEST) \
V(TLSWRAP) \
V(VERIFYREQUEST)
#else
#define NODE_ASYNC_CRYPTO_PROVIDER_TYPES(V)
#endif // HAVE_OPENSSL
#define NODE_ASYNC_PROVIDER_TYPES(V) \
NODE_ASYNC_NON_CRYPTO_PROVIDER_TYPES(V) \
NODE_ASYNC_CRYPTO_PROVIDER_TYPES(V)
class Environment;
class DestroyParam;
class ExternalReferenceRegistry;
class AsyncWrap : public BaseObject {
public:
enum ProviderType {
#define V(PROVIDER) \
PROVIDER_ ## PROVIDER,
NODE_ASYNC_PROVIDER_TYPES(V)
#undef V
PROVIDERS_LENGTH,
};
AsyncWrap(Environment* env,
v8::Local<v8::Object> object,
ProviderType provider,
double execution_async_id = kInvalidAsyncId);
// This constructor creates a reusable instance where user is responsible
// to call set_provider_type() and AsyncReset() before use.
AsyncWrap(Environment* env, v8::Local<v8::Object> object);
~AsyncWrap() override;
AsyncWrap() = delete;
static constexpr double kInvalidAsyncId = -1;
static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
IsolateData* isolate_data);
inline static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
Environment* env);
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);
static void CreatePerContextProperties(v8::Local<v8::Object> target,
v8::Local<v8::Value> unused,
v8::Local<v8::Context> context,
void* priv);
static void CreatePerIsolateProperties(IsolateData* isolate_data,
v8::Local<v8::ObjectTemplate> target);
static void GetAsyncId(const v8::FunctionCallbackInfo<v8::Value>& args);
static void PushAsyncContext(const v8::FunctionCallbackInfo<v8::Value>& args);
static void PopAsyncContext(const v8::FunctionCallbackInfo<v8::Value>& args);
static void ExecutionAsyncResource(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void ClearAsyncIdStack(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void AsyncReset(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetProviderType(const v8::FunctionCallbackInfo<v8::Value>& args);
static void QueueDestroyAsyncId(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetCallbackTrampoline(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void EmitAsyncInit(Environment* env,
v8::Local<v8::Object> object,
v8::Local<v8::String> type,
double async_id,
double trigger_async_id);
static void EmitDestroy(Environment* env, double async_id);
static void EmitBefore(Environment* env, double async_id);
static void EmitAfter(Environment* env, double async_id);
static void EmitPromiseResolve(Environment* env, double async_id);
void EmitDestroy(bool from_gc = false);
void EmitTraceEventBefore();
static void EmitTraceEventAfter(ProviderType type, double async_id);
void EmitTraceEventDestroy();
static void DestroyAsyncIdsCallback(Environment* env);
inline ProviderType provider_type() const;
inline ProviderType set_provider_type(ProviderType provider);
inline double get_async_id() const;
inline double get_trigger_async_id() const;
inline v8::Local<v8::Value> context_frame() const;
void AsyncReset(v8::Local<v8::Object> resource,
double execution_async_id = kInvalidAsyncId);
// Only call these within a valid HandleScope.
v8::MaybeLocal<v8::Value> MakeCallback(const v8::Local<v8::Function> cb,
int argc,
v8::Local<v8::Value>* argv);
inline v8::MaybeLocal<v8::Value> MakeCallback(
const v8::Local<v8::Symbol> symbol,
int argc,
v8::Local<v8::Value>* argv);
inline v8::MaybeLocal<v8::Value> MakeCallback(
const v8::Local<v8::String> symbol,
int argc,
v8::Local<v8::Value>* argv);
inline v8::MaybeLocal<v8::Value> MakeCallback(
const v8::Local<v8::Name> symbol,
int argc,
v8::Local<v8::Value>* argv);
virtual std::string diagnostic_name() const;
const char* MemoryInfoName() const override;
static void WeakCallback(const v8::WeakCallbackInfo<DestroyParam> &info);
// Returns the object that 'owns' an async wrap. For example, for a
// TCP connection handle, this is the corresponding net.Socket.
v8::Local<v8::Object> GetOwner();
static v8::Local<v8::Object> GetOwner(Environment* env,
v8::Local<v8::Object> obj);
bool IsDoneInitializing() const override;
private:
ProviderType provider_type_ = PROVIDER_NONE;
bool init_hook_ran_ = false;
// Because the values may be Reset(), cannot be made const.
double async_id_ = kInvalidAsyncId;
double trigger_async_id_ = kInvalidAsyncId;
v8::Global<v8::Value> context_frame_;
};
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_ASYNC_WRAP_H_

330
src/base_object-inl.h Normal file
View File

@ -0,0 +1,330 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
#ifndef SRC_BASE_OBJECT_INL_H_
#define SRC_BASE_OBJECT_INL_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "base_object.h"
#include "env-inl.h"
#include "util.h"
#include "v8.h"
namespace node {
BaseObject::BaseObject(Environment* env, v8::Local<v8::Object> object)
: BaseObject(env->principal_realm(), object) {
// TODO(legendecas): Check the shorthand is only used in the principal realm
// while allowing to create a BaseObject in a vm context.
}
void BaseObject::Detach() {
CHECK_GT(pointer_data()->strong_ptr_count, 0);
pointer_data()->is_detached = true;
}
v8::Global<v8::Object>& BaseObject::persistent() {
return persistent_handle_;
}
v8::Local<v8::Object> BaseObject::object() const {
return PersistentToLocal::Default(env()->isolate(), persistent_handle_);
}
v8::Local<v8::Object> BaseObject::object(v8::Isolate* isolate) const {
v8::Local<v8::Object> handle = object();
DCHECK_EQ(handle->GetCreationContextChecked()->GetIsolate(), isolate);
DCHECK_EQ(env()->isolate(), isolate);
return handle;
}
Environment* BaseObject::env() const {
return realm_->env();
}
Realm* BaseObject::realm() const {
return realm_;
}
bool BaseObject::IsBaseObject(IsolateData* isolate_data,
v8::Local<v8::Object> obj) {
if (obj->InternalFieldCount() < BaseObject::kInternalFieldCount) {
return false;
}
uint16_t* ptr = static_cast<uint16_t*>(
obj->GetAlignedPointerFromInternalField(BaseObject::kEmbedderType));
return ptr == isolate_data->embedder_id_for_non_cppgc();
}
void BaseObject::TagBaseObject(IsolateData* isolate_data,
v8::Local<v8::Object> object) {
DCHECK_GE(object->InternalFieldCount(), BaseObject::kInternalFieldCount);
object->SetAlignedPointerInInternalField(
BaseObject::kEmbedderType, isolate_data->embedder_id_for_non_cppgc());
}
void BaseObject::SetInternalFields(IsolateData* isolate_data,
v8::Local<v8::Object> object,
void* slot) {
TagBaseObject(isolate_data, object);
object->SetAlignedPointerInInternalField(BaseObject::kSlot, slot);
}
BaseObject* BaseObject::FromJSObject(v8::Local<v8::Value> value) {
v8::Local<v8::Object> obj = value.As<v8::Object>();
DCHECK_GE(obj->InternalFieldCount(), BaseObject::kInternalFieldCount);
return static_cast<BaseObject*>(
obj->GetAlignedPointerFromInternalField(BaseObject::kSlot));
}
template <typename T>
T* BaseObject::FromJSObject(v8::Local<v8::Value> object) {
return static_cast<T*>(FromJSObject(object));
}
void BaseObject::OnGCCollect() {
delete this;
}
void BaseObject::ClearWeak() {
if (has_pointer_data())
pointer_data()->wants_weak_jsobj = false;
persistent_handle_.ClearWeak();
}
bool BaseObject::IsWeakOrDetached() const {
if (persistent_handle_.IsWeak()) return true;
if (!has_pointer_data()) return false;
const PointerData* pd = const_cast<BaseObject*>(this)->pointer_data();
return pd->wants_weak_jsobj || pd->is_detached;
}
template <int Field>
void BaseObject::InternalFieldGet(
const v8::FunctionCallbackInfo<v8::Value>& args) {
args.GetReturnValue().Set(
args.This()->GetInternalField(Field).As<v8::Value>());
}
template <int Field, bool (v8::Value::*typecheck)() const>
void BaseObject::InternalFieldSet(
const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Local<v8::Value> value = args[0];
// This could be e.g. value->IsFunction().
CHECK(((*value)->*typecheck)());
args.This()->SetInternalField(Field, value);
}
bool BaseObject::has_pointer_data() const {
return pointer_data_ != nullptr;
}
template <typename T, bool kIsWeak>
BaseObject::PointerData*
BaseObjectPtrImpl<T, kIsWeak>::pointer_data() const {
if constexpr (kIsWeak) {
return data_.pointer_data;
}
if (get_base_object() == nullptr) {
return nullptr;
}
return get_base_object()->pointer_data();
}
template <typename T, bool kIsWeak>
BaseObject* BaseObjectPtrImpl<T, kIsWeak>::get_base_object() const {
if constexpr (kIsWeak) {
if (pointer_data() == nullptr) {
return nullptr;
}
return pointer_data()->self;
}
return data_.target;
}
template <typename T, bool kIsWeak>
BaseObjectPtrImpl<T, kIsWeak>::~BaseObjectPtrImpl() {
if constexpr (kIsWeak) {
if (pointer_data() != nullptr &&
--pointer_data()->weak_ptr_count == 0 &&
pointer_data()->self == nullptr) {
delete pointer_data();
}
} else if (get() != nullptr) {
get()->decrease_refcount();
}
}
template <typename T, bool kIsWeak>
BaseObjectPtrImpl<T, kIsWeak>::BaseObjectPtrImpl() {
data_.target = nullptr;
}
template <typename T, bool kIsWeak>
BaseObjectPtrImpl<T, kIsWeak>::BaseObjectPtrImpl(T* target)
: BaseObjectPtrImpl() {
if (target == nullptr) return;
if constexpr (kIsWeak) {
data_.pointer_data = target->pointer_data();
CHECK_NOT_NULL(pointer_data());
pointer_data()->weak_ptr_count++;
} else {
data_.target = target;
CHECK_NOT_NULL(pointer_data());
get()->increase_refcount();
}
}
template <typename T, bool kIsWeak>
template <typename U, bool kW>
BaseObjectPtrImpl<T, kIsWeak>::BaseObjectPtrImpl(
const BaseObjectPtrImpl<U, kW>& other)
: BaseObjectPtrImpl(other.get()) {}
template <typename T, bool kIsWeak>
BaseObjectPtrImpl<T, kIsWeak>::BaseObjectPtrImpl(const BaseObjectPtrImpl& other)
: BaseObjectPtrImpl(other.get()) {}
template <typename T, bool kIsWeak>
template <typename U, bool kW>
BaseObjectPtrImpl<T, kIsWeak>& BaseObjectPtrImpl<T, kIsWeak>::operator=(
const BaseObjectPtrImpl<U, kW>& other) {
if (other.get() == get()) return *this;
this->~BaseObjectPtrImpl();
return *new (this) BaseObjectPtrImpl(other);
}
template <typename T, bool kIsWeak>
BaseObjectPtrImpl<T, kIsWeak>& BaseObjectPtrImpl<T, kIsWeak>::operator=(
const BaseObjectPtrImpl& other) {
if (other.get() == get()) return *this;
this->~BaseObjectPtrImpl();
return *new (this) BaseObjectPtrImpl(other);
}
template <typename T, bool kIsWeak>
BaseObjectPtrImpl<T, kIsWeak>::BaseObjectPtrImpl(BaseObjectPtrImpl&& other)
: data_(other.data_) {
if constexpr (kIsWeak)
other.data_.target = nullptr;
else
other.data_.pointer_data = nullptr;
}
template <typename T, bool kIsWeak>
BaseObjectPtrImpl<T, kIsWeak>& BaseObjectPtrImpl<T, kIsWeak>::operator=(
BaseObjectPtrImpl&& other) {
if (&other == this) return *this;
this->~BaseObjectPtrImpl();
return *new (this) BaseObjectPtrImpl(std::move(other));
}
template <typename T, bool kIsWeak>
BaseObjectPtrImpl<T, kIsWeak>::BaseObjectPtrImpl(std::nullptr_t)
: BaseObjectPtrImpl() {}
template <typename T, bool kIsWeak>
BaseObjectPtrImpl<T, kIsWeak>& BaseObjectPtrImpl<T, kIsWeak>::operator=(
std::nullptr_t) {
this->~BaseObjectPtrImpl();
return *new (this) BaseObjectPtrImpl();
}
template <typename T, bool kIsWeak>
void BaseObjectPtrImpl<T, kIsWeak>::reset(T* ptr) {
*this = BaseObjectPtrImpl(ptr);
}
template <typename T, bool kIsWeak>
T* BaseObjectPtrImpl<T, kIsWeak>::get() const {
return static_cast<T*>(get_base_object());
}
template <typename T, bool kIsWeak>
T& BaseObjectPtrImpl<T, kIsWeak>::operator*() const {
return *get();
}
template <typename T, bool kIsWeak>
T* BaseObjectPtrImpl<T, kIsWeak>::operator->() const {
return get();
}
template <typename T, bool kIsWeak>
BaseObjectPtrImpl<T, kIsWeak>::operator bool() const {
return get() != nullptr;
}
template <typename T, bool kIsWeak>
template <typename U, bool kW>
bool BaseObjectPtrImpl<T, kIsWeak>::operator ==(
const BaseObjectPtrImpl<U, kW>& other) const {
return get() == other.get();
}
template <typename T, bool kIsWeak>
template <typename U, bool kW>
bool BaseObjectPtrImpl<T, kIsWeak>::operator !=(
const BaseObjectPtrImpl<U, kW>& other) const {
return get() != other.get();
}
template <typename T, bool kIsWeak>
bool operator==(const BaseObjectPtrImpl<T, kIsWeak> ptr, const std::nullptr_t) {
return ptr.get() == nullptr;
}
template <typename T, bool kIsWeak>
bool operator==(const std::nullptr_t, const BaseObjectPtrImpl<T, kIsWeak> ptr) {
return ptr.get() == nullptr;
}
template <typename T, typename... Args>
BaseObjectPtr<T> MakeBaseObject(Args&&... args) {
return BaseObjectPtr<T>(new T(std::forward<Args>(args)...));
}
template <typename T, typename... Args>
BaseObjectWeakPtr<T> MakeWeakBaseObject(Args&&... args) {
T* target = new T(std::forward<Args>(args)...);
target->MakeWeak();
return BaseObjectWeakPtr<T>(target);
}
template <typename T, typename... Args>
BaseObjectPtr<T> MakeDetachedBaseObject(Args&&... args) {
BaseObjectPtr<T> target = MakeBaseObject<T>(std::forward<Args>(args)...);
target->Detach();
return target;
}
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_BASE_OBJECT_INL_H_

181
src/base_object.cc Normal file
View File

@ -0,0 +1,181 @@
#include "base_object.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "node_messaging.h"
#include "node_realm-inl.h"
namespace node {
using v8::Context;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::HandleScope;
using v8::Just;
using v8::JustVoid;
using v8::Local;
using v8::Maybe;
using v8::Object;
using v8::Value;
using v8::ValueDeserializer;
using v8::WeakCallbackInfo;
using v8::WeakCallbackType;
BaseObject::BaseObject(Realm* realm, Local<Object> object)
: persistent_handle_(realm->isolate(), object), realm_(realm) {
CHECK_EQ(false, object.IsEmpty());
CHECK_GE(object->InternalFieldCount(), BaseObject::kInternalFieldCount);
SetInternalFields(realm->isolate_data(), object, static_cast<void*>(this));
realm->TrackBaseObject(this);
}
BaseObject::~BaseObject() {
realm()->UntrackBaseObject(this);
if (has_pointer_data()) [[unlikely]] {
PointerData* metadata = pointer_data();
CHECK_EQ(metadata->strong_ptr_count, 0);
metadata->self = nullptr;
if (metadata->weak_ptr_count == 0) delete metadata;
}
if (persistent_handle_.IsEmpty()) {
// This most likely happened because the weak callback below cleared it.
return;
}
{
HandleScope handle_scope(realm()->isolate());
object()->SetAlignedPointerInInternalField(BaseObject::kSlot, nullptr);
}
}
void BaseObject::MakeWeak() {
if (has_pointer_data()) {
pointer_data()->wants_weak_jsobj = true;
if (pointer_data()->strong_ptr_count > 0) return;
}
persistent_handle_.SetWeak(
this,
[](const WeakCallbackInfo<BaseObject>& data) {
BaseObject* obj = data.GetParameter();
// Clear the persistent handle so that ~BaseObject() doesn't attempt
// to mess with internal fields, since the JS object may have
// transitioned into an invalid state.
// Refs: https://github.com/nodejs/node/issues/18897
obj->persistent_handle_.Reset();
CHECK_IMPLIES(obj->has_pointer_data(),
obj->pointer_data()->strong_ptr_count == 0);
obj->OnGCCollect();
},
WeakCallbackType::kParameter);
}
void BaseObject::LazilyInitializedJSTemplateConstructor(
const FunctionCallbackInfo<Value>& args) {
DCHECK(args.IsConstructCall());
CHECK_GE(args.This()->InternalFieldCount(), BaseObject::kInternalFieldCount);
Environment* env = Environment::GetCurrent(args);
DCHECK_NOT_NULL(env);
SetInternalFields(env->isolate_data(), args.This(), nullptr);
}
Local<FunctionTemplate> BaseObject::MakeLazilyInitializedJSTemplate(
Environment* env) {
return MakeLazilyInitializedJSTemplate(env->isolate_data());
}
Local<FunctionTemplate> BaseObject::MakeLazilyInitializedJSTemplate(
IsolateData* isolate_data) {
Local<FunctionTemplate> t = NewFunctionTemplate(
isolate_data->isolate(), LazilyInitializedJSTemplateConstructor);
t->InstanceTemplate()->SetInternalFieldCount(BaseObject::kInternalFieldCount);
return t;
}
BaseObject::TransferMode BaseObject::GetTransferMode() const {
return TransferMode::kDisallowCloneAndTransfer;
}
std::unique_ptr<worker::TransferData> BaseObject::TransferForMessaging() {
return {};
}
std::unique_ptr<worker::TransferData> BaseObject::CloneForMessaging() const {
return {};
}
Maybe<std::vector<BaseObjectPtr<BaseObject>>> BaseObject::NestedTransferables()
const {
return Just(std::vector<BaseObjectPtr<BaseObject>>{});
}
Maybe<void> BaseObject::FinalizeTransferRead(Local<Context> context,
ValueDeserializer* deserializer) {
return JustVoid();
}
BaseObject::PointerData* BaseObject::pointer_data() {
if (!has_pointer_data()) {
PointerData* metadata = new PointerData();
metadata->wants_weak_jsobj = persistent_handle_.IsWeak();
metadata->self = this;
pointer_data_ = metadata;
}
CHECK(has_pointer_data());
return pointer_data_;
}
void BaseObject::decrease_refcount() {
CHECK(has_pointer_data());
PointerData* metadata = pointer_data();
CHECK_GT(metadata->strong_ptr_count, 0);
unsigned int new_refcount = --metadata->strong_ptr_count;
if (new_refcount == 0) {
if (metadata->is_detached) {
OnGCCollect();
} else if (metadata->wants_weak_jsobj && !persistent_handle_.IsEmpty()) {
MakeWeak();
}
}
}
void BaseObject::increase_refcount() {
unsigned int prev_refcount = pointer_data()->strong_ptr_count++;
if (prev_refcount == 0 && !persistent_handle_.IsEmpty())
persistent_handle_.ClearWeak();
}
void BaseObject::DeleteMe() {
if (has_pointer_data() && pointer_data()->strong_ptr_count > 0) {
return Detach();
}
delete this;
}
bool BaseObject::IsDoneInitializing() const {
return true;
}
Local<Object> BaseObject::WrappedObject() const {
return object();
}
bool BaseObject::IsNotIndicativeOfMemoryLeakAtExit() const {
return IsWeakOrDetached();
}
void BaseObjectList::Cleanup() {
while (!IsEmpty()) {
BaseObject* bo = PopFront();
bo->DeleteMe();
}
}
void BaseObjectList::MemoryInfo(node::MemoryTracker* tracker) const {
for (auto bo : *this) {
if (bo->IsDoneInitializing()) tracker->Track(bo);
}
}
} // namespace node

343
src/base_object.h Normal file
View File

@ -0,0 +1,343 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
#ifndef SRC_BASE_OBJECT_H_
#define SRC_BASE_OBJECT_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include <type_traits> // std::remove_reference
#include "base_object_types.h"
#include "memory_tracker.h"
#include "util.h"
#include "v8.h"
namespace node {
class Environment;
class IsolateData;
class Realm;
template <typename T, bool kIsWeak>
class BaseObjectPtrImpl;
namespace worker {
class TransferData;
}
class BaseObject : public MemoryRetainer {
public:
enum InternalFields { kEmbedderType, kSlot, kInternalFieldCount };
// Associates this object with `object`. It uses the 1st internal field for
// that, and in particular aborts if there is no such field.
// This is the designated constructor.
BaseObject(Realm* realm, v8::Local<v8::Object> object);
// Convenient constructor for constructing BaseObject in the principal realm.
inline BaseObject(Environment* env, v8::Local<v8::Object> object);
~BaseObject() override;
BaseObject() = delete;
// Returns the wrapped object. Returns an empty handle when
// persistent.IsEmpty() is true.
inline v8::Local<v8::Object> object() const;
// Same as the above, except it additionally verifies that this object
// is associated with the passed Isolate in debug mode.
inline v8::Local<v8::Object> object(v8::Isolate* isolate) const;
inline v8::Global<v8::Object>& persistent();
inline Environment* env() const;
inline Realm* realm() const;
// Get a BaseObject* pointer, or subclass pointer, for the JS object that
// was also passed to the `BaseObject()` constructor initially.
// This may return `nullptr` if the C++ object has not been constructed yet,
// e.g. when the JS object used `MakeLazilyInitializedJSTemplate`.
static inline void SetInternalFields(IsolateData* isolate_data,
v8::Local<v8::Object> object,
void* slot);
static inline bool IsBaseObject(IsolateData* isolate_data,
v8::Local<v8::Object> object);
static inline void TagBaseObject(IsolateData* isolate_data,
v8::Local<v8::Object> object);
static void LazilyInitializedJSTemplateConstructor(
const v8::FunctionCallbackInfo<v8::Value>& args);
static inline BaseObject* FromJSObject(v8::Local<v8::Value> object);
template <typename T>
static inline T* FromJSObject(v8::Local<v8::Value> object);
// Global alias for FromJSObject() to avoid churn.
template <typename T>
static inline T* Unwrap(v8::Local<v8::Value> obj) {
return BaseObject::FromJSObject<T>(obj);
}
// Make the `v8::Global` a weak reference and, `delete` this object once
// the JS object has been garbage collected and there are no (strong)
// BaseObjectPtr references to it.
void MakeWeak();
// Undo `MakeWeak()`, i.e. turn this into a strong reference that is a GC
// root and will not be touched by the garbage collector.
inline void ClearWeak();
// Reports whether this BaseObject is using a weak reference or detached,
// i.e. whether is can be deleted by GC once no strong BaseObjectPtrs refer
// to it anymore.
inline bool IsWeakOrDetached() const;
// Utility to create a FunctionTemplate with one internal field (used for
// the `BaseObject*` pointer) and a constructor that initializes that field
// to `nullptr`.
static v8::Local<v8::FunctionTemplate> MakeLazilyInitializedJSTemplate(
IsolateData* isolate);
static v8::Local<v8::FunctionTemplate> MakeLazilyInitializedJSTemplate(
Environment* env);
// Setter/Getter pair for internal fields that can be passed to SetAccessor.
template <int Field>
static void InternalFieldGet(const v8::FunctionCallbackInfo<v8::Value>& args);
template <int Field, bool (v8::Value::*typecheck)() const>
static void InternalFieldSet(const v8::FunctionCallbackInfo<v8::Value>& args);
// This is a bit of a hack. See the override in async_wrap.cc for details.
virtual bool IsDoneInitializing() const;
// Can be used to avoid this object keeping itself alive as a GC root
// indefinitely, for example when this object is owned and deleted by another
// BaseObject once that is torn down. This can only be called when there is
// a BaseObjectPtr to this object.
inline void Detach();
static inline v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
Environment* env);
static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
IsolateData* isolate_data);
// Interface for transferring BaseObject instances using the .postMessage()
// method of MessagePorts (and, by extension, Workers).
// GetTransferMode() returns a transfer mode that indicates how to deal with
// the current object:
// - kDisallowCloneAndTransfer:
// No transfer or clone is possible, either because this type of
// BaseObject does not know how to be transferred, or because it is not
// in a state in which it is possible to do so (e.g. because it has
// already been transferred).
// - kTransferable:
// This object can be transferred in a destructive fashion, i.e. will be
// rendered unusable on the sending side of the channel in the process
// of being transferred. (In C++ this would be referred to as movable but
// not copyable.) Objects of this type need to be listed in the
// `transferList` argument of the relevant postMessage() call in order to
// make sure that they are not accidentally destroyed on the sending side.
// TransferForMessaging() will be called to get a representation of the
// object that is used for subsequent deserialization.
// The NestedTransferables() method can be used to transfer other objects
// along with this one, if a situation requires it.
// - kCloneable:
// This object can be cloned without being modified.
// CloneForMessaging() will be called to get a representation of the
// object that is used for subsequent deserialization, unless the
// object is listed in transferList and is kTransferable, in which case
// TransferForMessaging() is attempted first.
// - kTransferableAndCloneable:
// This object can be transferred or cloned.
// After a successful clone, FinalizeTransferRead() is called on the receiving
// end, and can read deserialize JS data possibly serialized by a previous
// FinalizeTransferWrite() call.
// By default, a BaseObject is kDisallowCloneAndTransfer and a JS Object is
// kCloneable unless otherwise specified.
enum TransferMode : uint32_t {
kDisallowCloneAndTransfer = 0,
kTransferable = 1 << 0,
kCloneable = 1 << 1,
kTransferableAndCloneable = kTransferable | kCloneable,
};
virtual TransferMode GetTransferMode() const;
virtual std::unique_ptr<worker::TransferData> TransferForMessaging();
virtual std::unique_ptr<worker::TransferData> CloneForMessaging() const;
virtual v8::Maybe<std::vector<BaseObjectPtrImpl<BaseObject, false>>>
NestedTransferables() const;
virtual v8::Maybe<void> FinalizeTransferRead(
v8::Local<v8::Context> context, v8::ValueDeserializer* deserializer);
// Indicates whether this object is expected to use a strong reference during
// a clean process exit (due to an empty event loop).
virtual bool IsNotIndicativeOfMemoryLeakAtExit() const;
virtual inline void OnGCCollect();
virtual inline bool is_snapshotable() const { return false; }
private:
v8::Local<v8::Object> WrappedObject() const override;
void DeleteMe();
// persistent_handle_ needs to be at a fixed offset from the start of the
// class because it is used by src/node_postmortem_metadata.cc to calculate
// offsets and generate debug symbols for BaseObject, which assumes that the
// position of members in memory are predictable. For more information please
// refer to `doc/contributing/node-postmortem-support.md`
friend int GenDebugSymbols();
friend class CleanupQueue;
template <typename T, bool kIsWeak>
friend class BaseObjectPtrImpl;
v8::Global<v8::Object> persistent_handle_;
// Metadata that is associated with this BaseObject if there are BaseObjectPtr
// or BaseObjectWeakPtr references to it.
// This object is deleted when the BaseObject itself is destroyed, and there
// are no weak references to it.
struct PointerData {
// Number of BaseObjectPtr instances that refer to this object. If this
// is non-zero, the BaseObject is always a GC root and will not be destroyed
// during cleanup until the count drops to zero again.
unsigned int strong_ptr_count = 0;
// Number of BaseObjectWeakPtr instances that refer to this object.
unsigned int weak_ptr_count = 0;
// Indicates whether MakeWeak() has been called.
bool wants_weak_jsobj = false;
// Indicates whether Detach() has been called. If that is the case, this
// object will be destroyed once the strong pointer count drops to zero.
bool is_detached = false;
// Reference to the original BaseObject. This is used by weak pointers.
BaseObject* self = nullptr;
};
inline bool has_pointer_data() const;
// This creates a PointerData struct if none was associated with this
// BaseObject before.
PointerData* pointer_data();
// Functions that adjust the strong pointer count.
void decrease_refcount();
void increase_refcount();
Realm* realm_;
PointerData* pointer_data_ = nullptr;
ListNode<BaseObject> base_object_list_node_;
friend class BaseObjectList;
};
class BaseObjectList
: public ListHead<BaseObject, &BaseObject::base_object_list_node_>,
public MemoryRetainer {
public:
void Cleanup();
SET_MEMORY_INFO_NAME(BaseObjectList)
SET_SELF_SIZE(BaseObjectList)
void MemoryInfo(node::MemoryTracker* tracker) const override;
};
#define ASSIGN_OR_RETURN_UNWRAP(ptr, obj, ...) \
do { \
*ptr = static_cast<typename std::remove_reference<decltype(*ptr)>::type>( \
BaseObject::FromJSObject(obj)); \
if (*ptr == nullptr) return __VA_ARGS__; \
} while (0)
// Implementation of a generic strong or weak pointer to a BaseObject.
// If strong, this will keep the target BaseObject alive regardless of other
// circumstances such as the GC or Environment cleanup.
// If weak, destruction behaviour is not affected, but the pointer will be
// reset to nullptr once the BaseObject is destroyed.
// The API matches std::shared_ptr closely. However, this class is not thread
// safe, that is, we can't have different BaseObjectPtrImpl instances in
// different threads referring to the same BaseObject instance.
template <typename T, bool kIsWeak>
class BaseObjectPtrImpl final {
public:
inline BaseObjectPtrImpl();
inline ~BaseObjectPtrImpl();
inline explicit BaseObjectPtrImpl(T* target);
// Copy and move constructors. Note that the templated version is not a copy
// or move constructor in the C++ sense of the word, so an identical
// untemplated version is provided.
template <typename U, bool kW>
inline BaseObjectPtrImpl(const BaseObjectPtrImpl<U, kW>& other);
inline BaseObjectPtrImpl(const BaseObjectPtrImpl& other);
template <typename U, bool kW>
inline BaseObjectPtrImpl& operator=(const BaseObjectPtrImpl<U, kW>& other);
inline BaseObjectPtrImpl& operator=(const BaseObjectPtrImpl& other);
inline BaseObjectPtrImpl(BaseObjectPtrImpl&& other);
inline BaseObjectPtrImpl& operator=(BaseObjectPtrImpl&& other);
inline BaseObjectPtrImpl(std::nullptr_t);
inline BaseObjectPtrImpl& operator=(std::nullptr_t);
inline void reset(T* ptr = nullptr);
inline T* get() const;
inline T& operator*() const;
inline T* operator->() const;
inline operator bool() const;
template <typename U, bool kW>
inline bool operator ==(const BaseObjectPtrImpl<U, kW>& other) const;
template <typename U, bool kW>
inline bool operator !=(const BaseObjectPtrImpl<U, kW>& other) const;
private:
union {
BaseObject* target; // Used for strong pointers.
BaseObject::PointerData* pointer_data; // Used for weak pointers.
} data_;
inline BaseObject* get_base_object() const;
inline BaseObject::PointerData* pointer_data() const;
};
template <typename T, bool kIsWeak>
inline static bool operator==(const BaseObjectPtrImpl<T, kIsWeak>,
const std::nullptr_t);
template <typename T, bool kIsWeak>
inline static bool operator==(const std::nullptr_t,
const BaseObjectPtrImpl<T, kIsWeak>);
template <typename T>
using BaseObjectPtr = BaseObjectPtrImpl<T, false>;
template <typename T>
using BaseObjectWeakPtr = BaseObjectPtrImpl<T, true>;
// Create a BaseObject instance and return a pointer to it.
// This variant leaves the object as a GC root by default.
template <typename T, typename... Args>
inline BaseObjectPtr<T> MakeBaseObject(Args&&... args);
// Create a BaseObject instance and return a pointer to it.
// This variant makes the object a weak GC root by default.
template <typename T, typename... Args>
inline BaseObjectWeakPtr<T> MakeWeakBaseObject(Args&&... args);
// Create a BaseObject instance and return a pointer to it.
// This variant detaches the object by default, meaning that the caller fully
// owns it, and once the last BaseObjectPtr to it is destroyed, the object
// itself is also destroyed.
template <typename T, typename... Args>
inline BaseObjectPtr<T> MakeDetachedBaseObject(Args&&... args);
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_BASE_OBJECT_H_

74
src/base_object_types.h Normal file
View File

@ -0,0 +1,74 @@
#ifndef SRC_BASE_OBJECT_TYPES_H_
#define SRC_BASE_OBJECT_TYPES_H_
#include <cinttypes>
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
namespace node {
// List of internalBinding() data wrappers. The first argument should match
// what the class passes to SET_BINDING_ID(), the second argument should match
// the C++ class name.
#define SERIALIZABLE_BINDING_TYPES(V) \
V(encoding_binding_data, encoding_binding::BindingData) \
V(fs_binding_data, fs::BindingData) \
V(mksnapshot_binding_data, mksnapshot::BindingData) \
V(v8_binding_data, v8_utils::BindingData) \
V(blob_binding_data, BlobBindingData) \
V(process_binding_data, process::BindingData) \
V(timers_binding_data, timers::BindingData) \
V(url_binding_data, url::BindingData) \
V(modules_binding_data, modules::BindingData)
#define UNSERIALIZABLE_BINDING_TYPES(V) \
V(http2_binding_data, http2::BindingData) \
V(http_parser_binding_data, http_parser::BindingData) \
V(quic_binding_data, quic::BindingData)
// List of (non-binding) BaseObjects that are serializable in the snapshot.
// The first argument should match what the type passes to
// SET_OBJECT_ID(), the second argument should match the C++ class
// name.
#define SERIALIZABLE_NON_BINDING_TYPES(V)
// Helper list of all binding data wrapper types.
#define BINDING_TYPES(V) \
SERIALIZABLE_BINDING_TYPES(V) \
UNSERIALIZABLE_BINDING_TYPES(V)
// Helper list of all BaseObjects that implement snapshot support.
#define SERIALIZABLE_OBJECT_TYPES(V) \
SERIALIZABLE_BINDING_TYPES(V) \
SERIALIZABLE_NON_BINDING_TYPES(V)
#define V(TypeId, NativeType) k_##TypeId,
enum class BindingDataType : uint8_t { BINDING_TYPES(V) kBindingDataTypeCount };
// Make sure that we put the bindings first so that we can also use the enums
// for the bindings as index to the binding data store.
enum class EmbedderObjectType : uint8_t {
BINDING_TYPES(V) SERIALIZABLE_NON_BINDING_TYPES(V)
// We do not need to know about all the unserializable non-binding types for
// now so we do not list them.
kEmbedderObjectTypeCount
};
#undef V
// For now, BaseObjects only need to call this when they implement snapshot
// support.
#define SET_OBJECT_ID(TypeId) \
static constexpr EmbedderObjectType type_int = EmbedderObjectType::k_##TypeId;
// Binding data should call this so that they can be looked up from the binding
// data store.
#define SET_BINDING_ID(TypeId) \
static constexpr BindingDataType binding_type_int = \
BindingDataType::k_##TypeId; \
SET_OBJECT_ID(TypeId) \
static_assert(static_cast<uint8_t>(type_int) == \
static_cast<uint8_t>(binding_type_int));
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_BASE_OBJECT_TYPES_H_

View File

@ -0,0 +1,385 @@
#ifndef SRC_BLOB_SERIALIZER_DESERIALIZER_INL_H_
#define SRC_BLOB_SERIALIZER_DESERIALIZER_INL_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "blob_serializer_deserializer.h"
#include <ostream>
#include <sstream>
#include <string>
#include <type_traits>
#include <utility>
#include "debug_utils-inl.h"
// This is related to the blob that is used in snapshots and single executable
// applications and has nothing to do with `node_blob.h`.
namespace node {
struct EnvSerializeInfo;
struct PropInfo;
struct RealmSerializeInfo;
namespace builtins {
struct CodeCacheInfo;
} // namespace builtins
// These operator<< overload declarations are needed because
// BlobSerializerDeserializer::ToStr() uses these.
std::ostream& operator<<(std::ostream& output,
const builtins::CodeCacheInfo& info);
std::ostream& operator<<(std::ostream& output,
const std::vector<builtins::CodeCacheInfo>& vec);
std::ostream& operator<<(std::ostream& output, const std::vector<uint8_t>& vec);
std::ostream& operator<<(std::ostream& output,
const std::vector<PropInfo>& vec);
std::ostream& operator<<(std::ostream& output, const PropInfo& info);
std::ostream& operator<<(std::ostream& output,
const std::vector<std::string>& vec);
std::ostream& operator<<(std::ostream& output, const RealmSerializeInfo& i);
std::ostream& operator<<(std::ostream& output, const EnvSerializeInfo& i);
template <typename... Args>
void BlobSerializerDeserializer::Debug(const char* format,
Args&&... args) const {
if (is_debug) {
FPrintF(stderr, format, std::forward<Args>(args)...);
}
}
template <typename T>
std::string BlobSerializerDeserializer::ToStr(const T& arg) const {
std::stringstream ss;
ss << arg;
return ss.str();
}
template <typename T>
std::string BlobSerializerDeserializer::GetName() const {
#define TYPE_LIST(V) \
V(builtins::CodeCacheInfo) \
V(PropInfo) \
V(std::string)
#define V(TypeName) \
if constexpr (std::is_same_v<T, TypeName>) { \
return #TypeName; \
} else // NOLINT(readability/braces)
TYPE_LIST(V)
#undef V
if constexpr (std::is_arithmetic_v<T>) {
return (std::is_unsigned_v<T> ? "uint"
: std::is_integral_v<T> ? "int"
: "float") +
std::to_string(sizeof(T) * 8) + "_t";
}
return "";
}
// Helper for reading numeric types.
template <typename Impl>
template <typename T>
T BlobDeserializer<Impl>::ReadArithmetic() {
static_assert(std::is_arithmetic_v<T>, "Not an arithmetic type");
T result;
ReadArithmetic(&result, 1);
return result;
}
// Layout of vectors:
// [ 4/8 bytes ] count
// [ ... ] contents (count * size of individual elements)
template <typename Impl>
template <typename T>
std::vector<T> BlobDeserializer<Impl>::ReadVector() {
if (is_debug) {
std::string name = GetName<T>();
Debug("\nReadVector<%s>()(%d-byte)\n", name.c_str(), sizeof(T));
}
size_t count = static_cast<size_t>(ReadArithmetic<size_t>());
if (count == 0) {
return std::vector<T>();
}
if (is_debug) {
Debug("Reading %d vector elements...\n", count);
}
std::vector<T> result;
if constexpr (std::is_arithmetic_v<T>) {
result = ReadArithmeticVector<T>(count);
} else {
result = ReadNonArithmeticVector<T>(count);
}
if (is_debug) {
std::string str = std::is_arithmetic_v<T> ? "" : ToStr(result);
std::string name = GetName<T>();
Debug("ReadVector<%s>() read %s\n", name.c_str(), str.c_str());
}
return result;
}
template <typename Impl>
std::string BlobDeserializer<Impl>::ReadString() {
std::string_view view = ReadStringView(StringLogMode::kAddressAndContent);
return std::string(view);
}
template <typename Impl>
std::string_view BlobDeserializer<Impl>::ReadStringView(StringLogMode mode) {
size_t length = ReadArithmetic<size_t>();
Debug("ReadStringView(), length=%zu: ", length);
if (length == 0) {
Debug("ReadStringView() read an empty view\n");
return std::string_view();
}
std::string_view result(sink.data() + read_total, length);
Debug("%p, read %zu bytes", result.data(), result.size());
if (mode == StringLogMode::kAddressAndContent) {
Debug(", content:%s%s", length > 32 ? "\n" : " ", result);
}
Debug("\n");
read_total += length;
return result;
}
// Helper for reading an array of numeric types.
template <typename Impl>
template <typename T>
void BlobDeserializer<Impl>::ReadArithmetic(T* out, size_t count) {
static_assert(std::is_arithmetic_v<T>, "Not an arithmetic type");
DCHECK_GT(count, 0); // Should not read contents for vectors of size 0.
if (is_debug) {
std::string name = GetName<T>();
Debug("Read<%s>()(%d-byte), count=%d: ", name.c_str(), sizeof(T), count);
}
size_t size = sizeof(T) * count;
memcpy(out, sink.data() + read_total, size);
if (is_debug) {
std::string str =
"{ " + std::to_string(out[0]) + (count > 1 ? ", ... }" : " }");
Debug("%s, read %zu bytes\n", str.c_str(), size);
}
read_total += size;
}
// Helper for reading numeric vectors.
template <typename Impl>
template <typename Number>
std::vector<Number> BlobDeserializer<Impl>::ReadArithmeticVector(size_t count) {
static_assert(std::is_arithmetic_v<Number>, "Not an arithmetic type");
DCHECK_GT(count, 0); // Should not read contents for vectors of size 0.
std::vector<Number> result(count);
ReadArithmetic(result.data(), count);
return result;
}
// Helper for reading non-numeric vectors.
template <typename Impl>
template <typename T>
std::vector<T> BlobDeserializer<Impl>::ReadNonArithmeticVector(size_t count) {
static_assert(!std::is_arithmetic_v<T>, "Arithmetic type");
DCHECK_GT(count, 0); // Should not read contents for vectors of size 0.
std::vector<T> result;
result.reserve(count);
bool original_is_debug = is_debug;
is_debug = original_is_debug && !std::is_same_v<T, std::string>;
for (size_t i = 0; i < count; ++i) {
if (is_debug) {
Debug("\n[%d] ", i);
}
result.push_back(ReadElement<T>());
}
is_debug = original_is_debug;
return result;
}
template <typename Impl>
template <typename T>
T BlobDeserializer<Impl>::ReadElement() {
if constexpr (std::is_arithmetic_v<T>) {
return ReadArithmetic<T>();
} else if constexpr (std::is_same_v<T, std::string>) {
return ReadString();
} else {
return impl()->template Read<T>();
}
}
// Helper for writing numeric types.
template <typename Impl>
template <typename T>
size_t BlobSerializer<Impl>::WriteArithmetic(const T& data) {
static_assert(std::is_arithmetic_v<T>, "Not an arithmetic type");
return WriteArithmetic(&data, 1);
}
// Layout of vectors:
// [ 4/8 bytes ] count
// [ ... ] contents (count * size of individual elements)
template <typename Impl>
template <typename T>
size_t BlobSerializer<Impl>::WriteVector(const std::vector<T>& data) {
if (is_debug) {
std::string str = std::is_arithmetic_v<T> ? "" : ToStr(data);
std::string name = GetName<T>();
Debug("\nAt 0x%x: WriteVector<%s>() (%d-byte), count=%d: %s\n",
sink.size(),
name.c_str(),
sizeof(T),
data.size(),
str.c_str());
}
size_t written_total = WriteArithmetic<size_t>(data.size());
if (data.empty()) {
return written_total;
}
if constexpr (std::is_arithmetic_v<T>) {
written_total += WriteArithmeticVector<T>(data);
} else {
written_total += WriteNonArithmeticVector<T>(data);
}
if (is_debug) {
std::string name = GetName<T>();
Debug("WriteVector<%s>() wrote %d bytes\n", name.c_str(), written_total);
}
return written_total;
}
// The layout of a written string:
// [ 4/8 bytes ] length
// [ |length| bytes ] contents
template <typename Impl>
size_t BlobSerializer<Impl>::WriteStringView(std::string_view data,
StringLogMode mode) {
Debug("At 0x%x: WriteStringView(), length=%zu: %p\n",
sink.size(),
data.size(),
data.data());
size_t written_total = WriteArithmetic<size_t>(data.size());
size_t length = data.size();
if (length == 0) {
Debug("WriteStringView() wrote an empty view\n");
return written_total;
}
sink.insert(sink.end(), data.data(), data.data() + length);
written_total += length;
Debug("WriteStringView() wrote %zu bytes\n", written_total);
if (mode == StringLogMode::kAddressAndContent) {
Debug("%s", data);
}
return written_total;
}
template <typename Impl>
size_t BlobSerializer<Impl>::WriteString(const std::string& data) {
return WriteStringView(data, StringLogMode::kAddressAndContent);
}
static size_t kPreviewCount = 16;
// Helper for writing an array of numeric types.
template <typename Impl>
template <typename T>
size_t BlobSerializer<Impl>::WriteArithmetic(const T* data, size_t count) {
static_assert(std::is_arithmetic_v<T>, "Arithmetic type");
DCHECK_GT(count, 0); // Should not write contents for vectors of size 0.
if (is_debug) {
size_t preview_count = count < kPreviewCount ? count : kPreviewCount;
std::string str = "{ ";
for (size_t i = 0; i < preview_count; ++i) {
str += (std::to_string(data[i]) + ",");
}
if (count > preview_count) {
str += "...";
}
str += "}";
std::string name = GetName<T>();
Debug("At 0x%x: Write<%s>() (%zu-byte), count=%zu: %s",
sink.size(),
name.c_str(),
sizeof(T),
count,
str.c_str());
}
size_t size = sizeof(T) * count;
const char* pos = reinterpret_cast<const char*>(data);
sink.insert(sink.end(), pos, pos + size);
if (is_debug) {
Debug(", wrote %zu bytes\n", size);
}
return size;
}
// Helper for writing numeric vectors.
template <typename Impl>
template <typename Number>
size_t BlobSerializer<Impl>::WriteArithmeticVector(
const std::vector<Number>& data) {
static_assert(std::is_arithmetic_v<Number>, "Arithmetic type");
return WriteArithmetic(data.data(), data.size());
}
// Helper for writing non-numeric vectors.
template <typename Impl>
template <typename T>
size_t BlobSerializer<Impl>::WriteNonArithmeticVector(
const std::vector<T>& data) {
static_assert(!std::is_arithmetic_v<T>, "Arithmetic type");
DCHECK_GT(data.size(),
0); // Should not write contents for vectors of size 0.
size_t written_total = 0;
bool original_is_debug = is_debug;
is_debug = original_is_debug && !std::is_same_v<T, std::string>;
for (size_t i = 0; i < data.size(); ++i) {
if (is_debug) {
Debug("\n[%d] ", i);
}
written_total += WriteElement<T>(data[i]);
}
is_debug = original_is_debug;
return written_total;
}
template <typename Impl>
template <typename T>
size_t BlobSerializer<Impl>::WriteElement(const T& data) {
if constexpr (std::is_arithmetic_v<T>) {
return WriteArithmetic<T>(data);
} else if constexpr (std::is_same_v<T, std::string>) {
return WriteString(data);
} else {
return impl()->template Write<T>(data);
}
}
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_BLOB_SERIALIZER_DESERIALIZER_INL_H_

View File

@ -0,0 +1,132 @@
#ifndef SRC_BLOB_SERIALIZER_DESERIALIZER_H_
#define SRC_BLOB_SERIALIZER_DESERIALIZER_H_
#include <string>
#include <vector>
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
// This is related to the blob that is used in snapshots and single executable
// applications and has nothing to do with `node_blob.h`.
namespace node {
class BlobSerializerDeserializer {
public:
explicit BlobSerializerDeserializer(bool is_debug_v) : is_debug(is_debug_v) {}
template <typename... Args>
void Debug(const char* format, Args&&... args) const;
template <typename T>
std::string ToStr(const T& arg) const;
template <typename T>
std::string GetName() const;
bool is_debug = false;
};
enum class StringLogMode {
kAddressOnly, // Can be used when the string contains binary content.
kAddressAndContent,
};
// Child classes are expected to implement T Read<T>() where
// !std::is_arithmetic_v<T> && !std::is_same_v<T, std::string>
template <typename Impl>
class BlobDeserializer : public BlobSerializerDeserializer {
public:
explicit BlobDeserializer(bool is_debug_v, std::string_view s)
: BlobSerializerDeserializer(is_debug_v), sink(s) {}
~BlobDeserializer() = default;
size_t read_total = 0;
std::string_view sink;
Impl* impl() { return static_cast<Impl*>(this); }
const Impl* impl() const { return static_cast<const Impl*>(this); }
// Helper for reading numeric types.
template <typename T>
T ReadArithmetic();
// Layout of vectors:
// [ 4/8 bytes ] count
// [ ... ] contents (count * size of individual elements)
template <typename T>
std::vector<T> ReadVector();
// ReadString() creates a copy of the data. ReadStringView() doesn't.
std::string ReadString();
std::string_view ReadStringView(StringLogMode mode);
// Helper for reading an array of numeric types.
template <typename T>
void ReadArithmetic(T* out, size_t count);
// Helper for reading numeric vectors.
template <typename Number>
std::vector<Number> ReadArithmeticVector(size_t count);
private:
// Helper for reading non-numeric vectors.
template <typename T>
std::vector<T> ReadNonArithmeticVector(size_t count);
template <typename T>
T ReadElement();
};
// Child classes are expected to implement size_t Write<T>(const T&) where
// !std::is_arithmetic_v<T> && !std::is_same_v<T, std::string>
template <typename Impl>
class BlobSerializer : public BlobSerializerDeserializer {
public:
explicit BlobSerializer(bool is_debug_v)
: BlobSerializerDeserializer(is_debug_v) {}
~BlobSerializer() = default;
Impl* impl() { return static_cast<Impl*>(this); }
const Impl* impl() const { return static_cast<const Impl*>(this); }
std::vector<char> sink;
// Helper for writing numeric types.
template <typename T>
size_t WriteArithmetic(const T& data);
// Layout of vectors:
// [ 4/8 bytes ] count
// [ ... ] contents (count * size of individual elements)
template <typename T>
size_t WriteVector(const std::vector<T>& data);
// The layout of a written string:
// [ 4/8 bytes ] length
// [ |length| bytes ] contents
size_t WriteStringView(std::string_view data, StringLogMode mode);
size_t WriteString(const std::string& data);
// Helper for writing an array of numeric types.
template <typename T>
size_t WriteArithmetic(const T* data, size_t count);
// Helper for writing numeric vectors.
template <typename Number>
size_t WriteArithmeticVector(const std::vector<Number>& data);
private:
// Helper for writing non-numeric vectors.
template <typename T>
size_t WriteNonArithmeticVector(const std::vector<T>& data);
template <typename T>
size_t WriteElement(const T& data);
};
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_BLOB_SERIALIZER_DESERIALIZER_H_

97
src/callback_queue-inl.h Normal file
View File

@ -0,0 +1,97 @@
#ifndef SRC_CALLBACK_QUEUE_INL_H_
#define SRC_CALLBACK_QUEUE_INL_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "callback_queue.h"
namespace node {
template <typename R, typename... Args>
template <typename Fn>
std::unique_ptr<typename CallbackQueue<R, Args...>::Callback>
CallbackQueue<R, Args...>::CreateCallback(Fn&& fn, CallbackFlags::Flags flags) {
return std::make_unique<CallbackImpl<Fn>>(std::move(fn), flags);
}
template <typename R, typename... Args>
std::unique_ptr<typename CallbackQueue<R, Args...>::Callback>
CallbackQueue<R, Args...>::Shift() {
std::unique_ptr<Callback> ret = std::move(head_);
if (ret) {
head_ = ret->get_next();
if (!head_)
tail_ = nullptr; // The queue is now empty.
size_--;
}
return ret;
}
template <typename R, typename... Args>
void CallbackQueue<R, Args...>::Push(std::unique_ptr<Callback> cb) {
Callback* prev_tail = tail_;
size_++;
tail_ = cb.get();
if (prev_tail != nullptr)
prev_tail->set_next(std::move(cb));
else
head_ = std::move(cb);
}
template <typename R, typename... Args>
void CallbackQueue<R, Args...>::ConcatMove(CallbackQueue<R, Args...>&& other) {
size_ += other.size_;
if (tail_ != nullptr)
tail_->set_next(std::move(other.head_));
else
head_ = std::move(other.head_);
tail_ = other.tail_;
other.tail_ = nullptr;
other.size_ = 0;
}
template <typename R, typename... Args>
size_t CallbackQueue<R, Args...>::size() const {
return size_.load();
}
template <typename R, typename... Args>
CallbackQueue<R, Args...>::Callback::Callback(CallbackFlags::Flags flags)
: flags_(flags) {}
template <typename R, typename... Args>
CallbackFlags::Flags CallbackQueue<R, Args...>::Callback::flags() const {
return flags_;
}
template <typename R, typename... Args>
std::unique_ptr<typename CallbackQueue<R, Args...>::Callback>
CallbackQueue<R, Args...>::Callback::get_next() {
return std::move(next_);
}
template <typename R, typename... Args>
void CallbackQueue<R, Args...>::Callback::set_next(
std::unique_ptr<Callback> next) {
next_ = std::move(next);
}
template <typename R, typename... Args>
template <typename Fn>
CallbackQueue<R, Args...>::CallbackImpl<Fn>::CallbackImpl(
Fn&& callback, CallbackFlags::Flags flags)
: Callback(flags),
callback_(std::move(callback)) {}
template <typename R, typename... Args>
template <typename Fn>
R CallbackQueue<R, Args...>::CallbackImpl<Fn>::Call(Args... args) {
return callback_(std::forward<Args>(args)...);
}
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CALLBACK_QUEUE_INL_H_

79
src/callback_queue.h Normal file
View File

@ -0,0 +1,79 @@
#ifndef SRC_CALLBACK_QUEUE_H_
#define SRC_CALLBACK_QUEUE_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include <atomic>
#include <memory>
namespace node {
namespace CallbackFlags {
enum Flags {
kUnrefed = 0,
kRefed = 1,
};
}
// A queue of C++ functions that take Args... as arguments and return R
// (this is similar to the signature of std::function).
// New entries are added using `CreateCallback()`/`Push()`, and removed using
// `Shift()`.
// The `refed` flag is left for easier use in situations in which some of these
// should be run even if nothing else is keeping the event loop alive.
template <typename R, typename... Args>
class CallbackQueue {
public:
class Callback {
public:
explicit inline Callback(CallbackFlags::Flags flags);
virtual ~Callback() = default;
virtual R Call(Args... args) = 0;
inline CallbackFlags::Flags flags() const;
private:
inline std::unique_ptr<Callback> get_next();
inline void set_next(std::unique_ptr<Callback> next);
CallbackFlags::Flags flags_;
std::unique_ptr<Callback> next_;
friend class CallbackQueue;
};
template <typename Fn>
inline std::unique_ptr<Callback> CreateCallback(
Fn&& fn, CallbackFlags::Flags);
inline std::unique_ptr<Callback> Shift();
inline void Push(std::unique_ptr<Callback> cb);
// ConcatMove adds elements from 'other' to the end of this list, and clears
// 'other' afterwards.
inline void ConcatMove(CallbackQueue&& other);
// size() is atomic and may be called from any thread.
inline size_t size() const;
private:
template <typename Fn>
class CallbackImpl final : public Callback {
public:
CallbackImpl(Fn&& callback, CallbackFlags::Flags flags);
R Call(Args... args) override;
private:
Fn callback_;
};
std::atomic<size_t> size_ {0};
std::unique_ptr<Callback> head_;
Callback* tail_ = nullptr;
};
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CALLBACK_QUEUE_H_

2258
src/cares_wrap.cc Normal file

File diff suppressed because it is too large Load Diff

446
src/cares_wrap.h Normal file
View File

@ -0,0 +1,446 @@
#ifndef SRC_CARES_WRAP_H_
#define SRC_CARES_WRAP_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#define CARES_STATICLIB
#include "async_wrap.h"
#include "base_object.h"
#include "env.h"
#include "memory_tracker.h"
#include "node.h"
#include "node_internals.h"
#include "util.h"
#include "ares.h"
#include "v8.h"
#include "uv.h"
#include <unordered_set>
#ifdef __POSIX__
# include <netdb.h>
#endif // __POSIX__
# include <ares_nameser.h>
namespace node {
namespace cares_wrap {
constexpr int ns_t_cname_or_a = -1;
constexpr int DNS_ESETSRVPENDING = -1000;
constexpr uint8_t DNS_ORDER_VERBATIM = 0;
constexpr uint8_t DNS_ORDER_IPV4_FIRST = 1;
constexpr uint8_t DNS_ORDER_IPV6_FIRST = 2;
class ChannelWrap;
inline void safe_free_hostent(struct hostent* host);
using HostEntPointer = DeleteFnPtr<hostent, ares_free_hostent>;
using SafeHostEntPointer = DeleteFnPtr<hostent, safe_free_hostent>;
inline const char* ToErrorCodeString(int status) {
switch (status) {
#define V(code) case ARES_##code: return #code;
V(EADDRGETNETWORKPARAMS)
V(EBADFAMILY)
V(EBADFLAGS)
V(EBADHINTS)
V(EBADNAME)
V(EBADQUERY)
V(EBADRESP)
V(EBADSTR)
V(ECANCELLED)
V(ECONNREFUSED)
V(EDESTRUCTION)
V(EFILE)
V(EFORMERR)
V(ELOADIPHLPAPI)
V(ENODATA)
V(ENOMEM)
V(ENONAME)
V(ENOTFOUND)
V(ENOTIMP)
V(ENOTINITIALIZED)
V(EOF)
V(EREFUSED)
V(ESERVFAIL)
V(ETIMEOUT)
#undef V
}
return "UNKNOWN_ARES_ERROR";
}
inline void cares_wrap_hostent_cpy(
struct hostent* dest,
const struct hostent* src) {
dest->h_addr_list = nullptr;
dest->h_addrtype = 0;
dest->h_aliases = nullptr;
dest->h_length = 0;
dest->h_name = nullptr;
/* copy `h_name` */
size_t name_size = strlen(src->h_name) + 1;
dest->h_name = node::Malloc<char>(name_size);
memcpy(dest->h_name, src->h_name, name_size);
/* copy `h_aliases` */
size_t alias_count;
for (alias_count = 0;
src->h_aliases[alias_count] != nullptr;
alias_count++) {
}
dest->h_aliases = node::Malloc<char*>(alias_count + 1);
for (size_t i = 0; i < alias_count; i++) {
const size_t cur_alias_size = strlen(src->h_aliases[i]) + 1;
dest->h_aliases[i] = node::Malloc(cur_alias_size);
memcpy(dest->h_aliases[i], src->h_aliases[i], cur_alias_size);
}
dest->h_aliases[alias_count] = nullptr;
/* copy `h_addr_list` */
size_t list_count;
for (list_count = 0;
src->h_addr_list[list_count] != nullptr;
list_count++) {
}
dest->h_addr_list = node::Malloc<char*>(list_count + 1);
for (size_t i = 0; i < list_count; i++) {
dest->h_addr_list[i] = node::Malloc(src->h_length);
memcpy(dest->h_addr_list[i], src->h_addr_list[i], src->h_length);
}
dest->h_addr_list[list_count] = nullptr;
/* work after work */
dest->h_length = src->h_length;
dest->h_addrtype = src->h_addrtype;
}
struct NodeAresTask final : public MemoryRetainer {
ChannelWrap* channel;
ares_socket_t sock;
uv_poll_t poll_watcher;
inline void MemoryInfo(MemoryTracker* trakcer) const override;
SET_MEMORY_INFO_NAME(NodeAresTask)
SET_SELF_SIZE(NodeAresTask)
struct Hash {
inline size_t operator()(NodeAresTask* a) const {
return std::hash<ares_socket_t>()(a->sock);
}
};
struct Equal {
inline bool operator()(NodeAresTask* a, NodeAresTask* b) const {
return a->sock == b->sock;
}
};
static NodeAresTask* Create(ChannelWrap* channel, ares_socket_t sock);
using List = std::unordered_set<NodeAresTask*, Hash, Equal>;
};
class ChannelWrap final : public AsyncWrap {
public:
ChannelWrap(
Environment* env,
v8::Local<v8::Object> object,
int timeout,
int tries);
~ChannelWrap() override;
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
void Setup();
void EnsureServers();
void StartTimer();
void CloseTimer();
void ModifyActivityQueryCount(int count);
inline uv_timer_t* timer_handle() { return timer_handle_; }
inline ares_channel cares_channel() { return channel_; }
inline void set_query_last_ok(bool ok) { query_last_ok_ = ok; }
inline void set_is_servers_default(bool is_default) {
is_servers_default_ = is_default;
}
inline int active_query_count() { return active_query_count_; }
inline NodeAresTask::List* task_list() { return &task_list_; }
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(ChannelWrap)
SET_SELF_SIZE(ChannelWrap)
static void AresTimeout(uv_timer_t* handle);
private:
uv_timer_t* timer_handle_ = nullptr;
ares_channel channel_ = nullptr;
bool query_last_ok_ = true;
bool is_servers_default_ = true;
bool library_inited_ = false;
int timeout_;
int tries_;
int active_query_count_ = 0;
NodeAresTask::List task_list_;
};
class GetAddrInfoReqWrap final : public ReqWrap<uv_getaddrinfo_t> {
public:
GetAddrInfoReqWrap(Environment* env,
v8::Local<v8::Object> req_wrap_obj,
uint8_t order);
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(GetAddrInfoReqWrap)
SET_SELF_SIZE(GetAddrInfoReqWrap)
uint8_t order() const { return order_; }
private:
const uint8_t order_;
};
class GetNameInfoReqWrap final : public ReqWrap<uv_getnameinfo_t> {
public:
GetNameInfoReqWrap(Environment* env, v8::Local<v8::Object> req_wrap_obj);
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(GetNameInfoReqWrap)
SET_SELF_SIZE(GetNameInfoReqWrap)
};
struct ResponseData final {
int status;
bool is_host;
SafeHostEntPointer host;
MallocedBuffer<unsigned char> buf;
};
template <typename Traits>
class QueryWrap final : public AsyncWrap {
public:
QueryWrap(ChannelWrap* channel, v8::Local<v8::Object> req_wrap_obj)
: AsyncWrap(channel->env(), req_wrap_obj, AsyncWrap::PROVIDER_QUERYWRAP),
channel_(channel),
trace_name_(Traits::name) {}
~QueryWrap() {
CHECK_EQ(false, persistent().IsEmpty());
// Let Callback() know that this object no longer exists.
if (callback_ptr_ != nullptr)
*callback_ptr_ = nullptr;
}
int Send(const char* name) {
return Traits::Send(this, name);
}
void AresQuery(const char* name,
ares_dns_class_t dnsclass,
ares_dns_rec_type_t type) {
channel_->EnsureServers();
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
TRACING_CATEGORY_NODE2(dns, native), trace_name_, this,
"name", TRACE_STR_COPY(name));
ares_query_dnsrec(channel_->cares_channel(),
name,
dnsclass,
type,
Callback,
MakeCallbackPointer(),
nullptr);
}
void ParseError(int status) {
CHECK_NE(status, ARES_SUCCESS);
v8::HandleScope handle_scope(env()->isolate());
v8::Context::Scope context_scope(env()->context());
const char* code = ToErrorCodeString(status);
v8::Local<v8::Value> arg = OneByteString(env()->isolate(), code);
TRACE_EVENT_NESTABLE_ASYNC_END1(
TRACING_CATEGORY_NODE2(dns, native), trace_name_, this,
"error", status);
MakeCallback(env()->oncomplete_string(), 1, &arg);
}
const BaseObjectPtr<ChannelWrap>& channel() const { return channel_; }
void AfterResponse() {
CHECK(response_data_);
int status = response_data_->status;
if (status != ARES_SUCCESS)
return ParseError(status);
if (!Traits::Parse(this, response_data_).To(&status)) {
return ParseError(ARES_ECANCELLED);
}
if (status != ARES_SUCCESS)
ParseError(status);
}
void* MakeCallbackPointer() {
CHECK_NULL(callback_ptr_);
callback_ptr_ = new QueryWrap<Traits>*(this);
return callback_ptr_;
}
static QueryWrap<Traits>* FromCallbackPointer(void* arg) {
std::unique_ptr<QueryWrap<Traits>*> wrap_ptr {
static_cast<QueryWrap<Traits>**>(arg)
};
QueryWrap<Traits>* wrap = *wrap_ptr.get();
if (wrap == nullptr) return nullptr;
wrap->callback_ptr_ = nullptr;
return wrap;
}
static void Callback(void* arg,
ares_status_t status,
size_t timeouts,
const ares_dns_record_t* dnsrec) {
QueryWrap<Traits>* wrap = FromCallbackPointer(arg);
if (wrap == nullptr) return;
unsigned char* buf_copy = nullptr;
size_t answer_len = 0;
if (status == ARES_SUCCESS) {
// No need to explicitly call ares_free_string here,
// as it is a wrapper around free, which is already
// invoked when MallocedBuffer is destructed.
ares_dns_write(dnsrec, &buf_copy, &answer_len);
}
wrap->response_data_ = std::make_unique<ResponseData>();
ResponseData* data = wrap->response_data_.get();
data->status = status;
data->is_host = false;
data->buf = MallocedBuffer<unsigned char>(buf_copy, answer_len);
wrap->QueueResponseCallback(status);
}
static void Callback(
void* arg,
int status,
int timeouts,
struct hostent* host) {
QueryWrap<Traits>* wrap = FromCallbackPointer(arg);
if (wrap == nullptr) return;
struct hostent* host_copy = nullptr;
if (status == ARES_SUCCESS) {
host_copy = node::Malloc<hostent>(1);
cares_wrap_hostent_cpy(host_copy, host);
}
wrap->response_data_ = std::make_unique<ResponseData>();
ResponseData* data = wrap->response_data_.get();
data->status = status;
data->host.reset(host_copy);
data->is_host = true;
wrap->QueueResponseCallback(status);
}
void QueueResponseCallback(int status) {
BaseObjectPtr<QueryWrap<Traits>> strong_ref{this};
env()->SetImmediate([this, strong_ref](Environment*) {
AfterResponse();
// Delete once strong_ref goes out of scope.
Detach();
});
channel_->set_query_last_ok(status != ARES_ECONNREFUSED);
channel_->ModifyActivityQueryCount(-1);
}
void CallOnComplete(
v8::Local<v8::Value> answer,
v8::Local<v8::Value> extra = v8::Local<v8::Value>()) {
v8::HandleScope handle_scope(env()->isolate());
v8::Context::Scope context_scope(env()->context());
v8::Local<v8::Value> argv[] = {
v8::Integer::New(env()->isolate(), 0),
answer,
extra
};
const int argc = arraysize(argv) - extra.IsEmpty();
TRACE_EVENT_NESTABLE_ASYNC_END0(
TRACING_CATEGORY_NODE2(dns, native), trace_name_, this);
MakeCallback(env()->oncomplete_string(), argc, argv);
}
void MemoryInfo(MemoryTracker* tracker) const override {
tracker->TrackField("channel", channel_);
if (response_data_) {
tracker->TrackFieldWithSize("response", response_data_->buf.size);
}
}
SET_MEMORY_INFO_NAME(QueryWrap)
SET_SELF_SIZE(QueryWrap<Traits>)
private:
BaseObjectPtr<ChannelWrap> channel_;
std::unique_ptr<ResponseData> response_data_;
const char* trace_name_;
// Pointer to pointer to 'this' that can be reset from the destructor,
// in order to let Callback() know that 'this' no longer exists.
QueryWrap<Traits>** callback_ptr_ = nullptr;
};
#define QUERY_TYPES(V) \
V(Reverse, reverse, getHostByAddr) \
V(A, resolve4, queryA) \
V(Any, resolveAny, queryAny) \
V(Aaaa, resolve6, queryAaaa) \
V(Caa, resolveCaa, queryCaa) \
V(Cname, resolveCname, queryCname) \
V(Mx, resolveMx, queryMx) \
V(Naptr, resolveNaptr, queryNaptr) \
V(Ns, resolveNs, queryNs) \
V(Ptr, resolvePtr, queryPtr) \
V(Srv, resolveSrv, querySrv) \
V(Soa, resolveSoa, querySoa) \
V(Tlsa, resolveTlsa, queryTlsa) \
V(Txt, resolveTxt, queryTxt)
// All query type handlers share the same basic structure, so we can simplify
// the code a bit by using a macro to define that structure.
#define TYPE_TRAITS(Name, label) \
struct Name##Traits final { \
static constexpr const char* name = #label; \
static int Send(QueryWrap<Name##Traits>* wrap, const char* name); \
static v8::Maybe<int> Parse( \
QueryWrap<Name##Traits>* wrap, \
const std::unique_ptr<ResponseData>& response); \
}; \
using Query##Name##Wrap = QueryWrap<Name##Traits>;
#define V(NAME, LABEL, _) TYPE_TRAITS(NAME, LABEL)
QUERY_TYPES(V)
#undef V
#undef TYPE_TRAITS
} // namespace cares_wrap
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CARES_WRAP_H_

View File

@ -0,0 +1,6 @@
// This is an auto generated file, please do not edit.
// Refer to tools/dep_updaters/update-cjs-module-lexer.sh
#ifndef SRC_CJS_MODULE_LEXER_VERSION_H_
#define SRC_CJS_MODULE_LEXER_VERSION_H_
#define CJS_MODULE_LEXER_VERSION "2.1.0"
#endif // SRC_CJS_MODULE_LEXER_VERSION_H_

36
src/cleanup_queue-inl.h Normal file
View File

@ -0,0 +1,36 @@
#ifndef SRC_CLEANUP_QUEUE_INL_H_
#define SRC_CLEANUP_QUEUE_INL_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "cleanup_queue.h"
#include "util.h"
namespace node {
inline size_t CleanupQueue::SelfSize() const {
return sizeof(CleanupQueue) +
cleanup_hooks_.size() * sizeof(CleanupHookCallback);
}
bool CleanupQueue::empty() const {
return cleanup_hooks_.empty();
}
void CleanupQueue::Add(Callback cb, void* arg) {
auto insertion_info =
cleanup_hooks_.emplace(cb, arg, cleanup_hook_counter_++);
// Make sure there was no existing element with these values.
CHECK_EQ(insertion_info.second, true);
}
void CleanupQueue::Remove(Callback cb, void* arg) {
CleanupHookCallback search{cb, arg, 0};
cleanup_hooks_.erase(search);
}
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CLEANUP_QUEUE_INL_H_

52
src/cleanup_queue.cc Normal file
View File

@ -0,0 +1,52 @@
#include "cleanup_queue.h" // NOLINT(build/include_inline)
#include <algorithm>
#include <vector>
#include "cleanup_queue-inl.h"
namespace node {
std::vector<CleanupQueue::CleanupHookCallback> CleanupQueue::GetOrdered()
const {
// Copy into a vector, since we can't sort an unordered_set in-place.
std::vector<CleanupHookCallback> callbacks(cleanup_hooks_.begin(),
cleanup_hooks_.end());
// We can't erase the copied elements from `cleanup_hooks_` yet, because we
// need to be able to check whether they were un-scheduled by another hook.
std::sort(callbacks.begin(),
callbacks.end(),
[](const CleanupHookCallback& a, const CleanupHookCallback& b) {
// Sort in descending order so that the most recently inserted
// callbacks are run first.
return a.insertion_order_counter_ > b.insertion_order_counter_;
});
return callbacks;
}
void CleanupQueue::Drain() {
std::vector<CleanupHookCallback> callbacks = GetOrdered();
for (const CleanupHookCallback& cb : callbacks) {
if (cleanup_hooks_.count(cb) == 0) {
// This hook was removed from the `cleanup_hooks_` set during another
// hook that was run earlier. Nothing to do here.
continue;
}
cb.fn_(cb.arg_);
cleanup_hooks_.erase(cb);
}
}
size_t CleanupQueue::CleanupHookCallback::Hash::operator()(
const CleanupHookCallback& cb) const {
return std::hash<void*>()(cb.arg_);
}
bool CleanupQueue::CleanupHookCallback::Equal::operator()(
const CleanupHookCallback& a, const CleanupHookCallback& b) const {
return a.fn_ == b.fn_ && a.arg_ == b.arg_;
}
} // namespace node

79
src/cleanup_queue.h Normal file
View File

@ -0,0 +1,79 @@
#ifndef SRC_CLEANUP_QUEUE_H_
#define SRC_CLEANUP_QUEUE_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include <cstddef>
#include <cstdint>
#include <unordered_set>
#include <vector>
#include "memory_tracker.h"
namespace node {
class CleanupQueue : public MemoryRetainer {
public:
typedef void (*Callback)(void*);
CleanupQueue() = default;
// Not copyable.
CleanupQueue(const CleanupQueue&) = delete;
SET_MEMORY_INFO_NAME(CleanupQueue)
SET_NO_MEMORY_INFO()
inline size_t SelfSize() const override;
inline bool empty() const;
inline void Add(Callback cb, void* arg);
inline void Remove(Callback cb, void* arg);
void Drain();
private:
class CleanupHookCallback {
public:
CleanupHookCallback(Callback fn,
void* arg,
uint64_t insertion_order_counter)
: fn_(fn),
arg_(arg),
insertion_order_counter_(insertion_order_counter) {}
// Only hashes `arg_`, since that is usually enough to identify the hook.
struct Hash {
size_t operator()(const CleanupHookCallback& cb) const;
};
// Compares by `fn_` and `arg_` being equal.
struct Equal {
bool operator()(const CleanupHookCallback& a,
const CleanupHookCallback& b) const;
};
private:
friend class CleanupQueue;
Callback fn_;
void* arg_;
// We keep track of the insertion order for these objects, so that we can
// call the callbacks in reverse order when we are cleaning up.
uint64_t insertion_order_counter_;
};
std::vector<CleanupHookCallback> GetOrdered() const;
// Use an unordered_set, so that we have efficient insertion and removal.
std::unordered_set<CleanupHookCallback,
CleanupHookCallback::Hash,
CleanupHookCallback::Equal>
cleanup_hooks_;
uint64_t cleanup_hook_counter_ = 0;
};
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CLEANUP_QUEUE_H_

555
src/compile_cache.cc Normal file
View File

@ -0,0 +1,555 @@
#include "compile_cache.h"
#include <string>
#include "debug_utils-inl.h"
#include "env-inl.h"
#include "node_file.h"
#include "node_internals.h"
#include "node_version.h"
#include "path.h"
#include "util.h"
#include "zlib.h"
#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS
#include <unistd.h> // getuid
#endif
namespace node {
using v8::Function;
using v8::Local;
using v8::Module;
using v8::ScriptCompiler;
using v8::String;
namespace {
std::string Uint32ToHex(uint32_t crc) {
std::string str;
str.reserve(8);
for (int i = 28; i >= 0; i -= 4) {
char digit = (crc >> i) & 0xF;
digit += digit < 10 ? '0' : 'a' - 10;
str.push_back(digit);
}
return str;
}
// TODO(joyeecheung): use other hashes?
uint32_t GetHash(const char* data, size_t size) {
uLong crc = crc32(0L, Z_NULL, 0);
return crc32(crc, reinterpret_cast<const Bytef*>(data), size);
}
std::string GetCacheVersionTag() {
// On platforms where uids are available, use different folders for
// different users to avoid cache miss due to permission incompatibility.
// On platforms where uids are not available, bare with the cache miss.
// This should be fine on Windows, as there local directories tend to be
// user-specific.
std::string tag = std::string(NODE_VERSION) + '-' + std::string(NODE_ARCH) +
'-' + Uint32ToHex(ScriptCompiler::CachedDataVersionTag());
#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS
tag += '-' + std::to_string(getuid());
#endif
return tag;
}
uint32_t GetCacheKey(std::string_view filename, CachedCodeType type) {
uLong crc = crc32(0L, Z_NULL, 0);
crc = crc32(crc, reinterpret_cast<const Bytef*>(&type), sizeof(type));
crc = crc32(
crc, reinterpret_cast<const Bytef*>(filename.data()), filename.length());
return crc;
}
} // namespace
template <typename... Args>
inline void CompileCacheHandler::Debug(const char* format,
Args&&... args) const {
if (is_debug_) [[unlikely]] {
FPrintF(stderr, format, std::forward<Args>(args)...);
}
}
ScriptCompiler::CachedData* CompileCacheEntry::CopyCache() const {
DCHECK_NOT_NULL(cache);
int cache_size = cache->length;
uint8_t* data = new uint8_t[cache_size];
memcpy(data, cache->data, cache_size);
return new ScriptCompiler::CachedData(
data, cache_size, ScriptCompiler::CachedData::BufferOwned);
}
// Used for identifying and verifying a file is a compile cache file.
// See comments in CompileCacheHandler::Persist().
constexpr uint32_t kCacheMagicNumber = 0x8adfdbb2;
const char* CompileCacheEntry::type_name() const {
switch (type) {
case CachedCodeType::kCommonJS:
return "CommonJS";
case CachedCodeType::kESM:
return "ESM";
case CachedCodeType::kStrippedTypeScript:
return "StrippedTypeScript";
case CachedCodeType::kTransformedTypeScript:
return "TransformedTypeScript";
case CachedCodeType::kTransformedTypeScriptWithSourceMaps:
return "TransformedTypeScriptWithSourceMaps";
default:
UNREACHABLE();
}
}
void CompileCacheHandler::ReadCacheFile(CompileCacheEntry* entry) {
Debug("[compile cache] reading cache from %s for %s %s...",
entry->cache_filename,
entry->type_name(),
entry->source_filename);
uv_fs_t req;
auto defer_req_cleanup = OnScopeLeave([&req]() { uv_fs_req_cleanup(&req); });
const char* path = entry->cache_filename.c_str();
uv_file file = uv_fs_open(nullptr, &req, path, O_RDONLY, 0, nullptr);
if (req.result < 0) {
// req will be cleaned up by scope leave.
Debug(" %s\n", uv_strerror(req.result));
return;
}
uv_fs_req_cleanup(&req);
auto defer_close = OnScopeLeave([file]() {
uv_fs_t close_req;
CHECK_EQ(0, uv_fs_close(nullptr, &close_req, file, nullptr));
uv_fs_req_cleanup(&close_req);
});
// Read the headers.
std::vector<uint32_t> headers(kHeaderCount);
uv_buf_t headers_buf = uv_buf_init(reinterpret_cast<char*>(headers.data()),
kHeaderCount * sizeof(uint32_t));
const int r = uv_fs_read(nullptr, &req, file, &headers_buf, 1, 0, nullptr);
if (r != static_cast<int>(headers_buf.len)) {
Debug("reading header failed, bytes read %d", r);
if (req.result < 0 && is_debug_) {
Debug(", %s", uv_strerror(req.result));
}
Debug("\n");
return;
}
Debug("[%d %d %d %d %d]...",
headers[kMagicNumberOffset],
headers[kCodeSizeOffset],
headers[kCacheSizeOffset],
headers[kCodeHashOffset],
headers[kCacheHashOffset]);
if (headers[kMagicNumberOffset] != kCacheMagicNumber) {
Debug("magic number mismatch: expected %d, actual %d\n",
kCacheMagicNumber,
headers[kMagicNumberOffset]);
return;
}
// Check the code size and hash which are already computed.
if (headers[kCodeSizeOffset] != entry->code_size) {
Debug("code size mismatch: expected %d, actual %d\n",
entry->code_size,
headers[kCodeSizeOffset]);
return;
}
if (headers[kCodeHashOffset] != entry->code_hash) {
Debug("code hash mismatch: expected %d, actual %d\n",
entry->code_hash,
headers[kCodeHashOffset]);
return;
}
// Read the cache, grow the buffer exponentially whenever it fills up.
size_t offset = headers_buf.len;
size_t capacity = 4096; // Initial buffer capacity
size_t total_read = 0;
uint8_t* buffer = new uint8_t[capacity];
while (true) {
// If there is not enough space to read more data, do a simple
// realloc here (we don't actually realloc because V8 requires
// the underlying buffer to be delete[]-able).
if (total_read == capacity) {
size_t new_capacity = capacity * 2;
auto* new_buffer = new uint8_t[new_capacity];
memcpy(new_buffer, buffer, capacity);
delete[] buffer;
buffer = new_buffer;
capacity = new_capacity;
}
uv_buf_t iov = uv_buf_init(reinterpret_cast<char*>(buffer + total_read),
capacity - total_read);
int bytes_read =
uv_fs_read(nullptr, &req, file, &iov, 1, offset + total_read, nullptr);
if (req.result < 0) { // Error.
// req will be cleaned up by scope leave.
delete[] buffer;
Debug(" %s\n", uv_strerror(req.result));
return;
}
uv_fs_req_cleanup(&req);
if (bytes_read <= 0) {
break;
}
total_read += bytes_read;
}
// Check the cache size and hash.
if (headers[kCacheSizeOffset] != total_read) {
Debug("cache size mismatch: expected %d, actual %d\n",
headers[kCacheSizeOffset],
total_read);
return;
}
uint32_t cache_hash = GetHash(reinterpret_cast<char*>(buffer), total_read);
if (headers[kCacheHashOffset] != cache_hash) {
Debug("cache hash mismatch: expected %d, actual %d\n",
headers[kCacheHashOffset],
cache_hash);
return;
}
entry->cache.reset(new ScriptCompiler::CachedData(
buffer, total_read, ScriptCompiler::CachedData::BufferOwned));
Debug(" success, size=%d\n", total_read);
}
CompileCacheEntry* CompileCacheHandler::GetOrInsert(Local<String> code,
Local<String> filename,
CachedCodeType type) {
DCHECK(!compile_cache_dir_.empty());
Utf8Value filename_utf8(isolate_, filename);
uint32_t key = GetCacheKey(filename_utf8.ToStringView(), type);
// TODO(joyeecheung): don't encode this again into UTF8. If we read the
// UTF8 content on disk as raw buffer (from the JS layer, while watching out
// for monkey patching), we can just hash it directly.
Utf8Value code_utf8(isolate_, code);
uint32_t code_hash = GetHash(code_utf8.out(), code_utf8.length());
auto loaded = compiler_cache_store_.find(key);
// TODO(joyeecheung): let V8's in-isolate compilation cache take precedence.
if (loaded != compiler_cache_store_.end() &&
loaded->second->code_hash == code_hash) {
return loaded->second.get();
}
// If the code hash mismatches, the code has changed, discard the stale entry
// and create a new one.
auto emplaced =
compiler_cache_store_.emplace(key, std::make_unique<CompileCacheEntry>());
auto* result = emplaced.first->second.get();
result->code_hash = code_hash;
result->code_size = code_utf8.length();
result->cache_key = key;
result->cache_filename =
compile_cache_dir_ + kPathSeparator + Uint32ToHex(key);
result->source_filename = filename_utf8.ToString();
result->cache = nullptr;
result->type = type;
// TODO(joyeecheung): if we fail enough times, stop trying for any future
// files.
ReadCacheFile(result);
return result;
}
ScriptCompiler::CachedData* SerializeCodeCache(Local<Function> func) {
return ScriptCompiler::CreateCodeCacheForFunction(func);
}
ScriptCompiler::CachedData* SerializeCodeCache(Local<Module> mod) {
return ScriptCompiler::CreateCodeCache(mod->GetUnboundModuleScript());
}
template <typename T>
void CompileCacheHandler::MaybeSaveImpl(CompileCacheEntry* entry,
Local<T> func_or_mod,
bool rejected) {
DCHECK_NOT_NULL(entry);
Debug("[compile cache] V8 code cache for %s %s was %s, ",
entry->type_name(),
entry->source_filename,
rejected ? "rejected"
: (entry->cache == nullptr) ? "not initialized"
: "accepted");
if (entry->cache != nullptr && !rejected) { // accepted
Debug("keeping the in-memory entry\n");
return;
}
Debug("%s the in-memory entry\n",
entry->cache == nullptr ? "initializing" : "refreshing");
ScriptCompiler::CachedData* data = SerializeCodeCache(func_or_mod);
DCHECK_EQ(data->buffer_policy, ScriptCompiler::CachedData::BufferOwned);
entry->refreshed = true;
entry->cache.reset(data);
}
void CompileCacheHandler::MaybeSave(CompileCacheEntry* entry,
Local<Module> mod,
bool rejected) {
DCHECK(mod->IsSourceTextModule());
MaybeSaveImpl(entry, mod, rejected);
}
void CompileCacheHandler::MaybeSave(CompileCacheEntry* entry,
Local<Function> func,
bool rejected) {
MaybeSaveImpl(entry, func, rejected);
}
void CompileCacheHandler::MaybeSave(CompileCacheEntry* entry,
std::string_view transpiled) {
CHECK(entry->type == CachedCodeType::kStrippedTypeScript ||
entry->type == CachedCodeType::kTransformedTypeScript ||
entry->type == CachedCodeType::kTransformedTypeScriptWithSourceMaps);
Debug("[compile cache] saving transpilation cache for %s %s\n",
entry->type_name(),
entry->source_filename);
// TODO(joyeecheung): it's weird to copy it again here. Convert the v8::String
// directly into buffer held by v8::ScriptCompiler::CachedData here.
int cache_size = static_cast<int>(transpiled.size());
uint8_t* data = new uint8_t[cache_size];
memcpy(data, transpiled.data(), cache_size);
entry->cache.reset(new ScriptCompiler::CachedData(
data, cache_size, ScriptCompiler::CachedData::BufferOwned));
entry->refreshed = true;
}
/**
* Persist the compile cache accumulated in memory to disk.
*
* To avoid race conditions, the cache file includes hashes of the original
* source code and the cache content. It's first written to a temporary file
* before being renamed to the target name.
*
* Layout of a cache file:
* [uint32_t] magic number
* [uint32_t] code size
* [uint32_t] code hash
* [uint32_t] cache size
* [uint32_t] cache hash
* .... compile cache content ....
*/
void CompileCacheHandler::Persist() {
DCHECK(!compile_cache_dir_.empty());
// TODO(joyeecheung): do this using a separate event loop to utilize the
// libuv thread pool and do the file system operations concurrently.
// TODO(joyeecheung): Currently flushing is triggered by either process
// shutdown or user requests. In the future we should simply start the
// writes right after module loading on a separate thread, and this method
// only blocks until all the pending writes (if any) on the other thread are
// finished. In that case, the off-thread writes should finish long
// before any attempt of flushing is made so the method would then only
// incur a negligible overhead from thread synchronization.
for (auto& pair : compiler_cache_store_) {
auto* entry = pair.second.get();
const char* type_name = entry->type_name();
if (entry->cache == nullptr) {
Debug("[compile cache] skip persisting %s %s because the cache was not "
"initialized\n",
type_name,
entry->source_filename);
continue;
}
if (entry->refreshed == false) {
Debug(
"[compile cache] skip persisting %s %s because cache was the same\n",
type_name,
entry->source_filename);
continue;
}
if (entry->persisted == true) {
Debug("[compile cache] skip persisting %s %s because cache was already "
"persisted\n",
type_name,
entry->source_filename);
continue;
}
DCHECK_EQ(entry->cache->buffer_policy,
ScriptCompiler::CachedData::BufferOwned);
char* cache_ptr =
reinterpret_cast<char*>(const_cast<uint8_t*>(entry->cache->data));
uint32_t cache_size = static_cast<uint32_t>(entry->cache->length);
uint32_t cache_hash = GetHash(cache_ptr, cache_size);
// Generating headers.
std::vector<uint32_t> headers(kHeaderCount);
headers[kMagicNumberOffset] = kCacheMagicNumber;
headers[kCodeSizeOffset] = entry->code_size;
headers[kCacheSizeOffset] = cache_size;
headers[kCodeHashOffset] = entry->code_hash;
headers[kCacheHashOffset] = cache_hash;
// Generate the temporary filename.
// The temporary file should be placed in a location like:
//
// $NODE_COMPILE_CACHE_DIR/v23.0.0-pre-arm64-5fad6d45-501/e7f8ef7f.cache.tcqrsK
//
// 1. $NODE_COMPILE_CACHE_DIR either comes from the $NODE_COMPILE_CACHE
// environment
// variable or `module.enableCompileCache()`.
// 2. v23.0.0-pre-arm64-5fad6d45-501 is the sub cache directory and
// e7f8ef7f is the hash for the cache (see
// CompileCacheHandler::Enable()),
// 3. tcqrsK is generated by uv_fs_mkstemp() as a temporary identifier.
uv_fs_t mkstemp_req;
auto cleanup_mkstemp =
OnScopeLeave([&mkstemp_req]() { uv_fs_req_cleanup(&mkstemp_req); });
std::string cache_filename_tmp = entry->cache_filename + ".XXXXXX";
Debug("[compile cache] Creating temporary file for cache of %s (%s)...",
entry->source_filename,
type_name);
int err = uv_fs_mkstemp(
nullptr, &mkstemp_req, cache_filename_tmp.c_str(), nullptr);
if (err < 0) {
Debug("failed. %s\n", uv_strerror(err));
continue;
}
Debug(" -> %s\n", mkstemp_req.path);
Debug("[compile cache] writing cache for %s %s to temporary file %s [%d "
"%d %d "
"%d %d]...",
type_name,
entry->source_filename,
mkstemp_req.path,
headers[kMagicNumberOffset],
headers[kCodeSizeOffset],
headers[kCacheSizeOffset],
headers[kCodeHashOffset],
headers[kCacheHashOffset]);
// Write to the temporary file.
uv_buf_t headers_buf = uv_buf_init(reinterpret_cast<char*>(headers.data()),
headers.size() * sizeof(uint32_t));
uv_buf_t data_buf = uv_buf_init(cache_ptr, entry->cache->length);
uv_buf_t bufs[] = {headers_buf, data_buf};
uv_fs_t write_req;
auto cleanup_write =
OnScopeLeave([&write_req]() { uv_fs_req_cleanup(&write_req); });
err = uv_fs_write(
nullptr, &write_req, mkstemp_req.result, bufs, 2, 0, nullptr);
if (err < 0) {
Debug("failed: %s\n", uv_strerror(err));
continue;
}
uv_fs_t close_req;
auto cleanup_close =
OnScopeLeave([&close_req]() { uv_fs_req_cleanup(&close_req); });
err = uv_fs_close(nullptr, &close_req, mkstemp_req.result, nullptr);
if (err < 0) {
Debug("failed: %s\n", uv_strerror(err));
continue;
}
Debug("success\n");
// Rename the temporary file to the actual cache file.
uv_fs_t rename_req;
auto cleanup_rename =
OnScopeLeave([&rename_req]() { uv_fs_req_cleanup(&rename_req); });
std::string cache_filename_final = entry->cache_filename;
Debug("[compile cache] Renaming %s to %s...",
mkstemp_req.path,
cache_filename_final);
err = uv_fs_rename(nullptr,
&rename_req,
mkstemp_req.path,
cache_filename_final.c_str(),
nullptr);
if (err < 0) {
Debug("failed: %s\n", uv_strerror(err));
continue;
}
Debug("success\n");
entry->persisted = true;
}
// Clear the map at the end in one go instead of during the iteration to
// avoid rehashing costs.
Debug("[compile cache] Clear deserialized cache.\n");
compiler_cache_store_.clear();
}
CompileCacheHandler::CompileCacheHandler(Environment* env)
: isolate_(env->isolate()),
is_debug_(
env->enabled_debug_list()->enabled(DebugCategory::COMPILE_CACHE)) {}
// Directory structure:
// - Compile cache directory (from NODE_COMPILE_CACHE)
// - $NODE_VERSION-$ARCH-$CACHE_DATA_VERSION_TAG-$UID
// - $FILENAME_AND_MODULE_TYPE_HASH.cache: a hash of filename + module type
CompileCacheEnableResult CompileCacheHandler::Enable(Environment* env,
const std::string& dir) {
std::string cache_tag = GetCacheVersionTag();
std::string absolute_cache_dir_base = PathResolve(env, {dir});
std::string cache_dir_with_tag =
absolute_cache_dir_base + kPathSeparator + cache_tag;
CompileCacheEnableResult result;
Debug("[compile cache] resolved path %s + %s -> %s\n",
dir,
cache_tag,
cache_dir_with_tag);
if (!env->permission()->is_granted(
env,
permission::PermissionScope::kFileSystemWrite,
cache_dir_with_tag)) [[unlikely]] {
result.message = "Skipping compile cache because write permission for " +
cache_dir_with_tag + " is not granted";
result.status = CompileCacheEnableStatus::FAILED;
return result;
}
if (!env->permission()->is_granted(
env,
permission::PermissionScope::kFileSystemRead,
cache_dir_with_tag)) [[unlikely]] {
result.message = "Skipping compile cache because read permission for " +
cache_dir_with_tag + " is not granted";
result.status = CompileCacheEnableStatus::FAILED;
return result;
}
fs::FSReqWrapSync req_wrap;
int err = fs::MKDirpSync(
nullptr, &(req_wrap.req), cache_dir_with_tag, 0777, nullptr);
if (is_debug_) {
Debug("[compile cache] creating cache directory %s...%s\n",
cache_dir_with_tag,
err < 0 ? uv_strerror(err) : "success");
}
if (err != 0 && err != UV_EEXIST) {
result.message =
"Cannot create cache directory: " + std::string(uv_strerror(err));
result.status = CompileCacheEnableStatus::FAILED;
return result;
}
result.cache_directory = absolute_cache_dir_base;
compile_cache_dir_ = cache_dir_with_tag;
result.status = CompileCacheEnableStatus::ENABLED;
return result;
}
} // namespace node

113
src/compile_cache.h Normal file
View File

@ -0,0 +1,113 @@
#ifndef SRC_COMPILE_CACHE_H_
#define SRC_COMPILE_CACHE_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include <cinttypes>
#include <memory>
#include <string>
#include <string_view>
#include <unordered_map>
#include "v8.h"
namespace node {
class Environment;
#define CACHED_CODE_TYPES(V) \
V(kCommonJS, 0) \
V(kESM, 1) \
V(kStrippedTypeScript, 2) \
V(kTransformedTypeScript, 3) \
V(kTransformedTypeScriptWithSourceMaps, 4)
enum class CachedCodeType : uint8_t {
#define V(type, value) type = value,
CACHED_CODE_TYPES(V)
#undef V
};
struct CompileCacheEntry {
std::unique_ptr<v8::ScriptCompiler::CachedData> cache{nullptr};
uint32_t cache_key;
uint32_t code_hash;
uint32_t code_size;
std::string cache_filename;
std::string source_filename;
CachedCodeType type;
bool refreshed = false;
bool persisted = false;
// Copy the cache into a new store for V8 to consume. Caller takes
// ownership.
v8::ScriptCompiler::CachedData* CopyCache() const;
const char* type_name() const;
};
#define COMPILE_CACHE_STATUS(V) \
V(FAILED) /* Failed to enable the cache */ \
V(ENABLED) /* Was not enabled before, and now enabled. */ \
V(ALREADY_ENABLED) /* Was already enabled. */ \
V(DISABLED) /* Has been disabled by NODE_DISABLE_COMPILE_CACHE. */
enum class CompileCacheEnableStatus : uint8_t {
#define V(status) status,
COMPILE_CACHE_STATUS(V)
#undef V
};
struct CompileCacheEnableResult {
CompileCacheEnableStatus status;
std::string cache_directory;
std::string message; // Set in case of failure.
};
class CompileCacheHandler {
public:
explicit CompileCacheHandler(Environment* env);
CompileCacheEnableResult Enable(Environment* env, const std::string& dir);
void Persist();
CompileCacheEntry* GetOrInsert(v8::Local<v8::String> code,
v8::Local<v8::String> filename,
CachedCodeType type);
void MaybeSave(CompileCacheEntry* entry,
v8::Local<v8::Function> func,
bool rejected);
void MaybeSave(CompileCacheEntry* entry,
v8::Local<v8::Module> mod,
bool rejected);
void MaybeSave(CompileCacheEntry* entry, std::string_view transpiled);
std::string_view cache_dir() { return compile_cache_dir_; }
private:
void ReadCacheFile(CompileCacheEntry* entry);
template <typename T>
void MaybeSaveImpl(CompileCacheEntry* entry,
v8::Local<T> func_or_mod,
bool rejected);
template <typename... Args>
inline void Debug(const char* format, Args&&... args) const;
static constexpr size_t kMagicNumberOffset = 0;
static constexpr size_t kCodeSizeOffset = 1;
static constexpr size_t kCacheSizeOffset = 2;
static constexpr size_t kCodeHashOffset = 3;
static constexpr size_t kCacheHashOffset = 4;
static constexpr size_t kHeaderCount = 5;
v8::Isolate* isolate_ = nullptr;
bool is_debug_ = false;
std::string compile_cache_dir_;
std::unordered_map<uint32_t, std::unique_ptr<CompileCacheEntry>>
compiler_cache_store_;
};
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_COMPILE_CACHE_H_

16
src/connect_wrap.cc Normal file
View File

@ -0,0 +1,16 @@
#include "connect_wrap.h"
#include "req_wrap-inl.h"
namespace node {
using v8::Local;
using v8::Object;
class Environment;
ConnectWrap::ConnectWrap(Environment* env,
Local<Object> req_wrap_obj,
AsyncWrap::ProviderType provider) : ReqWrap(env, req_wrap_obj, provider) {
}
} // namespace node

26
src/connect_wrap.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef SRC_CONNECT_WRAP_H_
#define SRC_CONNECT_WRAP_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "req_wrap-inl.h"
#include "async_wrap.h"
namespace node {
class ConnectWrap : public ReqWrap<uv_connect_t> {
public:
ConnectWrap(Environment* env,
v8::Local<v8::Object> req_wrap_obj,
AsyncWrap::ProviderType provider);
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(ConnectWrap)
SET_SELF_SIZE(ConnectWrap)
};
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CONNECT_WRAP_H_

142
src/connection_wrap.cc Normal file
View File

@ -0,0 +1,142 @@
#include "connection_wrap.h"
#include "connect_wrap.h"
#include "env-inl.h"
#include "pipe_wrap.h"
#include "stream_base-inl.h"
#include "stream_wrap.h"
#include "tcp_wrap.h"
#include "util-inl.h"
namespace node {
using v8::Boolean;
using v8::Context;
using v8::HandleScope;
using v8::Integer;
using v8::Local;
using v8::Object;
using v8::Value;
template <typename WrapType, typename UVType>
ConnectionWrap<WrapType, UVType>::ConnectionWrap(Environment* env,
Local<Object> object,
ProviderType provider)
: LibuvStreamWrap(env,
object,
reinterpret_cast<uv_stream_t*>(&handle_),
provider) {}
template <typename WrapType, typename UVType>
void ConnectionWrap<WrapType, UVType>::OnConnection(uv_stream_t* handle,
int status) {
WrapType* wrap_data = static_cast<WrapType*>(handle->data);
CHECK_NOT_NULL(wrap_data);
CHECK_EQ(&wrap_data->handle_, reinterpret_cast<UVType*>(handle));
Environment* env = wrap_data->env();
HandleScope handle_scope(env->isolate());
Context::Scope context_scope(env->context());
// We should not be getting this callback if someone has already called
// uv_close() on the handle.
CHECK_EQ(wrap_data->persistent().IsEmpty(), false);
Local<Value> client_handle;
if (status == 0) {
// Instantiate the client javascript object and handle.
Local<Object> client_obj;
if (!WrapType::Instantiate(env, wrap_data, WrapType::SOCKET)
.ToLocal(&client_obj))
return;
// Unwrap the client javascript object.
WrapType* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap, client_obj);
uv_stream_t* client = reinterpret_cast<uv_stream_t*>(&wrap->handle_);
// uv_accept can fail if the new connection has already been closed, in
// which case an EAGAIN (resource temporarily unavailable) will be
// returned.
if (uv_accept(handle, client))
return;
// Successful accept. Call the onconnection callback in JavaScript land.
client_handle = client_obj;
} else {
client_handle = Undefined(env->isolate());
}
Local<Value> argv[] = { Integer::New(env->isolate(), status), client_handle };
wrap_data->MakeCallback(env->onconnection_string(), arraysize(argv), argv);
}
template <typename WrapType, typename UVType>
void ConnectionWrap<WrapType, UVType>::AfterConnect(uv_connect_t* req,
int status) {
BaseObjectPtr<ConnectWrap> req_wrap{static_cast<ConnectWrap*>(req->data)};
CHECK(req_wrap);
WrapType* wrap = static_cast<WrapType*>(req->handle->data);
CHECK_EQ(req_wrap->env(), wrap->env());
Environment* env = wrap->env();
HandleScope handle_scope(env->isolate());
Context::Scope context_scope(env->context());
// The wrap and request objects should still be there.
CHECK_EQ(req_wrap->persistent().IsEmpty(), false);
CHECK_EQ(wrap->persistent().IsEmpty(), false);
bool readable, writable;
if (status) {
readable = writable = false;
} else {
readable = uv_is_readable(req->handle) != 0;
writable = uv_is_writable(req->handle) != 0;
}
Local<Value> argv[5] = {
Integer::New(env->isolate(), status),
wrap->object(),
req_wrap->object(),
Boolean::New(env->isolate(), readable),
Boolean::New(env->isolate(), writable)
};
TRACE_EVENT_NESTABLE_ASYNC_END1(TRACING_CATEGORY_NODE2(net, native),
"connect",
req_wrap.get(),
"status",
status);
req_wrap->MakeCallback(env->oncomplete_string(), arraysize(argv), argv);
}
template ConnectionWrap<PipeWrap, uv_pipe_t>::ConnectionWrap(
Environment* env,
Local<Object> object,
ProviderType provider);
template ConnectionWrap<TCPWrap, uv_tcp_t>::ConnectionWrap(
Environment* env,
Local<Object> object,
ProviderType provider);
template void ConnectionWrap<PipeWrap, uv_pipe_t>::OnConnection(
uv_stream_t* handle, int status);
template void ConnectionWrap<TCPWrap, uv_tcp_t>::OnConnection(
uv_stream_t* handle, int status);
template void ConnectionWrap<PipeWrap, uv_pipe_t>::AfterConnect(
uv_connect_t* handle, int status);
template void ConnectionWrap<TCPWrap, uv_tcp_t>::AfterConnect(
uv_connect_t* handle, int status);
} // namespace node

30
src/connection_wrap.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef SRC_CONNECTION_WRAP_H_
#define SRC_CONNECTION_WRAP_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "stream_wrap.h"
namespace node {
class Environment;
template <typename WrapType, typename UVType>
class ConnectionWrap : public LibuvStreamWrap {
public:
static void OnConnection(uv_stream_t* handle, int status);
static void AfterConnect(uv_connect_t* req, int status);
protected:
ConnectionWrap(Environment* env,
v8::Local<v8::Object> object,
ProviderType provider);
UVType handle_;
};
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CONNECTION_WRAP_H_

65
src/cppgc_helpers-inl.h Normal file
View File

@ -0,0 +1,65 @@
#ifndef SRC_CPPGC_HELPERS_INL_H_
#define SRC_CPPGC_HELPERS_INL_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "cppgc_helpers.h"
#include "env-inl.h"
namespace node {
template <typename T>
void CppgcMixin::Wrap(T* ptr, Realm* realm, v8::Local<v8::Object> obj) {
CHECK_GE(obj->InternalFieldCount(), T::kInternalFieldCount);
ptr->realm_ = realm;
v8::Isolate* isolate = realm->isolate();
ptr->traced_reference_ = v8::TracedReference<v8::Object>(isolate, obj);
// Note that ptr must be of concrete type T in Wrap.
v8::Object::Wrap<v8::CppHeapPointerTag::kDefaultTag>(isolate, obj, ptr);
// Keep the layout consistent with BaseObjects.
obj->SetAlignedPointerInInternalField(
kEmbedderType, realm->isolate_data()->embedder_id_for_cppgc());
obj->SetAlignedPointerInInternalField(kSlot, ptr);
realm->TrackCppgcWrapper(ptr);
}
template <typename T>
void CppgcMixin::Wrap(T* ptr, Environment* env, v8::Local<v8::Object> obj) {
Wrap(ptr, env->principal_realm(), obj);
}
template <typename T>
T* CppgcMixin::Unwrap(v8::Local<v8::Object> obj) {
// We are not using v8::Object::Unwrap currently because that requires
// access to isolate which the ASSIGN_OR_RETURN_UNWRAP macro that we'll shim
// with ASSIGN_OR_RETURN_UNWRAP_GC doesn't take, and we also want a
// signature consistent with BaseObject::Unwrap() to avoid churn. Since
// cppgc-managed objects share the same layout as BaseObjects, just unwrap
// from the pointer in the internal field, which should be valid as long as
// the object is still alive.
if (obj->InternalFieldCount() != T::kInternalFieldCount) {
return nullptr;
}
T* ptr = static_cast<T*>(obj->GetAlignedPointerFromInternalField(T::kSlot));
return ptr;
}
v8::Local<v8::Object> CppgcMixin::object() const {
return traced_reference_.Get(realm_->isolate());
}
Environment* CppgcMixin::env() const {
return realm_->env();
}
CppgcMixin::~CppgcMixin() {
if (realm_ != nullptr) {
realm_->set_should_purge_empty_cppgc_wrappers(true);
}
}
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CPPGC_HELPERS_INL_H_

37
src/cppgc_helpers.cc Normal file
View File

@ -0,0 +1,37 @@
#include "cppgc_helpers.h"
#include "env-inl.h"
namespace node {
void CppgcWrapperList::Cleanup() {
for (auto node : *this) {
CppgcMixin* ptr = node->persistent.Get();
if (ptr != nullptr) {
ptr->Finalize();
}
}
}
void CppgcWrapperList::MemoryInfo(MemoryTracker* tracker) const {
for (auto node : *this) {
CppgcMixin* ptr = node->persistent.Get();
if (ptr != nullptr) {
tracker->Track(ptr);
}
}
}
void CppgcWrapperList::PurgeEmpty() {
for (auto weak_it = begin(); weak_it != end();) {
CppgcWrapperListNode* node = *weak_it;
auto next_it = ++weak_it;
// The underlying cppgc wrapper has already been garbage collected.
// Remove it from the list.
if (!node->persistent) {
node->persistent.Clear();
delete node;
}
weak_it = next_it;
}
}
} // namespace node

162
src/cppgc_helpers.h Normal file
View File

@ -0,0 +1,162 @@
#ifndef SRC_CPPGC_HELPERS_H_
#define SRC_CPPGC_HELPERS_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include <type_traits> // std::remove_reference
#include "cppgc/garbage-collected.h"
#include "cppgc/name-provider.h"
#include "cppgc/persistent.h"
#include "memory_tracker.h"
#include "util.h"
#include "v8-cppgc.h"
#include "v8-sandbox.h"
#include "v8.h"
namespace node {
class Environment;
class Realm;
class CppgcWrapperListNode;
/**
* This is a helper mixin with a BaseObject-like interface to help
* implementing wrapper objects managed by V8's cppgc (Oilpan) library.
* cppgc-manged objects in Node.js internals should extend this mixin,
* while non-cppgc-managed objects typically extend BaseObject - the
* latter are being migrated to be cppgc-managed wherever it's beneficial
* and practical. Typically cppgc-managed objects are more efficient to
* keep track of (which lowers initialization cost) and work better
* with V8's GC scheduling.
*
* A cppgc-managed native wrapper should look something like this, note
* that per cppgc rules, CPPGC_MIXIN(MyWrap) must be at the left-most
* position in the hierarchy (which ensures cppgc::GarbageCollected
* is at the left-most position).
*
* class MyWrap final : CPPGC_MIXIN(MyWrap) {
* public:
* SET_CPPGC_NAME(MyWrap) // Sets the heap snapshot name to "Node / MyWrap"
* void Trace(cppgc::Visitor* visitor) const final {
* CppgcMixin::Trace(visitor);
* visitor->Trace(...); // Trace any additional owned traceable data
* }
* }
*
* If the wrapper needs to perform cleanups when it's destroyed and that
* cleanup relies on a living Node.js `Realm`, it should implement a
* pattern like this:
*
* ~MyWrap() { this->Destroy(); }
* void Clean(Realm* env) override {
* // Do cleanup that relies on a living Environemnt.
* }
*/
class CppgcMixin : public cppgc::GarbageCollectedMixin, public MemoryRetainer {
public:
// To help various callbacks access wrapper objects with different memory
// management, cppgc-managed objects share the same layout as BaseObjects.
enum InternalFields { kEmbedderType = 0, kSlot, kInternalFieldCount };
// The initialization cannot be done in the mixin constructor but has to be
// invoked from the child class constructor, per cppgc::GarbageCollectedMixin
// rules.
template <typename T>
static inline void Wrap(T* ptr, Realm* realm, v8::Local<v8::Object> obj);
template <typename T>
static inline void Wrap(T* ptr, Environment* env, v8::Local<v8::Object> obj);
inline v8::Local<v8::Object> object() const;
inline Environment* env() const;
inline Realm* realm() const { return realm_; }
inline v8::Local<v8::Object> object(v8::Isolate* isolate) const {
return traced_reference_.Get(isolate);
}
template <typename T>
static inline T* Unwrap(v8::Local<v8::Object> obj);
// Subclasses are expected to invoke CppgcMixin::Trace() in their own Trace()
// methods.
void Trace(cppgc::Visitor* visitor) const override {
visitor->Trace(traced_reference_);
}
// TODO(joyeecheung): use ObjectSizeTrait;
inline size_t SelfSize() const override { return sizeof(*this); }
inline bool IsCppgcWrapper() const override { return true; }
// This is run for all the remaining Cppgc wrappers tracked in the Realm
// during Realm shutdown. The destruction of the wrappers would happen later,
// when the final garbage collection is triggered when CppHeap is torn down as
// part of the Isolate teardown. If subclasses of CppgcMixin wish to perform
// cleanups that depend on the Realm during destruction, they should implment
// it in a Clean() override, and then call this->Finalize() from their
// destructor. Outside of Finalize(), subclasses should avoid calling
// into JavaScript or perform any operation that can trigger garbage
// collection during the destruction.
void Finalize() {
if (realm_ == nullptr) return;
this->Clean(realm_);
realm_ = nullptr;
}
// The default implementation of Clean() is a no-op. If subclasses wish
// to perform cleanup that require a living Realm, they should
// should put the cleanups in a Clean() override, and call this->Finalize()
// in the destructor, instead of doing those cleanups directly in the
// destructor.
virtual void Clean(Realm* realm) {}
inline ~CppgcMixin();
friend class CppgcWrapperListNode;
private:
Realm* realm_ = nullptr;
v8::TracedReference<v8::Object> traced_reference_;
};
// If the class doesn't have additional owned traceable data, use this macro to
// save the implementation of a custom Trace() method.
#define DEFAULT_CPPGC_TRACE() \
void Trace(cppgc::Visitor* visitor) const final { \
CppgcMixin::Trace(visitor); \
}
// This macro sets the node name in the heap snapshot with a "Node /" prefix.
// Classes that use this macro must extend cppgc::NameProvider.
#define SET_CPPGC_NAME(Klass) \
inline const char* GetHumanReadableName() const final { \
return "Node / " #Klass; \
} \
inline const char* MemoryInfoName() const override { return #Klass; }
/**
* Similar to ASSIGN_OR_RETURN_UNWRAP() but works on cppgc-managed types
* inheriting CppgcMixin.
*/
#define ASSIGN_OR_RETURN_UNWRAP_CPPGC(ptr, obj, ...) \
do { \
*ptr = CppgcMixin::Unwrap< \
typename std::remove_reference<decltype(**ptr)>::type>(obj); \
if (*ptr == nullptr) return __VA_ARGS__; \
} while (0)
} // namespace node
/**
* Helper macro the manage the cppgc-based wrapper hierarchy. This must
* be used at the left-most position - right after `:` in the class inheritance,
* like this:
* class Klass : CPPGC_MIXIN(Klass) ... {}
*
* This needs to disable linters because it will be at odds with
* clang-format.
*/
#define CPPGC_MIXIN(Klass) \
public /* NOLINT(whitespace/indent) */ \
cppgc::GarbageCollected<Klass>, public cppgc::NameProvider, public CppgcMixin
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CPPGC_HELPERS_H_

338
src/crypto/README.md Normal file
View File

@ -0,0 +1,338 @@
# Node.js `src/crypto` documentation
Welcome. You've found your way to the Node.js native crypto subsystem.
Do not be afraid.
While crypto may be a dark, mysterious, and forboding subject; and while
this directory may be filled with many `*.h` and `*.cc` files, finding
your way around is not too difficult. And I can promise you that a Gru
will not jump out of the shadows and eat you (well, "promise" may be a
bit too strong, a Gru may jump out of the shadows and eat you if you
live in a place where such things are possible).
## Finding your way around
All of the code in this directory is structured into units organized by
function or crypto protocol.
The following provide generalized utility declarations that are used throughout
the various other crypto files and other parts of Node.js:
* `crypto_util.h` / `crypto_util.cc` (Core crypto definitions)
* `crypto_common.h` / `crypto_common.cc` (Shared TLS utility functions)
* `crypto_bio.h` / `crypto_bio.cc` (Custom OpenSSL i/o implementation)
Of these, `crypto_util.h` and `crypto_util.cc` are the most important, as
they provide the core declarations and utility functions used most extensively
throughout the rest of the code.
The rest of the files are structured by their function, as detailed in the
following table:
| File (\*.h/\*.cc) | Description |
| -------------------- | -------------------------------------------------------------------------- |
| `crypto_aes` | AES Cipher support. |
| `crypto_cipher` | General Encryption/Decryption utilities. |
| `crypto_clienthello` | TLS/SSL client hello parser implementation. Used during SSL/TLS handshake. |
| `crypto_context` | Implementation of the `SecureContext` object. |
| `crypto_dh` | Diffie-Hellman Key Agreement implementation. |
| `crypto_dsa` | DSA (Digital Signature) Key Generation functions. |
| `crypto_ec` | Elliptic-curve cryptography implementation. |
| `crypto_hash` | Basic hash (e.g. SHA-256) functions. |
| `crypto_hkdf` | HKDF (Key derivation) implementation. |
| `crypto_hmac` | HMAC implementations. |
| `crypto_keys` | Utilities for using and generating secret, private, and public keys. |
| `crypto_pbkdf2` | PBKDF2 key / bit generation implementation. |
| `crypto_rsa` | RSA Key Generation functions. |
| `crypto_scrypt` | Scrypt key / bit generation implementation. |
| `crypto_sig` | General digital signature and verification utilities. |
| `crypto_spkac` | Netscape SPKAC certificate utilities. |
| `crypto_ssl` | Implementation of the `SSLWrap` object. |
| `crypto_timing` | Implementation of the TimingSafeEqual. |
When new crypto protocols are added, they will be added into their own
`crypto_` `*.h` and `*.cc` files.
## Helpful concepts
Node.js currently uses OpenSSL to provide it's crypto substructure.
(Some custom Node.js distributions -- such as Electron -- use BoringSSL
instead.)
This section aims to explain some of the utilities that have been
provided to make working with the OpenSSL APIs a bit easier.
### Pointer types
Most of the key OpenSSL types need to be explicitly freed when they are
no longer needed. Failure to do so introduces memory leaks. To make this
easier (and less error prone), the `crypto_util.h` defines a number of
smart-pointer aliases that should be used:
```cpp
using X509Pointer = DeleteFnPtr<X509, X509_free>;
using BIOPointer = DeleteFnPtr<BIO, BIO_free_all>;
using SSLCtxPointer = DeleteFnPtr<SSL_CTX, SSL_CTX_free>;
using SSLSessionPointer = DeleteFnPtr<SSL_SESSION, SSL_SESSION_free>;
using SSLPointer = DeleteFnPtr<SSL, SSL_free>;
using PKCS8Pointer = DeleteFnPtr<PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO_free>;
using EVPKeyPointer = DeleteFnPtr<EVP_PKEY, EVP_PKEY_free>;
using EVPKeyCtxPointer = DeleteFnPtr<EVP_PKEY_CTX, EVP_PKEY_CTX_free>;
using EVPMDCtxPointer = DeleteFnPtr<EVP_MD_CTX, EVP_MD_CTX_free>;
using RSAPointer = DeleteFnPtr<RSA, RSA_free>;
using ECPointer = DeleteFnPtr<EC_KEY, EC_KEY_free>;
using BignumPointer = DeleteFnPtr<BIGNUM, BN_clear_free>;
using NetscapeSPKIPointer = DeleteFnPtr<NETSCAPE_SPKI, NETSCAPE_SPKI_free>;
using ECGroupPointer = DeleteFnPtr<EC_GROUP, EC_GROUP_free>;
using ECPointPointer = DeleteFnPtr<EC_POINT, EC_POINT_free>;
using ECKeyPointer = DeleteFnPtr<EC_KEY, EC_KEY_free>;
using DHPointer = DeleteFnPtr<DH, DH_free>;
using ECDSASigPointer = DeleteFnPtr<ECDSA_SIG, ECDSA_SIG_free>;
using HMACCtxPointer = DeleteFnPtr<HMAC_CTX, HMAC_CTX_free>;
using CipherCtxPointer = DeleteFnPtr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free>;
```
Examples of these being used are pervasive through the `src/crypto` code.
### `ByteSource`
The `ByteSource` class is a helper utility representing a _read-only_ byte
array. Instances can either wrap external ("foreign") data sources, such as
an `ArrayBuffer` (`v8::BackingStore`), or allocated data.
* If a pointer to external data is used to create a `ByteSource`, that pointer
must remain valid until the `ByteSource` is destroyed.
* If allocated data is used, then it must have been allocated using OpenSSL's
allocator. It will be freed automatically when the `ByteSource` is destroyed.
### `ArrayBufferOrViewContents`
The `ArrayBufferOrViewContents` class is a helper utility that abstracts
`ArrayBuffer`, `TypedArray`, or `DataView` inputs and provides access to
their underlying data pointers. It is used extensively through `src/crypto`
to make it easier to deal with inputs that allow any `ArrayBuffer`-backed
object.
The lifetime of `ArrayBufferOrViewContents` should not exceed the
lifetime of its input.
### Key objects
Most crypto operations involve the use of keys -- cryptographic inputs
that protect data. There are three general types of keys:
* Secret Keys (Symmetric)
* Public Keys (Asymmetric)
* Private Keys (Asymmetric)
Secret keys consist of a variable number of bytes. They are "symmetrical"
in that the same key used to encrypt data, or generate a signature, must
be used to decrypt or validate that signature. If two people are exchanging
messages encrypted using a secret key, both of them must have access to the
same secret key data.
Public and Private keys always come in pairs. When one is used to encrypt
data or generate a signature, the other is used to decrypt or validate the
signature. The Public key is intended to be shared and can be shared openly.
The Private key must be kept secret and known only to the owner of the key.
The `src/crypto` subsystem uses several objects to represent keys. These
objects are structured in a way to allow key data to be shared across
multiple threads (the Node.js main thread, Worker Threads, and the libuv
threadpool).
Refer to `crypto_keys.h` and `crypto_keys.cc` for all code relating to the
core key objects.
#### `KeyObjectData`
`KeyObjectData` is an internal thread-safe structure used to wrap either
a `EVPKeyPointer` (for Public or Private keys) or a `ByteSource` containing
a Secret key.
#### `KeyObjectHandle`
The `KeyObjectHandle` provides the interface between the native C++ code
handling keys and the public JavaScript `KeyObject` API.
#### `KeyObject`
A `KeyObject` is the public Node.js-specific API for keys. A single
`KeyObject` wraps exactly one `KeyObjectHandle`.
#### `CryptoKey`
A `CryptoKey` is the Web Crypto API's alternative to `KeyObject`. In the
Node.js implementation, `CryptoKey` is a thin wrapper around the
`KeyObject` and it is largely possible to use them interchangeably.
### `CryptoJob`
All operations that are not either Stream-based or single-use functions
are built around the `CryptoJob` class.
A `CryptoJob` encapsulates a single crypto operation that can be
invoked synchronously or asynchronously.
The `CryptoJob` class itself is a C++ template that takes a single
`CryptoJobTraits` struct as a parameter. The `CryptoJobTraits`
provides the implementation detail of the job.
There are (currently) four basic `CryptoJob` specializations:
* `CipherJob` (defined in `src/crypto_cipher.h`) -- Used for
encrypt and decrypt operations.
* `KeyGenJob` (defined in `src/crypto_keygen.h`) -- Used for
secret and key pair generation operations.
* `KeyExportJob` (defined in `src/crypto_keys.h`) -- Used for
key export operations.
* `DeriveBitsJob` (defined in `src/crypto_util.h`) -- Used for
key and byte derivation operations.
Every `CryptoJobTraits` provides two fundamental operations:
* Configuration -- Processes input arguments when a
`CryptoJob` instance is created.
* Implementation -- Provides the specific implementation of
the operation.
The Configuration is typically provided by an `AdditionalConfig()`
method, the signature of which is slightly different for each
of the above `CryptoJob` specializations. Despite the signature
differences, the purpose of the `AdditionalConfig()` function
remains the same: to process input arguments and set the properties
on the `CryptoJob`'s parameters object.
The parameters object is specific to each `CryptoJob` type, and
is stored with the `CryptoJob`. It holds all of the inputs that
are used by the Implementation. The inputs held by the parameters
must be threadsafe.
The `AdditionalConfig()` function is always called when the
`CryptoJob` instance is being created.
The Implementation function is unique to each of the `CryptoJob`
specializations and will either be called synchronously within
the current thread or from within the libuv threadpool.
Every `CryptoJob` instance exposes a `run()` function to the
JavaScript layer. When called, `run()` with either dispatch the
job to the libuv threadpool or invoke the Implementation
function synchronously. If invoked synchronously, run() will
return a JavaScript array. The first value in the array is
either an `Error` or `undefined`. If the operation was successful,
the second value in the array will contain the result of the
operation. Typically, the result is an `ArrayBuffer`, but
certain `CryptoJob` types can alter the output.
If the `CryptoJob` is processed asynchronously, then the job
must have an `ondone` property whose value is a function that
is invoked when the operation is complete. This function will
be called with two arguments. The first is either an `Error`
or `undefined`, and the second is the result of the operation
if successful.
For `CipherJob` types, the output is always an `ArrayBuffer`.
For `KeyExportJob` types, the output is either an `ArrayBuffer` or
a JavaScript object (for JWK output format);
For `KeyGenJob` types, the output is either a single KeyObject,
or an array containing a Public/Private key pair represented
either as a `KeyObjectHandle` object or a `Buffer`.
For `DeriveBitsJob` type output is typically an `ArrayBuffer` but
can be other values (`RandomBytesJob` for instance, fills an
input buffer and always returns `undefined`).
### Errors
#### `ThrowCryptoError` and the `THROW_ERR_CRYPTO_*` macros
The `ThrowCryptoError()` is a legacy utility that will throw a
JavaScript exception containing details collected from OpenSSL
about a failed operation. `ThrowCryptoError()` should only be
used when necessary to report low-level OpenSSL failures.
In `node_errors.h`, there are a number of `ERR_CRYPTO_*`
macro definitions that define semantically specific errors.
These can be called from within the C++ code as functions,
like `THROW_ERR_CRYPTO_INVALID_IV(env)`. These methods
should be used to throw JavaScript errors when necessary.
## Crypto API patterns
### Operation mode
All crypto functions in Node.js operate in one of three
modes:
* Synchronous single-call
* Asynchronous single-call
* Stream-oriented
It is often possible to perform various operations across
multiple modes. For instance, cipher and decipher operations
can be performed in any of the three modes.
Synchronous single-call operations are always blocking.
They perform their actions immediately.
```js
// Example synchronous single-call operation
const a = new Uint8Array(10);
const b = new Uint8Array(10);
crypto.timingSafeEqual(a, b);
```
Asynchronous single-call operations generally perform a
number of synchronous input validation steps, but then
defer the actual crypto-operation work to the libuv threadpool.
```js
// Example asynchronous single-call operation
const buf = new Uint8Array(10);
crypto.randomFill(buf, (err, buf) => {
console.log(buf);
});
```
For the legacy Node.js crypto API, asynchronous single-call
operations use the traditional Node.js callback pattern, as
illustrated in the previous `randomFill()` example. In the
Web Crypto API (accessible via `globalThis.crypto`),
all asynchronous single-call operations are Promise-based.
```js
// Example Web Crypto API asynchronous single-call operation
const { subtle } = globalThis.crypto;
subtle.generateKeys({ name: 'HMAC', length: 256 }, true, ['sign'])
.then((key) => {
console.log(key);
})
.catch((error) => {
console.error('an error occurred');
});
```
In nearly every case, asynchronous single-call operations make use
of the libuv threadpool to perform crypto operations off the main
event loop thread.
Stream-oriented operations use an object to maintain state
over multiple individual synchronous steps. The steps themselves
can be performed over time.
```js
// Example stream-oriented operation
const hash = crypto.createHash('sha256');
let updates = 10;
setTimeout(() => {
hash.update('hello world');
setTimeout(() => {
console.log(hash.digest();)
}, 1000);
}, 1000);
```

548
src/crypto/crypto_aes.cc Normal file
View File

@ -0,0 +1,548 @@
#include "crypto/crypto_aes.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "crypto/crypto_cipher.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
#include <openssl/bn.h>
#include <openssl/aes.h>
#include <vector>
namespace node {
using ncrypto::BignumPointer;
using ncrypto::Cipher;
using ncrypto::CipherCtxPointer;
using ncrypto::DataPointer;
using v8::FunctionCallbackInfo;
using v8::Just;
using v8::JustVoid;
using v8::Local;
using v8::Maybe;
using v8::Nothing;
using v8::Object;
using v8::Uint32;
using v8::Value;
namespace crypto {
namespace {
constexpr size_t kAesBlockSize = 16;
constexpr const char* kDefaultWrapIV = "\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6";
// Implements general AES encryption and decryption for CBC
// The key_data must be a secret key.
// On success, this function sets out to a new ByteSource
// instance containing the results and returns WebCryptoCipherStatus::OK.
WebCryptoCipherStatus AES_Cipher(Environment* env,
const KeyObjectData& key_data,
WebCryptoCipherMode cipher_mode,
const AESCipherConfig& params,
const ByteSource& in,
ByteSource* out) {
CHECK_EQ(key_data.GetKeyType(), kKeyTypeSecret);
auto ctx = CipherCtxPointer::New();
CHECK(ctx);
if (params.cipher.isWrapMode()) {
ctx.setAllowWrap();
}
const bool encrypt = cipher_mode == kWebCryptoCipherEncrypt;
if (!ctx.init(params.cipher, encrypt)) {
// Cipher init failed
return WebCryptoCipherStatus::FAILED;
}
if (params.cipher.isGcmMode() && !ctx.setIvLength(params.iv.size())) {
return WebCryptoCipherStatus::FAILED;
}
if (!ctx.setKeyLength(key_data.GetSymmetricKeySize()) ||
!ctx.init(
Cipher(),
encrypt,
reinterpret_cast<const unsigned char*>(key_data.GetSymmetricKey()),
params.iv.data<unsigned char>())) {
return WebCryptoCipherStatus::FAILED;
}
size_t tag_len = 0;
if (params.cipher.isGcmMode()) {
switch (cipher_mode) {
case kWebCryptoCipherDecrypt: {
// If in decrypt mode, the auth tag must be set in the params.tag.
CHECK(params.tag);
ncrypto::Buffer<const char> buffer = {
.data = params.tag.data<char>(),
.len = params.tag.size(),
};
if (!ctx.setAeadTag(buffer)) {
return WebCryptoCipherStatus::FAILED;
}
break;
}
case kWebCryptoCipherEncrypt: {
// In decrypt mode, we grab the tag length here. We'll use it to
// ensure that that allocated buffer has enough room for both the
// final block and the auth tag. Unlike our other AES-GCM implementation
// in CipherBase, in WebCrypto, the auth tag is concatenated to the end
// of the generated ciphertext and returned in the same ArrayBuffer.
tag_len = params.length;
break;
}
default:
UNREACHABLE();
}
}
size_t total = 0;
int buf_len = in.size() + ctx.getBlockSize() + tag_len;
int out_len;
ncrypto::Buffer<const unsigned char> buffer = {
.data = params.additional_data.data<unsigned char>(),
.len = params.additional_data.size(),
};
if (params.cipher.isGcmMode() && params.additional_data.size() &&
!ctx.update(buffer, nullptr, &out_len)) {
return WebCryptoCipherStatus::FAILED;
}
auto buf = DataPointer::Alloc(buf_len);
auto ptr = static_cast<unsigned char*>(buf.get());
// In some outdated version of OpenSSL (e.g.
// ubi81_sharedlibs_openssl111fips_x64) may be used in sharedlib mode, the
// logic will be failed when input size is zero. The newer OpenSSL has fixed
// it up. But we still have to regard zero as special in Node.js code to
// prevent old OpenSSL failure.
//
// Refs:
// https://github.com/openssl/openssl/commit/420cb707b880e4fb649094241371701013eeb15f
// Refs: https://github.com/nodejs/node/pull/38913#issuecomment-866505244
buffer = {
.data = in.data<unsigned char>(),
.len = in.size(),
};
if (in.empty()) {
out_len = 0;
} else if (!ctx.update(buffer, ptr, &out_len)) {
return WebCryptoCipherStatus::FAILED;
}
total += out_len;
CHECK_LE(out_len, buf_len);
out_len = ctx.getBlockSize();
if (!ctx.update({}, ptr + total, &out_len, true)) {
return WebCryptoCipherStatus::FAILED;
}
total += out_len;
// If using AES_GCM, grab the generated auth tag and append
// it to the end of the ciphertext.
if (encrypt && params.cipher.isGcmMode()) {
if (!ctx.getAeadTag(tag_len, ptr + total)) {
return WebCryptoCipherStatus::FAILED;
}
total += tag_len;
}
if (total == 0) {
*out = ByteSource::Allocated(nullptr, 0);
return WebCryptoCipherStatus::OK;
}
// It's possible that we haven't used the full allocated space. Size down.
buf = buf.resize(total);
*out = ByteSource::Allocated(buf.release());
return WebCryptoCipherStatus::OK;
}
// The AES_CTR implementation here takes it's inspiration from the chromium
// implementation here:
// https://github.com/chromium/chromium/blob/7af6cfd/components/webcrypto/algorithms/aes_ctr.cc
template <typename T>
T CeilDiv(T a, T b) {
return a == 0 ? 0 : 1 + (a - 1) / b;
}
BignumPointer GetCounter(const AESCipherConfig& params) {
unsigned int remainder = (params.length % CHAR_BIT);
const unsigned char* data = params.iv.data<unsigned char>();
if (remainder == 0) {
unsigned int byte_length = params.length / CHAR_BIT;
return BignumPointer(data + params.iv.size() - byte_length, byte_length);
}
unsigned int byte_length =
CeilDiv(params.length, static_cast<size_t>(CHAR_BIT));
std::vector<unsigned char> counter(
data + params.iv.size() - byte_length,
data + params.iv.size());
counter[0] &= ~(0xFF << remainder);
return BignumPointer(counter.data(), counter.size());
}
std::vector<unsigned char> BlockWithZeroedCounter(
const AESCipherConfig& params) {
unsigned int length_bytes = params.length / CHAR_BIT;
unsigned int remainder = params.length % CHAR_BIT;
const unsigned char* data = params.iv.data<unsigned char>();
std::vector<unsigned char> new_counter_block(data, data + params.iv.size());
size_t index = new_counter_block.size() - length_bytes;
memset(&new_counter_block.front() + index, 0, length_bytes);
if (remainder)
new_counter_block[index - 1] &= 0xFF << remainder;
return new_counter_block;
}
WebCryptoCipherStatus AES_CTR_Cipher2(const KeyObjectData& key_data,
WebCryptoCipherMode cipher_mode,
const AESCipherConfig& params,
const ByteSource& in,
unsigned const char* counter,
unsigned char* out) {
auto ctx = CipherCtxPointer::New();
if (!ctx) {
return WebCryptoCipherStatus::FAILED;
}
if (!ctx.init(
params.cipher,
cipher_mode == kWebCryptoCipherEncrypt,
reinterpret_cast<const unsigned char*>(key_data.GetSymmetricKey()),
counter)) {
// Cipher init failed
return WebCryptoCipherStatus::FAILED;
}
int out_len = 0;
int final_len = 0;
ncrypto::Buffer<const unsigned char> buffer = {
.data = in.data<unsigned char>(),
.len = in.size(),
};
if (!ctx.update(buffer, out, &out_len) ||
!ctx.update({}, out + out_len, &final_len, true)) {
return WebCryptoCipherStatus::FAILED;
}
return static_cast<unsigned>(out_len + final_len) != in.size()
? WebCryptoCipherStatus::FAILED
: WebCryptoCipherStatus::OK;
}
WebCryptoCipherStatus AES_CTR_Cipher(Environment* env,
const KeyObjectData& key_data,
WebCryptoCipherMode cipher_mode,
const AESCipherConfig& params,
const ByteSource& in,
ByteSource* out) {
auto num_counters = BignumPointer::NewLShift(params.length);
if (!num_counters) return WebCryptoCipherStatus::FAILED;
BignumPointer current_counter = GetCounter(params);
auto num_output = BignumPointer::New();
if (!num_output.setWord(CeilDiv(in.size(), kAesBlockSize))) {
return WebCryptoCipherStatus::FAILED;
}
// Just like in chromium's implementation, if the counter will
// be incremented more than there are counter values, we fail.
if (num_output > num_counters) return WebCryptoCipherStatus::FAILED;
auto remaining_until_reset =
BignumPointer::NewSub(num_counters, current_counter);
if (!remaining_until_reset) {
return WebCryptoCipherStatus::FAILED;
}
// Output size is identical to the input size.
auto buf = DataPointer::Alloc(in.size());
// Also just like in chromium's implementation, if we can process
// the input without wrapping the counter, we'll do it as a single
// call here. If we can't, we'll fallback to the a two-step approach
if (remaining_until_reset >= num_output) {
auto status = AES_CTR_Cipher2(key_data,
cipher_mode,
params,
in,
params.iv.data<unsigned char>(),
static_cast<unsigned char*>(buf.get()));
if (status == WebCryptoCipherStatus::OK) {
*out = ByteSource::Allocated(buf.release());
}
return status;
}
BN_ULONG input_size_part1 = remaining_until_reset.getWord() * kAesBlockSize;
// Encrypt the first part...
auto status =
AES_CTR_Cipher2(key_data,
cipher_mode,
params,
ByteSource::Foreign(in.data<char>(), input_size_part1),
params.iv.data<unsigned char>(),
static_cast<unsigned char*>(buf.get()));
if (status != WebCryptoCipherStatus::OK) {
return status;
}
// Wrap the counter around to zero
std::vector<unsigned char> new_counter_block = BlockWithZeroedCounter(params);
auto ptr = static_cast<unsigned char*>(buf.get()) + input_size_part1;
// Encrypt the second part...
status =
AES_CTR_Cipher2(key_data,
cipher_mode,
params,
ByteSource::Foreign(in.data<char>() + input_size_part1,
in.size() - input_size_part1),
new_counter_block.data(),
ptr);
if (status == WebCryptoCipherStatus::OK) {
*out = ByteSource::Allocated(buf.release());
}
return status;
}
bool ValidateIV(
Environment* env,
CryptoJobMode mode,
Local<Value> value,
AESCipherConfig* params) {
ArrayBufferOrViewContents<char> iv(value);
if (!iv.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "iv is too big");
return false;
}
params->iv = (mode == kCryptoJobAsync)
? iv.ToCopy()
: iv.ToByteSource();
return true;
}
bool ValidateCounter(
Environment* env,
Local<Value> value,
AESCipherConfig* params) {
CHECK(value->IsUint32()); // Length
params->length = value.As<Uint32>()->Value();
if (params->iv.size() != 16 ||
params->length == 0 ||
params->length > 128) {
THROW_ERR_CRYPTO_INVALID_COUNTER(env);
return false;
}
return true;
}
bool ValidateAuthTag(
Environment* env,
CryptoJobMode mode,
WebCryptoCipherMode cipher_mode,
Local<Value> value,
AESCipherConfig* params) {
switch (cipher_mode) {
case kWebCryptoCipherDecrypt: {
if (!IsAnyBufferSource(value)) {
THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env);
return false;
}
ArrayBufferOrViewContents<char> tag_contents(value);
if (!tag_contents.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "tagLength is too big");
return false;
}
params->tag = mode == kCryptoJobAsync
? tag_contents.ToCopy()
: tag_contents.ToByteSource();
break;
}
case kWebCryptoCipherEncrypt: {
if (!value->IsUint32()) {
THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env);
return false;
}
params->length = value.As<Uint32>()->Value();
if (params->length > 128) {
THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env);
return false;
}
break;
}
default:
UNREACHABLE();
}
return true;
}
bool ValidateAdditionalData(
Environment* env,
CryptoJobMode mode,
Local<Value> value,
AESCipherConfig* params) {
// Additional Data
if (IsAnyBufferSource(value)) {
ArrayBufferOrViewContents<char> additional(value);
if (!additional.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "additionalData is too big");
return false;
}
params->additional_data = mode == kCryptoJobAsync
? additional.ToCopy()
: additional.ToByteSource();
}
return true;
}
void UseDefaultIV(AESCipherConfig* params) {
params->iv = ByteSource::Foreign(kDefaultWrapIV, strlen(kDefaultWrapIV));
}
} // namespace
AESCipherConfig::AESCipherConfig(AESCipherConfig&& other) noexcept
: mode(other.mode),
variant(other.variant),
cipher(other.cipher),
length(other.length),
iv(std::move(other.iv)),
additional_data(std::move(other.additional_data)),
tag(std::move(other.tag)) {}
AESCipherConfig& AESCipherConfig::operator=(AESCipherConfig&& other) noexcept {
if (&other == this) return *this;
this->~AESCipherConfig();
return *new (this) AESCipherConfig(std::move(other));
}
void AESCipherConfig::MemoryInfo(MemoryTracker* tracker) const {
// If mode is sync, then the data in each of these properties
// is not owned by the AESCipherConfig, so we ignore it.
if (mode == kCryptoJobAsync) {
tracker->TrackFieldWithSize("iv", iv.size());
tracker->TrackFieldWithSize("additional_data", additional_data.size());
tracker->TrackFieldWithSize("tag", tag.size());
}
}
Maybe<void> AESCipherTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
WebCryptoCipherMode cipher_mode,
AESCipherConfig* params) {
Environment* env = Environment::GetCurrent(args);
params->mode = mode;
CHECK(args[offset]->IsUint32()); // Key Variant
params->variant =
static_cast<AESKeyVariant>(args[offset].As<Uint32>()->Value());
#define V(name, _, nid) \
case AESKeyVariant::name: { \
params->cipher = nid; \
break; \
}
switch (params->variant) {
VARIANTS(V)
default:
UNREACHABLE();
}
#undef V
if (!params->cipher) {
THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env);
return Nothing<void>();
}
if (!params->cipher.isWrapMode()) {
if (!ValidateIV(env, mode, args[offset + 1], params)) {
return Nothing<void>();
}
if (params->cipher.isCtrMode()) {
if (!ValidateCounter(env, args[offset + 2], params)) {
return Nothing<void>();
}
} else if (params->cipher.isGcmMode()) {
if (!ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) ||
!ValidateAdditionalData(env, mode, args[offset + 3], params)) {
return Nothing<void>();
}
}
} else {
UseDefaultIV(params);
}
if (params->iv.size() < static_cast<size_t>(params->cipher.getIvLength())) {
THROW_ERR_CRYPTO_INVALID_IV(env);
return Nothing<void>();
}
return JustVoid();
}
WebCryptoCipherStatus AESCipherTraits::DoCipher(Environment* env,
const KeyObjectData& key_data,
WebCryptoCipherMode cipher_mode,
const AESCipherConfig& params,
const ByteSource& in,
ByteSource* out) {
#define V(name, fn, _) \
case AESKeyVariant::name: \
return fn(env, key_data, cipher_mode, params, in, out);
switch (params.variant) {
VARIANTS(V)
default:
UNREACHABLE();
}
#undef V
}
void AES::Initialize(Environment* env, Local<Object> target) {
AESCryptoJob::Initialize(env, target);
#define V(name, _, __) \
constexpr static auto kKeyVariantAES_##name = \
static_cast<int>(AESKeyVariant::name); \
NODE_DEFINE_CONSTANT(target, kKeyVariantAES_##name);
VARIANTS(V)
#undef V
}
void AES::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
AESCryptoJob::RegisterExternalReferences(registry);
}
} // namespace crypto
} // namespace node

84
src/crypto/crypto_aes.h Normal file
View File

@ -0,0 +1,84 @@
#ifndef SRC_CRYPTO_CRYPTO_AES_H_
#define SRC_CRYPTO_CRYPTO_AES_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_cipher.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "env.h"
#include "v8.h"
namespace node::crypto {
constexpr unsigned kNoAuthTagLength = static_cast<unsigned>(-1);
#define VARIANTS(V) \
V(CTR_128, AES_CTR_Cipher, ncrypto::Cipher::AES_128_CTR) \
V(CTR_192, AES_CTR_Cipher, ncrypto::Cipher::AES_192_CTR) \
V(CTR_256, AES_CTR_Cipher, ncrypto::Cipher::AES_256_CTR) \
V(CBC_128, AES_Cipher, ncrypto::Cipher::AES_128_CBC) \
V(CBC_192, AES_Cipher, ncrypto::Cipher::AES_192_CBC) \
V(CBC_256, AES_Cipher, ncrypto::Cipher::AES_256_CBC) \
V(GCM_128, AES_Cipher, ncrypto::Cipher::AES_128_GCM) \
V(GCM_192, AES_Cipher, ncrypto::Cipher::AES_192_GCM) \
V(GCM_256, AES_Cipher, ncrypto::Cipher::AES_256_GCM) \
V(KW_128, AES_Cipher, ncrypto::Cipher::AES_128_KW) \
V(KW_192, AES_Cipher, ncrypto::Cipher::AES_192_KW) \
V(KW_256, AES_Cipher, ncrypto::Cipher::AES_256_KW)
enum class AESKeyVariant {
#define V(name, _, __) name,
VARIANTS(V)
#undef V
};
struct AESCipherConfig final : public MemoryRetainer {
CryptoJobMode mode;
AESKeyVariant variant;
ncrypto::Cipher cipher;
size_t length;
ByteSource iv; // Used for both iv or counter
ByteSource additional_data;
ByteSource tag; // Used only for authenticated modes (GCM)
AESCipherConfig() = default;
AESCipherConfig(AESCipherConfig&& other) noexcept;
AESCipherConfig& operator=(AESCipherConfig&& other) noexcept;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(AESCipherConfig)
SET_SELF_SIZE(AESCipherConfig)
};
struct AESCipherTraits final {
static constexpr const char* JobName = "AESCipherJob";
using AdditionalParameters = AESCipherConfig;
static v8::Maybe<void> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
WebCryptoCipherMode cipher_mode,
AESCipherConfig* config);
static WebCryptoCipherStatus DoCipher(Environment* env,
const KeyObjectData& key_data,
WebCryptoCipherMode cipher_mode,
const AESCipherConfig& params,
const ByteSource& in,
ByteSource* out);
};
using AESCryptoJob = CipherJob<AESCipherTraits>;
namespace AES {
void Initialize(Environment* env, v8::Local<v8::Object> target);
void RegisterExternalReferences(ExternalReferenceRegistry* registry);
} // namespace AES
} // namespace node::crypto
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_AES_H_

503
src/crypto/crypto_bio.cc Normal file
View File

@ -0,0 +1,503 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "crypto/crypto_bio.h"
#include "base_object-inl.h"
#include "memory_tracker-inl.h"
#include "util-inl.h"
#include <openssl/bio.h>
#include <climits>
#include <cstring>
namespace node {
using ncrypto::BIOPointer;
namespace crypto {
BIOPointer NodeBIO::New(Environment* env) {
auto bio = BIOPointer::New(GetMethod());
if (bio && env != nullptr)
NodeBIO::FromBIO(bio.get())->env_ = env;
return bio;
}
BIOPointer NodeBIO::NewFixed(const char* data, size_t len, Environment* env) {
BIOPointer bio = New(env);
if (!bio || len > INT_MAX ||
BIOPointer::Write(&bio, std::string_view(data, len)) !=
static_cast<int>(len) ||
BIO_set_mem_eof_return(bio.get(), 0) != 1) {
return BIOPointer();
}
return bio;
}
int NodeBIO::New(BIO* bio) {
BIO_set_data(bio, new NodeBIO());
BIO_set_init(bio, 1);
return 1;
}
int NodeBIO::Free(BIO* bio) {
if (bio == nullptr)
return 0;
if (BIO_get_shutdown(bio)) {
if (BIO_get_init(bio) && BIO_get_data(bio) != nullptr) {
delete FromBIO(bio);
BIO_set_data(bio, nullptr);
}
}
return 1;
}
int NodeBIO::Read(BIO* bio, char* out, int len) {
BIO_clear_retry_flags(bio);
NodeBIO* nbio = FromBIO(bio);
int bytes = nbio->Read(out, len);
if (bytes == 0) {
bytes = nbio->eof_return();
if (bytes != 0) {
BIO_set_retry_read(bio);
}
}
return bytes;
}
char* NodeBIO::Peek(size_t* size) {
*size = read_head_->write_pos_ - read_head_->read_pos_;
return read_head_->data_ + read_head_->read_pos_;
}
size_t NodeBIO::PeekMultiple(char** out, size_t* size, size_t* count) {
Buffer* pos = read_head_;
size_t max = *count;
size_t total = 0;
size_t i;
for (i = 0; i < max; i++) {
size[i] = pos->write_pos_ - pos->read_pos_;
total += size[i];
out[i] = pos->data_ + pos->read_pos_;
/* Don't get past write head */
if (pos == write_head_)
break;
else
pos = pos->next_;
}
if (i == max)
*count = i;
else
*count = i + 1;
return total;
}
int NodeBIO::Write(BIO* bio, const char* data, int len) {
BIO_clear_retry_flags(bio);
FromBIO(bio)->Write(data, len);
return len;
}
int NodeBIO::Puts(BIO* bio, const char* str) {
return Write(bio, str, strlen(str));
}
int NodeBIO::Gets(BIO* bio, char* out, int size) {
NodeBIO* nbio = FromBIO(bio);
if (nbio->Length() == 0)
return 0;
int i = nbio->IndexOf('\n', size);
// Include '\n', if it's there. If not, don't read off the end.
if (i < size && i >= 0 && static_cast<size_t>(i) < nbio->Length())
i++;
// Shift `i` a bit to nullptr-terminate string later
if (size == i)
i--;
// Flush read data
nbio->Read(out, i);
out[i] = 0;
return i;
}
long NodeBIO::Ctrl(BIO* bio, int cmd, long num, // NOLINT(runtime/int)
void* ptr) {
NodeBIO* nbio;
long ret; // NOLINT(runtime/int)
nbio = FromBIO(bio);
ret = 1;
switch (cmd) {
case BIO_CTRL_RESET:
nbio->Reset();
break;
case BIO_CTRL_EOF:
ret = nbio->Length() == 0;
break;
case BIO_C_SET_BUF_MEM_EOF_RETURN:
nbio->set_eof_return(num);
break;
case BIO_CTRL_INFO:
ret = nbio->Length();
if (ptr != nullptr)
*reinterpret_cast<void**>(ptr) = nullptr;
break;
case BIO_C_SET_BUF_MEM:
UNREACHABLE("Can't use SET_BUF_MEM_PTR with NodeBIO");
case BIO_C_GET_BUF_MEM_PTR:
UNREACHABLE("Can't use GET_BUF_MEM_PTR with NodeBIO");
case BIO_CTRL_GET_CLOSE:
ret = BIO_get_shutdown(bio);
break;
case BIO_CTRL_SET_CLOSE:
BIO_set_shutdown(bio, num);
break;
case BIO_CTRL_WPENDING:
ret = 0;
break;
case BIO_CTRL_PENDING:
ret = nbio->Length();
break;
case BIO_CTRL_DUP:
case BIO_CTRL_FLUSH:
ret = 1;
break;
case BIO_CTRL_PUSH:
case BIO_CTRL_POP:
default:
ret = 0;
break;
}
return ret;
}
const BIO_METHOD* NodeBIO::GetMethod() {
// Static initialization ensures that this is safe to use concurrently.
static const BIO_METHOD* method = [&]() {
BIO_METHOD* method = BIO_meth_new(BIO_TYPE_MEM, "node.js SSL buffer");
BIO_meth_set_write(method, Write);
BIO_meth_set_read(method, Read);
BIO_meth_set_puts(method, Puts);
BIO_meth_set_gets(method, Gets);
BIO_meth_set_ctrl(method, Ctrl);
BIO_meth_set_create(method, New);
BIO_meth_set_destroy(method, Free);
return method;
}();
return method;
}
void NodeBIO::TryMoveReadHead() {
// `read_pos_` and `write_pos_` means the position of the reader and writer
// inside the buffer, respectively. When they're equal - its safe to reset
// them, because both reader and writer will continue doing their stuff
// from new (zero) positions.
while (read_head_->read_pos_ != 0 &&
read_head_->read_pos_ == read_head_->write_pos_) {
// Reset positions
read_head_->read_pos_ = 0;
read_head_->write_pos_ = 0;
// Move read_head_ forward, just in case if there're still some data to
// read in the next buffer.
if (read_head_ != write_head_)
read_head_ = read_head_->next_;
}
}
size_t NodeBIO::Read(char* out, size_t size) {
size_t bytes_read = 0;
size_t expected = Length() > size ? size : Length();
size_t offset = 0;
size_t left = size;
while (bytes_read < expected) {
CHECK_LE(read_head_->read_pos_, read_head_->write_pos_);
size_t avail = read_head_->write_pos_ - read_head_->read_pos_;
if (avail > left)
avail = left;
// Copy data
if (out != nullptr)
memcpy(out + offset, read_head_->data_ + read_head_->read_pos_, avail);
read_head_->read_pos_ += avail;
// Move pointers
bytes_read += avail;
offset += avail;
left -= avail;
TryMoveReadHead();
}
CHECK_EQ(expected, bytes_read);
length_ -= bytes_read;
// Free all empty buffers, but write_head's child
FreeEmpty();
return bytes_read;
}
void NodeBIO::FreeEmpty() {
if (write_head_ == nullptr)
return;
Buffer* child = write_head_->next_;
if (child == write_head_ || child == read_head_)
return;
Buffer* cur = child->next_;
if (cur == write_head_ || cur == read_head_)
return;
Buffer* prev = child;
while (cur != read_head_) {
CHECK_NE(cur, write_head_);
CHECK_EQ(cur->write_pos_, cur->read_pos_);
Buffer* next = cur->next_;
delete cur;
cur = next;
}
prev->next_ = cur;
}
size_t NodeBIO::IndexOf(char delim, size_t limit) {
size_t bytes_read = 0;
size_t max = Length() > limit ? limit : Length();
size_t left = limit;
Buffer* current = read_head_;
while (bytes_read < max) {
CHECK_LE(current->read_pos_, current->write_pos_);
size_t avail = current->write_pos_ - current->read_pos_;
if (avail > left)
avail = left;
// Walk through data
char* tmp = current->data_ + current->read_pos_;
size_t off = 0;
while (off < avail && *tmp != delim) {
off++;
tmp++;
}
// Move pointers
bytes_read += off;
left -= off;
// Found `delim`
if (off != avail) {
return bytes_read;
}
// Move to next buffer
if (current->read_pos_ + avail == current->len_) {
current = current->next_;
}
}
CHECK_EQ(max, bytes_read);
return max;
}
void NodeBIO::Write(const char* data, size_t size) {
size_t offset = 0;
size_t left = size;
// Allocate initial buffer if the ring is empty
TryAllocateForWrite(left);
while (left > 0) {
size_t to_write = left;
CHECK_LE(write_head_->write_pos_, write_head_->len_);
size_t avail = write_head_->len_ - write_head_->write_pos_;
if (to_write > avail)
to_write = avail;
// Copy data
memcpy(write_head_->data_ + write_head_->write_pos_,
data + offset,
to_write);
// Move pointers
left -= to_write;
offset += to_write;
length_ += to_write;
write_head_->write_pos_ += to_write;
CHECK_LE(write_head_->write_pos_, write_head_->len_);
// Go to next buffer if there still are some bytes to write
if (left != 0) {
CHECK_EQ(write_head_->write_pos_, write_head_->len_);
TryAllocateForWrite(left);
write_head_ = write_head_->next_;
// Additionally, since we're moved to the next buffer, read head
// may be moved as well.
TryMoveReadHead();
}
}
CHECK_EQ(left, 0);
}
char* NodeBIO::PeekWritable(size_t* size) {
TryAllocateForWrite(*size);
size_t available = write_head_->len_ - write_head_->write_pos_;
if (*size == 0 || available <= *size)
*size = available;
return write_head_->data_ + write_head_->write_pos_;
}
void NodeBIO::Commit(size_t size) {
write_head_->write_pos_ += size;
length_ += size;
CHECK_LE(write_head_->write_pos_, write_head_->len_);
// Allocate new buffer if write head is full,
// and there're no other place to go
TryAllocateForWrite(0);
if (write_head_->write_pos_ == write_head_->len_) {
write_head_ = write_head_->next_;
// Additionally, since we're moved to the next buffer, read head
// may be moved as well.
TryMoveReadHead();
}
}
void NodeBIO::TryAllocateForWrite(size_t hint) {
Buffer* w = write_head_;
Buffer* r = read_head_;
// If write head is full, next buffer is either read head or not empty.
if (w == nullptr ||
(w->write_pos_ == w->len_ &&
(w->next_ == r || w->next_->write_pos_ != 0))) {
size_t len = w == nullptr ? initial_ :
kThroughputBufferLength;
if (len < hint)
len = hint;
// If there is a one time allocation size hint, use it.
if (allocate_hint_ > len) {
len = allocate_hint_;
allocate_hint_ = 0;
}
Buffer* next = new Buffer(env_, len);
if (w == nullptr) {
next->next_ = next;
write_head_ = next;
read_head_ = next;
} else {
next->next_ = w->next_;
w->next_ = next;
}
}
}
void NodeBIO::Reset() {
if (read_head_ == nullptr)
return;
while (read_head_->read_pos_ != read_head_->write_pos_) {
CHECK(read_head_->write_pos_ > read_head_->read_pos_);
length_ -= read_head_->write_pos_ - read_head_->read_pos_;
read_head_->write_pos_ = 0;
read_head_->read_pos_ = 0;
read_head_ = read_head_->next_;
}
write_head_ = read_head_;
CHECK_EQ(length_, 0);
}
NodeBIO::~NodeBIO() {
if (read_head_ == nullptr)
return;
Buffer* current = read_head_;
do {
Buffer* next = current->next_;
delete current;
current = next;
} while (current != read_head_);
read_head_ = nullptr;
write_head_ = nullptr;
}
NodeBIO* NodeBIO::FromBIO(BIO* bio) {
CHECK_NOT_NULL(BIO_get_data(bio));
return static_cast<NodeBIO*>(BIO_get_data(bio));
}
} // namespace crypto
} // namespace node

193
src/crypto/crypto_bio.h Normal file
View File

@ -0,0 +1,193 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
#ifndef SRC_CRYPTO_CRYPTO_BIO_H_
#define SRC_CRYPTO_CRYPTO_BIO_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "node_crypto.h"
#include "openssl/bio.h"
#include "util.h"
#include "v8.h"
namespace node {
class Environment;
namespace crypto {
// This class represents buffers for OpenSSL I/O, implemented as a singly-linked
// list of chunks. It can be used either for writing data from Node to OpenSSL,
// or for reading data back, but not both.
// The structure is only accessed, and owned by, the OpenSSL BIOPointer
// (a.k.a. std::unique_ptr<BIO>).
class NodeBIO : public MemoryRetainer {
public:
~NodeBIO() override;
static ncrypto::BIOPointer New(Environment* env = nullptr);
// NewFixed takes a copy of `len` bytes from `data` and returns a BIO that,
// when read from, returns those bytes followed by EOF.
static ncrypto::BIOPointer NewFixed(const char* data,
size_t len,
Environment* env = nullptr);
// Move read head to next buffer if needed
void TryMoveReadHead();
// Allocate new buffer for write if needed
void TryAllocateForWrite(size_t hint);
// Read `len` bytes maximum into `out`, return actual number of read bytes
size_t Read(char* out, size_t size);
// Memory optimization:
// Deallocate children of write head's child if they're empty
void FreeEmpty();
// Return pointer to internal data and amount of
// contiguous data available to read
char* Peek(size_t* size);
// Return pointers and sizes of multiple internal data chunks available for
// reading
size_t PeekMultiple(char** out, size_t* size, size_t* count);
// Find first appearance of `delim` in buffer or `limit` if `delim`
// wasn't found.
size_t IndexOf(char delim, size_t limit);
// Discard all available data
void Reset();
// Put `len` bytes from `data` into buffer
void Write(const char* data, size_t size);
// Return pointer to contiguous block of reserved data and the size available
// for future writes. Call Commit() once the write is complete.
char* PeekWritable(size_t* size);
// Specify how much data was written into the block returned by
// PeekWritable().
void Commit(size_t size);
// Return size of buffer in bytes
inline size_t Length() const {
return length_;
}
// Provide a hint about the size of the next pending set of writes. TLS
// writes records of a maximum length of 16k of data plus a 5-byte header,
// a MAC (up to 20 bytes for SSLv3, TLS 1.0, TLS 1.1, and up to 32 bytes
// for TLS 1.2), and padding if a block cipher is used. If there is a
// large write this will result in potentially many buffers being
// allocated and gc'ed which can cause long pauses. By providing a
// guess about the amount of buffer space that will be needed in the
// next allocation this overhead is removed.
inline void set_allocate_tls_hint(size_t size) {
constexpr size_t kThreshold = 16 * 1024;
if (size >= kThreshold) {
allocate_hint_ = (size / kThreshold + 1) * (kThreshold + 5 + 32);
}
}
inline void set_eof_return(int num) {
eof_return_ = num;
}
inline int eof_return() {
return eof_return_;
}
inline void set_initial(size_t initial) {
initial_ = initial;
}
static NodeBIO* FromBIO(BIO* bio);
void MemoryInfo(MemoryTracker* tracker) const override {
tracker->TrackFieldWithSize("buffer", length_, "NodeBIO::Buffer");
}
SET_MEMORY_INFO_NAME(NodeBIO)
SET_SELF_SIZE(NodeBIO)
private:
static int New(BIO* bio);
static int Free(BIO* bio);
static int Read(BIO* bio, char* out, int len);
static int Write(BIO* bio, const char* data, int len);
static int Puts(BIO* bio, const char* str);
static int Gets(BIO* bio, char* out, int size);
static long Ctrl(BIO* bio, int cmd, long num, // NOLINT(runtime/int)
void* ptr);
static const BIO_METHOD* GetMethod();
// Enough to handle the most of the client hellos
static const size_t kInitialBufferLength = 1024;
static const size_t kThroughputBufferLength = 16384;
class Buffer {
public:
Buffer(Environment* env, size_t len) : env_(env),
read_pos_(0),
write_pos_(0),
len_(len),
next_(nullptr) {
data_ = new char[len];
if (env_ != nullptr) {
env_->external_memory_accounter()->Increase(env_->isolate(), len);
}
}
~Buffer() {
delete[] data_;
if (env_ != nullptr) {
env_->external_memory_accounter()->Decrease(env_->isolate(), len_);
}
}
Environment* env_;
size_t read_pos_;
size_t write_pos_;
size_t len_;
Buffer* next_;
char* data_;
};
Environment* env_ = nullptr;
size_t initial_ = kInitialBufferLength;
size_t length_ = 0;
size_t allocate_hint_ = 0;
int eof_return_ = -1;
Buffer* read_head_ = nullptr;
Buffer* write_head_ = nullptr;
};
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_BIO_H_

933
src/crypto/crypto_cipher.cc Normal file
View File

@ -0,0 +1,933 @@
#include "crypto/crypto_cipher.h"
#include "base_object-inl.h"
#include "crypto/crypto_util.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "node_buffer.h"
#include "node_internals.h"
#include "node_process-inl.h"
#include "v8.h"
namespace node {
using ncrypto::Cipher;
using ncrypto::CipherCtxPointer;
using ncrypto::ClearErrorOnReturn;
using ncrypto::Digest;
using ncrypto::EVPKeyCtxPointer;
using ncrypto::EVPKeyPointer;
using ncrypto::MarkPopErrorOnReturn;
using ncrypto::SSLCtxPointer;
using ncrypto::SSLPointer;
using v8::Array;
using v8::ArrayBuffer;
using v8::BackingStore;
using v8::BackingStoreInitializationMode;
using v8::Context;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::HandleScope;
using v8::Int32;
using v8::Isolate;
using v8::Local;
using v8::LocalVector;
using v8::Object;
using v8::Uint32;
using v8::Value;
namespace crypto {
namespace {
// Collects and returns information on the given cipher
void GetCipherInfo(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsObject());
Local<Object> info = args[0].As<Object>();
CHECK(args[1]->IsString() || args[1]->IsInt32());
const auto cipher = ([&] {
if (args[1]->IsString()) {
Utf8Value name(env->isolate(), args[1]);
return Cipher::FromName(*name);
} else {
int nid = args[1].As<Int32>()->Value();
return Cipher::FromNid(nid);
}
})();
if (!cipher) return;
int iv_length = cipher.getIvLength();
int key_length = cipher.getKeyLength();
int block_length = cipher.getBlockSize();
auto mode_label = cipher.getModeLabel();
auto name = cipher.getName();
// If the testKeyLen and testIvLen arguments are specified,
// then we will make an attempt to see if they are usable for
// the cipher in question, returning undefined if they are not.
// If they are, the info object will be returned with the values
// given.
if (args[2]->IsInt32() || args[3]->IsInt32()) {
// Test and input IV or key length to determine if it's acceptable.
// If it is, then the getCipherInfo will succeed with the given
// values.
auto ctx = CipherCtxPointer::New();
if (!ctx.init(cipher, true)) {
return;
}
if (args[2]->IsInt32()) {
int check_len = args[2].As<Int32>()->Value();
if (!ctx.setKeyLength(check_len)) {
return;
}
key_length = check_len;
}
if (args[3]->IsInt32()) {
int check_len = args[3].As<Int32>()->Value();
// For CCM modes, the IV may be between 7 and 13 bytes.
// For GCM and OCB modes, we'll check by attempting to
// set the value. For everything else, just check that
// check_len == iv_length.
if (cipher.isCcmMode()) {
if (check_len < 7 || check_len > 13) return;
} else if (cipher.isGcmMode()) {
// Nothing to do.
} else if (cipher.isOcbMode()) {
if (!ctx.setIvLength(check_len)) return;
} else {
if (check_len != iv_length) return;
}
iv_length = check_len;
}
}
if (mode_label.length() &&
info->Set(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "mode"),
OneByteString(
env->isolate(), mode_label.data(), mode_label.length()))
.IsNothing()) {
return;
}
if (info->Set(env->context(),
env->name_string(),
OneByteString(env->isolate(), name))
.IsNothing()) {
return;
}
if (info->Set(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "nid"),
Int32::New(env->isolate(), cipher.getNid()))
.IsNothing()) {
return;
}
// Stream ciphers do not have a meaningful block size
if (!cipher.isStreamMode() &&
info->Set(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "blockSize"),
Int32::New(env->isolate(), block_length))
.IsNothing()) {
return;
}
// Ciphers that do not use an IV shouldn't report a length
if (iv_length != 0 &&
info->Set(
env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "ivLength"),
Int32::New(env->isolate(), iv_length)).IsNothing()) {
return;
}
if (info->Set(
env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "keyLength"),
Int32::New(env->isolate(), key_length)).IsNothing()) {
return;
}
args.GetReturnValue().Set(info);
}
} // namespace
void CipherBase::GetSSLCiphers(const FunctionCallbackInfo<Value>& args) {
ClearErrorOnReturn clear_error_on_return;
Environment* env = Environment::GetCurrent(args);
auto ctx = SSLCtxPointer::New();
if (!ctx) {
return ThrowCryptoError(
env, clear_error_on_return.peekError(), "SSL_CTX_new");
}
auto ssl = SSLPointer::New(ctx);
if (!ssl) {
return ThrowCryptoError(env, clear_error_on_return.peekError(), "SSL_new");
}
LocalVector<Value> arr(env->isolate());
ssl.getCiphers([&](const std::string_view name) {
arr.push_back(OneByteString(env->isolate(), name.data(), name.length()));
});
args.GetReturnValue().Set(Array::New(env->isolate(), arr.data(), arr.size()));
}
void CipherBase::GetCiphers(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
LocalVector<Value> ciphers(env->isolate());
bool errored = false;
Cipher::ForEach([&](std::string_view name) {
// If a prior iteration errored, do nothing further. We apparently
// can't actually stop openssl from stopping its iteration here.
// But why does it matter? Good question.
if (errored) return;
Local<Value> val;
if (!ToV8Value(env->context(), name, env->isolate()).ToLocal(&val)) {
errored = true;
return;
}
ciphers.push_back(val);
});
// If errored is true here, then we encountered a JavaScript error
// while trying to create the V8 String from the std::string_view
// in the iteration callback. That means we need to throw.
if (!errored) {
args.GetReturnValue().Set(
Array::New(env->isolate(), ciphers.data(), ciphers.size()));
}
}
CipherBase::CipherBase(Environment* env,
Local<Object> wrap,
CipherKind kind)
: BaseObject(env, wrap),
ctx_(nullptr),
kind_(kind),
auth_tag_state_(kAuthTagUnknown),
auth_tag_len_(kNoAuthTagLength),
pending_auth_failed_(false) {
MakeWeak();
}
void CipherBase::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackFieldWithSize("context", ctx_ ? kSizeOf_EVP_CIPHER_CTX : 0);
}
void CipherBase::Initialize(Environment* env, Local<Object> target) {
Isolate* isolate = env->isolate();
Local<Context> context = env->context();
Local<FunctionTemplate> t = NewFunctionTemplate(isolate, New);
t->InstanceTemplate()->SetInternalFieldCount(CipherBase::kInternalFieldCount);
SetProtoMethod(isolate, t, "update", Update);
SetProtoMethod(isolate, t, "final", Final);
SetProtoMethod(isolate, t, "setAutoPadding", SetAutoPadding);
SetProtoMethodNoSideEffect(isolate, t, "getAuthTag", GetAuthTag);
SetProtoMethod(isolate, t, "setAuthTag", SetAuthTag);
SetProtoMethod(isolate, t, "setAAD", SetAAD);
SetConstructorFunction(context, target, "CipherBase", t);
SetMethodNoSideEffect(context, target, "getSSLCiphers", GetSSLCiphers);
SetMethodNoSideEffect(context, target, "getCiphers", GetCiphers);
SetMethod(context,
target,
"publicEncrypt",
PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
ncrypto::Cipher::encrypt>);
SetMethod(context,
target,
"privateDecrypt",
PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
ncrypto::Cipher::decrypt>);
SetMethod(context,
target,
"privateEncrypt",
PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
ncrypto::Cipher::sign>);
SetMethod(context,
target,
"publicDecrypt",
PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
ncrypto::Cipher::recover>);
SetMethodNoSideEffect(context, target, "getCipherInfo", GetCipherInfo);
NODE_DEFINE_CONSTANT(target, kWebCryptoCipherEncrypt);
NODE_DEFINE_CONSTANT(target, kWebCryptoCipherDecrypt);
}
void CipherBase::RegisterExternalReferences(
ExternalReferenceRegistry* registry) {
registry->Register(New);
registry->Register(Update);
registry->Register(Final);
registry->Register(SetAutoPadding);
registry->Register(GetAuthTag);
registry->Register(SetAuthTag);
registry->Register(SetAAD);
registry->Register(GetSSLCiphers);
registry->Register(GetCiphers);
registry->Register(PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
ncrypto::Cipher::encrypt>);
registry->Register(PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
ncrypto::Cipher::decrypt>);
registry->Register(PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
ncrypto::Cipher::sign>);
registry->Register(PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
ncrypto::Cipher::recover>);
registry->Register(GetCipherInfo);
}
void CipherBase::New(const FunctionCallbackInfo<Value>& args) {
CHECK(args.IsConstructCall());
Environment* env = Environment::GetCurrent(args);
CHECK_EQ(args.Length(), 5);
CipherBase* cipher =
new CipherBase(env, args.This(), args[0]->IsTrue() ? kCipher : kDecipher);
const Utf8Value cipher_type(env->isolate(), args[1]);
// The argument can either be a KeyObjectHandle or a byte source
// (e.g. ArrayBuffer, TypedArray, etc). Whichever it is, grab the
// raw bytes and proceed...
const ByteSource key_buf = ByteSource::FromSecretKeyBytes(env, args[2]);
if (key_buf.size() > INT_MAX) [[unlikely]] {
return THROW_ERR_OUT_OF_RANGE(env, "key is too big");
}
ArrayBufferOrViewContents<unsigned char> iv_buf(
!args[3]->IsNull() ? args[3] : Local<Value>());
if (!iv_buf.CheckSizeInt32()) [[unlikely]] {
return THROW_ERR_OUT_OF_RANGE(env, "iv is too big");
}
// Don't assign to cipher->auth_tag_len_ directly; the value might not
// represent a valid length at this point.
unsigned int auth_tag_len;
if (args[4]->IsUint32()) {
auth_tag_len = args[4].As<Uint32>()->Value();
} else {
CHECK(args[4]->IsInt32() && args[4].As<Int32>()->Value() == -1);
auth_tag_len = kNoAuthTagLength;
}
cipher->InitIv(*cipher_type, key_buf, iv_buf, auth_tag_len);
}
void CipherBase::CommonInit(const char* cipher_type,
const ncrypto::Cipher& cipher,
const unsigned char* key,
int key_len,
const unsigned char* iv,
int iv_len,
unsigned int auth_tag_len) {
MarkPopErrorOnReturn mark_pop_error_on_return;
CHECK(!ctx_);
ctx_ = CipherCtxPointer::New();
CHECK(ctx_);
if (cipher.isWrapMode()) {
ctx_.setAllowWrap();
}
const bool encrypt = (kind_ == kCipher);
if (!ctx_.init(cipher, encrypt)) {
return ThrowCryptoError(env(),
mark_pop_error_on_return.peekError(),
"Failed to initialize cipher");
}
if (cipher.isSupportedAuthenticatedMode()) {
CHECK_GE(iv_len, 0);
if (!InitAuthenticated(cipher_type, iv_len, auth_tag_len)) {
return;
}
}
if (!ctx_.setKeyLength(key_len)) {
ctx_.reset();
return THROW_ERR_CRYPTO_INVALID_KEYLEN(env());
}
if (!ctx_.init(Cipher(), encrypt, key, iv)) {
return ThrowCryptoError(env(),
mark_pop_error_on_return.peekError(),
"Failed to initialize cipher");
}
}
void CipherBase::InitIv(const char* cipher_type,
const ByteSource& key_buf,
const ArrayBufferOrViewContents<unsigned char>& iv_buf,
unsigned int auth_tag_len) {
HandleScope scope(env()->isolate());
MarkPopErrorOnReturn mark_pop_error_on_return;
auto cipher = Cipher::FromName(cipher_type);
if (!cipher) return THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env());
const int expected_iv_len = cipher.getIvLength();
const bool has_iv = iv_buf.size() > 0;
// Throw if no IV was passed and the cipher requires an IV
if (!has_iv && expected_iv_len != 0) {
return THROW_ERR_CRYPTO_INVALID_IV(env());
}
// Throw if an IV was passed which does not match the cipher's fixed IV length
// static_cast<int> for the iv_buf.size() is safe because we've verified
// prior that the value is not larger than INT_MAX.
if (!cipher.isSupportedAuthenticatedMode() && has_iv &&
static_cast<int>(iv_buf.size()) != expected_iv_len) {
return THROW_ERR_CRYPTO_INVALID_IV(env());
}
if (cipher.isChaCha20Poly1305()) {
CHECK(has_iv);
// Check for invalid IV lengths, since OpenSSL does not under some
// conditions:
// https://www.openssl.org/news/secadv/20190306.txt.
if (iv_buf.size() > 12) {
return THROW_ERR_CRYPTO_INVALID_IV(env());
}
}
CommonInit(
cipher_type,
cipher,
key_buf.data<unsigned char>(),
key_buf.size(),
iv_buf.data(),
iv_buf.size(),
auth_tag_len);
}
bool CipherBase::InitAuthenticated(const char* cipher_type,
int iv_len,
unsigned int auth_tag_len) {
CHECK(IsAuthenticatedMode());
MarkPopErrorOnReturn mark_pop_error_on_return;
if (!ctx_.setIvLength(iv_len)) {
THROW_ERR_CRYPTO_INVALID_IV(env());
return false;
}
if (ctx_.isGcmMode()) {
if (auth_tag_len != kNoAuthTagLength) {
if (!Cipher::IsValidGCMTagLength(auth_tag_len)) {
THROW_ERR_CRYPTO_INVALID_AUTH_TAG(
env(),
"Invalid authentication tag length: %u",
auth_tag_len);
return false;
}
// Remember the given authentication tag length for later.
auth_tag_len_ = auth_tag_len;
}
} else {
if (auth_tag_len == kNoAuthTagLength) {
// We treat ChaCha20-Poly1305 specially. Like GCM, the authentication tag
// length defaults to 16 bytes when encrypting. Unlike GCM, the
// authentication tag length also defaults to 16 bytes when decrypting,
// whereas GCM would accept any valid authentication tag length.
if (ctx_.isChaCha20Poly1305()) {
auth_tag_len = EVP_CHACHAPOLY_TLS_TAG_LEN;
} else {
THROW_ERR_CRYPTO_INVALID_AUTH_TAG(
env(), "authTagLength required for %s", cipher_type);
return false;
}
}
// TODO(tniessen) Support CCM decryption in FIPS mode
if (ctx_.isCcmMode() && kind_ == kDecipher && ncrypto::isFipsEnabled()) {
THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env(),
"CCM encryption not supported in FIPS mode");
return false;
}
// Tell OpenSSL about the desired length.
if (!ctx_.setAeadTagLength(auth_tag_len)) {
THROW_ERR_CRYPTO_INVALID_AUTH_TAG(
env(), "Invalid authentication tag length: %u", auth_tag_len);
return false;
}
// Remember the given authentication tag length for later.
auth_tag_len_ = auth_tag_len;
if (ctx_.isCcmMode()) {
// Restrict the message length to min(INT_MAX, 2^(8*(15-iv_len))-1) bytes.
CHECK(iv_len >= 7 && iv_len <= 13);
max_message_size_ = INT_MAX;
if (iv_len == 12) max_message_size_ = 16777215;
if (iv_len == 13) max_message_size_ = 65535;
}
}
return true;
}
bool CipherBase::CheckCCMMessageLength(int message_len) {
CHECK(ctx_);
CHECK(ctx_.isCcmMode());
if (message_len > max_message_size_) {
THROW_ERR_CRYPTO_INVALID_MESSAGELEN(env());
return false;
}
return true;
}
bool CipherBase::IsAuthenticatedMode() const {
// Check if this cipher operates in an AEAD mode that we support.
CHECK(ctx_);
return ncrypto::Cipher::FromCtx(ctx_).isSupportedAuthenticatedMode();
}
void CipherBase::GetAuthTag(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CipherBase* cipher;
ASSIGN_OR_RETURN_UNWRAP(&cipher, args.This());
// Only callable after Final and if encrypting.
if (cipher->ctx_ || cipher->kind_ != kCipher ||
cipher->auth_tag_len_ == kNoAuthTagLength ||
cipher->auth_tag_state_ != kAuthTagComputed) {
return;
}
Local<Value> ret;
if (Buffer::Copy(env, cipher->auth_tag_, cipher->auth_tag_len_)
.ToLocal(&ret)) {
args.GetReturnValue().Set(ret);
}
}
void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
CipherBase* cipher;
ASSIGN_OR_RETURN_UNWRAP(&cipher, args.This());
Environment* env = Environment::GetCurrent(args);
if (!cipher->ctx_ ||
!cipher->IsAuthenticatedMode() ||
cipher->kind_ != kDecipher ||
cipher->auth_tag_state_ != kAuthTagUnknown) {
return args.GetReturnValue().Set(false);
}
ArrayBufferOrViewContents<char> auth_tag(args[0]);
if (!auth_tag.CheckSizeInt32()) [[unlikely]] {
return THROW_ERR_OUT_OF_RANGE(env, "buffer is too big");
}
unsigned int tag_len = auth_tag.size();
bool is_valid;
if (cipher->ctx_.isGcmMode()) {
// Restrict GCM tag lengths according to NIST 800-38d, page 9.
is_valid = (cipher->auth_tag_len_ == kNoAuthTagLength ||
cipher->auth_tag_len_ == tag_len) &&
Cipher::IsValidGCMTagLength(tag_len);
} else {
// At this point, the tag length is already known and must match the
// length of the given authentication tag.
CHECK(Cipher::FromCtx(cipher->ctx_).isSupportedAuthenticatedMode());
CHECK_NE(cipher->auth_tag_len_, kNoAuthTagLength);
is_valid = cipher->auth_tag_len_ == tag_len;
}
if (!is_valid) {
return THROW_ERR_CRYPTO_INVALID_AUTH_TAG(
env, "Invalid authentication tag length: %u", tag_len);
}
if (cipher->ctx_.isGcmMode() && cipher->auth_tag_len_ == kNoAuthTagLength &&
tag_len != EVP_GCM_TLS_TAG_LEN && env->EmitProcessEnvWarning()) {
if (ProcessEmitDeprecationWarning(
env,
"Using AES-GCM authentication tags of less than 128 bits without "
"specifying the authTagLength option when initializing decryption "
"is deprecated.",
"DEP0182")
.IsNothing())
return;
}
cipher->auth_tag_len_ = tag_len;
CHECK_LE(cipher->auth_tag_len_, ncrypto::Cipher::MAX_AUTH_TAG_LENGTH);
if (!cipher->ctx_.setAeadTag({auth_tag.data(), cipher->auth_tag_len_})) {
return args.GetReturnValue().Set(false);
}
cipher->auth_tag_state_ = kAuthTagSetByUser;
args.GetReturnValue().Set(true);
}
bool CipherBase::SetAAD(
const ArrayBufferOrViewContents<unsigned char>& data,
int plaintext_len) {
if (!ctx_ || !IsAuthenticatedMode())
return false;
MarkPopErrorOnReturn mark_pop_error_on_return;
int outlen;
// When in CCM mode, we need to set the authentication tag and the plaintext
// length in advance.
if (ctx_.isCcmMode()) {
if (plaintext_len < 0) {
THROW_ERR_MISSING_ARGS(env(),
"options.plaintextLength required for CCM mode with AAD");
return false;
}
if (!CheckCCMMessageLength(plaintext_len)) {
return false;
}
ncrypto::Buffer<const unsigned char> buffer{
.data = nullptr,
.len = static_cast<size_t>(plaintext_len),
};
// Specify the plaintext length.
if (!ctx_.update(buffer, nullptr, &outlen)) {
return false;
}
}
ncrypto::Buffer<const unsigned char> buffer{
.data = data.data(),
.len = data.size(),
};
return ctx_.update(buffer, nullptr, &outlen);
}
void CipherBase::SetAAD(const FunctionCallbackInfo<Value>& args) {
CipherBase* cipher;
ASSIGN_OR_RETURN_UNWRAP(&cipher, args.This());
Environment* env = Environment::GetCurrent(args);
CHECK_EQ(args.Length(), 2);
CHECK(args[1]->IsInt32());
int plaintext_len = args[1].As<Int32>()->Value();
ArrayBufferOrViewContents<unsigned char> buf(args[0]);
if (!buf.CheckSizeInt32()) [[unlikely]] {
return THROW_ERR_OUT_OF_RANGE(env, "buffer is too big");
}
args.GetReturnValue().Set(cipher->SetAAD(buf, plaintext_len));
}
CipherBase::UpdateResult CipherBase::Update(
const char* data,
size_t len,
std::unique_ptr<BackingStore>* out) {
if (!ctx_ || len > INT_MAX) return kErrorState;
MarkPopErrorOnReturn mark_pop_error_on_return;
if (ctx_.isCcmMode() && !CheckCCMMessageLength(len)) {
return kErrorMessageSize;
}
const int block_size = ctx_.getBlockSize();
CHECK_GT(block_size, 0);
if (len + block_size > INT_MAX) return kErrorState;
int buf_len = len + block_size;
ncrypto::Buffer<const unsigned char> buffer = {
.data = reinterpret_cast<const unsigned char*>(data),
.len = len,
};
if (kind_ == kCipher && ctx_.isWrapMode() &&
!ctx_.update(buffer, nullptr, &buf_len)) {
return kErrorState;
}
*out = ArrayBuffer::NewBackingStore(
env()->isolate(),
buf_len,
BackingStoreInitializationMode::kUninitialized);
buffer = {
.data = reinterpret_cast<const unsigned char*>(data),
.len = len,
};
bool r = ctx_.update(
buffer, static_cast<unsigned char*>((*out)->Data()), &buf_len);
CHECK_LE(static_cast<size_t>(buf_len), (*out)->ByteLength());
if (buf_len == 0) {
*out = ArrayBuffer::NewBackingStore(env()->isolate(), 0);
} else if (static_cast<size_t>(buf_len) != (*out)->ByteLength()) {
std::unique_ptr<BackingStore> old_out = std::move(*out);
*out = ArrayBuffer::NewBackingStore(
env()->isolate(),
buf_len,
BackingStoreInitializationMode::kUninitialized);
memcpy((*out)->Data(), old_out->Data(), buf_len);
}
// When in CCM mode, EVP_CipherUpdate will fail if the authentication tag is
// invalid. In that case, remember the error and throw in final().
if (!r && kind_ == kDecipher && ctx_.isCcmMode()) {
pending_auth_failed_ = true;
return kSuccess;
}
return r == 1 ? kSuccess : kErrorState;
}
void CipherBase::Update(const FunctionCallbackInfo<Value>& args) {
Decode<CipherBase>(
args,
[](CipherBase* cipher,
const FunctionCallbackInfo<Value>& args,
const char* data,
size_t size) {
MarkPopErrorOnReturn mark_pop_error_on_return;
std::unique_ptr<BackingStore> out;
Environment* env = Environment::GetCurrent(args);
if (size > INT_MAX) [[unlikely]] {
return THROW_ERR_OUT_OF_RANGE(env, "data is too long");
}
UpdateResult r = cipher->Update(data, size, &out);
if (r != kSuccess) {
if (r == kErrorState) {
ThrowCryptoError(env,
mark_pop_error_on_return.peekError(),
"Trying to add data in unsupported state");
}
return;
}
auto ab = ArrayBuffer::New(env->isolate(), std::move(out));
args.GetReturnValue().Set(Buffer::New(env, ab, 0, ab->ByteLength())
.FromMaybe(Local<Value>()));
});
}
bool CipherBase::SetAutoPadding(bool auto_padding) {
if (!ctx_) return false;
MarkPopErrorOnReturn mark_pop_error_on_return;
return ctx_.setPadding(auto_padding);
}
void CipherBase::SetAutoPadding(const FunctionCallbackInfo<Value>& args) {
CipherBase* cipher;
ASSIGN_OR_RETURN_UNWRAP(&cipher, args.This());
bool b = cipher->SetAutoPadding(args.Length() < 1 || args[0]->IsTrue());
args.GetReturnValue().Set(b); // Possibly report invalid state failure
}
bool CipherBase::Final(std::unique_ptr<BackingStore>* out) {
if (!ctx_) return false;
*out = ArrayBuffer::NewBackingStore(
env()->isolate(),
static_cast<size_t>(ctx_.getBlockSize()),
BackingStoreInitializationMode::kUninitialized);
#if (OPENSSL_VERSION_NUMBER < 0x30000000L)
// OpenSSL v1.x doesn't verify the presence of the auth tag so do
// it ourselves, see https://github.com/nodejs/node/issues/45874.
if (kind_ == kDecipher && ctx_.isChaCha20Poly1305() &&
auth_tag_state_ != kAuthTagSetByUser) {
return false;
}
#endif
// In CCM mode, final() only checks whether authentication failed in update().
// EVP_CipherFinal_ex must not be called and will fail.
bool ok;
if (kind_ == kDecipher && ctx_.isCcmMode()) {
ok = !pending_auth_failed_;
*out = ArrayBuffer::NewBackingStore(env()->isolate(), 0);
} else {
int out_len = (*out)->ByteLength();
ok = ctx_.update(
{}, static_cast<unsigned char*>((*out)->Data()), &out_len, true);
CHECK_LE(static_cast<size_t>(out_len), (*out)->ByteLength());
if (out_len == 0) {
*out = ArrayBuffer::NewBackingStore(env()->isolate(), 0);
} else if (static_cast<size_t>(out_len) != (*out)->ByteLength()) {
std::unique_ptr<BackingStore> old_out = std::move(*out);
*out = ArrayBuffer::NewBackingStore(
env()->isolate(),
out_len,
BackingStoreInitializationMode::kUninitialized);
memcpy((*out)->Data(), old_out->Data(), out_len);
}
if (ok && kind_ == kCipher && IsAuthenticatedMode()) {
// In GCM mode, the authentication tag length can be specified in advance,
// but defaults to 16 bytes when encrypting. In CCM and OCB mode, it must
// always be given by the user.
if (auth_tag_len_ == kNoAuthTagLength) {
CHECK(ctx_.isGcmMode());
auth_tag_len_ = EVP_GCM_TLS_TAG_LEN;
}
ok = ctx_.getAeadTag(auth_tag_len_,
reinterpret_cast<unsigned char*>(auth_tag_));
if (ok) {
auth_tag_state_ = kAuthTagComputed;
}
}
}
ctx_.reset();
return ok;
}
void CipherBase::Final(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
MarkPopErrorOnReturn mark_pop_error_on_return;
CipherBase* cipher;
ASSIGN_OR_RETURN_UNWRAP(&cipher, args.This());
if (cipher->ctx_ == nullptr) {
return THROW_ERR_CRYPTO_INVALID_STATE(env);
}
std::unique_ptr<BackingStore> out;
// Check IsAuthenticatedMode() first, Final() destroys the EVP_CIPHER_CTX.
const bool is_auth_mode = cipher->IsAuthenticatedMode();
bool r = cipher->Final(&out);
if (!r) {
const char* msg = is_auth_mode
? "Unsupported state or unable to authenticate data"
: "Unsupported state";
return ThrowCryptoError(env, mark_pop_error_on_return.peekError(), msg);
}
auto ab = ArrayBuffer::New(env->isolate(), std::move(out));
args.GetReturnValue().Set(
Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local<Value>()));
}
template <PublicKeyCipher::Cipher_t cipher>
bool PublicKeyCipher::Cipher(
Environment* env,
const EVPKeyPointer& pkey,
int padding,
const Digest& digest,
const ArrayBufferOrViewContents<unsigned char>& oaep_label,
const ArrayBufferOrViewContents<unsigned char>& data,
std::unique_ptr<BackingStore>* out) {
auto label = oaep_label.ToByteSource();
auto in = data.ToByteSource();
const ncrypto::Cipher::CipherParams params{
.padding = padding,
.digest = digest,
.label = label,
};
auto buf = cipher(pkey, params, in);
if (!buf) return false;
if (buf.size() == 0) {
*out = ArrayBuffer::NewBackingStore(env->isolate(), 0);
} else {
*out = ArrayBuffer::NewBackingStore(
env->isolate(),
buf.size(),
BackingStoreInitializationMode::kUninitialized);
memcpy((*out)->Data(), buf.get(), buf.size());
}
return true;
}
template <PublicKeyCipher::Operation operation,
PublicKeyCipher::Cipher_t cipher>
void PublicKeyCipher::Cipher(const FunctionCallbackInfo<Value>& args) {
MarkPopErrorOnReturn mark_pop_error_on_return;
Environment* env = Environment::GetCurrent(args);
unsigned int offset = 0;
auto data = KeyObjectData::GetPublicOrPrivateKeyFromJs(args, &offset);
if (!data) return;
const auto& pkey = data.GetAsymmetricKey();
if (!pkey) return;
ArrayBufferOrViewContents<unsigned char> buf(args[offset]);
if (!buf.CheckSizeInt32()) [[unlikely]] {
return THROW_ERR_OUT_OF_RANGE(env, "buffer is too long");
}
uint32_t padding;
if (!args[offset + 1]->Uint32Value(env->context()).To(&padding)) return;
if (cipher == ncrypto::Cipher::decrypt &&
operation == PublicKeyCipher::kPrivate && padding == RSA_PKCS1_PADDING) {
EVPKeyCtxPointer ctx = pkey.newCtx();
CHECK(ctx);
if (!ctx.initForDecrypt()) {
return ThrowCryptoError(env, ERR_get_error());
}
// RSA implicit rejection here is not supported by BoringSSL.
if (!ctx.setRsaImplicitRejection()) [[unlikely]] {
return THROW_ERR_INVALID_ARG_VALUE(
env,
"RSA_PKCS1_PADDING is no longer supported for private decryption");
}
}
Digest digest;
if (args[offset + 2]->IsString()) {
Utf8Value oaep_str(env->isolate(), args[offset + 2]);
digest = Digest::FromName(*oaep_str);
if (!digest) return THROW_ERR_OSSL_EVP_INVALID_DIGEST(env);
}
ArrayBufferOrViewContents<unsigned char> oaep_label(
!args[offset + 3]->IsUndefined() ? args[offset + 3] : Local<Value>());
if (!oaep_label.CheckSizeInt32()) [[unlikely]] {
return THROW_ERR_OUT_OF_RANGE(env, "oaepLabel is too big");
}
std::unique_ptr<BackingStore> out;
if (!Cipher<cipher>(env, pkey, padding, digest, oaep_label, buf, &out)) {
return ThrowCryptoError(env, ERR_get_error());
}
Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(out));
args.GetReturnValue().Set(
Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local<Value>()));
}
} // namespace crypto
} // namespace node

281
src/crypto/crypto_cipher.h Normal file
View File

@ -0,0 +1,281 @@
#ifndef SRC_CRYPTO_CRYPTO_CIPHER_H_
#define SRC_CRYPTO_CRYPTO_CIPHER_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "base_object.h"
#include "env.h"
#include "memory_tracker.h"
#include "v8.h"
#include <string>
namespace node {
namespace crypto {
class CipherBase : public BaseObject {
public:
static void GetSSLCiphers(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetCiphers(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Initialize(Environment* env, v8::Local<v8::Object> target);
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(CipherBase)
SET_SELF_SIZE(CipherBase)
protected:
enum CipherKind {
kCipher,
kDecipher
};
enum UpdateResult {
kSuccess,
kErrorMessageSize,
kErrorState
};
enum AuthTagState {
kAuthTagUnknown,
kAuthTagSetByUser,
kAuthTagComputed,
};
static const unsigned kNoAuthTagLength = static_cast<unsigned>(-1);
void CommonInit(const char* cipher_type,
const ncrypto::Cipher& cipher,
const unsigned char* key,
int key_len,
const unsigned char* iv,
int iv_len,
unsigned int auth_tag_len);
void InitIv(const char* cipher_type,
const ByteSource& key_buf,
const ArrayBufferOrViewContents<unsigned char>& iv_buf,
unsigned int auth_tag_len);
bool InitAuthenticated(const char* cipher_type,
int iv_len,
unsigned int auth_tag_len);
bool CheckCCMMessageLength(int message_len);
UpdateResult Update(const char* data, size_t len,
std::unique_ptr<v8::BackingStore>* out);
bool Final(std::unique_ptr<v8::BackingStore>* out);
bool SetAutoPadding(bool auto_padding);
bool IsAuthenticatedMode() const;
bool SetAAD(const ArrayBufferOrViewContents<unsigned char>& data,
int plaintext_len);
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Update(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Final(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetAutoPadding(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetAuthTag(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetAuthTag(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetAAD(const v8::FunctionCallbackInfo<v8::Value>& args);
CipherBase(Environment* env, v8::Local<v8::Object> wrap, CipherKind kind);
private:
ncrypto::CipherCtxPointer ctx_;
const CipherKind kind_;
AuthTagState auth_tag_state_;
unsigned int auth_tag_len_;
char auth_tag_[ncrypto::Cipher::MAX_AUTH_TAG_LENGTH];
bool pending_auth_failed_;
int max_message_size_;
};
class PublicKeyCipher {
public:
using Cipher_t =
ncrypto::DataPointer(const ncrypto::EVPKeyPointer&,
const ncrypto::Cipher::CipherParams& params,
const ncrypto::Buffer<const void>);
enum Operation {
kPublic,
kPrivate
};
template <Cipher_t cipher>
static bool Cipher(Environment* env,
const ncrypto::EVPKeyPointer& pkey,
int padding,
const ncrypto::Digest& digest,
const ArrayBufferOrViewContents<unsigned char>& oaep_label,
const ArrayBufferOrViewContents<unsigned char>& data,
std::unique_ptr<v8::BackingStore>* out);
template <Operation operation, Cipher_t cipher>
static void Cipher(const v8::FunctionCallbackInfo<v8::Value>& args);
};
enum WebCryptoCipherMode {
kWebCryptoCipherEncrypt,
kWebCryptoCipherDecrypt
};
enum class WebCryptoCipherStatus {
OK,
INVALID_KEY_TYPE,
FAILED
};
// CipherJob is a base implementation class for implementations of
// one-shot sync and async ciphers. It has been added primarily to
// support the AES and RSA ciphers underlying the WebCrypt API.
//
// See the crypto_aes and crypto_rsa headers for examples of how to
// use CipherJob.
template <typename CipherTraits>
class CipherJob final : public CryptoJob<CipherTraits> {
public:
using AdditionalParams = typename CipherTraits::AdditionalParameters;
static void New(const v8::FunctionCallbackInfo<v8::Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args.IsConstructCall());
CryptoJobMode mode = GetCryptoJobMode(args[0]);
CHECK(args[1]->IsUint32()); // Cipher Mode
auto cipher_mode =
static_cast<WebCryptoCipherMode>(args[1].As<v8::Uint32>()->Value());
CHECK(args[2]->IsObject()); // KeyObject
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args[2]);
CHECK_NOT_NULL(key);
ArrayBufferOrViewContents<char> data(args[3]); // data to operate on
if (!data.CheckSizeInt32())
return THROW_ERR_OUT_OF_RANGE(env, "data is too large");
AdditionalParams params;
if (CipherTraits::AdditionalConfig(mode, args, 4, cipher_mode, &params)
.IsNothing()) {
// The CipherTraits::AdditionalConfig is responsible for
// calling an appropriate THROW_CRYPTO_* variant reporting
// whatever error caused initialization to fail.
return;
}
new CipherJob<CipherTraits>(
env,
args.This(),
mode,
key,
cipher_mode,
data,
std::move(params));
}
static void Initialize(
Environment* env,
v8::Local<v8::Object> target) {
CryptoJob<CipherTraits>::Initialize(New, env, target);
}
static void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
CryptoJob<CipherTraits>::RegisterExternalReferences(New, registry);
}
CipherJob(Environment* env,
v8::Local<v8::Object> object,
CryptoJobMode mode,
KeyObjectHandle* key,
WebCryptoCipherMode cipher_mode,
const ArrayBufferOrViewContents<char>& data,
AdditionalParams&& params)
: CryptoJob<CipherTraits>(env,
object,
AsyncWrap::PROVIDER_CIPHERREQUEST,
mode,
std::move(params)),
key_(key->Data().addRef()),
cipher_mode_(cipher_mode),
in_(mode == kCryptoJobAsync ? data.ToCopy() : data.ToByteSource()) {}
const KeyObjectData& key() const { return key_; }
WebCryptoCipherMode cipher_mode() const { return cipher_mode_; }
void DoThreadPoolWork() override {
const WebCryptoCipherStatus status =
CipherTraits::DoCipher(
AsyncWrap::env(),
key(),
cipher_mode_,
*CryptoJob<CipherTraits>::params(),
in_,
&out_);
if (status == WebCryptoCipherStatus::OK) {
// Success!
return;
}
CryptoErrorStore* errors = CryptoJob<CipherTraits>::errors();
errors->Capture();
if (errors->Empty()) {
switch (status) {
case WebCryptoCipherStatus::OK:
UNREACHABLE();
break;
case WebCryptoCipherStatus::INVALID_KEY_TYPE:
errors->Insert(NodeCryptoError::INVALID_KEY_TYPE);
break;
case WebCryptoCipherStatus::FAILED:
errors->Insert(NodeCryptoError::CIPHER_JOB_FAILED);
break;
}
}
}
v8::Maybe<void> ToResult(v8::Local<v8::Value>* err,
v8::Local<v8::Value>* result) override {
Environment* env = AsyncWrap::env();
CryptoErrorStore* errors = CryptoJob<CipherTraits>::errors();
if (errors->Empty())
errors->Capture();
if (out_.size() > 0 || errors->Empty()) {
CHECK(errors->Empty());
*err = v8::Undefined(env->isolate());
*result = out_.ToArrayBuffer(env);
if (result->IsEmpty()) {
return v8::Nothing<void>();
}
} else {
*result = v8::Undefined(env->isolate());
if (!errors->ToException(env).ToLocal(err)) {
return v8::Nothing<void>();
}
}
CHECK(!result->IsEmpty());
CHECK(!err->IsEmpty());
return v8::JustVoid();
}
SET_SELF_SIZE(CipherJob)
void MemoryInfo(MemoryTracker* tracker) const override {
if (CryptoJob<CipherTraits>::mode() == kCryptoJobAsync)
tracker->TrackFieldWithSize("in", in_.size());
tracker->TrackFieldWithSize("out", out_.size());
CryptoJob<CipherTraits>::MemoryInfo(tracker);
}
private:
KeyObjectData key_;
WebCryptoCipherMode cipher_mode_;
ByteSource in_;
ByteSource out_;
};
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_CIPHER_H_

View File

@ -0,0 +1,90 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
#ifndef SRC_CRYPTO_CRYPTO_CLIENTHELLO_INL_H_
#define SRC_CRYPTO_CRYPTO_CLIENTHELLO_INL_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_clienthello.h"
#include "util.h"
namespace node {
namespace crypto {
inline ClientHelloParser::ClientHelloParser()
: state_(kEnded),
onhello_cb_(nullptr),
onend_cb_(nullptr),
cb_arg_(nullptr) {
Reset();
}
inline void ClientHelloParser::Reset() {
frame_len_ = 0;
body_offset_ = 0;
extension_offset_ = 0;
session_size_ = 0;
session_id_ = nullptr;
tls_ticket_size_ = -1;
tls_ticket_ = nullptr;
servername_size_ = 0;
servername_ = nullptr;
}
inline void ClientHelloParser::Start(ClientHelloParser::OnHelloCb onhello_cb,
ClientHelloParser::OnEndCb onend_cb,
void* cb_arg) {
if (!IsEnded())
return;
Reset();
CHECK_NOT_NULL(onhello_cb);
state_ = kWaiting;
onhello_cb_ = onhello_cb;
onend_cb_ = onend_cb;
cb_arg_ = cb_arg;
}
inline void ClientHelloParser::End() {
if (state_ == kEnded)
return;
state_ = kEnded;
if (onend_cb_ != nullptr) {
onend_cb_(cb_arg_);
onend_cb_ = nullptr;
}
}
inline bool ClientHelloParser::IsEnded() const {
return state_ == kEnded;
}
inline bool ClientHelloParser::IsPaused() const {
return state_ == kPaused;
}
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_CLIENTHELLO_INL_H_

View File

@ -0,0 +1,238 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "crypto/crypto_clienthello.h" // NOLINT(build/include_inline)
#include "crypto/crypto_clienthello-inl.h"
namespace node {
namespace crypto {
void ClientHelloParser::Parse(const uint8_t* data, size_t avail) {
switch (state_) {
case kWaiting:
if (!ParseRecordHeader(data, avail))
break;
[[fallthrough]];
case kTLSHeader:
ParseHeader(data, avail);
break;
case kPaused:
// Just nop
case kEnded:
// Already ended, just ignore it
break;
default:
break;
}
}
bool ClientHelloParser::ParseRecordHeader(const uint8_t* data, size_t avail) {
// >= 5 bytes for header parsing
if (avail < 5)
return false;
if (data[0] == kChangeCipherSpec ||
data[0] == kAlert ||
data[0] == kHandshake ||
data[0] == kApplicationData) {
frame_len_ = (data[3] << 8) + data[4];
state_ = kTLSHeader;
body_offset_ = 5;
} else {
End();
return false;
}
// Sanity check (too big frame, or too small)
// Let OpenSSL handle it
if (frame_len_ >= kMaxTLSFrameLen) {
End();
return false;
}
return true;
}
void ClientHelloParser::ParseHeader(const uint8_t* data, size_t avail) {
ClientHello hello;
// We need at least six bytes (one byte for kClientHello, three bytes for the
// length of the handshake message, and two bytes for the protocol version).
// If the client sent a frame that suggests a smaller ClientHello, give up.
if (frame_len_ < 6) return End();
// >= 5 + frame size bytes for frame parsing
if (body_offset_ + frame_len_ > avail)
return;
// Check hello protocol version. Protocol tuples that we know about:
//
// (3,1) TLS v1.0
// (3,2) TLS v1.1
// (3,3) TLS v1.2
//
// Note that TLS v1.3 uses a TLS v1.2 handshake so requires no specific
// support here.
if (data[body_offset_ + 4] != 0x03 ||
data[body_offset_ + 5] < 0x01 ||
data[body_offset_ + 5] > 0x03) {
return End();
}
if (data[body_offset_] == kClientHello) {
if (state_ == kTLSHeader) {
if (!ParseTLSClientHello(data, avail))
return End();
} else {
// We couldn't get here, but whatever
return End();
}
// Check if we overflowed (do not reply with any private data)
if (session_id_ == nullptr ||
session_size_ > 32 ||
session_id_ + session_size_ > data + avail) {
return End();
}
}
state_ = kPaused;
hello.session_id_ = session_id_;
hello.session_size_ = session_size_;
hello.has_ticket_ = tls_ticket_ != nullptr && tls_ticket_size_ != 0;
hello.servername_ = servername_;
hello.servername_size_ = static_cast<uint8_t>(servername_size_);
onhello_cb_(cb_arg_, hello);
}
void ClientHelloParser::ParseExtension(const uint16_t type,
const uint8_t* data,
size_t len) {
// NOTE: In case of anything we're just returning back, ignoring the problem.
// That's because we're heavily relying on OpenSSL to solve any problem with
// incoming data.
switch (type) {
case kServerName:
{
if (len < 2)
return;
uint32_t server_names_len = (data[0] << 8) + data[1];
if (server_names_len + 2 > len)
return;
for (size_t offset = 2; offset < 2 + server_names_len; ) {
if (offset + 3 > len)
return;
uint8_t name_type = data[offset];
if (name_type != kServernameHostname)
return;
uint16_t name_len = (data[offset + 1] << 8) + data[offset + 2];
offset += 3;
if (offset + name_len > len)
return;
servername_ = data + offset;
servername_size_ = name_len;
offset += name_len;
}
}
break;
case kTLSSessionTicket:
tls_ticket_size_ = len;
tls_ticket_ = data + len;
break;
default:
// Ignore
break;
}
}
bool ClientHelloParser::ParseTLSClientHello(const uint8_t* data, size_t avail) {
const uint8_t* body;
// Skip frame header, hello header, protocol version and random data
size_t session_offset = body_offset_ + 4 + 2 + 32;
if (session_offset + 1 >= avail)
return false;
body = data + session_offset;
session_size_ = *body;
session_id_ = body + 1;
size_t cipher_offset = session_offset + 1 + session_size_;
// Session OOB failure
if (cipher_offset + 1 >= avail)
return false;
uint16_t cipher_len =
(data[cipher_offset] << 8) + data[cipher_offset + 1];
size_t comp_offset = cipher_offset + 2 + cipher_len;
// Cipher OOB failure
if (comp_offset >= avail)
return false;
uint8_t comp_len = data[comp_offset];
size_t extension_offset = comp_offset + 1 + comp_len;
// Compression OOB failure
if (extension_offset > avail)
return false;
// No extensions present
if (extension_offset == avail)
return true;
size_t ext_off = extension_offset + 2;
// Parse known extensions
while (ext_off < avail) {
// Extension OOB
if (ext_off + 4 > avail)
return false;
uint16_t ext_type = (data[ext_off] << 8) + data[ext_off + 1];
uint16_t ext_len = (data[ext_off + 2] << 8) + data[ext_off + 3];
ext_off += 4;
// Extension OOB
if (ext_off + ext_len > avail)
return false;
ParseExtension(ext_type,
data + ext_off,
ext_len);
ext_off += ext_len;
}
// Extensions OOB failure
if (ext_off > avail)
return false;
return true;
}
} // namespace crypto
} // namespace node

View File

@ -0,0 +1,131 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
#ifndef SRC_CRYPTO_CRYPTO_CLIENTHELLO_H_
#define SRC_CRYPTO_CRYPTO_CLIENTHELLO_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include <cstddef> // size_t
#include <cstdint>
namespace node {
namespace crypto {
// Parse the client hello so we can do async session resumption. OpenSSL's
// session resumption uses synchronous callbacks, see SSL_CTX_sess_set_get_cb
// and get_session_cb.
//
// TLS1.3 handshakes masquerade as TLS1.2 session resumption, and to do this,
// they always include a session_id in the ClientHello, making up a bogus value
// if necessary. The parser can't know if its a bogus id, and will cause a
// 'newSession' event to be emitted. This should do no harm, the id won't be
// found, and the handshake will continue.
class ClientHelloParser {
public:
inline ClientHelloParser();
class ClientHello {
public:
inline uint8_t session_size() const { return session_size_; }
inline const uint8_t* session_id() const { return session_id_; }
inline bool has_ticket() const { return has_ticket_; }
inline uint8_t servername_size() const { return servername_size_; }
inline const uint8_t* servername() const { return servername_; }
private:
uint8_t session_size_;
const uint8_t* session_id_;
bool has_ticket_;
uint8_t servername_size_;
const uint8_t* servername_;
friend class ClientHelloParser;
};
typedef void (*OnHelloCb)(void* arg, const ClientHello& hello);
typedef void (*OnEndCb)(void* arg);
void Parse(const uint8_t* data, size_t avail);
inline void Reset();
inline void Start(OnHelloCb onhello_cb, OnEndCb onend_cb, void* cb_arg);
inline void End();
inline bool IsPaused() const;
inline bool IsEnded() const;
private:
static const size_t kMaxTLSFrameLen = 16 * 1024 + 5;
static const size_t kMaxSSLExFrameLen = 32 * 1024;
static const uint8_t kServernameHostname = 0;
static const size_t kMinStatusRequestSize = 5;
enum ParseState {
kWaiting,
kTLSHeader,
kPaused,
kEnded
};
enum FrameType {
kChangeCipherSpec = 20,
kAlert = 21,
kHandshake = 22,
kApplicationData = 23,
kOther = 255
};
enum HandshakeType {
kClientHello = 1
};
enum ExtensionType {
kServerName = 0,
kTLSSessionTicket = 35
};
bool ParseRecordHeader(const uint8_t* data, size_t avail);
void ParseHeader(const uint8_t* data, size_t avail);
void ParseExtension(const uint16_t type,
const uint8_t* data,
size_t len);
bool ParseTLSClientHello(const uint8_t* data, size_t avail);
ParseState state_;
OnHelloCb onhello_cb_;
OnEndCb onend_cb_;
void* cb_arg_;
size_t frame_len_ = 0;
size_t body_offset_ = 0;
size_t extension_offset_ = 0;
uint8_t session_size_ = 0;
const uint8_t* session_id_ = nullptr;
uint16_t servername_size_ = 0;
const uint8_t* servername_ = nullptr;
uint16_t tls_ticket_size_ = -1;
const uint8_t* tls_ticket_ = nullptr;
};
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_CLIENTHELLO_H_

345
src/crypto/crypto_common.cc Normal file
View File

@ -0,0 +1,345 @@
#include "crypto/crypto_common.h"
#include "base_object-inl.h"
#include "crypto/crypto_util.h"
#include "crypto/crypto_x509.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "nbytes.h"
#include "ncrypto.h"
#include "node.h"
#include "node_buffer.h"
#include "node_crypto.h"
#include "node_internals.h"
#include "string_bytes.h"
#include "v8.h"
#include <openssl/ec.h>
#include <openssl/ecdh.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/x509v3.h>
#include <openssl/hmac.h>
#include <openssl/rand.h>
#include <openssl/pkcs12.h>
#include <string>
#include <unordered_map>
namespace node {
using ncrypto::ClearErrorOnReturn;
using ncrypto::ECKeyPointer;
using ncrypto::EVPKeyPointer;
using ncrypto::SSLPointer;
using ncrypto::SSLSessionPointer;
using ncrypto::StackOfX509;
using ncrypto::X509Pointer;
using ncrypto::X509View;
using v8::ArrayBuffer;
using v8::BackingStoreInitializationMode;
using v8::Context;
using v8::EscapableHandleScope;
using v8::Integer;
using v8::Local;
using v8::MaybeLocal;
using v8::Object;
using v8::Undefined;
using v8::Value;
namespace crypto {
SSLSessionPointer GetTLSSession(const unsigned char* buf, size_t length) {
return SSLSessionPointer(d2i_SSL_SESSION(nullptr, &buf, length));
}
MaybeLocal<Value> GetValidationErrorReason(Environment* env, int err) {
auto reason = std::string(X509Pointer::ErrorReason(err).value_or(""));
if (reason == "") return Undefined(env->isolate());
// Suggest --use-system-ca if the error indicates a certificate issue
bool suggest_system_ca =
(err == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE) ||
(err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) ||
((err == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT) &&
!per_process::cli_options->use_system_ca);
if (suggest_system_ca) {
reason.append("; if the root CA is installed locally, "
"try running Node.js with --use-system-ca");
}
return OneByteString(env->isolate(), reason);
}
MaybeLocal<Value> GetValidationErrorCode(Environment* env, int err) {
if (err == 0) return Undefined(env->isolate());
auto error = X509Pointer::ErrorCode(err);
return OneByteString(env->isolate(), error);
}
MaybeLocal<Value> GetCert(Environment* env, const SSLPointer& ssl) {
if (auto cert = ssl.getCertificate()) {
return X509Certificate::toObject(env, cert);
}
return Undefined(env->isolate());
}
namespace {
StackOfX509 CloneSSLCerts(X509Pointer&& cert,
const STACK_OF(X509)* const ssl_certs) {
StackOfX509 peer_certs(sk_X509_new(nullptr));
if (!peer_certs) return StackOfX509();
if (cert && !sk_X509_push(peer_certs.get(), cert.release()))
return StackOfX509();
for (int i = 0; i < sk_X509_num(ssl_certs); i++) {
X509Pointer cert(X509_dup(sk_X509_value(ssl_certs, i)));
if (!cert || !sk_X509_push(peer_certs.get(), cert.get()))
return StackOfX509();
// `cert` is now managed by the stack.
cert.release();
}
return peer_certs;
}
MaybeLocal<Object> AddIssuerChainToObject(X509Pointer* cert,
Local<Object> object,
StackOfX509&& peer_certs,
Environment* const env) {
cert->reset(sk_X509_delete(peer_certs.get(), 0));
for (;;) {
int i;
for (i = 0; i < sk_X509_num(peer_certs.get()); i++) {
X509View ca(sk_X509_value(peer_certs.get(), i));
if (!cert->view().isIssuedBy(ca)) continue;
Local<Value> ca_info;
if (!X509Certificate::toObject(env, ca).ToLocal(&ca_info)) return {};
CHECK(ca_info->IsObject());
if (object
->Set(env->context(),
env->issuercert_string(),
ca_info.As<Object>())
.IsNothing()) {
return {};
}
object = ca_info.As<Object>();
// NOTE: Intentionally freeing cert that is not used anymore.
// Delete cert and continue aggregating issuers.
cert->reset(sk_X509_delete(peer_certs.get(), i));
break;
}
// Issuer not found, break out of the loop.
if (i == sk_X509_num(peer_certs.get()))
break;
}
return MaybeLocal<Object>(object);
}
MaybeLocal<Object> GetLastIssuedCert(
X509Pointer* cert,
const SSLPointer& ssl,
Local<Object> issuer_chain,
Environment* const env) {
Local<Value> ca_info;
while (!cert->view().isIssuedBy(cert->view())) {
auto ca = X509Pointer::IssuerFrom(ssl, cert->view());
if (!ca) break;
if (!X509Certificate::toObject(env, ca.view()).ToLocal(&ca_info)) return {};
CHECK(ca_info->IsObject());
if (issuer_chain
->Set(
env->context(), env->issuercert_string(), ca_info.As<Object>())
.IsNothing()) {
return {};
}
issuer_chain = ca_info.As<Object>();
// For self-signed certificates whose keyUsage field does not include
// keyCertSign, X509_check_issued() will return false. Avoid going into an
// infinite loop by checking if SSL_CTX_get_issuer() returned the same
// certificate.
if (cert->get() == ca.get()) break;
// Delete previous cert and continue aggregating issuers.
*cert = std::move(ca);
}
return MaybeLocal<Object>(issuer_chain);
}
Local<Value> maybeString(Environment* env,
std::optional<std::string_view> value) {
if (!value.has_value()) return Undefined(env->isolate());
return OneByteString(env->isolate(), value.value());
}
} // namespace
MaybeLocal<Object> GetCipherInfo(Environment* env, const SSLPointer& ssl) {
if (ssl.getCipher() == nullptr) return MaybeLocal<Object>();
EscapableHandleScope scope(env->isolate());
Local<Object> info = Object::New(env->isolate());
if (info->Set(env->context(),
env->name_string(),
maybeString(env, ssl.getCipherName()))
.IsNothing() ||
info->Set(env->context(),
env->standard_name_string(),
maybeString(env, ssl.getCipherStandardName()))
.IsNothing() ||
info->Set(env->context(),
env->version_string(),
maybeString(env, ssl.getCipherVersion()))
.IsNothing()) {
return MaybeLocal<Object>();
}
return scope.Escape(info);
}
MaybeLocal<Object> GetEphemeralKey(Environment* env, const SSLPointer& ssl) {
CHECK(!ssl.isServer());
EscapableHandleScope scope(env->isolate());
Local<Object> info = Object::New(env->isolate());
EVPKeyPointer key = ssl.getPeerTempKey();
if (!key) return scope.Escape(info);
Local<Context> context = env->context();
int kid = key.id();
switch (kid) {
case EVP_PKEY_DH:
if (info->Set(context, env->type_string(), env->dh_string())
.IsNothing() ||
info->Set(context,
env->size_string(),
Integer::New(env->isolate(), key.bits()))
.IsNothing()) {
return MaybeLocal<Object>();
}
break;
case EVP_PKEY_EC:
case EVP_PKEY_X25519:
case EVP_PKEY_X448:
{
const char* curve_name;
if (kid == EVP_PKEY_EC) {
int nid = ECKeyPointer::GetGroupName(key);
curve_name = OBJ_nid2sn(nid);
} else {
curve_name = OBJ_nid2sn(kid);
}
if (info->Set(context, env->type_string(), env->ecdh_string())
.IsNothing() ||
info->Set(context,
env->name_string(),
OneByteString(env->isolate(), curve_name))
.IsNothing() ||
info->Set(context,
env->size_string(),
Integer::New(env->isolate(), key.bits()))
.IsNothing()) {
return MaybeLocal<Object>();
}
}
break;
}
return scope.Escape(info);
}
MaybeLocal<Object> ECPointToBuffer(Environment* env,
const EC_GROUP* group,
const EC_POINT* point,
point_conversion_form_t form) {
size_t len = EC_POINT_point2oct(group, point, form, nullptr, 0, nullptr);
if (len == 0) {
THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get public key length");
return MaybeLocal<Object>();
}
auto bs = ArrayBuffer::NewBackingStore(
env->isolate(), len, BackingStoreInitializationMode::kUninitialized);
len = EC_POINT_point2oct(group,
point,
form,
reinterpret_cast<unsigned char*>(bs->Data()),
bs->ByteLength(),
nullptr);
if (len == 0) {
THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get public key");
return MaybeLocal<Object>();
}
Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(bs));
return Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local<Object>());
}
MaybeLocal<Value> GetPeerCert(
Environment* env,
const SSLPointer& ssl,
bool abbreviated,
bool is_server) {
ClearErrorOnReturn clear_error_on_return;
// NOTE: This is because of the odd OpenSSL behavior. On client `cert_chain`
// contains the `peer_certificate`, but on server it doesn't.
X509Pointer cert(is_server ? SSL_get_peer_certificate(ssl.get()) : nullptr);
STACK_OF(X509)* ssl_certs = SSL_get_peer_cert_chain(ssl.get());
if (!cert && (ssl_certs == nullptr || sk_X509_num(ssl_certs) == 0))
return Undefined(env->isolate());
// Short result requested.
if (abbreviated) {
if (cert) {
return X509Certificate::toObject(env, cert.view());
}
return X509Certificate::toObject(env,
X509View(sk_X509_value(ssl_certs, 0)));
}
StackOfX509 peer_certs = CloneSSLCerts(std::move(cert), ssl_certs);
if (peer_certs == nullptr)
return Undefined(env->isolate());
// First and main certificate.
Local<Value> result;
X509View first_cert(sk_X509_value(peer_certs.get(), 0));
CHECK(first_cert);
if (!X509Certificate::toObject(env, first_cert).ToLocal(&result)) return {};
CHECK(result->IsObject());
Local<Object> issuer_chain;
MaybeLocal<Object> maybe_issuer_chain;
maybe_issuer_chain = AddIssuerChainToObject(
&cert, result.As<Object>(), std::move(peer_certs), env);
if (!maybe_issuer_chain.ToLocal(&issuer_chain)) return {};
maybe_issuer_chain =
GetLastIssuedCert(
&cert,
ssl,
issuer_chain,
env);
issuer_chain.Clear();
if (!maybe_issuer_chain.ToLocal(&issuer_chain)) return {};
// Last certificate should be self-signed.
if (cert.view().isIssuedBy(cert.view()) &&
issuer_chain->Set(env->context(), env->issuercert_string(), issuer_chain)
.IsNothing()) {
return {};
}
return result;
}
} // namespace crypto
} // namespace node

View File

@ -0,0 +1,48 @@
#ifndef SRC_CRYPTO_CRYPTO_COMMON_H_
#define SRC_CRYPTO_CRYPTO_COMMON_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
#include "ncrypto.h"
#include "node_crypto.h"
#include "v8.h"
#include <string>
namespace node {
namespace crypto {
ncrypto::SSLSessionPointer GetTLSSession(const unsigned char* buf,
size_t length);
v8::MaybeLocal<v8::Value> GetValidationErrorReason(Environment* env, int err);
v8::MaybeLocal<v8::Value> GetValidationErrorCode(Environment* env, int err);
v8::MaybeLocal<v8::Value> GetCert(Environment* env,
const ncrypto::SSLPointer& ssl);
v8::MaybeLocal<v8::Object> GetCipherInfo(Environment* env,
const ncrypto::SSLPointer& ssl);
v8::MaybeLocal<v8::Object> GetEphemeralKey(Environment* env,
const ncrypto::SSLPointer& ssl);
v8::MaybeLocal<v8::Value> GetPeerCert(Environment* env,
const ncrypto::SSLPointer& ssl,
bool abbreviated = false,
bool is_server = false);
v8::MaybeLocal<v8::Object> ECPointToBuffer(Environment* env,
const EC_GROUP* group,
const EC_POINT* point,
point_conversion_form_t form);
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_COMMON_H_

2104
src/crypto/crypto_context.cc Normal file

File diff suppressed because it is too large Load Diff

171
src/crypto/crypto_context.h Normal file
View File

@ -0,0 +1,171 @@
#ifndef SRC_CRYPTO_CRYPTO_CONTEXT_H_
#define SRC_CRYPTO_CRYPTO_CONTEXT_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "base_object.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "env.h"
#include "memory_tracker.h"
#include "v8.h"
namespace node {
namespace crypto {
// A maxVersion of 0 means "any", but OpenSSL may support TLS versions that
// Node.js doesn't, so pin the max to what we do support.
constexpr int kMaxSupportedVersion = TLS1_3_VERSION;
void GetRootCertificates(
const v8::FunctionCallbackInfo<v8::Value>& args);
X509_STORE* NewRootCertStore();
X509_STORE* GetOrCreateRootCertStore();
ncrypto::BIOPointer LoadBIO(Environment* env, v8::Local<v8::Value> v);
class SecureContext final : public BaseObject {
public:
using GetSessionCb = SSL_SESSION* (*)(SSL*, const unsigned char*, int, int*);
using KeylogCb = void (*)(const SSL*, const char*);
using NewSessionCb = int (*)(SSL*, SSL_SESSION*);
using SelectSNIContextCb = int (*)(SSL*, int*, void*);
~SecureContext() override;
static bool HasInstance(Environment* env, const v8::Local<v8::Value>& value);
static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
Environment* env);
static void Initialize(Environment* env, v8::Local<v8::Object> target);
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);
static SecureContext* Create(Environment* env);
const ncrypto::SSLCtxPointer& ctx() const { return ctx_; }
// Non-const ctx() that allows for non-default initialization of
// the SecureContext.
ncrypto::SSLCtxPointer& ctx() { return ctx_; }
ncrypto::SSLPointer CreateSSL();
void SetGetSessionCallback(GetSessionCb cb);
void SetKeylogCallback(KeylogCb cb);
void SetNewSessionCallback(NewSessionCb cb);
void SetSelectSNIContextCallback(SelectSNIContextCb cb);
inline const ncrypto::X509Pointer& issuer() const { return issuer_; }
inline const ncrypto::X509Pointer& cert() const { return cert_; }
v8::Maybe<void> AddCert(Environment* env, ncrypto::BIOPointer&& bio);
v8::Maybe<void> SetCRL(Environment* env, const ncrypto::BIOPointer& bio);
v8::Maybe<void> UseKey(Environment* env, const KeyObjectData& key);
void SetCACert(const ncrypto::BIOPointer& bio);
void SetRootCerts();
void SetX509StoreFlag(unsigned long flags); // NOLINT(runtime/int)
X509_STORE* GetCertStoreOwnedByThisSecureContext();
// TODO(joyeecheung): track the memory used by OpenSSL types
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(SecureContext)
SET_SELF_SIZE(SecureContext)
static const int kMaxSessionSize = 10 * 1024;
// See TicketKeyCallback
static const int kTicketKeyReturnIndex = 0;
static const int kTicketKeyHMACIndex = 1;
static const int kTicketKeyAESIndex = 2;
static const int kTicketKeyNameIndex = 3;
static const int kTicketKeyIVIndex = 4;
protected:
// OpenSSL structures are opaque. This is sizeof(SSL_CTX) for OpenSSL 1.1.1b:
static const int64_t kExternalSize = 1024;
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Init(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetKey(const v8::FunctionCallbackInfo<v8::Value>& args);
#ifndef OPENSSL_NO_ENGINE
static void SetEngineKey(const v8::FunctionCallbackInfo<v8::Value>& args);
#endif // !OPENSSL_NO_ENGINE
static void SetCert(const v8::FunctionCallbackInfo<v8::Value>& args);
static void AddCACert(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetAllowPartialTrustChain(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void AddCRL(const v8::FunctionCallbackInfo<v8::Value>& args);
static void AddRootCerts(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetCipherSuites(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetCiphers(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetSigalgs(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetECDHCurve(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetDHParam(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetOptions(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetSessionIdContext(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetSessionTimeout(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetMinProto(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetMaxProto(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetMinProto(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetMaxProto(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Close(const v8::FunctionCallbackInfo<v8::Value>& args);
static void LoadPKCS12(const v8::FunctionCallbackInfo<v8::Value>& args);
#ifndef OPENSSL_NO_ENGINE
static void SetClientCertEngine(
const v8::FunctionCallbackInfo<v8::Value>& args);
#endif // !OPENSSL_NO_ENGINE
static void GetTicketKeys(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetTicketKeys(const v8::FunctionCallbackInfo<v8::Value>& args);
static void EnableTicketKeyCallback(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void CtxGetter(const v8::FunctionCallbackInfo<v8::Value>& info);
template <bool primary>
static void GetCertificate(const v8::FunctionCallbackInfo<v8::Value>& args);
static int TicketKeyCallback(SSL* ssl,
unsigned char* name,
unsigned char* iv,
EVP_CIPHER_CTX* ectx,
HMAC_CTX* hctx,
int enc);
static int TicketCompatibilityCallback(SSL* ssl,
unsigned char* name,
unsigned char* iv,
EVP_CIPHER_CTX* ectx,
HMAC_CTX* hctx,
int enc);
SecureContext(Environment* env, v8::Local<v8::Object> wrap);
void Reset();
private:
ncrypto::SSLCtxPointer ctx_;
ncrypto::X509Pointer cert_;
ncrypto::X509Pointer issuer_;
// Non-owning cache for SSL_CTX_get_cert_store(ctx_.get())
X509_STORE* own_cert_store_cache_ = nullptr;
#ifndef OPENSSL_NO_ENGINE
bool client_cert_engine_provided_ = false;
ncrypto::EnginePointer private_key_engine_;
#endif // !OPENSSL_NO_ENGINE
unsigned char ticket_key_name_[16];
unsigned char ticket_key_aes_[16];
unsigned char ticket_key_hmac_[16];
};
int SSL_CTX_use_certificate_chain(SSL_CTX* ctx,
ncrypto::BIOPointer&& in,
ncrypto::X509Pointer* cert,
ncrypto::X509Pointer* issuer);
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_CONTEXT_H_

629
src/crypto/crypto_dh.cc Normal file
View File

@ -0,0 +1,629 @@
#include "crypto/crypto_dh.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "ncrypto.h"
#include "node_errors.h"
#ifndef OPENSSL_IS_BORINGSSL
#include "openssl/bnerr.h"
#endif
#include "openssl/dh.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
namespace node {
using ncrypto::BignumPointer;
using ncrypto::DataPointer;
using ncrypto::DHPointer;
using ncrypto::EVPKeyCtxPointer;
using ncrypto::EVPKeyPointer;
using v8::ArrayBuffer;
using v8::BackingStoreInitializationMode;
using v8::BackingStoreOnFailureMode;
using v8::ConstructorBehavior;
using v8::Context;
using v8::DontDelete;
using v8::FunctionCallback;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Int32;
using v8::Isolate;
using v8::JustVoid;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Nothing;
using v8::Object;
using v8::PropertyAttribute;
using v8::ReadOnly;
using v8::SideEffectType;
using v8::Signature;
using v8::String;
using v8::Value;
namespace crypto {
DiffieHellman::DiffieHellman(Environment* env, Local<Object> wrap, DHPointer dh)
: BaseObject(env, wrap), dh_(std::move(dh)) {
MakeWeak();
}
void DiffieHellman::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackFieldWithSize("dh", dh_ ? kSizeOf_DH : 0);
}
namespace {
MaybeLocal<Value> DataPointerToBuffer(Environment* env, DataPointer&& data) {
struct Flag {
bool secure;
};
#ifdef V8_ENABLE_SANDBOX
auto backing = ArrayBuffer::NewBackingStore(
env->isolate(),
data.size(),
BackingStoreInitializationMode::kUninitialized,
BackingStoreOnFailureMode::kReturnNull);
if (!backing) {
THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
return MaybeLocal<Value>();
}
if (data.size() > 0) {
memcpy(backing->Data(), data.get(), data.size());
}
#else
auto backing = ArrayBuffer::NewBackingStore(
data.get(),
data.size(),
[](void* data, size_t len, void* ptr) {
std::unique_ptr<Flag> flag(static_cast<Flag*>(ptr));
DataPointer free_me(data, len, flag->secure);
},
new Flag{data.isSecure()});
data.release();
#endif // V8_ENABLE_SANDBOX
auto ab = ArrayBuffer::New(env->isolate(), std::move(backing));
return Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local<Value>());
}
void DiffieHellmanGroup(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK_EQ(args.Length(), 1);
THROW_AND_RETURN_IF_NOT_STRING(env, args[0], "Group name");
const node::Utf8Value group_name(env->isolate(), args[0]);
DHPointer dh = DHPointer::FromGroup(group_name.ToStringView());
if (!dh) {
return THROW_ERR_CRYPTO_UNKNOWN_DH_GROUP(env);
}
new DiffieHellman(env, args.This(), std::move(dh));
}
void New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
if (args.Length() != 2) {
return THROW_ERR_MISSING_ARGS(env, "Constructor must have two arguments");
}
if (args[0]->IsInt32()) {
int32_t bits = args[0].As<Int32>()->Value();
if (bits < 2) {
#ifndef OPENSSL_IS_BORINGSSL
#if OPENSSL_VERSION_MAJOR >= 3
ERR_put_error(ERR_LIB_DH, 0, DH_R_MODULUS_TOO_SMALL, __FILE__, __LINE__);
#else
ERR_put_error(ERR_LIB_BN, 0, BN_R_BITS_TOO_SMALL, __FILE__, __LINE__);
#endif // OPENSSL_VERSION_MAJOR >= 3
#else // OPENSSL_IS_BORINGSSL
OPENSSL_PUT_ERROR(BN, BN_R_BITS_TOO_SMALL);
#endif // OPENSSL_IS_BORINGSSL
return ThrowCryptoError(env, ERR_get_error(), "Invalid prime length");
}
// If the first argument is an Int32 then we are generating a new
// prime and then using that to generate the Diffie-Hellman parameters.
// The second argument must be an Int32 as well.
if (!args[1]->IsInt32()) {
return THROW_ERR_INVALID_ARG_TYPE(env,
"Second argument must be an int32");
}
int32_t generator = args[1].As<Int32>()->Value();
if (generator < 2) {
#ifndef OPENSSL_IS_BORINGSSL
ERR_put_error(ERR_LIB_DH, 0, DH_R_BAD_GENERATOR, __FILE__, __LINE__);
#else
OPENSSL_PUT_ERROR(DH, DH_R_BAD_GENERATOR);
#endif
return ThrowCryptoError(env, ERR_get_error(), "Invalid generator");
}
auto dh = DHPointer::New(bits, generator);
if (!dh) {
return THROW_ERR_INVALID_ARG_VALUE(env, "Invalid DH parameters");
}
new DiffieHellman(env, args.This(), std::move(dh));
return;
}
// The first argument must be an ArrayBuffer or ArrayBufferView with the
// prime, and the second argument must be an int32 with the generator
// or an ArrayBuffer or ArrayBufferView with the generator.
ArrayBufferOrViewContents<char> arg0(args[0]);
if (!arg0.CheckSizeInt32()) [[unlikely]]
return THROW_ERR_OUT_OF_RANGE(env, "prime is too big");
BignumPointer bn_p(reinterpret_cast<uint8_t*>(arg0.data()), arg0.size());
BignumPointer bn_g;
if (!bn_p) {
return THROW_ERR_INVALID_ARG_VALUE(env, "Invalid prime");
}
if (args[1]->IsInt32()) {
int32_t generator = args[1].As<Int32>()->Value();
if (generator < 2) {
#ifndef OPENSSL_IS_BORINGSSL
ERR_put_error(ERR_LIB_DH, 0, DH_R_BAD_GENERATOR, __FILE__, __LINE__);
#else
OPENSSL_PUT_ERROR(DH, DH_R_BAD_GENERATOR);
#endif
return ThrowCryptoError(env, ERR_get_error(), "Invalid generator");
}
bn_g = BignumPointer::New();
if (!bn_g.setWord(generator)) {
#ifndef OPENSSL_IS_BORINGSSL
ERR_put_error(ERR_LIB_DH, 0, DH_R_BAD_GENERATOR, __FILE__, __LINE__);
#else
OPENSSL_PUT_ERROR(DH, DH_R_BAD_GENERATOR);
#endif
return ThrowCryptoError(env, ERR_get_error(), "Invalid generator");
}
} else {
ArrayBufferOrViewContents<char> arg1(args[1]);
if (!arg1.CheckSizeInt32()) [[unlikely]]
return THROW_ERR_OUT_OF_RANGE(env, "generator is too big");
bn_g = BignumPointer(reinterpret_cast<uint8_t*>(arg1.data()), arg1.size());
if (!bn_g) {
#ifndef OPENSSL_IS_BORINGSSL
ERR_put_error(ERR_LIB_DH, 0, DH_R_BAD_GENERATOR, __FILE__, __LINE__);
#else
OPENSSL_PUT_ERROR(DH, DH_R_BAD_GENERATOR);
#endif
return ThrowCryptoError(env, ERR_get_error(), "Invalid generator");
}
if (bn_g.getWord() < 2) {
#ifndef OPENSSL_IS_BORINGSSL
ERR_put_error(ERR_LIB_DH, 0, DH_R_BAD_GENERATOR, __FILE__, __LINE__);
#else
OPENSSL_PUT_ERROR(DH, DH_R_BAD_GENERATOR);
#endif
return ThrowCryptoError(env, ERR_get_error(), "Invalid generator");
}
}
auto dh = DHPointer::New(std::move(bn_p), std::move(bn_g));
if (!dh) {
return THROW_ERR_INVALID_ARG_VALUE(env, "Invalid DH parameters");
}
new DiffieHellman(env, args.This(), std::move(dh));
}
void GenerateKeys(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
DiffieHellman* diffieHellman;
ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.This());
DHPointer& dh = *diffieHellman;
auto dp = dh.generateKeys();
if (!dp) {
return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Key generation failed");
}
Local<Value> buffer;
if (DataPointerToBuffer(env, std::move(dp)).ToLocal(&buffer)) {
args.GetReturnValue().Set(buffer);
}
}
void GetPrime(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
DiffieHellman* diffieHellman;
ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.This());
DHPointer& dh = *diffieHellman;
auto dp = dh.getPrime();
if (!dp) {
return THROW_ERR_CRYPTO_INVALID_STATE(env, "p is null");
}
Local<Value> buffer;
if (DataPointerToBuffer(env, std::move(dp)).ToLocal(&buffer)) {
args.GetReturnValue().Set(buffer);
}
}
void GetGenerator(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
DiffieHellman* diffieHellman;
ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.This());
DHPointer& dh = *diffieHellman;
auto dp = dh.getGenerator();
if (!dp) {
return THROW_ERR_CRYPTO_INVALID_STATE(env, "g is null");
}
Local<Value> buffer;
if (DataPointerToBuffer(env, std::move(dp)).ToLocal(&buffer)) {
args.GetReturnValue().Set(buffer);
}
}
void GetPublicKey(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
DiffieHellman* diffieHellman;
ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.This());
DHPointer& dh = *diffieHellman;
auto dp = dh.getPublicKey();
if (!dp) {
return THROW_ERR_CRYPTO_INVALID_STATE(
env, "No public key - did you forget to generate one?");
}
Local<Value> buffer;
if (DataPointerToBuffer(env, std::move(dp)).ToLocal(&buffer)) {
args.GetReturnValue().Set(buffer);
}
}
void GetPrivateKey(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
DiffieHellman* diffieHellman;
ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.This());
DHPointer& dh = *diffieHellman;
auto dp = dh.getPrivateKey();
if (!dp) {
return THROW_ERR_CRYPTO_INVALID_STATE(
env, "No private key - did you forget to generate one?");
}
Local<Value> buffer;
if (DataPointerToBuffer(env, std::move(dp)).ToLocal(&buffer)) {
args.GetReturnValue().Set(buffer);
}
}
void ComputeSecret(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
DiffieHellman* diffieHellman;
ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.This());
DHPointer& dh = *diffieHellman;
CHECK_EQ(args.Length(), 1);
ArrayBufferOrViewContents<unsigned char> key_buf(args[0]);
if (!key_buf.CheckSizeInt32()) [[unlikely]]
return THROW_ERR_OUT_OF_RANGE(env, "secret is too big");
BignumPointer key(key_buf.data(), key_buf.size());
switch (dh.checkPublicKey(key)) {
case DHPointer::CheckPublicKeyResult::INVALID:
// Fall-through
case DHPointer::CheckPublicKeyResult::CHECK_FAILED:
return THROW_ERR_CRYPTO_INVALID_KEYTYPE(env,
"Unspecified validation error");
case DHPointer::CheckPublicKeyResult::TOO_SMALL:
return THROW_ERR_CRYPTO_INVALID_KEYLEN(env, "Supplied key is too small");
case DHPointer::CheckPublicKeyResult::TOO_LARGE:
return THROW_ERR_CRYPTO_INVALID_KEYLEN(env, "Supplied key is too large");
case DHPointer::CheckPublicKeyResult::NONE:
break;
}
auto dp = dh.computeSecret(key);
Local<Value> buffer;
if (DataPointerToBuffer(env, std::move(dp)).ToLocal(&buffer)) {
args.GetReturnValue().Set(buffer);
}
}
void SetPublicKey(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
DiffieHellman* diffieHellman;
ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.This());
DHPointer& dh = *diffieHellman;
CHECK_EQ(args.Length(), 1);
ArrayBufferOrViewContents<unsigned char> buf(args[0]);
if (!buf.CheckSizeInt32()) [[unlikely]]
return THROW_ERR_OUT_OF_RANGE(env, "buf is too big");
BignumPointer num(buf.data(), buf.size());
CHECK(num);
CHECK(dh.setPublicKey(std::move(num)));
}
void SetPrivateKey(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
DiffieHellman* diffieHellman;
ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.This());
DHPointer& dh = *diffieHellman;
CHECK_EQ(args.Length(), 1);
ArrayBufferOrViewContents<unsigned char> buf(args[0]);
if (!buf.CheckSizeInt32()) [[unlikely]]
return THROW_ERR_OUT_OF_RANGE(env, "buf is too big");
BignumPointer num(buf.data(), buf.size());
CHECK(num);
CHECK(dh.setPrivateKey(std::move(num)));
}
void Check(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
DiffieHellman* diffieHellman;
ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.This());
DHPointer& dh = *diffieHellman;
auto result = dh.check();
if (result == DHPointer::CheckResult::CHECK_FAILED) {
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Checking DH parameters failed");
}
args.GetReturnValue().Set(static_cast<int>(result));
}
} // namespace
// The input arguments to DhKeyPairGenJob can vary
// 1. CryptoJobMode
// and either
// 2. Group name (as a string)
// or
// 2. Prime or Prime Length
// 3. Generator
// Followed by the public and private key encoding parameters:
// * Public format
// * Public type
// * Private format
// * Private type
// * Cipher
// * Passphrase
Maybe<void> DhKeyGenTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int* offset,
DhKeyPairGenConfig* params) {
Environment* env = Environment::GetCurrent(args);
if (args[*offset]->IsString()) {
Utf8Value group_name(env->isolate(), args[*offset]);
auto group = DHPointer::FindGroup(group_name.ToStringView());
if (!group) {
THROW_ERR_CRYPTO_UNKNOWN_DH_GROUP(env);
return Nothing<void>();
}
static constexpr int kStandardizedGenerator = 2;
params->params.prime = std::move(group);
params->params.generator = kStandardizedGenerator;
*offset += 1;
} else {
if (args[*offset]->IsInt32()) {
int size = args[*offset].As<Int32>()->Value();
if (size < 0) {
THROW_ERR_OUT_OF_RANGE(env, "Invalid prime size");
return Nothing<void>();
}
params->params.prime = size;
} else {
ArrayBufferOrViewContents<unsigned char> input(args[*offset]);
if (!input.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "prime is too big");
return Nothing<void>();
}
params->params.prime = BignumPointer(input.data(), input.size());
}
CHECK(args[*offset + 1]->IsInt32());
params->params.generator = args[*offset + 1].As<Int32>()->Value();
*offset += 2;
}
return JustVoid();
}
EVPKeyCtxPointer DhKeyGenTraits::Setup(DhKeyPairGenConfig* params) {
EVPKeyPointer key_params;
if (BignumPointer* prime_fixed_value =
std::get_if<BignumPointer>(&params->params.prime)) {
auto prime = prime_fixed_value->clone();
auto bn_g = BignumPointer::New();
if (!prime || !bn_g || !bn_g.setWord(params->params.generator)) {
return {};
}
auto dh = DHPointer::New(std::move(prime), std::move(bn_g));
if (!dh) return {};
key_params = EVPKeyPointer::NewDH(std::move(dh));
} else if (std::get_if<int>(&params->params.prime)) {
auto param_ctx = EVPKeyCtxPointer::NewFromID(EVP_PKEY_DH);
#ifndef OPENSSL_IS_BORINGSSL
int* prime_size = std::get_if<int>(&params->params.prime);
if (!param_ctx.initForParamgen() ||
!param_ctx.setDhParameters(*prime_size, params->params.generator)) {
return {};
}
key_params = param_ctx.paramgen();
#else
return {};
#endif
} else {
UNREACHABLE();
}
if (!key_params) return {};
EVPKeyCtxPointer ctx = key_params.newCtx();
if (!ctx.initForKeygen()) return {};
return ctx;
}
Maybe<void> DHKeyExportTraits::AdditionalConfig(
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
DHKeyExportConfig* params) {
return JustVoid();
}
WebCryptoKeyExportStatus DHKeyExportTraits::DoExport(
const KeyObjectData& key_data,
WebCryptoKeyFormat format,
const DHKeyExportConfig& params,
ByteSource* out) {
CHECK_NE(key_data.GetKeyType(), kKeyTypeSecret);
switch (format) {
case kWebCryptoKeyFormatPKCS8:
if (key_data.GetKeyType() != kKeyTypePrivate)
return WebCryptoKeyExportStatus::INVALID_KEY_TYPE;
return PKEY_PKCS8_Export(key_data, out);
case kWebCryptoKeyFormatSPKI:
if (key_data.GetKeyType() != kKeyTypePublic)
return WebCryptoKeyExportStatus::INVALID_KEY_TYPE;
return PKEY_SPKI_Export(key_data, out);
default:
UNREACHABLE();
}
}
Maybe<void> DHBitsTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
DHBitsConfig* params) {
CHECK(args[offset]->IsObject()); // public key
CHECK(args[offset + 1]->IsObject()); // private key
KeyObjectHandle* private_key;
KeyObjectHandle* public_key;
ASSIGN_OR_RETURN_UNWRAP(&public_key, args[offset], Nothing<void>());
ASSIGN_OR_RETURN_UNWRAP(&private_key, args[offset + 1], Nothing<void>());
CHECK(private_key->Data().GetKeyType() == kKeyTypePrivate);
CHECK(public_key->Data().GetKeyType() != kKeyTypeSecret);
params->public_key = public_key->Data().addRef();
params->private_key = private_key->Data().addRef();
return JustVoid();
}
MaybeLocal<Value> DHBitsTraits::EncodeOutput(Environment* env,
const DHBitsConfig& params,
ByteSource* out) {
return out->ToArrayBuffer(env);
}
bool DHBitsTraits::DeriveBits(Environment* env,
const DHBitsConfig& params,
ByteSource* out,
CryptoJobMode mode) {
auto dp = DHPointer::stateless(params.private_key.GetAsymmetricKey(),
params.public_key.GetAsymmetricKey());
if (!dp) {
bool can_throw = mode == CryptoJobMode::kCryptoJobSync;
if (can_throw) {
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
if (err) ThrowCryptoError(env, err, "diffieHellman failed");
}
return false;
}
*out = ByteSource::Allocated(dp.release());
CHECK(!out->empty());
return true;
}
bool GetDhKeyDetail(Environment* env,
const KeyObjectData& key,
Local<Object> target) {
CHECK_EQ(key.GetAsymmetricKey().id(), EVP_PKEY_DH);
return true;
}
void DiffieHellman::Initialize(Environment* env, Local<Object> target) {
Isolate* isolate = env->isolate();
Local<Context> context = env->context();
auto make = [&](Local<String> name, FunctionCallback callback) {
Local<FunctionTemplate> t = NewFunctionTemplate(isolate, callback);
const PropertyAttribute attributes =
static_cast<PropertyAttribute>(ReadOnly | DontDelete);
t->InstanceTemplate()->SetInternalFieldCount(
DiffieHellman::kInternalFieldCount);
SetProtoMethod(isolate, t, "generateKeys", GenerateKeys);
SetProtoMethod(isolate, t, "computeSecret", ComputeSecret);
SetProtoMethodNoSideEffect(isolate, t, "getPrime", GetPrime);
SetProtoMethodNoSideEffect(isolate, t, "getGenerator", GetGenerator);
SetProtoMethodNoSideEffect(isolate, t, "getPublicKey", GetPublicKey);
SetProtoMethodNoSideEffect(isolate, t, "getPrivateKey", GetPrivateKey);
SetProtoMethod(isolate, t, "setPublicKey", SetPublicKey);
SetProtoMethod(isolate, t, "setPrivateKey", SetPrivateKey);
Local<FunctionTemplate> verify_error_getter_templ =
FunctionTemplate::New(isolate,
Check,
Local<Value>(),
Signature::New(env->isolate(), t),
/* length */ 0,
ConstructorBehavior::kThrow,
SideEffectType::kHasNoSideEffect);
t->InstanceTemplate()->SetAccessorProperty(env->verify_error_string(),
verify_error_getter_templ,
Local<FunctionTemplate>(),
attributes);
SetConstructorFunction(context, target, name, t);
};
make(FIXED_ONE_BYTE_STRING(env->isolate(), "DiffieHellman"), New);
make(FIXED_ONE_BYTE_STRING(env->isolate(), "DiffieHellmanGroup"),
DiffieHellmanGroup);
DHKeyPairGenJob::Initialize(env, target);
DHKeyExportJob::Initialize(env, target);
DHBitsJob::Initialize(env, target);
}
void DiffieHellman::RegisterExternalReferences(
ExternalReferenceRegistry* registry) {
registry->Register(New);
registry->Register(DiffieHellmanGroup);
registry->Register(GenerateKeys);
registry->Register(ComputeSecret);
registry->Register(GetPrime);
registry->Register(GetGenerator);
registry->Register(GetPublicKey);
registry->Register(GetPrivateKey);
registry->Register(SetPublicKey);
registry->Register(SetPrivateKey);
registry->Register(Check);
DHKeyPairGenJob::RegisterExternalReferences(registry);
DHKeyExportJob::RegisterExternalReferences(registry);
DHBitsJob::RegisterExternalReferences(registry);
}
} // namespace crypto
} // namespace node

126
src/crypto/crypto_dh.h Normal file
View File

@ -0,0 +1,126 @@
#ifndef SRC_CRYPTO_CRYPTO_DH_H_
#define SRC_CRYPTO_CRYPTO_DH_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_keys.h"
#include "crypto/crypto_keygen.h"
#include "crypto/crypto_util.h"
#include "env.h"
#include "memory_tracker.h"
#include "v8.h"
#include <variant>
namespace node {
namespace crypto {
class DiffieHellman final : public BaseObject {
public:
static void Initialize(Environment* env, v8::Local<v8::Object> target);
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);
DiffieHellman(Environment* env,
v8::Local<v8::Object> wrap,
ncrypto::DHPointer dh);
operator ncrypto::DHPointer&() { return dh_; }
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(DiffieHellman)
SET_SELF_SIZE(DiffieHellman)
private:
ncrypto::DHPointer dh_;
};
struct DhKeyPairParams final : public MemoryRetainer {
// Diffie-Hellman can either generate keys using a fixed prime, or by first
// generating a random prime of a given size (in bits). Only one of both
// options may be specified.
std::variant<ncrypto::BignumPointer, int> prime;
unsigned int generator;
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(DhKeyPairParams)
SET_SELF_SIZE(DhKeyPairParams)
};
using DhKeyPairGenConfig = KeyPairGenConfig<DhKeyPairParams>;
struct DhKeyGenTraits final {
using AdditionalParameters = DhKeyPairGenConfig;
static constexpr const char* JobName = "DhKeyPairGenJob";
static ncrypto::EVPKeyCtxPointer Setup(DhKeyPairGenConfig* params);
static v8::Maybe<void> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int* offset,
DhKeyPairGenConfig* params);
};
using DHKeyPairGenJob = KeyGenJob<KeyPairGenTraits<DhKeyGenTraits>>;
struct DHKeyExportConfig final : public MemoryRetainer {
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(DHKeyExportConfig)
SET_SELF_SIZE(DHKeyExportConfig)
};
struct DHKeyExportTraits final {
static constexpr const char* JobName = "DHKeyExportJob";
using AdditionalParameters = DHKeyExportConfig;
static v8::Maybe<void> AdditionalConfig(
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
DHKeyExportConfig* config);
static WebCryptoKeyExportStatus DoExport(const KeyObjectData& key_data,
WebCryptoKeyFormat format,
const DHKeyExportConfig& params,
ByteSource* out);
};
using DHKeyExportJob = KeyExportJob<DHKeyExportTraits>;
struct DHBitsConfig final : public MemoryRetainer {
KeyObjectData private_key;
KeyObjectData public_key;
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(DHBitsConfig)
SET_SELF_SIZE(DHBitsConfig)
};
struct DHBitsTraits final {
using AdditionalParameters = DHBitsConfig;
static constexpr const char* JobName = "DHBitsJob";
static constexpr AsyncWrap::ProviderType Provider =
AsyncWrap::PROVIDER_DERIVEBITSREQUEST;
static v8::Maybe<void> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
DHBitsConfig* params);
static bool DeriveBits(Environment* env,
const DHBitsConfig& params,
ByteSource* out_,
CryptoJobMode mode);
static v8::MaybeLocal<v8::Value> EncodeOutput(Environment* env,
const DHBitsConfig& params,
ByteSource* out);
};
using DHBitsJob = DeriveBitsJob<DHBitsTraits>;
bool GetDhKeyDetail(Environment* env,
const KeyObjectData& key,
v8::Local<v8::Object> target);
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_DH_H_

144
src/crypto/crypto_dsa.cc Normal file
View File

@ -0,0 +1,144 @@
#include "crypto/crypto_dsa.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "async_wrap-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
#include <openssl/bn.h>
#include <openssl/dsa.h>
#include <cstdio>
namespace node {
using ncrypto::Dsa;
using ncrypto::EVPKeyCtxPointer;
using v8::FunctionCallbackInfo;
using v8::Int32;
using v8::JustVoid;
using v8::Local;
using v8::Maybe;
using v8::Number;
using v8::Object;
using v8::Uint32;
using v8::Value;
namespace crypto {
EVPKeyCtxPointer DsaKeyGenTraits::Setup(DsaKeyPairGenConfig* params) {
auto param_ctx = EVPKeyCtxPointer::NewFromID(EVP_PKEY_DSA);
if (!param_ctx || !param_ctx.initForParamgen() ||
!param_ctx.setDsaParameters(
params->params.modulus_bits,
params->params.divisor_bits != -1
? std::optional<int>(params->params.divisor_bits)
: std::nullopt)) {
return {};
}
auto key_params = param_ctx.paramgen();
if (!key_params) return {};
EVPKeyCtxPointer key_ctx = key_params.newCtx();
if (!key_ctx.initForKeygen()) return {};
return key_ctx;
}
// Input arguments for DsaKeyPairGenJob
// 1. CryptoJobMode
// 2. Modulus Bits
// 3. Divisor Bits
// 4. Public Format
// 5. Public Type
// 6. Private Format
// 7. Private Type
// 8. Cipher
// 9. Passphrase
Maybe<void> DsaKeyGenTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int* offset,
DsaKeyPairGenConfig* params) {
CHECK(args[*offset]->IsUint32()); // modulus bits
CHECK(args[*offset + 1]->IsInt32()); // divisor bits
params->params.modulus_bits = args[*offset].As<Uint32>()->Value();
params->params.divisor_bits = args[*offset + 1].As<Int32>()->Value();
CHECK_GE(params->params.divisor_bits, -1);
*offset += 2;
return JustVoid();
}
Maybe<void> DSAKeyExportTraits::AdditionalConfig(
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
DSAKeyExportConfig* params) {
return JustVoid();
}
WebCryptoKeyExportStatus DSAKeyExportTraits::DoExport(
const KeyObjectData& key_data,
WebCryptoKeyFormat format,
const DSAKeyExportConfig& params,
ByteSource* out) {
CHECK_NE(key_data.GetKeyType(), kKeyTypeSecret);
switch (format) {
case kWebCryptoKeyFormatRaw:
// Not supported for RSA keys of either type
return WebCryptoKeyExportStatus::FAILED;
case kWebCryptoKeyFormatPKCS8:
if (key_data.GetKeyType() != kKeyTypePrivate)
return WebCryptoKeyExportStatus::INVALID_KEY_TYPE;
return PKEY_PKCS8_Export(key_data, out);
case kWebCryptoKeyFormatSPKI:
if (key_data.GetKeyType() != kKeyTypePublic)
return WebCryptoKeyExportStatus::INVALID_KEY_TYPE;
return PKEY_SPKI_Export(key_data, out);
default:
UNREACHABLE();
}
}
bool GetDsaKeyDetail(Environment* env,
const KeyObjectData& key,
Local<Object> target) {
if (!key) return false;
Dsa dsa = key.GetAsymmetricKey();
if (!dsa) return false;
size_t modulus_length = dsa.getModulusLength();
size_t divisor_length = dsa.getDivisorLength();
return target
->Set(env->context(),
env->modulus_length_string(),
Number::New(env->isolate(),
static_cast<double>(modulus_length)))
.IsJust() &&
target
->Set(env->context(),
env->divisor_length_string(),
Number::New(env->isolate(),
static_cast<double>(divisor_length)))
.IsJust();
}
namespace DSAAlg {
void Initialize(Environment* env, Local<Object> target) {
DsaKeyPairGenJob::Initialize(env, target);
DSAKeyExportJob::Initialize(env, target);
}
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
DsaKeyPairGenJob::RegisterExternalReferences(registry);
DSAKeyExportJob::RegisterExternalReferences(registry);
}
} // namespace DSAAlg
} // namespace crypto
} // namespace node

73
src/crypto/crypto_dsa.h Normal file
View File

@ -0,0 +1,73 @@
#ifndef SRC_CRYPTO_CRYPTO_DSA_H_
#define SRC_CRYPTO_CRYPTO_DSA_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_keys.h"
#include "crypto/crypto_keygen.h"
#include "crypto/crypto_util.h"
#include "env.h"
#include "memory_tracker.h"
#include "v8.h"
namespace node::crypto {
struct DsaKeyPairParams final : public MemoryRetainer {
unsigned int modulus_bits;
int divisor_bits;
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(DsaKeyPairParams)
SET_SELF_SIZE(DsaKeyPairParams)
};
using DsaKeyPairGenConfig = KeyPairGenConfig<DsaKeyPairParams>;
struct DsaKeyGenTraits final {
using AdditionalParameters = DsaKeyPairGenConfig;
static constexpr const char* JobName = "DsaKeyPairGenJob";
static ncrypto::EVPKeyCtxPointer Setup(DsaKeyPairGenConfig* params);
static v8::Maybe<void> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int* offset,
DsaKeyPairGenConfig* params);
};
using DsaKeyPairGenJob = KeyGenJob<KeyPairGenTraits<DsaKeyGenTraits>>;
struct DSAKeyExportConfig final : public MemoryRetainer {
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(DSAKeyExportConfig)
SET_SELF_SIZE(DSAKeyExportConfig)
};
struct DSAKeyExportTraits final {
static constexpr const char* JobName = "DSAKeyExportJob";
using AdditionalParameters = DSAKeyExportConfig;
static v8::Maybe<void> AdditionalConfig(
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
DSAKeyExportConfig* config);
static WebCryptoKeyExportStatus DoExport(const KeyObjectData& key_data,
WebCryptoKeyFormat format,
const DSAKeyExportConfig& params,
ByteSource* out);
};
using DSAKeyExportJob = KeyExportJob<DSAKeyExportTraits>;
bool GetDsaKeyDetail(Environment* env,
const KeyObjectData& key,
v8::Local<v8::Object> target);
namespace DSAAlg {
void Initialize(Environment* env, v8::Local<v8::Object> target);
void RegisterExternalReferences(ExternalReferenceRegistry* registry);
} // namespace DSAAlg
} // namespace node::crypto
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_DSA_H_

919
src/crypto/crypto_ec.cc Normal file
View File

@ -0,0 +1,919 @@
#include "crypto/crypto_ec.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "crypto/crypto_common.h"
#include "crypto/crypto_util.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "node_buffer.h"
#include "string_bytes.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
#include <openssl/bn.h>
#include <openssl/ec.h>
#include <openssl/ecdh.h>
#include <algorithm>
namespace node {
using ncrypto::BignumPointer;
using ncrypto::DataPointer;
using ncrypto::Ec;
using ncrypto::ECGroupPointer;
using ncrypto::ECKeyPointer;
using ncrypto::ECPointPointer;
using ncrypto::EVPKeyCtxPointer;
using ncrypto::EVPKeyPointer;
using ncrypto::MarkPopErrorOnReturn;
using v8::Array;
using v8::ArrayBuffer;
using v8::BackingStoreInitializationMode;
using v8::Context;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Int32;
using v8::Isolate;
using v8::JustVoid;
using v8::Local;
using v8::LocalVector;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Nothing;
using v8::Object;
using v8::String;
using v8::Uint32;
using v8::Value;
namespace crypto {
void ECDH::Initialize(Environment* env, Local<Object> target) {
Isolate* isolate = env->isolate();
Local<Context> context = env->context();
Local<FunctionTemplate> t = NewFunctionTemplate(isolate, New);
t->InstanceTemplate()->SetInternalFieldCount(ECDH::kInternalFieldCount);
SetProtoMethod(isolate, t, "generateKeys", GenerateKeys);
SetProtoMethod(isolate, t, "computeSecret", ComputeSecret);
SetProtoMethodNoSideEffect(isolate, t, "getPublicKey", GetPublicKey);
SetProtoMethodNoSideEffect(isolate, t, "getPrivateKey", GetPrivateKey);
SetProtoMethod(isolate, t, "setPublicKey", SetPublicKey);
SetProtoMethod(isolate, t, "setPrivateKey", SetPrivateKey);
SetConstructorFunction(context, target, "ECDH", t);
SetMethodNoSideEffect(context, target, "ECDHConvertKey", ECDH::ConvertKey);
SetMethodNoSideEffect(context, target, "getCurves", ECDH::GetCurves);
ECDHBitsJob::Initialize(env, target);
ECKeyPairGenJob::Initialize(env, target);
ECKeyExportJob::Initialize(env, target);
NODE_DEFINE_CONSTANT(target, OPENSSL_EC_NAMED_CURVE);
NODE_DEFINE_CONSTANT(target, OPENSSL_EC_EXPLICIT_CURVE);
}
void ECDH::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(New);
registry->Register(GenerateKeys);
registry->Register(ComputeSecret);
registry->Register(GetPublicKey);
registry->Register(GetPrivateKey);
registry->Register(SetPublicKey);
registry->Register(SetPrivateKey);
registry->Register(ECDH::ConvertKey);
registry->Register(ECDH::GetCurves);
ECDHBitsJob::RegisterExternalReferences(registry);
ECKeyPairGenJob::RegisterExternalReferences(registry);
ECKeyExportJob::RegisterExternalReferences(registry);
}
void ECDH::GetCurves(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
LocalVector<Value> arr(env->isolate());
Ec::GetCurves([&](std::string_view curve) -> bool {
arr.push_back(OneByteString(env->isolate(), curve));
return true;
});
args.GetReturnValue().Set(Array::New(env->isolate(), arr.data(), arr.size()));
}
ECDH::ECDH(Environment* env, Local<Object> wrap, ECKeyPointer&& key)
: BaseObject(env, wrap), key_(std::move(key)), group_(key_.getGroup()) {
MakeWeak();
CHECK_NOT_NULL(group_);
}
void ECDH::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackFieldWithSize("key", key_ ? kSizeOf_EC_KEY : 0);
}
void ECDH::New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
MarkPopErrorOnReturn mark_pop_error_on_return;
// TODO(indutny): Support raw curves?
CHECK(args[0]->IsString());
node::Utf8Value curve(env->isolate(), args[0]);
int nid = OBJ_sn2nid(*curve);
if (nid == NID_undef)
return THROW_ERR_CRYPTO_INVALID_CURVE(env);
auto key = ECKeyPointer::NewByCurveName(nid);
if (!key)
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to create key using named curve");
new ECDH(env, args.This(), std::move(key));
}
void ECDH::GenerateKeys(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
ECDH* ecdh;
ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.This());
if (!ecdh->key_.generate()) {
return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to generate key");
}
}
ECPointPointer ECDH::BufferToPoint(Environment* env,
const EC_GROUP* group,
Local<Value> buf) {
ArrayBufferOrViewContents<unsigned char> input(buf);
if (!input.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "buffer is too big");
return {};
}
auto pub = ECPointPointer::New(group);
if (!pub) {
THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to allocate EC_POINT for a public key");
return pub;
}
ncrypto::Buffer<const unsigned char> buffer{
.data = input.data(),
.len = input.size(),
};
if (!pub.setFromBuffer(buffer, group)) {
return {};
}
return pub;
}
void ECDH::ComputeSecret(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(IsAnyBufferSource(args[0]));
ECDH* ecdh;
ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.This());
MarkPopErrorOnReturn mark_pop_error_on_return;
if (!ecdh->IsKeyPairValid())
return THROW_ERR_CRYPTO_INVALID_KEYPAIR(env);
auto pub = ECDH::BufferToPoint(env, ecdh->group_, args[0]);
if (!pub) {
args.GetReturnValue().Set(
FIXED_ONE_BYTE_STRING(env->isolate(),
"ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY"));
return;
}
int field_size = EC_GROUP_get_degree(ecdh->group_);
size_t out_len = (field_size + 7) / 8;
auto bs = ArrayBuffer::NewBackingStore(
env->isolate(), out_len, BackingStoreInitializationMode::kUninitialized);
if (!ECDH_compute_key(
bs->Data(), bs->ByteLength(), pub, ecdh->key_.get(), nullptr))
return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to compute ECDH key");
Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(bs));
Local<Value> buffer;
if (!Buffer::New(env, ab, 0, ab->ByteLength()).ToLocal(&buffer)) return;
args.GetReturnValue().Set(buffer);
}
void ECDH::GetPublicKey(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
// Conversion form
CHECK_EQ(args.Length(), 1);
ECDH* ecdh;
ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.This());
const auto group = ecdh->key_.getGroup();
const auto pub = ecdh->key_.getPublicKey();
if (pub == nullptr)
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to get ECDH public key");
CHECK(args[0]->IsUint32());
uint32_t val = args[0].As<Uint32>()->Value();
point_conversion_form_t form = static_cast<point_conversion_form_t>(val);
Local<Object> buf;
if (ECPointToBuffer(env, group, pub, form).ToLocal(&buf)) {
args.GetReturnValue().Set(buf);
}
}
void ECDH::GetPrivateKey(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
ECDH* ecdh;
ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.This());
auto b = ecdh->key_.getPrivateKey();
if (b == nullptr)
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to get ECDH private key");
auto bs = ArrayBuffer::NewBackingStore(
env->isolate(),
BignumPointer::GetByteCount(b),
BackingStoreInitializationMode::kUninitialized);
CHECK_EQ(bs->ByteLength(),
BignumPointer::EncodePaddedInto(
b, static_cast<unsigned char*>(bs->Data()), bs->ByteLength()));
Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(bs));
Local<Value> buffer;
if (!Buffer::New(env, ab, 0, ab->ByteLength()).ToLocal(&buffer)) return;
args.GetReturnValue().Set(buffer);
}
void ECDH::SetPrivateKey(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
ECDH* ecdh;
ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.This());
ArrayBufferOrViewContents<unsigned char> priv_buffer(args[0]);
if (!priv_buffer.CheckSizeInt32()) [[unlikely]]
return THROW_ERR_OUT_OF_RANGE(env, "key is too big");
BignumPointer priv(priv_buffer.data(), priv_buffer.size());
if (!priv) {
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to convert Buffer to BN");
}
if (!ecdh->IsKeyValidForCurve(priv)) {
return THROW_ERR_CRYPTO_INVALID_KEYTYPE(env,
"Private key is not valid for specified curve.");
}
auto new_key = ecdh->key_.clone();
CHECK(new_key);
bool result = new_key.setPrivateKey(priv);
priv.reset();
if (!result) {
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to convert BN to a private key");
}
MarkPopErrorOnReturn mark_pop_error_on_return;
USE(&mark_pop_error_on_return);
auto priv_key = new_key.getPrivateKey();
CHECK_NOT_NULL(priv_key);
auto pub = ECPointPointer::New(ecdh->group_);
CHECK(pub);
if (!pub.mul(ecdh->group_, priv_key)) {
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to generate ECDH public key");
}
if (!new_key.setPublicKey(pub)) {
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to set generated public key");
}
ecdh->key_ = std::move(new_key);
ecdh->group_ = ecdh->key_.getGroup();
}
void ECDH::SetPublicKey(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
ECDH* ecdh;
ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.This());
CHECK(IsAnyBufferSource(args[0]));
MarkPopErrorOnReturn mark_pop_error_on_return;
auto pub = ECDH::BufferToPoint(env, ecdh->group_, args[0]);
if (!pub) {
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to convert Buffer to EC_POINT");
}
if (!ecdh->key_.setPublicKey(pub)) {
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to set EC_POINT as the public key");
}
}
bool ECDH::IsKeyValidForCurve(const BignumPointer& private_key) {
CHECK(group_);
CHECK(private_key);
// Private keys must be in the range [1, n-1].
// Ref: Section 3.2.1 - http://www.secg.org/sec1-v2.pdf
if (private_key < BignumPointer::One()) {
return false;
}
auto order = BignumPointer::New();
CHECK(order);
return EC_GROUP_get_order(group_, order.get(), nullptr) &&
private_key < order;
}
bool ECDH::IsKeyPairValid() {
MarkPopErrorOnReturn mark_pop_error_on_return;
return key_.checkKey();
}
// Convert the input public key to compressed, uncompressed, or hybrid formats.
void ECDH::ConvertKey(const FunctionCallbackInfo<Value>& args) {
MarkPopErrorOnReturn mark_pop_error_on_return;
Environment* env = Environment::GetCurrent(args);
CHECK_EQ(args.Length(), 3);
CHECK(IsAnyBufferSource(args[0]));
ArrayBufferOrViewContents<char> args0(args[0]);
if (!args0.CheckSizeInt32()) [[unlikely]]
return THROW_ERR_OUT_OF_RANGE(env, "key is too big");
if (args0.empty()) return args.GetReturnValue().SetEmptyString();
node::Utf8Value curve(env->isolate(), args[1]);
int nid = OBJ_sn2nid(*curve);
if (nid == NID_undef)
return THROW_ERR_CRYPTO_INVALID_CURVE(env);
auto group = ECGroupPointer::NewByCurveName(nid);
if (!group)
return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get EC_GROUP");
auto pub = ECDH::BufferToPoint(env, group, args[0]);
if (!pub) {
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to convert Buffer to EC_POINT");
}
CHECK(args[2]->IsUint32());
uint32_t val = args[2].As<Uint32>()->Value();
point_conversion_form_t form = static_cast<point_conversion_form_t>(val);
Local<Object> buf;
if (ECPointToBuffer(env, group, pub, form).ToLocal(&buf)) {
args.GetReturnValue().Set(buf);
}
}
void ECDHBitsConfig::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("public", public_);
tracker->TrackField("private", private_);
}
MaybeLocal<Value> ECDHBitsTraits::EncodeOutput(Environment* env,
const ECDHBitsConfig& params,
ByteSource* out) {
return out->ToArrayBuffer(env);
}
Maybe<void> ECDHBitsTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
ECDHBitsConfig* params) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[offset]->IsObject()); // public key
CHECK(args[offset + 1]->IsObject()); // private key
KeyObjectHandle* private_key;
KeyObjectHandle* public_key;
ASSIGN_OR_RETURN_UNWRAP(&public_key, args[offset], Nothing<void>());
ASSIGN_OR_RETURN_UNWRAP(&private_key, args[offset + 1], Nothing<void>());
if (private_key->Data().GetKeyType() != kKeyTypePrivate ||
public_key->Data().GetKeyType() != kKeyTypePublic) {
THROW_ERR_CRYPTO_INVALID_KEYTYPE(env);
return Nothing<void>();
}
params->private_ = private_key->Data().addRef();
params->public_ = public_key->Data().addRef();
return JustVoid();
}
bool ECDHBitsTraits::DeriveBits(Environment* env,
const ECDHBitsConfig& params,
ByteSource* out,
CryptoJobMode mode) {
size_t len = 0;
const auto& m_privkey = params.private_.GetAsymmetricKey();
const auto& m_pubkey = params.public_.GetAsymmetricKey();
switch (m_privkey.id()) {
case EVP_PKEY_X25519:
// Fall through
case EVP_PKEY_X448: {
Mutex::ScopedLock pub_lock(params.public_.mutex());
EVPKeyCtxPointer ctx = m_privkey.newCtx();
if (!ctx.initForDerive(m_pubkey)) return false;
auto data = ctx.derive();
if (!data) return false;
DCHECK(!data.isSecure());
*out = ByteSource::Allocated(data.release());
break;
}
default: {
const EC_KEY* private_key;
{
Mutex::ScopedLock priv_lock(params.private_.mutex());
private_key = m_privkey;
}
Mutex::ScopedLock pub_lock(params.public_.mutex());
const EC_KEY* public_key = m_pubkey;
const auto group = ECKeyPointer::GetGroup(private_key);
if (group == nullptr)
return false;
CHECK(ECKeyPointer::Check(private_key));
CHECK(ECKeyPointer::Check(public_key));
const auto pub = ECKeyPointer::GetPublicKey(public_key);
int field_size = EC_GROUP_get_degree(group);
len = (field_size + 7) / 8;
auto buf = DataPointer::Alloc(len);
CHECK_NOT_NULL(pub);
CHECK_NOT_NULL(private_key);
if (ECDH_compute_key(
static_cast<char*>(buf.get()), len, pub, private_key, nullptr) <=
0) {
return false;
}
*out = ByteSource::Allocated(buf.release());
}
}
return true;
}
EVPKeyCtxPointer EcKeyGenTraits::Setup(EcKeyPairGenConfig* params) {
EVPKeyCtxPointer key_ctx;
switch (params->params.curve_nid) {
case EVP_PKEY_ED25519:
// Fall through
case EVP_PKEY_ED448:
// Fall through
case EVP_PKEY_X25519:
// Fall through
case EVP_PKEY_X448:
key_ctx = EVPKeyCtxPointer::NewFromID(params->params.curve_nid);
break;
default: {
auto param_ctx = EVPKeyCtxPointer::NewFromID(EVP_PKEY_EC);
if (!param_ctx.initForParamgen() ||
!param_ctx.setEcParameters(params->params.curve_nid,
params->params.param_encoding)) {
return {};
}
auto key_params = param_ctx.paramgen();
if (!key_params) return {};
key_ctx = key_params.newCtx();
}
}
if (!key_ctx.initForKeygen()) return {};
return key_ctx;
}
// EcKeyPairGenJob input arguments
// 1. CryptoJobMode
// 2. Curve Name
// 3. Param Encoding
// 4. Public Format
// 5. Public Type
// 6. Private Format
// 7. Private Type
// 8. Cipher
// 9. Passphrase
Maybe<void> EcKeyGenTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int* offset,
EcKeyPairGenConfig* params) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[*offset]->IsString()); // curve name
CHECK(args[*offset + 1]->IsInt32()); // param encoding
Utf8Value curve_name(env->isolate(), args[*offset]);
params->params.curve_nid = Ec::GetCurveIdFromName(*curve_name);
if (params->params.curve_nid == NID_undef) {
THROW_ERR_CRYPTO_INVALID_CURVE(env);
return Nothing<void>();
}
params->params.param_encoding = args[*offset + 1].As<Int32>()->Value();
if (params->params.param_encoding != OPENSSL_EC_NAMED_CURVE &&
params->params.param_encoding != OPENSSL_EC_EXPLICIT_CURVE) {
THROW_ERR_OUT_OF_RANGE(env, "Invalid param_encoding specified");
return Nothing<void>();
}
*offset += 2;
return JustVoid();
}
namespace {
WebCryptoKeyExportStatus EC_Raw_Export(const KeyObjectData& key_data,
const ECKeyExportConfig& params,
ByteSource* out) {
const auto& m_pkey = key_data.GetAsymmetricKey();
CHECK(m_pkey);
Mutex::ScopedLock lock(key_data.mutex());
const EC_KEY* ec_key = m_pkey;
if (ec_key == nullptr) {
switch (key_data.GetKeyType()) {
case kKeyTypePrivate: {
auto data = m_pkey.rawPrivateKey();
if (!data) return WebCryptoKeyExportStatus::INVALID_KEY_TYPE;
DCHECK(!data.isSecure());
*out = ByteSource::Allocated(data.release());
break;
}
case kKeyTypePublic: {
auto data = m_pkey.rawPublicKey();
if (!data) return WebCryptoKeyExportStatus::INVALID_KEY_TYPE;
DCHECK(!data.isSecure());
*out = ByteSource::Allocated(data.release());
break;
}
case kKeyTypeSecret:
UNREACHABLE();
}
} else {
if (key_data.GetKeyType() != kKeyTypePublic)
return WebCryptoKeyExportStatus::INVALID_KEY_TYPE;
const auto group = ECKeyPointer::GetGroup(ec_key);
const auto point = ECKeyPointer::GetPublicKey(ec_key);
point_conversion_form_t form = POINT_CONVERSION_UNCOMPRESSED;
// Get the allocated data size...
size_t len = EC_POINT_point2oct(group, point, form, nullptr, 0, nullptr);
if (len == 0)
return WebCryptoKeyExportStatus::FAILED;
auto data = DataPointer::Alloc(len);
size_t check_len =
EC_POINT_point2oct(group,
point,
form,
static_cast<unsigned char*>(data.get()),
len,
nullptr);
if (check_len == 0)
return WebCryptoKeyExportStatus::FAILED;
CHECK_EQ(len, check_len);
*out = ByteSource::Allocated(data.release());
}
return WebCryptoKeyExportStatus::OK;
}
} // namespace
Maybe<void> ECKeyExportTraits::AdditionalConfig(
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
ECKeyExportConfig* params) {
return JustVoid();
}
WebCryptoKeyExportStatus ECKeyExportTraits::DoExport(
const KeyObjectData& key_data,
WebCryptoKeyFormat format,
const ECKeyExportConfig& params,
ByteSource* out) {
CHECK_NE(key_data.GetKeyType(), kKeyTypeSecret);
switch (format) {
case kWebCryptoKeyFormatRaw:
return EC_Raw_Export(key_data, params, out);
case kWebCryptoKeyFormatPKCS8:
if (key_data.GetKeyType() != kKeyTypePrivate)
return WebCryptoKeyExportStatus::INVALID_KEY_TYPE;
return PKEY_PKCS8_Export(key_data, out);
case kWebCryptoKeyFormatSPKI: {
if (key_data.GetKeyType() != kKeyTypePublic)
return WebCryptoKeyExportStatus::INVALID_KEY_TYPE;
const auto& m_pkey = key_data.GetAsymmetricKey();
if (m_pkey.id() != EVP_PKEY_EC) {
return PKEY_SPKI_Export(key_data, out);
} else {
// Ensure exported key is in uncompressed point format.
// The temporary EC key is so we can have i2d_PUBKEY_bio() write out
// the header but it is a somewhat silly hoop to jump through because
// the header is for all practical purposes a static 26 byte sequence
// where only the second byte changes.
Mutex::ScopedLock lock(key_data.mutex());
const auto group = ECKeyPointer::GetGroup(m_pkey);
const auto point = ECKeyPointer::GetPublicKey(m_pkey);
const point_conversion_form_t form = POINT_CONVERSION_UNCOMPRESSED;
const size_t need =
EC_POINT_point2oct(group, point, form, nullptr, 0, nullptr);
if (need == 0) return WebCryptoKeyExportStatus::FAILED;
auto data = DataPointer::Alloc(need);
const size_t have =
EC_POINT_point2oct(group,
point,
form,
static_cast<unsigned char*>(data.get()),
need,
nullptr);
if (have == 0) return WebCryptoKeyExportStatus::FAILED;
auto ec = ECKeyPointer::New(group);
CHECK(ec);
auto uncompressed = ECPointPointer::New(group);
ncrypto::Buffer<const unsigned char> buffer{
.data = static_cast<unsigned char*>(data.get()),
.len = data.size(),
};
CHECK(uncompressed.setFromBuffer(buffer, group));
CHECK(ec.setPublicKey(uncompressed));
auto pkey = EVPKeyPointer::New();
CHECK(pkey.set(ec));
auto bio = pkey.derPublicKey();
if (!bio) return WebCryptoKeyExportStatus::FAILED;
*out = ByteSource::FromBIO(bio);
return WebCryptoKeyExportStatus::OK;
}
}
default:
UNREACHABLE();
}
}
bool ExportJWKEcKey(Environment* env,
const KeyObjectData& key,
Local<Object> target) {
Mutex::ScopedLock lock(key.mutex());
const auto& m_pkey = key.GetAsymmetricKey();
CHECK_EQ(m_pkey.id(), EVP_PKEY_EC);
const EC_KEY* ec = m_pkey;
CHECK_NOT_NULL(ec);
const auto pub = ECKeyPointer::GetPublicKey(ec);
const auto group = ECKeyPointer::GetGroup(ec);
int degree_bits = EC_GROUP_get_degree(group);
int degree_bytes =
(degree_bits / CHAR_BIT) + (7 + (degree_bits % CHAR_BIT)) / 8;
auto x = BignumPointer::New();
auto y = BignumPointer::New();
if (!EC_POINT_get_affine_coordinates(group, pub, x.get(), y.get(), nullptr)) {
ThrowCryptoError(env, ERR_get_error(),
"Failed to get elliptic-curve point coordinates");
return false;
}
if (target->Set(
env->context(),
env->jwk_kty_string(),
env->jwk_ec_string()).IsNothing()) {
return false;
}
if (SetEncodedValue(
env,
target,
env->jwk_x_string(),
x.get(),
degree_bytes).IsNothing() ||
SetEncodedValue(
env,
target,
env->jwk_y_string(),
y.get(),
degree_bytes).IsNothing()) {
return false;
}
Local<String> crv_name;
const int nid = EC_GROUP_get_curve_name(group);
switch (nid) {
case NID_X9_62_prime256v1:
crv_name = FIXED_ONE_BYTE_STRING(env->isolate(), "P-256");
break;
case NID_secp256k1:
crv_name = FIXED_ONE_BYTE_STRING(env->isolate(), "secp256k1");
break;
case NID_secp384r1:
crv_name = FIXED_ONE_BYTE_STRING(env->isolate(), "P-384");
break;
case NID_secp521r1:
crv_name = FIXED_ONE_BYTE_STRING(env->isolate(), "P-521");
break;
default: {
THROW_ERR_CRYPTO_JWK_UNSUPPORTED_CURVE(
env, "Unsupported JWK EC curve: %s.", OBJ_nid2sn(nid));
return false;
}
}
if (target->Set(
env->context(),
env->jwk_crv_string(),
crv_name).IsNothing()) {
return false;
}
if (key.GetKeyType() == kKeyTypePrivate) {
auto pvt = ECKeyPointer::GetPrivateKey(ec);
return SetEncodedValue(env, target, env->jwk_d_string(), pvt, degree_bytes)
.IsJust();
}
return true;
}
bool ExportJWKEdKey(Environment* env,
const KeyObjectData& key,
Local<Object> target) {
Mutex::ScopedLock lock(key.mutex());
const auto& pkey = key.GetAsymmetricKey();
const char* curve = ([&] {
switch (pkey.id()) {
case EVP_PKEY_ED25519:
return "Ed25519";
case EVP_PKEY_ED448:
return "Ed448";
case EVP_PKEY_X25519:
return "X25519";
case EVP_PKEY_X448:
return "X448";
default:
UNREACHABLE();
}
})();
static constexpr auto trySetKey = [](Environment* env,
DataPointer data,
Local<Object> target,
Local<String> key) {
Local<Value> encoded;
if (!data) return false;
const ncrypto::Buffer<const char> out = data;
return StringBytes::Encode(env->isolate(), out.data, out.len, BASE64URL)
.ToLocal(&encoded) &&
target->Set(env->context(), key, encoded).IsJust();
};
return !(
target
->Set(env->context(),
env->jwk_crv_string(),
OneByteString(env->isolate(), curve))
.IsNothing() ||
(key.GetKeyType() == kKeyTypePrivate &&
!trySetKey(env, pkey.rawPrivateKey(), target, env->jwk_d_string())) ||
!trySetKey(env, pkey.rawPublicKey(), target, env->jwk_x_string()) ||
target->Set(env->context(), env->jwk_kty_string(), env->jwk_okp_string())
.IsNothing());
}
KeyObjectData ImportJWKEcKey(Environment* env,
Local<Object> jwk,
const FunctionCallbackInfo<Value>& args,
unsigned int offset) {
CHECK(args[offset]->IsString()); // curve name
Utf8Value curve(env->isolate(), args[offset].As<String>());
int nid = Ec::GetCurveIdFromName(*curve);
if (nid == NID_undef) { // Unknown curve
THROW_ERR_CRYPTO_INVALID_CURVE(env);
return {};
}
Local<Value> x_value;
Local<Value> y_value;
Local<Value> d_value;
if (!jwk->Get(env->context(), env->jwk_x_string()).ToLocal(&x_value) ||
!jwk->Get(env->context(), env->jwk_y_string()).ToLocal(&y_value) ||
!jwk->Get(env->context(), env->jwk_d_string()).ToLocal(&d_value)) {
return {};
}
if (!x_value->IsString() ||
!y_value->IsString() ||
(!d_value->IsUndefined() && !d_value->IsString())) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK EC key");
return {};
}
KeyType type = d_value->IsString() ? kKeyTypePrivate : kKeyTypePublic;
auto ec = ECKeyPointer::NewByCurveName(nid);
if (!ec) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK EC key");
return {};
}
ByteSource x = ByteSource::FromEncodedString(env, x_value.As<String>());
ByteSource y = ByteSource::FromEncodedString(env, y_value.As<String>());
if (!ec.setPublicKeyRaw(x.ToBN(), y.ToBN())) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK EC key");
return {};
}
if (type == kKeyTypePrivate) {
ByteSource d = ByteSource::FromEncodedString(env, d_value.As<String>());
if (!ec.setPrivateKey(d.ToBN())) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK EC key");
return {};
}
}
auto pkey = EVPKeyPointer::New();
if (!pkey) return {};
CHECK(pkey.set(ec));
return KeyObjectData::CreateAsymmetric(type, std::move(pkey));
}
bool GetEcKeyDetail(Environment* env,
const KeyObjectData& key,
Local<Object> target) {
Mutex::ScopedLock lock(key.mutex());
const auto& m_pkey = key.GetAsymmetricKey();
CHECK_EQ(m_pkey.id(), EVP_PKEY_EC);
const EC_KEY* ec = m_pkey;
CHECK_NOT_NULL(ec);
const auto group = ECKeyPointer::GetGroup(ec);
int nid = EC_GROUP_get_curve_name(group);
return target
->Set(env->context(),
env->named_curve_string(),
OneByteString(env->isolate(), OBJ_nid2sn(nid)))
.IsJust();
}
// WebCrypto requires a different format for ECDSA signatures than
// what OpenSSL produces, so we need to convert between them. The
// implementation here is a adapted from Chromium's impl here:
// https://github.com/chromium/chromium/blob/7af6cfd/components/webcrypto/algorithms/ecdsa.cc
size_t GroupOrderSize(const EVPKeyPointer& key) {
const EC_KEY* ec = key;
CHECK_NOT_NULL(ec);
auto order = BignumPointer::New();
CHECK(order);
CHECK(EC_GROUP_get_order(ECKeyPointer::GetGroup(ec), order.get(), nullptr));
return order.byteLength();
}
} // namespace crypto
} // namespace node

163
src/crypto/crypto_ec.h Normal file
View File

@ -0,0 +1,163 @@
#ifndef SRC_CRYPTO_CRYPTO_EC_H_
#define SRC_CRYPTO_CRYPTO_EC_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "async_wrap.h"
#include "base_object.h"
#include "crypto/crypto_keygen.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "env.h"
#include "memory_tracker.h"
#include "node_internals.h"
#include "v8.h"
namespace node {
namespace crypto {
class ECDH final : public BaseObject {
public:
~ECDH() override = default;
static void Initialize(Environment* env, v8::Local<v8::Object> target);
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);
static ncrypto::ECPointPointer BufferToPoint(Environment* env,
const EC_GROUP* group,
v8::Local<v8::Value> buf);
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(ECDH)
SET_SELF_SIZE(ECDH)
static void ConvertKey(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetCurves(const v8::FunctionCallbackInfo<v8::Value>& args);
protected:
ECDH(Environment* env,
v8::Local<v8::Object> wrap,
ncrypto::ECKeyPointer&& key);
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GenerateKeys(const v8::FunctionCallbackInfo<v8::Value>& args);
static void ComputeSecret(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPrivateKey(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetPrivateKey(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPublicKey(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetPublicKey(const v8::FunctionCallbackInfo<v8::Value>& args);
bool IsKeyPairValid();
bool IsKeyValidForCurve(const ncrypto::BignumPointer& private_key);
ncrypto::ECKeyPointer key_;
const EC_GROUP* group_;
};
struct ECDHBitsConfig final : public MemoryRetainer {
int id_;
KeyObjectData private_;
KeyObjectData public_;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(ECDHBitsConfig)
SET_SELF_SIZE(ECDHBitsConfig)
};
struct ECDHBitsTraits final {
using AdditionalParameters = ECDHBitsConfig;
static constexpr const char* JobName = "ECDHBitsJob";
static constexpr AsyncWrap::ProviderType Provider =
AsyncWrap::PROVIDER_DERIVEBITSREQUEST;
static v8::Maybe<void> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
ECDHBitsConfig* params);
static bool DeriveBits(Environment* env,
const ECDHBitsConfig& params,
ByteSource* out_,
CryptoJobMode mode);
static v8::MaybeLocal<v8::Value> EncodeOutput(Environment* env,
const ECDHBitsConfig& params,
ByteSource* out);
};
using ECDHBitsJob = DeriveBitsJob<ECDHBitsTraits>;
struct EcKeyPairParams final : public MemoryRetainer {
int curve_nid;
int param_encoding;
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(EcKeyPairParams)
SET_SELF_SIZE(EcKeyPairParams)
};
using EcKeyPairGenConfig = KeyPairGenConfig<EcKeyPairParams>;
struct EcKeyGenTraits final {
using AdditionalParameters = EcKeyPairGenConfig;
static constexpr const char* JobName = "EcKeyPairGenJob";
static ncrypto::EVPKeyCtxPointer Setup(EcKeyPairGenConfig* params);
static v8::Maybe<void> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int* offset,
EcKeyPairGenConfig* params);
};
using ECKeyPairGenJob = KeyGenJob<KeyPairGenTraits<EcKeyGenTraits>>;
// There is currently no additional information that the
// ECKeyExport needs to collect, but we need to provide
// the base struct anyway.
struct ECKeyExportConfig final : public MemoryRetainer {
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(ECKeyExportConfig)
SET_SELF_SIZE(ECKeyExportConfig)
};
struct ECKeyExportTraits final {
static constexpr const char* JobName = "ECKeyExportJob";
using AdditionalParameters = ECKeyExportConfig;
static v8::Maybe<void> AdditionalConfig(
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
ECKeyExportConfig* config);
static WebCryptoKeyExportStatus DoExport(const KeyObjectData& key_data,
WebCryptoKeyFormat format,
const ECKeyExportConfig& params,
ByteSource* out);
};
using ECKeyExportJob = KeyExportJob<ECKeyExportTraits>;
bool ExportJWKEcKey(Environment* env,
const KeyObjectData& key,
v8::Local<v8::Object> target);
bool ExportJWKEdKey(Environment* env,
const KeyObjectData& key,
v8::Local<v8::Object> target);
KeyObjectData ImportJWKEcKey(Environment* env,
v8::Local<v8::Object> jwk,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset);
bool GetEcKeyDetail(Environment* env,
const KeyObjectData& key,
v8::Local<v8::Object> target);
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_EC_H_

503
src/crypto/crypto_hash.cc Normal file
View File

@ -0,0 +1,503 @@
#include "crypto/crypto_hash.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "string_bytes.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
#include <cstdio>
namespace node {
using ncrypto::DataPointer;
using ncrypto::EVPMDCtxPointer;
using ncrypto::MarkPopErrorOnReturn;
using v8::Context;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Int32;
using v8::Isolate;
using v8::Just;
using v8::JustVoid;
using v8::Local;
using v8::LocalVector;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Name;
using v8::Nothing;
using v8::Object;
using v8::Uint32;
using v8::Value;
namespace crypto {
Hash::Hash(Environment* env, Local<Object> wrap) : BaseObject(env, wrap) {
MakeWeak();
}
void Hash::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackFieldWithSize("mdctx", mdctx_ ? kSizeOf_EVP_MD_CTX : 0);
tracker->TrackFieldWithSize("md", digest_ ? md_len_ : 0);
}
#if OPENSSL_VERSION_MAJOR >= 3
void PushAliases(const char* name, void* data) {
static_cast<std::vector<std::string>*>(data)->push_back(name);
}
EVP_MD* GetCachedMDByID(Environment* env, size_t id) {
CHECK_LT(id, env->evp_md_cache.size());
EVP_MD* result = env->evp_md_cache[id].get();
CHECK_NOT_NULL(result);
return result;
}
struct MaybeCachedMD {
EVP_MD* explicit_md = nullptr;
const EVP_MD* implicit_md = nullptr;
int32_t cache_id = -1;
};
MaybeCachedMD FetchAndMaybeCacheMD(Environment* env, const char* search_name) {
const EVP_MD* implicit_md = ncrypto::getDigestByName(search_name);
if (!implicit_md) return {nullptr, nullptr, -1};
const char* real_name = EVP_MD_get0_name(implicit_md);
if (!real_name) return {nullptr, implicit_md, -1};
auto it = env->alias_to_md_id_map.find(real_name);
if (it != env->alias_to_md_id_map.end()) {
size_t id = it->second;
return {GetCachedMDByID(env, id), implicit_md, static_cast<int32_t>(id)};
}
// 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).
EVP_MD* explicit_md = EVP_MD_fetch(nullptr, real_name, nullptr);
if (!explicit_md) return {nullptr, implicit_md, -1};
// Cache the EVP_MD* fetched.
env->evp_md_cache.emplace_back(explicit_md);
size_t id = env->evp_md_cache.size() - 1;
// Add all the aliases to the map to speed up next lookup.
std::vector<std::string> aliases;
EVP_MD_names_do_all(explicit_md, PushAliases, &aliases);
for (const auto& alias : aliases) {
env->alias_to_md_id_map.emplace(alias, id);
}
env->alias_to_md_id_map.emplace(search_name, id);
return {explicit_md, implicit_md, static_cast<int32_t>(id)};
}
void SaveSupportedHashAlgorithmsAndCacheMD(const EVP_MD* md,
const char* from,
const char* to,
void* arg) {
if (!from) return;
Environment* env = static_cast<Environment*>(arg);
auto result = FetchAndMaybeCacheMD(env, from);
if (result.explicit_md) {
env->supported_hash_algorithms.push_back(from);
}
}
#else
void SaveSupportedHashAlgorithms(const EVP_MD* md,
const char* from,
const char* to,
void* arg) {
if (!from) return;
Environment* env = static_cast<Environment*>(arg);
env->supported_hash_algorithms.push_back(from);
}
#endif // OPENSSL_VERSION_MAJOR >= 3
const std::vector<std::string>& GetSupportedHashAlgorithms(Environment* env) {
if (env->supported_hash_algorithms.empty()) {
MarkPopErrorOnReturn mark_pop_error_on_return;
#if OPENSSL_VERSION_MAJOR >= 3
// Since we'll fetch the EVP_MD*, cache them along the way to speed up
// later lookups instead of throwing them away immediately.
EVP_MD_do_all_sorted(SaveSupportedHashAlgorithmsAndCacheMD, env);
#else
EVP_MD_do_all_sorted(SaveSupportedHashAlgorithms, env);
#endif
}
return env->supported_hash_algorithms;
}
void Hash::GetHashes(const FunctionCallbackInfo<Value>& args) {
Local<Context> context = args.GetIsolate()->GetCurrentContext();
Environment* env = Environment::GetCurrent(context);
const std::vector<std::string>& results = GetSupportedHashAlgorithms(env);
Local<Value> ret;
if (ToV8Value(context, results).ToLocal(&ret)) {
args.GetReturnValue().Set(ret);
}
}
void Hash::GetCachedAliases(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
Local<Context> context = args.GetIsolate()->GetCurrentContext();
Environment* env = Environment::GetCurrent(context);
size_t size = env->alias_to_md_id_map.size();
LocalVector<Name> names(isolate);
LocalVector<Value> values(isolate);
#if OPENSSL_VERSION_MAJOR >= 3
names.reserve(size);
values.reserve(size);
for (auto& [alias, id] : env->alias_to_md_id_map) {
names.push_back(OneByteString(isolate, alias));
values.push_back(v8::Uint32::New(isolate, id));
}
#else
CHECK(env->alias_to_md_id_map.empty());
#endif
Local<Value> prototype = v8::Null(isolate);
Local<Object> result =
Object::New(isolate, prototype, names.data(), values.data(), size);
args.GetReturnValue().Set(result);
}
const EVP_MD* GetDigestImplementation(Environment* env,
Local<Value> algorithm,
Local<Value> cache_id_val,
Local<Value> algorithm_cache) {
CHECK(algorithm->IsString());
CHECK(cache_id_val->IsInt32());
CHECK(algorithm_cache->IsObject());
#if OPENSSL_VERSION_MAJOR >= 3
int32_t cache_id = cache_id_val.As<Int32>()->Value();
if (cache_id != -1) { // Alias already cached, return the cached EVP_MD*.
return GetCachedMDByID(env, cache_id);
}
// Only decode the algorithm when we don't have it cached to avoid
// unnecessary overhead.
Isolate* isolate = env->isolate();
Utf8Value utf8(isolate, algorithm);
auto result = FetchAndMaybeCacheMD(env, *utf8);
if (result.cache_id != -1) {
// Add the alias to both C++ side and JS side to speedup the lookup
// next time.
env->alias_to_md_id_map.emplace(*utf8, result.cache_id);
if (algorithm_cache.As<Object>()
->Set(isolate->GetCurrentContext(),
algorithm,
v8::Int32::New(isolate, result.cache_id))
.IsNothing()) {
return nullptr;
}
}
return result.explicit_md ? result.explicit_md : result.implicit_md;
#else
Utf8Value utf8(env->isolate(), algorithm);
return ncrypto::getDigestByName(*utf8);
#endif
}
// crypto.digest(algorithm, algorithmId, algorithmCache,
// input, outputEncoding, outputEncodingId)
void Hash::OneShotDigest(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = env->isolate();
CHECK_EQ(args.Length(), 6);
CHECK(args[0]->IsString()); // algorithm
CHECK(args[1]->IsInt32()); // algorithmId
CHECK(args[2]->IsObject()); // algorithmCache
CHECK(args[3]->IsString() || args[3]->IsArrayBufferView()); // input
CHECK(args[4]->IsString()); // outputEncoding
CHECK(args[5]->IsUint32() || args[5]->IsUndefined()); // outputEncodingId
const EVP_MD* md = GetDigestImplementation(env, args[0], args[1], args[2]);
if (md == nullptr) [[unlikely]] {
Utf8Value method(isolate, args[0]);
std::string message =
"Digest method " + method.ToString() + " is not supported";
return ThrowCryptoError(env, ERR_get_error(), message.c_str());
}
enum encoding output_enc = ParseEncoding(isolate, args[4], args[5], HEX);
DataPointer output = ([&] {
if (args[3]->IsString()) {
Utf8Value utf8(isolate, args[3]);
ncrypto::Buffer<const unsigned char> buf{
.data = reinterpret_cast<const unsigned char*>(utf8.out()),
.len = utf8.length(),
};
return ncrypto::hashDigest(buf, md);
}
ArrayBufferViewContents<unsigned char> input(args[3]);
ncrypto::Buffer<const unsigned char> buf{
.data = reinterpret_cast<const unsigned char*>(input.data()),
.len = input.length(),
};
return ncrypto::hashDigest(buf, md);
})();
if (!output) [[unlikely]] {
return ThrowCryptoError(env, ERR_get_error());
}
Local<Value> ret;
if (StringBytes::Encode(env->isolate(),
static_cast<const char*>(output.get()),
output.size(),
output_enc)
.ToLocal(&ret)) {
args.GetReturnValue().Set(ret);
}
}
void Hash::Initialize(Environment* env, Local<Object> target) {
Isolate* isolate = env->isolate();
Local<Context> context = env->context();
Local<FunctionTemplate> t = NewFunctionTemplate(isolate, New);
t->InstanceTemplate()->SetInternalFieldCount(Hash::kInternalFieldCount);
SetProtoMethod(isolate, t, "update", HashUpdate);
SetProtoMethod(isolate, t, "digest", HashDigest);
SetConstructorFunction(context, target, "Hash", t);
SetMethodNoSideEffect(context, target, "getHashes", GetHashes);
SetMethodNoSideEffect(context, target, "getCachedAliases", GetCachedAliases);
SetMethodNoSideEffect(context, target, "oneShotDigest", OneShotDigest);
HashJob::Initialize(env, target);
}
void Hash::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(New);
registry->Register(HashUpdate);
registry->Register(HashDigest);
registry->Register(GetHashes);
registry->Register(GetCachedAliases);
registry->Register(OneShotDigest);
HashJob::RegisterExternalReferences(registry);
}
// new Hash(algorithm, algorithmId, xofLen, algorithmCache)
void Hash::New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
const Hash* orig = nullptr;
const EVP_MD* md = nullptr;
if (args[0]->IsObject()) {
ASSIGN_OR_RETURN_UNWRAP(&orig, args[0].As<Object>());
CHECK_NOT_NULL(orig);
md = orig->mdctx_.getDigest();
} else {
md = GetDigestImplementation(env, args[0], args[2], args[3]);
}
Maybe<unsigned int> xof_md_len = Nothing<unsigned int>();
if (!args[1]->IsUndefined()) {
CHECK(args[1]->IsUint32());
xof_md_len = Just<unsigned int>(args[1].As<Uint32>()->Value());
}
Hash* hash = new Hash(env, args.This());
if (md == nullptr || !hash->HashInit(md, xof_md_len)) {
return ThrowCryptoError(env, ERR_get_error(),
"Digest method not supported");
}
if (orig != nullptr && !orig->mdctx_.copyTo(hash->mdctx_)) {
return ThrowCryptoError(env, ERR_get_error(), "Digest copy error");
}
}
bool Hash::HashInit(const EVP_MD* md, Maybe<unsigned int> xof_md_len) {
mdctx_ = EVPMDCtxPointer::New();
if (!mdctx_.digestInit(md)) [[unlikely]] {
mdctx_.reset();
return false;
}
md_len_ = mdctx_.getDigestSize();
if (xof_md_len.IsJust() && xof_md_len.FromJust() != md_len_) {
// This is a little hack to cause createHash to fail when an incorrect
// hashSize option was passed for a non-XOF hash function.
if (!mdctx_.hasXofFlag()) [[unlikely]] {
EVPerr(EVP_F_EVP_DIGESTFINALXOF, EVP_R_NOT_XOF_OR_INVALID_LENGTH);
mdctx_.reset();
return false;
}
md_len_ = xof_md_len.FromJust();
}
return true;
}
bool Hash::HashUpdate(const char* data, size_t len) {
if (!mdctx_) return false;
return mdctx_.digestUpdate(ncrypto::Buffer<const void>{
.data = data,
.len = len,
});
}
void Hash::HashUpdate(const FunctionCallbackInfo<Value>& args) {
Decode<Hash>(args,
[](Hash* hash,
const FunctionCallbackInfo<Value>& args,
const char* data,
size_t size) {
Environment* env = Environment::GetCurrent(args);
if (size > INT_MAX) [[unlikely]]
return THROW_ERR_OUT_OF_RANGE(env, "data is too long");
bool r = hash->HashUpdate(data, size);
args.GetReturnValue().Set(r);
});
}
void Hash::HashDigest(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Hash* hash;
ASSIGN_OR_RETURN_UNWRAP(&hash, args.This());
enum encoding encoding = BUFFER;
if (args.Length() >= 1) {
encoding = ParseEncoding(env->isolate(), args[0], BUFFER);
}
unsigned int len = hash->md_len_;
// TODO(tniessen): SHA3_squeeze does not work for zero-length outputs on all
// platforms and will cause a segmentation fault if called. This workaround
// causes hash.digest() to correctly return an empty buffer / string.
// See https://github.com/openssl/openssl/issues/9431.
if (!hash->digest_ && len > 0) {
// Some hash algorithms such as SHA3 do not support calling
// EVP_DigestFinal_ex more than once, however, Hash._flush
// and Hash.digest can both be used to retrieve the digest,
// so we need to cache it.
// See https://github.com/nodejs/node/issues/28245.
auto data = hash->mdctx_.digestFinal(len);
if (!data) [[unlikely]] {
return ThrowCryptoError(env, ERR_get_error());
}
DCHECK(!data.isSecure());
hash->digest_ = ByteSource::Allocated(data.release());
}
Local<Value> ret;
if (StringBytes::Encode(
env->isolate(), hash->digest_.data<char>(), len, encoding)
.ToLocal(&ret)) {
args.GetReturnValue().Set(ret);
}
}
HashConfig::HashConfig(HashConfig&& other) noexcept
: mode(other.mode),
in(std::move(other.in)),
digest(other.digest),
length(other.length) {}
HashConfig& HashConfig::operator=(HashConfig&& other) noexcept {
if (&other == this) return *this;
this->~HashConfig();
return *new (this) HashConfig(std::move(other));
}
void HashConfig::MemoryInfo(MemoryTracker* tracker) const {
// If the Job is sync, then the HashConfig does not own the data.
if (mode == kCryptoJobAsync)
tracker->TrackFieldWithSize("in", in.size());
}
MaybeLocal<Value> HashTraits::EncodeOutput(Environment* env,
const HashConfig& params,
ByteSource* out) {
return out->ToArrayBuffer(env);
}
Maybe<void> HashTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
HashConfig* params) {
Environment* env = Environment::GetCurrent(args);
params->mode = mode;
CHECK(args[offset]->IsString()); // Hash algorithm
Utf8Value digest(env->isolate(), args[offset]);
params->digest = ncrypto::getDigestByName(*digest);
if (params->digest == nullptr) [[unlikely]] {
THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid digest: %s", *digest);
return Nothing<void>();
}
ArrayBufferOrViewContents<char> data(args[offset + 1]);
if (!data.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "data is too big");
return Nothing<void>();
}
params->in = mode == kCryptoJobAsync
? data.ToCopy()
: data.ToByteSource();
unsigned int expected = EVP_MD_size(params->digest);
params->length = expected;
if (args[offset + 2]->IsUint32()) [[unlikely]] {
// length is expressed in terms of bits
params->length =
static_cast<uint32_t>(args[offset + 2]
.As<Uint32>()->Value()) / CHAR_BIT;
if (params->length != expected) {
if ((EVP_MD_flags(params->digest) & EVP_MD_FLAG_XOF) == 0) [[unlikely]] {
THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Digest method not supported");
return Nothing<void>();
}
}
}
return JustVoid();
}
bool HashTraits::DeriveBits(Environment* env,
const HashConfig& params,
ByteSource* out,
CryptoJobMode mode) {
auto ctx = EVPMDCtxPointer::New();
if (!ctx.digestInit(params.digest) || !ctx.digestUpdate(params.in))
[[unlikely]] {
return false;
}
if (params.length > 0) [[likely]] {
auto data = ctx.digestFinal(params.length);
if (!data) [[unlikely]]
return false;
DCHECK(!data.isSecure());
*out = ByteSource::Allocated(data.release());
}
return true;
}
} // namespace crypto
} // namespace node

89
src/crypto/crypto_hash.h Normal file
View File

@ -0,0 +1,89 @@
#ifndef SRC_CRYPTO_CRYPTO_HASH_H_
#define SRC_CRYPTO_CRYPTO_HASH_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "base_object.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "env.h"
#include "memory_tracker.h"
#include "v8.h"
namespace node {
namespace crypto {
class Hash final : public BaseObject {
public:
static void Initialize(Environment* env, v8::Local<v8::Object> target);
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(Hash)
SET_SELF_SIZE(Hash)
bool HashInit(const EVP_MD* md, v8::Maybe<unsigned int> xof_md_len);
bool HashUpdate(const char* data, size_t len);
static void GetHashes(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetCachedAliases(const v8::FunctionCallbackInfo<v8::Value>& args);
static void OneShotDigest(const v8::FunctionCallbackInfo<v8::Value>& args);
protected:
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void HashUpdate(const v8::FunctionCallbackInfo<v8::Value>& args);
static void HashDigest(const v8::FunctionCallbackInfo<v8::Value>& args);
Hash(Environment* env, v8::Local<v8::Object> wrap);
private:
ncrypto::EVPMDCtxPointer mdctx_{};
unsigned int md_len_ = 0;
ByteSource digest_;
};
struct HashConfig final : public MemoryRetainer {
CryptoJobMode mode;
ByteSource in;
const EVP_MD* digest;
unsigned int length;
HashConfig() = default;
explicit HashConfig(HashConfig&& other) noexcept;
HashConfig& operator=(HashConfig&& other) noexcept;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(HashConfig)
SET_SELF_SIZE(HashConfig)
};
struct HashTraits final {
using AdditionalParameters = HashConfig;
static constexpr const char* JobName = "HashJob";
static constexpr AsyncWrap::ProviderType Provider =
AsyncWrap::PROVIDER_HASHREQUEST;
static v8::Maybe<void> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
HashConfig* params);
static bool DeriveBits(Environment* env,
const HashConfig& params,
ByteSource* out,
CryptoJobMode mode);
static v8::MaybeLocal<v8::Value> EncodeOutput(Environment* env,
const HashConfig& params,
ByteSource* out);
};
using HashJob = DeriveBitsJob<HashTraits>;
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_HASH_H_

136
src/crypto/crypto_hkdf.cc Normal file
View File

@ -0,0 +1,136 @@
#include "crypto/crypto_hkdf.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "crypto/crypto_keys.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
namespace node {
using ncrypto::Digest;
using v8::FunctionCallbackInfo;
using v8::JustVoid;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Nothing;
using v8::Uint32;
using v8::Value;
namespace crypto {
HKDFConfig::HKDFConfig(HKDFConfig&& other) noexcept
: mode(other.mode),
length(other.length),
digest(other.digest),
key(std::move(other.key)),
salt(std::move(other.salt)),
info(std::move(other.info)) {}
HKDFConfig& HKDFConfig::operator=(HKDFConfig&& other) noexcept {
if (&other == this) return *this;
this->~HKDFConfig();
return *new (this) HKDFConfig(std::move(other));
}
MaybeLocal<Value> HKDFTraits::EncodeOutput(Environment* env,
const HKDFConfig& params,
ByteSource* out) {
return out->ToArrayBuffer(env);
}
Maybe<void> HKDFTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
HKDFConfig* params) {
Environment* env = Environment::GetCurrent(args);
params->mode = mode;
CHECK(args[offset]->IsString()); // Hash
CHECK(args[offset + 1]->IsObject()); // Key
CHECK(IsAnyBufferSource(args[offset + 2])); // Salt
CHECK(IsAnyBufferSource(args[offset + 3])); // Info
CHECK(args[offset + 4]->IsUint32()); // Length
Utf8Value hash(env->isolate(), args[offset]);
params->digest = Digest::FromName(*hash);
if (!params->digest) [[unlikely]] {
THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid digest: %s", *hash);
return Nothing<void>();
}
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args[offset + 1], Nothing<void>());
params->key = key->Data().addRef();
ArrayBufferOrViewContents<char> salt(args[offset + 2]);
ArrayBufferOrViewContents<char> info(args[offset + 3]);
if (!salt.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "salt is too big");
return Nothing<void>();
}
if (!info.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "info is too big");
return Nothing<void>();
}
params->salt = mode == kCryptoJobAsync
? salt.ToCopy()
: salt.ToByteSource();
params->info = mode == kCryptoJobAsync
? info.ToCopy()
: info.ToByteSource();
params->length = args[offset + 4].As<Uint32>()->Value();
// HKDF-Expand computes up to 255 HMAC blocks, each having as many bits as the
// output of the hash function. 255 is a hard limit because HKDF appends an
// 8-bit counter to each HMAC'd message, starting at 1.
if (!ncrypto::checkHkdfLength(params->digest, params->length)) [[unlikely]] {
THROW_ERR_CRYPTO_INVALID_KEYLEN(env);
return Nothing<void>();
}
return JustVoid();
}
bool HKDFTraits::DeriveBits(Environment* env,
const HKDFConfig& params,
ByteSource* out,
CryptoJobMode mode) {
auto dp = ncrypto::hkdf(params.digest,
ncrypto::Buffer<const unsigned char>{
.data = reinterpret_cast<const unsigned char*>(
params.key.GetSymmetricKey()),
.len = params.key.GetSymmetricKeySize(),
},
ncrypto::Buffer<const unsigned char>{
.data = params.info.data<const unsigned char>(),
.len = params.info.size(),
},
ncrypto::Buffer<const unsigned char>{
.data = params.salt.data<const unsigned char>(),
.len = params.salt.size(),
},
params.length);
if (!dp) return false;
DCHECK(!dp.isSecure());
*out = ByteSource::Allocated(dp.release());
return true;
}
void HKDFConfig::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("key", key);
// If the job is sync, then the HKDFConfig does not own the data
if (mode == kCryptoJobAsync) {
tracker->TrackFieldWithSize("salt", salt.size());
tracker->TrackFieldWithSize("info", info.size());
}
}
} // namespace crypto
} // namespace node

61
src/crypto/crypto_hkdf.h Normal file
View File

@ -0,0 +1,61 @@
#ifndef SRC_CRYPTO_CRYPTO_HKDF_H_
#define SRC_CRYPTO_CRYPTO_HKDF_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "async_wrap.h"
#include "base_object.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "v8.h"
namespace node {
namespace crypto {
struct HKDFConfig final : public MemoryRetainer {
CryptoJobMode mode;
size_t length;
ncrypto::Digest digest;
KeyObjectData key;
ByteSource salt;
ByteSource info;
HKDFConfig() = default;
explicit HKDFConfig(HKDFConfig&& other) noexcept;
HKDFConfig& operator=(HKDFConfig&& other) noexcept;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(HKDFConfig)
SET_SELF_SIZE(HKDFConfig)
};
struct HKDFTraits final {
using AdditionalParameters = HKDFConfig;
static constexpr const char* JobName = "HKDFJob";
static constexpr AsyncWrap::ProviderType Provider =
AsyncWrap::PROVIDER_DERIVEBITSREQUEST;
static v8::Maybe<void> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
HKDFConfig* params);
static bool DeriveBits(Environment* env,
const HKDFConfig& params,
ByteSource* out,
CryptoJobMode mode);
static v8::MaybeLocal<v8::Value> EncodeOutput(Environment* env,
const HKDFConfig& params,
ByteSource* out);
};
using HKDFJob = DeriveBitsJob<HKDFTraits>;
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_HKDF_H_

279
src/crypto/crypto_hmac.cc Normal file
View File

@ -0,0 +1,279 @@
#include "crypto/crypto_hmac.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_sig.h"
#include "crypto/crypto_util.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "node_buffer.h"
#include "string_bytes.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
namespace node {
using ncrypto::Digest;
using ncrypto::HMACCtxPointer;
using v8::Boolean;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::HandleScope;
using v8::Isolate;
using v8::JustVoid;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Nothing;
using v8::Object;
using v8::Uint32;
using v8::Value;
namespace crypto {
Hmac::Hmac(Environment* env, Local<Object> wrap)
: BaseObject(env, wrap),
ctx_(nullptr) {
MakeWeak();
}
void Hmac::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackFieldWithSize("context", ctx_ ? kSizeOf_HMAC_CTX : 0);
}
void Hmac::Initialize(Environment* env, Local<Object> target) {
Isolate* isolate = env->isolate();
Local<FunctionTemplate> t = NewFunctionTemplate(isolate, New);
t->InstanceTemplate()->SetInternalFieldCount(Hmac::kInternalFieldCount);
SetProtoMethod(isolate, t, "init", HmacInit);
SetProtoMethod(isolate, t, "update", HmacUpdate);
SetProtoMethod(isolate, t, "digest", HmacDigest);
SetConstructorFunction(env->context(), target, "Hmac", t);
HmacJob::Initialize(env, target);
}
void Hmac::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(New);
registry->Register(HmacInit);
registry->Register(HmacUpdate);
registry->Register(HmacDigest);
HmacJob::RegisterExternalReferences(registry);
}
void Hmac::New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
new Hmac(env, args.This());
}
void Hmac::HmacInit(const char* hash_type, const char* key, int key_len) {
HandleScope scope(env()->isolate());
Digest md = Digest::FromName(hash_type);
if (!md) [[unlikely]] {
return THROW_ERR_CRYPTO_INVALID_DIGEST(
env(), "Invalid digest: %s", hash_type);
}
if (key_len == 0) {
key = "";
}
ctx_ = HMACCtxPointer::New();
ncrypto::Buffer<const void> key_buf{
.data = key,
.len = static_cast<size_t>(key_len),
};
if (!ctx_.init(key_buf, md)) [[unlikely]] {
ctx_.reset();
return ThrowCryptoError(env(), ERR_get_error());
}
}
void Hmac::HmacInit(const FunctionCallbackInfo<Value>& args) {
Hmac* hmac;
ASSIGN_OR_RETURN_UNWRAP(&hmac, args.This());
Environment* env = hmac->env();
const node::Utf8Value hash_type(env->isolate(), args[0]);
ByteSource key = ByteSource::FromSecretKeyBytes(env, args[1]);
hmac->HmacInit(*hash_type, key.data<char>(), key.size());
}
bool Hmac::HmacUpdate(const char* data, size_t len) {
ncrypto::Buffer<const void> buf{
.data = data,
.len = len,
};
return ctx_.update(buf);
}
void Hmac::HmacUpdate(const FunctionCallbackInfo<Value>& args) {
Decode<Hmac>(args, [](Hmac* hmac, const FunctionCallbackInfo<Value>& args,
const char* data, size_t size) {
Environment* env = Environment::GetCurrent(args);
if (size > INT_MAX) [[unlikely]]
return THROW_ERR_OUT_OF_RANGE(env, "data is too long");
bool r = hmac->HmacUpdate(data, size);
args.GetReturnValue().Set(r);
});
}
void Hmac::HmacDigest(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Hmac* hmac;
ASSIGN_OR_RETURN_UNWRAP(&hmac, args.This());
enum encoding encoding = BUFFER;
if (args.Length() >= 1) {
encoding = ParseEncoding(env->isolate(), args[0], BUFFER);
}
unsigned char md_value[Digest::MAX_SIZE];
ncrypto::Buffer<void> buf{
.data = md_value,
.len = sizeof(md_value),
};
if (hmac->ctx_) {
if (!hmac->ctx_.digestInto(&buf)) [[unlikely]] {
hmac->ctx_.reset();
return ThrowCryptoError(env, ERR_get_error(), "Failed to finalize HMAC");
}
hmac->ctx_.reset();
}
Local<Value> ret;
if (StringBytes::Encode(env->isolate(),
reinterpret_cast<const char*>(md_value),
buf.len,
encoding)
.ToLocal(&ret)) {
args.GetReturnValue().Set(ret);
}
}
HmacConfig::HmacConfig(HmacConfig&& other) noexcept
: job_mode(other.job_mode),
mode(other.mode),
key(std::move(other.key)),
data(std::move(other.data)),
signature(std::move(other.signature)),
digest(other.digest) {}
HmacConfig& HmacConfig::operator=(HmacConfig&& other) noexcept {
if (&other == this) return *this;
this->~HmacConfig();
return *new (this) HmacConfig(std::move(other));
}
void HmacConfig::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("key", key);
// If the job is sync, then the HmacConfig does not own the data
if (job_mode == kCryptoJobAsync) {
tracker->TrackFieldWithSize("data", data.size());
tracker->TrackFieldWithSize("signature", signature.size());
}
}
Maybe<void> HmacTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
HmacConfig* params) {
Environment* env = Environment::GetCurrent(args);
params->job_mode = mode;
CHECK(args[offset]->IsUint32()); // SignConfiguration::Mode
params->mode =
static_cast<SignConfiguration::Mode>(args[offset].As<Uint32>()->Value());
CHECK(args[offset + 1]->IsString()); // Hash
CHECK(args[offset + 2]->IsObject()); // Key
Utf8Value digest(env->isolate(), args[offset + 1]);
params->digest = Digest::FromName(*digest);
if (!params->digest) [[unlikely]] {
THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid digest: %s", *digest);
return Nothing<void>();
}
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args[offset + 2], Nothing<void>());
params->key = key->Data().addRef();
ArrayBufferOrViewContents<char> data(args[offset + 3]);
if (!data.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "data is too big");
return Nothing<void>();
}
params->data = mode == kCryptoJobAsync
? data.ToCopy()
: data.ToByteSource();
if (!args[offset + 4]->IsUndefined()) {
ArrayBufferOrViewContents<char> signature(args[offset + 4]);
if (!signature.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "signature is too big");
return Nothing<void>();
}
params->signature = mode == kCryptoJobAsync
? signature.ToCopy()
: signature.ToByteSource();
}
return JustVoid();
}
bool HmacTraits::DeriveBits(Environment* env,
const HmacConfig& params,
ByteSource* out,
CryptoJobMode mode) {
auto ctx = HMACCtxPointer::New();
ncrypto::Buffer<const void> key_buf{
.data = params.key.GetSymmetricKey(),
.len = params.key.GetSymmetricKeySize(),
};
if (!ctx.init(key_buf, params.digest)) [[unlikely]] {
return false;
}
ncrypto::Buffer<const void> buffer{
.data = params.data.data(),
.len = params.data.size(),
};
if (!ctx.update(buffer)) [[unlikely]] {
return false;
}
auto buf = ctx.digest();
if (!buf) [[unlikely]]
return false;
DCHECK(!buf.isSecure());
*out = ByteSource::Allocated(buf.release());
return true;
}
MaybeLocal<Value> HmacTraits::EncodeOutput(Environment* env,
const HmacConfig& params,
ByteSource* out) {
switch (params.mode) {
case SignConfiguration::Mode::Sign:
return out->ToArrayBuffer(env);
case SignConfiguration::Mode::Verify:
return Boolean::New(
env->isolate(),
out->size() > 0 && out->size() == params.signature.size() &&
memcmp(out->data(), params.signature.data(), out->size()) == 0);
}
UNREACHABLE();
}
} // namespace crypto
} // namespace node

92
src/crypto/crypto_hmac.h Normal file
View File

@ -0,0 +1,92 @@
#ifndef SRC_CRYPTO_CRYPTO_HMAC_H_
#define SRC_CRYPTO_CRYPTO_HMAC_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "base_object.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_sig.h"
#include "crypto/crypto_util.h"
#include "env.h"
#include "memory_tracker.h"
#include "v8.h"
namespace node {
namespace crypto {
class Hmac : public BaseObject {
public:
static void Initialize(Environment* env, v8::Local<v8::Object> target);
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(Hmac)
SET_SELF_SIZE(Hmac)
protected:
void HmacInit(const char* hash_type, const char* key, int key_len);
bool HmacUpdate(const char* data, size_t len);
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void HmacInit(const v8::FunctionCallbackInfo<v8::Value>& args);
static void HmacUpdate(const v8::FunctionCallbackInfo<v8::Value>& args);
static void HmacDigest(const v8::FunctionCallbackInfo<v8::Value>& args);
Hmac(Environment* env, v8::Local<v8::Object> wrap);
static void Sign(const v8::FunctionCallbackInfo<v8::Value>& args);
private:
ncrypto::HMACCtxPointer ctx_;
};
struct HmacConfig final : public MemoryRetainer {
CryptoJobMode job_mode;
SignConfiguration::Mode mode;
KeyObjectData key;
ByteSource data;
ByteSource signature;
ncrypto::Digest digest;
HmacConfig() = default;
explicit HmacConfig(HmacConfig&& other) noexcept;
HmacConfig& operator=(HmacConfig&& other) noexcept;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(HmacConfig)
SET_SELF_SIZE(HmacConfig)
};
struct HmacTraits final {
using AdditionalParameters = HmacConfig;
static constexpr const char* JobName = "HmacJob";
// TODO(@jasnell): Sign request vs. Verify request
static constexpr AsyncWrap::ProviderType Provider =
AsyncWrap::PROVIDER_SIGNREQUEST;
static v8::Maybe<void> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
HmacConfig* params);
static bool DeriveBits(Environment* env,
const HmacConfig& params,
ByteSource* out,
CryptoJobMode mode);
static v8::MaybeLocal<v8::Value> EncodeOutput(Environment* env,
const HmacConfig& params,
ByteSource* out);
};
using HmacJob = DeriveBitsJob<HmacTraits>;
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_HMAC_H_

101
src/crypto/crypto_keygen.cc Normal file
View File

@ -0,0 +1,101 @@
#include "crypto/crypto_keygen.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "debug_utils-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "ncrypto.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
#include <cmath>
namespace node {
using ncrypto::DataPointer;
using ncrypto::EVPKeyCtxPointer;
using v8::FunctionCallbackInfo;
using v8::Int32;
using v8::JustVoid;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Object;
using v8::Uint32;
using v8::Value;
namespace crypto {
// NidKeyPairGenJob input arguments:
// 1. CryptoJobMode
// 2. NID
// 3. Public Format
// 4. Public Type
// 5. Private Format
// 6. Private Type
// 7. Cipher
// 8. Passphrase
Maybe<void> NidKeyPairGenTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int* offset,
NidKeyPairGenConfig* params) {
CHECK(args[*offset]->IsInt32());
params->params.id = args[*offset].As<Int32>()->Value();
*offset += 1;
return JustVoid();
}
EVPKeyCtxPointer NidKeyPairGenTraits::Setup(NidKeyPairGenConfig* params) {
auto ctx = EVPKeyCtxPointer::NewFromID(params->params.id);
if (!ctx || !ctx.initForKeygen()) return {};
return ctx;
}
void SecretKeyGenConfig::MemoryInfo(MemoryTracker* tracker) const {
if (out) tracker->TrackFieldWithSize("out", length);
}
Maybe<void> SecretKeyGenTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int* offset,
SecretKeyGenConfig* params) {
CHECK(args[*offset]->IsUint32());
uint32_t bits = args[*offset].As<Uint32>()->Value();
params->length = bits / CHAR_BIT;
*offset += 1;
return JustVoid();
}
KeyGenJobStatus SecretKeyGenTraits::DoKeyGen(Environment* env,
SecretKeyGenConfig* params) {
auto bytes = DataPointer::Alloc(params->length);
if (!ncrypto::CSPRNG(static_cast<unsigned char*>(bytes.get()),
params->length)) {
return KeyGenJobStatus::FAILED;
}
params->out = ByteSource::Allocated(bytes.release());
return KeyGenJobStatus::OK;
}
MaybeLocal<Value> SecretKeyGenTraits::EncodeKey(Environment* env,
SecretKeyGenConfig* params) {
auto data = KeyObjectData::CreateSecret(std::move(params->out));
return KeyObjectHandle::Create(env, data).FromMaybe(Local<Value>());
}
namespace Keygen {
void Initialize(Environment* env, Local<Object> target) {
NidKeyPairGenJob::Initialize(env, target);
SecretKeyGenJob::Initialize(env, target);
}
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
NidKeyPairGenJob::RegisterExternalReferences(registry);
SecretKeyGenJob::RegisterExternalReferences(registry);
}
} // namespace Keygen
} // namespace crypto
} // namespace node

290
src/crypto/crypto_keygen.h Normal file
View File

@ -0,0 +1,290 @@
#ifndef SRC_CRYPTO_CRYPTO_KEYGEN_H_
#define SRC_CRYPTO_CRYPTO_KEYGEN_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "async_wrap.h"
#include "base_object.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "env.h"
#include "memory_tracker.h"
#include "v8.h"
namespace node::crypto {
namespace Keygen {
void Initialize(Environment* env, v8::Local<v8::Object> target);
void RegisterExternalReferences(ExternalReferenceRegistry* registry);
} // namespace Keygen
enum class KeyGenJobStatus {
OK,
FAILED
};
// A Base CryptoJob for generating secret keys or key pairs.
// The KeyGenTraits is largely responsible for the details of
// the implementation, while KeyGenJob handles the common
// mechanisms.
template <typename KeyGenTraits>
class KeyGenJob final : public CryptoJob<KeyGenTraits> {
public:
using AdditionalParams = typename KeyGenTraits::AdditionalParameters;
static void New(const v8::FunctionCallbackInfo<v8::Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args.IsConstructCall());
CryptoJobMode mode = GetCryptoJobMode(args[0]);
unsigned int offset = 1;
AdditionalParams params;
if (KeyGenTraits::AdditionalConfig(mode, args, &offset, &params)
.IsNothing()) {
// The KeyGenTraits::AdditionalConfig is responsible for
// calling an appropriate THROW_CRYPTO_* variant reporting
// whatever error caused initialization to fail.
return;
}
new KeyGenJob<KeyGenTraits>(env, args.This(), mode, std::move(params));
}
static void Initialize(
Environment* env,
v8::Local<v8::Object> target) {
CryptoJob<KeyGenTraits>::Initialize(New, env, target);
}
static void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
CryptoJob<KeyGenTraits>::RegisterExternalReferences(New, registry);
}
KeyGenJob(
Environment* env,
v8::Local<v8::Object> object,
CryptoJobMode mode,
AdditionalParams&& params)
: CryptoJob<KeyGenTraits>(
env,
object,
KeyGenTraits::Provider,
mode,
std::move(params)) {}
void DoThreadPoolWork() override {
AdditionalParams* params = CryptoJob<KeyGenTraits>::params();
switch (KeyGenTraits::DoKeyGen(AsyncWrap::env(), params)) {
case KeyGenJobStatus::OK:
status_ = KeyGenJobStatus::OK;
// Success!
break;
case KeyGenJobStatus::FAILED: {
CryptoErrorStore* errors = CryptoJob<KeyGenTraits>::errors();
errors->Capture();
if (errors->Empty())
errors->Insert(NodeCryptoError::KEY_GENERATION_JOB_FAILED);
}
}
}
v8::Maybe<void> ToResult(v8::Local<v8::Value>* err,
v8::Local<v8::Value>* result) override {
Environment* env = AsyncWrap::env();
CryptoErrorStore* errors = CryptoJob<KeyGenTraits>::errors();
AdditionalParams* params = CryptoJob<KeyGenTraits>::params();
if (status_ == KeyGenJobStatus::OK) {
v8::TryCatch try_catch(env->isolate());
if (KeyGenTraits::EncodeKey(env, params).ToLocal(result)) {
*err = Undefined(env->isolate());
} else {
CHECK(try_catch.HasCaught());
CHECK(try_catch.CanContinue());
*result = Undefined(env->isolate());
*err = try_catch.Exception();
}
} else {
if (errors->Empty()) errors->Capture();
CHECK(!errors->Empty());
*result = Undefined(env->isolate());
if (!errors->ToException(env).ToLocal(err)) {
return v8::Nothing<void>();
}
}
CHECK(!result->IsEmpty());
CHECK(!err->IsEmpty());
return v8::JustVoid();
}
SET_SELF_SIZE(KeyGenJob)
private:
KeyGenJobStatus status_ = KeyGenJobStatus::FAILED;
};
// A Base KeyGenTraits for Key Pair generation algorithms.
template <typename KeyPairAlgorithmTraits>
struct KeyPairGenTraits final {
using AdditionalParameters =
typename KeyPairAlgorithmTraits::AdditionalParameters;
static const AsyncWrap::ProviderType Provider =
AsyncWrap::PROVIDER_KEYPAIRGENREQUEST;
static constexpr const char* JobName = KeyPairAlgorithmTraits::JobName;
static v8::Maybe<void> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int* offset,
AdditionalParameters* params) {
// Notice that offset is a pointer. Each of the AdditionalConfig,
// GetPublicKeyEncodingFromJs, and GetPrivateKeyEncodingFromJs
// functions will update the value of the offset as they successfully
// process input parameters. This allows each job to have a variable
// number of input parameters specific to each job type.
if (KeyPairAlgorithmTraits::AdditionalConfig(mode, args, offset, params)
.IsNothing() ||
!KeyObjectData::GetPublicKeyEncodingFromJs(
args, offset, kKeyContextGenerate)
.To(&params->public_key_encoding) ||
!KeyObjectData::GetPrivateKeyEncodingFromJs(
args, offset, kKeyContextGenerate)
.To(&params->private_key_encoding)) {
return v8::Nothing<void>();
}
return v8::JustVoid();
}
static KeyGenJobStatus DoKeyGen(
Environment* env,
AdditionalParameters* params) {
ncrypto::EVPKeyCtxPointer ctx = KeyPairAlgorithmTraits::Setup(params);
if (!ctx)
return KeyGenJobStatus::FAILED;
// Generate the key
EVP_PKEY* pkey = nullptr;
if (!EVP_PKEY_keygen(ctx.get(), &pkey))
return KeyGenJobStatus::FAILED;
auto data = KeyObjectData::CreateAsymmetric(KeyType::kKeyTypePrivate,
ncrypto::EVPKeyPointer(pkey));
if (!data) [[unlikely]]
return KeyGenJobStatus::FAILED;
params->key = std::move(data);
return KeyGenJobStatus::OK;
}
static v8::MaybeLocal<v8::Value> EncodeKey(Environment* env,
AdditionalParameters* params) {
v8::Local<v8::Value> keys[2];
if (!params->key.ToEncodedPublicKey(
env, params->public_key_encoding, &keys[0]) ||
!params->key.ToEncodedPrivateKey(
env, params->private_key_encoding, &keys[1])) {
return {};
}
return v8::Array::New(env->isolate(), keys, arraysize(keys));
}
};
struct SecretKeyGenConfig final : public MemoryRetainer {
size_t length; // In bytes.
ByteSource out; // Placeholder for the generated key bytes.
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(SecretKeyGenConfig)
SET_SELF_SIZE(SecretKeyGenConfig)
};
struct SecretKeyGenTraits final {
using AdditionalParameters = SecretKeyGenConfig;
static const AsyncWrap::ProviderType Provider =
AsyncWrap::PROVIDER_KEYGENREQUEST;
static constexpr const char* JobName = "SecretKeyGenJob";
static v8::Maybe<void> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int* offset,
SecretKeyGenConfig* params);
static KeyGenJobStatus DoKeyGen(
Environment* env,
SecretKeyGenConfig* params);
static v8::MaybeLocal<v8::Value> EncodeKey(Environment* env,
SecretKeyGenConfig* params);
};
template <typename AlgorithmParams>
struct KeyPairGenConfig final : public MemoryRetainer {
ncrypto::EVPKeyPointer::PublicKeyEncodingConfig public_key_encoding;
ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig private_key_encoding;
KeyObjectData key;
AlgorithmParams params;
KeyPairGenConfig() = default;
explicit KeyPairGenConfig(KeyPairGenConfig&& other) noexcept
: public_key_encoding(other.public_key_encoding),
private_key_encoding(
std::forward<ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig>(
other.private_key_encoding)),
key(std::move(other.key)),
params(std::move(other.params)) {}
KeyPairGenConfig& operator=(KeyPairGenConfig&& other) noexcept {
if (&other == this) return *this;
this->~KeyPairGenConfig();
return *new (this) KeyPairGenConfig(std::move(other));
}
void MemoryInfo(MemoryTracker* tracker) const override {
tracker->TrackField("key", key);
if (private_key_encoding.passphrase.has_value()) {
auto& passphrase = private_key_encoding.passphrase.value();
tracker->TrackFieldWithSize("private_key_encoding.passphrase",
passphrase.size());
}
tracker->TrackField("params", params);
}
SET_MEMORY_INFO_NAME(KeyPairGenConfig)
SET_SELF_SIZE(KeyPairGenConfig)
};
struct NidKeyPairParams final : public MemoryRetainer {
int id;
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(NidKeyPairParams)
SET_SELF_SIZE(NidKeyPairParams)
};
using NidKeyPairGenConfig = KeyPairGenConfig<NidKeyPairParams>;
struct NidKeyPairGenTraits final {
using AdditionalParameters = NidKeyPairGenConfig;
static constexpr const char* JobName = "NidKeyPairGenJob";
static ncrypto::EVPKeyCtxPointer Setup(NidKeyPairGenConfig* params);
static v8::Maybe<void> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int* offset,
NidKeyPairGenConfig* params);
};
using NidKeyPairGenJob = KeyGenJob<KeyPairGenTraits<NidKeyPairGenTraits>>;
using SecretKeyGenJob = KeyGenJob<SecretKeyGenTraits>;
} // namespace node::crypto
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_KEYGEN_H_

1203
src/crypto/crypto_keys.cc Normal file

File diff suppressed because it is too large Load Diff

386
src/crypto/crypto_keys.h Normal file
View File

@ -0,0 +1,386 @@
#ifndef SRC_CRYPTO_CRYPTO_KEYS_H_
#define SRC_CRYPTO_CRYPTO_KEYS_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_util.h"
#include "base_object.h"
#include "env.h"
#include "memory_tracker.h"
#include "node_buffer.h"
#include "node_worker.h"
#include "v8.h"
#include <openssl/evp.h>
#include <memory>
#include <string>
namespace node::crypto {
enum KeyType {
kKeyTypeSecret,
kKeyTypePublic,
kKeyTypePrivate
};
enum KeyEncodingContext {
kKeyContextInput,
kKeyContextExport,
kKeyContextGenerate
};
enum class ParseKeyResult {
kParseKeyNotRecognized =
static_cast<int>(ncrypto::EVPKeyPointer::PKParseError::NOT_RECOGNIZED),
kParseKeyNeedPassphrase =
static_cast<int>(ncrypto::EVPKeyPointer::PKParseError::NEED_PASSPHRASE),
kParseKeyFailed =
static_cast<int>(ncrypto::EVPKeyPointer::PKParseError::FAILED),
kParseKeyOk,
};
// Objects of this class can safely be shared among threads.
class KeyObjectData final : public MemoryRetainer {
public:
static KeyObjectData CreateSecret(ByteSource key);
static KeyObjectData CreateAsymmetric(KeyType type,
ncrypto::EVPKeyPointer&& pkey);
KeyObjectData(std::nullptr_t = nullptr);
inline operator bool() const { return data_ != nullptr; }
KeyType GetKeyType() const;
// These functions allow unprotected access to the raw key material and should
// only be used to implement cryptographic operations requiring the key.
const ncrypto::EVPKeyPointer& GetAsymmetricKey() const;
const char* GetSymmetricKey() const;
size_t GetSymmetricKeySize() const;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(KeyObjectData)
SET_SELF_SIZE(KeyObjectData)
Mutex& mutex() const;
static v8::Maybe<ncrypto::EVPKeyPointer::PublicKeyEncodingConfig>
GetPublicKeyEncodingFromJs(const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int* offset,
KeyEncodingContext context);
static KeyObjectData GetPrivateKeyFromJs(
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int* offset,
bool allow_key_object);
static KeyObjectData GetPublicOrPrivateKeyFromJs(
const v8::FunctionCallbackInfo<v8::Value>& args, unsigned int* offset);
static v8::Maybe<ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig>
GetPrivateKeyEncodingFromJs(const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int* offset,
KeyEncodingContext context);
bool ToEncodedPublicKey(
Environment* env,
const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig& config,
v8::Local<v8::Value>* out);
bool ToEncodedPrivateKey(
Environment* env,
const ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig& config,
v8::Local<v8::Value>* out);
inline KeyObjectData addRef() const {
return KeyObjectData(key_type_, mutex_, data_);
}
inline KeyObjectData addRefWithType(KeyType type) const {
return KeyObjectData(type, mutex_, data_);
}
private:
explicit KeyObjectData(ByteSource symmetric_key);
explicit KeyObjectData(KeyType type, ncrypto::EVPKeyPointer&& pkey);
static KeyObjectData GetParsedKey(KeyType type,
Environment* env,
ncrypto::EVPKeyPointer&& pkey,
ParseKeyResult ret,
const char* default_msg);
KeyType key_type_;
mutable std::shared_ptr<Mutex> mutex_;
struct Data {
const ByteSource symmetric_key;
const ncrypto::EVPKeyPointer asymmetric_key;
explicit Data(ByteSource symmetric_key)
: symmetric_key(std::move(symmetric_key)) {}
explicit Data(ncrypto::EVPKeyPointer asymmetric_key)
: asymmetric_key(std::move(asymmetric_key)) {}
};
std::shared_ptr<Data> data_;
KeyObjectData(KeyType type,
std::shared_ptr<Mutex> mutex,
std::shared_ptr<Data> data)
: key_type_(type), mutex_(mutex), data_(data) {}
};
class KeyObjectHandle : public BaseObject {
public:
static bool HasInstance(Environment* env, v8::Local<v8::Value> value);
static v8::Local<v8::Function> Initialize(Environment* env);
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);
static v8::MaybeLocal<v8::Object> Create(Environment* env,
const KeyObjectData& data);
// TODO(tniessen): track the memory used by OpenSSL types
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(KeyObjectHandle)
SET_SELF_SIZE(KeyObjectHandle)
const KeyObjectData& Data();
protected:
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Init(const v8::FunctionCallbackInfo<v8::Value>& args);
static void InitECRaw(const v8::FunctionCallbackInfo<v8::Value>& args);
static void InitEDRaw(const v8::FunctionCallbackInfo<v8::Value>& args);
static void InitJWK(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetKeyDetail(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Equals(const v8::FunctionCallbackInfo<v8::Value>& args);
static void ExportJWK(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetAsymmetricKeyType(
const v8::FunctionCallbackInfo<v8::Value>& args);
v8::Local<v8::Value> GetAsymmetricKeyType() const;
static void CheckEcKeyData(const v8::FunctionCallbackInfo<v8::Value>& args);
bool CheckEcKeyData() const;
static void GetSymmetricKeySize(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void Export(const v8::FunctionCallbackInfo<v8::Value>& args);
v8::MaybeLocal<v8::Value> ExportSecretKey() const;
v8::MaybeLocal<v8::Value> ExportPublicKey(
const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig& config) const;
v8::MaybeLocal<v8::Value> ExportPrivateKey(
const ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig& config) const;
KeyObjectHandle(Environment* env,
v8::Local<v8::Object> wrap);
private:
KeyObjectData data_;
};
class NativeKeyObject : public BaseObject {
public:
static void Initialize(Environment* env, v8::Local<v8::Object> target);
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void CreateNativeKeyObjectClass(
const v8::FunctionCallbackInfo<v8::Value>& args);
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(NativeKeyObject)
SET_SELF_SIZE(NativeKeyObject)
class KeyObjectTransferData : public worker::TransferData {
public:
explicit KeyObjectTransferData(const KeyObjectData& data)
: data_(data.addRef()) {}
BaseObjectPtr<BaseObject> Deserialize(
Environment* env,
v8::Local<v8::Context> context,
std::unique_ptr<worker::TransferData> self) override;
SET_MEMORY_INFO_NAME(KeyObjectTransferData)
SET_SELF_SIZE(KeyObjectTransferData)
SET_NO_MEMORY_INFO()
private:
KeyObjectData data_;
};
BaseObject::TransferMode GetTransferMode() const override;
std::unique_ptr<worker::TransferData> CloneForMessaging() const override;
private:
NativeKeyObject(Environment* env,
v8::Local<v8::Object> wrap,
const KeyObjectData& handle_data)
: BaseObject(env, wrap), handle_data_(handle_data.addRef()) {
MakeWeak();
}
KeyObjectData handle_data_;
};
enum WebCryptoKeyFormat {
kWebCryptoKeyFormatRaw,
kWebCryptoKeyFormatPKCS8,
kWebCryptoKeyFormatSPKI,
kWebCryptoKeyFormatJWK
};
enum class WebCryptoKeyExportStatus {
OK,
INVALID_KEY_TYPE,
FAILED
};
template <typename KeyExportTraits>
class KeyExportJob final : public CryptoJob<KeyExportTraits> {
public:
using AdditionalParams = typename KeyExportTraits::AdditionalParameters;
static void New(const v8::FunctionCallbackInfo<v8::Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args.IsConstructCall());
CryptoJobMode mode = GetCryptoJobMode(args[0]);
CHECK(args[1]->IsUint32()); // Export Type
CHECK(args[2]->IsObject()); // KeyObject
WebCryptoKeyFormat format =
static_cast<WebCryptoKeyFormat>(args[1].As<v8::Uint32>()->Value());
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args[2]);
CHECK_NOT_NULL(key);
AdditionalParams params;
if (KeyExportTraits::AdditionalConfig(args, 3, &params).IsNothing()) {
// The KeyExportTraits::AdditionalConfig is responsible for
// calling an appropriate THROW_CRYPTO_* variant reporting
// whatever error caused initialization to fail.
return;
}
new KeyExportJob<KeyExportTraits>(
env,
args.This(),
mode,
key->Data(),
format,
std::move(params));
}
static void Initialize(
Environment* env,
v8::Local<v8::Object> target) {
CryptoJob<KeyExportTraits>::Initialize(New, env, target);
}
static void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
CryptoJob<KeyExportTraits>::RegisterExternalReferences(New, registry);
}
KeyExportJob(Environment* env,
v8::Local<v8::Object> object,
CryptoJobMode mode,
const KeyObjectData& key,
WebCryptoKeyFormat format,
AdditionalParams&& params)
: CryptoJob<KeyExportTraits>(env,
object,
AsyncWrap::PROVIDER_KEYEXPORTREQUEST,
mode,
std::move(params)),
key_(key.addRef()),
format_(format) {}
WebCryptoKeyFormat format() const { return format_; }
void DoThreadPoolWork() override {
const WebCryptoKeyExportStatus status =
KeyExportTraits::DoExport(
key_,
format_,
*CryptoJob<KeyExportTraits>::params(),
&out_);
if (status == WebCryptoKeyExportStatus::OK) {
// Success!
return;
}
CryptoErrorStore* errors = CryptoJob<KeyExportTraits>::errors();
errors->Capture();
if (errors->Empty()) {
switch (status) {
case WebCryptoKeyExportStatus::OK:
UNREACHABLE();
break;
case WebCryptoKeyExportStatus::INVALID_KEY_TYPE:
errors->Insert(NodeCryptoError::INVALID_KEY_TYPE);
break;
case WebCryptoKeyExportStatus::FAILED:
errors->Insert(NodeCryptoError::CIPHER_JOB_FAILED);
break;
}
}
}
v8::Maybe<void> ToResult(v8::Local<v8::Value>* err,
v8::Local<v8::Value>* result) override {
Environment* env = AsyncWrap::env();
CryptoErrorStore* errors = CryptoJob<KeyExportTraits>::errors();
if (out_.size() > 0) {
CHECK(errors->Empty());
*err = v8::Undefined(env->isolate());
*result = out_.ToArrayBuffer(env);
if (result->IsEmpty()) {
return v8::Nothing<void>();
}
} else {
if (errors->Empty()) errors->Capture();
CHECK(!errors->Empty());
*result = v8::Undefined(env->isolate());
if (!errors->ToException(env).ToLocal(err)) {
return v8::Nothing<void>();
}
}
CHECK(!result->IsEmpty());
CHECK(!err->IsEmpty());
return v8::JustVoid();
}
SET_SELF_SIZE(KeyExportJob)
void MemoryInfo(MemoryTracker* tracker) const override {
tracker->TrackFieldWithSize("out", out_.size());
CryptoJob<KeyExportTraits>::MemoryInfo(tracker);
}
private:
KeyObjectData key_;
WebCryptoKeyFormat format_;
ByteSource out_;
};
WebCryptoKeyExportStatus PKEY_SPKI_Export(const KeyObjectData& key_data,
ByteSource* out);
WebCryptoKeyExportStatus PKEY_PKCS8_Export(const KeyObjectData& key_data,
ByteSource* out);
namespace Keys {
void Initialize(Environment* env, v8::Local<v8::Object> target);
void RegisterExternalReferences(ExternalReferenceRegistry* registry);
} // namespace Keys
} // namespace node::crypto
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_KEYS_H_

137
src/crypto/crypto_pbkdf2.cc Normal file
View File

@ -0,0 +1,137 @@
#include "crypto/crypto_pbkdf2.h"
#include "async_wrap-inl.h"
#include "crypto/crypto_util.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "node_buffer.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
namespace node {
using ncrypto::Digest;
using v8::FunctionCallbackInfo;
using v8::Int32;
using v8::JustVoid;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Nothing;
using v8::Value;
namespace crypto {
PBKDF2Config::PBKDF2Config(PBKDF2Config&& other) noexcept
: mode(other.mode),
pass(std::move(other.pass)),
salt(std::move(other.salt)),
iterations(other.iterations),
length(other.length),
digest(other.digest) {}
PBKDF2Config& PBKDF2Config::operator=(PBKDF2Config&& other) noexcept {
if (&other == this) return *this;
this->~PBKDF2Config();
return *new (this) PBKDF2Config(std::move(other));
}
void PBKDF2Config::MemoryInfo(MemoryTracker* tracker) const {
// The job is sync, the PBKDF2Config does not own the data.
if (mode == kCryptoJobAsync) {
tracker->TrackFieldWithSize("pass", pass.size());
tracker->TrackFieldWithSize("salt", salt.size());
}
}
MaybeLocal<Value> PBKDF2Traits::EncodeOutput(Environment* env,
const PBKDF2Config& params,
ByteSource* out) {
return out->ToArrayBuffer(env);
}
// The input arguments for the job are:
// 1. CryptoJobMode
// 2. The password
// 3. The salt
// 4. The number of iterations
// 5. The number of bytes to generate
// 6. The digest algorithm name
Maybe<void> PBKDF2Traits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
PBKDF2Config* params) {
Environment* env = Environment::GetCurrent(args);
params->mode = mode;
ArrayBufferOrViewContents<char> pass(args[offset]);
ArrayBufferOrViewContents<char> salt(args[offset + 1]);
if (!pass.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "pass is too large");
return Nothing<void>();
}
if (!salt.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "salt is too large");
return Nothing<void>();
}
params->pass = mode == kCryptoJobAsync
? pass.ToCopy()
: pass.ToByteSource();
params->salt = mode == kCryptoJobAsync
? salt.ToCopy()
: salt.ToByteSource();
CHECK(args[offset + 2]->IsInt32()); // iteration_count
CHECK(args[offset + 3]->IsInt32()); // length
CHECK(args[offset + 4]->IsString()); // digest_name
params->iterations = args[offset + 2].As<Int32>()->Value();
if (params->iterations < 0) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "iterations must be <= %d", INT_MAX);
return Nothing<void>();
}
params->length = args[offset + 3].As<Int32>()->Value();
if (params->length < 0) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "length must be <= %d", INT_MAX);
return Nothing<void>();
}
Utf8Value name(args.GetIsolate(), args[offset + 4]);
params->digest = Digest::FromName(*name);
if (!params->digest) [[unlikely]] {
THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid digest: %s", *name);
return Nothing<void>();
}
return JustVoid();
}
bool PBKDF2Traits::DeriveBits(Environment* env,
const PBKDF2Config& params,
ByteSource* out,
CryptoJobMode mode) {
// Both pass and salt may be zero length here.
auto dp = ncrypto::pbkdf2(params.digest,
ncrypto::Buffer<const char>{
.data = params.pass.data<const char>(),
.len = params.pass.size(),
},
ncrypto::Buffer<const unsigned char>{
.data = params.salt.data<unsigned char>(),
.len = params.salt.size(),
},
params.iterations,
params.length);
if (!dp) return false;
DCHECK(!dp.isSecure());
*out = ByteSource::Allocated(dp.release());
return true;
}
} // namespace crypto
} // namespace node

View File

@ -0,0 +1,74 @@
#ifndef SRC_CRYPTO_CRYPTO_PBKDF2_H_
#define SRC_CRYPTO_CRYPTO_PBKDF2_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_util.h"
#include "async_wrap.h"
#include "env.h"
#include "memory_tracker.h"
#include "v8.h"
namespace node {
namespace crypto {
// PBKDF2 is a pseudo-random key derivation scheme defined
// in https://tools.ietf.org/html/rfc8018
//
// The algorithm takes as input a password and salt
// (both of which may, but should not be zero-length),
// a number of iterations, a hash digest algorithm, and
// an output length.
//
// The salt should be as unique as possible, and should
// be at least 16 bytes in length.
//
// The iteration count should be as high as possible.
struct PBKDF2Config final : public MemoryRetainer {
CryptoJobMode mode;
ByteSource pass;
ByteSource salt;
int32_t iterations;
int32_t length;
ncrypto::Digest digest;
PBKDF2Config() = default;
explicit PBKDF2Config(PBKDF2Config&& other) noexcept;
PBKDF2Config& operator=(PBKDF2Config&& other) noexcept;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(PBKDF2Config)
SET_SELF_SIZE(PBKDF2Config)
};
struct PBKDF2Traits final {
using AdditionalParameters = PBKDF2Config;
static constexpr const char* JobName = "PBKDF2Job";
static constexpr AsyncWrap::ProviderType Provider =
AsyncWrap::PROVIDER_PBKDF2REQUEST;
static v8::Maybe<void> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
PBKDF2Config* params);
static bool DeriveBits(Environment* env,
const PBKDF2Config& params,
ByteSource* out,
CryptoJobMode mode);
static v8::MaybeLocal<v8::Value> EncodeOutput(Environment* env,
const PBKDF2Config& params,
ByteSource* out);
};
using PBKDF2Job = DeriveBitsJob<PBKDF2Traits>;
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_PBKDF2_H_

227
src/crypto/crypto_random.cc Normal file
View File

@ -0,0 +1,227 @@
#include "crypto/crypto_random.h"
#include "async_wrap-inl.h"
#include "crypto/crypto_util.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "ncrypto.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
#include <compare>
namespace node {
using ncrypto::BignumPointer;
using ncrypto::ClearErrorOnReturn;
using ncrypto::DataPointer;
using v8::ArrayBuffer;
using v8::Boolean;
using v8::FunctionCallbackInfo;
using v8::Int32;
using v8::JustVoid;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Nothing;
using v8::Object;
using v8::Uint32;
using v8::Undefined;
using v8::Value;
namespace crypto {
namespace {
BignumPointer::PrimeCheckCallback getPrimeCheckCallback(Environment* env) {
// The callback is used to check if the operation should be stopped.
// Currently, the only check we perform is if env->is_stopping()
// is true.
return [env](int a, int b) -> bool { return !env->is_stopping(); };
}
} // namespace
MaybeLocal<Value> RandomBytesTraits::EncodeOutput(
Environment* env, const RandomBytesConfig& params, ByteSource* unused) {
return Undefined(env->isolate());
}
Maybe<void> RandomBytesTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
RandomBytesConfig* params) {
CHECK(IsAnyBufferSource(args[offset])); // Buffer to fill
CHECK(args[offset + 1]->IsUint32()); // Offset
CHECK(args[offset + 2]->IsUint32()); // Size
ArrayBufferOrViewContents<unsigned char> in(args[offset]);
const uint32_t byte_offset = args[offset + 1].As<Uint32>()->Value();
const uint32_t size = args[offset + 2].As<Uint32>()->Value();
CHECK_GE(byte_offset + size, byte_offset); // Overflow check.
CHECK_LE(byte_offset + size, in.size()); // Bounds check.
params->buffer = in.data() + byte_offset;
params->size = size;
return JustVoid();
}
bool RandomBytesTraits::DeriveBits(Environment* env,
const RandomBytesConfig& params,
ByteSource* unused,
CryptoJobMode mode) {
return ncrypto::CSPRNG(params.buffer, params.size);
}
void RandomPrimeConfig::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackFieldWithSize("prime", prime ? bits * 8 : 0);
}
MaybeLocal<Value> RandomPrimeTraits::EncodeOutput(
Environment* env, const RandomPrimeConfig& params, ByteSource* unused) {
size_t size = params.prime.byteLength();
auto store = ArrayBuffer::NewBackingStore(env->isolate(), size);
CHECK_EQ(size,
BignumPointer::EncodePaddedInto(
params.prime.get(),
reinterpret_cast<unsigned char*>(store->Data()),
size));
return ArrayBuffer::New(env->isolate(), std::move(store));
}
Maybe<void> RandomPrimeTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
RandomPrimeConfig* params) {
ClearErrorOnReturn clear_error;
Environment* env = Environment::GetCurrent(args);
CHECK(args[offset]->IsUint32()); // Size
CHECK(args[offset + 1]->IsBoolean()); // Safe
const uint32_t size = args[offset].As<Uint32>()->Value();
bool safe = args[offset + 1]->IsTrue();
if (!args[offset + 2]->IsUndefined()) {
ArrayBufferOrViewContents<unsigned char> add(args[offset + 2]);
params->add.reset(add.data(), add.size());
if (!params->add) [[unlikely]] {
THROW_ERR_CRYPTO_OPERATION_FAILED(env, "could not generate prime");
return Nothing<void>();
}
}
if (!args[offset + 3]->IsUndefined()) {
ArrayBufferOrViewContents<unsigned char> rem(args[offset + 3]);
params->rem.reset(rem.data(), rem.size());
if (!params->rem) [[unlikely]] {
THROW_ERR_CRYPTO_OPERATION_FAILED(env, "could not generate prime");
return Nothing<void>();
}
}
// The JS interface already ensures that the (positive) size fits into an int.
int bits = static_cast<int>(size);
CHECK_GT(bits, 0);
if (params->add) {
if (BignumPointer::GetBitCount(params->add.get()) > bits) [[unlikely]] {
// If we allowed this, the best case would be returning a static prime
// that wasn't generated randomly. The worst case would be an infinite
// loop within OpenSSL, blocking the main thread or one of the threads
// in the thread pool.
THROW_ERR_OUT_OF_RANGE(env, "invalid options.add");
return Nothing<void>();
}
if (params->rem && params->add <= params->rem) [[unlikely]] {
// This would definitely lead to an infinite loop if allowed since
// OpenSSL does not check this condition.
THROW_ERR_OUT_OF_RANGE(env, "invalid options.rem");
return Nothing<void>();
}
}
params->bits = bits;
params->safe = safe;
params->prime = BignumPointer::NewSecure();
if (!params->prime) [[unlikely]] {
THROW_ERR_CRYPTO_OPERATION_FAILED(env, "could not generate prime");
return Nothing<void>();
}
return JustVoid();
}
bool RandomPrimeTraits::DeriveBits(Environment* env,
const RandomPrimeConfig& params,
ByteSource* unused,
CryptoJobMode mode) {
return params.prime.generate(
BignumPointer::PrimeConfig{
.bits = params.bits,
.safe = params.safe,
.add = params.add,
.rem = params.rem,
},
getPrimeCheckCallback(env));
}
void CheckPrimeConfig::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackFieldWithSize("prime", candidate ? candidate.byteLength() : 0);
}
Maybe<void> CheckPrimeTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
CheckPrimeConfig* params) {
ArrayBufferOrViewContents<unsigned char> candidate(args[offset]);
params->candidate = BignumPointer(candidate.data(), candidate.size());
if (!params->candidate) {
ThrowCryptoError(
Environment::GetCurrent(args), ERR_get_error(), "BignumPointer");
return Nothing<void>();
}
CHECK(args[offset + 1]->IsInt32()); // Checks
params->checks = args[offset + 1].As<Int32>()->Value();
CHECK_GE(params->checks, 0);
return JustVoid();
}
bool CheckPrimeTraits::DeriveBits(Environment* env,
const CheckPrimeConfig& params,
ByteSource* out,
CryptoJobMode mode) {
int ret = params.candidate.isPrime(params.checks, getPrimeCheckCallback(env));
if (ret < 0) [[unlikely]]
return false;
auto buf = DataPointer::Alloc(1);
static_cast<char*>(buf.get())[0] = ret;
*out = ByteSource::Allocated(buf.release());
return true;
}
MaybeLocal<Value> CheckPrimeTraits::EncodeOutput(Environment* env,
const CheckPrimeConfig& params,
ByteSource* out) {
return Boolean::New(env->isolate(), out->data<char>()[0] != 0);
}
namespace Random {
void Initialize(Environment* env, Local<Object> target) {
RandomBytesJob::Initialize(env, target);
RandomPrimeJob::Initialize(env, target);
CheckPrimeJob::Initialize(env, target);
}
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
RandomBytesJob::RegisterExternalReferences(registry);
RandomPrimeJob::RegisterExternalReferences(registry);
CheckPrimeJob::RegisterExternalReferences(registry);
}
} // namespace Random
} // namespace crypto
} // namespace node

124
src/crypto/crypto_random.h Normal file
View File

@ -0,0 +1,124 @@
#ifndef SRC_CRYPTO_CRYPTO_RANDOM_H_
#define SRC_CRYPTO_CRYPTO_RANDOM_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "base_object.h"
#include "crypto/crypto_util.h"
#include "env.h"
#include "memory_tracker.h"
#include "node_internals.h"
#include "v8.h"
namespace node {
namespace crypto {
struct RandomBytesConfig final : public MemoryRetainer {
unsigned char* buffer;
size_t size;
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(RandomBytesConfig)
SET_SELF_SIZE(RandomBytesConfig)
};
struct RandomBytesTraits final {
using AdditionalParameters = RandomBytesConfig;
static constexpr const char* JobName = "RandomBytesJob";
static constexpr AsyncWrap::ProviderType Provider =
AsyncWrap::PROVIDER_RANDOMBYTESREQUEST;
static v8::Maybe<void> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
RandomBytesConfig* params);
static bool DeriveBits(Environment* env,
const RandomBytesConfig& params,
ByteSource* out_,
CryptoJobMode mode);
static v8::MaybeLocal<v8::Value> EncodeOutput(Environment* env,
const RandomBytesConfig& params,
ByteSource* unused);
};
using RandomBytesJob = DeriveBitsJob<RandomBytesTraits>;
struct RandomPrimeConfig final : public MemoryRetainer {
ncrypto::BignumPointer prime;
ncrypto::BignumPointer rem;
ncrypto::BignumPointer add;
int bits;
bool safe;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(RandomPrimeConfig)
SET_SELF_SIZE(RandomPrimeConfig)
};
struct RandomPrimeTraits final {
using AdditionalParameters = RandomPrimeConfig;
static constexpr const char* JobName = "RandomPrimeJob";
static constexpr AsyncWrap::ProviderType Provider =
AsyncWrap::PROVIDER_RANDOMPRIMEREQUEST;
static v8::Maybe<void> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
RandomPrimeConfig* params);
static bool DeriveBits(Environment* env,
const RandomPrimeConfig& params,
ByteSource* out_,
CryptoJobMode mode);
static v8::MaybeLocal<v8::Value> EncodeOutput(Environment* env,
const RandomPrimeConfig& params,
ByteSource* unused);
};
using RandomPrimeJob = DeriveBitsJob<RandomPrimeTraits>;
struct CheckPrimeConfig final : public MemoryRetainer {
ncrypto::BignumPointer candidate;
int checks = 1;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(CheckPrimeConfig)
SET_SELF_SIZE(CheckPrimeConfig)
};
struct CheckPrimeTraits final {
using AdditionalParameters = CheckPrimeConfig;
static constexpr const char* JobName = "CheckPrimeJob";
static constexpr AsyncWrap::ProviderType Provider =
AsyncWrap::PROVIDER_CHECKPRIMEREQUEST;
static v8::Maybe<void> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
CheckPrimeConfig* params);
static bool DeriveBits(Environment* env,
const CheckPrimeConfig& params,
ByteSource* out,
CryptoJobMode mode);
static v8::MaybeLocal<v8::Value> EncodeOutput(Environment* env,
const CheckPrimeConfig& params,
ByteSource* out);
};
using CheckPrimeJob = DeriveBitsJob<CheckPrimeTraits>;
namespace Random {
void Initialize(Environment* env, v8::Local<v8::Object> target);
void RegisterExternalReferences(ExternalReferenceRegistry* registry);
} // namespace Random
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_RANDOM_H_

550
src/crypto/crypto_rsa.cc Normal file
View File

@ -0,0 +1,550 @@
#include "crypto/crypto_rsa.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "crypto/crypto_bio.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
#include <openssl/bn.h>
#include <openssl/rsa.h>
namespace node {
using ncrypto::BignumPointer;
using ncrypto::DataPointer;
using ncrypto::Digest;
using ncrypto::EVPKeyCtxPointer;
using ncrypto::EVPKeyPointer;
using ncrypto::RSAPointer;
using v8::ArrayBuffer;
using v8::BackingStoreInitializationMode;
using v8::FunctionCallbackInfo;
using v8::Int32;
using v8::Integer;
using v8::JustVoid;
using v8::Local;
using v8::Maybe;
using v8::Nothing;
using v8::Number;
using v8::Object;
using v8::String;
using v8::Uint32;
using v8::Value;
namespace crypto {
EVPKeyCtxPointer RsaKeyGenTraits::Setup(RsaKeyPairGenConfig* params) {
auto ctx = EVPKeyCtxPointer::NewFromID(
params->params.variant == kKeyVariantRSA_PSS ? EVP_PKEY_RSA_PSS
: EVP_PKEY_RSA);
if (!ctx.initForKeygen() ||
!ctx.setRsaKeygenBits(params->params.modulus_bits)) {
return {};
}
// 0x10001 is the default RSA exponent.
if (params->params.exponent != EVPKeyCtxPointer::kDefaultRsaExponent) {
auto bn = BignumPointer::New();
if (!bn.setWord(params->params.exponent) ||
!ctx.setRsaKeygenPubExp(std::move(bn))) {
return {};
}
}
if (params->params.variant == kKeyVariantRSA_PSS) {
if (params->params.md && !ctx.setRsaPssKeygenMd(params->params.md)) {
return {};
}
// TODO(tniessen): This appears to only be necessary in OpenSSL 3, while
// OpenSSL 1.1.1 behaves as recommended by RFC 8017 and defaults the MGF1
// hash algorithm to the RSA-PSS hashAlgorithm. Remove this code if the
// behavior of OpenSSL 3 changes.
auto& mgf1_md = params->params.mgf1_md;
if (!mgf1_md && params->params.md) {
mgf1_md = params->params.md;
}
if (mgf1_md && !ctx.setRsaPssKeygenMgf1Md(mgf1_md)) {
return {};
}
int saltlen = params->params.saltlen;
if (saltlen < 0 && params->params.md) {
saltlen = params->params.md.size();
}
if (saltlen >= 0 && !ctx.setRsaPssSaltlen(saltlen)) {
return {};
}
}
return ctx;
}
// Input parameters to the RsaKeyGenJob:
// For key variants RSA-OAEP and RSA-SSA-PKCS1-v1_5
// 1. CryptoJobMode
// 2. Key Variant
// 3. Modulus Bits
// 4. Public Exponent
// 5. Public Format
// 6. Public Type
// 7. Private Format
// 8. Private Type
// 9. Cipher
// 10. Passphrase
//
// For RSA-PSS variant
// 1. CryptoJobMode
// 2. Key Variant
// 3. Modulus Bits
// 4. Public Exponent
// 5. Digest
// 6. mgf1 Digest
// 7. Salt length
// 8. Public Format
// 9. Public Type
// 10. Private Format
// 11. Private Type
// 12. Cipher
// 13. Passphrase
Maybe<void> RsaKeyGenTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int* offset,
RsaKeyPairGenConfig* params) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[*offset]->IsUint32()); // Variant
CHECK(args[*offset + 1]->IsUint32()); // Modulus bits
CHECK(args[*offset + 2]->IsUint32()); // Exponent
params->params.variant =
static_cast<RSAKeyVariant>(args[*offset].As<Uint32>()->Value());
CHECK_IMPLIES(params->params.variant != kKeyVariantRSA_PSS,
args.Length() == 10);
CHECK_IMPLIES(params->params.variant == kKeyVariantRSA_PSS,
args.Length() == 13);
params->params.modulus_bits = args[*offset + 1].As<Uint32>()->Value();
params->params.exponent = args[*offset + 2].As<Uint32>()->Value();
*offset += 3;
if (params->params.variant == kKeyVariantRSA_PSS) {
if (!args[*offset]->IsUndefined()) {
CHECK(args[*offset]->IsString());
Utf8Value digest(env->isolate(), args[*offset]);
params->params.md = Digest::FromName(*digest);
if (!params->params.md) {
THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid digest: %s", *digest);
return Nothing<void>();
}
}
if (!args[*offset + 1]->IsUndefined()) {
CHECK(args[*offset + 1]->IsString());
Utf8Value digest(env->isolate(), args[*offset + 1]);
params->params.mgf1_md = Digest::FromName(*digest);
if (!params->params.mgf1_md) {
THROW_ERR_CRYPTO_INVALID_DIGEST(
env, "Invalid MGF1 digest: %s", *digest);
return Nothing<void>();
}
}
if (!args[*offset + 2]->IsUndefined()) {
CHECK(args[*offset + 2]->IsInt32());
params->params.saltlen = args[*offset + 2].As<Int32>()->Value();
if (params->params.saltlen < 0) {
THROW_ERR_OUT_OF_RANGE(
env,
"salt length is out of range");
return Nothing<void>();
}
}
*offset += 3;
}
return JustVoid();
}
namespace {
WebCryptoKeyExportStatus RSA_JWK_Export(const KeyObjectData& key_data,
const RSAKeyExportConfig& params,
ByteSource* out) {
return WebCryptoKeyExportStatus::FAILED;
}
using Cipher_t = DataPointer(const EVPKeyPointer& key,
const ncrypto::Rsa::CipherParams& params,
const ncrypto::Buffer<const void> in);
template <Cipher_t cipher>
WebCryptoCipherStatus RSA_Cipher(Environment* env,
const KeyObjectData& key_data,
const RSACipherConfig& params,
const ByteSource& in,
ByteSource* out) {
CHECK_NE(key_data.GetKeyType(), kKeyTypeSecret);
Mutex::ScopedLock lock(key_data.mutex());
const auto& m_pkey = key_data.GetAsymmetricKey();
const ncrypto::Rsa::CipherParams nparams{
.padding = params.padding,
.digest = params.digest,
.label = params.label,
};
auto data = cipher(m_pkey, nparams, in);
if (!data) return WebCryptoCipherStatus::FAILED;
DCHECK(!data.isSecure());
*out = ByteSource::Allocated(data.release());
return WebCryptoCipherStatus::OK;
}
} // namespace
Maybe<void> RSAKeyExportTraits::AdditionalConfig(
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
RSAKeyExportConfig* params) {
CHECK(args[offset]->IsUint32()); // RSAKeyVariant
params->variant =
static_cast<RSAKeyVariant>(args[offset].As<Uint32>()->Value());
return JustVoid();
}
WebCryptoKeyExportStatus RSAKeyExportTraits::DoExport(
const KeyObjectData& key_data,
WebCryptoKeyFormat format,
const RSAKeyExportConfig& params,
ByteSource* out) {
CHECK_NE(key_data.GetKeyType(), kKeyTypeSecret);
switch (format) {
case kWebCryptoKeyFormatRaw:
// Not supported for RSA keys of either type
return WebCryptoKeyExportStatus::FAILED;
case kWebCryptoKeyFormatJWK:
return RSA_JWK_Export(key_data, params, out);
case kWebCryptoKeyFormatPKCS8:
if (key_data.GetKeyType() != kKeyTypePrivate)
return WebCryptoKeyExportStatus::INVALID_KEY_TYPE;
return PKEY_PKCS8_Export(key_data, out);
case kWebCryptoKeyFormatSPKI:
if (key_data.GetKeyType() != kKeyTypePublic)
return WebCryptoKeyExportStatus::INVALID_KEY_TYPE;
return PKEY_SPKI_Export(key_data, out);
default:
UNREACHABLE();
}
}
RSACipherConfig::RSACipherConfig(RSACipherConfig&& other) noexcept
: mode(other.mode),
label(std::move(other.label)),
padding(other.padding),
digest(other.digest) {}
void RSACipherConfig::MemoryInfo(MemoryTracker* tracker) const {
if (mode == kCryptoJobAsync)
tracker->TrackFieldWithSize("label", label.size());
}
Maybe<void> RSACipherTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
WebCryptoCipherMode cipher_mode,
RSACipherConfig* params) {
Environment* env = Environment::GetCurrent(args);
params->mode = mode;
params->padding = RSA_PKCS1_OAEP_PADDING;
CHECK(args[offset]->IsUint32());
RSAKeyVariant variant =
static_cast<RSAKeyVariant>(args[offset].As<Uint32>()->Value());
switch (variant) {
case kKeyVariantRSA_OAEP: {
CHECK(args[offset + 1]->IsString()); // digest
Utf8Value digest(env->isolate(), args[offset + 1]);
params->digest = Digest::FromName(*digest);
if (!params->digest) {
THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid digest: %s", *digest);
return Nothing<void>();
}
if (IsAnyBufferSource(args[offset + 2])) {
ArrayBufferOrViewContents<char> label(args[offset + 2]);
if (!label.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "label is too big");
return Nothing<void>();
}
params->label = label.ToCopy();
}
break;
}
default:
THROW_ERR_CRYPTO_INVALID_KEYTYPE(env);
return Nothing<void>();
}
return JustVoid();
}
WebCryptoCipherStatus RSACipherTraits::DoCipher(Environment* env,
const KeyObjectData& key_data,
WebCryptoCipherMode cipher_mode,
const RSACipherConfig& params,
const ByteSource& in,
ByteSource* out) {
switch (cipher_mode) {
case kWebCryptoCipherEncrypt:
CHECK_EQ(key_data.GetKeyType(), kKeyTypePublic);
return RSA_Cipher<ncrypto::Rsa::encrypt>(env, key_data, params, in, out);
case kWebCryptoCipherDecrypt:
CHECK_EQ(key_data.GetKeyType(), kKeyTypePrivate);
return RSA_Cipher<ncrypto::Rsa::decrypt>(env, key_data, params, in, out);
}
return WebCryptoCipherStatus::FAILED;
}
bool ExportJWKRsaKey(Environment* env,
const KeyObjectData& key,
Local<Object> target) {
Mutex::ScopedLock lock(key.mutex());
const auto& m_pkey = key.GetAsymmetricKey();
const ncrypto::Rsa rsa = m_pkey;
if (!rsa ||
target->Set(env->context(), env->jwk_kty_string(), env->jwk_rsa_string())
.IsNothing()) {
return false;
}
auto pub_key = rsa.getPublicKey();
if (SetEncodedValue(env, target, env->jwk_n_string(), pub_key.n)
.IsNothing() ||
SetEncodedValue(env, target, env->jwk_e_string(), pub_key.e)
.IsNothing()) {
return false;
}
if (key.GetKeyType() == kKeyTypePrivate) {
auto pvt_key = rsa.getPrivateKey();
if (SetEncodedValue(env, target, env->jwk_d_string(), pub_key.d)
.IsNothing() ||
SetEncodedValue(env, target, env->jwk_p_string(), pvt_key.p)
.IsNothing() ||
SetEncodedValue(env, target, env->jwk_q_string(), pvt_key.q)
.IsNothing() ||
SetEncodedValue(env, target, env->jwk_dp_string(), pvt_key.dp)
.IsNothing() ||
SetEncodedValue(env, target, env->jwk_dq_string(), pvt_key.dq)
.IsNothing() ||
SetEncodedValue(env, target, env->jwk_qi_string(), pvt_key.qi)
.IsNothing()) {
return false;
}
}
return true;
}
KeyObjectData ImportJWKRsaKey(Environment* env,
Local<Object> jwk,
const FunctionCallbackInfo<Value>& args,
unsigned int offset) {
Local<Value> n_value;
Local<Value> e_value;
Local<Value> d_value;
if (!jwk->Get(env->context(), env->jwk_n_string()).ToLocal(&n_value) ||
!jwk->Get(env->context(), env->jwk_e_string()).ToLocal(&e_value) ||
!jwk->Get(env->context(), env->jwk_d_string()).ToLocal(&d_value) ||
!n_value->IsString() ||
!e_value->IsString()) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK RSA key");
return {};
}
if (!d_value->IsUndefined() && !d_value->IsString()) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK RSA key");
return {};
}
KeyType type = d_value->IsString() ? kKeyTypePrivate : kKeyTypePublic;
RSAPointer rsa(RSA_new());
ncrypto::Rsa rsa_view(rsa.get());
ByteSource n = ByteSource::FromEncodedString(env, n_value.As<String>());
ByteSource e = ByteSource::FromEncodedString(env, e_value.As<String>());
if (!rsa_view.setPublicKey(n.ToBN(), e.ToBN())) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK RSA key");
return {};
}
if (type == kKeyTypePrivate) {
Local<Value> p_value;
Local<Value> q_value;
Local<Value> dp_value;
Local<Value> dq_value;
Local<Value> qi_value;
if (!jwk->Get(env->context(), env->jwk_p_string()).ToLocal(&p_value) ||
!jwk->Get(env->context(), env->jwk_q_string()).ToLocal(&q_value) ||
!jwk->Get(env->context(), env->jwk_dp_string()).ToLocal(&dp_value) ||
!jwk->Get(env->context(), env->jwk_dq_string()).ToLocal(&dq_value) ||
!jwk->Get(env->context(), env->jwk_qi_string()).ToLocal(&qi_value)) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK RSA key");
return {};
}
if (!p_value->IsString() ||
!q_value->IsString() ||
!dp_value->IsString() ||
!dq_value->IsString() ||
!qi_value->IsString()) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK RSA key");
return {};
}
ByteSource d = ByteSource::FromEncodedString(env, d_value.As<String>());
ByteSource q = ByteSource::FromEncodedString(env, q_value.As<String>());
ByteSource p = ByteSource::FromEncodedString(env, p_value.As<String>());
ByteSource dp = ByteSource::FromEncodedString(env, dp_value.As<String>());
ByteSource dq = ByteSource::FromEncodedString(env, dq_value.As<String>());
ByteSource qi = ByteSource::FromEncodedString(env, qi_value.As<String>());
if (!rsa_view.setPrivateKey(
d.ToBN(), q.ToBN(), p.ToBN(), dp.ToBN(), dq.ToBN(), qi.ToBN())) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK RSA key");
return {};
}
}
auto pkey = EVPKeyPointer::NewRSA(std::move(rsa));
if (!pkey) return {};
return KeyObjectData::CreateAsymmetric(type, std::move(pkey));
}
bool GetRsaKeyDetail(Environment* env,
const KeyObjectData& key,
Local<Object> target) {
Mutex::ScopedLock lock(key.mutex());
const auto& m_pkey = key.GetAsymmetricKey();
// TODO(tniessen): Remove the "else" branch once we drop support for OpenSSL
// versions older than 1.1.1e via FIPS / dynamic linking.
const ncrypto::Rsa rsa = m_pkey;
if (!rsa) return false;
auto pub_key = rsa.getPublicKey();
if (target
->Set(env->context(),
env->modulus_length_string(),
Number::New(
env->isolate(),
static_cast<double>(BignumPointer::GetBitCount(pub_key.n))))
.IsNothing()) {
return false;
}
auto public_exponent = ArrayBuffer::NewBackingStore(
env->isolate(),
BignumPointer::GetByteCount(pub_key.e),
BackingStoreInitializationMode::kUninitialized);
CHECK_EQ(BignumPointer::EncodePaddedInto(
pub_key.e,
static_cast<unsigned char*>(public_exponent->Data()),
public_exponent->ByteLength()),
public_exponent->ByteLength());
if (target
->Set(env->context(),
env->public_exponent_string(),
ArrayBuffer::New(env->isolate(), std::move(public_exponent)))
.IsNothing()) {
return false;
}
if (m_pkey.id() == EVP_PKEY_RSA_PSS) {
// Due to the way ASN.1 encoding works, default values are omitted when
// encoding the data structure. However, there are also RSA-PSS keys for
// which no parameters are set. In that case, the ASN.1 RSASSA-PSS-params
// sequence will be missing entirely and RSA_get0_pss_params will return
// nullptr. If parameters are present but all parameters are set to their
// default values, an empty sequence will be stored in the ASN.1 structure.
// In that case, RSA_get0_pss_params does not return nullptr but all fields
// of the returned RSA_PSS_PARAMS will be set to nullptr.
auto maybe_params = rsa.getPssParams();
if (maybe_params.has_value()) {
auto& params = maybe_params.value();
if (target
->Set(env->context(),
env->hash_algorithm_string(),
OneByteString(env->isolate(), params.digest))
.IsNothing()) {
return false;
}
// If, for some reason, the MGF is not MGF1, then the MGF1 hash function
// is intentionally not added to the object.
if (params.mgf1_digest.has_value()) {
auto digest = params.mgf1_digest.value();
if (target
->Set(env->context(),
env->mgf1_hash_algorithm_string(),
OneByteString(env->isolate(), digest))
.IsNothing()) {
return false;
}
}
if (target
->Set(env->context(),
env->salt_length_string(),
Integer::New(env->isolate(), params.salt_length))
.IsNothing()) {
return false;
}
}
}
return true;
}
namespace RSAAlg {
void Initialize(Environment* env, Local<Object> target) {
RSAKeyPairGenJob::Initialize(env, target);
RSAKeyExportJob::Initialize(env, target);
RSACipherJob::Initialize(env, target);
NODE_DEFINE_CONSTANT(target, kKeyVariantRSA_SSA_PKCS1_v1_5);
NODE_DEFINE_CONSTANT(target, kKeyVariantRSA_PSS);
NODE_DEFINE_CONSTANT(target, kKeyVariantRSA_OAEP);
}
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
RSAKeyPairGenJob::RegisterExternalReferences(registry);
RSAKeyExportJob::RegisterExternalReferences(registry);
RSACipherJob::RegisterExternalReferences(registry);
}
} // namespace RSAAlg
} // namespace crypto
} // namespace node

136
src/crypto/crypto_rsa.h Normal file
View File

@ -0,0 +1,136 @@
#ifndef SRC_CRYPTO_CRYPTO_RSA_H_
#define SRC_CRYPTO_CRYPTO_RSA_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_cipher.h"
#include "crypto/crypto_keygen.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "env.h"
#include "memory_tracker.h"
#include "v8.h"
namespace node {
namespace crypto {
enum RSAKeyVariant {
kKeyVariantRSA_SSA_PKCS1_v1_5,
kKeyVariantRSA_PSS,
kKeyVariantRSA_OAEP
};
struct RsaKeyPairParams final : public MemoryRetainer {
RSAKeyVariant variant;
unsigned int modulus_bits;
unsigned int exponent;
// The following options are used for RSA-PSS. If any of them are set, a
// RSASSA-PSS-params sequence will be added to the key.
ncrypto::Digest md = nullptr;
ncrypto::Digest mgf1_md = nullptr;
int saltlen = -1;
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(RsaKeyPairParams)
SET_SELF_SIZE(RsaKeyPairParams)
};
using RsaKeyPairGenConfig = KeyPairGenConfig<RsaKeyPairParams>;
struct RsaKeyGenTraits final {
using AdditionalParameters = RsaKeyPairGenConfig;
static constexpr const char* JobName = "RsaKeyPairGenJob";
static ncrypto::EVPKeyCtxPointer Setup(RsaKeyPairGenConfig* params);
static v8::Maybe<void> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int* offset,
RsaKeyPairGenConfig* params);
};
using RSAKeyPairGenJob = KeyGenJob<KeyPairGenTraits<RsaKeyGenTraits>>;
struct RSAKeyExportConfig final : public MemoryRetainer {
RSAKeyVariant variant = kKeyVariantRSA_SSA_PKCS1_v1_5;
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(RSAKeyExportConfig)
SET_SELF_SIZE(RSAKeyExportConfig)
};
struct RSAKeyExportTraits final {
static constexpr const char* JobName = "RSAKeyExportJob";
using AdditionalParameters = RSAKeyExportConfig;
static v8::Maybe<void> AdditionalConfig(
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
RSAKeyExportConfig* config);
static WebCryptoKeyExportStatus DoExport(const KeyObjectData& key_data,
WebCryptoKeyFormat format,
const RSAKeyExportConfig& params,
ByteSource* out);
};
using RSAKeyExportJob = KeyExportJob<RSAKeyExportTraits>;
struct RSACipherConfig final : public MemoryRetainer {
CryptoJobMode mode = kCryptoJobAsync;
ByteSource label;
int padding = 0;
ncrypto::Digest digest;
RSACipherConfig() = default;
RSACipherConfig(RSACipherConfig&& other) noexcept;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(RSACipherConfig)
SET_SELF_SIZE(RSACipherConfig)
};
struct RSACipherTraits final {
static constexpr const char* JobName = "RSACipherJob";
using AdditionalParameters = RSACipherConfig;
static v8::Maybe<void> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
WebCryptoCipherMode cipher_mode,
RSACipherConfig* config);
static WebCryptoCipherStatus DoCipher(Environment* env,
const KeyObjectData& key_data,
WebCryptoCipherMode cipher_mode,
const RSACipherConfig& params,
const ByteSource& in,
ByteSource* out);
};
using RSACipherJob = CipherJob<RSACipherTraits>;
bool ExportJWKRsaKey(Environment* env,
const KeyObjectData& key,
v8::Local<v8::Object> target);
KeyObjectData ImportJWKRsaKey(Environment* env,
v8::Local<v8::Object> jwk,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset);
bool GetRsaKeyDetail(Environment* env,
const KeyObjectData& key,
v8::Local<v8::Object> target);
namespace RSAAlg {
void Initialize(Environment* env, v8::Local<v8::Object> target);
void RegisterExternalReferences(ExternalReferenceRegistry* registry);
} // namespace RSAAlg
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_RSA_H_

151
src/crypto/crypto_scrypt.cc Normal file
View File

@ -0,0 +1,151 @@
#include "crypto/crypto_scrypt.h"
#include "async_wrap-inl.h"
#include "crypto/crypto_util.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "node_buffer.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
namespace node {
using v8::FunctionCallbackInfo;
using v8::Int32;
using v8::JustVoid;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Nothing;
using v8::Uint32;
using v8::Value;
namespace crypto {
#ifndef OPENSSL_NO_SCRYPT
ScryptConfig::ScryptConfig(ScryptConfig&& other) noexcept
: mode(other.mode),
pass(std::move(other.pass)),
salt(std::move(other.salt)),
N(other.N),
r(other.r),
p(other.p),
maxmem(other.maxmem),
length(other.length) {}
ScryptConfig& ScryptConfig::operator=(ScryptConfig&& other) noexcept {
if (&other == this) return *this;
this->~ScryptConfig();
return *new (this) ScryptConfig(std::move(other));
}
void ScryptConfig::MemoryInfo(MemoryTracker* tracker) const {
if (mode == kCryptoJobAsync) {
tracker->TrackFieldWithSize("pass", pass.size());
tracker->TrackFieldWithSize("salt", salt.size());
}
}
MaybeLocal<Value> ScryptTraits::EncodeOutput(Environment* env,
const ScryptConfig& params,
ByteSource* out) {
return out->ToArrayBuffer(env);
}
Maybe<void> ScryptTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
ScryptConfig* params) {
Environment* env = Environment::GetCurrent(args);
params->mode = mode;
ArrayBufferOrViewContents<char> pass(args[offset]);
ArrayBufferOrViewContents<char> salt(args[offset + 1]);
if (!pass.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "pass is too large");
return Nothing<void>();
}
if (!salt.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "salt is too large");
return Nothing<void>();
}
params->pass = mode == kCryptoJobAsync
? pass.ToCopy()
: pass.ToByteSource();
params->salt = mode == kCryptoJobAsync
? salt.ToCopy()
: salt.ToByteSource();
CHECK(args[offset + 2]->IsUint32()); // N
CHECK(args[offset + 3]->IsUint32()); // r
CHECK(args[offset + 4]->IsUint32()); // p
CHECK(args[offset + 5]->IsNumber()); // maxmem
CHECK(args[offset + 6]->IsInt32()); // length
params->N = args[offset + 2].As<Uint32>()->Value();
params->r = args[offset + 3].As<Uint32>()->Value();
params->p = args[offset + 4].As<Uint32>()->Value();
params->maxmem = args[offset + 5]->IntegerValue(env->context()).ToChecked();
params->length = args[offset + 6].As<Int32>()->Value();
CHECK_GE(params->length, 0);
if (!ncrypto::checkScryptParams(
params->N, params->r, params->p, params->maxmem)) {
// Do not use CryptoErrorStore or ThrowCryptoError here in order to maintain
// backward compatibility with ERR_CRYPTO_INVALID_SCRYPT_PARAMS.
uint32_t err = ERR_peek_last_error();
if (err != 0) {
char buf[256];
ERR_error_string_n(err, buf, sizeof(buf));
THROW_ERR_CRYPTO_INVALID_SCRYPT_PARAMS(
env, "Invalid scrypt params: %s", buf);
} else {
THROW_ERR_CRYPTO_INVALID_SCRYPT_PARAMS(env);
}
return Nothing<void>();
}
return JustVoid();
}
bool ScryptTraits::DeriveBits(Environment* env,
const ScryptConfig& params,
ByteSource* out,
CryptoJobMode mode) {
// If the params.length is zero-length, just return an empty buffer.
// It's useless, yes, but allowed via the API.
if (params.length == 0) {
*out = ByteSource();
return true;
}
auto dp = ncrypto::scrypt(
ncrypto::Buffer<const char>{
.data = params.pass.data<char>(),
.len = params.pass.size(),
},
ncrypto::Buffer<const unsigned char>{
.data = params.salt.data<unsigned char>(),
.len = params.salt.size(),
},
params.N,
params.r,
params.p,
params.maxmem,
params.length);
if (!dp) return false;
DCHECK(!dp.isSecure());
*out = ByteSource::Allocated(dp.release());
return true;
}
#endif // !OPENSSL_NO_SCRYPT
} // namespace crypto
} // namespace node

View File

@ -0,0 +1,85 @@
#ifndef SRC_CRYPTO_CRYPTO_SCRYPT_H_
#define SRC_CRYPTO_CRYPTO_SCRYPT_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_util.h"
#include "env.h"
#include "memory_tracker.h"
#include "v8.h"
namespace node {
namespace crypto {
#ifndef OPENSSL_NO_SCRYPT
// Scrypt is a password-based key derivation algorithm
// defined in https://tools.ietf.org/html/rfc7914
// It takes as input a password, a salt value, and a
// handful of additional parameters that control the
// cost of the operation. In this case, the higher
// the cost, the better the result. The length parameter
// defines the number of bytes that are generated.
// The salt must be as random as possible and should be
// at least 16 bytes in length.
struct ScryptConfig final : public MemoryRetainer {
CryptoJobMode mode;
ByteSource pass;
ByteSource salt;
uint32_t N;
uint32_t r;
uint32_t p;
uint64_t maxmem;
int32_t length;
ScryptConfig() = default;
explicit ScryptConfig(ScryptConfig&& other) noexcept;
ScryptConfig& operator=(ScryptConfig&& other) noexcept;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(ScryptConfig)
SET_SELF_SIZE(ScryptConfig)
};
struct ScryptTraits final {
using AdditionalParameters = ScryptConfig;
static constexpr const char* JobName = "ScryptJob";
static constexpr AsyncWrap::ProviderType Provider =
AsyncWrap::PROVIDER_SCRYPTREQUEST;
static v8::Maybe<void> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
ScryptConfig* params);
static bool DeriveBits(Environment* env,
const ScryptConfig& params,
ByteSource* out,
CryptoJobMode mode);
static v8::MaybeLocal<v8::Value> EncodeOutput(Environment* env,
const ScryptConfig& params,
ByteSource* out);
};
using ScryptJob = DeriveBitsJob<ScryptTraits>;
#else
// If there is no Scrypt support, ScryptJob becomes a non-op
struct ScryptJob {
static void Initialize(
Environment* env,
v8::Local<v8::Object> target) {}
};
#endif // !OPENSSL_NO_SCRYPT
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_SCRYPT_H_

732
src/crypto/crypto_sig.cc Normal file
View File

@ -0,0 +1,732 @@
#include "crypto/crypto_sig.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "crypto/crypto_ec.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "openssl/ec.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
namespace node {
using ncrypto::BignumPointer;
using ncrypto::ClearErrorOnReturn;
using ncrypto::DataPointer;
using ncrypto::Digest;
using ncrypto::ECDSASigPointer;
using ncrypto::EVPKeyCtxPointer;
using ncrypto::EVPKeyPointer;
using ncrypto::EVPMDCtxPointer;
using v8::ArrayBuffer;
using v8::BackingStore;
using v8::BackingStoreInitializationMode;
using v8::Boolean;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::HandleScope;
using v8::Int32;
using v8::Isolate;
using v8::JustVoid;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Nothing;
using v8::Object;
using v8::Uint32;
using v8::Value;
namespace crypto {
namespace {
int GetPaddingFromJS(const EVPKeyPointer& key, Local<Value> val) {
int padding = key.getDefaultSignPadding();
if (!val->IsUndefined()) [[likely]] {
CHECK(val->IsInt32());
padding = val.As<Int32>()->Value();
}
return padding;
}
std::optional<int> GetSaltLenFromJS(Local<Value> val) {
std::optional<int> salt_len;
if (!val->IsUndefined()) [[likely]] {
CHECK(val->IsInt32());
salt_len = val.As<Int32>()->Value();
}
return salt_len;
}
DSASigEnc GetDSASigEncFromJS(Local<Value> val) {
CHECK(val->IsInt32());
int i = val.As<Int32>()->Value();
if (i < 0 || i >= static_cast<int>(DSASigEnc::Invalid)) [[unlikely]] {
return DSASigEnc::Invalid;
}
return static_cast<DSASigEnc>(val.As<Int32>()->Value());
}
bool ApplyRSAOptions(const EVPKeyPointer& pkey,
EVP_PKEY_CTX* pkctx,
int padding,
std::optional<int> salt_len) {
if (pkey.isRsaVariant()) {
return EVPKeyCtxPointer::setRsaPadding(pkctx, padding, salt_len);
}
return true;
}
std::unique_ptr<BackingStore> Node_SignFinal(Environment* env,
EVPMDCtxPointer&& mdctx,
const EVPKeyPointer& pkey,
int padding,
std::optional<int> pss_salt_len) {
auto data = mdctx.digestFinal(mdctx.getExpectedSize());
if (!data) [[unlikely]]
return nullptr;
auto sig = ArrayBuffer::NewBackingStore(env->isolate(), pkey.size());
ncrypto::Buffer<unsigned char> sig_buf{
.data = static_cast<unsigned char*>(sig->Data()),
.len = pkey.size(),
};
EVPKeyCtxPointer pkctx = pkey.newCtx();
if (pkctx.initForSign() > 0 &&
ApplyRSAOptions(pkey, pkctx.get(), padding, pss_salt_len) &&
pkctx.setSignatureMd(mdctx) && pkctx.signInto(data, &sig_buf))
[[likely]] {
CHECK_LE(sig_buf.len, sig->ByteLength());
if (sig_buf.len < sig->ByteLength()) {
auto new_sig = ArrayBuffer::NewBackingStore(
env->isolate(),
sig_buf.len,
BackingStoreInitializationMode::kUninitialized);
if (sig_buf.len > 0) [[likely]] {
memcpy(new_sig->Data(), sig->Data(), sig_buf.len);
}
sig = std::move(new_sig);
}
return sig;
}
return nullptr;
}
// Returns the maximum size of each of the integers (r, s) of the DSA signature.
std::unique_ptr<BackingStore> ConvertSignatureToP1363(
Environment* env,
const EVPKeyPointer& pkey,
std::unique_ptr<BackingStore>&& signature) {
uint32_t n = pkey.getBytesOfRS().value_or(kNoDsaSignature);
if (n == kNoDsaSignature) return std::move(signature);
auto buf = ArrayBuffer::NewBackingStore(
env->isolate(), 2 * n, BackingStoreInitializationMode::kUninitialized);
ncrypto::Buffer<const unsigned char> sig_buffer{
.data = static_cast<const unsigned char*>(signature->Data()),
.len = signature->ByteLength(),
};
if (!ncrypto::extractP1363(
sig_buffer, static_cast<unsigned char*>(buf->Data()), n)) {
return std::move(signature);
}
return buf;
}
// Returns the maximum size of each of the integers (r, s) of the DSA signature.
ByteSource ConvertSignatureToP1363(Environment* env,
const EVPKeyPointer& pkey,
const ByteSource& signature) {
unsigned int n = pkey.getBytesOfRS().value_or(kNoDsaSignature);
if (n == kNoDsaSignature) [[unlikely]]
return {};
auto data = DataPointer::Alloc(n * 2);
if (!data) [[unlikely]]
return {};
unsigned char* out = static_cast<unsigned char*>(data.get());
// Extracting the signature may not actually use all of the allocated space.
// We need to ensure that the buffer is zeroed out before use.
data.zero();
if (!ncrypto::extractP1363(signature, out, n)) [[unlikely]] {
return {};
}
return ByteSource::Allocated(data.release());
}
ByteSource ConvertSignatureToDER(const EVPKeyPointer& pkey, ByteSource&& out) {
unsigned int n = pkey.getBytesOfRS().value_or(kNoDsaSignature);
if (n == kNoDsaSignature) return std::move(out);
const unsigned char* sig_data = out.data<unsigned char>();
if (out.size() != 2 * n) return {};
auto asn1_sig = ECDSASigPointer::New();
CHECK(asn1_sig);
BignumPointer r(sig_data, n);
CHECK(r);
BignumPointer s(sig_data + n, n);
CHECK(s);
CHECK(asn1_sig.setParams(std::move(r), std::move(s)));
auto buf = asn1_sig.encode();
if (buf.len <= 0) [[unlikely]]
return {};
CHECK_NOT_NULL(buf.data);
return ByteSource::Allocated(buf);
}
void CheckThrow(Environment* env, SignBase::Error error) {
HandleScope scope(env->isolate());
switch (error) {
case SignBase::Error::UnknownDigest:
return THROW_ERR_CRYPTO_INVALID_DIGEST(env);
case SignBase::Error::NotInitialised:
return THROW_ERR_CRYPTO_INVALID_STATE(env, "Not initialised");
case SignBase::Error::MalformedSignature:
return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Malformed signature");
case SignBase::Error::Init:
case SignBase::Error::Update:
case SignBase::Error::PrivateKey:
case SignBase::Error::PublicKey: {
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
if (err) return ThrowCryptoError(env, err);
switch (error) {
case SignBase::Error::Init:
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"EVP_SignInit_ex failed");
case SignBase::Error::Update:
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"EVP_SignUpdate failed");
case SignBase::Error::PrivateKey:
return THROW_ERR_CRYPTO_OPERATION_FAILED(
env, "PEM_read_bio_PrivateKey failed");
case SignBase::Error::PublicKey:
return THROW_ERR_CRYPTO_OPERATION_FAILED(
env, "PEM_read_bio_PUBKEY failed");
default:
ABORT();
}
}
case SignBase::Error::Ok:
return;
}
}
bool UseP1363Encoding(const EVPKeyPointer& key, const DSASigEnc dsa_encoding) {
return key.isSigVariant() && dsa_encoding == DSASigEnc::P1363;
}
} // namespace
SignBase::Error SignBase::Init(const char* digest) {
CHECK_NULL(mdctx_);
auto md = Digest::FromName(digest);
if (!md) [[unlikely]]
return Error::UnknownDigest;
mdctx_ = EVPMDCtxPointer::New();
if (!mdctx_.digestInit(md)) [[unlikely]] {
mdctx_.reset();
return Error::Init;
}
return Error::Ok;
}
SignBase::Error SignBase::Update(const char* data, size_t len) {
if (mdctx_ == nullptr) [[unlikely]]
return Error::NotInitialised;
ncrypto::Buffer<const void> buf{
.data = data,
.len = len,
};
return mdctx_.digestUpdate(buf) ? Error::Ok : Error::Update;
}
SignBase::SignBase(Environment* env, Local<Object> wrap)
: BaseObject(env, wrap) {
MakeWeak();
}
void SignBase::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackFieldWithSize("mdctx", mdctx_ ? kSizeOf_EVP_MD_CTX : 0);
}
Sign::Sign(Environment* env, Local<Object> wrap) : SignBase(env, wrap) {}
void Sign::Initialize(Environment* env, Local<Object> target) {
Isolate* isolate = env->isolate();
Local<FunctionTemplate> t = NewFunctionTemplate(isolate, New);
t->InstanceTemplate()->SetInternalFieldCount(SignBase::kInternalFieldCount);
SetProtoMethod(isolate, t, "init", SignInit);
SetProtoMethod(isolate, t, "update", SignUpdate);
SetProtoMethod(isolate, t, "sign", SignFinal);
SetConstructorFunction(env->context(), target, "Sign", t);
SignJob::Initialize(env, target);
constexpr int kSignJobModeSign =
static_cast<int>(SignConfiguration::Mode::Sign);
constexpr int kSignJobModeVerify =
static_cast<int>(SignConfiguration::Mode::Verify);
constexpr auto kSigEncDER = DSASigEnc::DER;
constexpr auto kSigEncP1363 = DSASigEnc::P1363;
NODE_DEFINE_CONSTANT(target, kSignJobModeSign);
NODE_DEFINE_CONSTANT(target, kSignJobModeVerify);
NODE_DEFINE_CONSTANT(target, kSigEncDER);
NODE_DEFINE_CONSTANT(target, kSigEncP1363);
NODE_DEFINE_CONSTANT(target, RSA_PKCS1_PSS_PADDING);
}
void Sign::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(New);
registry->Register(SignInit);
registry->Register(SignUpdate);
registry->Register(SignFinal);
SignJob::RegisterExternalReferences(registry);
}
void Sign::New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
new Sign(env, args.This());
}
void Sign::SignInit(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Sign* sign;
ASSIGN_OR_RETURN_UNWRAP(&sign, args.This());
const node::Utf8Value sign_type(env->isolate(), args[0]);
crypto::CheckThrow(env, sign->Init(*sign_type));
}
void Sign::SignUpdate(const FunctionCallbackInfo<Value>& args) {
Decode<Sign>(args, [](Sign* sign, const FunctionCallbackInfo<Value>& args,
const char* data, size_t size) {
Environment* env = Environment::GetCurrent(args);
if (size > INT_MAX) [[unlikely]]
return THROW_ERR_OUT_OF_RANGE(env, "data is too long");
Error err = sign->Update(data, size);
crypto::CheckThrow(sign->env(), err);
});
}
Sign::SignResult Sign::SignFinal(const EVPKeyPointer& pkey,
int padding,
std::optional<int> salt_len,
DSASigEnc dsa_sig_enc) {
if (!mdctx_) [[unlikely]] {
return SignResult(Error::NotInitialised);
}
EVPMDCtxPointer mdctx = std::move(mdctx_);
if (!pkey.validateDsaParameters()) {
return SignResult(Error::PrivateKey);
}
auto buffer =
Node_SignFinal(env(), std::move(mdctx), pkey, padding, salt_len);
Error error = buffer ? Error::Ok : Error::PrivateKey;
if (error == Error::Ok && dsa_sig_enc == DSASigEnc::P1363) {
buffer = ConvertSignatureToP1363(env(), pkey, std::move(buffer));
CHECK_NOT_NULL(buffer->Data());
}
return SignResult(error, std::move(buffer));
}
void Sign::SignFinal(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Sign* sign;
ASSIGN_OR_RETURN_UNWRAP(&sign, args.This());
ClearErrorOnReturn clear_error_on_return;
unsigned int offset = 0;
auto data = KeyObjectData::GetPrivateKeyFromJs(args, &offset, true);
if (!data) [[unlikely]]
return;
const auto& key = data.GetAsymmetricKey();
if (!key) [[unlikely]]
return;
if (key.isOneShotVariant()) [[unlikely]] {
THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env);
return;
}
int padding = GetPaddingFromJS(key, args[offset]);
std::optional<int> salt_len = GetSaltLenFromJS(args[offset + 1]);
DSASigEnc dsa_sig_enc = GetDSASigEncFromJS(args[offset + 2]);
if (dsa_sig_enc == DSASigEnc::Invalid) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "invalid signature encoding");
return;
}
SignResult ret = sign->SignFinal(key, padding, salt_len, dsa_sig_enc);
if (ret.error != Error::Ok) [[unlikely]] {
return crypto::CheckThrow(env, ret.error);
}
auto ab = ArrayBuffer::New(env->isolate(), std::move(ret.signature));
args.GetReturnValue().Set(
Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local<Value>()));
}
Verify::Verify(Environment* env, Local<Object> wrap) : SignBase(env, wrap) {}
void Verify::Initialize(Environment* env, Local<Object> target) {
Isolate* isolate = env->isolate();
Local<FunctionTemplate> t = NewFunctionTemplate(isolate, New);
t->InstanceTemplate()->SetInternalFieldCount(SignBase::kInternalFieldCount);
SetProtoMethod(isolate, t, "init", VerifyInit);
SetProtoMethod(isolate, t, "update", VerifyUpdate);
SetProtoMethod(isolate, t, "verify", VerifyFinal);
SetConstructorFunction(env->context(), target, "Verify", t);
}
void Verify::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(New);
registry->Register(VerifyInit);
registry->Register(VerifyUpdate);
registry->Register(VerifyFinal);
}
void Verify::New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
new Verify(env, args.This());
}
void Verify::VerifyInit(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Verify* verify;
ASSIGN_OR_RETURN_UNWRAP(&verify, args.This());
const node::Utf8Value verify_type(env->isolate(), args[0]);
crypto::CheckThrow(env, verify->Init(*verify_type));
}
void Verify::VerifyUpdate(const FunctionCallbackInfo<Value>& args) {
Decode<Verify>(args, [](Verify* verify,
const FunctionCallbackInfo<Value>& args,
const char* data, size_t size) {
Environment* env = Environment::GetCurrent(args);
if (size > INT_MAX) [[unlikely]] {
return THROW_ERR_OUT_OF_RANGE(env, "data is too long");
}
Error err = verify->Update(data, size);
crypto::CheckThrow(verify->env(), err);
});
}
SignBase::Error Verify::VerifyFinal(const EVPKeyPointer& pkey,
const ByteSource& sig,
int padding,
std::optional<int> saltlen,
bool* verify_result) {
if (!mdctx_) [[unlikely]]
return Error::NotInitialised;
*verify_result = false;
EVPMDCtxPointer mdctx = std::move(mdctx_);
auto data = mdctx.digestFinal(mdctx.getExpectedSize());
if (!data) [[unlikely]]
return Error::PublicKey;
EVPKeyCtxPointer pkctx = pkey.newCtx();
if (pkctx) [[likely]] {
const int init_ret = pkctx.initForVerify();
if (init_ret == -2) [[unlikely]]
return Error::PublicKey;
if (init_ret > 0 && ApplyRSAOptions(pkey, pkctx.get(), padding, saltlen) &&
pkctx.setSignatureMd(mdctx)) {
*verify_result = pkctx.verify(sig, data);
}
}
return Error::Ok;
}
void Verify::VerifyFinal(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
ClearErrorOnReturn clear_error_on_return;
Verify* verify;
ASSIGN_OR_RETURN_UNWRAP(&verify, args.This());
unsigned int offset = 0;
auto data = KeyObjectData::GetPublicOrPrivateKeyFromJs(args, &offset);
if (!data) [[unlikely]]
return;
const auto& key = data.GetAsymmetricKey();
if (!key) [[unlikely]]
return;
if (key.isOneShotVariant()) [[unlikely]] {
THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env);
return;
}
ArrayBufferOrViewContents<char> hbuf(args[offset]);
if (!hbuf.CheckSizeInt32()) [[unlikely]] {
return THROW_ERR_OUT_OF_RANGE(env, "buffer is too big");
}
int padding = GetPaddingFromJS(key, args[offset + 1]);
std::optional<int> salt_len = GetSaltLenFromJS(args[offset + 2]);
DSASigEnc dsa_sig_enc = GetDSASigEncFromJS(args[offset + 3]);
if (dsa_sig_enc == DSASigEnc::Invalid) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "invalid signature encoding");
return;
}
ByteSource signature = hbuf.ToByteSource();
if (dsa_sig_enc == DSASigEnc::P1363) {
signature = ConvertSignatureToDER(key, hbuf.ToByteSource());
if (signature.data() == nullptr) [[unlikely]] {
return crypto::CheckThrow(env, Error::MalformedSignature);
}
}
bool verify_result;
Error err =
verify->VerifyFinal(key, signature, padding, salt_len, &verify_result);
if (err != Error::Ok) [[unlikely]]
return crypto::CheckThrow(env, err);
args.GetReturnValue().Set(verify_result);
}
SignConfiguration::SignConfiguration(SignConfiguration&& other) noexcept
: job_mode(other.job_mode),
mode(other.mode),
key(std::move(other.key)),
data(std::move(other.data)),
signature(std::move(other.signature)),
digest(other.digest),
flags(other.flags),
padding(other.padding),
salt_length(other.salt_length),
dsa_encoding(other.dsa_encoding) {}
SignConfiguration& SignConfiguration::operator=(
SignConfiguration&& other) noexcept {
if (&other == this) return *this;
this->~SignConfiguration();
return *new (this) SignConfiguration(std::move(other));
}
void SignConfiguration::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("key", key);
if (job_mode == kCryptoJobAsync) {
tracker->TrackFieldWithSize("data", data.size());
tracker->TrackFieldWithSize("signature", signature.size());
}
}
Maybe<void> SignTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
SignConfiguration* params) {
ClearErrorOnReturn clear_error_on_return;
Environment* env = Environment::GetCurrent(args);
params->job_mode = mode;
CHECK(args[offset]->IsUint32()); // Sign Mode
params->mode =
static_cast<SignConfiguration::Mode>(args[offset].As<Uint32>()->Value());
unsigned int keyParamOffset = offset + 1;
if (params->mode == SignConfiguration::Mode::Verify) {
auto data =
KeyObjectData::GetPublicOrPrivateKeyFromJs(args, &keyParamOffset);
if (!data) return Nothing<void>();
params->key = std::move(data);
} else {
auto data = KeyObjectData::GetPrivateKeyFromJs(args, &keyParamOffset, true);
if (!data) return Nothing<void>();
params->key = std::move(data);
}
ArrayBufferOrViewContents<char> data(args[offset + 5]);
if (!data.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "data is too big");
return Nothing<void>();
}
params->data = mode == kCryptoJobAsync
? data.ToCopy()
: data.ToByteSource();
if (args[offset + 6]->IsString()) {
Utf8Value digest(env->isolate(), args[offset + 6]);
params->digest = Digest::FromName(*digest);
if (!params->digest) [[unlikely]] {
THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid digest: %s", *digest);
return Nothing<void>();
}
}
if (args[offset + 7]->IsInt32()) { // Salt length
params->flags |= SignConfiguration::kHasSaltLength;
params->salt_length =
GetSaltLenFromJS(args[offset + 7]).value_or(params->salt_length);
}
if (args[offset + 8]->IsUint32()) { // Padding
params->flags |= SignConfiguration::kHasPadding;
params->padding =
GetPaddingFromJS(params->key.GetAsymmetricKey(), args[offset + 8]);
}
if (args[offset + 9]->IsUint32()) { // DSA Encoding
params->dsa_encoding = GetDSASigEncFromJS(args[offset + 9]);
if (params->dsa_encoding == DSASigEnc::Invalid) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "invalid signature encoding");
return Nothing<void>();
}
}
if (params->mode == SignConfiguration::Mode::Verify) {
ArrayBufferOrViewContents<char> signature(args[offset + 10]);
if (!signature.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "signature is too big");
return Nothing<void>();
}
// If this is an EC key (assuming ECDSA) we need to convert the
// the signature from WebCrypto format into DER format...
Mutex::ScopedLock lock(params->key.mutex());
const auto& akey = params->key.GetAsymmetricKey();
if (UseP1363Encoding(akey, params->dsa_encoding)) {
params->signature = ConvertSignatureToDER(akey, signature.ToByteSource());
} else {
params->signature = mode == kCryptoJobAsync
? signature.ToCopy()
: signature.ToByteSource();
}
}
return JustVoid();
}
bool SignTraits::DeriveBits(Environment* env,
const SignConfiguration& params,
ByteSource* out,
CryptoJobMode mode) {
bool can_throw = mode == CryptoJobMode::kCryptoJobSync;
auto context = EVPMDCtxPointer::New();
if (!context) [[unlikely]]
return false;
const auto& key = params.key.GetAsymmetricKey();
auto ctx = ([&] {
switch (params.mode) {
case SignConfiguration::Mode::Sign:
return context.signInit(key, params.digest);
case SignConfiguration::Mode::Verify:
return context.verifyInit(key, params.digest);
}
UNREACHABLE();
})();
if (!ctx.has_value()) [[unlikely]] {
if (can_throw) crypto::CheckThrow(env, SignBase::Error::Init);
return false;
}
int padding = params.flags & SignConfiguration::kHasPadding
? params.padding
: key.getDefaultSignPadding();
std::optional<int> salt_length =
params.flags & SignConfiguration::kHasSaltLength
? std::optional<int>(params.salt_length)
: std::nullopt;
if (!ApplyRSAOptions(key, *ctx, padding, salt_length)) {
if (can_throw) crypto::CheckThrow(env, SignBase::Error::PrivateKey);
return false;
}
switch (params.mode) {
case SignConfiguration::Mode::Sign: {
if (key.isOneShotVariant()) {
auto data = context.signOneShot(params.data);
if (!data) [[unlikely]] {
if (can_throw) crypto::CheckThrow(env, SignBase::Error::PrivateKey);
return false;
}
DCHECK(!data.isSecure());
*out = ByteSource::Allocated(data.release());
} else {
auto data = context.sign(params.data);
if (!data) [[unlikely]] {
if (can_throw) crypto::CheckThrow(env, SignBase::Error::PrivateKey);
return false;
}
DCHECK(!data.isSecure());
auto bs = ByteSource::Allocated(data.release());
if (UseP1363Encoding(key, params.dsa_encoding)) {
*out = ConvertSignatureToP1363(env, key, std::move(bs));
} else {
*out = std::move(bs);
}
}
break;
}
case SignConfiguration::Mode::Verify: {
auto buf = DataPointer::Alloc(1);
static_cast<char*>(buf.get())[0] = 0;
if (context.verify(params.data, params.signature)) {
static_cast<char*>(buf.get())[0] = 1;
}
*out = ByteSource::Allocated(buf.release());
}
}
return true;
}
MaybeLocal<Value> SignTraits::EncodeOutput(Environment* env,
const SignConfiguration& params,
ByteSource* out) {
switch (params.mode) {
case SignConfiguration::Mode::Sign:
return out->ToArrayBuffer(env);
case SignConfiguration::Mode::Verify:
return Boolean::New(env->isolate(), out->data<char>()[0] == 1);
}
UNREACHABLE();
}
} // namespace crypto
} // namespace node

156
src/crypto/crypto_sig.h Normal file
View File

@ -0,0 +1,156 @@
#ifndef SRC_CRYPTO_CRYPTO_SIG_H_
#define SRC_CRYPTO_CRYPTO_SIG_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "base_object.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "env.h"
#include "memory_tracker.h"
namespace node {
namespace crypto {
static const unsigned int kNoDsaSignature = static_cast<unsigned int>(-1);
enum class DSASigEnc { DER, P1363, Invalid };
class SignBase : public BaseObject {
public:
enum class Error {
Ok,
UnknownDigest,
Init,
NotInitialised,
Update,
PrivateKey,
PublicKey,
MalformedSignature
};
SignBase(Environment* env, v8::Local<v8::Object> wrap);
Error Init(const char* digest);
Error Update(const char* data, size_t len);
// TODO(joyeecheung): track the memory used by OpenSSL types
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(SignBase)
SET_SELF_SIZE(SignBase)
protected:
ncrypto::EVPMDCtxPointer mdctx_;
};
class Sign final : public SignBase {
public:
static void Initialize(Environment* env, v8::Local<v8::Object> target);
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);
struct SignResult {
Error error;
std::unique_ptr<v8::BackingStore> signature;
inline explicit SignResult(
Error err, std::unique_ptr<v8::BackingStore>&& sig = nullptr)
: error(err), signature(std::move(sig)) {}
};
SignResult SignFinal(const ncrypto::EVPKeyPointer& pkey,
int padding,
std::optional<int> saltlen,
DSASigEnc dsa_sig_enc);
static void SignSync(const v8::FunctionCallbackInfo<v8::Value>& args);
protected:
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SignInit(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SignUpdate(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SignFinal(const v8::FunctionCallbackInfo<v8::Value>& args);
Sign(Environment* env, v8::Local<v8::Object> wrap);
};
class Verify final : public SignBase {
public:
static void Initialize(Environment* env, v8::Local<v8::Object> target);
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);
Error VerifyFinal(const ncrypto::EVPKeyPointer& key,
const ByteSource& sig,
int padding,
std::optional<int> saltlen,
bool* verify_result);
static void VerifySync(const v8::FunctionCallbackInfo<v8::Value>& args);
protected:
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void VerifyInit(const v8::FunctionCallbackInfo<v8::Value>& args);
static void VerifyUpdate(const v8::FunctionCallbackInfo<v8::Value>& args);
static void VerifyFinal(const v8::FunctionCallbackInfo<v8::Value>& args);
Verify(Environment* env, v8::Local<v8::Object> wrap);
};
struct SignConfiguration final : public MemoryRetainer {
enum class Mode { Sign, Verify };
enum Flags {
kHasNone = 0,
kHasSaltLength = 1,
kHasPadding = 2
};
CryptoJobMode job_mode;
Mode mode;
KeyObjectData key;
ByteSource data;
ByteSource signature;
ncrypto::Digest digest;
int flags = SignConfiguration::kHasNone;
int padding = 0;
int salt_length = 0;
DSASigEnc dsa_encoding = DSASigEnc::DER;
SignConfiguration() = default;
explicit SignConfiguration(SignConfiguration&& other) noexcept;
SignConfiguration& operator=(SignConfiguration&& other) noexcept;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(SignConfiguration)
SET_SELF_SIZE(SignConfiguration)
};
struct SignTraits final {
using AdditionalParameters = SignConfiguration;
static constexpr const char* JobName = "SignJob";
static constexpr AsyncWrap::ProviderType Provider =
AsyncWrap::PROVIDER_SIGNREQUEST;
static v8::Maybe<void> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
SignConfiguration* params);
static bool DeriveBits(Environment* env,
const SignConfiguration& params,
ByteSource* out,
CryptoJobMode mode);
static v8::MaybeLocal<v8::Value> EncodeOutput(Environment* env,
const SignConfiguration& params,
ByteSource* out);
};
using SignJob = DeriveBitsJob<SignTraits>;
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_SIG_H_

View File

@ -0,0 +1,90 @@
#include "crypto/crypto_spkac.h"
#include "crypto/crypto_common.h"
#include "crypto/crypto_util.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "ncrypto.h"
#include "node.h"
#include "string_bytes.h"
#include "v8.h"
namespace node {
using ncrypto::BIOPointer;
using v8::Context;
using v8::FunctionCallbackInfo;
using v8::Local;
using v8::Object;
using v8::Value;
namespace crypto {
namespace SPKAC {
void VerifySpkac(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
ArrayBufferOrViewContents<char> input(args[0]);
if (input.empty()) return args.GetReturnValue().SetEmptyString();
if (!input.CheckSizeInt32()) [[unlikely]]
return THROW_ERR_OUT_OF_RANGE(env, "spkac is too large");
args.GetReturnValue().Set(ncrypto::VerifySpkac(input.data(), input.size()));
}
void ExportPublicKey(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
ArrayBufferOrViewContents<char> input(args[0]);
if (input.empty()) return args.GetReturnValue().SetEmptyString();
if (!input.CheckSizeInt32()) [[unlikely]]
return THROW_ERR_OUT_OF_RANGE(env, "spkac is too large");
BIOPointer bio = ncrypto::ExportPublicKey(input.data(), input.size());
if (!bio) return args.GetReturnValue().SetEmptyString();
auto pkey = ByteSource::FromBIO(bio);
args.GetReturnValue().Set(pkey.ToBuffer(env).FromMaybe(Local<Value>()));
}
void ExportChallenge(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
ArrayBufferOrViewContents<char> input(args[0]);
if (input.empty()) return args.GetReturnValue().SetEmptyString();
if (!input.CheckSizeInt32()) [[unlikely]] {
return THROW_ERR_OUT_OF_RANGE(env, "spkac is too large");
}
auto cert = ByteSource::Allocated(
ncrypto::ExportChallenge(input.data(), input.size()));
if (!cert) {
return args.GetReturnValue().SetEmptyString();
}
Local<Value> outString;
if (StringBytes::Encode(
env->isolate(), cert.data<char>(), cert.size(), BUFFER)
.ToLocal(&outString)) {
args.GetReturnValue().Set(outString);
}
}
void Initialize(Environment* env, Local<Object> target) {
Local<Context> context = env->context();
SetMethodNoSideEffect(context, target, "certVerifySpkac", VerifySpkac);
SetMethodNoSideEffect(
context, target, "certExportPublicKey", ExportPublicKey);
SetMethodNoSideEffect(
context, target, "certExportChallenge", ExportChallenge);
}
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(VerifySpkac);
registry->Register(ExportPublicKey);
registry->Register(ExportChallenge);
}
} // namespace SPKAC
} // namespace crypto
} // namespace node

21
src/crypto/crypto_spkac.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef SRC_CRYPTO_CRYPTO_SPKAC_H_
#define SRC_CRYPTO_CRYPTO_SPKAC_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "env.h"
#include "v8.h"
#include <openssl/evp.h>
namespace node {
namespace crypto {
namespace SPKAC {
void Initialize(Environment* env, v8::Local<v8::Object>);
void RegisterExternalReferences(ExternalReferenceRegistry* registry);
} // namespace SPKAC
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_SPKAC_H_

View File

@ -0,0 +1,85 @@
#include "crypto/crypto_timing.h"
#include "crypto/crypto_util.h"
#include "env-inl.h"
#include "node.h"
#include "node_debug.h"
#include "node_errors.h"
#include "v8.h"
#include <openssl/crypto.h>
namespace node {
using v8::CFunction;
using v8::FastApiCallbackOptions;
using v8::FunctionCallbackInfo;
using v8::HandleScope;
using v8::Local;
using v8::Object;
using v8::Value;
namespace crypto {
namespace Timing {
void TimingSafeEqual(const FunctionCallbackInfo<Value>& args) {
// Moving the type checking into JS leads to test failures, most likely due
// to V8 inlining certain parts of the wrapper. Therefore, keep them in C++.
// Refs: https://github.com/nodejs/node/issues/34073.
Environment* env = Environment::GetCurrent(args);
if (!IsAnyBufferSource(args[0])) {
THROW_ERR_INVALID_ARG_TYPE(
env, "The \"buf1\" argument must be an instance of "
"ArrayBuffer, Buffer, TypedArray, or DataView.");
return;
}
if (!IsAnyBufferSource(args[1])) {
THROW_ERR_INVALID_ARG_TYPE(
env, "The \"buf2\" argument must be an instance of "
"ArrayBuffer, Buffer, TypedArray, or DataView.");
return;
}
ArrayBufferOrViewContents<char> buf1(args[0]);
ArrayBufferOrViewContents<char> buf2(args[1]);
if (buf1.size() != buf2.size()) {
THROW_ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH(env);
return;
}
return args.GetReturnValue().Set(
CRYPTO_memcmp(buf1.data(), buf2.data(), buf1.size()) == 0);
}
bool FastTimingSafeEqual(Local<Value> receiver,
Local<Value> a_obj,
Local<Value> b_obj,
// NOLINTNEXTLINE(runtime/references)
FastApiCallbackOptions& options) {
HandleScope scope(options.isolate);
ArrayBufferViewContents<uint8_t> a(a_obj);
ArrayBufferViewContents<uint8_t> b(b_obj);
if (a.length() != b.length()) {
TRACK_V8_FAST_API_CALL("crypto.timingSafeEqual.error");
THROW_ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH(options.isolate);
return false;
}
TRACK_V8_FAST_API_CALL("crypto.timingSafeEqual.ok");
return CRYPTO_memcmp(a.data(), b.data(), a.length()) == 0;
}
static CFunction fast_equal(CFunction::Make(FastTimingSafeEqual));
void Initialize(Environment* env, Local<Object> target) {
SetFastMethodNoSideEffect(
env->context(), target, "timingSafeEqual", TimingSafeEqual, &fast_equal);
}
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(TimingSafeEqual);
registry->Register(FastTimingSafeEqual);
registry->Register(fast_equal.GetTypeInfo());
}
} // namespace Timing
} // namespace crypto
} // namespace node

View File

@ -0,0 +1,20 @@
#ifndef SRC_CRYPTO_CRYPTO_TIMING_H_
#define SRC_CRYPTO_CRYPTO_TIMING_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "env.h"
#include "v8.h"
namespace node {
namespace crypto {
namespace Timing {
void Initialize(Environment* env, v8::Local<v8::Object> target);
void RegisterExternalReferences(ExternalReferenceRegistry* registry);
} // namespace Timing
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_TIMING_H_

2323
src/crypto/crypto_tls.cc Normal file

File diff suppressed because it is too large Load Diff

307
src/crypto/crypto_tls.h Normal file
View File

@ -0,0 +1,307 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
#ifndef SRC_CRYPTO_CRYPTO_TLS_H_
#define SRC_CRYPTO_CRYPTO_TLS_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_context.h"
#include "crypto/crypto_clienthello.h"
#include "async_wrap.h"
#include "stream_wrap.h"
#include "v8.h"
#include <openssl/ssl.h>
#include <string>
#include <vector>
namespace node {
namespace crypto {
class TLSWrap : public AsyncWrap,
public StreamBase,
public StreamListener {
public:
enum class Kind {
kClient,
kServer
};
enum class UnderlyingStreamWriteStatus { kHasActive, kVacancy };
static void Initialize(v8::Local<v8::Object> target,
v8::Local<v8::Value> unused,
v8::Local<v8::Context> context,
void* priv);
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);
~TLSWrap() override;
inline bool is_cert_cb_running() const { return cert_cb_running_; }
inline bool is_waiting_cert_cb() const { return cert_cb_ != nullptr; }
inline bool has_session_callbacks() const { return session_callbacks_; }
inline void set_cert_cb_running(bool on = true) { cert_cb_running_ = on; }
inline void set_awaiting_new_session(bool on = true) {
awaiting_new_session_ = on;
}
inline void enable_session_callbacks() { session_callbacks_ = true; }
inline bool is_server() const { return kind_ == Kind::kServer; }
inline bool is_client() const { return kind_ == Kind::kClient; }
inline bool is_awaiting_new_session() const { return awaiting_new_session_; }
// Implement StreamBase:
bool IsAlive() override;
bool IsClosing() override;
bool IsIPCPipe() override;
int GetFD() override;
ShutdownWrap* CreateShutdownWrap(
v8::Local<v8::Object> req_wrap_object) override;
AsyncWrap* GetAsyncWrap() override;
// Implement StreamResource:
int ReadStart() override; // Exposed to JS
int ReadStop() override; // Exposed to JS
int DoShutdown(ShutdownWrap* req_wrap) override;
int DoWrite(WriteWrap* w,
uv_buf_t* bufs,
size_t count,
uv_stream_t* send_handle) override;
// Return error_ string or nullptr if it's empty.
const char* Error() const override;
// Reset error_ string to empty. Not related to "clear text".
void ClearError() override;
v8::MaybeLocal<v8::ArrayBufferView> ocsp_response() const;
void ClearOcspResponse();
SSL_SESSION* ReleaseSession();
// Called by the done() callback of the 'newSession' event.
void NewSessionDoneCb();
// Implement MemoryRetainer:
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(TLSWrap)
SET_SELF_SIZE(TLSWrap)
std::string diagnostic_name() const override;
private:
// OpenSSL structures are opaque. Estimate SSL memory size for OpenSSL 1.1.1b:
// SSL: 6224
// SSL->SSL3_STATE: 1040
// ...some buffers: 42 * 1024
// NOTE: Actually it is much more than this
static constexpr int64_t kExternalSize = 6224 + 1040 + 42 * 1024;
static constexpr int kClearOutChunkSize = 16384;
// Maximum number of bytes for hello parser
static constexpr int kMaxHelloLength = 16384;
// Usual ServerHello + Certificate size
static constexpr int kInitialClientBufferLength = 4096;
// Maximum number of buffers passed to uv_write()
static constexpr int kSimultaneousBufferCount = 10;
typedef void (*CertCb)(void* arg);
// Alternative to StreamListener::stream(), that returns a StreamBase instead
// of a StreamResource.
inline StreamBase* underlying_stream() const {
return static_cast<StreamBase*>(stream());
}
void WaitForCertCb(CertCb cb, void* arg);
TLSWrap(Environment* env,
v8::Local<v8::Object> obj,
Kind kind,
StreamBase* stream,
SecureContext* sc,
UnderlyingStreamWriteStatus under_stream_ws);
static void SSLInfoCallback(const SSL* ssl_, int where, int ret);
void InitSSL();
// SSL has a "clear" text (unencrypted) side (to/from the node API) and
// encrypted ("enc") text side (to/from the underlying socket/stream).
// On each side data flows "in" or "out" of SSL context.
//
// EncIn() doesn't exist. Encrypted data is pushed from underlying stream into
// enc_in_ via the stream listener's OnStreamAlloc()/OnStreamRead() interface.
void EncOut(); // Write encrypted data from enc_out_ to underlying stream.
void ClearIn(); // SSL_write() clear data "in" to SSL.
void ClearOut(); // SSL_read() clear text "out" from SSL.
void Destroy();
// Call Done() on outstanding WriteWrap request.
void InvokeQueued(int status, const char* error_str = nullptr);
// Drive the SSL state machine by attempting to SSL_read() and SSL_write() to
// it. Transparent handshakes mean SSL_read() might trigger I/O on the
// underlying stream even if there is no clear text to read or write.
void Cycle();
// Implement StreamListener:
// Returns buf that points into enc_in_.
uv_buf_t OnStreamAlloc(size_t size) override;
void OnStreamRead(ssize_t nread, const uv_buf_t& buf) override;
void OnStreamAfterWrite(WriteWrap* w, int status) override;
int SetCACerts(SecureContext* sc);
static int SelectSNIContextCallback(SSL* s, int* ad, void* arg);
static void CertCbDone(const v8::FunctionCallbackInfo<v8::Value>& args);
static void DestroySSL(const v8::FunctionCallbackInfo<v8::Value>& args);
static void EnableCertCb(const v8::FunctionCallbackInfo<v8::Value>& args);
static void EnableALPNCb(const v8::FunctionCallbackInfo<v8::Value>& args);
static void EnableKeylogCallback(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void EnableSessionCallbacks(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void EnableTrace(const v8::FunctionCallbackInfo<v8::Value>& args);
static void EndParser(const v8::FunctionCallbackInfo<v8::Value>& args);
static void ExportKeyingMaterial(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetALPNNegotiatedProto(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetCertificate(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetX509Certificate(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetCipher(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetEphemeralKeyInfo(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetFinished(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPeerCertificate(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPeerX509Certificate(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPeerFinished(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetProtocol(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetServername(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetSession(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetSharedSigalgs(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetTLSTicket(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetWriteQueueSize(
const v8::FunctionCallbackInfo<v8::Value>& info);
static void IsSessionReused(const v8::FunctionCallbackInfo<v8::Value>& args);
static void LoadSession(const v8::FunctionCallbackInfo<v8::Value>& args);
static void NewSessionDone(const v8::FunctionCallbackInfo<v8::Value>& args);
static void OnClientHelloParseEnd(void* arg);
static void Receive(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Renegotiate(const v8::FunctionCallbackInfo<v8::Value>& args);
static void RequestOCSP(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetALPNProtocols(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetKeyCert(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetOCSPResponse(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetServername(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetSession(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetVerifyMode(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Start(const v8::FunctionCallbackInfo<v8::Value>& args);
static void VerifyError(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Wrap(const v8::FunctionCallbackInfo<v8::Value>& args);
static void WritesIssuedByPrevListenerDone(
const v8::FunctionCallbackInfo<v8::Value>& args);
#ifdef SSL_set_max_send_fragment
static void SetMaxSendFragment(
const v8::FunctionCallbackInfo<v8::Value>& args);
#endif // SSL_set_max_send_fragment
#ifndef OPENSSL_NO_PSK
static void EnablePskCallback(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetPskIdentityHint(
const v8::FunctionCallbackInfo<v8::Value>& args);
static unsigned int PskServerCallback(SSL* s,
const char* identity,
unsigned char* psk,
unsigned int max_psk_len);
static unsigned int PskClientCallback(SSL* s,
const char* hint,
char* identity,
unsigned int max_identity_len,
unsigned char* psk,
unsigned int max_psk_len);
#endif
Environment* const env_;
Kind kind_;
ncrypto::SSLSessionPointer next_sess_;
ncrypto::SSLPointer ssl_;
ClientHelloParser hello_parser_;
v8::Global<v8::ArrayBufferView> ocsp_response_;
BaseObjectPtr<SecureContext> sni_context_;
BaseObjectPtr<SecureContext> sc_;
// BIO buffers hold encrypted data.
BIO* enc_in_ = nullptr; // StreamListener fills this for SSL_read().
BIO* enc_out_ = nullptr; // SSL_write()/handshake fills this for EncOut().
// Waiting for ClearIn() to pass to SSL_write().
std::unique_ptr<v8::BackingStore> pending_cleartext_input_;
size_t write_size_ = 0;
BaseObjectPtr<AsyncWrap> current_write_;
BaseObjectPtr<AsyncWrap> current_empty_write_;
std::string error_;
bool session_callbacks_ = false;
bool awaiting_new_session_ = false;
bool in_dowrite_ = false;
bool started_ = false;
bool shutdown_ = false;
bool cert_cb_running_ = false;
bool eof_ = false;
// TODO(@jasnell): These state flags should be revisited.
// The established_ flag indicates that the handshake is
// completed. The write_callback_scheduled_ flag is less
// clear -- once it is set to true, it is never set to
// false and it is only set to true after established_
// is set to true, so it's likely redundant.
bool established_ = false;
bool write_callback_scheduled_ = false;
int cycle_depth_ = 0;
// SSL_set_cert_cb
CertCb cert_cb_ = nullptr;
void* cert_cb_arg_ = nullptr;
ncrypto::BIOPointer bio_trace_;
bool has_active_write_issued_by_prev_listener_ = false;
public:
std::vector<unsigned char> alpn_protos_; // Accessed by SelectALPNCallback.
bool alpn_callback_enabled_ = false; // Accessed by SelectALPNCallback.
};
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_TLS_H_

759
src/crypto/crypto_util.cc Normal file
View File

@ -0,0 +1,759 @@
#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::reverse(std::begin(errors_), std::end(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)) { 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) {
Environment* env = Environment::GetCurrent(args);
args.GetReturnValue().Set(
BigInt::New(env->isolate(), 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

597
src/crypto/crypto_util.h Normal file
View File

@ -0,0 +1,597 @@
#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 "ncrypto.h"
#include <algorithm>
#include <climits>
#include <cstdio>
#include <memory>
#include <optional>
#include <string>
#include <vector>
namespace node::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;
bool ProcessFipsOptions();
bool InitCryptoOnce(v8::Isolate* isolate);
void InitCryptoOnce();
void InitCrypto(v8::Local<v8::Object> target);
extern void UseExtraCaCerts(std::string_view file);
void CleanupCachedRootCertificates();
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 <typename T>
void Decode(const v8::FunctionCallbackInfo<v8::Value>& args,
void (*callback)(T*, const v8::FunctionCallbackInfo<v8::Value>&,
const char*, size_t)) {
T* ctx;
ASSIGN_OR_RETURN_UNWRAP(&ctx, args.This());
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<v8::String>(), enc).IsNothing())
return;
callback(ctx, args, decoder.out(), decoder.size());
} else {
ArrayBufferViewContents<char> 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
};
template <typename... Args>
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>(args)...);
}
// Utility struct used to harvest error information from openssl's error stack
struct CryptoErrorStore final : public MemoryRetainer {
public:
void Capture();
bool Empty() const;
template <typename... Args>
void Insert(const NodeCryptoError error, Args&&... args);
v8::MaybeLocal<v8::Value> ToException(
Environment* env,
v8::Local<v8::String> exception_string = v8::Local<v8::String>()) const;
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(CryptoErrorStore)
SET_SELF_SIZE(CryptoErrorStore)
private:
std::vector<std::string> errors_;
};
template <typename... Args>
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>(args)...));
}
v8::MaybeLocal<v8::Value> cryptoErrorListToException(
Environment* env, const ncrypto::CryptoErrorList& errors);
template <typename T>
T* MallocOpenSSL(size_t count) {
void* mem = OPENSSL_malloc(MultiplyWithOverflowCheck(count, sizeof(T)));
CHECK_IMPLIES(mem == nullptr, count == 0);
return static_cast<T*>(mem);
}
// A helper class representing a read-only byte array. When deallocated, its
// contents are zeroed.
class ByteSource final {
public:
ByteSource() = default;
ByteSource(ByteSource&& other) noexcept;
~ByteSource();
ByteSource& operator=(ByteSource&& other) noexcept;
ByteSource(const ByteSource&) = delete;
ByteSource& operator=(const ByteSource&) = delete;
template <typename T = void>
inline const T* data() const {
return reinterpret_cast<const T*>(data_);
}
template <typename T = void>
operator ncrypto::Buffer<const T>() const {
return ncrypto::Buffer<const T>{
.data = data<T>(),
.len = size(),
};
}
inline size_t size() const { return size_; }
inline bool empty() const { return size_ == 0; }
inline operator bool() const { return data_ != nullptr; }
inline ncrypto::BignumPointer ToBN() const {
return ncrypto::BignumPointer(data<unsigned char>(), size());
}
// 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<v8::BackingStore> ReleaseToBackingStore(Environment* env);
v8::Local<v8::ArrayBuffer> ToArrayBuffer(Environment* env);
v8::MaybeLocal<v8::Uint8Array> ToBuffer(Environment* env);
static ByteSource Allocated(void* data, size_t size);
template <typename T>
static ByteSource Allocated(const ncrypto::Buffer<T>& buffer) {
return Allocated(buffer.data, buffer.len);
}
static ByteSource Foreign(const void* data, size_t size);
static ByteSource FromEncodedString(Environment* env,
v8::Local<v8::String> value,
enum encoding enc = BASE64);
static ByteSource FromStringOrBuffer(Environment* env,
v8::Local<v8::Value> value);
static ByteSource FromString(Environment* env,
v8::Local<v8::String> str,
bool ntc = false);
static ByteSource FromBuffer(v8::Local<v8::Value> buffer,
bool ntc = false);
static ByteSource FromBIO(const ncrypto::BIOPointer& bio);
static ByteSource NullTerminatedCopy(Environment* env,
v8::Local<v8::Value> value);
static ByteSource FromSymmetricKeyObjectHandle(v8::Local<v8::Value> handle);
static ByteSource FromSecretKeyBytes(
Environment* env, v8::Local<v8::Value> 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<v8::Value> args);
template <typename CryptoJobTraits>
class CryptoJob : public AsyncWrap, public ThreadPoolWork {
public:
using AdditionalParams = typename CryptoJobTraits::AdditionalParameters;
explicit CryptoJob(Environment* env,
v8::Local<v8::Object> 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<CryptoJob> 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());
v8::Local<v8::Value> exception;
v8::Local<v8::Value> args[2];
{
node::errors::TryCatchScope try_catch(env);
// If ToResult returns Nothing, then an exception should have been
// thrown and we should have caught it. Otherwise, args[0] and args[1]
// both should have been set to a value, even if the value is undefined.
if (ptr->ToResult(&args[0], &args[1]).IsNothing()) {
CHECK(try_catch.HasCaught());
CHECK(try_catch.CanContinue());
exception = try_catch.Exception();
}
}
if (!exception.IsEmpty()) {
ptr->MakeCallback(env->ondone_string(), 1, &exception);
} else {
CHECK(!args[0].IsEmpty());
CHECK(!args[1].IsEmpty());
ptr->MakeCallback(env->ondone_string(), arraysize(args), args);
}
}
virtual v8::Maybe<void> ToResult(v8::Local<v8::Value>* err,
v8::Local<v8::Value>* result) = 0;
CryptoJobMode mode() const { return mode_; }
CryptoErrorStore* errors() { return &errors_; }
AdditionalParams* params() { return &params_; }
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<v8::Value>& args) {
Environment* env = Environment::GetCurrent(args);
CryptoJob<CryptoJobTraits>* job;
ASSIGN_OR_RETURN_UNWRAP(&job, args.This());
if (job->mode() == kCryptoJobAsync)
return job->ScheduleWork();
v8::Local<v8::Value> ret[2];
env->PrintSyncTrace();
job->DoThreadPoolWork();
if (job->ToResult(&ret[0], &ret[1]).IsJust()) {
CHECK(!ret[0].IsEmpty());
CHECK(!ret[1].IsEmpty());
args.GetReturnValue().Set(
v8::Array::New(env->isolate(), ret, arraysize(ret)));
}
}
static void Initialize(
v8::FunctionCallback new_fn,
Environment* env,
v8::Local<v8::Object> target) {
v8::Isolate* isolate = env->isolate();
v8::HandleScope scope(isolate);
v8::Local<v8::Context> context = env->context();
v8::Local<v8::FunctionTemplate> 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 <typename DeriveBitsTraits>
class DeriveBitsJob final : public CryptoJob<DeriveBitsTraits> {
public:
using AdditionalParams = typename DeriveBitsTraits::AdditionalParameters;
static void New(const v8::FunctionCallbackInfo<v8::Value>& args) {
Environment* env = Environment::GetCurrent(args);
CryptoJobMode mode = GetCryptoJobMode(args[0]);
AdditionalParams params;
if (DeriveBitsTraits::AdditionalConfig(mode, args, 1, &params)
.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<v8::Object> target) {
CryptoJob<DeriveBitsTraits>::Initialize(New, env, target);
}
static void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
CryptoJob<DeriveBitsTraits>::RegisterExternalReferences(New, registry);
}
DeriveBitsJob(
Environment* env,
v8::Local<v8::Object> object,
CryptoJobMode mode,
AdditionalParams&& params)
: CryptoJob<DeriveBitsTraits>(
env,
object,
DeriveBitsTraits::Provider,
mode,
std::move(params)) {}
void DoThreadPoolWork() override {
ncrypto::ClearErrorOnReturn clear_error_on_return;
if (!DeriveBitsTraits::DeriveBits(AsyncWrap::env(),
*CryptoJob<DeriveBitsTraits>::params(),
&out_,
this->mode())) {
CryptoErrorStore* errors = CryptoJob<DeriveBitsTraits>::errors();
errors->Capture();
if (errors->Empty())
errors->Insert(NodeCryptoError::DERIVING_BITS_FAILED);
return;
}
success_ = true;
}
v8::Maybe<void> ToResult(v8::Local<v8::Value>* err,
v8::Local<v8::Value>* result) override {
Environment* env = AsyncWrap::env();
CryptoErrorStore* errors = CryptoJob<DeriveBitsTraits>::errors();
if (success_) {
CHECK(errors->Empty());
*err = v8::Undefined(env->isolate());
if (!DeriveBitsTraits::EncodeOutput(
env, *CryptoJob<DeriveBitsTraits>::params(), &out_)
.ToLocal(result)) {
return v8::Nothing<void>();
}
} else {
if (errors->Empty()) errors->Capture();
CHECK(!errors->Empty());
*result = v8::Undefined(env->isolate());
if (!errors->ToException(env).ToLocal(err)) {
return v8::Nothing<void>();
}
}
CHECK(!result->IsEmpty());
CHECK(!err->IsEmpty());
return v8::JustVoid();
}
SET_SELF_SIZE(DeriveBitsJob)
void MemoryInfo(MemoryTracker* tracker) const override {
tracker->TrackFieldWithSize("out", out_.size());
CryptoJob<DeriveBitsTraits>::MemoryInfo(tracker);
}
private:
ByteSource out_;
bool success_ = false;
};
void ThrowCryptoError(Environment* env,
unsigned long err, // NOLINT(runtime/int)
const char* message = nullptr);
// WebIDL AllowSharedBufferSource.
inline bool IsAnyBufferSource(v8::Local<v8::Value> arg) {
return arg->IsArrayBufferView() ||
arg->IsArrayBuffer() ||
arg->IsSharedArrayBuffer();
}
template <typename T>
class ArrayBufferOrViewContents final {
public:
ArrayBufferOrViewContents() = default;
ArrayBufferOrViewContents(const ArrayBufferOrViewContents&) = delete;
void operator=(const ArrayBufferOrViewContents&) = delete;
inline explicit ArrayBufferOrViewContents(v8::Local<v8::Value> buf) {
if (buf.IsEmpty()) {
return;
}
CHECK(IsAnyBufferSource(buf));
if (buf->IsArrayBufferView()) {
auto view = buf.As<v8::ArrayBufferView>();
offset_ = view->ByteOffset();
length_ = view->ByteLength();
data_ = view->Buffer()->Data();
} else if (buf->IsArrayBuffer()) {
auto ab = buf.As<v8::ArrayBuffer>();
offset_ = 0;
length_ = ab->ByteLength();
data_ = ab->Data();
} else {
auto sab = buf.As<v8::SharedArrayBuffer>();
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 (empty()) return &buf;
return reinterpret_cast<T*>(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 (empty()) return &buf;
return reinterpret_cast<T*>(data_) + offset_;
}
inline size_t size() const { return length_; }
inline bool empty() const { return length_ == 0; }
// 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 (empty()) return {};
auto buf = ncrypto::DataPointer::Alloc(size());
memcpy(buf.get(), data(), size());
return ByteSource::Allocated(buf.release());
}
inline ByteSource ToNullTerminatedCopy() const {
if (empty()) return {};
auto buf = ncrypto::DataPointer::Alloc(size() + 1);
memcpy(buf.get(), data(), size());
static_cast<char*>(buf.get())[size()] = 0;
return ByteSource::Allocated(buf.release());
}
inline ncrypto::DataPointer ToDataPointer() const {
if (empty()) return {};
if (auto dp = ncrypto::DataPointer::Alloc(size())) {
memcpy(dp.get(), data(), size());
return dp;
}
return {};
}
template <typename M>
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<v8::Value> EncodeBignum(Environment* env,
const BIGNUM* bn,
int size);
v8::Maybe<void> SetEncodedValue(Environment* env,
v8::Local<v8::Object> target,
v8::Local<v8::String> name,
const BIGNUM* bn,
int size = 0);
namespace Util {
void Initialize(Environment* env, v8::Local<v8::Object> target);
void RegisterExternalReferences(ExternalReferenceRegistry* registry);
} // namespace Util
} // namespace node::crypto
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_UTIL_H_

1018
src/crypto/crypto_x509.cc Normal file

File diff suppressed because it is too large Load Diff

140
src/crypto/crypto_x509.h Normal file
View File

@ -0,0 +1,140 @@
#ifndef SRC_CRYPTO_CRYPTO_X509_H_
#define SRC_CRYPTO_CRYPTO_X509_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "base_object.h"
#include "crypto/crypto_util.h"
#include "env.h"
#include "memory_tracker.h"
#include "ncrypto.h"
#include "node_worker.h"
#include "v8.h"
namespace node {
namespace crypto {
// The ManagedX509 class is essentially a smart pointer for
// X509 objects that allows an X509Certificate instance to
// be cloned at the JS level while pointing at the same
// underlying X509 instance.
class ManagedX509 final : public MemoryRetainer {
public:
ManagedX509() = default;
explicit ManagedX509(ncrypto::X509Pointer&& cert);
ManagedX509(const ManagedX509& that);
ManagedX509& operator=(const ManagedX509& that);
inline operator bool() const { return !!cert_; }
inline X509* get() const { return cert_.get(); }
inline ncrypto::X509View view() const { return cert_; }
inline operator ncrypto::X509View() const { return cert_; }
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(ManagedX509)
SET_SELF_SIZE(ManagedX509)
private:
ncrypto::X509Pointer cert_;
};
class X509Certificate final : public BaseObject {
public:
enum class GetPeerCertificateFlag {
NONE,
SERVER
};
static void Initialize(Environment* env, v8::Local<v8::Object> target);
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);
static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
Environment* env);
static bool HasInstance(Environment* env, v8::Local<v8::Object> object);
static v8::MaybeLocal<v8::Object> New(
Environment* env,
ncrypto::X509Pointer cert,
STACK_OF(X509) * issuer_chain = nullptr);
static v8::MaybeLocal<v8::Object> New(
Environment* env,
std::shared_ptr<ManagedX509> cert,
STACK_OF(X509)* issuer_chain = nullptr);
static v8::MaybeLocal<v8::Object> GetCert(Environment* env,
const ncrypto::SSLPointer& ssl);
static v8::MaybeLocal<v8::Object> GetPeerCert(Environment* env,
const ncrypto::SSLPointer& ssl,
GetPeerCertificateFlag flag);
static v8::Local<v8::Object> Wrap(Environment* env,
v8::Local<v8::Object> object,
ncrypto::X509Pointer cert);
inline BaseObjectPtr<X509Certificate> getIssuerCert() const {
return issuer_cert_;
}
inline ncrypto::X509View view() const { return *cert_; }
inline X509* get() { return cert_->get(); }
v8::MaybeLocal<v8::Value> toObject(Environment* env);
static v8::MaybeLocal<v8::Value> toObject(Environment* env,
const ncrypto::X509View& cert);
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(X509Certificate)
SET_SELF_SIZE(X509Certificate)
class X509CertificateTransferData : public worker::TransferData {
public:
explicit X509CertificateTransferData(
const std::shared_ptr<ManagedX509>& data)
: data_(data) {}
BaseObjectPtr<BaseObject> Deserialize(
Environment* env,
v8::Local<v8::Context> context,
std::unique_ptr<worker::TransferData> self) override;
SET_MEMORY_INFO_NAME(X509CertificateTransferData)
SET_SELF_SIZE(X509CertificateTransferData)
SET_NO_MEMORY_INFO()
private:
std::shared_ptr<ManagedX509> data_;
};
BaseObject::TransferMode GetTransferMode() const override;
std::unique_ptr<worker::TransferData> CloneForMessaging() const override;
private:
X509Certificate(Environment* env,
v8::Local<v8::Object> object,
std::shared_ptr<ManagedX509> cert,
v8::Local<v8::Object> issuer_chain = v8::Local<v8::Object>());
std::shared_ptr<ManagedX509> cert_;
BaseObjectPtr<X509Certificate> issuer_cert_;
};
inline X509Certificate::GetPeerCertificateFlag operator|(
X509Certificate::GetPeerCertificateFlag lhs,
X509Certificate::GetPeerCertificateFlag rhs) {
return static_cast<X509Certificate::GetPeerCertificateFlag>(
static_cast<int>(lhs) | static_cast<int>(rhs));
}
inline X509Certificate::GetPeerCertificateFlag operator&(
X509Certificate::GetPeerCertificateFlag lhs,
X509Certificate::GetPeerCertificateFlag rhs) {
return static_cast<X509Certificate::GetPeerCertificateFlag>(
static_cast<int>(lhs) & static_cast<int>(rhs));
}
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_X509_H_

1149
src/dataqueue/queue.cc Normal file

File diff suppressed because it is too large Load Diff

306
src/dataqueue/queue.h Normal file
View File

@ -0,0 +1,306 @@
#pragma once
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include <base_object.h>
#include <memory_tracker.h>
#include <node.h>
#include <node_bob.h>
#include <node_file.h>
#include <stream_base.h>
#include <uv.h>
#include <v8.h>
#include <memory>
#include <optional>
#include <vector>
namespace node {
// Represents a sequenced collection of data sources that can be
// consumed as a single logical stream of data. Sources can be
// memory-resident or streaming.
//
// There are two essential kinds of DataQueue:
//
// * Idempotent - Multiple reads always produce the same result.
// This is even the case if individual sources
// are not memory-resident. Reads never change
// the state of the DataQueue. Every entry in
// an Idempotent DataQueue must also be idempotent.
//
// * Non-idempotent - Reads are destructive of the internal state.
// A non-idempotent DataQueue can be read at
// most once and only by a single reader.
// Entries in a non-idempotent DataQueue can
// be a mix of idempotent and non-idempotent
// entries.
//
// The DataQueue is essentially a collection of DataQueue::Entry
// instances. A DataQueue::Entry is a single logical source of
// data. The data may be memory-resident or streaming. The entry
// can be idempotent or non-idempotent. An entry cannot be read
// by itself, it must be part of a DataQueue to be consumed.
//
// Example of creating an idempotent DataQueue:
//
// std::shared_ptr<v8::BackingStore> store1 = getBackingStoreSomehow();
// std::shared_ptr<v8::BackingStore> store2 = getBackingStoreSomehow();
//
// std::vector<std::unique_ptr<DataQueue::Entry>> list;
// list.push_back(DataQueue::CreateInMemoryEntryFromBackingStore(
// store1, 0, len1));
// list.push_back(DataQueue::CreateInMemoryEntryFromBackingStore(
// store2, 0, len2));
//
// std::shared_ptr<DataQueue> data_queue =
// DataQueue::CreateIdempotent(std::move(list));
//
// Importantly, idempotent DataQueue's are immutable and all entries
// must be provided when the DataQueue is constructed. Every entry
// must be idempotent with known sizes. The entries may be memory
// resident or streaming. Streaming entries must be capable of
// being read multiple times.
//
// Because idempotent DataQueue's will always produce the same results
// when read, they can be sliced. Slices yield a new DataQueue instance
// that is a subset view over the original:
//
// std::shared_ptr<DataQueue> slice = data_queue.slice(
// 5, v8::Just(10UL));
//
// Example of creating a non-idempotent DataQueue:
//
// std::shared_ptr<v8::BackingStore> store1 = getBackingStoreSomehow();
// std::shared_ptr<v8::BackingStore> store2 = getBackingStoreSomehow();
//
// std::shared_ptr<DataQueue> data_queue = DataQueue::Create();
//
// data_queue->append(DataQueue::CreateInMemoryEntryFromBackingStore(
// store1, 0, len1));
//
// data_queue->append(DataQueue::CreateInMemoryEntryFromBackingStore(
// store2, 0, len2));
//
// These data-queues can have new entries appended to them. Entries can
// be memory-resident or streaming. Streaming entries might not have
// a known size. Entries may not be capable of being read multiple
// times.
//
// A non-idempotent data queue will, by default, allow any amount of
// entries to be appended to it. To limit the size of the DataQueue,
// or the close the DataQueue (preventing new entries from being
// appending), use the cap() method. The DataQueue can be capped
// at a specific size or whatever size it currently it.
//
// It might not be possible for a non-idempotent DataQueue to provide
// a size because it might not know how much data a streaming entry
// will ultimately provide.
//
// Non-idempotent DataQueues cannot be sliced.
//
// To read from a DataQueue, we use the node::bob::Source API
// (see src/node_bob.h).
//
// std::shared_ptr<DataQueue::Reader> reader = data_queue->get_reader();
//
// reader->Pull(
// [](int status, const DataQueue::Vec* vecs,
// uint64_t count, Done done) {
// // status is one of node::bob::Status
// // vecs is zero or more data buffers containing the read data
// // count is the number of vecs
// // done is a callback to be invoked when done processing the data
// }, options, nullptr, 0, 16);
//
// Keep calling Pull() until status is equal to node::bob::Status::STATUS_EOS.
//
// For idempotent DataQueues, any number of readers can be created and
// pull concurrently from the same DataQueue. The DataQueue can be read
// multiple times. Successful reads should always produce the same result.
// If, for whatever reason, the implementation cannot ensure that the
// data read will remain the same, the read must fail with an error status.
//
// For non-idempotent DataQueues, only a single reader is ever allowed for
// the DataQueue, and the data can only ever be read once.
class DataQueue : public MemoryRetainer {
public:
struct Vec {
uint8_t* base;
uint64_t len;
};
// A DataQueue::Reader consumes the DataQueue. If the data queue is
// idempotent, multiple Readers can be attached to the DataQueue at
// any given time, all guaranteed to yield the same result when the
// data is read. Otherwise, only a single Reader can be attached.
class Reader : public MemoryRetainer, public bob::Source<Vec> {
public:
using Next = bob::Next<Vec>;
using Done = bob::Done;
};
// A BackpressureListener can be used to receive notifications
// when a non-idempotent DataQueue releases entries as they
// are consumed.
class BackpressureListener {
public:
virtual void EntryRead(size_t amount) = 0;
};
// A DataQueue::Entry represents a logical chunk of data in the queue.
// The entry may or may not represent memory-resident data. It may
// or may not be consumable more than once.
class Entry : public MemoryRetainer {
public:
// Returns a new Entry that is a view over this entries data
// from the start offset to the ending offset. If the end
// offset is omitted, the slice extends to the end of the
// data.
//
// Creating a slice is only possible if is_idempotent() returns
// true. This is because consuming either the original entry or
// the new entry would change the state of the other in non-
// deterministic ways. When is_idempotent() returns false, slice()
// must return a nulled unique_ptr.
//
// Creating a slice is also only possible if the size of the
// entry is known. If size() returns std::nullopt, slice()
// must return a nulled unique_ptr.
virtual std::unique_ptr<Entry> slice(
uint64_t start, std::optional<uint64_t> end = std::nullopt) = 0;
// Returns the number of bytes represented by this Entry if it is
// known. Certain types of entries, such as those backed by streams
// might not know the size in advance and therefore cannot provide
// a value. In such cases, size() must return v8::Nothing<uint64_t>.
//
// If the entry is idempotent, a size should always be available.
virtual std::optional<uint64_t> size() const = 0;
// When true, multiple reads on the object must produce the exact
// same data or the reads will fail. Some sources of entry data,
// such as streams, may not be capable of preserving idempotency
// and therefore must not claim to be. If an entry claims to be
// idempotent and cannot preserve that quality, subsequent reads
// must fail with an error when a variance is detected.
virtual bool is_idempotent() const = 0;
};
// Creates an idempotent DataQueue with a pre-established collection
// of entries. All of the entries must also be idempotent otherwise
// an empty std::unique_ptr will be returned.
static std::shared_ptr<DataQueue> CreateIdempotent(
std::vector<std::unique_ptr<Entry>> list);
// Creates a non-idempotent DataQueue. This kind of queue can be
// mutated and updated such that multiple reads are not guaranteed
// to produce the same result. The entries added can be of any type.
static std::shared_ptr<DataQueue> Create(
std::optional<uint64_t> capped = std::nullopt);
// Creates an idempotent Entry from a v8::ArrayBufferView. To help
// ensure idempotency, the underlying ArrayBuffer is detached from
// the BackingStore. It is the callers responsibility to ensure that
// the BackingStore is not otherwise modified through any other
// means. If the ArrayBuffer is not detachable, nullptr will be
// returned.
static std::unique_ptr<Entry> CreateInMemoryEntryFromView(
v8::Local<v8::ArrayBufferView> view);
// Creates an idempotent Entry from a v8::BackingStore. It is the
// callers responsibility to ensure that the BackingStore is not
// otherwise modified through any other means. If the ArrayBuffer
// is not detachable, nullptr will be returned.
static std::unique_ptr<Entry> CreateInMemoryEntryFromBackingStore(
std::shared_ptr<v8::BackingStore> store,
uint64_t offset,
uint64_t length);
static std::unique_ptr<Entry> CreateDataQueueEntry(
std::shared_ptr<DataQueue> data_queue);
static std::unique_ptr<Entry> CreateFdEntry(Environment* env,
v8::Local<v8::Value> path);
// Creates a Reader for the given queue. If the queue is idempotent,
// any number of readers can be created, all of which are guaranteed
// to provide the same data. Otherwise, only a single reader is
// permitted.
virtual std::shared_ptr<Reader> get_reader() = 0;
// Append a single new entry to the queue. Appending is only allowed
// when is_idempotent() is false. std::nullopt will be returned
// if is_idempotent() is true. std::optional(false) will be returned if the
// data queue is not idempotent but the entry otherwise cannot be added.
virtual std::optional<bool> append(std::unique_ptr<Entry> entry) = 0;
// Caps the size of this DataQueue preventing additional entries to
// be added if those cause the size to extend beyond the specified
// limit.
//
// If limit is zero, or is less than the known current size of the
// data queue, the limit is set to the current known size, meaning
// that no additional entries can be added at all.
//
// If the size of the data queue is not known, the limit will be
// ignored and no additional entries will be allowed at all.
//
// If is_idempotent is true capping is unnecessary because the data
// queue cannot be appended to. In that case, cap() is a non-op.
//
// If the data queue has already been capped, cap can be called
// again with a smaller size.
virtual void cap(uint64_t limit = 0) = 0;
// Returns a new DataQueue that is a view over this queues data
// from the start offset to the ending offset. If the end offset
// is omitted, the slice extends to the end of the data.
//
// The slice will coverage a range from start up to, but excluding, end.
//
// Creating a slice is only possible if is_idempotent() returns
// true. This is because consuming either the original DataQueue or
// the new queue would change the state of the other in non-
// deterministic ways. When is_idempotent() returns false, slice()
// must return a nulled unique_ptr.
//
// Creating a slice is also only possible if the size of the
// DataQueue is known. If size() returns std::nullopt, slice()
// must return a null unique_ptr.
virtual std::shared_ptr<DataQueue> slice(
uint64_t start, std::optional<uint64_t> end = std::nullopt) = 0;
// The size of DataQueue is the total size of all of its member entries.
// If any of the entries is not able to specify a size, the DataQueue
// will also be incapable of doing so, in which case size() must return
// std::nullopt.
virtual std::optional<uint64_t> size() const = 0;
// A DataQueue is idempotent only if all of its member entries are
// idempotent.
virtual bool is_idempotent() const = 0;
// True only if cap is called or the data queue is a limited to a
// fixed size.
virtual bool is_capped() const = 0;
// If the data queue has been capped, and the size of the data queue
// is known, maybeCapRemaining will return the number of additional
// bytes the data queue can receive before reaching the cap limit.
// If the size of the queue cannot be known, or the cap has not
// been set, maybeCapRemaining() will return std::nullopt.
virtual std::optional<uint64_t> maybeCapRemaining() const = 0;
// BackpressureListeners only work on non-idempotent DataQueues.
virtual void addBackpressureListener(BackpressureListener* listener) = 0;
virtual void removeBackpressureListener(BackpressureListener* listener) = 0;
static void Initialize(Environment* env, v8::Local<v8::Object> target);
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);
};
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

232
src/debug_utils-inl.h Normal file
View File

@ -0,0 +1,232 @@
#ifndef SRC_DEBUG_UTILS_INL_H_
#define SRC_DEBUG_UTILS_INL_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "debug_utils.h"
#include "env.h"
#include "util-inl.h"
#include <type_traits>
namespace node {
struct ToStringHelper {
template <typename T>
static std::string Convert(
const T& value,
std::string(T::* to_string)() const = &T::ToString) {
return (value.*to_string)();
}
template <typename T,
typename test_for_number = typename std::
enable_if<std::is_arithmetic<T>::value, bool>::type,
typename dummy = bool>
static std::string Convert(const T& value) { return std::to_string(value); }
static std::string Convert(const char* value) {
return value != nullptr ? value : "(null)";
}
static std::string Convert(const std::string& value) { return value; }
static std::string Convert(std::string_view value) {
return std::string(value);
}
static std::string Convert(bool value) { return value ? "true" : "false"; }
template <unsigned BASE_BITS,
typename T,
typename = std::enable_if_t<std::is_integral_v<T>>>
static std::string BaseConvert(const T& value) {
auto v = static_cast<uint64_t>(value);
char ret[3 * sizeof(T)];
char* ptr = ret + 3 * sizeof(T) - 1;
*ptr = '\0';
const char* digits = "0123456789abcdef";
do {
unsigned digit = v & ((1 << BASE_BITS) - 1);
*--ptr =
(BASE_BITS < 4 ? static_cast<char>('0' + digit) : digits[digit]);
} while ((v >>= BASE_BITS) != 0);
return ptr;
}
template <unsigned BASE_BITS,
typename T,
typename = std::enable_if_t<!std::is_integral_v<T>>>
static std::string BaseConvert(T& value) { // NOLINT(runtime/references)
return Convert(std::forward<T>(value));
}
};
template <typename T>
std::string ToString(const T& value) {
return ToStringHelper::Convert(value);
}
template <unsigned BASE_BITS, typename T>
std::string ToBaseString(const T& value) {
return ToStringHelper::BaseConvert<BASE_BITS>(value);
}
inline std::string SPrintFImpl(const char* format) {
const char* p = strchr(format, '%');
if (p == nullptr) [[unlikely]]
return format;
CHECK_EQ(p[1], '%'); // Only '%%' allowed when there are no arguments.
return std::string(format, p + 1) + SPrintFImpl(p + 2);
}
template <typename Arg, typename... Args>
std::string COLD_NOINLINE SPrintFImpl( // NOLINT(runtime/string)
const char* format, Arg&& arg, Args&&... args) {
const char* p = strchr(format, '%');
CHECK_NOT_NULL(p); // If you hit this, you passed in too many arguments.
std::string ret(format, p);
// Ignore long / size_t modifiers
while (strchr("lz", *++p) != nullptr) {}
switch (*p) {
case '%': {
return ret + '%' + SPrintFImpl(p + 1,
std::forward<Arg>(arg),
std::forward<Args>(args)...);
}
default: {
return ret + '%' + SPrintFImpl(p,
std::forward<Arg>(arg),
std::forward<Args>(args)...);
}
case 'd':
case 'i':
case 'u':
case 's':
ret += ToString(arg);
break;
case 'o':
ret += ToBaseString<3>(arg);
break;
case 'x':
ret += ToBaseString<4>(arg);
break;
case 'X':
ret += node::ToUpper(ToBaseString<4>(arg));
break;
case 'p': {
CHECK(std::is_pointer<typename std::remove_reference<Arg>::type>::value);
char out[20];
int n = snprintf(out,
sizeof(out),
"%p",
*reinterpret_cast<const void* const*>(&arg));
CHECK_GE(n, 0);
ret += out;
break;
}
}
return ret + SPrintFImpl(p + 1, std::forward<Args>(args)...);
}
template <typename... Args>
std::string COLD_NOINLINE SPrintF( // NOLINT(runtime/string)
const char* format, Args&&... args) {
return SPrintFImpl(format, std::forward<Args>(args)...);
}
template <typename... Args>
void COLD_NOINLINE FPrintF(FILE* file, const char* format, Args&&... args) {
FWrite(file, SPrintF(format, std::forward<Args>(args)...));
}
template <typename... Args>
inline void FORCE_INLINE Debug(EnabledDebugList* list,
DebugCategory cat,
const char* format,
Args&&... args) {
if (!list->enabled(cat)) [[unlikely]]
return;
FPrintF(stderr, format, std::forward<Args>(args)...);
}
inline void FORCE_INLINE Debug(EnabledDebugList* list,
DebugCategory cat,
const char* message) {
if (!list->enabled(cat)) [[unlikely]]
return;
FPrintF(stderr, "%s", message);
}
template <typename... Args>
inline void FORCE_INLINE
Debug(Environment* env, DebugCategory cat, const char* format, Args&&... args) {
Debug(env->enabled_debug_list(), cat, format, std::forward<Args>(args)...);
}
inline void FORCE_INLINE Debug(Environment* env,
DebugCategory cat,
const char* message) {
Debug(env->enabled_debug_list(), cat, message);
}
template <typename... Args>
inline void Debug(Environment* env,
DebugCategory cat,
const std::string& format,
Args&&... args) {
Debug(env->enabled_debug_list(),
cat,
format.c_str(),
std::forward<Args>(args)...);
}
// Used internally by the 'real' Debug(AsyncWrap*, ...) functions below, so that
// the FORCE_INLINE flag on them doesn't apply to the contents of this function
// as well.
// We apply COLD_NOINLINE to tell the compiler that it's not worth optimizing
// this function for speed and it should rather focus on keeping it out of
// hot code paths. In particular, we want to keep the string concatenating code
// out of the function containing the original `Debug()` call.
template <typename... Args>
void COLD_NOINLINE UnconditionalAsyncWrapDebug(AsyncWrap* async_wrap,
const char* format,
Args&&... args) {
Debug(async_wrap->env(),
static_cast<DebugCategory>(async_wrap->provider_type()),
async_wrap->diagnostic_name() + " " + format + "\n",
std::forward<Args>(args)...);
}
template <typename... Args>
inline void FORCE_INLINE Debug(AsyncWrap* async_wrap,
const char* format,
Args&&... args) {
DCHECK_NOT_NULL(async_wrap);
if (auto cat = static_cast<DebugCategory>(async_wrap->provider_type());
!async_wrap->env()->enabled_debug_list()->enabled(cat)) [[unlikely]] {
return;
}
UnconditionalAsyncWrapDebug(async_wrap, format, std::forward<Args>(args)...);
}
template <typename... Args>
inline void FORCE_INLINE Debug(AsyncWrap* async_wrap,
const std::string& format,
Args&&... args) {
Debug(async_wrap, format.c_str(), std::forward<Args>(args)...);
}
namespace per_process {
template <typename... Args>
inline void FORCE_INLINE Debug(DebugCategory cat,
const char* format,
Args&&... args) {
Debug(&enabled_debug_list, cat, format, std::forward<Args>(args)...);
}
inline void FORCE_INLINE Debug(DebugCategory cat, const char* message) {
Debug(&enabled_debug_list, cat, message);
}
} // namespace per_process
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_DEBUG_UTILS_INL_H_

546
src/debug_utils.cc Normal file
View File

@ -0,0 +1,546 @@
#include "debug_utils-inl.h" // NOLINT(build/include)
#include "env-inl.h"
#include "node_internals.h"
#include "util.h"
#ifdef __POSIX__
#if defined(__linux__)
#include <features.h>
#endif
#ifdef __ANDROID__
#include <android/log.h>
#endif
#if defined(__linux__) && !defined(__GLIBC__) || \
defined(__UCLIBC__) || \
defined(_AIX)
#define HAVE_EXECINFO_H 0
#else
#define HAVE_EXECINFO_H 1
#endif
#if HAVE_EXECINFO_H
#include <cxxabi.h>
#include <dlfcn.h>
#include <execinfo.h>
#include <unistd.h>
#include <sys/mman.h>
#include <cstdio>
#endif
#endif // __POSIX__
#if defined(__linux__) || defined(__sun) || \
defined(__FreeBSD__) || defined(__OpenBSD__) || \
defined(__DragonFly__)
#include <link.h>
#endif
#ifdef __APPLE__
#include <mach-o/dyld.h> // _dyld_get_image_name()
#endif // __APPLE__
#ifdef _AIX
#include <sys/ldr.h> // ld_info structure
#endif // _AIX
#ifdef _WIN32
#include <Lm.h>
#include <Windows.h>
#include <dbghelp.h>
#include <process.h>
#include <psapi.h>
#include <tchar.h>
#endif // _WIN32
namespace node {
namespace per_process {
EnabledDebugList enabled_debug_list;
}
using v8::Local;
using v8::StackTrace;
void EnabledDebugList::Parse(Environment* env) {
std::string cats;
credentials::SafeGetenv("NODE_DEBUG_NATIVE", &cats, env);
Parse(cats);
}
void EnabledDebugList::Parse(const std::string& cats) {
std::string debug_categories = cats;
while (!debug_categories.empty()) {
std::string::size_type comma_pos = debug_categories.find(',');
std::string wanted = ToLower(debug_categories.substr(0, comma_pos));
#define V(name) \
{ \
static const std::string available_category = ToLower(#name); \
if (available_category.find(wanted) != std::string::npos) \
set_enabled(DebugCategory::name); \
}
DEBUG_CATEGORY_NAMES(V)
#undef V
if (comma_pos == std::string::npos) break;
// Use everything after the `,` as the list for the next iteration.
debug_categories = debug_categories.substr(comma_pos + 1);
}
}
#ifdef __POSIX__
#if HAVE_EXECINFO_H
class PosixSymbolDebuggingContext final : public NativeSymbolDebuggingContext {
public:
PosixSymbolDebuggingContext() : pagesize_(getpagesize()) { }
SymbolInfo LookupSymbol(void* address) override {
Dl_info info;
const bool have_info = dladdr(address, &info);
SymbolInfo ret;
if (!have_info)
return ret;
if (info.dli_sname != nullptr) {
if (char* demangled =
abi::__cxa_demangle(info.dli_sname, nullptr, nullptr, nullptr)) {
ret.name = demangled;
free(demangled);
} else {
ret.name = info.dli_sname;
}
}
if (info.dli_fname != nullptr) {
ret.filename = info.dli_fname;
}
return ret;
}
bool IsMapped(void* address) override {
void* page_aligned = reinterpret_cast<void*>(
reinterpret_cast<uintptr_t>(address) & ~(pagesize_ - 1));
return msync(page_aligned, pagesize_, MS_ASYNC) == 0;
}
int GetStackTrace(void** frames, int count) override {
return backtrace(frames, count);
}
private:
uintptr_t pagesize_;
};
std::unique_ptr<NativeSymbolDebuggingContext>
NativeSymbolDebuggingContext::New() {
return std::make_unique<PosixSymbolDebuggingContext>();
}
#else // HAVE_EXECINFO_H
std::unique_ptr<NativeSymbolDebuggingContext>
NativeSymbolDebuggingContext::New() {
return std::make_unique<NativeSymbolDebuggingContext>();
}
#endif // HAVE_EXECINFO_H
#else // __POSIX__
class Win32SymbolDebuggingContext final : public NativeSymbolDebuggingContext {
public:
Win32SymbolDebuggingContext() {
current_process_ = GetCurrentProcess();
USE(SymInitialize(current_process_, nullptr, true));
}
~Win32SymbolDebuggingContext() override {
USE(SymCleanup(current_process_));
}
using NameAndDisplacement = std::pair<std::string, DWORD64>;
NameAndDisplacement WrappedSymFromAddr(DWORD64 dwAddress) const {
// Refs: https://docs.microsoft.com/en-us/windows/desktop/Debug/retrieving-symbol-information-by-address
// Patches:
// Use `fprintf(stderr, ` instead of `printf`
// `sym.filename = pSymbol->Name` on success
// `current_process_` instead of `hProcess.
DWORD64 dwDisplacement = 0;
// Patch: made into arg - DWORD64 dwAddress = SOME_ADDRESS;
char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
const auto pSymbol = reinterpret_cast<PSYMBOL_INFO>(buffer);
pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
pSymbol->MaxNameLen = MAX_SYM_NAME;
if (SymFromAddr(current_process_, dwAddress, &dwDisplacement, pSymbol)) {
// SymFromAddr returned success
return NameAndDisplacement(pSymbol->Name, dwDisplacement);
} else {
// SymFromAddr failed
#ifdef DEBUG
const DWORD error = GetLastError();
fprintf(stderr, "SymFromAddr returned error : %lu\n", error);
#else
// Consume the error anyway
USE(GetLastError());
#endif // DEBUG
}
// End MSDN code
return NameAndDisplacement();
}
SymbolInfo WrappedGetLine(DWORD64 dwAddress) const {
SymbolInfo sym{};
// Refs: https://docs.microsoft.com/en-us/windows/desktop/Debug/retrieving-symbol-information-by-address
// Patches:
// Use `fprintf(stderr, ` instead of `printf`.
// Assign values to `sym` on success.
// `current_process_` instead of `hProcess.
// Patch: made into arg - DWORD64 dwAddress;
DWORD dwDisplacement;
IMAGEHLP_LINE64 line;
SymSetOptions(SYMOPT_LOAD_LINES);
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
// Patch: made into arg - dwAddress = 0x1000000;
if (SymGetLineFromAddr64(current_process_, dwAddress,
&dwDisplacement, &line)) {
// SymGetLineFromAddr64 returned success
sym.filename = line.FileName;
sym.line = line.LineNumber;
} else {
// SymGetLineFromAddr64 failed
#ifdef DEBUG
const DWORD error = GetLastError();
fprintf(stderr, "SymGetLineFromAddr64 returned error : %lu\n", error);
#else
// Consume the error anyway
USE(GetLastError());
#endif // DEBUG
}
// End MSDN code
return sym;
}
// Fills the SymbolInfo::name of the io/out argument `sym`
std::string WrappedUnDecorateSymbolName(const char* name) const {
// Refs: https://docs.microsoft.com/en-us/windows/desktop/Debug/retrieving-undecorated-symbol-names
// Patches:
// Use `fprintf(stderr, ` instead of `printf`.
// return `szUndName` instead of `printf` on success
char szUndName[MAX_SYM_NAME];
if (UnDecorateSymbolName(name, szUndName, sizeof(szUndName),
UNDNAME_COMPLETE)) {
// UnDecorateSymbolName returned success
return szUndName;
} else {
// UnDecorateSymbolName failed
#ifdef DEBUG
const DWORD error = GetLastError();
fprintf(stderr, "UnDecorateSymbolName returned error %lu\n", error);
#else
// Consume the error anyway
USE(GetLastError());
#endif // DEBUG
}
return {};
}
SymbolInfo LookupSymbol(void* address) override {
const DWORD64 dw_address = reinterpret_cast<DWORD64>(address);
SymbolInfo ret = WrappedGetLine(dw_address);
std::tie(ret.name, ret.dis) = WrappedSymFromAddr(dw_address);
if (!ret.name.empty()) {
ret.name = WrappedUnDecorateSymbolName(ret.name.c_str());
}
return ret;
}
bool IsMapped(void* address) override {
MEMORY_BASIC_INFORMATION info;
if (VirtualQuery(address, &info, sizeof(info)) != sizeof(info))
return false;
return info.State == MEM_COMMIT && info.Protect != 0;
}
int GetStackTrace(void** frames, int count) override {
return CaptureStackBackTrace(0, count, frames, nullptr);
}
Win32SymbolDebuggingContext(const Win32SymbolDebuggingContext&) = delete;
Win32SymbolDebuggingContext(Win32SymbolDebuggingContext&&) = delete;
Win32SymbolDebuggingContext operator=(const Win32SymbolDebuggingContext&)
= delete;
Win32SymbolDebuggingContext operator=(Win32SymbolDebuggingContext&&)
= delete;
private:
HANDLE current_process_;
};
std::unique_ptr<NativeSymbolDebuggingContext>
NativeSymbolDebuggingContext::New() {
return std::unique_ptr<NativeSymbolDebuggingContext>(
new Win32SymbolDebuggingContext());
}
#endif // __POSIX__
std::string NativeSymbolDebuggingContext::SymbolInfo::Display() const {
std::ostringstream oss;
oss << name;
if (dis != 0) {
oss << "+" << dis;
}
if (!filename.empty()) {
oss << " [" << filename << ']';
}
if (line != 0) {
oss << ":L" << line;
}
return oss.str();
}
void DumpNativeBacktrace(FILE* fp) {
fprintf(fp, "----- Native stack trace -----\n\n");
auto sym_ctx = NativeSymbolDebuggingContext::New();
void* frames[256];
const int size = sym_ctx->GetStackTrace(frames, arraysize(frames));
for (int i = 1; i < size; i += 1) {
void* frame = frames[i];
NativeSymbolDebuggingContext::SymbolInfo s = sym_ctx->LookupSymbol(frame);
fprintf(fp, "%2d: %p %s\n", i, frame, s.Display().c_str());
}
}
void DumpJavaScriptBacktrace(FILE* fp) {
v8::Isolate* isolate = v8::Isolate::TryGetCurrent();
if (isolate == nullptr) {
return;
}
Local<StackTrace> stack;
if (!GetCurrentStackTrace(isolate).ToLocal(&stack)) {
return;
}
fprintf(fp, "\n----- JavaScript stack trace -----\n\n");
PrintStackTrace(isolate, stack, StackTracePrefix::kNumber);
fprintf(fp, "\n");
}
void CheckedUvLoopClose(uv_loop_t* loop) {
if (uv_loop_close(loop) == 0) return;
PrintLibuvHandleInformation(loop, stderr);
fflush(stderr);
// Finally, abort.
UNREACHABLE("uv_loop_close() while having open handles");
}
void PrintLibuvHandleInformation(uv_loop_t* loop, FILE* stream) {
struct Info {
std::unique_ptr<NativeSymbolDebuggingContext> ctx;
FILE* stream;
size_t num_handles;
};
Info info { NativeSymbolDebuggingContext::New(), stream, 0 };
fprintf(stream, "uv loop at [%p] has open handles:\n", loop);
uv_walk(loop, [](uv_handle_t* handle, void* arg) {
Info* info = static_cast<Info*>(arg);
NativeSymbolDebuggingContext* sym_ctx = info->ctx.get();
FILE* stream = info->stream;
info->num_handles++;
fprintf(stream, "[%p] %s%s\n", handle, uv_handle_type_name(handle->type),
uv_is_active(handle) ? " (active)" : "");
void* close_cb = reinterpret_cast<void*>(handle->close_cb);
fprintf(stream, "\tClose callback: %p %s\n",
close_cb, sym_ctx->LookupSymbol(close_cb).Display().c_str());
fprintf(stream, "\tData: %p %s\n",
handle->data, sym_ctx->LookupSymbol(handle->data).Display().c_str());
// We are also interested in the first field of what `handle->data`
// points to, because for C++ code that is usually the virtual table pointer
// and gives us information about the exact kind of object we're looking at.
void* first_field = nullptr;
// `handle->data` might be any value, including `nullptr`, or something
// cast from a completely different type; therefore, check that its
// dereferenceable first.
if (sym_ctx->IsMapped(handle->data))
first_field = *reinterpret_cast<void**>(handle->data);
if (first_field != nullptr) {
fprintf(stream, "\t(First field): %p %s\n",
first_field, sym_ctx->LookupSymbol(first_field).Display().c_str());
}
}, &info);
fprintf(stream, "uv loop at [%p] has %zu open handles in total\n",
loop, info.num_handles);
}
std::vector<std::string> NativeSymbolDebuggingContext::GetLoadedLibraries() {
std::vector<std::string> list;
#if defined(__linux__) || defined(__FreeBSD__) || \
defined(__OpenBSD__) || defined(__DragonFly__)
dl_iterate_phdr(
[](struct dl_phdr_info* info, size_t size, void* data) {
auto list = static_cast<std::vector<std::string>*>(data);
if (*info->dlpi_name != '\0') {
list->emplace_back(info->dlpi_name);
}
return 0;
},
&list);
#elif __APPLE__
uint32_t i = 0;
for (const char* name = _dyld_get_image_name(i); name != nullptr;
name = _dyld_get_image_name(++i)) {
list.emplace_back(name);
}
#elif _AIX
// We can't tell in advance how large the buffer needs to be.
// Retry until we reach too large a size (1Mb).
const unsigned int kBufferGrowStep = 4096;
MallocedBuffer<char> buffer(kBufferGrowStep);
int rc = -1;
do {
rc = loadquery(L_GETINFO, buffer.data, buffer.size);
if (rc == 0) break;
buffer = MallocedBuffer<char>(buffer.size + kBufferGrowStep);
} while (buffer.size < 1024 * 1024);
if (rc == 0) {
char* buf = buffer.data;
ld_info* cur_info = nullptr;
do {
std::ostringstream str;
cur_info = reinterpret_cast<ld_info*>(buf);
char* member_name = cur_info->ldinfo_filename +
strlen(cur_info->ldinfo_filename) + 1;
if (*member_name != '\0') {
str << cur_info->ldinfo_filename << "(" << member_name << ")";
list.emplace_back(str.str());
str.str("");
} else {
list.emplace_back(cur_info->ldinfo_filename);
}
buf += cur_info->ldinfo_next;
} while (cur_info->ldinfo_next != 0);
}
#elif __sun
Link_map* p;
if (dlinfo(RTLD_SELF, RTLD_DI_LINKMAP, &p) != -1) {
for (Link_map* l = p; l != nullptr; l = l->l_next) {
list.emplace_back(l->l_name);
}
}
#elif _WIN32
// Windows implementation - get a handle to the process.
HANDLE process_handle = OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ,
FALSE, GetCurrentProcessId());
if (process_handle == nullptr) {
// Cannot proceed, return an empty list.
return list;
}
// Get a list of all the modules in this process
DWORD size_1 = 0;
DWORD size_2 = 0;
// First call to get the size of module array needed
if (EnumProcessModules(process_handle, nullptr, 0, &size_1)) {
MallocedBuffer<HMODULE> modules(size_1 / sizeof(HMODULE));
// Second call to populate the module array
if (EnumProcessModules(process_handle, modules.data, size_1, &size_2)) {
for (DWORD i = 0;
i < (size_1 / sizeof(HMODULE)) && i < (size_2 / sizeof(HMODULE));
i++) {
WCHAR module_name[MAX_PATH];
// Obtain and report the full pathname for each module
if (GetModuleFileNameW(
modules.data[i], module_name, arraysize(module_name))) {
DWORD size = WideCharToMultiByte(
CP_UTF8, 0, module_name, -1, nullptr, 0, nullptr, nullptr);
char* str = new char[size];
WideCharToMultiByte(
CP_UTF8, 0, module_name, -1, str, size, nullptr, nullptr);
list.emplace_back(str);
delete str;
}
}
}
}
// Release the handle to the process.
CloseHandle(process_handle);
#endif
return list;
}
void FWrite(FILE* file, const std::string& str) {
auto simple_fwrite = [&]() {
// The return value is ignored because there's no good way to handle it.
fwrite(str.data(), str.size(), 1, file);
};
if (file != stderr && file != stdout) {
simple_fwrite();
return;
}
#ifdef _WIN32
HANDLE handle =
GetStdHandle(file == stdout ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE);
// Check if stderr is something other than a tty/console
if (handle == INVALID_HANDLE_VALUE || handle == nullptr ||
uv_guess_handle(_fileno(file)) != UV_TTY) {
simple_fwrite();
return;
}
// Get required wide buffer size
int n = MultiByteToWideChar(CP_UTF8, 0, str.data(), str.size(), nullptr, 0);
std::vector<wchar_t> wbuf(n);
MultiByteToWideChar(CP_UTF8, 0, str.data(), str.size(), wbuf.data(), n);
WriteConsoleW(handle, wbuf.data(), n, nullptr, nullptr);
return;
#elif defined(__ANDROID__)
if (file == stderr) {
__android_log_print(ANDROID_LOG_ERROR, "nodejs", "%s", str.data());
return;
}
#endif
simple_fwrite();
}
} // namespace node
extern "C" void __DumpBacktrace(FILE* fp) {
node::DumpNativeBacktrace(fp);
node::DumpJavaScriptBacktrace(fp);
}

193
src/debug_utils.h Normal file
View File

@ -0,0 +1,193 @@
#ifndef SRC_DEBUG_UTILS_H_
#define SRC_DEBUG_UTILS_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "async_wrap.h"
#include "util.h"
#include <algorithm>
#include <sstream>
#include <string>
// Use FORCE_INLINE on functions that have a debug-category-enabled check first
// and then ideally only a single function call following it, to maintain
// performance for the common case (no debugging used).
#ifdef __GNUC__
#define FORCE_INLINE __attribute__((always_inline))
#define COLD_NOINLINE __attribute__((cold, noinline))
#else
#define FORCE_INLINE
#define COLD_NOINLINE
#endif
namespace node {
class Environment;
template <typename T>
inline std::string ToString(const T& value);
// C++-style variant of sprintf()/fprintf() that:
// - Returns an std::string
// - Handles \0 bytes correctly
// - Supports %p and %s. %d, %i and %u are aliases for %s.
// - Accepts any class that has a ToString() method for stringification.
template <typename... Args>
inline std::string SPrintF(const char* format, Args&&... args);
template <typename... Args>
inline void FPrintF(FILE* file, const char* format, Args&&... args);
void NODE_EXTERN_PRIVATE FWrite(FILE* file, const std::string& str);
// Listing the AsyncWrap provider types first enables us to cast directly
// from a provider type to a debug category.
#define DEBUG_CATEGORY_NAMES(V) \
NODE_ASYNC_PROVIDER_TYPES(V) \
V(COMPILE_CACHE) \
V(DIAGNOSTICS) \
V(HUGEPAGES) \
V(INSPECTOR_SERVER) \
V(INSPECTOR_CLIENT) \
V(INSPECTOR_PROFILER) \
V(CODE_CACHE) \
V(NGTCP2_DEBUG) \
V(SEA) \
V(WASI) \
V(MODULE) \
V(MKSNAPSHOT) \
V(SNAPSHOT_SERDES) \
V(PERMISSION_MODEL) \
V(PLATFORM_MINIMAL) \
V(PLATFORM_VERBOSE) \
V(QUIC)
enum class DebugCategory : unsigned int {
#define V(name) name,
DEBUG_CATEGORY_NAMES(V)
#undef V
};
#define V(name) +1
constexpr unsigned int kDebugCategoryCount = DEBUG_CATEGORY_NAMES(V);
#undef V
class NODE_EXTERN_PRIVATE EnabledDebugList {
public:
bool FORCE_INLINE enabled(DebugCategory category) const {
DCHECK_LT(static_cast<unsigned int>(category), kDebugCategoryCount);
return enabled_[static_cast<unsigned int>(category)];
}
// Uses NODE_DEBUG_NATIVE to initialize the categories. env->env_vars()
// is parsed if it is not a nullptr, otherwise the system environment
// variables are parsed.
void Parse(Environment* env);
private:
// Enable all categories matching cats.
void Parse(const std::string& cats);
void set_enabled(DebugCategory category) {
DCHECK_LT(static_cast<unsigned int>(category), kDebugCategoryCount);
enabled_[static_cast<int>(category)] = true;
}
bool enabled_[kDebugCategoryCount] = {false};
};
template <typename... Args>
inline void FORCE_INLINE Debug(EnabledDebugList* list,
DebugCategory cat,
const char* format,
Args&&... args);
inline void FORCE_INLINE Debug(EnabledDebugList* list,
DebugCategory cat,
const char* message);
template <typename... Args>
inline void FORCE_INLINE
Debug(Environment* env, DebugCategory cat, const char* format, Args&&... args);
inline void FORCE_INLINE Debug(Environment* env,
DebugCategory cat,
const char* message);
template <typename... Args>
inline void Debug(Environment* env,
DebugCategory cat,
const std::string& format,
Args&&... args);
// Used internally by the 'real' Debug(AsyncWrap*, ...) functions below, so that
// the FORCE_INLINE flag on them doesn't apply to the contents of this function
// as well.
// We apply COLD_NOINLINE to tell the compiler that it's not worth optimizing
// this function for speed and it should rather focus on keeping it out of
// hot code paths. In particular, we want to keep the string concatenating code
// out of the function containing the original `Debug()` call.
template <typename... Args>
void COLD_NOINLINE UnconditionalAsyncWrapDebug(AsyncWrap* async_wrap,
const char* format,
Args&&... args);
template <typename... Args>
inline void FORCE_INLINE Debug(AsyncWrap* async_wrap,
const char* format,
Args&&... args);
template <typename... Args>
inline void FORCE_INLINE Debug(AsyncWrap* async_wrap,
const std::string& format,
Args&&... args);
// Debug helper for inspecting the currently running `node` executable.
class NativeSymbolDebuggingContext {
public:
static std::unique_ptr<NativeSymbolDebuggingContext> New();
class SymbolInfo {
public:
std::string name;
std::string filename;
size_t line = 0;
size_t dis = 0;
std::string Display() const;
};
NativeSymbolDebuggingContext() = default;
virtual ~NativeSymbolDebuggingContext() = default;
virtual SymbolInfo LookupSymbol(void* address) { return {}; }
virtual bool IsMapped(void* address) { return false; }
virtual int GetStackTrace(void** frames, int count) { return 0; }
NativeSymbolDebuggingContext(const NativeSymbolDebuggingContext&) = delete;
NativeSymbolDebuggingContext(NativeSymbolDebuggingContext&&) = delete;
NativeSymbolDebuggingContext operator=(NativeSymbolDebuggingContext&)
= delete;
NativeSymbolDebuggingContext operator=(NativeSymbolDebuggingContext&&)
= delete;
static std::vector<std::string> GetLoadedLibraries();
};
// Variant of `uv_loop_close` that tries to be as helpful as possible
// about giving information on currently existing handles, if there are any,
// but still aborts the process.
void CheckedUvLoopClose(uv_loop_t* loop);
void PrintLibuvHandleInformation(uv_loop_t* loop, FILE* stream);
namespace per_process {
extern NODE_EXTERN_PRIVATE EnabledDebugList enabled_debug_list;
template <typename... Args>
inline void FORCE_INLINE Debug(DebugCategory cat,
const char* format,
Args&&... args);
inline void FORCE_INLINE Debug(DebugCategory cat, const char* message);
} // namespace per_process
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_DEBUG_UTILS_H_

View File

@ -0,0 +1,33 @@
#ifndef SRC_DIAGNOSTICFILENAME_INL_H_
#define SRC_DIAGNOSTICFILENAME_INL_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "node_internals.h"
namespace node {
class Environment;
inline DiagnosticFilename::DiagnosticFilename(
Environment* env,
const char* prefix,
const char* ext) :
filename_(MakeFilename(env->thread_id(), prefix, ext)) {
}
inline DiagnosticFilename::DiagnosticFilename(
uint64_t thread_id,
const char* prefix,
const char* ext) :
filename_(MakeFilename(thread_id, prefix, ext)) {
}
inline const char* DiagnosticFilename::operator*() const {
return filename_.c_str();
}
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_DIAGNOSTICFILENAME_INL_H_

Some files were not shown because too many files have changed in this diff Show More