Comparing version 0.1.0 to 0.2.0
{ | ||
"name": "miscreant", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"description": "Misuse resistant symmetric encryption using the AES-SIV (RFC 5297) and CHAIN/STREAM constructions", | ||
@@ -5,0 +5,0 @@ "homepage": "https://github.com/miscreant/miscreant/tree/master/js/", |
@@ -120,3 +120,3 @@ # miscreant.js [![Latest Version][npm-shield]][npm-link] [![Build Status][build-image]][build-link] [![Known Vulnerabilities][snyk-image]][snyk-link] [![MIT licensed][license-image]][license-link] [![Gitter Chat][gitter-image]][gitter-link] | ||
``` | ||
Miscreant.importKey(keyData, algorithm[, crypto = window.crypto]) | ||
Miscreant.importKey(keyData, algorithm[, provider = Miscreant.webCryptoProvider()]) | ||
``` | ||
@@ -129,6 +129,10 @@ | ||
SIV uses two distinct AES keys to perform its operations. | ||
* **algorithm**: a string describing the algorithm to use. The only algorithm | ||
presently supported is `"AES-SIV"`. | ||
* **crypto**: a cryptography provider that implements the WebCrypto API's | ||
[Crypto] interface. | ||
* **algorithm**: a string describing the algorithm to use. The following | ||
algorithms are supported: | ||
* `"AES-SIV"`: CMAC-based construction described in [RFC 5297]. Slower but | ||
standardized and more common. | ||
* `"AES-PMAC-SIV"`: PMAC-based construction. Supports potentially faster | ||
implementations, but is non-standard and only available in Miscreant libraries. | ||
* **provider**: a cryptography provider that implements Miscreant's | ||
[ICryptoProvider] interface. | ||
@@ -159,3 +163,3 @@ #### Return Value | ||
let key = await Miscreant.importKey(keyData, "AES-SIV"); | ||
let key = await Miscreant.importKey(keyData, "AES-PMAC-SIV"); | ||
``` | ||
@@ -195,3 +199,3 @@ | ||
let key = await Miscreant.importKey(keyData, "AES-SIV"); | ||
let key = await Miscreant.importKey(keyData, "AES-PMAC-SIV"); | ||
@@ -209,3 +213,4 @@ // Encrypt plaintext | ||
The **open()** method decrypts a message which has been encrypted using **AES-SIV**. | ||
The **open()** method decrypts a message which has been encrypted using | ||
**AES-SIV** or **AES-PMAC-SIV**. | ||
@@ -240,3 +245,3 @@ #### Syntax | ||
let key = await Miscreant.importKey(keyData, "AES-SIV"); | ||
let key = await Miscreant.importKey(keyData, "AES-PMAC-SIV"); | ||
@@ -283,3 +288,3 @@ // Encrypt plaintext | ||
``` | ||
Miscreant.getCryptoProvider("polyfill") | ||
Miscreant.polyfillCryptoProvider() | ||
``` | ||
@@ -290,6 +295,14 @@ | ||
``` | ||
const polyfillCrypto = Miscreant.getCryptoProvider("polyfill"); | ||
const key = Miscreant.importKey(keyData, "AES-SIV", polyfillCrypto); | ||
const key = Miscreant.importKey(keyData, "AES-PMAC-SIV", Miscreant.polyfillCryptoProvider()); | ||
``` | ||
## Code of Conduct | ||
We abide by the [Contributor Covenant][cc] and ask that you do as well. | ||
For more information, please see [CODE_OF_CONDUCT.md]. | ||
[cc]: https://contributor-covenant.org | ||
[CODE_OF_CONDUCT.md]: https://github.com/miscreant/miscreant/blob/master/CODE_OF_CONDUCT.md | ||
## Contributing | ||
@@ -296,0 +309,0 @@ |
// Copyright (C) 2017 Dmitry Chestnykh, Tony Arcieri | ||
// MIT License. See LICENSE file for details. | ||
import { equal } from "./constant-time"; | ||
import { dbl, defaultCryptoProvider, wipe, xor, zeroIVBits } from "./util"; | ||
import { equal } from "./util/constant-time"; | ||
import { wipe } from "./util/wipe"; | ||
import { xor } from "./util/xor"; | ||
import IntegrityError from "./exceptions/integrity_error"; | ||
import NotImplementedError from "./exceptions/not_implemented_error"; | ||
import { ICmacLike, ICtrLike, ISivLike } from "./interfaces"; | ||
import IntegrityError from "../exceptions/integrity_error"; | ||
import NotImplementedError from "../exceptions/not_implemented_error"; | ||
import Block from "./block"; | ||
import { ICryptoProvider, ICtrLike, IMacLike, ISivLike } from "./interfaces"; | ||
import PolyfillCrypto from "./polyfill"; | ||
import PolyfillAes from "./polyfill/aes"; | ||
import PolyfillAesCmac from "./polyfill/aes_cmac"; | ||
import PolyfillAesCtr from "./polyfill/aes_ctr"; | ||
import WebCryptoAesCmac from "./webcrypto/aes_cmac"; | ||
import WebCryptoAesCtr from "./webcrypto/aes_ctr"; | ||
import Cmac from "./mac/cmac"; | ||
import Pmac from "./mac/pmac"; | ||
@@ -25,4 +23,5 @@ /** Maximum number of associated data items */ | ||
public static async importKey( | ||
provider: ICryptoProvider, | ||
alg: string, | ||
keyData: Uint8Array, | ||
crypto: Crypto | PolyfillCrypto = defaultCryptoProvider(), | ||
): Promise<AesSiv> { | ||
@@ -37,37 +36,32 @@ // We only support AES-128 and AES-256. AES-SIV needs a key 2X as long the intended security level | ||
if (crypto instanceof PolyfillCrypto) { | ||
const mac = new PolyfillAesCmac(new PolyfillAes(macKey)); | ||
const ctr = new PolyfillAesCtr(new PolyfillAes(encKey)); | ||
return new AesSiv(mac, ctr, null); | ||
} else { | ||
const mac = await WebCryptoAesCmac.importKey(macKey, crypto); | ||
let mac: IMacLike; | ||
try { | ||
const ctr = await WebCryptoAesCtr.importKey(encKey, crypto); | ||
return new AesSiv(mac, ctr, crypto); | ||
} catch (e) { | ||
if (e.message.includes("unsupported")) { | ||
throw new NotImplementedError("AES-SIV: unsupported crypto backend (CTR missing). Use PolyfillCrypto."); | ||
} else { | ||
throw e; | ||
} | ||
} | ||
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.importAesCtrKey(encKey); | ||
return new AesSiv(mac, ctr); | ||
} | ||
public tagLength: number; | ||
private _mac: ICmacLike; | ||
private _mac: IMacLike; | ||
private _ctr: ICtrLike; | ||
private _tmp1: Uint8Array; | ||
private _tmp2: Uint8Array; | ||
private _crypto: Crypto | null; | ||
private _tmp1: Block; | ||
private _tmp2: Block; | ||
constructor(mac: ICmacLike, ctr: ICtrLike, crypto: Crypto | null = defaultCryptoProvider()) { | ||
constructor(mac: IMacLike, ctr: ICtrLike) { | ||
this._mac = mac; | ||
this._ctr = ctr; | ||
this._crypto = crypto; | ||
this._tmp1 = new Uint8Array(this._mac.digestLength); | ||
this._tmp2 = new Uint8Array(this._mac.digestLength); | ||
this.tagLength = this._mac.digestLength; | ||
this._tmp1 = new Block(); | ||
this._tmp2 = new Block(); | ||
} | ||
@@ -82,3 +76,3 @@ | ||
// Allocate space for sealed ciphertext. | ||
const resultLength = this.tagLength + plaintext.length; | ||
const resultLength = Block.SIZE + plaintext.length; | ||
const result = new Uint8Array(resultLength); | ||
@@ -92,3 +86,3 @@ | ||
zeroIVBits(iv); | ||
result.set(await this._ctr.encrypt(iv, plaintext), iv.length); | ||
result.set(await this._ctr.encryptCtr(iv, plaintext), iv.length); | ||
return result; | ||
@@ -103,3 +97,3 @@ } | ||
if (sealed.length < this.tagLength) { | ||
if (sealed.length < Block.SIZE) { | ||
throw new IntegrityError("AES-SIV: ciphertext is truncated"); | ||
@@ -109,8 +103,9 @@ } | ||
// Decrypt. | ||
const tag = sealed.subarray(0, this.tagLength); | ||
const iv = this._tmp1; | ||
const tag = sealed.subarray(0, Block.SIZE); | ||
const iv = this._tmp1.data; | ||
iv.set(tag); | ||
zeroIVBits(iv); | ||
const result = await this._ctr.decrypt(iv, sealed.subarray(this.tagLength)); | ||
// NOTE: "encryptCtr" is intentional. CTR encryption/decryption are the same | ||
const result = await this._ctr.encryptCtr(iv, sealed.subarray(Block.SIZE)); | ||
@@ -129,8 +124,7 @@ // Authenticate. | ||
/** Make a best effort to wipe memory used by this AesSiv instance */ | ||
public clean(): this { | ||
wipe(this._tmp1); | ||
wipe(this._tmp2); | ||
this._ctr.clean(); | ||
this._mac.clean(); | ||
this.tagLength = 0; | ||
public clear(): this { | ||
this._tmp1.clear(); | ||
this._tmp2.clear(); | ||
this._ctr.clear(); | ||
this._mac.clear(); | ||
@@ -148,3 +142,3 @@ return this; | ||
this._mac.reset(); | ||
wipe(this._tmp1); | ||
this._tmp1.clear(); | ||
@@ -155,5 +149,5 @@ // Note: the standalone S2V returns CMAC(1) if the number of passed | ||
// if it's zero-length), so we omit this case. | ||
await this._mac.update(this._tmp1); | ||
wipe(this._tmp2); | ||
this._tmp2 = await this._mac.finish(); | ||
await this._mac.update(this._tmp1.data); | ||
this._tmp2.clear(); | ||
this._tmp2.data.set(await this._mac.finish()); | ||
this._mac.reset(); | ||
@@ -163,24 +157,33 @@ | ||
await this._mac.update(ad); | ||
wipe(this._tmp1); | ||
this._tmp1 = await this._mac.finish(); | ||
this._tmp1.clear(); | ||
this._tmp1.data.set(await this._mac.finish()); | ||
this._mac.reset(); | ||
dbl(this._tmp2, this._tmp2); | ||
xor(this._tmp2, this._tmp1); | ||
this._tmp2.dbl(); | ||
xor(this._tmp2.data, this._tmp1.data); | ||
} | ||
wipe(this._tmp1); | ||
this._tmp1.clear(); | ||
if (plaintext.length >= this._mac.blockSize) { | ||
const n = plaintext.length - this._mac.blockSize; | ||
this._tmp1.set(plaintext.subarray(n)); | ||
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.set(plaintext); | ||
this._tmp1[plaintext.length] = 0x80; | ||
dbl(this._tmp2, this._tmp2); | ||
this._tmp1.data.set(plaintext); | ||
this._tmp1.data[plaintext.length] = 0x80; | ||
this._tmp2.dbl(); | ||
} | ||
xor(this._tmp1, this._tmp2); | ||
await this._mac.update(this._tmp1); | ||
xor(this._tmp1.data, this._tmp2.data); | ||
await this._mac.update(this._tmp1.data); | ||
return this._mac.finish(); | ||
} | ||
} | ||
/** Zero out the top bits in the last 32-bit words of the IV */ | ||
function zeroIVBits(iv: Uint8Array) { | ||
// "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; | ||
} |
/** Shared interfaces for cryptographic algorithms */ | ||
/** A cipher which provides a SIV-like interface and properties */ | ||
export interface ISivLike { | ||
seal(plaintext: Uint8Array, associatedData: Uint8Array[]): Promise<Uint8Array>; | ||
open(sealed: Uint8Array, associatedData: Uint8Array[]): Promise<Uint8Array>; | ||
clean(): this; | ||
import Block from "./block"; | ||
/** | ||
* A block cipher (with 128-bit blocks, i.e. AES) | ||
* | ||
* | ||
* WARNING: This interface should not be used directly! | ||
* That is why it is hiding under internal. | ||
* | ||
* It should only be used to implement a cipher mode. | ||
* This library uses it to implement AES-SIV. | ||
*/ | ||
export interface IBlockCipher { | ||
clear(): this; | ||
/** Encrypt 16-byte block in-place, replacing its contents with ciphertext. */ | ||
encryptBlock(block: Block): Promise<this>; | ||
} | ||
/** A cipher which provides CTR (counter mode) encryption */ | ||
/** | ||
* A backend which provides an implementation of cryptographic primitives | ||
*/ | ||
export interface ICryptoProvider { | ||
importAesKey(keyData: Uint8Array): Promise<IBlockCipher>; | ||
importAesCtrKey(keyData: Uint8Array): Promise<ICtrLike>; | ||
} | ||
/** | ||
* A cipher which provides CTR (counter mode) encryption | ||
*/ | ||
export interface ICtrLike { | ||
encrypt(iv: Uint8Array, plaintext: Uint8Array): Promise<Uint8Array>; | ||
decrypt(iv: Uint8Array, ciphertext: Uint8Array): Promise<Uint8Array>; | ||
clean(): this; | ||
encryptCtr(iv: Uint8Array, plaintext: Uint8Array): Promise<Uint8Array>; | ||
clear(): this; | ||
} | ||
/** An implementation of the CMAC message authentication code */ | ||
export interface ICmacLike { | ||
blockSize: number; | ||
digestLength: number; | ||
/** | ||
* An implementation of a message authentication code (MAC) | ||
*/ | ||
export interface IMacLike { | ||
reset(): this; | ||
clean(): void; | ||
clear(): void; | ||
update(data: Uint8Array): Promise<this>; | ||
finish(): Promise<Uint8Array>; | ||
} | ||
/** | ||
* A cipher which provides a SIV-like interface and properties | ||
*/ | ||
export interface ISivLike { | ||
seal(plaintext: Uint8Array, associatedData: Uint8Array[]): Promise<Uint8Array>; | ||
open(sealed: Uint8Array, associatedData: Uint8Array[]): Promise<Uint8Array>; | ||
clear(): this; | ||
} |
// Copyright (C) 2016 Dmitry Chestnykh | ||
// MIT License. See LICENSE file for details. | ||
import Block from "../block"; | ||
import { ICtrLike } from "../interfaces"; | ||
import { wipe } from "../util"; | ||
@@ -19,4 +19,4 @@ import PolyfillAes from "./aes"; | ||
export default class PolyfillAesCtr implements ICtrLike { | ||
private _counter: Uint8Array; | ||
private _buffer: Uint8Array; | ||
private _counter: Block; | ||
private _buffer: Block; | ||
private _cipher: PolyfillAes; | ||
@@ -29,10 +29,17 @@ | ||
// Allocate space for counter. | ||
this._counter = new Uint8Array(cipher.blockSize); | ||
this._counter = new Block(); | ||
// Allocate buffer for encrypted block. | ||
this._buffer = new Uint8Array(cipher.blockSize); | ||
this._buffer = new Block(); | ||
} | ||
public async encrypt(iv: Uint8Array, plaintext: Uint8Array): Promise<Uint8Array> { | ||
if (iv.length !== this._counter.length) { | ||
public clear(): this { | ||
this._buffer.clear(); | ||
this._counter.clear(); | ||
this._cipher.clear(); | ||
return this; | ||
} | ||
public async encryptCtr(iv: Uint8Array, plaintext: Uint8Array): Promise<Uint8Array> { | ||
if (iv.length !== Block.SIZE) { | ||
throw new Error("CTR: iv length must be equal to cipher block size"); | ||
@@ -42,7 +49,7 @@ } | ||
// Copy IV to counter, overwriting it. | ||
this._counter.set(iv); | ||
this._counter.data.set(iv); | ||
// Set buffer position to length of buffer | ||
// so that the first cipher block is generated. | ||
let bufpos = this._buffer.length; | ||
let bufferPos = Block.SIZE; | ||
@@ -52,8 +59,9 @@ const result = new Uint8Array(plaintext.length); | ||
for (let i = 0; i < plaintext.length; i++) { | ||
if (bufpos === this._buffer.length) { | ||
this._cipher.encryptBlock(this._counter, this._buffer); | ||
bufpos = 0; | ||
if (bufferPos === Block.SIZE) { | ||
this._buffer.copy(this._counter); | ||
this._cipher.encryptBlock(this._buffer); | ||
bufferPos = 0; | ||
incrementCounter(this._counter); | ||
} | ||
result[i] = plaintext[i] ^ this._buffer[bufpos++]; | ||
result[i] = plaintext[i] ^ this._buffer.data[bufferPos++]; | ||
} | ||
@@ -63,28 +71,13 @@ | ||
} | ||
public async decrypt(iv: Uint8Array, ciphertext: Uint8Array): Promise<Uint8Array> { | ||
// AES-CTR decryption is identical to encryption | ||
return this.encrypt(iv, ciphertext); | ||
} | ||
public clean(): this { | ||
wipe(this._buffer); | ||
wipe(this._counter); | ||
this._cipher.clean(); | ||
return this; | ||
} | ||
} | ||
function incrementCounter(counter: Uint8Array) { | ||
// Increment an AES-CTR mode counter, intentionally wrapping/overflowing | ||
function incrementCounter(counter: Block) { | ||
let carry = 1; | ||
for (let i = counter.length - 1; i >= 0; i--) { | ||
carry += (counter[i] & 0xff) | 0; | ||
counter[i] = carry & 0xff; | ||
for (let i = Block.SIZE - 1; i >= 0; i--) { | ||
carry += (counter.data[i] & 0xff) | 0; | ||
counter.data[i] = carry & 0xff; | ||
carry >>>= 8; | ||
} | ||
if (carry > 0) { | ||
throw new Error("CTR: counter overflow"); | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
// Copyright (C) 2016 Dmitry Chestnykh | ||
// Copyright (C) 2016-2017 Dmitry Chestnykh, Tony Arcieri | ||
// MIT License. See LICENSE file for details. | ||
@@ -14,3 +14,5 @@ | ||
import { wipe } from "../util"; | ||
import Block from "../block"; | ||
import { IBlockCipher } from "../interfaces"; | ||
import { wipe } from "../util/wipe"; | ||
@@ -76,2 +78,125 @@ // Powers of x mod poly in GF(2). | ||
/** | ||
* Polyfill for the AES block cipher. | ||
* | ||
* This implementation uses lookup tables, so it's susceptible to cache-timing | ||
* side-channel attacks. A constant-time version we tried was super slow (a few | ||
* kilobytes per second), so we'll have to live with it. | ||
* | ||
* Key size: 16 or 32 bytes, block size: 16 bytes. | ||
*/ | ||
export default class PolyfillAes implements IBlockCipher { | ||
// Expanded encryption key. | ||
private _encKey: Uint32Array; | ||
// A placeholder promise we always return to match the WebCrypto API | ||
private _emptyPromise: Promise<this>; | ||
/** | ||
* Constructs AES with the given 16 or 32-byte key | ||
* for AES-128 or AES-256. | ||
*/ | ||
constructor(keyData: Uint8Array) { | ||
if (!isInitialized) { | ||
initialize(); | ||
} | ||
// Only AES-128 and AES-256 supported. AES-192 is not. | ||
if (keyData.length !== 16 && keyData.length !== 32) { | ||
throw new Error(`Miscreant: invalid key length: ${keyData.length} (expected 16 or 32 bytes)`); | ||
} | ||
this._encKey = expandKey(keyData); | ||
this._emptyPromise = Promise.resolve(this); | ||
} | ||
/** | ||
* Cleans expanded keys from memory, setting them to zeros. | ||
*/ | ||
public clear(): this { | ||
if (this._encKey) { | ||
wipe(this._encKey); | ||
} | ||
return this; | ||
} | ||
/** | ||
* Encrypt 16-byte block in-place, replacing its contents with ciphertext. | ||
* | ||
* This function should not be used to encrypt data without any | ||
* cipher mode! It should only be used to implement a cipher mode. | ||
* This library uses it to implement AES-SIV. | ||
*/ | ||
public encryptBlock(block: Block): Promise<this> { | ||
const src = block.data; | ||
const dst = block.data; | ||
let s0 = readUint32BE(src, 0); | ||
let s1 = readUint32BE(src, 4); | ||
let s2 = readUint32BE(src, 8); | ||
let s3 = readUint32BE(src, 12); | ||
// First round just XORs input with key. | ||
s0 ^= this._encKey[0]; | ||
s1 ^= this._encKey[1]; | ||
s2 ^= this._encKey[2]; | ||
s3 ^= this._encKey[3]; | ||
let t0 = 0; | ||
let t1 = 0; | ||
let t2 = 0; | ||
let t3 = 0; | ||
// Middle rounds shuffle using tables. | ||
// Number of rounds is set by length of expanded key. | ||
const nr = this._encKey.length / 4 - 2; // - 2: one above, one more below | ||
let k = 4; | ||
for (let r = 0; r < nr; r++) { | ||
t0 = this._encKey[k + 0] ^ Te0[(s0 >>> 24) & 0xff] ^ Te1[(s1 >>> 16) & 0xff] ^ | ||
Te2[(s2 >>> 8) & 0xff] ^ Te3[s3 & 0xff]; | ||
t1 = this._encKey[k + 1] ^ Te0[(s1 >>> 24) & 0xff] ^ Te1[(s2 >>> 16) & 0xff] ^ | ||
Te2[(s3 >>> 8) & 0xff] ^ Te3[s0 & 0xff]; | ||
t2 = this._encKey[k + 2] ^ Te0[(s2 >>> 24) & 0xff] ^ Te1[(s3 >>> 16) & 0xff] ^ | ||
Te2[(s0 >>> 8) & 0xff] ^ Te3[s1 & 0xff]; | ||
t3 = this._encKey[k + 3] ^ Te0[(s3 >>> 24) & 0xff] ^ Te1[(s0 >>> 16) & 0xff] ^ | ||
Te2[(s1 >>> 8) & 0xff] ^ Te3[s2 & 0xff]; | ||
k += 4; | ||
s0 = t0; | ||
s1 = t1; | ||
s2 = t2; | ||
s3 = t3; | ||
} | ||
// Last round uses s-box directly and XORs to produce output. | ||
s0 = (SBOX0[t0 >>> 24] << 24) | (SBOX0[(t1 >>> 16) & 0xff]) << 16 | | ||
(SBOX0[(t2 >>> 8) & 0xff]) << 8 | (SBOX0[t3 & 0xff]); | ||
s1 = (SBOX0[t1 >>> 24] << 24) | (SBOX0[(t2 >>> 16) & 0xff]) << 16 | | ||
(SBOX0[(t3 >>> 8) & 0xff]) << 8 | (SBOX0[t0 & 0xff]); | ||
s2 = (SBOX0[t2 >>> 24] << 24) | (SBOX0[(t3 >>> 16) & 0xff]) << 16 | | ||
(SBOX0[(t0 >>> 8) & 0xff]) << 8 | (SBOX0[t1 & 0xff]); | ||
s3 = (SBOX0[t3 >>> 24] << 24) | (SBOX0[(t0 >>> 16) & 0xff]) << 16 | | ||
(SBOX0[(t1 >>> 8) & 0xff]) << 8 | (SBOX0[t2 & 0xff]); | ||
s0 ^= this._encKey[k + 0]; | ||
s1 ^= this._encKey[k + 1]; | ||
s2 ^= this._encKey[k + 2]; | ||
s3 ^= this._encKey[k + 3]; | ||
writeUint32BE(s0, dst, 0); | ||
writeUint32BE(s1, dst, 4); | ||
writeUint32BE(s2, dst, 8); | ||
writeUint32BE(s3, dst, 12); | ||
return this._emptyPromise; | ||
} | ||
} | ||
// Initialize generates encryption and decryption tables. | ||
@@ -160,111 +285,2 @@ function initialize() { | ||
/** | ||
* Polyfill for the AES block cipher. | ||
* | ||
* This implementation uses lookup tables, so it's susceptible to cache-timing | ||
* side-channel attacks. A constant-time version we tried was super slow (a few | ||
* kilobytes per second), so we'll have to live with it. | ||
* | ||
* Key size: 16, 24 or 32 bytes, block size: 16 bytes. | ||
*/ | ||
export default class PolyfillAes { | ||
// AES block size in bytes. | ||
public readonly blockSize = 16; | ||
// Key byte length. | ||
private _keyLen: number; | ||
// Expanded encryption key. | ||
private _encKey: Uint32Array; | ||
// Expanded decryption key. May be undefined if instance | ||
// was created "noDecryption" option set to true. | ||
private _decKey: Uint32Array | undefined; | ||
/** | ||
* Constructs AES with the given 16, 24 or 32-byte key | ||
* for AES-128, AES-192, or AES-256. | ||
* | ||
* If noDecryption is true, decryption key will not expanded, | ||
* saving time and memory for cipher modes when decryption | ||
* is not used (such as AES-CTR). | ||
* | ||
*/ | ||
constructor(key: Uint8Array, noDecryption = false) { | ||
if (!isInitialized) { | ||
initialize(); | ||
} | ||
this._keyLen = key.length; | ||
this.setKey(key, noDecryption); | ||
} | ||
/** | ||
* Re-initializes this instance with the new key. | ||
* | ||
* This is helpful to avoid allocations. | ||
*/ | ||
public setKey(key: Uint8Array, noDecryption = false): this { | ||
if (key.length !== 16 && key.length !== 24 && key.length !== 32) { | ||
throw new Error("AES: wrong key size (must be 16, 24 or 32)"); | ||
} | ||
if (this._keyLen !== key.length) { | ||
throw new Error("AES: initialized with different key size"); | ||
} | ||
// If we haven't yet, allocate space for expanded keys. | ||
if (!this._encKey) { | ||
this._encKey = new Uint32Array(key.length + 28); | ||
} | ||
if (noDecryption) { | ||
// Wipe decryption key, as we no longer need it. | ||
if (this._decKey) { | ||
wipe(this._decKey); | ||
} | ||
} else { | ||
if (!this._decKey) { | ||
this._decKey = new Uint32Array(key.length + 28); | ||
} | ||
} | ||
expandKey(key, this._encKey, this._decKey); | ||
return this; | ||
} | ||
/** | ||
* Cleans expanded keys from memory, setting them to zeros. | ||
*/ | ||
public clean(): this { | ||
if (this._encKey) { | ||
wipe(this._encKey); | ||
} | ||
if (this._decKey) { | ||
wipe(this._decKey); | ||
} | ||
return this; | ||
} | ||
// TODO(dchest): specify if blocks can be the same array. | ||
/** | ||
* Encrypt 16-byte block src into 16-byte block dst. | ||
* | ||
* This function should not be used to encrypt data without any | ||
* cipher mode! It should only be used to implement a cipher mode. | ||
* This library uses it to implement AES-SIV. | ||
*/ | ||
public encryptBlock(src: Uint8Array, dst: Uint8Array): this { | ||
// Check block lengths. | ||
if (src.length < this.blockSize) { | ||
throw new Error("AES: source block too small"); | ||
} | ||
if (dst.length < this.blockSize) { | ||
throw new Error("AES: destination block too small"); | ||
} | ||
// Encrypt block. | ||
encryptBlock(this._encKey, src, dst); | ||
return this; | ||
} | ||
} | ||
// Apply sbox0 to each byte in w. | ||
@@ -283,10 +299,14 @@ function subw(w: number): number { | ||
function expandKey(key: Uint8Array, encKey: Uint32Array, decKey?: Uint32Array): void { | ||
function expandKey(key: Uint8Array): Uint32Array { | ||
const encKey = new Uint32Array(key.length + 28); | ||
const nk = key.length / 4 | 0; | ||
const n = encKey.length; | ||
for (let i = 0; i < nk; i++) { | ||
encKey[i] = readUint32BE(key, i * 4); | ||
} | ||
for (let i = nk; i < n; i++) { | ||
let t = encKey[i - 1]; | ||
if (i % nk === 0) { | ||
@@ -297,153 +317,7 @@ t = subw(rotw(t)) ^ (POWX[i / nk - 1] << 24); | ||
} | ||
encKey[i] = encKey[i - nk] ^ t; | ||
} | ||
if (decKey) { | ||
// Derive decryption key from encryption key. | ||
// Reverse the 4-word round key sets from enc to produce dec. | ||
// All sets but the first and last get the MixColumn transform applied. | ||
for (let i = 0; i < n; i += 4) { | ||
const ei = n - i - 4; | ||
for (let j = 0; j < 4; j++) { | ||
let x = encKey[ei + j]; | ||
if (i > 0 && i + 4 < n) { | ||
x = Td0[SBOX0[(x >>> 24) & 0xff]] ^ Td1[SBOX0[(x >>> 16) & 0xff]] ^ | ||
Td2[SBOX0[(x >>> 8) & 0xff]] ^ Td3[SBOX0[x & 0xff]]; | ||
} | ||
decKey[i + j] = x; | ||
} | ||
} | ||
} | ||
return encKey; | ||
} | ||
function encryptBlock(xk: Uint32Array, src: Uint8Array, dst: Uint8Array): void { | ||
let s0 = readUint32BE(src, 0); | ||
let s1 = readUint32BE(src, 4); | ||
let s2 = readUint32BE(src, 8); | ||
let s3 = readUint32BE(src, 12); | ||
// First round just XORs input with key. | ||
s0 ^= xk[0]; | ||
s1 ^= xk[1]; | ||
s2 ^= xk[2]; | ||
s3 ^= xk[3]; | ||
let t0 = 0; | ||
let t1 = 0; | ||
let t2 = 0; | ||
let t3 = 0; | ||
// Middle rounds shuffle using tables. | ||
// Number of rounds is set by length of expanded key. | ||
const nr = xk.length / 4 - 2; // - 2: one above, one more below | ||
let k = 4; | ||
for (let r = 0; r < nr; r++) { | ||
t0 = xk[k + 0] ^ Te0[(s0 >>> 24) & 0xff] ^ Te1[(s1 >>> 16) & 0xff] ^ | ||
Te2[(s2 >>> 8) & 0xff] ^ Te3[s3 & 0xff]; | ||
t1 = xk[k + 1] ^ Te0[(s1 >>> 24) & 0xff] ^ Te1[(s2 >>> 16) & 0xff] ^ | ||
Te2[(s3 >>> 8) & 0xff] ^ Te3[s0 & 0xff]; | ||
t2 = xk[k + 2] ^ Te0[(s2 >>> 24) & 0xff] ^ Te1[(s3 >>> 16) & 0xff] ^ | ||
Te2[(s0 >>> 8) & 0xff] ^ Te3[s1 & 0xff]; | ||
t3 = xk[k + 3] ^ Te0[(s3 >>> 24) & 0xff] ^ Te1[(s0 >>> 16) & 0xff] ^ | ||
Te2[(s1 >>> 8) & 0xff] ^ Te3[s2 & 0xff]; | ||
k += 4; | ||
s0 = t0; | ||
s1 = t1; | ||
s2 = t2; | ||
s3 = t3; | ||
} | ||
// Last round uses s-box directly and XORs to produce output. | ||
s0 = (SBOX0[t0 >>> 24] << 24) | (SBOX0[(t1 >>> 16) & 0xff]) << 16 | | ||
(SBOX0[(t2 >>> 8) & 0xff]) << 8 | (SBOX0[t3 & 0xff]); | ||
s1 = (SBOX0[t1 >>> 24] << 24) | (SBOX0[(t2 >>> 16) & 0xff]) << 16 | | ||
(SBOX0[(t3 >>> 8) & 0xff]) << 8 | (SBOX0[t0 & 0xff]); | ||
s2 = (SBOX0[t2 >>> 24] << 24) | (SBOX0[(t3 >>> 16) & 0xff]) << 16 | | ||
(SBOX0[(t0 >>> 8) & 0xff]) << 8 | (SBOX0[t1 & 0xff]); | ||
s3 = (SBOX0[t3 >>> 24] << 24) | (SBOX0[(t0 >>> 16) & 0xff]) << 16 | | ||
(SBOX0[(t1 >>> 8) & 0xff]) << 8 | (SBOX0[t2 & 0xff]); | ||
s0 ^= xk[k + 0]; | ||
s1 ^= xk[k + 1]; | ||
s2 ^= xk[k + 2]; | ||
s3 ^= xk[k + 3]; | ||
writeUint32BE(s0, dst, 0); | ||
writeUint32BE(s1, dst, 4); | ||
writeUint32BE(s2, dst, 8); | ||
writeUint32BE(s3, dst, 12); | ||
} | ||
function decryptBlock(xk: Uint32Array, src: Uint8Array, dst: Uint8Array): void { | ||
let s0 = readUint32BE(src, 0); | ||
let s1 = readUint32BE(src, 4); | ||
let s2 = readUint32BE(src, 8); | ||
let s3 = readUint32BE(src, 12); | ||
// First round just XORs input with key. | ||
s0 ^= xk[0]; | ||
s1 ^= xk[1]; | ||
s2 ^= xk[2]; | ||
s3 ^= xk[3]; | ||
let t0 = 0; | ||
let t1 = 0; | ||
let t2 = 0; | ||
let t3 = 0; | ||
// Middle rounds shuffle using tables. | ||
// Number of rounds is set by length of expanded key. | ||
const nr = xk.length / 4 - 2; // - 2: one above, one more below | ||
let k = 4; | ||
for (let r = 0; r < nr; r++) { | ||
t0 = xk[k + 0] ^ Td0[(s0 >>> 24) & 0xff] ^ Td1[(s3 >>> 16) & 0xff] ^ | ||
Td2[(s2 >>> 8) & 0xff] ^ Td3[s1 & 0xff]; | ||
t1 = xk[k + 1] ^ Td0[(s1 >>> 24) & 0xff] ^ Td1[(s0 >>> 16) & 0xff] ^ | ||
Td2[(s3 >>> 8) & 0xff] ^ Td3[s2 & 0xff]; | ||
t2 = xk[k + 2] ^ Td0[(s2 >>> 24) & 0xff] ^ Td1[(s1 >>> 16) & 0xff] ^ | ||
Td2[(s0 >>> 8) & 0xff] ^ Td3[s3 & 0xff]; | ||
t3 = xk[k + 3] ^ Td0[(s3 >>> 24) & 0xff] ^ Td1[(s2 >>> 16) & 0xff] ^ | ||
Td2[(s1 >>> 8) & 0xff] ^ Td3[s0 & 0xff]; | ||
k += 4; | ||
s0 = t0; | ||
s1 = t1; | ||
s2 = t2; | ||
s3 = t3; | ||
} | ||
// Last round uses s-box directly and XORs to produce output. | ||
s0 = (SBOX1[t0 >>> 24] << 24) | (SBOX1[(t3 >>> 16) & 0xff]) << 16 | | ||
(SBOX1[(t2 >>> 8) & 0xff]) << 8 | (SBOX1[t1 & 0xff]); | ||
s1 = (SBOX1[t1 >>> 24] << 24) | (SBOX1[(t0 >>> 16) & 0xff]) << 16 | | ||
(SBOX1[(t3 >>> 8) & 0xff]) << 8 | (SBOX1[t2 & 0xff]); | ||
s2 = (SBOX1[t2 >>> 24] << 24) | (SBOX1[(t1 >>> 16) & 0xff]) << 16 | | ||
(SBOX1[(t0 >>> 8) & 0xff]) << 8 | (SBOX1[t3 & 0xff]); | ||
s3 = (SBOX1[t3 >>> 24] << 24) | (SBOX1[(t2 >>> 16) & 0xff]) << 16 | | ||
(SBOX1[(t1 >>> 8) & 0xff]) << 8 | (SBOX1[t0 & 0xff]); | ||
s0 ^= xk[k + 0]; | ||
s1 ^= xk[k + 1]; | ||
s2 ^= xk[k + 2]; | ||
s3 ^= xk[k + 3]; | ||
writeUint32BE(s0, dst, 0); | ||
writeUint32BE(s1, dst, 4); | ||
writeUint32BE(s2, dst, 8); | ||
writeUint32BE(s3, dst, 12); | ||
} |
import { ICtrLike } from "../interfaces"; | ||
import { defaultCryptoProvider } from "../util"; | ||
/** AES-CTR using a WebCrypto (or similar) API */ | ||
/** | ||
* AES-CTR using a WebCrypto (or similar) API | ||
*/ | ||
export default class WebCryptoAesCtr implements ICtrLike { | ||
public static async importKey(keyData: Uint8Array, crypto = defaultCryptoProvider()): Promise<WebCryptoAesCtr> { | ||
public static async importKey(crypto: Crypto, keyData: Uint8Array): Promise<WebCryptoAesCtr> { | ||
// Only AES-128 and AES-256 supported. AES-192 is not. | ||
if (keyData.length !== 16 && keyData.length !== 32) { | ||
throw new Error(`invalid key ${keyData.length} (expected 16 or 32 bytes)`); | ||
throw new Error(`Miscreant: invalid key length: ${keyData.length} (expected 16 or 32 bytes)`); | ||
} | ||
@@ -18,6 +19,6 @@ | ||
readonly key: CryptoKey, | ||
readonly crypto = defaultCryptoProvider(), | ||
readonly crypto: Crypto, | ||
) { } | ||
public async encrypt(iv: Uint8Array, plaintext: Uint8Array): Promise<Uint8Array> { | ||
public async encryptCtr(iv: Uint8Array, plaintext: Uint8Array): Promise<Uint8Array> { | ||
const ciphertext = await this.crypto.subtle.encrypt( | ||
@@ -32,11 +33,6 @@ { name: "AES-CTR", counter: iv, length: 16 }, | ||
public async decrypt(iv: Uint8Array, ciphertext: Uint8Array): Promise<Uint8Array> { | ||
// AES-CTR decryption is identical to encryption | ||
return this.encrypt(iv, ciphertext); | ||
} | ||
public clean(): this { | ||
// TODO: actually clean something. Do we need to? | ||
public clear(): this { | ||
// TODO: actually clear something. Do we need to? | ||
return this; | ||
} | ||
} |
/** miscreant.ts: Main entry point to the Miscreant library */ | ||
import { ISivLike } from "./internal/interfaces"; | ||
import { defaultCryptoProvider } from "./internal/util"; | ||
import NotImplementedError from "./exceptions/not_implemented_error"; | ||
import { ICryptoProvider, ISivLike } from "./internal/interfaces"; | ||
import AesSiv from "./internal/aes_siv"; | ||
import PolyfillCrypto from "./internal/polyfill"; | ||
import PolyfillCryptoProvider from "./internal/polyfill/provider"; | ||
import WebCryptoProvider from "./internal/webcrypto/provider"; | ||
@@ -15,21 +16,37 @@ /** Miscreant: A misuse-resistant symmetric encryption library */ | ||
alg: string, | ||
crypto: Crypto | PolyfillCrypto = defaultCryptoProvider(), | ||
provider: ICryptoProvider = Miscreant.webCryptoProvider(), | ||
): Promise<ISivLike> { | ||
if (alg === "AES-SIV") { | ||
return AesSiv.importKey(keyData, crypto); | ||
} else { | ||
throw new Error(`unsupported algorithm: ${alg}`); | ||
} | ||
return AesSiv.importKey(provider, alg, keyData); | ||
} | ||
/** Obtain a cryptographic provider */ | ||
public static getCryptoProvider(providerName = "default"): Crypto | PolyfillCrypto { | ||
if (providerName === "default") { | ||
return defaultCryptoProvider(); | ||
} else if (providerName === "polyfill") { | ||
return new PolyfillCrypto(); | ||
} else { | ||
throw new Error(`unsupported provider: ${providerName}`); | ||
/** | ||
* Autodetect and return the default WebCrypto provider for this environment. | ||
* | ||
* Cryptography providers returned by this function should implement | ||
* cryptography natively and not rely on JavaScript polyfills. | ||
*/ | ||
public static webCryptoProvider(crypto: Crypto = window.crypto): WebCryptoProvider { | ||
try { | ||
return new WebCryptoProvider(crypto); | ||
} catch (e) { | ||
// Handle the case where window is undefined because we're not in a browser | ||
if (e instanceof ReferenceError) { | ||
throw new NotImplementedError("Miscreant: window.crypto unavailable in this environment"); | ||
} else { | ||
throw e; | ||
} | ||
} | ||
} | ||
/** | ||
* Obtain a polyfill cryptographic provider | ||
* | ||
* WARNING: The polyfill implementation is not constant-time and may have | ||
* potentially severe security issues, including leaking secret keys! | ||
* | ||
* Please use the Web Crypto provider if at all possible. | ||
*/ | ||
public static polyfillCryptoProvider(): PolyfillCryptoProvider { | ||
return new PolyfillCryptoProvider(); | ||
} | ||
} |
@@ -9,7 +9,6 @@ // Copyright (C) 2016 Dmitry Chestnykh | ||
import WebCrypto = require("node-webcrypto-ossl"); | ||
import PolyfillCryptoProvider from "../src/internal/polyfill/provider"; | ||
import WebCryptoProvider from "../src/internal/webcrypto/provider"; | ||
import Cmac from "../src/internal/mac/cmac"; | ||
import PolyfillAes from "../src/internal/polyfill/aes"; | ||
import PolyfillAesCmac from "../src/internal/polyfill/aes_cmac"; | ||
import WebCryptoAesCmac from "../src/internal/webcrypto/aes_cmac"; | ||
@suite class PolyfillAesCmacSpec { | ||
@@ -23,4 +22,6 @@ static vectors: AesCmacExample[]; | ||
@test async "passes the AES-CMAC test vectors"() { | ||
const polyfillProvider = new PolyfillCryptoProvider(); | ||
for (let v of PolyfillAesCmacSpec.vectors) { | ||
const mac = new PolyfillAesCmac(new PolyfillAes(v.key)); | ||
const mac = await Cmac.importKey(polyfillProvider, v.key); | ||
await mac.update(v.message); | ||
@@ -40,4 +41,6 @@ expect(await mac.finish()).to.eql(v.tag); | ||
@test async "passes the AES-CMAC test vectors"() { | ||
const webCryptoProvider = new WebCryptoProvider(new WebCrypto()); | ||
for (let v of PolyfillAesCmacSpec.vectors) { | ||
const mac = await WebCryptoAesCmac.importKey(v.key, new WebCrypto()); | ||
const mac = await Cmac.importKey(webCryptoProvider, v.key); | ||
await mac.update(v.message); | ||
@@ -44,0 +47,0 @@ expect(await mac.finish()).to.eql(v.tag); |
@@ -24,3 +24,3 @@ // Copyright (C) 2016 Dmitry Chestnykh | ||
const ctrPolyfill = new PolyfillAesCtr(new PolyfillAes(v.key)); | ||
let ciphertext = await ctrPolyfill.encrypt(v.iv, v.plaintext); | ||
let ciphertext = await ctrPolyfill.encryptCtr(v.iv, v.plaintext); | ||
expect(ciphertext).to.eql(v.ciphertext); | ||
@@ -40,4 +40,4 @@ } | ||
for (let v of WebCryptoAesCtrSpec.vectors) { | ||
const ctrNative = await WebCryptoAesCtr.importKey(v.key, new WebCrypto()); | ||
let ciphertext = await ctrNative.encrypt(v.iv, v.plaintext); | ||
const ctrNative = await WebCryptoAesCtr.importKey(new WebCrypto(), v.key); | ||
let ciphertext = await ctrNative.encryptCtr(v.iv, v.plaintext); | ||
expect(ciphertext).to.eql(v.ciphertext); | ||
@@ -44,0 +44,0 @@ } |
@@ -10,6 +10,7 @@ // Copyright (C) 2017 Dmitry Chestnykh | ||
import WebCrypto = require("node-webcrypto-ossl"); | ||
import PolyfillCryptoProvider from "../src/internal/polyfill/provider"; | ||
import WebCryptoProvider from "../src/internal/webcrypto/provider"; | ||
import AesSiv from "../src/internal/aes_siv"; | ||
import PolyfillCrypto from "../src/internal/polyfill"; | ||
import IntegrityError from "../src/internal/exceptions/integrity_error"; | ||
import IntegrityError from "../src/exceptions/integrity_error"; | ||
@@ -27,4 +28,6 @@ let expect = chai.expect; | ||
@test async "should correctly seal and open with polyfill cipher implementations"() { | ||
const polyfillProvider = new PolyfillCryptoProvider(); | ||
for (let v of AesSivSpec.vectors) { | ||
const siv = await AesSiv.importKey(v.key, new PolyfillCrypto()); | ||
const siv = await AesSiv.importKey(polyfillProvider, "AES-SIV", v.key); | ||
const sealed = await siv.seal(v.plaintext, v.ad); | ||
@@ -36,3 +39,3 @@ expect(sealed).to.eql(v.ciphertext); | ||
expect(unsealed!).to.eql(v.plaintext); | ||
expect(() => siv.clean()).not.to.throw(); | ||
expect(() => siv.clear()).not.to.throw(); | ||
} | ||
@@ -42,4 +45,6 @@ } | ||
@test async "should correctly seal and open with WebCrypto cipher implementations"() { | ||
const webCryptoProvider = new WebCryptoProvider(new WebCrypto()); | ||
for (let v of AesSivSpec.vectors) { | ||
const siv = await AesSiv.importKey(v.key, new WebCrypto()); | ||
const siv = await AesSiv.importKey(webCryptoProvider, "AES-SIV", v.key); | ||
const sealed = await siv.seal(v.plaintext, v.ad); | ||
@@ -51,3 +56,3 @@ expect(sealed).to.eql(v.ciphertext); | ||
expect(unsealed!).to.eql(v.plaintext); | ||
expect(() => siv.clean()).not.to.throw(); | ||
expect(() => siv.clear()).not.to.throw(); | ||
} | ||
@@ -57,2 +62,4 @@ } | ||
@test async "should correctly seal and open different plaintext under the same key"() { | ||
const polyfillProvider = new PolyfillCryptoProvider(); | ||
const key = byteSeq(64); | ||
@@ -65,3 +72,3 @@ const ad1 = [byteSeq(32), byteSeq(10)]; | ||
const siv = await AesSiv.importKey(key, new PolyfillCrypto()); | ||
const siv = await AesSiv.importKey(polyfillProvider, "AES-SIV", key); | ||
@@ -78,6 +85,8 @@ const sealed1 = await siv.seal(pt1, ad1); | ||
expect(() => siv.clean()).not.to.throw(); | ||
expect(() => siv.clear()).not.to.throw(); | ||
} | ||
@test async "should not open with incorrect key"() { | ||
const polyfillProvider = new PolyfillCryptoProvider(); | ||
for (let v of AesSivSpec.vectors) { | ||
@@ -89,3 +98,3 @@ const badKey = v.key; | ||
const siv = await AesSiv.importKey(badKey, new PolyfillCrypto()); | ||
const siv = await AesSiv.importKey(polyfillProvider, "AES-SIV", badKey); | ||
expect(siv.open(v.ciphertext, v.ad)).to.be.rejectedWith(IntegrityError); | ||
@@ -96,2 +105,4 @@ } | ||
@test async "should not open with incorrect associated data"() { | ||
const polyfillProvider = new PolyfillCryptoProvider(); | ||
for (let v of AesSivSpec.vectors) { | ||
@@ -101,3 +112,3 @@ const badAd = v.ad; | ||
const siv = await AesSiv.importKey(v.key, new PolyfillCrypto()); | ||
const siv = await AesSiv.importKey(polyfillProvider, "AES-SIV", v.key); | ||
return expect(siv.open(v.ciphertext, badAd)).to.be.rejectedWith(IntegrityError); | ||
@@ -108,2 +119,4 @@ } | ||
@test async "should not open with incorrect ciphertext"() { | ||
const polyfillProvider = new PolyfillCryptoProvider(); | ||
for (let v of AesSivSpec.vectors) { | ||
@@ -115,3 +128,3 @@ const badOutput = v.ciphertext; | ||
const siv = await AesSiv.importKey(v.key, new PolyfillCrypto()); | ||
const siv = await AesSiv.importKey(polyfillProvider, "AES-SIV", v.key); | ||
return expect(siv.open(badOutput, v.ad)).to.be.rejectedWith(IntegrityError); | ||
@@ -118,0 +131,0 @@ } |
@@ -1,10 +0,17 @@ | ||
// Copyright (C) 2016 Dmitry Chestnykh | ||
// Copyright (C) 2016-2017 Dmitry Chestnykh, Tony Arcieri | ||
// MIT License. See LICENSE file for details. | ||
import { suite, test } from "mocha-typescript"; | ||
import { expect } from "chai"; | ||
import * as chai from "chai"; | ||
import * as chaiAsPromised from "chai-as-promised"; | ||
import { AesExample } from "./support/test_vectors"; | ||
import Block from "../src/internal/block"; | ||
import WebCrypto = require("node-webcrypto-ossl"); | ||
import WebCryptoAes from "../src/internal/webcrypto/aes"; | ||
import PolyfillAes from "../src/internal/polyfill/aes"; | ||
let expect = chai.expect; | ||
chai.use(chaiAsPromised); | ||
@suite class PolyfillAesSpec { | ||
@@ -18,16 +25,12 @@ static vectors: AesExample[]; | ||
@test "should not accept wrong key length"() { | ||
expect(() => new PolyfillAes(new Uint8Array(10))).to.throw(/^AES/); | ||
expect(() => new PolyfillAes(new Uint8Array(10))).to.throw(/invalid key length/); | ||
} | ||
@test "should not accept different key in setKey()"() { | ||
const cipher = new PolyfillAes(new Uint8Array(32)); | ||
expect(() => cipher.setKey(new Uint8Array(16))).to.throw(/^AES/); | ||
} | ||
@test "should correctly encrypt block"() { | ||
@test "should correctly encrypt blocks"() { | ||
for (let v of PolyfillAesSpec.vectors) { | ||
const cipher = new PolyfillAes(v.key); | ||
const dst = new Uint8Array(16); | ||
cipher.encryptBlock(v.src, dst); | ||
expect(dst).to.eql(v.dst); | ||
const block = new Block(); | ||
block.data.set(v.src); | ||
cipher.encryptBlock(block); | ||
expect(block.data).to.eql(v.dst); | ||
} | ||
@@ -38,3 +41,3 @@ } | ||
let key = new Uint8Array(32); | ||
let block = new Uint8Array(16); | ||
let block = new Block(); | ||
const newKey = new Uint8Array(32); | ||
@@ -44,6 +47,6 @@ for (let i = 0; i < 100; i++) { | ||
for (let j = 0; j < 100; j++) { | ||
cipher.encryptBlock(block, block); | ||
cipher.encryptBlock(block); | ||
} | ||
newKey.set(key.subarray(16, 32)); // move 16 bytes to left | ||
newKey.set(block, 16); // fill the rest 16 bytes with block | ||
newKey.set(block.data, 16); // fill the rest 16 bytes with block | ||
key.set(newKey); | ||
@@ -53,4 +56,29 @@ } | ||
let expected = new Uint8Array([58, 111, 217, 50, 246, 8, 131, 95, 31, 86, 217, 220, 31, 206, 207, 163]); | ||
expect(block).to.eql(expected); | ||
expect(block.data).to.eql(expected); | ||
} | ||
} | ||
@suite class WebCryptoAesSpec { | ||
static vectors: AesExample[]; | ||
static async before() { | ||
this.vectors = await AesExample.loadAll(); | ||
} | ||
@test "should not accept wrong key length"() { | ||
const crypto = new WebCrypto(); | ||
expect(WebCryptoAes.importKey(crypto, new Uint8Array(10))).to.be.rejectedWith(Error); | ||
} | ||
@test async "should correctly encrypt blocks"() { | ||
const crypto = new WebCrypto(); | ||
for (let v of WebCryptoAesSpec.vectors) { | ||
const cipher = await WebCryptoAes.importKey(crypto, v.key); | ||
const block = new Block(); | ||
block.data.set(v.src); | ||
await cipher.encryptBlock(block); | ||
expect(block.data).to.eql(v.dst); | ||
} | ||
} | ||
} |
@@ -6,3 +6,3 @@ // Copyright (C) 2016 Dmitry Chestnykh | ||
import { expect } from "chai"; | ||
import { select, compare, equal } from "../src/internal/constant-time"; | ||
import { select, compare, equal } from "../src/internal/util/constant-time"; | ||
@@ -9,0 +9,0 @@ @suite class SelectSpec { |
@@ -6,3 +6,4 @@ // Copyright (C) 2017 Dmitry Chestnykh | ||
import { expect } from "chai"; | ||
import { AesSivExample } from "./support/test_vectors"; | ||
import { AesSivExample, AesPmacSivExample } from "./support/test_vectors"; | ||
import WebCryptoProvider from "../src/internal/webcrypto/provider"; | ||
@@ -13,3 +14,3 @@ import WebCrypto = require("node-webcrypto-ossl"); | ||
@suite class SivSpec { | ||
@suite class MiscreantAesSivSpec { | ||
static vectors: AesSivExample[]; | ||
@@ -21,5 +22,21 @@ | ||
@test async "AES-SIV: should correctly seal and open with PolyfillCrypto"() { | ||
const polyfillProvider = Miscreant.polyfillCryptoProvider(); | ||
for (let v of MiscreantAesSivSpec.vectors) { | ||
const siv = await Miscreant.importKey(v.key, "AES-SIV", polyfillProvider); | ||
const sealed = await siv.seal(v.plaintext, v.ad); | ||
expect(sealed).to.eql(v.ciphertext); | ||
const unsealed = await siv.open(sealed, v.ad); | ||
expect(unsealed).not.to.be.null; | ||
expect(unsealed!).to.eql(v.plaintext); | ||
expect(() => siv.clear()).not.to.throw(); | ||
} | ||
} | ||
@test async "AES-SIV: should correctly seal and open with WebCrypto"() { | ||
for (let v of SivSpec.vectors) { | ||
const siv = await Miscreant.importKey(v.key, "AES-SIV", new WebCrypto()); | ||
const webCryptoProvider = new WebCryptoProvider(new WebCrypto()); | ||
for (let v of MiscreantAesSivSpec.vectors) { | ||
const siv = await Miscreant.importKey(v.key, "AES-SIV", webCryptoProvider); | ||
const sealed = await siv.seal(v.plaintext, v.ad); | ||
@@ -31,9 +48,18 @@ expect(sealed).to.eql(v.ciphertext); | ||
expect(unsealed!).to.eql(v.plaintext); | ||
expect(() => siv.clean()).not.to.throw(); | ||
expect(() => siv.clear()).not.to.throw(); | ||
} | ||
} | ||
} | ||
@test async "AES-SIV: should correctly seal and open with PolyfillCrypto"() { | ||
for (let v of SivSpec.vectors) { | ||
const siv = await Miscreant.importKey(v.key, "AES-SIV", Miscreant.getCryptoProvider("polyfill")); | ||
@suite class MiscreantAesPmacSivSpec { | ||
static vectors: AesPmacSivExample[]; | ||
static async before() { | ||
this.vectors = await AesPmacSivExample.loadAll(); | ||
} | ||
@test async "AES-PMAC-SIV: should correctly seal and open with PolyfillCrypto"() { | ||
const polyfillProvider = Miscreant.polyfillCryptoProvider(); | ||
for (let v of MiscreantAesPmacSivSpec.vectors) { | ||
const siv = await Miscreant.importKey(v.key, "AES-PMAC-SIV", polyfillProvider); | ||
const sealed = await siv.seal(v.plaintext, v.ad); | ||
@@ -45,5 +71,20 @@ expect(sealed).to.eql(v.ciphertext); | ||
expect(unsealed!).to.eql(v.plaintext); | ||
expect(() => siv.clean()).not.to.throw(); | ||
expect(() => siv.clear()).not.to.throw(); | ||
} | ||
} | ||
@test async "AES-PMAC-SIV: should correctly seal and open with WebCrypto"() { | ||
const webCryptoProvider = new WebCryptoProvider(new WebCrypto()); | ||
for (let v of MiscreantAesPmacSivSpec.vectors) { | ||
const siv = await Miscreant.importKey(v.key, "AES-PMAC-SIV", webCryptoProvider); | ||
const sealed = await siv.seal(v.plaintext, v.ad); | ||
expect(sealed).to.eql(v.ciphertext); | ||
const unsealed = await siv.open(sealed, v.ad); | ||
expect(unsealed).not.to.be.null; | ||
expect(unsealed!).to.eql(v.plaintext); | ||
expect(() => siv.clear()).not.to.throw(); | ||
} | ||
} | ||
} |
@@ -27,2 +27,25 @@ import * as fs from "async-file"; | ||
/** AES-PMAC-SIV test vectors */ | ||
export class AesPmacSivExample { | ||
static readonly DEFAULT_EXAMPLES_PATH = "../vectors/aes_pmac_siv.tjson"; | ||
public readonly name: string; | ||
public readonly key: Uint8Array; | ||
public readonly ad: Uint8Array[]; | ||
public readonly plaintext: Uint8Array; | ||
public readonly ciphertext: Uint8Array; | ||
static async loadAll(): Promise<AesPmacSivExample[]> { | ||
return AesPmacSivExample.loadFromFile(AesPmacSivExample.DEFAULT_EXAMPLES_PATH); | ||
} | ||
static async loadFromFile(filename: string): Promise<AesPmacSivExample[]> { | ||
let tjson = TJSON.parse(await fs.readFile(filename, "utf8")); | ||
return tjson["examples"].map((ex: any) => { | ||
let obj = Object.create(AesPmacSivExample.prototype); | ||
return Object.assign(obj, ex); | ||
}); | ||
} | ||
} | ||
/** AES (raw block function) test vectors */ | ||
@@ -92,2 +115,23 @@ export class AesExample { | ||
/** AES-PMAC test vectors */ | ||
export class AesPmacExample { | ||
static readonly DEFAULT_EXAMPLES_PATH = "../vectors/aes_pmac.tjson"; | ||
public readonly key: Uint8Array; | ||
public readonly message: Uint8Array; | ||
public readonly tag: Uint8Array; | ||
static async loadAll(): Promise<AesPmacExample[]> { | ||
return AesPmacExample.loadFromFile(AesPmacExample.DEFAULT_EXAMPLES_PATH); | ||
} | ||
static async loadFromFile(filename: string): Promise<AesPmacExample[]> { | ||
let tjson = TJSON.parse(await fs.readFile(filename, "utf8")); | ||
return tjson["examples"].map((ex: any) => { | ||
let obj = Object.create(AesPmacExample.prototype); | ||
return Object.assign(obj, ex); | ||
}); | ||
} | ||
} | ||
/** dbl() test vectors */ | ||
@@ -94,0 +138,0 @@ export class DblExample { |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
42
2005
315
92291
1