node/test/parallel/test-webcrypto-derivebits-hkdf.js
Filip Skokan 3ef38c4bd7
crypto: use WebIDL converters in WebCryptoAPI
WebCryptoAPI functions' arguments are now coersed and validated as per
their WebIDL definitions like in other Web Crypto API implementations.
This further improves interoperability with other implementations of
Web Crypto API.

PR-URL: https://github.com/nodejs/node/pull/46067
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
2023-01-17 08:57:58 +00:00

560 lines
14 KiB
JavaScript

'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const { subtle } = globalThis.crypto;
function getDeriveKeyInfo(name, length, hash, ...usages) {
return [{ name, length, hash }, usages];
}
const kDerivedKeyTypes = [
['AES-CBC', 128, undefined, 'encrypt', 'decrypt'],
['AES-CBC', 256, undefined, 'encrypt', 'decrypt'],
['AES-CTR', 128, undefined, 'encrypt', 'decrypt'],
['AES-CTR', 256, undefined, 'encrypt', 'decrypt'],
['AES-GCM', 128, undefined, 'encrypt', 'decrypt'],
['AES-GCM', 256, undefined, 'encrypt', 'decrypt'],
['AES-KW', 128, undefined, 'wrapKey', 'unwrapKey'],
['AES-KW', 256, undefined, 'wrapKey', 'unwrapKey'],
['HMAC', 256, 'SHA-1', 'sign', 'verify'],
['HMAC', 256, 'SHA-256', 'sign', 'verify'],
['HMAC', 256, 'SHA-384', 'sign', 'verify'],
['HMAC', 256, 'SHA-512', 'sign', 'verify'],
];
const kDerivedKeys = {
short: '5040737377307264',
long: '55736572732073686f756c64207069636b206c6f6e6720706173737068726' +
'173657320286e6f74207573652073686f72742070617373776f7264732921',
empty: ''
};
const kSalts = {
normal: '536f6469756d2043686c6f7269646520636f6d706f756e64',
empty: ''
};
const kInfos = {
normal: '484b444620657874726120696e666f',
empty: ''
};
const kDerivations = {
short: {
normal: {
'SHA-384': {
normal: '19ba74368e6b993390f27fe9a7d02bc3' +
'38173f72be71a19fc744fcdb3fd4b84b',
empty: '97601f4e0c53a5d3f3a2810099bc6820' +
'ec50083434769b59fc24a417a9543734'
},
'SHA-512': {
normal: '4bbd6db2435fb696157f6089c977c3c7' +
'3f3eac5ef3dd6baae604cb53bfbb153e',
empty: '2f3157e7fe0c10b01298c8f0886a90ed' +
'cf80abdef5dbc1df2b1482532b52b934'
},
'SHA-1': {
normal: '05ad22ed2138c9600e4d9e2725ded301' +
'f5d287fbfb5702f999bc6536d3edef98',
empty: 'd51b6fb7e599ca30c5ee264593e4b85f' +
'2220c7c3ab003157bff8cb4f369c7560'
},
'SHA-256': {
normal: '2af5901e28849c28443857386aa1ac3b' +
'b127e92631c1c051482d6690941772b4',
empty: '9e4b719033742101e90f1ad61e2ff3b4' +
'256863667296d74389f1f02af2c4e6a6'
}
},
empty: {
'SHA-384': {
normal: 'fb482ff22c4f8d466c4dfe6e29f2cc2e' +
'cdabf5884328fbf08a738fd945f166cb',
empty: '1e023c17b340533ceaef39230cb8b3bb' +
'dbf663a13d6075d0dd326c049478fba5'
},
'SHA-512': {
normal: 'f17b5bdcd8d7d3d4601036a19436317d' +
'1644f9a4e0956efc0e372b83acdacfdb',
empty: 'c7b474942f31f83faf5d14731802b1bd' +
'49478549cb3a8f3dbfedc4d3209cf5b6'
},
'SHA-1': {
normal: 'c126f1e6f25a9de42cf7d427059a52ed' +
'9601f29a5815cbfbc64bc7f668c6a341',
empty: '3215c3f08de70549b051b7033745a818' +
'4f8cbaa6b1735330d2bcb6b16f4642ef'
},
'SHA-256': {
normal: '733c8b6bcfac875c7f08982a6e3ffb56' +
'0acea6f165476eb83460b9353ed41dfe',
empty: 'c8e12774135305c9147f2cc4766e5ead' +
'25d8f457b9a1953d52677361ced558fb'
}
}
},
long: {
normal: {
'SHA-384': {
normal: 'f91571b521f7eef13e573aa46378659e' +
'f3b7f36ffdd1bb055db2cd77d260c467',
empty: '68af1c2cf6b9370d2054344798bdbb18' +
'47ccf407b7652b793dd136d4640e0348'
},
'SHA-512': {
normal: '710aae2fdf889e45fe0fb995b2c26b33' +
'eb988650ec0faef167028a7a6ccb3638',
empty: 'e5de568081c71e562750829871c34275' +
'8104765ed6f306f0613c9d4bb336f2aa'
},
'SHA-1': {
normal: '7f957edcbce3cb0b70566e1eb60efd1e' +
'405a13304c661d3663778109bf06899c',
empty: '3062f3cf1a730b9cef51f02c1dfac85e' +
'd91e4b0065eb50ca9fd8b0107e728733'
},
'SHA-256': {
normal: '31b7d68530a863e717c081ca6917b686' +
'50b3dd9a29f30606e2cad199bec14d13',
empty: 'e579d1f9e7f08e6f990ffcfcce1ed201' +
'c5e37e62cdf606f0ba4aca80427fbc44'
}
},
empty: {
'SHA-384': {
normal: '619eb6f9287395bbd5ed6a67c968465a' +
'd82b6c559f3c38b604bbb08f58320b03',
empty: 'ff447b423d83fe76836c32337228b56b' +
'5bd9bf68d58e7dca4b7cca842a45e11a'
},
'SHA-512': {
normal: '133e8a7f7ff433690cc88432c2a338c2' +
'77e5c13756ff878f46753fe6a564e3e5',
empty: 'de54f7eec80c9cc66d349fc987f80d46' +
'1db2ef4ff4e18505d28bd80cb42c7d76'
},
'SHA-1': {
normal: 'adb93cdbce79b7d51159b6c0131a2b62' +
'f23828d26acd685e34c06535e6f77496',
empty: '47710d2a7507e05a1ddcc87a7c2f9061' +
'77a266efb9e622510cccb3713cd08d58'
},
'SHA-256': {
normal: 'a401d7c9158a29e5c7193ab9730f0748' +
'851cc5baadb42cad024b6290fe213436',
empty: 'b4f7e7557674d501cbfbc0148ad800c0' +
'750189fe295a2aca5e1bf4122c85edf9'
}
}
},
};
async function setupBaseKeys() {
const promises = [];
const baseKeys = {};
const noBits = {};
const noKey = {};
let wrongKey = null;
Object.keys(kDerivedKeys).forEach((size) => {
const keyData = Buffer.from(kDerivedKeys[size], 'hex');
promises.push(
subtle.importKey(
'raw',
keyData,
{ name: 'HKDF' },
false,
['deriveKey', 'deriveBits'])
.then(
(baseKey) => baseKeys[size] = baseKey,
(err) => assert.ifError(err)));
promises.push(
subtle.importKey(
'raw',
keyData,
{ name: 'HKDF' },
false, ['deriveBits'])
.then(
(baseKey) => noKey[size] = baseKey,
(err) => assert.ifError(err)));
promises.push(
subtle.importKey(
'raw',
keyData,
{ name: 'HKDF' },
false, ['deriveKey'])
.then(
(baseKey) => noBits[size] = baseKey,
(err) => assert.ifError(err)));
});
promises.push(
subtle.generateKey(
{
name: 'ECDH',
namedCurve: 'P-521'
},
false,
['deriveKey', 'deriveBits'])
.then(
(baseKey) => wrongKey = baseKey.privateKey,
(err) => assert.ifError(err)));
await Promise.all(promises);
return {
baseKeys,
noBits,
noKey,
wrongKey
};
}
async function testDeriveBits(
baseKeys,
size,
saltSize,
hash,
infoSize) {
const algorithm = {
name: 'HKDF',
salt: Buffer.from(kSalts[saltSize], 'hex'),
info: Buffer.from(kInfos[infoSize], 'hex'),
hash
};
const bits = await subtle.deriveBits(
algorithm,
baseKeys[size],
256);
assert(bits instanceof ArrayBuffer);
assert.strictEqual(
Buffer.from(bits).toString('hex'),
kDerivations[size][saltSize][hash][infoSize]);
}
async function testDeriveBitsBadLengths(
baseKeys,
size,
saltSize,
hash,
infoSize) {
const algorithm = {
name: 'HKDF',
salt: Buffer.from(kSalts[saltSize], 'hex'),
info: Buffer.from(kInfos[infoSize], 'hex'),
hash
};
return Promise.all([
assert.rejects(
subtle.deriveBits(algorithm, baseKeys[size], undefined), {
name: 'OperationError',
}),
assert.rejects(
subtle.deriveBits(algorithm, baseKeys[size], 0), {
message: /length cannot be zero/,
name: 'OperationError',
}),
assert.rejects(
subtle.deriveBits(algorithm, baseKeys[size], null), {
message: 'length cannot be null',
name: 'OperationError',
}),
assert.rejects(
subtle.deriveBits(algorithm, baseKeys[size], 15), {
message: /length must be a multiple of 8/,
name: 'OperationError',
}),
]);
}
async function testDeriveBitsBadHash(
baseKeys,
size,
saltSize,
hash,
infoSize) {
const salt = Buffer.from(kSalts[saltSize], 'hex');
const info = Buffer.from(kInfos[infoSize], 'hex');
const algorithm = { name: 'HKDF', salt, info };
return Promise.all([
assert.rejects(
subtle.deriveBits(
{
...algorithm,
hash: hash.substring(0, 3) + hash.substring(4)
}, baseKeys[size], 256), {
message: /Unrecognized algorithm name/,
name: 'NotSupportedError',
}),
assert.rejects(
subtle.deriveBits(
{
...algorithm,
hash: 'PBKDF2'
},
baseKeys[size], 256), {
message: /Unrecognized algorithm name/,
name: 'NotSupportedError',
}),
]);
}
async function testDeriveBitsBadUsage(
noBits,
size,
saltSize,
hash,
infoSize) {
const algorithm = {
name: 'HKDF',
salt: Buffer.from(kSalts[saltSize], 'hex'),
info: Buffer.from(kInfos[infoSize], 'hex'),
hash
};
return assert.rejects(
subtle.deriveBits(algorithm, noBits[size], 256), {
message: /baseKey does not have deriveBits usage/
});
}
async function testDeriveBitsMissingSalt(
baseKeys,
size,
saltSize,
hash,
infoSize) {
const algorithm = {
name: 'HKDF',
info: Buffer.from(kInfos[infoSize], 'hex'),
hash
};
return assert.rejects(
subtle.deriveBits(algorithm, baseKeys[size], 0), {
code: 'ERR_MISSING_OPTION'
});
}
async function testDeriveBitsMissingInfo(
baseKeys,
size,
saltSize,
hash,
infoSize) {
const algorithm = {
name: 'HKDF',
salt: Buffer.from(kSalts[infoSize], 'hex'),
hash
};
return assert.rejects(
subtle.deriveBits(algorithm, baseKeys[size], 0), {
code: 'ERR_MISSING_OPTION'
});
}
async function testBitsWrongKeyType(
wrongKey,
saltSize,
hash,
infoSize) {
const algorithm = {
name: 'HKDF',
salt: Buffer.from(kSalts[saltSize], 'hex'),
info: Buffer.from(kInfos[infoSize], 'hex'),
hash
};
return assert.rejects(
subtle.deriveBits(algorithm, wrongKey, 256), {
message: /Key algorithm mismatch/
});
}
async function testDeriveKey(
baseKeys,
size,
saltSize,
hash,
infoSize,
keyType,
usages) {
const algorithm = {
name: 'HKDF',
salt: Buffer.from(kSalts[saltSize], 'hex'),
info: Buffer.from(kInfos[infoSize], 'hex'),
hash
};
const key = await subtle.deriveKey(
algorithm,
baseKeys[size],
keyType,
true,
usages);
const bits = await subtle.exportKey('raw', key);
assert.strictEqual(
Buffer.from(bits).toString('hex'),
kDerivations[size][saltSize][hash][infoSize].slice(0, keyType.length / 4));
}
async function testDeriveKeyBadHash(
baseKeys,
size,
saltSize,
hash,
infoSize,
keyType,
usages) {
const salt = Buffer.from(kSalts[saltSize], 'hex');
const info = Buffer.from(kInfos[infoSize], 'hex');
const algorithm = { name: 'HKDF', salt, info };
return Promise.all([
assert.rejects(
subtle.deriveKey(
{
...algorithm,
hash: hash.substring(0, 3) + hash.substring(4)
},
baseKeys[size],
keyType,
true,
usages),
{
message: /Unrecognized algorithm name/,
name: 'NotSupportedError',
}),
assert.rejects(
subtle.deriveKey(
{
...algorithm,
hash: 'PBKDF2'
},
baseKeys[size],
keyType,
true,
usages),
{
message: /Unrecognized algorithm name/,
name: 'NotSupportedError',
}),
]);
}
async function testDeriveKeyBadUsage(
noKey,
size,
saltSize,
hash,
infoSize,
keyType,
usages) {
const algorithm = {
name: 'HKDF',
salt: Buffer.from(kSalts[saltSize], 'hex'),
info: Buffer.from(kInfos[infoSize], 'hex'),
hash
};
return assert.rejects(
subtle.deriveKey(algorithm, noKey[size], keyType, true, usages), {
message: /baseKey does not have deriveKey usage/
});
}
async function testWrongKeyType(
wrongKey,
saltSize,
hash,
infoSize,
keyType,
usages
) {
const algorithm = {
name: 'HKDF',
salt: Buffer.from(kSalts[saltSize], 'hex'),
info: Buffer.from(kInfos[infoSize], 'hex'),
hash
};
return assert.rejects(
subtle.deriveKey(algorithm, wrongKey, keyType, true, usages), {
message: /Key algorithm mismatch/
});
}
(async function() {
const {
baseKeys,
noBits,
noKey,
wrongKey,
} = await setupBaseKeys();
const variations = [];
Object.keys(kDerivations).forEach((size) => {
Object.keys(kDerivations[size]).forEach((saltSize) => {
Object.keys(kDerivations[size][saltSize]).forEach((hash) => {
Object.keys(kDerivations[size][saltSize][hash]).forEach(
async (infoSize) => {
const args = [baseKeys, size, saltSize, hash, infoSize];
variations.push(testDeriveBits(...args));
variations.push(testDeriveBitsBadLengths(...args));
variations.push(testDeriveBitsMissingSalt(...args));
variations.push(testDeriveBitsMissingInfo(...args));
variations.push(testDeriveBitsBadHash(...args));
variations.push(
testDeriveBitsBadUsage(
noBits,
size,
saltSize,
hash,
infoSize));
variations.push(
testBitsWrongKeyType(
wrongKey,
saltSize,
hash,
infoSize));
kDerivedKeyTypes.forEach((keyType) => {
const keyArgs = getDeriveKeyInfo(...keyType);
variations.push(testDeriveKey(...args, ...keyArgs));
variations.push(testDeriveKeyBadHash(...args, ...keyArgs));
variations.push(testDeriveKeyBadUsage(
noKey, size, saltSize, hash, infoSize, ...keyArgs));
variations.push(
testWrongKeyType(
wrongKey,
saltSize,
hash,
infoSize,
...keyArgs));
});
});
});
});
});
await Promise.all(variations);
})().then(common.mustCall());