node/test/parallel/test-crypto-fips.js
Daniel Bevenius 1997aa3b40 src,test: raise error for --enable-fips when no FIPS
This commit moves the check for FIPS from the crypto module
initialization to process startup.

The motivation for this is that when OpenSSL is not FIPS enabled and the
command line options --enable-fips, or --force-fips are used, there will
only be an error raised if the crypto module is used. This can be
surprising and we have gotten feedback that users assumed that there
would be an error if these options were specified and FIPS is not
available.

PR-URL: https://github.com/nodejs/node/pull/38859
Reviewed-By: Michael Dawson <midawson@redhat.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Richard Lau <rlau@redhat.com>
2021-06-08 11:42:00 +02:00

268 lines
9.1 KiB
JavaScript

// Flags: --expose-internals
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const spawnSync = require('child_process').spawnSync;
const path = require('path');
const fixtures = require('../common/fixtures');
const { internalBinding } = require('internal/test/binding');
const { testFipsCrypto } = internalBinding('crypto');
const FIPS_ENABLED = 1;
const FIPS_DISABLED = 0;
const FIPS_ERROR_STRING2 =
'Error [ERR_CRYPTO_FIPS_FORCED]: Cannot set FIPS mode, it was forced with ' +
'--force-fips at startup.';
const FIPS_UNSUPPORTED_ERROR_STRING = 'fips mode not supported';
const FIPS_ENABLE_ERROR_STRING = 'OpenSSL error when trying to enable FIPS:';
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 sharedOpenSSL() {
return process.config.variables.node_shared_openssl;
}
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}' expect %j with args '${
args}' OPENSSL_CONF=%j`, expectedOutput, env.OPENSSL_CONF);
function childOk(child) {
console.error(`Child #${++num_children_ok} [pid:${child.pid}] OK.`);
}
function responseHandler(buffer, expectedOutput) {
const response = buffer.toString();
assert.notStrictEqual(response.length, 0);
if (FIPS_ENABLED !== expectedOutput && FIPS_DISABLED !== expectedOutput) {
// In the case of expected errors just look for a substring.
assert.ok(response.includes(expectedOutput));
} else {
const getFipsValue = Number(response);
if (!Number.isNaN(getFipsValue))
// Normal path where we expect either FIPS enabled or disabled.
assert.strictEqual(getFipsValue, expectedOutput);
}
childOk(child);
}
responseHandler(child[stream], expectedOutput);
}
// --enable-fips should raise an error if OpenSSL is not FIPS enabled.
testHelper(
testFipsCrypto() ? 'stdout' : 'stderr',
['--enable-fips'],
testFipsCrypto() ? FIPS_ENABLED : FIPS_ENABLE_ERROR_STRING,
'process.versions',
process.env);
// --force-fips should raise an error if OpenSSL is not FIPS enabled.
testHelper(
testFipsCrypto() ? 'stdout' : 'stderr',
['--force-fips'],
testFipsCrypto() ? FIPS_ENABLED : FIPS_ENABLE_ERROR_STRING,
'process.versions',
process.env);
// By default FIPS should be off in both FIPS and non-FIPS builds.
testHelper(
'stdout',
[],
FIPS_DISABLED,
'require("crypto").getFips()',
{ ...process.env, 'OPENSSL_CONF': '' });
// This should succeed for both FIPS and non-FIPS builds in combination with
// OpenSSL 1.1.1 or OpenSSL 3.0
const test_result = testFipsCrypto();
assert.ok(test_result === 1 || test_result === 0);
// If Node was configured using --shared-openssl fips support might be
// available depending on how OpenSSL was built. If fips support is
// available the tests that toggle the fips_mode on/off using the config
// file option will succeed and return 1 instead of 0.
//
// Note that this case is different from when calling the fips setter as the
// configuration file is handled by OpenSSL, so it is not possible for us
// to try to call the fips setter, to try to detect this situation, as
// that would throw an error:
// ("Error: Cannot set FIPS mode in a non-FIPS build.").
// Due to this uncertainty the following tests are skipped when configured
// with --shared-openssl.
if (!sharedOpenSSL() && !common.hasOpenSSL3) {
// OpenSSL config file should be able to turn on FIPS mode
testHelper(
'stdout',
[`--openssl-config=${CNF_FIPS_ON}`],
testFipsCrypto() ? FIPS_ENABLED : FIPS_DISABLED,
'require("crypto").getFips()',
process.env);
// OPENSSL_CONF should be able to turn on FIPS mode
testHelper(
'stdout',
[],
testFipsCrypto() ? FIPS_ENABLED : FIPS_DISABLED,
'require("crypto").getFips()',
Object.assign({}, process.env, { 'OPENSSL_CONF': CNF_FIPS_ON }));
// --openssl-config option should override OPENSSL_CONF
testHelper(
'stdout',
[`--openssl-config=${CNF_FIPS_ON}`],
testFipsCrypto() ? FIPS_ENABLED : FIPS_DISABLED,
'require("crypto").getFips()',
Object.assign({}, process.env, { 'OPENSSL_CONF': CNF_FIPS_OFF }));
}
// OpenSSL 3.x has changed the configuration files so the following tests
// will not work as expected with that version.
// TODO(danbev) Revisit these test once FIPS support is available in
// OpenSSL 3.x.
if (!common.hasOpenSSL3) {
testHelper(
'stdout',
[`--openssl-config=${CNF_FIPS_OFF}`],
FIPS_DISABLED,
'require("crypto").getFips()',
Object.assign({}, process.env, { 'OPENSSL_CONF': CNF_FIPS_ON }));
// --enable-fips should take precedence over OpenSSL config file
testHelper(
testFipsCrypto() ? 'stdout' : 'stderr',
['--enable-fips', `--openssl-config=${CNF_FIPS_OFF}`],
testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING,
'require("crypto").getFips()',
process.env);
// --force-fips should take precedence over OpenSSL config file
testHelper(
testFipsCrypto() ? 'stdout' : 'stderr',
['--force-fips', `--openssl-config=${CNF_FIPS_OFF}`],
testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING,
'require("crypto").getFips()',
process.env);
// --enable-fips should turn FIPS mode on
testHelper(
testFipsCrypto() ? 'stdout' : 'stderr',
['--enable-fips'],
testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING,
'require("crypto").getFips()',
process.env);
// --force-fips should turn FIPS mode on
testHelper(
testFipsCrypto() ? 'stdout' : 'stderr',
['--force-fips'],
testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING,
'require("crypto").getFips()',
process.env);
// OPENSSL_CONF should _not_ make a difference to --enable-fips
testHelper(
testFipsCrypto() ? 'stdout' : 'stderr',
['--enable-fips'],
testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING,
'require("crypto").getFips()',
Object.assign({}, process.env, { 'OPENSSL_CONF': CNF_FIPS_OFF }));
// Using OPENSSL_CONF should not make a difference to --force-fips
testHelper(
testFipsCrypto() ? 'stdout' : 'stderr',
['--force-fips'],
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(
testFipsCrypto() ? 'stdout' : 'stderr',
[],
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(
testFipsCrypto() ? 'stdout' : 'stderr',
[],
testFipsCrypto() ? FIPS_DISABLED : FIPS_UNSUPPORTED_ERROR_STRING,
'(require("crypto").setFips(true),' +
'require("crypto").setFips(false),' +
'require("crypto").getFips())',
process.env);
// setFipsCrypto takes precedence over OpenSSL config file, FIPS on
testHelper(
testFipsCrypto() ? 'stdout' : 'stderr',
[`--openssl-config=${CNF_FIPS_OFF}`],
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(
'stdout',
[`--openssl-config=${CNF_FIPS_ON}`],
FIPS_DISABLED,
'(require("crypto").setFips(false),' +
'require("crypto").getFips())',
process.env);
// --enable-fips does not prevent use of setFipsCrypto API
testHelper(
testFipsCrypto() ? 'stdout' : 'stderr',
['--enable-fips'],
testFipsCrypto() ? FIPS_DISABLED : FIPS_UNSUPPORTED_ERROR_STRING,
'(require("crypto").setFips(false),' +
'require("crypto").getFips())',
process.env);
// --force-fips prevents use of setFipsCrypto API
testHelper(
'stderr',
['--force-fips'],
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(
testFipsCrypto() ? 'stdout' : 'stderr',
['--force-fips'],
testFipsCrypto() ? FIPS_ENABLED : FIPS_UNSUPPORTED_ERROR_STRING,
'(require("crypto").setFips(true),' +
'require("crypto").getFips())',
process.env);
// --force-fips and --enable-fips order does not matter
testHelper(
'stderr',
['--force-fips', '--enable-fips'],
testFipsCrypto() ? FIPS_ERROR_STRING2 : FIPS_UNSUPPORTED_ERROR_STRING,
'require("crypto").setFips(false)',
process.env);
// --enable-fips and --force-fips order does not matter
testHelper(
'stderr',
['--enable-fips', '--force-fips'],
testFipsCrypto() ? FIPS_ERROR_STRING2 : FIPS_UNSUPPORTED_ERROR_STRING,
'require("crypto").setFips(false)',
process.env);
}