mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08:00
Delete test-crypto-ecb.js, which can no longer run anywhere: Blowfish is available only from OpenSSL's legacy provider and is absent from BoringSSL. The addon and cctest version guards become OPENSSL_IS_BORINGSSL checks. Remove legacy digest-alias coverage and unreachable OpenSSL 1.x branches. JavaScript test and benchmark gates, skip reasons, and TLS error expectations that distinguished OpenSSL 1.x from provider-backed OpenSSL now distinguish BoringSSL, including PKCS12 parsing, named DH groups, private-key serialization, RSA-PSS parameters, consolidated provider caches, FIPS availability, and empty KMAC output. Keep the CCM alternate finalization error only for BoringSSL. Provider-only test skips use version-neutral terminology or state the actual minimum OpenSSL version. Signed-off-by: Filip Skokan <panva.ip@gmail.com> Assisted-by: Codex PR-URL: https://github.com/nodejs/node/pull/64777 Fixes: https://github.com/nodejs/node/issues/56733 Refs: https://github.com/nodejs/node/pull/64211 Refs: https://github.com/nodejs/node/pull/63966 Refs: https://github.com/nodejs/node/pull/53329 Refs: https://github.com/nodejs/node/pull/48950 Refs: https://github.com/nodejs/node/issues/45465 Refs: https://github.com/nodejs/node/issues/42827 Refs: https://github.com/nodejs/node/issues/47076 Refs: https://github.com/nodejs/node/pull/62862 Refs: https://github.com/nodejs/node/pull/38512 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Richard Lau <richard.lau@ibm.com> Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
359 lines
9.9 KiB
JavaScript
359 lines
9.9 KiB
JavaScript
'use strict';
|
|
const common = require('../common');
|
|
if (!common.hasCrypto)
|
|
common.skip('missing crypto');
|
|
|
|
const assert = require('assert');
|
|
const crypto = require('crypto');
|
|
const {
|
|
hasFIPS,
|
|
isBoringSSL,
|
|
} = require('../common/crypto');
|
|
|
|
const fips4 = hasFIPS(4);
|
|
const validPassword = fips4 ? 'password' : 'pass';
|
|
const validSalt = fips4 ? '0123456789abcdef' : 'salt';
|
|
const validIterations = fips4 ? 1000 : 8;
|
|
const validKeyLength = fips4 ? 16 : 8;
|
|
|
|
function runPBKDF2(password, salt, iterations, keylen, hash, testAsync = true) {
|
|
const syncResult =
|
|
crypto.pbkdf2Sync(password, salt, iterations, keylen, hash);
|
|
|
|
if (testAsync) {
|
|
crypto.pbkdf2(password, salt, iterations, keylen, hash,
|
|
common.mustSucceed((asyncResult) => {
|
|
assert.deepStrictEqual(asyncResult, syncResult);
|
|
}));
|
|
}
|
|
|
|
return syncResult;
|
|
}
|
|
|
|
function assertPBKDF2Fails(password, salt, iterations, keylen, hash) {
|
|
const expected = { message: 'PBKDF2 derivation failed' };
|
|
assert.throws(
|
|
() => crypto.pbkdf2Sync(password, salt, iterations, keylen, hash),
|
|
expected,
|
|
);
|
|
crypto.pbkdf2(
|
|
password,
|
|
salt,
|
|
iterations,
|
|
keylen,
|
|
hash,
|
|
common.mustCall((err, result) => {
|
|
assert.strictEqual(err?.message, expected.message);
|
|
assert.strictEqual(result, undefined);
|
|
}),
|
|
);
|
|
}
|
|
|
|
function testPBKDF2(password, salt, iterations, keylen, expected, encoding,
|
|
testAsync = false) {
|
|
const actual = runPBKDF2(password, salt, iterations, keylen, 'sha256', testAsync);
|
|
assert.strictEqual(actual.toString(encoding || 'latin1'), expected);
|
|
}
|
|
|
|
//
|
|
// Test PBKDF2 with RFC 6070 test vectors (except #4). Run one known answer
|
|
// through the callback API; the rest only need synchronous derivation.
|
|
//
|
|
|
|
if (fips4) {
|
|
testPBKDF2(validPassword, validSalt, validIterations, 32,
|
|
'8514638175a45bc45eb1f22f04ff7d27' +
|
|
'f4f8be480498c455ff4b494ce8d1e7d2',
|
|
'hex', true);
|
|
|
|
for (const args of [
|
|
['short', validSalt, validIterations],
|
|
[validPassword, 'short', validIterations],
|
|
[validPassword, validSalt, 999],
|
|
]) {
|
|
assertPBKDF2Fails(...args, 32, 'sha256');
|
|
}
|
|
assertPBKDF2Fails(
|
|
validPassword, validSalt, validIterations, 8, 'sha256');
|
|
} else {
|
|
testPBKDF2('password', 'salt', 1, 20,
|
|
'\x12\x0f\xb6\xcf\xfc\xf8\xb3\x2c\x43\xe7\x22\x52' +
|
|
'\x56\xc4\xf8\x37\xa8\x65\x48\xc9');
|
|
|
|
testPBKDF2('password', 'salt', 2, 20,
|
|
'\xae\x4d\x0c\x95\xaf\x6b\x46\xd3\x2d\x0a\xdf\xf9' +
|
|
'\x28\xf0\x6d\xd0\x2a\x30\x3f\x8e');
|
|
|
|
testPBKDF2('password', 'salt', 4096, 20,
|
|
'\xc5\xe4\x78\xd5\x92\x88\xc8\x41\xaa\x53\x0d\xb6' +
|
|
'\x84\x5c\x4c\x8d\x96\x28\x93\xa0', 'latin1', true);
|
|
|
|
testPBKDF2('passwordPASSWORDpassword',
|
|
'saltSALTsaltSALTsaltSALTsaltSALTsalt',
|
|
4096,
|
|
25,
|
|
'\x34\x8c\x89\xdb\xcb\xd3\x2b\x2f\x32\xd8\x14\xb8\x11' +
|
|
'\x6e\x84\xcf\x2b\x17\x34\x7e\xbc\x18\x00\x18\x1c');
|
|
|
|
testPBKDF2('pass\0word', 'sa\0lt', 4096, 16,
|
|
'\x89\xb6\x9d\x05\x16\xf8\x29\x89\x3c\x69\x62\x26\x65' +
|
|
'\x0a\x86\x87');
|
|
|
|
testPBKDF2('password', 'salt', 32, 32,
|
|
'64c486c55d30d4c5a079b8823b7d7cb3' +
|
|
'7ff0556f537da8410233bcec330ed956',
|
|
'hex');
|
|
}
|
|
|
|
// Error path should not leak memory (check with valgrind).
|
|
assert.throws(
|
|
() => crypto.pbkdf2(
|
|
validPassword, validSalt, validIterations, 20, 'sha1'),
|
|
{
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
name: 'TypeError'
|
|
}
|
|
);
|
|
|
|
for (const iterations of [-1, 0, 2147483648]) {
|
|
assert.throws(
|
|
() => crypto.pbkdf2Sync(
|
|
validPassword, validSalt, iterations, 20, 'sha1'),
|
|
{
|
|
code: 'ERR_OUT_OF_RANGE',
|
|
name: 'RangeError',
|
|
}
|
|
);
|
|
}
|
|
|
|
['str', null, undefined, [], {}].forEach((notNumber) => {
|
|
assert.throws(
|
|
() => {
|
|
crypto.pbkdf2Sync(
|
|
validPassword, validSalt, validIterations, notNumber, 'sha256');
|
|
}, {
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
name: 'TypeError',
|
|
message: 'The "keylen" argument must be of type number.' +
|
|
`${common.invalidArgTypeHelper(notNumber)}`
|
|
});
|
|
});
|
|
|
|
[Infinity, -Infinity, NaN].forEach((input) => {
|
|
assert.throws(
|
|
() => {
|
|
crypto.pbkdf2(validPassword, validSalt, validIterations, input, 'sha256',
|
|
common.mustNotCall());
|
|
}, {
|
|
code: 'ERR_OUT_OF_RANGE',
|
|
name: 'RangeError',
|
|
message: 'The value of "keylen" is out of range. It ' +
|
|
`must be an integer. Received ${input}`
|
|
});
|
|
});
|
|
|
|
[-1, 2147483648, 4294967296].forEach((input) => {
|
|
assert.throws(
|
|
() => {
|
|
crypto.pbkdf2(validPassword, validSalt, validIterations, input, 'sha256',
|
|
common.mustNotCall());
|
|
}, {
|
|
code: 'ERR_OUT_OF_RANGE',
|
|
name: 'RangeError',
|
|
});
|
|
});
|
|
|
|
// `-0` keylen must not abort the process via the native binding's
|
|
// IsInt32() assertion. Behavior of `keylen=0` itself varies by OpenSSL
|
|
// build (bundled returns an empty buffer; some shared OpenSSL builds
|
|
// throw); the requirement here is only that `-0` produces the same
|
|
// outcome as `+0`.
|
|
{
|
|
let posError;
|
|
let posResult;
|
|
try {
|
|
posResult = crypto.pbkdf2Sync(
|
|
validPassword, validSalt, validIterations, 0, 'sha256');
|
|
} catch (err) {
|
|
posError = err;
|
|
}
|
|
let negError;
|
|
let negResult;
|
|
try {
|
|
negResult = crypto.pbkdf2Sync(
|
|
validPassword, validSalt, validIterations, -0, 'sha256');
|
|
} catch (err) {
|
|
negError = err;
|
|
}
|
|
if (posError !== undefined) {
|
|
assert.strictEqual(negError?.message, posError.message);
|
|
} else {
|
|
assert.deepStrictEqual(negResult, posResult);
|
|
}
|
|
|
|
crypto.pbkdf2(
|
|
validPassword, validSalt, validIterations, -0, 'sha256',
|
|
common.mustCall());
|
|
}
|
|
|
|
// Should not get FATAL ERROR with empty password and salt
|
|
// https://github.com/nodejs/node/issues/8571
|
|
if (fips4) {
|
|
assertPBKDF2Fails('', '', 1, 32, 'sha256');
|
|
} else {
|
|
crypto.pbkdf2('', '', 1, 32, 'sha256', common.mustSucceed());
|
|
}
|
|
|
|
assert.throws(
|
|
() => crypto.pbkdf2(
|
|
validPassword, validSalt, validIterations, 8, common.mustNotCall()),
|
|
{
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
name: 'TypeError',
|
|
message: 'The "digest" argument must be of type string. ' +
|
|
'Received undefined'
|
|
});
|
|
|
|
assert.throws(
|
|
() => crypto.pbkdf2Sync(validPassword, validSalt, validIterations, 8),
|
|
{
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
name: 'TypeError',
|
|
message: 'The "digest" argument must be of type string. ' +
|
|
'Received undefined'
|
|
});
|
|
|
|
assert.throws(
|
|
() => crypto.pbkdf2Sync(validPassword, validSalt, validIterations, 8, null),
|
|
{
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
name: 'TypeError',
|
|
message: 'The "digest" argument must be of type string. ' +
|
|
'Received null'
|
|
});
|
|
[1, {}, [], true, undefined, null].forEach((input) => {
|
|
assert.throws(
|
|
() => crypto.pbkdf2(
|
|
input, validSalt, validIterations, 8, 'sha256', common.mustNotCall()),
|
|
{
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
name: 'TypeError',
|
|
}
|
|
);
|
|
|
|
assert.throws(
|
|
() => crypto.pbkdf2(
|
|
validPassword, input, validIterations, 8, 'sha256',
|
|
common.mustNotCall()),
|
|
{
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
name: 'TypeError',
|
|
}
|
|
);
|
|
|
|
assert.throws(
|
|
() => crypto.pbkdf2Sync(
|
|
input, validSalt, validIterations, 8, 'sha256'),
|
|
{
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
name: 'TypeError',
|
|
}
|
|
);
|
|
|
|
assert.throws(
|
|
() => crypto.pbkdf2Sync(
|
|
validPassword, input, validIterations, 8, 'sha256'),
|
|
{
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
name: 'TypeError',
|
|
}
|
|
);
|
|
});
|
|
|
|
['test', {}, [], true, undefined, null].forEach((i) => {
|
|
const received = common.invalidArgTypeHelper(i);
|
|
assert.throws(
|
|
() => crypto.pbkdf2(
|
|
validPassword, validSalt, i, 8, 'sha256', common.mustNotCall()),
|
|
{
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
name: 'TypeError',
|
|
message: `The "iterations" argument must be of type number.${received}`
|
|
}
|
|
);
|
|
|
|
assert.throws(
|
|
() => crypto.pbkdf2Sync(validPassword, validSalt, i, 8, 'sha256'),
|
|
{
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
name: 'TypeError',
|
|
message: `The "iterations" argument must be of type number.${received}`
|
|
}
|
|
);
|
|
});
|
|
|
|
if (fips4) {
|
|
assertPBKDF2Fails(
|
|
new Uint8Array(1), validSalt, validIterations, validKeyLength, 'sha256');
|
|
assertPBKDF2Fails(
|
|
validPassword, new Uint8Array(1), validIterations, validKeyLength,
|
|
'sha256');
|
|
}
|
|
|
|
// Any TypedArray should work for password and salt.
|
|
for (const SomeArray of [Uint8Array, Uint16Array, Uint32Array, Float32Array,
|
|
Float64Array, ArrayBuffer, SharedArrayBuffer]) {
|
|
const length = fips4 ? 16 : 10;
|
|
const input = new SomeArray(length);
|
|
const bytes = ArrayBuffer.isView(input) ?
|
|
new Uint8Array(input.buffer, input.byteOffset, input.byteLength) :
|
|
new Uint8Array(input);
|
|
for (let index = 0; index < bytes.length; index++)
|
|
bytes[index] = index + 1;
|
|
runPBKDF2(input, validSalt, validIterations, validKeyLength, 'sha256');
|
|
runPBKDF2(validPassword, input, validIterations, validKeyLength, 'sha256');
|
|
}
|
|
|
|
assert.throws(
|
|
() => crypto.pbkdf2(
|
|
validPassword, validSalt, validIterations, 8, 'md55',
|
|
common.mustNotCall()),
|
|
{
|
|
code: 'ERR_CRYPTO_INVALID_DIGEST',
|
|
name: 'TypeError',
|
|
message: 'Invalid digest: md55'
|
|
}
|
|
);
|
|
|
|
assert.throws(
|
|
() => crypto.pbkdf2Sync(
|
|
validPassword, validSalt, validIterations, 8, 'md55'),
|
|
{
|
|
code: 'ERR_CRYPTO_INVALID_DIGEST',
|
|
name: 'TypeError',
|
|
message: 'Invalid digest: md55'
|
|
}
|
|
);
|
|
|
|
if (isBoringSSL) {
|
|
const kNotPBKDF2Supported = ['shake128', 'shake256'];
|
|
crypto.getHashes()
|
|
.filter((hash) => !kNotPBKDF2Supported.includes(hash))
|
|
.forEach((hash) => {
|
|
runPBKDF2(new Uint8Array(10), 'salt', 8, 8, hash);
|
|
});
|
|
}
|
|
|
|
{
|
|
// This should not crash.
|
|
assert.throws(
|
|
() => crypto.pbkdf2Sync(
|
|
validPassword, validSalt, validIterations, 1, '%'),
|
|
{
|
|
code: 'ERR_CRYPTO_INVALID_DIGEST',
|
|
name: 'TypeError',
|
|
message: 'Invalid digest: %'
|
|
}
|
|
);
|
|
}
|