From c59b196487111623753c8503f4fb191abc06841b Mon Sep 17 00:00:00 2001 From: Beaudan Date: Thu, 8 Nov 2018 14:28:48 +1100 Subject: [PATCH] Now reading messages and adding them to conversations. Some cleaning of unreachable code. Modified the message data to be encoded as base64 string before sending to server Sending our public key in header of message Now attaching our key to the source field when sending messages, allows messages to be decrypted with the fallback cypher Now polling the server for messages every 5 seconds Sending the source device with messages Added mock respond function to request to leave it that same as the websocket stuff. RetrieveMessages now just returns the result Polling now continues if the server responds with an error. Returning only the result from sendMessage and retrieveMessages Revert commenting of unreachable code Refactored http logic into own file Revert a change to websocket-resources --- Gruntfile.js | 1 + js/modules/loki_message_api.js | 17 ++++--- libloki/proof-of-work.js | 12 ++--- libtextsecure/http-resources.js | 83 +++++++++++++++++++++++++++++++ libtextsecure/message_receiver.js | 20 ++++---- libtextsecure/outgoing_message.js | 16 +++--- 6 files changed, 114 insertions(+), 35 deletions(-) create mode 100644 libtextsecure/http-resources.js diff --git a/Gruntfile.js b/Gruntfile.js index a1ccc8082..5c392b946 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -76,6 +76,7 @@ module.exports = grunt => { 'libtextsecure/event_target.js', 'libtextsecure/account_manager.js', 'libtextsecure/websocket-resources.js', + 'libtextsecure/http-resources.js', 'libtextsecure/message_receiver.js', 'libtextsecure/outgoing_message.js', 'libtextsecure/sendmessage.js', diff --git a/js/modules/loki_message_api.js b/js/modules/loki_message_api.js index 9ce734790..ebc4917a5 100644 --- a/js/modules/loki_message_api.js +++ b/js/modules/loki_message_api.js @@ -1,4 +1,4 @@ -/* global log */ +/* global log, dcodeIO */ const fetch = require('node-fetch'); const is = require('@sindresorhus/is'); @@ -33,7 +33,7 @@ function initialize({ url }) { timestamp, ttl, pubKey, - data: Array.from(data), + data, }); // Handle child process error (should never happen) @@ -57,7 +57,7 @@ function initialize({ url }) { const options = { url: `${url}/retrieve`, type: 'GET', - responseType: undefined, + responseType: 'json', timeout: undefined, }; @@ -93,18 +93,20 @@ function initialize({ url }) { if (response.status >= 0 && response.status < 400) { log.info(options.type, options.url, response.status, 'Success'); - return [result, response.status]; + return result; } log.error(options.type, options.url, response.status, 'Error'); throw HTTPError('retrieveMessages: error response', response.status, result); } async function sendMessage(pubKey, data, ttl) { + const data64 = dcodeIO.ByteBuffer.wrap(data).toString('base64'); + const timestamp = Math.floor(Date.now() / 1000); // Nonce is returned as a base64 string to include in header let nonce; try { - nonce = await getPoWNonce(timestamp, ttl, pubKey, data); + nonce = await getPoWNonce(timestamp, ttl, pubKey, data64); } catch (err) { // Something went horribly wrong // TODO: Handle gracefully @@ -122,13 +124,12 @@ function initialize({ url }) { const fetchOptions = { method: options.type, - body: data, + body: data64, headers: { 'X-Loki-pow-nonce': nonce, 'X-Loki-timestamp': timestamp.toString(), 'X-Loki-ttl': ttl.toString(), 'X-Loki-recipient': pubKey, - 'Content-Length': data.byteLength, }, timeout: options.timeout, }; @@ -155,7 +156,7 @@ function initialize({ url }) { if (response.status >= 0 && response.status < 400) { log.info(options.type, options.url, response.status, 'Success'); - return [result, response.status]; + return result; } log.error(options.type, options.url, response.status, 'Error'); throw HTTPError('sendMessage: error response', response.status, result); diff --git a/libloki/proof-of-work.js b/libloki/proof-of-work.js index 01a413955..e9bdafb1f 100644 --- a/libloki/proof-of-work.js +++ b/libloki/proof-of-work.js @@ -58,15 +58,9 @@ function greaterThan(arr1, arr2) { // Return nonce that hashes together with payload lower than the target function calcPoW(timestamp, ttl, pubKey, data) { - const leadingString = timestamp.toString() + ttl.toString() + pubKey; - const leadingArray = new Uint8Array( - bb.wrap(leadingString, 'binary').toArrayBuffer() + const payload = new Uint8Array( + bb.wrap(timestamp.toString() + ttl.toString() + pubKey + data, 'binary').toArrayBuffer() ); - // Payload constructed from concatenating timestamp, ttl and pubkey strings, - // converting to Uint8Array and then appending to the message data array - const payload = new Uint8Array(leadingArray.length + data.length); - payload.set(leadingArray); - payload.set(data, leadingArray.length); // payloadLength + NONCE_LEN const totalLen = new BigInteger(payload.length.toString()).add( @@ -118,7 +112,7 @@ process.on('message', msg => { msg.timestamp, msg.ttl, msg.pubKey, - new Uint8Array(msg.data) + msg.data ), }); }); diff --git a/libtextsecure/http-resources.js b/libtextsecure/http-resources.js new file mode 100644 index 000000000..8dc4212a1 --- /dev/null +++ b/libtextsecure/http-resources.js @@ -0,0 +1,83 @@ +/* global window, dcodeIO, textsecure, StringView */ + +// eslint-disable-next-line func-names +(function() { + let server; + + function stringToArrayBufferBase64(string) { + return dcodeIO.ByteBuffer.wrap(string, 'base64').toArrayBuffer(); + } + + const Response = function Response(options) { + this.verb = options.verb || options.type; + this.path = options.path || options.url; + this.body = options.body || options.data; + this.success = options.success; + this.error = options.error; + this.id = options.id; + + if (this.id === undefined) { + const bits = new Uint32Array(2); + window.crypto.getRandomValues(bits); + this.id = dcodeIO.Long.fromBits(bits[0], bits[1], true); + } + + if (this.body === undefined) { + this.body = null; + } + }; + + const IncomingHttpResponse = function IncomingHttpResponse(options) { + const request = new Response(options); + + this.verb = request.verb; + this.path = request.path; + this.body = request.body; + + this.respond = (status, message) => { + // Mock websocket response + window.log.info(status, message); + }; + }; + + + window.HttpResource = function HttpResource(_server, opts = {}) { + server = _server; + let { handleRequest } = opts; + if (typeof handleRequest !== 'function') { + handleRequest = request => request.respond(404, 'Not found'); + }; + + this.startPolling = async function pollServer() { + const myKeys = await textsecure.storage.protocol.getIdentityKeyPair(); + const pubKey = StringView.arrayBufferToHex(myKeys.pubKey) + let result; + try { + result = await server.retrieveMessages(pubKey); + } catch(err) { + setTimeout(() => { pollServer(); }, 5000); + return; + } + if (!result.messages) { + setTimeout(() => { pollServer(); }, 5000); + return; + } + result.messages.forEach(async message => { + const { data } = message; + const dataPlaintext = stringToArrayBufferBase64(data); + const messageBuf = textsecure.protobuf.WebSocketMessage.decode(dataPlaintext); + if (messageBuf.type === textsecure.protobuf.WebSocketMessage.Type.REQUEST) { + handleRequest( + new IncomingHttpResponse({ + verb: messageBuf.request.verb, + path: messageBuf.request.path, + body: messageBuf.request.body, + id: messageBuf.request.id, + }) + ); + } + }); + setTimeout(() => { pollServer(); }, 5000); + }; + }; +})(); diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index e462aa74e..26ec30507 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -1,12 +1,12 @@ /* global window: false */ /* global textsecure: false */ -/* global WebAPI: false */ +/* global StringView: false */ /* global libsignal: false */ -/* global WebSocketResource: false */ /* global WebSocket: false */ /* global Event: false */ /* global dcodeIO: false */ /* global _: false */ +/* global HttpResource: false */ /* global ContactBuffer: false */ /* global GroupBuffer: false */ /* global Worker: false */ @@ -147,7 +147,7 @@ MessageReceiver.arrayBufferToStringBase64 = arrayBuffer => MessageReceiver.prototype = new textsecure.EventTarget(); MessageReceiver.prototype.extend({ constructor: MessageReceiver, - async connect() { + connect() { if (this.calledClose) { return; } @@ -159,12 +159,13 @@ MessageReceiver.prototype.extend({ } this.hasConnected = true; - const myKeys = await textsecure.storage.protocol.getIdentityKeyPair(); - const result = await this.lokiserver.retrieveMessages(myKeys); - + this.hr = new HttpResource(this.lokiserver, { + handleRequest: this.handleRequest.bind(this), + }); + this.hr.startPolling(); + // TODO: Rework this socket stuff to work with online messaging return; - // TODO: Rework this socket stuff to work with online messaging if (this.socket && this.socket.readyState !== WebSocket.CLOSED) { this.socket.close(); this.wsr.close(); @@ -234,7 +235,6 @@ MessageReceiver.prototype.extend({ ); // TODO: handle properly return; - this.shutdown(); if (this.calledClose) { @@ -274,8 +274,8 @@ MessageReceiver.prototype.extend({ return; } - const promise = Promise.resolve(request.body.toArrayBuffer()) //textsecure.crypto - //.decryptWebsocketMessage(request.body, this.signalingKey) + const promise = Promise.resolve(request.body.toArrayBuffer()) // textsecure.crypto + // .decryptWebsocketMessage(request.body, this.signalingKey) .then(plaintext => { const envelope = textsecure.protobuf.Envelope.decode(plaintext); // After this point, decoding errors are not the server's diff --git a/libtextsecure/outgoing_message.js b/libtextsecure/outgoing_message.js index ca1b82180..f00fc7136 100644 --- a/libtextsecure/outgoing_message.js +++ b/libtextsecure/outgoing_message.js @@ -166,8 +166,8 @@ OutgoingMessage.prototype = { async transmitMessage(number, data, timestamp, ttl = 24 * 60 * 60) { const pubKey = number; try { - const [response] = await this.lokiserver.sendMessage(pubKey, data, ttl); - return response; + const result = await this.lokiserver.sendMessage(pubKey, data, ttl); + return result; } catch (e) { if (e.name === 'HTTPError' && (e.code !== 409 && e.code !== 410)) { // 409 and 410 should bubble and be handled by doSendMessage @@ -209,8 +209,8 @@ OutgoingMessage.prototype = { async wrapInWebsocketMessage(outgoingObject) { const messageEnvelope = new textsecure.protobuf.Envelope({ type: outgoingObject.type, - source: outgoingObject.address.getName(), - sourceDevice: outgoingObject.address.getDeviceId(), + source: outgoingObject.ourKey, + sourceDevice: outgoingObject.sourceDevice, timestamp: this.timestamp, content: outgoingObject.content, }); @@ -236,11 +236,11 @@ OutgoingMessage.prototype = { deviceIds.map(async deviceId => { const address = new libsignal.SignalProtocolAddress(number, deviceId); - const ourNumber = textsecure.storage.user.getNumber(); + const ourKey = textsecure.storage.user.getNumber(); const options = {}; // No limit on message keys if we're communicating with our other devices - if (ourNumber === number) { + if (ourKey === number) { options.messageKeysLimit = false; } @@ -270,8 +270,8 @@ OutgoingMessage.prototype = { }) .then(ciphertext => ({ type: ciphertext.type, - address, - destinationDeviceId: address.getDeviceId(), + ourKey, + sourceDevice: 1, destinationRegistrationId: ciphertext.registrationId, content: ciphertext.body, }));