From 1ea93c7f3ac5c495cb8c113c0f4c995589acc9c5 Mon Sep 17 00:00:00 2001 From: Chengzhong Wu Date: Thu, 26 Mar 2026 01:55:01 -0400 Subject: [PATCH 1/6] repl: use vm DONT_CONTEXTIFY context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/62371 Reviewed-By: Michaël Zasso Reviewed-By: Aviv Keller Reviewed-By: Luigi Pinca --- lib/repl.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/repl.js b/lib/repl.js index 779740bfd411ad..60ea4457ab1bf6 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -1132,10 +1132,10 @@ class REPLServer extends Interface { session.once('Runtime.executionContextCreated', ({ params }) => { this[kContextId] = params.context.id; }); - context = vm.createContext(); + context = vm.createContext(vm.constants.DONT_CONTEXTIFY); session.post('Runtime.disable'); }, () => { - context = vm.createContext(); + context = vm.createContext(vm.constants.DONT_CONTEXTIFY); }); ArrayPrototypeForEach(ObjectGetOwnPropertyNames(globalThis), (name) => { // Only set properties that do not already exist as a global builtin. From d4fa60cf9fcdcd528aac441994e0863e0eacd45b Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Fri, 13 Mar 2026 15:04:59 +0100 Subject: [PATCH 2/6] crypto: add raw key formats support to the KeyObject APIs Add raw key format support (raw-public, raw-private, raw-seed) to KeyObject.prototype.export(), crypto.createPrivateKey(), and crypto.createPublicKey() for applicable asymmetric keys (EC, CFRG curves, ML-DSA, ML-KEM, and SLH-DSA). Also wire these to the Web Cryptography APIs and remove the unnecessary KeyExportJob. The KeyExportJob classes were removed because the export operations they performed are not computationally expensive. They're just serialization of already-available key data (SPKI, PKCS8, raw bytes). Running them as async CryptoJobs on the libuv thread pool added unnecessary overhead and complexity for operations that complete instantly. PR-URL: https://github.com/nodejs/node/pull/62240 Reviewed-By: Matteo Collina Reviewed-By: Anna Henningsen Reviewed-By: Yagiz Nizipli Reviewed-By: James M Snell --- benchmark/crypto/create-keyobject.js | 39 +- benchmark/crypto/kem.js | 147 ++++-- benchmark/crypto/oneshot-sign.js | 34 +- benchmark/crypto/oneshot-verify.js | 24 +- doc/api/crypto.md | 410 +++++++++++++--- lib/internal/crypto/cfrg.js | 33 +- lib/internal/crypto/ec.js | 57 ++- lib/internal/crypto/keys.js | 163 +++++-- lib/internal/crypto/ml_dsa.js | 37 +- lib/internal/crypto/ml_kem.js | 37 +- lib/internal/crypto/rsa.js | 35 +- lib/internal/crypto/webcrypto.js | 24 +- src/crypto/README.md | 7 +- src/crypto/crypto_dh.cc | 30 -- src/crypto/crypto_dh.h | 23 - src/crypto/crypto_ec.cc | 133 ------ src/crypto/crypto_ec.h | 26 - src/crypto/crypto_keys.cc | 393 +++++++++++---- src/crypto/crypto_keys.h | 151 +----- src/crypto/crypto_rsa.cc | 44 -- src/crypto/crypto_rsa.h | 24 - src/node_errors.h | 3 + test/parallel/test-crypto-encap-decap.js | 32 +- test/parallel/test-crypto-key-objects-raw.js | 446 ++++++++++++++++++ test/parallel/test-crypto-key-objects.js | 83 ++++ .../test-crypto-pqc-key-objects-ml-dsa.js | 22 +- .../test-crypto-pqc-key-objects-ml-kem.js | 16 + .../test-crypto-pqc-key-objects-slh-dsa.js | 16 + .../test-crypto-pqc-sign-verify-ml-dsa.js | 22 + test/parallel/test-crypto-sign-verify.js | 32 +- .../test-webcrypto-export-import-cfrg.js | 7 +- .../test-webcrypto-export-import-ec.js | 16 +- .../test-webcrypto-export-import-ml-dsa.js | 2 - .../test-webcrypto-export-import-ml-kem.js | 2 - 34 files changed, 1826 insertions(+), 744 deletions(-) create mode 100644 test/parallel/test-crypto-key-objects-raw.js diff --git a/benchmark/crypto/create-keyobject.js b/benchmark/crypto/create-keyobject.js index 58b873cde7f27a..30f8213175df69 100644 --- a/benchmark/crypto/create-keyobject.js +++ b/benchmark/crypto/create-keyobject.js @@ -30,8 +30,22 @@ if (hasOpenSSL(3, 5)) { const bench = common.createBenchmark(main, { keyType: Object.keys(keyFixtures), - keyFormat: ['pkcs8', 'spki', 'der-pkcs8', 'der-spki', 'jwk-public', 'jwk-private'], + keyFormat: ['pkcs8', 'spki', 'der-pkcs8', 'der-spki', 'jwk-public', 'jwk-private', + 'raw-public', 'raw-private', 'raw-seed'], n: [1e3], +}, { + combinationFilter(p) { + // raw-private is not supported for rsa and ml-dsa + if (p.keyFormat === 'raw-private') + return p.keyType !== 'rsa' && !p.keyType.startsWith('ml-'); + // raw-public is not supported by rsa + if (p.keyFormat === 'raw-public') + return p.keyType !== 'rsa'; + // raw-seed is only supported for ml-dsa + if (p.keyFormat === 'raw-seed') + return p.keyType.startsWith('ml-'); + return true; + }, }); function measure(n, fn, input) { @@ -82,6 +96,29 @@ function main({ n, keyFormat, keyType }) { fn = crypto.createPrivateKey; break; } + case 'raw-public': { + const exportedKey = keyPair.publicKey.export({ format: 'raw-public' }); + key = { key: exportedKey, format: 'raw-public', asymmetricKeyType: keyType }; + if (keyType === 'ec') key.namedCurve = keyPair.publicKey.asymmetricKeyDetails.namedCurve; + fn = crypto.createPublicKey; + break; + } + case 'raw-private': { + const exportedKey = keyPair.privateKey.export({ format: 'raw-private' }); + key = { key: exportedKey, format: 'raw-private', asymmetricKeyType: keyType }; + if (keyType === 'ec') key.namedCurve = keyPair.privateKey.asymmetricKeyDetails.namedCurve; + fn = crypto.createPrivateKey; + break; + } + case 'raw-seed': { + key = { + key: keyPair.privateKey.export({ format: 'raw-seed' }), + format: 'raw-seed', + asymmetricKeyType: keyType, + }; + fn = crypto.createPrivateKey; + break; + } default: throw new Error('not implemented'); } diff --git a/benchmark/crypto/kem.js b/benchmark/crypto/kem.js index c36e79957a115c..e03ae65f1926ca 100644 --- a/benchmark/crypto/kem.js +++ b/benchmark/crypto/kem.js @@ -11,22 +11,29 @@ function readKey(name) { return fs.readFileSync(`${fixtures_keydir}/${name}.pem`, 'utf8'); } +function readKeyPair(publicKeyName, privateKeyName) { + return { + publicKey: readKey(publicKeyName), + privateKey: readKey(privateKeyName), + }; +} + const keyFixtures = {}; if (hasOpenSSL(3, 5)) { - keyFixtures['ml-kem-512'] = readKey('ml_kem_512_private'); - keyFixtures['ml-kem-768'] = readKey('ml_kem_768_private'); - keyFixtures['ml-kem-1024'] = readKey('ml_kem_1024_private'); + keyFixtures['ml-kem-512'] = readKeyPair('ml_kem_512_public', 'ml_kem_512_private'); + keyFixtures['ml-kem-768'] = readKeyPair('ml_kem_768_public', 'ml_kem_768_private'); + keyFixtures['ml-kem-1024'] = readKeyPair('ml_kem_1024_public', 'ml_kem_1024_private'); } if (hasOpenSSL(3, 2)) { - keyFixtures['p-256'] = readKey('ec_p256_private'); - keyFixtures['p-384'] = readKey('ec_p384_private'); - keyFixtures['p-521'] = readKey('ec_p521_private'); - keyFixtures.x25519 = readKey('x25519_private'); - keyFixtures.x448 = readKey('x448_private'); + keyFixtures['p-256'] = readKeyPair('ec_p256_public', 'ec_p256_private'); + keyFixtures['p-384'] = readKeyPair('ec_p384_public', 'ec_p384_private'); + keyFixtures['p-521'] = readKeyPair('ec_p521_public', 'ec_p521_private'); + keyFixtures.x25519 = readKeyPair('x25519_public', 'x25519_private'); + keyFixtures.x448 = readKeyPair('x448_public', 'x448_private'); } if (hasOpenSSL(3, 0)) { - keyFixtures.rsa = readKey('rsa_private_2048'); + keyFixtures.rsa = readKeyPair('rsa_public_2048', 'rsa_private_2048'); } if (Object.keys(keyFixtures).length === 0) { @@ -37,32 +44,46 @@ if (Object.keys(keyFixtures).length === 0) { const bench = common.createBenchmark(main, { keyType: Object.keys(keyFixtures), mode: ['sync', 'async', 'async-parallel'], - keyFormat: ['keyObject', 'keyObject.unique'], + keyFormat: ['keyObject', 'keyObject.unique', 'pem', 'der', 'jwk', + 'raw-public', 'raw-private', 'raw-seed'], op: ['encapsulate', 'decapsulate'], n: [1e3], }, { combinationFilter(p) { // "keyObject.unique" allows to compare the result with "keyObject" to // assess whether mutexes over the key material impact the operation - return p.keyFormat !== 'keyObject.unique' || - (p.keyFormat === 'keyObject.unique' && p.mode === 'async-parallel'); + if (p.keyFormat === 'keyObject.unique') + return p.mode === 'async-parallel'; + // JWK is not supported for ml-kem for now + if (p.keyFormat === 'jwk') + return !p.keyType.startsWith('ml-'); + // raw-public is only supported for encapsulate, not rsa + if (p.keyFormat === 'raw-public') + return p.keyType !== 'rsa' && p.op === 'encapsulate'; + // raw-private is not supported for rsa and ml-kem, only for decapsulate + if (p.keyFormat === 'raw-private') + return p.keyType !== 'rsa' && !p.keyType.startsWith('ml-') && p.op === 'decapsulate'; + // raw-seed is only supported for ml-kem + if (p.keyFormat === 'raw-seed') + return p.keyType.startsWith('ml-'); + return true; }, }); -function measureSync(n, op, privateKey, keys, ciphertexts) { +function measureSync(n, op, key, keys, ciphertexts) { bench.start(); for (let i = 0; i < n; ++i) { - const key = privateKey || keys[i]; + const k = key || keys[i]; if (op === 'encapsulate') { - crypto.encapsulate(key); + crypto.encapsulate(k); } else { - crypto.decapsulate(key, ciphertexts[i]); + crypto.decapsulate(k, ciphertexts[i]); } } bench.end(n); } -function measureAsync(n, op, privateKey, keys, ciphertexts) { +function measureAsync(n, op, key, keys, ciphertexts) { let remaining = n; function done() { if (--remaining === 0) @@ -72,18 +93,18 @@ function measureAsync(n, op, privateKey, keys, ciphertexts) { } function one() { - const key = privateKey || keys[n - remaining]; + const k = key || keys[n - remaining]; if (op === 'encapsulate') { - crypto.encapsulate(key, done); + crypto.encapsulate(k, done); } else { - crypto.decapsulate(key, ciphertexts[n - remaining], done); + crypto.decapsulate(k, ciphertexts[n - remaining], done); } } bench.start(); one(); } -function measureAsyncParallel(n, op, privateKey, keys, ciphertexts) { +function measureAsyncParallel(n, op, key, keys, ciphertexts) { let remaining = n; function done() { if (--remaining === 0) @@ -91,25 +112,79 @@ function measureAsyncParallel(n, op, privateKey, keys, ciphertexts) { } bench.start(); for (let i = 0; i < n; ++i) { - const key = privateKey || keys[i]; + const k = key || keys[i]; if (op === 'encapsulate') { - crypto.encapsulate(key, done); + crypto.encapsulate(k, done); } else { - crypto.decapsulate(key, ciphertexts[i], done); + crypto.decapsulate(k, ciphertexts[i], done); } } } function main({ n, mode, keyFormat, keyType, op }) { - const pems = [...Buffer.alloc(n)].map(() => keyFixtures[keyType]); - const keyObjects = pems.map(crypto.createPrivateKey); + const isEncapsulate = op === 'encapsulate'; + const pemSource = isEncapsulate ? + keyFixtures[keyType].publicKey : + keyFixtures[keyType].privateKey; + const createKeyFn = isEncapsulate ? crypto.createPublicKey : crypto.createPrivateKey; + const pems = [...Buffer.alloc(n)].map(() => pemSource); + const keyObjects = pems.map(createKeyFn); - let privateKey, keys, ciphertexts; + // Warm up OpenSSL's provider operation cache for each key object + if (isEncapsulate) { + for (const keyObject of keyObjects) { + crypto.encapsulate(keyObject); + } + } else { + const warmupCiphertext = crypto.encapsulate(keyObjects[0]).ciphertext; + for (const keyObject of keyObjects) { + crypto.decapsulate(keyObject, warmupCiphertext); + } + } + + const asymmetricKeyType = keyObjects[0].asymmetricKeyType; + let key, keys, ciphertexts; switch (keyFormat) { case 'keyObject': - privateKey = keyObjects[0]; + key = keyObjects[0]; + break; + case 'pem': + key = pems[0]; break; + case 'jwk': { + key = { key: keyObjects[0].export({ format: 'jwk' }), format: 'jwk' }; + break; + } + case 'der': { + const type = isEncapsulate ? 'spki' : 'pkcs8'; + key = { key: keyObjects[0].export({ format: 'der', type }), format: 'der', type }; + break; + } + case 'raw-public': { + const exportedKey = keyObjects[0].export({ format: 'raw-public' }); + const keyOpts = { key: exportedKey, format: 'raw-public', asymmetricKeyType }; + if (asymmetricKeyType === 'ec') keyOpts.namedCurve = keyObjects[0].asymmetricKeyDetails.namedCurve; + key = keyOpts; + break; + } + case 'raw-private': { + const exportedKey = keyObjects[0].export({ format: 'raw-private' }); + const keyOpts = { key: exportedKey, format: 'raw-private', asymmetricKeyType }; + if (asymmetricKeyType === 'ec') keyOpts.namedCurve = keyObjects[0].asymmetricKeyDetails.namedCurve; + key = keyOpts; + break; + } + case 'raw-seed': { + // raw-seed requires a private key to export from + const privateKeyObject = crypto.createPrivateKey(keyFixtures[keyType].privateKey); + key = { + key: privateKeyObject.export({ format: 'raw-seed' }), + format: 'raw-seed', + asymmetricKeyType, + }; + break; + } case 'keyObject.unique': keys = keyObjects; break; @@ -118,23 +193,25 @@ function main({ n, mode, keyFormat, keyType, op }) { } // Pre-generate ciphertexts for decapsulate operations - if (op === 'decapsulate') { - if (privateKey) { - ciphertexts = [...Buffer.alloc(n)].map(() => crypto.encapsulate(privateKey).ciphertext); + if (!isEncapsulate) { + const encapKey = crypto.createPublicKey( + crypto.createPrivateKey(keyFixtures[keyType].privateKey)); + if (key) { + ciphertexts = [...Buffer.alloc(n)].map(() => crypto.encapsulate(encapKey).ciphertext); } else { - ciphertexts = keys.map((key) => crypto.encapsulate(key).ciphertext); + ciphertexts = keys.map(() => crypto.encapsulate(encapKey).ciphertext); } } switch (mode) { case 'sync': - measureSync(n, op, privateKey, keys, ciphertexts); + measureSync(n, op, key, keys, ciphertexts); break; case 'async': - measureAsync(n, op, privateKey, keys, ciphertexts); + measureAsync(n, op, key, keys, ciphertexts); break; case 'async-parallel': - measureAsyncParallel(n, op, privateKey, keys, ciphertexts); + measureAsyncParallel(n, op, key, keys, ciphertexts); break; } } diff --git a/benchmark/crypto/oneshot-sign.js b/benchmark/crypto/oneshot-sign.js index e1942c347d7508..d0abc7b5412e60 100644 --- a/benchmark/crypto/oneshot-sign.js +++ b/benchmark/crypto/oneshot-sign.js @@ -29,14 +29,21 @@ let keyObjects; const bench = common.createBenchmark(main, { keyType: Object.keys(keyFixtures), mode: ['sync', 'async', 'async-parallel'], - keyFormat: ['pem', 'der', 'jwk', 'keyObject', 'keyObject.unique'], + keyFormat: ['pem', 'der', 'jwk', 'keyObject', 'keyObject.unique', 'raw-private', 'raw-seed'], n: [1e3], }, { combinationFilter(p) { // "keyObject.unique" allows to compare the result with "keyObject" to // assess whether mutexes over the key material impact the operation - return p.keyFormat !== 'keyObject.unique' || - (p.keyFormat === 'keyObject.unique' && p.mode === 'async-parallel'); + if (p.keyFormat === 'keyObject.unique') + return p.mode === 'async-parallel'; + // raw-private is not supported for rsa and ml-dsa + if (p.keyFormat === 'raw-private') + return p.keyType !== 'rsa' && !p.keyType.startsWith('ml-'); + // raw-seed is only supported for ml-dsa + if (p.keyFormat === 'raw-seed') + return p.keyType.startsWith('ml-'); + return true; }, }); @@ -91,6 +98,12 @@ function main({ n, mode, keyFormat, keyType }) { pems ||= [...Buffer.alloc(n)].map(() => keyFixtures[keyType]); keyObjects ||= pems.map(crypto.createPrivateKey); + // Warm up OpenSSL's provider operation cache for each key object + for (const keyObject of keyObjects) { + crypto.sign(keyType === 'rsa' || keyType === 'ec' ? 'sha256' : null, + data, keyObject); + } + let privateKey, keys, digest; switch (keyType) { @@ -120,6 +133,21 @@ function main({ n, mode, keyFormat, keyType }) { privateKey = { key: keyObjects[0].export({ format: 'der', type: 'pkcs8' }), format: 'der', type: 'pkcs8' }; break; } + case 'raw-private': { + const exportedKey = keyObjects[0].export({ format: 'raw-private' }); + const keyOpts = { key: exportedKey, format: 'raw-private', asymmetricKeyType: keyType }; + if (keyType === 'ec') keyOpts.namedCurve = keyObjects[0].asymmetricKeyDetails.namedCurve; + privateKey = keyOpts; + break; + } + case 'raw-seed': { + privateKey = { + key: keyObjects[0].export({ format: 'raw-seed' }), + format: 'raw-seed', + asymmetricKeyType: keyType, + }; + break; + } case 'keyObject.unique': keys = keyObjects; break; diff --git a/benchmark/crypto/oneshot-verify.js b/benchmark/crypto/oneshot-verify.js index e0bbc0ce755f15..c6a24f52126eb2 100644 --- a/benchmark/crypto/oneshot-verify.js +++ b/benchmark/crypto/oneshot-verify.js @@ -36,14 +36,18 @@ let keyObjects; const bench = common.createBenchmark(main, { keyType: Object.keys(keyFixtures), mode: ['sync', 'async', 'async-parallel'], - keyFormat: ['pem', 'der', 'jwk', 'keyObject', 'keyObject.unique'], + keyFormat: ['pem', 'der', 'jwk', 'keyObject', 'keyObject.unique', 'raw-public'], n: [1e3], }, { combinationFilter(p) { // "keyObject.unique" allows to compare the result with "keyObject" to // assess whether mutexes over the key material impact the operation - return p.keyFormat !== 'keyObject.unique' || - (p.keyFormat === 'keyObject.unique' && p.mode === 'async-parallel'); + if (p.keyFormat === 'keyObject.unique') + return p.mode === 'async-parallel'; + // raw-public is not supported by rsa + if (p.keyFormat === 'raw-public') + return p.keyType !== 'rsa'; + return true; }, }); @@ -101,6 +105,13 @@ function main({ n, mode, keyFormat, keyType }) { pems ||= [...Buffer.alloc(n)].map(() => keyFixtures[keyType].publicKey); keyObjects ||= pems.map(crypto.createPublicKey); + // Warm up OpenSSL's provider operation cache for each key object + const warmupDigest = keyType === 'rsa' || keyType === 'ec' ? 'sha256' : null; + const warmupSig = crypto.sign(warmupDigest, data, keyFixtures[keyType].privateKey); + for (const keyObject of keyObjects) { + crypto.verify(warmupDigest, data, keyObject, warmupSig); + } + let publicKey, keys, digest; switch (keyType) { @@ -130,6 +141,13 @@ function main({ n, mode, keyFormat, keyType }) { publicKey = { key: keyObjects[0].export({ format: 'der', type: 'spki' }), format: 'der', type: 'spki' }; break; } + case 'raw-public': { + const exportedKey = keyObjects[0].export({ format: 'raw-public' }); + const keyOpts = { key: exportedKey, format: 'raw-public', asymmetricKeyType: keyType }; + if (keyType === 'ec') keyOpts.namedCurve = keyObjects[0].asymmetricKeyDetails.namedCurve; + publicKey = keyOpts; + break; + } case 'keyObject.unique': keys = keyObjects; break; diff --git a/doc/api/crypto.md b/doc/api/crypto.md index acde45c346c84e..8f3f1fac1fae0b 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -75,37 +75,305 @@ try { ## Asymmetric key types -The following table lists the asymmetric key types recognized by the [`KeyObject`][] API: - -| Key Type | Description | OID | -| ---------------------------------- | ------------------ | ----------------------- | -| `'dh'` | Diffie-Hellman | 1.2.840.113549.1.3.1 | -| `'dsa'` | DSA | 1.2.840.10040.4.1 | -| `'ec'` | Elliptic curve | 1.2.840.10045.2.1 | -| `'ed25519'` | Ed25519 | 1.3.101.112 | -| `'ed448'` | Ed448 | 1.3.101.113 | -| `'ml-dsa-44'`[^openssl35] | ML-DSA-44 | 2.16.840.1.101.3.4.3.17 | -| `'ml-dsa-65'`[^openssl35] | ML-DSA-65 | 2.16.840.1.101.3.4.3.18 | -| `'ml-dsa-87'`[^openssl35] | ML-DSA-87 | 2.16.840.1.101.3.4.3.19 | -| `'ml-kem-512'`[^openssl35] | ML-KEM-512 | 2.16.840.1.101.3.4.4.1 | -| `'ml-kem-768'`[^openssl35] | ML-KEM-768 | 2.16.840.1.101.3.4.4.2 | -| `'ml-kem-1024'`[^openssl35] | ML-KEM-1024 | 2.16.840.1.101.3.4.4.3 | -| `'rsa-pss'` | RSA PSS | 1.2.840.113549.1.1.10 | -| `'rsa'` | RSA | 1.2.840.113549.1.1.1 | -| `'slh-dsa-sha2-128f'`[^openssl35] | SLH-DSA-SHA2-128f | 2.16.840.1.101.3.4.3.21 | -| `'slh-dsa-sha2-128s'`[^openssl35] | SLH-DSA-SHA2-128s | 2.16.840.1.101.3.4.3.20 | -| `'slh-dsa-sha2-192f'`[^openssl35] | SLH-DSA-SHA2-192f | 2.16.840.1.101.3.4.3.23 | -| `'slh-dsa-sha2-192s'`[^openssl35] | SLH-DSA-SHA2-192s | 2.16.840.1.101.3.4.3.22 | -| `'slh-dsa-sha2-256f'`[^openssl35] | SLH-DSA-SHA2-256f | 2.16.840.1.101.3.4.3.25 | -| `'slh-dsa-sha2-256s'`[^openssl35] | SLH-DSA-SHA2-256s | 2.16.840.1.101.3.4.3.24 | -| `'slh-dsa-shake-128f'`[^openssl35] | SLH-DSA-SHAKE-128f | 2.16.840.1.101.3.4.3.27 | -| `'slh-dsa-shake-128s'`[^openssl35] | SLH-DSA-SHAKE-128s | 2.16.840.1.101.3.4.3.26 | -| `'slh-dsa-shake-192f'`[^openssl35] | SLH-DSA-SHAKE-192f | 2.16.840.1.101.3.4.3.29 | -| `'slh-dsa-shake-192s'`[^openssl35] | SLH-DSA-SHAKE-192s | 2.16.840.1.101.3.4.3.28 | -| `'slh-dsa-shake-256f'`[^openssl35] | SLH-DSA-SHAKE-256f | 2.16.840.1.101.3.4.3.31 | -| `'slh-dsa-shake-256s'`[^openssl35] | SLH-DSA-SHAKE-256s | 2.16.840.1.101.3.4.3.30 | -| `'x25519'` | X25519 | 1.3.101.110 | -| `'x448'` | X448 | 1.3.101.111 | +The following table lists the asymmetric key types recognized by the +[`KeyObject`][] API and the export/import formats supported for each key type. + +| Key Type | Description | OID | `'pem'` | `'der'` | `'jwk'` | `'raw-public'` | `'raw-private'` | `'raw-seed'` | +| ---------------------------------- | ------------------ | ----------------------- | ------- | ------- | ------- | -------------- | --------------- | ------------ | +| `'dh'` | Diffie-Hellman | 1.2.840.113549.1.3.1 | ✔ | ✔ | | | | | +| `'dsa'` | DSA | 1.2.840.10040.4.1 | ✔ | ✔ | | | | | +| `'ec'` | Elliptic curve | 1.2.840.10045.2.1 | ✔ | ✔ | ✔ | ✔ | ✔ | | +| `'ed25519'` | Ed25519 | 1.3.101.112 | ✔ | ✔ | ✔ | ✔ | ✔ | | +| `'ed448'` | Ed448 | 1.3.101.113 | ✔ | ✔ | ✔ | ✔ | ✔ | | +| `'ml-dsa-44'`[^openssl35] | ML-DSA-44 | 2.16.840.1.101.3.4.3.17 | ✔ | ✔ | ✔ | ✔ | | ✔ | +| `'ml-dsa-65'`[^openssl35] | ML-DSA-65 | 2.16.840.1.101.3.4.3.18 | ✔ | ✔ | ✔ | ✔ | | ✔ | +| `'ml-dsa-87'`[^openssl35] | ML-DSA-87 | 2.16.840.1.101.3.4.3.19 | ✔ | ✔ | ✔ | ✔ | | ✔ | +| `'ml-kem-512'`[^openssl35] | ML-KEM-512 | 2.16.840.1.101.3.4.4.1 | ✔ | ✔ | | ✔ | | ✔ | +| `'ml-kem-768'`[^openssl35] | ML-KEM-768 | 2.16.840.1.101.3.4.4.2 | ✔ | ✔ | | ✔ | | ✔ | +| `'ml-kem-1024'`[^openssl35] | ML-KEM-1024 | 2.16.840.1.101.3.4.4.3 | ✔ | ✔ | | ✔ | | ✔ | +| `'rsa-pss'` | RSA PSS | 1.2.840.113549.1.1.10 | ✔ | ✔ | | | | | +| `'rsa'` | RSA | 1.2.840.113549.1.1.1 | ✔ | ✔ | ✔ | | | | +| `'slh-dsa-sha2-128f'`[^openssl35] | SLH-DSA-SHA2-128f | 2.16.840.1.101.3.4.3.21 | ✔ | ✔ | | ✔ | ✔ | | +| `'slh-dsa-sha2-128s'`[^openssl35] | SLH-DSA-SHA2-128s | 2.16.840.1.101.3.4.3.20 | ✔ | ✔ | | ✔ | ✔ | | +| `'slh-dsa-sha2-192f'`[^openssl35] | SLH-DSA-SHA2-192f | 2.16.840.1.101.3.4.3.23 | ✔ | ✔ | | ✔ | ✔ | | +| `'slh-dsa-sha2-192s'`[^openssl35] | SLH-DSA-SHA2-192s | 2.16.840.1.101.3.4.3.22 | ✔ | ✔ | | ✔ | ✔ | | +| `'slh-dsa-sha2-256f'`[^openssl35] | SLH-DSA-SHA2-256f | 2.16.840.1.101.3.4.3.25 | ✔ | ✔ | | ✔ | ✔ | | +| `'slh-dsa-sha2-256s'`[^openssl35] | SLH-DSA-SHA2-256s | 2.16.840.1.101.3.4.3.24 | ✔ | ✔ | | ✔ | ✔ | | +| `'slh-dsa-shake-128f'`[^openssl35] | SLH-DSA-SHAKE-128f | 2.16.840.1.101.3.4.3.27 | ✔ | ✔ | | ✔ | ✔ | | +| `'slh-dsa-shake-128s'`[^openssl35] | SLH-DSA-SHAKE-128s | 2.16.840.1.101.3.4.3.26 | ✔ | ✔ | | ✔ | ✔ | | +| `'slh-dsa-shake-192f'`[^openssl35] | SLH-DSA-SHAKE-192f | 2.16.840.1.101.3.4.3.29 | ✔ | ✔ | | ✔ | ✔ | | +| `'slh-dsa-shake-192s'`[^openssl35] | SLH-DSA-SHAKE-192s | 2.16.840.1.101.3.4.3.28 | ✔ | ✔ | | ✔ | ✔ | | +| `'slh-dsa-shake-256f'`[^openssl35] | SLH-DSA-SHAKE-256f | 2.16.840.1.101.3.4.3.31 | ✔ | ✔ | | ✔ | ✔ | | +| `'slh-dsa-shake-256s'`[^openssl35] | SLH-DSA-SHAKE-256s | 2.16.840.1.101.3.4.3.30 | ✔ | ✔ | | ✔ | ✔ | | +| `'x25519'` | X25519 | 1.3.101.110 | ✔ | ✔ | ✔ | ✔ | ✔ | | +| `'x448'` | X448 | 1.3.101.111 | ✔ | ✔ | ✔ | ✔ | ✔ | | + +### Key formats + +Asymmetric keys can be represented in several formats. **The recommended +approach is to import key material into a [`KeyObject`][] once and reuse it** +for all subsequent operations, as this avoids repeated parsing and delivers +the best performance. + +When a [`KeyObject`][] is not practical - for example, when key material +arrives in a protocol message and is used only once - most cryptographic +functions also accept a PEM string or an object specifying the format +and key material directly. See [`crypto.createPublicKey()`][], +[`crypto.createPrivateKey()`][], and [`keyObject.export()`][] for the full +options accepted by each format. + +#### KeyObject + +A [`KeyObject`][] is the in-memory representation of a parsed key. It is +created by [`crypto.createPublicKey()`][], [`crypto.createPrivateKey()`][], +[`crypto.createSecretKey()`][], or key generation functions such as +[`crypto.generateKeyPair()`][]. The first cryptographic operation with a given +[`KeyObject`][] may be slower than subsequent ones because OpenSSL lazily +initializes internal caches on first use. + +#### PEM and DER + +PEM and DER are the traditional encoding formats for asymmetric keys based on +ASN.1 structures. + +* **PEM** is a text encoding that wraps Base64-encoded DER data between + header and footer lines (e.g. `-----BEGIN PUBLIC KEY-----`). PEM strings can + be passed directly to most cryptographic operations. +* **DER** is the binary encoding of the same ASN.1 structures. When providing + DER input, the `type` (typically `'spki'` or `'pkcs8'`) must be specified + explicitly. + +#### JSON Web Key (JWK) + +JSON Web Key (JWK) is a JSON-based key representation defined in +[RFC 7517][]. JWK encodes each key component as an individual Base64url-encoded +value inside a JSON object. For RSA keys, JWK avoids ASN.1 parsing overhead +and is the fastest serialized import format. + +#### Raw key formats + +> Stability: 1.1 - Active development + +The `'raw-public'`, `'raw-private'`, and `'raw-seed'` key formats allow +importing and exporting raw key material without any encoding wrapper. +See [`keyObject.export()`][], [`crypto.createPublicKey()`][], and +[`crypto.createPrivateKey()`][] for usage details. + +`'raw-public'` is generally the fastest way to import a public key. +`'raw-private'` and `'raw-seed'` are not always faster than other formats +because they only contain the private scalar or seed - importing them requires +deriving the public key component (e.g. elliptic curve point multiplication or +seed expansion), which can be expensive. Other formats include both private +and public components, avoiding that computation. + +### Choosing a key format + +**Always prefer a [`KeyObject`][]** - create one from whatever format you +have and reuse it. The guidance below applies only when choosing between +serialization formats, either for importing into a [`KeyObject`][] or for +passing key material inline when a [`KeyObject`][] is not practical. + +#### Importing keys + +When creating a [`KeyObject`][] for repeated use, the import cost is paid once, +so choosing a faster format reduces startup latency. + +The import cost breaks down into two parts: **parsing overhead** (decoding the +serialization wrapper) and **key computation** (any mathematical work needed to +reconstruct the full key, such as deriving a public key from a private scalar +or expanding a seed). Which part dominates depends on the key type. For +example: + +* Public keys - `'raw-public'` is the fastest serialized format because the + raw format skips all ASN.1 and Base64 decoding. +* EC private keys - `'raw-private'` is faster than PEM or DER because it + avoids ASN.1 parsing. However, for larger curves (e.g. P-384, P-521) the + required derivation of the public point from the private scalar becomes + expensive, reducing the advantage. +* RSA keys - `'jwk'` is the fastest serialized format. JWK represents RSA + key components as individual Base64url-encoded integers, avoiding the + overhead of ASN.1 parsing entirely. + +#### Inline key material in operations + +When a [`KeyObject`][] cannot be reused (e.g. the key arrives as raw bytes in +a protocol message and is used only once), most cryptographic functions also +accept a PEM string or an object specifying the format and key +material directly. In this case the total cost is the sum of key import and +the cryptographic computation itself. + +For operations where the cryptographic computation dominates - such as +signing with RSA or ECDH key agreement with P-384 or P-521 - the +serialization format has negligible impact on overall throughput, so choose +whichever format is most convenient. For lightweight operations like Ed25519 +signing or verification, the import cost is a larger fraction of the total, +so a faster format like `'raw-public'` or `'raw-private'` can meaningfully +improve throughput. + +Even if the same key material is used only a few times, it is worth importing it +into a [`KeyObject`][] rather than passing the raw or PEM representation +repeatedly. + +### Examples + +Example: Reusing a [`KeyObject`][] across sign and verify operations: + +```mjs +import { promisify } from 'node:util'; +const { generateKeyPair, sign, verify } = await import('node:crypto'); + +const { publicKey, privateKey } = await promisify(generateKeyPair)('ed25519'); + +// A KeyObject holds the parsed key in memory and can be reused +// across multiple operations without re-parsing. +const data = new TextEncoder().encode('message to sign'); +const signature = sign(null, data, privateKey); +verify(null, data, publicKey, signature); +``` + +Example: Importing keys of various formats into [`KeyObject`][]s: + +```mjs +import { promisify } from 'node:util'; +const { + createPrivateKey, createPublicKey, generateKeyPair, +} = await import('node:crypto'); + +const generated = await promisify(generateKeyPair)('ed25519'); + +// PEM +const privatePem = generated.privateKey.export({ format: 'pem', type: 'pkcs8' }); +const publicPem = generated.publicKey.export({ format: 'pem', type: 'spki' }); +createPrivateKey(privatePem); +createPublicKey(publicPem); + +// DER - requires explicit type +const privateDer = generated.privateKey.export({ format: 'der', type: 'pkcs8' }); +const publicDer = generated.publicKey.export({ format: 'der', type: 'spki' }); +createPrivateKey({ key: privateDer, format: 'der', type: 'pkcs8' }); +createPublicKey({ key: publicDer, format: 'der', type: 'spki' }); + +// JWK +const privateJwk = generated.privateKey.export({ format: 'jwk' }); +const publicJwk = generated.publicKey.export({ format: 'jwk' }); +createPrivateKey({ key: privateJwk, format: 'jwk' }); +createPublicKey({ key: publicJwk, format: 'jwk' }); + +// Raw +const rawPriv = generated.privateKey.export({ format: 'raw-private' }); +const rawPub = generated.publicKey.export({ format: 'raw-public' }); +createPrivateKey({ key: rawPriv, format: 'raw-private', asymmetricKeyType: 'ed25519' }); +createPublicKey({ key: rawPub, format: 'raw-public', asymmetricKeyType: 'ed25519' }); +``` + +Example: Passing key material directly to [`crypto.sign()`][] and +[`crypto.verify()`][] without creating a [`KeyObject`][] first: + +```mjs +import { promisify } from 'node:util'; +const { generateKeyPair, sign, verify } = await import('node:crypto'); + +const generated = await promisify(generateKeyPair)('ed25519'); + +const data = new TextEncoder().encode('message to sign'); + +// PEM strings +const privatePem = generated.privateKey.export({ format: 'pem', type: 'pkcs8' }); +const publicPem = generated.publicKey.export({ format: 'pem', type: 'spki' }); +const sig1 = sign(null, data, privatePem); +verify(null, data, publicPem, sig1); + +// JWK objects +const privateJwk = generated.privateKey.export({ format: 'jwk' }); +const publicJwk = generated.publicKey.export({ format: 'jwk' }); +const sig2 = sign(null, data, { key: privateJwk, format: 'jwk' }); +verify(null, data, { key: publicJwk, format: 'jwk' }, sig2); + +// Raw key bytes +const rawPriv = generated.privateKey.export({ format: 'raw-private' }); +const rawPub = generated.publicKey.export({ format: 'raw-public' }); +const sig3 = sign(null, data, { + key: rawPriv, format: 'raw-private', asymmetricKeyType: 'ed25519', +}); +verify(null, data, { + key: rawPub, format: 'raw-public', asymmetricKeyType: 'ed25519', +}, sig3); +``` + +Example: For EC keys, the `namedCurve` option is required when importing +raw keys: + +```mjs +import { promisify } from 'node:util'; +const { + createPrivateKey, createPublicKey, generateKeyPair, sign, verify, +} = await import('node:crypto'); + +const generated = await promisify(generateKeyPair)('ec', { + namedCurve: 'P-256', +}); + +// Export the raw EC public key (uncompressed by default). +const rawPublicKey = generated.publicKey.export({ format: 'raw-public' }); + +// The following is equivalent. +const rawPublicKeyUncompressed = generated.publicKey.export({ + format: 'raw-public', + type: 'uncompressed', +}); + +// Export compressed point format. +const rawPublicKeyCompressed = generated.publicKey.export({ + format: 'raw-public', + type: 'compressed', +}); + +// Export the raw EC private key. +const rawPrivateKey = generated.privateKey.export({ format: 'raw-private' }); + +// Import the raw EC keys. +// Both compressed and uncompressed point formats are accepted. +const publicKey = createPublicKey({ + key: rawPublicKey, + format: 'raw-public', + asymmetricKeyType: 'ec', + namedCurve: 'P-256', +}); +const privateKey = createPrivateKey({ + key: rawPrivateKey, + format: 'raw-private', + asymmetricKeyType: 'ec', + namedCurve: 'P-256', +}); + +const data = new TextEncoder().encode('message to sign'); +const signature = sign('sha256', data, privateKey); +verify('sha256', data, publicKey, signature); +``` + +Example: Exporting raw seeds and importing them: + +```mjs +import { promisify } from 'node:util'; +const { + createPrivateKey, decapsulate, encapsulate, generateKeyPair, +} = await import('node:crypto'); + +const generated = await promisify(generateKeyPair)('ml-kem-768'); + +// Export the raw seed (64 bytes for ML-KEM). +const seed = generated.privateKey.export({ format: 'raw-seed' }); + +// Import the raw seed. +const privateKey = createPrivateKey({ + key: seed, + format: 'raw-seed', + asymmetricKeyType: 'ml-kem-768', +}); + +const { ciphertext } = encapsulate(generated.publicKey); +decapsulate(privateKey, ciphertext); +``` ## Class: `Certificate` @@ -2121,6 +2389,10 @@ type, value, and parameters. This method is not @@ -3673,6 +3957,9 @@ of the passphrase is limited to 1024 bytes. @@ -6539,6 +6831,7 @@ See the [list of SSL OP Flags][] for details. [RFC 4122]: https://www.rfc-editor.org/rfc/rfc4122.txt [RFC 5208]: https://www.rfc-editor.org/rfc/rfc5208.txt [RFC 5280]: https://www.rfc-editor.org/rfc/rfc5280.txt +[RFC 7517]: https://www.rfc-editor.org/rfc/rfc7517.txt [Web Crypto API documentation]: webcrypto.md [`BN_is_prime_ex`]: https://www.openssl.org/docs/man1.1.1/man3/BN_is_prime_ex.html [`Buffer`]: buffer.md @@ -6563,6 +6856,7 @@ See the [list of SSL OP Flags][] for details. [`crypto.createSign()`]: #cryptocreatesignalgorithm-options [`crypto.createVerify()`]: #cryptocreateverifyalgorithm-options [`crypto.generateKey()`]: #cryptogeneratekeytype-options-callback +[`crypto.generateKeyPair()`]: #cryptogeneratekeypairtype-options-callback [`crypto.getCurves()`]: #cryptogetcurves [`crypto.getDiffieHellman()`]: #cryptogetdiffiehellmangroupname [`crypto.getHashes()`]: #cryptogethashes @@ -6572,6 +6866,8 @@ See the [list of SSL OP Flags][] for details. [`crypto.publicEncrypt()`]: #cryptopublicencryptkey-buffer [`crypto.randomBytes()`]: #cryptorandombytessize-callback [`crypto.randomFill()`]: #cryptorandomfillbuffer-offset-size-callback +[`crypto.sign()`]: #cryptosignalgorithm-data-key-callback +[`crypto.verify()`]: #cryptoverifyalgorithm-data-key-signature-callback [`crypto.webcrypto.getRandomValues()`]: webcrypto.md#cryptogetrandomvaluestypedarray [`crypto.webcrypto.subtle`]: webcrypto.md#class-subtlecrypto [`decipher.final()`]: #decipherfinaloutputencoding diff --git a/lib/internal/crypto/cfrg.js b/lib/internal/crypto/cfrg.js index fd3f168435ddcb..b943ba020750f9 100644 --- a/lib/internal/crypto/cfrg.js +++ b/lib/internal/crypto/cfrg.js @@ -3,19 +3,23 @@ const { SafeSet, StringPrototypeToLowerCase, + TypedArrayPrototypeGetBuffer, } = primordials; const { Buffer } = require('buffer'); const { - ECKeyExportJob, KeyObjectHandle, SignJob, kCryptoJobAsync, + kKeyFormatDER, kKeyTypePrivate, kKeyTypePublic, kSignJobModeSign, kSignJobModeVerify, + kWebCryptoKeyFormatPKCS8, + kWebCryptoKeyFormatRaw, + kWebCryptoKeyFormatSPKI, } = internalBinding('crypto'); const { @@ -196,10 +200,29 @@ async function cfrgGenerateKey(algorithm, extractable, keyUsages) { } function cfrgExportKey(key, format) { - return jobPromise(() => new ECKeyExportJob( - kCryptoJobAsync, - format, - key[kKeyObject][kHandle])); + try { + switch (format) { + case kWebCryptoKeyFormatRaw: { + const handle = key[kKeyObject][kHandle]; + return TypedArrayPrototypeGetBuffer( + key[kKeyType] === 'private' ? handle.rawPrivateKey() : handle.rawPublicKey()); + } + case kWebCryptoKeyFormatSPKI: { + return TypedArrayPrototypeGetBuffer( + key[kKeyObject][kHandle].export(kKeyFormatDER, kWebCryptoKeyFormatSPKI)); + } + case kWebCryptoKeyFormatPKCS8: { + return TypedArrayPrototypeGetBuffer( + key[kKeyObject][kHandle].export(kKeyFormatDER, kWebCryptoKeyFormatPKCS8, null, null)); + } + default: + return undefined; + } + } catch (err) { + throw lazyDOMException( + 'The operation failed for an operation-specific reason', + { name: 'OperationError', cause: err }); + } } function cfrgImportKey( diff --git a/lib/internal/crypto/ec.js b/lib/internal/crypto/ec.js index dd7997c82cbf91..a73b1c6c0bc4bf 100644 --- a/lib/internal/crypto/ec.js +++ b/lib/internal/crypto/ec.js @@ -2,19 +2,30 @@ const { SafeSet, + TypedArrayPrototypeGetBuffer, + TypedArrayPrototypeGetByteLength, } = primordials; const { - ECKeyExportJob, KeyObjectHandle, SignJob, kCryptoJobAsync, + kKeyFormatDER, kKeyTypePrivate, kSignJobModeSign, kSignJobModeVerify, kSigEncP1363, + kWebCryptoKeyFormatPKCS8, + kWebCryptoKeyFormatRaw, + kWebCryptoKeyFormatSPKI, } = internalBinding('crypto'); +const { + crypto: { + POINT_CONVERSION_UNCOMPRESSED, + }, +} = internalBinding('constants'); + const { getUsagesUnion, hasAnyNotIn, @@ -41,6 +52,7 @@ const { PublicKeyObject, createPrivateKey, createPublicKey, + kAlgorithm, kKeyType, } = require('internal/crypto/keys'); @@ -139,10 +151,45 @@ async function ecGenerateKey(algorithm, extractable, keyUsages) { } function ecExportKey(key, format) { - return jobPromise(() => new ECKeyExportJob( - kCryptoJobAsync, - format, - key[kKeyObject][kHandle])); + try { + const handle = key[kKeyObject][kHandle]; + switch (format) { + case kWebCryptoKeyFormatRaw: { + return TypedArrayPrototypeGetBuffer( + handle.exportECPublicRaw(POINT_CONVERSION_UNCOMPRESSED)); + } + case kWebCryptoKeyFormatSPKI: { + let spki = handle.export(kKeyFormatDER, kWebCryptoKeyFormatSPKI); + // WebCrypto requires uncompressed point format for SPKI exports. + // This is a very rare edge case dependent on the imported key + // using compressed point format. + // Expected SPKI DER byte lengths with uncompressed points: + // P-256: 91 = 26 bytes of SPKI ASN.1 + 65-byte uncompressed point. + // P-384: 120 = 23 bytes of SPKI ASN.1 + 97-byte uncompressed point. + // P-521: 158 = 25 bytes of SPKI ASN.1 + 133-byte uncompressed point. + // Difference in initial SPKI ASN.1 is caused by OIDs and length encoding. + if (TypedArrayPrototypeGetByteLength(spki) !== { + '__proto__': null, 'P-256': 91, 'P-384': 120, 'P-521': 158, + }[key[kAlgorithm].namedCurve]) { + const raw = handle.exportECPublicRaw(POINT_CONVERSION_UNCOMPRESSED); + const tmp = new KeyObjectHandle(); + tmp.initECRaw(kNamedCurveAliases[key[kAlgorithm].namedCurve], raw); + spki = tmp.export(kKeyFormatDER, kWebCryptoKeyFormatSPKI); + } + return TypedArrayPrototypeGetBuffer(spki); + } + case kWebCryptoKeyFormatPKCS8: { + return TypedArrayPrototypeGetBuffer( + handle.export(kKeyFormatDER, kWebCryptoKeyFormatPKCS8, null, null)); + } + default: + return undefined; + } + } catch (err) { + throw lazyDOMException( + 'The operation failed for an operation-specific reason', + { name: 'OperationError', cause: err }); + } } function ecImportKey( diff --git a/lib/internal/crypto/keys.js b/lib/internal/crypto/keys.js index a3609690adeb36..80c073a1dbfac1 100644 --- a/lib/internal/crypto/keys.js +++ b/lib/internal/crypto/keys.js @@ -27,6 +27,13 @@ const { kKeyEncodingSEC1, } = internalBinding('crypto'); +const { + crypto: { + POINT_CONVERSION_COMPRESSED, + POINT_CONVERSION_UNCOMPRESSED, + }, +} = internalBinding('constants'); + const { validateObject, validateOneOf, @@ -82,6 +89,7 @@ const kKeyUsages = Symbol('kKeyUsages'); const kCachedAlgorithm = Symbol('kCachedAlgorithm'); const kCachedKeyUsages = Symbol('kCachedKeyUsages'); + // Key input contexts. const kConsumePublic = 0; const kConsumePrivate = 1; @@ -340,14 +348,27 @@ const { } export(options) { - if (options && options.format === 'jwk') { - return this[kHandle].exportJwk({}, false); + switch (options?.format) { + case 'jwk': + return this[kHandle].exportJwk({}, false); + case 'raw-public': { + if (this.asymmetricKeyType === 'ec') { + const { type = 'uncompressed' } = options; + validateOneOf(type, 'options.type', ['compressed', 'uncompressed']); + const form = type === 'compressed' ? + POINT_CONVERSION_COMPRESSED : POINT_CONVERSION_UNCOMPRESSED; + return this[kHandle].exportECPublicRaw(form); + } + return this[kHandle].rawPublicKey(); + } + default: { + const { + format, + type, + } = parsePublicKeyEncoding(options, this.asymmetricKeyType); + return this[kHandle].export(format, type); + } } - const { - format, - type, - } = parsePublicKeyEncoding(options, this.asymmetricKeyType); - return this[kHandle].export(format, type); } } @@ -357,20 +378,32 @@ const { } export(options) { - if (options && options.format === 'jwk') { - if (options.passphrase !== undefined) { - throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS( - 'jwk', 'does not support encryption'); + if (options?.passphrase !== undefined && + options.format !== 'pem' && options.format !== 'der') { + throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS( + options.format, 'does not support encryption'); + } + switch (options?.format) { + case 'jwk': + return this[kHandle].exportJwk({}, false); + case 'raw-private': { + if (this.asymmetricKeyType === 'ec') { + return this[kHandle].exportECPrivateRaw(); + } + return this[kHandle].rawPrivateKey(); + } + case 'raw-seed': + return this[kHandle].rawSeed(); + default: { + const { + format, + type, + cipher, + passphrase, + } = parsePrivateKeyEncoding(options, this.asymmetricKeyType); + return this[kHandle].export(format, type, cipher, passphrase); } - return this[kHandle].exportJwk({}, false); } - const { - format, - type, - cipher, - passphrase, - } = parsePrivateKeyEncoding(options, this.asymmetricKeyType); - return this[kHandle].export(format, type, cipher, passphrase); } } @@ -549,13 +582,8 @@ function mlDsaPubLen(alg) { function getKeyObjectHandleFromJwk(key, ctx) { validateObject(key, 'key'); - if (KeyObjectHandle.prototype.initPqcRaw) { - validateOneOf( - key.kty, 'key.kty', ['RSA', 'EC', 'OKP', 'AKP']); - } else { - validateOneOf( - key.kty, 'key.kty', ['RSA', 'EC', 'OKP']); - } + validateOneOf( + key.kty, 'key.kty', ['RSA', 'EC', 'OKP', 'AKP']); const isPublic = ctx === kConsumePublic || ctx === kCreatePublic; if (key.kty === 'AKP') { @@ -691,6 +719,79 @@ function getKeyObjectHandleFromJwk(key, ctx) { return handle; } + +function getKeyObjectHandleFromRaw(options, data, format) { + if (!isStringOrBuffer(data)) { + throw new ERR_INVALID_ARG_TYPE( + 'key.key', + ['ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'], + data); + } + + const keyData = getArrayBufferOrView(data, 'key.key'); + + validateString(options.asymmetricKeyType, 'key.asymmetricKeyType'); + const asymmetricKeyType = options.asymmetricKeyType; + + const handle = new KeyObjectHandle(); + + switch (asymmetricKeyType) { + case 'ec': { + validateString(options.namedCurve, 'key.namedCurve'); + if (format === 'raw-public') { + if (!handle.initECRaw(options.namedCurve, keyData)) { + throw new ERR_INVALID_ARG_VALUE('key.key', keyData); + } + } else if (!handle.initECPrivateRaw(options.namedCurve, keyData)) { + throw new ERR_INVALID_ARG_VALUE('key.key', keyData); + } + return handle; + } + case 'ed25519': + case 'ed448': + case 'x25519': + case 'x448': { + const keyType = format === 'raw-public' ? kKeyTypePublic : kKeyTypePrivate; + if (!handle.initEDRaw(asymmetricKeyType, keyData, keyType)) { + throw new ERR_INVALID_ARG_VALUE('key.key', keyData); + } + return handle; + } + case 'rsa': + case 'rsa-pss': + case 'dsa': + case 'dh': + throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS( + format, `is not supported for ${asymmetricKeyType} keys`); + case 'ml-dsa-44': + case 'ml-dsa-65': + case 'ml-dsa-87': + case 'ml-kem-512': + case 'ml-kem-768': + case 'ml-kem-1024': + case 'slh-dsa-sha2-128f': + case 'slh-dsa-sha2-128s': + case 'slh-dsa-sha2-192f': + case 'slh-dsa-sha2-192s': + case 'slh-dsa-sha2-256f': + case 'slh-dsa-sha2-256s': + case 'slh-dsa-shake-128f': + case 'slh-dsa-shake-128s': + case 'slh-dsa-shake-192f': + case 'slh-dsa-shake-192s': + case 'slh-dsa-shake-256f': + case 'slh-dsa-shake-256s': { + const keyType = format === 'raw-public' ? kKeyTypePublic : kKeyTypePrivate; + if (!handle.initPqcRaw(asymmetricKeyType, keyData, keyType)) { + throw new ERR_INVALID_ARG_VALUE('key.key', keyData); + } + return handle; + } + default: + throw new ERR_INVALID_ARG_VALUE('asymmetricKeyType', asymmetricKeyType); + } +} + function prepareAsymmetricKey(key, ctx) { if (isKeyObject(key)) { // Best case: A key object, as simple as that. @@ -712,6 +813,12 @@ function prepareAsymmetricKey(key, ctx) { else if (format === 'jwk') { validateObject(data, 'key.key'); return { data: getKeyObjectHandleFromJwk(data, ctx), format: 'jwk' }; + } else if (format === 'raw-public' || format === 'raw-private' || + format === 'raw-seed') { + return { + data: getKeyObjectHandleFromRaw(key, data, format), + format, + }; } // Either PEM or DER using PKCS#1 or SPKI. @@ -777,7 +884,7 @@ function createPublicKey(key) { const { format, type, data, passphrase } = prepareAsymmetricKey(key, kCreatePublic); let handle; - if (format === 'jwk') { + if (format === 'jwk' || format === 'raw-public') { handle = data; } else { handle = new KeyObjectHandle(); @@ -790,7 +897,7 @@ function createPrivateKey(key) { const { format, type, data, passphrase } = prepareAsymmetricKey(key, kCreatePrivate); let handle; - if (format === 'jwk') { + if (format === 'jwk' || format === 'raw-private' || format === 'raw-seed') { handle = data; } else { handle = new KeyObjectHandle(); diff --git a/lib/internal/crypto/ml_dsa.js b/lib/internal/crypto/ml_dsa.js index 11a7e8d843a0a7..4d90a80f0200f7 100644 --- a/lib/internal/crypto/ml_dsa.js +++ b/lib/internal/crypto/ml_dsa.js @@ -4,8 +4,7 @@ const { SafeSet, StringPrototypeToLowerCase, TypedArrayPrototypeGetBuffer, - TypedArrayPrototypeSet, - Uint8Array, + TypedArrayPrototypeGetByteLength, } = primordials; const { Buffer } = require('buffer'); @@ -54,7 +53,6 @@ const { PublicKeyObject, createPrivateKey, createPublicKey, - kAlgorithm, kKeyType, } = require('internal/crypto/keys'); @@ -124,31 +122,24 @@ function mlDsaExportKey(key, format) { try { switch (format) { case kWebCryptoKeyFormatRaw: { - if (key[kKeyType] === 'private') { - return TypedArrayPrototypeGetBuffer(key[kKeyObject][kHandle].rawSeed()); - } - - return TypedArrayPrototypeGetBuffer(key[kKeyObject][kHandle].rawPublicKey()); + const handle = key[kKeyObject][kHandle]; + return TypedArrayPrototypeGetBuffer( + key[kKeyType] === 'private' ? handle.rawSeed() : handle.rawPublicKey()); } case kWebCryptoKeyFormatSPKI: { return TypedArrayPrototypeGetBuffer(key[kKeyObject][kHandle].export(kKeyFormatDER, kWebCryptoKeyFormatSPKI)); } case kWebCryptoKeyFormatPKCS8: { - const seed = key[kKeyObject][kHandle].rawSeed(); - const buffer = new Uint8Array(54); - const orc = { - '__proto__': null, - 'ML-DSA-44': 0x11, - 'ML-DSA-65': 0x12, - 'ML-DSA-87': 0x13, - }[key[kAlgorithm].name]; - TypedArrayPrototypeSet(buffer, [ - 0x30, 0x34, 0x02, 0x01, 0x00, 0x30, 0x0b, 0x06, - 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, - 0x03, orc, 0x04, 0x22, 0x80, 0x20, - ], 0); - TypedArrayPrototypeSet(buffer, seed, 22); - return TypedArrayPrototypeGetBuffer(buffer); + const pkcs8 = key[kKeyObject][kHandle].export(kKeyFormatDER, kWebCryptoKeyFormatPKCS8, null, null); + // Edge case only possible when user creates a seedless KeyObject + // first and converts it with KeyObject.prototype.toCryptoKey. + // 54 = 22 bytes of PKCS#8 ASN.1 + 32-byte seed. + if (TypedArrayPrototypeGetByteLength(pkcs8) !== 54) { + throw lazyDOMException( + 'The operation failed for an operation-specific reason', + { name: 'OperationError' }); + } + return TypedArrayPrototypeGetBuffer(pkcs8); } default: return undefined; diff --git a/lib/internal/crypto/ml_kem.js b/lib/internal/crypto/ml_kem.js index 9cf44efc37f20a..0a82ca8453b8fe 100644 --- a/lib/internal/crypto/ml_kem.js +++ b/lib/internal/crypto/ml_kem.js @@ -5,8 +5,7 @@ const { SafeSet, StringPrototypeToLowerCase, TypedArrayPrototypeGetBuffer, - TypedArrayPrototypeSet, - Uint8Array, + TypedArrayPrototypeGetByteLength, } = primordials; const { @@ -44,7 +43,6 @@ const { PublicKeyObject, createPrivateKey, createPublicKey, - kAlgorithm, kKeyType, } = require('internal/crypto/keys'); @@ -95,31 +93,24 @@ function mlKemExportKey(key, format) { try { switch (format) { case kWebCryptoKeyFormatRaw: { - if (key[kKeyType] === 'private') { - return TypedArrayPrototypeGetBuffer(key[kKeyObject][kHandle].rawSeed()); - } - - return TypedArrayPrototypeGetBuffer(key[kKeyObject][kHandle].rawPublicKey()); + const handle = key[kKeyObject][kHandle]; + return TypedArrayPrototypeGetBuffer( + key[kKeyType] === 'private' ? handle.rawSeed() : handle.rawPublicKey()); } case kWebCryptoKeyFormatSPKI: { return TypedArrayPrototypeGetBuffer(key[kKeyObject][kHandle].export(kKeyFormatDER, kWebCryptoKeyFormatSPKI)); } case kWebCryptoKeyFormatPKCS8: { - const seed = key[kKeyObject][kHandle].rawSeed(); - const buffer = new Uint8Array(86); - const orc = { - '__proto__': null, - 'ML-KEM-512': 0x01, - 'ML-KEM-768': 0x02, - 'ML-KEM-1024': 0x03, - }[key[kAlgorithm].name]; - TypedArrayPrototypeSet(buffer, [ - 0x30, 0x54, 0x02, 0x01, 0x00, 0x30, 0x0b, 0x06, - 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, - 0x04, orc, 0x04, 0x42, 0x80, 0x40, - ], 0); - TypedArrayPrototypeSet(buffer, seed, 22); - return TypedArrayPrototypeGetBuffer(buffer); + const pkcs8 = key[kKeyObject][kHandle].export(kKeyFormatDER, kWebCryptoKeyFormatPKCS8, null, null); + // Edge case only possible when user creates a seedless KeyObject + // first and converts it with KeyObject.prototype.toCryptoKey. + // 86 = 22 bytes of PKCS#8 ASN.1 + 64-byte seed. + if (TypedArrayPrototypeGetByteLength(pkcs8) !== 86) { + throw lazyDOMException( + 'The operation failed for an operation-specific reason', + { name: 'OperationError' }); + } + return TypedArrayPrototypeGetBuffer(pkcs8); } default: return undefined; diff --git a/lib/internal/crypto/rsa.js b/lib/internal/crypto/rsa.js index c6b3985dbaee66..52c1257f48b0c6 100644 --- a/lib/internal/crypto/rsa.js +++ b/lib/internal/crypto/rsa.js @@ -3,22 +3,23 @@ const { MathCeil, SafeSet, + TypedArrayPrototypeGetBuffer, Uint8Array, } = primordials; const { KeyObjectHandle, RSACipherJob, - RSAKeyExportJob, SignJob, kCryptoJobAsync, + kKeyFormatDER, kSignJobModeSign, kSignJobModeVerify, - kKeyVariantRSA_SSA_PKCS1_v1_5, - kKeyVariantRSA_PSS, kKeyVariantRSA_OAEP, kKeyTypePrivate, kWebCryptoCipherEncrypt, + kWebCryptoKeyFormatPKCS8, + kWebCryptoKeyFormatSPKI, RSA_PKCS1_PSS_PADDING, } = internalBinding('crypto'); @@ -58,11 +59,6 @@ const { generateKeyPair: _generateKeyPair, } = require('internal/crypto/keygen'); -const kRsaVariants = { - 'RSASSA-PKCS1-v1_5': kKeyVariantRSA_SSA_PKCS1_v1_5, - 'RSA-PSS': kKeyVariantRSA_PSS, - 'RSA-OAEP': kKeyVariantRSA_OAEP, -}; const generateKeyPair = promisify(_generateKeyPair); function verifyAcceptableRsaKeyUse(name, isPublic, usages) { @@ -202,11 +198,24 @@ async function rsaKeyGenerate( } function rsaExportKey(key, format) { - return jobPromise(() => new RSAKeyExportJob( - kCryptoJobAsync, - format, - key[kKeyObject][kHandle], - kRsaVariants[key[kAlgorithm].name])); + try { + switch (format) { + case kWebCryptoKeyFormatSPKI: { + return TypedArrayPrototypeGetBuffer( + key[kKeyObject][kHandle].export(kKeyFormatDER, kWebCryptoKeyFormatSPKI)); + } + case kWebCryptoKeyFormatPKCS8: { + return TypedArrayPrototypeGetBuffer( + key[kKeyObject][kHandle].export(kKeyFormatDER, kWebCryptoKeyFormatPKCS8, null, null)); + } + default: + return undefined; + } + } catch (err) { + throw lazyDOMException( + 'The operation failed for an operation-specific reason', + { name: 'OperationError', cause: err }); + } } function rsaImportKey( diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js index dd36053f28b5a0..dc7f635a480a49 100644 --- a/lib/internal/crypto/webcrypto.js +++ b/lib/internal/crypto/webcrypto.js @@ -392,12 +392,12 @@ async function exportKeySpki(key) { case 'RSA-PSS': // Fall through case 'RSA-OAEP': - return await require('internal/crypto/rsa') + return require('internal/crypto/rsa') .rsaExportKey(key, kWebCryptoKeyFormatSPKI); case 'ECDSA': // Fall through case 'ECDH': - return await require('internal/crypto/ec') + return require('internal/crypto/ec') .ecExportKey(key, kWebCryptoKeyFormatSPKI); case 'Ed25519': // Fall through @@ -406,14 +406,13 @@ async function exportKeySpki(key) { case 'X25519': // Fall through case 'X448': - return await require('internal/crypto/cfrg') + return require('internal/crypto/cfrg') .cfrgExportKey(key, kWebCryptoKeyFormatSPKI); case 'ML-DSA-44': // Fall through case 'ML-DSA-65': // Fall through case 'ML-DSA-87': - // Note: mlDsaExportKey does not return a Promise. return require('internal/crypto/ml_dsa') .mlDsaExportKey(key, kWebCryptoKeyFormatSPKI); case 'ML-KEM-512': @@ -421,7 +420,6 @@ async function exportKeySpki(key) { case 'ML-KEM-768': // Fall through case 'ML-KEM-1024': - // Note: mlKemExportKey does not return a Promise. return require('internal/crypto/ml_kem') .mlKemExportKey(key, kWebCryptoKeyFormatSPKI); default: @@ -436,12 +434,12 @@ async function exportKeyPkcs8(key) { case 'RSA-PSS': // Fall through case 'RSA-OAEP': - return await require('internal/crypto/rsa') + return require('internal/crypto/rsa') .rsaExportKey(key, kWebCryptoKeyFormatPKCS8); case 'ECDSA': // Fall through case 'ECDH': - return await require('internal/crypto/ec') + return require('internal/crypto/ec') .ecExportKey(key, kWebCryptoKeyFormatPKCS8); case 'Ed25519': // Fall through @@ -450,14 +448,13 @@ async function exportKeyPkcs8(key) { case 'X25519': // Fall through case 'X448': - return await require('internal/crypto/cfrg') + return require('internal/crypto/cfrg') .cfrgExportKey(key, kWebCryptoKeyFormatPKCS8); case 'ML-DSA-44': // Fall through case 'ML-DSA-65': // Fall through case 'ML-DSA-87': - // Note: mlDsaExportKey does not return a Promise. return require('internal/crypto/ml_dsa') .mlDsaExportKey(key, kWebCryptoKeyFormatPKCS8); case 'ML-KEM-512': @@ -465,7 +462,6 @@ async function exportKeyPkcs8(key) { case 'ML-KEM-768': // Fall through case 'ML-KEM-1024': - // Note: mlKemExportKey does not return a Promise. return require('internal/crypto/ml_kem') .mlKemExportKey(key, kWebCryptoKeyFormatPKCS8); default: @@ -478,7 +474,7 @@ async function exportKeyRawPublic(key, format) { case 'ECDSA': // Fall through case 'ECDH': - return await require('internal/crypto/ec') + return require('internal/crypto/ec') .ecExportKey(key, kWebCryptoKeyFormatRaw); case 'Ed25519': // Fall through @@ -487,7 +483,7 @@ async function exportKeyRawPublic(key, format) { case 'X25519': // Fall through case 'X448': - return await require('internal/crypto/cfrg') + return require('internal/crypto/cfrg') .cfrgExportKey(key, kWebCryptoKeyFormatRaw); case 'ML-DSA-44': // Fall through @@ -498,7 +494,6 @@ async function exportKeyRawPublic(key, format) { if (format !== 'raw-public') { return undefined; } - // Note: mlDsaExportKey does not return a Promise. return require('internal/crypto/ml_dsa') .mlDsaExportKey(key, kWebCryptoKeyFormatRaw); } @@ -511,7 +506,6 @@ async function exportKeyRawPublic(key, format) { if (format !== 'raw-public') { return undefined; } - // Note: mlKemExportKey does not return a Promise. return require('internal/crypto/ml_kem') .mlKemExportKey(key, kWebCryptoKeyFormatRaw); } @@ -527,7 +521,6 @@ async function exportKeyRawSeed(key) { case 'ML-DSA-65': // Fall through case 'ML-DSA-87': - // Note: mlDsaExportKey does not return a Promise. return require('internal/crypto/ml_dsa') .mlDsaExportKey(key, kWebCryptoKeyFormatRaw); case 'ML-KEM-512': @@ -535,7 +528,6 @@ async function exportKeyRawSeed(key) { case 'ML-KEM-768': // Fall through case 'ML-KEM-1024': - // Note: mlKemExportKey does not return a Promise. return require('internal/crypto/ml_kem') .mlKemExportKey(key, kWebCryptoKeyFormatRaw); default: diff --git a/src/crypto/README.md b/src/crypto/README.md index 3f4400f595d356..0a2a5d6f285f60 100644 --- a/src/crypto/README.md +++ b/src/crypto/README.md @@ -180,14 +180,12 @@ The `CryptoJob` class itself is a C++ template that takes a single `CryptoJobTraits` struct as a parameter. The `CryptoJobTraits` provides the implementation detail of the job. -There are (currently) four basic `CryptoJob` specializations: +There are (currently) three basic `CryptoJob` specializations: * `CipherJob` (defined in `src/crypto_cipher.h`) -- Used for encrypt and decrypt operations. * `KeyGenJob` (defined in `src/crypto_keygen.h`) -- Used for secret and key pair generation operations. -* `KeyExportJob` (defined in `src/crypto_keys.h`) -- Used for - key export operations. * `DeriveBitsJob` (defined in `src/crypto_util.h`) -- Used for key and byte derivation operations. @@ -236,9 +234,6 @@ if successful. For `CipherJob` types, the output is always an `ArrayBuffer`. -For `KeyExportJob` types, the output is either an `ArrayBuffer` or -a JavaScript object (for JWK output format); - For `KeyGenJob` types, the output is either a single KeyObject, or an array containing a Public/Private key pair represented either as a `KeyObjectHandle` object or a `Buffer`. diff --git a/src/crypto/crypto_dh.cc b/src/crypto/crypto_dh.cc index e35fda9ad2e8c5..f2c43b796cf481 100644 --- a/src/crypto/crypto_dh.cc +++ b/src/crypto/crypto_dh.cc @@ -472,34 +472,6 @@ EVPKeyCtxPointer DhKeyGenTraits::Setup(DhKeyPairGenConfig* params) { return ctx; } -Maybe DHKeyExportTraits::AdditionalConfig( - const FunctionCallbackInfo& args, - unsigned int offset, - DHKeyExportConfig* params) { - return JustVoid(); -} - -WebCryptoKeyExportStatus DHKeyExportTraits::DoExport( - const KeyObjectData& key_data, - WebCryptoKeyFormat format, - const DHKeyExportConfig& params, - ByteSource* out) { - CHECK_NE(key_data.GetKeyType(), kKeyTypeSecret); - - switch (format) { - case kWebCryptoKeyFormatPKCS8: - if (key_data.GetKeyType() != kKeyTypePrivate) - return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; - return PKEY_PKCS8_Export(key_data, out); - case kWebCryptoKeyFormatSPKI: - if (key_data.GetKeyType() != kKeyTypePublic) - return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; - return PKEY_SPKI_Export(key_data, out); - default: - UNREACHABLE(); - } -} - Maybe DHBitsTraits::AdditionalConfig( CryptoJobMode mode, const FunctionCallbackInfo& args, @@ -600,7 +572,6 @@ void DiffieHellman::Initialize(Environment* env, Local target) { DiffieHellmanGroup); DHKeyPairGenJob::Initialize(env, target); - DHKeyExportJob::Initialize(env, target); DHBitsJob::Initialize(env, target); } @@ -621,7 +592,6 @@ void DiffieHellman::RegisterExternalReferences( registry->Register(Check); DHKeyPairGenJob::RegisterExternalReferences(registry); - DHKeyExportJob::RegisterExternalReferences(registry); DHBitsJob::RegisterExternalReferences(registry); } diff --git a/src/crypto/crypto_dh.h b/src/crypto/crypto_dh.h index 53e3a98c3918bd..72718ef757b16c 100644 --- a/src/crypto/crypto_dh.h +++ b/src/crypto/crypto_dh.h @@ -60,29 +60,6 @@ struct DhKeyGenTraits final { using DHKeyPairGenJob = KeyGenJob>; -struct DHKeyExportConfig final : public MemoryRetainer { - SET_NO_MEMORY_INFO() - SET_MEMORY_INFO_NAME(DHKeyExportConfig) - SET_SELF_SIZE(DHKeyExportConfig) -}; - -struct DHKeyExportTraits final { - static constexpr const char* JobName = "DHKeyExportJob"; - using AdditionalParameters = DHKeyExportConfig; - - static v8::Maybe AdditionalConfig( - const v8::FunctionCallbackInfo& args, - unsigned int offset, - DHKeyExportConfig* config); - - static WebCryptoKeyExportStatus DoExport(const KeyObjectData& key_data, - WebCryptoKeyFormat format, - const DHKeyExportConfig& params, - ByteSource* out); -}; - -using DHKeyExportJob = KeyExportJob; - struct DHBitsConfig final : public MemoryRetainer { KeyObjectData private_key; KeyObjectData public_key; diff --git a/src/crypto/crypto_ec.cc b/src/crypto/crypto_ec.cc index 95e1a68070ed67..dcf999fd3f3ca6 100644 --- a/src/crypto/crypto_ec.cc +++ b/src/crypto/crypto_ec.cc @@ -70,7 +70,6 @@ void ECDH::Initialize(Environment* env, Local target) { ECDHBitsJob::Initialize(env, target); ECKeyPairGenJob::Initialize(env, target); - ECKeyExportJob::Initialize(env, target); NODE_DEFINE_CONSTANT(target, OPENSSL_EC_NAMED_CURVE); NODE_DEFINE_CONSTANT(target, OPENSSL_EC_EXPLICIT_CURVE); @@ -89,7 +88,6 @@ void ECDH::RegisterExternalReferences(ExternalReferenceRegistry* registry) { ECDHBitsJob::RegisterExternalReferences(registry); ECKeyPairGenJob::RegisterExternalReferences(registry); - ECKeyExportJob::RegisterExternalReferences(registry); } void ECDH::GetCurves(const FunctionCallbackInfo& args) { @@ -559,137 +557,6 @@ Maybe EcKeyGenTraits::AdditionalConfig( return JustVoid(); } -namespace { -WebCryptoKeyExportStatus EC_Raw_Export(const KeyObjectData& key_data, - const ECKeyExportConfig& params, - ByteSource* out) { - const auto& m_pkey = key_data.GetAsymmetricKey(); - CHECK(m_pkey); - Mutex::ScopedLock lock(key_data.mutex()); - - const EC_KEY* ec_key = m_pkey; - - if (ec_key == nullptr) { - switch (key_data.GetKeyType()) { - case kKeyTypePrivate: { - auto data = m_pkey.rawPrivateKey(); - if (!data) return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; - DCHECK(!data.isSecure()); - *out = ByteSource::Allocated(data.release()); - break; - } - case kKeyTypePublic: { - auto data = m_pkey.rawPublicKey(); - if (!data) return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; - DCHECK(!data.isSecure()); - *out = ByteSource::Allocated(data.release()); - break; - } - case kKeyTypeSecret: - UNREACHABLE(); - } - } else { - if (key_data.GetKeyType() != kKeyTypePublic) - return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; - const auto group = ECKeyPointer::GetGroup(ec_key); - const auto point = ECKeyPointer::GetPublicKey(ec_key); - point_conversion_form_t form = POINT_CONVERSION_UNCOMPRESSED; - - // Get the allocated data size... - size_t len = EC_POINT_point2oct(group, point, form, nullptr, 0, nullptr); - if (len == 0) - return WebCryptoKeyExportStatus::FAILED; - auto data = DataPointer::Alloc(len); - size_t check_len = - EC_POINT_point2oct(group, - point, - form, - static_cast(data.get()), - len, - nullptr); - if (check_len == 0) - return WebCryptoKeyExportStatus::FAILED; - - CHECK_EQ(len, check_len); - *out = ByteSource::Allocated(data.release()); - } - - return WebCryptoKeyExportStatus::OK; -} -} // namespace - -Maybe ECKeyExportTraits::AdditionalConfig( - const FunctionCallbackInfo& args, - unsigned int offset, - ECKeyExportConfig* params) { - return JustVoid(); -} - -WebCryptoKeyExportStatus ECKeyExportTraits::DoExport( - const KeyObjectData& key_data, - WebCryptoKeyFormat format, - const ECKeyExportConfig& params, - ByteSource* out) { - CHECK_NE(key_data.GetKeyType(), kKeyTypeSecret); - - switch (format) { - case kWebCryptoKeyFormatRaw: - return EC_Raw_Export(key_data, params, out); - case kWebCryptoKeyFormatPKCS8: - if (key_data.GetKeyType() != kKeyTypePrivate) - return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; - return PKEY_PKCS8_Export(key_data, out); - case kWebCryptoKeyFormatSPKI: { - if (key_data.GetKeyType() != kKeyTypePublic) - return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; - - const auto& m_pkey = key_data.GetAsymmetricKey(); - if (m_pkey.id() != EVP_PKEY_EC) { - return PKEY_SPKI_Export(key_data, out); - } else { - // Ensure exported key is in uncompressed point format. - // The temporary EC key is so we can have i2d_PUBKEY_bio() write out - // the header but it is a somewhat silly hoop to jump through because - // the header is for all practical purposes a static 26 byte sequence - // where only the second byte changes. - Mutex::ScopedLock lock(key_data.mutex()); - const auto group = ECKeyPointer::GetGroup(m_pkey); - const auto point = ECKeyPointer::GetPublicKey(m_pkey); - const point_conversion_form_t form = POINT_CONVERSION_UNCOMPRESSED; - const size_t need = - EC_POINT_point2oct(group, point, form, nullptr, 0, nullptr); - if (need == 0) return WebCryptoKeyExportStatus::FAILED; - auto data = DataPointer::Alloc(need); - const size_t have = - EC_POINT_point2oct(group, - point, - form, - static_cast(data.get()), - need, - nullptr); - if (have == 0) return WebCryptoKeyExportStatus::FAILED; - auto ec = ECKeyPointer::New(group); - CHECK(ec); - auto uncompressed = ECPointPointer::New(group); - ncrypto::Buffer buffer{ - .data = static_cast(data.get()), - .len = data.size(), - }; - CHECK(uncompressed.setFromBuffer(buffer, group)); - CHECK(ec.setPublicKey(uncompressed)); - auto pkey = EVPKeyPointer::New(); - CHECK(pkey.set(ec)); - auto bio = pkey.derPublicKey(); - if (!bio) return WebCryptoKeyExportStatus::FAILED; - *out = ByteSource::FromBIO(bio); - return WebCryptoKeyExportStatus::OK; - } - } - default: - UNREACHABLE(); - } -} - bool ExportJWKEcKey(Environment* env, const KeyObjectData& key, Local target) { diff --git a/src/crypto/crypto_ec.h b/src/crypto/crypto_ec.h index 101d07e54e8e57..f7aaf2d523493a 100644 --- a/src/crypto/crypto_ec.h +++ b/src/crypto/crypto_ec.h @@ -114,32 +114,6 @@ struct EcKeyGenTraits final { using ECKeyPairGenJob = KeyGenJob>; -// There is currently no additional information that the -// ECKeyExport needs to collect, but we need to provide -// the base struct anyway. -struct ECKeyExportConfig final : public MemoryRetainer { - SET_NO_MEMORY_INFO() - SET_MEMORY_INFO_NAME(ECKeyExportConfig) - SET_SELF_SIZE(ECKeyExportConfig) -}; - -struct ECKeyExportTraits final { - static constexpr const char* JobName = "ECKeyExportJob"; - using AdditionalParameters = ECKeyExportConfig; - - static v8::Maybe AdditionalConfig( - const v8::FunctionCallbackInfo& args, - unsigned int offset, - ECKeyExportConfig* config); - - static WebCryptoKeyExportStatus DoExport(const KeyObjectData& key_data, - WebCryptoKeyFormat format, - const ECKeyExportConfig& params, - ByteSource* out); -}; - -using ECKeyExportJob = KeyExportJob; - bool ExportJWKEcKey(Environment* env, const KeyObjectData& key, v8::Local target); diff --git a/src/crypto/crypto_keys.cc b/src/crypto/crypto_keys.cc index 93a741fb1758a4..3fdbe9eacdf176 100644 --- a/src/crypto/crypto_keys.cc +++ b/src/crypto/crypto_keys.cc @@ -19,12 +19,13 @@ namespace node { +using ncrypto::BignumPointer; using ncrypto::BIOPointer; using ncrypto::ECKeyPointer; +using ncrypto::ECPointPointer; using ncrypto::EVPKeyCtxPointer; using ncrypto::EVPKeyPointer; using ncrypto::MarkPopErrorOnReturn; -using ncrypto::PKCS8Pointer; using v8::Array; using v8::Context; using v8::Function; @@ -278,33 +279,39 @@ bool ExportJWKInner(Environment* env, } int GetNidFromName(const char* name) { - int nid; - if (strcmp(name, "Ed25519") == 0) { - nid = EVP_PKEY_ED25519; - } else if (strcmp(name, "Ed448") == 0) { - nid = EVP_PKEY_ED448; - } else if (strcmp(name, "X25519") == 0) { - nid = EVP_PKEY_X25519; - } else if (strcmp(name, "X448") == 0) { - nid = EVP_PKEY_X448; + static constexpr struct { + const char* name; + int nid; + } kNameToNid[] = { + {"Ed25519", EVP_PKEY_ED25519}, + {"Ed448", EVP_PKEY_ED448}, + {"X25519", EVP_PKEY_X25519}, + {"X448", EVP_PKEY_X448}, #if OPENSSL_WITH_PQC - } else if (strcmp(name, "ML-DSA-44") == 0) { - nid = EVP_PKEY_ML_DSA_44; - } else if (strcmp(name, "ML-DSA-65") == 0) { - nid = EVP_PKEY_ML_DSA_65; - } else if (strcmp(name, "ML-DSA-87") == 0) { - nid = EVP_PKEY_ML_DSA_87; - } else if (strcmp(name, "ML-KEM-512") == 0) { - nid = EVP_PKEY_ML_KEM_512; - } else if (strcmp(name, "ML-KEM-768") == 0) { - nid = EVP_PKEY_ML_KEM_768; - } else if (strcmp(name, "ML-KEM-1024") == 0) { - nid = EVP_PKEY_ML_KEM_1024; + {"ML-DSA-44", EVP_PKEY_ML_DSA_44}, + {"ML-DSA-65", EVP_PKEY_ML_DSA_65}, + {"ML-DSA-87", EVP_PKEY_ML_DSA_87}, + {"ML-KEM-512", EVP_PKEY_ML_KEM_512}, + {"ML-KEM-768", EVP_PKEY_ML_KEM_768}, + {"ML-KEM-1024", EVP_PKEY_ML_KEM_1024}, + {"SLH-DSA-SHA2-128f", EVP_PKEY_SLH_DSA_SHA2_128F}, + {"SLH-DSA-SHA2-128s", EVP_PKEY_SLH_DSA_SHA2_128S}, + {"SLH-DSA-SHA2-192f", EVP_PKEY_SLH_DSA_SHA2_192F}, + {"SLH-DSA-SHA2-192s", EVP_PKEY_SLH_DSA_SHA2_192S}, + {"SLH-DSA-SHA2-256f", EVP_PKEY_SLH_DSA_SHA2_256F}, + {"SLH-DSA-SHA2-256s", EVP_PKEY_SLH_DSA_SHA2_256S}, + {"SLH-DSA-SHAKE-128f", EVP_PKEY_SLH_DSA_SHAKE_128F}, + {"SLH-DSA-SHAKE-128s", EVP_PKEY_SLH_DSA_SHAKE_128S}, + {"SLH-DSA-SHAKE-192f", EVP_PKEY_SLH_DSA_SHAKE_192F}, + {"SLH-DSA-SHAKE-192s", EVP_PKEY_SLH_DSA_SHAKE_192S}, + {"SLH-DSA-SHAKE-256f", EVP_PKEY_SLH_DSA_SHAKE_256F}, + {"SLH-DSA-SHAKE-256s", EVP_PKEY_SLH_DSA_SHAKE_256S}, #endif - } else { - nid = NID_undef; + }; + for (const auto& entry : kNameToNid) { + if (StringEqualNoCase(name, entry.name)) return entry.nid; } - return nid; + return NID_undef; } } // namespace @@ -633,11 +640,15 @@ Local KeyObjectHandle::Initialize(Environment* env) { SetProtoMethod(isolate, templ, "exportJwk", ExportJWK); SetProtoMethod(isolate, templ, "initECRaw", InitECRaw); SetProtoMethod(isolate, templ, "initEDRaw", InitEDRaw); -#if OPENSSL_WITH_PQC - SetProtoMethod(isolate, templ, "initPqcRaw", InitPqcRaw); SetProtoMethodNoSideEffect(isolate, templ, "rawPublicKey", RawPublicKey); + SetProtoMethodNoSideEffect(isolate, templ, "rawPrivateKey", RawPrivateKey); + SetProtoMethod(isolate, templ, "initPqcRaw", InitPqcRaw); SetProtoMethodNoSideEffect(isolate, templ, "rawSeed", RawSeed); -#endif + SetProtoMethod(isolate, templ, "initECPrivateRaw", InitECPrivateRaw); + SetProtoMethodNoSideEffect( + isolate, templ, "exportECPublicRaw", ExportECPublicRaw); + SetProtoMethodNoSideEffect( + isolate, templ, "exportECPrivateRaw", ExportECPrivateRaw); SetProtoMethod(isolate, templ, "initJwk", InitJWK); SetProtoMethod(isolate, templ, "keyDetail", GetKeyDetail); SetProtoMethod(isolate, templ, "equals", Equals); @@ -658,11 +669,13 @@ void KeyObjectHandle::RegisterExternalReferences( registry->Register(ExportJWK); registry->Register(InitECRaw); registry->Register(InitEDRaw); -#if OPENSSL_WITH_PQC - registry->Register(InitPqcRaw); registry->Register(RawPublicKey); + registry->Register(RawPrivateKey); + registry->Register(InitPqcRaw); registry->Register(RawSeed); -#endif + registry->Register(InitECPrivateRaw); + registry->Register(ExportECPublicRaw); + registry->Register(ExportECPrivateRaw); registry->Register(InitJWK); registry->Register(GetKeyDetail); registry->Register(Equals); @@ -787,7 +800,9 @@ void KeyObjectHandle::InitECRaw(const FunctionCallbackInfo& args) { MarkPopErrorOnReturn mark_pop_error_on_return; - int id = OBJ_txt2nid(*name); + int id = ncrypto::Ec::GetCurveIdFromName(*name); + if (id == NID_undef) return THROW_ERR_CRYPTO_INVALID_CURVE(env); + auto eckey = ECKeyPointer::NewByCurveName(id); if (!eckey) return args.GetReturnValue().Set(false); @@ -848,14 +863,14 @@ void KeyObjectHandle::InitEDRaw(const FunctionCallbackInfo& args) { break; } default: - UNREACHABLE(); + return args.GetReturnValue().Set(false); } args.GetReturnValue().Set(true); } -#if OPENSSL_WITH_PQC void KeyObjectHandle::InitPqcRaw(const FunctionCallbackInfo& args) { +#if OPENSSL_WITH_PQC KeyObjectHandle* key; ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); @@ -867,12 +882,11 @@ void KeyObjectHandle::InitPqcRaw(const FunctionCallbackInfo& args) { MarkPopErrorOnReturn mark_pop_error_on_return; + int id = GetNidFromName(*name); + typedef EVPKeyPointer (*new_key_fn)( int, const ncrypto::Buffer&); - new_key_fn fn = type == kKeyTypePrivate ? EVPKeyPointer::NewRawSeed - : EVPKeyPointer::NewRawPublic; - - int id = GetNidFromName(*name); + new_key_fn fn; switch (id) { case EVP_PKEY_ML_DSA_44: @@ -880,26 +894,46 @@ void KeyObjectHandle::InitPqcRaw(const FunctionCallbackInfo& args) { case EVP_PKEY_ML_DSA_87: case EVP_PKEY_ML_KEM_512: case EVP_PKEY_ML_KEM_768: - case EVP_PKEY_ML_KEM_1024: { - auto pkey = fn(id, - ncrypto::Buffer{ - .data = key_data.data(), - .len = key_data.size(), - }); - if (!pkey) { - return args.GetReturnValue().Set(false); - } - key->data_ = KeyObjectData::CreateAsymmetric(type, std::move(pkey)); - CHECK(key->data_); + case EVP_PKEY_ML_KEM_1024: + fn = type == kKeyTypePrivate ? EVPKeyPointer::NewRawSeed + : EVPKeyPointer::NewRawPublic; + break; + case EVP_PKEY_SLH_DSA_SHA2_128F: + case EVP_PKEY_SLH_DSA_SHA2_128S: + case EVP_PKEY_SLH_DSA_SHA2_192F: + case EVP_PKEY_SLH_DSA_SHA2_192S: + case EVP_PKEY_SLH_DSA_SHA2_256F: + case EVP_PKEY_SLH_DSA_SHA2_256S: + case EVP_PKEY_SLH_DSA_SHAKE_128F: + case EVP_PKEY_SLH_DSA_SHAKE_128S: + case EVP_PKEY_SLH_DSA_SHAKE_192F: + case EVP_PKEY_SLH_DSA_SHAKE_192S: + case EVP_PKEY_SLH_DSA_SHAKE_256F: + case EVP_PKEY_SLH_DSA_SHAKE_256S: + fn = type == kKeyTypePrivate ? EVPKeyPointer::NewRawPrivate + : EVPKeyPointer::NewRawPublic; break; - } default: - UNREACHABLE(); + return args.GetReturnValue().Set(false); } + auto pkey = fn(id, + ncrypto::Buffer{ + .data = key_data.data(), + .len = key_data.size(), + }); + if (!pkey) { + return args.GetReturnValue().Set(false); + } + key->data_ = KeyObjectData::CreateAsymmetric(type, std::move(pkey)); + CHECK(key->data_); + args.GetReturnValue().Set(true); -} +#else + Environment* env = Environment::GetCurrent(args); + THROW_ERR_INVALID_ARG_VALUE(env, "Unsupported key type"); #endif +} void KeyObjectHandle::Equals(const FunctionCallbackInfo& args) { KeyObjectHandle* self_handle; @@ -1125,7 +1159,6 @@ MaybeLocal KeyObjectHandle::ExportPrivateKey( return WritePrivateKey(env(), data_.GetAsymmetricKey(), config); } -#if OPENSSL_WITH_PQC void KeyObjectHandle::RawPublicKey( const v8::FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); @@ -1136,18 +1169,216 @@ void KeyObjectHandle::RawPublicKey( CHECK_NE(data.GetKeyType(), kKeyTypeSecret); Mutex::ScopedLock lock(data.mutex()); - auto raw_data = data.GetAsymmetricKey().rawPublicKey(); + const auto& pkey = data.GetAsymmetricKey(); + + switch (pkey.id()) { + case EVP_PKEY_ED25519: + case EVP_PKEY_ED448: + case EVP_PKEY_X25519: + case EVP_PKEY_X448: +#if OPENSSL_WITH_PQC + case EVP_PKEY_ML_DSA_44: + case EVP_PKEY_ML_DSA_65: + case EVP_PKEY_ML_DSA_87: + case EVP_PKEY_ML_KEM_512: + case EVP_PKEY_ML_KEM_768: + case EVP_PKEY_ML_KEM_1024: + case EVP_PKEY_SLH_DSA_SHA2_128F: + case EVP_PKEY_SLH_DSA_SHA2_128S: + case EVP_PKEY_SLH_DSA_SHA2_192F: + case EVP_PKEY_SLH_DSA_SHA2_192S: + case EVP_PKEY_SLH_DSA_SHA2_256F: + case EVP_PKEY_SLH_DSA_SHA2_256S: + case EVP_PKEY_SLH_DSA_SHAKE_128F: + case EVP_PKEY_SLH_DSA_SHAKE_128S: + case EVP_PKEY_SLH_DSA_SHAKE_192F: + case EVP_PKEY_SLH_DSA_SHAKE_192S: + case EVP_PKEY_SLH_DSA_SHAKE_256F: + case EVP_PKEY_SLH_DSA_SHAKE_256S: +#endif + break; + default: + return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); + } + + auto raw_data = pkey.rawPublicKey(); if (!raw_data) { return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get raw public key"); } args.GetReturnValue().Set( - Buffer::Copy( - env, reinterpret_cast(raw_data.get()), raw_data.size()) + Buffer::Copy(env, raw_data.get(), raw_data.size()) .FromMaybe(Local())); } +void KeyObjectHandle::RawPrivateKey( + const v8::FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + KeyObjectHandle* key; + ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); + + const KeyObjectData& data = key->Data(); + CHECK_EQ(data.GetKeyType(), kKeyTypePrivate); + + Mutex::ScopedLock lock(data.mutex()); + const auto& pkey = data.GetAsymmetricKey(); + + switch (pkey.id()) { + case EVP_PKEY_ED25519: + case EVP_PKEY_ED448: + case EVP_PKEY_X25519: + case EVP_PKEY_X448: +#if OPENSSL_WITH_PQC + case EVP_PKEY_SLH_DSA_SHA2_128F: + case EVP_PKEY_SLH_DSA_SHA2_128S: + case EVP_PKEY_SLH_DSA_SHA2_192F: + case EVP_PKEY_SLH_DSA_SHA2_192S: + case EVP_PKEY_SLH_DSA_SHA2_256F: + case EVP_PKEY_SLH_DSA_SHA2_256S: + case EVP_PKEY_SLH_DSA_SHAKE_128F: + case EVP_PKEY_SLH_DSA_SHAKE_128S: + case EVP_PKEY_SLH_DSA_SHAKE_192F: + case EVP_PKEY_SLH_DSA_SHAKE_192S: + case EVP_PKEY_SLH_DSA_SHAKE_256F: + case EVP_PKEY_SLH_DSA_SHAKE_256S: +#endif + break; + default: + return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); + } + + auto raw_data = pkey.rawPrivateKey(); + if (!raw_data) { + return THROW_ERR_CRYPTO_OPERATION_FAILED(env, + "Failed to get raw private key"); + } + + args.GetReturnValue().Set( + Buffer::Copy(env, raw_data.get(), raw_data.size()) + .FromMaybe(Local())); +} + +void KeyObjectHandle::ExportECPublicRaw( + const v8::FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + KeyObjectHandle* key; + ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); + + const KeyObjectData& data = key->Data(); + CHECK_NE(data.GetKeyType(), kKeyTypeSecret); + + Mutex::ScopedLock lock(data.mutex()); + const auto& m_pkey = data.GetAsymmetricKey(); + if (m_pkey.id() != EVP_PKEY_EC) { + return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); + } + + const EC_KEY* ec_key = m_pkey; + CHECK_NOT_NULL(ec_key); + + CHECK(args[0]->IsInt32()); + auto form = + static_cast(args[0].As()->Value()); + + const auto group = ECKeyPointer::GetGroup(ec_key); + const auto point = ECKeyPointer::GetPublicKey(ec_key); + + Local buf; + if (!ECPointToBuffer(env, group, point, form).ToLocal(&buf)) return; + + args.GetReturnValue().Set(buf); +} + +void KeyObjectHandle::ExportECPrivateRaw( + const v8::FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + KeyObjectHandle* key; + ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); + + const KeyObjectData& data = key->Data(); + CHECK_EQ(data.GetKeyType(), kKeyTypePrivate); + + Mutex::ScopedLock lock(data.mutex()); + const auto& m_pkey = data.GetAsymmetricKey(); + if (m_pkey.id() != EVP_PKEY_EC) { + return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); + } + + const EC_KEY* ec_key = m_pkey; + CHECK_NOT_NULL(ec_key); + + const BIGNUM* private_key = ECKeyPointer::GetPrivateKey(ec_key); + CHECK_NOT_NULL(private_key); + + const auto group = ECKeyPointer::GetGroup(ec_key); + auto order = BignumPointer::New(); + CHECK(order); + CHECK(EC_GROUP_get_order(group, order.get(), nullptr)); + + auto buf = BignumPointer::EncodePadded(private_key, order.byteLength()); + if (!buf) { + return THROW_ERR_CRYPTO_OPERATION_FAILED(env, + "Failed to export EC private key"); + } + + args.GetReturnValue().Set(Buffer::Copy(env, buf.get(), buf.size()) + .FromMaybe(Local())); +} + +void KeyObjectHandle::InitECPrivateRaw( + const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + KeyObjectHandle* key; + ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); + + CHECK(args[0]->IsString()); + Utf8Value name(env->isolate(), args[0]); + + ArrayBufferOrViewContents key_data(args[1]); + + MarkPopErrorOnReturn mark_pop_error_on_return; + + int nid = ncrypto::Ec::GetCurveIdFromName(*name); + if (nid == NID_undef) return THROW_ERR_CRYPTO_INVALID_CURVE(env); + + auto eckey = ECKeyPointer::NewByCurveName(nid); + if (!eckey) return args.GetReturnValue().Set(false); + + // Validate key data size matches the curve's expected private key length + const auto group = eckey.getGroup(); + auto order = BignumPointer::New(); + CHECK(order); + CHECK(EC_GROUP_get_order(group, order.get(), nullptr)); + if (key_data.size() != order.byteLength()) + return args.GetReturnValue().Set(false); + + BignumPointer priv_bn(key_data.data(), key_data.size()); + if (!priv_bn) return args.GetReturnValue().Set(false); + + if (!eckey.setPrivateKey(priv_bn)) return args.GetReturnValue().Set(false); + + // Compute public key from private key + auto pub_point = ECPointPointer::New(group); + if (!pub_point || !pub_point.mul(group, priv_bn.get())) { + return args.GetReturnValue().Set(false); + } + + if (!eckey.setPublicKey(pub_point)) return args.GetReturnValue().Set(false); + + auto pkey = EVPKeyPointer::New(); + if (!pkey.assign(eckey)) { + return args.GetReturnValue().Set(false); + } + + eckey.release(); + + key->data_ = + KeyObjectData::CreateAsymmetric(kKeyTypePrivate, std::move(pkey)); + + args.GetReturnValue().Set(true); +} + void KeyObjectHandle::RawSeed(const v8::FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); KeyObjectHandle* key; @@ -1157,17 +1388,33 @@ void KeyObjectHandle::RawSeed(const v8::FunctionCallbackInfo& args) { CHECK_EQ(data.GetKeyType(), kKeyTypePrivate); Mutex::ScopedLock lock(data.mutex()); - auto raw_data = data.GetAsymmetricKey().rawSeed(); + const auto& pkey = data.GetAsymmetricKey(); + + switch (pkey.id()) { +#if OPENSSL_WITH_PQC + case EVP_PKEY_ML_DSA_44: + case EVP_PKEY_ML_DSA_65: + case EVP_PKEY_ML_DSA_87: + case EVP_PKEY_ML_KEM_512: + case EVP_PKEY_ML_KEM_768: + case EVP_PKEY_ML_KEM_1024: + break; +#endif + default: + return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); + } + +#if OPENSSL_WITH_PQC + auto raw_data = pkey.rawSeed(); if (!raw_data) { return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get raw seed"); } args.GetReturnValue().Set( - Buffer::Copy( - env, reinterpret_cast(raw_data.get()), raw_data.size()) + Buffer::Copy(env, raw_data.get(), raw_data.size()) .FromMaybe(Local())); -} #endif +} void KeyObjectHandle::ExportJWK( const v8::FunctionCallbackInfo& args) { @@ -1290,32 +1537,6 @@ std::unique_ptr NativeKeyObject::CloneForMessaging() return std::make_unique(handle_data_); } -WebCryptoKeyExportStatus PKEY_SPKI_Export(const KeyObjectData& key_data, - ByteSource* out) { - CHECK_EQ(key_data.GetKeyType(), kKeyTypePublic); - Mutex::ScopedLock lock(key_data.mutex()); - auto bio = key_data.GetAsymmetricKey().derPublicKey(); - if (!bio) return WebCryptoKeyExportStatus::FAILED; - *out = ByteSource::FromBIO(bio); - return WebCryptoKeyExportStatus::OK; -} - -WebCryptoKeyExportStatus PKEY_PKCS8_Export(const KeyObjectData& key_data, - ByteSource* out) { - CHECK_EQ(key_data.GetKeyType(), kKeyTypePrivate); - Mutex::ScopedLock lock(key_data.mutex()); - const auto& m_pkey = key_data.GetAsymmetricKey(); - - auto bio = BIOPointer::NewMem(); - CHECK(bio); - PKCS8Pointer p8inf(EVP_PKEY2PKCS8(m_pkey.get())); - if (!i2d_PKCS8_PRIV_KEY_INFO_bio(bio.get(), p8inf.get())) - return WebCryptoKeyExportStatus::FAILED; - - *out = ByteSource::FromBIO(bio); - return WebCryptoKeyExportStatus::OK; -} - namespace Keys { void Initialize(Environment* env, Local target) { target->Set(env->context(), diff --git a/src/crypto/crypto_keys.h b/src/crypto/crypto_keys.h index 90c252eba28bea..4a8438b38c9f1e 100644 --- a/src/crypto/crypto_keys.h +++ b/src/crypto/crypto_keys.h @@ -170,11 +170,15 @@ class KeyObjectHandle : public BaseObject { static void Export(const v8::FunctionCallbackInfo& args); -#if OPENSSL_WITH_PQC - static void InitPqcRaw(const v8::FunctionCallbackInfo& args); static void RawPublicKey(const v8::FunctionCallbackInfo& args); + static void RawPrivateKey(const v8::FunctionCallbackInfo& args); + static void ExportECPublicRaw( + const v8::FunctionCallbackInfo& args); + static void ExportECPrivateRaw( + const v8::FunctionCallbackInfo& args); + static void InitECPrivateRaw(const v8::FunctionCallbackInfo& args); + static void InitPqcRaw(const v8::FunctionCallbackInfo& args); static void RawSeed(const v8::FunctionCallbackInfo& args); -#endif v8::MaybeLocal ExportSecretKey() const; v8::MaybeLocal ExportPublicKey( @@ -241,147 +245,6 @@ enum WebCryptoKeyFormat { kWebCryptoKeyFormatJWK }; -enum class WebCryptoKeyExportStatus { - OK, - INVALID_KEY_TYPE, - FAILED -}; - -template -class KeyExportJob final : public CryptoJob { - public: - using AdditionalParams = typename KeyExportTraits::AdditionalParameters; - - static void New(const v8::FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - CHECK(args.IsConstructCall()); - - CryptoJobMode mode = GetCryptoJobMode(args[0]); - - CHECK(args[1]->IsUint32()); // Export Type - CHECK(args[2]->IsObject()); // KeyObject - - WebCryptoKeyFormat format = - static_cast(args[1].As()->Value()); - - KeyObjectHandle* key; - ASSIGN_OR_RETURN_UNWRAP(&key, args[2]); - - CHECK_NOT_NULL(key); - - AdditionalParams params; - if (KeyExportTraits::AdditionalConfig(args, 3, ¶ms).IsNothing()) { - // The KeyExportTraits::AdditionalConfig is responsible for - // calling an appropriate THROW_CRYPTO_* variant reporting - // whatever error caused initialization to fail. - return; - } - - new KeyExportJob( - env, - args.This(), - mode, - key->Data(), - format, - std::move(params)); - } - - static void Initialize( - Environment* env, - v8::Local target) { - CryptoJob::Initialize(New, env, target); - } - - static void RegisterExternalReferences(ExternalReferenceRegistry* registry) { - CryptoJob::RegisterExternalReferences(New, registry); - } - - KeyExportJob(Environment* env, - v8::Local object, - CryptoJobMode mode, - const KeyObjectData& key, - WebCryptoKeyFormat format, - AdditionalParams&& params) - : CryptoJob(env, - object, - AsyncWrap::PROVIDER_KEYEXPORTREQUEST, - mode, - std::move(params)), - key_(key.addRef()), - format_(format) {} - - WebCryptoKeyFormat format() const { return format_; } - - void DoThreadPoolWork() override { - const WebCryptoKeyExportStatus status = - KeyExportTraits::DoExport( - key_, - format_, - *CryptoJob::params(), - &out_); - if (status == WebCryptoKeyExportStatus::OK) { - // Success! - return; - } - CryptoErrorStore* errors = CryptoJob::errors(); - errors->Capture(); - if (errors->Empty()) { - switch (status) { - case WebCryptoKeyExportStatus::OK: - UNREACHABLE(); - break; - case WebCryptoKeyExportStatus::INVALID_KEY_TYPE: - errors->Insert(NodeCryptoError::INVALID_KEY_TYPE); - break; - case WebCryptoKeyExportStatus::FAILED: - errors->Insert(NodeCryptoError::CIPHER_JOB_FAILED); - break; - } - } - } - - v8::Maybe ToResult(v8::Local* err, - v8::Local* result) override { - Environment* env = AsyncWrap::env(); - CryptoErrorStore* errors = CryptoJob::errors(); - if (out_.size() > 0) { - CHECK(errors->Empty()); - *err = v8::Undefined(env->isolate()); - *result = out_.ToArrayBuffer(env); - if (result->IsEmpty()) { - return v8::Nothing(); - } - } else { - if (errors->Empty()) errors->Capture(); - CHECK(!errors->Empty()); - *result = v8::Undefined(env->isolate()); - if (!errors->ToException(env).ToLocal(err)) { - return v8::Nothing(); - } - } - CHECK(!result->IsEmpty()); - CHECK(!err->IsEmpty()); - return v8::JustVoid(); - } - - SET_SELF_SIZE(KeyExportJob) - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackFieldWithSize("out", out_.size()); - CryptoJob::MemoryInfo(tracker); - } - - private: - KeyObjectData key_; - WebCryptoKeyFormat format_; - ByteSource out_; -}; - -WebCryptoKeyExportStatus PKEY_SPKI_Export(const KeyObjectData& key_data, - ByteSource* out); - -WebCryptoKeyExportStatus PKEY_PKCS8_Export(const KeyObjectData& key_data, - ByteSource* out); - namespace Keys { void Initialize(Environment* env, v8::Local target); void RegisterExternalReferences(ExternalReferenceRegistry* registry); diff --git a/src/crypto/crypto_rsa.cc b/src/crypto/crypto_rsa.cc index 62ee228945c45b..3619c1d21dd238 100644 --- a/src/crypto/crypto_rsa.cc +++ b/src/crypto/crypto_rsa.cc @@ -176,12 +176,6 @@ Maybe RsaKeyGenTraits::AdditionalConfig( } namespace { -WebCryptoKeyExportStatus RSA_JWK_Export(const KeyObjectData& key_data, - const RSAKeyExportConfig& params, - ByteSource* out) { - return WebCryptoKeyExportStatus::FAILED; -} - using Cipher_t = DataPointer(const EVPKeyPointer& key, const ncrypto::Rsa::CipherParams& params, const ncrypto::Buffer in); @@ -210,42 +204,6 @@ WebCryptoCipherStatus RSA_Cipher(Environment* env, } } // namespace -Maybe RSAKeyExportTraits::AdditionalConfig( - const FunctionCallbackInfo& args, - unsigned int offset, - RSAKeyExportConfig* params) { - CHECK(args[offset]->IsUint32()); // RSAKeyVariant - params->variant = - static_cast(args[offset].As()->Value()); - return JustVoid(); -} - -WebCryptoKeyExportStatus RSAKeyExportTraits::DoExport( - const KeyObjectData& key_data, - WebCryptoKeyFormat format, - const RSAKeyExportConfig& params, - ByteSource* out) { - CHECK_NE(key_data.GetKeyType(), kKeyTypeSecret); - - switch (format) { - case kWebCryptoKeyFormatRaw: - // Not supported for RSA keys of either type - return WebCryptoKeyExportStatus::FAILED; - case kWebCryptoKeyFormatJWK: - return RSA_JWK_Export(key_data, params, out); - case kWebCryptoKeyFormatPKCS8: - if (key_data.GetKeyType() != kKeyTypePrivate) - return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; - return PKEY_PKCS8_Export(key_data, out); - case kWebCryptoKeyFormatSPKI: - if (key_data.GetKeyType() != kKeyTypePublic) - return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; - return PKEY_SPKI_Export(key_data, out); - default: - UNREACHABLE(); - } -} - RSACipherConfig::RSACipherConfig(RSACipherConfig&& other) noexcept : mode(other.mode), label(std::move(other.label)), @@ -539,7 +497,6 @@ bool GetRsaKeyDetail(Environment* env, namespace RSAAlg { void Initialize(Environment* env, Local target) { RSAKeyPairGenJob::Initialize(env, target); - RSAKeyExportJob::Initialize(env, target); RSACipherJob::Initialize(env, target); NODE_DEFINE_CONSTANT(target, kKeyVariantRSA_SSA_PKCS1_v1_5); @@ -549,7 +506,6 @@ void Initialize(Environment* env, Local target) { void RegisterExternalReferences(ExternalReferenceRegistry* registry) { RSAKeyPairGenJob::RegisterExternalReferences(registry); - RSAKeyExportJob::RegisterExternalReferences(registry); RSACipherJob::RegisterExternalReferences(registry); } } // namespace RSAAlg diff --git a/src/crypto/crypto_rsa.h b/src/crypto/crypto_rsa.h index a9912d6f43674b..5b7f90f502df03 100644 --- a/src/crypto/crypto_rsa.h +++ b/src/crypto/crypto_rsa.h @@ -52,30 +52,6 @@ struct RsaKeyGenTraits final { using RSAKeyPairGenJob = KeyGenJob>; -struct RSAKeyExportConfig final : public MemoryRetainer { - RSAKeyVariant variant = kKeyVariantRSA_SSA_PKCS1_v1_5; - SET_NO_MEMORY_INFO() - SET_MEMORY_INFO_NAME(RSAKeyExportConfig) - SET_SELF_SIZE(RSAKeyExportConfig) -}; - -struct RSAKeyExportTraits final { - static constexpr const char* JobName = "RSAKeyExportJob"; - using AdditionalParameters = RSAKeyExportConfig; - - static v8::Maybe AdditionalConfig( - const v8::FunctionCallbackInfo& args, - unsigned int offset, - RSAKeyExportConfig* config); - - static WebCryptoKeyExportStatus DoExport(const KeyObjectData& key_data, - WebCryptoKeyFormat format, - const RSAKeyExportConfig& params, - ByteSource* out); -}; - -using RSAKeyExportJob = KeyExportJob; - struct RSACipherConfig final : public MemoryRetainer { CryptoJobMode mode = kCryptoJobAsync; ByteSource label; diff --git a/src/node_errors.h b/src/node_errors.h index 406ad251bcf4bb..8f14b75b10493c 100644 --- a/src/node_errors.h +++ b/src/node_errors.h @@ -51,6 +51,7 @@ void OOMErrorHandler(const char* location, const v8::OOMDetails& details); V(ERR_CPU_PROFILE_NOT_STARTED, Error) \ V(ERR_CPU_PROFILE_TOO_MANY, Error) \ V(ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED, Error) \ + V(ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS, Error) \ V(ERR_CRYPTO_INITIALIZATION_FAILED, Error) \ V(ERR_CRYPTO_INVALID_ARGON2_PARAMS, TypeError) \ V(ERR_CRYPTO_INVALID_AUTH_TAG, TypeError) \ @@ -191,6 +192,8 @@ ERRORS_WITH_CODE(V) V(ERR_CLOSED_MESSAGE_PORT, "Cannot send data on closed MessagePort") \ V(ERR_CONSTRUCT_CALL_INVALID, "Constructor cannot be called") \ V(ERR_CONSTRUCT_CALL_REQUIRED, "Cannot call constructor without `new`") \ + V(ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS, \ + "The selected key encoding is incompatible with the key type") \ V(ERR_CRYPTO_INITIALIZATION_FAILED, "Initialization failed") \ V(ERR_CRYPTO_INVALID_ARGON2_PARAMS, "Invalid Argon2 params") \ V(ERR_CRYPTO_INVALID_AUTH_TAG, "Invalid authentication tag") \ diff --git a/test/parallel/test-crypto-encap-decap.js b/test/parallel/test-crypto-encap-decap.js index 2c2ccd42ca2365..10dc451ef55d68 100644 --- a/test/parallel/test-crypto-encap-decap.js +++ b/test/parallel/test-crypto-encap-decap.js @@ -26,6 +26,7 @@ const keys = { privateKey: fixtures.readKey('rsa_private_2048.pem', 'ascii'), sharedSecretLength: 256, ciphertextLength: 256, + raw: false, }, 'rsa-pss': { supported: false, // Only raw RSA is supported @@ -38,6 +39,7 @@ const keys = { privateKey: fixtures.readKey('ec_p256_private.pem', 'ascii'), sharedSecretLength: 32, ciphertextLength: 65, + raw: true, }, 'p-384': { supported: hasOpenSSL(3, 2), // DHKEM was added in 3.2 @@ -45,6 +47,7 @@ const keys = { privateKey: fixtures.readKey('ec_p384_private.pem', 'ascii'), sharedSecretLength: 48, ciphertextLength: 97, + raw: true, }, 'p-521': { supported: hasOpenSSL(3, 2), // DHKEM was added in 3.2 @@ -52,6 +55,7 @@ const keys = { privateKey: fixtures.readKey('ec_p521_private.pem', 'ascii'), sharedSecretLength: 64, ciphertextLength: 133, + raw: true, }, 'secp256k1': { supported: false, // only P-256, P-384, and P-521 are supported @@ -64,6 +68,7 @@ const keys = { privateKey: fixtures.readKey('x25519_private.pem', 'ascii'), sharedSecretLength: 32, ciphertextLength: 32, + raw: true, }, 'x448': { supported: hasOpenSSL(3, 2), // DHKEM was added in 3.2 @@ -71,6 +76,7 @@ const keys = { privateKey: fixtures.readKey('x448_private.pem', 'ascii'), sharedSecretLength: 64, ciphertextLength: 56, + raw: true, }, 'ml-kem-512': { supported: hasOpenSSL(3, 5), @@ -78,6 +84,7 @@ const keys = { privateKey: fixtures.readKey('ml_kem_512_private.pem', 'ascii'), sharedSecretLength: 32, ciphertextLength: 768, + raw: true, }, 'ml-kem-768': { supported: hasOpenSSL(3, 5), @@ -85,6 +92,7 @@ const keys = { privateKey: fixtures.readKey('ml_kem_768_private.pem', 'ascii'), sharedSecretLength: 32, ciphertextLength: 1088, + raw: true, }, 'ml-kem-1024': { supported: hasOpenSSL(3, 5), @@ -92,10 +100,13 @@ const keys = { privateKey: fixtures.readKey('ml_kem_1024_private.pem', 'ascii'), sharedSecretLength: 32, ciphertextLength: 1568, + raw: true, }, }; -for (const [name, { supported, publicKey, privateKey, sharedSecretLength, ciphertextLength }] of Object.entries(keys)) { +for (const [name, { + supported, publicKey, privateKey, sharedSecretLength, ciphertextLength, raw, +}] of Object.entries(keys)) { if (!supported) { assert.throws(() => crypto.encapsulate(publicKey), { code: /ERR_OSSL_EVP_DECODE_ERROR|ERR_CRYPTO_OPERATION_FAILED/ }); @@ -136,6 +147,25 @@ for (const [name, { supported, publicKey, privateKey, sharedSecretLength, cipher }); } + if (raw) { + const { asymmetricKeyType } = keyObjects.privateKey; + const { namedCurve } = keyObjects.privateKey.asymmetricKeyDetails; + const privateFormat = asymmetricKeyType.startsWith('ml-') ? 'raw-seed' : 'raw-private'; + const rawPublic = { + key: keyObjects.publicKey.export({ format: 'raw-public' }), + format: 'raw-public', + asymmetricKeyType, + ...(namedCurve ? { namedCurve } : {}), + }; + const rawPrivate = { + key: keyObjects.privateKey.export({ format: privateFormat }), + format: privateFormat, + asymmetricKeyType, + ...(namedCurve ? { namedCurve } : {}), + }; + keyPairs.push({ publicKey: rawPublic, privateKey: rawPrivate }); + } + for (const kp of keyPairs) { // sync { diff --git a/test/parallel/test-crypto-key-objects-raw.js b/test/parallel/test-crypto-key-objects-raw.js new file mode 100644 index 00000000000000..f301cc1942fd9a --- /dev/null +++ b/test/parallel/test-crypto-key-objects-raw.js @@ -0,0 +1,446 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); +const fixtures = require('../common/fixtures'); +const { hasOpenSSL } = require('../common/crypto'); + +// EC: NIST and OpenSSL curve names are both recognized for raw-public and raw-private +{ + const pubKeyObj = crypto.createPublicKey( + fixtures.readKey('ec_p256_public.pem', 'ascii')); + const privKeyObj = crypto.createPrivateKey( + fixtures.readKey('ec_p256_private.pem', 'ascii')); + + const rawPub = pubKeyObj.export({ format: 'raw-public' }); + const rawPriv = privKeyObj.export({ format: 'raw-private' }); + + for (const namedCurve of ['P-256', 'prime256v1']) { + const importedPub = crypto.createPublicKey({ + key: rawPub, format: 'raw-public', asymmetricKeyType: 'ec', namedCurve, + }); + assert.strictEqual(importedPub.equals(pubKeyObj), true); + + const importedPriv = crypto.createPrivateKey({ + key: rawPriv, format: 'raw-private', asymmetricKeyType: 'ec', namedCurve, + }); + assert.strictEqual(importedPriv.equals(privKeyObj), true); + } +} + +// Key types that don't support raw-* formats +{ + for (const [type, pub, priv] of [ + ['rsa', 'rsa_public_2048.pem', 'rsa_private_2048.pem'], + ['dsa', 'dsa_public.pem', 'dsa_private.pem'], + ]) { + const pubKeyObj = crypto.createPublicKey( + fixtures.readKey(pub, 'ascii')); + const privKeyObj = crypto.createPrivateKey( + fixtures.readKey(priv, 'ascii')); + + assert.throws(() => pubKeyObj.export({ format: 'raw-public' }), + { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + assert.throws(() => privKeyObj.export({ format: 'raw-private' }), + { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + assert.throws(() => privKeyObj.export({ format: 'raw-seed' }), + { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + + for (const format of ['raw-public', 'raw-private', 'raw-seed']) { + assert.throws(() => crypto.createPublicKey({ + key: Buffer.alloc(32), format, asymmetricKeyType: type, + }), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + } + } + + // DH keys also don't support raw formats + { + const privKeyObj = crypto.createPrivateKey( + fixtures.readKey('dh_private.pem', 'ascii')); + assert.throws(() => privKeyObj.export({ format: 'raw-private' }), + { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + + for (const format of ['raw-public', 'raw-private', 'raw-seed']) { + assert.throws(() => crypto.createPrivateKey({ + key: Buffer.alloc(32), format, asymmetricKeyType: 'dh', + }), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + } + } +} + +// PQC import throws when PQC is not supported +if (!hasOpenSSL(3, 5)) { + for (const asymmetricKeyType of [ + 'ml-dsa-44', 'ml-dsa-65', 'ml-dsa-87', + 'ml-kem-512', 'ml-kem-768', 'ml-kem-1024', + 'slh-dsa-sha2-128f', 'slh-dsa-shake-128f', + ]) { + for (const format of ['raw-public', 'raw-private', 'raw-seed']) { + assert.throws(() => crypto.createPublicKey({ + key: Buffer.alloc(32), format, asymmetricKeyType, + }), { code: 'ERR_INVALID_ARG_VALUE' }); + } + } +} + +// EC: P-256 and P-384 keys cannot be imported as private/public of the other type +{ + const p256Pub = crypto.createPublicKey( + fixtures.readKey('ec_p256_public.pem', 'ascii')); + const p384Pub = crypto.createPublicKey( + fixtures.readKey('ec_p384_public.pem', 'ascii')); + const p256Priv = crypto.createPrivateKey( + fixtures.readKey('ec_p256_private.pem', 'ascii')); + const p384Priv = crypto.createPrivateKey( + fixtures.readKey('ec_p384_private.pem', 'ascii')); + + const p256RawPub = p256Pub.export({ format: 'raw-public' }); + const p384RawPub = p384Pub.export({ format: 'raw-public' }); + const p256RawPriv = p256Priv.export({ format: 'raw-private' }); + const p384RawPriv = p384Priv.export({ format: 'raw-private' }); + + // P-256 public imported as P-384 + assert.throws(() => crypto.createPublicKey({ + key: p256RawPub, format: 'raw-public', + asymmetricKeyType: 'ec', namedCurve: 'P-384', + }), { code: 'ERR_INVALID_ARG_VALUE' }); + + // P-384 public imported as P-256 + assert.throws(() => crypto.createPublicKey({ + key: p384RawPub, format: 'raw-public', + asymmetricKeyType: 'ec', namedCurve: 'P-256', + }), { code: 'ERR_INVALID_ARG_VALUE' }); + + // P-256 private imported as P-384 + assert.throws(() => crypto.createPrivateKey({ + key: p256RawPriv, format: 'raw-private', + asymmetricKeyType: 'ec', namedCurve: 'P-384', + }), { code: 'ERR_INVALID_ARG_VALUE' }); + + // P-384 private imported as P-256 + assert.throws(() => crypto.createPrivateKey({ + key: p384RawPriv, format: 'raw-private', + asymmetricKeyType: 'ec', namedCurve: 'P-256', + }), { code: 'ERR_INVALID_ARG_VALUE' }); +} + +// ML-KEM: -768 and -512 public keys cannot be imported as the other type +if (hasOpenSSL(3, 5)) { + const mlKem512Pub = crypto.createPublicKey( + fixtures.readKey('ml_kem_512_public.pem', 'ascii')); + const mlKem768Pub = crypto.createPublicKey( + fixtures.readKey('ml_kem_768_public.pem', 'ascii')); + + const mlKem512RawPub = mlKem512Pub.export({ format: 'raw-public' }); + const mlKem768RawPub = mlKem768Pub.export({ format: 'raw-public' }); + + assert.throws(() => crypto.createPublicKey({ + key: mlKem512RawPub, format: 'raw-public', asymmetricKeyType: 'ml-kem-768', + }), { code: 'ERR_INVALID_ARG_VALUE' }); + + assert.throws(() => crypto.createPublicKey({ + key: mlKem768RawPub, format: 'raw-public', asymmetricKeyType: 'ml-kem-512', + }), { code: 'ERR_INVALID_ARG_VALUE' }); +} + +// ML-DSA: -44 and -65 public keys cannot be imported as the other type +if (hasOpenSSL(3, 5)) { + const mlDsa44Pub = crypto.createPublicKey( + fixtures.readKey('ml_dsa_44_public.pem', 'ascii')); + const mlDsa65Pub = crypto.createPublicKey( + fixtures.readKey('ml_dsa_65_public.pem', 'ascii')); + + const mlDsa44RawPub = mlDsa44Pub.export({ format: 'raw-public' }); + const mlDsa65RawPub = mlDsa65Pub.export({ format: 'raw-public' }); + + assert.throws(() => crypto.createPublicKey({ + key: mlDsa44RawPub, format: 'raw-public', asymmetricKeyType: 'ml-dsa-65', + }), { code: 'ERR_INVALID_ARG_VALUE' }); + + assert.throws(() => crypto.createPublicKey({ + key: mlDsa65RawPub, format: 'raw-public', asymmetricKeyType: 'ml-dsa-44', + }), { code: 'ERR_INVALID_ARG_VALUE' }); +} + +// SLH-DSA: mismatched key types with different sizes are rejected +if (hasOpenSSL(3, 5)) { + const slh128fPub = crypto.createPublicKey( + fixtures.readKey('slh_dsa_sha2_128f_public.pem', 'ascii')); + const slh192fPub = crypto.createPublicKey( + fixtures.readKey('slh_dsa_sha2_192f_public.pem', 'ascii')); + const slh128fPriv = crypto.createPrivateKey( + fixtures.readKey('slh_dsa_sha2_128f_private.pem', 'ascii')); + const slh192fPriv = crypto.createPrivateKey( + fixtures.readKey('slh_dsa_sha2_192f_private.pem', 'ascii')); + + const rawPub128f = slh128fPub.export({ format: 'raw-public' }); + const rawPub192f = slh192fPub.export({ format: 'raw-public' }); + const rawPriv128f = slh128fPriv.export({ format: 'raw-private' }); + const rawPriv192f = slh192fPriv.export({ format: 'raw-private' }); + + assert.throws(() => crypto.createPublicKey({ + key: rawPub128f, format: 'raw-public', + asymmetricKeyType: 'slh-dsa-sha2-192f', + }), { code: 'ERR_INVALID_ARG_VALUE' }); + + assert.throws(() => crypto.createPublicKey({ + key: rawPub192f, format: 'raw-public', + asymmetricKeyType: 'slh-dsa-sha2-128f', + }), { code: 'ERR_INVALID_ARG_VALUE' }); + + assert.throws(() => crypto.createPrivateKey({ + key: rawPriv128f, format: 'raw-private', + asymmetricKeyType: 'slh-dsa-sha2-192f', + }), { code: 'ERR_INVALID_ARG_VALUE' }); + + assert.throws(() => crypto.createPrivateKey({ + key: rawPriv192f, format: 'raw-private', + asymmetricKeyType: 'slh-dsa-sha2-128f', + }), { code: 'ERR_INVALID_ARG_VALUE' }); +} + +// Passphrase cannot be used with raw formats +{ + const privKeyObj = crypto.createPrivateKey( + fixtures.readKey('ed25519_private.pem', 'ascii')); + + assert.throws(() => privKeyObj.export({ + format: 'raw-private', passphrase: 'test', + }), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + + assert.throws(() => privKeyObj.export({ + format: 'raw-seed', passphrase: 'test', + }), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); +} + +// raw-seed export is rejected for key types that do not support seeds +{ + const ecPriv = crypto.createPrivateKey( + fixtures.readKey('ec_p256_private.pem', 'ascii')); + assert.throws(() => ecPriv.export({ format: 'raw-seed' }), + { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + + for (const type of ['ed25519', 'ed448', 'x25519', 'x448']) { + const priv = crypto.createPrivateKey( + fixtures.readKey(`${type}_private.pem`, 'ascii')); + assert.throws(() => priv.export({ format: 'raw-seed' }), + { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + } + + if (hasOpenSSL(3, 5)) { + const slhPriv = crypto.createPrivateKey( + fixtures.readKey('slh_dsa_sha2_128f_private.pem', 'ascii')); + assert.throws(() => slhPriv.export({ format: 'raw-seed' }), + { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + } +} + +// raw-private cannot be used for ml-kem and ml-dsa +if (hasOpenSSL(3, 5)) { + for (const type of ['ml-kem-512', 'ml-dsa-44']) { + const priv = crypto.createPrivateKey( + fixtures.readKey(`${type.replaceAll('-', '_')}_private.pem`, 'ascii')); + assert.throws(() => priv.export({ format: 'raw-private' }), + { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + } +} + +// EC: defaults to uncompressed, can be switched to compressed, both can be imported +{ + const pubKeyObj = crypto.createPublicKey( + fixtures.readKey('ec_p256_public.pem', 'ascii')); + + const defaultExport = pubKeyObj.export({ format: 'raw-public' }); + const compressed = pubKeyObj.export({ format: 'raw-public', type: 'compressed' }); + const uncompressed = pubKeyObj.export({ format: 'raw-public', type: 'uncompressed' }); + + // Default is uncompressed + assert.deepStrictEqual(defaultExport, uncompressed); + + // Compressed starts with 0x02 or 0x03 and is 33 bytes for P-256 + assert.strictEqual(compressed.byteLength, 33); + assert(compressed[0] === 0x02 || compressed[0] === 0x03); + + // Uncompressed starts with 0x04 and is 65 bytes for P-256 + assert.strictEqual(uncompressed.byteLength, 65); + assert.strictEqual(uncompressed[0], 0x04); + + // Both can be imported + const fromCompressed = crypto.createPublicKey({ + key: compressed, format: 'raw-public', + asymmetricKeyType: 'ec', namedCurve: 'P-256', + }); + assert.strictEqual(fromCompressed.equals(pubKeyObj), true); + + const fromUncompressed = crypto.createPublicKey({ + key: uncompressed, format: 'raw-public', + asymmetricKeyType: 'ec', namedCurve: 'P-256', + }); + assert.strictEqual(fromUncompressed.equals(pubKeyObj), true); +} + +// Compressed and uncompressed are the only recognized options +{ + const pubKeyObj = crypto.createPublicKey( + fixtures.readKey('ec_p256_public.pem', 'ascii')); + + assert.throws(() => pubKeyObj.export({ format: 'raw-public', type: 'hybrid' }), + { code: 'ERR_INVALID_ARG_VALUE' }); + + assert.throws(() => pubKeyObj.export({ format: 'raw-public', type: 'invalid' }), + { code: 'ERR_INVALID_ARG_VALUE' }); +} + +// None of the raw types can be used with symmetric key objects +{ + const secretKey = crypto.createSecretKey(Buffer.alloc(32)); + + for (const format of ['raw-public', 'raw-private', 'raw-seed']) { + assert.throws(() => secretKey.export({ format }), + { code: 'ERR_INVALID_ARG_VALUE' }); + } +} + +// Private key objects created from raw-seed or raw-private can be passed to createPublicKey() +{ + // Ed25519 raw-private -> createPublicKey + const edPriv = crypto.createPrivateKey( + fixtures.readKey('ed25519_private.pem', 'ascii')); + const edPub = crypto.createPublicKey( + fixtures.readKey('ed25519_public.pem', 'ascii')); + const rawPriv = edPriv.export({ format: 'raw-private' }); + const importedPriv = crypto.createPrivateKey({ + key: rawPriv, format: 'raw-private', asymmetricKeyType: 'ed25519', + }); + const derivedPub = crypto.createPublicKey(importedPriv); + assert.strictEqual(derivedPub.equals(edPub), true); + // Private key must not be extractable from the derived public key. + assert.throws(() => derivedPub.export({ format: 'pem', type: 'pkcs8' }), + { code: 'ERR_INVALID_ARG_VALUE' }); + assert.throws(() => derivedPub.export({ format: 'der', type: 'pkcs8' }), + { code: 'ERR_INVALID_ARG_VALUE' }); + + // EC raw-private -> createPublicKey + const ecPriv = crypto.createPrivateKey( + fixtures.readKey('ec_p256_private.pem', 'ascii')); + const ecPub = crypto.createPublicKey( + fixtures.readKey('ec_p256_public.pem', 'ascii')); + const ecRawPriv = ecPriv.export({ format: 'raw-private' }); + const ecImportedPriv = crypto.createPrivateKey({ + key: ecRawPriv, format: 'raw-private', + asymmetricKeyType: 'ec', namedCurve: 'P-256', + }); + const ecDerivedPub = crypto.createPublicKey(ecImportedPriv); + assert.strictEqual(ecDerivedPub.equals(ecPub), true); + // Private key must not be extractable from the derived public key. + assert.throws(() => ecDerivedPub.export({ format: 'pem', type: 'pkcs8' }), + { code: 'ERR_INVALID_ARG_VALUE' }); + assert.throws(() => ecDerivedPub.export({ format: 'pem', type: 'sec1' }), + { code: 'ERR_INVALID_ARG_VALUE' }); + + // PQC raw-seed -> createPublicKey + if (hasOpenSSL(3, 5)) { + const mlDsaPriv = crypto.createPrivateKey( + fixtures.readKey('ml_dsa_44_private.pem', 'ascii')); + const mlDsaPub = crypto.createPublicKey( + fixtures.readKey('ml_dsa_44_public.pem', 'ascii')); + const mlDsaRawSeed = mlDsaPriv.export({ format: 'raw-seed' }); + const mlDsaImportedPriv = crypto.createPrivateKey({ + key: mlDsaRawSeed, format: 'raw-seed', asymmetricKeyType: 'ml-dsa-44', + }); + const mlDsaDerivedPub = crypto.createPublicKey(mlDsaImportedPriv); + assert.strictEqual(mlDsaDerivedPub.equals(mlDsaPub), true); + // Private key must not be extractable from the derived public key. + assert.throws(() => mlDsaDerivedPub.export({ format: 'pem', type: 'pkcs8' }), + { code: 'ERR_INVALID_ARG_VALUE' }); + } +} + +// raw-public EC keys that are garbage/not on curve are rejected +{ + const garbage = Buffer.alloc(33, 0xff); + garbage[0] = 0x02; // Valid compressed prefix but invalid point + + assert.throws(() => crypto.createPublicKey({ + key: garbage, format: 'raw-public', + asymmetricKeyType: 'ec', namedCurve: 'P-256', + }), { code: 'ERR_INVALID_ARG_VALUE' }); + + // Totally random garbage + assert.throws(() => crypto.createPublicKey({ + key: Buffer.alloc(10, 0xab), format: 'raw-public', + asymmetricKeyType: 'ec', namedCurve: 'P-256', + }), { code: 'ERR_INVALID_ARG_VALUE' }); +} + +// Unrecognized namedCurve values are rejected +{ + assert.throws(() => crypto.createPublicKey({ + key: Buffer.alloc(33), format: 'raw-public', + asymmetricKeyType: 'ec', namedCurve: 'not-a-curve', + }), { code: 'ERR_CRYPTO_INVALID_CURVE' }); + + assert.throws(() => crypto.createPrivateKey({ + key: Buffer.alloc(32), format: 'raw-private', + asymmetricKeyType: 'ec', namedCurve: 'not-a-curve', + }), { code: 'ERR_CRYPTO_INVALID_CURVE' }); +} + +// x25519, ed25519, x448, and ed448 cannot be used as 'ec' namedCurve values +{ + for (const type of ['ed25519', 'x25519', 'ed448', 'x448']) { + const priv = crypto.createPrivateKey( + fixtures.readKey(`${type}_private.pem`, 'ascii')); + const pub = crypto.createPublicKey( + fixtures.readKey(`${type}_public.pem`, 'ascii')); + + const rawPub = pub.export({ format: 'raw-public' }); + const rawPriv = priv.export({ format: 'raw-private' }); + + // Try to import as EC - must fail + assert.throws(() => crypto.createPublicKey({ + key: rawPub, format: 'raw-public', + asymmetricKeyType: 'ec', namedCurve: type, + }), { code: 'ERR_CRYPTO_INVALID_CURVE' }); + + assert.throws(() => crypto.createPrivateKey({ + key: rawPriv, format: 'raw-private', + asymmetricKeyType: 'ec', namedCurve: type, + }), { code: 'ERR_CRYPTO_INVALID_CURVE' }); + } +} + +// Missing asymmetricKeyType option +{ + assert.throws(() => crypto.createPublicKey({ + key: Buffer.alloc(32), format: 'raw-public', + }), { code: 'ERR_INVALID_ARG_TYPE' }); +} + +// Unknown asymmetricKeyType value +{ + assert.throws(() => crypto.createPublicKey({ + key: Buffer.alloc(32), format: 'raw-public', + asymmetricKeyType: 'unknown', + }), { code: 'ERR_INVALID_ARG_VALUE' }); +} + +// Non-buffer key data +{ + assert.throws(() => crypto.createPublicKey({ + key: 12345, format: 'raw-public', + asymmetricKeyType: 'ec', namedCurve: 'P-256', + }), { code: 'ERR_INVALID_ARG_TYPE' }); +} + +// Missing namedCurve for EC +{ + assert.throws(() => crypto.createPublicKey({ + key: Buffer.alloc(33), format: 'raw-public', + asymmetricKeyType: 'ec', + }), { code: 'ERR_INVALID_ARG_TYPE' }); +} diff --git a/test/parallel/test-crypto-key-objects.js b/test/parallel/test-crypto-key-objects.js index e8359ed6d0362c..6c1c3fd3afa448 100644 --- a/test/parallel/test-crypto-key-objects.js +++ b/test/parallel/test-crypto-key-objects.js @@ -170,6 +170,23 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', assert.strictEqual(derivedPublicKey.asymmetricKeyType, 'rsa'); assert.strictEqual(derivedPublicKey.symmetricKeySize, undefined); + // The private key should not be extractable from the derived public key. + assert.throws(() => derivedPublicKey.export({ format: 'pem', type: 'pkcs8' }), + { code: 'ERR_INVALID_ARG_VALUE' }); + assert.throws(() => derivedPublicKey.export({ format: 'der', type: 'pkcs8' }), + { code: 'ERR_INVALID_ARG_VALUE' }); + // JWK export should only contain public components, no 'd'. + { + const jwkExport = derivedPublicKey.export({ format: 'jwk' }); + assert.strictEqual(jwkExport.kty, 'RSA'); + assert.strictEqual(jwkExport.d, undefined); + assert.strictEqual(jwkExport.dp, undefined); + assert.strictEqual(jwkExport.dq, undefined); + assert.strictEqual(jwkExport.qi, undefined); + assert.strictEqual(jwkExport.p, undefined); + assert.strictEqual(jwkExport.q, undefined); + } + const publicKeyFromJwk = createPublicKey({ key: publicJwk, format: 'jwk' }); assert.strictEqual(publicKeyFromJwk.type, 'public'); assert.strictEqual(publicKeyFromJwk.toString(), '[object KeyObject]'); @@ -415,6 +432,33 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', key.export({ format: 'jwk' }), jwk); } } + + // Raw format round-trip + { + const privKey = createPrivateKey(info.private); + const pubKey = createPublicKey(info.public); + + const rawPriv = privKey.export({ format: 'raw-private' }); + const rawPub = pubKey.export({ format: 'raw-public' }); + assert(Buffer.isBuffer(rawPriv)); + assert(Buffer.isBuffer(rawPub)); + + const importedPriv = createPrivateKey({ + key: rawPriv, format: 'raw-private', asymmetricKeyType: keyType, + }); + assert.strictEqual(importedPriv.type, 'private'); + assert.strictEqual(importedPriv.asymmetricKeyType, keyType); + assert.deepStrictEqual( + importedPriv.export({ format: 'raw-private' }), rawPriv); + + const importedPub = createPublicKey({ + key: rawPub, format: 'raw-public', asymmetricKeyType: keyType, + }); + assert.strictEqual(importedPub.type, 'public'); + assert.strictEqual(importedPub.asymmetricKeyType, keyType); + assert.deepStrictEqual( + importedPub.export({ format: 'raw-public' }), rawPub); + } }); [ @@ -506,8 +550,47 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', delete jwk.d; assert.deepStrictEqual( key.export({ format: 'jwk' }), jwk); + + // Private key material must not be extractable from a derived public key. + assert.throws(() => key.export({ format: 'pem', type: 'pkcs8' }), + { code: 'ERR_INVALID_ARG_VALUE' }); + assert.throws(() => key.export({ format: 'pem', type: 'sec1' }), + { code: 'ERR_INVALID_ARG_VALUE' }); + assert.throws(() => key.export({ format: 'der', type: 'pkcs8' }), + { code: 'ERR_INVALID_ARG_VALUE' }); + assert.throws(() => key.export({ format: 'der', type: 'sec1' }), + { code: 'ERR_INVALID_ARG_VALUE' }); } } + + // Raw format round-trip + { + const privKey = createPrivateKey(info.private); + const pubKey = createPublicKey(info.public); + + const rawPriv = privKey.export({ format: 'raw-private' }); + const rawPub = pubKey.export({ format: 'raw-public' }); + assert(Buffer.isBuffer(rawPriv)); + assert(Buffer.isBuffer(rawPub)); + + const importedPriv = createPrivateKey({ + key: rawPriv, format: 'raw-private', + asymmetricKeyType: keyType, namedCurve, + }); + assert.strictEqual(importedPriv.type, 'private'); + assert.strictEqual(importedPriv.asymmetricKeyType, keyType); + assert.deepStrictEqual( + importedPriv.export({ format: 'raw-private' }), rawPriv); + + const importedPub = createPublicKey({ + key: rawPub, format: 'raw-public', + asymmetricKeyType: keyType, namedCurve, + }); + assert.strictEqual(importedPub.type, 'public'); + assert.strictEqual(importedPub.asymmetricKeyType, keyType); + assert.deepStrictEqual( + importedPub.export({ format: 'raw-public' }), rawPub); + } }); { diff --git a/test/parallel/test-crypto-pqc-key-objects-ml-dsa.js b/test/parallel/test-crypto-pqc-key-objects-ml-dsa.js index 8b9d0e197b9a55..1a832609d3f813 100644 --- a/test/parallel/test-crypto-pqc-key-objects-ml-dsa.js +++ b/test/parallel/test-crypto-pqc-key-objects-ml-dsa.js @@ -61,6 +61,15 @@ for (const [asymmetricKeyType, pubLen] of [ const jwk = key.export({ format: 'jwk' }); assertPublicJwk(jwk); assert.strictEqual(key.equals(createPublicKey({ format: 'jwk', key: jwk })), true); + + // Raw format round-trip + const rawPub = key.export({ format: 'raw-public' }); + assert(Buffer.isBuffer(rawPub)); + assert.strictEqual(rawPub.byteLength, pubLen); + const importedPub = createPublicKey({ + key: rawPub, format: 'raw-public', asymmetricKeyType, + }); + assert.strictEqual(importedPub.equals(key), true); } function assertPrivateKey(key, hasSeed) { @@ -78,6 +87,15 @@ for (const [asymmetricKeyType, pubLen] of [ assertPrivateJwk(jwk); assert.strictEqual(key.equals(createPrivateKey({ format: 'jwk', key: jwk })), true); assert.ok(createPublicKey({ format: 'jwk', key: jwk })); + + // Raw seed round-trip + const rawSeed = key.export({ format: 'raw-seed' }); + assert(Buffer.isBuffer(rawSeed)); + assert.strictEqual(rawSeed.byteLength, 32); + const importedPriv = createPrivateKey({ + key: rawSeed, format: 'raw-seed', asymmetricKeyType, + }); + assert.strictEqual(importedPriv.equals(key), true); } else { assert.throws(() => key.export({ format: 'jwk' }), { code: 'ERR_CRYPTO_OPERATION_FAILED', message: 'key does not have an available seed' }); @@ -172,8 +190,8 @@ for (const [asymmetricKeyType, pubLen] of [ } } else { assert.throws(() => createPrivateKey({ format, key: jwk }), - { code: 'ERR_INVALID_ARG_VALUE', message: /must be one of: 'RSA', 'EC', 'OKP'\. Received 'AKP'/ }); + { code: 'ERR_INVALID_ARG_VALUE', message: /Unsupported key type/ }); assert.throws(() => createPublicKey({ format, key: jwk }), - { code: 'ERR_INVALID_ARG_VALUE', message: /must be one of: 'RSA', 'EC', 'OKP'\. Received 'AKP'/ }); + { code: 'ERR_INVALID_ARG_VALUE', message: /Unsupported key type/ }); } } diff --git a/test/parallel/test-crypto-pqc-key-objects-ml-kem.js b/test/parallel/test-crypto-pqc-key-objects-ml-kem.js index d190a14dad87dd..0f4b226604c965 100644 --- a/test/parallel/test-crypto-pqc-key-objects-ml-kem.js +++ b/test/parallel/test-crypto-pqc-key-objects-ml-kem.js @@ -40,6 +40,14 @@ for (const asymmetricKeyType of ['ml-kem-512', 'ml-kem-768', 'ml-kem-1024']) { key.export({ format: 'der', type: 'spki' }); assert.throws(() => key.export({ format: 'jwk' }), { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE', message: 'Unsupported JWK Key Type.' }); + + // Raw format round-trip + const rawPub = key.export({ format: 'raw-public' }); + assert(Buffer.isBuffer(rawPub)); + const importedPub = createPublicKey({ + key: rawPub, format: 'raw-public', asymmetricKeyType, + }); + assert.strictEqual(importedPub.equals(key), true); } function assertPrivateKey(key, hasSeed) { @@ -49,6 +57,14 @@ for (const asymmetricKeyType of ['ml-kem-512', 'ml-kem-768', 'ml-kem-1024']) { key.export({ format: 'der', type: 'pkcs8' }); if (hasSeed) { assert.strictEqual(key.export({ format: 'pem', type: 'pkcs8' }), keys.private_seed_only); + + // Raw seed round-trip + const rawSeed = key.export({ format: 'raw-seed' }); + assert(Buffer.isBuffer(rawSeed)); + const importedPriv = createPrivateKey({ + key: rawSeed, format: 'raw-seed', asymmetricKeyType, + }); + assert.strictEqual(importedPriv.equals(key), true); } else { assert.strictEqual(key.export({ format: 'pem', type: 'pkcs8' }), keys.private_priv_only); } diff --git a/test/parallel/test-crypto-pqc-key-objects-slh-dsa.js b/test/parallel/test-crypto-pqc-key-objects-slh-dsa.js index 1b612de8b2e582..fdae27f2da797f 100644 --- a/test/parallel/test-crypto-pqc-key-objects-slh-dsa.js +++ b/test/parallel/test-crypto-pqc-key-objects-slh-dsa.js @@ -42,6 +42,14 @@ for (const asymmetricKeyType of [ key.export({ format: 'der', type: 'spki' }); assert.throws(() => key.export({ format: 'jwk' }), { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE', message: 'Unsupported JWK Key Type.' }); + + // Raw format round-trip + const rawPub = key.export({ format: 'raw-public' }); + assert(Buffer.isBuffer(rawPub)); + const importedPub = createPublicKey({ + key: rawPub, format: 'raw-public', asymmetricKeyType, + }); + assert.strictEqual(importedPub.equals(key), true); } function assertPrivateKey(key) { @@ -52,6 +60,14 @@ for (const asymmetricKeyType of [ assert.strictEqual(key.export({ format: 'pem', type: 'pkcs8' }), keys.private); assert.throws(() => key.export({ format: 'jwk' }), { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE', message: 'Unsupported JWK Key Type.' }); + + // Raw format round-trip + const rawPriv = key.export({ format: 'raw-private' }); + assert(Buffer.isBuffer(rawPriv)); + const importedPriv = createPrivateKey({ + key: rawPriv, format: 'raw-private', asymmetricKeyType, + }); + assert.strictEqual(importedPriv.equals(key), true); } if (!hasOpenSSL(3, 5)) { diff --git a/test/parallel/test-crypto-pqc-sign-verify-ml-dsa.js b/test/parallel/test-crypto-pqc-sign-verify-ml-dsa.js index de3937dbc07486..57d6692ca79b55 100644 --- a/test/parallel/test-crypto-pqc-sign-verify-ml-dsa.js +++ b/test/parallel/test-crypto-pqc-sign-verify-ml-dsa.js @@ -67,6 +67,28 @@ for (const [asymmetricKeyType, sigLen] of [ } } } + + // Raw format sign/verify + { + const pubKeyObj = createPublicKey(keys.public); + const privKeyObj = createPrivateKey(keys.private_seed_only); + + const rawPublic = { + key: pubKeyObj.export({ format: 'raw-public' }), + format: 'raw-public', + asymmetricKeyType, + }; + const rawSeed = { + key: privKeyObj.export({ format: 'raw-seed' }), + format: 'raw-seed', + asymmetricKeyType, + }; + + const data = randomBytes(32); + const signature = sign(undefined, data, rawSeed); + assert.strictEqual(signature.byteLength, sigLen); + assert.strictEqual(verify(undefined, data, rawPublic, signature), true); + } } // Test vectors from ietf-cose-dilithium diff --git a/test/parallel/test-crypto-sign-verify.js b/test/parallel/test-crypto-sign-verify.js index a66f0a94efd7c9..1900f244b8491a 100644 --- a/test/parallel/test-crypto-sign-verify.js +++ b/test/parallel/test-crypto-sign-verify.js @@ -422,16 +422,19 @@ assert.throws( { private: fixtures.readKey('ed25519_private.pem', 'ascii'), public: fixtures.readKey('ed25519_public.pem', 'ascii'), algo: null, - sigLen: 64 }, + sigLen: 64, + raw: true }, { private: fixtures.readKey('ed448_private.pem', 'ascii'), public: fixtures.readKey('ed448_public.pem', 'ascii'), algo: null, supportsContext: true, - sigLen: 114 }, + sigLen: 114, + raw: true }, { private: fixtures.readKey('rsa_private_2048.pem', 'ascii'), public: fixtures.readKey('rsa_public_2048.pem', 'ascii'), algo: 'sha1', - sigLen: 256 }, + sigLen: 256, + raw: false }, ].forEach((pair) => { const algo = pair.algo; @@ -458,6 +461,29 @@ assert.throws( assert.strictEqual(crypto.verify(algo, data, pubKeyObj, sig), true); } + if (pair.raw) { + const data = Buffer.from('Hello world'); + const privKeyObj = crypto.createPrivateKey(pair.private); + const pubKeyObj = crypto.createPublicKey(pair.public); + const { asymmetricKeyType } = privKeyObj; + const rawPrivate = { + key: privKeyObj.export({ format: 'raw-private' }), + format: 'raw-private', + asymmetricKeyType, + }; + const rawPublic = { + key: pubKeyObj.export({ format: 'raw-public' }), + format: 'raw-public', + asymmetricKeyType, + }; + + const sig = crypto.sign(algo, data, rawPrivate); + assert.strictEqual(sig.length, pair.sigLen); + + assert.strictEqual(crypto.verify(algo, data, rawPrivate, sig), true); + assert.strictEqual(crypto.verify(algo, data, rawPublic, sig), true); + } + { const data = Buffer.from('Hello world'); const otherData = Buffer.from('Goodbye world'); diff --git a/test/parallel/test-webcrypto-export-import-cfrg.js b/test/parallel/test-webcrypto-export-import-cfrg.js index 9cfc6e9e4ecf5a..c6e3509a4362bc 100644 --- a/test/parallel/test-webcrypto-export-import-cfrg.js +++ b/test/parallel/test-webcrypto-export-import-cfrg.js @@ -389,9 +389,10 @@ async function testImportJwk({ name, publicUsages, privateUsages }, extractable) async function testImportRaw({ name, publicUsages }) { const jwk = keyData[name].jwk; + const rawKeyData = Buffer.from(jwk.x, 'base64url'); const publicKey = await subtle.importKey( 'raw', - Buffer.from(jwk.x, 'base64url'), + rawKeyData, { name }, true, publicUsages); @@ -400,6 +401,10 @@ async function testImportRaw({ name, publicUsages }) { assert.strictEqual(publicKey.algorithm.name, name); assert.strictEqual(publicKey.algorithm, publicKey.algorithm); assert.strictEqual(publicKey.usages, publicKey.usages); + + // Test raw export round-trip + const exported = await subtle.exportKey('raw', publicKey); + assert.deepStrictEqual(Buffer.from(exported), rawKeyData); } (async function() { diff --git a/test/parallel/test-webcrypto-export-import-ec.js b/test/parallel/test-webcrypto-export-import-ec.js index a2e9df73bc2926..98d0d7d3342ccb 100644 --- a/test/parallel/test-webcrypto-export-import-ec.js +++ b/test/parallel/test-webcrypto-export-import-ec.js @@ -356,14 +356,16 @@ async function testImportJwk( async function testImportRaw({ name, publicUsages }, namedCurve) { const jwk = keyData[namedCurve].jwk; + const uncompressedRaw = Buffer.concat([ + Buffer.alloc(1, 0x04), + Buffer.from(jwk.x, 'base64url'), + Buffer.from(jwk.y, 'base64url'), + ]); + const [publicKey] = await Promise.all([ subtle.importKey( 'raw', - Buffer.concat([ - Buffer.alloc(1, 0x04), - Buffer.from(jwk.x, 'base64url'), - Buffer.from(jwk.y, 'base64url'), - ]), + uncompressedRaw, { name, namedCurve }, true, publicUsages), subtle.importKey( @@ -382,6 +384,10 @@ async function testImportRaw({ name, publicUsages }, namedCurve) { assert.strictEqual(publicKey.algorithm.namedCurve, namedCurve); assert.strictEqual(publicKey.algorithm, publicKey.algorithm); assert.strictEqual(publicKey.usages, publicKey.usages); + + // Test raw export round-trip (always uncompressed) + const exported = await subtle.exportKey('raw', publicKey); + assert.deepStrictEqual(Buffer.from(exported), uncompressedRaw); } (async function() { diff --git a/test/parallel/test-webcrypto-export-import-ml-dsa.js b/test/parallel/test-webcrypto-export-import-ml-dsa.js index 8fd8abb6f44d76..ebd61c67f55d32 100644 --- a/test/parallel/test-webcrypto-export-import-ml-dsa.js +++ b/test/parallel/test-webcrypto-export-import-ml-dsa.js @@ -515,8 +515,6 @@ async function testImportRawSeed({ name, privateUsages }, extractable) { const key = keyObject.toCryptoKey({ name }, true, privateUsages); await assert.rejects(subtle.exportKey('pkcs8', key), (err) => { assert.strictEqual(err.name, 'OperationError'); - assert.strictEqual(err.cause.code, 'ERR_CRYPTO_OPERATION_FAILED'); - assert.strictEqual(err.cause.message, 'Failed to get raw seed'); return true; }); } diff --git a/test/parallel/test-webcrypto-export-import-ml-kem.js b/test/parallel/test-webcrypto-export-import-ml-kem.js index 698de3cc1bcb97..50f3444ce87899 100644 --- a/test/parallel/test-webcrypto-export-import-ml-kem.js +++ b/test/parallel/test-webcrypto-export-import-ml-kem.js @@ -317,8 +317,6 @@ async function testImportRawSeed({ name, privateUsages }, extractable) { const key = keyObject.toCryptoKey({ name }, true, privateUsages); await assert.rejects(subtle.exportKey('pkcs8', key), (err) => { assert.strictEqual(err.name, 'OperationError'); - assert.strictEqual(err.cause.code, 'ERR_CRYPTO_OPERATION_FAILED'); - assert.strictEqual(err.cause.message, 'Failed to get raw seed'); return true; }); } From 1baafcc882e8056df7d17ce962517a6bd84c8a54 Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Sun, 22 Mar 2026 00:28:21 +0000 Subject: [PATCH 3/6] test: update WPT resources, interfaces and WebCryptoAPI PR-URL: https://github.com/nodejs/node/pull/62389 Reviewed-By: Colin Ihrig Reviewed-By: Luigi Pinca --- test/fixtures/wpt/README.md | 6 +- .../WebCryptoAPI/derive_bits_keys/argon2.js | 18 - .../argon2.tentative.https.any.js | 1 + .../derive_bits_keys/argon2_vectors.js | 1 - .../derive_bits_keys/cfrg_curves_bits.js | 33 - .../cfrg_curves_bits_curve25519.https.any.js | 1 + ...urves_bits_curve448.tentative.https.any.js | 1 + .../derive_bits_keys/cfrg_curves_keys.js | 33 - .../cfrg_curves_keys_curve25519.https.any.js | 1 + ...urves_keys_curve448.tentative.https.any.js | 1 + .../derived_bits_length.https.any.js | 1 + .../derive_bits_keys/ecdh_bits.https.any.js | 1 + .../derive_bits_keys/ecdh_bits.js | 33 - .../derive_bits_keys/ecdh_keys.https.any.js | 1 + .../derive_bits_keys/ecdh_keys.js | 33 - .../derive_bits_keys/hkdf.https.any.js | 1 + .../wpt/WebCryptoAPI/derive_bits_keys/hkdf.js | 17 - .../derive_bits_keys/pbkdf2.https.any.js | 1 + .../WebCryptoAPI/derive_bits_keys/pbkdf2.js | 17 - .../digest/cshake.tentative.https.any.js | 15 +- .../WebCryptoAPI/digest/digest.https.any.js | 31 +- .../kangarootwelve.tentative.https.any.js | 324 ++++++++ .../digest/sha3.tentative.https.any.js | 15 +- .../digest/turboshake.tentative.https.any.js | 297 ++++++++ .../encap_decap_bits.tentative.https.any.js | 41 +- .../encap_decap_keys.tentative.https.any.js | 614 ++++++++------- .../encap_decap/ml_kem_encap_decap.js | 410 ---------- .../wpt/WebCryptoAPI/encrypt_decrypt/aes.js | 29 - .../encrypt_decrypt/aes_cbc.https.any.js | 1 + .../encrypt_decrypt/aes_ctr.https.any.js | 1 + .../encrypt_decrypt/aes_gcm.https.any.js | 1 + .../aes_gcm_256_iv.https.any.js | 1 + .../encrypt_decrypt/aes_gcm_vectors.js | 1 - .../aes_ocb.tentative.https.any.js | 1 + .../chacha20_poly1305.tentative.https.any.js | 15 +- .../wpt/WebCryptoAPI/encrypt_decrypt/rsa.js | 42 +- .../encrypt_decrypt/rsa_oaep.https.any.js | 1 + .../getPublicKey.tentative.https.any.js | 12 - .../idlharness.tentative.https.any.js | 17 + .../import_export/ML-DSA_importKey.js | 92 --- .../import_export/ML-KEM_importKey.js | 92 --- .../import_export/ec_importKey.https.any.js | 84 -- .../import_export/okp_importKey.js | 84 -- .../import_export/rsa_importKey.https.any.js | 84 -- .../import_export/symmetric_importKey.js | 84 -- .../sign_verify/ecdsa.https.any.js | 1 + .../wpt/WebCryptoAPI/sign_verify/ecdsa.js | 29 - .../wpt/WebCryptoAPI/sign_verify/eddsa.js | 12 - .../sign_verify/eddsa_curve25519.https.any.js | 1 + .../eddsa_curve448.tentative.https.any.js | 1 + .../eddsa_small_order_points.https.any.js | 1 + .../sign_verify/hmac.https.any.js | 1 + .../wpt/WebCryptoAPI/sign_verify/hmac.js | 29 - .../wpt/WebCryptoAPI/sign_verify/kmac.js | 29 - .../sign_verify/kmac.tentative.https.any.js | 1 + .../wpt/WebCryptoAPI/sign_verify/mldsa.js | 29 - .../sign_verify/mldsa.tentative.https.any.js | 1 + .../wpt/WebCryptoAPI/sign_verify/rsa.js | 29 - .../sign_verify/rsa_pkcs.https.any.js | 1 + .../sign_verify/rsa_pss.https.any.js | 1 + .../fixtures/wpt/WebCryptoAPI/util/helpers.js | 93 ++- .../wrapKey_unwrapKey.https.any.js | 73 -- test/fixtures/wpt/interfaces/dom.idl | 10 +- test/fixtures/wpt/interfaces/html.idl | 717 ++++++++++-------- .../wpt/interfaces/performance-timeline.idl | 4 +- .../wpt/interfaces/resource-timing.idl | 8 +- test/fixtures/wpt/interfaces/streams.idl | 2 +- test/fixtures/wpt/interfaces/web-locks.idl | 9 +- .../wpt/interfaces/webcrypto-modern-algos.idl | 118 +++ .../interfaces/webcrypto-secure-curves.idl | 8 + test/fixtures/wpt/interfaces/webcrypto.idl | 6 +- test/fixtures/wpt/interfaces/webidl.idl | 13 + test/fixtures/wpt/resources/example.pdf | Bin 0 -> 618 bytes test/fixtures/wpt/resources/idlharness.js | 2 +- .../wpt/resources/testdriver-actions.js | 14 +- test/fixtures/wpt/resources/testdriver.js | 269 ++++++- test/fixtures/wpt/resources/testharness.js | 2 +- .../wpt/resources/web-extensions-helper.js | 40 + .../wpt/resources/webidl2/lib/VERSION.md | 2 +- .../wpt/resources/webidl2/lib/webidl2.js | 588 +++++++++----- test/fixtures/wpt/versions.json | 6 +- test/wpt/status/WebCryptoAPI.cjs | 6 + test/wpt/status/resource-timing.json | 12 +- 83 files changed, 2316 insertions(+), 2442 deletions(-) create mode 100644 test/fixtures/wpt/WebCryptoAPI/digest/kangarootwelve.tentative.https.any.js create mode 100644 test/fixtures/wpt/WebCryptoAPI/digest/turboshake.tentative.https.any.js delete mode 100644 test/fixtures/wpt/WebCryptoAPI/encap_decap/ml_kem_encap_decap.js create mode 100644 test/fixtures/wpt/WebCryptoAPI/idlharness.tentative.https.any.js create mode 100644 test/fixtures/wpt/interfaces/webcrypto-modern-algos.idl create mode 100644 test/fixtures/wpt/interfaces/webcrypto-secure-curves.idl create mode 100644 test/fixtures/wpt/resources/example.pdf create mode 100644 test/fixtures/wpt/resources/web-extensions-helper.js diff --git a/test/fixtures/wpt/README.md b/test/fixtures/wpt/README.md index cba3d03b9038fd..a577304b0d35a0 100644 --- a/test/fixtures/wpt/README.md +++ b/test/fixtures/wpt/README.md @@ -23,10 +23,10 @@ Last update: - html/webappapis/microtask-queuing: https://github.com/web-platform-tests/wpt/tree/2c5c3c4c27/html/webappapis/microtask-queuing - html/webappapis/structured-clone: https://github.com/web-platform-tests/wpt/tree/47d3fb280c/html/webappapis/structured-clone - html/webappapis/timers: https://github.com/web-platform-tests/wpt/tree/5873f2d8f1/html/webappapis/timers -- interfaces: https://github.com/web-platform-tests/wpt/tree/e1b27be06b/interfaces +- interfaces: https://github.com/web-platform-tests/wpt/tree/a8392bd021/interfaces - performance-timeline: https://github.com/web-platform-tests/wpt/tree/94caab7038/performance-timeline - resource-timing: https://github.com/web-platform-tests/wpt/tree/22d38586d0/resource-timing -- resources: https://github.com/web-platform-tests/wpt/tree/1d2c5fb36a/resources +- resources: https://github.com/web-platform-tests/wpt/tree/6a2f322376/resources - streams: https://github.com/web-platform-tests/wpt/tree/bc9dcbbf1a/streams - url: https://github.com/web-platform-tests/wpt/tree/c928b19ab0/url - urlpattern: https://github.com/web-platform-tests/wpt/tree/a2e15ad405/urlpattern @@ -34,7 +34,7 @@ Last update: - wasm/jsapi: https://github.com/web-platform-tests/wpt/tree/cde25e7e3c/wasm/jsapi - wasm/webapi: https://github.com/web-platform-tests/wpt/tree/fd1b23eeaa/wasm/webapi - web-locks: https://github.com/web-platform-tests/wpt/tree/10a122a6bc/web-locks -- WebCryptoAPI: https://github.com/web-platform-tests/wpt/tree/0acea989ac/WebCryptoAPI +- WebCryptoAPI: https://github.com/web-platform-tests/wpt/tree/caebe89c3b/WebCryptoAPI - webidl: https://github.com/web-platform-tests/wpt/tree/63ca529a02/webidl - webidl/ecmascript-binding/es-exceptions: https://github.com/web-platform-tests/wpt/tree/2f96fa1996/webidl/ecmascript-binding/es-exceptions - webmessaging/broadcastchannel: https://github.com/web-platform-tests/wpt/tree/6495c91853/webmessaging/broadcastchannel diff --git a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/argon2.js b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/argon2.js index f1609403d623fa..de3b4b5920df27 100644 --- a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/argon2.js +++ b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/argon2.js @@ -51,7 +51,6 @@ function define_tests() { var algorithmName = vector.algorithm; var password = vector.password; - // Key for normal operations promises.push( subtle .importKey('raw-secret', password, algorithmName, false, [ @@ -70,20 +69,3 @@ function define_tests() { }); } } - -function equalBuffers(a, b) { - if (a.byteLength !== b.byteLength) { - return false; - } - - var aView = new Uint8Array(a); - var bView = new Uint8Array(b); - - for (var i = 0; i < aView.length; i++) { - if (aView[i] !== bView[i]) { - return false; - } - } - - return true; -} diff --git a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/argon2.tentative.https.any.js b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/argon2.tentative.https.any.js index 55fb11c995653e..9422c39c958a06 100644 --- a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/argon2.tentative.https.any.js +++ b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/argon2.tentative.https.any.js @@ -1,5 +1,6 @@ // META: title=WebCryptoAPI: deriveBits() Using Argon2 // META: timeout=long +// META: script=../util/helpers.js // META: script=/common/subset-tests.js // META: script=argon2_vectors.js // META: script=argon2.js diff --git a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/argon2_vectors.js b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/argon2_vectors.js index 9b9acda5f42d3a..93d26b5eb96980 100644 --- a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/argon2_vectors.js +++ b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/argon2_vectors.js @@ -2,7 +2,6 @@ function getTestData() { // Test vectors from RFC 9106 // https://www.rfc-editor.org/rfc/rfc9106 - // Test vectors from RFC 9106 var testVectors = [ // Argon2d test vector { diff --git a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_bits.js b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_bits.js index 8ab9db7bf71318..1406e8bf0a1928 100644 --- a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_bits.js +++ b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_bits.js @@ -224,37 +224,4 @@ function define_tests(algorithmName) { .then(function(results) {return {privateKeys: privateKeys, publicKeys: publicKeys, noDeriveBitsKeys: noDeriveBitsKeys, ecdhKeys: ecdhPublicKeys}}); } - // Compares two ArrayBuffer or ArrayBufferView objects. If bitCount is - // omitted, the two values must be the same length and have the same contents - // in every byte. If bitCount is included, only that leading number of bits - // have to match. - function equalBuffers(a, b, bitCount) { - var remainder; - - if (typeof bitCount === "undefined" && a.byteLength !== b.byteLength) { - return false; - } - - var aBytes = new Uint8Array(a); - var bBytes = new Uint8Array(b); - - var length = a.byteLength; - if (typeof bitCount !== "undefined") { - length = Math.floor(bitCount / 8); - } - - for (var i=0; i> (8 - remainder) === bBytes[length] >> (8 - remainder); - } - - return true; - } - } diff --git a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_bits_curve25519.https.any.js b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_bits_curve25519.https.any.js index 866192e0193bc1..5684d7624076c7 100644 --- a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_bits_curve25519.https.any.js +++ b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_bits_curve25519.https.any.js @@ -1,4 +1,5 @@ // META: title=WebCryptoAPI: deriveKey() Using ECDH with CFRG Elliptic Curves +// META: script=../util/helpers.js // META: script=cfrg_curves_bits_fixtures.js // META: script=cfrg_curves_bits.js diff --git a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_bits_curve448.tentative.https.any.js b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_bits_curve448.tentative.https.any.js index 32485c68107e5c..5e482ef0b9d804 100644 --- a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_bits_curve448.tentative.https.any.js +++ b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_bits_curve448.tentative.https.any.js @@ -1,4 +1,5 @@ // META: title=WebCryptoAPI: deriveKey() Using ECDH with CFRG Elliptic Curves +// META: script=../util/helpers.js // META: script=cfrg_curves_bits_fixtures.js // META: script=cfrg_curves_bits.js diff --git a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_keys.js b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_keys.js index 62f9e00aa33846..cefc45ac692903 100644 --- a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_keys.js +++ b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_keys.js @@ -221,37 +221,4 @@ function define_tests(algorithmName) { .then(function(results) {return {privateKeys: privateKeys, publicKeys: publicKeys, noDeriveKeyKeys: noDeriveKeyKeys, ecdhKeys: ecdhPublicKeys}}); } - // Compares two ArrayBuffer or ArrayBufferView objects. If bitCount is - // omitted, the two values must be the same length and have the same contents - // in every byte. If bitCount is included, only that leading number of bits - // have to match. - function equalBuffers(a, b, bitCount) { - var remainder; - - if (typeof bitCount === "undefined" && a.byteLength !== b.byteLength) { - return false; - } - - var aBytes = new Uint8Array(a); - var bBytes = new Uint8Array(b); - - var length = a.byteLength; - if (typeof bitCount !== "undefined") { - length = Math.floor(bitCount / 8); - } - - for (var i=0; i> (8 - remainder) === bBytes[length] >> (8 - remainder); - } - - return true; - } - } diff --git a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_keys_curve25519.https.any.js b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_keys_curve25519.https.any.js index 91390ba5c2a17a..8bcc201d4e95ec 100644 --- a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_keys_curve25519.https.any.js +++ b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_keys_curve25519.https.any.js @@ -1,4 +1,5 @@ // META: title=WebCryptoAPI: deriveKey() Using ECDH with CFRG Elliptic Curves +// META: script=../util/helpers.js // META: script=cfrg_curves_bits_fixtures.js // META: script=cfrg_curves_keys.js diff --git a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_keys_curve448.tentative.https.any.js b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_keys_curve448.tentative.https.any.js index b34e366376a70f..0ed3954ac200b5 100644 --- a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_keys_curve448.tentative.https.any.js +++ b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_keys_curve448.tentative.https.any.js @@ -1,4 +1,5 @@ // META: title=WebCryptoAPI: deriveKey() Using ECDH with CFRG Elliptic Curves +// META: script=../util/helpers.js // META: script=cfrg_curves_bits_fixtures.js // META: script=cfrg_curves_keys.js diff --git a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/derived_bits_length.https.any.js b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/derived_bits_length.https.any.js index 0aee2e3c172d30..862945c6a316c6 100644 --- a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/derived_bits_length.https.any.js +++ b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/derived_bits_length.https.any.js @@ -1,4 +1,5 @@ // META: title=WebCryptoAPI: deriveBits() tests for the 'length' parameter +// META: script=../util/helpers.js // META: script=derived_bits_length.js // META: script=derived_bits_length_vectors.js // META: script=derived_bits_length_testcases.js diff --git a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/ecdh_bits.https.any.js b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/ecdh_bits.https.any.js index 37e3eb4324200c..58a0cecd5efed6 100644 --- a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/ecdh_bits.https.any.js +++ b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/ecdh_bits.https.any.js @@ -1,4 +1,5 @@ // META: title=WebCryptoAPI: deriveBits() Using ECDH +// META: script=../util/helpers.js // META: script=ecdh_bits.js // Define subtests from a `promise_test` to ensure the harness does not diff --git a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/ecdh_bits.js b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/ecdh_bits.js index 36b29c20a282ab..8e79909020d398 100644 --- a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/ecdh_bits.js +++ b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/ecdh_bits.js @@ -230,37 +230,4 @@ function define_tests() { .then(function(results) {return {privateKeys: privateKeys, publicKeys: publicKeys, ecdsaKeyPairs: ecdsaKeyPairs, noDeriveBitsKeys: noDeriveBitsKeys}}); } - // Compares two ArrayBuffer or ArrayBufferView objects. If bitCount is - // omitted, the two values must be the same length and have the same contents - // in every byte. If bitCount is included, only that leading number of bits - // have to match. - function equalBuffers(a, b, bitCount) { - var remainder; - - if (typeof bitCount === "undefined" && a.byteLength !== b.byteLength) { - return false; - } - - var aBytes = new Uint8Array(a); - var bBytes = new Uint8Array(b); - - var length = a.byteLength; - if (typeof bitCount !== "undefined") { - length = Math.floor(bitCount / 8); - } - - for (var i=0; i> (8 - remainder) === bBytes[length] >> (8 - remainder); - } - - return true; - } - } diff --git a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/ecdh_keys.https.any.js b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/ecdh_keys.https.any.js index d8235fce5a7412..6464dacfe3aa81 100644 --- a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/ecdh_keys.https.any.js +++ b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/ecdh_keys.https.any.js @@ -1,4 +1,5 @@ // META: title=WebCryptoAPI: deriveKey() Using ECDH +// META: script=../util/helpers.js // META: script=ecdh_keys.js // Define subtests from a `promise_test` to ensure the harness does not diff --git a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/ecdh_keys.js b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/ecdh_keys.js index fce76f185530ac..8c3d2aeb5a4976 100644 --- a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/ecdh_keys.js +++ b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/ecdh_keys.js @@ -209,37 +209,4 @@ function define_tests() { .then(function(results) {return {privateKeys: privateKeys, publicKeys: publicKeys, ecdsaKeyPairs: ecdsaKeyPairs, noDeriveKeyKeys: noDeriveKeyKeys}}); } - // Compares two ArrayBuffer or ArrayBufferView objects. If bitCount is - // omitted, the two values must be the same length and have the same contents - // in every byte. If bitCount is included, only that leading number of bits - // have to match. - function equalBuffers(a, b, bitCount) { - var remainder; - - if (typeof bitCount === "undefined" && a.byteLength !== b.byteLength) { - return false; - } - - var aBytes = new Uint8Array(a); - var bBytes = new Uint8Array(b); - - var length = a.byteLength; - if (typeof bitCount !== "undefined") { - length = Math.floor(bitCount / 8); - } - - for (var i=0; i> (8 - remainder) === bBytes[length] >> (8 - remainder); - } - - return true; - } - } diff --git a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/hkdf.https.any.js b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/hkdf.https.any.js index 02492c3741c7d1..3879ddb14b903a 100644 --- a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/hkdf.https.any.js +++ b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/hkdf.https.any.js @@ -3,6 +3,7 @@ // META: variant=?1001-2000 // META: variant=?2001-3000 // META: variant=?3001-last +// META: script=../util/helpers.js // META: script=/common/subset-tests.js // META: script=hkdf_vectors.js // META: script=hkdf.js diff --git a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/hkdf.js b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/hkdf.js index 0384f88ec73e43..08e8c0c8974617 100644 --- a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/hkdf.js +++ b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/hkdf.js @@ -275,21 +275,4 @@ function define_tests() { }); } - function equalBuffers(a, b) { - if (a.byteLength !== b.byteLength) { - return false; - } - - var aBytes = new Uint8Array(a); - var bBytes = new Uint8Array(b); - - for (var i=0; i 0) { + promise_test(function (test) { + var buffer = new Uint8Array(input); + // Alter the buffer before calling digest + buffer[0] = ~buffer[0]; + return subtle + .digest({ + get name() { + // Alter the buffer back while calling digest + buffer[0] = input[0]; + return alg; + }, + outputLength: outputLength, + customization: customization, + }, buffer) + .then(function (result) { + assert_true( + equalBuffers(result, hexToBytes(expected)), + 'digest matches expected' + ); + }); + }, label + ' and altered buffer during call'); + + promise_test(function (test) { + var buffer = new Uint8Array(input); + var promise = subtle + .digest(algorithmParams, buffer) + .then(function (result) { + assert_true( + equalBuffers(result, hexToBytes(expected)), + 'digest matches expected' + ); + }); + // Alter the buffer after calling digest + buffer[0] = ~buffer[0]; + return promise; + }, label + ' and altered buffer after call'); + + promise_test(function (test) { + var buffer = new Uint8Array(input); + return subtle + .digest({ + get name() { + // Transfer the buffer while calling digest + buffer.buffer.transfer(); + return alg; + }, + outputLength: outputLength, + customization: customization, + }, buffer) + .then(function (result) { + if (customizationEqual(emptyDataVector, customization) && outputLengthLessOrEqual(emptyDataVector, outputLength)) { + assert_true( + equalBuffers(result, Uint8Array.fromHex(emptyDataVector[2]).subarray(0, outputLength / 8)), + 'digest on transferred buffer should match result for empty buffer' + ); + } else { + assert_equals(result.byteLength, outputLength / 8, + 'digest on transferred buffer should have correct output length'); + } + }); + }, label + ' and transferred buffer during call'); + + promise_test(function (test) { + var buffer = new Uint8Array(input); + var promise = subtle + .digest(algorithmParams, buffer) + .then(function (result) { + assert_true( + equalBuffers(result, hexToBytes(expected)), + 'digest matches expected' + ); + }); + // Transfer the buffer after calling digest + buffer.buffer.transfer(); + return promise; + }, label + ' and transferred buffer after call'); + } + }); +}); diff --git a/test/fixtures/wpt/WebCryptoAPI/digest/sha3.tentative.https.any.js b/test/fixtures/wpt/WebCryptoAPI/digest/sha3.tentative.https.any.js index 4ae99791b8c95f..f9f38eadc2c39a 100644 --- a/test/fixtures/wpt/WebCryptoAPI/digest/sha3.tentative.https.any.js +++ b/test/fixtures/wpt/WebCryptoAPI/digest/sha3.tentative.https.any.js @@ -1,4 +1,5 @@ // META: title=WebCryptoAPI: digest() SHA-3 algorithms +// META: script=../util/helpers.js // META: timeout=long var subtle = crypto.subtle; // Change to test prefixed implementations @@ -176,17 +177,3 @@ Object.keys(sourceData).forEach(function (size) { } }); }); - -function equalBuffers(a, b) { - if (a.byteLength !== b.byteLength) { - return false; - } - var aBytes = new Uint8Array(a); - var bBytes = new Uint8Array(b); - for (var i = 0; i < a.byteLength; i++) { - if (aBytes[i] !== bBytes[i]) { - return false; - } - } - return true; -} diff --git a/test/fixtures/wpt/WebCryptoAPI/digest/turboshake.tentative.https.any.js b/test/fixtures/wpt/WebCryptoAPI/digest/turboshake.tentative.https.any.js new file mode 100644 index 00000000000000..243931cd119802 --- /dev/null +++ b/test/fixtures/wpt/WebCryptoAPI/digest/turboshake.tentative.https.any.js @@ -0,0 +1,297 @@ +// META: title=WebCryptoAPI: digest() TurboSHAKE algorithms +// META: script=../util/helpers.js +// META: timeout=long + +var subtle = crypto.subtle; // Change to test prefixed implementations + +// Generates a Uint8Array of length n by repeating the pattern 00 01 02 .. F9 FA. +function ptn(n) { + var buf = new Uint8Array(n); + for (var i = 0; i < n; i++) + buf[i] = i % 251; + return buf; +} + +function hexToBytes(hex) { + var bytes = new Uint8Array(hex.length / 2); + for (var i = 0; i < hex.length; i += 2) + bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16); + return bytes; +} + +// RFC 9861 Section 5 test vectors +// [input, outputLengthBits, expected hex(, domainSeparation)] +var turboSHAKE128Vectors = [ + [new Uint8Array(0), 256, + '1e415f1c5983aff2169217277d17bb53' + + '8cd945a397ddec541f1ce41af2c1b74c'], + [new Uint8Array(0), 512, + '1e415f1c5983aff2169217277d17bb53' + + '8cd945a397ddec541f1ce41af2c1b74c' + + '3e8ccae2a4dae56c84a04c2385c03c15' + + 'e8193bdf58737363321691c05462c8df'], + [ptn(1), 256, + '55cedd6f60af7bb29a4042ae832ef3f5' + + '8db7299f893ebb9247247d856958daa9'], + [ptn(17), 256, + '9c97d036a3bac819db70ede0ca554ec6' + + 'e4c2a1a4ffbfd9ec269ca6a111161233'], + [ptn(Math.pow(17, 2)), 256, + '96c77c279e0126f7fc07c9b07f5cdae1' + + 'e0be60bdbe10620040e75d7223a624d2'], + [ptn(Math.pow(17, 3)), 256, + 'd4976eb56bcf118520582b709f73e1d6' + + '853e001fdaf80e1b13e0d0599d5fb372'], + [ptn(Math.pow(17, 4)), 256, + 'da67c7039e98bf530cf7a37830c6664e' + + '14cbab7f540f58403b1b82951318ee5c'], + [ptn(Math.pow(17, 5)), 256, + 'b97a906fbf83ef7c812517abf3b2d0ae' + + 'a0c4f60318ce11cf103925127f59eecd'], + [ptn(Math.pow(17, 6)), 256, + '35cd494adeded2f25239af09a7b8ef0c' + + '4d1ca4fe2d1ac370fa63216fe7b4c2b1'], + [new Uint8Array([0xff, 0xff, 0xff]), 256, + 'bf323f940494e88ee1c540fe660be8a0' + + 'c93f43d15ec006998462fa994eed5dab', 0x01], + [new Uint8Array([0xff]), 256, + '8ec9c66465ed0d4a6c35d13506718d68' + + '7a25cb05c74cca1e42501abd83874a67', 0x06], + [new Uint8Array([0xff, 0xff, 0xff]), 256, + 'b658576001cad9b1e5f399a9f77723bb' + + 'a05458042d68206f7252682dba3663ed', 0x07], + [new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), 256, + '8deeaa1aec47ccee569f659c21dfa8e1' + + '12db3cee37b18178b2acd805b799cc37', 0x0b], + [new Uint8Array([0xff]), 256, + '553122e2135e363c3292bed2c6421fa2' + + '32bab03daa07c7d6636603286506325b', 0x30], + [new Uint8Array([0xff, 0xff, 0xff]), 256, + '16274cc656d44cefd422395d0f9053bd' + + 'a6d28e122aba15c765e5ad0e6eaf26f9', 0x7f], +]; + +var turboSHAKE256Vectors = [ + [new Uint8Array(0), 512, + '367a329dafea871c7802ec67f905ae13' + + 'c57695dc2c6663c61035f59a18f8e7db' + + '11edc0e12e91ea60eb6b32df06dd7f00' + + '2fbafabb6e13ec1cc20d995547600db0'], + [ptn(1), 512, + '3e1712f928f8eaf1054632b2aa0a246e' + + 'd8b0c378728f60bc970410155c28820e' + + '90cc90d8a3006aa2372c5c5ea176b068' + + '2bf22bae7467ac94f74d43d39b0482e2'], + [ptn(17), 512, + 'b3bab0300e6a191fbe61379398359235' + + '78794ea54843f5011090fa2f3780a9e5' + + 'cb22c59d78b40a0fbff9e672c0fbe097' + + '0bd2c845091c6044d687054da5d8e9c7'], + [ptn(Math.pow(17, 2)), 512, + '66b810db8e90780424c0847372fdc957' + + '10882fde31c6df75beb9d4cd9305cfca' + + 'e35e7b83e8b7e6eb4b78605880116316' + + 'fe2c078a09b94ad7b8213c0a738b65c0'], + [ptn(Math.pow(17, 3)), 512, + 'c74ebc919a5b3b0dd1228185ba02d29e' + + 'f442d69d3d4276a93efe0bf9a16a7dc0' + + 'cd4eabadab8cd7a5edd96695f5d360ab' + + 'e09e2c6511a3ec397da3b76b9e1674fb'], + [ptn(Math.pow(17, 4)), 512, + '02cc3a8897e6f4f6ccb6fd46631b1f52' + + '07b66c6de9c7b55b2d1a23134a170afd' + + 'ac234eaba9a77cff88c1f020b7372461' + + '8c5687b362c430b248cd38647f848a1d'], + [ptn(Math.pow(17, 5)), 512, + 'add53b06543e584b5823f626996aee50' + + 'fe45ed15f20243a7165485acb4aa76b4' + + 'ffda75cedf6d8cdc95c332bd56f4b986' + + 'b58bb17d1778bfc1b1a97545cdf4ec9f'], + [ptn(Math.pow(17, 6)), 512, + '9e11bc59c24e73993c1484ec66358ef7' + + '1db74aefd84e123f7800ba9c4853e02c' + + 'fe701d9e6bb765a304f0dc34a4ee3ba8' + + '2c410f0da70e86bfbd90ea877c2d6104'], + [new Uint8Array([0xff, 0xff, 0xff]), 512, + 'd21c6fbbf587fa2282f29aea620175fb' + + '0257413af78a0b1b2a87419ce031d933' + + 'ae7a4d383327a8a17641a34f8a1d1003' + + 'ad7da6b72dba84bb62fef28f62f12424', 0x01], + [new Uint8Array([0xff]), 512, + '738d7b4e37d18b7f22ad1b5313e357e3' + + 'dd7d07056a26a303c433fa3533455280' + + 'f4f5a7d4f700efb437fe6d281405e07b' + + 'e32a0a972e22e63adc1b090daefe004b', 0x06], + [new Uint8Array([0xff, 0xff, 0xff]), 512, + '18b3b5b7061c2e67c1753a00e6ad7ed7' + + 'ba1c906cf93efb7092eaf27fbeebb755' + + 'ae6e292493c110e48d260028492b8e09' + + 'b5500612b8f2578985ded5357d00ec67', 0x07], + [new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), 512, + 'bb36764951ec97e9d85f7ee9a67a7718' + + 'fc005cf42556be79ce12c0bde50e5736' + + 'd6632b0d0dfb202d1bbb8ffe3dd74cb0' + + '0834fa756cb03471bab13a1e2c16b3c0', 0x0b], + [new Uint8Array([0xff]), 512, + 'f3fe12873d34bcbb2e608779d6b70e7f' + + '86bec7e90bf113cbd4fdd0c4e2f4625e' + + '148dd7ee1a52776cf77f240514d9ccfc' + + '3b5ddab8ee255e39ee389072962c111a', 0x30], + [new Uint8Array([0xff, 0xff, 0xff]), 512, + 'abe569c1f77ec340f02705e7d37c9ab7' + + 'e155516e4a6a150021d70b6fac0bb40c' + + '069f9a9828a0d575cd99f9bae435ab1a' + + 'cf7ed9110ba97ce0388d074bac768776', 0x7f], +]; + +// Large output tests: verify last 32 bytes of extended output +var largeOutputTests = [ + // [algorithm, outputLengthBits, lastNBytes, expectedLastBytes] + ['TurboSHAKE128', 10032 * 8, 32, + 'a3b9b0385900ce761f22aed548e754da' + + '10a5242d62e8c658e3f3a923a7555607'], + ['TurboSHAKE256', 10032 * 8, 32, + 'abefa11630c661269249742685ec082f' + + '207265dccf2f43534e9c61ba0c9d1d75'], +]; + +largeOutputTests.forEach(function (entry) { + var alg = entry[0]; + var outputLength = entry[1]; + var lastN = entry[2]; + var expected = entry[3]; + + promise_test(function (test) { + return subtle + .digest({ name: alg, outputLength: outputLength }, new Uint8Array(0)) + .then(function (result) { + var full = new Uint8Array(result); + var last = full.slice(full.length - lastN); + assert_true( + equalBuffers(last.buffer, hexToBytes(expected)), + 'last ' + lastN + ' bytes of digest match expected' + ); + }); + }, alg + ' with ' + outputLength + ' bit output, verify last ' + lastN + ' bytes'); +}); + +function domainSeparationEqual(emptyDataVector, domainSeparation) { + return (domainSeparation ?? 0x1f) === (emptyDataVector[3] ?? 0x1f); +} + +function outputLengthLessOrEqual(emptyDataVector, outputLength) { + return outputLength <= emptyDataVector[1]; +} + +var allVectors = { + TurboSHAKE128: turboSHAKE128Vectors, + TurboSHAKE256: turboSHAKE256Vectors, +}; + +Object.keys(allVectors).forEach(function (alg) { + var emptyDataVector = allVectors[alg][0]; + allVectors[alg].forEach(function (vector, i) { + var input = vector[0]; + var outputLength = vector[1]; + var expected = vector[2]; + var domainSeparation = vector[3]; + + var algorithmParams = { name: alg, outputLength: outputLength }; + if (domainSeparation !== undefined) + algorithmParams.domainSeparation = domainSeparation; + + var label = alg + ' vector #' + (i + 1) + + ' (' + outputLength + ' bit output, ' + input.length + ' byte input' + + (domainSeparation !== undefined ? ', D=0x' + domainSeparation.toString(16) : '') + ')'; + + promise_test(function (test) { + return subtle + .digest(algorithmParams, input) + .then(function (result) { + assert_true( + equalBuffers(result, hexToBytes(expected)), + 'digest matches expected' + ); + }); + }, label); + + if (input.length > 0) { + promise_test(function (test) { + var buffer = new Uint8Array(input); + // Alter the buffer before calling digest + buffer[0] = ~buffer[0]; + return subtle + .digest({ + get name() { + // Alter the buffer back while calling digest + buffer[0] = input[0]; + return alg; + }, + outputLength: outputLength, + domainSeparation: domainSeparation, + }, buffer) + .then(function (result) { + assert_true( + equalBuffers(result, hexToBytes(expected)), + 'digest matches expected' + ); + }); + }, label + ' and altered buffer during call'); + + promise_test(function (test) { + var buffer = new Uint8Array(input); + var promise = subtle + .digest(algorithmParams, buffer) + .then(function (result) { + assert_true( + equalBuffers(result, hexToBytes(expected)), + 'digest matches expected' + ); + }); + // Alter the buffer after calling digest + buffer[0] = ~buffer[0]; + return promise; + }, label + ' and altered buffer after call'); + + promise_test(function (test) { + var buffer = new Uint8Array(input); + return subtle + .digest({ + get name() { + // Transfer the buffer while calling digest + buffer.buffer.transfer(); + return alg; + }, + outputLength: outputLength, + domainSeparation: domainSeparation, + }, buffer) + .then(function (result) { + if (domainSeparationEqual(emptyDataVector, domainSeparation) && outputLengthLessOrEqual(emptyDataVector, outputLength)) { + assert_true( + equalBuffers(result, Uint8Array.fromHex(emptyDataVector[2]).subarray(0, outputLength / 8)), + 'digest on transferred buffer should match result for empty buffer' + ); + } else { + assert_equals(result.byteLength, outputLength / 8, + 'digest on transferred buffer should have correct output length'); + } + }); + }, label + ' and transferred buffer during call'); + + promise_test(function (test) { + var buffer = new Uint8Array(input); + var promise = subtle + .digest(algorithmParams, buffer) + .then(function (result) { + assert_true( + equalBuffers(result, hexToBytes(expected)), + 'digest matches expected' + ); + }); + // Transfer the buffer after calling digest + buffer.buffer.transfer(); + return promise; + }, label + ' and transferred buffer after call'); + } + }); +}); diff --git a/test/fixtures/wpt/WebCryptoAPI/encap_decap/encap_decap_bits.tentative.https.any.js b/test/fixtures/wpt/WebCryptoAPI/encap_decap/encap_decap_bits.tentative.https.any.js index ab112e4d497676..5a669753cd2701 100644 --- a/test/fixtures/wpt/WebCryptoAPI/encap_decap/encap_decap_bits.tentative.https.any.js +++ b/test/fixtures/wpt/WebCryptoAPI/encap_decap/encap_decap_bits.tentative.https.any.js @@ -73,7 +73,7 @@ function define_bits_tests() { ); }, algorithmName + ' encapsulateBits basic functionality'); - // Test decapsulateBits operation + // Test encapsulateBits/decapsulateBits round-trip compatibility promise_test(async function (test) { // Generate a key pair for testing var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ @@ -109,30 +109,6 @@ function define_bits_tests() { equalBuffers(decapsulatedBits, encapsulatedBits.sharedKey), 'Decapsulated shared secret should match original' ); - }, algorithmName + ' decapsulateBits basic functionality'); - - // Test round-trip compatibility - promise_test(async function (test) { - var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ - 'encapsulateBits', - 'decapsulateBits', - ]); - - var encapsulatedBits = await subtle.encapsulateBits( - { name: algorithmName }, - keyPair.publicKey - ); - - var decapsulatedBits = await subtle.decapsulateBits( - { name: algorithmName }, - keyPair.privateKey, - encapsulatedBits.ciphertext - ); - - assert_true( - equalBuffers(encapsulatedBits.sharedKey, decapsulatedBits), - 'Encapsulated and decapsulated shared secrets should match' - ); }, algorithmName + ' encapsulateBits/decapsulateBits round-trip compatibility'); @@ -175,19 +151,4 @@ function define_bits_tests() { }); } -// Helper function to compare two ArrayBuffers -function equalBuffers(a, b) { - if (a.byteLength !== b.byteLength) { - return false; - } - var aBytes = new Uint8Array(a); - var bBytes = new Uint8Array(b); - for (var i = 0; i < a.byteLength; i++) { - if (aBytes[i] !== bBytes[i]) { - return false; - } - } - return true; -} - define_bits_tests(); diff --git a/test/fixtures/wpt/WebCryptoAPI/encap_decap/encap_decap_keys.tentative.https.any.js b/test/fixtures/wpt/WebCryptoAPI/encap_decap/encap_decap_keys.tentative.https.any.js index 4ccb0585b84f87..0a45c1fc4e9f6f 100644 --- a/test/fixtures/wpt/WebCryptoAPI/encap_decap/encap_decap_keys.tentative.https.any.js +++ b/test/fixtures/wpt/WebCryptoAPI/encap_decap/encap_decap_keys.tentative.https.any.js @@ -38,313 +38,318 @@ function define_key_tests() { variants.forEach(function (algorithmName) { sharedKeyConfigs.forEach(function (config) { - // Test encapsulateKey operation - promise_test(async function (test) { - // Generate a key pair for testing - var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ - 'encapsulateKey', - 'decapsulateKey', - ]); - - // Test encapsulateKey - var encapsulatedKey = await subtle.encapsulateKey( - { name: algorithmName }, - keyPair.publicKey, - config.algorithm, - true, - config.usages - ); - - assert_true( - encapsulatedKey instanceof Object, - 'encapsulateKey should return an object' - ); - assert_true( - encapsulatedKey.hasOwnProperty('sharedKey'), - 'Result should have sharedKey property' - ); - assert_true( - encapsulatedKey.hasOwnProperty('ciphertext'), - 'Result should have ciphertext property' - ); - assert_true( - encapsulatedKey.sharedKey instanceof CryptoKey, - 'sharedKey should be a CryptoKey' - ); - assert_true( - encapsulatedKey.ciphertext instanceof ArrayBuffer, - 'ciphertext should be ArrayBuffer' - ); - - // Verify the shared key properties - assert_equals( - encapsulatedKey.sharedKey.type, - 'secret', - 'Shared key should be secret type' - ); - assert_equals( - encapsulatedKey.sharedKey.algorithm.name, - config.algorithm.name, - 'Shared key algorithm should match' - ); - assert_true( - encapsulatedKey.sharedKey.extractable, - 'Shared key should be extractable as specified' - ); - assert_array_equals( - encapsulatedKey.sharedKey.usages, - config.usages, - 'Shared key should have correct usages' - ); - - // Verify algorithm-specific properties - if (config.algorithm.length) { + [true, false].forEach(function (extractable) { + // Test encapsulateKey operation + promise_test(async function (test) { + // Generate a key pair for testing + var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ + 'encapsulateKey', + 'decapsulateKey', + ]); + + // Test encapsulateKey + var encapsulatedKey = await subtle.encapsulateKey( + { name: algorithmName }, + keyPair.publicKey, + config.algorithm, + extractable, + config.usages + ); + + assert_true( + encapsulatedKey instanceof Object, + 'encapsulateKey should return an object' + ); + assert_true( + encapsulatedKey.hasOwnProperty('sharedKey'), + 'Result should have sharedKey property' + ); + assert_true( + encapsulatedKey.hasOwnProperty('ciphertext'), + 'Result should have ciphertext property' + ); + assert_true( + encapsulatedKey.sharedKey instanceof CryptoKey, + 'sharedKey should be a CryptoKey' + ); + assert_true( + encapsulatedKey.ciphertext instanceof ArrayBuffer, + 'ciphertext should be ArrayBuffer' + ); + + // Verify the shared key properties assert_equals( - encapsulatedKey.sharedKey.algorithm.length, - config.algorithm.length, - 'Key length should be 256' + encapsulatedKey.sharedKey.type, + 'secret', + 'Shared key should be secret type' ); - } - if (config.algorithm.hash) { assert_equals( - encapsulatedKey.sharedKey.algorithm.hash.name, - config.algorithm.hash, - 'Hash algorithm should match' + encapsulatedKey.sharedKey.algorithm.name, + config.algorithm.name, + 'Shared key algorithm should match' ); - } - - // Verify ciphertext length based on algorithm variant - var expectedCiphertextLength; - switch (algorithmName) { - case 'ML-KEM-512': - expectedCiphertextLength = 768; - break; - case 'ML-KEM-768': - expectedCiphertextLength = 1088; - break; - case 'ML-KEM-1024': - expectedCiphertextLength = 1568; - break; - } - assert_equals( - encapsulatedKey.ciphertext.byteLength, - expectedCiphertextLength, - 'Ciphertext should be ' + - expectedCiphertextLength + - ' bytes for ' + - algorithmName - ); - }, algorithmName + ' encapsulateKey with ' + config.description); - - // Test decapsulateKey operation - promise_test(async function (test) { - // Generate a key pair for testing - var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ - 'encapsulateKey', - 'decapsulateKey', - ]); - - // First encapsulate to get ciphertext - var encapsulatedKey = await subtle.encapsulateKey( - { name: algorithmName }, - keyPair.publicKey, - config.algorithm, - true, - config.usages - ); - - // Then decapsulate using the private key - var decapsulatedKey = await subtle.decapsulateKey( - { name: algorithmName }, - keyPair.privateKey, - encapsulatedKey.ciphertext, - config.algorithm, - true, - config.usages - ); - - assert_true( - decapsulatedKey instanceof CryptoKey, - 'decapsulateKey should return a CryptoKey' - ); - assert_equals( - decapsulatedKey.type, - 'secret', - 'Decapsulated key should be secret type' - ); - assert_equals( - decapsulatedKey.algorithm.name, - config.algorithm.name, - 'Decapsulated key algorithm should match' - ); - assert_true( - decapsulatedKey.extractable, - 'Decapsulated key should be extractable as specified' - ); - assert_array_equals( - decapsulatedKey.usages, - config.usages, - 'Decapsulated key should have correct usages' - ); - - // Extract both keys and verify they are identical - var originalKeyMaterial = await subtle.exportKey( - 'raw', - encapsulatedKey.sharedKey - ); - var decapsulatedKeyMaterial = await subtle.exportKey( - 'raw', - decapsulatedKey - ); - - assert_true( - equalBuffers(originalKeyMaterial, decapsulatedKeyMaterial), - 'Decapsulated key material should match original' - ); - - // Verify the key material is 32 bytes (256 bits) - assert_equals( - originalKeyMaterial.byteLength, - 32, - 'Shared key material should be 32 bytes' - ); - }, algorithmName + ' decapsulateKey with ' + config.description); - - // Test round-trip compatibility - promise_test(async function (test) { - var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ - 'encapsulateKey', - 'decapsulateKey', - ]); - - var encapsulatedKey = await subtle.encapsulateKey( - { name: algorithmName }, - keyPair.publicKey, - config.algorithm, - true, - config.usages - ); - - var decapsulatedKey = await subtle.decapsulateKey( - { name: algorithmName }, - keyPair.privateKey, - encapsulatedKey.ciphertext, - config.algorithm, - true, - config.usages - ); - - // Verify keys have the same material - var originalKeyMaterial = await subtle.exportKey( - 'raw', - encapsulatedKey.sharedKey - ); - var decapsulatedKeyMaterial = await subtle.exportKey( - 'raw', - decapsulatedKey - ); - - assert_true( - equalBuffers(originalKeyMaterial, decapsulatedKeyMaterial), - 'Encapsulated and decapsulated keys should have the same material' - ); - - // Test that the derived keys can actually be used for their intended purpose - if ( - config.algorithm.name.startsWith('AES') && - config.usages.includes('encrypt') - ) { - await testAESOperation( - encapsulatedKey.sharedKey, - decapsulatedKey, - config.algorithm + assert_equals( + encapsulatedKey.sharedKey.extractable, + extractable, + 'Shared key should have correct extractable property' + ); + assert_array_equals( + encapsulatedKey.sharedKey.usages, + config.usages, + 'Shared key should have correct usages' ); - } else if (config.algorithm.name === 'HMAC') { - await testHMACOperation(encapsulatedKey.sharedKey, decapsulatedKey); - } - }, algorithmName + - ' encapsulateKey/decapsulateKey round-trip with ' + - config.description); - }); - // Test vector-based decapsulation for each shared key config - sharedKeyConfigs.forEach(function (config) { - promise_test(async function (test) { - var vectors = ml_kem_vectors[algorithmName]; - - // Import the private key from the vector's privateSeed - var privateKey = await subtle.importKey( - 'raw-seed', - vectors.privateSeed, - { name: algorithmName }, - false, - ['decapsulateKey'] - ); - - // Decapsulate the sample ciphertext from the vectors to get a shared key - var decapsulatedKey = await subtle.decapsulateKey( - { name: algorithmName }, - privateKey, - vectors.sampleCiphertext, - config.algorithm, - true, - config.usages - ); - - assert_true( - decapsulatedKey instanceof CryptoKey, - 'decapsulateKey should return a CryptoKey' - ); - assert_equals( - decapsulatedKey.type, - 'secret', - 'Decapsulated key should be secret type' - ); - assert_equals( - decapsulatedKey.algorithm.name, - config.algorithm.name, - 'Decapsulated key algorithm should match' - ); - assert_true( - decapsulatedKey.extractable, - 'Decapsulated key should be extractable as specified' - ); - assert_array_equals( - decapsulatedKey.usages, - config.usages, - 'Decapsulated key should have correct usages' - ); - - // Extract the key material and verify it matches the expected shared secret - var keyMaterial = await subtle.exportKey('raw', decapsulatedKey); - assert_equals( - keyMaterial.byteLength, - 32, - 'Shared key material should be 32 bytes' - ); - assert_true( - equalBuffers(keyMaterial, vectors.expectedSharedSecret), - "Decapsulated key material should match vector's expectedSharedSecret" - ); - - // Verify algorithm-specific properties - if (config.algorithm.length) { + // Verify algorithm-specific properties + if (config.algorithm.length) { + assert_equals( + encapsulatedKey.sharedKey.algorithm.length, + config.algorithm.length, + 'Key length should be 256' + ); + } + if (config.algorithm.hash) { + assert_equals( + encapsulatedKey.sharedKey.algorithm.hash.name, + config.algorithm.hash, + 'Hash algorithm should match' + ); + } + + // Verify ciphertext length based on algorithm variant + var expectedCiphertextLength; + switch (algorithmName) { + case 'ML-KEM-512': + expectedCiphertextLength = 768; + break; + case 'ML-KEM-768': + expectedCiphertextLength = 1088; + break; + case 'ML-KEM-1024': + expectedCiphertextLength = 1568; + break; + } + assert_equals( + encapsulatedKey.ciphertext.byteLength, + expectedCiphertextLength, + 'Ciphertext should be ' + + expectedCiphertextLength + + ' bytes for ' + + algorithmName + ); + }, `${algorithmName} encapsulateKey with ${config.description} (extractable=${extractable})`); + + // Test decapsulateKey operation + promise_test(async function (test) { + // Generate a key pair for testing + var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ + 'encapsulateKey', + 'decapsulateKey', + ]); + + // First encapsulate to get ciphertext + var encapsulatedKey = await subtle.encapsulateKey( + { name: algorithmName }, + keyPair.publicKey, + config.algorithm, + extractable, + config.usages + ); + + // Then decapsulate using the private key + var decapsulatedKey = await subtle.decapsulateKey( + { name: algorithmName }, + keyPair.privateKey, + encapsulatedKey.ciphertext, + config.algorithm, + extractable, + config.usages + ); + + assert_true( + decapsulatedKey instanceof CryptoKey, + 'decapsulateKey should return a CryptoKey' + ); + assert_equals( + decapsulatedKey.type, + 'secret', + 'Decapsulated key should be secret type' + ); + assert_equals( + decapsulatedKey.algorithm.name, + config.algorithm.name, + 'Decapsulated key algorithm should match' + ); + assert_equals( + decapsulatedKey.extractable, + extractable, + 'Decapsulated key should have correct extractable property' + ); + assert_array_equals( + decapsulatedKey.usages, + config.usages, + 'Decapsulated key should have correct usages' + ); + + if (extractable) { + // Extract both keys and verify they are identical + var originalKeyMaterial = await subtle.exportKey( + 'raw', + encapsulatedKey.sharedKey + ); + var decapsulatedKeyMaterial = await subtle.exportKey( + 'raw', + decapsulatedKey + ); + + assert_true( + equalBuffers(originalKeyMaterial, decapsulatedKeyMaterial), + 'Decapsulated key material should match original' + ); + + // Verify the key material is 32 bytes (256 bits) + assert_equals( + originalKeyMaterial.byteLength, + 32, + 'Shared key material should be 32 bytes' + ); + } + }, `${algorithmName} decapsulateKey with ${config.description} (extractable=${extractable})`); + + // Test round-trip compatibility + promise_test(async function (test) { + var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ + 'encapsulateKey', + 'decapsulateKey', + ]); + + var encapsulatedKey = await subtle.encapsulateKey( + { name: algorithmName }, + keyPair.publicKey, + config.algorithm, + extractable, + config.usages + ); + + var decapsulatedKey = await subtle.decapsulateKey( + { name: algorithmName }, + keyPair.privateKey, + encapsulatedKey.ciphertext, + config.algorithm, + extractable, + config.usages + ); + + if (extractable) { + // Verify keys have the same material + var originalKeyMaterial = await subtle.exportKey( + 'raw', + encapsulatedKey.sharedKey + ); + var decapsulatedKeyMaterial = await subtle.exportKey( + 'raw', + decapsulatedKey + ); + + assert_true( + equalBuffers(originalKeyMaterial, decapsulatedKeyMaterial), + 'Encapsulated and decapsulated keys should have the same material' + ); + } + + // Test that the derived keys can actually be used for their intended purpose + if ( + config.algorithm.name.startsWith('AES') && + config.usages.includes('encrypt') + ) { + await testAESOperation( + encapsulatedKey.sharedKey, + decapsulatedKey, + config.algorithm + ); + } else if (config.algorithm.name === 'HMAC') { + await testHMACOperation(encapsulatedKey.sharedKey, decapsulatedKey); + } + }, `${algorithmName} encapsulateKey/decapsulateKey round-trip with ${config.description} (extractable=${extractable})`); + + // Test vector-based decapsulation + promise_test(async function (test) { + var vectors = ml_kem_vectors[algorithmName]; + + // Import the private key from the vector's privateSeed + var privateKey = await subtle.importKey( + 'raw-seed', + vectors.privateSeed, + { name: algorithmName }, + false, + ['decapsulateKey'] + ); + + // Decapsulate the sample ciphertext from the vectors to get a shared key + var decapsulatedKey = await subtle.decapsulateKey( + { name: algorithmName }, + privateKey, + vectors.sampleCiphertext, + config.algorithm, + extractable, + config.usages + ); + + assert_true( + decapsulatedKey instanceof CryptoKey, + 'decapsulateKey should return a CryptoKey' + ); assert_equals( - decapsulatedKey.algorithm.length, - config.algorithm.length, - 'Key length should be 256' + decapsulatedKey.type, + 'secret', + 'Decapsulated key should be secret type' ); - } - if (config.algorithm.hash) { assert_equals( - decapsulatedKey.algorithm.hash.name, - config.algorithm.hash, - 'Hash algorithm should match' + decapsulatedKey.algorithm.name, + config.algorithm.name, + 'Decapsulated key algorithm should match' + ); + assert_equals( + decapsulatedKey.extractable, + extractable, + 'Decapsulated key should have correct extractable property' + ); + assert_array_equals( + decapsulatedKey.usages, + config.usages, + 'Decapsulated key should have correct usages' ); - } - }, algorithmName + - ' vector-based sampleCiphertext decapsulation with ' + - config.description); + + if (extractable) { + // Extract the key material and verify it matches the expected shared secret + var keyMaterial = await subtle.exportKey('raw', decapsulatedKey); + assert_equals( + keyMaterial.byteLength, + 32, + 'Shared key material should be 32 bytes' + ); + assert_true( + equalBuffers(keyMaterial, vectors.expectedSharedSecret), + "Decapsulated key material should match vector's expectedSharedSecret" + ); + } + + // Verify algorithm-specific properties + if (config.algorithm.length) { + assert_equals( + decapsulatedKey.algorithm.length, + config.algorithm.length, + 'Key length should be 256' + ); + } + if (config.algorithm.hash) { + assert_equals( + decapsulatedKey.algorithm.hash.name, + config.algorithm.hash, + 'Hash algorithm should match' + ); + } + }, `${algorithmName} vector-based sampleCiphertext decapsulation with ${config.description} (extractable=${extractable})`); + }); }); }); } @@ -430,19 +435,4 @@ async function testHMACOperation(key1, key2) { assert_true(verified2, 'HMAC verification should succeed with key2'); } -// Helper function to compare two ArrayBuffers -function equalBuffers(a, b) { - if (a.byteLength !== b.byteLength) { - return false; - } - var aBytes = new Uint8Array(a); - var bBytes = new Uint8Array(b); - for (var i = 0; i < a.byteLength; i++) { - if (aBytes[i] !== bBytes[i]) { - return false; - } - } - return true; -} - define_key_tests(); diff --git a/test/fixtures/wpt/WebCryptoAPI/encap_decap/ml_kem_encap_decap.js b/test/fixtures/wpt/WebCryptoAPI/encap_decap/ml_kem_encap_decap.js deleted file mode 100644 index 9167efa63f0ca3..00000000000000 --- a/test/fixtures/wpt/WebCryptoAPI/encap_decap/ml_kem_encap_decap.js +++ /dev/null @@ -1,410 +0,0 @@ -// Test implementation for ML-KEM encapsulate and decapsulate operations - -function define_tests() { - var subtle = self.crypto.subtle; - - // Test data for all ML-KEM variants - var variants = ['ML-KEM-512', 'ML-KEM-768', 'ML-KEM-1024']; - - variants.forEach(function (algorithmName) { - var testVector = ml_kem_vectors[algorithmName]; - - // Test encapsulateBits operation - promise_test(async function (test) { - // Generate a key pair for testing - var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ - 'encapsulateBits', - 'decapsulateBits', - ]); - - // Test encapsulateBits - var encapsulatedBits = await subtle.encapsulateBits( - { name: algorithmName }, - keyPair.publicKey - ); - - assert_true( - encapsulatedBits instanceof Object, - 'encapsulateBits should return an object' - ); - assert_true( - encapsulatedBits.hasOwnProperty('sharedKey'), - 'Result should have sharedKey property' - ); - assert_true( - encapsulatedBits.hasOwnProperty('ciphertext'), - 'Result should have ciphertext property' - ); - assert_true( - encapsulatedBits.sharedKey instanceof ArrayBuffer, - 'sharedKey should be ArrayBuffer' - ); - assert_true( - encapsulatedBits.ciphertext instanceof ArrayBuffer, - 'ciphertext should be ArrayBuffer' - ); - - // Verify sharedKey length (should be 32 bytes for all ML-KEM variants) - assert_equals( - encapsulatedBits.sharedKey.byteLength, - 32, - 'Shared key should be 32 bytes' - ); - - // Verify ciphertext length based on algorithm variant - var expectedCiphertextLength; - switch (algorithmName) { - case 'ML-KEM-512': - expectedCiphertextLength = 768; - break; - case 'ML-KEM-768': - expectedCiphertextLength = 1088; - break; - case 'ML-KEM-1024': - expectedCiphertextLength = 1568; - break; - } - assert_equals( - encapsulatedBits.ciphertext.byteLength, - expectedCiphertextLength, - 'Ciphertext should be ' + - expectedCiphertextLength + - ' bytes for ' + - algorithmName - ); - }, algorithmName + ' encapsulateBits basic functionality'); - - // Test decapsulateBits operation - promise_test(async function (test) { - // Generate a key pair for testing - var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ - 'encapsulateBits', - 'decapsulateBits', - ]); - - // First encapsulate to get ciphertext - var encapsulatedBits = await subtle.encapsulateBits( - { name: algorithmName }, - keyPair.publicKey - ); - - // Then decapsulate using the private key - var decapsulatedBits = await subtle.decapsulateBits( - { name: algorithmName }, - keyPair.privateKey, - encapsulatedBits.ciphertext - ); - - assert_true( - decapsulatedBits instanceof ArrayBuffer, - 'decapsulateBits should return ArrayBuffer' - ); - assert_equals( - decapsulatedBits.byteLength, - 32, - 'Decapsulated bits should be 32 bytes' - ); - - // The decapsulated shared secret should match the original - assert_true( - equalBuffers(decapsulatedBits, encapsulatedBits.sharedKey), - 'Decapsulated shared secret should match original' - ); - }, algorithmName + ' decapsulateBits basic functionality'); - - // Test encapsulateKey operation - promise_test(async function (test) { - // Generate a key pair for testing - var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ - 'encapsulateKey', - 'decapsulateKey', - ]); - - // Test encapsulateKey with AES-GCM as the shared key algorithm - var encapsulatedKey = await subtle.encapsulateKey( - { name: algorithmName }, - keyPair.publicKey, - { name: 'AES-GCM', length: 256 }, - true, - ['encrypt', 'decrypt'] - ); - - assert_true( - encapsulatedKey instanceof Object, - 'encapsulateKey should return an object' - ); - assert_true( - encapsulatedKey.hasOwnProperty('sharedKey'), - 'Result should have sharedKey property' - ); - assert_true( - encapsulatedKey.hasOwnProperty('ciphertext'), - 'Result should have ciphertext property' - ); - assert_true( - encapsulatedKey.sharedKey instanceof CryptoKey, - 'sharedKey should be a CryptoKey' - ); - assert_true( - encapsulatedKey.ciphertext instanceof ArrayBuffer, - 'ciphertext should be ArrayBuffer' - ); - - // Verify the shared key properties - assert_equals( - encapsulatedKey.sharedKey.type, - 'secret', - 'Shared key should be secret type' - ); - assert_equals( - encapsulatedKey.sharedKey.algorithm.name, - 'AES-GCM', - 'Shared key algorithm should be AES-GCM' - ); - assert_equals( - encapsulatedKey.sharedKey.algorithm.length, - 256, - 'Shared key length should be 256' - ); - assert_true( - encapsulatedKey.sharedKey.extractable, - 'Shared key should be extractable as specified' - ); - assert_array_equals( - encapsulatedKey.sharedKey.usages, - ['encrypt', 'decrypt'], - 'Shared key should have correct usages' - ); - }, algorithmName + ' encapsulateKey basic functionality'); - - // Test decapsulateKey operation - promise_test(async function (test) { - // Generate a key pair for testing - var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ - 'encapsulateKey', - 'decapsulateKey', - ]); - - // First encapsulate to get ciphertext - var encapsulatedKey = await subtle.encapsulateKey( - { name: algorithmName }, - keyPair.publicKey, - { name: 'AES-GCM', length: 256 }, - true, - ['encrypt', 'decrypt'] - ); - - // Then decapsulate using the private key - var decapsulatedKey = await subtle.decapsulateKey( - { name: algorithmName }, - keyPair.privateKey, - encapsulatedKey.ciphertext, - { name: 'AES-GCM', length: 256 }, - true, - ['encrypt', 'decrypt'] - ); - - assert_true( - decapsulatedKey instanceof CryptoKey, - 'decapsulateKey should return a CryptoKey' - ); - assert_equals( - decapsulatedKey.type, - 'secret', - 'Decapsulated key should be secret type' - ); - assert_equals( - decapsulatedKey.algorithm.name, - 'AES-GCM', - 'Decapsulated key algorithm should be AES-GCM' - ); - assert_equals( - decapsulatedKey.algorithm.length, - 256, - 'Decapsulated key length should be 256' - ); - assert_true( - decapsulatedKey.extractable, - 'Decapsulated key should be extractable as specified' - ); - assert_array_equals( - decapsulatedKey.usages, - ['encrypt', 'decrypt'], - 'Decapsulated key should have correct usages' - ); - - // Extract both keys and verify they are identical - var originalKeyMaterial = await subtle.exportKey( - 'raw', - encapsulatedKey.sharedKey - ); - var decapsulatedKeyMaterial = await subtle.exportKey( - 'raw', - decapsulatedKey - ); - - assert_true( - equalBuffers(originalKeyMaterial, decapsulatedKeyMaterial), - 'Decapsulated key material should match original' - ); - }, algorithmName + ' decapsulateKey basic functionality'); - - // Test error cases for encapsulateBits - promise_test(async function (test) { - var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ - 'encapsulateBits', - 'decapsulateBits', - ]); - - // Test with wrong key type (private key instead of public) - await promise_rejects_dom( - test, - 'InvalidAccessError', - subtle.encapsulateBits({ name: algorithmName }, keyPair.privateKey), - 'encapsulateBits should reject private key' - ); - - // Test with wrong algorithm name - await promise_rejects_dom( - test, - 'InvalidAccessError', - subtle.encapsulateBits({ name: 'AES-GCM' }, keyPair.publicKey), - 'encapsulateBits should reject mismatched algorithm' - ); - }, algorithmName + ' encapsulateBits error cases'); - - // Test error cases for decapsulateBits - promise_test(async function (test) { - var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ - 'encapsulateBits', - 'decapsulateBits', - ]); - - var encapsulatedBits = await subtle.encapsulateBits( - { name: algorithmName }, - keyPair.publicKey - ); - - // Test with wrong key type (public key instead of private) - await promise_rejects_dom( - test, - 'InvalidAccessError', - subtle.decapsulateBits( - { name: algorithmName }, - keyPair.publicKey, - encapsulatedBits.ciphertext - ), - 'decapsulateBits should reject public key' - ); - - // Test with wrong algorithm name - await promise_rejects_dom( - test, - 'InvalidAccessError', - subtle.decapsulateBits( - { name: 'AES-GCM' }, - keyPair.privateKey, - encapsulatedBits.ciphertext - ), - 'decapsulateBits should reject mismatched algorithm' - ); - - // Test with invalid ciphertext - var invalidCiphertext = new Uint8Array(10); // Wrong size - await promise_rejects_dom( - test, - 'OperationError', - subtle.decapsulateBits( - { name: algorithmName }, - keyPair.privateKey, - invalidCiphertext - ), - 'decapsulateBits should reject invalid ciphertext' - ); - }, algorithmName + ' decapsulateBits error cases'); - - // Test error cases for encapsulateKey - promise_test(async function (test) { - var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ - 'encapsulateKey', - 'decapsulateKey', - ]); - - // Test with key without encapsulateKey usage - var wrongKeyPair = await subtle.generateKey( - { name: algorithmName }, - false, - ['decapsulateKey'] // Missing encapsulateKey usage - ); - - await promise_rejects_dom( - test, - 'InvalidAccessError', - subtle.encapsulateKey( - { name: algorithmName }, - wrongKeyPair.publicKey, - { name: 'AES-GCM', length: 256 }, - true, - ['encrypt', 'decrypt'] - ), - 'encapsulateKey should reject key without encapsulateKey usage' - ); - }, algorithmName + ' encapsulateKey error cases'); - - // Test error cases for decapsulateKey - promise_test(async function (test) { - var keyPair = await subtle.generateKey({ name: algorithmName }, false, [ - 'encapsulateKey', - 'decapsulateKey', - ]); - - var encapsulatedKey = await subtle.encapsulateKey( - { name: algorithmName }, - keyPair.publicKey, - { name: 'AES-GCM', length: 256 }, - true, - ['encrypt', 'decrypt'] - ); - - // Test with key without decapsulateKey usage - var wrongKeyPair = await subtle.generateKey( - { name: algorithmName }, - false, - ['encapsulateKey'] // Missing decapsulateKey usage - ); - - await promise_rejects_dom( - test, - 'InvalidAccessError', - subtle.decapsulateKey( - { name: algorithmName }, - wrongKeyPair.privateKey, - encapsulatedKey.ciphertext, - { name: 'AES-GCM', length: 256 }, - true, - ['encrypt', 'decrypt'] - ), - 'decapsulateKey should reject key without decapsulateKey usage' - ); - }, algorithmName + ' decapsulateKey error cases'); - }); -} - -// Helper function to compare two ArrayBuffers -function equalBuffers(a, b) { - if (a.byteLength !== b.byteLength) { - return false; - } - var aBytes = new Uint8Array(a); - var bBytes = new Uint8Array(b); - for (var i = 0; i < a.byteLength; i++) { - if (aBytes[i] !== bBytes[i]) { - return false; - } - } - return true; -} - -function run_test() { - define_tests(); -} diff --git a/test/fixtures/wpt/WebCryptoAPI/encrypt_decrypt/aes.js b/test/fixtures/wpt/WebCryptoAPI/encrypt_decrypt/aes.js index 456f66423419c8..879a6efe257e49 100644 --- a/test/fixtures/wpt/WebCryptoAPI/encrypt_decrypt/aes.js +++ b/test/fixtures/wpt/WebCryptoAPI/encrypt_decrypt/aes.js @@ -482,34 +482,5 @@ function run_test() { } } - // Returns a copy of the sourceBuffer it is sent. - function copyBuffer(sourceBuffer) { - var source = new Uint8Array(sourceBuffer); - var copy = new Uint8Array(sourceBuffer.byteLength) - - for (var i=0; i { + idl_array.add_objects({ + Crypto: ['crypto'], + SubtleCrypto: ['crypto.subtle'] + }); + } +); diff --git a/test/fixtures/wpt/WebCryptoAPI/import_export/ML-DSA_importKey.js b/test/fixtures/wpt/WebCryptoAPI/import_export/ML-DSA_importKey.js index 3723b321e542d5..d9257ac6982505 100644 --- a/test/fixtures/wpt/WebCryptoAPI/import_export/ML-DSA_importKey.js +++ b/test/fixtures/wpt/WebCryptoAPI/import_export/ML-DSA_importKey.js @@ -106,44 +106,6 @@ function testFormat(format, algorithm, keyData, keySize, usages, extractable) { // Helper methods follow: -// Are two array buffers the same? -function equalBuffers(a, b) { - if (a.byteLength !== b.byteLength) { - return false; - } - - var aBytes = new Uint8Array(a); - var bBytes = new Uint8Array(b); - - for (var i = 0; i < a.byteLength; i++) { - if (aBytes[i] !== bBytes[i]) { - return false; - } - } - - return true; -} - -// Are two Jwk objects "the same"? That is, does the object returned include -// matching values for each property that was expected? It's okay if the -// returned object has extra methods; they aren't checked. -function equalJwk(expected, got) { - var fields = Object.keys(expected); - var fieldName; - - for (var i = 0; i < fields.length; i++) { - fieldName = fields[i]; - if (!(fieldName in got)) { - return false; - } - if (expected[fieldName] !== got[fieldName]) { - return false; - } - } - - return true; -} - // Convert method parameters to a string to uniquely name each test function parameterString(format, data, algorithm, extractable, usages) { if ('byteLength' in data) { @@ -166,57 +128,3 @@ function parameterString(format, data, algorithm, extractable, usages) { return result; } - -// Character representation of any object we may use as a parameter. -function objectToString(obj) { - var keyValuePairs = []; - - if (Array.isArray(obj)) { - return ( - '[' + - obj - .map(function (elem) { - return objectToString(elem); - }) - .join(', ') + - ']' - ); - } else if (typeof obj === 'object') { - Object.keys(obj) - .sort() - .forEach(function (keyName) { - keyValuePairs.push(keyName + ': ' + objectToString(obj[keyName])); - }); - return '{' + keyValuePairs.join(', ') + '}'; - } else if (typeof obj === 'undefined') { - return 'undefined'; - } else { - return obj.toString(); - } - - var keyValuePairs = []; - - Object.keys(obj) - .sort() - .forEach(function (keyName) { - var value = obj[keyName]; - if (typeof value === 'object') { - value = objectToString(value); - } else if (typeof value === 'array') { - value = - '[' + - value - .map(function (elem) { - return objectToString(elem); - }) - .join(', ') + - ']'; - } else { - value = value.toString(); - } - - keyValuePairs.push(keyName + ': ' + value); - }); - - return '{' + keyValuePairs.join(', ') + '}'; -} diff --git a/test/fixtures/wpt/WebCryptoAPI/import_export/ML-KEM_importKey.js b/test/fixtures/wpt/WebCryptoAPI/import_export/ML-KEM_importKey.js index c264a163148438..ad2f75048556e4 100644 --- a/test/fixtures/wpt/WebCryptoAPI/import_export/ML-KEM_importKey.js +++ b/test/fixtures/wpt/WebCryptoAPI/import_export/ML-KEM_importKey.js @@ -106,44 +106,6 @@ function testFormat(format, algorithm, keyData, keySize, usages, extractable) { // Helper methods follow: -// Are two array buffers the same? -function equalBuffers(a, b) { - if (a.byteLength !== b.byteLength) { - return false; - } - - var aBytes = new Uint8Array(a); - var bBytes = new Uint8Array(b); - - for (var i = 0; i < a.byteLength; i++) { - if (aBytes[i] !== bBytes[i]) { - return false; - } - } - - return true; -} - -// Are two Jwk objects "the same"? That is, does the object returned include -// matching values for each property that was expected? It's okay if the -// returned object has extra methods; they aren't checked. -function equalJwk(expected, got) { - var fields = Object.keys(expected); - var fieldName; - - for (var i = 0; i < fields.length; i++) { - fieldName = fields[i]; - if (!(fieldName in got)) { - return false; - } - if (expected[fieldName] !== got[fieldName]) { - return false; - } - } - - return true; -} - // Convert method parameters to a string to uniquely name each test function parameterString(format, data, algorithm, extractable, usages) { if ('byteLength' in data) { @@ -166,57 +128,3 @@ function parameterString(format, data, algorithm, extractable, usages) { return result; } - -// Character representation of any object we may use as a parameter. -function objectToString(obj) { - var keyValuePairs = []; - - if (Array.isArray(obj)) { - return ( - '[' + - obj - .map(function (elem) { - return objectToString(elem); - }) - .join(', ') + - ']' - ); - } else if (typeof obj === 'object') { - Object.keys(obj) - .sort() - .forEach(function (keyName) { - keyValuePairs.push(keyName + ': ' + objectToString(obj[keyName])); - }); - return '{' + keyValuePairs.join(', ') + '}'; - } else if (typeof obj === 'undefined') { - return 'undefined'; - } else { - return obj.toString(); - } - - var keyValuePairs = []; - - Object.keys(obj) - .sort() - .forEach(function (keyName) { - var value = obj[keyName]; - if (typeof value === 'object') { - value = objectToString(value); - } else if (typeof value === 'array') { - value = - '[' + - value - .map(function (elem) { - return objectToString(elem); - }) - .join(', ') + - ']'; - } else { - value = value.toString(); - } - - keyValuePairs.push(keyName + ': ' + value); - }); - - return '{' + keyValuePairs.join(', ') + '}'; -} diff --git a/test/fixtures/wpt/WebCryptoAPI/import_export/ec_importKey.https.any.js b/test/fixtures/wpt/WebCryptoAPI/import_export/ec_importKey.https.any.js index 6a5cc8d4724b57..3b78bab4e74132 100644 --- a/test/fixtures/wpt/WebCryptoAPI/import_export/ec_importKey.https.any.js +++ b/test/fixtures/wpt/WebCryptoAPI/import_export/ec_importKey.https.any.js @@ -220,44 +220,6 @@ // Helper methods follow: - // Are two array buffers the same? - function equalBuffers(a, b) { - if (a.byteLength !== b.byteLength) { - return false; - } - - var aBytes = new Uint8Array(a); - var bBytes = new Uint8Array(b); - - for (var i=0; i> (8 - remainder) === bBytes[length] >> (8 - remainder); + } + + return true; +} + +// Returns a copy of the sourceBuffer it is sent. +function copyBuffer(sourceBuffer) { + var source = new Uint8Array(sourceBuffer); + var copy = new Uint8Array(sourceBuffer.byteLength) + + for (var i=0; i getAttributeNames(); DOMString? getAttribute(DOMString qualifiedName); DOMString? getAttributeNS(DOMString? namespace, DOMString localName); - [CEReactions] undefined setAttribute(DOMString qualifiedName, DOMString value); - [CEReactions] undefined setAttributeNS(DOMString? namespace, DOMString qualifiedName, DOMString value); + [CEReactions] undefined setAttribute(DOMString qualifiedName, (TrustedType or DOMString) value); + [CEReactions] undefined setAttributeNS(DOMString? namespace, DOMString qualifiedName, (TrustedType or DOMString) value); [CEReactions] undefined removeAttribute(DOMString qualifiedName); [CEReactions] undefined removeAttributeNS(DOMString? namespace, DOMString localName); [CEReactions] boolean toggleAttribute(DOMString qualifiedName, optional boolean force); @@ -412,7 +412,7 @@ dictionary ShadowRootInit { SlotAssignmentMode slotAssignment = "named"; boolean clonable = false; boolean serializable = false; - CustomElementRegistry customElementRegistry; + CustomElementRegistry? customElementRegistry; }; [Exposed=Window, diff --git a/test/fixtures/wpt/interfaces/html.idl b/test/fixtures/wpt/interfaces/html.idl index 9c84e6a67efa4f..567e5a79a36ab8 100644 --- a/test/fixtures/wpt/interfaces/html.idl +++ b/test/fixtures/wpt/interfaces/html.idl @@ -110,21 +110,21 @@ interface HTMLElement : Element { [HTMLConstructor] constructor(); // metadata attributes - [CEReactions] attribute DOMString title; - [CEReactions] attribute DOMString lang; + [CEReactions, Reflect] attribute DOMString title; + [CEReactions, Reflect] attribute DOMString lang; [CEReactions] attribute boolean translate; [CEReactions] attribute DOMString dir; // user interaction [CEReactions] attribute (boolean or unrestricted double or DOMString)? hidden; - [CEReactions] attribute boolean inert; + [CEReactions, Reflect] attribute boolean inert; undefined click(); - [CEReactions] attribute DOMString accessKey; + [CEReactions, Reflect] attribute DOMString accessKey; readonly attribute DOMString accessKeyLabel; [CEReactions] attribute boolean draggable; [CEReactions] attribute boolean spellcheck; - [CEReactions] attribute DOMString writingSuggestions; - [CEReactions] attribute DOMString autocapitalize; + [CEReactions, ReflectSetter] attribute DOMString writingSuggestions; + [CEReactions, ReflectSetter] attribute DOMString autocapitalize; [CEReactions] attribute boolean autocorrect; [CEReactions] attribute [LegacyNullToEmptyString] DOMString innerText; @@ -137,6 +137,9 @@ interface HTMLElement : Element { undefined hidePopover(); boolean togglePopover(optional (TogglePopoverOptions or boolean) options = {}); [CEReactions] attribute DOMString? popover; + + [CEReactions, Reflect, ReflectRange=(0, 8)] attribute unsigned long headingOffset; + [CEReactions, Reflect] attribute boolean headingReset; }; dictionary ShowPopoverOptions { @@ -160,8 +163,8 @@ interface mixin HTMLOrSVGElement { [SameObject] readonly attribute DOMStringMap dataset; attribute DOMString nonce; // intentionally no [CEReactions] - [CEReactions] attribute boolean autofocus; - [CEReactions] attribute long tabIndex; + [CEReactions, Reflect] attribute boolean autofocus; + [CEReactions, ReflectSetter] attribute long tabIndex; undefined focus(optional FocusOptions options = {}); undefined blur(); }; @@ -197,29 +200,29 @@ interface HTMLTitleElement : HTMLElement { interface HTMLBaseElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute USVString href; - [CEReactions] attribute DOMString target; + [CEReactions, ReflectSetter] attribute USVString href; + [CEReactions, Reflect] attribute DOMString target; }; [Exposed=Window] interface HTMLLinkElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute USVString href; + [CEReactions, ReflectURL] attribute USVString href; [CEReactions] attribute DOMString? crossOrigin; - [CEReactions] attribute DOMString rel; + [CEReactions, Reflect] attribute DOMString rel; [CEReactions] attribute DOMString as; - [SameObject, PutForwards=value] readonly attribute DOMTokenList relList; - [CEReactions] attribute DOMString media; - [CEReactions] attribute DOMString integrity; - [CEReactions] attribute DOMString hreflang; - [CEReactions] attribute DOMString type; - [SameObject, PutForwards=value] readonly attribute DOMTokenList sizes; - [CEReactions] attribute USVString imageSrcset; - [CEReactions] attribute DOMString imageSizes; + [SameObject, PutForwards=value, Reflect="rel"] readonly attribute DOMTokenList relList; + [CEReactions, Reflect] attribute DOMString media; + [CEReactions, Reflect] attribute DOMString integrity; + [CEReactions, Reflect] attribute DOMString hreflang; + [CEReactions, Reflect] attribute DOMString type; + [SameObject, PutForwards=value, Reflect] readonly attribute DOMTokenList sizes; + [CEReactions, Reflect] attribute USVString imageSrcset; + [CEReactions, Reflect] attribute DOMString imageSizes; [CEReactions] attribute DOMString referrerPolicy; - [SameObject, PutForwards=value] readonly attribute DOMTokenList blocking; - [CEReactions] attribute boolean disabled; + [SameObject, PutForwards=value, Reflect] readonly attribute DOMTokenList blocking; + [CEReactions, Reflect] attribute boolean disabled; [CEReactions] attribute DOMString fetchPriority; // also has obsolete members @@ -230,10 +233,10 @@ HTMLLinkElement includes LinkStyle; interface HTMLMetaElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute DOMString name; - [CEReactions] attribute DOMString httpEquiv; - [CEReactions] attribute DOMString content; - [CEReactions] attribute DOMString media; + [CEReactions, Reflect] attribute DOMString name; + [CEReactions, Reflect="http-equiv"] attribute DOMString httpEquiv; + [CEReactions, Reflect] attribute DOMString content; + [CEReactions, Reflect] attribute DOMString media; // also has obsolete members }; @@ -243,8 +246,8 @@ interface HTMLStyleElement : HTMLElement { [HTMLConstructor] constructor(); attribute boolean disabled; - [CEReactions] attribute DOMString media; - [SameObject, PutForwards=value] readonly attribute DOMTokenList blocking; + [CEReactions, Reflect] attribute DOMString media; + [SameObject, PutForwards=value, Reflect] readonly attribute DOMTokenList blocking; // also has obsolete members }; @@ -291,16 +294,16 @@ interface HTMLPreElement : HTMLElement { interface HTMLQuoteElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute USVString cite; + [CEReactions, ReflectURL] attribute USVString cite; }; [Exposed=Window] interface HTMLOListElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute boolean reversed; - [CEReactions] attribute long start; - [CEReactions] attribute DOMString type; + [CEReactions, Reflect] attribute boolean reversed; + [CEReactions, Reflect, ReflectDefault=1] attribute long start; + [CEReactions, Reflect] attribute DOMString type; // also has obsolete members }; @@ -323,7 +326,7 @@ interface HTMLMenuElement : HTMLElement { interface HTMLLIElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute long value; + [CEReactions, Reflect] attribute long value; // also has obsolete members }; @@ -346,13 +349,13 @@ interface HTMLDivElement : HTMLElement { interface HTMLAnchorElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute DOMString target; - [CEReactions] attribute DOMString download; - [CEReactions] attribute USVString ping; - [CEReactions] attribute DOMString rel; - [SameObject, PutForwards=value] readonly attribute DOMTokenList relList; - [CEReactions] attribute DOMString hreflang; - [CEReactions] attribute DOMString type; + [CEReactions, Reflect] attribute DOMString target; + [CEReactions, Reflect] attribute DOMString download; + [CEReactions, Reflect] attribute USVString ping; + [CEReactions, Reflect] attribute DOMString rel; + [SameObject, PutForwards=value, Reflect="rel"] readonly attribute DOMTokenList relList; + [CEReactions, Reflect] attribute DOMString hreflang; + [CEReactions, Reflect] attribute DOMString type; [CEReactions] attribute DOMString text; @@ -360,20 +363,21 @@ interface HTMLAnchorElement : HTMLElement { // also has obsolete members }; +HTMLAnchorElement includes HyperlinkElementUtils; HTMLAnchorElement includes HTMLHyperlinkElementUtils; [Exposed=Window] interface HTMLDataElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute DOMString value; + [CEReactions, Reflect] attribute DOMString value; }; [Exposed=Window] interface HTMLTimeElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute DOMString dateTime; + [CEReactions, Reflect] attribute DOMString dateTime; }; [Exposed=Window] @@ -388,8 +392,7 @@ interface HTMLBRElement : HTMLElement { // also has obsolete members }; -interface mixin HTMLHyperlinkElementUtils { - [CEReactions] stringifier attribute USVString href; +interface mixin HyperlinkElementUtils { readonly attribute USVString origin; [CEReactions] attribute USVString protocol; [CEReactions] attribute USVString username; @@ -402,12 +405,16 @@ interface mixin HTMLHyperlinkElementUtils { [CEReactions] attribute USVString hash; }; +interface mixin HTMLHyperlinkElementUtils { + [CEReactions, ReflectSetter] stringifier attribute USVString href; +}; + [Exposed=Window] interface HTMLModElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute USVString cite; - [CEReactions] attribute DOMString dateTime; + [CEReactions, ReflectURL] attribute USVString cite; + [CEReactions, Reflect] attribute DOMString dateTime; }; [Exposed=Window] @@ -419,13 +426,13 @@ interface HTMLPictureElement : HTMLElement { interface HTMLSourceElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute USVString src; - [CEReactions] attribute DOMString type; - [CEReactions] attribute USVString srcset; - [CEReactions] attribute DOMString sizes; - [CEReactions] attribute DOMString media; - [CEReactions] attribute unsigned long width; - [CEReactions] attribute unsigned long height; + [CEReactions, ReflectURL] attribute USVString src; + [CEReactions, Reflect] attribute DOMString type; + [CEReactions, Reflect] attribute USVString srcset; + [CEReactions, Reflect] attribute DOMString sizes; + [CEReactions, Reflect] attribute DOMString media; + [CEReactions, Reflect] attribute unsigned long width; + [CEReactions, Reflect] attribute unsigned long height; }; [Exposed=Window, @@ -433,15 +440,15 @@ interface HTMLSourceElement : HTMLElement { interface HTMLImageElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute DOMString alt; - [CEReactions] attribute USVString src; - [CEReactions] attribute USVString srcset; - [CEReactions] attribute DOMString sizes; + [CEReactions, Reflect] attribute DOMString alt; + [CEReactions, ReflectURL] attribute USVString src; + [CEReactions, Reflect] attribute USVString srcset; + [CEReactions, Reflect] attribute DOMString sizes; [CEReactions] attribute DOMString? crossOrigin; - [CEReactions] attribute DOMString useMap; - [CEReactions] attribute boolean isMap; - [CEReactions] attribute unsigned long width; - [CEReactions] attribute unsigned long height; + [CEReactions, Reflect] attribute DOMString useMap; + [CEReactions, Reflect] attribute boolean isMap; + [CEReactions, ReflectSetter] attribute unsigned long width; + [CEReactions, ReflectSetter] attribute unsigned long height; readonly attribute unsigned long naturalWidth; readonly attribute unsigned long naturalHeight; readonly attribute boolean complete; @@ -460,14 +467,14 @@ interface HTMLImageElement : HTMLElement { interface HTMLIFrameElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute USVString src; + [CEReactions, ReflectURL] attribute USVString src; [CEReactions] attribute (TrustedHTML or DOMString) srcdoc; - [CEReactions] attribute DOMString name; - [SameObject, PutForwards=value] readonly attribute DOMTokenList sandbox; - [CEReactions] attribute DOMString allow; - [CEReactions] attribute boolean allowFullscreen; - [CEReactions] attribute DOMString width; - [CEReactions] attribute DOMString height; + [CEReactions, Reflect] attribute DOMString name; + [SameObject, PutForwards=value, Reflect] readonly attribute DOMTokenList sandbox; + [CEReactions, Reflect] attribute DOMString allow; + [CEReactions, Reflect] attribute boolean allowFullscreen; + [CEReactions, Reflect] attribute DOMString width; + [CEReactions, Reflect] attribute DOMString height; [CEReactions] attribute DOMString referrerPolicy; [CEReactions] attribute DOMString loading; readonly attribute Document? contentDocument; @@ -481,10 +488,10 @@ interface HTMLIFrameElement : HTMLElement { interface HTMLEmbedElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute USVString src; - [CEReactions] attribute DOMString type; - [CEReactions] attribute DOMString width; - [CEReactions] attribute DOMString height; + [CEReactions, ReflectURL] attribute USVString src; + [CEReactions, Reflect] attribute DOMString type; + [CEReactions, Reflect] attribute DOMString width; + [CEReactions, Reflect] attribute DOMString height; Document? getSVGDocument(); // also has obsolete members @@ -494,12 +501,12 @@ interface HTMLEmbedElement : HTMLElement { interface HTMLObjectElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute USVString data; - [CEReactions] attribute DOMString type; - [CEReactions] attribute DOMString name; + [CEReactions, ReflectURL] attribute USVString data; + [CEReactions, Reflect] attribute DOMString type; + [CEReactions, Reflect] attribute DOMString name; readonly attribute HTMLFormElement? form; - [CEReactions] attribute DOMString width; - [CEReactions] attribute DOMString height; + [CEReactions, Reflect] attribute DOMString width; + [CEReactions, Reflect] attribute DOMString height; readonly attribute Document? contentDocument; readonly attribute WindowProxy? contentWindow; Document? getSVGDocument(); @@ -518,12 +525,12 @@ interface HTMLObjectElement : HTMLElement { interface HTMLVideoElement : HTMLMediaElement { [HTMLConstructor] constructor(); - [CEReactions] attribute unsigned long width; - [CEReactions] attribute unsigned long height; + [CEReactions, Reflect] attribute unsigned long width; + [CEReactions, Reflect] attribute unsigned long height; readonly attribute unsigned long videoWidth; readonly attribute unsigned long videoHeight; - [CEReactions] attribute USVString poster; - [CEReactions] attribute boolean playsInline; + [CEReactions, ReflectURL] attribute USVString poster; + [CEReactions, Reflect] attribute boolean playsInline; }; [Exposed=Window, @@ -537,10 +544,10 @@ interface HTMLTrackElement : HTMLElement { [HTMLConstructor] constructor(); [CEReactions] attribute DOMString kind; - [CEReactions] attribute USVString src; - [CEReactions] attribute DOMString srclang; - [CEReactions] attribute DOMString label; - [CEReactions] attribute boolean default; + [CEReactions, ReflectURL] attribute USVString src; + [CEReactions, Reflect] attribute DOMString srclang; + [CEReactions, Reflect] attribute DOMString label; + [CEReactions, Reflect] attribute boolean default; const unsigned short NONE = 0; const unsigned short LOADING = 1; @@ -561,7 +568,7 @@ interface HTMLMediaElement : HTMLElement { readonly attribute MediaError? error; // network state - [CEReactions] attribute USVString src; + [CEReactions, ReflectURL] attribute USVString src; attribute MediaProvider? srcObject; readonly attribute USVString currentSrc; [CEReactions] attribute DOMString? crossOrigin; @@ -596,16 +603,16 @@ interface HTMLMediaElement : HTMLElement { readonly attribute TimeRanges played; readonly attribute TimeRanges seekable; readonly attribute boolean ended; - [CEReactions] attribute boolean autoplay; - [CEReactions] attribute boolean loop; + [CEReactions, Reflect] attribute boolean autoplay; + [CEReactions, Reflect] attribute boolean loop; Promise play(); undefined pause(); // controls - [CEReactions] attribute boolean controls; + [CEReactions, Reflect] attribute boolean controls; attribute double volume; attribute boolean muted; - [CEReactions] attribute boolean defaultMuted; + [CEReactions, Reflect="muted"] attribute boolean defaultMuted; // tracks [SameObject] readonly attribute AudioTrackList audioTracks; @@ -742,7 +749,7 @@ dictionary TrackEventInit : EventInit { interface HTMLMapElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute DOMString name; + [CEReactions, Reflect] attribute DOMString name; [SameObject] readonly attribute HTMLCollection areas; }; @@ -750,18 +757,19 @@ interface HTMLMapElement : HTMLElement { interface HTMLAreaElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute DOMString alt; - [CEReactions] attribute DOMString coords; - [CEReactions] attribute DOMString shape; - [CEReactions] attribute DOMString target; - [CEReactions] attribute DOMString download; - [CEReactions] attribute USVString ping; - [CEReactions] attribute DOMString rel; - [SameObject, PutForwards=value] readonly attribute DOMTokenList relList; + [CEReactions, Reflect] attribute DOMString alt; + [CEReactions, Reflect] attribute DOMString coords; + [CEReactions, Reflect] attribute DOMString shape; + [CEReactions, Reflect] attribute DOMString target; + [CEReactions, Reflect] attribute DOMString download; + [CEReactions, Reflect] attribute USVString ping; + [CEReactions, Reflect] attribute DOMString rel; + [SameObject, PutForwards=value, Reflect="rel"] readonly attribute DOMTokenList relList; [CEReactions] attribute DOMString referrerPolicy; // also has obsolete members }; +HTMLAreaElement includes HyperlinkElementUtils; HTMLAreaElement includes HTMLHyperlinkElementUtils; [Exposed=Window] @@ -801,7 +809,7 @@ interface HTMLTableCaptionElement : HTMLElement { interface HTMLTableColElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute unsigned long span; + [CEReactions, Reflect, ReflectDefault=1, ReflectRange=(1, 1000)] attribute unsigned long span; // also has obsolete members }; @@ -834,13 +842,13 @@ interface HTMLTableRowElement : HTMLElement { interface HTMLTableCellElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute unsigned long colSpan; - [CEReactions] attribute unsigned long rowSpan; - [CEReactions] attribute DOMString headers; + [CEReactions, Reflect, ReflectDefault=1, ReflectRange=(1, 1000)] attribute unsigned long colSpan; + [CEReactions, Reflect, ReflectDefault=1, ReflectRange=(0, 65534)] attribute unsigned long rowSpan; + [CEReactions, Reflect] attribute DOMString headers; readonly attribute long cellIndex; [CEReactions] attribute DOMString scope; // only conforming for th elements - [CEReactions] attribute DOMString abbr; // only conforming for th elements + [CEReactions, Reflect] attribute DOMString abbr; // only conforming for th elements // also has obsolete members }; @@ -851,17 +859,17 @@ interface HTMLTableCellElement : HTMLElement { interface HTMLFormElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute DOMString acceptCharset; - [CEReactions] attribute USVString action; + [CEReactions, Reflect="accept-charset"] attribute DOMString acceptCharset; + [CEReactions, ReflectSetter] attribute USVString action; [CEReactions] attribute DOMString autocomplete; [CEReactions] attribute DOMString enctype; [CEReactions] attribute DOMString encoding; [CEReactions] attribute DOMString method; - [CEReactions] attribute DOMString name; - [CEReactions] attribute boolean noValidate; - [CEReactions] attribute DOMString target; - [CEReactions] attribute DOMString rel; - [SameObject, PutForwards=value] readonly attribute DOMTokenList relList; + [CEReactions, Reflect] attribute DOMString name; + [CEReactions, Reflect] attribute boolean noValidate; + [CEReactions, Reflect] attribute DOMString target; + [CEReactions, Reflect] attribute DOMString rel; + [SameObject, PutForwards=value, Reflect="rel"] readonly attribute DOMTokenList relList; [SameObject] readonly attribute HTMLFormControlsCollection elements; readonly attribute unsigned long length; @@ -880,7 +888,7 @@ interface HTMLLabelElement : HTMLElement { [HTMLConstructor] constructor(); readonly attribute HTMLFormElement? form; - [CEReactions] attribute DOMString htmlFor; + [CEReactions, Reflect="for"] attribute DOMString htmlFor; readonly attribute HTMLElement? control; }; @@ -888,44 +896,44 @@ interface HTMLLabelElement : HTMLElement { interface HTMLInputElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute DOMString accept; - [CEReactions] attribute boolean alpha; - [CEReactions] attribute DOMString alt; - [CEReactions] attribute DOMString autocomplete; - [CEReactions] attribute boolean defaultChecked; + [CEReactions, Reflect] attribute DOMString accept; + [CEReactions, Reflect] attribute boolean alpha; + [CEReactions, Reflect] attribute DOMString alt; + [CEReactions, ReflectSetter] attribute DOMString autocomplete; + [CEReactions, Reflect="checked"] attribute boolean defaultChecked; attribute boolean checked; [CEReactions] attribute DOMString colorSpace; - [CEReactions] attribute DOMString dirName; - [CEReactions] attribute boolean disabled; + [CEReactions, Reflect] attribute DOMString dirName; + [CEReactions, Reflect] attribute boolean disabled; readonly attribute HTMLFormElement? form; attribute FileList? files; - [CEReactions] attribute USVString formAction; + [CEReactions, ReflectSetter] attribute USVString formAction; [CEReactions] attribute DOMString formEnctype; [CEReactions] attribute DOMString formMethod; - [CEReactions] attribute boolean formNoValidate; - [CEReactions] attribute DOMString formTarget; - [CEReactions] attribute unsigned long height; + [CEReactions, Reflect] attribute boolean formNoValidate; + [CEReactions, Reflect] attribute DOMString formTarget; + [CEReactions, ReflectSetter] attribute unsigned long height; attribute boolean indeterminate; readonly attribute HTMLDataListElement? list; - [CEReactions] attribute DOMString max; - [CEReactions] attribute long maxLength; - [CEReactions] attribute DOMString min; - [CEReactions] attribute long minLength; - [CEReactions] attribute boolean multiple; - [CEReactions] attribute DOMString name; - [CEReactions] attribute DOMString pattern; - [CEReactions] attribute DOMString placeholder; - [CEReactions] attribute boolean readOnly; - [CEReactions] attribute boolean required; - [CEReactions] attribute unsigned long size; - [CEReactions] attribute USVString src; - [CEReactions] attribute DOMString step; + [CEReactions, Reflect] attribute DOMString max; + [CEReactions, ReflectNonNegative] attribute long maxLength; + [CEReactions, Reflect] attribute DOMString min; + [CEReactions, ReflectNonNegative] attribute long minLength; + [CEReactions, Reflect] attribute boolean multiple; + [CEReactions, Reflect] attribute DOMString name; + [CEReactions, Reflect] attribute DOMString pattern; + [CEReactions, Reflect] attribute DOMString placeholder; + [CEReactions, Reflect] attribute boolean readOnly; + [CEReactions, Reflect] attribute boolean required; + [CEReactions, Reflect] attribute unsigned long size; + [CEReactions, ReflectURL] attribute USVString src; + [CEReactions, Reflect] attribute DOMString step; [CEReactions] attribute DOMString type; - [CEReactions] attribute DOMString defaultValue; + [CEReactions, Reflect="value"] attribute DOMString defaultValue; [CEReactions] attribute [LegacyNullToEmptyString] DOMString value; attribute object? valueAsDate; attribute unrestricted double valueAsNumber; - [CEReactions] attribute unsigned long width; + [CEReactions, ReflectSetter] attribute unsigned long width; undefined stepUp(optional long n = 1); undefined stepDown(optional long n = 1); @@ -951,24 +959,24 @@ interface HTMLInputElement : HTMLElement { // also has obsolete members }; -HTMLInputElement includes PopoverInvokerElement; +HTMLInputElement includes PopoverTargetAttributes; [Exposed=Window] interface HTMLButtonElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute DOMString command; - [CEReactions] attribute Element? commandForElement; - [CEReactions] attribute boolean disabled; + [CEReactions, ReflectSetter] attribute DOMString command; + [CEReactions, Reflect] attribute Element? commandForElement; + [CEReactions, Reflect] attribute boolean disabled; readonly attribute HTMLFormElement? form; - [CEReactions] attribute USVString formAction; + [CEReactions, ReflectSetter] attribute USVString formAction; [CEReactions] attribute DOMString formEnctype; [CEReactions] attribute DOMString formMethod; - [CEReactions] attribute boolean formNoValidate; - [CEReactions] attribute DOMString formTarget; - [CEReactions] attribute DOMString name; - [CEReactions] attribute DOMString type; - [CEReactions] attribute DOMString value; + [CEReactions, Reflect] attribute boolean formNoValidate; + [CEReactions, Reflect] attribute DOMString formTarget; + [CEReactions, Reflect] attribute DOMString name; + [CEReactions, ReflectSetter] attribute DOMString type; + [CEReactions, Reflect] attribute DOMString value; readonly attribute boolean willValidate; readonly attribute ValidityState validity; @@ -979,19 +987,19 @@ interface HTMLButtonElement : HTMLElement { readonly attribute NodeList labels; }; -HTMLButtonElement includes PopoverInvokerElement; +HTMLButtonElement includes PopoverTargetAttributes; [Exposed=Window] interface HTMLSelectElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute DOMString autocomplete; - [CEReactions] attribute boolean disabled; + [CEReactions, ReflectSetter] attribute DOMString autocomplete; + [CEReactions, Reflect] attribute boolean disabled; readonly attribute HTMLFormElement? form; - [CEReactions] attribute boolean multiple; - [CEReactions] attribute DOMString name; - [CEReactions] attribute boolean required; - [CEReactions] attribute unsigned long size; + [CEReactions, Reflect] attribute boolean multiple; + [CEReactions, Reflect] attribute DOMString name; + [CEReactions, Reflect] attribute boolean required; + [CEReactions, Reflect, ReflectDefault=0] attribute unsigned long size; readonly attribute DOMString type; @@ -1031,8 +1039,8 @@ interface HTMLDataListElement : HTMLElement { interface HTMLOptGroupElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute boolean disabled; - [CEReactions] attribute DOMString label; + [CEReactions, Reflect] attribute boolean disabled; + [CEReactions, Reflect] attribute DOMString label; }; [Exposed=Window, @@ -1040,12 +1048,12 @@ interface HTMLOptGroupElement : HTMLElement { interface HTMLOptionElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute boolean disabled; + [CEReactions, Reflect] attribute boolean disabled; readonly attribute HTMLFormElement? form; - [CEReactions] attribute DOMString label; - [CEReactions] attribute boolean defaultSelected; + [CEReactions, ReflectSetter] attribute DOMString label; + [CEReactions, Reflect="selected"] attribute boolean defaultSelected; attribute boolean selected; - [CEReactions] attribute DOMString value; + [CEReactions, ReflectSetter] attribute DOMString value; [CEReactions] attribute DOMString text; readonly attribute long index; @@ -1055,19 +1063,19 @@ interface HTMLOptionElement : HTMLElement { interface HTMLTextAreaElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute DOMString autocomplete; - [CEReactions] attribute unsigned long cols; - [CEReactions] attribute DOMString dirName; - [CEReactions] attribute boolean disabled; + [CEReactions, ReflectSetter] attribute DOMString autocomplete; + [CEReactions, ReflectPositiveWithFallback, ReflectDefault=20] attribute unsigned long cols; + [CEReactions, Reflect] attribute DOMString dirName; + [CEReactions, Reflect] attribute boolean disabled; readonly attribute HTMLFormElement? form; - [CEReactions] attribute long maxLength; - [CEReactions] attribute long minLength; - [CEReactions] attribute DOMString name; - [CEReactions] attribute DOMString placeholder; - [CEReactions] attribute boolean readOnly; - [CEReactions] attribute boolean required; - [CEReactions] attribute unsigned long rows; - [CEReactions] attribute DOMString wrap; + [CEReactions, ReflectNonNegative] attribute long maxLength; + [CEReactions, ReflectNonNegative] attribute long minLength; + [CEReactions, Reflect] attribute DOMString name; + [CEReactions, Reflect] attribute DOMString placeholder; + [CEReactions, Reflect] attribute boolean readOnly; + [CEReactions, Reflect] attribute boolean required; + [CEReactions, ReflectPositiveWithFallback, ReflectDefault=2] attribute unsigned long rows; + [CEReactions, Reflect] attribute DOMString wrap; readonly attribute DOMString type; [CEReactions] attribute DOMString defaultValue; @@ -1096,9 +1104,9 @@ interface HTMLTextAreaElement : HTMLElement { interface HTMLOutputElement : HTMLElement { [HTMLConstructor] constructor(); - [SameObject, PutForwards=value] readonly attribute DOMTokenList htmlFor; + [SameObject, PutForwards=value, Reflect="for"] readonly attribute DOMTokenList htmlFor; readonly attribute HTMLFormElement? form; - [CEReactions] attribute DOMString name; + [CEReactions, Reflect] attribute DOMString name; readonly attribute DOMString type; [CEReactions] attribute DOMString defaultValue; @@ -1118,8 +1126,8 @@ interface HTMLOutputElement : HTMLElement { interface HTMLProgressElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute double value; - [CEReactions] attribute double max; + [CEReactions, ReflectSetter] attribute double value; + [CEReactions, ReflectPositive, ReflectDefault=1.0] attribute double max; readonly attribute double position; readonly attribute NodeList labels; }; @@ -1128,12 +1136,12 @@ interface HTMLProgressElement : HTMLElement { interface HTMLMeterElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute double value; - [CEReactions] attribute double min; - [CEReactions] attribute double max; - [CEReactions] attribute double low; - [CEReactions] attribute double high; - [CEReactions] attribute double optimum; + [CEReactions, ReflectSetter] attribute double value; + [CEReactions, ReflectSetter] attribute double min; + [CEReactions, ReflectSetter] attribute double max; + [CEReactions, ReflectSetter] attribute double low; + [CEReactions, ReflectSetter] attribute double high; + [CEReactions, ReflectSetter] attribute double optimum; readonly attribute NodeList labels; }; @@ -1141,9 +1149,9 @@ interface HTMLMeterElement : HTMLElement { interface HTMLFieldSetElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute boolean disabled; + [CEReactions, Reflect] attribute boolean disabled; readonly attribute HTMLFormElement? form; - [CEReactions] attribute DOMString name; + [CEReactions, Reflect] attribute DOMString name; readonly attribute DOMString type; @@ -1166,6 +1174,11 @@ interface HTMLLegendElement : HTMLElement { // also has obsolete members }; +[Exposed=Window] +interface HTMLSelectedContentElement : HTMLElement { + [HTMLConstructor] constructor(); +}; + enum SelectionMode { "select", "start", @@ -1214,17 +1227,17 @@ dictionary FormDataEventInit : EventInit { interface HTMLDetailsElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute DOMString name; - [CEReactions] attribute boolean open; + [CEReactions, Reflect] attribute DOMString name; + [CEReactions, Reflect] attribute boolean open; }; [Exposed=Window] interface HTMLDialogElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute boolean open; + [CEReactions, Reflect] attribute boolean open; attribute DOMString returnValue; - [CEReactions] attribute DOMString closedBy; + [CEReactions, ReflectSetter] attribute DOMString closedBy; [CEReactions] undefined show(); [CEReactions] undefined showModal(); [CEReactions] undefined close(optional DOMString returnValue); @@ -1235,18 +1248,19 @@ interface HTMLDialogElement : HTMLElement { interface HTMLScriptElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute USVString src; - [CEReactions] attribute DOMString type; - [CEReactions] attribute boolean noModule; + [CEReactions, Reflect] attribute DOMString type; + [CEReactions, ReflectURL] attribute USVString src; + [CEReactions, Reflect] attribute boolean noModule; [CEReactions] attribute boolean async; - [CEReactions] attribute boolean defer; + [CEReactions, Reflect] attribute boolean defer; + [SameObject, PutForwards=value, Reflect] readonly attribute DOMTokenList blocking; [CEReactions] attribute DOMString? crossOrigin; - [CEReactions] attribute DOMString text; - [CEReactions] attribute DOMString integrity; [CEReactions] attribute DOMString referrerPolicy; - [SameObject, PutForwards=value] readonly attribute DOMTokenList blocking; + [CEReactions, Reflect] attribute DOMString integrity; [CEReactions] attribute DOMString fetchPriority; + [CEReactions] attribute DOMString text; + static boolean supports(DOMString type); // also has obsolete members @@ -1258,17 +1272,17 @@ interface HTMLTemplateElement : HTMLElement { readonly attribute DocumentFragment content; [CEReactions] attribute DOMString shadowRootMode; - [CEReactions] attribute boolean shadowRootDelegatesFocus; - [CEReactions] attribute boolean shadowRootClonable; - [CEReactions] attribute boolean shadowRootSerializable; - [CEReactions] attribute DOMString shadowRootCustomElementRegistry; + [CEReactions, Reflect] attribute boolean shadowRootDelegatesFocus; + [CEReactions, Reflect] attribute boolean shadowRootClonable; + [CEReactions, Reflect] attribute boolean shadowRootSerializable; + [CEReactions, Reflect] attribute DOMString shadowRootCustomElementRegistry; }; [Exposed=Window] interface HTMLSlotElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute DOMString name; + [CEReactions, Reflect] attribute DOMString name; sequence assignedNodes(optional AssignedNodesOptions options = {}); sequence assignedElements(optional AssignedNodesOptions options = {}); undefined assign((Element or Text)... nodes); @@ -1306,8 +1320,6 @@ typedef (HTMLOrSVGImageElement or OffscreenCanvas or VideoFrame) CanvasImageSource; -enum PredefinedColorSpace { "srgb", "display-p3" }; - enum CanvasColorType { "unorm8", "float16" }; enum CanvasFillRule { "nonzero", "evenodd" }; @@ -1606,6 +1618,8 @@ OffscreenCanvasRenderingContext2D includes CanvasPathDrawingStyles; OffscreenCanvasRenderingContext2D includes CanvasTextDrawingStyles; OffscreenCanvasRenderingContext2D includes CanvasPath; +enum PredefinedColorSpace { "srgb", "srgb-linear", "display-p3", "display-p3-linear" }; + [Exposed=Window] interface CustomElementRegistry { constructor(); @@ -1615,7 +1629,7 @@ interface CustomElementRegistry { DOMString? getName(CustomElementConstructor constructor); Promise whenDefined(DOMString name); [CEReactions] undefined upgrade(Node root); - undefined initialize(Node root); + [CEReactions] undefined initialize(Node root); }; callback CustomElementConstructor = HTMLElement (); @@ -1694,11 +1708,13 @@ interface ToggleEvent : Event { constructor(DOMString type, optional ToggleEventInit eventInitDict = {}); readonly attribute DOMString oldState; readonly attribute DOMString newState; + readonly attribute Element? source; }; dictionary ToggleEventInit : EventInit { DOMString oldState = ""; DOMString newState = ""; + Element? source = null; }; [Exposed=Window] @@ -1791,11 +1807,23 @@ dictionary DragEventInit : MouseEventInit { DataTransfer? dataTransfer = null; }; -interface mixin PopoverInvokerElement { - [CEReactions] attribute Element? popoverTargetElement; +interface mixin PopoverTargetAttributes { + [CEReactions, Reflect] attribute Element? popoverTargetElement; [CEReactions] attribute DOMString popoverTargetAction; }; +[Exposed=*] +interface Origin { + constructor(); + + static Origin from(any value); + + readonly attribute boolean opaque; + + boolean isSameOrigin(Origin other); + boolean isSameSite(Origin other); +}; + [Global=Window, Exposed=Window, LegacyUnenumerableNamedProperties] @@ -1807,7 +1835,7 @@ interface Window : EventTarget { attribute DOMString name; [PutForwards=href, LegacyUnforgeable] readonly attribute Location location; readonly attribute History history; - readonly attribute Navigation navigation; + [Replaceable] readonly attribute Navigation navigation; readonly attribute CustomElementRegistry customElements; [Replaceable] readonly attribute BarProp locationbar; [Replaceable] readonly attribute BarProp menubar; @@ -1881,7 +1909,7 @@ interface Location { // but see also additional creation steps and overridden in [LegacyUnforgeable] undefined replace(USVString url); [LegacyUnforgeable] undefined reload(); - [LegacyUnforgeable, SameObject] readonly attribute DOMStringList ancestorOrigins; + [LegacyUnforgeable] readonly attribute DOMStringList ancestorOrigins; }; enum ScrollRestoration { "auto", "manual" }; @@ -1974,6 +2002,8 @@ interface NavigationHistoryEntry : EventTarget { interface NavigationTransition { readonly attribute NavigationType navigationType; readonly attribute NavigationHistoryEntry from; + readonly attribute NavigationDestination to; + readonly attribute Promise committed; readonly attribute Promise finished; }; @@ -2019,6 +2049,7 @@ dictionary NavigateEventInit : EventInit { }; dictionary NavigationInterceptOptions { + NavigationPrecommitHandler precommitHandler; NavigationInterceptHandler handler; NavigationFocusReset focusReset; NavigationScrollBehavior scroll; @@ -2036,6 +2067,14 @@ enum NavigationScrollBehavior { callback NavigationInterceptHandler = Promise (); +[Exposed=Window] +interface NavigationPrecommitController { + undefined redirect(USVString url, optional NavigationNavigateOptions options = {}); + undefined addHandler(NavigationInterceptHandler handler); +}; + +callback NavigationPrecommitHandler = Promise (NavigationPrecommitController controller); + [Exposed=Window] interface NavigationDestination { readonly attribute USVString url; @@ -2649,9 +2688,9 @@ interface Worker : EventTarget { }; dictionary WorkerOptions { + DOMString name = ""; WorkerType type = "classic"; RequestCredentials credentials = "same-origin"; // credentials is only used if type is "module" - DOMString name = ""; }; enum WorkerType { "classic", "module" }; @@ -2661,12 +2700,16 @@ Worker includes MessageEventTarget; [Exposed=Window] interface SharedWorker : EventTarget { - constructor((TrustedScriptURL or USVString) scriptURL, optional (DOMString or WorkerOptions) options = {}); + constructor((TrustedScriptURL or USVString) scriptURL, optional (DOMString or SharedWorkerOptions) options = {}); readonly attribute MessagePort port; }; SharedWorker includes AbstractWorker; +dictionary SharedWorkerOptions : WorkerOptions { + boolean extendedLifetime = false; +}; + interface mixin NavigatorConcurrentHardware { readonly attribute unsigned long long hardwareConcurrency; }; @@ -2748,17 +2791,17 @@ dictionary StorageEventInit : EventInit { interface HTMLMarqueeElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute DOMString behavior; - [CEReactions] attribute DOMString bgColor; - [CEReactions] attribute DOMString direction; - [CEReactions] attribute DOMString height; - [CEReactions] attribute unsigned long hspace; + [CEReactions, Reflect] attribute DOMString behavior; + [CEReactions, Reflect] attribute DOMString bgColor; + [CEReactions, Reflect] attribute DOMString direction; + [CEReactions, Reflect] attribute DOMString height; + [CEReactions, Reflect] attribute unsigned long hspace; [CEReactions] attribute long loop; - [CEReactions] attribute unsigned long scrollAmount; - [CEReactions] attribute unsigned long scrollDelay; - [CEReactions] attribute boolean trueSpeed; - [CEReactions] attribute unsigned long vspace; - [CEReactions] attribute DOMString width; + [CEReactions, Reflect, ReflectDefault=6] attribute unsigned long scrollAmount; + [CEReactions, Reflect, ReflectDefault=85] attribute unsigned long scrollDelay; + [CEReactions, Reflect] attribute boolean trueSpeed; + [CEReactions, Reflect] attribute unsigned long vspace; + [CEReactions, Reflect] attribute DOMString width; undefined start(); undefined stop(); @@ -2768,8 +2811,8 @@ interface HTMLMarqueeElement : HTMLElement { interface HTMLFrameSetElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute DOMString cols; - [CEReactions] attribute DOMString rows; + [CEReactions, Reflect] attribute DOMString cols; + [CEReactions, Reflect] attribute DOMString rows; }; HTMLFrameSetElement includes WindowEventHandlers; @@ -2777,242 +2820,242 @@ HTMLFrameSetElement includes WindowEventHandlers; interface HTMLFrameElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute DOMString name; - [CEReactions] attribute DOMString scrolling; - [CEReactions] attribute USVString src; - [CEReactions] attribute DOMString frameBorder; - [CEReactions] attribute USVString longDesc; - [CEReactions] attribute boolean noResize; + [CEReactions, Reflect] attribute DOMString name; + [CEReactions, Reflect] attribute DOMString scrolling; + [CEReactions, ReflectURL] attribute USVString src; + [CEReactions, Reflect] attribute DOMString frameBorder; + [CEReactions, ReflectURL] attribute USVString longDesc; + [CEReactions, Reflect] attribute boolean noResize; readonly attribute Document? contentDocument; readonly attribute WindowProxy? contentWindow; - [CEReactions] attribute [LegacyNullToEmptyString] DOMString marginHeight; - [CEReactions] attribute [LegacyNullToEmptyString] DOMString marginWidth; + [CEReactions, Reflect] attribute [LegacyNullToEmptyString] DOMString marginHeight; + [CEReactions, Reflect] attribute [LegacyNullToEmptyString] DOMString marginWidth; }; partial interface HTMLAnchorElement { - [CEReactions] attribute DOMString coords; - [CEReactions] attribute DOMString charset; - [CEReactions] attribute DOMString name; - [CEReactions] attribute DOMString rev; - [CEReactions] attribute DOMString shape; + [CEReactions, Reflect] attribute DOMString coords; + [CEReactions, Reflect] attribute DOMString charset; + [CEReactions, Reflect] attribute DOMString name; + [CEReactions, Reflect] attribute DOMString rev; + [CEReactions, Reflect] attribute DOMString shape; }; partial interface HTMLAreaElement { - [CEReactions] attribute boolean noHref; + [CEReactions, Reflect] attribute boolean noHref; }; partial interface HTMLBodyElement { - [CEReactions] attribute [LegacyNullToEmptyString] DOMString text; - [CEReactions] attribute [LegacyNullToEmptyString] DOMString link; - [CEReactions] attribute [LegacyNullToEmptyString] DOMString vLink; - [CEReactions] attribute [LegacyNullToEmptyString] DOMString aLink; - [CEReactions] attribute [LegacyNullToEmptyString] DOMString bgColor; - [CEReactions] attribute DOMString background; + [CEReactions, Reflect] attribute [LegacyNullToEmptyString] DOMString text; + [CEReactions, Reflect] attribute [LegacyNullToEmptyString] DOMString link; + [CEReactions, Reflect] attribute [LegacyNullToEmptyString] DOMString vLink; + [CEReactions, Reflect] attribute [LegacyNullToEmptyString] DOMString aLink; + [CEReactions, Reflect] attribute [LegacyNullToEmptyString] DOMString bgColor; + [CEReactions, Reflect] attribute DOMString background; }; partial interface HTMLBRElement { - [CEReactions] attribute DOMString clear; + [CEReactions, Reflect] attribute DOMString clear; }; partial interface HTMLTableCaptionElement { - [CEReactions] attribute DOMString align; + [CEReactions, Reflect] attribute DOMString align; }; partial interface HTMLTableColElement { - [CEReactions] attribute DOMString align; - [CEReactions] attribute DOMString ch; - [CEReactions] attribute DOMString chOff; - [CEReactions] attribute DOMString vAlign; - [CEReactions] attribute DOMString width; + [CEReactions, Reflect] attribute DOMString align; + [CEReactions, Reflect="char"] attribute DOMString ch; + [CEReactions, Reflect="charoff"] attribute DOMString chOff; + [CEReactions, Reflect] attribute DOMString vAlign; + [CEReactions, Reflect] attribute DOMString width; }; [Exposed=Window] interface HTMLDirectoryElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute boolean compact; + [CEReactions, Reflect] attribute boolean compact; }; partial interface HTMLDivElement { - [CEReactions] attribute DOMString align; + [CEReactions, Reflect] attribute DOMString align; }; partial interface HTMLDListElement { - [CEReactions] attribute boolean compact; + [CEReactions, Reflect] attribute boolean compact; }; partial interface HTMLEmbedElement { - [CEReactions] attribute DOMString align; - [CEReactions] attribute DOMString name; + [CEReactions, Reflect] attribute DOMString align; + [CEReactions, Reflect] attribute DOMString name; }; [Exposed=Window] interface HTMLFontElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute [LegacyNullToEmptyString] DOMString color; - [CEReactions] attribute DOMString face; - [CEReactions] attribute DOMString size; + [CEReactions, Reflect] attribute [LegacyNullToEmptyString] DOMString color; + [CEReactions, Reflect] attribute DOMString face; + [CEReactions, Reflect] attribute DOMString size; }; partial interface HTMLHeadingElement { - [CEReactions] attribute DOMString align; + [CEReactions, Reflect] attribute DOMString align; }; partial interface HTMLHRElement { - [CEReactions] attribute DOMString align; - [CEReactions] attribute DOMString color; - [CEReactions] attribute boolean noShade; - [CEReactions] attribute DOMString size; - [CEReactions] attribute DOMString width; + [CEReactions, Reflect] attribute DOMString align; + [CEReactions, Reflect] attribute DOMString color; + [CEReactions, Reflect] attribute boolean noShade; + [CEReactions, Reflect] attribute DOMString size; + [CEReactions, Reflect] attribute DOMString width; }; partial interface HTMLHtmlElement { - [CEReactions] attribute DOMString version; + [CEReactions, Reflect] attribute DOMString version; }; partial interface HTMLIFrameElement { - [CEReactions] attribute DOMString align; - [CEReactions] attribute DOMString scrolling; - [CEReactions] attribute DOMString frameBorder; - [CEReactions] attribute USVString longDesc; + [CEReactions, Reflect] attribute DOMString align; + [CEReactions, Reflect] attribute DOMString scrolling; + [CEReactions, Reflect] attribute DOMString frameBorder; + [CEReactions, ReflectURL] attribute USVString longDesc; - [CEReactions] attribute [LegacyNullToEmptyString] DOMString marginHeight; - [CEReactions] attribute [LegacyNullToEmptyString] DOMString marginWidth; + [CEReactions, Reflect] attribute [LegacyNullToEmptyString] DOMString marginHeight; + [CEReactions, Reflect] attribute [LegacyNullToEmptyString] DOMString marginWidth; }; partial interface HTMLImageElement { - [CEReactions] attribute DOMString name; - [CEReactions] attribute USVString lowsrc; - [CEReactions] attribute DOMString align; - [CEReactions] attribute unsigned long hspace; - [CEReactions] attribute unsigned long vspace; - [CEReactions] attribute USVString longDesc; + [CEReactions, Reflect] attribute DOMString name; + [CEReactions, ReflectURL] attribute USVString lowsrc; + [CEReactions, Reflect] attribute DOMString align; + [CEReactions, Reflect] attribute unsigned long hspace; + [CEReactions, Reflect] attribute unsigned long vspace; + [CEReactions, ReflectURL] attribute USVString longDesc; - [CEReactions] attribute [LegacyNullToEmptyString] DOMString border; + [CEReactions, Reflect] attribute [LegacyNullToEmptyString] DOMString border; }; partial interface HTMLInputElement { - [CEReactions] attribute DOMString align; - [CEReactions] attribute DOMString useMap; + [CEReactions, Reflect] attribute DOMString align; + [CEReactions, Reflect] attribute DOMString useMap; }; partial interface HTMLLegendElement { - [CEReactions] attribute DOMString align; + [CEReactions, Reflect] attribute DOMString align; }; partial interface HTMLLIElement { - [CEReactions] attribute DOMString type; + [CEReactions, Reflect] attribute DOMString type; }; partial interface HTMLLinkElement { - [CEReactions] attribute DOMString charset; - [CEReactions] attribute DOMString rev; - [CEReactions] attribute DOMString target; + [CEReactions, Reflect] attribute DOMString charset; + [CEReactions, Reflect] attribute DOMString rev; + [CEReactions, Reflect] attribute DOMString target; }; partial interface HTMLMenuElement { - [CEReactions] attribute boolean compact; + [CEReactions, Reflect] attribute boolean compact; }; partial interface HTMLMetaElement { - [CEReactions] attribute DOMString scheme; + [CEReactions, Reflect] attribute DOMString scheme; }; partial interface HTMLObjectElement { - [CEReactions] attribute DOMString align; - [CEReactions] attribute DOMString archive; - [CEReactions] attribute DOMString code; - [CEReactions] attribute boolean declare; - [CEReactions] attribute unsigned long hspace; - [CEReactions] attribute DOMString standby; - [CEReactions] attribute unsigned long vspace; - [CEReactions] attribute DOMString codeBase; - [CEReactions] attribute DOMString codeType; - [CEReactions] attribute DOMString useMap; + [CEReactions, Reflect] attribute DOMString align; + [CEReactions, Reflect] attribute DOMString archive; + [CEReactions, Reflect] attribute DOMString code; + [CEReactions, Reflect] attribute boolean declare; + [CEReactions, Reflect] attribute unsigned long hspace; + [CEReactions, Reflect] attribute DOMString standby; + [CEReactions, Reflect] attribute unsigned long vspace; + [CEReactions, ReflectURL] attribute DOMString codeBase; + [CEReactions, Reflect] attribute DOMString codeType; + [CEReactions, Reflect] attribute DOMString useMap; - [CEReactions] attribute [LegacyNullToEmptyString] DOMString border; + [CEReactions, Reflect] attribute [LegacyNullToEmptyString] DOMString border; }; partial interface HTMLOListElement { - [CEReactions] attribute boolean compact; + [CEReactions, Reflect] attribute boolean compact; }; partial interface HTMLParagraphElement { - [CEReactions] attribute DOMString align; + [CEReactions, Reflect] attribute DOMString align; }; [Exposed=Window] interface HTMLParamElement : HTMLElement { [HTMLConstructor] constructor(); - [CEReactions] attribute DOMString name; - [CEReactions] attribute DOMString value; - [CEReactions] attribute DOMString type; - [CEReactions] attribute DOMString valueType; + [CEReactions, Reflect] attribute DOMString name; + [CEReactions, Reflect] attribute DOMString value; + [CEReactions, Reflect] attribute DOMString type; + [CEReactions, Reflect] attribute DOMString valueType; }; partial interface HTMLPreElement { - [CEReactions] attribute long width; + [CEReactions, Reflect] attribute long width; }; partial interface HTMLStyleElement { - [CEReactions] attribute DOMString type; + [CEReactions, Reflect] attribute DOMString type; }; partial interface HTMLScriptElement { - [CEReactions] attribute DOMString charset; - [CEReactions] attribute DOMString event; - [CEReactions] attribute DOMString htmlFor; + [CEReactions, Reflect] attribute DOMString charset; + [CEReactions, Reflect] attribute DOMString event; + [CEReactions, Reflect="for"] attribute DOMString htmlFor; }; partial interface HTMLTableElement { - [CEReactions] attribute DOMString align; - [CEReactions] attribute DOMString border; - [CEReactions] attribute DOMString frame; - [CEReactions] attribute DOMString rules; - [CEReactions] attribute DOMString summary; - [CEReactions] attribute DOMString width; + [CEReactions, Reflect] attribute DOMString align; + [CEReactions, Reflect] attribute DOMString border; + [CEReactions, Reflect] attribute DOMString frame; + [CEReactions, Reflect] attribute DOMString rules; + [CEReactions, Reflect] attribute DOMString summary; + [CEReactions, Reflect] attribute DOMString width; - [CEReactions] attribute [LegacyNullToEmptyString] DOMString bgColor; - [CEReactions] attribute [LegacyNullToEmptyString] DOMString cellPadding; - [CEReactions] attribute [LegacyNullToEmptyString] DOMString cellSpacing; + [CEReactions, Reflect] attribute [LegacyNullToEmptyString] DOMString bgColor; + [CEReactions, Reflect] attribute [LegacyNullToEmptyString] DOMString cellPadding; + [CEReactions, Reflect] attribute [LegacyNullToEmptyString] DOMString cellSpacing; }; partial interface HTMLTableSectionElement { - [CEReactions] attribute DOMString align; - [CEReactions] attribute DOMString ch; - [CEReactions] attribute DOMString chOff; - [CEReactions] attribute DOMString vAlign; + [CEReactions, Reflect] attribute DOMString align; + [CEReactions, Reflect="char"] attribute DOMString ch; + [CEReactions, Reflect="charoff"] attribute DOMString chOff; + [CEReactions, Reflect] attribute DOMString vAlign; }; partial interface HTMLTableCellElement { - [CEReactions] attribute DOMString align; - [CEReactions] attribute DOMString axis; - [CEReactions] attribute DOMString height; - [CEReactions] attribute DOMString width; + [CEReactions, Reflect] attribute DOMString align; + [CEReactions, Reflect] attribute DOMString axis; + [CEReactions, Reflect] attribute DOMString height; + [CEReactions, Reflect] attribute DOMString width; - [CEReactions] attribute DOMString ch; - [CEReactions] attribute DOMString chOff; - [CEReactions] attribute boolean noWrap; - [CEReactions] attribute DOMString vAlign; + [CEReactions, Reflect="char"] attribute DOMString ch; + [CEReactions, Reflect="charoff"] attribute DOMString chOff; + [CEReactions, Reflect] attribute boolean noWrap; + [CEReactions, Reflect] attribute DOMString vAlign; - [CEReactions] attribute [LegacyNullToEmptyString] DOMString bgColor; + [CEReactions, Reflect] attribute [LegacyNullToEmptyString] DOMString bgColor; }; partial interface HTMLTableRowElement { - [CEReactions] attribute DOMString align; - [CEReactions] attribute DOMString ch; - [CEReactions] attribute DOMString chOff; - [CEReactions] attribute DOMString vAlign; + [CEReactions, Reflect] attribute DOMString align; + [CEReactions, Reflect="char"] attribute DOMString ch; + [CEReactions, Reflect="charoff"] attribute DOMString chOff; + [CEReactions, Reflect] attribute DOMString vAlign; - [CEReactions] attribute [LegacyNullToEmptyString] DOMString bgColor; + [CEReactions, Reflect] attribute [LegacyNullToEmptyString] DOMString bgColor; }; partial interface HTMLUListElement { - [CEReactions] attribute boolean compact; - [CEReactions] attribute DOMString type; + [CEReactions, Reflect] attribute boolean compact; + [CEReactions, Reflect] attribute DOMString type; }; partial interface Document { diff --git a/test/fixtures/wpt/interfaces/performance-timeline.idl b/test/fixtures/wpt/interfaces/performance-timeline.idl index 6ef84b6cbb8e60..f06ed9adb6c649 100644 --- a/test/fixtures/wpt/interfaces/performance-timeline.idl +++ b/test/fixtures/wpt/interfaces/performance-timeline.idl @@ -22,8 +22,8 @@ interface PerformanceEntry { }; callback PerformanceObserverCallback = undefined (PerformanceObserverEntryList entries, - PerformanceObserver observer, - optional PerformanceObserverCallbackOptions options = {}); + PerformanceObserver observer, + optional PerformanceObserverCallbackOptions options = {}); [Exposed=(Window,Worker)] interface PerformanceObserver { constructor(PerformanceObserverCallback callback); diff --git a/test/fixtures/wpt/interfaces/resource-timing.idl b/test/fixtures/wpt/interfaces/resource-timing.idl index 66f2841d744af3..ba8e3953b49705 100644 --- a/test/fixtures/wpt/interfaces/resource-timing.idl +++ b/test/fixtures/wpt/interfaces/resource-timing.idl @@ -22,12 +22,17 @@ interface PerformanceResourceTiming : PerformanceEntry { readonly attribute DOMHighResTimeStamp firstInterimResponseStart; readonly attribute DOMHighResTimeStamp responseStart; readonly attribute DOMHighResTimeStamp responseEnd; + readonly attribute DOMHighResTimeStamp workerRouterEvaluationStart; + readonly attribute DOMHighResTimeStamp workerCacheLookupStart; + readonly attribute DOMString workerMatchedRouterSource; + readonly attribute DOMString workerFinalRouterSource; readonly attribute unsigned long long transferSize; readonly attribute unsigned long long encodedBodySize; readonly attribute unsigned long long decodedBodySize; readonly attribute unsigned short responseStatus; readonly attribute RenderBlockingStatusType renderBlockingStatus; readonly attribute DOMString contentType; + readonly attribute DOMString contentEncoding; [Default] object toJSON(); }; @@ -36,8 +41,7 @@ enum RenderBlockingStatusType { "non-blocking" }; -partial interface Performance { - undefined clearResourceTimings (); +partial interface Performance { undefined clearResourceTimings (); undefined setResourceTimingBufferSize (unsigned long maxSize); attribute EventHandler onresourcetimingbufferfull; }; diff --git a/test/fixtures/wpt/interfaces/streams.idl b/test/fixtures/wpt/interfaces/streams.idl index ab9be033e43ba0..8abc8f5cfda9fe 100644 --- a/test/fixtures/wpt/interfaces/streams.idl +++ b/test/fixtures/wpt/interfaces/streams.idl @@ -17,7 +17,7 @@ interface ReadableStream { Promise pipeTo(WritableStream destination, optional StreamPipeOptions options = {}); sequence tee(); - async iterable(optional ReadableStreamIteratorOptions options = {}); + async_iterable(optional ReadableStreamIteratorOptions options = {}); }; typedef (ReadableStreamDefaultReader or ReadableStreamBYOBReader) ReadableStreamReader; diff --git a/test/fixtures/wpt/interfaces/web-locks.idl b/test/fixtures/wpt/interfaces/web-locks.idl index 14bc3a22cc395f..00648cc3b1e5f4 100644 --- a/test/fixtures/wpt/interfaces/web-locks.idl +++ b/test/fixtures/wpt/interfaces/web-locks.idl @@ -1,3 +1,8 @@ +// GENERATED CONTENT - DO NOT EDIT +// Content was automatically extracted by Reffy into webref +// (https://github.com/w3c/webref) +// Source: Web Locks API (https://w3c.github.io/web-locks/) + [SecureContext] interface mixin NavigatorLocks { readonly attribute LockManager locks; @@ -5,7 +10,7 @@ interface mixin NavigatorLocks { Navigator includes NavigatorLocks; WorkerNavigator includes NavigatorLocks; -[SecureContext, Exposed=(Window,Worker)] +[SecureContext, Exposed=(Window,Worker,SharedStorageWorklet)] interface LockManager { Promise request(DOMString name, LockGrantedCallback callback); @@ -38,7 +43,7 @@ dictionary LockInfo { DOMString clientId; }; -[SecureContext, Exposed=(Window,Worker)] +[SecureContext, Exposed=(Window,Worker,SharedStorageWorklet)] interface Lock { readonly attribute DOMString name; readonly attribute LockMode mode; diff --git a/test/fixtures/wpt/interfaces/webcrypto-modern-algos.idl b/test/fixtures/wpt/interfaces/webcrypto-modern-algos.idl new file mode 100644 index 00000000000000..aeb1d650b7d732 --- /dev/null +++ b/test/fixtures/wpt/interfaces/webcrypto-modern-algos.idl @@ -0,0 +1,118 @@ +// GENERATED CONTENT - DO NOT EDIT +// Content was automatically extracted by Reffy into webref +// (https://github.com/w3c/webref) +// Source: Modern Algorithms in the Web Cryptography API (https://wicg.github.io/webcrypto-modern-algos/) + +[SecureContext,Exposed=(Window,Worker)] +partial interface SubtleCrypto { + Promise encapsulateKey( + AlgorithmIdentifier encapsulationAlgorithm, + CryptoKey encapsulationKey, + AlgorithmIdentifier sharedKeyAlgorithm, + boolean extractable, + sequence keyUsages + ); + Promise encapsulateBits( + AlgorithmIdentifier encapsulationAlgorithm, + CryptoKey encapsulationKey + ); + + Promise decapsulateKey( + AlgorithmIdentifier decapsulationAlgorithm, + CryptoKey decapsulationKey, + BufferSource ciphertext, + AlgorithmIdentifier sharedKeyAlgorithm, + boolean extractable, + sequence keyUsages + ); + Promise decapsulateBits( + AlgorithmIdentifier decapsulationAlgorithm, + CryptoKey decapsulationKey, + BufferSource ciphertext + ); + + Promise getPublicKey( + CryptoKey key, + sequence keyUsages + ); + + static boolean supports(DOMString operation, + AlgorithmIdentifier algorithm, + optional unsigned long? length = null); + static boolean supports(DOMString operation, + AlgorithmIdentifier algorithm, + AlgorithmIdentifier additionalAlgorithm); +}; + +enum KeyFormat { "raw-public", "raw-private", "raw-seed", "raw-secret", "raw", "spki", "pkcs8", "jwk" }; + +enum KeyUsage { "encrypt", "decrypt", "sign", "verify", "deriveKey", "deriveBits", "wrapKey", "unwrapKey", "encapsulateKey", "encapsulateBits", "decapsulateKey", "decapsulateBits" }; + +dictionary EncapsulatedKey { + CryptoKey sharedKey; + ArrayBuffer ciphertext; +}; + +dictionary EncapsulatedBits { + ArrayBuffer sharedKey; + ArrayBuffer ciphertext; +}; + +partial dictionary JsonWebKey { + // The following fields are defined in draft-ietf-cose-dilithium-08 + DOMString pub; + DOMString priv; +}; + +dictionary ContextParams : Algorithm { + BufferSource context; +}; + +dictionary AeadParams : Algorithm { + required BufferSource iv; + BufferSource additionalData; + [EnforceRange] octet tagLength; +}; + +dictionary CShakeParams : Algorithm { + required [EnforceRange] unsigned long outputLength; + BufferSource functionName; + BufferSource customization; +}; + +dictionary TurboShakeParams : Algorithm { + required [EnforceRange] unsigned long outputLength; + [EnforceRange] octet domainSeparation; +}; + +dictionary KangarooTwelveParams : Algorithm { + required [EnforceRange] unsigned long outputLength; + BufferSource customization; +}; + +dictionary KmacKeyGenParams : Algorithm { + [EnforceRange] unsigned long length; +}; + +dictionary KmacImportParams : Algorithm { + [EnforceRange] unsigned long length; +}; + +dictionary KmacKeyAlgorithm : KeyAlgorithm { + required unsigned long length; +}; + +dictionary KmacParams : Algorithm { + required [EnforceRange] unsigned long outputLength; + BufferSource customization; +}; + +dictionary Argon2Params : Algorithm { + required BufferSource nonce; + required [EnforceRange] unsigned long parallelism; + required [EnforceRange] unsigned long memory; + required [EnforceRange] unsigned long passes; + [EnforceRange] octet version; + BufferSource secretValue; + BufferSource associatedData; +}; diff --git a/test/fixtures/wpt/interfaces/webcrypto-secure-curves.idl b/test/fixtures/wpt/interfaces/webcrypto-secure-curves.idl new file mode 100644 index 00000000000000..01bb290b747827 --- /dev/null +++ b/test/fixtures/wpt/interfaces/webcrypto-secure-curves.idl @@ -0,0 +1,8 @@ +// GENERATED CONTENT - DO NOT EDIT +// Content was automatically extracted by Reffy into webref +// (https://github.com/w3c/webref) +// Source: Secure Curves in the Web Cryptography API (https://wicg.github.io/webcrypto-secure-curves/) + +dictionary Ed448Params : Algorithm { + BufferSource context; +}; diff --git a/test/fixtures/wpt/interfaces/webcrypto.idl b/test/fixtures/wpt/interfaces/webcrypto.idl index ff7a89cd0d51be..ebb9cf5718b98d 100644 --- a/test/fixtures/wpt/interfaces/webcrypto.idl +++ b/test/fixtures/wpt/interfaces/webcrypto.idl @@ -1,7 +1,7 @@ // GENERATED CONTENT - DO NOT EDIT // Content was automatically extracted by Reffy into webref // (https://github.com/w3c/webref) -// Source: Web Cryptography API (https://w3c.github.io/webcrypto/) +// Source: Web Cryptography API Level 2 (https://w3c.github.io/webcrypto/) partial interface mixin WindowOrWorkerGlobalScope { [SameObject] readonly attribute Crypto crypto; @@ -28,8 +28,6 @@ dictionary KeyAlgorithm { enum KeyType { "public", "private", "secret" }; -enum KeyUsage { "encrypt", "decrypt", "sign", "verify", "deriveKey", "deriveBits", "wrapKey", "unwrapKey" }; - [SecureContext,Exposed=(Window,Worker),Serializable] interface CryptoKey { readonly attribute KeyType type; @@ -38,8 +36,6 @@ interface CryptoKey { readonly attribute object usages; }; -enum KeyFormat { "raw", "spki", "pkcs8", "jwk" }; - [SecureContext,Exposed=(Window,Worker)] interface SubtleCrypto { Promise encrypt( diff --git a/test/fixtures/wpt/interfaces/webidl.idl b/test/fixtures/wpt/interfaces/webidl.idl index f3db91096ac1be..651c1922115026 100644 --- a/test/fixtures/wpt/interfaces/webidl.idl +++ b/test/fixtures/wpt/interfaces/webidl.idl @@ -3,6 +3,19 @@ // (https://github.com/w3c/webref) // Source: Web IDL Standard (https://webidl.spec.whatwg.org/) +[Exposed=*, Serializable] +interface QuotaExceededError : DOMException { + constructor(optional DOMString message = "", optional QuotaExceededErrorOptions options = {}); + + readonly attribute double? quota; + readonly attribute double? requested; +}; + +dictionary QuotaExceededErrorOptions { + double quota; + double requested; +}; + typedef (Int8Array or Int16Array or Int32Array or Uint8Array or Uint16Array or Uint32Array or Uint8ClampedArray or BigInt64Array or BigUint64Array or diff --git a/test/fixtures/wpt/resources/example.pdf b/test/fixtures/wpt/resources/example.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7bad251ba7e08efc9c383d88518367a6f204cdd6 GIT binary patch literal 618 zcmZvZK~IA)7>4it74LNgS*%f_;7IJuyXdP>;Fl0)@@1ovi$?2a= wCDetVJuafgL)JM{t*y&4gn84@AEe?r!rSV@Lc`y%ah1#F>_TmS$7 literal 0 HcmV?d00001 diff --git a/test/fixtures/wpt/resources/idlharness.js b/test/fixtures/wpt/resources/idlharness.js index 2eb710c1827cda..57cefedc22a182 100644 --- a/test/fixtures/wpt/resources/idlharness.js +++ b/test/fixtures/wpt/resources/idlharness.js @@ -1398,7 +1398,7 @@ IdlInterface.prototype.default_to_json_operation = function() { if (I.has_default_to_json_regular_operation()) { isDefault = true; for (const m of I.members) { - if (m.special !== "static" && m.type == "attribute" && I.array.is_json_type(m.idlType)) { + if (!m.untested && m.special !== "static" && m.type == "attribute" && I.array.is_json_type(m.idlType)) { map.set(m.name, m.idlType); } } diff --git a/test/fixtures/wpt/resources/testdriver-actions.js b/test/fixtures/wpt/resources/testdriver-actions.js index edb4759954d4c3..616a90b36b1b27 100644 --- a/test/fixtures/wpt/resources/testdriver-actions.js +++ b/test/fixtures/wpt/resources/testdriver-actions.js @@ -252,7 +252,7 @@ */ addTick: function(duration) { this.tickIdx += 1; - if (duration) { + if (duration !== undefined && duration !== null) { this.pause(duration); } return this; @@ -279,6 +279,10 @@ /** * Create a keyDown event for the current default key source * + * To send special keys, send the respective key's codepoint, + * as defined by `WebDriver + * `_. + * * @param {String} key - Key to press * @param {String?} sourceName - Named key source to use or null for the default key source * @returns {Actions} @@ -292,6 +296,10 @@ /** * Create a keyUp event for the current default key source * + * To send special keys, send the respective key's codepoint, + * as defined by `WebDriver + * `_. + * * @param {String} key - Key to release * @param {String?} sourceName - Named key source to use or null for the default key source * @returns {Actions} @@ -536,7 +544,7 @@ tick = actions.addTick().tickIdx; } let moveAction = {type: "pointerMove", x, y, origin}; - if (duration) { + if (duration !== undefined && duration !== null) { moveAction.duration = duration; } let actionProperties = setPointerProperties(moveAction, width, height, pressure, @@ -581,7 +589,7 @@ tick = actions.addTick().tickIdx; } this.actions.set(tick, {type: "scroll", x, y, deltaX, deltaY, origin}); - if (duration) { + if (duration !== undefined && duration !== null) { this.actions.get(tick).duration = duration; } }, diff --git a/test/fixtures/wpt/resources/testdriver.js b/test/fixtures/wpt/resources/testdriver.js index 5b390dedeb72bb..4402fbd2235318 100644 --- a/test/fixtures/wpt/resources/testdriver.js +++ b/test/fixtures/wpt/resources/testdriver.js @@ -23,6 +23,14 @@ } } + function assertTestIsTentative(){ + const testPath = location.pathname; + const tentative = testPath.includes('.tentative.') || testPath.includes('/tentative/'); + if (!tentative) { + throw new Error("Method in testdriver.js intended for tentative tests used in non-tentative test"); + } + } + function getInViewCenterPoint(rect) { var left = Math.max(0, rect.left); var right = Math.min(window.innerWidth, rect.right); @@ -769,6 +777,78 @@ }, } }, + /** + * `speculation `_ module. + */ + speculation: { + /** + * `speculation.PrefetchStatusUpdated `_ + * event. + */ + prefetch_status_updated: { + /** + * @typedef {object} PrefetchStatusUpdated + * `speculation.PrefetchStatusUpdatedParameters `_ + * event. + */ + + /** + * Subscribes to the event. Events will be emitted only if + * there is a subscription for the event. This method does + * not add actual listeners. To listen to the event, use the + * `on` or `once` methods. The buffered events will be + * emitted before the command promise is resolved. + * + * @param {object} [params] Parameters for the subscription. + * @param {null|Array.<(Context)>} [params.contexts] The + * optional contexts parameter specifies which browsing + * contexts to subscribe to the event on. It should be + * either an array of Context objects, or null. If null, the + * event will be subscribed to globally. If omitted, the + * event will be subscribed to on the current browsing + * context. + * @returns {Promise<(function(): Promise)>} Callback + * for unsubscribing from the created subscription. + */ + subscribe: async function(params = {}) { + assertBidiIsEnabled(); + return window.test_driver_internal.bidi.speculation + .prefetch_status_updated.subscribe(params); + }, + /** + * Adds an event listener for the event. + * + * @param {function(PrefetchStatusUpdated): void} callback The + * callback to be called when the event is emitted. The + * callback is called with the event object as a parameter. + * @returns {function(): void} A function that removes the + * added event listener when called. + */ + on: function(callback) { + assertBidiIsEnabled(); + return window.test_driver_internal.bidi.speculation + .prefetch_status_updated.on(callback); + }, + /** + * Adds an event listener for the event that is only called + * once and removed afterward. + * + * @return {Promise} The promise which + * is resolved with the event object when the event is emitted. + */ + once: function() { + assertBidiIsEnabled(); + return new Promise(resolve => { + const remove_handler = + window.test_driver_internal.bidi.speculation + .prefetch_status_updated.on(event => { + resolve(event); + remove_handler(); + }); + }); + } + } + }, /** * `emulation `_ module. */ @@ -882,6 +962,61 @@ return window.test_driver_internal.bidi.emulation.set_screen_orientation_override( params); }, + /** + * Overrides the touch configuration for the specified browsing + * contexts. + * Matches the `emulation.setTouchOverride + * `_ + * WebDriver BiDi command. + * + * @example + * await test_driver.bidi.emulation.set_touch_override({ + * maxTouchPoints: 5 + * }); + * + * @param {object} params - Parameters for the command. + * @param {null|number} params.maxTouchPoints - The + * maximum number of simultaneous touch points to support. + * If null or omitted, the override will be removed. + * @param {null|Array.<(Context)>} [params.contexts] The + * optional contexts parameter specifies which browsing contexts + * to set the touch override on. It should be either an array of + * Context objects (window or browsing context id), or null. If + * null or omitted, the override will be set on the current + * browsing context. + * @returns {Promise} Resolves when the touch + * override is successfully set. + */ + set_touch_override: function (params) { + assertBidiIsEnabled(); + return window.test_driver_internal.bidi.emulation.set_touch_override( + params); + }, + }, + /** + * `user_agent_client_hints `_ module. + */ + user_agent_client_hints: { + /** + * Overrides the user agent client hints configuration for the specified browsing + * contexts. Matches the `userAgentClientHints.setClientHintsOverride + * `_ + * WebDriver BiDi command. + * + * @param {object} params - Parameters for the command. + * @param {null|object} params.clientHints - The client hints to override. + * Matches the `userAgentClientHints.ClientHints` type. + * If null or omitted, the override will be removed. + * @param {null|Array.<(Context)>} [params.contexts] The + * optional contexts parameter specifies which browsing contexts + * to set the override on. + * @returns {Promise} Resolves when the override is successfully set. + */ + set_client_hints_override: function (params) { + assertBidiIsEnabled(); + return window.test_driver_internal.bidi.user_agent_client_hints.set_client_hints_override( + params); + } }, /** * `log `_ module. @@ -973,9 +1108,12 @@ * @param {PermissionState} params.state - a `PermissionState * `_ * value. - * @param {string} [params.origin] - an optional `origin` string to set the + * @param {string} [params.origin] - an optional top-level `origin` string to set the * permission for. If omitted, the permission is set for the * current window's origin. + * @param {string} [params.embeddedOrigin] - an optional embedded `origin` string to set the + * permission for. If omitted, the top-level `origin` is used as the + * embedded origin. * @returns {Promise} fulfilled after the permission is set, or rejected if setting * the permission fails. */ @@ -1191,6 +1329,34 @@ return role; }, + /** + * Get accessibility properties for a DOM element. + * + * @param {Element} element + * @returns {Promise} fulfilled after the accessibility properties are + * returned, or rejected in the cases the WebDriver + * command errors + */ + get_accessibility_properties_for_element: async function(element) { + assertTestIsTentative(); + let acc = await window.test_driver_internal.get_accessibility_properties_for_element(element); + return acc; + }, + + /** + * Get properties for an accessibility node. + * + * @param {String} accId + * @returns {Promise} fulfilled after the accessibility properties are + * returned, or rejected in the cases the WebDriver + * command errors + */ + get_accessibility_properties_for_accessibility_node: async function(accId) { + assertTestIsTentative(); + let acc = await window.test_driver_internal.get_accessibility_properties_for_accessibility_node(accId); + return acc; + }, + /** * Send keys to an element. * @@ -2169,6 +2335,69 @@ */ clear_display_features: function(context=null) { return window.test_driver_internal.clear_display_features(context); + }, + + /** + * Gets the current globally-applied privacy control status + * + * @returns {Promise} Fulfils with an object with boolean property `gpc` + * that encodes the current "do not sell or share" + * signal the browser is configured to convey. + */ + get_global_privacy_control: function() { + return window.test_driver_internal.get_global_privacy_control(); + }, + + /** + * Gets the current globally-applied privacy control status + * + * @param {bool} newValue - The a boolean that is true if the browers + * should convey a "do not sell or share" signal + * and false otherwise + * + * @returns {Promise} Fulfils with an object with boolean property `gpc` + * that encodes the new "do not sell or share" + * after applying the new value. + */ + set_global_privacy_control: function(newValue) { + return window.test_driver_internal.set_global_privacy_control(newValue); + }, + + /** + * Installs a WebExtension. + * + * Matches the `Install WebExtension + * `_ + * WebDriver command. + * + * @param {Object} params - Parameters for loading the extension. + * @param {String} params.type - A type such as "path", "archivePath", or "base64". + * + * @param {String} params.path - The path to the extension's resources if type "path" or "archivePath" is specified. + * + * @param {String} params.value - The base64 encoded value of the extension's resources if type "base64" is specified. + * + * @returns {Promise} Returns the extension identifier as defined in the spec. + * Rejected if the extension fails to load. + */ + install_web_extension: function(params) { + return window.test_driver_internal.install_web_extension(params); + }, + + /** + * Uninstalls a WebExtension. + * + * Matches the `Uninstall WebExtension + * `_ + * WebDriver command. + * + * @param {String} extension_id - The extension identifier. + * + * @returns {Promise} Fulfilled after the extension has been removed. + * Rejected in case the WebDriver command errors out. + */ + uninstall_web_extension: function(extension_id) { + return window.test_driver_internal.uninstall_web_extension(extension_id); } }; @@ -2252,6 +2481,16 @@ set_screen_orientation_override: function (params) { throw new Error( "bidi.emulation.set_screen_orientation_override is not implemented by testdriver-vendor.js"); + }, + set_touch_override: function (params) { + throw new Error( + "bidi.emulation.set_touch_override is not implemented by testdriver-vendor.js"); + } + }, + user_agent_client_hints: { + set_client_hints_override: function (params) { + throw new Error( + "bidi.user_agent_client_hints.set_client_hints_override is not implemented by testdriver-vendor.js"); } }, log: { @@ -2271,6 +2510,18 @@ throw new Error( "bidi.permissions.set_permission() is not implemented by testdriver-vendor.js"); } + }, + speculation: { + prefetch_status_updated: { + async subscribe() { + throw new Error( + 'bidi.speculation.prefetch_status_updated.subscribe is not implemented by testdriver-vendor.js'); + }, + on() { + throw new Error( + 'bidi.speculation.prefetch_status_updated.on is not implemented by testdriver-vendor.js'); + } + }, } }, @@ -2304,6 +2555,14 @@ throw new Error("get_computed_name is a testdriver.js function which cannot be run in this context."); }, + async get_accessibility_properties_for_element(element) { + throw new Error("get_accessibility_properties_for_element is a testdriver.js function which cannot be run in this context."); + }, + + async get_accessibility_properties_for_accessibility_node(accId) { + throw new Error("get_accessibility_properties_for_accessibility_node is a testdriver.js function which cannot be run in this context."); + }, + async send_keys(element, keys) { if (this.in_automation) { throw new Error("send_keys() is not implemented by testdriver-vendor.js"); @@ -2486,6 +2745,14 @@ async clear_display_features(context=null) { throw new Error("clear_display_features() is not implemented by testdriver-vendor.js"); + }, + + async set_global_privacy_control(newValue) { + throw new Error("set_global_privacy_control() is not implemented by testdriver-vendor.js"); + }, + + async get_global_privacy_control() { + throw new Error("get_global_privacy_control() is not implemented by testdriver-vendor.js"); } }; })(); diff --git a/test/fixtures/wpt/resources/testharness.js b/test/fixtures/wpt/resources/testharness.js index f495b62458ba75..c7ce4f51e1db07 100644 --- a/test/fixtures/wpt/resources/testharness.js +++ b/test/fixtures/wpt/resources/testharness.js @@ -5144,7 +5144,7 @@ table#results.assertions > tbody > tr > td:last-child {\ width:35%;\ }\ \ -table#results > thead > > tr > th {\ +table#results > thead > tr > th {\ padding:0;\ padding-bottom:0.5em;\ border-bottom:medium solid black;\ diff --git a/test/fixtures/wpt/resources/web-extensions-helper.js b/test/fixtures/wpt/resources/web-extensions-helper.js new file mode 100644 index 00000000000000..57a40fe84dae0a --- /dev/null +++ b/test/fixtures/wpt/resources/web-extensions-helper.js @@ -0,0 +1,40 @@ +// testharness file with WebExtensions utilities + +/** + * Loads the WebExtension at the path specified and runs the tests defined in the extension's resources. + * Listens to messages sent from the user agent and converts the `browser.test` assertions + * into testharness.js assertions. + * + * @param {string} extensionPath - a path to the extension's resources. + */ + +setup({ explicit_done: true }) +globalThis.runTestsWithWebExtension = function(extensionPath) { + test_driver.install_web_extension({ + type: "path", + path: extensionPath + }) + .then((result) => { + let test; + browser.test.onTestStarted.addListener((data) => { + test = async_test(data.testName) + }) + + browser.test.onTestFinished.addListener((data) => { + test.step(() => { + let description = data.message ? `${data.assertionDescription}. ${data.message}` : data.assertionDescription + assert_true(data.result, description) + }) + + test.done() + + if (!data.result) { + test.set_status(test.FAIL) + } + + if (!data.remainingTests) { + test_driver.uninstall_web_extension(result.extension).then(() => { done() }) + } + }) + }) +} diff --git a/test/fixtures/wpt/resources/webidl2/lib/VERSION.md b/test/fixtures/wpt/resources/webidl2/lib/VERSION.md index 5a3726c6c00fe5..2614a8d194be62 100644 --- a/test/fixtures/wpt/resources/webidl2/lib/VERSION.md +++ b/test/fixtures/wpt/resources/webidl2/lib/VERSION.md @@ -1 +1 @@ -Currently using webidl2.js@6889aee6fc7d65915ab1267825248157dbc50486. +Currently using webidl2.js@e6d8ab852ec4e76596f6e308eb7f2efc8b613bfd. diff --git a/test/fixtures/wpt/resources/webidl2/lib/webidl2.js b/test/fixtures/wpt/resources/webidl2/lib/webidl2.js index 7161def899cf24..bae0b2047595d0 100644 --- a/test/fixtures/wpt/resources/webidl2/lib/webidl2.js +++ b/test/fixtures/wpt/resources/webidl2/lib/webidl2.js @@ -17,7 +17,7 @@ return /******/ (() => { // webpackBootstrap __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "parse": () => (/* binding */ parse) +/* harmony export */ parse: () => (/* binding */ parse) /* harmony export */ }); /* harmony import */ var _tokeniser_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); /* harmony import */ var _productions_enum_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(15); @@ -46,11 +46,22 @@ __webpack_require__.r(__webpack_exports__); +/** @typedef {'callbackInterface'|'dictionary'|'interface'|'mixin'|'namespace'} ExtendableInterfaces */ +/** @typedef {{ extMembers?: import("./productions/container.js").AllowedMember[]}} Extension */ +/** @typedef {Partial>} Extensions */ + +/** + * Parser options. + * @typedef {Object} ParserOptions + * @property {string} [sourceName] + * @property {boolean} [concrete] + * @property {Function[]} [productions] + * @property {Extensions} [extensions] + */ + /** * @param {Tokeniser} tokeniser - * @param {object} options - * @param {boolean} [options.concrete] - * @param {Function[]} [options.productions] + * @param {ParserOptions} options */ function parseByTokens(tokeniser, options) { const source = tokeniser.source; @@ -67,7 +78,9 @@ function parseByTokens(tokeniser, options) { const callback = consume("callback"); if (!callback) return; if (tokeniser.probe("interface")) { - return _productions_callback_interface_js__WEBPACK_IMPORTED_MODULE_10__.CallbackInterface.parse(tokeniser, callback); + return _productions_callback_interface_js__WEBPACK_IMPORTED_MODULE_10__.CallbackInterface.parse(tokeniser, callback, { + ...options?.extensions?.callbackInterface, + }); } return _productions_callback_js__WEBPACK_IMPORTED_MODULE_5__.CallbackFunction.parse(tokeniser, callback); } @@ -75,20 +88,32 @@ function parseByTokens(tokeniser, options) { function interface_(opts) { const base = consume("interface"); if (!base) return; - const ret = - _productions_mixin_js__WEBPACK_IMPORTED_MODULE_7__.Mixin.parse(tokeniser, base, opts) || - _productions_interface_js__WEBPACK_IMPORTED_MODULE_6__.Interface.parse(tokeniser, base, opts) || - error("Interface has no proper body"); - return ret; + return ( + _productions_mixin_js__WEBPACK_IMPORTED_MODULE_7__.Mixin.parse(tokeniser, base, { + ...opts, + ...options?.extensions?.mixin, + }) || + _productions_interface_js__WEBPACK_IMPORTED_MODULE_6__.Interface.parse(tokeniser, base, { + ...opts, + ...options?.extensions?.interface, + }) || + error("Interface has no proper body") + ); } function partial() { const partial = consume("partial"); if (!partial) return; return ( - _productions_dictionary_js__WEBPACK_IMPORTED_MODULE_8__.Dictionary.parse(tokeniser, { partial }) || + _productions_dictionary_js__WEBPACK_IMPORTED_MODULE_8__.Dictionary.parse(tokeniser, { + partial, + ...options?.extensions?.dictionary, + }) || interface_({ partial }) || - _productions_namespace_js__WEBPACK_IMPORTED_MODULE_9__.Namespace.parse(tokeniser, { partial }) || + _productions_namespace_js__WEBPACK_IMPORTED_MODULE_9__.Namespace.parse(tokeniser, { + partial, + ...options?.extensions?.namespace, + }) || error("Partial doesn't apply to anything") ); } @@ -107,11 +132,11 @@ function parseByTokens(tokeniser, options) { callback() || interface_() || partial() || - _productions_dictionary_js__WEBPACK_IMPORTED_MODULE_8__.Dictionary.parse(tokeniser) || + _productions_dictionary_js__WEBPACK_IMPORTED_MODULE_8__.Dictionary.parse(tokeniser, options?.extensions?.dictionary) || _productions_enum_js__WEBPACK_IMPORTED_MODULE_1__.Enum.parse(tokeniser) || _productions_typedef_js__WEBPACK_IMPORTED_MODULE_4__.Typedef.parse(tokeniser) || _productions_includes_js__WEBPACK_IMPORTED_MODULE_2__.Includes.parse(tokeniser) || - _productions_namespace_js__WEBPACK_IMPORTED_MODULE_9__.Namespace.parse(tokeniser) + _productions_namespace_js__WEBPACK_IMPORTED_MODULE_9__.Namespace.parse(tokeniser, options?.extensions?.namespace) ); } @@ -134,6 +159,7 @@ function parseByTokens(tokeniser, options) { } return defs; } + const res = definitions(); if (tokeniser.position < source.length) error("Unrecognised tokens"); return res; @@ -141,11 +167,7 @@ function parseByTokens(tokeniser, options) { /** * @param {string} str - * @param {object} [options] - * @param {*} [options.sourceName] - * @param {boolean} [options.concrete] - * @param {Function[]} [options.productions] - * @return {import("./productions/base.js").Base[]} + * @param {ParserOptions} [options] */ function parse(str, options = {}) { const tokeniser = new _tokeniser_js__WEBPACK_IMPORTED_MODULE_0__.Tokeniser(str); @@ -163,11 +185,11 @@ function parse(str, options = {}) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Tokeniser": () => (/* binding */ Tokeniser), -/* harmony export */ "WebIDLParseError": () => (/* binding */ WebIDLParseError), -/* harmony export */ "argumentNameKeywords": () => (/* binding */ argumentNameKeywords), -/* harmony export */ "stringTypes": () => (/* binding */ stringTypes), -/* harmony export */ "typeNameKeywords": () => (/* binding */ typeNameKeywords) +/* harmony export */ Tokeniser: () => (/* binding */ Tokeniser), +/* harmony export */ WebIDLParseError: () => (/* binding */ WebIDLParseError), +/* harmony export */ argumentNameKeywords: () => (/* binding */ argumentNameKeywords), +/* harmony export */ stringTypes: () => (/* binding */ stringTypes), +/* harmony export */ typeNameKeywords: () => (/* binding */ typeNameKeywords) /* harmony export */ }); /* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3); /* harmony import */ var _productions_helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); @@ -191,6 +213,7 @@ const tokenRe = { const typeNameKeywords = [ "ArrayBuffer", + "SharedArrayBuffer", "DataView", "Int8Array", "Int16Array", @@ -201,6 +224,7 @@ const typeNameKeywords = [ "Uint8ClampedArray", "BigInt64Array", "BigUint64Array", + "Float16Array", "Float32Array", "Float64Array", "any", @@ -243,6 +267,8 @@ const nonRegexTerminals = [ "NaN", "ObservableArray", "Promise", + "async_iterable", + "async_sequence", "bigint", "boolean", "byte", @@ -327,10 +353,10 @@ function tokenise(str) { if (result !== -1) { if (reserved.includes(token.value)) { const message = `${(0,_productions_helpers_js__WEBPACK_IMPORTED_MODULE_1__.unescape)( - token.value + token.value, )} is a reserved identifier and must not be used.`; throw new WebIDLParseError( - (0,_error_js__WEBPACK_IMPORTED_MODULE_0__.syntaxError)(tokens, lastIndex, null, message) + (0,_error_js__WEBPACK_IMPORTED_MODULE_0__.syntaxError)(tokens, lastIndex, null, message), ); } else if (nonRegexTerminals.includes(token.value)) { token.type = "inline"; @@ -414,7 +440,7 @@ class Tokeniser { */ error(message) { throw new WebIDLParseError( - (0,_error_js__WEBPACK_IMPORTED_MODULE_0__.syntaxError)(this.source, this.position, this.current, message) + (0,_error_js__WEBPACK_IMPORTED_MODULE_0__.syntaxError)(this.source, this.position, this.current, message), ); } @@ -522,8 +548,8 @@ class WebIDLParseError extends Error { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "syntaxError": () => (/* binding */ syntaxError), -/* harmony export */ "validationError": () => (/* binding */ validationError) +/* harmony export */ syntaxError: () => (/* binding */ syntaxError), +/* harmony export */ validationError: () => (/* binding */ validationError) /* harmony export */ }); /** * @param {string} text @@ -572,7 +598,7 @@ function error( current, message, kind, - { level = "error", autofix, ruleName } = {} + { level = "error", autofix, ruleName } = {}, ) { /** * @param {number} count @@ -606,11 +632,11 @@ function error( source[position].type !== "eof" ? source[position].line : source.length > 1 - ? source[position - 1].line - : 1; + ? source[position - 1].line + : 1; const precedingLastLine = lastLine( - tokensToText(sliceTokens(-maxTokens), { precedes: true }) + tokensToText(sliceTokens(-maxTokens), { precedes: true }), ); const subsequentTokens = sliceTokens(maxTokens); @@ -625,7 +651,7 @@ function error( const grammaticalContext = current && current.name ? `, ${contextType} \`${current.partial ? "partial " : ""}${contextAsText( - current + current, )}\`` : ""; const context = `${kind} error at line ${line}${inSourceName}${grammaticalContext}:\n${sourceContext}`; @@ -659,7 +685,7 @@ function validationError( current, ruleName, message, - options = {} + options = {}, ) { options.ruleName = ruleName; return error( @@ -668,7 +694,7 @@ function validationError( current, message, "Validation", - options + options, ); } @@ -679,21 +705,21 @@ function validationError( __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "argument_list": () => (/* binding */ argument_list), -/* harmony export */ "autoParenter": () => (/* binding */ autoParenter), -/* harmony export */ "autofixAddExposedWindow": () => (/* binding */ autofixAddExposedWindow), -/* harmony export */ "const_data": () => (/* binding */ const_data), -/* harmony export */ "const_value": () => (/* binding */ const_value), -/* harmony export */ "findLastIndex": () => (/* binding */ findLastIndex), -/* harmony export */ "getFirstToken": () => (/* binding */ getFirstToken), -/* harmony export */ "getLastIndentation": () => (/* binding */ getLastIndentation), -/* harmony export */ "getMemberIndentation": () => (/* binding */ getMemberIndentation), -/* harmony export */ "list": () => (/* binding */ list), -/* harmony export */ "primitive_type": () => (/* binding */ primitive_type), -/* harmony export */ "return_type": () => (/* binding */ return_type), -/* harmony export */ "stringifier": () => (/* binding */ stringifier), -/* harmony export */ "type_with_extended_attributes": () => (/* binding */ type_with_extended_attributes), -/* harmony export */ "unescape": () => (/* binding */ unescape) +/* harmony export */ argument_list: () => (/* binding */ argument_list), +/* harmony export */ autoParenter: () => (/* binding */ autoParenter), +/* harmony export */ autofixAddExposedWindow: () => (/* binding */ autofixAddExposedWindow), +/* harmony export */ const_data: () => (/* binding */ const_data), +/* harmony export */ const_value: () => (/* binding */ const_value), +/* harmony export */ findLastIndex: () => (/* binding */ findLastIndex), +/* harmony export */ getFirstToken: () => (/* binding */ getFirstToken), +/* harmony export */ getLastIndentation: () => (/* binding */ getLastIndentation), +/* harmony export */ getMemberIndentation: () => (/* binding */ getMemberIndentation), +/* harmony export */ list: () => (/* binding */ list), +/* harmony export */ primitive_type: () => (/* binding */ primitive_type), +/* harmony export */ return_type: () => (/* binding */ return_type), +/* harmony export */ stringifier: () => (/* binding */ stringifier), +/* harmony export */ type_with_extended_attributes: () => (/* binding */ type_with_extended_attributes), +/* harmony export */ unescape: () => (/* binding */ unescape) /* harmony export */ }); /* harmony import */ var _type_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5); /* harmony import */ var _argument_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); @@ -816,7 +842,7 @@ function primitive_type(tokeniser) { "boolean", "byte", "octet", - "undefined" + "undefined", ); if (base) { return new _type_js__WEBPACK_IMPORTED_MODULE_0__.Type({ source, tokens: { base } }); @@ -917,7 +943,7 @@ function autofixAddExposedWindow(def) { def.extAttrs.unshift(exposed); } else { autoParenter(def).extAttrs = _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__.ExtendedAttributes.parse( - new _tokeniser_js__WEBPACK_IMPORTED_MODULE_5__.Tokeniser("[Exposed=Window]") + new _tokeniser_js__WEBPACK_IMPORTED_MODULE_5__.Tokeniser("[Exposed=Window]"), ); const trivia = def.tokens.base.trivia; def.extAttrs.tokens.open.trivia = trivia; @@ -1010,7 +1036,7 @@ function autoParenter(data, parent) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Type": () => (/* binding */ Type) +/* harmony export */ Type: () => (/* binding */ Type) /* harmony export */ }); /* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); /* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); @@ -1034,14 +1060,15 @@ function generic_type(tokeniser, typeName) { "FrozenArray", "ObservableArray", "Promise", + "async_sequence", "sequence", - "record" + "record", ); if (!base) { return; } const ret = (0,_helpers_js__WEBPACK_IMPORTED_MODULE_1__.autoParenter)( - new Type({ source: tokeniser.source, tokens: { base } }) + new Type({ source: tokeniser.source, tokens: { base } }), ); ret.tokens.open = tokeniser.consume("<") || @@ -1056,6 +1083,7 @@ function generic_type(tokeniser, typeName) { ret.subtype.push(subtype); break; } + case "async_sequence": case "sequence": case "FrozenArray": case "ObservableArray": { @@ -1143,7 +1171,7 @@ function union_type(tokeniser, type) { ret.type = type || null; while (true) { const typ = - (0,_helpers_js__WEBPACK_IMPORTED_MODULE_1__.type_with_extended_attributes)(tokeniser) || + (0,_helpers_js__WEBPACK_IMPORTED_MODULE_1__.type_with_extended_attributes)(tokeniser, type) || tokeniser.error("No type after open parenthesis or 'or' in union type"); if (typ.idlType === "any") tokeniser.error("Type `any` cannot be included in a union type"); @@ -1157,7 +1185,7 @@ function union_type(tokeniser, type) { } if (ret.idlType.length < 2) { tokeniser.error( - "At least two types are expected in a union type but found less" + "At least two types are expected in a union type but found less", ); } tokens.close = @@ -1208,6 +1236,26 @@ class Type extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { *validate(defs) { yield* this.extAttrs.validate(defs); + if (this.idlType === "BufferSource") { + // XXX: For now this is a hack. Consider moving parents' extAttrs into types as the spec says: + // https://webidl.spec.whatwg.org/#idl-annotated-types + for (const extAttrs of [this.extAttrs, this.parent?.extAttrs]) { + for (const extAttr of extAttrs) { + if (extAttr.name !== "AllowShared") { + continue; + } + const message = `\`[AllowShared] BufferSource\` is now replaced with AllowSharedBufferSource.`; + yield (0,_error_js__WEBPACK_IMPORTED_MODULE_3__.validationError)( + this.tokens.base, + this, + "migrate-allowshared", + message, + { autofix: replaceAllowShared(this, extAttr, extAttrs) }, + ); + } + } + } + if (this.idlType === "void") { const message = `\`void\` is now replaced by \`undefined\`. Refer to the \ [relevant GitHub issue](https://github.com/whatwg/webidl/issues/60) \ @@ -1225,8 +1273,8 @@ for more information.`; const target = this.union ? this : typedef && typedef.type === "typedef" - ? typedef.idlType - : undefined; + ? typedef.idlType + : undefined; if (target && this.nullable) { // do not allow any dictionary const { reference } = (0,_validators_helpers_js__WEBPACK_IMPORTED_MODULE_4__.idlTypeIncludesDictionary)(target, defs) || {}; @@ -1237,7 +1285,7 @@ for more information.`; targetToken, this, "no-nullable-union-dict", - message + message, ); } } else { @@ -1274,7 +1322,7 @@ for more information.`; this.idlType ), context: this, - } + }, ); return w.ts.wrap([w.ts.trivia(firstToken.trivia), ref]); }; @@ -1287,6 +1335,23 @@ for more information.`; } } +/** + * @param {Type} type + * @param {import("./extended-attributes.js").SimpleExtendedAttribute} extAttr + * @param {ExtendedAttributes} extAttrs + */ +function replaceAllowShared(type, extAttr, extAttrs) { + return () => { + const index = extAttrs.indexOf(extAttr); + extAttrs.splice(index, 1); + if (!extAttrs.length && type.tokens.base.trivia.match(/^\s$/)) { + type.tokens.base.trivia = ""; // (let's not remove comments) + } + + type.tokens.base.value = "AllowSharedBufferSource"; + }; +} + /** * @param {Type} type */ @@ -1303,7 +1368,7 @@ function replaceVoid(type) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Base": () => (/* binding */ Base) +/* harmony export */ Base: () => (/* binding */ Base) /* harmony export */ }); class Base { /** @@ -1344,14 +1409,17 @@ class Base { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "dictionaryIncludesRequiredField": () => (/* binding */ dictionaryIncludesRequiredField), -/* harmony export */ "idlTypeIncludesDictionary": () => (/* binding */ idlTypeIncludesDictionary) +/* harmony export */ dictionaryIncludesRequiredField: () => (/* binding */ dictionaryIncludesRequiredField), +/* harmony export */ idlTypeIncludesDictionary: () => (/* binding */ idlTypeIncludesDictionary), +/* harmony export */ idlTypeIncludesEnforceRange: () => (/* binding */ idlTypeIncludesEnforceRange) /* harmony export */ }); /** + * @typedef {import("../validator.js").Definitions} Definitions * @typedef {import("../productions/dictionary.js").Dictionary} Dictionary + * @typedef {import("../../lib/productions/type").Type} Type * - * @param {*} idlType - * @param {import("../validator.js").Definitions} defs + * @param {Type} idlType + * @param {Definitions} defs * @param {object} [options] * @param {boolean} [options.useNullableInner] use when the input idlType is nullable and you want to use its inner type * @return {{ reference: *, dictionary: Dictionary }} the type reference that ultimately includes dictionary. @@ -1359,7 +1427,7 @@ __webpack_require__.r(__webpack_exports__); function idlTypeIncludesDictionary( idlType, defs, - { useNullableInner } = {} + { useNullableInner } = {}, ) { if (!idlType.union) { const def = defs.unique.get(idlType.idlType); @@ -1405,8 +1473,8 @@ function idlTypeIncludesDictionary( } /** - * @param {*} dict dictionary type - * @param {import("../validator.js").Definitions} defs + * @param {Dictionary} dict dictionary type + * @param {Definitions} defs * @return {boolean} */ function dictionaryIncludesRequiredField(dict, defs) { @@ -1430,6 +1498,34 @@ function dictionaryIncludesRequiredField(dict, defs) { return result; } +/** + * For now this only checks the most frequent cases: + * 1. direct inclusion of [EnforceRange] + * 2. typedef of that + * + * More complex cases with dictionaries and records are not covered yet. + * + * @param {Type} idlType + * @param {Definitions} defs + */ +function idlTypeIncludesEnforceRange(idlType, defs) { + if (idlType.union) { + // TODO: This should ideally be checked too + return false; + } + + if (idlType.extAttrs.some((e) => e.name === "EnforceRange")) { + return true; + } + + const def = defs.unique.get(idlType.idlType); + if (def?.type !== "typedef") { + return false; + } + + return def.idlType.extAttrs.some((e) => e.name === "EnforceRange"); +} + /***/ }), /* 8 */ @@ -1437,9 +1533,9 @@ function dictionaryIncludesRequiredField(dict, defs) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "ExtendedAttributeParameters": () => (/* binding */ ExtendedAttributeParameters), -/* harmony export */ "ExtendedAttributes": () => (/* binding */ ExtendedAttributes), -/* harmony export */ "SimpleExtendedAttribute": () => (/* binding */ SimpleExtendedAttribute) +/* harmony export */ ExtendedAttributeParameters: () => (/* binding */ ExtendedAttributeParameters), +/* harmony export */ ExtendedAttributes: () => (/* binding */ ExtendedAttributes), +/* harmony export */ SimpleExtendedAttribute: () => (/* binding */ SimpleExtendedAttribute) /* harmony export */ }); /* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); /* harmony import */ var _array_base_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9); @@ -1494,7 +1590,7 @@ function extAttrListItems(tokeniser) { } } tokeniser.error( - `Expected identifiers, strings, decimals, or integers but none found` + `Expected identifiers, strings, decimals, or integers but none found`, ); } @@ -1505,7 +1601,7 @@ class ExtendedAttributeParameters extends _base_js__WEBPACK_IMPORTED_MODULE_0__. static parse(tokeniser) { const tokens = { assign: tokeniser.consume("=") }; const ret = (0,_helpers_js__WEBPACK_IMPORTED_MODULE_3__.autoParenter)( - new ExtendedAttributeParameters({ source: tokeniser.source, tokens }) + new ExtendedAttributeParameters({ source: tokeniser.source, tokens }), ); ret.list = []; if (tokens.assign) { @@ -1603,8 +1699,8 @@ class SimpleExtendedAttribute extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base const value = this.params.rhsIsList ? list : this.params.tokens.secondaryName - ? (0,_helpers_js__WEBPACK_IMPORTED_MODULE_3__.unescape)(tokens.secondaryName.value) - : null; + ? (0,_helpers_js__WEBPACK_IMPORTED_MODULE_3__.unescape)(tokens.secondaryName.value) + : null; return { type, value }; } get arguments() { @@ -1627,7 +1723,7 @@ information.`; this, "no-nointerfaceobject", message, - { level: "warning" } + { level: "warning" }, ); } else if (renamedLegacies.has(name)) { const message = `\`[${name}]\` extended attribute is a legacy feature \ @@ -1652,7 +1748,7 @@ information.`; w.ts.wrap([ w.ts.extendedAttributeReference(this.name), this.params.write(w), - ]) + ]), ), w.token(this.tokens.separator), ]); @@ -1687,12 +1783,12 @@ class ExtendedAttributes extends _array_base_js__WEBPACK_IMPORTED_MODULE_1__.Arr ...(0,_helpers_js__WEBPACK_IMPORTED_MODULE_3__.list)(tokeniser, { parser: SimpleExtendedAttribute.parse, listName: "extended attribute", - }) + }), ); tokens.close = tokeniser.consume("]") || tokeniser.error( - "Expected a closing token for the extended attribute list" + "Expected a closing token for the extended attribute list", ); if (!ret.length) { tokeniser.unconsume(tokens.close.index); @@ -1700,7 +1796,7 @@ class ExtendedAttributes extends _array_base_js__WEBPACK_IMPORTED_MODULE_1__.Arr } if (tokeniser.probe("[")) { tokeniser.error( - "Illegal double extended attribute lists, consider merging them" + "Illegal double extended attribute lists, consider merging them", ); } return ret; @@ -1730,7 +1826,7 @@ class ExtendedAttributes extends _array_base_js__WEBPACK_IMPORTED_MODULE_1__.Arr __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "ArrayBase": () => (/* binding */ ArrayBase) +/* harmony export */ ArrayBase: () => (/* binding */ ArrayBase) /* harmony export */ }); class ArrayBase extends Array { constructor({ source, tokens }) { @@ -1750,8 +1846,8 @@ class ArrayBase extends Array { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Eof": () => (/* binding */ Eof), -/* harmony export */ "WrappedToken": () => (/* binding */ WrappedToken) +/* harmony export */ Eof: () => (/* binding */ Eof), +/* harmony export */ WrappedToken: () => (/* binding */ WrappedToken) /* harmony export */ }); /* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); /* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); @@ -1775,6 +1871,10 @@ class WrappedToken extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { }; } + get type() { + return this.tokens.value.type; + } + get value() { return (0,_helpers_js__WEBPACK_IMPORTED_MODULE_1__.unescape)(this.tokens.value.value); } @@ -1811,7 +1911,7 @@ class Eof extends WrappedToken { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Argument": () => (/* binding */ Argument) +/* harmony export */ Argument: () => (/* binding */ Argument) /* harmony export */ }); /* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); /* harmony import */ var _default_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(12); @@ -1837,7 +1937,7 @@ class Argument extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { /** @type {Base["tokens"]} */ const tokens = {}; const ret = (0,_helpers_js__WEBPACK_IMPORTED_MODULE_3__.autoParenter)( - new Argument({ source: tokeniser.source, tokens }) + new Argument({ source: tokeniser.source, tokens }), ); ret.extAttrs = _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__.ExtendedAttributes.parse(tokeniser); tokens.optional = tokeniser.consume("optional"); @@ -1887,7 +1987,7 @@ class Argument extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { this.tokens.name, this, "no-nullable-dict-arg", - message + message, ); } else if (!this.optional) { if ( @@ -1903,7 +2003,7 @@ class Argument extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { message, { autofix: autofixDictionaryArgumentOptionality(this), - } + }, ); } } else if (!this.default) { @@ -1915,7 +2015,7 @@ class Argument extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { message, { autofix: autofixOptionalDictionaryDefaultValue(this), - } + }, ); } } @@ -1977,7 +2077,7 @@ function autofixOptionalDictionaryDefaultValue(arg) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Default": () => (/* binding */ Default) +/* harmony export */ Default: () => (/* binding */ Default) /* harmony export */ }); /* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); /* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); @@ -2049,7 +2149,7 @@ class Default extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Operation": () => (/* binding */ Operation) +/* harmony export */ Operation: () => (/* binding */ Operation) /* harmony export */ }); /* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); /* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); @@ -2060,17 +2160,15 @@ __webpack_require__.r(__webpack_exports__); class Operation extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { /** - * @typedef {import("../tokeniser.js").Token} Token - * * @param {import("../tokeniser.js").Tokeniser} tokeniser * @param {object} [options] - * @param {Token} [options.special] - * @param {Token} [options.regular] + * @param {import("../tokeniser.js").Token} [options.special] + * @param {import("../tokeniser.js").Token} [options.regular] */ static parse(tokeniser, { special, regular } = {}) { const tokens = { special }; const ret = (0,_helpers_js__WEBPACK_IMPORTED_MODULE_1__.autoParenter)( - new Operation({ source: tokeniser.source, tokens }) + new Operation({ source: tokeniser.source, tokens }), ); if (special && special.value === "stringifier") { tokens.termination = tokeniser.consume(";"); @@ -2121,6 +2219,15 @@ class Operation extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { yield (0,_error_js__WEBPACK_IMPORTED_MODULE_2__.validationError)(this.tokens.open, this, "incomplete-op", message); } if (this.idlType) { + if (this.idlType.generic === "async_sequence") { + const message = `async_sequence types cannot be returned by an operation.`; + yield (0,_error_js__WEBPACK_IMPORTED_MODULE_2__.validationError)( + this.idlType.tokens.base, + this, + "async-sequence-idl-to-js", + message, + ); + } yield* this.idlType.validate(defs); } for (const argument of this.arguments) { @@ -2149,7 +2256,7 @@ class Operation extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { ...body, w.token(this.tokens.termination), ]), - { data: this, parent } + { data: this, parent }, ); } } @@ -2161,7 +2268,7 @@ class Operation extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Attribute": () => (/* binding */ Attribute) +/* harmony export */ Attribute: () => (/* binding */ Attribute) /* harmony export */ }); /* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3); /* harmony import */ var _validators_helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7); @@ -2182,12 +2289,12 @@ class Attribute extends _base_js__WEBPACK_IMPORTED_MODULE_2__.Base { */ static parse( tokeniser, - { special, noInherit = false, readonly = false } = {} + { special, noInherit = false, readonly = false } = {}, ) { const start_position = tokeniser.position; const tokens = { special }; const ret = (0,_helpers_js__WEBPACK_IMPORTED_MODULE_3__.autoParenter)( - new Attribute({ source: tokeniser.source, tokens }) + new Attribute({ source: tokeniser.source, tokens }), ); if (!special && !noInherit) { tokens.special = tokeniser.consume("inherit"); @@ -2237,32 +2344,34 @@ class Attribute extends _base_js__WEBPACK_IMPORTED_MODULE_2__.Base { yield* this.extAttrs.validate(defs); yield* this.idlType.validate(defs); - switch (this.idlType.generic) { - case "sequence": - case "record": { - const message = `Attributes cannot accept ${this.idlType.generic} types.`; - yield (0,_error_js__WEBPACK_IMPORTED_MODULE_0__.validationError)( - this.tokens.name, - this, - "attr-invalid-type", - message - ); - break; + if ( + ["async_sequence", "sequence", "record"].includes(this.idlType.generic) + ) { + const message = `Attributes cannot accept ${this.idlType.generic} types.`; + yield (0,_error_js__WEBPACK_IMPORTED_MODULE_0__.validationError)( + this.tokens.name, + this, + "attr-invalid-type", + message, + ); + } + + { + const { reference } = (0,_validators_helpers_js__WEBPACK_IMPORTED_MODULE_1__.idlTypeIncludesDictionary)(this.idlType, defs) || {}; + if (reference) { + const targetToken = (this.idlType.union ? reference : this.idlType) + .tokens.base; + const message = "Attributes cannot accept dictionary types."; + yield (0,_error_js__WEBPACK_IMPORTED_MODULE_0__.validationError)(targetToken, this, "attr-invalid-type", message); } - default: { - const { reference } = - (0,_validators_helpers_js__WEBPACK_IMPORTED_MODULE_1__.idlTypeIncludesDictionary)(this.idlType, defs) || {}; - if (reference) { - const targetToken = (this.idlType.union ? reference : this.idlType) - .tokens.base; - const message = "Attributes cannot accept dictionary types."; - yield (0,_error_js__WEBPACK_IMPORTED_MODULE_0__.validationError)( - targetToken, - this, - "attr-invalid-type", - message - ); - } + } + + if (this.readonly) { + if ((0,_validators_helpers_js__WEBPACK_IMPORTED_MODULE_1__.idlTypeIncludesEnforceRange)(this.idlType, defs)) { + const targetToken = this.idlType.tokens.base; + const message = + "Readonly attributes cannot accept [EnforceRange] extended attribute."; + yield (0,_error_js__WEBPACK_IMPORTED_MODULE_0__.validationError)(targetToken, this, "attr-invalid-type", message); } } } @@ -2280,7 +2389,7 @@ class Attribute extends _base_js__WEBPACK_IMPORTED_MODULE_2__.Base { w.name_token(this.tokens.name, { data: this, parent }), w.token(this.tokens.termination), ]), - { data: this, parent } + { data: this, parent }, ); } } @@ -2292,8 +2401,8 @@ class Attribute extends _base_js__WEBPACK_IMPORTED_MODULE_2__.Base { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Enum": () => (/* binding */ Enum), -/* harmony export */ "EnumValue": () => (/* binding */ EnumValue) +/* harmony export */ Enum: () => (/* binding */ Enum), +/* harmony export */ EnumValue: () => (/* binding */ EnumValue) /* harmony export */ }); /* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4); /* harmony import */ var _token_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(10); @@ -2327,7 +2436,7 @@ class EnumValue extends _token_js__WEBPACK_IMPORTED_MODULE_1__.WrappedToken { w.ts.trivia(this.tokens.value.trivia), w.ts.definition( w.ts.wrap(['"', w.ts.name(this.value, { data: this, parent }), '"']), - { data: this, parent } + { data: this, parent }, ), w.token(this.tokens.separator), ]); @@ -2388,7 +2497,7 @@ class Enum extends _base_js__WEBPACK_IMPORTED_MODULE_2__.Base { w.token(this.tokens.close), w.token(this.tokens.termination), ]), - { data: this } + { data: this }, ); } } @@ -2400,7 +2509,7 @@ class Enum extends _base_js__WEBPACK_IMPORTED_MODULE_2__.Base { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Includes": () => (/* binding */ Includes) +/* harmony export */ Includes: () => (/* binding */ Includes) /* harmony export */ }); /* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); /* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); @@ -2451,7 +2560,7 @@ class Includes extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { w.reference_token(this.tokens.mixin, this), w.token(this.tokens.termination), ]), - { data: this } + { data: this }, ); } } @@ -2463,7 +2572,7 @@ class Includes extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Typedef": () => (/* binding */ Typedef) +/* harmony export */ Typedef: () => (/* binding */ Typedef) /* harmony export */ }); /* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); /* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); @@ -2516,7 +2625,7 @@ class Typedef extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { w.name_token(this.tokens.name, { data: this }), w.token(this.tokens.termination), ]), - { data: this } + { data: this }, ); } } @@ -2528,10 +2637,12 @@ class Typedef extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "CallbackFunction": () => (/* binding */ CallbackFunction) +/* harmony export */ CallbackFunction: () => (/* binding */ CallbackFunction) /* harmony export */ }); /* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); /* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); +/* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(3); + @@ -2542,7 +2653,7 @@ class CallbackFunction extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { static parse(tokeniser, base) { const tokens = { base }; const ret = (0,_helpers_js__WEBPACK_IMPORTED_MODULE_1__.autoParenter)( - new CallbackFunction({ source: tokeniser.source, tokens }) + new CallbackFunction({ source: tokeniser.source, tokens }), ); tokens.name = tokeniser.consumeKind("identifier") || @@ -2573,6 +2684,18 @@ class CallbackFunction extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { *validate(defs) { yield* this.extAttrs.validate(defs); + for (const arg of this.arguments) { + yield* arg.validate(defs); + if (arg.idlType.generic === "async_sequence") { + const message = `async_sequence types cannot be returned as a callback argument.`; + yield (0,_error_js__WEBPACK_IMPORTED_MODULE_2__.validationError)( + arg.tokens.name, + arg, + "async-sequence-idl-to-js", + message, + ); + } + } yield* this.idlType.validate(defs); } @@ -2590,7 +2713,7 @@ class CallbackFunction extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { w.token(this.tokens.close), w.token(this.tokens.termination), ]), - { data: this } + { data: this }, ); } } @@ -2602,7 +2725,7 @@ class CallbackFunction extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Interface": () => (/* binding */ Interface) +/* harmony export */ Interface: () => (/* binding */ Interface) /* harmony export */ }); /* harmony import */ var _container_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(20); /* harmony import */ var _attribute_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(14); @@ -2643,8 +2766,12 @@ function static_member(tokeniser) { class Interface extends _container_js__WEBPACK_IMPORTED_MODULE_0__.Container { /** * @param {import("../tokeniser.js").Tokeniser} tokeniser + * @param {import("../tokeniser.js").Token} base + * @param {object} [options] + * @param {import("./container.js").AllowedMember[]} [options.extMembers] + * @param {import("../tokeniser.js").Token|null} [options.partial] */ - static parse(tokeniser, base, { partial = null } = {}) { + static parse(tokeniser, base, { extMembers = [], partial = null } = {}) { const tokens = { partial, base }; return _container_js__WEBPACK_IMPORTED_MODULE_0__.Container.parse( tokeniser, @@ -2652,6 +2779,7 @@ class Interface extends _container_js__WEBPACK_IMPORTED_MODULE_0__.Container { { inheritable: !partial, allowedMembers: [ + ...extMembers, [_constant_js__WEBPACK_IMPORTED_MODULE_3__.Constant.parse], [_constructor_js__WEBPACK_IMPORTED_MODULE_8__.Constructor.parse], [static_member], @@ -2660,7 +2788,7 @@ class Interface extends _container_js__WEBPACK_IMPORTED_MODULE_0__.Container { [_attribute_js__WEBPACK_IMPORTED_MODULE_1__.Attribute.parse], [_operation_js__WEBPACK_IMPORTED_MODULE_2__.Operation.parse], ], - } + }, ); } @@ -2686,11 +2814,11 @@ for more information.`; message, { autofix: (0,_helpers_js__WEBPACK_IMPORTED_MODULE_5__.autofixAddExposedWindow)(this), - } + }, ); } const oldConstructors = this.extAttrs.filter( - (extAttr) => extAttr.name === "Constructor" + (extAttr) => extAttr.name === "Constructor", ); for (const constructor of oldConstructors) { const message = `Constructors should now be represented as a \`constructor()\` operation on the interface \ @@ -2704,14 +2832,14 @@ for more information.`; message, { autofix: autofixConstructor(this, constructor), - } + }, ); } const isGlobal = this.extAttrs.some((extAttr) => extAttr.name === "Global"); if (isGlobal) { const factoryFunctions = this.extAttrs.filter( - (extAttr) => extAttr.name === "LegacyFactoryFunction" + (extAttr) => extAttr.name === "LegacyFactoryFunction", ); for (const named of factoryFunctions) { const message = `Interfaces marked as \`[Global]\` cannot have factory functions.`; @@ -2719,12 +2847,12 @@ for more information.`; named.tokens.name, this, "no-constructible-global", - message + message, ); } const constructors = this.members.filter( - (member) => member.type === "constructor" + (member) => member.type === "constructor", ); for (const named of constructors) { const message = `Interfaces marked as \`[Global]\` cannot have constructors.`; @@ -2732,7 +2860,7 @@ for more information.`; named.tokens.base, this, "no-constructible-global", - message + message, ); } } @@ -2748,13 +2876,13 @@ function autofixConstructor(interfaceDef, constructorExtAttr) { interfaceDef = (0,_helpers_js__WEBPACK_IMPORTED_MODULE_5__.autoParenter)(interfaceDef); return () => { const indentation = (0,_helpers_js__WEBPACK_IMPORTED_MODULE_5__.getLastIndentation)( - interfaceDef.extAttrs.tokens.open.trivia + interfaceDef.extAttrs.tokens.open.trivia, ); const memberIndent = interfaceDef.members.length ? (0,_helpers_js__WEBPACK_IMPORTED_MODULE_5__.getLastIndentation)((0,_helpers_js__WEBPACK_IMPORTED_MODULE_5__.getFirstToken)(interfaceDef.members[0]).trivia) : (0,_helpers_js__WEBPACK_IMPORTED_MODULE_5__.getMemberIndentation)(indentation); const constructorOp = _constructor_js__WEBPACK_IMPORTED_MODULE_8__.Constructor.parse( - new _tokeniser_js__WEBPACK_IMPORTED_MODULE_9__.Tokeniser(`\n${memberIndent}constructor();`) + new _tokeniser_js__WEBPACK_IMPORTED_MODULE_9__.Tokeniser(`\n${memberIndent}constructor();`), ); constructorOp.extAttrs = new _extended_attributes_js__WEBPACK_IMPORTED_MODULE_10__.ExtendedAttributes({ source: interfaceDef.source, @@ -2764,7 +2892,7 @@ function autofixConstructor(interfaceDef, constructorExtAttr) { const existingIndex = (0,_helpers_js__WEBPACK_IMPORTED_MODULE_5__.findLastIndex)( interfaceDef.members, - (m) => m.type === "constructor" + (m) => m.type === "constructor", ); interfaceDef.members.splice(existingIndex + 1, 0, constructorOp); @@ -2793,7 +2921,7 @@ function autofixConstructor(interfaceDef, constructorExtAttr) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Container": () => (/* binding */ Container) +/* harmony export */ Container: () => (/* binding */ Container) /* harmony export */ }); /* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); /* harmony import */ var _extended_attributes_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8); @@ -2816,6 +2944,19 @@ function inheritance(tokeniser) { return { colon, inheritance }; } +/** + * Parser callback. + * @callback ParserCallback + * @param {import("../tokeniser.js").Tokeniser} tokeniser + * @param {...*} args + */ + +/** + * A parser callback and optional option object. + * @typedef AllowedMember + * @type {[ParserCallback, object?]} + */ + class Container extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { /** * @param {import("../tokeniser.js").Tokeniser} tokeniser @@ -2889,7 +3030,7 @@ class Container extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { w.token(this.tokens.colon), w.ts.trivia(this.tokens.inheritance.trivia), w.ts.inheritance( - w.reference(this.tokens.inheritance.value, { context: this }) + w.reference(this.tokens.inheritance.value, { context: this }), ), ]); }; @@ -2908,7 +3049,7 @@ class Container extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { w.token(this.tokens.close), w.token(this.tokens.termination), ]), - { data: this } + { data: this }, ); } } @@ -2920,7 +3061,7 @@ class Container extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Constant": () => (/* binding */ Constant) +/* harmony export */ Constant: () => (/* binding */ Constant) /* harmony export */ }); /* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); /* harmony import */ var _type_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(5); @@ -2989,7 +3130,7 @@ class Constant extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { w.token(this.tokens.value), w.token(this.tokens.termination), ]), - { data: this, parent } + { data: this, parent }, ); } } @@ -3001,21 +3142,23 @@ class Constant extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "IterableLike": () => (/* binding */ IterableLike) +/* harmony export */ IterableLike: () => (/* binding */ IterableLike) /* harmony export */ }); -/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); -/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); +/* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); + -class IterableLike extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { +class IterableLike extends _base_js__WEBPACK_IMPORTED_MODULE_1__.Base { /** * @param {import("../tokeniser.js").Tokeniser} tokeniser */ static parse(tokeniser) { const start_position = tokeniser.position; - const ret = (0,_helpers_js__WEBPACK_IMPORTED_MODULE_1__.autoParenter)( - new IterableLike({ source: tokeniser.source, tokens: {} }) + const ret = (0,_helpers_js__WEBPACK_IMPORTED_MODULE_2__.autoParenter)( + new IterableLike({ source: tokeniser.source, tokens: {} }), ); const { tokens } = ret; tokens.readonly = tokeniser.consume("readonly"); @@ -3025,8 +3168,8 @@ class IterableLike extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { tokens.base = tokens.readonly ? tokeniser.consume("maplike", "setlike") : tokens.async - ? tokeniser.consume("iterable") - : tokeniser.consume("iterable", "maplike", "setlike"); + ? tokeniser.consume("iterable") + : tokeniser.consume("iterable", "async_iterable", "maplike", "setlike"); if (!tokens.base) { tokeniser.unconsume(start_position); return; @@ -3034,14 +3177,16 @@ class IterableLike extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { const { type } = ret; const secondTypeRequired = type === "maplike"; - const secondTypeAllowed = secondTypeRequired || type === "iterable"; - const argumentAllowed = ret.async && type === "iterable"; + const secondTypeAllowed = + secondTypeRequired || type === "iterable" || type === "async_iterable"; + const argumentAllowed = + type === "async_iterable" || (ret.async && type === "iterable"); tokens.open = tokeniser.consume("<") || tokeniser.error(`Missing less-than sign \`<\` in ${type} declaration`); const first = - (0,_helpers_js__WEBPACK_IMPORTED_MODULE_1__.type_with_extended_attributes)(tokeniser) || + (0,_helpers_js__WEBPACK_IMPORTED_MODULE_2__.type_with_extended_attributes)(tokeniser) || tokeniser.error(`Missing a type argument in ${type} declaration`); ret.idlType = [first]; ret.arguments = []; @@ -3049,7 +3194,7 @@ class IterableLike extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { if (secondTypeAllowed) { first.tokens.separator = tokeniser.consume(","); if (first.tokens.separator) { - ret.idlType.push((0,_helpers_js__WEBPACK_IMPORTED_MODULE_1__.type_with_extended_attributes)(tokeniser)); + ret.idlType.push((0,_helpers_js__WEBPACK_IMPORTED_MODULE_2__.type_with_extended_attributes)(tokeniser)); } else if (secondTypeRequired) { tokeniser.error(`Missing second type argument in ${type} declaration`); } @@ -3062,7 +3207,7 @@ class IterableLike extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { if (tokeniser.probe("(")) { if (argumentAllowed) { tokens.argsOpen = tokeniser.consume("("); - ret.arguments.push(...(0,_helpers_js__WEBPACK_IMPORTED_MODULE_1__.argument_list)(tokeniser)); + ret.arguments.push(...(0,_helpers_js__WEBPACK_IMPORTED_MODULE_2__.argument_list)(tokeniser)); tokens.argsClose = tokeniser.consume(")") || tokeniser.error("Unterminated async iterable argument list"); @@ -3089,6 +3234,18 @@ class IterableLike extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { } *validate(defs) { + if (this.async && this.type === "iterable") { + const message = "`async iterable` is now changed to `async_iterable`."; + yield (0,_error_js__WEBPACK_IMPORTED_MODULE_0__.validationError)( + this.tokens.async, + this, + "obsolete-async-iterable-syntax", + message, + { + autofix: autofixAsyncIterableSyntax(this), + }, + ); + } for (const type of this.idlType) { yield* type.validate(defs); } @@ -3113,11 +3270,26 @@ class IterableLike extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { w.token(this.tokens.argsClose), w.token(this.tokens.termination), ]), - { data: this, parent: this.parent } + { data: this, parent: this.parent }, ); } } +/** + * @param {IterableLike} iterableLike + */ +function autofixAsyncIterableSyntax(iterableLike) { + return () => { + const async = iterableLike.tokens.async; + iterableLike.tokens.base = { + ...async, + type: "async_iterable", + value: "async_iterable", + }; + delete iterableLike.tokens.async; + }; +} + /***/ }), /* 23 */ @@ -3125,7 +3297,7 @@ class IterableLike extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "checkInterfaceMemberDuplication": () => (/* binding */ checkInterfaceMemberDuplication) +/* harmony export */ checkInterfaceMemberDuplication: () => (/* binding */ checkInterfaceMemberDuplication) /* harmony export */ }); /* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3); @@ -3164,7 +3336,7 @@ function* checkInterfaceMemberDuplication(defs, i) { addition.tokens.name, ext, "no-cross-overload", - message + message, ); } } @@ -3185,10 +3357,10 @@ function* checkInterfaceMemberDuplication(defs, i) { const ops = getOperations(i); return { statics: new Set( - ops.filter((op) => op.special === "static").map((op) => op.name) + ops.filter((op) => op.special === "static").map((op) => op.name), ), nonstatics: new Set( - ops.filter((op) => op.special !== "static").map((op) => op.name) + ops.filter((op) => op.special !== "static").map((op) => op.name), ), }; } @@ -3201,7 +3373,7 @@ function* checkInterfaceMemberDuplication(defs, i) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Constructor": () => (/* binding */ Constructor) +/* harmony export */ Constructor: () => (/* binding */ Constructor) /* harmony export */ }); /* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); /* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); @@ -3255,7 +3427,7 @@ class Constructor extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { w.token(this.tokens.close), w.token(this.tokens.termination), ]), - { data: this, parent } + { data: this, parent }, ); } } @@ -3267,7 +3439,7 @@ class Constructor extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Mixin": () => (/* binding */ Mixin) +/* harmony export */ Mixin: () => (/* binding */ Mixin) /* harmony export */ }); /* harmony import */ var _container_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(20); /* harmony import */ var _constant_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(21); @@ -3282,14 +3454,13 @@ __webpack_require__.r(__webpack_exports__); class Mixin extends _container_js__WEBPACK_IMPORTED_MODULE_0__.Container { /** - * @typedef {import("../tokeniser.js").Token} Token - * * @param {import("../tokeniser.js").Tokeniser} tokeniser - * @param {Token} base + * @param {import("../tokeniser.js").Token} base * @param {object} [options] - * @param {Token} [options.partial] + * @param {import("./container.js").AllowedMember[]} [options.extMembers] + * @param {import("../tokeniser.js").Token} [options.partial] */ - static parse(tokeniser, base, { partial } = {}) { + static parse(tokeniser, base, { extMembers = [], partial } = {}) { const tokens = { partial, base }; tokens.mixin = tokeniser.consume("mixin"); if (!tokens.mixin) { @@ -3300,12 +3471,13 @@ class Mixin extends _container_js__WEBPACK_IMPORTED_MODULE_0__.Container { new Mixin({ source: tokeniser.source, tokens }), { allowedMembers: [ + ...extMembers, [_constant_js__WEBPACK_IMPORTED_MODULE_1__.Constant.parse], [_helpers_js__WEBPACK_IMPORTED_MODULE_4__.stringifier], [_attribute_js__WEBPACK_IMPORTED_MODULE_2__.Attribute.parse, { noInherit: true }], [_operation_js__WEBPACK_IMPORTED_MODULE_3__.Operation.parse, { regular: true }], ], - } + }, ); } @@ -3321,7 +3493,7 @@ class Mixin extends _container_js__WEBPACK_IMPORTED_MODULE_0__.Container { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Dictionary": () => (/* binding */ Dictionary) +/* harmony export */ Dictionary: () => (/* binding */ Dictionary) /* harmony export */ }); /* harmony import */ var _container_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(20); /* harmony import */ var _field_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(27); @@ -3332,9 +3504,10 @@ class Dictionary extends _container_js__WEBPACK_IMPORTED_MODULE_0__.Container { /** * @param {import("../tokeniser.js").Tokeniser} tokeniser * @param {object} [options] + * @param {import("./container.js").AllowedMember[]} [options.extMembers] * @param {import("../tokeniser.js").Token} [options.partial] */ - static parse(tokeniser, { partial } = {}) { + static parse(tokeniser, { extMembers = [], partial } = {}) { const tokens = { partial }; tokens.base = tokeniser.consume("dictionary"); if (!tokens.base) { @@ -3345,8 +3518,8 @@ class Dictionary extends _container_js__WEBPACK_IMPORTED_MODULE_0__.Container { new Dictionary({ source: tokeniser.source, tokens }), { inheritable: !partial, - allowedMembers: [[_field_js__WEBPACK_IMPORTED_MODULE_1__.Field.parse]], - } + allowedMembers: [...extMembers, [_field_js__WEBPACK_IMPORTED_MODULE_1__.Field.parse]], + }, ); } @@ -3362,7 +3535,7 @@ class Dictionary extends _container_js__WEBPACK_IMPORTED_MODULE_0__.Container { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Field": () => (/* binding */ Field) +/* harmony export */ Field: () => (/* binding */ Field) /* harmony export */ }); /* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); /* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); @@ -3424,7 +3597,7 @@ class Field extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { this.default ? this.default.write(w) : "", w.token(this.tokens.termination), ]), - { data: this, parent } + { data: this, parent }, ); } } @@ -3436,7 +3609,7 @@ class Field extends _base_js__WEBPACK_IMPORTED_MODULE_0__.Base { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Namespace": () => (/* binding */ Namespace) +/* harmony export */ Namespace: () => (/* binding */ Namespace) /* harmony export */ }); /* harmony import */ var _container_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(20); /* harmony import */ var _attribute_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(14); @@ -3455,9 +3628,10 @@ class Namespace extends _container_js__WEBPACK_IMPORTED_MODULE_0__.Container { /** * @param {import("../tokeniser.js").Tokeniser} tokeniser * @param {object} [options] + * @param {import("./container.js").AllowedMember[]} [options.extMembers] * @param {import("../tokeniser.js").Token} [options.partial] */ - static parse(tokeniser, { partial } = {}) { + static parse(tokeniser, { extMembers = [], partial } = {}) { const tokens = { partial }; tokens.base = tokeniser.consume("namespace"); if (!tokens.base) { @@ -3468,11 +3642,12 @@ class Namespace extends _container_js__WEBPACK_IMPORTED_MODULE_0__.Container { new Namespace({ source: tokeniser.source, tokens }), { allowedMembers: [ + ...extMembers, [_attribute_js__WEBPACK_IMPORTED_MODULE_1__.Attribute.parse, { noInherit: true, readonly: true }], [_constant_js__WEBPACK_IMPORTED_MODULE_5__.Constant.parse], [_operation_js__WEBPACK_IMPORTED_MODULE_2__.Operation.parse, { regular: true }], ], - } + }, ); } @@ -3497,7 +3672,7 @@ for more information.`; message, { autofix: (0,_helpers_js__WEBPACK_IMPORTED_MODULE_4__.autofixAddExposedWindow)(this), - } + }, ); } yield* super.validate(defs); @@ -3511,7 +3686,7 @@ for more information.`; __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "CallbackInterface": () => (/* binding */ CallbackInterface) +/* harmony export */ CallbackInterface: () => (/* binding */ CallbackInterface) /* harmony export */ }); /* harmony import */ var _container_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(20); /* harmony import */ var _operation_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(13); @@ -3523,8 +3698,11 @@ __webpack_require__.r(__webpack_exports__); class CallbackInterface extends _container_js__WEBPACK_IMPORTED_MODULE_0__.Container { /** * @param {import("../tokeniser.js").Tokeniser} tokeniser + * @param {*} callback + * @param {object} [options] + * @param {import("./container.js").AllowedMember[]} [options.extMembers] */ - static parse(tokeniser, callback, { partial = null } = {}) { + static parse(tokeniser, callback, { extMembers = [] } = {}) { const tokens = { callback }; tokens.base = tokeniser.consume("interface"); if (!tokens.base) { @@ -3534,12 +3712,12 @@ class CallbackInterface extends _container_js__WEBPACK_IMPORTED_MODULE_0__.Conta tokeniser, new CallbackInterface({ source: tokeniser.source, tokens }), { - inheritable: !partial, allowedMembers: [ + ...extMembers, [_constant_js__WEBPACK_IMPORTED_MODULE_2__.Constant.parse], [_operation_js__WEBPACK_IMPORTED_MODULE_1__.Operation.parse, { regular: true }], ], - } + }, ); } @@ -3555,8 +3733,8 @@ class CallbackInterface extends _container_js__WEBPACK_IMPORTED_MODULE_0__.Conta __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "Writer": () => (/* binding */ Writer), -/* harmony export */ "write": () => (/* binding */ write) +/* harmony export */ Writer: () => (/* binding */ Writer), +/* harmony export */ write: () => (/* binding */ write) /* harmony export */ }); function noop(arg) { return arg; @@ -3640,7 +3818,7 @@ function write(ast, { templates: ts = templates } = {}) { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "validate": () => (/* binding */ validate) +/* harmony export */ validate: () => (/* binding */ validate) /* harmony export */ }); /* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3); @@ -3797,14 +3975,14 @@ function validate(ast) { /******/ /************************************************************************/ var __webpack_exports__ = {}; -// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk. +// This entry needs to be wrapped in an IIFE because it needs to be isolated against other modules in the chunk. (() => { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { -/* harmony export */ "WebIDLParseError": () => (/* reexport safe */ _lib_tokeniser_js__WEBPACK_IMPORTED_MODULE_3__.WebIDLParseError), -/* harmony export */ "parse": () => (/* reexport safe */ _lib_webidl2_js__WEBPACK_IMPORTED_MODULE_0__.parse), -/* harmony export */ "validate": () => (/* reexport safe */ _lib_validator_js__WEBPACK_IMPORTED_MODULE_2__.validate), -/* harmony export */ "write": () => (/* reexport safe */ _lib_writer_js__WEBPACK_IMPORTED_MODULE_1__.write) +/* harmony export */ WebIDLParseError: () => (/* reexport safe */ _lib_tokeniser_js__WEBPACK_IMPORTED_MODULE_3__.WebIDLParseError), +/* harmony export */ parse: () => (/* reexport safe */ _lib_webidl2_js__WEBPACK_IMPORTED_MODULE_0__.parse), +/* harmony export */ validate: () => (/* reexport safe */ _lib_validator_js__WEBPACK_IMPORTED_MODULE_2__.validate), +/* harmony export */ write: () => (/* reexport safe */ _lib_writer_js__WEBPACK_IMPORTED_MODULE_1__.write) /* harmony export */ }); /* harmony import */ var _lib_webidl2_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); /* harmony import */ var _lib_writer_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(30); diff --git a/test/fixtures/wpt/versions.json b/test/fixtures/wpt/versions.json index 02c16cd1074435..22e374b606befe 100644 --- a/test/fixtures/wpt/versions.json +++ b/test/fixtures/wpt/versions.json @@ -52,7 +52,7 @@ "path": "html/webappapis/timers" }, "interfaces": { - "commit": "e1b27be06b43787a001b7297c4e0fabdd276560f", + "commit": "a8392bd0210378dda13278eeccf2c2602278e214", "path": "interfaces" }, "performance-timeline": { @@ -64,7 +64,7 @@ "path": "resource-timing" }, "resources": { - "commit": "1d2c5fb36a6e477c8f915bde7eca027be6abe792", + "commit": "6a2f32237615d7ea9c1eb1e30453066b5555c603", "path": "resources" }, "streams": { @@ -96,7 +96,7 @@ "path": "web-locks" }, "WebCryptoAPI": { - "commit": "0acea989ac21842d6545d16594f1a05491c6076e", + "commit": "caebe89c3b243513c4b7e5e8dd2a370e5800bb73", "path": "WebCryptoAPI" }, "webidl": { diff --git a/test/wpt/status/WebCryptoAPI.cjs b/test/wpt/status/WebCryptoAPI.cjs index 722a0b38398e1d..3f788d87fd0849 100644 --- a/test/wpt/status/WebCryptoAPI.cjs +++ b/test/wpt/status/WebCryptoAPI.cjs @@ -55,6 +55,12 @@ module.exports = { 'historical.any.js': { 'skip': 'Not relevant in Node.js context', }, + 'digest/turboshake.tentative.https.any.js': { + 'skip': 'not implemented', + }, + 'digest/kangarootwelve.tentative.https.any.js': { + 'skip': 'not implemented', + }, 'sign_verify/eddsa_small_order_points.https.any.js': { 'fail': { 'note': 'see https://github.com/nodejs/node/issues/54572', diff --git a/test/wpt/status/resource-timing.json b/test/wpt/status/resource-timing.json index 6406b88d3266f5..74de815387ed33 100644 --- a/test/wpt/status/resource-timing.json +++ b/test/wpt/status/resource-timing.json @@ -31,7 +31,17 @@ "PerformanceResourceTiming interface: resource must inherit property \"firstInterimResponseStart\" with the proper type", "PerformanceResourceTiming interface: resource must inherit property \"renderBlockingStatus\" with the proper type", "PerformanceResourceTiming interface: resource must inherit property \"contentType\" with the proper type", - "PerformanceResourceTiming interface: default toJSON operation on resource" + "PerformanceResourceTiming interface: default toJSON operation on resource", + "PerformanceResourceTiming interface: attribute workerRouterEvaluationStart", + "PerformanceResourceTiming interface: attribute workerCacheLookupStart", + "PerformanceResourceTiming interface: attribute workerMatchedRouterSource", + "PerformanceResourceTiming interface: attribute workerFinalRouterSource", + "PerformanceResourceTiming interface: attribute contentEncoding", + "PerformanceResourceTiming interface: resource must inherit property \"workerRouterEvaluationStart\" with the proper type", + "PerformanceResourceTiming interface: resource must inherit property \"workerCacheLookupStart\" with the proper type", + "PerformanceResourceTiming interface: resource must inherit property \"workerMatchedRouterSource\" with the proper type", + "PerformanceResourceTiming interface: resource must inherit property \"workerFinalRouterSource\" with the proper type", + "PerformanceResourceTiming interface: resource must inherit property \"contentEncoding\" with the proper type" ] } } From 38647b388bbf5d350f8febe77a6674ec0d0607c5 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Thu, 26 Mar 2026 14:02:48 +0100 Subject: [PATCH 4/6] src: enable compilation/linking with OpenSSL 4.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/62410 Reviewed-By: Richard Lau Reviewed-By: Tobias Nießen Reviewed-By: Yagiz Nizipli Reviewed-By: Luigi Pinca --- deps/ncrypto/ncrypto.cc | 56 +++++++++++++-------- deps/ncrypto/ncrypto.h | 5 +- test/parallel/test-tls-client-auth.js | 5 +- test/parallel/test-tls-empty-sni-context.js | 5 +- test/parallel/test-tls-psk-circuit.js | 5 +- test/parallel/test-tls-set-ciphers.js | 4 +- test/parallel/test-tls-set-sigalgs.js | 5 +- 7 files changed, 52 insertions(+), 33 deletions(-) diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index 3a26cfbdcab52e..87a6b7a3501e4d 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -777,11 +777,15 @@ bool PrintGeneralName(const BIOPointer& out, const GENERAL_NAME* gen) { // Note that the preferred name syntax (see RFCs 5280 and 1034) with // wildcards is a subset of what we consider "safe", so spec-compliant DNS // names will never need to be escaped. - PrintAltName(out, reinterpret_cast(name->data), name->length); + PrintAltName(out, + reinterpret_cast(ASN1_STRING_get0_data(name)), + ASN1_STRING_length(name)); } else if (gen->type == GEN_EMAIL) { ASN1_IA5STRING* name = gen->d.rfc822Name; BIO_write(out.get(), "email:", 6); - PrintAltName(out, reinterpret_cast(name->data), name->length); + PrintAltName(out, + reinterpret_cast(ASN1_STRING_get0_data(name)), + ASN1_STRING_length(name)); } else if (gen->type == GEN_URI) { ASN1_IA5STRING* name = gen->d.uniformResourceIdentifier; BIO_write(out.get(), "URI:", 4); @@ -789,7 +793,9 @@ bool PrintGeneralName(const BIOPointer& out, const GENERAL_NAME* gen) { // with a few exceptions, most notably URIs that contains commas (see // RFC 2396). In other words, most legitimate URIs will not require // escaping. - PrintAltName(out, reinterpret_cast(name->data), name->length); + PrintAltName(out, + reinterpret_cast(ASN1_STRING_get0_data(name)), + ASN1_STRING_length(name)); } else if (gen->type == GEN_DIRNAME) { // Earlier versions of Node.js used X509_NAME_oneline to print the X509_NAME // object. The format was non standard and should be avoided. The use of @@ -822,17 +828,18 @@ bool PrintGeneralName(const BIOPointer& out, const GENERAL_NAME* gen) { } else if (gen->type == GEN_IPADD) { BIO_printf(out.get(), "IP Address:"); const ASN1_OCTET_STRING* ip = gen->d.ip; - const unsigned char* b = ip->data; - if (ip->length == 4) { + const unsigned char* b = ASN1_STRING_get0_data(ip); + int ip_len = ASN1_STRING_length(ip); + if (ip_len == 4) { BIO_printf(out.get(), "%d.%d.%d.%d", b[0], b[1], b[2], b[3]); - } else if (ip->length == 16) { + } else if (ip_len == 16) { for (unsigned int j = 0; j < 8; j++) { uint16_t pair = (b[2 * j] << 8) | b[2 * j + 1]; BIO_printf(out.get(), (j == 0) ? "%X" : ":%X", pair); } } else { #if OPENSSL_VERSION_MAJOR >= 3 - BIO_printf(out.get(), "", ip->length); + BIO_printf(out.get(), "", ip_len); #else BIO_printf(out.get(), ""); #endif @@ -882,15 +889,15 @@ bool PrintGeneralName(const BIOPointer& out, const GENERAL_NAME* gen) { if (unicode) { auto name = gen->d.otherName->value->value.utf8string; PrintAltName(out, - reinterpret_cast(name->data), - name->length, + reinterpret_cast(ASN1_STRING_get0_data(name)), + ASN1_STRING_length(name), AltNameOption::UTF8, prefix); } else { auto name = gen->d.otherName->value->value.ia5string; PrintAltName(out, - reinterpret_cast(name->data), - name->length, + reinterpret_cast(ASN1_STRING_get0_data(name)), + ASN1_STRING_length(name), AltNameOption::NONE, prefix); } @@ -911,11 +918,14 @@ bool PrintGeneralName(const BIOPointer& out, const GENERAL_NAME* gen) { } } // namespace -bool SafeX509SubjectAltNamePrint(const BIOPointer& out, X509_EXTENSION* ext) { - auto ret = OBJ_obj2nid(X509_EXTENSION_get_object(ext)); +bool SafeX509SubjectAltNamePrint(const BIOPointer& out, + const X509_EXTENSION* ext) { + // const_cast needed for OpenSSL < 4.0 which lacks const-correctness + auto* mext = const_cast(ext); + auto ret = OBJ_obj2nid(X509_EXTENSION_get_object(mext)); if (ret != NID_subject_alt_name) return false; - GENERAL_NAMES* names = static_cast(X509V3_EXT_d2i(ext)); + GENERAL_NAMES* names = static_cast(X509V3_EXT_d2i(mext)); if (names == nullptr) return false; bool ok = true; @@ -934,12 +944,14 @@ bool SafeX509SubjectAltNamePrint(const BIOPointer& out, X509_EXTENSION* ext) { return ok; } -bool SafeX509InfoAccessPrint(const BIOPointer& out, X509_EXTENSION* ext) { - auto ret = OBJ_obj2nid(X509_EXTENSION_get_object(ext)); +bool SafeX509InfoAccessPrint(const BIOPointer& out, const X509_EXTENSION* ext) { + // const_cast needed for OpenSSL < 4.0 which lacks const-correctness + auto* mext = const_cast(ext); + auto ret = OBJ_obj2nid(X509_EXTENSION_get_object(mext)); if (ret != NID_info_access) return false; AUTHORITY_INFO_ACCESS* descs = - static_cast(X509V3_EXT_d2i(ext)); + static_cast(X509V3_EXT_d2i(mext)); if (descs == nullptr) return false; bool ok = true; @@ -1083,7 +1095,7 @@ BIOPointer X509View::getValidFrom() const { if (cert_ == nullptr) return {}; BIOPointer bio(BIO_new(BIO_s_mem())); if (!bio) return {}; - ASN1_TIME_print(bio.get(), X509_get_notBefore(cert_)); + ASN1_TIME_print(bio.get(), X509_get0_notBefore(cert_)); return bio; } @@ -1092,7 +1104,7 @@ BIOPointer X509View::getValidTo() const { if (cert_ == nullptr) return {}; BIOPointer bio(BIO_new(BIO_s_mem())); if (!bio) return {}; - ASN1_TIME_print(bio.get(), X509_get_notAfter(cert_)); + ASN1_TIME_print(bio.get(), X509_get0_notAfter(cert_)); return bio; } @@ -4643,12 +4655,12 @@ bool X509Name::Iterator::operator!=(const Iterator& other) const { std::pair X509Name::Iterator::operator*() const { if (loc_ == name_.total_) return {{}, {}}; - X509_NAME_ENTRY* entry = X509_NAME_get_entry(name_, loc_); + const X509_NAME_ENTRY* entry = X509_NAME_get_entry(name_, loc_); if (entry == nullptr) [[unlikely]] return {{}, {}}; - ASN1_OBJECT* name = X509_NAME_ENTRY_get_object(entry); - ASN1_STRING* value = X509_NAME_ENTRY_get_data(entry); + const ASN1_OBJECT* name = X509_NAME_ENTRY_get_object(entry); + const ASN1_STRING* value = X509_NAME_ENTRY_get_data(entry); if (name == nullptr || value == nullptr) [[unlikely]] { return {{}, {}}; diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index 5ffaee379e481a..c781d7e2e0288f 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -1578,8 +1578,9 @@ int NoPasswordCallback(char* buf, int size, int rwflag, void* u); int PasswordCallback(char* buf, int size, int rwflag, void* u); -bool SafeX509SubjectAltNamePrint(const BIOPointer& out, X509_EXTENSION* ext); -bool SafeX509InfoAccessPrint(const BIOPointer& out, X509_EXTENSION* ext); +bool SafeX509SubjectAltNamePrint(const BIOPointer& out, + const X509_EXTENSION* ext); +bool SafeX509InfoAccessPrint(const BIOPointer& out, const X509_EXTENSION* ext); // ============================================================================ // SPKAC diff --git a/test/parallel/test-tls-client-auth.js b/test/parallel/test-tls-client-auth.js index 04bf40b9a9e1ac..67aed40914c9fe 100644 --- a/test/parallel/test-tls-client-auth.js +++ b/test/parallel/test-tls-client-auth.js @@ -82,8 +82,9 @@ connect({ }, common.mustCall((err, pair, cleanup) => { assert.strictEqual(pair.server.err.code, 'ERR_SSL_PEER_DID_NOT_RETURN_A_CERTIFICATE'); - const expectedErr = hasOpenSSL(3, 2) ? - 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; + const expectedErr = hasOpenSSL(4, 0) ? + 'ERR_SSL_TLS_ALERT_HANDSHAKE_FAILURE' : hasOpenSSL(3, 2) ? + 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; assert.strictEqual(pair.client.err.code, expectedErr); return cleanup(); diff --git a/test/parallel/test-tls-empty-sni-context.js b/test/parallel/test-tls-empty-sni-context.js index 79f1ddd341d938..e4136ff71e1d52 100644 --- a/test/parallel/test-tls-empty-sni-context.js +++ b/test/parallel/test-tls-empty-sni-context.js @@ -26,8 +26,9 @@ const server = tls.createServer(options, (c) => { }, common.mustNotCall()); c.on('error', common.mustCall((err) => { - const expectedErr = hasOpenSSL(3, 2) ? - 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; + const expectedErr = hasOpenSSL(4, 0) ? + 'ERR_SSL_TLS_ALERT_HANDSHAKE_FAILURE' : hasOpenSSL(3, 2) ? + 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; assert.strictEqual(err.code, expectedErr); })); })); diff --git a/test/parallel/test-tls-psk-circuit.js b/test/parallel/test-tls-psk-circuit.js index 9cbc7a91fb852b..bdf9c86c26a7b6 100644 --- a/test/parallel/test-tls-psk-circuit.js +++ b/test/parallel/test-tls-psk-circuit.js @@ -64,8 +64,9 @@ test({ psk: USERS.UserA, identity: 'UserA' }, { minVersion: 'TLSv1.3' }); test({ psk: USERS.UserB, identity: 'UserB' }); test({ psk: USERS.UserB, identity: 'UserB' }, { minVersion: 'TLSv1.3' }); // Unrecognized user should fail handshake -const expectedHandshakeErr = hasOpenSSL(3, 2) ? - 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; +const expectedHandshakeErr = hasOpenSSL(4, 0) ? + 'ERR_SSL_TLS_ALERT_HANDSHAKE_FAILURE' : hasOpenSSL(3, 2) ? + 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; test({ psk: USERS.UserB, identity: 'UserC' }, {}, expectedHandshakeErr); // Recognized user but incorrect secret should fail handshake const expectedIllegalParameterErr = hasOpenSSL(3, 4) ? 'ERR_SSL_TLSV1_ALERT_DECRYPT_ERROR' : diff --git a/test/parallel/test-tls-set-ciphers.js b/test/parallel/test-tls-set-ciphers.js index 1e63e9376e134b..82a19bb9e90fc2 100644 --- a/test/parallel/test-tls-set-ciphers.js +++ b/test/parallel/test-tls-set-ciphers.js @@ -90,7 +90,9 @@ function test(cciphers, sciphers, cipher, cerr, serr, options) { const U = undefined; let expectedTLSAlertError = 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; -if (hasOpenSSL(3, 2)) { +if (hasOpenSSL(4, 0)) { + expectedTLSAlertError = 'ERR_SSL_TLS_ALERT_HANDSHAKE_FAILURE'; +} else if (hasOpenSSL(3, 2)) { expectedTLSAlertError = 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE'; } diff --git a/test/parallel/test-tls-set-sigalgs.js b/test/parallel/test-tls-set-sigalgs.js index 985ca13ba2ac7d..1bce814f3e8604 100644 --- a/test/parallel/test-tls-set-sigalgs.js +++ b/test/parallel/test-tls-set-sigalgs.js @@ -66,8 +66,9 @@ test('RSA-PSS+SHA256:RSA-PSS+SHA512:ECDSA+SHA256', ['RSA-PSS+SHA256', 'ECDSA+SHA256']); // Do not have shared sigalgs. -const handshakeErr = hasOpenSSL(3, 2) ? - 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; +const handshakeErr = hasOpenSSL(4, 0) ? + 'ERR_SSL_TLS_ALERT_HANDSHAKE_FAILURE' : hasOpenSSL(3, 2) ? + 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; test('RSA-PSS+SHA384', 'ECDSA+SHA256', undefined, handshakeErr, 'ERR_SSL_NO_SHARED_SIGNATURE_ALGORITHMS'); From 329c8e75545f9a4840b3ce30b5658e0995dbdee4 Mon Sep 17 00:00:00 2001 From: Rafael Gonzaga Date: Thu, 26 Mar 2026 17:59:14 -0300 Subject: [PATCH 5/6] doc: add Rafael to last security release steward MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/62423 Refs: https://github.com/nodejs-private/security-release/pull/71 Reviewed-By: Marco Ippolito Reviewed-By: Luigi Pinca Reviewed-By: Ulises Gascón Reviewed-By: Richard Lau Reviewed-By: Filip Skokan --- doc/contributing/security-release-process.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/contributing/security-release-process.md b/doc/contributing/security-release-process.md index 1107edc5a68976..ba7dbae378f730 100644 --- a/doc/contributing/security-release-process.md +++ b/doc/contributing/security-release-process.md @@ -39,6 +39,7 @@ The current security stewards are documented in the main Node.js | NodeSource | Rafael | 2025-May-14 | | NodeSource | Rafael | 2025-Jul-15 | | HeroDevs and NodeSource | Marco / Rafael | 2026-Jan-13 | +| NodeSource | Rafael | 2026-Mar-24 | | Datadog | Bryan | | | IBM | Joe | | | Platformatic | Matteo | | From e0cab9dcf7514cfabcf6e5989e2bf29455bf5f50 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 10 Mar 2026 14:11:56 +0100 Subject: [PATCH 6/6] crypto: add TurboSHAKE and KangarooTwelve Web Cryptography algorithms PR-URL: https://github.com/nodejs/node/pull/62183 Refs: https://wicg.github.io/webcrypto-modern-algos/#kangarootwelve Refs: https://wicg.github.io/webcrypto-modern-algos/#turboshake Refs: https://www.rfc-editor.org/rfc/rfc9861.html Refs: https://redirect.github.com/openssl/openssl/issues/30304 Reviewed-By: Anna Henningsen Reviewed-By: Yagiz Nizipli --- doc/api/webcrypto.md | 86 ++- lib/internal/crypto/hash.js | 20 + lib/internal/crypto/util.js | 12 + lib/internal/crypto/webidl.js | 46 ++ node.gyp | 2 + src/crypto/crypto_turboshake.cc | 642 ++++++++++++++++++ src/crypto/crypto_turboshake.h | 105 +++ src/node_crypto.cc | 5 +- src/node_crypto.h | 1 + .../webcrypto/supports-modern-algorithms.mjs | 24 + .../test-webcrypto-digest-turboshake-rfc.js | 399 +++++++++++ .../test-webcrypto-digest-turboshake.js | 181 +++++ test/wpt/status/WebCryptoAPI.cjs | 6 - 13 files changed, 1521 insertions(+), 8 deletions(-) create mode 100644 src/crypto/crypto_turboshake.cc create mode 100644 src/crypto/crypto_turboshake.h create mode 100644 test/parallel/test-webcrypto-digest-turboshake-rfc.js create mode 100644 test/parallel/test-webcrypto-digest-turboshake.js diff --git a/doc/api/webcrypto.md b/doc/api/webcrypto.md index 6a1a8a43375645..82eda8b796b2b1 100644 --- a/doc/api/webcrypto.md +++ b/doc/api/webcrypto.md @@ -2,6 +2,10 @@ -* `algorithm` {string|Algorithm|CShakeParams} +* `algorithm` {string|Algorithm|CShakeParams|TurboShakeParams|KangarooTwelveParams} * `data` {ArrayBuffer|TypedArray|DataView|Buffer} * Returns: {Promise} Fulfills with an {ArrayBuffer} upon success. @@ -1019,6 +1035,8 @@ If `algorithm` is provided as a {string}, it must be one of: * `'cSHAKE128'`[^modern-algos] * `'cSHAKE256'`[^modern-algos] +* `'KT128'`[^modern-algos] +* `'KT256'`[^modern-algos] * `'SHA-1'` * `'SHA-256'` * `'SHA-384'` @@ -1026,6 +1044,8 @@ If `algorithm` is provided as a {string}, it must be one of: * `'SHA3-256'`[^modern-algos] * `'SHA3-384'`[^modern-algos] * `'SHA3-512'`[^modern-algos] +* `'TurboSHAKE128'`[^modern-algos] +* `'TurboSHAKE256'`[^modern-algos] If `algorithm` is provided as an {Object}, it must have a `name` property whose value is one of the above. @@ -2316,6 +2336,38 @@ added: v15.0.0 * Type: {string} +### Class: `KangarooTwelveParams` + + + +#### `kangarooTwelveParams.customization` + + + +* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined} + +The optional customization string for KangarooTwelve. + +#### `kangarooTwelveParams.name` + + + +* Type: {string} Must be `'KT128'`[^modern-algos] or `'KT256'`[^modern-algos] + +#### `kangarooTwelveParams.outputLength` + + + +* Type: {number} represents the requested output length in bits. + ### Class: `KmacImportParams` + +#### `turboShakeParams.domainSeparation` + + + +* Type: {number|undefined} + +The optional domain separation byte (0x01-0x7f). Defaults to `0x1f`. + +#### `turboShakeParams.name` + + + +* Type: {string} Must be `'TurboSHAKE128'`[^modern-algos] or `'TurboSHAKE256'`[^modern-algos] + +#### `turboShakeParams.outputLength` + + + +* Type: {number} represents the requested output length in bits. + [^secure-curves]: See [Secure Curves in the Web Cryptography API][] [^modern-algos]: See [Modern Algorithms in the Web Cryptography API][] diff --git a/lib/internal/crypto/hash.js b/lib/internal/crypto/hash.js index e549c72fccc4bc..857753c2b39f9c 100644 --- a/lib/internal/crypto/hash.js +++ b/lib/internal/crypto/hash.js @@ -14,6 +14,8 @@ const { Hmac: _Hmac, kCryptoJobAsync, oneShotDigest, + TurboShakeJob, + KangarooTwelveJob, } = internalBinding('crypto'); const { @@ -224,6 +226,24 @@ async function asyncDigest(algorithm, data) { normalizeHashName(algorithm.name), data, algorithm.outputLength)); + case 'TurboSHAKE128': + // Fall through + case 'TurboSHAKE256': + return await jobPromise(() => new TurboShakeJob( + kCryptoJobAsync, + algorithm.name, + algorithm.domainSeparation ?? 0x1f, + algorithm.outputLength / 8, + data)); + case 'KT128': + // Fall through + case 'KT256': + return await jobPromise(() => new KangarooTwelveJob( + kCryptoJobAsync, + algorithm.name, + algorithm.customization, + algorithm.outputLength / 8, + data)); } throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js index ab10c53a655a60..70e1027946190a 100644 --- a/lib/internal/crypto/util.js +++ b/lib/internal/crypto/util.js @@ -244,6 +244,10 @@ const kAlgorithmDefinitions = { }, 'cSHAKE128': { 'digest': 'CShakeParams' }, 'cSHAKE256': { 'digest': 'CShakeParams' }, + 'KT128': { 'digest': 'KangarooTwelveParams' }, + 'KT256': { 'digest': 'KangarooTwelveParams' }, + 'TurboSHAKE128': { 'digest': 'TurboShakeParams' }, + 'TurboSHAKE256': { 'digest': 'TurboShakeParams' }, 'ECDH': { 'generateKey': 'EcKeyGenParams', 'exportKey': null, @@ -441,6 +445,10 @@ const experimentalAlgorithms = [ 'SHA3-256', 'SHA3-384', 'SHA3-512', + 'TurboSHAKE128', + 'TurboSHAKE256', + 'KT128', + 'KT256', 'X448', ]; @@ -513,6 +521,10 @@ const simpleAlgorithmDictionaries = { KmacParams: { customization: 'BufferSource', }, + KangarooTwelveParams: { + customization: 'BufferSource', + }, + TurboShakeParams: {}, }; function validateMaxBufferLength(data, name) { diff --git a/lib/internal/crypto/webidl.js b/lib/internal/crypto/webidl.js index 2b8ac167bc3627..4f371955a73bfd 100644 --- a/lib/internal/crypto/webidl.js +++ b/lib/internal/crypto/webidl.js @@ -897,6 +897,52 @@ converters.KmacParams = createDictionaryConverter( }, ]); +converters.KangarooTwelveParams = createDictionaryConverter( + 'KangarooTwelveParams', [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'outputLength', + converter: (V, opts) => + converters['unsigned long'](V, { ...opts, enforceRange: true }), + validator: (V, opts) => { + if (V === 0 || V % 8) + throw lazyDOMException('Invalid KangarooTwelveParams outputLength', 'OperationError'); + }, + required: true, + }, + { + key: 'customization', + converter: converters.BufferSource, + }, + ]); + +converters.TurboShakeParams = createDictionaryConverter( + 'TurboShakeParams', [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'outputLength', + converter: (V, opts) => + converters['unsigned long'](V, { ...opts, enforceRange: true }), + validator: (V, opts) => { + if (V === 0 || V % 8) + throw lazyDOMException('Invalid TurboShakeParams outputLength', 'OperationError'); + }, + required: true, + }, + { + key: 'domainSeparation', + converter: (V, opts) => + converters.octet(V, { ...opts, enforceRange: true }), + validator: (V) => { + if (V < 0x01 || V > 0x7F) { + throw lazyDOMException( + 'TurboShakeParams.domainSeparation must be in range 0x01-0x7f', + 'OperationError'); + } + }, + }, + ]); + module.exports = { converters, requiredArguments, diff --git a/node.gyp b/node.gyp index 1366e1a0cd3505..bd77943b105173 100644 --- a/node.gyp +++ b/node.gyp @@ -390,6 +390,7 @@ 'src/crypto/crypto_kem.cc', 'src/crypto/crypto_hmac.cc', 'src/crypto/crypto_kmac.cc', + 'src/crypto/crypto_turboshake.cc', 'src/crypto/crypto_random.cc', 'src/crypto/crypto_rsa.cc', 'src/crypto/crypto_spkac.cc', @@ -408,6 +409,7 @@ 'src/crypto/crypto_dh.h', 'src/crypto/crypto_hmac.h', 'src/crypto/crypto_kmac.h', + 'src/crypto/crypto_turboshake.h', 'src/crypto/crypto_rsa.h', 'src/crypto/crypto_spkac.h', 'src/crypto/crypto_util.h', diff --git a/src/crypto/crypto_turboshake.cc b/src/crypto/crypto_turboshake.cc new file mode 100644 index 00000000000000..60ef7a8dae5e44 --- /dev/null +++ b/src/crypto/crypto_turboshake.cc @@ -0,0 +1,642 @@ +#include "crypto/crypto_turboshake.h" +#include "async_wrap-inl.h" +#include "node_internals.h" +#include "threadpoolwork-inl.h" + +#include +#include + +namespace node::crypto { + +using v8::FunctionCallbackInfo; +using v8::JustVoid; +using v8::Local; +using v8::Maybe; +using v8::MaybeLocal; +using v8::Nothing; +using v8::Object; +using v8::Uint32; +using v8::Value; + +// ============================================================================ +// Keccak-p[1600, n_r=12] permutation +// Reference: FIPS 202, Section 3.3 and 3.4; RFC 9861 Section 2.2 +// Adapted from OpenSSL's keccak1600.c (KECCAK_REF variant) +// ============================================================================ +namespace { + +inline uint64_t ROL64(uint64_t val, int offset) { + DCHECK(offset >= 0 && offset < 64); + if (offset == 0) return val; + return (val << offset) | (val >> (64 - offset)); +} + +// Load/store 64-bit lanes in little-endian byte order. +// The Keccak state uses LE lane encoding (FIPS 202 Section 1, B.1). +// These helpers ensure correctness on both LE and BE platforms. +inline uint64_t LoadLE64(const uint8_t* src) { + return static_cast(src[0]) | (static_cast(src[1]) << 8) | + (static_cast(src[2]) << 16) | + (static_cast(src[3]) << 24) | + (static_cast(src[4]) << 32) | + (static_cast(src[5]) << 40) | + (static_cast(src[6]) << 48) | + (static_cast(src[7]) << 56); +} + +inline void StoreLE64(uint8_t* dst, uint64_t val) { + dst[0] = static_cast(val); + dst[1] = static_cast(val >> 8); + dst[2] = static_cast(val >> 16); + dst[3] = static_cast(val >> 24); + dst[4] = static_cast(val >> 32); + dst[5] = static_cast(val >> 40); + dst[6] = static_cast(val >> 48); + dst[7] = static_cast(val >> 56); +} + +static const unsigned char rhotates[5][5] = { + {0, 1, 62, 28, 27}, + {36, 44, 6, 55, 20}, + {3, 10, 43, 25, 39}, + {41, 45, 15, 21, 8}, + {18, 2, 61, 56, 14}, +}; + +// Round constants for Keccak-f[1600]. +// TurboSHAKE uses the last 12 rounds (indices 12..23). +static const uint64_t iotas[24] = { + 0x0000000000000001ULL, 0x0000000000008082ULL, 0x800000000000808aULL, + 0x8000000080008000ULL, 0x000000000000808bULL, 0x0000000080000001ULL, + 0x8000000080008081ULL, 0x8000000000008009ULL, 0x000000000000008aULL, + 0x0000000000000088ULL, 0x0000000080008009ULL, 0x000000008000000aULL, + 0x000000008000808bULL, 0x800000000000008bULL, 0x8000000000008089ULL, + 0x8000000000008003ULL, 0x8000000000008002ULL, 0x8000000000000080ULL, + 0x000000000000800aULL, 0x800000008000000aULL, 0x8000000080008081ULL, + 0x8000000000008080ULL, 0x0000000080000001ULL, 0x8000000080008008ULL, +}; + +// Keccak-p[1600, 12]: the reduced-round permutation used by TurboSHAKE. +void KeccakP1600_12(uint64_t A[5][5]) { + for (size_t round = 12; round < 24; round++) { + // Theta + uint64_t C[5]; + for (size_t x = 0; x < 5; x++) { + C[x] = A[0][x] ^ A[1][x] ^ A[2][x] ^ A[3][x] ^ A[4][x]; + } + uint64_t D[5]; + for (size_t x = 0; x < 5; x++) { + D[x] = C[(x + 4) % 5] ^ ROL64(C[(x + 1) % 5], 1); + } + for (size_t y = 0; y < 5; y++) { + for (size_t x = 0; x < 5; x++) { + A[y][x] ^= D[x]; + } + } + + // Rho + for (size_t y = 0; y < 5; y++) { + for (size_t x = 0; x < 5; x++) { + A[y][x] = ROL64(A[y][x], rhotates[y][x]); + } + } + + // Pi + uint64_t T[5][5]; + memcpy(T, A, sizeof(T)); + for (size_t y = 0; y < 5; y++) { + for (size_t x = 0; x < 5; x++) { + A[y][x] = T[x][(3 * y + x) % 5]; + } + } + + // Chi + for (size_t y = 0; y < 5; y++) { + uint64_t row[5]; + for (size_t x = 0; x < 5; x++) { + row[x] = A[y][x] ^ (~A[y][(x + 1) % 5] & A[y][(x + 2) % 5]); + } + memcpy(A[y], row, sizeof(row)); + } + + // Iota + A[0][0] ^= iotas[round]; + } +} + +// ============================================================================ +// TurboSHAKE sponge construction +// RFC 9861 Section 2.2, Appendix A.2/A.3 +// ============================================================================ + +// TurboSHAKE128 rate = 168 bytes (1344 bits), capacity = 256 bits +// TurboSHAKE256 rate = 136 bytes (1088 bits), capacity = 512 bits +static constexpr size_t kTurboSHAKE128Rate = 168; +static constexpr size_t kTurboSHAKE256Rate = 136; + +void TurboSHAKE(const uint8_t* input, + size_t input_len, + size_t rate, + uint8_t domain_sep, + uint8_t* output, + size_t output_len) { + uint64_t A[5][5] = {}; + // Both rates (168, 136) are multiples of 8 + size_t lane_count = rate / 8; + + size_t offset = 0; + + // Absorb complete blocks from input + while (offset + rate <= input_len) { + for (size_t i = 0; i < lane_count; i++) { + A[i / 5][i % 5] ^= LoadLE64(input + offset + i * 8); + } + KeccakP1600_12(A); + offset += rate; + } + + // Absorb last (partial) block: remaining input bytes + domain_sep + padding + size_t remaining = input_len - offset; + uint8_t pad[168] = {}; // sized for max rate (TurboSHAKE128) + if (remaining > 0) { + memcpy(pad, input + offset, remaining); + } + pad[remaining] ^= domain_sep; + pad[rate - 1] ^= 0x80; + + for (size_t i = 0; i < lane_count; i++) { + A[i / 5][i % 5] ^= LoadLE64(pad + i * 8); + } + KeccakP1600_12(A); + + // Squeeze output + size_t out_offset = 0; + while (out_offset < output_len) { + size_t block = output_len - out_offset; + if (block > rate) block = rate; + size_t full_lanes = block / 8; + for (size_t i = 0; i < full_lanes; i++) { + StoreLE64(output + out_offset + i * 8, A[i / 5][i % 5]); + } + size_t rem = block % 8; + if (rem > 0) { + uint8_t tmp[8]; + StoreLE64(tmp, A[full_lanes / 5][full_lanes % 5]); + memcpy(output + out_offset + full_lanes * 8, tmp, rem); + } + out_offset += block; + if (out_offset < output_len) { + KeccakP1600_12(A); + } + } +} + +// Convenience wrappers +void TurboSHAKE128(const uint8_t* input, + size_t input_len, + uint8_t domain_sep, + uint8_t* output, + size_t output_len) { + TurboSHAKE( + input, input_len, kTurboSHAKE128Rate, domain_sep, output, output_len); +} + +void TurboSHAKE256(const uint8_t* input, + size_t input_len, + uint8_t domain_sep, + uint8_t* output, + size_t output_len) { + TurboSHAKE( + input, input_len, kTurboSHAKE256Rate, domain_sep, output, output_len); +} + +// ============================================================================ +// KangarooTwelve tree hashing (RFC 9861 Section 3) +// ============================================================================ + +static constexpr size_t kChunkSize = 8192; + +// length_encode(x): RFC 9861 Section 3.3 +// Returns byte string x_(n-1) || ... || x_0 || n +// where x = sum of 256^i * x_i, n is smallest such that x < 256^n +std::vector LengthEncode(size_t x) { + if (x == 0) { + return {0x00}; + } + + std::vector result; + size_t val = x; + while (val > 0) { + result.push_back(static_cast(val & 0xFF)); + val >>= 8; + } + + // Reverse to get big-endian: x_(n-1) || ... || x_0 + size_t n = result.size(); + for (size_t i = 0; i < n / 2; i++) { + std::swap(result[i], result[n - 1 - i]); + } + + // Append n (the length of the encoding) + result.push_back(static_cast(n)); + return result; +} + +using TurboSHAKEFn = void (*)(const uint8_t* input, + size_t input_len, + uint8_t domain_sep, + uint8_t* output, + size_t output_len); + +void KangarooTwelve(const uint8_t* message, + size_t msg_len, + const uint8_t* customization, + size_t custom_len, + uint8_t* output, + size_t output_len, + TurboSHAKEFn turboshake, + size_t cv_len) { + // Build S = M || C || length_encode(|C|) + auto len_enc = LengthEncode(custom_len); + size_t s_len = msg_len + custom_len + len_enc.size(); + + // Short message path: |S| <= 8192 + if (s_len <= kChunkSize) { + // Build S in a contiguous buffer + std::vector s(s_len); + size_t pos = 0; + if (msg_len > 0) { + memcpy(s.data() + pos, message, msg_len); + pos += msg_len; + } + if (custom_len > 0) { + memcpy(s.data() + pos, customization, custom_len); + pos += custom_len; + } + memcpy(s.data() + pos, len_enc.data(), len_enc.size()); + + turboshake(s.data(), s_len, 0x07, output, output_len); + return; + } + + // Long message path: tree hashing + // We need to process S in chunks, but S is virtual (M || C || length_encode) + // Build a helper to read from this virtual concatenation. + + // First chunk is S[0:8192], compute chaining values for rest + // FinalNode = S[0:8192] || 0x03 || 0x00^7 + + // We need to read from S = M || C || length_encode(|C|) + // Helper lambda to copy from virtual S + auto read_s = [&](size_t s_offset, uint8_t* buf, size_t len) { + size_t copied = 0; + // Part 1: message + if (s_offset < msg_len && copied < len) { + size_t avail = msg_len - s_offset; + size_t to_copy = avail < (len - copied) ? avail : (len - copied); + memcpy(buf + copied, message + s_offset, to_copy); + copied += to_copy; + s_offset += to_copy; + } + // Part 2: customization + size_t custom_start = msg_len; + if (s_offset < custom_start + custom_len && copied < len) { + size_t off_in_custom = s_offset - custom_start; + size_t avail = custom_len - off_in_custom; + size_t to_copy = avail < (len - copied) ? avail : (len - copied); + memcpy(buf + copied, customization + off_in_custom, to_copy); + copied += to_copy; + s_offset += to_copy; + } + // Part 3: length_encode + size_t le_start = msg_len + custom_len; + if (s_offset < le_start + len_enc.size() && copied < len) { + size_t off_in_le = s_offset - le_start; + size_t avail = len_enc.size() - off_in_le; + size_t to_copy = avail < (len - copied) ? avail : (len - copied); + memcpy(buf + copied, len_enc.data() + off_in_le, to_copy); + copied += to_copy; + } + }; + + // Start building FinalNode + // FinalNode = S_0 || 0x03 0x00^7 || CV_1 || CV_2 || ... || CV_(n-1) + // || length_encode(n-1) || 0xFF 0xFF + + // Read first chunk S_0 + std::vector first_chunk(kChunkSize); + read_s(0, first_chunk.data(), kChunkSize); + + // Start FinalNode with S_0 || 0x03 || 0x00^7 + std::vector final_node; + final_node.reserve(kChunkSize + 8 + ((s_len / kChunkSize) * cv_len) + 16); + final_node.insert(final_node.end(), first_chunk.begin(), first_chunk.end()); + final_node.push_back(0x03); + final_node.insert(final_node.end(), 7, 0x00); + + // Process remaining chunks + size_t offset = kChunkSize; + size_t num_blocks = 0; + std::vector chunk(kChunkSize); + std::vector cv(cv_len); + + while (offset < s_len) { + size_t block_size = s_len - offset; + if (block_size > kChunkSize) block_size = kChunkSize; + + chunk.resize(block_size); + read_s(offset, chunk.data(), block_size); + + // CV = TurboSHAKE(chunk, 0x0B, cv_len) + turboshake(chunk.data(), block_size, 0x0B, cv.data(), cv_len); + + final_node.insert(final_node.end(), cv.begin(), cv.end()); + num_blocks++; + offset += block_size; + } + + // Append length_encode(num_blocks) || 0xFF 0xFF + auto num_blocks_enc = LengthEncode(num_blocks); + final_node.insert( + final_node.end(), num_blocks_enc.begin(), num_blocks_enc.end()); + final_node.push_back(0xFF); + final_node.push_back(0xFF); + + // Final hash + turboshake(final_node.data(), final_node.size(), 0x06, output, output_len); +} + +void KT128(const uint8_t* message, + size_t msg_len, + const uint8_t* customization, + size_t custom_len, + uint8_t* output, + size_t output_len) { + KangarooTwelve(message, + msg_len, + customization, + custom_len, + output, + output_len, + TurboSHAKE128, + 32); +} + +void KT256(const uint8_t* message, + size_t msg_len, + const uint8_t* customization, + size_t custom_len, + uint8_t* output, + size_t output_len) { + KangarooTwelve(message, + msg_len, + customization, + custom_len, + output, + output_len, + TurboSHAKE256, + 64); +} + +} // anonymous namespace + +// ============================================================================ +// TurboShake bindings +// ============================================================================ + +TurboShakeConfig::TurboShakeConfig(TurboShakeConfig&& other) noexcept + : job_mode(other.job_mode), + variant(other.variant), + output_length(other.output_length), + domain_separation(other.domain_separation), + data(std::move(other.data)) {} + +TurboShakeConfig& TurboShakeConfig::operator=( + TurboShakeConfig&& other) noexcept { + if (&other == this) return *this; + this->~TurboShakeConfig(); + return *new (this) TurboShakeConfig(std::move(other)); +} + +void TurboShakeConfig::MemoryInfo(MemoryTracker* tracker) const { + if (job_mode == kCryptoJobAsync) { + // TODO(addaleax): Implement MemoryRetainer protocol for ByteSource + tracker->TrackFieldWithSize("data", data.size()); + } +} + +Maybe TurboShakeTraits::AdditionalConfig( + CryptoJobMode mode, + const FunctionCallbackInfo& args, + unsigned int offset, + TurboShakeConfig* params) { + Environment* env = Environment::GetCurrent(args); + + params->job_mode = mode; + + // args[offset + 0] = algorithm name (string) + CHECK(args[offset]->IsString()); + Utf8Value algorithm_name(env->isolate(), args[offset]); + std::string_view alg = algorithm_name.ToStringView(); + + if (alg == "TurboSHAKE128") { + params->variant = TurboShakeVariant::TurboSHAKE128; + } else if (alg == "TurboSHAKE256") { + params->variant = TurboShakeVariant::TurboSHAKE256; + } else { + UNREACHABLE(); + } + + // args[offset + 1] = domain separation byte (uint32) + CHECK(args[offset + 1]->IsUint32()); + params->domain_separation = + static_cast(args[offset + 1].As()->Value()); + CHECK_GE(params->domain_separation, 0x01); + CHECK_LE(params->domain_separation, 0x7F); + + // args[offset + 2] = output length in bytes (uint32) + CHECK(args[offset + 2]->IsUint32()); + params->output_length = args[offset + 2].As()->Value(); + + // args[offset + 3] = data (ArrayBuffer/View) + ArrayBufferOrViewContents data(args[offset + 3]); + if (!data.CheckSizeInt32()) [[unlikely]] { + THROW_ERR_OUT_OF_RANGE(env, "data is too big"); + return Nothing(); + } + params->data = mode == kCryptoJobAsync ? data.ToCopy() : data.ToByteSource(); + + return JustVoid(); +} + +bool TurboShakeTraits::DeriveBits(Environment* env, + const TurboShakeConfig& params, + ByteSource* out, + CryptoJobMode mode) { + CHECK_GT(params.output_length, 0); + char* buf = MallocOpenSSL(params.output_length); + + const uint8_t* input = reinterpret_cast(params.data.data()); + size_t input_len = params.data.size(); + + switch (params.variant) { + case TurboShakeVariant::TurboSHAKE128: + TurboSHAKE128(input, + input_len, + params.domain_separation, + reinterpret_cast(buf), + params.output_length); + break; + case TurboShakeVariant::TurboSHAKE256: + TurboSHAKE256(input, + input_len, + params.domain_separation, + reinterpret_cast(buf), + params.output_length); + break; + } + + *out = ByteSource::Allocated(buf, params.output_length); + return true; +} + +MaybeLocal TurboShakeTraits::EncodeOutput(Environment* env, + const TurboShakeConfig& params, + ByteSource* out) { + return out->ToArrayBuffer(env); +} + +// ============================================================================ +// KangarooTwelve bindings +// ============================================================================ + +KangarooTwelveConfig::KangarooTwelveConfig( + KangarooTwelveConfig&& other) noexcept + : job_mode(other.job_mode), + variant(other.variant), + output_length(other.output_length), + data(std::move(other.data)), + customization(std::move(other.customization)) {} + +KangarooTwelveConfig& KangarooTwelveConfig::operator=( + KangarooTwelveConfig&& other) noexcept { + if (&other == this) return *this; + this->~KangarooTwelveConfig(); + return *new (this) KangarooTwelveConfig(std::move(other)); +} + +void KangarooTwelveConfig::MemoryInfo(MemoryTracker* tracker) const { + if (job_mode == kCryptoJobAsync) { + // TODO(addaleax): Implement MemoryRetainer protocol for ByteSource + tracker->TrackFieldWithSize("data", data.size()); + tracker->TrackFieldWithSize("customization", customization.size()); + } +} + +Maybe KangarooTwelveTraits::AdditionalConfig( + CryptoJobMode mode, + const FunctionCallbackInfo& args, + unsigned int offset, + KangarooTwelveConfig* params) { + Environment* env = Environment::GetCurrent(args); + + params->job_mode = mode; + + // args[offset + 0] = algorithm name (string) + CHECK(args[offset]->IsString()); + Utf8Value algorithm_name(env->isolate(), args[offset]); + std::string_view alg = algorithm_name.ToStringView(); + + if (alg == "KT128") { + params->variant = KangarooTwelveVariant::KT128; + } else if (alg == "KT256") { + params->variant = KangarooTwelveVariant::KT256; + } else { + UNREACHABLE(); + } + + // args[offset + 1] = customization (BufferSource or undefined) + if (!args[offset + 1]->IsUndefined()) { + ArrayBufferOrViewContents customization(args[offset + 1]); + if (!customization.CheckSizeInt32()) [[unlikely]] { + THROW_ERR_OUT_OF_RANGE(env, "customization is too big"); + return Nothing(); + } + params->customization = mode == kCryptoJobAsync + ? customization.ToCopy() + : customization.ToByteSource(); + } + + // args[offset + 2] = output length in bytes (uint32) + CHECK(args[offset + 2]->IsUint32()); + params->output_length = args[offset + 2].As()->Value(); + + // args[offset + 3] = data (ArrayBuffer/View) + ArrayBufferOrViewContents data(args[offset + 3]); + if (!data.CheckSizeInt32()) [[unlikely]] { + THROW_ERR_OUT_OF_RANGE(env, "data is too big"); + return Nothing(); + } + params->data = mode == kCryptoJobAsync ? data.ToCopy() : data.ToByteSource(); + + return JustVoid(); +} + +bool KangarooTwelveTraits::DeriveBits(Environment* env, + const KangarooTwelveConfig& params, + ByteSource* out, + CryptoJobMode mode) { + CHECK_GT(params.output_length, 0); + char* buf = MallocOpenSSL(params.output_length); + + const uint8_t* input = reinterpret_cast(params.data.data()); + size_t input_len = params.data.size(); + + const uint8_t* custom = + reinterpret_cast(params.customization.data()); + size_t custom_len = params.customization.size(); + + switch (params.variant) { + case KangarooTwelveVariant::KT128: + KT128(input, + input_len, + custom, + custom_len, + reinterpret_cast(buf), + params.output_length); + break; + case KangarooTwelveVariant::KT256: + KT256(input, + input_len, + custom, + custom_len, + reinterpret_cast(buf), + params.output_length); + break; + } + + *out = ByteSource::Allocated(buf, params.output_length); + return true; +} + +MaybeLocal KangarooTwelveTraits::EncodeOutput( + Environment* env, const KangarooTwelveConfig& params, ByteSource* out) { + return out->ToArrayBuffer(env); +} + +// ============================================================================ +// Registration +// ============================================================================ + +void TurboShake::Initialize(Environment* env, Local target) { + TurboShakeJob::Initialize(env, target); + KangarooTwelveJob::Initialize(env, target); +} + +void TurboShake::RegisterExternalReferences( + ExternalReferenceRegistry* registry) { + TurboShakeJob::RegisterExternalReferences(registry); + KangarooTwelveJob::RegisterExternalReferences(registry); +} + +} // namespace node::crypto diff --git a/src/crypto/crypto_turboshake.h b/src/crypto/crypto_turboshake.h new file mode 100644 index 00000000000000..53b01eec8bd7c8 --- /dev/null +++ b/src/crypto/crypto_turboshake.h @@ -0,0 +1,105 @@ +#ifndef SRC_CRYPTO_CRYPTO_TURBOSHAKE_H_ +#define SRC_CRYPTO_CRYPTO_TURBOSHAKE_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "crypto/crypto_util.h" + +namespace node::crypto { + +enum class TurboShakeVariant { TurboSHAKE128, TurboSHAKE256 }; + +struct TurboShakeConfig final : public MemoryRetainer { + CryptoJobMode job_mode; + TurboShakeVariant variant; + uint32_t output_length; // Output length in bytes + uint8_t domain_separation; // Domain separation byte (0x01–0x7F) + ByteSource data; + + TurboShakeConfig() = default; + + explicit TurboShakeConfig(TurboShakeConfig&& other) noexcept; + + TurboShakeConfig& operator=(TurboShakeConfig&& other) noexcept; + + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(TurboShakeConfig) + SET_SELF_SIZE(TurboShakeConfig) +}; + +struct TurboShakeTraits final { + using AdditionalParameters = TurboShakeConfig; + static constexpr const char* JobName = "TurboShakeJob"; + static constexpr AsyncWrap::ProviderType Provider = + AsyncWrap::PROVIDER_DERIVEBITSREQUEST; + + static v8::Maybe AdditionalConfig( + CryptoJobMode mode, + const v8::FunctionCallbackInfo& args, + unsigned int offset, + TurboShakeConfig* params); + + static bool DeriveBits(Environment* env, + const TurboShakeConfig& params, + ByteSource* out, + CryptoJobMode mode); + + static v8::MaybeLocal EncodeOutput(Environment* env, + const TurboShakeConfig& params, + ByteSource* out); +}; + +using TurboShakeJob = DeriveBitsJob; + +enum class KangarooTwelveVariant { KT128, KT256 }; + +struct KangarooTwelveConfig final : public MemoryRetainer { + CryptoJobMode job_mode; + KangarooTwelveVariant variant; + uint32_t output_length; // Output length in bytes + ByteSource data; + ByteSource customization; + + KangarooTwelveConfig() = default; + + explicit KangarooTwelveConfig(KangarooTwelveConfig&& other) noexcept; + + KangarooTwelveConfig& operator=(KangarooTwelveConfig&& other) noexcept; + + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(KangarooTwelveConfig) + SET_SELF_SIZE(KangarooTwelveConfig) +}; + +struct KangarooTwelveTraits final { + using AdditionalParameters = KangarooTwelveConfig; + static constexpr const char* JobName = "KangarooTwelveJob"; + static constexpr AsyncWrap::ProviderType Provider = + AsyncWrap::PROVIDER_DERIVEBITSREQUEST; + + static v8::Maybe AdditionalConfig( + CryptoJobMode mode, + const v8::FunctionCallbackInfo& args, + unsigned int offset, + KangarooTwelveConfig* params); + + static bool DeriveBits(Environment* env, + const KangarooTwelveConfig& params, + ByteSource* out, + CryptoJobMode mode); + + static v8::MaybeLocal EncodeOutput( + Environment* env, const KangarooTwelveConfig& params, ByteSource* out); +}; + +using KangarooTwelveJob = DeriveBitsJob; + +namespace TurboShake { +void Initialize(Environment* env, v8::Local target); +void RegisterExternalReferences(ExternalReferenceRegistry* registry); +} // namespace TurboShake + +} // namespace node::crypto + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS +#endif // SRC_CRYPTO_CRYPTO_TURBOSHAKE_H_ diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 991cbf95fbb786..84375f9a737675 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -75,6 +75,8 @@ namespace crypto { #define KMAC_NAMESPACE_LIST(V) #endif +#define TURBOSHAKE_NAMESPACE_LIST(V) V(TurboShake) + #ifdef OPENSSL_NO_SCRYPT #define SCRYPT_NAMESPACE_LIST(V) #else @@ -86,7 +88,8 @@ namespace crypto { ARGON2_NAMESPACE_LIST(V) \ KEM_NAMESPACE_LIST(V) \ KMAC_NAMESPACE_LIST(V) \ - SCRYPT_NAMESPACE_LIST(V) + SCRYPT_NAMESPACE_LIST(V) \ + TURBOSHAKE_NAMESPACE_LIST(V) void Initialize(Local target, Local unused, diff --git a/src/node_crypto.h b/src/node_crypto.h index e5e29544b57a81..cc8fc689f48438 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -55,6 +55,7 @@ #include "crypto/crypto_spkac.h" #include "crypto/crypto_timing.h" #include "crypto/crypto_tls.h" +#include "crypto/crypto_turboshake.h" #include "crypto/crypto_util.h" #include "crypto/crypto_x509.h" diff --git a/test/fixtures/webcrypto/supports-modern-algorithms.mjs b/test/fixtures/webcrypto/supports-modern-algorithms.mjs index eafb95c559a0f7..62b90daf7b0463 100644 --- a/test/fixtures/webcrypto/supports-modern-algorithms.mjs +++ b/test/fixtures/webcrypto/supports-modern-algorithms.mjs @@ -28,6 +28,30 @@ export const vectors = { [false, { name: 'cSHAKE256', outputLength: 256, functionName: Buffer.alloc(1) }], [false, { name: 'cSHAKE256', outputLength: 256, customization: Buffer.alloc(1) }], [false, { name: 'cSHAKE256', outputLength: 255 }], + [false, 'TurboSHAKE128'], + [true, { name: 'TurboSHAKE128', outputLength: 128 }], + [true, { name: 'TurboSHAKE128', outputLength: 128, domainSeparation: 0x07 }], + [false, { name: 'TurboSHAKE128', outputLength: 0 }], + [false, { name: 'TurboSHAKE128', outputLength: 127 }], + [false, { name: 'TurboSHAKE128', outputLength: 128, domainSeparation: 0x00 }], + [false, { name: 'TurboSHAKE128', outputLength: 128, domainSeparation: 0x80 }], + [false, 'TurboSHAKE256'], + [true, { name: 'TurboSHAKE256', outputLength: 256 }], + [true, { name: 'TurboSHAKE256', outputLength: 256, domainSeparation: 0x07 }], + [false, { name: 'TurboSHAKE256', outputLength: 0 }], + [false, { name: 'TurboSHAKE256', outputLength: 255 }], + [false, { name: 'TurboSHAKE256', outputLength: 256, domainSeparation: 0x00 }], + [false, { name: 'TurboSHAKE256', outputLength: 256, domainSeparation: 0x80 }], + [false, 'KT128'], + [true, { name: 'KT128', outputLength: 128 }], + [true, { name: 'KT128', outputLength: 128, customization: Buffer.alloc(0) }], + [false, { name: 'KT128', outputLength: 0 }], + [false, { name: 'KT128', outputLength: 127 }], + [false, 'KT256'], + [true, { name: 'KT256', outputLength: 256 }], + [true, { name: 'KT256', outputLength: 256, customization: Buffer.alloc(0) }], + [false, { name: 'KT256', outputLength: 0 }], + [false, { name: 'KT256', outputLength: 255 }], ], 'sign': [ [pqc, 'ML-DSA-44'], diff --git a/test/parallel/test-webcrypto-digest-turboshake-rfc.js b/test/parallel/test-webcrypto-digest-turboshake-rfc.js new file mode 100644 index 00000000000000..43762fecc2c41e --- /dev/null +++ b/test/parallel/test-webcrypto-digest-turboshake-rfc.js @@ -0,0 +1,399 @@ +'use strict'; + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { subtle } = globalThis.crypto; + +// RFC 9861 Section 5 test vectors + +// Generates a Buffer of length n by repeating the pattern 00 01 02 .. F9 FA. +function ptn(n) { + const buf = Buffer.allocUnsafe(n); + for (let i = 0; i < n; i++) + buf[i] = i % 251; + return buf; +} + +assert.deepStrictEqual( + ptn(17 ** 2).toString('hex'), + '000102030405060708090a0b0c0d0e0f' + + '101112131415161718191a1b1c1d1e1f' + + '202122232425262728292a2b2c2d2e2f' + + '303132333435363738393a3b3c3d3e3f' + + '404142434445464748494a4b4c4d4e4f' + + '505152535455565758595a5b5c5d5e5f' + + '606162636465666768696a6b6c6d6e6f' + + '707172737475767778797a7b7c7d7e7f' + + '808182838485868788898a8b8c8d8e8f' + + '909192939495969798999a9b9c9d9e9f' + + 'a0a1a2a3a4a5a6a7a8a9aaabacadaeaf' + + 'b0b1b2b3b4b5b6b7b8b9babbbcbdbebf' + + 'c0c1c2c3c4c5c6c7c8c9cacbcccdcecf' + + 'd0d1d2d3d4d5d6d7d8d9dadbdcdddedf' + + 'e0e1e2e3e4e5e6e7e8e9eaebecedeeef' + + 'f0f1f2f3f4f5f6f7f8f9fa0001020304' + + '05060708090a0b0c0d0e0f1011121314' + + '15161718191a1b1c1d1e1f2021222324' + + '25', +); + +const turboSHAKE128Vectors = [ + // [input, outputLengthBytes, expected(, domainSeparation)] + [new Uint8Array(0), 32, + '1e415f1c5983aff2169217277d17bb53' + + '8cd945a397ddec541f1ce41af2c1b74c'], + [new Uint8Array(0), 64, + '1e415f1c5983aff2169217277d17bb53' + + '8cd945a397ddec541f1ce41af2c1b74c' + + '3e8ccae2a4dae56c84a04c2385c03c15' + + 'e8193bdf58737363321691c05462c8df'], + [ptn(17 ** 0), 32, + '55cedd6f60af7bb29a4042ae832ef3f5' + + '8db7299f893ebb9247247d856958daa9'], + [ptn(17 ** 1), 32, + '9c97d036a3bac819db70ede0ca554ec6' + + 'e4c2a1a4ffbfd9ec269ca6a111161233'], + [ptn(17 ** 2), 32, + '96c77c279e0126f7fc07c9b07f5cdae1' + + 'e0be60bdbe10620040e75d7223a624d2'], + [ptn(17 ** 3), 32, + 'd4976eb56bcf118520582b709f73e1d6' + + '853e001fdaf80e1b13e0d0599d5fb372'], + [ptn(17 ** 4), 32, + 'da67c7039e98bf530cf7a37830c6664e' + + '14cbab7f540f58403b1b82951318ee5c'], + [ptn(17 ** 5), 32, + 'b97a906fbf83ef7c812517abf3b2d0ae' + + 'a0c4f60318ce11cf103925127f59eecd'], + [ptn(17 ** 6), 32, + '35cd494adeded2f25239af09a7b8ef0c' + + '4d1ca4fe2d1ac370fa63216fe7b4c2b1'], + [Buffer.from('ffffff', 'hex'), 32, + 'bf323f940494e88ee1c540fe660be8a0' + + 'c93f43d15ec006998462fa994eed5dab', 0x01], + [Buffer.from('ff', 'hex'), 32, + '8ec9c66465ed0d4a6c35d13506718d68' + + '7a25cb05c74cca1e42501abd83874a67', 0x06], + [Buffer.from('ffffff', 'hex'), 32, + 'b658576001cad9b1e5f399a9f77723bb' + + 'a05458042d68206f7252682dba3663ed', 0x07], + [Buffer.from('ffffffffffffff', 'hex'), 32, + '8deeaa1aec47ccee569f659c21dfa8e1' + + '12db3cee37b18178b2acd805b799cc37', 0x0b], + [Buffer.from('ff', 'hex'), 32, + '553122e2135e363c3292bed2c6421fa2' + + '32bab03daa07c7d6636603286506325b', 0x30], + [Buffer.from('ffffff', 'hex'), 32, + '16274cc656d44cefd422395d0f9053bd' + + 'a6d28e122aba15c765e5ad0e6eaf26f9', 0x7f], +]; + +const turboSHAKE256Vectors = [ + // [input, outputLengthBytes, expected(, domainSeparation)] + [new Uint8Array(0), 64, + '367a329dafea871c7802ec67f905ae13' + + 'c57695dc2c6663c61035f59a18f8e7db' + + '11edc0e12e91ea60eb6b32df06dd7f00' + + '2fbafabb6e13ec1cc20d995547600db0'], + [ptn(17 ** 0), 64, + '3e1712f928f8eaf1054632b2aa0a246e' + + 'd8b0c378728f60bc970410155c28820e' + + '90cc90d8a3006aa2372c5c5ea176b068' + + '2bf22bae7467ac94f74d43d39b0482e2'], + [ptn(17 ** 1), 64, + 'b3bab0300e6a191fbe61379398359235' + + '78794ea54843f5011090fa2f3780a9e5' + + 'cb22c59d78b40a0fbff9e672c0fbe097' + + '0bd2c845091c6044d687054da5d8e9c7'], + [ptn(17 ** 2), 64, + '66b810db8e90780424c0847372fdc957' + + '10882fde31c6df75beb9d4cd9305cfca' + + 'e35e7b83e8b7e6eb4b78605880116316' + + 'fe2c078a09b94ad7b8213c0a738b65c0'], + [ptn(17 ** 3), 64, + 'c74ebc919a5b3b0dd1228185ba02d29e' + + 'f442d69d3d4276a93efe0bf9a16a7dc0' + + 'cd4eabadab8cd7a5edd96695f5d360ab' + + 'e09e2c6511a3ec397da3b76b9e1674fb'], + [ptn(17 ** 4), 64, + '02cc3a8897e6f4f6ccb6fd46631b1f52' + + '07b66c6de9c7b55b2d1a23134a170afd' + + 'ac234eaba9a77cff88c1f020b7372461' + + '8c5687b362c430b248cd38647f848a1d'], + [ptn(17 ** 5), 64, + 'add53b06543e584b5823f626996aee50' + + 'fe45ed15f20243a7165485acb4aa76b4' + + 'ffda75cedf6d8cdc95c332bd56f4b986' + + 'b58bb17d1778bfc1b1a97545cdf4ec9f'], + [ptn(17 ** 6), 64, + '9e11bc59c24e73993c1484ec66358ef7' + + '1db74aefd84e123f7800ba9c4853e02c' + + 'fe701d9e6bb765a304f0dc34a4ee3ba8' + + '2c410f0da70e86bfbd90ea877c2d6104'], + [Buffer.from('ffffff', 'hex'), 64, + 'd21c6fbbf587fa2282f29aea620175fb' + + '0257413af78a0b1b2a87419ce031d933' + + 'ae7a4d383327a8a17641a34f8a1d1003' + + 'ad7da6b72dba84bb62fef28f62f12424', 0x01], + [Buffer.from('ff', 'hex'), 64, + '738d7b4e37d18b7f22ad1b5313e357e3' + + 'dd7d07056a26a303c433fa3533455280' + + 'f4f5a7d4f700efb437fe6d281405e07b' + + 'e32a0a972e22e63adc1b090daefe004b', 0x06], + [Buffer.from('ffffff', 'hex'), 64, + '18b3b5b7061c2e67c1753a00e6ad7ed7' + + 'ba1c906cf93efb7092eaf27fbeebb755' + + 'ae6e292493c110e48d260028492b8e09' + + 'b5500612b8f2578985ded5357d00ec67', 0x07], + [Buffer.from('ffffffffffffff', 'hex'), 64, + 'bb36764951ec97e9d85f7ee9a67a7718' + + 'fc005cf42556be79ce12c0bde50e5736' + + 'd6632b0d0dfb202d1bbb8ffe3dd74cb0' + + '0834fa756cb03471bab13a1e2c16b3c0', 0x0b], + [Buffer.from('ff', 'hex'), 64, + 'f3fe12873d34bcbb2e608779d6b70e7f' + + '86bec7e90bf113cbd4fdd0c4e2f4625e' + + '148dd7ee1a52776cf77f240514d9ccfc' + + '3b5ddab8ee255e39ee389072962c111a', 0x30], + [Buffer.from('ffffff', 'hex'), 64, + 'abe569c1f77ec340f02705e7d37c9ab7' + + 'e155516e4a6a150021d70b6fac0bb40c' + + '069f9a9828a0d575cd99f9bae435ab1a' + + 'cf7ed9110ba97ce0388d074bac768776', 0x7f], +]; + +const kt128Vectors = [ + // [input, outputLengthBytes, expected(, customization)] + [new Uint8Array(0), 32, + '1ac2d450fc3b4205d19da7bfca1b3751' + + '3c0803577ac7167f06fe2ce1f0ef39e5'], + [new Uint8Array(0), 64, + '1ac2d450fc3b4205d19da7bfca1b3751' + + '3c0803577ac7167f06fe2ce1f0ef39e5' + + '4269c056b8c82e48276038b6d292966c' + + 'c07a3d4645272e31ff38508139eb0a71'], + [ptn(1), 32, + '2bda92450e8b147f8a7cb629e784a058' + + 'efca7cf7d8218e02d345dfaa65244a1f'], + [ptn(17), 32, + '6bf75fa2239198db4772e36478f8e19b' + + '0f371205f6a9a93a273f51df37122888'], + [ptn(17 ** 2), 32, + '0c315ebcdedbf61426de7dcf8fb725d1' + + 'e74675d7f5327a5067f367b108ecb67c'], + [ptn(17 ** 3), 32, + 'cb552e2ec77d9910701d578b457ddf77' + + '2c12e322e4ee7fe417f92c758f0d59d0'], + [ptn(17 ** 4), 32, + '8701045e22205345ff4dda05555cbb5c' + + '3af1a771c2b89baef37db43d9998b9fe'], + [ptn(17 ** 5), 32, + '844d610933b1b9963cbdeb5ae3b6b05c' + + 'c7cbd67ceedf883eb678a0a8e0371682'], + [ptn(17 ** 6), 32, + '3c390782a8a4e89fa6367f72feaaf132' + + '55c8d95878481d3cd8ce85f58e880af8'], + [new Uint8Array(0), 32, + 'fab658db63e94a246188bf7af69a1330' + + '45f46ee984c56e3c3328caaf1aa1a583', ptn(1)], + [Buffer.from('ff', 'hex'), 32, + 'd848c5068ced736f4462159b9867fd4c' + + '20b808acc3d5bc48e0b06ba0a3762ec4', ptn(41)], + [Buffer.from('ffffff', 'hex'), 32, + 'c389e5009ae57120854c2e8c64670ac0' + + '1358cf4c1baf89447a724234dc7ced74', ptn(41 ** 2)], + [Buffer.from('ffffffffffffff', 'hex'), 32, + '75d2f86a2e644566726b4fbcfc5657b9' + + 'dbcf070c7b0dca06450ab291d7443bcf', ptn(41 ** 3)], + [ptn(8191), 32, + '1b577636f723643e990cc7d6a6598374' + + '36fd6a103626600eb8301cd1dbe553d6'], + [ptn(8192), 32, + '48f256f6772f9edfb6a8b661ec92dc93' + + 'b95ebd05a08a17b39ae3490870c926c3'], + [ptn(8192), 32, + '3ed12f70fb05ddb58689510ab3e4d23c' + + '6c6033849aa01e1d8c220a297fedcd0b', ptn(8189)], + [ptn(8192), 32, + '6a7c1b6a5cd0d8c9ca943a4a216cc646' + + '04559a2ea45f78570a15253d67ba00ae', ptn(8190)], +]; + +const kt256Vectors = [ + // [input, outputLengthBytes, expected(, customization)] + [new Uint8Array(0), 64, + 'b23d2e9cea9f4904e02bec06817fc10c' + + 'e38ce8e93ef4c89e6537076af8646404' + + 'e3e8b68107b8833a5d30490aa3348235' + + '3fd4adc7148ecb782855003aaebde4a9'], + [new Uint8Array(0), 128, + 'b23d2e9cea9f4904e02bec06817fc10c' + + 'e38ce8e93ef4c89e6537076af8646404' + + 'e3e8b68107b8833a5d30490aa3348235' + + '3fd4adc7148ecb782855003aaebde4a9' + + 'b0925319d8ea1e121a609821ec19efea' + + '89e6d08daee1662b69c840289f188ba8' + + '60f55760b61f82114c030c97e5178449' + + '608ccd2cd2d919fc7829ff69931ac4d0'], + [ptn(1), 64, + '0d005a194085360217128cf17f91e1f7' + + '1314efa5564539d444912e3437efa17f' + + '82db6f6ffe76e781eaa068bce01f2bbf' + + '81eacb983d7230f2fb02834a21b1ddd0'], + [ptn(17), 64, + '1ba3c02b1fc514474f06c8979978a905' + + '6c8483f4a1b63d0dccefe3a28a2f323e' + + '1cdcca40ebf006ac76ef039715234683' + + '7b1277d3e7faa9c9653b19075098527b'], + [ptn(17 ** 2), 64, + 'de8ccbc63e0f133ebb4416814d4c66f6' + + '91bbf8b6a61ec0a7700f836b086cb029' + + 'd54f12ac7159472c72db118c35b4e6aa' + + '213c6562caaa9dcc518959e69b10f3ba'], + [ptn(17 ** 3), 64, + '647efb49fe9d717500171b41e7f11bd4' + + '91544443209997ce1c2530d15eb1ffbb' + + '598935ef954528ffc152b1e4d731ee26' + + '83680674365cd191d562bae753b84aa5'], + [ptn(17 ** 4), 64, + 'b06275d284cd1cf205bcbe57dccd3ec1' + + 'ff6686e3ed15776383e1f2fa3c6ac8f0' + + '8bf8a162829db1a44b2a43ff83dd89c3' + + 'cf1ceb61ede659766d5ccf817a62ba8d'], + [ptn(17 ** 5), 64, + '9473831d76a4c7bf77ace45b59f1458b' + + '1673d64bcd877a7c66b2664aa6dd149e' + + '60eab71b5c2bab858c074ded81ddce2b' + + '4022b5215935c0d4d19bf511aeeb0772'], + [ptn(17 ** 6), 64, + '0652b740d78c5e1f7c8dcc1777097382' + + '768b7ff38f9a7a20f29f413bb1b3045b' + + '31a5578f568f911e09cf44746da84224' + + 'a5266e96a4a535e871324e4f9c7004da'], + [new Uint8Array(0), 64, + '9280f5cc39b54a5a594ec63de0bb9937' + + '1e4609d44bf845c2f5b8c316d72b1598' + + '11f748f23e3fabbe5c3226ec96c62186' + + 'df2d33e9df74c5069ceecbb4dd10eff6', ptn(1)], + [Buffer.from('ff', 'hex'), 64, + '47ef96dd616f200937aa7847e34ec2fe' + + 'ae8087e3761dc0f8c1a154f51dc9ccf8' + + '45d7adbce57ff64b639722c6a1672e3b' + + 'f5372d87e00aff89be97240756998853', ptn(41)], + [Buffer.from('ffffff', 'hex'), 64, + '3b48667a5051c5966c53c5d42b95de45' + + '1e05584e7806e2fb765eda959074172c' + + 'b438a9e91dde337c98e9c41bed94c4e0' + + 'aef431d0b64ef2324f7932caa6f54969', ptn(41 ** 2)], + [Buffer.from('ffffffffffffff', 'hex'), 64, + 'e0911cc00025e1540831e266d94add9b' + + '98712142b80d2629e643aac4efaf5a3a' + + '30a88cbf4ac2a91a2432743054fbcc98' + + '97670e86ba8cec2fc2ace9c966369724', ptn(41 ** 3)], + [ptn(8191), 64, + '3081434d93a4108d8d8a3305b89682ce' + + 'bedc7ca4ea8a3ce869fbb73cbe4a58ee' + + 'f6f24de38ffc170514c70e7ab2d01f03' + + '812616e863d769afb3753193ba045b20'], + [ptn(8192), 64, + 'c6ee8e2ad3200c018ac87aaa031cdac2' + + '2121b412d07dc6e0dccbb53423747e9a' + + '1c18834d99df596cf0cf4b8dfafb7bf0' + + '2d139d0c9035725adc1a01b7230a41fa'], + [ptn(8192), 64, + '74e47879f10a9c5d11bd2da7e194fe57' + + 'e86378bf3c3f7448eff3c576a0f18c5c' + + 'aae0999979512090a7f348af4260d4de' + + '3c37f1ecaf8d2c2c96c1d16c64b12496', ptn(8189)], + [ptn(8192), 64, + 'f4b5908b929ffe01e0f79ec2f21243d4' + + '1a396b2e7303a6af1d6399cd6c7a0a2d' + + 'd7c4f607e8277f9c9b1cb4ab9ddc59d4' + + 'b92d1fc7558441f1832c3279a4241b8b', ptn(8190)], +]; + +async function checkDigest(name, vectors) { + const isKT = name.startsWith('KT'); + for (const [input, outputLength, expected, ...rest] of vectors) { + const algorithm = { name, outputLength: outputLength * 8 }; + if (rest.length) { + if (isKT) + algorithm.customization = rest[0]; + else + algorithm.domainSeparation = rest[0]; + } + const result = await subtle.digest(algorithm, input); + assert.deepStrictEqual( + Buffer.from(result).toString('hex'), + expected, + ); + } +} + +(async () => { + await checkDigest('TurboSHAKE128', turboSHAKE128Vectors); + + // TurboSHAKE128(M=00^0, D=1F, 10032), last 32 bytes + { + const result = await subtle.digest({ + name: 'TurboSHAKE128', + outputLength: 10032 * 8, + }, new Uint8Array(0)); + assert.deepStrictEqual( + Buffer.from(result).subarray(-32).toString('hex'), + 'a3b9b0385900ce761f22aed548e754da' + + '10a5242d62e8c658e3f3a923a7555607', + ); + } + + await checkDigest('TurboSHAKE256', turboSHAKE256Vectors); + + // TurboSHAKE256(M=00^0, D=1F, 10032), last 32 bytes + { + const result = await subtle.digest({ + name: 'TurboSHAKE256', + outputLength: 10032 * 8, + }, new Uint8Array(0)); + assert.deepStrictEqual( + Buffer.from(result).subarray(-32).toString('hex'), + 'abefa11630c661269249742685ec082f' + + '207265dccf2f43534e9c61ba0c9d1d75', + ); + } + + await checkDigest('KT128', kt128Vectors); + + // KT128(M=00^0, C=00^0, 10032), last 32 bytes + { + const result = await subtle.digest({ + name: 'KT128', + outputLength: 10032 * 8, + }, new Uint8Array(0)); + assert.deepStrictEqual( + Buffer.from(result).subarray(-32).toString('hex'), + 'e8dc563642f7228c84684c898405d3a8' + + '34799158c079b12880277a1d28e2ff6d', + ); + } + + await checkDigest('KT256', kt256Vectors); + + // KT256(M=00^0, C=00^0, 10064), last 64 bytes + { + const result = await subtle.digest({ + name: 'KT256', + outputLength: 10064 * 8, + }, new Uint8Array(0)); + assert.deepStrictEqual( + Buffer.from(result).subarray(-64).toString('hex'), + 'ad4a1d718cf950506709a4c33396139b' + + '4449041fc79a05d68da35f1e453522e0' + + '56c64fe94958e7085f2964888259b993' + + '2752f3ccd855288efee5fcbb8b563069', + ); + } +})().then(common.mustCall()); diff --git a/test/parallel/test-webcrypto-digest-turboshake.js b/test/parallel/test-webcrypto-digest-turboshake.js new file mode 100644 index 00000000000000..0b5586b19286be --- /dev/null +++ b/test/parallel/test-webcrypto-digest-turboshake.js @@ -0,0 +1,181 @@ +'use strict'; + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { subtle } = globalThis.crypto; + +const kSourceData = { + empty: '', + short: '156eea7cc14c56cb94db030a4a9d95ff', + medium: 'b6c8f9df648cd088b70f38e74197b18cb81e1e435' + + '0d50bccb8fb5a7379c87bb2e3d6ed5461ed1e9f36' + + 'f340a3962a446b815b794b4bd43a4403502077b22' + + '56cc807837f3aacd118eb4b9c2baeb897068625ab' + + 'aca193', + long: null +}; + +kSourceData.long = kSourceData.medium.repeat(1024); + +// Test vectors generated with PyCryptodome as a reference implementation +const kDigestedData = { + 'turboshake128': { + empty: '1e415f1c5983aff2169217277d17bb538cd945a397ddec541f1ce41af2c1b74c', + short: 'f8d1ebf3b48b71b0514686090eb25f1de322a00149be9b4dc5f09ac9077cd8a8', + medium: '0d0e7eceb4ae58c3c48f6c2bad56d0f8ff3f887468d3ea55a138aedf395233c0', + long: '5747c06f02ffd9d6c911b6453cc8b717083ab6417319a6ec5c3bb39ed0baf331', + }, + 'turboshake256': { + empty: '367a329dafea871c7802ec67f905ae13c57695dc2c6663c61035f59a18f8e7db' + + '11edc0e12e91ea60eb6b32df06dd7f002fbafabb6e13ec1cc20d995547600db0', + short: 'b47aa0a5b76caf9b10cfaeff036df0cdb86362d2bd036a2fee0cd0d74e79279c' + + 'b9c57a70da1e3dd9e126a469857ba4c82b0efb3ae06d1a3781a6f102c3eb3a1d', + medium: '7fa19fd828762d2dba6eea8407d1fb04302b5a4f1ca3d00b3672c1e3b3331d18' + + '925b7ec380f3f04673a164dab04d2a0c5c12818046284c38d286645741a8aa3e', + long: '12d0b90c08f588710733cc07f0a2d6ab0795a4a24904c111062226fcd9d5dcb2' + + '1d6b5b848c9aebbcab221f031e9b4ea71e099ec785e822b1b83e73d0750ca1a7', + }, + 'kt128': { + empty: '1ac2d450fc3b4205d19da7bfca1b37513c0803577ac7167f06fe2ce1f0ef39e5', + short: '4719a2ac1dc1c592521cf201df3f476ea496fe461abe9a2604527f6bec047579', + medium: '00f3add71679681720b925416953897ac62cfae97060dd5f2e1641a076580cc9', + long: 'c05805c2736deb4be3fca6e3717b9af0aa18ceeaaeeab66b328a3ffebf0a814d', + }, + 'kt256': { + empty: 'b23d2e9cea9f4904e02bec06817fc10ce38ce8e93ef4c89e6537076af8646404' + + 'e3e8b68107b8833a5d30490aa33482353fd4adc7148ecb782855003aaebde4a9', + short: '6709e5e312f2dee4547ecb0ab7d42728ba57985983731afbd6c2a0676c522274' + + 'cf9153064ee07982129d3f58d4dbe00050eb28b392559bdb020aca302b7a28cb', + medium: '9078b6ff78e9c4b3c8ff49e5b9f337b36cc6d6749d23985035886d993db69f7e' + + '05fea97125e0889130da09fc5837761f7793e3e44d85be1ee1f6af7f4a1f50cb', + long: '41f83b7c7d02fc6d98f1fa1474d765caff4673f90cd7204894d7da72d97403b6' + + '2fe5c4bae2bf0ce3dcd51e80c98bd25ce5fe54040259d9466b67f1517dac0712', + }, +}; + +function buildAlg(name) { + const lower = name.toLowerCase(); + if (lower.startsWith('turboshake')) { + const outputLength = lower === 'turboshake128' ? 256 : 512; + return { name, outputLength }; + } + if (lower.startsWith('kt')) { + const outputLength = lower === 'kt128' ? 256 : 512; + return { name, outputLength }; + } + return name; +} + +async function testDigest(size, alg) { + const digest = await subtle.digest( + alg, + Buffer.from(kSourceData[size], 'hex')); + + assert.strictEqual( + Buffer.from(digest).toString('hex'), + kDigestedData[(alg.name || alg).toLowerCase()][size]); +} + +// Known-answer tests +(async function() { + const variations = []; + Object.keys(kSourceData).forEach((size) => { + Object.keys(kDigestedData).forEach((alg) => { + const upCase = alg.toUpperCase(); + const downCase = alg.toLowerCase(); + const mixedCase = upCase.slice(0, 1) + downCase.slice(1); + + variations.push(testDigest(size, buildAlg(upCase))); + variations.push(testDigest(size, buildAlg(downCase))); + variations.push(testDigest(size, buildAlg(mixedCase))); + }); + }); + + await Promise.all(variations); +})().then(common.mustCall()); + +// Edge cases: zero-length output rejects +(async () => { + await assert.rejects( + subtle.digest({ name: 'TurboSHAKE128', outputLength: 0 }, Buffer.alloc(1)), + { + name: 'OperationError', + message: 'Invalid TurboShakeParams outputLength', + }); + + await assert.rejects( + subtle.digest({ name: 'KT128', outputLength: 0 }, Buffer.alloc(1)), + { + name: 'OperationError', + message: 'Invalid KangarooTwelveParams outputLength', + }); +})().then(common.mustCall()); + +// Edge case: non-byte-aligned outputLength rejects +(async () => { + await assert.rejects( + subtle.digest({ name: 'TurboSHAKE128', outputLength: 7 }, Buffer.alloc(1)), + { + name: 'OperationError', + message: 'Invalid TurboShakeParams outputLength', + }); + + await assert.rejects( + subtle.digest({ name: 'KT128', outputLength: 7 }, Buffer.alloc(1)), + { + name: 'OperationError', + message: 'Invalid KangarooTwelveParams outputLength', + }); +})().then(common.mustCall()); + +// TurboSHAKE domain separation byte +(async () => { + // Domain separation 0x07 should produce different output than default 0x1F + const [d07, d1f] = await Promise.all([ + subtle.digest( + { name: 'TurboSHAKE128', outputLength: 256, domainSeparation: 0x07 }, + Buffer.alloc(0)), + subtle.digest( + { name: 'TurboSHAKE128', outputLength: 256 }, + Buffer.alloc(0)), + ]); + assert.notDeepStrictEqual( + new Uint8Array(d07), + new Uint8Array(d1f)); + + // Verify D=0x07 against known vector + assert.strictEqual( + Buffer.from(d07).toString('hex'), + '5a223ad30b3b8c66a243048cfced430f54e7529287d15150b973133adfac6a2f'); +})().then(common.mustCall()); + +// KT128 with customization string +(async () => { + const digest = await subtle.digest( + { name: 'KT128', outputLength: 256, customization: Buffer.from('test') }, + Buffer.from('hello')); + assert(digest instanceof ArrayBuffer); + assert.strictEqual(digest.byteLength, 32); +})().then(common.mustCall()); + +// TurboSHAKE domain separation out of range +(async () => { + await assert.rejects( + subtle.digest( + { name: 'TurboSHAKE128', outputLength: 256, domainSeparation: 0x00 }, + Buffer.alloc(0)), + { + name: 'OperationError', + }); + await assert.rejects( + subtle.digest( + { name: 'TurboSHAKE128', outputLength: 256, domainSeparation: 0x80 }, + Buffer.alloc(0)), + { + name: 'OperationError', + }); +})().then(common.mustCall()); diff --git a/test/wpt/status/WebCryptoAPI.cjs b/test/wpt/status/WebCryptoAPI.cjs index 3f788d87fd0849..722a0b38398e1d 100644 --- a/test/wpt/status/WebCryptoAPI.cjs +++ b/test/wpt/status/WebCryptoAPI.cjs @@ -55,12 +55,6 @@ module.exports = { 'historical.any.js': { 'skip': 'Not relevant in Node.js context', }, - 'digest/turboshake.tentative.https.any.js': { - 'skip': 'not implemented', - }, - 'digest/kangarootwelve.tentative.https.any.js': { - 'skip': 'not implemented', - }, 'sign_verify/eddsa_small_order_points.https.any.js': { 'fail': { 'note': 'see https://github.com/nodejs/node/issues/54572',