UNPKG

miscreant

Version:

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

170 lines (169 loc) 7.05 kB
"use strict"; // Copyright (C) 2016-2017 Tony Arcieri, Dmitry Chestnykh // MIT License. See LICENSE file for details. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const block_1 = require("../internals/block"); const constant_time_1 = require("../internals/constant-time"); const ctz_1 = require("../internals/ctz"); const xor_1 = require("../internals/xor"); // Number of L blocks to precompute (i.e. µ in the PMAC paper) // TODO: dynamically compute these as needed const PRECOMPUTED_BLOCKS = 31; /** * Polyfill for the AES-PMAC message authentication code * * Uses a non-constant-time (lookup table-based) AES polyfill. * See polyfill/aes.ts for more information on the security impact. */ class PMAC { constructor(cipher, l, lInv) { /** * finished is set true when we are done processing a message, and forbids * any subsequent writes until we reset the internal state */ this._finished = false; this._cipher = cipher; this._L = l; this._LInv = lInv; this._buffer = new block_1.default(); this._bufferPos = 0; this._counter = 0; this._offset = new block_1.default(); this._tag = new block_1.default(); } /** Create a new CMAC instance from the given key */ static importKey(provider, keyData) { return __awaiter(this, void 0, void 0, function* () { const cipher = yield provider.importBlockCipherKey(keyData); /** * L is defined as follows (quoted from the PMAC paper): * * Equation 1: * * a · x = * a<<1 if firstbit(a)=0 * (a<<1) ⊕ 0¹²⁰10000111 if firstbit(a)=1 * * Equation 2: * * a · x⁻¹ = * a>>1 if lastbit(a)=0 * (a>>1) ⊕ 10¹²⁰1000011 if lastbit(a)=1 * * Let L(0) ← L. For i ∈ [1..µ], compute L(i) ← L(i − 1) · x by * Equation (1) using a shift and a conditional xor. * * Compute L(−1) ← L · x⁻¹ by Equation (2), using a shift and a * conditional xor. * * Save the values L(−1), L(0), L(1), L(2), ..., L(µ) in a table. * (Alternatively, [ed: as we have done in this codebase] defer computing * some or all of these L(i) values until the value is actually needed.) */ const tmp = new block_1.default(); yield cipher.encryptBlock(tmp); const l = new Array(PRECOMPUTED_BLOCKS); for (let i = 0; i < PRECOMPUTED_BLOCKS; i++) { l[i] = tmp.clone(); tmp.dbl(); } /** * Compute L(−1) ← L · x⁻¹: * * a>>1 if lastbit(a)=0 * (a>>1) ⊕ 10¹²⁰1000011 if lastbit(a)=1 */ const lInv = l[0].clone(); const lastBit = lInv.data[block_1.default.SIZE - 1] & 0x01; for (let i = block_1.default.SIZE - 1; i > 0; i--) { const carry = constant_time_1.select(lInv.data[i - 1] & 1, 0x80, 0); lInv.data[i] = (lInv.data[i] >>> 1) | carry; } lInv.data[0] >>>= 1; lInv.data[0] ^= constant_time_1.select(lastBit, 0x80, 0); lInv.data[block_1.default.SIZE - 1] ^= constant_time_1.select(lastBit, block_1.default.R >>> 1, 0); return new PMAC(cipher, l, lInv); }); } reset() { this._buffer.clear(); this._bufferPos = 0; this._counter = 0; this._offset.clear(); this._tag.clear(); this._finished = false; return this; } clear() { this.reset(); this._cipher.clear(); } update(data) { return __awaiter(this, void 0, void 0, function* () { if (this._finished) { throw new Error("pmac: already finished"); } const left = block_1.default.SIZE - this._bufferPos; let dataPos = 0; let dataLength = data.length; // Finish filling the internal buf with the message if (dataLength > left) { this._buffer.data.set(data.slice(0, left), this._bufferPos); dataPos += left; dataLength -= left; yield this._processBuffer(); } // So long as we have more than a blocks worth of data, compute // whole-sized blocks at a time. while (dataLength > block_1.default.SIZE) { this._buffer.data.set(data.slice(dataPos, dataPos + block_1.default.SIZE)); dataPos += block_1.default.SIZE; dataLength -= block_1.default.SIZE; yield this._processBuffer(); } if (dataLength > 0) { this._buffer.data.set(data.slice(dataPos, dataPos + dataLength), this._bufferPos); this._bufferPos += dataLength; } return this; }); } finish() { return __awaiter(this, void 0, void 0, function* () { if (this._finished) { throw new Error("pmac: already finished"); } if (this._bufferPos === block_1.default.SIZE) { xor_1.xor(this._tag.data, this._buffer.data); xor_1.xor(this._tag.data, this._LInv.data); } else { xor_1.xor(this._tag.data, this._buffer.data.slice(0, this._bufferPos)); this._tag.data[this._bufferPos] ^= 0x80; } yield this._cipher.encryptBlock(this._tag); this._finished = true; return this._tag.clone().data; }); } // Update the internal tag state based on the buffer contents _processBuffer() { return __awaiter(this, void 0, void 0, function* () { xor_1.xor(this._offset.data, this._L[ctz_1.ctz(this._counter + 1)].data); xor_1.xor(this._buffer.data, this._offset.data); this._counter++; yield this._cipher.encryptBlock(this._buffer); xor_1.xor(this._tag.data, this._buffer.data); this._bufferPos = 0; }); } } exports.PMAC = PMAC;