Merge pull request #1153 from Mikunj/message-encrypter

Message encrypter
pull/1157/head
Mikunj Varsani 5 years ago committed by Vincent
commit 89c16b19b6

@ -179,7 +179,7 @@ export function updateGuardNodes(nodes: Array<string>): Promise<void>;
// Storage Items
export function createOrUpdateItem(data: StorageItem): Promise<void>;
export function getItemById(id: string): Promise<StorageItem>;
export function getItemById(id: string): Promise<StorageItem | undefined>;
export function getAlItems(): Promise<Array<StorageItem>>;
export function bulkAddItems(array: Array<StorageItem>): Promise<void>;
export function removeItemById(id: string): Promise<void>;

@ -1,7 +1,8 @@
/* global window, setTimeout, clearTimeout, IDBKeyRange, dcodeIO */
const electron = require('electron');
const { ipcRenderer } = electron;
// TODO: this results in poor readability, would be
// much better to explicitly call with `_`.
const {
@ -21,12 +22,6 @@ const _ = require('lodash');
const { base64ToArrayBuffer, arrayBufferToBase64 } = require('./crypto');
const MessageType = require('./types/message');
const { ipcRenderer } = electron;
// We listen to a lot of events on ipcRenderer, often on the same channel. This prevents
// any warnings that might be sent to the console in that case.
ipcRenderer.setMaxListeners(0);
const DATABASE_UPDATE_TIMEOUT = 2 * 60 * 1000; // two minutes
const SQL_CHANNEL_KEY = 'sql-channel';
@ -44,6 +39,7 @@ let _shutdownPromise = null;
const channels = {};
module.exports = {
init,
_jobs,
_cleanData,
@ -212,6 +208,42 @@ module.exports = {
createOrUpdateSenderKeys,
};
function init() {
// We listen to a lot of events on ipcRenderer, often on the same channel. This prevents
// any warnings that might be sent to the console in that case.
ipcRenderer.setMaxListeners(0);
forEach(module.exports, fn => {
if (isFunction(fn) && fn.name !== 'init') {
makeChannel(fn.name);
}
});
ipcRenderer.on(
`${SQL_CHANNEL_KEY}-done`,
(event, jobId, errorForDisplay, result) => {
const job = _getJob(jobId);
if (!job) {
throw new Error(
`Received SQL channel reply to job ${jobId}, but did not have it in our registry!`
);
}
const { resolve, reject, fnName } = job;
if (errorForDisplay) {
return reject(
new Error(
`Error received from SQL channel job ${jobId} (${fnName}): ${errorForDisplay}`
)
);
}
return resolve(result);
}
);
}
// When IPC arguments are prepared for the cross-process send, they are JSON.stringified.
// We can't send ArrayBuffers or BigNumbers (what we get from proto library for dates).
function _cleanData(data) {
@ -352,30 +384,6 @@ function _getJob(id) {
return _jobs[id];
}
ipcRenderer.on(
`${SQL_CHANNEL_KEY}-done`,
(event, jobId, errorForDisplay, result) => {
const job = _getJob(jobId);
if (!job) {
throw new Error(
`Received SQL channel reply to job ${jobId}, but did not have it in our registry!`
);
}
const { resolve, reject, fnName } = job;
if (errorForDisplay) {
return reject(
new Error(
`Error received from SQL channel job ${jobId} (${fnName}): ${errorForDisplay}`
)
);
}
return resolve(result);
}
);
function makeChannel(fnName) {
channels[fnName] = (...args) => {
const jobId = _makeJob(fnName);
@ -398,12 +406,6 @@ function makeChannel(fnName) {
};
}
forEach(module.exports, fn => {
if (isFunction(fn)) {
makeChannel(fn.name);
}
});
function keysToArrayBuffer(keys, data) {
const updated = cloneDeep(data);
for (let i = 0, max = keys.length; i < max; i += 1) {

@ -263,6 +263,8 @@ function initializeMigrations({
exports.setup = (options = {}) => {
const { Attachments, userDataPath, getRegionCode, logger } = options;
Data.init();
const Migrations = initializeMigrations({
userDataPath,
getRegionCode,

@ -149,10 +149,13 @@
myPrivateKey
);
const ivAndCiphertext = await DHEncrypt(symmetricKey, plaintext);
const binaryIvAndCiphertext = dcodeIO.ByteBuffer.wrap(
ivAndCiphertext
).toString('binary');
return {
type: textsecure.protobuf.Envelope.Type.FRIEND_REQUEST,
body: ivAndCiphertext,
registrationId: null,
body: binaryIvAndCiphertext,
registrationId: undefined,
};
}

@ -144,7 +144,7 @@
"@types/redux-logger": "3.0.7",
"@types/rimraf": "2.0.2",
"@types/semver": "5.5.0",
"@types/sinon": "4.3.1",
"@types/sinon": "9.0.4",
"@types/uuid": "3.4.4",
"arraybuffer-loader": "1.0.3",
"asar": "0.14.0",
@ -180,9 +180,10 @@
"qs": "6.5.1",
"react-docgen-typescript": "1.2.6",
"react-styleguidist": "7.0.1",
"sinon": "4.4.2",
"sinon": "9.0.2",
"spectron": "^10.0.0",
"ts-loader": "4.1.0",
"ts-mock-imports": "^1.3.0",
"tslint": "5.13.0",
"tslint-microsoft-contrib": "6.0.0",
"tslint-react": "3.6.0",

@ -1,7 +1,10 @@
import { EncryptionType } from '../types/EncryptionType';
import { SignalService } from '../../protobuf';
import { libloki, libsignal, Signal, textsecure } from '../../window';
import { CipherTextObject } from '../../window/types/libsignal-protocol';
import { UserUtil } from '../../util';
function padPlainTextBuffer(messageBuffer: Uint8Array): Uint8Array {
export function padPlainTextBuffer(messageBuffer: Uint8Array): Uint8Array {
const plaintext = new Uint8Array(
getPaddedMessageLength(messageBuffer.byteLength + 1) - 1
);
@ -22,19 +25,75 @@ function getPaddedMessageLength(originalLength: number): number {
return messagePartCount * 160;
}
export function encrypt(
export type Base64String = string;
/**
* Encrypt `plainTextBuffer` with given `encryptionType` for `device`.
*
* @param device The device to encrypt for.
* @param plainTextBuffer The unpadded plaintext buffer.
* @param encryptionType The type of encryption.
* @returns The envelope type and the base64 encoded cipher text
*/
export async function encrypt(
device: string,
plainTextBuffer: Uint8Array,
encryptionType: EncryptionType
): {
): Promise<{
envelopeType: SignalService.Envelope.Type;
cipherText: Uint8Array;
} {
cipherText: Base64String;
}> {
const plainText = padPlainTextBuffer(plainTextBuffer);
// TODO: Do encryption here?
const address = new libsignal.SignalProtocolAddress(device, 1);
if (encryptionType === EncryptionType.MediumGroup) {
// TODO: Do medium group stuff here
throw new Error('Encryption is not yet supported');
}
let innerCipherText: CipherTextObject;
if (encryptionType === EncryptionType.SessionReset) {
const cipher = new libloki.crypto.FallBackSessionCipher(address);
innerCipherText = await cipher.encrypt(plainText.buffer);
} else {
const cipher = new libsignal.SessionCipher(
textsecure.storage.protocol,
address
);
innerCipherText = await cipher.encrypt(plainText.buffer);
}
return encryptUsingSealedSender(device, innerCipherText);
}
async function encryptUsingSealedSender(
device: string,
innerCipherText: CipherTextObject
): Promise<{
envelopeType: SignalService.Envelope.Type;
cipherText: Base64String;
}> {
const ourNumber = await UserUtil.getCurrentDevicePubKey();
if (!ourNumber) {
throw new Error('Failed to fetch current device public key.');
}
const certificate = SignalService.SenderCertificate.create({
sender: ourNumber,
senderDevice: 1,
});
const cipher = new Signal.Metadata.SecretSessionCipher(
textsecure.storage.protocol
);
const cipherTextBuffer = await cipher.encrypt(
device,
certificate,
innerCipherText
);
return {
envelopeType: SignalService.Envelope.Type.CIPHERTEXT,
cipherText: new Uint8Array(),
envelopeType: SignalService.Envelope.Type.UNIDENTIFIED_SENDER,
cipherText: Buffer.from(cipherTextBuffer).toString('base64'),
};
}

@ -0,0 +1,163 @@
import { expect } from 'chai';
import * as crypto from 'crypto';
import * as sinon from 'sinon';
import { MessageEncrypter } from '../../../session/crypto';
import { EncryptionType } from '../../../session/types/EncryptionType';
import { Stubs, TestUtils } from '../../test-utils';
import { UserUtil } from '../../../util';
import { SignalService } from '../../../protobuf';
describe('MessageEncrypter', () => {
const sandbox = sinon.createSandbox();
const ourNumber = 'ourNumber';
beforeEach(() => {
TestUtils.stubWindow('libsignal', {
SignalProtocolAddress: sandbox.stub(),
SessionCipher: Stubs.SessionCipherStub,
} as any);
TestUtils.stubWindow('textsecure', {
storage: {
protocol: sandbox.stub(),
},
});
TestUtils.stubWindow('Signal', {
Metadata: {
SecretSessionCipher: Stubs.SecretSessionCipherStub,
},
});
TestUtils.stubWindow('libloki', {
crypto: {
FallBackSessionCipher: Stubs.FallBackSessionCipherStub,
},
});
sandbox.stub(UserUtil, 'getCurrentDevicePubKey').resolves(ourNumber);
});
afterEach(() => {
sandbox.restore();
TestUtils.restoreStubs();
});
describe('EncryptionType', () => {
describe('MediumGroup', () => {
it('should throw an error', async () => {
const data = crypto.randomBytes(10);
const promise = MessageEncrypter.encrypt(
'1',
data,
EncryptionType.MediumGroup
);
await expect(promise).to.be.rejectedWith(
'Encryption is not yet supported'
);
});
});
describe('SessionReset', () => {
it('should call FallbackSessionCipher encrypt', async () => {
const data = crypto.randomBytes(10);
const spy = sandbox.spy(
Stubs.FallBackSessionCipherStub.prototype,
'encrypt'
);
await MessageEncrypter.encrypt('1', data, EncryptionType.SessionReset);
expect(spy.called).to.equal(
true,
'FallbackSessionCipher.encrypt should be called.'
);
});
it('should pass the padded message body to encrypt', async () => {
const data = crypto.randomBytes(10);
const spy = sandbox.spy(
Stubs.FallBackSessionCipherStub.prototype,
'encrypt'
);
await MessageEncrypter.encrypt('1', data, EncryptionType.SessionReset);
const paddedData = MessageEncrypter.padPlainTextBuffer(data);
const firstArgument = new Uint8Array(spy.args[0][0]);
expect(firstArgument).to.deep.equal(paddedData);
});
it('should return an UNIDENTIFIED SENDER envelope type', async () => {
const data = crypto.randomBytes(10);
const result = await MessageEncrypter.encrypt(
'1',
data,
EncryptionType.SessionReset
);
expect(result.envelopeType).to.deep.equal(
SignalService.Envelope.Type.UNIDENTIFIED_SENDER
);
});
});
describe('Signal', () => {
it('should call SessionCipher encrypt', async () => {
const data = crypto.randomBytes(10);
const spy = sandbox.spy(Stubs.SessionCipherStub.prototype, 'encrypt');
await MessageEncrypter.encrypt('1', data, EncryptionType.Signal);
expect(spy.called).to.equal(
true,
'SessionCipher.encrypt should be called.'
);
});
it('should pass the padded message body to encrypt', async () => {
const data = crypto.randomBytes(10);
const spy = sandbox.spy(Stubs.SessionCipherStub.prototype, 'encrypt');
await MessageEncrypter.encrypt('1', data, EncryptionType.Signal);
const paddedData = MessageEncrypter.padPlainTextBuffer(data);
const firstArgument = new Uint8Array(spy.args[0][0]);
expect(firstArgument).to.deep.equal(paddedData);
});
it('should return an UNIDENTIFIED SENDER envelope type', async () => {
const data = crypto.randomBytes(10);
const result = await MessageEncrypter.encrypt(
'1',
data,
EncryptionType.Signal
);
expect(result.envelopeType).to.deep.equal(
SignalService.Envelope.Type.UNIDENTIFIED_SENDER
);
});
});
});
describe('Sealed Sender', () => {
it('should pass the correct values to SecretSessionCipher encrypt', async () => {
const types = [EncryptionType.SessionReset, EncryptionType.Signal];
for (const type of types) {
const spy = sandbox.spy(
Stubs.SecretSessionCipherStub.prototype,
'encrypt'
);
await MessageEncrypter.encrypt('user', crypto.randomBytes(10), type);
const args = spy.args[0];
const [device, certificate] = args;
const expectedCertificate = SignalService.SenderCertificate.create({
sender: ourNumber,
senderDevice: 1,
});
expect(device).to.equal('user');
expect(certificate.toJSON()).to.deep.equal(
expectedCertificate.toJSON()
);
spy.restore();
}
});
});
});

@ -1,7 +1,7 @@
import chai from 'chai';
import { v4 as uuid } from 'uuid';
import { JobQueue } from '../../../session/utils/JobQueue';
import { timeout } from '../../utils/timeout';
import { timeout } from '../../test-utils';
// tslint:disable-next-line: no-require-imports no-var-requires
const chaiAsPromised = require('chai-as-promised');

@ -0,0 +1,5 @@
import * as TestUtils from './testUtils';
import * as Stubs from './stubs';
export * from './timeout';
export { TestUtils, Stubs };

@ -0,0 +1,11 @@
import { CipherTextObject } from '../../../../window/types/libsignal-protocol';
import { SignalService } from '../../../../protobuf';
export class FallBackSessionCipherStub {
public async encrypt(buffer: ArrayBuffer): Promise<CipherTextObject> {
return {
type: SignalService.Envelope.Type.FRIEND_REQUEST,
body: Buffer.from(buffer).toString('binary'),
};
}
}

@ -0,0 +1,26 @@
import { SignalService } from '../../../../protobuf';
import { CipherTextObject } from '../../../../window/types/libsignal-protocol';
export class SecretSessionCipherStub {
public async encrypt(
_destinationPubkey: string,
_senderCertificate: SignalService.SenderCertificate,
innerEncryptedMessage: CipherTextObject
): Promise<ArrayBuffer> {
const { body } = innerEncryptedMessage;
return Buffer.from(body, 'binary').buffer;
}
public async decrypt(
_cipherText: ArrayBuffer,
_me: { number: string; deviceId: number }
): Promise<{
isMe?: boolean;
sender: string;
content: ArrayBuffer;
type: SignalService.Envelope.Type;
}> {
throw new Error('Not implemented');
}
}

@ -0,0 +1,20 @@
import { CipherTextObject } from '../../../../window/types/libsignal-protocol';
import { SignalService } from '../../../../protobuf';
export class SessionCipherStub {
public storage: any;
public address: any;
constructor(storage: any, address: any) {
this.storage = storage;
this.address = address;
}
public async encrypt(
buffer: ArrayBuffer | Uint8Array
): Promise<CipherTextObject> {
return {
type: SignalService.Envelope.Type.CIPHERTEXT,
body: Buffer.from(buffer).toString('binary'),
};
}
}

@ -0,0 +1,3 @@
export * from './SessionCipherStub';
export * from './SecretSessionCipherStub';
export * from './FallBackSessionCipherStub';

@ -0,0 +1 @@
export * from './ciphers';

@ -0,0 +1,42 @@
import * as sinon from 'sinon';
import { ImportMock } from 'ts-mock-imports';
import * as DataShape from '../../../js/modules/data';
import * as window from '../../window';
const sandbox = sinon.createSandbox();
// We have to do this in a weird way because Data uses module.exports
// which doesn't play well with sinon or ImportMock
// tslint:disable-next-line: no-require-imports no-var-requires
const Data = require('../../../js/modules/data');
type DataFunction = typeof DataShape;
/**
* Stub a function inside Data.
*
* Note: This uses a custom sandbox.
* Please call `restoreStubs()` or `stub.restore()` to restore original functionality.
*/
export function stubData(fn: keyof DataFunction): sinon.SinonStub {
return sandbox.stub(Data, fn);
}
type WindowFunction = typeof window;
/**
* Stub a window object.
*
* Note: This uses a custom sandbox.
* Please call `restoreStubs()` or `stub.restore()` to restore original functionality.
*/
export function stubWindow<K extends keyof WindowFunction>(
fn: K,
replaceWith?: Partial<WindowFunction[K]>
) {
return ImportMock.mockOther(window, fn, replaceWith);
}
export function restoreStubs() {
ImportMock.restore();
sandbox.restore();
}

@ -0,0 +1,27 @@
import { SignalProtocolAddress } from '../../../window/types/libsignal-protocol';
export class SignalProtocolAddressStub extends SignalProtocolAddress {
private readonly hexEncodedPublicKey: string;
private readonly deviceId: number;
constructor(hexEncodedPublicKey: string, deviceId: number) {
super(hexEncodedPublicKey, deviceId);
this.hexEncodedPublicKey = hexEncodedPublicKey;
this.deviceId = deviceId;
}
// tslint:disable-next-line: function-name
public static fromString(encodedAddress: string): SignalProtocolAddressStub {
const values = encodedAddress.split('.');
return new SignalProtocolAddressStub(values[0], Number(values[1]));
}
public getName(): string { return this.hexEncodedPublicKey; }
public getDeviceId(): number { return this.deviceId; }
public equals(other: SignalProtocolAddress): boolean {
return other.getName() === this.hexEncodedPublicKey;
}
public toString(): string { return this.hexEncodedPublicKey; }
}

@ -4,6 +4,7 @@ import { isFileDangerous } from './isFileDangerous';
import { missingCaseError } from './missingCaseError';
import { migrateColor } from './migrateColor';
import { makeLookup } from './makeLookup';
import * as UserUtil from './user';
export {
arrayBufferToObjectURL,
@ -12,4 +13,5 @@ export {
makeLookup,
migrateColor,
missingCaseError,
UserUtil,
};

@ -0,0 +1,17 @@
import { getItemById } from '../../js/modules/data';
import { KeyPair } from '../window/types/libsignal-protocol';
export async function getCurrentDevicePubKey(): Promise<string | undefined> {
const item = await getItemById('number_id');
if (!item || !item.value) {
return undefined;
}
return item.value.split('.')[0];
}
export async function getIdentityKeyPair(): Promise<KeyPair | undefined> {
const item = await getItemById('identityKey');
return item?.value;
}

@ -1,6 +1,8 @@
import { LocalizerType } from './types/Util';
import { LibsignalProtocol } from './types/libsignal-protocol';
import { SignalInterface } from './types/signal';
import { LocalizerType } from '../types/Util';
interface Window {
interface WindowInterface extends Window {
seedNodeList: any;
WebAPI: any;
@ -32,11 +34,11 @@ interface Window {
shortenPubkey: any;
dcodeIO: any;
libsignal: any;
libsignal: LibsignalProtocol;
libloki: any;
displayNameRegex: any;
Signal: any;
Signal: SignalInterface;
Whisper: any;
ConversationController: any;
@ -72,7 +74,16 @@ interface Window {
resetDatabase: any;
}
declare const window: Window;
// In the case for tests
// tslint:disable-next-line: no-typeof-undefined
if (typeof window === 'undefined') {
const globalAny: any = global;
globalAny.window = {};
}
declare const window: WindowInterface;
// TODO: Is there an easier way to dynamically export these?
// Utilities
export const WebAPI = window.WebAPI;
@ -118,3 +129,7 @@ export const clearLocalData = window.clearLocalData;
export const deleteAccount = window.deleteAccount;
export const resetDatabase = window.resetDatabase;
export const attemptConnection = window.attemptConnection;
export const libloki = window.libloki;
export const libsignal = window.libsignal;
export const textsecure = window.textsecure;

@ -0,0 +1,20 @@
import { SignalService } from '../../protobuf';
import { CipherTextObject } from './libsignal-protocol';
export declare class SecretSessionCipher {
constructor(storage: any);
public encrypt(
destinationPubkey: string,
senderCertificate: SignalService.SenderCertificate,
innerEncryptedMessage: CipherTextObject
): Promise<ArrayBuffer>;
public decrypt(
cipherText: ArrayBuffer,
me: { number: string; deviceId: number }
): Promise<{
isMe?: boolean;
sender: string;
content: ArrayBuffer;
type: SignalService.Envelope.Type;
}>;
}

@ -0,0 +1,127 @@
import { SignalService } from '../../protobuf';
export type BinaryString = string;
export type CipherTextObject = {
type: SignalService.Envelope.Type;
body: BinaryString;
registrationId?: number;
};
export declare class SignalProtocolAddress {
constructor(hexEncodedPublicKey: string, deviceId: number);
// tslint:disable-next-line: function-name
public static fromString(encodedAddress: string): SignalProtocolAddress;
public getName(): string;
public getDeviceId(): number;
public toString(): string;
public equals(other: SignalProtocolAddress): boolean;
}
export type KeyPair = {
pubKey: ArrayBuffer;
privKey: ArrayBuffer;
};
interface CurveSync {
generateKeyPair(): KeyPair;
createKeyPair(privKey: ArrayBuffer): KeyPair;
calculateAgreement(pubKey: ArrayBuffer, privKey: ArrayBuffer): ArrayBuffer;
verifySignature(
pubKey: ArrayBuffer,
msg: ArrayBuffer,
sig: ArrayBuffer
): void;
calculateSignature(privKey: ArrayBuffer, message: ArrayBuffer): ArrayBuffer;
validatePubKeyFormat(pubKey: ArrayBuffer): ArrayBuffer;
}
interface CurveAsync {
generateKeyPair(): Promise<KeyPair>;
createKeyPair(privKey: ArrayBuffer): Promise<KeyPair>;
calculateAgreement(
pubKey: ArrayBuffer,
privKey: ArrayBuffer
): Promise<ArrayBuffer>;
verifySignature(
pubKey: ArrayBuffer,
msg: ArrayBuffer,
sig: ArrayBuffer
): Promise<void>;
calculateSignature(
privKey: ArrayBuffer,
message: ArrayBuffer
): Promise<ArrayBuffer>;
validatePubKeyFormat(pubKey: ArrayBuffer): Promise<ArrayBuffer>;
}
export interface CurveInterface extends CurveSync {
async: CurveAsync;
}
export interface CryptoInterface {
encrypt(
key: ArrayBuffer,
data: ArrayBuffer,
iv: ArrayBuffer
): Promise<ArrayBuffer>;
decrypt(
key: ArrayBuffer,
data: ArrayBuffer,
iv: ArrayBuffer
): Promise<ArrayBuffer>;
calculateMAC(key: ArrayBuffer, data: ArrayBuffer): Promise<ArrayBuffer>;
verifyMAC(
data: ArrayBuffer,
key: ArrayBuffer,
mac: ArrayBuffer,
length: number
): Promise<void>;
getRandomBytes(size: number): ArrayBuffer;
}
export interface KeyHelperInterface {
generateIdentityKeyPair(): Promise<KeyPair>;
generateRegistrationId(): number;
generateSignedPreKey(
identityKeyPair: KeyPair,
signedKeyId: number
): Promise<{
keyId: number;
keyPair: KeyPair;
signature: ArrayBuffer;
}>;
generatePreKey(
keyId: number
): Promise<{
keyId: number;
keyPair: KeyPair;
}>;
}
export declare class SessionCipher {
constructor(storage: any, remoteAddress: SignalProtocolAddress);
/**
* @returns The envelope type, registration id and binary encoded encrypted body.
*/
public encrypt(buffer: ArrayBuffer | Uint8Array): Promise<CipherTextObject>;
public decryptPreKeyWhisperMessage(
buffer: ArrayBuffer | Uint8Array
): Promise<ArrayBuffer>;
public decryptWhisperMessage(
buffer: ArrayBuffer | Uint8Array
): Promise<ArrayBuffer>;
public getRecord(encodedNumber: string): Promise<any | undefined>;
public getRemoteRegistrationId(): Promise<number>;
public hasOpenSession(): Promise<boolean>;
public closeOpenSessionForDevice(): Promise<void>;
public deleteAllSessionsForDevice(): Promise<void>;
}
export interface LibsignalProtocol {
SignalProtocolAddress: typeof SignalProtocolAddress;
Curve: CurveInterface;
crypto: CryptoInterface;
KeyHelper: KeyHelperInterface;
SessionCipher: typeof SessionCipher;
}

@ -0,0 +1,9 @@
import { SecretSessionCipher } from './SecretSessionCipher';
interface Metadata {
SecretSessionCipher: typeof SecretSessionCipher;
}
export interface SignalInterface {
Metadata: Metadata;
}

@ -113,36 +113,43 @@
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==
"@sinonjs/commons@^1", "@sinonjs/commons@^1.3.0", "@sinonjs/commons@^1.7.0":
"@sinonjs/commons@^1", "@sinonjs/commons@^1.7.0":
version "1.7.1"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.7.1.tgz#da5fd19a5f71177a53778073978873964f49acf1"
integrity sha512-Debi3Baff1Qu1Unc3mjJ96MgpbwTn43S1+9yJ0llWygPwDNu2aaWBD6yc9y/Z8XDRNhx7U+u2UDg2OGQXkclUQ==
dependencies:
type-detect "4.0.8"
"@sinonjs/formatio@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-2.0.0.tgz#84db7e9eb5531df18a8c5e0bfb6e449e55e654b2"
integrity sha512-ls6CAMA6/5gG+O/IdsBcblvnd8qcO/l1TYoNeAzp3wcISOxlPXQEus0mLcdwazEkWjaBdaJ3TaxmNgCLWwvWzg==
"@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.2":
version "1.8.0"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.0.tgz#c8d68821a854c555bba172f3b06959a0039b236d"
integrity sha512-wEj54PfsZ5jGSwMX68G8ZXFawcSglQSXqCftWX3ec8MDUzQdHgcKvw97awHbY0efQEL5iKUOAmmVtoYgmrSG4Q==
dependencies:
samsam "1.3.0"
type-detect "4.0.8"
"@sinonjs/formatio@^3.2.1":
version "3.2.2"
resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-3.2.2.tgz#771c60dfa75ea7f2d68e3b94c7e888a78781372c"
integrity sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==
"@sinonjs/fake-timers@^6.0.0", "@sinonjs/fake-timers@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40"
integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==
dependencies:
"@sinonjs/commons" "^1.7.0"
"@sinonjs/formatio@^5.0.1":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-5.0.1.tgz#f13e713cb3313b1ab965901b01b0828ea6b77089"
integrity sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==
dependencies:
"@sinonjs/commons" "^1"
"@sinonjs/samsam" "^3.1.0"
"@sinonjs/samsam" "^5.0.2"
"@sinonjs/samsam@^3.1.0":
version "3.3.3"
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-3.3.3.tgz#46682efd9967b259b81136b9f120fd54585feb4a"
integrity sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==
"@sinonjs/samsam@^5.0.2", "@sinonjs/samsam@^5.0.3":
version "5.0.3"
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-5.0.3.tgz#86f21bdb3d52480faf0892a480c9906aa5a52938"
integrity sha512-QucHkc2uMJ0pFGjJUDP3F9dq5dx8QIaqISl9QgwLOh6P9yv877uONPGXh/OH/0zmM3tW1JjuJltAZV2l7zU+uQ==
dependencies:
"@sinonjs/commons" "^1.3.0"
array-from "^2.1.1"
lodash "^4.17.15"
"@sinonjs/commons" "^1.6.0"
lodash.get "^4.4.2"
type-detect "^4.0.8"
"@sinonjs/text-encoding@^0.7.1":
version "0.7.1"
@ -411,10 +418,17 @@
dependencies:
"@types/node" "*"
"@types/sinon@4.3.1":
version "4.3.1"
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-4.3.1.tgz#32458f9b166cd44c23844eee4937814276f35199"
integrity sha512-DK4YtH30I67k4klURIBS4VAe1aBISfS9lgNlHFkibSmKem2tLQc5VkKoJreT3dCJAd+xRyCS8bx1o97iq3yUVg==
"@types/sinon@9.0.4":
version "9.0.4"
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-9.0.4.tgz#e934f904606632287a6e7f7ab0ce3f08a0dad4b1"
integrity sha512-sJmb32asJZY6Z2u09bl0G2wglSxDlROlAejCjsnor+LzBMz17gu8IU7vKC/vWDnv9zEq2wqADHVXFjf4eE8Gdw==
dependencies:
"@types/sinonjs__fake-timers" "*"
"@types/sinonjs__fake-timers@*":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz#681df970358c82836b42f989188d133e218c458e"
integrity sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA==
"@types/sizzle@*":
version "2.3.2"
@ -777,11 +791,6 @@ array-flatten@^2.1.0:
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099"
integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==
array-from@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/array-from/-/array-from-2.1.1.tgz#cfe9d8c26628b9dc5aecc62a9f5d8f1f352c1195"
integrity sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=
array-includes@^3.0.3:
version "3.1.1"
resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348"
@ -2749,11 +2758,16 @@ diff@3.3.1:
resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75"
integrity sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==
diff@^3.1.0, diff@^3.2.0:
diff@^3.2.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
diff@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
diffie-hellman@^5.0.0:
version "5.0.3"
resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
@ -5945,18 +5959,6 @@ loglevel@^1.4.1:
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.7.tgz#b3e034233188c68b889f5b862415306f565e2c56"
integrity sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A==
lolex@^2.2.0:
version "2.7.5"
resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.7.5.tgz#113001d56bfc7e02d56e36291cc5c413d1aa0733"
integrity sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==
lolex@^5.0.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367"
integrity sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==
dependencies:
"@sinonjs/commons" "^1.7.0"
long@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
@ -6543,15 +6545,15 @@ neo-async@^2.5.0, neo-async@^2.6.0:
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
nise@^1.2.0:
version "1.5.3"
resolved "https://registry.yarnpkg.com/nise/-/nise-1.5.3.tgz#9d2cfe37d44f57317766c6e9408a359c5d3ac1f7"
integrity sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==
nise@^4.0.1:
version "4.0.3"
resolved "https://registry.yarnpkg.com/nise/-/nise-4.0.3.tgz#9f79ff02fa002ed5ffbc538ad58518fa011dc913"
integrity sha512-EGlhjm7/4KvmmE6B/UFsKh7eHykRl9VH+au8dduHLCyWUO/hr7+N+WtTvDUwc9zHuM1IaIJs/0lQ6Ag1jDkQSg==
dependencies:
"@sinonjs/formatio" "^3.2.1"
"@sinonjs/commons" "^1.7.0"
"@sinonjs/fake-timers" "^6.0.0"
"@sinonjs/text-encoding" "^0.7.1"
just-extend "^4.0.2"
lolex "^5.0.1"
path-to-regexp "^1.7.0"
node-dir@^0.1.10:
@ -8932,11 +8934,6 @@ safe-regex@^1.1.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
samsam@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50"
integrity sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==
sanitize-filename@^1.6.2, sanitize-filename@^1.6.3:
version "1.6.3"
resolved "https://registry.yarnpkg.com/sanitize-filename/-/sanitize-filename-1.6.3.tgz#755ebd752045931977e30b2025d340d7c9090378"
@ -9187,18 +9184,18 @@ single-line-log@^1.1.2:
dependencies:
string-width "^1.0.1"
sinon@4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/sinon/-/sinon-4.4.2.tgz#c4c41d4bd346e1d33594daec2d5df0548334fc65"
integrity sha512-cpOHpnRyY3Dk9dTHBYMfVBB0HUCSKIpxW07X6OGW2NiYPovs4AkcL8Q8MzecbAROjbfRA9esJCmlZgikxDz7DA==
dependencies:
"@sinonjs/formatio" "^2.0.0"
diff "^3.1.0"
lodash.get "^4.4.2"
lolex "^2.2.0"
nise "^1.2.0"
supports-color "^5.1.0"
type-detect "^4.0.5"
sinon@9.0.2:
version "9.0.2"
resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.0.2.tgz#b9017e24633f4b1c98dfb6e784a5f0509f5fd85d"
integrity sha512-0uF8Q/QHkizNUmbK3LRFqx5cpTttEVXudywY9Uwzy8bTfZUhljZ7ARzSxnRHWYWtVTeh4Cw+tTb3iU21FQVO9A==
dependencies:
"@sinonjs/commons" "^1.7.2"
"@sinonjs/fake-timers" "^6.0.1"
"@sinonjs/formatio" "^5.0.1"
"@sinonjs/samsam" "^5.0.3"
diff "^4.0.2"
nise "^4.0.1"
supports-color "^7.1.0"
slash@^1.0.0:
version "1.0.0"
@ -10103,6 +10100,11 @@ ts-loader@4.1.0:
micromatch "^3.1.4"
semver "^5.0.1"
ts-mock-imports@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/ts-mock-imports/-/ts-mock-imports-1.3.0.tgz#ed9b743349f3c27346afe5b7454ffd2bcaa2302d"
integrity sha512-cCrVcRYsp84eDvPict0ZZD/D7ppQ0/JSx4ve6aEU8DjlsaWRJWV6ADMovp2sCuh6pZcduLFoIYhKTDU2LARo7Q==
tslib@^1.8.0, tslib@^1.8.1:
version "1.11.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35"
@ -10184,7 +10186,7 @@ type-check@~0.3.2:
dependencies:
prelude-ls "~1.1.2"
type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5, type-detect@^4.0.8:
type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==

Loading…
Cancel
Save