From 982f10a50513486281a4e820f5843da1e510f2f0 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 10 Jan 2019 09:59:45 +1100 Subject: [PATCH 1/5] Split libloki logic. Added to gruntfile. --- Gruntfile.js | 4 +- libloki/api.js | 41 ++++++++++++++++++ libloki/crypto.js | 53 +++++++++++++++++++++++ libloki/service_nodes.js | 5 ++- libloki/storage.js | 93 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 193 insertions(+), 3 deletions(-) create mode 100644 libloki/api.js create mode 100644 libloki/crypto.js create mode 100644 libloki/storage.js 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/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/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/libloki/storage.js b/libloki/storage.js new file mode 100644 index 000000000..654addc48 --- /dev/null +++ b/libloki/storage.js @@ -0,0 +1,93 @@ +/* global window, libsignal, textsecure */ + +// eslint-disable-next-line func-names +(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, + }; +})(); From 070d18b514aead68d2bf2b49f37e967e2f86d18c Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 10 Jan 2019 10:26:25 +1100 Subject: [PATCH 2/5] Updated function calls. --- js/models/conversations.js | 8 ++++---- js/models/messages.js | 2 +- libloki/test/index.html | 4 +++- libloki/test/libloki-protocol_test.js | 10 +++++----- libtextsecure/message_receiver.js | 4 ++-- libtextsecure/outgoing_message.js | 6 +++--- 6 files changed, 18 insertions(+), 16 deletions(-) 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/test/index.html b/libloki/test/index.html index 9bd8fde01..e690b0bb8 100644 --- a/libloki/test/index.html +++ b/libloki/test/index.html @@ -24,8 +24,10 @@ - + + + diff --git a/libloki/test/libloki-protocol_test.js b/libloki/test/libloki-protocol_test.js index 9f73298a8..53c9d923a 100644 --- a/libloki/test/libloki-protocol_test.js +++ b/libloki/test/libloki-protocol_test.js @@ -18,7 +18,7 @@ describe('FallBackSessionCipher', () => { pubKeyString, 1 ); - fallbackCipher = new libloki.FallBackSessionCipher(address); + fallbackCipher = new libloki.crypto.FallBackSessionCipher(address); }); it('should encrypt fallback cipher messages as friend requests', async () => { @@ -53,7 +53,7 @@ describe('LibLoki Protocol', () => { 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 newBundle = await libloki.storage.getPreKeyBundleForContact(pubKeyString); const preKeyIdAfter = textsecure.storage.get('maxPreKeyId', 1); assert.strictEqual(preKeyIdAfter, preKeyIdBefore + 1); @@ -74,8 +74,8 @@ describe('LibLoki Protocol', () => { 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); + const bundle1 = await libloki.storage.getPreKeyBundleForContact(pubKeyString); + const bundle2 = await libloki.storage.getPreKeyBundleForContact(pubKeyString); assert.isDefined(bundle1); assert.isDefined(bundle2); assert.deepEqual(bundle1, bundle2); @@ -85,7 +85,7 @@ describe('LibLoki Protocol', () => { 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 newBundle = await libloki.storage.getPreKeyBundleForContact(pubKeyString); const preKeyIdAfter = textsecure.storage.get('maxPreKeyId', 1); assert.strictEqual(preKeyIdAfter, preKeyIdBefore + 1); 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'); From f9147663d53767ab6e0cd9fe8c36148356add3e7 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 10 Jan 2019 10:26:43 +1100 Subject: [PATCH 3/5] Updated tests. Removed libloki-protocol. --- libloki/libloki-protocol.js | 167 -------------------------- libloki/test/crypto_test.js | 38 ++++++ libloki/test/index.html | 3 +- libloki/test/libloki-protocol_test.js | 103 ---------------- libloki/test/storage_test.js | 72 +++++++++++ 5 files changed, 112 insertions(+), 271 deletions(-) delete mode 100644 libloki/libloki-protocol.js create mode 100644 libloki/test/crypto_test.js delete mode 100644 libloki/test/libloki-protocol_test.js create mode 100644 libloki/test/storage_test.js 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); + }); + }); +}); From 4eda13733a72edb438553cbdd516a85273537d79 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 10 Jan 2019 10:33:38 +1100 Subject: [PATCH 4/5] Cleanup tests. Moved service_node_test to assert to keep consistency. --- libloki/test/proof-of-work_test.js | 156 +++++++++++++++-------------- libloki/test/service_nodes_test.js | 31 ++++-- 2 files changed, 104 insertions(+), 83 deletions(-) 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']); }); }); }); From 4d1f4fcb4f3c28698a56f213157eb15993f69f67 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 10 Jan 2019 11:53:12 +1100 Subject: [PATCH 5/5] Moved loki_protocol_store.js into libloki/storage.js --- background.html | 1 - js/loki_protocol_store.js | 120 -------------------------------------- libloki/storage.js | 117 +++++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 121 deletions(-) delete mode 100644 js/loki_protocol_store.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/loki_protocol_store.js b/js/loki_protocol_store.js deleted file mode 100644 index 2a65747ca..000000000 --- a/js/loki_protocol_store.js +++ /dev/null @@ -1,120 +0,0 @@ -// eslint-disable-next-line func-names -(function() { - 'use strict'; - - const store = window.SignalProtocolStore.prototype; - - store.storeContactPreKey = async (pubKey, preKey) => { - const key = { - // id: (autoincrement) - identityKeyString: pubKey, - publicKey: preKey.publicKey, - keyId: preKey.keyId, - }; - - await window.Signal.Data.createOrUpdateContactPreKey(key); - }; - - store.loadContactPreKey = async pubKey => { - const preKey = await window.Signal.Data.getContactPreKeyByIdentityKey(pubKey); - if (preKey) { - return { - id: preKey.id, - keyId: preKey.keyId, - publicKey: preKey.publicKey, - identityKeyString: preKey.identityKeyString, - } - } - - window.log.warn('Failed to fetch contact prekey:', pubKey); - return undefined; - }; - - store.loadContactPreKeys = async filters => { - const { keyId, identityKeyString } = filters; - const keys = await window.Signal.Data.getContactPreKeys(keyId, identityKeyString); - if (keys) { - return keys.map(preKey => ({ - id: preKey.id, - keyId: preKey.keyId, - publicKey: preKey.publicKey, - identityKeyString: preKey.identityKeyString, - })); - } - - window.log.warn( - 'Failed to fetch signed prekey with filters', - filters - ); - return undefined; - }; - - store.removeContactPreKey = async pubKey => { - await window.Signal.Data.removeContactPreKeyByIdentityKey(pubKey); - }; - - store.clearContactPreKeysStore = async () => { - await window.Signal.Data.removeAllContactPreKeys(); - }; - - store.storeContactSignedPreKey = async (pubKey, signedPreKey) => { - const key = { - // id: (autoincrement) - identityKeyString: pubKey, - keyId: signedPreKey.keyId, - publicKey: signedPreKey.publicKey, - signature: signedPreKey.signature, - created_at: Date.now(), - confirmed: false, - }; - await window.Signal.Data.createOrUpdateContactSignedPreKey(key); - }; - - store.loadContactSignedPreKey = async pubKey => { - const preKey = await window.Signal.Data.getContactSignedPreKeyByIdentityKey(pubKey); - if (preKey) { - return { - id: preKey.id, - identityKeyString: preKey.identityKeyString, - publicKey: preKey.publicKey, - signature: preKey.signature, - created_at: preKey.created_at, - keyId: preKey.keyId, - confirmed: preKey.confirmed, - }; - } - window.log.warn('Failed to fetch contact signed prekey:', pubKey); - return undefined; - }; - - store.loadContactSignedPreKeys = async filters => { - const { keyId, identityKeyString } = filters; - const keys = await window.Signal.Data.getContactSignedPreKeys(keyId, identityKeyString); - if (keys) { - return keys.map(preKey => ({ - id: preKey.id, - identityKeyString: preKey.identityKeyString, - publicKey: preKey.publicKey, - signature: preKey.signature, - created_at: preKey.created_at, - keyId: preKey.keyId, - confirmed: preKey.confirmed, - })); - } - - window.log.warn( - 'Failed to fetch contact signed prekey with filters', - filters - ); - return undefined; - }; - - store.removeContactSignedPreKey = async pubKey => { - await window.Signal.Data.removeContactSignedPreKeyByIdentityKey(pubKey); - }; - - store.clearContactSignedPreKeysStore = async () => { - await window.Signal.Data.removeAllContactSignedPreKeys(); - }; - -})(); diff --git a/libloki/storage.js b/libloki/storage.js index 654addc48..a71676698 100644 --- a/libloki/storage.js +++ b/libloki/storage.js @@ -90,4 +90,121 @@ saveContactPreKeyBundle, removeContactPreKeyBundle, }; + + // Libloki protocol store + + const store = window.SignalProtocolStore.prototype; + + store.storeContactPreKey = async (pubKey, preKey) => { + const key = { + // id: (autoincrement) + identityKeyString: pubKey, + publicKey: preKey.publicKey, + keyId: preKey.keyId, + }; + + await window.Signal.Data.createOrUpdateContactPreKey(key); + }; + + store.loadContactPreKey = async pubKey => { + const preKey = await window.Signal.Data.getContactPreKeyByIdentityKey(pubKey); + if (preKey) { + return { + id: preKey.id, + keyId: preKey.keyId, + publicKey: preKey.publicKey, + identityKeyString: preKey.identityKeyString, + } + } + + window.log.warn('Failed to fetch contact prekey:', pubKey); + return undefined; + }; + + store.loadContactPreKeys = async filters => { + const { keyId, identityKeyString } = filters; + const keys = await window.Signal.Data.getContactPreKeys(keyId, identityKeyString); + if (keys) { + return keys.map(preKey => ({ + id: preKey.id, + keyId: preKey.keyId, + publicKey: preKey.publicKey, + identityKeyString: preKey.identityKeyString, + })); + } + + window.log.warn( + 'Failed to fetch signed prekey with filters', + filters + ); + return undefined; + }; + + store.removeContactPreKey = async pubKey => { + await window.Signal.Data.removeContactPreKeyByIdentityKey(pubKey); + }; + + store.clearContactPreKeysStore = async () => { + await window.Signal.Data.removeAllContactPreKeys(); + }; + + store.storeContactSignedPreKey = async (pubKey, signedPreKey) => { + const key = { + // id: (autoincrement) + identityKeyString: pubKey, + keyId: signedPreKey.keyId, + publicKey: signedPreKey.publicKey, + signature: signedPreKey.signature, + created_at: Date.now(), + confirmed: false, + }; + await window.Signal.Data.createOrUpdateContactSignedPreKey(key); + }; + + store.loadContactSignedPreKey = async pubKey => { + const preKey = await window.Signal.Data.getContactSignedPreKeyByIdentityKey(pubKey); + if (preKey) { + return { + id: preKey.id, + identityKeyString: preKey.identityKeyString, + publicKey: preKey.publicKey, + signature: preKey.signature, + created_at: preKey.created_at, + keyId: preKey.keyId, + confirmed: preKey.confirmed, + }; + } + window.log.warn('Failed to fetch contact signed prekey:', pubKey); + return undefined; + }; + + store.loadContactSignedPreKeys = async filters => { + const { keyId, identityKeyString } = filters; + const keys = await window.Signal.Data.getContactSignedPreKeys(keyId, identityKeyString); + if (keys) { + return keys.map(preKey => ({ + id: preKey.id, + identityKeyString: preKey.identityKeyString, + publicKey: preKey.publicKey, + signature: preKey.signature, + created_at: preKey.created_at, + keyId: preKey.keyId, + confirmed: preKey.confirmed, + })); + } + + window.log.warn( + 'Failed to fetch contact signed prekey with filters', + filters + ); + return undefined; + }; + + store.removeContactSignedPreKey = async pubKey => { + await window.Signal.Data.removeContactSignedPreKeyByIdentityKey(pubKey); + }; + + store.clearContactSignedPreKeysStore = async () => { + await window.Signal.Data.removeAllContactSignedPreKeys(); + }; })();