Skip to content

Commit

Permalink
On startup, import data from libolm cryptostore
Browse files Browse the repository at this point in the history
  • Loading branch information
richvdh committed Jan 8, 2024
1 parent 9c62595 commit 8c1e897
Show file tree
Hide file tree
Showing 8 changed files with 72,382 additions and 3 deletions.
47 changes: 46 additions & 1 deletion spec/integ/crypto/rust-crypto.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ limitations under the License.

import "fake-indexeddb/auto";
import { IDBFactory } from "fake-indexeddb";
import fetchMock from "fetch-mock-jest";

import { createClient } from "../../../src";
import { createClient, IndexedDBCryptoStore } from "../../../src";
import { populateStore } from "../../test-utils/test_indexeddb_cryptostore_dump";

jest.setTimeout(15000);

afterEach(() => {
// reset fake-indexeddb after each test, to make sure we don't leak connections
Expand Down Expand Up @@ -88,6 +92,47 @@ describe("MatrixClient.initRustCrypto", () => {
await matrixClient.initRustCrypto();
await matrixClient.initRustCrypto();
});

it("should migrate from libolm", async () => {
fetchMock.get("path:/_matrix/client/v3/room_keys/version", {
auth_data: {
public_key: "q+HZiJdHl2Yopv9GGvv7EYSzDMrAiRknK4glSdoaomI",
signatures: {
"@vdhtest200713:matrix.org": {
"ed25519:gh9fGr39eNZUdWynEMJ/q/WZq/Pk/foFxHXFBFm18ZI":
"reDp6Mu+j+tfUL3/T6f5OBT3N825Lzpc43vvG+RvjX6V+KxXzodBQArgCoeEHLtL9OgSBmNrhTkSOX87MWCKAw",
"ed25519:KMFSTJSMLB":
"F8tyV5W6wNi0GXTdSg+gxSCULQi0EYxdAAqfkyNq58KzssZMw5i+PRA0aI2b+D7NH/aZaJrtiYNHJ0gWLSQvAw",
},
},
},
version: "7",
algorithm: "m.megolm_backup.v1.curve25519-aes-sha2",
etag: "1",
count: 79,
});

const testStoreName = "test-store";
await populateStore(testStoreName);
const cryptoStore = new IndexedDBCryptoStore(indexedDB, testStoreName);

const matrixClient = createClient({
baseUrl: "http://test.server",
userId: "@vdhtest200713:matrix.org",
deviceId: "KMFSTJSMLB",
cryptoStore,
pickleKey: "+1k2Ppd7HIisUY824v7JtV3/oEE4yX0TqtmNPyhaD7o",
});

await matrixClient.initRustCrypto();

// Do some basic checks on the imported data
const deviceKeys = await matrixClient.getCrypto()!.getOwnDeviceKeys();
expect(deviceKeys.curve25519).toEqual("LKv0bKbc0EC4h0jknbemv3QalEkeYvuNeUXVRgVVTTU");
expect(deviceKeys.ed25519).toEqual("qK70DEqIXq7T+UU3v/al47Ab4JkMEBLpNrTBMbS5rrw");

expect(await matrixClient.getCrypto()!.getActiveSessionBackupVersion()).toEqual("7");
});
});

