From 49c78fc0731e8417e17c29e88c977e765127cea9 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Mon, 15 Jun 2026 21:52:35 +0200 Subject: [PATCH] crypto: share WebCrypto method and usage helpers Introduce prepareSubtleMethod() and convertSubtleArgument() for the common SubtleCrypto call prelude. Methods now reuse the same receiver check, required-argument check, error prefix construction, argument context selection, and WebIDL conversion path. Introduce WebCrypto key usage helpers for common usage validation and key pair usage splitting. Algorithm modules now define their allowed public, private, and key generation usages once, then call the shared helpers from generateKey() and importKey() paths. This removes repeated function-invocation setup in webcrypto.js and repeated key usage checks across the WebCrypto algorithm modules while preserving the existing validation behavior. Mark normalized-algorithm fallback branches as unreachable assertions. Signed-off-by: Filip Skokan --- lib/internal/crypto/aes.js | 48 +- lib/internal/crypto/cfrg.js | 114 ++--- lib/internal/crypto/chacha20_poly1305.js | 33 +- lib/internal/crypto/ec.js | 100 ++-- lib/internal/crypto/hash.js | 9 +- lib/internal/crypto/mac.js | 40 +- lib/internal/crypto/ml_dsa.js | 61 +-- lib/internal/crypto/ml_kem.js | 67 ++- lib/internal/crypto/rsa.js | 106 ++-- lib/internal/crypto/util.js | 17 - lib/internal/crypto/webcrypto.js | 600 ++++++++--------------- lib/internal/crypto/webcrypto_util.js | 118 ++++- 12 files changed, 541 insertions(+), 772 deletions(-) diff --git a/lib/internal/crypto/aes.js b/lib/internal/crypto/aes.js index dd7c5ac5fd247a..7ce1dabbf7c5b6 100644 --- a/lib/internal/crypto/aes.js +++ b/lib/internal/crypto/aes.js @@ -1,10 +1,5 @@ 'use strict'; -const { - ArrayPrototypePush, - SafeSet, -} = primordials; - const { AESCipherJob, kCryptoJobWebCrypto, @@ -28,7 +23,6 @@ const { const { getUsagesMask, - hasAnyNotIn, jobPromise, } = require('internal/crypto/util'); @@ -46,8 +40,22 @@ const { importJwkSecretKey, importSecretKey, validateJwk, + validateKeyUsages, + validateUsagesNotEmpty, } = require('internal/crypto/webcrypto_util'); +const kCipherUsages = ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey']; +const kWrapUsages = ['wrapKey', 'unwrapKey']; + +const kUsages = { + '__proto__': null, + 'AES-CBC': kCipherUsages, + 'AES-CTR': kCipherUsages, + 'AES-GCM': kCipherUsages, + 'AES-KW': kWrapUsages, + 'AES-OCB': kCipherUsages, +}; + function getAlgorithmName(name, length) { switch (name) { case 'AES-CBC': return `A${length}CBC`; @@ -176,21 +184,8 @@ function aesCipher(mode, key, data, algorithm) { function aesGenerateKey(algorithm, extractable, usages) { const { name, length } = algorithm; - const checkUsages = ['wrapKey', 'unwrapKey']; - if (name !== 'AES-KW') - ArrayPrototypePush(checkUsages, 'encrypt', 'decrypt'); - - const usagesSet = new SafeSet(usages); - if (hasAnyNotIn(usagesSet, checkUsages)) { - throw lazyDOMException( - 'Unsupported key usage for an AES key', - 'SyntaxError'); - } - if (usagesSet.size === 0) { - throw lazyDOMException( - 'Usages cannot be empty when creating a key.', - 'SyntaxError'); - } + const usagesSet = validateUsagesNotEmpty( + validateKeyUsages(usages, kUsages[name], name)); return jobPromise(() => new SecretKeyGenJob( kCryptoJobWebCrypto, @@ -207,16 +202,7 @@ function aesImportKey( extractable, usages) { const { name } = algorithm; - const checkUsages = ['wrapKey', 'unwrapKey']; - if (name !== 'AES-KW') - ArrayPrototypePush(checkUsages, 'encrypt', 'decrypt'); - - const usagesSet = new SafeSet(usages); - if (hasAnyNotIn(usagesSet, checkUsages)) { - throw lazyDOMException( - 'Unsupported key usage for an AES key', - 'SyntaxError'); - } + const usagesSet = validateKeyUsages(usages, kUsages[name], name); let handle; let length; diff --git a/lib/internal/crypto/cfrg.js b/lib/internal/crypto/cfrg.js index 681926a4b904c9..994b8f510c490d 100644 --- a/lib/internal/crypto/cfrg.js +++ b/lib/internal/crypto/cfrg.js @@ -26,8 +26,6 @@ const { const { getUsagesMask, - getUsagesUnion, - hasAnyNotIn, jobPromise, } = require('internal/crypto/util'); @@ -42,60 +40,33 @@ const { } = require('internal/crypto/keys'); const { + createKeyUsages, + getKeyPairUsages, importDerKey, importJwkKey, importRawKey, validateJwk, + validateKeyUsages, + validateUsagesNotEmpty, + verifyAcceptableKeyUse, } = require('internal/crypto/webcrypto_util'); -function verifyAcceptableCfrgKeyUse(name, isPublic, usages) { - let checkSet; - switch (name) { - case 'X25519': - // Fall through - case 'X448': - checkSet = isPublic ? [] : ['deriveKey', 'deriveBits']; - break; - case 'Ed25519': - // Fall through - case 'Ed448': - checkSet = isPublic ? ['verify'] : ['sign']; - break; - default: - throw lazyDOMException( - 'The algorithm is not supported', 'NotSupportedError'); - } - if (hasAnyNotIn(usages, checkSet)) { - throw lazyDOMException( - `Unsupported key usage for a ${name} key`, - 'SyntaxError'); - } -} +const kDeriveUsages = createKeyUsages([], ['deriveKey', 'deriveBits']); + +const kSignVerifyUsages = createKeyUsages(['verify'], ['sign']); + +const kUsages = { + '__proto__': null, + 'X25519': kDeriveUsages, + 'X448': kDeriveUsages, + 'Ed25519': kSignVerifyUsages, + 'Ed448': kSignVerifyUsages, +}; function cfrgGenerateKey(algorithm, extractable, usages) { const { name } = algorithm; - - const usageSet = new SafeSet(usages); - switch (name) { - case 'Ed25519': - // Fall through - case 'Ed448': - if (hasAnyNotIn(usageSet, ['sign', 'verify'])) { - throw lazyDOMException( - `Unsupported key usage for an ${name} key`, - 'SyntaxError'); - } - break; - case 'X25519': - // Fall through - case 'X448': - if (hasAnyNotIn(usageSet, ['deriveKey', 'deriveBits'])) { - throw lazyDOMException( - `Unsupported key usage for an ${name} key`, - 'SyntaxError'); - } - break; - } + const allowedUsages = kUsages[name]; + const usagesSet = validateKeyUsages(usages, allowedUsages.keygen, name); const nid = { '__proto__': null, 'Ed25519': EVP_PKEY_ED25519, @@ -104,37 +75,16 @@ function cfrgGenerateKey(algorithm, extractable, usages) { 'X448': EVP_PKEY_X448, }[name]; - let publicUsages; - let privateUsages; - switch (name) { - case 'Ed25519': - // Fall through - case 'Ed448': - publicUsages = getUsagesUnion(usageSet, 'verify'); - privateUsages = getUsagesUnion(usageSet, 'sign'); - break; - case 'X25519': - // Fall through - case 'X448': - publicUsages = new SafeSet(); - privateUsages = getUsagesUnion(usageSet, 'deriveKey', 'deriveBits'); - break; - } - const keyAlgorithm = { name }; - - if (privateUsages.size === 0) { - throw lazyDOMException( - 'Usages cannot be empty when creating a key.', - 'SyntaxError'); - } + const keyUsages = getKeyPairUsages(usagesSet, allowedUsages); + validateUsagesNotEmpty(keyUsages.private); return jobPromise(() => new NidKeyPairGenJob( kCryptoJobWebCrypto, nid, keyAlgorithm, - getUsagesMask(publicUsages), - getUsagesMask(privateUsages), + getUsagesMask(keyUsages.public), + getUsagesMask(keyUsages.private), extractable)); } @@ -173,20 +123,25 @@ function cfrgImportKey( const { name } = algorithm; let handle; + const allowedUsages = kUsages[name]; const usagesSet = new SafeSet(usages); switch (format) { case 'KeyObjectHandle': - verifyAcceptableCfrgKeyUse( - name, keyData.getKeyType() === kKeyTypePublic, usagesSet); + verifyAcceptableKeyUse( + name, + usagesSet, + keyData.getKeyType() === kKeyTypePublic ? + allowedUsages.public : + allowedUsages.private); handle = keyData; break; case 'spki': { - verifyAcceptableCfrgKeyUse(name, true, usagesSet); + verifyAcceptableKeyUse(name, usagesSet, allowedUsages.public); handle = importDerKey(keyData, true); break; } case 'pkcs8': { - verifyAcceptableCfrgKeyUse(name, false, usagesSet); + verifyAcceptableKeyUse(name, usagesSet, allowedUsages.private); handle = importDerKey(keyData, false); break; } @@ -205,12 +160,15 @@ function cfrgImportKey( } const isPublic = keyData.d === undefined; - verifyAcceptableCfrgKeyUse(name, isPublic, usagesSet); + verifyAcceptableKeyUse( + name, + usagesSet, + isPublic ? allowedUsages.public : allowedUsages.private); handle = importJwkKey(isPublic, keyData); break; } case 'raw': { - verifyAcceptableCfrgKeyUse(name, true, usagesSet); + verifyAcceptableKeyUse(name, usagesSet, allowedUsages.public); handle = importRawKey(true, keyData, kKeyFormatRawPublic, name); break; } diff --git a/lib/internal/crypto/chacha20_poly1305.js b/lib/internal/crypto/chacha20_poly1305.js index 8baaf6f981df21..45071cded217ee 100644 --- a/lib/internal/crypto/chacha20_poly1305.js +++ b/lib/internal/crypto/chacha20_poly1305.js @@ -1,9 +1,5 @@ 'use strict'; -const { - SafeSet, -} = primordials; - const { ChaCha20Poly1305CipherJob, SecretKeyGenJob, @@ -12,7 +8,6 @@ const { const { getUsagesMask, - hasAnyNotIn, jobPromise, } = require('internal/crypto/util'); @@ -29,8 +24,12 @@ const { importJwkSecretKey, importSecretKey, validateJwk, + validateKeyUsages, + validateUsagesNotEmpty, } = require('internal/crypto/webcrypto_util'); +const kUsages = ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey']; + function validateKeyLength(length) { if (length !== 256) throw lazyDOMException('Invalid key length', 'DataError'); @@ -49,19 +48,8 @@ function c20pCipher(mode, key, data, algorithm) { function c20pGenerateKey(algorithm, extractable, usages) { const { name } = algorithm; - const checkUsages = ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey']; - - const usagesSet = new SafeSet(usages); - if (hasAnyNotIn(usagesSet, checkUsages)) { - throw lazyDOMException( - `Unsupported key usage for a ${algorithm.name} key`, - 'SyntaxError'); - } - if (usagesSet.size === 0) { - throw lazyDOMException( - 'Usages cannot be empty when creating a key.', - 'SyntaxError'); - } + const usagesSet = validateUsagesNotEmpty( + validateKeyUsages(usages, kUsages, name)); return jobPromise(() => new SecretKeyGenJob( kCryptoJobWebCrypto, @@ -78,14 +66,9 @@ function c20pImportKey( extractable, usages) { const { name } = algorithm; - const checkUsages = ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey']; - const usagesSet = new SafeSet(usages); - if (hasAnyNotIn(usagesSet, checkUsages)) { - throw lazyDOMException( - `Unsupported key usage for a ${algorithm.name} key`, - 'SyntaxError'); - } + const usagesSet = validateKeyUsages( + usages, kUsages, name); let handle; switch (format) { diff --git a/lib/internal/crypto/ec.js b/lib/internal/crypto/ec.js index 94040cc7f71813..fc19e7cce6a5e7 100644 --- a/lib/internal/crypto/ec.js +++ b/lib/internal/crypto/ec.js @@ -30,8 +30,6 @@ const { const { getUsagesMask, - getUsagesUnion, - hasAnyNotIn, jobPromise, normalizeHashName, kNamedCurveAliases, @@ -49,81 +47,43 @@ const { } = require('internal/crypto/keys'); const { + createKeyUsages, + getKeyPairUsages, importDerKey, importJwkKey, importRawKey, validateJwk, + validateKeyUsages, + validateUsagesNotEmpty, + verifyAcceptableKeyUse, } = require('internal/crypto/webcrypto_util'); -function verifyAcceptableEcKeyUse(name, isPublic, usages) { - let checkSet; - switch (name) { - case 'ECDH': - checkSet = isPublic ? [] : ['deriveKey', 'deriveBits']; - break; - case 'ECDSA': - checkSet = isPublic ? ['verify'] : ['sign']; - break; - default: - throw lazyDOMException( - 'The algorithm is not supported', 'NotSupportedError'); - } - if (hasAnyNotIn(usages, checkSet)) { - throw lazyDOMException( - `Unsupported key usage for a ${name} key`, - 'SyntaxError'); - } -} +const kDeriveUsages = createKeyUsages([], ['deriveKey', 'deriveBits']); -function ecGenerateKey(algorithm, extractable, usages) { - const { name, namedCurve } = algorithm; +const kSignVerifyUsages = createKeyUsages(['verify'], ['sign']); - const usageSet = new SafeSet(usages); - switch (name) { - case 'ECDSA': - if (hasAnyNotIn(usageSet, ['sign', 'verify'])) { - throw lazyDOMException( - 'Unsupported key usage for an ECDSA key', - 'SyntaxError'); - } - break; - case 'ECDH': - if (hasAnyNotIn(usageSet, ['deriveKey', 'deriveBits'])) { - throw lazyDOMException( - 'Unsupported key usage for an ECDH key', - 'SyntaxError'); - } - // Fall through - } +const kUsages = { + '__proto__': null, + 'ECDH': kDeriveUsages, + 'ECDSA': kSignVerifyUsages, +}; - let publicUsages; - let privateUsages; - switch (name) { - case 'ECDSA': - publicUsages = getUsagesUnion(usageSet, 'verify'); - privateUsages = getUsagesUnion(usageSet, 'sign'); - break; - case 'ECDH': - publicUsages = new SafeSet(); - privateUsages = getUsagesUnion(usageSet, 'deriveKey', 'deriveBits'); - break; - } +function ecGenerateKey(algorithm, extractable, usages) { + const { name, namedCurve } = algorithm; + const allowedUsages = kUsages[name]; + const usagesSet = validateKeyUsages(usages, allowedUsages.keygen, name); const keyAlgorithm = { name, namedCurve }; - - if (privateUsages.size === 0) { - throw lazyDOMException( - 'Usages cannot be empty when creating a key.', - 'SyntaxError'); - } + const keyUsages = getKeyPairUsages(usagesSet, allowedUsages); + validateUsagesNotEmpty(keyUsages.private); return jobPromise(() => new EcKeyPairGenJob( kCryptoJobWebCrypto, namedCurve, undefined, keyAlgorithm, - getUsagesMask(publicUsages), - getUsagesMask(privateUsages), + getUsagesMask(keyUsages.public), + getUsagesMask(keyUsages.private), extractable)); } @@ -181,20 +141,25 @@ function ecImportKey( const { name, namedCurve } = algorithm; let handle; + const allowedUsages = kUsages[name]; const usagesSet = new SafeSet(usages); switch (format) { case 'KeyObjectHandle': - verifyAcceptableEcKeyUse( - name, keyData.getKeyType() === kKeyTypePublic, usagesSet); + verifyAcceptableKeyUse( + name, + usagesSet, + keyData.getKeyType() === kKeyTypePublic ? + allowedUsages.public : + allowedUsages.private); handle = keyData; break; case 'spki': { - verifyAcceptableEcKeyUse(name, true, usagesSet); + verifyAcceptableKeyUse(name, usagesSet, allowedUsages.public); handle = importDerKey(keyData, true); break; } case 'pkcs8': { - verifyAcceptableEcKeyUse(name, false, usagesSet); + verifyAcceptableKeyUse(name, usagesSet, allowedUsages.private); handle = importDerKey(keyData, false); break; } @@ -221,12 +186,15 @@ function ecImportKey( } const isPublic = keyData.d === undefined; - verifyAcceptableEcKeyUse(name, isPublic, usagesSet); + verifyAcceptableKeyUse( + name, + usagesSet, + isPublic ? allowedUsages.public : allowedUsages.private); handle = importJwkKey(isPublic, keyData); break; } case 'raw': { - verifyAcceptableEcKeyUse(name, true, usagesSet); + verifyAcceptableKeyUse(name, usagesSet, allowedUsages.public); handle = importRawKey(true, keyData, kKeyFormatRawPublic, 'ec', namedCurve); break; } diff --git a/lib/internal/crypto/hash.js b/lib/internal/crypto/hash.js index 86052cbc6bd440..3b2b246eaa31d8 100644 --- a/lib/internal/crypto/hash.js +++ b/lib/internal/crypto/hash.js @@ -33,7 +33,6 @@ const { } = require('internal/crypto/keys'); const { - lazyDOMException, normalizeEncoding, encodingsMap, getDeprecationWarningEmitter, @@ -254,9 +253,13 @@ function asyncDigest(algorithm, data) { algorithm.customization, algorithm.outputLength / 8, data)); + /* c8 ignore start */ + default: { + const assert = require('internal/assert'); + assert.fail('Unreachable code'); + } + /* c8 ignore stop */ } - - throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); } function hash(algorithm, input, options) { diff --git a/lib/internal/crypto/mac.js b/lib/internal/crypto/mac.js index 5c6955d22b1904..7562231688c7e8 100644 --- a/lib/internal/crypto/mac.js +++ b/lib/internal/crypto/mac.js @@ -1,7 +1,6 @@ 'use strict'; const { - SafeSet, StringPrototypeSubstring, } = primordials; @@ -17,7 +16,6 @@ const { const { getBlockSize, getUsagesMask, - hasAnyNotIn, jobPromise, normalizeHashName, } = require('internal/crypto/util'); @@ -36,8 +34,12 @@ const { importJwkSecretKey, importSecretKey, validateJwk, + validateKeyUsages, + validateUsagesNotEmpty, } = require('internal/crypto/webcrypto_util'); +const kUsages = ['sign', 'verify']; + function hmacGenerateKey(algorithm, extractable, usages) { const { hash, @@ -45,17 +47,8 @@ function hmacGenerateKey(algorithm, extractable, usages) { length = getBlockSize(hash.name), } = algorithm; - const usageSet = new SafeSet(usages); - if (hasAnyNotIn(usageSet, ['sign', 'verify'])) { - throw lazyDOMException( - 'Unsupported key usage for an HMAC key', - 'SyntaxError'); - } - if (usageSet.size === 0) { - throw lazyDOMException( - 'Usages cannot be empty when creating a key.', - 'SyntaxError'); - } + const usageSet = validateUsagesNotEmpty( + validateKeyUsages(usages, kUsages, name)); return jobPromise(() => new SecretKeyGenJob( kCryptoJobWebCrypto, @@ -75,17 +68,8 @@ function kmacGenerateKey(algorithm, extractable, usages) { }[name], } = algorithm; - const usageSet = new SafeSet(usages); - if (hasAnyNotIn(usageSet, ['sign', 'verify'])) { - throw lazyDOMException( - `Unsupported key usage for ${name} key`, - 'SyntaxError'); - } - if (usageSet.size === 0) { - throw lazyDOMException( - 'Usages cannot be empty when creating a key.', - 'SyntaxError'); - } + const usageSet = validateUsagesNotEmpty( + validateKeyUsages(usages, kUsages, name)); return jobPromise(() => new SecretKeyGenJob( kCryptoJobWebCrypto, @@ -103,12 +87,8 @@ function macImportKey( usages, ) { const isHmac = algorithm.name === 'HMAC'; - const usagesSet = new SafeSet(usages); - if (hasAnyNotIn(usagesSet, ['sign', 'verify'])) { - throw lazyDOMException( - `Unsupported key usage for ${algorithm.name} key`, - 'SyntaxError'); - } + const usagesSet = validateKeyUsages( + usages, kUsages, algorithm.name); let handle; let length; switch (format) { diff --git a/lib/internal/crypto/ml_dsa.js b/lib/internal/crypto/ml_dsa.js index 9eeabaee223112..857961ff6ef31b 100644 --- a/lib/internal/crypto/ml_dsa.js +++ b/lib/internal/crypto/ml_dsa.js @@ -27,8 +27,6 @@ const { const { getUsagesMask, - getUsagesUnion, - hasAnyNotIn, jobPromise, } = require('internal/crypto/util'); @@ -43,30 +41,22 @@ const { } = require('internal/crypto/keys'); const { + createKeyUsages, + getKeyPairUsages, importDerKey, importJwkKey, importRawKey, validateJwk, + validateKeyUsages, + validateUsagesNotEmpty, + verifyAcceptableKeyUse, } = require('internal/crypto/webcrypto_util'); -function verifyAcceptableMlDsaKeyUse(name, isPublic, usages) { - const checkSet = isPublic ? ['verify'] : ['sign']; - if (hasAnyNotIn(usages, checkSet)) { - throw lazyDOMException( - `Unsupported key usage for a ${name} key`, - 'SyntaxError'); - } -} +const kUsages = createKeyUsages(['verify'], ['sign']); function mlDsaGenerateKey(algorithm, extractable, usages) { const { name } = algorithm; - - const usageSet = new SafeSet(usages); - if (hasAnyNotIn(usageSet, ['sign', 'verify'])) { - throw lazyDOMException( - `Unsupported key usage for an ${name} key`, - 'SyntaxError'); - } + const usagesSet = validateKeyUsages(usages, kUsages.keygen, name); const nid = { '__proto__': null, @@ -75,23 +65,16 @@ function mlDsaGenerateKey(algorithm, extractable, usages) { 'ML-DSA-87': EVP_PKEY_ML_DSA_87, }[name]; - const publicUsages = getUsagesUnion(usageSet, 'verify'); - const privateUsages = getUsagesUnion(usageSet, 'sign'); - const keyAlgorithm = { name }; - - if (privateUsages.size === 0) { - throw lazyDOMException( - 'Usages cannot be empty when creating a key.', - 'SyntaxError'); - } + const keyUsages = getKeyPairUsages(usagesSet, kUsages); + validateUsagesNotEmpty(keyUsages.private); return jobPromise(() => new NidKeyPairGenJob( kCryptoJobWebCrypto, nid, keyAlgorithm, - getUsagesMask(publicUsages), - getUsagesMask(privateUsages), + getUsagesMask(keyUsages.public), + getUsagesMask(keyUsages.private), extractable)); } @@ -142,17 +125,21 @@ function mlDsaImportKey( const usagesSet = new SafeSet(usages); switch (format) { case 'KeyObjectHandle': - verifyAcceptableMlDsaKeyUse( - name, keyData.getKeyType() === kKeyTypePublic, usagesSet); + verifyAcceptableKeyUse( + name, + usagesSet, + keyData.getKeyType() === kKeyTypePublic ? + kUsages.public : + kUsages.private); handle = keyData; break; case 'spki': { - verifyAcceptableMlDsaKeyUse(name, true, usagesSet); + verifyAcceptableKeyUse(name, usagesSet, kUsages.public); handle = importDerKey(keyData, true); break; } case 'pkcs8': { - verifyAcceptableMlDsaKeyUse(name, false, usagesSet); + verifyAcceptableKeyUse(name, usagesSet, kUsages.private); const privOnlyLengths = { '__proto__': null, @@ -177,14 +164,20 @@ function mlDsaImportKey( 'JWK "alg" Parameter and algorithm name mismatch', 'DataError'); const isPublic = keyData.priv === undefined; - verifyAcceptableMlDsaKeyUse(name, isPublic, usagesSet); + verifyAcceptableKeyUse( + name, + usagesSet, + isPublic ? kUsages.public : kUsages.private); handle = importJwkKey(isPublic, keyData); break; } case 'raw-public': case 'raw-seed': { const isPublic = format === 'raw-public'; - verifyAcceptableMlDsaKeyUse(name, isPublic, usagesSet); + verifyAcceptableKeyUse( + name, + usagesSet, + isPublic ? kUsages.public : kUsages.private); handle = importRawKey(isPublic, keyData, isPublic ? kKeyFormatRawPublic : kKeyFormatRawSeed, name); break; } diff --git a/lib/internal/crypto/ml_kem.js b/lib/internal/crypto/ml_kem.js index f6df9239b21545..f18dcd13db7707 100644 --- a/lib/internal/crypto/ml_kem.js +++ b/lib/internal/crypto/ml_kem.js @@ -26,8 +26,6 @@ const { const { getUsagesMask, - getUsagesUnion, - hasAnyNotIn, jobPromise, } = require('internal/crypto/util'); @@ -42,21 +40,24 @@ const { } = require('internal/crypto/keys'); const { + createKeyUsages, + getKeyPairUsages, importDerKey, importJwkKey, importRawKey, validateJwk, + validateKeyUsages, + validateUsagesNotEmpty, + verifyAcceptableKeyUse, } = require('internal/crypto/webcrypto_util'); +const kUsages = createKeyUsages( + ['encapsulateKey', 'encapsulateBits'], + ['decapsulateKey', 'decapsulateBits']); + function mlKemGenerateKey(algorithm, extractable, usages) { const { name } = algorithm; - - const usageSet = new SafeSet(usages); - if (hasAnyNotIn(usageSet, ['encapsulateKey', 'encapsulateBits', 'decapsulateKey', 'decapsulateBits'])) { - throw lazyDOMException( - `Unsupported key usage for an ${name} key`, - 'SyntaxError'); - } + const usagesSet = validateKeyUsages(usages, kUsages.keygen, name); const nid = { '__proto__': null, @@ -65,25 +66,16 @@ function mlKemGenerateKey(algorithm, extractable, usages) { 'ML-KEM-1024': EVP_PKEY_ML_KEM_1024, }[name]; - const publicUsages = - getUsagesUnion(usageSet, 'encapsulateKey', 'encapsulateBits'); - const privateUsages = - getUsagesUnion(usageSet, 'decapsulateKey', 'decapsulateBits'); - const keyAlgorithm = { name }; - - if (privateUsages.size === 0) { - throw lazyDOMException( - 'Usages cannot be empty when creating a key.', - 'SyntaxError'); - } + const keyUsages = getKeyPairUsages(usagesSet, kUsages); + validateUsagesNotEmpty(keyUsages.private); return jobPromise(() => new NidKeyPairGenJob( kCryptoJobWebCrypto, nid, keyAlgorithm, - getUsagesMask(publicUsages), - getUsagesMask(privateUsages), + getUsagesMask(keyUsages.public), + getUsagesMask(keyUsages.private), extractable)); } @@ -122,15 +114,6 @@ function mlKemExportKey(key, format) { } } -function verifyAcceptableMlKemKeyUse(name, isPublic, usages) { - const checkSet = isPublic ? ['encapsulateKey', 'encapsulateBits'] : ['decapsulateKey', 'decapsulateBits']; - if (hasAnyNotIn(usages, checkSet)) { - throw lazyDOMException( - `Unsupported key usage for a ${name} key`, - 'SyntaxError'); - } -} - function mlKemImportKey( format, keyData, @@ -143,17 +126,21 @@ function mlKemImportKey( const usagesSet = new SafeSet(usages); switch (format) { case 'KeyObjectHandle': - verifyAcceptableMlKemKeyUse( - name, keyData.getKeyType() === kKeyTypePublic, usagesSet); + verifyAcceptableKeyUse( + name, + usagesSet, + keyData.getKeyType() === kKeyTypePublic ? + kUsages.public : + kUsages.private); handle = keyData; break; case 'spki': { - verifyAcceptableMlKemKeyUse(name, true, usagesSet); + verifyAcceptableKeyUse(name, usagesSet, kUsages.public); handle = importDerKey(keyData, true); break; } case 'pkcs8': { - verifyAcceptableMlKemKeyUse(name, false, usagesSet); + verifyAcceptableKeyUse(name, usagesSet, kUsages.private); const privOnlyLengths = { '__proto__': null, @@ -173,7 +160,10 @@ function mlKemImportKey( case 'raw-public': case 'raw-seed': { const isPublic = format === 'raw-public'; - verifyAcceptableMlKemKeyUse(name, isPublic, usagesSet); + verifyAcceptableKeyUse( + name, + usagesSet, + isPublic ? kUsages.public : kUsages.private); handle = importRawKey(isPublic, keyData, isPublic ? kKeyFormatRawPublic : kKeyFormatRawSeed, name); break; } @@ -185,7 +175,10 @@ function mlKemImportKey( 'JWK "alg" Parameter and algorithm name mismatch', 'DataError'); const isPublic = keyData.priv === undefined; - verifyAcceptableMlKemKeyUse(name, isPublic, usagesSet); + verifyAcceptableKeyUse( + name, + usagesSet, + isPublic ? kUsages.public : kUsages.private); handle = importJwkKey(isPublic, keyData); break; } diff --git a/lib/internal/crypto/rsa.js b/lib/internal/crypto/rsa.js index 0442f8a80db0d4..a2757384f4f490 100644 --- a/lib/internal/crypto/rsa.js +++ b/lib/internal/crypto/rsa.js @@ -32,8 +32,6 @@ const { bigIntArrayToUnsignedInt, getDigestSizeInBytes, getUsagesMask, - getUsagesUnion, - hasAnyNotIn, jobPromise, normalizeHashName, validateMaxBufferLength, @@ -51,32 +49,28 @@ const { } = require('internal/crypto/keys'); const { + createKeyUsages, + getKeyPairUsages, importDerKey, importJwkKey, validateJwk, + validateKeyUsages, + validateUsagesNotEmpty, + verifyAcceptableKeyUse, } = require('internal/crypto/webcrypto_util'); -function verifyAcceptableRsaKeyUse(name, isPublic, usages) { - let checkSet; - switch (name) { - case 'RSA-OAEP': - checkSet = isPublic ? ['encrypt', 'wrapKey'] : ['decrypt', 'unwrapKey']; - break; - case 'RSA-PSS': - // Fall through - case 'RSASSA-PKCS1-v1_5': - checkSet = isPublic ? ['verify'] : ['sign']; - break; - default: - throw lazyDOMException( - 'The algorithm is not supported', 'NotSupportedError'); - } - if (hasAnyNotIn(usages, checkSet)) { - throw lazyDOMException( - `Unsupported key usage for an ${name} key`, - 'SyntaxError'); - } -} +const kOaepUsages = createKeyUsages( + ['encrypt', 'wrapKey'], + ['decrypt', 'unwrapKey']); + +const kSignVerifyUsages = createKeyUsages(['verify'], ['sign']); + +const kUsages = { + '__proto__': null, + 'RSA-OAEP': kOaepUsages, + 'RSA-PSS': kSignVerifyUsages, + 'RSASSA-PKCS1-v1_5': kSignVerifyUsages, +}; function validateRsaOaepAlgorithm(algorithm) { if (algorithm.label !== undefined) { @@ -122,24 +116,8 @@ function rsaKeyGenerate( hash, } = algorithm; - const usageSet = new SafeSet(usages); - - switch (name) { - case 'RSA-OAEP': - if (hasAnyNotIn(usageSet, - ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'])) { - throw lazyDOMException( - 'Unsupported key usage for a RSA key', - 'SyntaxError'); - } - break; - default: - if (hasAnyNotIn(usageSet, ['sign', 'verify'])) { - throw lazyDOMException( - 'Unsupported key usage for a RSA key', - 'SyntaxError'); - } - } + const allowedUsages = kUsages[name]; + const usagesSet = validateKeyUsages(usages, allowedUsages.keygen, name); const keyAlgorithm = { name, @@ -154,26 +132,8 @@ function rsaKeyGenerate( 'OperationError'); } - let publicUsages; - let privateUsages; - switch (name) { - case 'RSA-OAEP': { - publicUsages = getUsagesUnion(usageSet, 'encrypt', 'wrapKey'); - privateUsages = getUsagesUnion(usageSet, 'decrypt', 'unwrapKey'); - break; - } - default: { - publicUsages = getUsagesUnion(usageSet, 'verify'); - privateUsages = getUsagesUnion(usageSet, 'sign'); - break; - } - } - - if (privateUsages.size === 0) { - throw lazyDOMException( - 'Usages cannot be empty when creating a key.', - 'SyntaxError'); - } + const keyUsages = getKeyPairUsages(usagesSet, allowedUsages); + validateUsagesNotEmpty(keyUsages.private); return jobPromise(() => new RsaKeyPairGenJob( kCryptoJobWebCrypto, @@ -181,8 +141,8 @@ function rsaKeyGenerate( modulusLength, publicExponentConverted, keyAlgorithm, - getUsagesMask(publicUsages), - getUsagesMask(privateUsages), + getUsagesMask(keyUsages.public), + getUsagesMask(keyUsages.private), extractable)); } @@ -213,21 +173,28 @@ function rsaImportKey( algorithm, extractable, usages) { + const allowedUsages = kUsages[algorithm.name]; const usagesSet = new SafeSet(usages); let handle; switch (format) { case 'KeyObjectHandle': - verifyAcceptableRsaKeyUse( - algorithm.name, keyData.getKeyType() === kKeyTypePublic, usagesSet); + verifyAcceptableKeyUse( + algorithm.name, + usagesSet, + keyData.getKeyType() === kKeyTypePublic ? + allowedUsages.public : + allowedUsages.private); handle = keyData; break; case 'spki': { - verifyAcceptableRsaKeyUse(algorithm.name, true, usagesSet); + verifyAcceptableKeyUse( + algorithm.name, usagesSet, allowedUsages.public); handle = importDerKey(keyData, true); break; } case 'pkcs8': { - verifyAcceptableRsaKeyUse(algorithm.name, false, usagesSet); + verifyAcceptableKeyUse( + algorithm.name, usagesSet, allowedUsages.private); handle = importDerKey(keyData, false); break; } @@ -249,7 +216,10 @@ function rsaImportKey( } const isPublic = keyData.d === undefined; - verifyAcceptableRsaKeyUse(algorithm.name, isPublic, usagesSet); + verifyAcceptableKeyUse( + algorithm.name, + usagesSet, + isPublic ? allowedUsages.public : allowedUsages.private); handle = importJwkKey(isPublic, keyData); break; } diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js index 74d86de3f1b9e1..c87db16ba0e3d0 100644 --- a/lib/internal/crypto/util.js +++ b/lib/internal/crypto/util.js @@ -18,7 +18,6 @@ const { PromiseReject, PromiseWithResolvers, SafeMap, - SafeSet, StringPrototypeToUpperCase, Symbol, TypedArrayPrototypeGetBuffer, @@ -782,21 +781,6 @@ function getStringOption(options, key) { return value; } -/** - * Returns the requested usages that are present in `usageSet`. - * @param {SafeSet} usageSet - * @param {...string} usages - * @returns {SafeSet} - */ -function getUsagesUnion(usageSet, ...usages) { - const newset = new SafeSet(); - for (let n = 0; n < usages.length; n++) { - if (usageSet.has(usages[n])) - newset.add(usages[n]); - } - return newset; -} - // Must be at most 31 entries. const kCanonicalUsageOrder = [ 'encrypt', 'decrypt', @@ -976,7 +960,6 @@ module.exports = { getBlockSize, getDigestSizeInBytes, getStringOption, - getUsagesUnion, getUsagesMask, getUsagesFromMask, hasUsage, diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js index f86ded93312859..61bea2ec42aa00 100644 --- a/lib/internal/crypto/webcrypto.js +++ b/lib/internal/crypto/webcrypto.js @@ -108,24 +108,42 @@ function callSubtleCryptoMethod(fn, receiver, args) { } } +const kArgumentContexts = [ + '1st argument', + '2nd argument', + '3rd argument', + '4th argument', + '5th argument', + '6th argument', + '7th argument', +]; + +function prepareSubtleMethod(receiver, method, argLength, required) { + if (receiver !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); + + webidl ??= require('internal/crypto/webidl'); + const prefix = `Failed to execute '${method}' on 'SubtleCrypto'`; + webidl.requiredArguments(argLength, required, { prefix }); + return prefix; +} + +function convertSubtleArgument(prefix, converter, value, index) { + return webidl.converters[converter](value, { + prefix, + context: kArgumentContexts[index], + }); +} + function digest(algorithm, data) { return callSubtleCryptoMethod(digestImpl, this, arguments); } function digestImpl(algorithm, data) { - if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); - - webidl ??= require('internal/crypto/webidl'); - const prefix = "Failed to execute 'digest' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: '1st argument', - }); - data = webidl.converters.BufferSource(data, { - prefix, - context: '2nd argument', - }); + const prefix = prepareSubtleMethod(this, 'digest', arguments.length, 2); + let i = 0; + algorithm = convertSubtleArgument( + prefix, 'AlgorithmIdentifier', algorithm, i++); + data = convertSubtleArgument(prefix, 'BufferSource', data, i++); const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'digest'); @@ -148,23 +166,13 @@ function generateKeyImpl( algorithm, extractable, keyUsages) { - if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); - - webidl ??= require('internal/crypto/webidl'); - const prefix = "Failed to execute 'generateKey' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: '1st argument', - }); - extractable = webidl.converters.boolean(extractable, { - prefix, - context: '2nd argument', - }); - const usages = webidl.converters['sequence'](keyUsages, { - prefix, - context: '3rd argument', - }); + const prefix = prepareSubtleMethod(this, 'generateKey', arguments.length, 3); + let i = 0; + algorithm = convertSubtleArgument( + prefix, 'AlgorithmIdentifier', algorithm, i++); + extractable = convertSubtleArgument(prefix, 'boolean', extractable, i++); + const usages = convertSubtleArgument( + prefix, 'sequence', keyUsages, i++); const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'generateKey'); switch (normalizedAlgorithm.name) { @@ -225,8 +233,12 @@ function generateKeyImpl( case 'KMAC256': return require('internal/crypto/mac') .kmacGenerateKey(normalizedAlgorithm, extractable, usages); - default: - throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); + /* c8 ignore start */ + default: { + const assert = require('internal/assert'); + assert.fail('Unreachable code'); + } + /* c8 ignore stop */ } } @@ -235,24 +247,13 @@ function deriveBits(algorithm, baseKey, length = null) { } function deriveBitsImpl(algorithm, baseKey, length = null) { - if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); - - webidl ??= require('internal/crypto/webidl'); - const prefix = "Failed to execute 'deriveBits' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: '1st argument', - }); - baseKey = webidl.converters.CryptoKey(baseKey, { - prefix, - context: '2nd argument', - }); + const prefix = prepareSubtleMethod(this, 'deriveBits', arguments.length, 2); + let i = 0; + algorithm = convertSubtleArgument( + prefix, 'AlgorithmIdentifier', algorithm, i++); + baseKey = convertSubtleArgument(prefix, 'CryptoKey', baseKey, i++); if (length !== null) { - length = webidl.converters['unsigned long'](length, { - prefix, - context: '3rd argument', - }); + length = convertSubtleArgument(prefix, 'unsigned long', length, i++); } const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'deriveBits'); @@ -284,8 +285,13 @@ function deriveBitsImpl(algorithm, baseKey, length = null) { case 'Argon2id': return require('internal/crypto/argon2') .argon2DeriveBits(normalizedAlgorithm, baseKey, length); + /* c8 ignore start */ + default: { + const assert = require('internal/assert'); + assert.fail('Unreachable code'); + } + /* c8 ignore stop */ } - throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); } function getKeyLength({ name, length, hash }) { @@ -342,31 +348,16 @@ function deriveKeyImpl( derivedKeyType, extractable, keyUsages) { - if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); - - webidl ??= require('internal/crypto/webidl'); - const prefix = "Failed to execute 'deriveKey' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 5, { prefix }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: '1st argument', - }); - baseKey = webidl.converters.CryptoKey(baseKey, { - prefix, - context: '2nd argument', - }); - derivedKeyType = webidl.converters.AlgorithmIdentifier(derivedKeyType, { - prefix, - context: '3rd argument', - }); - extractable = webidl.converters.boolean(extractable, { - prefix, - context: '4th argument', - }); - const usages = webidl.converters['sequence'](keyUsages, { - prefix, - context: '5th argument', - }); + const prefix = prepareSubtleMethod(this, 'deriveKey', arguments.length, 5); + let i = 0; + algorithm = convertSubtleArgument( + prefix, 'AlgorithmIdentifier', algorithm, i++); + baseKey = convertSubtleArgument(prefix, 'CryptoKey', baseKey, i++); + derivedKeyType = convertSubtleArgument( + prefix, 'AlgorithmIdentifier', derivedKeyType, i++); + extractable = convertSubtleArgument(prefix, 'boolean', extractable, i++); + const usages = convertSubtleArgument( + prefix, 'sequence', keyUsages, i++); const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'deriveBits'); const normalizedDerivedKeyAlgorithmImport = @@ -408,8 +399,12 @@ function deriveKeyImpl( secret = require('internal/crypto/argon2') .argon2DeriveBits(normalizedAlgorithm, baseKey, length); break; - default: - throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); + /* c8 ignore start */ + default: { + const assert = require('internal/assert'); + assert.fail('Unreachable code'); + } + /* c8 ignore stop */ } return jobPromiseThen(secret, (secret) => FunctionPrototypeCall( @@ -766,19 +761,10 @@ function exportKey(format, key) { } function exportKeyImpl(format, key) { - if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); - - webidl ??= require('internal/crypto/webidl'); - const prefix = "Failed to execute 'exportKey' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - format = webidl.converters.KeyFormat(format, { - prefix, - context: '1st argument', - }); - key = webidl.converters.CryptoKey(key, { - prefix, - context: '2nd argument', - }); + const prefix = prepareSubtleMethod(this, 'exportKey', arguments.length, 2); + let i = 0; + format = convertSubtleArgument(prefix, 'KeyFormat', format, i++); + key = convertSubtleArgument(prefix, 'CryptoKey', key, i++); return exportKeySync(format, key); } @@ -1008,32 +994,16 @@ function importKeyImpl( algorithm, extractable, keyUsages) { - if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); - - webidl ??= require('internal/crypto/webidl'); - const prefix = "Failed to execute 'importKey' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 5, { prefix }); - format = webidl.converters.KeyFormat(format, { - prefix, - context: '1st argument', - }); + const prefix = prepareSubtleMethod(this, 'importKey', arguments.length, 5); + let i = 0; + format = convertSubtleArgument(prefix, 'KeyFormat', format, i++); const type = format === 'jwk' ? 'JsonWebKey' : 'BufferSource'; - keyData = webidl.converters[type](keyData, { - prefix, - context: '2nd argument', - }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: '3rd argument', - }); - extractable = webidl.converters.boolean(extractable, { - prefix, - context: '4th argument', - }); - const usages = webidl.converters['sequence'](keyUsages, { - prefix, - context: '5th argument', - }); + keyData = convertSubtleArgument(prefix, type, keyData, i++); + algorithm = convertSubtleArgument( + prefix, 'AlgorithmIdentifier', algorithm, i++); + extractable = convertSubtleArgument(prefix, 'boolean', extractable, i++); + const usages = convertSubtleArgument( + prefix, 'sequence', keyUsages, i++); const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'importKey'); @@ -1055,29 +1025,14 @@ function wrapKey(format, key, wrappingKey, wrapAlgorithm) { } function wrapKeyImpl(format, key, wrappingKey, wrapAlgorithm) { - if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); - - webidl ??= require('internal/crypto/webidl'); - const prefix = "Failed to execute 'wrapKey' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 4, { prefix }); - format = webidl.converters.KeyFormat(format, { - prefix, - context: '1st argument', - }); - key = webidl.converters.CryptoKey(key, { - prefix, - context: '2nd argument', - }); - wrappingKey = webidl.converters.CryptoKey(wrappingKey, { - prefix, - context: '3rd argument', - }); - const algorithm = webidl.converters.AlgorithmIdentifier(wrapAlgorithm, { - prefix, - context: '4th argument', - }); + const prefix = prepareSubtleMethod(this, 'wrapKey', arguments.length, 4); + let i = 0; + format = convertSubtleArgument(prefix, 'KeyFormat', format, i++); + key = convertSubtleArgument(prefix, 'CryptoKey', key, i++); + wrappingKey = convertSubtleArgument(prefix, 'CryptoKey', wrappingKey, i++); + const algorithm = convertSubtleArgument( + prefix, 'AlgorithmIdentifier', wrapAlgorithm, i++); let normalizedAlgorithm; - try { normalizedAlgorithm = normalizeAlgorithm(algorithm, 'wrapKey'); } catch { @@ -1117,8 +1072,7 @@ function wrapKeyImpl(format, key, wrappingKey, wrapAlgorithm) { kWebCryptoCipherEncrypt, normalizedAlgorithm, wrappingKey, - bytes, - 'wrapKey'); + bytes); } // subtle.unwrapKey() is essentially a subtle.decrypt() followed @@ -1142,42 +1096,19 @@ function unwrapKeyImpl( unwrappedKeyAlgorithm, extractable, keyUsages) { - if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); - - webidl ??= require('internal/crypto/webidl'); - const prefix = "Failed to execute 'unwrapKey' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 7, { prefix }); - format = webidl.converters.KeyFormat(format, { - prefix, - context: '1st argument', - }); - wrappedKey = webidl.converters.BufferSource(wrappedKey, { - prefix, - context: '2nd argument', - }); - unwrappingKey = webidl.converters.CryptoKey(unwrappingKey, { - prefix, - context: '3rd argument', - }); - const algorithm = webidl.converters.AlgorithmIdentifier(unwrapAlgorithm, { - prefix, - context: '4th argument', - }); - unwrappedKeyAlgorithm = webidl.converters.AlgorithmIdentifier( - unwrappedKeyAlgorithm, - { - prefix, - context: '5th argument', - }, - ); - extractable = webidl.converters.boolean(extractable, { - prefix, - context: '6th argument', - }); - const usages = webidl.converters['sequence'](keyUsages, { - prefix, - context: '7th argument', - }); + const prefix = prepareSubtleMethod(this, 'unwrapKey', arguments.length, 7); + let i = 0; + format = convertSubtleArgument(prefix, 'KeyFormat', format, i++); + wrappedKey = convertSubtleArgument(prefix, 'BufferSource', wrappedKey, i++); + unwrappingKey = convertSubtleArgument( + prefix, 'CryptoKey', unwrappingKey, i++); + const algorithm = convertSubtleArgument( + prefix, 'AlgorithmIdentifier', unwrapAlgorithm, i++); + unwrappedKeyAlgorithm = convertSubtleArgument( + prefix, 'AlgorithmIdentifier', unwrappedKeyAlgorithm, i++); + extractable = convertSubtleArgument(prefix, 'boolean', extractable, i++); + const usages = convertSubtleArgument( + prefix, 'sequence', keyUsages, i++); let normalizedAlgorithm; try { @@ -1200,8 +1131,7 @@ function unwrapKeyImpl( kWebCryptoCipherDecrypt, normalizedAlgorithm, unwrappingKey, - wrappedKey, - 'unwrapKey'); + wrappedKey); return jobPromiseThen(bytes, (bytes) => { let keyData = bytes; @@ -1262,8 +1192,13 @@ function signVerify(algorithm, key, data, signature) { case 'KMAC256': return require('internal/crypto/mac') .kmacSignVerify(key, data, normalizedAlgorithm, signature); + /* c8 ignore start */ + default: { + const assert = require('internal/assert'); + assert.fail('Unreachable code'); + } + /* c8 ignore stop */ } - throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); } function sign(algorithm, key, data) { @@ -1271,23 +1206,12 @@ function sign(algorithm, key, data) { } function signImpl(algorithm, key, data) { - if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); - - webidl ??= require('internal/crypto/webidl'); - const prefix = "Failed to execute 'sign' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: '1st argument', - }); - key = webidl.converters.CryptoKey(key, { - prefix, - context: '2nd argument', - }); - data = webidl.converters.BufferSource(data, { - prefix, - context: '3rd argument', - }); + const prefix = prepareSubtleMethod(this, 'sign', arguments.length, 3); + let i = 0; + algorithm = convertSubtleArgument( + prefix, 'AlgorithmIdentifier', algorithm, i++); + key = convertSubtleArgument(prefix, 'CryptoKey', key, i++); + data = convertSubtleArgument(prefix, 'BufferSource', data, i++); return signVerify(algorithm, key, data); } @@ -1297,32 +1221,18 @@ function verify(algorithm, key, signature, data) { } function verifyImpl(algorithm, key, signature, data) { - if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); - - webidl ??= require('internal/crypto/webidl'); - const prefix = "Failed to execute 'verify' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 4, { prefix }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: '1st argument', - }); - key = webidl.converters.CryptoKey(key, { - prefix, - context: '2nd argument', - }); - signature = webidl.converters.BufferSource(signature, { - prefix, - context: '3rd argument', - }); - data = webidl.converters.BufferSource(data, { - prefix, - context: '4th argument', - }); + const prefix = prepareSubtleMethod(this, 'verify', arguments.length, 4); + let i = 0; + algorithm = convertSubtleArgument( + prefix, 'AlgorithmIdentifier', algorithm, i++); + key = convertSubtleArgument(prefix, 'CryptoKey', key, i++); + signature = convertSubtleArgument(prefix, 'BufferSource', signature, i++); + data = convertSubtleArgument(prefix, 'BufferSource', data, i++); return signVerify(algorithm, key, data, signature); } -function cipherOrWrap(mode, normalizedAlgorithm, key, data, operation) { +function cipherOrWrap(mode, normalizedAlgorithm, key, data) { // While WebCrypto allows for larger input buffer sizes, we limit // those to sizes that can fit within uint32_t because of limitations // in the OpenSSL API. @@ -1339,18 +1249,20 @@ function cipherOrWrap(mode, normalizedAlgorithm, key, data, operation) { case 'AES-GCM': // Fall through case 'AES-OCB': + // Fall through + case 'AES-KW': return require('internal/crypto/aes') .aesCipher(mode, key, data, normalizedAlgorithm); case 'ChaCha20-Poly1305': return require('internal/crypto/chacha20_poly1305') .c20pCipher(mode, key, data, normalizedAlgorithm); - case 'AES-KW': - if (operation === 'wrapKey' || operation === 'unwrapKey') { - return require('internal/crypto/aes') - .aesCipher(mode, key, data, normalizedAlgorithm); - } + /* c8 ignore start */ + default: { + const assert = require('internal/assert'); + assert.fail('Unreachable code'); + } + /* c8 ignore stop */ } - throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); } function encrypt(algorithm, key, data) { @@ -1358,23 +1270,12 @@ function encrypt(algorithm, key, data) { } function encryptImpl(algorithm, key, data) { - if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); - - webidl ??= require('internal/crypto/webidl'); - const prefix = "Failed to execute 'encrypt' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: '1st argument', - }); - key = webidl.converters.CryptoKey(key, { - prefix, - context: '2nd argument', - }); - data = webidl.converters.BufferSource(data, { - prefix, - context: '3rd argument', - }); + const prefix = prepareSubtleMethod(this, 'encrypt', arguments.length, 3); + let i = 0; + algorithm = convertSubtleArgument( + prefix, 'AlgorithmIdentifier', algorithm, i++); + key = convertSubtleArgument(prefix, 'CryptoKey', key, i++); + data = convertSubtleArgument(prefix, 'BufferSource', data, i++); const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'encrypt'); @@ -1390,7 +1291,6 @@ function encryptImpl(algorithm, key, data) { normalizedAlgorithm, key, data, - 'encrypt', ); } @@ -1399,23 +1299,12 @@ function decrypt(algorithm, key, data) { } function decryptImpl(algorithm, key, data) { - if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); - - webidl ??= require('internal/crypto/webidl'); - const prefix = "Failed to execute 'decrypt' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: '1st argument', - }); - key = webidl.converters.CryptoKey(key, { - prefix, - context: '2nd argument', - }); - data = webidl.converters.BufferSource(data, { - prefix, - context: '3rd argument', - }); + const prefix = prepareSubtleMethod(this, 'decrypt', arguments.length, 3); + let i = 0; + algorithm = convertSubtleArgument( + prefix, 'AlgorithmIdentifier', algorithm, i++); + key = convertSubtleArgument(prefix, 'CryptoKey', key, i++); + data = convertSubtleArgument(prefix, 'BufferSource', data, i++); const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'decrypt'); @@ -1431,7 +1320,6 @@ function decryptImpl(algorithm, key, data) { normalizedAlgorithm, key, data, - 'decrypt', ); } @@ -1442,19 +1330,11 @@ function getPublicKey(key, keyUsages) { function getPublicKeyImpl(key, keyUsages) { emitExperimentalWarning('The getPublicKey Web Crypto API method'); - if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); - - webidl ??= require('internal/crypto/webidl'); - const prefix = "Failed to execute 'getPublicKey' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - key = webidl.converters.CryptoKey(key, { - prefix, - context: '1st argument', - }); - const usages = webidl.converters['sequence'](keyUsages, { - prefix, - context: '2nd argument', - }); + const prefix = prepareSubtleMethod(this, 'getPublicKey', arguments.length, 2); + let i = 0; + key = convertSubtleArgument(prefix, 'CryptoKey', key, i++); + const usages = convertSubtleArgument( + prefix, 'sequence', keyUsages, i++); const type = getCryptoKeyType(key); if (type !== 'private') @@ -1470,22 +1350,13 @@ function encapsulateBits(encapsulationAlgorithm, encapsulationKey) { function encapsulateBitsImpl(encapsulationAlgorithm, encapsulationKey) { emitExperimentalWarning('The encapsulateBits Web Crypto API method'); - if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); - - webidl ??= require('internal/crypto/webidl'); - const prefix = "Failed to execute 'encapsulateBits' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - encapsulationAlgorithm = webidl.converters.AlgorithmIdentifier( - encapsulationAlgorithm, - { - prefix, - context: '1st argument', - }, - ); - encapsulationKey = webidl.converters.CryptoKey(encapsulationKey, { - prefix, - context: '2nd argument', - }); + const prefix = prepareSubtleMethod( + this, 'encapsulateBits', arguments.length, 2); + let i = 0; + encapsulationAlgorithm = convertSubtleArgument( + prefix, 'AlgorithmIdentifier', encapsulationAlgorithm, i++); + encapsulationKey = convertSubtleArgument( + prefix, 'CryptoKey', encapsulationKey, i++); const normalizedEncapsulationAlgorithm = normalizeAlgorithm(encapsulationAlgorithm, 'encapsulate'); @@ -1509,9 +1380,13 @@ function encapsulateBitsImpl(encapsulationAlgorithm, encapsulationKey) { case 'ML-KEM-1024': return require('internal/crypto/ml_kem') .mlKemEncapsulate(encapsulationKey); + /* c8 ignore start */ + default: { + const assert = require('internal/assert'); + assert.fail('Unreachable code'); + } + /* c8 ignore stop */ } - - throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); } function encapsulateKey( @@ -1530,37 +1405,18 @@ function encapsulateKeyImpl( extractable, keyUsages) { emitExperimentalWarning('The encapsulateKey Web Crypto API method'); - if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); - - webidl ??= require('internal/crypto/webidl'); - const prefix = "Failed to execute 'encapsulateKey' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 5, { prefix }); - encapsulationAlgorithm = webidl.converters.AlgorithmIdentifier( - encapsulationAlgorithm, - { - prefix, - context: '1st argument', - }, - ); - encapsulationKey = webidl.converters.CryptoKey(encapsulationKey, { - prefix, - context: '2nd argument', - }); - sharedKeyAlgorithm = webidl.converters.AlgorithmIdentifier( - sharedKeyAlgorithm, - { - prefix, - context: '3rd argument', - }, - ); - extractable = webidl.converters.boolean(extractable, { - prefix, - context: '4th argument', - }); - const usages = webidl.converters['sequence'](keyUsages, { - prefix, - context: '5th argument', - }); + const prefix = prepareSubtleMethod( + this, 'encapsulateKey', arguments.length, 5); + let i = 0; + encapsulationAlgorithm = convertSubtleArgument( + prefix, 'AlgorithmIdentifier', encapsulationAlgorithm, i++); + encapsulationKey = convertSubtleArgument( + prefix, 'CryptoKey', encapsulationKey, i++); + sharedKeyAlgorithm = convertSubtleArgument( + prefix, 'AlgorithmIdentifier', sharedKeyAlgorithm, i++); + extractable = convertSubtleArgument(prefix, 'boolean', extractable, i++); + const usages = convertSubtleArgument( + prefix, 'sequence', keyUsages, i++); const normalizedEncapsulationAlgorithm = normalizeAlgorithm(encapsulationAlgorithm, 'encapsulate'); @@ -1588,8 +1444,12 @@ function encapsulateKeyImpl( encapsulatedBits = require('internal/crypto/ml_kem') .mlKemEncapsulate(encapsulationKey); break; - default: - throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); + /* c8 ignore start */ + default: { + const assert = require('internal/assert'); + assert.fail('Unreachable code'); + } + /* c8 ignore stop */ } return jobPromiseThen(encapsulatedBits, (encapsulatedBits) => { @@ -1616,26 +1476,14 @@ function decapsulateBits(decapsulationAlgorithm, decapsulationKey, ciphertext) { function decapsulateBitsImpl(decapsulationAlgorithm, decapsulationKey, ciphertext) { emitExperimentalWarning('The decapsulateBits Web Crypto API method'); - if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); - - webidl ??= require('internal/crypto/webidl'); - const prefix = "Failed to execute 'decapsulateBits' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - decapsulationAlgorithm = webidl.converters.AlgorithmIdentifier( - decapsulationAlgorithm, - { - prefix, - context: '1st argument', - }, - ); - decapsulationKey = webidl.converters.CryptoKey(decapsulationKey, { - prefix, - context: '2nd argument', - }); - ciphertext = webidl.converters.BufferSource(ciphertext, { - prefix, - context: '3rd argument', - }); + const prefix = prepareSubtleMethod( + this, 'decapsulateBits', arguments.length, 3); + let i = 0; + decapsulationAlgorithm = convertSubtleArgument( + prefix, 'AlgorithmIdentifier', decapsulationAlgorithm, i++); + decapsulationKey = convertSubtleArgument( + prefix, 'CryptoKey', decapsulationKey, i++); + ciphertext = convertSubtleArgument(prefix, 'BufferSource', ciphertext, i++); const normalizedDecapsulationAlgorithm = normalizeAlgorithm(decapsulationAlgorithm, 'decapsulate'); @@ -1659,9 +1507,13 @@ function decapsulateBitsImpl(decapsulationAlgorithm, decapsulationKey, ciphertex case 'ML-KEM-1024': return require('internal/crypto/ml_kem') .mlKemDecapsulate(decapsulationKey, ciphertext); + /* c8 ignore start */ + default: { + const assert = require('internal/assert'); + assert.fail('Unreachable code'); + } + /* c8 ignore stop */ } - - throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); } function decapsulateKey( @@ -1682,41 +1534,19 @@ function decapsulateKeyImpl( extractable, keyUsages) { emitExperimentalWarning('The decapsulateKey Web Crypto API method'); - if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); - - webidl ??= require('internal/crypto/webidl'); - const prefix = "Failed to execute 'decapsulateKey' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 6, { prefix }); - decapsulationAlgorithm = webidl.converters.AlgorithmIdentifier( - decapsulationAlgorithm, - { - prefix, - context: '1st argument', - }, - ); - decapsulationKey = webidl.converters.CryptoKey(decapsulationKey, { - prefix, - context: '2nd argument', - }); - ciphertext = webidl.converters.BufferSource(ciphertext, { - prefix, - context: '3rd argument', - }); - sharedKeyAlgorithm = webidl.converters.AlgorithmIdentifier( - sharedKeyAlgorithm, - { - prefix, - context: '4th argument', - }, - ); - extractable = webidl.converters.boolean(extractable, { - prefix, - context: '5th argument', - }); - const usages = webidl.converters['sequence'](keyUsages, { - prefix, - context: '6th argument', - }); + const prefix = prepareSubtleMethod( + this, 'decapsulateKey', arguments.length, 6); + let i = 0; + decapsulationAlgorithm = convertSubtleArgument( + prefix, 'AlgorithmIdentifier', decapsulationAlgorithm, i++); + decapsulationKey = convertSubtleArgument( + prefix, 'CryptoKey', decapsulationKey, i++); + ciphertext = convertSubtleArgument(prefix, 'BufferSource', ciphertext, i++); + sharedKeyAlgorithm = convertSubtleArgument( + prefix, 'AlgorithmIdentifier', sharedKeyAlgorithm, i++); + extractable = convertSubtleArgument(prefix, 'boolean', extractable, i++); + const usages = convertSubtleArgument( + prefix, 'sequence', keyUsages, i++); const normalizedDecapsulationAlgorithm = normalizeAlgorithm(decapsulationAlgorithm, 'decapsulate'); @@ -1744,8 +1574,12 @@ function decapsulateKeyImpl( decapsulatedBits = require('internal/crypto/ml_kem') .mlKemDecapsulate(decapsulationKey, ciphertext); break; - default: - throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); + /* c8 ignore start */ + default: { + const assert = require('internal/assert'); + assert.fail('Unreachable code'); + } + /* c8 ignore stop */ } return jobPromiseThen(decapsulatedBits, (decapsulatedBits) => FunctionPrototypeCall( @@ -2016,10 +1850,12 @@ function check(op, alg, length) { return true; } + /* c8 ignore start */ default: { const assert = require('internal/assert'); assert.fail('Unreachable code'); } + /* c8 ignore stop */ } } diff --git a/lib/internal/crypto/webcrypto_util.js b/lib/internal/crypto/webcrypto_util.js index 320bf0436de553..1b802a6dc46624 100644 --- a/lib/internal/crypto/webcrypto_util.js +++ b/lib/internal/crypto/webcrypto_util.js @@ -1,5 +1,10 @@ 'use strict'; +const { + ArrayPrototypePush, + SafeSet, +} = primordials; + const { KeyObjectHandle, kKeyFormatDER, @@ -12,6 +17,7 @@ const { } = internalBinding('crypto'); const { + hasAnyNotIn, validateKeyOps, } = require('internal/crypto/util'); @@ -19,6 +25,110 @@ const { lazyDOMException, } = require('internal/util'); +/** + * @typedef {object} KeyUsageLists + * @property {string[]} public Usages accepted for public keys. + * @property {string[]} private Usages accepted for private keys. + * @property {string[]} keygen Usages accepted during key generation. + */ + +/** + * @typedef {object} KeyUsageSets + * @property {SafeSet} public Requested public key usages. + * @property {SafeSet} private Requested private key usages. + */ + +/** + * Validates that every requested key usage is allowed for `subject`. + * @param {string} subject + * @param {SafeSet} usagesSet + * @param {string[]} allowed + */ +function verifyAcceptableKeyUse(subject, usagesSet, allowed) { + if (hasAnyNotIn(usagesSet, allowed)) { + throw lazyDOMException( + `Unsupported key usage for ${subject} key`, + 'SyntaxError'); + } +} + +/** + * Converts a usage list to a set and validates it against `allowed`. + * @param {string[]} usages + * @param {string[]} allowed + * @param {string} subject + * @returns {SafeSet} + */ +function validateKeyUsages(usages, allowed, subject) { + const usagesSet = new SafeSet(usages); + verifyAcceptableKeyUse(subject, usagesSet, allowed); + return usagesSet; +} + +/** + * Validates that a usage set is not empty. + * @param {SafeSet} usagesSet + * @returns {SafeSet} + */ +function validateUsagesNotEmpty(usagesSet) { + if (usagesSet.size === 0) { + throw lazyDOMException( + 'Usages cannot be empty when creating a key.', + 'SyntaxError'); + } + return usagesSet; +} + +/** + * Returns the requested usages that are present in `usagesSet`. + * @param {SafeSet} usagesSet + * @param {string[]} usages + * @returns {SafeSet} + */ +function getUsagesUnion(usagesSet, usages) { + const newset = new SafeSet(); + for (let n = 0; n < usages.length; n++) { + if (usagesSet.has(usages[n])) + newset.add(usages[n]); + } + return newset; +} + +/** + * Splits requested usages into public and private key usage sets. + * @param {SafeSet} usagesSet + * @param {KeyUsageLists} allowed + * @returns {KeyUsageSets} + */ +function getKeyPairUsages(usagesSet, allowed) { + return { + public: getUsagesUnion(usagesSet, allowed.public), + private: getUsagesUnion(usagesSet, allowed.private), + }; +} + +/** + * Creates the accepted public, private, and key generation usage lists. + * @param {string[]} publicUsages + * @param {string[]} privateUsages + * @returns {KeyUsageLists} + */ +function createKeyUsages(publicUsages, privateUsages) { + const keygen = []; + for (let n = 0; n < publicUsages.length; n++) { + ArrayPrototypePush(keygen, publicUsages[n]); + } + for (let n = 0; n < privateUsages.length; n++) { + ArrayPrototypePush(keygen, privateUsages[n]); + } + return { + __proto__: null, + public: publicUsages, + private: privateUsages, + keygen, + }; +} + function importDerKey(keyData, isPublic) { const handle = new KeyObjectHandle(); const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate; @@ -74,11 +184,12 @@ function validateJwk(keyData, kty, extractable, usagesSet, expectedUse) { (keyData.priv !== undefined && typeof keyData.priv !== 'string')) throw lazyDOMException('Invalid keyData', 'DataError'); break; + /* c8 ignore start */ default: { - // It is not possible to get here because all possible cases are handled above. const assert = require('internal/assert'); assert.fail('Unreachable code'); } + /* c8 ignore stop */ } if (usagesSet.size > 0 && keyData.use !== undefined) { if (keyData.use !== expectedUse) @@ -136,10 +247,15 @@ function importJwkSecretKey(keyData) { } module.exports = { + createKeyUsages, + getKeyPairUsages, importDerKey, importJwkKey, importJwkSecretKey, importRawKey, importSecretKey, validateJwk, + validateKeyUsages, + validateUsagesNotEmpty, + verifyAcceptableKeyUse, };