@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 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.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.4.0",
81
- "gitHead": "9df4ed23ee62258dac598e16e86ef037ee143f6e"
80
+ "version": "30.5.1",
81
+ "gitHead": "b855fc2abbad739bd3db007455051eb89ebe9e10"
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
  /**
@@ -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 sendCommitBundleRemovalMessages;
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
- //@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
  }
@@ -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
- const coreCryptoClient = this.coreCryptoClientProvider();
850
- await coreCryptoClient.createConversation(groupIdDecodedFromBase64);
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.addUsersToExistingMLSConversation(groupIdDecodedFromBase64, coreCryptoKeyPackagesPayload);
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 coreCryptoClient = this.coreCryptoClientProvider();
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.getCoreCryptoKeyPackagesPayload([...qualifiedUserIds]);
892
- const response = await this.addUsersToExistingMLSConversation(groupIdDecodedFromBase64, coreCryptoKeyPackagesPayload);
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 sendCommitBundleRemovalMessages(groupIdDecodedFromBase64, commitBundle) {
900
- const coreCryptoClient = this.coreCryptoClientProvider();
901
- if (commitBundle === null || commitBundle === void 0 ? void 0 : commitBundle.welcome) {
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 commitBundle = await coreCryptoClient.removeClientsFromConversation(groupIdDecodedFromBase64, fullyQualifiedClientIds);
920
- 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);
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 coreCryptoClient.newExternalProposal(0 /* Add */, {
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 coreCryptoClient.conversationExists(groupIdDecodedFromBase64);
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(Object.keys(CryptographyDatabaseRepository.STORES).map(store => this.storeEngine.deleteAll(store)));
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 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