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 { 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;

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

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

@ -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<string>,
changes: MemberChanges
): Promise<SenderKeysContainer> {
// 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<RatchetState>
): Promise<Array<RatchetState>> {
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<RatchetState> = [];
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<Uint8Array> {
@ -322,6 +307,9 @@ async function getGroupSecretKey(groupId: string): Promise<Uint8Array> {
}
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<string>;
secretKey?: Uint8Array;
senderKeysContainer?: SenderKeysContainer;
}
interface UpdatableGroupState {
@ -605,15 +586,16 @@ export function calculateGroupDiff(
return groupDiff;
}
async function sendGroupUpdateForExistingMembers(
leavingMembers: Array<string>,
remainingMembers: Array<string>,
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<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 {
// 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<RatchetState>;
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
);
});
}
}
}

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

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

@ -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: (

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

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

Loading…
Cancel
Save