UNPKG

jose

Version:

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

178 lines (177 loc) 6.63 kB
import { assertNotSet } from '../../lib/helpers.js'; import { JWEInvalid } from '../../util/errors.js'; import { generateCek } from '../../lib/content_encryption.js'; import { encryptKeyManagement } from '../../lib/key_management.js'; import { encode as b64u } from '../../util/base64url.js'; import { checkDisjoint, checkEncryptHeaders, createJWE, encryptJWE } from '../../lib/jwe_encrypt.js'; import { prepareKey } from '../../lib/key.js'; import { jweAlgorithm } from '../../lib/jwe_algorithms.js'; import { assertUint8Array } from '../../lib/type_checks.js'; class IndividualRecipient { #parent; state; constructor(enc, key, crit) { this.#parent = enc; this.state = [undefined, undefined, key, crit]; } setUnprotectedHeader(unprotectedHeader) { assertNotSet(this.state[0], 'setUnprotectedHeader'); this.state[0] = unprotectedHeader; return this; } setKeyManagementParameters(parameters) { assertNotSet(this.state[1], 'setKeyManagementParameters'); this.state[1] = parameters; return this; } addRecipient(...args) { return this.#parent.addRecipient(...args); } encrypt(...args) { return this.#parent.encrypt(...args); } done() { return this.#parent; } } function copyOptionalMembers(flattened, jwe, recipient) { const { aad, protected: protectedHeader, unprotected, header } = flattened; if (aad) jwe.aad = aad; if (protectedHeader) jwe.protected = protectedHeader; if (unprotected) jwe.unprotected = unprotected; if (header) recipient.header = header; } export class GeneralEncrypt { #plaintext; #recipients = []; #protectedHeader; #unprotectedHeader; #aad; constructor(plaintext) { this.#plaintext = plaintext; } addRecipient(key, options) { const recipient = new IndividualRecipient(this, key, options?.crit); this.#recipients.push(recipient); return recipient; } setProtectedHeader(protectedHeader) { assertNotSet(this.#protectedHeader, 'setProtectedHeader'); this.#protectedHeader = protectedHeader; return this; } setSharedUnprotectedHeader(sharedUnprotectedHeader) { assertNotSet(this.#unprotectedHeader, 'setSharedUnprotectedHeader'); this.#unprotectedHeader = sharedUnprotectedHeader; return this; } setAdditionalAuthenticatedData(aad) { this.#aad = aad; return this; } async encrypt() { if (!this.#recipients.length) { throw new JWEInvalid('at least one recipient must be added'); } assertUint8Array(this.#plaintext, 'plaintext'); if (this.#recipients.length === 1) { const [unprotectedHeader, keyManagementParameters, key, crit] = this.#recipients[0].state; const flattened = await createJWE([ this.#plaintext, this.#protectedHeader, unprotectedHeader, this.#unprotectedHeader, this.#aad, undefined, undefined, keyManagementParameters, crit, false, ], key); const jwe = { ciphertext: flattened.ciphertext, iv: flattened.iv, recipients: [{}], tag: flattened.tag, }; if (flattened.encrypted_key) jwe.recipients[0].encrypted_key = flattened.encrypted_key; copyOptionalMembers(flattened, jwe, jwe.recipients[0]); return jwe; } let enc; let protectedHeader = this.#protectedHeader; let sharedUnprotectedHeader = this.#unprotectedHeader; const inputs = []; const checked = []; for (let i = 0; i < this.#recipients.length; i++) { const recipient = this.#recipients[i]; const [unprotectedHeader, keyManagementParameters, , crit] = recipient.state; const input = [ this.#plaintext, protectedHeader, unprotectedHeader, sharedUnprotectedHeader, this.#aad, undefined, undefined, keyManagementParameters, crit, true, ]; const headers = checkEncryptHeaders(input); inputs.push(input); checked.push(headers); if (i === 0) { protectedHeader = input[1]; sharedUnprotectedHeader = input[3]; } if (headers[1] === 'dir' || headers[1] === 'ECDH-ES') { throw new JWEInvalid(`"${headers[1]}" alg may only have a single recipient`); } if (!enc) { enc = headers[2]; } else if (enc !== headers[2]) { throw new JWEInvalid('JWE "enc" (Encryption Algorithm) Header Parameter must be the same for all recipients'); } } const cek = generateCek(checked[0][3]); const jwe = { ciphertext: '', recipients: [], }; for (let i = 0; i < this.#recipients.length; i++) { const recipient = this.#recipients[i]; const [, keyManagementParameters, key] = recipient.state; const target = {}; jwe.recipients.push(target); if (i === 0) { inputs[0][5] = cek; const flattened = await encryptJWE(inputs[0], checked[0], key); jwe.ciphertext = flattened.ciphertext; jwe.iv = flattened.iv; jwe.tag = flattened.tag; target.encrypted_key = flattened.encrypted_key; copyOptionalMembers(flattened, jwe, target); continue; } const [, alg, , encEntry] = checked[i]; const unprotectedHeader = inputs[i][2]; const k = await prepareKey(jweAlgorithm(alg), key, 'encrypt'); const [, encryptedKey, parameters] = await encryptKeyManagement(alg, encEntry, k, cek, keyManagementParameters); target.encrypted_key = b64u(encryptedKey); if (unprotectedHeader || parameters) { const header = { ...unprotectedHeader, ...parameters }; if (parameters) checkDisjoint(inputs[i][1], header, inputs[i][3]); target.header = header; } } return jwe; } }