Skip to content

Commit 7c48cb5

Browse files
stefanmbmhdawson
authored andcommitted
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: #3819 PR-URL: #5181 Reviewed-By: Fedor Indutny <[email protected]> Reviewed-by: Michael Dawson <[email protected]>
1 parent 23a584d commit 7c48cb5

File tree

10 files changed

+283
-11
lines changed

10 files changed

+283
-11
lines changed

doc/api/crypto.markdown

+5
Original file line numberDiff line numberDiff line change
@@ -815,6 +815,11 @@ with legacy programs that expect `'binary'` to be the default encoding.
815815
New applications should expect the default to be `'buffer'`. This property may
816816
become deprecated in a future Node.js release.
817817

818+
### crypto.fips
819+
820+
Property for checking and controlling whether a FIPS compliant crypto provider is
821+
currently in use. Setting to true requires a FIPS build of Node.js.
822+
818823
### crypto.createCipher(algorithm, password)
819824

820825
Creates and returns a `Cipher` object that uses the given `algorithm` and

lib/crypto.js

+6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ try {
1111
var getCiphers = binding.getCiphers;
1212
var getHashes = binding.getHashes;
1313
var getCurves = binding.getCurves;
14+
var getFipsCrypto = binding.getFipsCrypto;
15+
var setFipsCrypto = binding.setFipsCrypto;
1416
} catch (e) {
1517
throw new Error('Node.js is not compiled with openssl crypto support');
1618
}
@@ -645,6 +647,10 @@ exports.getCurves = function() {
645647
return filterDuplicates(getCurves());
646648
};
647649

650+
Object.defineProperty(exports, 'fips', {
651+
get: getFipsCrypto,
652+
set: setFipsCrypto
653+
});
648654

