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
JavaScript
"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;
}