Skip to content

Commit bd10385

Browse files
committed
crypto: recognize raw formats in keygen
Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: #62480 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 8db948b commit bd10385

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
@@ -855,6 +855,9 @@ class EVPKeyPointer final {
855855
DER,
856856
PEM,
857857
JWK,
858+
RAW_PUBLIC,
859+
RAW_PRIVATE,
860+
RAW_SEED,
858861
};
859862

860863
enum class PKParseError { NOT_RECOGNIZED, NEED_PASSPHRASE, FAILED };
@@ -864,6 +867,7 @@ class EVPKeyPointer final {
864867
bool output_key_object = false;
865868
PKFormatType format = PKFormatType::DER;
866869
PKEncodingType type = PKEncodingType::PKCS8;
870+
int ec_point_form = POINT_CONVERSION_UNCOMPRESSED;
867871
AsymmetricKeyEncodingConfig() = default;
868872
AsymmetricKeyEncodingConfig(bool output_key_object,
869873
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,
@@ -419,6 +422,12 @@ function parseKeyFormat(formatStr, defaultFormat, optionName) {
419422
return kKeyFormatDER;
420423
else if (formatStr === 'jwk')
421424
return kKeyFormatJWK;
425+
else if (formatStr === 'raw-public')
426+
return kKeyFormatRawPublic;
427+
else if (formatStr === 'raw-private')
428+
return kKeyFormatRawPrivate;
429+
else if (formatStr === 'raw-seed')
430+
return kKeyFormatRawSeed;
422431
throw new ERR_INVALID_ARG_VALUE(optionName, formatStr);
423432
}
424433

@@ -459,6 +468,33 @@ function parseKeyFormatAndType(enc, keyType, isPublic, objName) {
459468
isInput ? kKeyFormatPEM : undefined,
460469
option('format', objName));
461470

471+
if (format === kKeyFormatRawPublic) {
472+
if (isPublic === false) {
473+
throw new ERR_INVALID_ARG_VALUE(option('format', objName), 'raw-public');
474+
}
475+
let type;
476+
if (typeStr === undefined || typeStr === 'uncompressed') {
477+
type = POINT_CONVERSION_UNCOMPRESSED;
478+
} else if (typeStr === 'compressed') {
479+
type = POINT_CONVERSION_COMPRESSED;
480+
} else {
481+
throw new ERR_INVALID_ARG_VALUE(option('type', objName), typeStr);
482+
}
483+
return { format, type };
484+
}
485+
486+
if (format === kKeyFormatRawPrivate || format === kKeyFormatRawSeed) {
487+
if (isPublic === true) {
488+
throw new ERR_INVALID_ARG_VALUE(
489+
option('format', objName),
490+
format === kKeyFormatRawPrivate ? 'raw-private' : 'raw-seed');
491+
}
492+
if (typeStr !== undefined) {
493+
throw new ERR_INVALID_ARG_VALUE(option('type', objName), typeStr);
494+
}
495+
return { format };
496+
}
497+
462498
const isRequired = (!isInput ||
463499
format === kKeyFormatDER) &&
464500
format !== kKeyFormatJWK;
@@ -490,6 +526,14 @@ function parseKeyEncoding(enc, keyType, isPublic, objName) {
490526
if (isPublic !== true) {
491527
({ cipher, passphrase, encoding } = enc);
492528

529+
if (format === kKeyFormatRawPrivate || format === kKeyFormatRawSeed) {
530+
if (cipher != null || passphrase !== undefined) {
531+
throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
532+
'raw format', 'does not support encryption');
533+
}
534+
return { format, type };
535+
}
536+
493537
if (!isInput) {
494538
if (cipher != null) {
495539
if (typeof cipher !== 'string')

src/crypto/crypto_keys.cc

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

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

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

348485
return WritePrivateKey(env, GetAsymmetricKey(), config).ToLocal(out);
@@ -363,6 +500,14 @@ KeyObjectData::GetPrivateKeyEncodingFromJs(
363500
if (config.output_key_object) {
364501
if (context != kKeyContextInput)
365502
(*offset)++;
503+
} else if (config.format == EVPKeyPointer::PKFormatType::RAW_PRIVATE ||
504+
config.format == EVPKeyPointer::PKFormatType::RAW_SEED) {
505+
// Raw formats don't support encryption. Still consume the arg offsets.
506+
if (context != kKeyContextInput) {
507+
CHECK(args[*offset]->IsNullOrUndefined());
508+
(*offset)++;
509+
}
510+
CHECK(args[*offset]->IsNullOrUndefined());
366511
} else {
367512
bool needs_passphrase = false;
368513
if (context != kKeyContextInput) {
@@ -1581,6 +1726,12 @@ void Initialize(Environment* env, Local<Object> target) {
15811726
static_cast<int>(EVPKeyPointer::PKFormatType::PEM);
15821727
constexpr int kKeyFormatJWK =
15831728
static_cast<int>(EVPKeyPointer::PKFormatType::JWK);
1729+
constexpr int kKeyFormatRawPublic =
1730+
static_cast<int>(EVPKeyPointer::PKFormatType::RAW_PUBLIC);
1731+
constexpr int kKeyFormatRawPrivate =
1732+
static_cast<int>(EVPKeyPointer::PKFormatType::RAW_PRIVATE);
1733+
constexpr int kKeyFormatRawSeed =
1734+
static_cast<int>(EVPKeyPointer::PKFormatType::RAW_SEED);
15841735

15851736
constexpr auto kSigEncDER = DSASigEnc::DER;
15861737
constexpr auto kSigEncP1363 = DSASigEnc::P1363;
@@ -1620,6 +1771,9 @@ void Initialize(Environment* env, Local<Object> target) {
16201771
NODE_DEFINE_CONSTANT(target, kKeyFormatDER);
16211772
NODE_DEFINE_CONSTANT(target, kKeyFormatPEM);
16221773
NODE_DEFINE_CONSTANT(target, kKeyFormatJWK);
1774+
NODE_DEFINE_CONSTANT(target, kKeyFormatRawPublic);
1775+
NODE_DEFINE_CONSTANT(target, kKeyFormatRawPrivate);
1776+
NODE_DEFINE_CONSTANT(target, kKeyFormatRawSeed);
16231777
NODE_DEFINE_CONSTANT(target, kKeyTypeSecret);
16241778
NODE_DEFINE_CONSTANT(target, kKeyTypePublic);
16251779
NODE_DEFINE_CONSTANT(target, kKeyTypePrivate);

0 commit comments

Comments
 (0)