Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

crypto: Improve control of FIPS mode #5181

Closed
wants to merge 14 commits into from
Closed
5 changes: 5 additions & 0 deletions doc/api/crypto.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions lib/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
Expand Down Expand Up @@ -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,
Expand Down
25 changes: 23 additions & 2 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -3282,7 +3288,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"
Expand Down Expand Up @@ -3424,7 +3434,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;
Expand Down Expand Up @@ -4223,6 +4239,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);
Expand Down
4 changes: 4 additions & 0 deletions src/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
44 changes: 39 additions & 5 deletions src/node_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion test/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
});

Expand Down
12 changes: 12 additions & 0 deletions test/fixtures/openssl_fips_disabled.cnf
Original file line number Diff line number Diff line change
@@ -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 changes: 12 additions & 0 deletions test/fixtures/openssl_fips_enabled.cnf
Original file line number Diff line number Diff line change
@@ -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 changes: 180 additions & 0 deletions test/parallel/test-crypto-fips.js
Original file line number Diff line number Diff line change
@@ -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);
Loading