diff --git a/benchmark/crypto/kem.js b/benchmark/crypto/kem.js index a59a57b65956..34374dfc8494 100644 --- a/benchmark/crypto/kem.js +++ b/benchmark/crypto/kem.js @@ -45,7 +45,8 @@ if (Object.keys(keyFixtures).length === 0) { } const bench = common.createBenchmark(main, { - keyType: Object.keys(keyFixtures), + // Keep one size per family by default; other fixtures remain available via keyType. + keyType: ['rsa', 'p-256', 'x25519', 'ml-kem-768'].filter((type) => keyFixtures[type]), mode: ['sync', 'async', 'async-parallel'], keyFormat: ['keyObject', 'keyObject.unique', 'pem', 'der', 'jwk', 'raw-public', 'raw-private', 'raw-seed'], @@ -57,6 +58,9 @@ const bench = common.createBenchmark(main, { // assess whether mutexes over the key material impact the operation if (p.keyFormat === 'keyObject.unique') return p.mode === 'async-parallel'; + // Compare execution modes with pre-imported keys; measure parsing synchronously. + if (p.mode !== 'sync' && p.keyFormat !== 'keyObject') + return false; // raw-public is only supported for encapsulate, not rsa if (p.keyFormat === 'raw-public') return p.keyType !== 'rsa' && p.op === 'encapsulate'; @@ -127,7 +131,8 @@ function main({ n, mode, keyFormat, keyType, op }) { keyFixtures[keyType].publicKey : keyFixtures[keyType].privateKey; const createKeyFn = isEncapsulate ? crypto.createPublicKey : crypto.createPrivateKey; - const pems = [...Buffer.alloc(n)].map(() => pemSource); + const count = keyFormat === 'keyObject.unique' ? n : 1; + const pems = Array(count).fill(pemSource); const keyObjects = pems.map(createKeyFn); // Warm up OpenSSL's provider operation cache for each key object diff --git a/benchmark/crypto/keyobject-serialization.js b/benchmark/crypto/keyobject-serialization.js new file mode 100644 index 000000000000..76cd6cb3387c --- /dev/null +++ b/benchmark/crypto/keyobject-serialization.js @@ -0,0 +1,79 @@ +'use strict'; + +const common = require('../common.js'); +const { hasOpenSSL, hasFIPS, isBoringSSL } = require('../../test/common/crypto.js'); +const fixtures = require('../../test/common/fixtures.js'); +const { createPrivateKey, createPublicKey } = require('crypto'); + +const keys = { + 'rsa': 'rsa_private_2048', + 'rsa-pss': 'rsa_pss_private_2048', + 'p-256': 'ec_p256_private', + 'p-384': 'ec_p384_private', + 'p-521': 'ec_p521_private', + 'ed25519': 'ed25519_private', + 'x25519': 'x25519_private', +}; +if (!isBoringSSL) { + keys.ed448 = 'ed448_private'; + keys.x448 = 'x448_private'; + if (!hasFIPS()) keys['rsa-multiprime'] = 'rsa_private_2048_3_primes'; +} +if (hasOpenSSL(3, 5) || isBoringSSL) { + keys['ml-dsa-44'] = 'ml_dsa_44_private_seed_only'; + keys['ml-dsa-87'] = 'ml_dsa_87_private_seed_only'; + keys['ml-kem-768'] = 'ml_kem_768_private_seed_only'; + keys['ml-kem-1024'] = 'ml_kem_1024_private_seed_only'; +} +if (hasOpenSSL(3, 5)) { + keys['slh-dsa-sha2-128s'] = 'slh_dsa_sha2_128s_private'; + keys['slh-dsa-shake-256s'] = 'slh_dsa_shake_256s_private'; +} + +const bench = common.createBenchmark(main, { + // Keep one size per family by default; other fixtures remain available via keyType. + keyType: ['rsa', 'rsa-pss', 'p-256', 'ed25519', 'x25519', + 'ml-dsa-44', 'ml-kem-768', 'slh-dsa-sha2-128s'].filter((type) => keys[type]), + operation: ['import', 'export'], + // PEM can be selected with format=pem; DER covers ASN.1 encoding by default. + format: ['jwk', 'der', 'raw-public', 'raw-private', 'raw-seed'], + type: ['public', 'private'], + n: [1e4], +}, { + combinationFilter({ keyType, format, type }) { + if (format === 'jwk') return keyType !== 'rsa-pss'; + if (!format.startsWith('raw-')) return true; + if (keyType.startsWith('rsa')) return false; + if (format === 'raw-public') return type === 'public'; + if (format === 'raw-private') return type === 'private' && !keyType.startsWith('ml-'); + return type === 'private' && keyType.startsWith('ml-'); + }, +}); + +function main({ keyType, operation, format, type, n }) { + const privateKey = createPrivateKey(fixtures.readKey(`${keys[keyType]}.pem`)); + const key = type === 'private' ? privateKey : createPublicKey(privateKey); + const options = { format }; + if (format === 'pem' || format === 'der') { + options.type = type === 'private' ? 'pkcs8' : 'spki'; + } + let run; + if (operation === 'export') { + run = () => key.export(options); + } else { + const input = { ...options, key: key.export(options) }; + if (format.startsWith('raw-')) { + input.asymmetricKeyType = key.asymmetricKeyType; + if (input.asymmetricKeyType === 'ec') { + input.namedCurve = key.asymmetricKeyDetails.namedCurve; + } + } + const importKey = type === 'private' ? createPrivateKey : createPublicKey; + run = () => importKey(input); + } + // Resolve provider operations and warm the JS path before timing. + for (let i = 0; i < 100; i++) run(); + bench.start(); + for (let i = 0; i < n; i++) run(); + bench.end(n); +} diff --git a/benchmark/crypto/oneshot-sign.js b/benchmark/crypto/oneshot-sign.js index 78f0b5574aef..d2a8bab446d1 100644 --- a/benchmark/crypto/oneshot-sign.js +++ b/benchmark/crypto/oneshot-sign.js @@ -25,9 +25,6 @@ if (hasOpenSSL(3, 5)) { const data = crypto.randomBytes(256); -let pems; -let keyObjects; - const bench = common.createBenchmark(main, { keyType: Object.keys(keyFixtures), mode: ['sync', 'async', 'async-parallel'], @@ -39,6 +36,9 @@ const bench = common.createBenchmark(main, { // assess whether mutexes over the key material impact the operation if (p.keyFormat === 'keyObject.unique') return p.mode === 'async-parallel'; + // Compare execution modes with pre-imported keys; measure parsing synchronously. + if (p.mode !== 'sync' && p.keyFormat !== 'keyObject') + return false; // raw-private is not supported for rsa and ml-dsa if (p.keyFormat === 'raw-private') return p.keyType !== 'rsa' && !p.keyType.startsWith('ml-'); @@ -97,8 +97,9 @@ function measureAsyncParallel(n, digest, privateKey, keys) { } function main({ n, mode, keyFormat, keyType }) { - pems ||= [...Buffer.alloc(n)].map(() => keyFixtures[keyType]); - keyObjects ||= pems.map(crypto.createPrivateKey); + const count = keyFormat === 'keyObject.unique' ? n : 1; + const pems = Array(count).fill(keyFixtures[keyType]); + const keyObjects = pems.map(crypto.createPrivateKey); // Warm up OpenSSL's provider operation cache for each key object for (const keyObject of keyObjects) { diff --git a/benchmark/crypto/oneshot-verify.js b/benchmark/crypto/oneshot-verify.js index 9aac91692dfc..eb65d912877d 100644 --- a/benchmark/crypto/oneshot-verify.js +++ b/benchmark/crypto/oneshot-verify.js @@ -32,9 +32,6 @@ if (hasOpenSSL(3, 5)) { const data = crypto.randomBytes(256); -let pems; -let keyObjects; - const bench = common.createBenchmark(main, { keyType: Object.keys(keyFixtures), mode: ['sync', 'async', 'async-parallel'], @@ -46,6 +43,9 @@ const bench = common.createBenchmark(main, { // assess whether mutexes over the key material impact the operation if (p.keyFormat === 'keyObject.unique') return p.mode === 'async-parallel'; + // Compare execution modes with pre-imported keys; measure parsing synchronously. + if (p.mode !== 'sync' && p.keyFormat !== 'keyObject') + return false; // raw-public is not supported by rsa if (p.keyFormat === 'raw-public') return p.keyType !== 'rsa'; @@ -104,8 +104,9 @@ function measureAsyncParallel(n, digest, signature, publicKey, keys) { } function main({ n, mode, keyFormat, keyType }) { - pems ||= [...Buffer.alloc(n)].map(() => keyFixtures[keyType].publicKey); - keyObjects ||= pems.map(crypto.createPublicKey); + const count = keyFormat === 'keyObject.unique' ? n : 1; + const pems = Array(count).fill(keyFixtures[keyType].publicKey); + const keyObjects = pems.map(crypto.createPublicKey); // Warm up OpenSSL's provider operation cache for each key object const warmupDigest = keyType === 'rsa' || keyType === 'ec' ? 'sha256' : null; diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index 9c88d919e9ad..6c817b742412 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -29,40 +29,6 @@ #include #endif #endif -#if OPENSSL_WITH_PQC -struct PQCMapping { - const char* name; - int nid; -}; - -constexpr static PQCMapping pqc_mappings[] = { - {"ML-DSA-44", EVP_PKEY_ML_DSA_44}, - {"ML-DSA-65", EVP_PKEY_ML_DSA_65}, - {"ML-DSA-87", EVP_PKEY_ML_DSA_87}, - {"ML-KEM-768", EVP_PKEY_ML_KEM_768}, - {"ML-KEM-1024", EVP_PKEY_ML_KEM_1024}, - -#if OPENSSL_WITH_PQC_ML_KEM_512 - {"ML-KEM-512", EVP_PKEY_ML_KEM_512}, -#endif -#if OPENSSL_WITH_PQC_SLH_DSA - {"SLH-DSA-SHA2-128f", EVP_PKEY_SLH_DSA_SHA2_128F}, - {"SLH-DSA-SHA2-128s", EVP_PKEY_SLH_DSA_SHA2_128S}, - {"SLH-DSA-SHA2-192f", EVP_PKEY_SLH_DSA_SHA2_192F}, - {"SLH-DSA-SHA2-192s", EVP_PKEY_SLH_DSA_SHA2_192S}, - {"SLH-DSA-SHA2-256f", EVP_PKEY_SLH_DSA_SHA2_256F}, - {"SLH-DSA-SHA2-256s", EVP_PKEY_SLH_DSA_SHA2_256S}, - {"SLH-DSA-SHAKE-128f", EVP_PKEY_SLH_DSA_SHAKE_128F}, - {"SLH-DSA-SHAKE-128s", EVP_PKEY_SLH_DSA_SHAKE_128S}, - {"SLH-DSA-SHAKE-192f", EVP_PKEY_SLH_DSA_SHAKE_192F}, - {"SLH-DSA-SHAKE-192s", EVP_PKEY_SLH_DSA_SHAKE_192S}, - {"SLH-DSA-SHAKE-256f", EVP_PKEY_SLH_DSA_SHAKE_256F}, - {"SLH-DSA-SHAKE-256s", EVP_PKEY_SLH_DSA_SHAKE_256S}, -#endif -}; - -#endif - // EVP_PKEY_CTX_set_dsa_paramgen_q_bits was added in OpenSSL 1.1.1e. #if OPENSSL_VERSION_NUMBER < 0x1010105fL #define EVP_PKEY_CTX_set_dsa_paramgen_q_bits(ctx, qbits) \ @@ -225,8 +191,10 @@ bool GetOptionalPKeyBnParam(const EVP_PKEY* pkey, return true; } -EVPKeyPointer NewPKeyFromData(int id, int selection, OSSL_PARAM* params) { - auto ctx = EVPKeyCtxPointer::NewFromID(id); +EVPKeyPointer NewPKeyFromData(const KeyAlgorithm& algorithm, + int selection, + OSSL_PARAM* params) { + auto ctx = EVPKeyCtxPointer::NewFromAlgorithm(algorithm); if (!ctx || EVP_PKEY_fromdata_init(ctx.get()) != 1) return {}; EVP_PKEY* pkey = nullptr; @@ -266,7 +234,7 @@ EVPKeyPointer NewDhPKey(const BIGNUM* p, OSSLParamPointer params(OSSL_PARAM_BLD_to_param(bld.get())); if (!params) return {}; - return NewPKeyFromData(EVP_PKEY_DH, selection, params.get()); + return NewPKeyFromData(KeyAlgorithm::DH, selection, params.get()); } EVPKeyPointer NewDhPKey(const char* group_name, @@ -275,7 +243,7 @@ EVPKeyPointer NewDhPKey(const char* group_name, if (group_name == nullptr) return {}; if (pub == nullptr && priv == nullptr) { - EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new_from_name(nullptr, "DH", nullptr)); + auto ctx = EVPKeyCtxPointer::NewFromAlgorithm(KeyAlgorithm::DH); OSSL_PARAM params[] = { OSSL_PARAM_construct_utf8_string( OSSL_PKEY_PARAM_GROUP_NAME, const_cast(group_name), 0), @@ -311,7 +279,7 @@ EVPKeyPointer NewDhPKey(const char* group_name, OSSLParamPointer params(OSSL_PARAM_BLD_to_param(bld.get())); if (!params) return {}; - return NewPKeyFromData(EVP_PKEY_DH, selection, params.get()); + return NewPKeyFromData(KeyAlgorithm::DH, selection, params.get()); } bool GetDhParams(const EVP_PKEY* pkey, @@ -1681,8 +1649,7 @@ bool X509View::enumUsages(UsageCallback callback) const { bool X509View::ifRsa(KeyCallback callback) const { if (cert_ == nullptr) return true; OSSL3_CONST EVP_PKEY* pkey = X509_get0_pubkey(cert_); - auto id = EVP_PKEY_id(pkey); - if (id == EVP_PKEY_RSA || id == EVP_PKEY_RSA2 || id == EVP_PKEY_RSA_PSS) { + if (EVPKeyPointer::isRsaVariant(pkey)) { #if NCRYPTO_USE_OPENSSL3_PROVIDER Rsa rsa(pkey); #else @@ -1698,8 +1665,7 @@ bool X509View::ifRsa(KeyCallback callback) const { bool X509View::ifEc(KeyCallback callback) const { if (cert_ == nullptr) return true; OSSL3_CONST EVP_PKEY* pkey = X509_get0_pubkey(cert_); - auto id = EVP_PKEY_id(pkey); - if (id == EVP_PKEY_EC) { + if (EVPKeyPointer::isA(pkey, KeyAlgorithm::EC)) { #if NCRYPTO_USE_OPENSSL3_PROVIDER Ec ec(pkey); #else @@ -1860,25 +1826,18 @@ int BIOPointer::Write(BIOPointer* bio, std::string_view message) { // DHPointer namespace { -bool EqualNoCase(const std::string_view a, const std::string_view b) { - if (a.size() != b.size()) return false; - return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char a, char b) { - return std::tolower(a) == std::tolower(b); - }); -} - #if NCRYPTO_USE_OPENSSL3_PROVIDER const char* GetOpenSSLDhGroupName(const std::string_view name, DHPointer::FindGroupOption option) { if (option != DHPointer::FindGroupOption::NO_SMALL_PRIMES && - EqualNoCase(name, "modp5")) { + CaseInsensitiveNameEqual()(name, "modp5")) { return "modp_1536"; } - if (EqualNoCase(name, "modp14")) return "modp_2048"; - if (EqualNoCase(name, "modp15")) return "modp_3072"; - if (EqualNoCase(name, "modp16")) return "modp_4096"; - if (EqualNoCase(name, "modp17")) return "modp_6144"; - if (EqualNoCase(name, "modp18")) return "modp_8192"; + if (CaseInsensitiveNameEqual()(name, "modp14")) return "modp_2048"; + if (CaseInsensitiveNameEqual()(name, "modp15")) return "modp_3072"; + if (CaseInsensitiveNameEqual()(name, "modp16")) return "modp_4096"; + if (CaseInsensitiveNameEqual()(name, "modp17")) return "modp_6144"; + if (CaseInsensitiveNameEqual()(name, "modp18")) return "modp_8192"; return nullptr; } @@ -2112,7 +2071,7 @@ DH* DHPointer::release() { BignumPointer DHPointer::FindGroup(const std::string_view name, FindGroupOption option) { #define V(n, p) \ - if (EqualNoCase(name, n)) return BignumPointer(p(nullptr)); + if (CaseInsensitiveNameEqual()(name, n)) return BignumPointer(p(nullptr)); if (option != FindGroupOption::NO_SMALL_PRIMES) { #ifndef OPENSSL_IS_BORINGSSL // Boringssl does not support the 768 and 1024 small primes @@ -2180,7 +2139,7 @@ DHPointer DHPointer::New(BignumPointer&& p, BignumPointer&& g) { DHPointer DHPointer::New(size_t bits, unsigned int generator) { #if NCRYPTO_USE_OPENSSL3_PROVIDER - auto param_ctx = EVPKeyCtxPointer::NewFromID(EVP_PKEY_DH); + auto param_ctx = EVPKeyCtxPointer::NewFromAlgorithm(KeyAlgorithm::DH); if (!param_ctx.initForParamgen() || !param_ctx.setDhParameters(bits, generator)) { return {}; @@ -2705,7 +2664,7 @@ DataPointer hkdf(const Digest& md, return {}; } - auto ctx = EVPKeyCtxPointer::NewFromID(EVP_PKEY_HKDF); + auto ctx = EVPKeyCtxPointer::NewFromName("HKDF"); // OpenSSL < 3.0.0 accepted only a void* as the argument of // EVP_PKEY_CTX_set_hkdf_md. const EVP_MD* md_ptr = md; @@ -2944,120 +2903,278 @@ EVPKeyPointer::PrivateKeyEncodingConfig::operator=( return *new (this) PrivateKeyEncodingConfig(other); } +// clang-format off +// NOLINTBEGIN(whitespace/line_length) +const KeyAlgorithm KeyAlgorithm::RSA("RSA", Family::Other); +const KeyAlgorithm KeyAlgorithm::RSA_PSS("RSA-PSS", Family::Other); +const KeyAlgorithm KeyAlgorithm::DSA("DSA", Family::Other); +const KeyAlgorithm KeyAlgorithm::DH("DH", Family::Other); +const KeyAlgorithm KeyAlgorithm::EC("EC", Family::Other); +const KeyAlgorithm KeyAlgorithm::ED25519("Ed25519", Family::EdDSA); +const KeyAlgorithm KeyAlgorithm::ED448("Ed448", Family::EdDSA); +const KeyAlgorithm KeyAlgorithm::X25519("X25519", Family::XDH); +const KeyAlgorithm KeyAlgorithm::X448("X448", Family::XDH); +const KeyAlgorithm KeyAlgorithm::SM2("SM2", Family::Other, /* has_key_type */ false); +const KeyAlgorithm KeyAlgorithm::ML_DSA_44("ML-DSA-44", Family::MLDSA); +const KeyAlgorithm KeyAlgorithm::ML_DSA_65("ML-DSA-65", Family::MLDSA); +const KeyAlgorithm KeyAlgorithm::ML_DSA_87("ML-DSA-87", Family::MLDSA); +const KeyAlgorithm KeyAlgorithm::ML_KEM_512("ML-KEM-512", Family::MLKEM); +const KeyAlgorithm KeyAlgorithm::ML_KEM_768("ML-KEM-768", Family::MLKEM); +const KeyAlgorithm KeyAlgorithm::ML_KEM_1024("ML-KEM-1024", Family::MLKEM); +const KeyAlgorithm KeyAlgorithm::SLH_DSA_SHA2_128F("SLH-DSA-SHA2-128f", Family::SLHDSA); +const KeyAlgorithm KeyAlgorithm::SLH_DSA_SHA2_128S("SLH-DSA-SHA2-128s", Family::SLHDSA); +const KeyAlgorithm KeyAlgorithm::SLH_DSA_SHA2_192F("SLH-DSA-SHA2-192f", Family::SLHDSA); +const KeyAlgorithm KeyAlgorithm::SLH_DSA_SHA2_192S("SLH-DSA-SHA2-192s", Family::SLHDSA); +const KeyAlgorithm KeyAlgorithm::SLH_DSA_SHA2_256F("SLH-DSA-SHA2-256f", Family::SLHDSA); +const KeyAlgorithm KeyAlgorithm::SLH_DSA_SHA2_256S("SLH-DSA-SHA2-256s", Family::SLHDSA); +const KeyAlgorithm KeyAlgorithm::SLH_DSA_SHAKE_128F("SLH-DSA-SHAKE-128f", Family::SLHDSA); +const KeyAlgorithm KeyAlgorithm::SLH_DSA_SHAKE_128S("SLH-DSA-SHAKE-128s", Family::SLHDSA); +const KeyAlgorithm KeyAlgorithm::SLH_DSA_SHAKE_192F("SLH-DSA-SHAKE-192f", Family::SLHDSA); +const KeyAlgorithm KeyAlgorithm::SLH_DSA_SHAKE_192S("SLH-DSA-SHAKE-192s", Family::SLHDSA); +const KeyAlgorithm KeyAlgorithm::SLH_DSA_SHAKE_256F("SLH-DSA-SHAKE-256f", Family::SLHDSA); +const KeyAlgorithm KeyAlgorithm::SLH_DSA_SHAKE_256S("SLH-DSA-SHAKE-256s", Family::SLHDSA); +// NOLINTEND(whitespace/line_length) +// clang-format on + +namespace { +#if NCRYPTO_USE_OPENSSL3_PROVIDER +constexpr char kSignatureContextString[] = "context-string"; +constexpr char kSignatureInstance[] = "instance"; +#endif +const KeyAlgorithm* const kKeyAlgorithms[] = { + &KeyAlgorithm::RSA, + &KeyAlgorithm::RSA_PSS, + &KeyAlgorithm::DSA, + &KeyAlgorithm::DH, + &KeyAlgorithm::EC, + &KeyAlgorithm::ED25519, + &KeyAlgorithm::ED448, + &KeyAlgorithm::X25519, + &KeyAlgorithm::X448, + &KeyAlgorithm::SM2, + &KeyAlgorithm::ML_DSA_44, + &KeyAlgorithm::ML_DSA_65, + &KeyAlgorithm::ML_DSA_87, + &KeyAlgorithm::ML_KEM_512, + &KeyAlgorithm::ML_KEM_768, + &KeyAlgorithm::ML_KEM_1024, + &KeyAlgorithm::SLH_DSA_SHA2_128F, + &KeyAlgorithm::SLH_DSA_SHA2_128S, + &KeyAlgorithm::SLH_DSA_SHA2_192F, + &KeyAlgorithm::SLH_DSA_SHA2_192S, + &KeyAlgorithm::SLH_DSA_SHA2_256F, + &KeyAlgorithm::SLH_DSA_SHA2_256S, + &KeyAlgorithm::SLH_DSA_SHAKE_128F, + &KeyAlgorithm::SLH_DSA_SHAKE_128S, + &KeyAlgorithm::SLH_DSA_SHAKE_192F, + &KeyAlgorithm::SLH_DSA_SHAKE_192S, + &KeyAlgorithm::SLH_DSA_SHAKE_256F, + &KeyAlgorithm::SLH_DSA_SHAKE_256S, +}; +} // namespace + +const KeyAlgorithm* KeyAlgorithm::FromName(const char* name) { + if (name == nullptr) return nullptr; + for (const auto* algorithm : kKeyAlgorithms) { + if (CaseInsensitiveNameEqual()(name, algorithm->name())) return algorithm; + } + return nullptr; +} + +void KeyAlgorithm::ForEachPqc(Callback callback) { + for (const auto* algorithm : kKeyAlgorithms) { + if (algorithm->isPqc() && algorithm->isAvailable()) callback(*algorithm); + } +} + +bool KeyAlgorithm::isRsa() const { + return this == &RSA || this == &RSA_PSS; +} + +bool KeyAlgorithm::isPqc() const { + return family_ == Family::MLDSA || family_ == Family::MLKEM || + family_ == Family::SLHDSA; +} + +bool KeyAlgorithm::isOkp() const { + return family_ == Family::EdDSA || family_ == Family::XDH; +} + +bool KeyAlgorithm::isOneShot() const { + return family_ == Family::EdDSA || family_ == Family::MLDSA || + family_ == Family::SLHDSA; +} + +bool KeyAlgorithm::supportsRawPublic() const { + return isOkp() || isPqc(); +} + +bool KeyAlgorithm::supportsRawPrivate() const { + return isOkp() || family_ == Family::SLHDSA; +} + +size_t KeyAlgorithm::seedSize() const { + if (family_ == Family::MLDSA) return 32; + if (family_ == Family::MLKEM) return 64; + return 0; +} + +namespace { +#if !NCRYPTO_USE_OPENSSL3_PROVIDER +struct LegacyKeyAlgorithm { + const char* name; + int id; +#if NCRYPTO_USE_BORINGSSL + const EVP_PKEY_ALG* (*raw_key_algorithm)() = nullptr; +#endif +}; + +// These backends require native key IDs. BoringSSL also uses EVP_PKEY_ALG +// descriptors for raw keys; keep both adapters in the same table. +// clang-format off +// NOLINTBEGIN(whitespace/line_length) +const LegacyKeyAlgorithm kLegacyKeyAlgorithms[] = { + {KeyAlgorithm::RSA.name(), EVP_PKEY_RSA}, + {KeyAlgorithm::RSA_PSS.name(), EVP_PKEY_RSA_PSS}, + {KeyAlgorithm::DSA.name(), EVP_PKEY_DSA}, + {KeyAlgorithm::DH.name(), EVP_PKEY_DH}, + {KeyAlgorithm::EC.name(), EVP_PKEY_EC}, +#if NCRYPTO_USE_BORINGSSL + {KeyAlgorithm::ED25519.name(), EVP_PKEY_ED25519, EVP_pkey_ed25519}, + {KeyAlgorithm::X25519.name(), EVP_PKEY_X25519, EVP_pkey_x25519}, +#else + {KeyAlgorithm::ED25519.name(), EVP_PKEY_ED25519}, + {KeyAlgorithm::X25519.name(), EVP_PKEY_X25519}, +#endif + {"HKDF", EVP_PKEY_HKDF}, + {KeyAlgorithm::ED448.name(), EVP_PKEY_ED448}, + {KeyAlgorithm::X448.name(), EVP_PKEY_X448}, +#ifndef OPENSSL_NO_SM2 + {KeyAlgorithm::SM2.name(), EVP_PKEY_SM2}, +#endif +#if NCRYPTO_USE_BORINGSSL + {KeyAlgorithm::ML_DSA_44.name(), EVP_PKEY_ML_DSA_44, EVP_pkey_ml_dsa_44}, + {KeyAlgorithm::ML_DSA_65.name(), EVP_PKEY_ML_DSA_65, EVP_pkey_ml_dsa_65}, + {KeyAlgorithm::ML_DSA_87.name(), EVP_PKEY_ML_DSA_87, EVP_pkey_ml_dsa_87}, + {KeyAlgorithm::ML_KEM_768.name(), EVP_PKEY_ML_KEM_768, EVP_pkey_ml_kem_768}, + {KeyAlgorithm::ML_KEM_1024.name(), EVP_PKEY_ML_KEM_1024, EVP_pkey_ml_kem_1024}, +#endif +}; +// NOLINTEND(whitespace/line_length) +// clang-format on + +const LegacyKeyAlgorithm* FindLegacyKeyAlgorithm(const char* name) { + if (name == nullptr) return nullptr; + for (const auto& algorithm : kLegacyKeyAlgorithms) { + if (CaseInsensitiveNameEqual()(name, algorithm.name)) return &algorithm; + } + return nullptr; +} + +int GetLegacyKeyId(const char* name) { + const auto* algorithm = FindLegacyKeyAlgorithm(name); + return algorithm == nullptr ? NID_undef : algorithm->id; +} + +#if NCRYPTO_USE_BORINGSSL +const EVP_PKEY_ALG* GetBoringSSLKeyAlgorithm(const KeyAlgorithm& algorithm) { + const auto* entry = FindLegacyKeyAlgorithm(algorithm.name()); + return entry != nullptr && entry->raw_key_algorithm != nullptr + ? entry->raw_key_algorithm() + : nullptr; +} +#endif +#endif +} // namespace + +void ConfigurePqcEncoding() { +#if NCRYPTO_USE_OPENSSL3_PROVIDER && OPENSSL_VERSION_PREREQ(3, 5) + // Configure all loaded providers to prefer seed-only format for ML-KEM and + // ML-DSA private keys in PKCS#8 export, falling back to priv-only when a + // seed is not available. The provider encoder reads these parameters at + // encoding time via ossl_prov_ctx_get_param(). + OSSL_PROVIDER_do_all( + nullptr, + [](OSSL_PROVIDER* provider, void*) -> int { + OSSL_PROVIDER_add_conf_parameter( + provider, "ml-kem.output_formats", "seed-only,priv-only"); + OSSL_PROVIDER_add_conf_parameter( + provider, "ml-dsa.output_formats", "seed-only,priv-only"); + return 1; + }, + nullptr); +#endif +} + +bool KeyAlgorithm::isAvailable() const { + MarkPopErrorOnReturn mark_pop_error_on_return; + return static_cast(EVPKeyCtxPointer::NewFromName(name_)); +} + EVPKeyPointer EVPKeyPointer::New() { return EVPKeyPointer(EVP_PKEY_new()); } EVPKeyPointer EVPKeyPointer::NewRawPublic( - int id, const Buffer& data) { - if (id == 0) return {}; + const KeyAlgorithm& algorithm, const Buffer& data) { +#if NCRYPTO_USE_OPENSSL3_PROVIDER + return EVPKeyPointer(EVP_PKEY_new_raw_public_key_ex( + nullptr, algorithm.name(), nullptr, data.data, data.len)); +#elif NCRYPTO_USE_BORINGSSL + const auto* alg = GetBoringSSLKeyAlgorithm(algorithm); + if (alg == nullptr) return {}; + return EVPKeyPointer(EVP_PKEY_from_raw_public_key(alg, data.data, data.len)); +#else + const int id = GetLegacyKeyId(algorithm.name()); + if (id == NID_undef) return {}; return EVPKeyPointer( EVP_PKEY_new_raw_public_key(id, nullptr, data.data, data.len)); +#endif } EVPKeyPointer EVPKeyPointer::NewRawPrivate( - int id, const Buffer& data) { - if (id == 0) return {}; + const KeyAlgorithm& algorithm, const Buffer& data) { +#if NCRYPTO_USE_OPENSSL3_PROVIDER + return EVPKeyPointer(EVP_PKEY_new_raw_private_key_ex( + nullptr, algorithm.name(), nullptr, data.data, data.len)); +#elif NCRYPTO_USE_BORINGSSL + const auto* alg = GetBoringSSLKeyAlgorithm(algorithm); + if (alg == nullptr) return {}; + return EVPKeyPointer(EVP_PKEY_from_raw_private_key(alg, data.data, data.len)); +#else + const int id = GetLegacyKeyId(algorithm.name()); + if (id == NID_undef) return {}; return EVPKeyPointer( EVP_PKEY_new_raw_private_key(id, nullptr, data.data, data.len)); -} - -#if OPENSSL_WITH_PQC -namespace { -constexpr size_t kPqcMlDsaSeedSize = 32; -constexpr size_t kPqcMlKemSeedSize = 64; - -size_t GetPqcSeedSize(int id) { - switch (id) { - case EVP_PKEY_ML_DSA_44: - case EVP_PKEY_ML_DSA_65: - case EVP_PKEY_ML_DSA_87: - return kPqcMlDsaSeedSize; -#if OPENSSL_WITH_PQC_ML_KEM_512 - case EVP_PKEY_ML_KEM_512: -#endif - case EVP_PKEY_ML_KEM_768: - case EVP_PKEY_ML_KEM_1024: - return kPqcMlKemSeedSize; - default: - unreachable(); - } -} - -#if OPENSSL_WITH_BORINGSSL_PQC -const EVP_PKEY_ALG* GetPqcSeedAlg(int id) { - switch (id) { - case EVP_PKEY_ML_DSA_44: - return EVP_pkey_ml_dsa_44(); - case EVP_PKEY_ML_DSA_65: - return EVP_pkey_ml_dsa_65(); - case EVP_PKEY_ML_DSA_87: - return EVP_pkey_ml_dsa_87(); - case EVP_PKEY_ML_KEM_768: - return EVP_pkey_ml_kem_768(); - case EVP_PKEY_ML_KEM_1024: - return EVP_pkey_ml_kem_1024(); - default: - unreachable(); - } -} -#else -const char* GetPqcSeedParamName(int id) { - switch (id) { - case EVP_PKEY_ML_DSA_44: - case EVP_PKEY_ML_DSA_65: - case EVP_PKEY_ML_DSA_87: - return OSSL_PKEY_PARAM_ML_DSA_SEED; - case EVP_PKEY_ML_KEM_512: - case EVP_PKEY_ML_KEM_768: - case EVP_PKEY_ML_KEM_1024: - return OSSL_PKEY_PARAM_ML_KEM_SEED; - default: - unreachable(); - } -} #endif +} -EVPKeyPointer NewPqcKeyFromSeed(int id, - const Buffer& data) { -#if OPENSSL_WITH_BORINGSSL_PQC +EVPKeyPointer EVPKeyPointer::NewRawSeed( + const KeyAlgorithm& algorithm, const Buffer& data) { + if (algorithm.seedSize() == 0) return {}; +#if NCRYPTO_USE_BORINGSSL + const auto* seed_alg = GetBoringSSLKeyAlgorithm(algorithm); + if (seed_alg == nullptr) return {}; return EVPKeyPointer( - EVP_PKEY_from_private_seed(GetPqcSeedAlg(id), data.data, data.len)); -#else + EVP_PKEY_from_private_seed(seed_alg, data.data, data.len)); +#elif NCRYPTO_USE_OPENSSL3_PROVIDER + // ML-DSA and ML-KEM both use the provider parameter "seed". OSSL_PARAM params[] = { - OSSL_PARAM_construct_octet_string(GetPqcSeedParamName(id), - const_cast(data.data), - data.len), + OSSL_PARAM_construct_octet_string( + "seed", const_cast(data.data), data.len), OSSL_PARAM_END}; - - auto ctx = EVPKeyCtxPointer::NewFromID(id); + auto ctx = EVPKeyCtxPointer::NewFromAlgorithm(algorithm); if (!ctx) return {}; - EVP_PKEY* pkey = nullptr; if (EVP_PKEY_fromdata_init(ctx.get()) <= 0 || EVP_PKEY_fromdata(ctx.get(), &pkey, EVP_PKEY_KEYPAIR, params) <= 0) { return {}; } return EVPKeyPointer(pkey); -#endif -} - -bool GetPqcSeed(EVP_PKEY* pkey, int id, const Buffer& out) { - size_t len = out.len; -#if OPENSSL_WITH_BORINGSSL_PQC - return EVP_PKEY_get_private_seed(pkey, out.data, &len) == 1; #else - return EVP_PKEY_get_octet_string_param( - pkey, GetPqcSeedParamName(id), out.data, out.len, &len) == 1; + return {}; #endif } -} // namespace - -EVPKeyPointer EVPKeyPointer::NewRawSeed( - int id, const Buffer& data) { - return NewPqcKeyFromSeed(id, data); -} -#endif EVPKeyPointer EVPKeyPointer::NewDH(DHPointer&& dh) { if (!dh) return {}; @@ -3125,7 +3242,7 @@ EVPKeyPointer EVPKeyPointer::NewRSA(const Rsa& rsa) { OSSLParamPointer params(OSSL_PARAM_BLD_to_param(bld.get())); if (!params) return {}; - return NewPKeyFromData(EVP_PKEY_RSA, selection, params.get()); + return NewPKeyFromData(KeyAlgorithm::RSA, selection, params.get()); } #else EVPKeyPointer EVPKeyPointer::NewRSA(RSAPointer&& rsa) { @@ -3162,41 +3279,255 @@ EVP_PKEY* EVPKeyPointer::release() { return pkey_.release(); } -int EVPKeyPointer::id(const EVP_PKEY* key) { - if (key == nullptr) return 0; - int type = EVP_PKEY_id(key); -#if OPENSSL_WITH_OPENSSL_PQC - // EVP_PKEY_id returns -1 when EVP_PKEY_* is only implemented in a provider - // which is the case for all post-quantum NIST algorithms - // one suggested way would be to use a chain of `EVP_PKEY_is_a` - // https://github.com/openssl/openssl/issues/27738#issuecomment-3013215870 - // or, this way there are less calls to the OpenSSL provider, just - // getting the name once - if (type == -1) { - const char* type_name = EVP_PKEY_get0_type_name(key); - if (type_name == nullptr) return -1; - - for (const auto& mapping : pqc_mappings) { - if (strcmp(type_name, mapping.name) == 0) { - return mapping.nid; - } +bool EVPKeyPointer::isA(const EVP_PKEY* key, const char* name) { + if (key == nullptr || name == nullptr) return false; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + // EVP_PKEY_is_a() can match an untyped key to an unknown legacy name. + return EVP_PKEY_get0_type_name(key) != nullptr && + EVP_PKEY_is_a(key, name) == 1; +#else + const int id = GetLegacyKeyId(name); + return id != NID_undef && EVP_PKEY_id(key) == id; +#endif +} + +// Returns true unless the key is known not to be SM2, so that a key whose curve +// cannot be determined opts out of the prehashed fallback rather than into it. +bool EVPKeyPointer::mayBeSM2() const { +#ifdef OPENSSL_NO_SM2 + return false; +#else + if (isA(KeyAlgorithm::SM2)) return true; + if (!isA(KeyAlgorithm::EC)) return false; + +#if NCRYPTO_USE_OPENSSL3_PROVIDER + // An ECKeyPointer would also need the public point, which a provider-backed + // key need not expose. + char group_name[64]; + size_t group_name_len = 0; + if (EVP_PKEY_get_utf8_string_param(get(), + OSSL_PKEY_PARAM_GROUP_NAME, + group_name, + sizeof(group_name), + &group_name_len) != 1) { + return true; + } + return OBJ_sn2nid(group_name) == NID_sm2 || + EC_curve_nist2nid(group_name) == NID_sm2; +#else + ECKeyPointer ec(*this); + if (!ec) return true; + + const EC_GROUP* group = ec.getGroup(); + if (group == nullptr) return true; + return EC_GROUP_get_curve_name(group) == NID_sm2; +#endif +#endif +} + +bool EVPKeyPointer::isA(const char* name) const { + return isA(get(), name); +} + +bool EVPKeyPointer::isA(const EVP_PKEY* key, const KeyAlgorithm& algorithm) { + return isA(key, algorithm.name()); +} + +bool EVPKeyPointer::isA(const KeyAlgorithm& algorithm) const { + return isA(get(), algorithm); +} + +const KeyAlgorithm* EVPKeyPointer::getAlgorithm() const { + if (!pkey_) return nullptr; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + // Provider primary names identify algorithms. Legacy ASN.1 methods can + // share names (for example, SM2 uses EC), so resolve those through isA(). + // The fallback also handles providers with a noncanonical primary alias. + if (EVP_PKEY_get0_provider(get()) != nullptr) { + if (const auto* algorithm = + KeyAlgorithm::FromName(EVP_PKEY_get0_type_name(get()))) { + return algorithm; } } + for (const auto* algorithm : kKeyAlgorithms) { + if (isA(*algorithm)) return algorithm; + } +#else + const int id = EVP_PKEY_id(get()); + for (const auto& algorithm : kLegacyKeyAlgorithms) { + if (id == algorithm.id) return KeyAlgorithm::FromName(algorithm.name); + } +#endif + return nullptr; +} + +const char* EVPKeyPointer::getKeyTypeName() const { + const auto* algorithm = getAlgorithm(); + return algorithm == nullptr ? nullptr : algorithm->keyTypeName(); +} + +bool EVPKeyPointer::supportsRawPublic() const { + const auto* algorithm = getAlgorithm(); + return algorithm != nullptr && algorithm->supportsRawPublic(); +} + +bool EVPKeyPointer::supportsRawPrivate() const { + const auto* algorithm = getAlgorithm(); + return algorithm != nullptr && algorithm->supportsRawPrivate(); +} + +bool EVPKeyPointer::supportsContextString() const { + const auto* algorithm = getAlgorithm(); + if (algorithm == nullptr || !algorithm->isOneShot()) return false; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + MarkPopErrorOnReturn mark_pop_error_on_return; + DeleteFnPtr signature( + EVP_SIGNATURE_fetch(nullptr, EVP_PKEY_get0_type_name(get()), nullptr)); + if (!signature) return false; + const OSSL_PARAM* params = EVP_SIGNATURE_settable_ctx_params(signature.get()); + return params != nullptr && + OSSL_PARAM_locate_const(params, kSignatureContextString) != nullptr && + (algorithm != &KeyAlgorithm::ED25519 || + OSSL_PARAM_locate_const(params, kSignatureInstance) != nullptr); +#elif NCRYPTO_USE_BORINGSSL + return algorithm->isPqc(); +#else + return false; #endif - return type; } -int EVPKeyPointer::base_id(const EVP_PKEY* key) { - if (key == nullptr) return 0; - return EVP_PKEY_base_id(key); +namespace { +constexpr size_t kEd25519PointSize = 32; +constexpr size_t kEd448PointSize = 57; + +// Ed25519 has cofactor 8, so the first eight entries are the full +// canonical small-order subgroup: identity, one point of order 2, +// two points of order 4, and four points of order 8. +constexpr unsigned char kEd25519SmallOrderPoints[][kEd25519PointSize] = { + // Identity. + {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + // Order 2. + {0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, + // Order 4. + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + // Order 8. + {0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, + 0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, + 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0x7a}, + {0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, + 0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, + 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0xfa}, + {0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, + 0x89, 0xf2, 0xef, 0x98, 0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, + 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x05}, + {0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, + 0x89, 0xf2, 0xef, 0x98, 0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, + 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x85}, + // Non-canonical encodings of the same small-order points. + {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80}, + {0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + {0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, + {0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + {0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + {0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, +}; + +// Ed448 has cofactor 4, so these four entries are the full canonical +// small-order subgroup: identity, one point of order 2, and two points +// of order 4. +constexpr unsigned char kEd448SmallOrderPoints[][kEd448PointSize] = { + // Identity. + {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + // Order 2. + {0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00}, + // Order 4. + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80}, +}; + +template +bool ContainsPoint(const unsigned char* candidate, + const unsigned char (&points)[Count][PointSize]) { + for (const auto& point : points) { + if (memcmp(candidate, point, PointSize) == 0) return true; + } + return false; } -int EVPKeyPointer::id() const { - return id(get()); +bool IsSmallOrderEdDsaPoint(const EVPKeyPointer& key, + const unsigned char* candidate, + size_t size) { + if (key.isA(KeyAlgorithm::ED25519)) { + return size == kEd25519PointSize && + ContainsPoint(candidate, kEd25519SmallOrderPoints); + } + if (key.isA(KeyAlgorithm::ED448)) { + return size == kEd448PointSize && + ContainsPoint(candidate, kEd448SmallOrderPoints); + } + return false; } -int EVPKeyPointer::base_id() const { - return base_id(get()); +} // namespace + +bool EVPKeyPointer::hasSmallOrderEdDsaPoint( + const Buffer& signature) const { + const size_t point_size = isA(KeyAlgorithm::ED25519) ? kEd25519PointSize + : isA(KeyAlgorithm::ED448) ? kEd448PointSize + : 0; + if (point_size == 0) return false; + + if (signature.len != point_size * 2) return false; + + if (IsSmallOrderEdDsaPoint(*this, signature.data, point_size)) { + return true; + } + + unsigned char raw_public_key[kEd448PointSize]; + size_t raw_public_key_size = point_size; + if (EVP_PKEY_get_raw_public_key( + get(), raw_public_key, &raw_public_key_size) != 1) { + return false; + } + + return IsSmallOrderEdDsaPoint(*this, raw_public_key, raw_public_key_size); } int EVPKeyPointer::bits() const { @@ -3239,20 +3570,77 @@ DataPointer EVPKeyPointer::rawPublicKey() const { return {}; } -#if OPENSSL_WITH_PQC -DataPointer EVPKeyPointer::rawSeed() const { - if (!pkey_) return {}; +namespace { +DataPointer GetRawSeed([[maybe_unused]] EVP_PKEY* key, size_t seed_len) { + auto data = DataPointer::Alloc(seed_len); + if (!data) return {}; +#if NCRYPTO_USE_BORINGSSL || NCRYPTO_USE_OPENSSL3_PROVIDER + const Buffer buf = data; + size_t len = data.size(); +#endif +#if NCRYPTO_USE_BORINGSSL + if (EVP_PKEY_get_private_seed(key, buf.data, &len) != 1) return {}; +#elif NCRYPTO_USE_OPENSSL3_PROVIDER + if (EVP_PKEY_get_octet_string_param(key, "seed", buf.data, buf.len, &len) != + 1) + return {}; +#else + return {}; +#endif + return data; +} - const size_t seed_len = GetPqcSeedSize(id()); +} // namespace - if (auto data = DataPointer::Alloc(seed_len)) { - const Buffer buf = data; - if (!GetPqcSeed(get(), id(), buf)) return {}; - return data; +Result EVPKeyPointer::rawSeed() + const { + const auto* algorithm = getAlgorithm(); + if (algorithm == nullptr || algorithm->seedSize() == 0) { + return RawExportError::UNSUPPORTED_KEY_TYPE; + } + auto data = GetRawSeed(get(), algorithm->seedSize()); + if (!data) return RawExportError::MISSING_SEED; + return std::move(data); +} + +Result +EVPKeyPointer::exportRawJwk(bool include_private) const { + const auto* algorithm = getAlgorithm(); + if (algorithm == nullptr || (!algorithm->isOkp() && !algorithm->isPqc())) { + return RawExportError::UNSUPPORTED_KEY_TYPE; + } + RawJwkData data{algorithm, {}, {}}; + if (include_private) { + const size_t seed_len = algorithm->seedSize(); + data.private_key = + seed_len != 0 ? GetRawSeed(get(), seed_len) : rawPrivateKey(); + if (!data.private_key) { + return seed_len != 0 ? RawExportError::MISSING_SEED + : RawExportError::FAILED; + } } - return {}; + data.public_key = rawPublicKey(); + if (!data.public_key) return RawExportError::FAILED; + return std::move(data); +} + +EVPKeyPointer EVPKeyPointer::NewRawJwk( + const KeyAlgorithm& algorithm, + const Buffer& public_key, + const std::optional>& private_key) { + if (!algorithm.isOkp() && !algorithm.isPqc()) return {}; + if (!private_key) return NewRawPublic(algorithm, public_key); + auto key = algorithm.seedSize() != 0 ? NewRawSeed(algorithm, *private_key) + : NewRawPrivate(algorithm, *private_key); + if (!key) return {}; + const auto derived_public = key.rawPublicKey(); + if (!derived_public || derived_public.size() != public_key.len || + CRYPTO_memcmp(derived_public.get(), public_key.data, public_key.len) != + 0) { + return {}; + } + return key; } -#endif DataPointer EVPKeyPointer::rawPrivateKey() const { if (!pkey_) return {}; @@ -3337,7 +3725,7 @@ bool EVPKeyPointer::set(const ECKeyPointer& eckey) { OSSLParamPointer params(OSSL_PARAM_BLD_to_param(bld.get())); if (!params) return false; - auto pkey = NewPKeyFromData(EVP_PKEY_EC, selection, params.get()); + auto pkey = NewPKeyFromData(KeyAlgorithm::EC, selection, params.get()); if (!pkey) return false; reset(pkey.release()); return true; @@ -3482,7 +3870,7 @@ EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePublicKeyPEM( bp, "RSA PUBLIC KEY", [](const unsigned char** p, long l) { // NOLINT(runtime/int) - return d2i_PublicKey(EVP_PKEY_RSA, nullptr, p, l); + return d2i_PublicKey(NID_rsaEncryption, nullptr, p, l); })) { return ret; } @@ -3517,7 +3905,7 @@ EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePublicKey( EVP_PKEY* key = nullptr; if (config.type == PKEncodingType::PKCS1 && - (key = d2i_PublicKey(EVP_PKEY_RSA, nullptr, &start, buffer.len))) { + (key = d2i_PublicKey(NID_rsaEncryption, nullptr, &start, buffer.len))) { return EVPKeyPointer::ParseKeyResult(EVPKeyPointer(key)); } @@ -3633,7 +4021,7 @@ bool WriteEncryptedTraditionalPEM(BIO* bio, } bool ECKeyHasMissingOid(const EVPKeyPointer& key) { - if (key.id() != EVP_PKEY_EC) return false; + if (!key.isA(KeyAlgorithm::EC)) return false; const Ec ec(key.get()); const EC_GROUP* group = ec.getGroup(); @@ -3827,7 +4215,7 @@ Result EVPKeyPointer::writePrivateKey( switch (config.type) { case PKEncodingType::PKCS1: { // PKCS1 is only permitted for RSA keys. - if (id() != EVP_PKEY_RSA) return Result(false); + if (!isA(KeyAlgorithm::RSA)) return Result(false); #if NCRYPTO_USE_OPENSSL3_PROVIDER const EVP_CIPHER* cipher = @@ -3909,7 +4297,7 @@ Result EVPKeyPointer::writePrivateKey( } case PKEncodingType::SEC1: { // SEC1 is only permitted for EC keys - if (id() != EVP_PKEY_EC) return Result(false); + if (!isA(KeyAlgorithm::EC)) return Result(false); #if NCRYPTO_USE_OPENSSL3_PROVIDER const EVP_CIPHER* cipher = @@ -3979,7 +4367,7 @@ Result EVPKeyPointer::writePublicKey( if (config.type == ncrypto::EVPKeyPointer::PKEncodingType::PKCS1) { // PKCS#1 is only valid for RSA keys. #if NCRYPTO_USE_OPENSSL3_PROVIDER - if (id() != EVP_PKEY_RSA) return Result(false); + if (!isA(KeyAlgorithm::RSA)) return Result(false); if (!WriteEncodedPKey(bio.get(), get(), OSSL_KEYMGMT_SELECT_PUBLIC_KEY, @@ -4058,59 +4446,30 @@ Result EVPKeyPointer::writePublicKey( return bio; } -bool EVPKeyPointer::isRsaVariant() const { - if (!pkey_) return false; - int type = id(); - return type == EVP_PKEY_RSA || type == EVP_PKEY_RSA2 || - type == EVP_PKEY_RSA_PSS; +bool EVPKeyPointer::isRsaVariant(const EVP_PKEY* key) { +#if !NCRYPTO_USE_OPENSSL3_PROVIDER && !NCRYPTO_USE_BORINGSSL + if (key != nullptr && EVP_PKEY_id(key) == EVP_PKEY_RSA2) return true; +#endif + return isA(key, KeyAlgorithm::RSA) || isA(key, KeyAlgorithm::RSA_PSS); } -bool EVPKeyPointer::isOneShotVariant() const { - if (!pkey_) return false; - int type = id(); - switch (type) { - case EVP_PKEY_ED25519: - case EVP_PKEY_ED448: -#if OPENSSL_WITH_PQC - case EVP_PKEY_ML_DSA_44: - case EVP_PKEY_ML_DSA_65: - case EVP_PKEY_ML_DSA_87: -#if OPENSSL_WITH_PQC_SLH_DSA - case EVP_PKEY_SLH_DSA_SHA2_128F: - case EVP_PKEY_SLH_DSA_SHA2_128S: - case EVP_PKEY_SLH_DSA_SHA2_192F: - case EVP_PKEY_SLH_DSA_SHA2_192S: - case EVP_PKEY_SLH_DSA_SHA2_256F: - case EVP_PKEY_SLH_DSA_SHA2_256S: - case EVP_PKEY_SLH_DSA_SHAKE_128F: - case EVP_PKEY_SLH_DSA_SHAKE_128S: - case EVP_PKEY_SLH_DSA_SHAKE_192F: - case EVP_PKEY_SLH_DSA_SHAKE_192S: - case EVP_PKEY_SLH_DSA_SHAKE_256F: - case EVP_PKEY_SLH_DSA_SHAKE_256S: -#endif -#endif - return true; - default: - return false; - } +bool EVPKeyPointer::isRsaVariant() const { + return isRsaVariant(get()); } bool EVPKeyPointer::isSigVariant() const { - if (!pkey_) return false; - int type = id(); - return type == EVP_PKEY_EC || type == EVP_PKEY_DSA; + return isA(KeyAlgorithm::EC) || isA(KeyAlgorithm::DSA); } int EVPKeyPointer::getDefaultSignPadding() const { - return id() == EVP_PKEY_RSA_PSS ? RSA_PKCS1_PSS_PADDING : RSA_PKCS1_PADDING; + return isA(KeyAlgorithm::RSA_PSS) ? RSA_PKCS1_PSS_PADDING : RSA_PKCS1_PADDING; } std::optional EVPKeyPointer::getBytesOfRS() const { if (!pkey_) return std::nullopt; - int bits, id = base_id(); + int bits; - if (id == EVP_PKEY_DSA) { + if (isA(KeyAlgorithm::DSA)) { #if NCRYPTO_USE_OPENSSL3_PROVIDER DeleteFnPtr q; if (!GetPKeyBnParam(get(), OSSL_PKEY_PARAM_FFC_Q, &q)) return std::nullopt; @@ -4128,7 +4487,7 @@ std::optional EVPKeyPointer::getBytesOfRS() const { } if (!has_bits) return std::nullopt; #endif - } else if (id == EVP_PKEY_EC) { + } else if (isA(KeyAlgorithm::EC)) { #if NCRYPTO_USE_OPENSSL3_PROVIDER bits = EVP_PKEY_bits(get()); #else @@ -4148,8 +4507,7 @@ std::optional EVPKeyPointer::getBytesOfRS() const { } EVPKeyPointer::operator Rsa() const { - int type = id(); - if (type != EVP_PKEY_RSA && type != EVP_PKEY_RSA_PSS) return {}; + if (!isA(KeyAlgorithm::RSA) && !isA(KeyAlgorithm::RSA_PSS)) return {}; #if NCRYPTO_USE_OPENSSL3_PROVIDER return Rsa(get()); @@ -4168,8 +4526,7 @@ EVPKeyPointer::operator Rsa() const { } EVPKeyPointer::operator Dsa() const { - int type = id(); - if (type != EVP_PKEY_DSA) return {}; + if (!isA(KeyAlgorithm::DSA)) return {}; #if NCRYPTO_USE_OPENSSL3_PROVIDER return Dsa(get()); @@ -4183,9 +4540,10 @@ EVPKeyPointer::operator Dsa() const { bool EVPKeyPointer::validateDsaParameters() const { if (!pkey_) return false; #if OPENSSL_VERSION_MAJOR >= 3 - if (EVP_default_properties_is_fips_enabled(nullptr) && EVP_PKEY_DSA == id()) { + if (EVP_default_properties_is_fips_enabled(nullptr) && + isA(KeyAlgorithm::DSA)) { #else - if (FIPS_mode() && EVP_PKEY_DSA == id()) { + if (FIPS_mode() && isA(KeyAlgorithm::DSA)) { #endif // Validate DSA2 parameters from FIPS 186-4. #if NCRYPTO_USE_OPENSSL3_PROVIDER @@ -5366,7 +5724,7 @@ bool ECKeyPointer::checkPrivateKey() const { ECKeyPointer::ECKeyPointer() : key_(nullptr) {} ECKeyPointer::ECKeyPointer(const EVPKeyPointer& key) : key_(nullptr) { - if (key.id() != EVP_PKEY_EC) return; + if (!key.isA(KeyAlgorithm::EC)) return; const EC_KEY* ec = key; if (ec != nullptr) key_.reset(EC_KEY_dup(ec)); } @@ -5519,7 +5877,7 @@ ECKeyPointer ECKeyPointer::New(const EC_GROUP* group) { ECKeyPointer::ECKeyPointer() : group_(nullptr), pub_(nullptr), priv_(nullptr) {} ECKeyPointer::ECKeyPointer(const EVPKeyPointer& key) : ECKeyPointer() { - if (key.id() != EVP_PKEY_EC) return; + if (!key.isA(KeyAlgorithm::EC)) return; char group_name[80]; size_t group_name_len = 0; if (EVP_PKEY_get_utf8_string_param(key.get(), @@ -5605,7 +5963,7 @@ ECKeyPointer ECKeyPointer::clone() const { bool ECKeyPointer::generate() { if (!group_) return false; const int nid = EC_GROUP_get_curve_name(group_.get()); - auto ctx = EVPKeyCtxPointer::NewFromID(EVP_PKEY_EC); + auto ctx = EVPKeyCtxPointer::NewFromAlgorithm(KeyAlgorithm::EC); if (!ctx || !ctx.initForKeygen() || !ctx.setEcParameters(nid, OPENSSL_EC_NAMED_CURVE)) { return false; @@ -5804,12 +6162,24 @@ EVPKeyCtxPointer EVPKeyCtxPointer::New(const EVPKeyPointer& key) { return EVPKeyCtxPointer(EVP_PKEY_CTX_new(key.get(), nullptr)); } -EVPKeyCtxPointer EVPKeyCtxPointer::NewFromID(int id) { +EVPKeyCtxPointer EVPKeyCtxPointer::NewFromName(const char* name) { + if (name == nullptr) return {}; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + return EVPKeyCtxPointer(EVP_PKEY_CTX_new_from_name(nullptr, name, nullptr)); +#else + const int id = GetLegacyKeyId(name); + if (id == NID_undef) return {}; #ifdef OPENSSL_IS_BORINGSSL - // DSA keys are not supported with BoringSSL + // DSA keys are not supported with BoringSSL. if (id == EVP_PKEY_DSA) return {}; #endif return EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(id, nullptr)); +#endif +} + +EVPKeyCtxPointer EVPKeyCtxPointer::NewFromAlgorithm( + const KeyAlgorithm& algorithm) { + return NewFromName(algorithm.name()); } bool EVPKeyCtxPointer::initForDerive(const EVPKeyPointer& peer) { @@ -6404,9 +6774,8 @@ ASN1StringPointer EncodeRsaPssParams(const Rsa::PssParams& params) { Rsa::Rsa() : rsa_(false) {} Rsa::Rsa(const EVP_PKEY* pkey) : Rsa() { - const int type = EVPKeyPointer::id(pkey); - if (type != EVP_PKEY_RSA && type != EVP_PKEY_RSA_PSS) return; - rsa_pss_ = type == EVP_PKEY_RSA_PSS; + rsa_pss_ = EVPKeyPointer::isA(pkey, KeyAlgorithm::RSA_PSS); + if (!EVPKeyPointer::isA(pkey, KeyAlgorithm::RSA) && !rsa_pss_) return; if (!GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_N, &n_) || !GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_E, &e_)) { return; @@ -6433,7 +6802,7 @@ Rsa::Rsa(const EVP_PKEY* pkey) : Rsa() { other_prime_infos_.push_back(std::move(info)); } - if (type == EVP_PKEY_RSA_PSS) { + if (rsa_pss_) { MarkPopErrorOnReturn pop_errors; PssParams params; if (ReadRsaPssParams(pkey, ¶ms)) pss_params_ = params; @@ -6500,6 +6869,26 @@ const Rsa::OtherPrimeInfos Rsa::getOtherPrimeInfos() const { return infos; } +bool Rsa::checkPrimeProduct() const { + const auto pub = getPublicKey(); + const auto priv = getPrivateKey(); + if (pub.n == nullptr || priv.p == nullptr || priv.q == nullptr) return false; + auto product = BignumPointer::New(); + BignumCtxPointer ctx(BN_CTX_new()); + if (!product || !ctx || + BN_mul(product.get(), priv.p, priv.q, ctx.get()) != 1) { + return false; + } + for (const auto& info : getOtherPrimeInfos()) { + auto next = BignumPointer::New(); + if (!next || BN_mul(next.get(), product.get(), info.r, ctx.get()) != 1) { + return false; + } + product = std::move(next); + } + return BN_cmp(product.get(), pub.n) == 0; +} + const std::optional Rsa::getPssParams() const { #if NCRYPTO_USE_OPENSSL3_PROVIDER return pss_params_; @@ -6834,7 +7223,7 @@ void Cipher::ForEach(Cipher::CipherNameCallback callback) { Ec::Ec() : ec_(nullptr), pub_(nullptr) {} Ec::Ec(const EVP_PKEY* pkey) : Ec() { - if (EVPKeyPointer::id(pkey) != EVP_PKEY_EC) return; + if (!EVPKeyPointer::isA(pkey, KeyAlgorithm::EC)) return; char group_name[80]; size_t group_name_len = 0; if (EVP_PKEY_get_utf8_string_param(pkey, @@ -6931,7 +7320,7 @@ int Ec::getCurve() const { DataPointer Ec::TryExportPublic(const EVPKeyPointer& key, point_conversion_form_t form) { - if (form != POINT_CONVERSION_UNCOMPRESSED) return {}; + if (!key || form != POINT_CONVERSION_UNCOMPRESSED) return {}; #if NCRYPTO_USE_OPENSSL3_PROVIDER { MarkPopErrorOnReturn pop_errors; @@ -6955,6 +7344,7 @@ DataPointer Ec::TryExportPublic(const EVPKeyPointer& key, } DataPointer Ec::ExportPrivate(const EVPKeyPointer& key) { + if (!key) return {}; #if NCRYPTO_USE_OPENSSL3_PROVIDER { MarkPopErrorOnReturn pop_errors; @@ -6979,6 +7369,7 @@ bool Ec::GetKeyComponents(const EVPKeyPointer& key, BignumPointer* y, BignumPointer* priv, int* degree) { + if (!key) return false; #if NCRYPTO_USE_OPENSSL3_PROVIDER const int nid = GetCurveId(key); switch (nid) { @@ -7034,6 +7425,7 @@ bool Ec::GetKeyComponents(const EVPKeyPointer& key, } int Ec::GetCurveId(const EVPKeyPointer& key) { + if (!key) return NID_undef; #if NCRYPTO_USE_OPENSSL3_PROVIDER char name[80]; size_t length = 0; @@ -7059,6 +7451,14 @@ int Ec::GetCurveIdFromName(const char* name) { return nid; } +const KeyAlgorithm* Ec::GetNamedKeyAlgorithm(const char* name) { + // Preserve the aliases accepted by the historical namedCurve option. + const int nid = GetCurveIdFromName(name); + if (nid == NID_undef) return nullptr; + const auto* algorithm = KeyAlgorithm::FromName(OBJ_nid2sn(nid)); + return algorithm != nullptr && algorithm->isOkp() ? algorithm : nullptr; +} + bool Ec::GetCurves(Ec::GetCurveCallback callback) { const size_t count = EC_get_builtin_curves(nullptr, 0); std::vector curves(count); @@ -7202,18 +7602,17 @@ std::optional EVPMDCtxPointer::signInitWithContext( return std::nullopt; } return ctx; -#elif defined(OSSL_SIGNATURE_PARAM_CONTEXT_STRING) +#elif NCRYPTO_USE_OPENSSL3_PROVIDER EVP_PKEY_CTX* ctx = nullptr; -#ifdef OSSL_SIGNATURE_PARAM_INSTANCE // Ed25519 requires the INSTANCE param to switch into Ed25519ctx mode. // Without it, OpenSSL silently ignores the context string. - if (key.id() == EVP_PKEY_ED25519) { + if (key.isA(KeyAlgorithm::ED25519)) { const OSSL_PARAM params[] = { OSSL_PARAM_construct_utf8_string( - OSSL_SIGNATURE_PARAM_INSTANCE, const_cast("Ed25519ctx"), 0), + kSignatureInstance, const_cast("Ed25519ctx"), 0), OSSL_PARAM_construct_octet_string( - OSSL_SIGNATURE_PARAM_CONTEXT_STRING, + kSignatureContextString, const_cast(context_string.data), context_string.len), OSSL_PARAM_END}; @@ -7224,11 +7623,10 @@ std::optional EVPMDCtxPointer::signInitWithContext( } return ctx; } -#endif // OSSL_SIGNATURE_PARAM_INSTANCE const OSSL_PARAM params[] = { OSSL_PARAM_construct_octet_string( - OSSL_SIGNATURE_PARAM_CONTEXT_STRING, + kSignatureContextString, const_cast(context_string.data), context_string.len), OSSL_PARAM_END}; @@ -7257,18 +7655,17 @@ std::optional EVPMDCtxPointer::verifyInitWithContext( return std::nullopt; } return ctx; -#elif defined(OSSL_SIGNATURE_PARAM_CONTEXT_STRING) +#elif NCRYPTO_USE_OPENSSL3_PROVIDER EVP_PKEY_CTX* ctx = nullptr; -#ifdef OSSL_SIGNATURE_PARAM_INSTANCE // Ed25519 requires the INSTANCE param to switch into Ed25519ctx mode. // Without it, OpenSSL silently ignores the context string. - if (key.id() == EVP_PKEY_ED25519) { + if (key.isA(KeyAlgorithm::ED25519)) { const OSSL_PARAM params[] = { OSSL_PARAM_construct_utf8_string( - OSSL_SIGNATURE_PARAM_INSTANCE, const_cast("Ed25519ctx"), 0), + kSignatureInstance, const_cast("Ed25519ctx"), 0), OSSL_PARAM_construct_octet_string( - OSSL_SIGNATURE_PARAM_CONTEXT_STRING, + kSignatureContextString, const_cast(context_string.data), context_string.len), OSSL_PARAM_END}; @@ -7279,11 +7676,10 @@ std::optional EVPMDCtxPointer::verifyInitWithContext( } return ctx; } -#endif // OSSL_SIGNATURE_PARAM_INSTANCE const OSSL_PARAM params[] = { OSSL_PARAM_construct_octet_string( - OSSL_SIGNATURE_PARAM_CONTEXT_STRING, + kSignatureContextString, const_cast(context_string.data), context_string.len), OSSL_PARAM_END}; @@ -7807,7 +8203,7 @@ std::pair X509Name::Iterator::operator*() const { Dsa::Dsa() : dsa_(false) {} Dsa::Dsa(const EVP_PKEY* pkey) : Dsa() { - if (EVPKeyPointer::id(pkey) != EVP_PKEY_DSA) return; + if (!EVPKeyPointer::isA(pkey, KeyAlgorithm::DSA)) return; if (!GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_FFC_P, &p_) || !GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_FFC_Q, &q_)) { return; @@ -7953,37 +8349,25 @@ const Digest Digest::Fetch(const char* name) { // ============================================================================ // KEM Implementation #if OPENSSL_WITH_KEM -#if OPENSSL_WITH_KEM_OPERATION_PARAM +#if NCRYPTO_USE_OPENSSL3_PROVIDER bool KEM::SetOperationParameter(EVP_PKEY_CTX* ctx, const EVPKeyPointer& key) { - const char* operation = nullptr; - - switch (EVP_PKEY_id(key.get())) { - case EVP_PKEY_RSA: - operation = OSSL_KEM_PARAM_OPERATION_RSASVE; - break; -#if OPENSSL_WITH_OPENSSL_DHKEM - case EVP_PKEY_EC: - case EVP_PKEY_X25519: - case EVP_PKEY_X448: - operation = OSSL_KEM_PARAM_OPERATION_DHKEM; - break; -#endif - default: - unreachable(); - } - - if (operation != nullptr) { - OSSL_PARAM params[] = { - OSSL_PARAM_utf8_string( - OSSL_KEM_PARAM_OPERATION, const_cast(operation), 0), - OSSL_PARAM_END}; + const OSSL_PARAM* settable = EVP_PKEY_CTX_settable_params(ctx); + if (settable == nullptr || + OSSL_PARAM_locate_const(settable, "operation") == nullptr) + return true; - if (EVP_PKEY_CTX_set_params(ctx, params) <= 0) { - return false; - } + const char* operation = nullptr; + if (key.isA(KeyAlgorithm::RSA)) { + operation = "RSASVE"; + } else if (key.isA(KeyAlgorithm::EC) || key.isA(KeyAlgorithm::X25519) || + key.isA(KeyAlgorithm::X448)) { + operation = "DHKEM"; } - - return true; + if (operation == nullptr) return true; + OSSL_PARAM params[] = { + OSSL_PARAM_utf8_string("operation", const_cast(operation), 0), + OSSL_PARAM_END}; + return EVP_PKEY_CTX_set_params(ctx, params) > 0; } #endif @@ -7998,7 +8382,7 @@ std::optional KEM::Encapsulate( return std::nullopt; } -#if OPENSSL_WITH_KEM_OPERATION_PARAM +#if NCRYPTO_USE_OPENSSL3_PROVIDER if (!SetOperationParameter(ctx.get(), public_key)) { return std::nullopt; } @@ -8039,7 +8423,7 @@ DataPointer KEM::Decapsulate(const EVPKeyPointer& private_key, return {}; } -#if OPENSSL_WITH_KEM_OPERATION_PARAM +#if NCRYPTO_USE_OPENSSL3_PROVIDER if (!SetOperationParameter(ctx.get(), private_key)) { return {}; } diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index 944c42490d6c..53151503c364 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -120,53 +121,6 @@ #define OPENSSL_WITH_AES_GCM_SIV 0 #endif -#if defined(OPENSSL_IS_BORINGSSL) || OPENSSL_VERSION_PREREQ(3, 2) -#define OPENSSL_WITH_SIGNATURE_CONTEXT_STRING 1 -#else -#define OPENSSL_WITH_SIGNATURE_CONTEXT_STRING 0 -#endif - -#if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_PREREQ(3, 2) -#define OPENSSL_WITH_OPENSSL_DHKEM 1 -#else -#define OPENSSL_WITH_OPENSSL_DHKEM 0 -#endif - -#if OPENSSL_WITH_KEM && !defined(OPENSSL_IS_BORINGSSL) && \ - !OPENSSL_VERSION_PREREQ(3, 5) -#define OPENSSL_WITH_KEM_OPERATION_PARAM 1 -#else -#define OPENSSL_WITH_KEM_OPERATION_PARAM 0 -#endif - -// Post-quantum cryptography support. Keep these explicit so code can -// distinguish provider API shape from the available algorithm set. -#if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_PREREQ(3, 5) -#define OPENSSL_WITH_OPENSSL_PQC 1 -#else -#define OPENSSL_WITH_OPENSSL_PQC 0 -#endif - -#ifdef OPENSSL_IS_BORINGSSL -#define OPENSSL_WITH_BORINGSSL_PQC 1 -#else -#define OPENSSL_WITH_BORINGSSL_PQC 0 -#endif - -#define OPENSSL_WITH_PQC \ - (OPENSSL_WITH_OPENSSL_PQC || OPENSSL_WITH_BORINGSSL_PQC) -#define OPENSSL_WITH_PQC_ML_KEM_512 OPENSSL_WITH_OPENSSL_PQC -#define OPENSSL_WITH_PQC_SLH_DSA OPENSSL_WITH_OPENSSL_PQC - -#if OPENSSL_WITH_OPENSSL_PQC -#define EVP_PKEY_ML_KEM_512 NID_ML_KEM_512 -#define EVP_PKEY_ML_KEM_768 NID_ML_KEM_768 -#define EVP_PKEY_ML_KEM_1024 NID_ML_KEM_1024 -#elif OPENSSL_WITH_BORINGSSL_PQC -#define EVP_PKEY_ML_KEM_768 NID_ML_KEM_768 -#define EVP_PKEY_ML_KEM_1024 NID_ML_KEM_1024 -#endif - #if OPENSSL_VERSION_PREREQ(3, 0) #define OSSL3_CONST const #else @@ -366,6 +320,7 @@ class DataPointer; class DHPointer; class ECKeyPointer; class EVPKeyPointer; +class KeyAlgorithm; class MacCache; class EVPMacCtxPointer; class EVPMacPointer; @@ -750,6 +705,8 @@ class Rsa final { const PublicKey getPublicKey() const; const PrivateKey getPrivateKey() const; const OtherPrimeInfos getOtherPrimeInfos() const; + // Check that n is the product of all private-key prime factors. + bool checkPrimeProduct() const; const std::optional getPssParams() const; bool setPublicKey(BignumPointer&& n, BignumPointer&& e); @@ -821,6 +778,7 @@ class Ec final { BignumPointer* y, BignumPointer* priv, int* degree); + static const KeyAlgorithm* GetNamedKeyAlgorithm(const char* name); using GetCurveCallback = std::function; static bool GetCurves(GetCurveCallback callback); @@ -1094,6 +1052,79 @@ class CipherCtxPointer final { DeleteFnPtr ctx_; }; +// Known key algorithms are identified by provider names, never synthetic NIDs. +// Descriptors have static lifetime; availability is queried from the backend. +class KeyAlgorithm final { + public: + static const KeyAlgorithm RSA; + static const KeyAlgorithm RSA_PSS; + static const KeyAlgorithm DSA; + static const KeyAlgorithm DH; + static const KeyAlgorithm EC; + static const KeyAlgorithm ED25519; + static const KeyAlgorithm ED448; + static const KeyAlgorithm X25519; + static const KeyAlgorithm X448; + static const KeyAlgorithm SM2; + static const KeyAlgorithm ML_DSA_44; + static const KeyAlgorithm ML_DSA_65; + static const KeyAlgorithm ML_DSA_87; + static const KeyAlgorithm ML_KEM_512; + static const KeyAlgorithm ML_KEM_768; + static const KeyAlgorithm ML_KEM_1024; + static const KeyAlgorithm SLH_DSA_SHA2_128F; + static const KeyAlgorithm SLH_DSA_SHA2_128S; + static const KeyAlgorithm SLH_DSA_SHA2_192F; + static const KeyAlgorithm SLH_DSA_SHA2_192S; + static const KeyAlgorithm SLH_DSA_SHA2_256F; + static const KeyAlgorithm SLH_DSA_SHA2_256S; + static const KeyAlgorithm SLH_DSA_SHAKE_128F; + static const KeyAlgorithm SLH_DSA_SHAKE_128S; + static const KeyAlgorithm SLH_DSA_SHAKE_192F; + static const KeyAlgorithm SLH_DSA_SHAKE_192S; + static const KeyAlgorithm SLH_DSA_SHAKE_256F; + static const KeyAlgorithm SLH_DSA_SHAKE_256S; + + // Look up a canonical name case-insensitively, including unavailable + // algorithms. + static const KeyAlgorithm* FromName(const char* name); + using Callback = std::function; + static void ForEachPqc(Callback callback); + + const char* name() const { return name_; } + const char* keyTypeName() const { + return key_type_name_[0] == '\0' ? nullptr : key_type_name_.data(); + } + bool isRsa() const; + bool isAvailable() const; + bool isPqc() const; + bool isOkp() const; + bool isOneShot() const; + bool supportsRawPublic() const; + bool supportsRawPrivate() const; + size_t seedSize() const; + + private: + enum class Family { Other, EdDSA, XDH, MLDSA, MLKEM, SLHDSA }; + static constexpr size_t kMaxKeyTypeNameLength = 32; + template + constexpr KeyAlgorithm(const char (&name)[N], + Family family, + bool has_key_type = true) + : name_(name), family_(family) { + static_assert(N <= kMaxKeyTypeNameLength); + if (has_key_type) { + for (size_t i = 0; i < N; i++) { + key_type_name_[i] = + name[i] >= 'A' && name[i] <= 'Z' ? name[i] + ('a' - 'A') : name[i]; + } + } + } + const char* name_; + std::array key_type_name_{}; + Family family_; +}; + class EVPKeyCtxPointer final { public: EVPKeyCtxPointer(); @@ -1156,7 +1187,8 @@ class EVPKeyCtxPointer final { int initForSign(); static EVPKeyCtxPointer New(const EVPKeyPointer& key); - static EVPKeyCtxPointer NewFromID(int id); + static EVPKeyCtxPointer NewFromName(const char* name); + static EVPKeyCtxPointer NewFromAlgorithm(const KeyAlgorithm& algorithm); private: DeleteFnPtr ctx_; @@ -1165,14 +1197,12 @@ class EVPKeyCtxPointer final { class EVPKeyPointer final { public: static EVPKeyPointer New(); - static EVPKeyPointer NewRawPublic(int id, + static EVPKeyPointer NewRawPublic(const KeyAlgorithm& algorithm, const Buffer& data); - static EVPKeyPointer NewRawPrivate(int id, + static EVPKeyPointer NewRawPrivate(const KeyAlgorithm& algorithm, const Buffer& data); -#if OPENSSL_WITH_PQC - static EVPKeyPointer NewRawSeed(int id, + static EVPKeyPointer NewRawSeed(const KeyAlgorithm& algorithm, const Buffer& data); -#endif static EVPKeyPointer NewDH(DHPointer&& dh); #if NCRYPTO_USE_OPENSSL3_PROVIDER static EVPKeyPointer NewRSA(const Rsa& rsa); @@ -1277,11 +1307,19 @@ class EVPKeyPointer final { void reset(EVP_PKEY* pkey = nullptr); EVP_PKEY* release(); - static int id(const EVP_PKEY* key); - static int base_id(const EVP_PKEY* key); - - int id() const; - int base_id() const; + static bool isA(const EVP_PKEY* key, const char* name); + bool isA(const char* name) const; + static bool isA(const EVP_PKEY* key, const KeyAlgorithm& algorithm); + bool isA(const KeyAlgorithm& algorithm) const; + // Resolve a known algorithm without caching key or provider state. + const KeyAlgorithm* getAlgorithm() const; + // Stable public key-type name, or nullptr for an unsupported key type. + const char* getKeyTypeName() const; + bool supportsRawPublic() const; + bool supportsRawPrivate() const; + bool supportsContextString() const; + bool hasSmallOrderEdDsaPoint( + const Buffer& signature) const; int bits() const; size_t size() const; @@ -1291,9 +1329,22 @@ class EVPKeyPointer final { DataPointer rawPrivateKey() const; BIOPointer derPublicKey() const; -#if OPENSSL_WITH_PQC - DataPointer rawSeed() const; -#endif + enum class RawExportError { UNSUPPORTED_KEY_TYPE, MISSING_SEED, FAILED }; + Result rawSeed() const; + + struct RawJwkData { + const KeyAlgorithm* algorithm = nullptr; + DataPointer public_key; + DataPointer private_key; + }; + // Raw JWK material for OKP and AKP keys. Private bytes use the JWK + // representation (a seed for ML-DSA/ML-KEM, a raw private key otherwise). + Result exportRawJwk(bool include_private) const; + static EVPKeyPointer NewRawJwk( + const KeyAlgorithm& algorithm, + const Buffer& public_key, + const std::optional>& private_key = + std::nullopt); Result writePrivateKey( const PrivateKeyEncodingConfig& config) const; @@ -1309,9 +1360,10 @@ class EVPKeyPointer final { operator Rsa() const; operator Dsa() const; + static bool isRsaVariant(const EVP_PKEY* key); bool isRsaVariant() const; - bool isOneShotVariant() const; bool isSigVariant() const; + bool mayBeSM2() const; bool validateDsaParameters() const; private: @@ -2079,6 +2131,10 @@ class EnginePointer final { // FIPS bool isFipsEnabled(); +// Configure seed-preserving PQC private-key encoding when the backend supports +// it. +void ConfigurePqcEncoding(); + bool setFipsEnabled(bool enabled, CryptoErrorList* errors); uint64_t getFipsStateGeneration(); @@ -2190,7 +2246,7 @@ class KEM final { const Buffer& ciphertext); private: -#if OPENSSL_WITH_KEM_OPERATION_PARAM +#if NCRYPTO_USE_OPENSSL3_PROVIDER static bool SetOperationParameter(EVP_PKEY_CTX* ctx, const EVPKeyPointer& key); #endif diff --git a/lib/internal/crypto/cfrg.js b/lib/internal/crypto/cfrg.js index 1f7b3202fb10..3152362caa8b 100644 --- a/lib/internal/crypto/cfrg.js +++ b/lib/internal/crypto/cfrg.js @@ -16,11 +16,7 @@ const { kWebCryptoKeyFormatPKCS8, kWebCryptoKeyFormatRaw, kWebCryptoKeyFormatSPKI, - NidKeyPairGenJob, - EVP_PKEY_ED25519, - EVP_PKEY_ED448, - EVP_PKEY_X25519, - EVP_PKEY_X448, + NamedKeyPairGenJob, } = internalBinding('crypto'); const { @@ -67,21 +63,14 @@ function cfrgGenerateKey(algorithm, extractable, usages) { const { name } = algorithm; const allowedUsages = kUsages[name]; const usagesSet = validateKeyUsages(usages, allowedUsages.keygen, name); - const nid = { - '__proto__': null, - 'Ed25519': EVP_PKEY_ED25519, - 'Ed448': EVP_PKEY_ED448, - 'X25519': EVP_PKEY_X25519, - 'X448': EVP_PKEY_X448, - }[name]; const keyAlgorithm = { name }; const keyUsages = getKeyPairUsages(usagesSet, allowedUsages); validateUsagesNotEmpty(keyUsages.private); - return jobPromise(() => new NidKeyPairGenJob( + return jobPromise(() => new NamedKeyPairGenJob( kCryptoJobWebCrypto, - nid, + name, keyAlgorithm, getUsagesMask(keyUsages.public), getUsagesMask(keyUsages.private), @@ -170,7 +159,7 @@ function cfrgImportKey( } case 'raw': { verifyAcceptableKeyUse(name, usagesSet, allowedUsages.public); - handle = importRawKey(true, keyData, kKeyFormatRawPublic, name); + handle = importRawKey(true, keyData, kKeyFormatRawPublic, StringPrototypeToLowerCase(name)); break; } default: diff --git a/lib/internal/crypto/kem_hybrids.js b/lib/internal/crypto/kem_hybrids.js index 6565b8dcae0a..58fcb6e8fd1a 100644 --- a/lib/internal/crypto/kem_hybrids.js +++ b/lib/internal/crypto/kem_hybrids.js @@ -5,6 +5,7 @@ const { BigInt, PromiseWithResolvers, SafeSet, + StringPrototypeToLowerCase, TypedArrayOf, TypedArrayPrototypeGetBuffer, TypedArrayPrototypeGetByteLength, @@ -93,8 +94,8 @@ const kKemPq768EncapsulationKeyLength = 1184; const kKemPq768CiphertextLength = 1088; const kKemPq1024EncapsulationKeyLength = 1568; const kKemPq1024CiphertextLength = 1568; -const kKemPq768Name = 'ML-KEM-768'; -const kKemPq1024Name = 'ML-KEM-1024'; +const kKemPq768KeyType = 'ml-kem-768'; +const kKemPq1024KeyType = 'ml-kem-1024'; const kX25519Name = 'X25519'; const kEcdhName = 'ECDH'; const kEcKeyType = 'ec'; @@ -131,7 +132,7 @@ const kAlgorithms = { '__proto__': null, 'MLKEM768-P256': withHybridLengths({ name: 'MLKEM768-P256', - kemPqName: kKemPq768Name, + kemPqKeyType: kKemPq768KeyType, kemPqEncapsulationKeyLength: kKemPq768EncapsulationKeyLength, kemPqCiphertextLength: kKemPq768CiphertextLength, groupName: kEcdhName, @@ -149,11 +150,11 @@ const kAlgorithms = { }), 'MLKEM768-X25519': withHybridLengths({ name: 'MLKEM768-X25519', - kemPqName: kKemPq768Name, + kemPqKeyType: kKemPq768KeyType, kemPqEncapsulationKeyLength: kKemPq768EncapsulationKeyLength, kemPqCiphertextLength: kKemPq768CiphertextLength, groupName: kX25519Name, - groupKeyType: kX25519Name, + groupKeyType: StringPrototypeToLowerCase(kX25519Name), // Curve25519 RandomScalar is identity and Exp is X25519. // https://www.rfc-editor.org/rfc/rfc7748#section-5 groupElementLength: kX25519KeyLength, @@ -166,7 +167,7 @@ const kAlgorithms = { }), 'MLKEM1024-P384': withHybridLengths({ name: 'MLKEM1024-P384', - kemPqName: kKemPq1024Name, + kemPqKeyType: kKemPq1024KeyType, kemPqEncapsulationKeyLength: kKemPq1024EncapsulationKeyLength, kemPqCiphertextLength: kKemPq1024CiphertextLength, groupName: kEcdhName, @@ -400,7 +401,7 @@ function importKemPqDecapsulationHandle(seed, config) { kKeyTypePrivate, seed, kKeyFormatRawSeed, - config.kemPqName); + config.kemPqKeyType); } /** @@ -414,7 +415,7 @@ function importKemPqEncapsulationHandle(rawEncapsulationKey, config) { kKeyTypePublic, rawEncapsulationKey, kKeyFormatRawPublic, - config.kemPqName); + config.kemPqKeyType); } /** diff --git a/lib/internal/crypto/keygen.js b/lib/internal/crypto/keygen.js index 038582a044c2..4ac2fdee2df6 100644 --- a/lib/internal/crypto/keygen.js +++ b/lib/internal/crypto/keygen.js @@ -4,41 +4,21 @@ const { FunctionPrototypeCall, ObjectDefineProperty, SafeArrayIterator, + StringPrototypeToLowerCase, } = primordials; const { DhKeyPairGenJob, DsaKeyPairGenJob, EcKeyPairGenJob, - NidKeyPairGenJob, + NamedKeyPairGenJob, + getPqcKeyTypes, RsaKeyPairGenJob, SecretKeyGenJob, kCryptoJobAsync, kCryptoJobSync, kKeyVariantRSA_PSS, kKeyVariantRSA_SSA_PKCS1_v1_5, - EVP_PKEY_ED25519, - EVP_PKEY_ED448, - EVP_PKEY_ML_DSA_44, - EVP_PKEY_ML_DSA_65, - EVP_PKEY_ML_DSA_87, - EVP_PKEY_ML_KEM_1024, - EVP_PKEY_ML_KEM_512, - EVP_PKEY_ML_KEM_768, - EVP_PKEY_SLH_DSA_SHA2_128F, - EVP_PKEY_SLH_DSA_SHA2_128S, - EVP_PKEY_SLH_DSA_SHA2_192F, - EVP_PKEY_SLH_DSA_SHA2_192S, - EVP_PKEY_SLH_DSA_SHA2_256F, - EVP_PKEY_SLH_DSA_SHA2_256S, - EVP_PKEY_SLH_DSA_SHAKE_128F, - EVP_PKEY_SLH_DSA_SHAKE_128S, - EVP_PKEY_SLH_DSA_SHAKE_192F, - EVP_PKEY_SLH_DSA_SHAKE_192S, - EVP_PKEY_SLH_DSA_SHAKE_256F, - EVP_PKEY_SLH_DSA_SHAKE_256S, - EVP_PKEY_X25519, - EVP_PKEY_X448, OPENSSL_EC_NAMED_CURVE, OPENSSL_EC_EXPLICIT_CURVE, } = internalBinding('crypto'); @@ -180,31 +160,16 @@ function parseKeyEncoding(keyType, options = kEmptyObject) { ]; } -const nidOnlyKeyPairs = { +const namedKeyPairs = { '__proto__': null, - 'ed25519': EVP_PKEY_ED25519, - 'ed448': EVP_PKEY_ED448, - 'x25519': EVP_PKEY_X25519, - 'x448': EVP_PKEY_X448, - 'ml-dsa-44': EVP_PKEY_ML_DSA_44, - 'ml-dsa-65': EVP_PKEY_ML_DSA_65, - 'ml-dsa-87': EVP_PKEY_ML_DSA_87, - 'ml-kem-512': EVP_PKEY_ML_KEM_512, - 'ml-kem-768': EVP_PKEY_ML_KEM_768, - 'ml-kem-1024': EVP_PKEY_ML_KEM_1024, - 'slh-dsa-sha2-128f': EVP_PKEY_SLH_DSA_SHA2_128F, - 'slh-dsa-sha2-128s': EVP_PKEY_SLH_DSA_SHA2_128S, - 'slh-dsa-sha2-192f': EVP_PKEY_SLH_DSA_SHA2_192F, - 'slh-dsa-sha2-192s': EVP_PKEY_SLH_DSA_SHA2_192S, - 'slh-dsa-sha2-256f': EVP_PKEY_SLH_DSA_SHA2_256F, - 'slh-dsa-sha2-256s': EVP_PKEY_SLH_DSA_SHA2_256S, - 'slh-dsa-shake-128f': EVP_PKEY_SLH_DSA_SHAKE_128F, - 'slh-dsa-shake-128s': EVP_PKEY_SLH_DSA_SHAKE_128S, - 'slh-dsa-shake-192f': EVP_PKEY_SLH_DSA_SHAKE_192F, - 'slh-dsa-shake-192s': EVP_PKEY_SLH_DSA_SHAKE_192S, - 'slh-dsa-shake-256f': EVP_PKEY_SLH_DSA_SHAKE_256F, - 'slh-dsa-shake-256s': EVP_PKEY_SLH_DSA_SHAKE_256S, + 'ed25519': 'Ed25519', + 'ed448': 'Ed448', + 'x25519': 'X25519', + 'x448': 'X448', }; +for (const name of new SafeArrayIterator(getPqcKeyTypes())) { + namedKeyPairs[StringPrototypeToLowerCase(name)] = name; +} function createJob(mode, type, options) { validateString(type, 'type'); @@ -373,10 +338,10 @@ function createJob(mode, type, options) { ...encoding); } default: { - if (nidOnlyKeyPairs[type] === undefined) { + if (namedKeyPairs[type] === undefined) { throw new ERR_INVALID_ARG_VALUE('type', type, 'must be a supported key type'); } - return new NidKeyPairGenJob(mode, nidOnlyKeyPairs[type], ...encoding); + return new NamedKeyPairGenJob(mode, namedKeyPairs[type], ...encoding); } } } diff --git a/lib/internal/crypto/ml_dsa.js b/lib/internal/crypto/ml_dsa.js index 108ec22fc791..71238c4726f0 100644 --- a/lib/internal/crypto/ml_dsa.js +++ b/lib/internal/crypto/ml_dsa.js @@ -18,10 +18,7 @@ const { kWebCryptoKeyFormatRaw, kWebCryptoKeyFormatPKCS8, kWebCryptoKeyFormatSPKI, - NidKeyPairGenJob, - EVP_PKEY_ML_DSA_44, - EVP_PKEY_ML_DSA_65, - EVP_PKEY_ML_DSA_87, + NamedKeyPairGenJob, } = internalBinding('crypto'); const { @@ -59,20 +56,13 @@ function mlDsaGenerateKey(algorithm, extractable, usages) { const { name } = algorithm; const usagesSet = validateKeyUsages(usages, kUsages.keygen, name); - const nid = { - '__proto__': null, - 'ML-DSA-44': EVP_PKEY_ML_DSA_44, - 'ML-DSA-65': EVP_PKEY_ML_DSA_65, - 'ML-DSA-87': EVP_PKEY_ML_DSA_87, - }[name]; - const keyAlgorithm = { name }; const keyUsages = getKeyPairUsages(usagesSet, kUsages); validateUsagesNotEmpty(keyUsages.private); - return jobPromise(() => new NidKeyPairGenJob( + return jobPromise(() => new NamedKeyPairGenJob( kCryptoJobWebCrypto, - nid, + name, keyAlgorithm, getUsagesMask(keyUsages.public), getUsagesMask(keyUsages.private), @@ -180,7 +170,9 @@ function mlDsaImportKey( name, usagesSet, isPublic ? kUsages.public : kUsages.private); - handle = importRawKey(isPublic, keyData, isPublic ? kKeyFormatRawPublic : kKeyFormatRawSeed, name); + handle = importRawKey(isPublic, keyData, + isPublic ? kKeyFormatRawPublic : kKeyFormatRawSeed, + StringPrototypeToLowerCase(name)); break; } default: diff --git a/lib/internal/crypto/ml_kem.js b/lib/internal/crypto/ml_kem.js index da077ac8344d..025790829f04 100644 --- a/lib/internal/crypto/ml_kem.js +++ b/lib/internal/crypto/ml_kem.js @@ -17,10 +17,7 @@ const { kWebCryptoKeyFormatPKCS8, kWebCryptoKeyFormatRaw, kWebCryptoKeyFormatSPKI, - NidKeyPairGenJob, - EVP_PKEY_ML_KEM_512, - EVP_PKEY_ML_KEM_768, - EVP_PKEY_ML_KEM_1024, + NamedKeyPairGenJob, } = internalBinding('crypto'); const { @@ -60,20 +57,13 @@ function mlKemGenerateKey(algorithm, extractable, usages) { const { name } = algorithm; const usagesSet = validateKeyUsages(usages, kUsages.keygen, name); - const nid = { - '__proto__': null, - 'ML-KEM-512': EVP_PKEY_ML_KEM_512, - 'ML-KEM-768': EVP_PKEY_ML_KEM_768, - 'ML-KEM-1024': EVP_PKEY_ML_KEM_1024, - }[name]; - const keyAlgorithm = { name }; const keyUsages = getKeyPairUsages(usagesSet, kUsages); validateUsagesNotEmpty(keyUsages.private); - return jobPromise(() => new NidKeyPairGenJob( + return jobPromise(() => new NamedKeyPairGenJob( kCryptoJobWebCrypto, - nid, + name, keyAlgorithm, getUsagesMask(keyUsages.public), getUsagesMask(keyUsages.private), @@ -165,7 +155,9 @@ function mlKemImportKey( name, usagesSet, isPublic ? kUsages.public : kUsages.private); - handle = importRawKey(isPublic, keyData, isPublic ? kKeyFormatRawPublic : kKeyFormatRawSeed, name); + handle = importRawKey(isPublic, keyData, + isPublic ? kKeyFormatRawPublic : kKeyFormatRawSeed, + StringPrototypeToLowerCase(name)); break; } case 'jwk': { diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js index ca1d68b5127b..b38db7c6603f 100644 --- a/lib/internal/crypto/util.js +++ b/lib/internal/crypto/util.js @@ -42,17 +42,12 @@ const { getCachedAliases, getCachedMacAliases, getOpenSSLSecLevelCrypto: getOpenSSLSecLevel, - EVP_PKEY_ML_DSA_44, - EVP_PKEY_ML_DSA_65, - EVP_PKEY_ML_DSA_87, - EVP_PKEY_ML_KEM_512, - EVP_PKEY_ML_KEM_768, - EVP_PKEY_ML_KEM_1024, kKeyVariantAES_OCB_128: hasAesOcbMode, Argon2Job, getFipsCrypto, getFipsCryptoGeneration, KmacJob, + getPqcKeyTypes, } = internalBinding('crypto'); const isFips = getFipsCrypto() === 1; @@ -501,6 +496,8 @@ const kAlgorithmDefinitions = { }; // Conditionally supported algorithms +const pqcKeyTypes = getPqcKeyTypes(); + const conditionalAlgorithms = { 'AES-OCB': !!hasAesOcbMode, 'Argon2d': !!Argon2Job, @@ -517,21 +514,21 @@ const conditionalAlgorithms = { 'KMAC256': !!KmacJob, 'KT128': !isFips, 'KT256': !isFips, - 'ML-DSA-44': !!EVP_PKEY_ML_DSA_44, - 'ML-DSA-65': !!EVP_PKEY_ML_DSA_65, - 'ML-DSA-87': !!EVP_PKEY_ML_DSA_87, - 'ML-KEM-512': !!EVP_PKEY_ML_KEM_512, - 'ML-KEM-768': !!EVP_PKEY_ML_KEM_768, - 'ML-KEM-1024': !!EVP_PKEY_ML_KEM_1024, - 'MLKEM768-P256': !isFips && !!EVP_PKEY_ML_KEM_768 && + 'ML-DSA-44': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-DSA-44'), + 'ML-DSA-65': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-DSA-65'), + 'ML-DSA-87': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-DSA-87'), + 'ML-KEM-512': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-KEM-512'), + 'ML-KEM-768': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-KEM-768'), + 'ML-KEM-1024': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-KEM-1024'), + 'MLKEM768-P256': !isFips && ArrayPrototypeIncludes(pqcKeyTypes, 'ML-KEM-768') && (!process.features.openssl_is_boringssl || (ArrayPrototypeIncludes(getHashes(), 'sha3-256') && ArrayPrototypeIncludes(getHashes(), 'shake256'))), - 'MLKEM768-X25519': !isFips && !!EVP_PKEY_ML_KEM_768 && + 'MLKEM768-X25519': !isFips && ArrayPrototypeIncludes(pqcKeyTypes, 'ML-KEM-768') && (!process.features.openssl_is_boringssl || (ArrayPrototypeIncludes(getHashes(), 'sha3-256') && ArrayPrototypeIncludes(getHashes(), 'shake256'))), - 'MLKEM1024-P384': !isFips && !!EVP_PKEY_ML_KEM_1024 && + 'MLKEM1024-P384': !isFips && ArrayPrototypeIncludes(pqcKeyTypes, 'ML-KEM-1024') && (!process.features.openssl_is_boringssl || (ArrayPrototypeIncludes(getHashes(), 'sha3-256') && ArrayPrototypeIncludes(getHashes(), 'shake256'))), diff --git a/src/crypto/README.md b/src/crypto/README.md index 7bc4dbe4e148..de4883c424c4 100644 --- a/src/crypto/README.md +++ b/src/crypto/README.md @@ -42,9 +42,11 @@ following table: | `crypto_hash` | Basic hash (e.g. SHA-256) functions. | | `crypto_hkdf` | HKDF (Key derivation) implementation. | | `crypto_hmac` | HMAC implementations. | +| `crypto_keygen` | Secret and asymmetric key generation jobs. | | `crypto_keys` | Utilities for using and generating secret, private, and public keys. | | `crypto_mac` | Provider-generic MAC implementations. | | `crypto_pbkdf2` | PBKDF2 key / bit generation implementation. | +| `crypto_pqc` | Post-quantum algorithm enumeration. | | `crypto_rsa` | RSA Key Generation functions. | | `crypto_scrypt` | Scrypt key / bit generation implementation. | | `crypto_sig` | General digital signature and verification utilities. | @@ -58,41 +60,36 @@ When new crypto protocols are added, they will be added into their own ## Helpful concepts -Node.js currently uses OpenSSL to provide it's crypto substructure. +Node.js currently uses OpenSSL to provide its crypto substructure. (Some custom Node.js distributions -- such as Electron -- use BoringSSL instead.) This section aims to explain some of the utilities that have been provided to make working with the OpenSSL APIs a bit easier. +### The ncrypto boundary + +[`deps/ncrypto`](../../deps/ncrypto) provides the OpenSSL and BoringSSL +wrappers used by this subsystem. Put backend adaptation, algorithm metadata, +and reusable cryptographic operations there. Keep JavaScript argument +validation, public API policy and errors, V8 objects, and crypto job integration +in `src/crypto` and `lib/internal/crypto`. + +For provider operations, query the supported algorithms and parameters through +ncrypto. Version guards are still needed when a C API is unavailable in older +headers or when handling a known version-specific behavior. + ### Pointer types -Most of the key OpenSSL types need to be explicitly freed when they are -no longer needed. Failure to do so introduces memory leaks. To make this -easier (and less error prone), the `crypto_util.h` defines a number of -smart-pointer aliases that should be used: - -```cpp -using X509Pointer = DeleteFnPtr; -using BIOPointer = DeleteFnPtr; -using SSLCtxPointer = DeleteFnPtr; -using SSLSessionPointer = DeleteFnPtr; -using SSLPointer = DeleteFnPtr; -using PKCS8Pointer = DeleteFnPtr; -using EVPKeyPointer = DeleteFnPtr; -using EVPKeyCtxPointer = DeleteFnPtr; -using EVPMDCtxPointer = DeleteFnPtr; -using RSAPointer = DeleteFnPtr; -using ECPointer = DeleteFnPtr; -using BignumPointer = DeleteFnPtr; -using NetscapeSPKIPointer = DeleteFnPtr; -using ECGroupPointer = DeleteFnPtr; -using ECPointPointer = DeleteFnPtr; -using ECKeyPointer = DeleteFnPtr; -using DHPointer = DeleteFnPtr; -using ECDSASigPointer = DeleteFnPtr; -using CipherCtxPointer = DeleteFnPtr; -``` +Most OpenSSL objects need to be explicitly freed when they are no longer +needed. Use the ownership wrappers declared in +[`ncrypto.h`](../../deps/ncrypto/ncrypto.h) to manage their lifetime. + +Some wrappers, such as `PKCS8Pointer`, are `DeleteFnPtr` aliases. Others, +including `EVPKeyPointer`, `EVPKeyCtxPointer`, `EVPMDCtxPointer`, `BIOPointer`, +`BignumPointer`, and `ECKeyPointer`, are dedicated classes that also provide +operations on the wrapped state. Their representation can vary by backend; +use their methods to keep that adaptation inside ncrypto. Examples of these being used are pervasive through the `src/crypto` code. @@ -153,6 +150,70 @@ threadpool). Refer to `crypto_keys.h` and `crypto_keys.cc` for all code relating to the core key objects. +#### Asymmetric key algorithms + +Use ncrypto's `KeyAlgorithm` descriptors to identify known algorithms in C++. +They hold canonical algorithm names and metadata, with static lifetime, so +callers can retain descriptor pointers across asynchronous jobs. For example, +`KeyAlgorithm::RSA_PSS` names `RSA-PSS` and `KeyAlgorithm::ML_DSA_44` names +`ML-DSA-44`. + +For an existing key, use `key.isA(KeyAlgorithm::RSA_PSS)` or another descriptor. +On OpenSSL 3 and later this uses `EVP_PKEY_is_a()` to recognize provider aliases. +Numeric key IDs are unsuitable for provider-only keys: OpenSSL can return `-1` +for their ID. Numeric adapters for BoringSSL and legacy OpenSSL stay private to +ncrypto. + +When a function needs algorithm metadata, use `key.getAlgorithm()`. It returns +a pointer to a static descriptor, or `nullptr` for an empty key or an unrecognized +algorithm. Reuse it for multiple checks within that function. Key-based helpers +resolve the algorithm internally. Keep RSA distinct from RSA-PSS, and EC distinct +from SM2. + +`key.rawSeed()` validates seed support and extracts the seed in one operation. +Its result distinguishes an unsupported key type from an unavailable seed. +OKP and AKP JWKs share ncrypto's `key.exportRawJwk()` and +`EVPKeyPointer::NewRawJwk()` operations. Export returns the recognized algorithm +and public/private bytes, selecting seed versus raw private material internally. +Import checks that public bytes match the supplied private material. Node.js +handles the distinct JWK field names, input-name validation, base64url encoding, +JavaScript objects, and errors. RSA and EC retain their component and coordinate +representations; ncrypto handles prime-product validation and affine-coordinate +extraction through `Rsa` and `Ec`. + +`Ec::GetCurveId()`, `GetKeyComponents()`, and the raw-export helpers read +provider parameters directly where supported, with backend adaptation in +ncrypto. Keep these operations on the `EVPKeyPointer` when an `ECKeyPointer` +reconstruction is unnecessary. + +`key.getKeyTypeName()` returns the descriptor's lowercase public key-type name, +or `nullptr` when the key has no recognized public type. For example, ML-DSA-44 +returns `ml-dsa-44`, while SM2 has no public key-type name. These names live in +ncrypto alongside the algorithm metadata. + +For construction, use `EVPKeyCtxPointer::NewFromAlgorithm()` with a descriptor, +or `NewFromName()` when a backend algorithm name is needed. +`EVPKeyPointer::NewRawPublic()`, `NewRawPrivate()`, and `NewRawSeed()` take +descriptors too. Use the key's capability helpers for raw formats and signature +contexts; recognizing an algorithm does not establish support for an operation. + +`KeyAlgorithm::FromName()` looks up known canonical names case-insensitively +using the same `CaseInsensitiveNameEqual` as the digest, cipher, and MAC caches. +It does not resolve arbitrary provider aliases or establish availability. +`isAvailable()` checks whether the backend can create a context for the algorithm. +Public input validation remains specific to each API: PQC JWK `alg` values use +exact canonical names such as `ML-DSA-44`, while raw imports require exact public +`asymmetricKeyType` values such as `ml-dsa-44`. + +The internal JavaScript binding exposes `getPqcKeyTypes()` for the available +known PQC algorithm names in their canonical spelling. Named key generation +passes algorithm names to `NamedKeyPairGenJob`, which resolves the name to a static +`KeyAlgorithm` descriptor. Asymmetric key IDs are not exposed to JavaScript. + +Real EC curve and ASN.1/OID NIDs still have their own uses. The EC generation +path keeps Ed/X algorithm descriptors separate from curve NIDs while preserving +the existing accepted curve-name aliases. + #### `KeyObjectData` `KeyObjectData` is an internal thread-safe structure used to wrap either @@ -321,9 +382,10 @@ They perform their actions immediately. ```js // Example synchronous single-call operation +const { timingSafeEqual } = require('node:crypto'); const a = new Uint8Array(10); const b = new Uint8Array(10); -crypto.timingSafeEqual(a, b); +timingSafeEqual(a, b); ``` Asynchronous single-call operations generally perform a @@ -332,8 +394,10 @@ defer the actual crypto-operation work to the libuv threadpool. ```js // Example asynchronous single-call operation +const { randomFill } = require('node:crypto'); const buf = new Uint8Array(10); -crypto.randomFill(buf, (err, buf) => { +randomFill(buf, (err, buf) => { + if (err) throw err; console.log(buf); }); ``` @@ -348,12 +412,12 @@ all asynchronous single-call operations are Promise-based. // Example Web Crypto API asynchronous single-call operation const { subtle } = globalThis.crypto; -subtle.generateKeys({ name: 'HMAC', length: 256 }, true, ['sign']) +subtle.generateKey({ name: 'HMAC', hash: 'SHA-256', length: 256 }, true, ['sign']) .then((key) => { console.log(key); }) .catch((error) => { - console.error('an error occurred'); + console.error('an error occurred', error); }); ``` @@ -367,12 +431,12 @@ can be performed over time. ```js // Example stream-oriented operation -const hash = crypto.createHash('sha256'); -let updates = 10; +const { createHash } = require('node:crypto'); +const hash = createHash('sha256'); setTimeout(() => { hash.update('hello world'); setTimeout(() => { - console.log(hash.digest();) + console.log(hash.digest()); }, 1000); }, 1000); ``` diff --git a/src/crypto/crypto_common.cc b/src/crypto/crypto_common.cc index b1b1c48e4cc7..ea0747511a10 100644 --- a/src/crypto/crypto_common.cc +++ b/src/crypto/crypto_common.cc @@ -29,6 +29,7 @@ namespace node { using ncrypto::ClearErrorOnReturn; using ncrypto::EVPKeyPointer; +using ncrypto::KeyAlgorithm; using ncrypto::SSLPointer; using ncrypto::SSLSessionPointer; using ncrypto::StackOfX509; @@ -217,31 +218,26 @@ MaybeLocal GetEphemeralKey(Environment* env, const SSLPointer& ssl) { bool found = false; if (EVPKeyPointer key = ssl.getPeerTempKey()) { - int kid = key.id(); - switch (kid) { - case EVP_PKEY_DH: { - values[0] = env->dh_string(); - values[2] = Integer::New(env->isolate(), key.bits()); - found = true; - break; + const auto* algorithm = key.getAlgorithm(); + if (algorithm == &KeyAlgorithm::DH) { + values[0] = env->dh_string(); + values[2] = Integer::New(env->isolate(), key.bits()); + found = true; + } else if (algorithm == &KeyAlgorithm::EC || + algorithm == &KeyAlgorithm::X25519 || + algorithm == &KeyAlgorithm::X448) { + const char* curve_name = nullptr; + if (algorithm == &KeyAlgorithm::EC) { + const int nid = ncrypto::Ec::GetCurveId(key); + if (nid != NID_undef) curve_name = OBJ_nid2sn(nid); + } else { + curve_name = algorithm->name(); } - case EVP_PKEY_EC: - case EVP_PKEY_X25519: - case EVP_PKEY_X448: { - const char* curve_name; - if (kid == EVP_PKEY_EC) { - int nid = ncrypto::Ec::GetCurveId(key); - if (nid == NID_undef) break; - curve_name = OBJ_nid2sn(nid); - } else { - curve_name = OBJ_nid2sn(kid); - } - if (curve_name == nullptr) break; + if (curve_name != nullptr) { values[0] = env->ecdh_string(); values[1] = OneByteString(env->isolate(), curve_name); values[2] = Integer::New(env->isolate(), key.bits()); found = true; - break; } } } diff --git a/src/crypto/crypto_context.cc b/src/crypto/crypto_context.cc index 9dc68f4d9d2d..3ed08d9a558e 100644 --- a/src/crypto/crypto_context.cc +++ b/src/crypto/crypto_context.cc @@ -42,6 +42,7 @@ using ncrypto::Digest; using ncrypto::EnginePointer; #endif // !OPENSSL_NO_ENGINE using ncrypto::EVPKeyPointer; +using ncrypto::KeyAlgorithm; using ncrypto::MarkPopErrorOnReturn; using ncrypto::SSLPointer; using ncrypto::StackOfX509; @@ -1948,7 +1949,7 @@ void SecureContext::SetDHParam(const FunctionCallbackInfo& args) { #if NCRYPTO_USE_OPENSSL3_PROVIDER EVPKeyPointer params(PEM_read_bio_Parameters(bio.get(), nullptr)); - if (params && params.id() == EVP_PKEY_DH) dh.reset(params.release()); + if (params && params.isA(KeyAlgorithm::DH)) dh.reset(params.release()); #else dh.reset(PEM_read_bio_DHparams(bio.get(), nullptr, nullptr, nullptr)); #endif diff --git a/src/crypto/crypto_dh.cc b/src/crypto/crypto_dh.cc index 27f3a8a4cbc9..28abba798da9 100644 --- a/src/crypto/crypto_dh.cc +++ b/src/crypto/crypto_dh.cc @@ -21,6 +21,7 @@ using ncrypto::DataPointer; using ncrypto::DHPointer; using ncrypto::EVPKeyCtxPointer; using ncrypto::EVPKeyPointer; +using ncrypto::KeyAlgorithm; using v8::ArrayBuffer; using v8::ConstructorBehavior; using v8::Context; @@ -452,17 +453,13 @@ EVPKeyCtxPointer DhKeyGenTraits::Setup(DhKeyPairGenConfig* params) { key_params = EVPKeyPointer::NewDH(std::move(dh)); } else if (int* prime_size = std::get_if(¶ms->params.prime)) { - auto param_ctx = EVPKeyCtxPointer::NewFromID(EVP_PKEY_DH); -#ifndef OPENSSL_IS_BORINGSSL + auto param_ctx = EVPKeyCtxPointer::NewFromAlgorithm(KeyAlgorithm::DH); if (!param_ctx.initForParamgen() || !param_ctx.setDhParameters(*prime_size, params->params.generator)) { return {}; } key_params = param_ctx.paramgen(); -#else - return {}; -#endif } else { UNREACHABLE(); } @@ -519,7 +516,7 @@ bool DHBitsTraits::DeriveBits(Environment* env, bool GetDhKeyDetail(Environment* env, const KeyObjectData& key, Local target) { - CHECK_EQ(key.GetAsymmetricKey().id(), EVP_PKEY_DH); + DCHECK(key.GetAsymmetricKey().isA(KeyAlgorithm::DH)); return true; } diff --git a/src/crypto/crypto_dsa.cc b/src/crypto/crypto_dsa.cc index c2ae2eceb2cf..cf998c7fb473 100644 --- a/src/crypto/crypto_dsa.cc +++ b/src/crypto/crypto_dsa.cc @@ -16,6 +16,7 @@ namespace node { using ncrypto::Dsa; using ncrypto::EVPKeyCtxPointer; +using ncrypto::KeyAlgorithm; using v8::FunctionCallbackInfo; using v8::Int32; using v8::JustVoid; @@ -28,7 +29,7 @@ using v8::Value; namespace crypto { EVPKeyCtxPointer DsaKeyGenTraits::Setup(DsaKeyPairGenConfig* params) { - auto param_ctx = EVPKeyCtxPointer::NewFromID(EVP_PKEY_DSA); + auto param_ctx = EVPKeyCtxPointer::NewFromAlgorithm(KeyAlgorithm::DSA); if (!param_ctx || !param_ctx.initForParamgen() || !param_ctx.setDsaParameters( diff --git a/src/crypto/crypto_ec.cc b/src/crypto/crypto_ec.cc index f9263635d0b0..80845fed281e 100644 --- a/src/crypto/crypto_ec.cc +++ b/src/crypto/crypto_ec.cc @@ -6,7 +6,6 @@ #include "env-inl.h" #include "memory_tracker-inl.h" #include "node_buffer.h" -#include "string_bytes.h" #include "threadpoolwork-inl.h" #include "v8.h" @@ -19,13 +18,13 @@ namespace node { using ncrypto::BignumPointer; -using ncrypto::DataPointer; using ncrypto::Ec; using ncrypto::ECGroupPointer; using ncrypto::ECKeyPointer; using ncrypto::ECPointPointer; using ncrypto::EVPKeyCtxPointer; using ncrypto::EVPKeyPointer; +using ncrypto::KeyAlgorithm; using ncrypto::MarkPopErrorOnReturn; using v8::Array; using v8::ArrayBuffer; @@ -409,29 +408,18 @@ void ECDH::ConvertKey(const FunctionCallbackInfo& args) { EVPKeyCtxPointer EcKeyGenTraits::Setup(EcKeyPairGenConfig* params) { EVPKeyCtxPointer key_ctx; - switch (params->params.curve_nid) { - case EVP_PKEY_ED25519: - // Fall through - case EVP_PKEY_ED448: - // Fall through - case EVP_PKEY_X25519: - // Fall through - case EVP_PKEY_X448: - key_ctx = EVPKeyCtxPointer::NewFromID(params->params.curve_nid); - break; - default: { - auto param_ctx = EVPKeyCtxPointer::NewFromID(EVP_PKEY_EC); - if (!param_ctx.initForParamgen() || - !param_ctx.setEcParameters(params->params.curve_nid, - params->params.param_encoding)) { - return {}; - } - - auto key_params = param_ctx.paramgen(); - if (!key_params) return {}; - - key_ctx = key_params.newCtx(); + if (params->params.algorithm != nullptr) { + key_ctx = EVPKeyCtxPointer::NewFromAlgorithm(*params->params.algorithm); + } else { + auto param_ctx = EVPKeyCtxPointer::NewFromAlgorithm(KeyAlgorithm::EC); + if (!param_ctx.initForParamgen() || + !param_ctx.setEcParameters(params->params.curve_nid, + params->params.param_encoding)) { + return {}; } + auto key_params = param_ctx.paramgen(); + if (!key_params) return {}; + key_ctx = key_params.newCtx(); } if (!key_ctx.initForKeygen()) return {}; @@ -457,10 +445,13 @@ Maybe EcKeyGenTraits::AdditionalConfig( CHECK(args[*offset]->IsString()); // curve name Utf8Value curve_name(env->isolate(), args[*offset]); - params->params.curve_nid = Ec::GetCurveIdFromName(*curve_name); - if (params->params.curve_nid == NID_undef) { - THROW_ERR_CRYPTO_INVALID_CURVE(env); - return Nothing(); + params->params.algorithm = ncrypto::Ec::GetNamedKeyAlgorithm(*curve_name); + if (params->params.algorithm == nullptr) { + params->params.curve_nid = Ec::GetCurveIdFromName(*curve_name); + if (params->params.curve_nid == NID_undef) { + THROW_ERR_CRYPTO_INVALID_CURVE(env); + return Nothing(); + } } // param encoding @@ -486,7 +477,7 @@ bool ExportJWKEcKey(Environment* env, Local target) { Mutex::ScopedLock lock(key.mutex()); const auto& m_pkey = key.GetAsymmetricKey(); - CHECK_EQ(m_pkey.id(), EVP_PKEY_EC); + DCHECK(m_pkey.isA(KeyAlgorithm::EC)); BignumPointer x; BignumPointer y; @@ -510,18 +501,10 @@ bool ExportJWKEcKey(Environment* env, return false; } - if (SetEncodedValue( - env, - target, - env->jwk_x_string(), - x.get(), - degree_bytes).IsNothing() || - SetEncodedValue( - env, - target, - env->jwk_y_string(), - y.get(), - degree_bytes).IsNothing()) { + if (SetEncodedValue(env, target, env->jwk_x_string(), x.get(), degree_bytes) + .IsNothing() || + SetEncodedValue(env, target, env->jwk_y_string(), y.get(), degree_bytes) + .IsNothing()) { return false; } @@ -561,134 +544,6 @@ bool ExportJWKEcKey(Environment* env, return true; } -bool ExportJWKEdKey(Environment* env, - const KeyObjectData& key, - Local target) { - Mutex::ScopedLock lock(key.mutex()); - const auto& pkey = key.GetAsymmetricKey(); - - const char* curve = ([&] { - switch (pkey.id()) { - case EVP_PKEY_ED25519: - return "Ed25519"; - case EVP_PKEY_ED448: - return "Ed448"; - case EVP_PKEY_X25519: - return "X25519"; - case EVP_PKEY_X448: - return "X448"; - default: - UNREACHABLE(); - } - })(); - - static constexpr auto trySetKey = [](Environment* env, - DataPointer data, - Local target, - Local key) { - Local encoded; - if (!data) return false; - const ncrypto::Buffer out = data; - return StringBytes::Encode(env->isolate(), out.data, out.len, BASE64URL) - .ToLocal(&encoded) && - target->DefineOwnProperty(env->context(), key, encoded) - .FromMaybe(false); - }; - - return !( - !target - ->DefineOwnProperty(env->context(), - env->jwk_crv_string(), - OneByteString(env->isolate(), curve)) - .FromMaybe(false) || - (key.GetKeyType() == kKeyTypePrivate && - !trySetKey(env, pkey.rawPrivateKey(), target, env->jwk_d_string())) || - !trySetKey(env, pkey.rawPublicKey(), target, env->jwk_x_string()) || - !target - ->DefineOwnProperty( - env->context(), env->jwk_kty_string(), env->jwk_okp_string()) - .FromMaybe(false)); -} -KeyObjectData ImportJWKEdKey(Environment* env, Local jwk) { - Local crv_value; - Local x_value; - Local d_value; - - if (!jwk->Get(env->context(), env->jwk_crv_string()).ToLocal(&crv_value) || - !jwk->Get(env->context(), env->jwk_x_string()).ToLocal(&x_value) || - !jwk->Get(env->context(), env->jwk_d_string()).ToLocal(&d_value)) { - return {}; - } - - if (!crv_value->IsString() || !x_value->IsString() || - (!d_value->IsUndefined() && !d_value->IsString())) { - THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK OKP key"); - return {}; - } - - Utf8Value crv(env->isolate(), crv_value.As()); - - static constexpr struct { - const char* name; - int nid; - } kCurveToNid[] = { - {"Ed25519", EVP_PKEY_ED25519}, - {"Ed448", EVP_PKEY_ED448}, - {"X25519", EVP_PKEY_X25519}, - {"X448", EVP_PKEY_X448}, - }; - - int id = NID_undef; - for (const auto& entry : kCurveToNid) { - if (strcmp(*crv, entry.name) == 0) { - id = entry.nid; - break; - } - } - - if (id == NID_undef) { - THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK OKP key"); - return {}; - } - - KeyType type = d_value->IsString() ? kKeyTypePrivate : kKeyTypePublic; - - ByteSource raw; - if (type == kKeyTypePrivate) { - raw = ByteSource::FromEncodedString(env, d_value.As()); - } else { - raw = ByteSource::FromEncodedString(env, x_value.As()); - } - - typedef EVPKeyPointer (*new_key_fn)( - int, const ncrypto::Buffer&); - new_key_fn fn = type == kKeyTypePrivate ? EVPKeyPointer::NewRawPrivate - : EVPKeyPointer::NewRawPublic; - - auto pkey = fn(id, - ncrypto::Buffer{ - .data = raw.data(), - .len = raw.size(), - }); - if (!pkey) { - THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK OKP key"); - return {}; - } - - // When importing a private key, verify that the JWK's x field matches - // the public key derived from the private key. - if (type == kKeyTypePrivate && x_value->IsString()) { - ByteSource x = ByteSource::FromEncodedString(env, x_value.As()); - auto derived_pub = pkey.rawPublicKey(); - if (!derived_pub || derived_pub.size() != x.size() || - CRYPTO_memcmp(derived_pub.get(), x.data(), x.size()) != 0) { - THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK OKP key"); - return {}; - } - } - - return KeyObjectData::CreateAsymmetric(type, std::move(pkey)); -} KeyObjectData ImportJWKEcKey(Environment* env, Local jwk) { Local crv_value; if (!jwk->Get(env->context(), env->jwk_crv_string()).ToLocal(&crv_value) || @@ -767,7 +622,7 @@ bool GetEcKeyDetail(Environment* env, Local target) { Mutex::ScopedLock lock(key.mutex()); const auto& m_pkey = key.GetAsymmetricKey(); - CHECK_EQ(m_pkey.id(), EVP_PKEY_EC); + DCHECK(m_pkey.isA(KeyAlgorithm::EC)); int nid = Ec::GetCurveId(m_pkey); if (nid == NID_undef) return true; diff --git a/src/crypto/crypto_ec.h b/src/crypto/crypto_ec.h index 3e83d541d7ae..3f4e6d6f410b 100644 --- a/src/crypto/crypto_ec.h +++ b/src/crypto/crypto_ec.h @@ -59,7 +59,8 @@ class ECDH final : public BaseObject { }; struct EcKeyPairParams final : public MemoryRetainer { - int curve_nid; + const ncrypto::KeyAlgorithm* algorithm = nullptr; + int curve_nid = NID_undef; int param_encoding; SET_NO_MEMORY_INFO() SET_MEMORY_INFO_NAME(EcKeyPairParams) @@ -87,12 +88,6 @@ bool ExportJWKEcKey(Environment* env, const KeyObjectData& key, v8::Local target); -bool ExportJWKEdKey(Environment* env, - const KeyObjectData& key, - v8::Local target); - -KeyObjectData ImportJWKEdKey(Environment* env, v8::Local jwk); - KeyObjectData ImportJWKEcKey(Environment* env, v8::Local jwk); bool GetEcKeyDetail(Environment* env, diff --git a/src/crypto/crypto_keygen.cc b/src/crypto/crypto_keygen.cc index fd456465e0ee..7e273eb57a59 100644 --- a/src/crypto/crypto_keygen.cc +++ b/src/crypto/crypto_keygen.cc @@ -15,7 +15,6 @@ namespace node { using ncrypto::DataPointer; using ncrypto::EVPKeyCtxPointer; using v8::FunctionCallbackInfo; -using v8::Int32; using v8::JustVoid; using v8::Local; using v8::Maybe; @@ -25,30 +24,32 @@ using v8::Uint32; using v8::Value; namespace crypto { -// NidKeyPairGenJob input arguments: +// NamedKeyPairGenJob input arguments: // 1. CryptoJobMode -// 2. NID +// 2. Algorithm name // 3. Public Format // 4. Public Type // 5. Private Format // 6. Private Type // 7. Cipher // 8. Passphrase -Maybe NidKeyPairGenTraits::AdditionalConfig( +Maybe NamedKeyPairGenTraits::AdditionalConfig( CryptoJobMode mode, const FunctionCallbackInfo& args, unsigned int* offset, - NidKeyPairGenConfig* params) { - CHECK(args[*offset]->IsInt32()); - params->params.id = args[*offset].As()->Value(); + NamedKeyPairGenConfig* params) { + CHECK(args[*offset]->IsString()); + Utf8Value name(args.GetIsolate(), args[*offset]); + params->params.algorithm = ncrypto::KeyAlgorithm::FromName(*name); + CHECK_NOT_NULL(params->params.algorithm); *offset += 1; return JustVoid(); } -EVPKeyCtxPointer NidKeyPairGenTraits::Setup(NidKeyPairGenConfig* params) { - auto ctx = EVPKeyCtxPointer::NewFromID(params->params.id); +EVPKeyCtxPointer NamedKeyPairGenTraits::Setup(NamedKeyPairGenConfig* params) { + auto ctx = EVPKeyCtxPointer::NewFromAlgorithm(*params->params.algorithm); if (!ctx || !ctx.initForKeygen()) return {}; return ctx; } @@ -96,12 +97,12 @@ MaybeLocal SecretKeyGenTraits::EncodeKey(Environment* env, namespace Keygen { void Initialize(Environment* env, Local target) { - NidKeyPairGenJob::Initialize(env, target); + NamedKeyPairGenJob::Initialize(env, target); SecretKeyGenJob::Initialize(env, target); } void RegisterExternalReferences(ExternalReferenceRegistry* registry) { - NidKeyPairGenJob::RegisterExternalReferences(registry); + NamedKeyPairGenJob::RegisterExternalReferences(registry); SecretKeyGenJob::RegisterExternalReferences(registry); } } // namespace Keygen diff --git a/src/crypto/crypto_keygen.h b/src/crypto/crypto_keygen.h index 61421bfe5c11..c042edd2b9ad 100644 --- a/src/crypto/crypto_keygen.h +++ b/src/crypto/crypto_keygen.h @@ -352,29 +352,29 @@ struct KeyPairGenConfig final : public MemoryRetainer { SET_SELF_SIZE(KeyPairGenConfig) }; -struct NidKeyPairParams final : public MemoryRetainer { - int id; +struct NamedKeyPairParams final : public MemoryRetainer { + const ncrypto::KeyAlgorithm* algorithm = nullptr; SET_NO_MEMORY_INFO() - SET_MEMORY_INFO_NAME(NidKeyPairParams) - SET_SELF_SIZE(NidKeyPairParams) + SET_MEMORY_INFO_NAME(NamedKeyPairParams) + SET_SELF_SIZE(NamedKeyPairParams) }; -using NidKeyPairGenConfig = KeyPairGenConfig; +using NamedKeyPairGenConfig = KeyPairGenConfig; -struct NidKeyPairGenTraits final { - using AdditionalParameters = NidKeyPairGenConfig; - static constexpr const char* JobName = "NidKeyPairGenJob"; +struct NamedKeyPairGenTraits final { + using AdditionalParameters = NamedKeyPairGenConfig; + static constexpr const char* JobName = "NamedKeyPairGenJob"; - static ncrypto::EVPKeyCtxPointer Setup(NidKeyPairGenConfig* params); + static ncrypto::EVPKeyCtxPointer Setup(NamedKeyPairGenConfig* params); static v8::Maybe AdditionalConfig( CryptoJobMode mode, const v8::FunctionCallbackInfo& args, unsigned int* offset, - NidKeyPairGenConfig* params); + NamedKeyPairGenConfig* params); }; -using NidKeyPairGenJob = KeyGenJob>; +using NamedKeyPairGenJob = KeyGenJob>; using SecretKeyGenJob = KeyGenJob; } // namespace node::crypto diff --git a/src/crypto/crypto_keys.cc b/src/crypto/crypto_keys.cc index b7d3ef9ae6da..c7f01e401828 100644 --- a/src/crypto/crypto_keys.cc +++ b/src/crypto/crypto_keys.cc @@ -5,7 +5,6 @@ #include "crypto/crypto_dh.h" #include "crypto/crypto_dsa.h" #include "crypto/crypto_ec.h" -#include "crypto/crypto_pqc.h" #include "crypto/crypto_rsa.h" #include "crypto/crypto_util.h" #include "env-inl.h" @@ -26,6 +25,7 @@ using ncrypto::ECKeyPointer; using ncrypto::ECPointPointer; using ncrypto::EVPKeyCtxPointer; using ncrypto::EVPKeyPointer; +using ncrypto::KeyAlgorithm; using ncrypto::MarkPopErrorOnReturn; using v8::Array; using v8::Boolean; @@ -177,28 +177,128 @@ KeyObjectData ImportJWKSecretKey(Environment* env, Local jwk) { ByteSource::FromEncodedString(env, key.As())); } +static bool ExportJWKRawKey(Environment* env, + const KeyObjectData& key, + Local target) { + Mutex::ScopedLock lock(key.mutex()); + auto result = + key.GetAsymmetricKey().exportRawJwk(key.GetKeyType() == kKeyTypePrivate); + if (!result) { + if (result.error == EVPKeyPointer::RawExportError::MISSING_SEED) { + THROW_ERR_CRYPTO_OPERATION_FAILED(env, + "key does not have an available seed"); + } + return false; + } + const auto& data = result.value; + const bool is_akp = data.algorithm->isPqc(); + const auto name_field = + is_akp ? env->jwk_alg_string() : env->jwk_crv_string(); + const auto public_field = + is_akp ? env->jwk_pub_string() : env->jwk_x_string(); + const auto private_field = + is_akp ? env->jwk_priv_string() : env->jwk_d_string(); + const auto kty = is_akp ? env->jwk_akp_string() : env->jwk_okp_string(); + const auto context = env->context(); + const auto set_key = [&](const ncrypto::DataPointer& bytes, + Local field) { + Local encoded; + return StringBytes::Encode( + env->isolate(), bytes.get(), bytes.size(), BASE64URL) + .ToLocal(&encoded) && + target->DefineOwnProperty(context, field, encoded).FromMaybe(false); + }; + return target->DefineOwnProperty(context, env->jwk_kty_string(), kty) + .FromMaybe(false) && + target + ->DefineOwnProperty( + context, + name_field, + OneByteString(env->isolate(), data.algorithm->name())) + .FromMaybe(false) && + set_key(data.public_key, public_field) && + (!data.private_key || set_key(data.private_key, private_field)); +} + +static KeyObjectData ImportJWKRawKey(Environment* env, + Local jwk, + bool is_akp) { + const auto name_field = + is_akp ? env->jwk_alg_string() : env->jwk_crv_string(); + const auto public_field = + is_akp ? env->jwk_pub_string() : env->jwk_x_string(); + const auto private_field = + is_akp ? env->jwk_priv_string() : env->jwk_d_string(); + const char* invalid_key = + is_akp ? "Invalid JWK AKP key" : "Invalid JWK OKP key"; + Local name_value; + Local public_value; + Local private_value; + if (!jwk->Get(env->context(), name_field).ToLocal(&name_value) || + !jwk->Get(env->context(), public_field).ToLocal(&public_value) || + !jwk->Get(env->context(), private_field).ToLocal(&private_value)) { + return {}; + } + const bool valid_material = + public_value->IsString() && + (private_value->IsUndefined() || private_value->IsString()); + // OKP validates the field types first; AKP validates its algorithm first. + if (!is_akp && (!name_value->IsString() || !valid_material)) { + THROW_ERR_CRYPTO_INVALID_JWK(env, invalid_key); + return {}; + } + Utf8Value name(env->isolate(), + name_value->IsString() ? name_value.As() + : String::Empty(env->isolate())); + const auto* algorithm = KeyAlgorithm::FromName(*name); + if (algorithm == nullptr || + (is_akp ? !algorithm->isPqc() || !algorithm->isAvailable() + : !algorithm->isOkp()) || + strcmp(*name, algorithm->name()) != 0) { + THROW_ERR_CRYPTO_INVALID_JWK( + env, is_akp ? "Unsupported JWK AKP \"alg\"" : invalid_key); + return {}; + } + if (!valid_material) { + THROW_ERR_CRYPTO_INVALID_JWK(env, invalid_key); + return {}; + } + ByteSource private_bytes; + std::optional> private_key; + const KeyType type = + private_value->IsString() ? kKeyTypePrivate : kKeyTypePublic; + if (type == kKeyTypePrivate) { + private_bytes = + ByteSource::FromEncodedString(env, private_value.As()); + private_key = ncrypto::Buffer{ + private_bytes.data(), private_bytes.size()}; + } + const auto public_bytes = + ByteSource::FromEncodedString(env, public_value.As()); + auto pkey = EVPKeyPointer::NewRawJwk( + *algorithm, + {public_bytes.data(), public_bytes.size()}, + private_key); + if (!pkey) { + THROW_ERR_CRYPTO_INVALID_JWK(env, invalid_key); + return {}; + } + return KeyObjectData::CreateAsymmetric(type, std::move(pkey)); +} + bool ExportJWKAsymmetricKey(Environment* env, const KeyObjectData& key, Local target, bool handleRsaPss) { - const int id = key.GetAsymmetricKey().id(); -#if OPENSSL_WITH_PQC - if (IsPqcKeyId(id)) return ExportJwkPqcKey(env, key, target); -#endif - switch (id) { - case EVP_PKEY_RSA_PSS: { - if (handleRsaPss) return ExportJWKRsaKey(env, key, target); - break; - } - case EVP_PKEY_RSA: - return ExportJWKRsaKey(env, key, target); - case EVP_PKEY_EC: - return ExportJWKEcKey(env, key, target); - case EVP_PKEY_ED25519: - case EVP_PKEY_ED448: - case EVP_PKEY_X25519: - case EVP_PKEY_X448: - return ExportJWKEdKey(env, key, target); + const auto& pkey = key.GetAsymmetricKey(); + const auto* algorithm = pkey.getAlgorithm(); + if (algorithm == &KeyAlgorithm::RSA || + (handleRsaPss && algorithm == &KeyAlgorithm::RSA_PSS)) { + return ExportJWKRsaKey(env, key, target); + } + if (algorithm == &KeyAlgorithm::EC) return ExportJWKEcKey(env, key, target); + if (algorithm != nullptr && (algorithm->isOkp() || algorithm->isPqc())) { + return ExportJWKRawKey(env, key, target); } THROW_ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE(env); return false; @@ -225,16 +325,15 @@ bool GetAsymmetricKeyDetail(Environment* env, THROW_ERR_CRYPTO_OPERATION_FAILED(env); return false; } - switch (key.GetAsymmetricKey().id()) { - case EVP_PKEY_RSA: - // Fall through - case EVP_PKEY_RSA2: - // Fall through - case EVP_PKEY_RSA_PSS: return GetRsaKeyDetail(env, key, target); - case EVP_PKEY_DSA: return GetDsaKeyDetail(env, key, target); - case EVP_PKEY_EC: return GetEcKeyDetail(env, key, target); - case EVP_PKEY_DH: return GetDhKeyDetail(env, key, target); + const auto& pkey = key.GetAsymmetricKey(); + const auto* algorithm = pkey.getAlgorithm(); + // Preserve RSA2 support on legacy backends without exposing its numeric ID. + if (algorithm != nullptr ? algorithm->isRsa() : pkey.isRsaVariant()) { + return GetRsaKeyDetail(env, key, target); } + if (algorithm == &KeyAlgorithm::DSA) return GetDsaKeyDetail(env, key, target); + if (algorithm == &KeyAlgorithm::EC) return GetEcKeyDetail(env, key, target); + if (algorithm == &KeyAlgorithm::DH) return GetDhKeyDetail(env, key, target); THROW_ERR_CRYPTO_INVALID_KEYTYPE(env); return false; } @@ -268,103 +367,30 @@ bool ExportJWKInner(Environment* env, env, key, result.As(), handleRsaPss); } -int GetNidFromName(const char* name) { - static constexpr struct { - const char* name; - int nid; - } kNameToNid[] = { - {"Ed25519", EVP_PKEY_ED25519}, - {"Ed448", EVP_PKEY_ED448}, - {"X25519", EVP_PKEY_X25519}, - {"X448", EVP_PKEY_X448}, - }; - for (const auto& entry : kNameToNid) { - if (StringEqualNoCase(name, entry.name)) return entry.nid; - } -#if OPENSSL_WITH_PQC - return GetPqcNidFromName(name); -#else - return NID_undef; -#endif -} - -bool IsUnavailablePqcKeyType(Environment* env, Local key_type) { - return key_type->StringEquals(env->crypto_ml_dsa_44_string()) || - key_type->StringEquals(env->crypto_ml_dsa_65_string()) || - key_type->StringEquals(env->crypto_ml_dsa_87_string()) || - key_type->StringEquals(env->crypto_ml_kem_512_string()) || - key_type->StringEquals(env->crypto_ml_kem_768_string()) || - key_type->StringEquals(env->crypto_ml_kem_1024_string()) || - key_type->StringEquals(env->crypto_slh_dsa_sha2_128f_string()) || - key_type->StringEquals(env->crypto_slh_dsa_sha2_128s_string()) || - key_type->StringEquals(env->crypto_slh_dsa_sha2_192f_string()) || - key_type->StringEquals(env->crypto_slh_dsa_sha2_192s_string()) || - key_type->StringEquals(env->crypto_slh_dsa_sha2_256f_string()) || - key_type->StringEquals(env->crypto_slh_dsa_sha2_256s_string()) || - key_type->StringEquals(env->crypto_slh_dsa_shake_128f_string()) || - key_type->StringEquals(env->crypto_slh_dsa_shake_128s_string()) || - key_type->StringEquals(env->crypto_slh_dsa_shake_192f_string()) || - key_type->StringEquals(env->crypto_slh_dsa_shake_192s_string()) || - key_type->StringEquals(env->crypto_slh_dsa_shake_256f_string()) || - key_type->StringEquals(env->crypto_slh_dsa_shake_256s_string()); -} - -bool IsUnsupportedRawKeyType(Environment* env, Local key_type) { - return key_type->StringEquals(env->crypto_rsa_string()) || - key_type->StringEquals(env->crypto_rsa_pss_string()) || - key_type->StringEquals(env->crypto_dsa_string()) || - key_type->StringEquals(env->crypto_dh_string()); -} - void ValidateRawKeyImportFormat(Environment* env, - Local key_type, const char* key_type_name, - int id, + const KeyAlgorithm* algorithm, EVPKeyPointer::PKFormatType format) { - auto validate_raw_format = - [&](EVPKeyPointer::PKFormatType expected_private_format) { - if (format == EVPKeyPointer::PKFormatType::RAW_PUBLIC || - format == expected_private_format) { - return; - } - THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); - }; - - if (key_type->StringEquals(env->crypto_ec_string())) { - return validate_raw_format(EVPKeyPointer::PKFormatType::RAW_PRIVATE); - } - - switch (id) { - case EVP_PKEY_X25519: - case EVP_PKEY_X448: - case EVP_PKEY_ED25519: - case EVP_PKEY_ED448: - return validate_raw_format(EVPKeyPointer::PKFormatType::RAW_PRIVATE); - default: - break; - } - -#if OPENSSL_WITH_PQC - if (IsPqcSeedKeyId(id)) { - return validate_raw_format(EVPKeyPointer::PKFormatType::RAW_SEED); - } - if (IsPqcRawPrivateKeyId(id)) { - return validate_raw_format(EVPKeyPointer::PKFormatType::RAW_PRIVATE); + if (algorithm == nullptr) { + THROW_ERR_INVALID_ARG_VALUE( + env, "Invalid asymmetricKeyType: %s", key_type_name); + return; } -#endif - - if (IsUnavailablePqcKeyType(env, key_type)) { + if (algorithm->isPqc() && !algorithm->isAvailable()) { THROW_ERR_INVALID_ARG_VALUE(env, "Unsupported key type"); return; } - if (IsUnsupportedRawKeyType(env, key_type)) { - THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); + const auto private_format = algorithm->seedSize() != 0 + ? EVPKeyPointer::PKFormatType::RAW_SEED + : EVPKeyPointer::PKFormatType::RAW_PRIVATE; + const bool supports_raw = + algorithm == &KeyAlgorithm::EC || algorithm->supportsRawPublic(); + if (supports_raw && (format == EVPKeyPointer::PKFormatType::RAW_PUBLIC || + format == private_format)) { return; } - - THROW_ERR_INVALID_ARG_VALUE( - env, "Invalid asymmetricKeyType: %s", key_type_name); + THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); } } // namespace @@ -385,7 +411,8 @@ bool KeyObjectData::ToEncodedPublicKey( } else if (config.format == EVPKeyPointer::PKFormatType::RAW_PUBLIC) { Mutex::ScopedLock lock(mutex()); const auto& pkey = GetAsymmetricKey(); - if (pkey.id() == EVP_PKEY_EC) { + const auto* algorithm = pkey.getAlgorithm(); + if (algorithm == &KeyAlgorithm::EC) { auto form = static_cast(config.ec_point_form); auto bytes = ncrypto::Ec::TryExportPublic(pkey, form); if (bytes) @@ -405,12 +432,8 @@ bool KeyObjectData::ToEncodedPublicKey( env, ec_key.getGroup(), ec_key.getPublicKey(), form) .ToLocal(out); } - const int id = pkey.id(); - bool is_raw_supported = id == EVP_PKEY_ED25519 || id == EVP_PKEY_ED448 || - id == EVP_PKEY_X25519 || id == EVP_PKEY_X448; -#if OPENSSL_WITH_PQC - is_raw_supported = is_raw_supported || IsPqcKeyId(id); -#endif + const bool is_raw_supported = + algorithm != nullptr && algorithm->supportsRawPublic(); if (!is_raw_supported) { THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); return false; @@ -443,7 +466,8 @@ bool KeyObjectData::ToEncodedPrivateKey( } else if (config.format == EVPKeyPointer::PKFormatType::RAW_PRIVATE) { Mutex::ScopedLock lock(mutex()); const auto& pkey = GetAsymmetricKey(); - if (pkey.id() == EVP_PKEY_EC) { + const auto* algorithm = pkey.getAlgorithm(); + if (algorithm == &KeyAlgorithm::EC) { auto buf = ncrypto::Ec::ExportPrivate(pkey); if (!buf) { THROW_ERR_CRYPTO_OPERATION_FAILED(env, @@ -452,12 +476,8 @@ bool KeyObjectData::ToEncodedPrivateKey( } return Buffer::Copy(env, buf.get(), buf.size()).ToLocal(out); } - const int id = pkey.id(); - bool is_raw_supported = id == EVP_PKEY_ED25519 || id == EVP_PKEY_ED448 || - id == EVP_PKEY_X25519 || id == EVP_PKEY_X448; -#if OPENSSL_WITH_PQC - is_raw_supported = is_raw_supported || IsPqcRawPrivateKeyId(id); -#endif + const bool is_raw_supported = + algorithm != nullptr && algorithm->supportsRawPrivate(); if (!is_raw_supported) { THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); return false; @@ -470,24 +490,21 @@ bool KeyObjectData::ToEncodedPrivateKey( return Buffer::Copy(env, raw_data.get(), raw_data.size()) .ToLocal(out); } else if (config.format == EVPKeyPointer::PKFormatType::RAW_SEED) { -#if OPENSSL_WITH_PQC Mutex::ScopedLock lock(mutex()); const auto& pkey = GetAsymmetricKey(); - if (!IsPqcSeedKeyId(pkey.id())) { - THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); - return false; - } auto raw_data = pkey.rawSeed(); if (!raw_data) { - THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get raw seed"); + if (raw_data.error == + EVPKeyPointer::RawExportError::UNSUPPORTED_KEY_TYPE) { + THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); + } else { + THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get raw seed"); + } return false; } - return Buffer::Copy(env, raw_data.get(), raw_data.size()) + return Buffer::Copy( + env, raw_data.value.get(), raw_data.value.size()) .ToLocal(out); -#else - THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); - return false; -#endif } return WritePrivateKey(env, GetAsymmetricKey(), config).ToLocal(out); @@ -565,7 +582,7 @@ static KeyObjectData ImportRawKey(Environment* env, const unsigned char* key_data, size_t key_data_len, EVPKeyPointer::PKFormatType format, - Local key_type, + const KeyAlgorithm* algorithm, const char* key_type_name, const char* named_curve, KeyType target_type) { @@ -575,14 +592,13 @@ static KeyObjectData ImportRawKey(Environment* env, } }; - const int id = GetNidFromName(key_type_name); - ValidateRawKeyImportFormat(env, key_type, key_type_name, id, format); + ValidateRawKeyImportFormat(env, key_type_name, algorithm, format); if (env->isolate()->HasPendingException()) { return {}; } // EC keys - if (key_type->StringEquals(env->crypto_ec_string())) { + if (algorithm == &KeyAlgorithm::EC) { int curve_nid = ncrypto::Ec::GetCurveIdFromName(named_curve); if (curve_nid == NID_undef) { THROW_ERR_CRYPTO_INVALID_CURVE(env); @@ -640,45 +656,23 @@ static KeyObjectData ImportRawKey(Environment* env, return KeyObjectData::CreateAsymmetric(target_type, std::move(pkey)); } - typedef EVPKeyPointer (*new_key_fn)( - int, const ncrypto::Buffer&); - new_key_fn fn = nullptr; - switch (id) { - case EVP_PKEY_X25519: - case EVP_PKEY_X448: - case EVP_PKEY_ED25519: - case EVP_PKEY_ED448: - fn = target_type == kKeyTypePrivate ? EVPKeyPointer::NewRawPrivate - : EVPKeyPointer::NewRawPublic; - break; - default: -#if OPENSSL_WITH_PQC - if (IsPqcKeyId(id)) { - if (target_type == kKeyTypePrivate) { - fn = IsPqcSeedKeyId(id) ? EVPKeyPointer::NewRawSeed - : EVPKeyPointer::NewRawPrivate; - } else { - fn = EVPKeyPointer::NewRawPublic; - } - } -#endif - break; + const ncrypto::Buffer buffer{ + .data = key_data, + .len = key_data_len, + }; + EVPKeyPointer pkey; + if (target_type == kKeyTypePublic) { + pkey = EVPKeyPointer::NewRawPublic(*algorithm, buffer); + } else if (algorithm->seedSize() != 0) { + pkey = EVPKeyPointer::NewRawSeed(*algorithm, buffer); + } else { + pkey = EVPKeyPointer::NewRawPrivate(*algorithm, buffer); } - - if (fn != nullptr) { - auto pkey = fn(id, - ncrypto::Buffer{ - .data = key_data, - .len = key_data_len, - }); - if (!pkey) { - throw_invalid(); - return {}; - } - return KeyObjectData::CreateAsymmetric(target_type, std::move(pkey)); + if (!pkey) { + throw_invalid(); + return {}; } - - return {}; + return KeyObjectData::CreateAsymmetric(target_type, std::move(pkey)); } // Shared helper for importing a JWK asymmetric key. Extracts kty from the @@ -696,14 +690,9 @@ static KeyObjectData ImportJWKFromArgs(Environment* env, Local jwk) { } else if (*kty_string == std::string_view("EC")) { return ImportJWKEcKey(env, jwk); } else if (*kty_string == std::string_view("OKP")) { - return ImportJWKEdKey(env, jwk); + return ImportJWKRawKey(env, jwk, /* is_akp */ false); } else if (*kty_string == std::string_view("AKP")) { -#if OPENSSL_WITH_PQC - return ImportJWKPqcKey(env, jwk); -#else - THROW_ERR_INVALID_ARG_VALUE(env, "Unsupported key type"); - return {}; -#endif + return ImportJWKRawKey(env, jwk, /* is_akp */ true); } THROW_ERR_CRYPTO_INVALID_JWK( @@ -732,11 +721,16 @@ static KeyObjectData ImportRawKeyFromArgs( } CHECK(args[offset + 2]->IsString()); - Local key_type = args[offset + 2].As(); - Utf8Value key_type_name(env->isolate(), key_type); + Utf8Value key_type_name(env->isolate(), args[offset + 2]); + const auto* algorithm = KeyAlgorithm::FromName(*key_type_name); + // Raw key types require their exact public spelling. + if (algorithm != nullptr && + (algorithm->keyTypeName() == nullptr || + key_type_name.ToStringView() != algorithm->keyTypeName())) { + algorithm = nullptr; + } - DCHECK_IMPLIES(key_type->StringEquals(env->crypto_ec_string()), - args[offset + 4]->IsString()); + DCHECK_IMPLIES(algorithm == &KeyAlgorithm::EC, args[offset + 4]->IsString()); Utf8Value curve(env->isolate(), args[offset + 4]->IsString() ? args[offset + 4].As() : String::Empty(env->isolate())); @@ -745,7 +739,7 @@ static KeyObjectData ImportRawKeyFromArgs( key_data.data(), key_data.size(), format, - key_type, + algorithm, *key_type_name, *curve, type); @@ -1363,33 +1357,10 @@ void KeyObjectHandle::GetKeyDetail(const FunctionCallbackInfo& args) { } Local KeyObjectHandle::GetAsymmetricKeyType() const { - switch (data_.GetAsymmetricKey().id()) { - case EVP_PKEY_RSA: - return env()->crypto_rsa_string(); - case EVP_PKEY_RSA_PSS: - return env()->crypto_rsa_pss_string(); - case EVP_PKEY_DSA: - return env()->crypto_dsa_string(); - case EVP_PKEY_DH: - return env()->crypto_dh_string(); - case EVP_PKEY_EC: - return env()->crypto_ec_string(); - case EVP_PKEY_ED25519: - return env()->crypto_ed25519_string(); - case EVP_PKEY_ED448: - return env()->crypto_ed448_string(); - case EVP_PKEY_X25519: - return env()->crypto_x25519_string(); - case EVP_PKEY_X448: - return env()->crypto_x448_string(); -#if OPENSSL_WITH_PQC - default: - return GetPqcAsymmetricKeyType(env(), data_.GetAsymmetricKey().id()); -#else - default: - return Undefined(env()->isolate()); -#endif - } + const char* name = data_.GetAsymmetricKey().getKeyTypeName(); + if (name == nullptr) return Undefined(env()->isolate()); + return OneByteString( + env()->isolate(), name, -1, NewStringType::kInternalized); } void KeyObjectHandle::GetAsymmetricKeyType( @@ -1406,7 +1377,7 @@ bool KeyObjectHandle::CheckEcKeyData() const { const auto& key = data_.GetAsymmetricKey(); EVPKeyCtxPointer ctx = key.newCtx(); CHECK(ctx); - CHECK_EQ(key.id(), EVP_PKEY_EC); + DCHECK(key.isA(KeyAlgorithm::EC)); return data_.GetKeyType() == kKeyTypePrivate ? ctx.privateCheck() : ctx.publicCheck(); @@ -1497,12 +1468,7 @@ void KeyObjectHandle::RawPublicKey( Mutex::ScopedLock lock(data.mutex()); const auto& pkey = data.GetAsymmetricKey(); - const int id = pkey.id(); - bool is_raw_supported = id == EVP_PKEY_ED25519 || id == EVP_PKEY_ED448 || - id == EVP_PKEY_X25519 || id == EVP_PKEY_X448; -#if OPENSSL_WITH_PQC - is_raw_supported = is_raw_supported || IsPqcKeyId(id); -#endif + const bool is_raw_supported = pkey.supportsRawPublic(); if (!is_raw_supported) { return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); } @@ -1530,12 +1496,7 @@ void KeyObjectHandle::RawPrivateKey( Mutex::ScopedLock lock(data.mutex()); const auto& pkey = data.GetAsymmetricKey(); - const int id = pkey.id(); - bool is_raw_supported = id == EVP_PKEY_ED25519 || id == EVP_PKEY_ED448 || - id == EVP_PKEY_X25519 || id == EVP_PKEY_X448; -#if OPENSSL_WITH_PQC - is_raw_supported = is_raw_supported || IsPqcRawPrivateKeyId(id); -#endif + const bool is_raw_supported = pkey.supportsRawPrivate(); if (!is_raw_supported) { return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); } @@ -1562,7 +1523,7 @@ void KeyObjectHandle::ExportECPublicRaw( Mutex::ScopedLock lock(data.mutex()); const auto& m_pkey = data.GetAsymmetricKey(); - if (m_pkey.id() != EVP_PKEY_EC) { + if (!m_pkey.isA(KeyAlgorithm::EC)) { return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); } @@ -1601,7 +1562,7 @@ void KeyObjectHandle::ExportECPrivateRaw( Mutex::ScopedLock lock(data.mutex()); const auto& m_pkey = data.GetAsymmetricKey(); - if (m_pkey.id() != EVP_PKEY_EC) { + if (!m_pkey.isA(KeyAlgorithm::EC)) { return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); } @@ -1623,25 +1584,20 @@ void KeyObjectHandle::RawSeed(const v8::FunctionCallbackInfo& args) { const KeyObjectData& data = key->Data(); CHECK_EQ(data.GetKeyType(), kKeyTypePrivate); -#if OPENSSL_WITH_PQC Mutex::ScopedLock lock(data.mutex()); const auto& pkey = data.GetAsymmetricKey(); - if (!IsPqcSeedKeyId(pkey.id())) { - return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); - } - auto raw_data = pkey.rawSeed(); if (!raw_data) { + if (raw_data.error == EVPKeyPointer::RawExportError::UNSUPPORTED_KEY_TYPE) { + return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); + } return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get raw seed"); } args.GetReturnValue().Set( - Buffer::Copy(env, raw_data.get(), raw_data.size()) + Buffer::Copy(env, raw_data.value.get(), raw_data.value.size()) .FromMaybe(Local())); -#else - return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); -#endif } void KeyObjectHandle::ExportJWK( @@ -2195,6 +2151,7 @@ void NativeCryptoKey::CryptoKeyTransferData::MemoryInfo( namespace Keys { void Initialize(Environment* env, Local target) { + Local context = env->context(); target->Set(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "KeyObjectHandle"), KeyObjectHandle::Initialize(env)).Check(); @@ -2229,34 +2186,7 @@ void Initialize(Environment* env, Local target) { 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); -#if OPENSSL_WITH_PQC - NODE_DEFINE_CONSTANT(target, EVP_PKEY_ML_DSA_44); - NODE_DEFINE_CONSTANT(target, EVP_PKEY_ML_DSA_65); - NODE_DEFINE_CONSTANT(target, EVP_PKEY_ML_DSA_87); -#if OPENSSL_WITH_PQC_ML_KEM_512 - NODE_DEFINE_CONSTANT(target, EVP_PKEY_ML_KEM_512); -#endif - NODE_DEFINE_CONSTANT(target, EVP_PKEY_ML_KEM_768); - NODE_DEFINE_CONSTANT(target, EVP_PKEY_ML_KEM_1024); -#if OPENSSL_WITH_PQC_SLH_DSA - NODE_DEFINE_CONSTANT(target, EVP_PKEY_SLH_DSA_SHA2_128F); - NODE_DEFINE_CONSTANT(target, EVP_PKEY_SLH_DSA_SHA2_128S); - NODE_DEFINE_CONSTANT(target, EVP_PKEY_SLH_DSA_SHA2_192F); - NODE_DEFINE_CONSTANT(target, EVP_PKEY_SLH_DSA_SHA2_192S); - NODE_DEFINE_CONSTANT(target, EVP_PKEY_SLH_DSA_SHA2_256F); - NODE_DEFINE_CONSTANT(target, EVP_PKEY_SLH_DSA_SHA2_256S); - NODE_DEFINE_CONSTANT(target, EVP_PKEY_SLH_DSA_SHAKE_128F); - NODE_DEFINE_CONSTANT(target, EVP_PKEY_SLH_DSA_SHAKE_128S); - NODE_DEFINE_CONSTANT(target, EVP_PKEY_SLH_DSA_SHAKE_192F); - NODE_DEFINE_CONSTANT(target, EVP_PKEY_SLH_DSA_SHAKE_192S); - NODE_DEFINE_CONSTANT(target, EVP_PKEY_SLH_DSA_SHAKE_256F); - NODE_DEFINE_CONSTANT(target, EVP_PKEY_SLH_DSA_SHAKE_256S); -#endif -#endif - NODE_DEFINE_CONSTANT(target, EVP_PKEY_X25519); - NODE_DEFINE_CONSTANT(target, EVP_PKEY_X448); + SetMethod(context, target, "getPqcKeyTypes", GetPqcKeyTypes); NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS1); NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS8); NODE_DEFINE_CONSTANT(target, kKeyEncodingSPKI); @@ -2277,6 +2207,7 @@ void Initialize(Environment* env, Local target) { void RegisterExternalReferences(ExternalReferenceRegistry* registry) { KeyObjectHandle::RegisterExternalReferences(registry); + registry->Register(GetPqcKeyTypes); } } // namespace Keys diff --git a/src/crypto/crypto_pqc.cc b/src/crypto/crypto_pqc.cc index e12894bc5963..17d6855ada7b 100644 --- a/src/crypto/crypto_pqc.cc +++ b/src/crypto/crypto_pqc.cc @@ -1,295 +1,22 @@ #include "crypto/crypto_pqc.h" -#include "crypto/crypto_util.h" -#include "env-inl.h" -#include "string_bytes.h" +#include "ncrypto.h" +#include "util-inl.h" #include "v8.h" namespace node { -using ncrypto::DataPointer; -using ncrypto::EVPKeyPointer; -using v8::Local; -using v8::Object; -using v8::String; using v8::Value; namespace crypto { -#if OPENSSL_WITH_PQC -namespace { -using PqcKeyTypeGetter = Local (Environment::*)() const; - -enum PqcAlgorithmFlag { - kPqcRawPrivate = 1 << 0, - kPqcRawSeed = 1 << 1, - kPqcSignature = 1 << 2, -}; - -struct PqcAlgorithm { - int id; - const char* name; - PqcKeyTypeGetter key_type; - int flags; -}; - -// ML-DSA and ML-KEM carry private material as a seed. SLH-DSA uses the -// expanded private key and is only exposed by OpenSSL. -constexpr int kPqcMlDsaFlags = kPqcRawSeed | kPqcSignature; -constexpr int kPqcMlKemFlags = kPqcRawSeed; -constexpr int kPqcSlhDsaFlags = kPqcRawPrivate | kPqcSignature; - -constexpr PqcAlgorithm kPqcAlgorithms[] = { - {EVP_PKEY_ML_DSA_44, - "ML-DSA-44", - &Environment::crypto_ml_dsa_44_string, - kPqcMlDsaFlags}, - {EVP_PKEY_ML_DSA_65, - "ML-DSA-65", - &Environment::crypto_ml_dsa_65_string, - kPqcMlDsaFlags}, - {EVP_PKEY_ML_DSA_87, - "ML-DSA-87", - &Environment::crypto_ml_dsa_87_string, - kPqcMlDsaFlags}, - {EVP_PKEY_ML_KEM_768, - "ML-KEM-768", - &Environment::crypto_ml_kem_768_string, - kPqcMlKemFlags}, - {EVP_PKEY_ML_KEM_1024, - "ML-KEM-1024", - &Environment::crypto_ml_kem_1024_string, - kPqcMlKemFlags}, - -#if OPENSSL_WITH_PQC_ML_KEM_512 - {EVP_PKEY_ML_KEM_512, - "ML-KEM-512", - &Environment::crypto_ml_kem_512_string, - kPqcMlKemFlags}, -#endif -#if OPENSSL_WITH_PQC_SLH_DSA - {EVP_PKEY_SLH_DSA_SHA2_128F, - "SLH-DSA-SHA2-128f", - &Environment::crypto_slh_dsa_sha2_128f_string, - kPqcSlhDsaFlags}, - {EVP_PKEY_SLH_DSA_SHA2_128S, - "SLH-DSA-SHA2-128s", - &Environment::crypto_slh_dsa_sha2_128s_string, - kPqcSlhDsaFlags}, - {EVP_PKEY_SLH_DSA_SHA2_192F, - "SLH-DSA-SHA2-192f", - &Environment::crypto_slh_dsa_sha2_192f_string, - kPqcSlhDsaFlags}, - {EVP_PKEY_SLH_DSA_SHA2_192S, - "SLH-DSA-SHA2-192s", - &Environment::crypto_slh_dsa_sha2_192s_string, - kPqcSlhDsaFlags}, - {EVP_PKEY_SLH_DSA_SHA2_256F, - "SLH-DSA-SHA2-256f", - &Environment::crypto_slh_dsa_sha2_256f_string, - kPqcSlhDsaFlags}, - {EVP_PKEY_SLH_DSA_SHA2_256S, - "SLH-DSA-SHA2-256s", - &Environment::crypto_slh_dsa_sha2_256s_string, - kPqcSlhDsaFlags}, - {EVP_PKEY_SLH_DSA_SHAKE_128F, - "SLH-DSA-SHAKE-128f", - &Environment::crypto_slh_dsa_shake_128f_string, - kPqcSlhDsaFlags}, - {EVP_PKEY_SLH_DSA_SHAKE_128S, - "SLH-DSA-SHAKE-128s", - &Environment::crypto_slh_dsa_shake_128s_string, - kPqcSlhDsaFlags}, - {EVP_PKEY_SLH_DSA_SHAKE_192F, - "SLH-DSA-SHAKE-192f", - &Environment::crypto_slh_dsa_shake_192f_string, - kPqcSlhDsaFlags}, - {EVP_PKEY_SLH_DSA_SHAKE_192S, - "SLH-DSA-SHAKE-192s", - &Environment::crypto_slh_dsa_shake_192s_string, - kPqcSlhDsaFlags}, - {EVP_PKEY_SLH_DSA_SHAKE_256F, - "SLH-DSA-SHAKE-256f", - &Environment::crypto_slh_dsa_shake_256f_string, - kPqcSlhDsaFlags}, - {EVP_PKEY_SLH_DSA_SHAKE_256S, - "SLH-DSA-SHAKE-256s", - &Environment::crypto_slh_dsa_shake_256s_string, - kPqcSlhDsaFlags}, -#endif -}; - -const PqcAlgorithm* FindPqcAlgorithmById(int id) { - for (const auto& alg : kPqcAlgorithms) { - if (alg.id == id) return &alg; - } - return nullptr; -} - -const PqcAlgorithm* FindPqcAlgorithmByName(const char* name) { - for (const auto& alg : kPqcAlgorithms) { - if (strcmp(name, alg.name) == 0) return &alg; - } - return nullptr; -} - -bool HasPqcAlgorithmFlag(const PqcAlgorithm* alg, PqcAlgorithmFlag flag) { - return alg != nullptr && (alg->flags & flag) != 0; -} - -bool TrySetEncodedKey(Environment* env, - DataPointer data, - Local target, - Local key) { - Local encoded; - if (!data) return false; - const ncrypto::Buffer out = data; - return StringBytes::Encode(env->isolate(), out.data, out.len, BASE64URL) - .ToLocal(&encoded) && - target->DefineOwnProperty(env->context(), key, encoded) - .FromMaybe(false); -} -} // namespace - -bool ExportJwkPqcKey(Environment* env, - const KeyObjectData& key, - Local target) { - Mutex::ScopedLock lock(key.mutex()); - const auto& pkey = key.GetAsymmetricKey(); - - const PqcAlgorithm* alg = FindPqcAlgorithmById(pkey.id()); - CHECK(alg); - - if (key.GetKeyType() == kKeyTypePrivate) { - const bool uses_seed = HasPqcAlgorithmFlag(alg, kPqcRawSeed); - DataPointer priv_data = uses_seed ? pkey.rawSeed() : pkey.rawPrivateKey(); - if (uses_seed && !priv_data) { - THROW_ERR_CRYPTO_OPERATION_FAILED(env, - "key does not have an available seed"); - return false; - } - if (!TrySetEncodedKey( - env, std::move(priv_data), target, env->jwk_priv_string())) { - return false; - } - } - - return !(!target - ->DefineOwnProperty(env->context(), - env->jwk_kty_string(), - env->jwk_akp_string()) - .FromMaybe(false) || - !target - ->DefineOwnProperty(env->context(), - env->jwk_alg_string(), - OneByteString(env->isolate(), alg->name)) - .FromMaybe(false) || - !TrySetEncodedKey( - env, pkey.rawPublicKey(), target, env->jwk_pub_string())); -} - -KeyObjectData ImportJWKPqcKey(Environment* env, Local jwk) { - Local alg_value; - Local pub_value; - Local priv_value; - - if (!jwk->Get(env->context(), env->jwk_alg_string()).ToLocal(&alg_value) || - !jwk->Get(env->context(), env->jwk_pub_string()).ToLocal(&pub_value) || - !jwk->Get(env->context(), env->jwk_priv_string()).ToLocal(&priv_value)) { - return {}; - } - - Utf8Value alg_str(env->isolate(), - alg_value->IsString() ? alg_value.As() - : String::Empty(env->isolate())); - - const PqcAlgorithm* alg = FindPqcAlgorithmByName(*alg_str); - if (!alg) { - THROW_ERR_CRYPTO_INVALID_JWK(env, "Unsupported JWK AKP \"alg\""); - return {}; - } - - if (!pub_value->IsString() || - (!priv_value->IsUndefined() && !priv_value->IsString())) { - THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK AKP key"); - return {}; - } - - KeyType type = priv_value->IsString() ? kKeyTypePrivate : kKeyTypePublic; - - EVPKeyPointer pkey; - if (type == kKeyTypePrivate) { - ByteSource priv = - ByteSource::FromEncodedString(env, priv_value.As()); - ncrypto::Buffer buf{ - .data = priv.data(), - .len = priv.size(), - }; - pkey = HasPqcAlgorithmFlag(alg, kPqcRawSeed) - ? EVPKeyPointer::NewRawSeed(alg->id, buf) - : EVPKeyPointer::NewRawPrivate(alg->id, buf); - } else { - ByteSource pub = ByteSource::FromEncodedString(env, pub_value.As()); - pkey = - EVPKeyPointer::NewRawPublic(alg->id, - ncrypto::Buffer{ - .data = pub.data(), - .len = pub.size(), - }); - } - - if (!pkey) { - THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK AKP key"); - return {}; - } - - // When importing a private key, verify that the pub field matches - // the public key derived from the private key material. - if (type == kKeyTypePrivate && pub_value->IsString()) { - ByteSource pub = ByteSource::FromEncodedString(env, pub_value.As()); - auto derived_pub = pkey.rawPublicKey(); - if (!derived_pub || derived_pub.size() != pub.size() || - CRYPTO_memcmp(derived_pub.get(), pub.data(), pub.size()) != 0) { - THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK AKP key"); - return {}; - } - } - - return KeyObjectData::CreateAsymmetric(type, std::move(pkey)); -} - -bool IsPqcKeyId(int id) { - return FindPqcAlgorithmById(id) != nullptr; -} - -bool IsPqcRawPrivateKeyId(int id) { - const PqcAlgorithm* alg = FindPqcAlgorithmById(id); - return HasPqcAlgorithmFlag(alg, kPqcRawPrivate); -} - -bool IsPqcSeedKeyId(int id) { - const PqcAlgorithm* alg = FindPqcAlgorithmById(id); - return HasPqcAlgorithmFlag(alg, kPqcRawSeed); -} - -bool IsPqcSignatureKeyId(int id) { - const PqcAlgorithm* alg = FindPqcAlgorithmById(id); - return HasPqcAlgorithmFlag(alg, kPqcSignature); -} - -int GetPqcNidFromName(const char* name) { - for (const auto& alg : kPqcAlgorithms) { - if (StringEqualNoCase(name, alg.name)) return alg.id; - } - return NID_undef; -} - -Local GetPqcAsymmetricKeyType(Environment* env, int id) { - const PqcAlgorithm* alg = FindPqcAlgorithmById(id); - if (alg == nullptr) return v8::Undefined(env->isolate()); - - Local key_type = (env->*(alg->key_type))(); - return key_type.As(); +void GetPqcKeyTypes(const v8::FunctionCallbackInfo& args) { + v8::LocalVector names(args.GetIsolate()); + ncrypto::KeyAlgorithm::ForEachPqc( + [&](const ncrypto::KeyAlgorithm& algorithm) { + names.push_back(OneByteString(args.GetIsolate(), algorithm.name())); + }); + args.GetReturnValue().Set( + v8::Array::New(args.GetIsolate(), names.data(), names.size())); } -#endif } // namespace crypto } // namespace node diff --git a/src/crypto/crypto_pqc.h b/src/crypto/crypto_pqc.h index 14f919d94c6f..23cdcdc05123 100644 --- a/src/crypto/crypto_pqc.h +++ b/src/crypto/crypto_pqc.h @@ -3,35 +3,11 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#include "crypto/crypto_keys.h" -#include "env.h" #include "v8.h" namespace node { namespace crypto { -#if OPENSSL_WITH_PQC -bool ExportJwkPqcKey(Environment* env, - const KeyObjectData& key, - v8::Local target); - -KeyObjectData ImportJWKPqcKey(Environment* env, v8::Local jwk); - -// Returns true for PQC algorithms that support raw private key export/import. -bool IsPqcRawPrivateKeyId(int id); -// Returns true if the given EVP_PKEY id is a PQC algorithm known to Node. -bool IsPqcKeyId(int id); -// Returns true for PQC algorithms that carry the private key as a seed -// (ML-DSA, ML-KEM). Returns false for algorithms that use the expanded -// private key (SLH-DSA), or for non-PQC ids. -bool IsPqcSeedKeyId(int id); -// Returns true for PQC signature algorithms (ML-DSA, SLH-DSA). Returns false -// for ML-KEM or for non-PQC ids. -bool IsPqcSignatureKeyId(int id); -// Returns the EVP_PKEY id for the given PQC algorithm name, or NID_undef. -int GetPqcNidFromName(const char* name); -// Returns the JS asymmetricKeyType string for a PQC id, or undefined. -v8::Local GetPqcAsymmetricKeyType(Environment* env, int id); -#endif +void GetPqcKeyTypes(const v8::FunctionCallbackInfo& args); } // namespace crypto } // namespace node diff --git a/src/crypto/crypto_rsa.cc b/src/crypto/crypto_rsa.cc index f6919221a4f2..36b53a0dfa68 100644 --- a/src/crypto/crypto_rsa.cc +++ b/src/crypto/crypto_rsa.cc @@ -19,6 +19,7 @@ using ncrypto::DataPointer; using ncrypto::Digest; using ncrypto::EVPKeyCtxPointer; using ncrypto::EVPKeyPointer; +using ncrypto::KeyAlgorithm; #if NCRYPTO_USE_LEGACY_KEY_TYPES using ncrypto::RSAPointer; #endif @@ -57,9 +58,9 @@ bool IsRsaPssDigestEncodable(const Digest& digest) { } // namespace EVPKeyCtxPointer RsaKeyGenTraits::Setup(RsaKeyPairGenConfig* params) { - auto ctx = EVPKeyCtxPointer::NewFromID( - params->params.variant == kKeyVariantRSA_PSS ? EVP_PKEY_RSA_PSS - : EVP_PKEY_RSA); + auto ctx = EVPKeyCtxPointer::NewFromAlgorithm( + params->params.variant == kKeyVariantRSA_PSS ? KeyAlgorithm::RSA_PSS + : KeyAlgorithm::RSA); if (!ctx.initForKeygen() || !ctx.setRsaKeygenBits(params->params.modulus_bits)) { @@ -504,25 +505,7 @@ KeyObjectData ImportJWKRsaKey(Environment* env, Local jwk) { return {}; } - // Verify that n is the product of all prime factors. - const auto& pub = rsa_view.getPublicKey(); - const auto& priv = rsa_view.getPrivateKey(); - auto product = BignumPointer::New(); - BN_CTX* ctx = BN_CTX_new(); - bool n_valid = - ctx && product && BN_mul(product.get(), priv.p, priv.q, ctx) == 1; - for (const auto& info : rsa_view.getOtherPrimeInfos()) { - auto next = BignumPointer::New(); - if (!n_valid || !next || - BN_mul(next.get(), product.get(), info.r, ctx) != 1) { - n_valid = false; - break; - } - product = std::move(next); - } - n_valid = n_valid && BN_cmp(product.get(), pub.n) == 0; - BN_CTX_free(ctx); - if (!n_valid) { + if (!rsa_view.checkPrimeProduct()) { THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK RSA key"); return {}; } @@ -582,7 +565,7 @@ bool GetRsaKeyDetail(Environment* env, return false; } - if (m_pkey.id() == EVP_PKEY_RSA_PSS) { + if (m_pkey.isA(KeyAlgorithm::RSA_PSS)) { // Due to the way ASN.1 encoding works, default values are omitted when // encoding the data structure. However, there are also RSA-PSS keys for // which no parameters are set. In that case, the ASN.1 RSASSA-PSS-params diff --git a/src/crypto/crypto_sig.cc b/src/crypto/crypto_sig.cc index 5e09477a6913..dffa807d183c 100644 --- a/src/crypto/crypto_sig.cc +++ b/src/crypto/crypto_sig.cc @@ -22,7 +22,6 @@ using ncrypto::ClearErrorOnReturn; using ncrypto::DataPointer; using ncrypto::Digest; using ncrypto::ECDSASigPointer; -using ncrypto::ECKeyPointer; using ncrypto::EVPKeyCtxPointer; using ncrypto::EVPKeyPointer; using ncrypto::EVPMDCtxPointer; @@ -47,12 +46,11 @@ using v8::Value; namespace crypto { namespace { int GetPaddingFromJS(const EVPKeyPointer& key, Local val) { - int padding = key.getDefaultSignPadding(); if (!val->IsUndefined()) [[likely]] { CHECK(val->IsInt32()); - padding = val.As()->Value(); + return val.As()->Value(); } - return padding; + return key.getDefaultSignPadding(); } std::optional GetSaltLenFromJS(Local val) { @@ -83,147 +81,6 @@ bool ApplyRSAOptions(const EVPKeyPointer& pkey, return true; } -constexpr size_t kEd25519PointSize = 32; -constexpr size_t kEd448PointSize = 57; - -// Ed25519 has cofactor 8, so the first eight entries are the full -// canonical small-order subgroup: identity, one point of order 2, -// two points of order 4, and four points of order 8. -constexpr unsigned char kEd25519SmallOrderPoints[][kEd25519PointSize] = { - // Identity. - {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - // Order 2. - {0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, - // Order 4. - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - // Order 8. - {0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, - 0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, - 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0x7a}, - {0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, - 0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, - 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0xfa}, - {0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, - 0x89, 0xf2, 0xef, 0x98, 0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, - 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x05}, - {0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, - 0x89, 0xf2, 0xef, 0x98, 0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, - 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x85}, - // Non-canonical encodings of the same small-order points. - {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80}, - {0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, - {0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, - {0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, - {0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, - {0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, -}; - -// Ed448 has cofactor 4, so these four entries are the full canonical -// small-order subgroup: identity, one point of order 2, and two points -// of order 4. -constexpr unsigned char kEd448SmallOrderPoints[][kEd448PointSize] = { - // Identity. - {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - // Order 2. - {0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00}, - // Order 4. - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80}, -}; - -template -bool ContainsPoint(const unsigned char* candidate, - const unsigned char (&points)[Count][PointSize]) { - for (const auto& point : points) { - if (memcmp(candidate, point, PointSize) == 0) return true; - } - return false; -} - -bool IsSmallOrderEdDsaPoint(int id, - const unsigned char* candidate, - size_t size) { - switch (id) { - case EVP_PKEY_ED25519: - return size == kEd25519PointSize && - ContainsPoint(candidate, kEd25519SmallOrderPoints); - case EVP_PKEY_ED448: - return size == kEd448PointSize && - ContainsPoint(candidate, kEd448SmallOrderPoints); - default: - return false; - } -} - -bool HasSmallOrderEdDsaPoint(const EVPKeyPointer& key, - const ByteSource& signature) { - const int id = key.id(); - size_t point_size; - - switch (id) { - case EVP_PKEY_ED25519: - point_size = kEd25519PointSize; - break; - case EVP_PKEY_ED448: - point_size = kEd448PointSize; - break; - default: - return false; - } - - if (signature.size() != point_size * 2) return false; - - if (IsSmallOrderEdDsaPoint(id, signature.data(), point_size)) { - return true; - } - - unsigned char raw_public_key[kEd448PointSize]; - size_t raw_public_key_size = point_size; - if (EVP_PKEY_get_raw_public_key( - key.get(), raw_public_key, &raw_public_key_size) != 1) { - return false; - } - - return IsSmallOrderEdDsaPoint(id, raw_public_key, raw_public_key_size); -} - std::unique_ptr Node_SignFinal(Environment* env, EVPMDCtxPointer&& mdctx, const EVPKeyPointer& pkey, @@ -380,54 +237,7 @@ void CheckThrow(Environment* env, SignBase::Error error) { } bool UseP1363Encoding(const EVPKeyPointer& key, const DSASigEnc dsa_encoding) { - return key.isSigVariant() && dsa_encoding == DSASigEnc::P1363; -} - -bool SupportsContextString(const EVPKeyPointer& key) { - if (!OPENSSL_WITH_SIGNATURE_CONTEXT_STRING) return false; - - const int id = key.id(); -#if OPENSSL_WITH_PQC - if (IsPqcSignatureKeyId(id)) return true; -#endif -#ifndef OPENSSL_IS_BORINGSSL - if (id == EVP_PKEY_ED25519 || id == EVP_PKEY_ED448) return true; -#endif - return false; -} - -// Returns true unless the key is known not to be SM2, so that a key whose curve -// cannot be determined opts out of the prehashed fallback rather than into it. -bool MayBeSM2Key(const EVPKeyPointer& key) { -#ifdef OPENSSL_IS_BORINGSSL - return false; -#else - if (key.id() == EVP_PKEY_SM2) return true; - if (key.id() != EVP_PKEY_EC) return false; - -#if NCRYPTO_USE_OPENSSL3_PROVIDER - // An ECKeyPointer would also need the public point, which a provider-backed - // key need not expose. - char group_name[64]; - size_t group_name_len = 0; - if (EVP_PKEY_get_utf8_string_param(key.get(), - OSSL_PKEY_PARAM_GROUP_NAME, - group_name, - sizeof(group_name), - &group_name_len) != 1) { - return true; - } - return OBJ_sn2nid(group_name) == NID_sm2 || - EC_curve_nist2nid(group_name) == NID_sm2; -#else - ECKeyPointer ec(key); - if (!ec) return true; - - const EC_GROUP* group = ec.getGroup(); - if (group == nullptr) return true; - return EC_GROUP_get_curve_name(group) == NID_sm2; -#endif -#endif + return dsa_encoding == DSASigEnc::P1363 && key.isSigVariant(); } bool CanUsePrehashedFallback(const EVPKeyPointer& key, @@ -439,7 +249,7 @@ bool CanUsePrehashedFallback(const EVPKeyPointer& key, // SM2 digest signing first hashes the algorithm-specific Z value, so the // lower-level prehashed sign/verify operation is not equivalent. - return key.isSigVariant() && !MayBeSM2Key(key); + return key.isSigVariant() && !key.mayBeSM2(); } ByteSource SignPrehashed(Environment* env, @@ -644,7 +454,8 @@ void Sign::SignFinal(const FunctionCallbackInfo& args) { if (!key) [[unlikely]] return; - if (key.isOneShotVariant()) [[unlikely]] { + const auto* algorithm = key.getAlgorithm(); + if (algorithm != nullptr && algorithm->isOneShot()) [[unlikely]] { THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env); return; } @@ -761,7 +572,8 @@ void Verify::VerifyFinal(const FunctionCallbackInfo& args) { if (!key) [[unlikely]] return; - if (key.isOneShotVariant()) [[unlikely]] { + const auto* algorithm = key.getAlgorithm(); + if (algorithm != nullptr && algorithm->isOneShot()) [[unlikely]] { THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env); return; } @@ -924,15 +736,16 @@ bool SignTraits::DeriveBits(Environment* env, bool has_context = (params.flags & SignConfiguration::kHasContextString && params.context_string.size() > 0); - if (has_context && !SupportsContextString(key)) { + if (has_context && !key.supportsContextString()) { errors->Insert(NodeCryptoError::CONTEXT_UNSUPPORTED); errors->SetNodeErrorCode("ERR_CRYPTO_OPERATION_FAILED"); return false; } - int padding = params.flags & SignConfiguration::kHasPadding - ? params.padding - : key.getDefaultSignPadding(); + int padding = params.padding; + if (!(params.flags & SignConfiguration::kHasPadding)) { + padding = key.getDefaultSignPadding(); + } std::optional salt_length = params.flags & SignConfiguration::kHasSaltLength @@ -977,7 +790,8 @@ bool SignTraits::DeriveBits(Environment* env, switch (params.mode) { case SignConfiguration::Mode::Sign: { - if (key.isOneShotVariant()) { + const auto* algorithm = key.getAlgorithm(); + if (algorithm != nullptr && algorithm->isOneShot()) { auto data = context.signOneShot(params.data); if (!data) [[unlikely]] { return false; @@ -1023,7 +837,7 @@ bool SignTraits::DeriveBits(Environment* env, // Retrying 0 would perform a second verification for every mismatch. int verify_result = context.verifyOneShot(params.data, params.signature); if (verify_result == 1 && - !HasSmallOrderEdDsaPoint(key, params.signature)) { + !key.hasSmallOrderEdDsaPoint(params.signature)) { static_cast(buf.get())[0] = 1; } else if (verify_result < 0 && CanUsePrehashedFallback(key, params.digest, has_context) && diff --git a/src/crypto/crypto_tls.cc b/src/crypto/crypto_tls.cc index 14cf5cc8c85d..9629d62caa38 100644 --- a/src/crypto/crypto_tls.cc +++ b/src/crypto/crypto_tls.cc @@ -1954,19 +1954,19 @@ void TLSWrap::GetSharedSigalgs(const FunctionCallbackInfo& args) { nullptr); switch (sign_nid) { - case EVP_PKEY_RSA: + case NID_rsaEncryption: sig_with_md = "RSA+"; break; - case EVP_PKEY_RSA_PSS: + case NID_rsassaPss: sig_with_md = "RSA-PSS+"; break; - case EVP_PKEY_DSA: + case NID_dsa: sig_with_md = "DSA+"; break; - case EVP_PKEY_EC: + case NID_X9_62_id_ecPublicKey: sig_with_md = "ECDSA+"; break; diff --git a/src/crypto/crypto_util.cc b/src/crypto/crypto_util.cc index 568325a99462..a99b4f10af24 100644 --- a/src/crypto/crypto_util.cc +++ b/src/crypto/crypto_util.cc @@ -526,22 +526,7 @@ void InitCryptoOnce() { OPENSSL_init_ssl(0, settings); InstallFipsIndicatorCallback(); -#if OPENSSL_WITH_OPENSSL_PQC - // Configure all loaded providers to prefer seed-only format for ML-KEM and - // ML-DSA private keys in PKCS#8 export, falling back to priv-only when a - // seed is not available. The provider encoder reads these parameters at - // encoding time via ossl_prov_ctx_get_param(). - OSSL_PROVIDER_do_all( - nullptr, - [](OSSL_PROVIDER* provider, void*) -> int { - OSSL_PROVIDER_add_conf_parameter( - provider, "ml-kem.output_formats", "seed-only,priv-only"); - OSSL_PROVIDER_add_conf_parameter( - provider, "ml-dsa.output_formats", "seed-only,priv-only"); - return 1; - }, - nullptr); -#endif + ncrypto::ConfigurePqcEncoding(); OPENSSL_INIT_free(settings); settings = nullptr; diff --git a/src/env_properties.h b/src/env_properties.h index bfb981d1c89b..f682b2b521fc 100644 --- a/src/env_properties.h +++ b/src/env_properties.h @@ -114,33 +114,6 @@ V(code_string, "code") \ V(config_string, "config") \ V(constants_string, "constants") \ - V(crypto_dh_string, "dh") \ - V(crypto_dsa_string, "dsa") \ - V(crypto_ec_string, "ec") \ - V(crypto_ed25519_string, "ed25519") \ - V(crypto_ed448_string, "ed448") \ - V(crypto_ml_dsa_44_string, "ml-dsa-44") \ - V(crypto_ml_dsa_65_string, "ml-dsa-65") \ - V(crypto_ml_dsa_87_string, "ml-dsa-87") \ - V(crypto_ml_kem_512_string, "ml-kem-512") \ - V(crypto_ml_kem_768_string, "ml-kem-768") \ - V(crypto_ml_kem_1024_string, "ml-kem-1024") \ - V(crypto_slh_dsa_sha2_128f_string, "slh-dsa-sha2-128f") \ - V(crypto_slh_dsa_sha2_128s_string, "slh-dsa-sha2-128s") \ - V(crypto_slh_dsa_sha2_192f_string, "slh-dsa-sha2-192f") \ - V(crypto_slh_dsa_sha2_192s_string, "slh-dsa-sha2-192s") \ - V(crypto_slh_dsa_sha2_256f_string, "slh-dsa-sha2-256f") \ - V(crypto_slh_dsa_sha2_256s_string, "slh-dsa-sha2-256s") \ - V(crypto_slh_dsa_shake_128f_string, "slh-dsa-shake-128f") \ - V(crypto_slh_dsa_shake_128s_string, "slh-dsa-shake-128s") \ - V(crypto_slh_dsa_shake_192f_string, "slh-dsa-shake-192f") \ - V(crypto_slh_dsa_shake_192s_string, "slh-dsa-shake-192s") \ - V(crypto_slh_dsa_shake_256f_string, "slh-dsa-shake-256f") \ - V(crypto_slh_dsa_shake_256s_string, "slh-dsa-shake-256s") \ - V(crypto_x25519_string, "x25519") \ - V(crypto_x448_string, "x448") \ - V(crypto_rsa_string, "rsa") \ - V(crypto_rsa_pss_string, "rsa-pss") \ V(cwd_string, "cwd") \ V(data_string, "data") \ V(database_string, "database") \ diff --git a/test/cctest/test_node_crypto.cc b/test/cctest/test_node_crypto.cc index 94940e3c55f9..36bbf2c93f7a 100644 --- a/test/cctest/test_node_crypto.cc +++ b/test/cctest/test_node_crypto.cc @@ -10,6 +10,11 @@ #include +using ncrypto::Ec; +using ncrypto::EVPKeyCtxPointer; +using ncrypto::EVPKeyPointer; +using ncrypto::KeyAlgorithm; + /* * This test verifies that a call to NewRootCertDir with the build time * configuration option --openssl-system-ca-path set to an missing file, will @@ -65,3 +70,419 @@ TEST(NodeCrypto, TryGetIntCipherOutputLength) { EXPECT_FALSE(node::crypto::TryGetIntCipherOutputLength( 0, static_cast(INT_MAX) + 1, &output_len)); } + +TEST(NodeCrypto, KeyAlgorithmNames) { + EXPECT_EQ(KeyAlgorithm::FromName("rsa-pss"), &KeyAlgorithm::RSA_PSS); + EXPECT_EQ(KeyAlgorithm::FromName("ML-DSA-44"), &KeyAlgorithm::ML_DSA_44); + EXPECT_EQ(KeyAlgorithm::FromName("unknown-key-algorithm"), nullptr); + EXPECT_EQ(KeyAlgorithm::FromName(nullptr), nullptr); + EXPECT_FALSE(EVPKeyPointer().isA(KeyAlgorithm::RSA)); + EXPECT_FALSE(EVPKeyPointer::isA(nullptr, KeyAlgorithm::RSA)); + auto empty = EVPKeyPointer::New(); + ASSERT_TRUE(empty); + EXPECT_FALSE(empty.isA(KeyAlgorithm::RSA)); + EXPECT_FALSE(empty.supportsContextString()); + EXPECT_FALSE(EVPKeyPointer().supportsContextString()); + EXPECT_FALSE(empty.rawSeed()); + EXPECT_FALSE(EVPKeyPointer().rawSeed()); + EXPECT_FALSE(empty.isA("unknown-key-algorithm")); + EXPECT_FALSE(empty.isA(static_cast(nullptr))); +} + +TEST(NodeCrypto, UnsupportedRawExports) { + using Error = EVPKeyPointer::RawExportError; + EVPKeyPointer key; + for (int i = 0; i < 2; i++) { + const auto seed = key.rawSeed(); + EXPECT_FALSE(seed); + EXPECT_EQ(seed.error, Error::UNSUPPORTED_KEY_TYPE); + const auto jwk = key.exportRawJwk(/* include_private */ false); + EXPECT_FALSE(jwk); + EXPECT_EQ(jwk.error, Error::UNSUPPORTED_KEY_TYPE); + key = EVPKeyPointer::New(); + ASSERT_TRUE(key); + } + auto ctx = EVPKeyCtxPointer::NewFromAlgorithm(KeyAlgorithm::RSA); + ASSERT_TRUE(ctx); + ASSERT_EQ(EVP_PKEY_keygen_init(ctx.get()), 1); + EVP_PKEY* raw = nullptr; + ASSERT_EQ(EVP_PKEY_keygen(ctx.get(), &raw), 1); + key.reset(raw); + EXPECT_EQ(key.rawSeed().error, Error::UNSUPPORTED_KEY_TYPE); + EXPECT_EQ(key.exportRawJwk(/* include_private */ false).error, + Error::UNSUPPORTED_KEY_TYPE); +} + +namespace { +void CheckRawJwkImport(const EVPKeyPointer& key) { + auto jwk = key.exportRawJwk(/* include_private */ true); + ASSERT_TRUE(jwk); + const auto& data = jwk.value; + const ncrypto::Buffer pub = data.public_key; + const ncrypto::Buffer priv = data.private_key; + auto private_key = EVPKeyPointer::NewRawJwk(*data.algorithm, pub, priv); + ASSERT_TRUE(private_key); + auto exported = private_key.exportRawJwk(/* include_private */ true); + ASSERT_TRUE(exported); + EXPECT_EQ(exported.value.algorithm, data.algorithm); + ASSERT_EQ(exported.value.private_key.size(), priv.len); + EXPECT_EQ(memcmp(exported.value.private_key.get(), priv.data, priv.len), 0); + auto public_key = EVPKeyPointer::NewRawJwk(*data.algorithm, pub); + ASSERT_TRUE(public_key); + auto exported_public = public_key.exportRawJwk(/* include_private */ false); + ASSERT_TRUE(exported_public); + EXPECT_FALSE(exported_public.value.private_key); + ASSERT_EQ(exported_public.value.public_key.size(), pub.len); + EXPECT_EQ(memcmp(exported_public.value.public_key.get(), pub.data, pub.len), + 0); + + auto different_pub = ncrypto::DataPointer::Copy({pub.data, pub.len}); + ASSERT_TRUE(different_pub); + ASSERT_GT(different_pub.size(), 0u); + different_pub.get()[0] ^= 1; + const ncrypto::Buffer mismatch = different_pub; + EXPECT_FALSE(EVPKeyPointer::NewRawJwk(*data.algorithm, mismatch, priv)); + EXPECT_FALSE(EVPKeyPointer::NewRawJwk( + *data.algorithm, pub, ncrypto::Buffer{nullptr, 0})); + EXPECT_FALSE(EVPKeyPointer::NewRawJwk(KeyAlgorithm::RSA, pub, priv)); +} +} // namespace + +TEST(NodeCrypto, RsaPrimeProduct) { + ncrypto::ClearErrorOnReturn clear_errors; + EXPECT_FALSE(ncrypto::Rsa().checkPrimeProduct()); + auto ctx = EVPKeyCtxPointer::NewFromAlgorithm(KeyAlgorithm::RSA); + ASSERT_TRUE(ctx); + ASSERT_TRUE(ctx.initForKeygen()); + EVP_PKEY* raw = nullptr; + ASSERT_EQ(EVP_PKEY_keygen(ctx.get(), &raw), 1); + EVPKeyPointer key(raw); + ncrypto::Rsa rsa = key; + ASSERT_TRUE(rsa); + EXPECT_TRUE(rsa.checkPrimeProduct()); + auto pub = rsa.getPublicKey(); + ncrypto::BignumPointer n(BN_dup(pub.n)); + ncrypto::BignumPointer e(BN_dup(pub.e)); + ASSERT_TRUE(n); + ASSERT_TRUE(e); + ASSERT_EQ(BN_add_word(n.get(), 1), 1); + ASSERT_TRUE(rsa.setPublicKey(std::move(n), std::move(e))); + EXPECT_FALSE(rsa.checkPrimeProduct()); +} + +TEST(NodeCrypto, EcKeyComponents) { + ncrypto::ClearErrorOnReturn clear_errors; + ncrypto::BignumPointer x; + ncrypto::BignumPointer y; + ncrypto::BignumPointer priv; + int degree = 0; + EXPECT_FALSE( + ncrypto::Ec::GetKeyComponents(EVPKeyPointer(), &x, &y, &priv, °ree)); + EXPECT_EQ(ncrypto::Ec::GetCurveId(EVPKeyPointer()), NID_undef); + EXPECT_FALSE(ncrypto::Ec::TryExportPublic(EVPKeyPointer(), + POINT_CONVERSION_UNCOMPRESSED)); + EXPECT_FALSE(ncrypto::Ec::ExportPrivate(EVPKeyPointer())); + auto empty = EVPKeyPointer::New(); + ASSERT_TRUE(empty); + EXPECT_FALSE(ncrypto::Ec::GetKeyComponents(empty, &x, &y, &priv, °ree)); + for (int nid : {NID_X9_62_prime256v1, NID_secp384r1, NID_secp521r1}) { + auto ec = ncrypto::ECKeyPointer::NewByCurveName(nid); + ASSERT_TRUE(ec); + EXPECT_FALSE(ec.checkPrivateKey()); + auto parameters = EVPKeyPointer::New(); + ASSERT_TRUE(parameters); + ASSERT_TRUE(parameters.set(ec)); + EXPECT_EQ(ncrypto::Ec::GetCurveId(parameters), nid); + EXPECT_FALSE( + ncrypto::Ec::GetKeyComponents(parameters, &x, &y, nullptr, °ree)); + ASSERT_TRUE(ec.generate()); + EXPECT_TRUE(ec.checkPrivateKey()); + auto key = EVPKeyPointer::New(); + ASSERT_TRUE(key); + ASSERT_TRUE(key.set(ec)); + EXPECT_TRUE(key.isA(KeyAlgorithm::EC)); + EXPECT_EQ(ncrypto::Ec::GetCurveId(key), nid); + ASSERT_TRUE(ncrypto::Ec::GetKeyComponents(key, &x, &y, &priv, °ree)); + EXPECT_EQ(degree, EC_GROUP_get_degree(ec.getGroup())); + EXPECT_EQ(BN_cmp(priv.get(), ec.getPrivateKey()), 0); + const size_t width = (degree + 7) / 8; + auto point = ncrypto::DataPointer::Alloc(1 + 2 * width); + ASSERT_TRUE(point); + ASSERT_EQ(EC_POINT_point2oct(ec.getGroup(), + ec.getPublicKey(), + POINT_CONVERSION_UNCOMPRESSED, + point.get(), + point.size(), + nullptr), + point.size()); + auto x_bytes = x.encodePadded(width); + auto y_bytes = y.encodePadded(width); + ASSERT_TRUE(x_bytes); + ASSERT_TRUE(y_bytes); + EXPECT_EQ(memcmp(x_bytes.get(), point.get() + 1, width), 0); + EXPECT_EQ( + memcmp(y_bytes.get(), point.get() + 1 + width, width), + 0); + auto raw_private = ncrypto::Ec::ExportPrivate(key); + auto expected_private = priv.encodePadded(width); + ASSERT_TRUE(raw_private); + ASSERT_EQ(raw_private.size(), expected_private.size()); + EXPECT_EQ( + memcmp(raw_private.get(), expected_private.get(), raw_private.size()), + 0); + auto raw_public = + ncrypto::Ec::TryExportPublic(key, POINT_CONVERSION_UNCOMPRESSED); +#if NCRYPTO_USE_OPENSSL3_PROVIDER + ASSERT_TRUE(raw_public); + ASSERT_EQ(raw_public.size(), point.size()); + EXPECT_EQ(memcmp(raw_public.get(), point.get(), point.size()), 0); +#else + EXPECT_FALSE(raw_public); +#endif + EXPECT_FALSE( + ncrypto::Ec::TryExportPublic(key, POINT_CONVERSION_COMPRESSED)); + auto public_ec = ncrypto::ECKeyPointer::NewByCurveName(nid); + ASSERT_TRUE(public_ec); + ASSERT_TRUE(public_ec.setPublicKeyRaw(x, y)); + auto public_key = EVPKeyPointer::New(); + ASSERT_TRUE(public_key); + ASSERT_TRUE(public_key.set(public_ec)); + EXPECT_TRUE( + ncrypto::Ec::GetKeyComponents(public_key, &x, &y, nullptr, °ree)); + EXPECT_FALSE( + ncrypto::Ec::GetKeyComponents(public_key, &x, &y, &priv, °ree)); + EXPECT_FALSE(ncrypto::Ec::ExportPrivate(public_key)); + } +} + +TEST(NodeCrypto, ResolveKeyAlgorithm) { + ncrypto::ClearErrorOnReturn clear_errors; + EXPECT_EQ(EVPKeyPointer().getAlgorithm(), nullptr); + auto key = EVPKeyPointer::New(); + ASSERT_TRUE(key); + EXPECT_EQ(key.getAlgorithm(), nullptr); + + const unsigned char seed[64] = {}; + for (const auto* algorithm : {&KeyAlgorithm::ED25519, + &KeyAlgorithm::X25519, + &KeyAlgorithm::ML_DSA_44, + &KeyAlgorithm::ML_KEM_768}) { + if (!algorithm->isAvailable()) continue; + const ncrypto::Buffer input{ + seed, algorithm->seedSize() == 0 ? 32 : algorithm->seedSize()}; + key = algorithm->seedSize() == 0 + ? EVPKeyPointer::NewRawPrivate(*algorithm, input) + : EVPKeyPointer::NewRawSeed(*algorithm, input); + ASSERT_TRUE(key); + EXPECT_EQ(key.getAlgorithm(), algorithm); + CheckRawJwkImport(key); + if (algorithm->seedSize() != 0) { + auto exported = key.rawSeed(); + ASSERT_TRUE(exported); + ASSERT_EQ(exported.value.size(), input.len); + EXPECT_EQ(memcmp(exported.value.get(), input.data, input.len), 0); + } + EVPKeyPointer moved(std::move(key)); + EXPECT_EQ(key.getAlgorithm(), nullptr); + EXPECT_EQ(moved.getAlgorithm(), algorithm); + key.reset(moved.release()); + EXPECT_EQ(moved.getAlgorithm(), nullptr); + EXPECT_EQ(key.getAlgorithm(), algorithm); + key.reset(); + EXPECT_EQ(key.getAlgorithm(), nullptr); + } +#if NCRYPTO_USE_OPENSSL3_PROVIDER + // Legacy keys must resolve even without provider-backed key material. + key = EVPKeyPointer::New(); + ASSERT_EQ(EVP_PKEY_set_type(key.get(), NID_rsaEncryption), 1); + EXPECT_EQ(key.getAlgorithm(), &KeyAlgorithm::RSA); +#endif +} + +TEST(NodeCrypto, PublicKeyTypeNames) { + EXPECT_EQ(EVPKeyPointer().getKeyTypeName(), nullptr); + EXPECT_EQ(EVPKeyPointer::New().getKeyTypeName(), nullptr); + EXPECT_STREQ(KeyAlgorithm::RSA.keyTypeName(), "rsa"); + EXPECT_STREQ(KeyAlgorithm::RSA_PSS.keyTypeName(), "rsa-pss"); + EXPECT_STREQ(KeyAlgorithm::ML_DSA_44.keyTypeName(), "ml-dsa-44"); + EXPECT_STREQ(KeyAlgorithm::SLH_DSA_SHAKE_256S.keyTypeName(), + "slh-dsa-shake-256s"); + EXPECT_EQ(KeyAlgorithm::SM2.keyTypeName(), nullptr); + const unsigned char seed[32] = {}; + auto key = + EVPKeyPointer::NewRawPrivate(KeyAlgorithm::ED25519, {seed, sizeof(seed)}); + ASSERT_TRUE(key); + EXPECT_STREQ(key.getKeyTypeName(), "ed25519"); +} + +#if NCRYPTO_USE_OPENSSL3_PROVIDER +TEST(NodeCrypto, LegacyKeyAlgorithmResolution) { + ncrypto::ClearErrorOnReturn clear_errors; + auto key = EVPKeyPointer::New(); + ASSERT_TRUE(key); + ASSERT_EQ(EVP_PKEY_set_type(key.get(), NID_rsassaPss), 1); + EXPECT_EQ(key.getAlgorithm(), &KeyAlgorithm::RSA_PSS); + EXPECT_STREQ(key.getKeyTypeName(), "rsa-pss"); +#ifndef OPENSSL_NO_SM2 + ASSERT_EQ(EVP_PKEY_set_type(key.get(), NID_sm2), 1); + EXPECT_TRUE(key.isA(KeyAlgorithm::SM2)); + EXPECT_FALSE(key.isA(KeyAlgorithm::EC)); + EXPECT_EQ(key.getAlgorithm(), &KeyAlgorithm::SM2); + EXPECT_EQ(key.getKeyTypeName(), nullptr); +#endif +} +#endif + +TEST(NodeCrypto, NamedKeysAndEcCurves) { + EXPECT_EQ(Ec::GetNamedKeyAlgorithm("ED25519"), &KeyAlgorithm::ED25519); + EXPECT_EQ(Ec::GetNamedKeyAlgorithm("X25519"), &KeyAlgorithm::X25519); + EXPECT_EQ(Ec::GetNamedKeyAlgorithm("P-256"), nullptr); + EXPECT_EQ(Ec::GetNamedKeyAlgorithm("prime256v1"), nullptr); + EXPECT_EQ(Ec::GetNamedKeyAlgorithm("unknown-curve"), nullptr); + EXPECT_EQ(Ec::GetCurveIdFromName("P-256"), + Ec::GetCurveIdFromName("prime256v1")); +} + +TEST(NodeCrypto, NamedRawKey) { + const unsigned char seed[32] = {}; + const ncrypto::Buffer input{seed, sizeof(seed)}; + auto key = EVPKeyPointer::NewRawPrivate(KeyAlgorithm::ED25519, input); + ASSERT_TRUE(key); + EXPECT_TRUE(key.isA(KeyAlgorithm::ED25519)); + EXPECT_FALSE(key.isA(KeyAlgorithm::X25519)); + ASSERT_EQ(key.getAlgorithm(), &KeyAlgorithm::ED25519); + EXPECT_TRUE(key.getAlgorithm()->isOneShot()); + EXPECT_TRUE(key.supportsRawPublic()); + EXPECT_TRUE(key.supportsRawPrivate()); + EXPECT_EQ(key.getAlgorithm()->seedSize(), 0); +#if NCRYPTO_USE_OPENSSL3_PROVIDER + // Provider aliases are recognized without comparing the primary type name. + EXPECT_TRUE(key.isA("1.3.101.112")); + auto ctx = ncrypto::EVPKeyCtxPointer::NewFromName("1.3.101.112"); + ASSERT_TRUE(ctx); + ASSERT_TRUE(ctx.initForKeygen()); + EVP_PKEY* generated = nullptr; + ASSERT_EQ(EVP_PKEY_keygen(ctx.get(), &generated), 1); + EXPECT_TRUE(EVPKeyPointer(generated).isA(KeyAlgorithm::ED25519)); +#endif + auto pub = key.rawPublicKey(); + ASSERT_TRUE(pub); + auto imported = EVPKeyPointer::NewRawPublic(KeyAlgorithm::ED25519, pub); + ASSERT_TRUE(imported); + EXPECT_TRUE(imported.isA(KeyAlgorithm::ED25519)); + auto exported = imported.rawPublicKey(); + ASSERT_EQ(pub.size(), exported.size()); + EXPECT_EQ(memcmp(pub.get(), exported.get(), pub.size()), 0); +} + +TEST(NodeCrypto, NamedRsaPssKey) { + ncrypto::ClearErrorOnReturn clear_errors; + EXPECT_FALSE(EVPKeyCtxPointer::NewFromName("unknown-key-algorithm")); + EXPECT_FALSE(EVPKeyCtxPointer::NewFromName(nullptr)); +#ifndef OPENSSL_IS_BORINGSSL + auto ctx = EVPKeyCtxPointer::NewFromAlgorithm(KeyAlgorithm::RSA_PSS); + ASSERT_TRUE(ctx); + ASSERT_TRUE(ctx.initForKeygen()); + ASSERT_TRUE(ctx.setRsaKeygenBits(2048)); + EVP_PKEY* raw = nullptr; + ASSERT_EQ(EVP_PKEY_keygen(ctx.get(), &raw), 1); + EVPKeyPointer key(raw); + EXPECT_TRUE(key.isA(KeyAlgorithm::RSA_PSS)); + EXPECT_FALSE(key.isA(KeyAlgorithm::RSA)); + EXPECT_FALSE(key.supportsContextString()); + EXPECT_TRUE(key.isRsaVariant()); + EXPECT_EQ(key.getDefaultSignPadding(), RSA_PKCS1_PSS_PADDING); + EXPECT_FALSE(key.supportsRawPublic()); + ASSERT_EQ(key.getAlgorithm(), &KeyAlgorithm::RSA_PSS); + EXPECT_FALSE(key.getAlgorithm()->isOneShot()); +#endif +} + +TEST(NodeCrypto, ProviderPqcKeyWithoutLegacyId) { + ncrypto::ClearErrorOnReturn clear_errors; + if (!KeyAlgorithm::ML_DSA_44.isAvailable() || + !KeyAlgorithm::ML_KEM_768.isAvailable()) + GTEST_SKIP(); + const unsigned char seed[64] = {}; + for (const auto* algorithm : + {&KeyAlgorithm::ML_DSA_44, &KeyAlgorithm::ML_KEM_768}) { + const ncrypto::Buffer input{seed, + algorithm->seedSize()}; + auto key = EVPKeyPointer::NewRawSeed(*algorithm, input); + ASSERT_TRUE(key); +#if NCRYPTO_USE_OPENSSL3_PROVIDER + EXPECT_EQ(EVP_PKEY_id(key.get()), -1); +#endif + EXPECT_TRUE(key.isA(*algorithm)); + ASSERT_EQ(key.getAlgorithm(), algorithm); + EXPECT_EQ(key.getAlgorithm()->seedSize(), input.len); + EXPECT_TRUE(key.supportsRawPublic()); + EXPECT_FALSE(key.supportsRawPrivate()); + auto exported_seed = key.rawSeed(); + ASSERT_TRUE(exported_seed); + ASSERT_EQ(exported_seed.value.size(), input.len); + EXPECT_EQ(memcmp(exported_seed.value.get(), seed, input.len), 0); + auto pub = key.rawPublicKey(); + ASSERT_TRUE(pub); + auto imported = EVPKeyPointer::NewRawPublic(*algorithm, pub); + ASSERT_TRUE(imported); + EXPECT_EQ(imported.getAlgorithm(), algorithm); + + auto private_jwk = key.exportRawJwk(/* include_private */ true); + ASSERT_TRUE(private_jwk); + EXPECT_EQ(private_jwk.value.algorithm, algorithm); + ASSERT_EQ(private_jwk.value.private_key.size(), input.len); + EXPECT_EQ(memcmp(private_jwk.value.private_key.get(), seed, input.len), 0); + ASSERT_EQ(private_jwk.value.public_key.size(), pub.size()); + EXPECT_EQ(memcmp(private_jwk.value.public_key.get(), pub.get(), pub.size()), + 0); + auto public_jwk = imported.exportRawJwk(/* include_private */ false); + ASSERT_TRUE(public_jwk); + EXPECT_EQ(public_jwk.value.algorithm, algorithm); + EXPECT_FALSE(public_jwk.value.private_key); + ASSERT_EQ(public_jwk.value.public_key.size(), pub.size()); + EXPECT_EQ(memcmp(public_jwk.value.public_key.get(), pub.get(), pub.size()), + 0); + EXPECT_EQ(imported.rawSeed().error, + EVPKeyPointer::RawExportError::MISSING_SEED); + EXPECT_EQ(imported.exportRawJwk(/* include_private */ true).error, + EVPKeyPointer::RawExportError::MISSING_SEED); + } +} + +TEST(NodeCrypto, PqcJwkRawPrivateExport) { + ncrypto::ClearErrorOnReturn clear_errors; + const auto& algorithm = KeyAlgorithm::SLH_DSA_SHA2_128S; + if (!algorithm.isAvailable()) GTEST_SKIP(); + auto ctx = EVPKeyCtxPointer::NewFromAlgorithm(algorithm); + ASSERT_TRUE(ctx); + ASSERT_EQ(EVP_PKEY_keygen_init(ctx.get()), 1); + EVP_PKEY* raw = nullptr; + ASSERT_EQ(EVP_PKEY_keygen(ctx.get(), &raw), 1); + EVPKeyPointer key(raw); + CheckRawJwkImport(key); + auto priv = key.rawPrivateKey(); + auto pub = key.rawPublicKey(); + ASSERT_TRUE(priv); + ASSERT_TRUE(pub); + auto jwk = key.exportRawJwk(/* include_private */ true); + ASSERT_TRUE(jwk); + EXPECT_EQ(jwk.value.algorithm, &algorithm); + ASSERT_EQ(jwk.value.private_key.size(), priv.size()); + EXPECT_EQ(memcmp(jwk.value.private_key.get(), priv.get(), priv.size()), 0); + ASSERT_EQ(jwk.value.public_key.size(), pub.size()); + EXPECT_EQ(memcmp(jwk.value.public_key.get(), pub.get(), pub.size()), 0); + EXPECT_EQ(key.rawSeed().error, + EVPKeyPointer::RawExportError::UNSUPPORTED_KEY_TYPE); +} + +#ifdef OPENSSL_IS_BORINGSSL +TEST(NodeCrypto, UnavailableBoringSSLKeyAlgorithms) { + ncrypto::ClearErrorOnReturn clear_errors; + for (const auto* algorithm : + {&KeyAlgorithm::SM2, &KeyAlgorithm::X448, &KeyAlgorithm::ED448}) { + EXPECT_FALSE(algorithm->isAvailable()); + EXPECT_FALSE(EVPKeyCtxPointer::NewFromAlgorithm(*algorithm)); + } +} +#endif diff --git a/test/parallel/test-crypto-jwk-raw-validation.js b/test/parallel/test-crypto-jwk-raw-validation.js new file mode 100644 index 000000000000..d2085b43529a --- /dev/null +++ b/test/parallel/test-crypto-jwk-raw-validation.js @@ -0,0 +1,43 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('node:assert'); +const { createPrivateKey } = require('node:crypto'); +const fixtures = require('../common/fixtures'); +const { hasOpenSSL, isBoringSSL } = require('../common/crypto'); + +const cases = [ + ['ed25519_private.pem', 'OKP', 'crv', 'x', 'd'], +]; +if (hasOpenSSL(3, 5) || isBoringSSL) { + cases.push(['ml_dsa_44_private_seed_only.pem', 'AKP', 'alg', 'pub', 'priv']); +} + +for (const [file, kty, name, pub, priv] of cases) { + const jwk = createPrivateKey(fixtures.readKey(file)).export({ format: 'jwk' }); + const invalid = { code: 'ERR_CRYPTO_INVALID_JWK', message: `Invalid JWK ${kty} key` }; + const invalidName = kty === 'AKP' ? { + code: 'ERR_CRYPTO_INVALID_JWK', message: 'Unsupported JWK AKP "alg"', + } : invalid; + const importKey = (key) => createPrivateKey({ format: 'jwk', key }); + + assert.throws(() => importKey({ ...jwk, [name]: 'unknown' }), invalidName); + assert.throws(() => importKey({ ...jwk, [name]: jwk[name].toLowerCase() }), invalidName); + assert.throws(() => importKey({ ...jwk, [pub]: undefined }), invalid); + assert.throws(() => importKey({ ...jwk, [priv]: 1 }), invalid); + assert.throws(() => importKey({ ...jwk, [name]: 'unknown', [pub]: undefined }), invalidName); + + // A recognized algorithm from the other schema remains invalid. + const otherName = kty === 'AKP' ? 'Ed25519' : 'ML-DSA-44'; + assert.throws(() => importKey({ ...jwk, [name]: otherName }), invalidName); + + for (const field of [name, pub, priv]) { + const error = new Error(`getter for ${field}`); + const key = { ...jwk }; + Object.defineProperty(key, field, { get() { throw error; } }); + assert.throws(() => importKey(key), (actual) => actual === error); + } +} diff --git a/test/parallel/test-crypto-raw-key-type-validation.js b/test/parallel/test-crypto-raw-key-type-validation.js new file mode 100644 index 000000000000..c4ed8f10f523 --- /dev/null +++ b/test/parallel/test-crypto-raw-key-type-validation.js @@ -0,0 +1,81 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('node:assert'); +const { createPrivateKey, createPublicKey } = require('node:crypto'); +const { hasOpenSSL, hasFIPS, isBoringSSL } = require('../common/crypto'); + +const key = Buffer.alloc(32); +const formats = ['raw-public', 'raw-private', 'raw-seed']; + +// Recognized classical types reject raw encodings with a format error. +for (const asymmetricKeyType of ['rsa', 'rsa-pss', 'dsa', 'dh']) { + for (const format of formats) { + assert.throws(() => createPublicKey({ key, format, asymmetricKeyType }), { + code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS', + }); + } +} + +// Only exact public names are accepted, excluding aliases and unexposed types. +for (const asymmetricKeyType of [ + 'RSA', 'RSA-PSS', 'DSA', 'DH', 'EC', 'sm2', 'SM2', 'unknown', + 'Ed25519', 'ED25519', 'Ed448', 'ED448', 'X25519', 'X448', + 'ML-DSA-44', 'Ml-Dsa-65', 'ML-DSA-87', + 'ML-KEM-512', 'Ml-Kem-768', 'ML-KEM-1024', 'SLH-DSA-SHA2-128s', + 'rsaEncryption', 'id-ecPublicKey', '1.3.101.112', '1.3.101.110', + 'ed25519 ', ' ml-dsa-44', + 'rsa\0suffix', 'ec\0suffix', 'ed25519\0suffix', 'ml-dsa-44\0suffix', +]) { + for (const format of formats) { + assert.throws(() => createPublicKey({ + key, format, asymmetricKeyType, namedCurve: 'P-256', + }), { + code: 'ERR_INVALID_ARG_VALUE', + message: `Invalid asymmetricKeyType: ${asymmetricKeyType.split('\0')[0]}`, + }); + } +} + +assert.throws(() => createPublicKey({ + key, format: 'raw-seed', asymmetricKeyType: 'ec', namedCurve: 'P-256', +}), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + +// Recognized OKP and PQC names retain their raw-format restrictions. +for (const asymmetricKeyType of ['ed25519', 'x25519']) { + const options = { key, format: 'raw-private', asymmetricKeyType }; + if (asymmetricKeyType === 'x25519' && hasFIPS(3, 5)) { + assert.throws(() => createPrivateKey(options), { + code: 'ERR_INVALID_ARG_VALUE', message: 'Invalid key data', + }); + } else { + const imported = createPrivateKey(options); + assert.strictEqual(imported.asymmetricKeyType, asymmetricKeyType); + } + assert.throws(() => createPrivateKey({ + key, format: 'raw-seed', asymmetricKeyType, + }), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); +} + +{ + const asymmetricKeyType = 'ml-dsa-44'; + if (hasOpenSSL(3, 5) || isBoringSSL) { + const imported = createPrivateKey({ + key, format: 'raw-seed', asymmetricKeyType, + }); + assert.strictEqual(imported.asymmetricKeyType, 'ml-dsa-44'); + assert.throws(() => createPrivateKey({ + key, format: 'raw-private', asymmetricKeyType, + }), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + } else { + for (const format of formats) { + assert.throws(() => createPublicKey({ key, format, asymmetricKeyType }), { + code: 'ERR_INVALID_ARG_VALUE', + message: 'Unsupported key type', + }); + } + } +} diff --git a/typings/internalBinding/crypto.d.ts b/typings/internalBinding/crypto.d.ts index e45a45ea7193..fd76488c8883 100644 --- a/typings/internalBinding/crypto.d.ts +++ b/typings/internalBinding/crypto.d.ts @@ -382,19 +382,19 @@ declare namespace InternalCryptoBinding { ): CryptoJobWebCrypto>; } - interface NidKeyPairGenJobConstructor { + interface NamedKeyPairGenJobConstructor { new< M extends CryptoJobRegularMode, PublicFormat extends PublicKeyFormat = undefined, PrivateFormat extends PrivateKeyFormat = undefined, >( mode: M, - nid: number, + name: string, ...encoding: KeyPairEncodingArgs ): CryptoJobForMode>; new( mode: CryptoJobWebCryptoMode, - nid: number, + name: string, algorithm: object, publicUsagesMask: number, privateUsagesMask: number, @@ -825,7 +825,8 @@ export interface CryptoBinding { KEMEncapsulateJob?: InternalCryptoBinding.KEMEncapsulateJobConstructor; KangarooTwelveJob: InternalCryptoBinding.KangarooTwelveJobConstructor; KmacJob: InternalCryptoBinding.KmacJobConstructor; - NidKeyPairGenJob: InternalCryptoBinding.NidKeyPairGenJobConstructor; + getPqcKeyTypes(): string[]; + NamedKeyPairGenJob: InternalCryptoBinding.NamedKeyPairGenJobConstructor; PBKDF2Job: InternalCryptoBinding.PBKDF2JobConstructor; RandomBytesJob: InternalCryptoBinding.RandomBytesJobConstructor; RandomPrimeJob: InternalCryptoBinding.RandomPrimeJobConstructor; @@ -877,28 +878,6 @@ export interface CryptoBinding { Sign: new () => InternalCryptoBinding.SignHandle; Verify: new () => InternalCryptoBinding.VerifyHandle; - EVP_PKEY_ED25519: number; - EVP_PKEY_ED448: number; - EVP_PKEY_ML_DSA_44: number; - EVP_PKEY_ML_DSA_65: number; - EVP_PKEY_ML_DSA_87: number; - EVP_PKEY_ML_KEM_512: number; - EVP_PKEY_ML_KEM_768: number; - EVP_PKEY_ML_KEM_1024: number; - EVP_PKEY_SLH_DSA_SHA2_128F: number; - EVP_PKEY_SLH_DSA_SHA2_128S: number; - EVP_PKEY_SLH_DSA_SHA2_192F: number; - EVP_PKEY_SLH_DSA_SHA2_192S: number; - EVP_PKEY_SLH_DSA_SHA2_256F: number; - EVP_PKEY_SLH_DSA_SHA2_256S: number; - EVP_PKEY_SLH_DSA_SHAKE_128F: number; - EVP_PKEY_SLH_DSA_SHAKE_128S: number; - EVP_PKEY_SLH_DSA_SHAKE_192F: number; - EVP_PKEY_SLH_DSA_SHAKE_192S: number; - EVP_PKEY_SLH_DSA_SHAKE_256F: number; - EVP_PKEY_SLH_DSA_SHAKE_256S: number; - EVP_PKEY_X25519: number; - EVP_PKEY_X448: number; OPENSSL_EC_EXPLICIT_CURVE: number; OPENSSL_EC_NAMED_CURVE: number; RSA_PKCS1_PSS_PADDING: number;