[WebCrypto] Add WebCryptoAPI WPT for CryptoKey serialization

We added ML-DSA and ML-KEM with broken CryptoKey serialization, but
this wasn't exposed because there was no WPT test for it. Add a WPT test
for serialization by roundtripping a message with a CryptoKey and
comparing the exportKey results.

(The other main way to do CryptoKey serialization is to use an IndexedDB.
Posting messages was simpler.)

Bug: 512509718
Change-Id: I7499ec3a05b6b9e72f22c00e0815f4f847027fe3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7842729
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Reviewed-by: David Benjamin <davidben@chromium.org>
Commit-Queue: Hubert Chao <hchao@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1631811}
NOKEYCHECK=True
GitOrigin-RevId: dc989b88b1c28d312d47d12bea298ffb76dba6d2
diff --git a/blink/renderer/bindings/modules/v8/serialization/v8_script_value_serializer_for_modules.cc b/blink/renderer/bindings/modules/v8/serialization/v8_script_value_serializer_for_modules.cc
index 84707ab..38c1569 100644
--- a/blink/renderer/bindings/modules/v8/serialization/v8_script_value_serializer_for_modules.cc
+++ b/blink/renderer/bindings/modules/v8/serialization/v8_script_value_serializer_for_modules.cc
@@ -572,7 +572,10 @@
           break;
         }
         default:
