node/test/parallel/test-webcrypto-export-import.js
Filip Skokan 14c68e3b53
crypto: add KMAC Web Cryptography algorithms
PR-URL: https://github.com/nodejs/node/pull/59647
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: James M Snell <jasnell@gmail.com>
2025-09-06 22:43:15 +00:00

438 lines
12 KiB
JavaScript

'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
if (!common.hasCrypto)
common.skip('missing crypto');
const { hasOpenSSL } = require('../common/crypto');
const assert = require('assert');
const { subtle } = globalThis.crypto;
const { createPrivateKey, createPublicKey, createSecretKey } = require('crypto');
{
async function test() {
const keyData = globalThis.crypto.getRandomValues(new Uint8Array(32));
await Promise.all([1, null, undefined, {}, []].map((format) =>
assert.rejects(
subtle.importKey(format, keyData, {}, false, ['wrapKey']), {
code: 'ERR_INVALID_ARG_VALUE'
})
));
await assert.rejects(
subtle.importKey('not valid', keyData, {}, false, ['wrapKey']), {
code: 'ERR_INVALID_ARG_VALUE'
});
await assert.rejects(
subtle.importKey('KeyObject', keyData, {}, false, ['wrapKey']), {
message: /'KeyObject' is not a valid enum value of type KeyFormat/,
code: 'ERR_INVALID_ARG_VALUE'
});
await assert.rejects(
subtle.importKey('raw', 1, {}, false, ['deriveBits']), {
code: 'ERR_INVALID_ARG_TYPE'
});
await assert.rejects(
subtle.importKey('raw', keyData, {
name: 'HMAC'
}, false, ['sign', 'verify']), {
code: 'ERR_MISSING_OPTION'
});
await assert.rejects(
subtle.importKey('raw', keyData, {
name: 'HMAC',
hash: 'SHA-256',
length: 384,
}, false, ['sign', 'verify']), {
name: 'DataError',
message: 'Invalid key length'
});
await assert.rejects(
subtle.importKey('raw', keyData, {
name: 'HMAC',
hash: 'SHA-256'
}, false, ['deriveBits']), {
name: 'SyntaxError',
message: 'Unsupported key usage for HMAC key'
});
await assert.rejects(
subtle.importKey('raw', keyData, {
name: 'HMAC',
hash: 'SHA-256',
length: 0
}, false, ['sign', 'verify']), {
name: 'DataError',
message: 'HmacImportParams.length cannot be 0'
});
await assert.rejects(
subtle.importKey('raw', keyData, {
name: 'HMAC',
hash: 'SHA-256',
length: 1
}, false, ['sign', 'verify']), {
name: 'NotSupportedError',
message: 'Unsupported HmacImportParams.length'
});
await assert.rejects(
subtle.importKey('jwk', null, {
name: 'HMAC',
hash: 'SHA-256',
}, false, ['sign', 'verify']), {
name: 'DataError',
message: 'Invalid keyData'
});
}
test().then(common.mustCall());
}
// Import/Export HMAC Secret Key
{
async function test() {
const keyData = globalThis.crypto.getRandomValues(new Uint8Array(32));
const key = await subtle.importKey(
'raw',
keyData, {
name: 'HMAC',
hash: 'SHA-256'
}, true, ['sign', 'verify']);
assert.strictEqual(key.algorithm, key.algorithm);
assert.strictEqual(key.usages, key.usages);
const raw = await subtle.exportKey('raw', key);
assert.deepStrictEqual(
Buffer.from(keyData).toString('hex'),
Buffer.from(raw).toString('hex'));
const jwk = await subtle.exportKey('jwk', key);
assert.deepStrictEqual(jwk.key_ops, ['sign', 'verify']);
assert(jwk.ext);
assert.strictEqual(jwk.kty, 'oct');
assert.strictEqual(jwk.alg, 'HS256');
assert.deepStrictEqual(
Buffer.from(jwk.k, 'base64').toString('hex'),
Buffer.from(raw).toString('hex'));
await subtle.importKey(
'jwk',
jwk,
{
name: 'HMAC',
hash: 'SHA-256'
},
true,
['sign', 'verify']);
await subtle.importKey(
'jwk',
{ ...jwk, alg: undefined },
{
name: 'HMAC',
hash: 'SHA-256'
},
true,
['sign', 'verify']);
await assert.rejects(
subtle.importKey(
'jwk',
{ ...jwk, alg: 'HS384' },
{
name: 'HMAC',
hash: 'SHA-256'
},
true,
['sign', 'verify']),
{ name: 'DataError', message: 'JWK "alg" does not match the requested algorithm' });
await assert.rejects(
subtle.importKey(
'raw',
keyData,
{
name: 'HMAC',
hash: 'SHA-256'
},
true,
[/* empty usages */]),
{ name: 'SyntaxError', message: 'Usages cannot be empty when importing a secret key.' });
}
test().then(common.mustCall());
}
// Import/Export KMAC Secret Key
if (hasOpenSSL(3)) {
async function test(name) {
const keyData = globalThis.crypto.getRandomValues(new Uint8Array(32));
const key = await subtle.importKey(
'raw-secret',
keyData, name, true, ['sign', 'verify']);
assert.strictEqual(key.algorithm, key.algorithm);
assert.strictEqual(key.usages, key.usages);
const raw = await subtle.exportKey('raw-secret', key);
assert.deepStrictEqual(
Buffer.from(keyData).toString('hex'),
Buffer.from(raw).toString('hex'));
const jwk = await subtle.exportKey('jwk', key);
assert.deepStrictEqual(jwk.key_ops, ['sign', 'verify']);
assert(jwk.ext);
assert.strictEqual(jwk.kty, 'oct');
assert.strictEqual(jwk.alg, `K${name.substring(4)}`);
assert.deepStrictEqual(
Buffer.from(jwk.k, 'base64').toString('hex'),
Buffer.from(raw).toString('hex'));
await subtle.importKey(
'jwk',
jwk,
name,
true,
['sign', 'verify']);
await subtle.importKey(
'jwk',
{ ...jwk, alg: undefined },
name,
true,
['sign', 'verify']);
await assert.rejects(
subtle.importKey(
'jwk',
{ ...jwk, alg: name === 'KMAC128' ? 'K256' : 'K128' },
name,
true,
['sign', 'verify']),
{ name: 'DataError', message: 'JWK "alg" does not match the requested algorithm' });
await assert.rejects(
subtle.importKey(
'raw',
keyData, name, true, ['sign', 'verify']),
{ name: 'NotSupportedError', message: `Unable to import ${name} using raw format` });
await assert.rejects(
subtle.importKey(
'raw-secret',
keyData,
name,
true,
[/* empty usages */]),
{ name: 'SyntaxError', message: 'Usages cannot be empty when importing a secret key.' });
}
test('KMAC128').then(common.mustCall());
test('KMAC256').then(common.mustCall());
}
// Import/Export AES Secret Key
{
async function test() {
const keyData = globalThis.crypto.getRandomValues(new Uint8Array(32));
const key = await subtle.importKey(
'raw',
keyData, {
name: 'AES-CTR',
length: 256,
}, true, ['encrypt', 'decrypt']);
assert.strictEqual(key.algorithm, key.algorithm);
assert.strictEqual(key.usages, key.usages);
const raw = await subtle.exportKey('raw', key);
assert.deepStrictEqual(
Buffer.from(keyData).toString('hex'),
Buffer.from(raw).toString('hex'));
const jwk = await subtle.exportKey('jwk', key);
assert.deepStrictEqual(jwk.key_ops, ['encrypt', 'decrypt']);
assert(jwk.ext);
assert.strictEqual(jwk.kty, 'oct');
assert.deepStrictEqual(
Buffer.from(jwk.k, 'base64').toString('hex'),
Buffer.from(raw).toString('hex'));
await assert.rejects(
subtle.importKey(
'raw',
keyData,
{
name: 'AES-CTR',
length: 256,
},
true,
[/* empty usages */]),
{ name: 'SyntaxError', message: 'Usages cannot be empty when importing a secret key.' });
}
test().then(common.mustCall());
}
// Import/Export RSA Key Pairs
{
async function test() {
const { publicKey, privateKey } = await subtle.generateKey({
name: 'RSA-PSS',
modulusLength: 1024,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-384'
}, true, ['sign', 'verify']);
const [
spki,
pkcs8,
publicJwk,
privateJwk,
] = await Promise.all([
subtle.exportKey('spki', publicKey),
subtle.exportKey('pkcs8', privateKey),
subtle.exportKey('jwk', publicKey),
subtle.exportKey('jwk', privateKey),
]);
assert(spki);
assert(pkcs8);
assert(publicJwk);
assert(privateJwk);
const [
importedSpkiPublicKey,
importedPkcs8PrivateKey,
importedJwkPublicKey,
importedJwkPrivateKey,
] = await Promise.all([
subtle.importKey('spki', spki, {
name: 'RSA-PSS',
hash: 'SHA-384',
}, true, ['verify']),
subtle.importKey('pkcs8', pkcs8, {
name: 'RSA-PSS',
hash: 'SHA-384',
}, true, ['sign']),
subtle.importKey('jwk', publicJwk, {
name: 'RSA-PSS',
hash: 'SHA-384',
}, true, ['verify']),
subtle.importKey('jwk', privateJwk, {
name: 'RSA-PSS',
hash: 'SHA-384',
}, true, ['sign']),
]);
assert(importedSpkiPublicKey);
assert(importedPkcs8PrivateKey);
assert(importedJwkPublicKey);
assert(importedJwkPrivateKey);
}
test().then(common.mustCall());
}
// Import/Export EC Key Pairs
{
async function test() {
const { publicKey, privateKey } = await subtle.generateKey({
name: 'ECDSA',
namedCurve: 'P-384'
}, true, ['sign', 'verify']);
const [
spki,
pkcs8,
publicJwk,
privateJwk,
] = await Promise.all([
subtle.exportKey('spki', publicKey),
subtle.exportKey('pkcs8', privateKey),
subtle.exportKey('jwk', publicKey),
subtle.exportKey('jwk', privateKey),
]);
assert(spki);
assert(pkcs8);
assert(publicJwk);
assert(privateJwk);
const [
importedSpkiPublicKey,
importedPkcs8PrivateKey,
importedJwkPublicKey,
importedJwkPrivateKey,
] = await Promise.all([
subtle.importKey('spki', spki, {
name: 'ECDSA',
namedCurve: 'P-384'
}, true, ['verify']),
subtle.importKey('pkcs8', pkcs8, {
name: 'ECDSA',
namedCurve: 'P-384'
}, true, ['sign']),
subtle.importKey('jwk', publicJwk, {
name: 'ECDSA',
namedCurve: 'P-384'
}, true, ['verify']),
subtle.importKey('jwk', privateJwk, {
name: 'ECDSA',
namedCurve: 'P-384'
}, true, ['sign']),
]);
assert(importedSpkiPublicKey);
assert(importedPkcs8PrivateKey);
assert(importedJwkPublicKey);
assert(importedJwkPrivateKey);
}
test().then(common.mustCall());
}
// SHA-3 hashes and JWK "alg"
if (!process.features.openssl_is_boringssl) {
const rsa = fixtures.readKey('rsa_private_2048.pem');
const privateKey = createPrivateKey(rsa);
const publicKey = createPublicKey(privateKey);
async function test(keyObject, algorithm, usages) {
const key = keyObject.toCryptoKey(algorithm, true, usages);
const jwk = await subtle.exportKey('jwk', key);
assert.strictEqual(jwk.alg, undefined);
}
for (const hash of ['SHA3-256', 'SHA3-384', 'SHA3-512']) {
for (const name of ['RSA-OAEP', 'RSA-PSS', 'RSASSA-PKCS1-v1_5']) {
test(publicKey, { name, hash }, []).then(common.mustCall());
test(privateKey, { name, hash }, [name === 'RSA-OAEP' ? 'unwrapKey' : 'sign']).then(common.mustCall());
}
test(createSecretKey(Buffer.alloc(32)), { name: 'HMAC', hash }, ['sign']);
}
{
const jwk = createSecretKey(Buffer.alloc(16)).export({ format: 'jwk' });
// This is rejected for SHA-2 but ignored for SHA-3
// Otherwise, if the name attribute of hash is defined in another applicable specification:
// Perform any key import steps defined by other applicable specifications, passing format,
// jwk and hash and obtaining hash.
jwk.alg = 'HS3-256';
assert.rejects(subtle.importKey('jwk', jwk, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign', 'verify']), {
name: 'DataError',
message: 'JWK "alg" does not match the requested algorithm',
}).then(common.mustCall());
subtle.importKey('jwk', jwk, { name: 'HMAC', hash: 'SHA3-256' }, false, ['sign', 'verify']).then(common.mustCall());
}
}