mirror of https://github.com/nodejs/node.git
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:
parent
91bce94010
commit
5b9bf39b47
|
@ -80,7 +80,7 @@ std::optional<std::string> CryptoErrorList::pop_front() {
|
|||
|
||||
// ============================================================================
|
||||
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)
|
||||
|
@ -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() {
|
||||
return EVPKeyPointer(EVP_PKEY_new());
|
||||
}
|
||||
|
@ -1661,14 +1688,13 @@ EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePublicKeyPEM(
|
|||
}
|
||||
|
||||
EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePublicKey(
|
||||
PKFormatType format,
|
||||
PKEncodingType encoding,
|
||||
const PublicKeyEncodingConfig& config,
|
||||
const Buffer<const unsigned char>& buffer) {
|
||||
if (format == PKFormatType::PEM) {
|
||||
if (config.format == PKFormatType::PEM) {
|
||||
return TryParsePublicKeyPEM(buffer);
|
||||
}
|
||||
|
||||
if (format != PKFormatType::DER) {
|
||||
if (config.format != PKFormatType::DER) {
|
||||
return ParseKeyResult(PKParseError::FAILED);
|
||||
}
|
||||
|
||||
|
@ -1676,12 +1702,12 @@ EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePublicKey(
|
|||
|
||||
EVP_PKEY* key = nullptr;
|
||||
|
||||
if (encoding == PKEncodingType::PKCS1 &&
|
||||
if (config.type == PKEncodingType::PKCS1 &&
|
||||
(key = d2i_PublicKey(EVP_PKEY_RSA, nullptr, &start, buffer.len))) {
|
||||
return EVPKeyPointer::ParseKeyResult(EVPKeyPointer(key));
|
||||
}
|
||||
|
||||
if (encoding == PKEncodingType::SPKI &&
|
||||
if (config.type == PKEncodingType::SPKI &&
|
||||
(key = d2i_PUBKEY(nullptr, &start, buffer.len))) {
|
||||
return EVPKeyPointer::ParseKeyResult(EVPKeyPointer(key));
|
||||
}
|
||||
|
@ -1689,13 +1715,34 @@ EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePublicKey(
|
|||
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(
|
||||
PKFormatType format,
|
||||
PKEncodingType encoding,
|
||||
std::optional<Buffer<char>> maybe_passphrase,
|
||||
const PrivateKeyEncodingConfig& config,
|
||||
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 (ERR_GET_LIB(err) == ERR_LIB_PEM &&
|
||||
ERR_GET_REASON(err) == PEM_R_BAD_PASSWORD_READ &&
|
||||
|
@ -1708,24 +1755,23 @@ EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePrivateKey(
|
|||
return ParseKeyResult(std::move(pkey));
|
||||
};
|
||||
|
||||
Buffer<char>* passphrase = nullptr;
|
||||
if (maybe_passphrase.has_value()) {
|
||||
passphrase = &maybe_passphrase.value();
|
||||
}
|
||||
|
||||
auto bio = BIOPointer::New(buffer);
|
||||
if (!bio) return ParseKeyResult(PKParseError::FAILED);
|
||||
|
||||
if (format == PKFormatType::PEM) {
|
||||
auto key = PEM_read_bio_PrivateKey(bio.get(), nullptr, PasswordCallback, passphrase);
|
||||
return keyOrError(EVPKeyPointer(key), maybe_passphrase.has_value());
|
||||
auto passphrase = GetPassphrase(config);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
switch (encoding) {
|
||||
switch (config.type) {
|
||||
case PKEncodingType::PKCS1: {
|
||||
auto key = d2i_PrivateKey_bio(bio.get(), nullptr);
|
||||
return keyOrError(EVPKeyPointer(key));
|
||||
|
@ -1735,8 +1781,8 @@ EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePrivateKey(
|
|||
auto key = d2i_PKCS8PrivateKey_bio(bio.get(),
|
||||
nullptr,
|
||||
PasswordCallback,
|
||||
passphrase);
|
||||
return keyOrError(EVPKeyPointer(key), maybe_passphrase.has_value());
|
||||
config.passphrase.has_value() ? &passphrase : nullptr);
|
||||
return keyOrError(EVPKeyPointer(key), config.passphrase.has_value());
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -386,13 +386,13 @@ public:
|
|||
// SubjectPublicKeyInfo according to X.509.
|
||||
SPKI,
|
||||
// ECPrivateKey according to SEC1.
|
||||
SEC1
|
||||
SEC1,
|
||||
};
|
||||
|
||||
enum class PKFormatType {
|
||||
DER,
|
||||
PEM,
|
||||
JWK
|
||||
JWK,
|
||||
};
|
||||
|
||||
enum class PKParseError {
|
||||
|
@ -402,18 +402,36 @@ public:
|
|||
};
|
||||
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(
|
||||
PKFormatType format,
|
||||
PKEncodingType encoding,
|
||||
const PublicKeyEncodingConfig& config,
|
||||
const Buffer<const unsigned char>& buffer);
|
||||
|
||||
static ParseKeyResult TryParsePublicKeyPEM(
|
||||
const Buffer<const unsigned char>& buffer);
|
||||
|
||||
static ParseKeyResult TryParsePrivateKey(
|
||||
PKFormatType format,
|
||||
PKEncodingType encoding,
|
||||
std::optional<Buffer<char>> passphrase,
|
||||
const PrivateKeyEncodingConfig& config,
|
||||
const Buffer<const unsigned char>& buffer);
|
||||
|
||||
EVPKeyPointer() = default;
|
||||
|
@ -441,9 +459,11 @@ public:
|
|||
size_t rawPrivateKeySize() const;
|
||||
DataPointer rawPublicKey() const;
|
||||
DataPointer rawPrivateKey() const;
|
||||
|
||||
BIOPointer derPublicKey() const;
|
||||
|
||||
Result<BIOPointer, bool> writePrivateKey(const PrivateKeyEncodingConfig& config) const;
|
||||
Result<BIOPointer, bool> writePublicKey(const PublicKeyEncodingConfig& config) const;
|
||||
|
||||
EVPKeyCtxPointer newCtx() const;
|
||||
|
||||
static bool IsRSAPrivateKey(const Buffer<const unsigned char>& buffer);
|
||||
|
|
|
@ -48,8 +48,7 @@ Maybe<void> NidKeyPairGenTraits::AdditionalConfig(
|
|||
EVPKeyCtxPointer NidKeyPairGenTraits::Setup(NidKeyPairGenConfig* params) {
|
||||
EVPKeyCtxPointer ctx =
|
||||
EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(params->params.id, nullptr));
|
||||
if (!ctx || EVP_PKEY_keygen_init(ctx.get()) <= 0)
|
||||
return EVPKeyCtxPointer();
|
||||
if (!ctx || EVP_PKEY_keygen_init(ctx.get()) <= 0) return {};
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
|
|
@ -147,19 +147,16 @@ struct KeyPairGenTraits final {
|
|||
// process input parameters. This allows each job to have a variable
|
||||
// number of input parameters specific to each job type.
|
||||
if (KeyPairAlgorithmTraits::AdditionalConfig(mode, args, offset, params)
|
||||
.IsNothing()) {
|
||||
.IsNothing() ||
|
||||
!KeyObjectData::GetPublicKeyEncodingFromJs(
|
||||
args, offset, kKeyContextGenerate)
|
||||
.To(¶ms->public_key_encoding) ||
|
||||
!KeyObjectData::GetPrivateKeyEncodingFromJs(
|
||||
args, offset, kKeyContextGenerate)
|
||||
.To(¶ms->private_key_encoding)) {
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -230,8 +227,8 @@ struct SecretKeyGenTraits final {
|
|||
|
||||
template <typename AlgorithmParams>
|
||||
struct KeyPairGenConfig final : public MemoryRetainer {
|
||||
PublicKeyEncodingConfig public_key_encoding;
|
||||
PrivateKeyEncodingConfig private_key_encoding;
|
||||
ncrypto::EVPKeyPointer::PublicKeyEncodingConfig public_key_encoding;
|
||||
ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig private_key_encoding;
|
||||
KeyObjectData key;
|
||||
AlgorithmParams params;
|
||||
|
||||
|
@ -245,7 +242,7 @@ struct KeyPairGenConfig final : public MemoryRetainer {
|
|||
explicit KeyPairGenConfig(KeyPairGenConfig&& other) noexcept
|
||||
: public_key_encoding(other.public_key_encoding),
|
||||
private_key_encoding(
|
||||
std::forward<PrivateKeyEncodingConfig>(
|
||||
std::forward<ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig>(
|
||||
other.private_key_encoding)),
|
||||
key(std::move(other.key)),
|
||||
params(std::move(other.params)) {}
|
||||
|
@ -258,9 +255,10 @@ struct KeyPairGenConfig final : public MemoryRetainer {
|
|||
|
||||
void MemoryInfo(MemoryTracker* tracker) const override {
|
||||
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",
|
||||
private_key_encoding.passphrase_->size());
|
||||
passphrase.size());
|
||||
}
|
||||
tracker->TrackField("params", params);
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ using v8::FunctionCallbackInfo;
|
|||
using v8::FunctionTemplate;
|
||||
using v8::Int32;
|
||||
using v8::Isolate;
|
||||
using v8::Just;
|
||||
using v8::JustVoid;
|
||||
using v8::Local;
|
||||
using v8::Maybe;
|
||||
|
@ -40,199 +41,86 @@ using v8::Value;
|
|||
|
||||
namespace crypto {
|
||||
namespace {
|
||||
void GetKeyFormatAndTypeFromJs(
|
||||
AsymmetricKeyEncodingConfig* config,
|
||||
const FunctionCallbackInfo<Value>& args,
|
||||
unsigned int* offset,
|
||||
KeyEncodingContext context) {
|
||||
Maybe<ncrypto::EVPKeyPointer::AsymmetricKeyEncodingConfig>
|
||||
GetKeyFormatAndTypeFromJs(const FunctionCallbackInfo<Value>& args,
|
||||
unsigned int* offset,
|
||||
KeyEncodingContext context) {
|
||||
ncrypto::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;
|
||||
config.output_key_object = true;
|
||||
} else {
|
||||
config->output_key_object_ = false;
|
||||
config.output_key_object = false;
|
||||
|
||||
CHECK(args[*offset]->IsInt32());
|
||||
config->format_ = static_cast<PKFormatType>(
|
||||
config.format = static_cast<ncrypto::EVPKeyPointer::PKFormatType>(
|
||||
args[*offset].As<Int32>()->Value());
|
||||
|
||||
if (args[*offset + 1]->IsInt32()) {
|
||||
config->type_ =
|
||||
static_cast<PKEncodingType>(args[*offset + 1].As<Int32>()->Value());
|
||||
config.type = static_cast<ncrypto::EVPKeyPointer::PKEncodingType>(
|
||||
args[*offset + 1].As<Int32>()->Value());
|
||||
} else {
|
||||
CHECK(
|
||||
(context == kKeyContextInput &&
|
||||
config->format_ == kKeyFormatPEM) ||
|
||||
(context == kKeyContextGenerate &&
|
||||
config->format_ == kKeyFormatJWK));
|
||||
CHECK((context == kKeyContextInput &&
|
||||
config.format == ncrypto::EVPKeyPointer::PKFormatType::PEM) ||
|
||||
(context == kKeyContextGenerate &&
|
||||
config.format == ncrypto::EVPKeyPointer::PKFormatType::JWK));
|
||||
CHECK(args[*offset + 1]->IsNullOrUndefined());
|
||||
config->type_ = std::nullopt;
|
||||
config.type = ncrypto::EVPKeyPointer::PKEncodingType::PKCS1;
|
||||
}
|
||||
}
|
||||
|
||||
*offset += 2;
|
||||
return Just(config);
|
||||
}
|
||||
|
||||
MaybeLocal<Value> BIOToStringOrBuffer(Environment* env,
|
||||
const BIOPointer& bio,
|
||||
PKFormatType format) {
|
||||
MaybeLocal<Value> BIOToStringOrBuffer(
|
||||
Environment* env,
|
||||
const BIOPointer& bio,
|
||||
const ncrypto::EVPKeyPointer::AsymmetricKeyEncodingConfig& config) {
|
||||
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.
|
||||
return String::NewFromUtf8(env->isolate(), bptr->data,
|
||||
NewStringType::kNormal,
|
||||
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)
|
||||
return String::NewFromUtf8(
|
||||
env->isolate(), bptr->data, NewStringType::kNormal, bptr->length)
|
||||
.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,
|
||||
OSSL3_CONST EVP_PKEY* pkey,
|
||||
const PrivateKeyEncodingConfig& config) {
|
||||
auto bio = BIOPointer::NewMem();
|
||||
CHECK(bio);
|
||||
|
||||
// If an empty string was passed as the passphrase, the ByteSource might
|
||||
// contain a null pointer, which OpenSSL will ignore, causing it to invoke its
|
||||
// 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);
|
||||
}
|
||||
MaybeLocal<Value> WritePrivateKey(
|
||||
Environment* env,
|
||||
const EVPKeyPointer& pkey,
|
||||
const ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig& config) {
|
||||
CHECK(pkey);
|
||||
auto res = pkey.writePrivateKey(config);
|
||||
if (res) {
|
||||
return BIOToStringOrBuffer(env, std::move(res.value), config);
|
||||
}
|
||||
|
||||
MarkPopErrorOnReturn mark_pop_error_on_return;
|
||||
bool err;
|
||||
|
||||
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_);
|
||||
ThrowCryptoError(
|
||||
env, res.openssl_error.value_or(0), "Failed to encode private key");
|
||||
return MaybeLocal<Value>();
|
||||
}
|
||||
|
||||
bool WritePublicKeyInner(OSSL3_CONST EVP_PKEY* pkey,
|
||||
const BIOPointer& bio,
|
||||
const PublicKeyEncodingConfig& config) {
|
||||
if (config.type_.value() == kKeyEncodingPKCS1) {
|
||||
// PKCS#1 is only valid 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.
|
||||
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,
|
||||
const EVPKeyPointer& pkey,
|
||||
const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig& config) {
|
||||
CHECK(pkey);
|
||||
auto res = pkey.writePublicKey(config);
|
||||
if (res) {
|
||||
return BIOToStringOrBuffer(env, res.value, config);
|
||||
}
|
||||
}
|
||||
|
||||
MaybeLocal<Value> WritePublicKey(Environment* env,
|
||||
OSSL3_CONST EVP_PKEY* pkey,
|
||||
const PublicKeyEncodingConfig& config) {
|
||||
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_);
|
||||
ThrowCryptoError(
|
||||
env, res.openssl_error.value_or(0), "Failed to encode public key");
|
||||
return MaybeLocal<Value>();
|
||||
}
|
||||
|
||||
Maybe<void> ExportJWKSecretKey(Environment* env,
|
||||
|
@ -247,12 +135,11 @@ Maybe<void> ExportJWKSecretKey(Environment* env,
|
|||
key.GetSymmetricKeySize(),
|
||||
BASE64URL,
|
||||
&error);
|
||||
if (key_data.IsEmpty()) {
|
||||
if (!key_data.ToLocal(&raw)) {
|
||||
CHECK(!error.IsEmpty());
|
||||
env->isolate()->ThrowException(error);
|
||||
return Nothing<void>();
|
||||
}
|
||||
if (!key_data.ToLocal(&raw)) return Nothing<void>();
|
||||
|
||||
if (target->Set(
|
||||
env->context(),
|
||||
|
@ -277,8 +164,8 @@ KeyObjectData ImportJWKSecretKey(Environment* env, Local<Object> jwk) {
|
|||
}
|
||||
|
||||
static_assert(String::kMaxLength <= INT_MAX);
|
||||
auto key_data = ByteSource::FromEncodedString(env, key.As<String>());
|
||||
return KeyObjectData::CreateSecret(std::move(key_data));
|
||||
return KeyObjectData::CreateSecret(
|
||||
ByteSource::FromEncodedString(env, key.As<String>()));
|
||||
}
|
||||
|
||||
Maybe<void> ExportJWKAsymmetricKey(Environment* env,
|
||||
|
@ -290,7 +177,8 @@ Maybe<void> ExportJWKAsymmetricKey(Environment* env,
|
|||
if (handleRsaPss) return ExportJWKRsaKey(env, key, target);
|
||||
break;
|
||||
}
|
||||
case EVP_PKEY_RSA: return ExportJWKRsaKey(env, key, target);
|
||||
case EVP_PKEY_RSA:
|
||||
return ExportJWKRsaKey(env, key, target);
|
||||
case EVP_PKEY_EC:
|
||||
return ExportJWKEcKey(env, key, target);
|
||||
case EVP_PKEY_ED25519:
|
||||
|
@ -299,7 +187,8 @@ Maybe<void> ExportJWKAsymmetricKey(Environment* env,
|
|||
// Fall through
|
||||
case EVP_PKEY_X25519:
|
||||
// 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);
|
||||
return Nothing<void>();
|
||||
|
@ -354,38 +243,22 @@ Maybe<void> GetAsymmetricKeyDetail(Environment* env,
|
|||
|
||||
KeyObjectData TryParsePrivateKey(
|
||||
Environment* env,
|
||||
const PrivateKeyEncodingConfig& config,
|
||||
const ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig& config,
|
||||
const ncrypto::Buffer<const unsigned char>& buffer) {
|
||||
std::optional<ncrypto::Buffer<char>> maybePassphrase = std::nullopt;
|
||||
if (config.passphrase_.get() != nullptr) {
|
||||
maybePassphrase = ncrypto::Buffer<char>{
|
||||
.data = const_cast<char*>(config.passphrase_->data<char>()),
|
||||
.len = config.passphrase_->size(),
|
||||
};
|
||||
auto res = EVPKeyPointer::TryParsePrivateKey(config, buffer);
|
||||
if (res) {
|
||||
return KeyObjectData::CreateAsymmetric(KeyType::kKeyTypePrivate,
|
||||
std::move(res.value));
|
||||
}
|
||||
|
||||
auto res = EVPKeyPointer::TryParsePrivateKey(
|
||||
static_cast<EVPKeyPointer::PKFormatType>(config.format_),
|
||||
static_cast<EVPKeyPointer::PKEncodingType>(
|
||||
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 {};
|
||||
}
|
||||
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 {};
|
||||
}
|
||||
|
||||
return KeyObjectData::CreateAsymmetric(KeyType::kKeyTypePrivate,
|
||||
std::move(res.value));
|
||||
return {};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// This maps true to JustVoid and false to Nothing<void>().
|
||||
static inline Maybe<void> NothingIfFalse(bool b) {
|
||||
|
@ -408,58 +281,61 @@ Maybe<void> ExportJWKInner(Environment* env,
|
|||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Maybe<void> KeyObjectData::ToEncodedPublicKey(
|
||||
Environment* env,
|
||||
const PublicKeyEncodingConfig& config,
|
||||
const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig& config,
|
||||
Local<Value>* out) {
|
||||
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
|
||||
// private key.
|
||||
return NothingIfFalse(
|
||||
KeyObjectHandle::Create(env, addRefWithType(KeyType::kKeyTypePublic))
|
||||
.ToLocal(out));
|
||||
} else if (config.format_ == kKeyFormatJWK) {
|
||||
} else if (config.format == ncrypto::EVPKeyPointer::PKFormatType::JWK) {
|
||||
*out = Object::New(env->isolate());
|
||||
return ExportJWKInner(
|
||||
env, addRefWithType(KeyType::kKeyTypePublic), *out, false);
|
||||
}
|
||||
|
||||
return NothingIfFalse(
|
||||
WritePublicKey(env, GetAsymmetricKey().get(), config).ToLocal(out));
|
||||
WritePublicKey(env, GetAsymmetricKey(), config).ToLocal(out));
|
||||
}
|
||||
|
||||
Maybe<void> KeyObjectData::ToEncodedPrivateKey(
|
||||
Environment* env,
|
||||
const PrivateKeyEncodingConfig& config,
|
||||
const ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig& config,
|
||||
Local<Value>* out) {
|
||||
CHECK(key_type_ != KeyType::kKeyTypeSecret);
|
||||
if (config.output_key_object_) {
|
||||
if (config.output_key_object) {
|
||||
return NothingIfFalse(
|
||||
KeyObjectHandle::Create(env, addRefWithType(KeyType::kKeyTypePrivate))
|
||||
.ToLocal(out));
|
||||
} else if (config.format_ == kKeyFormatJWK) {
|
||||
} else if (config.format == ncrypto::EVPKeyPointer::PKFormatType::JWK) {
|
||||
*out = Object::New(env->isolate());
|
||||
return ExportJWKInner(
|
||||
env, addRefWithType(KeyType::kKeyTypePrivate), *out, false);
|
||||
}
|
||||
|
||||
return NothingIfFalse(
|
||||
WritePrivateKey(env, GetAsymmetricKey().get(), config).ToLocal(out));
|
||||
WritePrivateKey(env, GetAsymmetricKey(), config).ToLocal(out));
|
||||
}
|
||||
|
||||
NonCopyableMaybe<PrivateKeyEncodingConfig>
|
||||
Maybe<ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig>
|
||||
KeyObjectData::GetPrivateKeyEncodingFromJs(
|
||||
const FunctionCallbackInfo<Value>& args,
|
||||
unsigned int* offset,
|
||||
KeyEncodingContext context) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
|
||||
PrivateKeyEncodingConfig result;
|
||||
GetKeyFormatAndTypeFromJs(&result, args, offset, context);
|
||||
ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig config;
|
||||
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)
|
||||
(*offset)++;
|
||||
} else {
|
||||
|
@ -467,44 +343,43 @@ KeyObjectData::GetPrivateKeyEncodingFromJs(
|
|||
if (context != kKeyContextInput) {
|
||||
if (args[*offset]->IsString()) {
|
||||
Utf8Value cipher_name(env->isolate(), args[*offset]);
|
||||
result.cipher_ = EVP_get_cipherbyname(*cipher_name);
|
||||
if (result.cipher_ == nullptr) {
|
||||
config.cipher = EVP_get_cipherbyname(*cipher_name);
|
||||
if (config.cipher == nullptr) {
|
||||
THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env);
|
||||
return NonCopyableMaybe<PrivateKeyEncodingConfig>();
|
||||
return Nothing<ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig>();
|
||||
}
|
||||
needs_passphrase = true;
|
||||
} else {
|
||||
CHECK(args[*offset]->IsNullOrUndefined());
|
||||
result.cipher_ = nullptr;
|
||||
config.cipher = nullptr;
|
||||
}
|
||||
(*offset)++;
|
||||
}
|
||||
|
||||
if (IsAnyBufferSource(args[*offset])) {
|
||||
CHECK_IMPLIES(context != kKeyContextInput, result.cipher_ != nullptr);
|
||||
CHECK_IMPLIES(context != kKeyContextInput, config.cipher != nullptr);
|
||||
ArrayBufferOrViewContents<char> passphrase(args[*offset]);
|
||||
if (!passphrase.CheckSizeInt32()) [[unlikely]] {
|
||||
THROW_ERR_OUT_OF_RANGE(env, "passphrase is too big");
|
||||
return NonCopyableMaybe<PrivateKeyEncodingConfig>();
|
||||
return Nothing<ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig>();
|
||||
}
|
||||
result.passphrase_ = NonCopyableMaybe<ByteSource>(
|
||||
passphrase.ToNullTerminatedCopy());
|
||||
config.passphrase = passphrase.ToDataPointer();
|
||||
} else {
|
||||
CHECK(args[*offset]->IsNullOrUndefined() && !needs_passphrase);
|
||||
}
|
||||
}
|
||||
|
||||
(*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,
|
||||
unsigned int* offset,
|
||||
KeyEncodingContext context) {
|
||||
PublicKeyEncodingConfig result;
|
||||
GetKeyFormatAndTypeFromJs(&result, args, offset, context);
|
||||
return result;
|
||||
return GetKeyFormatAndTypeFromJs(args, offset, context);
|
||||
}
|
||||
|
||||
KeyObjectData KeyObjectData::GetPrivateKeyFromJs(
|
||||
|
@ -513,14 +388,17 @@ KeyObjectData KeyObjectData::GetPrivateKeyFromJs(
|
|||
bool allow_key_object) {
|
||||
if (args[*offset]->IsString() || IsAnyBufferSource(args[*offset])) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
ByteSource key = ByteSource::FromStringOrBuffer(env, args[(*offset)++]);
|
||||
NonCopyableMaybe<PrivateKeyEncodingConfig> config =
|
||||
GetPrivateKeyEncodingFromJs(args, offset, kKeyContextInput);
|
||||
auto key = ByteSource::FromStringOrBuffer(env, args[(*offset)++]);
|
||||
|
||||
ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig config;
|
||||
if (!GetPrivateKeyEncodingFromJs(args, offset, kKeyContextInput)
|
||||
.To(&config)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (config.IsEmpty()) return {};
|
||||
return TryParsePrivateKey(
|
||||
env,
|
||||
config.Release(),
|
||||
config,
|
||||
ncrypto::Buffer<const unsigned char>{
|
||||
.data = reinterpret_cast<const unsigned char*>(key.data()),
|
||||
.len = key.size(),
|
||||
|
@ -544,70 +422,62 @@ KeyObjectData KeyObjectData::GetPublicOrPrivateKeyFromJs(
|
|||
THROW_ERR_OUT_OF_RANGE(env, "keyData is too big");
|
||||
return {};
|
||||
}
|
||||
NonCopyableMaybe<PrivateKeyEncodingConfig> config_ =
|
||||
KeyObjectData::GetPrivateKeyEncodingFromJs(
|
||||
args, offset, kKeyContextInput);
|
||||
if (config_.IsEmpty()) return {};
|
||||
PrivateKeyEncodingConfig config = config_.Release();
|
||||
|
||||
ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig config;
|
||||
if (!KeyObjectData::GetPrivateKeyEncodingFromJs(
|
||||
args, offset, kKeyContextInput)
|
||||
.To(&config)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ncrypto::Buffer<const unsigned char> buffer = {
|
||||
.data = reinterpret_cast<const unsigned char*>(data.data()),
|
||||
.len = data.size(),
|
||||
};
|
||||
|
||||
std::optional<ncrypto::Buffer<char>> maybePassphrase = std::nullopt;
|
||||
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) {
|
||||
if (config.format == ncrypto::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) {
|
||||
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 {};
|
||||
if (res) {
|
||||
return CreateAsymmetric(kKeyTypePublic, std::move(res.value));
|
||||
}
|
||||
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.
|
||||
bool is_public = ([&] {
|
||||
switch (config.type_.value()) {
|
||||
case kKeyEncodingPKCS1:
|
||||
static const auto is_public = [](const auto& config,
|
||||
const auto& buffer) -> bool {
|
||||
switch (config.type) {
|
||||
case ncrypto::EVPKeyPointer::PKEncodingType::PKCS1:
|
||||
return !EVPKeyPointer::IsRSAPrivateKey(buffer);
|
||||
case kKeyEncodingSPKI:
|
||||
case ncrypto::EVPKeyPointer::PKEncodingType::SPKI:
|
||||
return true;
|
||||
case kKeyEncodingPKCS8:
|
||||
case ncrypto::EVPKeyPointer::PKEncodingType::PKCS8:
|
||||
return false;
|
||||
case kKeyEncodingSEC1:
|
||||
case ncrypto::EVPKeyPointer::PKEncodingType::SEC1:
|
||||
return false;
|
||||
default:
|
||||
UNREACHABLE("Invalid key encoding type");
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
if (is_public) {
|
||||
auto res = EVPKeyPointer::TryParsePublicKey(
|
||||
static_cast<EVPKeyPointer::PKFormatType>(config.format_),
|
||||
static_cast<EVPKeyPointer::PKEncodingType>(config.type_.value()),
|
||||
buffer);
|
||||
if (!res) {
|
||||
ThrowCryptoError(env,
|
||||
res.openssl_error.value_or(0),
|
||||
"Failed to read asymmetric key");
|
||||
return {};
|
||||
if (is_public(config, buffer)) {
|
||||
auto res = EVPKeyPointer::TryParsePublicKey(config, buffer);
|
||||
if (res) {
|
||||
return CreateAsymmetric(KeyType::kKeyTypePublic, std::move(res.value));
|
||||
}
|
||||
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);
|
||||
|
@ -1120,20 +990,25 @@ void KeyObjectHandle::Export(const FunctionCallbackInfo<Value>& args) {
|
|||
result = key->ExportSecretKey();
|
||||
} else if (type == kKeyTypePublic) {
|
||||
unsigned int offset = 0;
|
||||
PublicKeyEncodingConfig config = KeyObjectData::GetPublicKeyEncodingFromJs(
|
||||
args, &offset, kKeyContextExport);
|
||||
ncrypto::EVPKeyPointer::PublicKeyEncodingConfig config;
|
||||
if (!KeyObjectData::GetPublicKeyEncodingFromJs(
|
||||
args, &offset, kKeyContextExport)
|
||||
.To(&config)) {
|
||||
return;
|
||||
}
|
||||
CHECK_EQ(offset, static_cast<unsigned int>(args.Length()));
|
||||
result = key->ExportPublicKey(config);
|
||||
} else {
|
||||
CHECK_EQ(type, kKeyTypePrivate);
|
||||
unsigned int offset = 0;
|
||||
NonCopyableMaybe<PrivateKeyEncodingConfig> config =
|
||||
KeyObjectData::GetPrivateKeyEncodingFromJs(
|
||||
args, &offset, kKeyContextExport);
|
||||
if (config.IsEmpty())
|
||||
ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig config;
|
||||
if (!KeyObjectData::GetPrivateKeyEncodingFromJs(
|
||||
args, &offset, kKeyContextExport)
|
||||
.To(&config)) {
|
||||
return;
|
||||
}
|
||||
CHECK_EQ(offset, static_cast<unsigned int>(args.Length()));
|
||||
result = key->ExportPrivateKey(config.Release());
|
||||
result = key->ExportPrivateKey(config);
|
||||
}
|
||||
|
||||
if (!result.IsEmpty())
|
||||
|
@ -1147,13 +1022,13 @@ MaybeLocal<Value> KeyObjectHandle::ExportSecretKey() const {
|
|||
}
|
||||
|
||||
MaybeLocal<Value> KeyObjectHandle::ExportPublicKey(
|
||||
const PublicKeyEncodingConfig& config) const {
|
||||
return WritePublicKey(env(), data_.GetAsymmetricKey().get(), config);
|
||||
const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig& config) const {
|
||||
return WritePublicKey(env(), data_.GetAsymmetricKey(), config);
|
||||
}
|
||||
|
||||
MaybeLocal<Value> KeyObjectHandle::ExportPrivateKey(
|
||||
const PrivateKeyEncodingConfig& config) const {
|
||||
return WritePrivateKey(env(), data_.GetAsymmetricKey().get(), config);
|
||||
const ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig& config) const {
|
||||
return WritePrivateKey(env(), data_.GetAsymmetricKey(), config);
|
||||
}
|
||||
|
||||
void KeyObjectHandle::ExportJWK(
|
||||
|
@ -1308,11 +1183,25 @@ void Initialize(Environment* env, Local<Object> target) {
|
|||
FIXED_ONE_BYTE_STRING(env->isolate(), "KeyObjectHandle"),
|
||||
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, kWebCryptoKeyFormatPKCS8);
|
||||
NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatSPKI);
|
||||
NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatJWK);
|
||||
|
||||
NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED25519);
|
||||
NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED448);
|
||||
NODE_DEFINE_CONSTANT(target, EVP_PKEY_X25519);
|
||||
|
|
|
@ -18,24 +18,6 @@
|
|||
|
||||
namespace node {
|
||||
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 {
|
||||
kKeyTypeSecret,
|
||||
|
@ -58,22 +40,6 @@ enum class ParseKeyResult {
|
|||
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.
|
||||
class KeyObjectData final : public MemoryRetainer {
|
||||
public:
|
||||
|
@ -99,10 +65,10 @@ class KeyObjectData final : public MemoryRetainer {
|
|||
|
||||
Mutex& mutex() const;
|
||||
|
||||
static PublicKeyEncodingConfig GetPublicKeyEncodingFromJs(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args,
|
||||
unsigned int* offset,
|
||||
KeyEncodingContext context);
|
||||
static v8::Maybe<ncrypto::EVPKeyPointer::PublicKeyEncodingConfig>
|
||||
GetPublicKeyEncodingFromJs(const v8::FunctionCallbackInfo<v8::Value>& args,
|
||||
unsigned int* offset,
|
||||
KeyEncodingContext context);
|
||||
|
||||
static KeyObjectData GetPrivateKeyFromJs(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args,
|
||||
|
@ -112,18 +78,20 @@ class KeyObjectData final : public MemoryRetainer {
|
|||
static KeyObjectData GetPublicOrPrivateKeyFromJs(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args, unsigned int* offset);
|
||||
|
||||
static NonCopyableMaybe<PrivateKeyEncodingConfig> GetPrivateKeyEncodingFromJs(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args,
|
||||
unsigned int* offset,
|
||||
KeyEncodingContext context);
|
||||
static v8::Maybe<ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig>
|
||||
GetPrivateKeyEncodingFromJs(const v8::FunctionCallbackInfo<v8::Value>& args,
|
||||
unsigned int* offset,
|
||||
KeyEncodingContext context);
|
||||
|
||||
v8::Maybe<void> ToEncodedPublicKey(Environment* env,
|
||||
const PublicKeyEncodingConfig& config,
|
||||
v8::Local<v8::Value>* out);
|
||||
v8::Maybe<void> ToEncodedPublicKey(
|
||||
Environment* env,
|
||||
const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig& config,
|
||||
v8::Local<v8::Value>* out);
|
||||
|
||||
v8::Maybe<void> ToEncodedPrivateKey(Environment* env,
|
||||
const PrivateKeyEncodingConfig& config,
|
||||
v8::Local<v8::Value>* out);
|
||||
v8::Maybe<void> ToEncodedPrivateKey(
|
||||
Environment* env,
|
||||
const ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig& config,
|
||||
v8::Local<v8::Value>* out);
|
||||
|
||||
inline KeyObjectData addRef() const {
|
||||
return KeyObjectData(key_type_, mutex_, data_);
|
||||
|
@ -204,9 +172,9 @@ class KeyObjectHandle : public BaseObject {
|
|||
|
||||
v8::MaybeLocal<v8::Value> ExportSecretKey() const;
|
||||
v8::MaybeLocal<v8::Value> ExportPublicKey(
|
||||
const PublicKeyEncodingConfig& config) const;
|
||||
const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig& config) const;
|
||||
v8::MaybeLocal<v8::Value> ExportPrivateKey(
|
||||
const PrivateKeyEncodingConfig& config) const;
|
||||
const ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig& config) const;
|
||||
|
||||
KeyObjectHandle(Environment* env,
|
||||
v8::Local<v8::Object> wrap);
|
||||
|
|
|
@ -689,12 +689,22 @@ class ArrayBufferOrViewContents {
|
|||
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>
|
||||
void CopyTo(M* dest, size_t len) const {
|
||||
static_assert(sizeof(M) == 1, "sizeof(M) must equal 1");
|
||||
len = std::min(len, size());
|
||||
if (len > 0 && data() != nullptr)
|
||||
if (len > 0 && data() != nullptr) {
|
||||
memcpy(dest, data(), len);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
Loading…
Reference in New Issue