crypto: create key usage arrays without species

Build public usages and JWK key_ops from the native usage mask so
array species cannot replace or mutate internal usage arrays.

Signed-off-by: Filip Skokan <panva.ip@gmail.com>
Assisted-by: Codex
PR-URL: https://github.com/nodejs/node/pull/66237
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Aviv Keller <me@aviv.sh>
This commit is contained in:
Filip Skokan authored and Node.js GitHub Bot committed 2026-09-25 13:23:18 +00:00
1 parent 5af55e612c
commit 4e38081bec
4 files changed
+37 -9

No files matched your search

+3 -3
View File
@@ -1,7 +1,6 @@
'use strict';
const {
ArrayPrototypeSlice,
BigInt,
PromiseWithResolvers,
SafeSet,
@@ -24,7 +23,7 @@ const {
getCryptoKeySeedData,
getCryptoKeySecondaryHandle,
getCryptoKeyType,
getCryptoKeyUsages,
getCryptoKeyUsagesMask,
InternalCryptoKey,
} = require('internal/crypto/keys');
@@ -51,6 +50,7 @@ const {
const {
getBufferSourceBytes,
getUsagesMask,
getUsagesFromMask,
jobPromise,
jobPromiseThen,
resolveWebCryptoResult,
@@ -860,7 +860,7 @@ function exportJwkKey(key, rawEncapsulationKey, config) {
kty: 'AKP',
alg: config.name,
pub: base64urlEncode(rawEncapsulationKey),
key_ops: ArrayPrototypeSlice(getCryptoKeyUsages(key), 0),
key_ops: getUsagesFromMask(getCryptoKeyUsagesMask(key)),
ext: getCryptoKeyExtractable(key),
};
if (getCryptoKeyType(key) === 'private')
+2 -3
View File
@@ -2,7 +2,6 @@
const {
ArrayPrototypeMap,
ArrayPrototypeSlice,
ObjectDefineProperties,
ObjectPrototypeHasOwnProperty,
ObjectSetPrototypeOf,
@@ -1224,7 +1223,7 @@ const {
type: getCryptoKeyType(this),
extractable: getCryptoKeyExtractable(this),
algorithm: cloneAlgorithm(getCryptoKeyAlgorithm(this)),
usages: ArrayPrototypeSlice(getCryptoKeyUsages(this), 0),
usages: getUsagesFromMask(getCryptoKeyUsagesMask(this)),
}, opts)}`;
}
@@ -1258,7 +1257,7 @@ const {
const slots = getSlots(this);
let cached = slots[kSlotClonedUsages];
if (cached === undefined) {
cached = ArrayPrototypeSlice(getCryptoKeyUsagesFromSlots(slots), 0);
cached = getUsagesFromMask(slots[kSlotUsagesMask]);
slots[kSlotClonedUsages] = cached;
}
return cached;
+2 -3
View File
@@ -3,7 +3,6 @@
const {
ArrayBufferIsView,
ArrayIsArray,
ArrayPrototypeSlice,
FunctionPrototypeCall,
JSONParse,
JSONStringify,
@@ -52,7 +51,6 @@ const {
getCryptoKeyExtractable,
getCryptoKeyHandle,
getCryptoKeyType,
getCryptoKeyUsages,
getCryptoKeyUsagesMask,
hasCryptoKeyUsage,
importGenericSecretKey,
@@ -66,6 +64,7 @@ const {
const {
cleanupWebCryptoResult,
getBlockSize,
getUsagesFromMask,
jobPromiseThen,
normalizeAlgorithm,
normalizeHashName,
@@ -801,7 +800,7 @@ function exportKeyJWK(key) {
// `parameters` before native export populates key material. Delete it for
// algorithms without a JWK alg value to keep the expected shape.
const parameters = {
key_ops: ArrayPrototypeSlice(getCryptoKeyUsages(key), 0),
key_ops: getUsagesFromMask(getCryptoKeyUsagesMask(key)),
ext: getCryptoKeyExtractable(key),
alg,
};
@@ -0,0 +1,30 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const { subtle } = globalThis.crypto;
(async () => {
const key = await subtle.importKey('raw', new Uint8Array(16), 'AES-GCM', true,
['encrypt', 'decrypt']);
const descriptor = Object.getOwnPropertyDescriptor(Array.prototype, 'constructor');
let usages;
let jwk;
try {
Object.defineProperty(Array.prototype, 'constructor', { get: common.mustNotCall() });
usages = key.usages;
jwk = await subtle.exportKey('jwk', key);
} finally {
Object.defineProperty(Array.prototype, 'constructor', descriptor);
}
assert.strictEqual(Object.getPrototypeOf(usages), Array.prototype);
assert.strictEqual(key.usages, usages);
assert.deepStrictEqual(usages, ['encrypt', 'decrypt']);
assert.deepStrictEqual(jwk.key_ops, usages);
usages.push('sign');
assert.deepStrictEqual((await subtle.exportKey('jwk', key)).key_ops,
['encrypt', 'decrypt']);
})().then(common.mustCall());