|
|
@ -32,7 +32,7 @@ import {
|
|
|
|
import { Snode } from '../../data/data';
|
|
|
|
import { Snode } from '../../data/data';
|
|
|
|
import { updateIsOnline } from '../../state/ducks/onion';
|
|
|
|
import { updateIsOnline } from '../../state/ducks/onion';
|
|
|
|
import { ed25519Str } from '../onions/onionPath';
|
|
|
|
import { ed25519Str } from '../onions/onionPath';
|
|
|
|
import { UserUtils, StringUtils } from '../utils';
|
|
|
|
import { StringUtils, UserUtils } from '../utils';
|
|
|
|
|
|
|
|
|
|
|
|
// ONS name can have [a-zA-Z0-9_-] except that - is not allowed as start or end
|
|
|
|
// 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
|
|
|
|
// 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
|
|
|
@ -607,7 +607,8 @@ export async function retrieveNextMessages(
|
|
|
|
* @param snode Snode to send request to
|
|
|
|
* @param snode Snode to send request to
|
|
|
|
* @returns timestamp of the response from snode
|
|
|
|
* @returns timestamp of the response from snode
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
const getNetworkTime = async (snode: Snode): Promise<string | number> => {
|
|
|
|
// tslint:disable-next-line: variable-name
|
|
|
|
|
|
|
|
export const TEST_getNetworkTime = async (snode: Snode): Promise<string | number> => {
|
|
|
|
const response: any = await snodeRpc('info', {}, snode);
|
|
|
|
const response: any = await snodeRpc('info', {}, snode);
|
|
|
|
const body = JSON.parse(response.body);
|
|
|
|
const body = JSON.parse(response.body);
|
|
|
|
const timestamp = body?.timestamp;
|
|
|
|
const timestamp = body?.timestamp;
|
|
|
@ -625,139 +626,153 @@ export const forceNetworkDeletion = async (): Promise<Array<string> | null> => {
|
|
|
|
const userED25519KeyPair = await UserUtils.getUserED25519KeyPair();
|
|
|
|
const userED25519KeyPair = await UserUtils.getUserED25519KeyPair();
|
|
|
|
|
|
|
|
|
|
|
|
if (!userED25519KeyPair) {
|
|
|
|
if (!userED25519KeyPair) {
|
|
|
|
window.log.warn('Cannot forceNetworkDeletion, did not find user ed25519 key.');
|
|
|
|
window?.log?.warn('Cannot forceNetworkDeletion, did not find user ed25519 key.');
|
|
|
|
return null;
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const edKeyPriv = userED25519KeyPair.privKey;
|
|
|
|
const edKeyPriv = userED25519KeyPair.privKey;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const maliciousSnodes = await pRetry(async () => {
|
|
|
|
const maliciousSnodes = await pRetry(
|
|
|
|
const userSwarm = await getSwarmFor(userX25519PublicKey);
|
|
|
|
async () => {
|
|
|
|
const snodeToMakeRequestTo: Snode | undefined = _.sample(userSwarm);
|
|
|
|
const userSwarm = await getSwarmFor(userX25519PublicKey);
|
|
|
|
const edKeyPrivBytes = fromHexToArray(edKeyPriv);
|
|
|
|
const snodeToMakeRequestTo: Snode | undefined = _.sample(userSwarm);
|
|
|
|
|
|
|
|
const edKeyPrivBytes = fromHexToArray(edKeyPriv);
|
|
|
|
if (!snodeToMakeRequestTo) {
|
|
|
|
|
|
|
|
window.log.warn('Cannot forceNetworkDeletion, without a valid swarm node.');
|
|
|
|
if (!snodeToMakeRequestTo) {
|
|
|
|
return null;
|
|
|
|
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) {
|
|
|
|
return pRetry(
|
|
|
|
throw new Error(
|
|
|
|
async () => {
|
|
|
|
`Empty response got for delete_all on snode ${ed25519Str(
|
|
|
|
const timestamp = await exports.TEST_getNetworkTime(snodeToMakeRequestTo);
|
|
|
|
snodeToMakeRequestTo.pubkey_ed25519
|
|
|
|
|
|
|
|
)}`
|
|
|
|
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
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
if (!ret) {
|
|
|
|
const parsedResponse = JSON.parse(ret.body);
|
|
|
|
|
|
|
|
const { swarm } = parsedResponse;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!swarm) {
|
|
|
|
|
|
|
|
throw new Error(
|
|
|
|
throw new Error(
|
|
|
|
`Invalid JSON swarm response got for delete_all on snode ${ed25519Str(
|
|
|
|
`Empty response got for delete_all on snode ${ed25519Str(
|
|
|
|
snodeToMakeRequestTo.pubkey_ed25519
|
|
|
|
snodeToMakeRequestTo.pubkey_ed25519
|
|
|
|
)}, ${ret?.body}`
|
|
|
|
)}`
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const swarmAsArray = Object.entries(swarm) as Array<Array<any>>;
|
|
|
|
|
|
|
|
if (!swarmAsArray.length) {
|
|
|
|
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(
|
|
|
|
throw new Error(
|
|
|
|
`Invalid JSON swarmAsArray response got for delete_all on snode ${ed25519Str(
|
|
|
|
`Invalid JSON response got for delete_all on snode ${ed25519Str(
|
|
|
|
snodeToMakeRequestTo.pubkey_ed25519
|
|
|
|
snodeToMakeRequestTo.pubkey_ed25519
|
|
|
|
)}, ${ret?.body}`
|
|
|
|
)}, ${ret?.body}`
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// results will only contains the snode pubkeys which returned invalid/empty results
|
|
|
|
},
|
|
|
|
const results: Array<string> = _.compact(
|
|
|
|
{
|
|
|
|
swarmAsArray.map(snode => {
|
|
|
|
retries: 3,
|
|
|
|
const snodePubkey = snode[0];
|
|
|
|
minTimeout: exports.TEST_getMinTimeout(),
|
|
|
|
const snodeJson = snode[1];
|
|
|
|
onFailedAttempt: e => {
|
|
|
|
|
|
|
|
window?.log?.warn(
|
|
|
|
const isFailed = snodeJson.failed || false;
|
|
|
|
`delete_all INNER request attempt #${e.attemptNumber} failed. ${e.retriesLeft} retries left...`
|
|
|
|
|
|
|
|
);
|
|
|
|
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: exports.TEST_getMinTimeout(),
|
|
|
|
|
|
|
|
onFailedAttempt: e => {
|
|
|
|
|
|
|
|
window?.log?.warn(
|
|
|
|
|
|
|
|
`delete_all OUTER request attempt #${e.attemptNumber} failed. ${e.retriesLeft} retries left...`
|
|
|
|
|
|
|
|
);
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
}
|
|
|
|
retries: 3,
|
|
|
|
);
|
|
|
|
minTimeout: 500,
|
|
|
|
|
|
|
|
onFailedAttempt: e => {
|
|
|
|
|
|
|
|
window?.log?.warn(
|
|
|
|
|
|
|
|
`delete_all request attempt #${e.attemptNumber} failed. ${e.retriesLeft} retries left...`
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}, {});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return maliciousSnodes;
|
|
|
|
return maliciousSnodes;
|
|
|
|
} catch (e) {
|
|
|
|
} catch (e) {
|
|
|
|
window.log.warn('failed to delete everything on network:', e);
|
|
|
|
window?.log?.warn('failed to delete everything on network:', e);
|
|
|
|
return null;
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// tslint:disable-next-line: variable-name
|
|
|
|
|
|
|
|
export const TEST_getMinTimeout = () => 500;
|
|
|
|