Skip to content

Commit c61953e

Browse files
panvaaduh95
authored andcommitted
crypto: add strict mode to --force-fips
Allow --force-fips to take an optional provider or strict mode. Keep provider as the current default and explicit compatibility mode. In strict mode, return failure from the OpenSSL 3.4+ FIPS indicator callback when it reports a non-approved operation. Install the callback for enforcement without implicitly enabling diagnostics; --enable-fips-indicator-events remains the independent observation opt-in. Keep the bare form mapped to provider for compatibility, allowing the default to change to strict in a future major release. Preserve the mode when forwarding flags to child test processes, and keep the parser's internal mode storage out of process.allowedNodeEnvironmentFlags. Document the callback scope limitations and cover provider compatibility, validation, synchronous crypto, WebCrypto, Workers, and opt-in event publication. PR-URL: #65645 Backport-PR-URL: #66128 Assisted-by: Codex Signed-off-by: Filip Skokan <panva.ip@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent db6ea13 commit c61953e

12 files changed

Lines changed: 282 additions & 15 deletions

File tree

‎doc/api/cli.md‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1397,15 +1397,34 @@ added: v12.12.0
13971397

13981398
Disable loading native addons that are not [context-aware][].
13991399

1400-
### `--force-fips`
1400+
<a id="--force-fips"></a>
1401+
1402+
### `--force-fips[=mode]`
14011403

14021404
<!-- YAML
14031405
added: v6.0.0
1406+
changes:
1407+
- version: REPLACEME
1408+
pr-url: https://github.com/nodejs/node/pull/65645
1409+
description: Added the optional `provider` and `strict` modes.
14041410
-->
14051411

14061412
Enable [FIPS mode][] at startup and prevent it from being disabled from script
14071413
code. The same OpenSSL requirements as [`--enable-fips`][] apply.
14081414

1415+
An optional mode can be specified using `--force-fips=mode`:
1416+
1417+
* `provider`: Preserve the OpenSSL FIPS provider's configured handling of
1418+
non-approved operations. This is the current default when the mode is
1419+
omitted.
1420+
* `strict`: Reject non-approved operations reported through the OpenSSL FIPS
1421+
indicator callback. This mode requires OpenSSL 3.4 or later.
1422+
1423+
The `strict` mode only covers operations reported through the callback for
1424+
OpenSSL's default library context. It does not cover native addons that use
1425+
another `OSSL_LIB_CTX` or another copy of `libcrypto`, nor operation-specific
1426+
indicators that do not invoke the callback.
1427+
14091428
### `--force-node-api-uncaught-exceptions-policy`
14101429

14111430
<!-- YAML

‎doc/api/crypto.md‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7176,7 +7176,9 @@ startup. The following controls are also available:
71767176
* [`--enable-fips`][] and [`--force-fips`][] enable the property query and
71777177
additionally require the configured provider named `fips` to initialize and
71787178
pass its self-test. Node.js exits if that check fails. `--force-fips` also
7179-
prevents FIPS mode from being disabled from script code.
7179+
prevents FIPS mode from being disabled from script code. With
7180+
`--force-fips=strict`, Node.js also rejects non-approved operations reported
7181+
through the OpenSSL FIPS indicator callback.
71807182
* [`crypto.setFips()`][] changes the FIPS/property-query state. On OpenSSL 3, it
71817183
does not install, load, initialize, or validate a provider. Implementations
71827184
fetched before the call are not changed.
@@ -7519,7 +7521,7 @@ See the [list of SSL OP Flags][] for details.
75197521
[`--allow-openssl-store`]: cli.md#--allow-openssl-store
75207522
[`--enable-fips-indicator-events`]: cli.md#--enable-fips-indicator-events
75217523
[`--enable-fips`]: cli.md#--enable-fips
7522-
[`--force-fips`]: cli.md#--force-fips
7524+
[`--force-fips`]: cli.md#--force-fipsmode
75237525
[`--openssl-config`]: cli.md#--openssl-configfile
75247526
[`--openssl-shared-config`]: cli.md#--openssl-shared-config
75257527
[`BN_is_prime_ex`]: https://www.openssl.org/docs/man1.1.1/man3/BN_is_prime_ex.html

‎doc/api/diagnostics_channel.md‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,8 +1162,7 @@ added: REPLACEME
11621162
* `operation` {string} The provider-defined operation type.
11631163
* `reason` {string} The provider-defined description of why the operation is
11641164
not approved.
1165-
* `blocked` {boolean} Whether a native indicator callback installed before
1166-
Node.js blocked the operation.
1165+
* `blocked` {boolean} Whether an indicator callback blocked the operation.
11671166
* `count` {number} The number of matching pending indicator invocations
11681167
represented by this message.
11691168
* `dropped` {number} The number of additional indicator invocations dropped
@@ -1181,7 +1180,10 @@ messages are published.
11811180

