fix http request over onion

pull/1576/head
Audric Ackermann 4 years ago
parent cca4de710b
commit 710c9fa269
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -5,16 +5,8 @@
"contentProxyUrl": "", "contentProxyUrl": "",
"seedNodeList": [ "seedNodeList": [
{ {
"ip_url": "http://116.203.53.213:4433/", "url": "http://public.loki.foundation:38157/",
"url": "https://storage.seed1.loki.network:4433/" "ip_url": "http://144.76.164.202:38157/"
},
{
"ip_url": "http://212.199.114.66:4433/",
"url": "https://storage.seed3.loki.network:4433/"
},
{
"ip_url": "http://144.76.164.202:4433/",
"url": "https://public.loki.foundation:4433/"
} }
], ],
"updatesEnabled": false, "updatesEnabled": false,

@ -126,9 +126,10 @@
// If already exists we registered as a secondary device // If already exists we registered as a secondary device
if (!window.lokiFileServerAPI) { if (!window.lokiFileServerAPI) {
window.lokiFileServerAPIFactory = new window.LokiFileServerAPI(ourKey); window.lokiFileServerAPIFactory = new window.LokiFileServerAPI(ourKey);
window.lokiFileServerAPI = window.lokiFileServerAPIFactory.establishHomeConnection( // FIXME audric
window.getDefaultFileServer() // window.lokiFileServerAPI = window.lokiFileServerAPIFactory.establishHomeConnection(
); // window.getDefaultFileServer()
// );
} }
window.initialisedAPI = true; window.initialisedAPI = true;

@ -6,7 +6,10 @@ export interface CryptoInterface {
EncryptAESGCM: any; // AES-GCM EncryptAESGCM: any; // AES-GCM
_decodeSnodeAddressToPubKey: any; _decodeSnodeAddressToPubKey: any;
decryptToken: any; decryptToken: any;
encryptForPubkey: any; encryptForPubkey: (
publicKey: string,
data: Uint8Array
) => Promise<{ ciphertext: Uint8Array; symmetricKey: ArrayBuffer; ephemeralKey: ArrayBuffer }>;
generateEphemeralKeyPair: any; generateEphemeralKeyPair: any;
sha512: any; sha512: any;
} }

