Skip to content

Commit

Permalink
Merge pull request #6 from vault12/vault12-integration
Browse files Browse the repository at this point in the history
vault12-integration
  • Loading branch information
lomchik authored Jul 6, 2021
2 parents 2610efd + 9d5bf78 commit ef2f313
Show file tree
Hide file tree
Showing 18 changed files with 331 additions and 121 deletions.
6 changes: 5 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
const sessionTimeoutBuffer = 30 * 1000;

export const config = {
NONCE_TAG: '__nc',
SKEY_TAG: 'storage_key',
STORAGE_ROOT: '.v2.stor.vlt12',
// 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 - sessionTimeoutBuffer,
// 20 min - Session expiration on the server side, matched with config.x.relay.session_timeout on Zax server
RELAY_SESSION_TIMEOUT: 20 * 60 * 1000 - sessionTimeoutBuffer,
// 5 sec - Ajax request timeout
RELAY_AJAX_TIMEOUT: 5 * 1000
};
6 changes: 3 additions & 3 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');
NaCl.setDefaultInstance();
CryptoStorage.setDefaultStorageDriver();
storage = await CryptoStorage.new('test');
});

it('ASCII string write/read', async () => {
Expand Down
38 changes: 32 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,27 @@ export class CryptoStorage {
return storage;
}

static getStorageDriver() {
if (!this.storageDriver) {
throw new Error('[CryptoStorage] StorageDriver instance is not yet set');
} else {
return this.storageDriver;
}
}

static setDefaultStorageDriver() {
return this.setStorageDriver(new LocalStorageDriver());
}

static setStorageDriver(driver: StorageDriver) {
if (this.storageDriver) {
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 +124,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
};
4 changes: 3 additions & 1 deletion 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();
NaCl.setDefaultInstance();
CryptoStorage.setDefaultStorageDriver();
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
7 changes: 3 additions & 4 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 @@ -18,7 +19,8 @@ describe('Mailbox / File transfer', () => {
let metadata: FileUploadMetadata;

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

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
18 changes: 7 additions & 11 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 () => {
NaCl.setInstance();
beforeAll(() => {
NaCl.setDefaultInstance();
CryptoStorage.setDefaultStorageDriver();
});

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
4 changes: 3 additions & 1 deletion 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();
NaCl.setDefaultInstance();
CryptoStorage.setDefaultStorageDriver();
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.setDefaultInstance();
CryptoStorage.setDefaultStorageDriver();

});

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.map(m => m.data)).toEqual([msg2]);
expect(bobMsgs.map(m => m.data).sort()).toEqual([msg1, msg2].sort());
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).sort()).toEqual([msg1, msg2].sort());
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

0 comments on commit ef2f313

Please sign in to comment.