crypto: Improve control of FIPS mode
Default to FIPS off even in FIPS builds. Add JS API to check and control FIPS mode. Add command line arguments to force FIPS on/off. Respect OPENSSL_CONF variable and read the config. Add testing for new features. Fixes: https://github.com/nodejs/node/issues/3819 PR-URL: https://github.com/nodejs/node/pull/5181 Reviewed-By: Fedor Indutny <fedor@indutny.com> Reviewed-by: Michael Dawson <michael_dawson@ca.ibm.com>
This commit is contained in:
parent
23a584d517
commit
7c48cb5601
@ -815,6 +815,11 @@ with legacy programs that expect `'binary'` to be the default encoding.
|
||||
New applications should expect the default to be `'buffer'`. This property may
|
||||
become deprecated in a future Node.js release.
|
||||
|
||||
### crypto.fips
|
||||
|
||||
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.
|
||||
|
||||
### crypto.createCipher(algorithm, password)
|
||||
|
||||
Creates and returns a `Cipher` object that uses the given `algorithm` and
|
||||
|
@ -11,6 +11,8 @@ try {
|
||||
var getCiphers = binding.getCiphers;
|
||||
var getHashes = binding.getHashes;
|
||||
var getCurves = binding.getCurves;
|
||||
var getFipsCrypto = binding.getFipsCrypto;
|
||||
var setFipsCrypto = binding.setFipsCrypto;
|
||||
} catch (e) {
|
||||
throw new Error('Node.js is not compiled with openssl crypto support');
|
||||
}
|
||||
@ -645,6 +647,10 @@ exports.getCurves = function() {
|
||||
return filterDuplicates(getCurves());
|
||||
};
|
||||
|
||||
Object.defineProperty(exports, 'fips', {
|
||||
get: getFipsCrypto,
|
||||
set: setFipsCrypto
|
||||
});
|
||||
|
||||
function filterDuplicates(names) {
|
||||
// Drop all-caps names in favor of their lowercase aliases,
|
||||
|
25
src/node.cc
25
src/node.cc
@ -161,6 +161,12 @@ static const char* icu_data_dir = nullptr;
|
||||
// used by C++ modules as well
|
||||
bool no_deprecation = false;
|
||||
|
||||
#if HAVE_OPENSSL && NODE_FIPS_MODE
|
||||
// used by crypto module
|
||||
bool enable_fips_crypto = false;
|
||||
bool force_fips_crypto = false;
|
||||
#endif
|
||||
|
||||
// process-relative uptime base, initialized at start-up
|
||||
static double prog_start_time;
|
||||
static bool debugger_running;
|
||||
@ -3283,7 +3289,11 @@ static void PrintHelp() {
|
||||
" --v8-options print v8 command line options\n"
|
||||
#if HAVE_OPENSSL
|
||||
" --tls-cipher-list=val use an alternative default TLS cipher list\n"
|
||||
#endif
|
||||
#if NODE_FIPS_MODE
|
||||
" --enable-fips enable FIPS crypto at startup\n"
|
||||
" --force-fips force FIPS crypto (cannot be disabled)\n"
|
||||
#endif /* NODE_FIPS_MODE */
|
||||
#endif /* HAVE_OPENSSL */
|
||||
#if defined(NODE_HAVE_I18N_SUPPORT)
|
||||
" --icu-data-dir=dir set ICU data load path to dir\n"
|
||||
" (overrides NODE_ICU_DATA)\n"
|
||||
@ -3425,7 +3435,13 @@ static void ParseArgs(int* argc,
|
||||
#if HAVE_OPENSSL
|
||||
} else if (strncmp(arg, "--tls-cipher-list=", 18) == 0) {
|
||||
default_cipher_list = arg + 18;
|
||||
#endif
|
||||
#if NODE_FIPS_MODE
|
||||
} else if (strcmp(arg, "--enable-fips") == 0) {
|
||||
enable_fips_crypto = true;
|
||||
} else if (strcmp(arg, "--force-fips") == 0) {
|
||||
force_fips_crypto = true;
|
||||
#endif /* NODE_FIPS_MODE */
|
||||
#endif /* HAVE_OPENSSL */
|
||||
#if defined(NODE_HAVE_I18N_SUPPORT)
|
||||
} else if (strncmp(arg, "--icu-data-dir=", 15) == 0) {
|
||||
icu_data_dir = arg + 15;
|
||||
@ -4224,6 +4240,11 @@ int Start(int argc, char** argv) {
|
||||
Init(&argc, const_cast<const char**>(argv), &exec_argc, &exec_argv);
|
||||
|
||||
#if HAVE_OPENSSL
|
||||
#ifdef NODE_FIPS_MODE
|
||||
// In the case of FIPS builds we should make sure
|
||||
// the random source is properly initialized first.
|
||||
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);
|
||||
|
@ -179,6 +179,10 @@ typedef intptr_t ssize_t;
|
||||
namespace node {
|
||||
|
||||
NODE_EXTERN extern bool no_deprecation;
|
||||
#if HAVE_OPENSSL && NODE_FIPS_MODE
|
||||
NODE_EXTERN extern bool enable_fips_crypto;
|
||||
NODE_EXTERN extern bool force_fips_crypto;
|
||||
#endif
|
||||
|
||||
NODE_EXTERN int Start(int argc, char *argv[]);
|
||||
NODE_EXTERN void Init(int* argc,
|
||||
|
@ -3126,8 +3126,10 @@ void CipherBase::Init(const char* cipher_type,
|
||||
HandleScope scope(env()->isolate());
|
||||
|
||||
#ifdef NODE_FIPS_MODE
|
||||
return env()->ThrowError(
|
||||
"crypto.createCipher() is not supported in FIPS mode.");
|
||||
if (FIPS_mode()) {
|
||||
return env()->ThrowError(
|
||||
"crypto.createCipher() is not supported in FIPS mode.");
|
||||
}
|
||||
#endif // NODE_FIPS_MODE
|
||||
|
||||
CHECK_EQ(cipher_, nullptr);
|
||||
@ -3858,7 +3860,7 @@ SignBase::Error Sign::SignFinal(const char* key_pem,
|
||||
|
||||
#ifdef NODE_FIPS_MODE
|
||||
/* Validate DSA2 parameters from FIPS 186-4 */
|
||||
if (EVP_PKEY_DSA == pkey->type) {
|
||||
if (FIPS_mode() && EVP_PKEY_DSA == pkey->type) {
|
||||
size_t L = BN_num_bits(pkey->pkey.dsa->p);
|
||||
size_t N = BN_num_bits(pkey->pkey.dsa->q);
|
||||
bool result = false;
|
||||
@ -5665,14 +5667,21 @@ void InitCryptoOnce() {
|
||||
SSL_library_init();
|
||||
OpenSSL_add_all_algorithms();
|
||||
SSL_load_error_strings();
|
||||
OPENSSL_config(NULL);
|
||||
|
||||
crypto_lock_init();
|
||||
CRYPTO_set_locking_callback(crypto_lock_cb);
|
||||
CRYPTO_THREADID_set_callback(crypto_threadid_cb);
|
||||
|
||||
#ifdef NODE_FIPS_MODE
|
||||
if (!FIPS_mode_set(1)) {
|
||||
int err = ERR_get_error();
|
||||
/* Override FIPS settings in cnf file, if needed. */
|
||||
unsigned long err = 0;
|
||||
if (enable_fips_crypto || force_fips_crypto) {
|
||||
if (0 == FIPS_mode() && !FIPS_mode_set(1)) {
|
||||
err = ERR_get_error();
|
||||
}
|
||||
}
|
||||
if (0 != err) {
|
||||
fprintf(stderr, "openssl fips failed: %s\n", ERR_error_string(err, NULL));
|
||||
UNREACHABLE();
|
||||
}
|
||||
@ -5739,6 +5748,29 @@ void SetEngine(const FunctionCallbackInfo<Value>& args) {
|
||||
}
|
||||
#endif // !OPENSSL_NO_ENGINE
|
||||
|
||||
void GetFipsCrypto(const FunctionCallbackInfo<Value>& args) {
|
||||
if (FIPS_mode()) {
|
||||
args.GetReturnValue().Set(1);
|
||||
} else {
|
||||
args.GetReturnValue().Set(0);
|
||||
}
|
||||
}
|
||||
|
||||
void SetFipsCrypto(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
#ifdef NODE_FIPS_MODE
|
||||
bool mode = args[0]->BooleanValue();
|
||||
if (force_fips_crypto) {
|
||||
return env->ThrowError(
|
||||
"Cannot set FIPS mode, it was forced with --force-fips at startup.");
|
||||
} else if (!FIPS_mode_set(mode)) {
|
||||
unsigned long err = ERR_get_error();
|
||||
return ThrowCryptoError(env, err);
|
||||
}
|
||||
#else
|
||||
return env->ThrowError("Cannot set FIPS mode in a non-FIPS build.");
|
||||
#endif /* NODE_FIPS_MODE */
|
||||
}
|
||||
|
||||
// FIXME(bnoordhuis) Handle global init correctly.
|
||||
void InitCrypto(Local<Object> target,
|
||||
@ -5763,6 +5795,8 @@ void InitCrypto(Local<Object> target,
|
||||
#ifndef OPENSSL_NO_ENGINE
|
||||
env->SetMethod(target, "setEngine", SetEngine);
|
||||
#endif // !OPENSSL_NO_ENGINE
|
||||
env->SetMethod(target, "getFipsCrypto", GetFipsCrypto);
|
||||
env->SetMethod(target, "setFipsCrypto", SetFipsCrypto);
|
||||
env->SetMethod(target, "PBKDF2", PBKDF2);
|
||||
env->SetMethod(target, "randomBytes", RandomBytes);
|
||||
env->SetMethod(target, "getSSLCiphers", GetSSLCiphers);
|
||||
|
@ -161,7 +161,7 @@ Object.defineProperty(exports, 'hasCrypto', {
|
||||
|
||||
Object.defineProperty(exports, 'hasFipsCrypto', {
|
||||
get: function() {
|
||||
return process.config.variables.openssl_fips ? true : false;
|
||||
return exports.hasCrypto && require('crypto').fips;
|
||||
}
|
||||
});
|
||||
|
||||
|
12
test/fixtures/openssl_fips_disabled.cnf
vendored
Normal file
12
test/fixtures/openssl_fips_disabled.cnf
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
# Skeleton openssl.cnf for testing with FIPS
|
||||
|
||||
openssl_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
|
12
test/fixtures/openssl_fips_enabled.cnf
vendored
Normal file
12
test/fixtures/openssl_fips_enabled.cnf
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
# Skeleton openssl.cnf for testing with FIPS
|
||||
|
||||
openssl_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
|
180
test/parallel/test-crypto-fips.js
Normal file
180
test/parallel/test-crypto-fips.js
Normal file
@ -0,0 +1,180 @@
|
||||
'use strict';
|
||||
var common = require('../common');
|
||||
var assert = require('assert');
|
||||
var spawnSync = require('child_process').spawnSync;
|
||||
var path = require('path');
|
||||
|
||||
if (!common.hasCrypto) {
|
||||
console.log('1..0 # Skipped: missing crypto');
|
||||
return;
|
||||
}
|
||||
|
||||
const FIPS_ENABLED = 1;
|
||||
const FIPS_DISABLED = 0;
|
||||
const FIPS_ERROR_STRING = 'Error: Cannot set FIPS mode';
|
||||
const OPTION_ERROR_STRING = 'bad option';
|
||||
const CNF_FIPS_ON = path.join(common.fixturesDir, 'openssl_fips_enabled.cnf');
|
||||
const CNF_FIPS_OFF = path.join(common.fixturesDir, 'openssl_fips_disabled.cnf');
|
||||
var num_children_ok = 0;
|
||||
|
||||
function compiledWithFips() {
|
||||
return process.config.variables.openssl_fips ? true : false;
|
||||
}
|
||||
|
||||
function addToEnv(newVar, value) {
|
||||
var envCopy = {};
|
||||
for (const e in process.env) {
|
||||
envCopy[e] = process.env[e];
|
||||
}
|
||||
envCopy[newVar] = value;
|
||||
return envCopy;
|
||||
}
|
||||
|
||||
function testHelper(stream, args, expectedOutput, cmd, env) {
|
||||
const fullArgs = args.concat(['-e', 'console.log(' + cmd + ')']);
|
||||
const child = spawnSync(process.execPath, fullArgs, {
|
||||
cwd: path.dirname(process.execPath),
|
||||
env: env
|
||||
});
|
||||
|
||||
console.error('Spawned child [pid:' + child.pid + '] with cmd ' +
|
||||
cmd + ' and args \'' + args + '\'');
|
||||
|
||||
function childOk(child) {
|
||||
console.error('Child #' + ++num_children_ok +
|
||||
' [pid:' + child.pid + '] OK.');
|
||||
}
|
||||
|
||||
function responseHandler(buffer, expectedOutput) {
|
||||
const response = buffer.toString();
|
||||
assert.notEqual(0, response.length);
|
||||
if (FIPS_ENABLED !== expectedOutput && FIPS_DISABLED !== expectedOutput) {
|
||||
// In the case of expected errors just look for a substring.
|
||||
assert.notEqual(-1, response.indexOf(expectedOutput));
|
||||
} else {
|
||||
// Normal path where we expect either FIPS enabled or disabled.
|
||||
assert.equal(expectedOutput, response);
|
||||
}
|
||||
childOk(child);
|
||||
}
|
||||
|
||||
responseHandler(child[stream], expectedOutput);
|
||||
}
|
||||
|
||||
// By default FIPS should be off in both FIPS and non-FIPS builds.
|
||||
testHelper(
|
||||
'stdout',
|
||||
[],
|
||||
FIPS_DISABLED,
|
||||
'require("crypto").fips',
|
||||
addToEnv('OPENSSL_CONF', ''));
|
||||
|
||||
// --enable-fips should turn FIPS mode on
|
||||
testHelper(
|
||||
compiledWithFips() ? 'stdout' : 'stderr',
|
||||
['--enable-fips'],
|
||||
compiledWithFips() ? FIPS_ENABLED : OPTION_ERROR_STRING,
|
||||
'require("crypto").fips',
|
||||
process.env);
|
||||
|
||||
//--force-fips should turn FIPS mode on
|
||||
testHelper(
|
||||
compiledWithFips() ? 'stdout' : 'stderr',
|
||||
['--force-fips'],
|
||||
compiledWithFips() ? FIPS_ENABLED : OPTION_ERROR_STRING,
|
||||
'require("crypto").fips',
|
||||
process.env);
|
||||
|
||||
// OpenSSL config file should be able to turn on FIPS mode
|
||||
testHelper(
|
||||
'stdout',
|
||||
[],
|
||||
compiledWithFips() ? FIPS_ENABLED : FIPS_DISABLED,
|
||||
'require("crypto").fips',
|
||||
addToEnv('OPENSSL_CONF', CNF_FIPS_ON));
|
||||
|
||||
// --enable-fips should take precedence over OpenSSL config file
|
||||
testHelper(
|
||||
compiledWithFips() ? 'stdout' : 'stderr',
|
||||
['--enable-fips'],
|
||||
compiledWithFips() ? FIPS_ENABLED : OPTION_ERROR_STRING,
|
||||
'require("crypto").fips',
|
||||
addToEnv('OPENSSL_CONF', CNF_FIPS_OFF));
|
||||
|
||||
// --force-fips should take precedence over OpenSSL config file
|
||||
testHelper(
|
||||
compiledWithFips() ? 'stdout' : 'stderr',
|
||||
['--force-fips'],
|
||||
compiledWithFips() ? FIPS_ENABLED : OPTION_ERROR_STRING,
|
||||
'require("crypto").fips',
|
||||
addToEnv('OPENSSL_CONF', CNF_FIPS_OFF));
|
||||
|
||||
// setFipsCrypto should be able to turn FIPS mode on
|
||||
testHelper(
|
||||
compiledWithFips() ? 'stdout' : 'stderr',
|
||||
[],
|
||||
compiledWithFips() ? FIPS_ENABLED : FIPS_ERROR_STRING,
|
||||
'(require("crypto").fips = true,' +
|
||||
'require("crypto").fips)',
|
||||
addToEnv('OPENSSL_CONF', ''));
|
||||
|
||||
// setFipsCrypto should be able to turn FIPS mode on and off
|
||||
testHelper(
|
||||
compiledWithFips() ? 'stdout' : 'stderr',
|
||||
[],
|
||||
compiledWithFips() ? FIPS_DISABLED : FIPS_ERROR_STRING,
|
||||
'(require("crypto").fips = true,' +
|
||||
'require("crypto").fips = false,' +
|
||||
'require("crypto").fips)',
|
||||
addToEnv('OPENSSL_CONF', ''));
|
||||
|
||||
// setFipsCrypto takes precedence over OpenSSL config file, FIPS on
|
||||
testHelper(
|
||||
compiledWithFips() ? 'stdout' : 'stderr',
|
||||
[],
|
||||
compiledWithFips() ? FIPS_ENABLED : FIPS_ERROR_STRING,
|
||||
'(require("crypto").fips = true,' +
|
||||
'require("crypto").fips)',
|
||||
addToEnv('OPENSSL_CONF', CNF_FIPS_OFF));
|
||||
|
||||
// setFipsCrypto takes precedence over OpenSSL config file, FIPS off
|
||||
testHelper(
|
||||
compiledWithFips() ? 'stdout' : 'stderr',
|
||||
[],
|
||||
compiledWithFips() ? FIPS_DISABLED : FIPS_ERROR_STRING,
|
||||
'(require("crypto").fips = false,' +
|
||||
'require("crypto").fips)',
|
||||
addToEnv('OPENSSL_CONF', CNF_FIPS_ON));
|
||||
|
||||
// --enable-fips does not prevent use of setFipsCrypto API
|
||||
testHelper(
|
||||
compiledWithFips() ? 'stdout' : 'stderr',
|
||||
['--enable-fips'],
|
||||
compiledWithFips() ? FIPS_DISABLED : OPTION_ERROR_STRING,
|
||||
'(require("crypto").fips = false,' +
|
||||
'require("crypto").fips)',
|
||||
process.env);
|
||||
|
||||
// --force-fips prevents use of setFipsCrypto API
|
||||
testHelper(
|
||||
'stderr',
|
||||
['--force-fips'],
|
||||
compiledWithFips() ? FIPS_ERROR_STRING : OPTION_ERROR_STRING,
|
||||
'require("crypto").fips = false',
|
||||
process.env);
|
||||
|
||||
// --force-fips and --enable-fips order does not matter
|
||||
testHelper(
|
||||
'stderr',
|
||||
['--force-fips', '--enable-fips'],
|
||||
compiledWithFips() ? FIPS_ERROR_STRING : OPTION_ERROR_STRING,
|
||||
'require("crypto").fips = false',
|
||||
process.env);
|
||||
|
||||
//--enable-fips and --force-fips order does not matter
|
||||
testHelper(
|
||||
'stderr',
|
||||
['--enable-fips', '--force-fips'],
|
||||
compiledWithFips() ? FIPS_ERROR_STRING : OPTION_ERROR_STRING,
|
||||
'require("crypto").fips = false',
|
||||
process.env);
|
@ -2,9 +2,7 @@
|
||||
var common = require('../common');
|
||||
var assert = require('assert');
|
||||
|
||||
try {
|
||||
var crypto = require('crypto');
|
||||
} catch (e) {
|
||||
if (!common.hasCrypto) {
|
||||
console.log('1..0 # Skipped: node compiled without OpenSSL.');
|
||||
return;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user