fix(e2ee): stop reusing the AES-GCM nonce in setup; drop redundant key_check oracle
setup() encrypted BOTH the wrapped private key AND the key_check under the same wrapKey with the SAME wrapIv — a GCM nonce-reuse bug (leaks keystream + enables GHASH/tag forgery, which matters under E2EE's malicious-server threat model). Fixes: - setup() gives key_check its own independent nonce (checkIv). - unlock() no longer decrypts key_check to verify the passphrase — that was redundant (a wrong passphrase already fails the wrapped_private_key GCM auth tag) AND was the second consumer of the reused nonce. Backward-compatible: the uploaded bundle shape is unchanged, and existing users' bundles still unlock (unlock only presence-checks key_check now; the private-key decrypt path with wrap_iv is untouched). Also removed the unused, architecturally ineffective lock() export (re-unlocks from IndexedDB on next mount; the meaningful primitive is clearDevice). Re-review clean; tsc green.
This commit is contained in:
parent
3d00d75863
commit
40ddaa2c92
1 changed files with 7 additions and 9 deletions
|
|
@ -46,12 +46,6 @@ export function isUnlocked(): boolean {
|
||||||
return !!privKey;
|
return !!privKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function lock(): void {
|
|
||||||
privKey = null;
|
|
||||||
convKeys.clear();
|
|
||||||
emitUnlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- IndexedDB (per-device persistence of the unlocked private key) ---
|
// --- IndexedDB (per-device persistence of the unlocked private key) ---
|
||||||
const DB_NAME = "siftlode-e2ee";
|
const DB_NAME = "siftlode-e2ee";
|
||||||
const STORE = "privkeys";
|
const STORE = "privkeys";
|
||||||
|
|
@ -127,7 +121,11 @@ export async function setup(userId: number, passphrase: string): Promise<KeySetu
|
||||||
const wrapKey = await wrapKeyFrom(passphrase, salt);
|
const wrapKey = await wrapKeyFrom(passphrase, salt);
|
||||||
const pkcs8 = await subtle.exportKey("pkcs8", kp.privateKey);
|
const pkcs8 = await subtle.exportKey("pkcs8", kp.privateKey);
|
||||||
const wrapped = await subtle.encrypt({ name: "AES-GCM", iv: bsrc(wrapIv) }, wrapKey, bsrc(pkcs8));
|
const wrapped = await subtle.encrypt({ name: "AES-GCM", iv: bsrc(wrapIv) }, wrapKey, bsrc(pkcs8));
|
||||||
const keyCheck = await subtle.encrypt({ name: "AES-GCM", iv: bsrc(wrapIv) }, wrapKey, bsrc(enc.encode("ok")));
|
// key_check is retained for the upload schema but MUST use its own nonce: reusing wrapIv under
|
||||||
|
// wrapKey would be GCM nonce-reuse (leaks keystream + enables GHASH forgery). It is no longer a
|
||||||
|
// verification oracle either — unlock() authenticates the passphrase via the wrapped-key GCM tag.
|
||||||
|
const checkIv = crypto.getRandomValues(new Uint8Array(12));
|
||||||
|
const keyCheck = await subtle.encrypt({ name: "AES-GCM", iv: bsrc(checkIv) }, wrapKey, bsrc(enc.encode("ok")));
|
||||||
const spki = await subtle.exportKey("spki", kp.publicKey);
|
const spki = await subtle.exportKey("spki", kp.publicKey);
|
||||||
|
|
||||||
privKey = await importPrivate(pkcs8);
|
privKey = await importPrivate(pkcs8);
|
||||||
|
|
@ -149,8 +147,8 @@ export async function unlock(userId: number, passphrase: string, bundle: MyKeyBu
|
||||||
throw new Error("incomplete key bundle");
|
throw new Error("incomplete key bundle");
|
||||||
}
|
}
|
||||||
const wrapKey = await wrapKeyFrom(passphrase, ub64(bundle.salt));
|
const wrapKey = await wrapKeyFrom(passphrase, ub64(bundle.salt));
|
||||||
// Wrong passphrase → AES-GCM auth tag fails here.
|
// Wrong passphrase → the AES-GCM auth tag fails on this decrypt. (No separate key_check oracle:
|
||||||
await subtle.decrypt({ name: "AES-GCM", iv: bsrc(ub64(bundle.wrap_iv)) }, wrapKey, bsrc(ub64(bundle.key_check)));
|
// that was redundant with this tag and formerly shared wrapIv — see setup().)
|
||||||
const pkcs8 = await subtle.decrypt(
|
const pkcs8 = await subtle.decrypt(
|
||||||
{ name: "AES-GCM", iv: bsrc(ub64(bundle.wrap_iv)) },
|
{ name: "AES-GCM", iv: bsrc(ub64(bundle.wrap_iv)) },
|
||||||
wrapKey,
|
wrapKey,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue