UNPKG

jose

Version:

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

143 lines (142 loc) 6.01 kB
import { encode as b64u } from '../util/base64url.js'; import { encrypt } from './content_encryption.js'; import { encryptKeyManagement } from './key_management.js'; import { JWEInvalid } from '../util/errors.js'; import { assertUint8Array, isDisjoint, isObject } from './type_checks.js'; import { concat, encode } from './buffer_utils.js'; import { serializeJoseHeader, validateCrit, validateCritDuplicates, JWE_RECOGNIZED, } from './options.js'; import { prepareKey } from './key.js'; import { jweAlgorithm, jweEncryption } from './jwe_algorithms.js'; import { compress, validateZip } from './deflate.js'; import { unprotected } from './helpers.js'; export function checkDisjoint(protectedHeader, unprotectedHeader, sharedUnprotectedHeader) { if (!isDisjoint(protectedHeader, unprotectedHeader, sharedUnprotectedHeader)) { throw new JWEInvalid('JWE Protected, JWE Shared Unprotected and JWE Per-Recipient Header Parameter names must be disjoint'); } } export function checkEncryptHeaders(input) { let [, protectedHeader, unprotectedHeader, sharedUnprotectedHeader, aad, cek, iv, keyManagementParameters, crit,] = input; if (aad !== undefined) { assertUint8Array(aad, 'JWE Additional Authenticated Data'); } if (cek !== undefined) { assertUint8Array(cek, 'JWE Content Encryption Key'); } if (iv !== undefined) { assertUint8Array(iv, 'JWE Initialization Vector'); } if (protectedHeader !== undefined) { protectedHeader = serializeJoseHeader(JWEInvalid, protectedHeader)[0]; input[1] = protectedHeader; } if (unprotectedHeader !== undefined) { unprotectedHeader = serializeJoseHeader(JWEInvalid, unprotectedHeader)[0]; input[2] = unprotectedHeader; } if (sharedUnprotectedHeader !== undefined) { sharedUnprotectedHeader = serializeJoseHeader(JWEInvalid, sharedUnprotectedHeader)[0]; input[3] = sharedUnprotectedHeader; } if (keyManagementParameters !== undefined && !isObject(keyManagementParameters)) { throw new TypeError('JWE Key Management Parameters must be an object'); } checkDisjoint(protectedHeader, unprotectedHeader, sharedUnprotectedHeader); const joseHeader = { ...protectedHeader, ...unprotectedHeader, ...sharedUnprotectedHeader, }; validateCritDuplicates(JWEInvalid, protectedHeader); validateCrit(JWEInvalid, JWE_RECOGNIZED, crit, protectedHeader, joseHeader); validateZip(joseHeader, protectedHeader); const { alg, enc } = joseHeader; if (typeof alg !== 'string' || !alg) { throw new JWEInvalid('JWE "alg" (Algorithm) Header Parameter missing or invalid'); } if (typeof enc !== 'string' || !enc) { throw new JWEInvalid('JWE "enc" (Encryption Algorithm) Header Parameter missing or invalid'); } return [joseHeader, alg, enc, jweEncryption(enc)]; } export async function encryptJWE(input, checked, key) { const [joseHeader, alg, , encEntry] = checked; const [inputPlaintext, inputProtectedHeader, inputUnprotectedHeader, sharedUnprotectedHeader, aad, providedCek, inputIv, keyManagementParameters, , unprotectedParameters,] = input; let protectedHeader = inputProtectedHeader; let unprotectedHeader = inputUnprotectedHeader; if (providedCek && (alg === 'dir' || alg === 'ECDH-ES')) { throw new TypeError(`setContentEncryptionKey cannot be called with JWE "alg" (Algorithm) Header ${alg}`); } const algEntry = jweAlgorithm(alg); const k = await prepareKey(alg === 'dir' ? encEntry : algEntry, key, 'encrypt'); const [cek, encryptedKey, parameters] = await encryptKeyManagement(alg, encEntry, k, providedCek, keyManagementParameters); if (parameters) { if (unprotectedParameters) { unprotectedHeader = unprotectedHeader ? { ...unprotectedHeader, ...parameters } : parameters; } else { protectedHeader = protectedHeader ? { ...protectedHeader, ...parameters } : parameters; } checkDisjoint(protectedHeader, unprotectedHeader, sharedUnprotectedHeader); } let protectedHeaderS; let protectedHeaderB; if (protectedHeader) { protectedHeaderS = b64u(JSON.stringify(protectedHeader)); protectedHeaderB = encode(protectedHeaderS); } else { protectedHeaderS = ''; protectedHeaderB = new Uint8Array(); } let additionalData; let aadMember; if (aad?.byteLength) { aadMember = b64u(aad); additionalData = concat(protectedHeaderB, encode('.'), encode(aadMember)); } else { additionalData = protectedHeaderB; } let plaintext = inputPlaintext; if (joseHeader.zip === 'DEF') { plaintext = await compress(plaintext).catch((cause) => { throw new JWEInvalid('Failed to compress plaintext', { cause }); }); } const { ciphertext, tag, iv } = await encrypt(encEntry, plaintext, cek, inputIv, additionalData); const jwe = { ciphertext: b64u(ciphertext), }; if (iv) { jwe.iv = b64u(iv); } if (tag) { jwe.tag = b64u(tag); } if (encryptedKey) { jwe.encrypted_key = b64u(encryptedKey); } if (aadMember) { jwe.aad = aadMember; } if (protectedHeader) { jwe.protected = protectedHeaderS; } if (sharedUnprotectedHeader) { jwe.unprotected = sharedUnprotectedHeader; } if (unprotectedHeader) { jwe.header = unprotectedHeader; } return jwe; } export async function createJWE(input, key, options) { if (!input[1] && !input[2] && !input[3]) { throw new JWEInvalid('either setProtectedHeader, setUnprotectedHeader, or sharedUnprotectedHeader must be called before #encrypt()'); } if (options !== undefined) { input[8] = options?.crit; input[9] = options ? unprotected in options : false; } return encryptJWE(input, checkEncryptHeaders(input), key); }