node/test/parallel/test-webcrypto-keygen.js
Filip Skokan 13f518f6e3
crypto: add CryptoKey Symbol.toStringTag
closes #45987

PR-URL: https://github.com/nodejs/node/pull/46042
Fixes: https://github.com/nodejs/node/issues/45987
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Juan José Arboleda <soyjuanarbol@gmail.com>
2023-01-18 18:07:26 +00:00

666 lines
16 KiB
JavaScript

// Flags: --expose-internals
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const { types: { isCryptoKey } } = require('util');
const {
createSecretKey,
KeyObject,
} = require('crypto');
const { subtle } = globalThis.crypto;
const { bigIntArrayToUnsignedBigInt } = require('internal/crypto/util');
const allUsages = [
'encrypt',
'decrypt',
'sign',
'verify',
'deriveBits',
'deriveKey',
'wrapKey',
'unwrapKey',
];
const vectors = {
'AES-CTR': {
algorithm: { length: 256 },
result: 'CryptoKey',
usages: [
'encrypt',
'decrypt',
'wrapKey',
'unwrapKey',
],
},
'AES-CBC': {
algorithm: { length: 256 },
result: 'CryptoKey',
usages: [
'encrypt',
'decrypt',
'wrapKey',
'unwrapKey',
],
},
'AES-GCM': {
algorithm: { length: 256 },
result: 'CryptoKey',
usages: [
'encrypt',
'decrypt',
'wrapKey',
'unwrapKey',
],
},
'AES-KW': {
algorithm: { length: 256 },
result: 'CryptoKey',
usages: [
'wrapKey',
'unwrapKey',
],
},
'HMAC': {
algorithm: { length: 256, hash: 'SHA-256' },
result: 'CryptoKey',
usages: [
'sign',
'verify',
],
},
'RSASSA-PKCS1-v1_5': {
algorithm: {
modulusLength: 1024,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256'
},
result: 'CryptoKeyPair',
usages: [
'sign',
'verify',
],
},
'RSA-PSS': {
algorithm: {
modulusLength: 1024,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256'
},
result: 'CryptoKeyPair',
usages: [
'sign',
'verify',
],
},
'RSA-OAEP': {
algorithm: {
modulusLength: 1024,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256'
},
result: 'CryptoKeyPair',
usages: [
'encrypt',
'decrypt',
'wrapKey',
'unwrapKey',
],
},
'ECDSA': {
algorithm: { namedCurve: 'P-521' },
result: 'CryptoKeyPair',
usages: [
'sign',
'verify',
],
},
'ECDH': {
algorithm: { namedCurve: 'P-521' },
result: 'CryptoKeyPair',
usages: [
'deriveKey',
'deriveBits',
],
},
'Ed25519': {
result: 'CryptoKeyPair',
usages: [
'sign',
'verify',
],
},
'Ed448': {
result: 'CryptoKeyPair',
usages: [
'sign',
'verify',
],
},
'X25519': {
result: 'CryptoKeyPair',
usages: [
'deriveKey',
'deriveBits',
],
},
'X448': {
result: 'CryptoKeyPair',
usages: [
'deriveKey',
'deriveBits',
],
},
};
// Test invalid algorithms
{
async function test(algorithm) {
return assert.rejects(
// The extractable and usages values are invalid here also,
// but the unrecognized algorithm name should be caught first.
subtle.generateKey(algorithm, 7, []), {
message: /Unrecognized algorithm name/,
name: 'NotSupportedError',
});
}
const tests = [
'AES',
{ name: 'AES' },
{ name: 'AES-CMAC' },
{ name: 'AES-CFB' },
{ name: 'HMAC', hash: 'MD5' },
{
name: 'RSA',
hash: 'SHA-256',
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1])
},
{
name: 'RSA-PSS',
hash: 'SHA',
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1])
},
{
name: 'EC',
namedCurve: 'P521'
},
].map(async (algorithm) => test(algorithm));
Promise.all(tests).then(common.mustCall());
}
// Test bad usages
{
async function test(name) {
await assert.rejects(
subtle.generateKey(
{
name, ...vectors[name].algorithm
},
true,
[]),
{ message: /Usages cannot be empty/ });
// For CryptoKeyPair results the private key
// usages must not be empty.
// - ECDH(-like) algorithm key pairs only have private key usages
// - Signing algorithm key pairs may pass a non-empty array but
// with only a public key usage
if (
vectors[name].result === 'CryptoKeyPair' &&
vectors[name].usages.includes('verify')
) {
await assert.rejects(
subtle.generateKey(
{
name, ...vectors[name].algorithm
},
true,
['verify']),
{ message: /Usages cannot be empty/ });
}
const invalidUsages = [];
allUsages.forEach((usage) => {
if (!vectors[name].usages.includes(usage))
invalidUsages.push(usage);
});
for (const invalidUsage of invalidUsages) {
await assert.rejects(
subtle.generateKey(
{
name, ...vectors[name].algorithm
},
true,
[...vectors[name].usages, invalidUsage]),
{ message: /Unsupported key usage/ });
}
}
const tests = Object.keys(vectors).map(test);
Promise.all(tests).then(common.mustCall());
}
// Test RSA key generation
{
async function test(
name,
modulusLength,
publicExponent,
hash,
privateUsages,
publicUsages = privateUsages) {
let usages = privateUsages;
if (publicUsages !== privateUsages)
usages = usages.concat(publicUsages);
const { publicKey, privateKey } = await subtle.generateKey({
name,
modulusLength,
publicExponent,
hash
}, true, usages);
assert(publicKey);
assert(privateKey);
assert(isCryptoKey(publicKey));
assert(isCryptoKey(privateKey));
assert(publicKey instanceof CryptoKey);
assert(privateKey instanceof CryptoKey);
assert.strictEqual(publicKey.type, 'public');
assert.strictEqual(privateKey.type, 'private');
assert.strictEqual(publicKey.toString(), '[object CryptoKey]');
assert.strictEqual(privateKey.toString(), '[object CryptoKey]');
assert.strictEqual(publicKey.extractable, true);
assert.strictEqual(privateKey.extractable, true);
assert.deepStrictEqual(publicKey.usages, publicUsages);
assert.deepStrictEqual(privateKey.usages, privateUsages);
assert.strictEqual(publicKey.algorithm.name, name);
assert.strictEqual(publicKey.algorithm.modulusLength, modulusLength);
assert.deepStrictEqual(publicKey.algorithm.publicExponent, publicExponent);
assert.strictEqual(
KeyObject.from(publicKey).asymmetricKeyDetails.publicExponent,
bigIntArrayToUnsignedBigInt(publicExponent));
assert.strictEqual(publicKey.algorithm.hash.name, hash);
assert.strictEqual(privateKey.algorithm.name, name);
assert.strictEqual(privateKey.algorithm.modulusLength, modulusLength);
assert.deepStrictEqual(privateKey.algorithm.publicExponent, publicExponent);
assert.strictEqual(
KeyObject.from(privateKey).asymmetricKeyDetails.publicExponent,
bigIntArrayToUnsignedBigInt(publicExponent));
assert.strictEqual(privateKey.algorithm.hash.name, hash);
// Missing parameters
await assert.rejects(
subtle.generateKey({ name, publicExponent, hash }, true, usages), {
code: 'ERR_MISSING_OPTION'
});
await assert.rejects(
subtle.generateKey({ name, modulusLength, hash }, true, usages), {
code: 'ERR_MISSING_OPTION'
});
await assert.rejects(
subtle.generateKey({ name, modulusLength }, true, usages), {
code: 'ERR_MISSING_OPTION'
});
await Promise.all([{}].map((modulusLength) => {
return assert.rejects(subtle.generateKey({
name,
modulusLength,
publicExponent,
hash
}, true, usages), {
code: 'ERR_INVALID_ARG_TYPE'
});
}));
await Promise.all(
[
'',
true,
{},
1,
[],
new Uint32Array(2),
].map((publicExponent) => {
return assert.rejects(
subtle.generateKey(
{ name, modulusLength, publicExponent, hash }, true, usages),
{ code: 'ERR_INVALID_ARG_TYPE' });
}));
await Promise.all([true, 1].map((hash) => {
return assert.rejects(subtle.generateKey({
name,
modulusLength,
publicExponent,
hash
}, true, usages), {
message: /Unrecognized algorithm name/,
name: 'NotSupportedError',
});
}));
await Promise.all(['', {}, 1, false].map((usages) => {
return assert.rejects(subtle.generateKey({
name,
modulusLength,
publicExponent,
hash
}, true, usages), {
code: 'ERR_INVALID_ARG_TYPE'
});
}));
await Promise.all([[1], [1, 0, 0]].map((publicExponent) => {
return assert.rejects(subtle.generateKey({
name,
modulusLength,
publicExponent: new Uint8Array(publicExponent),
hash
}, true, usages), {
name: 'OperationError',
});
}));
}
const kTests = [
[
'RSASSA-PKCS1-v1_5',
1024,
Buffer.from([1, 0, 1]),
'SHA-256',
['sign'],
['verify'],
],
[
'RSA-PSS',
2048,
Buffer.from([1, 0, 1]),
'SHA-512',
['sign'],
['verify'],
],
[
'RSA-OAEP',
1024,
Buffer.from([3]),
'SHA-384',
['decrypt', 'unwrapKey'],
['encrypt', 'wrapKey'],
],
];
const tests = kTests.map((args) => test(...args));
Promise.all(tests).then(common.mustCall());
}
// Test EC Key Generation
{
async function test(
name,
namedCurve,
privateUsages,
publicUsages = privateUsages) {
let usages = privateUsages;
if (publicUsages !== privateUsages)
usages = usages.concat(publicUsages);
const { publicKey, privateKey } = await subtle.generateKey({
name,
namedCurve
}, true, usages);
assert(publicKey);
assert(privateKey);
assert(isCryptoKey(publicKey));
assert(isCryptoKey(privateKey));
assert.strictEqual(publicKey.type, 'public');
assert.strictEqual(privateKey.type, 'private');
assert.strictEqual(publicKey.toString(), '[object CryptoKey]');
assert.strictEqual(privateKey.toString(), '[object CryptoKey]');
assert.strictEqual(publicKey.extractable, true);
assert.strictEqual(privateKey.extractable, true);
assert.deepStrictEqual(publicKey.usages, publicUsages);
assert.deepStrictEqual(privateKey.usages, privateUsages);
assert.strictEqual(publicKey.algorithm.name, name);
assert.strictEqual(privateKey.algorithm.name, name);
assert.strictEqual(publicKey.algorithm.namedCurve, namedCurve);
assert.strictEqual(privateKey.algorithm.namedCurve, namedCurve);
// Invalid parameters
[1, true, {}, [], null].forEach(async (namedCurve) => {
await assert.rejects(
subtle.generateKey({ name, namedCurve }, true, privateUsages), {
name: 'NotSupportedError'
});
});
await assert.rejects(
subtle.generateKey({ name, namedCurve: undefined }, true, privateUsages), {
name: 'TypeError',
code: 'ERR_MISSING_OPTION'
});
}
const kTests = [
[
'ECDSA',
'P-384',
['sign'],
['verify'],
],
[
'ECDSA',
'P-521',
['sign'],
['verify'],
],
[
'ECDH',
'P-384',
['deriveKey', 'deriveBits'],
[],
],
[
'ECDH',
'P-521',
['deriveKey', 'deriveBits'],
[],
],
];
const tests = kTests.map((args) => test(...args));
// Test bad parameters
Promise.all(tests).then(common.mustCall());
}
// Test AES Key Generation
{
async function test(name, length, usages) {
const key = await subtle.generateKey({
name,
length
}, true, usages);
assert(key);
assert(isCryptoKey(key));
assert.strictEqual(key.type, 'secret');
assert.strictEqual(key.toString(), '[object CryptoKey]');
assert.strictEqual(key.extractable, true);
assert.deepStrictEqual(key.usages, usages);
assert.strictEqual(key.algorithm.name, name);
assert.strictEqual(key.algorithm.length, length);
// Invalid parameters
[1, 100, 257, '', false, null].forEach(async (length) => {
await assert.rejects(
subtle.generateKey({ name, length }, true, usages), {
name: 'OperationError'
});
});
await assert.rejects(
subtle.generateKey({ name, length: undefined }, true, usages), {
name: 'TypeError',
code: 'ERR_MISSING_OPTION'
});
}
const kTests = [
[ 'AES-CTR', 128, ['encrypt', 'decrypt', 'wrapKey']],
[ 'AES-CTR', 256, ['encrypt', 'decrypt', 'unwrapKey']],
[ 'AES-CBC', 128, ['encrypt', 'decrypt']],
[ 'AES-CBC', 256, ['encrypt', 'decrypt']],
[ 'AES-GCM', 128, ['encrypt', 'decrypt']],
[ 'AES-GCM', 256, ['encrypt', 'decrypt']],
[ 'AES-KW', 128, ['wrapKey', 'unwrapKey']],
[ 'AES-KW', 256, ['wrapKey', 'unwrapKey']],
];
const tests = Promise.all(kTests.map((args) => test(...args)));
tests.then(common.mustCall());
}
// Test HMAC Key Generation
{
async function test(length, hash, usages) {
const key = await subtle.generateKey({
name: 'HMAC',
length,
hash
}, true, usages);
if (length === undefined) {
switch (hash) {
case 'SHA-1': length = 512; break;
case 'SHA-256': length = 512; break;
case 'SHA-384': length = 1024; break;
case 'SHA-512': length = 1024; break;
}
}
assert(key);
assert(isCryptoKey(key));
assert.strictEqual(key.type, 'secret');
assert.strictEqual(key.toString(), '[object CryptoKey]');
assert.strictEqual(key.extractable, true);
assert.deepStrictEqual(key.usages, usages);
assert.strictEqual(key.algorithm.name, 'HMAC');
assert.strictEqual(key.algorithm.length, length);
assert.strictEqual(key.algorithm.hash.name, hash);
[1, false, null].forEach(async (hash) => {
await assert.rejects(
subtle.generateKey({ name: 'HMAC', length, hash }, true, usages), {
message: /Unrecognized algorithm name/,
name: 'NotSupportedError',
});
});
}
const kTests = [
[ undefined, 'SHA-1', ['sign', 'verify']],
[ undefined, 'SHA-256', ['sign', 'verify']],
[ undefined, 'SHA-384', ['sign', 'verify']],
[ undefined, 'SHA-512', ['sign', 'verify']],
[ 128, 'SHA-256', ['sign', 'verify']],
[ 1024, 'SHA-512', ['sign', 'verify']],
];
const tests = Promise.all(kTests.map((args) => test(...args)));
tests.then(common.mustCall());
}
// End user code cannot create CryptoKey directly
assert.throws(() => new CryptoKey(), { code: 'ERR_ILLEGAL_CONSTRUCTOR' });
{
const buffer = Buffer.from('Hello World');
const keyObject = createSecretKey(buffer);
assert(!isCryptoKey(buffer));
assert(!isCryptoKey(keyObject));
}
// Test OKP Key Generation
{
async function test(
name,
privateUsages,
publicUsages = privateUsages) {
let usages = privateUsages;
if (publicUsages !== privateUsages)
usages = usages.concat(publicUsages);
const { publicKey, privateKey } = await subtle.generateKey({
name,
}, true, usages);
assert(publicKey);
assert(privateKey);
assert(isCryptoKey(publicKey));
assert(isCryptoKey(privateKey));
assert.strictEqual(publicKey.type, 'public');
assert.strictEqual(privateKey.type, 'private');
assert.strictEqual(publicKey.toString(), '[object CryptoKey]');
assert.strictEqual(privateKey.toString(), '[object CryptoKey]');
assert.strictEqual(publicKey.extractable, true);
assert.strictEqual(privateKey.extractable, true);
assert.deepStrictEqual(publicKey.usages, publicUsages);
assert.deepStrictEqual(privateKey.usages, privateUsages);
assert.strictEqual(publicKey.algorithm.name, name);
assert.strictEqual(privateKey.algorithm.name, name);
}
const kTests = [
[
'Ed25519',
['sign'],
['verify'],
],
[
'Ed448',
['sign'],
['verify'],
],
[
'X25519',
['deriveKey', 'deriveBits'],
[],
],
[
'X448',
['deriveKey', 'deriveBits'],
[],
],
];
const tests = kTests.map((args) => test(...args));
// Test bad parameters
Promise.all(tests).then(common.mustCall());
}