Skip to content

Commit 48cfa0a

Browse files
pranavjain97claude
andcommitted
fix(sdk-api): add explicit AES-GCM tagLength and clear salt on session destroy
WCN-32: Address PR review feedback: - Explicitly set tagLength: 128 on AES-GCM encrypt/decrypt calls - Clear argon2SaltB64 in EncryptionSession.destroy() for consistent cleanup Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0a8e132 commit 48cfa0a

2 files changed

Lines changed: 18 additions & 6 deletions

File tree

modules/sdk-api/src/encryptV2.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,16 @@ export function hkdfDeriveAesKey(hkdfKey: CryptoKey, hkdfSalt: Uint8Array, usage
101101
}
102102

103103
export async function aesGcmEncrypt(key: CryptoKey, iv: Uint8Array, plaintext: string): Promise<Uint8Array> {
104-
const ct = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, new TextEncoder().encode(plaintext));
104+
const ct = await crypto.subtle.encrypt(
105+
{ name: 'AES-GCM', iv, tagLength: 128 },
106+
key,
107+
new TextEncoder().encode(plaintext)
108+
);
105109
return new Uint8Array(ct);
106110
}
107111

108112
export async function aesGcmDecrypt(key: CryptoKey, iv: Uint8Array, ct: Uint8Array): Promise<string> {
109-
const plaintext = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ct);
113+
const plaintext = await crypto.subtle.decrypt({ name: 'AES-GCM', iv, tagLength: 128 }, key, ct);
110114
return new TextDecoder().decode(plaintext);
111115
}
112116

modules/sdk-api/src/encryptionSession.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
*/
2222
export class EncryptionSession {
2323
private hkdfKey: CryptoKey | null;
24-
private readonly argon2SaltB64: string;
24+
private argon2SaltB64: string | null;
2525
private readonly memorySize: number;
2626
private readonly iterations: number;
2727
private readonly parallelism: number;
@@ -53,7 +53,7 @@ export class EncryptionSession {
5353
if (!envelope.hkdfSalt) {
5454
throw new Error('envelope was not encrypted with a session; use decryptV2 instead');
5555
}
56-
if (envelope.salt !== this.argon2SaltB64) {
56+
if (envelope.salt !== this.getSaltOrThrow()) {
5757
throw new Error('envelope was not encrypted with this session');
5858
}
5959
const iv = new Uint8Array(Buffer.from(envelope.iv, 'base64'));
@@ -65,22 +65,30 @@ export class EncryptionSession {
6565

6666
destroy(): void {
6767
this.hkdfKey = null;
68+
this.argon2SaltB64 = null;
6869
}
6970

7071
private getKeyOrThrow(): CryptoKey {
71-
if (this.hkdfKey === null) {
72+
if (this.hkdfKey === null || this.argon2SaltB64 === null) {
7273
throw new Error('EncryptionSession has been destroyed');
7374
}
7475
return this.hkdfKey;
7576
}
7677

78+
private getSaltOrThrow(): string {
79+
if (this.argon2SaltB64 === null) {
80+
throw new Error('EncryptionSession has been destroyed');
81+
}
82+
return this.argon2SaltB64;
83+
}
84+
7785
private buildEnvelope(hkdfSalt: Uint8Array, iv: Uint8Array, ct: Uint8Array): V2Envelope {
7886
return {
7987
v: 2,
8088
m: this.memorySize,
8189
t: this.iterations,
8290
p: this.parallelism,
83-
salt: this.argon2SaltB64,
91+
salt: this.getSaltOrThrow(),
8492
hkdfSalt: Buffer.from(hkdfSalt).toString('base64'),
8593
iv: Buffer.from(iv).toString('base64'),
8694
ct: Buffer.from(ct).toString('base64'),

0 commit comments

Comments
 (0)