From 646973e3301c6e268e765cb8abd61b8ad896d329 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 10 Jul 2020 09:11:23 +1000 Subject: [PATCH] Made changes to how messages are sent. Instead of blocking the message queue when we don't have a session, we instead just send out a session request and send the queued messages using fallback encryption. This means that users will be able to message right away without having to wait. The only down side is that all messages sent before sessions are established will be using the weaker encryption. This change also means we have to detach session requests from envelope type (which is a good thing) and thus now a message is a session request if it contains a preKeyBundle. --- integration_test/message_sync_test.js | 4 +- libloki/api.js | 2 +- libloki/crypto.js | 2 +- libloki/test/crypto_test.js | 4 +- libtextsecure/outgoing_message.js | 3 +- protos/SignalService.proto | 2 +- ts/receiver/contentMessage.ts | 16 ++-- ts/receiver/sessionHandling.ts | 3 +- ts/session/crypto/MessageEncrypter.ts | 2 +- ts/session/protocols/SessionProtocol.ts | 2 +- ts/session/sending/MessageQueue.ts | 3 +- ts/session/sending/PendingMessageCache.ts | 2 +- ts/session/types/EncryptionType.ts | 2 +- ts/session/utils/Messages.ts | 26 +++-- .../session/crypto/MessageEncrypter_test.ts | 16 +--- ts/test/session/messages/RawMessage_test.ts | 59 ------------ ts/test/session/sending/MessageQueue_test.ts | 52 ++++------ .../sending/PendingMessageCache_test.ts | 16 +++- ts/test/session/utils/Messages_test.ts | 95 ++++++++++++++++++- .../ciphers/FallBackSessionCipherStub.ts | 2 +- 20 files changed, 165 insertions(+), 148 deletions(-) delete mode 100644 ts/test/session/messages/RawMessage_test.ts diff --git a/integration_test/message_sync_test.js b/integration_test/message_sync_test.js index 4f40a89d3..3aa078e23 100644 --- a/integration_test/message_sync_test.js +++ b/integration_test/message_sync_test.js @@ -50,7 +50,7 @@ describe('Message Syncing', function() { // Linking Alice2 to Alice1 // alice2 should trigger auto FR with bob1 as it's one of her friend - // and alice2 should trigger a SESSION_REQUEST with bob1 as he is in a closed group with her + // and alice2 should trigger a FALLBACK_MESSAGE with bob1 as he is in a closed group with her await common.linkApp2ToApp(Alice1, Alice2, common.TEST_PUBKEY1); await common.timeout(25000); @@ -119,7 +119,7 @@ describe('Message Syncing', function() { // once autoFR is auto-accepted, alice2 trigger contact sync await common.logsContains( bob1Logs, - `Received SESSION_REQUEST from source: ${alice2Pubkey}`, + `Received FALLBACK_MESSAGE from source: ${alice2Pubkey}`, 1 ); await common.logsContains( diff --git a/libloki/api.js b/libloki/api.js index d187cfb45..596807898 100644 --- a/libloki/api.js +++ b/libloki/api.js @@ -8,7 +8,7 @@ const DebugFlagsEnum = { GROUP_SYNC_MESSAGES: 1, CONTACT_SYNC_MESSAGES: 2, - SESSION_REQUEST_MESSAGES: 8, + FALLBACK_MESSAGES: 8, SESSION_MESSAGE_SENDING: 16, SESSION_BACKGROUND_MESSAGE: 32, GROUP_REQUEST_INFO: 64, diff --git a/libloki/crypto.js b/libloki/crypto.js index cec6ae99a..806aed9b9 100644 --- a/libloki/crypto.js +++ b/libloki/crypto.js @@ -153,7 +153,7 @@ ivAndCiphertext ).toString('binary'); return { - type: textsecure.protobuf.Envelope.Type.SESSION_REQUEST, + type: textsecure.protobuf.Envelope.Type.FALLBACK_MESSAGE, body: binaryIvAndCiphertext, registrationId: undefined, }; diff --git a/libloki/test/crypto_test.js b/libloki/test/crypto_test.js index 8a294c4ba..4d57df09a 100644 --- a/libloki/test/crypto_test.js +++ b/libloki/test/crypto_test.js @@ -19,12 +19,12 @@ describe('Crypto', () => { fallbackCipher = new libloki.crypto.FallBackSessionCipher(address); }); - it('should encrypt fallback cipher messages as friend requests', async () => { + it('should encrypt fallback cipher messages as fallback messages', async () => { const buffer = new ArrayBuffer(10); const { type } = await fallbackCipher.encrypt(buffer); assert.strictEqual( type, - textsecure.protobuf.Envelope.Type.SESSION_REQUEST + textsecure.protobuf.Envelope.Type.FALLBACK_MESSAGE ); }); diff --git a/libtextsecure/outgoing_message.js b/libtextsecure/outgoing_message.js index 3ef1c3799..0dd3ed43a 100644 --- a/libtextsecure/outgoing_message.js +++ b/libtextsecure/outgoing_message.js @@ -325,8 +325,7 @@ OutgoingMessage.prototype = { // END_SESSION means Session reset message const isEndSession = flags === textsecure.protobuf.DataMessage.Flags.END_SESSION; - const isSessionRequest = - flags === textsecure.protobuf.DataMessage.Flags.SESSION_REQUEST; + const isSessionRequest = false; if (enableFallBackEncryption || isEndSession) { // Encrypt them with the fallback diff --git a/protos/SignalService.proto b/protos/SignalService.proto index 0a5300c5d..bd0f937c0 100644 --- a/protos/SignalService.proto +++ b/protos/SignalService.proto @@ -13,7 +13,7 @@ message Envelope { RECEIPT = 5; UNIDENTIFIED_SENDER = 6; MEDIUM_GROUP_CIPHERTEXT = 7; - SESSION_REQUEST = 101; // contains prekeys and is using simple encryption + FALLBACK_MESSAGE = 101; // contains prekeys and is using simple encryption } optional Type type = 1; diff --git a/ts/receiver/contentMessage.ts b/ts/receiver/contentMessage.ts index 3e8d105b9..0022131f2 100644 --- a/ts/receiver/contentMessage.ts +++ b/ts/receiver/contentMessage.ts @@ -183,9 +183,9 @@ async function decryptUnidentifiedSender( } // We might have substituted the type based on decrypted content - if (type === SignalService.Envelope.Type.SESSION_REQUEST) { + if (type === SignalService.Envelope.Type.FALLBACK_MESSAGE) { // eslint-disable-next-line no-param-reassign - envelope.type = SignalService.Envelope.Type.SESSION_REQUEST; + envelope.type = SignalService.Envelope.Type.FALLBACK_MESSAGE; } const blocked = await isBlocked(sender.getName()); @@ -227,8 +227,8 @@ async function doDecrypt( return lokiSessionCipher.decryptWhisperMessage(ciphertext).then(unpad); case SignalService.Envelope.Type.MEDIUM_GROUP_CIPHERTEXT: return decryptForMediumGroup(envelope, ciphertext); - case SignalService.Envelope.Type.SESSION_REQUEST: { - window.log.info('session-request message from ', envelope.source); + case SignalService.Envelope.Type.FALLBACK_MESSAGE: { + window.log.info('fallback message from ', envelope.source); const fallBackSessionCipher = new libloki.crypto.FallBackSessionCipher( address @@ -344,13 +344,13 @@ export async function innerHandleContentMessage( const content = SignalService.Content.decode(new Uint8Array(plaintext)); - const { SESSION_REQUEST } = SignalService.Envelope.Type; + const { FALLBACK_MESSAGE } = SignalService.Envelope.Type; await ConversationController.getOrCreateAndWait(envelope.source, 'private'); - if (envelope.type === SESSION_REQUEST) { - await handleSessionRequestMessage(envelope, content); - } else { + if (content.preKeyBundleMessage) { + await handleSessionRequestMessage(envelope, content.preKeyBundleMessage); + } else if (envelope.type !== FALLBACK_MESSAGE) { const device = new PubKey(envelope.source); await SessionProtocol.onSessionEstablished(device); diff --git a/ts/receiver/sessionHandling.ts b/ts/receiver/sessionHandling.ts index 6a8966149..1dd04c571 100644 --- a/ts/receiver/sessionHandling.ts +++ b/ts/receiver/sessionHandling.ts @@ -25,10 +25,9 @@ export async function handleEndSession(number: string): Promise { export async function handleSessionRequestMessage( envelope: EnvelopePlus, - content: SignalService.Content + preKeyBundleMessage: SignalService.IPreKeyBundleMessage ) { const { libsignal, libloki, StringView, textsecure, dcodeIO, log } = window; - const { preKeyBundleMessage } = content; window.console.log( `Received SESSION_REQUEST from source: ${envelope.source}` diff --git a/ts/session/crypto/MessageEncrypter.ts b/ts/session/crypto/MessageEncrypter.ts index 9a9471e1a..ded12577b 100644 --- a/ts/session/crypto/MessageEncrypter.ts +++ b/ts/session/crypto/MessageEncrypter.ts @@ -53,7 +53,7 @@ export async function encrypt( } let innerCipherText: CipherTextObject; - if (encryptionType === EncryptionType.SessionRequest) { + if (encryptionType === EncryptionType.Fallback) { const cipher = new window.libloki.crypto.FallBackSessionCipher(address); innerCipherText = await cipher.encrypt(plainText.buffer); } else { diff --git a/ts/session/protocols/SessionProtocol.ts b/ts/session/protocols/SessionProtocol.ts index 45b3673f6..8e58f465c 100644 --- a/ts/session/protocols/SessionProtocol.ts +++ b/ts/session/protocols/SessionProtocol.ts @@ -144,7 +144,7 @@ export class SessionProtocol { SessionProtocol.pendingSendSessionsTimestamp.add(pubkey.key); try { - const rawMessage = MessageUtils.toRawMessage(pubkey, message); + const rawMessage = await MessageUtils.toRawMessage(pubkey, message); await MessageSender.send(rawMessage); await SessionProtocol.updateSentSessionTimestamp(pubkey.key, timestamp); } catch (e) { diff --git a/ts/session/sending/MessageQueue.ts b/ts/session/sending/MessageQueue.ts index f8c089d14..9c99d892e 100644 --- a/ts/session/sending/MessageQueue.ts +++ b/ts/session/sending/MessageQueue.ts @@ -130,10 +130,9 @@ export class MessageQueue implements MessageQueueInterface { const isMediumGroup = GroupUtils.isMediumGroup(device); const hasSession = await SessionProtocol.hasSession(device); + // If we don't have a session then try and establish one and then continue sending messages if (!isMediumGroup && !hasSession) { await SessionProtocol.sendSessionRequestIfNeeded(device); - - return; } const jobQueue = this.getJobQueue(device); diff --git a/ts/session/sending/PendingMessageCache.ts b/ts/session/sending/PendingMessageCache.ts index a26cea8c6..7ccf7fafb 100644 --- a/ts/session/sending/PendingMessageCache.ts +++ b/ts/session/sending/PendingMessageCache.ts @@ -41,7 +41,7 @@ export class PendingMessageCache { message: ContentMessage ): Promise { await this.loadFromDBIfNeeded(); - const rawMessage = MessageUtils.toRawMessage(device, message); + const rawMessage = await MessageUtils.toRawMessage(device, message); // Does it exist in cache already? if (this.find(rawMessage)) { diff --git a/ts/session/types/EncryptionType.ts b/ts/session/types/EncryptionType.ts index 86d1aeda7..d48f962aa 100644 --- a/ts/session/types/EncryptionType.ts +++ b/ts/session/types/EncryptionType.ts @@ -1,5 +1,5 @@ export enum EncryptionType { Signal, - SessionRequest, + Fallback, MediumGroup, } diff --git a/ts/session/utils/Messages.ts b/ts/session/utils/Messages.ts index d09cb45b9..269275373 100644 --- a/ts/session/utils/Messages.ts +++ b/ts/session/utils/Messages.ts @@ -1,18 +1,30 @@ import { RawMessage } from '../types/RawMessage'; -import { ContentMessage, SessionRequestMessage } from '../messages/outgoing'; +import { + ContentMessage, + MediumGroupMessage, + SessionRequestMessage, +} from '../messages/outgoing'; import { EncryptionType, PubKey } from '../types'; +import { SessionProtocol } from '../protocols'; -export function toRawMessage( +export async function toRawMessage( device: PubKey, message: ContentMessage -): RawMessage { +): Promise { const timestamp = message.timestamp; const ttl = message.ttl(); const plainTextBuffer = message.plainTextBuffer(); - const encryption = - message instanceof SessionRequestMessage - ? EncryptionType.SessionRequest - : EncryptionType.Signal; + + let encryption: EncryptionType; + if (message instanceof MediumGroupMessage) { + encryption = EncryptionType.MediumGroup; + } else if (message instanceof SessionRequestMessage) { + encryption = EncryptionType.Fallback; + } else { + // If we don't have a session yet then send using fallback encryption until we have a session + const hasSession = await SessionProtocol.hasSession(device); + encryption = hasSession ? EncryptionType.Signal : EncryptionType.Fallback; + } // tslint:disable-next-line: no-unnecessary-local-variable const rawMessage: RawMessage = { diff --git a/ts/test/session/crypto/MessageEncrypter_test.ts b/ts/test/session/crypto/MessageEncrypter_test.ts index 7612f3c37..d46b80fae 100644 --- a/ts/test/session/crypto/MessageEncrypter_test.ts +++ b/ts/test/session/crypto/MessageEncrypter_test.ts @@ -66,11 +66,7 @@ describe('MessageEncrypter', () => { Stubs.FallBackSessionCipherStub.prototype, 'encrypt' ); - await MessageEncrypter.encrypt( - '1', - data, - EncryptionType.SessionRequest - ); + await MessageEncrypter.encrypt('1', data, EncryptionType.Fallback); expect(spy.called).to.equal( true, 'FallbackSessionCipher.encrypt should be called.' @@ -83,11 +79,7 @@ describe('MessageEncrypter', () => { Stubs.FallBackSessionCipherStub.prototype, 'encrypt' ); - await MessageEncrypter.encrypt( - '1', - data, - EncryptionType.SessionRequest - ); + await MessageEncrypter.encrypt('1', data, EncryptionType.Fallback); const paddedData = MessageEncrypter.padPlainTextBuffer(data); const firstArgument = new Uint8Array(spy.args[0][0]); @@ -99,7 +91,7 @@ describe('MessageEncrypter', () => { const result = await MessageEncrypter.encrypt( '1', data, - EncryptionType.SessionRequest + EncryptionType.Fallback ); expect(result.envelopeType).to.deep.equal( SignalService.Envelope.Type.UNIDENTIFIED_SENDER @@ -144,7 +136,7 @@ describe('MessageEncrypter', () => { describe('Sealed Sender', () => { it('should pass the correct values to SecretSessionCipher encrypt', async () => { - const types = [EncryptionType.SessionRequest, EncryptionType.Signal]; + const types = [EncryptionType.Fallback, EncryptionType.Signal]; for (const type of types) { const spy = sandbox.spy( Stubs.SecretSessionCipherStub.prototype, diff --git a/ts/test/session/messages/RawMessage_test.ts b/ts/test/session/messages/RawMessage_test.ts deleted file mode 100644 index 208247b68..000000000 --- a/ts/test/session/messages/RawMessage_test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { expect } from 'chai'; -import { beforeEach } from 'mocha'; - -import { - DeviceUnlinkMessage, - SessionRequestMessage, -} from '../../../session/messages/outgoing'; -import { SignalService } from '../../../protobuf'; -import { toRawMessage } from '../../../session/utils/Messages'; -import { EncryptionType, PubKey, RawMessage } from '../../../session/types'; -import { TestUtils } from '../../test-utils'; -import { TextEncoder } from 'util'; - -describe('toRawMessage', () => { - let message: DeviceUnlinkMessage; - const pubkey: PubKey = TestUtils.generateFakePubKey(); - let raw: RawMessage; - - beforeEach(() => { - const timestamp = Date.now(); - message = new DeviceUnlinkMessage({ timestamp }); - raw = toRawMessage(pubkey, message); - }); - - it('copied fields are set', () => { - expect(raw).to.have.property('ttl', message.ttl()); - expect(raw) - .to.have.property('plainTextBuffer') - .to.be.deep.equal(message.plainTextBuffer()); - expect(raw).to.have.property('timestamp', message.timestamp); - expect(raw).to.have.property('identifier', message.identifier); - expect(raw).to.have.property('device', pubkey.key); - }); - - it('encryption is set to SESSION_REQUEST if message is of instance SessionRequestMessage', () => { - const preKeyBundle = { - deviceId: 123456, - preKeyId: 654321, - signedKeyId: 111111, - preKey: new TextEncoder().encode('preKey'), - signature: new TextEncoder().encode('signature'), - signedKey: new TextEncoder().encode('signedKey'), - identityKey: new TextEncoder().encode('identityKey'), - }; - const sessionRequest = new SessionRequestMessage({ - timestamp: Date.now(), - preKeyBundle, - }); - const sessionRequestRaw = toRawMessage(pubkey, sessionRequest); - expect(sessionRequestRaw).to.have.property( - 'encryption', - EncryptionType.SessionRequest - ); - }); - - it('encryption is set to Signal if message is not of instance SessionRequestMessage', () => { - expect(raw).to.have.property('encryption', EncryptionType.Signal); - }); -}); diff --git a/ts/test/session/sending/MessageQueue_test.ts b/ts/test/session/sending/MessageQueue_test.ts index 7c7241f9f..4e1644d27 100644 --- a/ts/test/session/sending/MessageQueue_test.ts +++ b/ts/test/session/sending/MessageQueue_test.ts @@ -83,7 +83,7 @@ describe('MessageQueue', () => { }); describe('processPending', () => { - it('will send session request message if no session', async () => { + it('will send session request if no session and not sending to medium group', async () => { hasSessionStub.resolves(false); isMediumGroupStub.returns(false); @@ -97,48 +97,33 @@ describe('MessageQueue', () => { await expect(stubCallPromise).to.be.fulfilled; }); - it('will send message if session exists', async () => { - hasSessionStub.resolves(true); - isMediumGroupStub.returns(false); - sendStub.resolves(); + it('will not send session request if sending to medium group', async () => { + hasSessionStub.resolves(false); + isMediumGroupStub.returns(true); const device = TestUtils.generateFakePubKey(); - await pendingMessageCache.add(device, TestUtils.generateChatMessage()); - - const successPromise = PromiseUtils.waitForTask(done => { - messageQueueStub.events.once('success', done); - }); - await messageQueueStub.processPending(device); - await expect(successPromise).to.be.fulfilled; - expect(sendSessionRequestIfNeededStub.called).to.equal( - false, - 'Session request triggered when we have a session.' - ); + + expect(sendSessionRequestIfNeededStub.callCount).to.equal(0); }); - it('will send message if sending to medium group', async () => { - isMediumGroupStub.returns(true); - sendStub.resolves(); + it('will send messages', async () => { + for (const hasSession of [true, false]) { + hasSessionStub.resolves(hasSession); - const device = TestUtils.generateFakePubKey(); - await pendingMessageCache.add(device, TestUtils.generateChatMessage()); - - const successPromise = PromiseUtils.waitForTask(done => { - messageQueueStub.events.once('success', done); - }); + const device = TestUtils.generateFakePubKey(); + await pendingMessageCache.add(device, TestUtils.generateChatMessage()); - await messageQueueStub.processPending(device); - await expect(successPromise).to.be.fulfilled; - expect(sendSessionRequestIfNeededStub.called).to.equal( - false, - 'Session request triggered on medium group' - ); + const successPromise = PromiseUtils.waitForTask(done => { + messageQueueStub.events.once('success', done); + }); + await messageQueueStub.processPending(device); + await expect(successPromise).to.be.fulfilled; + } }); it('should remove message from cache', async () => { hasSessionStub.resolves(true); - isMediumGroupStub.returns(false); const events = ['success', 'fail']; for (const event of events) { @@ -166,8 +151,6 @@ describe('MessageQueue', () => { describe('events', () => { it('should send a success event if message was sent', async () => { hasSessionStub.resolves(true); - isMediumGroupStub.returns(false); - sendStub.resolves(); const device = TestUtils.generateFakePubKey(); const message = TestUtils.generateChatMessage(); @@ -188,7 +171,6 @@ describe('MessageQueue', () => { it('should send a fail event if something went wrong while sending', async () => { hasSessionStub.resolves(true); - isMediumGroupStub.returns(false); sendStub.throws(new Error('failure')); const spy = sandbox.spy(); diff --git a/ts/test/session/sending/PendingMessageCache_test.ts b/ts/test/session/sending/PendingMessageCache_test.ts index 5f806b5dc..995d424e3 100644 --- a/ts/test/session/sending/PendingMessageCache_test.ts +++ b/ts/test/session/sending/PendingMessageCache_test.ts @@ -1,8 +1,10 @@ import { expect } from 'chai'; +import * as sinon from 'sinon'; import * as _ from 'lodash'; import { MessageUtils } from '../../../session/utils'; import { TestUtils } from '../../../test/test-utils'; import { PendingMessageCache } from '../../../session/sending/PendingMessageCache'; +import { SessionProtocol } from '../../../session/protocols'; // Equivalent to Data.StorageItem interface StorageItem { @@ -11,6 +13,7 @@ interface StorageItem { } describe('PendingMessageCache', () => { + const sandbox = sinon.createSandbox(); // Initialize new stubbed cache let data: StorageItem; let pendingMessageCacheStub: PendingMessageCache; @@ -36,9 +39,12 @@ describe('PendingMessageCache', () => { }); pendingMessageCacheStub = new PendingMessageCache(); + + sandbox.stub(SessionProtocol, 'hasSession').resolves(true); }); afterEach(() => { + sandbox.restore(); TestUtils.restoreStubs(); }); @@ -53,7 +59,7 @@ describe('PendingMessageCache', () => { it('can add to cache', async () => { const device = TestUtils.generateFakePubKey(); const message = TestUtils.generateChatMessage(); - const rawMessage = MessageUtils.toRawMessage(device, message); + const rawMessage = await MessageUtils.toRawMessage(device, message); await pendingMessageCacheStub.add(device, message); @@ -86,7 +92,7 @@ describe('PendingMessageCache', () => { it('can remove from cache', async () => { const device = TestUtils.generateFakePubKey(); const message = TestUtils.generateChatMessage(); - const rawMessage = MessageUtils.toRawMessage(device, message); + const rawMessage = await MessageUtils.toRawMessage(device, message); await pendingMessageCacheStub.add(device, message); @@ -105,7 +111,7 @@ describe('PendingMessageCache', () => { it('should only remove messages with different timestamp and device', async () => { const device = TestUtils.generateFakePubKey(); const message = TestUtils.generateChatMessage(); - const rawMessage = MessageUtils.toRawMessage(device, message); + const rawMessage = await MessageUtils.toRawMessage(device, message); await pendingMessageCacheStub.add(device, message); await TestUtils.timeout(5); @@ -195,7 +201,7 @@ describe('PendingMessageCache', () => { it('can find nothing when empty', async () => { const device = TestUtils.generateFakePubKey(); const message = TestUtils.generateChatMessage(); - const rawMessage = MessageUtils.toRawMessage(device, message); + const rawMessage = await MessageUtils.toRawMessage(device, message); const foundMessage = pendingMessageCacheStub.find(rawMessage); expect(foundMessage, 'a message was found in empty cache').to.be.undefined; @@ -204,7 +210,7 @@ describe('PendingMessageCache', () => { it('can find message in cache', async () => { const device = TestUtils.generateFakePubKey(); const message = TestUtils.generateChatMessage(); - const rawMessage = MessageUtils.toRawMessage(device, message); + const rawMessage = await MessageUtils.toRawMessage(device, message); await pendingMessageCacheStub.add(device, message); diff --git a/ts/test/session/utils/Messages_test.ts b/ts/test/session/utils/Messages_test.ts index a3775abfb..cd8b052b9 100644 --- a/ts/test/session/utils/Messages_test.ts +++ b/ts/test/session/utils/Messages_test.ts @@ -1,7 +1,14 @@ import chai from 'chai'; +import * as sinon from 'sinon'; +import crypto from 'crypto'; import { TestUtils } from '../../test-utils/'; import { MessageUtils } from '../../../session/utils/'; -import { PubKey } from '../../../session/types/'; +import { EncryptionType, PubKey } from '../../../session/types/'; +import { SessionProtocol } from '../../../session/protocols'; +import { + MediumGroupChatMessage, + SessionRequestMessage, +} from '../../../session/messages/outgoing'; // tslint:disable-next-line: no-require-imports no-var-requires const chaiAsPromised = require('chai-as-promised'); @@ -10,12 +17,26 @@ chai.use(chaiAsPromised); const { expect } = chai; describe('Message Utils', () => { + const sandbox = sinon.createSandbox(); + + afterEach(() => { + sandbox.restore(); + }); + describe('toRawMessage', () => { + let hasSessionStub: sinon.SinonStub<[PubKey], Promise>; + + beforeEach(() => { + hasSessionStub = sandbox + .stub(SessionProtocol, 'hasSession') + .resolves(true); + }); + it('can convert to raw message', async () => { const device = TestUtils.generateFakePubKey(); const message = TestUtils.generateChatMessage(); - const rawMessage = MessageUtils.toRawMessage(device, message); + const rawMessage = await MessageUtils.toRawMessage(device, message); expect(Object.keys(rawMessage)).to.have.length(6); expect(rawMessage.identifier).to.exist; @@ -24,13 +45,21 @@ describe('Message Utils', () => { expect(rawMessage.plainTextBuffer).to.exist; expect(rawMessage.timestamp).to.exist; expect(rawMessage.ttl).to.exist; + + expect(rawMessage.identifier).to.equal(message.identifier); + expect(rawMessage.device).to.equal(device.key); + expect(rawMessage.plainTextBuffer).to.deep.equal( + message.plainTextBuffer() + ); + expect(rawMessage.timestamp).to.equal(message.timestamp); + expect(rawMessage.ttl).to.equal(message.ttl()); }); it('should generate valid plainTextBuffer', async () => { const device = TestUtils.generateFakePubKey(); const message = TestUtils.generateChatMessage(); - const rawMessage = MessageUtils.toRawMessage(device, message); + const rawMessage = await MessageUtils.toRawMessage(device, message); const rawBuffer = rawMessage.plainTextBuffer; const rawBufferJSON = JSON.stringify(rawBuffer); @@ -50,7 +79,7 @@ describe('Message Utils', () => { const device = TestUtils.generateFakePubKey(); const message = TestUtils.generateChatMessage(); - const rawMessage = MessageUtils.toRawMessage(device, message); + const rawMessage = await MessageUtils.toRawMessage(device, message); const derivedPubKey = PubKey.from(rawMessage.device); expect(derivedPubKey).to.exist; @@ -59,5 +88,63 @@ describe('Message Utils', () => { 'pubkey of message was not converted correctly' ); }); + + it('should set encryption to MediumGroup if a MediumGroupMessage is passed in', async () => { + hasSessionStub.resolves(true); + + const device = TestUtils.generateFakePubKey(); + const groupId = TestUtils.generateFakePubKey(); + const chatMessage = TestUtils.generateChatMessage(); + const message = new MediumGroupChatMessage({ chatMessage, groupId }); + + const rawMessage = await MessageUtils.toRawMessage(device, message); + expect(rawMessage.encryption).to.equal(EncryptionType.MediumGroup); + }); + + it('should set encryption to Fallback if a SessionRequestMessage is passed in', async () => { + hasSessionStub.resolves(true); + + const device = TestUtils.generateFakePubKey(); + const preKeyBundle = { + deviceId: 123456, + preKeyId: 654321, + signedKeyId: 111111, + preKey: crypto.randomBytes(16), + signature: crypto.randomBytes(16), + signedKey: crypto.randomBytes(16), + identityKey: crypto.randomBytes(16), + }; + const sessionRequest = new SessionRequestMessage({ + timestamp: Date.now(), + preKeyBundle, + }); + + const rawMessage = await MessageUtils.toRawMessage( + device, + sessionRequest + ); + + expect(rawMessage.encryption).to.equal(EncryptionType.Fallback); + }); + + it('should set encryption to Fallback on other messages if we do not have a session', async () => { + hasSessionStub.resolves(false); + + const device = TestUtils.generateFakePubKey(); + const message = TestUtils.generateChatMessage(); + const rawMessage = await MessageUtils.toRawMessage(device, message); + + expect(rawMessage.encryption).to.equal(EncryptionType.Fallback); + }); + + it('should set encryption to Signal on other messages if we have a session', async () => { + hasSessionStub.resolves(true); + + const device = TestUtils.generateFakePubKey(); + const message = TestUtils.generateChatMessage(); + const rawMessage = await MessageUtils.toRawMessage(device, message); + + expect(rawMessage.encryption).to.equal(EncryptionType.Signal); + }); }); }); diff --git a/ts/test/test-utils/stubs/ciphers/FallBackSessionCipherStub.ts b/ts/test/test-utils/stubs/ciphers/FallBackSessionCipherStub.ts index d53b258d6..acff841fe 100644 --- a/ts/test/test-utils/stubs/ciphers/FallBackSessionCipherStub.ts +++ b/ts/test/test-utils/stubs/ciphers/FallBackSessionCipherStub.ts @@ -5,7 +5,7 @@ import { StringUtils } from '../../../../session/utils'; export class FallBackSessionCipherStub { public async encrypt(buffer: ArrayBuffer): Promise { return { - type: SignalService.Envelope.Type.SESSION_REQUEST, + type: SignalService.Envelope.Type.FALLBACK_MESSAGE, body: StringUtils.decode(buffer, 'binary'), }; }