jose
Version:
JWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno, Bun, and other Web-interoperable runtimes
76 lines (75 loc) • 5.97 kB
JavaScript
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, serializeJoseHeader, validateCrit, validateCritDuplicates, JWE_RECOGNIZED } from "./validate.js";
import { encode } from "./buffer_utils.js";
import { prepareKey } from "./key.js";
import { JWE, jweAlgorithm, jweEncryption } from "./jwe_algorithms.js";
import { compress, validateZip } from "./deflate.js";
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");
}
function checkEncryptHeaders(input, options, sharedHeadersNormalized = !1) {
if (!input[1] && !input[2] && !input[3])
throw new JWEInvalid("either setProtectedHeader, setUnprotectedHeader, or sharedUnprotectedHeader must be called before #encrypt()");
options !== void 0 && (input[8] = options?.crit);
let [, protectedHeader, unprotectedHeader, sharedUnprotectedHeader, aad, cek, iv, keyManagementParameters, crit] = input;
if (aad !== void 0 && assertUint8Array(aad, "JWE Additional Authenticated Data"), cek !== void 0 && assertUint8Array(cek, "JWE Content Encryption Key"), iv !== void 0 && assertUint8Array(iv, "JWE Initialization Vector"), !sharedHeadersNormalized && protectedHeader !== void 0 && (protectedHeader = serializeJoseHeader(JWEInvalid, protectedHeader)[0], input[1] = protectedHeader), unprotectedHeader !== void 0 && (unprotectedHeader = serializeJoseHeader(JWEInvalid, unprotectedHeader)[0], input[2] = unprotectedHeader), !sharedHeadersNormalized && sharedUnprotectedHeader !== void 0 && (sharedUnprotectedHeader = serializeJoseHeader(JWEInvalid, sharedUnprotectedHeader)[0], input[3] = sharedUnprotectedHeader), keyManagementParameters !== void 0 && !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');
const algEntry = JWE[alg];
if (algEntry?.mode === "integrated-encryption") {
if (enc !== void 0)
throw new JWEInvalid('JWE "enc" (Encryption Algorithm) Header Parameter must not be present for integrated encryption');
if (cek !== void 0)
throw new TypeError(`setContentEncryptionKey cannot be called with JWE "alg" (Algorithm) Header ${alg}`);
if (iv !== void 0)
throw new TypeError(`setInitializationVector cannot be called with JWE "alg" (Algorithm) Header ${alg}`);
return [joseHeader, void 0, algEntry];
}
if (typeof enc != "string" || !enc)
throw new JWEInvalid('JWE "enc" (Encryption Algorithm) Header Parameter missing or invalid');
return [joseHeader, jweEncryption(enc), algEntry];
}
async function encryptJWE(input, checked, key) {
const [joseHeader, encEntry, selected] = checked, [inputPlaintext, inputProtectedHeader, inputUnprotectedHeader, sharedUnprotectedHeader, aad, providedCek, inputIv, keyManagementParameters, , unprotectedParameters] = input;
let protectedHeader = inputProtectedHeader, unprotectedHeader = inputUnprotectedHeader;
const algEntry = selected ?? jweAlgorithm(joseHeader.alg);
let encryptedKey, parameters, cek;
algEntry.mode === "integrated-encryption" ? cek = await prepareKey(algEntry, key, "encrypt") : [cek, encryptedKey, parameters] = await encryptKeyManagement(algEntry, encEntry, key, joseHeader, providedCek, keyManagementParameters), parameters && (unprotectedParameters ? unprotectedHeader = unprotectedHeader ? { ...unprotectedHeader, ...parameters } : parameters : protectedHeader = protectedHeader ? { ...protectedHeader, ...parameters } : parameters, checkDisjoint(protectedHeader, unprotectedHeader, sharedUnprotectedHeader));
const encodedProtectedHeader = protectedHeader ? b64u(JSON.stringify(protectedHeader)) : "", encodedAad = aad?.byteLength ? b64u(aad) : void 0, additionalAuthenticatedData = encode(encodedAad ? `${encodedProtectedHeader}.${encodedAad}` : encodedProtectedHeader);
let plaintext = inputPlaintext;
joseHeader.zip === "DEF" && (plaintext = await compress(plaintext).catch((cause) => {
throw new JWEInvalid("Failed to compress plaintext", { cause });
}));
let ciphertext, tag, iv;
algEntry.mode === "integrated-encryption" ? [encryptedKey, ciphertext] = await algEntry.encrypt(cek, plaintext, additionalAuthenticatedData, protectedHeader, joseHeader, keyManagementParameters) : { ciphertext, tag, iv } = await encrypt(encEntry, plaintext, cek, inputIv, additionalAuthenticatedData);
const jwe = {
ciphertext: b64u(ciphertext)
};
return iv && (jwe.iv = b64u(iv)), tag && (jwe.tag = b64u(tag)), encryptedKey?.byteLength && (jwe.encrypted_key = b64u(encryptedKey)), encodedAad && (jwe.aad = encodedAad), protectedHeader && (jwe.protected = encodedProtectedHeader), sharedUnprotectedHeader && (jwe.unprotected = sharedUnprotectedHeader), unprotectedHeader && (jwe.header = unprotectedHeader), jwe;
}
async function createJWE(input, key, options) {
return encryptJWE(input, checkEncryptHeaders(input, options), key);
}
function compactJWE(jwe) {
return [jwe.protected, jwe.encrypted_key, jwe.iv, jwe.ciphertext, jwe.tag].join(".");
}
export {
checkDisjoint,
checkEncryptHeaders,
compactJWE,
createJWE,
encryptJWE
};