@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 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.0",
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.3.0",
81
- "gitHead": "0a4d4b6057006da93a731115ab02c9753b155127"
80
+ "version": "30.5.0",
81
+ "gitHead": "a2a55f074ebfb3fe5e3a32df1153a9c1ae7091dd"
82
82
  }
@@ -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;
@@ -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, this.storeEngine, () => this.coreCryptoClient);
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
- }, () => this.coreCryptoClient, notificationService);
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
- }, coreCryptoClientProvider: () => CoreCrypto, notificationService: NotificationService);
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 sendCommitBundleRemovalMessages;
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
- //@todo: this function is temporary, we wait for the update from core-crypto side
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, coreCryptoClientProvider, notificationService) {
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 (conversation.members.others
62
+ return conversation.members.others
67
63
  .filter(member => !!member.qualified_id)
68
64
  .map(member => member.qualified_id)
69
- // TODO(Federation): Use 'domain' from 'conversation.members.self' when backend has it implemented
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 sendingClientId = this.apiClient.validatedClientId;
527
- const recipients = {};
528
- const text = new Uint8Array();
529
- return new Promise(async (resolve) => {
530
- const onClientMismatch = (mismatch) => {
531
- resolve(mismatch.missing);
532
- // When the mismatch happens, we ask the messageService to cancel the sending
533
- return false;
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
- const coreCryptoClient = this.coreCryptoClientProvider();
870
- await coreCryptoClient.createConversation(groupIdDecodedFromBase64);
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.addUsersToExistingMLSConversation(groupIdDecodedFromBase64, coreCryptoKeyPackagesPayload);
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 coreCryptoClient = this.coreCryptoClientProvider();
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.getCoreCryptoKeyPackagesPayload([...qualifiedUserIds]);
912
- const response = await this.addUsersToExistingMLSConversation(groupIdDecodedFromBase64, coreCryptoKeyPackagesPayload);
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 sendCommitBundleRemovalMessages(groupIdDecodedFromBase64, commitBundle) {
920
- const coreCryptoClient = this.coreCryptoClientProvider();
921
- if (commitBundle === null || commitBundle === void 0 ? void 0 : commitBundle.welcome) {
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 commitBundle = await coreCryptoClient.removeClientsFromConversation(groupIdDecodedFromBase64, fullyQualifiedClientIds);
940
- const messageResponse = await this.sendCommitBundleRemovalMessages(groupIdDecodedFromBase64, commitBundle);
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 coreCryptoClient.newExternalProposal(0 /* Add */, {
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 coreCryptoClient.conversationExists(groupIdDecodedFromBase64);
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 coreCryptoClientProvider;
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, storeEngine: CRUDEngine, coreCryptoClientProvider: () => CoreCrypto | undefined);
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, storeEngine, coreCryptoClientProvider) {
77
+ constructor(apiClient, cryptographyService, mlsService, storeEngine) {
75
78
  super();
76
- this.coreCryptoClientProvider = coreCryptoClientProvider;
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 coreCryptoClient.processWelcomeMessage(data);
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 coreCryptoClient.decryptMessage(groupId, encryptedData);
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 coreCryptoClient.commitPendingProposals(bazinga64_1.Decoder.fromBase64(groupId).asBytes);
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;
@@ -15,3 +15,6 @@ export declare type CommitPendingProposalsParams = {
15
15
  export declare type StorePendingProposalsParams = {
16
16
  firingDate: number;
17
17
  } & CommonMLS;
18
+ export declare type LastKeyMaterialUpdateParams = {
19
+ previousUpdateDate: number;
20
+ } & CommonMLS;
@@ -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