Skip to content

Commit d482318

Browse files
committed
crypto: validate raw private import formats
Reject raw-private and raw-seed imports before key construction when the selected asymmetric key type does not support that raw private material. Signed-off-by: Filip Skokan <panva.ip@gmail.com>
1 parent 2c7e17e commit d482318

3 files changed

Lines changed: 128 additions & 39 deletions

File tree

src/crypto/crypto_keys.cc

Lines changed: 101 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,71 @@ int GetNidFromName(const char* name) {
283283
return NID_undef;
284284
#endif
285285
}
286+
287+
enum class RawPrivateKeyImportFormat {
288+
kInvalid,
289+
kUnsupported,
290+
kUnavailable,
291+
kRawPrivate,
292+
kRawSeed,
293+
};
294+
295+
bool IsUnavailablePqcKeyType(Environment* env, Local<String> key_type) {
296+
return key_type->StringEquals(env->crypto_ml_dsa_44_string()) ||
297+
key_type->StringEquals(env->crypto_ml_dsa_65_string()) ||
298+
key_type->StringEquals(env->crypto_ml_dsa_87_string()) ||
299+
key_type->StringEquals(env->crypto_ml_kem_512_string()) ||
300+
key_type->StringEquals(env->crypto_ml_kem_768_string()) ||
301+
key_type->StringEquals(env->crypto_ml_kem_1024_string()) ||
302+
key_type->StringEquals(env->crypto_slh_dsa_sha2_128f_string()) ||
303+
key_type->StringEquals(env->crypto_slh_dsa_sha2_128s_string()) ||
304+
key_type->StringEquals(env->crypto_slh_dsa_sha2_192f_string()) ||
305+
key_type->StringEquals(env->crypto_slh_dsa_sha2_192s_string()) ||
306+
key_type->StringEquals(env->crypto_slh_dsa_sha2_256f_string()) ||
307+
key_type->StringEquals(env->crypto_slh_dsa_sha2_256s_string()) ||
308+
key_type->StringEquals(env->crypto_slh_dsa_shake_128f_string()) ||
309+
key_type->StringEquals(env->crypto_slh_dsa_shake_128s_string()) ||
310+
key_type->StringEquals(env->crypto_slh_dsa_shake_192f_string()) ||
311+
key_type->StringEquals(env->crypto_slh_dsa_shake_192s_string()) ||
312+
key_type->StringEquals(env->crypto_slh_dsa_shake_256f_string()) ||
313+
key_type->StringEquals(env->crypto_slh_dsa_shake_256s_string());
314+
}
315+
316+
RawPrivateKeyImportFormat GetRawPrivateKeyImportFormat(Environment* env,
317+
Local<String> key_type,
318+
int id) {
319+
if (key_type->StringEquals(env->crypto_ec_string())) {
320+
return RawPrivateKeyImportFormat::kRawPrivate;
321+
}
322+
323+
switch (id) {
324+
case EVP_PKEY_X25519:
325+
case EVP_PKEY_X448:
326+
case EVP_PKEY_ED25519:
327+
case EVP_PKEY_ED448:
328+
return RawPrivateKeyImportFormat::kRawPrivate;
329+
default:
330+
break;
331+
}
332+
333+
#if OPENSSL_WITH_PQC
334+
if (IsPqcSeedKeyId(id)) return RawPrivateKeyImportFormat::kRawSeed;
335+
if (IsPqcRawPrivateKeyId(id)) return RawPrivateKeyImportFormat::kRawPrivate;
336+
#endif
337+
338+
if (IsUnavailablePqcKeyType(env, key_type)) {
339+
return RawPrivateKeyImportFormat::kUnavailable;
340+
}
341+
342+
if (key_type->StringEquals(env->crypto_rsa_string()) ||
343+
key_type->StringEquals(env->crypto_rsa_pss_string()) ||
344+
key_type->StringEquals(env->crypto_dsa_string()) ||
345+
key_type->StringEquals(env->crypto_dh_string())) {
346+
return RawPrivateKeyImportFormat::kUnsupported;
347+
}
348+
349+
return RawPrivateKeyImportFormat::kInvalid;
350+
}
286351
} // namespace
287352