@ -50,13 +50,11 @@
return symmetricKey; return symmetricKey;
} }
// encryptForPubkey: string, payloadBytes: Uint8Array
async function encryptForPubkey(pubkeyX25519, payloadBytes) { async function encryptForPubkey(pubkeyX25519, payloadBytes) {
const ephemeral = await libloki.crypto.generateEphemeralKeyPair(); const ephemeral = await libloki.crypto.generateEphemeralKeyPair();
const snPubkey = StringView.hexToArrayBuffer(pubkeyX25519); const snPubkey = StringView.hexToArrayBuffer(pubkeyX25519);
const symmetricKey = await deriveSymmetricKey(snPubkey, ephemeral.privKey); const symmetricKey = await deriveSymmetricKey(snPubkey, ephemeral.privKey);
const ciphertext = await EncryptAESGCM(symmetricKey, payloadBytes); const ciphertext = await EncryptAESGCM(symmetricKey, payloadBytes);
return { ciphertext, symmetricKey, ephemeralKey: ephemeral.pubKey }; return { ciphertext, symmetricKey, ephemeralKey: ephemeral.pubKey };
@ -86,7 +84,6 @@
async function DecryptAESGCM(symmetricKey, ivAndCiphertext) { async function DecryptAESGCM(symmetricKey, ivAndCiphertext) {
const nonce = ivAndCiphertext.slice(0, NONCE_LENGTH); const nonce = ivAndCiphertext.slice(0, NONCE_LENGTH);
const ciphertext = ivAndCiphertext.slice(NONCE_LENGTH); const ciphertext = ivAndCiphertext.slice(NONCE_LENGTH);
const key = await crypto.subtle.importKey('raw', symmetricKey, { name: 'AES-GCM' }, false, [ const key = await crypto.subtle.importKey('raw', symmetricKey, { name: 'AES-GCM' }, false, [
'decrypt', 'decrypt',
]); ]);

@ -1,19 +0,0 @@
/* global window */
// eslint-disable-next-line func-names
(function() {
window.libloki = window.libloki || {};
function getGuardNodes() {
return window.Signal.Data.getGuardNodes();
}
function updateGuardNodes(nodes) {
return window.Signal.Data.updateGuardNodes(nodes);
}
window.libloki.storage = {
getGuardNodes,
updateGuardNodes,
};
})();

@ -49,7 +49,9 @@ window.getServerTrustRoot = () => config.serverTrustRoot;
window.JobQueue = JobQueue; window.JobQueue = JobQueue;
window.isBehindProxy = () => Boolean(config.proxyUrl); window.isBehindProxy = () => Boolean(config.proxyUrl);
window.getStoragePubKey = key => (window.isDev() ? key.substring(0, key.length - 2) : key); // FIXME audric
window.getStoragePubKey = key =>
window.isDev() ? key.substring(0, key.length - 2) : key.substring(0, key.length - 2);
window.getDefaultFileServer = () => config.defaultFileServer; window.getDefaultFileServer = () => config.defaultFileServer;
window.initialisedAPI = false; window.initialisedAPI = false;

@ -36,7 +36,7 @@ const SessionJoinableRoomAvatar = (props: JoinableRoomProps) => {
}); });
} }
} catch (e) { } catch (e) {
console.warn(e); window.log.warn(e);
} }
}, [props.imageId, props.completeUrl]); }, [props.imageId, props.completeUrl]);
return ( return (
@ -72,7 +72,7 @@ export const SessionJoinableRooms = () => {
const joinableRooms = useSelector((state: StateType) => state.defaultRooms); const joinableRooms = useSelector((state: StateType) => state.defaultRooms);
if (!joinableRooms?.length) { if (!joinableRooms?.length) {
console.warn('no default joinable rooms yet'); window.log.info('no default joinable rooms yet');
return <></>; return <></>;
} }

@ -288,13 +288,13 @@ function _updateJob(id: number, data: any) {
...data, ...data,
resolve: (value: any) => { resolve: (value: any) => {
_removeJob(id); _removeJob(id);
// const end = Date.now(); if (_DEBUG) {
// const delta = end - start; const end = Date.now();
// if (delta > 10) { const delta = end - start;
// window.log.debug( if (delta > 10) {
// `SQL channel job ${id} (${fnName}) succeeded in ${end - start}ms` window.log.debug(`SQL channel job ${id} (${fnName}) succeeded in ${end - start}ms`);
// ); }
// } }
return resolve(value); return resolve(value);
}, },
reject: (error: any) => { reject: (error: any) => {

@ -1046,7 +1046,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
read = read.filter(item => !item.hasErrors); read = read.filter(item => !item.hasErrors);
if (this.isPublic()) { if (this.isPublic()) {
window.log.debug('public conversation... No need to send read receipt');
return; return;
} }
if (this.isPrivate() && read.length && options.sendReadReceipts) { if (this.isPrivate() && read.length && options.sendReadReceipts) {

@ -8,10 +8,6 @@ import { parseOpenGroupV2 } from './JoinOpenGroupV2';
import { getAllRoomInfos } from './OpenGroupAPIV2'; import { getAllRoomInfos } from './OpenGroupAPIV2';
import { OpenGroupMessageV2 } from './OpenGroupMessageV2'; import { OpenGroupMessageV2 } from './OpenGroupMessageV2';
export const defaultServer = 'https://sessionopengroup.com';
export const defaultServerPublicKey =
'658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231b';
export type OpenGroupRequestCommonType = { export type OpenGroupRequestCommonType = {
serverUrl: string; serverUrl: string;
roomId: string; roomId: string;
@ -107,12 +103,21 @@ export const parseMessages = async (
); );
return _.compact(messages); return _.compact(messages);
}; };
// tslint:disable: no-http-string
// FIXME audric change this to
// const defaultRoom =
// 'https://opengroup.bilb.us/main?public_key=1352534ba73d4265973280431dbc72e097a3e43275d1ada984f9805b4943047d';
// const defaultRoom =
// 'http://opengroup.bilb.us:9861/main?public_key=1352534ba73d4265973280431dbc72e097a3e43275d1ada984f9805b4943047d';
// 'http://sessionopengroup.com/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231b' // const defaultRoom =
// 'https://sog.ibolpap.finance/main?public_key=b464aa186530c97d6bcf663a3a3b7465a5f782beaa67c83bee99468824b4aa10';
// FIXME audric change this to sessionopengroup.com once http is fixed const defaultServerUrl = 'http://116.203.70.33';
const defaultRoom = const defaultServerPublicKey = 'a03c383cf63c3c4efe67acc52112a6dd734b3a946b9545f488aaa93da7991238';
'https://opengroup.bilb.us/main?public_key=1352534ba73d4265973280431dbc72e097a3e43275d1ada984f9805b4943047d'; const defaultRoom = `${defaultServerUrl}/main?public_key=${defaultServerPublicKey}`;
const loadDefaultRoomsSingle = () => const loadDefaultRoomsSingle = () =>
allowOnlyOneAtATime( allowOnlyOneAtATime(

@ -33,10 +33,8 @@ export function parseOpenGroupV2(urlWithPubkey: string): OpenGroupV2Room | undef
// new URL fails if the protocol is not explicit // new URL fails if the protocol is not explicit
const url = new URL(prefixedUrl); const url = new URL(prefixedUrl);
let serverUrl = `${url.protocol}//${url.host}`; // the port (if any is set) is already in the url.host so no need to += url.port
if (url.port) { const serverUrl = `${url.protocol}//${url.host}`;
serverUrl += `:${url.port}`;
}
const room: OpenGroupV2Room = { const room: OpenGroupV2Room = {
serverUrl, serverUrl,

@ -50,7 +50,7 @@ async function sendOpenGroupV2Request(request: OpenGroupV2Request): Promise<Obje
body = JSON.stringify(request.queryParams); body = JSON.stringify(request.queryParams);
} }
let serverPubKey: string; let destinationX25519Key: string;
if (!request.serverPublicKey) { if (!request.serverPublicKey) {
const roomDetails = await getV2OpenGroupRoomByRoomId({ const roomDetails = await getV2OpenGroupRoomByRoomId({
serverUrl: request.server, serverUrl: request.server,
@ -59,9 +59,9 @@ async function sendOpenGroupV2Request(request: OpenGroupV2Request): Promise<Obje
if (!roomDetails?.serverPublicKey) { if (!roomDetails?.serverPublicKey) {
throw new Error('PublicKey not found for this server.'); throw new Error('PublicKey not found for this server.');
} }
serverPubKey = roomDetails.serverPublicKey; destinationX25519Key = roomDetails.serverPublicKey;
} else { } else {
serverPubKey = request.serverPublicKey; destinationX25519Key = request.serverPublicKey;
} }
// Because auth happens on a per-room basis, we need both to make an authenticated request // Because auth happens on a per-room basis, we need both to make an authenticated request
if (request.isAuthRequired && request.room) { if (request.isAuthRequired && request.room) {
@ -78,7 +78,7 @@ async function sendOpenGroupV2Request(request: OpenGroupV2Request): Promise<Obje
} }
headers.Authorization = token; headers.Authorization = token;
const res = await sendViaOnion( const res = await sendViaOnion(
serverPubKey, destinationX25519Key,
builtUrl, builtUrl,
{ {
method: request.method, method: request.method,
@ -113,7 +113,7 @@ async function sendOpenGroupV2Request(request: OpenGroupV2Request): Promise<Obje
return res as object; return res as object;
} else { } else {
// no need for auth, just do the onion request // no need for auth, just do the onion request
const res = await sendViaOnion(serverPubKey, builtUrl, { const res = await sendViaOnion(destinationX25519Key, builtUrl, {
method: request.method, method: request.method,
headers, headers,
body, body,
@ -170,7 +170,6 @@ export async function requestNewAuthToken({
const token = toHex(plaintextBuffer); const token = toHex(plaintextBuffer);
console.warn('token', token);
return token; return token;
} catch (e) { } catch (e) {
window.log.error('Failed to decrypt token open group v2'); window.log.error('Failed to decrypt token open group v2');
@ -256,7 +255,7 @@ export async function getAuthToken({
await allowOnlyOneAtATime(`getAuthTokenV2${serverUrl}:${roomId}`, async () => { await allowOnlyOneAtATime(`getAuthTokenV2${serverUrl}:${roomId}`, async () => {
try { try {
console.warn('TRIGGERING NEW AUTH TOKEN WITH', { serverUrl, roomId }); window.log.info('TRIGGERING NEW AUTH TOKEN WITH', { serverUrl, roomId });
const token = await requestNewAuthToken({ serverUrl, roomId }); const token = await requestNewAuthToken({ serverUrl, roomId });
if (!token) { if (!token) {
window.log.warn('invalid new auth token', token); window.log.warn('invalid new auth token', token);
@ -363,7 +362,6 @@ export const banUser = async (
}; };
const banResult = await sendOpenGroupV2Request(request); const banResult = await sendOpenGroupV2Request(request);
const isOk = parseStatusCodeFromOnionRequest(banResult) === 200; const isOk = parseStatusCodeFromOnionRequest(banResult) === 200;
console.warn('banResult', banResult);
return isOk; return isOk;
}; };
@ -380,7 +378,6 @@ export const unbanUser = async (
}; };
const unbanResult = await sendOpenGroupV2Request(request); const unbanResult = await sendOpenGroupV2Request(request);
const isOk = parseStatusCodeFromOnionRequest(unbanResult) === 200; const isOk = parseStatusCodeFromOnionRequest(unbanResult) === 200;
console.warn('unbanResult', unbanResult);
return isOk; return isOk;
}; };
@ -636,7 +633,6 @@ export const addModerator = async (
}; };
const addModResult = await sendOpenGroupV2Request(request); const addModResult = await sendOpenGroupV2Request(request);
const isOk = parseStatusCodeFromOnionRequest(addModResult) === 200; const isOk = parseStatusCodeFromOnionRequest(addModResult) === 200;
console.warn('addModResult', addModResult);
return isOk; return isOk;
}; };
@ -653,6 +649,5 @@ export const removeModerator = async (
}; };
const removeModResult = await sendOpenGroupV2Request(request); const removeModResult = await sendOpenGroupV2Request(request);
const isOk = parseStatusCodeFromOnionRequest(removeModResult) === 200; const isOk = parseStatusCodeFromOnionRequest(removeModResult) === 200;
console.warn('removeModResult', removeModResult);
return isOk; return isOk;
}; };

@ -57,13 +57,8 @@ const getCompactPollRequest = async (
room_id: roomId, room_id: roomId,
auth_token: token || '', auth_token: token || '',
}; };
// if (lastMessageDeletedServerID) {
roomRequestContent.from_deletion_server_id = lastMessageDeletedServerID; roomRequestContent.from_deletion_server_id = lastMessageDeletedServerID;
// }
// if (lastMessageFetchedServerID) {
roomRequestContent.from_message_server_id = lastMessageFetchedServerID; roomRequestContent.from_message_server_id = lastMessageFetchedServerID;
// }
console.warn('compactPoll, ', roomRequestContent);
return roomRequestContent; return roomRequestContent;
} catch (e) { } catch (e) {

@ -184,8 +184,6 @@ export class OpenGroupManagerV2 {
room.roomName = roomInfos.name || undefined; room.roomName = roomInfos.name || undefined;
await saveV2OpenGroupRoom(room); await saveV2OpenGroupRoom(room);
console.warn('openGroupRoom info', roomInfos);
// mark active so it's not in the contacts list but in the conversation list // mark active so it's not in the contacts list but in the conversation list
conversation.set({ conversation.set({
active_at: Date.now(), active_at: Date.now(),

@ -156,7 +156,6 @@ export class OpenGroupServerPoller {
compactFetchResults = compactFetchResults.filter(result => compactFetchResults = compactFetchResults.filter(result =>
this.roomIdsToPoll.has(result.roomId) this.roomIdsToPoll.has(result.roomId)
); );
// window.log.debug(`compactFetchResults for ${this.serverUrl}:`, compactFetchResults);
// ==> At this point all those results need to trigger conversation updates, so update what we have to update // ==> At this point all those results need to trigger conversation updates, so update what we have to update
await handleCompactPollResults(this.serverUrl, compactFetchResults); await handleCompactPollResults(this.serverUrl, compactFetchResults);

@ -24,7 +24,7 @@ export const openGroupV2ServerUrlRegex = new RegExp(
* Regex to use to check if a string is a v2open completeURL with pubkey. * Regex to use to check if a string is a v2open completeURL with pubkey.
* Be aware that the /g flag is not set as .test() will otherwise return alternating result * Be aware that the /g flag is not set as .test() will otherwise return alternating result
* *
* @see https://stackoverflow.com/a/9275499/1680951 * see https://stackoverflow.com/a/9275499/1680951
*/ */
export const openGroupV2CompleteURLRegex = new RegExp( export const openGroupV2CompleteURLRegex = new RegExp(
`${openGroupV2ServerUrlRegex.source}\/${roomIdV2Regex}${qMark}${publicKeyParam}${publicKeyRegex}`, `${openGroupV2ServerUrlRegex.source}\/${roomIdV2Regex}${qMark}${publicKeyParam}${publicKeyRegex}`,

@ -1,4 +1,4 @@
import { getGuardNodes } from '../../../ts/data/data'; import { getGuardNodes, updateGuardNodes } from '../../../ts/data/data';
import * as SnodePool from '../snode_api/snodePool'; import * as SnodePool from '../snode_api/snodePool';
import _ from 'lodash'; import _ from 'lodash';
import { default as insecureNodeFetch } from 'node-fetch'; import { default as insecureNodeFetch } from 'node-fetch';
@ -217,7 +217,7 @@ export class OnionPaths {
const edKeys = guardNodes.map(n => n.pubkey_ed25519); const edKeys = guardNodes.map(n => n.pubkey_ed25519);
await window.libloki.storage.updateGuardNodes(edKeys); await updateGuardNodes(edKeys);
return guardNodes; return guardNodes;
} }

@ -9,8 +9,10 @@ import {
SnodeResponse, SnodeResponse,
} from '../snode_api/onions'; } from '../snode_api/onions';
import { Snode } from '../snode_api/snodePool'; import { Snode } from '../snode_api/snodePool';
import _ from 'lodash'; import _, { toNumber } from 'lodash';
import { default as insecureNodeFetch } from 'node-fetch'; import { default as insecureNodeFetch } from 'node-fetch';
import { PROTOCOLS } from '../constants';
import { toHex } from '../utils/String';
// FIXME: replace with something on urlPubkeyMap... // FIXME: replace with something on urlPubkeyMap...
const FILESERVER_HOSTS = [ const FILESERVER_HOSTS = [
@ -111,7 +113,7 @@ export const getOnionPathForSending = async (requestNumber: number) => {
} catch (e) { } catch (e) {
window.log.error(`sendViaOnion #${requestNumber} - getOnionPath Error ${e.code} ${e.message}`); window.log.error(`sendViaOnion #${requestNumber} - getOnionPath Error ${e.code} ${e.message}`);
} }
if (!pathNodes || !pathNodes.length) { if (!pathNodes?.length) {
window.log.warn(`sendViaOnion #${requestNumber} - failing, no path available`); window.log.warn(`sendViaOnion #${requestNumber} - failing, no path available`);
// should we retry? // should we retry?
return null; return null;
@ -132,7 +134,7 @@ const initOptionsWithDefaults = (options: OnionFetchBasicOptions) => {
* result is status_code and whatever the body should be * result is status_code and whatever the body should be
*/ */
export const sendViaOnion = async ( export const sendViaOnion = async (
srvPubKey: string, destinationX25519Key: string,
url: URL, url: URL,
fetchOptions: OnionFetchOptions, fetchOptions: OnionFetchOptions,
options: OnionFetchBasicOptions = {}, options: OnionFetchBasicOptions = {},
@ -142,9 +144,11 @@ export const sendViaOnion = async (
txtResponse: string; txtResponse: string;
response: string; response: string;
} | null> => { } | null> => {
if (!srvPubKey) { const castedDestinationX25519Key =
window.log.error('sendViaOnion - called without a server public key'); typeof destinationX25519Key !== 'string' ? toHex(destinationX25519Key) : destinationX25519Key;
return null; // FIXME audric looks like this might happen for opengroupv1
if (!destinationX25519Key || typeof destinationX25519Key !== 'string') {
window.log.error('sendViaOnion - called without a server public key or not a string key');
} }
const defaultedOptions = initOptionsWithDefaults(options); const defaultedOptions = initOptionsWithDefaults(options);
@ -158,19 +162,24 @@ export const sendViaOnion = async (
// do the request // do the request
let result: SnodeResponse | RequestError; let result: SnodeResponse | RequestError;
try { try {
// if protocol is forced to 'http:' => just use http (without the ':').
// otherwise use https as protocol (this is the default)
const forcedHttp = url.protocol === PROTOCOLS.HTTP;
const finalRelayOptions: FinalRelayOptions = { const finalRelayOptions: FinalRelayOptions = {
host: url.host, host: url.hostname,
// FIXME http open groups v2 are not working
// protocol: url.protocol,
// port: url.port,
}; };
// window.log.debug('sendViaOnion payloadObj ==> ', payloadObj); if (forcedHttp) {
finalRelayOptions.protocol = 'http';
}
if (forcedHttp) {
finalRelayOptions.port = url.port ? toNumber(url.port) : 80;
}
result = await sendOnionRequestLsrpcDest( result = await sendOnionRequestLsrpcDest(
0, 0,
pathNodes, pathNodes,
srvPubKey, castedDestinationX25519Key,
finalRelayOptions, finalRelayOptions,
payloadObj, payloadObj,
defaultedOptions.requestNumber, defaultedOptions.requestNumber,
@ -190,7 +199,7 @@ export const sendViaOnion = async (
const retriedResult = await handleSendViaOnionRetry( const retriedResult = await handleSendViaOnionRetry(
result, result,
defaultedOptions, defaultedOptions,
srvPubKey, castedDestinationX25519Key,
url, url,
fetchOptions, fetchOptions,
abortSignal abortSignal

@ -5,7 +5,7 @@ import { Snode } from './snodePool';
import ByteBuffer from 'bytebuffer'; import ByteBuffer from 'bytebuffer';
import { StringUtils } from '../utils'; import { StringUtils } from '../utils';
import { OnionPaths } from '../onions'; import { OnionPaths } from '../onions';
import Long from 'long'; import { fromBase64ToArrayBuffer, toHex } from '../utils/String';
export enum RequestError { export enum RequestError {
BAD_PATH = 'BAD_PATH', BAD_PATH = 'BAD_PATH',
@ -36,9 +36,22 @@ async function encryptForPubKey(pubKeyX25519hex: string, reqObj: any): Promise<D
return window.libloki.crypto.encryptForPubkey(pubKeyX25519hex, plaintext); return window.libloki.crypto.encryptForPubkey(pubKeyX25519hex, plaintext);
} }
export type DestinationRelayV2 = {
host?: string;
protocol?: string;
port?: number;
destination?: string;
method?: string;
target?: string;
};
// `ctx` holds info used by `node` to relay further // `ctx` holds info used by `node` to relay further
async function encryptForRelayV2(relayX25519hex: string, destination: any, ctx: any) { async function encryptForRelayV2(
const { log, StringView } = window; relayX25519hex: string,
destination: DestinationRelayV2,
ctx: DestinationContext
) {
const { log } = window;
if (!destination.host && !destination.destination) { if (!destination.host && !destination.destination) {
log.warn('loki_rpc::encryptForRelayV2 - no destination', destination); log.warn('loki_rpc::encryptForRelayV2 - no destination', destination);
@ -46,7 +59,7 @@ async function encryptForRelayV2(relayX25519hex: string, destination: any, ctx:
const reqObj = { const reqObj = {
...destination, ...destination,
ephemeral_key: StringView.arrayBufferToHex(ctx.ephemeralKey), ephemeral_key: toHex(ctx.ephemeralKey),
}; };
const plaintext = encodeCiphertextPlusJson(ctx.ciphertext, reqObj); const plaintext = encodeCiphertextPlusJson(ctx.ciphertext, reqObj);
@ -55,12 +68,15 @@ async function encryptForRelayV2(relayX25519hex: string, destination: any, ctx:
} }
/// Encode ciphertext as (len || binary) and append payloadJson as utf8 /// Encode ciphertext as (len || binary) and append payloadJson as utf8
function encodeCiphertextPlusJson(ciphertext: any, payloadJson: any): Uint8Array { function encodeCiphertextPlusJson(
ciphertext: Uint8Array,
payloadJson: Record<string, any>
): Uint8Array {
const payloadStr = JSON.stringify(payloadJson); const payloadStr = JSON.stringify(payloadJson);
const bufferJson = ByteBuffer.wrap(payloadStr, 'utf8'); const bufferJson = ByteBuffer.wrap(payloadStr, 'utf8');
const len = ciphertext.length as number; const len = ciphertext.length;
const arrayLen = bufferJson.buffer.length + 4 + len; const arrayLen = bufferJson.buffer.length + 4 + len;
const littleEndian = true; const littleEndian = true;
const buffer = new ByteBuffer(arrayLen, littleEndian); const buffer = new ByteBuffer(arrayLen, littleEndian);
@ -72,19 +88,10 @@ function encodeCiphertextPlusJson(ciphertext: any, payloadJson: any): Uint8Array
return new Uint8Array(buffer.buffer); return new Uint8Array(buffer.buffer);
} }
// New "semi-binary" encoding
function makeGuardPayloadV2(guardCtx: any): Uint8Array {
const guardPayloadObj = {
ephemeral_key: StringUtils.decode(guardCtx.ephemeralKey, 'hex'),
};
return encodeCiphertextPlusJson(guardCtx.ciphertext, guardPayloadObj);
}
async function buildOnionCtxs( async function buildOnionCtxs(
nodePath: Array<Snode>, nodePath: Array<Snode>,
destCtx: DestinationContext, destCtx: DestinationContext,
targetED25519Hex: string, targetED25519Hex?: string,
finalRelayOptions?: FinalRelayOptions, finalRelayOptions?: FinalRelayOptions,
id = '' id = ''
) { ) {
@ -95,14 +102,7 @@ async function buildOnionCtxs(
const firstPos = nodePath.length - 1; const firstPos = nodePath.length - 1;
for (let i = firstPos; i > -1; i -= 1) { for (let i = firstPos; i > -1; i -= 1) {
let dest: { let dest: DestinationRelayV2;
host?: string;
protocol?: string;
port?: string;
destination?: string;
method?: string;
target?: string;
};
const relayingToFinalDestination = i === firstPos; // if last position const relayingToFinalDestination = i === firstPos; // if last position
if (relayingToFinalDestination && finalRelayOptions) { if (relayingToFinalDestination && finalRelayOptions) {
@ -120,10 +120,10 @@ async function buildOnionCtxs(
}; };
// FIXME http open groups v2 are not working // FIXME http open groups v2 are not working
// tslint:disable-next-line: no-http-string // tslint:disable-next-line: no-http-string
// if (finalRelayOptions?.protocol === 'http:') { if (finalRelayOptions?.protocol === 'http') {
// dest.protocol = 'http'; dest.protocol = finalRelayOptions.protocol;
// dest.port = '80'; dest.port = finalRelayOptions.port || 80;
// } }
} else { } else {
// set x25519 if destination snode // set x25519 if destination snode
let pubkeyHex = targetED25519Hex; // relayingToFinalDestination let pubkeyHex = targetED25519Hex; // relayingToFinalDestination
@ -132,7 +132,7 @@ async function buildOnionCtxs(
pubkeyHex = nodePath[i + 1].pubkey_ed25519; pubkeyHex = nodePath[i + 1].pubkey_ed25519;
if (!pubkeyHex) { if (!pubkeyHex) {
log.error( log.error(
`loki_rpc:::buildOnionRequest ${id} - no ed25519 for`, `loki_rpc:::buildOnionGuardNodePayload ${id} - no ed25519 for`,
nodePath[i + 1], nodePath[i + 1],
'path node', 'path node',
i + 1 i + 1
@ -150,7 +150,7 @@ async function buildOnionCtxs(
ctxes.push(ctx); ctxes.push(ctx);
} catch (e) { } catch (e) {
log.error( log.error(
`loki_rpc:::buildOnionRequest ${id} - encryptForRelayV2 failure`, `loki_rpc:::buildOnionGuardNodePayload ${id} - encryptForRelayV2 failure`,
e.code, e.code,
e.message e.message
); );
@ -163,19 +163,26 @@ async function buildOnionCtxs(
// we just need the targetNode.pubkey_ed25519 for the encryption // we just need the targetNode.pubkey_ed25519 for the encryption
// targetPubKey is ed25519 if snode is the target // targetPubKey is ed25519 if snode is the target
async function buildOnionRequest( async function buildOnionGuardNodePayload(
nodePath: Array<Snode>, nodePath: Array<Snode>,
destCtx: DestinationContext, destCtx: DestinationContext,
targetED25519Hex: string, targetED25519Hex?: string,
finalRelayOptions?: FinalRelayOptions, finalRelayOptions?: FinalRelayOptions,
id = '' id = ''
) { ) {
const ctxes = await buildOnionCtxs(nodePath, destCtx, targetED25519Hex, finalRelayOptions, id); const ctxes = await buildOnionCtxs(nodePath, destCtx, targetED25519Hex, finalRelayOptions, id);
// this is the OUTER side of the onion, the one encoded with multiple layer
// So the one we will send to the first guard node.
const guardCtx = ctxes[ctxes.length - 1]; // last ctx const guardCtx = ctxes[ctxes.length - 1]; // last ctx
// all these requests should use AesGcm // New "semi-binary" encoding
return makeGuardPayloadV2(guardCtx);
const guardPayloadObj = {
ephemeral_key: toHex(guardCtx.ephemeralKey),
};
return encodeCiphertextPlusJson(guardCtx.ciphertext, guardPayloadObj);
} }
// Process a response as it arrives from `fetch`, handling // Process a response as it arrives from `fetch`, handling
@ -184,11 +191,11 @@ async function buildOnionRequest(
const processOnionResponse = async ( const processOnionResponse = async (
reqIdx: number, reqIdx: number,
response: any, response: any,
sharedKey: ArrayBuffer, symmetricKey: ArrayBuffer,
debug: boolean, debug: boolean,
abortSignal?: AbortSignal abortSignal?: AbortSignal
): Promise<SnodeResponse | RequestError> => { ): Promise<SnodeResponse | RequestError> => {
const { log, libloki, dcodeIO, StringView } = window; const { log, libloki } = window;
if (abortSignal?.aborted) { if (abortSignal?.aborted) {
log.warn(`(${reqIdx}) [path] Call aborted`); log.warn(`(${reqIdx}) [path] Call aborted`);
@ -230,7 +237,7 @@ const processOnionResponse = async (
return RequestError.OTHER; return RequestError.OTHER;
} }
let ciphertext = await response.text(); let ciphertext = (await response.text()) as string;
if (!ciphertext) { if (!ciphertext) {
log.warn( log.warn(
`(${reqIdx}) [path] lokiRpc::processOnionResponse - Target node return empty ciphertext` `(${reqIdx}) [path] lokiRpc::processOnionResponse - Target node return empty ciphertext`
@ -251,16 +258,16 @@ const processOnionResponse = async (
// just try to get a json object from what is inside (for PN requests), if it fails, continue () // just try to get a json object from what is inside (for PN requests), if it fails, continue ()
} }
try { try {
ciphertextBuffer = dcodeIO.ByteBuffer.wrap(ciphertext, 'base64').toArrayBuffer(); ciphertextBuffer = fromBase64ToArrayBuffer(ciphertext);
if (debug) { if (debug) {
log.debug( log.debug(
`(${reqIdx}) [path] lokiRpc::processOnionResponse - ciphertextBuffer`, `(${reqIdx}) [path] lokiRpc::processOnionResponse - ciphertextBuffer`,
StringView.arrayBufferToHex(ciphertextBuffer) toHex(ciphertextBuffer)
); );
} }
const plaintextBuffer = await libloki.crypto.DecryptAESGCM(sharedKey, ciphertextBuffer); const plaintextBuffer = await libloki.crypto.DecryptAESGCM(symmetricKey, ciphertextBuffer);
if (debug) { if (debug) {
log.debug('lokiRpc::processOnionResponse - plaintextBuffer', plaintextBuffer.toString()); log.debug('lokiRpc::processOnionResponse - plaintextBuffer', plaintextBuffer.toString());
} }
@ -269,13 +276,13 @@ const processOnionResponse = async (
} catch (e) { } catch (e) {
log.error(`(${reqIdx}) [path] lokiRpc::processOnionResponse - decode error`, e); log.error(`(${reqIdx}) [path] lokiRpc::processOnionResponse - decode error`, e);
log.error( log.error(
`(${reqIdx}) [path] lokiRpc::processOnionResponse - symKey`, `(${reqIdx}) [path] lokiRpc::processOnionResponse - symmetricKey`,
StringView.arrayBufferToHex(sharedKey) toHex(symmetricKey)
); );
if (ciphertextBuffer) { if (ciphertextBuffer) {
log.error( log.error(
`(${reqIdx}) [path] lokiRpc::processOnionResponse - ciphertextBuffer`, `(${reqIdx}) [path] lokiRpc::processOnionResponse - ciphertextBuffer`,
StringView.arrayBufferToHex(ciphertextBuffer) toHex(ciphertextBuffer)
); );
} }
return RequestError.OTHER; return RequestError.OTHER;
@ -308,9 +315,8 @@ export const snodeHttpsAgent = new https.Agent({
export type FinalRelayOptions = { export type FinalRelayOptions = {
host: string; host: string;
// FIXME http open groups v2 are not working protocol?: 'http' | 'https'; // default to https
// protocol?: string; // default to https port?: number; // default to 443
// port?: string; // default to 443
}; };
export type DestinationContext = { export type DestinationContext = {
@ -346,10 +352,10 @@ const sendOnionRequest = async (
body?: string; body?: string;
}, },
finalRelayOptions?: FinalRelayOptions, finalRelayOptions?: FinalRelayOptions,
lsrpcIdx?: any, lsrpcIdx?: number,
abortSignal?: AbortSignal abortSignal?: AbortSignal
): Promise<SnodeResponse | RequestError> => { ): Promise<SnodeResponse | RequestError> => {
const { log, StringView } = window; const { log } = window;
let id = ''; let id = '';
if (lsrpcIdx !== undefined) { if (lsrpcIdx !== undefined) {
@ -363,7 +369,8 @@ const sendOnionRequest = async (
let destX25519hex = destX25519Any; let destX25519hex = destX25519Any;
if (typeof destX25519hex !== 'string') { if (typeof destX25519hex !== 'string') {
// convert AB to hex // convert AB to hex
destX25519hex = StringView.arrayBufferToHex(destX25519Any as any); window.log.warn('destX25519hex was not a string');
destX25519hex = toHex(destX25519Any as any);
} }
// safely build destination // safely build destination
@ -378,9 +385,7 @@ const sendOnionRequest = async (
const options = finalDestOptions; // lint const options = finalDestOptions; // lint
// do we need this? // do we need this?
if (options.headers === undefined) { options.headers = options.headers || {};
options.headers = {};
}
const isLsrpc = !!finalRelayOptions; const isLsrpc = !!finalRelayOptions;
@ -413,10 +418,10 @@ const sendOnionRequest = async (
throw e; throw e;
} }
const payload = await buildOnionRequest( const payload = await buildOnionGuardNodePayload(
nodePath, nodePath,
destCtx, destCtx,
targetEd25519hex as string, // FIXME targetEd25519hex,
finalRelayOptions, finalRelayOptions,
id id
); );
@ -436,7 +441,6 @@ const sendOnionRequest = async (
// window.log.info('insecureNodeFetch => plaintext for sendOnionRequest'); // window.log.info('insecureNodeFetch => plaintext for sendOnionRequest');
const response = await insecureNodeFetch(guardUrl, guardFetchOptions); const response = await insecureNodeFetch(guardUrl, guardFetchOptions);
return processOnionResponse(reqIdx, response, destCtx.symmetricKey, false, abortSignal); return processOnionResponse(reqIdx, response, destCtx.symmetricKey, false, abortSignal);
}; };
@ -460,7 +464,6 @@ async function sendOnionRequestSnodeDest(
} }
// need relay node's pubkey_x25519_hex // need relay node's pubkey_x25519_hex
// always the same target: /loki/v1/lsrpc
export async function sendOnionRequestLsrpcDest( export async function sendOnionRequestLsrpcDest(
reqIdx: number, reqIdx: number,
nodePath: Array<Snode>, nodePath: Array<Snode>,

@ -347,7 +347,6 @@ export async function storeOnNode(targetNode: Snode, params: SendParams): Promis
await sleepFor(successiveFailures * 500); await sleepFor(successiveFailures * 500);
try { try {
const result = await snodeRpc('store', params, targetNode); const result = await snodeRpc('store', params, targetNode);
console.warn('snode storeOnNode result', result);
// do not return true if we get false here... // do not return true if we get false here...
if (result === false) { if (result === false) {

@ -53,7 +53,8 @@ export class SwarmPolling {
public start(): void { public start(): void {
this.loadGroupIds(); this.loadGroupIds();
void this.pollForAllKeys(); // FIXME audric
// void this.pollForAllKeys();
} }
public addGroupId(pubkey: PubKey) { public addGroupId(pubkey: PubKey) {

Loading…
Cancel
Save