Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vault12-integration #6

Merged
merged 12 commits into from
Jul 6, 2021
4 changes: 3 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ export const config = {
// Relay tokens, keys and hashes are 32 bytes
RELAY_TOKEN_LEN: 32,
// 5 min - Token expiration on the server side, matched with config.x.relay.token_timeout on Zax server
RELAY_TOKEN_TIMEOUT: 5 * 60 * 1000,
RELAY_TOKEN_TIMEOUT: 5 * 60 * 1000 * 0.9, //use buffer
// 20 min - Session expiration on the server side, matched with config.x.relay.session_timeout on Zax server
RELAY_SESSION_TIMEOUT: 20 * 60 * 1000 * 0.9, // use buffer
pavlo-liapin marked this conversation as resolved.
Show resolved Hide resolved
// 5 sec - Ajax request timeout
RELAY_AJAX_TIMEOUT: 5 * 1000
};
4 changes: 2 additions & 2 deletions src/crypto-storage/crypto-storage.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { CryptoStorage } from './crypto-storage';
import { LocalStorageDriver } from './local-storage.driver';
import { NaCl } from '../nacl/nacl';

describe('CryptoStorage', () => {
let storage: CryptoStorage;

beforeAll(async () => {
NaCl.setInstance();
storage = await CryptoStorage.new(new LocalStorageDriver(), 'test');
CryptoStorage.setStorageDriver();
dmitry-salnikov marked this conversation as resolved.
Show resolved Hide resolved
storage = await CryptoStorage.new('test');
});

it('ASCII string write/read', async () => {
Expand Down
34 changes: 28 additions & 6 deletions src/crypto-storage/crypto-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,31 @@ import { Utils } from '../utils/utils';
import { config } from '../config';
import { StorageDriver } from './storage-driver.interface';
import { NaClDriver } from '../nacl/nacl-driver.interface';
import { LocalStorageDriver } from './local-storage.driver';

// TODO: add bulk operations to set(), get() and remove() pairs of values simultaneously

/* CryptoStorage is a handy wrapper around any storage that provides JavaScript interface,
that allows to store symmetrically encrypted serializable Javascript objects and primitives. */
export class CryptoStorage {

private static storageDriver: StorageDriver;

private driver?: StorageDriver;
private rootKey?: string;
private id?: string;
private storageKey?: Uint8Array;
private nacl: NaClDriver;

private constructor(storageDriver: StorageDriver, rootKey?: string) {
private constructor(storageDriver: StorageDriver, id: string) {
const nacl = NaCl.getInstance();
this.nacl = nacl;
this.driver = storageDriver;
this.rootKey = rootKey ? `.${rootKey}${config.STORAGE_ROOT}` : config.STORAGE_ROOT;
this.id = id ? `.${id}${config.STORAGE_ROOT}` : config.STORAGE_ROOT;
}

static async new(storageDriver: StorageDriver, rootKey?: string): Promise<CryptoStorage> {
const storage = new CryptoStorage(storageDriver, rootKey);
static async new(id: string): Promise<CryptoStorage> {
const storageDriver = this.getStorageDriver();
const storage = new CryptoStorage(storageDriver, id);
const prefixedStorageKeyTag = storage.addPrefix(config.SKEY_TAG);
const storageKey = await storageDriver.get(prefixedStorageKeyTag);
// Either load or generate a storage key
Expand All @@ -36,6 +41,23 @@ export class CryptoStorage {
return storage;
}

static getStorageDriver() {
pavlo-liapin marked this conversation as resolved.
Show resolved Hide resolved
if (!this.storageDriver) {
throw new Error('[CryptoStorage] StorageDriver instance is not yet set');
} else {
return this.storageDriver;
}
}

static setStorageDriver(driver: StorageDriver = new LocalStorageDriver()) {
pavlo-liapin marked this conversation as resolved.
Show resolved Hide resolved
if (this.storageDriver) {
dmitry-salnikov marked this conversation as resolved.
Show resolved Hide resolved
throw new Error('[NaCl] NaCl driver has been already set, it is supposed to be set only once');
} else {
this.storageDriver = driver;
}
return true;
}

async save(tag: string, data: unknown): Promise<boolean> {
if (!this.driver) {
throw new Error('[CryptoStorage] Storage driver is not set');
Expand Down Expand Up @@ -98,7 +120,7 @@ export class CryptoStorage {

// Keys are tagged in the storage with a versioned prefix
private addPrefix(key: string): string {
return this.rootKey ? (key + this.rootKey) : key;
return this.id ? (key + this.id) : key;
}

private addNonceTag(tag: string): string {
Expand Down
6 changes: 5 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { LocalStorageDriver } from './crypto-storage/local-storage.driver.js';
import { Mailbox } from './mailbox/mailbox';
import { Relay } from './relay/relay';
import { ZaxMessageKind, ZaxTextMessage, ZaxFileMessage, ZaxPlainMessage, ZaxParsedMessage } from './zax.interface';
import { JsNaClDriver } from './nacl/js-nacl-driver';
import { Utils } from './utils/utils';

export {
NaCl,
Expand All @@ -21,5 +23,7 @@ export {
ZaxTextMessage,
ZaxFileMessage,
ZaxPlainMessage,
ZaxParsedMessage
ZaxParsedMessage,
JsNaClDriver,
Utils
};
2 changes: 2 additions & 0 deletions src/keyring/keyring.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import { NaCl } from '../nacl/nacl';
import { NaClDriver } from '../nacl/nacl-driver.interface';
import { Keys } from '../keys/keys';
import { Utils } from '../utils/utils';
import { CryptoStorage } from '../crypto-storage/crypto-storage';

describe('Keyring', () => {
let nacl: NaClDriver;

beforeAll(() => {
NaCl.setInstance();
CryptoStorage.setStorageDriver();
nacl = NaCl.getInstance();
});

Expand Down
10 changes: 4 additions & 6 deletions src/keyring/keyring.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { CryptoStorage } from '../crypto-storage/crypto-storage';
import { StorageDriver } from '../crypto-storage/storage-driver.interface';
import { LocalStorageDriver } from '../crypto-storage/local-storage.driver';
import { Keys } from '../keys/keys';
import { NaCl } from '../nacl/nacl';
import { Utils, Base64 } from '../utils/utils';
Expand Down Expand Up @@ -34,9 +32,9 @@ export class KeyRing {
this.commKey = commKey;
}

static async new(id: string, storageDriver?: StorageDriver): Promise<KeyRing> {
static async new(id: string): Promise<KeyRing> {
const nacl = NaCl.getInstance();
const cryptoStorage = await CryptoStorage.new(storageDriver || new LocalStorageDriver(), id);
const cryptoStorage = await CryptoStorage.new(id);
const commKey = await KeyRing.getCommKey(nacl, cryptoStorage);
const keyRing = new KeyRing(nacl, cryptoStorage, commKey);

Expand All @@ -45,10 +43,10 @@ export class KeyRing {
return keyRing;
}

static async fromBackup(id: string, backupString: string, storageDriver?: StorageDriver): Promise<KeyRing> {
static async fromBackup(id: string, backupString: string): Promise<KeyRing> {
const backup: KeyRingBackup = JSON.parse(backupString);
const secretKey = Utils.fromBase64(backup[commKeyTag]);
const restoredKeyRing = await KeyRing.new(id, storageDriver);
const restoredKeyRing = await KeyRing.new(id);
await restoredKeyRing.setCommFromSecKey(secretKey);
for (const [key, value] of Object.entries(backup)) {
if (key !== commKeyTag) {
Expand Down
5 changes: 2 additions & 3 deletions src/mailbox/mailbox.files.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { NaCl } from '../nacl/nacl';
import { Mailbox } from './mailbox';
import { randomNumber, testRelayURL } from '../tests.helper';
import { FileUploadMetadata } from '../zax.interface';
import { CryptoStorage } from '../crypto-storage/crypto-storage';

describe('Mailbox / File transfer', () => {

Expand All @@ -19,6 +20,7 @@ describe('Mailbox / File transfer', () => {

beforeAll(async () => {
NaCl.setInstance();
CryptoStorage.setStorageDriver();

Alice = await Mailbox.new('Alice');
Bob = await Mailbox.new('Bob');
Expand All @@ -27,8 +29,6 @@ describe('Mailbox / File transfer', () => {
await Alice.keyRing.addGuest('Bob', bobKey);
await Bob.keyRing.addGuest('Alice', aliceKey);

await Alice.connectToRelay(testRelayURL);

// Generate a random binary file
file = new Uint8Array(randomNumber(500, 1000)).map(() => randomNumber(0, 255));

Expand Down Expand Up @@ -74,7 +74,6 @@ describe('Mailbox / File transfer', () => {
expect(statusAlice.total_chunks).toBe(numberOfChunks);
expect(statusAlice.bytes_stored).toBeGreaterThan(file.length);

await Bob.connectToRelay(testRelayURL);
const statusBob = await Bob.getFileStatus(testRelayURL, uploadID);
expect(statusBob.status).toBe('COMPLETE');
expect(statusAlice.file_size).toBe(file.length);
Expand Down
16 changes: 6 additions & 10 deletions src/mailbox/mailbox.messages.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@ import { NaCl } from '../nacl/nacl';
import { Mailbox } from './mailbox';
import { testRelayURL } from '../tests.helper';
import { MessageStatusResponse } from '../zax.interface';
import { CryptoStorage } from '../crypto-storage/crypto-storage';
import { config } from '../config';
import { Relay } from '../relay/relay';

describe('Mailbox / Messages', () => {
let Alice: Mailbox;
let Bob: Mailbox;
let nonce: string;
let token: string;

beforeAll(async () => {
beforeAll(() => {
NaCl.setInstance();
CryptoStorage.setStorageDriver();
});

beforeEach(async () => {
Alice = await Mailbox.new('Alice');
Bob = await Mailbox.new('Bob');

Expand All @@ -22,7 +25,6 @@ describe('Mailbox / Messages', () => {
});

it('send a message', async () => {
await Alice.connectToRelay(testRelayURL);
const wrongRecipient = Alice.upload(testRelayURL, 'Carl', 'some message');
expect(wrongRecipient).rejects.toThrow(Error);

Expand All @@ -33,7 +35,6 @@ describe('Mailbox / Messages', () => {
});

it('count Bob mailbox', async () => {
await Bob.connectToRelay(testRelayURL);
const count = await Bob.count(testRelayURL);
expect(count).toBe(1);
});
Expand All @@ -56,15 +57,13 @@ describe('Mailbox / Messages', () => {
});

it('check deleted message status', async () => {
await Alice.connectToRelay(testRelayURL);
const ttl = await Alice.messageStatus(testRelayURL, token);
expect(ttl).toBe(MessageStatusResponse.MissingKey); // the key is missing on the relay
});

it('send unencrypted message', async () => {
const token = await Alice.upload(testRelayURL, 'Bob', 'some unencrypted message', false);
expect(token.length).toBeGreaterThan(0);
await Bob.connectToRelay(testRelayURL);
const count = await Bob.count(testRelayURL);
expect(count).toBe(1);
const [ message ] = await Bob.download(testRelayURL);
Expand All @@ -77,18 +76,15 @@ describe('Mailbox / Messages', () => {
// TODO: fix this
jest.useFakeTimers('legacy');
await Bob.connectToRelay(testRelayURL);
const relay = await Relay.getInstance(testRelayURL);
const connectSpy = jest.spyOn(Bob, 'connectToRelay');

// fast-forward to where the token is not yet expired
jest.advanceTimersByTime(config.RELAY_TOKEN_TIMEOUT - 1);
expect(relay.isTokenExpired).toBe(false);
jest.advanceTimersByTime(config.RELAY_SESSION_TIMEOUT - 1);
await Bob.download(testRelayURL);
expect(connectSpy).not.toHaveBeenCalled();

// has to reconnect under the hood when a token expires
jest.advanceTimersByTime(2);
expect(relay.isTokenExpired).toBe(true);
await Bob.download(testRelayURL);
expect(connectSpy).toHaveBeenCalledTimes(1);

Expand Down
2 changes: 2 additions & 0 deletions src/mailbox/mailbox.offline.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { Mailbox } from './mailbox';
import { NaCl } from '../nacl/nacl';
import { Utils } from '../utils/utils';
import { CryptoStorage } from '../crypto-storage/crypto-storage';

describe('Mailbox / Offline tests', () => {
let Alice: Mailbox;
let Bob: Mailbox;

beforeAll(async () => {
NaCl.setInstance();
CryptoStorage.setStorageDriver();
Alice = await Mailbox.new('Alice');
Bob = await Mailbox.new('Bob');
});
Expand Down
82 changes: 82 additions & 0 deletions src/mailbox/mailbox.simultaneously.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { NaCl } from '../nacl/nacl';
import { Mailbox } from './mailbox';
import { testRelayURL, testRelayURL2 } from '../tests.helper';
import { CryptoStorage } from '../crypto-storage/crypto-storage';

describe('Mailbox simultaneously', () => {
let Alice: Mailbox;
let Bob: Mailbox;
const msg1 = 'hi';
const msg2 = 'hi there';

beforeAll(async () => {
NaCl.setInstance();
CryptoStorage.setStorageDriver();

});

describe('using different mailboxes simultaneously', () => {

beforeAll(async () => {
await createMailboxes();
});

it('send messages from different mailboxes', async() => {
await Promise.all([
Alice.upload(testRelayURL, 'Bob', msg1),
Alice.upload(testRelayURL, 'Bob', msg2),
Bob.upload(testRelayURL, 'Alice', msg2)
]);
});

it('receive messages', async() => {
const [aliceMsgs, bobMsgs] = await Promise.all([
Alice.download(testRelayURL),
Bob.download(testRelayURL)
]);
expect(aliceMsgs[0].data).toBe(msg2);
expect(bobMsgs.map(m => m.data)).toEqual([msg1, msg2]);
await Promise.all([
Alice.delete(testRelayURL, aliceMsgs.map(m => m.nonce)),
Bob.delete(testRelayURL, bobMsgs.map(m => m.nonce))
]);
});
});

describe('using different relays simultaneously', () => {

beforeAll(async () => {
await createMailboxes();
});

it('send messages', async () => {
await Promise.all([
Alice.upload(testRelayURL, 'Bob', msg1),
Alice.upload(testRelayURL2, 'Bob', msg1),
Alice.upload(testRelayURL2, 'Bob', msg2),
]);
});

it('receive messages', async () => {
const [msgsRelay1, msgsRelay2] = await Promise.all([
Bob.download(testRelayURL),
Bob.download(testRelayURL2),
]);
expect(msgsRelay1.map(m => m.data)).toEqual([msg1]);
expect(msgsRelay2.map(m => m.data)).toEqual([msg1, msg2]);
Bob.delete(testRelayURL, msgsRelay1.map(m => m.nonce));
Bob.delete(testRelayURL2, msgsRelay2.map(m => m.nonce));
});
});


async function createMailboxes() {

Alice = await Mailbox.new('Alice');
Bob = await Mailbox.new('Bob');

await Alice.keyRing.addGuest('Bob', Bob.keyRing.getPubCommKey());
await Bob.keyRing.addGuest('Alice', Alice.keyRing.getPubCommKey());
}

});
Loading