#include "crypto/crypto_keys.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "crypto/crypto_common.h"
#include "crypto/crypto_dh.h"
#include "crypto/crypto_dsa.h"
#include "crypto/crypto_ec.h"
#include "crypto/crypto_rsa.h"
#include "crypto/crypto_util.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "node.h"
#include "node_buffer.h"
#include "permission/permission.h"
#include "string_bytes.h"
#include "threadpoolwork-inl.h"
#include "util-inl.h"
#include "v8.h"
namespace node {
using ncrypto::BignumPointer;
using ncrypto::BIOPointer;
using ncrypto::ClearErrorOnReturn;
using ncrypto::ECKeyPointer;
using ncrypto::ECPointPointer;
using ncrypto::EVPKeyCtxPointer;
using ncrypto::EVPKeyPointer;
using ncrypto::KeyAlgorithm;
using ncrypto::MarkPopErrorOnReturn;
using v8::Array;
using v8::Boolean;
using v8::Context;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Int32;
using v8::Isolate;
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::NewStringType;
using v8::Nothing;
using v8::Number;
using v8::Object;
using v8::String;
using v8::Uint32;
using v8::Undefined;
using v8::Value;
namespace crypto {
namespace {
void IsKeyAlgorithmAvailable(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsString());
const Utf8Value name(args.GetIsolate(), args[0]);
const auto* algorithm = KeyAlgorithm::FromName(*name);
args.GetReturnValue().Set(algorithm != nullptr && algorithm->isAvailable());
}
Maybe<EVPKeyPointer::AsymmetricKeyEncodingConfig> GetKeyFormatAndTypeFromJs(
const FunctionCallbackInfo<Value>& args,
unsigned int* offset,
KeyEncodingContext context) {
EVPKeyPointer::AsymmetricKeyEncodingConfig config;
// During key pair generation, it is possible not to specify a key encoding,
// which will lead to a key object being returned.
if (args[*offset]->IsUndefined()) {
CHECK_EQ(context, kKeyContextGenerate);
CHECK(args[*offset + 1]->IsUndefined());
config.output_key_object = true;
} else {
config.output_key_object = false;
CHECK(args[*offset]->IsInt32());
config.format = static_cast<EVPKeyPointer::PKFormatType>(
args[*offset].As<Int32>()->Value());
if (config.format == EVPKeyPointer::PKFormatType::RAW_PUBLIC ||
config.format == EVPKeyPointer::PKFormatType::RAW_PRIVATE ||
config.format == EVPKeyPointer::PKFormatType::RAW_SEED) {
// Raw formats use the type slot for ec_point_form (int) or null.
if (args[*offset + 1]->IsInt32()) {
config.ec_point_form = args[*offset + 1].As<Int32>()->Value();
} else {
CHECK(args[*offset + 1]->IsNullOrUndefined());
}
} else if (args[*offset + 1]->IsInt32()) {
config.type = static_cast<EVPKeyPointer::PKEncodingType>(
args[*offset + 1].As<Int32>()->Value());
} else {
CHECK((context == kKeyContextInput &&
config.format == EVPKeyPointer::PKFormatType::PEM) ||
(context == kKeyContextGenerate &&
config.format == EVPKeyPointer::PKFormatType::JWK));
CHECK(args[*offset + 1]->IsNullOrUndefined());
config.type = EVPKeyPointer::PKEncodingType::PKCS1;
}
}
*offset += 2;
return Just(config);
}
MaybeLocal<Value> ToV8Value(
Environment* env,
const BIOPointer& bio,
const EVPKeyPointer::AsymmetricKeyEncodingConfig& config) {
if (!bio) {
THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Invalid BIO pointer");
return {};
}
BUF_MEM* bptr = bio;
if (!bptr) {
THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Unable to create BUF_MEM pointer");
return {};
}
if (config.format == EVPKeyPointer::PKFormatType::PEM) {
// PEM is an ASCII format, so we will return it as a string.
return String::NewFromUtf8(
env->isolate(), bptr->data, NewStringType::kNormal, bptr->length)
.FromMaybe(Local<Value>());
}
CHECK_EQ(config.format, EVPKeyPointer::PKFormatType::DER);
// DER is binary, return it as a buffer.
return Buffer::Copy(env, bptr->data, bptr->length).FromMaybe(Local<Value>());
}
MaybeLocal<Value> WritePrivateKey(
Environment* env,
const EVPKeyPointer& pkey,
const EVPKeyPointer::PrivateKeyEncodingConfig& config) {
if (!pkey) return {};
auto res = pkey.writePrivateKey(config);
if (res) return ToV8Value(env, std::move(res.value), config);
ThrowCryptoError(
env, res.openssl_error.value_or(0), "Failed to encode private key");
return MaybeLocal<Value>();
}
MaybeLocal<Value> WritePublicKey(
Environment* env,
const EVPKeyPointer& pkey,
const EVPKeyPointer::PublicKeyEncodingConfig& config) {
if (!pkey) return {};
auto res = pkey.writePublicKey(config);
if (res) return ToV8Value(env, res.value, config);
ThrowCryptoError(
env, res.openssl_error.value_or(0), "Failed to encode public key");
return MaybeLocal<Value>();
}
bool ExportJWKSecretKey(Environment* env,
const KeyObjectData& key,
Local<Object> target) {
CHECK_EQ(key.GetKeyType(), kKeyTypeSecret);
Local<Value> raw;
return StringBytes::Encode(env->isolate(),
key.GetSymmetricKey(),
key.GetSymmetricKeySize(),
BASE64URL)
.ToLocal(&raw) &&
target
->DefineOwnProperty(
env->context(), env->jwk_kty_string(), env->jwk_oct_string())
.FromMaybe(false) &&
target->DefineOwnProperty(env->context(), env->jwk_k_string(), raw)
.FromMaybe(false);
}
KeyObjectData ImportJWKSecretKey(Environment* env, Local<Object> jwk) {
Local<Value> key;
if (!jwk->Get(env->context(), env->jwk_k_string()).ToLocal(&key) ||
!key->IsString()) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK secret key format");
return {};
}
static_assert(String::kMaxLength <= INT_MAX);
return KeyObjectData::CreateSecret(
ByteSource::FromEncodedString(env, key.As<String>()));
}
static bool ExportJWKRawKey(Environment* env,
const KeyObjectData& key,
Local<Object> target) {
auto result =
key.GetAsymmetricKey().exportRawJwk(key.GetKeyType() == kKeyTypePrivate);
if (!result) {
if (result.error == EVPKeyPointer::RawExportError::MISSING_SEED) {
THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"key does not have an available seed");
}
return false;
}
const auto& data = result.value;
const bool is_akp = data.algorithm->isPqc();
const auto name_field =
is_akp ? env->jwk_alg_string() : env->jwk_crv_string();
const auto public_field =
is_akp ? env->jwk_pub_string() : env->jwk_x_string();
const auto private_field =
is_akp ? env->jwk_priv_string() : env->jwk_d_string();
const auto kty = is_akp ? env->jwk_akp_string() : env->jwk_okp_string();
const auto context = env->context();
const auto set_key = [&](const ncrypto::DataPointer& bytes,
Local<String> field) {
Local<Value> encoded;
return StringBytes::Encode(
env->isolate(), bytes.get<const char>(), bytes.size(), BASE64URL)
.ToLocal(&encoded) &&
target->DefineOwnProperty(context, field, encoded).FromMaybe(false);
};
return target->DefineOwnProperty(context, env->jwk_kty_string(), kty)
.FromMaybe(false) &&
target
->DefineOwnProperty(
context,
name_field,
OneByteString(env->isolate(), data.algorithm->name()))
.FromMaybe(false) &&
set_key(data.public_key, public_field) &&
(!data.private_key || set_key(data.private_key, private_field));
}
static KeyObjectData ImportJWKRawKey(Environment* env,
Local<Object> jwk,
bool is_akp) {
const auto name_field =
is_akp ? env->jwk_alg_string() : env->jwk_crv_string();
const auto public_field =
is_akp ? env->jwk_pub_string() : env->jwk_x_string();
const auto private_field =
is_akp ? env->jwk_priv_string() : env->jwk_d_string();
const char* invalid_key =
is_akp ? "Invalid JWK AKP key" : "Invalid JWK OKP key";
Local<Value> name_value;
Local<Value> public_value;
Local<Value> private_value;
if (!jwk->Get(env->context(), name_field).ToLocal(&name_value) ||
!jwk->Get(env->context(), public_field).ToLocal(&public_value) ||
!jwk->Get(env->context(), private_field).ToLocal(&private_value)) {
return {};
}
const bool valid_material =
public_value->IsString() &&
(private_value->IsUndefined() || private_value->IsString());
// OKP validates the field types first; AKP validates its algorithm first.
if (!is_akp && (!name_value->IsString() || !valid_material)) {
THROW_ERR_CRYPTO_INVALID_JWK(env, invalid_key);
return {};
}
Utf8Value name(env->isolate(),
name_value->IsString() ? name_value.As<String>()
: String::Empty(env->isolate()));
const auto* algorithm = KeyAlgorithm::FromName(*name);
if (algorithm == nullptr ||
(is_akp ? !algorithm->isPqc() || !algorithm->isAvailable()
: !algorithm->isOkp()) ||
strcmp(*name, algorithm->name()) != 0) {
THROW_ERR_CRYPTO_INVALID_JWK(
env, is_akp ? "Unsupported JWK AKP \"alg\"" : invalid_key);
return {};
}
if (!valid_material) {
THROW_ERR_CRYPTO_INVALID_JWK(env, invalid_key);
return {};
}
ByteSource private_bytes;
std::optional<ncrypto::Buffer<const unsigned char>> private_key;
const KeyType type =
private_value->IsString() ? kKeyTypePrivate : kKeyTypePublic;
if (type == kKeyTypePrivate) {
private_bytes =
ByteSource::FromEncodedString(env, private_value.As<String>());
private_key = ncrypto::Buffer<const unsigned char>{
private_bytes.data<const unsigned char>(), private_bytes.size()};
}
const auto public_bytes =
ByteSource::FromEncodedString(env, public_value.As<String>());
auto pkey = EVPKeyPointer::NewRawJwk(
*algorithm,
{public_bytes.data<const unsigned char>(), public_bytes.size()},
private_key);
if (!pkey) {
THROW_ERR_CRYPTO_INVALID_JWK(env, invalid_key);
return {};
}
return KeyObjectData::CreateAsymmetric(type, std::move(pkey));
}
bool ExportJWKAsymmetricKey(Environment* env,
const KeyObjectData& key,
Local<Object> target,
bool handleRsaPss) {
const auto& pkey = key.GetAsymmetricKey();
const auto* algorithm = pkey.getAlgorithm();
if (algorithm == &KeyAlgorithm::RSA ||
(handleRsaPss && algorithm == &KeyAlgorithm::RSA_PSS)) {
return ExportJWKRsaKey(env, key, target);
}
if (algorithm == &KeyAlgorithm::EC) return ExportJWKEcKey(env, key, target);
if (algorithm != nullptr && (algorithm->isOkp() || algorithm->isPqc())) {
return ExportJWKRawKey(env, key, target);
}
THROW_ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE(env);
return false;
}
bool GetSecretKeyDetail(Environment* env,
const KeyObjectData& key,
Local<Object> target) {
// For the secret key detail, all we care about is the length,
// converted to bits.
return target
->Set(env->context(),
env->length_string(),
Number::New(
env->isolate(),
static_cast<double>(key.GetSymmetricKeySize() * CHAR_BIT)))
.IsJust();
}
bool GetAsymmetricKeyDetail(Environment* env,
const KeyObjectData& key,
Local<Object> target) {
if (!key) {
THROW_ERR_CRYPTO_OPERATION_FAILED(env);
return false;
}
const auto& pkey = key.GetAsymmetricKey();
const auto* algorithm = pkey.getAlgorithm();
if (algorithm != nullptr && algorithm->isRsa()) {
return GetRsaKeyDetail(env, key, target);
}
if (algorithm == &KeyAlgorithm::DSA) return GetDsaKeyDetail(env, key, target);
if (algorithm == &KeyAlgorithm::EC) return GetEcKeyDetail(env, key, target);
if (algorithm == &KeyAlgorithm::DH) return GetDhKeyDetail(env, key, target);
THROW_ERR_CRYPTO_INVALID_KEYTYPE(env);
return false;
}
KeyObjectData TryParsePrivateKey(
Environment* env,
const EVPKeyPointer::PrivateKeyEncodingConfig& config,
const ncrypto::Buffer<const unsigned char>& buffer) {
auto res = EVPKeyPointer::TryParsePrivateKey(config, buffer);
if (res) {
return KeyObjectData::CreateAsymmetric(KeyType::kKeyTypePrivate,
std::move(res.value));
}
if (res.error.value() == EVPKeyPointer::PKParseError::NEED_PASSPHRASE) {
THROW_ERR_MISSING_PASSPHRASE(env, "Passphrase required for encrypted key");
} else {
ThrowCryptoError(
env, res.openssl_error.value_or(0), "Failed to read private key");
}
return {};
}
bool ExportJWKInner(Environment* env,
const KeyObjectData& key,
Local<Value> result,
bool handleRsaPss) {
return key.GetKeyType() == kKeyTypeSecret
? ExportJWKSecretKey(env, key, result.As<Object>())
: ExportJWKAsymmetricKey(
env, key, result.As<Object>(), handleRsaPss);
}
void ValidateRawKeyImportFormat(Environment* env,
const char* key_type_name,
const KeyAlgorithm* algorithm,
EVPKeyPointer::PKFormatType format) {
if (algorithm == nullptr) {
THROW_ERR_INVALID_ARG_VALUE(
env, "Invalid asymmetricKeyType: %s", key_type_name);
return;
}
if (algorithm->isPqc() && !algorithm->isAvailable()) {
THROW_ERR_INVALID_ARG_VALUE(env, "Unsupported key type");
return;
}
const auto private_format = algorithm->seedSize() != 0
? EVPKeyPointer::PKFormatType::RAW_SEED
: EVPKeyPointer::PKFormatType::RAW_PRIVATE;
const bool supports_raw =
algorithm == &KeyAlgorithm::EC || algorithm->supportsRawPublic();
if (supports_raw && (format == EVPKeyPointer::PKFormatType::RAW_PUBLIC ||
format == private_format)) {
return;
}
THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
}
} // namespace
bool KeyObjectData::ToEncodedPublicKey(
Environment* env,
const EVPKeyPointer::PublicKeyEncodingConfig& config,
Local<Value>* out) {
CHECK(key_type_ != KeyType::kKeyTypeSecret);
if (config.output_key_object) {
// Note that this has the downside of containing sensitive data of the
// private key.
return KeyObjectHandle::Create(env, addRefWithType(KeyType::kKeyTypePublic))
.ToLocal(out);
} else if (config.format == EVPKeyPointer::PKFormatType::JWK) {
*out = Object::New(env->isolate());
return ExportJWKInner(
env, addRefWithType(KeyType::kKeyTypePublic), *out, false);
} else if (config.format == EVPKeyPointer::PKFormatType::RAW_PUBLIC) {
const auto& pkey = GetAsymmetricKey();
const auto* algorithm = pkey.getAlgorithm();
if (algorithm == &KeyAlgorithm::EC) {
auto form = static_cast<point_conversion_form_t>(config.ec_point_form);
auto bytes = ncrypto::Ec::TryExportPublic(pkey, form);
if (bytes)
return Buffer::Copy(env, bytes.get<const char>(), bytes.size())
.ToLocal(out);
ECKeyPointer ec_key(pkey);
if (!ec_key) {
THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
return false;
}
if (ec_key.getPublicKey() == nullptr) {
THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to export EC public key");
return false;
}
return ECPointToBuffer(
env, ec_key.getGroup(), ec_key.getPublicKey(), form)
.ToLocal(out);
}
const bool is_raw_supported =
algorithm != nullptr && algorithm->supportsRawPublic();
if (!is_raw_supported) {
THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
return false;
}
auto raw_data = pkey.rawPublicKey();
if (!raw_data) {
THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get raw public key");
return false;
}
return Buffer::Copy(env, raw_data.get<const char>(), raw_data.size())
.ToLocal(out);
}
return WritePublicKey(env, GetAsymmetricKey(), config).ToLocal(out);
}
bool KeyObjectData::ToEncodedPrivateKey(
Environment* env,
const EVPKeyPointer::PrivateKeyEncodingConfig& config,
Local<Value>* out) {
CHECK(key_type_ != KeyType::kKeyTypeSecret);
if (config.output_key_object) {
return KeyObjectHandle::Create(env,
addRefWithType(KeyType::kKeyTypePrivate))
.ToLocal(out);
} else if (config.format == EVPKeyPointer::PKFormatType::JWK) {
*out = Object::New(env->isolate());
return ExportJWKInner(
env, addRefWithType(KeyType::kKeyTypePrivate), *out, false);
} else if (config.format == EVPKeyPointer::PKFormatType::RAW_PRIVATE) {
const auto& pkey = GetAsymmetricKey();
const auto* algorithm = pkey.getAlgorithm();
if (algorithm == &KeyAlgorithm::EC) {
auto buf = ncrypto::Ec::ExportPrivate(pkey);
if (!buf) {
THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to export EC private key");
return false;
}
return Buffer::Copy(env, buf.get<const char>(), buf.size()).ToLocal(out);
}
const bool is_raw_supported =
algorithm != nullptr && algorithm->supportsRawPrivate();
if (!is_raw_supported) {
THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
return false;
}
auto raw_data = pkey.rawPrivateKey();
if (!raw_data) {
THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get raw private key");
return false;
}
return Buffer::Copy(env, raw_data.get<const char>(), raw_data.size())
.ToLocal(out);
} else if (config.format == EVPKeyPointer::PKFormatType::RAW_SEED) {
const auto& pkey = GetAsymmetricKey();
auto raw_data = pkey.rawSeed();
if (!raw_data) {
if (raw_data.error ==
EVPKeyPointer::RawExportError::UNSUPPORTED_KEY_TYPE) {
THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
} else {
THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get raw seed");
}
return false;
}
return Buffer::Copy(
env, raw_data.value.get<const char>(), raw_data.value.size())
.ToLocal(out);
}
return WritePrivateKey(env, GetAsymmetricKey(), config).ToLocal(out);
}
Maybe<EVPKeyPointer::PrivateKeyEncodingConfig>
KeyObjectData::GetPrivateKeyEncodingFromJs(
const FunctionCallbackInfo<Value>& args,
unsigned int* offset,
KeyEncodingContext context) {
Environment* env = Environment::GetCurrent(args);
EVPKeyPointer::PrivateKeyEncodingConfig config;
if (!GetKeyFormatAndTypeFromJs(args, offset, context).To(&config)) {
return Nothing<EVPKeyPointer::PrivateKeyEncodingConfig>();
}
if (config.output_key_object) {
if (context != kKeyContextInput)
(*offset)++;
} else if (config.format == EVPKeyPointer::PKFormatType::RAW_PRIVATE ||
config.format == EVPKeyPointer::PKFormatType::RAW_SEED) {
// Raw formats don't support encryption. Still consume the arg offsets.
if (context != kKeyContextInput) {
CHECK(args[*offset]->IsNullOrUndefined());
(*offset)++;
}
CHECK(args[*offset]->IsNullOrUndefined());
} else {
bool needs_passphrase = false;
if (context != kKeyContextInput) {
if (args[*offset]->IsString()) {
Utf8Value cipher_name(env->isolate(), args[*offset]);
config.cipher = ncrypto::Cipher::FromNameForKeyEncoding(*cipher_name);
if (!config.cipher) {
THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env);
return Nothing<EVPKeyPointer::PrivateKeyEncodingConfig>();
}
needs_passphrase = true;
} else {
CHECK(args[*offset]->IsNullOrUndefined());
config.cipher = nullptr;
}
(*offset)++;
}
if (IsAnyBufferSource(args[*offset])) {
CHECK_IMPLIES(context != kKeyContextInput, config.cipher);
ArrayBufferOrViewContents<char> passphrase(args[*offset]);
if (!passphrase.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "passphrase is too big");
return Nothing<EVPKeyPointer::PrivateKeyEncodingConfig>();
}
config.passphrase = passphrase.ToDataPointer();
} else {
CHECK(args[*offset]->IsNullOrUndefined() && !needs_passphrase);
}
}
(*offset)++;
return Just<EVPKeyPointer::PrivateKeyEncodingConfig>(std::move(config));
}
Maybe<EVPKeyPointer::PublicKeyEncodingConfig>
KeyObjectData::GetPublicKeyEncodingFromJs(
const FunctionCallbackInfo<Value>& args,
unsigned int* offset,
KeyEncodingContext context) {
return GetKeyFormatAndTypeFromJs(args, offset, context);
}
// Shared helper for importing raw asymmetric keys. Called from
// ImportRawKeyFromArgs.
static KeyObjectData ImportRawKey(Environment* env,
const unsigned char* key_data,
size_t key_data_len,
EVPKeyPointer::PKFormatType format,
const KeyAlgorithm* algorithm,
const char* key_type_name,
const char* named_curve,
KeyType target_type) {
auto throw_invalid = [&]() {
if (!env->isolate()->HasPendingException()) {
THROW_ERR_INVALID_ARG_VALUE(env, "Invalid key data");
}
};
ValidateRawKeyImportFormat(env, key_type_name, algorithm, format);
if (env->isolate()->HasPendingException()) {
return {};
}
// EC keys
if (algorithm == &KeyAlgorithm::EC) {
int curve_nid = ncrypto::Ec::GetCurveIdFromName(named_curve);
if (curve_nid == NID_undef) {
THROW_ERR_CRYPTO_INVALID_CURVE(env);
return {};
}
auto eckey = ECKeyPointer::NewByCurveName(curve_nid);
if (!eckey) {
throw_invalid();
return {};
}
if (format == EVPKeyPointer::PKFormatType::RAW_PUBLIC) {
const auto group = eckey.getGroup();
auto pub = ECPointPointer::New(group);
if (!pub) {
throw_invalid();
return {};
}
ncrypto::Buffer<const unsigned char> buffer{
.data = key_data,
.len = key_data_len,
};
if (!pub.setFromBuffer(buffer, group) || !eckey.setPublicKey(pub)) {
throw_invalid();
return {};
}
} else {
const auto group = eckey.getGroup();
auto order = BignumPointer::New();
CHECK(order);
CHECK(EC_GROUP_get_order(group, order.get(), nullptr));
if (key_data_len != order.byteLength()) {
throw_invalid();
return {};
}
BignumPointer priv_bn(key_data, key_data_len);
if (!priv_bn || !eckey.setPrivateKey(priv_bn)) {
throw_invalid();
return {};
}
auto pub_point = ECPointPointer::New(group);
if (!pub_point || !pub_point.mul(group, priv_bn.get()) ||
!eckey.setPublicKey(pub_point)) {
throw_invalid();
return {};
}
}
auto pkey = EVPKeyPointer::New();
if (!pkey.assign(eckey)) {
throw_invalid();
return {};
}
#if NCRYPTO_USE_LEGACY_KEY_TYPES
eckey.release();
#endif
return KeyObjectData::CreateAsymmetric(target_type, std::move(pkey));
}
const ncrypto::Buffer<const unsigned char> buffer{
.data = key_data,
.len = key_data_len,
};
EVPKeyPointer pkey;
if (target_type == kKeyTypePublic) {
pkey = EVPKeyPointer::NewRawPublic(*algorithm, buffer);
} else if (algorithm->seedSize() != 0) {
pkey = EVPKeyPointer::NewRawSeed(*algorithm, buffer);
} else {
pkey = EVPKeyPointer::NewRawPrivate(*algorithm, buffer);
}
if (!pkey) {
throw_invalid();
return {};
}
return KeyObjectData::CreateAsymmetric(target_type, std::move(pkey));
}
// Shared helper for importing a JWK asymmetric key. Extracts kty from the
// JWK object and dispatches to the appropriate importer.
static KeyObjectData ImportJWKFromArgs(Environment* env, Local<Object> jwk) {
ClearErrorOnReturn clear_error_on_return;
Local<Value> kty;
if (!jwk->Get(env->context(), env->jwk_kty_string()).ToLocal(&kty) ||
!kty->IsString()) {
THROW_ERR_CRYPTO_INVALID_JWK(env);
return {};
}
Utf8Value kty_string(env->isolate(), kty);
if (*kty_string == std::string_view("RSA")) {
return ImportJWKRsaKey(env, jwk);
} else if (*kty_string == std::string_view("EC")) {
return ImportJWKEcKey(env, jwk);
} else if (*kty_string == std::string_view("OKP")) {
return ImportJWKRawKey(env, jwk, /* is_akp */ false);
} else if (*kty_string == std::string_view("AKP")) {
return ImportJWKRawKey(env, jwk, /* is_akp */ true);
}
THROW_ERR_CRYPTO_INVALID_JWK(
env, "%s is not a supported JWK key type", *kty_string);
return {};
}
// Shared helper for importing raw asymmetric keys from positional args.
// args layout: [... offset+0: buffer, offset+1: formatInt,
// offset+2: asymmetricKeyType, offset+3: passphrase,
// offset+4: namedCurve]
static KeyObjectData ImportRawKeyFromArgs(
const FunctionCallbackInfo<Value>& args, unsigned int offset) {
Environment* env = Environment::GetCurrent(args);
auto format = static_cast<EVPKeyPointer::PKFormatType>(
args[offset + 1].As<Int32>()->Value());
KeyType type = (format == EVPKeyPointer::PKFormatType::RAW_PUBLIC)
? kKeyTypePublic
: kKeyTypePrivate;
ArrayBufferOrViewContents<unsigned char> key_data(args[offset]);
if (!key_data.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "keyData is too big");
return {};
}
CHECK(args[offset + 2]->IsString());
Utf8Value key_type_name(env->isolate(), args[offset + 2]);
const auto* algorithm = KeyAlgorithm::FromName(*key_type_name);
// Raw key types require their exact public spelling.
if (algorithm != nullptr &&
(algorithm->keyTypeName() == nullptr ||
key_type_name.ToStringView() != algorithm->keyTypeName())) {
algorithm = nullptr;
}
DCHECK_IMPLIES(algorithm == &KeyAlgorithm::EC, args[offset + 4]->IsString());
Utf8Value curve(env->isolate(),
args[offset + 4]->IsString() ? args[offset + 4].As<String>()
: String::Empty(env->isolate()));
return ImportRawKey(env,
key_data.data(),
key_data.size(),
format,
algorithm,
*key_type_name,
*curve,
type);
}
KeyObjectData KeyObjectData::GetPrivateKeyFromJs(
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int* offset,
bool allow_key_object) {
Environment* env = Environment::GetCurrent(args);
// Store descriptor: data is a { uri, properties } object, format int is
// STORE, and the passphrase slot carries an optional passphrase/PIN.
if (args[*offset]->IsObject() && !IsAnyBufferSource(args[*offset]) &&
args[*offset + 1]->IsInt32() &&
static_cast<EVPKeyPointer::PKFormatType>(
args[*offset + 1].As<Int32>()->Value()) ==
EVPKeyPointer::PKFormatType::STORE) {
Local<Object> store = args[*offset].As<Object>();
Local<Value> uri_value;
if (!store
->Get(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "uri"))
.ToLocal(&uri_value)) {
return {};
}
CHECK(uri_value->IsString());
Utf8Value uri(env->isolate(), uri_value);
Local<Value> properties_value;
if (!store
->Get(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "properties"))
.ToLocal(&properties_value)) {
return {};
}
std::string properties_storage;
std::optional<std::string_view> properties;
if (properties_value->IsString()) {
Utf8Value properties_string(env->isolate(), properties_value);
std::string_view properties_view = properties_string.ToStringView();
properties_storage.assign(properties_view.data(), properties_view.size());
properties = std::string_view(properties_storage);
} else {
CHECK(properties_value->IsNullOrUndefined());
}
// OpenSSLStore is a global permission. URIs passed to STORE loaders can
// contain credentials, so they must not be exposed through permission
// errors or diagnostics.
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kOpenSSLStore, "", KeyObjectData());
std::optional<ArrayBufferOrViewContents<char>> passphrase_content;
std::optional<ncrypto::Buffer<const char>> passphrase;
if (IsAnyBufferSource(args[*offset + 3])) {
passphrase_content.emplace(args[*offset + 3]);
if (!passphrase_content->CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "passphrase is too big");
return {};
}
passphrase = ncrypto::Buffer<const char>{
.data = passphrase_content->data(),
.len = passphrase_content->size(),
};
} else {
CHECK(args[*offset + 3]->IsNullOrUndefined());
}
*offset += 5;
EVPKeyPointer::StorePrivateKeyConfig config{
.uri = uri.ToStringView(),
.properties = properties,
.passphrase = passphrase,
};
auto res = EVPKeyPointer::TryLoadPrivateKeyFromStore(config);
if (res) {
return CreateAsymmetric(KeyType::kKeyTypePrivate, std::move(res.value));
}
switch (res.error.value()) {
case EVPKeyPointer::PKParseError::NEED_PASSPHRASE:
ERR_clear_error();
THROW_ERR_MISSING_PASSPHRASE(env,
"Passphrase required for encrypted key");
break;
case EVPKeyPointer::PKParseError::NOT_RECOGNIZED:
ERR_clear_error();
THROW_ERR_CRYPTO_OPERATION_FAILED(
env, "No private key found through the OpenSSL STORE loader");
break;
default: {
static constexpr const char* msg =
"Failed to load private key through an OpenSSL STORE loader";
// A loader may report a failure without leaving anything in the error
// queue, in which case ThrowCryptoError() would produce a bare Error
// carrying no code at all.
if (res.openssl_error.value_or(0) == 0) {
THROW_ERR_CRYPTO_OPERATION_FAILED(env, msg);
} else {
ThrowCryptoError(env, res.openssl_error.value(), msg);
}
break;
}
}
return {};
}
// Object formats: data is a JS Object (not buffer), format int determines
// whether this is a JWK or an OpenSSL STORE loader descriptor.
if (args[*offset]->IsObject() && !IsAnyBufferSource(args[*offset]) &&
args[*offset + 1]->IsInt32()) {
auto format = static_cast<EVPKeyPointer::PKFormatType>(
args[*offset + 1].As<Int32>()->Value());
if (format == EVPKeyPointer::PKFormatType::JWK) {
auto data = ImportJWKFromArgs(env, args[*offset].As<Object>());
*offset += 5;
return data;
}
}
if (args[*offset]->IsString() || IsAnyBufferSource(args[*offset])) {
// Raw format: buffer + raw format int.
if (args[*offset + 1]->IsInt32()) {
auto format = static_cast<EVPKeyPointer::PKFormatType>(
args[*offset + 1].As<Int32>()->Value());
if (format == EVPKeyPointer::PKFormatType::RAW_PRIVATE ||
format == EVPKeyPointer::PKFormatType::RAW_SEED) {
auto data = ImportRawKeyFromArgs(args, *offset);
*offset += 5;
return data;
}
}
auto key = ByteSource::FromStringOrBuffer(env, args[(*offset)++]);
EVPKeyPointer::PrivateKeyEncodingConfig config;
if (!GetPrivateKeyEncodingFromJs(args, offset, kKeyContextInput)
.To(&config)) {
return {};
}
// Skip the namedCurve argument (only used by raw format imports).
(*offset)++;
return TryParsePrivateKey(
env,
config,
ncrypto::Buffer<const unsigned char>{
.data = reinterpret_cast<const unsigned char*>(key.data()),
.len = key.size(),
});
}
CHECK(args[*offset]->IsObject() && allow_key_object);
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args[*offset].As<Object>(), KeyObjectData());
CHECK_EQ(key->Data().GetKeyType(), kKeyTypePrivate);
(*offset) += 5;
return key->Data().addRef();
}
KeyObjectData KeyObjectData::GetPublicOrPrivateKeyFromJs(
const FunctionCallbackInfo<Value>& args,
unsigned int* offset,
bool allow_private_key_store) {
Environment* env = Environment::GetCurrent(args);
// JWK format: data is a JS Object (not buffer), format int is JWK.
if (args[*offset]->IsObject() && !IsAnyBufferSource(args[*offset]) &&
args[*offset + 1]->IsInt32()) {
auto format = static_cast<EVPKeyPointer::PKFormatType>(
args[*offset + 1].As<Int32>()->Value());
if (format == EVPKeyPointer::PKFormatType::JWK) {
auto data = ImportJWKFromArgs(env, args[*offset].As<Object>());
*offset += 5;
return data;
}
if (format == EVPKeyPointer::PKFormatType::STORE) {
if (allow_private_key_store) {
return GetPrivateKeyFromJs(args, offset, false);
}
THROW_ERR_INVALID_ARG_VALUE(
env,
"URLs for OpenSSL STORE loaders are only accepted for private keys");
return {};
}
}
if (args[*offset]->IsString() || IsAnyBufferSource(args[*offset])) {
// Raw format: buffer + raw format int.
if (args[*offset + 1]->IsInt32()) {
auto format = static_cast<EVPKeyPointer::PKFormatType>(
args[*offset + 1].As<Int32>()->Value());
if (format == EVPKeyPointer::PKFormatType::RAW_PUBLIC ||
format == EVPKeyPointer::PKFormatType::RAW_PRIVATE ||
format == EVPKeyPointer::PKFormatType::RAW_SEED) {
auto data = ImportRawKeyFromArgs(args, *offset);
*offset += 5;
return data;
}
}
ArrayBufferOrViewContents<char> data(args[(*offset)++]);
if (!data.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "keyData is too big");
return {};
}
EVPKeyPointer::PrivateKeyEncodingConfig config;
if (!KeyObjectData::GetPrivateKeyEncodingFromJs(
args, offset, kKeyContextInput)
.To(&config)) {
return {};
}
// Skip the namedCurve argument (only used by raw format imports).
(*offset)++;
ncrypto::Buffer<const unsigned char> buffer = {
.data = reinterpret_cast<const unsigned char*>(data.data()),
.len = data.size(),
};
if (config.format == EVPKeyPointer::PKFormatType::PEM) {
// For PEM, we can easily determine whether it is a public or private key
// by looking for the respective PEM tags.
auto res = EVPKeyPointer::TryParsePublicKeyPEM(buffer);
if (res) {
return CreateAsymmetric(kKeyTypePublic, std::move(res.value));
}
if (res.error.value() == EVPKeyPointer::PKParseError::NOT_RECOGNIZED) {
return TryParsePrivateKey(env, config, buffer);
}
ThrowCryptoError(
env, res.openssl_error.value_or(0), "Failed to read asymmetric key");
return {};
}
// For DER, the type determines how to parse it. SPKI, PKCS#8 and SEC1 are
// easy, but PKCS#1 can be a public key or a private key.
static const auto is_public = [](const auto& config,
const auto& buffer) -> bool {
switch (config.type) {
case EVPKeyPointer::PKEncodingType::PKCS1:
return !EVPKeyPointer::IsRSAPrivateKey(buffer);
case EVPKeyPointer::PKEncodingType::SPKI:
return true;
case EVPKeyPointer::PKEncodingType::PKCS8:
return false;
case EVPKeyPointer::PKEncodingType::SEC1:
return false;
default:
UNREACHABLE("Invalid key encoding type");
}
};
if (is_public(config, buffer)) {
auto res = EVPKeyPointer::TryParsePublicKey(config, buffer);
if (res) {
return CreateAsymmetric(KeyType::kKeyTypePublic, std::move(res.value));
}
ThrowCryptoError(
env, res.openssl_error.value_or(0), "Failed to read asymmetric key");
return {};
}
return TryParsePrivateKey(env, config, buffer);
}
CHECK(args[*offset]->IsObject());
KeyObjectHandle* key =
BaseObject::Unwrap<KeyObjectHandle>(args[*offset].As<Object>());
CHECK_NOT_NULL(key);
CHECK_NE(key->Data().GetKeyType(), kKeyTypeSecret);
(*offset) += 5;
return key->Data().addRef();
}
KeyObjectData KeyObjectData::GetParsedKey(KeyType type,
Environment* env,
EVPKeyPointer&& pkey,
ParseKeyResult ret,
const char* default_msg) {
MarkPopErrorOnReturn mark_pop_error_on_return;
switch (ret) {
case ParseKeyResult::kParseKeyOk: {
return CreateAsymmetric(type, std::move(pkey));
}
case ParseKeyResult::kParseKeyNeedPassphrase: {
THROW_ERR_MISSING_PASSPHRASE(env,
"Passphrase required for encrypted key");
return {};
}
default: {
ThrowCryptoError(env, mark_pop_error_on_return.peekError(), default_msg);
return {};
}
}
}
KeyObjectData::KeyObjectData(std::nullptr_t)
: key_type_(KeyType::kKeyTypeSecret) {}
KeyObjectData::KeyObjectData(ByteSource symmetric_key)
: key_type_(KeyType::kKeyTypeSecret),
data_(std::make_shared<Data>(std::move(symmetric_key))) {}
KeyObjectData::KeyObjectData(KeyType type, EVPKeyPointer&& pkey)
: key_type_(type), data_(std::make_shared<Data>(std::move(pkey))) {}
void KeyObjectData::Data::MemoryInfo(MemoryTracker* tracker) const {
if (asymmetric_key) {
tracker->TrackFieldWithSize("key",
kSizeOf_EVP_PKEY +
asymmetric_key.rawPublicKeySize() +
asymmetric_key.rawPrivateKeySize());
} else {
tracker->TraitTrackInline(symmetric_key, "symmetric_key");
}
}
void KeyObjectData::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("data", data_);
}
KeyObjectData KeyObjectData::CreateSecret(ByteSource key) {
return KeyObjectData(std::move(key));
}
KeyObjectData KeyObjectData::CreateAsymmetric(KeyType key_type,
EVPKeyPointer&& pkey) {
CHECK(pkey);
return KeyObjectData(key_type, std::move(pkey));
}
KeyType KeyObjectData::GetKeyType() const {
CHECK(data_);
return key_type_;
}
const EVPKeyPointer& KeyObjectData::GetAsymmetricKey() const {
CHECK_NE(key_type_, kKeyTypeSecret);
CHECK(data_);
return data_->asymmetric_key;
}
const char* KeyObjectData::GetSymmetricKey() const {
CHECK_EQ(key_type_, kKeyTypeSecret);
CHECK(data_);
return data_->symmetric_key.data<char>();
}
size_t KeyObjectData::GetSymmetricKeySize() const {
CHECK_EQ(key_type_, kKeyTypeSecret);
CHECK(data_);
return data_->symmetric_key.size();
}
bool KeyObjectHandle::HasInstance(Environment* env, Local<Value> value) {
auto t = env->crypto_key_object_handle_constructor();
return !t.IsEmpty() && t->HasInstance(value);
}
Local<Function> KeyObjectHandle::Initialize(Environment* env) {
auto templ = env->crypto_key_object_handle_constructor();
if (templ.IsEmpty()) {
Isolate* isolate = env->isolate();
templ = NewFunctionTemplate(isolate, New);
templ->InstanceTemplate()->SetInternalFieldCount(
KeyObjectHandle::kInternalFieldCount);
SetProtoMethod(isolate, templ, "init", Init);
SetProtoMethodNoSideEffect(isolate, templ, "getKeyType", GetKeyType);
SetProtoMethodNoSideEffect(
isolate, templ, "getSymmetricKeySize", GetSymmetricKeySize);
SetProtoMethodNoSideEffect(
isolate, templ, "getAsymmetricKeyType", GetAsymmetricKeyType);
SetProtoMethodNoSideEffect(
isolate, templ, "checkEcKeyData", CheckEcKeyData);
SetProtoMethod(isolate, templ, "export", Export);
SetProtoMethod(isolate, templ, "exportJwk", ExportJWK);
SetProtoMethodNoSideEffect(isolate, templ, "rawPublicKey", RawPublicKey);
SetProtoMethodNoSideEffect(isolate, templ, "rawPrivateKey", RawPrivateKey);
SetProtoMethodNoSideEffect(isolate, templ, "rawSeed", RawSeed);
SetProtoMethodNoSideEffect(
isolate, templ, "exportECPublicRaw", ExportECPublicRaw);
SetProtoMethodNoSideEffect(
isolate, templ, "exportECPrivateRaw", ExportECPrivateRaw);
SetProtoMethodNoSideEffect(
isolate, templ, "exportECPrivatePkcs8", ExportECPrivatePkcs8);
SetProtoMethod(isolate, templ, "keyDetail", GetKeyDetail);
SetProtoMethod(isolate, templ, "equals", Equals);
env->set_crypto_key_object_handle_constructor(templ);
}
return templ->GetFunction(env->context()).ToLocalChecked();
}
void KeyObjectHandle::RegisterExternalReferences(
ExternalReferenceRegistry* registry) {
registry->Register(New);
registry->Register(Init);
registry->Register(GetKeyType);
registry->Register(GetSymmetricKeySize);
registry->Register(GetAsymmetricKeyType);
registry->Register(CheckEcKeyData);
registry->Register(Export);
registry->Register(ExportJWK);
registry->Register(RawPublicKey);
registry->Register(RawPrivateKey);
registry->Register(RawSeed);
registry->Register(ExportECPublicRaw);
registry->Register(ExportECPrivateRaw);
registry->Register(ExportECPrivatePkcs8);
registry->Register(GetKeyDetail);
registry->Register(Equals);
}
MaybeLocal<Object> KeyObjectHandle::Create(Environment* env,
const KeyObjectData& data) {
Local<Object> obj;
Local<Function> ctor = KeyObjectHandle::Initialize(env);
CHECK(!env->crypto_key_object_handle_constructor().IsEmpty());
if (!ctor->NewInstance(env->context(), 0, nullptr).ToLocal(&obj)) {
return {};
}
KeyObjectHandle* key = Unwrap<KeyObjectHandle>(obj);
CHECK_NOT_NULL(key);
key->data_ = data.addRef();
return obj;
}
const KeyObjectData& KeyObjectHandle::Data() {
return data_;
}
void KeyObjectHandle::New(const FunctionCallbackInfo<Value>& args) {
CHECK(args.IsConstructCall());
Environment* env = Environment::GetCurrent(args);
new KeyObjectHandle(env, args.This());
}
KeyObjectHandle::KeyObjectHandle(Environment* env,
Local<Object> wrap)
: BaseObject(env, wrap) {
MakeWeak();
}
void KeyObjectHandle::Init(const FunctionCallbackInfo<Value>& args) {
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args.This());
MarkPopErrorOnReturn mark_pop_error_on_return;
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsInt32());
KeyType type = static_cast<KeyType>(args[0].As<Uint32>()->Value());
unsigned int offset;
switch (type) {
case kKeyTypeSecret: {
if (args.Length() == 5 && args[2]->IsInt32()) {
auto format = static_cast<EVPKeyPointer::PKFormatType>(
args[2].As<Int32>()->Value());
if (format == EVPKeyPointer::PKFormatType::JWK) {
CHECK(args[1]->IsObject());
key->data_ = ImportJWKSecretKey(env, args[1].As<Object>());
break;
}
}
CHECK_EQ(args.Length(), 2);
ArrayBufferOrViewContents<char> buf(args[1]);
key->data_ = KeyObjectData::CreateSecret(buf.ToCopy());
break;
}
case kKeyTypePublic:
case kKeyTypePrivate: {
CHECK_EQ(args.Length(), 6);
// Check if this is a raw or JWK format import:
// args: [keyType, buffer/object, formatInt, typeString/null,
// passphrase/null, namedCurve/null]
if (args[2]->IsInt32()) {
auto format = static_cast<EVPKeyPointer::PKFormatType>(
args[2].As<Int32>()->Value());
if (format == EVPKeyPointer::PKFormatType::RAW_PUBLIC ||
format == EVPKeyPointer::PKFormatType::RAW_PRIVATE ||
format == EVPKeyPointer::PKFormatType::RAW_SEED) {
auto data = ImportRawKeyFromArgs(args, 1);
if (!data) return;
if (type == kKeyTypePublic && data.GetKeyType() == kKeyTypePrivate) {
key->data_ = data.addRefWithType(kKeyTypePublic);
} else {
key->data_ = std::move(data);
}
break;
}
if (format == EVPKeyPointer::PKFormatType::JWK) {
CHECK(args[1]->IsObject());
key->data_ = ImportJWKFromArgs(env, args[1].As<Object>());
if (!key->data_) return;
if (type == kKeyTypePublic &&
key->data_.GetKeyType() == kKeyTypePrivate) {
key->data_ = key->data_.addRefWithType(kKeyTypePublic);
} else if (type == kKeyTypePrivate &&
key->data_.GetKeyType() == kKeyTypePublic) {
THROW_ERR_CRYPTO_INVALID_JWK(
env, "JWK does not contain private key material");
return;
}
args.GetReturnValue().Set(key->data_.GetKeyType());
break;
}
}
offset = 1;
if (type == kKeyTypePublic) {
auto data = KeyObjectData::GetPublicOrPrivateKeyFromJs(args, &offset);
if (!data) return;
key->data_ = data.addRefWithType(kKeyTypePublic);
} else {
if (auto data =
KeyObjectData::GetPrivateKeyFromJs(args, &offset, false)) {
key->data_ = std::move(data);
}
}
break;
}
default:
UNREACHABLE();
}
}
void KeyObjectHandle::GetKeyType(const FunctionCallbackInfo<Value>& args) {
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args.This());
args.GetReturnValue().Set(
Uint32::NewFromUnsigned(args.GetIsolate(), key->Data().GetKeyType()));
}
void KeyObjectHandle::Equals(const FunctionCallbackInfo<Value>& args) {
KeyObjectHandle* self_handle;
KeyObjectHandle* arg_handle;
ASSIGN_OR_RETURN_UNWRAP(&self_handle, args.This());
ASSIGN_OR_RETURN_UNWRAP(&arg_handle, args[0].As<Object>());
const auto& key = self_handle->Data();
const auto& key2 = arg_handle->Data();
KeyType key_type = key.GetKeyType();
CHECK_EQ(key_type, key2.GetKeyType());
bool ret;
switch (key_type) {
case kKeyTypeSecret: {
size_t size = key.GetSymmetricKeySize();
if (size == key2.GetSymmetricKeySize()) {
ret = CRYPTO_memcmp(
key.GetSymmetricKey(), key2.GetSymmetricKey(), size) == 0;
} else {
ret = false;
}
break;
}
case kKeyTypePublic:
// Fall through
case kKeyTypePrivate: {
ClearErrorOnReturn clear_error_on_return;
EVP_PKEY* pkey = key.GetAsymmetricKey().get();
EVP_PKEY* pkey2 = key2.GetAsymmetricKey().get();
#ifndef OPENSSL_IS_BORINGSSL
int ok = EVP_PKEY_eq(pkey, pkey2);
#elif defined(OPENSSL_IS_BORINGSSL)
int ok = EVP_PKEY_cmp(pkey, pkey2);
#endif
if (ok == -2) {
Environment* env = Environment::GetCurrent(args);
return THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env);
}
ret = ok == 1;
break;
}
default:
UNREACHABLE("unsupported key type");
}
args.GetReturnValue().Set(ret);
}
void KeyObjectHandle::GetKeyDetail(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args.This());
CHECK(args[0]->IsObject());
const auto& data = key->Data();
if (data.GetKeyType() == kKeyTypeSecret) {
if (GetSecretKeyDetail(env, data, args[0].As<Object>())) [[likely]] {
args.GetReturnValue().Set(args[0]);
}
return;
}
if (GetAsymmetricKeyDetail(env, data, args[0].As<Object>())) [[likely]] {
args.GetReturnValue().Set(args[0]);
}
}
Local<Value> KeyObjectHandle::GetAsymmetricKeyType() const {
const char* name = data_.GetAsymmetricKey().getKeyTypeName();
if (name == nullptr) return Undefined(env()->isolate());
return OneByteString(
env()->isolate(), name, -1, NewStringType::kInternalized);
}
void KeyObjectHandle::GetAsymmetricKeyType(
const FunctionCallbackInfo<Value>& args) {
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args.This());
args.GetReturnValue().Set(key->GetAsymmetricKeyType());
}
bool KeyObjectHandle::CheckEcKeyData() const {
MarkPopErrorOnReturn mark_pop_error_on_return;
const auto& key = data_.GetAsymmetricKey();
EVPKeyCtxPointer ctx = key.newCtx();
CHECK(ctx);
DCHECK(key.isA(KeyAlgorithm::EC));
return data_.GetKeyType() == kKeyTypePrivate ? ctx.privateCheck()
: ctx.publicCheck();
}
void KeyObjectHandle::CheckEcKeyData(const FunctionCallbackInfo<Value>& args) {
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args.This());
args.GetReturnValue().Set(key->CheckEcKeyData());
}
void KeyObjectHandle::GetSymmetricKeySize(
const FunctionCallbackInfo<Value>& args) {
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args.This());
args.GetReturnValue().Set(
static_cast<uint32_t>(key->Data().GetSymmetricKeySize()));
}
void KeyObjectHandle::Export(const FunctionCallbackInfo<Value>& args) {
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args.This());
KeyType type = key->Data().GetKeyType();
unsigned int offset = 0;
Local<Value> result;
if (type == kKeyTypeSecret) {
if (key->ExportSecretKey().ToLocal(&result)) [[likely]] {
args.GetReturnValue().Set(result);
}
return;
}
if (type == kKeyTypePublic) {
EVPKeyPointer::PublicKeyEncodingConfig config;
if (!KeyObjectData::GetPublicKeyEncodingFromJs(
args, &offset, kKeyContextExport)
.To(&config)) {
return;
}
CHECK_EQ(offset, static_cast<unsigned int>(args.Length()));
if (key->ExportPublicKey(config).ToLocal(&result)) [[likely]] {
args.GetReturnValue().Set(result);
}
return;
}
CHECK_EQ(type, kKeyTypePrivate);
EVPKeyPointer::PrivateKeyEncodingConfig config;
if (!KeyObjectData::GetPrivateKeyEncodingFromJs(
args, &offset, kKeyContextExport)
.To(&config)) {
return;
}
CHECK_EQ(offset, static_cast<unsigned int>(args.Length()));
if (key->ExportPrivateKey(config).ToLocal(&result)) [[likely]] {
args.GetReturnValue().Set(result);
}
}
MaybeLocal<Value> KeyObjectHandle::ExportSecretKey() const {
return Buffer::Copy(
env(), data_.GetSymmetricKey(), data_.GetSymmetricKeySize())
.FromMaybe(Local<Value>());
}
MaybeLocal<Value> KeyObjectHandle::ExportPublicKey(
const EVPKeyPointer::PublicKeyEncodingConfig& config) const {
return WritePublicKey(env(), data_.GetAsymmetricKey(), config);
}
MaybeLocal<Value> KeyObjectHandle::ExportPrivateKey(
const EVPKeyPointer::PrivateKeyEncodingConfig& config) const {
return WritePrivateKey(env(), data_.GetAsymmetricKey(), config);
}
void KeyObjectHandle::RawPublicKey(
const v8::FunctionCallbackInfo<v8::Value>& args) {
Environment* env = Environment::GetCurrent(args);
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args.This());
const KeyObjectData& data = key->Data();
CHECK_NE(data.GetKeyType(), kKeyTypeSecret);
const auto& pkey = data.GetAsymmetricKey();
const bool is_raw_supported = pkey.supportsRawPublic();
if (!is_raw_supported) {
return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
}
auto raw_data = pkey.rawPublicKey();
if (!raw_data) {
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to get raw public key");
}
args.GetReturnValue().Set(
Buffer::Copy(env, raw_data.get<const char>(), raw_data.size())
.FromMaybe(Local<Value>()));
}
void KeyObjectHandle::RawPrivateKey(
const v8::FunctionCallbackInfo<v8::Value>& args) {
Environment* env = Environment::GetCurrent(args);
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args.This());
const KeyObjectData& data = key->Data();
CHECK_EQ(data.GetKeyType(), kKeyTypePrivate);
const auto& pkey = data.GetAsymmetricKey();
const bool is_raw_supported = pkey.supportsRawPrivate();
if (!is_raw_supported) {
return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
}
auto raw_data = pkey.rawPrivateKey();
if (!raw_data) {
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to get raw private key");
}
args.GetReturnValue().Set(
Buffer::Copy(env, raw_data.get<const char>(), raw_data.size())
.FromMaybe(Local<Value>()));
}
void KeyObjectHandle::ExportECPublicRaw(
const v8::FunctionCallbackInfo<v8::Value>& args) {
Environment* env = Environment::GetCurrent(args);
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args.This());
const KeyObjectData& data = key->Data();
CHECK_NE(data.GetKeyType(), kKeyTypeSecret);
const auto& m_pkey = data.GetAsymmetricKey();
if (!m_pkey.isA(KeyAlgorithm::EC)) {
return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
}
CHECK(args[0]->IsInt32());
auto form =
static_cast<point_conversion_form_t>(args[0].As<Int32>()->Value());
auto bytes = ncrypto::Ec::TryExportPublic(m_pkey, form);
if (bytes) {
args.GetReturnValue().Set(
Buffer::Copy(env, bytes.get<const char>(), bytes.size())
.FromMaybe(Local<Value>()));
return;
}
ECKeyPointer ec_key(m_pkey);
if (!ec_key) return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
if (ec_key.getPublicKey() == nullptr) {
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to export EC public key");
}
Local<Object> buf;
if (!ECPointToBuffer(env, ec_key.getGroup(), ec_key.getPublicKey(), form)
.ToLocal(&buf))
return;
args.GetReturnValue().Set(buf);
}
void KeyObjectHandle::ExportECPrivateRaw(
const v8::FunctionCallbackInfo<v8::Value>& args) {
Environment* env = Environment::GetCurrent(args);
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args.This());
const KeyObjectData& data = key->Data();
CHECK_EQ(data.GetKeyType(), kKeyTypePrivate);
const auto& m_pkey = data.GetAsymmetricKey();
if (!m_pkey.isA(KeyAlgorithm::EC)) {
return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
}
auto buf = ncrypto::Ec::ExportPrivate(m_pkey);
if (!buf) {
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to export EC private key");
}
args.GetReturnValue().Set(Buffer::Copy(env, buf.get<const char>(), buf.size())
.FromMaybe(Local<Value>()));
}
void KeyObjectHandle::ExportECPrivatePkcs8(
const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args.This());
const KeyObjectData& data = key->Data();
CHECK_EQ(data.GetKeyType(), kKeyTypePrivate);
auto encoded = ncrypto::Ec::ExportPrivatePkcs8(data.GetAsymmetricKey());
if (!encoded) {
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to export EC private key");
}
const EVPKeyPointer::PrivateKeyEncodingConfig config;
args.GetReturnValue().Set(
ToV8Value(env, encoded, config).FromMaybe(Local<Value>()));
}
void KeyObjectHandle::RawSeed(const v8::FunctionCallbackInfo<v8::Value>& args) {
Environment* env = Environment::GetCurrent(args);
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args.This());
const KeyObjectData& data = key->Data();
CHECK_EQ(data.GetKeyType(), kKeyTypePrivate);
const auto& pkey = data.GetAsymmetricKey();
auto raw_data = pkey.rawSeed();
if (!raw_data) {
if (raw_data.error == EVPKeyPointer::RawExportError::UNSUPPORTED_KEY_TYPE) {
return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
}
return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get raw seed");
}
args.GetReturnValue().Set(
Buffer::Copy(env, raw_data.value.get<const char>(), raw_data.value.size())
.FromMaybe(Local<Value>()));
}
void KeyObjectHandle::ExportJWK(
const v8::FunctionCallbackInfo<v8::Value>& args) {
Environment* env = Environment::GetCurrent(args);
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args.This());
CHECK(args[0]->IsObject());
CHECK(args[1]->IsBoolean());
if (ExportJWKInner(env, key->Data(), args[0], args[1]->IsTrue())) {
args.GetReturnValue().Set(args[0]);
} else if (!env->isolate()->HasPendingException()) {
THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to export JWK");
}
}
void NativeKeyObject::Initialize(Environment* env, Local<Object> target) {
SetMethod(env->context(),
target,
"createNativeKeyObjectClass",
NativeKeyObject::CreateNativeKeyObjectClass);
SetMethod(
env->context(), target, "getKeyObjectSlots", NativeKeyObject::GetSlots);
SetMethodNoSideEffect(
env->context(), target, "isKeyObject", NativeKeyObject::IsKeyObject);
}
void NativeKeyObject::RegisterExternalReferences(
ExternalReferenceRegistry* registry) {
registry->Register(NativeKeyObject::CreateNativeKeyObjectClass);
registry->Register(NativeKeyObject::GetSlots);
registry->Register(NativeKeyObject::IsKeyObject);
registry->Register(NativeKeyObject::New);
}
bool NativeKeyObject::HasInstance(Environment* env, Local<Value> value) {
auto t = env->crypto_key_object_constructor_template();
return !t.IsEmpty() && t->HasInstance(value);
}
void NativeKeyObject::IsKeyObject(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK_EQ(args.Length(), 1);
args.GetReturnValue().Set(HasInstance(env, args[0]));
}
void NativeKeyObject::New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK_EQ(args.Length(), 1);
CHECK(KeyObjectHandle::HasInstance(env, args[0]));
KeyObjectHandle* handle = Unwrap<KeyObjectHandle>(args[0].As<Object>());
CHECK_NOT_NULL(handle);
new NativeKeyObject(env, args.This(), handle->Data());
}
void NativeKeyObject::CreateNativeKeyObjectClass(
const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = env->isolate();
CHECK_EQ(args.Length(), 1);
Local<Value> callback = args[0];
CHECK(callback->IsFunction());
Local<FunctionTemplate> t = env->crypto_key_object_constructor_template();
if (t.IsEmpty()) {
t = NewFunctionTemplate(isolate, NativeKeyObject::New);
t->InstanceTemplate()->SetInternalFieldCount(
NativeKeyObject::kInternalFieldCount);
env->set_crypto_key_object_constructor_template(t);
}
Local<Value> ctor;
if (!t->GetFunction(env->context()).ToLocal(&ctor))
return;
Local<Value> recv = Undefined(env->isolate());
Local<Value> ret_v;
if (!callback.As<Function>()->Call(
env->context(), recv, 1, &ctor).ToLocal(&ret_v)) {
return;
}
Local<Array> ret = ret_v.As<Array>();
if (!ret->Get(env->context(), 1).ToLocal(&ctor)) return;
env->set_crypto_key_object_secret_constructor(ctor.As<Function>());
if (!ret->Get(env->context(), 2).ToLocal(&ctor)) return;
env->set_crypto_key_object_public_constructor(ctor.As<Function>());
if (!ret->Get(env->context(), 3).ToLocal(&ctor)) return;
env->set_crypto_key_object_private_constructor(ctor.As<Function>());
args.GetReturnValue().Set(ret);
}
// Returns the key's native hidden slot tuple as a single Array:
// [type enum, handle]. JS-side helpers call this once per key to prime
// a per-instance cache; derived metadata is appended lazily from JS by
// calling methods on the returned KeyObjectHandle.
void NativeKeyObject::GetSlots(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK_EQ(args.Length(), 1);
if (!HasInstance(env, args[0])) {
THROW_ERR_INVALID_THIS(env, "Value of \"this\" must be of type KeyObject");
return;
}
NativeKeyObject* native = Unwrap<NativeKeyObject>(args[0].As<Object>());
CHECK_NOT_NULL(native);
Local<Object> handle;
if (!KeyObjectHandle::Create(env, native->handle_data_).ToLocal(&handle)) {
return;
}
Isolate* isolate = env->isolate();
Local<Value> slots[] = {
Uint32::NewFromUnsigned(isolate, native->handle_data_.GetKeyType()),
handle,
};
args.GetReturnValue().Set(Array::New(isolate, slots, arraysize(slots)));
}
BaseObjectPtr<BaseObject> NativeKeyObject::KeyObjectTransferData::Deserialize(
Environment* env,
Local<Context> context,
std::unique_ptr<worker::TransferData> self) {
if (context != env->context()) {
THROW_ERR_MESSAGE_TARGET_CONTEXT_UNAVAILABLE(env);
return {};
}
Local<Value> handle;
if (!KeyObjectHandle::Create(env, data_).ToLocal(&handle))
return {};
Local<Function> key_ctor;
Local<Value> arg = env->internal_crypto_keys_string();
if (env->builtin_module_require()
->Call(context, Null(env->isolate()), 1, &arg)
.IsEmpty()) {
return {};
}
switch (data_.GetKeyType()) {
case kKeyTypeSecret:
key_ctor = env->crypto_key_object_secret_constructor();
break;
case kKeyTypePublic:
key_ctor = env->crypto_key_object_public_constructor();
break;
case kKeyTypePrivate:
key_ctor = env->crypto_key_object_private_constructor();
break;
default:
UNREACHABLE();
}
Local<Value> key;
if (!key_ctor->NewInstance(context, 1, &handle).ToLocal(&key))
return {};
return BaseObjectPtr<BaseObject>(Unwrap<NativeKeyObject>(key.As<Object>()));
}
BaseObject::TransferMode NativeKeyObject::GetTransferMode() const {
return BaseObject::TransferMode::kCloneable;
}
std::unique_ptr<worker::TransferData> NativeKeyObject::CloneForMessaging()
const {
return std::make_unique<KeyObjectTransferData>(handle_data_);
}
void NativeCryptoKey::Initialize(Environment* env, Local<Object> target) {
SetMethod(env->context(),
target,
"createCryptoKeyClass",
NativeCryptoKey::CreateCryptoKeyClass);
SetMethod(
env->context(), target, "getCryptoKeySlots", NativeCryptoKey::GetSlots);
SetMethodNoSideEffect(
env->context(), target, "isCryptoKey", NativeCryptoKey::IsCryptoKey);
}
void NativeCryptoKey::RegisterExternalReferences(
ExternalReferenceRegistry* registry) {
registry->Register(NativeCryptoKey::CreateCryptoKeyClass);
registry->Register(NativeCryptoKey::GetSlots);
registry->Register(NativeCryptoKey::IsCryptoKey);
registry->Register(NativeCryptoKey::New);
}
namespace {
// Verifies that `value` is a `NativeCryptoKey` by checking whether it
// was constructed from the Environment's `NativeCryptoKey` template.
bool IsNativeCryptoKey(Environment* env, Local<Value> value) {
auto t = env->crypto_cryptokey_constructor_template();
return !t.IsEmpty() && t->HasInstance(value);
}
} // namespace
bool NativeCryptoKey::HasInstance(Environment* env, Local<Value> value) {
return IsNativeCryptoKey(env, value);
}
void NativeCryptoKey::IsCryptoKey(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK_EQ(args.Length(), 1);
args.GetReturnValue().Set(HasInstance(env, args[0]));
}
MaybeLocal<Value> NativeCryptoKey::Create(Environment* env,
const KeyObjectData& data,
Local<Value> algorithm,
uint32_t usages_mask,
bool extractable) {
Local<Context> context = env->context();
Isolate* isolate = env->isolate();
CHECK(algorithm->IsObject());
Local<Object> handle;
if (!KeyObjectHandle::Create(env, data).ToLocal(&handle)) return {};
if (env->crypto_internal_cryptokey_constructor().IsEmpty()) {
Local<Value> arg = env->internal_crypto_keys_string();
if (env->builtin_module_require()
->Call(context, Null(isolate), 1, &arg)
.IsEmpty()) {
return {};
}
}
Local<Function> cryptokey_ctor = env->crypto_internal_cryptokey_constructor();
CHECK(!cryptokey_ctor.IsEmpty());
Local<Value> ctor_args[] = {
handle,
algorithm,
Uint32::NewFromUnsigned(isolate, usages_mask),
Boolean::New(isolate, extractable),
};
return cryptokey_ctor->NewInstance(context, arraysize(ctor_args), ctor_args);
}
void NativeCryptoKey::New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK_GE(args.Length(), 4);
CHECK_LE(args.Length(), 6);
// args[0] is a KeyObjectHandle; we keep its KeyObjectData directly.
// args[1] is the algorithm dictionary object.
// args[2] is the usages mask.
// args[3] is the extractable boolean.
// args[4], when present, is an optional secondary KeyObjectHandle for
// Hybrid KEM CryptoKeys.
// args[5], when present, is optional seed_data for Hybrid KEM CryptoKeys.
//
// args[1] is undefined only when called from
// CryptoKeyTransferData::Deserialize for a partially-initialized
// CryptoKey: algorithm/usages mask/extractable get filled in afterwards
// by FinalizeTransferRead before any JS can see the object.
//
// This constructor is not exposed to user JS - the public CryptoKey
// class throws from its constructor and InternalCryptoKey is kept
// in a module-closure.
CHECK(KeyObjectHandle::HasInstance(env, args[0]));
KeyObjectHandle* handle = Unwrap<KeyObjectHandle>(args[0].As<Object>());
CHECK_NOT_NULL(handle);
KeyObjectData secondary_data;
if (args.Length() >= 5 && !args[4]->IsUndefined()) {
CHECK(KeyObjectHandle::HasInstance(env, args[4]));
KeyObjectHandle* secondary_handle =
Unwrap<KeyObjectHandle>(args[4].As<Object>());
CHECK_NOT_NULL(secondary_handle);
secondary_data = secondary_handle->Data();
}
ByteSource seed_data;
if (args.Length() == 6 && !args[5]->IsUndefined()) {
CHECK(IsAnyBufferSource(args[5]));
ArrayBufferOrViewContents<char> buf(args[5]);
seed_data = buf.ToCopy();
}
auto* native = new NativeCryptoKey(
env, args.This(), handle->Data(), secondary_data, std::move(seed_data));
if (!args[1]->IsUndefined()) {
CHECK(args[1]->IsObject());
CHECK(args[2]->IsUint32());
CHECK(args[3]->IsBoolean());
args.This()->SetInternalField(kAlgorithmField, args[1]);
native->usages_mask_ = args[2].As<Uint32>()->Value();
native->extractable_ = args[3]->IsTrue();
}
}
void NativeCryptoKey::CreateCryptoKeyClass(
const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = env->isolate();
CHECK_EQ(args.Length(), 1);
Local<Value> callback = args[0];
CHECK(callback->IsFunction());
Local<FunctionTemplate> t = env->crypto_cryptokey_constructor_template();
if (t.IsEmpty()) {
t = NewFunctionTemplate(isolate, NativeCryptoKey::New);
t->InstanceTemplate()->SetInternalFieldCount(
NativeCryptoKey::kInternalFieldCount);
env->set_crypto_cryptokey_constructor_template(t);
}
Local<Value> ctor;
if (!t->GetFunction(env->context()).ToLocal(&ctor)) return;
Local<Value> recv = Undefined(env->isolate());
Local<Value> ret_v;
if (!callback.As<Function>()
->Call(env->context(), recv, 1, &ctor)
.ToLocal(&ret_v)) {
return;
}
Local<Array> ret = ret_v.As<Array>();
Local<Value> internal_ctor_v;
if (!ret->Get(env->context(), 1).ToLocal(&internal_ctor_v)) return;
CHECK(env->crypto_internal_cryptokey_constructor().IsEmpty());
env->set_crypto_internal_cryptokey_constructor(
internal_ctor_v.As<Function>());
args.GetReturnValue().Set(ret);
}
// Returns all of the key's internal slot values as a single Array:
// [type enum, extractable, algorithm, usages mask, handle, secondary handle,
// seed data]. JS-side helpers call this once per key to prime a per-instance
// cache, so subsequent reads don't need to cross into C++ at all. The
// secondary handle and seed data slots are only used by Hybrid KEM CryptoKeys.
void NativeCryptoKey::GetSlots(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK_EQ(args.Length(), 1);
if (!HasInstance(env, args[0])) {
THROW_ERR_INVALID_THIS(env, "Value of \"this\" must be of type CryptoKey");
return;
}
Local<Object> obj = args[0].As<Object>();
NativeCryptoKey* native = Unwrap<NativeCryptoKey>(obj);
CHECK_NOT_NULL(native);
Local<Object> handle;
if (!KeyObjectHandle::Create(env, native->handle_data_).ToLocal(&handle)) {
return;
}
Isolate* isolate = env->isolate();
Local<Value> secondary_handle = Undefined(isolate);
if (native->secondary_handle_data_) {
Local<Object> secondary_handle_object;
if (!KeyObjectHandle::Create(env, native->secondary_handle_data_)
.ToLocal(&secondary_handle_object)) {
return;
}
secondary_handle = secondary_handle_object;
}
Local<Value> seed_data = Undefined(isolate);
if (native->seed_data_) {
Local<Object> buf;
if (!Buffer::Copy(
env, native->seed_data_.data<char>(), native->seed_data_.size())
.ToLocal(&buf)) {
return;
}
seed_data = buf;
}
Local<Value> algorithm = obj->GetInternalField(kAlgorithmField).As<Value>();
CHECK(algorithm->IsObject());
Local<Value> slots[] = {
Uint32::NewFromUnsigned(isolate, native->handle_data_.GetKeyType()),
v8::Boolean::New(isolate, native->extractable_),
algorithm,
Uint32::NewFromUnsigned(isolate, native->usages_mask_),
handle,
secondary_handle,
seed_data,
};
args.GetReturnValue().Set(Array::New(isolate, slots, arraysize(slots)));
}
BaseObject::TransferMode NativeCryptoKey::GetTransferMode() const {
return BaseObject::TransferMode::kCloneable;
}
std::unique_ptr<worker::TransferData> NativeCryptoKey::CloneForMessaging()
const {
Isolate* isolate = env()->isolate();
Local<Object> obj = object();
Local<Value> algorithm_v = obj->GetInternalField(kAlgorithmField).As<Value>();
CHECK(algorithm_v->IsObject());
v8::Global<Object> algorithm_copy(isolate, algorithm_v.As<Object>());
return std::make_unique<CryptoKeyTransferData>(handle_data_,
secondary_handle_data_,
seed_data_.ToCopy(),
std::move(algorithm_copy),
usages_mask_,
extractable_);
}
Maybe<void> NativeCryptoKey::FinalizeTransferRead(
Local<Context> context, v8::ValueDeserializer* deserializer) {
Local<Value> bundle_v;
if (!deserializer->ReadValue(context).ToLocal(&bundle_v)) {
return Nothing<void>();
}
CHECK(bundle_v->IsObject());
Local<Object> bundle = bundle_v.As<Object>();
Local<Object> obj = object();
// The partially-initialized object produced by
// CryptoKeyTransferData::Deserialize should not have algorithm set yet.
CHECK(obj->GetInternalField(kAlgorithmField).As<Value>()->IsUndefined());
Local<Value> algorithm_v;
if (!bundle->Get(context, env()->algorithm_string()).ToLocal(&algorithm_v)) {
return Nothing<void>();
}
CHECK(algorithm_v->IsObject());
obj->SetInternalField(kAlgorithmField, algorithm_v);
Local<Value> usages_v;
if (!bundle->Get(context, env()->usages_string()).ToLocal(&usages_v)) {
return Nothing<void>();
}
CHECK(usages_v->IsUint32());
usages_mask_ = usages_v.As<Uint32>()->Value();
Local<Value> extractable_v;
if (!bundle->Get(context, env()->extractable_string())
.ToLocal(&extractable_v)) {
return Nothing<void>();
}
CHECK(extractable_v->IsBoolean());
extractable_ = extractable_v->IsTrue();
return v8::JustVoid();
}
Maybe<bool> NativeCryptoKey::CryptoKeyTransferData::FinalizeTransferWrite(
Local<Context> context, v8::ValueSerializer* serializer) {
Isolate* isolate = Isolate::GetCurrent();
Environment* env = Environment::GetCurrent(isolate);
CHECK(!algorithm_.IsEmpty());
Local<Object> bundle = Object::New(isolate);
Local<Value> algorithm_v = PersistentToLocal::Strong(algorithm_);
if (bundle->Set(context, env->algorithm_string(), algorithm_v).IsNothing() ||
bundle
->Set(context,
env->usages_string(),
Uint32::NewFromUnsigned(isolate, usages_mask_))
.IsNothing() ||
bundle
->Set(context,
env->extractable_string(),
v8::Boolean::New(isolate, extractable_))
.IsNothing()) {
return Nothing<bool>();
}
auto ret = serializer->WriteValue(context, bundle);
algorithm_.Reset();
return ret;
}
BaseObjectPtr<BaseObject> NativeCryptoKey::CryptoKeyTransferData::Deserialize(
Environment* env,
Local<Context> context,
std::unique_ptr<worker::TransferData> self) {
if (context != env->context()) {
THROW_ERR_MESSAGE_TARGET_CONTEXT_UNAVAILABLE(env);
return {};
}
// Reconstruct the KeyObjectHandle for the transferred KeyObjectData.
Local<Object> handle;
if (!KeyObjectHandle::Create(env, data_).ToLocal(&handle)) return {};
Isolate* isolate = env->isolate();
Local<Value> secondary_handle = Undefined(isolate);
if (secondary_data_) {
Local<Object> secondary_handle_object;
if (!KeyObjectHandle::Create(env, secondary_data_)
.ToLocal(&secondary_handle_object)) {
return {};
}
secondary_handle = secondary_handle_object;
}
Local<Value> seed_data = Undefined(isolate);
if (seed_data_) {
Local<Object> buf;
if (!Buffer::Copy(env, seed_data_.data<char>(), seed_data_.size())
.ToLocal(&buf)) {
return {};
}
seed_data = buf;
}
// Make sure internal/crypto/keys has been loaded so that the
// CryptoKey constructor is registered with the Environment.
Local<Value> arg = env->internal_crypto_keys_string();
if (env->builtin_module_require()
->Call(context, Null(isolate), 1, &arg)
.IsEmpty()) {
return {};
}
// Construct a partially-initialized InternalCryptoKey; algorithm,
// usages mask and extractable are filled in via FinalizeTransferRead.
Local<Function> cryptokey_ctor = env->crypto_internal_cryptokey_constructor();
CHECK(!cryptokey_ctor.IsEmpty());
Local<Value> ctor_args[] = {
handle,
Undefined(isolate),
Undefined(isolate),
Undefined(isolate),
secondary_handle,
seed_data,
};
Local<Value> cryptokey;
if (!cryptokey_ctor->NewInstance(context, 6, ctor_args).ToLocal(&cryptokey)) {
return {};
}
return BaseObjectPtr<BaseObject>(
Unwrap<NativeCryptoKey>(cryptokey.As<Object>()));
}
void NativeCryptoKey::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("handle_data", handle_data_);
if (secondary_handle_data_) {
tracker->TrackField("secondary_handle_data", secondary_handle_data_);
}
tracker->TrackFieldWithSize("seed_data", seed_data_.size());
}
void NativeCryptoKey::CryptoKeyTransferData::MemoryInfo(
MemoryTracker* tracker) const {
tracker->TrackField("data", data_);
if (secondary_data_) {
tracker->TrackField("secondary_data", secondary_data_);
}
tracker->TrackFieldWithSize("seed_data", seed_data_.size());
tracker->TrackField("algorithm", algorithm_);
}
namespace Keys {
void Initialize(Environment* env, Local<Object> target) {
Local<Context> context = env->context();
target->Set(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "KeyObjectHandle"),
KeyObjectHandle::Initialize(env)).Check();
constexpr int kKeyEncodingPKCS1 =
static_cast<int>(EVPKeyPointer::PKEncodingType::PKCS1);
constexpr int kKeyEncodingPKCS8 =
static_cast<int>(EVPKeyPointer::PKEncodingType::PKCS8);
constexpr int kKeyEncodingSPKI =
static_cast<int>(EVPKeyPointer::PKEncodingType::SPKI);
constexpr int kKeyEncodingSEC1 =
static_cast<int>(EVPKeyPointer::PKEncodingType::SEC1);
constexpr int kKeyFormatDER =
static_cast<int>(EVPKeyPointer::PKFormatType::DER);
constexpr int kKeyFormatPEM =
static_cast<int>(EVPKeyPointer::PKFormatType::PEM);
constexpr int kKeyFormatJWK =
static_cast<int>(EVPKeyPointer::PKFormatType::JWK);
constexpr int kKeyFormatRawPublic =
static_cast<int>(EVPKeyPointer::PKFormatType::RAW_PUBLIC);
constexpr int kKeyFormatRawPrivate =
static_cast<int>(EVPKeyPointer::PKFormatType::RAW_PRIVATE);
constexpr int kKeyFormatRawSeed =
static_cast<int>(EVPKeyPointer::PKFormatType::RAW_SEED);
constexpr int kKeyFormatStore =
static_cast<int>(EVPKeyPointer::PKFormatType::STORE);
constexpr auto kSigEncDER = DSASigEnc::DER;
constexpr auto kSigEncP1363 = DSASigEnc::P1363;
NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatRaw);
NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatPKCS8);
NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatSPKI);
NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatJWK);
SetMethodNoSideEffect(
context, target, "isKeyAlgorithmAvailable", IsKeyAlgorithmAvailable);
NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS1);
NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS8);
NODE_DEFINE_CONSTANT(target, kKeyEncodingSPKI);
NODE_DEFINE_CONSTANT(target, kKeyEncodingSEC1);
NODE_DEFINE_CONSTANT(target, kKeyFormatDER);
NODE_DEFINE_CONSTANT(target, kKeyFormatPEM);
NODE_DEFINE_CONSTANT(target, kKeyFormatJWK);
NODE_DEFINE_CONSTANT(target, kKeyFormatRawPublic);
NODE_DEFINE_CONSTANT(target, kKeyFormatRawPrivate);
NODE_DEFINE_CONSTANT(target, kKeyFormatRawSeed);
NODE_DEFINE_CONSTANT(target, kKeyFormatStore);
NODE_DEFINE_CONSTANT(target, kKeyTypeSecret);
NODE_DEFINE_CONSTANT(target, kKeyTypePublic);
NODE_DEFINE_CONSTANT(target, kKeyTypePrivate);
NODE_DEFINE_CONSTANT(target, kSigEncDER);
NODE_DEFINE_CONSTANT(target, kSigEncP1363);
}
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
KeyObjectHandle::RegisterExternalReferences(registry);
registry->Register(IsKeyAlgorithmAvailable);
}
} // namespace Keys
} // namespace crypto
} // namespace node