|
import { concat, uint64be } from './buffer_utils.js'; |
|
import checkIvLength from './check_iv_length.js'; |
|
import checkCekLength from './check_cek_length.js'; |
|
import { JOSENotSupported, JWEDecryptionFailed, JWEInvalid } from '../util/errors.js'; |
|
import { checkEncCryptoKey } from './crypto_key.js'; |
|
import invalidKeyInput from './invalid_key_input.js'; |
|
import { isCryptoKey } from './is_key_like.js'; |
|
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) { |
|
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, ['decrypt']); |
|
const macKey = await crypto.subtle.importKey('raw', cek.subarray(0, keySize >> 3), { |
|
hash: `SHA-${keySize << 1}`, |
|
name: 'HMAC', |
|
}, false, ['sign']); |
|
const macData = concat(aad, iv, ciphertext, uint64be(aad.length << 3)); |
|
const expectedTag = new Uint8Array((await crypto.subtle.sign('HMAC', macKey, macData)).slice(0, keySize >> 3)); |
|
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, name: 'AES-CBC' }, encKey, ciphertext)); |
|
} |
|
catch { |
|
} |
|
if (!plaintext) { |
|
throw new JWEDecryptionFailed(); |
|
} |
|
return plaintext; |
|
} |
|
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, |
|
name: 'AES-GCM', |
|
tagLength: 128, |
|
}, encKey, concat(ciphertext, tag))); |
|
} |
|
catch { |
|
throw new JWEDecryptionFailed(); |
|
} |
|
} |
|
export default async (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('Unsupported JWE Content Encryption Algorithm'); |
|
} |
|
}; |
|
|