1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
import { prfEncrypt, prfDecrypt } from "$lib/crypto";
import { rememberPrfKey, defaultRememberForMs } from "$lib/passwordCache";
import { post } from "$lib/utils";
export function generateCanonicalKey(): ArrayBuffer {
return crypto.getRandomValues(new Uint8Array(32)).buffer as ArrayBuffer;
}
export async function wrapCanonicalKey(
wrappingKey: ArrayBuffer,
canonicalKey: ArrayBuffer,
): Promise<string> {
return prfEncrypt(wrappingKey, new Uint8Array(canonicalKey));
}
export async function unwrapCanonicalKey(
wrappingKey: ArrayBuffer,
encrypted: string,
): Promise<ArrayBuffer> {
const bytes = await prfDecrypt(wrappingKey, encrypted);
return bytes.buffer as ArrayBuffer;
}
export async function resolveCanonicalKey(
methodId: string,
wrappingKey: ArrayBuffer,
encryptedKeys: Record<string, string> | null | undefined,
oldPrfKey: ArrayBuffer,
): Promise<{ canonicalKey: ArrayBuffer; encryptedKeys: Record<string, string> }> {
if (encryptedKeys && encryptedKeys[methodId]) {
try {
// Try new wrapping key first (e.g. PRF-derived)
const canonicalKey = await unwrapCanonicalKey(wrappingKey, encryptedKeys[methodId]);
rememberPrfKey(canonicalKey, defaultRememberForMs);
return { canonicalKey, encryptedKeys };
} catch {
// Fallback: try old key (e.g. SHA-256(credentialId)) and re-wrap with new key
const canonicalKey = await unwrapCanonicalKey(oldPrfKey, encryptedKeys[methodId]);
const wrapped = await wrapCanonicalKey(wrappingKey, canonicalKey);
const updated = { ...encryptedKeys, [methodId]: wrapped };
await saveEncryptedKeys(updated);
rememberPrfKey(canonicalKey, defaultRememberForMs);
return { canonicalKey, encryptedKeys: updated };
}
}
// Migration: old PRF key becomes canonical key
const canonicalKey = oldPrfKey;
const wrapped = await wrapCanonicalKey(wrappingKey, canonicalKey);
const updated = { ...encryptedKeys, [methodId]: wrapped };
await saveEncryptedKeys(updated);
rememberPrfKey(canonicalKey, defaultRememberForMs);
return { canonicalKey, encryptedKeys: updated };
}
export async function saveEncryptedKeys(
encryptedKeys: Record<string, string>,
): Promise<void> {
await post("/user", { encryptedKeys });
}
export function passwordMethodId(): string {
return "password";
}
export function passkeyMethodId(credentialId: string): string {
return `passkey:${credentialId}`;
}
export function nostrMethodId(pubkey: string): string {
return `nostr:${pubkey}`;
}
|