API implementation for LNS
This commit is contained in:
parent
24114bab8d
commit
932ea23ceb
|
@ -79,7 +79,7 @@ const BAD_PATH = 'bad_path';
|
|||
|
||||
// May return false BAD_PATH, indicating that we should try a new
|
||||
const sendOnionRequest = async (reqIdx, nodePath, targetNode, plaintext) => {
|
||||
log.info('Sending an onion request');
|
||||
log.debug('Sending an onion request');
|
||||
|
||||
const ctx1 = await encryptForDestination(targetNode, plaintext);
|
||||
const ctx2 = await encryptForRelay(nodePath[2], targetNode, ctx1);
|
||||
|
@ -113,7 +113,7 @@ const sendOnionRequest = async (reqIdx, nodePath, targetNode, plaintext) => {
|
|||
// 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) => {
|
||||
log.info(`(${reqIdx}) [path] processing onion response`);
|
||||
log.debug(`(${reqIdx}) [path] processing onion response`);
|
||||
|
||||
// detect SNode is not ready (not in swarm; not done syncing)
|
||||
if (response.status === 503) {
|
||||
|
@ -427,7 +427,7 @@ const lokiFetch = async (url, options = {}, targetNode = null) => {
|
|||
const thisIdx = onionReqIdx;
|
||||
onionReqIdx += 1;
|
||||
|
||||
log.info(
|
||||
log.debug(
|
||||
`(${thisIdx}) using path ${path[0].ip}:${path[0].port} -> ${
|
||||
path[1].ip
|
||||
}:${path[1].port} -> ${path[2].ip}:${path[2].port} => ${
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* eslint-disable class-methods-use-this */
|
||||
/* global window, textsecure, ConversationController, _, log, clearTimeout, process */
|
||||
/* global window, textsecure, ConversationController, _, log, clearTimeout, process, Buffer, StringView, dcodeIO */
|
||||
|
||||
const is = require('@sindresorhus/is');
|
||||
const { lokiRpc } = require('./loki_rpc');
|
||||
|
@ -270,6 +270,16 @@ class LokiSnodeAPI {
|
|||
];
|
||||
}
|
||||
|
||||
async getNodesMinVersion(minVersion) {
|
||||
const _ = window.Lodash;
|
||||
|
||||
return _.flatten(
|
||||
_.entries(this.versionPools)
|
||||
.filter(v => semver.gte(v[0], minVersion))
|
||||
.map(v => v[1])
|
||||
);
|
||||
}
|
||||
|
||||
// use nodes that support more than 1mb
|
||||
async getRandomProxySnodeAddress() {
|
||||
/* resolve random snode */
|
||||
|
@ -624,6 +634,98 @@ class LokiSnodeAPI {
|
|||
return newSwarmNodes;
|
||||
}
|
||||
|
||||
// helper function
|
||||
async _requestLnsMapping(node, nameHash) {
|
||||
log.debug('[lns] lns requests to {}:{}', node.ip, node.port);
|
||||
|
||||
try {
|
||||
// Hm, in case of proxy/onion routing we
|
||||
// are not even using ip/port...
|
||||
return lokiRpc(
|
||||
`https://${node.ip}`,
|
||||
node.port,
|
||||
'get_lns_mapping',
|
||||
{
|
||||
name_hash: nameHash,
|
||||
},
|
||||
{},
|
||||
'/storage_rpc/v1',
|
||||
node
|
||||
);
|
||||
} catch (e) {
|
||||
log.warn('exception caught making lns requests to a node', node, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async getLnsMapping(lnsName) {
|
||||
const _ = window.Lodash;
|
||||
|
||||
const input = Buffer.from(lnsName);
|
||||
|
||||
const output = await window.blake2b(input);
|
||||
|
||||
const nameHash = dcodeIO.ByteBuffer.wrap(output).toString('base64');
|
||||
|
||||
// Get nodes capable of doing LNS
|
||||
let lnsNodes = await this.getNodesMinVersion('2.0.3');
|
||||
lnsNodes = _.shuffle(lnsNodes);
|
||||
|
||||
// Loop until 3 confirmations
|
||||
|
||||
// We don't trust any single node, so we accumulate
|
||||
// answers here and select a dominating answer
|
||||
const allResults = [];
|
||||
let ciphertextHex = null;
|
||||
|
||||
while (!ciphertextHex) {
|
||||
if (lnsNodes.length < 3) {
|
||||
log.error('Not enough nodes for lns lookup');
|
||||
return false;
|
||||
}
|
||||
|
||||
// extract 3 and make requests in parallel
|
||||
const nodes = lnsNodes.splice(0, 3);
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const results = await Promise.all(
|
||||
nodes.map(node => this._requestLnsMapping(node, nameHash))
|
||||
);
|
||||
|
||||
results.forEach(res => {
|
||||
if (
|
||||
res &&
|
||||
res.result &&
|
||||
res.result.status === 'OK' &&
|
||||
res.result.entries &&
|
||||
res.result.entries.length > 0
|
||||
) {
|
||||
allResults.push(results[0].result.entries[0].encrypted_value);
|
||||
}
|
||||
});
|
||||
|
||||
const [winner, count] = _.maxBy(
|
||||
_.entries(_.countBy(allResults)),
|
||||
x => x[1]
|
||||
);
|
||||
|
||||
if (count >= 3) {
|
||||
// eslint-disable-next-lint prefer-destructuring
|
||||
ciphertextHex = winner;
|
||||
}
|
||||
}
|
||||
|
||||
const ciphertext = new Uint8Array(
|
||||
StringView.hexToArrayBuffer(ciphertextHex)
|
||||
);
|
||||
|
||||
const res = await window.decryptLnsEntry(lnsName, ciphertext);
|
||||
|
||||
const pubkey = StringView.arrayBufferToHex(res);
|
||||
|
||||
return pubkey;
|
||||
}
|
||||
|
||||
async getSnodesForPubkey(snode, pubKey) {
|
||||
try {
|
||||
const result = await lokiRpc(
|
||||
|
|
53
main.js
53
main.js
|
@ -82,6 +82,13 @@ const {
|
|||
} = require('./app/protocol_filter');
|
||||
const { installPermissionsHandler } = require('./app/permissions');
|
||||
|
||||
const _sodium = require('libsodium-wrappers');
|
||||
|
||||
async function getSodium() {
|
||||
await _sodium.ready;
|
||||
return _sodium;
|
||||
}
|
||||
|
||||
let appStartInitialSpellcheckSetting = true;
|
||||
|
||||
async function getSpellCheckSetting() {
|
||||
|
@ -1119,6 +1126,52 @@ ipc.on('get-auto-update-setting', event => {
|
|||
event.returnValue = typeof configValue !== 'boolean' ? true : configValue;
|
||||
});
|
||||
|
||||
async function decryptLns(event, lnsName, ciphertext) {
|
||||
const sodium = await getSodium();
|
||||
|
||||
const salt = new Uint8Array(sodium.crypto_pwhash_SALTBYTES);
|
||||
|
||||
try {
|
||||
const key = sodium.crypto_pwhash(
|
||||
sodium.crypto_secretbox_KEYBYTES,
|
||||
lnsName,
|
||||
salt,
|
||||
sodium.crypto_pwhash_OPSLIMIT_MODERATE,
|
||||
sodium.crypto_pwhash_MEMLIMIT_MODERATE,
|
||||
sodium.crypto_pwhash_ALG_ARGON2ID13
|
||||
);
|
||||
|
||||
const nonce = new Uint8Array(sodium.crypto_secretbox_NONCEBYTES);
|
||||
|
||||
const res = sodium.crypto_secretbox_open_easy(ciphertext, nonce, key);
|
||||
|
||||
// null as first parameter to indivate no error
|
||||
event.reply('decrypt-lns-response', null, res);
|
||||
} catch (err) {
|
||||
event.reply('decrypt-lns-response', err);
|
||||
}
|
||||
}
|
||||
|
||||
async function blake2bDigest(event, input) {
|
||||
const sodium = await getSodium();
|
||||
|
||||
try {
|
||||
const res = sodium.crypto_generichash(32, input);
|
||||
|
||||
event.reply('blake2b-digest-response', null, res);
|
||||
} catch (err) {
|
||||
event.reply('blake2b-digest-response', err);
|
||||
}
|
||||
}
|
||||
|
||||
ipc.on('blake2b-digest', (event, input) => {
|
||||
blake2bDigest(event, input);
|
||||
});
|
||||
|
||||
ipc.on('decrypt-lns-entry', (event, lnsName, ciphertext) => {
|
||||
decryptLns(event, lnsName, ciphertext);
|
||||
});
|
||||
|
||||
ipc.on('set-auto-update-setting', (event, enabled) => {
|
||||
userConfig.set('autoUpdate', !!enabled);
|
||||
|
||||
|
|
|
@ -90,6 +90,7 @@
|
|||
"js-sha512": "0.8.0",
|
||||
"js-yaml": "3.13.0",
|
||||
"jsbn": "1.1.0",
|
||||
"libsodium-wrappers": "^0.7.6",
|
||||
"linkify-it": "2.0.3",
|
||||
"lodash": "4.17.11",
|
||||
"mkdirp": "0.5.1",
|
||||
|
|
20
preload.js
20
preload.js
|
@ -95,6 +95,26 @@ window.wrapDeferred = deferredToPromise;
|
|||
const ipc = electron.ipcRenderer;
|
||||
const localeMessages = ipc.sendSync('locale-data');
|
||||
|
||||
window.blake2b = input =>
|
||||
new Promise((resolve, reject) => {
|
||||
ipc.once('blake2b-digest-response', (event, error, res) => {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
error ? reject(error) : resolve(res);
|
||||
});
|
||||
|
||||
ipc.send('blake2b-digest', input);
|
||||
});
|
||||
|
||||
window.decryptLnsEntry = (key, value) =>
|
||||
new Promise((resolve, reject) => {
|
||||
ipc.once('decrypt-lns-response', (event, error, res) => {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
error ? reject(error) : resolve(res);
|
||||
});
|
||||
|
||||
ipc.send('decrypt-lns-entry', key, value);
|
||||
});
|
||||
|
||||
window.updateZoomFactor = () => {
|
||||
const zoomFactor = window.getSettingValue('zoom-factor-setting') || 100;
|
||||
window.setZoomFactor(zoomFactor / 100);
|
||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -5972,6 +5972,18 @@ levn@^0.3.0, levn@~0.3.0:
|
|||
prelude-ls "~1.1.2"
|
||||
type-check "~0.3.2"
|
||||
|
||||
libsodium-wrappers@^0.7.6:
|
||||
version "0.7.6"
|
||||
resolved "https://registry.yarnpkg.com/libsodium-wrappers/-/libsodium-wrappers-0.7.6.tgz#baed4c16d4bf9610104875ad8a8e164d259d48fb"
|
||||
integrity sha512-OUO2CWW5bHdLr6hkKLHIKI4raEkZrf3QHkhXsJ1yCh6MZ3JDA7jFD3kCATNquuGSG6MjjPHQIQms0y0gBDzjQg==
|
||||
dependencies:
|
||||
libsodium "0.7.6"
|
||||
|
||||
libsodium@0.7.6:
|
||||
version "0.7.6"
|
||||
resolved "https://registry.yarnpkg.com/libsodium/-/libsodium-0.7.6.tgz#018b80c5728054817845fbffa554274441bda277"
|
||||
integrity sha512-hPb/04sEuLcTRdWDtd+xH3RXBihpmbPCsKW/Jtf4PsvdyKh+D6z2D2gvp/5BfoxseP+0FCOg66kE+0oGUE/loQ==
|
||||
|
||||
lie@*:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
|
||||
|
|
Loading…
Reference in New Issue