crypto: make FIPS related options always awailable

There is no reason to hide FIPS functionality behind build flags.
OpenSSL always provide the information about FIPS availability via
`FIPS_mode()` function.

This makes the user experience more consistent, because the OpenSSL
library is always queried and the `crypto.getFips()` always returns
OpenSSL settings.

Fixes #34903

PR-URL: https://github.com/nodejs/node/pull/36341
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Michael Dawson <midawson@redhat.com>
Reviewed-By: Daniel Bevenius <daniel.bevenius@gmail.com>
This commit is contained in:
Vít Ondruch 2020-08-25 14:04:54 +02:00 committed by Michael Dawson
parent 148bc33347
commit f392ac0bbe
15 changed files with 77 additions and 108 deletions

View file

@ -186,8 +186,8 @@ code from strings throw an exception instead. This does not affect the Node.js
added: v6.0.0
-->
Enable FIPS-compliant crypto at startup. (Requires Node.js to be built with
`./configure --openssl-fips`.)
Enable FIPS-compliant crypto at startup. (Requires Node.js to be built
against FIPS-compatible OpenSSL.)
### `--enable-source-maps`
<!-- YAML
@ -613,8 +613,8 @@ added: v6.9.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.
### `--pending-deprecation`
<!-- YAML

View file

@ -37,12 +37,10 @@ assertCrypto();
const {
ERR_CRYPTO_FIPS_FORCED,
ERR_CRYPTO_FIPS_UNAVAILABLE
} = require('internal/errors').codes;
const constants = internalBinding('constants').crypto;
const { getOptionValue } = require('internal/options');
const pendingDeprecation = getOptionValue('--pending-deprecation');
const { fipsMode } = internalBinding('config');
const fipsForced = getOptionValue('--force-fips');
const {
getFipsCrypto,
@ -218,10 +216,8 @@ module.exports = {
sign: signOneShot,
setEngine,
timingSafeEqual,
getFips: !fipsMode ? getFipsDisabled :
fipsForced ? getFipsForced : getFipsCrypto,
setFips: !fipsMode ? setFipsDisabled :
fipsForced ? setFipsForced : setFipsCrypto,
getFips: fipsForced ? getFipsForced : getFipsCrypto,
setFips: fipsForced ? setFipsForced : setFipsCrypto,
verify: verifyOneShot,
// Classes
@ -242,19 +238,11 @@ module.exports = {
secureHeapUsed,
};
function setFipsDisabled() {
throw new ERR_CRYPTO_FIPS_UNAVAILABLE();
}
function setFipsForced(val) {
if (val) return;
throw new ERR_CRYPTO_FIPS_FORCED();
}
function getFipsDisabled() {
return 0;
}
function getFipsForced() {
return 1;
}
@ -276,10 +264,8 @@ ObjectDefineProperties(module.exports, {
},
// crypto.fips is deprecated. DEP0093. Use crypto.getFips()/crypto.setFips()
fips: {
get: !fipsMode ? getFipsDisabled :
fipsForced ? getFipsForced : getFipsCrypto,
set: !fipsMode ? setFipsDisabled :
fipsForced ? setFipsForced : setFipsCrypto
get: fipsForced ? getFipsForced : getFipsCrypto,
set: fipsForced ? setFipsForced : setFipsCrypto
},
DEFAULT_ENCODING: {
enumerable: false,

View file

@ -319,9 +319,6 @@
[ 'node_use_openssl=="true"', {
'defines': [ 'HAVE_OPENSSL=1' ],
'conditions': [
['openssl_fips != "" or openssl_is_fips=="true"', {
'defines': [ 'NODE_FIPS_MODE' ],
}],
[ 'node_shared_openssl=="false"', {
'dependencies': [
'./deps/openssl/openssl.gyp:openssl',

View file

@ -343,12 +343,10 @@ void CipherBase::Init(const char* cipher_type,
HandleScope scope(env()->isolate());
MarkPopErrorOnReturn mark_pop_error_on_return;
#ifdef NODE_FIPS_MODE
if (FIPS_mode()) {
return THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env(),
"crypto.createCipher() is not supported in FIPS mode.");
}
#endif // NODE_FIPS_MODE
const EVP_CIPHER* const cipher = EVP_get_cipherbyname(cipher_type);
if (cipher == nullptr)
@ -528,14 +526,12 @@ bool CipherBase::InitAuthenticated(
return false;
}
#ifdef NODE_FIPS_MODE
// TODO(tniessen) Support CCM decryption in FIPS mode
if (mode == EVP_CIPH_CCM_MODE && kind_ == kDecipher && FIPS_mode()) {
THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env(),
"CCM encryption not supported in FIPS mode");
return false;
}
#endif
// Tell OpenSSL about the desired length.
if (!EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_AEAD_SET_TAG, auth_tag_len,

View file

@ -27,7 +27,6 @@ using v8::Value;
namespace crypto {
namespace {
bool ValidateDSAParameters(EVP_PKEY* key) {
#ifdef NODE_FIPS_MODE
/* Validate DSA2 parameters from FIPS 186-4 */
if (FIPS_mode() && EVP_PKEY_DSA == EVP_PKEY_base_id(key)) {
DSA* dsa = EVP_PKEY_get0_DSA(key);
@ -43,7 +42,6 @@ bool ValidateDSAParameters(EVP_PKEY* key) {
(L == 2048 && N == 256) ||
(L == 3072 && N == 256);
}
#endif // NODE_FIPS_MODE
return true;
}

View file

@ -133,7 +133,6 @@ void InitCryptoOnce() {
}
#endif
#ifdef NODE_FIPS_MODE
/* Override FIPS settings in cnf file, if needed. */
unsigned long err = 0; // NOLINT(runtime/int)
if (per_process::cli_options->enable_fips_crypto ||
@ -143,12 +142,10 @@ void InitCryptoOnce() {
}
}
if (0 != err) {
fprintf(stderr,
"openssl fips failed: %s\n",
ERR_error_string(err, nullptr));
UNREACHABLE();
auto* isolate = Isolate::GetCurrent();
auto* env = Environment::GetCurrent(isolate);
return ThrowCryptoError(env, err);
}
#endif // NODE_FIPS_MODE
// Turn off compression. Saves memory and protects against CRIME attacks.
// No-op with OPENSSL_NO_COMP builds of OpenSSL.
@ -162,7 +159,6 @@ void InitCryptoOnce() {
NodeBIO::GetMethod();
}
#ifdef NODE_FIPS_MODE
void GetFipsCrypto(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(FIPS_mode() ? 1 : 0);
}
@ -180,7 +176,16 @@ void SetFipsCrypto(const FunctionCallbackInfo<Value>& args) {
return ThrowCryptoError(env, err);
}
}
#endif /* NODE_FIPS_MODE */
void TestFipsCrypto(const v8::FunctionCallbackInfo<v8::Value>& args) {
#ifdef OPENSSL_FIPS
const auto enabled = FIPS_selftest() ? 1 : 0;
#else // OPENSSL_FIPS
const auto enabled = 0;
#endif // OPENSSL_FIPS
args.GetReturnValue().Set(enabled);
}
void CryptoErrorVector::Capture() {
clear();
@ -652,10 +657,9 @@ void Initialize(Environment* env, Local<Object> target) {
env->SetMethod(target, "setEngine", SetEngine);
#endif // !OPENSSL_NO_ENGINE
#ifdef NODE_FIPS_MODE
env->SetMethodNoSideEffect(target, "getFipsCrypto", GetFipsCrypto);
env->SetMethod(target, "setFipsCrypto", SetFipsCrypto);
#endif
env->SetMethodNoSideEffect(target, "testFipsCrypto", TestFipsCrypto);
NODE_DEFINE_CONSTANT(target, kCryptoJobAsync);
NODE_DEFINE_CONSTANT(target, kCryptoJobSync);

View file

@ -22,6 +22,11 @@
#ifndef OPENSSL_NO_ENGINE
# include <openssl/engine.h>
#endif // !OPENSSL_NO_ENGINE
// The FIPS-related functions are only available
// when the OpenSSL itself was compiled with FIPS support.
#ifdef OPENSSL_FIPS
# include <openssl/fips.h>
#endif // OPENSSL_FIPS
#include <algorithm>
#include <memory>
@ -510,11 +515,11 @@ bool SetEngine(
void SetEngine(const v8::FunctionCallbackInfo<v8::Value>& args);
#endif // !OPENSSL_NO_ENGINE
#ifdef NODE_FIPS_MODE
void GetFipsCrypto(const v8::FunctionCallbackInfo<v8::Value>& args);
void SetFipsCrypto(const v8::FunctionCallbackInfo<v8::Value>& args);
#endif /* NODE_FIPS_MODE */
void TestFipsCrypto(const v8::FunctionCallbackInfo<v8::Value>& args);
class CipherPushContext {
public:

View file

@ -1013,11 +1013,11 @@ InitializationResult InitializeOncePerProcess(int argc, char** argv) {
if (credentials::SafeGetenv("NODE_EXTRA_CA_CERTS", &extra_ca_certs))
crypto::UseExtraCaCerts(extra_ca_certs);
}
#ifdef NODE_FIPS_MODE
// In the case of FIPS builds we should make sure
// the random source is properly initialized first.
if (FIPS_mode()) {
OPENSSL_init();
#endif // NODE_FIPS_MODE
}
// V8 on Windows doesn't have a good source of entropy. Seed it from
// OpenSSL's pool.
V8::SetEntropySource(crypto::EntropySource);

View file

@ -42,9 +42,7 @@ static void Initialize(Local<Object> target,
READONLY_FALSE_PROPERTY(target, "hasOpenSSL");
#endif // HAVE_OPENSSL
#ifdef NODE_FIPS_MODE
READONLY_TRUE_PROPERTY(target, "fipsMode");
#endif
#ifdef NODE_HAVE_I18N_SUPPORT

View file

@ -31,6 +31,7 @@ namespace node {
using v8::Context;
using v8::Local;
using v8::Object;
using v8::TryCatch;
using v8::Value;
namespace crypto {
@ -39,11 +40,16 @@ void Initialize(Local<Object> target,
Local<Value> unused,
Local<Context> context,
void* priv) {
static uv_once_t init_once = UV_ONCE_INIT;
uv_once(&init_once, InitCryptoOnce);
Environment* env = Environment::GetCurrent(context);
static uv_once_t init_once = UV_ONCE_INIT;
TryCatch try_catch{env->isolate()};
uv_once(&init_once, InitCryptoOnce);
if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
try_catch.ReThrow();
return;
}
AES::Initialize(env, target);
CipherBase::Initialize(env, target);
DiffieHellman::Initialize(env, target);

View file

@ -766,7 +766,6 @@ PerProcessOptionsParser::PerProcessOptionsParser(
&PerProcessOptions::ssl_openssl_cert_store);
Implies("--use-openssl-ca", "[ssl_openssl_cert_store]");
ImpliesNot("--use-bundled-ca", "[ssl_openssl_cert_store]");
#if NODE_FIPS_MODE
AddOption("--enable-fips",
"enable FIPS crypto at startup",
&PerProcessOptions::enable_fips_crypto,
@ -775,7 +774,6 @@ PerProcessOptionsParser::PerProcessOptionsParser(
"force FIPS crypto (cannot be disabled)",
&PerProcessOptions::force_fips_crypto,
kAllowedInEnvironment);
#endif
AddOption("--secure-heap",
"total size of the OpenSSL secure heap",
&PerProcessOptions::secure_heap,

View file

@ -245,10 +245,8 @@ class PerProcessOptions : public Options {
#endif
bool use_openssl_ca = false;
bool use_bundled_ca = false;
#if NODE_FIPS_MODE
bool enable_fips_crypto = false;
bool force_fips_crypto = false;
#endif
#endif
// Per-process because reports can be triggered outside a known V8 context.

View file

@ -8,8 +8,6 @@ const common = require('../common');
const assert = require('assert');
const { exec } = require('child_process');
const { internalBinding } = require('internal/test/binding');
const { fipsMode } = internalBinding('config');
let stdOut;
@ -28,9 +26,8 @@ function validateNodePrintHelp() {
const cliHelpOptions = [
{ compileConstant: HAVE_OPENSSL,
flags: [ '--openssl-config=...', '--tls-cipher-list=...',
'--use-bundled-ca', '--use-openssl-ca' ] },
{ compileConstant: fipsMode,
flags: [ '--enable-fips', '--force-fips' ] },
'--use-bundled-ca', '--use-openssl-ca',
'--enable-fips', '--force-fips' ] },
{ compileConstant: NODE_HAVE_I18N_SUPPORT,
flags: [ '--icu-data-dir=...', 'NODE_ICU_DATA' ] },
{ compileConstant: HAVE_INSPECTOR,

View file

@ -9,27 +9,20 @@ const spawnSync = require('child_process').spawnSync;
const path = require('path');
const fixtures = require('../common/fixtures');
const { internalBinding } = require('internal/test/binding');
const { fipsMode } = internalBinding('config');
const { testFipsCrypto } = internalBinding('crypto');
const FIPS_ENABLED = 1;
const FIPS_DISABLED = 0;
const FIPS_ERROR_STRING =
'Error [ERR_CRYPTO_FIPS_UNAVAILABLE]: Cannot set FIPS mode in a ' +
'non-FIPS build.';
const FIPS_ERROR_STRING2 =
'Error [ERR_CRYPTO_FIPS_FORCED]: Cannot set FIPS mode, it was forced with ' +
'--force-fips at startup.';
const OPTION_ERROR_STRING = 'bad option';
const FIPS_UNSUPPORTED_ERROR_STRING = 'fips mode not supported';
const CNF_FIPS_ON = fixtures.path('openssl_fips_enabled.cnf');
const CNF_FIPS_OFF = fixtures.path('openssl_fips_disabled.cnf');
let num_children_ok = 0;
function compiledWithFips() {
return fipsMode ? true : false;
}
function sharedOpenSSL() {
return process.config.variables.node_shared_openssl;
}
@ -75,17 +68,17 @@ testHelper(
// --enable-fips should turn FIPS mode on
testHelper(
compiledWithFips() ? 'stdout' : 'stderr',
testFipsCrypto() ? 'stdout' : 'stderr',
['--enable-fips'],
compiledWithFips() ? FIPS_ENABLED : OPTION_ERROR_STRING,
testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING,
'require("crypto").getFips()',
process.env);
// --force-fips should turn FIPS mode on
testHelper(
compiledWithFips() ? 'stdout' : 'stderr',
testFipsCrypto() ? 'stdout' : 'stderr',
['--force-fips'],
compiledWithFips() ? FIPS_ENABLED : OPTION_ERROR_STRING,
testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING,
'require("crypto").getFips()',
process.env);
@ -106,7 +99,7 @@ if (!sharedOpenSSL()) {
testHelper(
'stdout',
[`--openssl-config=${CNF_FIPS_ON}`],
compiledWithFips() ? FIPS_ENABLED : FIPS_DISABLED,
testFipsCrypto() ? FIPS_ENABLED : FIPS_DISABLED,
'require("crypto").getFips()',
process.env);
@ -114,7 +107,7 @@ if (!sharedOpenSSL()) {
testHelper(
'stdout',
[],
compiledWithFips() ? FIPS_ENABLED : FIPS_DISABLED,
testFipsCrypto() ? FIPS_ENABLED : FIPS_DISABLED,
'require("crypto").getFips()',
Object.assign({}, process.env, { 'OPENSSL_CONF': CNF_FIPS_ON }));
@ -122,7 +115,7 @@ if (!sharedOpenSSL()) {
testHelper(
'stdout',
[`--openssl-config=${CNF_FIPS_ON}`],
compiledWithFips() ? FIPS_ENABLED : FIPS_DISABLED,
testFipsCrypto() ? FIPS_ENABLED : FIPS_DISABLED,
'require("crypto").getFips()',
Object.assign({}, process.env, { 'OPENSSL_CONF': CNF_FIPS_OFF }));
}
@ -136,50 +129,50 @@ testHelper(
// --enable-fips should take precedence over OpenSSL config file
testHelper(
compiledWithFips() ? 'stdout' : 'stderr',
testFipsCrypto() ? 'stdout' : 'stderr',
['--enable-fips', `--openssl-config=${CNF_FIPS_OFF}`],
compiledWithFips() ? FIPS_ENABLED : OPTION_ERROR_STRING,
testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING,
'require("crypto").getFips()',
process.env);
// OPENSSL_CONF should _not_ make a difference to --enable-fips
testHelper(
compiledWithFips() ? 'stdout' : 'stderr',
testFipsCrypto() ? 'stdout' : 'stderr',
['--enable-fips'],
compiledWithFips() ? FIPS_ENABLED : OPTION_ERROR_STRING,
testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING,
'require("crypto").getFips()',
Object.assign({}, process.env, { 'OPENSSL_CONF': CNF_FIPS_OFF }));
// --force-fips should take precedence over OpenSSL config file
testHelper(
compiledWithFips() ? 'stdout' : 'stderr',
testFipsCrypto() ? 'stdout' : 'stderr',
['--force-fips', `--openssl-config=${CNF_FIPS_OFF}`],
compiledWithFips() ? FIPS_ENABLED : OPTION_ERROR_STRING,
testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING,
'require("crypto").getFips()',
process.env);
// Using OPENSSL_CONF should not make a difference to --force-fips
testHelper(
compiledWithFips() ? 'stdout' : 'stderr',
testFipsCrypto() ? 'stdout' : 'stderr',
['--force-fips'],
compiledWithFips() ? FIPS_ENABLED : OPTION_ERROR_STRING,
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(
compiledWithFips() ? 'stdout' : 'stderr',
testFipsCrypto() ? 'stdout' : 'stderr',
[],
compiledWithFips() ? FIPS_ENABLED : FIPS_ERROR_STRING,
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(
compiledWithFips() ? 'stdout' : 'stderr',
testFipsCrypto() ? 'stdout' : 'stderr',
[],
compiledWithFips() ? FIPS_DISABLED : FIPS_ERROR_STRING,
testFipsCrypto() ? FIPS_DISABLED : FIPS_UNSUPPORTED_ERROR_STRING,
'(require("crypto").setFips(true),' +
'require("crypto").setFips(false),' +
'require("crypto").getFips())',
@ -187,27 +180,27 @@ testHelper(
// setFipsCrypto takes precedence over OpenSSL config file, FIPS on
testHelper(
compiledWithFips() ? 'stdout' : 'stderr',
testFipsCrypto() ? 'stdout' : 'stderr',
[`--openssl-config=${CNF_FIPS_OFF}`],
compiledWithFips() ? FIPS_ENABLED : FIPS_ERROR_STRING,
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(
compiledWithFips() ? 'stdout' : 'stderr',
'stdout',
[`--openssl-config=${CNF_FIPS_ON}`],
compiledWithFips() ? FIPS_DISABLED : FIPS_ERROR_STRING,
FIPS_DISABLED,
'(require("crypto").setFips(false),' +
'require("crypto").getFips())',
process.env);
// --enable-fips does not prevent use of setFipsCrypto API
testHelper(
compiledWithFips() ? 'stdout' : 'stderr',
testFipsCrypto() ? 'stdout' : 'stderr',
['--enable-fips'],
compiledWithFips() ? FIPS_DISABLED : OPTION_ERROR_STRING,
testFipsCrypto() ? FIPS_DISABLED : FIPS_UNSUPPORTED_ERROR_STRING,
'(require("crypto").setFips(false),' +
'require("crypto").getFips())',
process.env);
@ -216,15 +209,15 @@ testHelper(
testHelper(
'stderr',
['--force-fips'],
compiledWithFips() ? FIPS_ERROR_STRING2 : OPTION_ERROR_STRING,
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(
compiledWithFips() ? 'stdout' : 'stderr',
testFipsCrypto() ? 'stdout' : 'stderr',
['--force-fips'],
compiledWithFips() ? FIPS_ENABLED : OPTION_ERROR_STRING,
testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING,
'(require("crypto").setFips(true),' +
'require("crypto").getFips())',
process.env);
@ -233,7 +226,7 @@ testHelper(
testHelper(
'stderr',
['--force-fips', '--enable-fips'],
compiledWithFips() ? FIPS_ERROR_STRING2 : OPTION_ERROR_STRING,
testFipsCrypto() ? FIPS_ERROR_STRING2 : FIPS_UNSUPPORTED_ERROR_STRING,
'require("crypto").setFips(false)',
process.env);
@ -241,6 +234,6 @@ testHelper(
testHelper(
'stderr',
['--enable-fips', '--force-fips'],
compiledWithFips() ? FIPS_ERROR_STRING2 : OPTION_ERROR_STRING,
testFipsCrypto() ? FIPS_ERROR_STRING2 : FIPS_UNSUPPORTED_ERROR_STRING,
'require("crypto").setFips(false)',
process.env);

View file

@ -51,17 +51,10 @@ const conditionalOpts = [
'--use-openssl-ca',
'--secure-heap',
'--secure-heap-min',
'--enable-fips',
'--force-fips',
].includes(opt);
}
}, {
// We are using openssl_is_fips from the configuration because it could be
// the case that OpenSSL is FIPS compatible but fips has not been enabled
// (starting node with --enable-fips). If we use common.hasFipsCrypto
// that would only tells us if fips has been enabled, but in this case we
// want to check options which will be available regardless of whether fips
// is enabled at runtime or not.
include: process.config.variables.openssl_is_fips,
filter: (opt) => opt.includes('-fips')
}, {
include: common.hasIntl,
filter: (opt) => opt === '--icu-data-dir'