From f25cb5990de3077b37b36f216a1ed7c8ab530332 Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Sun, 18 Oct 2026 00:30:01 +0000 Subject: [PATCH 1/2] test: update WPT for WebCryptoAPI to e15690d276 --- .../wpt/WebCryptoAPI/digest/digest.js | 4 +- .../wpt/WebCryptoAPI/encrypt_decrypt/aes.js | 8 +- .../wpt/WebCryptoAPI/encrypt_decrypt/rsa.js | 8 +- .../okp_fixed_length.https.any.js | 127 ++++++++++++++++++ .../okp_malformed_asn1.https.any.js | 99 ++++++++++++++ .../modern-algos/digest/xof_digest.js | 4 +- .../wpt/WebCryptoAPI/sign_verify/mac.js | 8 +- .../wpt/WebCryptoAPI/sign_verify/signature.js | 8 +- .../aes_kw_unwrapKey_failures.https.any.js | 38 ++++++ test/fixtures/wpt/versions.json | 2 +- 10 files changed, 295 insertions(+), 11 deletions(-) create mode 100644 test/fixtures/wpt/WebCryptoAPI/import_export/okp_fixed_length.https.any.js create mode 100644 test/fixtures/wpt/WebCryptoAPI/import_export/okp_malformed_asn1.https.any.js create mode 100644 test/fixtures/wpt/WebCryptoAPI/wrapKey_unwrapKey/aes_kw_unwrapKey_failures.https.any.js diff --git a/test/fixtures/wpt/WebCryptoAPI/digest/digest.js b/test/fixtures/wpt/WebCryptoAPI/digest/digest.js index 63dfd86ebf1c..ab1453c58aee 100644 --- a/test/fixtures/wpt/WebCryptoAPI/digest/digest.js +++ b/test/fixtures/wpt/WebCryptoAPI/digest/digest.js @@ -59,7 +59,9 @@ function runDigestTests(subtle, sourceData, getVectors) { promise_test(function () { var buffer = new Uint8Array(sourceData[size]); var algorithm = withNameGetter(vector.algorithm, function () { - buffer.buffer.transfer(); + if (!buffer.buffer.detached) { + buffer.buffer.transfer(); + } return algorithmName(vector.algorithm); }); return subtle.digest(algorithm, buffer).then(function (result) { diff --git a/test/fixtures/wpt/WebCryptoAPI/encrypt_decrypt/aes.js b/test/fixtures/wpt/WebCryptoAPI/encrypt_decrypt/aes.js index 9caecf695cf7..57567fd4975f 100644 --- a/test/fixtures/wpt/WebCryptoAPI/encrypt_decrypt/aes.js +++ b/test/fixtures/wpt/WebCryptoAPI/encrypt_decrypt/aes.js @@ -102,7 +102,9 @@ function run_test() { var operation = subtle.encrypt({ ...vector.algorithm, get name() { - plaintext.buffer.transfer(); + if (!plaintext.buffer.detached) { + plaintext.buffer.transfer(); + } return vector.algorithm.name; } }, vector.key, plaintext) @@ -244,7 +246,9 @@ function run_test() { var operation = subtle.decrypt({ ...vector.algorithm, get name() { - ciphertext.buffer.transfer(); + if (!ciphertext.buffer.detached) { + ciphertext.buffer.transfer(); + } return vector.algorithm.name; } }, vector.key, ciphertext) diff --git a/test/fixtures/wpt/WebCryptoAPI/encrypt_decrypt/rsa.js b/test/fixtures/wpt/WebCryptoAPI/encrypt_decrypt/rsa.js index 951ffd56e581..d866b7b47791 100644 --- a/test/fixtures/wpt/WebCryptoAPI/encrypt_decrypt/rsa.js +++ b/test/fixtures/wpt/WebCryptoAPI/encrypt_decrypt/rsa.js @@ -124,7 +124,9 @@ function run_test() { var operation = subtle.decrypt({ ...vector.algorithm, get name() { - ciphertext.buffer.transfer(); + if (!ciphertext.buffer.detached) { + ciphertext.buffer.transfer(); + } return vector.algorithm.name; } }, vector.privateKey, ciphertext) @@ -337,7 +339,9 @@ function run_test() { var operation = subtle.encrypt({ ...vector.algorithm, get name() { - plaintext.buffer.transfer(); + if (!plaintext.buffer.detached) { + plaintext.buffer.transfer(); + } return vector.algorithm.name; } }, vector.publicKey, plaintext) diff --git a/test/fixtures/wpt/WebCryptoAPI/import_export/okp_fixed_length.https.any.js b/test/fixtures/wpt/WebCryptoAPI/import_export/okp_fixed_length.https.any.js new file mode 100644 index 000000000000..66123b24c793 --- /dev/null +++ b/test/fixtures/wpt/WebCryptoAPI/import_export/okp_fixed_length.https.any.js @@ -0,0 +1,127 @@ +// META: title=WebCryptoAPI: OKP imports preserve fixed-length key material + +const message = bytes("010203"); +const peer = { + pkcs8: bytes("302e020100300506032b656e0422042038adaddfe94506a579a2cabcc9a50794b464689e065ed3e86b76e1ef1011f01a"), + spki: bytes("302a300506032b656e032100a1d178cd56e2528f7b8d82ba2e3772b219c944f0e8fbfb0d1ca3951d0ce00b13"), +}; + +const vectors = [ + { + algorithm: "Ed25519", + name: "a leading-zero private key", + pkcs8: "302e020100300506032b6570042204200007070707070707070707070707070707070707070707070707070707070707", + raw: "ff812f84e53570c36ae9479aad71e649c3cc4a04a6768632150febdd66a238a8", + signature: "94ff57055f3b90728d704bfbf19a3e4b49806f98a10384ae01ed5c700e6d482a12dd5f6372ce160f5cf94e8cd53555a7b525658d8f4126d463fa4200578d710e", + spki: "302a300506032b6570032100ff812f84e53570c36ae9479aad71e649c3cc4a04a6768632150febdd66a238a8", + }, + { + algorithm: "Ed25519", + name: "an all-zero private key", + pkcs8: "302e020100300506032b6570042204200000000000000000000000000000000000000000000000000000000000000000", + raw: "3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29", + signature: "2a26779ba6cbb5e54292257f725af112b273c38728329682d99ed81ba6d7670350ae4cc53c5456fa437128d19298a5d949ab46e3d41ab3dbcfb0b35c895e9304", + spki: "302a300506032b65700321003b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29", + }, + { + algorithm: "Ed25519", + name: "a leading-zero public key", + pkcs8: "302e020100300506032b6570042204204dca5ad94292a81e64a2c780e98d5469f53523107d9b00a63e37b5097786481f", + raw: "00009314998a59a276e35cac5d39d9cf642b5add8fc428baca5d18a7b30cda40", + signature: "b6a45af39f7545b0fb894a583c5156e027a97cb09e46a999ffb269aec6fc0d0af76169c7771d09431c08ee22cb99c8966fa261097c843855cd2005678c88710a", + spki: "302a300506032b657003210000009314998a59a276e35cac5d39d9cf642b5add8fc428baca5d18a7b30cda40", + }, + { + algorithm: "X25519", + name: "a leading-zero private key", + pkcs8: "302e020100300506032b656e042204200007070707070707070707070707070707070707070707070707070707070707", + raw: "13be4feaeaf204c7fd3358fc9c00721881d174278128227ec674f37f7fe97b6d", + secret: "51eafdc7ed3371472837de1aca43bee0b198aa02d1f619eb26ed1d1045bcd936", + spki: "302a300506032b656e03210013be4feaeaf204c7fd3358fc9c00721881d174278128227ec674f37f7fe97b6d", + }, + { + algorithm: "X25519", + name: "an all-zero private key", + pkcs8: "302e020100300506032b656e042204200000000000000000000000000000000000000000000000000000000000000000", + raw: "2fe57da347cd62431528daac5fbb290730fff684afc4cfc2ed90995f58cb3b74", + secret: "caa5f83ef211ee3ce3ae25b011f85ba0c55384769049a04bfc19770373c6df30", + spki: "302a300506032b656e0321002fe57da347cd62431528daac5fbb290730fff684afc4cfc2ed90995f58cb3b74", + }, + { + algorithm: "X25519", + name: "a leading-zero public key", + pkcs8: "302e020100300506032b656e0422042031821845335f559760a05a2e1d3b0fd6e603b6a2640d969cd29e28ea9c5dddd7", + raw: "00a3785afdc7bb07fa13a4b075f00576cba6946a70cd6ad20b25c949eae6f636", + secret: "83b1b1eafc349edfc4401760c9949bc2fa95fceebd51cd7d87c2fd4a6ac85d21", + spki: "302a300506032b656e03210000a3785afdc7bb07fa13a4b075f00576cba6946a70cd6ad20b25c949eae6f636", + }, +]; + +function bytes(hex) { + return new Uint8Array(hex.match(/../g).map(value => Number.parseInt(value, 16))); +} + +function base64url(value) { + return btoa(String.fromCharCode(...value)).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, ""); +} + +function privateBytes(pkcs8) { + return pkcs8.slice(-32); +} + +for (const vector of vectors) { + promise_test(async () => { + const pkcs8 = bytes(vector.pkcs8); + const privateUsages = vector.algorithm === "Ed25519" ? ["sign"] : ["deriveBits"]; + const privateKey = await crypto.subtle.importKey("pkcs8", pkcs8, vector.algorithm, true, privateUsages); + + assert_array_equals( + new Uint8Array(await crypto.subtle.exportKey("pkcs8", privateKey)), + pkcs8, + "PKCS#8 round trip", + ); + const jwk = await crypto.subtle.exportKey("jwk", privateKey); + assert_equals(jwk.d, base64url(privateBytes(pkcs8)), "JWK private key bytes"); + assert_equals(jwk.x, base64url(bytes(vector.raw)), "JWK public key bytes"); + + const spki = bytes(vector.spki); + const publicUsages = vector.algorithm === "Ed25519" ? ["verify"] : []; + const publicKey = await crypto.subtle.importKey("spki", spki, vector.algorithm, true, publicUsages); + assert_array_equals( + new Uint8Array(await crypto.subtle.exportKey("spki", publicKey)), + spki, + "SPKI round trip", + ); + assert_array_equals( + new Uint8Array(await crypto.subtle.exportKey("raw", publicKey)), + bytes(vector.raw), + "raw public key", + ); + + if (vector.algorithm === "Ed25519") { + const signature = new Uint8Array(await crypto.subtle.sign("Ed25519", privateKey, message)); + assert_true( + await crypto.subtle.verify("Ed25519", publicKey, signature, message), + "generated signature verifies", + ); + assert_true( + await crypto.subtle.verify("Ed25519", publicKey, bytes(vector.signature), message), + "independent reference signature verifies", + ); + return; + } + + const peerPublic = await crypto.subtle.importKey("spki", peer.spki, "X25519", false, []); + assert_array_equals( + new Uint8Array(await crypto.subtle.deriveBits({name: "X25519", public: peerPublic}, privateKey, 256)), + bytes(vector.secret), + "independent reference shared secret", + ); + const peerPrivate = await crypto.subtle.importKey("pkcs8", peer.pkcs8, "X25519", false, ["deriveBits"]); + assert_array_equals( + new Uint8Array(await crypto.subtle.deriveBits({name: "X25519", public: publicKey}, peerPrivate, 256)), + bytes(vector.secret), + "imported public key shared secret", + ); + }, `${vector.algorithm} preserves ${vector.name}`); +} diff --git a/test/fixtures/wpt/WebCryptoAPI/import_export/okp_malformed_asn1.https.any.js b/test/fixtures/wpt/WebCryptoAPI/import_export/okp_malformed_asn1.https.any.js new file mode 100644 index 000000000000..adfd9f68c1b7 --- /dev/null +++ b/test/fixtures/wpt/WebCryptoAPI/import_export/okp_malformed_asn1.https.any.js @@ -0,0 +1,99 @@ +// META: title=WebCryptoAPI: OKP imports reject malformed ASN.1 + +const vectors = [ + { + algorithm: "Ed25519", + pkcs8: "302e020100300506032b6570042204200007070707070707070707070707070707070707070707070707070707070707", + raw: "ff812f84e53570c36ae9479aad71e649c3cc4a04a6768632150febdd66a238a8", + leadingZeroRaw: "00009314998a59a276e35cac5d39d9cf642b5add8fc428baca5d18a7b30cda40", + spki: "302a300506032b6570032100ff812f84e53570c36ae9479aad71e649c3cc4a04a6768632150febdd66a238a8", + }, + { + algorithm: "X25519", + pkcs8: "302e020100300506032b656e042204200007070707070707070707070707070707070707070707070707070707070707", + raw: "13be4feaeaf204c7fd3358fc9c00721881d174278128227ec674f37f7fe97b6d", + leadingZeroRaw: "00a3785afdc7bb07fa13a4b075f00576cba6946a70cd6ad20b25c949eae6f636", + spki: "302a300506032b656e03210013be4feaeaf204c7fd3358fc9c00721881d174278128227ec674f37f7fe97b6d", + }, +]; + +function bytes(hex) { + return new Uint8Array(hex.match(/../g).map(value => Number.parseInt(value, 16))); +} + +function spki(algorithm, raw) { + const oid = algorithm === "Ed25519" ? 0x70 : 0x6e; + return new Uint8Array([ + 0x30, raw.length + 10, + 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, oid, + 0x03, raw.length + 1, 0x00, + ...raw, + ]); +} + +for (const vector of vectors) { + for (const format of ["pkcs8", "spki"]) { + promise_test(async test => { + const encoded = bytes(vector[format]); + const usages = format === "pkcs8" + ? (vector.algorithm === "Ed25519" ? ["sign"] : ["deriveBits"]) + : (vector.algorithm === "Ed25519" ? ["verify"] : []); + await promise_rejects_dom( + test, + "DataError", + crypto.subtle.importKey(format, encoded.slice(0, -1), vector.algorithm, true, usages), + "truncated input", + ); + const invalidTag = encoded.slice(); + invalidTag[0] = 0x31; + await promise_rejects_dom( + test, + "DataError", + crypto.subtle.importKey(format, invalidTag, vector.algorithm, true, usages), + "invalid outer ASN.1 tag", + ); + }, `${vector.algorithm} rejects malformed ${format}`); + } +} + +for (const vector of vectors) { + const usages = vector.algorithm === "Ed25519" ? ["verify"] : []; + const raw = bytes(vector.raw); + const leadingZeroRaw = bytes(vector.leadingZeroRaw); + + promise_test(async () => { + const key = await crypto.subtle.importKey( + "spki", + spki(vector.algorithm, leadingZeroRaw), + vector.algorithm, + true, + usages, + ); + assert_array_equals( + new Uint8Array(await crypto.subtle.exportKey("raw", key)), + leadingZeroRaw, + "valid fixed-width public value", + ); + }, `${vector.algorithm} accepts a valid 32-byte leading-zero SPKI public value`); + + const invalidPublicValues = [ + { name: "31-byte", raw: raw.slice(1) }, + { name: "33-byte", raw: new Uint8Array([...raw, 0x01]) }, + { name: "leading-zero 33-byte", raw: new Uint8Array([0x00, ...raw]) }, + ]; + for (const invalid of invalidPublicValues) { + promise_test(async test => { + await promise_rejects_dom( + test, + "DataError", + crypto.subtle.importKey( + "spki", + spki(vector.algorithm, invalid.raw), + vector.algorithm, + true, + usages, + ), + ); + }, `${vector.algorithm} rejects a ${invalid.name} SPKI public value`); + } +} diff --git a/test/fixtures/wpt/WebCryptoAPI/modern-algos/digest/xof_digest.js b/test/fixtures/wpt/WebCryptoAPI/modern-algos/digest/xof_digest.js index f5ef5846b796..124d74f63927 100644 --- a/test/fixtures/wpt/WebCryptoAPI/modern-algos/digest/xof_digest.js +++ b/test/fixtures/wpt/WebCryptoAPI/modern-algos/digest/xof_digest.js @@ -98,7 +98,9 @@ function runXofDigestTests(subtle, options) { var duringCallParams = { get name() { // Transfer the buffer while calling digest - buffer.buffer.transfer(); + if (!buffer.buffer.detached) { + buffer.buffer.transfer(); + } return alg; }, outputLength: outputLength, diff --git a/test/fixtures/wpt/WebCryptoAPI/sign_verify/mac.js b/test/fixtures/wpt/WebCryptoAPI/sign_verify/mac.js index 27787c91d8a2..fd92e0c4949e 100644 --- a/test/fixtures/wpt/WebCryptoAPI/sign_verify/mac.js +++ b/test/fixtures/wpt/WebCryptoAPI/sign_verify/mac.js @@ -107,7 +107,9 @@ function runMacTests(options) { promise_test(function(test) { var signature = copyBuffer(vector.signature); var operation = subtle.verify(operationAlgorithm(vector, function() { - signature.buffer.transfer(); + if (!signature.buffer.detached) { + signature.buffer.transfer(); + } return options.operationAlgorithm(vector).name; }), vector.key, signature, vector.plaintext) .then(function(is_verified) { @@ -212,7 +214,9 @@ function runMacTests(options) { promise_test(function(test) { var plaintext = copyBuffer(vector.plaintext); var operation = subtle.verify(operationAlgorithm(vector, function() { - plaintext.buffer.transfer(); + if (!plaintext.buffer.detached) { + plaintext.buffer.transfer(); + } return options.operationAlgorithm(vector).name; }), vector.key, vector.signature, plaintext) .then(function(is_verified) { diff --git a/test/fixtures/wpt/WebCryptoAPI/sign_verify/signature.js b/test/fixtures/wpt/WebCryptoAPI/sign_verify/signature.js index 2d734e1d973e..1c6efa0bd223 100644 --- a/test/fixtures/wpt/WebCryptoAPI/sign_verify/signature.js +++ b/test/fixtures/wpt/WebCryptoAPI/sign_verify/signature.js @@ -125,7 +125,9 @@ function runSignatureTests(options) { const key = await publicKey(vector); const signature = copyBuffer(vector.signature); const duringCallAlgorithm = algorithmWithNameGetter(vector, function () { - signature.buffer.transfer(); + if (!signature.buffer.detached) { + signature.buffer.transfer(); + } return algorithmName(vector); }); const isVerified = await subtle.verify( @@ -174,7 +176,9 @@ function runSignatureTests(options) { const key = await publicKey(vector); const data = copyBuffer(vector.data); const duringCallAlgorithm = algorithmWithNameGetter(vector, function () { - data.buffer.transfer(); + if (!data.buffer.detached) { + data.buffer.transfer(); + } return algorithmName(vector); }); const isVerified = await subtle.verify( diff --git a/test/fixtures/wpt/WebCryptoAPI/wrapKey_unwrapKey/aes_kw_unwrapKey_failures.https.any.js b/test/fixtures/wpt/WebCryptoAPI/wrapKey_unwrapKey/aes_kw_unwrapKey_failures.https.any.js new file mode 100644 index 000000000000..aa198151a1fe --- /dev/null +++ b/test/fixtures/wpt/WebCryptoAPI/wrapKey_unwrapKey/aes_kw_unwrapKey_failures.https.any.js @@ -0,0 +1,38 @@ +// META: title=WebCryptoAPI: unwrapKey() with invalid AES-KW wrapped keys + +// Test vector from RFC 3394, section 4.1: wrap 128 bits of key data with a +// 128-bit KEK. +var kekBytes = new Uint8Array([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f]); +var keyBytes = new Uint8Array([0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]); +var wrappedKeyBytes = new Uint8Array([0x1f, 0xa6, 0x8b, 0x0a, 0x81, 0x12, 0xb4, 0x47, + 0xae, 0xf3, 0x4b, 0xd8, 0xfb, 0x5a, 0x7b, 0x82, 0x9d, 0x3e, 0x86, 0x23, + 0x71, 0xd2, 0xcf, 0xe5]); + +function importKek() { + return crypto.subtle.importKey("raw", kekBytes, "AES-KW", false, ["unwrapKey"]); +} + +function unwrapKey(kek, wrappedKey) { + return crypto.subtle.unwrapKey("raw", wrappedKey, kek, "AES-KW", "AES-CBC", true, ["encrypt", "decrypt"]); +} + +promise_test(async () => { + const kek = await importKek(); + const key = await unwrapKey(kek, wrappedKeyBytes); + const exported = new Uint8Array(await crypto.subtle.exportKey("raw", key)); + assert_array_equals(exported, keyBytes); +}, "AES-KW unwrapKey with a valid wrapped key"); + +promise_test(async (t) => { + const kek = await importKek(); + const tamperedKeyBytes = wrappedKeyBytes.slice(); + tamperedKeyBytes[tamperedKeyBytes.length - 1] ^= 1; + return promise_rejects_dom(t, "OperationError", unwrapKey(kek, tamperedKeyBytes)); +}, "AES-KW unwrapKey with a tampered wrapped key"); + +promise_test(async (t) => { + const kek = await importKek(); + return promise_rejects_dom(t, "OperationError", unwrapKey(kek, wrappedKeyBytes.slice(0, 16))); +}, "AES-KW unwrapKey with a wrapped key shorter than 24 bytes"); diff --git a/test/fixtures/wpt/versions.json b/test/fixtures/wpt/versions.json index 2d12ad453602..711d469cc367 100644 --- a/test/fixtures/wpt/versions.json +++ b/test/fixtures/wpt/versions.json @@ -100,7 +100,7 @@ "path": "web-locks" }, "WebCryptoAPI": { - "commit": "b638c093cd3e1849715130bcd99041f79980bb12", + "commit": "e15690d276ae50728b9d557f4e04f894588d34a0", "path": "WebCryptoAPI" }, "webidl": { From 2a5837c1880e28655bb23911198b7c46eb28c21f Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sun, 18 Oct 2026 10:35:31 +0200 Subject: [PATCH 2/2] fixup! test: update WPT for WebCryptoAPI to e15690d276 Signed-off-by: Filip Skokan Assisted-by: Codex --- test/wpt/status/WebCryptoAPI.cjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/wpt/status/WebCryptoAPI.cjs b/test/wpt/status/WebCryptoAPI.cjs index 606cbbac34d6..a73db3793a92 100644 --- a/test/wpt/status/WebCryptoAPI.cjs +++ b/test/wpt/status/WebCryptoAPI.cjs @@ -176,6 +176,8 @@ if (hasFIPS(3, 5)) { 'derive_bits_keys/derived_bits_length.https.any.js', /^X25519 derivation/, ], + ['import_export/okp_fixed_length.https.any.js', /^X25519 /], + ['import_export/okp_malformed_asn1.https.any.js', /^X25519 /], ['modern-algos/encap_decap/encap_decap_bits.https.any.js', /mlkem/i], ['modern-algos/encap_decap/encap_decap_keys.https.any.js', /mlkem/i], ['modern-algos/getPublicKey.https.any.js', /(?:X25519|X448|MLKEM)/],