mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 12:20:27 +01:00
PR-URL: https://github.com/nodejs/node/pull/59647 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com>
438 lines
12 KiB
JavaScript
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());
|
|
}
|
|
}
|