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/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index 3a26cfbdcab52e..87a6b7a3501e4d 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -777,11 +777,15 @@ bool PrintGeneralName(const BIOPointer& out, const GENERAL_NAME* gen) { // Note that the preferred name syntax (see RFCs 5280 and 1034) with // wildcards is a subset of what we consider "safe", so spec-compliant DNS // names will never need to be escaped. - PrintAltName(out, reinterpret_cast(name->data), name->length); + PrintAltName(out, + reinterpret_cast(ASN1_STRING_get0_data(name)), + ASN1_STRING_length(name)); } else if (gen->type == GEN_EMAIL) { ASN1_IA5STRING* name = gen->d.rfc822Name; BIO_write(out.get(), "email:", 6); - PrintAltName(out, reinterpret_cast(name->data), name->length); + PrintAltName(out, + reinterpret_cast(ASN1_STRING_get0_data(name)), + ASN1_STRING_length(name)); } else if (gen->type == GEN_URI) { ASN1_IA5STRING* name = gen->d.uniformResourceIdentifier; BIO_write(out.get(), "URI:", 4); @@ -789,7 +793,9 @@ bool PrintGeneralName(const BIOPointer& out, const GENERAL_NAME* gen) { // with a few exceptions, most notably URIs that contains commas (see // RFC 2396). In other words, most legitimate URIs will not require // escaping. - PrintAltName(out, reinterpret_cast(name->data), name->length); + PrintAltName(out, + reinterpret_cast(ASN1_STRING_get0_data(name)), + ASN1_STRING_length(name)); } else if (gen->type == GEN_DIRNAME) { // Earlier versions of Node.js used X509_NAME_oneline to print the X509_NAME // object. The format was non standard and should be avoided. The use of @@ -822,17 +828,18 @@ bool PrintGeneralName(const BIOPointer& out, const GENERAL_NAME* gen) { } else if (gen->type == GEN_IPADD) { BIO_printf(out.get(), "IP Address:"); const ASN1_OCTET_STRING* ip = gen->d.ip; - const unsigned char* b = ip->data; - if (ip->length == 4) { + const unsigned char* b = ASN1_STRING_get0_data(ip); + int ip_len = ASN1_STRING_length(ip); + if (ip_len == 4) { BIO_printf(out.get(), "%d.%d.%d.%d", b[0], b[1], b[2], b[3]); - } else if (ip->length == 16) { + } else if (ip_len == 16) { for (unsigned int j = 0; j < 8; j++) { uint16_t pair = (b[2 * j] << 8) | b[2 * j + 1]; BIO_printf(out.get(), (j == 0) ? "%X" : ":%X", pair); } } else { #if OPENSSL_VERSION_MAJOR >= 3 - BIO_printf(out.get(), "", ip->length); + BIO_printf(out.get(), "", ip_len); #else BIO_printf(out.get(), ""); #endif @@ -882,15 +889,15 @@ bool PrintGeneralName(const BIOPointer& out, const GENERAL_NAME* gen) { if (unicode) { auto name = gen->d.otherName->value->value.utf8string; PrintAltName(out, - reinterpret_cast(name->data), - name->length, + reinterpret_cast(ASN1_STRING_get0_data(name)), + ASN1_STRING_length(name), AltNameOption::UTF8, prefix); } else { auto name = gen->d.otherName->value->value.ia5string; PrintAltName(out, - reinterpret_cast(name->data), - name->length, + reinterpret_cast(ASN1_STRING_get0_data(name)), + ASN1_STRING_length(name), AltNameOption::NONE, prefix); } @@ -911,11 +918,14 @@ bool PrintGeneralName(const BIOPointer& out, const GENERAL_NAME* gen) { } } // namespace -bool SafeX509SubjectAltNamePrint(const BIOPointer& out, X509_EXTENSION* ext) { - auto ret = OBJ_obj2nid(X509_EXTENSION_get_object(ext)); +bool SafeX509SubjectAltNamePrint(const BIOPointer& out, + const X509_EXTENSION* ext) { + // const_cast needed for OpenSSL < 4.0 which lacks const-correctness + auto* mext = const_cast(ext); + auto ret = OBJ_obj2nid(X509_EXTENSION_get_object(mext)); if (ret != NID_subject_alt_name) return false; - GENERAL_NAMES* names = static_cast(X509V3_EXT_d2i(ext)); + GENERAL_NAMES* names = static_cast(X509V3_EXT_d2i(mext)); if (names == nullptr) return false; bool ok = true; @@ -934,12 +944,14 @@ bool SafeX509SubjectAltNamePrint(const BIOPointer& out, X509_EXTENSION* ext) { return ok; } -bool SafeX509InfoAccessPrint(const BIOPointer& out, X509_EXTENSION* ext) { - auto ret = OBJ_obj2nid(X509_EXTENSION_get_object(ext)); +bool SafeX509InfoAccessPrint(const BIOPointer& out, const X509_EXTENSION* ext) { + // const_cast needed for OpenSSL < 4.0 which lacks const-correctness + auto* mext = const_cast(ext); + auto ret = OBJ_obj2nid(X509_EXTENSION_get_object(mext)); if (ret != NID_info_access) return false; AUTHORITY_INFO_ACCESS* descs = - static_cast(X509V3_EXT_d2i(ext)); + static_cast(X509V3_EXT_d2i(mext)); if (descs == nullptr) return false; bool ok = true; @@ -1083,7 +1095,7 @@ BIOPointer X509View::getValidFrom() const { if (cert_ == nullptr) return {}; BIOPointer bio(BIO_new(BIO_s_mem())); if (!bio) return {}; - ASN1_TIME_print(bio.get(), X509_get_notBefore(cert_)); + ASN1_TIME_print(bio.get(), X509_get0_notBefore(cert_)); return bio; } @@ -1092,7 +1104,7 @@ BIOPointer X509View::getValidTo() const { if (cert_ == nullptr) return {}; BIOPointer bio(BIO_new(BIO_s_mem())); if (!bio) return {}; - ASN1_TIME_print(bio.get(), X509_get_notAfter(cert_)); + ASN1_TIME_print(bio.get(), X509_get0_notAfter(cert_)); return bio; } @@ -4643,12 +4655,12 @@ bool X509Name::Iterator::operator!=(const Iterator& other) const { std::pair X509Name::Iterator::operator*() const { if (loc_ == name_.total_) return {{}, {}}; - X509_NAME_ENTRY* entry = X509_NAME_get_entry(name_, loc_); + const X509_NAME_ENTRY* entry = X509_NAME_get_entry(name_, loc_); if (entry == nullptr) [[unlikely]] return {{}, {}}; - ASN1_OBJECT* name = X509_NAME_ENTRY_get_object(entry); - ASN1_STRING* value = X509_NAME_ENTRY_get_data(entry); + const ASN1_OBJECT* name = X509_NAME_ENTRY_get_object(entry); + const ASN1_STRING* value = X509_NAME_ENTRY_get_data(entry); if (name == nullptr || value == nullptr) [[unlikely]] { return {{}, {}}; diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index 5ffaee379e481a..c781d7e2e0288f 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -1578,8 +1578,9 @@ int NoPasswordCallback(char* buf, int size, int rwflag, void* u); int PasswordCallback(char* buf, int size, int rwflag, void* u); -bool SafeX509SubjectAltNamePrint(const BIOPointer& out, X509_EXTENSION* ext); -bool SafeX509InfoAccessPrint(const BIOPointer& out, X509_EXTENSION* ext); +bool SafeX509SubjectAltNamePrint(const BIOPointer& out, + const X509_EXTENSION* ext); +bool SafeX509InfoAccessPrint(const BIOPointer& out, const X509_EXTENSION* ext); // ============================================================================ // SPKAC diff --git a/doc/api/crypto.md b/doc/api/crypto.md index acde45c346c84e..8f3f1fac1fae0b 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -75,37 +75,305 @@ try { ## Asymmetric key types -The following table lists the asymmetric key types recognized by the [`KeyObject`][] API: - -| Key Type | Description | OID | -| ---------------------------------- | ------------------ | ----------------------- | -| `'dh'` | Diffie-Hellman | 1.2.840.113549.1.3.1 | -| `'dsa'` | DSA | 1.2.840.10040.4.1 | -| `'ec'` | Elliptic curve | 1.2.840.10045.2.1 | -| `'ed25519'` | Ed25519 | 1.3.101.112 | -| `'ed448'` | Ed448 | 1.3.101.113 | -| `'ml-dsa-44'`[^openssl35] | ML-DSA-44 | 2.16.840.1.101.3.4.3.17 | -| `'ml-dsa-65'`[^openssl35] | ML-DSA-65 | 2.16.840.1.101.3.4.3.18 | -| `'ml-dsa-87'`[^openssl35] | ML-DSA-87 | 2.16.840.1.101.3.4.3.19 | -| `'ml-kem-512'`[^openssl35] | ML-KEM-512 | 2.16.840.1.101.3.4.4.1 | -| `'ml-kem-768'`[^openssl35] | ML-KEM-768 | 2.16.840.1.101.3.4.4.2 | -| `'ml-kem-1024'`[^openssl35] | ML-KEM-1024 | 2.16.840.1.101.3.4.4.3 | -| `'rsa-pss'` | RSA PSS | 1.2.840.113549.1.1.10 | -| `'rsa'` | RSA | 1.2.840.113549.1.1.1 | -| `'slh-dsa-sha2-128f'`[^openssl35] | SLH-DSA-SHA2-128f | 2.16.840.1.101.3.4.3.21 | -| `'slh-dsa-sha2-128s'`[^openssl35] | SLH-DSA-SHA2-128s | 2.16.840.1.101.3.4.3.20 | -| `'slh-dsa-sha2-192f'`[^openssl35] | SLH-DSA-SHA2-192f | 2.16.840.1.101.3.4.3.23 | -| `'slh-dsa-sha2-192s'`[^openssl35] | SLH-DSA-SHA2-192s | 2.16.840.1.101.3.4.3.22 | -| `'slh-dsa-sha2-256f'`[^openssl35] | SLH-DSA-SHA2-256f | 2.16.840.1.101.3.4.3.25 | -| `'slh-dsa-sha2-256s'`[^openssl35] | SLH-DSA-SHA2-256s | 2.16.840.1.101.3.4.3.24 | -| `'slh-dsa-shake-128f'`[^openssl35] | SLH-DSA-SHAKE-128f | 2.16.840.1.101.3.4.3.27 | -| `'slh-dsa-shake-128s'`[^openssl35] | SLH-DSA-SHAKE-128s | 2.16.840.1.101.3.4.3.26 | -| `'slh-dsa-shake-192f'`[^openssl35] | SLH-DSA-SHAKE-192f | 2.16.840.1.101.3.4.3.29 | -| `'slh-dsa-shake-192s'`[^openssl35] | SLH-DSA-SHAKE-192s | 2.16.840.1.101.3.4.3.28 | -| `'slh-dsa-shake-256f'`[^openssl35] | SLH-DSA-SHAKE-256f | 2.16.840.1.101.3.4.3.31 | -| `'slh-dsa-shake-256s'`[^openssl35] | SLH-DSA-SHAKE-256s | 2.16.840.1.101.3.4.3.30 | -| `'x25519'` | X25519 | 1.3.101.110 | -| `'x448'` | X448 | 1.3.101.111 | +The following table lists the asymmetric key types recognized by the +[`KeyObject`][] API and the export/import formats supported for each key type. + +| Key Type | Description | OID | `'pem'` | `'der'` | `'jwk'` | `'raw-public'` | `'raw-private'` | `'raw-seed'` | +| ---------------------------------- | ------------------ | ----------------------- | ------- | ------- | ------- | -------------- | --------------- | ------------ | +| `'dh'` | Diffie-Hellman | 1.2.840.113549.1.3.1 | ✔ | ✔ | | | | | +| `'dsa'` | DSA | 1.2.840.10040.4.1 | ✔ | ✔ | | | | | +| `'ec'` | Elliptic curve | 1.2.840.10045.2.1 | ✔ | ✔ | ✔ | ✔ | ✔ | | +| `'ed25519'` | Ed25519 | 1.3.101.112 | ✔ | ✔ | ✔ | ✔ | ✔ | | +| `'ed448'` | Ed448 | 1.3.101.113 | ✔ | ✔ | ✔ | ✔ | ✔ | | +| `'ml-dsa-44'`[^openssl35] | ML-DSA-44 | 2.16.840.1.101.3.4.3.17 | ✔ | ✔ | ✔ | ✔ | | ✔ | +| `'ml-dsa-65'`[^openssl35] | ML-DSA-65 | 2.16.840.1.101.3.4.3.18 | ✔ | ✔ | ✔ | ✔ | | ✔ | +| `'ml-dsa-87'`[^openssl35] | ML-DSA-87 | 2.16.840.1.101.3.4.3.19 | ✔ | ✔ | ✔ | ✔ | | ✔ | +| `'ml-kem-512'`[^openssl35] | ML-KEM-512 | 2.16.840.1.101.3.4.4.1 | ✔ | ✔ | | ✔ | | ✔ | +| `'ml-kem-768'`[^openssl35] | ML-KEM-768 | 2.16.840.1.101.3.4.4.2 | ✔ | ✔ | | ✔ | | ✔ | +| `'ml-kem-1024'`[^openssl35] | ML-KEM-1024 | 2.16.840.1.101.3.4.4.3 | ✔ | ✔ | | ✔ | | ✔ | +| `'rsa-pss'` | RSA PSS | 1.2.840.113549.1.1.10 | ✔ | ✔ | | | | | +| `'rsa'` | RSA | 1.2.840.113549.1.1.1 | ✔ | ✔ | ✔ | | | | +| `'slh-dsa-sha2-128f'`[^openssl35] | SLH-DSA-SHA2-128f | 2.16.840.1.101.3.4.3.21 | ✔ | ✔ | | ✔ | ✔ | | +| `'slh-dsa-sha2-128s'`[^openssl35] | SLH-DSA-SHA2-128s | 2.16.840.1.101.3.4.3.20 | ✔ | ✔ | | ✔ | ✔ | | +| `'slh-dsa-sha2-192f'`[^openssl35] | SLH-DSA-SHA2-192f | 2.16.840.1.101.3.4.3.23 | ✔ | ✔ | | ✔ | ✔ | | +| `'slh-dsa-sha2-192s'`[^openssl35] | SLH-DSA-SHA2-192s | 2.16.840.1.101.3.4.3.22 | ✔ | ✔ | | ✔ | ✔ | | +| `'slh-dsa-sha2-256f'`[^openssl35] | SLH-DSA-SHA2-256f | 2.16.840.1.101.3.4.3.25 | ✔ | ✔ | | ✔ | ✔ | | +| `'slh-dsa-sha2-256s'`[^openssl35] | SLH-DSA-SHA2-256s | 2.16.840.1.101.3.4.3.24 | ✔ | ✔ | | ✔ | ✔ | | +| `'slh-dsa-shake-128f'`[^openssl35] | SLH-DSA-SHAKE-128f | 2.16.840.1.101.3.4.3.27 | ✔ | ✔ | | ✔ | ✔ | | +| `'slh-dsa-shake-128s'`[^openssl35] | SLH-DSA-SHAKE-128s | 2.16.840.1.101.3.4.3.26 | ✔ | ✔ | | ✔ | ✔ | | +| `'slh-dsa-shake-192f'`[^openssl35] | SLH-DSA-SHAKE-192f | 2.16.840.1.101.3.4.3.29 | ✔ | ✔ | | ✔ | ✔ | | +| `'slh-dsa-shake-192s'`[^openssl35] | SLH-DSA-SHAKE-192s | 2.16.840.1.101.3.4.3.28 | ✔ | ✔ | | ✔ | ✔ | | +| `'slh-dsa-shake-256f'`[^openssl35] | SLH-DSA-SHAKE-256f | 2.16.840.1.101.3.4.3.31 | ✔ | ✔ | | ✔ | ✔ | | +| `'slh-dsa-shake-256s'`[^openssl35] | SLH-DSA-SHAKE-256s | 2.16.840.1.101.3.4.3.30 | ✔ | ✔ | | ✔ | ✔ | | +| `'x25519'` | X25519 | 1.3.101.110 | ✔ | ✔ | ✔ | ✔ | ✔ | | +| `'x448'` | X448 | 1.3.101.111 | ✔ | ✔ | ✔ | ✔ | ✔ | | + +### Key formats + +Asymmetric keys can be represented in several formats. **The recommended +approach is to import key material into a [`KeyObject`][] once and reuse it** +for all subsequent operations, as this avoids repeated parsing and delivers +the best performance. + +When a [`KeyObject`][] is not practical - for example, when key material +arrives in a protocol message and is used only once - most cryptographic +functions also accept a PEM string or an object specifying the format +and key material directly. See [`crypto.createPublicKey()`][], +[`crypto.createPrivateKey()`][], and [`keyObject.export()`][] for the full +options accepted by each format. + +#### KeyObject + +A [`KeyObject`][] is the in-memory representation of a parsed key. It is +created by [`crypto.createPublicKey()`][], [`crypto.createPrivateKey()`][], +[`crypto.createSecretKey()`][], or key generation functions such as +[`crypto.generateKeyPair()`][]. The first cryptographic operation with a given +[`KeyObject`][] may be slower than subsequent ones because OpenSSL lazily +initializes internal caches on first use. + +#### PEM and DER + +PEM and DER are the traditional encoding formats for asymmetric keys based on +ASN.1 structures. + +* **PEM** is a text encoding that wraps Base64-encoded DER data between + header and footer lines (e.g. `-----BEGIN PUBLIC KEY-----`). PEM strings can + be passed directly to most cryptographic operations. +* **DER** is the binary encoding of the same ASN.1 structures. When providing + DER input, the `type` (typically `'spki'` or `'pkcs8'`) must be specified + explicitly. + +#### JSON Web Key (JWK) + +JSON Web Key (JWK) is a JSON-based key representation defined in +[RFC 7517][]. JWK encodes each key component as an individual Base64url-encoded +value inside a JSON object. For RSA keys, JWK avoids ASN.1 parsing overhead +and is the fastest serialized import format. + +#### Raw key formats + +> Stability: 1.1 - Active development + +The `'raw-public'`, `'raw-private'`, and `'raw-seed'` key formats allow +importing and exporting raw key material without any encoding wrapper. +See [`keyObject.export()`][], [`crypto.createPublicKey()`][], and +[`crypto.createPrivateKey()`][] for usage details. + +`'raw-public'` is generally the fastest way to import a public key. +`'raw-private'` and `'raw-seed'` are not always faster than other formats +because they only contain the private scalar or seed - importing them requires +deriving the public key component (e.g. elliptic curve point multiplication or +seed expansion), which can be expensive. Other formats include both private +and public components, avoiding that computation. + +### Choosing a key format + +**Always prefer a [`KeyObject`][]** - create one from whatever format you +have and reuse it. The guidance below applies only when choosing between +serialization formats, either for importing into a [`KeyObject`][] or for +passing key material inline when a [`KeyObject`][] is not practical. + +#### Importing keys + +When creating a [`KeyObject`][] for repeated use, the import cost is paid once, +so choosing a faster format reduces startup latency. + +The import cost breaks down into two parts: **parsing overhead** (decoding the +serialization wrapper) and **key computation** (any mathematical work needed to +reconstruct the full key, such as deriving a public key from a private scalar +or expanding a seed). Which part dominates depends on the key type. For +example: + +* Public keys - `'raw-public'` is the fastest serialized format because the + raw format skips all ASN.1 and Base64 decoding. +* EC private keys - `'raw-private'` is faster than PEM or DER because it + avoids ASN.1 parsing. However, for larger curves (e.g. P-384, P-521) the + required derivation of the public point from the private scalar becomes + expensive, reducing the advantage. +* RSA keys - `'jwk'` is the fastest serialized format. JWK represents RSA + key components as individual Base64url-encoded integers, avoiding the + overhead of ASN.1 parsing entirely. + +#### Inline key material in operations + +When a [`KeyObject`][] cannot be reused (e.g. the key arrives as raw bytes in +a protocol message and is used only once), most cryptographic functions also +accept a PEM string or an object specifying the format and key +material directly. In this case the total cost is the sum of key import and +the cryptographic computation itself. + +For operations where the cryptographic computation dominates - such as +signing with RSA or ECDH key agreement with P-384 or P-521 - the +serialization format has negligible impact on overall throughput, so choose +whichever format is most convenient. For lightweight operations like Ed25519 +signing or verification, the import cost is a larger fraction of the total, +so a faster format like `'raw-public'` or `'raw-private'` can meaningfully +improve throughput. + +Even if the same key material is used only a few times, it is worth importing it +into a [`KeyObject`][] rather than passing the raw or PEM representation +repeatedly. + +### Examples + +Example: Reusing a [`KeyObject`][] across sign and verify operations: + +```mjs +import { promisify } from 'node:util'; +const { generateKeyPair, sign, verify } = await import('node:crypto'); + +const { publicKey, privateKey } = await promisify(generateKeyPair)('ed25519'); + +// A KeyObject holds the parsed key in memory and can be reused +// across multiple operations without re-parsing. +const data = new TextEncoder().encode('message to sign'); +const signature = sign(null, data, privateKey); +verify(null, data, publicKey, signature); +``` + +Example: Importing keys of various formats into [`KeyObject`][]s: + +```mjs +import { promisify } from 'node:util'; +const { + createPrivateKey, createPublicKey, generateKeyPair, +} = await import('node:crypto'); + +const generated = await promisify(generateKeyPair)('ed25519'); + +// PEM +const privatePem = generated.privateKey.export({ format: 'pem', type: 'pkcs8' }); +const publicPem = generated.publicKey.export({ format: 'pem', type: 'spki' }); +createPrivateKey(privatePem); +createPublicKey(publicPem); + +// DER - requires explicit type +const privateDer = generated.privateKey.export({ format: 'der', type: 'pkcs8' }); +const publicDer = generated.publicKey.export({ format: 'der', type: 'spki' }); +createPrivateKey({ key: privateDer, format: 'der', type: 'pkcs8' }); +createPublicKey({ key: publicDer, format: 'der', type: 'spki' }); + +// JWK +const privateJwk = generated.privateKey.export({ format: 'jwk' }); +const publicJwk = generated.publicKey.export({ format: 'jwk' }); +createPrivateKey({ key: privateJwk, format: 'jwk' }); +createPublicKey({ key: publicJwk, format: 'jwk' }); + +// Raw +const rawPriv = generated.privateKey.export({ format: 'raw-private' }); +const rawPub = generated.publicKey.export({ format: 'raw-public' }); +createPrivateKey({ key: rawPriv, format: 'raw-private', asymmetricKeyType: 'ed25519' }); +createPublicKey({ key: rawPub, format: 'raw-public', asymmetricKeyType: 'ed25519' }); +``` + +Example: Passing key material directly to [`crypto.sign()`][] and +[`crypto.verify()`][] without creating a [`KeyObject`][] first: + +```mjs +import { promisify } from 'node:util'; +const { generateKeyPair, sign, verify } = await import('node:crypto'); + +const generated = await promisify(generateKeyPair)('ed25519'); + +const data = new TextEncoder().encode('message to sign'); + +// PEM strings +const privatePem = generated.privateKey.export({ format: 'pem', type: 'pkcs8' }); +const publicPem = generated.publicKey.export({ format: 'pem', type: 'spki' }); +const sig1 = sign(null, data, privatePem); +verify(null, data, publicPem, sig1); + +// JWK objects +const privateJwk = generated.privateKey.export({ format: 'jwk' }); +const publicJwk = generated.publicKey.export({ format: 'jwk' }); +const sig2 = sign(null, data, { key: privateJwk, format: 'jwk' }); +verify(null, data, { key: publicJwk, format: 'jwk' }, sig2); + +// Raw key bytes +const rawPriv = generated.privateKey.export({ format: 'raw-private' }); +const rawPub = generated.publicKey.export({ format: 'raw-public' }); +const sig3 = sign(null, data, { + key: rawPriv, format: 'raw-private', asymmetricKeyType: 'ed25519', +}); +verify(null, data, { + key: rawPub, format: 'raw-public', asymmetricKeyType: 'ed25519', +}, sig3); +``` + +Example: For EC keys, the `namedCurve` option is required when importing +raw keys: + +```mjs +import { promisify } from 'node:util'; +const { + createPrivateKey, createPublicKey, generateKeyPair, sign, verify, +} = await import('node:crypto'); + +const generated = await promisify(generateKeyPair)('ec', { + namedCurve: 'P-256', +}); + +// Export the raw EC public key (uncompressed by default). +const rawPublicKey = generated.publicKey.export({ format: 'raw-public' }); + +// The following is equivalent. +const rawPublicKeyUncompressed = generated.publicKey.export({ + format: 'raw-public', + type: 'uncompressed', +}); + +// Export compressed point format. +const rawPublicKeyCompressed = generated.publicKey.export({ + format: 'raw-public', + type: 'compressed', +}); + +// Export the raw EC private key. +const rawPrivateKey = generated.privateKey.export({ format: 'raw-private' }); + +// Import the raw EC keys. +// Both compressed and uncompressed point formats are accepted. +const publicKey = createPublicKey({ + key: rawPublicKey, + format: 'raw-public', + asymmetricKeyType: 'ec', + namedCurve: 'P-256', +}); +const privateKey = createPrivateKey({ + key: rawPrivateKey, + format: 'raw-private', + asymmetricKeyType: 'ec', + namedCurve: 'P-256', +}); + +const data = new TextEncoder().encode('message to sign'); +const signature = sign('sha256', data, privateKey); +verify('sha256', data, publicKey, signature); +``` + +Example: Exporting raw seeds and importing them: + +```mjs +import { promisify } from 'node:util'; +const { + createPrivateKey, decapsulate, encapsulate, generateKeyPair, +} = await import('node:crypto'); + +const generated = await promisify(generateKeyPair)('ml-kem-768'); + +// Export the raw seed (64 bytes for ML-KEM). +const seed = generated.privateKey.export({ format: 'raw-seed' }); + +// Import the raw seed. +const privateKey = createPrivateKey({ + key: seed, + format: 'raw-seed', + asymmetricKeyType: 'ml-kem-768', +}); + +const { ciphertext } = encapsulate(generated.publicKey); +decapsulate(privateKey, ciphertext); +``` ## Class: `Certificate` @@ -2121,6 +2389,10 @@ type, value, and parameters. This method is not @@ -3673,6 +3957,9 @@ of the passphrase is limited to 1024 bytes. @@ -6539,6 +6831,7 @@ See the [list of SSL OP Flags][] for details. [RFC 4122]: https://www.rfc-editor.org/rfc/rfc4122.txt [RFC 5208]: https://www.rfc-editor.org/rfc/rfc5208.txt [RFC 5280]: https://www.rfc-editor.org/rfc/rfc5280.txt +[RFC 7517]: https://www.rfc-editor.org/rfc/rfc7517.txt [Web Crypto API documentation]: webcrypto.md [`BN_is_prime_ex`]: https://www.openssl.org/docs/man1.1.1/man3/BN_is_prime_ex.html [`Buffer`]: buffer.md @@ -6563,6 +6856,7 @@ See the [list of SSL OP Flags][] for details. [`crypto.createSign()`]: #cryptocreatesignalgorithm-options [`crypto.createVerify()`]: #cryptocreateverifyalgorithm-options [`crypto.generateKey()`]: #cryptogeneratekeytype-options-callback +[`crypto.generateKeyPair()`]: #cryptogeneratekeypairtype-options-callback [`crypto.getCurves()`]: #cryptogetcurves [`crypto.getDiffieHellman()`]: #cryptogetdiffiehellmangroupname [`crypto.getHashes()`]: #cryptogethashes @@ -6572,6 +6866,8 @@ See the [list of SSL OP Flags][] for details. [`crypto.publicEncrypt()`]: #cryptopublicencryptkey-buffer [`crypto.randomBytes()`]: #cryptorandombytessize-callback [`crypto.randomFill()`]: #cryptorandomfillbuffer-offset-size-callback +[`crypto.sign()`]: #cryptosignalgorithm-data-key-callback +[`crypto.verify()`]: #cryptoverifyalgorithm-data-key-signature-callback [`crypto.webcrypto.getRandomValues()`]: webcrypto.md#cryptogetrandomvaluestypedarray [`crypto.webcrypto.subtle`]: webcrypto.md#class-subtlecrypto [`decipher.final()`]: #decipherfinaloutputencoding diff --git a/doc/api/webcrypto.md b/doc/api/webcrypto.md index 6a1a8a43375645..82eda8b796b2b1 100644 --- a/doc/api/webcrypto.md +++ b/doc/api/webcrypto.md @@ -2,6 +2,10 @@ -* `algorithm` {string|Algorithm|CShakeParams} +* `algorithm` {string|Algorithm|CShakeParams|TurboShakeParams|KangarooTwelveParams} * `data` {ArrayBuffer|TypedArray|DataView|Buffer} * Returns: {Promise} Fulfills with an {ArrayBuffer} upon success. @@ -1019,6 +1035,8 @@ If `algorithm` is provided as a {string}, it must be one of: * `'cSHAKE128'`[^modern-algos] * `'cSHAKE256'`[^modern-algos] +* `'KT128'`[^modern-algos] +* `'KT256'`[^modern-algos] * `'SHA-1'` * `'SHA-256'` * `'SHA-384'` @@ -1026,6 +1044,8 @@ If `algorithm` is provided as a {string}, it must be one of: * `'SHA3-256'`[^modern-algos] * `'SHA3-384'`[^modern-algos] * `'SHA3-512'`[^modern-algos] +* `'TurboSHAKE128'`[^modern-algos] +* `'TurboSHAKE256'`[^modern-algos] If `algorithm` is provided as an {Object}, it must have a `name` property whose value is one of the above. @@ -2316,6 +2336,38 @@ added: v15.0.0 * Type: {string} +### Class: `KangarooTwelveParams` + + + +#### `kangarooTwelveParams.customization` + + + +* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined} + +The optional customization string for KangarooTwelve. + +#### `kangarooTwelveParams.name` + + + +* Type: {string} Must be `'KT128'`[^modern-algos] or `'KT256'`[^modern-algos] + +#### `kangarooTwelveParams.outputLength` + + + +* Type: {number} represents the requested output length in bits. + ### Class: `KmacImportParams` + +#### `turboShakeParams.domainSeparation` + + + +* Type: {number|undefined} + +The optional domain separation byte (0x01-0x7f). Defaults to `0x1f`. + +#### `turboShakeParams.name` + + + +* Type: {string} Must be `'TurboSHAKE128'`[^modern-algos] or `'TurboSHAKE256'`[^modern-algos] + +#### `turboShakeParams.outputLength` + + + +* Type: {number} represents the requested output length in bits. + [^secure-curves]: See [Secure Curves in the Web Cryptography API][] [^modern-algos]: See [Modern Algorithms in the Web Cryptography API][] diff --git a/doc/contributing/security-release-process.md b/doc/contributing/security-release-process.md index 1107edc5a68976..ba7dbae378f730 100644 --- a/doc/contributing/security-release-process.md +++ b/doc/contributing/security-release-process.md @@ -39,6 +39,7 @@ The current security stewards are documented in the main Node.js | NodeSource | Rafael | 2025-May-14 | | NodeSource | Rafael | 2025-Jul-15 | | HeroDevs and NodeSource | Marco / Rafael | 2026-Jan-13 | +| NodeSource | Rafael | 2026-Mar-24 | | Datadog | Bryan | | | IBM | Joe | | | Platformatic | Matteo | | diff --git a/lib/internal/crypto/cfrg.js b/lib/internal/crypto/cfrg.js index fd3f168435ddcb..b943ba020750f9 100644 --- a/lib/internal/crypto/cfrg.js +++ b/lib/internal/crypto/cfrg.js @@ -3,19 +3,23 @@ const { SafeSet, StringPrototypeToLowerCase, + TypedArrayPrototypeGetBuffer, } = primordials; const { Buffer } = require('buffer'); const { - ECKeyExportJob, KeyObjectHandle, SignJob, kCryptoJobAsync, + kKeyFormatDER, kKeyTypePrivate, kKeyTypePublic, kSignJobModeSign, kSignJobModeVerify, + kWebCryptoKeyFormatPKCS8, + kWebCryptoKeyFormatRaw, + kWebCryptoKeyFormatSPKI, } = internalBinding('crypto'); const { @@ -196,10 +200,29 @@ async function cfrgGenerateKey(algorithm, extractable, keyUsages) { } function cfrgExportKey(key, format) { - return jobPromise(() => new ECKeyExportJob( - kCryptoJobAsync, - format, - key[kKeyObject][kHandle])); + try { + switch (format) { + case kWebCryptoKeyFormatRaw: { + const handle = key[kKeyObject][kHandle]; + return TypedArrayPrototypeGetBuffer( + key[kKeyType] === 'private' ? handle.rawPrivateKey() : handle.rawPublicKey()); + } + case kWebCryptoKeyFormatSPKI: { + return TypedArrayPrototypeGetBuffer( + key[kKeyObject][kHandle].export(kKeyFormatDER, kWebCryptoKeyFormatSPKI)); + } + case kWebCryptoKeyFormatPKCS8: { + return TypedArrayPrototypeGetBuffer( + key[kKeyObject][kHandle].export(kKeyFormatDER, kWebCryptoKeyFormatPKCS8, null, null)); + } + default: + return undefined; + } + } catch (err) { + throw lazyDOMException( + 'The operation failed for an operation-specific reason', + { name: 'OperationError', cause: err }); + } } function cfrgImportKey( diff --git a/lib/internal/crypto/ec.js b/lib/internal/crypto/ec.js index dd7997c82cbf91..a73b1c6c0bc4bf 100644 --- a/lib/internal/crypto/ec.js +++ b/lib/internal/crypto/ec.js @@ -2,19 +2,30 @@ const { SafeSet, + TypedArrayPrototypeGetBuffer, + TypedArrayPrototypeGetByteLength, } = primordials; const { - ECKeyExportJob, KeyObjectHandle, SignJob, kCryptoJobAsync, + kKeyFormatDER, kKeyTypePrivate, kSignJobModeSign, kSignJobModeVerify, kSigEncP1363, + kWebCryptoKeyFormatPKCS8, + kWebCryptoKeyFormatRaw, + kWebCryptoKeyFormatSPKI, } = internalBinding('crypto'); +const { + crypto: { + POINT_CONVERSION_UNCOMPRESSED, + }, +} = internalBinding('constants'); + const { getUsagesUnion, hasAnyNotIn, @@ -41,6 +52,7 @@ const { PublicKeyObject, createPrivateKey, createPublicKey, + kAlgorithm, kKeyType, } = require('internal/crypto/keys'); @@ -139,10 +151,45 @@ async function ecGenerateKey(algorithm, extractable, keyUsages) { } function ecExportKey(key, format) { - return jobPromise(() => new ECKeyExportJob( - kCryptoJobAsync, - format, - key[kKeyObject][kHandle])); + try { + const handle = key[kKeyObject][kHandle]; + switch (format) { + case kWebCryptoKeyFormatRaw: { + return TypedArrayPrototypeGetBuffer( + handle.exportECPublicRaw(POINT_CONVERSION_UNCOMPRESSED)); + } + case kWebCryptoKeyFormatSPKI: { + let spki = handle.export(kKeyFormatDER, kWebCryptoKeyFormatSPKI); + // WebCrypto requires uncompressed point format for SPKI exports. + // This is a very rare edge case dependent on the imported key + // using compressed point format. + // Expected SPKI DER byte lengths with uncompressed points: + // P-256: 91 = 26 bytes of SPKI ASN.1 + 65-byte uncompressed point. + // P-384: 120 = 23 bytes of SPKI ASN.1 + 97-byte uncompressed point. + // P-521: 158 = 25 bytes of SPKI ASN.1 + 133-byte uncompressed point. + // Difference in initial SPKI ASN.1 is caused by OIDs and length encoding. + if (TypedArrayPrototypeGetByteLength(spki) !== { + '__proto__': null, 'P-256': 91, 'P-384': 120, 'P-521': 158, + }[key[kAlgorithm].namedCurve]) { + const raw = handle.exportECPublicRaw(POINT_CONVERSION_UNCOMPRESSED); + const tmp = new KeyObjectHandle(); + tmp.initECRaw(kNamedCurveAliases[key[kAlgorithm].namedCurve], raw); + spki = tmp.export(kKeyFormatDER, kWebCryptoKeyFormatSPKI); + } + return TypedArrayPrototypeGetBuffer(spki); + } + case kWebCryptoKeyFormatPKCS8: { + return TypedArrayPrototypeGetBuffer( + handle.export(kKeyFormatDER, kWebCryptoKeyFormatPKCS8, null, null)); + } + default: + return undefined; + } + } catch (err) { + throw lazyDOMException( + 'The operation failed for an operation-specific reason', + { name: 'OperationError', cause: err }); + } } function ecImportKey( diff --git a/lib/internal/crypto/hash.js b/lib/internal/crypto/hash.js index e549c72fccc4bc..857753c2b39f9c 100644 --- a/lib/internal/crypto/hash.js +++ b/lib/internal/crypto/hash.js @@ -14,6 +14,8 @@ const { Hmac: _Hmac, kCryptoJobAsync, oneShotDigest, + TurboShakeJob, + KangarooTwelveJob, } = internalBinding('crypto'); const { @@ -224,6 +226,24 @@ async function asyncDigest(algorithm, data) { normalizeHashName(algorithm.name), data, algorithm.outputLength)); + case 'TurboSHAKE128': + // Fall through + case 'TurboSHAKE256': + return await jobPromise(() => new TurboShakeJob( + kCryptoJobAsync, + algorithm.name, + algorithm.domainSeparation ?? 0x1f, + algorithm.outputLength / 8, + data)); + case 'KT128': + // Fall through + case 'KT256': + return await jobPromise(() => new KangarooTwelveJob( + kCryptoJobAsync, + algorithm.name, + algorithm.customization, + algorithm.outputLength / 8, + data)); } throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); diff --git a/lib/internal/crypto/keys.js b/lib/internal/crypto/keys.js index a3609690adeb36..80c073a1dbfac1 100644 --- a/lib/internal/crypto/keys.js +++ b/lib/internal/crypto/keys.js @@ -27,6 +27,13 @@ const { kKeyEncodingSEC1, } = internalBinding('crypto'); +const { + crypto: { + POINT_CONVERSION_COMPRESSED, + POINT_CONVERSION_UNCOMPRESSED, + }, +} = internalBinding('constants'); + const { validateObject, validateOneOf, @@ -82,6 +89,7 @@ const kKeyUsages = Symbol('kKeyUsages'); const kCachedAlgorithm = Symbol('kCachedAlgorithm'); const kCachedKeyUsages = Symbol('kCachedKeyUsages'); + // Key input contexts. const kConsumePublic = 0; const kConsumePrivate = 1; @@ -340,14 +348,27 @@ const { } export(options) { - if (options && options.format === 'jwk') { - return this[kHandle].exportJwk({}, false); + switch (options?.format) { + case 'jwk': + return this[kHandle].exportJwk({}, false); + case 'raw-public': { + if (this.asymmetricKeyType === 'ec') { + const { type = 'uncompressed' } = options; + validateOneOf(type, 'options.type', ['compressed', 'uncompressed']); + const form = type === 'compressed' ? + POINT_CONVERSION_COMPRESSED : POINT_CONVERSION_UNCOMPRESSED; + return this[kHandle].exportECPublicRaw(form); + } + return this[kHandle].rawPublicKey(); + } + default: { + const { + format, + type, + } = parsePublicKeyEncoding(options, this.asymmetricKeyType); + return this[kHandle].export(format, type); + } } - const { - format, - type, - } = parsePublicKeyEncoding(options, this.asymmetricKeyType); - return this[kHandle].export(format, type); } } @@ -357,20 +378,32 @@ const { } export(options) { - if (options && options.format === 'jwk') { - if (options.passphrase !== undefined) { - throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS( - 'jwk', 'does not support encryption'); + if (options?.passphrase !== undefined && + options.format !== 'pem' && options.format !== 'der') { + throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS( + options.format, 'does not support encryption'); + } + switch (options?.format) { + case 'jwk': + return this[kHandle].exportJwk({}, false); + case 'raw-private': { + if (this.asymmetricKeyType === 'ec') { + return this[kHandle].exportECPrivateRaw(); + } + return this[kHandle].rawPrivateKey(); + } + case 'raw-seed': + return this[kHandle].rawSeed(); + default: { + const { + format, + type, + cipher, + passphrase, + } = parsePrivateKeyEncoding(options, this.asymmetricKeyType); + return this[kHandle].export(format, type, cipher, passphrase); } - return this[kHandle].exportJwk({}, false); } - const { - format, - type, - cipher, - passphrase, - } = parsePrivateKeyEncoding(options, this.asymmetricKeyType); - return this[kHandle].export(format, type, cipher, passphrase); } } @@ -549,13 +582,8 @@ function mlDsaPubLen(alg) { function getKeyObjectHandleFromJwk(key, ctx) { validateObject(key, 'key'); - if (KeyObjectHandle.prototype.initPqcRaw) { - validateOneOf( - key.kty, 'key.kty', ['RSA', 'EC', 'OKP', 'AKP']); - } else { - validateOneOf( - key.kty, 'key.kty', ['RSA', 'EC', 'OKP']); - } + validateOneOf( + key.kty, 'key.kty', ['RSA', 'EC', 'OKP', 'AKP']); const isPublic = ctx === kConsumePublic || ctx === kCreatePublic; if (key.kty === 'AKP') { @@ -691,6 +719,79 @@ function getKeyObjectHandleFromJwk(key, ctx) { return handle; } + +function getKeyObjectHandleFromRaw(options, data, format) { + if (!isStringOrBuffer(data)) { + throw new ERR_INVALID_ARG_TYPE( + 'key.key', + ['ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'], + data); + } + + const keyData = getArrayBufferOrView(data, 'key.key'); + + validateString(options.asymmetricKeyType, 'key.asymmetricKeyType'); + const asymmetricKeyType = options.asymmetricKeyType; + + const handle = new KeyObjectHandle(); + + switch (asymmetricKeyType) { + case 'ec': { + validateString(options.namedCurve, 'key.namedCurve'); + if (format === 'raw-public') { + if (!handle.initECRaw(options.namedCurve, keyData)) { + throw new ERR_INVALID_ARG_VALUE('key.key', keyData); + } + } else if (!handle.initECPrivateRaw(options.namedCurve, keyData)) { + throw new ERR_INVALID_ARG_VALUE('key.key', keyData); + } + return handle; + } + case 'ed25519': + case 'ed448': + case 'x25519': + case 'x448': { + const keyType = format === 'raw-public' ? kKeyTypePublic : kKeyTypePrivate; + if (!handle.initEDRaw(asymmetricKeyType, keyData, keyType)) { + throw new ERR_INVALID_ARG_VALUE('key.key', keyData); + } + return handle; + } + case 'rsa': + case 'rsa-pss': + case 'dsa': + case 'dh': + throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS( + format, `is not supported for ${asymmetricKeyType} keys`); + case 'ml-dsa-44': + case 'ml-dsa-65': + case 'ml-dsa-87': + case 'ml-kem-512': + case 'ml-kem-768': + case 'ml-kem-1024': + case 'slh-dsa-sha2-128f': + case 'slh-dsa-sha2-128s': + case 'slh-dsa-sha2-192f': + case 'slh-dsa-sha2-192s': + case 'slh-dsa-sha2-256f': + case 'slh-dsa-sha2-256s': + case 'slh-dsa-shake-128f': + case 'slh-dsa-shake-128s': + case 'slh-dsa-shake-192f': + case 'slh-dsa-shake-192s': + case 'slh-dsa-shake-256f': + case 'slh-dsa-shake-256s': { + const keyType = format === 'raw-public' ? kKeyTypePublic : kKeyTypePrivate; + if (!handle.initPqcRaw(asymmetricKeyType, keyData, keyType)) { + throw new ERR_INVALID_ARG_VALUE('key.key', keyData); + } + return handle; + } + default: + throw new ERR_INVALID_ARG_VALUE('asymmetricKeyType', asymmetricKeyType); + } +} + function prepareAsymmetricKey(key, ctx) { if (isKeyObject(key)) { // Best case: A key object, as simple as that. @@ -712,6 +813,12 @@ function prepareAsymmetricKey(key, ctx) { else if (format === 'jwk') { validateObject(data, 'key.key'); return { data: getKeyObjectHandleFromJwk(data, ctx), format: 'jwk' }; + } else if (format === 'raw-public' || format === 'raw-private' || + format === 'raw-seed') { + return { + data: getKeyObjectHandleFromRaw(key, data, format), + format, + }; } // Either PEM or DER using PKCS#1 or SPKI. @@ -777,7 +884,7 @@ function createPublicKey(key) { const { format, type, data, passphrase } = prepareAsymmetricKey(key, kCreatePublic); let handle; - if (format === 'jwk') { + if (format === 'jwk' || format === 'raw-public') { handle = data; } else { handle = new KeyObjectHandle(); @@ -790,7 +897,7 @@ function createPrivateKey(key) { const { format, type, data, passphrase } = prepareAsymmetricKey(key, kCreatePrivate); let handle; - if (format === 'jwk') { + if (format === 'jwk' || format === 'raw-private' || format === 'raw-seed') { handle = data; } else { handle = new KeyObjectHandle(); diff --git a/lib/internal/crypto/ml_dsa.js b/lib/internal/crypto/ml_dsa.js index 11a7e8d843a0a7..4d90a80f0200f7 100644 --- a/lib/internal/crypto/ml_dsa.js +++ b/lib/internal/crypto/ml_dsa.js @@ -4,8 +4,7 @@ const { SafeSet, StringPrototypeToLowerCase, TypedArrayPrototypeGetBuffer, - TypedArrayPrototypeSet, - Uint8Array, + TypedArrayPrototypeGetByteLength, } = primordials; const { Buffer } = require('buffer'); @@ -54,7 +53,6 @@ const { PublicKeyObject, createPrivateKey, createPublicKey, - kAlgorithm, kKeyType, } = require('internal/crypto/keys'); @@ -124,31 +122,24 @@ function mlDsaExportKey(key, format) { try { switch (format) { case kWebCryptoKeyFormatRaw: { - if (key[kKeyType] === 'private') { - return TypedArrayPrototypeGetBuffer(key[kKeyObject][kHandle].rawSeed()); - } - - return TypedArrayPrototypeGetBuffer(key[kKeyObject][kHandle].rawPublicKey()); + const handle = key[kKeyObject][kHandle]; + return TypedArrayPrototypeGetBuffer( + key[kKeyType] === 'private' ? handle.rawSeed() : handle.rawPublicKey()); } case kWebCryptoKeyFormatSPKI: { return TypedArrayPrototypeGetBuffer(key[kKeyObject][kHandle].export(kKeyFormatDER, kWebCryptoKeyFormatSPKI)); } case kWebCryptoKeyFormatPKCS8: { - const seed = key[kKeyObject][kHandle].rawSeed(); - const buffer = new Uint8Array(54); - const orc = { - '__proto__': null, - 'ML-DSA-44': 0x11, - 'ML-DSA-65': 0x12, - 'ML-DSA-87': 0x13, - }[key[kAlgorithm].name]; - TypedArrayPrototypeSet(buffer, [ - 0x30, 0x34, 0x02, 0x01, 0x00, 0x30, 0x0b, 0x06, - 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, - 0x03, orc, 0x04, 0x22, 0x80, 0x20, - ], 0); - TypedArrayPrototypeSet(buffer, seed, 22); - return TypedArrayPrototypeGetBuffer(buffer); + const pkcs8 = key[kKeyObject][kHandle].export(kKeyFormatDER, kWebCryptoKeyFormatPKCS8, null, null); + // Edge case only possible when user creates a seedless KeyObject + // first and converts it with KeyObject.prototype.toCryptoKey. + // 54 = 22 bytes of PKCS#8 ASN.1 + 32-byte seed. + if (TypedArrayPrototypeGetByteLength(pkcs8) !== 54) { + throw lazyDOMException( + 'The operation failed for an operation-specific reason', + { name: 'OperationError' }); + } + return TypedArrayPrototypeGetBuffer(pkcs8); } default: return undefined; diff --git a/lib/internal/crypto/ml_kem.js b/lib/internal/crypto/ml_kem.js index 9cf44efc37f20a..0a82ca8453b8fe 100644 --- a/lib/internal/crypto/ml_kem.js +++ b/lib/internal/crypto/ml_kem.js @@ -5,8 +5,7 @@ const { SafeSet, StringPrototypeToLowerCase, TypedArrayPrototypeGetBuffer, - TypedArrayPrototypeSet, - Uint8Array, + TypedArrayPrototypeGetByteLength, } = primordials; const { @@ -44,7 +43,6 @@ const { PublicKeyObject, createPrivateKey, createPublicKey, - kAlgorithm, kKeyType, } = require('internal/crypto/keys'); @@ -95,31 +93,24 @@ function mlKemExportKey(key, format) { try { switch (format) { case kWebCryptoKeyFormatRaw: { - if (key[kKeyType] === 'private') { - return TypedArrayPrototypeGetBuffer(key[kKeyObject][kHandle].rawSeed()); - } - - return TypedArrayPrototypeGetBuffer(key[kKeyObject][kHandle].rawPublicKey()); + const handle = key[kKeyObject][kHandle]; + return TypedArrayPrototypeGetBuffer( + key[kKeyType] === 'private' ? handle.rawSeed() : handle.rawPublicKey()); } case kWebCryptoKeyFormatSPKI: { return TypedArrayPrototypeGetBuffer(key[kKeyObject][kHandle].export(kKeyFormatDER, kWebCryptoKeyFormatSPKI)); } case kWebCryptoKeyFormatPKCS8: { - const seed = key[kKeyObject][kHandle].rawSeed(); - const buffer = new Uint8Array(86); - const orc = { - '__proto__': null, - 'ML-KEM-512': 0x01, - 'ML-KEM-768': 0x02, - 'ML-KEM-1024': 0x03, - }[key[kAlgorithm].name]; - TypedArrayPrototypeSet(buffer, [ - 0x30, 0x54, 0x02, 0x01, 0x00, 0x30, 0x0b, 0x06, - 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, - 0x04, orc, 0x04, 0x42, 0x80, 0x40, - ], 0); - TypedArrayPrototypeSet(buffer, seed, 22); - return TypedArrayPrototypeGetBuffer(buffer); + const pkcs8 = key[kKeyObject][kHandle].export(kKeyFormatDER, kWebCryptoKeyFormatPKCS8, null, null); + // Edge case only possible when user creates a seedless KeyObject + // first and converts it with KeyObject.prototype.toCryptoKey. + // 86 = 22 bytes of PKCS#8 ASN.1 + 64-byte seed. + if (TypedArrayPrototypeGetByteLength(pkcs8) !== 86) { + throw lazyDOMException( + 'The operation failed for an operation-specific reason', + { name: 'OperationError' }); + } + return TypedArrayPrototypeGetBuffer(pkcs8); } default: return undefined; diff --git a/lib/internal/crypto/rsa.js b/lib/internal/crypto/rsa.js index c6b3985dbaee66..52c1257f48b0c6 100644 --- a/lib/internal/crypto/rsa.js +++ b/lib/internal/crypto/rsa.js @@ -3,22 +3,23 @@ const { MathCeil, SafeSet, + TypedArrayPrototypeGetBuffer, Uint8Array, } = primordials; const { KeyObjectHandle, RSACipherJob, - RSAKeyExportJob, SignJob, kCryptoJobAsync, + kKeyFormatDER, kSignJobModeSign, kSignJobModeVerify, - kKeyVariantRSA_SSA_PKCS1_v1_5, - kKeyVariantRSA_PSS, kKeyVariantRSA_OAEP, kKeyTypePrivate, kWebCryptoCipherEncrypt, + kWebCryptoKeyFormatPKCS8, + kWebCryptoKeyFormatSPKI, RSA_PKCS1_PSS_PADDING, } = internalBinding('crypto'); @@ -58,11 +59,6 @@ const { generateKeyPair: _generateKeyPair, } = require('internal/crypto/keygen'); -const kRsaVariants = { - 'RSASSA-PKCS1-v1_5': kKeyVariantRSA_SSA_PKCS1_v1_5, - 'RSA-PSS': kKeyVariantRSA_PSS, - 'RSA-OAEP': kKeyVariantRSA_OAEP, -}; const generateKeyPair = promisify(_generateKeyPair); function verifyAcceptableRsaKeyUse(name, isPublic, usages) { @@ -202,11 +198,24 @@ async function rsaKeyGenerate( } function rsaExportKey(key, format) { - return jobPromise(() => new RSAKeyExportJob( - kCryptoJobAsync, - format, - key[kKeyObject][kHandle], - kRsaVariants[key[kAlgorithm].name])); + try { + switch (format) { + case kWebCryptoKeyFormatSPKI: { + return TypedArrayPrototypeGetBuffer( + key[kKeyObject][kHandle].export(kKeyFormatDER, kWebCryptoKeyFormatSPKI)); + } + case kWebCryptoKeyFormatPKCS8: { + return TypedArrayPrototypeGetBuffer( + key[kKeyObject][kHandle].export(kKeyFormatDER, kWebCryptoKeyFormatPKCS8, null, null)); + } + default: + return undefined; + } + } catch (err) { + throw lazyDOMException( + 'The operation failed for an operation-specific reason', + { name: 'OperationError', cause: err }); + } } function rsaImportKey( diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js index ab10c53a655a60..70e1027946190a 100644 --- a/lib/internal/crypto/util.js +++ b/lib/internal/crypto/util.js @@ -244,6 +244,10 @@ const kAlgorithmDefinitions = { }, 'cSHAKE128': { 'digest': 'CShakeParams' }, 'cSHAKE256': { 'digest': 'CShakeParams' }, + 'KT128': { 'digest': 'KangarooTwelveParams' }, + 'KT256': { 'digest': 'KangarooTwelveParams' }, + 'TurboSHAKE128': { 'digest': 'TurboShakeParams' }, + 'TurboSHAKE256': { 'digest': 'TurboShakeParams' }, 'ECDH': { 'generateKey': 'EcKeyGenParams', 'exportKey': null, @@ -441,6 +445,10 @@ const experimentalAlgorithms = [ 'SHA3-256', 'SHA3-384', 'SHA3-512', + 'TurboSHAKE128', + 'TurboSHAKE256', + 'KT128', + 'KT256', 'X448', ]; @@ -513,6 +521,10 @@ const simpleAlgorithmDictionaries = { KmacParams: { customization: 'BufferSource', }, + KangarooTwelveParams: { + customization: 'BufferSource', + }, + TurboShakeParams: {}, }; function validateMaxBufferLength(data, name) { diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js index dd36053f28b5a0..dc7f635a480a49 100644 --- a/lib/internal/crypto/webcrypto.js +++ b/lib/internal/crypto/webcrypto.js @@ -392,12 +392,12 @@ async function exportKeySpki(key) { case 'RSA-PSS': // Fall through case 'RSA-OAEP': - return await require('internal/crypto/rsa') + return require('internal/crypto/rsa') .rsaExportKey(key, kWebCryptoKeyFormatSPKI); case 'ECDSA': // Fall through case 'ECDH': - return await require('internal/crypto/ec') + return require('internal/crypto/ec') .ecExportKey(key, kWebCryptoKeyFormatSPKI); case 'Ed25519': // Fall through @@ -406,14 +406,13 @@ async function exportKeySpki(key) { case 'X25519': // Fall through case 'X448': - return await require('internal/crypto/cfrg') + return require('internal/crypto/cfrg') .cfrgExportKey(key, kWebCryptoKeyFormatSPKI); case 'ML-DSA-44': // Fall through case 'ML-DSA-65': // Fall through case 'ML-DSA-87': - // Note: mlDsaExportKey does not return a Promise. return require('internal/crypto/ml_dsa') .mlDsaExportKey(key, kWebCryptoKeyFormatSPKI); case 'ML-KEM-512': @@ -421,7 +420,6 @@ async function exportKeySpki(key) { case 'ML-KEM-768': // Fall through case 'ML-KEM-1024': - // Note: mlKemExportKey does not return a Promise. return require('internal/crypto/ml_kem') .mlKemExportKey(key, kWebCryptoKeyFormatSPKI); default: @@ -436,12 +434,12 @@ async function exportKeyPkcs8(key) { case 'RSA-PSS': // Fall through case 'RSA-OAEP': - return await require('internal/crypto/rsa') + return require('internal/crypto/rsa') .rsaExportKey(key, kWebCryptoKeyFormatPKCS8); case 'ECDSA': // Fall through case 'ECDH': - return await require('internal/crypto/ec') + return require('internal/crypto/ec') .ecExportKey(key, kWebCryptoKeyFormatPKCS8); case 'Ed25519': // Fall through @@ -450,14 +448,13 @@ async function exportKeyPkcs8(key) { case 'X25519': // Fall through case 'X448': - return await require('internal/crypto/cfrg') + return require('internal/crypto/cfrg') .cfrgExportKey(key, kWebCryptoKeyFormatPKCS8); case 'ML-DSA-44': // Fall through case 'ML-DSA-65': // Fall through case 'ML-DSA-87': - // Note: mlDsaExportKey does not return a Promise. return require('internal/crypto/ml_dsa') .mlDsaExportKey(key, kWebCryptoKeyFormatPKCS8); case 'ML-KEM-512': @@ -465,7 +462,6 @@ async function exportKeyPkcs8(key) { case 'ML-KEM-768': // Fall through case 'ML-KEM-1024': - // Note: mlKemExportKey does not return a Promise. return require('internal/crypto/ml_kem') .mlKemExportKey(key, kWebCryptoKeyFormatPKCS8); default: @@ -478,7 +474,7 @@ async function exportKeyRawPublic(key, format) { case 'ECDSA': // Fall through case 'ECDH': - return await require('internal/crypto/ec') + return require('internal/crypto/ec') .ecExportKey(key, kWebCryptoKeyFormatRaw); case 'Ed25519': // Fall through @@ -487,7 +483,7 @@ async function exportKeyRawPublic(key, format) { case 'X25519': // Fall through case 'X448': - return await require('internal/crypto/cfrg') + return require('internal/crypto/cfrg') .cfrgExportKey(key, kWebCryptoKeyFormatRaw); case 'ML-DSA-44': // Fall through @@ -498,7 +494,6 @@ async function exportKeyRawPublic(key, format) { if (format !== 'raw-public') { return undefined; } - // Note: mlDsaExportKey does not return a Promise. return require('internal/crypto/ml_dsa') .mlDsaExportKey(key, kWebCryptoKeyFormatRaw); } @@ -511,7 +506,6 @@ async function exportKeyRawPublic(key, format) { if (format !== 'raw-public') { return undefined; } - // Note: mlKemExportKey does not return a Promise. return require('internal/crypto/ml_kem') .mlKemExportKey(key, kWebCryptoKeyFormatRaw); } @@ -527,7 +521,6 @@ async function exportKeyRawSeed(key) { case 'ML-DSA-65': // Fall through case 'ML-DSA-87': - // Note: mlDsaExportKey does not return a Promise. return require('internal/crypto/ml_dsa') .mlDsaExportKey(key, kWebCryptoKeyFormatRaw); case 'ML-KEM-512': @@ -535,7 +528,6 @@ async function exportKeyRawSeed(key) { case 'ML-KEM-768': // Fall through case 'ML-KEM-1024': - // Note: mlKemExportKey does not return a Promise. return require('internal/crypto/ml_kem') .mlKemExportKey(key, kWebCryptoKeyFormatRaw); default: diff --git a/lib/internal/crypto/webidl.js b/lib/internal/crypto/webidl.js index 2b8ac167bc3627..4f371955a73bfd 100644 --- a/lib/internal/crypto/webidl.js +++ b/lib/internal/crypto/webidl.js @@ -897,6 +897,52 @@ converters.KmacParams = createDictionaryConverter( }, ]); +converters.KangarooTwelveParams = createDictionaryConverter( + 'KangarooTwelveParams', [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'outputLength', + converter: (V, opts) => + converters['unsigned long'](V, { ...opts, enforceRange: true }), + validator: (V, opts) => { + if (V === 0 || V % 8) + throw lazyDOMException('Invalid KangarooTwelveParams outputLength', 'OperationError'); + }, + required: true, + }, + { + key: 'customization', + converter: converters.BufferSource, + }, + ]); + +converters.TurboShakeParams = createDictionaryConverter( + 'TurboShakeParams', [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'outputLength', + converter: (V, opts) => + converters['unsigned long'](V, { ...opts, enforceRange: true }), + validator: (V, opts) => { + if (V === 0 || V % 8) + throw lazyDOMException('Invalid TurboShakeParams outputLength', 'OperationError'); + }, + required: true, + }, + { + key: 'domainSeparation', + converter: (V, opts) => + converters.octet(V, { ...opts, enforceRange: true }), + validator: (V) => { + if (V < 0x01 || V > 0x7F) { + throw lazyDOMException( + 'TurboShakeParams.domainSeparation must be in range 0x01-0x7f', + 'OperationError'); + } + }, + }, + ]); + module.exports = { converters, requiredArguments, diff --git a/lib/repl.js b/lib/repl.js index 779740bfd411ad..60ea4457ab1bf6 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -1132,10 +1132,10 @@ class REPLServer extends Interface { session.once('Runtime.executionContextCreated', ({ params }) => { this[kContextId] = params.context.id; }); - context = vm.createContext(); + context = vm.createContext(vm.constants.DONT_CONTEXTIFY); session.post('Runtime.disable'); }, () => { - context = vm.createContext(); + context = vm.createContext(vm.constants.DONT_CONTEXTIFY); }); ArrayPrototypeForEach(ObjectGetOwnPropertyNames(globalThis), (name) => { // Only set properties that do not already exist as a global builtin. diff --git a/node.gyp b/node.gyp index 1366e1a0cd3505..bd77943b105173 100644 --- a/node.gyp +++ b/node.gyp @@ -390,6 +390,7 @@ 'src/crypto/crypto_kem.cc', 'src/crypto/crypto_hmac.cc', 'src/crypto/crypto_kmac.cc', + 'src/crypto/crypto_turboshake.cc', 'src/crypto/crypto_random.cc', 'src/crypto/crypto_rsa.cc', 'src/crypto/crypto_spkac.cc', @@ -408,6 +409,7 @@ 'src/crypto/crypto_dh.h', 'src/crypto/crypto_hmac.h', 'src/crypto/crypto_kmac.h', + 'src/crypto/crypto_turboshake.h', 'src/crypto/crypto_rsa.h', 'src/crypto/crypto_spkac.h', 'src/crypto/crypto_util.h', diff --git a/src/crypto/README.md b/src/crypto/README.md index 3f4400f595d356..0a2a5d6f285f60 100644 --- a/src/crypto/README.md +++ b/src/crypto/README.md @@ -180,14 +180,12 @@ The `CryptoJob` class itself is a C++ template that takes a single `CryptoJobTraits` struct as a parameter. The `CryptoJobTraits` provides the implementation detail of the job. -There are (currently) four basic `CryptoJob` specializations: +There are (currently) three basic `CryptoJob` specializations: * `CipherJob` (defined in `src/crypto_cipher.h`) -- Used for encrypt and decrypt operations. * `KeyGenJob` (defined in `src/crypto_keygen.h`) -- Used for secret and key pair generation operations. -* `KeyExportJob` (defined in `src/crypto_keys.h`) -- Used for - key export operations. * `DeriveBitsJob` (defined in `src/crypto_util.h`) -- Used for key and byte derivation operations. @@ -236,9 +234,6 @@ if successful. For `CipherJob` types, the output is always an `ArrayBuffer`. -For `KeyExportJob` types, the output is either an `ArrayBuffer` or -a JavaScript object (for JWK output format); - For `KeyGenJob` types, the output is either a single KeyObject, or an array containing a Public/Private key pair represented either as a `KeyObjectHandle` object or a `Buffer`. diff --git a/src/crypto/crypto_dh.cc b/src/crypto/crypto_dh.cc index e35fda9ad2e8c5..f2c43b796cf481 100644 --- a/src/crypto/crypto_dh.cc +++ b/src/crypto/crypto_dh.cc @@ -472,34 +472,6 @@ EVPKeyCtxPointer DhKeyGenTraits::Setup(DhKeyPairGenConfig* params) { return ctx; } -Maybe DHKeyExportTraits::AdditionalConfig( - const FunctionCallbackInfo& args, - unsigned int offset, - DHKeyExportConfig* params) { - return JustVoid(); -} - -WebCryptoKeyExportStatus DHKeyExportTraits::DoExport( - const KeyObjectData& key_data, - WebCryptoKeyFormat format, - const DHKeyExportConfig& params, - ByteSource* out) { - CHECK_NE(key_data.GetKeyType(), kKeyTypeSecret); - - switch (format) { - case kWebCryptoKeyFormatPKCS8: - if (key_data.GetKeyType() != kKeyTypePrivate) - return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; - return PKEY_PKCS8_Export(key_data, out); - case kWebCryptoKeyFormatSPKI: - if (key_data.GetKeyType() != kKeyTypePublic) - return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; - return PKEY_SPKI_Export(key_data, out); - default: - UNREACHABLE(); - } -} - Maybe DHBitsTraits::AdditionalConfig( CryptoJobMode mode, const FunctionCallbackInfo& args, @@ -600,7 +572,6 @@ void DiffieHellman::Initialize(Environment* env, Local target) { DiffieHellmanGroup); DHKeyPairGenJob::Initialize(env, target); - DHKeyExportJob::Initialize(env, target); DHBitsJob::Initialize(env, target); } @@ -621,7 +592,6 @@ void DiffieHellman::RegisterExternalReferences( registry->Register(Check); DHKeyPairGenJob::RegisterExternalReferences(registry); - DHKeyExportJob::RegisterExternalReferences(registry); DHBitsJob::RegisterExternalReferences(registry); } diff --git a/src/crypto/crypto_dh.h b/src/crypto/crypto_dh.h index 53e3a98c3918bd..72718ef757b16c 100644 --- a/src/crypto/crypto_dh.h +++ b/src/crypto/crypto_dh.h @@ -60,29 +60,6 @@ struct DhKeyGenTraits final { using DHKeyPairGenJob = KeyGenJob>; -struct DHKeyExportConfig final : public MemoryRetainer { - SET_NO_MEMORY_INFO() - SET_MEMORY_INFO_NAME(DHKeyExportConfig) - SET_SELF_SIZE(DHKeyExportConfig) -}; - -struct DHKeyExportTraits final { - static constexpr const char* JobName = "DHKeyExportJob"; - using AdditionalParameters = DHKeyExportConfig; - - static v8::Maybe AdditionalConfig( - const v8::FunctionCallbackInfo& args, - unsigned int offset, - DHKeyExportConfig* config); - - static WebCryptoKeyExportStatus DoExport(const KeyObjectData& key_data, - WebCryptoKeyFormat format, - const DHKeyExportConfig& params, - ByteSource* out); -}; - -using DHKeyExportJob = KeyExportJob; - struct DHBitsConfig final : public MemoryRetainer { KeyObjectData private_key; KeyObjectData public_key; diff --git a/src/crypto/crypto_ec.cc b/src/crypto/crypto_ec.cc index 95e1a68070ed67..dcf999fd3f3ca6 100644 --- a/src/crypto/crypto_ec.cc +++ b/src/crypto/crypto_ec.cc @@ -70,7 +70,6 @@ void ECDH::Initialize(Environment* env, Local target) { ECDHBitsJob::Initialize(env, target); ECKeyPairGenJob::Initialize(env, target); - ECKeyExportJob::Initialize(env, target); NODE_DEFINE_CONSTANT(target, OPENSSL_EC_NAMED_CURVE); NODE_DEFINE_CONSTANT(target, OPENSSL_EC_EXPLICIT_CURVE); @@ -89,7 +88,6 @@ void ECDH::RegisterExternalReferences(ExternalReferenceRegistry* registry) { ECDHBitsJob::RegisterExternalReferences(registry); ECKeyPairGenJob::RegisterExternalReferences(registry); - ECKeyExportJob::RegisterExternalReferences(registry); } void ECDH::GetCurves(const FunctionCallbackInfo& args) { @@ -559,137 +557,6 @@ Maybe EcKeyGenTraits::AdditionalConfig( return JustVoid(); } -namespace { -WebCryptoKeyExportStatus EC_Raw_Export(const KeyObjectData& key_data, - const ECKeyExportConfig& params, - ByteSource* out) { - const auto& m_pkey = key_data.GetAsymmetricKey(); - CHECK(m_pkey); - Mutex::ScopedLock lock(key_data.mutex()); - - const EC_KEY* ec_key = m_pkey; - - if (ec_key == nullptr) { - switch (key_data.GetKeyType()) { - case kKeyTypePrivate: { - auto data = m_pkey.rawPrivateKey(); - if (!data) return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; - DCHECK(!data.isSecure()); - *out = ByteSource::Allocated(data.release()); - break; - } - case kKeyTypePublic: { - auto data = m_pkey.rawPublicKey(); - if (!data) return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; - DCHECK(!data.isSecure()); - *out = ByteSource::Allocated(data.release()); - break; - } - case kKeyTypeSecret: - UNREACHABLE(); - } - } else { - if (key_data.GetKeyType() != kKeyTypePublic) - return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; - const auto group = ECKeyPointer::GetGroup(ec_key); - const auto point = ECKeyPointer::GetPublicKey(ec_key); - point_conversion_form_t form = POINT_CONVERSION_UNCOMPRESSED; - - // Get the allocated data size... - size_t len = EC_POINT_point2oct(group, point, form, nullptr, 0, nullptr); - if (len == 0) - return WebCryptoKeyExportStatus::FAILED; - auto data = DataPointer::Alloc(len); - size_t check_len = - EC_POINT_point2oct(group, - point, - form, - static_cast(data.get()), - len, - nullptr); - if (check_len == 0) - return WebCryptoKeyExportStatus::FAILED; - - CHECK_EQ(len, check_len); - *out = ByteSource::Allocated(data.release()); - } - - return WebCryptoKeyExportStatus::OK; -} -} // namespace - -Maybe ECKeyExportTraits::AdditionalConfig( - const FunctionCallbackInfo& args, - unsigned int offset, - ECKeyExportConfig* params) { - return JustVoid(); -} - -WebCryptoKeyExportStatus ECKeyExportTraits::DoExport( - const KeyObjectData& key_data, - WebCryptoKeyFormat format, - const ECKeyExportConfig& params, - ByteSource* out) { - CHECK_NE(key_data.GetKeyType(), kKeyTypeSecret); - - switch (format) { - case kWebCryptoKeyFormatRaw: - return EC_Raw_Export(key_data, params, out); - case kWebCryptoKeyFormatPKCS8: - if (key_data.GetKeyType() != kKeyTypePrivate) - return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; - return PKEY_PKCS8_Export(key_data, out); - case kWebCryptoKeyFormatSPKI: { - if (key_data.GetKeyType() != kKeyTypePublic) - return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; - - const auto& m_pkey = key_data.GetAsymmetricKey(); - if (m_pkey.id() != EVP_PKEY_EC) { - return PKEY_SPKI_Export(key_data, out); - } else { - // Ensure exported key is in uncompressed point format. - // The temporary EC key is so we can have i2d_PUBKEY_bio() write out - // the header but it is a somewhat silly hoop to jump through because - // the header is for all practical purposes a static 26 byte sequence - // where only the second byte changes. - Mutex::ScopedLock lock(key_data.mutex()); - const auto group = ECKeyPointer::GetGroup(m_pkey); - const auto point = ECKeyPointer::GetPublicKey(m_pkey); - const point_conversion_form_t form = POINT_CONVERSION_UNCOMPRESSED; - const size_t need = - EC_POINT_point2oct(group, point, form, nullptr, 0, nullptr); - if (need == 0) return WebCryptoKeyExportStatus::FAILED; - auto data = DataPointer::Alloc(need); - const size_t have = - EC_POINT_point2oct(group, - point, - form, - static_cast(data.get()), - need, - nullptr); - if (have == 0) return WebCryptoKeyExportStatus::FAILED; - auto ec = ECKeyPointer::New(group); - CHECK(ec); - auto uncompressed = ECPointPointer::New(group); - ncrypto::Buffer buffer{ - .data = static_cast(data.get()), - .len = data.size(), - }; - CHECK(uncompressed.setFromBuffer(buffer, group)); - CHECK(ec.setPublicKey(uncompressed)); - auto pkey = EVPKeyPointer::New(); - CHECK(pkey.set(ec)); - auto bio = pkey.derPublicKey(); - if (!bio) return WebCryptoKeyExportStatus::FAILED; - *out = ByteSource::FromBIO(bio); - return WebCryptoKeyExportStatus::OK; - } - } - default: - UNREACHABLE(); - } -} - bool ExportJWKEcKey(Environment* env, const KeyObjectData& key, Local target) { diff --git a/src/crypto/crypto_ec.h b/src/crypto/crypto_ec.h index 101d07e54e8e57..f7aaf2d523493a 100644 --- a/src/crypto/crypto_ec.h +++ b/src/crypto/crypto_ec.h @@ -114,32 +114,6 @@ struct EcKeyGenTraits final { using ECKeyPairGenJob = KeyGenJob>; -// There is currently no additional information that the -// ECKeyExport needs to collect, but we need to provide -// the base struct anyway. -struct ECKeyExportConfig final : public MemoryRetainer { - SET_NO_MEMORY_INFO() - SET_MEMORY_INFO_NAME(ECKeyExportConfig) - SET_SELF_SIZE(ECKeyExportConfig) -}; - -struct ECKeyExportTraits final { - static constexpr const char* JobName = "ECKeyExportJob"; - using AdditionalParameters = ECKeyExportConfig; - - static v8::Maybe AdditionalConfig( - const v8::FunctionCallbackInfo& args, - unsigned int offset, - ECKeyExportConfig* config); - - static WebCryptoKeyExportStatus DoExport(const KeyObjectData& key_data, - WebCryptoKeyFormat format, - const ECKeyExportConfig& params, - ByteSource* out); -}; - -using ECKeyExportJob = KeyExportJob; - bool ExportJWKEcKey(Environment* env, const KeyObjectData& key, v8::Local target); diff --git a/src/crypto/crypto_keys.cc b/src/crypto/crypto_keys.cc index 93a741fb1758a4..3fdbe9eacdf176 100644 --- a/src/crypto/crypto_keys.cc +++ b/src/crypto/crypto_keys.cc @@ -19,12 +19,13 @@ namespace node { +using ncrypto::BignumPointer; using ncrypto::BIOPointer; using ncrypto::ECKeyPointer; +using ncrypto::ECPointPointer; using ncrypto::EVPKeyCtxPointer; using ncrypto::EVPKeyPointer; using ncrypto::MarkPopErrorOnReturn; -using ncrypto::PKCS8Pointer; using v8::Array; using v8::Context; using v8::Function; @@ -278,33 +279,39 @@ bool ExportJWKInner(Environment* env, } int GetNidFromName(const char* name) { - int nid; - if (strcmp(name, "Ed25519") == 0) { - nid = EVP_PKEY_ED25519; - } else if (strcmp(name, "Ed448") == 0) { - nid = EVP_PKEY_ED448; - } else if (strcmp(name, "X25519") == 0) { - nid = EVP_PKEY_X25519; - } else if (strcmp(name, "X448") == 0) { - nid = EVP_PKEY_X448; + static constexpr struct { + const char* name; + int nid; + } kNameToNid[] = { + {"Ed25519", EVP_PKEY_ED25519}, + {"Ed448", EVP_PKEY_ED448}, + {"X25519", EVP_PKEY_X25519}, + {"X448", EVP_PKEY_X448}, #if OPENSSL_WITH_PQC - } else if (strcmp(name, "ML-DSA-44") == 0) { - nid = EVP_PKEY_ML_DSA_44; - } else if (strcmp(name, "ML-DSA-65") == 0) { - nid = EVP_PKEY_ML_DSA_65; - } else if (strcmp(name, "ML-DSA-87") == 0) { - nid = EVP_PKEY_ML_DSA_87; - } else if (strcmp(name, "ML-KEM-512") == 0) { - nid = EVP_PKEY_ML_KEM_512; - } else if (strcmp(name, "ML-KEM-768") == 0) { - nid = EVP_PKEY_ML_KEM_768; - } else if (strcmp(name, "ML-KEM-1024") == 0) { - nid = EVP_PKEY_ML_KEM_1024; + {"ML-DSA-44", EVP_PKEY_ML_DSA_44}, + {"ML-DSA-65", EVP_PKEY_ML_DSA_65}, + {"ML-DSA-87", EVP_PKEY_ML_DSA_87}, + {"ML-KEM-512", EVP_PKEY_ML_KEM_512}, + {"ML-KEM-768", EVP_PKEY_ML_KEM_768}, + {"ML-KEM-1024", EVP_PKEY_ML_KEM_1024}, + {"SLH-DSA-SHA2-128f", EVP_PKEY_SLH_DSA_SHA2_128F}, + {"SLH-DSA-SHA2-128s", EVP_PKEY_SLH_DSA_SHA2_128S}, + {"SLH-DSA-SHA2-192f", EVP_PKEY_SLH_DSA_SHA2_192F}, + {"SLH-DSA-SHA2-192s", EVP_PKEY_SLH_DSA_SHA2_192S}, + {"SLH-DSA-SHA2-256f", EVP_PKEY_SLH_DSA_SHA2_256F}, + {"SLH-DSA-SHA2-256s", EVP_PKEY_SLH_DSA_SHA2_256S}, + {"SLH-DSA-SHAKE-128f", EVP_PKEY_SLH_DSA_SHAKE_128F}, + {"SLH-DSA-SHAKE-128s", EVP_PKEY_SLH_DSA_SHAKE_128S}, + {"SLH-DSA-SHAKE-192f", EVP_PKEY_SLH_DSA_SHAKE_192F}, + {"SLH-DSA-SHAKE-192s", EVP_PKEY_SLH_DSA_SHAKE_192S}, + {"SLH-DSA-SHAKE-256f", EVP_PKEY_SLH_DSA_SHAKE_256F}, + {"SLH-DSA-SHAKE-256s", EVP_PKEY_SLH_DSA_SHAKE_256S}, #endif - } else { - nid = NID_undef; + }; + for (const auto& entry : kNameToNid) { + if (StringEqualNoCase(name, entry.name)) return entry.nid; } - return nid; + return NID_undef; } } // namespace @@ -633,11 +640,15 @@ Local KeyObjectHandle::Initialize(Environment* env) { SetProtoMethod(isolate, templ, "exportJwk", ExportJWK); SetProtoMethod(isolate, templ, "initECRaw", InitECRaw); SetProtoMethod(isolate, templ, "initEDRaw", InitEDRaw); -#if OPENSSL_WITH_PQC - SetProtoMethod(isolate, templ, "initPqcRaw", InitPqcRaw); SetProtoMethodNoSideEffect(isolate, templ, "rawPublicKey", RawPublicKey); + SetProtoMethodNoSideEffect(isolate, templ, "rawPrivateKey", RawPrivateKey); + SetProtoMethod(isolate, templ, "initPqcRaw", InitPqcRaw); SetProtoMethodNoSideEffect(isolate, templ, "rawSeed", RawSeed); -#endif + SetProtoMethod(isolate, templ, "initECPrivateRaw", InitECPrivateRaw); + SetProtoMethodNoSideEffect( + isolate, templ, "exportECPublicRaw", ExportECPublicRaw); + SetProtoMethodNoSideEffect( + isolate, templ, "exportECPrivateRaw", ExportECPrivateRaw); SetProtoMethod(isolate, templ, "initJwk", InitJWK); SetProtoMethod(isolate, templ, "keyDetail", GetKeyDetail); SetProtoMethod(isolate, templ, "equals", Equals); @@ -658,11 +669,13 @@ void KeyObjectHandle::RegisterExternalReferences( registry->Register(ExportJWK); registry->Register(InitECRaw); registry->Register(InitEDRaw); -#if OPENSSL_WITH_PQC - registry->Register(InitPqcRaw); registry->Register(RawPublicKey); + registry->Register(RawPrivateKey); + registry->Register(InitPqcRaw); registry->Register(RawSeed); -#endif + registry->Register(InitECPrivateRaw); + registry->Register(ExportECPublicRaw); + registry->Register(ExportECPrivateRaw); registry->Register(InitJWK); registry->Register(GetKeyDetail); registry->Register(Equals); @@ -787,7 +800,9 @@ void KeyObjectHandle::InitECRaw(const FunctionCallbackInfo& args) { MarkPopErrorOnReturn mark_pop_error_on_return; - int id = OBJ_txt2nid(*name); + int id = ncrypto::Ec::GetCurveIdFromName(*name); + if (id == NID_undef) return THROW_ERR_CRYPTO_INVALID_CURVE(env); + auto eckey = ECKeyPointer::NewByCurveName(id); if (!eckey) return args.GetReturnValue().Set(false); @@ -848,14 +863,14 @@ void KeyObjectHandle::InitEDRaw(const FunctionCallbackInfo& args) { break; } default: - UNREACHABLE(); + return args.GetReturnValue().Set(false); } args.GetReturnValue().Set(true); } -#if OPENSSL_WITH_PQC void KeyObjectHandle::InitPqcRaw(const FunctionCallbackInfo& args) { +#if OPENSSL_WITH_PQC KeyObjectHandle* key; ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); @@ -867,12 +882,11 @@ void KeyObjectHandle::InitPqcRaw(const FunctionCallbackInfo& args) { MarkPopErrorOnReturn mark_pop_error_on_return; + int id = GetNidFromName(*name); + typedef EVPKeyPointer (*new_key_fn)( int, const ncrypto::Buffer&); - new_key_fn fn = type == kKeyTypePrivate ? EVPKeyPointer::NewRawSeed - : EVPKeyPointer::NewRawPublic; - - int id = GetNidFromName(*name); + new_key_fn fn; switch (id) { case EVP_PKEY_ML_DSA_44: @@ -880,26 +894,46 @@ void KeyObjectHandle::InitPqcRaw(const FunctionCallbackInfo& args) { case EVP_PKEY_ML_DSA_87: case EVP_PKEY_ML_KEM_512: case EVP_PKEY_ML_KEM_768: - case EVP_PKEY_ML_KEM_1024: { - auto pkey = fn(id, - ncrypto::Buffer{ - .data = key_data.data(), - .len = key_data.size(), - }); - if (!pkey) { - return args.GetReturnValue().Set(false); - } - key->data_ = KeyObjectData::CreateAsymmetric(type, std::move(pkey)); - CHECK(key->data_); + case EVP_PKEY_ML_KEM_1024: + fn = type == kKeyTypePrivate ? EVPKeyPointer::NewRawSeed + : EVPKeyPointer::NewRawPublic; + break; + case EVP_PKEY_SLH_DSA_SHA2_128F: + case EVP_PKEY_SLH_DSA_SHA2_128S: + case EVP_PKEY_SLH_DSA_SHA2_192F: + case EVP_PKEY_SLH_DSA_SHA2_192S: + case EVP_PKEY_SLH_DSA_SHA2_256F: + case EVP_PKEY_SLH_DSA_SHA2_256S: + case EVP_PKEY_SLH_DSA_SHAKE_128F: + case EVP_PKEY_SLH_DSA_SHAKE_128S: + case EVP_PKEY_SLH_DSA_SHAKE_192F: + case EVP_PKEY_SLH_DSA_SHAKE_192S: + case EVP_PKEY_SLH_DSA_SHAKE_256F: + case EVP_PKEY_SLH_DSA_SHAKE_256S: + fn = type == kKeyTypePrivate ? EVPKeyPointer::NewRawPrivate + : EVPKeyPointer::NewRawPublic; break; - } default: - UNREACHABLE(); + return args.GetReturnValue().Set(false); } + auto pkey = fn(id, + ncrypto::Buffer{ + .data = key_data.data(), + .len = key_data.size(), + }); + if (!pkey) { + return args.GetReturnValue().Set(false); + } + key->data_ = KeyObjectData::CreateAsymmetric(type, std::move(pkey)); + CHECK(key->data_); + args.GetReturnValue().Set(true); -} +#else + Environment* env = Environment::GetCurrent(args); + THROW_ERR_INVALID_ARG_VALUE(env, "Unsupported key type"); #endif +} void KeyObjectHandle::Equals(const FunctionCallbackInfo& args) { KeyObjectHandle* self_handle; @@ -1125,7 +1159,6 @@ MaybeLocal KeyObjectHandle::ExportPrivateKey( return WritePrivateKey(env(), data_.GetAsymmetricKey(), config); } -#if OPENSSL_WITH_PQC void KeyObjectHandle::RawPublicKey( const v8::FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); @@ -1136,18 +1169,216 @@ void KeyObjectHandle::RawPublicKey( CHECK_NE(data.GetKeyType(), kKeyTypeSecret); Mutex::ScopedLock lock(data.mutex()); - auto raw_data = data.GetAsymmetricKey().rawPublicKey(); + const auto& pkey = data.GetAsymmetricKey(); + + switch (pkey.id()) { + case EVP_PKEY_ED25519: + case EVP_PKEY_ED448: + case EVP_PKEY_X25519: + case EVP_PKEY_X448: +#if OPENSSL_WITH_PQC + case EVP_PKEY_ML_DSA_44: + case EVP_PKEY_ML_DSA_65: + case EVP_PKEY_ML_DSA_87: + case EVP_PKEY_ML_KEM_512: + case EVP_PKEY_ML_KEM_768: + case EVP_PKEY_ML_KEM_1024: + case EVP_PKEY_SLH_DSA_SHA2_128F: + case EVP_PKEY_SLH_DSA_SHA2_128S: + case EVP_PKEY_SLH_DSA_SHA2_192F: + case EVP_PKEY_SLH_DSA_SHA2_192S: + case EVP_PKEY_SLH_DSA_SHA2_256F: + case EVP_PKEY_SLH_DSA_SHA2_256S: + case EVP_PKEY_SLH_DSA_SHAKE_128F: + case EVP_PKEY_SLH_DSA_SHAKE_128S: + case EVP_PKEY_SLH_DSA_SHAKE_192F: + case EVP_PKEY_SLH_DSA_SHAKE_192S: + case EVP_PKEY_SLH_DSA_SHAKE_256F: + case EVP_PKEY_SLH_DSA_SHAKE_256S: +#endif + break; + default: + return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); + } + + auto raw_data = pkey.rawPublicKey(); if (!raw_data) { return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get raw public key"); } args.GetReturnValue().Set( - Buffer::Copy( - env, reinterpret_cast(raw_data.get()), raw_data.size()) + Buffer::Copy(env, raw_data.get(), raw_data.size()) .FromMaybe(Local())); } +void KeyObjectHandle::RawPrivateKey( + const v8::FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + KeyObjectHandle* key; + ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); + + const KeyObjectData& data = key->Data(); + CHECK_EQ(data.GetKeyType(), kKeyTypePrivate); + + Mutex::ScopedLock lock(data.mutex()); + const auto& pkey = data.GetAsymmetricKey(); + + switch (pkey.id()) { + case EVP_PKEY_ED25519: + case EVP_PKEY_ED448: + case EVP_PKEY_X25519: + case EVP_PKEY_X448: +#if OPENSSL_WITH_PQC + case EVP_PKEY_SLH_DSA_SHA2_128F: + case EVP_PKEY_SLH_DSA_SHA2_128S: + case EVP_PKEY_SLH_DSA_SHA2_192F: + case EVP_PKEY_SLH_DSA_SHA2_192S: + case EVP_PKEY_SLH_DSA_SHA2_256F: + case EVP_PKEY_SLH_DSA_SHA2_256S: + case EVP_PKEY_SLH_DSA_SHAKE_128F: + case EVP_PKEY_SLH_DSA_SHAKE_128S: + case EVP_PKEY_SLH_DSA_SHAKE_192F: + case EVP_PKEY_SLH_DSA_SHAKE_192S: + case EVP_PKEY_SLH_DSA_SHAKE_256F: + case EVP_PKEY_SLH_DSA_SHAKE_256S: +#endif + break; + default: + return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); + } + + auto raw_data = pkey.rawPrivateKey(); + if (!raw_data) { + return THROW_ERR_CRYPTO_OPERATION_FAILED(env, + "Failed to get raw private key"); + } + + args.GetReturnValue().Set( + Buffer::Copy(env, raw_data.get(), raw_data.size()) + .FromMaybe(Local())); +} + +void KeyObjectHandle::ExportECPublicRaw( + const v8::FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + KeyObjectHandle* key; + ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); + + const KeyObjectData& data = key->Data(); + CHECK_NE(data.GetKeyType(), kKeyTypeSecret); + + Mutex::ScopedLock lock(data.mutex()); + const auto& m_pkey = data.GetAsymmetricKey(); + if (m_pkey.id() != EVP_PKEY_EC) { + return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); + } + + const EC_KEY* ec_key = m_pkey; + CHECK_NOT_NULL(ec_key); + + CHECK(args[0]->IsInt32()); + auto form = + static_cast(args[0].As()->Value()); + + const auto group = ECKeyPointer::GetGroup(ec_key); + const auto point = ECKeyPointer::GetPublicKey(ec_key); + + Local buf; + if (!ECPointToBuffer(env, group, point, form).ToLocal(&buf)) return; + + args.GetReturnValue().Set(buf); +} + +void KeyObjectHandle::ExportECPrivateRaw( + const v8::FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + KeyObjectHandle* key; + ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); + + const KeyObjectData& data = key->Data(); + CHECK_EQ(data.GetKeyType(), kKeyTypePrivate); + + Mutex::ScopedLock lock(data.mutex()); + const auto& m_pkey = data.GetAsymmetricKey(); + if (m_pkey.id() != EVP_PKEY_EC) { + return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); + } + + const EC_KEY* ec_key = m_pkey; + CHECK_NOT_NULL(ec_key); + + const BIGNUM* private_key = ECKeyPointer::GetPrivateKey(ec_key); + CHECK_NOT_NULL(private_key); + + const auto group = ECKeyPointer::GetGroup(ec_key); + auto order = BignumPointer::New(); + CHECK(order); + CHECK(EC_GROUP_get_order(group, order.get(), nullptr)); + + auto buf = BignumPointer::EncodePadded(private_key, order.byteLength()); + if (!buf) { + return THROW_ERR_CRYPTO_OPERATION_FAILED(env, + "Failed to export EC private key"); + } + + args.GetReturnValue().Set(Buffer::Copy(env, buf.get(), buf.size()) + .FromMaybe(Local())); +} + +void KeyObjectHandle::InitECPrivateRaw( + const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + KeyObjectHandle* key; + ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); + + CHECK(args[0]->IsString()); + Utf8Value name(env->isolate(), args[0]); + + ArrayBufferOrViewContents key_data(args[1]); + + MarkPopErrorOnReturn mark_pop_error_on_return; + + int nid = ncrypto::Ec::GetCurveIdFromName(*name); + if (nid == NID_undef) return THROW_ERR_CRYPTO_INVALID_CURVE(env); + + auto eckey = ECKeyPointer::NewByCurveName(nid); + if (!eckey) return args.GetReturnValue().Set(false); + + // Validate key data size matches the curve's expected private key length + const auto group = eckey.getGroup(); + auto order = BignumPointer::New(); + CHECK(order); + CHECK(EC_GROUP_get_order(group, order.get(), nullptr)); + if (key_data.size() != order.byteLength()) + return args.GetReturnValue().Set(false); + + BignumPointer priv_bn(key_data.data(), key_data.size()); + if (!priv_bn) return args.GetReturnValue().Set(false); + + if (!eckey.setPrivateKey(priv_bn)) return args.GetReturnValue().Set(false); + + // Compute public key from private key + auto pub_point = ECPointPointer::New(group); + if (!pub_point || !pub_point.mul(group, priv_bn.get())) { + return args.GetReturnValue().Set(false); + } + + if (!eckey.setPublicKey(pub_point)) return args.GetReturnValue().Set(false); + + auto pkey = EVPKeyPointer::New(); + if (!pkey.assign(eckey)) { + return args.GetReturnValue().Set(false); + } + + eckey.release(); + + key->data_ = + KeyObjectData::CreateAsymmetric(kKeyTypePrivate, std::move(pkey)); + + args.GetReturnValue().Set(true); +} + void KeyObjectHandle::RawSeed(const v8::FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); KeyObjectHandle* key; @@ -1157,17 +1388,33 @@ void KeyObjectHandle::RawSeed(const v8::FunctionCallbackInfo& args) { CHECK_EQ(data.GetKeyType(), kKeyTypePrivate); Mutex::ScopedLock lock(data.mutex()); - auto raw_data = data.GetAsymmetricKey().rawSeed(); + const auto& pkey = data.GetAsymmetricKey(); + + switch (pkey.id()) { +#if OPENSSL_WITH_PQC + case EVP_PKEY_ML_DSA_44: + case EVP_PKEY_ML_DSA_65: + case EVP_PKEY_ML_DSA_87: + case EVP_PKEY_ML_KEM_512: + case EVP_PKEY_ML_KEM_768: + case EVP_PKEY_ML_KEM_1024: + break; +#endif + default: + return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); + } + +#if OPENSSL_WITH_PQC + auto raw_data = pkey.rawSeed(); if (!raw_data) { return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get raw seed"); } args.GetReturnValue().Set( - Buffer::Copy( - env, reinterpret_cast(raw_data.get()), raw_data.size()) + Buffer::Copy(env, raw_data.get(), raw_data.size()) .FromMaybe(Local())); -} #endif +} void KeyObjectHandle::ExportJWK( const v8::FunctionCallbackInfo& args) { @@ -1290,32 +1537,6 @@ std::unique_ptr NativeKeyObject::CloneForMessaging() return std::make_unique(handle_data_); } -WebCryptoKeyExportStatus PKEY_SPKI_Export(const KeyObjectData& key_data, - ByteSource* out) { - CHECK_EQ(key_data.GetKeyType(), kKeyTypePublic); - Mutex::ScopedLock lock(key_data.mutex()); - auto bio = key_data.GetAsymmetricKey().derPublicKey(); - if (!bio) return WebCryptoKeyExportStatus::FAILED; - *out = ByteSource::FromBIO(bio); - return WebCryptoKeyExportStatus::OK; -} - -WebCryptoKeyExportStatus PKEY_PKCS8_Export(const KeyObjectData& key_data, - ByteSource* out) { - CHECK_EQ(key_data.GetKeyType(), kKeyTypePrivate); - Mutex::ScopedLock lock(key_data.mutex()); - const auto& m_pkey = key_data.GetAsymmetricKey(); - - auto bio = BIOPointer::NewMem(); - CHECK(bio); - PKCS8Pointer p8inf(EVP_PKEY2PKCS8(m_pkey.get())); - if (!i2d_PKCS8_PRIV_KEY_INFO_bio(bio.get(), p8inf.get())) - return WebCryptoKeyExportStatus::FAILED; - - *out = ByteSource::FromBIO(bio); - return WebCryptoKeyExportStatus::OK; -} - namespace Keys { void Initialize(Environment* env, Local target) { target->Set(env->context(), diff --git a/src/crypto/crypto_keys.h b/src/crypto/crypto_keys.h index 90c252eba28bea..4a8438b38c9f1e 100644 --- a/src/crypto/crypto_keys.h +++ b/src/crypto/crypto_keys.h @@ -170,11 +170,15 @@ class KeyObjectHandle : public BaseObject { static void Export(const v8::FunctionCallbackInfo& args); -#if OPENSSL_WITH_PQC - static void InitPqcRaw(const v8::FunctionCallbackInfo& args); static void RawPublicKey(const v8::FunctionCallbackInfo& args); + static void RawPrivateKey(const v8::FunctionCallbackInfo& args); + static void ExportECPublicRaw( + const v8::FunctionCallbackInfo& args); + static void ExportECPrivateRaw( + const v8::FunctionCallbackInfo& args); + static void InitECPrivateRaw(const v8::FunctionCallbackInfo& args); + static void InitPqcRaw(const v8::FunctionCallbackInfo& args); static void RawSeed(const v8::FunctionCallbackInfo& args); -#endif v8::MaybeLocal ExportSecretKey() const; v8::MaybeLocal ExportPublicKey( @@ -241,147 +245,6 @@ enum WebCryptoKeyFormat { kWebCryptoKeyFormatJWK }; -enum class WebCryptoKeyExportStatus { - OK, - INVALID_KEY_TYPE, - FAILED -}; - -template -class KeyExportJob final : public CryptoJob { - public: - using AdditionalParams = typename KeyExportTraits::AdditionalParameters; - - static void New(const v8::FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - CHECK(args.IsConstructCall()); - - CryptoJobMode mode = GetCryptoJobMode(args[0]); - - CHECK(args[1]->IsUint32()); // Export Type - CHECK(args[2]->IsObject()); // KeyObject - - WebCryptoKeyFormat format = - static_cast(args[1].As()->Value()); - - KeyObjectHandle* key; - ASSIGN_OR_RETURN_UNWRAP(&key, args[2]); - - CHECK_NOT_NULL(key); - - AdditionalParams params; - if (KeyExportTraits::AdditionalConfig(args, 3, ¶ms).IsNothing()) { - // The KeyExportTraits::AdditionalConfig is responsible for - // calling an appropriate THROW_CRYPTO_* variant reporting - // whatever error caused initialization to fail. - return; - } - - new KeyExportJob( - env, - args.This(), - mode, - key->Data(), - format, - std::move(params)); - } - - static void Initialize( - Environment* env, - v8::Local target) { - CryptoJob::Initialize(New, env, target); - } - - static void RegisterExternalReferences(ExternalReferenceRegistry* registry) { - CryptoJob::RegisterExternalReferences(New, registry); - } - - KeyExportJob(Environment* env, - v8::Local object, - CryptoJobMode mode, - const KeyObjectData& key, - WebCryptoKeyFormat format, - AdditionalParams&& params) - : CryptoJob(env, - object, - AsyncWrap::PROVIDER_KEYEXPORTREQUEST, - mode, - std::move(params)), - key_(key.addRef()), - format_(format) {} - - WebCryptoKeyFormat format() const { return format_; } - - void DoThreadPoolWork() override { - const WebCryptoKeyExportStatus status = - KeyExportTraits::DoExport( - key_, - format_, - *CryptoJob::params(), - &out_); - if (status == WebCryptoKeyExportStatus::OK) { - // Success! - return; - } - CryptoErrorStore* errors = CryptoJob::errors(); - errors->Capture(); - if (errors->Empty()) { - switch (status) { - case WebCryptoKeyExportStatus::OK: - UNREACHABLE(); - break; - case WebCryptoKeyExportStatus::INVALID_KEY_TYPE: - errors->Insert(NodeCryptoError::INVALID_KEY_TYPE); - break; - case WebCryptoKeyExportStatus::FAILED: - errors->Insert(NodeCryptoError::CIPHER_JOB_FAILED); - break; - } - } - } - - v8::Maybe ToResult(v8::Local* err, - v8::Local* result) override { - Environment* env = AsyncWrap::env(); - CryptoErrorStore* errors = CryptoJob::errors(); - if (out_.size() > 0) { - CHECK(errors->Empty()); - *err = v8::Undefined(env->isolate()); - *result = out_.ToArrayBuffer(env); - if (result->IsEmpty()) { - return v8::Nothing(); - } - } else { - if (errors->Empty()) errors->Capture(); - CHECK(!errors->Empty()); - *result = v8::Undefined(env->isolate()); - if (!errors->ToException(env).ToLocal(err)) { - return v8::Nothing(); - } - } - CHECK(!result->IsEmpty()); - CHECK(!err->IsEmpty()); - return v8::JustVoid(); - } - - SET_SELF_SIZE(KeyExportJob) - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackFieldWithSize("out", out_.size()); - CryptoJob::MemoryInfo(tracker); - } - - private: - KeyObjectData key_; - WebCryptoKeyFormat format_; - ByteSource out_; -}; - -WebCryptoKeyExportStatus PKEY_SPKI_Export(const KeyObjectData& key_data, - ByteSource* out); - -WebCryptoKeyExportStatus PKEY_PKCS8_Export(const KeyObjectData& key_data, - ByteSource* out); - namespace Keys { void Initialize(Environment* env, v8::Local target); void RegisterExternalReferences(ExternalReferenceRegistry* registry); diff --git a/src/crypto/crypto_rsa.cc b/src/crypto/crypto_rsa.cc index 62ee228945c45b..3619c1d21dd238 100644 --- a/src/crypto/crypto_rsa.cc +++ b/src/crypto/crypto_rsa.cc @@ -176,12 +176,6 @@ Maybe RsaKeyGenTraits::AdditionalConfig( } namespace { -WebCryptoKeyExportStatus RSA_JWK_Export(const KeyObjectData& key_data, - const RSAKeyExportConfig& params, - ByteSource* out) { - return WebCryptoKeyExportStatus::FAILED; -} - using Cipher_t = DataPointer(const EVPKeyPointer& key, const ncrypto::Rsa::CipherParams& params, const ncrypto::Buffer in); @@ -210,42 +204,6 @@ WebCryptoCipherStatus RSA_Cipher(Environment* env, } } // namespace -Maybe RSAKeyExportTraits::AdditionalConfig( - const FunctionCallbackInfo& args, - unsigned int offset, - RSAKeyExportConfig* params) { - CHECK(args[offset]->IsUint32()); // RSAKeyVariant - params->variant = - static_cast(args[offset].As()->Value()); - return JustVoid(); -} - -WebCryptoKeyExportStatus RSAKeyExportTraits::DoExport( - const KeyObjectData& key_data, - WebCryptoKeyFormat format, - const RSAKeyExportConfig& params, - ByteSource* out) { - CHECK_NE(key_data.GetKeyType(), kKeyTypeSecret); - - switch (format) { - case kWebCryptoKeyFormatRaw: - // Not supported for RSA keys of either type - return WebCryptoKeyExportStatus::FAILED; - case kWebCryptoKeyFormatJWK: - return RSA_JWK_Export(key_data, params, out); - case kWebCryptoKeyFormatPKCS8: - if (key_data.GetKeyType() != kKeyTypePrivate) - return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; - return PKEY_PKCS8_Export(key_data, out); - case kWebCryptoKeyFormatSPKI: - if (key_data.GetKeyType() != kKeyTypePublic) - return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; - return PKEY_SPKI_Export(key_data, out); - default: - UNREACHABLE(); - } -} - RSACipherConfig::RSACipherConfig(RSACipherConfig&& other) noexcept : mode(other.mode), label(std::move(other.label)), @@ -539,7 +497,6 @@ bool GetRsaKeyDetail(Environment* env, namespace RSAAlg { void Initialize(Environment* env, Local target) { RSAKeyPairGenJob::Initialize(env, target); - RSAKeyExportJob::Initialize(env, target); RSACipherJob::Initialize(env, target); NODE_DEFINE_CONSTANT(target, kKeyVariantRSA_SSA_PKCS1_v1_5); @@ -549,7 +506,6 @@ void Initialize(Environment* env, Local target) { void RegisterExternalReferences(ExternalReferenceRegistry* registry) { RSAKeyPairGenJob::RegisterExternalReferences(registry); - RSAKeyExportJob::RegisterExternalReferences(registry); RSACipherJob::RegisterExternalReferences(registry); } } // namespace RSAAlg diff --git a/src/crypto/crypto_rsa.h b/src/crypto/crypto_rsa.h index a9912d6f43674b..5b7f90f502df03 100644 --- a/src/crypto/crypto_rsa.h +++ b/src/crypto/crypto_rsa.h @@ -52,30 +52,6 @@ struct RsaKeyGenTraits final { using RSAKeyPairGenJob = KeyGenJob>; -struct RSAKeyExportConfig final : public MemoryRetainer { - RSAKeyVariant variant = kKeyVariantRSA_SSA_PKCS1_v1_5; - SET_NO_MEMORY_INFO() - SET_MEMORY_INFO_NAME(RSAKeyExportConfig) - SET_SELF_SIZE(RSAKeyExportConfig) -}; - -struct RSAKeyExportTraits final { - static constexpr const char* JobName = "RSAKeyExportJob"; - using AdditionalParameters = RSAKeyExportConfig; - - static v8::Maybe AdditionalConfig( - const v8::FunctionCallbackInfo& args, - unsigned int offset, - RSAKeyExportConfig* config); - - static WebCryptoKeyExportStatus DoExport(const KeyObjectData& key_data, - WebCryptoKeyFormat format, - const RSAKeyExportConfig& params, - ByteSource* out); -}; - -using RSAKeyExportJob = KeyExportJob; - struct RSACipherConfig final : public MemoryRetainer { CryptoJobMode mode = kCryptoJobAsync; ByteSource label; diff --git a/src/crypto/crypto_turboshake.cc b/src/crypto/crypto_turboshake.cc new file mode 100644 index 00000000000000..60ef7a8dae5e44 --- /dev/null +++ b/src/crypto/crypto_turboshake.cc @@ -0,0 +1,642 @@ +#include "crypto/crypto_turboshake.h" +#include "async_wrap-inl.h" +#include "node_internals.h" +#include "threadpoolwork-inl.h" + +#include +#include + +namespace node::crypto { + +using v8::FunctionCallbackInfo; +using v8::JustVoid; +using v8::Local; +using v8::Maybe; +using v8::MaybeLocal; +using v8::Nothing; +using v8::Object; +using v8::Uint32; +using v8::Value; + +// ============================================================================ +// Keccak-p[1600, n_r=12] permutation +// Reference: FIPS 202, Section 3.3 and 3.4; RFC 9861 Section 2.2 +// Adapted from OpenSSL's keccak1600.c (KECCAK_REF variant) +// ============================================================================ +namespace { + +inline uint64_t ROL64(uint64_t val, int offset) { + DCHECK(offset >= 0 && offset < 64); + if (offset == 0) return val; + return (val << offset) | (val >> (64 - offset)); +} + +// Load/store 64-bit lanes in little-endian byte order. +// The Keccak state uses LE lane encoding (FIPS 202 Section 1, B.1). +// These helpers ensure correctness on both LE and BE platforms. +inline uint64_t LoadLE64(const uint8_t* src) { + return static_cast(src[0]) | (static_cast(src[1]) << 8) | + (static_cast(src[2]) << 16) | + (static_cast(src[3]) << 24) | + (static_cast(src[4]) << 32) | + (static_cast(src[5]) << 40) | + (static_cast(src[6]) << 48) | + (static_cast(src[7]) << 56); +} + +inline void StoreLE64(uint8_t* dst, uint64_t val) { + dst[0] = static_cast(val); + dst[1] = static_cast(val >> 8); + dst[2] = static_cast(val >> 16); + dst[3] = static_cast(val >> 24); + dst[4] = static_cast(val >> 32); + dst[5] = static_cast(val >> 40); + dst[6] = static_cast(val >> 48); + dst[7] = static_cast(val >> 56); +} + +static const unsigned char rhotates[5][5] = { + {0, 1, 62, 28, 27}, + {36, 44, 6, 55, 20}, + {3, 10, 43, 25, 39}, + {41, 45, 15, 21, 8}, + {18, 2, 61, 56, 14}, +}; + +// Round constants for Keccak-f[1600]. +// TurboSHAKE uses the last 12 rounds (indices 12..23). +static const uint64_t iotas[24] = { + 0x0000000000000001ULL, 0x0000000000008082ULL, 0x800000000000808aULL, + 0x8000000080008000ULL, 0x000000000000808bULL, 0x0000000080000001ULL, + 0x8000000080008081ULL, 0x8000000000008009ULL, 0x000000000000008aULL, + 0x0000000000000088ULL, 0x0000000080008009ULL, 0x000000008000000aULL, + 0x000000008000808bULL, 0x800000000000008bULL, 0x8000000000008089ULL, + 0x8000000000008003ULL, 0x8000000000008002ULL, 0x8000000000000080ULL, + 0x000000000000800aULL, 0x800000008000000aULL, 0x8000000080008081ULL, + 0x8000000000008080ULL, 0x0000000080000001ULL, 0x8000000080008008ULL, +}; + +// Keccak-p[1600, 12]: the reduced-round permutation used by TurboSHAKE. +void KeccakP1600_12(uint64_t A[5][5]) { + for (size_t round = 12; round < 24; round++) { + // Theta + uint64_t C[5]; + for (size_t x = 0; x < 5; x++) { + C[x] = A[0][x] ^ A[1][x] ^ A[2][x] ^ A[3][x] ^ A[4][x]; + } + uint64_t D[5]; + for (size_t x = 0; x < 5; x++) { + D[x] = C[(x + 4) % 5] ^ ROL64(C[(x + 1) % 5], 1); + } + for (size_t y = 0; y < 5; y++) { + for (size_t x = 0; x < 5; x++) { + A[y][x] ^= D[x]; + } + } + + // Rho + for (size_t y = 0; y < 5; y++) { + for (size_t x = 0; x < 5; x++) { + A[y][x] = ROL64(A[y][x], rhotates[y][x]); + } + } + + // Pi + uint64_t T[5][5]; + memcpy(T, A, sizeof(T)); + for (size_t y = 0; y < 5; y++) { + for (size_t x = 0; x < 5; x++) { + A[y][x] = T[x][(3 * y + x) % 5]; + } + } + + // Chi + for (size_t y = 0; y < 5; y++) { + uint64_t row[5]; + for (size_t x = 0; x < 5; x++) { + row[x] = A[y][x] ^ (~A[y][(x + 1) % 5] & A[y][(x + 2) % 5]); + } + memcpy(A[y], row, sizeof(row)); + } + + // Iota + A[0][0] ^= iotas[round]; + } +} + +// ============================================================================ +// TurboSHAKE sponge construction +// RFC 9861 Section 2.2, Appendix A.2/A.3 +// ============================================================================ + +// TurboSHAKE128 rate = 168 bytes (1344 bits), capacity = 256 bits +// TurboSHAKE256 rate = 136 bytes (1088 bits), capacity = 512 bits +static constexpr size_t kTurboSHAKE128Rate = 168; +static constexpr size_t kTurboSHAKE256Rate = 136; + +void TurboSHAKE(const uint8_t* input, + size_t input_len, + size_t rate, + uint8_t domain_sep, + uint8_t* output, + size_t output_len) { + uint64_t A[5][5] = {}; + // Both rates (168, 136) are multiples of 8 + size_t lane_count = rate / 8; + + size_t offset = 0; + + // Absorb complete blocks from input + while (offset + rate <= input_len) { + for (size_t i = 0; i < lane_count; i++) { + A[i / 5][i % 5] ^= LoadLE64(input + offset + i * 8); + } + KeccakP1600_12(A); + offset += rate; + } + + // Absorb last (partial) block: remaining input bytes + domain_sep + padding + size_t remaining = input_len - offset; + uint8_t pad[168] = {}; // sized for max rate (TurboSHAKE128) + if (remaining > 0) { + memcpy(pad, input + offset, remaining); + } + pad[remaining] ^= domain_sep; + pad[rate - 1] ^= 0x80; + + for (size_t i = 0; i < lane_count; i++) { + A[i / 5][i % 5] ^= LoadLE64(pad + i * 8); + } + KeccakP1600_12(A); + + // Squeeze output + size_t out_offset = 0; + while (out_offset < output_len) { + size_t block = output_len - out_offset; + if (block > rate) block = rate; + size_t full_lanes = block / 8; + for (size_t i = 0; i < full_lanes; i++) { + StoreLE64(output + out_offset + i * 8, A[i / 5][i % 5]); + } + size_t rem = block % 8; + if (rem > 0) { + uint8_t tmp[8]; + StoreLE64(tmp, A[full_lanes / 5][full_lanes % 5]); + memcpy(output + out_offset + full_lanes * 8, tmp, rem); + } + out_offset += block; + if (out_offset < output_len) { + KeccakP1600_12(A); + } + } +} + +// Convenience wrappers +void TurboSHAKE128(const uint8_t* input, + size_t input_len, + uint8_t domain_sep, + uint8_t* output, + size_t output_len) { + TurboSHAKE( + input, input_len, kTurboSHAKE128Rate, domain_sep, output, output_len); +} + +void TurboSHAKE256(const uint8_t* input, + size_t input_len, + uint8_t domain_sep, + uint8_t* output, + size_t output_len) { + TurboSHAKE( + input, input_len, kTurboSHAKE256Rate, domain_sep, output, output_len); +} + +// ============================================================================ +// KangarooTwelve tree hashing (RFC 9861 Section 3) +// ============================================================================ + +static constexpr size_t kChunkSize = 8192; + +// length_encode(x): RFC 9861 Section 3.3 +// Returns byte string x_(n-1) || ... || x_0 || n +// where x = sum of 256^i * x_i, n is smallest such that x < 256^n +std::vector LengthEncode(size_t x) { + if (x == 0) { + return {0x00}; + } + + std::vector result; + size_t val = x; + while (val > 0) { + result.push_back(static_cast(val & 0xFF)); + val >>= 8; + } + + // Reverse to get big-endian: x_(n-1) || ... || x_0 + size_t n = result.size(); + for (size_t i = 0; i < n / 2; i++) { + std::swap(result[i], result[n - 1 - i]); + } + + // Append n (the length of the encoding) + result.push_back(static_cast(n)); + return result; +} + +using TurboSHAKEFn = void (*)(const uint8_t* input, + size_t input_len, + uint8_t domain_sep, + uint8_t* output, + size_t output_len); + +void KangarooTwelve(const uint8_t* message, + size_t msg_len, + const uint8_t* customization, + size_t custom_len, + uint8_t* output, + size_t output_len, + TurboSHAKEFn turboshake, + size_t cv_len) { + // Build S = M || C || length_encode(|C|) + auto len_enc = LengthEncode(custom_len); + size_t s_len = msg_len + custom_len + len_enc.size(); + + // Short message path: |S| <= 8192 + if (s_len <= kChunkSize) { + // Build S in a contiguous buffer + std::vector s(s_len); + size_t pos = 0; + if (msg_len > 0) { + memcpy(s.data() + pos, message, msg_len); + pos += msg_len; + } + if (custom_len > 0) { + memcpy(s.data() + pos, customization, custom_len); + pos += custom_len; + } + memcpy(s.data() + pos, len_enc.data(), len_enc.size()); + + turboshake(s.data(), s_len, 0x07, output, output_len); + return; + } + + // Long message path: tree hashing + // We need to process S in chunks, but S is virtual (M || C || length_encode) + // Build a helper to read from this virtual concatenation. + + // First chunk is S[0:8192], compute chaining values for rest + // FinalNode = S[0:8192] || 0x03 || 0x00^7 + + // We need to read from S = M || C || length_encode(|C|) + // Helper lambda to copy from virtual S + auto read_s = [&](size_t s_offset, uint8_t* buf, size_t len) { + size_t copied = 0; + // Part 1: message + if (s_offset < msg_len && copied < len) { + size_t avail = msg_len - s_offset; + size_t to_copy = avail < (len - copied) ? avail : (len - copied); + memcpy(buf + copied, message + s_offset, to_copy); + copied += to_copy; + s_offset += to_copy; + } + // Part 2: customization + size_t custom_start = msg_len; + if (s_offset < custom_start + custom_len && copied < len) { + size_t off_in_custom = s_offset - custom_start; + size_t avail = custom_len - off_in_custom; + size_t to_copy = avail < (len - copied) ? avail : (len - copied); + memcpy(buf + copied, customization + off_in_custom, to_copy); + copied += to_copy; + s_offset += to_copy; + } + // Part 3: length_encode + size_t le_start = msg_len + custom_len; + if (s_offset < le_start + len_enc.size() && copied < len) { + size_t off_in_le = s_offset - le_start; + size_t avail = len_enc.size() - off_in_le; + size_t to_copy = avail < (len - copied) ? avail : (len - copied); + memcpy(buf + copied, len_enc.data() + off_in_le, to_copy); + copied += to_copy; + } + }; + + // Start building FinalNode + // FinalNode = S_0 || 0x03 0x00^7 || CV_1 || CV_2 || ... || CV_(n-1) + // || length_encode(n-1) || 0xFF 0xFF + + // Read first chunk S_0 + std::vector first_chunk(kChunkSize); + read_s(0, first_chunk.data(), kChunkSize); + + // Start FinalNode with S_0 || 0x03 || 0x00^7 + std::vector final_node; + final_node.reserve(kChunkSize + 8 + ((s_len / kChunkSize) * cv_len) + 16); + final_node.insert(final_node.end(), first_chunk.begin(), first_chunk.end()); + final_node.push_back(0x03); + final_node.insert(final_node.end(), 7, 0x00); + + // Process remaining chunks + size_t offset = kChunkSize; + size_t num_blocks = 0; + std::vector chunk(kChunkSize); + std::vector cv(cv_len); + + while (offset < s_len) { + size_t block_size = s_len - offset; + if (block_size > kChunkSize) block_size = kChunkSize; + + chunk.resize(block_size); + read_s(offset, chunk.data(), block_size); + + // CV = TurboSHAKE(chunk, 0x0B, cv_len) + turboshake(chunk.data(), block_size, 0x0B, cv.data(), cv_len); + + final_node.insert(final_node.end(), cv.begin(), cv.end()); + num_blocks++; + offset += block_size; + } + + // Append length_encode(num_blocks) || 0xFF 0xFF + auto num_blocks_enc = LengthEncode(num_blocks); + final_node.insert( + final_node.end(), num_blocks_enc.begin(), num_blocks_enc.end()); + final_node.push_back(0xFF); + final_node.push_back(0xFF); + + // Final hash + turboshake(final_node.data(), final_node.size(), 0x06, output, output_len); +} + +void KT128(const uint8_t* message, + size_t msg_len, + const uint8_t* customization, + size_t custom_len, + uint8_t* output, + size_t output_len) { + KangarooTwelve(message, + msg_len, + customization, + custom_len, + output, + output_len, + TurboSHAKE128, + 32); +} + +void KT256(const uint8_t* message, + size_t msg_len, + const uint8_t* customization, + size_t custom_len, + uint8_t* output, + size_t output_len) { + KangarooTwelve(message, + msg_len, + customization, + custom_len, + output, + output_len, + TurboSHAKE256, + 64); +} + +} // anonymous namespace + +// ============================================================================ +// TurboShake bindings +// ============================================================================ + +TurboShakeConfig::TurboShakeConfig(TurboShakeConfig&& other) noexcept + : job_mode(other.job_mode), + variant(other.variant), + output_length(other.output_length), + domain_separation(other.domain_separation), + data(std::move(other.data)) {} + +TurboShakeConfig& TurboShakeConfig::operator=( + TurboShakeConfig&& other) noexcept { + if (&other == this) return *this; + this->~TurboShakeConfig(); + return *new (this) TurboShakeConfig(std::move(other)); +} + +void TurboShakeConfig::MemoryInfo(MemoryTracker* tracker) const { + if (job_mode == kCryptoJobAsync) { + // TODO(addaleax): Implement MemoryRetainer protocol for ByteSource + tracker->TrackFieldWithSize("data", data.size()); + } +} + +Maybe TurboShakeTraits::AdditionalConfig( + CryptoJobMode mode, + const FunctionCallbackInfo& args, + unsigned int offset, + TurboShakeConfig* params) { + Environment* env = Environment::GetCurrent(args); + + params->job_mode = mode; + + // args[offset + 0] = algorithm name (string) + CHECK(args[offset]->IsString()); + Utf8Value algorithm_name(env->isolate(), args[offset]); + std::string_view alg = algorithm_name.ToStringView(); + + if (alg == "TurboSHAKE128") { + params->variant = TurboShakeVariant::TurboSHAKE128; + } else if (alg == "TurboSHAKE256") { + params->variant = TurboShakeVariant::TurboSHAKE256; + } else { + UNREACHABLE(); + } + + // args[offset + 1] = domain separation byte (uint32) + CHECK(args[offset + 1]->IsUint32()); + params->domain_separation = + static_cast(args[offset + 1].As()->Value()); + CHECK_GE(params->domain_separation, 0x01); + CHECK_LE(params->domain_separation, 0x7F); + + // args[offset + 2] = output length in bytes (uint32) + CHECK(args[offset + 2]->IsUint32()); + params->output_length = args[offset + 2].As()->Value(); + + // args[offset + 3] = data (ArrayBuffer/View) + ArrayBufferOrViewContents data(args[offset + 3]); + if (!data.CheckSizeInt32()) [[unlikely]] { + THROW_ERR_OUT_OF_RANGE(env, "data is too big"); + return Nothing(); + } + params->data = mode == kCryptoJobAsync ? data.ToCopy() : data.ToByteSource(); + + return JustVoid(); +} + +bool TurboShakeTraits::DeriveBits(Environment* env, + const TurboShakeConfig& params, + ByteSource* out, + CryptoJobMode mode) { + CHECK_GT(params.output_length, 0); + char* buf = MallocOpenSSL(params.output_length); + + const uint8_t* input = reinterpret_cast(params.data.data()); + size_t input_len = params.data.size(); + + switch (params.variant) { + case TurboShakeVariant::TurboSHAKE128: + TurboSHAKE128(input, + input_len, + params.domain_separation, + reinterpret_cast(buf), + params.output_length); + break; + case TurboShakeVariant::TurboSHAKE256: + TurboSHAKE256(input, + input_len, + params.domain_separation, + reinterpret_cast(buf), + params.output_length); + break; + } + + *out = ByteSource::Allocated(buf, params.output_length); + return true; +} + +MaybeLocal TurboShakeTraits::EncodeOutput(Environment* env, + const TurboShakeConfig& params, + ByteSource* out) { + return out->ToArrayBuffer(env); +} + +// ============================================================================ +// KangarooTwelve bindings +// ============================================================================ + +KangarooTwelveConfig::KangarooTwelveConfig( + KangarooTwelveConfig&& other) noexcept + : job_mode(other.job_mode), + variant(other.variant), + output_length(other.output_length), + data(std::move(other.data)), + customization(std::move(other.customization)) {} + +KangarooTwelveConfig& KangarooTwelveConfig::operator=( + KangarooTwelveConfig&& other) noexcept { + if (&other == this) return *this; + this->~KangarooTwelveConfig(); + return *new (this) KangarooTwelveConfig(std::move(other)); +} + +void KangarooTwelveConfig::MemoryInfo(MemoryTracker* tracker) const { + if (job_mode == kCryptoJobAsync) { + // TODO(addaleax): Implement MemoryRetainer protocol for ByteSource + tracker->TrackFieldWithSize("data", data.size()); + tracker->TrackFieldWithSize("customization", customization.size()); + } +} + +Maybe KangarooTwelveTraits::AdditionalConfig( + CryptoJobMode mode, + const FunctionCallbackInfo& args, + unsigned int offset, + KangarooTwelveConfig* params) { + Environment* env = Environment::GetCurrent(args); + + params->job_mode = mode; + + // args[offset + 0] = algorithm name (string) + CHECK(args[offset]->IsString()); + Utf8Value algorithm_name(env->isolate(), args[offset]); + std::string_view alg = algorithm_name.ToStringView(); + + if (alg == "KT128") { + params->variant = KangarooTwelveVariant::KT128; + } else if (alg == "KT256") { + params->variant = KangarooTwelveVariant::KT256; + } else { + UNREACHABLE(); + } + + // args[offset + 1] = customization (BufferSource or undefined) + if (!args[offset + 1]->IsUndefined()) { + ArrayBufferOrViewContents customization(args[offset + 1]); + if (!customization.CheckSizeInt32()) [[unlikely]] { + THROW_ERR_OUT_OF_RANGE(env, "customization is too big"); + return Nothing(); + } + params->customization = mode == kCryptoJobAsync + ? customization.ToCopy() + : customization.ToByteSource(); + } + + // args[offset + 2] = output length in bytes (uint32) + CHECK(args[offset + 2]->IsUint32()); + params->output_length = args[offset + 2].As()->Value(); + + // args[offset + 3] = data (ArrayBuffer/View) + ArrayBufferOrViewContents data(args[offset + 3]); + if (!data.CheckSizeInt32()) [[unlikely]] { + THROW_ERR_OUT_OF_RANGE(env, "data is too big"); + return Nothing(); + } + params->data = mode == kCryptoJobAsync ? data.ToCopy() : data.ToByteSource(); + + return JustVoid(); +} + +bool KangarooTwelveTraits::DeriveBits(Environment* env, + const KangarooTwelveConfig& params, + ByteSource* out, + CryptoJobMode mode) { + CHECK_GT(params.output_length, 0); + char* buf = MallocOpenSSL(params.output_length); + + const uint8_t* input = reinterpret_cast(params.data.data()); + size_t input_len = params.data.size(); + + const uint8_t* custom = + reinterpret_cast(params.customization.data()); + size_t custom_len = params.customization.size(); + + switch (params.variant) { + case KangarooTwelveVariant::KT128: + KT128(input, + input_len, + custom, + custom_len, + reinterpret_cast(buf), + params.output_length); + break; + case KangarooTwelveVariant::KT256: + KT256(input, + input_len, + custom, + custom_len, + reinterpret_cast(buf), + params.output_length); + break; + } + + *out = ByteSource::Allocated(buf, params.output_length); + return true; +} + +MaybeLocal KangarooTwelveTraits::EncodeOutput( + Environment* env, const KangarooTwelveConfig& params, ByteSource* out) { + return out->ToArrayBuffer(env); +} + +// ============================================================================ +// Registration +// ============================================================================ + +void TurboShake::Initialize(Environment* env, Local target) { + TurboShakeJob::Initialize(env, target); + KangarooTwelveJob::Initialize(env, target); +} + +void TurboShake::RegisterExternalReferences( + ExternalReferenceRegistry* registry) { + TurboShakeJob::RegisterExternalReferences(registry); + KangarooTwelveJob::RegisterExternalReferences(registry); +} + +} // namespace node::crypto diff --git a/src/crypto/crypto_turboshake.h b/src/crypto/crypto_turboshake.h new file mode 100644 index 00000000000000..53b01eec8bd7c8 --- /dev/null +++ b/src/crypto/crypto_turboshake.h @@ -0,0 +1,105 @@ +#ifndef SRC_CRYPTO_CRYPTO_TURBOSHAKE_H_ +#define SRC_CRYPTO_CRYPTO_TURBOSHAKE_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "crypto/crypto_util.h" + +namespace node::crypto { + +enum class TurboShakeVariant { TurboSHAKE128, TurboSHAKE256 }; + +struct TurboShakeConfig final : public MemoryRetainer { + CryptoJobMode job_mode; + TurboShakeVariant variant; + uint32_t output_length; // Output length in bytes + uint8_t domain_separation; // Domain separation byte (0x01–0x7F) + ByteSource data; + + TurboShakeConfig() = default; + + explicit TurboShakeConfig(TurboShakeConfig&& other) noexcept; + + TurboShakeConfig& operator=(TurboShakeConfig&& other) noexcept; + + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(TurboShakeConfig) + SET_SELF_SIZE(TurboShakeConfig) +}; + +struct TurboShakeTraits final { + using AdditionalParameters = TurboShakeConfig; + static constexpr const char* JobName = "TurboShakeJob"; + static constexpr AsyncWrap::ProviderType Provider = + AsyncWrap::PROVIDER_DERIVEBITSREQUEST; + + static v8::Maybe AdditionalConfig( + CryptoJobMode mode, + const v8::FunctionCallbackInfo& args, + unsigned int offset, + TurboShakeConfig* params); + + static bool DeriveBits(Environment* env, + const TurboShakeConfig& params, + ByteSource* out, + CryptoJobMode mode); + + static v8::MaybeLocal EncodeOutput(Environment* env, + const TurboShakeConfig& params, + ByteSource* out); +}; + +using TurboShakeJob = DeriveBitsJob; + +enum class KangarooTwelveVariant { KT128, KT256 }; + +struct KangarooTwelveConfig final : public MemoryRetainer { + CryptoJobMode job_mode; + KangarooTwelveVariant variant; + uint32_t output_length; // Output length in bytes + ByteSource data; + ByteSource customization; + + KangarooTwelveConfig() = default; + + explicit KangarooTwelveConfig(KangarooTwelveConfig&& other) noexcept; + + KangarooTwelveConfig& operator=(KangarooTwelveConfig&& other) noexcept; + + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(KangarooTwelveConfig) + SET_SELF_SIZE(KangarooTwelveConfig) +}; + +struct KangarooTwelveTraits final { + using AdditionalParameters = KangarooTwelveConfig; + static constexpr const char* JobName = "KangarooTwelveJob"; + static constexpr AsyncWrap::ProviderType Provider = + AsyncWrap::PROVIDER_DERIVEBITSREQUEST; + + static v8::Maybe AdditionalConfig( + CryptoJobMode mode, + const v8::FunctionCallbackInfo& args, + unsigned int offset, + KangarooTwelveConfig* params); + + static bool DeriveBits(Environment* env, + const KangarooTwelveConfig& params, + ByteSource* out, + CryptoJobMode mode); + + static v8::MaybeLocal EncodeOutput( + Environment* env, const KangarooTwelveConfig& params, ByteSource* out); +}; + +using KangarooTwelveJob = DeriveBitsJob; + +namespace TurboShake { +void Initialize(Environment* env, v8::Local target); +void RegisterExternalReferences(ExternalReferenceRegistry* registry); +} // namespace TurboShake + +} // namespace node::crypto + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS +#endif // SRC_CRYPTO_CRYPTO_TURBOSHAKE_H_ diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 991cbf95fbb786..84375f9a737675 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -75,6 +75,8 @@ namespace crypto { #define KMAC_NAMESPACE_LIST(V) #endif +#define TURBOSHAKE_NAMESPACE_LIST(V) V(TurboShake) + #ifdef OPENSSL_NO_SCRYPT #define SCRYPT_NAMESPACE_LIST(V) #else @@ -86,7 +88,8 @@ namespace crypto { ARGON2_NAMESPACE_LIST(V) \ KEM_NAMESPACE_LIST(V) \ KMAC_NAMESPACE_LIST(V) \ - SCRYPT_NAMESPACE_LIST(V) + SCRYPT_NAMESPACE_LIST(V) \ + TURBOSHAKE_NAMESPACE_LIST(V) void Initialize(Local target, Local unused, diff --git a/src/node_crypto.h b/src/node_crypto.h index e5e29544b57a81..cc8fc689f48438 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -55,6 +55,7 @@ #include "crypto/crypto_spkac.h" #include "crypto/crypto_timing.h" #include "crypto/crypto_tls.h" +#include "crypto/crypto_turboshake.h" #include "crypto/crypto_util.h" #include "crypto/crypto_x509.h" diff --git a/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/fixtures/webcrypto/supports-modern-algorithms.mjs b/test/fixtures/webcrypto/supports-modern-algorithms.mjs index eafb95c559a0f7..62b90daf7b0463 100644 --- a/test/fixtures/webcrypto/supports-modern-algorithms.mjs +++ b/test/fixtures/webcrypto/supports-modern-algorithms.mjs @@ -28,6 +28,30 @@ export const vectors = { [false, { name: 'cSHAKE256', outputLength: 256, functionName: Buffer.alloc(1) }], [false, { name: 'cSHAKE256', outputLength: 256, customization: Buffer.alloc(1) }], [false, { name: 'cSHAKE256', outputLength: 255 }], + [false, 'TurboSHAKE128'], + [true, { name: 'TurboSHAKE128', outputLength: 128 }], + [true, { name: 'TurboSHAKE128', outputLength: 128, domainSeparation: 0x07 }], + [false, { name: 'TurboSHAKE128', outputLength: 0 }], + [false, { name: 'TurboSHAKE128', outputLength: 127 }], + [false, { name: 'TurboSHAKE128', outputLength: 128, domainSeparation: 0x00 }], + [false, { name: 'TurboSHAKE128', outputLength: 128, domainSeparation: 0x80 }], + [false, 'TurboSHAKE256'], + [true, { name: 'TurboSHAKE256', outputLength: 256 }], + [true, { name: 'TurboSHAKE256', outputLength: 256, domainSeparation: 0x07 }], + [false, { name: 'TurboSHAKE256', outputLength: 0 }], + [false, { name: 'TurboSHAKE256', outputLength: 255 }], + [false, { name: 'TurboSHAKE256', outputLength: 256, domainSeparation: 0x00 }], + [false, { name: 'TurboSHAKE256', outputLength: 256, domainSeparation: 0x80 }], + [false, 'KT128'], + [true, { name: 'KT128', outputLength: 128 }], + [true, { name: 'KT128', outputLength: 128, customization: Buffer.alloc(0) }], + [false, { name: 'KT128', outputLength: 0 }], + [false, { name: 'KT128', outputLength: 127 }], + [false, 'KT256'], + [true, { name: 'KT256', outputLength: 256 }], + [true, { name: 'KT256', outputLength: 256, customization: Buffer.alloc(0) }], + [false, { name: 'KT256', outputLength: 0 }], + [false, { name: 'KT256', outputLength: 255 }], ], 'sign': [ [pqc, 'ML-DSA-44'], diff --git a/test/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 00000000000000..7bad251ba7e08e Binary files /dev/null and b/test/fixtures/wpt/resources/example.pdf differ 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/parallel/test-crypto-encap-decap.js b/test/parallel/test-crypto-encap-decap.js index 2c2ccd42ca2365..10dc451ef55d68 100644 --- a/test/parallel/test-crypto-encap-decap.js +++ b/test/parallel/test-crypto-encap-decap.js @@ -26,6 +26,7 @@ const keys = { privateKey: fixtures.readKey('rsa_private_2048.pem', 'ascii'), sharedSecretLength: 256, ciphertextLength: 256, + raw: false, }, 'rsa-pss': { supported: false, // Only raw RSA is supported @@ -38,6 +39,7 @@ const keys = { privateKey: fixtures.readKey('ec_p256_private.pem', 'ascii'), sharedSecretLength: 32, ciphertextLength: 65, + raw: true, }, 'p-384': { supported: hasOpenSSL(3, 2), // DHKEM was added in 3.2 @@ -45,6 +47,7 @@ const keys = { privateKey: fixtures.readKey('ec_p384_private.pem', 'ascii'), sharedSecretLength: 48, ciphertextLength: 97, + raw: true, }, 'p-521': { supported: hasOpenSSL(3, 2), // DHKEM was added in 3.2 @@ -52,6 +55,7 @@ const keys = { privateKey: fixtures.readKey('ec_p521_private.pem', 'ascii'), sharedSecretLength: 64, ciphertextLength: 133, + raw: true, }, 'secp256k1': { supported: false, // only P-256, P-384, and P-521 are supported @@ -64,6 +68,7 @@ const keys = { privateKey: fixtures.readKey('x25519_private.pem', 'ascii'), sharedSecretLength: 32, ciphertextLength: 32, + raw: true, }, 'x448': { supported: hasOpenSSL(3, 2), // DHKEM was added in 3.2 @@ -71,6 +76,7 @@ const keys = { privateKey: fixtures.readKey('x448_private.pem', 'ascii'), sharedSecretLength: 64, ciphertextLength: 56, + raw: true, }, 'ml-kem-512': { supported: hasOpenSSL(3, 5), @@ -78,6 +84,7 @@ const keys = { privateKey: fixtures.readKey('ml_kem_512_private.pem', 'ascii'), sharedSecretLength: 32, ciphertextLength: 768, + raw: true, }, 'ml-kem-768': { supported: hasOpenSSL(3, 5), @@ -85,6 +92,7 @@ const keys = { privateKey: fixtures.readKey('ml_kem_768_private.pem', 'ascii'), sharedSecretLength: 32, ciphertextLength: 1088, + raw: true, }, 'ml-kem-1024': { supported: hasOpenSSL(3, 5), @@ -92,10 +100,13 @@ const keys = { privateKey: fixtures.readKey('ml_kem_1024_private.pem', 'ascii'), sharedSecretLength: 32, ciphertextLength: 1568, + raw: true, }, }; -for (const [name, { supported, publicKey, privateKey, sharedSecretLength, ciphertextLength }] of Object.entries(keys)) { +for (const [name, { + supported, publicKey, privateKey, sharedSecretLength, ciphertextLength, raw, +}] of Object.entries(keys)) { if (!supported) { assert.throws(() => crypto.encapsulate(publicKey), { code: /ERR_OSSL_EVP_DECODE_ERROR|ERR_CRYPTO_OPERATION_FAILED/ }); @@ -136,6 +147,25 @@ for (const [name, { supported, publicKey, privateKey, sharedSecretLength, cipher }); } + if (raw) { + const { asymmetricKeyType } = keyObjects.privateKey; + const { namedCurve } = keyObjects.privateKey.asymmetricKeyDetails; + const privateFormat = asymmetricKeyType.startsWith('ml-') ? 'raw-seed' : 'raw-private'; + const rawPublic = { + key: keyObjects.publicKey.export({ format: 'raw-public' }), + format: 'raw-public', + asymmetricKeyType, + ...(namedCurve ? { namedCurve } : {}), + }; + const rawPrivate = { + key: keyObjects.privateKey.export({ format: privateFormat }), + format: privateFormat, + asymmetricKeyType, + ...(namedCurve ? { namedCurve } : {}), + }; + keyPairs.push({ publicKey: rawPublic, privateKey: rawPrivate }); + } + for (const kp of keyPairs) { // sync { diff --git a/test/parallel/test-crypto-key-objects-raw.js b/test/parallel/test-crypto-key-objects-raw.js new file mode 100644 index 00000000000000..f301cc1942fd9a --- /dev/null +++ b/test/parallel/test-crypto-key-objects-raw.js @@ -0,0 +1,446 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); +const fixtures = require('../common/fixtures'); +const { hasOpenSSL } = require('../common/crypto'); + +// EC: NIST and OpenSSL curve names are both recognized for raw-public and raw-private +{ + const pubKeyObj = crypto.createPublicKey( + fixtures.readKey('ec_p256_public.pem', 'ascii')); + const privKeyObj = crypto.createPrivateKey( + fixtures.readKey('ec_p256_private.pem', 'ascii')); + + const rawPub = pubKeyObj.export({ format: 'raw-public' }); + const rawPriv = privKeyObj.export({ format: 'raw-private' }); + + for (const namedCurve of ['P-256', 'prime256v1']) { + const importedPub = crypto.createPublicKey({ + key: rawPub, format: 'raw-public', asymmetricKeyType: 'ec', namedCurve, + }); + assert.strictEqual(importedPub.equals(pubKeyObj), true); + + const importedPriv = crypto.createPrivateKey({ + key: rawPriv, format: 'raw-private', asymmetricKeyType: 'ec', namedCurve, + }); + assert.strictEqual(importedPriv.equals(privKeyObj), true); + } +} + +// Key types that don't support raw-* formats +{ + for (const [type, pub, priv] of [ + ['rsa', 'rsa_public_2048.pem', 'rsa_private_2048.pem'], + ['dsa', 'dsa_public.pem', 'dsa_private.pem'], + ]) { + const pubKeyObj = crypto.createPublicKey( + fixtures.readKey(pub, 'ascii')); + const privKeyObj = crypto.createPrivateKey( + fixtures.readKey(priv, 'ascii')); + + assert.throws(() => pubKeyObj.export({ format: 'raw-public' }), + { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + assert.throws(() => privKeyObj.export({ format: 'raw-private' }), + { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + assert.throws(() => privKeyObj.export({ format: 'raw-seed' }), + { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + + for (const format of ['raw-public', 'raw-private', 'raw-seed']) { + assert.throws(() => crypto.createPublicKey({ + key: Buffer.alloc(32), format, asymmetricKeyType: type, + }), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + } + } + + // DH keys also don't support raw formats + { + const privKeyObj = crypto.createPrivateKey( + fixtures.readKey('dh_private.pem', 'ascii')); + assert.throws(() => privKeyObj.export({ format: 'raw-private' }), + { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + + for (const format of ['raw-public', 'raw-private', 'raw-seed']) { + assert.throws(() => crypto.createPrivateKey({ + key: Buffer.alloc(32), format, asymmetricKeyType: 'dh', + }), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + } + } +} + +// PQC import throws when PQC is not supported +if (!hasOpenSSL(3, 5)) { + for (const asymmetricKeyType of [ + 'ml-dsa-44', 'ml-dsa-65', 'ml-dsa-87', + 'ml-kem-512', 'ml-kem-768', 'ml-kem-1024', + 'slh-dsa-sha2-128f', 'slh-dsa-shake-128f', + ]) { + for (const format of ['raw-public', 'raw-private', 'raw-seed']) { + assert.throws(() => crypto.createPublicKey({ + key: Buffer.alloc(32), format, asymmetricKeyType, + }), { code: 'ERR_INVALID_ARG_VALUE' }); + } + } +} + +// EC: P-256 and P-384 keys cannot be imported as private/public of the other type +{ + const p256Pub = crypto.createPublicKey( + fixtures.readKey('ec_p256_public.pem', 'ascii')); + const p384Pub = crypto.createPublicKey( + fixtures.readKey('ec_p384_public.pem', 'ascii')); + const p256Priv = crypto.createPrivateKey( + fixtures.readKey('ec_p256_private.pem', 'ascii')); + const p384Priv = crypto.createPrivateKey( + fixtures.readKey('ec_p384_private.pem', 'ascii')); + + const p256RawPub = p256Pub.export({ format: 'raw-public' }); + const p384RawPub = p384Pub.export({ format: 'raw-public' }); + const p256RawPriv = p256Priv.export({ format: 'raw-private' }); + const p384RawPriv = p384Priv.export({ format: 'raw-private' }); + + // P-256 public imported as P-384 + assert.throws(() => crypto.createPublicKey({ + key: p256RawPub, format: 'raw-public', + asymmetricKeyType: 'ec', namedCurve: 'P-384', + }), { code: 'ERR_INVALID_ARG_VALUE' }); + + // P-384 public imported as P-256 + assert.throws(() => crypto.createPublicKey({ + key: p384RawPub, format: 'raw-public', + asymmetricKeyType: 'ec', namedCurve: 'P-256', + }), { code: 'ERR_INVALID_ARG_VALUE' }); + + // P-256 private imported as P-384 + assert.throws(() => crypto.createPrivateKey({ + key: p256RawPriv, format: 'raw-private', + asymmetricKeyType: 'ec', namedCurve: 'P-384', + }), { code: 'ERR_INVALID_ARG_VALUE' }); + + // P-384 private imported as P-256 + assert.throws(() => crypto.createPrivateKey({ + key: p384RawPriv, format: 'raw-private', + asymmetricKeyType: 'ec', namedCurve: 'P-256', + }), { code: 'ERR_INVALID_ARG_VALUE' }); +} + +// ML-KEM: -768 and -512 public keys cannot be imported as the other type +if (hasOpenSSL(3, 5)) { + const mlKem512Pub = crypto.createPublicKey( + fixtures.readKey('ml_kem_512_public.pem', 'ascii')); + const mlKem768Pub = crypto.createPublicKey( + fixtures.readKey('ml_kem_768_public.pem', 'ascii')); + + const mlKem512RawPub = mlKem512Pub.export({ format: 'raw-public' }); + const mlKem768RawPub = mlKem768Pub.export({ format: 'raw-public' }); + + assert.throws(() => crypto.createPublicKey({ + key: mlKem512RawPub, format: 'raw-public', asymmetricKeyType: 'ml-kem-768', + }), { code: 'ERR_INVALID_ARG_VALUE' }); + + assert.throws(() => crypto.createPublicKey({ + key: mlKem768RawPub, format: 'raw-public', asymmetricKeyType: 'ml-kem-512', + }), { code: 'ERR_INVALID_ARG_VALUE' }); +} + +// ML-DSA: -44 and -65 public keys cannot be imported as the other type +if (hasOpenSSL(3, 5)) { + const mlDsa44Pub = crypto.createPublicKey( + fixtures.readKey('ml_dsa_44_public.pem', 'ascii')); + const mlDsa65Pub = crypto.createPublicKey( + fixtures.readKey('ml_dsa_65_public.pem', 'ascii')); + + const mlDsa44RawPub = mlDsa44Pub.export({ format: 'raw-public' }); + const mlDsa65RawPub = mlDsa65Pub.export({ format: 'raw-public' }); + + assert.throws(() => crypto.createPublicKey({ + key: mlDsa44RawPub, format: 'raw-public', asymmetricKeyType: 'ml-dsa-65', + }), { code: 'ERR_INVALID_ARG_VALUE' }); + + assert.throws(() => crypto.createPublicKey({ + key: mlDsa65RawPub, format: 'raw-public', asymmetricKeyType: 'ml-dsa-44', + }), { code: 'ERR_INVALID_ARG_VALUE' }); +} + +// SLH-DSA: mismatched key types with different sizes are rejected +if (hasOpenSSL(3, 5)) { + const slh128fPub = crypto.createPublicKey( + fixtures.readKey('slh_dsa_sha2_128f_public.pem', 'ascii')); + const slh192fPub = crypto.createPublicKey( + fixtures.readKey('slh_dsa_sha2_192f_public.pem', 'ascii')); + const slh128fPriv = crypto.createPrivateKey( + fixtures.readKey('slh_dsa_sha2_128f_private.pem', 'ascii')); + const slh192fPriv = crypto.createPrivateKey( + fixtures.readKey('slh_dsa_sha2_192f_private.pem', 'ascii')); + + const rawPub128f = slh128fPub.export({ format: 'raw-public' }); + const rawPub192f = slh192fPub.export({ format: 'raw-public' }); + const rawPriv128f = slh128fPriv.export({ format: 'raw-private' }); + const rawPriv192f = slh192fPriv.export({ format: 'raw-private' }); + + assert.throws(() => crypto.createPublicKey({ + key: rawPub128f, format: 'raw-public', + asymmetricKeyType: 'slh-dsa-sha2-192f', + }), { code: 'ERR_INVALID_ARG_VALUE' }); + + assert.throws(() => crypto.createPublicKey({ + key: rawPub192f, format: 'raw-public', + asymmetricKeyType: 'slh-dsa-sha2-128f', + }), { code: 'ERR_INVALID_ARG_VALUE' }); + + assert.throws(() => crypto.createPrivateKey({ + key: rawPriv128f, format: 'raw-private', + asymmetricKeyType: 'slh-dsa-sha2-192f', + }), { code: 'ERR_INVALID_ARG_VALUE' }); + + assert.throws(() => crypto.createPrivateKey({ + key: rawPriv192f, format: 'raw-private', + asymmetricKeyType: 'slh-dsa-sha2-128f', + }), { code: 'ERR_INVALID_ARG_VALUE' }); +} + +// Passphrase cannot be used with raw formats +{ + const privKeyObj = crypto.createPrivateKey( + fixtures.readKey('ed25519_private.pem', 'ascii')); + + assert.throws(() => privKeyObj.export({ + format: 'raw-private', passphrase: 'test', + }), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + + assert.throws(() => privKeyObj.export({ + format: 'raw-seed', passphrase: 'test', + }), { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); +} + +// raw-seed export is rejected for key types that do not support seeds +{ + const ecPriv = crypto.createPrivateKey( + fixtures.readKey('ec_p256_private.pem', 'ascii')); + assert.throws(() => ecPriv.export({ format: 'raw-seed' }), + { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + + for (const type of ['ed25519', 'ed448', 'x25519', 'x448']) { + const priv = crypto.createPrivateKey( + fixtures.readKey(`${type}_private.pem`, 'ascii')); + assert.throws(() => priv.export({ format: 'raw-seed' }), + { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + } + + if (hasOpenSSL(3, 5)) { + const slhPriv = crypto.createPrivateKey( + fixtures.readKey('slh_dsa_sha2_128f_private.pem', 'ascii')); + assert.throws(() => slhPriv.export({ format: 'raw-seed' }), + { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + } +} + +// raw-private cannot be used for ml-kem and ml-dsa +if (hasOpenSSL(3, 5)) { + for (const type of ['ml-kem-512', 'ml-dsa-44']) { + const priv = crypto.createPrivateKey( + fixtures.readKey(`${type.replaceAll('-', '_')}_private.pem`, 'ascii')); + assert.throws(() => priv.export({ format: 'raw-private' }), + { code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + } +} + +// EC: defaults to uncompressed, can be switched to compressed, both can be imported +{ + const pubKeyObj = crypto.createPublicKey( + fixtures.readKey('ec_p256_public.pem', 'ascii')); + + const defaultExport = pubKeyObj.export({ format: 'raw-public' }); + const compressed = pubKeyObj.export({ format: 'raw-public', type: 'compressed' }); + const uncompressed = pubKeyObj.export({ format: 'raw-public', type: 'uncompressed' }); + + // Default is uncompressed + assert.deepStrictEqual(defaultExport, uncompressed); + + // Compressed starts with 0x02 or 0x03 and is 33 bytes for P-256 + assert.strictEqual(compressed.byteLength, 33); + assert(compressed[0] === 0x02 || compressed[0] === 0x03); + + // Uncompressed starts with 0x04 and is 65 bytes for P-256 + assert.strictEqual(uncompressed.byteLength, 65); + assert.strictEqual(uncompressed[0], 0x04); + + // Both can be imported + const fromCompressed = crypto.createPublicKey({ + key: compressed, format: 'raw-public', + asymmetricKeyType: 'ec', namedCurve: 'P-256', + }); + assert.strictEqual(fromCompressed.equals(pubKeyObj), true); + + const fromUncompressed = crypto.createPublicKey({ + key: uncompressed, format: 'raw-public', + asymmetricKeyType: 'ec', namedCurve: 'P-256', + }); + assert.strictEqual(fromUncompressed.equals(pubKeyObj), true); +} + +// Compressed and uncompressed are the only recognized options +{ + const pubKeyObj = crypto.createPublicKey( + fixtures.readKey('ec_p256_public.pem', 'ascii')); + + assert.throws(() => pubKeyObj.export({ format: 'raw-public', type: 'hybrid' }), + { code: 'ERR_INVALID_ARG_VALUE' }); + + assert.throws(() => pubKeyObj.export({ format: 'raw-public', type: 'invalid' }), + { code: 'ERR_INVALID_ARG_VALUE' }); +} + +// None of the raw types can be used with symmetric key objects +{ + const secretKey = crypto.createSecretKey(Buffer.alloc(32)); + + for (const format of ['raw-public', 'raw-private', 'raw-seed']) { + assert.throws(() => secretKey.export({ format }), + { code: 'ERR_INVALID_ARG_VALUE' }); + } +} + +// Private key objects created from raw-seed or raw-private can be passed to createPublicKey() +{ + // Ed25519 raw-private -> createPublicKey + const edPriv = crypto.createPrivateKey( + fixtures.readKey('ed25519_private.pem', 'ascii')); + const edPub = crypto.createPublicKey( + fixtures.readKey('ed25519_public.pem', 'ascii')); + const rawPriv = edPriv.export({ format: 'raw-private' }); + const importedPriv = crypto.createPrivateKey({ + key: rawPriv, format: 'raw-private', asymmetricKeyType: 'ed25519', + }); + const derivedPub = crypto.createPublicKey(importedPriv); + assert.strictEqual(derivedPub.equals(edPub), true); + // Private key must not be extractable from the derived public key. + assert.throws(() => derivedPub.export({ format: 'pem', type: 'pkcs8' }), + { code: 'ERR_INVALID_ARG_VALUE' }); + assert.throws(() => derivedPub.export({ format: 'der', type: 'pkcs8' }), + { code: 'ERR_INVALID_ARG_VALUE' }); + + // EC raw-private -> createPublicKey + const ecPriv = crypto.createPrivateKey( + fixtures.readKey('ec_p256_private.pem', 'ascii')); + const ecPub = crypto.createPublicKey( + fixtures.readKey('ec_p256_public.pem', 'ascii')); + const ecRawPriv = ecPriv.export({ format: 'raw-private' }); + const ecImportedPriv = crypto.createPrivateKey({ + key: ecRawPriv, format: 'raw-private', + asymmetricKeyType: 'ec', namedCurve: 'P-256', + }); + const ecDerivedPub = crypto.createPublicKey(ecImportedPriv); + assert.strictEqual(ecDerivedPub.equals(ecPub), true); + // Private key must not be extractable from the derived public key. + assert.throws(() => ecDerivedPub.export({ format: 'pem', type: 'pkcs8' }), + { code: 'ERR_INVALID_ARG_VALUE' }); + assert.throws(() => ecDerivedPub.export({ format: 'pem', type: 'sec1' }), + { code: 'ERR_INVALID_ARG_VALUE' }); + + // PQC raw-seed -> createPublicKey + if (hasOpenSSL(3, 5)) { + const mlDsaPriv = crypto.createPrivateKey( + fixtures.readKey('ml_dsa_44_private.pem', 'ascii')); + const mlDsaPub = crypto.createPublicKey( + fixtures.readKey('ml_dsa_44_public.pem', 'ascii')); + const mlDsaRawSeed = mlDsaPriv.export({ format: 'raw-seed' }); + const mlDsaImportedPriv = crypto.createPrivateKey({ + key: mlDsaRawSeed, format: 'raw-seed', asymmetricKeyType: 'ml-dsa-44', + }); + const mlDsaDerivedPub = crypto.createPublicKey(mlDsaImportedPriv); + assert.strictEqual(mlDsaDerivedPub.equals(mlDsaPub), true); + // Private key must not be extractable from the derived public key. + assert.throws(() => mlDsaDerivedPub.export({ format: 'pem', type: 'pkcs8' }), + { code: 'ERR_INVALID_ARG_VALUE' }); + } +} + +// raw-public EC keys that are garbage/not on curve are rejected +{ + const garbage = Buffer.alloc(33, 0xff); + garbage[0] = 0x02; // Valid compressed prefix but invalid point + + assert.throws(() => crypto.createPublicKey({ + key: garbage, format: 'raw-public', + asymmetricKeyType: 'ec', namedCurve: 'P-256', + }), { code: 'ERR_INVALID_ARG_VALUE' }); + + // Totally random garbage + assert.throws(() => crypto.createPublicKey({ + key: Buffer.alloc(10, 0xab), format: 'raw-public', + asymmetricKeyType: 'ec', namedCurve: 'P-256', + }), { code: 'ERR_INVALID_ARG_VALUE' }); +} + +// Unrecognized namedCurve values are rejected +{ + assert.throws(() => crypto.createPublicKey({ + key: Buffer.alloc(33), format: 'raw-public', + asymmetricKeyType: 'ec', namedCurve: 'not-a-curve', + }), { code: 'ERR_CRYPTO_INVALID_CURVE' }); + + assert.throws(() => crypto.createPrivateKey({ + key: Buffer.alloc(32), format: 'raw-private', + asymmetricKeyType: 'ec', namedCurve: 'not-a-curve', + }), { code: 'ERR_CRYPTO_INVALID_CURVE' }); +} + +// x25519, ed25519, x448, and ed448 cannot be used as 'ec' namedCurve values +{ + for (const type of ['ed25519', 'x25519', 'ed448', 'x448']) { + const priv = crypto.createPrivateKey( + fixtures.readKey(`${type}_private.pem`, 'ascii')); + const pub = crypto.createPublicKey( + fixtures.readKey(`${type}_public.pem`, 'ascii')); + + const rawPub = pub.export({ format: 'raw-public' }); + const rawPriv = priv.export({ format: 'raw-private' }); + + // Try to import as EC - must fail + assert.throws(() => crypto.createPublicKey({ + key: rawPub, format: 'raw-public', + asymmetricKeyType: 'ec', namedCurve: type, + }), { code: 'ERR_CRYPTO_INVALID_CURVE' }); + + assert.throws(() => crypto.createPrivateKey({ + key: rawPriv, format: 'raw-private', + asymmetricKeyType: 'ec', namedCurve: type, + }), { code: 'ERR_CRYPTO_INVALID_CURVE' }); + } +} + +// Missing asymmetricKeyType option +{ + assert.throws(() => crypto.createPublicKey({ + key: Buffer.alloc(32), format: 'raw-public', + }), { code: 'ERR_INVALID_ARG_TYPE' }); +} + +// Unknown asymmetricKeyType value +{ + assert.throws(() => crypto.createPublicKey({ + key: Buffer.alloc(32), format: 'raw-public', + asymmetricKeyType: 'unknown', + }), { code: 'ERR_INVALID_ARG_VALUE' }); +} + +// Non-buffer key data +{ + assert.throws(() => crypto.createPublicKey({ + key: 12345, format: 'raw-public', + asymmetricKeyType: 'ec', namedCurve: 'P-256', + }), { code: 'ERR_INVALID_ARG_TYPE' }); +} + +// Missing namedCurve for EC +{ + assert.throws(() => crypto.createPublicKey({ + key: Buffer.alloc(33), format: 'raw-public', + asymmetricKeyType: 'ec', + }), { code: 'ERR_INVALID_ARG_TYPE' }); +} diff --git a/test/parallel/test-crypto-key-objects.js b/test/parallel/test-crypto-key-objects.js index e8359ed6d0362c..6c1c3fd3afa448 100644 --- a/test/parallel/test-crypto-key-objects.js +++ b/test/parallel/test-crypto-key-objects.js @@ -170,6 +170,23 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', assert.strictEqual(derivedPublicKey.asymmetricKeyType, 'rsa'); assert.strictEqual(derivedPublicKey.symmetricKeySize, undefined); + // The private key should not be extractable from the derived public key. + assert.throws(() => derivedPublicKey.export({ format: 'pem', type: 'pkcs8' }), + { code: 'ERR_INVALID_ARG_VALUE' }); + assert.throws(() => derivedPublicKey.export({ format: 'der', type: 'pkcs8' }), + { code: 'ERR_INVALID_ARG_VALUE' }); + // JWK export should only contain public components, no 'd'. + { + const jwkExport = derivedPublicKey.export({ format: 'jwk' }); + assert.strictEqual(jwkExport.kty, 'RSA'); + assert.strictEqual(jwkExport.d, undefined); + assert.strictEqual(jwkExport.dp, undefined); + assert.strictEqual(jwkExport.dq, undefined); + assert.strictEqual(jwkExport.qi, undefined); + assert.strictEqual(jwkExport.p, undefined); + assert.strictEqual(jwkExport.q, undefined); + } + const publicKeyFromJwk = createPublicKey({ key: publicJwk, format: 'jwk' }); assert.strictEqual(publicKeyFromJwk.type, 'public'); assert.strictEqual(publicKeyFromJwk.toString(), '[object KeyObject]'); @@ -415,6 +432,33 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', key.export({ format: 'jwk' }), jwk); } } + + // Raw format round-trip + { + const privKey = createPrivateKey(info.private); + const pubKey = createPublicKey(info.public); + + const rawPriv = privKey.export({ format: 'raw-private' }); + const rawPub = pubKey.export({ format: 'raw-public' }); + assert(Buffer.isBuffer(rawPriv)); + assert(Buffer.isBuffer(rawPub)); + + const importedPriv = createPrivateKey({ + key: rawPriv, format: 'raw-private', asymmetricKeyType: keyType, + }); + assert.strictEqual(importedPriv.type, 'private'); + assert.strictEqual(importedPriv.asymmetricKeyType, keyType); + assert.deepStrictEqual( + importedPriv.export({ format: 'raw-private' }), rawPriv); + + const importedPub = createPublicKey({ + key: rawPub, format: 'raw-public', asymmetricKeyType: keyType, + }); + assert.strictEqual(importedPub.type, 'public'); + assert.strictEqual(importedPub.asymmetricKeyType, keyType); + assert.deepStrictEqual( + importedPub.export({ format: 'raw-public' }), rawPub); + } }); [ @@ -506,8 +550,47 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', delete jwk.d; assert.deepStrictEqual( key.export({ format: 'jwk' }), jwk); + + // Private key material must not be extractable from a derived public key. + assert.throws(() => key.export({ format: 'pem', type: 'pkcs8' }), + { code: 'ERR_INVALID_ARG_VALUE' }); + assert.throws(() => key.export({ format: 'pem', type: 'sec1' }), + { code: 'ERR_INVALID_ARG_VALUE' }); + assert.throws(() => key.export({ format: 'der', type: 'pkcs8' }), + { code: 'ERR_INVALID_ARG_VALUE' }); + assert.throws(() => key.export({ format: 'der', type: 'sec1' }), + { code: 'ERR_INVALID_ARG_VALUE' }); } } + + // Raw format round-trip + { + const privKey = createPrivateKey(info.private); + const pubKey = createPublicKey(info.public); + + const rawPriv = privKey.export({ format: 'raw-private' }); + const rawPub = pubKey.export({ format: 'raw-public' }); + assert(Buffer.isBuffer(rawPriv)); + assert(Buffer.isBuffer(rawPub)); + + const importedPriv = createPrivateKey({ + key: rawPriv, format: 'raw-private', + asymmetricKeyType: keyType, namedCurve, + }); + assert.strictEqual(importedPriv.type, 'private'); + assert.strictEqual(importedPriv.asymmetricKeyType, keyType); + assert.deepStrictEqual( + importedPriv.export({ format: 'raw-private' }), rawPriv); + + const importedPub = createPublicKey({ + key: rawPub, format: 'raw-public', + asymmetricKeyType: keyType, namedCurve, + }); + assert.strictEqual(importedPub.type, 'public'); + assert.strictEqual(importedPub.asymmetricKeyType, keyType); + assert.deepStrictEqual( + importedPub.export({ format: 'raw-public' }), rawPub); + } }); { diff --git a/test/parallel/test-crypto-pqc-key-objects-ml-dsa.js b/test/parallel/test-crypto-pqc-key-objects-ml-dsa.js index 8b9d0e197b9a55..1a832609d3f813 100644 --- a/test/parallel/test-crypto-pqc-key-objects-ml-dsa.js +++ b/test/parallel/test-crypto-pqc-key-objects-ml-dsa.js @@ -61,6 +61,15 @@ for (const [asymmetricKeyType, pubLen] of [ const jwk = key.export({ format: 'jwk' }); assertPublicJwk(jwk); assert.strictEqual(key.equals(createPublicKey({ format: 'jwk', key: jwk })), true); + + // Raw format round-trip + const rawPub = key.export({ format: 'raw-public' }); + assert(Buffer.isBuffer(rawPub)); + assert.strictEqual(rawPub.byteLength, pubLen); + const importedPub = createPublicKey({ + key: rawPub, format: 'raw-public', asymmetricKeyType, + }); + assert.strictEqual(importedPub.equals(key), true); } function assertPrivateKey(key, hasSeed) { @@ -78,6 +87,15 @@ for (const [asymmetricKeyType, pubLen] of [ assertPrivateJwk(jwk); assert.strictEqual(key.equals(createPrivateKey({ format: 'jwk', key: jwk })), true); assert.ok(createPublicKey({ format: 'jwk', key: jwk })); + + // Raw seed round-trip + const rawSeed = key.export({ format: 'raw-seed' }); + assert(Buffer.isBuffer(rawSeed)); + assert.strictEqual(rawSeed.byteLength, 32); + const importedPriv = createPrivateKey({ + key: rawSeed, format: 'raw-seed', asymmetricKeyType, + }); + assert.strictEqual(importedPriv.equals(key), true); } else { assert.throws(() => key.export({ format: 'jwk' }), { code: 'ERR_CRYPTO_OPERATION_FAILED', message: 'key does not have an available seed' }); @@ -172,8 +190,8 @@ for (const [asymmetricKeyType, pubLen] of [ } } else { assert.throws(() => createPrivateKey({ format, key: jwk }), - { code: 'ERR_INVALID_ARG_VALUE', message: /must be one of: 'RSA', 'EC', 'OKP'\. Received 'AKP'/ }); + { code: 'ERR_INVALID_ARG_VALUE', message: /Unsupported key type/ }); assert.throws(() => createPublicKey({ format, key: jwk }), - { code: 'ERR_INVALID_ARG_VALUE', message: /must be one of: 'RSA', 'EC', 'OKP'\. Received 'AKP'/ }); + { code: 'ERR_INVALID_ARG_VALUE', message: /Unsupported key type/ }); } } diff --git a/test/parallel/test-crypto-pqc-key-objects-ml-kem.js b/test/parallel/test-crypto-pqc-key-objects-ml-kem.js index d190a14dad87dd..0f4b226604c965 100644 --- a/test/parallel/test-crypto-pqc-key-objects-ml-kem.js +++ b/test/parallel/test-crypto-pqc-key-objects-ml-kem.js @@ -40,6 +40,14 @@ for (const asymmetricKeyType of ['ml-kem-512', 'ml-kem-768', 'ml-kem-1024']) { key.export({ format: 'der', type: 'spki' }); assert.throws(() => key.export({ format: 'jwk' }), { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE', message: 'Unsupported JWK Key Type.' }); + + // Raw format round-trip + const rawPub = key.export({ format: 'raw-public' }); + assert(Buffer.isBuffer(rawPub)); + const importedPub = createPublicKey({ + key: rawPub, format: 'raw-public', asymmetricKeyType, + }); + assert.strictEqual(importedPub.equals(key), true); } function assertPrivateKey(key, hasSeed) { @@ -49,6 +57,14 @@ for (const asymmetricKeyType of ['ml-kem-512', 'ml-kem-768', 'ml-kem-1024']) { key.export({ format: 'der', type: 'pkcs8' }); if (hasSeed) { assert.strictEqual(key.export({ format: 'pem', type: 'pkcs8' }), keys.private_seed_only); + + // Raw seed round-trip + const rawSeed = key.export({ format: 'raw-seed' }); + assert(Buffer.isBuffer(rawSeed)); + const importedPriv = createPrivateKey({ + key: rawSeed, format: 'raw-seed', asymmetricKeyType, + }); + assert.strictEqual(importedPriv.equals(key), true); } else { assert.strictEqual(key.export({ format: 'pem', type: 'pkcs8' }), keys.private_priv_only); } diff --git a/test/parallel/test-crypto-pqc-key-objects-slh-dsa.js b/test/parallel/test-crypto-pqc-key-objects-slh-dsa.js index 1b612de8b2e582..fdae27f2da797f 100644 --- a/test/parallel/test-crypto-pqc-key-objects-slh-dsa.js +++ b/test/parallel/test-crypto-pqc-key-objects-slh-dsa.js @@ -42,6 +42,14 @@ for (const asymmetricKeyType of [ key.export({ format: 'der', type: 'spki' }); assert.throws(() => key.export({ format: 'jwk' }), { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE', message: 'Unsupported JWK Key Type.' }); + + // Raw format round-trip + const rawPub = key.export({ format: 'raw-public' }); + assert(Buffer.isBuffer(rawPub)); + const importedPub = createPublicKey({ + key: rawPub, format: 'raw-public', asymmetricKeyType, + }); + assert.strictEqual(importedPub.equals(key), true); } function assertPrivateKey(key) { @@ -52,6 +60,14 @@ for (const asymmetricKeyType of [ assert.strictEqual(key.export({ format: 'pem', type: 'pkcs8' }), keys.private); assert.throws(() => key.export({ format: 'jwk' }), { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE', message: 'Unsupported JWK Key Type.' }); + + // Raw format round-trip + const rawPriv = key.export({ format: 'raw-private' }); + assert(Buffer.isBuffer(rawPriv)); + const importedPriv = createPrivateKey({ + key: rawPriv, format: 'raw-private', asymmetricKeyType, + }); + assert.strictEqual(importedPriv.equals(key), true); } if (!hasOpenSSL(3, 5)) { diff --git a/test/parallel/test-crypto-pqc-sign-verify-ml-dsa.js b/test/parallel/test-crypto-pqc-sign-verify-ml-dsa.js index de3937dbc07486..57d6692ca79b55 100644 --- a/test/parallel/test-crypto-pqc-sign-verify-ml-dsa.js +++ b/test/parallel/test-crypto-pqc-sign-verify-ml-dsa.js @@ -67,6 +67,28 @@ for (const [asymmetricKeyType, sigLen] of [ } } } + + // Raw format sign/verify + { + const pubKeyObj = createPublicKey(keys.public); + const privKeyObj = createPrivateKey(keys.private_seed_only); + + const rawPublic = { + key: pubKeyObj.export({ format: 'raw-public' }), + format: 'raw-public', + asymmetricKeyType, + }; + const rawSeed = { + key: privKeyObj.export({ format: 'raw-seed' }), + format: 'raw-seed', + asymmetricKeyType, + }; + + const data = randomBytes(32); + const signature = sign(undefined, data, rawSeed); + assert.strictEqual(signature.byteLength, sigLen); + assert.strictEqual(verify(undefined, data, rawPublic, signature), true); + } } // Test vectors from ietf-cose-dilithium diff --git a/test/parallel/test-crypto-sign-verify.js b/test/parallel/test-crypto-sign-verify.js index a66f0a94efd7c9..1900f244b8491a 100644 --- a/test/parallel/test-crypto-sign-verify.js +++ b/test/parallel/test-crypto-sign-verify.js @@ -422,16 +422,19 @@ assert.throws( { private: fixtures.readKey('ed25519_private.pem', 'ascii'), public: fixtures.readKey('ed25519_public.pem', 'ascii'), algo: null, - sigLen: 64 }, + sigLen: 64, + raw: true }, { private: fixtures.readKey('ed448_private.pem', 'ascii'), public: fixtures.readKey('ed448_public.pem', 'ascii'), algo: null, supportsContext: true, - sigLen: 114 }, + sigLen: 114, + raw: true }, { private: fixtures.readKey('rsa_private_2048.pem', 'ascii'), public: fixtures.readKey('rsa_public_2048.pem', 'ascii'), algo: 'sha1', - sigLen: 256 }, + sigLen: 256, + raw: false }, ].forEach((pair) => { const algo = pair.algo; @@ -458,6 +461,29 @@ assert.throws( assert.strictEqual(crypto.verify(algo, data, pubKeyObj, sig), true); } + if (pair.raw) { + const data = Buffer.from('Hello world'); + const privKeyObj = crypto.createPrivateKey(pair.private); + const pubKeyObj = crypto.createPublicKey(pair.public); + const { asymmetricKeyType } = privKeyObj; + const rawPrivate = { + key: privKeyObj.export({ format: 'raw-private' }), + format: 'raw-private', + asymmetricKeyType, + }; + const rawPublic = { + key: pubKeyObj.export({ format: 'raw-public' }), + format: 'raw-public', + asymmetricKeyType, + }; + + const sig = crypto.sign(algo, data, rawPrivate); + assert.strictEqual(sig.length, pair.sigLen); + + assert.strictEqual(crypto.verify(algo, data, rawPrivate, sig), true); + assert.strictEqual(crypto.verify(algo, data, rawPublic, sig), true); + } + { const data = Buffer.from('Hello world'); const otherData = Buffer.from('Goodbye world'); diff --git a/test/parallel/test-tls-client-auth.js b/test/parallel/test-tls-client-auth.js index 04bf40b9a9e1ac..67aed40914c9fe 100644 --- a/test/parallel/test-tls-client-auth.js +++ b/test/parallel/test-tls-client-auth.js @@ -82,8 +82,9 @@ connect({ }, common.mustCall((err, pair, cleanup) => { assert.strictEqual(pair.server.err.code, 'ERR_SSL_PEER_DID_NOT_RETURN_A_CERTIFICATE'); - const expectedErr = hasOpenSSL(3, 2) ? - 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; + const expectedErr = hasOpenSSL(4, 0) ? + 'ERR_SSL_TLS_ALERT_HANDSHAKE_FAILURE' : hasOpenSSL(3, 2) ? + 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; assert.strictEqual(pair.client.err.code, expectedErr); return cleanup(); diff --git a/test/parallel/test-tls-empty-sni-context.js b/test/parallel/test-tls-empty-sni-context.js index 79f1ddd341d938..e4136ff71e1d52 100644 --- a/test/parallel/test-tls-empty-sni-context.js +++ b/test/parallel/test-tls-empty-sni-context.js @@ -26,8 +26,9 @@ const server = tls.createServer(options, (c) => { }, common.mustNotCall()); c.on('error', common.mustCall((err) => { - const expectedErr = hasOpenSSL(3, 2) ? - 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; + const expectedErr = hasOpenSSL(4, 0) ? + 'ERR_SSL_TLS_ALERT_HANDSHAKE_FAILURE' : hasOpenSSL(3, 2) ? + 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; assert.strictEqual(err.code, expectedErr); })); })); diff --git a/test/parallel/test-tls-psk-circuit.js b/test/parallel/test-tls-psk-circuit.js index 9cbc7a91fb852b..bdf9c86c26a7b6 100644 --- a/test/parallel/test-tls-psk-circuit.js +++ b/test/parallel/test-tls-psk-circuit.js @@ -64,8 +64,9 @@ test({ psk: USERS.UserA, identity: 'UserA' }, { minVersion: 'TLSv1.3' }); test({ psk: USERS.UserB, identity: 'UserB' }); test({ psk: USERS.UserB, identity: 'UserB' }, { minVersion: 'TLSv1.3' }); // Unrecognized user should fail handshake -const expectedHandshakeErr = hasOpenSSL(3, 2) ? - 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; +const expectedHandshakeErr = hasOpenSSL(4, 0) ? + 'ERR_SSL_TLS_ALERT_HANDSHAKE_FAILURE' : hasOpenSSL(3, 2) ? + 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; test({ psk: USERS.UserB, identity: 'UserC' }, {}, expectedHandshakeErr); // Recognized user but incorrect secret should fail handshake const expectedIllegalParameterErr = hasOpenSSL(3, 4) ? 'ERR_SSL_TLSV1_ALERT_DECRYPT_ERROR' : diff --git a/test/parallel/test-tls-set-ciphers.js b/test/parallel/test-tls-set-ciphers.js index 1e63e9376e134b..82a19bb9e90fc2 100644 --- a/test/parallel/test-tls-set-ciphers.js +++ b/test/parallel/test-tls-set-ciphers.js @@ -90,7 +90,9 @@ function test(cciphers, sciphers, cipher, cerr, serr, options) { const U = undefined; let expectedTLSAlertError = 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; -if (hasOpenSSL(3, 2)) { +if (hasOpenSSL(4, 0)) { + expectedTLSAlertError = 'ERR_SSL_TLS_ALERT_HANDSHAKE_FAILURE'; +} else if (hasOpenSSL(3, 2)) { expectedTLSAlertError = 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE'; } diff --git a/test/parallel/test-tls-set-sigalgs.js b/test/parallel/test-tls-set-sigalgs.js index 985ca13ba2ac7d..1bce814f3e8604 100644 --- a/test/parallel/test-tls-set-sigalgs.js +++ b/test/parallel/test-tls-set-sigalgs.js @@ -66,8 +66,9 @@ test('RSA-PSS+SHA256:RSA-PSS+SHA512:ECDSA+SHA256', ['RSA-PSS+SHA256', 'ECDSA+SHA256']); // Do not have shared sigalgs. -const handshakeErr = hasOpenSSL(3, 2) ? - 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; +const handshakeErr = hasOpenSSL(4, 0) ? + 'ERR_SSL_TLS_ALERT_HANDSHAKE_FAILURE' : hasOpenSSL(3, 2) ? + 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; test('RSA-PSS+SHA384', 'ECDSA+SHA256', undefined, handshakeErr, 'ERR_SSL_NO_SHARED_SIGNATURE_ALGORITHMS'); diff --git a/test/parallel/test-webcrypto-digest-turboshake-rfc.js b/test/parallel/test-webcrypto-digest-turboshake-rfc.js new file mode 100644 index 00000000000000..43762fecc2c41e --- /dev/null +++ b/test/parallel/test-webcrypto-digest-turboshake-rfc.js @@ -0,0 +1,399 @@ +'use strict'; + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { subtle } = globalThis.crypto; + +// RFC 9861 Section 5 test vectors + +// Generates a Buffer of length n by repeating the pattern 00 01 02 .. F9 FA. +function ptn(n) { + const buf = Buffer.allocUnsafe(n); + for (let i = 0; i < n; i++) + buf[i] = i % 251; + return buf; +} + +assert.deepStrictEqual( + ptn(17 ** 2).toString('hex'), + '000102030405060708090a0b0c0d0e0f' + + '101112131415161718191a1b1c1d1e1f' + + '202122232425262728292a2b2c2d2e2f' + + '303132333435363738393a3b3c3d3e3f' + + '404142434445464748494a4b4c4d4e4f' + + '505152535455565758595a5b5c5d5e5f' + + '606162636465666768696a6b6c6d6e6f' + + '707172737475767778797a7b7c7d7e7f' + + '808182838485868788898a8b8c8d8e8f' + + '909192939495969798999a9b9c9d9e9f' + + 'a0a1a2a3a4a5a6a7a8a9aaabacadaeaf' + + 'b0b1b2b3b4b5b6b7b8b9babbbcbdbebf' + + 'c0c1c2c3c4c5c6c7c8c9cacbcccdcecf' + + 'd0d1d2d3d4d5d6d7d8d9dadbdcdddedf' + + 'e0e1e2e3e4e5e6e7e8e9eaebecedeeef' + + 'f0f1f2f3f4f5f6f7f8f9fa0001020304' + + '05060708090a0b0c0d0e0f1011121314' + + '15161718191a1b1c1d1e1f2021222324' + + '25', +); + +const turboSHAKE128Vectors = [ + // [input, outputLengthBytes, expected(, domainSeparation)] + [new Uint8Array(0), 32, + '1e415f1c5983aff2169217277d17bb53' + + '8cd945a397ddec541f1ce41af2c1b74c'], + [new Uint8Array(0), 64, + '1e415f1c5983aff2169217277d17bb53' + + '8cd945a397ddec541f1ce41af2c1b74c' + + '3e8ccae2a4dae56c84a04c2385c03c15' + + 'e8193bdf58737363321691c05462c8df'], + [ptn(17 ** 0), 32, + '55cedd6f60af7bb29a4042ae832ef3f5' + + '8db7299f893ebb9247247d856958daa9'], + [ptn(17 ** 1), 32, + '9c97d036a3bac819db70ede0ca554ec6' + + 'e4c2a1a4ffbfd9ec269ca6a111161233'], + [ptn(17 ** 2), 32, + '96c77c279e0126f7fc07c9b07f5cdae1' + + 'e0be60bdbe10620040e75d7223a624d2'], + [ptn(17 ** 3), 32, + 'd4976eb56bcf118520582b709f73e1d6' + + '853e001fdaf80e1b13e0d0599d5fb372'], + [ptn(17 ** 4), 32, + 'da67c7039e98bf530cf7a37830c6664e' + + '14cbab7f540f58403b1b82951318ee5c'], + [ptn(17 ** 5), 32, + 'b97a906fbf83ef7c812517abf3b2d0ae' + + 'a0c4f60318ce11cf103925127f59eecd'], + [ptn(17 ** 6), 32, + '35cd494adeded2f25239af09a7b8ef0c' + + '4d1ca4fe2d1ac370fa63216fe7b4c2b1'], + [Buffer.from('ffffff', 'hex'), 32, + 'bf323f940494e88ee1c540fe660be8a0' + + 'c93f43d15ec006998462fa994eed5dab', 0x01], + [Buffer.from('ff', 'hex'), 32, + '8ec9c66465ed0d4a6c35d13506718d68' + + '7a25cb05c74cca1e42501abd83874a67', 0x06], + [Buffer.from('ffffff', 'hex'), 32, + 'b658576001cad9b1e5f399a9f77723bb' + + 'a05458042d68206f7252682dba3663ed', 0x07], + [Buffer.from('ffffffffffffff', 'hex'), 32, + '8deeaa1aec47ccee569f659c21dfa8e1' + + '12db3cee37b18178b2acd805b799cc37', 0x0b], + [Buffer.from('ff', 'hex'), 32, + '553122e2135e363c3292bed2c6421fa2' + + '32bab03daa07c7d6636603286506325b', 0x30], + [Buffer.from('ffffff', 'hex'), 32, + '16274cc656d44cefd422395d0f9053bd' + + 'a6d28e122aba15c765e5ad0e6eaf26f9', 0x7f], +]; + +const turboSHAKE256Vectors = [ + // [input, outputLengthBytes, expected(, domainSeparation)] + [new Uint8Array(0), 64, + '367a329dafea871c7802ec67f905ae13' + + 'c57695dc2c6663c61035f59a18f8e7db' + + '11edc0e12e91ea60eb6b32df06dd7f00' + + '2fbafabb6e13ec1cc20d995547600db0'], + [ptn(17 ** 0), 64, + '3e1712f928f8eaf1054632b2aa0a246e' + + 'd8b0c378728f60bc970410155c28820e' + + '90cc90d8a3006aa2372c5c5ea176b068' + + '2bf22bae7467ac94f74d43d39b0482e2'], + [ptn(17 ** 1), 64, + 'b3bab0300e6a191fbe61379398359235' + + '78794ea54843f5011090fa2f3780a9e5' + + 'cb22c59d78b40a0fbff9e672c0fbe097' + + '0bd2c845091c6044d687054da5d8e9c7'], + [ptn(17 ** 2), 64, + '66b810db8e90780424c0847372fdc957' + + '10882fde31c6df75beb9d4cd9305cfca' + + 'e35e7b83e8b7e6eb4b78605880116316' + + 'fe2c078a09b94ad7b8213c0a738b65c0'], + [ptn(17 ** 3), 64, + 'c74ebc919a5b3b0dd1228185ba02d29e' + + 'f442d69d3d4276a93efe0bf9a16a7dc0' + + 'cd4eabadab8cd7a5edd96695f5d360ab' + + 'e09e2c6511a3ec397da3b76b9e1674fb'], + [ptn(17 ** 4), 64, + '02cc3a8897e6f4f6ccb6fd46631b1f52' + + '07b66c6de9c7b55b2d1a23134a170afd' + + 'ac234eaba9a77cff88c1f020b7372461' + + '8c5687b362c430b248cd38647f848a1d'], + [ptn(17 ** 5), 64, + 'add53b06543e584b5823f626996aee50' + + 'fe45ed15f20243a7165485acb4aa76b4' + + 'ffda75cedf6d8cdc95c332bd56f4b986' + + 'b58bb17d1778bfc1b1a97545cdf4ec9f'], + [ptn(17 ** 6), 64, + '9e11bc59c24e73993c1484ec66358ef7' + + '1db74aefd84e123f7800ba9c4853e02c' + + 'fe701d9e6bb765a304f0dc34a4ee3ba8' + + '2c410f0da70e86bfbd90ea877c2d6104'], + [Buffer.from('ffffff', 'hex'), 64, + 'd21c6fbbf587fa2282f29aea620175fb' + + '0257413af78a0b1b2a87419ce031d933' + + 'ae7a4d383327a8a17641a34f8a1d1003' + + 'ad7da6b72dba84bb62fef28f62f12424', 0x01], + [Buffer.from('ff', 'hex'), 64, + '738d7b4e37d18b7f22ad1b5313e357e3' + + 'dd7d07056a26a303c433fa3533455280' + + 'f4f5a7d4f700efb437fe6d281405e07b' + + 'e32a0a972e22e63adc1b090daefe004b', 0x06], + [Buffer.from('ffffff', 'hex'), 64, + '18b3b5b7061c2e67c1753a00e6ad7ed7' + + 'ba1c906cf93efb7092eaf27fbeebb755' + + 'ae6e292493c110e48d260028492b8e09' + + 'b5500612b8f2578985ded5357d00ec67', 0x07], + [Buffer.from('ffffffffffffff', 'hex'), 64, + 'bb36764951ec97e9d85f7ee9a67a7718' + + 'fc005cf42556be79ce12c0bde50e5736' + + 'd6632b0d0dfb202d1bbb8ffe3dd74cb0' + + '0834fa756cb03471bab13a1e2c16b3c0', 0x0b], + [Buffer.from('ff', 'hex'), 64, + 'f3fe12873d34bcbb2e608779d6b70e7f' + + '86bec7e90bf113cbd4fdd0c4e2f4625e' + + '148dd7ee1a52776cf77f240514d9ccfc' + + '3b5ddab8ee255e39ee389072962c111a', 0x30], + [Buffer.from('ffffff', 'hex'), 64, + 'abe569c1f77ec340f02705e7d37c9ab7' + + 'e155516e4a6a150021d70b6fac0bb40c' + + '069f9a9828a0d575cd99f9bae435ab1a' + + 'cf7ed9110ba97ce0388d074bac768776', 0x7f], +]; + +const kt128Vectors = [ + // [input, outputLengthBytes, expected(, customization)] + [new Uint8Array(0), 32, + '1ac2d450fc3b4205d19da7bfca1b3751' + + '3c0803577ac7167f06fe2ce1f0ef39e5'], + [new Uint8Array(0), 64, + '1ac2d450fc3b4205d19da7bfca1b3751' + + '3c0803577ac7167f06fe2ce1f0ef39e5' + + '4269c056b8c82e48276038b6d292966c' + + 'c07a3d4645272e31ff38508139eb0a71'], + [ptn(1), 32, + '2bda92450e8b147f8a7cb629e784a058' + + 'efca7cf7d8218e02d345dfaa65244a1f'], + [ptn(17), 32, + '6bf75fa2239198db4772e36478f8e19b' + + '0f371205f6a9a93a273f51df37122888'], + [ptn(17 ** 2), 32, + '0c315ebcdedbf61426de7dcf8fb725d1' + + 'e74675d7f5327a5067f367b108ecb67c'], + [ptn(17 ** 3), 32, + 'cb552e2ec77d9910701d578b457ddf77' + + '2c12e322e4ee7fe417f92c758f0d59d0'], + [ptn(17 ** 4), 32, + '8701045e22205345ff4dda05555cbb5c' + + '3af1a771c2b89baef37db43d9998b9fe'], + [ptn(17 ** 5), 32, + '844d610933b1b9963cbdeb5ae3b6b05c' + + 'c7cbd67ceedf883eb678a0a8e0371682'], + [ptn(17 ** 6), 32, + '3c390782a8a4e89fa6367f72feaaf132' + + '55c8d95878481d3cd8ce85f58e880af8'], + [new Uint8Array(0), 32, + 'fab658db63e94a246188bf7af69a1330' + + '45f46ee984c56e3c3328caaf1aa1a583', ptn(1)], + [Buffer.from('ff', 'hex'), 32, + 'd848c5068ced736f4462159b9867fd4c' + + '20b808acc3d5bc48e0b06ba0a3762ec4', ptn(41)], + [Buffer.from('ffffff', 'hex'), 32, + 'c389e5009ae57120854c2e8c64670ac0' + + '1358cf4c1baf89447a724234dc7ced74', ptn(41 ** 2)], + [Buffer.from('ffffffffffffff', 'hex'), 32, + '75d2f86a2e644566726b4fbcfc5657b9' + + 'dbcf070c7b0dca06450ab291d7443bcf', ptn(41 ** 3)], + [ptn(8191), 32, + '1b577636f723643e990cc7d6a6598374' + + '36fd6a103626600eb8301cd1dbe553d6'], + [ptn(8192), 32, + '48f256f6772f9edfb6a8b661ec92dc93' + + 'b95ebd05a08a17b39ae3490870c926c3'], + [ptn(8192), 32, + '3ed12f70fb05ddb58689510ab3e4d23c' + + '6c6033849aa01e1d8c220a297fedcd0b', ptn(8189)], + [ptn(8192), 32, + '6a7c1b6a5cd0d8c9ca943a4a216cc646' + + '04559a2ea45f78570a15253d67ba00ae', ptn(8190)], +]; + +const kt256Vectors = [ + // [input, outputLengthBytes, expected(, customization)] + [new Uint8Array(0), 64, + 'b23d2e9cea9f4904e02bec06817fc10c' + + 'e38ce8e93ef4c89e6537076af8646404' + + 'e3e8b68107b8833a5d30490aa3348235' + + '3fd4adc7148ecb782855003aaebde4a9'], + [new Uint8Array(0), 128, + 'b23d2e9cea9f4904e02bec06817fc10c' + + 'e38ce8e93ef4c89e6537076af8646404' + + 'e3e8b68107b8833a5d30490aa3348235' + + '3fd4adc7148ecb782855003aaebde4a9' + + 'b0925319d8ea1e121a609821ec19efea' + + '89e6d08daee1662b69c840289f188ba8' + + '60f55760b61f82114c030c97e5178449' + + '608ccd2cd2d919fc7829ff69931ac4d0'], + [ptn(1), 64, + '0d005a194085360217128cf17f91e1f7' + + '1314efa5564539d444912e3437efa17f' + + '82db6f6ffe76e781eaa068bce01f2bbf' + + '81eacb983d7230f2fb02834a21b1ddd0'], + [ptn(17), 64, + '1ba3c02b1fc514474f06c8979978a905' + + '6c8483f4a1b63d0dccefe3a28a2f323e' + + '1cdcca40ebf006ac76ef039715234683' + + '7b1277d3e7faa9c9653b19075098527b'], + [ptn(17 ** 2), 64, + 'de8ccbc63e0f133ebb4416814d4c66f6' + + '91bbf8b6a61ec0a7700f836b086cb029' + + 'd54f12ac7159472c72db118c35b4e6aa' + + '213c6562caaa9dcc518959e69b10f3ba'], + [ptn(17 ** 3), 64, + '647efb49fe9d717500171b41e7f11bd4' + + '91544443209997ce1c2530d15eb1ffbb' + + '598935ef954528ffc152b1e4d731ee26' + + '83680674365cd191d562bae753b84aa5'], + [ptn(17 ** 4), 64, + 'b06275d284cd1cf205bcbe57dccd3ec1' + + 'ff6686e3ed15776383e1f2fa3c6ac8f0' + + '8bf8a162829db1a44b2a43ff83dd89c3' + + 'cf1ceb61ede659766d5ccf817a62ba8d'], + [ptn(17 ** 5), 64, + '9473831d76a4c7bf77ace45b59f1458b' + + '1673d64bcd877a7c66b2664aa6dd149e' + + '60eab71b5c2bab858c074ded81ddce2b' + + '4022b5215935c0d4d19bf511aeeb0772'], + [ptn(17 ** 6), 64, + '0652b740d78c5e1f7c8dcc1777097382' + + '768b7ff38f9a7a20f29f413bb1b3045b' + + '31a5578f568f911e09cf44746da84224' + + 'a5266e96a4a535e871324e4f9c7004da'], + [new Uint8Array(0), 64, + '9280f5cc39b54a5a594ec63de0bb9937' + + '1e4609d44bf845c2f5b8c316d72b1598' + + '11f748f23e3fabbe5c3226ec96c62186' + + 'df2d33e9df74c5069ceecbb4dd10eff6', ptn(1)], + [Buffer.from('ff', 'hex'), 64, + '47ef96dd616f200937aa7847e34ec2fe' + + 'ae8087e3761dc0f8c1a154f51dc9ccf8' + + '45d7adbce57ff64b639722c6a1672e3b' + + 'f5372d87e00aff89be97240756998853', ptn(41)], + [Buffer.from('ffffff', 'hex'), 64, + '3b48667a5051c5966c53c5d42b95de45' + + '1e05584e7806e2fb765eda959074172c' + + 'b438a9e91dde337c98e9c41bed94c4e0' + + 'aef431d0b64ef2324f7932caa6f54969', ptn(41 ** 2)], + [Buffer.from('ffffffffffffff', 'hex'), 64, + 'e0911cc00025e1540831e266d94add9b' + + '98712142b80d2629e643aac4efaf5a3a' + + '30a88cbf4ac2a91a2432743054fbcc98' + + '97670e86ba8cec2fc2ace9c966369724', ptn(41 ** 3)], + [ptn(8191), 64, + '3081434d93a4108d8d8a3305b89682ce' + + 'bedc7ca4ea8a3ce869fbb73cbe4a58ee' + + 'f6f24de38ffc170514c70e7ab2d01f03' + + '812616e863d769afb3753193ba045b20'], + [ptn(8192), 64, + 'c6ee8e2ad3200c018ac87aaa031cdac2' + + '2121b412d07dc6e0dccbb53423747e9a' + + '1c18834d99df596cf0cf4b8dfafb7bf0' + + '2d139d0c9035725adc1a01b7230a41fa'], + [ptn(8192), 64, + '74e47879f10a9c5d11bd2da7e194fe57' + + 'e86378bf3c3f7448eff3c576a0f18c5c' + + 'aae0999979512090a7f348af4260d4de' + + '3c37f1ecaf8d2c2c96c1d16c64b12496', ptn(8189)], + [ptn(8192), 64, + 'f4b5908b929ffe01e0f79ec2f21243d4' + + '1a396b2e7303a6af1d6399cd6c7a0a2d' + + 'd7c4f607e8277f9c9b1cb4ab9ddc59d4' + + 'b92d1fc7558441f1832c3279a4241b8b', ptn(8190)], +]; + +async function checkDigest(name, vectors) { + const isKT = name.startsWith('KT'); + for (const [input, outputLength, expected, ...rest] of vectors) { + const algorithm = { name, outputLength: outputLength * 8 }; + if (rest.length) { + if (isKT) + algorithm.customization = rest[0]; + else + algorithm.domainSeparation = rest[0]; + } + const result = await subtle.digest(algorithm, input); + assert.deepStrictEqual( + Buffer.from(result).toString('hex'), + expected, + ); + } +} + +(async () => { + await checkDigest('TurboSHAKE128', turboSHAKE128Vectors); + + // TurboSHAKE128(M=00^0, D=1F, 10032), last 32 bytes + { + const result = await subtle.digest({ + name: 'TurboSHAKE128', + outputLength: 10032 * 8, + }, new Uint8Array(0)); + assert.deepStrictEqual( + Buffer.from(result).subarray(-32).toString('hex'), + 'a3b9b0385900ce761f22aed548e754da' + + '10a5242d62e8c658e3f3a923a7555607', + ); + } + + await checkDigest('TurboSHAKE256', turboSHAKE256Vectors); + + // TurboSHAKE256(M=00^0, D=1F, 10032), last 32 bytes + { + const result = await subtle.digest({ + name: 'TurboSHAKE256', + outputLength: 10032 * 8, + }, new Uint8Array(0)); + assert.deepStrictEqual( + Buffer.from(result).subarray(-32).toString('hex'), + 'abefa11630c661269249742685ec082f' + + '207265dccf2f43534e9c61ba0c9d1d75', + ); + } + + await checkDigest('KT128', kt128Vectors); + + // KT128(M=00^0, C=00^0, 10032), last 32 bytes + { + const result = await subtle.digest({ + name: 'KT128', + outputLength: 10032 * 8, + }, new Uint8Array(0)); + assert.deepStrictEqual( + Buffer.from(result).subarray(-32).toString('hex'), + 'e8dc563642f7228c84684c898405d3a8' + + '34799158c079b12880277a1d28e2ff6d', + ); + } + + await checkDigest('KT256', kt256Vectors); + + // KT256(M=00^0, C=00^0, 10064), last 64 bytes + { + const result = await subtle.digest({ + name: 'KT256', + outputLength: 10064 * 8, + }, new Uint8Array(0)); + assert.deepStrictEqual( + Buffer.from(result).subarray(-64).toString('hex'), + 'ad4a1d718cf950506709a4c33396139b' + + '4449041fc79a05d68da35f1e453522e0' + + '56c64fe94958e7085f2964888259b993' + + '2752f3ccd855288efee5fcbb8b563069', + ); + } +})().then(common.mustCall()); diff --git a/test/parallel/test-webcrypto-digest-turboshake.js b/test/parallel/test-webcrypto-digest-turboshake.js new file mode 100644 index 00000000000000..0b5586b19286be --- /dev/null +++ b/test/parallel/test-webcrypto-digest-turboshake.js @@ -0,0 +1,181 @@ +'use strict'; + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { subtle } = globalThis.crypto; + +const kSourceData = { + empty: '', + short: '156eea7cc14c56cb94db030a4a9d95ff', + medium: 'b6c8f9df648cd088b70f38e74197b18cb81e1e435' + + '0d50bccb8fb5a7379c87bb2e3d6ed5461ed1e9f36' + + 'f340a3962a446b815b794b4bd43a4403502077b22' + + '56cc807837f3aacd118eb4b9c2baeb897068625ab' + + 'aca193', + long: null +}; + +kSourceData.long = kSourceData.medium.repeat(1024); + +// Test vectors generated with PyCryptodome as a reference implementation +const kDigestedData = { + 'turboshake128': { + empty: '1e415f1c5983aff2169217277d17bb538cd945a397ddec541f1ce41af2c1b74c', + short: 'f8d1ebf3b48b71b0514686090eb25f1de322a00149be9b4dc5f09ac9077cd8a8', + medium: '0d0e7eceb4ae58c3c48f6c2bad56d0f8ff3f887468d3ea55a138aedf395233c0', + long: '5747c06f02ffd9d6c911b6453cc8b717083ab6417319a6ec5c3bb39ed0baf331', + }, + 'turboshake256': { + empty: '367a329dafea871c7802ec67f905ae13c57695dc2c6663c61035f59a18f8e7db' + + '11edc0e12e91ea60eb6b32df06dd7f002fbafabb6e13ec1cc20d995547600db0', + short: 'b47aa0a5b76caf9b10cfaeff036df0cdb86362d2bd036a2fee0cd0d74e79279c' + + 'b9c57a70da1e3dd9e126a469857ba4c82b0efb3ae06d1a3781a6f102c3eb3a1d', + medium: '7fa19fd828762d2dba6eea8407d1fb04302b5a4f1ca3d00b3672c1e3b3331d18' + + '925b7ec380f3f04673a164dab04d2a0c5c12818046284c38d286645741a8aa3e', + long: '12d0b90c08f588710733cc07f0a2d6ab0795a4a24904c111062226fcd9d5dcb2' + + '1d6b5b848c9aebbcab221f031e9b4ea71e099ec785e822b1b83e73d0750ca1a7', + }, + 'kt128': { + empty: '1ac2d450fc3b4205d19da7bfca1b37513c0803577ac7167f06fe2ce1f0ef39e5', + short: '4719a2ac1dc1c592521cf201df3f476ea496fe461abe9a2604527f6bec047579', + medium: '00f3add71679681720b925416953897ac62cfae97060dd5f2e1641a076580cc9', + long: 'c05805c2736deb4be3fca6e3717b9af0aa18ceeaaeeab66b328a3ffebf0a814d', + }, + 'kt256': { + empty: 'b23d2e9cea9f4904e02bec06817fc10ce38ce8e93ef4c89e6537076af8646404' + + 'e3e8b68107b8833a5d30490aa33482353fd4adc7148ecb782855003aaebde4a9', + short: '6709e5e312f2dee4547ecb0ab7d42728ba57985983731afbd6c2a0676c522274' + + 'cf9153064ee07982129d3f58d4dbe00050eb28b392559bdb020aca302b7a28cb', + medium: '9078b6ff78e9c4b3c8ff49e5b9f337b36cc6d6749d23985035886d993db69f7e' + + '05fea97125e0889130da09fc5837761f7793e3e44d85be1ee1f6af7f4a1f50cb', + long: '41f83b7c7d02fc6d98f1fa1474d765caff4673f90cd7204894d7da72d97403b6' + + '2fe5c4bae2bf0ce3dcd51e80c98bd25ce5fe54040259d9466b67f1517dac0712', + }, +}; + +function buildAlg(name) { + const lower = name.toLowerCase(); + if (lower.startsWith('turboshake')) { + const outputLength = lower === 'turboshake128' ? 256 : 512; + return { name, outputLength }; + } + if (lower.startsWith('kt')) { + const outputLength = lower === 'kt128' ? 256 : 512; + return { name, outputLength }; + } + return name; +} + +async function testDigest(size, alg) { + const digest = await subtle.digest( + alg, + Buffer.from(kSourceData[size], 'hex')); + + assert.strictEqual( + Buffer.from(digest).toString('hex'), + kDigestedData[(alg.name || alg).toLowerCase()][size]); +} + +// Known-answer tests +(async function() { + const variations = []; + Object.keys(kSourceData).forEach((size) => { + Object.keys(kDigestedData).forEach((alg) => { + const upCase = alg.toUpperCase(); + const downCase = alg.toLowerCase(); + const mixedCase = upCase.slice(0, 1) + downCase.slice(1); + + variations.push(testDigest(size, buildAlg(upCase))); + variations.push(testDigest(size, buildAlg(downCase))); + variations.push(testDigest(size, buildAlg(mixedCase))); + }); + }); + + await Promise.all(variations); +})().then(common.mustCall()); + +// Edge cases: zero-length output rejects +(async () => { + await assert.rejects( + subtle.digest({ name: 'TurboSHAKE128', outputLength: 0 }, Buffer.alloc(1)), + { + name: 'OperationError', + message: 'Invalid TurboShakeParams outputLength', + }); + + await assert.rejects( + subtle.digest({ name: 'KT128', outputLength: 0 }, Buffer.alloc(1)), + { + name: 'OperationError', + message: 'Invalid KangarooTwelveParams outputLength', + }); +})().then(common.mustCall()); + +// Edge case: non-byte-aligned outputLength rejects +(async () => { + await assert.rejects( + subtle.digest({ name: 'TurboSHAKE128', outputLength: 7 }, Buffer.alloc(1)), + { + name: 'OperationError', + message: 'Invalid TurboShakeParams outputLength', + }); + + await assert.rejects( + subtle.digest({ name: 'KT128', outputLength: 7 }, Buffer.alloc(1)), + { + name: 'OperationError', + message: 'Invalid KangarooTwelveParams outputLength', + }); +})().then(common.mustCall()); + +// TurboSHAKE domain separation byte +(async () => { + // Domain separation 0x07 should produce different output than default 0x1F + const [d07, d1f] = await Promise.all([ + subtle.digest( + { name: 'TurboSHAKE128', outputLength: 256, domainSeparation: 0x07 }, + Buffer.alloc(0)), + subtle.digest( + { name: 'TurboSHAKE128', outputLength: 256 }, + Buffer.alloc(0)), + ]); + assert.notDeepStrictEqual( + new Uint8Array(d07), + new Uint8Array(d1f)); + + // Verify D=0x07 against known vector + assert.strictEqual( + Buffer.from(d07).toString('hex'), + '5a223ad30b3b8c66a243048cfced430f54e7529287d15150b973133adfac6a2f'); +})().then(common.mustCall()); + +// KT128 with customization string +(async () => { + const digest = await subtle.digest( + { name: 'KT128', outputLength: 256, customization: Buffer.from('test') }, + Buffer.from('hello')); + assert(digest instanceof ArrayBuffer); + assert.strictEqual(digest.byteLength, 32); +})().then(common.mustCall()); + +// TurboSHAKE domain separation out of range +(async () => { + await assert.rejects( + subtle.digest( + { name: 'TurboSHAKE128', outputLength: 256, domainSeparation: 0x00 }, + Buffer.alloc(0)), + { + name: 'OperationError', + }); + await assert.rejects( + subtle.digest( + { name: 'TurboSHAKE128', outputLength: 256, domainSeparation: 0x80 }, + Buffer.alloc(0)), + { + name: 'OperationError', + }); +})().then(common.mustCall()); diff --git a/test/parallel/test-webcrypto-export-import-cfrg.js b/test/parallel/test-webcrypto-export-import-cfrg.js index 9cfc6e9e4ecf5a..c6e3509a4362bc 100644 --- a/test/parallel/test-webcrypto-export-import-cfrg.js +++ b/test/parallel/test-webcrypto-export-import-cfrg.js @@ -389,9 +389,10 @@ async function testImportJwk({ name, publicUsages, privateUsages }, extractable) async function testImportRaw({ name, publicUsages }) { const jwk = keyData[name].jwk; + const rawKeyData = Buffer.from(jwk.x, 'base64url'); const publicKey = await subtle.importKey( 'raw', - Buffer.from(jwk.x, 'base64url'), + rawKeyData, { name }, true, publicUsages); @@ -400,6 +401,10 @@ async function testImportRaw({ name, publicUsages }) { assert.strictEqual(publicKey.algorithm.name, name); assert.strictEqual(publicKey.algorithm, publicKey.algorithm); assert.strictEqual(publicKey.usages, publicKey.usages); + + // Test raw export round-trip + const exported = await subtle.exportKey('raw', publicKey); + assert.deepStrictEqual(Buffer.from(exported), rawKeyData); } (async function() { diff --git a/test/parallel/test-webcrypto-export-import-ec.js b/test/parallel/test-webcrypto-export-import-ec.js index a2e9df73bc2926..98d0d7d3342ccb 100644 --- a/test/parallel/test-webcrypto-export-import-ec.js +++ b/test/parallel/test-webcrypto-export-import-ec.js @@ -356,14 +356,16 @@ async function testImportJwk( async function testImportRaw({ name, publicUsages }, namedCurve) { const jwk = keyData[namedCurve].jwk; + const uncompressedRaw = Buffer.concat([ + Buffer.alloc(1, 0x04), + Buffer.from(jwk.x, 'base64url'), + Buffer.from(jwk.y, 'base64url'), + ]); + const [publicKey] = await Promise.all([ subtle.importKey( 'raw', - Buffer.concat([ - Buffer.alloc(1, 0x04), - Buffer.from(jwk.x, 'base64url'), - Buffer.from(jwk.y, 'base64url'), - ]), + uncompressedRaw, { name, namedCurve }, true, publicUsages), subtle.importKey( @@ -382,6 +384,10 @@ async function testImportRaw({ name, publicUsages }, namedCurve) { assert.strictEqual(publicKey.algorithm.namedCurve, namedCurve); assert.strictEqual(publicKey.algorithm, publicKey.algorithm); assert.strictEqual(publicKey.usages, publicKey.usages); + + // Test raw export round-trip (always uncompressed) + const exported = await subtle.exportKey('raw', publicKey); + assert.deepStrictEqual(Buffer.from(exported), uncompressedRaw); } (async function() { diff --git a/test/parallel/test-webcrypto-export-import-ml-dsa.js b/test/parallel/test-webcrypto-export-import-ml-dsa.js index 8fd8abb6f44d76..ebd61c67f55d32 100644 --- a/test/parallel/test-webcrypto-export-import-ml-dsa.js +++ b/test/parallel/test-webcrypto-export-import-ml-dsa.js @@ -515,8 +515,6 @@ async function testImportRawSeed({ name, privateUsages }, extractable) { const key = keyObject.toCryptoKey({ name }, true, privateUsages); await assert.rejects(subtle.exportKey('pkcs8', key), (err) => { assert.strictEqual(err.name, 'OperationError'); - assert.strictEqual(err.cause.code, 'ERR_CRYPTO_OPERATION_FAILED'); - assert.strictEqual(err.cause.message, 'Failed to get raw seed'); return true; }); } diff --git a/test/parallel/test-webcrypto-export-import-ml-kem.js b/test/parallel/test-webcrypto-export-import-ml-kem.js index 698de3cc1bcb97..50f3444ce87899 100644 --- a/test/parallel/test-webcrypto-export-import-ml-kem.js +++ b/test/parallel/test-webcrypto-export-import-ml-kem.js @@ -317,8 +317,6 @@ async function testImportRawSeed({ name, privateUsages }, extractable) { const key = keyObject.toCryptoKey({ name }, true, privateUsages); await assert.rejects(subtle.exportKey('pkcs8', key), (err) => { assert.strictEqual(err.name, 'OperationError'); - assert.strictEqual(err.cause.code, 'ERR_CRYPTO_OPERATION_FAILED'); - assert.strictEqual(err.cause.message, 'Failed to get raw seed'); return true; }); } 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" ] } }