288353
bool KeyObjectData::ToEncodedPublicKey(
@@ -487,6 +552,39 @@ static KeyObjectData ImportRawKey(Environment* env,
487552
THROW_ERR_INVALID_ARG_VALUE(env, "Invalid key data");
488553
}
489554
};
555+
auto throw_incompatible = [&]() -> KeyObjectData {
556+
THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
557+
return {};
558+
};
559+
560+
const int id = GetNidFromName(key_type_name);
561+
const RawPrivateKeyImportFormat private_format =
562+
GetRawPrivateKeyImportFormat(env, key_type, id);
563+
564+
switch (private_format) {
565+
case RawPrivateKeyImportFormat::kInvalid:
566+
THROW_ERR_INVALID_ARG_VALUE(
567+
env, "Invalid asymmetricKeyType: %s", key_type_name);
568+
return {};
569+
case RawPrivateKeyImportFormat::kUnsupported:
570+
return throw_incompatible();
571+
case RawPrivateKeyImportFormat::kUnavailable:
572+
THROW_ERR_INVALID_ARG_VALUE(env, "Unsupported key type");
573+
return {};
574+
case RawPrivateKeyImportFormat::kRawPrivate:
575+
case RawPrivateKeyImportFormat::kRawSeed:
576+
break;
577+
}
578+
579+
if (format == EVPKeyPointer::PKFormatType::RAW_PRIVATE &&
580+
private_format != RawPrivateKeyImportFormat::kRawPrivate) {
581+
return throw_incompatible();
582+
}
583+
584+
if (format == EVPKeyPointer::PKFormatType::RAW_SEED &&
585+
private_format != RawPrivateKeyImportFormat::kRawSeed) {
586+
return throw_incompatible();
587+
}
490588

491589
// EC keys
492590
if (key_type->StringEquals(env->crypto_ec_string())) {
@@ -545,8 +643,6 @@ static KeyObjectData ImportRawKey(Environment* env,
545643
return KeyObjectData::CreateAsymmetric(target_type, std::move(pkey));
546644
}
547645

548-
int id = GetNidFromName(key_type_name);
549-
550646
typedef EVPKeyPointer (*new_key_fn)(
551647
int, const ncrypto::Buffer<const unsigned char>&);
552648
new_key_fn fn = nullptr;
@@ -562,8 +658,9 @@ static KeyObjectData ImportRawKey(Environment* env,
562658
#if OPENSSL_WITH_PQC
563659
if (IsPqcKeyId(id)) {
564660
if (target_type == kKeyTypePrivate) {
565-
fn = IsPqcSeedKeyId(id) ? EVPKeyPointer::NewRawSeed
566-
: EVPKeyPointer::NewRawPrivate;
661+
fn = private_format == RawPrivateKeyImportFormat::kRawSeed
662+
? EVPKeyPointer::NewRawSeed
663+
: EVPKeyPointer::NewRawPrivate;
567664
} else {
568665
fn = EVPKeyPointer::NewRawPublic;
569666
}
@@ -584,41 +681,6 @@ static KeyObjectData ImportRawKey(Environment* env,
584681
}
585682
return KeyObjectData::CreateAsymmetric(target_type, std::move(pkey));
586683
}
587-
588-
if (key_type->StringEquals(env->crypto_rsa_string()) ||
589-
key_type->StringEquals(env->crypto_rsa_pss_string()) ||
590-
key_type->StringEquals(env->crypto_dsa_string()) ||
591-
key_type->StringEquals(env->crypto_dh_string())) {
592-
THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env);
593-
return {};
594-
}
595-
596-
#if !OPENSSL_WITH_PQC
597-
if (key_type->StringEquals(env->crypto_ml_dsa_44_string()) ||
598-
key_type->StringEquals(env->crypto_ml_dsa_65_string()) ||
599-
key_type->StringEquals(env->crypto_ml_dsa_87_string()) ||
600-
key_type->StringEquals(env->crypto_ml_kem_512_string()) ||
601-
key_type->StringEquals(env->crypto_ml_kem_768_string()) ||
602-
key_type->StringEquals(env->crypto_ml_kem_1024_string()) ||
603-
key_type->StringEquals(env->crypto_slh_dsa_sha2_128f_string()) ||
604-
key_type->StringEquals(env->crypto_slh_dsa_sha2_128s_string()) ||
605-
key_type->StringEquals(env->crypto_slh_dsa_sha2_192f_string()) ||
606-
key_type->StringEquals(env->crypto_slh_dsa_sha2_192s_string()) ||
607-
key_type->StringEquals(env->crypto_slh_dsa_sha2_256f_string()) ||
608-
key_type->StringEquals(env->crypto_slh_dsa_sha2_256s_string()) ||
609-
key_type->StringEquals(env->crypto_slh_dsa_shake_128f_string()) ||
610-
key_type->StringEquals(env->crypto_slh_dsa_shake_128s_string()) ||
611-
key_type->StringEquals(env->crypto_slh_dsa_shake_192f_string()) ||
612-
key_type->StringEquals(env->crypto_slh_dsa_shake_192s_string()) ||
613-
key_type->StringEquals(env->crypto_slh_dsa_shake_256f_string()) ||
614-
key_type->StringEquals(env->crypto_slh_dsa_shake_256s_string())) {
615-
THROW_ERR_INVALID_ARG_VALUE(env, "Unsupported key type");
616-
return {};
617-
}
618-
#endif
619-
620-
THROW_ERR_INVALID_ARG_VALUE(
621-
env, "Invalid asymmetricKeyType: %s", key_type_name);
622684
return {};
623685
}
624686

