From ff6027d487279e16bc0e2fa7956e3e41fcd8013d Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 22 Sep 2026 22:19:32 +0200 Subject: [PATCH] crypto: reject duplicate unknown key usages Check all JWK key_ops entries for duplicates before ignoring unknown usage names. Signed-off-by: Filip Skokan Assisted-by: Codex PR-URL: https://github.com/nodejs/node/pull/66237 Reviewed-By: James M Snell Reviewed-By: Aviv Keller --- lib/internal/crypto/util.js | 7 ++++--- .../test-webcrypto-key-ops-duplicates.js | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 test/parallel/test-webcrypto-key-ops-duplicates.js diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js index 34e09b8b6ed..537870f25e8 100644 --- a/lib/internal/crypto/util.js +++ b/lib/internal/crypto/util.js @@ -1220,15 +1220,16 @@ function validateKeyOps(keyOps, usagesSet) { if (keyOps === undefined) return; validateArray(keyOps, 'keyData.key_ops'); let keyOpsMask = 0; + const seen = new SafeSet(); for (let n = 0; n < keyOps.length; n++) { const op = keyOps[n]; + if (seen.has(op)) + throw lazyDOMException('Duplicate key operation', 'DataError'); + seen.add(op); const opMask = kUsageMasks[op]; // Skipping unknown key ops if (opMask === undefined) continue; - // Have we seen it already? if so, error - if (keyOpsMask & opMask) - throw lazyDOMException('Duplicate key operation', 'DataError'); keyOpsMask |= opMask; // TODO(@jasnell): RFC7517 section 4.3 strong recommends validating diff --git a/test/parallel/test-webcrypto-key-ops-duplicates.js b/test/parallel/test-webcrypto-key-ops-duplicates.js new file mode 100644 index 00000000000..4a666a1caa4 --- /dev/null +++ b/test/parallel/test-webcrypto-key-ops-duplicates.js @@ -0,0 +1,20 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { subtle } = globalThis.crypto; + +(async () => { + const algorithm = { name: 'HMAC', hash: 'SHA-256' }; + const jwk = { kty: 'oct', k: 'AAAAAAAAAAAAAAAAAAAAAA' }; + await subtle.importKey('jwk', { ...jwk, key_ops: ['sign', 'future'] }, + algorithm, false, ['sign']); + for (const key_ops of [['sign', 'sign'], ['sign', 'future', 'future']]) { + await assert.rejects(subtle.importKey('jwk', { ...jwk, key_ops }, + algorithm, false, ['sign']), + { name: 'DataError' }); + } +})().then(common.mustCall());