649655
function filterDuplicates(names) {
650656
// Drop all-caps names in favor of their lowercase aliases,

src/node.cc

+23-2
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,12 @@ static const char* icu_data_dir = nullptr;
161161
// used by C++ modules as well
162162
bool no_deprecation = false;
163163

164+
#if HAVE_OPENSSL && NODE_FIPS_MODE
165+
// used by crypto module
166+
bool enable_fips_crypto = false;
167+
bool force_fips_crypto = false;
168+
#endif
169+
164170
// process-relative uptime base, initialized at start-up
165171
static double prog_start_time;
166172
static bool debugger_running;
@@ -3283,7 +3289,11 @@ static void PrintHelp() {
32833289
" --v8-options print v8 command line options\n"
32843290
#if HAVE_OPENSSL
32853291
" --tls-cipher-list=val use an alternative default TLS cipher list\n"
3286-
#endif
3292+
#if NODE_FIPS_MODE
3293+
" --enable-fips enable FIPS crypto at startup\n"
3294+
" --force-fips force FIPS crypto (cannot be disabled)\n"
3295+
#endif /* NODE_FIPS_MODE */
3296+
#endif /* HAVE_OPENSSL */
32873297
#if defined(NODE_HAVE_I18N_SUPPORT)
32883298
" --icu-data-dir=dir set ICU data load path to dir\n"
32893299
" (overrides NODE_ICU_DATA)\n"
@@ -3425,7 +3435,13 @@ static void ParseArgs(int* argc,
34253435
#if HAVE_OPENSSL
34263436
} else if (strncmp(arg, "--tls-cipher-list=", 18) == 0) {
34273437
default_cipher_list = arg + 18;
3428-
#endif
3438+
#if NODE_FIPS_MODE
3439+
} else if (strcmp(arg, "--enable-fips") == 0) {
3440+
enable_fips_crypto = true;
3441+
} else if (strcmp(arg, "--force-fips") == 0) {
3442+
force_fips_crypto = true;
3443+
#endif /* NODE_FIPS_MODE */
3444+
#endif /* HAVE_OPENSSL */
34293445
#if defined(NODE_HAVE_I18N_SUPPORT)
34303446
} else if (strncmp(arg, "--icu-data-dir=", 15) == 0) {
34313447
icu_data_dir = arg + 15;
@@ -4224,6 +4240,11 @@ int Start(int argc, char** argv) {
42244240
Init(&argc, const_cast<const char**>(argv), &exec_argc, &exec_argv);
42254241

42264242
#if HAVE_OPENSSL
4243+
#ifdef NODE_FIPS_MODE
4244+
// In the case of FIPS builds we should make sure
4245+
// the random source is properly initialized first.
4246+
OPENSSL_init();
4247+
#endif // NODE_FIPS_MODE
42274248
// V8 on Windows doesn't have a good source of entropy. Seed it from
42284249
// OpenSSL's pool.
42294250
V8::SetEntropySource(crypto::EntropySource);

src/node.h

+4
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,10 @@ typedef intptr_t ssize_t;
179179
namespace node {
180180

181181
NODE_EXTERN extern bool no_deprecation;
182+
#if HAVE_OPENSSL && NODE_FIPS_MODE
183+
NODE_EXTERN extern bool enable_fips_crypto;
184+
NODE_EXTERN extern bool force_fips_crypto;
185+
#endif
182186

183187
NODE_EXTERN int Start(int argc, char *argv[]);
184188
NODE_EXTERN void Init(int* argc,

src/node_crypto.cc

+39-5
Original file line numberDiff line numberDiff line change
@@ -3126,8 +3126,10 @@ void CipherBase::Init(const char* cipher_type,
31263126
HandleScope scope(env()->isolate());
31273127

31283128
#ifdef NODE_FIPS_MODE
3129-
return env()->ThrowError(
3130-
"crypto.createCipher() is not supported in FIPS mode.");
3129+
if (FIPS_mode()) {
3130+
return env()->ThrowError(
3131+
"crypto.createCipher() is not supported in FIPS mode.");
3132+
}
31313133
#endif // NODE_FIPS_MODE
31323134

31333135
CHECK_EQ(cipher_, nullptr);
@@ -3858,7 +3860,7 @@ SignBase::Error Sign::SignFinal(const char* key_pem,
38583860

38593861
#ifdef NODE_FIPS_MODE
38603862
/* Validate DSA2 parameters from FIPS 186-4 */
3861-
if (EVP_PKEY_DSA == pkey->type) {
3863+
if (FIPS_mode() && EVP_PKEY_DSA == pkey->type) {
38623864
size_t L = BN_num_bits(pkey->pkey.dsa->p);
38633865
size_t N = BN_num_bits(pkey->pkey.dsa->q);
38643866
bool result = false;
@@ -5665,14 +5667,21 @@ void InitCryptoOnce() {
56655667
SSL_library_init();
56665668
OpenSSL_add_all_algorithms();
56675669
SSL_load_error_strings();
5670+
OPENSSL_config(NULL);
56685671

56695672
crypto_lock_init();
56705673
CRYPTO_set_locking_callback(crypto_lock_cb);
56715674
CRYPTO_THREADID_set_callback(crypto_threadid_cb);
56725675

56735676
#ifdef NODE_FIPS_MODE
5674-
if (!FIPS_mode_set(1)) {
5675-
int err = ERR_get_error();
5677+
/* Override FIPS settings in cnf file, if needed. */
5678+
unsigned long err = 0;
5679+
if (enable_fips_crypto || force_fips_crypto) {
5680+
if (0 == FIPS_mode() && !FIPS_mode_set(1)) {
5681+
err = ERR_get_error();
5682+
}
5683+
}
5684+
if (0 != err) {
56765685
fprintf(stderr, "openssl fips failed: %s\n", ERR_error_string(err, NULL));
56775686
UNREACHABLE();
56785687
}
@@ -5739,6 +5748,29 @@ void SetEngine(const FunctionCallbackInfo<Value>& args) {
57395748
}
57405749
#endif // !OPENSSL_NO_ENGINE
57415750

5751+
void GetFipsCrypto(const FunctionCallbackInfo<Value>& args) {
5752+
if (FIPS_mode()) {
5753+
args.GetReturnValue().Set(1);
5754+
} else {
5755+
args.GetReturnValue().Set(0);
5756+
}
5757+
}
5758+
5759+
void SetFipsCrypto(const FunctionCallbackInfo<Value>& args) {
5760+
Environment* env = Environment::GetCurrent(args);
5761+
#ifdef NODE_FIPS_MODE
5762+
bool mode = args[0]->BooleanValue();
5763+
if (force_fips_crypto) {
5764+
return env->ThrowError(
5765+
"Cannot set FIPS mode, it was forced with --force-fips at startup.");
5766+
} else if (!FIPS_mode_set(mode)) {
5767+
unsigned long err = ERR_get_error();
5768+
return ThrowCryptoError(env, err);
5769+
}
5770+
#else
5771+
return env->ThrowError("Cannot set FIPS mode in a non-FIPS build.");
5772+
#endif /* NODE_FIPS_MODE */
5773+
}
57425774

57435775
// FIXME(bnoordhuis) Handle global init correctly.
57445776
void InitCrypto(Local<Object> target,
@@ -5763,6 +5795,8 @@ void InitCrypto(Local<Object> target,
57635795
#ifndef OPENSSL_NO_ENGINE
57645796
env->SetMethod(target, "setEngine", SetEngine);
57655797
#endif // !OPENSSL_NO_ENGINE
5798+
env->SetMethod(target, "getFipsCrypto", GetFipsCrypto);
5799+
env->SetMethod(target, "setFipsCrypto", SetFipsCrypto);
57665800
env->SetMethod(target, "PBKDF2", PBKDF2);
57675801
env->SetMethod(target, "randomBytes", RandomBytes);
57685802
env->SetMethod(target, "getSSLCiphers", GetSSLCiphers);

test/common.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ Object.defineProperty(exports, 'hasCrypto', {
161161

162162
Object.defineProperty(exports, 'hasFipsCrypto', {
163163
get: function() {
164-
return process.config.variables.openssl_fips ? true : false;
164+
return exports.hasCrypto && require('crypto').fips;
165165
}
166166
});
167167

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Skeleton openssl.cnf for testing with FIPS
2+
3+
openssl_conf = openssl_conf_section
4+
authorityKeyIdentifier=keyid:always,issuer:always
5+
6+
[openssl_conf_section]
7+
# Configuration module list
8+
alg_section = evp_sect
9+
10+
[ evp_sect ]
11+
# Set to "yes" to enter FIPS mode if supported
12+
fips_mode = no
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Skeleton openssl.cnf for testing with FIPS
2+
3+
openssl_conf = openssl_conf_section
4+
authorityKeyIdentifier=keyid:always,issuer:always
5+
6+
[openssl_conf_section]
7+
# Configuration module list
8+
alg_section = evp_sect
9+
10+
[ evp_sect ]
11+
# Set to "yes" to enter FIPS mode if supported
12+
fips_mode = yes

test/parallel/test-crypto-fips.js

+180
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
'use strict';
2+
var common = require('../common');
3+
var assert = require('assert');
4+
var spawnSync = require('child_process').spawnSync;
5+
var path = require('path');
6+
7+
if (!common.hasCrypto) {
8+
console.log('1..0 # Skipped: missing crypto');
9+
return;
10+
}
11+
12+
const FIPS_ENABLED = 1;
13+
const FIPS_DISABLED = 0;
14+
const FIPS_ERROR_STRING = 'Error: Cannot set FIPS mode';
15+
const OPTION_ERROR_STRING = 'bad option';
16+
const CNF_FIPS_ON = path.join(common.fixturesDir, 'openssl_fips_enabled.cnf');
17+
const CNF_FIPS_OFF = path.join(common.fixturesDir, 'openssl_fips_disabled.cnf');
18+
var num_children_ok = 0;
19+
20+
function compiledWithFips() {
21+
return process.config.variables.openssl_fips ? true : false;
22+
}
23+
24+
function addToEnv(newVar, value) {
25+
var envCopy = {};
26+
for (const e in process.env) {
27+
envCopy[e] = process.env[e];
28+
}
29+
envCopy[newVar] = value;
30+
return envCopy;
31+
}
32+
33+
function testHelper(stream, args, expectedOutput, cmd, env) {
34+
const fullArgs = args.concat(['-e', 'console.log(' + cmd + ')']);
35+
const child = spawnSync(process.execPath, fullArgs, {
36+
cwd: path.dirname(process.execPath),
37+
env: env
38+
});
39+
40+
console.error('Spawned child [pid:' + child.pid + '] with cmd ' +
41+
cmd + ' and args \'' + args + '\'');
42+
43+
function childOk(child) {
44+
console.error('Child #' + ++num_children_ok +
45+
' [pid:' + child.pid + '] OK.');
46+
}
47+
48+
function responseHandler(buffer, expectedOutput) {
49+
const response = buffer.toString();
50+
assert.notEqual(0, response.length);
51+
if (FIPS_ENABLED !== expectedOutput && FIPS_DISABLED !== expectedOutput) {
52+
// In the case of expected errors just look for a substring.
53+
assert.notEqual(-1, response.indexOf(expectedOutput));
54+
} else {
55+
// Normal path where we expect either FIPS enabled or disabled.
56+
assert.equal(expectedOutput, response);
57+
}
58+
childOk(child);
59+
}
60+
61+
responseHandler(child[stream], expectedOutput);
62+
}
63+
64+
// By default FIPS should be off in both FIPS and non-FIPS builds.
65+
testHelper(
66+
'stdout',
67+
[],
68+
FIPS_DISABLED,
69+
'require("crypto").fips',
70+
addToEnv('OPENSSL_CONF', ''));
71+
72+
// --enable-fips should turn FIPS mode on
73+
testHelper(
74+
compiledWithFips() ? 'stdout' : 'stderr',
75+
['--enable-fips'],
76+
compiledWithFips() ? FIPS_ENABLED : OPTION_ERROR_STRING,
77+
'require("crypto").fips',
78+
process.env);
79+
80+
//--force-fips should turn FIPS mode on
81+
testHelper(
82+
compiledWithFips() ? 'stdout' : 'stderr',
83+
['--force-fips'],
84+
compiledWithFips() ? FIPS_ENABLED : OPTION_ERROR_STRING,
85+
'require("crypto").fips',
86+
process.env);
87+
88+
// OpenSSL config file should be able to turn on FIPS mode
89+
testHelper(
90+
'stdout',
91+
[],
92+
compiledWithFips() ? FIPS_ENABLED : FIPS_DISABLED,
93+
'require("crypto").fips',
94+
addToEnv('OPENSSL_CONF', CNF_FIPS_ON));
95+
96+
// --enable-fips should take precedence over OpenSSL config file
97+
testHelper(
98+
compiledWithFips() ? 'stdout' : 'stderr',
99+
['--enable-fips'],
100+
compiledWithFips() ? FIPS_ENABLED : OPTION_ERROR_STRING,
101+
'require("crypto").fips',
102+
addToEnv('OPENSSL_CONF', CNF_FIPS_OFF));
103+
104+
// --force-fips should take precedence over OpenSSL config file
105+
testHelper(
106+
compiledWithFips() ? 'stdout' : 'stderr',
107+
['--force-fips'],
108+
compiledWithFips() ? FIPS_ENABLED : OPTION_ERROR_STRING,
109+
'require("crypto").fips',
110+
addToEnv('OPENSSL_CONF', CNF_FIPS_OFF));
111+
112+
// setFipsCrypto should be able to turn FIPS mode on
113+
testHelper(
114+
compiledWithFips() ? 'stdout' : 'stderr',
115+
[],
116+
compiledWithFips() ? FIPS_ENABLED : FIPS_ERROR_STRING,
117+
'(require("crypto").fips = true,' +
118+
'require("crypto").fips)',
119+
addToEnv('OPENSSL_CONF', ''));
120+
121+
// setFipsCrypto should be able to turn FIPS mode on and off
122+
testHelper(
123+
compiledWithFips() ? 'stdout' : 'stderr',
124+
[],
125+
compiledWithFips() ? FIPS_DISABLED : FIPS_ERROR_STRING,
126+
'(require("crypto").fips = true,' +
127+
'require("crypto").fips = false,' +
128+
'require("crypto").fips)',
129+
addToEnv('OPENSSL_CONF', ''));
130+
131+
// setFipsCrypto takes precedence over OpenSSL config file, FIPS on
132+
testHelper(
133+
compiledWithFips() ? 'stdout' : 'stderr',
134+
[],
135+
compiledWithFips() ? FIPS_ENABLED : FIPS_ERROR_STRING,
136+
'(require("crypto").fips = true,' +
137+
'require("crypto").fips)',
138+
addToEnv('OPENSSL_CONF', CNF_FIPS_OFF));
139+
140+
// setFipsCrypto takes precedence over OpenSSL config file, FIPS off
141+
testHelper(
142+
compiledWithFips() ? 'stdout' : 'stderr',
143+
[],
144+
compiledWithFips() ? FIPS_DISABLED : FIPS_ERROR_STRING,
145+
'(require("crypto").fips = false,' +
146+
'require("crypto").fips)',
147+
addToEnv('OPENSSL_CONF', CNF_FIPS_ON));
148+
149+
// --enable-fips does not prevent use of setFipsCrypto API
150+
testHelper(
151+
compiledWithFips() ? 'stdout' : 'stderr',
152+
['--enable-fips'],
153+
compiledWithFips() ? FIPS_DISABLED : OPTION_ERROR_STRING,
154+
'(require("crypto").fips = false,' +
155+
'require("crypto").fips)',
156+
process.env);
157+
158+
// --force-fips prevents use of setFipsCrypto API
159+
testHelper(
160+
'stderr',
161+
['--force-fips'],
162+
compiledWithFips() ? FIPS_ERROR_STRING : OPTION_ERROR_STRING,
163+
'require("crypto").fips = false',
164+
process.env);
165+
166+
// --force-fips and --enable-fips order does not matter
167+
testHelper(
168+
'stderr',
169+
['--force-fips', '--enable-fips'],
170+
compiledWithFips() ? FIPS_ERROR_STRING : OPTION_ERROR_STRING,
171+
'require("crypto").fips = false',
172+
process.env);
173+
174+
//--enable-fips and --force-fips order does not matter
175+
testHelper(
176+
'stderr',
177+
['--enable-fips', '--force-fips'],
178+
compiledWithFips() ? FIPS_ERROR_STRING : OPTION_ERROR_STRING,
179+
'require("crypto").fips = false',
180+
process.env);

0 commit comments

Comments
 (0)