{"version":3,"file":"encrypt.mjs","names":["protectedHeader: Record<string, unknown>","cek: Uint8Array | undefined","recipientEntries: JweGeneralJson['recipients']","out: JweGeneralJson","header: Record<string, unknown>"],"sources":["../../src/jwe/encrypt.ts"],"sourcesContent":["/**\n * JWE General JSON Serialization encryption (RFC 7516 §7.2).\n *\n * One ciphertext, many recipients. Direct Key Agreement (ECDH-ES)\n * permits exactly one recipient; key-wrap algs (ECDH-ES+A256KW, when\n * added) mix freely.\n */\n\nimport { gcm } from '@noble/ciphers/aes'\n\nimport { b64uEncode } from './b64url'\n\n/**\n * Cross-platform CSPRNG. Web Crypto API is available natively in\n * Node 18+, browsers, and React Native (via the polyfill credo-ts\n * already requires). Throws if no Web Crypto is reachable rather\n * than silently producing a non-random IV.\n */\nfunction randomBytes(n: number): Uint8Array {\n  const webcrypto = (globalThis as { crypto?: { getRandomValues?: (a: Uint8Array) => Uint8Array } })\n    .crypto\n  if (!webcrypto?.getRandomValues) {\n    throw new Error('JWE encrypt: no Web Crypto getRandomValues available for CSPRNG')\n  }\n  const out = new Uint8Array(n)\n  webcrypto.getRandomValues(out)\n  return out\n}\nimport { cekLengthForEnc, resolveAlg } from './algs'\nimport {\n  ALG_ECDH_ES,\n  ENC_A256GCM,\n  JweAlgUnsupportedError,\n  JweEncryptError,\n  type JweGeneralJson,\n  type Recipient,\n} from './types'\n\nconst DIRECT_KEY_AGREEMENT_ALGS = new Set([ALG_ECDH_ES])\n\nconst textEncoder = new TextEncoder()\n\nexport function encrypt({\n  plaintext,\n  recipients,\n  enc = ENC_A256GCM,\n  aad = new Uint8Array(0),\n  protectedHeaderExtra,\n  apu = new Uint8Array(0),\n  apv = new Uint8Array(0),\n}: {\n  plaintext: Uint8Array\n  recipients: Recipient[]\n  enc?: string\n  aad?: Uint8Array\n  protectedHeaderExtra?: Record<string, unknown>\n  apu?: Uint8Array\n  apv?: Uint8Array\n}): JweGeneralJson {\n  if (!recipients.length) {\n    throw new JweEncryptError('recipients must be non-empty')\n  }\n\n  // Default each recipient's alg if unset.\n  const normalized = recipients.map((r) => ({\n    ...r,\n    alg: r.alg ?? ALG_ECDH_ES,\n  }))\n  const algs = new Set(normalized.map((r) => r.alg))\n  // ECDH-ES Direct Key Agreement derives the CEK from the recipient's\n  // public key, so two recipients ⇒ two CEKs ⇒ can't share the bulk\n  // ciphertext. Spec-conformant senders MUST refuse this combo.\n  const usesDirect = [...algs].some((a) => DIRECT_KEY_AGREEMENT_ALGS.has(a))\n  if (usesDirect && normalized.length > 1) {\n    throw new JweEncryptError(\n      'ECDH-ES Direct Key Agreement requires exactly one recipient; use a key-wrap alg for multi-recipient'\n    )\n  }\n\n  // Protected header. `enc` is required; `alg` lands here only for\n  // single-recipient flows because in multi-recipient flows each\n  // recipient carries its own per-header alg.\n  const protectedHeader: Record<string, unknown> = { ...(protectedHeaderExtra ?? {}) }\n  protectedHeader.enc = enc\n  if (normalized.length === 1) {\n    protectedHeader.alg = normalized[0].alg\n    if (normalized[0].kid && protectedHeader.kid === undefined) {\n      protectedHeader.kid = normalized[0].kid\n    }\n  }\n\n  const cekLen = cekLengthForEnc(enc)\n  let cek: Uint8Array | undefined\n  const recipientEntries: JweGeneralJson['recipients'] = []\n\n  if (normalized.length === 1 && DIRECT_KEY_AGREEMENT_ALGS.has(normalized[0].alg)) {\n    const { sender } = resolveAlg(normalized[0].alg)\n    const agreement = sender({\n      recipientPublicKey: normalized[0].publicKey,\n      enc,\n      apu,\n      apv,\n    })\n    cek = agreement.cek\n    recipientEntries.push(buildRecipientEntry(normalized[0], agreement, /* dropAlg */ true))\n  } else {\n    cek = randomBytes(cekLen)\n    for (const r of normalized) {\n      if (DIRECT_KEY_AGREEMENT_ALGS.has(r.alg)) {\n        throw new JweEncryptError(`alg ${r.alg} cannot be used in multi-recipient JWE`)\n      }\n      const { sender } = resolveAlg(r.alg)\n      const agreement = sender({\n        recipientPublicKey: r.publicKey,\n        enc,\n        apu,\n        apv,\n      })\n      recipientEntries.push(buildRecipientEntry(r, agreement, /* dropAlg */ false))\n    }\n  }\n\n  if (!cek || cek.length !== cekLen) {\n    throw new JweEncryptError(`CEK length mismatch (got ${cek?.length}, expected ${cekLen})`)\n  }\n\n  const iv = randomBytes(12)\n  const protectedB64 = b64uEncode(textEncoder.encode(canonicalJson(protectedHeader)))\n\n  // AAD per RFC 7516 §5.1 step 14:\n  // ASCII(BASE64URL(protected)) + '.' + BASE64URL(aad)\n  const aeadAad =\n    aad.length > 0\n      ? textEncoder.encode(protectedB64 + '.' + b64uEncode(aad))\n      : textEncoder.encode(protectedB64)\n\n  if (enc !== ENC_A256GCM) {\n    throw new JweAlgUnsupportedError(`only enc=A256GCM supported in this build (got ${JSON.stringify(enc)})`)\n  }\n  const cipher = gcm(cek, iv, aeadAad).encrypt(plaintext)\n  const ciphertext = cipher.subarray(0, cipher.length - 16)\n  const tag = cipher.subarray(cipher.length - 16)\n\n  const out: JweGeneralJson = {\n    protected: protectedB64,\n    recipients: recipientEntries,\n    iv: b64uEncode(iv),\n    ciphertext: b64uEncode(ciphertext),\n    tag: b64uEncode(tag),\n  }\n  if (aad.length > 0) {\n    out.aad = b64uEncode(aad)\n  }\n  return out\n}\n\nfunction buildRecipientEntry(\n  r: { kid: string; alg: string },\n  agreement: { encryptedKey: Uint8Array; headerExtra: Record<string, unknown> },\n  dropAlg: boolean\n): JweGeneralJson['recipients'][number] {\n  const header: Record<string, unknown> = {}\n  if (!dropAlg) header.alg = r.alg\n  if (r.kid) header.kid = r.kid\n  for (const [k, v] of Object.entries(agreement.headerExtra)) {\n    header[k] = v\n  }\n  return {\n    header,\n    encrypted_key: agreement.encryptedKey.length > 0 ? b64uEncode(agreement.encryptedKey) : '',\n  }\n}\n\n/** RFC 8785 JCS-style canonical JSON for the protected header. */\nfunction canonicalJson(value: unknown): string {\n  if (value === null) return 'null'\n  if (typeof value === 'boolean') return value ? 'true' : 'false'\n  if (typeof value === 'number') {\n    if (!Number.isFinite(value)) {\n      throw new JweEncryptError('non-finite numbers are not allowed in JWE protected headers')\n    }\n    return String(value)\n  }\n  if (typeof value === 'string') {\n    return JSON.stringify(value)\n  }\n  if (Array.isArray(value)) {\n    return '[' + value.map((v) => canonicalJson(v)).join(',') + ']'\n  }\n  if (typeof value === 'object') {\n    const entries = Object.entries(value as Record<string, unknown>).sort(([a], [b]) =>\n      a < b ? -1 : a > b ? 1 : 0\n    )\n    return '{' + entries.map(([k, v]) => JSON.stringify(k) + ':' + canonicalJson(v)).join(',') + '}'\n  }\n  throw new JweEncryptError(`cannot serialize ${typeof value} into a JWE header`)\n}\n\nexport { canonicalJson as canonicalJsonForTest }\n"],"mappings":"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"}