diff --git a/benchmark/crypto/create-keyobject.js b/benchmark/crypto/create-keyobject.js index 30f8213175df69..7cd6db2d567ad6 100644 --- a/benchmark/crypto/create-keyobject.js +++ b/benchmark/crypto/create-keyobject.js @@ -26,6 +26,8 @@ const keyFixtures = { if (hasOpenSSL(3, 5)) { keyFixtures['ml-dsa-44'] = readKeyPair('ml_dsa_44_public', 'ml_dsa_44_private'); +} else if (process.features.openssl_is_boringssl) { + keyFixtures['ml-dsa-44'] = readKeyPair('ml_dsa_44_public', 'ml_dsa_44_private_seed_only'); } const bench = common.createBenchmark(main, { diff --git a/benchmark/crypto/kem.js b/benchmark/crypto/kem.js index ffdcac6d7fcb0d..a544fc2124afe9 100644 --- a/benchmark/crypto/kem.js +++ b/benchmark/crypto/kem.js @@ -24,6 +24,9 @@ if (hasOpenSSL(3, 5)) { keyFixtures['ml-kem-512'] = readKeyPair('ml_kem_512_public', 'ml_kem_512_private'); keyFixtures['ml-kem-768'] = readKeyPair('ml_kem_768_public', 'ml_kem_768_private'); keyFixtures['ml-kem-1024'] = readKeyPair('ml_kem_1024_public', 'ml_kem_1024_private'); +} else if (process.features.openssl_is_boringssl) { + keyFixtures['ml-kem-768'] = readKeyPair('ml_kem_768_public', 'ml_kem_768_private_seed_only'); + keyFixtures['ml-kem-1024'] = readKeyPair('ml_kem_1024_public', 'ml_kem_1024_private_seed_only'); } if (hasOpenSSL(3, 2)) { keyFixtures['p-256'] = readKeyPair('ec_p256_public', 'ec_p256_private'); diff --git a/benchmark/crypto/oneshot-sign.js b/benchmark/crypto/oneshot-sign.js index d0abc7b5412e60..72e3726d9a5349 100644 --- a/benchmark/crypto/oneshot-sign.js +++ b/benchmark/crypto/oneshot-sign.js @@ -19,6 +19,8 @@ const keyFixtures = { if (hasOpenSSL(3, 5)) { keyFixtures['ml-dsa-44'] = readKey('ml_dsa_44_private'); +} else if (process.features.openssl_is_boringssl) { + keyFixtures['ml-dsa-44'] = readKey('ml_dsa_44_private_seed_only'); } const data = crypto.randomBytes(256); diff --git a/benchmark/crypto/oneshot-verify.js b/benchmark/crypto/oneshot-verify.js index c6a24f52126eb2..8b397b02dbf285 100644 --- a/benchmark/crypto/oneshot-verify.js +++ b/benchmark/crypto/oneshot-verify.js @@ -26,6 +26,8 @@ const keyFixtures = { if (hasOpenSSL(3, 5)) { keyFixtures['ml-dsa-44'] = readKeyPair('ml_dsa_44_public', 'ml_dsa_44_private'); +} else if (process.features.openssl_is_boringssl) { + keyFixtures['ml-dsa-44'] = readKeyPair('ml_dsa_44_public', 'ml_dsa_44_private_seed_only'); } const data = crypto.randomBytes(256); diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index b7a0c96ee2ea60..3232df9a3d15be 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -7,6 +7,11 @@ #include #include #include +#ifdef OPENSSL_IS_BORINGSSL +#include +#include +#include +#endif #include #include #include @@ -15,7 +20,7 @@ #include #include #include -#if OPENSSL_VERSION_NUMBER >= 0x30200000L +#if OPENSSL_WITH_ARGON2 #include #endif #endif @@ -29,9 +34,13 @@ 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-512", EVP_PKEY_ML_KEM_512}, {"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}, @@ -44,6 +53,7 @@ constexpr static PQCMapping pqc_mappings[] = { {"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 @@ -67,6 +77,28 @@ using NetscapeSPKIPointer = DeleteFnPtr; static constexpr int kX509NameFlagsRFC2253WithinUtf8JSON = XN_FLAG_RFC2253 & ~ASN1_STRFLGS_ESC_MSB & ~ASN1_STRFLGS_ESC_CTRL; + +#if NCRYPTO_USE_BORINGSSL_EVP_DO_ALL_FALLBACK +struct BoringSSLCipher { + const EVP_CIPHER* (*get)(); + const char* name; +}; + +constexpr BoringSSLCipher kBoringSSLCiphers[] = { + {EVP_aes_128_cbc, "aes-128-cbc"}, {EVP_aes_128_ctr, "aes-128-ctr"}, + {EVP_aes_128_ecb, "aes-128-ecb"}, {EVP_aes_128_gcm, "aes-128-gcm"}, + {EVP_aes_128_ofb, "aes-128-ofb"}, {EVP_aes_192_cbc, "aes-192-cbc"}, + {EVP_aes_192_ctr, "aes-192-ctr"}, {EVP_aes_192_ecb, "aes-192-ecb"}, + {EVP_aes_192_gcm, "aes-192-gcm"}, {EVP_aes_192_ofb, "aes-192-ofb"}, + {EVP_aes_256_cbc, "aes-256-cbc"}, {EVP_aes_256_ctr, "aes-256-ctr"}, + {EVP_aes_256_ecb, "aes-256-ecb"}, {EVP_aes_256_gcm, "aes-256-gcm"}, + {EVP_aes_256_ofb, "aes-256-ofb"}, {EVP_des_cbc, "des-cbc"}, + {EVP_des_ecb, "des-ecb"}, {EVP_des_ede, "des-ede"}, + {EVP_des_ede3_cbc, "des-ede3-cbc"}, {EVP_des_ede_cbc, "des-ede-cbc"}, + {EVP_rc2_cbc, "rc2-cbc"}, {EVP_rc4, "rc4"}, +}; + +#endif } // namespace // ============================================================================ @@ -1928,8 +1960,7 @@ DataPointer pbkdf2(const Digest& md, return {}; } -#if OPENSSL_VERSION_NUMBER >= 0x30200000L -#ifndef OPENSSL_NO_ARGON2 +#if OPENSSL_WITH_ARGON2 DataPointer argon2(const Buffer& pass, const Buffer& salt, uint32_t lanes, @@ -2022,7 +2053,6 @@ DataPointer argon2(const Buffer& pass, return {}; } #endif -#endif // ============================================================================ @@ -2070,27 +2100,99 @@ EVPKeyPointer EVPKeyPointer::NewRawPrivate( } #if OPENSSL_WITH_PQC -EVPKeyPointer EVPKeyPointer::NewRawSeed( - int id, const Buffer& data) { - if (id == 0) return {}; +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 + return EVPKeyPointer( + EVP_PKEY_from_private_seed(GetPqcSeedAlg(id), data.data, data.len)); +#else OSSL_PARAM params[] = { - OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_ML_DSA_SEED, + OSSL_PARAM_construct_octet_string(GetPqcSeedParamName(id), const_cast(data.data), data.len), OSSL_PARAM_END}; - EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(id, nullptr); - if (ctx == nullptr) return {}; + auto ctx = EVPKeyCtxPointer::NewFromID(id); + if (!ctx) return {}; EVP_PKEY* pkey = nullptr; - if (ctx == nullptr || EVP_PKEY_fromdata_init(ctx) <= 0 || - EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params) <= 0) { - EVP_PKEY_CTX_free(ctx); + 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; +#endif +} +} // namespace + +EVPKeyPointer EVPKeyPointer::NewRawSeed( + int id, const Buffer& data) { + return NewPqcKeyFromSeed(id, data); } #endif @@ -2140,7 +2242,7 @@ EVP_PKEY* EVPKeyPointer::release() { int EVPKeyPointer::id(const EVP_PKEY* key) { if (key == nullptr) return 0; int type = EVP_PKEY_id(key); -#if OPENSSL_WITH_PQC +#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` @@ -2218,34 +2320,11 @@ DataPointer EVPKeyPointer::rawPublicKey() const { DataPointer EVPKeyPointer::rawSeed() const { if (!pkey_) return {}; - // Determine seed length and parameter name based on key type - size_t seed_len; - const char* param_name; - - switch (id()) { - case EVP_PKEY_ML_DSA_44: - case EVP_PKEY_ML_DSA_65: - case EVP_PKEY_ML_DSA_87: - seed_len = 32; // ML-DSA uses 32-byte seeds - param_name = OSSL_PKEY_PARAM_ML_DSA_SEED; - break; - case EVP_PKEY_ML_KEM_512: - case EVP_PKEY_ML_KEM_768: - case EVP_PKEY_ML_KEM_1024: - seed_len = 64; // ML-KEM uses 64-byte seeds - param_name = OSSL_PKEY_PARAM_ML_KEM_SEED; - break; - default: - unreachable(); - } + const size_t seed_len = GetPqcSeedSize(id()); if (auto data = DataPointer::Alloc(seed_len)) { const Buffer buf = data; - size_t len = data.size(); - - if (EVP_PKEY_get_octet_string_param( - get(), param_name, buf.data, len, &seed_len) != 1) - return {}; + if (!GetPqcSeed(get(), id(), buf)) return {}; return data; } return {}; @@ -2287,6 +2366,7 @@ EVPKeyPointer::operator const EC_KEY*() const { } namespace { + EVPKeyPointer::ParseKeyResult TryParsePublicKeyInner(const BIOPointer& bp, const char* name, auto&& parse) { @@ -2714,6 +2794,7 @@ bool EVPKeyPointer::isOneShotVariant() const { 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: @@ -2726,6 +2807,7 @@ bool EVPKeyPointer::isOneShotVariant() const { 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: @@ -4209,6 +4291,12 @@ void Cipher::ForEach(Cipher::CipherNameCallback callback) { CipherCallbackContext context; context.cb = std::move(callback); +#if NCRYPTO_USE_BORINGSSL_EVP_DO_ALL_FALLBACK + for (const auto& cipher : kBoringSSLCiphers) { + static_cast(cipher.get); + context.cb(cipher.name); + } +#else EVP_CIPHER_do_all_sorted( #if OPENSSL_VERSION_MAJOR >= 3 array_push_back, #endif &context); +#endif } // ============================================================================ @@ -4369,7 +4458,17 @@ std::optional EVPMDCtxPointer::signInitWithContext( const EVPKeyPointer& key, const Digest& digest, const Buffer& context_string) { -#ifdef OSSL_SIGNATURE_PARAM_CONTEXT_STRING +#ifdef OPENSSL_IS_BORINGSSL + EVP_PKEY_CTX* ctx = nullptr; + if (!EVP_DigestSignInit(ctx_.get(), &ctx, digest, nullptr, key.get())) { + return std::nullopt; + } + if (EVP_PKEY_CTX_set1_signature_context_string( + ctx, context_string.data, context_string.len) <= 0) { + return std::nullopt; + } + return ctx; +#elif defined(OSSL_SIGNATURE_PARAM_CONTEXT_STRING) EVP_PKEY_CTX* ctx = nullptr; #ifdef OSSL_SIGNATURE_PARAM_INSTANCE @@ -4414,7 +4513,17 @@ std::optional EVPMDCtxPointer::verifyInitWithContext( const EVPKeyPointer& key, const Digest& digest, const Buffer& context_string) { -#ifdef OSSL_SIGNATURE_PARAM_CONTEXT_STRING +#ifdef OPENSSL_IS_BORINGSSL + EVP_PKEY_CTX* ctx = nullptr; + if (!EVP_DigestVerifyInit(ctx_.get(), &ctx, digest, nullptr, key.get())) { + return std::nullopt; + } + if (EVP_PKEY_CTX_set1_signature_context_string( + ctx, context_string.data, context_string.len) <= 0) { + return std::nullopt; + } + return ctx; +#elif defined(OSSL_SIGNATURE_PARAM_CONTEXT_STRING) EVP_PKEY_CTX* ctx = nullptr; #ifdef OSSL_SIGNATURE_PARAM_INSTANCE @@ -4580,7 +4689,7 @@ HMACCtxPointer HMACCtxPointer::New() { return HMACCtxPointer(HMAC_CTX_new()); } -#if OPENSSL_VERSION_MAJOR >= 3 +#if OPENSSL_WITH_KMAC EVPMacPointer::EVPMacPointer(EVP_MAC* mac) : mac_(mac) {} EVPMacPointer::EVPMacPointer(EVPMacPointer&& other) noexcept @@ -4668,7 +4777,7 @@ EVPMacCtxPointer EVPMacCtxPointer::New(EVP_MAC* mac) { if (!mac) return EVPMacCtxPointer(); return EVPMacCtxPointer(EVP_MAC_CTX_new(mac)); } -#endif // OPENSSL_VERSION_MAJOR >= 3 +#endif // OPENSSL_WITH_KMAC DataPointer hashDigest(const Buffer& buf, const EVP_MD* md) { @@ -4815,8 +4924,8 @@ const Digest Digest::FromName(const char* name) { // ============================================================================ // KEM Implementation -#if OPENSSL_VERSION_MAJOR >= 3 -#if !OPENSSL_VERSION_PREREQ(3, 5) +#if OPENSSL_WITH_KEM +#if OPENSSL_WITH_KEM_OPERATION_PARAM bool KEM::SetOperationParameter(EVP_PKEY_CTX* ctx, const EVPKeyPointer& key) { const char* operation = nullptr; @@ -4824,7 +4933,7 @@ bool KEM::SetOperationParameter(EVP_PKEY_CTX* ctx, const EVPKeyPointer& key) { case EVP_PKEY_RSA: operation = OSSL_KEM_PARAM_OPERATION_RSASVE; break; -#if OPENSSL_VERSION_PREREQ(3, 2) +#if OPENSSL_WITH_OPENSSL_DHKEM case EVP_PKEY_EC: case EVP_PKEY_X25519: case EVP_PKEY_X448: @@ -4861,7 +4970,7 @@ std::optional KEM::Encapsulate( return std::nullopt; } -#if !OPENSSL_VERSION_PREREQ(3, 5) +#if OPENSSL_WITH_KEM_OPERATION_PARAM if (!SetOperationParameter(ctx.get(), public_key)) { return std::nullopt; } @@ -4902,7 +5011,7 @@ DataPointer KEM::Decapsulate(const EVPKeyPointer& private_key, return {}; } -#if !OPENSSL_VERSION_PREREQ(3, 5) +#if OPENSSL_WITH_KEM_OPERATION_PARAM if (!SetOperationParameter(ctx.get(), private_key)) { return {}; } @@ -4932,6 +5041,6 @@ DataPointer KEM::Decapsulate(const EVPKeyPointer& private_key, return shared_key; } -#endif // OPENSSL_VERSION_MAJOR >= 3 +#endif // OPENSSL_WITH_KEM } // namespace ncrypto diff --git a/deps/ncrypto/ncrypto.gyp b/deps/ncrypto/ncrypto.gyp index cf9b7c6cdb6d2c..1747f3ea0149b9 100644 --- a/deps/ncrypto/ncrypto.gyp +++ b/deps/ncrypto/ncrypto.gyp @@ -1,5 +1,6 @@ { 'variables': { + 'ncrypto_bssl_libdecrepit_missing%': 1, 'ncrypto_sources': [ 'engine.cc', 'ncrypto.cc', @@ -11,8 +12,14 @@ 'target_name': 'ncrypto', 'type': 'static_library', 'include_dirs': ['.'], + 'defines': [ + 'NCRYPTO_BSSL_LIBDECREPIT_MISSING=<(ncrypto_bssl_libdecrepit_missing)', + ], 'direct_dependent_settings': { 'include_dirs': ['.'], + 'defines': [ + 'NCRYPTO_BSSL_LIBDECREPIT_MISSING=<(ncrypto_bssl_libdecrepit_missing)', + ], }, 'sources': [ '<@(ncrypto_sources)' ], 'conditions': [ diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index 1f116169f57a27..b27e2e76c3dcfc 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -22,22 +22,103 @@ #ifndef OPENSSL_NO_ENGINE #include #endif // !OPENSSL_NO_ENGINE + +#ifndef OPENSSL_VERSION_PREREQ +#define OPENSSL_VERSION_PREREQ(maj, min) \ + (OPENSSL_VERSION_NUMBER >= (((maj) << 28) | ((min) << 20))) +#endif + +// BoringSSL declares the EVP_*_do_all* APIs, but their implementation may +// live in libdecrepit. This matches standalone ncrypto's build flag. +#ifndef NCRYPTO_BSSL_LIBDECREPIT_MISSING +#define NCRYPTO_BSSL_LIBDECREPIT_MISSING 0 +#endif + +#if defined(OPENSSL_IS_BORINGSSL) && NCRYPTO_BSSL_LIBDECREPIT_MISSING +#define NCRYPTO_USE_BORINGSSL_EVP_DO_ALL_FALLBACK 1 +#else +#define NCRYPTO_USE_BORINGSSL_EVP_DO_ALL_FALLBACK 0 +#endif + // The FIPS-related functions are only available // when the OpenSSL itself was compiled with FIPS support. -#if defined(OPENSSL_FIPS) && OPENSSL_VERSION_MAJOR < 3 +#if defined(OPENSSL_FIPS) && !OPENSSL_VERSION_PREREQ(3, 0) #include #endif // OPENSSL_FIPS -// Define OPENSSL_WITH_PQC for post-quantum cryptography support -#if OPENSSL_VERSION_NUMBER >= 0x30500000L -#define OPENSSL_WITH_PQC 1 +#if OPENSSL_VERSION_PREREQ(3, 0) +#define OPENSSL_WITH_AES_OCB 1 +#else +#define OPENSSL_WITH_AES_OCB 0 +#endif + +#if !defined(OPENSSL_NO_ARGON2) && OPENSSL_VERSION_PREREQ(3, 2) +#define OPENSSL_WITH_ARGON2 1 +#else +#define OPENSSL_WITH_ARGON2 0 +#endif + +#if OPENSSL_VERSION_PREREQ(3, 0) || defined(OPENSSL_IS_BORINGSSL) +#define OPENSSL_WITH_KEM 1 +#else +#define OPENSSL_WITH_KEM 0 +#endif + +#if OPENSSL_VERSION_PREREQ(3, 0) +#define OPENSSL_WITH_KMAC 1 +#else +#define OPENSSL_WITH_KMAC 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 #include +#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_MAJOR >= 3 +#if OPENSSL_VERSION_PREREQ(3, 0) #define OSSL3_CONST const #else #define OSSL3_CONST @@ -1474,7 +1555,7 @@ class HMACCtxPointer final { DeleteFnPtr ctx_; }; -#if OPENSSL_VERSION_MAJOR >= 3 +#if OPENSSL_WITH_KMAC class EVPMacPointer final { public: EVPMacPointer() = default; @@ -1522,7 +1603,7 @@ class EVPMacCtxPointer final { private: DeleteFnPtr ctx_; }; -#endif // OPENSSL_VERSION_MAJOR >= 3 +#endif // OPENSSL_WITH_KMAC #ifndef OPENSSL_NO_ENGINE class EnginePointer final { @@ -1635,8 +1716,7 @@ DataPointer pbkdf2(const Digest& md, uint32_t iterations, size_t length); -#if OPENSSL_VERSION_NUMBER >= 0x30200000L -#ifndef OPENSSL_NO_ARGON2 +#if OPENSSL_WITH_ARGON2 enum class Argon2Type { ARGON2D, ARGON2I, ARGON2ID }; DataPointer argon2(const Buffer& pass, @@ -1650,11 +1730,10 @@ DataPointer argon2(const Buffer& pass, const Buffer& ad, Argon2Type type); #endif -#endif // ============================================================================ // KEM (Key Encapsulation Mechanism) -#if OPENSSL_VERSION_MAJOR >= 3 +#if OPENSSL_WITH_KEM class KEM final { public: @@ -1678,13 +1757,13 @@ class KEM final { const Buffer& ciphertext); private: -#if !OPENSSL_VERSION_PREREQ(3, 5) +#if OPENSSL_WITH_KEM_OPERATION_PARAM static bool SetOperationParameter(EVP_PKEY_CTX* ctx, const EVPKeyPointer& key); #endif }; -#endif // OPENSSL_VERSION_MAJOR >= 3 +#endif // OPENSSL_WITH_KEM // ============================================================================ // Version metadata diff --git a/doc/api/errors.md b/doc/api/errors.md index 540e8122b9f876..a5988802f72ed5 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -3106,6 +3106,13 @@ Failed to set PSK identity hint. Hint may be too long. An attempt was made to renegotiate TLS on a socket instance with renegotiation disabled. + + +### `ERR_TLS_RENEGOTIATION_UNSUPPORTED` + +An attempt was made to renegotiate TLS, but the TLS implementation does not +support caller-initiated renegotiation. + ### `ERR_TLS_REQUIRED_SERVER_NAME` diff --git a/lib/internal/crypto/keys.js b/lib/internal/crypto/keys.js index 03ace95df21dca..f9fd873d371e71 100644 --- a/lib/internal/crypto/keys.js +++ b/lib/internal/crypto/keys.js @@ -666,6 +666,10 @@ function prepareAsymmetricKey(key, ctx, name = 'key') { return { data, format: kKeyFormatJWK }; } else if (format === 'raw-public' || format === 'raw-private' || format === 'raw-seed') { + if ((ctx === kConsumePrivate || ctx === kCreatePrivate) && + format === 'raw-public') { + throw new ERR_INVALID_ARG_VALUE(`${name}.format`, format); + } if (!isArrayBufferView(data) && !isAnyArrayBuffer(data)) { throw new ERR_INVALID_ARG_TYPE( `${name}.key`, diff --git a/lib/internal/crypto/ml_kem.js b/lib/internal/crypto/ml_kem.js index 67f5ddd0ff2499..abb156ee07262d 100644 --- a/lib/internal/crypto/ml_kem.js +++ b/lib/internal/crypto/ml_kem.js @@ -13,8 +13,8 @@ const { KEMDecapsulateJob, KEMEncapsulateJob, kKeyFormatDER, - kKeyFormatRawPrivate, kKeyFormatRawPublic, + kKeyFormatRawSeed, kWebCryptoKeyFormatPKCS8, kWebCryptoKeyFormatRaw, kWebCryptoKeyFormatSPKI, @@ -178,7 +178,7 @@ function mlKemImportKey( case 'raw-seed': { const isPublic = format === 'raw-public'; verifyAcceptableMlKemKeyUse(name, isPublic, usagesSet); - handle = importRawKey(isPublic, keyData, isPublic ? kKeyFormatRawPublic : kKeyFormatRawPrivate, name); + handle = importRawKey(isPublic, keyData, isPublic ? kKeyFormatRawPublic : kKeyFormatRawSeed, name); break; } case 'jwk': { diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js index b743f3f93a149e..6a32215c71bb52 100644 --- a/lib/internal/crypto/util.js +++ b/lib/internal/crypto/util.js @@ -395,12 +395,11 @@ const kAlgorithmDefinitions = { // Conditionally supported algorithms const conditionalAlgorithms = { - 'AES-KW': !process.features.openssl_is_boringssl, 'AES-OCB': !!hasAesOcbMode, 'Argon2d': !!Argon2Job, 'Argon2i': !!Argon2Job, 'Argon2id': !!Argon2Job, - 'ChaCha20-Poly1305': !process.features.openssl_is_boringssl || + 'ChaCha20-Poly1305': process.features.openssl_is_boringssl || ArrayPrototypeIncludes(getCiphers(), 'chacha20-poly1305'), 'cSHAKE128': !process.features.openssl_is_boringssl || ArrayPrototypeIncludes(getHashes(), 'shake128'), diff --git a/lib/internal/crypto/webidl.js b/lib/internal/crypto/webidl.js index a87edcb411d08e..f4ae6a8191720a 100644 --- a/lib/internal/crypto/webidl.js +++ b/lib/internal/crypto/webidl.js @@ -569,14 +569,18 @@ converters.ContextParams = createDictionaryConverter( key: 'context', converter: converters.BufferSource, validator(V, dict) { - let { 0: major, 1: minor } = process.versions.openssl.split('.'); - major = NumberParseInt(major, 10); - minor = NumberParseInt(minor, 10); - if (major > 3 || (major === 3 && minor >= 2)) { + if (process.features.openssl_is_boringssl) { this.validator = undefined; } else { - this.validator = validateZeroLength('ContextParams.context'); - this.validator(V, dict); + let { 0: major, 1: minor } = process.versions.openssl.split('.'); + major = NumberParseInt(major, 10); + minor = NumberParseInt(minor, 10); + if (major > 3 || (major === 3 && minor >= 2)) { + this.validator = undefined; + } else { + this.validator = validateZeroLength('ContextParams.context'); + this.validator(V, dict); + } } }, }, diff --git a/lib/internal/errors.js b/lib/internal/errors.js index c40eed86bca834..998e618ced5b42 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1840,6 +1840,8 @@ E('ERR_TLS_PROTOCOL_VERSION_CONFLICT', 'TLS protocol version %j conflicts with secureProtocol %j', TypeError); E('ERR_TLS_RENEGOTIATION_DISABLED', 'TLS session renegotiation disabled for this socket', Error); +E('ERR_TLS_RENEGOTIATION_UNSUPPORTED', + 'TLS session renegotiation is unsupported by this TLS implementation', Error); // This should probably be a `TypeError`. E('ERR_TLS_REQUIRED_SERVER_NAME', diff --git a/lib/internal/tls/wrap.js b/lib/internal/tls/wrap.js index d89e501432968a..05ce6955ed9217 100644 --- a/lib/internal/tls/wrap.js +++ b/lib/internal/tls/wrap.js @@ -72,6 +72,7 @@ const { ERR_TLS_INVALID_CONTEXT, ERR_TLS_INVALID_STATE, ERR_TLS_RENEGOTIATION_DISABLED, + ERR_TLS_RENEGOTIATION_UNSUPPORTED, ERR_TLS_REQUIRED_SERVER_NAME, ERR_TLS_SESSION_ATTACK, ERR_TLS_SNI_FROM_SERVER, @@ -1014,8 +1015,13 @@ TLSSocket.prototype.renegotiate = function(options, callback) { try { this._handle.renegotiate(); } catch (err) { + const isBoringSSLRenegotiationUnsupported = + process.features.openssl_is_boringssl && + err?.code === 'ERR_SSL_FUNCTION_SHOULD_NOT_HAVE_BEEN_CALLED'; + const error = isBoringSSLRenegotiationUnsupported ? + new ERR_TLS_RENEGOTIATION_UNSUPPORTED() : err; if (callback) { - process.nextTick(callback, err); + process.nextTick(callback, error); } return false; } diff --git a/src/crypto/crypto_aes.cc b/src/crypto/crypto_aes.cc index fa619696ffd5b2..815c972837049a 100644 --- a/src/crypto/crypto_aes.cc +++ b/src/crypto/crypto_aes.cc @@ -181,6 +181,68 @@ WebCryptoCipherStatus AES_Cipher(Environment* env, return WebCryptoCipherStatus::OK; } +#ifdef OPENSSL_IS_BORINGSSL +// AES Key Wrap using BoringSSL's low-level AES_wrap_key / AES_unwrap_key. +// BoringSSL does not expose EVP_aes_*_wrap via the +// EVP_CIPHER registry, so the EVP-based AES_Cipher path is unusable for +// AES-KW. This matches Chromium's WebCrypto AES-KW implementation. +WebCryptoCipherStatus AES_KW_Cipher(Environment* env, + const KeyObjectData& key_data, + WebCryptoCipherMode cipher_mode, + const AESCipherConfig& params, + const ByteSource& in, + ByteSource* out) { + CHECK_EQ(key_data.GetKeyType(), kKeyTypeSecret); + + const unsigned key_bits = + static_cast(key_data.GetSymmetricKeySize()) * 8; + const auto key_bytes = + reinterpret_cast(key_data.GetSymmetricKey()); + const bool encrypt = cipher_mode == kWebCryptoCipherEncrypt; + + AES_KEY aes_key; + if (encrypt) { + // Input must be a multiple of 8 bytes and at least 16 bytes. + if (in.size() < 16 || in.size() % 8 != 0) { + return WebCryptoCipherStatus::FAILED; + } + if (AES_set_encrypt_key(key_bytes, key_bits, &aes_key) != 0) { + return WebCryptoCipherStatus::FAILED; + } + auto buf = DataPointer::Alloc(in.size() + 8); + int len = AES_wrap_key(&aes_key, + nullptr, + static_cast(buf.get()), + in.data(), + in.size()); + if (len < 0 || static_cast(len) != in.size() + 8) { + return WebCryptoCipherStatus::FAILED; + } + *out = ByteSource::Allocated(buf.release()); + } else { + // Input must be a multiple of 8 bytes and at least 24 bytes. + if (in.size() < 24 || in.size() % 8 != 0) { + return WebCryptoCipherStatus::FAILED; + } + if (AES_set_decrypt_key(key_bytes, key_bits, &aes_key) != 0) { + return WebCryptoCipherStatus::FAILED; + } + auto buf = DataPointer::Alloc(in.size() - 8); + int len = AES_unwrap_key(&aes_key, + nullptr, + static_cast(buf.get()), + in.data(), + in.size()); + if (len < 0 || static_cast(len) != in.size() - 8) { + return WebCryptoCipherStatus::FAILED; + } + *out = ByteSource::Allocated(buf.release()); + } + + return WebCryptoCipherStatus::OK; +} +#endif // OPENSSL_IS_BORINGSSL + // The AES_CTR implementation here takes it's inspiration from the chromium // implementation here: // https://github.com/chromium/chromium/blob/7af6cfd/components/webcrypto/algorithms/aes_ctr.cc @@ -465,6 +527,19 @@ Maybe AESCipherTraits::AdditionalConfig( } #undef V +#ifdef OPENSSL_IS_BORINGSSL + // On BoringSSL the KW variants have no backing EVP_CIPHER; they use + // low-level AES_wrap_key / AES_unwrap_key instead. + const bool is_kw = params->variant == AESKeyVariant::KW_128 || + params->variant == AESKeyVariant::KW_192 || + params->variant == AESKeyVariant::KW_256; + + if (is_kw) { + UseDefaultIV(params); + return JustVoid(); + } +#endif + if (!params->cipher) { THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env); return Nothing(); diff --git a/src/crypto/crypto_aes.h b/src/crypto/crypto_aes.h index 5627f9020bad54..401e7b2c338a1b 100644 --- a/src/crypto/crypto_aes.h +++ b/src/crypto/crypto_aes.h @@ -22,11 +22,23 @@ constexpr unsigned kNoAuthTagLength = static_cast(-1); V(GCM_128, AES_Cipher, ncrypto::Cipher::AES_128_GCM) \ V(GCM_192, AES_Cipher, ncrypto::Cipher::AES_192_GCM) \ V(GCM_256, AES_Cipher, ncrypto::Cipher::AES_256_GCM) \ + VARIANTS_KW(V) + +#ifdef OPENSSL_IS_BORINGSSL +// BoringSSL does not expose EVP_aes_*_wrap via the EVP_CIPHER registry. +// Route AES-KW through low-level AES_wrap_key / AES_unwrap_key instead. +#define VARIANTS_KW(V) \ + V(KW_128, AES_KW_Cipher, static_cast(nullptr)) \ + V(KW_192, AES_KW_Cipher, static_cast(nullptr)) \ + V(KW_256, AES_KW_Cipher, static_cast(nullptr)) +#else +#define VARIANTS_KW(V) \ V(KW_128, AES_Cipher, ncrypto::Cipher::AES_128_KW) \ V(KW_192, AES_Cipher, ncrypto::Cipher::AES_192_KW) \ V(KW_256, AES_Cipher, ncrypto::Cipher::AES_256_KW) +#endif -#if OPENSSL_VERSION_MAJOR >= 3 +#if OPENSSL_WITH_AES_OCB #define VARIANTS_OCB(V) \ V(OCB_128, AES_Cipher, ncrypto::Cipher::AES_128_OCB) \ V(OCB_192, AES_Cipher, ncrypto::Cipher::AES_192_OCB) \ diff --git a/src/crypto/crypto_argon2.cc b/src/crypto/crypto_argon2.cc index 7bb995ca51c0df..d5207f4be57bb2 100644 --- a/src/crypto/crypto_argon2.cc +++ b/src/crypto/crypto_argon2.cc @@ -2,8 +2,7 @@ #include "async_wrap-inl.h" #include "threadpoolwork-inl.h" -#if OPENSSL_VERSION_NUMBER >= 0x30200000L -#ifndef OPENSSL_NO_ARGON2 +#if OPENSSL_WITH_ARGON2 #include namespace node::crypto { @@ -159,4 +158,3 @@ void Argon2::RegisterExternalReferences(ExternalReferenceRegistry* registry) { } // namespace node::crypto #endif -#endif diff --git a/src/crypto/crypto_argon2.h b/src/crypto/crypto_argon2.h index 73e8460d204dd3..354d0a4be6f392 100644 --- a/src/crypto/crypto_argon2.h +++ b/src/crypto/crypto_argon2.h @@ -6,7 +6,7 @@ #include "crypto/crypto_util.h" namespace node::crypto { -#if !defined(OPENSSL_NO_ARGON2) && OPENSSL_VERSION_NUMBER >= 0x30200000L +#if OPENSSL_WITH_ARGON2 // Argon2 is a password-based key derivation algorithm // defined in https://datatracker.ietf.org/doc/html/rfc9106 diff --git a/src/crypto/crypto_chacha20_poly1305.cc b/src/crypto/crypto_chacha20_poly1305.cc index 0fd3e0517317ca..43d63fa8c5e409 100644 --- a/src/crypto/crypto_chacha20_poly1305.cc +++ b/src/crypto/crypto_chacha20_poly1305.cc @@ -10,6 +10,9 @@ #include "v8.h" #include +#ifdef OPENSSL_IS_BORINGSSL +#include +#endif namespace node { @@ -110,10 +113,15 @@ Maybe ChaCha20Poly1305CipherTraits::AdditionalConfig( params->mode = mode; params->cipher = ncrypto::Cipher::CHACHA20_POLY1305; +#ifndef OPENSSL_IS_BORINGSSL + // On BoringSSL, ChaCha20-Poly1305 is not exposed via the EVP_CIPHER registry + // so FromNid() returns a null Cipher. We use EVP_AEAD directly in DoCipher + // instead. if (!params->cipher) { THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env); return Nothing(); } +#endif // IV parameter (required) if (!ValidateIV(env, mode, args[offset], params)) { @@ -144,6 +152,75 @@ WebCryptoCipherStatus ChaCha20Poly1305CipherTraits::DoCipher( return WebCryptoCipherStatus::INVALID_KEY_TYPE; } +#ifdef OPENSSL_IS_BORINGSSL + // BoringSSL does not expose ChaCha20-Poly1305 via the EVP_CIPHER registry; + // it is only available through the EVP_AEAD API. Matches Chromium's + // WebCrypto ChaCha20-Poly1305 implementation. + const auto key_bytes = + reinterpret_cast(key_data.GetSymmetricKey()); + const auto ad_bytes = params.additional_data.data(); + const auto ad_len = params.additional_data.size(); + const auto iv_bytes = params.iv.data(); + const auto iv_len = params.iv.size(); + + bssl::ScopedEVP_AEAD_CTX ctx; + if (!EVP_AEAD_CTX_init(ctx.get(), + EVP_aead_chacha20_poly1305(), + key_bytes, + key_data.GetSymmetricKeySize(), + kChaCha20Poly1305TagSize, + nullptr)) { + return WebCryptoCipherStatus::FAILED; + } + + if (cipher_mode == kWebCryptoCipherEncrypt) { + size_t out_len = 0; + const size_t max_out_len = in.size() + kChaCha20Poly1305TagSize; + auto buf = DataPointer::Alloc(max_out_len); + if (!EVP_AEAD_CTX_seal(ctx.get(), + static_cast(buf.get()), + &out_len, + max_out_len, + iv_bytes, + iv_len, + in.data(), + in.size(), + ad_bytes, + ad_len)) { + return WebCryptoCipherStatus::FAILED; + } + buf = buf.resize(out_len); + *out = ByteSource::Allocated(buf.release()); + return WebCryptoCipherStatus::OK; + } + + // Decrypt + if (in.size() < kChaCha20Poly1305TagSize) { + return WebCryptoCipherStatus::FAILED; + } + size_t out_len = 0; + const size_t max_out_len = in.size(); // at most |in_len| bytes written + auto buf = DataPointer::Alloc(max_out_len == 0 ? 1 : max_out_len); + if (!EVP_AEAD_CTX_open(ctx.get(), + static_cast(buf.get()), + &out_len, + max_out_len, + iv_bytes, + iv_len, + in.data(), + in.size(), + ad_bytes, + ad_len)) { + return WebCryptoCipherStatus::FAILED; + } + if (out_len == 0) { + *out = ByteSource(); + } else { + buf = buf.resize(out_len); + *out = ByteSource::Allocated(buf.release()); + } + return WebCryptoCipherStatus::OK; +#else auto ctx = CipherCtxPointer::New(); CHECK(ctx); @@ -242,6 +319,7 @@ WebCryptoCipherStatus ChaCha20Poly1305CipherTraits::DoCipher( *out = ByteSource::Allocated(buf.release()); return WebCryptoCipherStatus::OK; +#endif // OPENSSL_IS_BORINGSSL } void ChaCha20Poly1305::Initialize(Environment* env, Local target) { diff --git a/src/crypto/crypto_cipher.cc b/src/crypto/crypto_cipher.cc index 2e9acf86099ee8..dec72c20412e4e 100644 --- a/src/crypto/crypto_cipher.cc +++ b/src/crypto/crypto_cipher.cc @@ -711,7 +711,7 @@ bool CipherBase::Final(std::unique_ptr* out) { static_cast(ctx_.getBlockSize()), BackingStoreInitializationMode::kUninitialized); -#if (OPENSSL_VERSION_NUMBER < 0x30000000L) +#if !OPENSSL_VERSION_PREREQ(3, 0) // OpenSSL v1.x doesn't verify the presence of the auth tag so do // it ourselves, see https://github.com/nodejs/node/issues/45874. if (kind_ == kDecipher && ctx_.isChaCha20Poly1305() && diff --git a/src/crypto/crypto_hash.cc b/src/crypto/crypto_hash.cc index 9b76b900049484..c42926bb4ce61f 100644 --- a/src/crypto/crypto_hash.cc +++ b/src/crypto/crypto_hash.cc @@ -7,6 +7,10 @@ #include "threadpoolwork-inl.h" #include "v8.h" +#if NCRYPTO_USE_BORINGSSL_EVP_DO_ALL_FALLBACK +#include +#endif + #include namespace node { @@ -41,6 +45,24 @@ void Hash::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackFieldWithSize("md", digest_ ? md_len_ : 0); } +#if NCRYPTO_USE_BORINGSSL_EVP_DO_ALL_FALLBACK +struct BoringSSLDigest { + const EVP_MD* (*get)(); + const char* name; +}; + +constexpr BoringSSLDigest kBoringSSLDigests[] = { + {EVP_md4, "md4"}, + {EVP_md5, "md5"}, + {EVP_sha1, "sha1"}, + {EVP_sha224, "sha224"}, + {EVP_sha256, "sha256"}, + {EVP_sha384, "sha384"}, + {EVP_sha512, "sha512"}, + {EVP_sha512_256, "sha512-256"}, +}; +#endif + #if OPENSSL_VERSION_MAJOR >= 3 void PushAliases(const char* name, void* data) { static_cast*>(data)->push_back(name); @@ -122,7 +144,12 @@ void SaveSupportedHashAlgorithms(const EVP_MD* md, const std::vector& GetSupportedHashAlgorithms(Environment* env) { if (env->supported_hash_algorithms.empty()) { MarkPopErrorOnReturn mark_pop_error_on_return; -#if OPENSSL_VERSION_MAJOR >= 3 +#if NCRYPTO_USE_BORINGSSL_EVP_DO_ALL_FALLBACK + for (const auto& digest : kBoringSSLDigests) { + static_cast(digest.get); + env->supported_hash_algorithms.emplace_back(digest.name); + } +#elif OPENSSL_VERSION_MAJOR >= 3 // Since we'll fetch the EVP_MD*, cache them along the way to speed up // later lookups instead of throwing them away immediately. EVP_MD_do_all_sorted(SaveSupportedHashAlgorithmsAndCacheMD, env); diff --git a/src/crypto/crypto_kem.cc b/src/crypto/crypto_kem.cc index dff69f2e18f947..d30c6aaef6253f 100644 --- a/src/crypto/crypto_kem.cc +++ b/src/crypto/crypto_kem.cc @@ -1,6 +1,6 @@ #include "crypto/crypto_kem.h" -#if OPENSSL_VERSION_MAJOR >= 3 +#if OPENSSL_WITH_KEM #include "async_wrap-inl.h" #include "base_object-inl.h" diff --git a/src/crypto/crypto_kem.h b/src/crypto/crypto_kem.h index 2b4671cfc7a0ec..e00aa04baa897e 100644 --- a/src/crypto/crypto_kem.h +++ b/src/crypto/crypto_kem.h @@ -10,7 +10,7 @@ #include "memory_tracker.h" #include "node_external_reference.h" -#if OPENSSL_VERSION_MAJOR >= 3 +#if OPENSSL_WITH_KEM namespace node { namespace crypto { diff --git a/src/crypto/crypto_keys.cc b/src/crypto/crypto_keys.cc index 92bb7dbb9714ce..abd97ff7c47f3c 100644 --- a/src/crypto/crypto_keys.cc +++ b/src/crypto/crypto_keys.cc @@ -177,7 +177,11 @@ bool ExportJWKAsymmetricKey(Environment* env, const KeyObjectData& key, Local target, bool handleRsaPss) { - switch (key.GetAsymmetricKey().id()) { + 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; @@ -187,51 +191,10 @@ bool ExportJWKAsymmetricKey(Environment* env, case EVP_PKEY_EC: return ExportJWKEcKey(env, key, target); case EVP_PKEY_ED25519: - // Fall through case EVP_PKEY_ED448: - // Fall through case EVP_PKEY_X25519: - // Fall through case EVP_PKEY_X448: return ExportJWKEdKey(env, key, target); -#if OPENSSL_WITH_PQC - case EVP_PKEY_ML_DSA_44: - // Fall through - case EVP_PKEY_ML_DSA_65: - // Fall through - case EVP_PKEY_ML_DSA_87: - // Fall through - case EVP_PKEY_SLH_DSA_SHA2_128F: - // Fall through - case EVP_PKEY_SLH_DSA_SHA2_128S: - // Fall through - case EVP_PKEY_SLH_DSA_SHA2_192F: - // Fall through - case EVP_PKEY_SLH_DSA_SHA2_192S: - // Fall through - case EVP_PKEY_SLH_DSA_SHA2_256F: - // Fall through - case EVP_PKEY_SLH_DSA_SHA2_256S: - // Fall through - case EVP_PKEY_SLH_DSA_SHAKE_128F: - // Fall through - case EVP_PKEY_SLH_DSA_SHAKE_128S: - // Fall through - case EVP_PKEY_SLH_DSA_SHAKE_192F: - // Fall through - case EVP_PKEY_SLH_DSA_SHAKE_192S: - // Fall through - case EVP_PKEY_SLH_DSA_SHAKE_256F: - // Fall through - case EVP_PKEY_SLH_DSA_SHAKE_256S: - // Fall through - case EVP_PKEY_ML_KEM_512: - // Fall through - case EVP_PKEY_ML_KEM_768: - // Fall through - case EVP_PKEY_ML_KEM_1024: - return ExportJwkPqcKey(env, key, target); -#endif } THROW_ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE(env); return false; @@ -306,35 +269,98 @@ int GetNidFromName(const char* name) { const char* name; int nid; } kNameToNid[] = { - {"Ed25519", EVP_PKEY_ED25519}, - {"Ed448", EVP_PKEY_ED448}, - {"X25519", EVP_PKEY_X25519}, - {"X448", EVP_PKEY_X448}, -#if OPENSSL_WITH_PQC - {"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}, -#endif + {"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, + 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); + } +#endif + + if (IsUnavailablePqcKeyType(env, key_type)) { + THROW_ERR_INVALID_ARG_VALUE(env, "Unsupported key type"); + return; + } + + if (IsUnsupportedRawKeyType(env, key_type)) { + THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); + return; + } + + THROW_ERR_INVALID_ARG_VALUE( + env, "Invalid asymmetricKeyType: %s", key_type_name); } } // namespace @@ -363,35 +389,15 @@ bool KeyObjectData::ToEncodedPublicKey( const auto point = ECKeyPointer::GetPublicKey(ec_key); return ECPointToBuffer(env, group, point, form).ToLocal(out); } - switch (pkey.id()) { - case EVP_PKEY_ED25519: - case EVP_PKEY_ED448: - case EVP_PKEY_X25519: - case EVP_PKEY_X448: + 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 - case EVP_PKEY_ML_DSA_44: - case EVP_PKEY_ML_DSA_65: - case EVP_PKEY_ML_DSA_87: - case EVP_PKEY_ML_KEM_512: - case EVP_PKEY_ML_KEM_768: - case EVP_PKEY_ML_KEM_1024: - 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: + is_raw_supported = is_raw_supported || IsPqcKeyId(id); #endif - break; - default: - THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); - return false; + if (!is_raw_supported) { + THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); + return false; } auto raw_data = pkey.rawPublicKey(); if (!raw_data) { @@ -438,29 +444,15 @@ bool KeyObjectData::ToEncodedPrivateKey( } return Buffer::Copy(env, buf.get(), buf.size()).ToLocal(out); } - switch (pkey.id()) { - case EVP_PKEY_ED25519: - case EVP_PKEY_ED448: - case EVP_PKEY_X25519: - case EVP_PKEY_X448: + 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 - 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: + is_raw_supported = is_raw_supported || IsPqcRawPrivateKeyId(id); #endif - break; - default: - THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); - return false; + if (!is_raw_supported) { + THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); + return false; } auto raw_data = pkey.rawPrivateKey(); if (!raw_data) { @@ -470,23 +462,13 @@ 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(); - switch (pkey.id()) { -#if OPENSSL_WITH_PQC - case EVP_PKEY_ML_DSA_44: - case EVP_PKEY_ML_DSA_65: - case EVP_PKEY_ML_DSA_87: - case EVP_PKEY_ML_KEM_512: - case EVP_PKEY_ML_KEM_768: - case EVP_PKEY_ML_KEM_1024: - break; -#endif - default: - THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); - return false; + if (!IsPqcSeedKeyId(pkey.id())) { + THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); + return false; } -#if OPENSSL_WITH_PQC auto raw_data = pkey.rawSeed(); if (!raw_data) { THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get raw seed"); @@ -585,6 +567,12 @@ static KeyObjectData ImportRawKey(Environment* env, } }; + const int id = GetNidFromName(key_type_name); + ValidateRawKeyImportFormat(env, key_type, key_type_name, id, format); + if (env->isolate()->HasPendingException()) { + return {}; + } + // EC keys if (key_type->StringEquals(env->crypto_ec_string())) { int curve_nid = ncrypto::Ec::GetCurveIdFromName(named_curve); @@ -642,8 +630,6 @@ static KeyObjectData ImportRawKey(Environment* env, return KeyObjectData::CreateAsymmetric(target_type, std::move(pkey)); } - int id = GetNidFromName(key_type_name); - typedef EVPKeyPointer (*new_key_fn)( int, const ncrypto::Buffer&); new_key_fn fn = nullptr; @@ -655,33 +641,17 @@ static KeyObjectData ImportRawKey(Environment* env, fn = target_type == kKeyTypePrivate ? EVPKeyPointer::NewRawPrivate : EVPKeyPointer::NewRawPublic; break; + default: #if OPENSSL_WITH_PQC - case EVP_PKEY_ML_DSA_44: - case EVP_PKEY_ML_DSA_65: - case EVP_PKEY_ML_DSA_87: - case EVP_PKEY_ML_KEM_512: - case EVP_PKEY_ML_KEM_768: - case EVP_PKEY_ML_KEM_1024: - fn = target_type == kKeyTypePrivate ? EVPKeyPointer::NewRawSeed - : EVPKeyPointer::NewRawPublic; - break; - 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: - fn = target_type == kKeyTypePrivate ? EVPKeyPointer::NewRawPrivate - : EVPKeyPointer::NewRawPublic; - break; + if (IsPqcKeyId(id)) { + if (target_type == kKeyTypePrivate) { + fn = IsPqcSeedKeyId(id) ? EVPKeyPointer::NewRawSeed + : EVPKeyPointer::NewRawPrivate; + } else { + fn = EVPKeyPointer::NewRawPublic; + } + } #endif - default: break; } @@ -698,40 +668,6 @@ static KeyObjectData ImportRawKey(Environment* env, return KeyObjectData::CreateAsymmetric(target_type, std::move(pkey)); } - if (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())) { - THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); - return {}; - } - -#if !OPENSSL_WITH_PQC - if (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())) { - THROW_ERR_INVALID_ARG_VALUE(env, "Unsupported key type"); - return {}; - } -#endif - - THROW_ERR_INVALID_ARG_VALUE( - env, "Invalid asymmetricKeyType: %s", key_type_name); return {}; } @@ -1328,45 +1264,12 @@ Local KeyObjectHandle::GetAsymmetricKeyType() const { case EVP_PKEY_X448: return env()->crypto_x448_string(); #if OPENSSL_WITH_PQC - case EVP_PKEY_ML_DSA_44: - return env()->crypto_ml_dsa_44_string(); - case EVP_PKEY_ML_DSA_65: - return env()->crypto_ml_dsa_65_string(); - case EVP_PKEY_ML_DSA_87: - return env()->crypto_ml_dsa_87_string(); - case EVP_PKEY_ML_KEM_512: - return env()->crypto_ml_kem_512_string(); - case EVP_PKEY_ML_KEM_768: - return env()->crypto_ml_kem_768_string(); - case EVP_PKEY_ML_KEM_1024: - return env()->crypto_ml_kem_1024_string(); - case EVP_PKEY_SLH_DSA_SHA2_128F: - return env()->crypto_slh_dsa_sha2_128f_string(); - case EVP_PKEY_SLH_DSA_SHA2_128S: - return env()->crypto_slh_dsa_sha2_128s_string(); - case EVP_PKEY_SLH_DSA_SHA2_192F: - return env()->crypto_slh_dsa_sha2_192f_string(); - case EVP_PKEY_SLH_DSA_SHA2_192S: - return env()->crypto_slh_dsa_sha2_192s_string(); - case EVP_PKEY_SLH_DSA_SHA2_256F: - return env()->crypto_slh_dsa_sha2_256f_string(); - case EVP_PKEY_SLH_DSA_SHA2_256S: - return env()->crypto_slh_dsa_sha2_256s_string(); - case EVP_PKEY_SLH_DSA_SHAKE_128F: - return env()->crypto_slh_dsa_shake_128f_string(); - case EVP_PKEY_SLH_DSA_SHAKE_128S: - return env()->crypto_slh_dsa_shake_128s_string(); - case EVP_PKEY_SLH_DSA_SHAKE_192F: - return env()->crypto_slh_dsa_shake_192f_string(); - case EVP_PKEY_SLH_DSA_SHAKE_192S: - return env()->crypto_slh_dsa_shake_192s_string(); - case EVP_PKEY_SLH_DSA_SHAKE_256F: - return env()->crypto_slh_dsa_shake_256f_string(); - case EVP_PKEY_SLH_DSA_SHAKE_256S: - return env()->crypto_slh_dsa_shake_256s_string(); -#endif + default: + return GetPqcAsymmetricKeyType(env(), data_.GetAsymmetricKey().id()); +#else default: return Undefined(env()->isolate()); +#endif } } @@ -1475,34 +1378,14 @@ void KeyObjectHandle::RawPublicKey( Mutex::ScopedLock lock(data.mutex()); const auto& pkey = data.GetAsymmetricKey(); - switch (pkey.id()) { - case EVP_PKEY_ED25519: - case EVP_PKEY_ED448: - case EVP_PKEY_X25519: - case EVP_PKEY_X448: + 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 - case EVP_PKEY_ML_DSA_44: - case EVP_PKEY_ML_DSA_65: - case EVP_PKEY_ML_DSA_87: - case EVP_PKEY_ML_KEM_512: - case EVP_PKEY_ML_KEM_768: - case EVP_PKEY_ML_KEM_1024: - 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: + is_raw_supported = is_raw_supported || IsPqcKeyId(id); #endif - break; - default: - return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); + if (!is_raw_supported) { + return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); } auto raw_data = pkey.rawPublicKey(); @@ -1528,28 +1411,14 @@ void KeyObjectHandle::RawPrivateKey( Mutex::ScopedLock lock(data.mutex()); const auto& pkey = data.GetAsymmetricKey(); - switch (pkey.id()) { - case EVP_PKEY_ED25519: - case EVP_PKEY_ED448: - case EVP_PKEY_X25519: - case EVP_PKEY_X448: + 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 - 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: + is_raw_supported = is_raw_supported || IsPqcRawPrivateKeyId(id); #endif - break; - default: - return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); + if (!is_raw_supported) { + return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); } auto raw_data = pkey.rawPrivateKey(); @@ -1638,24 +1507,14 @@ 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(); - switch (pkey.id()) { -#if OPENSSL_WITH_PQC - case EVP_PKEY_ML_DSA_44: - case EVP_PKEY_ML_DSA_65: - case EVP_PKEY_ML_DSA_87: - case EVP_PKEY_ML_KEM_512: - case EVP_PKEY_ML_KEM_768: - case EVP_PKEY_ML_KEM_1024: - break; -#endif - default: - return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); + if (!IsPqcSeedKeyId(pkey.id())) { + return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); } -#if OPENSSL_WITH_PQC auto raw_data = pkey.rawSeed(); if (!raw_data) { return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get raw seed"); @@ -1664,6 +1523,8 @@ void KeyObjectHandle::RawSeed(const v8::FunctionCallbackInfo& args) { args.GetReturnValue().Set( Buffer::Copy(env, raw_data.get(), raw_data.size()) .FromMaybe(Local())); +#else + return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); #endif } @@ -2092,9 +1953,12 @@ void Initialize(Environment* env, Local target) { 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); @@ -2107,6 +1971,7 @@ void Initialize(Environment* env, Local target) { 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); diff --git a/src/crypto/crypto_kmac.cc b/src/crypto/crypto_kmac.cc index fd431ffc1b47b7..ed4a8e9d526983 100644 --- a/src/crypto/crypto_kmac.cc +++ b/src/crypto/crypto_kmac.cc @@ -3,7 +3,7 @@ #include "node_internals.h" #include "threadpoolwork-inl.h" -#if OPENSSL_VERSION_MAJOR >= 3 +#if OPENSSL_WITH_KMAC #include #include #include "crypto/crypto_keys.h" @@ -220,4 +220,4 @@ void Kmac::RegisterExternalReferences(ExternalReferenceRegistry* registry) { } // namespace node::crypto -#endif +#endif // OPENSSL_WITH_KMAC diff --git a/src/crypto/crypto_kmac.h b/src/crypto/crypto_kmac.h index 9ee6192ee3dd17..5a8c9e5039f22b 100644 --- a/src/crypto/crypto_kmac.h +++ b/src/crypto/crypto_kmac.h @@ -10,8 +10,7 @@ namespace node::crypto { -// KMAC (Keccak Message Authentication Code) is available since OpenSSL 3.0. -#if OPENSSL_VERSION_MAJOR >= 3 +#if OPENSSL_WITH_KMAC enum class KmacVariant { KMAC128, KMAC256 }; @@ -72,7 +71,7 @@ namespace Kmac { void Initialize(Environment* env, v8::Local target) {} void RegisterExternalReferences(ExternalReferenceRegistry* registry) {} } // namespace Kmac -#endif +#endif // OPENSSL_WITH_KMAC } // namespace node::crypto diff --git a/src/crypto/crypto_pqc.cc b/src/crypto/crypto_pqc.cc index cd2024cbe2f05d..8d4af1e7801180 100644 --- a/src/crypto/crypto_pqc.cc +++ b/src/crypto/crypto_pqc.cc @@ -17,32 +17,105 @@ 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; - bool - use_seed; // true: rawSeed/NewRawSeed, false: rawPrivateKey/NewRawPrivate + 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", true}, - {EVP_PKEY_ML_DSA_65, "ML-DSA-65", true}, - {EVP_PKEY_ML_DSA_87, "ML-DSA-87", true}, - {EVP_PKEY_ML_KEM_512, "ML-KEM-512", true}, - {EVP_PKEY_ML_KEM_768, "ML-KEM-768", true}, - {EVP_PKEY_ML_KEM_1024, "ML-KEM-1024", true}, - {EVP_PKEY_SLH_DSA_SHA2_128F, "SLH-DSA-SHA2-128f", false}, - {EVP_PKEY_SLH_DSA_SHA2_128S, "SLH-DSA-SHA2-128s", false}, - {EVP_PKEY_SLH_DSA_SHA2_192F, "SLH-DSA-SHA2-192f", false}, - {EVP_PKEY_SLH_DSA_SHA2_192S, "SLH-DSA-SHA2-192s", false}, - {EVP_PKEY_SLH_DSA_SHA2_256F, "SLH-DSA-SHA2-256f", false}, - {EVP_PKEY_SLH_DSA_SHA2_256S, "SLH-DSA-SHA2-256s", false}, - {EVP_PKEY_SLH_DSA_SHAKE_128F, "SLH-DSA-SHAKE-128f", false}, - {EVP_PKEY_SLH_DSA_SHAKE_128S, "SLH-DSA-SHAKE-128s", false}, - {EVP_PKEY_SLH_DSA_SHAKE_192F, "SLH-DSA-SHAKE-192f", false}, - {EVP_PKEY_SLH_DSA_SHAKE_192S, "SLH-DSA-SHAKE-192s", false}, - {EVP_PKEY_SLH_DSA_SHAKE_256F, "SLH-DSA-SHAKE-256f", false}, - {EVP_PKEY_SLH_DSA_SHAKE_256S, "SLH-DSA-SHAKE-256s", false}, + {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) { @@ -59,6 +132,10 @@ const PqcAlgorithm* FindPqcAlgorithmByName(const char* name) { return nullptr; } +bool HasPqcAlgorithmFlag(const PqcAlgorithm* alg, PqcAlgorithmFlag flag) { + return alg != nullptr && (alg->flags & flag) != 0; +} + bool TrySetEncodedKey(Environment* env, DataPointer data, Local target, @@ -82,9 +159,9 @@ bool ExportJwkPqcKey(Environment* env, CHECK(alg); if (key.GetKeyType() == kKeyTypePrivate) { - DataPointer priv_data = - alg->use_seed ? pkey.rawSeed() : pkey.rawPrivateKey(); - if (alg->use_seed && !priv_data) { + 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; @@ -144,8 +221,9 @@ KeyObjectData ImportJWKPqcKey(Environment* env, Local jwk) { .data = priv.data(), .len = priv.size(), }; - pkey = alg->use_seed ? EVPKeyPointer::NewRawSeed(alg->id, buf) - : EVPKeyPointer::NewRawPrivate(alg->id, buf); + pkey = HasPqcAlgorithmFlag(alg, kPqcRawSeed) + ? EVPKeyPointer::NewRawSeed(alg->id, buf) + : EVPKeyPointer::NewRawPrivate(alg->id, buf); } else { ByteSource pub = ByteSource::FromEncodedString(env, pub_value.As()); pkey = @@ -175,6 +253,40 @@ KeyObjectData ImportJWKPqcKey(Environment* env, Local jwk) { 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(); +} #endif } // namespace crypto } // namespace node diff --git a/src/crypto/crypto_pqc.h b/src/crypto/crypto_pqc.h index 7a805c0e36c6a3..14f919d94c6f8a 100644 --- a/src/crypto/crypto_pqc.h +++ b/src/crypto/crypto_pqc.h @@ -15,6 +15,22 @@ bool ExportJwkPqcKey(Environment* env, 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 } // namespace crypto } // namespace node diff --git a/src/crypto/crypto_sig.cc b/src/crypto/crypto_sig.cc index bd3c9f538c5de5..d8a4fe395a5f47 100644 --- a/src/crypto/crypto_sig.cc +++ b/src/crypto/crypto_sig.cc @@ -3,6 +3,7 @@ #include "base_object-inl.h" #include "crypto/crypto_ec.h" #include "crypto/crypto_keys.h" +#include "crypto/crypto_pqc.h" #include "crypto/crypto_util.h" #include "env-inl.h" #include "memory_tracker-inl.h" @@ -237,34 +238,16 @@ bool UseP1363Encoding(const EVPKeyPointer& key, const DSASigEnc dsa_encoding) { } bool SupportsContextString(const EVPKeyPointer& key) { -#if OPENSSL_VERSION_NUMBER < 0x3020000fL - return false; -#else - switch (key.id()) { - case EVP_PKEY_ED25519: - case EVP_PKEY_ED448: + if (!OPENSSL_WITH_SIGNATURE_CONTEXT_STRING) return false; + + const int id = key.id(); #if OPENSSL_WITH_PQC - case EVP_PKEY_ML_DSA_44: - case EVP_PKEY_ML_DSA_65: - case EVP_PKEY_ML_DSA_87: - 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: + if (IsPqcSignatureKeyId(id)) return true; #endif - return true; - default: - return false; - } +#ifndef OPENSSL_IS_BORINGSSL + if (id == EVP_PKEY_ED25519 || id == EVP_PKEY_ED448) return true; #endif + return false; } } // namespace diff --git a/src/crypto/crypto_util.cc b/src/crypto/crypto_util.cc index 0e743135e8de15..523be7f6cd5d23 100644 --- a/src/crypto/crypto_util.cc +++ b/src/crypto/crypto_util.cc @@ -139,7 +139,7 @@ void InitCryptoOnce() { OPENSSL_init_ssl(0, settings); -#if OPENSSL_WITH_PQC +#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 diff --git a/src/node_crypto.cc b/src/node_crypto.cc index c0869f40e0410d..91d80e0dd379ba 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -61,21 +61,24 @@ namespace crypto { V(Verify) \ V(X509Certificate) -#if !defined(OPENSSL_NO_ARGON2) && OPENSSL_VERSION_NUMBER >= 0x30200000L +#if OPENSSL_WITH_ARGON2 #define ARGON2_NAMESPACE_LIST(V) V(Argon2) #else #define ARGON2_NAMESPACE_LIST(V) -#endif // !OPENSSL_NO_ARGON2 && OpenSSL >= 3.2 +#endif // OPENSSL_WITH_ARGON2 -// KEM and KMAC functionality requires OpenSSL 3.0.0 or later -#if OPENSSL_VERSION_MAJOR >= 3 +#if OPENSSL_WITH_KEM #define KEM_NAMESPACE_LIST(V) V(KEM) -#define KMAC_NAMESPACE_LIST(V) V(Kmac) #else #define KEM_NAMESPACE_LIST(V) -#define KMAC_NAMESPACE_LIST(V) #endif +#if OPENSSL_WITH_KMAC +#define KMAC_NAMESPACE_LIST(V) V(Kmac) +#else +#define KMAC_NAMESPACE_LIST(V) +#endif // OPENSSL_WITH_KMAC + #define TURBOSHAKE_NAMESPACE_LIST(V) V(TurboShake) #ifdef OPENSSL_NO_SCRYPT diff --git a/src/node_crypto.h b/src/node_crypto.h index 80657431a791db..ecc2b8c6a358c8 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -40,8 +40,10 @@ #include "crypto/crypto_hash.h" #include "crypto/crypto_hkdf.h" #include "crypto/crypto_hmac.h" -#if OPENSSL_VERSION_MAJOR >= 3 +#if OPENSSL_WITH_KEM #include "crypto/crypto_kem.h" +#endif +#if OPENSSL_WITH_KMAC #include "crypto/crypto_kmac.h" #endif #include "crypto/crypto_keygen.h" diff --git a/test/addons/openssl-get-ssl-ctx/binding.cc b/test/addons/openssl-get-ssl-ctx/binding.cc index 3945ec870fb8b9..47468ffbcd789d 100644 --- a/test/addons/openssl-get-ssl-ctx/binding.cc +++ b/test/addons/openssl-get-ssl-ctx/binding.cc @@ -18,12 +18,12 @@ void GetSSLCtx(const v8::FunctionCallbackInfo& args) { return; } - // Verify the pointer is a valid SSL_CTX by calling an OpenSSL function. - const SSL_METHOD* method = SSL_CTX_get_ssl_method(ctx); - if (method == nullptr) { + // Verify the pointer is a valid SSL_CTX by calling a function available + // across OpenSSL-compatible TLS backends and checking context-owned state. + STACK_OF(SSL_CIPHER)* ciphers = SSL_CTX_get_ciphers(ctx); + if (ciphers == nullptr) { isolate->ThrowException(v8::Exception::Error( - v8::String::NewFromUtf8(isolate, - "SSL_CTX_get_ssl_method returned nullptr") + v8::String::NewFromUtf8(isolate, "SSL_CTX_get_ciphers returned nullptr") .ToLocalChecked())); return; } diff --git a/test/common/boringssl.js b/test/common/boringssl.js new file mode 100644 index 00000000000000..e6e91387c304c7 --- /dev/null +++ b/test/common/boringssl.js @@ -0,0 +1,346 @@ +/* eslint-disable node-core/crypto-check */ + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const fixtures = require('../common/fixtures'); +const tls = require('tls'); + +// This module is for BoringSSL-specific branches in tests whose original +// OpenSSL coverage cannot run unchanged. Each helper should assert the +// observable BoringSSL behavior that explains why the OpenSSL-specific +// assertions are bypassed. + +/** + * BoringSSL exposes many removed or disabled TLS cipher suites as "no match" + * at secure-context creation time. This is used for suites such as + * finite-field DHE and anonymous ECDH that OpenSSL builds may still negotiate + * in tests. + * @param {Function} fn + */ +function assertNoCipherMatch(fn) { + assert.throws(fn, { + code: 'ERR_SSL_NO_CIPHER_MATCH', + library: 'SSL routines', + function: 'OPENSSL_internal', + reason: 'NO_CIPHER_MATCH', + }); +} + +/** + * BoringSSL does not parse OpenSSL cipher-string commands such as `@SECLEVEL`. + * Those are OpenSSL policy directives, not cipher names. + * @param {Function} fn + */ +function assertInvalidCommand(fn) { + assert.throws(fn, { + code: 'ERR_SSL_INVALID_COMMAND', + library: 'SSL routines', + function: 'OPENSSL_internal', + reason: 'INVALID_COMMAND', + }); +} + +/** + * Node's DHE tests exercise OpenSSL's finite-field DHE cipher support and DH + * parameter-size policy. BoringSSL does not offer these DHE cipher suites on + * this surface, so creating a server context with a DHE-only cipher list fails + * before a handshake can test DH parameter behavior. + */ +function assertFiniteFieldDheUnsupported() { + assertNoCipherMatch(() => { + tls.createServer({ + key: fixtures.readKey('agent2-key.pem'), + cert: fixtures.readKey('agent2-cert.pem'), + ciphers: 'DHE-RSA-AES128-GCM-SHA256', + }); + }); +} + +/** + * OpenSSL security levels reject small keys by policy and can be adjusted with + * `@SECLEVEL` in the cipher string. BoringSSL does not implement those security + * levels: the small-key server context is accepted, while the OpenSSL-specific + * `@SECLEVEL` command is rejected as invalid cipher-string syntax. + */ +function assertOpenSSLSecurityLevelsUnsupported() { + const options = { + key: fixtures.readKey('agent11-key.pem'), + cert: fixtures.readKey('agent11-cert.pem'), + ciphers: 'DEFAULT', + }; + + tls.createServer(options).close(); + + options.ciphers = 'DEFAULT:@SECLEVEL=0'; + assertInvalidCommand(() => tls.createServer(options)); +} + +/** + * Node's multi-key tests rely on OpenSSL accepting an array of private keys and + * matching them with an array of certificates. BoringSSL rejects this mixed + * EC/RSA identity configuration while configuring the certificate chain, before + * a client can negotiate either identity. + */ +function assertMultiKeyUnsupported() { + assert.throws(() => { + tls.createServer({ + key: [ + fixtures.readKey('ec10-key.pem'), + fixtures.readKey('agent1-key.pem'), + ], + cert: [ + fixtures.readKey('agent1-cert.pem'), + fixtures.readKey('ec10-cert.pem'), + ], + }); + }, { + code: 'ERR_OSSL_X509_KEY_TYPE_MISMATCH', + library: 'X.509 certificate routines', + function: 'OPENSSL_internal', + reason: 'KEY_TYPE_MISMATCH', + }); +} + +/** + * BoringSSL does not support caller-initiated renegotiation. Even on a TLS 1.2 + * connection, TLSSocket#renegotiate() returns false and the callback receives + * Node's BoringSSL-specific unsupported-renegotiation error instead of + * entering the native binding or exercising Node's renegotiation-limit logic. + */ +function testRenegotiationUnsupported() { + const server = tls.createServer({ + key: fixtures.readKey('rsa_private.pem'), + cert: fixtures.readKey('rsa_cert.crt'), + maxVersion: 'TLSv1.2', + }, (socket) => socket.resume()); + + server.listen(0, common.mustCall(() => { + const client = tls.connect({ + port: server.address().port, + rejectUnauthorized: false, + maxVersion: 'TLSv1.2', + }, common.mustCall(() => { + const ok = client.renegotiate({}, common.mustCall((err) => { + assert.throws(() => { throw err; }, { + code: 'ERR_TLS_RENEGOTIATION_UNSUPPORTED', + message: 'TLS session renegotiation is unsupported by this TLS ' + + 'implementation', + }); + client.destroy(); + server.close(); + })); + assert.strictEqual(ok, false); + })); + client.on('error', common.mustNotCall()); + })); +} + +/** + * OpenSSL exposes the negotiated ephemeral key type, name, and size for TLS + * clients. With BoringSSL the same ECDHE TLS 1.2 handshake succeeds, but + * getEphemeralKeyInfo() returns null on the server side and an object whose + * fields are undefined on the client side. + */ +function testEphemeralKeyInfoUnsupported() { + const server = tls.createServer({ + key: fixtures.readKey('agent2-key.pem'), + cert: fixtures.readKey('agent2-cert.pem'), + ciphers: 'ECDHE-RSA-AES256-GCM-SHA384', + ecdhCurve: 'prime256v1', + maxVersion: 'TLSv1.2', + }, common.mustCall((socket) => { + assert.strictEqual(socket.getEphemeralKeyInfo(), null); + socket.end(); + })); + + server.listen(0, common.mustCall(() => { + const client = tls.connect({ + port: server.address().port, + rejectUnauthorized: false, + maxVersion: 'TLSv1.2', + }, common.mustCall(() => { + assert.deepStrictEqual(client.getEphemeralKeyInfo(), { + type: undefined, + name: undefined, + size: undefined, + }); + server.close(); + })); + })); +} + +/** + * The protocol matrix tests cover OpenSSL behavior for legacy TLS protocols. + * For BoringSSL we only need to exhibit that a TLSv1-only client cannot connect + * to a server whose minimum protocol is TLS 1.2; the client receives the + * protocol-version alert instead of the OpenSSL version-specific matrix. + */ +function testLegacyProtocolUnsupported() { + const server = tls.createServer({ + key: fixtures.readKey('agent2-key.pem'), + cert: fixtures.readKey('agent2-cert.pem'), + minVersion: 'TLSv1.2', + }, common.mustNotCall()); + + server.on('tlsClientError', common.mustCall()); + server.listen(0, common.mustCall(() => { + const client = tls.connect({ + port: server.address().port, + rejectUnauthorized: false, + secureProtocol: 'TLSv1_method', + }, common.mustNotCall()); + client.on('error', common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_SSL_TLSV1_ALERT_PROTOCOL_VERSION'); + server.close(); + })); + })); +} + +/** + * BoringSSL can load a multi-PFX option well enough to serve the ECDSA + * identity, but it does not provide the same OpenSSL multi-identity selection + * behavior. After the ECDSA handshake succeeds, an RSA-only client fails with + * no shared cipher instead of selecting the RSA identity from the same PFX list. + */ +function testMultiPfxSelectionDifference() { + const server = tls.createServer({ + pfx: [ + { + buf: fixtures.readKey('agent1.pfx'), + passphrase: 'sample', + }, + fixtures.readKey('ec.pfx'), + ], + }, common.mustCallAtLeast((socket) => socket.end(), 1)); + + server.listen(0, common.mustCall(() => { + const ecdsa = tls.connect(server.address().port, { + ciphers: 'ECDHE-ECDSA-AES256-GCM-SHA384', + maxVersion: 'TLSv1.2', + rejectUnauthorized: false, + }, common.mustCall(() => { + assert.strictEqual(ecdsa.getCipher().name, + 'ECDHE-ECDSA-AES256-GCM-SHA384'); + ecdsa.end(); + + server.once('tlsClientError', common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_SSL_NO_SHARED_CIPHER'); + })); + const rsa = tls.connect(server.address().port, { + ciphers: 'ECDHE-RSA-AES256-GCM-SHA384', + maxVersion: 'TLSv1.2', + rejectUnauthorized: false, + }, common.mustNotCall()); + rsa.on('error', common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'); + server.close(); + })); + })); + })); +} + +/** + * PSK works for TLS 1.2 in BoringSSL, but Node's PSK tests also cover the + * default TLS 1.3 path. In that path BoringSSL does not complete a certificate- + * less PSK-only handshake through Node's current server setup: the server + * reports NO_CERTIFICATE_SET and the client receives an internal-error alert. + */ +function testPskTls13Unsupported() { + const key = Buffer.from('d731ef57be09e5204f0b205b60627028', 'hex'); + let gotClientError = false; + let gotServerError = false; + function maybeClose(server) { + if (gotClientError && gotServerError) + server.close(); + } + + const server = tls.createServer({ + ciphers: 'PSK+HIGH', + pskCallback() { return key; }, + }, common.mustNotCall()); + + server.once('tlsClientError', common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_SSL_NO_CERTIFICATE_SET'); + gotServerError = true; + maybeClose(server); + })); + + server.listen(0, common.mustCall(() => { + const client = tls.connect({ + port: server.address().port, + ciphers: 'PSK+HIGH', + checkServerIdentity() {}, + pskCallback() { + return { psk: key, identity: 'TestUser' }; + }, + }, common.mustNotCall()); + client.on('error', common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_SSL_TLSV1_ALERT_INTERNAL_ERROR'); + gotClientError = true; + maybeClose(server); + })); + })); +} + +/** + * The OpenSSL ticket tests assume that once a TLS 1.3 session is reused, the + * client will not necessarily receive a replacement session event before close. + * BoringSSL emits new session tickets on both the initial and resumed TLS 1.3 + * connections, so the resumed connection still emits at least one 'session' + * event while isSessionReused() is true. + */ +function testTls13SessionTicketSemanticsDiffer() { + const server = tls.createServer({ + key: fixtures.readKey('agent1-key.pem'), + cert: fixtures.readKey('agent1-cert.pem'), + }, (socket) => socket.end()); + + let session; + let secondSessionEvents = 0; + + server.listen(0, common.mustCall(() => { + const first = tls.connect({ + port: server.address().port, + rejectUnauthorized: false, + }, common.mustCall(() => { + assert.strictEqual(first.isSessionReused(), false); + })); + first.on('session', common.mustCallAtLeast((sess) => { + session = sess; + }, 1)); + first.on('close', common.mustCall(() => { + assert(Buffer.isBuffer(session)); + + const second = tls.connect({ + port: server.address().port, + rejectUnauthorized: false, + session, + }, common.mustCall(() => { + assert.strictEqual(second.isSessionReused(), true); + })); + second.on('session', common.mustCallAtLeast(() => { + secondSessionEvents++; + }, 1)); + second.on('close', common.mustCall(() => { + assert(secondSessionEvents > 0); + server.close(); + })); + second.resume(); + })); + first.resume(); + })); +} + +module.exports = { + assertFiniteFieldDheUnsupported, + assertMultiKeyUnsupported, + assertNoCipherMatch, + assertOpenSSLSecurityLevelsUnsupported, + testEphemeralKeyInfoUnsupported, + testLegacyProtocolUnsupported, + testMultiPfxSelectionDifference, + testPskTls13Unsupported, + testRenegotiationUnsupported, + testTls13SessionTicketSemanticsDiffer, +}; diff --git a/test/fixtures/keys/Makefile b/test/fixtures/keys/Makefile index d255b5eea80e6a..d5bdd8f46ff573 100644 --- a/test/fixtures/keys/Makefile +++ b/test/fixtures/keys/Makefile @@ -101,6 +101,8 @@ all: \ ml_dsa_44_private.pem \ ml_dsa_44_private_seed_only.pem \ ml_dsa_44_private_priv_only.pem \ + ml_dsa_44_private_encrypted.pem \ + ml_dsa_44_private_encrypted.der \ ml_dsa_44_public.pem \ ml_dsa_65_private.pem \ ml_dsa_65_private_seed_only.pem \ @@ -123,6 +125,8 @@ all: \ ml_kem_768_private.pem \ ml_kem_768_private_seed_only.pem \ ml_kem_768_private_priv_only.pem \ + ml_kem_768_private_encrypted.pem \ + ml_kem_768_private_encrypted.der \ ml_kem_768_public.pem \ ml_kem_1024_private.pem \ ml_kem_1024_private_seed_only.pem \ @@ -1005,6 +1009,12 @@ ml_dsa_44_private_priv_only.pem: ml_dsa_44_private.pem ml_dsa_44_public.pem: ml_dsa_44_private.pem openssl pkey -in ml_dsa_44_private.pem -pubout -out ml_dsa_44_public.pem +ml_dsa_44_private_encrypted.pem: ml_dsa_44_private_seed_only.pem + openssl pkcs8 -topk8 -v2 aes-256-cbc -provparam ml-dsa.output_formats=seed-only -in ml_dsa_44_private_seed_only.pem -passout 'pass:password' -out ml_dsa_44_private_encrypted.pem + +ml_dsa_44_private_encrypted.der: ml_dsa_44_private_seed_only.pem + openssl pkcs8 -topk8 -v2 aes-256-cbc -provparam ml-dsa.output_formats=seed-only -in ml_dsa_44_private_seed_only.pem -passout 'pass:password' -outform DER -out ml_dsa_44_private_encrypted.der + ml_dsa_65_private.pem: openssl genpkey -algorithm ml-dsa-65 -out ml_dsa_65_private.pem @@ -1053,6 +1063,12 @@ ml_kem_768_private_priv_only.pem: ml_kem_768_private.pem ml_kem_768_public.pem: ml_kem_768_private.pem openssl pkey -in ml_kem_768_private.pem -pubout -out ml_kem_768_public.pem +ml_kem_768_private_encrypted.pem: ml_kem_768_private_seed_only.pem + openssl pkcs8 -topk8 -v2 aes-256-cbc -provparam ml-kem.output_formats=seed-only -in ml_kem_768_private_seed_only.pem -passout 'pass:password' -out ml_kem_768_private_encrypted.pem + +ml_kem_768_private_encrypted.der: ml_kem_768_private_seed_only.pem + openssl pkcs8 -topk8 -v2 aes-256-cbc -provparam ml-kem.output_formats=seed-only -in ml_kem_768_private_seed_only.pem -passout 'pass:password' -outform DER -out ml_kem_768_private_encrypted.der + ml_kem_1024_private.pem: openssl genpkey -algorithm ml-kem-1024 -out ml_kem_1024_private.pem diff --git a/test/fixtures/keys/ml_dsa_44_private_encrypted.der b/test/fixtures/keys/ml_dsa_44_private_encrypted.der new file mode 100644 index 00000000000000..2ae136bc7961e5 Binary files /dev/null and b/test/fixtures/keys/ml_dsa_44_private_encrypted.der differ diff --git a/test/fixtures/keys/ml_dsa_44_private_encrypted.pem b/test/fixtures/keys/ml_dsa_44_private_encrypted.pem new file mode 100644 index 00000000000000..e127aa0085bc5f --- /dev/null +++ b/test/fixtures/keys/ml_dsa_44_private_encrypted.pem @@ -0,0 +1,6 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIGjMF8GCSqGSIb3DQEFDTBSMDEGCSqGSIb3DQEFDDAkBBD1YJCeuwCAuw/ktX9I +K9g9AgIIADAMBggqhkiG9w0CCQUAMB0GCWCGSAFlAwQBKgQQ7kmeI0FzRNLI2m54 +BMASEgRAWBi1BRsuBBVt2kWVTbz8tQa8K3lV+nNE+iRGlMaOhnF5o5Kx4mQnzE1q +ppIFNbWPGGr+xKHTU6fNfNnMecVXKA== +-----END ENCRYPTED PRIVATE KEY----- diff --git a/test/fixtures/keys/ml_kem_768_private_encrypted.der b/test/fixtures/keys/ml_kem_768_private_encrypted.der new file mode 100644 index 00000000000000..4f9097751c2249 Binary files /dev/null and b/test/fixtures/keys/ml_kem_768_private_encrypted.der differ diff --git a/test/fixtures/keys/ml_kem_768_private_encrypted.pem b/test/fixtures/keys/ml_kem_768_private_encrypted.pem new file mode 100644 index 00000000000000..0e3d54e75a3259 --- /dev/null +++ b/test/fixtures/keys/ml_kem_768_private_encrypted.pem @@ -0,0 +1,7 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIHDMF8GCSqGSIb3DQEFDTBSMDEGCSqGSIb3DQEFDDAkBBBSwnAxR1nLC5FZtJyu +lumDAgIIADAMBggqhkiG9w0CCQUAMB0GCWCGSAFlAwQBKgQQyBgMhkKPAMK6jaIc +YxhYcgRg3P97VHfT14YDN024txZznhhzC0mWGNpP6f1EV/mP/YttQp2JTXMKID4V +um3QuQes5my0oOIuiRl3gYIz/BDjKkqLagYBQmUcUUlURgaYJ67Yk3BZg6ULjXmq +EdLYqK5D +-----END ENCRYPTED PRIVATE KEY----- diff --git a/test/fixtures/webcrypto/supports-level-2.mjs b/test/fixtures/webcrypto/supports-level-2.mjs index 196f4588188b48..51a3ff10ac2188 100644 --- a/test/fixtures/webcrypto/supports-level-2.mjs +++ b/test/fixtures/webcrypto/supports-level-2.mjs @@ -74,7 +74,7 @@ export const vectors = { [false, { name: 'AES-CBC', length: 25 }], [true, { name: 'AES-GCM', length: 128 }], [false, { name: 'AES-GCM', length: 25 }], - [!boringSSL, { name: 'AES-KW', length: 128 }], + [true, { name: 'AES-KW', length: 128 }], [false, { name: 'AES-KW', length: 25 }], [true, { name: 'HMAC', hash: 'SHA-256' }], [true, { name: 'HMAC', hash: 'SHA-256', length: 256 }], @@ -166,7 +166,7 @@ export const vectors = { [true, 'AES-CTR'], [true, 'AES-CBC'], [true, 'AES-GCM'], - [!boringSSL, 'AES-KW'], + [true, 'AES-KW'], [true, { name: 'HMAC', hash: 'SHA-256' }], [true, { name: 'HMAC', hash: 'SHA-256', length: 256 }], [false, { name: 'HMAC', hash: 'SHA-256', length: 25 }], @@ -188,18 +188,18 @@ export const vectors = { [true, 'AES-CTR'], [true, 'AES-CBC'], [true, 'AES-GCM'], - [!boringSSL, 'AES-KW'], + [true, 'AES-KW'], [true, 'Ed25519'], [true, 'X25519'], ], 'wrapKey': [ [false, 'AES-KW'], - [!boringSSL, 'AES-KW', 'AES-CTR'], - [!boringSSL, 'AES-KW', 'HMAC'], + [true, 'AES-KW', 'AES-CTR'], + [true, 'AES-KW', 'HMAC'], ], 'unwrapKey': [ [false, 'AES-KW'], - [!boringSSL, 'AES-KW', 'AES-CTR'], + [true, 'AES-KW', 'AES-CTR'], ], 'unsupported operation': [ [false, ''], diff --git a/test/fixtures/webcrypto/supports-modern-algorithms.mjs b/test/fixtures/webcrypto/supports-modern-algorithms.mjs index 0572107e9f492e..2d370b8e21d3d5 100644 --- a/test/fixtures/webcrypto/supports-modern-algorithms.mjs +++ b/test/fixtures/webcrypto/supports-modern-algorithms.mjs @@ -2,14 +2,13 @@ import * as crypto from 'node:crypto' import { hasOpenSSL } from '../../common/crypto.js' -const pqc = hasOpenSSL(3, 5); +const boringSSL = process.features.openssl_is_boringssl; +const pqc = hasOpenSSL(3, 5) || boringSSL; const argon2 = hasOpenSSL(3, 2); const shake128 = crypto.getHashes().includes('shake128'); const shake256 = crypto.getHashes().includes('shake256'); -const chacha = crypto.getCiphers().includes('chacha20-poly1305'); const ocb = hasOpenSSL(3); const kmac = hasOpenSSL(3); -const boringSSL = process.features.openssl_is_boringssl; const { subtle } = globalThis.crypto; const X25519 = await subtle.generateKey('X25519', false, ['deriveBits', 'deriveKey']); @@ -75,10 +74,10 @@ export const vectors = { [pqc, 'ML-DSA-44'], [pqc, 'ML-DSA-65'], [pqc, 'ML-DSA-87'], - [pqc, 'ML-KEM-512'], + [pqc && !boringSSL, 'ML-KEM-512'], [pqc, 'ML-KEM-768'], [pqc, 'ML-KEM-1024'], - [chacha, 'ChaCha20-Poly1305'], + [true, 'ChaCha20-Poly1305'], [ocb, { name: 'AES-OCB', length: 128 }], [false, 'Argon2d'], [false, 'Argon2i'], @@ -96,10 +95,10 @@ export const vectors = { [pqc, 'ML-DSA-44'], [pqc, 'ML-DSA-65'], [pqc, 'ML-DSA-87'], - [pqc, 'ML-KEM-512'], + [pqc && !boringSSL, 'ML-KEM-512'], [pqc, 'ML-KEM-768'], [pqc, 'ML-KEM-1024'], - [chacha, 'ChaCha20-Poly1305'], + [true, 'ChaCha20-Poly1305'], [ocb, { name: 'AES-OCB', length: 128 }], [argon2, 'Argon2d'], [argon2, 'Argon2i'], @@ -117,10 +116,10 @@ export const vectors = { [pqc, 'ML-DSA-44'], [pqc, 'ML-DSA-65'], [pqc, 'ML-DSA-87'], - [pqc, 'ML-KEM-512'], + [pqc && !boringSSL, 'ML-KEM-512'], [pqc, 'ML-KEM-768'], [pqc, 'ML-KEM-1024'], - [chacha, 'ChaCha20-Poly1305'], + [true, 'ChaCha20-Poly1305'], [ocb, 'AES-OCB'], [false, 'Argon2d'], [false, 'Argon2i'], @@ -141,7 +140,7 @@ export const vectors = { [pqc, 'ML-DSA-44'], [pqc, 'ML-DSA-65'], [pqc, 'ML-DSA-87'], - [pqc, 'ML-KEM-512'], + [pqc && !boringSSL, 'ML-KEM-512'], [pqc, 'ML-KEM-768'], [pqc, 'ML-KEM-1024'], [false, 'AES-CTR'], @@ -186,9 +185,9 @@ export const vectors = { [false, { name: 'Argon2d', nonce: Buffer.alloc(8), parallelism: 16777215, memory: 8, passes: 1 }, 32], ], 'encrypt': [ - [chacha, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(12) }], + [true, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(12) }], [false, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(16) }], - [chacha, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(12), tagLength: 128 }], + [true, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(12), tagLength: 128 }], [false, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(12), tagLength: 64 }], [false, 'ChaCha20-Poly1305'], [ocb, { name: 'AES-OCB', iv: Buffer.alloc(15) }], @@ -200,37 +199,39 @@ export const vectors = { [false, 'AES-OCB'], ], 'encapsulateBits': [ - [pqc, 'ML-KEM-512'], + [pqc && !boringSSL, 'ML-KEM-512'], [pqc, 'ML-KEM-768'], [pqc, 'ML-KEM-1024'], ], 'encapsulateKey': [ - [pqc, 'ML-KEM-512', 'AES-KW'], - [pqc, 'ML-KEM-512', 'AES-GCM'], - [pqc, 'ML-KEM-512', 'AES-CTR'], - [pqc, 'ML-KEM-512', 'AES-CBC'], - [pqc, 'ML-KEM-512', 'ChaCha20-Poly1305'], - [pqc, 'ML-KEM-512', 'HKDF'], - [pqc, 'ML-KEM-512', 'PBKDF2'], - [pqc, 'ML-KEM-512', { name: 'HMAC', hash: 'SHA-256' }], - [pqc, 'ML-KEM-512', { name: 'HMAC', hash: 'SHA-256', length: 256 }], - [false, 'ML-KEM-512', { name: 'HMAC', hash: 'SHA-256', length: 128 }], + [pqc && !boringSSL, 'ML-KEM-512', 'AES-KW'], + [pqc, 'ML-KEM-768', 'AES-KW'], + [pqc, 'ML-KEM-768', 'AES-GCM'], + [pqc, 'ML-KEM-768', 'AES-CTR'], + [pqc, 'ML-KEM-768', 'AES-CBC'], + [pqc, 'ML-KEM-768', 'ChaCha20-Poly1305'], + [pqc, 'ML-KEM-768', 'HKDF'], + [pqc, 'ML-KEM-768', 'PBKDF2'], + [pqc, 'ML-KEM-768', { name: 'HMAC', hash: 'SHA-256' }], + [pqc, 'ML-KEM-768', { name: 'HMAC', hash: 'SHA-256', length: 256 }], + [false, 'ML-KEM-768', { name: 'HMAC', hash: 'SHA-256', length: 128 }], ], 'decapsulateBits': [ - [pqc, 'ML-KEM-512'], + [pqc && !boringSSL, 'ML-KEM-512'], [pqc, 'ML-KEM-768'], [pqc, 'ML-KEM-1024'], ], 'decapsulateKey': [ - [pqc, 'ML-KEM-512', 'AES-KW'], - [pqc, 'ML-KEM-512', 'AES-GCM'], - [pqc, 'ML-KEM-512', 'AES-CTR'], - [pqc, 'ML-KEM-512', 'AES-CBC'], - [pqc, 'ML-KEM-512', 'ChaCha20-Poly1305'], - [pqc, 'ML-KEM-512', 'HKDF'], - [pqc, 'ML-KEM-512', 'PBKDF2'], - [pqc, 'ML-KEM-512', { name: 'HMAC', hash: 'SHA-256' }], - [pqc, 'ML-KEM-512', { name: 'HMAC', hash: 'SHA-256', length: 256 }], - [false, 'ML-KEM-512', { name: 'HMAC', hash: 'SHA-256', length: 128 }], + [pqc && !boringSSL, 'ML-KEM-512', 'AES-KW'], + [pqc, 'ML-KEM-768', 'AES-KW'], + [pqc, 'ML-KEM-768', 'AES-GCM'], + [pqc, 'ML-KEM-768', 'AES-CTR'], + [pqc, 'ML-KEM-768', 'AES-CBC'], + [pqc, 'ML-KEM-768', 'ChaCha20-Poly1305'], + [pqc, 'ML-KEM-768', 'HKDF'], + [pqc, 'ML-KEM-768', 'PBKDF2'], + [pqc, 'ML-KEM-768', { name: 'HMAC', hash: 'SHA-256' }], + [pqc, 'ML-KEM-768', { name: 'HMAC', hash: 'SHA-256', length: 256 }], + [false, 'ML-KEM-768', { name: 'HMAC', hash: 'SHA-256', length: 128 }], ], }; diff --git a/test/parallel/test-crypto-boringssl-evp-list.js b/test/parallel/test-crypto-boringssl-evp-list.js new file mode 100644 index 00000000000000..3f142c24f28a7c --- /dev/null +++ b/test/parallel/test-crypto-boringssl-evp-list.js @@ -0,0 +1,31 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +if (!process.features.openssl_is_boringssl) + common.skip('BoringSSL-only test'); + +const assert = require('assert'); +const { getCiphers, getHashes } = require('crypto'); + +const ciphers = getCiphers(); +[ + 'aes-128-cbc', + 'aes-256-gcm', + 'des-ede', + 'des-ede-cbc', + 'des-ede3-cbc', + 'rc2-cbc', + 'rc4', +].forEach((cipher) => assert(ciphers.includes(cipher), cipher)); + +const hashes = getHashes(); +[ + 'md4', + 'md5', + 'sha1', + 'sha256', + 'sha512-256', +].forEach((hash) => assert(hashes.includes(hash), hash)); diff --git a/test/parallel/test-crypto-dh-stateless.js b/test/parallel/test-crypto-dh-stateless.js index 9e13c62595ef26..0ade828eb2342b 100644 --- a/test/parallel/test-crypto-dh-stateless.js +++ b/test/parallel/test-crypto-dh-stateless.js @@ -6,6 +6,7 @@ if (!common.hasCrypto) const assert = require('assert'); const crypto = require('crypto'); const { hasOpenSSL } = require('../common/crypto'); +const isBoringSSL = process.features.openssl_is_boringssl; // Error code for a key-type mismatch during (EC)DH. The underlying OpenSSL // error code varies by version, and in OpenSSL 4.0 by platform: some builds @@ -212,8 +213,28 @@ function testDHError(options, expected) { })); } -const alicePrivateKey = crypto.createPrivateKey({ - key: '-----BEGIN PRIVATE KEY-----\n' + +{ + const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', { + namedCurve: 'P-256', + }); + + assert.throws(() => crypto.diffieHellman({ privateKey }), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + }); + + assert.throws(() => crypto.diffieHellman({ publicKey }), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + }); +} + +if (isBoringSSL) { + common.printSkipMessage('Skipping finite-field DH KeyObject import and ' + + 'generation tests unsupported by BoringSSL'); +} else { + const alicePrivateKey = crypto.createPrivateKey({ + key: '-----BEGIN PRIVATE KEY-----\n' + 'MIIBoQIBADCB1QYJKoZIhvcNAQMBMIHHAoHBAP//////////yQ/aoiFowjTExmKL\n' + 'gNwc0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVt\n' + 'bVHCReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR\n' + @@ -224,10 +245,10 @@ const alicePrivateKey = crypto.createPrivateKey({ 'iIt9FmvFaaOVe2DupqSr6xzbf/zyON+WF5B5HNVOWXswgpgdUsCyygs98hKy/Xje\n' + 'TGzJUoWInW39t0YgMXenJrkS0m6wol8Rhxx81AGgELNV7EHZqg==\n' + '-----END PRIVATE KEY-----', - format: 'pem' -}); -const alicePublicKey = crypto.createPublicKey({ - key: '-----BEGIN PUBLIC KEY-----\n' + + format: 'pem' + }); + const alicePublicKey = crypto.createPublicKey({ + key: '-----BEGIN PUBLIC KEY-----\n' + 'MIIBnzCB1QYJKoZIhvcNAQMBMIHHAoHBAP//////////yQ/aoiFowjTExmKLgNwc\n' + '0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVtbVHC\n' + 'ReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR7ORb\n' + @@ -238,11 +259,11 @@ const alicePublicKey = crypto.createPublicKey({ 'rDEz8mjIlnvbWpKB9+uYmbjfVoc3leFvUBqfG2In2m23Md1swsPxr3n7g68H66JX\n' + 'iBJKZLQMqNdbY14G9rdKmhhTJrQjC+i7Q/wI8JPhOFzHIGA=\n' + '-----END PUBLIC KEY-----', - format: 'pem' -}); + format: 'pem' + }); -const bobPrivateKey = crypto.createPrivateKey({ - key: '-----BEGIN PRIVATE KEY-----\n' + + const bobPrivateKey = crypto.createPrivateKey({ + key: '-----BEGIN PRIVATE KEY-----\n' + 'MIIBoQIBADCB1QYJKoZIhvcNAQMBMIHHAoHBAP//////////yQ/aoiFowjTExmKL\n' + 'gNwc0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVt\n' + 'bVHCReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR\n' + @@ -253,11 +274,11 @@ const bobPrivateKey = crypto.createPrivateKey({ 'GagGtIy3dV5f4FA0B/2C97jQ1pO16ah8gSLQRKsNpTCw2rqsZusE0rK6RaYAef7H\n' + 'y/0tmLIsHxLIn+WK9CANqMbCWoP4I178BQaqhiOBkNyNZ0ndqA==\n' + '-----END PRIVATE KEY-----', - format: 'pem' -}); + format: 'pem' + }); -const bobPublicKey = crypto.createPublicKey({ - key: '-----BEGIN PUBLIC KEY-----\n' + + const bobPublicKey = crypto.createPublicKey({ + key: '-----BEGIN PUBLIC KEY-----\n' + 'MIIBoDCB1QYJKoZIhvcNAQMBMIHHAoHBAP//////////yQ/aoiFowjTExmKLgNwc\n' + '0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVtbVHC\n' + 'ReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR7ORb\n' + @@ -268,83 +289,83 @@ const bobPublicKey = crypto.createPublicKey({ 'QFKfjzNaJRNMFFd4f2Dn8MSB4yu1xpA1T2i0JSk24vS2H55jx24xhUYtfhT2LJgK\n' + 'JvnaODey/xtY4Kql10ZKf43Lw6gdQC3G8opC9OxVxt9oNR7Z\n' + '-----END PUBLIC KEY-----', - format: 'pem' -}); + format: 'pem' + }); -assert.throws(() => crypto.diffieHellman({ privateKey: alicePrivateKey }), { - name: 'TypeError', - code: 'ERR_INVALID_ARG_TYPE', -}); + assert.throws(() => crypto.diffieHellman({ privateKey: alicePrivateKey }), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + }); -assert.throws(() => crypto.diffieHellman({ publicKey: alicePublicKey }), { - name: 'TypeError', - code: 'ERR_INVALID_ARG_TYPE', -}); + assert.throws(() => crypto.diffieHellman({ publicKey: alicePublicKey }), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + }); -const privateKey = Buffer.from( - '487CD880159D835FD0A8DBA9848898317283DB07E822741B344AD397BA84CDDD3920A51588' + + const privateKey = Buffer.from( + '487CD880159D835FD0A8DBA9848898317283DB07E822741B344AD397BA84CDDD3920A51588' + 'B891B03B3EBEF3C9F767D921FAC1294D4B5E09CABB6D1DE3EB4527989754FEB64D007EBBDA' + '2E6C8CE7A17EF41DE3C2DFE7CEAAF963199F55D5DBD9A415E77552FE69B7A41D87888B7D16' + '6BC569A3957B60EEA6A4ABEB1CDB7FFCF238DF961790791CD54E597B3082981D52C0B2CA0B' + '3DF212B2FD78DE4C6CC95285889D6DFDB746203177A726B912D26EB0A25F11871C7CD401A0' + '10B355EC41D9AA', 'hex'); -const publicKey = Buffer.from( - '8b6ea8abccff18d4819b7ce280db7b480edc02b5016d3c4835af622d85a9e9bc6bbc22b00d' + + const publicKey = Buffer.from( + '8b6ea8abccff18d4819b7ce280db7b480edc02b5016d3c4835af622d85a9e9bc6bbc22b00d' + '0c0848ddfafd0530f275007bc691c8cb74a189fecbabd63f0e4e94ef932eb51e94c5456800' + 'c4ce8628987d335466f4b16e1a04df21682d266eb3edf50b21802be3af58443c49da40529f' + '8f335a25134c1457787f60e7f0c481e32bb5c690354f68b4252936e2f4b61f9e63c76e3185' + '462d7e14f62c980a26f9da3837b2ff1b58e0aaa5d7464a7f8dcbc3a81d402dc6f28a42f4ec' + '55c6df68351ed9', 'hex'); -const group = crypto.getDiffieHellman('modp5'); -const dh = crypto.createDiffieHellman(group.getPrime(), group.getGenerator()); -dh.setPrivateKey(privateKey); + const group = crypto.getDiffieHellman('modp5'); + const dh = crypto.createDiffieHellman(group.getPrime(), group.getGenerator()); + dh.setPrivateKey(privateKey); -// Test simple Diffie-Hellman, no curves involved. -test({ publicKey: alicePublicKey, privateKey: alicePrivateKey }, - { publicKey: bobPublicKey, privateKey: bobPrivateKey }, - dh.computeSecret(publicKey)); + // Test simple Diffie-Hellman, no curves involved. + test({ publicKey: alicePublicKey, privateKey: alicePrivateKey }, + { publicKey: bobPublicKey, privateKey: bobPrivateKey }, + dh.computeSecret(publicKey)); -test(crypto.generateKeyPairSync('dh', { group: 'modp5' }), - crypto.generateKeyPairSync('dh', { group: 'modp5' })); + test(crypto.generateKeyPairSync('dh', { group: 'modp5' }), + crypto.generateKeyPairSync('dh', { group: 'modp5' })); -test(crypto.generateKeyPairSync('dh', { group: 'modp5' }), - crypto.generateKeyPairSync('dh', { prime: group.getPrime() })); + test(crypto.generateKeyPairSync('dh', { group: 'modp5' }), + crypto.generateKeyPairSync('dh', { prime: group.getPrime() })); -// DH parameter mismatch tests -{ - const list = [ + // DH parameter mismatch tests + { + const list = [ // Same generator, but different primes. - [{ group: 'modp5' }, { group: 'modp18' }]]; + [{ group: 'modp5' }, { group: 'modp18' }]]; - // TODO(danbev): Take a closer look if there should be a check in OpenSSL3 - // when the dh parameters differ. - if (!hasOpenSSL(3)) { + // TODO(danbev): Take a closer look if there should be a check in OpenSSL3 + // when the dh parameters differ. + if (!hasOpenSSL(3)) { // Same primes, but different generator. - list.push([{ group: 'modp5' }, { prime: group.getPrime(), generator: 5 }]); - // Same generator, but different primes. - list.push([{ primeLength: 1024 }, { primeLength: 1024 }]); - } + list.push([{ group: 'modp5' }, { prime: group.getPrime(), generator: 5 }]); + // Same generator, but different primes. + list.push([{ primeLength: 1024 }, { primeLength: 1024 }]); + } - for (const [params1, params2] of list) { - const options = { - privateKey: crypto.generateKeyPairSync('dh', params1).privateKey, - publicKey: crypto.generateKeyPairSync('dh', params2).publicKey, - }; - testDHError(options, { - name: 'Error', - code: hasOpenSSL(3) ? - 'ERR_OSSL_MISMATCHING_DOMAIN_PARAMETERS' : - 'ERR_OSSL_EVP_DIFFERENT_PARAMETERS' - }); + for (const [params1, params2] of list) { + const options = { + privateKey: crypto.generateKeyPairSync('dh', params1).privateKey, + publicKey: crypto.generateKeyPairSync('dh', params2).publicKey, + }; + testDHError(options, { + name: 'Error', + code: hasOpenSSL(3) ? + 'ERR_OSSL_MISMATCHING_DOMAIN_PARAMETERS' : + 'ERR_OSSL_EVP_DIFFERENT_PARAMETERS' + }); + } } -} -// This key combination will result in an unusually short secret, and should -// not cause an assertion failure. -{ - const shortPrivateKey = crypto.createPrivateKey({ - key: '-----BEGIN PRIVATE KEY-----\n' + + // This key combination will result in an unusually short secret, and should + // not cause an assertion failure. + { + const shortPrivateKey = crypto.createPrivateKey({ + key: '-----BEGIN PRIVATE KEY-----\n' + 'MIIBoQIBADCB1QYJKoZIhvcNAQMBMIHHAoHBAP//////////yQ/aoiFowjTExmKL\n' + 'gNwc0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVt\n' + 'bVHCReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR\n' + @@ -355,9 +376,9 @@ test(crypto.generateKeyPairSync('dh', { group: 'modp5' }), 'RQD0QogW7ejSwMG8hCYibfrvMm0b5PHlwimISyEKh7VtDQ1frYN/Wr9ZbiV+FePJ\n' + '2j6RUKYNj1Pv+B4zdMgiLLjILAs8WUfbHciU21KSJh1izVQaUQ==\n' + '-----END PRIVATE KEY-----' - }); - const shortPublicKey = crypto.createPublicKey({ - key: '-----BEGIN PUBLIC KEY-----\n' + + }); + const shortPublicKey = crypto.createPublicKey({ + key: '-----BEGIN PUBLIC KEY-----\n' + 'MIIBoDCB1QYJKoZIhvcNAQMBMIHHAoHBAP//////////yQ/aoiFowjTExmKLgNwc\n' + '0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVtbVHC\n' + 'ReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR7ORb\n' + @@ -368,19 +389,20 @@ test(crypto.generateKeyPairSync('dh', { group: 'modp5' }), 'taGX4mP3247golVx2DS4viDYs7UtaMdx03dWaP6y5StNUZQlgCIUzL7MYpC16V5y\n' + 'KkFrE+Kp/Z77gEjivaG6YuxVj4GPLxJYbNFVTel42oSVeKuq\n' + '-----END PUBLIC KEY-----', - format: 'pem' - }); + format: 'pem' + }); - testDH({ publicKey: shortPublicKey, privateKey: shortPrivateKey }, - Buffer.from( - '0099d0fa242af5db9ea7330e23937a27db041f79c581500fc7f9976' + + testDH({ publicKey: shortPublicKey, privateKey: shortPrivateKey }, + Buffer.from( + '0099d0fa242af5db9ea7330e23937a27db041f79c581500fc7f9976' + '554d59d5b9ced934778d72e19a1fefc81e9d981013198748c0b5c6c' + '762985eec687dc5bec5c9367b05837daee9d0bcc29024ed7f3abba1' + '2794b65a745117fb0d87bc5b1b2b68c296c3f686cc29e450e4e1239' + - '21f56a5733fe58aabf71f14582954059c2185d342b9b0fa10c2598a' + - '5426c2baee7f9a686fc1e16cd4757c852bf7225a2732250548efe28' + - 'debc26f1acdec51efe23d20786a6f8a14d360803bbc71972e87fd3', - 'hex')); + '21f56a5733fe58aabf71f14582954059c2185d342b9b0fa10c2598a' + + '5426c2baee7f9a686fc1e16cd4757c852bf7225a2732250548efe28' + + 'debc26f1acdec51efe23d20786a6f8a14d360803bbc71972e87fd3', + 'hex')); + } } // Test ECDH. @@ -401,20 +423,25 @@ test(crypto.generateKeyPairSync('ec', { namedCurve: 'P-256' }), }); } -test(crypto.generateKeyPairSync('x448'), - crypto.generateKeyPairSync('x448')); +if (isBoringSSL) { + common.printSkipMessage('Skipping x448 diffieHellman test cases ' + + 'unsupported by BoringSSL'); +} else { + test(crypto.generateKeyPairSync('x448'), + crypto.generateKeyPairSync('x448')); + + { + const options = { + privateKey: crypto.generateKeyPairSync('x448').privateKey, + publicKey: crypto.generateKeyPairSync('x25519').publicKey, + }; + testDHError(options, { code: keyTypeMismatchCode }); + } +} test(crypto.generateKeyPairSync('x25519'), crypto.generateKeyPairSync('x25519')); -{ - const options = { - privateKey: crypto.generateKeyPairSync('x448').privateKey, - publicKey: crypto.generateKeyPairSync('x25519').publicKey, - }; - testDHError(options, { code: keyTypeMismatchCode }); -} - // Test all key encoding formats for (const { privateKey: alicePriv, publicKey: bobPub } of [ crypto.generateKeyPairSync('ec', { namedCurve: 'P-256' }), @@ -514,7 +541,7 @@ for (const { privateKey: alicePriv, publicKey: bobPub } of [ { const ec256 = crypto.generateKeyPairSync('ec', { namedCurve: 'P-256' }); const ec384 = crypto.generateKeyPairSync('ec', { namedCurve: 'P-384' }); - const x448 = crypto.generateKeyPairSync('x448'); + const x448 = isBoringSSL ? null : crypto.generateKeyPairSync('x448'); const x25519 = crypto.generateKeyPairSync('x25519'); const ed25519 = crypto.generateKeyPairSync('ed25519'); @@ -564,18 +591,21 @@ for (const { privateKey: alicePriv, publicKey: bobPub } of [ /^ERR_OSSL_EVP_(OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE|INTERNAL_ERROR)$/ : 'ERR_OSSL_EVP_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE' }); - // Incompatible key types (x448 + x25519) - testDHError({ - privateKey: privKey(x448.privateKey), - publicKey: pubKey(x25519.publicKey), - }, { code: keyTypeMismatchCode }); + if (!isBoringSSL) { + // Incompatible key types (x448 + x25519) + testDHError({ + privateKey: privKey(x448.privateKey), + publicKey: pubKey(x25519.publicKey), + }, { code: keyTypeMismatchCode }); + } // Zero x25519 public key testDHError({ privateKey: privKey(x25519.privateKey), publicKey: pubKey(zeroX25519PublicKey), - }, hasOpenSSL(3) ? - { code: 'ERR_OSSL_FAILED_DURING_DERIVATION' } : - { message: /Deriving bits failed/ }); + }, isBoringSSL ? { code: 'ERR_OSSL_EVP_INVALID_PEER_KEY' } : + hasOpenSSL(3) ? + { code: 'ERR_OSSL_FAILED_DURING_DERIVATION' } : + { message: /Deriving bits failed/ }); } } diff --git a/test/parallel/test-crypto-encap-decap.js b/test/parallel/test-crypto-encap-decap.js index 38e24a7341713a..f2259194a9e15d 100644 --- a/test/parallel/test-crypto-encap-decap.js +++ b/test/parallel/test-crypto-encap-decap.js @@ -9,7 +9,9 @@ const fixtures = require('../common/fixtures'); const { hasOpenSSL } = require('../common/crypto'); const { promisify } = require('util'); -if (!hasOpenSSL(3)) { +const isBoringSSL = process.features.openssl_is_boringssl; + +if (!hasOpenSSL(3) && !isBoringSSL) { assert.throws(() => crypto.encapsulate(), { code: 'ERR_CRYPTO_KEM_NOT_SUPPORTED' }); return; } @@ -79,25 +81,25 @@ const keys = { raw: true, }, 'ml-kem-512': { - supported: hasOpenSSL(3, 5), + supported: hasOpenSSL(3, 5), // BoringSSL does not support ML-KEM-512 publicKey: fixtures.readKey('ml_kem_512_public.pem', 'ascii'), - privateKey: fixtures.readKey('ml_kem_512_private.pem', 'ascii'), + privateKey: fixtures.readKey('ml_kem_512_private_seed_only.pem', 'ascii'), sharedSecretLength: 32, ciphertextLength: 768, raw: true, }, 'ml-kem-768': { - supported: hasOpenSSL(3, 5), + supported: hasOpenSSL(3, 5) || isBoringSSL, publicKey: fixtures.readKey('ml_kem_768_public.pem', 'ascii'), - privateKey: fixtures.readKey('ml_kem_768_private.pem', 'ascii'), + privateKey: fixtures.readKey('ml_kem_768_private_seed_only.pem', 'ascii'), sharedSecretLength: 32, ciphertextLength: 1088, raw: true, }, 'ml-kem-1024': { - supported: hasOpenSSL(3, 5), + supported: hasOpenSSL(3, 5) || isBoringSSL, publicKey: fixtures.readKey('ml_kem_1024_public.pem', 'ascii'), - privateKey: fixtures.readKey('ml_kem_1024_private.pem', 'ascii'), + privateKey: fixtures.readKey('ml_kem_1024_private_seed_only.pem', 'ascii'), sharedSecretLength: 32, ciphertextLength: 1568, raw: true, @@ -109,7 +111,7 @@ for (const [name, { }] of Object.entries(keys)) { if (!supported) { assert.throws(() => crypto.encapsulate(publicKey), - { code: /ERR_OSSL_EVP_DECODE_ERROR|ERR_CRYPTO_OPERATION_FAILED/ }); + { code: /ERR_OSSL_EVP_DECODE_ERROR|ERR_OSSL_EVP_UNSUPPORTED_ALGORITHM|ERR_CRYPTO_OPERATION_FAILED/ }); continue; } @@ -211,7 +213,7 @@ for (const [name, { } else if (name.startsWith('p-')) { wrongPrivateKey = name === 'p-256' ? keys['p-384'].privateKey : keys['p-256'].privateKey; } else if (name.startsWith('ml-')) { - wrongPrivateKey = name === 'ml-kem-512' ? keys['ml-kem-768'].privateKey : keys['ml-kem-512'].privateKey; + wrongPrivateKey = name === 'ml-kem-768' ? keys['ml-kem-1024'].privateKey : keys['ml-kem-768'].privateKey; } else { wrongPrivateKey = keys.x25519.privateKey; } diff --git a/test/parallel/test-crypto-key-objects-raw.js b/test/parallel/test-crypto-key-objects-raw.js index 9ef4bd3b9004d1..024d5f6f199ffc 100644 --- a/test/parallel/test-crypto-key-objects-raw.js +++ b/test/parallel/test-crypto-key-objects-raw.js @@ -59,6 +59,47 @@ const { hasOpenSSL } = require('../common/crypto'); } } +// Raw public keys cannot be imported as private keys. +{ + const rawPublicKeys = [ + ['ec', 'ec_p256_public.pem', { namedCurve: 'P-256' }], + ['ed25519', 'ed25519_public.pem'], + ['x25519', 'x25519_public.pem'], + ]; + + if (!process.features.openssl_is_boringssl) { + rawPublicKeys.push( + ['ed448', 'ed448_public.pem'], + ['x448', 'x448_public.pem'], + ); + } else { + common.printSkipMessage('Skipping unsupported ed448/x448 test cases'); + } + + if (hasOpenSSL(3, 5) || process.features.openssl_is_boringssl) { + rawPublicKeys.push( + ['ml-dsa-44', 'ml_dsa_44_public.pem'], + ['ml-kem-768', 'ml_kem_768_public.pem'], + ); + } + + if (hasOpenSSL(3, 5)) { + rawPublicKeys.push( + ['slh-dsa-sha2-128f', 'slh_dsa_sha2_128f_public.pem'], + ); + } + + for (const [asymmetricKeyType, fixture, options = {}] of rawPublicKeys) { + const publicKey = crypto.createPublicKey(fixtures.readKey(fixture, 'ascii')); + assert.throws(() => crypto.createPrivateKey({ + key: publicKey.export({ format: 'raw-public' }), + format: 'raw-public', + asymmetricKeyType, + ...options, + }), { code: 'ERR_INVALID_ARG_VALUE' }); + } +} + // Raw seed imports do not support strings. if (hasOpenSSL(3, 5)) { const privKeyObj = crypto.createPrivateKey( @@ -113,7 +154,11 @@ if (hasOpenSSL(3, 5)) { assert.throws(() => privKeyObj.export({ format: 'raw-private' }), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); - for (const format of ['raw-public', 'raw-private', 'raw-seed']) { + assert.throws(() => crypto.createPrivateKey({ + key: Buffer.alloc(32), format: 'raw-public', asymmetricKeyType: 'dh', + }), { code: 'ERR_INVALID_ARG_VALUE' }); + + for (const format of ['raw-private', 'raw-seed']) { assert.throws(() => crypto.createPrivateKey({ key: Buffer.alloc(32), format, asymmetricKeyType: 'dh', }), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); @@ -125,15 +170,22 @@ if (hasOpenSSL(3, 5)) { // PQC import throws when PQC is not supported if (!hasOpenSSL(3, 5)) { - for (const asymmetricKeyType of [ - 'ml-dsa-44', 'ml-dsa-65', 'ml-dsa-87', - 'ml-kem-512', 'ml-kem-768', 'ml-kem-1024', - 'slh-dsa-sha2-128f', 'slh-dsa-shake-128f', - ]) { + const unsupported = process.features.openssl_is_boringssl ? + // BoringSSL supports ML-DSA and ML-KEM-{768,1024}, but not ML-KEM-512 or SLH-DSA. + ['ml-kem-512', 'slh-dsa-sha2-128f', 'slh-dsa-shake-128f'] : + [ + 'ml-dsa-44', 'ml-dsa-65', 'ml-dsa-87', + 'ml-kem-512', 'ml-kem-768', 'ml-kem-1024', + 'slh-dsa-sha2-128f', 'slh-dsa-shake-128f', + ]; + for (const asymmetricKeyType of unsupported) { for (const format of ['raw-public', 'raw-private', 'raw-seed']) { assert.throws(() => crypto.createPublicKey({ key: Buffer.alloc(32), format, asymmetricKeyType, - }), { code: 'ERR_INVALID_ARG_VALUE' }); + }), { + code: 'ERR_INVALID_ARG_VALUE', + message: /Invalid asymmetricKeyType|Unsupported key type/ + }); } } } @@ -179,27 +231,27 @@ if (!hasOpenSSL(3, 5)) { }), { code: 'ERR_INVALID_ARG_VALUE' }); } -// ML-KEM: -768 and -512 public keys cannot be imported as the other type -if (hasOpenSSL(3, 5)) { - const mlKem512Pub = crypto.createPublicKey( - fixtures.readKey('ml_kem_512_public.pem', 'ascii')); +// ML-KEM: public keys of different type cannot be imported as the other type +if (hasOpenSSL(3, 5) || process.features.openssl_is_boringssl) { const mlKem768Pub = crypto.createPublicKey( fixtures.readKey('ml_kem_768_public.pem', 'ascii')); + const mlKem1024Pub = crypto.createPublicKey( + fixtures.readKey('ml_kem_1024_public.pem', 'ascii')); - const mlKem512RawPub = mlKem512Pub.export({ format: 'raw-public' }); const mlKem768RawPub = mlKem768Pub.export({ format: 'raw-public' }); + const mlKem1024RawPub = mlKem1024Pub.export({ format: 'raw-public' }); assert.throws(() => crypto.createPublicKey({ - key: mlKem512RawPub, format: 'raw-public', asymmetricKeyType: 'ml-kem-768', + key: mlKem768RawPub, format: 'raw-public', asymmetricKeyType: 'ml-kem-1024', }), { code: 'ERR_INVALID_ARG_VALUE' }); assert.throws(() => crypto.createPublicKey({ - key: mlKem768RawPub, format: 'raw-public', asymmetricKeyType: 'ml-kem-512', + key: mlKem1024RawPub, format: 'raw-public', asymmetricKeyType: 'ml-kem-768', }), { code: 'ERR_INVALID_ARG_VALUE' }); } // ML-DSA: -44 and -65 public keys cannot be imported as the other type -if (hasOpenSSL(3, 5)) { +if (hasOpenSSL(3, 5) || process.features.openssl_is_boringssl) { const mlDsa44Pub = crypto.createPublicKey( fixtures.readKey('ml_dsa_44_public.pem', 'ascii')); const mlDsa65Pub = crypto.createPublicKey( @@ -274,6 +326,12 @@ if (hasOpenSSL(3, 5)) { fixtures.readKey('ec_p256_private.pem', 'ascii')); assert.throws(() => ecPriv.export({ format: 'raw-seed' }), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + assert.throws(() => crypto.createPrivateKey({ + key: ecPriv.export({ format: 'raw-private' }), + format: 'raw-seed', + asymmetricKeyType: 'ec', + namedCurve: 'P-256', + }), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); if (process.features.openssl_is_boringssl) { common.printSkipMessage('Skipping unsupported ed448/x448 test cases'); @@ -285,6 +343,11 @@ if (hasOpenSSL(3, 5)) { fixtures.readKey(`${type}_private.pem`, 'ascii')); assert.throws(() => priv.export({ format: 'raw-seed' }), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + assert.throws(() => crypto.createPrivateKey({ + key: priv.export({ format: 'raw-private' }), + format: 'raw-seed', + asymmetricKeyType: type, + }), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); } if (hasOpenSSL(3, 5)) { @@ -292,16 +355,26 @@ if (hasOpenSSL(3, 5)) { fixtures.readKey('slh_dsa_sha2_128f_private.pem', 'ascii')); assert.throws(() => slhPriv.export({ format: 'raw-seed' }), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + assert.throws(() => crypto.createPrivateKey({ + key: slhPriv.export({ format: 'raw-private' }), + format: 'raw-seed', + asymmetricKeyType: 'slh-dsa-sha2-128f', + }), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); } } // raw-private cannot be used for ml-kem and ml-dsa -if (hasOpenSSL(3, 5)) { - for (const type of ['ml-kem-512', 'ml-dsa-44']) { +if (hasOpenSSL(3, 5) || process.features.openssl_is_boringssl) { + for (const type of ['ml-kem-768', 'ml-dsa-44']) { const priv = crypto.createPrivateKey( - fixtures.readKey(`${type.replaceAll('-', '_')}_private.pem`, 'ascii')); + fixtures.readKey(`${type.replaceAll('-', '_')}_private_seed_only.pem`, 'ascii')); assert.throws(() => priv.export({ format: 'raw-private' }), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + assert.throws(() => crypto.createPrivateKey({ + key: priv.export({ format: 'raw-seed' }), + format: 'raw-private', + asymmetricKeyType: type, + }), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); } } @@ -399,9 +472,9 @@ if (hasOpenSSL(3, 5)) { { code: 'ERR_INVALID_ARG_VALUE' }); // PQC raw-seed -> createPublicKey - if (hasOpenSSL(3, 5)) { + if (hasOpenSSL(3, 5) || process.features.openssl_is_boringssl) { const mlDsaPriv = crypto.createPrivateKey( - fixtures.readKey('ml_dsa_44_private.pem', 'ascii')); + fixtures.readKey('ml_dsa_44_private_seed_only.pem', 'ascii')); const mlDsaPub = crypto.createPublicKey( fixtures.readKey('ml_dsa_44_public.pem', 'ascii')); const mlDsaRawSeed = mlDsaPriv.export({ format: 'raw-seed' }); diff --git a/test/parallel/test-crypto-key-objects-to-crypto-key.js b/test/parallel/test-crypto-key-objects-to-crypto-key.js index 54449329cb551a..5c3148647324b0 100644 --- a/test/parallel/test-crypto-key-objects-to-crypto-key.js +++ b/test/parallel/test-crypto-key-objects-to-crypto-key.js @@ -26,15 +26,10 @@ function assertCryptoKey(cryptoKey, keyObject, algorithm, extractable, usages) { { for (const length of [128, 192, 256]) { const key = createSecretKey(randomBytes(length >> 3)); - let algorithms = ['AES-CTR', 'AES-CBC', 'AES-GCM', 'AES-KW']; + const algorithms = ['AES-CTR', 'AES-CBC', 'AES-GCM', 'AES-KW']; if (length === 256) algorithms.push('ChaCha20-Poly1305'); - if (process.features.openssl_is_boringssl) { - algorithms = algorithms.filter((a) => a !== 'AES-KW' && a !== 'ChaCha20-Poly1305'); - common.printSkipMessage('Skipping unsupported AES-KW/ChaCha20-Poly1305 test cases'); - } - for (const algorithm of algorithms) { const usages = algorithm === 'AES-KW' ? ['wrapKey', 'unwrapKey'] : ['encrypt', 'decrypt']; for (const extractable of [true, false]) { @@ -200,7 +195,7 @@ function assertCryptoKey(cryptoKey, keyObject, algorithm, extractable, usages) { } } -if (hasOpenSSL(3, 5)) { +if (hasOpenSSL(3, 5) || process.features.openssl_is_boringssl) { for (const name of ['ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87']) { const { publicKey, privateKey } = generateKeyPairSync(name.toLowerCase()); assert.throws(() => { diff --git a/test/parallel/test-crypto-keygen-raw.js b/test/parallel/test-crypto-keygen-raw.js index 5b7abe3f72d9dd..e55c3f10eed8e3 100644 --- a/test/parallel/test-crypto-keygen-raw.js +++ b/test/parallel/test-crypto-keygen-raw.js @@ -205,7 +205,7 @@ if (!process.features.openssl_is_boringssl) { } // PQC key types -if (hasOpenSSL(3, 5)) { +if (hasOpenSSL(3, 5) || process.features.openssl_is_boringssl) { // Test raw encoding for ML-DSA key types (raw-public + raw-seed only). { for (const type of ['ml-dsa-44', 'ml-dsa-65', 'ml-dsa-87']) { @@ -232,6 +232,10 @@ if (hasOpenSSL(3, 5)) { // Test raw encoding for ML-KEM key types (raw-public + raw-seed only). { for (const type of ['ml-kem-512', 'ml-kem-768', 'ml-kem-1024']) { + if (process.features.openssl_is_boringssl && type === 'ml-kem-512') { + common.printSkipMessage(`Skipping unsupported ${type} test case`); + continue; + } const { publicKey, privateKey } = generateKeyPairSync(type, { publicKeyEncoding: { format: 'raw-public' }, privateKeyEncoding: { format: 'raw-seed' }, @@ -246,7 +250,7 @@ if (hasOpenSSL(3, 5)) { // Test error: raw-private with ML-KEM (not supported). { - assert.throws(() => generateKeyPairSync('ml-kem-512', { + assert.throws(() => generateKeyPairSync('ml-kem-768', { publicKeyEncoding: { format: 'raw-public' }, privateKeyEncoding: { format: 'raw-private' }, }), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); @@ -255,6 +259,10 @@ if (hasOpenSSL(3, 5)) { // Test raw encoding for SLH-DSA key types. { for (const type of ['slh-dsa-sha2-128f', 'slh-dsa-shake-128f']) { + if (process.features.openssl_is_boringssl) { + common.printSkipMessage(`Skipping unsupported ${type} test case`); + continue; + } const { publicKey, privateKey } = generateKeyPairSync(type, { publicKeyEncoding: { format: 'raw-public' }, privateKeyEncoding: { format: 'raw-private' }, @@ -266,11 +274,13 @@ if (hasOpenSSL(3, 5)) { } // Test error: raw-seed with SLH-DSA (not supported). - { + if (!process.features.openssl_is_boringssl) { assert.throws(() => generateKeyPairSync('slh-dsa-sha2-128f', { publicKeyEncoding: { format: 'raw-public' }, privateKeyEncoding: { format: 'raw-seed' }, }), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + } else { + common.printSkipMessage('Skipping unsupported slh-dsa test case'); } // Test async generateKeyPair with raw encoding for PQC types. diff --git a/test/parallel/test-crypto-keygen.js b/test/parallel/test-crypto-keygen.js index 7911520af34481..751029e1921edb 100644 --- a/test/parallel/test-crypto-keygen.js +++ b/test/parallel/test-crypto-keygen.js @@ -15,6 +15,7 @@ const { const { inspect } = require('util'); const { hasOpenSSL3 } = require('../common/crypto'); +const isBoringSSL = process.features.openssl_is_boringssl; // Test invalid parameter encoding. { @@ -361,13 +362,24 @@ const { hasOpenSSL3 } = require('../common/crypto'); // Test invalid exponents. (caught by OpenSSL) for (const publicExponent of [1, 1 + 0x10001]) { - generateKeyPair('rsa', { - modulusLength: 4096, - publicExponent - }, common.mustCall((err) => { - assert.strictEqual(err.name, 'Error'); - assert.match(err.message, hasOpenSSL3 ? /exponent/ : /bad e value/); - })); + if (isBoringSSL) { + assert.throws(() => generateKeyPair('rsa', { + modulusLength: 4096, + publicExponent + }, common.mustNotCall()), { + name: 'RangeError', + code: 'ERR_OUT_OF_RANGE', + message: 'publicExponent is invalid', + }); + } else { + generateKeyPair('rsa', { + modulusLength: 4096, + publicExponent + }, common.mustCall((err) => { + assert.strictEqual(err.name, 'Error'); + assert.match(err.message, hasOpenSSL3 ? /exponent/ : /bad e value/); + })); + } } } @@ -494,16 +506,21 @@ const { hasOpenSSL3 } = require('../common/crypto'); }); })); - generateKeyPair('ec', { - namedCurve: 'secp256k1', - }, common.mustSucceed((publicKey, privateKey) => { - assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { - namedCurve: 'secp256k1' - }); - assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { - namedCurve: 'secp256k1' - }); - })); + if (isBoringSSL) { + common.printSkipMessage('Skipping secp256k1 keygen test case ' + + 'unsupported by BoringSSL'); + } else { + generateKeyPair('ec', { + namedCurve: 'secp256k1', + }, common.mustSucceed((publicKey, privateKey) => { + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { + namedCurve: 'secp256k1' + }); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { + namedCurve: 'secp256k1' + }); + })); + } } { diff --git a/test/parallel/test-crypto-pqc-encrypted-pkcs8.js b/test/parallel/test-crypto-pqc-encrypted-pkcs8.js new file mode 100644 index 00000000000000..b4a1b586d21d10 --- /dev/null +++ b/test/parallel/test-crypto-pqc-encrypted-pkcs8.js @@ -0,0 +1,134 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const { hasOpenSSL } = require('../common/crypto'); + +if (!hasOpenSSL(3, 5) && !process.features.openssl_is_boringssl) + common.skip('requires OpenSSL >= 3.5 or BoringSSL'); + +const assert = require('assert'); +const { + createPrivateKey, + generateKeyPairSync, + getCiphers, +} = require('crypto'); + +const algorithms = new Set([ + 'ml-dsa-44', 'ml-dsa-65', 'ml-dsa-87', + 'ml-kem-512', 'ml-kem-768', 'ml-kem-1024', +]); +// BoringSSL does not support ML-KEM-512. +if (process.features.openssl_is_boringssl) { + algorithms.delete('ml-kem-512'); +} + +// Exercise each CBC cipher that PBES2 may use. This covers multiple +// EVP_CIPHER_key_length values (16 / 24 / 32) and, for variable-key +// ciphers like RC2, the optional PBKDF2 keyLength INTEGER branch in +// the EncryptedPrivateKeyInfo parser. +const availableCiphers = new Set(getCiphers()); +const ciphers = [ + 'aes-128-cbc', 'aes-192-cbc', 'aes-256-cbc', + 'des-ede3-cbc', 'rc2-cbc', +].filter((c) => availableCiphers.has(c)); + +const passphrase = 'top secret'; +const wrongPassphraseError = + /bad decrypt|DECRYPTION_FAILED|BAD_DECRYPT|bad password|DECODE[ _]ERROR/i; +// A wrong passphrase usually fails during cipher finalization, but CBC output +// can have valid padding by chance. OpenSSL then parses the bad plaintext as +// PKCS#8 and may report ASN.1 or decoder errors from the same failed import. +function assertWrongPassphrase(fn) { + assert.throws(fn, (err) => wrongPassphraseError.test(err.message) || + err.code?.startsWith('ERR_OSSL_ASN1_') || + err.code === 'ERR_OSSL_UNSUPPORTED'); +} + +for (const asymmetricKeyType of algorithms) { + const { privateKey } = generateKeyPairSync(asymmetricKeyType); + assert.strictEqual(privateKey.asymmetricKeyType, asymmetricKeyType); + + const plainDer = privateKey.export({ type: 'pkcs8', format: 'der' }); + + for (const cipher of ciphers) { + for (const format of ['pem', 'der']) { + const encrypted = privateKey.export({ + type: 'pkcs8', + format, + cipher, + passphrase, + }); + + const imported = createPrivateKey({ + key: encrypted, + format, + type: 'pkcs8', + passphrase, + }); + assert.strictEqual(imported.type, 'private'); + assert.strictEqual(imported.asymmetricKeyType, asymmetricKeyType); + assert.deepStrictEqual( + imported.export({ type: 'pkcs8', format: 'der' }), + plainDer, + ); + + assertWrongPassphrase(() => createPrivateKey({ + key: encrypted, + format, + type: 'pkcs8', + passphrase: 'wrong', + })); + } + } +} + +// Cross-implementation compatibility: load encrypted PKCS#8 fixtures that +// were generated by OpenSSL's `openssl pkcs8` from the seed-only PQC +// PrivateKeyInfo fixtures. The inner seed-only form is portable across +// OpenSSL (>=3.5) and BoringSSL, and the matching JWK fixture provides the +// canonical key material used to derive the expected PKCS#8 bytes. +const fixtures = require('../common/fixtures'); +const fixtureCases = [ + { alg: 'ml-dsa-44', jwkFile: 'ml-dsa-44.json', + encBase: 'ml_dsa_44_private_encrypted' }, + { alg: 'ml-kem-768', jwkFile: 'ml-kem-768.json', + encBase: 'ml_kem_768_private_encrypted' }, +]; + +for (const { alg, jwkFile, encBase } of fixtureCases) { + const jwkKey = createPrivateKey({ + key: JSON.parse(fixtures.readKey(jwkFile, 'utf8')), + format: 'jwk', + }); + assert.strictEqual(jwkKey.asymmetricKeyType, alg); + const expectedDer = jwkKey.export({ type: 'pkcs8', format: 'der' }); + + for (const format of ['pem', 'der']) { + const encryptedFixture = fixtures.readKey( + `${encBase}.${format}`, + format === 'pem' ? 'utf8' : null, + ); + + const imported = createPrivateKey({ + key: encryptedFixture, + format, + type: 'pkcs8', + passphrase: 'password', + }); + assert.strictEqual(imported.asymmetricKeyType, alg); + assert.deepStrictEqual( + imported.export({ type: 'pkcs8', format: 'der' }), + expectedDer, + ); + + assertWrongPassphrase(() => createPrivateKey({ + key: encryptedFixture, + format, + type: 'pkcs8', + passphrase: 'wrong', + })); + } +} diff --git a/test/parallel/test-crypto-pqc-key-objects-ml-dsa.js b/test/parallel/test-crypto-pqc-key-objects-ml-dsa.js index f18c555d4653d4..f2a19799c51541 100644 --- a/test/parallel/test-crypto-pqc-key-objects-ml-dsa.js +++ b/test/parallel/test-crypto-pqc-key-objects-ml-dsa.js @@ -4,10 +4,6 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); -if (process.features.openssl_is_boringssl) { - common.skip('Skipping unsupported ML-DSA key tests'); -} - const { hasOpenSSL } = require('../common/crypto'); const assert = require('assert'); @@ -104,7 +100,7 @@ for (const [asymmetricKeyType, pubLen] of [ } } - if (!hasOpenSSL(3, 5)) { + if (!hasOpenSSL(3, 5) && !process.features.openssl_is_boringssl) { assert.throws(() => createPublicKey(keys.public), { code: hasOpenSSL(3) ? 'ERR_OSSL_EVP_DECODE_ERROR' : 'ERR_OSSL_EVP_UNSUPPORTED_ALGORITHM', }); @@ -119,11 +115,15 @@ for (const [asymmetricKeyType, pubLen] of [ assertPublicKey(publicKey); { - for (const [pem, hasSeed] of [ - [keys.private, true], - [keys.private_seed_only, true], - [keys.private_priv_only, false], + for (const [pem, hasSeed, seedOnly] of [ + [keys.private, true, false], + [keys.private_seed_only, true, true], + [keys.private_priv_only, false, false], ]) { + if (process.features.openssl_is_boringssl && !seedOnly) { + common.printSkipMessage('Skipping unsupported private key format test'); + continue; + } const pubFromPriv = createPublicKey(pem); assertPublicKey(pubFromPriv); assertPrivateKey(createPrivateKey(pem), hasSeed); diff --git a/test/parallel/test-crypto-pqc-key-objects-ml-kem.js b/test/parallel/test-crypto-pqc-key-objects-ml-kem.js index 0c344ed100c2da..4c2ef216a0f1a7 100644 --- a/test/parallel/test-crypto-pqc-key-objects-ml-kem.js +++ b/test/parallel/test-crypto-pqc-key-objects-ml-kem.js @@ -100,7 +100,7 @@ for (const [asymmetricKeyType, pubLen] of [ } } - if (!hasOpenSSL(3, 5)) { + if (!hasOpenSSL(3, 5) && !process.features.openssl_is_boringssl) { assert.throws(() => createPublicKey(keys.public), { code: hasOpenSSL(3) ? 'ERR_OSSL_EVP_DECODE_ERROR' : 'ERR_OSSL_EVP_UNSUPPORTED_ALGORITHM', }); @@ -110,16 +110,28 @@ for (const [asymmetricKeyType, pubLen] of [ code: hasOpenSSL(3) ? 'ERR_OSSL_UNSUPPORTED' : 'ERR_OSSL_EVP_UNSUPPORTED_ALGORITHM', }); } + } else if (process.features.openssl_is_boringssl && asymmetricKeyType === 'ml-kem-512') { + // BoringSSL does not support ML-KEM-512 + assert.throws(() => createPublicKey(keys.public), + { code: 'ERR_OSSL_EVP_UNSUPPORTED_ALGORITHM' }); + for (const pem of [keys.private, keys.private_seed_only, keys.private_priv_only]) { + assert.throws(() => createPrivateKey(pem), + { code: 'ERR_OSSL_EVP_UNSUPPORTED_ALGORITHM' }); + } } else { const publicKey = createPublicKey(keys.public); assertPublicKey(publicKey); { - for (const [pem, hasSeed] of [ - [keys.private, true], - [keys.private_seed_only, true], - [keys.private_priv_only, false], - ]) { + const entries = process.features.openssl_is_boringssl ? + // BoringSSL only supports the seed-only PKCS#8 private key encoding. + [[keys.private_seed_only, true]] : + [ + [keys.private, true], + [keys.private_seed_only, true], + [keys.private_priv_only, false], + ]; + for (const [pem, hasSeed] of entries) { const pubFromPriv = createPublicKey(pem); assertPublicKey(pubFromPriv); assertPrivateKey(createPrivateKey(pem), hasSeed); diff --git a/test/parallel/test-crypto-pqc-key-objects-slh-dsa.js b/test/parallel/test-crypto-pqc-key-objects-slh-dsa.js index 98af15dc795f8b..eff309468c3117 100644 --- a/test/parallel/test-crypto-pqc-key-objects-slh-dsa.js +++ b/test/parallel/test-crypto-pqc-key-objects-slh-dsa.js @@ -91,6 +91,12 @@ for (const asymmetricKeyType of [ key: rawPriv, format: 'raw-private', asymmetricKeyType, }); assert.strictEqual(importedPriv.equals(key), true); + assert.throws(() => createPrivateKey({ + key: rawPriv, format: 'raw-seed', asymmetricKeyType, + }), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + assert.throws(() => createPublicKey({ + key: rawPriv, format: 'raw-seed', asymmetricKeyType, + }), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); } if (!hasOpenSSL(3, 5)) { diff --git a/test/parallel/test-crypto-pqc-keygen-ml-dsa.js b/test/parallel/test-crypto-pqc-keygen-ml-dsa.js index abad2c15cf01d1..e6534c988c4e2b 100644 --- a/test/parallel/test-crypto-pqc-keygen-ml-dsa.js +++ b/test/parallel/test-crypto-pqc-keygen-ml-dsa.js @@ -11,7 +11,7 @@ const { generateKeyPair, } = require('crypto'); -if (!hasOpenSSL(3, 5)) { +if (!hasOpenSSL(3, 5) && !process.features.openssl_is_boringssl) { for (const asymmetricKeyType of ['ml-dsa-44', 'ml-dsa-65', 'ml-dsa-87']) { assert.throws(() => generateKeyPair(asymmetricKeyType, common.mustNotCall()), { code: 'ERR_INVALID_ARG_VALUE', diff --git a/test/parallel/test-crypto-pqc-keygen-ml-kem.js b/test/parallel/test-crypto-pqc-keygen-ml-kem.js index ea3ac9f4dde137..620f65c3a8d156 100644 --- a/test/parallel/test-crypto-pqc-keygen-ml-kem.js +++ b/test/parallel/test-crypto-pqc-keygen-ml-kem.js @@ -11,7 +11,12 @@ const { generateKeyPair, } = require('crypto'); -if (!hasOpenSSL(3, 5)) { +const algorithms = process.features.openssl_is_boringssl ? + // BoringSSL does not support ML-KEM-512. + ['ml-kem-768', 'ml-kem-1024'] : + ['ml-kem-512', 'ml-kem-768', 'ml-kem-1024']; + +if (!hasOpenSSL(3, 5) && !process.features.openssl_is_boringssl) { for (const asymmetricKeyType of ['ml-kem-512', 'ml-kem-768', 'ml-kem-1024']) { assert.throws(() => generateKeyPair(asymmetricKeyType, common.mustNotCall()), { code: 'ERR_INVALID_ARG_VALUE', @@ -19,8 +24,7 @@ if (!hasOpenSSL(3, 5)) { }); } } else { - for (const asymmetricKeyType of ['ml-kem-512', 'ml-kem-768', 'ml-kem-1024']) { - + for (const asymmetricKeyType of algorithms) { function assertJwk(jwk) { assert.strictEqual(jwk.kty, 'AKP'); assert.strictEqual(jwk.alg, asymmetricKeyType.toUpperCase()); @@ -67,3 +71,10 @@ if (!hasOpenSSL(3, 5)) { } } } + +if (process.features.openssl_is_boringssl) { + assert.throws(() => generateKeyPair('ml-kem-512', common.mustNotCall()), { + code: 'ERR_INVALID_ARG_VALUE', + message: /The argument 'type' must be a supported key type/ + }); +} diff --git a/test/parallel/test-crypto-pqc-sign-verify-ml-dsa.js b/test/parallel/test-crypto-pqc-sign-verify-ml-dsa.js index 57d6692ca79b55..535e6a33d5ccb0 100644 --- a/test/parallel/test-crypto-pqc-sign-verify-ml-dsa.js +++ b/test/parallel/test-crypto-pqc-sign-verify-ml-dsa.js @@ -6,8 +6,8 @@ if (!common.hasCrypto) const { hasOpenSSL } = require('../common/crypto'); -if (!hasOpenSSL(3, 5)) - common.skip('requires OpenSSL >= 3.5'); +if (!hasOpenSSL(3, 5) && !process.features.openssl_is_boringssl) + common.skip('requires OpenSSL >= 3.5 or BoringSSL'); const assert = require('assert'); const { @@ -34,7 +34,15 @@ for (const [asymmetricKeyType, sigLen] of [ private_priv_only: fixtures.readKey(getKeyFileName(asymmetricKeyType, 'private_priv_only'), 'ascii'), }; - for (const privateKey of [keys.private, keys.private_seed_only, keys.private_priv_only]) { + for (const [privateKey, seedOnly] of [ + [keys.private, false], + [keys.private_seed_only, true], + [keys.private_priv_only, false], + ]) { + if (process.features.openssl_is_boringssl && !seedOnly) { + common.printSkipMessage('Skipping unsupported private key format test'); + continue; + } for (const data of [randomBytes(0), randomBytes(1), randomBytes(32), randomBytes(128), randomBytes(1024)]) { // sync { @@ -44,10 +52,12 @@ for (const [asymmetricKeyType, sigLen] of [ assert.strictEqual(verify(undefined, data, keys.public, Buffer.alloc(sigLen)), false); assert.strictEqual(verify(undefined, data, keys.public, signature), true); assert.strictEqual(verify(undefined, data, privateKey, signature), true); - assert.throws(() => sign('sha256', data, privateKey), { code: 'ERR_OSSL_INVALID_DIGEST' }); + const code = process.features.openssl_is_boringssl ? + 'ERR_OSSL_EVP_COMMAND_NOT_SUPPORTED' : 'ERR_OSSL_INVALID_DIGEST'; + assert.throws(() => sign('sha256', data, privateKey), { code }); assert.throws( () => verify('sha256', data, keys.public, Buffer.alloc(sigLen)), - { code: 'ERR_OSSL_INVALID_DIGEST' }); + { code }); } // async @@ -62,8 +72,9 @@ for (const [asymmetricKeyType, sigLen] of [ })); })); - sign('sha256', data, privateKey, common.expectsError(/invalid digest/)); - verify('sha256', data, keys.public, Buffer.alloc(sigLen), common.expectsError(/invalid digest/)); + const message = process.features.openssl_is_boringssl ? /COMMAND_NOT_SUPPORTED/ : /invalid digest/; + sign('sha256', data, privateKey, common.expectsError(message)); + verify('sha256', data, keys.public, Buffer.alloc(sigLen), common.expectsError(message)); } } } diff --git a/test/parallel/test-crypto.js b/test/parallel/test-crypto.js index fbb065dd442876..46f4571b33dfe8 100644 --- a/test/parallel/test-crypto.js +++ b/test/parallel/test-crypto.js @@ -62,7 +62,7 @@ assert.throws(() => { // Throws general Error, so there is no opensslErrorStack property. return err instanceof Error && err.name === 'Error' && - /^Error: mac verify failure$/.test(err) && + /^Error: (mac verify failure|INCORRECT_PASSWORD)$/.test(err) && !('opensslErrorStack' in err); }); @@ -72,7 +72,7 @@ assert.throws(() => { // Throws general Error, so there is no opensslErrorStack property. return err instanceof Error && err.name === 'Error' && - /^Error: mac verify failure$/.test(err) && + /^Error: (mac verify failure|INCORRECT_PASSWORD)$/.test(err) && !('opensslErrorStack' in err); }); @@ -82,7 +82,7 @@ assert.throws(() => { // Throws general Error, so there is no opensslErrorStack property. return err instanceof Error && err.name === 'Error' && - /^Error: not enough data$/.test(err) && + /^Error: (not enough data|BAD_PKCS12_DATA)$/.test(err) && !('opensslErrorStack' in err); }); @@ -211,49 +211,72 @@ assert.throws(() => { ].join('\n'); crypto.createSign('SHA256').update('test').sign(priv); }, (err) => { - if (!hasOpenSSL3) - assert.ok(!('opensslErrorStack' in err)); - assert.throws(() => { throw err; }, hasOpenSSL3 ? { - name: 'Error', - message: 'error:02000070:rsa routines::digest too big for rsa key', - library: 'rsa routines', - } : { - name: 'Error', - message: /routines:RSA_sign:digest too big for rsa key$/, - library: /rsa routines/i, - function: 'RSA_sign', - reason: /digest[\s_]too[\s_]big[\s_]for[\s_]rsa[\s_]key/i, - code: 'ERR_OSSL_RSA_DIGEST_TOO_BIG_FOR_RSA_KEY' - }); + if (process.features.openssl_is_boringssl) { + // BoringSSL rejects the tiny RSA key while decoding it, before signing. + assert.throws(() => { throw err; }, { + name: 'Error', + message: 'error:06000066:public key routines:OPENSSL_internal:' + + 'DECODE_ERROR', + library: 'public key routines', + function: 'OPENSSL_internal', + reason: 'DECODE_ERROR', + code: 'ERR_OSSL_EVP_DECODE_ERROR' + }); + assert(Array.isArray(err.opensslErrorStack)); + assert(err.opensslErrorStack.length > 0); + } else { + if (!hasOpenSSL3) + assert.ok(!('opensslErrorStack' in err)); + assert.throws(() => { throw err; }, hasOpenSSL3 ? { + name: 'Error', + message: 'error:02000070:rsa routines::digest too big for rsa key', + library: 'rsa routines', + } : { + name: 'Error', + message: /routines:RSA_sign:digest too big for rsa key$/, + library: /rsa routines/i, + function: 'RSA_sign', + reason: /digest[\s_]too[\s_]big[\s_]for[\s_]rsa[\s_]key/i, + code: 'ERR_OSSL_RSA_DIGEST_TOO_BIG_FOR_RSA_KEY' + }); + } return true; }); if (!hasOpenSSL3) { - assert.throws(() => { - // The correct header inside `rsa_private_pkcs8_bad.pem` should have been - // -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY----- - // instead of - // -----BEGIN RSA PRIVATE KEY----- and -----END RSA PRIVATE KEY----- - const sha1_privateKey = fixtures.readKey('rsa_private_pkcs8_bad.pem', - 'ascii'); - // This would inject errors onto OpenSSL's error stack - crypto.createSign('sha1').sign(sha1_privateKey); - }, (err) => { - // Do the standard checks, but then do some custom checks afterwards. - assert.throws(() => { throw err; }, { - message: 'error:0D0680A8:asn1 encoding routines:asn1_check_tlen:' + - 'wrong tag', - library: 'asn1 encoding routines', - function: 'asn1_check_tlen', - reason: 'wrong tag', - code: 'ERR_OSSL_ASN1_WRONG_TAG', + // The correct header inside `rsa_private_pkcs8_bad.pem` should have been + // -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY----- + // instead of + // -----BEGIN RSA PRIVATE KEY----- and -----END RSA PRIVATE KEY----- + const sha1_privateKey = fixtures.readKey('rsa_private_pkcs8_bad.pem', + 'ascii'); + + if (process.features.openssl_is_boringssl) { + // BoringSSL accepts the PKCS#8 payload despite the legacy PEM label. + const signature = crypto.createSign('sha1').sign(sha1_privateKey); + assert(Buffer.isBuffer(signature)); + assert.strictEqual(signature.length, 256); + } else { + assert.throws(() => { + // This would inject errors onto OpenSSL's error stack + crypto.createSign('sha1').sign(sha1_privateKey); + }, (err) => { + // Do the standard checks, but then do some custom checks afterwards. + assert.throws(() => { throw err; }, { + message: 'error:0D0680A8:asn1 encoding routines:asn1_check_tlen:' + + 'wrong tag', + library: 'asn1 encoding routines', + function: 'asn1_check_tlen', + reason: 'wrong tag', + code: 'ERR_OSSL_ASN1_WRONG_TAG', + }); + // Throws crypto error, so there is an opensslErrorStack property. + // The openSSL stack should have content. + assert(Array.isArray(err.opensslErrorStack)); + assert(err.opensslErrorStack.length > 0); + return true; }); - // Throws crypto error, so there is an opensslErrorStack property. - // The openSSL stack should have content. - assert(Array.isArray(err.opensslErrorStack)); - assert(err.opensslErrorStack.length > 0); - return true; - }); + } } // Make sure memory isn't released before being returned diff --git a/test/parallel/test-https-agent-session-reuse.js b/test/parallel/test-https-agent-session-reuse.js index 485f4b1ca308c9..c5b7b78b8e0272 100644 --- a/test/parallel/test-https-agent-session-reuse.js +++ b/test/parallel/test-https-agent-session-reuse.js @@ -5,6 +5,11 @@ const assert = require('assert'); if (!common.hasCrypto) common.skip('missing crypto'); +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').testTls13SessionTicketSemanticsDiffer(); + return; +} + const https = require('https'); const crypto = require('crypto'); const fixtures = require('../common/fixtures'); diff --git a/test/parallel/test-https-client-renegotiation-limit.js b/test/parallel/test-https-client-renegotiation-limit.js index 6614090e737614..729176b7c1aa21 100644 --- a/test/parallel/test-https-client-renegotiation-limit.js +++ b/test/parallel/test-https-client-renegotiation-limit.js @@ -25,6 +25,11 @@ if (!common.hasCrypto) { common.skip('missing crypto'); } +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').testRenegotiationUnsupported(); + return; +} + const assert = require('assert'); const tls = require('tls'); const https = require('https'); diff --git a/test/parallel/test-https-foafssl.js b/test/parallel/test-https-foafssl.js index ffa44f218b935d..a191bdcf32b73e 100644 --- a/test/parallel/test-https-foafssl.js +++ b/test/parallel/test-https-foafssl.js @@ -56,8 +56,8 @@ const server = https.createServer(options, common.mustCall(function(req, res) { cert = req.connection.getPeerCertificate(); assert.strictEqual(cert.subjectaltname, webIdUrl); - assert.strictEqual(cert.exponent, exponent); - assert.strictEqual(cert.modulus, modulus); + assert.strictEqual(cert.exponent.toLowerCase(), exponent.toLowerCase()); + assert.strictEqual(cert.modulus.toLowerCase(), modulus.toLowerCase()); res.writeHead(200, { 'content-type': 'text/plain' }); res.end(body, () => { console.log('stream finished'); }); console.log('sent response'); diff --git a/test/parallel/test-https-options-boolean-check.js b/test/parallel/test-https-options-boolean-check.js index 9740704e169f1e..fa02a165b80f10 100644 --- a/test/parallel/test-https-options-boolean-check.js +++ b/test/parallel/test-https-options-boolean-check.js @@ -40,9 +40,23 @@ const keyDataView = toDataView(keyBuff); const certDataView = toDataView(certBuff); const caArrDataView = toDataView(caCert); +function filterBoringSSLKeyCertArrayCases(options, setName) { + if (!process.features.openssl_is_boringssl) + return options; + + // The array-valued cases exercise multi-identity key/cert handling. + // BoringSSL may reject those cases with backend key/cert mismatch errors + // before the boolean/type validation this test is targeting. Keep the scalar + // cases so https.createServer() option type validation is still covered. + common.printSkipMessage( + `BoringSSL: skipping ${setName} key/cert array cases`); + return options.filter(([key, cert]) => !Array.isArray(key) && + !Array.isArray(cert)); +} + // Checks to ensure https.createServer doesn't throw an error // Format ['key', 'cert'] -[ +const validOptions = [ [keyBuff, certBuff], [false, certBuff], [keyBuff, false], @@ -62,13 +76,16 @@ const caArrDataView = toDataView(caCert); [false, [certStr, certStr2]], [[{ pem: keyBuff }], false], [[{ pem: keyBuff }, { pem: keyBuff }], false], -].forEach(([key, cert]) => { - https.createServer({ key, cert }); -}); +]; + +filterBoringSSLKeyCertArrayCases(validOptions, 'valid') + .forEach(([key, cert]) => { + https.createServer({ key, cert }); + }); // Checks to ensure https.createServer predictably throws an error // Format ['key', 'cert', 'expected message'] -[ +const invalidKeyOptions = [ [true, certBuff], [true, certStr], [true, certArrBuff], @@ -81,7 +98,10 @@ const caArrDataView = toDataView(caCert); [[true, keyStr2], [certStr, certStr2], 0], [[true, false], [certBuff, certBuff2], 0], [true, [certBuff, certBuff2]], -].forEach(([key, cert, index]) => { +]; + +for (const [key, cert, index] of + filterBoringSSLKeyCertArrayCases(invalidKeyOptions, 'invalid key')) { const val = index === undefined ? key : key[index]; assert.throws(() => { https.createServer({ key, cert }); @@ -92,9 +112,9 @@ const caArrDataView = toDataView(caCert); 'instance of Buffer, TypedArray, or DataView.' + common.invalidArgTypeHelper(val) }); -}); +} -[ +const invalidCertOptions = [ [keyBuff, true], [keyStr, true], [keyArrBuff, true], @@ -107,7 +127,10 @@ const caArrDataView = toDataView(caCert); [[keyStr, keyStr2], [certStr, true], 1], [[keyStr, keyStr2], [true, false], 0], [[keyStr, keyStr2], true], -].forEach(([key, cert, index]) => { +]; + +for (const [key, cert, index] of + filterBoringSSLKeyCertArrayCases(invalidCertOptions, 'invalid cert')) { const val = index === undefined ? cert : cert[index]; assert.throws(() => { https.createServer({ key, cert }); @@ -118,7 +141,7 @@ const caArrDataView = toDataView(caCert); 'instance of Buffer, TypedArray, or DataView.' + common.invalidArgTypeHelper(val) }); -}); +} // Checks to ensure https.createServer works with the CA parameter // Format ['key', 'cert', 'ca'] diff --git a/test/parallel/test-tls-alert.js b/test/parallel/test-tls-alert.js index 23c92e7293458f..64b7080e39ba25 100644 --- a/test/parallel/test-tls-alert.js +++ b/test/parallel/test-tls-alert.js @@ -48,6 +48,33 @@ const server = tls.Server({ key: loadPEM('agent2-key'), cert: loadPEM('agent2-cert') }, null).listen(0, common.mustCall(() => { + if (process.features.openssl_is_boringssl) { + let gotClientError = false; + let gotServerError = false; + function maybeClose() { + if (gotClientError && gotServerError) + server.close(); + } + + server.once('tlsClientError', common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_SSL_UNSUPPORTED_PROTOCOL'); + gotServerError = true; + maybeClose(); + })); + + const client = tls.connect({ + port: server.address().port, + rejectUnauthorized: false, + secureProtocol: 'TLSv1_1_method', + }, common.mustNotCall()); + client.once('error', common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_SSL_TLSV1_ALERT_PROTOCOL_VERSION'); + gotClientError = true; + maybeClose(); + })); + return; + } + const args = ['s_client', '-quiet', '-tls1_1', '-cipher', (hasOpenSSL(3, 1) ? 'DEFAULT:@SECLEVEL=0' : 'DEFAULT'), '-connect', `127.0.0.1:${server.address().port}`]; diff --git a/test/parallel/test-tls-client-auth.js b/test/parallel/test-tls-client-auth.js index 67aed40914c9fe..517054c6e290dc 100644 --- a/test/parallel/test-tls-client-auth.js +++ b/test/parallel/test-tls-client-auth.js @@ -111,7 +111,10 @@ if (tls.DEFAULT_MAX_VERSION === 'TLSv1.3') connect({ // and sends a fatal Alert to the client that the client discovers there has // been a fatal error. pair.client.conn.once('error', common.mustCall((err) => { - assert.strictEqual(err.code, 'ERR_SSL_TLSV13_ALERT_CERTIFICATE_REQUIRED'); + const expectedErr = process.features.openssl_is_boringssl ? + 'ERR_SSL_TLSV1_ALERT_CERTIFICATE_REQUIRED' : + 'ERR_SSL_TLSV13_ALERT_CERTIFICATE_REQUIRED'; + assert.strictEqual(err.code, expectedErr); cleanup(); })); })); diff --git a/test/parallel/test-tls-client-getephemeralkeyinfo.js b/test/parallel/test-tls-client-getephemeralkeyinfo.js index 19728e3733d868..2107d024012c4d 100644 --- a/test/parallel/test-tls-client-getephemeralkeyinfo.js +++ b/test/parallel/test-tls-client-getephemeralkeyinfo.js @@ -2,6 +2,12 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); + +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').testEphemeralKeyInfoUnsupported(); + return; +} + const fixtures = require('../common/fixtures'); const { hasOpenSSL } = require('../common/crypto'); diff --git a/test/parallel/test-tls-client-mindhsize.js b/test/parallel/test-tls-client-mindhsize.js index cd7b16ea566fe8..fa494c583a2f3b 100644 --- a/test/parallel/test-tls-client-mindhsize.js +++ b/test/parallel/test-tls-client-mindhsize.js @@ -85,21 +85,25 @@ function testDHE3072() { test(3072, false, null); } -if (hasOpenSSL(4, 0)) { - // OpenSSL 4.0 implements RFC 7919 FFDHE negotiation for TLS 1.2 and - // ignores the server-supplied dhparam in favor of FFDHE-2048. The 3072 - // success case is therefore replaced by a 2048 success case. - testDHE2048(true, () => test(2048, false, null, 2048)); -} else if (secLevel > 1) { - // Minimum size for OpenSSL security level 2 and above is 2048 by default - testDHE2048(true, testDHE3072); +if (!process.features.openssl_is_boringssl) { + if (hasOpenSSL(4, 0)) { + // OpenSSL 4.0 implements RFC 7919 FFDHE negotiation for TLS 1.2 and + // ignores the server-supplied dhparam in favor of FFDHE-2048. The 3072 + // success case is therefore replaced by a 2048 success case. + testDHE2048(true, () => test(2048, false, null, 2048)); + } else if (secLevel > 1) { + // Minimum size for OpenSSL security level 2 and above is 2048 by default + testDHE2048(true, testDHE3072); + } else { + testDHE1024(); + } + + assert.throws(() => test(512, true, common.mustNotCall()), + /DH parameter is less than 1024 bits/); } else { - testDHE1024(); + require('../common/boringssl').assertFiniteFieldDheUnsupported(); } -assert.throws(() => test(512, true, common.mustNotCall()), - /DH parameter is less than 1024 bits/); - for (const minDHSize of [0, -1, -Infinity, NaN]) { assert.throws(() => { tls.connect({ minDHSize }); @@ -118,7 +122,9 @@ for (const minDHSize of [true, false, null, undefined, {}, [], '', '1']) { }); } -process.on('exit', function() { - assert.strictEqual(nsuccess, 1); - assert.strictEqual(nerror, 1); -}); +if (!process.features.openssl_is_boringssl) { + process.on('exit', function() { + assert.strictEqual(nsuccess, 1); + assert.strictEqual(nerror, 1); + }); +} diff --git a/test/parallel/test-tls-client-reject.js b/test/parallel/test-tls-client-reject.js index 68922e3690eac0..cff0aabc89a774 100644 --- a/test/parallel/test-tls-client-reject.js +++ b/test/parallel/test-tls-client-reject.js @@ -30,7 +30,8 @@ const fixtures = require('../common/fixtures'); const options = { key: fixtures.readKey('rsa_private.pem'), - cert: fixtures.readKey('rsa_cert.crt') + cert: fixtures.readKey('rsa_cert.crt'), + ...(process.features.openssl_is_boringssl ? { maxVersion: 'TLSv1.2' } : {}), }; const server = tls.createServer(options, function(socket) { @@ -46,7 +47,8 @@ function unauthorized() { const socket = tls.connect({ port: server.address().port, servername: 'localhost', - rejectUnauthorized: false + rejectUnauthorized: false, + ...(process.features.openssl_is_boringssl ? { maxVersion: 'TLSv1.2' } : {}), }, common.mustCall(function() { let _data; assert(!socket.authorized); @@ -67,7 +69,8 @@ function unauthorized() { function rejectUnauthorized() { console.log('reject unauthorized'); const socket = tls.connect(server.address().port, { - servername: 'localhost' + servername: 'localhost', + ...(process.features.openssl_is_boringssl ? { maxVersion: 'TLSv1.2' } : {}), }, common.mustNotCall()); socket.on('data', common.mustNotCall()); socket.on('error', common.mustCall(function(err) { @@ -80,7 +83,8 @@ function rejectUnauthorizedUndefined() { console.log('reject unauthorized undefined'); const socket = tls.connect(server.address().port, { servername: 'localhost', - rejectUnauthorized: undefined + rejectUnauthorized: undefined, + ...(process.features.openssl_is_boringssl ? { maxVersion: 'TLSv1.2' } : {}), }, common.mustNotCall()); socket.on('data', common.mustNotCall()); socket.on('error', common.mustCall(function(err) { @@ -93,7 +97,8 @@ function authorized() { console.log('connect authorized'); const socket = tls.connect(server.address().port, { ca: [fixtures.readKey('rsa_cert.crt')], - servername: 'localhost' + servername: 'localhost', + ...(process.features.openssl_is_boringssl ? { maxVersion: 'TLSv1.2' } : {}), }, common.mustCall(function() { console.log('... authorized'); assert(socket.authorized); diff --git a/test/parallel/test-tls-client-renegotiation-13.js b/test/parallel/test-tls-client-renegotiation-13.js index 5afa8389ed37ca..80c4753d065ec1 100644 --- a/test/parallel/test-tls-client-renegotiation-13.js +++ b/test/parallel/test-tls-client-renegotiation-13.js @@ -32,14 +32,22 @@ connect({ assert.strictEqual(client.getProtocol(), 'TLSv1.3'); const ok = client.renegotiate({}, common.mustCall((err) => { - assert.throws(() => { throw err; }, { - message: hasOpenSSL3 ? - 'error:0A00010A:SSL routines::wrong ssl version' : - 'error:1420410A:SSL routines:SSL_renegotiate:wrong ssl version', - code: 'ERR_SSL_WRONG_SSL_VERSION', - library: 'SSL routines', - reason: 'wrong ssl version', - }); + if (process.features.openssl_is_boringssl) { + assert.throws(() => { throw err; }, { + message: 'TLS session renegotiation is unsupported by this TLS ' + + 'implementation', + code: 'ERR_TLS_RENEGOTIATION_UNSUPPORTED', + }); + } else { + assert.throws(() => { throw err; }, { + message: hasOpenSSL3 ? + 'error:0A00010A:SSL routines::wrong ssl version' : + 'error:1420410A:SSL routines:SSL_renegotiate:wrong ssl version', + code: 'ERR_SSL_WRONG_SSL_VERSION', + library: 'SSL routines', + reason: 'wrong ssl version', + }); + } cleanup(); })); diff --git a/test/parallel/test-tls-client-renegotiation-limit.js b/test/parallel/test-tls-client-renegotiation-limit.js index 86111d6da0b402..9b7f62865b336d 100644 --- a/test/parallel/test-tls-client-renegotiation-limit.js +++ b/test/parallel/test-tls-client-renegotiation-limit.js @@ -31,6 +31,11 @@ if (!opensslCli) { common.skip('node compiled without OpenSSL CLI.'); } +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').testRenegotiationUnsupported(); + return; +} + const assert = require('assert'); const tls = require('tls'); const fixtures = require('../common/fixtures'); diff --git a/test/parallel/test-tls-dhe.js b/test/parallel/test-tls-dhe.js index 03750bc206adbe..b788d153293899 100644 --- a/test/parallel/test-tls-dhe.js +++ b/test/parallel/test-tls-dhe.js @@ -26,6 +26,11 @@ if (!common.hasCrypto) { common.skip('missing crypto'); } +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').assertFiniteFieldDheUnsupported(); + return; +} + const { opensslCli, hasOpenSSL, diff --git a/test/parallel/test-tls-disable-renegotiation.js b/test/parallel/test-tls-disable-renegotiation.js index f91868c6345d71..84a6ead4a5441c 100644 --- a/test/parallel/test-tls-disable-renegotiation.js +++ b/test/parallel/test-tls-disable-renegotiation.js @@ -8,6 +8,11 @@ const fixtures = require('../common/fixtures'); if (!common.hasCrypto) common.skip('missing crypto'); +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').testRenegotiationUnsupported(); + return; +} + const tls = require('tls'); // Renegotiation as a protocol feature was dropped after TLS1.2. diff --git a/test/parallel/test-tls-ecdh-multiple.js b/test/parallel/test-tls-ecdh-multiple.js index ee52f288610956..ed60044197d7da 100644 --- a/test/parallel/test-tls-ecdh-multiple.js +++ b/test/parallel/test-tls-ecdh-multiple.js @@ -26,7 +26,7 @@ function loadPEM(n) { // OpenSSL 4.0 disables support for deprecated elliptic curves from RFC 8422 // (including secp256k1) by default. -const ecdhCurve = hasOpenSSL(4, 0) ? +const ecdhCurve = process.features.openssl_is_boringssl || hasOpenSSL(4, 0) ? 'prime256v1:secp521r1' : 'secp256k1:prime256v1:secp521r1'; @@ -67,7 +67,7 @@ const server = tls.createServer(options, (conn) => { } // Deprecated RFC 8422 curves are disabled by default in OpenSSL 4.0. - if (hasOpenSSL(4, 0)) { + if (process.features.openssl_is_boringssl || hasOpenSSL(4, 0)) { unsupportedCurves.push('secp256k1'); } diff --git a/test/parallel/test-tls-empty-sni-context.js b/test/parallel/test-tls-empty-sni-context.js index e4136ff71e1d52..6ecdfbeecbe3c9 100644 --- a/test/parallel/test-tls-empty-sni-context.js +++ b/test/parallel/test-tls-empty-sni-context.js @@ -16,7 +16,7 @@ const options = { const server = tls.createServer(options, (c) => { assert.fail('Should not be called'); }).on('tlsClientError', common.mustCall((err, c) => { - assert.match(err.message, /no suitable signature algorithm/i); + assert.match(err.message, /no suitable signature algorithm|NO_CERTIFICATE_SET/i); server.close(); })).listen(0, common.mustCall(() => { const c = tls.connect({ @@ -26,9 +26,10 @@ const server = tls.createServer(options, (c) => { }, common.mustNotCall()); c.on('error', common.mustCall((err) => { - const expectedErr = hasOpenSSL(4, 0) ? - 'ERR_SSL_TLS_ALERT_HANDSHAKE_FAILURE' : hasOpenSSL(3, 2) ? - 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; + const expectedErr = process.features.openssl_is_boringssl ? + 'ERR_SSL_TLSV1_ALERT_INTERNAL_ERROR' : hasOpenSSL(4, 0) ? + 'ERR_SSL_TLS_ALERT_HANDSHAKE_FAILURE' : hasOpenSSL(3, 2) ? + 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; assert.strictEqual(err.code, expectedErr); })); })); diff --git a/test/parallel/test-tls-finished.js b/test/parallel/test-tls-finished.js index 8b52934b049d95..b23b4567d27ec6 100644 --- a/test/parallel/test-tls-finished.js +++ b/test/parallel/test-tls-finished.js @@ -20,7 +20,8 @@ const msg = {}; const pem = (n) => fixtures.readKey(`${n}.pem`); const server = tls.createServer({ key: pem('agent1-key'), - cert: pem('agent1-cert') + cert: pem('agent1-cert'), + ...(process.features.openssl_is_boringssl ? { maxVersion: 'TLSv1.2' } : {}), }, common.mustCall((alice) => { msg.server = { alice: alice.getFinished(), @@ -32,7 +33,8 @@ const server = tls.createServer({ server.listen(0, common.mustCall(() => { const bob = tls.connect({ port: server.address().port, - rejectUnauthorized: false + rejectUnauthorized: false, + ...(process.features.openssl_is_boringssl ? { maxVersion: 'TLSv1.2' } : {}), }, common.mustCall(() => { msg.client = { alice: bob.getPeerFinished(), diff --git a/test/parallel/test-tls-getcipher.js b/test/parallel/test-tls-getcipher.js index 4d5042d6e6beab..2d4de5639afb70 100644 --- a/test/parallel/test-tls-getcipher.js +++ b/test/parallel/test-tls-getcipher.js @@ -36,27 +36,42 @@ const options = { honorCipherOrder: true }; +const isBoringSSL = process.features.openssl_is_boringssl; let clients = 0; +const expectedClients = isBoringSSL ? 1 : 2; const server = tls.createServer(options, common.mustCall(() => { if (--clients === 0) server.close(); -}, 2)); +}, expectedClients)); server.listen(0, '127.0.0.1', common.mustCall(function() { - clients++; - tls.connect({ - host: '127.0.0.1', - port: this.address().port, - ciphers: 'AES256-SHA256', - rejectUnauthorized: false, - maxVersion: 'TLSv1.2', - }, common.mustCall(function() { - const cipher = this.getCipher(); - assert.strictEqual(cipher.name, 'AES256-SHA256'); - assert.strictEqual(cipher.standardName, 'TLS_RSA_WITH_AES_256_CBC_SHA256'); - assert.strictEqual(cipher.version, 'TLSv1.2'); - this.end(); - })); + if (isBoringSSL) { + // BoringSSL does not provide this static RSA TLS 1.2 cipher suite on + // Node's supported cipher surface, so keep the OpenSSL getCipher() + // assertion below limited to backends that can create the context. + common.printSkipMessage('BoringSSL does not provide AES256-SHA256'); + assert.throws(() => tls.createSecureContext({ ciphers: 'AES256-SHA256' }), { + code: 'ERR_SSL_NO_CIPHER_MATCH', + library: 'SSL routines', + function: 'OPENSSL_internal', + reason: 'NO_CIPHER_MATCH', + }); + } else { + clients++; + tls.connect({ + host: '127.0.0.1', + port: this.address().port, + ciphers: 'AES256-SHA256', + rejectUnauthorized: false, + maxVersion: 'TLSv1.2', + }, common.mustCall(function() { + const cipher = this.getCipher(); + assert.strictEqual(cipher.name, 'AES256-SHA256'); + assert.strictEqual(cipher.standardName, 'TLS_RSA_WITH_AES_256_CBC_SHA256'); + assert.strictEqual(cipher.version, 'TLSv1.2'); + this.end(); + })); + } clients++; tls.connect({ @@ -70,7 +85,9 @@ server.listen(0, '127.0.0.1', common.mustCall(function() { assert.strictEqual(cipher.name, 'ECDHE-RSA-AES256-GCM-SHA384'); assert.strictEqual(cipher.standardName, 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384'); - assert.strictEqual(cipher.version, 'TLSv1.2'); + assert.strictEqual(cipher.version, isBoringSSL ? + 'TLSv1/SSLv3' : + 'TLSv1.2'); this.end(); })); })); @@ -90,9 +107,14 @@ tls.createServer({ rejectUnauthorized: false }, common.mustCall(() => { const cipher = client.getCipher(); - assert.strictEqual(cipher.name, 'TLS_AES_256_GCM_SHA384'); + const expectedCipher = isBoringSSL ? + 'TLS_AES_128_GCM_SHA256' : + 'TLS_AES_256_GCM_SHA384'; + assert.strictEqual(cipher.name, expectedCipher); assert.strictEqual(cipher.standardName, cipher.name); - assert.strictEqual(cipher.version, 'TLSv1.3'); + assert.strictEqual(cipher.version, isBoringSSL ? + 'TLSv1/SSLv3' : + 'TLSv1.3'); client.end(); })); })); diff --git a/test/parallel/test-tls-getprotocol.js b/test/parallel/test-tls-getprotocol.js index 5fe46c43c376cf..2945ff99b5a290 100644 --- a/test/parallel/test-tls-getprotocol.js +++ b/test/parallel/test-tls-getprotocol.js @@ -12,7 +12,7 @@ const assert = require('assert'); const tls = require('tls'); const fixtures = require('../common/fixtures'); -const clientConfigs = [ +let clientConfigs = [ { secureProtocol: 'TLSv1_method', version: 'TLSv1', @@ -27,6 +27,14 @@ const clientConfigs = [ }, ]; +if (process.features.openssl_is_boringssl) { + // Remove the TLSv1 and TLSv1.1 cases. BoringSSL does not negotiate those + // legacy protocols in this configuration; keep TLSv1.2 to cover getProtocol() + // on a successful BoringSSL TLS handshake. + common.printSkipMessage('BoringSSL: skipping TLSv1/TLSv1.1 getProtocol cases'); + clientConfigs = clientConfigs.filter(({ version }) => version === 'TLSv1.2'); +} + const serverConfig = { secureProtocol: 'TLS_method', key: fixtures.readKey('agent2-key.pem'), diff --git a/test/parallel/test-tls-handshake-error.js b/test/parallel/test-tls-handshake-error.js index 5547964780cd60..94a21a14975b5d 100644 --- a/test/parallel/test-tls-handshake-error.js +++ b/test/parallel/test-tls-handshake-error.js @@ -20,7 +20,7 @@ const server = tls.createServer({ port: this.address().port, ciphers: 'no-such-cipher' }, common.mustNotCall()); - }, /no cipher match/i); + }, /no[_ ]cipher[_ ]match/i); server.close(); })); diff --git a/test/parallel/test-tls-honorcipherorder.js b/test/parallel/test-tls-honorcipherorder.js index 5f123cd739a4c0..d86a59aa4cdc6d 100644 --- a/test/parallel/test-tls-honorcipherorder.js +++ b/test/parallel/test-tls-honorcipherorder.js @@ -16,14 +16,40 @@ const util = require('util'); // default method is updated in the future const SSL_Method = 'TLSv1_2_method'; const localhost = '127.0.0.1'; +const config = process.features.openssl_is_boringssl ? { + serverCiphers: + 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256', + clientPreferenceCiphers: + 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384', + clientPreferredCipher: 'ECDHE-RSA-AES128-GCM-SHA256', + serverPreferredCipher: 'ECDHE-RSA-AES256-GCM-SHA384', + singleCipher: 'ECDHE-RSA-AES128-GCM-SHA256', + defaultCipher: 'ECDHE-RSA-AES256-GCM-SHA384', + limitedDefaultCipher: 'ECDHE-RSA-AES128-GCM-SHA256', + extraCases: [], +} : { + serverCiphers: 'AES256-SHA256:AES128-GCM-SHA256:AES128-SHA256:' + + 'ECDHE-RSA-AES128-GCM-SHA256', + clientPreferenceCiphers: 'AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256', + clientPreferredCipher: 'AES128-GCM-SHA256', + serverPreferredCipher: 'AES256-SHA256', + singleCipher: 'AES128-SHA256', + defaultCipher: 'AES256-SHA256', + limitedDefaultCipher: 'ECDHE-RSA-AES128-GCM-SHA256', + extraCases: [ + // Server has the preference of cipher suites. AES128-GCM-SHA256 is given + // higher priority over AES128-SHA256 among client cipher suites. + [true, 'AES128-SHA256:AES128-GCM-SHA256', 'AES128-GCM-SHA256'], + [undefined, 'AES128-SHA256:AES128-GCM-SHA256', 'AES128-GCM-SHA256'], + ], +}; function test(honorCipherOrder, clientCipher, expectedCipher, defaultCiphers) { const soptions = { secureProtocol: SSL_Method, key: fixtures.readKey('agent2-key.pem'), cert: fixtures.readKey('agent2-cert.pem'), - ciphers: 'AES256-SHA256:AES128-GCM-SHA256:AES128-SHA256:' + - 'ECDHE-RSA-AES128-GCM-SHA256', + ciphers: config.serverCiphers, honorCipherOrder: honorCipherOrder, }; @@ -57,34 +83,27 @@ function test(honorCipherOrder, clientCipher, expectedCipher, defaultCiphers) { } // Client explicitly has the preference of cipher suites, not the default. -test(false, 'AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256', - 'AES128-GCM-SHA256'); +test(false, config.clientPreferenceCiphers, config.clientPreferredCipher); -// Server has the preference of cipher suites, and AES256-SHA256 is -// the server's top choice. -test(true, 'AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256', - 'AES256-SHA256'); -test(undefined, 'AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256', - 'AES256-SHA256'); - -// Server has the preference of cipher suites. AES128-GCM-SHA256 is given -// higher priority over AES128-SHA256 among client cipher suites. -test(true, 'AES128-SHA256:AES128-GCM-SHA256', 'AES128-GCM-SHA256'); -test(undefined, 'AES128-SHA256:AES128-GCM-SHA256', 'AES128-GCM-SHA256'); +// Server has the preference of cipher suites. +test(true, config.clientPreferenceCiphers, config.serverPreferredCipher); +test(undefined, config.clientPreferenceCiphers, config.serverPreferredCipher); +for (const args of config.extraCases) { + test(...args); +} // As client has only one cipher, server has no choice, irrespective // of honorCipherOrder. -test(true, 'AES128-SHA256', 'AES128-SHA256'); -test(undefined, 'AES128-SHA256', 'AES128-SHA256'); +test(true, config.singleCipher, config.singleCipher); +test(undefined, config.singleCipher, config.singleCipher); -// Client did not explicitly set ciphers and client offers -// tls.DEFAULT_CIPHERS. All ciphers of the server are included in the -// default list so the negotiated cipher is selected according to the -// server's top preference of AES256-SHA256. -test(true, tls.DEFAULT_CIPHERS, 'AES256-SHA256'); -test(true, null, 'AES256-SHA256'); -test(undefined, null, 'AES256-SHA256'); +// Client did not explicitly set ciphers and client offers tls.DEFAULT_CIPHERS. +// All ciphers of the server are included in the default list so the negotiated +// cipher is selected according to server preference. +test(true, tls.DEFAULT_CIPHERS, config.defaultCipher); +test(true, null, config.defaultCipher); +test(undefined, null, config.defaultCipher); // Ensure that `tls.DEFAULT_CIPHERS` is used when its a limited cipher set. -test(true, null, 'ECDHE-RSA-AES128-GCM-SHA256', 'ECDHE-RSA-AES128-GCM-SHA256'); +test(true, null, config.limitedDefaultCipher, config.limitedDefaultCipher); diff --git a/test/parallel/test-tls-junk-server.js b/test/parallel/test-tls-junk-server.js index 42f089f8f90ed2..b6ff3cd2a467f2 100644 --- a/test/parallel/test-tls-junk-server.js +++ b/test/parallel/test-tls-junk-server.js @@ -24,7 +24,7 @@ server.listen(0, common.mustCall(function() { // Different OpenSSL versions report different errors for junk data on a // TLS connection, depending on which record validation check fires first. const expectedErrorMessage = - /wrong version number|packet length too long|bad record type/; + /wrong[ _]version[ _]number|packet length too long|bad record type/i; req.once('error', common.mustCall(function(err) { assert.match(err.message, expectedErrorMessage); server.close(); diff --git a/test/parallel/test-tls-key-mismatch.js b/test/parallel/test-tls-key-mismatch.js index df8848a03de4a9..797c7c171dc5ff 100644 --- a/test/parallel/test-tls-key-mismatch.js +++ b/test/parallel/test-tls-key-mismatch.js @@ -31,9 +31,11 @@ const { hasOpenSSL3 } = require('../common/crypto'); const assert = require('assert'); const tls = require('tls'); -const errorMessageRegex = hasOpenSSL3 ? - /^Error: error:05800074:x509 certificate routines::key values mismatch$/ : - /^Error: error:0B080074:x509 certificate routines:X509_check_private_key:key values mismatch$/; +const errorMessageRegex = process.features.openssl_is_boringssl ? + /^Error: error:0b000074:X\.509 certificate routines:OPENSSL_internal:KEY_VALUES_MISMATCH$/ : + hasOpenSSL3 ? + /^Error: error:05800074:x509 certificate routines::key values mismatch$/ : + /^Error: error:0B080074:x509 certificate routines:X509_check_private_key:key values mismatch$/; const options = { key: fixtures.readKey('agent1-key.pem'), diff --git a/test/parallel/test-tls-max-send-fragment.js b/test/parallel/test-tls-max-send-fragment.js index 009021045624bb..2e319fcdaeafea 100644 --- a/test/parallel/test-tls-max-send-fragment.js +++ b/test/parallel/test-tls-max-send-fragment.js @@ -60,9 +60,15 @@ const server = tls.createServer({ assert.throws(() => c.setMaxSendFragment(Symbol()), { name: 'TypeError' }); - // Lower and upper limits. - assert(!c.setMaxSendFragment(511)); - assert(!c.setMaxSendFragment(16385)); + // OpenSSL enforces Node's documented fragment size range. BoringSSL accepts + // both out-of-range values and reports success, so assert that difference + // explicitly instead of using a truthiness shortcut. + const acceptsOutOfRangeFragmentSize = + process.features.openssl_is_boringssl; + assert.strictEqual(c.setMaxSendFragment(511), + acceptsOutOfRangeFragmentSize); + assert.strictEqual(c.setMaxSendFragment(16385), + acceptsOutOfRangeFragmentSize); // Correct fragment size. assert(c.setMaxSendFragment(maxChunk)); diff --git a/test/parallel/test-tls-min-max-version.js b/test/parallel/test-tls-min-max-version.js index 4903d92f5c5700..abddbbeb0eba1b 100644 --- a/test/parallel/test-tls-min-max-version.js +++ b/test/parallel/test-tls-min-max-version.js @@ -4,6 +4,12 @@ const common = require('../common'); if (!common.hasCrypto) { common.skip('missing crypto'); } + +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').testLegacyProtocolUnsupported(); + return; +} + const { hasOpenSSL, hasOpenSSL3, diff --git a/test/parallel/test-tls-multi-key.js b/test/parallel/test-tls-multi-key.js index 89f9931e5bdd77..0a9c6f108bf675 100644 --- a/test/parallel/test-tls-multi-key.js +++ b/test/parallel/test-tls-multi-key.js @@ -27,6 +27,11 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').assertMultiKeyUnsupported(); + return; +} + const fixtures = require('../common/fixtures'); const assert = require('assert'); const tls = require('tls'); diff --git a/test/parallel/test-tls-multi-pfx.js b/test/parallel/test-tls-multi-pfx.js index 526b77b1484cd3..fec697cd3b7093 100644 --- a/test/parallel/test-tls-multi-pfx.js +++ b/test/parallel/test-tls-multi-pfx.js @@ -3,6 +3,11 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').testMultiPfxSelectionDifference(); + return; +} + const assert = require('assert'); const tls = require('tls'); const fixtures = require('../common/fixtures'); diff --git a/test/parallel/test-tls-no-cert-required.js b/test/parallel/test-tls-no-cert-required.js index b3dcfa516ab502..499ab2dfd14ed2 100644 --- a/test/parallel/test-tls-no-cert-required.js +++ b/test/parallel/test-tls-no-cert-required.js @@ -28,10 +28,15 @@ const assert = require('assert'); const tls = require('tls'); // Omitting the cert or pfx option to tls.createServer() should not throw. -// AECDH-NULL-SHA is a no-authentication/no-encryption cipher and hence -// doesn't need a certificate. -tls.createServer({ ciphers: 'AECDH-NULL-SHA' }) - .listen(0, common.mustCall(close)); +if (process.features.openssl_is_boringssl) { + // AECDH-NULL-SHA is a no-authentication/no-encryption cipher and hence + // does not need a certificate. BoringSSL does not provide that anonymous + // cipher suite, so only this cipher-specific no-cert case is skipped. + common.printSkipMessage('BoringSSL: skipping anonymous AECDH-NULL-SHA case'); +} else { + tls.createServer({ ciphers: 'AECDH-NULL-SHA' }) + .listen(0, common.mustCall(close)); +} tls.createServer(assert.fail) .listen(0, common.mustCall(close)); diff --git a/test/parallel/test-tls-options-boolean-check.js b/test/parallel/test-tls-options-boolean-check.js index 900a39f0c1cd42..f7dd7bb102f361 100644 --- a/test/parallel/test-tls-options-boolean-check.js +++ b/test/parallel/test-tls-options-boolean-check.js @@ -40,9 +40,23 @@ const keyDataView = toDataView(keyBuff); const certDataView = toDataView(certBuff); const caArrDataView = toDataView(caCert); +function filterBoringSSLKeyCertArrayCases(options, setName) { + if (!process.features.openssl_is_boringssl) + return options; + + // The array-valued cases exercise multi-identity key/cert handling. + // BoringSSL may reject those cases with backend key/cert mismatch errors + // before the boolean/type validation this test is targeting. Keep the scalar + // cases so tls.createServer() option type validation is still covered. + common.printSkipMessage( + `BoringSSL: skipping ${setName} key/cert array cases`); + return options.filter(([key, cert]) => !Array.isArray(key) && + !Array.isArray(cert)); +} + // Checks to ensure tls.createServer doesn't throw an error // Format ['key', 'cert'] -[ +const validOptions = [ [keyBuff, certBuff], [false, certBuff], [keyBuff, false], @@ -62,13 +76,16 @@ const caArrDataView = toDataView(caCert); [false, [certStr, certStr2]], [[{ pem: keyBuff }], false], [[{ pem: keyBuff }, { pem: keyBuff }], false], -].forEach(([key, cert]) => { - tls.createServer({ key, cert }); -}); +]; + +filterBoringSSLKeyCertArrayCases(validOptions, 'valid') + .forEach(([key, cert]) => { + tls.createServer({ key, cert }); + }); // Checks to ensure tls.createServer predictably throws an error // Format ['key', 'cert', 'expected message'] -[ +const invalidKeyOptions = [ [true, certBuff], [true, certStr], [true, certArrBuff], @@ -80,7 +97,10 @@ const caArrDataView = toDataView(caCert); [[true, keyStr2], [certStr, certStr2], 0], [[true, false], [certBuff, certBuff2], 0], [true, [certBuff, certBuff2]], -].forEach(([key, cert, index]) => { +]; + +for (const [key, cert, index] of + filterBoringSSLKeyCertArrayCases(invalidKeyOptions, 'invalid key')) { const val = index === undefined ? key : key[index]; assert.throws(() => { tls.createServer({ key, cert }); @@ -91,9 +111,9 @@ const caArrDataView = toDataView(caCert); 'instance of Buffer, TypedArray, or DataView.' + common.invalidArgTypeHelper(val) }); -}); +} -[ +const invalidCertOptions = [ [keyBuff, true], [keyStr, true], [keyArrBuff, true], @@ -106,7 +126,10 @@ const caArrDataView = toDataView(caCert); [[keyStr, keyStr2], [certStr, true], 1], [[keyStr, keyStr2], [true, false], 0], [[keyStr, keyStr2], true], -].forEach(([key, cert, index]) => { +]; + +for (const [key, cert, index] of + filterBoringSSLKeyCertArrayCases(invalidCertOptions, 'invalid cert')) { const val = index === undefined ? cert : cert[index]; assert.throws(() => { tls.createServer({ key, cert }); @@ -117,7 +140,7 @@ const caArrDataView = toDataView(caCert); 'instance of Buffer, TypedArray, or DataView.' + common.invalidArgTypeHelper(val) }); -}); +} // Checks to ensure tls.createServer works with the CA parameter // Format ['key', 'cert', 'ca'] diff --git a/test/parallel/test-tls-passphrase.js b/test/parallel/test-tls-passphrase.js index 8d802400f6ee3b..4372da249bb509 100644 --- a/test/parallel/test-tls-passphrase.js +++ b/test/parallel/test-tls-passphrase.js @@ -223,7 +223,7 @@ server.listen(0, common.mustCall(function() { }, onSecureConnect()); })).unref(); -const errMessageDecrypt = /bad decrypt/; +const errMessageDecrypt = /bad[ _]decrypt/i; // Missing passphrase assert.throws(function() { diff --git a/test/parallel/test-tls-psk-alpn-callback-exception-handling.js b/test/parallel/test-tls-psk-alpn-callback-exception-handling.js index 881215672ecd0d..cdeb9f3b31f8fe 100644 --- a/test/parallel/test-tls-psk-alpn-callback-exception-handling.js +++ b/test/parallel/test-tls-psk-alpn-callback-exception-handling.js @@ -14,6 +14,11 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').testPskTls13Unsupported(); + return; +} + const assert = require('assert'); const { describe, it } = require('node:test'); const tls = require('tls'); diff --git a/test/parallel/test-tls-psk-circuit.js b/test/parallel/test-tls-psk-circuit.js index bdf9c86c26a7b6..c9c93d53350165 100644 --- a/test/parallel/test-tls-psk-circuit.js +++ b/test/parallel/test-tls-psk-circuit.js @@ -5,6 +5,11 @@ if (!common.hasCrypto) { common.skip('missing crypto'); } +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').testPskTls13Unsupported(); + return; +} + const { hasOpenSSL } = require('../common/crypto'); const assert = require('assert'); const tls = require('tls'); diff --git a/test/parallel/test-tls-psk-server.js b/test/parallel/test-tls-psk-server.js index af038493469880..692550fc1c198b 100644 --- a/test/parallel/test-tls-psk-server.js +++ b/test/parallel/test-tls-psk-server.js @@ -5,6 +5,11 @@ if (!common.hasCrypto) { common.skip('missing crypto'); } +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').testPskTls13Unsupported(); + return; +} + const { opensslCli } = require('../common/crypto'); if (!opensslCli) { diff --git a/test/parallel/test-tls-reduced-SECLEVEL-in-cipher.js b/test/parallel/test-tls-reduced-SECLEVEL-in-cipher.js index 9f4458e0a7d671..cca22067a0fe19 100644 --- a/test/parallel/test-tls-reduced-SECLEVEL-in-cipher.js +++ b/test/parallel/test-tls-reduced-SECLEVEL-in-cipher.js @@ -4,6 +4,11 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').assertOpenSSLSecurityLevelsUnsupported(); + return; +} + const assert = require('assert'); const tls = require('tls'); const fixtures = require('../common/fixtures'); diff --git a/test/parallel/test-tls-server-failed-handshake-emits-clienterror.js b/test/parallel/test-tls-server-failed-handshake-emits-clienterror.js index 2fb43b9cbbf87a..9c30989af0afb3 100644 --- a/test/parallel/test-tls-server-failed-handshake-emits-clienterror.js +++ b/test/parallel/test-tls-server-failed-handshake-emits-clienterror.js @@ -22,7 +22,7 @@ const server = tls.createServer({}) 'Instance of Error should be passed to error handler'); assert.match( e.message, - /SSL routines:[^:]*:wrong version number/, + /SSL routines:[^:]*:wrong[ _]version[ _]number/i, ); server.close(); diff --git a/test/parallel/test-tls-server-verify.js b/test/parallel/test-tls-server-verify.js index 94f372d37a3b1f..439e321310305a 100644 --- a/test/parallel/test-tls-server-verify.js +++ b/test/parallel/test-tls-server-verify.js @@ -47,7 +47,7 @@ const { SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION } = const tls = require('tls'); const fixtures = require('../common/fixtures'); -const testCases = +let testCases = [{ title: 'Do not request certs. Everyone is unauthorized.', requestCert: false, rejectUnauthorized: false, @@ -125,6 +125,15 @@ const testCases = ] }, ]; +if (process.features.openssl_is_boringssl) { + // Remove the delayed client-certificate verification case. It depends on TLS + // renegotiation to request a client certificate after the initial handshake, + // but BoringSSL does not support caller-initiated renegotiation. + common.printSkipMessage( + 'BoringSSL: skipping renegotiated client certificate verification case'); + testCases = testCases.filter((tcase) => !tcase.renegotiate); +} + function filenamePEM(n) { return fixtures.path('keys', `${n}.pem`); } diff --git a/test/parallel/test-tls-session-cache.js b/test/parallel/test-tls-session-cache.js index aaf9c2c03c83e9..ae560e567980c9 100644 --- a/test/parallel/test-tls-session-cache.js +++ b/test/parallel/test-tls-session-cache.js @@ -37,6 +37,7 @@ const fixtures = require('../common/fixtures'); const assert = require('assert'); const tls = require('tls'); const { spawn } = require('child_process'); +const isBoringSSL = process.features.openssl_is_boringssl; doTest({ tickets: false }, function() { doTest({ tickets: true }, function() { @@ -56,7 +57,9 @@ function doTest(testOptions, callback) { requestCert: true, rejectUnauthorized: false, secureProtocol: 'TLS_method', - ciphers: 'RSA@SECLEVEL=0' + // BoringSSL supports the RSA cipher selector, but not OpenSSL's + // cipher-string policy command syntax. + ciphers: isBoringSSL ? 'RSA' : 'RSA@SECLEVEL=0' }; let requestCount = 0; let resumeCount = 0; @@ -105,7 +108,7 @@ function doTest(testOptions, callback) { server.listen(0, common.mustCall(function() { const args = [ 's_client', - '-tls1', + isBoringSSL ? '-tls1_2' : '-tls1', '-cipher', (hasOpenSSL(3, 1) ? 'DEFAULT:@SECLEVEL=0' : 'DEFAULT'), '-connect', `localhost:${this.address().port}`, '-servername', 'ohgod', diff --git a/test/parallel/test-tls-set-ciphers-error.js b/test/parallel/test-tls-set-ciphers-error.js index 3cfc8c391bf7d5..b79bd512ffe1db 100644 --- a/test/parallel/test-tls-set-ciphers-error.js +++ b/test/parallel/test-tls-set-ciphers-error.js @@ -21,8 +21,12 @@ const { hasOpenSSL } = require('../common/crypto'); assert.throws(() => tls.createServer(options, common.mustNotCall()), /no[_ ]cipher[_ ]match/i); options.ciphers = 'TLS_not_a_cipher'; - assert.throws(() => tls.createServer(options, common.mustNotCall()), - /no[_ ]cipher[_ ]match/i); + if (process.features.openssl_is_boringssl) { + tls.createServer(options).close(); + } else { + assert.throws(() => tls.createServer(options, common.mustNotCall()), + /no[_ ]cipher[_ ]match/i); + } } // Cipher name matching is case-sensitive prior to OpenSSL 4.0, and diff --git a/test/parallel/test-tls-set-default-ca-certificates-recovery.js b/test/parallel/test-tls-set-default-ca-certificates-recovery.js index e3eb0e84149ae8..ea6f98d5686e03 100644 --- a/test/parallel/test-tls-set-default-ca-certificates-recovery.js +++ b/test/parallel/test-tls-set-default-ca-certificates-recovery.js @@ -27,7 +27,9 @@ function testRecovery(expectedCerts) { { const invalidCert = '-----BEGIN CERTIFICATE-----\nvalid cert content\n-----END CERTIFICATE-----'; assert.throws(() => tls.setDefaultCACertificates([fixtureCert, invalidCert]), { - code: 'ERR_OSSL_PEM_ASN1_LIB', + code: process.features.openssl_is_boringssl ? + 'ERR_OSSL_PEM_ASN.1_ENCODING_ROUTINES' : + 'ERR_OSSL_PEM_ASN1_LIB', }); assertEqualCerts(tls.getCACertificates('default'), expectedCerts); } diff --git a/test/parallel/test-tls-set-sigalgs.js b/test/parallel/test-tls-set-sigalgs.js index 1bce814f3e8604..e1bf8b93f8a342 100644 --- a/test/parallel/test-tls-set-sigalgs.js +++ b/test/parallel/test-tls-set-sigalgs.js @@ -39,9 +39,14 @@ function test(csigalgs, ssigalgs, shared_sigalgs, cerr, serr) { assert.ifError(pair.client.err); assert(pair.server.conn); assert(pair.client.conn); + // BoringSSL's OpenSSL-compatible SSL_get_shared_sigalgs() API always + // returns zero, so a successful handshake still reports an empty list. + const expectedSharedSigalgs = process.features.openssl_is_boringssl ? + [] : + shared_sigalgs; assert.deepStrictEqual( pair.server.conn.getSharedSigalgs(), - shared_sigalgs + expectedSharedSigalgs ); } else { if (serr) { @@ -69,10 +74,13 @@ test('RSA-PSS+SHA256:RSA-PSS+SHA512:ECDSA+SHA256', const handshakeErr = hasOpenSSL(4, 0) ? 'ERR_SSL_TLS_ALERT_HANDSHAKE_FAILURE' : hasOpenSSL(3, 2) ? 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; +const noSharedSigalgsErr = process.features.openssl_is_boringssl ? + 'ERR_SSL_NO_COMMON_SIGNATURE_ALGORITHMS' : + 'ERR_SSL_NO_SHARED_SIGNATURE_ALGORITHMS'; test('RSA-PSS+SHA384', 'ECDSA+SHA256', undefined, handshakeErr, - 'ERR_SSL_NO_SHARED_SIGNATURE_ALGORITHMS'); + noSharedSigalgsErr); test('RSA-PSS+SHA384:ECDSA+SHA256', 'ECDSA+SHA384:RSA-PSS+SHA256', undefined, handshakeErr, - 'ERR_SSL_NO_SHARED_SIGNATURE_ALGORITHMS'); + noSharedSigalgsErr); diff --git a/test/parallel/test-tls-socket-failed-handshake-emits-error.js b/test/parallel/test-tls-socket-failed-handshake-emits-error.js index c88f0c3a1855f2..c64d4ad4aabe8d 100644 --- a/test/parallel/test-tls-socket-failed-handshake-emits-error.js +++ b/test/parallel/test-tls-socket-failed-handshake-emits-error.js @@ -22,7 +22,7 @@ const server = net.createServer(common.mustCall((c) => { 'Instance of Error should be passed to error handler'); assert.match( e.message, - /SSL routines:[^:]*:wrong version number/, + /SSL routines:[^:]*:wrong[ _]version[ _]number/i, ); })); diff --git a/test/parallel/test-tls-ticket-cluster.js b/test/parallel/test-tls-ticket-cluster.js index 2ed4abb93c8d47..f183b53f24c0b9 100644 --- a/test/parallel/test-tls-ticket-cluster.js +++ b/test/parallel/test-tls-ticket-cluster.js @@ -24,6 +24,11 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').testTls13SessionTicketSemanticsDiffer(); + return; +} + const assert = require('assert'); const tls = require('tls'); const cluster = require('cluster'); diff --git a/test/parallel/test-tls-ticket.js b/test/parallel/test-tls-ticket.js index 0a77e52fb275cd..8316f5e8da8d8f 100644 --- a/test/parallel/test-tls-ticket.js +++ b/test/parallel/test-tls-ticket.js @@ -30,6 +30,12 @@ const net = require('net'); const crypto = require('crypto'); const fixtures = require('../common/fixtures'); +if (process.features.openssl_is_boringssl && + tls.DEFAULT_MAX_VERSION !== 'TLSv1.2') { + require('../common/boringssl').testTls13SessionTicketSemanticsDiffer(); + return; +} + const keys = crypto.randomBytes(48); const serverLog = []; const ticketLog = []; diff --git a/test/parallel/test-webcrypto-aead-decrypt-detached-buffer.js b/test/parallel/test-webcrypto-aead-decrypt-detached-buffer.js index a96e709095430f..316d706e7b7948 100644 --- a/test/parallel/test-webcrypto-aead-decrypt-detached-buffer.js +++ b/test/parallel/test-webcrypto-aead-decrypt-detached-buffer.js @@ -29,14 +29,11 @@ async function test(algorithmName, keyLength, ivLength, format = 'raw') { const tests = [ test('AES-GCM', 32, 12), + test('ChaCha20-Poly1305', 32, 12, 'raw-secret'), ]; if (hasOpenSSL(3)) { tests.push(test('AES-OCB', 32, 12, 'raw-secret')); } -if (!process.features.openssl_is_boringssl) { - tests.push(test('ChaCha20-Poly1305', 32, 12, 'raw-secret')); -} - Promise.all(tests).then(common.mustCall()); diff --git a/test/parallel/test-webcrypto-deduplicate-usages.js b/test/parallel/test-webcrypto-deduplicate-usages.js index e30dbe7887166e..70b35f6cfa3849 100644 --- a/test/parallel/test-webcrypto-deduplicate-usages.js +++ b/test/parallel/test-webcrypto-deduplicate-usages.js @@ -42,17 +42,13 @@ function assertSameSet(actual, expected, msg) { { algorithm: { name: 'AES-GCM', length: 128 }, usages: ['decrypt', 'encrypt', 'decrypt'], expected: ['encrypt', 'decrypt'] }, - ]; - - if (!process.features.openssl_is_boringssl) { - symmetric.push({ - algorithm: { name: 'AES-KW', length: 128 }, + { algorithm: { name: 'AES-KW', length: 128 }, usages: ['wrapKey', 'unwrapKey', 'wrapKey', 'unwrapKey'], - expected: ['wrapKey', 'unwrapKey'], - }); - } else { - common.printSkipMessage('AES-KW is not supported in BoringSSL'); - } + expected: ['wrapKey', 'unwrapKey'] }, + { algorithm: { name: 'ChaCha20-Poly1305' }, + usages: ['wrapKey', 'decrypt', 'encrypt', 'unwrapKey', 'wrapKey', 'encrypt'], + expected: ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'] }, + ]; if (hasOpenSSL(3)) { symmetric.push({ @@ -69,16 +65,6 @@ function assertSameSet(actual, expected, msg) { common.printSkipMessage('AES-OCB and KMAC require OpenSSL >= 3'); } - if (!process.features.openssl_is_boringssl) { - symmetric.push({ - algorithm: { name: 'ChaCha20-Poly1305' }, - usages: ['wrapKey', 'decrypt', 'encrypt', 'unwrapKey', 'wrapKey', 'encrypt'], - expected: ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'], - }); - } else { - common.printSkipMessage('ChaCha20-Poly1305 is not supported in BoringSSL'); - } - for (const { algorithm, usages, expected } of symmetric) { tests.push((async () => { const key = await subtle.generateKey(algorithm, true, usages); @@ -121,7 +107,7 @@ function assertSameSet(actual, expected, msg) { privateExpected: ['deriveKey', 'deriveBits'] }, ]; - if (hasOpenSSL(3, 5)) { + if (hasOpenSSL(3, 5) || process.features.openssl_is_boringssl) { asymmetric.push({ algorithm: { name: 'ML-DSA-65' }, usages: ['verify', 'sign', 'verify', 'sign'], @@ -136,7 +122,7 @@ function assertSameSet(actual, expected, msg) { privateExpected: ['decapsulateKey', 'decapsulateBits'], }); } else { - common.printSkipMessage('ML-DSA and ML-KEM require OpenSSL >= 3.5'); + common.printSkipMessage('ML-DSA and ML-KEM require OpenSSL >= 3.5 or BoringSSL'); } for (const { algorithm, usages, publicExpected, privateExpected } of asymmetric) { @@ -172,17 +158,10 @@ function assertSameSet(actual, expected, msg) { { algorithm: { name: 'HMAC', hash: 'SHA-256' }, keyData: new Uint8Array(32), usages: ['verify', 'sign', 'verify', 'sign'], expected: ['sign', 'verify'] }, - ]; - - if (!process.features.openssl_is_boringssl) { - rawSymmetric.push({ - algorithm: { name: 'AES-KW' }, keyData: new Uint8Array(16), + { algorithm: { name: 'AES-KW' }, keyData: new Uint8Array(16), usages: ['wrapKey', 'unwrapKey', 'wrapKey'], - expected: ['wrapKey', 'unwrapKey'], - }); - } else { - common.printSkipMessage('AES-KW is not supported in BoringSSL'); - } + expected: ['wrapKey', 'unwrapKey'] }, + ]; if (hasOpenSSL(3)) { // KMAC does not support `raw` format, only `raw-secret` and `jwk`. @@ -310,7 +289,7 @@ function assertSameSet(actual, expected, msg) { assert.deepStrictEqual(imported.usages, ['sign']); })()); - if (hasOpenSSL(3, 5)) { + if (hasOpenSSL(3, 5) || process.features.openssl_is_boringssl) { // ML-DSA JWK roundtrip. tests.push((async () => { const { privateKey } = await subtle.generateKey( @@ -336,7 +315,7 @@ function assertSameSet(actual, expected, msg) { ['decapsulateKey', 'decapsulateBits']); })()); } else { - common.printSkipMessage('ML-DSA and ML-KEM require OpenSSL >= 3.5'); + common.printSkipMessage('ML-DSA and ML-KEM require OpenSSL >= 3.5 or BoringSSL'); } // Spki import of RSA public key. @@ -356,20 +335,16 @@ function assertSameSet(actual, expected, msg) { })()); // ChaCha20-Poly1305 raw-secret import. - if (!process.features.openssl_is_boringssl) { - tests.push((async () => { - const key = await subtle.importKey( - 'raw-secret', - new Uint8Array(32), - { name: 'ChaCha20-Poly1305' }, - true, - ['decrypt', 'encrypt', 'decrypt', 'encrypt']); - assertSameSet(key.usages, ['encrypt', 'decrypt']); - assert.strictEqual(key.usages.length, 2); - })()); - } else { - common.printSkipMessage('ChaCha20-Poly1305 is not supported in BoringSSL'); - } + tests.push((async () => { + const key = await subtle.importKey( + 'raw-secret', + new Uint8Array(32), + { name: 'ChaCha20-Poly1305' }, + true, + ['decrypt', 'encrypt', 'decrypt', 'encrypt']); + assertSameSet(key.usages, ['encrypt', 'decrypt']); + assert.strictEqual(key.usages.length, 2); + })()); // AES-OCB raw-secret import. if (hasOpenSSL(3)) { @@ -455,17 +430,10 @@ function assertSameSet(actual, expected, msg) { { algorithm: { name: 'AES-GCM', length: 128 }, usages: ['decrypt', 'encrypt', 'decrypt'], expected: ['encrypt', 'decrypt'] }, - ]; - - if (!process.features.openssl_is_boringssl) { - jwkVectors.push({ - algorithm: { name: 'AES-KW', length: 128 }, + { algorithm: { name: 'AES-KW', length: 128 }, usages: ['wrapKey', 'unwrapKey', 'wrapKey', 'unwrapKey'], - expected: ['wrapKey', 'unwrapKey'], - }); - } else { - common.printSkipMessage('AES-KW is not supported in BoringSSL'); - } + expected: ['wrapKey', 'unwrapKey'] }, + ]; if (hasOpenSSL(3)) { jwkVectors.push({ @@ -523,7 +491,7 @@ function assertSameSet(actual, expected, msg) { privateExpected: ['deriveKey', 'deriveBits'] }, ]; - if (hasOpenSSL(3, 5)) { + if (hasOpenSSL(3, 5) || process.features.openssl_is_boringssl) { jwkPairVectors.push({ algorithm: { name: 'ML-DSA-65' }, usages: ['verify', 'sign', 'verify', 'sign'], @@ -538,7 +506,7 @@ function assertSameSet(actual, expected, msg) { privateExpected: ['decapsulateKey', 'decapsulateBits'], }); } else { - common.printSkipMessage('ML-DSA and ML-KEM require OpenSSL >= 3.5'); + common.printSkipMessage('ML-DSA and ML-KEM require OpenSSL >= 3.5 or BoringSSL'); } for (const { algorithm, usages, publicExpected, privateExpected } of jwkPairVectors) { diff --git a/test/parallel/test-webcrypto-derivebits-hkdf.js b/test/parallel/test-webcrypto-derivebits-hkdf.js index 689eaeb38fd66f..d2057d1f782e7f 100644 --- a/test/parallel/test-webcrypto-derivebits-hkdf.js +++ b/test/parallel/test-webcrypto-derivebits-hkdf.js @@ -24,12 +24,12 @@ const kDerivedKeyTypes = [ ['HMAC', 256, 'SHA-256', 'sign', 'verify'], ['HMAC', 256, 'SHA-384', 'sign', 'verify'], ['HMAC', 256, 'SHA-512', 'sign', 'verify'], + ['AES-KW', 128, undefined, 'wrapKey', 'unwrapKey'], + ['AES-KW', 256, undefined, 'wrapKey', 'unwrapKey'], ]; if (!process.features.openssl_is_boringssl) { kDerivedKeyTypes.push( - ['AES-KW', 128, undefined, 'wrapKey', 'unwrapKey'], - ['AES-KW', 256, undefined, 'wrapKey', 'unwrapKey'], ['HMAC', 256, 'SHA3-256', 'sign', 'verify'], ['HMAC', 256, 'SHA3-384', 'sign', 'verify'], ['HMAC', 256, 'SHA3-512', 'sign', 'verify'], diff --git a/test/parallel/test-webcrypto-encap-decap-ml-kem.js b/test/parallel/test-webcrypto-encap-decap-ml-kem.js index 450ba2cefb0a4f..958a4d240db148 100644 --- a/test/parallel/test-webcrypto-encap-decap-ml-kem.js +++ b/test/parallel/test-webcrypto-encap-decap-ml-kem.js @@ -7,8 +7,8 @@ if (!common.hasCrypto) const { hasOpenSSL } = require('../common/crypto'); -if (!hasOpenSSL(3, 5)) - common.skip('requires OpenSSL >= 3.5'); +if (!hasOpenSSL(3, 5) && !process.features.openssl_is_boringssl) + common.skip('requires OpenSSL >= 3.5 or BoringSSL'); const assert = require('assert'); const crypto = require('crypto'); @@ -253,12 +253,16 @@ async function testDecapsulateBits({ name, publicKeyPem, privateKeyPem, results (async function() { const variations = []; - vectors.forEach((vector) => { + for (const vector of vectors) { + if (process.features.openssl_is_boringssl && vector.name === 'ML-KEM-512') { + common.printSkipMessage(`Skipping unsupported ${vector.name} test`); + continue; + } variations.push(testEncapsulateKey(vector)); variations.push(testEncapsulateBits(vector)); variations.push(testDecapsulateKey(vector)); variations.push(testDecapsulateBits(vector)); - }); + } await Promise.all(variations); })().then(common.mustCall()); diff --git a/test/parallel/test-webcrypto-encrypt-decrypt-chacha20-poly1305.js b/test/parallel/test-webcrypto-encrypt-decrypt-chacha20-poly1305.js index 0f930a356712ed..723fd26ea5708b 100644 --- a/test/parallel/test-webcrypto-encrypt-decrypt-chacha20-poly1305.js +++ b/test/parallel/test-webcrypto-encrypt-decrypt-chacha20-poly1305.js @@ -5,9 +5,6 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); -if (process.features.openssl_is_boringssl) - common.skip('Skipping unsupported ChaCha20-Poly1305 test case'); - const assert = require('assert'); const { subtle } = globalThis.crypto; diff --git a/test/parallel/test-webcrypto-export-import-ml-dsa.js b/test/parallel/test-webcrypto-export-import-ml-dsa.js index 63766a7b377c77..20d46870e430f1 100644 --- a/test/parallel/test-webcrypto-export-import-ml-dsa.js +++ b/test/parallel/test-webcrypto-export-import-ml-dsa.js @@ -7,8 +7,8 @@ if (!common.hasCrypto) const { hasOpenSSL } = require('../common/crypto'); -if (!hasOpenSSL(3, 5)) - common.skip('requires OpenSSL >= 3.5'); +if (!hasOpenSSL(3, 5) && !process.features.openssl_is_boringssl) + common.skip('requires OpenSSL >= 3.5 or BoringSSL'); const assert = require('assert'); const { subtle } = globalThis.crypto; @@ -96,12 +96,23 @@ async function testImportSpki({ name, publicUsages }, extractable) { } async function testImportPkcs8({ name, privateUsages }, extractable) { - const key = await subtle.importKey( - 'pkcs8', - keyData[name].pkcs8, - { name }, - extractable, - privateUsages); + let key; + try { + key = await subtle.importKey( + 'pkcs8', + keyData[name].pkcs8, + { name }, + extractable, + privateUsages); + } catch (err) { + if (process.features.openssl_is_boringssl) { + assert.strictEqual(err.name, 'DataError'); + assert.strictEqual(err.cause.code, 'ERR_OSSL_EVP_PRIVATE_KEY_WAS_NOT_SEED'); + common.printSkipMessage('Skipping unsupported private key format test'); + return; + } + throw err; + } assert.strictEqual(key.type, 'private'); assert.strictEqual(key.extractable, extractable); assert.deepStrictEqual(key.usages, privateUsages); @@ -480,14 +491,18 @@ async function testImportRawSeed({ name, privateUsages }, extractable) { }); })().then(common.mustCall()); -(async function() { - for (const { name, privateUsages } of testVectors) { - const pem = fixtures.readKey(getKeyFileName(name.toLowerCase(), 'private_priv_only'), 'ascii'); - const keyObject = createPrivateKey(pem); - const key = keyObject.toCryptoKey({ name }, true, privateUsages); - await assert.rejects(subtle.exportKey('pkcs8', key), (err) => { - assert.strictEqual(err.name, 'OperationError'); - return true; - }); - } -})().then(common.mustCall()); +if (!process.features.openssl_is_boringssl) { + (async function() { + for (const { name, privateUsages } of testVectors) { + const pem = fixtures.readKey(getKeyFileName(name.toLowerCase(), 'private_priv_only'), 'ascii'); + const keyObject = createPrivateKey(pem); + const key = keyObject.toCryptoKey({ name }, true, privateUsages); + await assert.rejects(subtle.exportKey('pkcs8', key), (err) => { + assert.strictEqual(err.name, 'OperationError'); + return true; + }); + } + })().then(common.mustCall()); +} else { + common.printSkipMessage('Skipping unsupported private key format test'); +} diff --git a/test/parallel/test-webcrypto-export-import-ml-kem.js b/test/parallel/test-webcrypto-export-import-ml-kem.js index 332d88d93f69d1..a3b1b3fe773090 100644 --- a/test/parallel/test-webcrypto-export-import-ml-kem.js +++ b/test/parallel/test-webcrypto-export-import-ml-kem.js @@ -7,8 +7,8 @@ if (!common.hasCrypto) const { hasOpenSSL } = require('../common/crypto'); -if (!hasOpenSSL(3, 5)) - common.skip('requires OpenSSL >= 3.5'); +if (!hasOpenSSL(3, 5) && !process.features.openssl_is_boringssl) + common.skip('requires OpenSSL >= 3.5 or BoringSSL'); const assert = require('assert'); const { subtle } = globalThis.crypto; @@ -96,12 +96,26 @@ async function testImportSpki({ name, publicUsages }, extractable) { } async function testImportPkcs8({ name, privateUsages }, extractable) { - const key = await subtle.importKey( - 'pkcs8', - keyData[name].pkcs8, - { name }, - extractable, - privateUsages); + let key; + try { + key = await subtle.importKey( + 'pkcs8', + keyData[name].pkcs8, + { name }, + extractable, + privateUsages); + } catch (err) { + if (process.features.openssl_is_boringssl) { + assert.strictEqual(err.name, 'DataError'); + // It should really only be ERR_OSSL_EVP_PRIVATE_KEY_WAS_NOT_SEED + // but BoringSSL is inconsistent between handling ML-KEM and ML-DSA + // Fixed in https://github.com/google/boringssl/commit/94c4c7f9e0eeeff72ea1ac6abf1aed5bd2a82c0c + assert.match(err.cause.code, /ERR_OSSL_EVP_UNSUPPORTED_ALGORITHM|ERR_OSSL_EVP_PRIVATE_KEY_WAS_NOT_SEED/); + common.printSkipMessage('Skipping unsupported private key format test'); + return; + } + throw err; + } assert.strictEqual(key.type, 'private'); assert.strictEqual(key.extractable, extractable); assert.deepStrictEqual(key.usages, privateUsages); @@ -239,7 +253,7 @@ async function testImportRawPublic({ name, publicUsages }, extractable) { subtle.importKey( 'raw-public', pub, - { name: name === 'ML-KEM-512' ? 'ML-KEM-768' : 'ML-KEM-512' }, + { name: name === 'ML-KEM-768' ? 'ML-KEM-1024' : 'ML-KEM-768' }, extractable, publicUsages), { message: 'Invalid keyData' }); } @@ -415,7 +429,7 @@ async function testImportJwk({ name, publicUsages, privateUsages }, extractable) privateUsages), // Invalid for a public key { message: /Unsupported key usage/ }); - for (const alg of [undefined, name === 'ML-KEM-512' ? 'ML-KEM-1024' : 'ML-KEM-512']) { + for (const alg of [undefined, name === 'ML-KEM-768' ? 'ML-KEM-1024' : 'ML-KEM-768']) { await assert.rejects( subtle.importKey( 'jwk', @@ -457,6 +471,10 @@ async function testImportJwk({ name, publicUsages, privateUsages }, extractable) (async function() { const tests = []; for (const vector of testVectors) { + if (process.features.openssl_is_boringssl && vector.name === 'ML-KEM-512') { + common.printSkipMessage('Skipping unsupported ML-KEM-512 test'); + continue; + } for (const extractable of [true, false]) { tests.push(testImportSpki(vector, extractable)); tests.push(testImportPkcs8(vector, extractable)); @@ -472,26 +490,14 @@ async function testImportJwk({ name, publicUsages, privateUsages }, extractable) })().then(common.mustCall()); (async function() { - const alg = 'ML-KEM-512'; + const alg = 'ML-KEM-768'; const pub = Buffer.from(keyData[alg].jwk.pub, 'base64url'); await assert.rejects(subtle.importKey('raw', pub, alg, false, []), { name: 'NotSupportedError', - message: 'Unable to import ML-KEM-512 using raw format', + message: 'Unable to import ML-KEM-768 using raw format', }); })().then(common.mustCall()); -(async function() { - for (const { name, privateUsages } of testVectors) { - const pem = fixtures.readKey(getKeyFileName(name.toLowerCase(), 'private_priv_only'), 'ascii'); - const keyObject = createPrivateKey(pem); - const key = keyObject.toCryptoKey({ name }, true, privateUsages); - await assert.rejects(subtle.exportKey('pkcs8', key), (err) => { - assert.strictEqual(err.name, 'OperationError'); - return true; - }); - } -})().then(common.mustCall()); - // Regression test: JWK `key_ops` validation must recognize ML-KEM operations // (encapsulateKey, encapsulateBits, decapsulateKey, decapsulateBits) so that // duplicate entries are rejected @@ -504,3 +510,19 @@ async function testImportJwk({ name, publicUsages, privateUsages }, extractable) { name: 'DataError', message: /Duplicate key operation/ }); } })().then(common.mustCall()); + +if (!process.features.openssl_is_boringssl) { + (async function() { + for (const { name, privateUsages } of testVectors) { + const pem = fixtures.readKey(getKeyFileName(name.toLowerCase(), 'private_priv_only'), 'ascii'); + const keyObject = createPrivateKey(pem); + const key = keyObject.toCryptoKey({ name }, true, privateUsages); + await assert.rejects(subtle.exportKey('pkcs8', key), (err) => { + assert.strictEqual(err.name, 'OperationError'); + return true; + }); + } + })().then(common.mustCall()); +} else { + common.printSkipMessage('Skipping unsupported private key format test'); +} diff --git a/test/parallel/test-webcrypto-keygen.js b/test/parallel/test-webcrypto-keygen.js index e57c34436578ab..d73ffd21e563a5 100644 --- a/test/parallel/test-webcrypto-keygen.js +++ b/test/parallel/test-webcrypto-keygen.js @@ -135,6 +135,23 @@ const vectors = { 'deriveBits', ], }, + 'AES-KW': { + algorithm: { length: 256 }, + result: 'CryptoKey', + usages: [ + 'wrapKey', + 'unwrapKey', + ], + }, + 'ChaCha20-Poly1305': { + result: 'CryptoKey', + usages: [ + 'encrypt', + 'decrypt', + 'wrapKey', + 'unwrapKey', + ], + }, }; if (!process.features.openssl_is_boringssl) { @@ -152,23 +169,6 @@ if (!process.features.openssl_is_boringssl) { 'deriveBits', ], }; - vectors['AES-KW'] = { - algorithm: { length: 256 }, - result: 'CryptoKey', - usages: [ - 'wrapKey', - 'unwrapKey', - ], - }; - vectors['ChaCha20-Poly1305'] = { - result: 'CryptoKey', - usages: [ - 'encrypt', - 'decrypt', - 'wrapKey', - 'unwrapKey', - ], - }; } else { common.printSkipMessage('Skipping unsupported test cases'); } @@ -196,7 +196,7 @@ if (hasOpenSSL(3)) { } } -if (hasOpenSSL(3, 5)) { +if (hasOpenSSL(3, 5) || process.features.openssl_is_boringssl) { for (const name of ['ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87']) { vectors[name] = { result: 'CryptoKeyPair', @@ -606,17 +606,10 @@ if (hasOpenSSL(3, 5)) { [ 'AES-CBC', 256, ['encrypt', 'decrypt']], [ 'AES-GCM', 128, ['encrypt', 'decrypt']], [ 'AES-GCM', 256, ['encrypt', 'decrypt']], + [ 'AES-KW', 128, ['wrapKey', 'unwrapKey']], + [ 'AES-KW', 256, ['wrapKey', 'unwrapKey']], ]; - if (!process.features.openssl_is_boringssl) { - kTests.push( - [ 'AES-KW', 128, ['wrapKey', 'unwrapKey']], - [ 'AES-KW', 256, ['wrapKey', 'unwrapKey']], - ); - } else { - common.printSkipMessage('Skipping unsupported AES-KW test cases'); - } - const tests = Promise.all(kTests.map((args) => test(...args))); tests.then(common.mustCall()); @@ -772,7 +765,7 @@ assert.throws(() => new CryptoKey(), { code: 'ERR_ILLEGAL_CONSTRUCTOR' }); } // Test ML-DSA Key Generation -if (hasOpenSSL(3, 5)) { +if (hasOpenSSL(3, 5) || process.features.openssl_is_boringssl) { async function test( name, privateUsages, @@ -815,7 +808,7 @@ if (hasOpenSSL(3, 5)) { } // Test ML-KEM Key Generation -if (hasOpenSSL(3, 5)) { +if (hasOpenSSL(3, 5) || process.features.openssl_is_boringssl) { async function test( name, privateUsages, @@ -850,7 +843,13 @@ if (hasOpenSSL(3, 5)) { assert.strictEqual(publicKey.usages, publicKey.usages); } - const kTests = ['ML-KEM-512', 'ML-KEM-768', 'ML-KEM-1024']; + const kTests = ['ML-KEM-768', 'ML-KEM-1024']; + + if (!process.features.openssl_is_boringssl) { + kTests.unshift('ML-KEM-512'); + } else { + common.printSkipMessage('Skipping unsupported ML-KEM-512 test'); + } const tests = kTests.map((name) => test(name, ['decapsulateKey', 'decapsulateBits'], diff --git a/test/parallel/test-webcrypto-promise-prototype-pollution.mjs b/test/parallel/test-webcrypto-promise-prototype-pollution.mjs index 3ea0a961f41b90..d479abe3dcc989 100644 --- a/test/parallel/test-webcrypto-promise-prototype-pollution.mjs +++ b/test/parallel/test-webcrypto-promise-prototype-pollution.mjs @@ -59,28 +59,24 @@ await subtle.deriveKey( true, ['encrypt', 'decrypt']); -if (!process.features.openssl_is_boringssl) { - const wrappingKey = await subtle.generateKey( - { name: 'AES-KW', length: 256 }, true, ['wrapKey', 'unwrapKey']); +const wrappingKey = await subtle.generateKey( + { name: 'AES-KW', length: 256 }, true, ['wrapKey', 'unwrapKey']); - const keyToWrap = await subtle.generateKey( - { name: 'AES-CBC', length: 256 }, true, ['encrypt', 'decrypt']); +const keyToWrap = await subtle.generateKey( + { name: 'AES-CBC', length: 256 }, true, ['encrypt', 'decrypt']); - const wrapped = await subtle.wrapKey('raw', keyToWrap, wrappingKey, 'AES-KW'); +const wrapped = await subtle.wrapKey('raw', keyToWrap, wrappingKey, 'AES-KW'); - await subtle.unwrapKey( - 'raw', wrapped, wrappingKey, 'AES-KW', - { name: 'AES-CBC', length: 256 }, true, ['encrypt', 'decrypt']); -} else { - common.printSkipMessage('Skipping unsupported AES-KW test case'); -} +await subtle.unwrapKey( + 'raw', wrapped, wrappingKey, 'AES-KW', + { name: 'AES-CBC', length: 256 }, true, ['encrypt', 'decrypt']); const { privateKey } = await subtle.generateKey( { name: 'ECDSA', namedCurve: 'P-256' }, true, ['sign', 'verify']); await subtle.getPublicKey(privateKey, ['verify']); -if (hasOpenSSL(3, 5)) { +if (hasOpenSSL(3, 5) || process.features.openssl_is_boringssl) { const kemPair = await subtle.generateKey( { name: 'ML-KEM-768' }, false, ['encapsulateKey', 'encapsulateBits', 'decapsulateKey', 'decapsulateBits']); diff --git a/test/parallel/test-webcrypto-sign-verify-ml-dsa.js b/test/parallel/test-webcrypto-sign-verify-ml-dsa.js index 1ed74c2508f438..b11e65ade79185 100644 --- a/test/parallel/test-webcrypto-sign-verify-ml-dsa.js +++ b/test/parallel/test-webcrypto-sign-verify-ml-dsa.js @@ -7,8 +7,8 @@ if (!common.hasCrypto) const { hasOpenSSL } = require('../common/crypto'); -if (!hasOpenSSL(3, 5)) - common.skip('requires OpenSSL >= 3.5'); +if (!hasOpenSSL(3, 5) && !process.features.openssl_is_boringssl) + common.skip('requires OpenSSL >= 3.5 or BoringSSL'); const assert = require('assert'); const crypto = require('crypto'); diff --git a/test/parallel/test-webcrypto-sign-verify.js b/test/parallel/test-webcrypto-sign-verify.js index 26e66d9aa0fa8b..0a6f5cffe7b934 100644 --- a/test/parallel/test-webcrypto-sign-verify.js +++ b/test/parallel/test-webcrypto-sign-verify.js @@ -173,7 +173,7 @@ if (!process.features.openssl_is_boringssl) { } // Test Sign/Verify ML-DSA -if (hasOpenSSL(3, 5)) { +if (hasOpenSSL(3, 5) || process.features.openssl_is_boringssl) { async function test(name, data) { const ec = new TextEncoder(); const { publicKey, privateKey } = await subtle.generateKey({ diff --git a/test/parallel/test-webcrypto-wrap-unwrap.js b/test/parallel/test-webcrypto-wrap-unwrap.js index 8c57111daebca6..49f63e215fadfc 100644 --- a/test/parallel/test-webcrypto-wrap-unwrap.js +++ b/test/parallel/test-webcrypto-wrap-unwrap.js @@ -39,25 +39,20 @@ const kWrappingData = { }, pair: false }, -}; - -if (!process.features.openssl_is_boringssl) { - kWrappingData['AES-KW'] = { + 'AES-KW': { generate: { length: 128 }, wrap: { }, pair: false - }; - kWrappingData['ChaCha20-Poly1305'] = { + }, + 'ChaCha20-Poly1305': { wrap: { iv: new Uint8Array(12), additionalData: new Uint8Array(16), tagLength: 128 }, pair: false - }; -} else { - common.printSkipMessage('Skipping unsupported AES-KW test case'); -} + } +}; if (hasOpenSSL(3)) { kWrappingData['AES-OCB'] = { @@ -188,34 +183,24 @@ async function generateKeysToWrap() { usages: ['sign', 'verify'], pair: false, }, - ]; - - if (!process.features.openssl_is_boringssl) { - parameters.push({ + { algorithm: { name: 'AES-KW', length: 128 }, usages: ['wrapKey', 'unwrapKey'], pair: false, - }); - } else { - common.printSkipMessage('Skipping unsupported AES-KW test case'); - } - - if (!process.features.openssl_is_boringssl) { - parameters.push({ + }, + { algorithm: { name: 'ChaCha20-Poly1305' }, usages: ['encrypt', 'decrypt'], pair: false, - }); - } else { - common.printSkipMessage('Skipping unsupported ChaCha20-Poly1305 test case'); - } + }, + ]; - if (hasOpenSSL(3, 5)) { + if (hasOpenSSL(3, 5) || process.features.openssl_is_boringssl) { for (const name of ['ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87']) { parameters.push({ algorithm: { name }, diff --git a/test/parallel/test-x509-escaping.js b/test/parallel/test-x509-escaping.js index a5937a09cb1535..ab91e334555669 100644 --- a/test/parallel/test-x509-escaping.js +++ b/test/parallel/test-x509-escaping.js @@ -438,7 +438,9 @@ const { hasOpenSSL3 } = require('../common/crypto'); const cert = fixtures.readKey('incorrect_san_correct_subject-cert.pem'); // The hostname is the CN, but not a SAN entry. - const servername = process.features.openssl_is_boringssl ? undefined : 'good.example.com'; + const servername = 'good.example.com'; + const cnFallback = process.features.openssl_is_boringssl ? undefined : + servername; const certX509 = new X509Certificate(cert); assert.strictEqual(certX509.subject, `CN=${servername}`); assert.strictEqual(certX509.subjectAltName, 'DNS:evil.example.com'); @@ -448,7 +450,7 @@ const { hasOpenSSL3 } = require('../common/crypto'); assert.strictEqual(certX509.checkHost(servername, { subject: 'default' }), undefined); assert.strictEqual(certX509.checkHost(servername, { subject: 'always' }), - servername); + cnFallback); assert.strictEqual(certX509.checkHost(servername, { subject: 'never' }), undefined); @@ -483,11 +485,13 @@ const { hasOpenSSL3 } = require('../common/crypto'); assert.strictEqual(certX509.subjectAltName, 'IP Address:1.2.3.4'); // The newer X509Certificate API allows customizing this behavior: - assert.strictEqual(certX509.checkHost(servername), servername); + const cnFallback = process.features.openssl_is_boringssl ? undefined : + servername; + assert.strictEqual(certX509.checkHost(servername), cnFallback); assert.strictEqual(certX509.checkHost(servername, { subject: 'default' }), - servername); + cnFallback); assert.strictEqual(certX509.checkHost(servername, { subject: 'always' }), - servername); + cnFallback); assert.strictEqual(certX509.checkHost(servername, { subject: 'never' }), undefined); diff --git a/test/pummel/test-webcrypto-derivebits-pbkdf2.js b/test/pummel/test-webcrypto-derivebits-pbkdf2.js index cbe64bff77505c..bfb01ac0c94fe0 100644 --- a/test/pummel/test-webcrypto-derivebits-pbkdf2.js +++ b/test/pummel/test-webcrypto-derivebits-pbkdf2.js @@ -28,17 +28,10 @@ const kDerivedKeyTypes = [ ['HMAC', 256, 'SHA-256', 'sign', 'verify'], ['HMAC', 256, 'SHA-384', 'sign', 'verify'], ['HMAC', 256, 'SHA-512', 'sign', 'verify'], + ['AES-KW', 128, undefined, 'wrapKey', 'unwrapKey'], + ['AES-KW', 256, undefined, 'wrapKey', 'unwrapKey'], ]; -if (!process.features.openssl_is_boringssl) { - kDerivedKeyTypes.push( - ['AES-KW', 128, undefined, 'wrapKey', 'unwrapKey'], - ['AES-KW', 256, undefined, 'wrapKey', 'unwrapKey'], - ); -} else { - common.printSkipMessage('Skipping unsupported AES-KW test cases'); -} - const kPasswords = { short: '5040737377307264', long: '55736572732073686f756c64207069636b206c6f6' + diff --git a/test/sequential/test-tls-connect.js b/test/sequential/test-tls-connect.js index 189b9afa6352bb..ca8a1d8128554e 100644 --- a/test/sequential/test-tls-connect.js +++ b/test/sequential/test-tls-connect.js @@ -57,5 +57,5 @@ const tls = require('tls'); port: common.PORT, ciphers: 'rick-128-roll', }, common.mustNotCall()); - }, /no cipher match/i); + }, /no[_ ]cipher[_ ]match/i); } diff --git a/test/sequential/test-tls-psk-client.js b/test/sequential/test-tls-psk-client.js index 65e628a6f4e0eb..2eb6228f79f265 100644 --- a/test/sequential/test-tls-psk-client.js +++ b/test/sequential/test-tls-psk-client.js @@ -5,6 +5,11 @@ if (!common.hasCrypto) { common.skip('missing crypto'); } +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').testPskTls13Unsupported(); + return; +} + const { opensslCli } = require('../common/crypto'); if (!opensslCli) { diff --git a/test/wpt/status/WebCryptoAPI.cjs b/test/wpt/status/WebCryptoAPI.cjs index 722a0b38398e1d..e3bb8e415b398d 100644 --- a/test/wpt/status/WebCryptoAPI.cjs +++ b/test/wpt/status/WebCryptoAPI.cjs @@ -6,16 +6,27 @@ const { hasOpenSSL } = require('../../common/crypto.js'); const s390x = os.arch() === 's390x'; -const conditionalSkips = {}; +const conditionalFileSkips = {}; +const conditionalSubtestSkips = {}; function skip(...files) { for (const file of files) { - conditionalSkips[file] = { - 'skip': `Unsupported in OpenSSL ${process.versions.openssl}`, + conditionalFileSkips[file] = { + 'skip': 'Unsupported in ' + (process.features.openssl_is_boringssl ? 'BoringSSL' : `OpenSSL ${process.versions.openssl}`), }; } } +function skipSubtests(...entries) { + for (const [file, regexp] of entries) { + conditionalSubtestSkips[file] ||= { + 'skipTests': [], + }; + + conditionalSubtestSkips[file].skipTests.push(regexp); + } +} + if (!hasOpenSSL(3, 0)) { skip( 'encrypt_decrypt/aes_ocb.tentative.https.any.js', @@ -34,7 +45,7 @@ if (!hasOpenSSL(3, 2)) { 'import_export/Argon2_importKey.tentative.https.any.js'); } -if (!hasOpenSSL(3, 5)) { +if (!hasOpenSSL(3, 5) && !process.features.openssl_is_boringssl) { skip( 'encap_decap/encap_decap_bits.tentative.https.any.js', 'encap_decap/encap_decap_keys.tentative.https.any.js', @@ -47,8 +58,44 @@ if (!hasOpenSSL(3, 5)) { 'sign_verify/mldsa.tentative.https.any.js'); } +if (process.features.openssl_is_boringssl) { + skip( + 'derive_bits_keys/cfrg_curves_bits_curve448.tentative.https.any.js', + 'derive_bits_keys/cfrg_curves_keys_curve448.tentative.https.any.js', + 'digest/cshake.tentative.https.any.js', + 'digest/sha3.tentative.https.any.js', + 'generateKey/failures_Ed448.tentative.https.any.js', + 'generateKey/failures_X448.tentative.https.any.js', + 'generateKey/successes_Ed448.tentative.https.any.js', + 'generateKey/successes_X448.tentative.https.any.js', + 'import_export/okp_importKey_Ed448.tentative.https.any.js', + 'import_export/okp_importKey_failures_Ed448.tentative.https.any.js', + 'import_export/okp_importKey_failures_X448.tentative.https.any.js', + 'import_export/okp_importKey_X448.tentative.https.any.js', + 'sign_verify/eddsa_curve448.tentative.https.any.js'); + + skipSubtests( + ['encap_decap/encap_decap_bits.tentative.https.any.js', /ml-kem-512/i], + ['encap_decap/encap_decap_keys.tentative.https.any.js', /ml-kem-512/i], + ['generateKey/failures_ML-KEM.tentative.https.any.js', /ml-kem-512/i], + ['generateKey/successes_ML-KEM.tentative.https.any.js', /ml-kem-512/i], + ['import_export/ML-KEM_importKey.tentative.https.any.js', /ml-kem-512/i]); +} + +function assertNoOverlap(fileSkips, subtestSkips) { + const subtestSkipFiles = new Set(Object.keys(subtestSkips)); + const overlap = Object.keys(fileSkips).filter((file) => subtestSkipFiles.has(file)); + + if (overlap.length !== 0) { + throw new Error(`conditionalFileSkips and conditionalSubtestSkips overlap: ${overlap.join(', ')}`); + } +} + +assertNoOverlap(conditionalFileSkips, conditionalSubtestSkips); + module.exports = { - ...conditionalSkips, + ...conditionalFileSkips, + ...conditionalSubtestSkips, 'algorithm-discards-context.https.window.js': { 'skip': 'Not relevant in Node.js context', }, diff --git a/tools/dep_updaters/update-nixpkgs-pin.sh b/tools/dep_updaters/update-nixpkgs-pin.sh index 97bcd878181c7b..25cd1e463394c2 100755 --- a/tools/dep_updaters/update-nixpkgs-pin.sh +++ b/tools/dep_updaters/update-nixpkgs-pin.sh @@ -29,16 +29,16 @@ mv "$TMP_FILE" "$NIXPKGS_PIN_FILE" nix-instantiate -I "nixpkgs=$NIXPKGS_PIN_FILE" --eval --strict --json -E " let pkgs = import {}; + opensslAttrs = builtins.filter + (n: builtins.match \"openssl_[0-9]+(_[0-9]+)?\" n != null) + (builtins.attrNames pkgs); + extraMatrixAttrs = [ \"boringssl\" ]; attrs = builtins.filter (n: let t = builtins.tryEval pkgs.\${n}; in t.success && (builtins.tryEval t.value.version).success ) - ( - builtins.filter - (n: builtins.match \"openssl_[0-9]+(_[0-9]+)?\" n != null) - (builtins.attrNames pkgs) - ); + (opensslAttrs ++ extraMatrixAttrs); in { inherit attrs; diff --git a/tools/nix/openssl-matrix.nix b/tools/nix/openssl-matrix.nix index 3f9476acd7f7e0..5e85aae3f31ddd 100644 --- a/tools/nix/openssl-matrix.nix +++ b/tools/nix/openssl-matrix.nix @@ -11,5 +11,6 @@ openssl_3_5 openssl_3_6 openssl_4_0 + boringssl ; }