Skip to content

Commit 53f63d4

Browse files
committed
crypto: recognize raw formats in keygen
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
1 parent f3633ef commit 53f63d4

File tree

4 files changed

+488
-1
lines changed

4 files changed

+488
-1
lines changed

deps/ncrypto/ncrypto.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,9 @@ class EVPKeyPointer final {
858858
DER,
859859
PEM,
860860
JWK,
861+
RAW_PUBLIC,
862+
RAW_PRIVATE,
863+
RAW_SEED,
861864
};
862865

863866
enum class PKParseError { NOT_RECOGNIZED, NEED_PASSPHRASE, FAILED };
@@ -867,6 +870,7 @@ class EVPKeyPointer final {
867870
bool output_key_object = false;
868871
PKFormatType format = PKFormatType::DER;
869872
PKEncodingType type = PKEncodingType::PKCS8;
873+
int ec_point_form = POINT_CONVERSION_UNCOMPRESSED;
870874
AsymmetricKeyEncodingConfig() = default;
871875
AsymmetricKeyEncodingConfig(bool output_key_object,
872876
PKFormatType format,

lib/internal/crypto/keys.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ const {
2121
kKeyFormatPEM,
2222
kKeyFormatDER,
2323
kKeyFormatJWK,
24+
kKeyFormatRawPublic,
25+
kKeyFormatRawPrivate,
26+
kKeyFormatRawSeed,
2427
kKeyEncodingPKCS1,
2528
kKeyEncodingPKCS8,
2629
kKeyEncodingSPKI,
@@ -433,6 +436,12 @@ function parseKeyFormat(formatStr, defaultFormat, optionName) {
433436
return kKeyFormatDER;
434437
else if (formatStr === 'jwk')
435438
return kKeyFormatJWK;
439+
else if (formatStr === 'raw-public')
440+
return kKeyFormatRawPublic;
441+
else if (formatStr === 'raw-private')
442+
return kKeyFormatRawPrivate;
443+
else if (formatStr === 'raw-seed')
444+
return kKeyFormatRawSeed;
436445
throw new ERR_INVALID_ARG_VALUE(optionName, formatStr);
437446
}
438447

@@ -473,6 +482,33 @@ function parseKeyFormatAndType(enc, keyType, isPublic, objName) {
473482
isInput ? kKeyFormatPEM : undefined,
474483
option('format', objName));
475484

485+
if (format === kKeyFormatRawPublic) {
486+
if (isPublic === false) {
487+
throw new ERR_INVALID_ARG_VALUE(option('format', objName), 'raw-public');
488+
}
489+
let type;
490+
if (typeStr === undefined || typeStr === 'uncompressed') {
491+
type = POINT_CONVERSION_UNCOMPRESSED;
492+
} else if (typeStr === 'compressed') {
493+
type = POINT_CONVERSION_COMPRESSED;
494+
} else {
495+
throw new ERR_INVALID_ARG_VALUE(option('type', objName), typeStr);
496+
}
497+
return { format, type };
498+
}
499+
500+
if (format === kKeyFormatRawPrivate || format === kKeyFormatRawSeed) {
501+
if (isPublic === true) {
502+
throw new ERR_INVALID_ARG_VALUE(
503+
option('format', objName),
504+
format === kKeyFormatRawPrivate ? 'raw-private' : 'raw-seed');
505+
}
506+
if (typeStr !== undefined) {
507+
throw new ERR_INVALID_ARG_VALUE(option('type', objName), typeStr);
508+
}
509+
return { format };
510+
}
511+
476512
const isRequired = (!isInput ||
477513
format === kKeyFormatDER) &&
478514
format !== kKeyFormatJWK;
@@ -504,6 +540,14 @@ function parseKeyEncoding(enc, keyType, isPublic, objName) {
504540
if (isPublic !== true) {
505541
({ cipher, passphrase, encoding } = enc);
506542

543+
if (format === kKeyFormatRawPrivate || format === kKeyFormatRawSeed) {
544+
if (cipher != null || passphrase !== undefined) {
545+
throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
546+
'raw format', 'does not support encryption');
547+
}
548+
return { format, type };
549+
}
550+
507551
if (!isInput) {
508552
if (cipher != null) {
509553
if (typeof cipher !== 'string')

src/crypto/crypto_keys.cc

Lines changed: 155 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,16 @@ Maybe<EVPKeyPointer::AsymmetricKeyEncodingConfig> GetKeyFormatAndTypeFromJs(
6666
config.format = static_cast<EVPKeyPointer::PKFormatType>(
6767
args[*offset].As<Int32>()->Value());
6868

69-
if (args[*offset + 1]->IsInt32()) {
69+
if (config.format == EVPKeyPointer::PKFormatType::RAW_PUBLIC ||
70+
config.format == EVPKeyPointer::PKFormatType::RAW_PRIVATE ||
71+
config.format == EVPKeyPointer::PKFormatType::RAW_SEED) {
72+
// Raw formats use the type slot for ec_point_form (int) or null.
73+
if (args[*offset + 1]->IsInt32()) {
74+
config.ec_point_form = args[*offset + 1].As<Int32>()->Value();
75+
} else {
76+
CHECK(args[*offset + 1]->IsNullOrUndefined());
77+
}
78+
} else if (args[*offset + 1]->IsInt32()) {
7079
config.type = static_cast<EVPKeyPointer::PKEncodingType>(
7180
args[*offset + 1].As<Int32>()->Value());
7281
} else {
@@ -329,6 +338,54 @@ bool KeyObjectData::ToEncodedPublicKey(
329338
*out = Object::New(env->isolate());
330339
return ExportJWKInner(
331340
env, addRefWithType(KeyType::kKeyTypePublic), *out, false);
341+
} else if (config.format == EVPKeyPointer::PKFormatType::RAW_PUBLIC) {
342+
Mutex::ScopedLock lock(mutex());
343+
const auto& pkey = GetAsymmetricKey();
344+
if (pkey.id() == EVP_PKEY_EC) {
345+
const EC_KEY* ec_key = pkey;
346+
CHECK_NOT_NULL(ec_key);
347+
auto form = static_cast<point_conversion_form_t>(config.ec_point_form);
348+
const auto group = ECKeyPointer::GetGroup(ec_key);
349+
const auto point = ECKeyPointer::GetPublicKey(ec_key);
350+
return ECPointToBuffer(env, group, point, form).ToLocal(out);
351+
}
352+
switch (pkey.id()) {
353+
case EVP_PKEY_ED25519:
354+
case EVP_PKEY_ED448:
355+
case EVP_PKEY_X25519:
356+
case EVP_PKEY_X448:
357+
#if OPENSSL_WITH_PQC
358+
case EVP_PKEY_ML_DSA_44:
359+
case EVP_PKEY_ML_DSA_65:
360+
case EVP_PKEY_ML_DSA_87:
361+
case EVP_PKEY_ML_KEM_512:
362+
case EVP_PKEY_ML_KEM_768:
363+
case EVP_PKEY_ML_KEM_1024:
364+
case EVP_PKEY_SLH_DSA_SHA2_128F:
365+
case EVP_PKEY_SLH_DSA_SHA2_128S:
366+
case EVP_PKEY_SLH_DSA_SHA2_192F:
367+
case EVP_PKEY_SLH_DSA_SHA2_192S:
368+
case EVP_PKEY_SLH_DSA_SHA2_256F:
369+
case EVP_PKEY_SLH_DSA_SHA2_256S:
370+
case EVP_PKEY_SLH_DSA_SHAKE_128F:
371+
case EVP_PKEY_SLH_DSA_SHAKE_128S:
372+
case EVP_PKEY_SLH_DSA_SHAKE_192F:
373+
case EVP_PKEY_SLH_DSA_SHAKE_192S:
374+
case EVP_PKEY_SLH_DSA_SHAKE_256F:
375+
case EVP_PKEY_SLH_DSA_SHAKE_256S:
376+
#endif
377+
break;
378+
default:
379+
THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
380+
return false;
381+
}
382+
auto raw_data = pkey.rawPublicKey();
383+
if (!raw_data) {
384+
THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get raw public key");
385+
return false;
386+
}
387+
return Buffer::Copy(env, raw_data.get<const char>(), raw_data.size())
388+
.ToLocal(out);
332389
}
333390

334391
return WritePublicKey(env, GetAsymmetricKey(), config).ToLocal(out);
@@ -347,6 +404,86 @@ bool KeyObjectData::ToEncodedPrivateKey(
347404
*out = Object::New(env->isolate());
348405
return ExportJWKInner(
349406
env, addRefWithType(KeyType::kKeyTypePrivate), *out, false);
407+
} else if (config.format == EVPKeyPointer::PKFormatType::RAW_PRIVATE) {
408+
Mutex::ScopedLock lock(mutex());
409+
const auto& pkey = GetAsymmetricKey();
410+
if (pkey.id() == EVP_PKEY_EC) {
411+
const EC_KEY* ec_key = pkey;
412+
CHECK_NOT_NULL(ec_key);
413+
const BIGNUM* private_key = ECKeyPointer::GetPrivateKey(ec_key);
414+
CHECK_NOT_NULL(private_key);
415+
const auto group = ECKeyPointer::GetGroup(ec_key);
416+
auto order = BignumPointer::New();
417+
CHECK(order);
418+
CHECK(EC_GROUP_get_order(group, order.get(), nullptr));
419+
auto buf = BignumPointer::EncodePadded(private_key, order.byteLength());
420+
if (!buf) {
421+
THROW_ERR_CRYPTO_OPERATION_FAILED(env,
422+
"Failed to export EC private key");
423+
return false;
424+
}
425+
return Buffer::Copy(env, buf.get<const char>(), buf.size()).ToLocal(out);
426+
}
427+
switch (pkey.id()) {
428+
case EVP_PKEY_ED25519:
429+
case EVP_PKEY_ED448:
430+
case EVP_PKEY_X25519:
431+
case EVP_PKEY_X448:
432+
#if OPENSSL_WITH_PQC
433+
case EVP_PKEY_SLH_DSA_SHA2_128F:
434+
case EVP_PKEY_SLH_DSA_SHA2_128S:
435+
case EVP_PKEY_SLH_DSA_SHA2_192F:
436+
case EVP_PKEY_SLH_DSA_SHA2_192S:
437+
case EVP_PKEY_SLH_DSA_SHA2_256F:
438+
case EVP_PKEY_SLH_DSA_SHA2_256S:
439+
case EVP_PKEY_SLH_DSA_SHAKE_128F:
440+
case EVP_PKEY_SLH_DSA_SHAKE_128S:
441+
case EVP_PKEY_SLH_DSA_SHAKE_192F:
442+
case EVP_PKEY_SLH_DSA_SHAKE_192S:
443+
case EVP_PKEY_SLH_DSA_SHAKE_256F:
444+
case EVP_PKEY_SLH_DSA_SHAKE_256S:
445+
#endif
446+
break;
447+
default:
448+
THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
449+
return false;
450+
}
451+
auto raw_data = pkey.rawPrivateKey();
452+
if (!raw_data) {
453+
THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get raw private key");
454+
return false;
455+
}
456+
return Buffer::Copy(env, raw_data.get<const char>(), raw_data.size())
457+
.ToLocal(out);
458+
} else if (config.format == EVPKeyPointer::PKFormatType::RAW_SEED) {
459+
Mutex::ScopedLock lock(mutex());
460+
const auto& pkey = GetAsymmetricKey();
461+
switch (pkey.id()) {
462+
#if OPENSSL_WITH_PQC
463+
case EVP_PKEY_ML_DSA_44:
464+
case EVP_PKEY_ML_DSA_65:
465+
case EVP_PKEY_ML_DSA_87:
466+
case EVP_PKEY_ML_KEM_512:
467+
case EVP_PKEY_ML_KEM_768:
468+
case EVP_PKEY_ML_KEM_1024:
469+
break;
470+
#endif
471+
default:
472+
THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
473+
return false;
474+
}
475+
#if OPENSSL_WITH_PQC
476+
auto raw_data = pkey.rawSeed();
477+
if (!raw_data) {
478+
THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get raw seed");
479+
return false;
480+
}
481+
return Buffer::Copy(env, raw_data.get<const char>(), raw_data.size())
482+
.ToLocal(out);
483+
#else
484+
THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
485+
return false;
486+
#endif
350487
}
351488

352489
return WritePrivateKey(env, GetAsymmetricKey(), config).ToLocal(out);
@@ -367,6 +504,14 @@ KeyObjectData::GetPrivateKeyEncodingFromJs(
367504
if (config.output_key_object) {
368505
if (context != kKeyContextInput)
369506
(*offset)++;
507+
} else if (config.format == EVPKeyPointer::PKFormatType::RAW_PRIVATE ||
508+
config.format == EVPKeyPointer::PKFormatType::RAW_SEED) {
509+
// Raw formats don't support encryption. Still consume the arg offsets.
510+
if (context != kKeyContextInput) {
511+
CHECK(args[*offset]->IsNullOrUndefined());
512+
(*offset)++;
513+
}
514+
CHECK(args[*offset]->IsNullOrUndefined());
370515
} else {
371516
bool needs_passphrase = false;
372517
if (context != kKeyContextInput) {
@@ -1557,6 +1702,12 @@ void Initialize(Environment* env, Local<Object> target) {
15571702
static_cast<int>(EVPKeyPointer::PKFormatType::PEM);
15581703
constexpr int kKeyFormatJWK =
15591704
static_cast<int>(EVPKeyPointer::PKFormatType::JWK);
1705+
constexpr int kKeyFormatRawPublic =
1706+
static_cast<int>(EVPKeyPointer::PKFormatType::RAW_PUBLIC);
1707+
constexpr int kKeyFormatRawPrivate =
1708+
static_cast<int>(EVPKeyPointer::PKFormatType::RAW_PRIVATE);
1709+
constexpr int kKeyFormatRawSeed =
1710+
static_cast<int>(EVPKeyPointer::PKFormatType::RAW_SEED);
15601711

15611712
constexpr auto kSigEncDER = DSASigEnc::DER;
15621713
constexpr auto kSigEncP1363 = DSASigEnc::P1363;
@@ -1596,6 +1747,9 @@ void Initialize(Environment* env, Local<Object> target) {
15961747
NODE_DEFINE_CONSTANT(target, kKeyFormatDER);
15971748
NODE_DEFINE_CONSTANT(target, kKeyFormatPEM);
15981749
NODE_DEFINE_CONSTANT(target, kKeyFormatJWK);
1750+
NODE_DEFINE_CONSTANT(target, kKeyFormatRawPublic);
1751+
NODE_DEFINE_CONSTANT(target, kKeyFormatRawPrivate);
1752+
NODE_DEFINE_CONSTANT(target, kKeyFormatRawSeed);
15991753
NODE_DEFINE_CONSTANT(target, kKeyTypeSecret);
16001754
NODE_DEFINE_CONSTANT(target, kKeyTypePublic);
16011755
NODE_DEFINE_CONSTANT(target, kKeyTypePrivate);

0 commit comments

Comments
 (0)