From b9dbf11ebc92147725d34d2d7cf25ed0daa4033c Mon Sep 17 00:00:00 2001 From: sachaaaaa Date: Thu, 18 Oct 2018 16:44:50 +1100 Subject: [PATCH 1/3] Fix various things from previous commits --- libloki/libloki-protocol.js | 8 ++++---- libtextsecure/outgoing_message.js | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libloki/libloki-protocol.js b/libloki/libloki-protocol.js index 059b261cb..f0bbbab96 100644 --- a/libloki/libloki-protocol.js +++ b/libloki/libloki-protocol.js @@ -74,13 +74,13 @@ ]); const preKeyMessage = new textsecure.protobuf.PreKeyBundleMessage({ - identityKey, + identityKey: new Uint8Array(identityKey), deviceId: 1, // TODO: fetch from somewhere preKeyId: preKey.keyId, signedKeyId, - preKey: preKey.pubKey, - signedKey: signedKey.pubKey, - signature: signedKey.signature, + preKey: new Uint8Array(preKey.pubKey), + signedKey: new Uint8Array(signedKey.pubKey), + signature: new Uint8Array(signedKey.signature), }); return preKeyMessage; diff --git a/libtextsecure/outgoing_message.js b/libtextsecure/outgoing_message.js index 665975f1b..badc6fe94 100644 --- a/libtextsecure/outgoing_message.js +++ b/libtextsecure/outgoing_message.js @@ -116,7 +116,7 @@ OutgoingMessage.prototype = { if (updateDevices === undefined) { return this.server.getKeysForNumber(number).then(handleResult); } - let promise = Promise.resolve(); + let promise = Promise.resolve(true); updateDevices.forEach(device => { promise = promise.then(() => Promise.all([ @@ -217,7 +217,7 @@ OutgoingMessage.prototype = { request: requestMessage }); const bytes = new Uint8Array(websocketMessage.encode().toArrayBuffer()) - bytes.toString(); // print bytes for debugging purposes: can be injected in mock socket server + console.log(bytes.toString()); // print bytes for debugging purposes: can be injected in mock socket server return bytes; }, doSendMessage(number, deviceIds, recurse) { From 025d13a72b9cdd9e1b8a5a80ffc44424748ac69a Mon Sep 17 00:00:00 2001 From: sachaaaaa Date: Thu, 18 Oct 2018 16:46:33 +1100 Subject: [PATCH 2/3] Add keyId index for contact prekeys and allow retrieving prekeys for a specific pubkey and keyid --- ...rations_0_database_with_attachment_data.js | 2 + js/signal_protocol_store.js | 55 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/js/modules/migrations/migrations_0_database_with_attachment_data.js b/js/modules/migrations/migrations_0_database_with_attachment_data.js index e7ca8847d..2cfac0de4 100644 --- a/js/modules/migrations/migrations_0_database_with_attachment_data.js +++ b/js/modules/migrations/migrations_0_database_with_attachment_data.js @@ -52,9 +52,11 @@ const migrations = [ const contactPreKeys = transaction.db.createObjectStore('contactPreKeys', { keyPath: 'id', autoIncrement : true }); contactPreKeys.createIndex('identityKeyString', 'identityKeyString', { unique: false }); + contactPreKeys.createIndex('keyId', 'keyId', { unique: false }); const contactSignedPreKeys = transaction.db.createObjectStore('contactSignedPreKeys', { keyPath: 'id', autoIncrement : true }); contactSignedPreKeys.createIndex('identityKeyString', 'identityKeyString', { unique: false }); + contactSignedPreKeys.createIndex('keyId', 'keyId', { unique: false }); window.log.info('creating debug log'); transaction.db.createObjectStore('debug'); diff --git a/js/signal_protocol_store.js b/js/signal_protocol_store.js index 467ab29b9..9bb562ff9 100644 --- a/js/signal_protocol_store.js +++ b/js/signal_protocol_store.js @@ -179,7 +179,23 @@ const Group = Model.extend({ storeName: 'groups' }); const Item = Model.extend({ storeName: 'items' }); const ContactPreKey = Model.extend({ storeName: 'contactPreKeys' }); + const ContactPreKeyCollection = Backbone.Collection.extend({ + storeName: 'contactPreKeys', + database: Whisper.Database, + model: ContactPreKey, + fetchBy(filter) { + return this.fetch({ conditions: filter, }); + }, + }); const ContactSignedPreKey = Model.extend({ storeName: 'contactSignedPreKeys' }); + const ContactSignedPreKeyCollection = Backbone.Collection.extend({ + storeName: 'contactSignedPreKeys', + database: Whisper.Database, + model: ContactSignedPreKey, + fetchBy(filter) { + return this.fetch({ conditions: filter, }); + }, + }); function SignalProtocolStore() {} @@ -261,6 +277,24 @@ ); }); }, + loadContactPreKeys(filters) { + const contactPreKeys = new ContactPreKeyCollection(); + return new Promise((resolve, reject) => { + contactPreKeys.fetchBy(filters).then(() => { + resolve( + contactPreKeys.map(prekey => ({ + id: prekey.get('id'), + keyId: prekey.get('keyId'), + publicKey: prekey.get('publicKey'), + identityKeyString: prekey.get('identityKeyString'), + })) + ); + }).fail(e => { + window.log.error('Failed to fetch signed prekey with filters', filters); + reject(e); + }); + }); + }, storeContactPreKey(pubKey, preKey) { const prekey = new ContactPreKey({ // id: (autoincrement) @@ -340,6 +374,27 @@ }); }); }, + loadContactSignedPreKeys(filters) { + const contactSignedPreKeys = new ContactSignedPreKeyCollection(); + return new Promise((resolve, reject) => { + contactSignedPreKeys.fetchBy(filters).then(() => { + resolve( + contactSignedPreKeys.map(prekey => ({ + id: prekey.get('id'), + identityKeyString: prekey.get('identityKeyString'), + publicKey: prekey.get('publicKey'), + signature: prekey.get('signature'), + created_at: prekey.get('created_at'), + keyId: prekey.get('keyId'), + confirmed: prekey.get('confirmed'), + })) + ); + }).fail(e => { + window.log.error('Failed to fetch signed prekey with filters', filters); + reject(e); + }); + }); + }, loadContactSignedPreKey(pubKey) { const prekey = new ContactSignedPreKey({ identityKeyString: pubKey }); return new Promise(resolve => { From 93f1534512e1486728ea2c2236594e7a81580613 Mon Sep 17 00:00:00 2001 From: sachaaaaa Date: Fri, 19 Oct 2018 09:48:47 +1100 Subject: [PATCH 3/3] Save prekeys upon reception of friend request. Add function to send an empty message as friend request acceptance --- libloki/libloki-protocol.js | 59 ++++++++++++++++++++++++++++++- libtextsecure/message_receiver.js | 31 +++++++++++++++- libtextsecure/outgoing_message.js | 3 ++ 3 files changed, 91 insertions(+), 2 deletions(-) diff --git a/libloki/libloki-protocol.js b/libloki/libloki-protocol.js index f0bbbab96..37f89ff6b 100644 --- a/libloki/libloki-protocol.js +++ b/libloki/libloki-protocol.js @@ -1,4 +1,4 @@ -/* global window, libsignal, textsecure */ +/* global window, libsignal, textsecure, OutgoingMessage */ // eslint-disable-next-line func-names (function() { @@ -85,9 +85,66 @@ return preKeyMessage; } + + savePreKeyBundleForNumber = async function({ pubKey, preKeyId, preKey, signedKeyId, signedKey, signature }) { + const signedKeyPromise = new Promise(async (resolve) => { + const existingSignedKeys = await textsecure.storage.protocol.loadContactSignedPreKeys({ identityKeyString: pubKey, keyId: signedKeyId }); + if (!existingSignedKeys || (existingSignedKeys instanceof Array && existingSignedKeys.length == 0)) + { + const signedPreKey = { + keyId: signedKeyId, + publicKey: signedKey, + signature, + }; + await textsecure.storage.protocol.storeContactSignedPreKey(pubKey, signedPreKey); + } + resolve(); + }); + + const preKeyPromise = new Promise(async (resolve) => { + const existingPreKeys = textsecure.storage.protocol.loadContactPreKeys({ identityKeyString: pubKey, keyId: preKeyId }); + if (!existingPreKeys || (existingPreKeys instanceof Array && existingPreKeys.length == 0)) + { + const preKeyObject = { + publicKey: preKey, + keyId: preKeyId, + } + await textsecure.storage.protocol.storeContactPreKey(pubKey, preKeyObject); + } + resolve(); + }); + + await Promise.all([signedKeyPromise, preKeyPromise]); + } + + sendEmptyMessageWithPreKeys = async function(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 => console.error(error)); + } else { + console.log('empty message sent successfully'); + } + }; + // 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 + ); + await outgoingMessage.sendToNumber(pubKey); + } window.libloki.FallBackSessionCipher = FallBackSessionCipher; window.libloki.getPreKeyBundleForNumber = getPreKeyBundleForNumber; window.libloki.FallBackDecryptionError = FallBackDecryptionError; + window.libloki.savePreKeyBundleForNumber = savePreKeyBundleForNumber; + window.libloki.sendEmptyMessageWithPreKeys = sendEmptyMessageWithPreKeys; })(); \ No newline at end of file diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index 7ece2215a..bc158f29e 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -829,8 +829,13 @@ MessageReceiver.prototype.extend({ } }); }, - innerHandleContentMessage(envelope, plaintext) { + async innerHandleContentMessage(envelope, plaintext) { const content = textsecure.protobuf.Content.decode(plaintext); + + if (content.preKeyBundleMessage) { + await this.handlePreKeyBundleMessage(envelope, content.preKeyBundleMessage); + } + if (content.syncMessage) { return this.handleSyncMessage(envelope, content.syncMessage); } else if (content.dataMessage) { @@ -1052,6 +1057,30 @@ MessageReceiver.prototype.extend({ return this.removeFromCache(envelope); }, + async handlePreKeyBundleMessage(envelope, preKeyBundleMessage) { + + const { preKeyId, signedKeyId } = preKeyBundleMessage; + const [ identityKey, preKey, signedKey, signature ] = [ + preKeyBundleMessage.identityKey, + preKeyBundleMessage.preKey, + preKeyBundleMessage.signedKey, + preKeyBundleMessage.signature + ].map(k => dcodeIO.ByteBuffer.wrap(k).toArrayBuffer()); + + if (envelope.source != StringView.arrayBufferToHex(identityKey)) { + throw new Error("Error in handlePreKeyBundleMessage: envelope pubkey does not match pubkey in prekey bundle"); + } + const pubKey = envelope.source; + + return await libloki.savePreKeyBundleForNumber({ + pubKey, + preKeyId, + signedKeyId, + preKey, + signedKey, + signature, + }); + }, isBlocked(number) { return textsecure.storage.get('blocked', []).indexOf(number) >= 0; }, diff --git a/libtextsecure/outgoing_message.js b/libtextsecure/outgoing_message.js index badc6fe94..bf37b700b 100644 --- a/libtextsecure/outgoing_message.js +++ b/libtextsecure/outgoing_message.js @@ -409,3 +409,6 @@ OutgoingMessage.prototype = { ); }, }; + +window.textsecure = window.textsecure || {}; +window.textsecure.OutgoingMessage = OutgoingMessage;