From c01cd634e4c924ad3c3b5133ad2ad052e185abfe Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 24 Sep 2020 14:28:13 +1000 Subject: [PATCH 01/20] Generate our own senderkey if it does not exist This case can happen on restore from seed --- ts/receiver/mediumGroups.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/ts/receiver/mediumGroups.ts b/ts/receiver/mediumGroups.ts index 021bdcb31..a48851fd8 100644 --- a/ts/receiver/mediumGroups.ts +++ b/ts/receiver/mediumGroups.ts @@ -32,13 +32,19 @@ async function handleSenderKeyRequest( log.debug('[sender key] sender key request from:', senderIdentity); - const maybeKey = await getChainKey(groupId, ourIdentity); + let maybeKey = await getChainKey(groupId, ourIdentity); if (!maybeKey) { - // Regenerate? This should never happen though - log.error('Could not find own sender key'); - await removeFromCache(envelope); - return; + log.error('Could not find own sender key. Generating new one.'); + maybeKey = await SenderKeyAPI.createSenderKeyForGroup( + groupId, + PubKey.cast(ourIdentity) + ); + if (!maybeKey) { + log.error('Could not find own sender key after regenerate'); + await removeFromCache(envelope); + return; + } } // We reuse the same message type for sender keys From 4578c635a6f1fd79137797aef640992d294ee8fb Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Fri, 25 Sep 2020 14:13:32 +1000 Subject: [PATCH 02/20] render @ mention correctly with long title on ConversationListItem --- stylesheets/_mentions.scss | 5 ++--- ts/components/ConversationListItem.tsx | 30 +++++++++++--------------- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/stylesheets/_mentions.scss b/stylesheets/_mentions.scss index 04e6d0377..34337564c 100644 --- a/stylesheets/_mentions.scss +++ b/stylesheets/_mentions.scss @@ -100,9 +100,8 @@ padding-inline-start: 3px; padding-inline-end: 3px; - position: absolute; - left: 50%; - margin-inline-start: 30px; + position: static; + margin-inline-start: 5px; top: 2px; font-weight: 300; diff --git a/ts/components/ConversationListItem.tsx b/ts/components/ConversationListItem.tsx index 3bcc26d29..0d2c62451 100644 --- a/ts/components/ConversationListItem.tsx +++ b/ts/components/ConversationListItem.tsx @@ -108,28 +108,21 @@ class ConversationListItem extends React.PureComponent { ); } - public renderUnread() { - const { unreadCount, mentionedUs } = this.props; + public renderHeader() { + const { unreadCount, mentionedUs, i18n, isMe, lastUpdated } = this.props; + const {} = this.props; + let atSymbol = null; + let unreadCountDiv = null; if (unreadCount > 0) { - const atSymbol = mentionedUs ?

@

: null; - - return ( -
-

- {unreadCount} -

- {atSymbol} -
+ atSymbol = mentionedUs ?

@

: null; + unreadCountDiv = ( +

+ {unreadCount} +

); } - return null; - } - - public renderHeader() { - const { unreadCount, i18n, isMe, lastUpdated } = this.props; - return (
{ > {this.renderUser()}
- {this.renderUnread()} + {unreadCountDiv} + {atSymbol} {
Date: Fri, 25 Sep 2020 14:29:32 +1000 Subject: [PATCH 03/20] themify the typing bubble --- js/views/conversation_view.js | 4 ++++ ts/components/conversation/TypingBubble.tsx | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index c2752c2bf..b89a90bf2 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -701,9 +701,13 @@ const { sender } = mostRecent; const contact = ConversationController.getOrCreate(sender, 'private'); + // we need the opposite theme + const color = + window.Events.getThemeSetting() === 'light' ? 'dark' : 'light'; const props = { ...contact.format(), conversationType: this.model.isPrivate() ? 'direct' : 'group', + color, }; if (this.typingBubbleView) { diff --git a/ts/components/conversation/TypingBubble.tsx b/ts/components/conversation/TypingBubble.tsx index 5a683ca2f..7b4756de3 100644 --- a/ts/components/conversation/TypingBubble.tsx +++ b/ts/components/conversation/TypingBubble.tsx @@ -58,7 +58,7 @@ export class TypingBubble extends React.Component { )} >
- +
{this.renderAvatar()}
From d4e9b43f0c2f571ef38e18fd607097f3c61be30f Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 30 Sep 2020 10:13:47 +1000 Subject: [PATCH 04/20] remove QUIT type of mediumgroupupdate --- protos/SignalService.proto | 1 - ts/receiver/mediumGroups.ts | 2 -- ts/session/medium_group/index.ts | 1 - .../data/mediumgroup/MediumGroupQuitMessage.ts | 12 ------------ 4 files changed, 16 deletions(-) delete mode 100644 ts/session/messages/outgoing/content/data/mediumgroup/MediumGroupQuitMessage.ts diff --git a/protos/SignalService.proto b/protos/SignalService.proto index f7b76be2f..8a40c1576 100644 --- a/protos/SignalService.proto +++ b/protos/SignalService.proto @@ -57,7 +57,6 @@ message MediumGroupUpdate { INFO = 1; // groupPublicKey, name, senderKeys, members, admins SENDER_KEY = 2; // groupPublicKey, senderKeys SENDER_KEY_REQUEST = 3; // groupPublicKey - QUIT = 4; // groupPublicKey } message SenderKey { diff --git a/ts/receiver/mediumGroups.ts b/ts/receiver/mediumGroups.ts index 810e6340e..9620e11d6 100644 --- a/ts/receiver/mediumGroups.ts +++ b/ts/receiver/mediumGroups.ts @@ -427,8 +427,6 @@ export async function handleMediumGroupUpdate( await handleNewGroup(envelope, groupUpdate); } else if (type === Type.INFO) { await handleMediumGroupChange(envelope, groupUpdate); - } else if (type === Type.QUIT) { - await handleQuit(envelope, groupUpdate); } else { window.log.error('Unknown group update type: ', type); } diff --git a/ts/session/medium_group/index.ts b/ts/session/medium_group/index.ts index 1ce3d5197..fac27a0a7 100644 --- a/ts/session/medium_group/index.ts +++ b/ts/session/medium_group/index.ts @@ -13,7 +13,6 @@ import { getChainKey } from './ratchet'; import { MultiDeviceProtocol } from '../protocols'; import { BufferType } from '../utils/String'; import { UserUtil } from '../../util'; -import { MediumGroupQuitMessage } from '../messages/outgoing/content/data/mediumgroup/MediumGroupQuitMessage'; import { ClosedGroupChatMessage, ClosedGroupMessage, diff --git a/ts/session/messages/outgoing/content/data/mediumgroup/MediumGroupQuitMessage.ts b/ts/session/messages/outgoing/content/data/mediumgroup/MediumGroupQuitMessage.ts deleted file mode 100644 index 42d6e9f9b..000000000 --- a/ts/session/messages/outgoing/content/data/mediumgroup/MediumGroupQuitMessage.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { SignalService } from '../../../../../../protobuf'; -import { MediumGroupMessage } from '.'; - -export class MediumGroupQuitMessage extends MediumGroupMessage { - protected mediumGroupContext(): SignalService.MediumGroupUpdate { - const mediumGroupContext = super.mediumGroupContext(); - - mediumGroupContext.type = SignalService.MediumGroupUpdate.Type.QUIT; - - return mediumGroupContext; - } -} From 22afe50c05a958851ddd09154378c9eba22b1806 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 30 Sep 2020 10:22:36 +1000 Subject: [PATCH 05/20] make messageQueue handle medium group message in sendToGroup --- js/models/conversations.js | 2 +- ts/receiver/mediumGroups.ts | 2 +- ts/session/sending/MessageQueue.ts | 10 +++++++++- ts/session/sending/MessageQueueInterface.ts | 3 ++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/js/models/conversations.js b/js/models/conversations.js index b2ec3bf66..be3b0aa6b 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -1403,7 +1403,7 @@ await libsession .getMessageQueue() - .send(destinationPubkey, mediumGroupChatMessage); + .sendToGroup(mediumGroupChatMessage); } else { const closedGroupChatMessage = new libsession.Messages.Outgoing.ClosedGroupChatMessage( { diff --git a/ts/receiver/mediumGroups.ts b/ts/receiver/mediumGroups.ts index 9620e11d6..4dece5006 100644 --- a/ts/receiver/mediumGroups.ts +++ b/ts/receiver/mediumGroups.ts @@ -307,7 +307,7 @@ async function handleMediumGroupChange( return; } - // // Check that the sender is admin (make sure it words with multidevice) + // Check that the sender is admin (make sure it words with multidevice) const isAdmin = true; if (!isAdmin) { diff --git a/ts/session/sending/MessageQueue.ts b/ts/session/sending/MessageQueue.ts index b5e86e41d..4cddd8e73 100644 --- a/ts/session/sending/MessageQueue.ts +++ b/ts/session/sending/MessageQueue.ts @@ -7,6 +7,7 @@ import { ClosedGroupMessage, ContentMessage, ExpirationTimerUpdateMessage, + MediumGroupMessage, OpenGroupMessage, SessionRequestMessage, SyncMessage, @@ -51,7 +52,7 @@ export class MessageQueue implements MessageQueueInterface { } public async sendToGroup( - message: OpenGroupMessage | ContentMessage + message: OpenGroupMessage | ContentMessage | MediumGroupMessage ): Promise { // Open groups if (message instanceof OpenGroupMessage) { @@ -93,11 +94,18 @@ export class MessageQueue implements MessageQueueInterface { groupId = message.groupId; } else if (message instanceof ExpirationTimerUpdateMessage) { groupId = message.groupId; + } else if (message instanceof MediumGroupMessage) { + groupId = message.groupId; } if (!groupId) { throw new Error('Invalid group message passed in sendToGroup.'); } + // if this is a medium group message. We just need to send to the group pubkey + if (message instanceof MediumGroupMessage) { + window.log.warn('sending medium ', message, ' to ', groupId) + return this.send(PubKey.cast(groupId), message); + } // Get devices in group let recipients = await GroupUtils.getGroupMembers(groupId); diff --git a/ts/session/sending/MessageQueueInterface.ts b/ts/session/sending/MessageQueueInterface.ts index c30592741..af44e6e7f 100644 --- a/ts/session/sending/MessageQueueInterface.ts +++ b/ts/session/sending/MessageQueueInterface.ts @@ -1,6 +1,7 @@ import { ClosedGroupMessage, ContentMessage, + MediumGroupMessage, OpenGroupMessage, SyncMessage, } from '../messages/outgoing'; @@ -8,7 +9,7 @@ import { RawMessage } from '../types/RawMessage'; import { TypedEventEmitter } from '../utils'; import { PubKey } from '../types'; -type GroupMessageType = OpenGroupMessage | ClosedGroupMessage; +type GroupMessageType = OpenGroupMessage | ClosedGroupMessage | MediumGroupMessage; export interface MessageQueueInterfaceEvents { success: ( From 06d0683ce5be5285a4e8021861d4a153231bf095 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 30 Sep 2020 10:23:27 +1000 Subject: [PATCH 06/20] WIP --- ts/receiver/mediumGroups.ts | 2 +- ts/session/medium_group/index.ts | 77 +++++++++++++++++++++++--------- 2 files changed, 58 insertions(+), 21 deletions(-) diff --git a/ts/receiver/mediumGroups.ts b/ts/receiver/mediumGroups.ts index 4dece5006..c3312c1df 100644 --- a/ts/receiver/mediumGroups.ts +++ b/ts/receiver/mediumGroups.ts @@ -67,7 +67,7 @@ async function handleSenderKeyRequest( await removeFromCache(envelope); } -async function shareSenderKeys( +export async function shareSenderKeys( groupId: string, recipientsPrimary: Array, senderKey: RatchetState diff --git a/ts/session/medium_group/index.ts b/ts/session/medium_group/index.ts index fac27a0a7..aeb72d767 100644 --- a/ts/session/medium_group/index.ts +++ b/ts/session/medium_group/index.ts @@ -28,6 +28,7 @@ import { ConversationModel } from '../../../js/models/conversations'; import { MediumGroupUpdateMessage } from '../messages/outgoing/content/data/mediumgroup/MediumGroupUpdateMessage'; import uuid from 'uuid'; import { BlockedNumberController } from '../../util/blockedNumberController'; +import { shareSenderKeys } from '../../receiver/mediumGroups'; export { createSenderKeyForGroup, @@ -184,6 +185,7 @@ export async function leaveMediumGroup(groupId: string) { // TODO: need to reset everyone's sender keys window.SwarmPolling.removePubkey(groupId); + // TODO audric: we just left a group, we have to regenerate our senderkey const maybeConvo = await ConversationController.get(groupId); @@ -207,15 +209,23 @@ export async function leaveMediumGroup(groupId: string) { received_at: now, }); - const updateParams = { - timestamp: Date.now(), - identifier: dbMessage.id, - groupId, + // do not include senderkey as everyone needs to generate new one + const groupUpdate: GroupInfo = { + id: convo.get('id'), + name: convo.get('name'), + members: convo.get('members'), + is_medium_group: true, + admins: convo.get('groupAdmins'), + senderKeysContainer: undefined, }; - const message = new MediumGroupQuitMessage(updateParams); + const ourPrimary = await UserUtil.getPrimary(); - await sendToMembers(groupId, message, dbMessage); + await sendGroupUpdateForMedium( + { leavingMembers: [ourPrimary.key] }, + groupUpdate, + dbMessage.id + ); } // Just a container to store two named list of keys @@ -602,13 +612,16 @@ async function sendGroupUpdateForExistingMembers( messageId?: string ) { const { id: groupId, members, name: groupName } = groupUpdate; + const ourPrimary = await UserUtil.getPrimary(); + + const wasAnyUserRemoved = leavingMembers.length > 0; + const isUserLeaving = leavingMembers.includes(ourPrimary.key); const membersBin = members.map( (pkHex: string) => new Uint8Array(fromHex(pkHex)) ); const admins = groupUpdate.admins || []; - const adminsBin = admins.map( (pkHex: string) => new Uint8Array(fromHex(pkHex)) ); @@ -625,23 +638,47 @@ async function sendGroupUpdateForExistingMembers( members: membersBin, groupName, admins: adminsBin, - senderKeys, + senderKeys: senderKeys, }; - const message = new MediumGroupUpdateMessage(params); + if (wasAnyUserRemoved) { + if (isUserLeaving && leavingMembers.length !== 1) { + window.log.warn("Can't remove self and others simultaneously."); + return; + } + // Send the update to the group (don't include new ratchets as everyone should regenerate new ratchets individually) + const paramsWithoutSenderKeys = { + ...params, + senderKeys: [], + }; - remainingMembers.forEach(async member => { - const memberPubKey = new PubKey(member); - await getMessageQueue().sendUsingMultiDevice(memberPubKey, message); - }); + const messageStripped = new MediumGroupUpdateMessage(paramsWithoutSenderKeys); + window.log.warn('Sending to groupUpdateMessage without senderKeys'); + await getMessageQueue().sendToGroup(messageStripped); + + // TODO Delete all ratchets (it's important that this happens * after * sending out the update) + if (isUserLeaving) { + // nothing to do on desktop + } else { + // Send out the user's new ratchet to all members (minus the removed ones) using established channels + const ourPrimaryKey = new Uint8Array(fromHex(ourPrimary.key)) + const ourNewSenderKey = senderKeys.find(s => _.isEqual(s.pubKey, ourPrimaryKey)); + + if (! ourNewSenderKey) { + window.console.warn('We need to share our senderkey with remaining member but our senderKeys was not given.'); + } else { + window.log.warn('Sharing our new senderKey with remainingMembers via message', remainingMembers, ourNewSenderKey); + await shareSenderKeys(groupId, remainingMembers, ourNewSenderKey); + } + + } + } else { + const message = new MediumGroupUpdateMessage(params); + window.log.warn('Sending to groupUpdateMessage with senderKeys to groupAddress', senderKeys); + + await getMessageQueue().sendToGroup(message); + } - // Remove sender keys from the params to send to leaving memebers - params.senderKeys = []; - const strippedMessage = new MediumGroupUpdateMessage(params); - leavingMembers.forEach(async member => { - const memberPubKey = new PubKey(member); - await getMessageQueue().sendUsingMultiDevice(memberPubKey, strippedMessage); - }); } async function sendGroupUpdateForJoiningMembers( From 82bc050ddd2b0da5c0a61a2eb41f674631f85415 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 1 Oct 2020 09:58:10 +1000 Subject: [PATCH 07/20] always create a closed group the type set in the feature flag speaking about enableSenderKeys --- ts/components/session/LeftPaneMessageSection.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ts/components/session/LeftPaneMessageSection.tsx b/ts/components/session/LeftPaneMessageSection.tsx index 08e93a46a..08fd9a2ef 100644 --- a/ts/components/session/LeftPaneMessageSection.tsx +++ b/ts/components/session/LeftPaneMessageSection.tsx @@ -306,9 +306,8 @@ export class LeftPaneMessageSection extends React.Component { }} onButtonClick={async ( groupName: string, - groupMembers: Array, - senderKeys: boolean - ) => this.onCreateClosedGroup(groupName, groupMembers, senderKeys)} + groupMembers: Array + ) => this.onCreateClosedGroup(groupName, groupMembers, window.lokiFeatureFlags.enableSenderKeys)} searchTerm={searchTerm} updateSearch={this.updateSearchBound} showSpinner={loading} From 9dbf1900832a33d425b1582b6a3da3d8b2723bb4 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 1 Oct 2020 14:58:17 +1000 Subject: [PATCH 08/20] fix mismatches protobuf for senderkey type on mediun groups with android --- protos/SignalService.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/protos/SignalService.proto b/protos/SignalService.proto index 8a40c1576..741d5ab8f 100644 --- a/protos/SignalService.proto +++ b/protos/SignalService.proto @@ -55,8 +55,8 @@ message MediumGroupUpdate { enum Type { NEW = 0; // groupPublicKey, name, senderKeys, members, admins, groupPrivateKey INFO = 1; // groupPublicKey, name, senderKeys, members, admins - SENDER_KEY = 2; // groupPublicKey, senderKeys - SENDER_KEY_REQUEST = 3; // groupPublicKey + SENDER_KEY_REQUEST = 2; // groupPublicKey + SENDER_KEY = 3; // groupPublicKey, senderKeys } message SenderKey { From baaca1a29fac3795802eed726bf2af5d034cae8b Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 1 Oct 2020 15:05:25 +1000 Subject: [PATCH 09/20] switch medium or closed group deeper in code --- ts/components/MainViewController.tsx | 3 +-- ts/components/session/LeftPaneMessageSection.tsx | 11 +++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/ts/components/MainViewController.tsx b/ts/components/MainViewController.tsx index cdc0086e6..20dd05901 100644 --- a/ts/components/MainViewController.tsx +++ b/ts/components/MainViewController.tsx @@ -50,7 +50,6 @@ export class MessageView extends React.Component { async function createClosedGroup( groupName: string, groupMembers: Array, - senderKeys: boolean, onSuccess: any ) { // Validate groupName and groupMembers length @@ -95,7 +94,7 @@ async function createClosedGroup( const groupMemberIds = groupMembers.map(m => m.id); - if (senderKeys) { + if (window.lokiFeatureFlags.enableSenderKeys) { await createMediumGroup(groupName, groupMemberIds); } else { await createLegacyGroup(groupName, groupMemberIds); diff --git a/ts/components/session/LeftPaneMessageSection.tsx b/ts/components/session/LeftPaneMessageSection.tsx index 08fd9a2ef..2e3d525e2 100644 --- a/ts/components/session/LeftPaneMessageSection.tsx +++ b/ts/components/session/LeftPaneMessageSection.tsx @@ -307,7 +307,12 @@ export class LeftPaneMessageSection extends React.Component { onButtonClick={async ( groupName: string, groupMembers: Array - ) => this.onCreateClosedGroup(groupName, groupMembers, window.lokiFeatureFlags.enableSenderKeys)} + ) => + this.onCreateClosedGroup( + groupName, + groupMembers + ) + } searchTerm={searchTerm} updateSearch={this.updateSearchBound} showSpinner={loading} @@ -490,13 +495,11 @@ export class LeftPaneMessageSection extends React.Component { private async onCreateClosedGroup( groupName: string, - groupMembers: Array, - senderKeys: boolean + groupMembers: Array ) { await MainViewController.createClosedGroup( groupName, groupMembers, - senderKeys, () => { this.handleToggleOverlay(undefined); } From a80f9a5965e63c6afd5eefed17fc6bae08edaed7 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 1 Oct 2020 15:12:51 +1000 Subject: [PATCH 10/20] fix medium group to match mobile way --- ts/receiver/contentMessage.ts | 42 ++-- ts/receiver/dataMessage.ts | 5 +- ts/receiver/mediumGroups.ts | 10 +- ts/session/medium_group/index.ts | 263 +++++++++----------- ts/session/medium_group/ratchet.ts | 15 +- ts/session/sending/MessageQueue.ts | 7 +- ts/session/sending/MessageQueueInterface.ts | 5 +- ts/session/sending/MessageSender.ts | 1 + ts/session/utils/Messages.ts | 6 +- 9 files changed, 168 insertions(+), 186 deletions(-) diff --git a/ts/receiver/contentMessage.ts b/ts/receiver/contentMessage.ts index 7ed9eca8e..b32505438 100644 --- a/ts/receiver/contentMessage.ts +++ b/ts/receiver/contentMessage.ts @@ -19,6 +19,7 @@ import { BlockedNumberController } from '../util/blockedNumberController'; import { decryptWithSenderKey } from '../session/medium_group/ratchet'; import { StringUtils } from '../session/utils'; import { UserUtil } from '../util'; +import { getMessageQueue } from '../session'; export async function handleContentMessage(envelope: EnvelopePlus) { try { @@ -89,8 +90,7 @@ async function decryptForMediumGroup( groupId, sourceAsStr ); - - return unpad(plaintext); + return plaintext ? unpad(plaintext) : null; } function unpad(paddedData: ArrayBuffer): ArrayBuffer { @@ -288,28 +288,32 @@ async function decrypt( return plaintext; } catch (error) { - if (error && error instanceof textsecure.SenderKeyMissing) { + if ( + error && + (error instanceof textsecure.SenderKeyMissing || + error instanceof DOMException) + ) { const groupId = envelope.source; const { senderIdentity } = error; + if (senderIdentity) { + log.info( + 'Requesting missing key for identity: ', + senderIdentity, + 'groupId: ', + groupId + ); - log.info( - 'Requesting missing key for identity: ', - senderIdentity, - 'groupId: ', - groupId - ); - - const params = { - timestamp: Date.now(), - groupId, - }; + const params = { + timestamp: Date.now(), + groupId, + }; - const requestKeysMessage = new MediumGroupRequestKeysMessage(params); - const sender = new PubKey(senderIdentity); - // tslint:disable-next-line no-floating-promises - libsession.getMessageQueue().send(sender, requestKeysMessage); + const requestKeysMessage = new MediumGroupRequestKeysMessage(params); + const sender = new PubKey(senderIdentity); + void getMessageQueue().send(sender, requestKeysMessage); - return; + return; + } } let errorToThrow = error; diff --git a/ts/receiver/dataMessage.ts b/ts/receiver/dataMessage.ts index ea8049a71..31f3f7cbf 100644 --- a/ts/receiver/dataMessage.ts +++ b/ts/receiver/dataMessage.ts @@ -279,7 +279,10 @@ export async function handleDataMessage( envelope: EnvelopePlus, dataMessage: SignalService.IDataMessage ): Promise { - window.log.info('data message from', getEnvelopeId(envelope)); + window.log.info( + 'data message from', + getEnvelopeId(envelope) + ); if (dataMessage.mediumGroupUpdate) { await handleMediumGroupUpdate(envelope, dataMessage.mediumGroupUpdate); diff --git a/ts/receiver/mediumGroups.ts b/ts/receiver/mediumGroups.ts index c3312c1df..74eaaebc3 100644 --- a/ts/receiver/mediumGroups.ts +++ b/ts/receiver/mediumGroups.ts @@ -243,10 +243,10 @@ function sanityCheckMediumGroupUpdate( const joining = diff.joiningMembers || []; const leaving = diff.leavingMembers || []; - // 1. When there are no member changes, we don't expect any sender keys + // 1. When there are no member changes, we expect all sender keys if (!joining.length && !leaving.length) { - if (groupUpdate.senderKeys.length) { - window.log.error('Unexpected sender keys in group update'); + if (groupUpdate.senderKeys.length !== groupUpdate.members.length) { + window.log.error('Incorrect number of sender keys in group update'); } } @@ -270,13 +270,10 @@ async function handleMediumGroupChange( envelope: EnvelopePlus, groupUpdate: SignalService.MediumGroupUpdate ) { - const senderIdentity = envelope.source; - const { name, groupPublicKey, members: membersBinary, - admins: adminsBinary, senderKeys, } = groupUpdate; const { log } = window; @@ -342,6 +339,7 @@ async function handleMediumGroupChange( convo.set('isKickedFromGroup', true); // Disable typing: convo.updateTextInputState(); + window.SwarmPolling.removePubkey(groupId); } await convo.commit(); diff --git a/ts/session/medium_group/index.ts b/ts/session/medium_group/index.ts index aeb72d767..51324250f 100644 --- a/ts/session/medium_group/index.ts +++ b/ts/session/medium_group/index.ts @@ -208,19 +208,18 @@ export async function leaveMediumGroup(groupId: string) { sent_at: now, received_at: now, }); + const ourPrimary = await UserUtil.getPrimary(); + const members = convo.get('members').filter(m => m !== ourPrimary.key); // do not include senderkey as everyone needs to generate new one const groupUpdate: GroupInfo = { id: convo.get('id'), name: convo.get('name'), - members: convo.get('members'), + members, is_medium_group: true, admins: convo.get('groupAdmins'), - senderKeysContainer: undefined, }; - const ourPrimary = await UserUtil.getPrimary(); - await sendGroupUpdateForMedium( { leavingMembers: [ourPrimary.key] }, groupUpdate, @@ -260,24 +259,23 @@ async function getExistingSenderKeysForGroup( return maybeKeys.filter(d => d !== null).map(d => d as RatchetState); } -// Create all sender keys based on the changes in -// the group's composition -async function getOrCreateSenderKeysForUpdate( +// Get a list of senderKeys we have to send to joining members +// Basically this is the senderkey of all members who joined concatenated with +// the one of members currently in the group. + +// Also, the list of senderkeys for existing member must be empty if there is any leaving members, +// as they each member need to regenerate a new senderkey +async function getOrUpdateSenderKeysForJoiningMembers( groupId: string, members: Array, - changes: MemberChanges -): Promise { - // 1. Create sender keys for every joining member - const joining = changes.joiningMembers || []; - const leaving = changes.leavingMembers || []; - - let newKeys = await createSenderKeysForMembers(groupId, joining); - - // 2. Get ratchet states for existing members + diff?: GroupDiff, + joiningMembersSenderKeys?: Array +): Promise> { + const leavingMembers = diff?.leavingMembers || []; + const joiningMembers = diff?.joiningMembers || []; - const existingMembers = _.difference(members, joining); + const existingMembers = _.difference(members, joiningMembers); // get all devices for members - const allDevices = _.flatten( await Promise.all( existingMembers.map(m => MultiDeviceProtocol.getAllDevices(m)) @@ -285,23 +283,10 @@ async function getOrCreateSenderKeysForUpdate( ); let existingKeys: Array = []; - - if (leaving.length > 0) { - // If we have leaving members, we have to re-generate ratchet - // keys for existing members - const otherKeys = await Promise.all( - allDevices.map(async device => { - return createSenderKeyForGroup(groupId, PubKey.cast(device)); - }) - ); - - newKeys = _.union(newKeys, otherKeys); - } else { - // We can reuse existing keys + if (leavingMembers.length === 0) { existingKeys = await getExistingSenderKeysForGroup(groupId, allDevices); } - - return { existingKeys, newKeys }; + return _.union(joiningMembersSenderKeys, existingKeys); } async function getGroupSecretKey(groupId: string): Promise { @@ -322,6 +307,9 @@ async function getGroupSecretKey(groupId: string): Promise { } async function syncMediumGroup(group: ConversationModel) { + throw new Error( + 'Medium group syncing must be done once multi device is enabled back' + ); const ourPrimary = await UserUtil.getPrimary(); const groupId = group.get('id'); @@ -352,7 +340,6 @@ async function syncMediumGroup(group: ConversationModel) { members: group.get('members'), is_medium_group: true, admins: group.get('groupAdmins'), - senderKeysContainer, secretKey, }; @@ -418,12 +405,7 @@ export async function initiateGroupUpdate( }; if (isMediumGroup) { - // Send sender keys and group secret key - updateObj.senderKeysContainer = await getOrCreateSenderKeysForUpdate( - groupId, - members, - diff - ); + // Send group secret key const secretKey = await getGroupSecretKey(groupId); updateObj.secretKey = secretKey; @@ -531,7 +513,6 @@ interface GroupInfo { blocked?: boolean; admins?: Array; secretKey?: Uint8Array; - senderKeysContainer?: SenderKeysContainer; } interface UpdatableGroupState { @@ -605,15 +586,16 @@ export function calculateGroupDiff( return groupDiff; } -async function sendGroupUpdateForExistingMembers( - leavingMembers: Array, - remainingMembers: Array, +async function sendGroupUpdateForMedium( + diff: MemberChanges, groupUpdate: GroupInfo, messageId?: string ) { const { id: groupId, members, name: groupName } = groupUpdate; const ourPrimary = await UserUtil.getPrimary(); + const leavingMembers = diff.leavingMembers || []; + const joiningMembers = diff.joiningMembers || []; const wasAnyUserRemoved = leavingMembers.length > 0; const isUserLeaving = leavingMembers.includes(ourPrimary.key); @@ -626,10 +608,7 @@ async function sendGroupUpdateForExistingMembers( (pkHex: string) => new Uint8Array(fromHex(pkHex)) ); - // Existing members only receive new sender keys - const senderKeys = groupUpdate.senderKeysContainer - ? groupUpdate.senderKeysContainer.newKeys - : []; + const remainingMembers = _.difference(groupUpdate.members, joiningMembers); const params = { timestamp: Date.now(), @@ -638,7 +617,6 @@ async function sendGroupUpdateForExistingMembers( members: membersBin, groupName, admins: adminsBin, - senderKeys: senderKeys, }; if (wasAnyUserRemoved) { @@ -652,116 +630,101 @@ async function sendGroupUpdateForExistingMembers( senderKeys: [], }; - const messageStripped = new MediumGroupUpdateMessage(paramsWithoutSenderKeys); + const messageStripped = new MediumGroupUpdateMessage( + paramsWithoutSenderKeys + ); window.log.warn('Sending to groupUpdateMessage without senderKeys'); await getMessageQueue().sendToGroup(messageStripped); - // TODO Delete all ratchets (it's important that this happens * after * sending out the update) - if (isUserLeaving) { - // nothing to do on desktop - } else { - // Send out the user's new ratchet to all members (minus the removed ones) using established channels - const ourPrimaryKey = new Uint8Array(fromHex(ourPrimary.key)) - const ourNewSenderKey = senderKeys.find(s => _.isEqual(s.pubKey, ourPrimaryKey)); - - if (! ourNewSenderKey) { - window.console.warn('We need to share our senderkey with remaining member but our senderKeys was not given.'); - } else { - window.log.warn('Sharing our new senderKey with remainingMembers via message', remainingMembers, ourNewSenderKey); - await shareSenderKeys(groupId, remainingMembers, ourNewSenderKey); + getMessageQueue().events.addListener('success', async message => { + if (message.identifier === params.identifier) { + // console.log('Our first message encrypted with old sk is sent.'); + // TODO Delete all ratchets (it's important that this happens * after * sending out the update) + if (isUserLeaving) { + // nothing to do on desktop + } else { + // Send out the user's new ratchet to all members (minus the removed ones) using established channels + const userSenderKey = await createSenderKeyForGroup( + groupId, + ourPrimary + ); + window.log.warn( + 'Sharing our new senderKey with remainingMembers via message', + remainingMembers, + userSenderKey + ); + + await shareSenderKeys(groupId, remainingMembers, userSenderKey); + } } - - } - } else { - const message = new MediumGroupUpdateMessage(params); - window.log.warn('Sending to groupUpdateMessage with senderKeys to groupAddress', senderKeys); - - await getMessageQueue().sendToGroup(message); - } - -} - -async function sendGroupUpdateForJoiningMembers( - recipients: Array, - groupUpdate: GroupInfo, - messageId?: string -) { - const { id: groupId, name, members } = groupUpdate; - - const now = Date.now(); - - const { secretKey, senderKeysContainer } = groupUpdate; - - if (!secretKey) { - window.log.error('Group secret key not specified, aborting...'); - return; - } - - let senderKeys: Array = []; - if (!senderKeysContainer) { - window.log.warn('Sender keys for joining members not found'); + }); } else { - // Joining members should receive all known sender keys - senderKeys = _.union( - senderKeysContainer.existingKeys, - senderKeysContainer.newKeys - ); - } - - const membersBin = members.map( - (pkHex: string) => new Uint8Array(fromHex(pkHex)) - ); - - const admins = groupUpdate.admins || []; - - const adminsBin = admins.map( - (pkHex: string) => new Uint8Array(fromHex(pkHex)) - ); - - const createParams = { - timestamp: now, - groupId, - identifier: messageId || uuid(), - groupSecretKey: secretKey, - members: membersBin, - groupName: name, - admins: adminsBin, - senderKeys, - }; - - const mediumGroupCreateMessage = new MediumGroupCreateMessage(createParams); - - recipients.forEach(async member => { - const memberPubKey = new PubKey(member); - await getMessageQueue().sendUsingMultiDevice( - memberPubKey, - mediumGroupCreateMessage + let senderKeys: Array; + if (joiningMembers.length > 0) { + // Generate ratchets for any new members + senderKeys = await createSenderKeysForMembers(groupId, joiningMembers); + } else { + // It's not a member change, maybe an name change. So just reuse all senderkeys + senderKeys = await getOrUpdateSenderKeysForJoiningMembers( + groupId, + members + ); + } + const paramsWithSenderKeys = { + ...params, + senderKeys, + }; + // Send a closed group update message to the existing members with the new members' ratchets (this message is aimed at the group) + const message = new MediumGroupUpdateMessage(paramsWithSenderKeys); + window.log.warn( + 'Sending to groupUpdateMessage with joining members senderKeys to groupAddress', + senderKeys ); - }); -} -async function sendGroupUpdateForMedium( - diff: MemberChanges, - groupUpdate: GroupInfo, - messageId?: string -) { - const joining = diff.joiningMembers || []; - const leaving = diff.leavingMembers || []; + await getMessageQueue().sendToGroup(message); - // 1. create group for all joining members (send timeout timer if necessary) - if (joining.length) { - await sendGroupUpdateForJoiningMembers(joining, groupUpdate, messageId); - } + // now send a CREATE group message with all senderkeys no matter what to all joining members, using established channels + if (joiningMembers.length) { + const { secretKey } = groupUpdate; - // 2. send group update to all other members - const others = _.difference(groupUpdate.members, joining); - if (others.length) { - await sendGroupUpdateForExistingMembers( - leaving, - others, - groupUpdate, - messageId - ); + if (!secretKey) { + window.log.error('Group secret key not specified, aborting...'); + return; + } + const allSenderKeys = await getOrUpdateSenderKeysForJoiningMembers( + groupId, + members + ); + + const createParams = { + timestamp: Date.now(), + identifier: messageId || uuid(), + groupSecretKey: secretKey, + groupId, + members: membersBin, + groupName, + admins: adminsBin, + senderKeys: allSenderKeys, + }; + + const mediumGroupCreateMessage = new MediumGroupCreateMessage( + createParams + ); + // console.warn( + // 'sending group create to', + // joiningMembers, + // ' obj: ', + // mediumGroupCreateMessage + // ); + + joiningMembers.forEach(async member => { + const memberPubKey = new PubKey(member); + await getMessageQueue().sendUsingMultiDevice( + memberPubKey, + mediumGroupCreateMessage + ); + }); + } } } diff --git a/ts/session/medium_group/ratchet.ts b/ts/session/medium_group/ratchet.ts index 223a16266..e64fd5ffd 100644 --- a/ts/session/medium_group/ratchet.ts +++ b/ts/session/medium_group/ratchet.ts @@ -14,12 +14,15 @@ async function queueJobForNumber(number: string, runJob: any) { const runCurrent = runPrevious.then(runJob, runJob); jobQueue[number] = runCurrent; // tslint:disable-next-line no-floating-promises - runCurrent.then(() => { - if (jobQueue[number] === runCurrent) { - // tslint:disable-next-line no-dynamic-delete - delete jobQueue[number]; - } - }); + runCurrent + .then(() => { + if (jobQueue[number] === runCurrent) { + // tslint:disable-next-line no-dynamic-delete + delete jobQueue[number]; + } + }).catch((e: any) => { + window.log.error('queueJobForNumber() Caught error', e); + }); return runCurrent; } diff --git a/ts/session/sending/MessageQueue.ts b/ts/session/sending/MessageQueue.ts index 4cddd8e73..ca495f63c 100644 --- a/ts/session/sending/MessageQueue.ts +++ b/ts/session/sending/MessageQueue.ts @@ -7,6 +7,7 @@ import { ClosedGroupMessage, ContentMessage, ExpirationTimerUpdateMessage, + MediumGroupChatMessage, MediumGroupMessage, OpenGroupMessage, SessionRequestMessage, @@ -102,8 +103,10 @@ export class MessageQueue implements MessageQueueInterface { throw new Error('Invalid group message passed in sendToGroup.'); } // if this is a medium group message. We just need to send to the group pubkey - if (message instanceof MediumGroupMessage) { - window.log.warn('sending medium ', message, ' to ', groupId) + if ( + message instanceof MediumGroupMessage || + message instanceof MediumGroupChatMessage + ) { return this.send(PubKey.cast(groupId), message); } diff --git a/ts/session/sending/MessageQueueInterface.ts b/ts/session/sending/MessageQueueInterface.ts index af44e6e7f..596b84bea 100644 --- a/ts/session/sending/MessageQueueInterface.ts +++ b/ts/session/sending/MessageQueueInterface.ts @@ -9,7 +9,10 @@ import { RawMessage } from '../types/RawMessage'; import { TypedEventEmitter } from '../utils'; import { PubKey } from '../types'; -type GroupMessageType = OpenGroupMessage | ClosedGroupMessage | MediumGroupMessage; +type GroupMessageType = + | OpenGroupMessage + | ClosedGroupMessage + | MediumGroupMessage; export interface MessageQueueInterfaceEvents { success: ( diff --git a/ts/session/sending/MessageSender.ts b/ts/session/sending/MessageSender.ts index 6341274d7..374fc2d10 100644 --- a/ts/session/sending/MessageSender.ts +++ b/ts/session/sending/MessageSender.ts @@ -45,6 +45,7 @@ export async function send( timestamp, cipherText ); + // console.warn('sending', envelope, ' to ', device.key); const data = wrapEnvelope(envelope); return pRetry( diff --git a/ts/session/utils/Messages.ts b/ts/session/utils/Messages.ts index 5169afdd7..3d84b0c59 100644 --- a/ts/session/utils/Messages.ts +++ b/ts/session/utils/Messages.ts @@ -6,6 +6,7 @@ import { } 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( device: PubKey, @@ -16,7 +17,10 @@ export async function toRawMessage( const plainTextBuffer = message.plainTextBuffer(); let encryption: EncryptionType; - if (message instanceof MediumGroupChatMessage) { + if ( + message instanceof MediumGroupChatMessage || + message instanceof MediumGroupUpdateMessage + ) { encryption = EncryptionType.MediumGroup; } else if (message instanceof SessionRequestMessage) { encryption = EncryptionType.Fallback; From 5d7d66f65a8a7a5f3aa2ee456372f425a392bf44 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 1 Oct 2020 15:13:06 +1000 Subject: [PATCH 11/20] fix bug making session request being sent for medium group create --- ts/receiver/mediumGroups.ts | 2 +- ts/session/medium_group/index.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ts/receiver/mediumGroups.ts b/ts/receiver/mediumGroups.ts index 74eaaebc3..db6dd0d1d 100644 --- a/ts/receiver/mediumGroups.ts +++ b/ts/receiver/mediumGroups.ts @@ -323,7 +323,7 @@ async function handleMediumGroupChange( const primary = await UserUtil.getPrimary(); sanityCheckMediumGroupUpdate(primary, diff, groupUpdate); - + // console.log(`Got group update`, groupUpdate); await saveIncomingRatchetKeys(groupId, senderKeys); // Only add update message if we have something to show diff --git a/ts/session/medium_group/index.ts b/ts/session/medium_group/index.ts index 51324250f..0d899a9fe 100644 --- a/ts/session/medium_group/index.ts +++ b/ts/session/medium_group/index.ts @@ -118,12 +118,14 @@ export async function createMediumGroup( const dbMessage = await addUpdateMessage(convo, groupDiff, 'outgoing'); + // be sure to call this before sending the message. + // the sending pipeline needs to know from GroupUtils when a message is for a medium group + await updateOrCreateGroup(groupDetails); + await sendGroupUpdate(convo, groupDiff, groupDetails, dbMessage.id); // ***** 3. Add update message to the conversation ***** - await updateOrCreateGroup(groupDetails); - convo.updateGroupAdmins(admins); window.owsDesktopApp.appView.openConversation(groupId, {}); From e0e0830d3b5b1f0ffa865cfc69e7fec39a18a765 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 1 Oct 2020 16:13:26 +1000 Subject: [PATCH 12/20] remove all ratchet when needed to get fresh senderKey from members --- app/sql.js | 7 +++++++ js/modules/data.d.ts | 1 + js/modules/data.js | 5 +++++ .../session/LeftPaneMessageSection.tsx | 17 ++++------------- ts/receiver/contentMessage.ts | 3 +-- ts/receiver/dataMessage.ts | 5 +---- ts/receiver/mediumGroups.ts | 17 ++++++++++++++++- ts/session/medium_group/index.ts | 3 ++- ts/session/medium_group/ratchet.ts | 3 ++- 9 files changed, 39 insertions(+), 22 deletions(-) diff --git a/app/sql.js b/app/sql.js index 276746a25..7b5fa4363 100644 --- a/app/sql.js +++ b/app/sql.js @@ -171,6 +171,7 @@ module.exports = { getSenderKeys, createOrUpdateSenderKeys, + removeAllClosedGroupRatchets, }; function generateUUID() { @@ -923,6 +924,12 @@ async function createOrUpdateSenderKeys(data) { ); } +async function removeAllClosedGroupRatchets(groupId) { + await db.run(`DELETE FROM ${SENDER_KEYS_TABLE} WHERE groupId = $groupId;`, { + $groupId: groupId, + }); +} + async function updateToLokiSchemaVersion4(currentVersion, instance) { if (currentVersion >= 4) { return; diff --git a/js/modules/data.d.ts b/js/modules/data.d.ts index 0b344b121..b19fb4c5a 100644 --- a/js/modules/data.d.ts +++ b/js/modules/data.d.ts @@ -412,3 +412,4 @@ export function getMessagesWithFileAttachments( // Sender Keys export function getSenderKeys(groupId: any, senderIdentity: any): Promise; export function createOrUpdateSenderKeys(data: any): Promise; +export function removeAllClosedGroupRatchets(groupId: string): Promise; diff --git a/js/modules/data.js b/js/modules/data.js index 8111770f5..c135bf44c 100644 --- a/js/modules/data.js +++ b/js/modules/data.js @@ -196,6 +196,7 @@ module.exports = { getSenderKeys, createOrUpdateSenderKeys, + removeAllClosedGroupRatchets, }; function init() { @@ -704,6 +705,10 @@ async function createOrUpdateSenderKeys(data) { await channels.createOrUpdateSenderKeys(data); } +async function removeAllClosedGroupRatchets(groupId) { + await channels.removeAllClosedGroupRatchets(groupId); +} + // Sessions async function createOrUpdateSession(data) { diff --git a/ts/components/session/LeftPaneMessageSection.tsx b/ts/components/session/LeftPaneMessageSection.tsx index 2e3d525e2..e426099a1 100644 --- a/ts/components/session/LeftPaneMessageSection.tsx +++ b/ts/components/session/LeftPaneMessageSection.tsx @@ -307,12 +307,7 @@ export class LeftPaneMessageSection extends React.Component { onButtonClick={async ( groupName: string, groupMembers: Array - ) => - this.onCreateClosedGroup( - groupName, - groupMembers - ) - } + ) => this.onCreateClosedGroup(groupName, groupMembers)} searchTerm={searchTerm} updateSearch={this.updateSearchBound} showSpinner={loading} @@ -497,13 +492,9 @@ export class LeftPaneMessageSection extends React.Component { groupName: string, groupMembers: Array ) { - await MainViewController.createClosedGroup( - groupName, - groupMembers, - () => { - this.handleToggleOverlay(undefined); - } - ); + await MainViewController.createClosedGroup(groupName, groupMembers, () => { + this.handleToggleOverlay(undefined); + }); } private handleNewSessionButtonClick() { diff --git a/ts/receiver/contentMessage.ts b/ts/receiver/contentMessage.ts index b32505438..5a6a61ca4 100644 --- a/ts/receiver/contentMessage.ts +++ b/ts/receiver/contentMessage.ts @@ -19,7 +19,6 @@ import { BlockedNumberController } from '../util/blockedNumberController'; import { decryptWithSenderKey } from '../session/medium_group/ratchet'; import { StringUtils } from '../session/utils'; import { UserUtil } from '../util'; -import { getMessageQueue } from '../session'; export async function handleContentMessage(envelope: EnvelopePlus) { try { @@ -310,7 +309,7 @@ async function decrypt( const requestKeysMessage = new MediumGroupRequestKeysMessage(params); const sender = new PubKey(senderIdentity); - void getMessageQueue().send(sender, requestKeysMessage); + void libsession.getMessageQueue().send(sender, requestKeysMessage); return; } diff --git a/ts/receiver/dataMessage.ts b/ts/receiver/dataMessage.ts index 31f3f7cbf..ea8049a71 100644 --- a/ts/receiver/dataMessage.ts +++ b/ts/receiver/dataMessage.ts @@ -279,10 +279,7 @@ export async function handleDataMessage( envelope: EnvelopePlus, dataMessage: SignalService.IDataMessage ): Promise { - window.log.info( - 'data message from', - getEnvelopeId(envelope) - ); + window.log.info('data message from', getEnvelopeId(envelope)); if (dataMessage.mediumGroupUpdate) { await handleMediumGroupUpdate(envelope, dataMessage.mediumGroupUpdate); diff --git a/ts/receiver/mediumGroups.ts b/ts/receiver/mediumGroups.ts index db6dd0d1d..5c1cffd52 100644 --- a/ts/receiver/mediumGroups.ts +++ b/ts/receiver/mediumGroups.ts @@ -13,7 +13,10 @@ import { BufferType } from '../session/utils/String'; import { MultiDeviceProtocol } from '../session/protocols'; import { ConversationModel } from '../../js/models/conversations'; import { UserUtil } from '../util'; -import { RatchetState } from '../session/medium_group/senderKeys'; +import { + createSenderKeyForGroup, + RatchetState, +} from '../session/medium_group/senderKeys'; const toHex = (d: BufferType) => StringUtils.decode(d, 'hex'); const fromHex = (d: string) => StringUtils.encode(d, 'hex'); @@ -344,6 +347,18 @@ async function handleMediumGroupChange( await convo.commit(); + if (diff.leavingMembers && diff.leavingMembers.length > 0) { + // Send out the user's new ratchet to all members (minus the removed ones) using established channels + const userSenderKey = await createSenderKeyForGroup(groupId, primary); + window.log.warn( + 'Sharing our new senderKey with remainingMembers via message', + members, + userSenderKey + ); + + await shareSenderKeys(groupId, members, userSenderKey); + } + await removeFromCache(envelope); } diff --git a/ts/session/medium_group/index.ts b/ts/session/medium_group/index.ts index 0d899a9fe..5a79c84b2 100644 --- a/ts/session/medium_group/index.ts +++ b/ts/session/medium_group/index.ts @@ -641,7 +641,8 @@ async function sendGroupUpdateForMedium( getMessageQueue().events.addListener('success', async message => { if (message.identifier === params.identifier) { // console.log('Our first message encrypted with old sk is sent.'); - // TODO Delete all ratchets (it's important that this happens * after * sending out the update) + // Delete all ratchets (it's important that this happens * after * sending out the update) + await Data.removeAllClosedGroupRatchets(groupId); if (isUserLeaving) { // nothing to do on desktop } else { diff --git a/ts/session/medium_group/ratchet.ts b/ts/session/medium_group/ratchet.ts index e64fd5ffd..4b3c35dbd 100644 --- a/ts/session/medium_group/ratchet.ts +++ b/ts/session/medium_group/ratchet.ts @@ -20,7 +20,8 @@ async function queueJobForNumber(number: string, runJob: any) { // tslint:disable-next-line no-dynamic-delete delete jobQueue[number]; } - }).catch((e: any) => { + }) + .catch((e: any) => { window.log.error('queueJobForNumber() Caught error', e); }); return runCurrent; From a05b6556b0268c17e6207f8ea8b4030f3e2f1338 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 1 Oct 2020 16:16:46 +1000 Subject: [PATCH 13/20] remove unused handleQuit for medium groups --- ts/receiver/mediumGroups.ts | 63 ------------------------------------- 1 file changed, 63 deletions(-) diff --git a/ts/receiver/mediumGroups.ts b/ts/receiver/mediumGroups.ts index 5c1cffd52..28b14dacf 100644 --- a/ts/receiver/mediumGroups.ts +++ b/ts/receiver/mediumGroups.ts @@ -362,69 +362,6 @@ async function handleMediumGroupChange( await removeFromCache(envelope); } -async function handleQuit( - envelope: EnvelopePlus, - groupUpdate: SignalService.MediumGroupUpdate -) { - const quitter = envelope.source; - const groupId = toHex(groupUpdate.groupPublicKey); - - const quitterPrimary = await MultiDeviceProtocol.getPrimaryDevice(quitter); - - const maybeConvo = await window.ConversationController.get(groupId); - - if (!maybeConvo) { - window.log.warn('Received QUIT for a non-existing medium group'); - await removeFromCache(envelope); - return; - } - - const convo = maybeConvo; - - // 1. Remove primary device from members: - - const members = convo.get('members'); - - const membersUpdated = _.without(members, quitterPrimary.key); - - convo.set({ members: membersUpdated }); - - convo.commit(); - - // 2. Show message (device left the group); - - await SenderKeyAPI.addUpdateMessage( - convo, - { leavingMembers: [quitterPrimary.key] }, - 'incoming' - ); - - const ourNumber = (await UserUtil.getCurrentDevicePubKey()) as string; - const primary = await UserUtil.getPrimary(); - - if (quitterPrimary.key === primary.key) { - convo.set('isKickedFromGroup', true); - // Disable typing: - convo.updateTextInputState(); - await convo.commit(); - - await removeFromCache(envelope); - return; - } - - // 3. update your own sender key - const senderKey = await SenderKeyAPI.createSenderKeyForGroup( - groupId, - PubKey.cast(ourNumber) - ); - - // Send keys in the background - // tslint:disable-next-line no-floating-promises - shareSenderKeys(groupId, membersUpdated, senderKey); - - await removeFromCache(envelope); -} - export async function handleMediumGroupUpdate( envelope: EnvelopePlus, groupUpdate: any From cfb9e65f30b38240220766dc4d95329ee0137c5b Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Fri, 2 Oct 2020 14:27:46 +1000 Subject: [PATCH 14/20] trigger new ratchet send on message sent success send our new ratchet to all new members when we just sent a message containing no senderKey in it. an set but empty senderKey array should only be sent when a user left or was removed. --- js/models/messages.js | 42 ++++++++++++++++++++++++++- ts/receiver/mediumGroups.ts | 19 +----------- ts/session/index.ts | 3 +- ts/session/medium_group/index.ts | 30 ++----------------- ts/session/medium_group/senderKeys.ts | 19 ++++++++++++ 5 files changed, 65 insertions(+), 48 deletions(-) diff --git a/js/models/messages.js b/js/models/messages.js index d762d95c3..e9997fdcf 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -1238,7 +1238,7 @@ const isOurDevice = await window.libsession.Protocols.MultiDeviceProtocol.isOurDevice( sentMessage.device ); - + // FIXME this is not correct and will cause issues with syncing // At this point the only way to check for medium // group is by comparing the encryption type const isMediumGroupMessage = @@ -1272,6 +1272,46 @@ sentMessage.device ); + if (isMediumGroupMessage) { + // Delete all ratchets (it's important that this happens * after * sending out the update) + const shouldTriggerRatchetReset = + dataMessage && + dataMessage.mediumGroupUpdate && + dataMessage.mediumGroupUpdate.senderKeys && + dataMessage.mediumGroupUpdate.senderKeys.length === 0; + if (shouldTriggerRatchetReset) { + const { groupPublicKey } = dataMessage.mediumGroupUpdate; + const groupPubKeyUint = new Uint8Array( + groupPublicKey.toArrayBuffer() + ); + const groupId = libsession.Utils.StringUtils.decode( + groupPubKeyUint, + 'hex' + ); + await window.Signal.Data.removeAllClosedGroupRatchets(groupId); + // Send out the user's new ratchet to all members (minus the removed ones) using established channels + const ourPrimary = await window.Signal.Util.UserUtil.getPrimary(); + + const userSenderKey = await window.libsession.MediumGroup.createSenderKeyForGroup( + groupId, + ourPrimary + ); + + const currentMembers = this.getConversation().get('members'); + + window.log.warn( + 'Sharing our new senderKey with remainingMembers via established channels with', + currentMembers + ); + + await window.libsession.MediumGroup.shareSenderKeys( + groupId, + currentMembers, + userSenderKey + ); + } + } + /** * We should hit the notify endpoint for push notification only if: * • It's a one-to-one chat or a closed group diff --git a/ts/receiver/mediumGroups.ts b/ts/receiver/mediumGroups.ts index 28b14dacf..ddb3849b8 100644 --- a/ts/receiver/mediumGroups.ts +++ b/ts/receiver/mediumGroups.ts @@ -10,12 +10,12 @@ import * as SenderKeyAPI from '../session/medium_group'; import { getChainKey } from '../session/medium_group/ratchet'; import { StringUtils } from '../session/utils'; import { BufferType } from '../session/utils/String'; -import { MultiDeviceProtocol } from '../session/protocols'; import { ConversationModel } from '../../js/models/conversations'; import { UserUtil } from '../util'; import { createSenderKeyForGroup, RatchetState, + shareSenderKeys, } from '../session/medium_group/senderKeys'; const toHex = (d: BufferType) => StringUtils.decode(d, 'hex'); @@ -70,23 +70,6 @@ async function handleSenderKeyRequest( await removeFromCache(envelope); } -export async function shareSenderKeys( - groupId: string, - recipientsPrimary: Array, - senderKey: RatchetState -) { - const message = new MediumGroupResponseKeysMessage({ - timestamp: Date.now(), - groupId, - senderKey, - }); - - const recipients = recipientsPrimary.map(pk => PubKey.cast(pk)); - await Promise.all( - recipients.map(pk => getMessageQueue().sendUsingMultiDevice(pk, message)) - ); -} - async function handleSenderKey( envelope: EnvelopePlus, groupUpdate: SignalService.MediumGroupUpdate diff --git a/ts/session/index.ts b/ts/session/index.ts index c2a115733..1db6e3736 100644 --- a/ts/session/index.ts +++ b/ts/session/index.ts @@ -4,7 +4,8 @@ import * as Types from './types'; import * as Utils from './utils'; import * as Sending from './sending'; import * as Constants from './constants'; +import * as MediumGroup from './medium_group'; export * from './instance'; -export { Messages, Utils, Protocols, Types, Sending, Constants }; +export { Messages, Utils, Protocols, Types, Sending, Constants, MediumGroup }; diff --git a/ts/session/medium_group/index.ts b/ts/session/medium_group/index.ts index 5a79c84b2..ac00cfc7b 100644 --- a/ts/session/medium_group/index.ts +++ b/ts/session/medium_group/index.ts @@ -8,6 +8,7 @@ import { RatchetState, saveSenderKeys, saveSenderKeysInner, + shareSenderKeys, } from './senderKeys'; import { getChainKey } from './ratchet'; import { MultiDeviceProtocol } from '../protocols'; @@ -20,7 +21,6 @@ import { ExpirationTimerUpdateMessage, MediumGroupCreateMessage, MediumGroupMessage, - Message, } from '../messages/outgoing'; import { MessageModel, MessageModelType } from '../../../js/models/messages'; import { getMessageQueue } from '../../session'; @@ -28,13 +28,13 @@ import { ConversationModel } from '../../../js/models/conversations'; import { MediumGroupUpdateMessage } from '../messages/outgoing/content/data/mediumgroup/MediumGroupUpdateMessage'; import uuid from 'uuid'; import { BlockedNumberController } from '../../util/blockedNumberController'; -import { shareSenderKeys } from '../../receiver/mediumGroups'; export { createSenderKeyForGroup, saveSenderKeys, saveSenderKeysInner, getChainKey, + shareSenderKeys, }; const toHex = (d: BufferType) => StringUtils.decode(d, 'hex'); @@ -610,8 +610,6 @@ async function sendGroupUpdateForMedium( (pkHex: string) => new Uint8Array(fromHex(pkHex)) ); - const remainingMembers = _.difference(groupUpdate.members, joiningMembers); - const params = { timestamp: Date.now(), identifier: messageId || uuid(), @@ -637,30 +635,6 @@ async function sendGroupUpdateForMedium( ); window.log.warn('Sending to groupUpdateMessage without senderKeys'); await getMessageQueue().sendToGroup(messageStripped); - - getMessageQueue().events.addListener('success', async message => { - if (message.identifier === params.identifier) { - // console.log('Our first message encrypted with old sk is sent.'); - // Delete all ratchets (it's important that this happens * after * sending out the update) - await Data.removeAllClosedGroupRatchets(groupId); - if (isUserLeaving) { - // nothing to do on desktop - } else { - // Send out the user's new ratchet to all members (minus the removed ones) using established channels - const userSenderKey = await createSenderKeyForGroup( - groupId, - ourPrimary - ); - window.log.warn( - 'Sharing our new senderKey with remainingMembers via message', - remainingMembers, - userSenderKey - ); - - await shareSenderKeys(groupId, remainingMembers, userSenderKey); - } - } - }); } else { let senderKeys: Array; if (joiningMembers.length > 0) { diff --git a/ts/session/medium_group/senderKeys.ts b/ts/session/medium_group/senderKeys.ts index 5c645e557..b48a2f17b 100644 --- a/ts/session/medium_group/senderKeys.ts +++ b/ts/session/medium_group/senderKeys.ts @@ -1,6 +1,8 @@ import { PubKey } from '../types'; import { StringUtils } from '../utils'; import * as Data from '../../../js/modules/data'; +import { MediumGroupResponseKeysMessage } from '../messages/outgoing'; +import { getMessageQueue } from '..'; const toHex = (buffer: ArrayBuffer) => StringUtils.decode(buffer, 'hex'); const fromHex = (hex: string) => StringUtils.encode(hex, 'hex'); @@ -86,3 +88,20 @@ export async function saveSenderKeys( messageKeys ); } + +export async function shareSenderKeys( + groupId: string, + recipientsPrimary: Array, + senderKey: RatchetState +) { + const message = new MediumGroupResponseKeysMessage({ + timestamp: Date.now(), + groupId, + senderKey, + }); + + const recipients = recipientsPrimary.map(pk => PubKey.cast(pk)); + await Promise.all( + recipients.map(pk => getMessageQueue().sendUsingMultiDevice(pk, message)) + ); +} From 3fa1a8f2b50c0a4ecbe56cc8b0b0aa8b15278fb3 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Fri, 2 Oct 2020 15:29:43 +1000 Subject: [PATCH 15/20] mark medium group as not left if we get a message INFO adding us back --- ts/receiver/mediumGroups.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ts/receiver/mediumGroups.ts b/ts/receiver/mediumGroups.ts index ddb3849b8..a0fe3eb75 100644 --- a/ts/receiver/mediumGroups.ts +++ b/ts/receiver/mediumGroups.ts @@ -326,11 +326,18 @@ async function handleMediumGroupChange( // Disable typing: convo.updateTextInputState(); window.SwarmPolling.removePubkey(groupId); + } else { + if (maybeConvo.get('isKickedFromGroup')) { + // Enable typing: + maybeConvo.set('isKickedFromGroup', false); + maybeConvo.set('left', false); + maybeConvo.updateTextInputState(); + } } await convo.commit(); - if (diff.leavingMembers && diff.leavingMembers.length > 0) { + if (diff.leavingMembers && diff.leavingMembers.length > 0 && !areWeKicked) { // Send out the user's new ratchet to all members (minus the removed ones) using established channels const userSenderKey = await createSenderKeyForGroup(groupId, primary); window.log.warn( From 05e682cdc2d795c9fd8cb093610c1125197f5e0c Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Mon, 5 Oct 2020 10:53:09 +1100 Subject: [PATCH 16/20] enable back swarm polling when we get added back to a group --- ts/receiver/mediumGroups.ts | 5 +++-- ts/session/snode_api/swarmPolling.ts | 8 ++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/ts/receiver/mediumGroups.ts b/ts/receiver/mediumGroups.ts index a0fe3eb75..7c9582b3a 100644 --- a/ts/receiver/mediumGroups.ts +++ b/ts/receiver/mediumGroups.ts @@ -331,6 +331,8 @@ async function handleMediumGroupChange( // Enable typing: maybeConvo.set('isKickedFromGroup', false); maybeConvo.set('left', false); + // Subscribe to this group id + window.SwarmPolling.addGroupId(new PubKey(groupId)); maybeConvo.updateTextInputState(); } } @@ -342,8 +344,7 @@ async function handleMediumGroupChange( const userSenderKey = await createSenderKeyForGroup(groupId, primary); window.log.warn( 'Sharing our new senderKey with remainingMembers via message', - members, - userSenderKey + members ); await shareSenderKeys(groupId, members, userSenderKey); diff --git a/ts/session/snode_api/swarmPolling.ts b/ts/session/snode_api/swarmPolling.ts index ac2eaacc7..0f3407c27 100644 --- a/ts/session/snode_api/swarmPolling.ts +++ b/ts/session/snode_api/swarmPolling.ts @@ -51,12 +51,16 @@ export class SwarmPolling { } public addGroupId(pubkey: PubKey) { - this.groupPubkeys.push(pubkey); + if (this.groupPubkeys.findIndex(m => m.key === pubkey.key) === -1) { + this.groupPubkeys.push(pubkey); + } } public addPubkey(pk: PubKey | string) { const pubkey = PubKey.cast(pk); - this.pubkeys.push(pubkey); + if (this.pubkeys.findIndex(m => m.key === pubkey.key) === -1) { + this.pubkeys.push(pubkey); + } } public removePubkey(pk: PubKey | string) { From c0497e3df179dedbf25420893ddc3fe98eb52853 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Mon, 5 Oct 2020 11:16:13 +1100 Subject: [PATCH 17/20] trigger senderKeyRequest if we get a DOMException on decryptGCM() --- ts/session/medium_group/ratchet.ts | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/ts/session/medium_group/ratchet.ts b/ts/session/medium_group/ratchet.ts index 4b3c35dbd..5808ad4bf 100644 --- a/ts/session/medium_group/ratchet.ts +++ b/ts/session/medium_group/ratchet.ts @@ -2,6 +2,8 @@ import { PubKey } from '../types'; import * as Data from '../../../js/modules/data'; import { saveSenderKeysInner } from './index'; import { StringUtils } from '../utils'; +import { MediumGroupRequestKeysMessage } from '../messages/outgoing'; +import { getMessageQueue } from '..'; const toHex = (buffer: ArrayBuffer) => StringUtils.decode(buffer, 'hex'); const fromHex = (hex: string) => StringUtils.encode(hex, 'hex'); @@ -269,11 +271,24 @@ async function decryptWithSenderKeyInner( return null; } - // TODO: this might fail, handle this - const plaintext = await window.libloki.crypto.DecryptGCM( - messageKey, - ciphertext - ); + try { + const plaintext = await window.libloki.crypto.DecryptGCM( + messageKey, + ciphertext + ); + return plaintext; + } catch (e) { + window.log.error('Got error during DecryptGCM():', e); + if (e instanceof DOMException) { + const params = { + timestamp: Date.now(), + groupId, + }; + // we consider we don't have the correct key for this sender, so request the latest one + const requestKeysMessage = new MediumGroupRequestKeysMessage(params); + const sender = new PubKey(senderIdentity); + await getMessageQueue().send(sender, requestKeysMessage); + } + } - return plaintext; } From 97eaf680859a072fb9c2f7ad77368b5c2653ce04 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Mon, 5 Oct 2020 11:29:02 +1100 Subject: [PATCH 18/20] clean getExistingSenderKeysForGroup as we don't need the diff anymore --- ts/session/medium_group/index.ts | 18 +++--------------- ts/session/medium_group/ratchet.ts | 1 - 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/ts/session/medium_group/index.ts b/ts/session/medium_group/index.ts index ac00cfc7b..4949066a4 100644 --- a/ts/session/medium_group/index.ts +++ b/ts/session/medium_group/index.ts @@ -269,26 +269,14 @@ async function getExistingSenderKeysForGroup( // as they each member need to regenerate a new senderkey async function getOrUpdateSenderKeysForJoiningMembers( groupId: string, - members: Array, - diff?: GroupDiff, - joiningMembersSenderKeys?: Array + members: Array ): Promise> { - const leavingMembers = diff?.leavingMembers || []; - const joiningMembers = diff?.joiningMembers || []; - - const existingMembers = _.difference(members, joiningMembers); // get all devices for members const allDevices = _.flatten( - await Promise.all( - existingMembers.map(m => MultiDeviceProtocol.getAllDevices(m)) - ) + await Promise.all(members.map(m => MultiDeviceProtocol.getAllDevices(m))) ); - let existingKeys: Array = []; - if (leavingMembers.length === 0) { - existingKeys = await getExistingSenderKeysForGroup(groupId, allDevices); - } - return _.union(joiningMembersSenderKeys, existingKeys); + return getExistingSenderKeysForGroup(groupId, allDevices); } async function getGroupSecretKey(groupId: string): Promise { diff --git a/ts/session/medium_group/ratchet.ts b/ts/session/medium_group/ratchet.ts index 5808ad4bf..5f9d81db9 100644 --- a/ts/session/medium_group/ratchet.ts +++ b/ts/session/medium_group/ratchet.ts @@ -290,5 +290,4 @@ async function decryptWithSenderKeyInner( await getMessageQueue().send(sender, requestKeysMessage); } } - } From 4215aa1e3bbdd8060f308dadc8679e870b3b7974 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Mon, 5 Oct 2020 11:39:00 +1100 Subject: [PATCH 19/20] require admin for editing a closed group, but not a medium one --- js/views/conversation_view.js | 6 +++++- js/views/create_group_dialog_view.js | 5 ++++- ts/receiver/groups.ts | 5 +++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index c2752c2bf..fc0219f9c 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -271,6 +271,10 @@ }; const getGroupSettingsProps = () => { const members = this.model.get('members') || []; + const ourPK = window.textsecure.storage.user.getNumber(); + const isAdmin = this.model.isMediumGroup() + ? true + : this.model.get('groupAdmins').includes(ourPK); return { id: this.model.id, @@ -280,7 +284,7 @@ avatarPath: this.model.getAvatarPath(), isGroup: !this.model.isPrivate(), isPublic: this.model.isPublic(), - isAdmin: true, // allow closed group edits from anyone this.model.get('groupAdmins').includes(ourPK), + isAdmin, isRss: this.model.isRss(), memberCount: members.length, amMod: this.model.isModerator( diff --git a/js/views/create_group_dialog_view.js b/js/views/create_group_dialog_view.js index 6a0f5715b..ddc183d2e 100644 --- a/js/views/create_group_dialog_view.js +++ b/js/views/create_group_dialog_view.js @@ -98,7 +98,10 @@ } else { this.titleText = i18n('updateGroupDialogTitle', this.groupName); // anybody can edit a closed group name or members - this.isAdmin = true; + const ourPK = window.textsecure.storage.user.getNumber(); + this.isAdmin = groupConvo.isMediumGroup() + ? true + : groupConvo.get('groupAdmins').includes(ourPK); const convos = window.getConversations().models.filter(d => !!d); this.existingMembers = groupConvo.get('members') || []; diff --git a/ts/receiver/groups.ts b/ts/receiver/groups.ts index df5a70a28..2c74f92a0 100644 --- a/ts/receiver/groups.ts +++ b/ts/receiver/groups.ts @@ -85,8 +85,9 @@ export async function preprocessGroupMessage( if (newGroup) { conversation.updateGroupAdmins(group.admins); } else { - // group members and names can be changed from any member - const fromAdmin = true; + // be sure to drop a message from a non admin if it tries to change group members + // or change the group name + const fromAdmin = conversation.get('groupAdmins').includes(primarySource); if (!fromAdmin) { // Make sure the message is not removing members / renaming the group From 2f8f79c5d284d36ab99dfce870a7f94bd9764e47 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Mon, 5 Oct 2020 12:31:07 +1100 Subject: [PATCH 20/20] make message clearer when we try to reuse a keyIdx in the ratchet --- ts/receiver/contentMessage.ts | 6 +----- ts/session/medium_group/ratchet.ts | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/ts/receiver/contentMessage.ts b/ts/receiver/contentMessage.ts index 5a6a61ca4..bb6fec351 100644 --- a/ts/receiver/contentMessage.ts +++ b/ts/receiver/contentMessage.ts @@ -287,11 +287,7 @@ async function decrypt( return plaintext; } catch (error) { - if ( - error && - (error instanceof textsecure.SenderKeyMissing || - error instanceof DOMException) - ) { + if (error && error instanceof textsecure.SenderKeyMissing) { const groupId = envelope.source; const { senderIdentity } = error; if (senderIdentity) { diff --git a/ts/session/medium_group/ratchet.ts b/ts/session/medium_group/ratchet.ts index 5f9d81db9..2c3180fa7 100644 --- a/ts/session/medium_group/ratchet.ts +++ b/ts/session/medium_group/ratchet.ts @@ -204,6 +204,11 @@ async function advanceRatchet( log.error('[idx] not found key for idx: ', idx); // I probably want a better error handling than this return null; + } else if (idx === ratchet.keyIdx) { + log.error( + `advanceRatchet() called with idx:${idx}, current ratchetIdx:${ratchet.keyIdx}. We already burnt that keyIdx before.` + ); + return null; } const { messageKeys } = ratchet; @@ -278,16 +283,12 @@ async function decryptWithSenderKeyInner( ); return plaintext; } catch (e) { - window.log.error('Got error during DecryptGCM():', e); + window.log.error('Got error during DecryptGCM()', e); if (e instanceof DOMException) { - const params = { - timestamp: Date.now(), - groupId, - }; - // we consider we don't have the correct key for this sender, so request the latest one - const requestKeysMessage = new MediumGroupRequestKeysMessage(params); - const sender = new PubKey(senderIdentity); - await getMessageQueue().send(sender, requestKeysMessage); + window.log.error( + 'Got DOMException during DecryptGCM(). Rethrowing as SenderKeyMissing ' + ); + throw new window.textsecure.SenderKeyMissing(senderIdentity); } } }