|
|
|
@ -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,
|
|
|
|
|
};
|
|
|
|
|