You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-desktop/ts/test/session/unit/sogsv3/knownBlindedKeys_test.ts

589 lines
20 KiB
TypeScript

// tslint:disable: no-implicit-dependencies max-func-body-length no-unused-expression
import { expect } from 'chai';
import Sinon from 'sinon';
import { ConversationCollection } from '../../../../models/conversation';
import { ConversationTypeEnum } from '../../../../models/conversationAttributes';
import { getSodiumNode } from '../../../../node/sodiumNode';
import {
addCachedBlindedKey,
BlindedIdMapping,
findCachedBlindedIdFromUnblinded,
findCachedBlindedMatchOrLookItUp,
getCachedNakedKeyFromBlinded,
getCachedNakedKeyFromBlindedNoServerPubkey,
isNonBlindedKey,
isUsAnySogsFromCache,
KNOWN_BLINDED_KEYS_ITEM,
loadKnownBlindedKeys,
TEST_getCachedBlindedKeys,
TEST_resetCachedBlindedKeys,
tryMatchBlindWithStandardKey,
writeKnownBlindedKeys,
} from '../../../../session/apis/open_group_api/sogsv3/knownBlindedkeys';
import { getConversationController } from '../../../../session/conversations';
import { LibSodiumWrappers } from '../../../../session/crypto';
import { UserUtils } from '../../../../session/utils';
import { expectAsyncToThrow, stubData, stubWindowLog } from '../../../test-utils/utils';
// tslint:disable: chai-vague-errors
const serverPublicKey = 'serverPublicKey';
const blindedId = '151111';
const blindedId2 = '152222';
const realSessionId = '051111';
const realSessionId2 = '052222';
const knownBlindingMatch: BlindedIdMapping = {
realSessionId: '055bcd2bb6e600c43741173e489d925a505a11ab2b971afde56e50272e430f8b37',
blindedId: '1562b2368a1d5452cc25c713b9add4f29405e5e58f6c4aa83fde602a7308bae714',
serverPublicKey: '1615317ca1d8ecdf12dad1bbf1d28d3a90c94fc0010043a7fdc1609ad1c5d111',
};
describe('knownBlindedKeys', () => {
let getItemById: Sinon.SinonStub;
let createOrUpdateItem: Sinon.SinonStub;
let sodium: LibSodiumWrappers;
beforeEach(async () => {
getItemById = stubData('getItemById');
createOrUpdateItem = stubData('createOrUpdateItem');
TEST_resetCachedBlindedKeys();
stubWindowLog();
sodium = await getSodiumNode();
});
afterEach(Sinon.restore);
describe('loadFromDb', () => {
it('loadFromDb with null', async () => {
getItemById.resolves({ id: '', value: null });
await loadKnownBlindedKeys();
expect(TEST_getCachedBlindedKeys()).to.deep.eq([]);
});
it('loadFromDb with empty string', async () => {
getItemById.resolves({ id: '', value: '[]' });
await loadKnownBlindedKeys();
expect(TEST_getCachedBlindedKeys()).to.deep.eq([]);
});
it('loadFromDb with valid json', async () => {
getItemById.resolves({ id: '', value: JSON.stringify([{}, {}]) });
await loadKnownBlindedKeys();
expect(TEST_getCachedBlindedKeys()).to.deep.eq([{}, {}]);
});
it('loadFromDb with invalid json', async () => {
getItemById.resolves({ id: '', value: 'plop invalid json' });
await loadKnownBlindedKeys();
expect(TEST_getCachedBlindedKeys()).to.deep.eq([]);
});
it('loadFromDb once or throws if retry', async () => {
getItemById.resolves({ id: '', value: JSON.stringify([{}, {}]) });
await loadKnownBlindedKeys();
expect(TEST_getCachedBlindedKeys()).to.deep.eq([{}, {}]);
try {
await loadKnownBlindedKeys();
throw new Error('fake'); // the loadKnownBlindedKeys should throw so we should not throw fake
} catch (e) {
expect(e.message).to.not.eq('fake');
expect(e.message).to.eq('loadKnownBlindedKeys must only be called once');
}
});
});
describe('writeKnownBlindedKeys', () => {
it('writeKnownBlindedKeys with null', async () => {
// the cached blinded keys is resetted on each test, so that first one we try to write null
await writeKnownBlindedKeys();
expect(createOrUpdateItem.notCalled).to.be.true;
});
it('writeKnownBlindedKeys with null but loaded', async () => {
// the cached blinded keys is resetted on each test, so that first one we try to write null
getItemById.resolves(null);
await loadKnownBlindedKeys();
await writeKnownBlindedKeys();
expect(createOrUpdateItem.notCalled).to.be.true;
});
it('writeKnownBlindedKeys with array with few elements', async () => {
getItemById.resolves({ id: '', value: JSON.stringify([{}, {}]) });
await loadKnownBlindedKeys();
await writeKnownBlindedKeys();
expect(createOrUpdateItem.callCount).to.be.eq(1);
expect(createOrUpdateItem.lastCall.args[0]).to.be.deep.eq({
id: KNOWN_BLINDED_KEYS_ITEM,
value: '[{},{}]',
});
});
});
describe('isNonBlindedKey', () => {
it('with unblinded key', () => {
expect(isNonBlindedKey('00abcdef1234')).to.be.true;
});
it('with naked blinded key', () => {
expect(isNonBlindedKey('05abcdef1234')).to.be.true;
});
it('with blinded key', () => {
expect(isNonBlindedKey('15abcdef1234')).to.be.false;
});
});
describe('getCachedNakedKeyFromBlinded', () => {
it('with blinded key', async () => {
const initialInDb: Array<BlindedIdMapping> = [{ blindedId, realSessionId, serverPublicKey }];
getItemById.resolves({
id: KNOWN_BLINDED_KEYS_ITEM,
value: JSON.stringify(initialInDb),
});
await loadKnownBlindedKeys();
expect(getCachedNakedKeyFromBlinded(blindedId, serverPublicKey)).to.be.eq(realSessionId);
});
it('with unblinded key', async () => {
const initialInDb: Array<BlindedIdMapping> = [
{ blindedId, realSessionId: realSessionId2, serverPublicKey: 'serverPublicKey1' },
];
getItemById.resolves({
id: KNOWN_BLINDED_KEYS_ITEM,
value: JSON.stringify(initialInDb),
});
await loadKnownBlindedKeys();
expect(getCachedNakedKeyFromBlinded(realSessionId2, 'serverPublicKey1')).to.be.eq(
realSessionId2
);
});
it('with blinded key not found', async () => {
const initialInDb: Array<BlindedIdMapping> = [
{ blindedId, realSessionId: realSessionId2, serverPublicKey: 'serverPublicKey1' },
];
getItemById.resolves({
id: KNOWN_BLINDED_KEYS_ITEM,
value: JSON.stringify(initialInDb),
});
await loadKnownBlindedKeys();
expect(getCachedNakedKeyFromBlinded('151112', 'serverPublicKey1')).to.be.eq(undefined);
});
});
describe('findCachedBlindedIdFromUnblinded', () => {
it('throws with blinded key', async () => {
const initialInDb: Array<BlindedIdMapping> = [
{ blindedId, realSessionId: realSessionId2, serverPublicKey: 'serverPublicKey1' },
];
getItemById.resolves({
id: KNOWN_BLINDED_KEYS_ITEM,
value: JSON.stringify(initialInDb),
});
await loadKnownBlindedKeys();
expect(() => findCachedBlindedIdFromUnblinded(blindedId, 'serverPublicKey1')).to.throw(
'findCachedBlindedIdFromUnblinded needs an unblindedID'
);
});
it('with unblinded key', async () => {
const initialInDb: Array<BlindedIdMapping> = [
{ blindedId, realSessionId: realSessionId2, serverPublicKey: 'serverPublicKey1' },
];
getItemById.resolves({
id: KNOWN_BLINDED_KEYS_ITEM,
value: JSON.stringify(initialInDb),
});
await loadKnownBlindedKeys();
expect(findCachedBlindedIdFromUnblinded(realSessionId2, 'serverPublicKey1')).to.be.eq(
blindedId
);
});
it('with blinded key not found', async () => {
const initialInDb: Array<BlindedIdMapping> = [
{ blindedId, realSessionId: realSessionId2, serverPublicKey: 'serverPublicKey1' },
];
getItemById.resolves({
id: KNOWN_BLINDED_KEYS_ITEM,
value: JSON.stringify(initialInDb),
});
await loadKnownBlindedKeys();
expect(findCachedBlindedIdFromUnblinded('051112', 'serverPublicKey1')).to.be.eq(undefined);
});
});
describe('addCachedBlindedKey', () => {
it('throws with blinded key not blinded', async () => {
getItemById.resolves();
await loadKnownBlindedKeys();
await expectAsyncToThrow(async () => {
await addCachedBlindedKey({
blindedId: realSessionId,
serverPublicKey: 'serverPublicKey1',
realSessionId: realSessionId2,
});
}, 'blindedId is not a blinded key');
});
it('throws with realSessionId not unlinded', async () => {
getItemById.resolves();
await loadKnownBlindedKeys();
await expectAsyncToThrow(async () => {
await addCachedBlindedKey({
blindedId,
serverPublicKey: 'serverPublicKey1',
realSessionId: blindedId2,
});
}, 'realSessionId must not be blinded');
});
it('add to cache and write', async () => {
getItemById.resolves();
await loadKnownBlindedKeys();
await addCachedBlindedKey({
blindedId,
serverPublicKey: 'serverPublicKey1',
realSessionId: realSessionId2,
});
expect(createOrUpdateItem.callCount).to.be.eq(1);
expect(createOrUpdateItem.lastCall.args[0]).to.be.deep.eq({
id: KNOWN_BLINDED_KEYS_ITEM,
value: JSON.stringify([
{
blindedId,
serverPublicKey: 'serverPublicKey1',
realSessionId: realSessionId2,
},
]),
});
});
it('replace existing if found matching with server and blindedId', async () => {
getItemById.resolves();
await loadKnownBlindedKeys();
await addCachedBlindedKey({
blindedId,
serverPublicKey: 'serverPublicKey1',
realSessionId: realSessionId2,
});
await addCachedBlindedKey({
blindedId,
serverPublicKey: 'serverPublicKey1',
realSessionId: '052223', // changing this the second time
});
expect(createOrUpdateItem.callCount).to.be.eq(2);
expect(createOrUpdateItem.firstCall.args[0]).to.be.deep.eq({
id: KNOWN_BLINDED_KEYS_ITEM,
value: JSON.stringify([
{
blindedId,
serverPublicKey: 'serverPublicKey1',
realSessionId: realSessionId2,
},
]),
});
expect(createOrUpdateItem.lastCall.args[0]).to.be.deep.eq({
id: KNOWN_BLINDED_KEYS_ITEM,
value: JSON.stringify([
{
blindedId,
serverPublicKey: 'serverPublicKey1',
realSessionId: '052223',
},
]),
});
});
it('adds a new one if not matching serverpubkey', async () => {
getItemById.resolves();
await loadKnownBlindedKeys();
await addCachedBlindedKey({
blindedId,
serverPublicKey: 'serverPublicKey1',
realSessionId: realSessionId2,
});
await addCachedBlindedKey({
blindedId,
serverPublicKey: 'serverPublicKey2', // changing this the second time
realSessionId: realSessionId2,
});
expect(createOrUpdateItem.callCount).to.be.eq(2);
expect(createOrUpdateItem.firstCall.args[0]).to.be.deep.eq({
id: KNOWN_BLINDED_KEYS_ITEM,
value: JSON.stringify([
{
blindedId,
serverPublicKey: 'serverPublicKey1',
realSessionId: realSessionId2,
},
]),
});
expect(createOrUpdateItem.lastCall.args[0]).to.be.deep.eq({
id: KNOWN_BLINDED_KEYS_ITEM,
value: JSON.stringify([
{
blindedId,
serverPublicKey: 'serverPublicKey1',
realSessionId: realSessionId2,
},
{
blindedId,
serverPublicKey: 'serverPublicKey2',
realSessionId: realSessionId2,
},
]),
});
});
});
describe('isUsAnySogsFromCache', () => {
it('not blinded pubkey, just check if for a match with us from cache', () => {
Sinon.stub(UserUtils, 'getOurPubKeyStrFromCache').returns(realSessionId);
expect(isUsAnySogsFromCache(realSessionId)).to.be.true;
});
it('blinded pubkey, look for something matching it in the cache', async () => {
Sinon.stub(UserUtils, 'getOurPubKeyStrFromCache').returns(realSessionId);
getItemById.resolves();
await loadKnownBlindedKeys();
await addCachedBlindedKey({
blindedId,
serverPublicKey: 'serverPublicKey1',
realSessionId,
});
expect(isUsAnySogsFromCache(blindedId)).to.be.true;
});
it('blinded pubkey, look for something matching it in the cache, but there is none', async () => {
Sinon.stub(UserUtils, 'getOurPubKeyStrFromCache').returns(realSessionId);
getItemById.resolves();
await loadKnownBlindedKeys();
await addCachedBlindedKey({
blindedId,
serverPublicKey: 'serverPublicKey1',
realSessionId: '051112',
});
expect(isUsAnySogsFromCache(blindedId)).to.be.false;
});
});
describe('getCachedNakedKeyFromBlindedNoServerPubkey', () => {
it('not blinded pubkey, just return it', async () => {
getItemById.resolves();
await loadKnownBlindedKeys();
expect(getCachedNakedKeyFromBlindedNoServerPubkey(realSessionId)).to.be.eq(realSessionId);
});
it('blinded pubkey, find the corresponding realSessionID ', async () => {
getItemById.resolves({
id: KNOWN_BLINDED_KEYS_ITEM,
value: JSON.stringify([{ blindedId, realSessionId, serverPublicKey: 'whatever' }]),
});
await loadKnownBlindedKeys();
expect(getCachedNakedKeyFromBlindedNoServerPubkey(blindedId)).to.be.eq(realSessionId);
});
});
describe('tryMatchBlindWithStandardKey', () => {
it('throws if standardSessionId is not standard', async () => {
await expectAsyncToThrow(async () => {
tryMatchBlindWithStandardKey(blindedId, blindedId, serverPublicKey, await getSodiumNode());
}, 'standardKey must be a standard key (starting with 05)');
});
it('throws if blindedSessionId is not standard', () => {
expect(() => {
tryMatchBlindWithStandardKey(realSessionId, realSessionId, serverPublicKey, sodium);
}).to.throw('blindedKey must be a blinded key (starting with 15)');
});
it('returns true if those keys are not matching blind & naked', () => {
const matching = tryMatchBlindWithStandardKey(
knownBlindingMatch.realSessionId,
knownBlindingMatch.blindedId,
knownBlindingMatch.serverPublicKey,
sodium
);
expect(matching).to.be.true;
});
it('returns false if those keys are not matching blind & naked', () => {
const matching = tryMatchBlindWithStandardKey(
'054dc9d99c11060ccff2bef18bfe4f53e8bb548f07d1bfa17fc45cf8f019fb0846', // fake but real sessionID
knownBlindingMatch.blindedId,
knownBlindingMatch.serverPublicKey,
sodium
);
expect(matching).to.be.false;
});
});
describe('findCachedBlindedMatchOrLookItUp', () => {
it('return unblinded pubkey if already unblinded', async () => {
const real = await findCachedBlindedMatchOrLookItUp(realSessionId, serverPublicKey, sodium);
expect(createOrUpdateItem.callCount).to.be.eq(0);
expect(real).to.be.eq(realSessionId);
});
it('does hit the cache first', async () => {
getItemById.resolves();
await loadKnownBlindedKeys();
await addCachedBlindedKey({
blindedId: knownBlindingMatch.blindedId,
serverPublicKey: knownBlindingMatch.serverPublicKey,
realSessionId: knownBlindingMatch.realSessionId,
});
const real = await findCachedBlindedMatchOrLookItUp(realSessionId, serverPublicKey, sodium);
// just one call with that addCachedBlindedKey, and none else, as the cache was hit
expect(createOrUpdateItem.callCount).to.eq(1);
expect(real).to.eq(realSessionId);
});
it('does hit the cache first', async () => {
getItemById.resolves();
await loadKnownBlindedKeys();
await addCachedBlindedKey({
blindedId: knownBlindingMatch.blindedId,
serverPublicKey: knownBlindingMatch.serverPublicKey,
realSessionId: knownBlindingMatch.realSessionId,
});
const real = await findCachedBlindedMatchOrLookItUp(realSessionId, serverPublicKey, sodium);
// just one call with that addCachedBlindedKey, and none else, as the cache was hit
expect(createOrUpdateItem.callCount).to.eq(1);
expect(real).to.eq(realSessionId);
});
describe('when not in cache', () => {
beforeEach(async () => {
getConversationController().reset();
stubData('getAllConversations').resolves(new ConversationCollection([]));
stubData('saveConversation').resolves();
await getConversationController().load();
});
it('does iterate over all the conversations and find the first one matching (fails)', async () => {
getItemById.resolves();
await loadKnownBlindedKeys();
const shouldBeWrittenToDb = {
blindedId: knownBlindingMatch.blindedId,
serverPublicKey: knownBlindingMatch.serverPublicKey,
realSessionId: knownBlindingMatch.realSessionId,
};
await addCachedBlindedKey(shouldBeWrittenToDb);
Sinon.stub(getConversationController(), 'getConversations').returns([]);
const real = await findCachedBlindedMatchOrLookItUp(realSessionId, serverPublicKey, sodium);
// we should have 1 call here as the value was already added to the cache
expect(createOrUpdateItem.callCount).to.eq(1);
expect(createOrUpdateItem.lastCall.args[0]).to.deep.eq({
id: KNOWN_BLINDED_KEYS_ITEM,
value: JSON.stringify([shouldBeWrittenToDb]),
});
expect(real).to.eq(realSessionId);
});
it('does iterate over all the conversations and find the first one matching (passes)', async () => {
getItemById.resolves();
await loadKnownBlindedKeys();
// adding a private conversation with a known match of the blinded pubkey we have
await getConversationController().getOrCreateAndWait(
realSessionId,
ConversationTypeEnum.PRIVATE
);
const convo = await getConversationController().getOrCreateAndWait(
knownBlindingMatch.realSessionId,
ConversationTypeEnum.PRIVATE
);
await getConversationController().getOrCreateAndWait(
realSessionId2,
ConversationTypeEnum.PRIVATE
);
convo.set({ isApproved: true });
const real = await findCachedBlindedMatchOrLookItUp(
knownBlindingMatch.blindedId,
knownBlindingMatch.serverPublicKey,
sodium
);
expect(createOrUpdateItem.callCount).to.eq(1);
const lastCall = createOrUpdateItem.lastCall.args[0];
const idLastCall = lastCall.id;
const valueLastCall = JSON.parse(lastCall.value);
expect(idLastCall).to.be.deep.eq(KNOWN_BLINDED_KEYS_ITEM);
expect(valueLastCall).to.be.deep.eq([knownBlindingMatch]);
expect(real).to.eq(knownBlindingMatch.realSessionId);
});
it('does iterate over all the conversations but is not approved so must fail', async () => {
getItemById.resolves();
await loadKnownBlindedKeys();
// adding a private conversation with a known match of the blinded pubkey we have
const convo = await getConversationController().getOrCreateAndWait(
knownBlindingMatch.realSessionId,
ConversationTypeEnum.PRIVATE
);
convo.set({ isApproved: false });
const real = await findCachedBlindedMatchOrLookItUp(
knownBlindingMatch.blindedId,
knownBlindingMatch.serverPublicKey,
sodium
);
expect(createOrUpdateItem.callCount).to.eq(0);
expect(real).to.eq(undefined);
});
it('does iterate over all the conversations but is not private so must fail: group', async () => {
getItemById.resolves();
await loadKnownBlindedKeys();
// adding a private conversation with a known match of the blinded pubkey we have
const convo = await getConversationController().getOrCreateAndWait(
knownBlindingMatch.realSessionId,
ConversationTypeEnum.GROUP
);
convo.set({ isApproved: false });
const real = await findCachedBlindedMatchOrLookItUp(
knownBlindingMatch.blindedId,
knownBlindingMatch.serverPublicKey,
sodium
);
expect(createOrUpdateItem.callCount).to.eq(0);
expect(real).to.eq(undefined);
});
it('does iterate over all the conversations but is not private so must fail: groupv3', () => {
// we actually cannot test this one as we would need to create a conversation with groupv3 as type but 05 as prefix, and the conversation controller denies it, as expected
});
});
});
});