fix medium group to match mobile way

pull/1364/head
Audric Ackermann 5 years ago
parent baaca1a29f
commit a80f9a5965
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -19,6 +19,7 @@ import { BlockedNumberController } from '../util/blockedNumberController';
import { decryptWithSenderKey } from '../session/medium_group/ratchet'; import { decryptWithSenderKey } from '../session/medium_group/ratchet';
import { StringUtils } from '../session/utils'; import { StringUtils } from '../session/utils';
import { UserUtil } from '../util'; import { UserUtil } from '../util';
import { getMessageQueue } from '../session';
export async function handleContentMessage(envelope: EnvelopePlus) { export async function handleContentMessage(envelope: EnvelopePlus) {
try { try {
@ -89,8 +90,7 @@ async function decryptForMediumGroup(
groupId, groupId,
sourceAsStr sourceAsStr
); );
return plaintext ? unpad(plaintext) : null;
return unpad(plaintext);
} }
function unpad(paddedData: ArrayBuffer): ArrayBuffer { function unpad(paddedData: ArrayBuffer): ArrayBuffer {
@ -288,28 +288,32 @@ async function decrypt(
return plaintext; return plaintext;
} catch (error) { } catch (error) {
if (error && error instanceof textsecure.SenderKeyMissing) { if (
error &&
(error instanceof textsecure.SenderKeyMissing ||
error instanceof DOMException)
) {
const groupId = envelope.source; const groupId = envelope.source;
const { senderIdentity } = error; const { senderIdentity } = error;
if (senderIdentity) {
log.info(
'Requesting missing key for identity: ',
senderIdentity,
'groupId: ',
groupId
);
log.info( const params = {
'Requesting missing key for identity: ', timestamp: Date.now(),
senderIdentity, groupId,
'groupId: ', };
groupId
);
const params = {
timestamp: Date.now(),
groupId,
};
const requestKeysMessage = new MediumGroupRequestKeysMessage(params); const requestKeysMessage = new MediumGroupRequestKeysMessage(params);
const sender = new PubKey(senderIdentity); const sender = new PubKey(senderIdentity);
// tslint:disable-next-line no-floating-promises void getMessageQueue().send(sender, requestKeysMessage);
libsession.getMessageQueue().send(sender, requestKeysMessage);
return; return;
}
} }
let errorToThrow = error; let errorToThrow = error;

@ -279,7 +279,10 @@ export async function handleDataMessage(
envelope: EnvelopePlus, envelope: EnvelopePlus,
dataMessage: SignalService.IDataMessage dataMessage: SignalService.IDataMessage
): Promise<void> { ): Promise<void> {
window.log.info('data message from', getEnvelopeId(envelope)); window.log.info(
'data message from',
getEnvelopeId(envelope)
);
if (dataMessage.mediumGroupUpdate) { if (dataMessage.mediumGroupUpdate) {
await handleMediumGroupUpdate(envelope, dataMessage.mediumGroupUpdate); await handleMediumGroupUpdate(envelope, dataMessage.mediumGroupUpdate);

@ -243,10 +243,10 @@ function sanityCheckMediumGroupUpdate(
const joining = diff.joiningMembers || []; const joining = diff.joiningMembers || [];
const leaving = diff.leavingMembers || []; 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 (!joining.length && !leaving.length) {
if (groupUpdate.senderKeys.length) { if (groupUpdate.senderKeys.length !== groupUpdate.members.length) {
window.log.error('Unexpected sender keys in group update'); window.log.error('Incorrect number of sender keys in group update');
} }
} }
@ -270,13 +270,10 @@ async function handleMediumGroupChange(
envelope: EnvelopePlus, envelope: EnvelopePlus,
groupUpdate: SignalService.MediumGroupUpdate groupUpdate: SignalService.MediumGroupUpdate
) { ) {
const senderIdentity = envelope.source;
const { const {
name, name,
groupPublicKey, groupPublicKey,
members: membersBinary, members: membersBinary,
admins: adminsBinary,
senderKeys, senderKeys,
} = groupUpdate; } = groupUpdate;
const { log } = window; const { log } = window;
@ -342,6 +339,7 @@ async function handleMediumGroupChange(
convo.set('isKickedFromGroup', true); convo.set('isKickedFromGroup', true);
// Disable typing: // Disable typing:
convo.updateTextInputState(); convo.updateTextInputState();
window.SwarmPolling.removePubkey(groupId);
} }
await convo.commit(); await convo.commit();

@ -208,19 +208,18 @@ export async function leaveMediumGroup(groupId: string) {
sent_at: now, sent_at: now,
received_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 // do not include senderkey as everyone needs to generate new one
const groupUpdate: GroupInfo = { const groupUpdate: GroupInfo = {
id: convo.get('id'), id: convo.get('id'),
name: convo.get('name'), name: convo.get('name'),
members: convo.get('members'), members,
is_medium_group: true, is_medium_group: true,
admins: convo.get('groupAdmins'), admins: convo.get('groupAdmins'),
senderKeysContainer: undefined,
}; };
const ourPrimary = await UserUtil.getPrimary();
await sendGroupUpdateForMedium( await sendGroupUpdateForMedium(
{ leavingMembers: [ourPrimary.key] }, { leavingMembers: [ourPrimary.key] },
groupUpdate, groupUpdate,
@ -260,24 +259,23 @@ async function getExistingSenderKeysForGroup(
return maybeKeys.filter(d => d !== null).map(d => d as RatchetState); return maybeKeys.filter(d => d !== null).map(d => d as RatchetState);
} }
// Create all sender keys based on the changes in // Get a list of senderKeys we have to send to joining members
// the group's composition // Basically this is the senderkey of all members who joined concatenated with
async function getOrCreateSenderKeysForUpdate( // 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, groupId: string,
members: Array<string>, members: Array<string>,
changes: MemberChanges diff?: GroupDiff,
): Promise<SenderKeysContainer> { joiningMembersSenderKeys?: Array<RatchetState>
// 1. Create sender keys for every joining member ): Promise<Array<RatchetState>> {
const joining = changes.joiningMembers || []; const leavingMembers = diff?.leavingMembers || [];
const leaving = changes.leavingMembers || []; const joiningMembers = diff?.joiningMembers || [];
let newKeys = await createSenderKeysForMembers(groupId, joining);
// 2. Get ratchet states for existing members
const existingMembers = _.difference(members, joining); const existingMembers = _.difference(members, joiningMembers);
// get all devices for members // get all devices for members
const allDevices = _.flatten( const allDevices = _.flatten(
await Promise.all( await Promise.all(
existingMembers.map(m => MultiDeviceProtocol.getAllDevices(m)) existingMembers.map(m => MultiDeviceProtocol.getAllDevices(m))
@ -285,23 +283,10 @@ async function getOrCreateSenderKeysForUpdate(
); );
let existingKeys: Array<RatchetState> = []; let existingKeys: Array<RatchetState> = [];
if (leavingMembers.length === 0) {
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
existingKeys = await getExistingSenderKeysForGroup(groupId, allDevices); existingKeys = await getExistingSenderKeysForGroup(groupId, allDevices);
} }
return _.union(joiningMembersSenderKeys, existingKeys);
return { existingKeys, newKeys };
} }
async function getGroupSecretKey(groupId: string): Promise<Uint8Array> { async function getGroupSecretKey(groupId: string): Promise<Uint8Array> {
@ -322,6 +307,9 @@ async function getGroupSecretKey(groupId: string): Promise<Uint8Array> {
} }
async function syncMediumGroup(group: ConversationModel) { 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 ourPrimary = await UserUtil.getPrimary();
const groupId = group.get('id'); const groupId = group.get('id');
@ -352,7 +340,6 @@ async function syncMediumGroup(group: ConversationModel) {
members: group.get('members'), members: group.get('members'),
is_medium_group: true, is_medium_group: true,
admins: group.get('groupAdmins'), admins: group.get('groupAdmins'),
senderKeysContainer,
secretKey, secretKey,
}; };
@ -418,12 +405,7 @@ export async function initiateGroupUpdate(
}; };
if (isMediumGroup) { if (isMediumGroup) {
// Send sender keys and group secret key // Send group secret key
updateObj.senderKeysContainer = await getOrCreateSenderKeysForUpdate(
groupId,
members,
diff
);
const secretKey = await getGroupSecretKey(groupId); const secretKey = await getGroupSecretKey(groupId);
updateObj.secretKey = secretKey; updateObj.secretKey = secretKey;
@ -531,7 +513,6 @@ interface GroupInfo {
blocked?: boolean; blocked?: boolean;
admins?: Array<string>; admins?: Array<string>;
secretKey?: Uint8Array; secretKey?: Uint8Array;
senderKeysContainer?: SenderKeysContainer;
} }
interface UpdatableGroupState { interface UpdatableGroupState {
@ -605,15 +586,16 @@ export function calculateGroupDiff(
return groupDiff; return groupDiff;
} }
async function sendGroupUpdateForExistingMembers( async function sendGroupUpdateForMedium(
leavingMembers: Array<string>, diff: MemberChanges,
remainingMembers: Array<string>,
groupUpdate: GroupInfo, groupUpdate: GroupInfo,
messageId?: string messageId?: string
) { ) {
const { id: groupId, members, name: groupName } = groupUpdate; const { id: groupId, members, name: groupName } = groupUpdate;
const ourPrimary = await UserUtil.getPrimary(); const ourPrimary = await UserUtil.getPrimary();
const leavingMembers = diff.leavingMembers || [];
const joiningMembers = diff.joiningMembers || [];
const wasAnyUserRemoved = leavingMembers.length > 0; const wasAnyUserRemoved = leavingMembers.length > 0;
const isUserLeaving = leavingMembers.includes(ourPrimary.key); const isUserLeaving = leavingMembers.includes(ourPrimary.key);
@ -626,10 +608,7 @@ async function sendGroupUpdateForExistingMembers(
(pkHex: string) => new Uint8Array(fromHex(pkHex)) (pkHex: string) => new Uint8Array(fromHex(pkHex))
); );
// Existing members only receive new sender keys const remainingMembers = _.difference(groupUpdate.members, joiningMembers);
const senderKeys = groupUpdate.senderKeysContainer
? groupUpdate.senderKeysContainer.newKeys
: [];
const params = { const params = {
timestamp: Date.now(), timestamp: Date.now(),
@ -638,7 +617,6 @@ async function sendGroupUpdateForExistingMembers(
members: membersBin, members: membersBin,
groupName, groupName,
admins: adminsBin, admins: adminsBin,
senderKeys: senderKeys,
}; };
if (wasAnyUserRemoved) { if (wasAnyUserRemoved) {
@ -652,116 +630,101 @@ async function sendGroupUpdateForExistingMembers(
senderKeys: [], senderKeys: [],
}; };
const messageStripped = new MediumGroupUpdateMessage(paramsWithoutSenderKeys); const messageStripped = new MediumGroupUpdateMessage(
paramsWithoutSenderKeys
);
window.log.warn('Sending to groupUpdateMessage without senderKeys'); window.log.warn('Sending to groupUpdateMessage without senderKeys');
await getMessageQueue().sendToGroup(messageStripped); await getMessageQueue().sendToGroup(messageStripped);
// TODO Delete all ratchets (it's important that this happens * after * sending out the update) getMessageQueue().events.addListener('success', async message => {
if (isUserLeaving) { if (message.identifier === params.identifier) {
// nothing to do on desktop // console.log('Our first message encrypted with old sk is sent.');
} else { // TODO Delete all ratchets (it's important that this happens * after * sending out the update)
// Send out the user's new ratchet to all members (minus the removed ones) using established channels if (isUserLeaving) {
const ourPrimaryKey = new Uint8Array(fromHex(ourPrimary.key)) // nothing to do on desktop
const ourNewSenderKey = senderKeys.find(s => _.isEqual(s.pubKey, ourPrimaryKey)); } else {
// Send out the user's new ratchet to all members (minus the removed ones) using established channels
if (! ourNewSenderKey) { const userSenderKey = await createSenderKeyForGroup(
window.console.warn('We need to share our senderkey with remaining member but our senderKeys was not given.'); groupId,
} else { ourPrimary
window.log.warn('Sharing our new senderKey with remainingMembers via message', remainingMembers, ourNewSenderKey); );
await shareSenderKeys(groupId, remainingMembers, ourNewSenderKey); 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<string>,
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<RatchetState> = [];
if (!senderKeysContainer) {
window.log.warn('Sender keys for joining members not found');
} else { } else {
// Joining members should receive all known sender keys let senderKeys: Array<RatchetState>;
senderKeys = _.union( if (joiningMembers.length > 0) {
senderKeysContainer.existingKeys, // Generate ratchets for any new members
senderKeysContainer.newKeys senderKeys = await createSenderKeysForMembers(groupId, joiningMembers);
); } else {
} // It's not a member change, maybe an name change. So just reuse all senderkeys
senderKeys = await getOrUpdateSenderKeysForJoiningMembers(
const membersBin = members.map( groupId,
(pkHex: string) => new Uint8Array(fromHex(pkHex)) members
); );
}
const admins = groupUpdate.admins || []; const paramsWithSenderKeys = {
...params,
const adminsBin = admins.map( senderKeys,
(pkHex: string) => new Uint8Array(fromHex(pkHex)) };
); // 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);
const createParams = { window.log.warn(
timestamp: now, 'Sending to groupUpdateMessage with joining members senderKeys to groupAddress',
groupId, senderKeys
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
); );
});
}
async function sendGroupUpdateForMedium( await getMessageQueue().sendToGroup(message);
diff: MemberChanges,
groupUpdate: GroupInfo,
messageId?: string
) {
const joining = diff.joiningMembers || [];
const leaving = diff.leavingMembers || [];
// 1. create group for all joining members (send timeout timer if necessary) // now send a CREATE group message with all senderkeys no matter what to all joining members, using established channels
if (joining.length) { if (joiningMembers.length) {
await sendGroupUpdateForJoiningMembers(joining, groupUpdate, messageId); const { secretKey } = groupUpdate;
}
// 2. send group update to all other members if (!secretKey) {
const others = _.difference(groupUpdate.members, joining); window.log.error('Group secret key not specified, aborting...');
if (others.length) { return;
await sendGroupUpdateForExistingMembers( }
leaving, const allSenderKeys = await getOrUpdateSenderKeysForJoiningMembers(
others, groupId,
groupUpdate, members
messageId );
);
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
);
});
}
} }
} }

@ -14,12 +14,15 @@ async function queueJobForNumber(number: string, runJob: any) {
const runCurrent = runPrevious.then(runJob, runJob); const runCurrent = runPrevious.then(runJob, runJob);
jobQueue[number] = runCurrent; jobQueue[number] = runCurrent;
// tslint:disable-next-line no-floating-promises // tslint:disable-next-line no-floating-promises
runCurrent.then(() => { runCurrent
if (jobQueue[number] === runCurrent) { .then(() => {
// tslint:disable-next-line no-dynamic-delete if (jobQueue[number] === runCurrent) {
delete jobQueue[number]; // tslint:disable-next-line no-dynamic-delete
} delete jobQueue[number];
}); }
}).catch((e: any) => {
window.log.error('queueJobForNumber() Caught error', e);
});
return runCurrent; return runCurrent;
} }

@ -7,6 +7,7 @@ import {
ClosedGroupMessage, ClosedGroupMessage,
ContentMessage, ContentMessage,
ExpirationTimerUpdateMessage, ExpirationTimerUpdateMessage,
MediumGroupChatMessage,
MediumGroupMessage, MediumGroupMessage,
OpenGroupMessage, OpenGroupMessage,
SessionRequestMessage, SessionRequestMessage,
@ -102,8 +103,10 @@ export class MessageQueue implements MessageQueueInterface {
throw new Error('Invalid group message passed in sendToGroup.'); 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 this is a medium group message. We just need to send to the group pubkey
if (message instanceof MediumGroupMessage) { if (
window.log.warn('sending medium ', message, ' to ', groupId) message instanceof MediumGroupMessage ||
message instanceof MediumGroupChatMessage
) {
return this.send(PubKey.cast(groupId), message); return this.send(PubKey.cast(groupId), message);
} }

@ -9,7 +9,10 @@ import { RawMessage } from '../types/RawMessage';
import { TypedEventEmitter } from '../utils'; import { TypedEventEmitter } from '../utils';
import { PubKey } from '../types'; import { PubKey } from '../types';
type GroupMessageType = OpenGroupMessage | ClosedGroupMessage | MediumGroupMessage; type GroupMessageType =
| OpenGroupMessage
| ClosedGroupMessage
| MediumGroupMessage;
export interface MessageQueueInterfaceEvents { export interface MessageQueueInterfaceEvents {
success: ( success: (

@ -45,6 +45,7 @@ export async function send(
timestamp, timestamp,
cipherText cipherText
); );
// console.warn('sending', envelope, ' to ', device.key);
const data = wrapEnvelope(envelope); const data = wrapEnvelope(envelope);
return pRetry( return pRetry(

@ -6,6 +6,7 @@ import {
} from '../messages/outgoing'; } from '../messages/outgoing';
import { EncryptionType, PubKey } from '../types'; import { EncryptionType, PubKey } from '../types';
import { SessionProtocol } from '../protocols'; import { SessionProtocol } from '../protocols';
import { MediumGroupUpdateMessage } from '../messages/outgoing/content/data/mediumgroup/MediumGroupUpdateMessage';
export async function toRawMessage( export async function toRawMessage(
device: PubKey, device: PubKey,
@ -16,7 +17,10 @@ export async function toRawMessage(
const plainTextBuffer = message.plainTextBuffer(); const plainTextBuffer = message.plainTextBuffer();
let encryption: EncryptionType; let encryption: EncryptionType;
if (message instanceof MediumGroupChatMessage) { if (
message instanceof MediumGroupChatMessage ||
message instanceof MediumGroupUpdateMessage
) {
encryption = EncryptionType.MediumGroup; encryption = EncryptionType.MediumGroup;
} else if (message instanceof SessionRequestMessage) { } else if (message instanceof SessionRequestMessage) {
encryption = EncryptionType.Fallback; encryption = EncryptionType.Fallback;

Loading…
Cancel
Save