From 64737a89d7e6896811068d1480f5327d939465c6 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 4 Feb 2021 11:53:37 +1100 Subject: [PATCH] add ConfigurationMessage --- protos/SignalService.proto | 60 ++++++++----- ts/receiver/closedGroups.ts | 6 +- ts/session/group/index.ts | 14 ++- ts/session/messages/outgoing/Message.ts | 4 + .../outgoing/content/ConfigurationMessage.ts | 90 +++++++++++++++++++ .../data/group/ClosedGroupNewMessage.ts | 6 +- ts/session/utils/Messages.ts | 63 ++++++++++++- 7 files changed, 207 insertions(+), 36 deletions(-) create mode 100644 ts/session/messages/outgoing/content/ConfigurationMessage.ts diff --git a/protos/SignalService.proto b/protos/SignalService.proto index 3ede0c1f1..3329f2b21 100644 --- a/protos/SignalService.proto +++ b/protos/SignalService.proto @@ -33,9 +33,17 @@ message TypingMessage { message Content { - optional DataMessage dataMessage = 1; - optional ReceiptMessage receiptMessage = 5; - optional TypingMessage typingMessage = 6; + optional DataMessage dataMessage = 1; + optional ReceiptMessage receiptMessage = 5; + optional TypingMessage typingMessage = 6; + optional ConfigurationMessage configurationMessage = 7; +} + +message KeyPair { + // @required + required bytes publicKey = 1; + // @required + required bytes privateKey = 2; } @@ -144,6 +152,7 @@ message DataMessage { optional string profilePicture = 2; } + message ClosedGroupControlMessage { enum Type { @@ -156,12 +165,7 @@ message DataMessage { MEMBER_LEFT = 7; } - message KeyPair { - // @required - required bytes publicKey = 1; - // @required - required bytes privateKey = 2; - } + message KeyPairWrapper { // @required @@ -186,20 +190,34 @@ message DataMessage { optional string serverName = 3; } - optional string body = 1; - repeated AttachmentPointer attachments = 2; - optional GroupContext group = 3; - optional uint32 flags = 4; - optional uint32 expireTimer = 5; - optional bytes profileKey = 6; - optional uint64 timestamp = 7; - optional Quote quote = 8; - repeated Contact contact = 9; - repeated Preview preview = 10; - optional LokiProfile profile = 101; - optional GroupInvitation groupInvitation = 102; + optional string body = 1; + repeated AttachmentPointer attachments = 2; + optional GroupContext group = 3; + optional uint32 flags = 4; + optional uint32 expireTimer = 5; + optional bytes profileKey = 6; + optional uint64 timestamp = 7; + optional Quote quote = 8; + repeated Contact contact = 9; + repeated Preview preview = 10; + optional LokiProfile profile = 101; + optional GroupInvitation groupInvitation = 102; optional ClosedGroupControlMessage closedGroupControlMessage = 104; + optional string syncTarget = 105; +} + +message ConfigurationMessage { + + message ClosedGroup { + optional bytes publicKey = 1; + optional string name = 2; + optional KeyPair encryptionKeyPair = 3; + repeated bytes members = 4; + repeated bytes admins = 5; + } + repeated ClosedGroup closedGroups = 1; + repeated string openGroups = 2; } message ReceiptMessage { diff --git a/ts/receiver/closedGroups.ts b/ts/receiver/closedGroups.ts index 83bdf1fb2..e38c42e02 100644 --- a/ts/receiver/closedGroups.ts +++ b/ts/receiver/closedGroups.ts @@ -385,11 +385,9 @@ async function handleClosedGroupEncryptionKeyPair( } // Parse it - let proto: SignalService.DataMessage.ClosedGroupControlMessage.KeyPair; + let proto: SignalService.KeyPair; try { - proto = SignalService.DataMessage.ClosedGroupControlMessage.KeyPair.decode( - plaintext - ); + proto = SignalService.KeyPair.decode(plaintext); if ( !proto || proto.privateKey.length === 0 || diff --git a/ts/session/group/index.ts b/ts/session/group/index.ts index c576f69ec..3b68bafec 100644 --- a/ts/session/group/index.ts +++ b/ts/session/group/index.ts @@ -673,15 +673,11 @@ export async function generateAndSendNewEncryptionKeyPair( ); return; } - const proto = new SignalService.DataMessage.ClosedGroupControlMessage.KeyPair( - { - privateKey: newKeyPair?.privateKeyData, - publicKey: newKeyPair?.publicKeyData, - } - ); - const plaintext = SignalService.DataMessage.ClosedGroupControlMessage.KeyPair.encode( - proto - ).finish(); + const proto = new SignalService.KeyPair({ + privateKey: newKeyPair?.privateKeyData, + publicKey: newKeyPair?.publicKeyData, + }); + const plaintext = SignalService.KeyPair.encode(proto).finish(); // Distribute it const wrappers = await Promise.all( diff --git a/ts/session/messages/outgoing/Message.ts b/ts/session/messages/outgoing/Message.ts index 382d5ff7c..1c84975b6 100644 --- a/ts/session/messages/outgoing/Message.ts +++ b/ts/session/messages/outgoing/Message.ts @@ -16,4 +16,8 @@ export abstract class Message { } this.identifier = identifier || uuid(); } + + public isSelfSendValid() { + return false; + } } diff --git a/ts/session/messages/outgoing/content/ConfigurationMessage.ts b/ts/session/messages/outgoing/content/ConfigurationMessage.ts new file mode 100644 index 000000000..560c60532 --- /dev/null +++ b/ts/session/messages/outgoing/content/ConfigurationMessage.ts @@ -0,0 +1,90 @@ +// this is not a very good name, but a configuration message is a message sent to our other devices so sync our current public and closed groups + +import { ContentMessage } from './ContentMessage'; +import { SignalService } from '../../../../protobuf'; +import { MessageParams } from '../Message'; +import { Constants } from '../../..'; +import { ECKeyPair } from '../../../../receiver/keypairs'; +import { fromHexToArray } from '../../../utils/String'; + +interface ConfigurationMessageParams extends MessageParams { + activeClosedGroups: Array; + activeOpenGroups: Array; +} + +export class ConfigurationMessage extends ContentMessage { + private readonly activeClosedGroups: Array; + private readonly activeOpenGroups: Array; + + constructor(params: ConfigurationMessageParams) { + super({ timestamp: params.timestamp, identifier: params.identifier }); + this.activeClosedGroups = params.activeClosedGroups; + this.activeOpenGroups = params.activeOpenGroups; + } + + public ttl(): number { + return Constants.TTL_DEFAULT.TYPING_MESSAGE; + } + + public contentProto(): SignalService.Content { + return new SignalService.Content({ + configurationMessage: this.configurationProto(), + }); + } + + protected configurationProto(): SignalService.ConfigurationMessage { + return new SignalService.ConfigurationMessage({ + closedGroups: this.mapClosedGroupsObjectToProto(this.activeClosedGroups), + openGroups: this.activeOpenGroups, + }); + } + + private mapClosedGroupsObjectToProto( + closedGroups: Array + ): Array { + return (closedGroups || []).map(m => + new ConfigurationMessageClosedGroup(m).toProto() + ); + } +} + +export class ConfigurationMessageClosedGroup { + public publicKey: string; + public name: string; + public encryptionKeyPair: ECKeyPair; + public members: Array; + public admins: Array; + + public constructor({ + publicKey, + name, + encryptionKeyPair, + members, + admins, + }: { + publicKey: string; + name: string; + encryptionKeyPair: ECKeyPair; + members: Array; + admins: Array; + }) { + this.publicKey = publicKey; + this.name = name; + this.encryptionKeyPair = encryptionKeyPair; + this.members = members; + this.admins = admins; + } + + public toProto(): SignalService.ConfigurationMessage.ClosedGroup { + return new SignalService.ConfigurationMessage.ClosedGroup({ + publicKey: fromHexToArray(this.publicKey), + name: this.name, + encryptionKeyPair: { + publicKey: this.encryptionKeyPair.publicKeyData, + privateKey: this.encryptionKeyPair.privateKeyData, + }, + members: this.members.map(fromHexToArray), + admins: this.admins.map(fromHexToArray), + }); + } +} diff --git a/ts/session/messages/outgoing/content/data/group/ClosedGroupNewMessage.ts b/ts/session/messages/outgoing/content/data/group/ClosedGroupNewMessage.ts index d42de8327..eeb47839b 100644 --- a/ts/session/messages/outgoing/content/data/group/ClosedGroupNewMessage.ts +++ b/ts/session/messages/outgoing/content/data/group/ClosedGroupNewMessage.ts @@ -73,7 +73,7 @@ export class ClosedGroupNewMessage extends ClosedGroupMessage { fromHexToArray ); try { - dataMessage.closedGroupControlMessage.encryptionKeyPair = new SignalService.DataMessage.ClosedGroupControlMessage.KeyPair(); + dataMessage.closedGroupControlMessage.encryptionKeyPair = new SignalService.KeyPair(); dataMessage.closedGroupControlMessage.encryptionKeyPair.privateKey = new Uint8Array( this.keypair.privateKeyData ); @@ -87,4 +87,8 @@ export class ClosedGroupNewMessage extends ClosedGroupMessage { return dataMessage; } + + public isSelfSendValid() { + return true; + } } diff --git a/ts/session/utils/Messages.ts b/ts/session/utils/Messages.ts index 937ac2e3d..8f6931862 100644 --- a/ts/session/utils/Messages.ts +++ b/ts/session/utils/Messages.ts @@ -2,11 +2,20 @@ import { RawMessage } from '../types/RawMessage'; import { ContentMessage, ExpirationTimerUpdateMessage, - TypingMessage, } from '../messages/outgoing'; import { EncryptionType, PubKey } from '../types'; import { ClosedGroupMessage } from '../messages/outgoing/content/data/group/ClosedGroupMessage'; import { ClosedGroupNewMessage } from '../messages/outgoing/content/data/group/ClosedGroupNewMessage'; +import { ConversationModel } from '../../../js/models/conversations'; +import { + ConfigurationMessage, + ConfigurationMessageClosedGroup, +} from '../messages/outgoing/content/ConfigurationMessage'; +import uuid from 'uuid'; +import * as Data from '../../../js/modules/data'; +import { UserUtils } from '.'; +import { ECKeyPair } from '../../receiver/keypairs'; +import _ from 'lodash'; export function getEncryptionTypeFromMessageType( message: ContentMessage @@ -51,3 +60,55 @@ export async function toRawMessage( return rawMessage; } + +export const getCurrentConfigurationMessage = async ( + convos: Array +) => { + const ourPubKey = (await UserUtils.getOurNumber()).key; + const openGroupsIds = convos + .filter( + c => + !!c.get('active_at') && + c.get('members').includes(ourPubKey) && + c.isPublic() && + !c.get('left') + ) + .map(c => c.id) as Array; + const closedGroupModels = convos.filter( + c => + !!c.get('active_at') && + c.isMediumGroup() && + !c.get('left') && + !c.get('isKickedFromGroup') + ); + + const closedGroups = await Promise.all( + closedGroupModels.map(async c => { + const groupPubKey = c.get('id'); + const fetchEncryptionKeyPair = await Data.getLatestClosedGroupEncryptionKeyPair( + groupPubKey + ); + if (!fetchEncryptionKeyPair) { + return null; + } + + return new ConfigurationMessageClosedGroup({ + publicKey: groupPubKey, + name: c.get('name'), + members: c.get('members') || [], + admins: c.get('groupAdmins') || [], + encryptionKeyPair: ECKeyPair.fromHexKeyPair(fetchEncryptionKeyPair), + }); + }) + ); + + const onlyValidClosedGroup = closedGroups.filter(m => m !== null) as Array< + ConfigurationMessageClosedGroup + >; + return new ConfigurationMessage({ + identifier: uuid(), + timestamp: Date.now(), + activeOpenGroups: openGroupsIds, + activeClosedGroups: onlyValidClosedGroup, + }); +};