Skip to content

Commit 725bcf5

Browse files
committed
Don't run migration for Rust crypto if the legacy store is empty
Fixes element-hq/element-web#27447
1 parent 36196ea commit 725bcf5

File tree

5 files changed

+99
-0
lines changed

5 files changed

+99
-0
lines changed

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { populateStore } from "../../test-utils/test_indexeddb_cryptostore_dump"
2323
import { MSK_NOT_CACHED_DATASET } from "../../test-utils/test_indexeddb_cryptostore_dump/no_cached_msk_dump";
2424
import { IDENTITY_NOT_TRUSTED_DATASET } from "../../test-utils/test_indexeddb_cryptostore_dump/unverified";
2525
import { FULL_ACCOUNT_DATASET } from "../../test-utils/test_indexeddb_cryptostore_dump/full_account";
26+
import { EMPTY_ACCOUNT_DATASET } from "../../test-utils/test_indexeddb_cryptostore_dump/empty_account";
2627

2728
jest.setTimeout(15000);
2829

@@ -304,6 +305,36 @@ describe("MatrixClient.initRustCrypto", () => {
304305
});
305306
});
306307

308+
it("should not migrate if account data is missing", async () => {
309+
// See https://github.com/element-hq/element-web/issues/27447
310+
311+
// Given we have an almost-empty legacy account in the database
312+
fetchMock.get("path:/_matrix/client/v3/room_keys/version", EMPTY_ACCOUNT_DATASET.backupResponse);
313+
314+
fetchMock.post("path:/_matrix/client/v3/keys/query", EMPTY_ACCOUNT_DATASET.keyQueryResponse);
315+
316+
const testStoreName = "test-store";
317+
await populateStore(testStoreName, EMPTY_ACCOUNT_DATASET.dumpPath);
318+
const cryptoStore = new IndexedDBCryptoStore(indexedDB, testStoreName);
319+
320+
const matrixClient = createClient({
321+
baseUrl: "http://test.server",
322+
userId: EMPTY_ACCOUNT_DATASET.userId,
323+
deviceId: EMPTY_ACCOUNT_DATASET.deviceId,
324+
cryptoStore,
325+
pickleKey: EMPTY_ACCOUNT_DATASET.pickleKey,
326+
});
327+
328+
// When we start Rust crypto, potentially triggering an upgrade
329+
const progressListener = jest.fn();
330+
matrixClient.addListener(CryptoEvent.LegacyCryptoStoreMigrationProgress, progressListener);
331+
332+
await matrixClient.initRustCrypto();
333+
334+
// Then no error occurs, and no upgrade happens
335+
expect(progressListener.mock.calls.length).toBe(0);
336+
}, 60000);
337+
307338
describe("Legacy trust migration", () => {
308339
async function populateAndStartLegacyCryptoStore(dumpPath: string): Promise<IndexedDBCryptoStore> {
309340
const testStoreName = "test-store";
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
## Dump of an empty libolm indexeddb cryptostore to test skipping migration
2+
3+
A dump of an account which is almost completely empty, and totally unsuitable
4+
for use as a real account.
5+
6+
This dump was manually created by copying and editing full_account.
7+
8+
Created to test
9+
["Unable to restore session" error due due to half-initialised legacy indexeddb crypto store #27447](https://github.com/element-hq/element-web/issues/27447).
10+
We should not launch the Rust migration code when we find a DB in this state.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"account": [],
3+
"device_data": [],
4+
"inbound_group_sessions": [],
5+
"inbound_group_sessions_withheld": [],
6+
"notified_error_devices": [],
7+
"outgoingRoomKeyRequests": [],
8+
"parked_shared_history": [],
9+
"rooms": [],
10+
"session_problems": [],
11+
"sessions": [],
12+
"sessions_needing_backup": [],
13+
"shared_history_inbound_group_sessions": []
14+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { DumpDataSetInfo } from "../index";
2+
3+
/**
4+
* A key query response containing the current keys of the tested user.
5+
* To be used during tests with fetchmock.
6+
*/
7+
const KEYS_QUERY_RESPONSE: any = {
8+
device_keys: {},
9+
master_keys: {},
10+
self_signing_keys: {},
11+
user_signing_keys: {},
12+
};
13+
14+
/**
15+
* A `/room_keys/version` response containing the current server-side backup info.
16+
* To be used during tests with fetchmock.
17+
*/
18+
const BACKUP_RESPONSE: any = {};
19+
20+
/**
21+
* A dataset containing the information for the tested user.
22+
* To be used during tests.
23+
*/
24+
export const EMPTY_ACCOUNT_DATASET: DumpDataSetInfo = {
25+
userId: "@emptyuser:example.com",
26+
deviceId: "EMPTYDEVIC",
27+
pickleKey: "+/bcdefghijklmnopqrstu1/zyxvutsrqponmlkjih2",
28+
backupResponse: BACKUP_RESPONSE,
29+
keyQueryResponse: KEYS_QUERY_RESPONSE,
30+
dumpPath: "spec/test-utils/test_indexeddb_cryptostore_dump/empty_account/dump.json",
31+
};

src/rust-crypto/libolm_migration.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,19 @@ export async function migrateFromLegacyCrypto(args: {
8383
}
8484

8585
await legacyStore.startup();
86+
87+
let account;
88+
await legacyStore.doTxn("readonly", [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => {
89+
legacyStore.getAccount(txn, (acct) => {
90+
account = acct;
91+
});
92+
});
93+
if (!account) {
94+
// This store is not properly set up. Nothing to migrate.
95+
logger.warn("Legacy crypto store is not set up (no account found). Not migrating.");
96+
return;
97+
}
98+
8699
let migrationState = await legacyStore.getMigrationState();
87100

88101
if (migrationState >= MigrationState.MEGOLM_SESSIONS_MIGRATED) {

0 commit comments

Comments
 (0)