Skip to content

Commit 7fa991e

Browse files
bartlomiejuclaude
andauthored
fix(ext/crypto): support structuredClone for CryptoKey (#32674)
## Summary - Enables `structuredClone()` and `postMessage()` for `CryptoKey` objects using the cloneable resource registry from #32672 - Works for all key types (AES, RSA, HMAC, EC, Ed25519, X25519, X448) including **non-extractable** keys - Clones internal key data directly, bypassing the public `exportKey` API Closes #12734 --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7e58228 commit 7fa991e

File tree

2 files changed

+99
-0
lines changed

2 files changed

+99
-0
lines changed

ext/crypto/00_crypto.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const {
6565
JSONStringify,
6666
MathCeil,
6767
ObjectAssign,
68+
ObjectDefineProperty,
6869
ObjectHasOwn,
6970
ObjectPrototypeIsPrototypeOf,
7071
SafeArrayIterator,
@@ -438,9 +439,35 @@ function constructKey(type, extractable, usages, algorithm, handle) {
438439
key[_algorithm] = algorithm;
439440
key[_handle] = handle;
440441
key[kKeyObject] = WeakMapPrototypeGet(KEY_STORE, handle);
442+
ObjectDefineProperty(key, core.hostObjectBrand, {
443+
__proto__: null,
444+
value: () => ({
445+
type: "CryptoKey",
446+
keyType: type,
447+
extractable,
448+
usages,
449+
algorithm,
450+
keyData: WeakMapPrototypeGet(KEY_STORE, handle),
451+
}),
452+
enumerable: false,
453+
configurable: false,
454+
writable: false,
455+
});
441456
return key;
442457
}
443458

459+
core.registerCloneableResource("CryptoKey", (data) => {
460+
const handle = {};
461+
WeakMapPrototypeSet(KEY_STORE, handle, data.keyData);
462+
return constructKey(
463+
data.keyType,
464+
data.extractable,
465+
data.usages,
466+
data.algorithm,
467+
handle,
468+
);
469+
});
470+
444471
// https://w3c.github.io/webcrypto/#concept-usage-intersection
445472
/**
446473
* @param {string[]} a

tests/unit/structured_clone_test.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,75 @@ Deno.test("correct DataCloneError message", () => {
5959
// ab2 should not be detached after above failure
6060
structuredClone(ab2, { transfer: [ab2] });
6161
});
62+
63+
Deno.test("structuredClone CryptoKey", async () => {
64+
// AES key
65+
const aesKey = await crypto.subtle.generateKey(
66+
{ name: "AES-GCM", length: 256 },
67+
true,
68+
["encrypt", "decrypt"],
69+
);
70+
const aesClone = structuredClone(aesKey);
71+
assert(aesKey !== aesClone);
72+
assertEquals(aesClone.type, aesKey.type);
73+
assertEquals(aesClone.extractable, aesKey.extractable);
74+
assertEquals(aesClone.algorithm, aesKey.algorithm);
75+
assertEquals([...aesClone.usages], [...aesKey.usages]);
76+
77+
// Verify the cloned key actually works
78+
const data = new TextEncoder().encode("hello");
79+
const iv = crypto.getRandomValues(new Uint8Array(12));
80+
const encrypted = await crypto.subtle.encrypt(
81+
{ name: "AES-GCM", iv },
82+
aesClone,
83+
data,
84+
);
85+
const decrypted = await crypto.subtle.decrypt(
86+
{ name: "AES-GCM", iv },
87+
aesKey,
88+
encrypted,
89+
);
90+
assertEquals(new Uint8Array(decrypted), data);
91+
92+
// Non-extractable key can be cloned
93+
const nonExtractable = await crypto.subtle.generateKey(
94+
{ name: "AES-GCM", length: 256 },
95+
false,
96+
["encrypt", "decrypt"],
97+
);
98+
const nonExtractableClone = structuredClone(nonExtractable);
99+
assertEquals(nonExtractableClone.extractable, false);
100+
assertEquals(nonExtractableClone.algorithm, nonExtractable.algorithm);
101+
102+
// HMAC key
103+
const hmacKey = await crypto.subtle.generateKey(
104+
{ name: "HMAC", hash: "SHA-256" },
105+
true,
106+
["sign", "verify"],
107+
);
108+
const hmacClone = structuredClone(hmacKey);
109+
assertEquals(hmacClone.type, hmacKey.type);
110+
assertEquals(hmacClone.algorithm, hmacKey.algorithm);
111+
assertEquals([...hmacClone.usages], [...hmacKey.usages]);
112+
113+
// EC key pair
114+
const ecKeyPair = await crypto.subtle.generateKey(
115+
{ name: "ECDSA", namedCurve: "P-256" },
116+
true,
117+
["sign", "verify"],
118+
) as CryptoKeyPair;
119+
const ecPrivateClone = structuredClone(ecKeyPair.privateKey);
120+
const ecPublicClone = structuredClone(ecKeyPair.publicKey);
121+
assertEquals(ecPrivateClone.type, "private");
122+
assertEquals(ecPublicClone.type, "public");
123+
124+
// Ed25519 key pair
125+
const edKeyPair = await crypto.subtle.generateKey(
126+
"Ed25519",
127+
true,
128+
["sign", "verify"],
129+
) as CryptoKeyPair;
130+
const edClone = structuredClone(edKeyPair.privateKey);
131+
assertEquals(edClone.type, "private");
132+
assertEquals(edClone.algorithm.name, "Ed25519");
133+
});

0 commit comments

Comments
 (0)