Skip to content

Commit 36196ea

Browse files
authored
initRustCrypto: allow app to pass in the store key directly (#4210)
* `initRustCrypto`: allow app to pass in the store key directly ... instead of using the pickleKey. This allows us to avoid a slow PBKDF operation. * Fix link in doc-comment
1 parent a81adf5 commit 36196ea

File tree

4 files changed

+112
-25
lines changed

4 files changed

+112
-25
lines changed

spec/integ/crypto/rust-crypto.spec.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,44 @@ describe("MatrixClient.initRustCrypto", () => {
8585
);
8686
});
8787

88+
it("should create the meta db if given a storageKey", async () => {
89+
const matrixClient = createClient({
90+
baseUrl: "http://test.server",
91+
userId: "@alice:localhost",
92+
deviceId: "aliceDevice",
93+
});
94+
95+
// No databases.
96+
expect(await indexedDB.databases()).toHaveLength(0);
97+
98+
await matrixClient.initRustCrypto({ storageKey: new Uint8Array(32) });
99+
100+
// should have two indexed dbs now
101+
const databaseNames = (await indexedDB.databases()).map((db) => db.name);
102+
expect(databaseNames).toEqual(
103+
expect.arrayContaining(["matrix-js-sdk::matrix-sdk-crypto", "matrix-js-sdk::matrix-sdk-crypto-meta"]),
104+
);
105+
});
106+
107+
it("should create the meta db if given a storagePassword", async () => {
108+
const matrixClient = createClient({
109+
baseUrl: "http://test.server",
110+
userId: "@alice:localhost",
111+
deviceId: "aliceDevice",
112+
});
113+
114+
// No databases.
115+
expect(await indexedDB.databases()).toHaveLength(0);
116+
117+
await matrixClient.initRustCrypto({ storagePassword: "the cow is on the moon" });
118+
119+
// should have two indexed dbs now
120+
const databaseNames = (await indexedDB.databases()).map((db) => db.name);
121+
expect(databaseNames).toEqual(
122+
expect.arrayContaining(["matrix-js-sdk::matrix-sdk-crypto", "matrix-js-sdk::matrix-sdk-crypto-meta"]),
123+
);
124+
});
125+
88126
it("should ignore a second call", async () => {
89127
const matrixClient = createClient({
90128
baseUrl: "http://test.server",

spec/unit/rust-crypto/rust-crypto.spec.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ describe("initRustCrypto", () => {
104104
} as unknown as Mocked<OlmMachine>;
105105
}
106106

107-
it("passes through the store params", async () => {
107+
it("passes through the store params (passphrase)", async () => {
108108
const mockStore = { free: jest.fn() } as unknown as StoreHandle;
109109
jest.spyOn(StoreHandle, "open").mockResolvedValue(mockStore);
110110

@@ -126,7 +126,30 @@ describe("initRustCrypto", () => {
126126
expect(OlmMachine.initFromStore).toHaveBeenCalledWith(expect.anything(), expect.anything(), mockStore);
127127
});
128128

129-
it("suppresses the storePassphrase if storePrefix is unset", async () => {
129+
it("passes through the store params (key)", async () => {
130+
const mockStore = { free: jest.fn() } as unknown as StoreHandle;
131+
jest.spyOn(StoreHandle, "openWithKey").mockResolvedValue(mockStore);
132+
133+
const testOlmMachine = makeTestOlmMachine();
134+
jest.spyOn(OlmMachine, "initFromStore").mockResolvedValue(testOlmMachine);
135+
136+
const storeKey = new Uint8Array(32);
137+
await initRustCrypto({
138+
logger,
139+
http: {} as MatrixClient["http"],
140+
userId: TEST_USER,
141+
deviceId: TEST_DEVICE_ID,
142+
secretStorage: {} as ServerSideSecretStorage,
143+
cryptoCallbacks: {} as CryptoCallbacks,
144+
storePrefix: "storePrefix",
145+
storeKey: storeKey,
146+
});
147+
148+
expect(StoreHandle.openWithKey).toHaveBeenCalledWith("storePrefix", storeKey);
149+
expect(OlmMachine.initFromStore).toHaveBeenCalledWith(expect.anything(), expect.anything(), mockStore);
150+
});
151+
152+
it("suppresses the storePassphrase and storeKey if storePrefix is unset", async () => {
130153
const mockStore = { free: jest.fn() } as unknown as StoreHandle;
131154
jest.spyOn(StoreHandle, "open").mockResolvedValue(mockStore);
132155

@@ -141,10 +164,11 @@ describe("initRustCrypto", () => {
141164
secretStorage: {} as ServerSideSecretStorage,
142165
cryptoCallbacks: {} as CryptoCallbacks,
143166
storePrefix: null,
167+
storeKey: new Uint8Array(),
144168
storePassphrase: "storePassphrase",
145169
});
146170

147-
expect(StoreHandle.open).toHaveBeenCalledWith(undefined, undefined);
171+
expect(StoreHandle.open).toHaveBeenCalledWith();
148172
expect(OlmMachine.initFromStore).toHaveBeenCalledWith(expect.anything(), expect.anything(), mockStore);
149173
});
150174

src/client.ts

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -357,14 +357,14 @@ export interface ICreateClientOpts {
357357
deviceToImport?: IExportedDevice;
358358

359359
/**
360-
* Encryption key used for encrypting sensitive data (such as e2ee keys) in storage.
360+
* Encryption key used for encrypting sensitive data (such as e2ee keys) in {@link ICreateClientOpts#cryptoStore}.
361361
*
362362
* This must be set to the same value every time the client is initialised for the same device.
363363
*
364-
* If unset, either a hardcoded key or no encryption at all is used, depending on the Crypto implementation.
365-
*
366-
* No particular requirement is placed on the key data (it is fed into an HKDF to generate the actual encryption
367-
* keys).
364+
* This is only used for the legacy crypto implementation (as used by {@link MatrixClient#initCrypto}),
365+
* but if you use the rust crypto implementation ({@link MatrixClient#initRustCrypto}) and the device
366+
* previously used legacy crypto (so must be migrated), then this must still be provided, so that the
367+
* data can be migrated from the legacy store.
368368
*/
369369
pickleKey?: string;
370370

@@ -2229,17 +2229,24 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
22292229
*
22302230
* An alternative to {@link initCrypto}.
22312231
*
2232-
* *WARNING*: this API is very experimental, should not be used in production, and may change without notice!
2233-
* Eventually it will be deprecated and `initCrypto` will do the same thing.
2234-
*
2235-
* @experimental
2236-
*
2237-
* @param useIndexedDB - True to use an indexeddb store, false to use an in-memory store. Defaults to 'true'.
2232+
* @param args.useIndexedDB - True to use an indexeddb store, false to use an in-memory store. Defaults to 'true'.
2233+
* @param args.storageKey - A key with which to encrypt the indexeddb store. If provided, it must be exactly
2234+
* 32 bytes of data, and must be the same each time the client is initialised for a given device.
2235+
* If both this and `storagePassword` are unspecified, the store will be unencrypted.
2236+
* @param args.storagePassword - An alternative to `storageKey`. A password which will be used to derive a key to
2237+
* encrypt the store with. Deriving a key from a password is (deliberately) a slow operation, so prefer
2238+
* to pass a `storageKey` directly where possible.
22382239
*
22392240
* @returns a Promise which will resolve when the crypto layer has been
22402241
* successfully initialised.
22412242
*/
2242-
public async initRustCrypto({ useIndexedDB = true }: { useIndexedDB?: boolean } = {}): Promise<void> {
2243+
public async initRustCrypto(
2244+
args: {
2245+
useIndexedDB?: boolean;
2246+
storageKey?: Uint8Array;
2247+
storagePassword?: string;
2248+
} = {},
2249+
): Promise<void> {
22432250
if (this.cryptoBackend) {
22442251
this.logger.warn("Attempt to re-initialise e2e encryption on MatrixClient");
22452252
return;
@@ -2272,11 +2279,15 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
22722279
deviceId: deviceId,
22732280
secretStorage: this.secretStorage,
22742281
cryptoCallbacks: this.cryptoCallbacks,
2275-
storePrefix: useIndexedDB ? RUST_SDK_STORE_PREFIX : null,
2276-
storePassphrase: this.pickleKey,
2282+
storePrefix: args.useIndexedDB === false ? null : RUST_SDK_STORE_PREFIX,
2283+
storeKey: args.storageKey,
2284+
2285+
// temporary compatibility hack: if there is no storageKey nor storagePassword, fall back to the pickleKey
2286+
storePassphrase: args.storagePassword ?? this.pickleKey,
2287+
22772288
legacyCryptoStore: this.cryptoStore,
22782289
legacyPickleKey: this.pickleKey ?? "DEFAULT_KEY",
2279-
legacyMigrationProgressListener: (progress, total) => {
2290+
legacyMigrationProgressListener: (progress: number, total: number): void => {
22802291
this.emit(CryptoEvent.LegacyCryptoStoreMigrationProgress, progress, total);
22812292
},
22822293
});

src/rust-crypto/index.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,21 @@ export async function initRustCrypto(args: {
6464
storePrefix: string | null;
6565

6666
/**
67-
* A passphrase to use to encrypt the indexeddbs created by rust-crypto.
67+
* A passphrase to use to encrypt the indexeddb created by rust-crypto.
6868
*
69-
* Ignored if `storePrefix` is null. If this is `undefined` (and `storePrefix` is not null), the indexeddbs
70-
* will be unencrypted.
69+
* Ignored if `storePrefix` is null, or `storeKey` is set. If neither this nor `storeKey` is set
70+
* (and `storePrefix` is not null), the indexeddb will be unencrypted.
7171
*/
7272
storePassphrase?: string;
7373

74+
/**
75+
* A key to use to encrypt the indexeddb created by rust-crypto.
76+
*
77+
* Ignored if `storePrefix` is null. Otherwise, if it is set, it must be a 32-byte cryptographic key, which
78+
* will be used to encrypt the indexeddb. See also `storePassphrase`.
79+
*/
80+
storeKey?: Uint8Array;
81+
7482
/** If defined, we will check if any data needs migrating from this store to the rust store. */
7583
legacyCryptoStore?: CryptoStore;
7684

@@ -94,10 +102,16 @@ export async function initRustCrypto(args: {
94102
new RustSdkCryptoJs.Tracing(RustSdkCryptoJs.LoggerLevel.Debug).turnOn();
95103

96104
logger.debug("Opening Rust CryptoStore");
97-
const storeHandle: StoreHandle = await StoreHandle.open(
98-
args.storePrefix ?? undefined,
99-
(args.storePrefix && args.storePassphrase) ?? undefined,
100-
);
105+
let storeHandle;
106+
if (args.storePrefix) {
107+
if (args.storeKey) {
108+
storeHandle = await StoreHandle.openWithKey(args.storePrefix, args.storeKey);
109+
} else {
110+
storeHandle = await StoreHandle.open(args.storePrefix, args.storePassphrase);
111+
}
112+
} else {
113+
storeHandle = await StoreHandle.open();
114+
}
101115

102116
if (args.legacyCryptoStore) {
103117
// We have a legacy crypto store, which we may need to migrate from.

0 commit comments

Comments
 (0)