@wireapp/core 30.3.0 → 30.5.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 +30 -0
- package/package.json +3 -3
- package/src/main/Account.d.ts +1 -17
- package/src/main/Account.js +7 -3
- package/src/main/conversation/ConversationService/ConversationService.d.ts +5 -11
- package/src/main/conversation/ConversationService/ConversationService.js +37 -108
- package/src/main/cryptography/CryptographyDatabaseRepository.d.ts +2 -1
- package/src/main/cryptography/CryptographyDatabaseRepository.js +1 -0
- package/src/main/mls/MLSService/MLSService.d.ts +26 -0
- package/src/main/mls/MLSService/MLSService.js +131 -0
- package/src/main/mls/index.d.ts +1 -0
- package/src/main/mls/index.js +32 -0
- package/src/main/mls/types.d.ts +22 -0
- package/src/main/mls/types.js +21 -0
- package/src/main/notification/NotificationDatabaseRepository.d.ts +22 -1
- package/src/main/notification/NotificationDatabaseRepository.js +29 -0
- package/src/main/notification/NotificationService.d.ts +28 -4
- package/src/main/notification/NotificationService.js +73 -16
- package/src/main/notification/types.d.ts +3 -0
- package/src/main/util/LowPrecisionTaskScheduler/LowPrecisionTaskScheduler.d.ts +21 -0
- package/src/main/util/LowPrecisionTaskScheduler/LowPrecisionTaskScheduler.js +62 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,36 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [30.5.0](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/compare/@wireapp/core@30.4.1...@wireapp/core@30.5.0) (2022-09-09)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **core:** update keys periodically (FS-562) ([#4374](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/issues/4374)) ([9b9d362](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/commit/9b9d362bca320691fb15314cb1a3ef940a51892c))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## [30.4.1](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/compare/@wireapp/core@30.4.0...@wireapp/core@30.4.1) (2022-09-08)
|
|
18
|
+
|
|
19
|
+
**Note:** Version bump only for package @wireapp/core
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# [30.4.0](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/compare/@wireapp/core@30.3.0...@wireapp/core@30.4.0) (2022-09-07)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Features
|
|
29
|
+
|
|
30
|
+
* Update getAllParticipantsClients to use better endpoints without a hack ([#4379](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/issues/4379)) ([f38258d](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/commit/f38258db39e81c4b517126792aa6c605b1ea51c5))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
6
36
|
# [30.3.0](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/compare/@wireapp/core@30.2.0...@wireapp/core@30.3.0) (2022-09-07)
|
|
7
37
|
|
|
8
38
|
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"@otak/core-crypto": "0.3.0-es2017",
|
|
8
8
|
"@types/long": "4.0.1",
|
|
9
9
|
"@types/node": "~14",
|
|
10
|
-
"@wireapp/api-client": "20.1
|
|
10
|
+
"@wireapp/api-client": "20.2.1",
|
|
11
11
|
"@wireapp/commons": "4.3.0",
|
|
12
12
|
"@wireapp/cryptobox": "12.8.0",
|
|
13
13
|
"@wireapp/store-engine-dexie": "1.6.10",
|
|
@@ -77,6 +77,6 @@
|
|
|
77
77
|
"test:node": "nyc jasmine --config=jasmine.json",
|
|
78
78
|
"watch": "tsc ---watch"
|
|
79
79
|
},
|
|
80
|
-
"version": "30.
|
|
81
|
-
"gitHead": "
|
|
80
|
+
"version": "30.5.0",
|
|
81
|
+
"gitHead": "a2a55f074ebfb3fe5e3a32df1153a9c1ae7091dd"
|
|
82
82
|
}
|
package/src/main/Account.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { UserService } from './user/';
|
|
|
22
22
|
import { AccountService } from './account/';
|
|
23
23
|
import { LinkPreviewService } from './linkPreview';
|
|
24
24
|
import { WEBSOCKET_STATE } from '@wireapp/api-client/src/tcp/ReconnectingWebsocket';
|
|
25
|
+
import type { MLSConfig } from './mls/types';
|
|
25
26
|
export declare type ProcessedEventPayload = HandledEventPayload;
|
|
26
27
|
declare enum TOPIC {
|
|
27
28
|
ERROR = "Account.TOPIC.ERROR"
|
|
@@ -55,23 +56,6 @@ export interface Account {
|
|
|
55
56
|
on(event: TOPIC.ERROR, listener: (payload: CoreError) => void): this;
|
|
56
57
|
}
|
|
57
58
|
export declare type CreateStoreFn = (storeName: string, context: Context) => undefined | Promise<CRUDEngine | undefined>;
|
|
58
|
-
declare type SecretCrypto<T> = {
|
|
59
|
-
encrypt: (value: Uint8Array) => Promise<T>;
|
|
60
|
-
decrypt: (payload: T) => Promise<Uint8Array>;
|
|
61
|
-
};
|
|
62
|
-
interface MLSConfig<T = any> {
|
|
63
|
-
/**
|
|
64
|
-
* encrypt/decrypt function pair that will be called before storing/fetching secrets in the secrets database.
|
|
65
|
-
* If not provided will use the built in encryption mechanism
|
|
66
|
-
*/
|
|
67
|
-
secretsCrypto?: SecretCrypto<T>;
|
|
68
|
-
/**
|
|
69
|
-
* path on the public server to the core crypto wasm file.
|
|
70
|
-
* This file will be downloaded lazily when corecrypto is needed.
|
|
71
|
-
* It, thus, needs to know where, on the server, the file can be found
|
|
72
|
-
*/
|
|
73
|
-
coreCrypoWasmFilePath: string;
|
|
74
|
-
}
|
|
75
59
|
interface AccountOptions<T> {
|
|
76
60
|
/** Used to store info in the database (will create a inMemory engine if returns undefined) */
|
|
77
61
|
createStore?: CreateStoreFn;
|
package/src/main/Account.js
CHANGED
|
@@ -72,6 +72,7 @@ const account_1 = require("./account/");
|
|
|
72
72
|
const linkPreview_1 = require("./linkPreview");
|
|
73
73
|
const encryptedStore_1 = require("./util/encryptedStore");
|
|
74
74
|
const bazinga64_1 = require("bazinga64");
|
|
75
|
+
const mls_1 = require("./mls");
|
|
75
76
|
var TOPIC;
|
|
76
77
|
(function (TOPIC) {
|
|
77
78
|
TOPIC["ERROR"] = "Account.TOPIC.ERROR";
|
|
@@ -137,7 +138,7 @@ class Account extends events_1.EventEmitter {
|
|
|
137
138
|
* @param cookie The cookie to identify the user against backend (will use the browser's one if not given)
|
|
138
139
|
*/
|
|
139
140
|
async init(clientType, cookie, initClient = true) {
|
|
140
|
-
var _a;
|
|
141
|
+
var _a, _b;
|
|
141
142
|
const context = await this.apiClient.init(clientType, cookie);
|
|
142
143
|
await this.initServices(context);
|
|
143
144
|
// Assumption: client gets only initialized once
|
|
@@ -145,6 +146,8 @@ class Account extends events_1.EventEmitter {
|
|
|
145
146
|
await this.initClient({ clientType });
|
|
146
147
|
// initialize schedulers for pending mls proposals once client is initialized
|
|
147
148
|
await ((_a = this.service) === null || _a === void 0 ? void 0 : _a.notification.checkExistingPendingProposals());
|
|
149
|
+
// initialize schedulers for renewing key materials
|
|
150
|
+
await ((_b = this.service) === null || _b === void 0 ? void 0 : _b.notification.checkForKeyMaterialsUpdate());
|
|
148
151
|
}
|
|
149
152
|
return context;
|
|
150
153
|
}
|
|
@@ -230,14 +233,15 @@ class Account extends events_1.EventEmitter {
|
|
|
230
233
|
nbPrekeys: this.nbPrekeys,
|
|
231
234
|
});
|
|
232
235
|
const clientService = new client_2.ClientService(this.apiClient, this.storeEngine, cryptographyService);
|
|
236
|
+
const mlsService = new mls_1.MLSService(this.mlsConfig, this.apiClient, () => this.coreCryptoClient);
|
|
233
237
|
const connectionService = new connection_1.ConnectionService(this.apiClient);
|
|
234
238
|
const giphyService = new giphy_1.GiphyService(this.apiClient);
|
|
235
239
|
const linkPreviewService = new linkPreview_1.LinkPreviewService(assetService);
|
|
236
|
-
const notificationService = new notification_1.NotificationService(this.apiClient, cryptographyService,
|
|
240
|
+
const notificationService = new notification_1.NotificationService(this.apiClient, cryptographyService, mlsService, this.storeEngine);
|
|
237
241
|
const conversationService = new conversation_1.ConversationService(this.apiClient, cryptographyService, {
|
|
238
242
|
// We can use qualified ids to send messages as long as the backend supports federated endpoints
|
|
239
243
|
useQualifiedIds: this.backendFeatures.federationEndpoints,
|
|
240
|
-
},
|
|
244
|
+
}, notificationService, mlsService);
|
|
241
245
|
const selfService = new self_1.SelfService(this.apiClient);
|
|
242
246
|
const teamService = new team_1.TeamService(this.apiClient);
|
|
243
247
|
const broadcastService = new broadcast_1.BroadcastService(this.apiClient, cryptographyService);
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { CoreCrypto } from '@otak/core-crypto';
|
|
2
1
|
import type { APIClient } from '@wireapp/api-client';
|
|
3
2
|
import { MessageSendingStatus, Conversation, DefaultConversationRoleName, MutedStatus, NewConversation, QualifiedUserClients, UserClients, ClientMismatch } from '@wireapp/api-client/src/conversation';
|
|
4
3
|
import type { ConversationMemberLeaveEvent } from '@wireapp/api-client/src/event';
|
|
@@ -6,21 +5,22 @@ import type { QualifiedId, UserPreKeyBundleMap } from '@wireapp/api-client/src/u
|
|
|
6
5
|
import { MessageTimer, RemoveUsersParams } from '../../conversation/';
|
|
7
6
|
import type { RemoteData } from '../content';
|
|
8
7
|
import type { CryptographyService } from '../../cryptography/';
|
|
8
|
+
import type { MLSService } from '../../mls';
|
|
9
|
+
import type { NotificationService } from '../../notification';
|
|
9
10
|
import type { ClearConversationMessage, DeleteMessage, HideMessage, OtrMessage } from '../message/OtrMessage';
|
|
10
11
|
import { XOR } from '@wireapp/commons/src/main/util/TypeUtil';
|
|
11
|
-
import type { NotificationService } from '../../notification';
|
|
12
12
|
import { AddUsersParams, MessageSendingCallbacks, MessageSendingOptions, MLSReturnType, SendMlsMessageParams, SendProteusMessageParams } from './ConversationService.types';
|
|
13
13
|
export declare class ConversationService {
|
|
14
14
|
private readonly apiClient;
|
|
15
15
|
private readonly config;
|
|
16
|
-
private readonly coreCryptoClientProvider;
|
|
17
16
|
private readonly notificationService;
|
|
17
|
+
private readonly mlsService;
|
|
18
18
|
readonly messageTimer: MessageTimer;
|
|
19
19
|
private readonly messageService;
|
|
20
20
|
private selfConversationId?;
|
|
21
21
|
constructor(apiClient: APIClient, cryptographyService: CryptographyService, config: {
|
|
22
22
|
useQualifiedIds?: boolean;
|
|
23
|
-
},
|
|
23
|
+
}, notificationService: NotificationService, mlsService: MLSService);
|
|
24
24
|
private createEphemeral;
|
|
25
25
|
private getConversationQualifiedMembers;
|
|
26
26
|
/**
|
|
@@ -92,10 +92,6 @@ export declare class ConversationService {
|
|
|
92
92
|
})>;
|
|
93
93
|
/**
|
|
94
94
|
* Get a fresh list from backend of clients for all the participants of the conversation.
|
|
95
|
-
* This is a hacky way of getting all the clients for a conversation.
|
|
96
|
-
* The idea is to send an empty message to the backend to absolutely no users and let backend reply with a mismatch error.
|
|
97
|
-
* We then get the missing members in the mismatch, that is our fresh list of participants' clients.
|
|
98
|
-
*
|
|
99
95
|
* @param {string} conversationId
|
|
100
96
|
* @param {string} conversationDomain? - If given will send the message to the new qualified endpoint
|
|
101
97
|
*/
|
|
@@ -150,12 +146,10 @@ export declare class ConversationService {
|
|
|
150
146
|
* ################ MLS Functions ################
|
|
151
147
|
* ###############################################
|
|
152
148
|
*/
|
|
153
|
-
private getCoreCryptoKeyPackagesPayload;
|
|
154
|
-
private addUsersToExistingMLSConversation;
|
|
155
149
|
createMLSConversation(conversationData: NewConversation): Promise<MLSReturnType>;
|
|
156
150
|
private sendMLSMessage;
|
|
157
151
|
addUsersToMLSConversation({ qualifiedUserIds, groupId, conversationId, }: Required<AddUsersParams>): Promise<MLSReturnType>;
|
|
158
|
-
private
|
|
152
|
+
private storeLastKeyMaterialUpdateDateWithCurrentTime;
|
|
159
153
|
removeUsersFromMLSConversation({ groupId, conversationId, qualifiedUserIds, }: RemoveUsersParams): Promise<MLSReturnType>;
|
|
160
154
|
/**
|
|
161
155
|
* Will send an external proposal for the current device to join a specific conversation.
|
|
@@ -31,17 +31,13 @@ const MessageToProtoMapper_1 = require("../message/MessageToProtoMapper");
|
|
|
31
31
|
const ConversationService_types_1 = require("./ConversationService.types");
|
|
32
32
|
const bazinga64_1 = require("bazinga64");
|
|
33
33
|
const mapQualifiedUserClientIdsToFullyQualifiedClientIds_1 = require("../../util/mapQualifiedUserClientIdsToFullyQualifiedClientIds");
|
|
34
|
-
|
|
35
|
-
//they are returning regular array instead of Uint8Array for commit and welcome messages
|
|
36
|
-
const optionalToUint8Array = (array) => {
|
|
37
|
-
return Array.isArray(array) ? Uint8Array.from(array) : array;
|
|
38
|
-
};
|
|
34
|
+
const mls_1 = require("../../mls");
|
|
39
35
|
class ConversationService {
|
|
40
|
-
constructor(apiClient, cryptographyService, config,
|
|
36
|
+
constructor(apiClient, cryptographyService, config, notificationService, mlsService) {
|
|
41
37
|
this.apiClient = apiClient;
|
|
42
38
|
this.config = config;
|
|
43
|
-
this.coreCryptoClientProvider = coreCryptoClientProvider;
|
|
44
39
|
this.notificationService = notificationService;
|
|
40
|
+
this.mlsService = mlsService;
|
|
45
41
|
this.messageTimer = new conversation_2.MessageTimer();
|
|
46
42
|
this.messageService = new MessageService_1.MessageService(this.apiClient, cryptographyService);
|
|
47
43
|
}
|
|
@@ -63,11 +59,10 @@ class ConversationService {
|
|
|
63
59
|
* yourself in the list of users if you want to sync a message also to your
|
|
64
60
|
* other clients.
|
|
65
61
|
*/
|
|
66
|
-
return
|
|
62
|
+
return conversation.members.others
|
|
67
63
|
.filter(member => !!member.qualified_id)
|
|
68
64
|
.map(member => member.qualified_id)
|
|
69
|
-
|
|
70
|
-
.concat({ domain: this.apiClient.context.domain, id: conversation.members.self.id }));
|
|
65
|
+
.concat(conversation.members.self.qualified_id);
|
|
71
66
|
}
|
|
72
67
|
/**
|
|
73
68
|
* Will generate a prekey bundle for specific users.
|
|
@@ -515,37 +510,18 @@ class ConversationService {
|
|
|
515
510
|
}
|
|
516
511
|
/**
|
|
517
512
|
* Get a fresh list from backend of clients for all the participants of the conversation.
|
|
518
|
-
* This is a hacky way of getting all the clients for a conversation.
|
|
519
|
-
* The idea is to send an empty message to the backend to absolutely no users and let backend reply with a mismatch error.
|
|
520
|
-
* We then get the missing members in the mismatch, that is our fresh list of participants' clients.
|
|
521
|
-
*
|
|
522
513
|
* @param {string} conversationId
|
|
523
514
|
* @param {string} conversationDomain? - If given will send the message to the new qualified endpoint
|
|
524
515
|
*/
|
|
525
|
-
getAllParticipantsClients(conversationId, conversationDomain) {
|
|
526
|
-
const
|
|
527
|
-
const
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
};
|
|
535
|
-
if (conversationDomain && this.config.useQualifiedIds) {
|
|
536
|
-
await this.messageService.sendFederatedMessage(sendingClientId, recipients, text, {
|
|
537
|
-
conversationId: { id: conversationId, domain: conversationDomain },
|
|
538
|
-
onClientMismatch,
|
|
539
|
-
reportMissing: true,
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
else {
|
|
543
|
-
await this.messageService.sendMessage(sendingClientId, recipients, text, {
|
|
544
|
-
conversationId,
|
|
545
|
-
onClientMismatch,
|
|
546
|
-
});
|
|
547
|
-
}
|
|
548
|
-
});
|
|
516
|
+
async getAllParticipantsClients(conversationId, conversationDomain) {
|
|
517
|
+
const qualifiedMembers = await this.getConversationQualifiedMembers(conversationDomain ? { id: conversationId, domain: conversationDomain } : conversationId);
|
|
518
|
+
const allClients = await this.apiClient.api.user.postListClients({ qualified_users: qualifiedMembers });
|
|
519
|
+
const qualifiedUserClients = {};
|
|
520
|
+
Object.entries(allClients.qualified_user_map).map(([domain, userClientMap]) => Object.entries(userClientMap).map(async ([userId, clients]) => {
|
|
521
|
+
qualifiedUserClients[domain] || (qualifiedUserClients[domain] = {});
|
|
522
|
+
qualifiedUserClients[domain][userId] = clients.map(client => client.id);
|
|
523
|
+
}));
|
|
524
|
+
return qualifiedUserClients;
|
|
549
525
|
}
|
|
550
526
|
async deleteMessageLocal(conversationId, messageIdToHide, sendAsProtobuf, conversationDomain) {
|
|
551
527
|
const messageId = MessageBuilder_1.MessageBuilder.createId();
|
|
@@ -810,46 +786,6 @@ class ConversationService {
|
|
|
810
786
|
* ################ MLS Functions ################
|
|
811
787
|
* ###############################################
|
|
812
788
|
*/
|
|
813
|
-
async getCoreCryptoKeyPackagesPayload(qualifiedUsers) {
|
|
814
|
-
/**
|
|
815
|
-
* @note We need to fetch key packages for all the users
|
|
816
|
-
* we want to add to the new MLS conversations,
|
|
817
|
-
* includes self user too.
|
|
818
|
-
*/
|
|
819
|
-
const keyPackages = await Promise.all([
|
|
820
|
-
...qualifiedUsers.map(({ id, domain, skipOwn }) => this.apiClient.api.client.claimMLSKeyPackages(id, domain, skipOwn)),
|
|
821
|
-
]);
|
|
822
|
-
const coreCryptoKeyPackagesPayload = keyPackages.reduce((previousValue, currentValue) => {
|
|
823
|
-
// skip users that have not uploaded their MLS key packages
|
|
824
|
-
if (currentValue.key_packages.length > 0) {
|
|
825
|
-
return [
|
|
826
|
-
...previousValue,
|
|
827
|
-
...currentValue.key_packages.map(keyPackage => ({
|
|
828
|
-
id: bazinga64_1.Encoder.toBase64(keyPackage.client).asBytes,
|
|
829
|
-
kp: bazinga64_1.Decoder.fromBase64(keyPackage.key_package).asBytes,
|
|
830
|
-
})),
|
|
831
|
-
];
|
|
832
|
-
}
|
|
833
|
-
return previousValue;
|
|
834
|
-
}, []);
|
|
835
|
-
return coreCryptoKeyPackagesPayload;
|
|
836
|
-
}
|
|
837
|
-
async addUsersToExistingMLSConversation(groupIdDecodedFromBase64, invitee) {
|
|
838
|
-
const coreCryptoClient = this.coreCryptoClientProvider();
|
|
839
|
-
const memberAddedMessages = await coreCryptoClient.addClientsToConversation(groupIdDecodedFromBase64, invitee);
|
|
840
|
-
if (memberAddedMessages === null || memberAddedMessages === void 0 ? void 0 : memberAddedMessages.welcome) {
|
|
841
|
-
//@todo: it's temporary - we wait for core-crypto fix to return the actual Uint8Array instead of regular array
|
|
842
|
-
await this.apiClient.api.conversation.postMlsWelcomeMessage(optionalToUint8Array(memberAddedMessages.welcome));
|
|
843
|
-
}
|
|
844
|
-
if (memberAddedMessages === null || memberAddedMessages === void 0 ? void 0 : memberAddedMessages.commit) {
|
|
845
|
-
const messageResponse = await this.apiClient.api.conversation.postMlsMessage(
|
|
846
|
-
//@todo: it's temporary - we wait for core-crypto fix to return the actual Uint8Array instead of regular array
|
|
847
|
-
optionalToUint8Array(memberAddedMessages.commit));
|
|
848
|
-
await coreCryptoClient.commitAccepted(groupIdDecodedFromBase64);
|
|
849
|
-
return messageResponse;
|
|
850
|
-
}
|
|
851
|
-
return null;
|
|
852
|
-
}
|
|
853
789
|
async createMLSConversation(conversationData) {
|
|
854
790
|
/**
|
|
855
791
|
* @note For creating MLS conversations the users & qualified_users
|
|
@@ -866,9 +802,8 @@ class ConversationService {
|
|
|
866
802
|
if (!selfUserId) {
|
|
867
803
|
throw new Error('You need to pass self user qualified id in order to create an MLS conversation');
|
|
868
804
|
}
|
|
869
|
-
|
|
870
|
-
await
|
|
871
|
-
const coreCryptoKeyPackagesPayload = await this.getCoreCryptoKeyPackagesPayload([
|
|
805
|
+
await this.mlsService.createConversation(groupIdDecodedFromBase64);
|
|
806
|
+
const coreCryptoKeyPackagesPayload = await this.mlsService.getKeyPackagesPayload([
|
|
872
807
|
{
|
|
873
808
|
id: selfUserId.id,
|
|
874
809
|
domain: selfUserId.domain,
|
|
@@ -880,8 +815,14 @@ class ConversationService {
|
|
|
880
815
|
},
|
|
881
816
|
...qualifiedUsers,
|
|
882
817
|
]);
|
|
883
|
-
const response = await this.
|
|
818
|
+
const response = await this.mlsService.addUsersToExistingConversation(groupIdDecodedFromBase64, coreCryptoKeyPackagesPayload);
|
|
884
819
|
await this.notificationService.saveConversationGroupId(newConversation);
|
|
820
|
+
//We store the info when conversation (along with key material) was created, so we will know when to renew it
|
|
821
|
+
const groupCreationTimeStamp = new Date().getTime();
|
|
822
|
+
await this.notificationService.storeLastKeyMaterialUpdateDate({
|
|
823
|
+
previousUpdateDate: groupCreationTimeStamp,
|
|
824
|
+
groupId,
|
|
825
|
+
});
|
|
885
826
|
// We fetch the fresh version of the conversation created on backend with the newly added users
|
|
886
827
|
const conversation = await this.getConversations(qualifiedId.id);
|
|
887
828
|
return {
|
|
@@ -895,8 +836,7 @@ class ConversationService {
|
|
|
895
836
|
const groupIdBytes = bazinga64_1.Decoder.fromBase64(groupId).asBytes;
|
|
896
837
|
// immediately execute pending commits before sending the message
|
|
897
838
|
await this.notificationService.commitPendingProposals({ groupId });
|
|
898
|
-
const
|
|
899
|
-
const encrypted = await coreCryptoClient.encryptMessage(groupIdBytes, protocol_messaging_1.GenericMessage.encode(genericMessage).finish());
|
|
839
|
+
const encrypted = await this.mlsService.encryptMessage(groupIdBytes, protocol_messaging_1.GenericMessage.encode(genericMessage).finish());
|
|
900
840
|
try {
|
|
901
841
|
const { time = '' } = await this.apiClient.api.conversation.postMlsMessage(encrypted);
|
|
902
842
|
onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess(genericMessage, (time === null || time === void 0 ? void 0 : time.length) > 0 ? time : new Date().toISOString());
|
|
@@ -908,36 +848,25 @@ class ConversationService {
|
|
|
908
848
|
}
|
|
909
849
|
async addUsersToMLSConversation({ qualifiedUserIds, groupId, conversationId, }) {
|
|
910
850
|
const groupIdDecodedFromBase64 = bazinga64_1.Decoder.fromBase64(groupId).asBytes;
|
|
911
|
-
const coreCryptoKeyPackagesPayload = await this.
|
|
912
|
-
const response = await this.
|
|
851
|
+
const coreCryptoKeyPackagesPayload = await this.mlsService.getKeyPackagesPayload([...qualifiedUserIds]);
|
|
852
|
+
const response = await this.mlsService.addUsersToExistingConversation(groupIdDecodedFromBase64, coreCryptoKeyPackagesPayload);
|
|
913
853
|
const conversation = await this.getConversations(conversationId.id);
|
|
914
854
|
return {
|
|
915
855
|
events: (response === null || response === void 0 ? void 0 : response.events) || [],
|
|
916
856
|
conversation,
|
|
917
857
|
};
|
|
918
858
|
}
|
|
919
|
-
async
|
|
920
|
-
const
|
|
921
|
-
|
|
922
|
-
//@todo: it's temporary - we wait for core-crypto fix to return the actual Uint8Array instead of regular array
|
|
923
|
-
await this.apiClient.api.conversation.postMlsWelcomeMessage(optionalToUint8Array(commitBundle.welcome));
|
|
924
|
-
}
|
|
925
|
-
if (commitBundle === null || commitBundle === void 0 ? void 0 : commitBundle.commit) {
|
|
926
|
-
const messageResponse = await this.apiClient.api.conversation.postMlsMessage(
|
|
927
|
-
//@todo: it's temporary - we wait for core-crypto fix to return the actual Uint8Array instead of regular array
|
|
928
|
-
optionalToUint8Array(commitBundle.commit));
|
|
929
|
-
await coreCryptoClient.commitAccepted(groupIdDecodedFromBase64);
|
|
930
|
-
return messageResponse;
|
|
931
|
-
}
|
|
932
|
-
return null;
|
|
859
|
+
async storeLastKeyMaterialUpdateDateWithCurrentTime(groupId) {
|
|
860
|
+
const currentTime = new Date().getTime();
|
|
861
|
+
await this.notificationService.storeLastKeyMaterialUpdateDate({ groupId, previousUpdateDate: currentTime });
|
|
933
862
|
}
|
|
934
863
|
async removeUsersFromMLSConversation({ groupId, conversationId, qualifiedUserIds, }) {
|
|
935
|
-
const coreCryptoClient = this.coreCryptoClientProvider();
|
|
936
864
|
const groupIdDecodedFromBase64 = bazinga64_1.Decoder.fromBase64(groupId).asBytes;
|
|
937
865
|
const clientsToRemove = await this.apiClient.api.user.postListClients({ qualified_users: qualifiedUserIds });
|
|
938
866
|
const fullyQualifiedClientIds = (0, mapQualifiedUserClientIdsToFullyQualifiedClientIds_1.mapQualifiedUserClientIdsToFullyQualifiedClientIds)(clientsToRemove.qualified_user_map);
|
|
939
|
-
const
|
|
940
|
-
|
|
867
|
+
const messageResponse = await this.mlsService.removeClientsFromConversation(groupIdDecodedFromBase64, fullyQualifiedClientIds);
|
|
868
|
+
//key material gets updated after removing a user from the group, so we can reset last key update time value in the store
|
|
869
|
+
await this.storeLastKeyMaterialUpdateDateWithCurrentTime(groupId);
|
|
941
870
|
const conversation = await this.getConversations(conversationId.id);
|
|
942
871
|
return {
|
|
943
872
|
events: (messageResponse === null || messageResponse === void 0 ? void 0 : messageResponse.events) || [],
|
|
@@ -951,20 +880,20 @@ class ConversationService {
|
|
|
951
880
|
* @param epoch The current epoch of the local conversation
|
|
952
881
|
*/
|
|
953
882
|
async sendExternalJoinProposal(conversationGroupId, epoch) {
|
|
954
|
-
const coreCryptoClient = this.coreCryptoClientProvider();
|
|
955
883
|
const groupIdDecodedFromBase64 = bazinga64_1.Decoder.fromBase64(conversationGroupId).asBytes;
|
|
956
|
-
const externalProposal = await
|
|
884
|
+
const externalProposal = await this.mlsService.newExternalProposal(0 /* Add */, {
|
|
957
885
|
epoch,
|
|
958
886
|
conversationId: groupIdDecodedFromBase64,
|
|
959
887
|
});
|
|
960
888
|
await this.apiClient.api.conversation.postMlsMessage(
|
|
961
889
|
//@todo: it's temporary - we wait for core-crypto fix to return the actual Uint8Array instead of regular array
|
|
962
|
-
optionalToUint8Array(externalProposal));
|
|
890
|
+
(0, mls_1.optionalToUint8Array)(externalProposal));
|
|
891
|
+
//We store the info when user was added (and key material was created), so we will know when to renew it
|
|
892
|
+
await this.storeLastKeyMaterialUpdateDateWithCurrentTime(conversationGroupId);
|
|
963
893
|
}
|
|
964
894
|
async isMLSConversationEstablished(conversationGroupId) {
|
|
965
|
-
const coreCryptoClient = this.coreCryptoClientProvider();
|
|
966
895
|
const groupIdDecodedFromBase64 = bazinga64_1.Decoder.fromBase64(conversationGroupId).asBytes;
|
|
967
|
-
return
|
|
896
|
+
return this.mlsService.conversationExists(groupIdDecodedFromBase64);
|
|
968
897
|
}
|
|
969
898
|
}
|
|
970
899
|
exports.ConversationService = ConversationService;
|
|
@@ -6,7 +6,8 @@ export declare enum DatabaseStores {
|
|
|
6
6
|
PRE_KEYS = "prekeys",
|
|
7
7
|
SESSIONS = "sessions",
|
|
8
8
|
GROUP_IDS = "group_ids",
|
|
9
|
-
PENDING_PROPOSALS = "pending_proposals"
|
|
9
|
+
PENDING_PROPOSALS = "pending_proposals",
|
|
10
|
+
LAST_KEY_MATERIAL_UPDATE_DATES = "last_key_material_update_dates"
|
|
10
11
|
}
|
|
11
12
|
export declare class CryptographyDatabaseRepository {
|
|
12
13
|
private readonly storeEngine;
|
|
@@ -28,6 +28,7 @@ var DatabaseStores;
|
|
|
28
28
|
DatabaseStores["SESSIONS"] = "sessions";
|
|
29
29
|
DatabaseStores["GROUP_IDS"] = "group_ids";
|
|
30
30
|
DatabaseStores["PENDING_PROPOSALS"] = "pending_proposals";
|
|
31
|
+
DatabaseStores["LAST_KEY_MATERIAL_UPDATE_DATES"] = "last_key_material_update_dates";
|
|
31
32
|
})(DatabaseStores = exports.DatabaseStores || (exports.DatabaseStores = {}));
|
|
32
33
|
class CryptographyDatabaseRepository {
|
|
33
34
|
constructor(storeEngine) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { AddProposalArgs, CommitBundle, ConversationConfiguration, ConversationId, CoreCrypto, DecryptedMessage, ExternalProposalArgs, ExternalProposalType, ExternalRemoveProposalArgs, Invitee, ProposalArgs, ProposalType, RemoveProposalArgs } from '@otak/core-crypto/platforms/web/corecrypto';
|
|
2
|
+
import { APIClient } from '@wireapp/api-client';
|
|
3
|
+
import { QualifiedUsers } from '../../conversation';
|
|
4
|
+
import type { MLSConfig } from '../types';
|
|
5
|
+
import { PostMlsMessageResponse } from '@wireapp/api-client/src/conversation';
|
|
6
|
+
export declare const optionalToUint8Array: (array: Uint8Array | []) => Uint8Array;
|
|
7
|
+
export declare class MLSService {
|
|
8
|
+
readonly config: MLSConfig | undefined;
|
|
9
|
+
private readonly apiClient;
|
|
10
|
+
private readonly coreCryptoClientProvider;
|
|
11
|
+
constructor(config: MLSConfig | undefined, apiClient: APIClient, coreCryptoClientProvider: () => CoreCrypto | undefined);
|
|
12
|
+
private getCoreCryptoClient;
|
|
13
|
+
private uploadCommitBundle;
|
|
14
|
+
addUsersToExistingConversation(groupIdDecodedFromBase64: Uint8Array, invitee: Invitee[]): Promise<PostMlsMessageResponse | null>;
|
|
15
|
+
getKeyPackagesPayload(qualifiedUsers: QualifiedUsers[]): Promise<Invitee[]>;
|
|
16
|
+
newProposal(proposalType: ProposalType, args: ProposalArgs | AddProposalArgs | RemoveProposalArgs): Promise<Uint8Array>;
|
|
17
|
+
newExternalProposal(externalProposalType: ExternalProposalType, args: ExternalProposalArgs | ExternalRemoveProposalArgs): Promise<Uint8Array>;
|
|
18
|
+
processWelcomeMessage(welcomeMessage: Uint8Array): Promise<ConversationId>;
|
|
19
|
+
decryptMessage(conversationId: ConversationId, payload: Uint8Array): Promise<DecryptedMessage>;
|
|
20
|
+
encryptMessage(conversationId: ConversationId, message: Uint8Array): Promise<Uint8Array>;
|
|
21
|
+
updateKeyingMaterial(conversationId: ConversationId): Promise<PostMlsMessageResponse | null>;
|
|
22
|
+
createConversation(conversationId: ConversationId, configuration?: ConversationConfiguration): Promise<any>;
|
|
23
|
+
removeClientsFromConversation(conversationId: ConversationId, clientIds: Uint8Array[]): Promise<PostMlsMessageResponse | null>;
|
|
24
|
+
commitPendingProposals(conversationId: ConversationId): Promise<CommitBundle>;
|
|
25
|
+
conversationExists(conversationId: ConversationId): Promise<boolean>;
|
|
26
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Wire
|
|
4
|
+
* Copyright (C) 2022 Wire Swiss GmbH
|
|
5
|
+
*
|
|
6
|
+
* This program is free software: you can redistribute it and/or modify
|
|
7
|
+
* it under the terms of the GNU General Public License as published by
|
|
8
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
* (at your option) any later version.
|
|
10
|
+
*
|
|
11
|
+
* This program is distributed in the hope that it will be useful,
|
|
12
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
* GNU General Public License for more details.
|
|
15
|
+
*
|
|
16
|
+
* You should have received a copy of the GNU General Public License
|
|
17
|
+
* along with this program. If not, see http://www.gnu.org/licenses/.
|
|
18
|
+
*
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.MLSService = exports.optionalToUint8Array = void 0;
|
|
22
|
+
const bazinga64_1 = require("bazinga64");
|
|
23
|
+
//@todo: this function is temporary, we wait for the update from core-crypto side
|
|
24
|
+
//they are returning regular array instead of Uint8Array for commit and welcome messages
|
|
25
|
+
const optionalToUint8Array = (array) => {
|
|
26
|
+
return Array.isArray(array) ? Uint8Array.from(array) : array;
|
|
27
|
+
};
|
|
28
|
+
exports.optionalToUint8Array = optionalToUint8Array;
|
|
29
|
+
class MLSService {
|
|
30
|
+
constructor(config, apiClient, coreCryptoClientProvider) {
|
|
31
|
+
this.config = config;
|
|
32
|
+
this.apiClient = apiClient;
|
|
33
|
+
this.coreCryptoClientProvider = coreCryptoClientProvider;
|
|
34
|
+
}
|
|
35
|
+
getCoreCryptoClient() {
|
|
36
|
+
const client = this.coreCryptoClientProvider();
|
|
37
|
+
if (!client) {
|
|
38
|
+
throw new Error('Could not get coreCryptoClient');
|
|
39
|
+
}
|
|
40
|
+
return client;
|
|
41
|
+
}
|
|
42
|
+
async uploadCommitBundle(groupIdDecodedFromBase64, commitBundle) {
|
|
43
|
+
const coreCryptoClient = this.getCoreCryptoClient();
|
|
44
|
+
if (commitBundle.welcome) {
|
|
45
|
+
//@todo: it's temporary - we wait for core-crypto fix to return the actual Uint8Array instead of regular array
|
|
46
|
+
await this.apiClient.api.conversation.postMlsWelcomeMessage((0, exports.optionalToUint8Array)(commitBundle.welcome));
|
|
47
|
+
}
|
|
48
|
+
if (commitBundle.commit) {
|
|
49
|
+
const messageResponse = await this.apiClient.api.conversation.postMlsMessage(
|
|
50
|
+
//@todo: it's temporary - we wait for core-crypto fix to return the actual Uint8Array instead of regular array
|
|
51
|
+
(0, exports.optionalToUint8Array)(commitBundle.commit));
|
|
52
|
+
await coreCryptoClient.commitAccepted(groupIdDecodedFromBase64);
|
|
53
|
+
return messageResponse;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
async addUsersToExistingConversation(groupIdDecodedFromBase64, invitee) {
|
|
58
|
+
const coreCryptoClient = this.getCoreCryptoClient();
|
|
59
|
+
const memberAddedMessages = await coreCryptoClient.addClientsToConversation(groupIdDecodedFromBase64, invitee);
|
|
60
|
+
if (memberAddedMessages === null || memberAddedMessages === void 0 ? void 0 : memberAddedMessages.welcome) {
|
|
61
|
+
//@todo: it's temporary - we wait for core-crypto fix to return the actual Uint8Array instead of regular array
|
|
62
|
+
await this.apiClient.api.conversation.postMlsWelcomeMessage((0, exports.optionalToUint8Array)(memberAddedMessages.welcome));
|
|
63
|
+
}
|
|
64
|
+
if (memberAddedMessages === null || memberAddedMessages === void 0 ? void 0 : memberAddedMessages.commit) {
|
|
65
|
+
const messageResponse = await this.apiClient.api.conversation.postMlsMessage(
|
|
66
|
+
//@todo: it's temporary - we wait for core-crypto fix to return the actual Uint8Array instead of regular array
|
|
67
|
+
(0, exports.optionalToUint8Array)(memberAddedMessages.commit));
|
|
68
|
+
await coreCryptoClient.commitAccepted(groupIdDecodedFromBase64);
|
|
69
|
+
return messageResponse;
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
async getKeyPackagesPayload(qualifiedUsers) {
|
|
74
|
+
/**
|
|
75
|
+
* @note We need to fetch key packages for all the users
|
|
76
|
+
* we want to add to the new MLS conversations,
|
|
77
|
+
* includes self user too.
|
|
78
|
+
*/
|
|
79
|
+
const keyPackages = await Promise.all([
|
|
80
|
+
...qualifiedUsers.map(({ id, domain, skipOwn }) => this.apiClient.api.client.claimMLSKeyPackages(id, domain, skipOwn)),
|
|
81
|
+
]);
|
|
82
|
+
const coreCryptoKeyPackagesPayload = keyPackages.reduce((previousValue, currentValue) => {
|
|
83
|
+
// skip users that have not uploaded their MLS key packages
|
|
84
|
+
if (currentValue.key_packages.length > 0) {
|
|
85
|
+
return [
|
|
86
|
+
...previousValue,
|
|
87
|
+
...currentValue.key_packages.map(keyPackage => ({
|
|
88
|
+
id: bazinga64_1.Encoder.toBase64(keyPackage.client).asBytes,
|
|
89
|
+
kp: bazinga64_1.Decoder.fromBase64(keyPackage.key_package).asBytes,
|
|
90
|
+
})),
|
|
91
|
+
];
|
|
92
|
+
}
|
|
93
|
+
return previousValue;
|
|
94
|
+
}, []);
|
|
95
|
+
return coreCryptoKeyPackagesPayload;
|
|
96
|
+
}
|
|
97
|
+
async newProposal(proposalType, args) {
|
|
98
|
+
return this.getCoreCryptoClient().newProposal(proposalType, args);
|
|
99
|
+
}
|
|
100
|
+
async newExternalProposal(externalProposalType, args) {
|
|
101
|
+
return this.getCoreCryptoClient().newExternalProposal(externalProposalType, args);
|
|
102
|
+
}
|
|
103
|
+
async processWelcomeMessage(welcomeMessage) {
|
|
104
|
+
return this.getCoreCryptoClient().processWelcomeMessage(welcomeMessage);
|
|
105
|
+
}
|
|
106
|
+
async decryptMessage(conversationId, payload) {
|
|
107
|
+
return this.getCoreCryptoClient().decryptMessage(conversationId, payload);
|
|
108
|
+
}
|
|
109
|
+
async encryptMessage(conversationId, message) {
|
|
110
|
+
return this.getCoreCryptoClient().encryptMessage(conversationId, message);
|
|
111
|
+
}
|
|
112
|
+
async updateKeyingMaterial(conversationId) {
|
|
113
|
+
const commitBundle = await this.getCoreCryptoClient().updateKeyingMaterial(conversationId);
|
|
114
|
+
return this.uploadCommitBundle(conversationId, commitBundle);
|
|
115
|
+
}
|
|
116
|
+
async createConversation(conversationId, configuration) {
|
|
117
|
+
return this.getCoreCryptoClient().createConversation(conversationId, configuration);
|
|
118
|
+
}
|
|
119
|
+
async removeClientsFromConversation(conversationId, clientIds) {
|
|
120
|
+
const commitBundle = await this.getCoreCryptoClient().removeClientsFromConversation(conversationId, clientIds);
|
|
121
|
+
return this.uploadCommitBundle(conversationId, commitBundle);
|
|
122
|
+
}
|
|
123
|
+
async commitPendingProposals(conversationId) {
|
|
124
|
+
return this.getCoreCryptoClient().commitPendingProposals(conversationId);
|
|
125
|
+
}
|
|
126
|
+
async conversationExists(conversationId) {
|
|
127
|
+
return this.getCoreCryptoClient().conversationExists(conversationId);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
exports.MLSService = MLSService;
|
|
131
|
+
//# sourceMappingURL=MLSService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './MLSService/MLSService';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Wire
|
|
4
|
+
* Copyright (C) 2022 Wire Swiss GmbH
|
|
5
|
+
*
|
|
6
|
+
* This program is free software: you can redistribute it and/or modify
|
|
7
|
+
* it under the terms of the GNU General Public License as published by
|
|
8
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
* (at your option) any later version.
|
|
10
|
+
*
|
|
11
|
+
* This program is distributed in the hope that it will be useful,
|
|
12
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
* GNU General Public License for more details.
|
|
15
|
+
*
|
|
16
|
+
* You should have received a copy of the GNU General Public License
|
|
17
|
+
* along with this program. If not, see http://www.gnu.org/licenses/.
|
|
18
|
+
*
|
|
19
|
+
*/
|
|
20
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
23
|
+
}) : (function(o, m, k, k2) {
|
|
24
|
+
if (k2 === undefined) k2 = k;
|
|
25
|
+
o[k2] = m[k];
|
|
26
|
+
}));
|
|
27
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
28
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
29
|
+
};
|
|
30
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
+
__exportStar(require("./MLSService/MLSService"), exports);
|
|
32
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
declare type SecretCrypto<T> = {
|
|
2
|
+
encrypt: (value: Uint8Array) => Promise<T>;
|
|
3
|
+
decrypt: (payload: T) => Promise<Uint8Array>;
|
|
4
|
+
};
|
|
5
|
+
export interface MLSConfig<T = any> {
|
|
6
|
+
/**
|
|
7
|
+
* encrypt/decrypt function pair that will be called before storing/fetching secrets in the secrets database.
|
|
8
|
+
* If not provided will use the built in encryption mechanism
|
|
9
|
+
*/
|
|
10
|
+
secretsCrypto?: SecretCrypto<T>;
|
|
11
|
+
/**
|
|
12
|
+
* path on the public server to the core crypto wasm file.
|
|
13
|
+
* This file will be downloaded lazily when corecrypto is needed.
|
|
14
|
+
* It, thus, needs to know where, on the server, the file can be found
|
|
15
|
+
*/
|
|
16
|
+
coreCrypoWasmFilePath: string;
|
|
17
|
+
/**
|
|
18
|
+
* (milliseconds) period of time between automatic updates of the keying material (30 days by default)
|
|
19
|
+
*/
|
|
20
|
+
keyingMaterialUpdateThreshold?: number;
|
|
21
|
+
}
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Wire
|
|
4
|
+
* Copyright (C) 2022 Wire Swiss GmbH
|
|
5
|
+
*
|
|
6
|
+
* This program is free software: you can redistribute it and/or modify
|
|
7
|
+
* it under the terms of the GNU General Public License as published by
|
|
8
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
* (at your option) any later version.
|
|
10
|
+
*
|
|
11
|
+
* This program is distributed in the hope that it will be useful,
|
|
12
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
* GNU General Public License for more details.
|
|
15
|
+
*
|
|
16
|
+
* You should have received a copy of the GNU General Public License
|
|
17
|
+
* along with this program. If not, see http://www.gnu.org/licenses/.
|
|
18
|
+
*
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { BackendEvent } from '@wireapp/api-client/src/event';
|
|
2
2
|
import type { Notification } from '@wireapp/api-client/src/notification/';
|
|
3
3
|
import type { CRUDEngine } from '@wireapp/store-engine';
|
|
4
|
-
import { CommonMLS, CompoundGroupIdParams, StorePendingProposalsParams } from './types';
|
|
4
|
+
import { CommonMLS, CompoundGroupIdParams, LastKeyMaterialUpdateParams, StorePendingProposalsParams } from './types';
|
|
5
5
|
export declare enum DatabaseStores {
|
|
6
6
|
EVENTS = "events"
|
|
7
7
|
}
|
|
@@ -42,4 +42,25 @@ export declare class NotificationDatabaseRepository {
|
|
|
42
42
|
*
|
|
43
43
|
*/
|
|
44
44
|
getStoredPendingProposals(): Promise<StorePendingProposalsParams[]>;
|
|
45
|
+
/**
|
|
46
|
+
* ## MLS only ##
|
|
47
|
+
* Store groupIds with last key material update dates.
|
|
48
|
+
*
|
|
49
|
+
* @param {groupId} params.groupId - groupId of the mls conversation
|
|
50
|
+
* @param {previousUpdateDate} params.previousUpdateDate - date of the previous key material update
|
|
51
|
+
*/
|
|
52
|
+
storeLastKeyMaterialUpdateDate(params: LastKeyMaterialUpdateParams): Promise<boolean>;
|
|
53
|
+
/**
|
|
54
|
+
* ## MLS only ##
|
|
55
|
+
* Delete stored entries for last key materials update dates.
|
|
56
|
+
*
|
|
57
|
+
* @param {groupId} groupId - of the mls conversation
|
|
58
|
+
*/
|
|
59
|
+
deleteLastKeyMaterialUpdateDate({ groupId }: CommonMLS): Promise<boolean>;
|
|
60
|
+
/**
|
|
61
|
+
* ## MLS only ##
|
|
62
|
+
* Get all stored entries for last key materials update dates.
|
|
63
|
+
*
|
|
64
|
+
*/
|
|
65
|
+
getStoredLastKeyMaterialUpdateDates(): Promise<LastKeyMaterialUpdateParams[]>;
|
|
45
66
|
}
|
|
@@ -102,6 +102,35 @@ class NotificationDatabaseRepository {
|
|
|
102
102
|
async getStoredPendingProposals() {
|
|
103
103
|
return this.storeEngine.readAll(STORES.PENDING_PROPOSALS);
|
|
104
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* ## MLS only ##
|
|
107
|
+
* Store groupIds with last key material update dates.
|
|
108
|
+
*
|
|
109
|
+
* @param {groupId} params.groupId - groupId of the mls conversation
|
|
110
|
+
* @param {previousUpdateDate} params.previousUpdateDate - date of the previous key material update
|
|
111
|
+
*/
|
|
112
|
+
async storeLastKeyMaterialUpdateDate(params) {
|
|
113
|
+
await this.storeEngine.updateOrCreate(STORES.LAST_KEY_MATERIAL_UPDATE_DATES, `${params.groupId}`, params);
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* ## MLS only ##
|
|
118
|
+
* Delete stored entries for last key materials update dates.
|
|
119
|
+
*
|
|
120
|
+
* @param {groupId} groupId - of the mls conversation
|
|
121
|
+
*/
|
|
122
|
+
async deleteLastKeyMaterialUpdateDate({ groupId }) {
|
|
123
|
+
await this.storeEngine.delete(STORES.LAST_KEY_MATERIAL_UPDATE_DATES, `${groupId}`);
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* ## MLS only ##
|
|
128
|
+
* Get all stored entries for last key materials update dates.
|
|
129
|
+
*
|
|
130
|
+
*/
|
|
131
|
+
async getStoredLastKeyMaterialUpdateDates() {
|
|
132
|
+
return this.storeEngine.readAll(STORES.LAST_KEY_MATERIAL_UPDATE_DATES);
|
|
133
|
+
}
|
|
105
134
|
}
|
|
106
135
|
exports.NotificationDatabaseRepository = NotificationDatabaseRepository;
|
|
107
136
|
//# sourceMappingURL=NotificationDatabaseRepository.js.map
|
|
@@ -9,10 +9,10 @@ import { NotificationError } from '../CoreError';
|
|
|
9
9
|
import type { CryptographyService } from '../cryptography';
|
|
10
10
|
import { GenericMessage } from '@wireapp/protocol-messaging';
|
|
11
11
|
import { AbortHandler } from '@wireapp/api-client/src/tcp';
|
|
12
|
-
import type { CoreCrypto } from '@otak/core-crypto/platforms/web/corecrypto';
|
|
13
12
|
import { QualifiedId } from '@wireapp/api-client/src/user';
|
|
14
13
|
import { Conversation } from '@wireapp/api-client/src/conversation';
|
|
15
|
-
import { CommitPendingProposalsParams } from './types';
|
|
14
|
+
import { CommitPendingProposalsParams, LastKeyMaterialUpdateParams } from './types';
|
|
15
|
+
import type { MLSService } from '../mls';
|
|
16
16
|
export declare type HandledEventPayload = {
|
|
17
17
|
event: Events.BackendEvent;
|
|
18
18
|
mappedEvent?: PayloadBundle;
|
|
@@ -33,14 +33,14 @@ export interface NotificationService {
|
|
|
33
33
|
on(event: TOPIC.NOTIFICATION_ERROR, listener: (payload: NotificationError) => void): this;
|
|
34
34
|
}
|
|
35
35
|
export declare class NotificationService extends EventEmitter {
|
|
36
|
-
private readonly
|
|
36
|
+
private readonly mlsService;
|
|
37
37
|
private readonly apiClient;
|
|
38
38
|
private readonly backend;
|
|
39
39
|
private readonly cryptographyService;
|
|
40
40
|
private readonly database;
|
|
41
41
|
private readonly logger;
|
|
42
42
|
static readonly TOPIC: typeof TOPIC;
|
|
43
|
-
constructor(apiClient: APIClient, cryptographyService: CryptographyService,
|
|
43
|
+
constructor(apiClient: APIClient, cryptographyService: CryptographyService, mlsService: MLSService, storeEngine: CRUDEngine);
|
|
44
44
|
private getAllNotifications;
|
|
45
45
|
/** Should only be called with a completely new client. */
|
|
46
46
|
initializeNotificationStream(): Promise<string>;
|
|
@@ -103,5 +103,29 @@ export declare class NotificationService extends EventEmitter {
|
|
|
103
103
|
*
|
|
104
104
|
*/
|
|
105
105
|
checkExistingPendingProposals(): Promise<void>;
|
|
106
|
+
/**
|
|
107
|
+
* ## MLS only ##
|
|
108
|
+
* Store groupIds with last key material update dates.
|
|
109
|
+
*
|
|
110
|
+
* @param {groupId} params.groupId - groupId of the mls conversation
|
|
111
|
+
* @param {previousUpdateDate} params.previousUpdateDate - date of the previous key material update
|
|
112
|
+
*/
|
|
113
|
+
storeLastKeyMaterialUpdateDate(params: LastKeyMaterialUpdateParams): Promise<void>;
|
|
114
|
+
/**
|
|
115
|
+
* ## MLS only ##
|
|
116
|
+
* Renew key material for a given groupId
|
|
117
|
+
*
|
|
118
|
+
* @param groupId groupId of the conversation
|
|
119
|
+
*/
|
|
120
|
+
private renewKeyMaterial;
|
|
121
|
+
private createKeyMaterialUpdateTaskSchedulerId;
|
|
122
|
+
private scheduleTaskToRenewKeyMaterial;
|
|
123
|
+
/**
|
|
124
|
+
* ## MLS only ##
|
|
125
|
+
* Get all pending proposals from the database and schedule them
|
|
126
|
+
* Function must only be called once, after application start
|
|
127
|
+
*
|
|
128
|
+
*/
|
|
129
|
+
checkForKeyMaterialsUpdate(): Promise<void>;
|
|
106
130
|
}
|
|
107
131
|
export {};
|
|
@@ -53,6 +53,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
53
53
|
};
|
|
54
54
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
55
55
|
exports.NotificationService = void 0;
|
|
56
|
+
const commons_1 = require("@wireapp/commons");
|
|
56
57
|
const Events = __importStar(require("@wireapp/api-client/src/event"));
|
|
57
58
|
const store_engine_1 = require("@wireapp/store-engine");
|
|
58
59
|
const events_1 = require("events");
|
|
@@ -66,14 +67,16 @@ const NotificationDatabaseRepository_1 = require("./NotificationDatabaseReposito
|
|
|
66
67
|
const protocol_messaging_1 = require("@wireapp/protocol-messaging");
|
|
67
68
|
const bazinga64_1 = require("bazinga64");
|
|
68
69
|
const TaskScheduler_1 = require("../util/TaskScheduler/TaskScheduler");
|
|
70
|
+
const LowPrecisionTaskScheduler_1 = require("../util/LowPrecisionTaskScheduler/LowPrecisionTaskScheduler");
|
|
69
71
|
var TOPIC;
|
|
70
72
|
(function (TOPIC) {
|
|
71
73
|
TOPIC["NOTIFICATION_ERROR"] = "NotificationService.TOPIC.NOTIFICATION_ERROR";
|
|
72
74
|
})(TOPIC || (TOPIC = {}));
|
|
75
|
+
const DEFAULT_KEYING_MATERIAL_UPDATE_THRESHOLD = 1000 * 60 * 60 * 24 * 30; //30 days
|
|
73
76
|
class NotificationService extends events_1.EventEmitter {
|
|
74
|
-
constructor(apiClient, cryptographyService,
|
|
77
|
+
constructor(apiClient, cryptographyService, mlsService, storeEngine) {
|
|
75
78
|
super();
|
|
76
|
-
this.
|
|
79
|
+
this.mlsService = mlsService;
|
|
77
80
|
this.logger = (0, logdown_1.default)('@wireapp/core/notification/NotificationService', {
|
|
78
81
|
logger: console,
|
|
79
82
|
markdown: false,
|
|
@@ -211,15 +214,11 @@ class NotificationService extends events_1.EventEmitter {
|
|
|
211
214
|
}
|
|
212
215
|
async handleEvent(event, source, dryRun = false) {
|
|
213
216
|
var _a, _b;
|
|
214
|
-
const coreCryptoClient = this.coreCryptoClientProvider();
|
|
215
217
|
switch (event.type) {
|
|
216
218
|
case Events.CONVERSATION_EVENT.MLS_WELCOME_MESSAGE:
|
|
217
|
-
if (!coreCryptoClient) {
|
|
218
|
-
throw new Error('Unable to access core crypto client');
|
|
219
|
-
}
|
|
220
219
|
const data = bazinga64_1.Decoder.fromBase64(event.data).asBytes;
|
|
221
220
|
// We extract the groupId from the welcome message and let coreCrypto store this group
|
|
222
|
-
const newGroupId = await
|
|
221
|
+
const newGroupId = await this.mlsService.processWelcomeMessage(data);
|
|
223
222
|
const groupIdStr = bazinga64_1.Encoder.toBase64(newGroupId).asString;
|
|
224
223
|
// The groupId can then be sent back to the consumer
|
|
225
224
|
return {
|
|
@@ -227,13 +226,10 @@ class NotificationService extends events_1.EventEmitter {
|
|
|
227
226
|
mappedEvent: ConversationMapper_1.ConversationMapper.mapConversationEvent(Object.assign(Object.assign({}, event), { data: groupIdStr }), source),
|
|
228
227
|
};
|
|
229
228
|
case Events.CONVERSATION_EVENT.MLS_MESSAGE_ADD:
|
|
230
|
-
if (!coreCryptoClient) {
|
|
231
|
-
throw new Error('Unable to access core crypto client');
|
|
232
|
-
}
|
|
233
229
|
const encryptedData = bazinga64_1.Decoder.fromBase64(event.data).asBytes;
|
|
234
230
|
const groupId = await this.getUint8ArrayFromConversationGroupId((_a = event.qualified_conversation) !== null && _a !== void 0 ? _a : { id: event.conversation, domain: '' });
|
|
235
231
|
// Check if the message includes proposals
|
|
236
|
-
const { proposals, commitDelay, message } = await
|
|
232
|
+
const { proposals, commitDelay, message } = await this.mlsService.decryptMessage(groupId, encryptedData);
|
|
237
233
|
if (proposals.length > 0) {
|
|
238
234
|
await this.handlePendingProposals({
|
|
239
235
|
groupId: groupId.toString(),
|
|
@@ -369,12 +365,8 @@ class NotificationService extends events_1.EventEmitter {
|
|
|
369
365
|
* @param skipDelete if true, do not delete the pending proposals from the database
|
|
370
366
|
*/
|
|
371
367
|
async commitPendingProposals({ groupId, skipDelete = false }) {
|
|
372
|
-
const coreCryptoClient = this.coreCryptoClientProvider();
|
|
373
|
-
if (!coreCryptoClient) {
|
|
374
|
-
throw new Error('Could not get coreCryptoClient');
|
|
375
|
-
}
|
|
376
368
|
try {
|
|
377
|
-
await
|
|
369
|
+
await this.mlsService.commitPendingProposals(bazinga64_1.Decoder.fromBase64(groupId).asBytes);
|
|
378
370
|
if (!skipDelete) {
|
|
379
371
|
TaskScheduler_1.TaskScheduler.cancelTask(groupId);
|
|
380
372
|
await this.database.deletePendingProposal({ groupId });
|
|
@@ -405,6 +397,71 @@ class NotificationService extends events_1.EventEmitter {
|
|
|
405
397
|
this.logger.error('Could not get pending proposals', error);
|
|
406
398
|
}
|
|
407
399
|
}
|
|
400
|
+
/**
|
|
401
|
+
* ## MLS only ##
|
|
402
|
+
* Store groupIds with last key material update dates.
|
|
403
|
+
*
|
|
404
|
+
* @param {groupId} params.groupId - groupId of the mls conversation
|
|
405
|
+
* @param {previousUpdateDate} params.previousUpdateDate - date of the previous key material update
|
|
406
|
+
*/
|
|
407
|
+
async storeLastKeyMaterialUpdateDate(params) {
|
|
408
|
+
await this.database.storeLastKeyMaterialUpdateDate(params);
|
|
409
|
+
await this.scheduleTaskToRenewKeyMaterial(params);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* ## MLS only ##
|
|
413
|
+
* Renew key material for a given groupId
|
|
414
|
+
*
|
|
415
|
+
* @param groupId groupId of the conversation
|
|
416
|
+
*/
|
|
417
|
+
async renewKeyMaterial({ groupId }) {
|
|
418
|
+
try {
|
|
419
|
+
const groupConversationExists = await this.mlsService.conversationExists(bazinga64_1.Decoder.fromBase64(groupId).asBytes);
|
|
420
|
+
if (!groupConversationExists) {
|
|
421
|
+
await this.database.deleteLastKeyMaterialUpdateDate({ groupId });
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const groupIdDecodedFromBase64 = bazinga64_1.Decoder.fromBase64(groupId).asBytes;
|
|
425
|
+
await this.mlsService.updateKeyingMaterial(groupIdDecodedFromBase64);
|
|
426
|
+
const keyRenewalTime = new Date().getTime();
|
|
427
|
+
const keyMaterialUpdateDate = { groupId, previousUpdateDate: keyRenewalTime };
|
|
428
|
+
await this.storeLastKeyMaterialUpdateDate(keyMaterialUpdateDate);
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
this.logger.error(`Error while renewing key material for groupId ${groupId}`, error);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
createKeyMaterialUpdateTaskSchedulerId(groupId) {
|
|
435
|
+
return `renew-key-material-update-${groupId}`;
|
|
436
|
+
}
|
|
437
|
+
async scheduleTaskToRenewKeyMaterial({ groupId, previousUpdateDate }) {
|
|
438
|
+
var _a;
|
|
439
|
+
//given period of time (30 days by default) after last update date renew key material
|
|
440
|
+
const keyingMaterialUpdateThreshold = ((_a = this.mlsService.config) === null || _a === void 0 ? void 0 : _a.keyingMaterialUpdateThreshold) || DEFAULT_KEYING_MATERIAL_UPDATE_THRESHOLD;
|
|
441
|
+
const firingDate = previousUpdateDate + keyingMaterialUpdateThreshold;
|
|
442
|
+
const key = this.createKeyMaterialUpdateTaskSchedulerId(groupId);
|
|
443
|
+
LowPrecisionTaskScheduler_1.LowPrecisionTaskScheduler.addTask({
|
|
444
|
+
task: () => this.renewKeyMaterial({ groupId }),
|
|
445
|
+
intervalDelay: commons_1.TimeUtil.TimeInMillis.MINUTE,
|
|
446
|
+
firingDate,
|
|
447
|
+
key,
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* ## MLS only ##
|
|
452
|
+
* Get all pending proposals from the database and schedule them
|
|
453
|
+
* Function must only be called once, after application start
|
|
454
|
+
*
|
|
455
|
+
*/
|
|
456
|
+
async checkForKeyMaterialsUpdate() {
|
|
457
|
+
try {
|
|
458
|
+
const keyMaterialUpdateDates = await this.database.getStoredLastKeyMaterialUpdateDates();
|
|
459
|
+
keyMaterialUpdateDates.forEach(date => this.scheduleTaskToRenewKeyMaterial(date));
|
|
460
|
+
}
|
|
461
|
+
catch (error) {
|
|
462
|
+
this.logger.error('Could not get last key material update dates', error);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
408
465
|
}
|
|
409
466
|
exports.NotificationService = NotificationService;
|
|
410
467
|
NotificationService.TOPIC = TOPIC;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
interface IntervalTask {
|
|
2
|
+
key: string;
|
|
3
|
+
firingDate: number;
|
|
4
|
+
task: () => Promise<any>;
|
|
5
|
+
}
|
|
6
|
+
interface ScheduleLowPrecisionTaskParams extends IntervalTask {
|
|
7
|
+
intervalDelay: number;
|
|
8
|
+
}
|
|
9
|
+
interface CancelLowPrecisionTaskParams {
|
|
10
|
+
key: string;
|
|
11
|
+
intervalDelay: number;
|
|
12
|
+
}
|
|
13
|
+
interface CancelLowPrecisionTaskParams {
|
|
14
|
+
key: string;
|
|
15
|
+
intervalDelay: number;
|
|
16
|
+
}
|
|
17
|
+
export declare const LowPrecisionTaskScheduler: {
|
|
18
|
+
addTask: ({ key, firingDate, task, intervalDelay }: ScheduleLowPrecisionTaskParams) => void;
|
|
19
|
+
cancelTask: ({ intervalDelay, key }: CancelLowPrecisionTaskParams) => void;
|
|
20
|
+
};
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Wire
|
|
4
|
+
* Copyright (C) 2022 Wire Swiss GmbH
|
|
5
|
+
*
|
|
6
|
+
* This program is free software: you can redistribute it and/or modify
|
|
7
|
+
* it under the terms of the GNU General Public License as published by
|
|
8
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
* (at your option) any later version.
|
|
10
|
+
*
|
|
11
|
+
* This program is distributed in the hope that it will be useful,
|
|
12
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
* GNU General Public License for more details.
|
|
15
|
+
*
|
|
16
|
+
* You should have received a copy of the GNU General Public License
|
|
17
|
+
* along with this program. If not, see http://www.gnu.org/licenses/.
|
|
18
|
+
*
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.LowPrecisionTaskScheduler = void 0;
|
|
22
|
+
const intervals = {};
|
|
23
|
+
const addTask = ({ key, firingDate, task, intervalDelay }) => {
|
|
24
|
+
var _a, _b;
|
|
25
|
+
const existingIntervalId = (_a = intervals[intervalDelay]) === null || _a === void 0 ? void 0 : _a.timeoutId;
|
|
26
|
+
if (existingIntervalId) {
|
|
27
|
+
clearInterval(existingIntervalId);
|
|
28
|
+
}
|
|
29
|
+
const tasks = ((_b = intervals[intervalDelay]) === null || _b === void 0 ? void 0 : _b.tasks) || [];
|
|
30
|
+
tasks.push({ key, firingDate, task });
|
|
31
|
+
const timeoutId = setInterval(async () => {
|
|
32
|
+
var _a;
|
|
33
|
+
const nowTime = new Date().getTime();
|
|
34
|
+
const tasks = (_a = intervals[intervalDelay]) === null || _a === void 0 ? void 0 : _a.tasks;
|
|
35
|
+
if ((tasks === null || tasks === void 0 ? void 0 : tasks.length) !== 0) {
|
|
36
|
+
const tasksToExecute = await tasks.reduce(async (accPromise, { firingDate, task }) => {
|
|
37
|
+
const acc = await accPromise;
|
|
38
|
+
if (nowTime >= firingDate) {
|
|
39
|
+
const taskPromise = task();
|
|
40
|
+
acc.push(taskPromise);
|
|
41
|
+
cancelTask({ intervalDelay, key });
|
|
42
|
+
}
|
|
43
|
+
return acc;
|
|
44
|
+
}, Promise.resolve([]));
|
|
45
|
+
await Promise.all(tasksToExecute);
|
|
46
|
+
}
|
|
47
|
+
}, intervalDelay);
|
|
48
|
+
intervals[intervalDelay] = { timeoutId, tasks };
|
|
49
|
+
};
|
|
50
|
+
const cancelTask = ({ intervalDelay, key }) => {
|
|
51
|
+
if (intervals[intervalDelay]) {
|
|
52
|
+
const tasks = intervals[intervalDelay].tasks || [];
|
|
53
|
+
const newTasks = tasks.filter(task => task.key !== key);
|
|
54
|
+
intervals[intervalDelay].tasks = newTasks;
|
|
55
|
+
if (newTasks.length === 0) {
|
|
56
|
+
clearInterval(intervals[intervalDelay].timeoutId);
|
|
57
|
+
delete intervals[intervalDelay];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
exports.LowPrecisionTaskScheduler = { addTask, cancelTask };
|
|
62
|
+
//# sourceMappingURL=LowPrecisionTaskScheduler.js.map
|