diff --git a/doc/api/errors.md b/doc/api/errors.md index 5dbb2106163810..350a9260cdbdc0 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -3104,7 +3104,7 @@ The context must be a `SecureContext`. ### `ERR_TLS_INVALID_PROTOCOL_METHOD` -The specified `secureProtocol` method is invalid. It is either unknown, or +The specified `secureProtocol` method is invalid. It is either unknown, or disabled because it is insecure. diff --git a/doc/api/webcrypto.md b/doc/api/webcrypto.md index f357ebb6ae0282..164b8569fb5299 100644 --- a/doc/api/webcrypto.md +++ b/doc/api/webcrypto.md @@ -850,7 +850,7 @@ The algorithms currently supported include: * `'ML-KEM-768'`[^modern-algos] * `'ML-KEM-1024'`[^modern-algos] -### `subtle.decapsulateKey(decapsulationAlgorithm, decapsulationKey, ciphertext, sharedKeyAlgorithm, extractable, usages)` +### `subtle.decapsulateKey(decapsulationAlgorithm, decapsulationKey, ciphertext, sharedKeyAlgorithm, extractable, keyUsages)` -* `unwrapAlgo` {string|Algorithm|RsaOaepParams|AesCtrParams|AesCbcParams|AeadParams} -* `unwrappedKeyAlgo` {string|Algorithm|RsaHashedImportParams|EcKeyImportParams|HmacImportParams|KmacImportParams} +* `unwrapAlgorithm` {string|Algorithm|RsaOaepParams|AesCtrParams|AesCbcParams|AeadParams} +* `unwrappedKeyAlgorithm` {string|Algorithm|RsaHashedImportParams|EcKeyImportParams|HmacImportParams|KmacImportParams} @@ -1452,8 +1452,8 @@ In cryptography, "wrapping a key" refers to exporting and then encrypting the keying material. This method attempts to decrypt a wrapped key and create a {CryptoKey} instance. It is equivalent to calling [`subtle.decrypt()`][] first on the encrypted key data (using the `wrappedKey`, -`unwrapAlgo`, and `unwrappingKey` arguments as input) then passing the results -to the [`subtle.importKey()`][] method using the `unwrappedKeyAlgo`, +`unwrapAlgorithm`, and `unwrappingKey` arguments as input) then passing the results +to the [`subtle.importKey()`][] method using the `unwrappedKeyAlgorithm`, `extractable`, and `keyUsages` arguments as inputs. If successful, the returned promise is resolved with a {CryptoKey} object. @@ -1541,7 +1541,7 @@ The algorithms currently supported include: * `'RSA-PSS'` * `'RSASSA-PKCS1-v1_5'` -### `subtle.wrapKey(format, key, wrappingKey, wrapAlgo)` +### `subtle.wrapKey(format, key, wrappingKey, wrapAlgorithm)` @@ -1568,10 +1568,10 @@ changes: In cryptography, "wrapping a key" refers to exporting and then encrypting the keying material. This method exports the keying material into the format identified by `format`, then encrypts it using the method and -parameters specified by `wrapAlgo` and the keying material provided by +parameters specified by `wrapAlgorithm` and the keying material provided by `wrappingKey`. It is the equivalent to calling [`subtle.exportKey()`][] using `format` and `key` as the arguments, then passing the result to the -[`subtle.encrypt()`][] method using `wrappingKey` and `wrapAlgo` as inputs. If +[`subtle.encrypt()`][] method using `wrappingKey` and `wrapAlgorithm` as inputs. If successful, the returned promise will be resolved with an {ArrayBuffer} containing the encrypted key data. @@ -2815,19 +2815,19 @@ added: [Web Crypto API]: https://www.w3.org/TR/WebCryptoAPI/ [`SubtleCrypto.supports()`]: #static-method-subtlecryptosupportsoperation-algorithm-lengthoradditionalalgorithm [`subtle.decapsulateBits()`]: #subtledecapsulatebitsdecapsulationalgorithm-decapsulationkey-ciphertext -[`subtle.decapsulateKey()`]: #subtledecapsulatekeydecapsulationalgorithm-decapsulationkey-ciphertext-sharedkeyalgorithm-extractable-usages +[`subtle.decapsulateKey()`]: #subtledecapsulatekeydecapsulationalgorithm-decapsulationkey-ciphertext-sharedkeyalgorithm-extractable-keyusages [`subtle.decrypt()`]: #subtledecryptalgorithm-key-data [`subtle.deriveBits()`]: #subtlederivebitsalgorithm-basekey-length -[`subtle.deriveKey()`]: #subtlederivekeyalgorithm-basekey-derivedkeyalgorithm-extractable-keyusages +[`subtle.deriveKey()`]: #subtlederivekeyalgorithm-basekey-derivedkeytype-extractable-keyusages [`subtle.digest()`]: #subtledigestalgorithm-data [`subtle.encapsulateBits()`]: #subtleencapsulatebitsencapsulationalgorithm-encapsulationkey -[`subtle.encapsulateKey()`]: #subtleencapsulatekeyencapsulationalgorithm-encapsulationkey-sharedkeyalgorithm-extractable-usages +[`subtle.encapsulateKey()`]: #subtleencapsulatekeyencapsulationalgorithm-encapsulationkey-sharedkeyalgorithm-extractable-keyusages [`subtle.encrypt()`]: #subtleencryptalgorithm-key-data [`subtle.exportKey()`]: #subtleexportkeyformat-key [`subtle.generateKey()`]: #subtlegeneratekeyalgorithm-extractable-keyusages [`subtle.getPublicKey()`]: #subtlegetpublickeykey-keyusages [`subtle.importKey()`]: #subtleimportkeyformat-keydata-algorithm-extractable-keyusages [`subtle.sign()`]: #subtlesignalgorithm-key-data -[`subtle.unwrapKey()`]: #subtleunwrapkeyformat-wrappedkey-unwrappingkey-unwrapalgo-unwrappedkeyalgo-extractable-keyusages +[`subtle.unwrapKey()`]: #subtleunwrapkeyformat-wrappedkey-unwrappingkey-unwrapalgorithm-unwrappedkeyalgorithm-extractable-keyusages [`subtle.verify()`]: #subtleverifyalgorithm-key-signature-data -[`subtle.wrapKey()`]: #subtlewrapkeyformat-key-wrappingkey-wrapalgo +[`subtle.wrapKey()`]: #subtlewrapkeyformat-key-wrappingkey-wrapalgorithm diff --git a/lib/internal/crypto/aes.js b/lib/internal/crypto/aes.js index 2ed6c69f43e3d4..981502c51700be 100644 --- a/lib/internal/crypto/aes.js +++ b/lib/internal/crypto/aes.js @@ -175,14 +175,14 @@ function aesCipher(mode, key, data, algorithm) { } } -function aesGenerateKey(algorithm, extractable, keyUsages) { +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(keyUsages); + const usagesSet = new SafeSet(usages); if (hasAnyNotIn(usagesSet, checkUsages)) { throw lazyDOMException( 'Unsupported key usage for an AES key', @@ -207,13 +207,13 @@ function aesImportKey( format, keyData, extractable, - keyUsages) { + usages) { const { name } = algorithm; const checkUsages = ['wrapKey', 'unwrapKey']; if (name !== 'AES-KW') ArrayPrototypePush(checkUsages, 'encrypt', 'decrypt'); - const usagesSet = new SafeSet(keyUsages); + const usagesSet = new SafeSet(usages); if (hasAnyNotIn(usagesSet, checkUsages)) { throw lazyDOMException( 'Unsupported key usage for an AES key', diff --git a/lib/internal/crypto/cfrg.js b/lib/internal/crypto/cfrg.js index 98b1862b7ad1f8..3e6152b1f55501 100644 --- a/lib/internal/crypto/cfrg.js +++ b/lib/internal/crypto/cfrg.js @@ -73,10 +73,10 @@ function verifyAcceptableCfrgKeyUse(name, isPublic, usages) { } } -function cfrgGenerateKey(algorithm, extractable, keyUsages) { +function cfrgGenerateKey(algorithm, extractable, usages) { const { name } = algorithm; - const usageSet = new SafeSet(keyUsages); + const usageSet = new SafeSet(usages); switch (name) { case 'Ed25519': // Fall through @@ -170,11 +170,11 @@ function cfrgImportKey( keyData, algorithm, extractable, - keyUsages) { + usages) { const { name } = algorithm; let handle; - const usagesSet = new SafeSet(keyUsages); + const usagesSet = new SafeSet(usages); switch (format) { case 'KeyObject': { verifyAcceptableCfrgKeyUse( diff --git a/lib/internal/crypto/chacha20_poly1305.js b/lib/internal/crypto/chacha20_poly1305.js index 9d4606090af8ee..689cab59f3fbf2 100644 --- a/lib/internal/crypto/chacha20_poly1305.js +++ b/lib/internal/crypto/chacha20_poly1305.js @@ -47,12 +47,12 @@ function c20pCipher(mode, key, data, algorithm) { algorithm.additionalData)); } -function c20pGenerateKey(algorithm, extractable, keyUsages) { +function c20pGenerateKey(algorithm, extractable, usages) { const { name } = algorithm; const checkUsages = ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey']; - const usagesSet = new SafeSet(keyUsages); + const usagesSet = new SafeSet(usages); if (hasAnyNotIn(usagesSet, checkUsages)) { throw lazyDOMException( `Unsupported key usage for a ${algorithm.name} key`, @@ -77,11 +77,11 @@ function c20pImportKey( format, keyData, extractable, - keyUsages) { + usages) { const { name } = algorithm; const checkUsages = ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey']; - const usagesSet = new SafeSet(keyUsages); + const usagesSet = new SafeSet(usages); if (hasAnyNotIn(usagesSet, checkUsages)) { throw lazyDOMException( `Unsupported key usage for a ${algorithm.name} key`, diff --git a/lib/internal/crypto/ec.js b/lib/internal/crypto/ec.js index 212ba75e0a9b11..d102b3fe05a29c 100644 --- a/lib/internal/crypto/ec.js +++ b/lib/internal/crypto/ec.js @@ -77,10 +77,10 @@ function verifyAcceptableEcKeyUse(name, isPublic, usages) { } } -function ecGenerateKey(algorithm, extractable, keyUsages) { +function ecGenerateKey(algorithm, extractable, usages) { const { name, namedCurve } = algorithm; - const usageSet = new SafeSet(keyUsages); + const usageSet = new SafeSet(usages); switch (name) { case 'ECDSA': if (hasAnyNotIn(usageSet, ['sign', 'verify'])) { @@ -178,12 +178,12 @@ function ecImportKey( keyData, algorithm, extractable, - keyUsages, + usages, ) { const { name, namedCurve } = algorithm; let handle; - const usagesSet = new SafeSet(keyUsages); + const usagesSet = new SafeSet(usages); switch (format) { case 'KeyObject': { verifyAcceptableEcKeyUse( diff --git a/lib/internal/crypto/mac.js b/lib/internal/crypto/mac.js index c3418231650b02..724b2104d4b8c8 100644 --- a/lib/internal/crypto/mac.js +++ b/lib/internal/crypto/mac.js @@ -40,14 +40,14 @@ const { validateJwk, } = require('internal/crypto/webcrypto_util'); -function hmacGenerateKey(algorithm, extractable, keyUsages) { +function hmacGenerateKey(algorithm, extractable, usages) { const { hash, name, length = getBlockSize(hash.name), } = algorithm; - const usageSet = new SafeSet(keyUsages); + const usageSet = new SafeSet(usages); if (hasAnyNotIn(usageSet, ['sign', 'verify'])) { throw lazyDOMException( 'Unsupported key usage for an HMAC key', @@ -67,7 +67,7 @@ function hmacGenerateKey(algorithm, extractable, keyUsages) { extractable)); } -function kmacGenerateKey(algorithm, extractable, keyUsages) { +function kmacGenerateKey(algorithm, extractable, usages) { const { name, length = { @@ -77,7 +77,7 @@ function kmacGenerateKey(algorithm, extractable, keyUsages) { }[name], } = algorithm; - const usageSet = new SafeSet(keyUsages); + const usageSet = new SafeSet(usages); if (hasAnyNotIn(usageSet, ['sign', 'verify'])) { throw lazyDOMException( `Unsupported key usage for ${name} key`, @@ -102,10 +102,10 @@ function macImportKey( keyData, algorithm, extractable, - keyUsages, + usages, ) { const isHmac = algorithm.name === 'HMAC'; - const usagesSet = new SafeSet(keyUsages); + const usagesSet = new SafeSet(usages); if (hasAnyNotIn(usagesSet, ['sign', 'verify'])) { throw lazyDOMException( `Unsupported key usage for ${algorithm.name} key`, diff --git a/lib/internal/crypto/ml_dsa.js b/lib/internal/crypto/ml_dsa.js index 5a08291562bcf2..e2497a2b722b97 100644 --- a/lib/internal/crypto/ml_dsa.js +++ b/lib/internal/crypto/ml_dsa.js @@ -59,10 +59,10 @@ function verifyAcceptableMlDsaKeyUse(name, isPublic, usages) { } } -function mlDsaGenerateKey(algorithm, extractable, keyUsages) { +function mlDsaGenerateKey(algorithm, extractable, usages) { const { name } = algorithm; - const usageSet = new SafeSet(keyUsages); + const usageSet = new SafeSet(usages); if (hasAnyNotIn(usageSet, ['sign', 'verify'])) { throw lazyDOMException( `Unsupported key usage for an ${name} key`, @@ -136,11 +136,11 @@ function mlDsaImportKey( keyData, algorithm, extractable, - keyUsages) { + usages) { const { name } = algorithm; let handle; - const usagesSet = new SafeSet(keyUsages); + const usagesSet = new SafeSet(usages); switch (format) { case 'KeyObject': { verifyAcceptableMlDsaKeyUse( diff --git a/lib/internal/crypto/ml_kem.js b/lib/internal/crypto/ml_kem.js index 530507be4e340d..2dea4d00af052f 100644 --- a/lib/internal/crypto/ml_kem.js +++ b/lib/internal/crypto/ml_kem.js @@ -49,10 +49,10 @@ const { validateJwk, } = require('internal/crypto/webcrypto_util'); -function mlKemGenerateKey(algorithm, extractable, keyUsages) { +function mlKemGenerateKey(algorithm, extractable, usages) { const { name } = algorithm; - const usageSet = new SafeSet(keyUsages); + const usageSet = new SafeSet(usages); if (hasAnyNotIn(usageSet, ['encapsulateKey', 'encapsulateBits', 'decapsulateKey', 'decapsulateBits'])) { throw lazyDOMException( `Unsupported key usage for an ${name} key`, @@ -137,11 +137,11 @@ function mlKemImportKey( keyData, algorithm, extractable, - keyUsages) { + usages) { const { name } = algorithm; let handle; - const usagesSet = new SafeSet(keyUsages); + const usagesSet = new SafeSet(usages); switch (format) { case 'KeyObject': { verifyAcceptableMlKemKeyUse( diff --git a/lib/internal/crypto/rsa.js b/lib/internal/crypto/rsa.js index d72d55c2bbff42..a09dd7b9f0fda9 100644 --- a/lib/internal/crypto/rsa.js +++ b/lib/internal/crypto/rsa.js @@ -108,7 +108,7 @@ function rsaOaepCipher(mode, key, data, algorithm) { function rsaKeyGenerate( algorithm, extractable, - keyUsages, + usages, ) { const publicExponentConverted = bigIntArrayToUnsignedInt(algorithm.publicExponent); if (publicExponentConverted === undefined) { @@ -123,7 +123,7 @@ function rsaKeyGenerate( hash, } = algorithm; - const usageSet = new SafeSet(keyUsages); + const usageSet = new SafeSet(usages); switch (name) { case 'RSA-OAEP': @@ -213,8 +213,8 @@ function rsaImportKey( keyData, algorithm, extractable, - keyUsages) { - const usagesSet = new SafeSet(keyUsages); + usages) { + const usagesSet = new SafeSet(usages); let handle; switch (format) { case 'KeyObject': { diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js index 046efc4554ca36..74d86de3f1b9e1 100644 --- a/lib/internal/crypto/util.js +++ b/lib/internal/crypto/util.js @@ -960,6 +960,7 @@ module.exports = { toBuf, kNamedCurveAliases, + kSupportedAlgorithms, normalizeAlgorithm, normalizeHashName, hasAnyNotIn, diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js index 996bcb1a729275..05c337d3262229 100644 --- a/lib/internal/crypto/webcrypto.js +++ b/lib/internal/crypto/webcrypto.js @@ -128,9 +128,9 @@ function digestImpl(algorithm, data) { context: '2nd argument', }); - algorithm = normalizeAlgorithm(algorithm, 'digest'); + const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'digest'); - return FunctionPrototypeCall(asyncDigest, this, algorithm, data); + return FunctionPrototypeCall(asyncDigest, this, normalizedAlgorithm, data); } function randomUUID() { @@ -162,20 +162,20 @@ function generateKeyImpl( prefix, context: '2nd argument', }); - keyUsages = webidl.converters['sequence'](keyUsages, { + const usages = webidl.converters['sequence'](keyUsages, { prefix, context: '3rd argument', }); - algorithm = normalizeAlgorithm(algorithm, 'generateKey'); - switch (algorithm.name) { + const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'generateKey'); + switch (normalizedAlgorithm.name) { case 'RSASSA-PKCS1-v1_5': // Fall through case 'RSA-PSS': // Fall through case 'RSA-OAEP': return require('internal/crypto/rsa') - .rsaKeyGenerate(algorithm, extractable, keyUsages); + .rsaKeyGenerate(normalizedAlgorithm, extractable, usages); case 'Ed25519': // Fall through case 'Ed448': @@ -184,15 +184,15 @@ function generateKeyImpl( // Fall through case 'X448': return require('internal/crypto/cfrg') - .cfrgGenerateKey(algorithm, extractable, keyUsages); + .cfrgGenerateKey(normalizedAlgorithm, extractable, usages); case 'ECDSA': // Fall through case 'ECDH': return require('internal/crypto/ec') - .ecGenerateKey(algorithm, extractable, keyUsages); + .ecGenerateKey(normalizedAlgorithm, extractable, usages); case 'HMAC': return require('internal/crypto/mac') - .hmacGenerateKey(algorithm, extractable, keyUsages); + .hmacGenerateKey(normalizedAlgorithm, extractable, usages); case 'AES-CTR': // Fall through case 'AES-CBC': @@ -203,29 +203,29 @@ function generateKeyImpl( // Fall through case 'AES-KW': return require('internal/crypto/aes') - .aesGenerateKey(algorithm, extractable, keyUsages); + .aesGenerateKey(normalizedAlgorithm, extractable, usages); case 'ChaCha20-Poly1305': return require('internal/crypto/chacha20_poly1305') - .c20pGenerateKey(algorithm, extractable, keyUsages); + .c20pGenerateKey(normalizedAlgorithm, extractable, usages); case 'ML-DSA-44': // Fall through case 'ML-DSA-65': // Fall through case 'ML-DSA-87': return require('internal/crypto/ml_dsa') - .mlDsaGenerateKey(algorithm, extractable, keyUsages); + .mlDsaGenerateKey(normalizedAlgorithm, extractable, usages); case 'ML-KEM-512': // Fall through case 'ML-KEM-768': // Fall through case 'ML-KEM-1024': return require('internal/crypto/ml_kem') - .mlKemGenerateKey(algorithm, extractable, keyUsages); + .mlKemGenerateKey(normalizedAlgorithm, extractable, usages); case 'KMAC128': // Fall through case 'KMAC256': return require('internal/crypto/mac') - .kmacGenerateKey(algorithm, extractable, keyUsages); + .kmacGenerateKey(normalizedAlgorithm, extractable, usages); default: throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); } @@ -256,35 +256,35 @@ function deriveBitsImpl(algorithm, baseKey, length = null) { }); } - algorithm = normalizeAlgorithm(algorithm, 'deriveBits'); + const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'deriveBits'); if (!hasCryptoKeyUsage(baseKey, 'deriveBits')) { throw lazyDOMException( 'baseKey does not have deriveBits usage', 'InvalidAccessError'); } - if (getCryptoKeyAlgorithm(baseKey).name !== algorithm.name) + if (getCryptoKeyAlgorithm(baseKey).name !== normalizedAlgorithm.name) throw lazyDOMException('Key algorithm mismatch', 'InvalidAccessError'); - switch (algorithm.name) { + switch (normalizedAlgorithm.name) { case 'X25519': // Fall through case 'X448': // Fall through case 'ECDH': return require('internal/crypto/diffiehellman') - .ecdhDeriveBits(algorithm, baseKey, length); + .ecdhDeriveBits(normalizedAlgorithm, baseKey, length); case 'HKDF': return require('internal/crypto/hkdf') - .hkdfDeriveBits(algorithm, baseKey, length); + .hkdfDeriveBits(normalizedAlgorithm, baseKey, length); case 'PBKDF2': return require('internal/crypto/pbkdf2') - .pbkdf2DeriveBits(algorithm, baseKey, length); + .pbkdf2DeriveBits(normalizedAlgorithm, baseKey, length); case 'Argon2d': // Fall through case 'Argon2i': // Fall through case 'Argon2id': return require('internal/crypto/argon2') - .argon2DeriveBits(algorithm, baseKey, length); + .argon2DeriveBits(normalizedAlgorithm, baseKey, length); } throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); } @@ -331,7 +331,7 @@ function getKeyLength({ name, length, hash }) { function deriveKey( algorithm, baseKey, - derivedKeyAlgorithm, + derivedKeyType, extractable, keyUsages) { return callSubtleCryptoMethod(deriveKeyImpl, this, arguments); @@ -340,7 +340,7 @@ function deriveKey( function deriveKeyImpl( algorithm, baseKey, - derivedKeyAlgorithm, + derivedKeyType, extractable, keyUsages) { if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); @@ -356,7 +356,7 @@ function deriveKeyImpl( prefix, context: '2nd argument', }); - derivedKeyAlgorithm = webidl.converters.AlgorithmIdentifier(derivedKeyAlgorithm, { + derivedKeyType = webidl.converters.AlgorithmIdentifier(derivedKeyType, { prefix, context: '3rd argument', }); @@ -364,56 +364,63 @@ function deriveKeyImpl( prefix, context: '4th argument', }); - keyUsages = webidl.converters['sequence'](keyUsages, { + const usages = webidl.converters['sequence'](keyUsages, { prefix, context: '5th argument', }); - algorithm = normalizeAlgorithm(algorithm, 'deriveBits'); - derivedKeyAlgorithm = normalizeAlgorithm(derivedKeyAlgorithm, 'importKey'); + const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'deriveBits'); + const normalizedDerivedKeyAlgorithmImport = + normalizeAlgorithm(derivedKeyType, 'importKey'); + const normalizedDerivedKeyAlgorithmLength = + normalizeAlgorithm(derivedKeyType, 'get key length'); if (!hasCryptoKeyUsage(baseKey, 'deriveKey')) { throw lazyDOMException( 'baseKey does not have deriveKey usage', 'InvalidAccessError'); } - if (getCryptoKeyAlgorithm(baseKey).name !== algorithm.name) + if (getCryptoKeyAlgorithm(baseKey).name !== normalizedAlgorithm.name) throw lazyDOMException('Key algorithm mismatch', 'InvalidAccessError'); - const length = getKeyLength(normalizeAlgorithm(arguments[2], 'get key length')); - let bits; - switch (algorithm.name) { + const length = getKeyLength(normalizedDerivedKeyAlgorithmLength); + let secret; + switch (normalizedAlgorithm.name) { case 'X25519': // Fall through case 'X448': // Fall through case 'ECDH': - bits = require('internal/crypto/diffiehellman') - .ecdhDeriveBits(algorithm, baseKey, length); + secret = require('internal/crypto/diffiehellman') + .ecdhDeriveBits(normalizedAlgorithm, baseKey, length); break; case 'HKDF': - bits = require('internal/crypto/hkdf') - .hkdfDeriveBits(algorithm, baseKey, length); + secret = require('internal/crypto/hkdf') + .hkdfDeriveBits(normalizedAlgorithm, baseKey, length); break; case 'PBKDF2': - bits = require('internal/crypto/pbkdf2') - .pbkdf2DeriveBits(algorithm, baseKey, length); + secret = require('internal/crypto/pbkdf2') + .pbkdf2DeriveBits(normalizedAlgorithm, baseKey, length); break; case 'Argon2d': // Fall through case 'Argon2i': // Fall through case 'Argon2id': - bits = require('internal/crypto/argon2') - .argon2DeriveBits(algorithm, baseKey, length); + secret = require('internal/crypto/argon2') + .argon2DeriveBits(normalizedAlgorithm, baseKey, length); break; default: throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); } - return jobPromiseThen(bits, (bits) => FunctionPrototypeCall( + return jobPromiseThen(secret, (secret) => FunctionPrototypeCall( importKeySync, this, - 'raw-secret', bits, derivedKeyAlgorithm, extractable, keyUsages, + 'raw-secret', + secret, + normalizedDerivedKeyAlgorithmImport, + extractable, + usages, )); } @@ -805,14 +812,14 @@ function detachFromUserPrototypes(value) { } // Parse wrapped JWK bytes according to WebCrypto's "parse a JWK" procedure. -function parseJwk(keyData) { +function parseJwk(data) { let key; try { // WebCrypto parses JWKs in a fresh global. Detach parsed JSON values // from user-mutated prototypes before WebIDL dictionary conversion. // Wrapped JWKs may be produced outside WebCrypto, so parse using the // spec-required UTF-8. - const json = decodeUTF8(keyData, false, true); + const json = decodeUTF8(data, false, true); const result = JSONParse(json); detachFromUserPrototypes(result); key = webidl.converters.JsonWebKey(result); @@ -841,7 +848,7 @@ function aliasKeyFormat(format) { } } -function importKeySync(format, keyData, algorithm, extractable, keyUsages) { +function importKeySync(format, keyData, algorithm, extractable, usages) { let result; switch (algorithm.name) { case 'RSASSA-PKCS1-v1_5': @@ -851,14 +858,24 @@ function importKeySync(format, keyData, algorithm, extractable, keyUsages) { case 'RSA-OAEP': format = aliasKeyFormat(format); result = require('internal/crypto/rsa') - .rsaImportKey(format, keyData, algorithm, extractable, keyUsages); + .rsaImportKey( + format, + keyData, + algorithm, + extractable, + usages); break; case 'ECDSA': // Fall through case 'ECDH': format = aliasKeyFormat(format); result = require('internal/crypto/ec') - .ecImportKey(format, keyData, algorithm, extractable, keyUsages); + .ecImportKey( + format, + keyData, + algorithm, + extractable, + usages); break; case 'Ed25519': // Fall through @@ -869,7 +886,12 @@ function importKeySync(format, keyData, algorithm, extractable, keyUsages) { case 'X448': format = aliasKeyFormat(format); result = require('internal/crypto/cfrg') - .cfrgImportKey(format, keyData, algorithm, extractable, keyUsages); + .cfrgImportKey( + format, + keyData, + algorithm, + extractable, + usages); break; case 'HMAC': // Fall through @@ -877,7 +899,12 @@ function importKeySync(format, keyData, algorithm, extractable, keyUsages) { // Fall through case 'KMAC256': result = require('internal/crypto/mac') - .macImportKey(format, keyData, algorithm, extractable, keyUsages); + .macImportKey( + format, + keyData, + algorithm, + extractable, + usages); break; case 'AES-CTR': // Fall through @@ -889,11 +916,21 @@ function importKeySync(format, keyData, algorithm, extractable, keyUsages) { // Fall through case 'AES-OCB': result = require('internal/crypto/aes') - .aesImportKey(algorithm, format, keyData, extractable, keyUsages); + .aesImportKey( + algorithm, + format, + keyData, + extractable, + usages); break; case 'ChaCha20-Poly1305': result = require('internal/crypto/chacha20_poly1305') - .c20pImportKey(algorithm, format, keyData, extractable, keyUsages); + .c20pImportKey( + algorithm, + format, + keyData, + extractable, + usages); break; case 'HKDF': // Fall through @@ -904,7 +941,7 @@ function importKeySync(format, keyData, algorithm, extractable, keyUsages) { format, keyData, extractable, - keyUsages); + usages); break; case 'Argon2d': // Fall through @@ -917,7 +954,7 @@ function importKeySync(format, keyData, algorithm, extractable, keyUsages) { format, keyData, extractable, - keyUsages); + usages); } break; case 'ML-DSA-44': @@ -926,7 +963,12 @@ function importKeySync(format, keyData, algorithm, extractable, keyUsages) { // Fall through case 'ML-DSA-87': result = require('internal/crypto/ml_dsa') - .mlDsaImportKey(format, keyData, algorithm, extractable, keyUsages); + .mlDsaImportKey( + format, + keyData, + algorithm, + extractable, + usages); break; case 'ML-KEM-512': // Fall through @@ -934,7 +976,12 @@ function importKeySync(format, keyData, algorithm, extractable, keyUsages) { // Fall through case 'ML-KEM-1024': result = require('internal/crypto/ml_kem') - .mlKemImportKey(format, keyData, algorithm, extractable, keyUsages); + .mlKemImportKey( + format, + keyData, + algorithm, + extractable, + usages); break; } @@ -991,27 +1038,31 @@ function importKeyImpl( prefix, context: '4th argument', }); - keyUsages = webidl.converters['sequence'](keyUsages, { + const usages = webidl.converters['sequence'](keyUsages, { prefix, context: '5th argument', }); - algorithm = normalizeAlgorithm(algorithm, 'importKey'); + const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'importKey'); return FunctionPrototypeCall( importKeySync, this, - format, keyData, algorithm, extractable, keyUsages, + format, + keyData, + normalizedAlgorithm, + extractable, + usages, ); } // subtle.wrapKey() is essentially a subtle.exportKey() followed // by a subtle.encrypt(). -function wrapKey(format, key, wrappingKey, algorithm) { +function wrapKey(format, key, wrappingKey, wrapAlgorithm) { return callSubtleCryptoMethod(wrapKeyImpl, this, arguments); } -function wrapKeyImpl(format, key, wrappingKey, algorithm) { +function wrapKeyImpl(format, key, wrappingKey, wrapAlgorithm) { if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); webidl ??= require('internal/crypto/webidl'); @@ -1029,50 +1080,52 @@ function wrapKeyImpl(format, key, wrappingKey, algorithm) { prefix, context: '3rd argument', }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + const algorithm = webidl.converters.AlgorithmIdentifier(wrapAlgorithm, { prefix, context: '4th argument', }); + let normalizedAlgorithm; try { - algorithm = normalizeAlgorithm(algorithm, 'wrapKey'); + normalizedAlgorithm = normalizeAlgorithm(algorithm, 'wrapKey'); } catch { - algorithm = normalizeAlgorithm(algorithm, 'encrypt'); + normalizedAlgorithm = normalizeAlgorithm(algorithm, 'encrypt'); } - if (algorithm.name !== getCryptoKeyAlgorithm(wrappingKey).name) + if (normalizedAlgorithm.name !== getCryptoKeyAlgorithm(wrappingKey).name) throw lazyDOMException('Key algorithm mismatch', 'InvalidAccessError'); if (!hasCryptoKeyUsage(wrappingKey, 'wrapKey')) throw lazyDOMException( 'Unable to use this key to wrapKey', 'InvalidAccessError'); - let keyData = exportKeySync(format, key); + const exportedKey = exportKeySync(format, key); + let bytes = exportedKey; if (format === 'jwk') { // The WebCrypto spec stringifies JWKs in a new global object. Rather // than create a new realm here, detach this internally generated JWK from // user-mutated prototypes so JSON.stringify cannot read inherited toJSON // hooks from the current global. - detachFromUserPrototypes(keyData); - const json = JSONStringify(keyData); + detachFromUserPrototypes(exportedKey); + const json = JSONStringify(exportedKey); // As per the NOTE in step 13 https://w3c.github.io/webcrypto/#SubtleCrypto-method-wrapKey // we're padding AES-KW wrapped JWK to make sure it is always a multiple of 8 bytes // in length // The spec then UTF-8 encodes json. - if (algorithm.name === 'AES-KW' && json.length % 8 !== 0) { - keyData = encodeUtf8String( + if (normalizedAlgorithm.name === 'AES-KW' && json.length % 8 !== 0) { + bytes = encodeUtf8String( json + StringPrototypeRepeat(' ', 8 - (json.length % 8))); } else { - keyData = encodeUtf8String(json); + bytes = encodeUtf8String(json); } } return cipherOrWrap( kWebCryptoCipherEncrypt, - algorithm, + normalizedAlgorithm, wrappingKey, - keyData, + bytes, 'wrapKey'); } @@ -1082,8 +1135,8 @@ function unwrapKey( format, wrappedKey, unwrappingKey, - unwrapAlgo, - unwrappedKeyAlgo, + unwrapAlgorithm, + unwrappedKeyAlgorithm, extractable, keyUsages) { return callSubtleCryptoMethod(unwrapKeyImpl, this, arguments); @@ -1093,8 +1146,8 @@ function unwrapKeyImpl( format, wrappedKey, unwrappingKey, - unwrapAlgo, - unwrappedKeyAlgo, + unwrapAlgorithm, + unwrappedKeyAlgorithm, extractable, keyUsages) { if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); @@ -1114,12 +1167,12 @@ function unwrapKeyImpl( prefix, context: '3rd argument', }); - unwrapAlgo = webidl.converters.AlgorithmIdentifier(unwrapAlgo, { + const algorithm = webidl.converters.AlgorithmIdentifier(unwrapAlgorithm, { prefix, context: '4th argument', }); - unwrappedKeyAlgo = webidl.converters.AlgorithmIdentifier( - unwrappedKeyAlgo, + unwrappedKeyAlgorithm = webidl.converters.AlgorithmIdentifier( + unwrappedKeyAlgorithm, { prefix, context: '5th argument', @@ -1129,87 +1182,94 @@ function unwrapKeyImpl( prefix, context: '6th argument', }); - keyUsages = webidl.converters['sequence'](keyUsages, { + const usages = webidl.converters['sequence'](keyUsages, { prefix, context: '7th argument', }); + let normalizedAlgorithm; try { - unwrapAlgo = normalizeAlgorithm(unwrapAlgo, 'unwrapKey'); + normalizedAlgorithm = normalizeAlgorithm(algorithm, 'unwrapKey'); } catch { - unwrapAlgo = normalizeAlgorithm(unwrapAlgo, 'decrypt'); + normalizedAlgorithm = normalizeAlgorithm(algorithm, 'decrypt'); } - unwrappedKeyAlgo = normalizeAlgorithm(unwrappedKeyAlgo, 'importKey'); + const normalizedKeyAlgorithm = + normalizeAlgorithm(unwrappedKeyAlgorithm, 'importKey'); - if (unwrapAlgo.name !== getCryptoKeyAlgorithm(unwrappingKey).name) + if (normalizedAlgorithm.name !== getCryptoKeyAlgorithm(unwrappingKey).name) throw lazyDOMException('Key algorithm mismatch', 'InvalidAccessError'); if (!hasCryptoKeyUsage(unwrappingKey, 'unwrapKey')) throw lazyDOMException( 'Unable to use this key to unwrapKey', 'InvalidAccessError'); - const keyData = cipherOrWrap( + const bytes = cipherOrWrap( kWebCryptoCipherDecrypt, - unwrapAlgo, + normalizedAlgorithm, unwrappingKey, wrappedKey, 'unwrapKey'); - return jobPromiseThen(keyData, (keyData) => { + return jobPromiseThen(bytes, (bytes) => { + let keyData = bytes; if (format === 'jwk') { - keyData = parseJwk(keyData); + keyData = parseJwk(bytes); } return FunctionPrototypeCall( importKeySync, this, - format, keyData, unwrappedKeyAlgo, extractable, keyUsages, + format, + keyData, + normalizedKeyAlgorithm, + extractable, + usages, ); }); } function signVerify(algorithm, key, data, signature) { - const op = signature !== undefined ? 'verify' : 'sign'; // This is also usage - algorithm = normalizeAlgorithm(algorithm, op); + const operation = signature !== undefined ? 'verify' : 'sign'; // This is also usage + const normalizedAlgorithm = normalizeAlgorithm(algorithm, operation); - if (algorithm.name !== getCryptoKeyAlgorithm(key).name) + if (normalizedAlgorithm.name !== getCryptoKeyAlgorithm(key).name) throw lazyDOMException('Key algorithm mismatch', 'InvalidAccessError'); - if (!hasCryptoKeyUsage(key, op)) + if (!hasCryptoKeyUsage(key, operation)) throw lazyDOMException( - `Unable to use this key to ${op}`, 'InvalidAccessError'); + `Unable to use this key to ${operation}`, 'InvalidAccessError'); - switch (algorithm.name) { + switch (normalizedAlgorithm.name) { case 'RSA-PSS': // Fall through case 'RSASSA-PKCS1-v1_5': return require('internal/crypto/rsa') - .rsaSignVerify(key, data, algorithm, signature); + .rsaSignVerify(key, data, normalizedAlgorithm, signature); case 'ECDSA': return require('internal/crypto/ec') - .ecdsaSignVerify(key, data, algorithm, signature); + .ecdsaSignVerify(key, data, normalizedAlgorithm, signature); case 'Ed25519': // Fall through case 'Ed448': // Fall through return require('internal/crypto/cfrg') - .eddsaSignVerify(key, data, algorithm, signature); + .eddsaSignVerify(key, data, normalizedAlgorithm, signature); case 'HMAC': return require('internal/crypto/mac') - .hmacSignVerify(key, data, algorithm, signature); + .hmacSignVerify(key, data, normalizedAlgorithm, signature); case 'ML-DSA-44': // Fall through case 'ML-DSA-65': // Fall through case 'ML-DSA-87': return require('internal/crypto/ml_dsa') - .mlDsaSignVerify(key, data, algorithm, signature); + .mlDsaSignVerify(key, data, normalizedAlgorithm, signature); case 'KMAC128': // Fall through case 'KMAC256': return require('internal/crypto/mac') - .kmacSignVerify(key, data, algorithm, signature); + .kmacSignVerify(key, data, normalizedAlgorithm, signature); } throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); } @@ -1270,16 +1330,16 @@ function verifyImpl(algorithm, key, signature, data) { return signVerify(algorithm, key, data, signature); } -function cipherOrWrap(mode, algorithm, key, data, op) { +function cipherOrWrap(mode, normalizedAlgorithm, key, data, operation) { // 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. validateMaxBufferLength(data, 'data'); - switch (algorithm.name) { + switch (normalizedAlgorithm.name) { case 'RSA-OAEP': return require('internal/crypto/rsa') - .rsaCipher(mode, key, data, algorithm); + .rsaCipher(mode, key, data, normalizedAlgorithm); case 'AES-CTR': // Fall through case 'AES-CBC': @@ -1288,14 +1348,14 @@ function cipherOrWrap(mode, algorithm, key, data, op) { // Fall through case 'AES-OCB': return require('internal/crypto/aes') - .aesCipher(mode, key, data, algorithm); + .aesCipher(mode, key, data, normalizedAlgorithm); case 'ChaCha20-Poly1305': return require('internal/crypto/chacha20_poly1305') - .c20pCipher(mode, key, data, algorithm); + .c20pCipher(mode, key, data, normalizedAlgorithm); case 'AES-KW': - if (op === 'wrapKey' || op === 'unwrapKey') { + if (operation === 'wrapKey' || operation === 'unwrapKey') { return require('internal/crypto/aes') - .aesCipher(mode, key, data, algorithm); + .aesCipher(mode, key, data, normalizedAlgorithm); } } throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); @@ -1324,9 +1384,9 @@ function encryptImpl(algorithm, key, data) { context: '3rd argument', }); - algorithm = normalizeAlgorithm(algorithm, 'encrypt'); + const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'encrypt'); - if (algorithm.name !== getCryptoKeyAlgorithm(key).name) + if (normalizedAlgorithm.name !== getCryptoKeyAlgorithm(key).name) throw lazyDOMException('Key algorithm mismatch', 'InvalidAccessError'); if (!hasCryptoKeyUsage(key, 'encrypt')) @@ -1335,7 +1395,7 @@ function encryptImpl(algorithm, key, data) { return cipherOrWrap( kWebCryptoCipherEncrypt, - algorithm, + normalizedAlgorithm, key, data, 'encrypt', @@ -1365,9 +1425,9 @@ function decryptImpl(algorithm, key, data) { context: '3rd argument', }); - algorithm = normalizeAlgorithm(algorithm, 'decrypt'); + const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'decrypt'); - if (algorithm.name !== getCryptoKeyAlgorithm(key).name) + if (normalizedAlgorithm.name !== getCryptoKeyAlgorithm(key).name) throw lazyDOMException('Key algorithm mismatch', 'InvalidAccessError'); if (!hasCryptoKeyUsage(key, 'decrypt')) @@ -1376,7 +1436,7 @@ function decryptImpl(algorithm, key, data) { return cipherOrWrap( kWebCryptoCipherDecrypt, - algorithm, + normalizedAlgorithm, key, data, 'decrypt', @@ -1399,7 +1459,7 @@ function getPublicKeyImpl(key, keyUsages) { prefix, context: '1st argument', }); - keyUsages = webidl.converters['sequence'](keyUsages, { + const usages = webidl.converters['sequence'](keyUsages, { prefix, context: '2nd argument', }); @@ -1412,7 +1472,7 @@ function getPublicKeyImpl(key, keyUsages) { // TODO(panva): this is by no means a hot path, but let's still follow up to get // rid of this awkwardness const keyObject = createPublicKey(new PrivateKeyObject(getCryptoKeyHandle(key))); - return keyObject.toCryptoKey(getCryptoKeyAlgorithm(key), true, keyUsages); + return keyObject.toCryptoKey(getCryptoKeyAlgorithm(key), true, usages); } function encapsulateBits(encapsulationAlgorithm, encapsulationKey) { @@ -1426,10 +1486,13 @@ function encapsulateBitsImpl(encapsulationAlgorithm, encapsulationKey) { 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', - }); + encapsulationAlgorithm = webidl.converters.AlgorithmIdentifier( + encapsulationAlgorithm, + { + prefix, + context: '1st argument', + }, + ); encapsulationKey = webidl.converters.CryptoKey(encapsulationKey, { prefix, context: '2nd argument', @@ -1467,7 +1530,7 @@ function encapsulateKey( encapsulationKey, sharedKeyAlgorithm, extractable, - usages) { + keyUsages) { return callSubtleCryptoMethod(encapsulateKeyImpl, this, arguments); } @@ -1476,30 +1539,36 @@ function encapsulateKeyImpl( encapsulationKey, sharedKeyAlgorithm, extractable, - usages) { + 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', - }); + 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', - }); + sharedKeyAlgorithm = webidl.converters.AlgorithmIdentifier( + sharedKeyAlgorithm, + { + prefix, + context: '3rd argument', + }, + ); extractable = webidl.converters.boolean(extractable, { prefix, context: '4th argument', }); - usages = webidl.converters['sequence'](usages, { + const usages = webidl.converters['sequence'](keyUsages, { prefix, context: '5th argument', }); @@ -1522,28 +1591,31 @@ function encapsulateKeyImpl( 'InvalidAccessError'); } - let encapsulateBits; + let encapsulatedBits; switch (keyAlgorithm.name) { case 'ML-KEM-512': case 'ML-KEM-768': case 'ML-KEM-1024': - encapsulateBits = require('internal/crypto/ml_kem') + encapsulatedBits = require('internal/crypto/ml_kem') .mlKemEncapsulate(encapsulationKey); break; default: throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); } - return jobPromiseThen(encapsulateBits, (encapsulateBits) => { + return jobPromiseThen(encapsulatedBits, (encapsulatedBits) => { const sharedKey = FunctionPrototypeCall( importKeySync, this, - 'raw-secret', encapsulateBits.sharedKey, normalizedSharedKeyAlgorithm, - extractable, usages, + 'raw-secret', + encapsulatedBits.sharedKey, + normalizedSharedKeyAlgorithm, + extractable, + usages, ); return { - ciphertext: encapsulateBits.ciphertext, + ciphertext: encapsulatedBits.ciphertext, sharedKey, }; }); @@ -1560,10 +1632,13 @@ function decapsulateBitsImpl(decapsulationAlgorithm, decapsulationKey, ciphertex 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', - }); + decapsulationAlgorithm = webidl.converters.AlgorithmIdentifier( + decapsulationAlgorithm, + { + prefix, + context: '1st argument', + }, + ); decapsulationKey = webidl.converters.CryptoKey(decapsulationKey, { prefix, context: '2nd argument', @@ -1601,8 +1676,12 @@ function decapsulateBitsImpl(decapsulationAlgorithm, decapsulationKey, ciphertex } function decapsulateKey( - decapsulationAlgorithm, decapsulationKey, ciphertext, sharedKeyAlgorithm, extractable, usages, -) { + decapsulationAlgorithm, + decapsulationKey, + ciphertext, + sharedKeyAlgorithm, + extractable, + keyUsages) { return callSubtleCryptoMethod(decapsulateKeyImpl, this, arguments); } @@ -1612,17 +1691,20 @@ function decapsulateKeyImpl( ciphertext, sharedKeyAlgorithm, extractable, - usages) { + 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', - }); + decapsulationAlgorithm = webidl.converters.AlgorithmIdentifier( + decapsulationAlgorithm, + { + prefix, + context: '1st argument', + }, + ); decapsulationKey = webidl.converters.CryptoKey(decapsulationKey, { prefix, context: '2nd argument', @@ -1631,15 +1713,18 @@ function decapsulateKeyImpl( prefix, context: '3rd argument', }); - sharedKeyAlgorithm = webidl.converters.AlgorithmIdentifier(sharedKeyAlgorithm, { - prefix, - context: '4th argument', - }); + sharedKeyAlgorithm = webidl.converters.AlgorithmIdentifier( + sharedKeyAlgorithm, + { + prefix, + context: '4th argument', + }, + ); extractable = webidl.converters.boolean(extractable, { prefix, context: '5th argument', }); - usages = webidl.converters['sequence'](usages, { + const usages = webidl.converters['sequence'](keyUsages, { prefix, context: '6th argument', }); @@ -1677,7 +1762,10 @@ function decapsulateKeyImpl( return jobPromiseThen(decapsulatedBits, (decapsulatedBits) => FunctionPrototypeCall( importKeySync, this, - 'raw-secret', decapsulatedBits, normalizedSharedKeyAlgorithm, extractable, + 'raw-secret', + decapsulatedBits, + normalizedSharedKeyAlgorithm, + extractable, usages, )); } @@ -1733,10 +1821,13 @@ class SubtleCrypto { let length; let additionalAlgorithm; if (operation === 'deriveKey') { - additionalAlgorithm = webidl.converters.AlgorithmIdentifier(lengthOrAdditionalAlgorithm, { - prefix, - context: '3rd argument', - }); + additionalAlgorithm = webidl.converters.AlgorithmIdentifier( + lengthOrAdditionalAlgorithm, + { + prefix, + context: '3rd argument', + }, + ); if (!check('importKey', additionalAlgorithm)) { return false; @@ -1750,19 +1841,25 @@ class SubtleCrypto { operation = 'deriveBits'; } else if (operation === 'wrapKey') { - additionalAlgorithm = webidl.converters.AlgorithmIdentifier(lengthOrAdditionalAlgorithm, { - prefix, - context: '3rd argument', - }); + additionalAlgorithm = webidl.converters.AlgorithmIdentifier( + lengthOrAdditionalAlgorithm, + { + prefix, + context: '3rd argument', + }, + ); if (!check('exportKey', additionalAlgorithm)) { return false; } } else if (operation === 'unwrapKey') { - additionalAlgorithm = webidl.converters.AlgorithmIdentifier(lengthOrAdditionalAlgorithm, { - prefix, - context: '3rd argument', - }); + additionalAlgorithm = webidl.converters.AlgorithmIdentifier( + lengthOrAdditionalAlgorithm, + { + prefix, + context: '3rd argument', + }, + ); if (!check('importKey', additionalAlgorithm)) { return false; @@ -1796,10 +1893,13 @@ class SubtleCrypto { return false; } } else if (operation === 'encapsulateKey' || operation === 'decapsulateKey') { - additionalAlgorithm = webidl.converters.AlgorithmIdentifier(lengthOrAdditionalAlgorithm, { - prefix, - context: '3rd argument', - }); + additionalAlgorithm = webidl.converters.AlgorithmIdentifier( + lengthOrAdditionalAlgorithm, + { + prefix, + context: '3rd argument', + }, + ); let normalizedAdditionalAlgorithm; try { @@ -1824,7 +1924,8 @@ class SubtleCrypto { case 'HMAC': case 'KMAC128': case 'KMAC256': - if (normalizedAdditionalAlgorithm.length === undefined || normalizedAdditionalAlgorithm.length === 256) { + if (normalizedAdditionalAlgorithm.length === undefined || + normalizedAdditionalAlgorithm.length === 256) { break; } return false; diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index f23f25ba0d58fe..522d3c24cfba70 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -2237,7 +2237,6 @@ static int xConflict(void* pCtx, int eConflict, sqlite3_changeset_iter* pIter) { static int xFilter(void* pCtx, const char* zTab) { auto ctx = static_cast(pCtx); - if (!ctx->filterCallback) return 1; return ctx->filterCallback(zTab) ? 1 : 0; } @@ -2348,7 +2347,7 @@ void DatabaseSync::ApplyChangeset(const FunctionCallbackInfo& args) { db->connection_, buf.length(), const_cast(static_cast(buf.data())), - xFilter, + context.filterCallback ? xFilter : nullptr, xConflict, static_cast(&context)); if (r == SQLITE_OK) { diff --git a/test/fixtures/test426/decoding/scopes/README.md b/test/fixtures/test426/decoding/scopes/README.md new file mode 100644 index 00000000000000..7b3271adb3ada7 --- /dev/null +++ b/test/fixtures/test426/decoding/scopes/README.md @@ -0,0 +1,18 @@ +## Test cases + +- **empty-scopes-field**: Empty original scopes field +- **nil-scopes**: Multiple null original scopes +- **close-start-end-position-scopes**: Scopes with very close start and end + positions +- **single-root-original-scope**: A single global root original scope +- **nested-scopes**: Nested original scopes representing functions and blocks +- **sibling-scopes**: Multiple sibling top-level functions +- **scope-variables**: Scopes containing variable declarations +- **multiple-root-original-scopes-with-nil**: Multiple global root scopes with a + null scope in between +- **sibling-ranges**: Multiple sibling root ranges +- **nested-ranges**: A root range with a nested function range +- **range-values**: A root range with a non-function range with bindings +- **sub-range-values**: A root range with sub-range bindings +- **range-call-site**: A function inlined into the root scope +- **hidden-ranges**: A function range corresponding to an original block scope diff --git a/test/fixtures/test426/decoding/scopes/hidden-ranges.map b/test/fixtures/test426/decoding/scopes/hidden-ranges.map new file mode 100644 index 00000000000000..779a24269ff2a4 --- /dev/null +++ b/test/fixtures/test426/decoding/scopes/hidden-ranges.map @@ -0,0 +1,13 @@ +{ + "version": 3, + "file": "hidden-ranges.js", + "sources": [ + "original_0.js" + ], + "mappings": "", + "names": [ + "global", + "block" + ], + "scopes": "BCAAA,BCBAC,CEA,CFA,ECAA,EPBAC,FEA,FFA" +} \ No newline at end of file diff --git a/test/fixtures/test426/decoding/scopes/hidden-ranges.map.golden b/test/fixtures/test426/decoding/scopes/hidden-ranges.map.golden new file mode 100644 index 00000000000000..6564cca5ea4e9d --- /dev/null +++ b/test/fixtures/test426/decoding/scopes/hidden-ranges.map.golden @@ -0,0 +1,75 @@ +{ + "file": "hidden-ranges.js", + "mappings": [], + "sources": [ + { + "url": "original_0.js", + "content": null, + "ignored": false, + "scope": { + "start": { + "line": 0, + "column": 0 + }, + "end": { + "line": 10, + "column": 0 + }, + "name": null, + "kind": "global", + "isStackFrame": false, + "variables": [], + "children": [ + { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 5, + "column": 0 + }, + "name": null, + "kind": "block", + "isStackFrame": false, + "variables": [], + "children": [] + } + ] + } + } + ], + "ranges": [ + { + "start": { + "line": 0, + "column": 0 + }, + "end": { + "line": 10, + "column": 0 + }, + "definitionIndex": 0, + "stackFrameType": "none", + "callSite": null, + "bindings": [], + "children": [ + { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 5, + "column": 0 + }, + "definitionIndex": 1, + "stackFrameType": "hidden", + "callSite": null, + "bindings": [], + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/test/fixtures/test426/decoding/scopes/nested-ranges.map b/test/fixtures/test426/decoding/scopes/nested-ranges.map new file mode 100644 index 00000000000000..2ab612337ed9a5 --- /dev/null +++ b/test/fixtures/test426/decoding/scopes/nested-ranges.map @@ -0,0 +1,14 @@ +{ + "version": 3, + "file": "nested-ranges.js", + "sources": [ + "original_0.js" + ], + "mappings": "", + "names": [ + "global", + "foo", + "function" + ], + "scopes": "BCAAA,BHBACE,CEA,CFA,ECAA,EGCC,FC,FG" +} \ No newline at end of file diff --git a/test/fixtures/test426/decoding/scopes/nested-ranges.map.golden b/test/fixtures/test426/decoding/scopes/nested-ranges.map.golden new file mode 100644 index 00000000000000..4d4138f113d10e --- /dev/null +++ b/test/fixtures/test426/decoding/scopes/nested-ranges.map.golden @@ -0,0 +1,75 @@ +{ + "file": "nested-ranges.js", + "mappings": [], + "sources": [ + { + "url": "original_0.js", + "content": null, + "ignored": false, + "scope": { + "start": { + "line": 0, + "column": 0 + }, + "end": { + "line": 10, + "column": 0 + }, + "name": null, + "kind": "global", + "isStackFrame": false, + "variables": [], + "children": [ + { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 5, + "column": 0 + }, + "name": "foo", + "kind": "function", + "isStackFrame": true, + "variables": [], + "children": [] + } + ] + } + } + ], + "ranges": [ + { + "start": { + "line": 0, + "column": 0 + }, + "end": { + "line": 0, + "column": 10 + }, + "definitionIndex": 0, + "stackFrameType": "none", + "callSite": null, + "bindings": [], + "children": [ + { + "start": { + "line": 0, + "column": 2 + }, + "end": { + "line": 0, + "column": 4 + }, + "definitionIndex": 1, + "stackFrameType": "original", + "callSite": null, + "bindings": [], + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/test/fixtures/test426/decoding/scopes/range-call-site.map b/test/fixtures/test426/decoding/scopes/range-call-site.map new file mode 100644 index 00000000000000..61e5a4c5a5dcee --- /dev/null +++ b/test/fixtures/test426/decoding/scopes/range-call-site.map @@ -0,0 +1,14 @@ +{ + "version": 3, + "file": "range-call-site.js", + "sources": [ + "original_0.js" + ], + "mappings": "", + "names": [ + "global", + "inlineMe", + "function" + ], + "scopes": "BCAAA,BHBACE,CEA,CFA,ECAA,EDBAC,IAGC,FK,FJA" +} \ No newline at end of file diff --git a/test/fixtures/test426/decoding/scopes/range-call-site.map.golden b/test/fixtures/test426/decoding/scopes/range-call-site.map.golden new file mode 100644 index 00000000000000..b3ee2302946c44 --- /dev/null +++ b/test/fixtures/test426/decoding/scopes/range-call-site.map.golden @@ -0,0 +1,79 @@ +{ + "file": "range-call-site.js", + "mappings": [], + "sources": [ + { + "url": "original_0.js", + "content": null, + "ignored": false, + "scope": { + "start": { + "line": 0, + "column": 0 + }, + "end": { + "line": 10, + "column": 0 + }, + "name": null, + "kind": "global", + "isStackFrame": false, + "variables": [], + "children": [ + { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 5, + "column": 0 + }, + "name": "inlineMe", + "kind": "function", + "isStackFrame": true, + "variables": [], + "children": [] + } + ] + } + } + ], + "ranges": [ + { + "start": { + "line": 0, + "column": 0 + }, + "end": { + "line": 10, + "column": 0 + }, + "definitionIndex": 0, + "stackFrameType": "none", + "callSite": null, + "bindings": [], + "children": [ + { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 10 + }, + "definitionIndex": 1, + "stackFrameType": "none", + "callSite": { + "sourceIndex": 0, + "line": 6, + "column": 2 + }, + "bindings": [], + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/test/fixtures/test426/decoding/scopes/range-values.map b/test/fixtures/test426/decoding/scopes/range-values.map new file mode 100644 index 00000000000000..c397423a2431c8 --- /dev/null +++ b/test/fixtures/test426/decoding/scopes/range-values.map @@ -0,0 +1,20 @@ +{ + "version": 3, + "file": "range-values.js", + "sources": [ + "original_0.js" + ], + "mappings": "", + "names": [ + "global", + "x", + "y", + "z", + "block", + "a", + "x_val", + "z_val", + "a_val" + ], + "scopes": "BCAAA,DCCC,BCBAI,DE,CEA,CFA,ECAA,GHAI,ECCC,GJ,FC,FG" +} \ No newline at end of file diff --git a/test/fixtures/test426/decoding/scopes/range-values.map.golden b/test/fixtures/test426/decoding/scopes/range-values.map.golden new file mode 100644 index 00000000000000..a7cdec5139da88 --- /dev/null +++ b/test/fixtures/test426/decoding/scopes/range-values.map.golden @@ -0,0 +1,119 @@ +{ + "file": "range-values.js", + "mappings": [], + "sources": [ + { + "url": "original_0.js", + "content": null, + "ignored": false, + "scope": { + "start": { + "line": 0, + "column": 0 + }, + "end": { + "line": 10, + "column": 0 + }, + "name": null, + "kind": "global", + "isStackFrame": false, + "variables": [ + "x", + "y", + "z" + ], + "children": [ + { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 5, + "column": 0 + }, + "name": null, + "kind": "block", + "isStackFrame": false, + "variables": [ + "a" + ], + "children": [] + } + ] + } + } + ], + "ranges": [ + { + "start": { + "line": 0, + "column": 0 + }, + "end": { + "line": 0, + "column": 10 + }, + "definitionIndex": 0, + "stackFrameType": "none", + "callSite": null, + "bindings": [ + [ + { + "binding": "x_val", + "from": { + "line": 0, + "column": 0 + } + } + ], + [ + { + "binding": null, + "from": { + "line": 0, + "column": 0 + } + } + ], + [ + { + "binding": "z_val", + "from": { + "line": 0, + "column": 0 + } + } + ] + ], + "children": [ + { + "start": { + "line": 0, + "column": 2 + }, + "end": { + "line": 0, + "column": 4 + }, + "definitionIndex": 1, + "stackFrameType": "none", + "callSite": null, + "bindings": [ + [ + { + "binding": "a_val", + "from": { + "line": 0, + "column": 2 + } + } + ] + ], + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/test/fixtures/test426/decoding/scopes/sibling-ranges.map b/test/fixtures/test426/decoding/scopes/sibling-ranges.map new file mode 100644 index 00000000000000..ea90f645612cc4 --- /dev/null +++ b/test/fixtures/test426/decoding/scopes/sibling-ranges.map @@ -0,0 +1,13 @@ +{ + "version": 3, + "file": "sibling-ranges.js", + "sources": [ + "original_0.js", + "original_1.js" + ], + "mappings": "", + "names": [ + "global" + ], + "scopes": "BCAAA,CKA,BCKAA,CKA,ECAA,FK,EDKAC,FK,EBKA,FK" +} \ No newline at end of file diff --git a/test/fixtures/test426/decoding/scopes/sibling-ranges.map.golden b/test/fixtures/test426/decoding/scopes/sibling-ranges.map.golden new file mode 100644 index 00000000000000..dc1975d40d00a5 --- /dev/null +++ b/test/fixtures/test426/decoding/scopes/sibling-ranges.map.golden @@ -0,0 +1,93 @@ +{ + "file": "sibling-ranges.js", + "mappings": [], + "sources": [ + { + "url": "original_0.js", + "content": null, + "ignored": false, + "scope": { + "start": { + "line": 0, + "column": 0 + }, + "end": { + "line": 10, + "column": 0 + }, + "name": null, + "kind": "global", + "isStackFrame": false, + "variables": [], + "children": [] + } + }, + { + "url": "original_1.js", + "content": null, + "ignored": false, + "scope": { + "start": { + "line": 10, + "column": 0 + }, + "end": { + "line": 20, + "column": 0 + }, + "name": null, + "kind": "global", + "isStackFrame": false, + "variables": [], + "children": [] + } + } + ], + "ranges": [ + { + "start": { + "line": 0, + "column": 0 + }, + "end": { + "line": 0, + "column": 10 + }, + "definitionIndex": 0, + "stackFrameType": "none", + "callSite": null, + "bindings": [], + "children": [] + }, + { + "start": { + "line": 10, + "column": 0 + }, + "end": { + "line": 10, + "column": 10 + }, + "definitionIndex": 1, + "stackFrameType": "none", + "callSite": null, + "bindings": [], + "children": [] + }, + { + "start": { + "line": 20, + "column": 0 + }, + "end": { + "line": 20, + "column": 10 + }, + "definitionIndex": null, + "stackFrameType": "none", + "callSite": null, + "bindings": [], + "children": [] + } + ] +} \ No newline at end of file diff --git a/test/fixtures/test426/decoding/scopes/sub-range-values.map b/test/fixtures/test426/decoding/scopes/sub-range-values.map new file mode 100644 index 00000000000000..40155a819383c4 --- /dev/null +++ b/test/fixtures/test426/decoding/scopes/sub-range-values.map @@ -0,0 +1,17 @@ +{ + "version": 3, + "file": "sub-range-values.js", + "sources": [ + "original_0.js" + ], + "mappings": "", + "names": [ + "global", + "x", + "y", + "x_sub_val1", + "y_val", + "x_sub_val2" + ], + "scopes": "BCAAA,DCC,CKA,ECAA,GEF,HAADAADG,FK" +} \ No newline at end of file diff --git a/test/fixtures/test426/decoding/scopes/sub-range-values.map.golden b/test/fixtures/test426/decoding/scopes/sub-range-values.map.golden new file mode 100644 index 00000000000000..c0662102819561 --- /dev/null +++ b/test/fixtures/test426/decoding/scopes/sub-range-values.map.golden @@ -0,0 +1,79 @@ +{ + "file": "sub-range-values.js", + "mappings": [], + "sources": [ + { + "url": "original_0.js", + "content": null, + "ignored": false, + "scope": { + "start": { + "line": 0, + "column": 0 + }, + "end": { + "line": 10, + "column": 0 + }, + "name": null, + "kind": "global", + "isStackFrame": false, + "variables": [ + "x", + "y" + ], + "children": [] + } + } + ], + "ranges": [ + { + "start": { + "line": 0, + "column": 0 + }, + "end": { + "line": 0, + "column": 10 + }, + "definitionIndex": 0, + "stackFrameType": "none", + "callSite": null, + "bindings": [ + [ + { + "binding": "x_sub_val1", + "from": { + "line": 0, + "column": 0 + } + }, + { + "binding": null, + "from": { + "line": 0, + "column": 3 + } + }, + { + "binding": "x_sub_val2", + "from": { + "line": 0, + "column": 6 + } + } + ], + [ + { + "binding": "y_val", + "from": { + "line": 0, + "column": 0 + } + } + ] + ], + "children": [] + } + ] +} \ No newline at end of file diff --git a/test/fixtures/test426/range-mappings-proposal-tests.json b/test/fixtures/test426/range-mappings-proposal-tests.json index 42d47d57650338..7b108e7e5e15bd 100644 --- a/test/fixtures/test426/range-mappings-proposal-tests.json +++ b/test/fixtures/test426/range-mappings-proposal-tests.json @@ -36,6 +36,13 @@ "sourceMapFile": "invalid-base64-char-2.js.map", "sourceMapIsValid": false }, + { + "name": "rangeMappingsInvalidVLQZero", + "description": "VLQ of zero is invalid because offsets are 1-based", + "baseFile": "invalid-vlq-zero.js", + "sourceMapFile": "invalid-vlq-zero.js.map", + "sourceMapIsValid": false + }, { "name": "rangeMappingsOutOfRange", "description": "Test an invalid range mapping which is outside the mappings length", diff --git a/test/fixtures/test426/resources/proposals/range-mappings/invalid-vlq-zero.js b/test/fixtures/test426/resources/proposals/range-mappings/invalid-vlq-zero.js new file mode 100644 index 00000000000000..c19bce975ac9e5 --- /dev/null +++ b/test/fixtures/test426/resources/proposals/range-mappings/invalid-vlq-zero.js @@ -0,0 +1 @@ +//# sourceMappingURL=invalid-vlq-zero.js.map diff --git a/test/fixtures/test426/resources/proposals/range-mappings/invalid-vlq-zero.js.map b/test/fixtures/test426/resources/proposals/range-mappings/invalid-vlq-zero.js.map new file mode 100644 index 00000000000000..fe13e8f5225acd --- /dev/null +++ b/test/fixtures/test426/resources/proposals/range-mappings/invalid-vlq-zero.js.map @@ -0,0 +1,9 @@ +{ + "version": 3, + "names": [], + "file": "invalid-vlq-zero.js", + "sources": ["empty-original.js"], + "sourcesContent": [""], + "mappings": "A", + "rangeMappings": "A" +} diff --git a/test/fixtures/test426/resources/proposals/range-mappings/multiple-mappings.js.map b/test/fixtures/test426/resources/proposals/range-mappings/multiple-mappings.js.map index 2fab6bd1977c25..c7ffc83e012283 100644 --- a/test/fixtures/test426/resources/proposals/range-mappings/multiple-mappings.js.map +++ b/test/fixtures/test426/resources/proposals/range-mappings/multiple-mappings.js.map @@ -4,5 +4,5 @@ "sources": ["multiple-mappings-original.js"], "sourcesContent": ["\"Hello World\"; function f() { } "], "mappings": ";CAAA,aAAa,EAAG,iBAAoB;A", - "rangeMappings": ";AC;" + "rangeMappings": ";BC;" } diff --git a/test/fixtures/test426/resources/proposals/range-mappings/newline-semantics.js.map b/test/fixtures/test426/resources/proposals/range-mappings/newline-semantics.js.map index 9f9e581b9edd17..b6f7b44282c3ff 100644 --- a/test/fixtures/test426/resources/proposals/range-mappings/newline-semantics.js.map +++ b/test/fixtures/test426/resources/proposals/range-mappings/newline-semantics.js.map @@ -5,5 +5,5 @@ "sources": ["newline-semantics-original.js"], "sourcesContent": ["1234\n5678"], "mappings": "CAAA;GACG", - "rangeMappings": "A;" + "rangeMappings": "B;" } diff --git a/test/fixtures/test426/resources/proposals/range-mappings/non-full-line-coverage.js.map b/test/fixtures/test426/resources/proposals/range-mappings/non-full-line-coverage.js.map index a381b123995747..952a3c30b1d084 100644 --- a/test/fixtures/test426/resources/proposals/range-mappings/non-full-line-coverage.js.map +++ b/test/fixtures/test426/resources/proposals/range-mappings/non-full-line-coverage.js.map @@ -5,5 +5,5 @@ "sources": ["simple-original.js"], "sourcesContent": ["\"Hello World\""], "mappings": ";CAAA;A", - "rangeMappings": ";A" + "rangeMappings": ";B" } diff --git a/test/fixtures/test426/resources/proposals/range-mappings/out-of-range-2.js.map b/test/fixtures/test426/resources/proposals/range-mappings/out-of-range-2.js.map index 152fd6653a8546..f573e035dca549 100644 --- a/test/fixtures/test426/resources/proposals/range-mappings/out-of-range-2.js.map +++ b/test/fixtures/test426/resources/proposals/range-mappings/out-of-range-2.js.map @@ -5,5 +5,5 @@ "sources": ["foo.js"], "sourcesContent": ["\"foo\""], "mappings": "AAA", - "rangeMappings": "B;A;A" + "rangeMappings": "C;B;B" } diff --git a/test/fixtures/test426/resources/proposals/range-mappings/out-of-range.js.map b/test/fixtures/test426/resources/proposals/range-mappings/out-of-range.js.map index 5ad9a234d91120..5da575a7a6355d 100644 --- a/test/fixtures/test426/resources/proposals/range-mappings/out-of-range.js.map +++ b/test/fixtures/test426/resources/proposals/range-mappings/out-of-range.js.map @@ -5,5 +5,5 @@ "sources": ["foo.js"], "sourcesContent": ["\"foo\""], "mappings": "AAA", - "rangeMappings": "B" + "rangeMappings": "C" } diff --git a/test/fixtures/test426/resources/proposals/range-mappings/simple.js.map b/test/fixtures/test426/resources/proposals/range-mappings/simple.js.map index 0cd782bcee0638..856d31a9c84e14 100644 --- a/test/fixtures/test426/resources/proposals/range-mappings/simple.js.map +++ b/test/fixtures/test426/resources/proposals/range-mappings/simple.js.map @@ -5,5 +5,5 @@ "sources": ["simple-original.js"], "sourcesContent": ["\"Hello World\""], "mappings": ";CAAA;A", - "rangeMappings": ";A;" + "rangeMappings": ";B;" } diff --git a/test/parallel/test-webcrypto-promise-prototype-pollution.mjs b/test/parallel/test-webcrypto-promise-prototype-pollution.mjs index 17cc5c97716df0..5c13561dc26063 100644 --- a/test/parallel/test-webcrypto-promise-prototype-pollution.mjs +++ b/test/parallel/test-webcrypto-promise-prototype-pollution.mjs @@ -1,23 +1,39 @@ +// Flags: --expose-internals + import * as common from '../common/index.mjs'; import assert from 'node:assert'; +import { createRequire } from 'node:module'; if (!common.hasCrypto) common.skip('missing crypto'); -// WebCrypto subtle methods must not leak intermediate values -// through Promise.prototype.then or constructor pollution. +// WebCrypto subtle methods must not leak intermediate values through +// Promise.prototype.then, constructor/species, thenable assimilation, or +// inherited accessors on internally-created JWK/result objects. // Regression test for https://github.com/nodejs/node/pull/61492 // and https://github.com/nodejs/node/issues/59699. +// +// When adding WebCrypto algorithms: +// - Add a fixture with addFixture() below. Prefer the shared fixture builders +// unless an algorithm needs operation-specific parameters. +// - Add new operation names to operationOrder and implement that operation on +// every affected fixture. +// - Add new "get key length" algorithms to keyLengthTargets, unless the +// algorithm is itself a KDF whose getKeyLength() result is null; those belong +// in nullKeyLengthAlgorithms. +// The registry assertions at the bottom make missing updates fail loudly. -import { hasOpenSSL } from '../common/crypto.js'; - +const require = createRequire(import.meta.url); +const { kSupportedAlgorithms } = require('internal/crypto/util'); const { subtle } = globalThis.crypto; Promise.prototype.then = common.mustNotCall('Promise.prototype.then'); +const data = new TextEncoder().encode('prototype pollution'); + // WebCrypto methods return native promises. Re-wrapping a promise with // PromiseResolve() or chaining it with Promise.prototype.then can read // user-mutated constructor/species accessors. -async function assertNoPromiseConstructorAccess(name, fn) { +function assertNoPromiseConstructorAccess(name, fn) { const constructorDescriptor = Object.getOwnPropertyDescriptor(Promise.prototype, 'constructor'); const speciesDescriptor = @@ -43,7 +59,7 @@ async function assertNoPromiseConstructorAccess(name, fn) { constructorDescriptor); Object.defineProperty(Promise, Symbol.species, speciesDescriptor); } - return await promise; + return promise; } // Exercise each export format through the same promise-constructor guard. @@ -96,6 +112,11 @@ function assertNoInheritedObjectThenAccess(name, fn) { fn); } +function assertCryptoKeyResult(name, fn) { + return assertNoInheritedCryptoKeyThenAccess(name, () => + assertNoPromiseConstructorAccess(name, fn)); +} + // wrapKey('jwk') stringifies an internally exported JWK. The spec does this // in a fresh global, so inherited toJSON hooks from the current global must // not observe or replace key material. @@ -131,7 +152,9 @@ async function assertNoInheritedToJSONAccess(name, fn) { } // JWK export creates and fills a result object. The exported members must be -// own data properties, not writes that can observe inherited accessors. +// own data properties, not writes that can observe inherited accessors. Keep +// this list complete: if exportKey('jwk') starts returning a new JWK member, +// this helper must poison that member on Object.prototype too. async function assertNoInheritedJwkPropertyAccess(name, fn) { const properties = [ 'alg', @@ -153,6 +176,7 @@ async function assertNoInheritedJwkPropertyAccess(name, fn) { 'x', 'y', ]; + const handledProperties = new Set(properties); const descriptors = new Map(); for (const property of properties) { descriptors.set( @@ -165,8 +189,9 @@ async function assertNoInheritedJwkPropertyAccess(name, fn) { set: common.mustNotCall(`${name} Object.prototype.${property} setter`), }); } + let result; try { - return await fn(); + result = await fn(); } finally { for (const property of properties) { const descriptor = descriptors.get(property); @@ -177,6 +202,16 @@ async function assertNoInheritedJwkPropertyAccess(name, fn) { } } } + + if (result !== null && typeof result === 'object') { + for (const property of Object.keys(result)) { + assert( + handledProperties.has(property), + `${name} returned unhandled JWK property ${property}`); + } + } + + return result; } // unwrapKey('jwk') parses a JWK and then converts it to the JsonWebKey IDL @@ -292,389 +327,786 @@ async function assertNoRawSharedKeyObjectThenAccess(name, fn) { } } -await assertNoPromiseConstructorAccess('digest', () => - subtle.digest('SHA-256', new Uint8Array([1, 2, 3]))); +function algorithm(name, params = {}) { + return { name, ...params }; +} -const secretKey = await assertNoPromiseConstructorAccess( - 'generateKey secret', - () => subtle.generateKey( - { name: 'AES-CBC', length: 256 }, - true, - ['encrypt', 'decrypt'])); +function rsaAlgorithm(name) { + return algorithm(name, { + modulusLength: 1024, + publicExponent: new Uint8Array([1, 0, 1]), + hash: 'SHA-256', + }); +} -const extractableKeyPair = await assertNoPromiseConstructorAccess('generateKey pair', () => - subtle.generateKey( - { name: 'ECDSA', namedCurve: 'P-256' }, - true, - ['sign', 'verify'])); - -const rawKey = globalThis.crypto.getRandomValues(new Uint8Array(32)); - -const importedKey = await assertNoPromiseConstructorAccess('importKey', () => - subtle.importKey( - 'raw', - rawKey, - { name: 'AES-CBC', length: 256 }, - false, - ['encrypt', 'decrypt'])); - -await assertNoInheritedCryptoKeyThenAccess('importKey', () => - subtle.importKey( - 'raw', - rawKey, - { name: 'AES-CBC', length: 256 }, - false, - ['encrypt', 'decrypt'])); - -await assertNoInheritedJwkPropertyAccess('exportKey jwk secret', () => - assertExportKeyNoPromiseConstructorAccess( - 'jwk secret', - 'jwk', - secretKey)); -await assertNoInheritedObjectThenAccess('exportKey jwk secret', () => - subtle.exportKey('jwk', secretKey)); -await assertNoInheritedJwkPropertyAccess('exportKey jwk public', () => - assertExportKeyNoPromiseConstructorAccess( - 'jwk public', - 'jwk', - extractableKeyPair.publicKey)); -await assertNoInheritedObjectThenAccess('exportKey jwk public', () => - subtle.exportKey('jwk', extractableKeyPair.publicKey)); -await assertNoInheritedJwkPropertyAccess('exportKey jwk private', () => - assertExportKeyNoPromiseConstructorAccess( - 'jwk private', - 'jwk', - extractableKeyPair.privateKey)); -await assertNoInheritedObjectThenAccess('exportKey jwk private', () => - subtle.exportKey('jwk', extractableKeyPair.privateKey)); -await assertNoInheritedArrayBufferThenAccess('exportKey raw secret', () => - subtle.exportKey('raw', secretKey)); -await assertExportKeyNoPromiseConstructorAccess( - 'raw secret', - 'raw', - secretKey); -await assertNoInheritedArrayBufferThenAccess('exportKey spki', () => - subtle.exportKey('spki', extractableKeyPair.publicKey)); -await assertExportKeyNoPromiseConstructorAccess( - 'spki', - 'spki', - extractableKeyPair.publicKey); -await assertNoInheritedArrayBufferThenAccess('exportKey pkcs8', () => - subtle.exportKey('pkcs8', extractableKeyPair.privateKey)); -await assertExportKeyNoPromiseConstructorAccess( - 'pkcs8', - 'pkcs8', - extractableKeyPair.privateKey); -await assertNoInheritedArrayBufferThenAccess('exportKey raw-public', () => - subtle.exportKey('raw-public', extractableKeyPair.publicKey)); -await assertExportKeyNoPromiseConstructorAccess( - 'raw-public', - 'raw-public', - extractableKeyPair.publicKey); - -const iv = globalThis.crypto.getRandomValues(new Uint8Array(16)); -const plaintext = new TextEncoder().encode('Hello, world!'); - -const ciphertext = await assertNoPromiseConstructorAccess('encrypt', () => - subtle.encrypt({ name: 'AES-CBC', iv }, importedKey, plaintext)); - -await assertNoPromiseConstructorAccess('decrypt', () => - subtle.decrypt({ name: 'AES-CBC', iv }, importedKey, ciphertext)); - -const signingKey = await subtle.generateKey( - { name: 'HMAC', hash: 'SHA-256' }, - false, - ['sign', 'verify']); +function importRsaAlgorithm(name) { + return algorithm(name, { hash: 'SHA-256' }); +} -const data = new TextEncoder().encode('test data'); +function exportArrayBuffer(name, format, key) { + return assertNoInheritedArrayBufferThenAccess(`exportKey ${format} ${name}`, () => + assertExportKeyNoPromiseConstructorAccess(`${format} ${name}`, format, key)); +} -const signature = await assertNoPromiseConstructorAccess('sign', () => - subtle.sign('HMAC', signingKey, data)); +function exportJwk(name, key) { + return assertNoInheritedJwkPropertyAccess(`exportKey jwk ${name}`, () => + assertNoInheritedObjectThenAccess(`exportKey jwk ${name}`, () => + assertExportKeyNoPromiseConstructorAccess(`jwk ${name}`, 'jwk', key))); +} -await assertNoPromiseConstructorAccess('verify', () => - subtle.verify('HMAC', signingKey, signature, data)); +function importCryptoKey(name, format, keyData, importAlgorithm, extractable, usages) { + return assertCryptoKeyResult(`importKey ${format} ${name}`, () => + subtle.importKey(format, keyData, importAlgorithm, extractable, usages)); +} -const pbkdf2Key = await subtle.importKey( - 'raw', rawKey, 'PBKDF2', false, ['deriveBits', 'deriveKey']); - -await assertNoPromiseConstructorAccess('deriveBits', () => - subtle.deriveBits( - { name: 'PBKDF2', salt: rawKey, iterations: 1000, hash: 'SHA-256' }, - pbkdf2Key, - 256)); - -await assertNoPromiseConstructorAccess('deriveBits PBKDF2 zero-length', () => - subtle.deriveBits( - { name: 'PBKDF2', salt: rawKey, iterations: 1000, hash: 'SHA-256' }, - pbkdf2Key, - 0)); - -const hkdfKey = await subtle.importKey( - 'raw', rawKey, 'HKDF', false, ['deriveBits']); - -await assertNoPromiseConstructorAccess('deriveBits HKDF zero-length', () => - subtle.deriveBits( - { name: 'HKDF', hash: 'SHA-256', salt: rawKey, info: rawKey }, - hkdfKey, - 0)); - -const ecdhKeyPair = await subtle.generateKey( - { name: 'ECDH', namedCurve: 'P-256' }, - false, - ['deriveBits']); - -await assertNoPromiseConstructorAccess('deriveBits ECDH', () => - subtle.deriveBits( - { name: 'ECDH', public: ecdhKeyPair.publicKey }, - ecdhKeyPair.privateKey, - 256)); - -await assertNoPromiseConstructorAccess('deriveKey', () => - subtle.deriveKey( - { name: 'PBKDF2', salt: rawKey, iterations: 1000, hash: 'SHA-256' }, - pbkdf2Key, - { name: 'AES-CBC', length: 256 }, - true, - ['encrypt', 'decrypt'])); -await assertNoInheritedArrayBufferThenAccess('deriveKey', () => - subtle.deriveKey( - { name: 'PBKDF2', salt: rawKey, iterations: 1000, hash: 'SHA-256' }, - pbkdf2Key, - { name: 'AES-CBC', length: 256 }, - true, - ['encrypt', 'decrypt'])); -await assertNoInheritedCryptoKeyThenAccess('deriveKey result', () => - subtle.deriveKey( - { name: 'PBKDF2', salt: rawKey, iterations: 1000, hash: 'SHA-256' }, - pbkdf2Key, - { name: 'AES-CBC', length: 256 }, - true, - ['encrypt', 'decrypt'])); - -const wrappingKey = await subtle.generateKey( - { name: 'AES-KW', length: 256 }, true, ['wrapKey', 'unwrapKey']); - -const keyToWrap = await subtle.generateKey( - { name: 'AES-CBC', length: 256 }, true, ['encrypt', 'decrypt']); - -const wrapped = await assertNoPromiseConstructorAccess('wrapKey', () => - subtle.wrapKey('raw', keyToWrap, wrappingKey, 'AES-KW')); - -const wrappedJwk = await assertNoInheritedJwkPropertyAccess('wrapKey jwk', () => - assertNoInheritedToJSONAccess('wrapKey jwk', () => - assertNoUserMutableEncodeAccess('wrapKey jwk', () => - assertNoPromiseConstructorAccess('wrapKey jwk', () => - subtle.wrapKey('jwk', keyToWrap, wrappingKey, 'AES-KW'))))); - -await assertNoPromiseConstructorAccess('unwrapKey', () => - subtle.unwrapKey( - 'raw', - wrapped, - wrappingKey, - 'AES-KW', - { name: 'AES-CBC', length: 256 }, - true, - ['encrypt', 'decrypt'])); -await assertNoInheritedArrayBufferThenAccess('unwrapKey', () => - subtle.unwrapKey( - 'raw', - wrapped, - wrappingKey, - 'AES-KW', - { name: 'AES-CBC', length: 256 }, - true, - ['encrypt', 'decrypt'])); -await assertNoInheritedCryptoKeyThenAccess('unwrapKey result', () => - subtle.unwrapKey( - 'raw', - wrapped, - wrappingKey, - 'AES-KW', - { name: 'AES-CBC', length: 256 }, - true, - ['encrypt', 'decrypt'])); +async function getKeyToWrap() { + if (getKeyToWrap.key === undefined) { + getKeyToWrap.key = await subtle.generateKey( + algorithm('AES-CBC', { length: 128 }), + true, + ['encrypt']); + } + return getKeyToWrap.key; +} -await assertNoUserMutableDecodeAccess('unwrapKey jwk', () => - assertNoPromiseConstructorAccess('unwrapKey jwk', () => - subtle.unwrapKey( +function addCommonKeyExportTests(fixture) { + fixture.exportKey = async (ctx) => { + if (fixture.rawFormat !== undefined) + ctx.raw = await exportArrayBuffer(fixture.name, fixture.rawFormat, ctx.key); + ctx.jwk = await exportJwk(fixture.name, ctx.key); + }; + fixture.importKey = async (ctx) => { + if (fixture.rawFormat !== undefined) { + ctx.importedRaw = await importCryptoKey( + fixture.name, + fixture.rawFormat, + ctx.raw, + fixture.importAlgorithm, + true, + fixture.usages); + } + ctx.importedJwk = await importCryptoKey( + fixture.name, 'jwk', - wrappedJwk, - wrappingKey, - 'AES-KW', - { name: 'AES-CBC', length: 256 }, + ctx.jwk, + fixture.importAlgorithm, true, - ['encrypt', 'decrypt']))); -await assertNoInheritedArrayBufferThenAccess('unwrapKey jwk', () => - subtle.unwrapKey( - 'jwk', - wrappedJwk, - wrappingKey, - 'AES-KW', - { name: 'AES-CBC', length: 256 }, - true, - ['encrypt', 'decrypt'])); -await assertNoInheritedCryptoKeyThenAccess('unwrapKey jwk result', () => - subtle.unwrapKey( - 'jwk', - wrappedJwk, - wrappingKey, - 'AES-KW', - { name: 'AES-CBC', length: 256 }, - true, - ['encrypt', 'decrypt'])); + fixture.usages); + }; +} -{ - const jwkUnwrappingKey = await subtle.generateKey( - { name: 'AES-CBC', length: 128 }, - true, - ['encrypt', 'unwrapKey']); +function secretKeyFixture(options) { + const fixture = { + ...options, + generateKey: async (ctx) => { + ctx.key = await assertNoPromiseConstructorAccess(`generateKey ${options.name}`, () => + subtle.generateKey(options.generateAlgorithm, true, options.usages)); + }, + }; - { - const iv = globalThis.crypto.getRandomValues(new Uint8Array(16)); - const validWrappedJwk = await subtle.encrypt( - { name: 'AES-CBC', iv }, - jwkUnwrappingKey, - Buffer.from('{"kty":"oct","k":"AAAAAAAAAAAAAAAAAAAAAA"}')); - - await assertNoUserMutableDecodeAccess('unwrapKey jwk AES-CBC', () => - assertNoPromiseConstructorAccess('unwrapKey jwk AES-CBC', () => - subtle.unwrapKey( - 'jwk', - validWrappedJwk, - jwkUnwrappingKey, - { name: 'AES-CBC', iv }, - { name: 'AES-CBC', length: 128 }, + addCommonKeyExportTests(fixture); + + if (options.encryptAlgorithm !== undefined) { + fixture.encrypt = async (ctx) => { + ctx.ciphertext = await assertNoPromiseConstructorAccess(`encrypt ${options.name}`, () => + subtle.encrypt(options.encryptAlgorithm, ctx.key, data)); + }; + fixture.decrypt = async (ctx) => { + await assertNoPromiseConstructorAccess(`decrypt ${options.name}`, () => + subtle.decrypt(options.encryptAlgorithm, ctx.key, ctx.ciphertext)); + }; + } + + if (options.signAlgorithm !== undefined) { + fixture.sign = async (ctx) => { + ctx.signature = await assertNoPromiseConstructorAccess(`sign ${options.name}`, () => + subtle.sign(options.signAlgorithm, ctx.key, data)); + }; + fixture.verify = async (ctx) => { + await assertNoPromiseConstructorAccess(`verify ${options.name}`, () => + subtle.verify(options.signAlgorithm, ctx.key, ctx.signature, data)); + }; + } + + if (options.wrapAlgorithm !== undefined) { + fixture.wrapKey = async (ctx) => { + const keyToWrap = await getKeyToWrap(); + ctx.wrappedRawSecret = await assertNoPromiseConstructorAccess( + `wrapKey raw-secret ${options.name}`, + () => subtle.wrapKey('raw-secret', keyToWrap, ctx.key, options.wrapAlgorithm)); + ctx.wrappedJwk = await assertNoInheritedJwkPropertyAccess( + `wrapKey jwk ${options.name}`, + () => assertNoInheritedToJSONAccess( + `wrapKey jwk ${options.name}`, + () => assertNoUserMutableEncodeAccess( + `wrapKey jwk ${options.name}`, () => + assertNoPromiseConstructorAccess(`wrapKey jwk ${options.name}`, () => + subtle.wrapKey('jwk', keyToWrap, ctx.key, options.wrapAlgorithm))))); + }; + fixture.unwrapKey = async (ctx) => { + await assertNoInheritedArrayBufferThenAccess(`unwrapKey raw-secret ${options.name}`, () => + assertCryptoKeyResult(`unwrapKey raw-secret ${options.name} result`, () => + subtle.unwrapKey( + 'raw-secret', + ctx.wrappedRawSecret, + ctx.key, + options.wrapAlgorithm, + algorithm('AES-CBC', { length: 128 }), + true, + ['encrypt']))); + await assertNoUserMutableDecodeAccess(`unwrapKey jwk ${options.name}`, () => + assertNoInheritedArrayBufferThenAccess(`unwrapKey jwk ${options.name}`, () => + assertCryptoKeyResult(`unwrapKey jwk ${options.name} result`, () => + subtle.unwrapKey( + 'jwk', + ctx.wrappedJwk, + ctx.key, + options.wrapAlgorithm, + algorithm('AES-CBC', { length: 128 }), + true, + ['encrypt'])))); + }; + } + + return fixture; +} + +function pairKeyFixture(options) { + const fixture = { + ...options, + generateKey: async (ctx) => { + ctx.keyPair = await assertNoPromiseConstructorAccess(`generateKey ${options.name}`, () => + subtle.generateKey(options.generateAlgorithm, true, options.usages)); + }, + exportKey: async (ctx) => { + if (options.spki !== false) { + ctx.spki = await exportArrayBuffer(`${options.name} public`, 'spki', ctx.keyPair.publicKey); + ctx.pkcs8 = await exportArrayBuffer(`${options.name} private`, 'pkcs8', ctx.keyPair.privateKey); + } + if (options.rawPublic === true) { + ctx.rawPublic = await exportArrayBuffer( + `${options.name} public`, + 'raw-public', + ctx.keyPair.publicKey); + } + if (options.rawSeed === true) { + ctx.rawSeed = await exportArrayBuffer( + `${options.name} private`, + 'raw-seed', + ctx.keyPair.privateKey); + } + ctx.publicJwk = await exportJwk(`${options.name} public`, ctx.keyPair.publicKey); + ctx.privateJwk = await exportJwk(`${options.name} private`, ctx.keyPair.privateKey); + }, + importKey: async (ctx) => { + if (options.spki !== false) { + ctx.importedSpki = await importCryptoKey( + `${options.name} public`, + 'spki', + ctx.spki, + options.importAlgorithm, + true, + options.publicUsages); + ctx.importedPkcs8 = await importCryptoKey( + `${options.name} private`, + 'pkcs8', + ctx.pkcs8, + options.importAlgorithm, + true, + options.privateUsages); + } + if (options.rawPublic === true) { + ctx.importedRawPublic = await importCryptoKey( + `${options.name} public`, + 'raw-public', + ctx.rawPublic, + options.importAlgorithm, true, - ['encrypt']))); - await assertNoInheritedCryptoKeyThenAccess( - 'unwrapKey jwk AES-CBC result', - () => subtle.unwrapKey( + options.publicUsages); + } + if (options.rawSeed === true) { + ctx.importedRawSeed = await importCryptoKey( + `${options.name} private`, + 'raw-seed', + ctx.rawSeed, + options.importAlgorithm, + true, + options.privateUsages); + } + ctx.importedPublicJwk = await importCryptoKey( + `${options.name} public`, + 'jwk', + ctx.publicJwk, + options.importAlgorithm, + true, + options.publicUsages); + ctx.importedPrivateJwk = await importCryptoKey( + `${options.name} private`, 'jwk', - validWrappedJwk, - jwkUnwrappingKey, - { name: 'AES-CBC', iv }, - { name: 'AES-CBC', length: 128 }, + ctx.privateJwk, + options.importAlgorithm, true, - ['encrypt'])); + options.privateUsages); + }, + }; + + if (options.signAlgorithm !== undefined) { + fixture.sign = async (ctx) => { + ctx.signature = await assertNoPromiseConstructorAccess(`sign ${options.name}`, () => + subtle.sign(options.signAlgorithm, ctx.keyPair.privateKey, data)); + }; + fixture.verify = async (ctx) => { + await assertNoPromiseConstructorAccess(`verify ${options.name}`, () => + subtle.verify(options.signAlgorithm, ctx.keyPair.publicKey, ctx.signature, data)); + }; } - { - const iv = globalThis.crypto.getRandomValues(new Uint8Array(16)); - const wrappedRawKey = await subtle.encrypt( - { name: 'AES-CBC', iv }, - jwkUnwrappingKey, - rawKey); - - await assertNoPromiseConstructorAccess('unwrapKey raw AES-CBC', () => - subtle.unwrapKey( - 'raw', - wrappedRawKey, - jwkUnwrappingKey, - { name: 'AES-CBC', iv }, - { name: 'AES-CBC', length: 256 }, - true, - ['encrypt'])); - await assertNoInheritedArrayBufferThenAccess('unwrapKey raw AES-CBC', () => - subtle.unwrapKey( - 'raw', - wrappedRawKey, - jwkUnwrappingKey, - { name: 'AES-CBC', iv }, - { name: 'AES-CBC', length: 256 }, + if (options.encryptAlgorithm !== undefined) { + fixture.encrypt = async (ctx) => { + ctx.ciphertext = await assertNoPromiseConstructorAccess(`encrypt ${options.name}`, () => + subtle.encrypt(options.encryptAlgorithm, ctx.keyPair.publicKey, data)); + }; + fixture.decrypt = async (ctx) => { + await assertNoPromiseConstructorAccess(`decrypt ${options.name}`, () => + subtle.decrypt(options.encryptAlgorithm, ctx.keyPair.privateKey, ctx.ciphertext)); + }; + } + + if (options.deriveAlgorithm !== undefined) { + fixture.deriveBits = async (ctx) => { + ctx.peerKeyPair ??= await subtle.generateKey( + options.generateAlgorithm, true, - ['encrypt'])); - await assertNoInheritedCryptoKeyThenAccess( - 'unwrapKey raw AES-CBC result', - () => subtle.unwrapKey( - 'raw', - wrappedRawKey, - jwkUnwrappingKey, - { name: 'AES-CBC', iv }, - { name: 'AES-CBC', length: 256 }, + options.usages); + ctx.derivedBits = await assertNoPromiseConstructorAccess(`deriveBits ${options.name}`, () => + subtle.deriveBits( + options.deriveAlgorithm(ctx.peerKeyPair.publicKey), + ctx.keyPair.privateKey, + options.deriveLength)); + }; + fixture.extra = async (ctx) => { + ctx.peerKeyPair ??= await subtle.generateKey( + options.generateAlgorithm, true, - ['encrypt'])); + options.usages); + await assertNoInheritedArrayBufferThenAccess(`deriveKey ${options.name}`, () => + assertCryptoKeyResult(`deriveKey ${options.name} result`, () => + subtle.deriveKey( + options.deriveAlgorithm(ctx.peerKeyPair.publicKey), + ctx.keyPair.privateKey, + algorithm('AES-CBC', { length: 128 }), + true, + ['encrypt']))); + }; } - { - const iv = globalThis.crypto.getRandomValues(new Uint8Array(16)); - const missingKtyWrappedJwk = await subtle.encrypt( - { name: 'AES-CBC', iv }, - jwkUnwrappingKey, - Buffer.from('{"k":"AAAAAAAAAAAAAAAAAAAAAA"}')); + fixture.getPublicKey = async (ctx) => { + await assertCryptoKeyResult(`getPublicKey ${options.name}`, () => + subtle.getPublicKey(ctx.keyPair.privateKey, options.publicUsages)); + }; - await assertMissingJwkKtyIgnoresPrototype(() => - subtle.unwrapKey( - 'jwk', - missingKtyWrappedJwk, - jwkUnwrappingKey, - { name: 'AES-CBC', iv }, - { name: 'AES-CBC', length: 128 }, - true, - ['encrypt'])); - } + return fixture; } -await assertNoPromiseConstructorAccess('getPublicKey', () => - subtle.getPublicKey(extractableKeyPair.privateKey, ['verify'])); -await assertNoInheritedCryptoKeyThenAccess('getPublicKey', () => - subtle.getPublicKey(extractableKeyPair.privateKey, ['verify'])); - -if (hasOpenSSL(3, 5) || process.features.openssl_is_boringssl) { - const kemPair = await subtle.generateKey( - { name: 'ML-KEM-768' }, true, - ['encapsulateKey', 'encapsulateBits', 'decapsulateKey', 'decapsulateBits']); +function kdfFixture(options) { + return { + ...options, + importKey: async (ctx) => { + ctx.key = await importCryptoKey( + options.name, + options.rawFormat, + new Uint8Array(32).fill(1), + options.importAlgorithm, + false, + ['deriveBits', 'deriveKey']); + }, + deriveBits: async (ctx) => { + await assertNoPromiseConstructorAccess(`deriveBits ${options.name}`, () => + subtle.deriveBits(options.deriveAlgorithm, ctx.key, 256)); + }, + extra: async (ctx) => { + await assertNoInheritedArrayBufferThenAccess(`deriveKey ${options.name}`, () => + assertCryptoKeyResult(`deriveKey ${options.name} result`, () => + subtle.deriveKey( + options.deriveAlgorithm, + ctx.key, + algorithm('AES-CBC', { length: 128 }), + true, + ['encrypt']))); + }, + }; +} - await assertNoInheritedArrayBufferThenAccess('exportKey raw-seed', () => - subtle.exportKey('raw-seed', kemPair.privateKey)); - await assertExportKeyNoPromiseConstructorAccess( - 'raw-seed', - 'raw-seed', - kemPair.privateKey); +function kemFixture(options) { + const fixture = pairKeyFixture({ + ...options, + usages: [ + 'encapsulateKey', + 'encapsulateBits', + 'decapsulateKey', + 'decapsulateBits', + ], + publicUsages: ['encapsulateKey', 'encapsulateBits'], + privateUsages: ['decapsulateKey', 'decapsulateBits'], + rawPublic: true, + rawSeed: true, + }); - const { ciphertext: ct1 } = - await assertNoRawSharedKeyObjectThenAccess('encapsulateKey', () => - assertNoPromiseConstructorAccess('encapsulateKey', () => + fixture.encapsulate = async (ctx) => { + ctx.encapsulatedBits = await assertNoPromiseConstructorAccess( + `encapsulateBits ${options.name}`, () => + subtle.encapsulateBits(algorithm(options.name), ctx.keyPair.publicKey)); + ctx.encapsulatedKey = await assertNoRawSharedKeyObjectThenAccess( + `encapsulateKey ${options.name}`, + () => assertNoPromiseConstructorAccess(`encapsulateKey ${options.name}`, () => subtle.encapsulateKey( - { name: 'ML-KEM-768' }, - kemPair.publicKey, + algorithm(options.name), + ctx.keyPair.publicKey, 'HKDF', false, ['deriveBits']))); + }; + fixture.decapsulate = async (ctx) => { + await assertNoPromiseConstructorAccess(`decapsulateBits ${options.name}`, () => + subtle.decapsulateBits( + algorithm(options.name), + ctx.keyPair.privateKey, + ctx.encapsulatedBits.ciphertext)); + await assertNoInheritedArrayBufferThenAccess(`decapsulateKey ${options.name}`, () => + assertCryptoKeyResult(`decapsulateKey ${options.name} result`, () => + subtle.decapsulateKey( + algorithm(options.name), + ctx.keyPair.privateKey, + ctx.encapsulatedKey.ciphertext, + 'HKDF', + false, + ['deriveBits']))); + }; + + return fixture; +} + +// The fixture registry mirrors kSupportedAlgorithms by algorithm name. Each +// fixture supplies the public SubtleCrypto calls needed to exercise the +// registered operations for that algorithm. +const fixtures = new Map(); + +function addFixture(name, fixture) { + fixtures.set(name, fixture); +} + +for (const name of ['AES-CBC', 'AES-CTR', 'AES-GCM', 'AES-OCB']) { + const encryptAlgorithms = { + 'AES-CBC': algorithm(name, { iv: new Uint8Array(16) }), + 'AES-CTR': algorithm(name, { counter: new Uint8Array(16), length: 64 }), + 'AES-GCM': algorithm(name, { + iv: new Uint8Array(12), + additionalData: new Uint8Array(1), + tagLength: 128, + }), + 'AES-OCB': algorithm(name, { + iv: new Uint8Array(15), + additionalData: new Uint8Array(1), + tagLength: 128, + }), + }; + addFixture(name, secretKeyFixture({ + name, + generateAlgorithm: algorithm(name, { length: 128 }), + importAlgorithm: algorithm(name), + usages: ['encrypt', 'decrypt'], + rawFormat: 'raw-secret', + encryptAlgorithm: encryptAlgorithms[name], + })); +} + +addFixture('AES-KW', secretKeyFixture({ + name: 'AES-KW', + generateAlgorithm: algorithm('AES-KW', { length: 128 }), + importAlgorithm: algorithm('AES-KW'), + usages: ['wrapKey', 'unwrapKey'], + rawFormat: 'raw-secret', + wrapAlgorithm: 'AES-KW', +})); + +addFixture('ChaCha20-Poly1305', secretKeyFixture({ + name: 'ChaCha20-Poly1305', + generateAlgorithm: algorithm('ChaCha20-Poly1305'), + importAlgorithm: algorithm('ChaCha20-Poly1305'), + usages: ['encrypt', 'decrypt'], + rawFormat: 'raw-secret', + encryptAlgorithm: algorithm('ChaCha20-Poly1305', { + iv: new Uint8Array(12), + additionalData: new Uint8Array(1), + tagLength: 128, + }), +})); + +addFixture('HMAC', secretKeyFixture({ + name: 'HMAC', + generateAlgorithm: algorithm('HMAC', { hash: 'SHA-256', length: 256 }), + importAlgorithm: algorithm('HMAC', { hash: 'SHA-256' }), + usages: ['sign', 'verify'], + rawFormat: 'raw-secret', + signAlgorithm: 'HMAC', +})); + +for (const name of ['KMAC128', 'KMAC256']) { + addFixture(name, secretKeyFixture({ + name, + generateAlgorithm: algorithm(name, { + length: name === 'KMAC128' ? 128 : 256, + }), + importAlgorithm: algorithm(name), + usages: ['sign', 'verify'], + rawFormat: 'raw-secret', + signAlgorithm: algorithm(name, { outputLength: 256 }), + })); +} + +for (const name of ['RSASSA-PKCS1-v1_5', 'RSA-PSS']) { + addFixture(name, pairKeyFixture({ + name, + generateAlgorithm: rsaAlgorithm(name), + importAlgorithm: importRsaAlgorithm(name), + usages: ['sign', 'verify'], + publicUsages: ['verify'], + privateUsages: ['sign'], + signAlgorithm: name === 'RSA-PSS' ? + algorithm(name, { saltLength: 32 }) : + algorithm(name), + })); +} + +addFixture('RSA-OAEP', pairKeyFixture({ + name: 'RSA-OAEP', + generateAlgorithm: rsaAlgorithm('RSA-OAEP'), + importAlgorithm: importRsaAlgorithm('RSA-OAEP'), + usages: ['encrypt', 'decrypt'], + publicUsages: ['encrypt'], + privateUsages: ['decrypt'], + encryptAlgorithm: algorithm('RSA-OAEP', { label: new Uint8Array(1) }), +})); + +addFixture('ECDSA', pairKeyFixture({ + name: 'ECDSA', + generateAlgorithm: algorithm('ECDSA', { namedCurve: 'P-256' }), + importAlgorithm: algorithm('ECDSA', { namedCurve: 'P-256' }), + usages: ['sign', 'verify'], + publicUsages: ['verify'], + privateUsages: ['sign'], + rawPublic: true, + signAlgorithm: algorithm('ECDSA', { hash: 'SHA-256' }), +})); + +addFixture('ECDH', pairKeyFixture({ + name: 'ECDH', + generateAlgorithm: algorithm('ECDH', { namedCurve: 'P-256' }), + importAlgorithm: algorithm('ECDH', { namedCurve: 'P-256' }), + usages: ['deriveBits', 'deriveKey'], + publicUsages: [], + privateUsages: ['deriveBits', 'deriveKey'], + rawPublic: true, + deriveAlgorithm: (publicKey) => algorithm('ECDH', { public: publicKey }), + deriveLength: 256, +})); + +for (const name of ['Ed25519', 'Ed448']) { + addFixture(name, pairKeyFixture({ + name, + generateAlgorithm: algorithm(name), + importAlgorithm: algorithm(name), + usages: ['sign', 'verify'], + publicUsages: ['verify'], + privateUsages: ['sign'], + rawPublic: true, + signAlgorithm: algorithm(name), + })); +} + +for (const name of ['X25519', 'X448']) { + addFixture(name, pairKeyFixture({ + name, + generateAlgorithm: algorithm(name), + importAlgorithm: algorithm(name), + usages: ['deriveBits', 'deriveKey'], + publicUsages: [], + privateUsages: ['deriveBits', 'deriveKey'], + rawPublic: true, + deriveAlgorithm: (publicKey) => algorithm(name, { public: publicKey }), + deriveLength: name === 'X25519' ? 256 : 448, + })); +} + +for (const name of ['ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87']) { + addFixture(name, pairKeyFixture({ + name, + generateAlgorithm: algorithm(name), + importAlgorithm: algorithm(name), + usages: ['sign', 'verify'], + publicUsages: ['verify'], + privateUsages: ['sign'], + rawPublic: true, + rawSeed: true, + signAlgorithm: algorithm(name), + })); +} + +for (const name of [ + 'ML-KEM-512', + 'ML-KEM-768', + 'ML-KEM-1024', +]) { + addFixture(name, kemFixture({ + name, + generateAlgorithm: algorithm(name), + importAlgorithm: algorithm(name), + })); +} + +for (const name of ['HKDF', 'PBKDF2']) { + addFixture(name, kdfFixture({ + name, + importAlgorithm: name, + rawFormat: 'raw-secret', + deriveAlgorithm: name === 'HKDF' ? + algorithm(name, { + hash: 'SHA-256', + salt: new Uint8Array(8), + info: new Uint8Array(8), + }) : + algorithm(name, { + hash: 'SHA-256', + salt: new Uint8Array(8), + iterations: 1, + }), + })); +} + +for (const name of ['Argon2d', 'Argon2i', 'Argon2id']) { + addFixture(name, kdfFixture({ + name, + importAlgorithm: name, + rawFormat: 'raw-secret', + deriveAlgorithm: algorithm(name, { + memory: 32, + passes: 1, + parallelism: 1, + nonce: new Uint8Array(16), + }), + })); +} + +function digestAlgorithm(name) { + if (name === 'cSHAKE128') + return algorithm(name, { outputLength: 256 }); + if (name === 'cSHAKE256') + return algorithm(name, { outputLength: 512 }); + if (name === 'TurboSHAKE128') + return algorithm(name, { outputLength: 256 }); + if (name === 'TurboSHAKE256') + return algorithm(name, { outputLength: 512 }); + if (name === 'KT128') + return algorithm(name, { outputLength: 256 }); + if (name === 'KT256') + return algorithm(name, { outputLength: 512 }); + return name; +} + +for (const name of [ + 'SHA-1', + 'SHA-256', + 'SHA-384', + 'SHA-512', + 'SHA3-256', + 'SHA3-384', + 'SHA3-512', + 'cSHAKE128', + 'cSHAKE256', + 'TurboSHAKE128', + 'TurboSHAKE256', + 'KT128', + 'KT256', +]) { + addFixture(name, { + name, + digest: async () => { + await assertNoPromiseConstructorAccess(`digest ${name}`, () => + subtle.digest(digestAlgorithm(name), data)); + }, + }); +} + +// deriveKey() is the only public API that performs the "get key length" +// registry operation. Keep this table in sync with algorithms that can be a +// concrete derived-key target. +const keyLengthTargets = { + 'AES-CBC': { + algorithm: algorithm('AES-CBC', { length: 128 }), + usages: ['encrypt'], + }, + 'AES-CTR': { + algorithm: algorithm('AES-CTR', { length: 128 }), + usages: ['encrypt'], + }, + 'AES-GCM': { + algorithm: algorithm('AES-GCM', { length: 128 }), + usages: ['encrypt'], + }, + 'AES-KW': { + algorithm: algorithm('AES-KW', { length: 128 }), + usages: ['wrapKey'], + }, + 'AES-OCB': { + algorithm: algorithm('AES-OCB', { length: 128 }), + usages: ['encrypt'], + }, + 'ChaCha20-Poly1305': { + algorithm: algorithm('ChaCha20-Poly1305'), + usages: ['encrypt'], + }, + 'HMAC': { + algorithm: algorithm('HMAC', { hash: 'SHA-256', length: 256 }), + usages: ['sign'], + }, + 'KMAC128': { + algorithm: algorithm('KMAC128', { length: 128 }), + usages: ['sign'], + }, + 'KMAC256': { + algorithm: algorithm('KMAC256', { length: 256 }), + usages: ['sign'], + }, +}; + +function getSupportedAlgorithmOperations() { + const algorithms = new Map(); + for (const operation of Object.keys(kSupportedAlgorithms)) { + if (operation === 'get key length') + continue; + for (const name of Object.keys(kSupportedAlgorithms[operation])) { + if (!algorithms.has(name)) + algorithms.set(name, new Set()); + algorithms.get(name).add(operation); + } + } + return algorithms; +} + +// This is the list of supported public registry operations that this file +// exercises. A new operation name must be added here before the registry +// assertion below will pass. +const operationOrder = [ + 'digest', + 'generateKey', + 'exportKey', + 'importKey', + 'encrypt', + 'decrypt', + 'sign', + 'verify', + 'deriveBits', + 'encapsulate', + 'decapsulate', + 'wrapKey', + 'unwrapKey', +]; + +const coveredOperations = new Set([ + ...operationOrder, + 'get key length', +]); + +for (const operation of Object.keys(kSupportedAlgorithms)) { + assert( + coveredOperations.has(operation), + `missing prototype pollution operation coverage for ${operation}`); +} + +const supportedAlgorithms = getSupportedAlgorithmOperations(); +for (const [name, operations] of supportedAlgorithms) { + const fixture = fixtures.get(name); + assert(fixture, `missing prototype pollution fixture for ${name}`); + + const ctx = { __proto__: null }; + for (const operation of operationOrder) { + if (!operations.has(operation)) + continue; + assert.strictEqual( + typeof fixture[operation], + 'function', + `missing prototype pollution coverage for ${name} ${operation}`); + await fixture[operation](ctx); + } + + if (typeof fixture.getPublicKey === 'function' && + ctx.keyPair?.privateKey !== undefined) { + await fixture.getPublicKey(ctx); + } + if (typeof fixture.extra === 'function') + await fixture.extra(ctx); +} + +const getKeyLengthAlgorithms = + Object.keys(kSupportedAlgorithms['get key length'] ?? {}); +// KDF base algorithms return null from getKeyLength(). They still need to be +// listed explicitly so a newly registered get-key-length algorithm does not +// silently skip prototype-pollution coverage. +const nullKeyLengthAlgorithms = [ + 'HKDF', + 'PBKDF2', + 'Argon2d', + 'Argon2i', + 'Argon2id', +]; +const pbkdf2Key = await subtle.importKey( + 'raw-secret', + new Uint8Array(32).fill(1), + 'PBKDF2', + false, + ['deriveKey']); +for (const name of getKeyLengthAlgorithms) { + const target = keyLengthTargets[name]; + if (target === undefined) { + assert( + nullKeyLengthAlgorithms.includes(name), + `missing get key length coverage for ${name}`); + continue; + } + + await assertCryptoKeyResult(`get key length ${name}`, () => + subtle.deriveKey( + algorithm('PBKDF2', { + hash: 'SHA-256', + salt: new Uint8Array(8), + iterations: 1, + }), + pbkdf2Key, + target.algorithm, + true, + target.usages)); +} + +// Keep one explicit unwrapKey('jwk') negative case: the parsed object must not +// inherit kty from Object.prototype when the wrapped JSON does not have it. +{ + const jwkUnwrappingKey = await subtle.generateKey( + algorithm('AES-CBC', { length: 128 }), + true, + ['encrypt', 'unwrapKey']); + const iv = new Uint8Array(16); + const missingKtyWrappedJwk = await subtle.encrypt( + algorithm('AES-CBC', { iv }), + jwkUnwrappingKey, + new TextEncoder().encode('{"k":"AAAAAAAAAAAAAAAAAAAAAA"}')); - await assertNoPromiseConstructorAccess('decapsulateKey', () => - subtle.decapsulateKey( - { name: 'ML-KEM-768' }, - kemPair.privateKey, - ct1, - 'HKDF', - false, - ['deriveBits'])); - await assertNoInheritedArrayBufferThenAccess('decapsulateKey', () => - subtle.decapsulateKey( - { name: 'ML-KEM-768' }, - kemPair.privateKey, - ct1, - 'HKDF', - false, - ['deriveBits'])); - await assertNoInheritedCryptoKeyThenAccess('decapsulateKey result', () => - subtle.decapsulateKey( - { name: 'ML-KEM-768' }, - kemPair.privateKey, - ct1, - 'HKDF', - false, - ['deriveBits'])); - - const { ciphertext: ct2 } = - await assertNoPromiseConstructorAccess('encapsulateBits', () => - subtle.encapsulateBits( - { name: 'ML-KEM-768' }, - kemPair.publicKey)); - - await assertNoPromiseConstructorAccess('decapsulateBits', () => - subtle.decapsulateBits( - { name: 'ML-KEM-768' }, - kemPair.privateKey, - ct2)); + await assertMissingJwkKtyIgnoresPrototype(() => + subtle.unwrapKey( + 'jwk', + missingKtyWrappedJwk, + jwkUnwrappingKey, + algorithm('AES-CBC', { iv }), + algorithm('AES-CBC', { length: 128 }), + true, + ['encrypt'])); } diff --git a/test/test426/README.md b/test/test426/README.md index 58b499920f7087..00f249c8d36a29 100644 --- a/test/test426/README.md +++ b/test/test426/README.md @@ -7,7 +7,7 @@ suite ensures that the Node.js source map implementation conforms to the The [`test/fixtures/test426`](../fixtures/test426/) contains a copy of the set of [Source Map Tests][] suite. The last updated hash is: -* +* See the json files in [the `status` folder](./status) for prerequisites, expected failures, and support status for specific tests.