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
JavaScript
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
};