UNPKG

jose

Version:

JWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno, Bun, and other Web-interoperable runtimes

188 lines (187 loc) • 9.05 kB
import { decrypt, generateCek } from "./content_encryption.js"; import { decodeBase64url, encodeAsciiMember, parseJoseHeader, isDisjoint, isObject, validateCrit, validateAlgorithms, JWE_RECOGNIZED } from "./validate.js"; import { JOSEAlgNotAllowed, JOSENotSupported, JWEInvalid } from "../util/errors.js"; import { decryptKeyManagement } from "./key_management.js"; import { decoder } from "./buffer_utils.js"; import { prepareKey } from "./key.js"; import { JWE, isJWECEKTransport, jweAlgorithm, jweEncryption } from "./jwe_algorithms.js"; import { decompress, validateZip } from "./deflate.js"; function snapshotSharedJWE(jwe) { const { aad, ciphertext, iv, protected: encodedProtected, tag, unprotected } = jwe; if (iv !== void 0 && (typeof iv != "string" || !iv)) throw new JWEInvalid("JWE Initialization Vector incorrect type"); if (typeof ciphertext != "string") throw new JWEInvalid("JWE Ciphertext missing or incorrect type"); if (tag !== void 0 && (typeof tag != "string" || !tag)) throw new JWEInvalid("JWE Authentication Tag incorrect type"); if (encodedProtected !== void 0 && typeof encodedProtected != "string") throw new JWEInvalid("JWE Protected Header incorrect type"); if (aad !== void 0 && (typeof aad != "string" || !aad)) throw new JWEInvalid("JWE AAD incorrect type"); if (unprotected !== void 0 && !isObject(unprotected)) throw new JWEInvalid("JWE Shared Unprotected Header incorrect type"); return { aad, ciphertext, iv, protected: encodedProtected, tag, unprotected: unprotected === void 0 ? void 0 : { ...unprotected } }; } function snapshotRecipientJWE(recipient) { let header, headerAlg; try { const { header: inputHeader } = recipient; if (isObject(inputHeader)) { headerAlg = inputHeader.alg; const parameters = Object.keys(inputHeader); parameters.includes("alg") || (headerAlg = void 0), header = Object.fromEntries(parameters.map((parameter) => [ parameter, parameter === "alg" ? headerAlg : inputHeader[parameter] ])); } else header = inputHeader; const { encrypted_key: encryptedKey } = recipient; return [{ encrypted_key: encryptedKey, header }, headerAlg]; } catch (error) { return [void 0, headerAlg, error]; } } function checkRecipient(jwe) { const { encrypted_key: encryptedKey, header } = jwe; if (encryptedKey !== void 0 && typeof encryptedKey != "string") throw new JWEInvalid("JWE Encrypted Key incorrect type"); if (header !== void 0 && !isObject(header)) throw new JWEInvalid("JWE Per-Recipient Unprotected Header incorrect type"); if (jwe.protected === void 0 && header === void 0 && jwe.unprotected === void 0) throw new JWEInvalid("JOSE Header missing"); } function shareJWE(jwe) { const { protected: encodedProtected, ciphertext, iv, tag, aad } = jwe; let protectedHeader; return encodedProtected !== void 0 && (protectedHeader = parseJoseHeader(encodedProtected, JWEInvalid, "JWE Protected Header is invalid")), [ protectedHeader, decodeBase64url(ciphertext, "ciphertext", JWEInvalid), iv !== void 0 ? decodeBase64url(iv, "iv", JWEInvalid) : void 0, tag !== void 0 ? decodeBase64url(tag, "tag", JWEInvalid) : void 0, encodeAsciiMember((encodedProtected ?? "") + (aad !== void 0 ? `.${aad}` : ""), "aad", JWEInvalid) ]; } function prepareDecrypt(options) { return [ options && validateAlgorithms("keyManagementAlgorithms", options.keyManagementAlgorithms), options && validateAlgorithms("contentEncryptionAlgorithms", options.contentEncryptionAlgorithms), options?.crit, options?.maxPBES2Count, options?.maxDecompressedLength ]; } async function decryptJWE(jwe, shared, key, token = shareJWE(jwe)) { const [protectedHeader, ciphertext, iv, tag, additionalAuthenticatedData] = token, { header, unprotected, aad } = jwe; let joseHeader; if (header !== void 0 || unprotected !== void 0) { if (!isDisjoint(protectedHeader, header, unprotected)) throw new JWEInvalid("JWE Protected, JWE Unprotected Header, and JWE Per-Recipient Unprotected Header Parameter names must be disjoint"); joseHeader = { ...protectedHeader, ...header, ...unprotected }; } else joseHeader = protectedHeader ?? {}; const [keyManagementAlgorithms, contentEncryptionAlgorithms, crit, maxPBES2Count, maxDecompressedLength] = shared, { encrypted_key: encodedKey } = jwe; validateCrit(JWEInvalid, JWE_RECOGNIZED, crit, protectedHeader, joseHeader), validateZip(joseHeader, protectedHeader); const { alg, enc } = joseHeader; if (typeof alg != "string" || !alg) throw new JWEInvalid("missing JWE Algorithm (alg) in JWE Header"); const selected = JWE[alg]; if (encodedKey === "" && (!selected || !isJWECEKTransport(selected))) throw new JWEInvalid("JWE Encrypted Key incorrect type"); const integrated = selected?.mode === "integrated-encryption"; if (!integrated && (typeof enc != "string" || !enc)) throw new JWEInvalid("missing JWE Encryption Algorithm (enc) in JWE Header"); if (keyManagementAlgorithms && !keyManagementAlgorithms.has(alg) || !keyManagementAlgorithms && alg.startsWith("PBES2")) throw new JOSEAlgNotAllowed('"alg" (Algorithm) Header Parameter value not allowed'); let encEntry; if (integrated) { if (enc !== void 0) throw new JWEInvalid('JWE "enc" (Encryption Algorithm) Header Parameter must not be present for integrated encryption'); if (iv?.byteLength) throw new JWEInvalid("JWE Initialization Vector must be empty for integrated encryption"); if (tag?.byteLength) throw new JWEInvalid("JWE Authentication Tag must be empty for integrated encryption"); } else { if (contentEncryptionAlgorithms && !contentEncryptionAlgorithms.has(enc)) throw new JOSEAlgNotAllowed('"enc" (Encryption Algorithm) Header Parameter value not allowed'); encEntry = jweEncryption(enc); } let encryptedKey; if (encodedKey !== void 0) try { encryptedKey = decodeBase64url(encodedKey, "encrypted_key", JWEInvalid); } catch (error) { if (!selected || !isJWECEKTransport(selected)) throw error; encryptedKey = new Uint8Array(); } let resolvedKey = !1; typeof key == "function" && (key = await key(protectedHeader, jwe), resolvedKey = !0); const algEntry = selected ?? jweAlgorithm(alg); isJWECEKTransport(algEntry) && encryptedKey === void 0 && (encryptedKey = new Uint8Array()); const k = await prepareKey(algEntry.mode === "direct-encryption" ? encEntry : algEntry, key, "decrypt"); let plaintext; if (algEntry.mode === "integrated-encryption") plaintext = await algEntry.decrypt(k, encryptedKey, ciphertext, additionalAuthenticatedData, protectedHeader, joseHeader); else { const encryption = encEntry; let cek; try { cek = await decryptKeyManagement(algEntry, encryption, k, encryptedKey, joseHeader, maxPBES2Count), isJWECEKTransport(algEntry) && cek instanceof Uint8Array && cek.byteLength << 3 !== encryption.cekBits && (cek = generateCek(encryption)); } catch (err) { if (err instanceof TypeError || err instanceof JWEInvalid || err instanceof JOSENotSupported) throw err; cek = generateCek(encryption); } plaintext = await decrypt(encryption, cek, ciphertext, iv, tag, additionalAuthenticatedData); } if (joseHeader.zip === "DEF") { const decompressionLimit = maxDecompressedLength ?? 25e4; if (decompressionLimit === 0) throw new JOSENotSupported('JWE "zip" (Compression Algorithm) Header Parameter is not supported.'); if (decompressionLimit !== 1 / 0 && (!Number.isSafeInteger(decompressionLimit) || decompressionLimit < 1)) throw new TypeError("maxDecompressedLength must be 0, a positive safe integer, or Infinity"); plaintext = await decompress(plaintext, decompressionLimit).catch((cause) => { throw cause instanceof JWEInvalid ? cause : new JWEInvalid("Failed to decompress plaintext", { cause }); }); } return { plaintext, ...protectedHeader && { protectedHeader }, ...aad !== void 0 && { additionalAuthenticatedData: decodeBase64url(aad, "aad", JWEInvalid) }, ...unprotected && { sharedUnprotectedHeader: unprotected }, ...header && { unprotectedHeader: header }, ...resolvedKey && { key: k } }; } async function decryptCompact(jwe, shared, key) { if (jwe instanceof Uint8Array && (jwe = decoder.decode(jwe)), typeof jwe != "string") throw new JWEInvalid("Compact JWE must be a string or Uint8Array"); const { 0: protectedHeader, 1: encryptedKey, 2: iv, 3: ciphertext, 4: tag, length } = jwe.split("."); if (length !== 5) throw new JWEInvalid("Invalid Compact JWE"); return decryptJWE({ ciphertext, iv: iv || void 0, protected: protectedHeader, tag: tag || void 0, encrypted_key: encryptedKey || void 0 }, shared, key); } export { checkRecipient, decryptCompact, decryptJWE, prepareDecrypt, shareJWE, snapshotRecipientJWE, snapshotSharedJWE };