{"version":3,"file":"key-derivation.mjs","names":[],"sources":["../../../../../../encryption/src/key-derivation.ts"],"sourcesContent":["import {\n  getSubtle,\n  toBase64,\n  utf8ToBytes,\n  type CryptoKeyLike,\n} from \"./crypto-runtime\";\n\n/**\n * Derived keys are cached in memory, keyed by the exact\n * (password, salt, iterations) triple that produced them.\n *\n * PBKDF2 at the default work factor costs ~100ms per call, and a browser app\n * that reads a dozen encrypted values from storage on boot would otherwise pay\n * it a dozen times. The cache never widens who can decrypt what: a hit\n * requires the same password AND the same salt, and the cached `CryptoKey` is\n * non-extractable, so it is not a more useful thing to hold than the password\n * the caller already keeps in memory.\n */\nconst MAX_CACHED_KEYS = 64;\n\nconst keyCache = new Map<string, Promise<CryptoKeyLike>>();\n\n/**\n * Drop every cached derived key. Call it on logout, or between tests.\n */\nexport function clearKeyCache(): void {\n  keyCache.clear();\n}\n\nfunction cacheKeyFor(\n  password: string,\n  salt: Uint8Array,\n  iterations: number\n): string {\n  return `${iterations}:${toBase64(salt)}:${password}`;\n}\n\n/**\n * Derive a 256-bit AES-GCM key from a passphrase with PBKDF2-HMAC-SHA256.\n *\n * The derived key is non-extractable: it can encrypt and decrypt through\n * WebCrypto but its bytes cannot be read back out by application code.\n */\nexport async function deriveKey(\n  password: string,\n  salt: Uint8Array,\n  iterations: number\n): Promise<CryptoKeyLike> {\n  const cacheKey = cacheKeyFor(password, salt, iterations);\n  const cached = keyCache.get(cacheKey);\n\n  if (cached) return cached;\n\n  const subtle = getSubtle();\n\n  const derivation = (async () => {\n    const baseKey = await subtle.importKey(\n      \"raw\",\n      utf8ToBytes(password),\n      { name: \"PBKDF2\" },\n      false,\n      [\"deriveKey\"]\n    );\n\n    return subtle.deriveKey(\n      { name: \"PBKDF2\", salt, iterations, hash: \"SHA-256\" },\n      baseKey,\n      { name: \"AES-GCM\", length: 256 },\n      false,\n      [\"encrypt\", \"decrypt\"]\n    );\n  })();\n\n  // A failed derivation must not be memoised, otherwise a transient runtime\n  // error would be replayed to every later caller with the same inputs.\n  derivation.catch(() => keyCache.delete(cacheKey));\n\n  if (keyCache.size >= MAX_CACHED_KEYS) {\n    const oldest = keyCache.keys().next();\n    if (!oldest.done) keyCache.delete(oldest.value);\n  }\n\n  keyCache.set(cacheKey, derivation);\n\n  return derivation;\n}\n"],"mappings":";;;;;;;;;;;;;;AAkBA,MAAM,kBAAkB;AAExB,MAAM,2BAAW,IAAI,IAAoC;;;;AAKzD,SAAgB,gBAAsB;CACpC,SAAS,MAAM;AACjB;AAEA,SAAS,YACP,UACA,MACA,YACQ;CACR,OAAO,GAAG,WAAW,GAAG,SAAS,IAAI,EAAE,GAAG;AAC5C;;;;;;;AAQA,eAAsB,UACpB,UACA,MACA,YACwB;CACxB,MAAM,WAAW,YAAY,UAAU,MAAM,UAAU;CACvD,MAAM,SAAS,SAAS,IAAI,QAAQ;CAEpC,IAAI,QAAQ,OAAO;CAEnB,MAAM,SAAS,UAAU;CAEzB,MAAM,cAAc,YAAY;EAC9B,MAAM,UAAU,MAAM,OAAO,UAC3B,OACA,YAAY,QAAQ,GACpB,EAAE,MAAM,SAAS,GACjB,OACA,CAAC,WAAW,CACd;EAEA,OAAO,OAAO,UACZ;GAAE,MAAM;GAAU;GAAM;GAAY,MAAM;EAAU,GACpD,SACA;GAAE,MAAM;GAAW,QAAQ;EAAI,GAC/B,OACA,CAAC,WAAW,SAAS,CACvB;CACF,EAAC,CAAE;CAIH,WAAW,YAAY,SAAS,OAAO,QAAQ,CAAC;CAEhD,IAAI,SAAS,QAAQ,iBAAiB;EACpC,MAAM,SAAS,SAAS,KAAK,CAAC,CAAC,KAAK;EACpC,IAAI,CAAC,OAAO,MAAM,SAAS,OAAO,OAAO,KAAK;CAChD;CAEA,SAAS,IAAI,UAAU,UAAU;CAEjC,OAAO;AACT"}