Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 88 additions & 95 deletions src/crypto/crypto_kem.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,20 @@
#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::EVPKeyPointer;
using v8::Array;
using v8::ArrayBufferView;
using v8::FunctionCallbackInfo;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Nothing;
using v8::Object;
using v8::Uint8Array;
using v8::Value;

namespace crypto {
Expand All @@ -49,49 +48,6 @@ void KEMConfiguration::MemoryInfo(MemoryTracker* tracker) const {

namespace {

bool DoKEMEncapsulate(Environment* env,
const EVPKeyPointer& public_key,
ByteSource* out,
CryptoJobMode mode,
CryptoErrorStore* errors) {
auto result = ncrypto::KEM::Encapsulate(public_key);
if (!result) {
errors->Insert(NodeCryptoError::ENCAPSULATION_FAILED);
errors->SetNodeErrorCode("ERR_CRYPTO_OPERATION_FAILED");
return false;
}

// Pack the result: [ciphertext_len][shared_key_len][ciphertext][shared_key]
size_t ciphertext_len = result->ciphertext.size();
size_t shared_key_len = result->shared_key.size();
size_t total_len =
sizeof(uint32_t) + sizeof(uint32_t) + ciphertext_len + shared_key_len;

auto data = ncrypto::DataPointer::Alloc(total_len);
if (!data) {
errors->Insert(NodeCryptoError::ALLOCATION_FAILED);
errors->SetNodeErrorCode("ERR_CRYPTO_OPERATION_FAILED");
return false;
}

unsigned char* ptr = static_cast<unsigned char*>(data.get());

// Write size headers
*reinterpret_cast<uint32_t*>(ptr) = static_cast<uint32_t>(ciphertext_len);
*reinterpret_cast<uint32_t*>(ptr + sizeof(uint32_t)) =
static_cast<uint32_t>(shared_key_len);

// Write ciphertext and shared key data
unsigned char* ciphertext_ptr = ptr + 2 * sizeof(uint32_t);
unsigned char* shared_key_ptr = ciphertext_ptr + ciphertext_len;

std::memcpy(ciphertext_ptr, result->ciphertext.get(), ciphertext_len);
std::memcpy(shared_key_ptr, result->shared_key.get(), shared_key_len);

*out = ByteSource::Allocated(data.release());
return true;
}

bool DoKEMDecapsulate(Environment* env,
const EVPKeyPointer& private_key,
const ByteSource& ciphertext,
Expand Down Expand Up @@ -133,72 +89,109 @@ Maybe<void> KEMEncapsulateTraits::AdditionalConfig(
return v8::JustVoid();
}

bool KEMEncapsulateTraits::DeriveBits(Environment* env,
const KEMConfiguration& params,
ByteSource* out,
CryptoJobMode mode,
CryptoErrorStore* errors) {
Mutex::ScopedLock lock(params.key.mutex());
const auto& public_key = params.key.GetAsymmetricKey();
void KEMEncapsulateJob::New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args.IsConstructCall());

CryptoJobMode mode = GetCryptoJobMode(args[0]);
AdditionalParams params;
if (KEMEncapsulateTraits::AdditionalConfig(mode, args, 1, &params)
.IsNothing()) {
return;
}

return DoKEMEncapsulate(env, public_key, out, mode, errors);
new KEMEncapsulateJob(env, args.This(), mode, std::move(params));
}

MaybeLocal<Value> KEMEncapsulateTraits::EncodeOutput(
Environment* env, const KEMConfiguration& params, ByteSource* out) {
// The output contains:
// [ciphertext_len][shared_key_len][ciphertext][shared_key]
const unsigned char* data = out->data<unsigned char>();

uint32_t ciphertext_len = *reinterpret_cast<const uint32_t*>(data);
uint32_t shared_key_len =
*reinterpret_cast<const uint32_t*>(data + sizeof(uint32_t));

const unsigned char* ciphertext_ptr = data + 2 * sizeof(uint32_t);
const unsigned char* shared_key_ptr = ciphertext_ptr + ciphertext_len;

MaybeLocal<Object> ciphertext_buf =
node::Buffer::Copy(env->isolate(),
reinterpret_cast<const char*>(ciphertext_ptr),
ciphertext_len);

MaybeLocal<Object> shared_key_buf =
node::Buffer::Copy(env->isolate(),
reinterpret_cast<const char*>(shared_key_ptr),
shared_key_len);

Local<Object> ciphertext_obj;
Local<Object> shared_key_obj;
if (!ciphertext_buf.ToLocal(&ciphertext_obj) ||
!shared_key_buf.ToLocal(&shared_key_obj)) {
return MaybeLocal<Value>();
void KEMEncapsulateJob::Initialize(Environment* env, Local<Object> target) {
CryptoJob<KEMEncapsulateTraits>::Initialize(New, env, target);
}

void KEMEncapsulateJob::RegisterExternalReferences(
ExternalReferenceRegistry* registry) {
CryptoJob<KEMEncapsulateTraits>::RegisterExternalReferences(New, registry);
}

KEMEncapsulateJob::KEMEncapsulateJob(Environment* env,
Local<Object> object,
CryptoJobMode mode,
AdditionalParams&& params)
: CryptoJob<KEMEncapsulateTraits>(env,
object,
KEMEncapsulateTraits::Provider,
mode,
std::move(params)) {}

void KEMEncapsulateJob::DoThreadPoolWork() {
ncrypto::ClearErrorOnReturn clear_error_on_return;
AdditionalParams* params = CryptoJob<KEMEncapsulateTraits>::params();
Mutex::ScopedLock lock(params->key.mutex());
out_ = ncrypto::KEM::Encapsulate(params->key.GetAsymmetricKey());
if (!out_) {
CryptoErrorStore* errors = CryptoJob<KEMEncapsulateTraits>::errors();
errors->Insert(NodeCryptoError::ENCAPSULATION_FAILED);
errors->SetNodeErrorCode("ERR_CRYPTO_OPERATION_FAILED");
}
}

if (params.job_mode == kCryptoJobWebCrypto) {
Local<Object> result = Object::New(env->isolate());
if (!result
Maybe<void> KEMEncapsulateJob::ToResult(Local<Value>* err,
Local<Value>* result) {
Environment* env = AsyncWrap::env();
CryptoErrorStore* errors = CryptoJob<KEMEncapsulateTraits>::errors();
if (!out_) {
if (errors->Empty()) errors->Capture();
CHECK(!errors->Empty());
*result = v8::Undefined(env->isolate());
if (!errors->ToException(env).ToLocal(err)) return Nothing<void>();
return v8::JustVoid();
}

CHECK(errors->Empty());
*err = v8::Undefined(env->isolate());

ByteSource ciphertext = ByteSource::Allocated(out_->ciphertext.release());
ByteSource shared_key = ByteSource::Allocated(out_->shared_key.release());

if (mode() == kCryptoJobWebCrypto) {
Local<Object> output = Object::New(env->isolate());
if (!output
->DefineOwnProperty(env->context(),
OneByteString(env->isolate(), "sharedKey"),
shared_key_obj.As<ArrayBufferView>()->Buffer())
shared_key.ToArrayBuffer(env))
.FromMaybe(false) ||
!result
!output
->DefineOwnProperty(env->context(),
OneByteString(env->isolate(), "ciphertext"),
ciphertext_obj.As<ArrayBufferView>()->Buffer())
ciphertext.ToArrayBuffer(env))
.FromMaybe(false)) {
return MaybeLocal<Value>();
return Nothing<void>();
}
return result;
*result = output;
return v8::JustVoid();
}

// Return an array [sharedKey, ciphertext].
Local<Array> result = Array::New(env->isolate(), 2);
if (result->Set(env->context(), 0, shared_key_obj).IsNothing() ||
result->Set(env->context(), 1, ciphertext_obj).IsNothing()) {
return MaybeLocal<Value>();
Local<Uint8Array> shared_key_buf;
Local<Uint8Array> ciphertext_buf;
if (!shared_key.ToBuffer(env).ToLocal(&shared_key_buf) ||
!ciphertext.ToBuffer(env).ToLocal(&ciphertext_buf)) {
return Nothing<void>();
}

return result;
Local<Array> output = Array::New(env->isolate(), 2);
if (output->Set(env->context(), 0, shared_key_buf).IsNothing() ||
output->Set(env->context(), 1, ciphertext_buf).IsNothing()) {
return Nothing<void>();
}
*result = output;
return v8::JustVoid();
}

void KEMEncapsulateJob::MemoryInfo(MemoryTracker* tracker) const {
if (out_) {
tracker->TrackFieldWithSize("ciphertext", out_->ciphertext.size());
tracker->TrackFieldWithSize("shared_key", out_->shared_key.size());
}
CryptoJob<KEMEncapsulateTraits>::MemoryInfo(tracker);
}

// KEMDecapsulateTraits implementation
Expand Down
31 changes: 22 additions & 9 deletions src/crypto/crypto_kem.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,30 @@ struct KEMEncapsulateTraits final {
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
KEMConfiguration* params);
};

static bool DeriveBits(Environment* env,
const KEMConfiguration& params,
ByteSource* out,
CryptoJobMode mode,
CryptoErrorStore* errors);
class KEMEncapsulateJob final : public CryptoJob<KEMEncapsulateTraits> {
public:
using AdditionalParams = KEMEncapsulateTraits::AdditionalParameters;

static v8::MaybeLocal<v8::Value> EncodeOutput(Environment* env,
const KEMConfiguration& params,
ByteSource* out);
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Initialize(Environment* env, v8::Local<v8::Object> target);
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);

KEMEncapsulateJob(Environment* env,
v8::Local<v8::Object> object,
CryptoJobMode mode,
AdditionalParams&& params);

void DoThreadPoolWork() override;
v8::Maybe<void> ToResult(v8::Local<v8::Value>* err,
v8::Local<v8::Value>* result) override;

SET_SELF_SIZE(KEMEncapsulateJob)
void MemoryInfo(MemoryTracker* tracker) const override;

private:
std::optional<ncrypto::KEM::EncapsulateResult> out_;
};

struct KEMDecapsulateTraits final {
Expand All @@ -80,7 +94,6 @@ struct KEMDecapsulateTraits final {
ByteSource* out);
};

using KEMEncapsulateJob = DeriveBitsJob<KEMEncapsulateTraits>;
using KEMDecapsulateJob = DeriveBitsJob<KEMDecapsulateTraits>;

void InitializeKEM(Environment* env, v8::Local<v8::Object> target);
Expand Down
Loading