UNPKG

@hackbg/miscreant-esm

Version:

(ESM port) Misuse resistant symmetric encryption library providing AES-SIV (RFC 5297), AES-PMAC-SIV, and STREAM constructions

116 lines (115 loc) 3.85 kB
import {equal} from "./internals/constant-time.dist.mjs"; import {wipe} from "./internals/wipe.dist.mjs"; import {xor} from "./internals/xor.dist.mjs"; import {IntegrityError, NotImplementedError} from "./exceptions.dist.mjs"; import Block from "./internals/block.dist.mjs"; import {CMAC} from "./mac/cmac.dist.mjs"; import {PMAC} from "./mac/pmac.dist.mjs"; import {WebCryptoProvider} from "./providers/webcrypto.dist.mjs"; export const MAX_ASSOCIATED_DATA = 126; export class SIV { static async importKey(keyData, alg, provider = new WebCryptoProvider()) { if (keyData.length !== 32 && keyData.length !== 64) { throw new Error(`AES-SIV: key must be 32 or 64-bytes (got ${keyData.length}`); } const macKey = keyData.subarray(0, keyData.length / 2 | 0); const encKey = keyData.subarray(keyData.length / 2 | 0); let mac; switch (alg) { case "AES-SIV": mac = await CMAC.importKey(provider, macKey); break; case "AES-CMAC-SIV": mac = await CMAC.importKey(provider, macKey); break; case "AES-PMAC-SIV": mac = await PMAC.importKey(provider, macKey); break; default: throw new NotImplementedError(`Miscreant: algorithm not supported: ${alg}`); } const ctr = await provider.importCTRKey(encKey); return new SIV(mac, ctr); } _mac; _ctr; _tmp1; _tmp2; constructor(mac, ctr) { this._mac = mac; this._ctr = ctr; this._tmp1 = new Block(); this._tmp2 = new Block(); } async seal(plaintext, associatedData) { if (associatedData.length > MAX_ASSOCIATED_DATA) { throw new Error("AES-SIV: too many associated data items"); } const resultLength = Block.SIZE + plaintext.length; const result = new Uint8Array(resultLength); const iv = await this._s2v(associatedData, plaintext); result.set(iv); zeroIVBits(iv); result.set(await this._ctr.encryptCtr(iv, plaintext), iv.length); return result; } async open(sealed, associatedData) { if (associatedData.length > MAX_ASSOCIATED_DATA) { throw new Error("AES-SIV: too many associated data items"); } if (sealed.length < Block.SIZE) { throw new IntegrityError("AES-SIV: ciphertext is truncated"); } const tag = sealed.subarray(0, Block.SIZE); const iv = this._tmp1.data; iv.set(tag); zeroIVBits(iv); const result = await this._ctr.encryptCtr(iv, sealed.subarray(Block.SIZE)); const expectedTag = await this._s2v(associatedData, result); if (!equal(expectedTag, tag)) { wipe(result); throw new IntegrityError("AES-SIV: ciphertext verification failure!"); } return result; } clear() { this._tmp1.clear(); this._tmp2.clear(); this._ctr.clear(); this._mac.clear(); return this; } async _s2v(associated_data, plaintext) { this._mac.reset(); this._tmp1.clear(); await this._mac.update(this._tmp1.data); this._tmp2.clear(); this._tmp2.data.set(await this._mac.finish()); this._mac.reset(); for (const ad of associated_data) { await this._mac.update(ad); this._tmp1.clear(); this._tmp1.data.set(await this._mac.finish()); this._mac.reset(); this._tmp2.dbl(); xor(this._tmp2.data, this._tmp1.data); } this._tmp1.clear(); if (plaintext.length >= Block.SIZE) { const n = plaintext.length - Block.SIZE; this._tmp1.data.set(plaintext.subarray(n)); await this._mac.update(plaintext.subarray(0, n)); } else { this._tmp1.data.set(plaintext); this._tmp1.data[plaintext.length] = 0x80; this._tmp2.dbl(); } xor(this._tmp1.data, this._tmp2.data); await this._mac.update(this._tmp1.data); return this._mac.finish(); } } function zeroIVBits(iv) { iv[iv.length - 8] &= 0x7f; iv[iv.length - 4] &= 0x7f; }