style(frontend): Prettier config + one-time repo-wide format pass
Prettier 3 (pinned), printWidth 100, double quotes — chosen to match the existing hand-formatting and minimise reflow. This commit is ONLY the mechanical format pass (`prettier --write`) plus the config/ignore/scripts, isolated so it never pollutes a feature diff. Verified behaviour-neutral: typecheck, eslint (0 errors), and vitest all green before and after. See .git-blame-ignore-revs — `git blame` skips this sha.
This commit is contained in:
+44
-13
@@ -87,18 +87,22 @@ export async function clearDevice(userId: number): Promise<void> {
|
||||
|
||||
// --- key derivation ---
|
||||
async function wrapKeyFrom(passphrase: string, salt: Uint8Array): Promise<CryptoKey> {
|
||||
const base = await subtle.importKey("raw", bsrc(enc.encode(passphrase)), "PBKDF2", false, ["deriveKey"]);
|
||||
const base = await subtle.importKey("raw", bsrc(enc.encode(passphrase)), "PBKDF2", false, [
|
||||
"deriveKey",
|
||||
]);
|
||||
return subtle.deriveKey(
|
||||
{ name: "PBKDF2", salt: bsrc(salt), iterations: PBKDF2_ITERS, hash: "SHA-256" },
|
||||
base,
|
||||
{ name: "AES-GCM", length: 256 },
|
||||
false,
|
||||
["encrypt", "decrypt"]
|
||||
["encrypt", "decrypt"],
|
||||
);
|
||||
}
|
||||
async function importPrivate(pkcs8: ArrayBuffer): Promise<CryptoKey> {
|
||||
// Non-extractable: usable for deriveBits but can never be exported again.
|
||||
return subtle.importKey("pkcs8", bsrc(pkcs8), { name: "ECDH", namedCurve: "P-256" }, false, ["deriveBits"]);
|
||||
return subtle.importKey("pkcs8", bsrc(pkcs8), { name: "ECDH", namedCurve: "P-256" }, false, [
|
||||
"deriveBits",
|
||||
]);
|
||||
}
|
||||
|
||||
// Try to restore the unlocked key from this device's storage. Returns true if unlocked.
|
||||
@@ -134,7 +138,11 @@ export async function setup(userId: number, passphrase: string): Promise<KeySetu
|
||||
// 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 keyCheck = await subtle.encrypt(
|
||||
{ name: "AES-GCM", iv: bsrc(checkIv) },
|
||||
wrapKey,
|
||||
bsrc(enc.encode("ok")),
|
||||
);
|
||||
const spki = await subtle.exportKey("spki", kp.publicKey);
|
||||
|
||||
await installKey(userId, await importPrivate(pkcs8));
|
||||
@@ -148,7 +156,11 @@ export async function setup(userId: number, passphrase: string): Promise<KeySetu
|
||||
}
|
||||
|
||||
// Unlock on a device from the server blob; throws if the passphrase is wrong.
|
||||
export async function unlock(userId: number, passphrase: string, bundle: MyKeyBundle): Promise<void> {
|
||||
export async function unlock(
|
||||
userId: number,
|
||||
passphrase: string,
|
||||
bundle: MyKeyBundle,
|
||||
): Promise<void> {
|
||||
if (!bundle.wrapped_private_key || !bundle.salt || !bundle.wrap_iv || !bundle.key_check) {
|
||||
throw new Error("incomplete key bundle");
|
||||
}
|
||||
@@ -158,7 +170,7 @@ export async function unlock(userId: number, passphrase: string, bundle: MyKeyBu
|
||||
const pkcs8 = await subtle.decrypt(
|
||||
{ name: "AES-GCM", iv: bsrc(ub64(bundle.wrap_iv)) },
|
||||
wrapKey,
|
||||
bsrc(ub64(bundle.wrapped_private_key))
|
||||
bsrc(ub64(bundle.wrapped_private_key)),
|
||||
);
|
||||
await installKey(userId, await importPrivate(pkcs8));
|
||||
}
|
||||
@@ -168,15 +180,26 @@ async function convKeyFor(partnerId: number, partnerPubB64: string): Promise<Cry
|
||||
const cached = convKeys.get(partnerId);
|
||||
if (cached) return cached;
|
||||
if (!privKey) throw new Error("locked");
|
||||
const pub = await subtle.importKey("spki", bsrc(ub64(partnerPubB64)), { name: "ECDH", namedCurve: "P-256" }, false, []);
|
||||
const pub = await subtle.importKey(
|
||||
"spki",
|
||||
bsrc(ub64(partnerPubB64)),
|
||||
{ name: "ECDH", namedCurve: "P-256" },
|
||||
false,
|
||||
[],
|
||||
);
|
||||
const bits = await subtle.deriveBits({ name: "ECDH", public: pub }, privKey, 256);
|
||||
const hk = await subtle.importKey("raw", bits, "HKDF", false, ["deriveKey"]);
|
||||
const key = await subtle.deriveKey(
|
||||
{ name: "HKDF", hash: "SHA-256", salt: bsrc(new Uint8Array(0)), info: bsrc(enc.encode(HKDF_INFO)) },
|
||||
{
|
||||
name: "HKDF",
|
||||
hash: "SHA-256",
|
||||
salt: bsrc(new Uint8Array(0)),
|
||||
info: bsrc(enc.encode(HKDF_INFO)),
|
||||
},
|
||||
hk,
|
||||
{ name: "AES-GCM", length: 256 },
|
||||
false,
|
||||
["encrypt", "decrypt"]
|
||||
["encrypt", "decrypt"],
|
||||
);
|
||||
convKeys.set(partnerId, key);
|
||||
return key;
|
||||
@@ -185,11 +208,15 @@ async function convKeyFor(partnerId: number, partnerPubB64: string): Promise<Cry
|
||||
export async function encryptFor(
|
||||
partnerId: number,
|
||||
partnerPubB64: string,
|
||||
plaintext: string
|
||||
plaintext: string,
|
||||
): Promise<{ ciphertext: string; iv: string }> {
|
||||
const key = await convKeyFor(partnerId, partnerPubB64);
|
||||
const iv = crypto.getRandomValues(new Uint8Array(12));
|
||||
const ct = await subtle.encrypt({ name: "AES-GCM", iv: bsrc(iv) }, key, bsrc(enc.encode(plaintext)));
|
||||
const ct = await subtle.encrypt(
|
||||
{ name: "AES-GCM", iv: bsrc(iv) },
|
||||
key,
|
||||
bsrc(enc.encode(plaintext)),
|
||||
);
|
||||
return { ciphertext: b64(ct), iv: b64(iv) };
|
||||
}
|
||||
|
||||
@@ -197,9 +224,13 @@ export async function decryptFrom(
|
||||
partnerId: number,
|
||||
partnerPubB64: string,
|
||||
ciphertext: string,
|
||||
iv: string
|
||||
iv: string,
|
||||
): Promise<string> {
|
||||
const key = await convKeyFor(partnerId, partnerPubB64);
|
||||
const pt = await subtle.decrypt({ name: "AES-GCM", iv: bsrc(ub64(iv)) }, key, bsrc(ub64(ciphertext)));
|
||||
const pt = await subtle.decrypt(
|
||||
{ name: "AES-GCM", iv: bsrc(ub64(iv)) },
|
||||
key,
|
||||
bsrc(ub64(ciphertext)),
|
||||
);
|
||||
return dec.decode(pt);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user