Skip to content

Commit 79ac019

Browse files
committed
moved decryption logic to tests, normalized text encoding to NFC
1 parent 8b7ad31 commit 79ac019

File tree

2 files changed

+47
-14
lines changed

2 files changed

+47
-14
lines changed

export/index.template.html

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1315,10 +1315,12 @@ <h2>Message log</h2>
13151315
const salt = crypto.getRandomValues(new Uint8Array(16));
13161316
const iv = crypto.getRandomValues(new Uint8Array(12));
13171317

1318-
// Import passphrase as PBKDF2 key material
1318+
// Import passphrase as PBKDF2 key material.
1319+
// Normalize to NFC so the same visual passphrase always produces the
1320+
// same bytes regardless of OS/keyboard Unicode decomposition form.
13191321
const keyMaterial = await crypto.subtle.importKey(
13201322
"raw",
1321-
new TextEncoder().encode(passphrase),
1323+
new TextEncoder().encode(passphrase.normalize("NFC")),
13221324
"PBKDF2",
13231325
false,
13241326
["deriveBits", "deriveKey"]
@@ -1369,10 +1371,10 @@ <h2>Message log</h2>
13691371
const iv = encryptedBuf.slice(16, 28);
13701372
const ciphertext = encryptedBuf.slice(28);
13711373

1372-
// Import passphrase as PBKDF2 key material
1374+
// Import passphrase as PBKDF2 key material (NFC-normalized, matching encryptWithPassphrase).
13731375
const keyMaterial = await crypto.subtle.importKey(
13741376
"raw",
1375-
new TextEncoder().encode(passphrase),
1377+
new TextEncoder().encode(passphrase.normalize("NFC")),
13761378
"PBKDF2",
13771379
false,
13781380
["deriveBits", "deriveKey"]
@@ -1433,7 +1435,6 @@ <h2>Message log</h2>
14331435
getSettings,
14341436
setSettings,
14351437
encryptWithPassphrase,
1436-
decryptWithPassphrase,
14371438
};
14381439
})();
14391440
</script>
@@ -1547,10 +1548,7 @@ <h2>Message log</h2>
15471548
TKHQ.sendMessageUp("ERROR", e.toString(), event.data["requestId"]);
15481549
}
15491550
}
1550-
if (
1551-
event.data &&
1552-
event.data["type"] == "CONFIRM_PASSPHRASE_EXPORT"
1553-
) {
1551+
if (event.data && event.data["type"] == "CONFIRM_PASSPHRASE_EXPORT") {
15541552
TKHQ.logMessage(`⬇️ Received message ${event.data["type"]}`);
15551553
try {
15561554
await onConfirmPassphraseExport(event.data["requestId"]);

export/index.test.js

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,41 @@ const html = fs
88
.readFileSync(path.resolve(__dirname, "./index.template.html"), "utf8")
99
.replace("${TURNKEY_SIGNER_ENVIRONMENT}", "prod");
1010

11+
/**
12+
* Mirror of the iframe's encryptWithPassphrase, used to verify round-trips in tests.
13+
* Kept here rather than exported from TKHQ to avoid exposing decrypt on the global
14+
* in production.
15+
*/
16+
async function decryptWithPassphrase(encryptedBuf, passphrase) {
17+
const salt = encryptedBuf.slice(0, 16);
18+
const iv = encryptedBuf.slice(16, 28);
19+
const ciphertext = encryptedBuf.slice(28);
20+
21+
const keyMaterial = await crypto.webcrypto.subtle.importKey(
22+
"raw",
23+
new TextEncoder().encode(passphrase.normalize("NFC")),
24+
"PBKDF2",
25+
false,
26+
["deriveBits", "deriveKey"]
27+
);
28+
29+
const aesKey = await crypto.webcrypto.subtle.deriveKey(
30+
{ name: "PBKDF2", salt, iterations: 600000, hash: "SHA-256" },
31+
keyMaterial,
32+
{ name: "AES-GCM", length: 256 },
33+
false,
34+
["decrypt"]
35+
);
36+
37+
const decrypted = await crypto.webcrypto.subtle.decrypt(
38+
{ name: "AES-GCM", iv },
39+
aesKey,
40+
ciphertext
41+
);
42+
43+
return new Uint8Array(decrypted);
44+
}
45+
1146
let dom;
1247
let TKHQ;
1348

@@ -475,7 +510,7 @@ describe("TKHQ", () => {
475510
const encrypted = await TKHQ.encryptWithPassphrase(plaintext, passphrase);
476511

477512
// Decrypt
478-
const decrypted = await TKHQ.decryptWithPassphrase(encrypted, passphrase);
513+
const decrypted = await decryptWithPassphrase(encrypted, passphrase);
479514

480515
// Verify
481516
const decryptedText = new TextDecoder().decode(decrypted);
@@ -494,7 +529,7 @@ describe("TKHQ", () => {
494529

495530
// Attempting to decrypt with wrong passphrase should throw
496531
await expect(
497-
TKHQ.decryptWithPassphrase(encrypted, wrongPassphrase)
532+
decryptWithPassphrase(encrypted, wrongPassphrase)
498533
).rejects.toThrow();
499534
});
500535

@@ -511,8 +546,8 @@ describe("TKHQ", () => {
511546
);
512547

513548
// But both should decrypt to the same plaintext
514-
const decrypted1 = await TKHQ.decryptWithPassphrase(encrypted1, passphrase);
515-
const decrypted2 = await TKHQ.decryptWithPassphrase(encrypted2, passphrase);
549+
const decrypted1 = await decryptWithPassphrase(encrypted1, passphrase);
550+
const decrypted2 = await decryptWithPassphrase(encrypted2, passphrase);
516551

517552
expect(new TextDecoder().decode(decrypted1)).toBe("same plaintext");
518553
expect(new TextDecoder().decode(decrypted2)).toBe("same plaintext");
@@ -547,7 +582,7 @@ describe("TKHQ", () => {
547582
);
548583

549584
// Decrypt
550-
const decrypted = await TKHQ.decryptWithPassphrase(
585+
const decrypted = await decryptWithPassphrase(
551586
encryptedFromBase64,
552587
passphrase
553588
);

0 commit comments

Comments
 (0)