move delete_all api call to snodeAPI.ts

pull/1833/head
audric 4 years ago
parent c276bf8dbe
commit 61b48dd06b

@ -1,9 +1,7 @@
import React, { useCallback, useState } from 'react';
import { ed25519Str } from '../../session/onions/onionPath';
import {
forceNetworkDeletion,
forceSyncConfigurationNowIfNeeded,
} from '../../session/utils/syncUtils';
import { forceNetworkDeletion } from '../../session/snode_api/SNodeAPI';
import { forceSyncConfigurationNowIfNeeded } from '../../session/utils/syncUtils';
import { updateConfirmModal, updateDeleteAccountModal } from '../../state/ducks/modalDialog';
import { SpacerLG } from '../basic/Text';
import { SessionButton, SessionButtonColor } from '../session/SessionButton';

@ -11,12 +11,18 @@ const { remote } = Electron;
import { snodeRpc } from './lokiRpc';
import { getRandomSnode, getRandomSnodePool, requiredSnodesForAgreement } from './snodePool';
import {
getRandomSnode,
getRandomSnodePool,
getSwarmFor,
requiredSnodesForAgreement,
} from './snodePool';
import { Constants } from '..';
import { getSodium, sha256 } from '../crypto';
import _, { range } from 'lodash';
import pRetry from 'p-retry';
import {
fromBase64ToArray,
fromHex,
fromHexToArray,
fromUInt8ArrayToBase64,
@ -25,6 +31,8 @@ import {
} from '../utils/String';
import { Snode } from '../../data/data';
import { updateIsOnline } from '../../state/ducks/onion';
import { ed25519Str } from '../onions/onionPath';
import { UserUtils, StringUtils } from '../utils';
// ONS name can have [a-zA-Z0-9_-] except that - is not allowed as start or end
// do not define a regex but rather create it on the fly to avoid https://stackoverflow.com/questions/3891641/regex-test-only-works-every-other-time
@ -593,3 +601,163 @@ export async function retrieveNextMessages(
return [];
}
}
/**
* Makes a post to a node to receive the timestamp info. If non-existant, returns -1
* @param snode Snode to send request to
* @returns timestamp of the response from snode
*/
const getNetworkTime = async (snode: Snode): Promise<string | number> => {
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;
};
// tslint:disable-next-line: max-func-body-length
export const forceNetworkDeletion = async (): Promise<Array<string> | null> => {
const sodium = await getSodium();
const userX25519PublicKey = UserUtils.getOurPubKeyStrFromCache();
const userED25519KeyPair = await UserUtils.getUserED25519KeyPair();
if (!userED25519KeyPair) {
window.log.warn('Cannot forceNetworkDeletion, did not find user ed25519 key.');
return null;
}
const edKeyPriv = userED25519KeyPair.privKey;
try {
const maliciousSnodes = await pRetry(async () => {
const userSwarm = await getSwarmFor(userX25519PublicKey);
const snodeToMakeRequestTo: Snode | undefined = _.sample(userSwarm);
const edKeyPrivBytes = fromHexToArray(edKeyPriv);
if (!snodeToMakeRequestTo) {
window.log.warn('Cannot forceNetworkDeletion, without a valid swarm node.');
return null;
}
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,
pubkey_ed25519: userED25519KeyPair.pubKey.toUpperCase(),
timestamp,
signature: signatureBase64,
};
const ret = await snodeRpc(
'delete_all',
deleteMessageParams,
snodeToMakeRequestTo,
userX25519PublicKey
);
if (!ret) {
throw new Error(
`Empty response got for delete_all on snode ${ed25519Str(
snodeToMakeRequestTo.pubkey_ed25519
)}`
);
}
try {
const parsedResponse = JSON.parse(ret.body);
const { swarm } = parsedResponse;
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}`
);
}
// results will only contains the snode pubkeys which returned invalid/empty results
const results: Array<string> = _.compact(
swarmAsArray.map(snode => {
const snodePubkey = snode[0];
const snodeJson = snode[1];
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;
}
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}${timestamp}${hashes.join('')}`;
const dataToVerifyUtf8 = StringUtils.encode(dataToVerify, 'utf8');
const isValid = sodium.crypto_sign_verify_detached(
fromBase64ToArray(signatureSnode),
new Uint8Array(dataToVerifyUtf8),
fromHexToArray(snodePubkey)
);
if (!isValid) {
return snodePubkey;
}
return null;
})
);
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 maliciousSnodes;
} catch (e) {
window.log.warn('failed to delete everything on network:', e);
return null;
}
};

@ -120,166 +120,6 @@ export const forceSyncConfigurationNowIfNeeded = async (waitForMessageSent = fal
});
});
/**
* Makes a post to a node to receive the timestamp info. If non-existant, returns -1
* @param snode Snode to send request to
* @returns timestamp of the response from snode
*/
const getNetworkTime = async (snode: Snode): Promise<string | number> => {
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;
};
// tslint:disable-next-line: max-func-body-length
export const forceNetworkDeletion = async (): Promise<Array<string> | null> => {
const sodium = await getSodium();
const userX25519PublicKey = UserUtils.getOurPubKeyStrFromCache();
const userED25519KeyPair = await UserUtils.getUserED25519KeyPair();
if (!userED25519KeyPair) {
window.log.warn('Cannot forceNetworkDeletion, did not find user ed25519 key.');
return null;
}
const edKeyPriv = userED25519KeyPair.privKey;
try {
const maliciousSnodes = await pRetry(async () => {
const userSwarm = await getSwarmFor(userX25519PublicKey);
const snodeToMakeRequestTo: Snode | undefined = _.sample(userSwarm);
const edKeyPrivBytes = fromHexToArray(edKeyPriv);
if (!snodeToMakeRequestTo) {
window.log.warn('Cannot forceNetworkDeletion, without a valid swarm node.');
return null;
}
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,
pubkey_ed25519: userED25519KeyPair.pubKey.toUpperCase(),
timestamp,
signature: signatureBase64,
};
const ret = await snodeRpc(
'delete_all',
deleteMessageParams,
snodeToMakeRequestTo,
userX25519PublicKey
);
if (!ret) {
throw new Error(
`Empty response got for delete_all on snode ${ed25519Str(
snodeToMakeRequestTo.pubkey_ed25519
)}`
);
}
try {
const parsedResponse = JSON.parse(ret.body);
const { swarm } = parsedResponse;
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}`
);
}
// results will only contains the snode pubkeys which returned invalid/empty results
const results: Array<string> = _.compact(
swarmAsArray.map(snode => {
const snodePubkey = snode[0];
const snodeJson = snode[1];
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;
}
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}${timestamp}${hashes.join('')}`;
const dataToVerifyUtf8 = StringUtils.encode(dataToVerify, 'utf8');
const isValid = sodium.crypto_sign_verify_detached(
fromBase64ToArray(signatureSnode),
new Uint8Array(dataToVerifyUtf8),
fromHexToArray(snodePubkey)
);
if (!isValid) {
return snodePubkey;
}
return null;
})
);
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 maliciousSnodes;
} catch (e) {
window.log.warn('failed to delete everything on network:', e);
return null;
}
};
const getActiveOpenGroupV2CompleteUrls = async (
convos: Array<ConversationModel>
): Promise<Array<string>> => {

@ -4,18 +4,11 @@ import { UserUtils } from '../session/utils';
import { fromArrayBufferToBase64, fromHex, toHex } from '../session/utils/String';
import { getOurPubKeyStrFromCache } from '../session/utils/User';
import { trigger } from '../shims/events';
import {
forceNetworkDeletion,
forceSyncConfigurationNowIfNeeded,
} from '../session/utils/syncUtils';
import { actions as userActions } from '../state/ducks/user';
import { mn_decode, mn_encode } from '../session/crypto/mnemonic';
import { ConversationTypeEnum } from '../models/conversation';
import _ from 'underscore';
import { persistStore } from 'redux-persist';
import { ed25519Str } from '../session/onions/onionPath';
import { SessionButtonColor } from '../components/session/SessionButton';
import { updateConfirmModal } from '../state/ducks/modalDialog';
/**
* Might throw

Loading…
Cancel
Save