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;