From 40b577a61d4ed3aed1ce72e4929ab52b8c7bbbf5 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Fri, 27 Mar 2026 09:23:46 +0100 Subject: [PATCH 1/2] crypto: add raw key formats support to the KeyObject APIs PR-URL: https://github.com/nodejs/node/pull/62240 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/keys.js | 163 +++++-- src/crypto/crypto_keys.cc | 366 +++++++++++--- src/crypto/crypto_keys.h | 10 +- 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 +- 17 files changed, 1670 insertions(+), 195 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 9e9dc92fcf742b..dc84097f846cd2 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` @@ -2125,6 +2393,10 @@ type, value, and parameters. This method is not @@ -3677,6 +3961,9 @@ of the passphrase is limited to 1024 bytes. @@ -6542,6 +6834,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 @@ -6566,6 +6859,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 @@ -6575,6 +6869,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/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/src/crypto/crypto_keys.cc b/src/crypto/crypto_keys.cc index 93a741fb1758a4..00d831f8f0f13c 100644 --- a/src/crypto/crypto_keys.cc +++ b/src/crypto/crypto_keys.cc @@ -19,8 +19,10 @@ namespace node { +using ncrypto::BignumPointer; using ncrypto::BIOPointer; using ncrypto::ECKeyPointer; +using ncrypto::ECPointPointer; using ncrypto::EVPKeyCtxPointer; using ncrypto::EVPKeyPointer; using ncrypto::MarkPopErrorOnReturn; @@ -278,33 +280,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 +641,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 +670,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 +801,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 +864,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 +883,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 +895,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 +1160,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 +1170,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 +1389,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) { diff --git a/src/crypto/crypto_keys.h b/src/crypto/crypto_keys.h index 90c252eba28bea..d35249724a448e 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( 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 37eab463deae47..aef1012098fbb9 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 293c0c87265eea..5990258b04f697 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); + + // 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'); From 19049e45dac817685b1747ea0b0c3e4070cbadda Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Sun, 22 Mar 2026 00:28:21 +0000 Subject: [PATCH 2/2] 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 +- test/wpt/status/webidl.json | 15 + 84 files changed, 2331 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 d23fb1d40453fe..1abc8e4943628a 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" ] } } diff --git a/test/wpt/status/webidl.json b/test/wpt/status/webidl.json index 26a6e44f484149..66506388b4ade0 100644 --- a/test/wpt/status/webidl.json +++ b/test/wpt/status/webidl.json @@ -66,5 +66,20 @@ "ObservableArray's ownKeys trap" ] } + }, + "idlharness.any.js": { + "fail": { + "note": "https://github.com/nodejs/node/pull/62293", + "expected": [ + "QuotaExceededError interface: existence and properties of interface object", + "QuotaExceededError interface object length", + "QuotaExceededError interface object name", + "QuotaExceededError interface: existence and properties of interface prototype object", + "QuotaExceededError interface: existence and properties of interface prototype object's \"constructor\" property", + "QuotaExceededError interface: existence and properties of interface prototype object's @@unscopables property", + "QuotaExceededError interface: attribute quota", + "QuotaExceededError interface: attribute requested" + ] + } } }