@wireapp/cryptobox
Version:
High-level API with persistent storage for Proteus.
240 lines (204 loc) • 8.79 kB
JavaScript
/*
* Wire
* Copyright (C) 2017 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
*/
/* eslint no-magic-numbers: "off" */
const cryptobox = require('@wireapp/cryptobox');
const fs = require('fs-extra');
const LRUCache = require('@wireapp/lru-cache').default;
const path = require('path');
const Proteus = require('@wireapp/proteus');
const {StoreEngine} = require('@wireapp/store-engine');
describe('cryptobox.store.CryptoboxCRUDStore', () => {
let storagePath = '';
let engine = undefined;
let fileStore = undefined;
beforeEach(async done => {
storagePath = fs.mkdtempSync(path.normalize(`${__dirname}/test`));
engine = new StoreEngine.FileEngine();
await engine.init(storagePath, {fileExtension: '.json'});
fileStore = new cryptobox.store.CryptoboxCRUDStore(engine);
done();
});
afterEach(done =>
fs
.remove(storagePath)
.then(done)
.catch(done.fail)
);
describe('"delete_all"', () => {
it('deletes everything from the storage', async done => {
try {
const alicePreKeys = await Proteus.keys.PreKey.generate_prekeys(0, 10);
const aliceIdentity = await Proteus.keys.IdentityKeyPair.new();
const bobIdentity = await Proteus.keys.IdentityKeyPair.new();
const bobLastResortPreKey = await Proteus.keys.PreKey.new(Proteus.keys.PreKey.MAX_PREKEY_ID);
const bobPreKeyBundle = Proteus.keys.PreKeyBundle.new(bobIdentity.public_key, bobLastResortPreKey);
const sessionId = 'my_session_with_bob';
const sessionWithBob = await Proteus.session.Session.init_from_prekey(aliceIdentity, bobPreKeyBundle);
await Promise.all([
fileStore.save_identity(aliceIdentity),
fileStore.save_prekeys(alicePreKeys),
fileStore.create_session(sessionId, sessionWithBob),
]);
const hasBeenDeleted = await fileStore.delete_all();
expect(hasBeenDeleted).toBe(true);
done();
} catch (error) {
done.fail(error);
}
});
});
describe('"delete_prekey"', () => {
it('deletes a PreKey', async done => {
try {
const preKeyId = 0;
const preKey = await Proteus.keys.PreKey.new(preKeyId);
const savedPreKey = await fileStore.save_prekey(preKey);
expect(savedPreKey.key_id).toBe(preKeyId);
await fileStore.delete_prekey(preKeyId);
done();
} catch (error) {
done.fail(error);
}
});
});
describe('"load_prekey"', () => {
it('saves and loads a single PreKey', async done => {
try {
const preKeyId = 0;
const preKey = await Proteus.keys.PreKey.new(preKeyId);
const savedPreKey = await fileStore.save_prekey(preKey);
expect(savedPreKey.key_id).toBe(preKeyId);
const loadedPreKey = await fileStore.load_prekey(preKeyId);
expect(loadedPreKey.key_id).toBe(preKeyId);
done();
} catch (error) {
done.fail(error);
}
});
});
describe('"load_prekeys"', () => {
it('loads multiple PreKeys', async done => {
try {
await Promise.all([
fileStore.save_prekey(await Proteus.keys.PreKey.new(1)),
fileStore.save_prekey(await Proteus.keys.PreKey.new(2)),
fileStore.save_prekey(await Proteus.keys.PreKey.new(3)),
]);
const preKeys = await fileStore.load_prekeys();
expect(preKeys).toBeDefined();
done();
} catch (error) {
done.fail(error);
}
});
});
describe('"save_identity"', () => {
it('saves the local identity', async done => {
const ikp = await Proteus.keys.IdentityKeyPair.new();
fileStore
.save_identity(ikp)
.then(identity => {
expect(identity.public_key.fingerprint()).toEqual(ikp.public_key.fingerprint());
done();
})
.catch(done.fail);
});
});
describe('"save_prekeys"', () => {
it('saves multiple PreKeys', async done => {
try {
const preKeys = await Promise.all([
Proteus.keys.PreKey.new(0),
Proteus.keys.PreKey.new(Proteus.keys.PreKey.MAX_PREKEY_ID),
]);
savedPreKeys = await fileStore.save_prekeys(preKeys);
expect(savedPreKeys.length).toBe(preKeys.length);
done();
} catch (error) {
done.fail(error);
}
});
});
describe('"update_session"', () => {
it('updates an already persisted session', async done => {
try {
const aliceIdentity = await Proteus.keys.IdentityKeyPair.new();
const bobIdentity = await Proteus.keys.IdentityKeyPair.new();
const bobLastResortPreKey = await Proteus.keys.PreKey.new(Proteus.keys.PreKey.MAX_PREKEY_ID);
const bobPreKeyBundle = Proteus.keys.PreKeyBundle.new(bobIdentity.public_key, bobLastResortPreKey);
const sessionId = 'my_session_with_bob';
let proteusSession = await Proteus.session.Session.init_from_prekey(aliceIdentity, bobPreKeyBundle);
proteusSession = await fileStore.create_session(sessionId, proteusSession);
expect(proteusSession.local_identity.public_key.fingerprint()).toBe(aliceIdentity.public_key.fingerprint());
expect(proteusSession.remote_identity.public_key.fingerprint()).toBe(bobIdentity.public_key.fingerprint());
expect(proteusSession.version).toBe(1);
proteusSession.version = 2;
await fileStore.update_session(sessionId, proteusSession);
proteusSession = await fileStore.read_session(aliceIdentity, sessionId);
expect(proteusSession.local_identity.public_key.fingerprint()).toBe(aliceIdentity.public_key.fingerprint());
expect(proteusSession.remote_identity.public_key.fingerprint()).toBe(bobIdentity.public_key.fingerprint());
expect(proteusSession.version).toBe(2);
done();
} catch (error) {
done.fail(error);
}
});
});
describe('session_from_prekey', () => {
it('saves and caches a valid session from a serialized PreKey bundle', async done => {
try {
const alice = new cryptobox.Cryptobox(fileStore, 1);
const sessionId = 'session_with_bob';
const bob = await Proteus.keys.IdentityKeyPair.new();
const preKey = await Proteus.keys.PreKey.new(Proteus.keys.PreKey.MAX_PREKEY_ID);
const bobPreKeyBundle = Proteus.keys.PreKeyBundle.new(bob.public_key, preKey);
const allPreKeys = await alice.create();
expect(allPreKeys.length).toBe(1);
let cryptoboxSession = await alice.session_from_prekey(sessionId, bobPreKeyBundle.serialise());
expect(cryptoboxSession.fingerprint_remote()).toBe(bob.public_key.fingerprint());
cryptoboxSession = alice.load_session_from_cache(sessionId);
expect(cryptoboxSession.fingerprint_remote()).toBe(bob.public_key.fingerprint());
cryptoboxSession = await alice.session_from_prekey(sessionId, bobPreKeyBundle.serialise());
expect(cryptoboxSession.fingerprint_remote()).toBe(bob.public_key.fingerprint());
done();
} catch (error) {
done.fail(error);
}
});
it('reinforces a session from the store without cache', async done => {
try {
const alice = new cryptobox.Cryptobox(fileStore, 1);
const sessionId = 'session_with_bob';
const bob = await Proteus.keys.IdentityKeyPair.new();
const preKey = await Proteus.keys.PreKey.new(Proteus.keys.PreKey.MAX_PREKEY_ID);
const bobPreKeyBundle = Proteus.keys.PreKeyBundle.new(bob.public_key, preKey);
const allPreKeys = await alice.create();
expect(allPreKeys.length).toBe(1);
let cryptoboxSession = await alice.session_from_prekey(sessionId, bobPreKeyBundle.serialise());
expect(cryptoboxSession.fingerprint_remote()).toBe(bob.public_key.fingerprint());
alice.cachedSessions = new LRUCache(1);
cryptoboxSession = await alice.session_from_prekey(sessionId, bobPreKeyBundle.serialise());
expect(cryptoboxSession.fingerprint_remote()).toBe(bob.public_key.fingerprint());
done();
} catch (error) {
done.fail(error);
}
});
});
});