+          // TODO(crbug.com/512509718): fix ML-KEM and ML-DSA serialization.
           DCHECK(WebCryptoAlgorithm::IsKdf(algorithm.Id()) ||
+                 WebCryptoAlgorithm::IsMlDsa(algorithm.Id()) ||
+                 WebCryptoAlgorithm::IsMlKem(algorithm.Id()) ||
                  algorithm.Id() == kWebCryptoAlgorithmIdChaCha20Poly1305);
           WriteOneByte(kNoParamsKeyTag);
           WriteUint32(AlgorithmIdForWireFormat(algorithm.Id()));
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-cbc.https.window-expected.txt b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-cbc.https.window-expected.txt
new file mode 100644
index 0000000..822a362
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-cbc.https.window-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+[FAIL] serialization test {length: 192, name: AES-CBC}
+  promise_test: Unhandled rejection with value: object "OperationError: 192-bit AES keys are not supported"
+Harness: the test ran to completion.
+
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-cbc.https.window.js b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-cbc.https.window.js
new file mode 100644
index 0000000..8405666
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-cbc.https.window.js
@@ -0,0 +1,11 @@
+// META: title=WebCryptoAPI: CryptoKey serialization
+// META: script=../util/helpers.js
+// META: script=serialization.js
+run_test([
+  {
+    name: 'AES-CBC',
+    resultType: 'CryptoKey',
+    usages: ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'],
+    exportFormat: 'raw'
+  },
+]);
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-ctr.https.window-expected.txt b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-ctr.https.window-expected.txt
new file mode 100644
index 0000000..c88050e
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-ctr.https.window-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+[FAIL] serialization test {length: 192, name: AES-CTR}
+  promise_test: Unhandled rejection with value: object "OperationError: 192-bit AES keys are not supported"
+Harness: the test ran to completion.
+
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-ctr.https.window.js b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-ctr.https.window.js
new file mode 100644
index 0000000..3b3fb68
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-ctr.https.window.js
@@ -0,0 +1,11 @@
+// META: title=WebCryptoAPI: CryptoKey serialization
+// META: script=../util/helpers.js
+// META: script=serialization.js
+run_test([
+  {
+    name: 'AES-CTR',
+    resultType: 'CryptoKey',
+    usages: ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'],
+    exportFormat: 'raw'
+  },
+]);
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-gcm.https.window-expected.txt b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-gcm.https.window-expected.txt
new file mode 100644
index 0000000..fb4dcc0
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-gcm.https.window-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+[FAIL] serialization test {length: 192, name: AES-GCM}
+  promise_test: Unhandled rejection with value: object "OperationError: 192-bit AES keys are not supported"
+Harness: the test ran to completion.
+
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-gcm.https.window.js b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-gcm.https.window.js
new file mode 100644
index 0000000..b54eba6
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-gcm.https.window.js
@@ -0,0 +1,11 @@
+// META: title=WebCryptoAPI: CryptoKey serialization
+// META: script=../util/helpers.js
+// META: script=serialization.js
+run_test([
+  {
+    name: 'AES-GCM',
+    resultType: 'CryptoKey',
+    usages: ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'],
+    exportFormat: 'raw'
+  },
+]);
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-kw.https.window-expected.txt b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-kw.https.window-expected.txt
new file mode 100644
index 0000000..7fd97bf
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-kw.https.window-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+[FAIL] serialization test {length: 192, name: AES-KW}
+  promise_test: Unhandled rejection with value: object "OperationError: 192-bit AES keys are not supported"
+Harness: the test ran to completion.
+
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-kw.https.window.js b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-kw.https.window.js
new file mode 100644
index 0000000..9948dce
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-kw.https.window.js
@@ -0,0 +1,11 @@
+// META: title=WebCryptoAPI: CryptoKey serialization
+// META: script=../util/helpers.js
+// META: script=serialization.js
+run_test([
+  {
+    name: 'AES-KW',
+    resultType: 'CryptoKey',
+    usages: ['wrapKey', 'unwrapKey'],
+    exportFormat: 'raw'
+  },
+]);
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-ocb.tentative.https.window-expected.txt b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-ocb.tentative.https.window-expected.txt
new file mode 100644
index 0000000..208906f
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-ocb.tentative.https.window-expected.txt
@@ -0,0 +1,9 @@
+This is a testharness.js-based test.
+[FAIL] serialization test {length: 128, name: AES-OCB}
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+[FAIL] serialization test {length: 192, name: AES-OCB}
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+[FAIL] serialization test {length: 256, name: AES-OCB}
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+Harness: the test ran to completion.
+
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-ocb.tentative.https.window.js b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-ocb.tentative.https.window.js
new file mode 100644
index 0000000..33b8144
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/aes-ocb.tentative.https.window.js
@@ -0,0 +1,11 @@
+// META: title=WebCryptoAPI: CryptoKey serialization
+// META: script=../util/helpers.js
+// META: script=serialization.js
+run_test([
+  {
+    name: 'AES-OCB',
+    resultType: 'CryptoKey',
+    usages: ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'],
+    exportFormat: 'raw-secret'
+  },
+]);
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/chacha20-poly1305.tentative.https.window-expected.txt b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/chacha20-poly1305.tentative.https.window-expected.txt
new file mode 100644
index 0000000..44577cf
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/chacha20-poly1305.tentative.https.window-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+[FAIL] serialization test ChaCha20-Poly1305
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+[FAIL] serialization test {name: ChaCha20-Poly1305}
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+Harness: the test ran to completion.
+
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/chacha20-poly1305.tentative.https.window.js b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/chacha20-poly1305.tentative.https.window.js
new file mode 100644
index 0000000..aaaa24a
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/chacha20-poly1305.tentative.https.window.js
@@ -0,0 +1,11 @@
+// META: title=WebCryptoAPI: CryptoKey serialization
+// META: script=../util/helpers.js
+// META: script=serialization.js
+run_test([
+  {
+    name: 'ChaCha20-Poly1305',
+    resultType: 'CryptoKey',
+    usages: ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'],
+    exportFormat: 'raw-secret'
+  },
+]);
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/ecdh.https.window.js b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/ecdh.https.window.js
new file mode 100644
index 0000000..17df8a8
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/ecdh.https.window.js
@@ -0,0 +1,12 @@
+// META: title=WebCryptoAPI: CryptoKey serialization
+// META: script=../util/helpers.js
+// META: script=serialization.js
+run_test([
+  {
+    name: 'ECDH',
+    resultType: 'CryptoKeyPair',
+    usages: ['deriveKey', 'deriveBits'],
+    publicFormat: 'raw',
+    privateFormat: 'pkcs8'
+  },
+]);
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/ecdsa.https.window.js b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/ecdsa.https.window.js
new file mode 100644
index 0000000..679dc06
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/ecdsa.https.window.js
@@ -0,0 +1,12 @@
+// META: title=WebCryptoAPI: CryptoKey serialization
+// META: script=../util/helpers.js
+// META: script=serialization.js
+run_test([
+  {
+    name: 'ECDSA',
+    resultType: 'CryptoKeyPair',
+    usages: ['sign', 'verify'],
+    publicFormat: 'raw',
+    privateFormat: 'pkcs8'
+  },
+]);
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/ed25519.https.window.js b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/ed25519.https.window.js
new file mode 100644
index 0000000..831e917
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/ed25519.https.window.js
@@ -0,0 +1,12 @@
+// META: title=WebCryptoAPI: CryptoKey serialization
+// META: script=../util/helpers.js
+// META: script=serialization.js
+run_test([
+  {
+    name: 'Ed25519',
+    resultType: 'CryptoKeyPair',
+    usages: ['sign', 'verify'],
+    publicFormat: 'raw',
+    privateFormat: 'pkcs8'
+  },
+]);
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/ed448.https.window-expected.txt b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/ed448.https.window-expected.txt
new file mode 100644
index 0000000..8e72bdc
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/ed448.https.window-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+[FAIL] serialization test Ed448
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+[FAIL] serialization test {name: Ed448}
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+Harness: the test ran to completion.
+
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/ed448.https.window.js b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/ed448.https.window.js
new file mode 100644
index 0000000..4abe562
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/ed448.https.window.js
@@ -0,0 +1,12 @@
+// META: title=WebCryptoAPI: CryptoKey serialization
+// META: script=../util/helpers.js
+// META: script=serialization.js
+run_test([
+  {
+    name: 'Ed448',
+    resultType: 'CryptoKeyPair',
+    usages: ['sign', 'verify'],
+    publicFormat: 'raw',
+    privateFormat: 'pkcs8'
+  },
+]);
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/hmac.https.window.js b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/hmac.https.window.js
new file mode 100644
index 0000000..99efea6
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/hmac.https.window.js
@@ -0,0 +1,11 @@
+// META: title=WebCryptoAPI: CryptoKey serialization
+// META: script=../util/helpers.js
+// META: script=serialization.js
+run_test([
+  {
+    name: 'HMAC',
+    resultType: 'CryptoKey',
+    usages: ['sign', 'verify'],
+    exportFormat: 'raw'
+  },
+]);
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/kmac.tentative.https.window-expected.txt b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/kmac.tentative.https.window-expected.txt
new file mode 100644
index 0000000..6b48d95
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/kmac.tentative.https.window-expected.txt
@@ -0,0 +1,15 @@
+This is a testharness.js-based test.
+[FAIL] serialization test {length: 128, name: KMAC128}
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+[FAIL] serialization test {length: 160, name: KMAC128}
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+[FAIL] serialization test {length: 256, name: KMAC128}
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+[FAIL] serialization test {length: 128, name: KMAC256}
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+[FAIL] serialization test {length: 160, name: KMAC256}
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+[FAIL] serialization test {length: 256, name: KMAC256}
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+Harness: the test ran to completion.
+
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/kmac.tentative.https.window.js b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/kmac.tentative.https.window.js
new file mode 100644
index 0000000..645d16b
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/kmac.tentative.https.window.js
@@ -0,0 +1,17 @@
+// META: title=WebCryptoAPI: CryptoKey serialization
+// META: script=../util/helpers.js
+// META: script=serialization.js
+run_test([
+  {
+    name: 'KMAC128',
+    resultType: 'CryptoKey',
+    usages: ['sign', 'verify'],
+    exportFormat: 'raw-secret'
+  },
+  {
+    name: 'KMAC256',
+    resultType: 'CryptoKey',
+    usages: ['sign', 'verify'],
+    exportFormat: 'raw-secret'
+  },
+]);
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/mldsa.tentative.https.window-expected.txt b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/mldsa.tentative.https.window-expected.txt
new file mode 100644
index 0000000..e56e3b6
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/mldsa.tentative.https.window-expected.txt
@@ -0,0 +1,15 @@
+This is a testharness.js-based test.
+[FAIL] serialization test ML-DSA-44
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+[FAIL] serialization test {name: ML-DSA-44}
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+[FAIL] serialization test ML-DSA-65
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+[FAIL] serialization test {name: ML-DSA-65}
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+[FAIL] serialization test ML-DSA-87
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+[FAIL] serialization test {name: ML-DSA-87}
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+Harness: the test ran to completion.
+
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/mldsa.tentative.https.window.js b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/mldsa.tentative.https.window.js
new file mode 100644
index 0000000..3f61248
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/mldsa.tentative.https.window.js
@@ -0,0 +1,27 @@
+// META: title=WebCryptoAPI: CryptoKey serialization
+// META: script=../util/helpers.js
+// META: script=serialization.js
+
+run_test([
+  {
+    name: 'ML-DSA-44',
+    resultType: 'CryptoKeyPair',
+    usages: ['sign', 'verify'],
+    publicFormat: 'raw-public',
+    privateFormat: 'raw-seed'
+  },
+  {
+    name: 'ML-DSA-65',
+    resultType: 'CryptoKeyPair',
+    usages: ['sign', 'verify'],
+    publicFormat: 'raw-public',
+    privateFormat: 'raw-seed'
+  },
+  {
+    name: 'ML-DSA-87',
+    resultType: 'CryptoKeyPair',
+    usages: ['sign', 'verify'],
+    publicFormat: 'raw-public',
+    privateFormat: 'raw-seed'
+  },
+]);
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/mlkem.tentative.https.window-expected.txt b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/mlkem.tentative.https.window-expected.txt
new file mode 100644
index 0000000..5ee22f2
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/mlkem.tentative.https.window-expected.txt
@@ -0,0 +1,15 @@
+This is a testharness.js-based test.
+[FAIL] serialization test ML-KEM-512
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+[FAIL] serialization test {name: ML-KEM-512}
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+[FAIL] serialization test ML-KEM-768
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+[FAIL] serialization test {name: ML-KEM-768}
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+[FAIL] serialization test ML-KEM-1024
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+[FAIL] serialization test {name: ML-KEM-1024}
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+Harness: the test ran to completion.
+
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/mlkem.tentative.https.window.js b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/mlkem.tentative.https.window.js
new file mode 100644
index 0000000..62a210f
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/mlkem.tentative.https.window.js
@@ -0,0 +1,32 @@
+// META: title=WebCryptoAPI: CryptoKey serialization
+// META: script=../util/helpers.js
+// META: script=serialization.js
+run_test([
+  {
+    name: 'ML-KEM-512',
+    resultType: 'CryptoKeyPair',
+    usages: [
+      'decapsulateBits', 'decapsulateKey', 'encapsulateBits', 'encapsulateKey'
+    ],
+    publicFormat: 'raw-public',
+    privateFormat: 'raw-seed'
+  },
+  {
+    name: 'ML-KEM-768',
+    resultType: 'CryptoKeyPair',
+    usages: [
+      'decapsulateBits', 'decapsulateKey', 'encapsulateBits', 'encapsulateKey'
+    ],
+    publicFormat: 'raw-public',
+    privateFormat: 'raw-seed'
+  },
+  {
+    name: 'ML-KEM-1024',
+    resultType: 'CryptoKeyPair',
+    usages: [
+      'decapsulateBits', 'decapsulateKey', 'encapsulateBits', 'encapsulateKey'
+    ],
+    publicFormat: 'raw-public',
+    privateFormat: 'raw-seed'
+  },
+]);
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/resources/post-page.html b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/resources/post-page.html
new file mode 100644
index 0000000..0226732
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/resources/post-page.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Post Target</title>
+
+<script>
+window.addEventListener('message', function(e) {
+  opener.postMessage(e.data);
+});
+</script>
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/rsa-oaep.https.window.js b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/rsa-oaep.https.window.js
new file mode 100644
index 0000000..4246930
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/rsa-oaep.https.window.js
@@ -0,0 +1,12 @@
+// META: title=WebCryptoAPI: CryptoKey serialization
+// META: script=../util/helpers.js
+// META: script=serialization.js
+run_test([
+  {
+    name: 'RSA-OAEP',
+    resultType: 'CryptoKeyPair',
+    usages: ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'],
+    publicFormat: 'spki',
+    privateFormat: 'pkcs8'
+  },
+]);
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/rsa-pss.https.window.js b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/rsa-pss.https.window.js
new file mode 100644
index 0000000..a1255b7
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/rsa-pss.https.window.js
@@ -0,0 +1,12 @@
+// META: title=WebCryptoAPI: CryptoKey serialization
+// META: script=../util/helpers.js
+// META: script=serialization.js
+run_test([
+  {
+    name: 'RSA-PSS',
+    resultType: 'CryptoKeyPair',
+    usages: ['sign', 'verify'],
+    publicFormat: 'spki',
+    privateFormat: 'pkcs8'
+  },
+]);
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/rsassa-pkcs1-v1_5.https.window.js b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/rsassa-pkcs1-v1_5.https.window.js
new file mode 100644
index 0000000..051eb87
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/rsassa-pkcs1-v1_5.https.window.js
@@ -0,0 +1,12 @@
+// META: title=WebCryptoAPI: CryptoKey serialization
+// META: script=../util/helpers.js
+// META: script=serialization.js
+run_test([
+  {
+    name: 'RSASSA-PKCS1-v1_5',
+    resultType: 'CryptoKeyPair',
+    usages: ['sign', 'verify'],
+    publicFormat: 'spki',
+    privateFormat: 'pkcs8'
+  },
+]);
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/serialization.js b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/serialization.js
new file mode 100644
index 0000000..35394e2
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/serialization.js
@@ -0,0 +1,76 @@
+function run_test(vectors) {
+  function waitForEvent(obj, ev) {
+    return new Promise((resolve) => {
+      obj.addEventListener(ev, resolve, {once: true});
+    });
+  }
+
+  function testCryptoKeySerialization(
+      generateKeyAlgorithm, generateKeyUsages, exportFormat) {
+    promise_test(async t => {
+      var cryptoKey = await crypto.subtle.generateKey(
+          generateKeyAlgorithm, true, generateKeyUsages);
+      const keyExported =
+          await crypto.subtle.exportKey(exportFormat, cryptoKey);
+
+      const popup = window.open('resources/post-page.html');
+      t.add_cleanup(() => popup.close());
+
+
+      // Wait for window to load.
+      await waitForEvent(popup, 'load');
+      popup.postMessage({key: cryptoKey});
+      // Wait to get key back via post.
+      let evt = await waitForEvent(window, 'message');
+      const newKeyExported =
+          await crypto.subtle.exportKey(exportFormat, evt.data.key);
+      assert_true(equalBuffers(keyExported, newKeyExported));
+    }, 'serialization test ' + objectToString(generateKeyAlgorithm));
+  };
+
+  function testCryptoKeyPairSerialization(
+      generateKeyAlgorithm, generateKeyUsages, publicExportFormat,
+      privateExportFormat) {
+    promise_test(async t => {
+      var keyPair = await crypto.subtle.generateKey(
+          generateKeyAlgorithm, true, generateKeyUsages);
+      const publicKeyExported =
+          await crypto.subtle.exportKey(publicExportFormat, keyPair.publicKey);
+      const privateKeyExported = await crypto.subtle.exportKey(
+          privateExportFormat, keyPair.privateKey);
+
+      const popup = window.open('resources/post-page.html');
+      t.add_cleanup(() => popup.close());
+
+      // Wait for window to load.
+      await waitForEvent(popup, 'load');
+      popup.postMessage(
+          {publicKey: keyPair.publicKey, privateKey: keyPair.privateKey});
+      // Wait to get keys back via post.
+      let evt = await waitForEvent(window, 'message');
+      const newPublicKeyExported =
+          await crypto.subtle.exportKey(publicExportFormat, evt.data.publicKey);
+      assert_true(equalBuffers(publicKeyExported, newPublicKeyExported));
+      const newPrivateKeyExported = await crypto.subtle.exportKey(
+          privateExportFormat, evt.data.privateKey);
+      assert_true(equalBuffers(privateKeyExported, newPrivateKeyExported));
+    }, 'serialization test ' + objectToString(generateKeyAlgorithm));
+  };
+
+  vectors.forEach(function(vector) {
+    if (vector.resultType === 'CryptoKey') {
+      allAlgorithmSpecifiersFor(vector.name)
+          .forEach(function(generateKeyAlgorithm) {
+            testCryptoKeySerialization(
+                generateKeyAlgorithm, vector.usages, vector.exportFormat);
+          });
+    } else {
+      allAlgorithmSpecifiersFor(vector.name)
+          .forEach(function(generateKeyAlgorithm) {
+            testCryptoKeyPairSerialization(
+                generateKeyAlgorithm, vector.usages, vector.publicFormat,
+                vector.privateFormat);
+          });
+    }
+  });
+}
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/x25519.https.window.js b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/x25519.https.window.js
new file mode 100644
index 0000000..618bf29
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/x25519.https.window.js
@@ -0,0 +1,12 @@
+// META: title=WebCryptoAPI: CryptoKey serialization
+// META: script=../util/helpers.js
+// META: script=serialization.js
+run_test([
+  {
+    name: 'X25519',
+    resultType: 'CryptoKeyPair',
+    usages: ['deriveKey', 'deriveBits'],
+    publicFormat: 'raw',
+    privateFormat: 'pkcs8'
+  },
+]);
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/x448.https.window-expected.txt b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/x448.https.window-expected.txt
new file mode 100644
index 0000000..8c153d7
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/x448.https.window-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+[FAIL] serialization test X448
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+[FAIL] serialization test {name: X448}
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+Harness: the test ran to completion.
+
diff --git a/blink/web_tests/external/wpt/WebCryptoAPI/serialization/x448.https.window.js b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/x448.https.window.js
new file mode 100644
index 0000000..cafc001
--- /dev/null
+++ b/blink/web_tests/external/wpt/WebCryptoAPI/serialization/x448.https.window.js
@@ -0,0 +1,12 @@
+// META: title=WebCryptoAPI: CryptoKey serialization
+// META: script=../util/helpers.js
+// META: script=serialization.js
+run_test([
+  {
+    name: 'X448',
+    resultType: 'CryptoKeyPair',
+    usages: ['deriveKey', 'deriveBits'],
+    publicFormat: 'raw',
+    privateFormat: 'pkcs8'
+  },
+]);
diff --git a/blink/web_tests/virtual/webcrypto-pqc/external/wpt/WebCryptoAPI/serialization/chacha20-poly1305.tentative.https.window-expected.txt b/blink/web_tests/virtual/webcrypto-pqc/external/wpt/WebCryptoAPI/serialization/chacha20-poly1305.tentative.https.window-expected.txt
new file mode 100644
index 0000000..5b37deb
--- /dev/null
+++ b/blink/web_tests/virtual/webcrypto-pqc/external/wpt/WebCryptoAPI/serialization/chacha20-poly1305.tentative.https.window-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+All subtests passed and are omitted for brevity.
+See https://chromium.googlesource.com/chromium/src/+/HEAD/docs/testing/writing_web_tests.md#Text-Test-Baselines for details.
+Harness: the test ran to completion.
diff --git a/blink/web_tests/virtual/webcrypto-pqc/external/wpt/WebCryptoAPI/serialization/mldsa.tentative.https.window-expected.txt b/blink/web_tests/virtual/webcrypto-pqc/external/wpt/WebCryptoAPI/serialization/mldsa.tentative.https.window-expected.txt
new file mode 100644
index 0000000..d5b5aa4
--- /dev/null
+++ b/blink/web_tests/virtual/webcrypto-pqc/external/wpt/WebCryptoAPI/serialization/mldsa.tentative.https.window-expected.txt
@@ -0,0 +1,15 @@
+This is a testharness.js-based test.
+[FAIL] serialization test ML-DSA-44
+  promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of null (reading 'publicKey')"
+[FAIL] serialization test {name: ML-DSA-44}
+  promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of null (reading 'publicKey')"
+[FAIL] serialization test ML-DSA-65
+  promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of null (reading 'publicKey')"
+[FAIL] serialization test {name: ML-DSA-65}
+  promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of null (reading 'publicKey')"
+[FAIL] serialization test ML-DSA-87
+  promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of null (reading 'publicKey')"
+[FAIL] serialization test {name: ML-DSA-87}
+  promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of null (reading 'publicKey')"
+Harness: the test ran to completion.
+
diff --git a/blink/web_tests/virtual/webcrypto-pqc/external/wpt/WebCryptoAPI/serialization/mlkem.tentative.https.window-expected.txt b/blink/web_tests/virtual/webcrypto-pqc/external/wpt/WebCryptoAPI/serialization/mlkem.tentative.https.window-expected.txt
new file mode 100644
index 0000000..4baa7a2
--- /dev/null
+++ b/blink/web_tests/virtual/webcrypto-pqc/external/wpt/WebCryptoAPI/serialization/mlkem.tentative.https.window-expected.txt
@@ -0,0 +1,15 @@
+This is a testharness.js-based test.
+[FAIL] serialization test ML-KEM-512
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+[FAIL] serialization test {name: ML-KEM-512}
+  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'generateKey' on 'SubtleCrypto': Algorithm: Unrecognized name"
+[FAIL] serialization test ML-KEM-768
+  promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of null (reading 'publicKey')"
+[FAIL] serialization test {name: ML-KEM-768}
+  promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of null (reading 'publicKey')"
+[FAIL] serialization test ML-KEM-1024
+  promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of null (reading 'publicKey')"
+[FAIL] serialization test {name: ML-KEM-1024}
+  promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of null (reading 'publicKey')"
+Harness: the test ran to completion.
+