@wireapp/core 30.4.0 → 30.5.1
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 +27 -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 -7
- package/src/main/conversation/ConversationService/ConversationService.js +26 -77
- package/src/main/cryptography/CryptographyDatabaseRepository.d.ts +2 -1
- package/src/main/cryptography/CryptographyDatabaseRepository.js +4 -1
- 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,33 @@
|
|
|
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.1](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/compare/@wireapp/core@30.5.0...@wireapp/core@30.5.1) (2022-09-12)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @wireapp/core
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# [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)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* **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))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## [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)
|
|
26
|
+
|
|
27
|
+
**Note:** Version bump only for package @wireapp/core
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
6
33
|
# [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)
|
|
7
34
|
|
|
8
35
|
|
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.2.
|
|
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.1",
|
|
81
|
+
"gitHead": "b855fc2abbad739bd3db007455051eb89ebe9e10"
|
|
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
|
/**
|
|
@@ -146,12 +146,10 @@ export declare class ConversationService {
|
|
|
146
146
|
* ################ MLS Functions ################
|
|
147
147
|
* ###############################################
|
|
148
148
|
*/
|
|
149
|
-
private getCoreCryptoKeyPackagesPayload;
|
|
150
|
-
private addUsersToExistingMLSConversation;
|
|
151
149
|
createMLSConversation(conversationData: NewConversation): Promise<MLSReturnType>;
|
|
152
150
|
private sendMLSMessage;
|
|
153
151
|
addUsersToMLSConversation({ qualifiedUserIds, groupId, conversationId, }: Required<AddUsersParams>): Promise<MLSReturnType>;
|
|
154
|
-
private
|
|
152
|
+
private storeLastKeyMaterialUpdateDateWithCurrentTime;
|
|
155
153
|
removeUsersFromMLSConversation({ groupId, conversationId, qualifiedUserIds, }: RemoveUsersParams): Promise<MLSReturnType>;
|
|
156
154
|
/**
|
|
157
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
|
}
|
|
@@ -790,46 +786,6 @@ class ConversationService {
|
|
|
790
786
|
* ################ MLS Functions ################
|
|
791
787
|
* ###############################################
|
|
792
788
|
*/
|
|
793
|
-
async getCoreCryptoKeyPackagesPayload(qualifiedUsers) {
|
|
794
|
-
/**
|
|
795
|
-
* @note We need to fetch key packages for all the users
|
|
796
|
-
* we want to add to the new MLS conversations,
|
|
797
|
-
* includes self user too.
|
|
798
|
-
*/
|
|
799
|
-
const keyPackages = await Promise.all([
|
|
800
|
-
...qualifiedUsers.map(({ id, domain, skipOwn }) => this.apiClient.api.client.claimMLSKeyPackages(id, domain, skipOwn)),
|
|
801
|
-
]);
|
|
802
|
-
const coreCryptoKeyPackagesPayload = keyPackages.reduce((previousValue, currentValue) => {
|
|
803
|
-
// skip users that have not uploaded their MLS key packages
|
|
804
|
-
if (currentValue.key_packages.length > 0) {
|
|
805
|
-
return [
|
|
806
|
-
...previousValue,
|
|
807
|
-
...currentValue.key_packages.map(keyPackage => ({
|
|
808
|
-
id: bazinga64_1.Encoder.toBase64(keyPackage.client).asBytes,
|
|
809
|
-
kp: bazinga64_1.Decoder.fromBase64(keyPackage.key_package).asBytes,
|
|
810
|
-
})),
|
|
811
|
-
];
|
|
812
|
-
}
|
|
813
|
-
return previousValue;
|
|
814
|
-
}, []);
|
|
815
|
-
return coreCryptoKeyPackagesPayload;
|
|
816
|
-
}
|
|
817
|
-
async addUsersToExistingMLSConversation(groupIdDecodedFromBase64, invitee) {
|
|
818
|
-
const coreCryptoClient = this.coreCryptoClientProvider();
|
|
819
|
-
const memberAddedMessages = await coreCryptoClient.addClientsToConversation(groupIdDecodedFromBase64, invitee);
|
|
820
|
-
if (memberAddedMessages === null || memberAddedMessages === void 0 ? void 0 : memberAddedMessages.welcome) {
|
|
821
|
-
//@todo: it's temporary - we wait for core-crypto fix to return the actual Uint8Array instead of regular array
|
|
822
|
-
await this.apiClient.api.conversation.postMlsWelcomeMessage(optionalToUint8Array(memberAddedMessages.welcome));
|
|
823
|
-
}
|
|
824
|
-
if (memberAddedMessages === null || memberAddedMessages === void 0 ? void 0 : memberAddedMessages.commit) {
|
|
825
|
-
const messageResponse = await this.apiClient.api.conversation.postMlsMessage(
|
|
826
|
-
//@todo: it's temporary - we wait for core-crypto fix to return the actual Uint8Array instead of regular array
|
|
827
|
-
optionalToUint8Array(memberAddedMessages.commit));
|
|
828
|
-
await coreCryptoClient.commitAccepted(groupIdDecodedFromBase64);
|
|
829
|
-
return messageResponse;
|
|
830
|
-
}
|
|
831
|
-
return null;
|
|
832
|
-
}
|
|
833
789
|
async createMLSConversation(conversationData) {
|
|
834
790
|
/**
|
|
835
791
|
* @note For creating MLS conversations the users & qualified_users
|
|
@@ -846,9 +802,8 @@ class ConversationService {
|
|
|
846
802
|
if (!selfUserId) {
|
|
847
803
|
throw new Error('You need to pass self user qualified id in order to create an MLS conversation');
|
|
848
804
|
}
|
|
849
|
-
|
|
850
|
-
await
|
|
851
|
-
const coreCryptoKeyPackagesPayload = await this.getCoreCryptoKeyPackagesPayload([
|
|
805
|
+
await this.mlsService.createConversation(groupIdDecodedFromBase64);
|
|
806
|
+
const coreCryptoKeyPackagesPayload = await this.mlsService.getKeyPackagesPayload([
|
|
852
807
|
{
|
|
853
808
|
id: selfUserId.id,
|
|
854
809
|
domain: selfUserId.domain,
|
|
@@ -860,8 +815,14 @@ class ConversationService {
|
|
|
860
815
|
},
|
|
861
816
|
...qualifiedUsers,
|
|
862
817
|
]);
|
|
863
|
-
const response = await this.
|
|
818
|
+
const response = await this.mlsService.addUsersToExistingConversation(groupIdDecodedFromBase64, coreCryptoKeyPackagesPayload);
|
|
864
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
|
+
});
|
|
865
826
|
// We fetch the fresh version of the conversation created on backend with the newly added users
|
|
866
827
|
const conversation = await this.getConversations(qualifiedId.id);
|
|
867
828
|
return {
|
|
@@ -875,8 +836,7 @@ class ConversationService {
|
|
|
875
836
|
const groupIdBytes = bazinga64_1.Decoder.fromBase64(groupId).asBytes;
|
|
876
837
|
// immediately execute pending commits before sending the message
|
|
877
838
|
await this.notificationService.commitPendingProposals({ groupId });
|
|
878
|
-
const
|
|
879
|
-
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());
|
|
880
840
|
try {
|
|
881
841
|
const { time = '' } = await this.apiClient.api.conversation.postMlsMessage(encrypted);
|
|
882
842
|
onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess(genericMessage, (time === null || time === void 0 ? void 0 : time.length) > 0 ? time : new Date().toISOString());
|
|
@@ -888,36 +848,25 @@ class ConversationService {
|
|
|
888
848
|
}
|
|
889
849
|
async addUsersToMLSConversation({ qualifiedUserIds, groupId, conversationId, }) {
|
|
890
850
|
const groupIdDecodedFromBase64 = bazinga64_1.Decoder.fromBase64(groupId).asBytes;
|
|
891
|
-
const coreCryptoKeyPackagesPayload = await this.
|
|
892
|
-
const response = await this.
|
|
851
|
+
const coreCryptoKeyPackagesPayload = await this.mlsService.getKeyPackagesPayload([...qualifiedUserIds]);
|
|
852
|
+
const response = await this.mlsService.addUsersToExistingConversation(groupIdDecodedFromBase64, coreCryptoKeyPackagesPayload);
|
|
893
853
|
const conversation = await this.getConversations(conversationId.id);
|
|
894
854
|
return {
|
|
895
855
|
events: (response === null || response === void 0 ? void 0 : response.events) || [],
|
|
896
856
|
conversation,
|
|
897
857
|
};
|
|
898
858
|
}
|
|
899
|
-
async
|
|
900
|
-
const
|
|
901
|
-
|
|
902
|
-
//@todo: it's temporary - we wait for core-crypto fix to return the actual Uint8Array instead of regular array
|
|
903
|
-
await this.apiClient.api.conversation.postMlsWelcomeMessage(optionalToUint8Array(commitBundle.welcome));
|
|
904
|
-
}
|
|
905
|
-
if (commitBundle === null || commitBundle === void 0 ? void 0 : commitBundle.commit) {
|
|
906
|
-
const messageResponse = await this.apiClient.api.conversation.postMlsMessage(
|
|
907
|
-
//@todo: it's temporary - we wait for core-crypto fix to return the actual Uint8Array instead of regular array
|
|
908
|
-
optionalToUint8Array(commitBundle.commit));
|
|
909
|
-
await coreCryptoClient.commitAccepted(groupIdDecodedFromBase64);
|
|
910
|
-
return messageResponse;
|
|
911
|
-
}
|
|
912
|
-
return null;
|
|
859
|
+
async storeLastKeyMaterialUpdateDateWithCurrentTime(groupId) {
|
|
860
|
+
const currentTime = new Date().getTime();
|
|
861
|
+
await this.notificationService.storeLastKeyMaterialUpdateDate({ groupId, previousUpdateDate: currentTime });
|
|
913
862
|
}
|
|
914
863
|
async removeUsersFromMLSConversation({ groupId, conversationId, qualifiedUserIds, }) {
|
|
915
|
-
const coreCryptoClient = this.coreCryptoClientProvider();
|
|
916
864
|
const groupIdDecodedFromBase64 = bazinga64_1.Decoder.fromBase64(groupId).asBytes;
|
|
917
865
|
const clientsToRemove = await this.apiClient.api.user.postListClients({ qualified_users: qualifiedUserIds });
|
|
918
866
|
const fullyQualifiedClientIds = (0, mapQualifiedUserClientIdsToFullyQualifiedClientIds_1.mapQualifiedUserClientIdsToFullyQualifiedClientIds)(clientsToRemove.qualified_user_map);
|
|
919
|
-
const
|
|
920
|
-
|
|
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);
|
|
921
870
|
const conversation = await this.getConversations(conversationId.id);
|
|
922
871
|
return {
|
|
923
872
|
events: (messageResponse === null || messageResponse === void 0 ? void 0 : messageResponse.events) || [],
|
|
@@ -931,20 +880,20 @@ class ConversationService {
|
|
|
931
880
|
* @param epoch The current epoch of the local conversation
|
|
932
881
|
*/
|
|
933
882
|
async sendExternalJoinProposal(conversationGroupId, epoch) {
|
|
934
|
-
const coreCryptoClient = this.coreCryptoClientProvider();
|
|
935
883
|
const groupIdDecodedFromBase64 = bazinga64_1.Decoder.fromBase64(conversationGroupId).asBytes;
|
|
936
|
-
const externalProposal = await
|
|
884
|
+
const externalProposal = await this.mlsService.newExternalProposal(0 /* Add */, {
|
|
937
885
|
epoch,
|
|
938
886
|
conversationId: groupIdDecodedFromBase64,
|
|
939
887
|
});
|
|
940
888
|
await this.apiClient.api.conversation.postMlsMessage(
|
|
941
889
|
//@todo: it's temporary - we wait for core-crypto fix to return the actual Uint8Array instead of regular array
|
|
942
|
-
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);
|
|
943
893
|
}
|
|
944
894
|
async isMLSConversationEstablished(conversationGroupId) {
|
|
945
|
-
const coreCryptoClient = this.coreCryptoClientProvider();
|
|
946
895
|
const groupIdDecodedFromBase64 = bazinga64_1.Decoder.fromBase64(conversationGroupId).asBytes;
|
|
947
|
-
return
|
|
896
|
+
return this.mlsService.conversationExists(groupIdDecodedFromBase64);
|
|
948
897
|
}
|
|
949
898
|
}
|
|
950
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,13 +28,16 @@ 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) {
|
|
34
35
|
this.storeEngine = storeEngine;
|
|
35
36
|
}
|
|
36
37
|
deleteStores() {
|
|
37
|
-
return Promise.all(
|
|
38
|
+
return Promise.all(
|
|
39
|
+
//make sure we use enum's lowercase values, not uppercase keys
|
|
40
|
+
Object.values(CryptographyDatabaseRepository.STORES).map(store => this.storeEngine.deleteAll(store)));
|
|
38
41
|
}
|
|
39
42
|
}
|
|
40
43
|
exports.CryptographyDatabaseRepository = CryptographyDatabaseRepository;
|
|
@@ -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
|