From 4198f30b990881d5f99812758b2f0bef56be07b7 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Mon, 20 Jan 2020 21:20:45 -0800 Subject: [PATCH] Label errors better, make _sendToProxy use ephermal layering --- js/modules/loki_app_dot_net_api.js | 102 ++++++++++++++++++++++------- 1 file changed, 79 insertions(+), 23 deletions(-) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index f50a5e441..24b7dc1db 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -1,6 +1,6 @@ /* global log, textsecure, libloki, Signal, Whisper, Headers, ConversationController, clearTimeout, MessageController, libsignal, StringView, window, _, -dcodeIO, Buffer */ +dcodeIO, Buffer, lokiSnodeAPI */ const nodeFetch = require('node-fetch'); const { URL, URLSearchParams } = require('url'); const FormData = require('form-data'); @@ -19,6 +19,9 @@ const MESSAGE_ATTACHMENT_TYPE = 'net.app.core.oembed'; const LOKI_ATTACHMENT_TYPE = 'attachment'; const LOKI_PREVIEW_TYPE = 'preview'; +// for onion routing +const IV_LENGTH = 16; + // the core ADN class that handles all communication with a specific server class LokiAppDotNetServerAPI { constructor(ourKey, url) { @@ -108,7 +111,7 @@ class LokiAppDotNetServerAPI { // no big deal if it fails... if (res.err || !res.response || !res.response.data) { if (res.err) { - log.error(`Error ${res.err}`); + log.error(`setProfileName Error ${res.err}`); } return []; } @@ -135,7 +138,7 @@ class LokiAppDotNetServerAPI { if (res.err || !res.response || !res.response.data) { if (res.err) { - log.error(`Error ${res.err}`); + log.error(`setHomeServer Error ${res.err}`); } return []; } @@ -292,23 +295,67 @@ class LokiAppDotNetServerAPI { } async _sendToProxy(fetchOptions, endpoint, method) { + const randSnode = await lokiSnodeAPI.getRandomSnodeAddress(); + const url = `https://${randSnode.ip}:${randSnode.port}/file_proxy`; + + // make temporary key for this request/response + const ephemeralKey = libsignal.Curve.generateKeyPair(); - const rand_snode = await lokiSnodeAPI.getRandomSnodeAddress(); - const url = `https://${rand_snode.ip}:${rand_snode.port}/file_proxy`; + // some randomness + const iv = libsignal.crypto.getRandomBytes(IV_LENGTH); - const body = fetchOptions.body; + // mix server pub key with our priv key + const symKey = libsignal.Curve.calculateAgreement( + this.pubKey, // server's pubkey + ephemeralKey.privKey // our privkey + ); + + const payloadObj = { + // I think this is a stream, we may need to collect it all? + body: fetchOptions.body, // might need to b64 if binary... + endpoint, + method, + headers: fetchOptions.headers, + }; + + // convert our payload to binary buffer + const payloadData = Buffer.from( + dcodeIO.ByteBuffer.wrap(JSON.stringify(payloadObj)).toArrayBuffer() + ); + + // encrypt payloadData with symmetric Key using iv + const cipherBody = await libsignal.crypto.encrypt(symKey, payloadData, iv); + + // make final buffer for cipherText + const ivAndCiphertext = new Uint8Array( + iv.byteLength + cipherBody.byteLength + ); + // add iv + ivAndCiphertext.set(new Uint8Array(iv)); + // add ciphertext after iv position + ivAndCiphertext.set(new Uint8Array(cipherBody), iv.byteLength); + + // convert final buffer to base64 + const cipherText64 = dcodeIO.ByteBuffer.wrap(ivAndCiphertext).toString( + 'base64' + ); + const ephemeralPubKey64 = dcodeIO.ByteBuffer.wrap( + ephemeralKey.privKey + ).toString('base64'); const firstHopOptions = { method: 'POST', - body, + cipherText64, headers: { - "X-Loki-File-Server-Target": `/${endpoint}`, - "X-Loki-File-Server-Verb": method, - "X-Loki-File-Server-Headers": JSON.stringify(fetchOptions.headers), - } - } + 'X-Loki-File-Server-Target': `/loki/v1/secure_rpc`, + 'X-Loki-File-Server-Verb': 'POST', + 'X-Loki-File-Server-Headers': JSON.stringify({ + 'X-Loki-File-Server-Ephemeral-Key': ephemeralPubKey64, + }), + }, + }; - return await nodeFetch(url, firstHopOptions); + return nodeFetch(url, firstHopOptions); } // make a request to the server @@ -320,6 +367,7 @@ class LokiAppDotNetServerAPI { objBody, forceFreshToken = false, } = options; + const url = new URL(`${this.baseServerUrl}/${endpoint}`); if (params) { url.search = new URLSearchParams(params); @@ -345,14 +393,22 @@ class LokiAppDotNetServerAPI { } fetchOptions.headers = new Headers(headers); - if (window.lokiFeatureFlags.useSnodeProxy && this.baseServerUrl === "https://file.lokinet.org") { - log.info("Sending a proxy request to https://file.lokinet.org"); - result = await this._sendToProxy({...fetchOptions, headers}, endpoint, method); + if ( + window.lokiFeatureFlags.useSnodeProxy && + (this.baseServerUrl === 'https://file-dev.lokinet.org' || + this.baseServerUrl === 'https://file.lokinet.org') + ) { + log.info('Sending a proxy request to', this.baseServerUrl); + result = await this._sendToProxy( + { ...fetchOptions, headers }, + endpoint, + method + ); } else { result = await nodeFetch(url, fetchOptions || undefined); } } catch (e) { - log.info(`e ${e}`); + log.info(`serverRequest nodeFetch/_sendToProxy error: ${e}`); return { err: e, }; @@ -404,7 +460,7 @@ class LokiAppDotNetServerAPI { if (res.err || !res.response || !res.response.data) { if (res.err) { - log.error(`Error ${res.err}`); + log.error(`getUserAnnotations Error ${res.err}`); } return []; } @@ -523,7 +579,7 @@ class LokiAppDotNetServerAPI { if (res.err || !res.response || !res.response.data) { if (res.err) { - log.error(`Error ${res.err}`); + log.error(`getSubscribers Error ${res.err}`); } return []; } @@ -553,7 +609,7 @@ class LokiAppDotNetServerAPI { if (res.err || !res.response || !res.response.data) { if (res.err) { - log.error(`Error ${res.err}`); + log.error(`getUsers Error ${res.err}`); } return []; } @@ -708,7 +764,7 @@ class LokiPublicChannelAPI { if (res.err || !res.response || !res.response.data) { if (res.err) { - log.error(`Error ${res.err}`); + log.error(`banUser Error ${res.err}`); } return false; } @@ -824,7 +880,7 @@ class LokiPublicChannelAPI { ); if (updateRes.err || !updateRes.response || !updateRes.response.data) { if (updateRes.err) { - log.error(`Error ${updateRes.err}`); + log.error(`setChannelSettings Error ${updateRes.err}`); } return false; } @@ -975,7 +1031,7 @@ class LokiPublicChannelAPI { // if any problems, abort out if (res.err || !res.response) { if (res.err) { - log.error(`Error ${res.err}`); + log.error(`pollOnceForDeletions Error ${res.err}`); } break; }