Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 2587615

Browse files
committed
Element-R: pass pickleKey in as raw key for indexeddb encryption
Currently, we pass the `pickleKey` to the rust library for use as a passphrase for encrypting its crypto store. The Rust libary then passes that passphrase through 200000 rounds of PBKDF2 to generate an encryption key, which is (deliberately) slow. However, the pickleKey is actually 32 bytes of random data (base64-encoded). By passing the raw key into the rust library, we can therefore save the PBKDF operation. Backwards-compatibility with existing sessions is maintained, because if the rust library discovers that the store was previously encrypted with a key based on a PBKDF, it will re-base64 and PBKDF the key we provide, thus reconstructing the right key.
1 parent 4e91d8b commit 2587615

File tree

3 files changed

+48
-20
lines changed

3 files changed

+48
-20
lines changed

src/Lifecycle.ts

+16-7
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ limitations under the License.
1818
*/
1919

2020
import { ReactNode } from "react";
21-
import { createClient, MatrixClient, SSOAction, OidcTokenRefresher } from "matrix-js-sdk/src/matrix";
21+
import { createClient, MatrixClient, SSOAction, OidcTokenRefresher, decodeBase64 } from "matrix-js-sdk/src/matrix";
2222
import { IEncryptedPayload } from "matrix-js-sdk/src/crypto/aes";
2323
import { QueryDict } from "matrix-js-sdk/src/utils";
2424
import { logger } from "matrix-js-sdk/src/logger";
@@ -821,7 +821,10 @@ async function doSetLoggedIn(credentials: IMatrixClientCreds, clearStorageEnable
821821
checkSessionLock();
822822

823823
dis.fire(Action.OnLoggedIn);
824-
await startMatrixClient(client, /*startSyncing=*/ !softLogout);
824+
// The pickleKey, if provided, is a base64-encoded 256-bit key, so can be used for the crypto store.
825+
const storageKey = credentials.pickleKey ? decodeBase64(credentials.pickleKey) : undefined;
826+
await startMatrixClient(client, /*startSyncing=*/ !softLogout, storageKey);
827+
storageKey?.fill(0);
825828

826829
return client;
827830
}
@@ -955,11 +958,17 @@ export function isLoggingOut(): boolean {
955958
/**
956959
* Starts the matrix client and all other react-sdk services that
957960
* listen for events while a session is logged in.
961+
*
958962
* @param client the matrix client to start
959-
* @param {boolean} startSyncing True (default) to actually start
960-
* syncing the client.
963+
* @param startSyncing True to actually start syncing the client.
964+
* @param rustCryptoStoreKey - If we are using Rust Crypto, a key to encrypt the store with.
965+
* If undefined, the store will be unencrypted.
961966
*/
962-
async function startMatrixClient(client: MatrixClient, startSyncing = true): Promise<void> {
967+
async function startMatrixClient(
968+
client: MatrixClient,
969+
startSyncing: boolean,
970+
rustCryptoStoreKey?: Uint8Array,
971+
): Promise<void> {
963972
logger.log(`Lifecycle: Starting MatrixClient`);
964973

965974
// dispatch this before starting the matrix client: it's used
@@ -990,10 +999,10 @@ async function startMatrixClient(client: MatrixClient, startSyncing = true): Pro
990999
// index (e.g. the FilePanel), therefore initialize the event index
9911000
// before the client.
9921001
await EventIndexPeg.init();
993-
await MatrixClientPeg.start();
1002+
await MatrixClientPeg.start(rustCryptoStoreKey);
9941003
} else {
9951004
logger.warn("Caller requested only auxiliary services be started");
996-
await MatrixClientPeg.assign();
1005+
await MatrixClientPeg.assign(rustCryptoStoreKey);
9971006
}
9981007

9991008
checkSessionLock();

src/MatrixClientPeg.ts

+28-10
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,20 @@ export interface IMatrixClientPeg {
103103
unset(): void;
104104

105105
/**
106-
* Prepare the MatrixClient for use, including initialising the store and crypto, but do not start it
106+
* Prepare the MatrixClient for use, including initialising the store and crypto, but do not start it.
107+
*
108+
* @param rustCryptoStoreKey - If we are using Rust crypto, a key with which to encrypt the indexeddb.
109+
* If undefined, the store will be unencrypted.
107110
*/
108-
assign(): Promise<IStartClientOpts>;
111+
assign(rustCryptoStoreKey?: Uint8Array): Promise<IStartClientOpts>;
109112

110113
/**
111-
* Prepare the MatrixClient for use, including initialising the store and crypto, and start it
114+
* Prepare the MatrixClient for use, including initialising the store and crypto, and start it.
115+
*
116+
* @param rustCryptoStoreKey - If we are using Rust crypto, a key with which to encrypt the indexeddb.
117+
* If undefined, the store will be unencrypted.
112118
*/
113-
start(): Promise<void>;
119+
start(rustCryptoStoreKey?: Uint8Array): Promise<void>;
114120

115121
/**
116122
* If we've registered a user ID we set this to the ID of the
@@ -257,7 +263,10 @@ class MatrixClientPegClass implements IMatrixClientPeg {
257263
PlatformPeg.get()?.reload();
258264
};
259265

260-
public async assign(): Promise<IStartClientOpts> {
266+
/**
267+
* Implementation of {@link IMatrixClientPeg.assign}.
268+
*/
269+
public async assign(rustCryptoStoreKey?: Uint8Array): Promise<IStartClientOpts> {
261270
if (!this.matrixClient) {
262271
throw new Error("createClient must be called first");
263272
}
@@ -284,7 +293,7 @@ class MatrixClientPegClass implements IMatrixClientPeg {
284293

285294
// try to initialise e2e on the new client
286295
if (!SettingsStore.getValue("lowBandwidth")) {
287-
await this.initClientCrypto();
296+
await this.initClientCrypto(rustCryptoStoreKey);
288297
}
289298

290299
const opts = utils.deepCopy(this.opts);
@@ -310,8 +319,11 @@ class MatrixClientPegClass implements IMatrixClientPeg {
310319

311320
/**
312321
* Attempt to initialize the crypto layer on a newly-created MatrixClient
322+
*
323+
* @param rustCryptoStoreKey - If we are using Rust crypto, a key with which to encrypt the indexeddb.
324+
* If undefined, the store will be unencrypted.
313325
*/
314-
private async initClientCrypto(): Promise<void> {
326+
private async initClientCrypto(rustCryptoStoreKey?: Uint8Array): Promise<void> {
315327
if (!this.matrixClient) {
316328
throw new Error("createClient must be called first");
317329
}
@@ -347,7 +359,10 @@ class MatrixClientPegClass implements IMatrixClientPeg {
347359

348360
// Now we can initialise the right crypto impl.
349361
if (useRustCrypto) {
350-
await this.matrixClient.initRustCrypto();
362+
if (!rustCryptoStoreKey) {
363+
logger.error("Warning! Not using an encryption key for rust crypto store.");
364+
}
365+
await this.matrixClient.initRustCrypto({ storageKey: rustCryptoStoreKey });
351366

352367
StorageManager.setCryptoInitialised(true);
353368
// TODO: device dehydration and whathaveyou
@@ -376,8 +391,11 @@ class MatrixClientPegClass implements IMatrixClientPeg {
376391
}
377392
}
378393

379-
public async start(): Promise<void> {
380-
const opts = await this.assign();
394+
/**
395+
* Implementation of {@link IMatrixClientPeg.start}.
396+
*/
397+
public async start(rustCryptoStoreKey?: Uint8Array): Promise<void> {
398+
const opts = await this.assign(rustCryptoStoreKey);
381399

382400
logger.log(`MatrixClientPeg: really starting MatrixClient`);
383401
await this.matrixClient!.startClient(opts);

test/MatrixClientPeg-test.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,10 @@ describe("MatrixClientPeg", () => {
243243
const mockInitCrypto = jest.spyOn(testPeg.safeGet(), "initCrypto").mockResolvedValue(undefined);
244244
const mockInitRustCrypto = jest.spyOn(testPeg.safeGet(), "initRustCrypto").mockResolvedValue(undefined);
245245

246-
await testPeg.start();
246+
const cryptoStoreKey = new Uint8Array([1, 2, 3, 4]);
247+
await testPeg.start(cryptoStoreKey);
247248
expect(mockInitCrypto).not.toHaveBeenCalled();
248-
expect(mockInitRustCrypto).toHaveBeenCalledTimes(1);
249+
expect(mockInitRustCrypto).toHaveBeenCalledWith({ storageKey: cryptoStoreKey });
249250

250251
// we should have stashed the setting in the settings store
251252
expect(mockSetValue).toHaveBeenCalledWith("feature_rust_crypto", null, SettingLevel.DEVICE, true);
@@ -271,7 +272,7 @@ describe("MatrixClientPeg", () => {
271272

272273
await testPeg.start();
273274
expect(mockInitCrypto).toHaveBeenCalled();
274-
expect(mockInitRustCrypto).not.toHaveBeenCalledTimes(1);
275+
expect(mockInitRustCrypto).not.toHaveBeenCalled();
275276

276277
// we should have stashed the setting in the settings store
277278
expect(mockSetValue).toHaveBeenCalledWith("feature_rust_crypto", null, SettingLevel.DEVICE, false);

0 commit comments

Comments
 (0)