src: move more key related stuff to ncrypto

PR-URL: https://github.com/nodejs/node/pull/55368
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
This commit is contained in:
James M Snell 2024-11-02 08:11:28 -07:00 committed by GitHub
parent 91bce94010
commit 5b9bf39b47
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 466 additions and 374 deletions

View File

@ -80,7 +80,7 @@ std::optional<std::string> CryptoErrorList::pop_front() {
// ============================================================================ // ============================================================================
DataPointer DataPointer::Alloc(size_t len) { DataPointer DataPointer::Alloc(size_t len) {
return DataPointer(OPENSSL_malloc(len), len); return DataPointer(OPENSSL_zalloc(len), len);
} }
DataPointer::DataPointer(void* data, size_t length) DataPointer::DataPointer(void* data, size_t length)
@ -1428,6 +1428,33 @@ DataPointer pbkdf2(const EVP_MD* md,
// ============================================================================ // ============================================================================
EVPKeyPointer::PrivateKeyEncodingConfig::PrivateKeyEncodingConfig(
const PrivateKeyEncodingConfig& other)
: PrivateKeyEncodingConfig(other.output_key_object, other.format, other.type) {
cipher = other.cipher;
if (other.passphrase.has_value()) {
auto& otherPassphrase = other.passphrase.value();
auto newPassphrase = DataPointer::Alloc(otherPassphrase.size());
memcpy(newPassphrase.get(), otherPassphrase.get(), otherPassphrase.size());
passphrase = std::move(newPassphrase);
}
}
EVPKeyPointer::AsymmetricKeyEncodingConfig::AsymmetricKeyEncodingConfig(
bool output_key_object,
PKFormatType format,
PKEncodingType type)
: output_key_object(output_key_object),
format(format),
type(type) {}
EVPKeyPointer::PrivateKeyEncodingConfig& EVPKeyPointer::PrivateKeyEncodingConfig::operator=(
const PrivateKeyEncodingConfig& other) {
if (this == &other) return *this;
this->~PrivateKeyEncodingConfig();
return *new (this) PrivateKeyEncodingConfig(other);
}
EVPKeyPointer EVPKeyPointer::New() { EVPKeyPointer EVPKeyPointer::New() {
return EVPKeyPointer(EVP_PKEY_new()); return EVPKeyPointer(EVP_PKEY_new());
} }
@ -1661,14 +1688,13 @@ EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePublicKeyPEM(
} }
EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePublicKey( EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePublicKey(
PKFormatType format, const PublicKeyEncodingConfig& config,
PKEncodingType encoding,
const Buffer<const unsigned char>& buffer) { const Buffer<const unsigned char>& buffer) {
if (format == PKFormatType::PEM) { if (config.format == PKFormatType::PEM) {
return TryParsePublicKeyPEM(buffer); return TryParsePublicKeyPEM(buffer);
} }
if (format != PKFormatType::DER) { if (config.format != PKFormatType::DER) {
return ParseKeyResult(PKParseError::FAILED); return ParseKeyResult(PKParseError::FAILED);
} }
@ -1676,12 +1702,12 @@ EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePublicKey(
EVP_PKEY* key = nullptr; EVP_PKEY* key = nullptr;
if (encoding == PKEncodingType::PKCS1 && if (config.type == PKEncodingType::PKCS1 &&
(key = d2i_PublicKey(EVP_PKEY_RSA, nullptr, &start, buffer.len))) { (key = d2i_PublicKey(EVP_PKEY_RSA, nullptr, &start, buffer.len))) {
return EVPKeyPointer::ParseKeyResult(EVPKeyPointer(key)); return EVPKeyPointer::ParseKeyResult(EVPKeyPointer(key));
} }
if (encoding == PKEncodingType::SPKI && if (config.type == PKEncodingType::SPKI &&
(key = d2i_PUBKEY(nullptr, &start, buffer.len))) { (key = d2i_PUBKEY(nullptr, &start, buffer.len))) {
return EVPKeyPointer::ParseKeyResult(EVPKeyPointer(key)); return EVPKeyPointer::ParseKeyResult(EVPKeyPointer(key));
} }
@ -1689,13 +1715,34 @@ EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePublicKey(
return ParseKeyResult(PKParseError::FAILED); return ParseKeyResult(PKParseError::FAILED);
} }
namespace {
Buffer<char> GetPassphrase(const EVPKeyPointer::PrivateKeyEncodingConfig& config) {
Buffer<char> pass {
// OpenSSL will not actually dereference this pointer, so it can be any
// non-null pointer. We cannot assert that directly, which is why we
// intentionally use a pointer that will likely cause a segmentation fault
// when dereferenced.
.data = reinterpret_cast<char*>(-1),
.len = 0,
};
if (config.passphrase.has_value()) {
auto& passphrase = config.passphrase.value();
// The pass.data can't be a nullptr, even if the len is zero or else
// openssl will prompt for a password and we really don't want that.
if (passphrase.get() != nullptr) {
pass.data = static_cast<char*>(passphrase.get());
}
pass.len = passphrase.size();
}
return pass;
}
} // namespace
EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePrivateKey( EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePrivateKey(
PKFormatType format, const PrivateKeyEncodingConfig& config,
PKEncodingType encoding,
std::optional<Buffer<char>> maybe_passphrase,
const Buffer<const unsigned char>& buffer) { const Buffer<const unsigned char>& buffer) {
static auto keyOrError = [&](EVPKeyPointer pkey, bool had_passphrase = false) { static constexpr auto keyOrError = [](EVPKeyPointer pkey, bool had_passphrase = false) {
if (int err = ERR_peek_error()) { if (int err = ERR_peek_error()) {
if (ERR_GET_LIB(err) == ERR_LIB_PEM && if (ERR_GET_LIB(err) == ERR_LIB_PEM &&
ERR_GET_REASON(err) == PEM_R_BAD_PASSWORD_READ && ERR_GET_REASON(err) == PEM_R_BAD_PASSWORD_READ &&
@ -1708,24 +1755,23 @@ EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePrivateKey(
return ParseKeyResult(std::move(pkey)); return ParseKeyResult(std::move(pkey));
}; };
Buffer<char>* passphrase = nullptr;
if (maybe_passphrase.has_value()) {
passphrase = &maybe_passphrase.value();
}
auto bio = BIOPointer::New(buffer); auto bio = BIOPointer::New(buffer);
if (!bio) return ParseKeyResult(PKParseError::FAILED); if (!bio) return ParseKeyResult(PKParseError::FAILED);
if (format == PKFormatType::PEM) { auto passphrase = GetPassphrase(config);
auto key = PEM_read_bio_PrivateKey(bio.get(), nullptr, PasswordCallback, passphrase);
return keyOrError(EVPKeyPointer(key), maybe_passphrase.has_value()); if (config.format == PKFormatType::PEM) {
auto key = PEM_read_bio_PrivateKey(bio.get(), nullptr, PasswordCallback,
config.passphrase.has_value() ? &passphrase : nullptr);
return keyOrError(EVPKeyPointer(key), config.passphrase.has_value());
} }
if (format != PKFormatType::DER) { if (config.format != PKFormatType::DER) {
return ParseKeyResult(PKParseError::FAILED); return ParseKeyResult(PKParseError::FAILED);
} }
switch (encoding) { switch (config.type) {
case PKEncodingType::PKCS1: { case PKEncodingType::PKCS1: {
auto key = d2i_PrivateKey_bio(bio.get(), nullptr); auto key = d2i_PrivateKey_bio(bio.get(), nullptr);
return keyOrError(EVPKeyPointer(key)); return keyOrError(EVPKeyPointer(key));
@ -1735,8 +1781,8 @@ EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePrivateKey(
auto key = d2i_PKCS8PrivateKey_bio(bio.get(), auto key = d2i_PKCS8PrivateKey_bio(bio.get(),
nullptr, nullptr,
PasswordCallback, PasswordCallback,
passphrase); config.passphrase.has_value() ? &passphrase : nullptr);
return keyOrError(EVPKeyPointer(key), maybe_passphrase.has_value()); return keyOrError(EVPKeyPointer(key), config.passphrase.has_value());
} }
PKCS8Pointer p8inf(d2i_PKCS8_PRIV_KEY_INFO_bio(bio.get(), nullptr)); PKCS8Pointer p8inf(d2i_PKCS8_PRIV_KEY_INFO_bio(bio.get(), nullptr));
@ -1755,4 +1801,166 @@ EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePrivateKey(
}; };
} }
Result<BIOPointer, bool> EVPKeyPointer::writePrivateKey(
const PrivateKeyEncodingConfig& config) const {
if (config.format == PKFormatType::JWK) {
return Result<BIOPointer, bool>(false);
}
auto bio = BIOPointer::NewMem();
if (!bio) {
return Result<BIOPointer, bool>(false);
}
auto passphrase = GetPassphrase(config);
MarkPopErrorOnReturn mark_pop_error_on_return;
bool err;
switch (config.type) {
case PKEncodingType::PKCS1: {
// PKCS1 is only permitted for RSA keys.
if (id() != EVP_PKEY_RSA) return Result<BIOPointer, bool>(false);
#if OPENSSL_VERSION_MAJOR >= 3
const RSA* rsa = EVP_PKEY_get0_RSA(get());
#else
RSA* rsa = EVP_PKEY_get0_RSA(get());
#endif
switch (config.format) {
case PKFormatType::PEM: {
err = PEM_write_bio_RSAPrivateKey(bio.get(), rsa, config.cipher,
reinterpret_cast<unsigned char*>(passphrase.data),
passphrase.len, nullptr, nullptr) != 1;
break;
}
case PKFormatType::DER: {
// Encoding PKCS1 as DER. This variation does not permit encryption.
err = i2d_RSAPrivateKey_bio(bio.get(), rsa) != 1;
break;
}
default: {
// Should never get here.
return Result<BIOPointer, bool>(false);
}
}
break;
}
case PKEncodingType::PKCS8: {
switch (config.format) {
case PKFormatType::PEM: {
// Encode PKCS#8 as PEM.
err = PEM_write_bio_PKCS8PrivateKey(
bio.get(), get(),
config.cipher,
passphrase.data,
passphrase.len,
nullptr, nullptr) != 1;
break;
}
case PKFormatType::DER: {
err = i2d_PKCS8PrivateKey_bio(
bio.get(), get(),
config.cipher,
passphrase.data,
passphrase.len,
nullptr, nullptr) != 1;
break;
}
default: {
// Should never get here.
return Result<BIOPointer, bool>(false);
}
}
break;
}
case PKEncodingType::SEC1: {
// SEC1 is only permitted for EC keys
if (id() != EVP_PKEY_EC) return Result<BIOPointer, bool>(false);
#if OPENSSL_VERSION_MAJOR >= 3
const EC_KEY* ec = EVP_PKEY_get0_EC_KEY(get());
#else
EC_KEY* ec = EVP_PKEY_get0_EC_KEY(get());
#endif
switch (config.format) {
case PKFormatType::PEM: {
err = PEM_write_bio_ECPrivateKey(bio.get(),
ec,
config.cipher,
reinterpret_cast<unsigned char*>(passphrase.data),
passphrase.len,
nullptr,
nullptr) != 1;
break;
}
case PKFormatType::DER: {
// Encoding SEC1 as DER. This variation does not permit encryption.
err = i2d_ECPrivateKey_bio(bio.get(), ec) != 1;
break;
}
default: {
// Should never get here.
return Result<BIOPointer, bool>(false);
}
}
break;
}
default: {
// Not a valid private key encoding
return Result<BIOPointer, bool>(false);
}
}
if (err) {
// Failed to encode the private key.
return Result<BIOPointer, bool>(false, mark_pop_error_on_return.peekError());
}
return bio;
}
Result<BIOPointer, bool> EVPKeyPointer::writePublicKey(
const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig& config) const {
auto bio = BIOPointer::NewMem();
if (!bio) return Result<BIOPointer, bool>(false);
MarkPopErrorOnReturn mark_pop_error_on_return;
if (config.type == ncrypto::EVPKeyPointer::PKEncodingType::PKCS1) {
// PKCS#1 is only valid for RSA keys.
#if OPENSSL_VERSION_MAJOR >= 3
const RSA* rsa = EVP_PKEY_get0_RSA(get());
#else
RSA* rsa = EVP_PKEY_get0_RSA(get());
#endif
if (config.format == ncrypto::EVPKeyPointer::PKFormatType::PEM) {
// Encode PKCS#1 as PEM.
if (PEM_write_bio_RSAPublicKey(bio.get(), rsa) != 1) {
return Result<BIOPointer, bool>(false, mark_pop_error_on_return.peekError());
}
return bio;
}
// Encode PKCS#1 as DER.
if (i2d_RSAPublicKey_bio(bio.get(), rsa) != 1) {
return Result<BIOPointer, bool>(false, mark_pop_error_on_return.peekError());
}
return bio;
}
if (config.format == ncrypto::EVPKeyPointer::PKFormatType::PEM) {
// Encode SPKI as PEM.
if (PEM_write_bio_PUBKEY(bio.get(), get()) != 1) {
return Result<BIOPointer, bool>(false, mark_pop_error_on_return.peekError());
}
return bio;
}
// Encode SPKI as DER.
if (i2d_PUBKEY_bio(bio.get(), get()) != 1) {
return Result<BIOPointer, bool>(false, mark_pop_error_on_return.peekError());
}
return bio;
}
} // namespace ncrypto } // namespace ncrypto

View File

@ -386,13 +386,13 @@ public:
// SubjectPublicKeyInfo according to X.509. // SubjectPublicKeyInfo according to X.509.
SPKI, SPKI,
// ECPrivateKey according to SEC1. // ECPrivateKey according to SEC1.
SEC1 SEC1,
}; };
enum class PKFormatType { enum class PKFormatType {
DER, DER,
PEM, PEM,
JWK JWK,
}; };
enum class PKParseError { enum class PKParseError {
@ -402,18 +402,36 @@ public:
}; };
using ParseKeyResult = Result<EVPKeyPointer, PKParseError>; using ParseKeyResult = Result<EVPKeyPointer, PKParseError>;
struct AsymmetricKeyEncodingConfig {
bool output_key_object = false;
PKFormatType format = PKFormatType::DER;
PKEncodingType type = PKEncodingType::PKCS8;
AsymmetricKeyEncodingConfig() = default;
AsymmetricKeyEncodingConfig(bool output_key_object, PKFormatType format, PKEncodingType type);
AsymmetricKeyEncodingConfig(const AsymmetricKeyEncodingConfig&) = default;
AsymmetricKeyEncodingConfig& operator=(const AsymmetricKeyEncodingConfig&) = default;
};
using PublicKeyEncodingConfig = AsymmetricKeyEncodingConfig;
struct PrivateKeyEncodingConfig: public AsymmetricKeyEncodingConfig {
const EVP_CIPHER* cipher = nullptr;
std::optional<DataPointer> passphrase = std::nullopt;
PrivateKeyEncodingConfig() = default;
PrivateKeyEncodingConfig(bool output_key_object, PKFormatType format, PKEncodingType type)
: AsymmetricKeyEncodingConfig(output_key_object, format, type) {}
PrivateKeyEncodingConfig(const PrivateKeyEncodingConfig&);
PrivateKeyEncodingConfig& operator=(const PrivateKeyEncodingConfig&);
};
static ParseKeyResult TryParsePublicKey( static ParseKeyResult TryParsePublicKey(
PKFormatType format, const PublicKeyEncodingConfig& config,
PKEncodingType encoding,
const Buffer<const unsigned char>& buffer); const Buffer<const unsigned char>& buffer);
static ParseKeyResult TryParsePublicKeyPEM( static ParseKeyResult TryParsePublicKeyPEM(
const Buffer<const unsigned char>& buffer); const Buffer<const unsigned char>& buffer);
static ParseKeyResult TryParsePrivateKey( static ParseKeyResult TryParsePrivateKey(
PKFormatType format, const PrivateKeyEncodingConfig& config,
PKEncodingType encoding,
std::optional<Buffer<char>> passphrase,
const Buffer<const unsigned char>& buffer); const Buffer<const unsigned char>& buffer);
EVPKeyPointer() = default; EVPKeyPointer() = default;
@ -441,9 +459,11 @@ public:
size_t rawPrivateKeySize() const; size_t rawPrivateKeySize() const;
DataPointer rawPublicKey() const; DataPointer rawPublicKey() const;
DataPointer rawPrivateKey() const; DataPointer rawPrivateKey() const;
BIOPointer derPublicKey() const; BIOPointer derPublicKey() const;
Result<BIOPointer, bool> writePrivateKey(const PrivateKeyEncodingConfig& config) const;
Result<BIOPointer, bool> writePublicKey(const PublicKeyEncodingConfig& config) const;
EVPKeyCtxPointer newCtx() const; EVPKeyCtxPointer newCtx() const;
static bool IsRSAPrivateKey(const Buffer<const unsigned char>& buffer); static bool IsRSAPrivateKey(const Buffer<const unsigned char>& buffer);

View File

@ -48,8 +48,7 @@ Maybe<void> NidKeyPairGenTraits::AdditionalConfig(
EVPKeyCtxPointer NidKeyPairGenTraits::Setup(NidKeyPairGenConfig* params) { EVPKeyCtxPointer NidKeyPairGenTraits::Setup(NidKeyPairGenConfig* params) {
EVPKeyCtxPointer ctx = EVPKeyCtxPointer ctx =
EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(params->params.id, nullptr)); EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(params->params.id, nullptr));
if (!ctx || EVP_PKEY_keygen_init(ctx.get()) <= 0) if (!ctx || EVP_PKEY_keygen_init(ctx.get()) <= 0) return {};
return EVPKeyCtxPointer();
return ctx; return ctx;
} }

View File

@ -147,19 +147,16 @@ struct KeyPairGenTraits final {
// process input parameters. This allows each job to have a variable // process input parameters. This allows each job to have a variable
// number of input parameters specific to each job type. // number of input parameters specific to each job type.
if (KeyPairAlgorithmTraits::AdditionalConfig(mode, args, offset, params) if (KeyPairAlgorithmTraits::AdditionalConfig(mode, args, offset, params)
.IsNothing()) { .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::Nothing<void>();
} }
params->public_key_encoding = KeyObjectData::GetPublicKeyEncodingFromJs(
args, offset, kKeyContextGenerate);
auto private_key_encoding = KeyObjectData::GetPrivateKeyEncodingFromJs(
args, offset, kKeyContextGenerate);
if (!private_key_encoding.IsEmpty())
params->private_key_encoding = private_key_encoding.Release();
return v8::JustVoid(); return v8::JustVoid();
} }
@ -230,8 +227,8 @@ struct SecretKeyGenTraits final {
template <typename AlgorithmParams> template <typename AlgorithmParams>
struct KeyPairGenConfig final : public MemoryRetainer { struct KeyPairGenConfig final : public MemoryRetainer {
PublicKeyEncodingConfig public_key_encoding; ncrypto::EVPKeyPointer::PublicKeyEncodingConfig public_key_encoding;
PrivateKeyEncodingConfig private_key_encoding; ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig private_key_encoding;
KeyObjectData key; KeyObjectData key;
AlgorithmParams params; AlgorithmParams params;
@ -245,7 +242,7 @@ struct KeyPairGenConfig final : public MemoryRetainer {
explicit KeyPairGenConfig(KeyPairGenConfig&& other) noexcept explicit KeyPairGenConfig(KeyPairGenConfig&& other) noexcept
: public_key_encoding(other.public_key_encoding), : public_key_encoding(other.public_key_encoding),
private_key_encoding( private_key_encoding(
std::forward<PrivateKeyEncodingConfig>( std::forward<ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig>(
other.private_key_encoding)), other.private_key_encoding)),
key(std::move(other.key)), key(std::move(other.key)),
params(std::move(other.params)) {} params(std::move(other.params)) {}
@ -258,9 +255,10 @@ struct KeyPairGenConfig final : public MemoryRetainer {
void MemoryInfo(MemoryTracker* tracker) const override { void MemoryInfo(MemoryTracker* tracker) const override {
tracker->TrackField("key", key); tracker->TrackField("key", key);
if (!private_key_encoding.passphrase_.IsEmpty()) { if (private_key_encoding.passphrase.has_value()) {
auto& passphrase = private_key_encoding.passphrase.value();
tracker->TrackFieldWithSize("private_key_encoding.passphrase", tracker->TrackFieldWithSize("private_key_encoding.passphrase",
private_key_encoding.passphrase_->size()); passphrase.size());
} }
tracker->TrackField("params", params); tracker->TrackField("params", params);
} }

View File

@ -25,6 +25,7 @@ using v8::FunctionCallbackInfo;
using v8::FunctionTemplate; using v8::FunctionTemplate;
using v8::Int32; using v8::Int32;
using v8::Isolate; using v8::Isolate;
using v8::Just;
using v8::JustVoid; using v8::JustVoid;
using v8::Local; using v8::Local;
using v8::Maybe; using v8::Maybe;
@ -40,199 +41,86 @@ using v8::Value;
namespace crypto { namespace crypto {
namespace { namespace {
void GetKeyFormatAndTypeFromJs( Maybe<ncrypto::EVPKeyPointer::AsymmetricKeyEncodingConfig>
AsymmetricKeyEncodingConfig* config, GetKeyFormatAndTypeFromJs(const FunctionCallbackInfo<Value>& args,
const FunctionCallbackInfo<Value>& args, unsigned int* offset,
unsigned int* offset, KeyEncodingContext context) {
KeyEncodingContext context) { ncrypto::EVPKeyPointer::AsymmetricKeyEncodingConfig config;
// During key pair generation, it is possible not to specify a key encoding, // During key pair generation, it is possible not to specify a key encoding,
// which will lead to a key object being returned. // which will lead to a key object being returned.
if (args[*offset]->IsUndefined()) { if (args[*offset]->IsUndefined()) {
CHECK_EQ(context, kKeyContextGenerate); CHECK_EQ(context, kKeyContextGenerate);
CHECK(args[*offset + 1]->IsUndefined()); CHECK(args[*offset + 1]->IsUndefined());
config->output_key_object_ = true; config.output_key_object = true;
} else { } else {
config->output_key_object_ = false; config.output_key_object = false;
CHECK(args[*offset]->IsInt32()); CHECK(args[*offset]->IsInt32());
config->format_ = static_cast<PKFormatType>( config.format = static_cast<ncrypto::EVPKeyPointer::PKFormatType>(
args[*offset].As<Int32>()->Value()); args[*offset].As<Int32>()->Value());
if (args[*offset + 1]->IsInt32()) { if (args[*offset + 1]->IsInt32()) {
config->type_ = config.type = static_cast<ncrypto::EVPKeyPointer::PKEncodingType>(
static_cast<PKEncodingType>(args[*offset + 1].As<Int32>()->Value()); args[*offset + 1].As<Int32>()->Value());
} else { } else {
CHECK( CHECK((context == kKeyContextInput &&
(context == kKeyContextInput && config.format == ncrypto::EVPKeyPointer::PKFormatType::PEM) ||
config->format_ == kKeyFormatPEM) || (context == kKeyContextGenerate &&
(context == kKeyContextGenerate && config.format == ncrypto::EVPKeyPointer::PKFormatType::JWK));
config->format_ == kKeyFormatJWK));
CHECK(args[*offset + 1]->IsNullOrUndefined()); CHECK(args[*offset + 1]->IsNullOrUndefined());
config->type_ = std::nullopt; config.type = ncrypto::EVPKeyPointer::PKEncodingType::PKCS1;
} }
} }
*offset += 2; *offset += 2;
return Just(config);
} }
MaybeLocal<Value> BIOToStringOrBuffer(Environment* env, MaybeLocal<Value> BIOToStringOrBuffer(
const BIOPointer& bio, Environment* env,
PKFormatType format) { const BIOPointer& bio,
const ncrypto::EVPKeyPointer::AsymmetricKeyEncodingConfig& config) {
BUF_MEM* bptr = bio; BUF_MEM* bptr = bio;
if (format == kKeyFormatPEM) { if (config.format == ncrypto::EVPKeyPointer::PKFormatType::PEM) {
// PEM is an ASCII format, so we will return it as a string. // PEM is an ASCII format, so we will return it as a string.
return String::NewFromUtf8(env->isolate(), bptr->data, return String::NewFromUtf8(
NewStringType::kNormal, env->isolate(), bptr->data, NewStringType::kNormal, bptr->length)
bptr->length).FromMaybe(Local<Value>());
} else {
CHECK_EQ(format, kKeyFormatDER);
// DER is binary, return it as a buffer.
return Buffer::Copy(env, bptr->data, bptr->length)
.FromMaybe(Local<Value>()); .FromMaybe(Local<Value>());
} }
CHECK_EQ(config.format, ncrypto::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, MaybeLocal<Value> WritePrivateKey(
OSSL3_CONST EVP_PKEY* pkey, Environment* env,
const PrivateKeyEncodingConfig& config) { const EVPKeyPointer& pkey,
auto bio = BIOPointer::NewMem(); const ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig& config) {
CHECK(bio); CHECK(pkey);
auto res = pkey.writePrivateKey(config);
// If an empty string was passed as the passphrase, the ByteSource might if (res) {
// contain a null pointer, which OpenSSL will ignore, causing it to invoke its return BIOToStringOrBuffer(env, std::move(res.value), config);
// default passphrase callback, which would block the thread until the user
// manually enters a passphrase. We could supply our own passphrase callback
// to handle this special case, but it is easier to avoid passing a null
// pointer to OpenSSL.
char* pass = nullptr;
size_t pass_len = 0;
if (!config.passphrase_.IsEmpty()) {
pass = const_cast<char*>(config.passphrase_->data<char>());
pass_len = config.passphrase_->size();
if (pass == nullptr) {
// OpenSSL will not actually dereference this pointer, so it can be any
// non-null pointer. We cannot assert that directly, which is why we
// intentionally use a pointer that will likely cause a segmentation fault
// when dereferenced.
CHECK_EQ(pass_len, 0);
pass = reinterpret_cast<char*>(-1);
CHECK_NE(pass, nullptr);
}
} }
MarkPopErrorOnReturn mark_pop_error_on_return; ThrowCryptoError(
bool err; env, res.openssl_error.value_or(0), "Failed to encode private key");
return MaybeLocal<Value>();
PKEncodingType encoding_type = config.type_.value();
if (encoding_type == kKeyEncodingPKCS1) {
// PKCS#1 is only permitted for RSA keys.
CHECK_EQ(EVPKeyPointer::id(pkey), EVP_PKEY_RSA);
OSSL3_CONST RSA* rsa = EVP_PKEY_get0_RSA(pkey);
if (config.format_ == kKeyFormatPEM) {
// Encode PKCS#1 as PEM.
err = PEM_write_bio_RSAPrivateKey(bio.get(),
rsa,
config.cipher_,
reinterpret_cast<unsigned char*>(pass),
pass_len,
nullptr,
nullptr) != 1;
} else {
// Encode PKCS#1 as DER. This does not permit encryption.
CHECK_EQ(config.format_, kKeyFormatDER);
CHECK_NULL(config.cipher_);
err = i2d_RSAPrivateKey_bio(bio.get(), rsa) != 1;
}
} else if (encoding_type == kKeyEncodingPKCS8) {
if (config.format_ == kKeyFormatPEM) {
// Encode PKCS#8 as PEM.
err = PEM_write_bio_PKCS8PrivateKey(
bio.get(), pkey,
config.cipher_,
pass,
pass_len,
nullptr, nullptr) != 1;
} else {
// Encode PKCS#8 as DER.
CHECK_EQ(config.format_, kKeyFormatDER);
err = i2d_PKCS8PrivateKey_bio(
bio.get(), pkey,
config.cipher_,
pass,
pass_len,
nullptr, nullptr) != 1;
}
} else {
CHECK_EQ(encoding_type, kKeyEncodingSEC1);
// SEC1 is only permitted for EC keys.
CHECK_EQ(EVPKeyPointer::id(pkey), EVP_PKEY_EC);
OSSL3_CONST EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(pkey);
if (config.format_ == kKeyFormatPEM) {
// Encode SEC1 as PEM.
err = PEM_write_bio_ECPrivateKey(bio.get(),
ec_key,
config.cipher_,
reinterpret_cast<unsigned char*>(pass),
pass_len,
nullptr,
nullptr) != 1;
} else {
// Encode SEC1 as DER. This does not permit encryption.
CHECK_EQ(config.format_, kKeyFormatDER);
CHECK_NULL(config.cipher_);
err = i2d_ECPrivateKey_bio(bio.get(), ec_key) != 1;
}
}
if (err) {
ThrowCryptoError(env, ERR_get_error(), "Failed to encode private key");
return MaybeLocal<Value>();
}
return BIOToStringOrBuffer(env, bio, config.format_);
} }
bool WritePublicKeyInner(OSSL3_CONST EVP_PKEY* pkey, MaybeLocal<Value> WritePublicKey(
const BIOPointer& bio, Environment* env,
const PublicKeyEncodingConfig& config) { const EVPKeyPointer& pkey,
if (config.type_.value() == kKeyEncodingPKCS1) { const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig& config) {
// PKCS#1 is only valid for RSA keys. CHECK(pkey);
CHECK_EQ(EVPKeyPointer::id(pkey), EVP_PKEY_RSA); auto res = pkey.writePublicKey(config);
OSSL3_CONST RSA* rsa = EVP_PKEY_get0_RSA(pkey); if (res) {
if (config.format_ == kKeyFormatPEM) { return BIOToStringOrBuffer(env, res.value, config);
// Encode PKCS#1 as PEM.
return PEM_write_bio_RSAPublicKey(bio.get(), rsa) == 1;
} else {
// Encode PKCS#1 as DER.
CHECK_EQ(config.format_, kKeyFormatDER);
return i2d_RSAPublicKey_bio(bio.get(), rsa) == 1;
}
} else {
CHECK_EQ(config.type_.value(), kKeyEncodingSPKI);
if (config.format_ == kKeyFormatPEM) {
// Encode SPKI as PEM.
return PEM_write_bio_PUBKEY(bio.get(), pkey) == 1;
} else {
// Encode SPKI as DER.
CHECK_EQ(config.format_, kKeyFormatDER);
return i2d_PUBKEY_bio(bio.get(), pkey) == 1;
}
} }
}
MaybeLocal<Value> WritePublicKey(Environment* env, ThrowCryptoError(
OSSL3_CONST EVP_PKEY* pkey, env, res.openssl_error.value_or(0), "Failed to encode public key");
const PublicKeyEncodingConfig& config) { return MaybeLocal<Value>();
auto bio = BIOPointer::NewMem();
CHECK(bio);
if (!WritePublicKeyInner(pkey, bio, config)) {
ThrowCryptoError(env, ERR_get_error(), "Failed to encode public key");
return MaybeLocal<Value>();
}
return BIOToStringOrBuffer(env, bio, config.format_);
} }
Maybe<void> ExportJWKSecretKey(Environment* env, Maybe<void> ExportJWKSecretKey(Environment* env,
@ -247,12 +135,11 @@ Maybe<void> ExportJWKSecretKey(Environment* env,
key.GetSymmetricKeySize(), key.GetSymmetricKeySize(),
BASE64URL, BASE64URL,
&error); &error);
if (key_data.IsEmpty()) { if (!key_data.ToLocal(&raw)) {
CHECK(!error.IsEmpty()); CHECK(!error.IsEmpty());
env->isolate()->ThrowException(error); env->isolate()->ThrowException(error);
return Nothing<void>(); return Nothing<void>();
} }
if (!key_data.ToLocal(&raw)) return Nothing<void>();
if (target->Set( if (target->Set(
env->context(), env->context(),
@ -277,8 +164,8 @@ KeyObjectData ImportJWKSecretKey(Environment* env, Local<Object> jwk) {
} }
static_assert(String::kMaxLength <= INT_MAX); static_assert(String::kMaxLength <= INT_MAX);
auto key_data = ByteSource::FromEncodedString(env, key.As<String>()); return KeyObjectData::CreateSecret(
return KeyObjectData::CreateSecret(std::move(key_data)); ByteSource::FromEncodedString(env, key.As<String>()));
} }
Maybe<void> ExportJWKAsymmetricKey(Environment* env, Maybe<void> ExportJWKAsymmetricKey(Environment* env,
@ -290,7 +177,8 @@ Maybe<void> ExportJWKAsymmetricKey(Environment* env,
if (handleRsaPss) return ExportJWKRsaKey(env, key, target); if (handleRsaPss) return ExportJWKRsaKey(env, key, target);
break; break;
} }
case EVP_PKEY_RSA: return ExportJWKRsaKey(env, key, target); case EVP_PKEY_RSA:
return ExportJWKRsaKey(env, key, target);
case EVP_PKEY_EC: case EVP_PKEY_EC:
return ExportJWKEcKey(env, key, target); return ExportJWKEcKey(env, key, target);
case EVP_PKEY_ED25519: case EVP_PKEY_ED25519:
@ -299,7 +187,8 @@ Maybe<void> ExportJWKAsymmetricKey(Environment* env,
// Fall through // Fall through
case EVP_PKEY_X25519: case EVP_PKEY_X25519:
// Fall through // Fall through
case EVP_PKEY_X448: return ExportJWKEdKey(env, key, target); case EVP_PKEY_X448:
return ExportJWKEdKey(env, key, target);
} }
THROW_ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE(env); THROW_ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE(env);
return Nothing<void>(); return Nothing<void>();
@ -354,38 +243,22 @@ Maybe<void> GetAsymmetricKeyDetail(Environment* env,
KeyObjectData TryParsePrivateKey( KeyObjectData TryParsePrivateKey(
Environment* env, Environment* env,
const PrivateKeyEncodingConfig& config, const ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig& config,
const ncrypto::Buffer<const unsigned char>& buffer) { const ncrypto::Buffer<const unsigned char>& buffer) {
std::optional<ncrypto::Buffer<char>> maybePassphrase = std::nullopt; auto res = EVPKeyPointer::TryParsePrivateKey(config, buffer);
if (config.passphrase_.get() != nullptr) { if (res) {
maybePassphrase = ncrypto::Buffer<char>{ return KeyObjectData::CreateAsymmetric(KeyType::kKeyTypePrivate,
.data = const_cast<char*>(config.passphrase_->data<char>()), std::move(res.value));
.len = config.passphrase_->size(),
};
} }
auto res = EVPKeyPointer::TryParsePrivateKey( if (res.error.value() == EVPKeyPointer::PKParseError::NEED_PASSPHRASE) {
static_cast<EVPKeyPointer::PKFormatType>(config.format_), THROW_ERR_MISSING_PASSPHRASE(env, "Passphrase required for encrypted key");
static_cast<EVPKeyPointer::PKEncodingType>( } else {
config.type_.value_or(kKeyEncodingPKCS8)),
std::move(maybePassphrase),
buffer);
if (!res) {
if (res.error.value() == EVPKeyPointer::PKParseError::NEED_PASSPHRASE) {
THROW_ERR_MISSING_PASSPHRASE(env,
"Passphrase required for encrypted key");
return {};
}
ThrowCryptoError( ThrowCryptoError(
env, res.openssl_error.value_or(0), "Failed to read private key"); env, res.openssl_error.value_or(0), "Failed to read private key");
return {};
} }
return {};
return KeyObjectData::CreateAsymmetric(KeyType::kKeyTypePrivate,
std::move(res.value));
} }
} // namespace
// This maps true to JustVoid and false to Nothing<void>(). // This maps true to JustVoid and false to Nothing<void>().
static inline Maybe<void> NothingIfFalse(bool b) { static inline Maybe<void> NothingIfFalse(bool b) {
@ -408,58 +281,61 @@ Maybe<void> ExportJWKInner(Environment* env,
UNREACHABLE(); UNREACHABLE();
} }
} }
} // namespace
Maybe<void> KeyObjectData::ToEncodedPublicKey( Maybe<void> KeyObjectData::ToEncodedPublicKey(
Environment* env, Environment* env,
const PublicKeyEncodingConfig& config, const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig& config,
Local<Value>* out) { Local<Value>* out) {
CHECK(key_type_ != KeyType::kKeyTypeSecret); CHECK(key_type_ != KeyType::kKeyTypeSecret);
if (config.output_key_object_) { if (config.output_key_object) {
// Note that this has the downside of containing sensitive data of the // Note that this has the downside of containing sensitive data of the
// private key. // private key.
return NothingIfFalse( return NothingIfFalse(
KeyObjectHandle::Create(env, addRefWithType(KeyType::kKeyTypePublic)) KeyObjectHandle::Create(env, addRefWithType(KeyType::kKeyTypePublic))
.ToLocal(out)); .ToLocal(out));
} else if (config.format_ == kKeyFormatJWK) { } else if (config.format == ncrypto::EVPKeyPointer::PKFormatType::JWK) {
*out = Object::New(env->isolate()); *out = Object::New(env->isolate());
return ExportJWKInner( return ExportJWKInner(
env, addRefWithType(KeyType::kKeyTypePublic), *out, false); env, addRefWithType(KeyType::kKeyTypePublic), *out, false);
} }
return NothingIfFalse( return NothingIfFalse(
WritePublicKey(env, GetAsymmetricKey().get(), config).ToLocal(out)); WritePublicKey(env, GetAsymmetricKey(), config).ToLocal(out));
} }
Maybe<void> KeyObjectData::ToEncodedPrivateKey( Maybe<void> KeyObjectData::ToEncodedPrivateKey(
Environment* env, Environment* env,
const PrivateKeyEncodingConfig& config, const ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig& config,
Local<Value>* out) { Local<Value>* out) {
CHECK(key_type_ != KeyType::kKeyTypeSecret); CHECK(key_type_ != KeyType::kKeyTypeSecret);
if (config.output_key_object_) { if (config.output_key_object) {
return NothingIfFalse( return NothingIfFalse(
KeyObjectHandle::Create(env, addRefWithType(KeyType::kKeyTypePrivate)) KeyObjectHandle::Create(env, addRefWithType(KeyType::kKeyTypePrivate))
.ToLocal(out)); .ToLocal(out));
} else if (config.format_ == kKeyFormatJWK) { } else if (config.format == ncrypto::EVPKeyPointer::PKFormatType::JWK) {
*out = Object::New(env->isolate()); *out = Object::New(env->isolate());
return ExportJWKInner( return ExportJWKInner(
env, addRefWithType(KeyType::kKeyTypePrivate), *out, false); env, addRefWithType(KeyType::kKeyTypePrivate), *out, false);
} }
return NothingIfFalse( return NothingIfFalse(
WritePrivateKey(env, GetAsymmetricKey().get(), config).ToLocal(out)); WritePrivateKey(env, GetAsymmetricKey(), config).ToLocal(out));
} }
NonCopyableMaybe<PrivateKeyEncodingConfig> Maybe<ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig>
KeyObjectData::GetPrivateKeyEncodingFromJs( KeyObjectData::GetPrivateKeyEncodingFromJs(
const FunctionCallbackInfo<Value>& args, const FunctionCallbackInfo<Value>& args,
unsigned int* offset, unsigned int* offset,
KeyEncodingContext context) { KeyEncodingContext context) {
Environment* env = Environment::GetCurrent(args); Environment* env = Environment::GetCurrent(args);
PrivateKeyEncodingConfig result; ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig config;
GetKeyFormatAndTypeFromJs(&result, args, offset, context); if (!GetKeyFormatAndTypeFromJs(args, offset, context).To(&config)) {
return Nothing<ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig>();
}
if (result.output_key_object_) { if (config.output_key_object) {
if (context != kKeyContextInput) if (context != kKeyContextInput)
(*offset)++; (*offset)++;
} else { } else {
@ -467,44 +343,43 @@ KeyObjectData::GetPrivateKeyEncodingFromJs(
if (context != kKeyContextInput) { if (context != kKeyContextInput) {
if (args[*offset]->IsString()) { if (args[*offset]->IsString()) {
Utf8Value cipher_name(env->isolate(), args[*offset]); Utf8Value cipher_name(env->isolate(), args[*offset]);
result.cipher_ = EVP_get_cipherbyname(*cipher_name); config.cipher = EVP_get_cipherbyname(*cipher_name);
if (result.cipher_ == nullptr) { if (config.cipher == nullptr) {
THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env); THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env);
return NonCopyableMaybe<PrivateKeyEncodingConfig>(); return Nothing<ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig>();
} }
needs_passphrase = true; needs_passphrase = true;
} else { } else {
CHECK(args[*offset]->IsNullOrUndefined()); CHECK(args[*offset]->IsNullOrUndefined());
result.cipher_ = nullptr; config.cipher = nullptr;
} }
(*offset)++; (*offset)++;
} }
if (IsAnyBufferSource(args[*offset])) { if (IsAnyBufferSource(args[*offset])) {
CHECK_IMPLIES(context != kKeyContextInput, result.cipher_ != nullptr); CHECK_IMPLIES(context != kKeyContextInput, config.cipher != nullptr);
ArrayBufferOrViewContents<char> passphrase(args[*offset]); ArrayBufferOrViewContents<char> passphrase(args[*offset]);
if (!passphrase.CheckSizeInt32()) [[unlikely]] { if (!passphrase.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "passphrase is too big"); THROW_ERR_OUT_OF_RANGE(env, "passphrase is too big");
return NonCopyableMaybe<PrivateKeyEncodingConfig>(); return Nothing<ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig>();
} }
result.passphrase_ = NonCopyableMaybe<ByteSource>( config.passphrase = passphrase.ToDataPointer();
passphrase.ToNullTerminatedCopy());
} else { } else {
CHECK(args[*offset]->IsNullOrUndefined() && !needs_passphrase); CHECK(args[*offset]->IsNullOrUndefined() && !needs_passphrase);
} }
} }
(*offset)++; (*offset)++;
return NonCopyableMaybe<PrivateKeyEncodingConfig>(std::move(result)); return Just<ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig>(
std::move(config));
} }
PublicKeyEncodingConfig KeyObjectData::GetPublicKeyEncodingFromJs( Maybe<ncrypto::EVPKeyPointer::PublicKeyEncodingConfig>
KeyObjectData::GetPublicKeyEncodingFromJs(
const FunctionCallbackInfo<Value>& args, const FunctionCallbackInfo<Value>& args,
unsigned int* offset, unsigned int* offset,
KeyEncodingContext context) { KeyEncodingContext context) {
PublicKeyEncodingConfig result; return GetKeyFormatAndTypeFromJs(args, offset, context);
GetKeyFormatAndTypeFromJs(&result, args, offset, context);
return result;
} }
KeyObjectData KeyObjectData::GetPrivateKeyFromJs( KeyObjectData KeyObjectData::GetPrivateKeyFromJs(
@ -513,14 +388,17 @@ KeyObjectData KeyObjectData::GetPrivateKeyFromJs(
bool allow_key_object) { bool allow_key_object) {
if (args[*offset]->IsString() || IsAnyBufferSource(args[*offset])) { if (args[*offset]->IsString() || IsAnyBufferSource(args[*offset])) {
Environment* env = Environment::GetCurrent(args); Environment* env = Environment::GetCurrent(args);
ByteSource key = ByteSource::FromStringOrBuffer(env, args[(*offset)++]); auto key = ByteSource::FromStringOrBuffer(env, args[(*offset)++]);
NonCopyableMaybe<PrivateKeyEncodingConfig> config =
GetPrivateKeyEncodingFromJs(args, offset, kKeyContextInput); ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig config;
if (!GetPrivateKeyEncodingFromJs(args, offset, kKeyContextInput)
.To(&config)) {
return {};
}
if (config.IsEmpty()) return {};
return TryParsePrivateKey( return TryParsePrivateKey(
env, env,
config.Release(), config,
ncrypto::Buffer<const unsigned char>{ ncrypto::Buffer<const unsigned char>{
.data = reinterpret_cast<const unsigned char*>(key.data()), .data = reinterpret_cast<const unsigned char*>(key.data()),
.len = key.size(), .len = key.size(),
@ -544,70 +422,62 @@ KeyObjectData KeyObjectData::GetPublicOrPrivateKeyFromJs(
THROW_ERR_OUT_OF_RANGE(env, "keyData is too big"); THROW_ERR_OUT_OF_RANGE(env, "keyData is too big");
return {}; return {};
} }
NonCopyableMaybe<PrivateKeyEncodingConfig> config_ =
KeyObjectData::GetPrivateKeyEncodingFromJs( ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig config;
args, offset, kKeyContextInput); if (!KeyObjectData::GetPrivateKeyEncodingFromJs(
if (config_.IsEmpty()) return {}; args, offset, kKeyContextInput)
PrivateKeyEncodingConfig config = config_.Release(); .To(&config)) {
return {};
}
ncrypto::Buffer<const unsigned char> buffer = { ncrypto::Buffer<const unsigned char> buffer = {
.data = reinterpret_cast<const unsigned char*>(data.data()), .data = reinterpret_cast<const unsigned char*>(data.data()),
.len = data.size(), .len = data.size(),
}; };
std::optional<ncrypto::Buffer<char>> maybePassphrase = std::nullopt; if (config.format == ncrypto::EVPKeyPointer::PKFormatType::PEM) {
if (config.passphrase_.get() != nullptr) {
maybePassphrase = ncrypto::Buffer<char>{
.data = const_cast<char*>(config.passphrase_->data<char>()),
.len = config.passphrase_->size(),
};
}
if (config.format_ == kKeyFormatPEM) {
// For PEM, we can easily determine whether it is a public or private key // For PEM, we can easily determine whether it is a public or private key
// by looking for the respective PEM tags. // by looking for the respective PEM tags.
auto res = EVPKeyPointer::TryParsePublicKeyPEM(buffer); auto res = EVPKeyPointer::TryParsePublicKeyPEM(buffer);
if (!res) { if (res) {
if (res.error.value() == EVPKeyPointer::PKParseError::NOT_RECOGNIZED) { return CreateAsymmetric(kKeyTypePublic, std::move(res.value));
return TryParsePrivateKey(env, config, buffer);
}
ThrowCryptoError(env,
res.openssl_error.value_or(0),
"Failed to read asymmetric key");
return {};
} }
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 // 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. // easy, but PKCS#1 can be a public key or a private key.
bool is_public = ([&] { static const auto is_public = [](const auto& config,
switch (config.type_.value()) { const auto& buffer) -> bool {
case kKeyEncodingPKCS1: switch (config.type) {
case ncrypto::EVPKeyPointer::PKEncodingType::PKCS1:
return !EVPKeyPointer::IsRSAPrivateKey(buffer); return !EVPKeyPointer::IsRSAPrivateKey(buffer);
case kKeyEncodingSPKI: case ncrypto::EVPKeyPointer::PKEncodingType::SPKI:
return true; return true;
case kKeyEncodingPKCS8: case ncrypto::EVPKeyPointer::PKEncodingType::PKCS8:
return false; return false;
case kKeyEncodingSEC1: case ncrypto::EVPKeyPointer::PKEncodingType::SEC1:
return false; return false;
default: default:
UNREACHABLE("Invalid key encoding type"); UNREACHABLE("Invalid key encoding type");
} }
})(); };
if (is_public) { if (is_public(config, buffer)) {
auto res = EVPKeyPointer::TryParsePublicKey( auto res = EVPKeyPointer::TryParsePublicKey(config, buffer);
static_cast<EVPKeyPointer::PKFormatType>(config.format_), if (res) {
static_cast<EVPKeyPointer::PKEncodingType>(config.type_.value()), return CreateAsymmetric(KeyType::kKeyTypePublic, std::move(res.value));
buffer);
if (!res) {
ThrowCryptoError(env,
res.openssl_error.value_or(0),
"Failed to read asymmetric key");
return {};
} }
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); return TryParsePrivateKey(env, config, buffer);
@ -1120,20 +990,25 @@ void KeyObjectHandle::Export(const FunctionCallbackInfo<Value>& args) {
result = key->ExportSecretKey(); result = key->ExportSecretKey();
} else if (type == kKeyTypePublic) { } else if (type == kKeyTypePublic) {
unsigned int offset = 0; unsigned int offset = 0;
PublicKeyEncodingConfig config = KeyObjectData::GetPublicKeyEncodingFromJs( ncrypto::EVPKeyPointer::PublicKeyEncodingConfig config;
args, &offset, kKeyContextExport); if (!KeyObjectData::GetPublicKeyEncodingFromJs(
args, &offset, kKeyContextExport)
.To(&config)) {
return;
}
CHECK_EQ(offset, static_cast<unsigned int>(args.Length())); CHECK_EQ(offset, static_cast<unsigned int>(args.Length()));
result = key->ExportPublicKey(config); result = key->ExportPublicKey(config);
} else { } else {
CHECK_EQ(type, kKeyTypePrivate); CHECK_EQ(type, kKeyTypePrivate);
unsigned int offset = 0; unsigned int offset = 0;
NonCopyableMaybe<PrivateKeyEncodingConfig> config = ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig config;
KeyObjectData::GetPrivateKeyEncodingFromJs( if (!KeyObjectData::GetPrivateKeyEncodingFromJs(
args, &offset, kKeyContextExport); args, &offset, kKeyContextExport)
if (config.IsEmpty()) .To(&config)) {
return; return;
}
CHECK_EQ(offset, static_cast<unsigned int>(args.Length())); CHECK_EQ(offset, static_cast<unsigned int>(args.Length()));
result = key->ExportPrivateKey(config.Release()); result = key->ExportPrivateKey(config);
} }
if (!result.IsEmpty()) if (!result.IsEmpty())
@ -1147,13 +1022,13 @@ MaybeLocal<Value> KeyObjectHandle::ExportSecretKey() const {
} }
MaybeLocal<Value> KeyObjectHandle::ExportPublicKey( MaybeLocal<Value> KeyObjectHandle::ExportPublicKey(
const PublicKeyEncodingConfig& config) const { const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig& config) const {
return WritePublicKey(env(), data_.GetAsymmetricKey().get(), config); return WritePublicKey(env(), data_.GetAsymmetricKey(), config);
} }
MaybeLocal<Value> KeyObjectHandle::ExportPrivateKey( MaybeLocal<Value> KeyObjectHandle::ExportPrivateKey(
const PrivateKeyEncodingConfig& config) const { const ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig& config) const {
return WritePrivateKey(env(), data_.GetAsymmetricKey().get(), config); return WritePrivateKey(env(), data_.GetAsymmetricKey(), config);
} }
void KeyObjectHandle::ExportJWK( void KeyObjectHandle::ExportJWK(
@ -1308,11 +1183,25 @@ void Initialize(Environment* env, Local<Object> target) {
FIXED_ONE_BYTE_STRING(env->isolate(), "KeyObjectHandle"), FIXED_ONE_BYTE_STRING(env->isolate(), "KeyObjectHandle"),
KeyObjectHandle::Initialize(env)).Check(); KeyObjectHandle::Initialize(env)).Check();
constexpr int kKeyEncodingPKCS1 =
static_cast<int>(ncrypto::EVPKeyPointer::PKEncodingType::PKCS1);
constexpr int kKeyEncodingPKCS8 =
static_cast<int>(ncrypto::EVPKeyPointer::PKEncodingType::PKCS8);
constexpr int kKeyEncodingSPKI =
static_cast<int>(ncrypto::EVPKeyPointer::PKEncodingType::SPKI);
constexpr int kKeyEncodingSEC1 =
static_cast<int>(ncrypto::EVPKeyPointer::PKEncodingType::SEC1);
constexpr int kKeyFormatDER =
static_cast<int>(ncrypto::EVPKeyPointer::PKFormatType::DER);
constexpr int kKeyFormatPEM =
static_cast<int>(ncrypto::EVPKeyPointer::PKFormatType::PEM);
constexpr int kKeyFormatJWK =
static_cast<int>(ncrypto::EVPKeyPointer::PKFormatType::JWK);
NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatRaw); NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatRaw);
NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatPKCS8); NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatPKCS8);
NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatSPKI); NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatSPKI);
NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatJWK); NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatJWK);
NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED25519); NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED25519);
NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED448); NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED448);
NODE_DEFINE_CONSTANT(target, EVP_PKEY_X25519); NODE_DEFINE_CONSTANT(target, EVP_PKEY_X25519);

View File

@ -18,24 +18,6 @@
namespace node { namespace node {
namespace crypto { namespace crypto {
// TODO(@jasnell): These static casts are temporarily while this code
// is being shifted over into ncrypto
enum PKEncodingType {
// RSAPublicKey / RSAPrivateKey according to PKCS#1.
kKeyEncodingPKCS1 = static_cast<int>(EVPKeyPointer::PKEncodingType::PKCS1),
// PrivateKeyInfo or EncryptedPrivateKeyInfo according to PKCS#8.
kKeyEncodingPKCS8 = static_cast<int>(EVPKeyPointer::PKEncodingType::PKCS8),
// SubjectPublicKeyInfo according to X.509.
kKeyEncodingSPKI = static_cast<int>(EVPKeyPointer::PKEncodingType::SPKI),
// ECPrivateKey according to SEC1.
kKeyEncodingSEC1 = static_cast<int>(EVPKeyPointer::PKEncodingType::SEC1),
};
enum PKFormatType {
kKeyFormatDER = static_cast<int>(EVPKeyPointer::PKFormatType::DER),
kKeyFormatPEM = static_cast<int>(EVPKeyPointer::PKFormatType::PEM),
kKeyFormatJWK = static_cast<int>(EVPKeyPointer::PKFormatType::JWK),
};
enum KeyType { enum KeyType {
kKeyTypeSecret, kKeyTypeSecret,
@ -58,22 +40,6 @@ enum class ParseKeyResult {
kParseKeyOk, kParseKeyOk,
}; };
struct AsymmetricKeyEncodingConfig {
bool output_key_object_ = false;
PKFormatType format_ = kKeyFormatDER;
std::optional<PKEncodingType> type_ = std::nullopt;
};
using PublicKeyEncodingConfig = AsymmetricKeyEncodingConfig;
struct PrivateKeyEncodingConfig : public AsymmetricKeyEncodingConfig {
const EVP_CIPHER* cipher_;
// The ByteSource alone is not enough to distinguish between "no passphrase"
// and a zero-length passphrase (which can be a null pointer), therefore, we
// use a NonCopyableMaybe.
NonCopyableMaybe<ByteSource> passphrase_;
};
// Objects of this class can safely be shared among threads. // Objects of this class can safely be shared among threads.
class KeyObjectData final : public MemoryRetainer { class KeyObjectData final : public MemoryRetainer {
public: public:
@ -99,10 +65,10 @@ class KeyObjectData final : public MemoryRetainer {
Mutex& mutex() const; Mutex& mutex() const;
static PublicKeyEncodingConfig GetPublicKeyEncodingFromJs( static v8::Maybe<ncrypto::EVPKeyPointer::PublicKeyEncodingConfig>
const v8::FunctionCallbackInfo<v8::Value>& args, GetPublicKeyEncodingFromJs(const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int* offset, unsigned int* offset,
KeyEncodingContext context); KeyEncodingContext context);
static KeyObjectData GetPrivateKeyFromJs( static KeyObjectData GetPrivateKeyFromJs(
const v8::FunctionCallbackInfo<v8::Value>& args, const v8::FunctionCallbackInfo<v8::Value>& args,
@ -112,18 +78,20 @@ class KeyObjectData final : public MemoryRetainer {
static KeyObjectData GetPublicOrPrivateKeyFromJs( static KeyObjectData GetPublicOrPrivateKeyFromJs(
const v8::FunctionCallbackInfo<v8::Value>& args, unsigned int* offset); const v8::FunctionCallbackInfo<v8::Value>& args, unsigned int* offset);
static NonCopyableMaybe<PrivateKeyEncodingConfig> GetPrivateKeyEncodingFromJs( static v8::Maybe<ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig>
const v8::FunctionCallbackInfo<v8::Value>& args, GetPrivateKeyEncodingFromJs(const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int* offset, unsigned int* offset,
KeyEncodingContext context); KeyEncodingContext context);
v8::Maybe<void> ToEncodedPublicKey(Environment* env, v8::Maybe<void> ToEncodedPublicKey(
const PublicKeyEncodingConfig& config, Environment* env,
v8::Local<v8::Value>* out); const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig& config,
v8::Local<v8::Value>* out);
v8::Maybe<void> ToEncodedPrivateKey(Environment* env, v8::Maybe<void> ToEncodedPrivateKey(
const PrivateKeyEncodingConfig& config, Environment* env,
v8::Local<v8::Value>* out); const ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig& config,
v8::Local<v8::Value>* out);
inline KeyObjectData addRef() const { inline KeyObjectData addRef() const {
return KeyObjectData(key_type_, mutex_, data_); return KeyObjectData(key_type_, mutex_, data_);
@ -204,9 +172,9 @@ class KeyObjectHandle : public BaseObject {
v8::MaybeLocal<v8::Value> ExportSecretKey() const; v8::MaybeLocal<v8::Value> ExportSecretKey() const;
v8::MaybeLocal<v8::Value> ExportPublicKey( v8::MaybeLocal<v8::Value> ExportPublicKey(
const PublicKeyEncodingConfig& config) const; const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig& config) const;
v8::MaybeLocal<v8::Value> ExportPrivateKey( v8::MaybeLocal<v8::Value> ExportPrivateKey(
const PrivateKeyEncodingConfig& config) const; const ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig& config) const;
KeyObjectHandle(Environment* env, KeyObjectHandle(Environment* env,
v8::Local<v8::Object> wrap); v8::Local<v8::Object> wrap);

View File

@ -689,12 +689,22 @@ class ArrayBufferOrViewContents {
return std::move(buf).release(size()); return std::move(buf).release(size());
} }
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> template <typename M>
void CopyTo(M* dest, size_t len) const { void CopyTo(M* dest, size_t len) const {
static_assert(sizeof(M) == 1, "sizeof(M) must equal 1"); static_assert(sizeof(M) == 1, "sizeof(M) must equal 1");
len = std::min(len, size()); len = std::min(len, size());
if (len > 0 && data() != nullptr) if (len > 0 && data() != nullptr) {
memcpy(dest, data(), len); memcpy(dest, data(), len);
}
} }
private: private: