Merge pull request #1191 from Mikunj/auth-verification

Pairing Authorisation verification when fetched from file server
pull/1195/head
Mikunj Varsani 5 years ago committed by GitHub
commit c23d7a2fe3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,27 @@
import { PairingAuthorisation } from '../js/modules/data';
declare enum PairingTypeEnum {
REQUEST = 1,
GRANT,
}
export interface CryptoInterface {
DHDecrypt: any;
DHEncrypt: any;
DecryptGCM: any; // AES-GCM
EncryptGCM: any; // AES-GCM
FallBackDecryptionError: any;
FallBackSessionCipher: any;
LokiSessionCipher: any;
PairingType: PairingTypeEnum;
_decodeSnodeAddressToPubKey: any;
decryptForPubkey: any;
decryptToken: any;
encryptForPubkey: any;
generateEphemeralKeyPair: any;
generateSignatureForPairing: any;
sha512: any;
validateAuthorisation: any;
verifyAuthorisation(authorisation: PairingAuthorisation): Promise<boolean>;
verifyPairingSignature: any;
}

@ -0,0 +1,8 @@
import { CryptoInterface } from './crypto';
export interface Libloki {
api: any;
crypto: CryptoInterface;
storage: any;
serviceNodes: any;
}

@ -82,25 +82,45 @@ export class MultiDeviceProtocol {
const mapping = await window.lokiFileServerAPI.getUserDeviceMapping( const mapping = await window.lokiFileServerAPI.getUserDeviceMapping(
device.key device.key
); );
// TODO: Filter out invalid authorisations
if (!mapping || !mapping.authorisations) { if (!mapping || !mapping.authorisations) {
return []; return [];
} }
return mapping.authorisations.map( try {
({ const authorisations = mapping.authorisations.map(
primaryDevicePubKey, ({
secondaryDevicePubKey, primaryDevicePubKey,
requestSignature, secondaryDevicePubKey,
grantSignature, requestSignature,
}) => ({ grantSignature,
primaryDevicePubKey, }) => ({
secondaryDevicePubKey, primaryDevicePubKey,
requestSignature: StringUtils.encode(requestSignature, 'base64'), secondaryDevicePubKey,
grantSignature: StringUtils.encode(grantSignature, 'base64'), requestSignature: StringUtils.encode(requestSignature, 'base64'),
}) grantSignature: StringUtils.encode(grantSignature, 'base64'),
); })
);
const validAuthorisations = await Promise.all(
authorisations.map(async authorisation => {
const valid = await window.libloki.crypto.verifyAuthorisation(
authorisation
);
return valid ? authorisation : undefined;
})
);
return validAuthorisations.filter(a => !!a) as Array<
PairingAuthorisation
>;
} catch (e) {
console.warn(
`MultiDeviceProtocol::fetchPairingAuthorisation: Failed to map authorisations for ${device.key}.`,
e
);
return [];
}
} }
/** /**

@ -33,7 +33,7 @@ describe('MessageEncrypter', () => {
TestUtils.stubWindow('libloki', { TestUtils.stubWindow('libloki', {
crypto: { crypto: {
FallBackSessionCipher: Stubs.FallBackSessionCipherStub, FallBackSessionCipher: Stubs.FallBackSessionCipherStub,
}, } as any,
}); });
sandbox.stub(UserUtil, 'getCurrentDevicePubKey').resolves(ourNumber); sandbox.stub(UserUtil, 'getCurrentDevicePubKey').resolves(ourNumber);

@ -71,6 +71,21 @@ describe('MultiDeviceProtocol', () => {
}); });
describe('fetchPairingAuthorisations', () => { describe('fetchPairingAuthorisations', () => {
let verifyAuthorisationStub: sinon.SinonStub<
[PairingAuthorisation],
Promise<boolean>
>;
beforeEach(() => {
verifyAuthorisationStub = sandbox
.stub<[PairingAuthorisation], Promise<boolean>>()
.resolves(true);
TestUtils.stubWindow('libloki', {
crypto: {
verifyAuthorisation: verifyAuthorisationStub,
} as any,
});
});
it('should throw if lokiFileServerAPI does not exist', async () => { it('should throw if lokiFileServerAPI does not exist', async () => {
TestUtils.stubWindow('lokiFileServerAPI', undefined); TestUtils.stubWindow('lokiFileServerAPI', undefined);
expect( expect(
@ -125,9 +140,96 @@ describe('MultiDeviceProtocol', () => {
networkAuth.grantSignature networkAuth.grantSignature
); );
}); });
it('should not return invalid authorisations', async () => {
const networkAuth = {
primaryDevicePubKey:
'05caa6310a490415df45f8f4ad1b3655ad7a11e722257887a30cf71601d679720b',
secondaryDevicePubKey:
'051296b9588641eea268d60ad6636eecb53a95150e91c0531a00203e01a2c16a39',
requestSignature:
'+knEdlenTV+MooRqlFsZRPWW8s9pcjKwB40fY5o0GJmAi2RPZtaVGRTqgApTIn2zPBTE4GQlmPD7uxcczHDjAg==',
grantSignature:
'eKzcOWMEVetybkuiVK2u18B9en5pywohn2Hn25/VOVTMrIsKSCW4xXpqwipfqvgvi62WtUt6SA9bCEB5Ngcyiw==',
};
const stub = sinon.stub().resolves({
isPrimary: false,
authorisations: [networkAuth],
});
TestUtils.stubWindow('lokiFileServerAPI', {
getUserDeviceMapping: stub,
});
verifyAuthorisationStub.resolves(false);
const authorisations = await MultiDeviceProtocol.fetchPairingAuthorisations(
TestUtils.generateFakePubKey()
);
expect(verifyAuthorisationStub.callCount).to.equal(1);
expect(authorisations.length).to.equal(0);
});
it('should handle incorrect pairing authorisations from the file server', async () => {
const invalidAuth = {
primaryDevicePubKey:
'05caa6310a490415df45f8f4ad1b3655ad7a11e722257887a30cf71601d679720b',
secondaryDevicePubKey:
'051296b9588641eea268d60ad6636eecb53a95150e91c0531a00203e01a2c16a39',
requestSignatures:
'+knEdlenTV+MooRqlFsZRPWW8s9pcjKwB40fY5o0GJmAi2RPZtaVGRTqgApTIn2zPBTE4GQlmPD7uxcczHDjAg==',
};
const stub = sinon.stub().resolves({
isPrimary: false,
authorisations: [invalidAuth],
});
TestUtils.stubWindow('lokiFileServerAPI', {
getUserDeviceMapping: stub,
});
const authorisations = await MultiDeviceProtocol.fetchPairingAuthorisations(
TestUtils.generateFakePubKey()
);
expect(authorisations.length).to.equal(0);
});
it('should return empty array if mapping is null', async () => {
const stub = sinon.stub().resolves(null);
TestUtils.stubWindow('lokiFileServerAPI', {
getUserDeviceMapping: stub,
});
const authorisations = await MultiDeviceProtocol.fetchPairingAuthorisations(
TestUtils.generateFakePubKey()
);
expect(authorisations.length).to.equal(0);
});
it('should return empty array if authorisations in mapping are null', async () => {
const stub = sinon.stub().resolves({
isPrimary: false,
authorisations: null,
});
TestUtils.stubWindow('lokiFileServerAPI', {
getUserDeviceMapping: stub,
});
const authorisations = await MultiDeviceProtocol.fetchPairingAuthorisations(
TestUtils.generateFakePubKey()
);
expect(authorisations.length).to.equal(0);
});
}); });
describe('fetchPairingAuthorisationIfNeeded', () => { describe('fetchPairingAuthorisationIfNeeded', () => {
beforeEach(() => {
TestUtils.stubWindow('libloki', {
crypto: {
verifyAuthorisation: async () => true,
} as any,
});
});
let fetchPairingAuthorisationStub: sinon.SinonStub< let fetchPairingAuthorisationStub: sinon.SinonStub<
[PubKey], [PubKey],
Promise<Array<PairingAuthorisation>> Promise<Array<PairingAuthorisation>>
@ -250,6 +352,16 @@ describe('MultiDeviceProtocol', () => {
expect(allDevicePubKeys).to.have.same.members(devices.map(d => d.key)); expect(allDevicePubKeys).to.have.same.members(devices.map(d => d.key));
} }
}); });
it('should return the passed in user device if no pairing authorisations are found', async () => {
const pubKey = TestUtils.generateFakePubKey();
sandbox
.stub(MultiDeviceProtocol, 'getPairingAuthorisations')
.resolves([]);
const allDevices = await MultiDeviceProtocol.getAllDevices(pubKey);
expect(allDevices).to.have.length(1);
expect(allDevices[0].key).to.equal(pubKey.key);
});
}); });
describe('getPrimaryDevice', () => { describe('getPrimaryDevice', () => {

3
ts/window.d.ts vendored

@ -3,6 +3,7 @@ import LokiMessageAPI from '../../js/modules/loki_message_api';
import LokiPublicChatFactoryAPI from '../../js/modules/loki_public_chat_api'; import LokiPublicChatFactoryAPI from '../../js/modules/loki_public_chat_api';
import { LibsignalProtocol } from '../../libtextsecure/libsignal-protocol'; import { LibsignalProtocol } from '../../libtextsecure/libsignal-protocol';
import { SignalInterface } from '../../js/modules/signal'; import { SignalInterface } from '../../js/modules/signal';
import { Libloki } from '../libloki';
/* /*
We declare window stuff here instead of global.d.ts because we are importing other declarations. We declare window stuff here instead of global.d.ts because we are importing other declarations.
@ -42,7 +43,7 @@ declare global {
getFriendsFromContacts: any; getFriendsFromContacts: any;
getSettingValue: any; getSettingValue: any;
i18n: LocalizerType; i18n: LocalizerType;
libloki: any; libloki: Libloki;
libsignal: LibsignalProtocol; libsignal: LibsignalProtocol;
log: any; log: any;
lokiFeatureFlags: any; lokiFeatureFlags: any;

Loading…
Cancel
Save