@wireapp/core 27.4.2 → 28.0.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,44 @@
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
+ # [28.0.0](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/compare/@wireapp/core@27.6.0...@wireapp/core@28.0.0) (2022-07-01)
7
+
8
+
9
+ ### Features
10
+
11
+ * Use mls config object ([#4307](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/issues/4307)) ([3d510d0](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/commit/3d510d0e041a3d049282e6a312ffa880d9bafd89))
12
+
13
+
14
+ ### BREAKING CHANGES
15
+
16
+ * the enableMLS flag has been removed in favor of a config object. If the config object is set, then MLS will be activated
17
+
18
+
19
+
20
+
21
+
22
+ # [27.6.0](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/compare/@wireapp/core@27.5.0...@wireapp/core@27.6.0) (2022-06-30)
23
+
24
+
25
+ ### Features
26
+
27
+ * Give proper database name to corecrypto db ([#4306](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/issues/4306)) ([50c7a7a](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/commit/50c7a7a6ca97a0e848a0bb7d9a781e5410909368))
28
+
29
+
30
+
31
+
32
+
33
+ # [27.5.0](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/compare/@wireapp/core@27.4.2...@wireapp/core@27.5.0) (2022-06-30)
34
+
35
+
36
+ ### Features
37
+
38
+ * Add encrypted storage for coreCrypto database key [FS-565] ([#4302](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/issues/4302)) ([5c16877](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/commit/5c16877eae4a85eedfd876160446994d98bbcd85))
39
+
40
+
41
+
42
+
43
+
6
44
  ## [27.4.2](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/compare/@wireapp/core@27.4.1...@wireapp/core@27.4.2) (2022-06-27)
7
45
 
8
46
  **Note:** Version bump only for package @wireapp/core
package/package.json CHANGED
@@ -7,11 +7,12 @@
7
7
  "@otak/core-crypto": "0.2.0-beta-3",
8
8
  "@types/long": "4.0.1",
9
9
  "@types/node": "~14",
10
- "@wireapp/api-client": "19.7.1",
10
+ "@wireapp/api-client": "19.7.2",
11
11
  "@wireapp/cryptobox": "12.8.0",
12
12
  "bazinga64": "5.10.0",
13
13
  "hash.js": "1.1.7",
14
14
  "http-status-codes": "2.1.4",
15
+ "idb": "7.0.2",
15
16
  "logdown": "3.3.1",
16
17
  "long": "4.0.0",
17
18
  "protobufjs": "6.11.3",
@@ -26,6 +27,7 @@
26
27
  "commander": "8.0.0",
27
28
  "cross-env": "7.0.3",
28
29
  "dotenv-defaults": "2.0.2",
30
+ "fake-indexeddb": "3.1.8",
29
31
  "faker": "5.5.3",
30
32
  "istanbul": "1.1.0-alpha.1",
31
33
  "jasmine": "3.8.0",
@@ -71,6 +73,6 @@
71
73
  "test:project": "yarn dist && yarn test",
72
74
  "test:node": "nyc jasmine --config=jasmine.json"
73
75
  },
74
- "version": "27.4.2",
75
- "gitHead": "bec7aeedec3675abc05ea68852ce5d26f7efd99d"
76
+ "version": "28.0.0",
77
+ "gitHead": "89c2a626f3f385edea215a96640c2805972a161a"
76
78
  }
@@ -55,7 +55,24 @@ export interface Account {
55
55
  on(event: TOPIC.ERROR, listener: (payload: CoreError) => void): this;
56
56
  }
57
57
  export declare type CreateStoreFn = (storeName: string, context: Context) => undefined | Promise<CRUDEngine | undefined>;
58
- interface AccountOptions {
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
+ interface AccountOptions<T> {
59
76
  /** Used to store info in the database (will create a inMemory engine if returns undefined) */
60
77
  createStore?: CreateStoreFn;
61
78
  /** Number of prekeys to generate when creating a new device (defaults to 2)
@@ -68,15 +85,18 @@ interface AccountOptions {
68
85
  * - make it likely that all prekeys get consumed while the device is offline and the last resort prekey will be used to create new session
69
86
  */
70
87
  nbPrekeys?: number;
71
- enableMLS?: boolean;
88
+ /**
89
+ * Config for MLS devices. Will not load corecrypt or create MLS devices if undefined
90
+ */
91
+ mlsConfig?: MLSConfig<T>;
72
92
  }
73
- export declare class Account extends EventEmitter {
93
+ export declare class Account<T = unknown> extends EventEmitter {
74
94
  private readonly apiClient;
75
95
  private readonly logger;
76
96
  private readonly createStore;
77
97
  private storeEngine?;
78
98
  private readonly nbPrekeys;
79
- private readonly enableMLS;
99
+ private readonly mlsConfig?;
80
100
  private coreCryptoClient?;
81
101
  static readonly TOPIC: typeof TOPIC;
82
102
  service?: {
@@ -97,9 +117,9 @@ export declare class Account extends EventEmitter {
97
117
  backendFeatures: BackendFeatures;
98
118
  /**
99
119
  * @param apiClient The apiClient instance to use in the core (will create a new new one if undefined)
100
- * @param storeEngineProvider
120
+ * @param accountOptions
101
121
  */
102
- constructor(apiClient?: APIClient, { createStore, nbPrekeys, enableMLS }?: AccountOptions);
122
+ constructor(apiClient?: APIClient, { createStore, nbPrekeys, mlsConfig }?: AccountOptions<T>);
103
123
  private persistCookie;
104
124
  get clientId(): string;
105
125
  get userId(): string;
@@ -189,6 +209,7 @@ export declare class Account extends EventEmitter {
189
209
  */
190
210
  dryRun?: boolean;
191
211
  }): Promise<() => void>;
212
+ private generateDbName;
192
213
  private initEngine;
193
214
  }
194
215
  export {};
@@ -70,6 +70,8 @@ const team_1 = require("./team/");
70
70
  const user_1 = require("./user/");
71
71
  const account_1 = require("./account/");
72
72
  const linkPreview_1 = require("./linkPreview");
73
+ const encryptedStore_1 = require("./util/encryptedStore");
74
+ const bazinga64_1 = require("bazinga64");
73
75
  var TOPIC;
74
76
  (function (TOPIC) {
75
77
  TOPIC["ERROR"] = "Account.TOPIC.ERROR";
@@ -82,15 +84,15 @@ const coreDefaultClient = {
82
84
  class Account extends events_1.EventEmitter {
83
85
  /**
84
86
  * @param apiClient The apiClient instance to use in the core (will create a new new one if undefined)
85
- * @param storeEngineProvider
87
+ * @param accountOptions
86
88
  */
87
- constructor(apiClient = new api_client_1.APIClient(), { createStore = () => undefined, nbPrekeys = 2, enableMLS = false } = {}) {
89
+ constructor(apiClient = new api_client_1.APIClient(), { createStore = () => undefined, nbPrekeys = 2, mlsConfig } = {}) {
88
90
  super();
89
91
  this.apiClient = apiClient;
90
92
  this.backendFeatures = this.apiClient.backendFeatures;
93
+ this.mlsConfig = this.mlsConfig;
91
94
  this.nbPrekeys = nbPrekeys;
92
95
  this.createStore = createStore;
93
- this.enableMLS = enableMLS;
94
96
  apiClient.on(api_client_1.APIClient.TOPIC.COOKIE_REFRESH, async (cookie) => {
95
97
  if (cookie && this.storeEngine) {
96
98
  try {
@@ -257,17 +259,27 @@ class Account extends events_1.EventEmitter {
257
259
  const loadedClient = await this.service.client.getLocalClient();
258
260
  await this.apiClient.api.client.getClient(loadedClient.id);
259
261
  this.apiClient.context.clientId = loadedClient.id;
260
- if (this.enableMLS) {
261
- this.coreCryptoClient = await this.createMLSClient(loadedClient);
262
+ if (this.mlsConfig) {
263
+ this.coreCryptoClient = await this.createMLSClient(loadedClient, this.apiClient.context, this.mlsConfig);
262
264
  }
263
265
  return loadedClient;
264
266
  }
265
- async createMLSClient(client) {
267
+ async createMLSClient(client, context, mlsConfig) {
268
+ const coreCryptoKeyId = 'corecrypto-key';
266
269
  const { CoreCrypto } = await Promise.resolve().then(() => __importStar(require('@otak/core-crypto')));
270
+ const dbName = `secrets-${this.generateDbName(context)}`;
271
+ const secretStore = mlsConfig.secretsCrypto
272
+ ? await (0, encryptedStore_1.createCustomEncryptedStore)(dbName, mlsConfig.secretsCrypto)
273
+ : await (0, encryptedStore_1.createEncryptedStore)(dbName);
274
+ let key = await secretStore.getsecretValue(coreCryptoKeyId);
275
+ if (!key) {
276
+ key = window.crypto.getRandomValues(new Uint8Array(16));
277
+ await secretStore.saveSecretValue(coreCryptoKeyId, key);
278
+ }
267
279
  const { userId, domain } = this.apiClient.context;
268
280
  return CoreCrypto.init({
269
- path: 'path/to/database',
270
- key: 'a root identity key (i.e. enclaved encryption key for this device)',
281
+ path: `corecrypto-${this.generateDbName(context)}`,
282
+ key: bazinga64_1.Encoder.toBase64(key).asString,
271
283
  clientId: `${userId}:${client.id}@${domain}`,
272
284
  });
273
285
  }
@@ -275,10 +287,10 @@ class Account extends events_1.EventEmitter {
275
287
  if (!this.service) {
276
288
  throw new Error('Services are not set.');
277
289
  }
278
- this.logger.info(`Creating new client {mls: ${!!this.enableMLS}}`);
290
+ this.logger.info(`Creating new client {mls: ${!!this.mlsConfig}}`);
279
291
  const registeredClient = await this.service.client.register(loginData, clientInfo, entropyData);
280
- if (this.enableMLS) {
281
- this.coreCryptoClient = await this.createMLSClient(registeredClient);
292
+ if (this.mlsConfig) {
293
+ this.coreCryptoClient = await this.createMLSClient(registeredClient, this.apiClient.context, this.mlsConfig);
282
294
  await this.service.client.uploadMLSPublicKeys(this.coreCryptoClient.clientPublicKey(), registeredClient.id);
283
295
  await this.service.client.uploadMLSKeyPackages(this.coreCryptoClient.clientKeypackages(this.nbPrekeys), registeredClient.id);
284
296
  }
@@ -365,9 +377,12 @@ class Account extends events_1.EventEmitter {
365
377
  this.apiClient.disconnect();
366
378
  };
367
379
  }
368
- async initEngine(context) {
380
+ generateDbName(context) {
369
381
  const clientType = context.clientType === client_1.ClientType.NONE ? '' : `@${context.clientType}`;
370
- const dbName = `wire@${this.apiClient.config.urls.name}@${context.userId}${clientType}`;
382
+ return `wire@${this.apiClient.config.urls.name}@${context.userId}${clientType}`;
383
+ }
384
+ async initEngine(context) {
385
+ const dbName = this.generateDbName(context);
371
386
  this.logger.log(`Initialising store with name "${dbName}"...`);
372
387
  const openDb = async () => {
373
388
  const initializedDb = await this.createStore(dbName, context);
@@ -0,0 +1,44 @@
1
+ import { DBSchema, IDBPDatabase } from 'idb';
2
+ interface DefaultEncryptedPayload {
3
+ iv: Uint8Array;
4
+ value: Uint8Array;
5
+ }
6
+ interface EncryptedDB<EncryptedPayload> extends DBSchema {
7
+ key: {
8
+ key: string;
9
+ value: CryptoKey;
10
+ };
11
+ secrets: {
12
+ key: string;
13
+ value: EncryptedPayload;
14
+ };
15
+ }
16
+ declare type DecryptFn<EncryptedPayload> = (payload: EncryptedPayload) => Promise<Uint8Array>;
17
+ declare type EncryptFn<EncryptedPayload> = (value: Uint8Array) => Promise<EncryptedPayload>;
18
+ declare type EncryptedStoreConfig<EncryptedPayload> = {
19
+ encrypt: EncryptFn<EncryptedPayload>;
20
+ decrypt: DecryptFn<EncryptedPayload>;
21
+ };
22
+ declare class EncryptedStore<EncryptedPayload> {
23
+ #private;
24
+ private readonly db;
25
+ constructor(db: IDBPDatabase<EncryptedDB<EncryptedPayload>>, { encrypt, decrypt }: EncryptedStoreConfig<EncryptedPayload>);
26
+ saveSecretValue(primaryKey: string, value: Uint8Array): Promise<void>;
27
+ getsecretValue(primaryKey: string): Promise<Uint8Array | undefined>;
28
+ }
29
+ /**
30
+ * Will create a database that uses the built in encryption/decryption functions.
31
+ * The master key will be created and stored inside the database
32
+ *
33
+ * @param dbName the name of the database to create
34
+ */
35
+ export declare function createEncryptedStore(dbName: string): Promise<EncryptedStore<DefaultEncryptedPayload>>;
36
+ /**
37
+ * Will create a database that uses a custom encryption method. It needs the encrypt and decrypt function to be able to process the values stored.
38
+ * It's the responsability of the consumer to store the encryption key
39
+ *
40
+ * @param dbName the name of the database to create
41
+ * @param config contains the encrypt and decrypt methods
42
+ */
43
+ export declare function createCustomEncryptedStore<EncryptedPayload>(dbName: string, config: EncryptedStoreConfig<EncryptedPayload>): Promise<EncryptedStore<EncryptedPayload>>;
44
+ export {};
@@ -0,0 +1,112 @@
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 __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
21
+ if (kind === "m") throw new TypeError("Private method is not writable");
22
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
23
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
24
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
25
+ };
26
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
27
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
28
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
29
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
30
+ };
31
+ var _EncryptedStore_decrypt, _EncryptedStore_encrypt;
32
+ Object.defineProperty(exports, "__esModule", { value: true });
33
+ exports.createCustomEncryptedStore = exports.createEncryptedStore = void 0;
34
+ const idb_1 = require("idb");
35
+ class EncryptedStore {
36
+ constructor(db, { encrypt, decrypt }) {
37
+ this.db = db;
38
+ _EncryptedStore_decrypt.set(this, void 0);
39
+ _EncryptedStore_encrypt.set(this, void 0);
40
+ __classPrivateFieldSet(this, _EncryptedStore_encrypt, encrypt, "f");
41
+ __classPrivateFieldSet(this, _EncryptedStore_decrypt, decrypt, "f");
42
+ }
43
+ async saveSecretValue(primaryKey, value) {
44
+ const encrypted = await __classPrivateFieldGet(this, _EncryptedStore_encrypt, "f").call(this, value);
45
+ await this.db.put('secrets', encrypted, primaryKey);
46
+ }
47
+ async getsecretValue(primaryKey) {
48
+ const result = await this.db.get('secrets', primaryKey);
49
+ if (!result) {
50
+ return undefined;
51
+ }
52
+ return __classPrivateFieldGet(this, _EncryptedStore_decrypt, "f").call(this, result);
53
+ }
54
+ }
55
+ _EncryptedStore_decrypt = new WeakMap(), _EncryptedStore_encrypt = new WeakMap();
56
+ async function generateKey() {
57
+ return crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, false, //whether the key is extractable (i.e. can be used in exportKey)
58
+ ['encrypt', 'decrypt']);
59
+ }
60
+ async function defaultDecrypt({ value, iv }, key) {
61
+ const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, value);
62
+ return new Uint8Array(decrypted);
63
+ }
64
+ async function defaultEncrypt(data, key) {
65
+ const iv = await crypto.getRandomValues(new Uint8Array(12));
66
+ return {
67
+ iv,
68
+ value: await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, data),
69
+ };
70
+ }
71
+ /**
72
+ * Will create a database that uses the built in encryption/decryption functions.
73
+ * The master key will be created and stored inside the database
74
+ *
75
+ * @param dbName the name of the database to create
76
+ */
77
+ async function createEncryptedStore(dbName) {
78
+ const db = await (0, idb_1.openDB)(dbName, 1, {
79
+ upgrade: async (database) => {
80
+ database.createObjectStore('key');
81
+ database.createObjectStore('secrets');
82
+ },
83
+ });
84
+ const keyPrimaryKey = 'dbKey';
85
+ let key = await db.get('key', keyPrimaryKey);
86
+ if (!key) {
87
+ key = await generateKey();
88
+ await db.put('key', key, keyPrimaryKey);
89
+ }
90
+ return new EncryptedStore(db, {
91
+ encrypt: value => defaultEncrypt(value, key),
92
+ decrypt: payload => defaultDecrypt(payload, key),
93
+ });
94
+ }
95
+ exports.createEncryptedStore = createEncryptedStore;
96
+ /**
97
+ * Will create a database that uses a custom encryption method. It needs the encrypt and decrypt function to be able to process the values stored.
98
+ * It's the responsability of the consumer to store the encryption key
99
+ *
100
+ * @param dbName the name of the database to create
101
+ * @param config contains the encrypt and decrypt methods
102
+ */
103
+ async function createCustomEncryptedStore(dbName, config) {
104
+ const db = await (0, idb_1.openDB)(dbName, 1, {
105
+ upgrade: async (database) => {
106
+ database.createObjectStore('secrets');
107
+ },
108
+ });
109
+ return new EncryptedStore(db, config);
110
+ }
111
+ exports.createCustomEncryptedStore = createCustomEncryptedStore;
112
+ //# sourceMappingURL=encryptedStore.js.map