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.
pull/1233/head
Mikunj 5 years ago
parent 4381d0135f
commit 646973e330

@ -50,7 +50,7 @@ describe('Message Syncing', function() {
// Linking Alice2 to Alice1 // Linking Alice2 to Alice1
// alice2 should trigger auto FR with bob1 as it's one of her friend // 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.linkApp2ToApp(Alice1, Alice2, common.TEST_PUBKEY1);
await common.timeout(25000); await common.timeout(25000);
@ -119,7 +119,7 @@ describe('Message Syncing', function() {
// once autoFR is auto-accepted, alice2 trigger contact sync // once autoFR is auto-accepted, alice2 trigger contact sync
await common.logsContains( await common.logsContains(
bob1Logs, bob1Logs,
`Received SESSION_REQUEST from source: ${alice2Pubkey}`, `Received FALLBACK_MESSAGE from source: ${alice2Pubkey}`,
1 1
); );
await common.logsContains( await common.logsContains(

@ -8,7 +8,7 @@
const DebugFlagsEnum = { const DebugFlagsEnum = {
GROUP_SYNC_MESSAGES: 1, GROUP_SYNC_MESSAGES: 1,
CONTACT_SYNC_MESSAGES: 2, CONTACT_SYNC_MESSAGES: 2,
SESSION_REQUEST_MESSAGES: 8, FALLBACK_MESSAGES: 8,
SESSION_MESSAGE_SENDING: 16, SESSION_MESSAGE_SENDING: 16,
SESSION_BACKGROUND_MESSAGE: 32, SESSION_BACKGROUND_MESSAGE: 32,
GROUP_REQUEST_INFO: 64, GROUP_REQUEST_INFO: 64,

@ -153,7 +153,7 @@
ivAndCiphertext ivAndCiphertext
).toString('binary'); ).toString('binary');
return { return {
type: textsecure.protobuf.Envelope.Type.SESSION_REQUEST, type: textsecure.protobuf.Envelope.Type.FALLBACK_MESSAGE,
body: binaryIvAndCiphertext, body: binaryIvAndCiphertext,
registrationId: undefined, registrationId: undefined,
}; };

@ -19,12 +19,12 @@ describe('Crypto', () => {
fallbackCipher = new libloki.crypto.FallBackSessionCipher(address); 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 buffer = new ArrayBuffer(10);
const { type } = await fallbackCipher.encrypt(buffer); const { type } = await fallbackCipher.encrypt(buffer);
assert.strictEqual( assert.strictEqual(
type, type,
textsecure.protobuf.Envelope.Type.SESSION_REQUEST textsecure.protobuf.Envelope.Type.FALLBACK_MESSAGE
); );
}); });

@ -325,8 +325,7 @@ OutgoingMessage.prototype = {
// END_SESSION means Session reset message // END_SESSION means Session reset message
const isEndSession = const isEndSession =
flags === textsecure.protobuf.DataMessage.Flags.END_SESSION; flags === textsecure.protobuf.DataMessage.Flags.END_SESSION;
const isSessionRequest = const isSessionRequest = false;
flags === textsecure.protobuf.DataMessage.Flags.SESSION_REQUEST;
if (enableFallBackEncryption || isEndSession) { if (enableFallBackEncryption || isEndSession) {
// Encrypt them with the fallback // Encrypt them with the fallback

@ -13,7 +13,7 @@ message Envelope {
RECEIPT = 5; RECEIPT = 5;
UNIDENTIFIED_SENDER = 6; UNIDENTIFIED_SENDER = 6;
MEDIUM_GROUP_CIPHERTEXT = 7; 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; optional Type type = 1;

@ -183,9 +183,9 @@ async function decryptUnidentifiedSender(
} }
// We might have substituted the type based on decrypted content // 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 // 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()); const blocked = await isBlocked(sender.getName());
@ -227,8 +227,8 @@ async function doDecrypt(
return lokiSessionCipher.decryptWhisperMessage(ciphertext).then(unpad); return lokiSessionCipher.decryptWhisperMessage(ciphertext).then(unpad);
case SignalService.Envelope.Type.MEDIUM_GROUP_CIPHERTEXT: case SignalService.Envelope.Type.MEDIUM_GROUP_CIPHERTEXT:
return decryptForMediumGroup(envelope, ciphertext); return decryptForMediumGroup(envelope, ciphertext);
case SignalService.Envelope.Type.SESSION_REQUEST: { case SignalService.Envelope.Type.FALLBACK_MESSAGE: {
window.log.info('session-request message from ', envelope.source); window.log.info('fallback message from ', envelope.source);
const fallBackSessionCipher = new libloki.crypto.FallBackSessionCipher( const fallBackSessionCipher = new libloki.crypto.FallBackSessionCipher(
address address
@ -344,13 +344,13 @@ export async function innerHandleContentMessage(
const content = SignalService.Content.decode(new Uint8Array(plaintext)); 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'); await ConversationController.getOrCreateAndWait(envelope.source, 'private');
if (envelope.type === SESSION_REQUEST) { if (content.preKeyBundleMessage) {
await handleSessionRequestMessage(envelope, content); await handleSessionRequestMessage(envelope, content.preKeyBundleMessage);
} else { } else if (envelope.type !== FALLBACK_MESSAGE) {
const device = new PubKey(envelope.source); const device = new PubKey(envelope.source);
await SessionProtocol.onSessionEstablished(device); await SessionProtocol.onSessionEstablished(device);

@ -25,10 +25,9 @@ export async function handleEndSession(number: string): Promise<void> {
export async function handleSessionRequestMessage( export async function handleSessionRequestMessage(
envelope: EnvelopePlus, envelope: EnvelopePlus,
content: SignalService.Content preKeyBundleMessage: SignalService.IPreKeyBundleMessage
) { ) {
const { libsignal, libloki, StringView, textsecure, dcodeIO, log } = window; const { libsignal, libloki, StringView, textsecure, dcodeIO, log } = window;
const { preKeyBundleMessage } = content;
window.console.log( window.console.log(
`Received SESSION_REQUEST from source: ${envelope.source}` `Received SESSION_REQUEST from source: ${envelope.source}`

@ -53,7 +53,7 @@ export async function encrypt(
} }
let innerCipherText: CipherTextObject; let innerCipherText: CipherTextObject;
if (encryptionType === EncryptionType.SessionRequest) { if (encryptionType === EncryptionType.Fallback) {
const cipher = new window.libloki.crypto.FallBackSessionCipher(address); const cipher = new window.libloki.crypto.FallBackSessionCipher(address);
innerCipherText = await cipher.encrypt(plainText.buffer); innerCipherText = await cipher.encrypt(plainText.buffer);
} else { } else {

@ -144,7 +144,7 @@ export class SessionProtocol {
SessionProtocol.pendingSendSessionsTimestamp.add(pubkey.key); SessionProtocol.pendingSendSessionsTimestamp.add(pubkey.key);
try { try {
const rawMessage = MessageUtils.toRawMessage(pubkey, message); const rawMessage = await MessageUtils.toRawMessage(pubkey, message);
await MessageSender.send(rawMessage); await MessageSender.send(rawMessage);
await SessionProtocol.updateSentSessionTimestamp(pubkey.key, timestamp); await SessionProtocol.updateSentSessionTimestamp(pubkey.key, timestamp);
} catch (e) { } catch (e) {

@ -130,10 +130,9 @@ export class MessageQueue implements MessageQueueInterface {
const isMediumGroup = GroupUtils.isMediumGroup(device); const isMediumGroup = GroupUtils.isMediumGroup(device);
const hasSession = await SessionProtocol.hasSession(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) { if (!isMediumGroup && !hasSession) {
await SessionProtocol.sendSessionRequestIfNeeded(device); await SessionProtocol.sendSessionRequestIfNeeded(device);
return;
} }
const jobQueue = this.getJobQueue(device); const jobQueue = this.getJobQueue(device);

@ -41,7 +41,7 @@ export class PendingMessageCache {
message: ContentMessage message: ContentMessage
): Promise<RawMessage> { ): Promise<RawMessage> {
await this.loadFromDBIfNeeded(); await this.loadFromDBIfNeeded();
const rawMessage = MessageUtils.toRawMessage(device, message); const rawMessage = await MessageUtils.toRawMessage(device, message);
// Does it exist in cache already? // Does it exist in cache already?
if (this.find(rawMessage)) { if (this.find(rawMessage)) {

@ -1,5 +1,5 @@
export enum EncryptionType { export enum EncryptionType {
Signal, Signal,
SessionRequest, Fallback,
MediumGroup, MediumGroup,
} }

@ -1,18 +1,30 @@
import { RawMessage } from '../types/RawMessage'; import { RawMessage } from '../types/RawMessage';
import { ContentMessage, SessionRequestMessage } from '../messages/outgoing'; import {
ContentMessage,
MediumGroupMessage,
SessionRequestMessage,
} from '../messages/outgoing';
import { EncryptionType, PubKey } from '../types'; import { EncryptionType, PubKey } from '../types';
import { SessionProtocol } from '../protocols';
export function toRawMessage( export async function toRawMessage(
device: PubKey, device: PubKey,
message: ContentMessage message: ContentMessage
): RawMessage { ): Promise<RawMessage> {
const timestamp = message.timestamp; const timestamp = message.timestamp;
const ttl = message.ttl(); const ttl = message.ttl();
const plainTextBuffer = message.plainTextBuffer(); const plainTextBuffer = message.plainTextBuffer();
const encryption =
message instanceof SessionRequestMessage let encryption: EncryptionType;
? EncryptionType.SessionRequest if (message instanceof MediumGroupMessage) {
: EncryptionType.Signal; 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 // tslint:disable-next-line: no-unnecessary-local-variable
const rawMessage: RawMessage = { const rawMessage: RawMessage = {

@ -66,11 +66,7 @@ describe('MessageEncrypter', () => {
Stubs.FallBackSessionCipherStub.prototype, Stubs.FallBackSessionCipherStub.prototype,
'encrypt' 'encrypt'
); );
await MessageEncrypter.encrypt( await MessageEncrypter.encrypt('1', data, EncryptionType.Fallback);
'1',
data,
EncryptionType.SessionRequest
);
expect(spy.called).to.equal( expect(spy.called).to.equal(
true, true,
'FallbackSessionCipher.encrypt should be called.' 'FallbackSessionCipher.encrypt should be called.'
@ -83,11 +79,7 @@ describe('MessageEncrypter', () => {
Stubs.FallBackSessionCipherStub.prototype, Stubs.FallBackSessionCipherStub.prototype,
'encrypt' 'encrypt'
); );
await MessageEncrypter.encrypt( await MessageEncrypter.encrypt('1', data, EncryptionType.Fallback);
'1',
data,
EncryptionType.SessionRequest
);
const paddedData = MessageEncrypter.padPlainTextBuffer(data); const paddedData = MessageEncrypter.padPlainTextBuffer(data);
const firstArgument = new Uint8Array(spy.args[0][0]); const firstArgument = new Uint8Array(spy.args[0][0]);
@ -99,7 +91,7 @@ describe('MessageEncrypter', () => {
const result = await MessageEncrypter.encrypt( const result = await MessageEncrypter.encrypt(
'1', '1',
data, data,
EncryptionType.SessionRequest EncryptionType.Fallback
); );
expect(result.envelopeType).to.deep.equal( expect(result.envelopeType).to.deep.equal(
SignalService.Envelope.Type.UNIDENTIFIED_SENDER SignalService.Envelope.Type.UNIDENTIFIED_SENDER
@ -144,7 +136,7 @@ describe('MessageEncrypter', () => {
describe('Sealed Sender', () => { describe('Sealed Sender', () => {
it('should pass the correct values to SecretSessionCipher encrypt', async () => { 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) { for (const type of types) {
const spy = sandbox.spy( const spy = sandbox.spy(
Stubs.SecretSessionCipherStub.prototype, Stubs.SecretSessionCipherStub.prototype,

@ -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);
});
});

@ -83,7 +83,7 @@ describe('MessageQueue', () => {
}); });
describe('processPending', () => { 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); hasSessionStub.resolves(false);
isMediumGroupStub.returns(false); isMediumGroupStub.returns(false);
@ -97,48 +97,33 @@ describe('MessageQueue', () => {
await expect(stubCallPromise).to.be.fulfilled; await expect(stubCallPromise).to.be.fulfilled;
}); });
it('will send message if session exists', async () => { it('will not send session request if sending to medium group', async () => {
hasSessionStub.resolves(true); hasSessionStub.resolves(false);
isMediumGroupStub.returns(false); isMediumGroupStub.returns(true);
sendStub.resolves();
const device = TestUtils.generateFakePubKey(); 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 messageQueueStub.processPending(device);
await expect(successPromise).to.be.fulfilled;
expect(sendSessionRequestIfNeededStub.called).to.equal( expect(sendSessionRequestIfNeededStub.callCount).to.equal(0);
false,
'Session request triggered when we have a session.'
);
}); });
it('will send message if sending to medium group', async () => { it('will send messages', async () => {
isMediumGroupStub.returns(true); for (const hasSession of [true, false]) {
sendStub.resolves(); hasSessionStub.resolves(hasSession);
const device = TestUtils.generateFakePubKey(); const device = TestUtils.generateFakePubKey();
await pendingMessageCache.add(device, TestUtils.generateChatMessage()); await pendingMessageCache.add(device, TestUtils.generateChatMessage());
const successPromise = PromiseUtils.waitForTask(done => {
messageQueueStub.events.once('success', done);
});
await messageQueueStub.processPending(device); const successPromise = PromiseUtils.waitForTask(done => {
await expect(successPromise).to.be.fulfilled; messageQueueStub.events.once('success', done);
expect(sendSessionRequestIfNeededStub.called).to.equal( });
false, await messageQueueStub.processPending(device);
'Session request triggered on medium group' await expect(successPromise).to.be.fulfilled;
); }
}); });
it('should remove message from cache', async () => { it('should remove message from cache', async () => {
hasSessionStub.resolves(true); hasSessionStub.resolves(true);
isMediumGroupStub.returns(false);
const events = ['success', 'fail']; const events = ['success', 'fail'];
for (const event of events) { for (const event of events) {
@ -166,8 +151,6 @@ describe('MessageQueue', () => {
describe('events', () => { describe('events', () => {
it('should send a success event if message was sent', async () => { it('should send a success event if message was sent', async () => {
hasSessionStub.resolves(true); hasSessionStub.resolves(true);
isMediumGroupStub.returns(false);
sendStub.resolves();
const device = TestUtils.generateFakePubKey(); const device = TestUtils.generateFakePubKey();
const message = TestUtils.generateChatMessage(); const message = TestUtils.generateChatMessage();
@ -188,7 +171,6 @@ describe('MessageQueue', () => {
it('should send a fail event if something went wrong while sending', async () => { it('should send a fail event if something went wrong while sending', async () => {
hasSessionStub.resolves(true); hasSessionStub.resolves(true);
isMediumGroupStub.returns(false);
sendStub.throws(new Error('failure')); sendStub.throws(new Error('failure'));
const spy = sandbox.spy(); const spy = sandbox.spy();

@ -1,8 +1,10 @@
import { expect } from 'chai'; import { expect } from 'chai';
import * as sinon from 'sinon';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { MessageUtils } from '../../../session/utils'; import { MessageUtils } from '../../../session/utils';
import { TestUtils } from '../../../test/test-utils'; import { TestUtils } from '../../../test/test-utils';
import { PendingMessageCache } from '../../../session/sending/PendingMessageCache'; import { PendingMessageCache } from '../../../session/sending/PendingMessageCache';
import { SessionProtocol } from '../../../session/protocols';
// Equivalent to Data.StorageItem // Equivalent to Data.StorageItem
interface StorageItem { interface StorageItem {
@ -11,6 +13,7 @@ interface StorageItem {
} }
describe('PendingMessageCache', () => { describe('PendingMessageCache', () => {
const sandbox = sinon.createSandbox();
// Initialize new stubbed cache // Initialize new stubbed cache
let data: StorageItem; let data: StorageItem;
let pendingMessageCacheStub: PendingMessageCache; let pendingMessageCacheStub: PendingMessageCache;
@ -36,9 +39,12 @@ describe('PendingMessageCache', () => {
}); });
pendingMessageCacheStub = new PendingMessageCache(); pendingMessageCacheStub = new PendingMessageCache();
sandbox.stub(SessionProtocol, 'hasSession').resolves(true);
}); });
afterEach(() => { afterEach(() => {
sandbox.restore();
TestUtils.restoreStubs(); TestUtils.restoreStubs();
}); });
@ -53,7 +59,7 @@ describe('PendingMessageCache', () => {
it('can add to cache', async () => { it('can add to cache', async () => {
const device = TestUtils.generateFakePubKey(); const device = TestUtils.generateFakePubKey();
const message = TestUtils.generateChatMessage(); const message = TestUtils.generateChatMessage();
const rawMessage = MessageUtils.toRawMessage(device, message); const rawMessage = await MessageUtils.toRawMessage(device, message);
await pendingMessageCacheStub.add(device, message); await pendingMessageCacheStub.add(device, message);
@ -86,7 +92,7 @@ describe('PendingMessageCache', () => {
it('can remove from cache', async () => { it('can remove from cache', async () => {
const device = TestUtils.generateFakePubKey(); const device = TestUtils.generateFakePubKey();
const message = TestUtils.generateChatMessage(); const message = TestUtils.generateChatMessage();
const rawMessage = MessageUtils.toRawMessage(device, message); const rawMessage = await MessageUtils.toRawMessage(device, message);
await pendingMessageCacheStub.add(device, message); await pendingMessageCacheStub.add(device, message);
@ -105,7 +111,7 @@ describe('PendingMessageCache', () => {
it('should only remove messages with different timestamp and device', async () => { it('should only remove messages with different timestamp and device', async () => {
const device = TestUtils.generateFakePubKey(); const device = TestUtils.generateFakePubKey();
const message = TestUtils.generateChatMessage(); const message = TestUtils.generateChatMessage();
const rawMessage = MessageUtils.toRawMessage(device, message); const rawMessage = await MessageUtils.toRawMessage(device, message);
await pendingMessageCacheStub.add(device, message); await pendingMessageCacheStub.add(device, message);
await TestUtils.timeout(5); await TestUtils.timeout(5);
@ -195,7 +201,7 @@ describe('PendingMessageCache', () => {
it('can find nothing when empty', async () => { it('can find nothing when empty', async () => {
const device = TestUtils.generateFakePubKey(); const device = TestUtils.generateFakePubKey();
const message = TestUtils.generateChatMessage(); const message = TestUtils.generateChatMessage();
const rawMessage = MessageUtils.toRawMessage(device, message); const rawMessage = await MessageUtils.toRawMessage(device, message);
const foundMessage = pendingMessageCacheStub.find(rawMessage); const foundMessage = pendingMessageCacheStub.find(rawMessage);
expect(foundMessage, 'a message was found in empty cache').to.be.undefined; 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 () => { it('can find message in cache', async () => {
const device = TestUtils.generateFakePubKey(); const device = TestUtils.generateFakePubKey();
const message = TestUtils.generateChatMessage(); const message = TestUtils.generateChatMessage();
const rawMessage = MessageUtils.toRawMessage(device, message); const rawMessage = await MessageUtils.toRawMessage(device, message);
await pendingMessageCacheStub.add(device, message); await pendingMessageCacheStub.add(device, message);

@ -1,7 +1,14 @@
import chai from 'chai'; import chai from 'chai';
import * as sinon from 'sinon';
import crypto from 'crypto';
import { TestUtils } from '../../test-utils/'; import { TestUtils } from '../../test-utils/';
import { MessageUtils } from '../../../session/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 // tslint:disable-next-line: no-require-imports no-var-requires
const chaiAsPromised = require('chai-as-promised'); const chaiAsPromised = require('chai-as-promised');
@ -10,12 +17,26 @@ chai.use(chaiAsPromised);
const { expect } = chai; const { expect } = chai;
describe('Message Utils', () => { describe('Message Utils', () => {
const sandbox = sinon.createSandbox();
afterEach(() => {
sandbox.restore();
});
describe('toRawMessage', () => { describe('toRawMessage', () => {
let hasSessionStub: sinon.SinonStub<[PubKey], Promise<boolean>>;
beforeEach(() => {
hasSessionStub = sandbox
.stub(SessionProtocol, 'hasSession')
.resolves(true);
});
it('can convert to raw message', async () => { it('can convert to raw message', async () => {
const device = TestUtils.generateFakePubKey(); const device = TestUtils.generateFakePubKey();
const message = TestUtils.generateChatMessage(); 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(Object.keys(rawMessage)).to.have.length(6);
expect(rawMessage.identifier).to.exist; expect(rawMessage.identifier).to.exist;
@ -24,13 +45,21 @@ describe('Message Utils', () => {
expect(rawMessage.plainTextBuffer).to.exist; expect(rawMessage.plainTextBuffer).to.exist;
expect(rawMessage.timestamp).to.exist; expect(rawMessage.timestamp).to.exist;
expect(rawMessage.ttl).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 () => { it('should generate valid plainTextBuffer', async () => {
const device = TestUtils.generateFakePubKey(); const device = TestUtils.generateFakePubKey();
const message = TestUtils.generateChatMessage(); const message = TestUtils.generateChatMessage();
const rawMessage = MessageUtils.toRawMessage(device, message); const rawMessage = await MessageUtils.toRawMessage(device, message);
const rawBuffer = rawMessage.plainTextBuffer; const rawBuffer = rawMessage.plainTextBuffer;
const rawBufferJSON = JSON.stringify(rawBuffer); const rawBufferJSON = JSON.stringify(rawBuffer);
@ -50,7 +79,7 @@ describe('Message Utils', () => {
const device = TestUtils.generateFakePubKey(); const device = TestUtils.generateFakePubKey();
const message = TestUtils.generateChatMessage(); const message = TestUtils.generateChatMessage();
const rawMessage = MessageUtils.toRawMessage(device, message); const rawMessage = await MessageUtils.toRawMessage(device, message);
const derivedPubKey = PubKey.from(rawMessage.device); const derivedPubKey = PubKey.from(rawMessage.device);
expect(derivedPubKey).to.exist; expect(derivedPubKey).to.exist;
@ -59,5 +88,63 @@ describe('Message Utils', () => {
'pubkey of message was not converted correctly' '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);
});
}); });
}); });

@ -5,7 +5,7 @@ import { StringUtils } from '../../../../session/utils';
export class FallBackSessionCipherStub { export class FallBackSessionCipherStub {
public async encrypt(buffer: ArrayBuffer): Promise<CipherTextObject> { public async encrypt(buffer: ArrayBuffer): Promise<CipherTextObject> {
return { return {
type: SignalService.Envelope.Type.SESSION_REQUEST, type: SignalService.Envelope.Type.FALLBACK_MESSAGE,
body: StringUtils.decode(buffer, 'binary'), body: StringUtils.decode(buffer, 'binary'),
}; };
} }

Loading…
Cancel
Save