@wireapp/core 17.21.1 → 17.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/package.json +2 -2
- package/src/main/conversation/ConversationService.d.ts +20 -6
- package/src/main/conversation/ConversationService.js +77 -42
- package/src/main/conversation/ConversationService.js.map +1 -1
- package/src/main/conversation/ConversationService.ts +91 -81
- package/src/main/conversation/message/MessageService.d.ts +11 -4
- package/src/main/conversation/message/MessageService.js +138 -129
- package/src/main/conversation/message/MessageService.js.map +1 -1
- package/src/main/conversation/message/MessageService.test.node.js +61 -18
- package/src/main/conversation/message/MessageService.test.node.js.map +1 -1
- package/src/main/conversation/message/MessageService.test.node.ts +99 -31
- package/src/main/conversation/message/MessageService.ts +159 -152
- package/src/main/util/TypePredicateUtil.d.ts +1 -0
- package/src/main/util/TypePredicateUtil.js +6 -2
- package/src/main/util/TypePredicateUtil.js.map +1 -1
- package/src/main/util/TypePredicateUtil.ts +5 -1
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import UUID from 'uuidjs';
|
|
21
21
|
import {StatusCodes} from 'http-status-codes';
|
|
22
22
|
import {APIClient} from '@wireapp/api-client';
|
|
23
|
-
import {MessageSendingStatus,
|
|
23
|
+
import {MessageSendingStatus, QualifiedUserClients} from '@wireapp/api-client/src/conversation';
|
|
24
24
|
import {CryptographyService} from '../../cryptography';
|
|
25
25
|
import {MessageService} from './MessageService';
|
|
26
26
|
|
|
@@ -36,12 +36,11 @@ type TestUser = {id: string; domain: string; clients: string[]};
|
|
|
36
36
|
const user1: TestUser = {id: UUID.genV4().toString(), domain: '1.wire.test', clients: ['client1.1', 'client1.2']};
|
|
37
37
|
const user2: TestUser = {id: UUID.genV4().toString(), domain: '2.wire.test', clients: ['client2.1', 'client2.2']};
|
|
38
38
|
|
|
39
|
-
function
|
|
40
|
-
const payload:
|
|
39
|
+
function generateQualifiedRecipients(users: TestUser[]): QualifiedUserClients {
|
|
40
|
+
const payload: QualifiedUserClients = {};
|
|
41
41
|
users.forEach(({id, domain, clients}) => {
|
|
42
42
|
payload[domain] ||= {};
|
|
43
|
-
payload[domain][id] =
|
|
44
|
-
clients.forEach(client => (payload[domain][id][client] = new Uint8Array()));
|
|
43
|
+
payload[domain][id] = clients;
|
|
45
44
|
});
|
|
46
45
|
return payload;
|
|
47
46
|
}
|
|
@@ -53,43 +52,112 @@ describe('MessageService', () => {
|
|
|
53
52
|
describe('sendFederatedMessage', () => {
|
|
54
53
|
it('sends a message', async () => {
|
|
55
54
|
spyOn(apiClient.conversation.api, 'postOTRMessageV2').and.returnValue(Promise.resolve(baseMessageSendingStatus));
|
|
56
|
-
const recipients
|
|
55
|
+
const recipients = generateQualifiedRecipients([user1, user2]);
|
|
57
56
|
|
|
58
|
-
await messageService.sendFederatedOTRMessage(
|
|
57
|
+
await messageService.sendFederatedOTRMessage(
|
|
58
|
+
'senderclientid',
|
|
59
|
+
{id: 'convid', domain: ''},
|
|
60
|
+
recipients,
|
|
61
|
+
new Uint8Array(),
|
|
62
|
+
);
|
|
59
63
|
expect(apiClient.conversation.api.postOTRMessageV2).toHaveBeenCalled();
|
|
60
64
|
});
|
|
61
65
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
describe('client mismatch', () => {
|
|
67
|
+
it('handles client mismatch internally if no onClientMismatch is given', async () => {
|
|
68
|
+
let spyCounter = 0;
|
|
69
|
+
const clientMismatch = {...baseMessageSendingStatus, missing: {'2.wire.test': {[user2.id]: ['client22']}}};
|
|
70
|
+
spyOn(apiClient.conversation.api, 'postOTRMessageV2').and.callFake(() => {
|
|
71
|
+
spyCounter++;
|
|
72
|
+
if (spyCounter === 1) {
|
|
73
|
+
const error = new Error();
|
|
74
|
+
(error as any).response = {
|
|
75
|
+
status: StatusCodes.PRECONDITION_FAILED,
|
|
76
|
+
data: clientMismatch,
|
|
77
|
+
};
|
|
78
|
+
return Promise.reject(error);
|
|
79
|
+
}
|
|
80
|
+
return Promise.resolve(baseMessageSendingStatus);
|
|
81
|
+
});
|
|
82
|
+
spyOn(apiClient.user.api, 'postQualifiedMultiPreKeyBundles').and.returnValue(Promise.resolve({}));
|
|
83
|
+
spyOn(cryptographyService, 'encryptQualified').and.returnValue(
|
|
84
|
+
Promise.resolve({'2.wire.test': {[user2.id]: {client22: new Uint8Array()}}}),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const recipients = generateQualifiedRecipients([user1, user2]);
|
|
88
|
+
|
|
89
|
+
await messageService.sendFederatedOTRMessage(
|
|
90
|
+
'senderclientid',
|
|
91
|
+
{id: 'convid', domain: ''},
|
|
92
|
+
recipients,
|
|
93
|
+
new Uint8Array(),
|
|
94
|
+
{reportMissing: true},
|
|
95
|
+
);
|
|
96
|
+
expect(apiClient.conversation.api.postOTRMessageV2).toHaveBeenCalledTimes(2);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('continues message sending if onClientMismatch returns true', async () => {
|
|
100
|
+
const onClientMismatch = jasmine.createSpy().and.returnValue(Promise.resolve(true));
|
|
101
|
+
const clientMismatch = {...baseMessageSendingStatus, missing: {'2.wire.test': {[user2.id]: ['client22']}}};
|
|
102
|
+
let spyCounter = 0;
|
|
103
|
+
spyOn(apiClient.conversation.api, 'postOTRMessageV2').and.callFake(() => {
|
|
104
|
+
spyCounter++;
|
|
105
|
+
if (spyCounter === 1) {
|
|
106
|
+
const error = new Error();
|
|
107
|
+
(error as any).response = {
|
|
108
|
+
status: StatusCodes.PRECONDITION_FAILED,
|
|
109
|
+
data: clientMismatch,
|
|
110
|
+
};
|
|
111
|
+
return Promise.reject(error);
|
|
112
|
+
}
|
|
113
|
+
return Promise.resolve(baseMessageSendingStatus);
|
|
114
|
+
});
|
|
115
|
+
spyOn(apiClient.user.api, 'postQualifiedMultiPreKeyBundles').and.returnValue(Promise.resolve({}));
|
|
116
|
+
spyOn(cryptographyService, 'encryptQualified').and.returnValue(
|
|
117
|
+
Promise.resolve({'2.wire.test': {[user2.id]: {client22: new Uint8Array()}}}),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const recipients = generateQualifiedRecipients([user1, user2]);
|
|
121
|
+
|
|
122
|
+
await messageService.sendFederatedOTRMessage(
|
|
123
|
+
'senderclientid',
|
|
124
|
+
{id: 'convid', domain: ''},
|
|
125
|
+
recipients,
|
|
126
|
+
new Uint8Array(),
|
|
127
|
+
{reportMissing: true, onClientMismatch},
|
|
128
|
+
);
|
|
129
|
+
expect(apiClient.conversation.api.postOTRMessageV2).toHaveBeenCalledTimes(2);
|
|
130
|
+
expect(onClientMismatch).toHaveBeenCalledWith(clientMismatch);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('stops message sending if onClientMismatch returns false', async () => {
|
|
134
|
+
const onClientMismatch = jasmine.createSpy().and.returnValue(Promise.resolve(false));
|
|
135
|
+
const clientMismatch = {...baseMessageSendingStatus, missing: {'2.wire.test': {[user2.id]: ['client22']}}};
|
|
136
|
+
spyOn(apiClient.conversation.api, 'postOTRMessageV2').and.callFake(() => {
|
|
67
137
|
const error = new Error();
|
|
68
138
|
(error as any).response = {
|
|
69
139
|
status: StatusCodes.PRECONDITION_FAILED,
|
|
70
|
-
data:
|
|
140
|
+
data: clientMismatch,
|
|
71
141
|
};
|
|
72
142
|
return Promise.reject(error);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
Promise.resolve({'2.wire.test': {[user2.id]: {client22: new Uint8Array()}}}),
|
|
79
|
-
);
|
|
143
|
+
});
|
|
144
|
+
spyOn(apiClient.user.api, 'postQualifiedMultiPreKeyBundles').and.returnValue(Promise.resolve({}));
|
|
145
|
+
spyOn(cryptographyService, 'encryptQualified').and.returnValue(
|
|
146
|
+
Promise.resolve({'2.wire.test': {[user2.id]: {client22: new Uint8Array()}}}),
|
|
147
|
+
);
|
|
80
148
|
|
|
81
|
-
|
|
149
|
+
const recipients = generateQualifiedRecipients([user1, user2]);
|
|
82
150
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
151
|
+
await messageService.sendFederatedOTRMessage(
|
|
152
|
+
'senderclientid',
|
|
153
|
+
{id: 'convid', domain: ''},
|
|
154
|
+
recipients,
|
|
155
|
+
new Uint8Array(),
|
|
156
|
+
{reportMissing: true, onClientMismatch},
|
|
157
|
+
);
|
|
158
|
+
expect(apiClient.conversation.api.postOTRMessageV2).toHaveBeenCalledTimes(1);
|
|
159
|
+
expect(onClientMismatch).toHaveBeenCalledWith(clientMismatch);
|
|
160
|
+
});
|
|
93
161
|
});
|
|
94
162
|
});
|
|
95
163
|
});
|
|
@@ -35,11 +35,9 @@ import {
|
|
|
35
35
|
import {Decoder, Encoder} from 'bazinga64';
|
|
36
36
|
|
|
37
37
|
import {CryptographyService} from '../../cryptography';
|
|
38
|
+
import {QualifiedId, QualifiedUserPreKeyBundleMap} from '@wireapp/api-client/src/user';
|
|
38
39
|
|
|
39
|
-
type ClientMismatchError = AxiosError<
|
|
40
|
-
deleted: UserClients;
|
|
41
|
-
missing: UserClients;
|
|
42
|
-
}>;
|
|
40
|
+
type ClientMismatchError = AxiosError<ClientMismatch>;
|
|
43
41
|
|
|
44
42
|
export class MessageService {
|
|
45
43
|
constructor(private readonly apiClient: APIClient, private readonly cryptographyService: CryptographyService) {}
|
|
@@ -75,8 +73,11 @@ export class MessageService {
|
|
|
75
73
|
ignoreMissing,
|
|
76
74
|
);
|
|
77
75
|
} catch (error) {
|
|
76
|
+
if (!this.isClientMismatchError(error)) {
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
78
79
|
const reEncryptedMessage = await this.onClientMismatch(
|
|
79
|
-
error
|
|
80
|
+
error.response!.data,
|
|
80
81
|
{...message, data: base64CipherText ? Decoder.fromBase64(base64CipherText).asBytes : undefined, recipients},
|
|
81
82
|
plainTextArray,
|
|
82
83
|
);
|
|
@@ -148,14 +149,18 @@ export class MessageService {
|
|
|
148
149
|
|
|
149
150
|
public async sendFederatedOTRMessage(
|
|
150
151
|
sendingClientId: string,
|
|
151
|
-
conversationId:
|
|
152
|
-
|
|
153
|
-
recipients: QualifiedOTRRecipients,
|
|
152
|
+
{id: conversationId, domain}: QualifiedId,
|
|
153
|
+
recipients: QualifiedUserClients | QualifiedUserPreKeyBundleMap,
|
|
154
154
|
plainTextArray: Uint8Array,
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
options: {
|
|
156
|
+
assetData?: Uint8Array;
|
|
157
|
+
reportMissing?: boolean;
|
|
158
|
+
onClientMismatch?: (mismatch: MessageSendingStatus) => Promise<boolean | undefined>;
|
|
159
|
+
} = {},
|
|
157
160
|
): Promise<MessageSendingStatus> {
|
|
158
|
-
const
|
|
161
|
+
const otrRecipients = await this.cryptographyService.encryptQualified(plainTextArray, recipients);
|
|
162
|
+
|
|
163
|
+
const qualifiedUserEntries = Object.entries(otrRecipients).map<ProtobufOTR.IQualifiedUserEntry>(
|
|
159
164
|
([domain, otrRecipients]) => {
|
|
160
165
|
const userEntries = Object.entries(otrRecipients).map<ProtobufOTR.IUserEntry>(([userId, otrClientMap]) => {
|
|
161
166
|
const clientEntries = Object.entries(otrClientMap).map<ProtobufOTR.IClientEntry>(([clientId, payload]) => {
|
|
@@ -186,8 +191,8 @@ export class MessageService {
|
|
|
186
191
|
},
|
|
187
192
|
});
|
|
188
193
|
|
|
189
|
-
if (assetData) {
|
|
190
|
-
protoMessage.blob = assetData;
|
|
194
|
+
if (options.assetData) {
|
|
195
|
+
protoMessage.blob = options.assetData;
|
|
191
196
|
}
|
|
192
197
|
|
|
193
198
|
/*
|
|
@@ -195,7 +200,7 @@ export class MessageService {
|
|
|
195
200
|
* missing clients. We have to ignore missing clients because there can be the case that there are clients that
|
|
196
201
|
* don't provide PreKeys (clients from the Pre-E2EE era).
|
|
197
202
|
*/
|
|
198
|
-
if (reportMissing) {
|
|
203
|
+
if (options.reportMissing) {
|
|
199
204
|
protoMessage.reportAll = {};
|
|
200
205
|
} else {
|
|
201
206
|
protoMessage.ignoreAll = {};
|
|
@@ -203,11 +208,7 @@ export class MessageService {
|
|
|
203
208
|
|
|
204
209
|
let sendingStatus: MessageSendingStatus;
|
|
205
210
|
try {
|
|
206
|
-
sendingStatus = await this.apiClient.conversation.api.postOTRMessageV2(
|
|
207
|
-
conversationId,
|
|
208
|
-
conversationDomain,
|
|
209
|
-
protoMessage,
|
|
210
|
-
);
|
|
211
|
+
sendingStatus = await this.apiClient.conversation.api.postOTRMessageV2(conversationId, domain, protoMessage);
|
|
211
212
|
} catch (error) {
|
|
212
213
|
if (!this.isClientMismatchError(error)) {
|
|
213
214
|
throw error;
|
|
@@ -218,8 +219,12 @@ export class MessageService {
|
|
|
218
219
|
const mismatch = this.checkFederatedClientsMismatch(protoMessage, sendingStatus);
|
|
219
220
|
|
|
220
221
|
if (mismatch) {
|
|
221
|
-
const
|
|
222
|
-
|
|
222
|
+
const shouldStopSending = options.onClientMismatch && !(await options.onClientMismatch(mismatch));
|
|
223
|
+
if (shouldStopSending) {
|
|
224
|
+
return sendingStatus;
|
|
225
|
+
}
|
|
226
|
+
const reEncryptedMessage = await this.onFederatedMismatch(protoMessage, mismatch, plainTextArray);
|
|
227
|
+
await this.apiClient.conversation.api.postOTRMessageV2(conversationId, domain, reEncryptedMessage);
|
|
223
228
|
}
|
|
224
229
|
return sendingStatus;
|
|
225
230
|
}
|
|
@@ -282,7 +287,11 @@ export class MessageService {
|
|
|
282
287
|
ignoreMissing,
|
|
283
288
|
);
|
|
284
289
|
} catch (error) {
|
|
285
|
-
|
|
290
|
+
if (!this.isClientMismatchError(error)) {
|
|
291
|
+
throw error;
|
|
292
|
+
}
|
|
293
|
+
const mismatch = error.response!.data;
|
|
294
|
+
const reEncryptedMessage = await this.onClientProtobufMismatch(mismatch, protoMessage, plainTextArray);
|
|
286
295
|
if (conversationId === null) {
|
|
287
296
|
return await this.apiClient.broadcast.api.postBroadcastProtobufMessage(sendingClientId, reEncryptedMessage);
|
|
288
297
|
}
|
|
@@ -296,189 +305,187 @@ export class MessageService {
|
|
|
296
305
|
}
|
|
297
306
|
|
|
298
307
|
private async onClientMismatch(
|
|
299
|
-
|
|
308
|
+
clientMismatch: ClientMismatch,
|
|
300
309
|
message: NewOTRMessage<Uint8Array>,
|
|
301
310
|
plainTextArray: Uint8Array,
|
|
302
311
|
): Promise<NewOTRMessage<Uint8Array>> {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
for (const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
delete deletedUser[deletedClientId];
|
|
315
|
-
}
|
|
312
|
+
const {missing, deleted} = clientMismatch;
|
|
313
|
+
|
|
314
|
+
const deletedUserIds = Object.keys(deleted);
|
|
315
|
+
const missingUserIds = Object.keys(missing);
|
|
316
|
+
|
|
317
|
+
if (deletedUserIds.length) {
|
|
318
|
+
for (const deletedUserId of deletedUserIds) {
|
|
319
|
+
for (const deletedClientId of deleted[deletedUserId]) {
|
|
320
|
+
const deletedUser = message.recipients[deletedUserId];
|
|
321
|
+
if (deletedUser) {
|
|
322
|
+
delete deletedUser[deletedClientId];
|
|
316
323
|
}
|
|
317
324
|
}
|
|
318
325
|
}
|
|
326
|
+
}
|
|
319
327
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
message.recipients[missingUserId][missingClientId] = reEncryptedPayloads[missingUserId][missingClientId];
|
|
328
|
+
if (missingUserIds.length) {
|
|
329
|
+
const missingPreKeyBundles = await this.apiClient.user.api.postMultiPreKeyBundles(missing);
|
|
330
|
+
const reEncryptedPayloads = await this.cryptographyService.encrypt(plainTextArray, missingPreKeyBundles);
|
|
331
|
+
for (const missingUserId of missingUserIds) {
|
|
332
|
+
for (const missingClientId in reEncryptedPayloads[missingUserId]) {
|
|
333
|
+
const missingUser = message.recipients[missingUserId];
|
|
334
|
+
if (!missingUser) {
|
|
335
|
+
message.recipients[missingUserId] = {};
|
|
331
336
|
}
|
|
337
|
+
|
|
338
|
+
message.recipients[missingUserId][missingClientId] = reEncryptedPayloads[missingUserId][missingClientId];
|
|
332
339
|
}
|
|
333
340
|
}
|
|
334
|
-
|
|
335
|
-
return message;
|
|
336
341
|
}
|
|
337
342
|
|
|
338
|
-
|
|
343
|
+
return message;
|
|
339
344
|
}
|
|
340
345
|
|
|
341
346
|
private async onClientProtobufMismatch(
|
|
342
|
-
|
|
347
|
+
clientMismatch: {missing: UserClients; deleted: UserClients},
|
|
343
348
|
message: ProtobufOTR.NewOtrMessage,
|
|
344
349
|
plainTextArray: Uint8Array,
|
|
345
350
|
): Promise<ProtobufOTR.NewOtrMessage> {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
for (const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
delete message.recipients[deletedUserIndex].clients?.[deletedClientIndex!];
|
|
362
|
-
}
|
|
351
|
+
const {missing, deleted} = clientMismatch;
|
|
352
|
+
|
|
353
|
+
const deletedUserIds = Object.keys(deleted);
|
|
354
|
+
const missingUserIds = Object.keys(missing);
|
|
355
|
+
|
|
356
|
+
if (deletedUserIds.length) {
|
|
357
|
+
for (const deletedUserId of deletedUserIds) {
|
|
358
|
+
for (const deletedClientId of deleted[deletedUserId]) {
|
|
359
|
+
const deletedUserIndex = message.recipients.findIndex(({user}) => bytesToUUID(user.uuid) === deletedUserId);
|
|
360
|
+
if (deletedUserIndex > -1) {
|
|
361
|
+
const deletedClientIndex = message.recipients[deletedUserIndex].clients?.findIndex(({client}) => {
|
|
362
|
+
return client.client.toString(16) === deletedClientId;
|
|
363
|
+
});
|
|
364
|
+
if (typeof deletedClientIndex !== 'undefined' && deletedClientIndex > -1) {
|
|
365
|
+
delete message.recipients[deletedUserIndex].clients?.[deletedClientIndex!];
|
|
363
366
|
}
|
|
364
367
|
}
|
|
365
368
|
}
|
|
366
369
|
}
|
|
370
|
+
}
|
|
367
371
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
},
|
|
381
|
-
text: reEncryptedPayloads[missingUserId][missingClientId],
|
|
372
|
+
if (missingUserIds.length) {
|
|
373
|
+
const missingPreKeyBundles = await this.apiClient.user.api.postMultiPreKeyBundles(missing);
|
|
374
|
+
const reEncryptedPayloads = await this.cryptographyService.encrypt(plainTextArray, missingPreKeyBundles);
|
|
375
|
+
for (const missingUserId of missingUserIds) {
|
|
376
|
+
for (const missingClientId in reEncryptedPayloads[missingUserId]) {
|
|
377
|
+
const missingUserIndex = message.recipients.findIndex(({user}) => bytesToUUID(user.uuid) === missingUserId);
|
|
378
|
+
if (missingUserIndex === -1) {
|
|
379
|
+
message.recipients.push({
|
|
380
|
+
clients: [
|
|
381
|
+
{
|
|
382
|
+
client: {
|
|
383
|
+
client: Long.fromString(missingClientId, 16),
|
|
382
384
|
},
|
|
383
|
-
|
|
384
|
-
user: {
|
|
385
|
-
uuid: uuidToBytes(missingUserId),
|
|
385
|
+
text: reEncryptedPayloads[missingUserId][missingClientId],
|
|
386
386
|
},
|
|
387
|
-
|
|
388
|
-
|
|
387
|
+
],
|
|
388
|
+
user: {
|
|
389
|
+
uuid: uuidToBytes(missingUserId),
|
|
390
|
+
},
|
|
391
|
+
});
|
|
389
392
|
}
|
|
390
393
|
}
|
|
391
394
|
}
|
|
392
|
-
|
|
393
|
-
return message;
|
|
394
395
|
}
|
|
395
396
|
|
|
396
|
-
|
|
397
|
+
return message;
|
|
397
398
|
}
|
|
398
399
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
* @param {MessageSendingStatus} messageSendingStatus Info about the missing/deleted clients
|
|
404
|
-
* @param {Uint8Array} plainTextArray The text that should be encrypted for the missing clients
|
|
405
|
-
* @return resolves with a new message payload that can be sent
|
|
406
|
-
*/
|
|
407
|
-
private async encryptForMissingClients(
|
|
408
|
-
messageData: ProtobufOTR.QualifiedNewOtrMessage,
|
|
409
|
-
messageSendingStatus: MessageSendingStatus,
|
|
410
|
-
plainTextArray: Uint8Array,
|
|
411
|
-
): Promise<ProtobufOTR.QualifiedNewOtrMessage> {
|
|
400
|
+
private deleteExtraQualifiedClients(
|
|
401
|
+
message: ProtobufOTR.QualifiedNewOtrMessage,
|
|
402
|
+
deletedClients: MessageSendingStatus['deleted'],
|
|
403
|
+
): ProtobufOTR.QualifiedNewOtrMessage {
|
|
412
404
|
// walk through deleted domain/user map
|
|
413
|
-
for (const [deletedUserDomain, deletedUserIdClients] of Object.entries(
|
|
414
|
-
if (!
|
|
405
|
+
for (const [deletedUserDomain, deletedUserIdClients] of Object.entries(deletedClients)) {
|
|
406
|
+
if (!message.recipients.find(recipient => recipient.domain === deletedUserDomain)) {
|
|
415
407
|
// no user from this domain was deleted
|
|
416
408
|
continue;
|
|
417
409
|
}
|
|
418
410
|
// walk through deleted user ids
|
|
419
411
|
for (const [deletedUserId] of Object.entries(deletedUserIdClients)) {
|
|
420
412
|
// walk through message recipients
|
|
421
|
-
for (const recipientIndex in
|
|
413
|
+
for (const recipientIndex in message.recipients) {
|
|
422
414
|
// check if message recipients' domain is the same as the deleted user's domain
|
|
423
|
-
if (
|
|
415
|
+
if (message.recipients[recipientIndex].domain === deletedUserDomain) {
|
|
424
416
|
// check if message recipients' id is the same as the deleted user's id
|
|
425
|
-
for (const entriesIndex in
|
|
426
|
-
const uuid =
|
|
417
|
+
for (const entriesIndex in message.recipients[recipientIndex].entries || []) {
|
|
418
|
+
const uuid = message.recipients[recipientIndex].entries![entriesIndex].user?.uuid;
|
|
427
419
|
if (!!uuid && bytesToUUID(uuid) === deletedUserId) {
|
|
428
420
|
// delete this user from the message recipients
|
|
429
|
-
delete
|
|
421
|
+
delete message.recipients[recipientIndex].entries![entriesIndex];
|
|
430
422
|
}
|
|
431
423
|
}
|
|
432
424
|
}
|
|
433
425
|
}
|
|
434
426
|
}
|
|
435
427
|
}
|
|
428
|
+
return message;
|
|
429
|
+
}
|
|
436
430
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
431
|
+
/**
|
|
432
|
+
* Will re-encrypt a message when there were some missing clients in the initial send (typically when the server replies with a client mismatch error)
|
|
433
|
+
*
|
|
434
|
+
* @param {ProtobufOTR.QualifiedNewOtrMessage} messageData The initial message that was sent
|
|
435
|
+
* @param {MessageSendingStatus} messageSendingStatus Info about the missing/deleted clients
|
|
436
|
+
* @param {Uint8Array} plainText The text that should be encrypted for the missing clients
|
|
437
|
+
* @return resolves with a new message payload that can be sent
|
|
438
|
+
*/
|
|
439
|
+
private async onFederatedMismatch(
|
|
440
|
+
message: ProtobufOTR.QualifiedNewOtrMessage,
|
|
441
|
+
{deleted, missing}: MessageSendingStatus,
|
|
442
|
+
plainText: Uint8Array,
|
|
443
|
+
): Promise<ProtobufOTR.QualifiedNewOtrMessage> {
|
|
444
|
+
message = this.deleteExtraQualifiedClients(message, deleted);
|
|
445
|
+
if (Object.keys(missing).length) {
|
|
446
|
+
const missingPreKeyBundles = await this.apiClient.user.api.postQualifiedMultiPreKeyBundles(missing);
|
|
447
|
+
const reEncryptedPayloads = await this.cryptographyService.encryptQualified(plainText, missingPreKeyBundles);
|
|
448
|
+
message = this.addMissingQualifiedClients(message, reEncryptedPayloads);
|
|
449
|
+
}
|
|
450
|
+
return message;
|
|
451
|
+
}
|
|
443
452
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
453
|
+
private addMissingQualifiedClients(
|
|
454
|
+
messageData: ProtobufOTR.QualifiedNewOtrMessage,
|
|
455
|
+
reEncryptedPayloads: QualifiedOTRRecipients,
|
|
456
|
+
): ProtobufOTR.QualifiedNewOtrMessage {
|
|
457
|
+
// walk through missing domain/user map
|
|
458
|
+
for (const [missingDomain, userClients] of Object.entries(reEncryptedPayloads)) {
|
|
459
|
+
// walk through missing user ids
|
|
460
|
+
for (const [missingUserId, missingClientIds] of Object.entries(userClients)) {
|
|
461
|
+
// walk through message recipients
|
|
462
|
+
for (const domain of messageData.recipients) {
|
|
463
|
+
// check if message recipients' domain is the same as the missing user's domain
|
|
464
|
+
if (domain.domain === missingDomain) {
|
|
465
|
+
// check if there is a recipient with same user id as the missing user's id
|
|
466
|
+
let userIndex = domain.entries?.findIndex(({user}) => bytesToUUID(user.uuid) === missingUserId);
|
|
467
|
+
|
|
468
|
+
if (userIndex === -1) {
|
|
469
|
+
// no recipient found, let's create it
|
|
470
|
+
userIndex =
|
|
471
|
+
domain.entries!.push({
|
|
472
|
+
user: {
|
|
473
|
+
uuid: uuidToBytes(missingUserId),
|
|
474
|
+
},
|
|
475
|
+
}) - 1;
|
|
476
|
+
}
|
|
450
477
|
|
|
451
|
-
|
|
452
|
-
for (const [missingUserId, missingClientIds] of Object.entries(missingUserIdClients)) {
|
|
453
|
-
// walk through message recipients
|
|
454
|
-
for (const domain of messageData.recipients) {
|
|
455
|
-
// check if message recipients' domain is the same as the missing user's domain
|
|
456
|
-
if (domain.domain === missingUserDomain) {
|
|
457
|
-
// check if there is a recipient with same user id as the missing user's id
|
|
458
|
-
let userIndex = domain.entries?.findIndex(({user}) => bytesToUUID(user.uuid) === missingUserId);
|
|
459
|
-
|
|
460
|
-
if (userIndex === -1) {
|
|
461
|
-
// no recipient found, let's create it
|
|
462
|
-
userIndex =
|
|
463
|
-
domain.entries!.push({
|
|
464
|
-
user: {
|
|
465
|
-
uuid: uuidToBytes(missingUserId),
|
|
466
|
-
},
|
|
467
|
-
}) - 1;
|
|
468
|
-
}
|
|
478
|
+
const missingUserUUID = domain.entries![userIndex!].user.uuid;
|
|
469
479
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
text: reEncryptedPayloads[missingUserDomain][missingUserId][missingClientId],
|
|
480
|
-
});
|
|
481
|
-
}
|
|
480
|
+
if (bytesToUUID(missingUserUUID) === missingUserId) {
|
|
481
|
+
for (const [missingClientId, missingClientPayload] of Object.entries(missingClientIds)) {
|
|
482
|
+
domain.entries![userIndex!].clients ||= [];
|
|
483
|
+
domain.entries![userIndex!].clients?.push({
|
|
484
|
+
client: {
|
|
485
|
+
client: Long.fromString(missingClientId, 16),
|
|
486
|
+
},
|
|
487
|
+
text: missingClientPayload,
|
|
488
|
+
});
|
|
482
489
|
}
|
|
483
490
|
}
|
|
484
491
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { QualifiedUserClients, UserClients } from '@wireapp/api-client/src/conversation/';
|
|
2
2
|
import type { QualifiedId } from '@wireapp/api-client/src/user/';
|
|
3
3
|
export declare function isStringArray(obj: any): obj is string[];
|
|
4
|
+
export declare function isQualifiedId(obj: any): obj is QualifiedId;
|
|
4
5
|
export declare function isQualifiedIdArray(obj: any): obj is QualifiedId[];
|
|
5
6
|
export declare function isQualifiedUserClients(obj: any): obj is QualifiedUserClients;
|
|
6
7
|
export declare function isUserClients(obj: any): obj is UserClients;
|
|
@@ -18,13 +18,17 @@
|
|
|
18
18
|
*
|
|
19
19
|
*/
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.isUserClients = exports.isQualifiedUserClients = exports.isQualifiedIdArray = exports.isStringArray = void 0;
|
|
21
|
+
exports.isUserClients = exports.isQualifiedUserClients = exports.isQualifiedIdArray = exports.isQualifiedId = exports.isStringArray = void 0;
|
|
22
22
|
function isStringArray(obj) {
|
|
23
23
|
return Array.isArray(obj) && typeof obj[0] === 'string';
|
|
24
24
|
}
|
|
25
25
|
exports.isStringArray = isStringArray;
|
|
26
|
+
function isQualifiedId(obj) {
|
|
27
|
+
return typeof obj === 'object' && typeof obj['domain'] === 'string';
|
|
28
|
+
}
|
|
29
|
+
exports.isQualifiedId = isQualifiedId;
|
|
26
30
|
function isQualifiedIdArray(obj) {
|
|
27
|
-
return Array.isArray(obj) &&
|
|
31
|
+
return Array.isArray(obj) && isQualifiedId(obj[0]);
|
|
28
32
|
}
|
|
29
33
|
exports.isQualifiedIdArray = isQualifiedIdArray;
|
|
30
34
|
function isQualifiedUserClients(obj) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TypePredicateUtil.js","sourceRoot":"","sources":["TypePredicateUtil.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;;AAKH,SAAgB,aAAa,CAAC,GAAQ;IACpC,OAAO,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC;AAC1D,CAAC;AAFD,sCAEC;AAED,SAAgB,
|
|
1
|
+
{"version":3,"file":"TypePredicateUtil.js","sourceRoot":"","sources":["TypePredicateUtil.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;;AAKH,SAAgB,aAAa,CAAC,GAAQ;IACpC,OAAO,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC;AAC1D,CAAC;AAFD,sCAEC;AAED,SAAgB,aAAa,CAAC,GAAQ;IACpC,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,OAAO,GAAG,CAAC,QAAQ,CAAC,KAAK,QAAQ,CAAC;AACtE,CAAC;AAFD,sCAEC;AAED,SAAgB,kBAAkB,CAAC,GAAQ;IACzC,OAAO,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AACrD,CAAC;AAFD,gDAEC;AAED,SAAgB,sBAAsB,CAAC,GAAQ;;IAC7C,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE;QAC3B,MAAM,qBAAqB,GAAG,MAAA,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,0CAAG,CAAC,CAAC,CAAC;QACtD,IAAI,OAAO,qBAAqB,KAAK,QAAQ,EAAE;YAC7C,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,CAAC,qBAA+B,CAAC,CAAC,CAAC,CAAC,CAAC;YAC7E,IAAI,KAAK,CAAC,OAAO,CAAC,kBAAkB,CAAC,EAAE;gBACrC,MAAM,aAAa,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAC;gBAC5C,OAAO,OAAO,aAAa,KAAK,QAAQ,IAAI,OAAO,aAAa,KAAK,WAAW,CAAC;aAClF;SACF;KACF;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAZD,wDAYC;AAED,SAAgB,aAAa,CAAC,GAAQ;;IACpC,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE;QAC3B,MAAM,oBAAoB,GAAG,MAAA,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,0CAAG,CAAC,CAAC,CAAC;QACrD,IAAI,KAAK,CAAC,OAAO,CAAC,oBAAoB,CAAC,EAAE;YACvC,MAAM,aAAa,GAAG,oBAAoB,CAAC,CAAC,CAAC,CAAC;YAC9C,OAAO,OAAO,aAAa,KAAK,QAAQ,CAAC;SAC1C;KACF;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AATD,sCASC"}
|
|
@@ -24,8 +24,12 @@ export function isStringArray(obj: any): obj is string[] {
|
|
|
24
24
|
return Array.isArray(obj) && typeof obj[0] === 'string';
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
export function isQualifiedId(obj: any): obj is QualifiedId {
|
|
28
|
+
return typeof obj === 'object' && typeof obj['domain'] === 'string';
|
|
29
|
+
}
|
|
30
|
+
|
|
27
31
|
export function isQualifiedIdArray(obj: any): obj is QualifiedId[] {
|
|
28
|
-
return Array.isArray(obj) &&
|
|
32
|
+
return Array.isArray(obj) && isQualifiedId(obj[0]);
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
export function isQualifiedUserClients(obj: any): obj is QualifiedUserClients {
|