UNPKG

@veramo/did-comm

Version:

Veramo messaging plugin implementing DIDComm v2.

109 lines 4.69 kB
import { randomBytes } from '@noble/hashes/utils'; import { hmac } from '@noble/hashes/hmac'; import { sha512 } from '@noble/hashes/sha512'; import { cbc } from '@noble/ciphers/aes'; import { base64ToBytes, bytesToBase64url, concat, encodeBase64url } from '@veramo/utils'; import { fromString } from 'uint8arrays/from-string'; const MAX_INT32 = 2 ** 32; function writeUInt32BE(buf, value, offset) { if (value < 0 || value >= MAX_INT32) { throw new RangeError(`value must be >= 0 and <= ${MAX_INT32 - 1}. Received ${value}`); } buf.set([value >>> 24, value >>> 16, value >>> 8, value & 0xff], offset); } function uint64be(value) { const high = Math.floor(value / MAX_INT32); const low = value % MAX_INT32; const buf = new Uint8Array(8); writeUInt32BE(buf, high, 0); writeUInt32BE(buf, low, 4); return buf; } /** * Code copied and adapted from https://github.com/panva/jose * @param enc - the JWE content encryption algorithm (e.g. A256CBC-HS512) * @param plaintext - the content to encrypt * @param cek - the raw content encryption key * @param providedIV - optional provided Initialization Vector * @param aad - optional additional authenticated data */ async function cbcEncrypt(enc = 'A256CBC-HS512', plaintext, cek, providedIV, aad = new Uint8Array(0)) { // A256CBC-HS512 CEK size should be 512 bits; first 256 bits are used for HMAC with SHA-512 and the rest for AES-CBC const keySize = parseInt(enc.slice(1, 4), 10); const encKey = cek.subarray(keySize >> 3); const macKey = cek.subarray(0, keySize >> 3); if (providedIV && providedIV.length !== keySize >> 4) { throw new Error(`illegal_argument: Invalid IV size, expected ${keySize >> 4}, got ${providedIV.length}`); } const iv = providedIV ?? randomBytes(keySize >> 4); const ciphertext = cbc(encKey, iv).encrypt(plaintext); const macData = concat([aad, iv, ciphertext, uint64be(aad.length << 3)]); const tag = hmac(sha512, macKey, macData).slice(0, keySize >> 3); return { enc: 'dir', ciphertext, tag, iv }; } export function timingSafeEqual(a, b) { if (a.length !== b.length) { return false; } let result = 0; let len = a.length; for (let i = 0; i < len; i++) { result |= a[i] ^ b[i]; } return result === 0; } async function cbcDecrypt(enc = 'A256CBC-HS512', cek, ciphertext, iv, tag, aad) { const keySize = parseInt(enc.slice(1, 4), 10); const encKey = cek.subarray(keySize >> 3); const macKey = cek.subarray(0, keySize >> 3); const macData = concat([aad, iv, ciphertext, uint64be(aad.length << 3)]); const expectedTag = hmac(sha512, macKey, macData).slice(0, keySize >> 3); let macCheckPassed = false; try { macCheckPassed = timingSafeEqual(tag, expectedTag); } catch { // nop } if (!macCheckPassed) { // current JWE decryption pipeline tries to decrypt multiple times with different keys, so return null instead of // throwing an error return null; // throw new Error('jwe_decryption_failed: MAC check failed') } let plaintext = null; try { plaintext = cbc(encKey, iv).decrypt(ciphertext); } catch (e) { // current JWE decryption pipeline tries to decrypt multiple times with different keys, so return null instead of // throwing an error } return plaintext; } export function a256cbcHs512DirDecrypter(key) { // const cipher = new GCM(new AES(key)) async function decrypt(sealed, iv, aad) { // did-jwt#decryptJWE combines the ciphertext and the tag into a single `sealed` array const TAG_LENGTH = 32; const ciphertext = sealed.subarray(0, sealed.length - TAG_LENGTH); const tag = sealed.subarray(sealed.length - TAG_LENGTH); return cbcDecrypt('A256CBC-HS512', key, ciphertext, iv, tag, aad ?? new Uint8Array(0)); } return { alg: 'dir', enc: 'A256GCM', decrypt }; } export function a256cbcHs512DirEncrypter(cek) { const enc = 'A256CBC-HS512'; const alg = 'dir'; async function encrypt(cleartext, protectedHeader = {}, aad) { const protHeader = encodeBase64url(JSON.stringify(Object.assign({ alg }, protectedHeader, { enc }))); const encodedAad = fromString(aad ? `${protHeader}.${bytesToBase64url(aad)}` : protHeader, 'utf-8'); const iv = protectedHeader.iv ? base64ToBytes(protectedHeader.iv) : undefined; return { ...(await cbcEncrypt('A256CBC-HS512', cleartext, cek, iv, encodedAad)), protectedHeader: protHeader, }; } return { alg, enc, encrypt }; } //# sourceMappingURL=a256cbc-hs512-dir.js.map