do not try to fetch seed node data with ip as cert will not be valid

pull/1833/head
audric 4 years ago
parent ab8b6e85b4
commit 67c2124a3b

@ -82,7 +82,7 @@ export async function snodeRpc(
method: string,
params: any,
targetNode: Snode,
associatedWith?: string //the user pubkey this call is for. if the onion request fails, this is used to handle the error for this user swarm for isntance
associatedWith?: string //the user pubkey this call is for. if the onion request fails, this is used to handle the error for this user swarm for instance
): Promise<undefined | SnodeResponse> {
const url = `https://${targetNode.ip}:${targetNode.port}/storage_rpc/v1`;

@ -387,7 +387,11 @@ const debug = false;
/**
* Only exported for testing purpose
*/
export async function decodeOnionResult(symmetricKey: ArrayBuffer, ciphertext: string, test?: string) {
export async function decodeOnionResult(
symmetricKey: ArrayBuffer,
ciphertext: string,
test?: string
) {
let parsedCiphertext = ciphertext;
try {
const jsonRes = JSON.parse(ciphertext);
@ -417,7 +421,7 @@ export async function processOnionResponse({
abortSignal,
associatedWith,
lsrpcEd25519Key,
test
test,
}: {
response?: { text: () => Promise<string>; status: number };
symmetricKey?: ArrayBuffer;
@ -663,7 +667,7 @@ const sendOnionRequestHandlingSnodeEject = async ({
abortSignal,
associatedWith,
finalRelayOptions,
test
test,
}: {
nodePath: Array<Snode>;
destX25519Any: string;
@ -689,7 +693,7 @@ const sendOnionRequestHandlingSnodeEject = async ({
finalDestOptions,
finalRelayOptions,
abortSignal,
test
test,
});
response = result.response;
@ -706,7 +710,7 @@ const sendOnionRequestHandlingSnodeEject = async ({
lsrpcEd25519Key: finalDestOptions?.destination_ed25519_hex,
abortSignal,
associatedWith,
test
test,
});
return processed;
@ -729,7 +733,7 @@ const sendOnionRequest = async ({
finalDestOptions,
finalRelayOptions,
abortSignal,
test
test,
}: {
nodePath: Array<Snode>;
destX25519Any: string;
@ -827,7 +831,6 @@ const sendOnionRequest = async ({
// no logs for that one insecureNodeFetch as we do need to call insecureNodeFetch to our guardNode
// window?.log?.info('insecureNodeFetch => plaintext for sendOnionRequest');
const response = await insecureNodeFetch(guardUrl, guardFetchOptions);
return { response, decodingSymmetricKey: destCtx.symmetricKey };
};
@ -847,7 +850,7 @@ async function sendOnionRequestSnodeDest(
body: plaintext,
},
associatedWith,
test
test,
});
}

@ -66,24 +66,12 @@ async function tryGetSnodeListFromLokidSeednode(
// throw before clearing the lock, so the retries can kick in
if (snodes.length === 0) {
window?.log?.warn(
`loki_snode_api::tryGetSnodeListFromLokidSeednode - ${seedNode.url} did not return any snodes, falling back to IP`,
seedNode.ip_url
);
// fall back on ip_url
const tryIpUrl = new URL(seedNode.ip_url);
snodes = await getSnodesFromSeedUrl(tryIpUrl);
if (snodes.length === 0) {
window?.log?.warn(
`loki_snode_api::tryGetSnodeListFromLokidSeednode - ${seedNode.ip_url} did not return any snodes`
);
// does this error message need to be exactly this?
throw new window.textsecure.SeedNodeError('Failed to contact seed node');
}
} else {
window?.log?.info(
`loki_snode_api::tryGetSnodeListFromLokidSeednode - ${seedNode.url} returned ${snodes.length} snodes`
`loki_snode_api::tryGetSnodeListFromLokidSeednode - ${seedNode.ip_url} did not return any snodes`
);
// does this error message need to be exactly this?
throw new window.textsecure.SeedNodeError('Failed to contact seed node');
}
return snodes;
} catch (e) {
window?.log?.warn(

@ -53,6 +53,8 @@ import {
import { KeyPair } from '../../../libtextsecure/libsignal-protocol';
import { getIdentityKeyPair } from './User';
import { PubKey } from '../types';
import pRetry from 'p-retry';
import { ed25519Str } from '../onions/onionPath';
const ITEM_ID_LAST_SYNC_TIMESTAMP = 'lastSyncedTimestamp';
@ -124,132 +126,152 @@ export const forceSyncConfigurationNowIfNeeded = async (waitForMessageSent = fal
* @returns timestamp of the response from snode
*/
const getNetworkTime = async (snode: Snode): Promise<string | number> => {
// let response: any = await insecureNodeFetch(url, fetchOptions)
try {
const response: any = await snodeRpc('info', {}, snode);
const body = JSON.parse(response.body);
const timestamp = body.timestamp;
return timestamp ? timestamp : -1;
} catch (e) {
return -1;
const response: any = await snodeRpc('info', {}, snode);
const body = JSON.parse(response.body);
const timestamp = body.timestamp;
if (!timestamp) {
throw new Error(`getNetworkTime returned invalid timestamp: ${timestamp}`);
}
return timestamp;
};
export const forceNetworkDeletion = async () => {
export const forceNetworkDeletion = async (): Promise<Map<string, boolean> | null> => {
const sodium = await getSodium();
const userPubKey = UserUtils.getOurPubKeyFromCache();
const edKey = await UserUtils.getUserED25519KeyPair();
const edKeyPriv = edKey?.privKey || '';
console.warn({ edKey });
console.warn({ edKeyPriv });
const snode: Snode | undefined = _.shuffle(await getSwarmFor(userPubKey.key))[0];
const timestamp = await getNetworkTime(snode);
const text = `delete_all${timestamp.toString()}`;
const toSign = StringUtils.encode(text, 'utf8');
const toSignBytes = new Uint8Array(toSign);
console.warn({ toSign });
console.warn({ toSignBytes });
const edKeyBytes = fromHexToArray(edKeyPriv);
// using uint or string for message input makes no difference here.
// let sig = sodium.crypto_sign_detached(toSignBytes, edKeyBytes);
const sig = sodium.crypto_sign_detached(toSignBytes, edKeyBytes); // NO
const kp = await UserUtils.getIdentityKeyPair();
// let sig = await window.libsignal.Curve.async.calculateSignature(kp?.privKey, toSign)
// console.log({isVerified: sodium.crypto_sign_verify_detached(sig, text, fromHexToArray(edKey?.pubKey || ''))})
// try: encode toSign to base64 before signing then decode to bytes afterwards
// todo:
// try the exact example with fillter values off the documentation.
const sig64 = fromUInt8ArrayToBase64(sig);
const sig64a = to_base64(sig);
console.warn({ sig64a });
console.warn({ sig });
console.warn({ sig64: sig64 });
console.warn({ sigLength: sig64.length });
// pubkey - hex - from xSK.public_key
// timestamp - ms
// signature - ? Base64.encodeBytes(signature)
const deleteMessageParams = {
pubkey: userPubKey.key, // pubkey is doing alright
pubkeyED25519: edKey?.pubKey.toUpperCase(), // ed pubkey is right
timestamp,
signature: sig64,
};
// let lokiRpcRes = await snodeRpc('delete_all', deleteMessageParams, snode, userPubKey.key);
// let lokiRpcRes = sendOnionRequest()
const userX25519PublicKey = UserUtils.getOurPubKeyFromCache();
await send(deleteMessageParams, snode, userPubKey.key);
};
const send = async (params: any, snode: Snode, userPubKey: string) => {
// window?.log?.info(`Testing a candidate guard node ${ed25519Str(snode.pubkey_ed25519)}`);
// Send a post request and make sure it is OK
const endpoint = '/storage_rpc/v1';
const userED25519KeyPair = await UserUtils.getUserED25519KeyPair();
const url = `https://${snode.ip}:${snode.port}${endpoint}`;
const ourPK = UserUtils.getOurPubKeyStrFromCache();
const pubKey = window.getStoragePubKey(ourPK); // truncate if testnet
if (!userED25519KeyPair) {
window.log.warn('Cannot forceNetworkDeletion, did not find user ed25519 key.');
return null;
}
const edKeyPriv = userED25519KeyPair.privKey;
// testt
const method = 'delete_all';
params.pubkey = pubKey; // trying with getStoragePubkey
console.warn({ userED25519KeyPair });
console.warn({ edKeyPriv });
const body = {
jsonrpc: '2.0',
id: '0',
method,
params,
};
return pRetry(
async () => {
const userSwarm = await getSwarmFor(userX25519PublicKey.key);
const snodeToMakeRequestTo: Snode | undefined = _.sample(userSwarm);
const edKeyPrivBytes = fromHexToArray(edKeyPriv);
const fetchOptions = {
method: 'POST',
body: JSON.stringify(body),
headers: {
'Content-Type': 'application/json',
'User-Agent': 'WhatsApp',
'Accept-Language': 'en-us',
},
timeout: 10000, // 10s, we want a smaller timeout for testing
agent: snodeHttpsAgent,
};
if (!snodeToMakeRequestTo) {
window.log.warn('Cannot forceNetworkDeletion, without a valid swarm node.');
return null;
}
let response;
// FIXME audric pretry getNetworkTime separately too
return pRetry(
async () => {
const timestamp = await getNetworkTime(snodeToMakeRequestTo);
const verificationData = StringUtils.encode(`delete_all${timestamp}`, 'utf8');
const message = new Uint8Array(verificationData);
const signature = sodium.crypto_sign_detached(message, edKeyPrivBytes);
const signatureBase64 = fromUInt8ArrayToBase64(signature);
const deleteMessageParams = {
pubkey: userX25519PublicKey.key,
pubkey_ed25519: userED25519KeyPair.pubKey.toUpperCase(),
timestamp,
signature: signatureBase64,
};
const ret = await snodeRpc(
'delete_all',
deleteMessageParams,
snodeToMakeRequestTo,
userX25519PublicKey.key
);
if (!ret) {
throw new Error(
`Empty response got for delete_all on snode ${ed25519Str(
snodeToMakeRequestTo.pubkey_ed25519
)}`
);
}
try {
// Log this line for testing
// curl -k -X POST -H 'Content-Type: application/json' -d '"+fetchOptions.body.replace(/"/g, "\\'")+"'", url
window?.log?.info('Sending delete all');
try {
const parsedResponse = JSON.parse(ret.body);
const { swarm } = parsedResponse;
response = await insecureNodeFetch(url, fetchOptions);
} catch (e) {
if (e.type === 'request-timeout') {
window?.log?.warn('test timeout for node,', snode);
if (!swarm) {
throw new Error(
`Invalid JSON swarm response got for delete_all on snode ${ed25519Str(
snodeToMakeRequestTo.pubkey_ed25519
)}, ${ret?.body}`
);
}
const swarmAsArray = Object.entries(swarm) as Array<Array<any>>;
if (!swarmAsArray.length) {
throw new Error(
`Invalid JSON swarmAsArray response got for delete_all on snode ${ed25519Str(
snodeToMakeRequestTo.pubkey_ed25519
)}, ${ret?.body}`
);
}
const results: Map<string, boolean> = new Map(
swarmAsArray.map(snode => {
const snodePubkey = snode[0];
const snodeJson = snode[1];
console.warn({ snodePubkey, snodeJson });
const isFailed = snodeJson.failed || false;
if (isFailed) {
const reason = snodeJson.reason;
const statusCode = snodeJson.code;
if (reason && statusCode) {
window.log.warn(
`Could not delete data from ${ed25519Str(
snodeToMakeRequestTo.pubkey_ed25519
)} due to error: ${reason}: ${statusCode}`
);
} else {
window.log.warn(
`Could not delete data from ${ed25519Str(snodeToMakeRequestTo.pubkey_ed25519)}`
);
}
return [snodePubkey, false];
}
const hashes = snodeJson.deleted as Array<string>;
const signatureSnode = snodeJson.signature as string;
// The signature format is ( PUBKEY_HEX || TIMESTAMP || DELETEDHASH[0] || ... || DELETEDHASH[N] )
const dataToVerify = `${userX25519PublicKey.key}${timestamp}${hashes.join('')}`;
const dataToVerifyUtf8 = StringUtils.encode(dataToVerify, 'utf8');
const isValid = sodium.crypto_sign_verify_detached(
fromBase64ToArray(signatureSnode),
new Uint8Array(dataToVerifyUtf8),
fromHexToArray(snodePubkey)
);
return [snodePubkey, isValid];
})
);
return results;
} catch (e) {
throw new Error(
`Invalid JSON response got for delete_all on snode ${ed25519Str(
snodeToMakeRequestTo.pubkey_ed25519
)}, ${ret?.body}`
);
}
},
{
retries: 3,
minTimeout: 500,
onFailedAttempt: e => {
window?.log?.warn(
`delete_all request attempt #${e.attemptNumber} failed. ${e.retriesLeft} retries left...`
);
},
},
}
return false;
}
if (!response.ok) {
const tg = await response.text();
window?.log?.info('Node failed the guard test:', snode);
}
);
return;
};
const getActiveOpenGroupV2CompleteUrls = async (

Loading…
Cancel
Save