UNPKG

jose

Version:

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

91 lines (90 loc) • 4.31 kB
import { concat, uint64be } from "./buffer_utils.js"; import { rawKey, invalidKeyInput, isCryptoKey } from "./key.js"; import { JWEDecryptionFailed, JWEInvalid } from "../util/errors.js"; const generateCek = (enc) => crypto.getRandomValues(new Uint8Array(enc.cekBits >> 3)); function checkCekLength(cek, expected) { const actual = cek.byteLength << 3; if (actual !== expected) throw new JWEInvalid(`Invalid Content Encryption Key length. Expected ${expected} bits, got ${actual} bits`); } const generateIv = (enc) => crypto.getRandomValues(new Uint8Array(enc.ivBits >> 3)); function checkIvLength(enc, iv) { if (iv.length << 3 !== enc.ivBits) throw new JWEInvalid("Invalid Initialization Vector length"); } async function cbcKeySetup(enc, cek, usage) { if (!(cek instanceof Uint8Array)) throw new TypeError(invalidKeyInput(cek, "Uint8Array")); const keyLengthBits = enc.cekBits >> 1, encKey = await crypto.subtle.importKey("raw", cek.subarray(keyLengthBits >> 3), "AES-CBC", !1, [usage]), macKey = await crypto.subtle.importKey("raw", cek.subarray(0, keyLengthBits >> 3), { hash: `SHA-${keyLengthBits << 1}`, name: "HMAC" }, !1, ["sign"]); return [encKey, macKey, keyLengthBits]; } async function cbcHmacTag(macKey, macData, keyLengthBits) { return new Uint8Array((await crypto.subtle.sign("HMAC", macKey, macData)).slice(0, keyLengthBits >> 3)); } async function cbcEncrypt(enc, plaintext, cek, iv, aad) { const [encKey, macKey, keyLengthBits] = await cbcKeySetup(enc, cek, "encrypt"), ciphertext = new Uint8Array(await crypto.subtle.encrypt({ iv, name: "AES-CBC" }, encKey, plaintext)), al = uint64be(aad.length * 8), macData = concat(aad, iv, ciphertext, al), tag = await cbcHmacTag(macKey, macData, keyLengthBits); return { ciphertext, tag, iv }; } async function timingSafeEqual(a, b) { const algorithm = { name: "HMAC", hash: "SHA-256" }, key = await crypto.subtle.generateKey(algorithm, !1, ["sign", "verify"]), aHmac = await crypto.subtle.sign(algorithm, key, a); return crypto.subtle.verify(algorithm, key, aHmac, b); } async function cbcDecrypt(enc, cek, ciphertext, iv, tag, aad) { const [encKey, macKey, keyLengthBits] = await cbcKeySetup(enc, cek, "decrypt"), al = uint64be(aad.length * 8), macData = concat(aad, iv, ciphertext, al), expectedTag = await cbcHmacTag(macKey, macData, keyLengthBits); try { if (await timingSafeEqual(tag, expectedTag)) return new Uint8Array(await crypto.subtle.decrypt({ iv, name: "AES-CBC" }, encKey, ciphertext)); } catch { } throw new JWEDecryptionFailed(); } async function encrypt(enc, plaintext, cek, iv, aad) { if (!isCryptoKey(cek) && !(cek instanceof Uint8Array)) throw new TypeError(invalidKeyInput(cek, "CryptoKey", "KeyObject", "Uint8Array", "JSON Web Key")); if (iv ? checkIvLength(enc, iv) : iv = generateIv(enc), cek instanceof Uint8Array && checkCekLength(cek, enc.cekBits), enc.cbc) return cbcEncrypt(enc, plaintext, cek, iv, aad); const encKey = await rawKey(cek, enc.subtle, "encrypt"), encrypted = new Uint8Array(await crypto.subtle.encrypt({ additionalData: aad, iv, name: "AES-GCM", tagLength: 128 }, encKey, plaintext)); return { ciphertext: encrypted.subarray(0, -16), tag: encrypted.subarray(-16), iv }; } async function decrypt(enc, cek, ciphertext, iv, tag, aad) { if (!isCryptoKey(cek) && !(cek instanceof Uint8Array)) throw new TypeError(invalidKeyInput(cek, "CryptoKey", "KeyObject", "Uint8Array", "JSON Web Key")); if (!iv) throw new JWEInvalid("JWE Initialization Vector missing"); if (!tag) throw new JWEInvalid("JWE Authentication Tag missing"); if (!enc.cbc && tag.length !== 16) throw new JWEInvalid("Invalid Authentication Tag length"); if (checkIvLength(enc, iv), cek instanceof Uint8Array && checkCekLength(cek, enc.cekBits), enc.cbc) return cbcDecrypt(enc, cek, ciphertext, iv, tag, aad); const encKey = await rawKey(cek, enc.subtle, "decrypt"); try { return new Uint8Array(await crypto.subtle.decrypt({ additionalData: aad, iv, name: "AES-GCM", tagLength: 128 }, encKey, concat(ciphertext, tag))); } catch { throw new JWEDecryptionFailed(); } } export { checkCekLength, checkIvLength, decrypt, encrypt, generateCek, generateIv };