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