fix SessionProtocol decrypt of messages with Android
also disable the old SessionRequest logic
This commit is contained in:
parent
b0a229bf13
commit
2af4938ff2
|
@ -1616,27 +1616,27 @@
|
|||
this.get('sessionResetStatus') !== SessionResetEnum.request_received
|
||||
) {
|
||||
await this.onSessionResetInitiated();
|
||||
const message = await this.createAndStoreEndSessionMessage({
|
||||
type: 'outgoing',
|
||||
endSessionType: 'ongoing',
|
||||
});
|
||||
window.log.info('resetting secure session');
|
||||
const device = new libsession.Types.PubKey(this.id);
|
||||
const preKeyBundle = await window.libloki.storage.getPreKeyBundleForContact(
|
||||
device.key
|
||||
);
|
||||
const endSessionMessage = new libsession.Messages.Outgoing.EndSessionMessage(
|
||||
{
|
||||
timestamp: message.get('sent_at'),
|
||||
preKeyBundle,
|
||||
}
|
||||
);
|
||||
// const message = await this.createAndStoreEndSessionMessage({
|
||||
// type: 'outgoing',
|
||||
// endSessionType: 'ongoing',
|
||||
// });
|
||||
// window.log.info('resetting secure session');
|
||||
// const device = new libsession.Types.PubKey(this.id);
|
||||
// const preKeyBundle = await window.libloki.storage.getPreKeyBundleForContact(
|
||||
// device.key
|
||||
// );
|
||||
// // const endSessionMessage = new libsession.Messages.Outgoing.EndSessionMessage(
|
||||
// // {
|
||||
// // timestamp: message.get('sent_at'),
|
||||
// // preKeyBundle,
|
||||
// // }
|
||||
// // );
|
||||
|
||||
await libsession.getMessageQueue().send(device, endSessionMessage);
|
||||
// TODO handle errors to reset session reset status with the new pipeline
|
||||
if (message.hasErrors()) {
|
||||
await this.setSessionResetStatus(SessionResetEnum.none);
|
||||
}
|
||||
// // await libsession.getMessageQueue().send(device, endSessionMessage);
|
||||
// // // TODO handle errors to reset session reset status with the new pipeline
|
||||
// // if (message.hasErrors()) {
|
||||
// // await this.setSessionResetStatus(SessionResetEnum.none);
|
||||
// // }
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -6,7 +6,6 @@ import { removeFromCache, updateCache } from './cache';
|
|||
import { SignalService } from '../protobuf';
|
||||
import * as Lodash from 'lodash';
|
||||
import * as libsession from '../session';
|
||||
import { handleSessionRequestMessage } from './sessionHandling';
|
||||
import { handlePairingAuthorisationMessage } from './multidevice';
|
||||
import { MediumGroupRequestKeysMessage } from '../session/messages/outgoing';
|
||||
import { MultiDeviceProtocol, SessionProtocol } from '../session/protocols';
|
||||
|
@ -106,7 +105,11 @@ async function decryptForMediumGroup(
|
|||
ciphertext
|
||||
);
|
||||
return retSessionProtocol;
|
||||
} catch {
|
||||
} catch (e) {
|
||||
window.log.warn(
|
||||
'decryptWithSessionProtocol for medium group message throw:',
|
||||
e
|
||||
);
|
||||
const retSSK = await decryptWithSharedSenderKeys(envelope, ciphertext);
|
||||
return retSSK;
|
||||
}
|
||||
|
@ -155,8 +158,11 @@ async function decryptWithSessionProtocol(
|
|||
recipientX25519PrivateKey = await libsession.MediumGroup.getGroupSecretKey(
|
||||
hexEncodedGroupPublicKey
|
||||
); // throws if not found
|
||||
const groupPubKeyHexWithoutPrefix = PubKey.remove05PrefixIfNeeded(
|
||||
hexEncodedGroupPublicKey
|
||||
);
|
||||
recipientX25519PublicKey = new Uint8Array(
|
||||
fromHex(hexEncodedGroupPublicKey)
|
||||
fromHex(groupPubKeyHexWithoutPrefix)
|
||||
);
|
||||
|
||||
break;
|
||||
|
@ -175,7 +181,10 @@ async function decryptWithSessionProtocol(
|
|||
recipientX25519PublicKey,
|
||||
new Uint8Array(recipientX25519PrivateKey)
|
||||
);
|
||||
if (plaintextWithMetadata.byteLength > signatureSize + ed25519PublicKeySize) {
|
||||
if (
|
||||
plaintextWithMetadata.byteLength <=
|
||||
signatureSize + ed25519PublicKeySize
|
||||
) {
|
||||
throw new Error('Decryption failed.'); // throw Error.decryptionFailed;
|
||||
}
|
||||
|
||||
|
@ -219,6 +228,8 @@ async function decryptWithSessionProtocol(
|
|||
// set the sender identity on the envelope itself.
|
||||
if (isMediumGroup) {
|
||||
envelope.senderIdentity = `05${toHex(senderX25519PublicKey)}`;
|
||||
} else {
|
||||
envelope.source = `05${toHex(senderX25519PublicKey)}`;
|
||||
}
|
||||
return unpad(plaintext);
|
||||
}
|
||||
|
@ -363,7 +374,11 @@ async function decryptUnidentifiedSender(
|
|||
ciphertext
|
||||
);
|
||||
return retSessionProtocol;
|
||||
} catch {
|
||||
} catch (e) {
|
||||
window.log.warn(
|
||||
'decryptWithSessionProtocol for unidentified message throw:',
|
||||
e
|
||||
);
|
||||
const retSignalProtocol = await decryptWithSignalProtocol(
|
||||
envelope,
|
||||
ciphertext
|
||||
|
@ -585,9 +600,7 @@ export async function innerHandleContentMessage(
|
|||
|
||||
await ConversationController.getOrCreateAndWait(envelope.source, 'private');
|
||||
|
||||
if (content.preKeyBundleMessage) {
|
||||
await handleSessionRequestMessage(envelope, content.preKeyBundleMessage);
|
||||
} else if (envelope.type !== FALLBACK_MESSAGE) {
|
||||
if (envelope.type !== FALLBACK_MESSAGE) {
|
||||
const device = new PubKey(envelope.source);
|
||||
|
||||
await SessionProtocol.onSessionEstablished(device);
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
import { EnvelopePlus } from './types';
|
||||
import { SignalService } from '../protobuf';
|
||||
import * as libsession from './../session';
|
||||
import { toNumber } from 'lodash';
|
||||
import { PubKey } from '../session/types';
|
||||
import { SessionEstablishedMessage } from '../session/messages/outgoing';
|
||||
import { SessionProtocol } from '../session/protocols';
|
||||
|
||||
export async function handleEndSession(number: string): Promise<void> {
|
||||
window.log.info('got end session');
|
||||
|
||||
|
@ -24,97 +16,3 @@ export async function handleEndSession(number: string): Promise<void> {
|
|||
window.log.error('Error getting conversation: ', number);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleSessionRequestMessage(
|
||||
envelope: EnvelopePlus,
|
||||
preKeyBundleMessage: SignalService.IPreKeyBundleMessage
|
||||
) {
|
||||
const { libsignal, StringView, textsecure, dcodeIO, log } = window;
|
||||
|
||||
window.log.info(`Received SESSION_REQUEST from source: ${envelope.source}`);
|
||||
|
||||
if (!preKeyBundleMessage) {
|
||||
log.warn('No pre-key bundle found in a session request');
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldProcessSessionRequest = await SessionProtocol.shouldProcessSessionRequest(
|
||||
new PubKey(envelope.source),
|
||||
toNumber(envelope.timestamp)
|
||||
);
|
||||
|
||||
if (!shouldProcessSessionRequest) {
|
||||
log.debug('Ignoring a session request message');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// device id are always 1 with Session
|
||||
const deviceId = 1;
|
||||
const pubkey = envelope.source;
|
||||
const address = new libsignal.SignalProtocolAddress(
|
||||
envelope.source,
|
||||
deviceId
|
||||
);
|
||||
// we process the new prekeys and initiate a new session.
|
||||
// The old sessions will get deleted once the correspondant
|
||||
// has switch to the new session.
|
||||
const [identityKey, preKey, signedKey, signature] = [
|
||||
preKeyBundleMessage.identityKey,
|
||||
preKeyBundleMessage.preKey,
|
||||
preKeyBundleMessage.signedKey,
|
||||
preKeyBundleMessage.signature,
|
||||
].map(k => dcodeIO.ByteBuffer.wrap(k).toArrayBuffer());
|
||||
const { preKeyId, signedKeyId } = preKeyBundleMessage;
|
||||
|
||||
if (pubkey !== StringView.arrayBufferToHex(identityKey)) {
|
||||
throw new Error(
|
||||
'Error in savePreKeyBundleMessage: envelope pubkey does not match pubkey in prekey bundle'
|
||||
);
|
||||
}
|
||||
if (preKey === undefined || signedKey === undefined) {
|
||||
window.log.warn(
|
||||
"Couldn't process prekey bundle without preKey or signedKey"
|
||||
);
|
||||
return;
|
||||
}
|
||||
const signedPreKey = {
|
||||
keyId: signedKeyId,
|
||||
publicKey: signedKey,
|
||||
signature,
|
||||
};
|
||||
|
||||
const preKeyObject = {
|
||||
publicKey: preKey,
|
||||
keyId: preKeyId,
|
||||
};
|
||||
|
||||
const device = {
|
||||
identityKey,
|
||||
deviceId,
|
||||
preKey: preKeyObject,
|
||||
signedPreKey,
|
||||
registrationId: 0,
|
||||
};
|
||||
const builder = new libsignal.SessionBuilder(
|
||||
textsecure.storage.protocol,
|
||||
address
|
||||
);
|
||||
await builder.processPreKey(device);
|
||||
|
||||
await SessionProtocol.onSessionRequestProcessed(
|
||||
new PubKey(envelope.source)
|
||||
);
|
||||
log.debug('sending session established to', envelope.source);
|
||||
// We don't need to await the call below because we just want to send it off
|
||||
|
||||
const user = new PubKey(envelope.source);
|
||||
|
||||
const sessionEstablished = new SessionEstablishedMessage({
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
await libsession.getMessageQueue().send(user, sessionEstablished);
|
||||
} catch (e) {
|
||||
log.warn('Failed to process session request', e);
|
||||
// TODO how to handle a failed session request?
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,6 +97,7 @@ export class SessionProtocol {
|
|||
}
|
||||
|
||||
/**
|
||||
* This is disabled until we remove it completely once we removed
|
||||
* Triggers a SessionRequestMessage to be sent if:
|
||||
* - we do not already have a session and
|
||||
* - we did not sent a session request already to that device and
|
||||
|
@ -105,27 +106,24 @@ export class SessionProtocol {
|
|||
public static async sendSessionRequestIfNeeded(
|
||||
pubkey: PubKey
|
||||
): Promise<void> {
|
||||
if (
|
||||
(await SessionProtocol.hasSession(pubkey)) ||
|
||||
(await SessionProtocol.hasSentSessionRequest(pubkey))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const preKeyBundle = await window.libloki.storage.getPreKeyBundleForContact(
|
||||
pubkey.key
|
||||
);
|
||||
|
||||
const sessionReset = new SessionRequestMessage({
|
||||
preKeyBundle,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
try {
|
||||
await SessionProtocol.sendSessionRequest(sessionReset, pubkey);
|
||||
} catch (error) {
|
||||
console.warn('Failed to send session request to:', pubkey.key, error);
|
||||
}
|
||||
// if (
|
||||
// (await SessionProtocol.hasSession(pubkey)) ||
|
||||
// (await SessionProtocol.hasSentSessionRequest(pubkey))
|
||||
// ) {
|
||||
// return;
|
||||
// }
|
||||
// const preKeyBundle = await window.libloki.storage.getPreKeyBundleForContact(
|
||||
// pubkey.key
|
||||
// );
|
||||
// const sessionReset = new SessionRequestMessage({
|
||||
// preKeyBundle,
|
||||
// timestamp: Date.now(),
|
||||
// });
|
||||
// try {
|
||||
// await SessionProtocol.sendSessionRequest(sessionReset, pubkey);
|
||||
// } catch (error) {
|
||||
// console.warn('Failed to send session request to:', pubkey.key, error);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -136,22 +134,20 @@ export class SessionProtocol {
|
|||
message: SessionRequestMessage,
|
||||
pubkey: PubKey
|
||||
): Promise<void> {
|
||||
const timestamp = Date.now();
|
||||
|
||||
// mark the session as being pending send with current timestamp
|
||||
// so we know we already triggered a new session with that device
|
||||
// so sendSessionRequestIfNeeded does not sent another session request
|
||||
SessionProtocol.pendingSendSessionsTimestamp.add(pubkey.key);
|
||||
|
||||
try {
|
||||
const rawMessage = await MessageUtils.toRawMessage(pubkey, message);
|
||||
await MessageSender.send(rawMessage);
|
||||
await SessionProtocol.updateSentSessionTimestamp(pubkey.key, timestamp);
|
||||
} catch (e) {
|
||||
throw e;
|
||||
} finally {
|
||||
SessionProtocol.pendingSendSessionsTimestamp.delete(pubkey.key);
|
||||
}
|
||||
// const timestamp = Date.now();
|
||||
// // mark the session as being pending send with current timestamp
|
||||
// // so we know we already triggered a new session with that device
|
||||
// // so sendSessionRequestIfNeeded does not sent another session request
|
||||
// SessionProtocol.pendingSendSessionsTimestamp.add(pubkey.key);
|
||||
// try {
|
||||
// const rawMessage = await MessageUtils.toRawMessage(pubkey, message);
|
||||
// await MessageSender.send(rawMessage);
|
||||
// await SessionProtocol.updateSentSessionTimestamp(pubkey.key, timestamp);
|
||||
// } catch (e) {
|
||||
// throw e;
|
||||
// } finally {
|
||||
// SessionProtocol.pendingSendSessionsTimestamp.delete(pubkey.key);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
import { RawMessage } from '../types/RawMessage';
|
||||
import {
|
||||
ContentMessage,
|
||||
MediumGroupChatMessage,
|
||||
SessionRequestMessage,
|
||||
} from '../messages/outgoing';
|
||||
import { ContentMessage, MediumGroupChatMessage } from '../messages/outgoing';
|
||||
import { EncryptionType, PubKey } from '../types';
|
||||
import { SessionProtocol } from '../protocols';
|
||||
import { MediumGroupUpdateMessage } from '../messages/outgoing/content/data/mediumgroup/MediumGroupUpdateMessage';
|
||||
|
||||
export async function toRawMessage(
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
import { expect } from 'chai';
|
||||
import { beforeEach } from 'mocha';
|
||||
|
||||
import { SessionEstablishedMessage } from '../../../../session/messages/outgoing';
|
||||
import { SignalService } from '../../../../protobuf';
|
||||
import { Constants } from '../../../../session';
|
||||
|
||||
describe('SessionEstablishedMessage', () => {
|
||||
let message: SessionEstablishedMessage;
|
||||
beforeEach(() => {
|
||||
const timestamp = Date.now();
|
||||
message = new SessionEstablishedMessage({ timestamp });
|
||||
});
|
||||
|
||||
it('has a nullMessage not null', () => {
|
||||
const plainText = message.plainTextBuffer();
|
||||
const decoded = SignalService.Content.decode(plainText);
|
||||
|
||||
expect(decoded.nullMessage).to.be.not.equal(
|
||||
null,
|
||||
'decoded.dataMessage.nullMessage should not be null'
|
||||
);
|
||||
});
|
||||
|
||||
it('correct ttl', () => {
|
||||
expect(message.ttl()).to.equal(Constants.TTL_DEFAULT.SESSION_ESTABLISHED);
|
||||
});
|
||||
|
||||
it('has an identifier', () => {
|
||||
expect(message.identifier).to.not.equal(null, 'identifier cannot be null');
|
||||
expect(message.identifier).to.not.equal(
|
||||
undefined,
|
||||
'identifier cannot be undefined'
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,79 +0,0 @@
|
|||
import { expect } from 'chai';
|
||||
import { beforeEach } from 'mocha';
|
||||
|
||||
import { SessionRequestMessage } from '../../../../session/messages/outgoing';
|
||||
import { SignalService } from '../../../../protobuf';
|
||||
import { TextDecoder, TextEncoder } from 'util';
|
||||
import { Constants } from '../../../../session';
|
||||
|
||||
describe('SessionRequestMessage', () => {
|
||||
let message: 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'),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
const timestamp = Date.now();
|
||||
message = new SessionRequestMessage({ timestamp, preKeyBundle });
|
||||
});
|
||||
|
||||
it('has a preKeyBundle', () => {
|
||||
const plainText = message.plainTextBuffer();
|
||||
const decoded = SignalService.Content.decode(plainText);
|
||||
|
||||
expect(decoded.preKeyBundleMessage).to.have.property(
|
||||
'deviceId',
|
||||
preKeyBundle.deviceId
|
||||
);
|
||||
expect(decoded.preKeyBundleMessage).to.have.property(
|
||||
'preKeyId',
|
||||
preKeyBundle.preKeyId
|
||||
);
|
||||
expect(decoded.preKeyBundleMessage).to.have.property(
|
||||
'signedKeyId',
|
||||
preKeyBundle.signedKeyId
|
||||
);
|
||||
|
||||
const signature = new TextDecoder().decode(
|
||||
decoded.preKeyBundleMessage?.signature
|
||||
);
|
||||
const signedKey = new TextDecoder().decode(
|
||||
decoded.preKeyBundleMessage?.signedKey
|
||||
);
|
||||
const identityKey = new TextDecoder().decode(
|
||||
decoded.preKeyBundleMessage?.identityKey
|
||||
);
|
||||
|
||||
expect(signature).to.be.deep.equal('signature');
|
||||
expect(signedKey).to.be.deep.equal('signedKey');
|
||||
expect(identityKey).to.be.deep.equal('identityKey');
|
||||
});
|
||||
|
||||
it('has a nullMessage not null', () => {
|
||||
const plainText = message.plainTextBuffer();
|
||||
const decoded = SignalService.Content.decode(plainText);
|
||||
|
||||
expect(decoded.nullMessage).to.be.not.equal(
|
||||
null,
|
||||
'decoded.dataMessage.nullMessage should not be null'
|
||||
);
|
||||
});
|
||||
|
||||
it('correct ttl', () => {
|
||||
expect(message.ttl()).to.equal(Constants.TTL_DEFAULT.SESSION_REQUEST);
|
||||
});
|
||||
|
||||
it('has an identifier', () => {
|
||||
expect(message.identifier).to.not.equal(null, 'identifier cannot be null');
|
||||
expect(message.identifier).to.not.equal(
|
||||
undefined,
|
||||
'identifier cannot be undefined'
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,403 +0,0 @@
|
|||
import { expect } from 'chai';
|
||||
import * as sinon from 'sinon';
|
||||
import { SessionProtocol } from '../../../../session/protocols';
|
||||
import { Stubs, TestUtils } from '../../../test-utils';
|
||||
import { UserUtil } from '../../../../util';
|
||||
import { SessionRequestMessage } from '../../../../session/messages/outgoing';
|
||||
import { TextEncoder } from 'util';
|
||||
import { MessageSender } from '../../../../session/sending';
|
||||
import { PubKey } from '../../../../session/types';
|
||||
import { Constants } from '../../../../session';
|
||||
|
||||
// tslint:disable-next-line: max-func-body-length
|
||||
describe('SessionProtocol', () => {
|
||||
const sandbox = sinon.createSandbox();
|
||||
const ourNumber = TestUtils.generateFakePubKey();
|
||||
const pubkey = TestUtils.generateFakePubKey();
|
||||
let getItemById: sinon.SinonStub;
|
||||
let send: sinon.SinonStub;
|
||||
|
||||
const resetMessage: SessionRequestMessage = new SessionRequestMessage({
|
||||
timestamp: Date.now(),
|
||||
preKeyBundle: {
|
||||
identityKey: new TextEncoder().encode('identityKey'),
|
||||
deviceId: 1,
|
||||
preKeyId: 2,
|
||||
signedKeyId: 3,
|
||||
preKey: new TextEncoder().encode('preKey'),
|
||||
signedKey: new TextEncoder().encode('signedKey'),
|
||||
signature: new TextEncoder().encode('signature'),
|
||||
},
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
TestUtils.stubWindow('libsignal', {
|
||||
SignalProtocolAddress: sandbox.stub(),
|
||||
SessionCipher: Stubs.SessionCipherStub,
|
||||
} as any);
|
||||
|
||||
TestUtils.stubWindow('libloki', {
|
||||
storage: {
|
||||
getPreKeyBundleForContact: sandbox.stub(),
|
||||
},
|
||||
});
|
||||
|
||||
TestUtils.stubWindow('textsecure', {
|
||||
storage: {
|
||||
protocol: sandbox.stub(),
|
||||
},
|
||||
});
|
||||
|
||||
TestUtils.stubData('createOrUpdateItem');
|
||||
|
||||
getItemById = TestUtils.stubData('getItemById').resolves({ value: {} });
|
||||
|
||||
sandbox.stub(UserUtil, 'getCurrentDevicePubKey').resolves(ourNumber.key);
|
||||
send = sandbox.stub(MessageSender, 'send' as any);
|
||||
SessionProtocol.reset();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
TestUtils.restoreStubs();
|
||||
});
|
||||
|
||||
describe('db fetch', () => {
|
||||
it('protocol: should fetch from DB `sentSessionsTimestamp` and `processedSessionsTimestamp`', async () => {
|
||||
await SessionProtocol.hasSentSessionRequest(pubkey);
|
||||
expect(getItemById.calledWith('sentSessionsTimestamp'));
|
||||
expect(getItemById.calledWith('processedSessionsTimestamp'));
|
||||
expect(getItemById.callCount).to.equal(2);
|
||||
});
|
||||
|
||||
it('protocol: should fetch only once', async () => {
|
||||
await SessionProtocol.hasSentSessionRequest(pubkey);
|
||||
await SessionProtocol.hasSentSessionRequest(pubkey);
|
||||
await SessionProtocol.hasSentSessionRequest(pubkey);
|
||||
await SessionProtocol.hasSentSessionRequest(pubkey);
|
||||
expect(getItemById.calledWith('sentSessionsTimestamp'));
|
||||
expect(getItemById.calledWith('processedSessionsTimestamp'));
|
||||
expect(getItemById.callCount).to.equal(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendSessionRequest', () => {
|
||||
beforeEach(async () => {
|
||||
// trigger a sessionReset
|
||||
await SessionProtocol.sendSessionRequest(resetMessage, pubkey);
|
||||
});
|
||||
|
||||
it('protocol: sendSessionRequest should add the deviceID to the sentMap', async () => {
|
||||
expect(SessionProtocol.getSentSessionsTimestamp())
|
||||
.to.have.property(pubkey.key)
|
||||
.to.be.approximately(Date.now(), 100);
|
||||
});
|
||||
|
||||
it('protocol: sendSessionRequest should not have pendingSend set after', async () => {
|
||||
expect(
|
||||
SessionProtocol.getPendingSendSessionTimestamp()
|
||||
).to.not.have.property(pubkey.key);
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkSessionRequestExpiry', () => {
|
||||
let clock: sinon.SinonFakeTimers;
|
||||
let now: number;
|
||||
let sendSessionRequestStub: sinon.SinonStub<
|
||||
[SessionRequestMessage, PubKey],
|
||||
Promise<void>
|
||||
>;
|
||||
beforeEach(() => {
|
||||
now = Date.now();
|
||||
clock = sandbox.useFakeTimers(now);
|
||||
|
||||
sendSessionRequestStub = sandbox
|
||||
.stub(SessionProtocol, 'sendSessionRequest')
|
||||
.resolves();
|
||||
});
|
||||
|
||||
it('should not send a session request if none have expired', async () => {
|
||||
getItemById.withArgs('sentSessionsTimestamp').resolves({
|
||||
id: 'sentSessionsTimestamp',
|
||||
value: {
|
||||
[pubkey.key]: now,
|
||||
},
|
||||
});
|
||||
|
||||
// Set the time just before expiry
|
||||
clock.tick(Constants.TTL_DEFAULT.SESSION_REQUEST - 100);
|
||||
|
||||
await SessionProtocol.checkSessionRequestExpiry();
|
||||
expect(getItemById.calledWith('sentSessionsTimestamp'));
|
||||
expect(sendSessionRequestStub.callCount).to.equal(0);
|
||||
});
|
||||
|
||||
it('should send a session request if expired', async () => {
|
||||
getItemById.withArgs('sentSessionsTimestamp').resolves({
|
||||
id: 'sentSessionsTimestamp',
|
||||
value: {
|
||||
[pubkey.key]: now,
|
||||
},
|
||||
});
|
||||
|
||||
// Expire the request
|
||||
clock.tick(Constants.TTL_DEFAULT.SESSION_REQUEST + 100);
|
||||
|
||||
await SessionProtocol.checkSessionRequestExpiry();
|
||||
expect(getItemById.calledWith('sentSessionsTimestamp'));
|
||||
expect(sendSessionRequestStub.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('should remove the old sent timestamp when expired', async () => {
|
||||
getItemById.withArgs('sentSessionsTimestamp').resolves({
|
||||
id: 'sentSessionsTimestamp',
|
||||
value: {
|
||||
[pubkey.key]: now,
|
||||
},
|
||||
});
|
||||
|
||||
// Remove this call from the equation
|
||||
sandbox.stub(SessionProtocol, 'sendSessionRequestIfNeeded').resolves();
|
||||
|
||||
// Expire the request
|
||||
clock.tick(Constants.TTL_DEFAULT.SESSION_REQUEST + 100);
|
||||
|
||||
await SessionProtocol.checkSessionRequestExpiry();
|
||||
expect(getItemById.calledWith('sentSessionsTimestamp'));
|
||||
expect(await SessionProtocol.hasSentSessionRequest(pubkey)).to.equal(
|
||||
false,
|
||||
'hasSentSessionRequest should return false.'
|
||||
);
|
||||
expect(SessionProtocol.getSentSessionsTimestamp()).to.not.have.property(
|
||||
pubkey.key
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onSessionEstablished', () => {
|
||||
beforeEach(async () => {
|
||||
// add an existing entry in the sentMap
|
||||
await SessionProtocol.sendSessionRequest(resetMessage, pubkey);
|
||||
});
|
||||
|
||||
it('protocol: onSessionEstablished should remove the device in sentTimestamps', async () => {
|
||||
expect(SessionProtocol.getSentSessionsTimestamp()).to.have.property(
|
||||
pubkey.key
|
||||
);
|
||||
await SessionProtocol.onSessionEstablished(pubkey);
|
||||
expect(SessionProtocol.getSentSessionsTimestamp()).to.not.have.property(
|
||||
pubkey.key
|
||||
);
|
||||
});
|
||||
|
||||
it('protocol: onSessionEstablished should remove the device in sentTimestamps and ONLY that one', async () => {
|
||||
// add a second item to the map
|
||||
const anotherPubKey = TestUtils.generateFakePubKey();
|
||||
await SessionProtocol.sendSessionRequest(resetMessage, anotherPubKey);
|
||||
|
||||
expect(SessionProtocol.getSentSessionsTimestamp()).to.have.property(
|
||||
pubkey.key
|
||||
);
|
||||
expect(SessionProtocol.getSentSessionsTimestamp()).to.have.property(
|
||||
anotherPubKey.key
|
||||
);
|
||||
|
||||
await SessionProtocol.onSessionEstablished(pubkey);
|
||||
expect(SessionProtocol.getSentSessionsTimestamp()).to.not.have.property(
|
||||
pubkey.key
|
||||
);
|
||||
expect(SessionProtocol.getSentSessionsTimestamp()).to.have.property(
|
||||
anotherPubKey.key
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasSentSessionRequest', () => {
|
||||
it('protocol: hasSentSessionRequest returns false if a message was not sent to that device', async () => {
|
||||
const hasSent = await SessionProtocol.hasSentSessionRequest(pubkey);
|
||||
expect(hasSent).to.be.equal(
|
||||
false,
|
||||
`hasSent should be false for ${pubkey.key}`
|
||||
);
|
||||
});
|
||||
|
||||
it('protocol: hasSentSessionRequest returns true if a message is already sent for that device', async () => {
|
||||
// add an existing entry in the sentMap
|
||||
await SessionProtocol.sendSessionRequest(resetMessage, pubkey);
|
||||
const hasSent = await SessionProtocol.hasSentSessionRequest(pubkey);
|
||||
expect(hasSent).to.be.equal(
|
||||
true,
|
||||
`hasSent should be true for ${pubkey.key}`
|
||||
);
|
||||
});
|
||||
|
||||
// TODO add a test to validate that pending is filled when message is triggered and not yet sent
|
||||
});
|
||||
|
||||
describe('sendSessionRequestIfNeeded', () => {
|
||||
it('protocol: sendSessionRequestIfNeeded should send a new sessionMessage ', async () => {
|
||||
// not called before, so the message reset sending should be triggered
|
||||
await SessionProtocol.sendSessionRequestIfNeeded(pubkey);
|
||||
expect(send.callCount).to.be.equal(
|
||||
1,
|
||||
'MessageSender.send() should have been called'
|
||||
);
|
||||
|
||||
// check that the map is updated with that ID
|
||||
const hasSent = await SessionProtocol.hasSentSessionRequest(pubkey);
|
||||
expect(hasSent).to.be.equal(
|
||||
true,
|
||||
`hasSent should be true for ${pubkey.key}`
|
||||
);
|
||||
});
|
||||
|
||||
it('protocol: sendSessionRequestIfNeeded should NOT send a new sessionMessage on second try ', async () => {
|
||||
await SessionProtocol.sendSessionRequestIfNeeded(pubkey);
|
||||
expect(send.callCount).to.be.equal(
|
||||
1,
|
||||
'MessageSender.send() should have been called'
|
||||
);
|
||||
|
||||
// check that the map is updated with that ID
|
||||
const hasSent = await SessionProtocol.hasSentSessionRequest(pubkey);
|
||||
expect(hasSent).to.be.equal(
|
||||
true,
|
||||
`hasSent should be true for ${pubkey.key}`
|
||||
);
|
||||
send.resetHistory();
|
||||
|
||||
// trigger a second call, Message.send().calledCount should still be 1
|
||||
await SessionProtocol.sendSessionRequestIfNeeded(pubkey);
|
||||
expect(send.callCount).to.be.equal(
|
||||
0,
|
||||
'MessageSender.send() should NOT have been called a second time'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onSessionRequestProcessed', () => {
|
||||
it('protocol: onSessionRequestProcessed should insert a new item in the processedMap ', async () => {
|
||||
// trigger the requestProcessed and check the map is updated
|
||||
await SessionProtocol.onSessionRequestProcessed(pubkey);
|
||||
expect(SessionProtocol.getProcessedSessionsTimestamp())
|
||||
.to.have.property(pubkey.key)
|
||||
.to.be.approximately(Date.now(), 5);
|
||||
});
|
||||
|
||||
it('protocol: onSessionRequestProcessed should update an existing item in the processedMap ', async () => {
|
||||
// trigger the requestProcessed and check the map is updated
|
||||
// then trigger it a second time, and expect a change in the processed timestamp
|
||||
|
||||
await SessionProtocol.onSessionRequestProcessed(pubkey);
|
||||
expect(SessionProtocol.getProcessedSessionsTimestamp())
|
||||
.to.have.property(pubkey.key)
|
||||
.to.be.approximately(Date.now(), 5);
|
||||
await TestUtils.timeout(5);
|
||||
const oldTimestamp = SessionProtocol.getProcessedSessionsTimestamp()[
|
||||
pubkey.key
|
||||
];
|
||||
await SessionProtocol.onSessionRequestProcessed(pubkey);
|
||||
expect(SessionProtocol.getProcessedSessionsTimestamp())
|
||||
.to.have.property(pubkey.key)
|
||||
.to.be.approximately(Date.now(), 5)
|
||||
.to.not.be.equal(oldTimestamp);
|
||||
});
|
||||
});
|
||||
|
||||
describe('shouldProcessSessionRequest', () => {
|
||||
it('protocol: shouldProcessSessionRequest returns true if timestamp is more recent than processed timestamp', async () => {
|
||||
await SessionProtocol.onSessionRequestProcessed(pubkey); // adds a Date.now() entry
|
||||
expect(
|
||||
SessionProtocol.shouldProcessSessionRequest(pubkey, Date.now() + 1000)
|
||||
).to.be.eventually.equal(
|
||||
true,
|
||||
'shouldProcessSessionRequest should return true when existingProcessed is less recent'
|
||||
);
|
||||
});
|
||||
|
||||
it('protocol: shouldProcessSessionRequest returns true if there is no processed timestamp yet for this device', async () => {
|
||||
expect(
|
||||
SessionProtocol.shouldProcessSessionRequest(pubkey, 100)
|
||||
).to.be.eventually.equal(
|
||||
true,
|
||||
'shouldProcessSessionRequest should return false when existingProcessed is empty for this device'
|
||||
);
|
||||
});
|
||||
|
||||
it('protocol: shouldProcessSessionRequest returns false if timestamp is less recent than current processed timestamp', async () => {
|
||||
await SessionProtocol.onSessionRequestProcessed(pubkey); // adds a Date.now() entry
|
||||
expect(
|
||||
SessionProtocol.shouldProcessSessionRequest(pubkey, 100)
|
||||
).to.be.eventually.equal(
|
||||
false,
|
||||
'shouldProcessSessionRequest should return false when existingProcessed is more recent'
|
||||
);
|
||||
});
|
||||
|
||||
it('protocol: shouldProcessSessionRequest returns false if timestamp is less recent than current sent timestamp', async () => {
|
||||
await SessionProtocol.sendSessionRequest(resetMessage, pubkey); // adds a Date.now() entry
|
||||
expect(
|
||||
SessionProtocol.shouldProcessSessionRequest(pubkey, 100)
|
||||
).to.be.eventually.equal(
|
||||
false,
|
||||
'shouldProcessSessionRequest should return false when existingSent is more recent'
|
||||
);
|
||||
});
|
||||
|
||||
it('protocol: shouldProcessSessionRequest returns true if timestamp is more recent than current sent timestamp', async () => {
|
||||
await SessionProtocol.sendSessionRequest(resetMessage, pubkey); // adds a Date.now() entry
|
||||
expect(
|
||||
SessionProtocol.shouldProcessSessionRequest(pubkey, Date.now() + 1000)
|
||||
).to.be.eventually.equal(
|
||||
true,
|
||||
'shouldProcessSessionRequest should return true when existingSent is less recent'
|
||||
);
|
||||
});
|
||||
|
||||
it('protocol: shouldProcessSessionRequest returns true if there is no sent timestamp', async () => {
|
||||
expect(
|
||||
SessionProtocol.shouldProcessSessionRequest(pubkey, 100)
|
||||
).to.be.eventually.equal(
|
||||
true,
|
||||
'shouldProcessSessionRequest should return true as there is no sent timestamp'
|
||||
);
|
||||
});
|
||||
|
||||
it('protocol: shouldProcessSessionRequest returns false if there is a more recent sent but a less recent processed', async () => {
|
||||
await SessionProtocol.sendSessionRequest(resetMessage, pubkey); // adds a Date.now() entry
|
||||
await TestUtils.timeout(100);
|
||||
await SessionProtocol.onSessionRequestProcessed(pubkey); // adds a Date.now() entry 100ms after
|
||||
|
||||
expect(
|
||||
SessionProtocol.shouldProcessSessionRequest(pubkey, Date.now() - 50)
|
||||
).to.be.eventually.equal(
|
||||
false,
|
||||
'shouldProcessSessionRequest should return false if there is a more recent sent but a less recent processed'
|
||||
);
|
||||
});
|
||||
|
||||
it('protocol: shouldProcessSessionRequest returns false if there is a more recent processed but a less recent sent', async () => {
|
||||
await SessionProtocol.onSessionRequestProcessed(pubkey); // adds a Date.now() entry
|
||||
await TestUtils.timeout(100);
|
||||
await SessionProtocol.sendSessionRequest(resetMessage, pubkey); // adds a Date.now() entry 100ms after
|
||||
|
||||
expect(
|
||||
SessionProtocol.shouldProcessSessionRequest(pubkey, Date.now() - 50)
|
||||
).to.be.eventually.equal(
|
||||
false,
|
||||
'shouldProcessSessionRequest should return false if there is a more recent processed but a less recent sent'
|
||||
);
|
||||
});
|
||||
|
||||
it('protocol: shouldProcessSessionRequest returns true if both sent and processed timestamp are older', async () => {
|
||||
await SessionProtocol.onSessionRequestProcessed(pubkey); // adds a Date.now() entry
|
||||
await SessionProtocol.sendSessionRequest(resetMessage, pubkey); // adds a Date.now() entry
|
||||
expect(
|
||||
SessionProtocol.shouldProcessSessionRequest(pubkey, Date.now() + 1000)
|
||||
).to.be.eventually.equal(
|
||||
true,
|
||||
'shouldProcessSessionRequest should return true if there if both processed and sent are set but are older'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -101,32 +101,6 @@ describe('Message Utils', () => {
|
|||
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);
|
||||
|
||||
|
|
Loading…
Reference in New Issue