11821181
Subscribing to the channel is observation-only and never changes the result of
11831182
an operation. Node.js preserves the result from any native indicator callback
1184-
installed before Node.js initializes its crypto support.
1183+
installed before Node.js initializes its crypto support. When
1184+
[`--force-fips=strict`][] is used, Node.js rejects callback-indicated
1185+
non-approved operations whether or not indicator events are enabled or the
1186+
channel has subscribers.
11851187

11861188
Messages are published asynchronously on the main thread because OpenSSL
11871189
indicators can originate from Workers or other threads. Only subscriptions on
@@ -1604,6 +1606,7 @@ statement, since both are still in use while the event is being delivered; see
16041606
[TracingChannel Channels]: #tracingchannel-channels
16051607
[`'uncaughtException'`]: process.md#event-uncaughtexception
16061608
[`--enable-fips-indicator-events`]: cli.md#--enable-fips-indicator-events
1609+
[`--force-fips=strict`]: cli.md#--force-fipsmode
16071610
[`DatabaseSync`]: sqlite.md#class-databasesync
16081611
[`TracingChannel`]: #class-tracingchannel
16091612
[`asyncEnd` event]: #asyncendevent

‎doc/api/errors.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4586,7 +4586,7 @@ An error occurred trying to allocate memory. This should never happen.
45864586
[`"imports"`]: packages.md#imports
45874587
[`'uncaughtException'`]: process.md#event-uncaughtexception
45884588
[`--disable-proto=throw`]: cli.md#--disable-protomode
4589-
[`--force-fips`]: cli.md#--force-fips
4589+
[`--force-fips`]: cli.md#--force-fipsmode
45904590
[`--no-addons`]: cli.md#--no-addons
45914591
[`--unhandled-rejections`]: cli.md#--unhandled-rejectionsmode
45924592
[`BoundSocket`]: net.md#class-netboundsocket

‎doc/node.1‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,9 +327,23 @@ Enable experimental support for storage inspection.
327327
.It Fl -force-context-aware
328328
Disable loading native addons that are not context-aware.
329329
.
330-
.It Fl -force-fips
330+
.It Fl -force-fips Ns = Ns Ar [mode]
331331
Enable FIPS mode at startup and prevent it from being disabled from script
332332
code. The same OpenSSL requirements as \fB--enable-fips\fR apply.
333+
An optional mode can be specified using \fB--force-fips=mode\fR:
334+
.Bl -bullet
335+
.It
336+
\fBprovider\fR: Preserve the OpenSSL FIPS provider's configured handling of
337+
non-approved operations. This is the current default when the mode is
338+
omitted.
339+
.It
340+
\fBstrict\fR: Reject non-approved operations reported through the OpenSSL FIPS
341+
indicator callback. This mode requires OpenSSL 3.4 or later.
342+
.El
343+
The \fBstrict\fR mode only covers operations reported through the callback for
344+
OpenSSL's default library context. It does not cover native addons that use
345+
another \fBOSSL_LIB_CTX\fR or another copy of \fBlibcrypto\fR, nor operation-specific
346+
indicators that do not invoke the callback.
333347
.
334348
.It Fl -frozen-intrinsics
335349
Enable experimental frozen intrinsics support.

