diff --git a/Gruntfile.js b/Gruntfile.js
index 9ca02afd2..8921a0d79 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -101,8 +101,10 @@ module.exports = grunt => {
},
libloki: {
src: [
- 'libloki/libloki-protocol.js',
+ 'libloki/api.js',
+ 'libloki/crypto.js',
'libloki/service_nodes.js',
+ 'libloki/storage.js',
],
dest: 'js/libloki.js',
},
diff --git a/background.html b/background.html
index 0d4888b77..2b45235bf 100644
--- a/background.html
+++ b/background.html
@@ -709,7 +709,6 @@
-
diff --git a/js/models/conversations.js b/js/models/conversations.js
index aa1412608..ddc73093e 100644
--- a/js/models/conversations.js
+++ b/js/models/conversations.js
@@ -620,7 +620,7 @@
response: 'declined',
direction: 'incoming',
});
- await window.libloki.removeContactPreKeyBundle(this.id);
+ await window.libloki.storage.removeContactPreKeyBundle(this.id);
},
// We have accepted an incoming friend request
async onAcceptFriendRequest() {
@@ -630,7 +630,7 @@
response: 'accepted',
direction: 'incoming',
});
- window.libloki.sendFriendRequestAccepted(this.id);
+ window.libloki.api.sendFriendRequestAccepted(this.id);
}
},
// Our outgoing friend request has been accepted
@@ -1548,7 +1548,7 @@
await this.setSessionResetStatus(SessionResetEnum.request_received);
// send empty message, this will trigger the new session to propagate
// to the reset initiator.
- await window.libloki.sendEmptyMessage(this.id);
+ await window.libloki.api.sendEmptyMessage(this.id);
},
isSessionResetReceived() {
@@ -1582,7 +1582,7 @@
async onNewSessionAdopted() {
if (this.get('sessionResetStatus') === SessionResetEnum.initiated) {
// send empty message to confirm that we have adopted the new session
- await window.libloki.sendEmptyMessage(this.id);
+ await window.libloki.api.sendEmptyMessage(this.id);
}
await this.createAndStoreEndSessionMessage({ type: 'incoming', endSessionType: 'done' });
await this.setSessionResetStatus(SessionResetEnum.none);
diff --git a/js/models/messages.js b/js/models/messages.js
index e3f568e7f..6f919391d 100644
--- a/js/models/messages.js
+++ b/js/models/messages.js
@@ -1407,7 +1407,7 @@
autoAccept = true;
message.set({ friendStatus: 'accepted' });
await conversation.onFriendRequestAccepted();
- window.libloki.sendFriendRequestAccepted(message.get('source'));
+ window.libloki.api.sendFriendRequestAccepted(message.get('source'));
} else if (!conversation.isFriend()) {
await conversation.onFriendRequestReceived();
}
diff --git a/libloki/api.js b/libloki/api.js
new file mode 100644
index 000000000..b714e2cb5
--- /dev/null
+++ b/libloki/api.js
@@ -0,0 +1,41 @@
+/* global window, textsecure, log */
+
+// eslint-disable-next-line func-names
+(function () {
+ window.libloki = window.libloki || {};
+
+ 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.api = {
+ sendFriendRequestAccepted,
+ sendEmptyMessage,
+ };
+})();
diff --git a/libloki/crypto.js b/libloki/crypto.js
new file mode 100644
index 000000000..1042f59c2
--- /dev/null
+++ b/libloki/crypto.js
@@ -0,0 +1,53 @@
+/* global window, libsignal, textsecure, StringView */
+
+// 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.`);
+ }
+ }
+ }
+
+ window.libloki.crypto = {
+ FallBackSessionCipher,
+ FallBackDecryptionError,
+ };
+})();
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/service_nodes.js b/libloki/service_nodes.js
index 21e676493..58698bd7e 100644
--- a/libloki/service_nodes.js
+++ b/libloki/service_nodes.js
@@ -3,7 +3,6 @@
// eslint-disable-next-line func-names
(function () {
window.libloki = window.libloki || {};
- window.libloki.serviceNodes = window.libloki.serviceNodes || {};
function consolidateLists(lists, threshold = 1){
if (typeof threshold !== 'number') {
@@ -31,5 +30,7 @@
.map(keyValue => keyValue[0]);
}
- window.libloki.serviceNodes.consolidateLists = consolidateLists;
+ window.libloki.serviceNodes = {
+ consolidateLists,
+ };
})();
diff --git a/js/loki_protocol_store.js b/libloki/storage.js
similarity index 55%
rename from js/loki_protocol_store.js
rename to libloki/storage.js
index 2a65747ca..a71676698 100644
--- a/js/loki_protocol_store.js
+++ b/libloki/storage.js
@@ -1,6 +1,97 @@
+/* global window, libsignal, textsecure */
+
// eslint-disable-next-line func-names
-(function() {
- 'use strict';
+(function () {
+ window.libloki = window.libloki || {};
+
+ 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),
+ ]);
+ }
+
+ window.libloki.storage = {
+ getPreKeyBundleForContact,
+ saveContactPreKeyBundle,
+ removeContactPreKeyBundle,
+ };
+
+ // Libloki protocol store
const store = window.SignalProtocolStore.prototype;
@@ -116,5 +207,4 @@
store.clearContactSignedPreKeysStore = async () => {
await window.Signal.Data.removeAllContactSignedPreKeys();
};
-
})();
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 9bd8fde01..f9eb9c2e4 100644
--- a/libloki/test/index.html
+++ b/libloki/test/index.html
@@ -24,12 +24,15 @@
-
+
+
+
+
-
+
diff --git a/libloki/test/libloki-protocol_test.js b/libloki/test/libloki-protocol_test.js
deleted file mode 100644
index 9f73298a8..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.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.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.getPreKeyBundleForContact(pubKeyString);
- const bundle2 = await libloki.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.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/proof-of-work_test.js b/libloki/test/proof-of-work_test.js
index f31f1b4bc..68a85ea19 100644
--- a/libloki/test/proof-of-work_test.js
+++ b/libloki/test/proof-of-work_test.js
@@ -8,88 +8,98 @@ const {
greaterThan,
} = pow;
-describe('Proof of Work Worker', () => {
- it('should increment a Uint8Array nonce correctly', () => {
- const arr1Before = new Uint8Array([0,0,0,0,0,0,0,0]);
- const arr1After = incrementNonce(arr1Before);
- assert.strictEqual(arr1After[0], 0);
- assert.strictEqual(arr1After[1], 0);
- assert.strictEqual(arr1After[2], 0);
- assert.strictEqual(arr1After[3], 0);
- assert.strictEqual(arr1After[4], 0);
- assert.strictEqual(arr1After[5], 0);
- assert.strictEqual(arr1After[6], 0);
- assert.strictEqual(arr1After[7], 1);
- });
+describe('Proof of Work', () => {
+ describe('#incrementNonce', () => {
+ it('should increment a Uint8Array nonce correctly', () => {
+ const arr1Before = new Uint8Array([0,0,0,0,0,0,0,0]);
+ const arr1After = incrementNonce(arr1Before);
+ assert.strictEqual(arr1After[0], 0);
+ assert.strictEqual(arr1After[1], 0);
+ assert.strictEqual(arr1After[2], 0);
+ assert.strictEqual(arr1After[3], 0);
+ assert.strictEqual(arr1After[4], 0);
+ assert.strictEqual(arr1After[5], 0);
+ assert.strictEqual(arr1After[6], 0);
+ assert.strictEqual(arr1After[7], 1);
+ });
- it('should increment a Uint8Array nonce correctly', () => {
- let arr = new Uint8Array([0,0,0,0,0,0,0,0]);
- assert.deepEqual(incrementNonce(arr), new Uint8Array([0,0,0,0,0,0,0,1]));
- arr = new Uint8Array([0,0,0,0,0,0,0,0]);
- for(let i = 0; i <= 255; i += 1) {
- arr = incrementNonce(arr);
- }
- assert.deepEqual(arr, new Uint8Array([0,0,0,0,0,0,1,0]));
- arr = new Uint8Array([255,255,255,255,255,255,255,255]);
- assert.deepEqual(incrementNonce(arr), new Uint8Array([0,0,0,0,0,0,0,0]));
+ it('should increment a Uint8Array nonce correctly in a loop', () => {
+ let arr = new Uint8Array([0,0,0,0,0,0,0,0]);
+ assert.deepEqual(incrementNonce(arr), new Uint8Array([0,0,0,0,0,0,0,1]));
+ arr = new Uint8Array([0,0,0,0,0,0,0,0]);
+ for(let i = 0; i <= 255; i += 1) {
+ arr = incrementNonce(arr);
+ }
+ assert.deepEqual(arr, new Uint8Array([0,0,0,0,0,0,1,0]));
+ arr = new Uint8Array([255,255,255,255,255,255,255,255]);
+ assert.deepEqual(incrementNonce(arr), new Uint8Array([0,0,0,0,0,0,0,0]));
+ });
});
- it('should calculate a correct difficulty target', () => {
- // These values will need to be updated if we adjust the difficulty settings
- let payloadLen = 625;
- const ttl = 86400;
- let expectedTarget = new Uint8Array([0,4,119,164,35,224,222,64]);
+ describe('#calcTarget', () => {
+ it('should calculate a correct difficulty target', () => {
+ // These values will need to be updated if we adjust the difficulty settings
+ let payloadLen = 625;
+ const ttl = 86400;
+ let expectedTarget = new Uint8Array([0,4,119,164,35,224,222,64]);
- let actualTarget = calcTarget(ttl, payloadLen, 10);
- assert.deepEqual(actualTarget, expectedTarget);
- payloadLen = 6597;
- expectedTarget = new Uint8Array([0,0,109,145,174,146,124,3]);
- actualTarget = calcTarget(ttl, payloadLen, 10);
- assert.deepEqual(actualTarget, expectedTarget);
+ let actualTarget = calcTarget(ttl, payloadLen, 10);
+ assert.deepEqual(actualTarget, expectedTarget);
+ payloadLen = 6597;
+ expectedTarget = new Uint8Array([0,0,109,145,174,146,124,3]);
+ actualTarget = calcTarget(ttl, payloadLen, 10);
+ assert.deepEqual(actualTarget, expectedTarget);
+ });
});
- it('should correclty compare two Uint8Arrays', () => {
- let arr1 = new Uint8Array([0,0,0,0,0,0,0,0,0,1]);
- let arr2 = new Uint8Array([0,0,0,0,0,0,0,0,0,1]);
- assert.isFalse(greaterThan(arr1, arr2))
- arr1 = new Uint8Array([0,0,0,0,0,0,0,0,0,2]);
- arr2 = new Uint8Array([0,0,0,0,0,0,0,0,0,1]);
- assert.isTrue(greaterThan(arr1, arr2))
- arr1 = new Uint8Array([255,255,255,255,255,255,255,255,255,255]);
- arr2 = new Uint8Array([255,255,255,255,255,255,255,255,255,254]);
- assert.isTrue(greaterThan(arr1, arr2))
- arr1 = new Uint8Array([254,255,255,255,255,255,255,255,255,255]);
- arr2 = new Uint8Array([255,255,255,255,255,255,255,255,255,255]);
- assert.isFalse(greaterThan(arr1, arr2));
- arr1 = new Uint8Array([0]);
- arr2 = new Uint8Array([0,0]);
- assert.isFalse(greaterThan(arr1, arr2))
+ describe('#greaterThan', () => {
+ it('should correclty compare two Uint8Arrays', () => {
+ let arr1 = new Uint8Array([0,0,0,0,0,0,0,0,0,1]);
+ let arr2 = new Uint8Array([0,0,0,0,0,0,0,0,0,1]);
+ assert.isFalse(greaterThan(arr1, arr2))
+ arr1 = new Uint8Array([0,0,0,0,0,0,0,0,0,2]);
+ arr2 = new Uint8Array([0,0,0,0,0,0,0,0,0,1]);
+ assert.isTrue(greaterThan(arr1, arr2))
+ arr1 = new Uint8Array([255,255,255,255,255,255,255,255,255,255]);
+ arr2 = new Uint8Array([255,255,255,255,255,255,255,255,255,254]);
+ assert.isTrue(greaterThan(arr1, arr2))
+ arr1 = new Uint8Array([254,255,255,255,255,255,255,255,255,255]);
+ arr2 = new Uint8Array([255,255,255,255,255,255,255,255,255,255]);
+ assert.isFalse(greaterThan(arr1, arr2));
+ arr1 = new Uint8Array([0]);
+ arr2 = new Uint8Array([0,0]);
+ assert.isFalse(greaterThan(arr1, arr2))
+ });
});
- it('should correclty convert a Uint8Array to a base64 string', () => {
- let arr = new Uint8Array([1,2,3]);
- let expected = 'AQID';
- assert.strictEqual(bufferToBase64(arr), expected);
- arr = new Uint8Array([123,25,3,121,45,87,24,111]);
- expected = 'exkDeS1XGG8=';
- assert.strictEqual(bufferToBase64(arr), expected);
- arr = new Uint8Array([]);
- expected = '';
- assert.strictEqual(bufferToBase64(arr), expected);
+ describe('#bufferToBase64', () => {
+ it('should correclty convert a Uint8Array to a base64 string', () => {
+ let arr = new Uint8Array([1,2,3]);
+ let expected = 'AQID';
+ assert.strictEqual(bufferToBase64(arr), expected);
+ arr = new Uint8Array([123,25,3,121,45,87,24,111]);
+ expected = 'exkDeS1XGG8=';
+ assert.strictEqual(bufferToBase64(arr), expected);
+ arr = new Uint8Array([]);
+ expected = '';
+ assert.strictEqual(bufferToBase64(arr), expected);
+ });
});
- it('should correclty convert a BigInteger to a Uint8Array', () => {
- let bigInt = JSBI.BigInt(Number.MAX_SAFE_INTEGER);
- let expected = new Uint8Array([0, 31, 255, 255, 255, 255, 255, 255]);
- assert.deepEqual(bigIntToUint8Array(bigInt), expected);
- bigInt = JSBI.BigInt('0');
- expected = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
- assert.deepEqual(bigIntToUint8Array(bigInt), expected);
- bigInt = JSBI.BigInt('255');
- expected = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 255]);
- assert.deepEqual(bigIntToUint8Array(bigInt), expected);
- bigInt = JSBI.BigInt('256');
- expected = new Uint8Array([0, 0, 0, 0, 0, 0, 1, 0]);
- assert.deepEqual(bigIntToUint8Array(bigInt), expected);
+ describe('#bigIntToUint8Array', () => {
+ it('should correclty convert a BigInteger to a Uint8Array', () => {
+ let bigInt = JSBI.BigInt(Number.MAX_SAFE_INTEGER);
+ let expected = new Uint8Array([0, 31, 255, 255, 255, 255, 255, 255]);
+ assert.deepEqual(bigIntToUint8Array(bigInt), expected);
+ bigInt = JSBI.BigInt('0');
+ expected = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
+ assert.deepEqual(bigIntToUint8Array(bigInt), expected);
+ bigInt = JSBI.BigInt('255');
+ expected = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 255]);
+ assert.deepEqual(bigIntToUint8Array(bigInt), expected);
+ bigInt = JSBI.BigInt('256');
+ expected = new Uint8Array([0, 0, 0, 0, 0, 0, 1, 0]);
+ assert.deepEqual(bigIntToUint8Array(bigInt), expected);
+ });
});
});
diff --git a/libloki/test/service_nodes_test.js b/libloki/test/service_nodes_test.js
index e502e8532..1d72d0b92 100644
--- a/libloki/test/service_nodes_test.js
+++ b/libloki/test/service_nodes_test.js
@@ -1,40 +1,50 @@
-/* global libloki, chai */
+/* global libloki, assert */
describe('ServiceNodes', () => {
describe('#consolidateLists', () => {
it('should throw when provided a non-iterable list', () => {
- chai.expect(() => libloki.serviceNodes.consolidateLists(null, 1)).to.throw();
+ assert.throws(() => libloki.serviceNodes.consolidateLists(null, 1), Error);
});
+
it('should throw when provided a non-iterable item in the list', () => {
- chai.expect(() => libloki.serviceNodes.consolidateLists([1, 2, 3], 1)).to.throw();
+ assert.throws(() => libloki.serviceNodes.consolidateLists([1, 2, 3], 1), Error);
});
+
it('should throw when provided a non-number threshold', () => {
- chai.expect(() => libloki.serviceNodes.consolidateLists([], 'a')).to.throw();
+ assert.throws(
+ () => libloki.serviceNodes.consolidateLists([], 'a'),
+ 'Provided threshold is not a number'
+ );
});
+
it('should return an empty array when the input is an empty array', () => {
const result = libloki.serviceNodes.consolidateLists([]);
- chai.expect(result).to.deep.equal([]);
+ assert.deepEqual(result, []);
});
+
it('should return the input when only 1 list is provided', () => {
const result = libloki.serviceNodes.consolidateLists([['a', 'b', 'c']]);
- chai.expect(result).to.deep.equal(['a', 'b', 'c']);
+ assert.deepEqual(result, ['a', 'b', 'c']);
});
+
it('should return the union of all lists when threshold is 0', () => {
const result = libloki.serviceNodes.consolidateLists([
['a', 'b', 'c', 'h'],
['d', 'e', 'f', 'g'],
['g', 'h'],
], 0);
- chai.expect(result.sort()).to.deep.equal(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']);
+ assert.deepEqual(result.sort(), ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']);
});
+
it('should return the intersection of all lists when threshold is 1', () => {
const result = libloki.serviceNodes.consolidateLists([
['a', 'b', 'c', 'd'],
['a', 'e', 'f', 'g'],
['a', 'h'],
], 1);
- chai.expect(result).to.deep.equal(['a']);
+ assert.deepEqual(result, ['a']);
});
+
it('should return the elements that have an occurence >= the provided threshold', () => {
const result = libloki.serviceNodes.consolidateLists([
['a', 'b', 'c', 'd', 'e', 'f', 'g'],
@@ -42,8 +52,9 @@ describe('ServiceNodes', () => {
['a', 'b', 'c', 'd', 'e', 'f', 'g'],
['a', 'b', 'c', 'd', 'e', 'g', 'h'],
], 3/4);
- chai.expect(result).to.deep.equal(['a', 'b', 'c', 'd', 'e', 'f', 'g']);
+ assert.deepEqual(result, ['a', 'b', 'c', 'd', 'e', 'f', 'g']);
});
+
it('should work with sets as well', () => {
const result = libloki.serviceNodes.consolidateLists(new Set([
new Set(['a', 'b', 'c', 'd', 'e', 'f', 'g']),
@@ -51,7 +62,7 @@ describe('ServiceNodes', () => {
new Set(['a', 'b', 'c', 'd', 'e', 'f', 'g']),
new Set(['a', 'b', 'c', 'd', 'e', 'g', 'h']),
]), 3/4);
- chai.expect(result).to.deep.equal(['a', 'b', 'c', 'd', 'e', 'f', 'g']);
+ assert.deepEqual(result, ['a', 'b', 'c', 'd', 'e', 'f', 'g']);
});
});
});
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);
+ });
+ });
+});
diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js
index 04eaa43f6..ac61def0f 100644
--- a/libtextsecure/message_receiver.js
+++ b/libtextsecure/message_receiver.js
@@ -605,7 +605,7 @@ MessageReceiver.prototype.extend({
textsecure.storage.protocol
);
- const fallBackSessionCipher = new libloki.FallBackSessionCipher(
+ const fallBackSessionCipher = new libloki.crypto.FallBackSessionCipher(
address
);
@@ -1238,7 +1238,7 @@ MessageReceiver.prototype.extend({
);
}
- await libloki.saveContactPreKeyBundle({
+ await libloki.storage.saveContactPreKeyBundle({
pubKey,
preKeyId,
signedKeyId,
diff --git a/libtextsecure/outgoing_message.js b/libtextsecure/outgoing_message.js
index 9e702a906..9ab24570c 100644
--- a/libtextsecure/outgoing_message.js
+++ b/libtextsecure/outgoing_message.js
@@ -121,7 +121,7 @@ OutgoingMessage.prototype = {
}
return builder.processPreKey(device).then(async () => {
// TODO: only remove the keys that were used above!
- await window.libloki.removeContactPreKeyBundle(number);
+ await window.libloki.storage.removeContactPreKeyBundle(number);
return true;
}
).catch(error => {
@@ -288,7 +288,7 @@ OutgoingMessage.prototype = {
const address = new libsignal.SignalProtocolAddress(number, deviceId);
const ourKey = textsecure.storage.user.getNumber();
const options = {};
- const fallBackCipher = new libloki.FallBackSessionCipher(address);
+ const fallBackCipher = new libloki.crypto.FallBackSessionCipher(address);
// Check if we need to attach the preKeys
let sessionCipher;
@@ -297,7 +297,7 @@ OutgoingMessage.prototype = {
const isEndSession = flags === textsecure.protobuf.DataMessage.Flags.END_SESSION;
if (isFriendRequest || isEndSession) {
// Encrypt them with the fallback
- const pkb = await libloki.getPreKeyBundleForContact(number);
+ const pkb = await libloki.storage.getPreKeyBundleForContact(number);
const preKeyBundleMessage = new textsecure.protobuf.PreKeyBundleMessage(pkb);
this.message.preKeyBundleMessage = preKeyBundleMessage;
window.log.info('attaching prekeys to outgoing message');