mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08:00
Delete test-crypto-ecb.js, which can no longer run anywhere: Blowfish is available only from OpenSSL's legacy provider and is absent from BoringSSL. The addon and cctest version guards become OPENSSL_IS_BORINGSSL checks. Remove legacy digest-alias coverage and unreachable OpenSSL 1.x branches. JavaScript test and benchmark gates, skip reasons, and TLS error expectations that distinguished OpenSSL 1.x from provider-backed OpenSSL now distinguish BoringSSL, including PKCS12 parsing, named DH groups, private-key serialization, RSA-PSS parameters, consolidated provider caches, FIPS availability, and empty KMAC output. Keep the CCM alternate finalization error only for BoringSSL. Provider-only test skips use version-neutral terminology or state the actual minimum OpenSSL version. Signed-off-by: Filip Skokan <panva.ip@gmail.com> Assisted-by: Codex PR-URL: https://github.com/nodejs/node/pull/64777 Fixes: https://github.com/nodejs/node/issues/56733 Refs: https://github.com/nodejs/node/pull/64211 Refs: https://github.com/nodejs/node/pull/63966 Refs: https://github.com/nodejs/node/pull/53329 Refs: https://github.com/nodejs/node/pull/48950 Refs: https://github.com/nodejs/node/issues/45465 Refs: https://github.com/nodejs/node/issues/42827 Refs: https://github.com/nodejs/node/issues/47076 Refs: https://github.com/nodejs/node/pull/62862 Refs: https://github.com/nodejs/node/pull/38512 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Richard Lau <richard.lau@ibm.com> Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
582 lines
21 KiB
JavaScript
582 lines
21 KiB
JavaScript
'use strict';
|
|
|
|
// Regression test for https://github.com/nodejs/node/issues/62899
|
|
// SubtleCrypto.generateKey(), SubtleCrypto.importKey(), and
|
|
// KeyObject.prototype.toCryptoKey() should produce CryptoKey values whose
|
|
// `usages` have been de-duplicated and returned in a canonical order.
|
|
// The same applies to `key_ops` on JWK exports of extractable keys.
|
|
|
|
const common = require('../common');
|
|
|
|
if (!common.hasCrypto)
|
|
common.skip('missing crypto');
|
|
|
|
const assert = require('assert');
|
|
const { createSecretKey } = require('crypto');
|
|
const { hasOpenSSL, hasFIPS, isBoringSSL } = require('../common/crypto');
|
|
const { subtle } = globalThis.crypto;
|
|
|
|
function assertSameSet(actual, expected, msg) {
|
|
if (msg === undefined) {
|
|
assert.deepStrictEqual(actual, expected);
|
|
} else {
|
|
assert.deepStrictEqual(actual, expected, msg);
|
|
}
|
|
}
|
|
|
|
{
|
|
const tests = [];
|
|
|
|
// Symmetric keys (single CryptoKey result). Inputs are deliberately in
|
|
// non-canonical order so the test exercises the canonical re-ordering.
|
|
const symmetric = [
|
|
{ algorithm: { name: 'HMAC', hash: 'SHA-256' },
|
|
usages: ['verify', 'sign', 'verify', 'sign'],
|
|
expected: ['sign', 'verify'] },
|
|
{ algorithm: { name: 'AES-CTR', length: 128 },
|
|
usages: ['wrapKey', 'decrypt', 'encrypt', 'unwrapKey', 'wrapKey', 'encrypt'],
|
|
expected: ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'] },
|
|
{ algorithm: { name: 'AES-CBC', length: 128 },
|
|
usages: ['encrypt', 'encrypt'],
|
|
expected: ['encrypt'] },
|
|
{ algorithm: { name: 'AES-GCM', length: 128 },
|
|
usages: ['decrypt', 'encrypt', 'decrypt'],
|
|
expected: ['encrypt', 'decrypt'] },
|
|
{ algorithm: { name: 'AES-KW', length: 128 },
|
|
usages: ['wrapKey', 'unwrapKey', 'wrapKey', 'unwrapKey'],
|
|
expected: ['wrapKey', 'unwrapKey'] },
|
|
{ algorithm: { name: 'ChaCha20-Poly1305' },
|
|
usages: ['wrapKey', 'decrypt', 'encrypt', 'unwrapKey', 'wrapKey', 'encrypt'],
|
|
expected: ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'] },
|
|
];
|
|
|
|
if (hasFIPS(3))
|
|
symmetric.splice(symmetric.findIndex(({ algorithm }) =>
|
|
algorithm.name === 'ChaCha20-Poly1305'), 1);
|
|
|
|
if (!isBoringSSL) {
|
|
symmetric.push({
|
|
algorithm: { name: 'AES-OCB', length: 128 },
|
|
usages: ['decrypt', 'encrypt', 'decrypt', 'encrypt'],
|
|
expected: ['encrypt', 'decrypt'],
|
|
});
|
|
symmetric.push({
|
|
algorithm: { name: 'KMAC128', length: 128 },
|
|
usages: ['verify', 'sign', 'verify', 'sign'],
|
|
expected: ['sign', 'verify'],
|
|
});
|
|
} else {
|
|
common.printSkipMessage('AES-OCB and KMAC are not supported by BoringSSL');
|
|
}
|
|
|
|
for (const { algorithm, usages, expected } of symmetric) {
|
|
tests.push((async () => {
|
|
const generated = subtle.generateKey(algorithm, true, usages);
|
|
if (hasFIPS(3) && algorithm.name === 'AES-OCB') {
|
|
await assert.rejects(generated, {
|
|
name: 'NotSupportedError', message: 'Unrecognized algorithm name',
|
|
});
|
|
return;
|
|
}
|
|
const key = await generated;
|
|
assertSameSet(key.usages, expected,
|
|
`generateKey ${algorithm.name}`);
|
|
assert.strictEqual(key.usages.length, expected.length,
|
|
`generateKey ${algorithm.name} usage count`);
|
|
})());
|
|
}
|
|
|
|
// Asymmetric keys (CryptoKeyPair result). Duplicates across the input
|
|
// must not produce duplicates on either the public or private key.
|
|
const asymmetric = [
|
|
{ algorithm: { name: 'RSA-OAEP', modulusLength: 2048,
|
|
publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' },
|
|
usages: ['wrapKey', 'unwrapKey', 'decrypt', 'encrypt',
|
|
'unwrapKey', 'wrapKey', 'decrypt', 'encrypt'],
|
|
publicExpected: ['encrypt', 'wrapKey'],
|
|
privateExpected: ['decrypt', 'unwrapKey'] },
|
|
{ algorithm: { name: 'RSA-PSS', modulusLength: 2048,
|
|
publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' },
|
|
usages: ['verify', 'sign', 'verify', 'sign'],
|
|
publicExpected: ['verify'],
|
|
privateExpected: ['sign'] },
|
|
{ algorithm: { name: 'ECDSA', namedCurve: 'P-256' },
|
|
usages: ['verify', 'sign', 'verify', 'sign', 'verify'],
|
|
publicExpected: ['verify'],
|
|
privateExpected: ['sign'] },
|
|
{ algorithm: { name: 'ECDH', namedCurve: 'P-256' },
|
|
usages: ['deriveBits', 'deriveKey', 'deriveBits', 'deriveKey'],
|
|
publicExpected: [],
|
|
privateExpected: ['deriveKey', 'deriveBits'] },
|
|
{ algorithm: { name: 'Ed25519' },
|
|
usages: ['verify', 'sign', 'verify', 'sign'],
|
|
publicExpected: ['verify'],
|
|
privateExpected: ['sign'] },
|
|
{ algorithm: { name: 'X25519' },
|
|
usages: ['deriveBits', 'deriveKey', 'deriveBits', 'deriveKey'],
|
|
publicExpected: [],
|
|
privateExpected: ['deriveKey', 'deriveBits'] },
|
|
];
|
|
|
|
if (hasFIPS(3))
|
|
asymmetric.splice(asymmetric.findIndex(({ algorithm }) =>
|
|
algorithm.name === 'X25519'), 1);
|
|
|
|
if (hasOpenSSL(3, 5) || isBoringSSL) {
|
|
asymmetric.push({
|
|
algorithm: { name: 'ML-DSA-65' },
|
|
usages: ['verify', 'sign', 'verify', 'sign'],
|
|
publicExpected: ['verify'],
|
|
privateExpected: ['sign'],
|
|
});
|
|
asymmetric.push({
|
|
algorithm: { name: 'ML-KEM-768' },
|
|
usages: ['decapsulateBits', 'encapsulateBits', 'decapsulateKey',
|
|
'encapsulateKey', 'decapsulateBits', 'encapsulateBits'],
|
|
publicExpected: ['encapsulateKey', 'encapsulateBits'],
|
|
privateExpected: ['decapsulateKey', 'decapsulateBits'],
|
|
});
|
|
} else {
|
|
common.printSkipMessage('ML-DSA and ML-KEM require OpenSSL >= 3.5 or BoringSSL');
|
|
}
|
|
|
|
for (const { algorithm, usages, publicExpected, privateExpected } of asymmetric) {
|
|
tests.push((async () => {
|
|
const { publicKey, privateKey } =
|
|
await subtle.generateKey(algorithm, true, usages);
|
|
assertSameSet(publicKey.usages, publicExpected,
|
|
`generateKey ${algorithm.name} publicKey`);
|
|
assert.strictEqual(publicKey.usages.length, publicExpected.length);
|
|
assertSameSet(privateKey.usages, privateExpected,
|
|
`generateKey ${algorithm.name} privateKey`);
|
|
assert.strictEqual(privateKey.usages.length, privateExpected.length);
|
|
})());
|
|
}
|
|
|
|
Promise.all(tests).then(common.mustCall());
|
|
}
|
|
|
|
{
|
|
const tests = [];
|
|
|
|
// Symmetric raw imports.
|
|
const rawSymmetric = [
|
|
{ algorithm: { name: 'AES-CBC' }, keyData: new Uint8Array(16),
|
|
usages: ['decrypt', 'encrypt', 'decrypt', 'encrypt'],
|
|
expected: ['encrypt', 'decrypt'] },
|
|
{ algorithm: { name: 'AES-CTR' }, keyData: new Uint8Array(16),
|
|
usages: ['wrapKey', 'encrypt', 'wrapKey', 'encrypt'],
|
|
expected: ['encrypt', 'wrapKey'] },
|
|
{ algorithm: { name: 'AES-GCM' }, keyData: new Uint8Array(16),
|
|
usages: ['decrypt', 'encrypt', 'decrypt'],
|
|
expected: ['encrypt', 'decrypt'] },
|
|
{ algorithm: { name: 'HMAC', hash: 'SHA-256' }, keyData: new Uint8Array(32),
|
|
usages: ['verify', 'sign', 'verify', 'sign'],
|
|
expected: ['sign', 'verify'] },
|
|
{ algorithm: { name: 'AES-KW' }, keyData: new Uint8Array(16),
|
|
usages: ['wrapKey', 'unwrapKey', 'wrapKey'],
|
|
expected: ['wrapKey', 'unwrapKey'] },
|
|
];
|
|
|
|
if (!isBoringSSL) {
|
|
// KMAC does not support `raw` format, only `raw-secret` and `jwk`.
|
|
tests.push((async () => {
|
|
const key = await subtle.importKey(
|
|
'raw-secret', new Uint8Array(16), { name: 'KMAC128' }, true,
|
|
['verify', 'sign', 'verify', 'sign']);
|
|
assertSameSet(key.usages, ['sign', 'verify'],
|
|
'importKey raw-secret KMAC128');
|
|
assert.strictEqual(key.usages.length, 2);
|
|
})());
|
|
|
|
tests.push((async () => {
|
|
const jwk = {
|
|
kty: 'oct',
|
|
k: Buffer.from(new Uint8Array(16)).toString('base64url'),
|
|
alg: 'K128',
|
|
};
|
|
const key = await subtle.importKey(
|
|
'jwk', jwk, { name: 'KMAC128' }, true,
|
|
['verify', 'sign', 'verify', 'sign']);
|
|
assertSameSet(key.usages, ['sign', 'verify'],
|
|
'importKey jwk KMAC128');
|
|
assert.strictEqual(key.usages.length, 2);
|
|
})());
|
|
} else {
|
|
common.printSkipMessage('AES-OCB and KMAC are not supported by BoringSSL');
|
|
}
|
|
|
|
for (const { algorithm, keyData, usages, expected } of rawSymmetric) {
|
|
tests.push((async () => {
|
|
const key = await subtle.importKey('raw', keyData, algorithm, true, usages);
|
|
assertSameSet(key.usages, expected,
|
|
`importKey raw ${algorithm.name}`);
|
|
assert.strictEqual(key.usages.length, expected.length);
|
|
})());
|
|
}
|
|
|
|
// Generic secret keys (HKDF, PBKDF2) - importGenericSecretKey path.
|
|
// These are not extractable.
|
|
for (const name of ['HKDF', 'PBKDF2']) {
|
|
tests.push((async () => {
|
|
const key = await subtle.importKey(
|
|
'raw',
|
|
new Uint8Array(16),
|
|
name,
|
|
false,
|
|
['deriveBits', 'deriveKey', 'deriveBits', 'deriveKey']);
|
|
assertSameSet(key.usages, ['deriveKey', 'deriveBits'],
|
|
`importKey raw ${name}`);
|
|
assert.strictEqual(key.usages.length, 2);
|
|
})());
|
|
}
|
|
|
|
// Argon2 - also via importGenericSecretKey, deriveBits-only.
|
|
// Argon2 only supports raw-secret import.
|
|
if (hasOpenSSL(3, 2)) {
|
|
tests.push((async () => {
|
|
const imported = subtle.importKey(
|
|
'raw-secret',
|
|
new Uint8Array(16),
|
|
'Argon2id',
|
|
false,
|
|
['deriveBits', 'deriveBits']);
|
|
if (hasFIPS(3)) {
|
|
await assert.rejects(imported, {
|
|
name: 'NotSupportedError', message: 'Unrecognized algorithm name',
|
|
});
|
|
return;
|
|
}
|
|
const key = await imported;
|
|
assertSameSet(key.usages, ['deriveBits'],
|
|
'importKey raw-secret Argon2id');
|
|
assert.strictEqual(key.usages.length, 1);
|
|
})());
|
|
} else {
|
|
common.printSkipMessage('Argon2 requires OpenSSL >= 3.2');
|
|
}
|
|
|
|
// JWK symmetric import.
|
|
tests.push((async () => {
|
|
const jwk = {
|
|
kty: 'oct',
|
|
k: 'AAAAAAAAAAAAAAAAAAAAAA',
|
|
alg: 'A128CBC',
|
|
};
|
|
const key = await subtle.importKey('jwk', jwk, { name: 'AES-CBC' }, true,
|
|
['decrypt', 'encrypt', 'decrypt']);
|
|
assertSameSet(key.usages, ['encrypt', 'decrypt'],
|
|
'importKey jwk AES-CBC');
|
|
assert.strictEqual(key.usages.length, 2);
|
|
})());
|
|
|
|
// Asymmetric import via JWK - RSA, ECDSA, Ed25519.
|
|
tests.push((async () => {
|
|
// Generate, export, re-import with duplicate usages.
|
|
const { privateKey } = await subtle.generateKey(
|
|
{ name: 'RSA-PSS', modulusLength: 2048,
|
|
publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' },
|
|
true, ['sign', 'verify']);
|
|
const jwk = await subtle.exportKey('jwk', privateKey);
|
|
const imported = await subtle.importKey(
|
|
'jwk', jwk,
|
|
{ name: 'RSA-PSS', hash: 'SHA-256' },
|
|
true,
|
|
['sign', 'sign', 'sign']);
|
|
assert.deepStrictEqual(imported.usages, ['sign']);
|
|
})());
|
|
|
|
tests.push((async () => {
|
|
const { privateKey } = await subtle.generateKey(
|
|
{ name: 'ECDSA', namedCurve: 'P-256' },
|
|
true, ['sign', 'verify']);
|
|
const jwk = await subtle.exportKey('jwk', privateKey);
|
|
const imported = await subtle.importKey(
|
|
'jwk', jwk,
|
|
{ name: 'ECDSA', namedCurve: 'P-256' },
|
|
true,
|
|
['sign', 'sign']);
|
|
assert.deepStrictEqual(imported.usages, ['sign']);
|
|
})());
|
|
|
|
tests.push((async () => {
|
|
const { privateKey } = await subtle.generateKey(
|
|
{ name: 'Ed25519' }, true, ['sign', 'verify']);
|
|
const pkcs8 = await subtle.exportKey('pkcs8', privateKey);
|
|
const imported = await subtle.importKey(
|
|
'pkcs8', pkcs8,
|
|
{ name: 'Ed25519' },
|
|
true,
|
|
['sign', 'sign', 'sign']);
|
|
assert.deepStrictEqual(imported.usages, ['sign']);
|
|
})());
|
|
|
|
if (hasOpenSSL(3, 5) || isBoringSSL) {
|
|
// ML-DSA JWK roundtrip.
|
|
tests.push((async () => {
|
|
const { privateKey } = await subtle.generateKey(
|
|
{ name: 'ML-DSA-65' }, true, ['sign', 'verify']);
|
|
const jwk = await subtle.exportKey('jwk', privateKey);
|
|
const imported = await subtle.importKey(
|
|
'jwk', jwk, { name: 'ML-DSA-65' }, true,
|
|
['sign', 'sign', 'sign']);
|
|
assert.deepStrictEqual(imported.usages, ['sign']);
|
|
})());
|
|
|
|
// ML-KEM JWK roundtrip.
|
|
tests.push((async () => {
|
|
const { privateKey } = await subtle.generateKey(
|
|
{ name: 'ML-KEM-768' }, true,
|
|
['decapsulateKey', 'decapsulateBits']);
|
|
const jwk = await subtle.exportKey('jwk', privateKey);
|
|
const imported = await subtle.importKey(
|
|
'jwk', jwk, { name: 'ML-KEM-768' }, true,
|
|
['decapsulateBits', 'decapsulateKey',
|
|
'decapsulateBits', 'decapsulateKey']);
|
|
assert.deepStrictEqual(imported.usages,
|
|
['decapsulateKey', 'decapsulateBits']);
|
|
})());
|
|
} else {
|
|
common.printSkipMessage('ML-DSA and ML-KEM require OpenSSL >= 3.5 or BoringSSL');
|
|
}
|
|
|
|
// Spki import of RSA public key.
|
|
tests.push((async () => {
|
|
const { publicKey } = await subtle.generateKey(
|
|
{ name: 'RSA-OAEP', modulusLength: 2048,
|
|
publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' },
|
|
true, ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey']);
|
|
const spki = await subtle.exportKey('spki', publicKey);
|
|
const imported = await subtle.importKey(
|
|
'spki', spki,
|
|
{ name: 'RSA-OAEP', hash: 'SHA-256' },
|
|
true,
|
|
['wrapKey', 'encrypt', 'wrapKey', 'encrypt']);
|
|
assertSameSet(imported.usages, ['encrypt', 'wrapKey']);
|
|
assert.strictEqual(imported.usages.length, 2);
|
|
})());
|
|
|
|
// ChaCha20-Poly1305 raw-secret import.
|
|
tests.push((async () => {
|
|
const imported = subtle.importKey(
|
|
'raw-secret',
|
|
new Uint8Array(32),
|
|
{ name: 'ChaCha20-Poly1305' },
|
|
true,
|
|
['decrypt', 'encrypt', 'decrypt', 'encrypt']);
|
|
if (hasFIPS(3)) {
|
|
await assert.rejects(imported, { name: 'NotSupportedError' });
|
|
return;
|
|
}
|
|
const key = await imported;
|
|
assertSameSet(key.usages, ['encrypt', 'decrypt']);
|
|
assert.strictEqual(key.usages.length, 2);
|
|
})());
|
|
|
|
// AES-OCB raw-secret import.
|
|
if (!isBoringSSL) {
|
|
tests.push((async () => {
|
|
const imported = subtle.importKey(
|
|
'raw-secret',
|
|
new Uint8Array(16),
|
|
{ name: 'AES-OCB' },
|
|
true,
|
|
['decrypt', 'encrypt', 'decrypt', 'encrypt']);
|
|
if (hasFIPS(3)) {
|
|
await assert.rejects(imported, {
|
|
name: 'NotSupportedError', message: 'Unrecognized algorithm name',
|
|
});
|
|
return;
|
|
}
|
|
const key = await imported;
|
|
assertSameSet(key.usages, ['encrypt', 'decrypt']);
|
|
assert.strictEqual(key.usages.length, 2);
|
|
})());
|
|
} else {
|
|
common.printSkipMessage('AES-OCB is not supported by BoringSSL');
|
|
}
|
|
|
|
Promise.all(tests).then(common.mustCall());
|
|
}
|
|
|
|
{
|
|
const tests = [];
|
|
|
|
// Symmetric: HMAC, AES-*, HKDF, PBKDF2
|
|
tests.push((async () => {
|
|
const keyObject = createSecretKey(new Uint8Array(32));
|
|
const key = keyObject.toCryptoKey(
|
|
{ name: 'HMAC', hash: 'SHA-256' },
|
|
true,
|
|
['verify', 'sign', 'verify', 'sign']);
|
|
assertSameSet(key.usages, ['sign', 'verify']);
|
|
assert.strictEqual(key.usages.length, 2);
|
|
})());
|
|
|
|
tests.push((async () => {
|
|
const keyObject = createSecretKey(new Uint8Array(16));
|
|
const key = keyObject.toCryptoKey(
|
|
{ name: 'AES-GCM' },
|
|
true,
|
|
['decrypt', 'encrypt', 'decrypt']);
|
|
assertSameSet(key.usages, ['encrypt', 'decrypt']);
|
|
assert.strictEqual(key.usages.length, 2);
|
|
})());
|
|
|
|
tests.push((async () => {
|
|
const keyObject = createSecretKey(new Uint8Array(32));
|
|
const key = keyObject.toCryptoKey(
|
|
'HKDF',
|
|
false,
|
|
['deriveBits', 'deriveKey', 'deriveBits']);
|
|
assertSameSet(key.usages, ['deriveKey', 'deriveBits']);
|
|
assert.strictEqual(key.usages.length, 2);
|
|
})());
|
|
|
|
Promise.all(tests).then(common.mustCall());
|
|
}
|
|
|
|
{
|
|
(async () => {
|
|
const key = await subtle.generateKey(
|
|
{ name: 'AES-CTR', length: 128 },
|
|
true,
|
|
['wrapKey', 'encrypt', 'decrypt', 'encrypt', 'wrapKey', 'unwrapKey']);
|
|
// Regardless of the input order, de-duplicated usages are returned in
|
|
// a canonical order.
|
|
assert.deepStrictEqual(
|
|
key.usages,
|
|
['encrypt', 'decrypt', 'wrapKey', 'unwrapKey']);
|
|
})().then(common.mustCall());
|
|
}
|
|
|
|
// Exported JWK `key_ops` must also be de-duplicated.
|
|
{
|
|
const tests = [];
|
|
|
|
const jwkVectors = [
|
|
{ algorithm: { name: 'HMAC', hash: 'SHA-256' },
|
|
usages: ['verify', 'sign', 'verify', 'sign'],
|
|
expected: ['sign', 'verify'] },
|
|
{ algorithm: { name: 'AES-CBC', length: 128 },
|
|
usages: ['decrypt', 'encrypt', 'decrypt', 'encrypt'],
|
|
expected: ['encrypt', 'decrypt'] },
|
|
{ algorithm: { name: 'AES-GCM', length: 128 },
|
|
usages: ['decrypt', 'encrypt', 'decrypt'],
|
|
expected: ['encrypt', 'decrypt'] },
|
|
{ algorithm: { name: 'AES-KW', length: 128 },
|
|
usages: ['wrapKey', 'unwrapKey', 'wrapKey', 'unwrapKey'],
|
|
expected: ['wrapKey', 'unwrapKey'] },
|
|
];
|
|
|
|
if (!isBoringSSL) {
|
|
jwkVectors.push({
|
|
algorithm: { name: 'AES-OCB', length: 128 },
|
|
usages: ['decrypt', 'encrypt', 'decrypt', 'encrypt'],
|
|
expected: ['encrypt', 'decrypt'],
|
|
});
|
|
jwkVectors.push({
|
|
algorithm: { name: 'KMAC128', length: 128 },
|
|
usages: ['verify', 'sign', 'verify', 'sign'],
|
|
expected: ['sign', 'verify'],
|
|
});
|
|
} else {
|
|
common.printSkipMessage('AES-OCB and KMAC are not supported by BoringSSL');
|
|
}
|
|
|
|
for (const { algorithm, usages, expected } of jwkVectors) {
|
|
tests.push((async () => {
|
|
const generated = subtle.generateKey(algorithm, true, usages);
|
|
if (hasFIPS(3) && algorithm.name === 'AES-OCB') {
|
|
await assert.rejects(generated, {
|
|
name: 'NotSupportedError', message: 'Unrecognized algorithm name',
|
|
});
|
|
return;
|
|
}
|
|
const key = await generated;
|
|
const jwk = await subtle.exportKey('jwk', key);
|
|
assertSameSet(jwk.key_ops, expected,
|
|
`jwk key_ops for ${algorithm.name}`);
|
|
assert.strictEqual(jwk.key_ops.length, expected.length,
|
|
`jwk key_ops length for ${algorithm.name}`);
|
|
})());
|
|
}
|
|
|
|
const jwkPairVectors = [
|
|
{ algorithm: { name: 'RSA-OAEP', modulusLength: 2048,
|
|
publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' },
|
|
usages: ['wrapKey', 'unwrapKey', 'decrypt', 'encrypt',
|
|
'unwrapKey', 'wrapKey', 'decrypt', 'encrypt'],
|
|
publicExpected: ['encrypt', 'wrapKey'],
|
|
privateExpected: ['decrypt', 'unwrapKey'] },
|
|
{ algorithm: { name: 'RSA-PSS', modulusLength: 2048,
|
|
publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' },
|
|
usages: ['verify', 'sign', 'verify', 'sign'],
|
|
publicExpected: ['verify'],
|
|
privateExpected: ['sign'] },
|
|
{ algorithm: { name: 'ECDSA', namedCurve: 'P-256' },
|
|
usages: ['verify', 'sign', 'verify', 'sign', 'verify'],
|
|
publicExpected: ['verify'],
|
|
privateExpected: ['sign'] },
|
|
{ algorithm: { name: 'ECDH', namedCurve: 'P-256' },
|
|
usages: ['deriveBits', 'deriveKey', 'deriveBits', 'deriveKey'],
|
|
publicExpected: undefined,
|
|
privateExpected: ['deriveKey', 'deriveBits'] },
|
|
{ algorithm: { name: 'Ed25519' },
|
|
usages: ['verify', 'sign', 'verify', 'sign'],
|
|
publicExpected: ['verify'],
|
|
privateExpected: ['sign'] },
|
|
{ algorithm: { name: 'X25519' },
|
|
usages: ['deriveBits', 'deriveKey', 'deriveBits', 'deriveKey'],
|
|
publicExpected: undefined,
|
|
privateExpected: ['deriveKey', 'deriveBits'] },
|
|
];
|
|
|
|
if (hasFIPS(3))
|
|
jwkPairVectors.splice(jwkPairVectors.findIndex(({ algorithm }) =>
|
|
algorithm.name === 'X25519'), 1);
|
|
|
|
if (hasOpenSSL(3, 5) || isBoringSSL) {
|
|
jwkPairVectors.push({
|
|
algorithm: { name: 'ML-DSA-65' },
|
|
usages: ['verify', 'sign', 'verify', 'sign'],
|
|
publicExpected: ['verify'],
|
|
privateExpected: ['sign'],
|
|
});
|
|
jwkPairVectors.push({
|
|
algorithm: { name: 'ML-KEM-768' },
|
|
usages: ['decapsulateBits', 'encapsulateBits', 'decapsulateKey',
|
|
'encapsulateKey', 'decapsulateBits', 'encapsulateBits'],
|
|
publicExpected: ['encapsulateKey', 'encapsulateBits'],
|
|
privateExpected: ['decapsulateKey', 'decapsulateBits'],
|
|
});
|
|
} else {
|
|
common.printSkipMessage('ML-DSA and ML-KEM require OpenSSL >= 3.5 or BoringSSL');
|
|
}
|
|
|
|
for (const { algorithm, usages, publicExpected, privateExpected } of jwkPairVectors) {
|
|
tests.push((async () => {
|
|
const { publicKey, privateKey } =
|
|
await subtle.generateKey(algorithm, true, usages);
|
|
const publicJwk = await subtle.exportKey('jwk', publicKey);
|
|
const privateJwk = await subtle.exportKey('jwk', privateKey);
|
|
|
|
if (publicExpected === undefined) {
|
|
// Empty public-key usages result in an empty `key_ops`.
|
|
assert.deepStrictEqual(publicJwk.key_ops, [],
|
|
`jwk key_ops for ${algorithm.name} publicKey`);
|
|
} else {
|
|
assertSameSet(publicJwk.key_ops, publicExpected,
|
|
`jwk key_ops for ${algorithm.name} publicKey`);
|
|
assert.strictEqual(publicJwk.key_ops.length, publicExpected.length);
|
|
}
|
|
|
|
assertSameSet(privateJwk.key_ops, privateExpected,
|
|
`jwk key_ops for ${algorithm.name} privateKey`);
|
|
assert.strictEqual(privateJwk.key_ops.length, privateExpected.length);
|
|
})());
|
|
}
|
|
|
|
Promise.all(tests).then(common.mustCall());
|
|
}
|