diff --git a/libloki/libloki-protocol.js b/libloki/libloki-protocol.js
deleted file mode 100644
index 252e423e4..000000000
--- a/libloki/libloki-protocol.js
+++ /dev/null
@@ -1,167 +0,0 @@
-/* global window, libsignal, textsecure, StringView, log */
-
-// eslint-disable-next-line func-names
-(function () {
- window.libloki = window.libloki || {};
-
- class FallBackDecryptionError extends Error { }
-
- const IV_LENGTH = 16;
-
- class FallBackSessionCipher {
-
- constructor(address) {
- this.identityKeyString = address.getName();
- this.pubKey = StringView.hexToArrayBuffer(address.getName());
- }
-
- async encrypt(plaintext) {
- const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair();
- const myPrivateKey = myKeyPair.privKey;
- const symmetricKey = libsignal.Curve.calculateAgreement(this.pubKey, myPrivateKey);
- const iv = libsignal.crypto.getRandomBytes(IV_LENGTH);
- const ciphertext = await libsignal.crypto.encrypt(symmetricKey, plaintext, iv);
- const ivAndCiphertext = new Uint8Array(iv.byteLength + ciphertext.byteLength);
- ivAndCiphertext.set(new Uint8Array(iv));
- ivAndCiphertext.set(new Uint8Array(ciphertext), iv.byteLength);
- return {
- type: textsecure.protobuf.Envelope.Type.FRIEND_REQUEST,
- body: ivAndCiphertext,
- registrationId: null,
- };
- }
-
- async decrypt(ivAndCiphertext) {
- const iv = ivAndCiphertext.slice(0, IV_LENGTH);
- const cipherText = ivAndCiphertext.slice(IV_LENGTH);
- const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair();
- const myPrivateKey = myKeyPair.privKey;
- const symmetricKey = libsignal.Curve.calculateAgreement(this.pubKey, myPrivateKey);
- try {
- return await libsignal.crypto.decrypt(symmetricKey, cipherText, iv);
- }
- catch (e) {
- throw new FallBackDecryptionError(`Could not decrypt message from ${this.identityKeyString} using FallBack encryption.`);
- }
- }
- }
-
- async function getPreKeyBundleForContact(pubKey) {
- const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair();
- const identityKey = myKeyPair.pubKey;
-
- // Retrieve ids. The ids stored are always the latest generated + 1
- const signedKeyId = textsecure.storage.get('signedKeyId', 2) - 1;
-
- const [signedKey, preKey] = await Promise.all([
- textsecure.storage.protocol.loadSignedPreKey(signedKeyId),
- new Promise(async resolve => {
- // retrieve existing prekey if we already generated one for that recipient
- const storedPreKey = await textsecure.storage.protocol.loadPreKeyForContact(
- pubKey
- );
- if (storedPreKey) {
- resolve({ pubKey: storedPreKey.pubKey, keyId: storedPreKey.keyId });
- } else {
- // generate and store new prekey
- const preKeyId = textsecure.storage.get('maxPreKeyId', 1);
- textsecure.storage.put('maxPreKeyId', preKeyId + 1);
- const newPreKey = await libsignal.KeyHelper.generatePreKey(preKeyId);
- await textsecure.storage.protocol.storePreKey(
- newPreKey.keyId,
- newPreKey.keyPair,
- pubKey
- );
- resolve({ pubKey: newPreKey.keyPair.pubKey, keyId: preKeyId });
- }
- }),
- ]);
-
- return {
- identityKey: new Uint8Array(identityKey),
- deviceId: 1, // TODO: fetch from somewhere
- preKeyId: preKey.keyId,
- signedKeyId,
- preKey: new Uint8Array(preKey.pubKey),
- signedKey: new Uint8Array(signedKey.pubKey),
- signature: new Uint8Array(signedKey.signature),
- };
- }
-
- async function saveContactPreKeyBundle({
- pubKey,
- preKeyId,
- preKey,
- signedKeyId,
- signedKey,
- signature,
- }) {
- const signedPreKey = {
- keyId: signedKeyId,
- publicKey: signedKey,
- signature,
- };
-
- const signedKeyPromise = textsecure.storage.protocol.storeContactSignedPreKey(
- pubKey,
- signedPreKey
- );
-
- const preKeyObject = {
- publicKey: preKey,
- keyId: preKeyId,
- };
-
- const preKeyPromise = textsecure.storage.protocol.storeContactPreKey(
- pubKey,
- preKeyObject
- );
-
- await Promise.all([signedKeyPromise, preKeyPromise]);
- }
-
- async function removeContactPreKeyBundle(pubKey) {
- await Promise.all([
- textsecure.storage.protocol.removeContactPreKey(pubKey),
- textsecure.storage.protocol.removeContactSignedPreKey(pubKey),
- ]);
- }
-
- async function sendFriendRequestAccepted(pubKey) {
- return sendEmptyMessage(pubKey);
- }
-
- async function sendEmptyMessage(pubKey) {
- // empty content message
- const content = new textsecure.protobuf.Content();
-
- // will be called once the transmission succeeded or failed
- const callback = res => {
- if (res.errors.length > 0) {
- res.errors.forEach(error => log.error(error));
- } else {
- log.info('empty message sent successfully');
- }
- };
- const options = {};
- // send an empty message. The logic in ougoing_message will attach the prekeys.
- const outgoingMessage = new textsecure.OutgoingMessage(
- null, // server
- Date.now(), // timestamp,
- [pubKey], // numbers
- content, // message
- true, // silent
- callback, // callback
- options
- );
- await outgoingMessage.sendToNumber(pubKey);
- }
-
- window.libloki.FallBackSessionCipher = FallBackSessionCipher;
- window.libloki.getPreKeyBundleForContact = getPreKeyBundleForContact;
- window.libloki.FallBackDecryptionError = FallBackDecryptionError;
- window.libloki.saveContactPreKeyBundle = saveContactPreKeyBundle;
- window.libloki.removeContactPreKeyBundle = removeContactPreKeyBundle;
- window.libloki.sendFriendRequestAccepted = sendFriendRequestAccepted;
- window.libloki.sendEmptyMessage = sendEmptyMessage;
-})();
diff --git a/libloki/test/crypto_test.js b/libloki/test/crypto_test.js
new file mode 100644
index 000000000..15dfa02f0
--- /dev/null
+++ b/libloki/test/crypto_test.js
@@ -0,0 +1,38 @@
+/* global libsignal, libloki, textsecure, StringView */
+
+'use strict';
+
+describe('Crypto', () => {
+ describe('FallBackSessionCipher', () => {
+ let fallbackCipher;
+ let identityKey;
+ let address;
+ const store = textsecure.storage.protocol;
+
+ before(async () => {
+ clearDatabase();
+ identityKey = await libsignal.KeyHelper.generateIdentityKeyPair();
+ store.put('identityKey', identityKey);
+ const key = libsignal.crypto.getRandomBytes(32);
+ const pubKeyString = StringView.arrayBufferToHex(key);
+ address = new libsignal.SignalProtocolAddress(
+ pubKeyString,
+ 1
+ );
+ fallbackCipher = new libloki.crypto.FallBackSessionCipher(address);
+ });
+
+ it('should encrypt fallback cipher messages as friend requests', async () => {
+ const buffer = new ArrayBuffer(10);
+ const { type } = await fallbackCipher.encrypt(buffer);
+ assert.strictEqual(type, textsecure.protobuf.Envelope.Type.FRIEND_REQUEST);
+ });
+
+ it('should encrypt and then decrypt a message with the same result', async () => {
+ const arr = new Uint8Array([1,2,3,4,5]);
+ const { body } = await fallbackCipher.encrypt(arr.buffer);
+ const result = await fallbackCipher.decrypt(body);
+ assert.deepEqual(result, arr.buffer);
+ });
+ });
+});
diff --git a/libloki/test/index.html b/libloki/test/index.html
index e690b0bb8..f9eb9c2e4 100644
--- a/libloki/test/index.html
+++ b/libloki/test/index.html
@@ -29,9 +29,10 @@
+
-
+
diff --git a/libloki/test/libloki-protocol_test.js b/libloki/test/libloki-protocol_test.js
deleted file mode 100644
index 53c9d923a..000000000
--- a/libloki/test/libloki-protocol_test.js
+++ /dev/null
@@ -1,103 +0,0 @@
-/* global libsignal, libloki, textsecure, StringView */
-
-'use strict';
-
-describe('FallBackSessionCipher', () => {
- let fallbackCipher;
- let identityKey;
- let address;
- const store = textsecure.storage.protocol;
-
- before(async () => {
- clearDatabase();
- identityKey = await libsignal.KeyHelper.generateIdentityKeyPair();
- store.put('identityKey', identityKey);
- const key = libsignal.crypto.getRandomBytes(32);
- const pubKeyString = StringView.arrayBufferToHex(key);
- address = new libsignal.SignalProtocolAddress(
- pubKeyString,
- 1
- );
- fallbackCipher = new libloki.crypto.FallBackSessionCipher(address);
- });
-
- it('should encrypt fallback cipher messages as friend requests', async () => {
- const buffer = new ArrayBuffer(10);
- const { type } = await fallbackCipher.encrypt(buffer);
- assert.strictEqual(type, textsecure.protobuf.Envelope.Type.FRIEND_REQUEST);
- });
-
- it('should encrypt and then decrypt a message with the same result', async () => {
- const arr = new Uint8Array([1,2,3,4,5]);
- const { body } = await fallbackCipher.encrypt(arr.buffer);
- const result = await fallbackCipher.decrypt(body);
- assert.deepEqual(result, arr.buffer);
- });
-});
-
-describe('LibLoki Protocol', () => {
- let testKey;
- const store = textsecure.storage.protocol;
-
- beforeEach(async () => {
- clearDatabase();
- testKey = {
- pubKey: libsignal.crypto.getRandomBytes(33),
- privKey: libsignal.crypto.getRandomBytes(32),
- };
- textsecure.storage.put('signedKeyId', 2);
- await store.storeSignedPreKey(1, testKey);
- });
-
- it('should generate a new prekey bundle for a new contact', async () => {
- const pubKey = libsignal.crypto.getRandomBytes(32);
- const pubKeyString = StringView.arrayBufferToHex(pubKey);
- const preKeyIdBefore = textsecure.storage.get('maxPreKeyId', 1);
- const newBundle = await libloki.storage.getPreKeyBundleForContact(pubKeyString);
- const preKeyIdAfter = textsecure.storage.get('maxPreKeyId', 1);
- assert.strictEqual(preKeyIdAfter, preKeyIdBefore + 1);
-
- const testKeyArray = new Uint8Array(testKey.pubKey);
- assert.isDefined(newBundle);
- assert.isDefined(newBundle.identityKey);
- assert.isDefined(newBundle.deviceId);
- assert.isDefined(newBundle.preKeyId);
- assert.isDefined(newBundle.signedKeyId);
- assert.isDefined(newBundle.preKey);
- assert.isDefined(newBundle.signedKey);
- assert.isDefined(newBundle.signature);
- assert.strictEqual(testKeyArray.byteLength, newBundle.signedKey.byteLength);
- for (let i = 0 ; i !== testKeyArray.byteLength; i += 1)
- assert.strictEqual(testKeyArray[i], newBundle.signedKey[i]);
- });
-
- it('should return the same prekey bundle after creating a contact', async () => {
- const pubKey = libsignal.crypto.getRandomBytes(32);
- const pubKeyString = StringView.arrayBufferToHex(pubKey);
- const bundle1 = await libloki.storage.getPreKeyBundleForContact(pubKeyString);
- const bundle2 = await libloki.storage.getPreKeyBundleForContact(pubKeyString);
- assert.isDefined(bundle1);
- assert.isDefined(bundle2);
- assert.deepEqual(bundle1, bundle2);
- });
-
- it('should save the signed keys and prekeys from a bundle', async () => {
- const pubKey = libsignal.crypto.getRandomBytes(32);
- const pubKeyString = StringView.arrayBufferToHex(pubKey);
- const preKeyIdBefore = textsecure.storage.get('maxPreKeyId', 1);
- const newBundle = await libloki.storage.getPreKeyBundleForContact(pubKeyString);
- const preKeyIdAfter = textsecure.storage.get('maxPreKeyId', 1);
- assert.strictEqual(preKeyIdAfter, preKeyIdBefore + 1);
-
- const testKeyArray = new Uint8Array(testKey.pubKey);
- assert.isDefined(newBundle);
- assert.isDefined(newBundle.identityKey);
- assert.isDefined(newBundle.deviceId);
- assert.isDefined(newBundle.preKeyId);
- assert.isDefined(newBundle.signedKeyId);
- assert.isDefined(newBundle.preKey);
- assert.isDefined(newBundle.signedKey);
- assert.isDefined(newBundle.signature);
- assert.deepEqual(testKeyArray, newBundle.signedKey);
- });
-});
diff --git a/libloki/test/storage_test.js b/libloki/test/storage_test.js
new file mode 100644
index 000000000..dc883f06b
--- /dev/null
+++ b/libloki/test/storage_test.js
@@ -0,0 +1,72 @@
+/* global libsignal, libloki, textsecure, StringView */
+
+'use strict';
+
+describe('Storage', () => {
+ let testKey;
+ const store = textsecure.storage.protocol;
+
+ describe('#getPreKeyBundleForContact', () => {
+ beforeEach(async () => {
+ clearDatabase();
+ testKey = {
+ pubKey: libsignal.crypto.getRandomBytes(33),
+ privKey: libsignal.crypto.getRandomBytes(32),
+ };
+ textsecure.storage.put('signedKeyId', 2);
+ await store.storeSignedPreKey(1, testKey);
+ });
+
+ it('should generate a new prekey bundle for a new contact', async () => {
+ const pubKey = libsignal.crypto.getRandomBytes(32);
+ const pubKeyString = StringView.arrayBufferToHex(pubKey);
+ const preKeyIdBefore = textsecure.storage.get('maxPreKeyId', 1);
+ const newBundle = await libloki.storage.getPreKeyBundleForContact(pubKeyString);
+ const preKeyIdAfter = textsecure.storage.get('maxPreKeyId', 1);
+ assert.strictEqual(preKeyIdAfter, preKeyIdBefore + 1);
+
+ const testKeyArray = new Uint8Array(testKey.pubKey);
+ assert.isDefined(newBundle);
+ assert.isDefined(newBundle.identityKey);
+ assert.isDefined(newBundle.deviceId);
+ assert.isDefined(newBundle.preKeyId);
+ assert.isDefined(newBundle.signedKeyId);
+ assert.isDefined(newBundle.preKey);
+ assert.isDefined(newBundle.signedKey);
+ assert.isDefined(newBundle.signature);
+ assert.strictEqual(testKeyArray.byteLength, newBundle.signedKey.byteLength);
+ for (let i = 0 ; i !== testKeyArray.byteLength; i += 1)
+ assert.strictEqual(testKeyArray[i], newBundle.signedKey[i]);
+ });
+
+ it('should return the same prekey bundle after creating a contact', async () => {
+ const pubKey = libsignal.crypto.getRandomBytes(32);
+ const pubKeyString = StringView.arrayBufferToHex(pubKey);
+ const bundle1 = await libloki.storage.getPreKeyBundleForContact(pubKeyString);
+ const bundle2 = await libloki.storage.getPreKeyBundleForContact(pubKeyString);
+ assert.isDefined(bundle1);
+ assert.isDefined(bundle2);
+ assert.deepEqual(bundle1, bundle2);
+ });
+
+ it('should save the signed keys and prekeys from a bundle', async () => {
+ const pubKey = libsignal.crypto.getRandomBytes(32);
+ const pubKeyString = StringView.arrayBufferToHex(pubKey);
+ const preKeyIdBefore = textsecure.storage.get('maxPreKeyId', 1);
+ const newBundle = await libloki.storage.getPreKeyBundleForContact(pubKeyString);
+ const preKeyIdAfter = textsecure.storage.get('maxPreKeyId', 1);
+ assert.strictEqual(preKeyIdAfter, preKeyIdBefore + 1);
+
+ const testKeyArray = new Uint8Array(testKey.pubKey);
+ assert.isDefined(newBundle);
+ assert.isDefined(newBundle.identityKey);
+ assert.isDefined(newBundle.deviceId);
+ assert.isDefined(newBundle.preKeyId);
+ assert.isDefined(newBundle.signedKeyId);
+ assert.isDefined(newBundle.preKey);
+ assert.isDefined(newBundle.signedKey);
+ assert.isDefined(newBundle.signature);
+ assert.deepEqual(testKeyArray, newBundle.signedKey);
+ });
+ });
+});