@wireapp/core 27.4.1 → 27.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/package.json +5 -3
- package/src/main/Account.d.ts +14 -4
- package/src/main/Account.js +25 -9
- package/src/main/util/encryptedStore.d.ts +44 -0
- package/src/main/util/encryptedStore.js +112 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,36 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [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)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* 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))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# [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)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* 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))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
## [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)
|
|
29
|
+
|
|
30
|
+
**Note:** Version bump only for package @wireapp/core
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
6
36
|
## [27.4.1](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/compare/@wireapp/core@27.4.0...@wireapp/core@27.4.1) (2022-06-21)
|
|
7
37
|
|
|
8
38
|
**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.
|
|
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.
|
|
75
|
-
"gitHead": "
|
|
76
|
+
"version": "27.6.0",
|
|
77
|
+
"gitHead": "8bc302af20f400f4c0cb3e15edfcaebb4494f1d0"
|
|
76
78
|
}
|
package/src/main/Account.d.ts
CHANGED
|
@@ -55,9 +55,17 @@ 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
|
-
|
|
58
|
+
declare type SecretCrypto<T> = {
|
|
59
|
+
encrypt: (value: Uint8Array) => Promise<T>;
|
|
60
|
+
decrypt: (payload: T) => Promise<Uint8Array>;
|
|
61
|
+
};
|
|
62
|
+
interface AccountOptions<T> {
|
|
59
63
|
/** Used to store info in the database (will create a inMemory engine if returns undefined) */
|
|
60
64
|
createStore?: CreateStoreFn;
|
|
65
|
+
/** encrypt/decrypt function pair that will be called before storing/fetching secrets in the secrets database.
|
|
66
|
+
* If not provided will use the built in encryption mechanism
|
|
67
|
+
*/
|
|
68
|
+
secretsCrypto?: SecretCrypto<T>;
|
|
61
69
|
/** Number of prekeys to generate when creating a new device (defaults to 2)
|
|
62
70
|
* Prekeys are Diffie-Hellmann public keys which allow offline initiation of a secure Proteus session between two devices.
|
|
63
71
|
* Having a high value will:
|
|
@@ -70,11 +78,12 @@ interface AccountOptions {
|
|
|
70
78
|
nbPrekeys?: number;
|
|
71
79
|
enableMLS?: boolean;
|
|
72
80
|
}
|
|
73
|
-
export declare class Account extends EventEmitter {
|
|
81
|
+
export declare class Account<T = unknown> extends EventEmitter {
|
|
74
82
|
private readonly apiClient;
|
|
75
83
|
private readonly logger;
|
|
76
84
|
private readonly createStore;
|
|
77
85
|
private storeEngine?;
|
|
86
|
+
private readonly secretsCrypto?;
|
|
78
87
|
private readonly nbPrekeys;
|
|
79
88
|
private readonly enableMLS;
|
|
80
89
|
private coreCryptoClient?;
|
|
@@ -97,9 +106,9 @@ export declare class Account extends EventEmitter {
|
|
|
97
106
|
backendFeatures: BackendFeatures;
|
|
98
107
|
/**
|
|
99
108
|
* @param apiClient The apiClient instance to use in the core (will create a new new one if undefined)
|
|
100
|
-
* @param
|
|
109
|
+
* @param accountOptions
|
|
101
110
|
*/
|
|
102
|
-
constructor(apiClient?: APIClient, { createStore, nbPrekeys, enableMLS }?: AccountOptions);
|
|
111
|
+
constructor(apiClient?: APIClient, { createStore, nbPrekeys, secretsCrypto, enableMLS }?: AccountOptions<T>);
|
|
103
112
|
private persistCookie;
|
|
104
113
|
get clientId(): string;
|
|
105
114
|
get userId(): string;
|
|
@@ -189,6 +198,7 @@ export declare class Account extends EventEmitter {
|
|
|
189
198
|
*/
|
|
190
199
|
dryRun?: boolean;
|
|
191
200
|
}): Promise<() => void>;
|
|
201
|
+
private generateDbName;
|
|
192
202
|
private initEngine;
|
|
193
203
|
}
|
|
194
204
|
export {};
|
package/src/main/Account.js
CHANGED
|
@@ -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,12 +84,13 @@ 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
|
|
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, secretsCrypto, enableMLS = false } = {}) {
|
|
88
90
|
super();
|
|
89
91
|
this.apiClient = apiClient;
|
|
90
92
|
this.backendFeatures = this.apiClient.backendFeatures;
|
|
93
|
+
this.secretsCrypto = secretsCrypto;
|
|
91
94
|
this.nbPrekeys = nbPrekeys;
|
|
92
95
|
this.createStore = createStore;
|
|
93
96
|
this.enableMLS = enableMLS;
|
|
@@ -258,16 +261,26 @@ class Account extends events_1.EventEmitter {
|
|
|
258
261
|
await this.apiClient.api.client.getClient(loadedClient.id);
|
|
259
262
|
this.apiClient.context.clientId = loadedClient.id;
|
|
260
263
|
if (this.enableMLS) {
|
|
261
|
-
this.coreCryptoClient = await this.createMLSClient(loadedClient);
|
|
264
|
+
this.coreCryptoClient = await this.createMLSClient(loadedClient, this.apiClient.context);
|
|
262
265
|
}
|
|
263
266
|
return loadedClient;
|
|
264
267
|
}
|
|
265
|
-
async createMLSClient(client) {
|
|
268
|
+
async createMLSClient(client, context) {
|
|
269
|
+
const coreCryptoKeyId = 'corecrypto-key';
|
|
266
270
|
const { CoreCrypto } = await Promise.resolve().then(() => __importStar(require('@otak/core-crypto')));
|
|
271
|
+
const dbName = `secrets-${this.generateDbName(context)}`;
|
|
272
|
+
const secretStore = this.secretsCrypto
|
|
273
|
+
? await (0, encryptedStore_1.createCustomEncryptedStore)(dbName, this.secretsCrypto)
|
|
274
|
+
: await (0, encryptedStore_1.createEncryptedStore)(dbName);
|
|
275
|
+
let key = await secretStore.getsecretValue(coreCryptoKeyId);
|
|
276
|
+
if (!key) {
|
|
277
|
+
key = window.crypto.getRandomValues(new Uint8Array(16));
|
|
278
|
+
await secretStore.saveSecretValue(coreCryptoKeyId, key);
|
|
279
|
+
}
|
|
267
280
|
const { userId, domain } = this.apiClient.context;
|
|
268
281
|
return CoreCrypto.init({
|
|
269
|
-
path:
|
|
270
|
-
key:
|
|
282
|
+
path: `corecrypto-${this.generateDbName(context)}`,
|
|
283
|
+
key: bazinga64_1.Encoder.toBase64(key).asString,
|
|
271
284
|
clientId: `${userId}:${client.id}@${domain}`,
|
|
272
285
|
});
|
|
273
286
|
}
|
|
@@ -278,7 +291,7 @@ class Account extends events_1.EventEmitter {
|
|
|
278
291
|
this.logger.info(`Creating new client {mls: ${!!this.enableMLS}}`);
|
|
279
292
|
const registeredClient = await this.service.client.register(loginData, clientInfo, entropyData);
|
|
280
293
|
if (this.enableMLS) {
|
|
281
|
-
this.coreCryptoClient = await this.createMLSClient(registeredClient);
|
|
294
|
+
this.coreCryptoClient = await this.createMLSClient(registeredClient, this.apiClient.context);
|
|
282
295
|
await this.service.client.uploadMLSPublicKeys(this.coreCryptoClient.clientPublicKey(), registeredClient.id);
|
|
283
296
|
await this.service.client.uploadMLSKeyPackages(this.coreCryptoClient.clientKeypackages(this.nbPrekeys), registeredClient.id);
|
|
284
297
|
}
|
|
@@ -365,9 +378,12 @@ class Account extends events_1.EventEmitter {
|
|
|
365
378
|
this.apiClient.disconnect();
|
|
366
379
|
};
|
|
367
380
|
}
|
|
368
|
-
|
|
381
|
+
generateDbName(context) {
|
|
369
382
|
const clientType = context.clientType === client_1.ClientType.NONE ? '' : `@${context.clientType}`;
|
|
370
|
-
|
|
383
|
+
return `wire@${this.apiClient.config.urls.name}@${context.userId}${clientType}`;
|
|
384
|
+
}
|
|
385
|
+
async initEngine(context) {
|
|
386
|
+
const dbName = this.generateDbName(context);
|
|
371
387
|
this.logger.log(`Initialising store with name "${dbName}"...`);
|
|
372
388
|
const openDb = async () => {
|
|
373
389
|
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
|