‎lib/internal/process/per_thread.js‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,9 @@ function buildAllowedFlags() {
392392

393393
const allowedNodeEnvironmentFlags = [];
394394
for (const { 0: name, 1: info } of options) {
395+
// Bracketed options are internal parser targets. They can be allowed in
396+
// NODE_OPTIONS so aliases expand to them, but are not public flags.
397+
if (name[0] === '[') continue;
395398
if (info.envVarSettings === kAllowedInEnvvar) {
396399
ArrayPrototypePush(allowedNodeEnvironmentFlags, name);
397400
if (info.type === kBoolean) {

‎src/crypto/crypto_util.cc‎

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,10 @@ class FipsIndicatorState final {
254254
}
255255

256256
void Install() {
257+
reject_unapproved_.store(
258+
per_process::cli_options->force_fips_crypto &&
259+
per_process::cli_options->force_fips_crypto_policy == "strict",
260+
std::memory_order_release);
257261
std::call_once(install_once_, [this]() {
258262
OSSL_INDICATOR_get_callback(nullptr, &previous_callback_);
259263
OSSL_INDICATOR_set_callback(nullptr, OnOpenSSLIndicator);
@@ -314,9 +318,12 @@ class FipsIndicatorState final {
314318
previous_callback_ == nullptr
315319
? 1
316320
: previous_callback_(operation, reason, params);
317-
if (!active_.load(std::memory_order_acquire)) return previous_result;
321+
const int result = reject_unapproved_.load(std::memory_order_acquire)
322+
? 0
323+
: previous_result;
324+
if (!active_.load(std::memory_order_acquire)) return result;
318325

319-
const bool blocked = previous_result == 0;
326+
const bool blocked = result == 0;
320327
{
321328
Mutex::ScopedLock lock(mutex_);
322329
if (env_ != nullptr && active_.load(std::memory_order_relaxed)) {
@@ -349,7 +356,7 @@ class FipsIndicatorState final {
349356
}
350357
}
351358
}
352-
return previous_result;
359+
return result;
353360
}
354361

355362
void Drain(Environment* env) {
@@ -404,6 +411,7 @@ class FipsIndicatorState final {
404411

405412
std::once_flag install_once_;
406413
std::atomic<bool> active_{false};
414+
std::atomic<bool> reject_unapproved_{false};
407415
OSSL_INDICATOR_CALLBACK* previous_callback_ = nullptr;
408416
Mutex mutex_;
409417
Environment* env_ = nullptr;
@@ -419,7 +427,10 @@ class FipsIndicatorState final {
419427

420428
void InstallFipsIndicatorCallback() {
421429
#if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_PREREQ(3, 4)
422-
if (per_process::cli_options->enable_fips_indicator_events) {
430+
const auto& options = per_process::cli_options;
431+
const bool strict = options->force_fips_crypto &&
432+
options->force_fips_crypto_policy == "strict";
433+
if (options->enable_fips_indicator_events || strict) {
423434
FipsIndicatorState::Get().Install();
424435
}
425436
#endif

‎src/node.cc‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1256,13 +1256,13 @@ InitializeOncePerProcessInternal(const std::vector<std::string>& args,
12561256
OPENSSL_init();
12571257
}
12581258
#endif
1259-
crypto::InstallFipsIndicatorCallback();
12601259
if (auto fips_error = crypto::ProcessFipsOptions()) {
12611260
result->exit_code_ = ExitCode::kGenericUserError;
12621261
result->early_return_ = true;
12631262
result->errors_.emplace_back(std::move(*fips_error));
12641263
return result;
12651264
}
1265+
crypto::InstallFipsIndicatorCallback();
12661266

12671267
// Ensure CSPRNG is properly seeded.
12681268
CHECK(ncrypto::CSPRNG(nullptr, 0));

‎src/node_options.cc‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,21 @@ void PerProcessOptions::CheckOptions(std::vector<std::string>* errors,
8484
"used, not both");
8585
}
8686

87+
if (force_fips_crypto_policy != "provider" &&
88+
force_fips_crypto_policy != "strict") {
89+
errors->push_back(
90+
"invalid value for --force-fips; expected 'provider' or 'strict'");
91+
}
92+
8793
#if defined(OPENSSL_IS_BORINGSSL) || !OPENSSL_VERSION_PREREQ(3, 4)
8894
if (enable_fips_indicator_events) {
8995
errors->push_back(
9096
"--enable-fips-indicator-events requires OpenSSL 3.4 or later");
9197
}
98+
99+
if (force_fips_crypto && force_fips_crypto_policy == "strict") {
100+
errors->push_back("--force-fips=strict requires OpenSSL 3.4 or later");
101+
}
92102
#endif
93103

94104
// Any value less than 2 disables use of the secure heap.
@@ -1460,9 +1470,14 @@ PerProcessOptionsParser::PerProcessOptionsParser(
14601470
&PerProcessOptions::enable_fips_indicator_events,
14611471
kAllowedInEnvvar);
14621472
AddOption("--force-fips",
1463-
"force FIPS crypto (cannot be disabled)",
1473+
"force FIPS crypto (optional mode: provider or strict)",
14641474
&PerProcessOptions::force_fips_crypto,
14651475
kAllowedInEnvvar);
1476+
AddOption("[force_fips_crypto_policy]",
1477+
"",
1478+
&PerProcessOptions::force_fips_crypto_policy,
1479+
kAllowedInEnvvar);
1480+
AddAlias("--force-fips=", {"[force_fips_crypto_policy]", "--force-fips"});
14661481
#ifndef V8_ENABLE_SANDBOX
14671482
AddOption("--secure-heap",
14681483
"total size of the OpenSSL secure heap",
@@ -2059,6 +2074,12 @@ void GetOptionsAsFlags(const FunctionCallbackInfo<Value>& args) {
20592074
switch (option_info.type) {
20602075
case kBoolean: {
20612076
bool current_value = *_ppop_instance.Lookup<bool>(field, opts);
2077+
#if HAVE_OPENSSL
2078+
if (option_name == "--force-fips" && current_value) {
2079+
flags.push_back(option_name + "=" + opts->force_fips_crypto_policy);
2080+
break;
2081+
}
2082+
#endif
20622083
// For boolean options with default_is_true, we want the opposite logic
20632084
if (option_info.default_is_true) {
20642085
if (!current_value) {

‎src/node_options.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@ class PerProcessOptions : public Options {
373373
bool enable_fips_crypto = false;
374374
bool enable_fips_indicator_events = false;
375375
bool force_fips_crypto = false;
376+
std::string force_fips_crypto_policy = "provider";
376377
#endif
377378
#if OPENSSL_VERSION_MAJOR >= 3
378379
bool openssl_legacy_provider = false;

0 commit comments

Comments
 (0)