From 627c4b2462e4170cb02acdbed8d28b71f9b3d799 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Thu, 23 Apr 2020 23:11:35 -0700 Subject: [PATCH 01/28] encryptForNode=>encryptForPubKey, refactor makeOnionRequest() out of sendOnionRequest, some logging style --- js/modules/loki_rpc.js | 106 +++++++++++++++++++++++++++-------------- 1 file changed, 71 insertions(+), 35 deletions(-) diff --git a/js/modules/loki_rpc.js b/js/modules/loki_rpc.js index 0734428bd..6b7aede05 100644 --- a/js/modules/loki_rpc.js +++ b/js/modules/loki_rpc.js @@ -14,16 +14,20 @@ const endpointBase = '/storage_rpc/v1'; // Request index for debugging let onionReqIdx = 0; -const encryptForNode = async (node, payload) => { +// Returns the actual ciphertext, symmetric key that will be used +// for decryption, and an ephemeral_key to send to the next hop +const encryptForPubKey = async (pubKeyAB, reqJson) => { + // Do we still need "headers"? + + const reqStr = JSON.stringify(reqJson); + const textEncoder = new TextEncoder(); - const plaintext = textEncoder.encode(payload); + const plaintext = textEncoder.encode(reqStr); const ephemeral = libloki.crypto.generateEphemeralKeyPair(); - const snPubkey = StringView.hexToArrayBuffer(node.pubkey_x25519); - const ephemeralSecret = libsignal.Curve.calculateAgreement( - snPubkey, + pubKeyAB, ephemeral.privKey ); @@ -50,45 +54,45 @@ const encryptForNode = async (node, payload) => { return { ciphertext, symmetricKey, ephemeral_key: ephemeral.pubKey }; }; -// Returns the actual ciphertext, symmetric key that will be used -// for decryption, and an ephemeral_key to send to the next hop -const encryptForDestination = async (node, payload) => { - // Do we still need "headers"? - const reqStr = JSON.stringify({ body: payload, headers: '' }); - - return encryptForNode(node, reqStr); -}; - // `ctx` holds info used by `node` to relay further -const encryptForRelay = async (node, nextNode, ctx) => { +const encryptForRelay = async (node, nextNodePubKey_ed25519_hex, ctx) => { const payload = ctx.ciphertext; + // ciphertext, symmetricKey, ephemeral_key + //console.log('encryptForRelay ctx', ctx) const reqJson = { ciphertext: dcodeIO.ByteBuffer.wrap(payload).toString('base64'), ephemeral_key: StringView.arrayBufferToHex(ctx.ephemeral_key), - destination: nextNode.pubkey_ed25519, + destination: nextNodePubKey_ed25519_hex, }; - const reqStr = JSON.stringify(reqJson); - - return encryptForNode(node, reqStr); + const snPubkey = StringView.hexToArrayBuffer(node.pubkey_x25519); + return encryptForPubKey(snPubkey, reqJson); }; const BAD_PATH = 'bad_path'; // May return false BAD_PATH, indicating that we should try a new -const sendOnionRequest = async (reqIdx, nodePath, targetNode, plaintext) => { - const ctxes = [await encryptForDestination(targetNode, plaintext)]; +// we just need the targetNode.pubkey_ed25519 for the encryption +// targetPubKey is ed25519 if snode is the target +const makeOnionRequest = async (nodePath, destCtx, targetPubKey) => { + const ctxes = [destCtx]; // from (3) 2 to 0 const firstPos = nodePath.length - 1; + // console.log('targetPubKey', targetPubKey) + // console.log('nodePath', nodePath.length, 'first', firstPos) + for (let i = firstPos; i > -1; i -= 1) { + // console.log('makeOnionRequest - encryptForRelay', i) // this nodePath points to the previous (i + 1) context + // console.log(i + 1, 'pubkey_ed25519', nodePath[i + 1] ? nodePath[i + 1].pubkey_ed25519 : null) + // console.log('node', i, 'to', i === firstPos ? targetPubKey : nodePath[i + 1].pubkey_ed25519) ctxes.push( // eslint-disable-next-line no-await-in-loop await encryptForRelay( nodePath[i], - i === firstPos ? targetNode : nodePath[i + 1], + i === firstPos ? targetPubKey : nodePath[i + 1].pubkey_ed25519, ctxes[ctxes.length - 1] ) ); @@ -99,24 +103,44 @@ const sendOnionRequest = async (reqIdx, nodePath, targetNode, plaintext) => { guardCtx.ciphertext ).toString('base64'); - const payload = { + const payloadObj = { ciphertext: ciphertextBase64, ephemeral_key: StringView.arrayBufferToHex(guardCtx.ephemeral_key), }; + // all these requests should use AesGcm + return payloadObj; +}; + +// May return false BAD_PATH, indicating that we should try a new +const sendOnionRequest = async (reqIdx, nodePath, targetNode, plaintext, options = {}) => { + if (!targetNode) { + console.trace('loki_rpc::sendOnionRequest - no targetNode given') + return {} + } + if (!targetNode.pubkey_x25519) { + console.trace('loki_rpc::sendOnionRequest - pubkey_x25519 in targetNode', targetNode) + return {} + } + const snPubkey = StringView.hexToArrayBuffer(targetNode.pubkey_x25519); + + const destCtx = await encryptForPubKey(snPubkey, { + ...options, body: plaintext, headers: '', + }); + + const payloadObj = await makeOnionRequest(nodePath, destCtx, targetNode.pubkey_ed25519); + const fetchOptions = { method: 'POST', - body: JSON.stringify(payload), + body: JSON.stringify(payloadObj), + // we are talking to a snode... + agent: snodeHttpsAgent, }; const url = `https://${nodePath[0].ip}:${nodePath[0].port}/onion_req`; - - // we only proxy to snodes... - process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; const response = await nodeFetch(url, fetchOptions); - process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1'; - return processOnionResponse(reqIdx, response, ctxes[0].symmetricKey, true); + return processOnionResponse(reqIdx, response, destCtx.symmetricKey, true); }; // Process a response as it arrives from `nodeFetch`, handling @@ -144,16 +168,19 @@ const processOnionResponse = async (reqIdx, response, sharedKey, useAesGcm) => { if (response.status !== 200) { log.warn( - `(${reqIdx}) [path] fetch unhandled error code: ${response.status}` + `(${reqIdx}) [path] lokiRpc::processOnionResponse - fetch unhandled error code: ${response.status}` ); return false; } const ciphertext = await response.text(); if (!ciphertext) { - log.warn(`(${reqIdx}) [path]: Target node return empty ciphertext`); + log.warn(`(${reqIdx}) [path] lokiRpc::processOnionResponse - Target node return empty ciphertext`); return false; } + if (reqIdx === 0) { + //console.log(`(${reqIdx}) [path] lokiRpc::processOnionResponse - ciphertext`, ciphertext) + } let plaintext; let ciphertextBuffer; @@ -163,6 +190,10 @@ const processOnionResponse = async (reqIdx, response, sharedKey, useAesGcm) => { 'base64' ).toArrayBuffer(); + if (reqIdx === 0) { + console.log(`(${reqIdx}) [path] lokiRpc::processOnionResponse - ciphertextBuffer`, StringView.arrayBufferToHex(ciphertextBuffer), 'useAesGcm', useAesGcm) + } + const decryptFn = useAesGcm ? window.libloki.crypto.DecryptGCM : window.libloki.crypto.DHDecrypt; @@ -172,9 +203,9 @@ const processOnionResponse = async (reqIdx, response, sharedKey, useAesGcm) => { const textDecoder = new TextDecoder(); plaintext = textDecoder.decode(plaintextBuffer); } catch (e) { - log.error(`(${reqIdx}) [path] decode error`); + log.error(`(${reqIdx}) [path] lokiRpc::processOnionResponse - decode error`, e.code, e.message); if (ciphertextBuffer) { - log.error(`(${reqIdx}) [path] ciphertextBuffer`, ciphertextBuffer); + log.error(`(${reqIdx}) [path] lokiRpc::processOnionResponse - ciphertextBuffer`, ciphertextBuffer); } return false; } @@ -187,13 +218,13 @@ const processOnionResponse = async (reqIdx, response, sharedKey, useAesGcm) => { const res = JSON.parse(jsonRes.body); return res; } catch (e) { - log.error(`(${reqIdx}) [path] parse error json: `, jsonRes.body); + log.error(`(${reqIdx}) [path] lokiRpc::processOnionResponse - parse error json: `, jsonRes.body); } return false; }; return jsonRes; } catch (e) { - log.error('[path] parse error', e.code, e.message, `json:`, plaintext); + log.error(`(${reqIdx}) [path] lokiRpc::processOnionResponse - parse error`, e.code, e.message, `json:`, plaintext); return false; } }; @@ -308,6 +339,7 @@ const sendToProxy = async (options = {}, targetNode, retryNumber = 0) => { // grab a fresh random one return sendToProxy(options, targetNode, pRetryNumber); } + // 502 is "Next node not found" // detect SNode is not ready (not in swarm; not done syncing) // 503 can be proxy target or destination in pre 2.0.3 @@ -669,4 +701,8 @@ const lokiRpc = ( module.exports = { lokiRpc, + makeOnionRequest, + encryptForPubKey, + encryptForRelay, + processOnionResponse, }; From 74c41ded87abcf6cae16fff43ac71ae95ea68aea Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Thu, 23 Apr 2020 23:12:26 -0700 Subject: [PATCH 02/28] sendViaOnion(), useOnionRequests feature flag support --- js/modules/loki_app_dot_net_api.js | 183 +++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index a7a17763f..773caa33f 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -7,6 +7,8 @@ const FormData = require('form-data'); const https = require('https'); const path = require('path'); +const lokiRpcUtils = require('./loki_rpc'); + // Can't be less than 1200 if we have unauth'd requests const PUBLICCHAT_MSG_POLL_EVERY = 1.5 * 1000; // 1.5s const PUBLICCHAT_CHAN_POLL_EVERY = 20 * 1000; // 20s @@ -34,6 +36,163 @@ const snodeHttpsAgent = new https.Agent({ const timeoutDelay = ms => new Promise(resolve => setTimeout(resolve, ms)); +const sendViaOnion = async ( + srvPubKey, + url, + pFetchOptions, + options = {} +) => { + if (!srvPubKey) { + log.error( + 'loki_app_dot_net:::sendViaOnion - called without a server public key' + ); + return {}; + } + + const fetchOptions = pFetchOptions; // make lint happy + // safety issue with file server, just safer to have this + if (fetchOptions.headers === undefined) { + fetchOptions.headers = {}; + } + + const payloadObj = { + method: fetchOptions.method, + headers: {...fetchOptions.headers, bob: "kob" }, + }; + //console.log('payloadObj', payloadObj) + //if (fetchOptions.body === undefined) fetchOptions.body = ''; + payloadObj.body = fetchOptions.body; // might need to b64 if binary... + //console.log('body', payloadObj.body) + console.log('loki_app_dot_net:::sendViaOnion - payloadObj', payloadObj) + + // from https://github.com/sindresorhus/is-stream/blob/master/index.js + let fileUpload = false; + if ( + payloadObj.body && + typeof payloadObj.body === 'object' && + typeof payloadObj.body.pipe === 'function' + ) { + const fData = payloadObj.body.getBuffer(); + const fHeaders = payloadObj.body.getHeaders(); + // update headers for boundary + payloadObj.headers = { ...payloadObj.headers, ...fHeaders }; + // update body with base64 chunk + payloadObj.body = { + fileUpload: fData.toString('base64'), + }; + fileUpload = true; + } + + const pathNodes = await lokiSnodeAPI.getOnionPath(); + if (!pathNodes || !pathNodes.length) { + log.warn( + 'loki_app_dot_net:::sendViaOnion - failing, no path available' + ); + return {}; + } + + //console.log('loki_app_dot_net:::sendViaOnion - ourPubKey', StringView.arrayBufferToHex(srvPubKey).substr(0,32), '...', StringView.arrayBufferToHex(srvPubKey).substr(32)) + //console.log('loki_app_dot_net:::sendViaOnion - pathNodes', pathNodes) + // pathNodes = [''] + const guardUrl = `https://${pathNodes[0].ip}:${pathNodes[0].port}/onion_req`; + // first parameter takes an arrayBuffer + const destCtx = await lokiRpcUtils.encryptForPubKey(srvPubKey, payloadObj); + + const tPayload = destCtx.ciphertext; + const reqJson = { + ciphertext: dcodeIO.ByteBuffer.wrap(tPayload).toString('base64'), + ephemeral_key: StringView.arrayBufferToHex(destCtx), + }; + const reqStr = JSON.stringify(reqJson); + + const snPubkey = StringView.hexToArrayBuffer(pathNodes[0].pubkey_x25519); + const guardCtx = await lokiRpcUtils.encryptForPubKey(snPubkey, reqStr); + //const guardCtx = await lokiRpcUtils.encryptForRelay(pathNodes[0], StringView.arrayBufferToHex(srvPubKey), destCtx); + // we don't want a destination so don't need a relay at all + // const guardPayloadObj = await lokiRpcUtils.makeOnionRequest(pathNodes, destCtx, StringView.arrayBufferToHex(srvPubKey)); + + //const guardCtx = destCtx; + + const ciphertextBase64 = dcodeIO.ByteBuffer.wrap( + guardCtx.ciphertext + ).toString('base64'); + + const guardPayloadObj = { + ciphertext: ciphertextBase64, + ephemeral_key: StringView.arrayBufferToHex(guardCtx.ephemeral_key), + host: url.host, + target: '/loki/v1/lsrpc', + }; + + const firstHopOptions = { + method: 'POST', + body: JSON.stringify(guardPayloadObj), + // we are talking to a snode... + agent: snodeHttpsAgent, + }; + const encryptedResult = await nodeFetch(guardUrl, firstHopOptions); + // weird this doesn't need NODE_TLS_REJECT_UNAUTHORIZED = '0' + + const result = await lokiRpcUtils.processOnionResponse(0, encryptedResult, destCtx.symmetricKey, true); + console.log('result', result) + let response = {}; + let txtResponse = ''; + +/* + const txtResponse = await result.text(); + if (txtResponse.match(/^Service node is not ready: not in any swarm/i)) { + // mark snode bad + const randomPoolRemainingCount = lokiSnodeAPI.markRandomNodeUnreachable( + randSnode + ); + log.warn( + `loki_app_dot_net:::sendViaOnion - Marking random snode bad, internet address ${ + randSnode.ip + }:${ + randSnode.port + }. ${randomPoolRemainingCount} snodes remaining in randomPool` + ); + // retry (hopefully with new snode) + // FIXME: max number of retries... + return sendViaOnion(srvPubKey, url, fetchOptions, options); + } + + let response = {}; + try { + // it's no longer JSON + response = txtResponse; + } catch (e) { + log.warn( + `loki_app_dot_net:::sendViaOnion - Could not parse outer JSON [${txtResponse}]`, + url, + ); + } + + // convert base64 in response to binary + const ivAndCiphertextResponse = dcodeIO.ByteBuffer.wrap( + response, + 'base64' + ).toArrayBuffer(); + const decrypted = await libloki.crypto.DHDecrypt( + symKey, + ivAndCiphertextResponse + ); + const textDecoder = new TextDecoder(); + const respStr = textDecoder.decode(decrypted); + + // replace response + try { + response = options.textResponse ? respStr : JSON.parse(respStr); + } catch (e) { + log.warn( + `loki_app_dot_net:::sendViaOnion - Could not parse inner JSON [${respStr}]`, + url, + ); + } +*/ + return { result, txtResponse, response }; +}; + const sendToProxy = async ( srvPubKey, endpoint, @@ -256,6 +415,22 @@ const serverRequest = async (endpoint, options = {}) => { try { const host = url.host.toLowerCase(); // log.info('host', host, FILESERVER_HOSTS); + if ( + window.lokiFeatureFlags.useOnionRequests && + FILESERVER_HOSTS.includes(host) + ) { + mode = 'sendViaOnion'; + // url.search automatically includes the ? part + // const search = url.search || ''; + // strip first slash + // const endpointWithQS = `${url.pathname}${search}`.replace(/^\//, ''); + ({ response, txtResponse, result } = await sendViaOnion( + srvPubKey, + url, + fetchOptions, + options + )); + } else if ( window.lokiFeatureFlags.useSnodeProxy && FILESERVER_HOSTS.includes(host) @@ -317,6 +492,14 @@ const serverRequest = async (endpoint, options = {}) => { err: e, }; } + + if (!result) { + return { + err: 'noResult', + response, + }; + } + // if it's a response style with a meta if (result.status !== 200) { if (!forceFreshToken && (!response.meta || response.meta.code === 401)) { From ae210c4312930bd4b156ec350e58026ca95a14d5 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Sun, 17 May 2020 20:07:24 -0700 Subject: [PATCH 03/28] urlPubkeyMap to allow LOKIFOUNDATION_DEVFILESERVER_PUBKEY to work seemlessly (while hardcoding), set this._server.pubKeyHex --- js/modules/loki_file_server_api.js | 54 ++++++++++++++++++------------ 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/js/modules/loki_file_server_api.js b/js/modules/loki_file_server_api.js index 13ca54800..864e59ad6 100644 --- a/js/modules/loki_file_server_api.js +++ b/js/modules/loki_file_server_api.js @@ -1,4 +1,4 @@ -/* global log, libloki, process, window */ +/* global log, libloki, process, window, StringView */ /* global storage: false */ /* global Signal: false */ /* global log: false */ @@ -8,18 +8,41 @@ const LokiAppDotNetAPI = require('./loki_app_dot_net_api'); const DEVICE_MAPPING_USER_ANNOTATION_TYPE = 'network.loki.messenger.devicemapping'; -// const LOKIFOUNDATION_DEVFILESERVER_PUBKEY = -// 'BSZiMVxOco/b3sYfaeyiMWv/JnqokxGXkHoclEx8TmZ6'; +const LOKIFOUNDATION_DEVFILESERVER_PUBKEY = + 'BSZiMVxOco/b3sYfaeyiMWv/JnqokxGXkHoclEx8TmZ6'; const LOKIFOUNDATION_FILESERVER_PUBKEY = 'BWJQnVm97sQE3Q1InB4Vuo+U/T1hmwHBv0ipkiv8tzEc'; +const urlPubkeyMap = { + 'https://file-dev.getsession.org': LOKIFOUNDATION_DEVFILESERVER_PUBKEY, + 'https://file-dev.lokinet.org': LOKIFOUNDATION_DEVFILESERVER_PUBKEY, + 'https://file.getsession.org': LOKIFOUNDATION_FILESERVER_PUBKEY, + 'https://file.lokinet.org': LOKIFOUNDATION_FILESERVER_PUBKEY, +}; // can have multiple of these instances as each user can have a // different home server class LokiFileServerInstance { constructor(ourKey) { this.ourKey = ourKey; + } + + // FIXME: this is not file-server specific + // and is currently called by LokiAppDotNetAPI. + // LokiAppDotNetAPI (base) should not know about LokiFileServer. + async establishConnection(serverUrl, options) { + // why don't we extend this? + if (process.env.USE_STUBBED_NETWORK) { + // eslint-disable-next-line global-require + const StubAppDotNetAPI = require('../../integration_test/stubs/stub_app_dot_net_api.js'); + this._server = new StubAppDotNetAPI(this.ourKey, serverUrl); + } else { + this._server = new LokiAppDotNetAPI(this.ourKey, serverUrl); + } // do we have their pubkey locally? + // FIXME: this._server won't be set yet... + // can't really do this for the file server because we'll need the key + // before we can communicate with lsrpc /* // get remote pubKey this._server.serverRequest('loki/v1/public_key').then(keyRes => { @@ -41,9 +64,11 @@ class LokiFileServerInstance { }); */ // Hard coded - this.pubKey = window.Signal.Crypto.base64ToArrayBuffer( - LOKIFOUNDATION_FILESERVER_PUBKEY - ); + if (urlPubkeyMap) { + this.pubKey = window.Signal.Crypto.base64ToArrayBuffer( + urlPubkeyMap[serverUrl] + ); + } if (this.pubKey.byteLength && this.pubKey.byteLength !== 33) { log.error( 'FILESERVER PUBKEY is invalid, length:', @@ -51,23 +76,10 @@ class LokiFileServerInstance { ); process.exit(1); } - } - - // FIXME: this is not file-server specific - // and is currently called by LokiAppDotNetAPI. - // LokiAppDotNetAPI (base) should not know about LokiFileServer. - async establishConnection(serverUrl, options) { - // why don't we extend this? - if (process.env.USE_STUBBED_NETWORK) { - // eslint-disable-next-line global-require - const StubAppDotNetAPI = require('../../integration_test/stubs/stub_app_dot_net_api.js'); - this._server = new StubAppDotNetAPI(this.ourKey, serverUrl); - } else { - this._server = new LokiAppDotNetAPI(this.ourKey, serverUrl); - } - // configure proxy + // configure proxy/onion request this._server.pubKey = this.pubKey; + this._server.pubKeyHex = StringView.arrayBufferToHex(this.pubKey); if (options !== undefined && options.skipToken) { return; From 2a889f5d99d9506e37fdd183a0f2f0f5e146b2cf Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Sun, 17 May 2020 20:09:50 -0700 Subject: [PATCH 04/28] sendOnionRequestLsrpcDest() refactor, log WRONG POW, makeGuardPayload(), makeOnionRequest(), sendOnionRequest => sendOnionRequestSnodeDest --- js/modules/loki_rpc.js | 417 ++++++++++++++++++++++++++++++++++------- 1 file changed, 347 insertions(+), 70 deletions(-) diff --git a/js/modules/loki_rpc.js b/js/modules/loki_rpc.js index d2b8f69fd..ab84323f4 100644 --- a/js/modules/loki_rpc.js +++ b/js/modules/loki_rpc.js @@ -1,5 +1,5 @@ /* global log, libloki, textsecure, getStoragePubKey, lokiSnodeAPI, StringView, - libsignal, window, TextDecoder, TextEncoder, dcodeIO, process */ + libsignal, window, TextDecoder, TextEncoder, dcodeIO, process, crypto */ const nodeFetch = require('node-fetch'); const https = require('https'); @@ -16,138 +16,364 @@ let onionReqIdx = 0; // Returns the actual ciphertext, symmetric key that will be used // for decryption, and an ephemeral_key to send to the next hop -const encryptForPubKey = async (pubKeyAB, reqJson) => { +const encryptForPubKey = async (pubKeyX25519AB, reqObj, debug = false) => { // Do we still need "headers"? - - const reqStr = JSON.stringify(reqJson); + const reqStr = JSON.stringify(reqObj); const textEncoder = new TextEncoder(); const plaintext = textEncoder.encode(reqStr); - const ephemeral = libloki.crypto.generateEphemeralKeyPair(); + const ephemeral = await libloki.crypto.generateEphemeralKeyPair(); + if (debug) { + log.debug( + 'encryptForPubKey', + debug, + '- pubKeyX25519AB', + StringView.arrayBufferToHex(pubKeyX25519AB) + ); + log.debug( + 'encryptForPubKey', + debug, + '- ephermalPriv', + StringView.arrayBufferToHex(ephemeral.privKey) + ); + log.debug( + 'encryptForPubKey', + debug, + '- ephermalPub', + StringView.arrayBufferToHex(ephemeral.pubKey) + ); + } const ephemeralSecret = libsignal.Curve.calculateAgreement( - pubKeyAB, + pubKeyX25519AB, ephemeral.privKey ); + if (debug) { + log.debug( + 'encryptForPubKey', + debug, + '- ephemeralSecret', + StringView.arrayBufferToHex(ephemeralSecret) + ); + } - const salt = window.Signal.Crypto.bytesFromString('LOKI'); + // FIXME: window.libloki.crypto.deriveSymmetricKey refactor + const salt = window.Signal.Crypto.bytesFromString('LOKI'); // ArrayBuffer (object) + if (debug) { + log.debug( + 'encryptForPubKey', + debug, + '- salt', + StringView.arrayBufferToHex(salt) + ); + } const key = await crypto.subtle.importKey( 'raw', salt, { name: 'HMAC', hash: { name: 'SHA-256' } }, - false, + true, ['sign'] ); + + // CrsptoKey (object) + const exportKey = await crypto.subtle.exportKey('raw', key); + // ArrayBuffer (object) + if (debug) { + log.error( + 'encryptForPubKey', + debug, + '- key', + StringView.arrayBufferToHex(exportKey) + ); + } + const symmetricKey = await crypto.subtle.sign( { name: 'HMAC', hash: 'SHA-256' }, key, ephemeralSecret ); + // ArrayBuffer (object) + if (debug) { + log.debug( + 'encryptForPubKey', + debug, + '- symmetricKey', + StringView.arrayBufferToHex(symmetricKey) + ); + } const ciphertext = await window.libloki.crypto.EncryptGCM( symmetricKey, - plaintext + plaintext, + debug, + ephemeral.pubKey ); + // looks textEncoder'd... Uint8Array + if (debug) { + log.debug( + 'encryptForPubKey', + debug, + '- ciphertext', + StringView.arrayBufferToHex(ciphertext), + ciphertext + ); + } + // ephemeral_key => ephemeralKey? return { ciphertext, symmetricKey, ephemeral_key: ephemeral.pubKey }; }; // `ctx` holds info used by `node` to relay further -const encryptForRelay = async (node, nextNodePubKey_ed25519_hex, ctx) => { +// destination needs ed25519_hex +const encryptForRelay = async (relayX25519AB, destination, ctx) => { + // cyx contains: ciphertext, symmetricKey, ephemeral_key const payload = ctx.ciphertext; - // ciphertext, symmetricKey, ephemeral_key - //console.log('encryptForRelay ctx', ctx) - const reqJson = { + if (!destination.host && !destination.destination) { + log.warn(`loki_rpc::encryptForRelay - no destination`, destination); + } + + const reqObj = { + ...destination, ciphertext: dcodeIO.ByteBuffer.wrap(payload).toString('base64'), ephemeral_key: StringView.arrayBufferToHex(ctx.ephemeral_key), - destination: nextNodePubKey_ed25519_hex, - //ephemeral_key: StringView.arrayBufferToHex(ctx.ephemeralKey), - //destination: nextNode.pubkey_ed25519, }; - const snPubkey = StringView.hexToArrayBuffer(node.pubkey_x25519); - return encryptForPubKey(snPubkey, reqJson); + return encryptForPubKey(relayX25519AB, reqObj); }; -const BAD_PATH = 'bad_path'; +const makeGuardPayload = guardCtx => { + const ciphertextBase64 = dcodeIO.ByteBuffer.wrap( + guardCtx.ciphertext + ).toString('base64'); + + const guardPayloadObj = { + ciphertext: ciphertextBase64, + ephemeral_key: StringView.arrayBufferToHex(guardCtx.ephemeral_key), + }; + return guardPayloadObj; +}; -// May return false BAD_PATH, indicating that we should try a new // we just need the targetNode.pubkey_ed25519 for the encryption // targetPubKey is ed25519 if snode is the target -const makeOnionRequest = async (nodePath, destCtx, targetPubKey) => { +const makeOnionRequest = async ( + nodePath, + destCtx, + targetED25519Hex, + finalRelayOptions = false, + id = '' +) => { const ctxes = [destCtx]; // from (3) 2 to 0 const firstPos = nodePath.length - 1; - // console.log('targetPubKey', targetPubKey) + // console.log('targetED25519Hex', targetED25519Hex) // console.log('nodePath', nodePath.length, 'first', firstPos) for (let i = firstPos; i > -1; i -= 1) { - // console.log('makeOnionRequest - encryptForRelay', i) - // this nodePath points to the previous (i + 1) context - // console.log(i + 1, 'pubkey_ed25519', nodePath[i + 1] ? nodePath[i + 1].pubkey_ed25519 : null) - // console.log('node', i, 'to', i === firstPos ? targetPubKey : nodePath[i + 1].pubkey_ed25519) - ctxes.push( - // eslint-disable-next-line no-await-in-loop - await encryptForRelay( - nodePath[i], - i === firstPos ? targetPubKey : nodePath[i + 1].pubkey_ed25519, - ctxes[ctxes.length - 1] - ) + let dest; + const relayingToFinalDestination = i === 0; // if last position + + if (relayingToFinalDestination && finalRelayOptions) { + dest = { + host: finalRelayOptions.host, + target: '/loki/v1/lsrpc', + method: 'POST', + }; + log.info( + `loki_rpc:::makeOnionRequest ${id} - lsrpc destination set`, + dest + ); + } else { + // set x25519 if destination snode + let pubkeyHex = targetED25519Hex; // relayingToFinalDestination + // or ed25519 snode destination + if (!relayingToFinalDestination) { + pubkeyHex = nodePath[i + 1].pubkey_ed25519; + if (!pubkeyHex) { + log.error( + `loki_rpc:::makeOnionRequest ${id} - no ed25519 for`, + nodePath[i + 1], + 'path node', + i + 1 + ); + } + } + // destination takes a hex key + dest = { + destination: pubkeyHex, + }; + } + // FIXME: we should store this inside snode pool + const relayX25519AB = StringView.hexToArrayBuffer( + nodePath[i].pubkey_x25519 ); + try { + ctxes.push( + // eslint-disable-next-line no-await-in-loop + await encryptForRelay(relayX25519AB, dest, ctxes[ctxes.length - 1]) + ); + } catch (e) { + log.error( + `loki_rpc:::makeOnionRequest ${id} - encryptForRelay failure`, + e.code, + e.message + ); + throw e; + } } const guardCtx = ctxes[ctxes.length - 1]; // last ctx - const ciphertextBase64 = dcodeIO.ByteBuffer.wrap( - guardCtx.ciphertext - ).toString('base64'); - - const payloadObj = { - ciphertext: ciphertextBase64, - ephemeral_key: StringView.arrayBufferToHex(guardCtx.ephemeralKey), - }; + const payloadObj = makeGuardPayload(guardCtx); // all these requests should use AesGcm return payloadObj; }; -// May return false BAD_PATH, indicating that we should try a new -const sendOnionRequest = async (reqIdx, nodePath, targetNode, plaintext, options = {}) => { - if (!targetNode) { - console.trace('loki_rpc::sendOnionRequest - no targetNode given') - return {} +// finalDestOptions is an object +// FIXME: internally track reqIdx, not externally +const sendOnionRequest = async ( + reqIdx, + nodePath, + destX25519Any, + finalDestOptions, + finalRelayOptions = false, + lsrpcIdx +) => { + if (!destX25519Any) { + log.error('loki_rpc::sendOnionRequest - no destX25519Any given'); + return {}; } - if (!targetNode.pubkey_x25519) { - console.trace('loki_rpc::sendOnionRequest - pubkey_x25519 in targetNode', targetNode) - return {} + + // loki-storage may need this to function correctly + // but ADN calls will not always have a body + /* + if (!finalDestOptions.body) { + finalDestOptions.body = ''; } - const snPubkey = StringView.hexToArrayBuffer(targetNode.pubkey_x25519); + */ - const destCtx = await encryptForPubKey(snPubkey, { - ...options, body: plaintext, headers: '', - }); + let id = ''; + if (lsrpcIdx !== undefined) { + id += `${lsrpcIdx}=>`; + } + if (reqIdx !== undefined) { + id += `${reqIdx}`; + } - const payloadObj = await makeOnionRequest(nodePath, destCtx, targetNode.pubkey_ed25519); + // get destination pubkey in array buffer format + let destX25519AB = destX25519Any; + if (typeof destX25519AB === 'string') { + destX25519AB = StringView.hexToArrayBuffer(destX25519Any); + } - const fetchOptions = { + // safely build destination + let targetEd25519hex; + if (finalDestOptions) { + if (finalDestOptions.destination_ed25519_hex) { + // snode destination + targetEd25519hex = finalDestOptions.destination_ed25519_hex; + // eslint-disable-next-line no-param-reassign + delete finalDestOptions.destination_ed25519_hex; + } + // else it's lsrpc... + } else { + // eslint-disable-next-line no-param-reassign + finalDestOptions = {}; + log.warn(`loki_rpc::sendOnionRequest ${id} - no finalDestOptions`); + return {}; + } + + const options = finalDestOptions; // lint + // do we need this? + if (options.headers === undefined) { + options.headers = ''; + } + + let destCtx; + try { + destCtx = await encryptForPubKey(destX25519AB, options); + } catch (e) { + const hex = StringView.arrayBufferToHex(destX25519AB); + log.error( + `loki_rpc::sendOnionRequest ${id} - encryptForPubKey failure [`, + e.code, + e.message, + '] destination X25519', + hex.substr(0, 32), + '...', + hex.substr(32), + 'options', + options + ); + throw e; + } + + const payloadObj = await makeOnionRequest( + nodePath, + destCtx, + targetEd25519hex, + finalRelayOptions, + id + ); + + const guardFetchOptions = { method: 'POST', body: JSON.stringify(payloadObj), // we are talking to a snode... agent: snodeHttpsAgent, }; - const url = `https://${nodePath[0].ip}:${nodePath[0].port}/onion_req`; - const response = await nodeFetch(url, fetchOptions); + const guardUrl = `https://${nodePath[0].ip}:${nodePath[0].port}/onion_req`; + const response = await nodeFetch(guardUrl, guardFetchOptions); return processOnionResponse(reqIdx, response, destCtx.symmetricKey, true); }; +const sendOnionRequestSnodeDest = async ( + reqIdx, + nodePath, + targetNode, + plaintext +) => + sendOnionRequest(reqIdx, nodePath, targetNode.pubkey_x25519, { + destination_ed25519_hex: targetNode.pubkey_ed25519, + body: plaintext, + }); + +// need relay node's pubkey_x25519_hex +// always the same target: /loki/v1/lsrpc +const sendOnionRequestLsrpcDest = async ( + reqIdx, + nodePath, + destX25519Any, + host, + payloadObj, + lsrpcIdx = 0 +) => + sendOnionRequest( + reqIdx, + nodePath, + destX25519Any, + payloadObj, + { host }, + lsrpcIdx + ); + +const BAD_PATH = 'bad_path'; + // Process a response as it arrives from `nodeFetch`, handling // http errors and attempting to decrypt the body with `sharedKey` -const processOnionResponse = async (reqIdx, response, sharedKey, useAesGcm) => { +// May return false BAD_PATH, indicating that we should try a new path. +const processOnionResponse = async ( + reqIdx, + response, + sharedKey, + useAesGcm, + debug +) => { // FIXME: 401/500 handling? // detect SNode is not ready (not in swarm; not done syncing) @@ -170,18 +396,22 @@ const processOnionResponse = async (reqIdx, response, sharedKey, useAesGcm) => { if (response.status !== 200) { log.warn( - `(${reqIdx}) [path] lokiRpc::processOnionResponse - fetch unhandled error code: ${response.status}` + `(${reqIdx}) [path] lokiRpc::processOnionResponse - fetch unhandled error code: ${ + response.status + }` ); return false; } const ciphertext = await response.text(); if (!ciphertext) { - log.warn(`(${reqIdx}) [path] lokiRpc::processOnionResponse - Target node return empty ciphertext`); + log.warn( + `(${reqIdx}) [path] lokiRpc::processOnionResponse - Target node return empty ciphertext` + ); return false; } - if (reqIdx === 0) { - //console.log(`(${reqIdx}) [path] lokiRpc::processOnionResponse - ciphertext`, ciphertext) + if (debug) { + // log.debug(`(${reqIdx}) [path] lokiRpc::processOnionResponse - ciphertext`, ciphertext) } let plaintext; @@ -192,25 +422,61 @@ const processOnionResponse = async (reqIdx, response, sharedKey, useAesGcm) => { 'base64' ).toArrayBuffer(); - if (reqIdx === 0) { - console.log(`(${reqIdx}) [path] lokiRpc::processOnionResponse - ciphertextBuffer`, StringView.arrayBufferToHex(ciphertextBuffer), 'useAesGcm', useAesGcm) + if (debug) { + log.debug( + `(${reqIdx}) [path] lokiRpc::processOnionResponse - ciphertextBuffer`, + StringView.arrayBufferToHex(ciphertextBuffer), + 'useAesGcm', + useAesGcm + ); } const decryptFn = useAesGcm ? window.libloki.crypto.DecryptGCM : window.libloki.crypto.DHDecrypt; - const plaintextBuffer = await decryptFn(sharedKey, ciphertextBuffer); + const plaintextBuffer = await decryptFn(sharedKey, ciphertextBuffer, debug); + if (debug) { + log.debug( + 'lokiRpc::processOnionResponse - plaintextBuffer', + plaintextBuffer.toString() + ); + } const textDecoder = new TextDecoder(); plaintext = textDecoder.decode(plaintextBuffer); } catch (e) { - log.error(`(${reqIdx}) [path] lokiRpc::processOnionResponse - decode error`, e.code, e.message); + log.error( + `(${reqIdx}) [path] lokiRpc::processOnionResponse - decode error`, + e.code, + e.message + ); + log.error( + `(${reqIdx}) [path] lokiRpc::processOnionResponse - symKey`, + StringView.arrayBufferToHex(sharedKey) + ); if (ciphertextBuffer) { - log.error(`(${reqIdx}) [path] lokiRpc::processOnionResponse - ciphertextBuffer`, ciphertextBuffer); + log.error( + `(${reqIdx}) [path] lokiRpc::processOnionResponse - ciphertextBuffer`, + StringView.arrayBufferToHex(ciphertextBuffer) + ); } return false; } + /* + if (!plaintext) { + log.debug('Trying again with', useAesGcm?'gcm':'dh') + try { + const plaintextBuffer2 = await decryptFn(sharedKey, ciphertextBuffer, true); + log.info(`(${reqIdx}) [path] lokiRpc::processOnionResponse - plaintextBufferHex`, StringView.arrayBufferToHex(plaintextBuffer2)); + } catch(e) { + } + } + */ + + if (debug) { + log.debug('lokiRpc::processOnionResponse - plaintext', plaintext); + } try { const jsonRes = JSON.parse(plaintext); @@ -220,13 +486,22 @@ const processOnionResponse = async (reqIdx, response, sharedKey, useAesGcm) => { const res = JSON.parse(jsonRes.body); return res; } catch (e) { - log.error(`(${reqIdx}) [path] lokiRpc::processOnionResponse - parse error json: `, jsonRes.body); + log.error( + `(${reqIdx}) [path] lokiRpc::processOnionResponse - parse error inner json: `, + jsonRes.body + ); } return false; }; return jsonRes; } catch (e) { - log.error(`(${reqIdx}) [path] lokiRpc::processOnionResponse - parse error`, e.code, e.message, `json:`, plaintext); + log.error( + `(${reqIdx}) [path] lokiRpc::processOnionResponse - parse error outer json`, + e.code, + e.message, + `json:`, + plaintext + ); return false; } }; @@ -523,6 +798,7 @@ const lokiFetch = async (url, options = {}, targetNode = null) => { // Wrong PoW difficulty if (response.status === 432) { const result = await response.json(); + log.error('WRONG POW', result); throw new textsecure.WrongDifficultyError(result.difficulty); } @@ -547,7 +823,7 @@ const lokiFetch = async (url, options = {}, targetNode = null) => { onionReqIdx += 1; // eslint-disable-next-line no-await-in-loop - const result = await sendOnionRequest( + const result = await sendOnionRequestSnodeDest( thisIdx, path, targetNode, @@ -707,4 +983,5 @@ module.exports = { encryptForPubKey, encryptForRelay, processOnionResponse, + sendOnionRequestLsrpcDest, }; From d4011aaf6d34b66302a0996d3556e81e3f1299b1 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Sun, 17 May 2020 20:11:27 -0700 Subject: [PATCH 05/28] add adnOnionRequestCounter for sendViaOnion, use lokiRpcUtils.sendOnionRequestLsrpcDest --- js/modules/loki_app_dot_net_api.js | 183 ++++++++++++----------------- 1 file changed, 77 insertions(+), 106 deletions(-) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index 8d6e2ff59..defb14ebd 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -36,12 +36,8 @@ const snodeHttpsAgent = new https.Agent({ const timeoutDelay = ms => new Promise(resolve => setTimeout(resolve, ms)); -const sendViaOnion = async ( - srvPubKey, - url, - pFetchOptions, - options = {} -) => { +let adnOnionRequestCounter = 0; +const sendViaOnion = async (srvPubKey, url, pFetchOptions, options = {}) => { if (!srvPubKey) { log.error( 'loki_app_dot_net:::sendViaOnion - called without a server public key' @@ -49,7 +45,19 @@ const sendViaOnion = async ( return {}; } + // set retry count + let adnOnionRequestCount; + if (options.retry === undefined) { + // eslint-disable-next-line no-param-reassign + options.retry = 0; + adnOnionRequestCounter += 1; // increment counter + adnOnionRequestCount = 0 + adnOnionRequestCounter; // copy value + } else { + adnOnionRequestCount = options.counter; + } + const fetchOptions = pFetchOptions; // make lint happy + // safety issue with file server, just safer to have this if (fetchOptions.headers === undefined) { fetchOptions.headers = {}; @@ -57,16 +65,16 @@ const sendViaOnion = async ( const payloadObj = { method: fetchOptions.method, - headers: {...fetchOptions.headers, bob: "kob" }, + body: fetchOptions.body, + // no initial / + endpoint: url.pathname.replace(/^\//, ''), + headers: { ...fetchOptions.headers }, }; - //console.log('payloadObj', payloadObj) - //if (fetchOptions.body === undefined) fetchOptions.body = ''; - payloadObj.body = fetchOptions.body; // might need to b64 if binary... - //console.log('body', payloadObj.body) - console.log('loki_app_dot_net:::sendViaOnion - payloadObj', payloadObj) + if (url.search) { + payloadObj.endpoint += `?${url.search}`; + } // from https://github.com/sindresorhus/is-stream/blob/master/index.js - let fileUpload = false; if ( payloadObj.body && typeof payloadObj.body === 'object' && @@ -80,116 +88,76 @@ const sendViaOnion = async ( payloadObj.body = { fileUpload: fData.toString('base64'), }; - fileUpload = true; } const pathNodes = await lokiSnodeAPI.getOnionPath(); if (!pathNodes || !pathNodes.length) { log.warn( - 'loki_app_dot_net:::sendViaOnion - failing, no path available' + `loki_app_dot_net:::sendViaOnion #${adnOnionRequestCount} - failing, no path available` ); return {}; } - //console.log('loki_app_dot_net:::sendViaOnion - ourPubKey', StringView.arrayBufferToHex(srvPubKey).substr(0,32), '...', StringView.arrayBufferToHex(srvPubKey).substr(32)) - //console.log('loki_app_dot_net:::sendViaOnion - pathNodes', pathNodes) - // pathNodes = [''] - const guardUrl = `https://${pathNodes[0].ip}:${pathNodes[0].port}/onion_req`; - // first parameter takes an arrayBuffer - const destCtx = await lokiRpcUtils.encryptForPubKey(srvPubKey, payloadObj); - - const tPayload = destCtx.ciphertext; - const reqJson = { - ciphertext: dcodeIO.ByteBuffer.wrap(tPayload).toString('base64'), - ephemeral_key: StringView.arrayBufferToHex(destCtx), - }; - const reqStr = JSON.stringify(reqJson); - - const snPubkey = StringView.hexToArrayBuffer(pathNodes[0].pubkey_x25519); - const guardCtx = await lokiRpcUtils.encryptForPubKey(snPubkey, reqStr); - //const guardCtx = await lokiRpcUtils.encryptForRelay(pathNodes[0], StringView.arrayBufferToHex(srvPubKey), destCtx); - // we don't want a destination so don't need a relay at all - // const guardPayloadObj = await lokiRpcUtils.makeOnionRequest(pathNodes, destCtx, StringView.arrayBufferToHex(srvPubKey)); - - //const guardCtx = destCtx; - - const ciphertextBase64 = dcodeIO.ByteBuffer.wrap( - guardCtx.ciphertext - ).toString('base64'); - - const guardPayloadObj = { - ciphertext: ciphertextBase64, - ephemeral_key: StringView.arrayBufferToHex(guardCtx.ephemeral_key), - host: url.host, - target: '/loki/v1/lsrpc', - }; - - const firstHopOptions = { - method: 'POST', - body: JSON.stringify(guardPayloadObj), - // we are talking to a snode... - agent: snodeHttpsAgent, - }; - const encryptedResult = await nodeFetch(guardUrl, firstHopOptions); - // weird this doesn't need NODE_TLS_REJECT_UNAUTHORIZED = '0' - - const result = await lokiRpcUtils.processOnionResponse(0, encryptedResult, destCtx.symmetricKey, true); - console.log('result', result) - let response = {}; - let txtResponse = ''; - -/* - const txtResponse = await result.text(); - if (txtResponse.match(/^Service node is not ready: not in any swarm/i)) { - // mark snode bad - const randomPoolRemainingCount = lokiSnodeAPI.markRandomNodeUnreachable( - randSnode + // do the request + let result; + try { + result = await lokiRpcUtils.sendOnionRequestLsrpcDest( + 0, + pathNodes, + srvPubKey, + url.host, + payloadObj, + adnOnionRequestCount ); - log.warn( - `loki_app_dot_net:::sendViaOnion - Marking random snode bad, internet address ${ - randSnode.ip - }:${ - randSnode.port - }. ${randomPoolRemainingCount} snodes remaining in randomPool` + } catch (e) { + log.error( + 'loki_app_dot_net:::sendViaOnion - lokiRpcUtils error', + e.code, + e.message ); - // retry (hopefully with new snode) - // FIXME: max number of retries... - return sendViaOnion(srvPubKey, url, fetchOptions, options); + return {}; } - let response = {}; - try { - // it's no longer JSON - response = txtResponse; - } catch (e) { - log.warn( - `loki_app_dot_net:::sendViaOnion - Could not parse outer JSON [${txtResponse}]`, - url, + // handle error/retries + if (!result.status) { + log.error( + `loki_app_dot_net:::sendViaOnion #${adnOnionRequestCount} - Retry #${ + options.retry + } Couldnt handle onion request, retrying`, + payloadObj ); + // eslint-disable-next-line no-param-reassign + options.retry += 1; + // eslint-disable-next-line no-param-reassign + options.counter = adnOnionRequestCount; + return sendViaOnion(srvPubKey, url, pFetchOptions, options); } - // convert base64 in response to binary - const ivAndCiphertextResponse = dcodeIO.ByteBuffer.wrap( - response, - 'base64' - ).toArrayBuffer(); - const decrypted = await libloki.crypto.DHDecrypt( - symKey, - ivAndCiphertextResponse - ); - const textDecoder = new TextDecoder(); - const respStr = textDecoder.decode(decrypted); - - // replace response + // get the return variables we need + let response = {}; + let txtResponse = ''; + let body = ''; try { - response = options.textResponse ? respStr : JSON.parse(respStr); + body = JSON.parse(result.body); } catch (e) { - log.warn( - `loki_app_dot_net:::sendViaOnion - Could not parse inner JSON [${respStr}]`, - url, + log.error( + `loki_app_dot_net:::sendViaOnion #${adnOnionRequestCount} - Cant decode JSON body`, + result.body ); } -*/ + const code = result.status; + txtResponse = JSON.stringify(body); + response = body; + response.headers = result.headers; + log.debug( + `loki_app_dot_net:::sendViaOnion #${adnOnionRequestCount} - ADN GCM body length`, + txtResponse.length, + 'code', + code, + 'headers', + response.headers + ); + return { result, txtResponse, response }; }; @@ -257,7 +225,11 @@ const sendToProxy = async ( payloadObj.body = false; // free memory // make temporary key for this request/response - const ephemeralKey = await libsignal.Curve.async.generateKeyPair(); + // const ephemeralKey = await libsignal.Curve.generateKeyPair(); // sync + // async maybe preferable to avoid cpu spikes + // tho I think sync might be more apt in certain cases here... + // like sending + const ephemeralKey = await libloki.crypto.generateEphemeralKeyPair(); // mix server pub key with our priv key const symKey = await libsignal.Curve.async.calculateAgreement( @@ -430,8 +402,7 @@ const serverRequest = async (endpoint, options = {}) => { fetchOptions, options )); - } else - if ( + } else if ( window.lokiFeatureFlags.useSnodeProxy && FILESERVER_HOSTS.includes(host) ) { From 7afaa47de91e3b2b6a24f4bf825bb49964e3d184 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Sun, 17 May 2020 20:41:15 -0700 Subject: [PATCH 06/28] expose less, logging/dead code clean up --- js/modules/loki_rpc.js | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/js/modules/loki_rpc.js b/js/modules/loki_rpc.js index ab84323f4..6ca5cbcf0 100644 --- a/js/modules/loki_rpc.js +++ b/js/modules/loki_rpc.js @@ -182,10 +182,6 @@ const makeOnionRequest = async ( target: '/loki/v1/lsrpc', method: 'POST', }; - log.info( - `loki_rpc:::makeOnionRequest ${id} - lsrpc destination set`, - dest - ); } else { // set x25519 if destination snode let pubkeyHex = targetED25519Hex; // relayingToFinalDestination @@ -411,7 +407,10 @@ const processOnionResponse = async ( return false; } if (debug) { - // log.debug(`(${reqIdx}) [path] lokiRpc::processOnionResponse - ciphertext`, ciphertext) + log.debug( + `(${reqIdx}) [path] lokiRpc::processOnionResponse - ciphertext`, + ciphertext + ); } let plaintext; @@ -463,16 +462,6 @@ const processOnionResponse = async ( } return false; } - /* - if (!plaintext) { - log.debug('Trying again with', useAesGcm?'gcm':'dh') - try { - const plaintextBuffer2 = await decryptFn(sharedKey, ciphertextBuffer, true); - log.info(`(${reqIdx}) [path] lokiRpc::processOnionResponse - plaintextBufferHex`, StringView.arrayBufferToHex(plaintextBuffer2)); - } catch(e) { - } - } - */ if (debug) { log.debug('lokiRpc::processOnionResponse - plaintext', plaintext); @@ -798,7 +787,7 @@ const lokiFetch = async (url, options = {}, targetNode = null) => { // Wrong PoW difficulty if (response.status === 432) { const result = await response.json(); - log.error('WRONG POW', result); + log.error(`lokirpc:::lokiFetch ${type} - WRONG POW`, result); throw new textsecure.WrongDifficultyError(result.difficulty); } @@ -979,9 +968,5 @@ const lokiRpc = ( module.exports = { lokiRpc, - makeOnionRequest, - encryptForPubKey, - encryptForRelay, - processOnionResponse, sendOnionRequestLsrpcDest, }; From 97de1eb6ec372c54572b5b5a0e1e44446f85980d Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Sun, 17 May 2020 21:32:21 -0700 Subject: [PATCH 07/28] getPubKeyForUrl() from file_server_api, remove success logging --- js/modules/loki_app_dot_net_api.js | 71 ++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 9 deletions(-) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index defb14ebd..9451d893f 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -16,6 +16,7 @@ const PUBLICCHAT_DELETION_POLL_EVERY = 5 * 1000; // 5s const PUBLICCHAT_MOD_POLL_EVERY = 30 * 1000; // 30s const PUBLICCHAT_MIN_TIME_BETWEEN_DUPLICATE_MESSAGES = 10 * 1000; // 10s +// FIXME: replace with something on urlPubkeyMap... const FILESERVER_HOSTS = [ 'file-dev.lokinet.org', 'file.lokinet.org', @@ -23,6 +24,17 @@ const FILESERVER_HOSTS = [ 'file.getsession.org', ]; +const LOKIFOUNDATION_DEVFILESERVER_PUBKEY = + 'BSZiMVxOco/b3sYfaeyiMWv/JnqokxGXkHoclEx8TmZ6'; +const LOKIFOUNDATION_FILESERVER_PUBKEY = + 'BWJQnVm97sQE3Q1InB4Vuo+U/T1hmwHBv0ipkiv8tzEc'; +const urlPubkeyMap = { + 'https://file-dev.getsession.org': LOKIFOUNDATION_DEVFILESERVER_PUBKEY, + 'https://file-dev.lokinet.org': LOKIFOUNDATION_DEVFILESERVER_PUBKEY, + 'https://file.getsession.org': LOKIFOUNDATION_FILESERVER_PUBKEY, + 'https://file.lokinet.org': LOKIFOUNDATION_FILESERVER_PUBKEY, +}; + const HOMESERVER_USER_ANNOTATION_TYPE = 'network.loki.messenger.homeserver'; const AVATAR_USER_ANNOTATION_TYPE = 'network.loki.messenger.avatar'; const SETTINGS_CHANNEL_ANNOTATION_TYPE = 'net.patter-app.settings'; @@ -145,18 +157,10 @@ const sendViaOnion = async (srvPubKey, url, pFetchOptions, options = {}) => { result.body ); } - const code = result.status; + // result.status has the http response code txtResponse = JSON.stringify(body); response = body; response.headers = result.headers; - log.debug( - `loki_app_dot_net:::sendViaOnion #${adnOnionRequestCount} - ADN GCM body length`, - txtResponse.length, - 'code', - code, - 'headers', - response.headers - ); return { result, txtResponse, response }; }; @@ -572,6 +576,55 @@ class LokiAppDotNetServerAPI { this.channels.splice(i, 1); } + // set up pubKey & pubKeyHex properties + // optionally called for mainly file server comms + getPubKeyForUrl() { + // Hard coded + let pubKeyAB; + if (urlPubkeyMap) { + pubKeyAB = window.Signal.Crypto.base64ToArrayBuffer( + urlPubkeyMap[this.baseServerUrl] + ); + } + // else will fail validation later + + // do we have their pubkey locally? + // FIXME: this._server won't be set yet... + // can't really do this for the file server because we'll need the key + // before we can communicate with lsrpc + /* + // get remote pubKey + this._server.serverRequest('loki/v1/public_key').then(keyRes => { + // we don't need to delay to protect identity because the token request + // should only be done over lokinet-lite + this.delayToken = true; + if (keyRes.err || !keyRes.response || !keyRes.response.data) { + if (keyRes.err) { + log.error(`Error ${keyRes.err}`); + } + } else { + // store it + this.pubKey = dcodeIO.ByteBuffer.wrap( + keyRes.response.data, + 'base64' + ).toArrayBuffer(); + // write it to a file + } + }); + */ + + // now that key is loaded, lets verify + if (pubKeyAB && pubKeyAB.byteLength && pubKeyAB.byteLength !== 33) { + log.error('FILESERVER PUBKEY is invalid, length:', pubKeyAB.byteLength); + process.exit(1); + } + + this.pubKey = pubKeyAB; + this.pubKeyHex = StringView.arrayBufferToHex(pubKeyAB); + + return pubKeyAB; + } + async setProfileName(profileName) { // when we add an annotation, may need this /* From fd4d52ac97f2106443cf8a1e80e43a0b23dd8975 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Sun, 17 May 2020 21:32:52 -0700 Subject: [PATCH 08/28] remove LokiFileServerFactoryAPI.secureRpcPubKey, remove pubkeys calculation from here and put into adn class --- js/modules/loki_file_server_api.js | 60 ++---------------------------- 1 file changed, 4 insertions(+), 56 deletions(-) diff --git a/js/modules/loki_file_server_api.js b/js/modules/loki_file_server_api.js index 864e59ad6..241741cb7 100644 --- a/js/modules/loki_file_server_api.js +++ b/js/modules/loki_file_server_api.js @@ -1,4 +1,4 @@ -/* global log, libloki, process, window, StringView */ +/* global log, libloki, process, window */ /* global storage: false */ /* global Signal: false */ /* global log: false */ @@ -8,17 +8,6 @@ const LokiAppDotNetAPI = require('./loki_app_dot_net_api'); const DEVICE_MAPPING_USER_ANNOTATION_TYPE = 'network.loki.messenger.devicemapping'; -const LOKIFOUNDATION_DEVFILESERVER_PUBKEY = - 'BSZiMVxOco/b3sYfaeyiMWv/JnqokxGXkHoclEx8TmZ6'; -const LOKIFOUNDATION_FILESERVER_PUBKEY = - 'BWJQnVm97sQE3Q1InB4Vuo+U/T1hmwHBv0ipkiv8tzEc'; -const urlPubkeyMap = { - 'https://file-dev.getsession.org': LOKIFOUNDATION_DEVFILESERVER_PUBKEY, - 'https://file-dev.lokinet.org': LOKIFOUNDATION_DEVFILESERVER_PUBKEY, - 'https://file.getsession.org': LOKIFOUNDATION_FILESERVER_PUBKEY, - 'https://file.lokinet.org': LOKIFOUNDATION_FILESERVER_PUBKEY, -}; - // can have multiple of these instances as each user can have a // different home server class LokiFileServerInstance { @@ -38,48 +27,8 @@ class LokiFileServerInstance { } else { this._server = new LokiAppDotNetAPI(this.ourKey, serverUrl); } - - // do we have their pubkey locally? - // FIXME: this._server won't be set yet... - // can't really do this for the file server because we'll need the key - // before we can communicate with lsrpc - /* - // get remote pubKey - this._server.serverRequest('loki/v1/public_key').then(keyRes => { - // we don't need to delay to protect identity because the token request - // should only be done over lokinet-lite - this.delayToken = true; - if (keyRes.err || !keyRes.response || !keyRes.response.data) { - if (keyRes.err) { - log.error(`Error ${keyRes.err}`); - } - } else { - // store it - this.pubKey = dcodeIO.ByteBuffer.wrap( - keyRes.response.data, - 'base64' - ).toArrayBuffer(); - // write it to a file - } - }); - */ - // Hard coded - if (urlPubkeyMap) { - this.pubKey = window.Signal.Crypto.base64ToArrayBuffer( - urlPubkeyMap[serverUrl] - ); - } - if (this.pubKey.byteLength && this.pubKey.byteLength !== 33) { - log.error( - 'FILESERVER PUBKEY is invalid, length:', - this.pubKey.byteLength - ); - process.exit(1); - } - - // configure proxy/onion request - this._server.pubKey = this.pubKey; - this._server.pubKeyHex = StringView.arrayBufferToHex(this.pubKey); + // make sure pubKey & pubKeyHex are set in _server + this.pubKey = this._server.getPubKeyForUrl(); if (options !== undefined && options.skipToken) { return; @@ -92,6 +41,7 @@ class LokiFileServerInstance { log.error('You are blacklisted form this home server'); } } + async getUserDeviceMapping(pubKey) { const annotations = await this._server.getUserAnnotations(pubKey); const deviceMapping = annotations.find( @@ -345,7 +295,5 @@ class LokiFileServerFactoryAPI { return thisServer; } } -// smuggle some data out of this joint (for expire.js/version upgrade check) -LokiFileServerFactoryAPI.secureRpcPubKey = LOKIFOUNDATION_FILESERVER_PUBKEY; module.exports = LokiFileServerFactoryAPI; From 0936c6e04c243cd2a404490f7454f1d2a0f69c46 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Sun, 17 May 2020 21:33:39 -0700 Subject: [PATCH 09/28] use .getPubKeyForUrl() to set up window.tokenlessFileServerAdnAPI --- js/expire.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/js/expire.js b/js/expire.js index 228508caf..0d4750c3b 100644 --- a/js/expire.js +++ b/js/expire.js @@ -1,4 +1,4 @@ -/* global LokiAppDotNetServerAPI, LokiFileServerAPI, semver, log */ +/* global LokiAppDotNetServerAPI, semver, log */ // eslint-disable-next-line func-names (function() { 'use strict'; @@ -12,9 +12,8 @@ ); // use the anonymous access token window.tokenlessFileServerAdnAPI.token = 'loki'; - window.tokenlessFileServerAdnAPI.pubKey = window.Signal.Crypto.base64ToArrayBuffer( - LokiFileServerAPI.secureRpcPubKey - ); + // configure for file server comms + window.tokenlessFileServerAdnAPI.getPubKeyForUrl(); let nextWaitSeconds = 5; const checkForUpgrades = async () => { From 6887526ccd31835b6c202dcb32a0152581749cfd Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Sun, 17 May 2020 22:11:23 -0700 Subject: [PATCH 10/28] prefer libloki over window.libloki (it's more precise), encryptForPubKey() refactor to use libloki.crypto helper function, ephemeral_key => ephemeralKey, encryptForRelay take hex and pass hex key instead of AB now, makeOnionRequest/sendOnionRequest correct parameters for encryptForRelay --- js/modules/loki_rpc.js | 153 +++++++---------------------------------- 1 file changed, 26 insertions(+), 127 deletions(-) diff --git a/js/modules/loki_rpc.js b/js/modules/loki_rpc.js index 6ca5cbcf0..3f3ba2896 100644 --- a/js/modules/loki_rpc.js +++ b/js/modules/loki_rpc.js @@ -1,5 +1,5 @@ /* global log, libloki, textsecure, getStoragePubKey, lokiSnodeAPI, StringView, - libsignal, window, TextDecoder, TextEncoder, dcodeIO, process, crypto */ + libsignal, window, TextDecoder, TextEncoder, dcodeIO, process */ const nodeFetch = require('node-fetch'); const https = require('https'); @@ -16,119 +16,19 @@ let onionReqIdx = 0; // Returns the actual ciphertext, symmetric key that will be used // for decryption, and an ephemeral_key to send to the next hop -const encryptForPubKey = async (pubKeyX25519AB, reqObj, debug = false) => { +const encryptForPubKey = async (pubKeyX25519hex, reqObj) => { // Do we still need "headers"? const reqStr = JSON.stringify(reqObj); const textEncoder = new TextEncoder(); const plaintext = textEncoder.encode(reqStr); - const ephemeral = await libloki.crypto.generateEphemeralKeyPair(); - if (debug) { - log.debug( - 'encryptForPubKey', - debug, - '- pubKeyX25519AB', - StringView.arrayBufferToHex(pubKeyX25519AB) - ); - log.debug( - 'encryptForPubKey', - debug, - '- ephermalPriv', - StringView.arrayBufferToHex(ephemeral.privKey) - ); - log.debug( - 'encryptForPubKey', - debug, - '- ephermalPub', - StringView.arrayBufferToHex(ephemeral.pubKey) - ); - } - - const ephemeralSecret = libsignal.Curve.calculateAgreement( - pubKeyX25519AB, - ephemeral.privKey - ); - if (debug) { - log.debug( - 'encryptForPubKey', - debug, - '- ephemeralSecret', - StringView.arrayBufferToHex(ephemeralSecret) - ); - } - - // FIXME: window.libloki.crypto.deriveSymmetricKey refactor - const salt = window.Signal.Crypto.bytesFromString('LOKI'); // ArrayBuffer (object) - if (debug) { - log.debug( - 'encryptForPubKey', - debug, - '- salt', - StringView.arrayBufferToHex(salt) - ); - } - - const key = await crypto.subtle.importKey( - 'raw', - salt, - { name: 'HMAC', hash: { name: 'SHA-256' } }, - true, - ['sign'] - ); - - // CrsptoKey (object) - const exportKey = await crypto.subtle.exportKey('raw', key); - // ArrayBuffer (object) - if (debug) { - log.error( - 'encryptForPubKey', - debug, - '- key', - StringView.arrayBufferToHex(exportKey) - ); - } - - const symmetricKey = await crypto.subtle.sign( - { name: 'HMAC', hash: 'SHA-256' }, - key, - ephemeralSecret - ); - // ArrayBuffer (object) - if (debug) { - log.debug( - 'encryptForPubKey', - debug, - '- symmetricKey', - StringView.arrayBufferToHex(symmetricKey) - ); - } - - const ciphertext = await window.libloki.crypto.EncryptGCM( - symmetricKey, - plaintext, - debug, - ephemeral.pubKey - ); - // looks textEncoder'd... Uint8Array - if (debug) { - log.debug( - 'encryptForPubKey', - debug, - '- ciphertext', - StringView.arrayBufferToHex(ciphertext), - ciphertext - ); - } - - // ephemeral_key => ephemeralKey? - return { ciphertext, symmetricKey, ephemeral_key: ephemeral.pubKey }; + return libloki.crypto.encryptForPubkey(pubKeyX25519hex, plaintext); }; // `ctx` holds info used by `node` to relay further -// destination needs ed25519_hex -const encryptForRelay = async (relayX25519AB, destination, ctx) => { - // cyx contains: ciphertext, symmetricKey, ephemeral_key +const encryptForRelay = async (relayX25519hex, destination, ctx) => { + // ctx contains: ciphertext, symmetricKey, ephemeralKey const payload = ctx.ciphertext; if (!destination.host && !destination.destination) { @@ -138,10 +38,10 @@ const encryptForRelay = async (relayX25519AB, destination, ctx) => { const reqObj = { ...destination, ciphertext: dcodeIO.ByteBuffer.wrap(payload).toString('base64'), - ephemeral_key: StringView.arrayBufferToHex(ctx.ephemeral_key), + ephemeral_key: StringView.arrayBufferToHex(ctx.ephemeralKey), }; - return encryptForPubKey(relayX25519AB, reqObj); + return encryptForPubKey(relayX25519hex, reqObj); }; const makeGuardPayload = guardCtx => { @@ -151,7 +51,7 @@ const makeGuardPayload = guardCtx => { const guardPayloadObj = { ciphertext: ciphertextBase64, - ephemeral_key: StringView.arrayBufferToHex(guardCtx.ephemeral_key), + ephemeral_key: StringView.arrayBufferToHex(guardCtx.ephemeralKey), }; return guardPayloadObj; }; @@ -202,15 +102,14 @@ const makeOnionRequest = async ( destination: pubkeyHex, }; } - // FIXME: we should store this inside snode pool - const relayX25519AB = StringView.hexToArrayBuffer( - nodePath[i].pubkey_x25519 - ); try { - ctxes.push( - // eslint-disable-next-line no-await-in-loop - await encryptForRelay(relayX25519AB, dest, ctxes[ctxes.length - 1]) + // eslint-disable-next-line no-await-in-loop + const ctx = await encryptForRelay( + nodePath[i].pubkey_x25519, + dest, + ctxes[ctxes.length - 1] ); + ctxes.push(ctx); } catch (e) { log.error( `loki_rpc:::makeOnionRequest ${id} - encryptForRelay failure`, @@ -260,9 +159,10 @@ const sendOnionRequest = async ( } // get destination pubkey in array buffer format - let destX25519AB = destX25519Any; - if (typeof destX25519AB === 'string') { - destX25519AB = StringView.hexToArrayBuffer(destX25519Any); + let destX25519hex = destX25519Any; + if (typeof destX25519hex !== 'string') { + // convert AB to hex + destX25519hex = StringView.arrayBufferToHex(destX25519Any); } // safely build destination @@ -290,17 +190,16 @@ const sendOnionRequest = async ( let destCtx; try { - destCtx = await encryptForPubKey(destX25519AB, options); + destCtx = await encryptForPubKey(destX25519hex, options); } catch (e) { - const hex = StringView.arrayBufferToHex(destX25519AB); log.error( `loki_rpc::sendOnionRequest ${id} - encryptForPubKey failure [`, e.code, e.message, '] destination X25519', - hex.substr(0, 32), + destX25519hex.substr(0, 32), '...', - hex.substr(32), + destX25519hex.substr(32), 'options', options ); @@ -431,8 +330,8 @@ const processOnionResponse = async ( } const decryptFn = useAesGcm - ? window.libloki.crypto.DecryptGCM - : window.libloki.crypto.DHDecrypt; + ? libloki.crypto.DecryptGCM + : libloki.crypto.DHDecrypt; const plaintextBuffer = await decryptFn(sharedKey, ciphertextBuffer, debug); if (debug) { @@ -532,7 +431,7 @@ const sendToProxy = async (options = {}, targetNode, retryNumber = 0) => { const snPubkeyHex = StringView.hexToArrayBuffer(targetNode.pubkey_x25519); - const myKeys = await window.libloki.crypto.generateEphemeralKeyPair(); + const myKeys = await libloki.crypto.generateEphemeralKeyPair(); const symmetricKey = await libsignal.Curve.async.calculateAgreement( snPubkeyHex, @@ -543,7 +442,7 @@ const sendToProxy = async (options = {}, targetNode, retryNumber = 0) => { const body = JSON.stringify(options); const plainText = textEncoder.encode(body); - const ivAndCiphertext = await window.libloki.crypto.DHEncrypt( + const ivAndCiphertext = await libloki.crypto.DHEncrypt( symmetricKey, plainText ); @@ -691,7 +590,7 @@ const sendToProxy = async (options = {}, targetNode, retryNumber = 0) => { 'base64' ).toArrayBuffer(); - const plaintextBuffer = await window.libloki.crypto.DHDecrypt( + const plaintextBuffer = await libloki.crypto.DHDecrypt( symmetricKey, ciphertextBuffer ); From 80a3eb0521852a5fe229912af7956b54642d7019 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 20 May 2020 21:13:31 -0700 Subject: [PATCH 11/28] cannot use libloki.crypto.generateEphemeralKeyPair() because it removes the 05 prefix --- js/modules/loki_app_dot_net_api.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index 9451d893f..70d696f3b 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -233,7 +233,8 @@ const sendToProxy = async ( // async maybe preferable to avoid cpu spikes // tho I think sync might be more apt in certain cases here... // like sending - const ephemeralKey = await libloki.crypto.generateEphemeralKeyPair(); + // cannot use libloki.crypto.generateEphemeralKeyPair() because it removes the 05 prefix + const ephemeralKey = await libsignal.Curve.async.generateKeyPair(); // mix server pub key with our priv key const symKey = await libsignal.Curve.async.calculateAgreement( From f134c6ae488dabfa2b3fe9893128364fa8f38554 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 20 May 2020 21:43:41 -0700 Subject: [PATCH 12/28] Revert libloki.crypto.generateEphemeralKeyPair, use ... for OR retries --- js/modules/loki_app_dot_net_api.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index 70d696f3b..457ded652 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -138,11 +138,11 @@ const sendViaOnion = async (srvPubKey, url, pFetchOptions, options = {}) => { } Couldnt handle onion request, retrying`, payloadObj ); - // eslint-disable-next-line no-param-reassign - options.retry += 1; - // eslint-disable-next-line no-param-reassign - options.counter = adnOnionRequestCount; - return sendViaOnion(srvPubKey, url, pFetchOptions, options); + return sendViaOnion(srvPubKey, url, pFetchOptions, { + ...options, + retry: options.retry + 1, + counter: adnOnionRequestCount + }); } // get the return variables we need @@ -233,8 +233,7 @@ const sendToProxy = async ( // async maybe preferable to avoid cpu spikes // tho I think sync might be more apt in certain cases here... // like sending - // cannot use libloki.crypto.generateEphemeralKeyPair() because it removes the 05 prefix - const ephemeralKey = await libsignal.Curve.async.generateKeyPair(); + const ephemeralKey = await libloki.crypto.generateEphemeralKeyPair(); // mix server pub key with our priv key const symKey = await libsignal.Curve.async.calculateAgreement( From 0d40ee7ae6cc64bc10ff64f8ebcc5a58f90a0a47 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 20 May 2020 21:45:27 -0700 Subject: [PATCH 13/28] Don't allow file-dev in proxy mode --- js/modules/loki_app_dot_net_api.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index 457ded652..e7f740d4e 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -588,6 +588,14 @@ class LokiAppDotNetServerAPI { } // else will fail validation later + // if in proxy mode, don't allow "file-dev."... + // it only supports "file."... host. + if (window.lokiFeatureFlags.useSnodeProxy) { + pubKeyAB = window.Signal.Crypto.base64ToArrayBuffer( + LOKIFOUNDATION_FILESERVER_PUBKEY + ); + } + // do we have their pubkey locally? // FIXME: this._server won't be set yet... // can't really do this for the file server because we'll need the key From e71672ef70ae9fdb29be100aec2f9b38c9c3796c Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 20 May 2020 21:46:17 -0700 Subject: [PATCH 14/28] remove dead debug --- js/modules/loki_rpc.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/js/modules/loki_rpc.js b/js/modules/loki_rpc.js index 3f3ba2896..7234f2141 100644 --- a/js/modules/loki_rpc.js +++ b/js/modules/loki_rpc.js @@ -69,9 +69,6 @@ const makeOnionRequest = async ( // from (3) 2 to 0 const firstPos = nodePath.length - 1; - // console.log('targetED25519Hex', targetED25519Hex) - // console.log('nodePath', nodePath.length, 'first', firstPos) - for (let i = firstPos; i > -1; i -= 1) { let dest; const relayingToFinalDestination = i === 0; // if last position From 7d961ad40ab718eb1f177510ffd8bb29f77a7c34 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 20 May 2020 22:27:26 -0700 Subject: [PATCH 15/28] make sure OR is off and proxy is on to override file host --- js/modules/loki_app_dot_net_api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index e7f740d4e..85b22634f 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -590,7 +590,7 @@ class LokiAppDotNetServerAPI { // if in proxy mode, don't allow "file-dev."... // it only supports "file."... host. - if (window.lokiFeatureFlags.useSnodeProxy) { + if (window.lokiFeatureFlags.useSnodeProxy && !window.lokiFeatureFlags.useOnionRequests) { pubKeyAB = window.Signal.Crypto.base64ToArrayBuffer( LOKIFOUNDATION_FILESERVER_PUBKEY ); From 373224eee20a1aca87ddea4c699719e7f32c7189 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 26 May 2020 11:47:34 +1000 Subject: [PATCH 16/28] Fix window sizing --- main.js | 60 ++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/main.js b/main.js index 408771485..dde67177d 100644 --- a/main.js +++ b/main.js @@ -206,19 +206,30 @@ function captureClicks(window) { window.webContents.on('new-window', handleUrl); } -const DEFAULT_WIDTH = 880; -// add contact button needs to be visible (on HiDpi screens?) -// otherwise integration test fail -const DEFAULT_HEIGHT = 820; -const MIN_WIDTH = 880; -const MIN_HEIGHT = 820; -const BOUNDS_BUFFER = 100; +const WINDOW_SIZE = Object.freeze({ + defaultWidth: 880, + defaultHeight: 820, + minWidth: 880, + minHeight: 820, +}); + +function getWindowSize() { + const { screen } = electron; + const screenSize = screen.getPrimaryDisplay().workAreaSize; + const { minWidth, minHeight, defaultWidth, defaultHeight } = WINDOW_SIZE; + // Ensure that the screen can fit within the default size + const width = Math.min(defaultWidth, Math.max(minWidth, screenSize.width)); + const height = Math.min(defaultHeight, Math.max(minHeight, screenSize.height)); + + return { width, height, minWidth, minHeight }; +} function isVisible(window, bounds) { const boundsX = _.get(bounds, 'x') || 0; const boundsY = _.get(bounds, 'y') || 0; - const boundsWidth = _.get(bounds, 'width') || DEFAULT_WIDTH; - const boundsHeight = _.get(bounds, 'height') || DEFAULT_HEIGHT; + const boundsWidth = _.get(bounds, 'width') || WINDOW_SIZE.defaultWidth; + const boundsHeight = _.get(bounds, 'height') || WINDOW_SIZE.defaultHeight; + const BOUNDS_BUFFER = 100; // requiring BOUNDS_BUFFER pixels on the left or right side const rightSideClearOfLeftBound = @@ -241,13 +252,14 @@ function isVisible(window, bounds) { async function createWindow() { const { screen } = electron; + const { minWidth, minHeight, width, height } = getWindowSize(); const windowOptions = Object.assign( { show: !startInTray, // allow to start minimised in tray - width: DEFAULT_WIDTH, - height: DEFAULT_HEIGHT, - minWidth: MIN_WIDTH, - minHeight: MIN_HEIGHT, + width, + height, + minWidth, + minHeight, autoHideMenuBar: false, backgroundColor: '#fff', webPreferences: { @@ -270,11 +282,11 @@ async function createWindow() { ]) ); - if (!_.isNumber(windowOptions.width) || windowOptions.width < MIN_WIDTH) { - windowOptions.width = DEFAULT_WIDTH; + if (!_.isNumber(windowOptions.width) || windowOptions.width < minWidth) { + windowOptions.width = Math.max(minWidth, width); } - if (!_.isNumber(windowOptions.height) || windowOptions.height < MIN_HEIGHT) { - windowOptions.height = DEFAULT_HEIGHT; + if (!_.isNumber(windowOptions.height) || windowOptions.height < minHeight) { + windowOptions.height = Math.max(minHeight, height); } if (!_.isBoolean(windowOptions.maximized)) { delete windowOptions.maximized; @@ -516,13 +528,13 @@ function showPasswordWindow() { passwordWindow.show(); return; } - + const { minWidth, minHeight, width, height } = getWindowSize(); const windowOptions = { show: true, // allow to start minimised in tray - width: DEFAULT_WIDTH, - height: DEFAULT_HEIGHT, - minWidth: MIN_WIDTH, - minHeight: MIN_HEIGHT, + width, + height, + minWidth, + minHeight, autoHideMenuBar: false, webPreferences: { nodeIntegration: false, @@ -631,8 +643,8 @@ async function showDebugLogWindow() { const theme = await getThemeFromMainWindow(); const size = mainWindow.getSize(); const options = { - width: Math.max(size[0] - 100, MIN_WIDTH), - height: Math.max(size[1] - 100, MIN_HEIGHT), + width: Math.max(size[0] - 100, WINDOW_SIZE.minWidth), + height: Math.max(size[1] - 100, WINDOW_SIZE.minHeight), resizable: false, title: locale.messages.signalDesktopPreferences.message, autoHideMenuBar: true, From bf4557fbe81aa263d51c2da63129e4a32c382bee Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 26 May 2020 12:34:04 +1000 Subject: [PATCH 17/28] Fix styling on registration page --- main.js | 2 +- stylesheets/_session_signin.scss | 33 +++++++++++++---- .../session/SessionRegistrationView.tsx | 37 ++++++++++--------- 3 files changed, 46 insertions(+), 26 deletions(-) diff --git a/main.js b/main.js index dde67177d..12d60881c 100644 --- a/main.js +++ b/main.js @@ -210,7 +210,7 @@ const WINDOW_SIZE = Object.freeze({ defaultWidth: 880, defaultHeight: 820, minWidth: 880, - minHeight: 820, + minHeight: 600, }); function getWindowSize() { diff --git a/stylesheets/_session_signin.scss b/stylesheets/_session_signin.scss index b5f618bfa..221e6cfe3 100644 --- a/stylesheets/_session_signin.scss +++ b/stylesheets/_session_signin.scss @@ -10,6 +10,7 @@ height: 100%; display: flex; align-items: center; + flex-direction: column; &-accent { flex-grow: 1; @@ -28,21 +29,33 @@ } &-registration { - height: 45%; padding-right: 128px; } + &-header { + display: flex; + flex-direction: row; + width: 100%; + justify-content: space-between; + padding: 17px 20px; + } + + &-body { + display: flex; + flex-direction: row; + flex: 1; + align-items: center; + width: 100%; + padding-bottom: 20px; + } + + &-close-button { - position: absolute; - top: 17px; - left: 20px; + display: flex; + align-items: center; } &-session-button { - position: absolute; - top: 17px; - right: 20px; - img { width: 30px; } @@ -246,6 +259,8 @@ display: inline-block; font-family: $session-font-mono; user-select: all; + overflow: hidden; + resize: none; } } } @@ -259,3 +274,5 @@ .registration-content-centered { text-align: center; } + + diff --git a/ts/components/session/SessionRegistrationView.tsx b/ts/components/session/SessionRegistrationView.tsx index 3ab0d345e..6f1ef293a 100644 --- a/ts/components/session/SessionRegistrationView.tsx +++ b/ts/components/session/SessionRegistrationView.tsx @@ -8,24 +8,27 @@ export const SessionRegistrationView: React.FC = () => (
-
- { - window.close(); - }} - /> +
+
+ { + window.close(); + }} + /> +
+
+ brand +
- -
- -
-
- -
-
- brand +
+
+ +
+
+ +
); From d694a71b4cdd07e648e90b2096e4c790289182ae Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 26 May 2020 13:30:40 +1000 Subject: [PATCH 18/28] Made add contact overlay scrollable --- stylesheets/_session_left_pane.scss | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/stylesheets/_session_left_pane.scss b/stylesheets/_session_left_pane.scss index fca8c1d42..4887b3402 100644 --- a/stylesheets/_session_left_pane.scss +++ b/stylesheets/_session_left_pane.scss @@ -239,7 +239,8 @@ $session-compose-margin: 20px; display: flex; flex-direction: column; align-items: center; - height: -webkit-fill-available; + overflow-y: auto; + overflow-x: hidden; .session-icon .exit { padding: 13px; } @@ -339,7 +340,8 @@ $session-compose-margin: 20px; .session-left-pane-section-content { display: flex; flex-direction: column; - flex-grow: 1; + flex: 1; + overflow: hidden; } .user-search-dropdown { @@ -471,7 +473,7 @@ $session-compose-margin: 20px; &-content { display: flex; flex-direction: column; - flex-grow: 1; + overflow: hidden; .module-conversation-list-item { background-color: $session-shade-4; From d6374f53d6fd9eeae186bf237f240104a45e28c7 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 26 May 2020 13:56:30 +1000 Subject: [PATCH 19/28] Added scroll bars to modals --- stylesheets/_session.scss | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/stylesheets/_session.scss b/stylesheets/_session.scss index 03168a8c5..34cca4d3b 100644 --- a/stylesheets/_session.scss +++ b/stylesheets/_session.scss @@ -558,7 +558,9 @@ label { max-width: 70vw; background-color: $session-shade-4; border: 1px solid $session-shade-8; - padding-bottom: $session-margin-lg; + overflow: hidden; + display: flex; + flex-direction: column; &__header { display: flex; @@ -609,6 +611,8 @@ label { font-family: $session-font-accent; line-height: $session-font-md; font-size: $session-font-sm; + overflow-y: auto; + overflow-x: hidden; .message { text-align: center; @@ -1566,6 +1570,13 @@ input { text-align: center; padding: 20px; } + + // Height at which scroll bar appears on the group member list + @media (max-height: 804px) { + &__container { + overflow-y: visible; + } + } } .create-group-name-input { .session-id-editable { From ebc768887f4c353ae5323db9cf084754011184f3 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 26 May 2020 13:59:42 +1000 Subject: [PATCH 20/28] Fix scrolling in settings --- stylesheets/_session.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/stylesheets/_session.scss b/stylesheets/_session.scss index 34cca4d3b..7af79716b 100644 --- a/stylesheets/_session.scss +++ b/stylesheets/_session.scss @@ -1065,7 +1065,8 @@ label { flex-direction: column; &-list { - overflow-y: scroll; + overflow-y: auto; + overflow-x: hidden; } &-header { @@ -1135,6 +1136,7 @@ label { display: flex; flex-direction: column; justify-content: space-between; + overflow: hidden; } &__version-info { From a351ce6c30847116bae865c6ad974eae9952f400 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 26 May 2020 14:04:30 +1000 Subject: [PATCH 21/28] Linting --- main.js | 5 ++++- stylesheets/_session_signin.scss | 3 --- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/main.js b/main.js index 12d60881c..0b4f7f688 100644 --- a/main.js +++ b/main.js @@ -219,7 +219,10 @@ function getWindowSize() { const { minWidth, minHeight, defaultWidth, defaultHeight } = WINDOW_SIZE; // Ensure that the screen can fit within the default size const width = Math.min(defaultWidth, Math.max(minWidth, screenSize.width)); - const height = Math.min(defaultHeight, Math.max(minHeight, screenSize.height)); + const height = Math.min( + defaultHeight, + Math.max(minHeight, screenSize.height) + ); return { width, height, minWidth, minHeight }; } diff --git a/stylesheets/_session_signin.scss b/stylesheets/_session_signin.scss index 221e6cfe3..65f0eed86 100644 --- a/stylesheets/_session_signin.scss +++ b/stylesheets/_session_signin.scss @@ -49,7 +49,6 @@ padding-bottom: 20px; } - &-close-button { display: flex; align-items: center; @@ -274,5 +273,3 @@ .registration-content-centered { text-align: center; } - - From b8ed01953494bf4e77501035d9727f4d4cac9695 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 26 May 2020 19:35:46 -0700 Subject: [PATCH 22/28] remove stale comment, use window.lokiSnodeAPI.getOnionRequestNumber --- js/modules/loki_rpc.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/js/modules/loki_rpc.js b/js/modules/loki_rpc.js index 7234f2141..93739cdfc 100644 --- a/js/modules/loki_rpc.js +++ b/js/modules/loki_rpc.js @@ -11,13 +11,9 @@ const snodeHttpsAgent = new https.Agent({ const endpointBase = '/storage_rpc/v1'; -// Request index for debugging -let onionReqIdx = 0; - // Returns the actual ciphertext, symmetric key that will be used // for decryption, and an ephemeral_key to send to the next hop const encryptForPubKey = async (pubKeyX25519hex, reqObj) => { - // Do we still need "headers"? const reqStr = JSON.stringify(reqObj); const textEncoder = new TextEncoder(); @@ -704,8 +700,7 @@ const lokiFetch = async (url, options = {}, targetNode = null) => { // Get a path excluding `targetNode`: // eslint-disable-next-line no-await-in-loop const path = await lokiSnodeAPI.getOnionPath(targetNode); - const thisIdx = onionReqIdx; - onionReqIdx += 1; + const thisIdx = window.lokiSnodeAPI.getOnionRequestNumber(); // eslint-disable-next-line no-await-in-loop const result = await sendOnionRequestSnodeDest( From 1f9df11a0e7f5f81375affb46358321182827be8 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 26 May 2020 19:38:00 -0700 Subject: [PATCH 23/28] better seedNode error handling, getOnionRequestNumber(), getOnionPath() fixes --- js/modules/loki_snode_api.js | 102 ++++++++++++++++++++++++++++++++--- 1 file changed, 95 insertions(+), 7 deletions(-) diff --git a/js/modules/loki_snode_api.js b/js/modules/loki_snode_api.js index 0ad0cd8f5..75487a226 100644 --- a/js/modules/loki_snode_api.js +++ b/js/modules/loki_snode_api.js @@ -23,8 +23,17 @@ const compareSnodes = (current, search) => // just get the filtered list async function tryGetSnodeListFromLokidSeednode( - seedNodes = [...window.seedNodeList] + seedNodes = window.seedNodeList ) { + if (!seedNodes.length) { + log.error( + `loki_snodes:::tryGetSnodeListFromLokidSeednode - no seedNodes given`, + seedNodes, + 'window', + window.seedNodeList + ); + return []; + } // Removed limit until there is a way to get snode info // for individual nodes (needed for guard nodes); this way // we get all active nodes @@ -42,6 +51,13 @@ async function tryGetSnodeListFromLokidSeednode( Math.floor(Math.random() * seedNodes.length), 1 )[0]; + if (!seedNode) { + log.error( + `loki_snodes:::tryGetSnodeListFromLokidSeednode - seedNode selection failure - seedNodes`, + seedNodes + ); + return []; + } let snodes = []; try { const getSnodesFromSeedUrl = async urlObj => { @@ -53,6 +69,30 @@ async function tryGetSnodeListFromLokidSeednode( {}, // Options '/json_rpc' // Seed request endpoint ); + if (!response) { + log.error( + `loki_snodes:::tryGetSnodeListFromLokidSeednode - invalid response from seed ${urlObj.toString()}:`, + response + ); + return []; + } + + // should we try to JSON.parse this? + if (typeof response === 'string') { + log.error( + `loki_snodes:::tryGetSnodeListFromLokidSeednode - invalid string response from seed ${urlObj.toString()}:`, + response + ); + return []; + } + + if (!response.result) { + log.error( + `loki_snodes:::tryGetSnodeListFromLokidSeednode - invalid result from seed ${urlObj.toString()}:`, + response + ); + return []; + } // Filter 0.0.0.0 nodes which haven't submitted uptime proofs return response.result.service_node_states.filter( snode => snode.public_ip !== '0.0.0.0' @@ -72,6 +112,13 @@ async function tryGetSnodeListFromLokidSeednode( ); } } + if (snodes.length) { + log.info( + `loki_snodes:::tryGetSnodeListFromLokidSeednode - got ${ + snodes.length + } service nodes from seed` + ); + } return snodes; } catch (e) { log.warn( @@ -87,9 +134,18 @@ async function tryGetSnodeListFromLokidSeednode( } async function getSnodeListFromLokidSeednode( - seedNodes = [...window.seedNodeList], + seedNodes = window.seedNodeList, retries = 0 ) { + if (!seedNodes.length) { + log.error( + `loki_snodes:::getSnodeListFromLokidSeednode - no seedNodes given`, + seedNodes, + 'window', + window.seedNodeList + ); + return []; + } let snodes = []; try { snodes = await tryGetSnodeListFromLokidSeednode(seedNodes); @@ -129,6 +185,12 @@ class LokiSnodeAPI { this.onionPaths = []; this.guardNodes = []; + this.onionRequestCounter = 0; // Request index for debugging + } + + getOnionRequestNumber() { + this.onionRequestCounter += 1; + return this.onionRequestCounter; } async getRandomSnodePool() { @@ -202,7 +264,7 @@ class LokiSnodeAPI { // FIXME: handle rejections let nodePool = await this.getRandomSnodePool(); if (nodePool.length === 0) { - log.error(`Could not select guarn nodes: node pool is empty`); + log.error(`Could not select guard nodes: node pool is empty`); return []; } @@ -213,7 +275,7 @@ class LokiSnodeAPI { const DESIRED_GUARD_COUNT = 3; if (shuffled.length < DESIRED_GUARD_COUNT) { log.error( - `Could not select guarn nodes: node pool is not big enough, pool size ${ + `Could not select guard nodes: node pool is not big enough, pool size ${ shuffled.length }, need ${DESIRED_GUARD_COUNT}, attempting to refresh randomPool` ); @@ -222,7 +284,7 @@ class LokiSnodeAPI { shuffled = _.shuffle(nodePool); if (shuffled.length < DESIRED_GUARD_COUNT) { log.error( - `Could not select guarn nodes: node pool is not big enough, pool size ${ + `Could not select guard nodes: node pool is not big enough, pool size ${ shuffled.length }, need ${DESIRED_GUARD_COUNT}, failing...` ); @@ -278,12 +340,15 @@ class LokiSnodeAPI { `Must have at least 2 good onion paths, actual: ${goodPaths.length}` ); await this.buildNewOnionPaths(); + // should we add a delay? buildNewOnionPaths should act as one + // reload goodPaths now + return this.getOnionPath(toExclude); } const paths = _.shuffle(goodPaths); if (!toExclude) { - return paths[0]; + return paths[0].path; } // Select a path that doesn't contain `toExclude` @@ -294,6 +359,19 @@ class LokiSnodeAPI { if (otherPaths.length === 0) { // This should never happen! + // well it did happen, should we + // await this.buildNewOnionPaths(); + // and restart call? + log.error( + `loki_snode_api::getOnionPath - no paths without`, + toExclude.pubkey_ed25519, + 'path count', + paths.length, + 'goodPath count', + goodPaths.length, + 'paths', + paths + ); throw new Error('No onion paths available after filtering'); } @@ -569,7 +647,17 @@ class LokiSnodeAPI { ); } - async refreshRandomPool(seedNodes = [...window.seedNodeList]) { + async refreshRandomPool(seedNodes = window.seedNodeList) { + if (!seedNodes.length) { + if (!window.seedNodeList || !window.seedNodeList.length) { + log.error( + `loki_snodes:::refreshRandomPool - seedNodeList has not been loaded yet` + ); + return []; + } + // eslint-disable-next-line no-param-reassign + seedNodes = window.seedNodeList; + } return primitives.allowOnlyOneAtATime('refreshRandomPool', async () => { // are we running any _getAllVerionsForRandomSnodePool if (this.stopGetAllVersionPromiseControl !== false) { From 4b6aaeab5665f7a0f1440c37c11065e2a3b9a01e Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 26 May 2020 19:39:38 -0700 Subject: [PATCH 24/28] use window.lokiSnodeAPI.getOnionRequestNumber, catch lokiSnodeAPI.getOnionPath exceptions, remove dead code, lint --- js/modules/loki_app_dot_net_api.js | 74 ++++++++++++++---------------- 1 file changed, 35 insertions(+), 39 deletions(-) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index 85b22634f..637b75962 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -48,8 +48,7 @@ const snodeHttpsAgent = new https.Agent({ const timeoutDelay = ms => new Promise(resolve => setTimeout(resolve, ms)); -let adnOnionRequestCounter = 0; -const sendViaOnion = async (srvPubKey, url, pFetchOptions, options = {}) => { +const sendViaOnion = async (srvPubKey, url, fetchOptions, options = {}) => { if (!srvPubKey) { log.error( 'loki_app_dot_net:::sendViaOnion - called without a server public key' @@ -58,29 +57,20 @@ const sendViaOnion = async (srvPubKey, url, pFetchOptions, options = {}) => { } // set retry count - let adnOnionRequestCount; if (options.retry === undefined) { // eslint-disable-next-line no-param-reassign options.retry = 0; - adnOnionRequestCounter += 1; // increment counter - adnOnionRequestCount = 0 + adnOnionRequestCounter; // copy value - } else { - adnOnionRequestCount = options.counter; - } - - const fetchOptions = pFetchOptions; // make lint happy - - // safety issue with file server, just safer to have this - if (fetchOptions.headers === undefined) { - fetchOptions.headers = {}; + // eslint-disable-next-line no-param-reassign + options.requestNumber = window.lokiSnodeAPI.getOnionRequestNumber(); } const payloadObj = { method: fetchOptions.method, body: fetchOptions.body, + // safety issue with file server, just safer to have this + headers: fetchOptions.headers || {}, // no initial / endpoint: url.pathname.replace(/^\//, ''), - headers: { ...fetchOptions.headers }, }; if (url.search) { payloadObj.endpoint += `?${url.search}`; @@ -102,11 +92,23 @@ const sendViaOnion = async (srvPubKey, url, pFetchOptions, options = {}) => { }; } - const pathNodes = await lokiSnodeAPI.getOnionPath(); + let pathNodes = []; + try { + pathNodes = await lokiSnodeAPI.getOnionPath(); + } catch (e) { + log.error( + `loki_app_dot_net:::sendViaOnion #${ + options.requestNumber + } - getOnionPath Error ${e.code} ${e.message}` + ); + } if (!pathNodes || !pathNodes.length) { log.warn( - `loki_app_dot_net:::sendViaOnion #${adnOnionRequestCount} - failing, no path available` + `loki_app_dot_net:::sendViaOnion #${ + options.requestNumber + } - failing, no path available` ); + // should we retry? return {}; } @@ -119,7 +121,7 @@ const sendViaOnion = async (srvPubKey, url, pFetchOptions, options = {}) => { srvPubKey, url.host, payloadObj, - adnOnionRequestCount + options.requestNumber ); } catch (e) { log.error( @@ -133,15 +135,15 @@ const sendViaOnion = async (srvPubKey, url, pFetchOptions, options = {}) => { // handle error/retries if (!result.status) { log.error( - `loki_app_dot_net:::sendViaOnion #${adnOnionRequestCount} - Retry #${ + `loki_app_dot_net:::sendViaOnion #${options.requestNumber} - Retry #${ options.retry } Couldnt handle onion request, retrying`, payloadObj ); - return sendViaOnion(srvPubKey, url, pFetchOptions, { + return sendViaOnion(srvPubKey, url, fetchOptions, { ...options, retry: options.retry + 1, - counter: adnOnionRequestCount + counter: options.requestNumber, }); } @@ -153,7 +155,9 @@ const sendViaOnion = async (srvPubKey, url, pFetchOptions, options = {}) => { body = JSON.parse(result.body); } catch (e) { log.error( - `loki_app_dot_net:::sendViaOnion #${adnOnionRequestCount} - Cant decode JSON body`, + `loki_app_dot_net:::sendViaOnion #${ + options.requestNumber + } - Cant decode JSON body`, result.body ); } @@ -165,12 +169,7 @@ const sendViaOnion = async (srvPubKey, url, pFetchOptions, options = {}) => { return { result, txtResponse, response }; }; -const sendToProxy = async ( - srvPubKey, - endpoint, - pFetchOptions, - options = {} -) => { +const sendToProxy = async (srvPubKey, endpoint, fetchOptions, options = {}) => { if (!srvPubKey) { log.error( 'loki_app_dot_net:::sendToProxy - called without a server public key' @@ -178,17 +177,12 @@ const sendToProxy = async ( return {}; } - const fetchOptions = pFetchOptions; // make lint happy - // safety issue with file server, just safer to have this - if (fetchOptions.headers === undefined) { - fetchOptions.headers = {}; - } - const payloadObj = { body: fetchOptions.body, // might need to b64 if binary... endpoint, method: fetchOptions.method, - headers: fetchOptions.headers, + // safety issue with file server, just safer to have this + headers: fetchOptions.headers || {}, }; // from https://github.com/sindresorhus/is-stream/blob/master/index.js @@ -218,7 +212,7 @@ const sendToProxy = async ( log.warn('proxy random snode pool is not ready, retrying 10s', endpoint); // no nodes in the pool yet, give it some time and retry await timeoutDelay(1000); - return sendToProxy(srvPubKey, endpoint, pFetchOptions, options); + return sendToProxy(srvPubKey, endpoint, fetchOptions, options); } const url = `https://${randSnode.ip}:${randSnode.port}/file_proxy`; @@ -229,7 +223,6 @@ const sendToProxy = async ( payloadObj.body = false; // free memory // make temporary key for this request/response - // const ephemeralKey = await libsignal.Curve.generateKeyPair(); // sync // async maybe preferable to avoid cpu spikes // tho I think sync might be more apt in certain cases here... // like sending @@ -590,12 +583,15 @@ class LokiAppDotNetServerAPI { // if in proxy mode, don't allow "file-dev."... // it only supports "file."... host. - if (window.lokiFeatureFlags.useSnodeProxy && !window.lokiFeatureFlags.useOnionRequests) { + if ( + window.lokiFeatureFlags.useSnodeProxy && + !window.lokiFeatureFlags.useOnionRequests + ) { pubKeyAB = window.Signal.Crypto.base64ToArrayBuffer( LOKIFOUNDATION_FILESERVER_PUBKEY ); } - + // do we have their pubkey locally? // FIXME: this._server won't be set yet... // can't really do this for the file server because we'll need the key From 2d715fdfed776d390bfcdb46fe136bf7eb3ed2a5 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 26 May 2020 21:55:00 -0700 Subject: [PATCH 25/28] getOnionRequestNumber=>assignOnionRequestNumber --- js/modules/loki_app_dot_net_api.js | 2 +- js/modules/loki_rpc.js | 2 +- js/modules/loki_snode_api.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index 637b75962..a9c92933c 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -61,7 +61,7 @@ const sendViaOnion = async (srvPubKey, url, fetchOptions, options = {}) => { // eslint-disable-next-line no-param-reassign options.retry = 0; // eslint-disable-next-line no-param-reassign - options.requestNumber = window.lokiSnodeAPI.getOnionRequestNumber(); + options.requestNumber = window.lokiSnodeAPI.assignOnionRequestNumber(); } const payloadObj = { diff --git a/js/modules/loki_rpc.js b/js/modules/loki_rpc.js index 93739cdfc..dfbce16ae 100644 --- a/js/modules/loki_rpc.js +++ b/js/modules/loki_rpc.js @@ -700,7 +700,7 @@ const lokiFetch = async (url, options = {}, targetNode = null) => { // Get a path excluding `targetNode`: // eslint-disable-next-line no-await-in-loop const path = await lokiSnodeAPI.getOnionPath(targetNode); - const thisIdx = window.lokiSnodeAPI.getOnionRequestNumber(); + const thisIdx = window.lokiSnodeAPI.assignOnionRequestNumber(); // eslint-disable-next-line no-await-in-loop const result = await sendOnionRequestSnodeDest( diff --git a/js/modules/loki_snode_api.js b/js/modules/loki_snode_api.js index 75487a226..d6d030493 100644 --- a/js/modules/loki_snode_api.js +++ b/js/modules/loki_snode_api.js @@ -188,7 +188,7 @@ class LokiSnodeAPI { this.onionRequestCounter = 0; // Request index for debugging } - getOnionRequestNumber() { + assignOnionRequestNumber() { this.onionRequestCounter += 1; return this.onionRequestCounter; } From 78991416f137712a04d4f304623d74208180ffe8 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Tue, 26 May 2020 22:16:04 -0700 Subject: [PATCH 26/28] put file server onion requests behind a feature flag --- js/modules/loki_app_dot_net_api.js | 2 +- preload.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index a9c92933c..e6645cf69 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -385,7 +385,7 @@ const serverRequest = async (endpoint, options = {}) => { const host = url.host.toLowerCase(); // log.info('host', host, FILESERVER_HOSTS); if ( - window.lokiFeatureFlags.useOnionRequests && + window.lokiFeatureFlags.useFileOnionRequests && FILESERVER_HOSTS.includes(host) ) { mode = 'sendViaOnion'; diff --git a/preload.js b/preload.js index caab83f98..23bddf50c 100644 --- a/preload.js +++ b/preload.js @@ -416,6 +416,7 @@ window.lokiFeatureFlags = { privateGroupChats: true, useSnodeProxy: !process.env.USE_STUBBED_NETWORK, useOnionRequests: true, + useFileOnionRequests: false, onionRequestHops: 1, }; From ef01e46ba4bd2b901299770bb8fc3661dc821035 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 28 May 2020 09:38:45 +1000 Subject: [PATCH 27/28] Fix bottom bar layout --- stylesheets/_session_left_pane.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/stylesheets/_session_left_pane.scss b/stylesheets/_session_left_pane.scss index 4887b3402..8c0c4a48c 100644 --- a/stylesheets/_session_left_pane.scss +++ b/stylesheets/_session_left_pane.scss @@ -406,8 +406,6 @@ $session-compose-margin: 20px; @mixin bottom-buttons() { display: flex; flex-direction: row; - position: absolute; - bottom: 2px; width: 100%; @at-root .light-theme #{&} { @@ -474,6 +472,7 @@ $session-compose-margin: 20px; display: flex; flex-direction: column; overflow: hidden; + flex: 1; .module-conversation-list-item { background-color: $session-shade-4; From 2e2c08461a14e9ecabe2de276337a54bb512f8b1 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Wed, 27 May 2020 18:23:19 -0700 Subject: [PATCH 28/28] move expires after libtextsecure --- test/index.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/index.html b/test/index.html index b503f8b23..a5ffa28a3 100644 --- a/test/index.html +++ b/test/index.html @@ -492,13 +492,14 @@ - + +