test/parallel/test-crypto-key-objects-raw.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,12 @@ if (hasOpenSSL(3, 5)) {
281281
fixtures.readKey('ec_p256_private.pem', 'ascii'));
282282
assert.throws(() => ecPriv.export({ format: 'raw-seed' }),
283283
{ code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' });
284+
assert.throws(() => crypto.createPrivateKey({
285+
key: ecPriv.export({ format: 'raw-private' }),
286+
format: 'raw-seed',
287+
asymmetricKeyType: 'ec',
288+
namedCurve: 'P-256',
289+
}), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' });
284290

285291
if (process.features.openssl_is_boringssl) {
286292
common.printSkipMessage('Skipping unsupported ed448/x448 test cases');
@@ -292,13 +298,23 @@ if (hasOpenSSL(3, 5)) {
292298
fixtures.readKey(`${type}_private.pem`, 'ascii'));
293299
assert.throws(() => priv.export({ format: 'raw-seed' }),
294300
{ code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' });
301+
assert.throws(() => crypto.createPrivateKey({
302+
key: priv.export({ format: 'raw-private' }),
303+
format: 'raw-seed',
304+
asymmetricKeyType: type,
305+
}), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' });
295306
}
296307

297308
if (hasOpenSSL(3, 5)) {
298309
const slhPriv = crypto.createPrivateKey(
299310
fixtures.readKey('slh_dsa_sha2_128f_private.pem', 'ascii'));
300311
assert.throws(() => slhPriv.export({ format: 'raw-seed' }),
301312
{ code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' });
313+
assert.throws(() => crypto.createPrivateKey({
314+
key: slhPriv.export({ format: 'raw-private' }),
315+
format: 'raw-seed',
316+
asymmetricKeyType: 'slh-dsa-sha2-128f',
317+
}), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' });
302318
}
303319
}
304320

@@ -309,6 +325,11 @@ if (hasOpenSSL(3, 5) || process.features.openssl_is_boringssl) {
309325
fixtures.readKey(`${type.replaceAll('-', '_')}_private_seed_only.pem`, 'ascii'));
310326
assert.throws(() => priv.export({ format: 'raw-private' }),
311327
{ code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' });
328+
assert.throws(() => crypto.createPrivateKey({
329+
key: priv.export({ format: 'raw-seed' }),
330+
format: 'raw-private',
331+
asymmetricKeyType: type,
332+
}), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' });
312333
}
313334
}
314335

test/parallel/test-crypto-pqc-key-objects-slh-dsa.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ for (const asymmetricKeyType of [
9191
key: rawPriv, format: 'raw-private', asymmetricKeyType,
9292
});
9393
assert.strictEqual(importedPriv.equals(key), true);
94+
assert.throws(() => createPrivateKey({
95+
key: rawPriv, format: 'raw-seed', asymmetricKeyType,
96+
}), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' });
97+
assert.throws(() => createPublicKey({
98+
key: rawPriv, format: 'raw-seed', asymmetricKeyType,
99+
}), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' });
94100
}
95101

96102
if (!hasOpenSSL(3, 5)) {

0 commit comments

Comments
 (0)