node/test/parallel/test-crypto-key-objects-to-crypto-key.js
Filip Skokan 90e3e5e173
crypto: add KeyObject.prototype.toCryptoKey
PR-URL: https://github.com/nodejs/node/pull/55262
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
2024-10-06 18:09:02 +00:00

183 lines
5.9 KiB
JavaScript

'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const {
createSecretKey,
KeyObject,
randomBytes,
generateKeyPairSync,
} = require('crypto');
function assertCryptoKey(cryptoKey, keyObject, algorithm, extractable, usages) {
assert.strictEqual(cryptoKey instanceof CryptoKey, true);
assert.strictEqual(cryptoKey.type, keyObject.type);
assert.strictEqual(cryptoKey.algorithm.name, algorithm);
assert.strictEqual(cryptoKey.extractable, extractable);
assert.deepStrictEqual(cryptoKey.usages, usages);
assert.strictEqual(keyObject.equals(KeyObject.from(cryptoKey)), true);
}
{
for (const length of [128, 192, 256]) {
const aes = createSecretKey(randomBytes(length >> 3));
for (const algorithm of ['AES-CTR', 'AES-CBC', 'AES-GCM', 'AES-KW']) {
const usages = algorithm === 'AES-KW' ? ['wrapKey', 'unwrapKey'] : ['encrypt', 'decrypt'];
for (const extractable of [true, false]) {
const cryptoKey = aes.toCryptoKey(algorithm, extractable, usages);
assertCryptoKey(cryptoKey, aes, algorithm, extractable, usages);
assert.strictEqual(cryptoKey.algorithm.length, length);
}
}
}
}
{
const pbkdf2 = createSecretKey(randomBytes(16));
const algorithm = 'PBKDF2';
const usages = ['deriveBits'];
assert.throws(() => pbkdf2.toCryptoKey(algorithm, true, usages), {
name: 'SyntaxError',
message: 'PBKDF2 keys are not extractable'
});
assert.throws(() => pbkdf2.toCryptoKey(algorithm, false, ['wrapKey']), {
name: 'SyntaxError',
message: 'Unsupported key usage for a PBKDF2 key'
});
const cryptoKey = pbkdf2.toCryptoKey(algorithm, false, usages);
assertCryptoKey(cryptoKey, pbkdf2, algorithm, false, usages);
assert.strictEqual(cryptoKey.algorithm.length, undefined);
}
{
for (const length of [128, 192, 256]) {
const hmac = createSecretKey(randomBytes(length >> 3));
const algorithm = 'HMAC';
const usages = ['sign', 'verify'];
assert.throws(() => {
createSecretKey(Buffer.alloc(0)).toCryptoKey({ name: algorithm, hash: 'SHA-256' }, true, usages);
}, {
name: 'DataError',
message: 'Zero-length key is not supported',
});
assert.throws(() => {
hmac.toCryptoKey({
name: algorithm,
hash: 'SHA-256',
}, true, []);
}, {
name: 'SyntaxError',
message: 'Usages cannot be empty when importing a secret key.'
});
for (const hash of ['SHA-1', 'SHA-256', 'SHA-384', 'SHA-512']) {
for (const extractable of [true, false]) {
assert.throws(() => {
hmac.toCryptoKey({ name: algorithm, hash: 'SHA-256', length: 0 }, true, usages);
}, {
name: 'DataError',
message: 'Zero-length key is not supported',
});
const cryptoKey = hmac.toCryptoKey({ name: algorithm, hash }, extractable, usages);
assertCryptoKey(cryptoKey, hmac, algorithm, extractable, usages);
assert.strictEqual(cryptoKey.algorithm.length, length);
}
}
}
}
{
for (const algorithm of ['Ed25519', 'Ed448', 'X25519', 'X448']) {
const { publicKey, privateKey } = generateKeyPairSync(algorithm.toLowerCase());
assert.throws(() => {
publicKey.toCryptoKey(algorithm === 'Ed25519' ? 'X25519' : 'Ed25519', true, []);
}, {
name: 'DataError',
message: 'Invalid key type'
});
for (const key of [publicKey, privateKey]) {
let usages;
if (algorithm.startsWith('E')) {
usages = key.type === 'public' ? ['verify'] : ['sign'];
} else {
usages = key.type === 'public' ? [] : ['deriveBits'];
}
for (const extractable of [true, false]) {
const cryptoKey = key.toCryptoKey(algorithm, extractable, usages);
assertCryptoKey(cryptoKey, key, algorithm, extractable, usages);
}
}
}
}
{
const { publicKey, privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 });
for (const key of [publicKey, privateKey]) {
for (const algorithm of ['RSASSA-PKCS1-v1_5', 'RSA-PSS', 'RSA-OAEP']) {
let usages;
if (algorithm === 'RSA-OAEP') {
usages = key.type === 'public' ? ['encrypt', 'wrapKey'] : ['decrypt', 'unwrapKey'];
} else {
usages = key.type === 'public' ? ['verify'] : ['sign'];
}
for (const extractable of [true, false]) {
for (const hash of ['SHA-1', 'SHA-256', 'SHA-384', 'SHA-512']) {
const cryptoKey = key.toCryptoKey({
name: algorithm,
hash
}, extractable, usages);
assertCryptoKey(cryptoKey, key, algorithm, extractable, usages);
assert.strictEqual(cryptoKey.algorithm.hash.name, hash);
}
}
}
}
}
{
for (const namedCurve of ['P-256', 'P-384', 'P-521']) {
const { publicKey, privateKey } = generateKeyPairSync('ec', { namedCurve });
assert.throws(() => {
privateKey.toCryptoKey({
name: 'ECDH',
namedCurve,
}, true, []);
}, {
name: 'SyntaxError',
message: 'Usages cannot be empty when importing a private key.'
});
assert.throws(() => {
publicKey.toCryptoKey({
name: 'ECDH',
namedCurve: namedCurve === 'P-256' ? 'P-384' : 'P-256'
}, true, []);
}, {
name: 'DataError',
message: 'Named curve mismatch'
});
for (const key of [publicKey, privateKey]) {
for (const algorithm of ['ECDH', 'ECDSA']) {
let usages;
if (algorithm === 'ECDH') {
usages = key.type === 'public' ? [] : ['deriveBits'];
} else {
usages = key.type === 'public' ? ['verify'] : ['sign'];
}
for (const extractable of [true, false]) {
const cryptoKey = key.toCryptoKey({
name: algorithm,
namedCurve
}, extractable, usages);
assertCryptoKey(cryptoKey, key, algorithm, extractable, usages);
assert.strictEqual(cryptoKey.algorithm.namedCurve, namedCurve);
}
}
}
}
}