| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- import { concat, uint64be } from './buffer_utils.js';
- import { checkEncCryptoKey } from './crypto_key.js';
- import { invalidKeyInput } from './invalid_key_input.js';
- import { JOSENotSupported, JWEDecryptionFailed, JWEInvalid } from '../util/errors.js';
- import { isCryptoKey } from './is_key_like.js';
- export function cekLength(alg) {
- switch (alg) {
- case 'A128GCM':
- return 128;
- case 'A192GCM':
- return 192;
- case 'A256GCM':
- case 'A128CBC-HS256':
- return 256;
- case 'A192CBC-HS384':
- return 384;
- case 'A256CBC-HS512':
- return 512;
- default:
- throw new JOSENotSupported(`Unsupported JWE Algorithm: ${alg}`);
- }
- }
- export const generateCek = (alg) => crypto.getRandomValues(new Uint8Array(cekLength(alg) >> 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`);
- }
- }
- function ivBitLength(alg) {
- switch (alg) {
- case 'A128GCM':
- case 'A128GCMKW':
- case 'A192GCM':
- case 'A192GCMKW':
- case 'A256GCM':
- case 'A256GCMKW':
- return 96;
- case 'A128CBC-HS256':
- case 'A192CBC-HS384':
- case 'A256CBC-HS512':
- return 128;
- default:
- throw new JOSENotSupported(`Unsupported JWE Algorithm: ${alg}`);
- }
- }
- export const generateIv = (alg) => crypto.getRandomValues(new Uint8Array(ivBitLength(alg) >> 3));
- export function checkIvLength(enc, iv) {
- if (iv.length << 3 !== ivBitLength(enc)) {
- throw new JWEInvalid('Invalid Initialization Vector length');
- }
- }
- async function cbcKeySetup(enc, cek, usage) {
- if (!(cek instanceof Uint8Array)) {
- throw new TypeError(invalidKeyInput(cek, 'Uint8Array'));
- }
- const keySize = parseInt(enc.slice(1, 4), 10);
- const encKey = await crypto.subtle.importKey('raw', cek.subarray(keySize >> 3), 'AES-CBC', false, [usage]);
- const macKey = await crypto.subtle.importKey('raw', cek.subarray(0, keySize >> 3), {
- hash: `SHA-${keySize << 1}`,
- name: 'HMAC',
- }, false, ['sign']);
- return { encKey, macKey, keySize };
- }
- async function cbcHmacTag(macKey, macData, keySize) {
- return new Uint8Array((await crypto.subtle.sign('HMAC', macKey, macData)).slice(0, keySize >> 3));
- }
- async function cbcEncrypt(enc, plaintext, cek, iv, aad) {
- const { encKey, macKey, keySize } = await cbcKeySetup(enc, cek, 'encrypt');
- const ciphertext = new Uint8Array(await crypto.subtle.encrypt({
- iv: iv,
- name: 'AES-CBC',
- }, encKey, plaintext));
- const macData = concat(aad, iv, ciphertext, uint64be(aad.length << 3));
- const tag = await cbcHmacTag(macKey, macData, keySize);
- return { ciphertext, tag, iv };
- }
- async function timingSafeEqual(a, b) {
- if (!(a instanceof Uint8Array)) {
- throw new TypeError('First argument must be a buffer');
- }
- if (!(b instanceof Uint8Array)) {
- throw new TypeError('Second argument must be a buffer');
- }
- const algorithm = { name: 'HMAC', hash: 'SHA-256' };
- const key = (await crypto.subtle.generateKey(algorithm, false, ['sign']));
- const aHmac = new Uint8Array(await crypto.subtle.sign(algorithm, key, a));
- const bHmac = new Uint8Array(await crypto.subtle.sign(algorithm, key, b));
- let out = 0;
- let i = -1;
- while (++i < 32) {
- out |= aHmac[i] ^ bHmac[i];
- }
- return out === 0;
- }
- async function cbcDecrypt(enc, cek, ciphertext, iv, tag, aad) {
- const { encKey, macKey, keySize } = await cbcKeySetup(enc, cek, 'decrypt');
- const macData = concat(aad, iv, ciphertext, uint64be(aad.length << 3));
- const expectedTag = await cbcHmacTag(macKey, macData, keySize);
- let macCheckPassed;
- try {
- macCheckPassed = await timingSafeEqual(tag, expectedTag);
- }
- catch {
- }
- if (!macCheckPassed) {
- throw new JWEDecryptionFailed();
- }
- let plaintext;
- try {
- plaintext = new Uint8Array(await crypto.subtle.decrypt({ iv: iv, name: 'AES-CBC' }, encKey, ciphertext));
- }
- catch {
- }
- if (!plaintext) {
- throw new JWEDecryptionFailed();
- }
- return plaintext;
- }
- async function gcmEncrypt(enc, plaintext, cek, iv, aad) {
- let encKey;
- if (cek instanceof Uint8Array) {
- encKey = await crypto.subtle.importKey('raw', cek, 'AES-GCM', false, ['encrypt']);
- }
- else {
- checkEncCryptoKey(cek, enc, 'encrypt');
- encKey = cek;
- }
- const encrypted = new Uint8Array(await crypto.subtle.encrypt({
- additionalData: aad,
- iv: iv,
- name: 'AES-GCM',
- tagLength: 128,
- }, encKey, plaintext));
- const tag = encrypted.slice(-16);
- const ciphertext = encrypted.slice(0, -16);
- return { ciphertext, tag, iv };
- }
- async function gcmDecrypt(enc, cek, ciphertext, iv, tag, aad) {
- let encKey;
- if (cek instanceof Uint8Array) {
- encKey = await crypto.subtle.importKey('raw', cek, 'AES-GCM', false, ['decrypt']);
- }
- else {
- checkEncCryptoKey(cek, enc, 'decrypt');
- encKey = cek;
- }
- try {
- return new Uint8Array(await crypto.subtle.decrypt({
- additionalData: aad,
- iv: iv,
- name: 'AES-GCM',
- tagLength: 128,
- }, encKey, concat(ciphertext, tag)));
- }
- catch {
- throw new JWEDecryptionFailed();
- }
- }
- const unsupportedEnc = 'Unsupported JWE Content Encryption Algorithm';
- export 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);
- }
- else {
- iv = generateIv(enc);
- }
- switch (enc) {
- case 'A128CBC-HS256':
- case 'A192CBC-HS384':
- case 'A256CBC-HS512':
- if (cek instanceof Uint8Array) {
- checkCekLength(cek, parseInt(enc.slice(-3), 10));
- }
- return cbcEncrypt(enc, plaintext, cek, iv, aad);
- case 'A128GCM':
- case 'A192GCM':
- case 'A256GCM':
- if (cek instanceof Uint8Array) {
- checkCekLength(cek, parseInt(enc.slice(1, 4), 10));
- }
- return gcmEncrypt(enc, plaintext, cek, iv, aad);
- default:
- throw new JOSENotSupported(unsupportedEnc);
- }
- }
- export 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');
- }
- checkIvLength(enc, iv);
- switch (enc) {
- case 'A128CBC-HS256':
- case 'A192CBC-HS384':
- case 'A256CBC-HS512':
- if (cek instanceof Uint8Array)
- checkCekLength(cek, parseInt(enc.slice(-3), 10));
- return cbcDecrypt(enc, cek, ciphertext, iv, tag, aad);
- case 'A128GCM':
- case 'A192GCM':
- case 'A256GCM':
- if (cek instanceof Uint8Array)
- checkCekLength(cek, parseInt(enc.slice(1, 4), 10));
- return gcmDecrypt(enc, cek, ciphertext, iv, tag, aad);
- default:
- throw new JOSENotSupported(unsupportedEnc);
- }
- }
|