Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions ext/crypto/00_crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const {
JSONStringify,
MathCeil,
ObjectAssign,
ObjectDefineProperty,
ObjectHasOwn,
ObjectPrototypeIsPrototypeOf,
SafeArrayIterator,
Expand Down Expand Up @@ -438,9 +439,35 @@ function constructKey(type, extractable, usages, algorithm, handle) {
key[_algorithm] = algorithm;
key[_handle] = handle;
key[kKeyObject] = WeakMapPrototypeGet(KEY_STORE, handle);
ObjectDefineProperty(key, core.hostObjectBrand, {
__proto__: null,
value: () => ({
type: "CryptoKey",
keyType: type,
extractable,
usages,
algorithm,
keyData: WeakMapPrototypeGet(KEY_STORE, handle),
}),
enumerable: false,
configurable: false,
writable: false,
});
return key;
}

core.registerCloneableResource("CryptoKey", (data) => {
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, data.keyData);
return constructKey(
data.keyType,
data.extractable,
data.usages,
data.algorithm,
handle,
);
});

// https://w3c.github.io/webcrypto/#concept-usage-intersection
/**
* @param {string[]} a
Expand Down
72 changes: 72 additions & 0 deletions tests/unit/structured_clone_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,75 @@ Deno.test("correct DataCloneError message", () => {
// ab2 should not be detached after above failure
structuredClone(ab2, { transfer: [ab2] });
});

Deno.test("structuredClone CryptoKey", async () => {
// AES key
const aesKey = await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 },
true,
["encrypt", "decrypt"],
);
const aesClone = structuredClone(aesKey);
assert(aesKey !== aesClone);
assertEquals(aesClone.type, aesKey.type);
assertEquals(aesClone.extractable, aesKey.extractable);
assertEquals(aesClone.algorithm, aesKey.algorithm);
assertEquals([...aesClone.usages], [...aesKey.usages]);

// Verify the cloned key actually works
const data = new TextEncoder().encode("hello");
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv },
aesClone,
data,
);
const decrypted = await crypto.subtle.decrypt(
{ name: "AES-GCM", iv },
aesKey,
encrypted,
);
assertEquals(new Uint8Array(decrypted), data);

// Non-extractable key can be cloned
const nonExtractable = await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"],
);
const nonExtractableClone = structuredClone(nonExtractable);
assertEquals(nonExtractableClone.extractable, false);
assertEquals(nonExtractableClone.algorithm, nonExtractable.algorithm);

// HMAC key
const hmacKey = await crypto.subtle.generateKey(
{ name: "HMAC", hash: "SHA-256" },
true,
["sign", "verify"],
);
const hmacClone = structuredClone(hmacKey);
assertEquals(hmacClone.type, hmacKey.type);
assertEquals(hmacClone.algorithm, hmacKey.algorithm);
assertEquals([...hmacClone.usages], [...hmacKey.usages]);

// EC key pair
const ecKeyPair = await crypto.subtle.generateKey(
{ name: "ECDSA", namedCurve: "P-256" },
true,
["sign", "verify"],
) as CryptoKeyPair;
const ecPrivateClone = structuredClone(ecKeyPair.privateKey);
const ecPublicClone = structuredClone(ecKeyPair.publicKey);
assertEquals(ecPrivateClone.type, "private");
assertEquals(ecPublicClone.type, "public");

// Ed25519 key pair
const edKeyPair = await crypto.subtle.generateKey(
"Ed25519",
true,
["sign", "verify"],
) as CryptoKeyPair;
const edClone = structuredClone(edKeyPair.privateKey);
assertEquals(edClone.type, "private");
assertEquals(edClone.algorithm.name, "Ed25519");
});
Loading