Files
node/test/parallel/test-crypto-mac.js
T
Filip Skokan acbc3ff133 test,benchmark: drop OpenSSL 1.x-only coverage
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>
2026-10-25 22:36:32 +01:00

217 lines
6.9 KiB
JavaScript

// Flags: --expose-internals
'use strict';
const common = require('../common');
if (!common.hasCrypto) {
common.skip('missing crypto');
}
const { isBoringSSL } = require('../common/crypto');
if (isBoringSSL) {
common.skip('OpenSSL EVP_MAC support is required');
}
const assert = require('node:assert');
const crypto = require('node:crypto');
const { encodingsMap } = require('internal/util');
const {
createHmac,
createMac,
createSecretKey,
getMacs,
} = crypto;
const { finished } = require('node:stream/promises');
const { Transform } = require('node:stream');
assert.strictEqual(crypto.Mac, undefined);
const macs = getMacs();
assert(macs.every((name) => name === name.toLowerCase()));
assert(macs.every((name) => !/^\d+(?:\.\d+)+$/.test(name)));
const availableMacs = new Set(macs);
if (!availableMacs.has('hmac')) {
common.printSkipMessage('HMAC is not available from the active providers');
} else {
const algorithm = 'HMAC';
const options = { digest: 'sha256' };
const key = Buffer.from('000102030405060708090a0b0c0d0e0f', 'hex');
const data = Buffer.from('The quick brown fox jumps over the lazy dog');
const expected = createHmac('sha256', key).update(data).digest();
const expectedEmpty = createHmac('sha256', key).digest();
const expectedEmptyKey = createHmac('sha256', Buffer.alloc(0))
.update(data)
.digest();
assert.deepStrictEqual(
createMac(algorithm, key, options).update(data.toString()).final(),
expected,
);
assert.deepStrictEqual(
createMac(algorithm, key, options).final(),
expectedEmpty,
);
assert.deepStrictEqual(
createMac(algorithm, Buffer.alloc(0), options).update(data).final(),
expectedEmptyKey,
);
const nullPrototypeOptions = Object.assign({ __proto__: null }, options);
assert.deepStrictEqual(
createMac(algorithm, key, nullPrototypeOptions).update(data).final(),
expected,
);
const inheritedUnknownOptions = Object.assign(
{ __proto__: { unknown: true } }, options);
assert.deepStrictEqual(
createMac(algorithm, key, inheritedUnknownOptions).update(data).final(),
expected,
);
assert.deepStrictEqual(
createMac(algorithm, key, { ...options, unknown: true })
.update(data)
.final(),
expected,
);
const incremental = createMac(algorithm, key, options);
assert(incremental instanceof Transform);
assert.strictEqual(incremental.update(data.subarray(0, 10)), incremental);
assert.strictEqual(incremental.update(Buffer.alloc(0)), incremental);
incremental.update(data.subarray(10));
assert.deepStrictEqual(incremental.final(), expected);
assert.throws(
() => incremental.update(Buffer.alloc(0)),
{ code: 'ERR_CRYPTO_MAC_FINALIZED' },
);
assert.throws(
() => incremental.final(),
{ code: 'ERR_CRYPTO_MAC_FINALIZED' },
);
// Input encodings are handled by update(), while final() accepts an output
// encoding.
assert.strictEqual(
createMac(algorithm, key, options)
.update(data.toString('hex'), 'hex')
.final('hex'),
expected.toString('hex'),
);
assert.strictEqual(
createMac(algorithm, key, options)
.update(data.toString('base64'), 'base64')
.final('base64url'),
expected.toString('base64url'),
);
for (const outputEncoding of Object.keys(encodingsMap)) {
if (outputEncoding === 'buffer') continue;
assert.strictEqual(
createMac(algorithm, key, options)
.update(data)
.final(outputEncoding),
expected.toString(outputEncoding),
);
}
assert.deepStrictEqual(
createMac(algorithm, key, options)
.update(data, 'not-an-encoding')
.final(),
expected,
);
const explicitBuffer = createMac(algorithm, key, options)
.update(data)
.final('buffer');
assert(Buffer.isBuffer(explicitBuffer));
assert.deepStrictEqual(explicitBuffer, expected);
const encodedFinal = createMac(algorithm, key, options).update(data);
assert.strictEqual(encodedFinal.final('hex'), expected.toString('hex'));
assert.throws(
() => encodedFinal.update(data),
{ code: 'ERR_CRYPTO_MAC_FINALIZED' },
);
assert.throws(
() => encodedFinal.final('hex'),
{ code: 'ERR_CRYPTO_MAC_FINALIZED' },
);
// BufferSource keys and data must honor view offsets and lengths.
const keyStorage = Uint8Array.from([0xff, ...key, 0xff]);
const keyView = new Uint8Array(keyStorage.buffer, 1, key.length);
const keyDataView = new DataView(keyStorage.buffer, 1, key.length);
const dataStorage = Uint8Array.from([0xff, ...data, 0xff]);
const dataView = new DataView(dataStorage.buffer, 1, data.length);
assert.deepStrictEqual(
createMac(algorithm, keyView, options).update(dataView).final(),
expected,
);
assert.deepStrictEqual(
createMac(algorithm, keyDataView, options).update(dataView).final(),
expected,
);
const arrayBufferKey = key.buffer.slice(
key.byteOffset,
key.byteOffset + key.byteLength,
);
assert.deepStrictEqual(
createMac(algorithm, arrayBufferKey, options).update(data).final(),
expected,
);
const secretKey = createSecretKey(key);
assert.deepStrictEqual(
createMac(algorithm, secretKey, options).update(data).final(),
expected,
);
(async () => {
const esmCrypto = await import('node:crypto');
assert.strictEqual(esmCrypto.createMac, createMac);
assert.strictEqual(esmCrypto.getMacs, getMacs);
assert.strictEqual(esmCrypto.Mac, undefined);
const emptyStream = createMac(algorithm, key, options);
const emptyChunks = [];
emptyStream.on(
'data', common.mustCall((chunk) => emptyChunks.push(chunk), 1));
const emptyFinished = finished(emptyStream);
emptyStream.end();
await emptyFinished;
assert.deepStrictEqual(Buffer.concat(emptyChunks), expectedEmpty);
const streamed = createMac(algorithm, key, {
...options,
highWaterMark: 1,
});
const chunks = [];
streamed.on('data', common.mustCall((chunk) => chunks.push(chunk), 1));
assert.strictEqual(streamed.writableHighWaterMark, 1);
assert.strictEqual(streamed.readableHighWaterMark, 1);
const streamedFinished = finished(streamed);
streamed.write(data.subarray(0, 10));
streamed.end(data.subarray(10));
await streamedFinished;
assert.deepStrictEqual(Buffer.concat(chunks), expected);
assert.throws(
() => streamed.update(Buffer.alloc(0)),
{ code: 'ERR_CRYPTO_MAC_FINALIZED' },
);
assert.throws(
() => streamed.final(),
{ code: 'ERR_CRYPTO_MAC_FINALIZED' },
);
// Direct finalization followed by stream finalization fails through the
// stream error path and does not emit a second tag.
const mixed = createMac(algorithm, key, options);
mixed.on('data', common.mustNotCall());
const mixedFinished = finished(mixed);
assert.deepStrictEqual(mixed.update(data).final(), expected);
mixed.end();
await assert.rejects(mixedFinished, {
code: 'ERR_CRYPTO_MAC_FINALIZED',
});
})().then(common.mustCall());
}