describe("MatrixClient.clearStores", () => {
Expand Down
49 changes: 49 additions & 0 deletions spec/test-utils/test_indexeddb_cryptostore_dump/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
Dump of libolm indexeddb cryptostore
------------------------------------

This directory contains a dump of a real indexeddb store from a session using
libolm crypto.

The corresponding pickle key is `+1k2Ppd7HIisUY824v7JtV3/oEE4yX0TqtmNPyhaD7o`.

It was created by pasting the following into the browser console:

```javascript
async function exportIndexedDb(name) {
const db = await new Promise((resolve, reject) => {
const dbReq = indexedDB.open(name);
dbReq.onerror = reject;
dbReq.onsuccess = () => resolve(dbReq.result);
});

const storeNames = db.objectStoreNames;
const exports = {};
for (const store of storeNames) {
exports[store] = [];
const txn = db.transaction(store, "readonly");
const objectStore = txn.objectStore(store);
await new Promise((resolve, reject) => {
const cursorReq = objectStore.openCursor();
cursorReq.onerror = reject;
cursorReq.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
const entry = {value: cursor.value};
if (!objectStore.keyPath) {entry.key = cursor.key}
exports[store].push(entry);
cursor.continue();
} else {
resolve();
}
};
});
}
return exports;
}

window.saveAs(new Blob([
JSON.stringify(await exportIndexedDb("matrix-js-sdk:crypto"), null, 2)
], {type: "application/json;charset=utf-8"}), "dump.json");
```

The pickle key is extracted via `mxMatrixClientPeg.get().crypto.olmDevice.pickleKey`.
71,732 changes: 71,732 additions & 0 deletions spec/test-utils/test_indexeddb_cryptostore_dump/dump.json

Large diffs are not rendered by default.

136 changes: 136 additions & 0 deletions spec/test-utils/test_indexeddb_cryptostore_dump/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
Copyright 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { readFile } from "node:fs/promises";
import { resolve } from "node:path";

/**
* Populate an IndexedDB store with the test data from this directory.
*
* @param name - Name of the IndexedDB database to create.
*/
export async function populateStore(name: string): Promise<IDBDatabase> {
const req = indexedDB.open(name, 11);

const db = await new Promise<IDBDatabase>((resolve, reject) => {
req.onupgradeneeded = (ev): void => {
const db = req.result;
const oldVersion = ev.oldVersion;
upgradeDatabase(oldVersion, db);
};

req.onerror = (ev): void => {
reject(req.error);
};

req.onsuccess = (): void => {
const db = req.result;
resolve(db);
};
});

await importData(db);

return db;
}

/** Create the schema for the indexed db store */
function upgradeDatabase(oldVersion: number, db: IDBDatabase) {
if (oldVersion < 1) {
const outgoingRoomKeyRequestsStore = db.createObjectStore("outgoingRoomKeyRequests", { keyPath: "requestId" });
outgoingRoomKeyRequestsStore.createIndex("session", ["requestBody.room_id", "requestBody.session_id"]);
outgoingRoomKeyRequestsStore.createIndex("state", "state");
}

if (oldVersion < 2) {
db.createObjectStore("account");
}

if (oldVersion < 3) {
const sessionsStore = db.createObjectStore("sessions", { keyPath: ["deviceKey", "sessionId"] });
sessionsStore.createIndex("deviceKey", "deviceKey");
}

if (oldVersion < 4) {
db.createObjectStore("inbound_group_sessions", { keyPath: ["senderCurve25519Key", "sessionId"] });
}

if (oldVersion < 5) {
db.createObjectStore("device_data");
}

if (oldVersion < 6) {
db.createObjectStore("rooms");
}

if (oldVersion < 7) {
db.createObjectStore("sessions_needing_backup", { keyPath: ["senderCurve25519Key", "sessionId"] });
}

if (oldVersion < 8) {
db.createObjectStore("inbound_group_sessions_withheld", { keyPath: ["senderCurve25519Key", "sessionId"] });
}

if (oldVersion < 9) {
const problemsStore = db.createObjectStore("session_problems", { keyPath: ["deviceKey", "time"] });
problemsStore.createIndex("deviceKey", "deviceKey");

db.createObjectStore("notified_error_devices", { keyPath: ["userId", "deviceId"] });
}

if (oldVersion < 10) {
db.createObjectStore("shared_history_inbound_group_sessions", { keyPath: ["roomId"] });
}

if (oldVersion < 11) {
db.createObjectStore("parked_shared_history", { keyPath: ["roomId"] });
}
}

async function importData(db: IDBDatabase) {
const path = resolve("spec/test-utils/test_indexeddb_cryptostore_dump/dump.json");
const json: Record<string, Array<{ key?: any; value: any }>> = JSON.parse(
await readFile(path, { encoding: "utf8" }),
);

for (const [storeName, data] of Object.entries(json)) {
await new Promise((resolve, reject) => {
const store = db.transaction(storeName, "readwrite").objectStore(storeName);

function putEntry(idx: number) {
if (idx >= data.length) {
resolve(undefined);
return;
}

const { key, value } = data[idx];
try {
const putReq = store.put(value, key);
putReq.onsuccess = (_) => putEntry(idx + 1);
putReq.onerror = (_) => reject(putReq.error);
} catch (e) {
throw new Error(
`Error populating '${storeName}' with key ${JSON.stringify(key)}, value ${JSON.stringify(
value,
)}: ${e}`,
);
}
}

putEntry(0);
});
}
}
Loading

0 comments on commit 8c1e897

Please sign in to comment.