From 2e82bf98c1f57978bffa5a334b4feefe9c8eed08 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 23 Jun 2020 10:44:46 +1000 Subject: [PATCH 1/3] Added libloki typings --- libloki/crypto.d.ts | 27 +++++++++++++++++++++++++++ libloki/index.d.ts | 8 ++++++++ ts/window.d.ts | 3 ++- 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 libloki/crypto.d.ts create mode 100644 libloki/index.d.ts diff --git a/libloki/crypto.d.ts b/libloki/crypto.d.ts new file mode 100644 index 000000000..b7bfcb63b --- /dev/null +++ b/libloki/crypto.d.ts @@ -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; + verifyPairingSignature: any, +} diff --git a/libloki/index.d.ts b/libloki/index.d.ts new file mode 100644 index 000000000..885a1b80d --- /dev/null +++ b/libloki/index.d.ts @@ -0,0 +1,8 @@ +import { CryptoInterface } from "./crypto"; + +export interface Libloki { + api: any; + crypto: CryptoInterface; + storage: any; + serviceNodes: any; +} diff --git a/ts/window.d.ts b/ts/window.d.ts index ca938e632..be0f5e570 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -3,6 +3,7 @@ import LokiMessageAPI from '../../js/modules/loki_message_api'; import LokiPublicChatFactoryAPI from '../../js/modules/loki_public_chat_api'; import { LibsignalProtocol } from '../../libtextsecure/libsignal-protocol'; 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. @@ -42,7 +43,7 @@ declare global { getFriendsFromContacts: any; getSettingValue: any; i18n: LocalizerType; - libloki: any; + libloki: Libloki; libsignal: LibsignalProtocol; log: any; lokiFeatureFlags: any; From d4ea483aa058e688c24d348d4317123f796a1b2c Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 23 Jun 2020 16:38:05 +1000 Subject: [PATCH 2/3] Filter invalid authorisations from the file server --- libloki/crypto.d.ts | 38 +++--- libloki/index.d.ts | 2 +- ts/session/protocols/MultiDeviceProtocol.ts | 48 +++++--- .../session/crypto/MessageEncrypter_test.ts | 2 +- .../protocols/MultiDeviceProtocol_test.ts | 112 ++++++++++++++++++ 5 files changed, 167 insertions(+), 35 deletions(-) diff --git a/libloki/crypto.d.ts b/libloki/crypto.d.ts index b7bfcb63b..d928decb9 100644 --- a/libloki/crypto.d.ts +++ b/libloki/crypto.d.ts @@ -1,27 +1,27 @@ -import { PairingAuthorisation } from "../js/modules/data"; +import { PairingAuthorisation } from '../js/modules/data'; declare enum PairingTypeEnum { REQUEST = 1, - GRANT + 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, + 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; - verifyPairingSignature: any, + verifyPairingSignature: any; } diff --git a/libloki/index.d.ts b/libloki/index.d.ts index 885a1b80d..82ad4d784 100644 --- a/libloki/index.d.ts +++ b/libloki/index.d.ts @@ -1,4 +1,4 @@ -import { CryptoInterface } from "./crypto"; +import { CryptoInterface } from './crypto'; export interface Libloki { api: any; diff --git a/ts/session/protocols/MultiDeviceProtocol.ts b/ts/session/protocols/MultiDeviceProtocol.ts index 3958eeec3..3692a63f1 100644 --- a/ts/session/protocols/MultiDeviceProtocol.ts +++ b/ts/session/protocols/MultiDeviceProtocol.ts @@ -82,25 +82,45 @@ export class MultiDeviceProtocol { const mapping = await window.lokiFileServerAPI.getUserDeviceMapping( device.key ); - // TODO: Filter out invalid authorisations if (!mapping || !mapping.authorisations) { return []; } - return mapping.authorisations.map( - ({ - primaryDevicePubKey, - secondaryDevicePubKey, - requestSignature, - grantSignature, - }) => ({ - primaryDevicePubKey, - secondaryDevicePubKey, - requestSignature: StringUtils.encode(requestSignature, 'base64'), - grantSignature: StringUtils.encode(grantSignature, 'base64'), - }) - ); + try { + const authorisations = mapping.authorisations.map( + ({ + primaryDevicePubKey, + secondaryDevicePubKey, + requestSignature, + grantSignature, + }) => ({ + primaryDevicePubKey, + secondaryDevicePubKey, + requestSignature: StringUtils.encode(requestSignature, 'base64'), + grantSignature: StringUtils.encode(grantSignature, 'base64'), + }) + ); + + const validAuthorisations = await Promise.all( + authorisations.map(async authoritsation => { + const valid = await window.libloki.crypto.verifyAuthorisation( + authoritsation + ); + return valid ? authoritsation : undefined; + }) + ); + + return validAuthorisations.filter(a => !!a) as Array< + PairingAuthorisation + >; + } catch (e) { + console.warn( + `MultiDeviceProtocol::fetchPairingAuthorisation: Failed to map authorisations for ${device.key}.`, + e + ); + return []; + } } /** diff --git a/ts/test/session/crypto/MessageEncrypter_test.ts b/ts/test/session/crypto/MessageEncrypter_test.ts index 6f16e77c7..7612f3c37 100644 --- a/ts/test/session/crypto/MessageEncrypter_test.ts +++ b/ts/test/session/crypto/MessageEncrypter_test.ts @@ -33,7 +33,7 @@ describe('MessageEncrypter', () => { TestUtils.stubWindow('libloki', { crypto: { FallBackSessionCipher: Stubs.FallBackSessionCipherStub, - }, + } as any, }); sandbox.stub(UserUtil, 'getCurrentDevicePubKey').resolves(ourNumber); diff --git a/ts/test/session/protocols/MultiDeviceProtocol_test.ts b/ts/test/session/protocols/MultiDeviceProtocol_test.ts index ad287a3dc..e1d248121 100644 --- a/ts/test/session/protocols/MultiDeviceProtocol_test.ts +++ b/ts/test/session/protocols/MultiDeviceProtocol_test.ts @@ -71,6 +71,21 @@ describe('MultiDeviceProtocol', () => { }); describe('fetchPairingAuthorisations', () => { + let verifyAuthorisationStub: sinon.SinonStub< + [PairingAuthorisation], + Promise + >; + beforeEach(() => { + verifyAuthorisationStub = sandbox + .stub<[PairingAuthorisation], Promise>() + .resolves(true); + TestUtils.stubWindow('libloki', { + crypto: { + verifyAuthorisation: verifyAuthorisationStub, + } as any, + }); + }); + it('should throw if lokiFileServerAPI does not exist', async () => { TestUtils.stubWindow('lokiFileServerAPI', undefined); expect( @@ -125,9 +140,96 @@ describe('MultiDeviceProtocol', () => { 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', () => { + beforeEach(() => { + TestUtils.stubWindow('libloki', { + crypto: { + verifyAuthorisation: async () => true, + } as any, + }); + }); + let fetchPairingAuthorisationStub: sinon.SinonStub< [PubKey], Promise> @@ -250,6 +352,16 @@ describe('MultiDeviceProtocol', () => { 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', () => { From 212b002c62881ae834d48954ae63c076ee3e1f75 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 24 Jun 2020 14:25:34 +1000 Subject: [PATCH 3/3] Fix typo --- ts/session/protocols/MultiDeviceProtocol.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ts/session/protocols/MultiDeviceProtocol.ts b/ts/session/protocols/MultiDeviceProtocol.ts index 3692a63f1..45c5b7b08 100644 --- a/ts/session/protocols/MultiDeviceProtocol.ts +++ b/ts/session/protocols/MultiDeviceProtocol.ts @@ -103,11 +103,11 @@ export class MultiDeviceProtocol { ); const validAuthorisations = await Promise.all( - authorisations.map(async authoritsation => { + authorisations.map(async authorisation => { const valid = await window.libloki.crypto.verifyAuthorisation( - authoritsation + authorisation ); - return valid ? authoritsation : undefined; + return valid ? authorisation : undefined; }) );