From 540b43f56a3b0a309ccc18204dd8f17280738c77 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sun, 26 Jul 2026 14:18:37 +0200 Subject: [PATCH 1/9] build: require OpenSSL 3 or later configure now fails when --shared-openssl points at OpenSSL 1.x, and the CI matrix no longer builds against it. BoringSSL reports itself as OpenSSL 1.1.1, so it is exempt. Signed-off-by: Filip Skokan --- .github/workflows/test-shared.yml | 5 +++-- BUILDING.md | 10 ++++++---- configure.py | 10 +++++++++- tools/dep_updaters/update-nixpkgs-pin.sh | 8 +++++--- tools/nix/openssl-matrix.nix | 3 +-- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test-shared.yml b/.github/workflows/test-shared.yml index 3638aa51371f54..24460e9f5bcc97 100644 --- a/.github/workflows/test-shared.yml +++ b/.github/workflows/test-shared.yml @@ -247,8 +247,9 @@ jobs: # the matrix-selected nixpkgs attribute (e.g. `openssl_3_6`). All # other shared libs (brotli, cares, libuv, …) keep their defaults. # `permittedInsecurePackages` whitelists just the matrix-selected - # release (e.g. `openssl-1.1.1w`) so EOL-with-extended-support - # cycles evaluate without relaxing nixpkgs' meta check globally. + # releases so EOL-with-extended-support cycles evaluate without + # relaxing nixpkgs' meta check globally. It is empty while every + # matrix entry is a supported release. extra-nix-flags: | --arg useSeparateDerivationForV8 ${{ needs.build-aarch64-linux-v8.outputs.local-cache && '"$(nix-store --import < libv8-aarch64-linux.nar)"' || 'true' }} \ --arg sharedLibDeps "(import $TAR_DIR/tools/nix/sharedLibDeps.nix {}) // { diff --git a/BUILDING.md b/BUILDING.md index 5473f823cafbbf..12f9f7660b36fb 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -206,7 +206,7 @@ on your Linux distribution. #### OpenSSL asm support -OpenSSL-1.1.1 requires the following assembler version for use of asm +OpenSSL requires the following assembler version for use of asm support on x86\_64 and ia32. For use of AVX-512, @@ -214,8 +214,6 @@ For use of AVX-512, * gas (GNU assembler) version 2.26 or higher * nasm version 2.11.8 or higher in Windows -AVX-512 is disabled for Skylake-X by OpenSSL-1.1.1. - For use of AVX2, * gas (GNU assembler) version 2.23 or higher @@ -223,7 +221,7 @@ For use of AVX2, * llvm version 3.3 or higher * nasm version 2.10 or higher in Windows -Please refer to for details. +Please refer to for details. If compiling without one of the above, use `configure` with the `--openssl-no-asm` flag. Otherwise, `configure` will fail. @@ -1133,6 +1131,10 @@ A number of `configure` options are provided to support this use case. provide the ability to set the path to an external JavaScript file for the dependency to be used at runtime. +When building with `--shared-openssl`, Node.js requires OpenSSL 3.0 or later. +Support for building against OpenSSL 1.x was removed in Node.js 27.0.0, and +`configure` fails if an older version is detected. + It is the responsibility of any distribution shipping with these options to: diff --git a/configure.py b/configure.py index def52c6fb22f73..a3ab5bd2d281d2 100755 --- a/configure.py +++ b/configure.py @@ -1523,7 +1523,7 @@ def get_openssl_version(o): return version_number - except (OSError, ValueError, subprocess.SubprocessError) as e: + except (OSError, TypeError, ValueError, subprocess.SubprocessError) as e: warn(f'Failed to determine OpenSSL version from header: {e}') return 0 @@ -2341,6 +2341,14 @@ def without_ssl_error(option): o['variables']['openssl_version'] = get_openssl_version(o) o['variables']['openssl_is_boringssl'] = get_openssl_is_boringssl(o) + # BoringSSL identifies itself as OpenSSL 1.1.1 and is exempt from this check. + # A version of 0 means detection failed, which is already warned about in + # get_openssl_version() and is caught at compile time by ncrypto.h. + openssl_version = o['variables']['openssl_version'] + if o['variables']['openssl_is_boringssl'] == 'false' and \ + 0 < openssl_version < 0x30000000: + error('OpenSSL 1.x is no longer supported, v3.0.0 or later is required.') + def configure_lief(o): if options.without_lief: if options.shared_lief: diff --git a/tools/dep_updaters/update-nixpkgs-pin.sh b/tools/dep_updaters/update-nixpkgs-pin.sh index a025676e20a7b5..ddf50433d0cbb6 100755 --- a/tools/dep_updaters/update-nixpkgs-pin.sh +++ b/tools/dep_updaters/update-nixpkgs-pin.sh @@ -45,14 +45,16 @@ update_pkgs_file "$NIXPKGS_COMPAT_PIN_FILE" "$COMPAT_VERSION_SHA1" "$COMPAT_UPST 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); + isOpensslAttr = n: builtins.match \"openssl_[0-9]+(_[0-9]+)?\" n != null; + opensslAttrs = builtins.filter isOpensslAttr (builtins.attrNames pkgs); extraMatrixAttrs = [ \"boringssl\" ]; attrs = builtins.filter (n: let t = builtins.tryEval pkgs.\${n}; in t.success && (builtins.tryEval t.value.version).success + # Node.js requires OpenSSL 3 or later. BoringSSL is versioned + # independently and is not subject to this floor. + && (!isOpensslAttr n || pkgs.lib.versionAtLeast t.value.version \"3\") ) (opensslAttrs ++ extraMatrixAttrs); in diff --git a/tools/nix/openssl-matrix.nix b/tools/nix/openssl-matrix.nix index 36978c5d4efcb0..45ab600df4fcd2 100644 --- a/tools/nix/openssl-matrix.nix +++ b/tools/nix/openssl-matrix.nix @@ -1,13 +1,12 @@ { pkgs ? import ./pkgs.nix { - config.permittedInsecurePackages = [ "openssl-1.1.1w" ]; + config.permittedInsecurePackages = [ ]; }, }: { inherit (pkgs) boringssl - openssl_1_1 openssl_3 openssl_3_5 openssl_3_6 From c37629f07cc88fd265266cb19e5baad01c64bd92 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sun, 26 Jul 2026 14:18:37 +0200 Subject: [PATCH 2/9] deps: remove ncrypto legacy OpenSSL backend Remove the OpenSSL 1.x backend. Version guards become OPENSSL_IS_BORINGSSL checks rather than being deleted, because BoringSSL reports itself as OpenSSL 1.1.1 and relies on those branches. Signed-off-by: Filip Skokan --- deps/ncrypto/engine.cc | 5 ++-- deps/ncrypto/ncrypto.cc | 64 ++++++++++------------------------------ deps/ncrypto/ncrypto.gyp | 14 ++------- deps/ncrypto/ncrypto.h | 45 ++++++++-------------------- 4 files changed, 33 insertions(+), 95 deletions(-) diff --git a/deps/ncrypto/engine.cc b/deps/ncrypto/engine.cc index a8e64e25049183..6b9514b8565e39 100644 --- a/deps/ncrypto/engine.cc +++ b/deps/ncrypto/engine.cc @@ -1,8 +1,7 @@ #include "ncrypto.h" -#if !defined(OPENSSL_NO_ENGINE) && \ - ((defined(NCRYPTO_ENGINE_COMPAT) && NCRYPTO_ENGINE_COMPAT) || \ - NCRYPTO_USE_LEGACY_OPENSSL) +#if !defined(OPENSSL_NO_ENGINE) && defined(NCRYPTO_ENGINE_COMPAT) && \ + NCRYPTO_ENGINE_COMPAT #include #endif diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index bed4d48d118d51..8fe25c48ebb321 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -17,7 +17,7 @@ #include #include #include -#if OPENSSL_VERSION_MAJOR >= 3 +#ifndef OPENSSL_IS_BORINGSSL #include #include #include @@ -59,17 +59,6 @@ constexpr static PQCMapping pqc_mappings[] = { #endif -// EVP_PKEY_CTX_set_dsa_paramgen_q_bits was added in OpenSSL 1.1.1e. -#if OPENSSL_VERSION_NUMBER < 0x1010105fL -#define EVP_PKEY_CTX_set_dsa_paramgen_q_bits(ctx, qbits) \ - EVP_PKEY_CTX_ctrl((ctx), \ - EVP_PKEY_DSA, \ - EVP_PKEY_OP_PARAMGEN, \ - EVP_PKEY_CTRL_DSA_PARAMGEN_Q_BITS, \ - (qbits), \ - nullptr) -#endif - namespace ncrypto { namespace { using BignumCtxPointer = DeleteFnPtr; @@ -515,7 +504,7 @@ DataPointer DataPointer::resize(size_t len) { // ============================================================================ bool isFipsEnabled() { ClearErrorOnReturn clear_error_on_return; -#if OPENSSL_VERSION_MAJOR >= 3 +#ifndef OPENSSL_IS_BORINGSSL return EVP_default_properties_is_fips_enabled(nullptr) == 1; #else return FIPS_mode() == 1; @@ -525,7 +514,7 @@ bool isFipsEnabled() { bool setFipsEnabled(bool enable, CryptoErrorList* errors) { if (isFipsEnabled() == enable) return true; ClearErrorOnReturn clearErrorOnReturn(errors); -#if OPENSSL_VERSION_MAJOR >= 3 +#ifndef OPENSSL_IS_BORINGSSL return EVP_default_properties_enable_fips(nullptr, enable ? 1 : 0) == 1 && EVP_default_properties_is_fips_enabled(nullptr); #else @@ -535,7 +524,7 @@ bool setFipsEnabled(bool enable, CryptoErrorList* errors) { bool testFipsEnabled() { ClearErrorOnReturn clear_error_on_return; -#if OPENSSL_VERSION_MAJOR >= 3 +#ifndef OPENSSL_IS_BORINGSSL OSSL_PROVIDER* fips_provider = nullptr; if (OSSL_PROVIDER_available(nullptr, "fips")) { fips_provider = OSSL_PROVIDER_load(nullptr, "fips"); @@ -802,7 +791,7 @@ bool CSPRNG(void* buffer, size_t length) { auto buf = reinterpret_cast(buffer); do { if (1 == RAND_status()) { -#if OPENSSL_VERSION_MAJOR >= 3 +#ifndef OPENSSL_IS_BORINGSSL if (1 == RAND_bytes_ex(nullptr, buf, length, 0)) { return true; } @@ -815,7 +804,7 @@ bool CSPRNG(void* buffer, size_t length) { return true; #endif } -#if OPENSSL_VERSION_MAJOR >= 3 +#ifndef OPENSSL_IS_BORINGSSL const auto code = ERR_peek_last_error(); // A misconfigured OpenSSL 3 installation may report 1 from RAND_poll() // and RAND_status() but fail in RAND_bytes() if it cannot look up @@ -1112,7 +1101,7 @@ bool PrintGeneralName(const BIOPointer& out, const GENERAL_NAME* gen) { BIO_printf(out.get(), (j == 0) ? "%X" : ":%X", pair); } } else { -#if OPENSSL_VERSION_MAJOR >= 3 +#ifndef OPENSSL_IS_BORINGSSL BIO_printf(out.get(), "", ip_len); #else BIO_printf(out.get(), ""); @@ -1131,9 +1120,9 @@ bool PrintGeneralName(const BIOPointer& out, const GENERAL_NAME* gen) { // awkward, especially when passed to translatePeerCertificate. bool unicode = true; const char* prefix = nullptr; - // OpenSSL 1.1.1 does not support othername in GENERAL_NAME_print and may + // BoringSSL does not support othername in GENERAL_NAME_print and may // not define these NIDs. -#if OPENSSL_VERSION_MAJOR >= 3 +#ifndef OPENSSL_IS_BORINGSSL int nid = OBJ_obj2nid(gen->d.otherName->type_id); switch (nid) { case NID_id_on_SmtpUTF8Mailbox: @@ -1153,7 +1142,7 @@ bool PrintGeneralName(const BIOPointer& out, const GENERAL_NAME* gen) { prefix = "NAIRealm"; break; } -#endif // OPENSSL_VERSION_MAJOR >= 3 +#endif // !OPENSSL_IS_BORINGSSL int val_type = gen->d.otherName->value->type; if (prefix == nullptr || (unicode && val_type != V_ASN1_UTF8STRING) || (!unicode && val_type != V_ASN1_IA5STRING)) { @@ -1244,7 +1233,7 @@ bool SafeX509InfoAccessPrint(const BIOPointer& out, const X509_EXTENSION* ext) { } sk_ACCESS_DESCRIPTION_pop_free(descs, ACCESS_DESCRIPTION_free); -#if OPENSSL_VERSION_MAJOR < 3 +#ifdef OPENSSL_IS_BORINGSSL BIO_write(out.get(), "\n", 1); #endif @@ -3679,12 +3668,8 @@ Result EVPKeyPointer::writePrivateKey( cipher, passphrase); } -#else -#if OPENSSL_VERSION_MAJOR >= 3 - const RSA* rsa = EVP_PKEY_get0_RSA(get()); #else RSA* rsa = EVP_PKEY_get0_RSA(get()); -#endif switch (config.format) { case PKFormatType::PEM: { err = PEM_write_bio_RSAPrivateKey( @@ -3754,12 +3739,8 @@ Result EVPKeyPointer::writePrivateKey( "type-specific", cipher, passphrase); -#else -#if OPENSSL_VERSION_MAJOR >= 3 - const EC_KEY* ec = EVP_PKEY_get0_EC_KEY(get()); #else EC_KEY* ec = EVP_PKEY_get0_EC_KEY(get()); -#endif switch (config.format) { case PKFormatType::PEM: { err = PEM_write_bio_ECPrivateKey( @@ -3820,12 +3801,8 @@ Result EVPKeyPointer::writePublicKey( mark_pop_error_on_return.peekError()); } return bio; -#else -#if OPENSSL_VERSION_MAJOR >= 3 - const RSA* rsa = EVP_PKEY_get0_RSA(get()); #else RSA* rsa = EVP_PKEY_get0_RSA(get()); -#endif if (config.format == ncrypto::EVPKeyPointer::PKFormatType::PEM) { // Encode PKCS#1 as PEM. if (PEM_write_bio_RSAPublicKey(bio.get(), rsa) != 1) { @@ -3953,14 +3930,7 @@ EVPKeyPointer::operator Rsa() const { #if NCRYPTO_USE_OPENSSL3_PROVIDER return Rsa(get()); #else - // TODO(tniessen): Remove the "else" branch once we drop support for OpenSSL - // versions older than 1.1.1e via FIPS / dynamic linking. - OSSL3_CONST RSA* rsa; - if (OPENSSL_VERSION_NUMBER >= 0x1010105fL) { - rsa = EVP_PKEY_get0_RSA(get()); - } else { - rsa = static_cast(EVP_PKEY_get0(get())); - } + OSSL3_CONST RSA* rsa = EVP_PKEY_get0_RSA(get()); if (rsa == nullptr) return {}; return Rsa(rsa); #endif @@ -3982,7 +3952,7 @@ EVPKeyPointer::operator Dsa() const { bool EVPKeyPointer::validateDsaParameters() const { if (!pkey_) return false; /* Validate DSA2 parameters from FIPS 186-4 */ -#if OPENSSL_VERSION_MAJOR >= 3 +#ifndef OPENSSL_IS_BORINGSSL if (EVP_default_properties_is_fips_enabled(nullptr) && EVP_PKEY_DSA == id()) { #else if (FIPS_mode() && EVP_PKEY_DSA == id()) { @@ -5391,11 +5361,7 @@ EVPKeyPointer EVPKeyCtxPointer::paramgen() const { bool EVPKeyCtxPointer::publicCheck() const { if (!ctx_) return false; #ifndef OPENSSL_IS_BORINGSSL -#if OPENSSL_VERSION_MAJOR >= 3 return EVP_PKEY_public_check_quick(ctx_.get()) == 1; -#else - return EVP_PKEY_public_check(ctx_.get()) == 1; -#endif #else // OPENSSL_IS_BORINGSSL // Boringssl appears not to support this operation. // TODO(jasnell): Is there an alternative approach that Boringssl does @@ -5994,7 +5960,7 @@ struct CipherCallbackContext { void operator()(const char* name) { cb(name); } }; -#if OPENSSL_VERSION_MAJOR >= 3 +#ifndef OPENSSL_IS_BORINGSSL template = 3 +#ifndef OPENSSL_IS_BORINGSSL array_push_back= 0x3000000f', { - 'defines!': [ '<@(ncrypto_legacy_openssl_defines)' ], + ['openssl_is_boringssl=="false"', { 'defines': [ '<@(ncrypto_strict_defines)' ], }], ], }, 'sources': [ '<@(ncrypto_sources)' ], 'conditions': [ - ['openssl_is_boringssl=="false" and openssl_version >= 0x3000000f', { - 'defines!': [ '<@(ncrypto_legacy_openssl_defines)' ], + ['openssl_is_boringssl=="false"', { 'defines': [ '<@(ncrypto_strict_defines)' ], 'dependencies': [ 'ncrypto_engine', ], }], - ['openssl_is_boringssl=="false" and openssl_version < 0x3000000f', { - 'sources': [ '<@(ncrypto_engine_sources)' ], - }], ['node_shared_openssl=="false"', { 'dependencies': [ '../openssl/openssl.gyp:openssl' @@ -63,7 +55,7 @@ }, ], 'conditions': [ - ['openssl_is_boringssl=="false" and openssl_version >= 0x3000000f', { + ['openssl_is_boringssl=="false"', { 'targets': [ { 'target_name': 'ncrypto_engine', diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index 6fb6b384fd4fa4..8eea2760de9919 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -29,6 +29,11 @@ (OPENSSL_VERSION_NUMBER >= (((maj) << 28) | ((min) << 20))) #endif +// BoringSSL reports itself as OpenSSL 1.1.1, so it has to be excluded here. +#if !defined(OPENSSL_IS_BORINGSSL) && !OPENSSL_VERSION_PREREQ(3, 0) +#error "OpenSSL 1.x is no longer supported, v3.0.0 or later is required." +#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 @@ -43,31 +48,17 @@ // Backend split: // - OpenSSL >= 3 uses provider APIs and hides deprecated low-level objects. -// - BoringSSL has its own API-compatible branch. -// - OpenSSL < 3 remains the legacy fallback branch. -#if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_PREREQ(3, 0) -#define NCRYPTO_USE_OPENSSL3_PROVIDER 1 -#else -#define NCRYPTO_USE_OPENSSL3_PROVIDER 0 -#endif - +// - BoringSSL has its own API-compatible branch and keeps using the legacy +// low-level key types. #ifdef OPENSSL_IS_BORINGSSL #define NCRYPTO_USE_BORINGSSL 1 +#define NCRYPTO_USE_OPENSSL3_PROVIDER 0 #else #define NCRYPTO_USE_BORINGSSL 0 +#define NCRYPTO_USE_OPENSSL3_PROVIDER 1 #endif -#if !NCRYPTO_USE_OPENSSL3_PROVIDER && !NCRYPTO_USE_BORINGSSL -#define NCRYPTO_USE_LEGACY_OPENSSL 1 -#else -#define NCRYPTO_USE_LEGACY_OPENSSL 0 -#endif - -#if NCRYPTO_USE_BORINGSSL || NCRYPTO_USE_LEGACY_OPENSSL -#define NCRYPTO_USE_LEGACY_KEY_TYPES 1 -#else -#define NCRYPTO_USE_LEGACY_KEY_TYPES 0 -#endif +#define NCRYPTO_USE_LEGACY_KEY_TYPES NCRYPTO_USE_BORINGSSL #if NCRYPTO_USE_OPENSSL3_PROVIDER #include @@ -75,13 +66,7 @@ #include #endif -// The FIPS-related functions are only available -// when the OpenSSL itself was compiled with FIPS support. -#if defined(OPENSSL_FIPS) && !OPENSSL_VERSION_PREREQ(3, 0) -#include -#endif // OPENSSL_FIPS - -#if OPENSSL_VERSION_PREREQ(3, 0) +#if !defined(OPENSSL_IS_BORINGSSL) #define OPENSSL_WITH_AES_OCB 1 #else #define OPENSSL_WITH_AES_OCB 0 @@ -93,13 +78,9 @@ #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) +#if !defined(OPENSSL_IS_BORINGSSL) #define OPENSSL_WITH_EVP_MAC 1 #else #define OPENSSL_WITH_EVP_MAC 0 @@ -152,7 +133,7 @@ #define EVP_PKEY_ML_KEM_1024 NID_ML_KEM_1024 #endif -#if OPENSSL_VERSION_PREREQ(3, 0) +#if !defined(OPENSSL_IS_BORINGSSL) #define OSSL3_CONST const #else #define OSSL3_CONST From 159fd35b9b0fe3f2f14441ca57e3b7d2be3cc018 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sun, 26 Jul 2026 14:18:38 +0200 Subject: [PATCH 3/9] crypto: remove legacy OpenSSL code paths Remove the OpenSSL 1.x branches from src/. The remaining version guards become OPENSSL_IS_BORINGSSL checks, since BoringSSL reports itself as OpenSSL 1.1.1 and their else branches are its own. Signed-off-by: Filip Skokan --- src/crypto/README.md | 4 ++-- src/crypto/crypto_cipher.cc | 4 ++-- src/crypto/crypto_context.cc | 4 ++-- src/crypto/crypto_dh.cc | 14 ++------------ src/crypto/crypto_hash.cc | 12 ++++++------ src/crypto/crypto_keys.cc | 2 +- src/crypto/crypto_tls.cc | 2 +- src/crypto/crypto_util.cc | 18 +----------------- src/env.h | 4 ++-- src/node.cc | 10 +++------- src/node_constants.cc | 4 ++-- src/node_constants.h | 2 +- src/node_metadata.cc | 2 +- src/node_options.cc | 4 ++-- src/node_options.h | 2 +- 15 files changed, 29 insertions(+), 59 deletions(-) diff --git a/src/crypto/README.md b/src/crypto/README.md index cc5093a385cad3..aae3c3b74080f6 100644 --- a/src/crypto/README.md +++ b/src/crypto/README.md @@ -96,8 +96,8 @@ using CipherCtxPointer = DeleteFnPtr; Examples of these being used are pervasive through the `src/crypto` code. `HMACCtxPointer` is a dedicated HMAC state wrapper rather than a plain -`DeleteFnPtr` alias. On OpenSSL 3 and later it owns the provider-backed -`EVP_MAC`/`EVP_MAC_CTX` state. On OpenSSL 1.1.1 and BoringSSL it owns the +`DeleteFnPtr` alias. On OpenSSL it owns the provider-backed +`EVP_MAC`/`EVP_MAC_CTX` state. On BoringSSL it owns the legacy `HMAC_CTX` state. HMAC call sites should use `HMACCtxPointer::New()`, `init()`, `update()`, and `digest()`/`digestInto()` so the backend selection stays contained in ncrypto. diff --git a/src/crypto/crypto_cipher.cc b/src/crypto/crypto_cipher.cc index a165e8e3409d6f..759c2dfd883a39 100644 --- a/src/crypto/crypto_cipher.cc +++ b/src/crypto/crypto_cipher.cc @@ -719,8 +719,8 @@ bool CipherBase::Final(std::unique_ptr* out) { static_cast(ctx_.getBlockSize()), BackingStoreInitializationMode::kUninitialized); -#if !OPENSSL_VERSION_PREREQ(3, 0) - // OpenSSL v1.x doesn't verify the presence of the auth tag so do +#ifdef OPENSSL_IS_BORINGSSL + // BoringSSL 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() && auth_tag_state_ != kAuthTagSetByUser) { diff --git a/src/crypto/crypto_context.cc b/src/crypto/crypto_context.cc index 5447de596f98ec..aff44b56f4e5f8 100644 --- a/src/crypto/crypto_context.cc +++ b/src/crypto/crypto_context.cc @@ -1645,7 +1645,7 @@ void SecureContext::Init(const FunctionCallbackInfo& args) { // SSLv3 is disabled because it's susceptible to downgrade attacks (POODLE.) SSL_CTX_set_options(sc->ctx_.get(), SSL_OP_NO_SSLv2); SSL_CTX_set_options(sc->ctx_.get(), SSL_OP_NO_SSLv3); -#if OPENSSL_VERSION_MAJOR >= 3 +#ifndef OPENSSL_IS_BORINGSSL SSL_CTX_set_options(sc->ctx_.get(), SSL_OP_ALLOW_CLIENT_RENEGOTIATION); #endif @@ -2361,7 +2361,7 @@ void SecureContext::LoadPKCS12(const FunctionCallbackInfo& args) { // TODO(@jasnell): Should this use ThrowCryptoError? unsigned long err = ERR_get_error(); // NOLINT(runtime/int) -#if OPENSSL_VERSION_MAJOR >= 3 +#ifndef OPENSSL_IS_BORINGSSL if (ERR_GET_REASON(err) == ERR_R_UNSUPPORTED) { // OpenSSL's "unsupported" error without any context is very // common and not very helpful, so we override it: diff --git a/src/crypto/crypto_dh.cc b/src/crypto/crypto_dh.cc index a824e4e0ce90d2..4975dc54228c8c 100644 --- a/src/crypto/crypto_dh.cc +++ b/src/crypto/crypto_dh.cc @@ -92,20 +92,14 @@ MaybeLocal DataPointerToBuffer(Environment* env, DataPointer&& data) { void PutDhError(int reason) { #ifdef OPENSSL_IS_BORINGSSL OPENSSL_PUT_ERROR(DH, reason); -#elif NCRYPTO_USE_OPENSSL3_PROVIDER - ERR_raise(ERR_LIB_DH, reason); #else - ERR_put_error(ERR_LIB_DH, 0, reason, __FILE__, __LINE__); + ERR_raise(ERR_LIB_DH, reason); #endif } -#if defined(OPENSSL_IS_BORINGSSL) || !NCRYPTO_USE_OPENSSL3_PROVIDER -void PutBnError(int reason) { #ifdef OPENSSL_IS_BORINGSSL +void PutBnError(int reason) { OPENSSL_PUT_ERROR(BN, reason); -#else - ERR_put_error(ERR_LIB_BN, 0, reason, __FILE__, __LINE__); -#endif } #endif @@ -134,11 +128,7 @@ void New(const FunctionCallbackInfo& args) { int32_t bits = args[0].As()->Value(); if (bits < 2) { #ifndef OPENSSL_IS_BORINGSSL -#if OPENSSL_VERSION_MAJOR >= 3 PutDhError(DH_R_MODULUS_TOO_SMALL); -#else - PutBnError(BN_R_BITS_TOO_SMALL); -#endif // OPENSSL_VERSION_MAJOR >= 3 #else // OPENSSL_IS_BORINGSSL PutBnError(BN_R_BITS_TOO_SMALL); #endif // OPENSSL_IS_BORINGSSL diff --git a/src/crypto/crypto_hash.cc b/src/crypto/crypto_hash.cc index aaa3fee58c35b4..8168dd20fccadf 100644 --- a/src/crypto/crypto_hash.cc +++ b/src/crypto/crypto_hash.cc @@ -79,7 +79,7 @@ constexpr BoringSSLDigest kBoringSSLDigests[] = { }; #endif -#if OPENSSL_VERSION_MAJOR >= 3 +#ifndef OPENSSL_IS_BORINGSSL void PushAliases(const char* name, void* data) { static_cast*>(data)->push_back(name); } @@ -155,7 +155,7 @@ void SaveSupportedHashAlgorithms(const EVP_MD* md, Environment* env = static_cast(arg); env->supported_hash_algorithms.push_back(from); } -#endif // OPENSSL_VERSION_MAJOR >= 3 +#endif // !OPENSSL_IS_BORINGSSL const std::vector& GetSupportedHashAlgorithms(Environment* env) { if (env->supported_hash_algorithms.empty()) { @@ -165,7 +165,7 @@ const std::vector& GetSupportedHashAlgorithms(Environment* env) { static_cast(digest.get); env->supported_hash_algorithms.emplace_back(digest.name); } -#elif OPENSSL_VERSION_MAJOR >= 3 +#elif !defined(OPENSSL_IS_BORINGSSL) // 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); @@ -194,7 +194,7 @@ void Hash::GetCachedAliases(const FunctionCallbackInfo& args) { size_t size = env->alias_to_md_id_map.size(); LocalVector names(isolate); LocalVector values(isolate); -#if OPENSSL_VERSION_MAJOR >= 3 +#ifndef OPENSSL_IS_BORINGSSL names.reserve(size); values.reserve(size); for (auto& [alias, id] : env->alias_to_md_id_map) { @@ -218,7 +218,7 @@ const EVP_MD* GetDigestImplementation(Environment* env, CHECK(cache_id_val->IsInt32()); CHECK(algorithm_cache->IsObject()); -#if OPENSSL_VERSION_MAJOR >= 3 +#ifndef OPENSSL_IS_BORINGSSL int32_t cache_id = cache_id_val.As()->Value(); if (cache_id != -1) { // Alias already cached, return the cached EVP_MD*. return GetCachedMDByID(env, cache_id); @@ -266,7 +266,7 @@ void MarkInvalidXofLength() { // version-independent. #if !OPENSSL_VERSION_PREREQ(3, 4) bool IsShakeDigest(const EVP_MD* md) { -#if OPENSSL_VERSION_MAJOR >= 3 +#ifndef OPENSSL_IS_BORINGSSL return EVP_MD_is_a(md, "SHAKE128") || EVP_MD_is_a(md, "SHAKE256"); #else const char* name = OBJ_nid2sn(EVP_MD_type(md)); diff --git a/src/crypto/crypto_keys.cc b/src/crypto/crypto_keys.cc index 1404390f4bc8bc..08e63acc3ffde4 100644 --- a/src/crypto/crypto_keys.cc +++ b/src/crypto/crypto_keys.cc @@ -1230,7 +1230,7 @@ void KeyObjectHandle::Equals(const FunctionCallbackInfo& args) { case kKeyTypePrivate: { EVP_PKEY* pkey = key.GetAsymmetricKey().get(); EVP_PKEY* pkey2 = key2.GetAsymmetricKey().get(); -#if OPENSSL_VERSION_MAJOR >= 3 +#ifndef OPENSSL_IS_BORINGSSL int ok = EVP_PKEY_eq(pkey, pkey2); #else int ok = EVP_PKEY_cmp(pkey, pkey2); diff --git a/src/crypto/crypto_tls.cc b/src/crypto/crypto_tls.cc index ce44ad15fa79d3..d12853b5a5e62a 100644 --- a/src/crypto/crypto_tls.cc +++ b/src/crypto/crypto_tls.cc @@ -82,7 +82,7 @@ namespace { // that the user user Connection::VerifyError after the `secure` // callback has been made. int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx) { - // From https://www.openssl.org/docs/man1.1.1/man3/SSL_verify_cb: + // From https://www.openssl.org/docs/man3.0/man3/SSL_verify_cb: // // If VerifyCallback returns 1, the verification process is continued. If // VerifyCallback always returns 1, the TLS/SSL handshake will not be diff --git a/src/crypto/crypto_util.cc b/src/crypto/crypto_util.cc index 711984e5e4f23f..628038d770ada1 100644 --- a/src/crypto/crypto_util.cc +++ b/src/crypto/crypto_util.cc @@ -14,7 +14,7 @@ #include "math.h" -#if OPENSSL_VERSION_MAJOR >= 3 +#ifndef OPENSSL_IS_BORINGSSL #include "openssl/provider.h" #endif @@ -85,14 +85,8 @@ bool ProcessFipsOptions() { /* Override FIPS settings in configuration file, if needed. */ if (per_process::cli_options->enable_fips_crypto || per_process::cli_options->force_fips_crypto) { -#if OPENSSL_VERSION_MAJOR >= 3 if (!ncrypto::testFipsEnabled()) return false; return ncrypto::setFipsEnabled(true, nullptr); -#else - // TODO(@jasnell): Remove this ifdef branch when openssl 1.1.1 is - // no longer supported. - if (FIPS_mode() == 0) return FIPS_mode_set(1); -#endif } return true; } @@ -118,15 +112,6 @@ void InitCryptoOnce() { #ifndef OPENSSL_IS_BORINGSSL OPENSSL_INIT_SETTINGS* settings = OPENSSL_INIT_new(); -#if OPENSSL_VERSION_MAJOR < 3 - // --openssl-config=... - if (!per_process::cli_options->openssl_config.empty()) { - const char* conf = per_process::cli_options->openssl_config.c_str(); - OPENSSL_INIT_set_config_filename(settings, conf); - } -#endif - -#if OPENSSL_VERSION_MAJOR >= 3 // --openssl-legacy-provider if (per_process::cli_options->openssl_legacy_provider) { OSSL_PROVIDER* legacy_provider = OSSL_PROVIDER_load(nullptr, "legacy"); @@ -134,7 +119,6 @@ void InitCryptoOnce() { fprintf(stderr, "Unable to load legacy provider.\n"); } } -#endif OPENSSL_init_ssl(0, settings); diff --git a/src/env.h b/src/env.h index c2caf979023811..1922763724c9f4 100644 --- a/src/env.h +++ b/src/env.h @@ -1069,11 +1069,11 @@ class Environment final : public MemoryRetainer { }; #if HAVE_OPENSSL -#if OPENSSL_VERSION_MAJOR >= 3 +#ifndef OPENSSL_IS_BORINGSSL // We declare another alias here to avoid having to include crypto_util.h using EVPMDPointer = DeleteFnPtr; std::vector evp_md_cache; -#endif // OPENSSL_VERSION_MAJOR >= 3 +#endif // !OPENSSL_IS_BORINGSSL std::unordered_map alias_to_md_id_map; std::vector supported_hash_algorithms; #endif // HAVE_OPENSSL diff --git a/src/node.cc b/src/node.cc index b368c58734345f..979e278326e042 100644 --- a/src/node.cc +++ b/src/node.cc @@ -50,7 +50,7 @@ #if HAVE_OPENSSL #include "ncrypto.h" #include "node_crypto.h" -#if OPENSSL_VERSION_MAJOR >= 3 && !defined(CONF_MFLAGS_IGNORE_MISSING_FILE) +#if !defined(OPENSSL_IS_BORINGSSL) && !defined(CONF_MFLAGS_IGNORE_MISSING_FILE) // OpenSSL hides this deprecated macro under OPENSSL_NO_DEPRECATED, but the // non-deprecated OPENSSL_INIT settings API still accepts the flag value. #define CONF_MFLAGS_IGNORE_MISSING_FILE 0x10 @@ -1183,7 +1183,7 @@ InitializeOncePerProcessInternal(const std::vector& args, // In the case of FIPS builds we should make sure // the random source is properly initialized first. -#if OPENSSL_VERSION_MAJOR >= 3 + // // Call OPENSSL_init_crypto to initialize OPENSSL_INIT_LOAD_CONFIG to // avoid the default behavior where errors raised during the parsing of the // OpenSSL configuration file are not propagated and cannot be detected. @@ -1239,11 +1239,7 @@ InitializeOncePerProcessInternal(const std::vector& args, GetOpenSSLErrorString()); return result; } -#else // OPENSSL_VERSION_MAJOR < 3 - if (FIPS_mode()) { - OPENSSL_init(); - } -#endif + if (!crypto::ProcessFipsOptions()) { result->exit_code_ = ExitCode::kGenericUserError; result->early_return_ = true; diff --git a/src/node_constants.cc b/src/node_constants.cc index db670789dc063a..c06324204b4e13 100644 --- a/src/node_constants.cc +++ b/src/node_constants.cc @@ -57,7 +57,7 @@ #if !defined(RSA_PKCS1_PSS_PADDING) #define RSA_PKCS1_PSS_PADDING 6 #endif -#if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_MAJOR >= 3 +#ifndef OPENSSL_IS_BORINGSSL // OpenSSL hides these deprecated DH check constants under // OPENSSL_NO_DEPRECATED, but the numeric verifyError values remain public API. #if !defined(DH_CHECK_P_NOT_PRIME) @@ -74,7 +74,7 @@ #endif #endif #ifndef OPENSSL_NO_ENGINE -#if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_MAJOR >= 3 +#ifndef OPENSSL_IS_BORINGSSL // Engine constants remain public API while engine implementation lives in the // dedicated compatibility target. #define ENGINE_METHOD_RSA (unsigned int)0x0001 diff --git a/src/node_constants.h b/src/node_constants.h index 97429c0e5e9462..115de09587d31e 100644 --- a/src/node_constants.h +++ b/src/node_constants.h @@ -48,7 +48,7 @@ #define DEFAULT_CIPHER_LIST_CORE NODE_OPENSSL_DEFAULT_CIPHER_LIST #else // TLSv1.3 suites start with TLS_, and are the OpenSSL defaults, see: -// https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_ciphersuites.html +// https://www.openssl.org/docs/man3.0/man3/SSL_CTX_set_ciphersuites.html #define DEFAULT_CIPHER_LIST_CORE \ "TLS_AES_256_GCM_SHA384:" \ "TLS_CHACHA20_POLY1305_SHA256:" \ diff --git a/src/node_metadata.cc b/src/node_metadata.cc index b91b1b4881489a..68daae837fc1fb 100644 --- a/src/node_metadata.cc +++ b/src/node_metadata.cc @@ -68,7 +68,7 @@ static constexpr size_t search(const char* s, char c, size_t n = 0) { static inline std::string GetOpenSSLVersion() { // sample openssl version string format - // for reference: "OpenSSL 1.1.0i 14 Aug 2018" + // for reference: "OpenSSL 3.5.7 9 Jun 2026" const char* version = OpenSSL_version(OPENSSL_VERSION); const size_t first_space = search(version, ' '); diff --git a/src/node_options.cc b/src/node_options.cc index b9d3dd56092e35..1354fd5250885c 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -1489,7 +1489,7 @@ PerProcessOptionsParser::PerProcessOptionsParser( kAllowedInEnvvar); #endif // V8_ENABLE_SANDBOX #endif // HAVE_OPENSSL -#if OPENSSL_VERSION_MAJOR >= 3 +#if HAVE_OPENSSL && !defined(OPENSSL_IS_BORINGSSL) AddOption("--openssl-legacy-provider", "enable OpenSSL 3.0 legacy provider", &PerProcessOptions::openssl_legacy_provider, @@ -1499,7 +1499,7 @@ PerProcessOptionsParser::PerProcessOptionsParser( &PerProcessOptions::openssl_shared_config, kAllowedInEnvvar); -#endif // OPENSSL_VERSION_MAJOR +#endif // HAVE_OPENSSL && !OPENSSL_IS_BORINGSSL AddOption("--use-largepages", "Map the Node.js static code to large pages. Options are " "'off' (the default value, meaning do not map), " diff --git a/src/node_options.h b/src/node_options.h index 7d9ff9e5147d18..4debd167857ac0 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -378,7 +378,7 @@ class PerProcessOptions : public Options { bool enable_fips_crypto = false; bool force_fips_crypto = false; #endif -#if OPENSSL_VERSION_MAJOR >= 3 +#if HAVE_OPENSSL && !defined(OPENSSL_IS_BORINGSSL) bool openssl_legacy_provider = false; bool openssl_shared_config = false; #endif From 0889870eb77dd9093ada405af90cfd08e8dbf3fd Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sun, 26 Jul 2026 14:18:38 +0200 Subject: [PATCH 4/9] test: drop OpenSSL 1.x-only coverage Delete test-crypto-ecb.js, which can no longer run anywhere: Blowfish is legacy-provider only on OpenSSL 3 and absent from BoringSSL. The addon and cctest version guards become OPENSSL_IS_BORINGSSL checks. Signed-off-by: Filip Skokan --- test/addons/openssl-providers/binding.cc | 9 ++-- test/cctest/test_node_crypto_env.cc | 2 +- test/parallel/test-crypto-ecb.js | 63 ---------------------- test/parallel/test-crypto-sec-level.js | 2 +- test/parallel/test-tls-client-mindhsize.js | 2 +- test/parallel/test-tls-dhe.js | 2 +- 6 files changed, 9 insertions(+), 71 deletions(-) delete mode 100644 test/parallel/test-crypto-ecb.js diff --git a/test/addons/openssl-providers/binding.cc b/test/addons/openssl-providers/binding.cc index 785a103bb6c6c7..36f8de59ccd93f 100644 --- a/test/addons/openssl-providers/binding.cc +++ b/test/addons/openssl-providers/binding.cc @@ -1,8 +1,9 @@ #include #include -#include -#if OPENSSL_VERSION_MAJOR >= 3 +// BoringSSL declares OPENSSL_IS_BORINGSSL in crypto.h. +#include +#ifndef OPENSSL_IS_BORINGSSL #include #endif @@ -18,7 +19,7 @@ using v8::Object; using v8::String; using v8::Value; -#if OPENSSL_VERSION_MAJOR >= 3 +#ifndef OPENSSL_IS_BORINGSSL int collectProviders(OSSL_PROVIDER* provider, void* cbdata) { static_cast*>(cbdata)->push_back(provider); return 1; @@ -28,7 +29,7 @@ int collectProviders(OSSL_PROVIDER* provider, void* cbdata) { inline void GetProviders(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); LocalVector arr(isolate, 0); -#if OPENSSL_VERSION_MAJOR >= 3 +#ifndef OPENSSL_IS_BORINGSSL std::vector providers; OSSL_PROVIDER_do_all(nullptr, &collectProviders, &providers); for (auto provider : providers) { diff --git a/test/cctest/test_node_crypto_env.cc b/test/cctest/test_node_crypto_env.cc index fddf584d7d41f7..a0cbbc1fbb42f2 100644 --- a/test/cctest/test_node_crypto_env.cc +++ b/test/cctest/test_node_crypto_env.cc @@ -26,7 +26,7 @@ TEST_F(NodeCryptoEnv, LoadBIO) { // just put a random string into BIO Local key = String::NewFromUtf8(isolate_, "abcdef").ToLocalChecked(); ncrypto::BIOPointer bio(node::crypto::LoadBIO(*env, key)); -#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#ifndef OPENSSL_IS_BORINGSSL const int ofs = 2; ASSERT_EQ(BIO_seek(bio.get(), ofs), ofs); ASSERT_EQ(BIO_tell(bio.get()), ofs); diff --git a/test/parallel/test-crypto-ecb.js b/test/parallel/test-crypto-ecb.js deleted file mode 100644 index 06c88272438a05..00000000000000 --- a/test/parallel/test-crypto-ecb.js +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -'use strict'; -const common = require('../common'); -if (!common.hasCrypto) { - common.skip('missing crypto'); -} - -const { hasOpenSSL3 } = require('../common/crypto'); -const crypto = require('crypto'); - -if (crypto.getFips()) { - common.skip('BF-ECB is not FIPS 140-2 compatible'); -} - -if (hasOpenSSL3) { - common.skip('Blowfish is only available with the legacy provider in ' + - 'OpenSSl 3.x'); -} - -if (!crypto.getCiphers().includes('BF-ECB')) { - common.skip('BF-ECB cipher is not available'); -} - -const assert = require('assert'); - -// Testing whether EVP_CipherInit_ex is functioning correctly. -// Reference: bug#1997 - -{ - const encrypt = - crypto.createCipheriv('BF-ECB', 'SomeRandomBlahz0c5GZVnR', ''); - let hex = encrypt.update('Hello World!', 'ascii', 'hex'); - hex += encrypt.final('hex'); - assert.strictEqual(hex.toUpperCase(), '6D385F424AAB0CFBF0BB86E07FFB7D71'); -} - -{ - const decrypt = - crypto.createDecipheriv('BF-ECB', 'SomeRandomBlahz0c5GZVnR', ''); - let msg = decrypt.update('6D385F424AAB0CFBF0BB86E07FFB7D71', 'hex', 'ascii'); - msg += decrypt.final('ascii'); - assert.strictEqual(msg, 'Hello World!'); -} diff --git a/test/parallel/test-crypto-sec-level.js b/test/parallel/test-crypto-sec-level.js index d7d2252be6c35d..3305ffb14ffd7d 100644 --- a/test/parallel/test-crypto-sec-level.js +++ b/test/parallel/test-crypto-sec-level.js @@ -11,7 +11,7 @@ const assert = require('assert'); // are available by default. Different OpenSSL versions have different // default security levels and we use this value to adjust what a test // expects based on the security level. You can read more in -// https://docs.openssl.org/1.1.1/man3/SSL_CTX_set_security_level/#default-callback-behaviour +// https://docs.openssl.org/3.0/man3/SSL_CTX_set_security_level/#default-callback-behaviour // This test simply validates that we can get some value for the secLevel // when needed by tests. const secLevel = require('internal/crypto/util').getOpenSSLSecLevel(); diff --git a/test/parallel/test-tls-client-mindhsize.js b/test/parallel/test-tls-client-mindhsize.js index d777a9bfa97f3f..cfdd7702eb1359 100644 --- a/test/parallel/test-tls-client-mindhsize.js +++ b/test/parallel/test-tls-client-mindhsize.js @@ -8,7 +8,7 @@ if (!common.hasCrypto) // are available by default. Different OpenSSL versions have different // default security levels and we use this value to adjust what a test // expects based on the security level. You can read more in -// https://docs.openssl.org/1.1.1/man3/SSL_CTX_set_security_level/#default-callback-behaviour +// https://docs.openssl.org/3.0/man3/SSL_CTX_set_security_level/#default-callback-behaviour const secLevel = require('internal/crypto/util').getOpenSSLSecLevel(); const assert = require('assert'); const tls = require('tls'); diff --git a/test/parallel/test-tls-dhe.js b/test/parallel/test-tls-dhe.js index 83af6daccbd0c9..66797249189fcc 100644 --- a/test/parallel/test-tls-dhe.js +++ b/test/parallel/test-tls-dhe.js @@ -40,7 +40,7 @@ const { // are available by default. Different OpenSSL versions have different // default security levels and we use this value to adjust what a test // expects based on the security level. You can read more in -// https://docs.openssl.org/1.1.1/man3/SSL_CTX_set_security_level/#default-callback-behaviour +// https://docs.openssl.org/3.0/man3/SSL_CTX_set_security_level/#default-callback-behaviour const secLevel = require('internal/crypto/util').getOpenSSLSecLevel(); if (!opensslCli) { From d19a0558347fd654693e0c54ddeeecf3f5bf53b6 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sun, 26 Jul 2026 14:18:38 +0200 Subject: [PATCH 5/9] doc: remove OpenSSL 1.x references Rename the openssl30 footnote to say what it now means, and refresh the stale man1.1.1 links. Signed-off-by: Filip Skokan --- doc/api/cli.md | 4 ++-- doc/api/crypto.md | 14 +++++++------- doc/api/tls.md | 18 +++++++++--------- doc/api/webcrypto.md | 8 ++++---- doc/node.1 | 4 ++-- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/doc/api/cli.md b/doc/api/cli.md index 5d59844d4cbc72..71a341e1c49088 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -4209,8 +4209,8 @@ added: v6.11.0 --> Load an OpenSSL configuration file on startup. Among other uses, this can be -used to enable FIPS-compliant crypto if Node.js is built with -`./configure --openssl-fips`. +used to enable FIPS-compliant crypto if Node.js is built against +FIPS-enabled OpenSSL. If the [`--openssl-config`][] command-line option is used, the environment variable is ignored. diff --git a/doc/api/crypto.md b/doc/api/crypto.md index 4770e0c36c3948..18166014f9fdf1 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -4190,7 +4190,7 @@ Key decapsulation using a KEM algorithm with a private key. Supported key types and their KEM algorithms are: -* `'rsa'`[^openssl30] RSA Secret Value Encapsulation +* `'rsa'`[^noboringssl] RSA Secret Value Encapsulation * `'ec'`[^openssl32] DHKEM(P-256, HKDF-SHA256), DHKEM(P-384, HKDF-SHA256), DHKEM(P-521, HKDF-SHA256) * `'x25519'`[^openssl32] DHKEM(X25519, HKDF-SHA256) * `'x448'`[^openssl32] DHKEM(X448, HKDF-SHA512) @@ -4262,7 +4262,7 @@ Key encapsulation using a KEM algorithm with a public key. Supported key types and their KEM algorithms are: -* `'rsa'`[^openssl30] RSA Secret Value Encapsulation +* `'rsa'`[^noboringssl] RSA Secret Value Encapsulation * `'ec'`[^openssl32] DHKEM(P-256, HKDF-SHA256), DHKEM(P-384, HKDF-SHA256), DHKEM(P-521, HKDF-SHA256) * `'x25519'`[^openssl32] DHKEM(X25519, HKDF-SHA256) * `'x448'`[^openssl32] DHKEM(X448, HKDF-SHA512) @@ -6621,9 +6621,9 @@ console.log(receivedPlaintext); ### FIPS mode -When using OpenSSL 3, Node.js supports FIPS 140-2 when used with an appropriate -OpenSSL 3 provider, such as the [FIPS provider from OpenSSL 3][] which can be -installed by following the instructions in [OpenSSL's FIPS README file][]. +Node.js supports FIPS 140-2 when used with an appropriate OpenSSL 3 provider, +such as the [FIPS provider from OpenSSL 3][] which can be installed by +following the instructions in [OpenSSL's FIPS README file][]. For FIPS support in Node.js you will need: @@ -6969,7 +6969,7 @@ See the [list of SSL OP Flags][] for details. -[^openssl30]: Requires OpenSSL >= 3.0 +[^noboringssl]: Not available when Node.js is built against BoringSSL [^openssl32]: Requires OpenSSL >= 3.2 @@ -7003,7 +7003,7 @@ See the [list of SSL OP Flags][] for details. [RFC 8032]: https://www.rfc-editor.org/rfc/rfc8032.txt [RFC 9562]: https://www.rfc-editor.org/rfc/rfc9562.txt [Web Crypto API documentation]: webcrypto.md -[`BN_is_prime_ex`]: https://www.openssl.org/docs/man1.1.1/man3/BN_is_prime_ex.html +[`BN_is_prime_ex`]: https://www.openssl.org/docs/man3.0/man3/BN_is_prime_ex.html [`Buffer`]: buffer.md [`DH_generate_key()`]: https://www.openssl.org/docs/man3.0/man3/DH_generate_key.html [`DiffieHellmanGroup`]: #class-diffiehellmangroup diff --git a/doc/api/tls.md b/doc/api/tls.md index ae3de645eaa3b6..5e7bb70587f432 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -182,8 +182,8 @@ On the client connection, a custom `checkServerIdentity` should be passed because the default one will fail in the absence of a certificate. According to the [RFC 4279][], PSK identities up to 128 bytes in length and -PSKs up to 64 bytes in length must be supported. As of OpenSSL 1.1.0 -maximum identity size is 128 bytes, and maximum PSK length is 256 bytes. +PSKs up to 64 bytes in length must be supported. In OpenSSL the maximum +identity size is 128 bytes, and the maximum PSK length is 256 bytes. The current implementation doesn't support asynchronous PSK callbacks due to the limitations of the underlying OpenSSL API. @@ -1195,7 +1195,7 @@ For example, a TLSv1.2 protocol with AES256-SHA cipher: ``` See -[SSL\_CIPHER\_get\_name](https://www.openssl.org/docs/man1.1.1/man3/SSL_CIPHER_get_name.html) +[SSL\_CIPHER\_get\_name](https://www.openssl.org/docs/man3.0/man3/SSL_CIPHER_get_name.html) for more information. ### `tlsSocket.getEphemeralKeyInfo()` @@ -1447,7 +1447,7 @@ added: v12.11.0 the client in the order of decreasing preference. See -[SSL\_get\_shared\_sigalgs](https://www.openssl.org/docs/man1.1.1/man3/SSL_get_shared_sigalgs.html) +[SSL\_get\_shared\_sigalgs](https://www.openssl.org/docs/man3.0/man3/SSL_get_shared_sigalgs.html) for more information. ### `tlsSocket.getTLSTicket()` @@ -2034,7 +2034,7 @@ changes: The list can contain digest algorithms (`SHA256`, `MD5` etc.), public key algorithms (`RSA-PSS`, `ECDSA` etc.), combination of both (e.g 'RSA+SHA384') or TLS v1.3 scheme names (e.g. `rsa_pss_pss_sha512`). - See [OpenSSL man pages](https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set1_sigalgs_list.html) + See [OpenSSL man pages](https://www.openssl.org/docs/man3.0/man3/SSL_CTX_set1_sigalgs_list.html) for more info. * `ciphers` {string} Cipher suite specification, replacing the default. For more information, see [Modifying the default TLS cipher suite][]. Permitted @@ -2539,7 +2539,7 @@ added: v0.11.3 [RFC 5077]: https://tools.ietf.org/html/rfc5077 [RFC 5929]: https://tools.ietf.org/html/rfc5929 [RFC 8879]: https://tools.ietf.org/html/rfc8879 -[SSL_METHODS]: https://www.openssl.org/docs/man1.1.1/man7/ssl.html#Dealing-with-Protocol-Methods +[SSL_METHODS]: https://www.openssl.org/docs/man3.0/man7/ssl.html#Dealing-with-Protocol-Methods [Session Resumption]: #session-resumption [Stream]: stream.md#stream [TLS recommendations]: https://wiki.mozilla.org/Security/Server_Side_TLS @@ -2556,8 +2556,8 @@ added: v0.11.3 [`Duplex`]: stream.md#class-streamduplex [`NODE_EXTRA_CA_CERTS`]: cli.md#node_extra_ca_certsfile [`NODE_OPTIONS`]: cli.md#node_optionsoptions -[`SSL_export_keying_material`]: https://www.openssl.org/docs/man1.1.1/man3/SSL_export_keying_material.html -[`SSL_get_version`]: https://www.openssl.org/docs/man1.1.1/man3/SSL_get_version.html +[`SSL_export_keying_material`]: https://www.openssl.org/docs/man3.0/man3/SSL_export_keying_material.html +[`SSL_get_version`]: https://www.openssl.org/docs/man3.0/man3/SSL_get_version.html [`crypto.getCurves()`]: crypto.md#cryptogetcurves [`import()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import [`net.Server.address()`]: net.md#serveraddress @@ -2591,6 +2591,6 @@ added: v0.11.3 [`x509.checkHost()`]: crypto.md#x509checkhostname-options [asn1.js]: https://www.npmjs.com/package/asn1.js [certificate object]: #certificate-object -[cipher list format]: https://www.openssl.org/docs/man1.1.1/man1/ciphers.html#CIPHER-LIST-FORMAT +[cipher list format]: https://www.openssl.org/docs/man3.0/man1/ciphers.html#CIPHER-LIST-FORMAT [forward secrecy]: https://en.wikipedia.org/wiki/Perfect_forward_secrecy [perfect forward secrecy]: #perfect-forward-secrecy diff --git a/doc/api/webcrypto.md b/doc/api/webcrypto.md index 12d21ae05e73a9..2d11a9b1505c20 100644 --- a/doc/api/webcrypto.md +++ b/doc/api/webcrypto.md @@ -119,15 +119,15 @@ WICG proposal: Algorithms: -* `'AES-OCB'`[^openssl30] +* `'AES-OCB'`[^noboringssl] * `'Argon2d'`[^openssl32] * `'Argon2i'`[^openssl32] * `'Argon2id'`[^openssl32] * `'ChaCha20-Poly1305'` * `'cSHAKE128'` * `'cSHAKE256'` -* `'KMAC128'`[^openssl30] -* `'KMAC256'`[^openssl30] +* `'KMAC128'`[^noboringssl] +* `'KMAC256'`[^noboringssl] * `'KT128'` * `'KT256'` * `'ML-DSA-44'`[^openssl35] @@ -2715,7 +2715,7 @@ added: [^modern-algos]: See [Modern Algorithms in the Web Cryptography API][] -[^openssl30]: Requires OpenSSL >= 3.0 +[^noboringssl]: Not available when Node.js is built against BoringSSL [^openssl32]: Requires OpenSSL >= 3.2 diff --git a/doc/node.1 b/doc/node.1 index cc51fde7661ac3..8b07db0cfb05fc 100644 --- a/doc/node.1 +++ b/doc/node.1 @@ -2345,8 +2345,8 @@ environment variable is arbitrary. . .It Ev OPENSSL_CONF Ar file Load an OpenSSL configuration file on startup. Among other uses, this can be -used to enable FIPS-compliant crypto if Node.js is built with -\fB./configure --openssl-fips\fR. +used to enable FIPS-compliant crypto if Node.js is built against +FIPS-enabled OpenSSL. If the \fB--openssl-config\fR command-line option is used, the environment variable is ignored. . From c1fa107bc56adb652e6d2bddef07ad00f6695703 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Mon, 27 Jul 2026 01:09:10 +0200 Subject: [PATCH 6/9] crypto: remove runtime FIPS mode API crypto.setFips() only narrowed OpenSSL's default property query: it succeeded with no FIPS provider present, and setFips(false) threw "Error: Ok" while working. Remove crypto.setFips(), crypto.fips and --force-fips. crypto.getFips() and --enable-fips are retained. Signed-off-by: Filip Skokan --- deps/ncrypto/ncrypto.cc | 11 +- deps/ncrypto/ncrypto.h | 6 +- doc/api/cli.md | 10 - doc/api/crypto.md | 146 +++++----- doc/api/deprecations.md | 16 +- doc/api/errors.md | 37 ++- doc/node-config-schema.json | 6 +- doc/node.1 | 6 - lib/crypto.js | 33 +-- lib/internal/errors.js | 4 - src/crypto/crypto_util.cc | 25 +- src/node_options.cc | 6 +- src/node_options.h | 1 - test/parallel/test-cli-node-print-help.js | 2 +- test/parallel/test-crypto-fips.js | 265 ++---------------- test/parallel/test-crypto-no-algorithm.js | 22 -- ...rocess-env-allowed-flags-are-documented.js | 1 - typings/internalBinding/crypto.d.ts | 1 - 18 files changed, 150 insertions(+), 448 deletions(-) diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index 8fe25c48ebb321..15bd3ec4dfcc81 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -511,14 +511,13 @@ bool isFipsEnabled() { #endif } -bool setFipsEnabled(bool enable, CryptoErrorList* errors) { - if (isFipsEnabled() == enable) return true; - ClearErrorOnReturn clearErrorOnReturn(errors); +bool setFipsEnabled() { + if (isFipsEnabled()) return true; + ClearErrorOnReturn clearErrorOnReturn; #ifndef OPENSSL_IS_BORINGSSL - return EVP_default_properties_enable_fips(nullptr, enable ? 1 : 0) == 1 && - EVP_default_properties_is_fips_enabled(nullptr); + return EVP_default_properties_enable_fips(nullptr, 1) == 1; #else - return FIPS_mode_set(enable ? 1 : 0) == 1; + return FIPS_mode_set(1) == 1; #endif } diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index 8eea2760de9919..a9f59ff4ed0fc5 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -1823,7 +1823,11 @@ class EnginePointer final { // FIPS bool isFipsEnabled(); -bool setFipsEnabled(bool enabled, CryptoErrorList* errors); +// Restricts the default library context to FIPS-approved implementations. +// This only narrows the default property query; it neither loads nor +// activates the FIPS provider. Callers are expected to have established +// that a FIPS provider is active first, e.g. via testFipsEnabled(). +bool setFipsEnabled(); bool testFipsEnabled(); diff --git a/doc/api/cli.md b/doc/api/cli.md index 71a341e1c49088..876f0577a22475 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -1562,15 +1562,6 @@ added: v12.12.0 Disable loading native addons that are not [context-aware][]. -### `--force-fips` - - - -Force FIPS-compliant crypto on startup. (Cannot be disabled from script code.) -(Same requirements as `--enable-fips`.) - ### `--force-node-api-uncaught-exceptions-policy` - -> Stability: 0 - Deprecated - -Property for checking and controlling whether a FIPS compliant crypto provider -is currently in use. Setting to true requires a FIPS build of Node.js. - -This property is deprecated. Please use `crypto.setFips()` and -`crypto.getFips()` instead. - ### `crypto.generateKey(type, options, callback)` -* Returns: {number} `1` if and only if a FIPS compliant crypto provider is - currently in use, `0` otherwise. A future semver-major release may change - the return type of this API to a {boolean}. +* Returns: {number} `1` if the default OpenSSL library context restricts + algorithm selection to FIPS-approved implementations, `0` otherwise. A future + semver-major release may change the return type of this API to a {boolean}. + +This reports whether the default property query is `fips=yes`. It is a +statement of intent, not proof: it does not establish that a FIPS provider is +loaded, nor that any provider in use is FIPS validated. See [FIPS mode][]. ### `crypto.getHashes()` @@ -6167,17 +6156,6 @@ is a bit field taking one of or a mix of the following flags (defined in * `crypto.constants.ENGINE_METHOD_ALL` * `crypto.constants.ENGINE_METHOD_NONE` -### `crypto.setFips(bool)` - - - -* `bool` {boolean} `true` to enable FIPS mode. - -Enables the FIPS compliant crypto provider in a FIPS-enabled Node.js build. -Throws an error if FIPS mode is not available. - ### `crypto.sign(algorithm, data, key[, callback])` -Type: Runtime +Type: End-of-Life -The [`crypto.fips`][] property is deprecated. Please use `crypto.setFips()` -and `crypto.getFips()` instead. +The `crypto.fips` property has been removed. Use [`crypto.getFips()`][] to read +whether the default OpenSSL library context is restricted to FIPS-approved +implementations. Changing that at runtime is no longer possible; configure the +FIPS provider through OpenSSL and use [`--enable-fips`][] to require at startup +that one is active. See [FIPS mode][] for details. An automated migration is available ([source](https://github.com/nodejs/userland-migrations/tree/main/recipes/crypto-fips-to-getFips)). @@ -4696,6 +4702,7 @@ successfully before the response closed. [DEP0142]: #dep0142-repl_builtinlibs [DEP0156]: #dep0156-aborted-property-and-abort-aborted-event-in-http +[FIPS mode]: crypto.md#fips-mode [NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf [RFC 6066]: https://tools.ietf.org/html/rfc6066#section-3 [RFC 8247 Section 2.4]: https://www.rfc-editor.org/rfc/rfc8247#section-2.4 @@ -4703,6 +4710,7 @@ successfully before the response closed. [WHATWG URL API]: url.md#the-whatwg-url-api [`"exports"` or `"main"` entry]: packages.md#main-entry-point-export [`'uncaughtException'`]: process.md#event-uncaughtexception +[`--enable-fips`]: cli.md#--enable-fips [`--force-node-api-uncaught-exceptions-policy`]: cli.md#--force-node-api-uncaught-exceptions-policy [`--pending-deprecation`]: cli.md#--pending-deprecation [`--throw-deprecation`]: cli.md#--throw-deprecation @@ -4739,7 +4747,7 @@ successfully before the response closed. [`crypto.createDecipheriv()`]: crypto.md#cryptocreatedecipherivalgorithm-key-iv-options [`crypto.createHash()`]: crypto.md#cryptocreatehashalgorithm-options [`crypto.createHmac()`]: crypto.md#cryptocreatehmacalgorithm-key-options -[`crypto.fips`]: crypto.md#cryptofips +[`crypto.getFips()`]: crypto.md#cryptogetfips [`crypto.pbkdf2()`]: crypto.md#cryptopbkdf2password-salt-iterations-keylen-digest-callback [`crypto.randomBytes()`]: crypto.md#cryptorandombytessize-callback [`crypto.scrypt()`]: crypto.md#cryptoscryptpassword-salt-keylen-options-callback diff --git a/doc/api/errors.md b/doc/api/errors.md index a7a7e6549f68e6..1b833d9c99c25b 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -911,20 +911,6 @@ key lies outside of the elliptic curve. An invalid crypto engine identifier was passed to [`require('node:crypto').setEngine()`][]. - - -### `ERR_CRYPTO_FIPS_FORCED` - -The [`--force-fips`][] command-line argument was used but there was an attempt -to enable or disable FIPS mode in the `node:crypto` module. - - - -### `ERR_CRYPTO_FIPS_UNAVAILABLE` - -An attempt was made to enable or disable FIPS mode, but FIPS mode was not -available. - ### `ERR_CRYPTO_HASH_FINALIZED` @@ -3768,6 +3754,28 @@ removed: v15.0.0 The native call from `process.cpuUsage` could not be processed. + + +### `ERR_CRYPTO_FIPS_FORCED` + + + +The `--force-fips` command-line argument was used but there was an attempt +to enable or disable FIPS mode in the `node:crypto` module. + + + +### `ERR_CRYPTO_FIPS_UNAVAILABLE` + + + +An attempt was made to enable or disable FIPS mode, but FIPS mode was not +available. + ### `ERR_CRYPTO_HASH_DIGEST_NO_UTF16` @@ -4592,7 +4600,6 @@ An error occurred trying to allocate memory. This should never happen. [`"imports"`]: packages.md#imports [`'uncaughtException'`]: process.md#event-uncaughtexception [`--disable-proto=throw`]: cli.md#--disable-protomode -[`--force-fips`]: cli.md#--force-fips [`--no-addons`]: cli.md#--no-addons [`--unhandled-rejections`]: cli.md#--unhandled-rejectionsmode [`BoundSocket`]: net.md#class-netboundsocket diff --git a/doc/node-config-schema.json b/doc/node-config-schema.json index 116f0c28681816..cbb6654f2d90d8 100644 --- a/doc/node-config-schema.json +++ b/doc/node-config-schema.json @@ -191,7 +191,7 @@ }, "enable-fips": { "type": "boolean", - "description": "enable FIPS crypto at startup" + "description": "require an active OpenSSL FIPS provider at startup" }, "enable-source-maps": { "type": "boolean", @@ -284,10 +284,6 @@ "type": "boolean", "description": "disable loading non-context-aware addons" }, - "force-fips": { - "type": "boolean", - "description": "force FIPS crypto (cannot be disabled)" - }, "force-node-api-uncaught-exceptions-policy": { "type": "boolean", "description": "enforces 'uncaughtException' event on Node API asynchronous callbacks" diff --git a/doc/node.1 b/doc/node.1 index 8b07db0cfb05fc..72f4fb62328bbc 100644 --- a/doc/node.1 +++ b/doc/node.1 @@ -834,10 +834,6 @@ if (globalThis.gc) { .It Fl -force-context-aware Disable loading native addons that are not context-aware. . -.It Fl -force-fips -Force FIPS-compliant crypto on startup. (Cannot be disabled from script code.) -(Same requirements as \fB--enable-fips\fR.) -. .It Fl -force-node-api-uncaught-exceptions-policy Enforces \fBuncaughtException\fR event on Node-API asynchronous callbacks. To prevent from an existing add-on from crashing the process, this flag is not @@ -1993,8 +1989,6 @@ one is included in the list below. .It \fB--force-context-aware\fR .It -\fB--force-fips\fR -.It \fB--force-node-api-uncaught-exceptions-policy\fR .It \fB--frozen-intrinsics\fR diff --git a/lib/crypto.js b/lib/crypto.js index ac4b0a33efb8ab..fd6405740b5a4a 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -35,15 +35,10 @@ const { } = require('internal/util'); assertCrypto(); -const { - ERR_CRYPTO_FIPS_FORCED, - ERR_WORKER_UNSUPPORTED_OPERATION, -} = require('internal/errors').codes; const constants = internalBinding('constants').crypto; const { getOptionValue } = require('internal/options'); const { getFipsCrypto, - setFipsCrypto, timingSafeEqual, } = internalBinding('crypto'); const { @@ -134,12 +129,6 @@ function lazyWebCrypto() { return webcrypto; } -let ownsProcessState; -function lazyOwnsProcessState() { - ownsProcessState ??= require('internal/worker').ownsProcessState; - return ownsProcessState; -} - // These helper functions are needed because the constructors can // use new, in which case V8 cannot inline the recursive constructor call function createHash(algorithm, options) { @@ -228,7 +217,6 @@ module.exports = { setEngine, timingSafeEqual, getFips, - setFips, verify: verifyOneShot, hash, encapsulate, @@ -251,19 +239,7 @@ module.exports = { }; function getFips() { - return getOptionValue('--force-fips') ? 1 : getFipsCrypto(); -} - -function setFips(val) { - if (getOptionValue('--force-fips')) { - if (val) return; - throw new ERR_CRYPTO_FIPS_FORCED(); - } else { - if (!lazyOwnsProcessState()) { - throw new ERR_WORKER_UNSUPPORTED_OPERATION('Calling crypto.setFips()'); - } - setFipsCrypto(val); - } + return getFipsCrypto(); } function getRandomValues(array) { @@ -340,13 +316,6 @@ function getRandomBytesAlias(key) { } ObjectDefineProperties(module.exports, { - fips: { - __proto__: null, - get: deprecate(getFips, 'The crypto.fips is deprecated. ' + - 'Please use crypto.getFips()', 'DEP0093'), - set: deprecate(setFips, 'The crypto.fips is deprecated. ' + - 'Please use crypto.setFips()', 'DEP0093'), - }, constants: { __proto__: null, configurable: false, diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 1b5487849169cb..d0e6761b9e355b 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1175,10 +1175,6 @@ E('ERR_CRYPTO_ECDH_INVALID_FORMAT', 'Invalid ECDH format: %s', TypeError); E('ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY', 'Public key is not valid for specified curve', Error); E('ERR_CRYPTO_ENGINE_UNKNOWN', 'Engine "%s" was not found', Error); -E('ERR_CRYPTO_FIPS_FORCED', - 'Cannot set FIPS mode, it was forced with --force-fips at startup.', Error); -E('ERR_CRYPTO_FIPS_UNAVAILABLE', 'Cannot set FIPS mode in a non-FIPS build.', - Error); E('ERR_CRYPTO_HASH_FINALIZED', 'Digest already called', Error); E('ERR_CRYPTO_HASH_UPDATE_FAILED', 'Hash update failed', Error); E('ERR_CRYPTO_INCOMPATIBLE_KEY', 'Incompatible %s: %s', Error); diff --git a/src/crypto/crypto_util.cc b/src/crypto/crypto_util.cc index 628038d770ada1..b9f3202b5e5b2d 100644 --- a/src/crypto/crypto_util.cc +++ b/src/crypto/crypto_util.cc @@ -83,10 +83,9 @@ int NoPasswordCallback(char* buf, int size, int rwflag, void* u) { bool ProcessFipsOptions() { /* Override FIPS settings in configuration file, if needed. */ - if (per_process::cli_options->enable_fips_crypto || - per_process::cli_options->force_fips_crypto) { + if (per_process::cli_options->enable_fips_crypto) { if (!ncrypto::testFipsEnabled()) return false; - return ncrypto::setFipsEnabled(true, nullptr); + return ncrypto::setFipsEnabled(); } return true; } @@ -177,24 +176,6 @@ void GetFipsCrypto(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(ncrypto::isFipsEnabled() ? 1 : 0); } -void SetFipsCrypto(const FunctionCallbackInfo& args) { - Mutex::ScopedLock lock(per_process::cli_options_mutex); - Mutex::ScopedLock fips_lock(fips_mutex); - - CHECK(!per_process::cli_options->force_fips_crypto); - Environment* env = Environment::GetCurrent(args); - CHECK(env->owns_process_state()); - bool enable = args[0]->BooleanValue(env->isolate()); - - CryptoErrorList errors; - if (!ncrypto::setFipsEnabled(enable, &errors)) { - Local exception; - if (cryptoErrorListToException(env, errors).ToLocal(&exception)) { - env->isolate()->ThrowException(exception); - } - } -} - void TestFipsCrypto(const v8::FunctionCallbackInfo& args) { Mutex::ScopedLock lock(per_process::cli_options_mutex); Mutex::ScopedLock fips_lock(fips_mutex); @@ -841,7 +822,6 @@ void Initialize(Environment* env, Local target) { #endif // !OPENSSL_NO_ENGINE SetMethodNoSideEffect(context, target, "getFipsCrypto", GetFipsCrypto); - SetMethod(context, target, "setFipsCrypto", SetFipsCrypto); SetMethodNoSideEffect(context, target, "testFipsCrypto", TestFipsCrypto); NODE_DEFINE_CONSTANT(target, kCryptoJobAsync); @@ -860,7 +840,6 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { #endif // !OPENSSL_NO_ENGINE registry->Register(GetFipsCrypto); - registry->Register(SetFipsCrypto); registry->Register(TestFipsCrypto); registry->Register(SecureBuffer); registry->Register(SecureHeapUsed); diff --git a/src/node_options.cc b/src/node_options.cc index 1354fd5250885c..3d9e1bb2258ab2 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -1471,13 +1471,9 @@ PerProcessOptionsParser::PerProcessOptionsParser( Implies("--use-openssl-ca", "[ssl_openssl_cert_store]"); ImpliesNot("--use-bundled-ca", "[ssl_openssl_cert_store]"); AddOption("--enable-fips", - "enable FIPS crypto at startup", + "require an active OpenSSL FIPS provider at startup", &PerProcessOptions::enable_fips_crypto, kAllowedInEnvvar); - AddOption("--force-fips", - "force FIPS crypto (cannot be disabled)", - &PerProcessOptions::force_fips_crypto, - kAllowedInEnvvar); #ifndef V8_ENABLE_SANDBOX AddOption("--secure-heap", "total size of the OpenSSL secure heap", diff --git a/src/node_options.h b/src/node_options.h index 4debd167857ac0..2a89d6a063ae87 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -376,7 +376,6 @@ class PerProcessOptions : public Options { bool use_openssl_ca = false; bool use_bundled_ca = false; bool enable_fips_crypto = false; - bool force_fips_crypto = false; #endif #if HAVE_OPENSSL && !defined(OPENSSL_IS_BORINGSSL) bool openssl_legacy_provider = false; diff --git a/test/parallel/test-cli-node-print-help.js b/test/parallel/test-cli-node-print-help.js index f42129eb133026..3e56f0bbdaad1c 100644 --- a/test/parallel/test-cli-node-print-help.js +++ b/test/parallel/test-cli-node-print-help.js @@ -27,7 +27,7 @@ function validateNodePrintHelp() { { compileConstant: HAVE_OPENSSL, flags: [ '--openssl-config=...', '--tls-cipher-list=...', '--use-bundled-ca', '--use-openssl-ca', '--use-system-ca', - '--enable-fips', '--force-fips' ] }, + '--enable-fips' ] }, { compileConstant: NODE_HAVE_I18N_SUPPORT, flags: [ '--icu-data-dir=...', 'NODE_ICU_DATA' ] }, { compileConstant: HAVE_INSPECTOR, diff --git a/test/parallel/test-crypto-fips.js b/test/parallel/test-crypto-fips.js index 04a95fa5fd37c9..ed059ad952a4f6 100644 --- a/test/parallel/test-crypto-fips.js +++ b/test/parallel/test-crypto-fips.js @@ -8,27 +8,23 @@ if (process.features.openssl_is_boringssl) common.skip('BoringSSL does not support FIPS'); const assert = require('assert'); +const crypto = require('crypto'); const spawnSync = require('child_process').spawnSync; const path = require('path'); -const fixtures = require('../common/fixtures'); const { internalBinding } = require('internal/test/binding'); const { testFipsCrypto } = internalBinding('crypto'); -const { hasOpenSSL3 } = require('../common/crypto'); const FIPS_ENABLED = 1; const FIPS_DISABLED = 0; -const FIPS_ERROR_STRING2 = - 'Error [ERR_CRYPTO_FIPS_FORCED]: Cannot set FIPS mode, it was forced with ' + - '--force-fips at startup.'; -const FIPS_UNSUPPORTED_ERROR_STRING = 'fips mode not supported'; const FIPS_ENABLE_ERROR_STRING = 'OpenSSL error when trying to enable FIPS:'; -const CNF_FIPS_ON = fixtures.path('openssl_fips_enabled.cnf'); -const CNF_FIPS_OFF = fixtures.path('openssl_fips_disabled.cnf'); - const kNoFailure = 0; const kGenericUserError = 1; +// Whether a provider named "fips" is activated in the default library context. +// This is a runtime property of the OpenSSL configuration, not of the build. +const fipsProviderAvailable = testFipsCrypto() === 1; + let num_children_ok = 0; function sharedOpenSSL() { @@ -39,7 +35,7 @@ function testHelper(stream, args, expectedStatus, expectedOutput, cmd, env) { const fullArgs = args.concat(['-e', `console.log(${cmd})`]); const child = spawnSync(process.execPath, fullArgs, { cwd: path.dirname(process.execPath), - env: env + env: env, }); console.error( @@ -69,27 +65,35 @@ function testHelper(stream, args, expectedStatus, expectedOutput, cmd, env) { responseHandler(child[stream], expectedOutput); } -// --enable-fips should raise an error if OpenSSL is not FIPS enabled. +// There is no way to enter or leave FIPS mode at runtime. FIPS mode is a +// property of the OpenSSL configuration, and is only asserted at startup. +assert.strictEqual(crypto.setFips, undefined); +assert.strictEqual(Object.hasOwn(crypto, 'fips'), false); + +// testFipsCrypto() reports whether a provider named "fips" is activated, so it +// is always a well-defined 0 or 1 regardless of how Node.js was built. +assert.ok(testFipsCrypto() === 1 || testFipsCrypto() === 0); + +// --enable-fips fails closed: if no FIPS provider is active it must refuse to +// start rather than leave the process in a half-configured state. testHelper( - testFipsCrypto() ? 'stdout' : 'stderr', + fipsProviderAvailable ? 'stdout' : 'stderr', ['--enable-fips'], - testFipsCrypto() ? kNoFailure : kGenericUserError, - testFipsCrypto() ? FIPS_ENABLED : FIPS_ENABLE_ERROR_STRING, - 'process.versions', + fipsProviderAvailable ? kNoFailure : kGenericUserError, + fipsProviderAvailable ? FIPS_ENABLED : FIPS_ENABLE_ERROR_STRING, + 'require("crypto").getFips()', process.env); -// --force-fips should raise an error if OpenSSL is not FIPS enabled. -testHelper( - testFipsCrypto() ? 'stdout' : 'stderr', - ['--force-fips'], - testFipsCrypto() ? kNoFailure : kGenericUserError, - testFipsCrypto() ? FIPS_ENABLED : FIPS_ENABLE_ERROR_STRING, - 'process.versions', - process.env); +// --force-fips was removed; it only existed to lock out crypto.setFips(). +{ + const child = spawnSync(process.execPath, ['--force-fips', '-p', '1'], + { encoding: 'utf8' }); + assert.notStrictEqual(child.status, 0); + assert.match(child.stderr, /bad option: --force-fips/); +} -// By default FIPS should be off in both FIPS and non-FIPS builds -// unless Node.js was configured using --shared-openssl in -// which case it may be enabled by the system. +// By default FIPS should be off, unless Node.js was configured using +// --shared-openssl in which case it may be enabled by the system. if (!sharedOpenSSL()) { testHelper( 'stdout', @@ -99,212 +103,3 @@ if (!sharedOpenSSL()) { 'require("crypto").getFips()', { ...process.env, 'OPENSSL_CONF': ' ' }); } - -// Toggling fips with setFips should not be allowed from a worker thread -testHelper( - 'stderr', - [], - kGenericUserError, - 'Calling crypto.setFips() is not supported in workers', - 'new worker_threads.Worker(\'require("crypto").setFips(true);\', { eval: true })', - process.env); - -// This should succeed for both FIPS and non-FIPS builds in combination with -// OpenSSL 1.1.1 or OpenSSL 3.0 -const test_result = testFipsCrypto(); -assert.ok(test_result === 1 || test_result === 0); - -// If Node was configured using --shared-openssl fips support might be -// available depending on how OpenSSL was built. If fips support is -// available the tests that toggle the fips_mode on/off using the config -// file option will succeed and return 1 instead of 0. -// -// Note that this case is different from when calling the fips setter as the -// configuration file is handled by OpenSSL, so it is not possible for us -// to try to call the fips setter, to try to detect this situation, as -// that would throw an error: -// ("Error: Cannot set FIPS mode in a non-FIPS build."). -// Due to this uncertainty the following tests are skipped when configured -// with --shared-openssl. -if (!sharedOpenSSL() && !hasOpenSSL3) { - // OpenSSL config file should be able to turn on FIPS mode - testHelper( - 'stdout', - [`--openssl-config=${CNF_FIPS_ON}`], - kNoFailure, - testFipsCrypto() ? FIPS_ENABLED : FIPS_DISABLED, - 'require("crypto").getFips()', - process.env); - - // OPENSSL_CONF should be able to turn on FIPS mode - testHelper( - 'stdout', - [], - kNoFailure, - testFipsCrypto() ? FIPS_ENABLED : FIPS_DISABLED, - 'require("crypto").getFips()', - Object.assign({}, process.env, { 'OPENSSL_CONF': CNF_FIPS_ON })); - - // --openssl-config option should override OPENSSL_CONF - testHelper( - 'stdout', - [`--openssl-config=${CNF_FIPS_ON}`], - kNoFailure, - testFipsCrypto() ? FIPS_ENABLED : FIPS_DISABLED, - 'require("crypto").getFips()', - Object.assign({}, process.env, { 'OPENSSL_CONF': CNF_FIPS_OFF })); -} - -// OpenSSL 3.x has changed the configuration files so the following tests -// will not work as expected with that version. -// TODO(danbev) Revisit these test once FIPS support is available in -// OpenSSL 3.x. -if (!hasOpenSSL3) { - testHelper( - 'stdout', - [`--openssl-config=${CNF_FIPS_OFF}`], - kNoFailure, - FIPS_DISABLED, - 'require("crypto").getFips()', - Object.assign({}, process.env, { 'OPENSSL_CONF': CNF_FIPS_ON })); - - // --enable-fips should take precedence over OpenSSL config file - testHelper( - testFipsCrypto() ? 'stdout' : 'stderr', - ['--enable-fips', `--openssl-config=${CNF_FIPS_OFF}`], - testFipsCrypto() ? kNoFailure : kGenericUserError, - testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING, - 'require("crypto").getFips()', - process.env); - // --force-fips should take precedence over OpenSSL config file - testHelper( - testFipsCrypto() ? 'stdout' : 'stderr', - ['--force-fips', `--openssl-config=${CNF_FIPS_OFF}`], - testFipsCrypto() ? kNoFailure : kGenericUserError, - testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING, - 'require("crypto").getFips()', - process.env); - // --enable-fips should turn FIPS mode on - testHelper( - testFipsCrypto() ? 'stdout' : 'stderr', - ['--enable-fips'], - testFipsCrypto() ? kNoFailure : kGenericUserError, - testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING, - 'require("crypto").getFips()', - process.env); - - // --force-fips should turn FIPS mode on - testHelper( - testFipsCrypto() ? 'stdout' : 'stderr', - ['--force-fips'], - testFipsCrypto() ? kNoFailure : kGenericUserError, - testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING, - 'require("crypto").getFips()', - process.env); - - // OPENSSL_CONF should _not_ make a difference to --enable-fips - testHelper( - testFipsCrypto() ? 'stdout' : 'stderr', - ['--enable-fips'], - testFipsCrypto() ? kNoFailure : kGenericUserError, - testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING, - 'require("crypto").getFips()', - Object.assign({}, process.env, { 'OPENSSL_CONF': CNF_FIPS_OFF })); - - // Using OPENSSL_CONF should not make a difference to --force-fips - testHelper( - testFipsCrypto() ? 'stdout' : 'stderr', - ['--force-fips'], - testFipsCrypto() ? kNoFailure : kGenericUserError, - testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING, - 'require("crypto").getFips()', - Object.assign({}, process.env, { 'OPENSSL_CONF': CNF_FIPS_OFF })); - - // setFipsCrypto should be able to turn FIPS mode on - testHelper( - testFipsCrypto() ? 'stdout' : 'stderr', - [], - testFipsCrypto() ? kNoFailure : kGenericUserError, - testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING, - '(require("crypto").setFips(true),' + - 'require("crypto").getFips())', - process.env); - - // setFipsCrypto should be able to turn FIPS mode on and off - testHelper( - testFipsCrypto() ? 'stdout' : 'stderr', - [], - testFipsCrypto() ? kNoFailure : kGenericUserError, - testFipsCrypto() ? FIPS_DISABLED : FIPS_UNSUPPORTED_ERROR_STRING, - '(require("crypto").setFips(true),' + - 'require("crypto").setFips(false),' + - 'require("crypto").getFips())', - process.env); - - // setFipsCrypto takes precedence over OpenSSL config file, FIPS on - testHelper( - testFipsCrypto() ? 'stdout' : 'stderr', - [`--openssl-config=${CNF_FIPS_OFF}`], - testFipsCrypto() ? kNoFailure : kGenericUserError, - testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING, - '(require("crypto").setFips(true),' + - 'require("crypto").getFips())', - process.env); - - // setFipsCrypto takes precedence over OpenSSL config file, FIPS off - testHelper( - 'stdout', - [`--openssl-config=${CNF_FIPS_ON}`], - kNoFailure, - FIPS_DISABLED, - '(require("crypto").setFips(false),' + - 'require("crypto").getFips())', - process.env); - - // --enable-fips does not prevent use of setFipsCrypto API - testHelper( - testFipsCrypto() ? 'stdout' : 'stderr', - ['--enable-fips'], - testFipsCrypto() ? kNoFailure : kGenericUserError, - testFipsCrypto() ? FIPS_DISABLED : FIPS_UNSUPPORTED_ERROR_STRING, - '(require("crypto").setFips(false),' + - 'require("crypto").getFips())', - process.env); - - // --force-fips prevents use of setFipsCrypto API - testHelper( - 'stderr', - ['--force-fips'], - kGenericUserError, - testFipsCrypto() ? FIPS_ERROR_STRING2 : FIPS_UNSUPPORTED_ERROR_STRING, - 'require("crypto").setFips(false)', - process.env); - - // --force-fips makes setFipsCrypto enable a no-op (FIPS stays on) - testHelper( - testFipsCrypto() ? 'stdout' : 'stderr', - ['--force-fips'], - testFipsCrypto() ? kNoFailure : kGenericUserError, - testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING, - '(require("crypto").setFips(true),' + - 'require("crypto").getFips())', - process.env); - - // --force-fips and --enable-fips order does not matter - testHelper( - 'stderr', - ['--force-fips', '--enable-fips'], - kGenericUserError, - testFipsCrypto() ? FIPS_ERROR_STRING2 : FIPS_UNSUPPORTED_ERROR_STRING, - 'require("crypto").setFips(false)', - process.env); - - // --enable-fips and --force-fips order does not matter - testHelper( - 'stderr', - ['--enable-fips', '--force-fips'], - kGenericUserError, - testFipsCrypto() ? FIPS_ERROR_STRING2 : FIPS_UNSUPPORTED_ERROR_STRING, - 'require("crypto").setFips(false)', - process.env); -} diff --git a/test/parallel/test-crypto-no-algorithm.js b/test/parallel/test-crypto-no-algorithm.js index 96236a976a89ef..cd72fd249406d9 100644 --- a/test/parallel/test-crypto-no-algorithm.js +++ b/test/parallel/test-crypto-no-algorithm.js @@ -4,29 +4,7 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); -const { hasOpenSSL3 } = require('../common/crypto'); - -if (!hasOpenSSL3) - common.skip('this test requires OpenSSL 3.x'); - const assert = require('node:assert/strict'); -const crypto = require('node:crypto'); -const { isMainThread } = require('worker_threads'); - -if (isMainThread) { - // TODO(richardlau): Decide if `crypto.setFips` should error if the - // provider named "fips" is not available. - crypto.setFips(1); - crypto.randomBytes(20, common.mustCall((err) => { - // crypto.randomBytes should either succeed or fail but not hang. - if (err) { - assert.match(err.message, /digital envelope routines::unsupported/); - const expected = /random number generator::unable to fetch drbg/; - assert(err.opensslErrorStack.some((msg) => expected.test(msg)), - `did not find ${expected} in ${err.opensslErrorStack}`); - } - })); -} { // Startup test. Should not hang. diff --git a/test/parallel/test-process-env-allowed-flags-are-documented.js b/test/parallel/test-process-env-allowed-flags-are-documented.js index 6028bbab787e29..cc3bbdb371f7c1 100644 --- a/test/parallel/test-process-env-allowed-flags-are-documented.js +++ b/test/parallel/test-process-env-allowed-flags-are-documented.js @@ -72,7 +72,6 @@ const conditionalOpts = [ '--secure-heap', '--secure-heap-min', '--enable-fips', - '--force-fips', ].includes(opt); } }, { diff --git a/typings/internalBinding/crypto.d.ts b/typings/internalBinding/crypto.d.ts index d91c5018ba688a..51ca77b17fd730 100644 --- a/typings/internalBinding/crypto.d.ts +++ b/typings/internalBinding/crypto.d.ts @@ -955,7 +955,6 @@ export interface CryptoBinding { secureBuffer(length: number): Uint8Array | undefined; secureHeapUsed(): bigint | undefined; setEngine?(engine: string, flags: number): void; - setFipsCrypto(fips: boolean | number): void; startLoadingCertificatesOffThread(): void; testFipsCrypto(): 0 | 1; timingSafeEqual( From 5e039a4efb1ac6ac1701e27ef7f50d87502f2871 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Mon, 27 Jul 2026 01:11:26 +0200 Subject: [PATCH 7/9] crypto: report why --enable-fips failed The startup failure always appended the OpenSSL error queue, so when Node.js itself detected the missing fips provider it printed an error header followed by nothing. Report the reason instead. Signed-off-by: Filip Skokan --- src/crypto/crypto_util.cc | 23 +++++++++++++++++------ src/crypto/crypto_util.h | 5 ++++- src/node.cc | 12 ++++++++---- test/parallel/test-crypto-fips.js | 3 ++- 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/crypto/crypto_util.cc b/src/crypto/crypto_util.cc index b9f3202b5e5b2d..da70d6d8d716e4 100644 --- a/src/crypto/crypto_util.cc +++ b/src/crypto/crypto_util.cc @@ -81,13 +81,24 @@ int NoPasswordCallback(char* buf, int size, int rwflag, void* u) { return 0; } -bool ProcessFipsOptions() { - /* Override FIPS settings in configuration file, if needed. */ - if (per_process::cli_options->enable_fips_crypto) { - if (!ncrypto::testFipsEnabled()) return false; - return ncrypto::setFipsEnabled(); +std::optional ProcessFipsOptions() { + if (!per_process::cli_options->enable_fips_crypto) return std::nullopt; + + // Whether FIPS-approved implementations are reachable is decided by the + // OpenSSL configuration, not by Node.js. Refuse to start rather than + // restrict the default property query to a provider that is not there, + // which would leave every operation failing as unsupported. + if (!ncrypto::testFipsEnabled()) { + return "--enable-fips requires an active OpenSSL provider named \"fips\". " + "FIPS mode is configured through OpenSSL; see " + "https://nodejs.org/api/crypto.html#fips-mode"; } - return true; + + if (!ncrypto::setFipsEnabled()) { + return "OpenSSL error when trying to enable FIPS"; + } + + return std::nullopt; } bool InitCryptoOnce(Isolate* isolate) { diff --git a/src/crypto/crypto_util.h b/src/crypto/crypto_util.h index c27b745ce83c8e..5f45cab022ffa5 100644 --- a/src/crypto/crypto_util.h +++ b/src/crypto/crypto_util.h @@ -48,7 +48,10 @@ constexpr T NumBitsToBytes(T bits) { return (bits / CHAR_BIT) + ((CHAR_BIT - 1 + (bits % CHAR_BIT)) / CHAR_BIT); } -bool ProcessFipsOptions(); +// Applies the FIPS related command line options. Returns a description of +// what went wrong, or std::nullopt when there was nothing to do or the +// options were applied successfully. +std::optional ProcessFipsOptions(); bool InitCryptoOnce(v8::Isolate* isolate); void InitCryptoOnce(); diff --git a/src/node.cc b/src/node.cc index 979e278326e042..f680ab67b31d00 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1240,12 +1240,16 @@ InitializeOncePerProcessInternal(const std::vector& args, return result; } - if (!crypto::ProcessFipsOptions()) { + if (auto fips_error = crypto::ProcessFipsOptions()) { result->exit_code_ = ExitCode::kGenericUserError; result->early_return_ = true; - result->errors_.emplace_back( - "OpenSSL error when trying to enable FIPS:\n" + - GetOpenSSLErrorString()); + // The error queue is empty when the failure was detected by Node.js + // rather than reported by OpenSSL, in which case appending it would + // produce a message that trails off into nothing. + std::string openssl_errors = GetOpenSSLErrorString(); + result->errors_.emplace_back(openssl_errors.empty() + ? *fips_error + : *fips_error + ":\n" + openssl_errors); return result; } diff --git a/test/parallel/test-crypto-fips.js b/test/parallel/test-crypto-fips.js index ed059ad952a4f6..53a5552ed0913c 100644 --- a/test/parallel/test-crypto-fips.js +++ b/test/parallel/test-crypto-fips.js @@ -16,7 +16,8 @@ const { testFipsCrypto } = internalBinding('crypto'); const FIPS_ENABLED = 1; const FIPS_DISABLED = 0; -const FIPS_ENABLE_ERROR_STRING = 'OpenSSL error when trying to enable FIPS:'; +const FIPS_ENABLE_ERROR_STRING = + '--enable-fips requires an active OpenSSL provider named "fips"'; const kNoFailure = 0; const kGenericUserError = 1; From 5b3cf9fe0f060814d8359cb57dd62dbdf70d7d8e Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Mon, 27 Jul 2026 01:13:01 +0200 Subject: [PATCH 8/9] test,tools: drop OpenSSL 1.x-era FIPS leftovers The openssl_fips_*.cnf fixtures use OpenSSL 1.x syntax and are unused, get_env_type() sniffed for a "-fips" version suffix that can no longer occur, and the crypto-check lint rule listed a helper that no longer exists. Signed-off-by: Filip Skokan --- test/fixtures/openssl_fips_disabled.cnf | 12 ------------ test/fixtures/openssl_fips_enabled.cnf | 12 ------------ test/parallel/test-dsa-fips-invalid-key.js | 2 +- test/parallel/test-process-versions.js | 12 ++++-------- tools/eslint-rules/crypto-check.js | 2 +- tools/test.py | 13 ++++--------- 6 files changed, 10 insertions(+), 43 deletions(-) delete mode 100644 test/fixtures/openssl_fips_disabled.cnf delete mode 100644 test/fixtures/openssl_fips_enabled.cnf diff --git a/test/fixtures/openssl_fips_disabled.cnf b/test/fixtures/openssl_fips_disabled.cnf deleted file mode 100644 index 253c6906e3f3a3..00000000000000 --- a/test/fixtures/openssl_fips_disabled.cnf +++ /dev/null @@ -1,12 +0,0 @@ -# Skeleton openssl.cnf for testing with FIPS - -nodejs_conf = openssl_conf_section -authorityKeyIdentifier=keyid:always,issuer:always - -[openssl_conf_section] - # Configuration module list -alg_section = evp_sect - -[ evp_sect ] -# Set to "yes" to enter FIPS mode if supported -fips_mode = no diff --git a/test/fixtures/openssl_fips_enabled.cnf b/test/fixtures/openssl_fips_enabled.cnf deleted file mode 100644 index 79733c657a96d0..00000000000000 --- a/test/fixtures/openssl_fips_enabled.cnf +++ /dev/null @@ -1,12 +0,0 @@ -# Skeleton openssl.cnf for testing with FIPS - -nodejs_conf = openssl_conf_section -authorityKeyIdentifier=keyid:always,issuer:always - -[openssl_conf_section] - # Configuration module list -alg_section = evp_sect - -[ evp_sect ] -# Set to "yes" to enter FIPS mode if supported -fips_mode = yes diff --git a/test/parallel/test-dsa-fips-invalid-key.js b/test/parallel/test-dsa-fips-invalid-key.js index 3df51bfbed3517..43ac7e22ced6a0 100644 --- a/test/parallel/test-dsa-fips-invalid-key.js +++ b/test/parallel/test-dsa-fips-invalid-key.js @@ -9,7 +9,7 @@ const fixtures = require('../common/fixtures'); const crypto = require('crypto'); if (!crypto.getFips()) { - common.skip('node compiled without FIPS OpenSSL.'); + common.skip('OpenSSL is not configured for FIPS mode'); } const assert = require('assert'); diff --git a/test/parallel/test-process-versions.js b/test/parallel/test-process-versions.js index 14ac88d76cd24d..7b434d3e5daa45 100644 --- a/test/parallel/test-process-versions.js +++ b/test/parallel/test-process-versions.js @@ -104,18 +104,14 @@ assert.match( assert.match(process.versions.modules, /^\d+$/); if (common.hasCrypto) { - const { hasOpenSSL3 } = require('../common/crypto'); assert.match(process.versions.ncrypto, commonTemplate); if (process.config.variables.node_shared_openssl) { assert.ok(process.versions.openssl); } else { - const versionRegex = hasOpenSSL3 ? - // The following also matches a development version of OpenSSL 3.x which - // can be in the format '3.0.0-alpha4-dev'. This can be handy when - // building and linking against the main development branch of OpenSSL. - /^\d+\.\d+\.\d+(?:[-+][a-z0-9]+)*$/ : - /^\d+\.\d+\.\d+[a-z]?(\+quic)?(-fips)?$/; - assert.match(process.versions.openssl, versionRegex); + // The following also matches a development version of OpenSSL 3.x which + // can be in the format '3.0.0-alpha4-dev'. This can be handy when + // building and linking against the main development branch of OpenSSL. + assert.match(process.versions.openssl, /^\d+\.\d+\.\d+(?:[-+][a-z0-9]+)*$/); } } diff --git a/tools/eslint-rules/crypto-check.js b/tools/eslint-rules/crypto-check.js index 10862c1b160b3b..bd79303829bff8 100644 --- a/tools/eslint-rules/crypto-check.js +++ b/tools/eslint-rules/crypto-check.js @@ -48,7 +48,7 @@ module.exports = { } function isCryptoCheck(node) { - return utils.usesCommonProperty(node, ['hasCrypto', 'hasFipsCrypto']); + return utils.usesCommonProperty(node, ['hasCrypto']); } function checkCryptoCall(node) { diff --git a/tools/test.py b/tools/test.py index 2c2a4d78d80aea..aa8c3fbddf53f9 100755 --- a/tools/test.py +++ b/tools/test.py @@ -1460,7 +1460,7 @@ def BuildOptions(): help='Send SIGABRT instead of SIGTERM to kill processes that time out', default=False, action="store_true", dest="abort_on_timeout") result.add_argument("--type", - help="Type of build (simple, fips, coverage)", + help="Type of build (simple, coverage)", default=None) result.add_argument("--error-reporter", help="use error reporter if the test uses node:test", @@ -1622,14 +1622,9 @@ def ArgsToTestPaths(test_root, args, suites): def get_env_type(vm, options_type, context): if options_type is not None: - env_type = options_type - else: - # 'simple' is the default value for 'env_type'. - env_type = 'simple' - ssl_ver = Execute([vm, '-p', 'process.versions.openssl'], context).stdout - if 'fips' in ssl_ver: - env_type = 'fips' - return env_type + return options_type + # 'simple' is the default value for 'env_type'. + return 'simple' def get_asan_state(vm, context): From 1e2b712396e9b2d0f068c59bef3f30f793e99d81 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Mon, 27 Jul 2026 01:17:22 +0200 Subject: [PATCH 9/9] build: remove the bundled FIPS provider build --openssl-is-fips with bundled OpenSSL never worked: the openssl-fipsmodule target had no dependency edge, so fipsinstall's input was produced by nothing. Repairing it would not help, since a FIPS provider built out of tree has no validation status. Remove the machinery and restrict --openssl-is-fips to --shared-openssl. Signed-off-by: Filip Skokan --- BUILDING.md | 20 ++++-- configure.py | 10 +-- deps/openssl/openssl.gyp | 31 +-------- node.gyp | 97 +++++------------------------ src/node_config.cc | 2 - tools/enable_fips_include.py | 42 ------------- typings/internalBinding/config.d.ts | 1 - 7 files changed, 36 insertions(+), 167 deletions(-) delete mode 100644 tools/enable_fips_include.py diff --git a/BUILDING.md b/BUILDING.md index 12f9f7660b36fb..f05762945f9ad7 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -1030,7 +1030,7 @@ as `deps/icu` (You'll have: `deps/icu/source/...`) ### Configure OpenSSL appname Node.js can use an OpenSSL configuration file by specifying the environment -variable `OPENSSL_CONF`, or using the command line option `--openssl-conf`, and +variable `OPENSSL_CONF`, or using the command line option `--openssl-config`, and if none of those are specified will default to reading the default OpenSSL configuration file `openssl.cnf`. Node.js will only read a section that is by default named `nodejs_conf`, but this name can be overridden using the following @@ -1042,12 +1042,20 @@ configure option: ## Building Node.js with FIPS-compliant OpenSSL -Node.js supports FIPS when statically or dynamically linked with OpenSSL 3 via -[OpenSSL's provider model](https://docs.openssl.org/3.0/man7/crypto/#OPENSSL-PROVIDERS). -It is not necessary to rebuild Node.js to enable support for FIPS. +Node.js can use an OpenSSL FIPS provider via +[OpenSSL's provider model](https://docs.openssl.org/master/man7/crypto/#openssl-providers), +whether OpenSSL is linked statically or dynamically. It is not necessary to +rebuild Node.js to do so; the provider and the OpenSSL configuration that +activates it are supplied at runtime. -See [FIPS mode](doc/api/crypto.md#fips-mode) for more information on how to -enable FIPS support in Node.js. +Node.js does not build a FIPS provider. OpenSSL requires that a FIPS provider +be built from a release that carries a FIPS certificate, so a provider built +as part of the Node.js build would have no validation status. + +`./configure --openssl-is-fips` only records that the OpenSSL being linked is +FIPS capable, and requires `--shared-openssl`. + +See [FIPS mode](doc/api/crypto.md#fips-mode) for how to configure it. ## Building Node.js with Temporal support diff --git a/configure.py b/configure.py index a3ab5bd2d281d2..28e7264c5834df 100755 --- a/configure.py +++ b/configure.py @@ -268,7 +268,8 @@ action='store_true', dest='openssl_is_fips', default=None, - help='specifies that the OpenSSL library is FIPS compatible') + help='specifies that the shared OpenSSL library is FIPS capable ' + '(requires --shared-openssl)') parser.add_argument('--openssl-use-def-ca-store', action='store_true', @@ -2275,7 +2276,6 @@ def configure_openssl(o): variables['node_shared_ngtcp2'] = b(options.shared_ngtcp2) variables['node_shared_nghttp3'] = b(options.shared_nghttp3) variables['openssl_is_fips'] = b(options.openssl_is_fips) - variables['node_fipsinstall'] = b(False) if options.openssl_no_asm: variables['openssl_no_asm'] = 1 @@ -2330,12 +2330,12 @@ def without_ssl_error(option): if options.openssl_no_asm and options.shared_openssl: error('--openssl-no-asm is incompatible with --shared-openssl') + if options.openssl_is_fips and not options.shared_openssl: + error('--openssl-is-fips is only available with --shared-openssl') + if options.openssl_is_fips: o['defines'] += ['OPENSSL_FIPS'] - if options.openssl_is_fips and not options.shared_openssl: - variables['node_fipsinstall'] = b(True) - configure_library('openssl', o) o['variables']['openssl_version'] = get_openssl_version(o) diff --git a/deps/openssl/openssl.gyp b/deps/openssl/openssl.gyp index 4e16412a028340..61234b5c6c4d8f 100644 --- a/deps/openssl/openssl.gyp +++ b/deps/openssl/openssl.gyp @@ -97,35 +97,6 @@ }, }], ] - }, { - # openssl-fipsmodule target - 'target_name': 'openssl-fipsmodule', - 'type': 'shared_library', - 'dependencies': ['openssl-cli'], - 'includes': ['./openssl_common.gypi'], - 'include_dirs+': ['openssl/apps/include'], - 'cflags': [ '-fPIC' ], - #'ldflags': [ '-o', 'fips.so' ], - #'ldflags': [ '-Wl,--version-script=providers/fips.ld',], - 'conditions': [ - [ 'openssl_no_asm==1', { - 'includes': ['./openssl-fips_no_asm.gypi'], - }, 'target_arch=="arm64" and OS=="win"', { - # VC-WIN64-ARM inherits from VC-noCE-common that has no asms. - 'includes': ['./openssl-fips_no_asm.gypi'], - }, 'gas_version and v(gas_version) >= v("2.26") or ' - 'nasm_version and v(nasm_version) >= v("2.11.8")', { - # Require AVX512IFMA supported. See - # https://www.openssl.org/docs/man1.1.1/man3/OPENSSL_ia32cap.html - # Currently crypto/poly1305/asm/poly1305-x86_64.pl requires AVX512IFMA. - 'includes': ['./openssl-fips_asm.gypi'], - }, { - 'includes': ['./openssl-fips_asm_avx2.gypi'], - }], - ], - 'direct_dependent_settings': { - 'include_dirs': [ 'openssl/include', 'openssl/crypto/include'] - } - }, + }, ] } diff --git a/node.gyp b/node.gyp index e88bac122b93d4..9a3691388360a1 100644 --- a/node.gyp +++ b/node.gyp @@ -790,87 +790,22 @@ ] }], - ['node_fipsinstall=="true"', { - 'variables': { - 'openssl-cli': '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)openssl-cli<(EXECUTABLE_SUFFIX)', - 'provider_name': 'libopenssl-fipsmodule', - 'opensslconfig': './deps/openssl/nodejs-openssl.cnf', - 'conditions': [ - ['GENERATOR == "ninja"', { - 'fipsmodule_internal': '<(PRODUCT_DIR)/lib/<(provider_name).so', - 'fipsmodule': '<(PRODUCT_DIR)/obj/lib/openssl-modules/fips.so', - 'fipsconfig': '<(PRODUCT_DIR)/obj/lib/fipsmodule.cnf', - 'opensslconfig_internal': '<(PRODUCT_DIR)/obj/lib/openssl.cnf', - }, { - 'fipsmodule_internal': '<(PRODUCT_DIR)/obj.target/deps/openssl/<(provider_name).so', - 'fipsmodule': '<(PRODUCT_DIR)/obj.target/deps/openssl/lib/openssl-modules/fips.so', - 'fipsconfig': '<(PRODUCT_DIR)/obj.target/deps/openssl/fipsmodule.cnf', - 'opensslconfig_internal': '<(PRODUCT_DIR)/obj.target/deps/openssl/openssl.cnf', - }], - ], - }, - 'actions': [ - { - 'action_name': 'fipsinstall', - 'process_outputs_as_sources': 1, - 'inputs': [ - '<(fipsmodule_internal)', - ], - 'outputs': [ - '<(fipsconfig)', - ], - 'action': [ - '<(openssl-cli)', 'fipsinstall', - '-provider_name', '<(provider_name)', - '-module', '<(fipsmodule_internal)', - '-out', '<(fipsconfig)', - #'-quiet', - ], - }, - { - 'action_name': 'copy_fips_module', - 'inputs': [ - '<(fipsmodule_internal)', - ], - 'outputs': [ - '<(fipsmodule)', - ], - 'action': [ - '<(python)', 'tools/copyfile.py', - '<(fipsmodule_internal)', - '<(fipsmodule)', - ], - }, - { - 'action_name': 'copy_openssl_cnf_and_include_fips_cnf', - 'inputs': [ '<(opensslconfig)', ], - 'outputs': [ '<(opensslconfig_internal)', ], - 'action': [ - '<(python)', 'tools/enable_fips_include.py', - '<(opensslconfig)', - '<(opensslconfig_internal)', - '<(fipsconfig)', - ], - }, + ], + 'variables': { + 'opensslconfig_internal': '<(obj_dir)/deps/openssl/openssl.cnf', + 'opensslconfig': './deps/openssl/nodejs-openssl.cnf', + }, + 'actions': [ + { + 'action_name': 'reset_openssl_cnf', + 'inputs': [ '<(opensslconfig)', ], + 'outputs': [ '<(opensslconfig_internal)', ], + 'action': [ + '<(python)', 'tools/copyfile.py', + '<(opensslconfig)', + '<(opensslconfig_internal)', ], - }, { - 'variables': { - 'opensslconfig_internal': '<(obj_dir)/deps/openssl/openssl.cnf', - 'opensslconfig': './deps/openssl/nodejs-openssl.cnf', - }, - 'actions': [ - { - 'action_name': 'reset_openssl_cnf', - 'inputs': [ '<(opensslconfig)', ], - 'outputs': [ '<(opensslconfig_internal)', ], - 'action': [ - '<(python)', 'tools/copyfile.py', - '<(opensslconfig)', - '<(opensslconfig_internal)', - ], - }, - ], - }], + }, ], }, # node_core_target_name { @@ -1053,7 +988,7 @@ ], }, 'conditions': [ - ['openssl_is_fips!=""', { + ['openssl_is_fips=="true"', { 'variables': { 'mkssldef_flags': ['-DOPENSSL_FIPS'] }, }], ], diff --git a/src/node_config.cc b/src/node_config.cc index 7245d9130d0365..2de1ee244ddb13 100644 --- a/src/node_config.cc +++ b/src/node_config.cc @@ -64,8 +64,6 @@ static void InitConfig(Local target, READONLY_FALSE_PROPERTY(target, "hasOpenSSL"); #endif // HAVE_OPENSSL - READONLY_TRUE_PROPERTY(target, "fipsMode"); - #ifdef NODE_HAVE_I18N_SUPPORT READONLY_TRUE_PROPERTY(target, "hasIntl"); diff --git a/tools/enable_fips_include.py b/tools/enable_fips_include.py deleted file mode 100644 index cb24c7d83b689b..00000000000000 --- a/tools/enable_fips_include.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright 2008 the V8 project authors. All rights reserved. -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials provided -# with the distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import sys - -# Copy openssl.cnf into output directory -__import__('copyfile') - -# Open the copied openssl.cnf file -fin = open(sys.argv[2], "rt") -data = fin.read() -data = data.replace('# .include fipsmodule.cnf', '.include %s' % sys.argv[3]) -data = data.replace('# fips = fips_sect', 'fips = fips_sect') -data = data.replace('# activate = 1', 'activate = 1') -fin.close() -fin = open(sys.argv[2], "wt") -fin.write(data) -fin.close() diff --git a/typings/internalBinding/config.d.ts b/typings/internalBinding/config.d.ts index 5651b391b88e35..e85f1a815a8eb9 100644 --- a/typings/internalBinding/config.d.ts +++ b/typings/internalBinding/config.d.ts @@ -2,7 +2,6 @@ export interface ConfigBinding { isDebugBuild: boolean; openSSLIsBoringSSL: boolean; hasOpenSSL: boolean; - fipsMode: boolean; hasIntl: boolean; hasSmallICU: boolean; hasTracing: boolean;