UNPKG

miscreant

Version:

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

161 lines (160 loc) 7.26 kB
"use strict"; // Copyright (C) 2017-2018 Dmitry Chestnykh, Tony Arcieri // 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 constant_time_1 = require("./internals/constant-time"); const wipe_1 = require("./internals/wipe"); const xor_1 = require("./internals/xor"); const exceptions_1 = require("./exceptions"); const block_1 = require("./internals/block"); const cmac_1 = require("./mac/cmac"); const pmac_1 = require("./mac/pmac"); const webcrypto_1 = require("./providers/webcrypto"); /** Maximum number of associated data items */ exports.MAX_ASSOCIATED_DATA = 126; /** The AES-SIV mode of authenticated encryption */ class SIV { /** Create a new AES-SIV instance with the given 32-byte or 64-byte key */ static importKey(keyData, alg, provider = new webcrypto_1.WebCryptoProvider()) { return __awaiter(this, void 0, void 0, function* () { // We only support AES-128 and AES-256. AES-SIV needs a key 2X as long the intended security level 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 = yield cmac_1.CMAC.importKey(provider, macKey); break; case "AES-CMAC-SIV": mac = yield cmac_1.CMAC.importKey(provider, macKey); break; case "AES-PMAC-SIV": mac = yield pmac_1.PMAC.importKey(provider, macKey); break; default: throw new exceptions_1.NotImplementedError(`Miscreant: algorithm not supported: ${alg}`); } const ctr = yield provider.importCTRKey(encKey); return new SIV(mac, ctr); }); } constructor(mac, ctr) { this._mac = mac; this._ctr = ctr; this._tmp1 = new block_1.default(); this._tmp2 = new block_1.default(); } /** Encrypt and authenticate data using AES-SIV */ seal(plaintext, associatedData) { return __awaiter(this, void 0, void 0, function* () { if (associatedData.length > exports.MAX_ASSOCIATED_DATA) { throw new Error("AES-SIV: too many associated data items"); } // Allocate space for sealed ciphertext. const resultLength = block_1.default.SIZE + plaintext.length; const result = new Uint8Array(resultLength); // Authenticate. const iv = yield this._s2v(associatedData, plaintext); result.set(iv); // Encrypt. zeroIVBits(iv); result.set(yield this._ctr.encryptCtr(iv, plaintext), iv.length); return result; }); } /** Decrypt and authenticate data using AES-SIV */ open(sealed, associatedData) { return __awaiter(this, void 0, void 0, function* () { if (associatedData.length > exports.MAX_ASSOCIATED_DATA) { throw new Error("AES-SIV: too many associated data items"); } if (sealed.length < block_1.default.SIZE) { throw new exceptions_1.IntegrityError("AES-SIV: ciphertext is truncated"); } // Decrypt. const tag = sealed.subarray(0, block_1.default.SIZE); const iv = this._tmp1.data; iv.set(tag); zeroIVBits(iv); // NOTE: "encryptCtr" is intentional. CTR encryption/decryption are the same const result = yield this._ctr.encryptCtr(iv, sealed.subarray(block_1.default.SIZE)); // Authenticate. const expectedTag = yield this._s2v(associatedData, result); if (!constant_time_1.equal(expectedTag, tag)) { wipe_1.wipe(result); throw new exceptions_1.IntegrityError("AES-SIV: ciphertext verification failure!"); } return result; }); } /** Make a best effort to wipe memory used by this instance */ clear() { this._tmp1.clear(); this._tmp2.clear(); this._ctr.clear(); this._mac.clear(); return this; } /** * The S2V operation consists of the doubling and XORing of the outputs * of the pseudo-random function CMAC (or PMAC in the case of AES-PMAC-SIV). * * See Section 2.4 of RFC 5297 for more information */ _s2v(associated_data, plaintext) { return __awaiter(this, void 0, void 0, function* () { this._mac.reset(); this._tmp1.clear(); // Note: the standalone S2V returns CMAC(1) if the number of passed // vectors is zero, however in SIV construction this case is never // triggered, since we always pass plaintext as the last vector (even // if it's zero-length), so we omit this case. yield this._mac.update(this._tmp1.data); this._tmp2.clear(); this._tmp2.data.set(yield this._mac.finish()); this._mac.reset(); for (const ad of associated_data) { yield this._mac.update(ad); this._tmp1.clear(); this._tmp1.data.set(yield this._mac.finish()); this._mac.reset(); this._tmp2.dbl(); xor_1.xor(this._tmp2.data, this._tmp1.data); } this._tmp1.clear(); if (plaintext.length >= block_1.default.SIZE) { const n = plaintext.length - block_1.default.SIZE; this._tmp1.data.set(plaintext.subarray(n)); yield this._mac.update(plaintext.subarray(0, n)); } else { this._tmp1.data.set(plaintext); this._tmp1.data[plaintext.length] = 0x80; this._tmp2.dbl(); } xor_1.xor(this._tmp1.data, this._tmp2.data); yield this._mac.update(this._tmp1.data); return this._mac.finish(); }); } } exports.SIV = SIV; /** Zero out the top bits in the last 32-bit words of the IV */ function zeroIVBits(iv) { // "We zero-out the top bit in each of the last two 32-bit words // of the IV before assigning it to Ctr" // — http://web.cs.ucdavis.edu/~rogaway/papers/siv.pdf iv[iv.length - 8] &= 0x7f; iv[iv.length - 4] &= 0x7f; }