fix linter warnings

pull/1000/head
Maxim Shishmarev 5 years ago
parent f2ef69f00d
commit 0221e7b8c0

@ -993,7 +993,6 @@ async function updateToLokiSchemaVersion3(currentVersion, instance) {
console.log('updateToLokiSchemaVersion3: starting...'); console.log('updateToLokiSchemaVersion3: starting...');
await instance.run('BEGIN TRANSACTION;'); await instance.run('BEGIN TRANSACTION;');
await instance.run( await instance.run(
`INSERT INTO loki_schema ( `INSERT INTO loki_schema (
version version
@ -1505,7 +1504,6 @@ async function getSecondaryDevicesFor(primaryDevicePubKey) {
} }
async function getGuardNodes() { async function getGuardNodes() {
const nodes = await db.all(`SELECT ed25519PubKey FROM ${GUARD_NODE_TABLE};`); const nodes = await db.all(`SELECT ed25519PubKey FROM ${GUARD_NODE_TABLE};`);
if (!nodes) { if (!nodes) {
@ -1513,55 +1511,27 @@ async function getGuardNodes() {
} }
return nodes; return nodes;
}
async function createOrUpdatePreKey(data) {
const { id, recipient } = data;
if (!id) {
throw new Error('createOrUpdate: Provided data did not have a truthy id');
}
await db.run(
`INSERT OR REPLACE INTO ${PRE_KEYS_TABLE} (
id,
recipient,
json
) values (
$id,
$recipient,
$json
)`,
{
$id: id,
$recipient: recipient || '',
$json: objectToJSON(data),
}
);
} }
async function updateGuardNodes(nodes) { async function updateGuardNodes(nodes) {
await db.run('BEGIN TRANSACTION;'); await db.run('BEGIN TRANSACTION;');
await db.run(`DELETE FROM ${GUARD_NODE_TABLE}`); await db.run(`DELETE FROM ${GUARD_NODE_TABLE}`);
await Promise.all(nodes.map(edkey => await Promise.all(
nodes.map(edkey =>
db.run( db.run(
`INSERT INTO ${GUARD_NODE_TABLE} ( `INSERT INTO ${GUARD_NODE_TABLE} (
ed25519PubKey ed25519PubKey
) values ($ed25519PubKey)`, ) values ($ed25519PubKey)`,
{ {
$ed25519PubKey: edkey, $ed25519PubKey: edkey,
} }
)
) )
);
));
await db.run('END TRANSACTION;'); await db.run('END TRANSACTION;');
} }
async function getPrimaryDeviceFor(secondaryDevicePubKey) { async function getPrimaryDeviceFor(secondaryDevicePubKey) {

@ -7,7 +7,6 @@ const { lokiRpc } = require('./loki_rpc');
const DEFAULT_CONNECTIONS = 3; const DEFAULT_CONNECTIONS = 3;
const MAX_ACCEPTABLE_FAILURES = 1; const MAX_ACCEPTABLE_FAILURES = 1;
const LOKI_LONGPOLL_HEADER = 'X-Loki-Long-Poll';
function sleepFor(time) { function sleepFor(time) {
return new Promise(resolve => { return new Promise(resolve => {
@ -283,7 +282,6 @@ class LokiMessageAPI {
!stopPollingResult && !stopPollingResult &&
successiveFailures < MAX_ACCEPTABLE_FAILURES successiveFailures < MAX_ACCEPTABLE_FAILURES
) { ) {
// TODO: Revert back to using snode address instead of IP // TODO: Revert back to using snode address instead of IP
try { try {
// in general, I think we want exceptions to bubble up // in general, I think we want exceptions to bubble up
@ -339,7 +337,6 @@ class LokiMessageAPI {
// Always wait a bit as we are no longer long-polling // Always wait a bit as we are no longer long-polling
await sleepFor(Math.max(successiveFailures, 2) * 1000); await sleepFor(Math.max(successiveFailures, 2) * 1000);
} }
if (successiveFailures >= MAX_ACCEPTABLE_FAILURES) { if (successiveFailures >= MAX_ACCEPTABLE_FAILURES) {
const remainingSwarmSnodes = await lokiSnodeAPI.unreachableNode( const remainingSwarmSnodes = await lokiSnodeAPI.unreachableNode(

@ -1,5 +1,5 @@
/* global log, libloki, textsecure, getStoragePubKey, lokiSnodeAPI, StringView, /* 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 nodeFetch = require('node-fetch');
const https = require('https'); const https = require('https');
@ -12,9 +12,8 @@ const snodeHttpsAgent = new https.Agent({
const LOKI_EPHEMKEY_HEADER = 'X-Loki-EphemKey'; const LOKI_EPHEMKEY_HEADER = 'X-Loki-EphemKey';
const endpointBase = '/storage_rpc/v1'; const endpointBase = '/storage_rpc/v1';
// Request index for debugging // Request index for debugging
let onion_req_idx = 0; let onionReqIdx = 0;
const decryptResponse = async (response, address) => { const decryptResponse = async (response, address) => {
let plaintext = false; let plaintext = false;
@ -36,7 +35,6 @@ const decryptResponse = async (response, address) => {
const timeoutDelay = ms => new Promise(resolve => setTimeout(resolve, ms)); const timeoutDelay = ms => new Promise(resolve => setTimeout(resolve, ms));
const encryptForNode = async (node, payload) => { const encryptForNode = async (node, payload) => {
const textEncoder = new TextEncoder(); const textEncoder = new TextEncoder();
const plaintext = textEncoder.encode(payload); const plaintext = textEncoder.encode(payload);
@ -49,64 +47,72 @@ const encryptForNode = async (node, payload) => {
ephemeral.privKey ephemeral.privKey
); );
const salt = window.Signal.Crypto.bytesFromString("LOKI"); const salt = window.Signal.Crypto.bytesFromString('LOKI');
let key = await crypto.subtle.importKey('raw', salt, {name: 'HMAC', hash: {name: 'SHA-256'}}, false, ['sign']); const key = await crypto.subtle.importKey(
let symmetricKey = await crypto.subtle.sign( {name: 'HMAC', hash: 'SHA-256'}, key, ephemeralSecret); 'raw',
salt,
{ name: 'HMAC', hash: { name: 'SHA-256' } },
false,
['sign']
);
const symmetricKey = await crypto.subtle.sign(
{ name: 'HMAC', hash: 'SHA-256' },
key,
ephemeralSecret
);
const ciphertext = await window.libloki.crypto.EncryptGCM( const ciphertext = await window.libloki.crypto.EncryptGCM(
symmetricKey, symmetricKey,
plaintext plaintext
); );
return {ciphertext, symmetricKey, "ephemeral_key": ephemeral.pubKey}; return { ciphertext, symmetricKey, ephemeral_key: ephemeral.pubKey };
};
}
// Returns the actual ciphertext, symmetric key that will be used // Returns the actual ciphertext, symmetric key that will be used
// for decryption, and an ephemeral_key to send to the next hop // for decryption, and an ephemeral_key to send to the next hop
const encryptForDestination = async (node, payload) => { const encryptForDestination = async (node, payload) => {
// Do we still need "headers"? // Do we still need "headers"?
const req_str = JSON.stringify({"body": payload, "headers": ""}); const reqStr = JSON.stringify({ body: payload, headers: '' });
return await encryptForNode(node, req_str); return encryptForNode(node, reqStr);
} };
// `ctx` holds info used by `node` to relay further // `ctx` holds info used by `node` to relay further
const encryptForRelay = async (node, next_node, ctx) => { const encryptForRelay = async (node, nextNode, ctx) => {
const payload = ctx.ciphertext; const payload = ctx.ciphertext;
const req_json = { const reqJson = {
"ciphertext": dcodeIO.ByteBuffer.wrap(payload).toString('base64'), ciphertext: dcodeIO.ByteBuffer.wrap(payload).toString('base64'),
"ephemeral_key": StringView.arrayBufferToHex(ctx.ephemeral_key), ephemeral_key: StringView.arrayBufferToHex(ctx.ephemeral_key),
"destination": next_node.pubkey_ed25519, destination: nextNode.pubkey_ed25519,
} };
const req_str = JSON.stringify(req_json);
return await encryptForNode(node, req_str); const reqStr = JSON.stringify(reqJson);
} return encryptForNode(node, reqStr);
};
const BAD_PATH = "bad_path"; const BAD_PATH = 'bad_path';
// May return false BAD_PATH, indicating that we should try a new // May return false BAD_PATH, indicating that we should try a new
const sendOnionRequest = async (req_idx, nodePath, targetNode, plaintext) => { const sendOnionRequest = async (reqIdx, nodePath, targetNode, plaintext) => {
log.info('Sending an onion request');
log.info("Sending an onion request");
let ctx_1 = await encryptForDestination(targetNode, plaintext); const ctx1 = await encryptForDestination(targetNode, plaintext);
let ctx_2 = await encryptForRelay(nodePath[2], targetNode, ctx_1); const ctx2 = await encryptForRelay(nodePath[2], targetNode, ctx1);
let ctx_3 = await encryptForRelay(nodePath[1], nodePath[2], ctx_2); const ctx3 = await encryptForRelay(nodePath[1], nodePath[2], ctx2);
let ctx_4 = await encryptForRelay(nodePath[0], nodePath[1], ctx_3); const ctx4 = await encryptForRelay(nodePath[0], nodePath[1], ctx3);
const ciphertext_base64 = dcodeIO.ByteBuffer.wrap(ctx_4.ciphertext).toString('base64'); const ciphertextBase64 = dcodeIO.ByteBuffer.wrap(ctx4.ciphertext).toString(
'base64'
);
const payload = { const payload = {
"ciphertext": ciphertext_base64, ciphertext: ciphertextBase64,
"ephemeral_key": StringView.arrayBufferToHex(ctx_4.ephemeral_key), ephemeral_key: StringView.arrayBufferToHex(ctx4.ephemeral_key),
} };
const fetchOptions = { const fetchOptions = {
method: 'POST', method: 'POST',
@ -120,42 +126,44 @@ const sendOnionRequest = async (req_idx, nodePath, targetNode, plaintext) => {
const response = await nodeFetch(url, fetchOptions); const response = await nodeFetch(url, fetchOptions);
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1'; process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1';
return await processOnionResponse(req_idx, response, ctx_1.symmetricKey, true); return processOnionResponse(reqIdx, response, ctx1.symmetricKey, true);
} };
// Process a response as it arrives from `nodeFetch`, handling // Process a response as it arrives from `nodeFetch`, handling
// http errors and attempting to decrypt the body with `shared_key` // http errors and attempting to decrypt the body with `sharedKey`
const processOnionResponse = async (req_idx, response, shared_key, use_aes_gcm) => { const processOnionResponse = async (reqIdx, response, sharedKey, useAesGcm) => {
log.info(`(${reqIdx}) [path] processing onion response`);
console.log(`(${req_idx}) [path] processing onion response`);
// detect SNode is not ready (not in swarm; not done syncing) // detect SNode is not ready (not in swarm; not done syncing)
if (response.status === 503) { if (response.status === 503) {
console.warn("Got 503: snode not ready"); log.warn('Got 503: snode not ready');
return BAD_PATH; return BAD_PATH;
} }
if (response.status == 504) { if (response.status === 504) {
log.warn('Got 504: Gateway timeout'); log.warn('Got 504: Gateway timeout');
return BAD_PATH; return BAD_PATH;
} }
if (response.status == 404) { if (response.status === 404) {
// Why would we get this error on testnet? // Why would we get this error on testnet?
log.warn('Got 404: Gateway timeout'); log.warn('Got 404: Gateway timeout');
return BAD_PATH; return BAD_PATH;
} }
if (response.status !== 200) { if (response.status !== 200) {
log.warn('lokiRpc sendToProxy fetch unhandled error code:', response.status); log.warn(
return; 'lokiRpc sendToProxy fetch unhandled error code:',
response.status
);
return false;
} }
const ciphertext = await response.text(); const ciphertext = await response.text();
if (!ciphertext) { if (!ciphertext) {
log.warn("[path]: Target node return empty ciphertext"); log.warn('[path]: Target node return empty ciphertext');
return; return false;
} }
let plaintext; let plaintext;
@ -166,27 +174,20 @@ const processOnionResponse = async (req_idx, response, shared_key, use_aes_gcm)
'base64' 'base64'
).toArrayBuffer(); ).toArrayBuffer();
const decrypt_fn = use_aes_gcm ? window.libloki.crypto.DecryptGCM : window.libloki.crypto.DHDecrypt; const decryptFn = useAesGcm
? window.libloki.crypto.DecryptGCM
: window.libloki.crypto.DHDecrypt;
const plaintextBuffer = await decrypt_fn( const plaintextBuffer = await decryptFn(sharedKey, ciphertextBuffer);
shared_key,
ciphertextBuffer
);
const textDecoder = new TextDecoder(); const textDecoder = new TextDecoder();
plaintext = textDecoder.decode(plaintextBuffer); plaintext = textDecoder.decode(plaintextBuffer);
} catch(e) { } catch (e) {
log.error( log.error(`(${reqIdx}) lokiRpc sendToProxy decode error`);
'lokiRpc sendToProxy decode error',
e.code,
e.message,
`from ${randSnode.ip}:${randSnode.port} ciphertext:`,
ciphertext
);
if (ciphertextBuffer) { if (ciphertextBuffer) {
log.error('ciphertextBuffer', ciphertextBuffer); log.error('ciphertextBuffer', ciphertextBuffer);
} }
return; return false;
} }
try { try {
@ -194,14 +195,11 @@ const processOnionResponse = async (req_idx, response, shared_key, use_aes_gcm)
// emulate nodeFetch response... // emulate nodeFetch response...
jsonRes.json = () => { jsonRes.json = () => {
try { try {
let res = JSON.parse(jsonRes.body); const res = JSON.parse(jsonRes.body);
return res; return res;
} catch (e) { } catch (e) {
log.error( log.error(
'lokiRpc sendToProxy parse error', `(${reqIdx}) lokiRpc sendToProxy parse error json: `,
e.code,
e.message,
`from ${randSnode.ip}:${randSnode.port} json:`,
jsonRes.body jsonRes.body
); );
} }
@ -213,22 +211,24 @@ const processOnionResponse = async (req_idx, response, shared_key, use_aes_gcm)
'lokiRpc sendToProxy parse error', 'lokiRpc sendToProxy parse error',
e.code, e.code,
e.message, e.message,
`from ${randSnode.ip}:${randSnode.port} json:`, `json:`,
plaintext plaintext
); );
return false;
return;
} }
};
}
const sendToProxy = async (options = {}, targetNode, retryNumber = 0) => { const sendToProxy = async (options = {}, targetNode, retryNumber = 0) => {
const _ = window.Lodash;
let snodePool = await lokiSnodeAPI.getRandomSnodePool(); const snodePool = await lokiSnodeAPI.getRandomSnodePool();
if (snodePool.length < 2) { if (snodePool.length < 2) {
console.error("Not enough service nodes for a proxy request, only have: ", snodePool.length); log.error(
return; 'Not enough service nodes for a proxy request, only have: ',
snodePool.length
);
return false;
} }
// Making sure the proxy node is not the same as the target node: // Making sure the proxy node is not the same as the target node:
@ -467,34 +467,43 @@ const lokiFetch = async (url, options = {}, targetNode = null) => {
}; };
try { try {
// Absence of targetNode indicates that we want a direct connection // Absence of targetNode indicates that we want a direct connection
// (e.g. to connect to a seed node for the first time) // (e.g. to connect to a seed node for the first time)
if (window.lokiFeatureFlags.useOnionRequests && targetNode) { if (window.lokiFeatureFlags.useOnionRequests && targetNode) {
// Loop until the result is not BAD_PATH // Loop until the result is not BAD_PATH
// eslint-disable-next-line no-constant-condition
while (true) { while (true) {
// Get a path excluding `targetNode`: // Get a path excluding `targetNode`:
// eslint-disable-next-line no-await-in-loop
const path = await lokiSnodeAPI.getOnionPath(targetNode); const path = await lokiSnodeAPI.getOnionPath(targetNode);
const this_idx = onion_req_idx++; const thisIdx = onionReqIdx;
onionReqIdx += 1;
log.info(`(${this_idx}) using path ${path[0].ip}:${path[0].port} -> ${path[1].ip}:${path[1].port} -> ${path[2].ip}:${path[2].port} => ${targetNode.ip}:${targetNode.port}`);
log.info(
`(${thisIdx}) using path ${path[0].ip}:${path[0].port} -> ${
path[1].ip
}:${path[1].port} -> ${path[2].ip}:${path[2].port} => ${
targetNode.ip
}:${targetNode.port}`
);
const result = await sendOnionRequest(this_idx, path, targetNode, fetchOptions.body); // eslint-disable-next-line no-await-in-loop
const result = await sendOnionRequest(
thisIdx,
path,
targetNode,
fetchOptions.body
);
if (result == BAD_PATH) { if (result === BAD_PATH) {
log.error("[path] Error on the path"); log.error('[path] Error on the path');
lokiSnodeAPI.markPathAsBad(path); lokiSnodeAPI.markPathAsBad(path);
} else { } else {
return result ? result.json() : false; return result ? result.json() : false;
} }
} }
} }
if (window.lokiFeatureFlags.useSnodeProxy && targetNode) { if (window.lokiFeatureFlags.useSnodeProxy && targetNode) {
const result = await sendToProxy(fetchOptions, targetNode); const result = await sendToProxy(fetchOptions, targetNode);
// if not result, maybe we should throw?? // if not result, maybe we should throw??

@ -1,12 +1,11 @@
/* eslint-disable class-methods-use-this */ /* eslint-disable class-methods-use-this */
/* global window, textsecure, ConversationController, _, log, clearTimeout */ /* global window, textsecure, ConversationController, _, log, clearTimeout, process */
const is = require('@sindresorhus/is'); const is = require('@sindresorhus/is');
const { lokiRpc } = require('./loki_rpc'); const { lokiRpc } = require('./loki_rpc');
const nodeFetch = require('node-fetch'); const nodeFetch = require('node-fetch');
const RANDOM_SNODES_TO_USE_FOR_PUBKEY_SWARM = 3; const RANDOM_SNODES_TO_USE_FOR_PUBKEY_SWARM = 3;
const RANDOM_SNODES_POOL_SIZE = 1024;
const SEED_NODE_RETRIES = 3; const SEED_NODE_RETRIES = 3;
class LokiSnodeAPI { class LokiSnodeAPI {
@ -25,27 +24,25 @@ class LokiSnodeAPI {
} }
async getRandomSnodePool() { async getRandomSnodePool() {
if (this.randomSnodePool.length === 0) { if (this.randomSnodePool.length === 0) {
await this.refreshRandomPool(); await this.refreshRandomPool();
} }
return this.randomSnodePool; return this.randomSnodePool;
} }
async test_guard_node(snode) { async testGuardNode(snode) {
log.info('Testing a candidate guard node ', snode);
log.info("[maxim] Testing a candidate guard node ", snode);
// Send a post request and make sure it is OK // Send a post request and make sure it is OK
const endpoint = "/storage_rpc/v1"; const endpoint = '/storage_rpc/v1';
const url = `https://${snode.ip}:${snode.port}${endpoint}`; const url = `https://${snode.ip}:${snode.port}${endpoint}`;
const our_pk = textsecure.storage.user.getNumber(); const ourPK = textsecure.storage.user.getNumber();
const pubKey = window.getStoragePubKey(our_pk); // truncate if testnet const pubKey = window.getStoragePubKey(ourPK); // truncate if testnet
const method = 'get_snodes_for_pubkey'; const method = 'get_snodes_for_pubkey';
const params = { pubKey } const params = { pubKey };
const body = { const body = {
jsonrpc: '2.0', jsonrpc: '2.0',
id: '0', id: '0',
@ -55,9 +52,9 @@ class LokiSnodeAPI {
const fetchOptions = { const fetchOptions = {
method: 'POST', method: 'POST',
body: JSON.stringify(body), body: JSON.stringify(body),
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
timeout: 10000 // 10s, we want a smaller timeout for testing timeout: 10000, // 10s, we want a smaller timeout for testing
}; };
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
@ -68,7 +65,7 @@ class LokiSnodeAPI {
response = await nodeFetch(url, fetchOptions); response = await nodeFetch(url, fetchOptions);
} catch (e) { } catch (e) {
if (e.type === 'request-timeout') { if (e.type === 'request-timeout') {
log.warn(`[maxim] test timeout for node,`, snode); log.warn(`test timeout for node,`, snode);
} }
return false; return false;
} finally { } finally {
@ -83,152 +80,164 @@ class LokiSnodeAPI {
} }
async selectGuardNodes() { async selectGuardNodes() {
const _ = window.Lodash; const _ = window.Lodash;
let node_pool = await this.getRandomSnodePool(); const nodePool = await this.getRandomSnodePool();
if (node_pool.length === 0) { if (nodePool.length === 0) {
log.error(`Could not select guarn nodes: node pool is empty`) log.error(`Could not select guarn nodes: node pool is empty`);
return []; return [];
} }
let shuffled = _.shuffle(node_pool); const shuffled = _.shuffle(nodePool);
let guard_nodes = []; let guardNodes = [];
const DESIRED_GUARD_COUNT = 3; const DESIRED_GUARD_COUNT = 3;
while (guard_nodes.length < 3) { // The use of await inside while is intentional:
// we only want to repeat if the await fails
// eslint-disable-next-line-no-await-in-loop
while (guardNodes.length < 3) {
if (shuffled.length < DESIRED_GUARD_COUNT) { if (shuffled.length < DESIRED_GUARD_COUNT) {
log.error(`Not enought nodes in the pool`); log.error(`Not enought nodes in the pool`);
break; break;
} }
const candidate_nodes = shuffled.splice(0, DESIRED_GUARD_COUNT); const candidateNodes = shuffled.splice(0, DESIRED_GUARD_COUNT);
// Test all three nodes at once // Test all three nodes at once
const idx_ok = await Promise.all(candidate_nodes.map(n => this.test_guard_node(n))); // eslint-disable-next-line no-await-in-loop
const idxOk = await Promise.all(
candidateNodes.map(n => this.testGuardNode(n))
);
const good_nodes = _.zip(idx_ok, candidate_nodes).filter(x => x[0]).map(x => x[1]); const goodNodes = _.zip(idxOk, candidateNodes)
.filter(x => x[0])
.map(x => x[1]);
guard_nodes = _.concat(guard_nodes, good_nodes); guardNodes = _.concat(guardNodes, goodNodes);
} }
if (guard_nodes.length < DESIRED_GUARD_COUNT) { if (guardNodes.length < DESIRED_GUARD_COUNT) {
log.error(`COULD NOT get enough guard nodes, only have: ${guard_nodes.length}`); log.error(
debugger; `COULD NOT get enough guard nodes, only have: ${guardNodes.length}`
);
} }
console.log("new guard nodes: ", guard_nodes); log.info('new guard nodes: ', guardNodes);
const edKeys = guard_nodes.map(n => n.pubkey_ed25519); const edKeys = guardNodes.map(n => n.pubkey_ed25519);
await window.libloki.storage.updateGuardNodes(edKeys); await window.libloki.storage.updateGuardNodes(edKeys);
return guard_nodes; return guardNodes;
} }
async getOnionPath(toExclude = null) { async getOnionPath(toExclude = null) {
const _ = window.Lodash; const _ = window.Lodash;
const good_paths = this.onionPaths.filter(x => !x.bad); const goodPaths = this.onionPaths.filter(x => !x.bad);
if (good_paths.length < 2) { if (goodPaths.length < 2) {
log.error(`Must have at least 2 good onion paths, actual: ${good_paths.length}`); log.error(
`Must have at least 2 good onion paths, actual: ${goodPaths.length}`
);
await this.buildNewOnionPaths(); await this.buildNewOnionPaths();
} }
const paths = _.shuffle(good_paths); const paths = _.shuffle(goodPaths);
if (!toExclude) { if (!toExclude) {
return paths[0]; return paths[0];
} }
// Select a path that doesn't contain `toExclude` // Select a path that doesn't contain `toExclude`
const other_paths = paths.filter(path => !_.some(path, node => node.pubkey_ed25519 == toExclude.pubkey_ed25519)); const otherPaths = paths.filter(
path =>
!_.some(path, node => node.pubkey_ed25519 === toExclude.pubkey_ed25519)
);
if (other_paths.length === 0) { if (otherPaths.length === 0) {
// This should never happen! // This should never happen!
log.error("No onion paths available after filtering"); log.error('No onion paths available after filtering');
} }
return other_paths[0].path; return otherPaths[0].path;
} }
async markPathAsBad(path) { async markPathAsBad(path) {
this.onionPaths.forEach(p => { this.onionPaths.forEach(p => {
if (p.path == path) { if (p.path === path) {
// eslint-disable-next-line no-param-reassign
p.bad = true; p.bad = true;
} }
}) });
} }
async buildNewOnionPaths() { async buildNewOnionPaths() {
// Note: this function may be called concurrently, so // Note: this function may be called concurrently, so
// might consider blocking the other calls // might consider blocking the other calls
const _ = window.Lodash; const _ = window.Lodash;
log.info("building new onion paths"); log.info('building new onion paths');
const all_nodes = await this.getRandomSnodePool();
log.info("[maxim] all nodes: ", all_nodes.length); const allNodes = await this.getRandomSnodePool();
if (this.guardNodes.length == 0) {
if (this.guardNodes.length === 0) {
// Not cached, load from DB // Not cached, load from DB
let nodes = await window.libloki.storage.getGuardNodes(); const nodes = await window.libloki.storage.getGuardNodes();
if (nodes.length == 0) { if (nodes.length === 0) {
log.warn("no guard nodes in DB. Will be selecting new guards nodes..."); log.warn('no guard nodes in DB. Will be selecting new guards nodes...');
} else { } else {
// We only store the nodes' keys, need to find full entries: // We only store the nodes' keys, need to find full entries:
let ed_keys = nodes.map(x => x.ed25519PubKey); const edKeys = nodes.map(x => x.ed25519PubKey);
this.guardNodes = all_nodes.filter(x => ed_keys.indexOf(x.pubkey_ed25519) !== -1); this.guardNodes = allNodes.filter(
x => edKeys.indexOf(x.pubkey_ed25519) !== -1
);
if (this.guardNodes.length < ed_keys.length) { if (this.guardNodes.length < edKeys.length) {
log.warn(`could not find some guard nodes: ${this.guardNodes.length}/${ed_keys.length}`); log.warn(
`could not find some guard nodes: ${this.guardNodes.length}/${
edKeys.length
}`
);
} }
} }
// If guard nodes is still empty (the old nodes are now invalid), select new ones: // If guard nodes is still empty (the old nodes are now invalid), select new ones:
if (this.guardNodes.length == 0 || true) { if (this.guardNodes.length === 0) {
this.guardNodes = await this.selectGuardNodes(); this.guardNodes = await this.selectGuardNodes();
} }
} }
// TODO: select one guard node and 2 other nodes randomly // TODO: select one guard node and 2 other nodes randomly
let other_nodes = _.difference(all_nodes, this.guardNodes); let otherNodes = _.difference(allNodes, this.guardNodes);
if (other_nodes.length < 2) { if (otherNodes.length < 2) {
log.error("Too few nodes to build an onion path!"); log.error('Too few nodes to build an onion path!');
return; return;
} }
other_nodes = _.shuffle(other_nodes); otherNodes = _.shuffle(otherNodes);
const guards = _.shuffle(this.guardNodes); const guards = _.shuffle(this.guardNodes);
// Create path for every guard node: // Create path for every guard node:
// Each path needs 2 nodes in addition to the guard node: // Each path needs 2 nodes in addition to the guard node:
const max_path = Math.floor(Math.min(guards.length, other_nodes.length / 2)); const maxPath = Math.floor(Math.min(guards.length, otherNodes.length / 2));
// TODO: might want to keep some of the existing paths // TODO: might want to keep some of the existing paths
this.onionPaths = []; this.onionPaths = [];
for (let i = 0; i < max_path; i++) { for (let i = 0; i < maxPath; i += 1) {
const path = [guards[i], other_nodes[i * 2], other_nodes[i * 2 + 1]]; const path = [guards[i], otherNodes[i * 2], otherNodes[i * 2 + 1]];
this.onionPaths.push({path, bad: false}); this.onionPaths.push({ path, bad: false });
} }
log.info("Built onion paths: ", this.onionPaths); log.info('Built onion paths: ', this.onionPaths);
} }
async getRandomSnodeAddress() { async getRandomSnodeAddress() {
@ -253,7 +262,6 @@ class LokiSnodeAPI {
let timeoutTimer = null; let timeoutTimer = null;
// private retry container // private retry container
const trySeedNode = async (consecutiveErrors = 0) => { const trySeedNode = async (consecutiveErrors = 0) => {
// Removed limit until there is a way to get snode info // Removed limit until there is a way to get snode info
// for individual nodes (needed for guard nodes); this way // for individual nodes (needed for guard nodes); this way
// we get all active nodes // we get all active nodes

@ -35,12 +35,21 @@
} }
async function EncryptGCM(symmetricKey, plaintext) { async function EncryptGCM(symmetricKey, plaintext) {
const nonce = crypto.getRandomValues(new Uint8Array(NONCE_LENGTH)); const nonce = crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
const key = await crypto.subtle.importKey('raw', symmetricKey, {name: 'AES-GCM'}, false, ['encrypt']); const key = await crypto.subtle.importKey(
'raw',
symmetricKey,
{ name: 'AES-GCM' },
false,
['encrypt']
);
const ciphertext = await crypto.subtle.encrypt({name: 'AES-GCM', iv: nonce, tagLength: 128}, key, plaintext); const ciphertext = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv: nonce, tagLength: 128 },
key,
plaintext
);
const ivAndCiphertext = new Uint8Array( const ivAndCiphertext = new Uint8Array(
NONCE_LENGTH + ciphertext.byteLength NONCE_LENGTH + ciphertext.byteLength
@ -53,14 +62,22 @@
} }
async function DecryptGCM(symmetricKey, ivAndCiphertext) { async function DecryptGCM(symmetricKey, ivAndCiphertext) {
const nonce = ivAndCiphertext.slice(0, NONCE_LENGTH);
const ciphertext = ivAndCiphertext.slice(NONCE_LENGTH);
const nonce = ivAndCiphertext.slice(0, NONCE_LENGTH); const key = await crypto.subtle.importKey(
const ciphertext = ivAndCiphertext.slice(NONCE_LENGTH); 'raw',
symmetricKey,
const key = await crypto.subtle.importKey('raw', symmetricKey, {name: 'AES-GCM'}, false, ['decrypt']); { name: 'AES-GCM' },
false,
return await crypto.subtle.decrypt({name: 'AES-GCM', iv: nonce}, key, ciphertext); ['decrypt']
);
return crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: nonce },
key,
ciphertext
);
} }
async function DHDecrypt(symmetricKey, ivAndCiphertext) { async function DHDecrypt(symmetricKey, ivAndCiphertext) {
@ -137,7 +154,7 @@
} }
function generateEphemeralKeyPair() { function generateEphemeralKeyPair() {
let keys = libsignal.Curve.generateKeyPair(); const keys = libsignal.Curve.generateKeyPair();
// Signal protocol prepends with "0x05" // Signal protocol prepends with "0x05"
keys.pubKey = keys.pubKey.slice(1); keys.pubKey = keys.pubKey.slice(1);
return keys; return keys;
@ -145,7 +162,6 @@
class LokiSnodeChannel { class LokiSnodeChannel {
constructor() { constructor() {
this._ephemeralKeyPair = generateEphemeralKeyPair(); this._ephemeralKeyPair = generateEphemeralKeyPair();
this._ephemeralPubKeyHex = StringView.arrayBufferToHex( this._ephemeralPubKeyHex = StringView.arrayBufferToHex(
this._ephemeralKeyPair.pubKey this._ephemeralKeyPair.pubKey

@ -274,7 +274,7 @@
getSecondaryDevicesFor, getSecondaryDevicesFor,
getPrimaryDeviceMapping, getPrimaryDeviceMapping,
getGuardNodes, getGuardNodes,
updateGuardNodes updateGuardNodes,
}; };
// Libloki protocol store // Libloki protocol store

@ -420,7 +420,11 @@ Promise.prototype.ignore = function() {
this.then(() => {}); this.then(() => {});
}; };
if (config.environment.includes('test') && !config.environment == "swarm-testing1" && !config.environment == "swarm-testing2") { if (
config.environment.includes('test') &&
!config.environment === 'swarm-testing1' &&
!config.environment === 'swarm-testing2'
) {
const isWindows = process.platform === 'win32'; const isWindows = process.platform === 'win32';
/* eslint-disable global-require, import/no-extraneous-dependencies */ /* eslint-disable global-require, import/no-extraneous-dependencies */
window.test = { window.test = {

Loading…
Cancel
Save