From 124a70c68680da903270ce219a5ad49de886bc93 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 19 Dec 2023 12:50:11 +0000 Subject: [PATCH 01/13] Allow specifing a specific device ID for impersonation. --- src/appservice/Intent.ts | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/src/appservice/Intent.ts b/src/appservice/Intent.ts index 61a9917e..318e73ca 100644 --- a/src/appservice/Intent.ts +++ b/src/appservice/Intent.ts @@ -97,10 +97,12 @@ export class Intent { /** * Sets up crypto on the client if it hasn't already been set up. + * @param providedDeviceId Optional device ID. If given, this will used instead of trying to + * masquerade as the first non-key enabled device. * @returns {Promise} Resolves when complete. */ @timedIntentFunctionCall() - public async enableEncryption(): Promise { + public async enableEncryption(providedDeviceId?: string): Promise { if (!this.cryptoSetupPromise) { // eslint-disable-next-line no-async-promise-executor this.cryptoSetupPromise = new Promise(async (resolve, reject) => { @@ -116,23 +118,32 @@ export class Intent { throw new Error("Failed to create crypto store"); } - // Try to impersonate a device ID - const ownDevices = await this.client.getOwnDevices(); let deviceId = await cryptoStore.getDeviceId(); - if (!deviceId || !ownDevices.some(d => d.device_id === deviceId)) { - const deviceKeys = await this.client.getUserDevices([this.userId]); - const userDeviceKeys = deviceKeys.device_keys[this.userId]; - if (userDeviceKeys) { - // We really should be validating signatures here, but we're actively looking - // for devices without keys to impersonate, so it should be fine. In theory, - // those devices won't even be present but we're cautious. - const devicesWithKeys = Array.from(Object.entries(userDeviceKeys)) - .filter(d => d[0] === d[1].device_id && !!d[1].keys?.[`${DeviceKeyAlgorithm.Curve25519}:${d[1].device_id}`]) - .map(t => t[0]); // grab device ID from tuple - deviceId = ownDevices.find(d => !devicesWithKeys.includes(d.device_id))?.device_id; + if (!providedDeviceId) { + // Try to impersonate a device ID + const ownDevices = await this.client.getOwnDevices(); + let deviceId = await cryptoStore.getDeviceId(); + if (!deviceId || !ownDevices.some(d => d.device_id === deviceId)) { + const deviceKeys = await this.client.getUserDevices([this.userId]); + const userDeviceKeys = deviceKeys.device_keys[this.userId]; + if (userDeviceKeys) { + // We really should be validating signatures here, but we're actively looking + // for devices without keys to impersonate, so it should be fine. In theory, + // those devices won't even be present but we're cautious. + const devicesWithKeys = Array.from(Object.entries(userDeviceKeys)) + .filter(d => d[0] === d[1].device_id && !!d[1].keys?.[`${DeviceKeyAlgorithm.Curve25519}:${d[1].device_id}`]) + .map(t => t[0]); // grab device ID from tuple + deviceId = ownDevices.find(d => !devicesWithKeys.includes(d.device_id))?.device_id; + } } + } else { + if (deviceId && deviceId !== providedDeviceId) { + throw Error(`Storage already configured with an existing device ${deviceId}, cannot use provided device ${providedDeviceId}`); + } + deviceId = providedDeviceId; } let prepared = false; + if (deviceId) { this.makeClient(true); this.client.impersonateUserId(this.userId, deviceId); From 6ab2f498af96bf313a76c1313c7ebc20f295fb31 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 19 Dec 2023 13:08:31 +0000 Subject: [PATCH 02/13] Warn if the device changes./ --- src/appservice/Intent.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/appservice/Intent.ts b/src/appservice/Intent.ts index 318e73ca..a5beeef9 100644 --- a/src/appservice/Intent.ts +++ b/src/appservice/Intent.ts @@ -145,6 +145,11 @@ export class Intent { let prepared = false; if (deviceId) { + const cryptoStore = this.cryptoStorage?.storageForUser(this.userId); + const existingDeviceId = await cryptoStore.getDeviceId(); + if (existingDeviceId && existingDeviceId !== deviceId) { + LogService.warn("Intent", `Device ID has changed for user ${this.userId} from ${existingDeviceId} to ${deviceId}`); + } this.makeClient(true); this.client.impersonateUserId(this.userId, deviceId); From 9014b6b0ea0c2bd954489444b3b403e998f0a331 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 19 Dec 2023 13:15:26 +0000 Subject: [PATCH 03/13] Clear storage if the device changes. --- src/e2ee/CryptoClient.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index 110fa090..2ef4ace1 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -27,6 +27,7 @@ import { RustSdkCryptoStorageProvider } from "../storage/RustSdkCryptoStoragePro import { RustEngine, SYNC_LOCK_NAME } from "./RustEngine"; import { MembershipEvent } from "../models/events/MembershipEvent"; import { IKeyBackupInfoRetrieved } from "../models/KeyBackup"; +import { rm } from "fs/promises"; /** * Manages encryption for a MatrixClient. Get an instance from a MatrixClient directly @@ -72,10 +73,14 @@ export class CryptoClient { if (this.ready) return; // stop re-preparing here const storedDeviceId = await this.client.cryptoStore.getDeviceId(); - if (storedDeviceId) { + const { user_id: userId, device_id: deviceId} = (await this.client.getWhoAmI()); + if (storedDeviceId === deviceId) { this.deviceId = storedDeviceId; + } else if (storedDeviceId && storedDeviceId !== deviceId) { + LogService.warn("CryptoClient", `Device ID for ${userId} has changed from ${storedDeviceId} to ${deviceId}`); + // Clear storage for old device. + await rm(this.storage.storagePath, { recursive: true }); } else { - const deviceId = (await this.client.getWhoAmI())['device_id']; if (!deviceId) { throw new Error("Encryption not possible: server not revealing device ID"); } @@ -86,7 +91,7 @@ export class CryptoClient { LogService.debug("CryptoClient", "Starting with device ID:", this.deviceId); const machine = await OlmMachine.initialize( - new UserId(await this.client.getUserId()), + new UserId(userId), new DeviceId(this.deviceId), this.storage.storagePath, "", this.storage.storageType, From 277d3d8ce4de5e2059c43dee0b7611ceb15d403e Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 19 Dec 2023 13:17:11 +0000 Subject: [PATCH 04/13] Cleanup --- src/e2ee/CryptoClient.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index 2ef4ace1..363349c1 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -74,21 +74,21 @@ export class CryptoClient { const storedDeviceId = await this.client.cryptoStore.getDeviceId(); const { user_id: userId, device_id: deviceId} = (await this.client.getWhoAmI()); - if (storedDeviceId === deviceId) { - this.deviceId = storedDeviceId; - } else if (storedDeviceId && storedDeviceId !== deviceId) { + + if (!deviceId) { + throw new Error("Encryption not possible: server not revealing device ID"); + } + + if (storedDeviceId && storedDeviceId !== deviceId) { LogService.warn("CryptoClient", `Device ID for ${userId} has changed from ${storedDeviceId} to ${deviceId}`); // Clear storage for old device. await rm(this.storage.storagePath, { recursive: true }); - } else { - if (!deviceId) { - throw new Error("Encryption not possible: server not revealing device ID"); - } - this.deviceId = deviceId; - await this.client.cryptoStore.setDeviceId(this.deviceId); } - LogService.debug("CryptoClient", "Starting with device ID:", this.deviceId); + this.deviceId = deviceId; + await this.client.cryptoStore.setDeviceId(this.deviceId); + + LogService.debug("CryptoClient", `Starting ${userId} with device ID:`, this.deviceId); const machine = await OlmMachine.initialize( new UserId(userId), From 78d48dc562909db0f6e192229ffc4ac453f20db0 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 19 Dec 2023 13:18:03 +0000 Subject: [PATCH 05/13] Lint --- src/e2ee/CryptoClient.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index 363349c1..39be4713 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -7,6 +7,7 @@ import { Attachment, EncryptedAttachment, } from "@matrix-org/matrix-sdk-crypto-nodejs"; +import { rm } from "fs/promises"; import { MatrixClient } from "../MatrixClient"; import { LogService } from "../logging/LogService"; @@ -27,7 +28,6 @@ import { RustSdkCryptoStorageProvider } from "../storage/RustSdkCryptoStoragePro import { RustEngine, SYNC_LOCK_NAME } from "./RustEngine"; import { MembershipEvent } from "../models/events/MembershipEvent"; import { IKeyBackupInfoRetrieved } from "../models/KeyBackup"; -import { rm } from "fs/promises"; /** * Manages encryption for a MatrixClient. Get an instance from a MatrixClient directly @@ -73,7 +73,7 @@ export class CryptoClient { if (this.ready) return; // stop re-preparing here const storedDeviceId = await this.client.cryptoStore.getDeviceId(); - const { user_id: userId, device_id: deviceId} = (await this.client.getWhoAmI()); + const { user_id: userId, device_id: deviceId } = (await this.client.getWhoAmI()); if (!deviceId) { throw new Error("Encryption not possible: server not revealing device ID"); From e25be55b62d873e28bb7887694ec7378204e3fef Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 19 Dec 2023 13:22:20 +0000 Subject: [PATCH 06/13] Warn properly --- src/appservice/Intent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/appservice/Intent.ts b/src/appservice/Intent.ts index a5beeef9..38c7d1ce 100644 --- a/src/appservice/Intent.ts +++ b/src/appservice/Intent.ts @@ -138,7 +138,7 @@ export class Intent { } } else { if (deviceId && deviceId !== providedDeviceId) { - throw Error(`Storage already configured with an existing device ${deviceId}, cannot use provided device ${providedDeviceId}`); + LogService.warn(`Storage already configured with an existing device ${deviceId}. Old storage will be cleared.`); } deviceId = providedDeviceId; } From e5dcc5a698b26a9e47df6e34abf48ae88b8c3abd Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 19 Dec 2023 18:09:44 +0000 Subject: [PATCH 07/13] Make tests happy --- src/e2ee/CryptoClient.ts | 14 ++++++++++++-- test/TestUtils.ts | 5 ++++- test/encryption/CryptoClientTest.ts | 10 ++++++---- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index 39be4713..f229cfb7 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -28,6 +28,7 @@ import { RustSdkCryptoStorageProvider } from "../storage/RustSdkCryptoStoragePro import { RustEngine, SYNC_LOCK_NAME } from "./RustEngine"; import { MembershipEvent } from "../models/events/MembershipEvent"; import { IKeyBackupInfoRetrieved } from "../models/KeyBackup"; +import path = require("path"); /** * Manages encryption for a MatrixClient. Get an instance from a MatrixClient directly @@ -82,11 +83,20 @@ export class CryptoClient { if (storedDeviceId && storedDeviceId !== deviceId) { LogService.warn("CryptoClient", `Device ID for ${userId} has changed from ${storedDeviceId} to ${deviceId}`); // Clear storage for old device. - await rm(this.storage.storagePath, { recursive: true }); + try { + await rm(path.join(this.storage.storagePath, "matrix-sdk-crypto.sqlite3")); + } catch (ex) { + if (ex.code !== 'ENOENT') { + throw ex; + } + } } + if (storedDeviceId !== deviceId) { + this.client.cryptoStore.setDeviceId(deviceId); + } this.deviceId = deviceId; - await this.client.cryptoStore.setDeviceId(this.deviceId); + LogService.debug("CryptoClient", `Starting ${userId} with device ID:`, this.deviceId); diff --git a/test/TestUtils.ts b/test/TestUtils.ts index f340e63e..02730902 100644 --- a/test/TestUtils.ts +++ b/test/TestUtils.ts @@ -1,7 +1,7 @@ import * as tmp from "tmp"; import HttpBackend from "matrix-mock-request"; import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; - +import * as simple from "simple-mock"; import { IStorageProvider, MatrixClient, OTKAlgorithm, RustSdkCryptoStorageProvider, UnpaddedBase64, setRequestFn } from "../src"; export const TEST_DEVICE_ID = "TEST_DEVICE"; @@ -44,6 +44,9 @@ export function createTestClient( (client).userId = userId; // private member access setRequestFn(http.requestFn); + // Ensure we always respond to a well-known + client.getWhoAmI = () => Promise.resolve({ user_id: userId, device_id: TEST_DEVICE_ID }); + return { http, hsUrl, accessToken, client }; } diff --git a/test/encryption/CryptoClientTest.ts b/test/encryption/CryptoClientTest.ts index f097be90..e5d17322 100644 --- a/test/encryption/CryptoClientTest.ts +++ b/test/encryption/CryptoClientTest.ts @@ -8,7 +8,6 @@ describe('CryptoClient', () => { it('should not have a device ID or be ready until prepared', () => testCryptoStores(async (cryptoStoreType) => { const userId = "@alice:example.org"; const { client, http } = createTestClient(null, userId, cryptoStoreType); - client.getWhoAmI = () => Promise.resolve({ user_id: userId, device_id: TEST_DEVICE_ID }); expect(client.crypto).toBeDefined(); @@ -46,8 +45,9 @@ describe('CryptoClient', () => { const { client, http } = createTestClient(null, userId, cryptoStoreType); await client.cryptoStore.setDeviceId(TEST_DEVICE_ID); + const CORRECT_DEVICE = "new_device"; - const whoamiSpy = simple.stub().callFn(() => Promise.resolve({ user_id: userId, device_id: "wrong" })); + const whoamiSpy = simple.stub().callFn(() => Promise.resolve({ user_id: userId, device_id: CORRECT_DEVICE })); client.getWhoAmI = whoamiSpy; bindNullEngine(http); @@ -55,8 +55,10 @@ describe('CryptoClient', () => { client.crypto.prepare(), http.flushAllExpected(), ]); - expect(whoamiSpy.callCount).toEqual(0); - expect(client.crypto.clientDeviceId).toEqual(TEST_DEVICE_ID); + // This should be called to check + expect(whoamiSpy.callCount).toEqual(1); + expect(client.crypto.clientDeviceId).toEqual(CORRECT_DEVICE); + expect(await client.cryptoStore.getDeviceId()).toEqual(CORRECT_DEVICE); })); }); From 34f86955c1c2a079bfc14c5982d706521c4b8a60 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 19 Dec 2023 18:16:39 +0000 Subject: [PATCH 08/13] Tidy more --- src/e2ee/CryptoClient.ts | 3 +-- test/MatrixClientTest.ts | 4 ++-- test/TestUtils.ts | 9 ++++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index f229cfb7..3483ba0f 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -8,6 +8,7 @@ import { EncryptedAttachment, } from "@matrix-org/matrix-sdk-crypto-nodejs"; import { rm } from "fs/promises"; +import * as path from 'path'; import { MatrixClient } from "../MatrixClient"; import { LogService } from "../logging/LogService"; @@ -28,7 +29,6 @@ import { RustSdkCryptoStorageProvider } from "../storage/RustSdkCryptoStoragePro import { RustEngine, SYNC_LOCK_NAME } from "./RustEngine"; import { MembershipEvent } from "../models/events/MembershipEvent"; import { IKeyBackupInfoRetrieved } from "../models/KeyBackup"; -import path = require("path"); /** * Manages encryption for a MatrixClient. Get an instance from a MatrixClient directly @@ -97,7 +97,6 @@ export class CryptoClient { } this.deviceId = deviceId; - LogService.debug("CryptoClient", `Starting ${userId} with device ID:`, this.deviceId); const machine = await OlmMachine.initialize( diff --git a/test/MatrixClientTest.ts b/test/MatrixClientTest.ts index 33343809..99c3c404 100644 --- a/test/MatrixClientTest.ts +++ b/test/MatrixClientTest.ts @@ -1043,7 +1043,7 @@ describe('MatrixClient', () => { }); it('should request the user ID if it is not known', async () => { - const { client, http } = createTestClient(); + const { client, http } = createTestClient(undefined, undefined, undefined, { handleWhoAmI: false }); const userId = "@example:example.org"; const response = { @@ -1061,7 +1061,7 @@ describe('MatrixClient', () => { describe('getWhoAmI', () => { it('should call the right endpoint', async () => { - const { client, http } = createTestClient(); + const { client, http } = createTestClient(undefined, undefined, undefined, { handleWhoAmI: false }); const response = { user_id: "@user:example.org", diff --git a/test/TestUtils.ts b/test/TestUtils.ts index 02730902..e8d2cfa3 100644 --- a/test/TestUtils.ts +++ b/test/TestUtils.ts @@ -1,7 +1,7 @@ import * as tmp from "tmp"; import HttpBackend from "matrix-mock-request"; import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; -import * as simple from "simple-mock"; + import { IStorageProvider, MatrixClient, OTKAlgorithm, RustSdkCryptoStorageProvider, UnpaddedBase64, setRequestFn } from "../src"; export const TEST_DEVICE_ID = "TEST_DEVICE"; @@ -31,6 +31,7 @@ export function createTestClient( storage: IStorageProvider = null, userId: string = null, cryptoStoreType?: StoreType, + opts = { handleWhoAmI: true }, ): { client: MatrixClient; http: HttpBackend; @@ -44,8 +45,10 @@ export function createTestClient( (client).userId = userId; // private member access setRequestFn(http.requestFn); - // Ensure we always respond to a well-known - client.getWhoAmI = () => Promise.resolve({ user_id: userId, device_id: TEST_DEVICE_ID }); + if (opts.handleWhoAmI) { + // Ensure we always respond to a whoami + client.getWhoAmI = () => Promise.resolve({ user_id: userId, device_id: TEST_DEVICE_ID }); + } return { http, hsUrl, accessToken, client }; } From 959e603986ee08e47e64702f127ec62cd5b1c23c Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 19 Dec 2023 18:26:36 +0000 Subject: [PATCH 09/13] Fix another test --- test/SynapseAdminApisTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/SynapseAdminApisTest.ts b/test/SynapseAdminApisTest.ts index 1dcd8541..ee3b266a 100644 --- a/test/SynapseAdminApisTest.ts +++ b/test/SynapseAdminApisTest.ts @@ -24,7 +24,7 @@ export function createTestSynapseAdminClient( hsUrl: string; accessToken: string; } { - const result = createTestClient(storage); + const result = createTestClient(storage, undefined, undefined, { handleWhoAmI: false }); const mxClient = result.client; const client = new SynapseAdminApis(mxClient); From 3d82d8315693af7414b9f870d654d84d3dc9388d Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Wed, 20 Dec 2023 12:21:14 +0000 Subject: [PATCH 10/13] Refactor to instead create a new storage per device, rather than deleting old storage. --- src/e2ee/CryptoClient.ts | 16 ++--------- src/storage/IAppserviceStorageProvider.ts | 4 +-- src/storage/RustSdkCryptoStorageProvider.ts | 30 +++++++++++++++++++-- 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index 3483ba0f..be8b789d 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -7,8 +7,6 @@ import { Attachment, EncryptedAttachment, } from "@matrix-org/matrix-sdk-crypto-nodejs"; -import { rm } from "fs/promises"; -import * as path from 'path'; import { MatrixClient } from "../MatrixClient"; import { LogService } from "../logging/LogService"; @@ -80,17 +78,7 @@ export class CryptoClient { throw new Error("Encryption not possible: server not revealing device ID"); } - if (storedDeviceId && storedDeviceId !== deviceId) { - LogService.warn("CryptoClient", `Device ID for ${userId} has changed from ${storedDeviceId} to ${deviceId}`); - // Clear storage for old device. - try { - await rm(path.join(this.storage.storagePath, "matrix-sdk-crypto.sqlite3")); - } catch (ex) { - if (ex.code !== 'ENOENT') { - throw ex; - } - } - } + const storagePath = await this.storage.getMachineStoragePath(deviceId); if (storedDeviceId !== deviceId) { this.client.cryptoStore.setDeviceId(deviceId); @@ -102,7 +90,7 @@ export class CryptoClient { const machine = await OlmMachine.initialize( new UserId(userId), new DeviceId(this.deviceId), - this.storage.storagePath, "", + storagePath, "", this.storage.storageType, ); this.engine = new RustEngine(machine, this.client); diff --git a/src/storage/IAppserviceStorageProvider.ts b/src/storage/IAppserviceStorageProvider.ts index da9eb2c0..aca046bb 100644 --- a/src/storage/IAppserviceStorageProvider.ts +++ b/src/storage/IAppserviceStorageProvider.ts @@ -47,8 +47,8 @@ export interface IAppserviceStorageProvider { export interface IAppserviceCryptoStorageProvider { /** * Gets a storage provider to use for the given user ID. - * @param {string} userId The user ID. - * @returns {ICryptoStorageProvider} The storage provider. + * @param userId The user ID. + * @returns The storage provider. */ storageForUser(userId: string): ICryptoStorageProvider; } diff --git a/src/storage/RustSdkCryptoStorageProvider.ts b/src/storage/RustSdkCryptoStorageProvider.ts index 200845f5..f955bede 100644 --- a/src/storage/RustSdkCryptoStorageProvider.ts +++ b/src/storage/RustSdkCryptoStorageProvider.ts @@ -2,6 +2,8 @@ import * as lowdb from "lowdb"; import * as FileSync from "lowdb/adapters/FileSync"; import * as mkdirp from "mkdirp"; import * as path from "path"; +import { stat, rename, mkdir } from "fs/promises"; +import { PathLike } from "fs"; import * as sha512 from "hash.js/lib/hash/sha/512"; import * as sha256 from "hash.js/lib/hash/sha/256"; import { StoreType as RustSdkCryptoStoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; @@ -12,6 +14,10 @@ import { ICryptoRoomInformation } from "../e2ee/ICryptoRoomInformation"; export { RustSdkCryptoStoreType }; +async function doesFileExist(path: PathLike) { + return stat(path).then(() => true).catch(() => false); +} + /** * A crypto storage provider for the file-based rust-sdk store. * @category Storage providers @@ -40,6 +46,26 @@ export class RustSdkCryptoStorageProvider implements ICryptoStorageProvider { }); } + public async getMachineStoragePath(deviceId: string): Promise { + const newPath = path.join(this.storagePath, sha256().update(deviceId).digest('hex')); + if (await doesFileExist(newPath)) { + // Already exists, short circuit. + return newPath; + } // else: If the path does NOT exist we might need to perform a migration. + + const legacyFilePath = path.join(this.storagePath, 'matrix-sdk-crypto.sqlite3'); + // XXX: Slightly gross cross-dependency file name expectations. + if (await doesFileExist(legacyFilePath) === false) { + // No machine files at all, we can skip. + return newPath; + } + + // We need to move the file. + await mkdir(newPath); + await rename(legacyFilePath, path.join(newPath, 'matrix-sdk-crypto.sqlite3')); + return newPath; + } + public async getDeviceId(): Promise { return this.db.get('deviceId').value(); } @@ -75,7 +101,7 @@ export class RustSdkAppserviceCryptoStorageProvider extends RustSdkCryptoStorage public storageForUser(userId: string): ICryptoStorageProvider { // sha256 because sha512 is a bit big for some operating systems - const key = sha256().update(userId).digest('hex'); - return new RustSdkCryptoStorageProvider(path.join(this.baseStoragePath, key), this.storageType); + const storagePath = path.join(this.baseStoragePath, sha256().update(userId).digest('hex')); + return new RustSdkCryptoStorageProvider(storagePath, this.storageType); } } From 5672a5a4b74acbca3be3c8f3e027bac3656b7168 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Wed, 20 Dec 2023 12:34:11 +0000 Subject: [PATCH 11/13] Log the migration --- src/storage/RustSdkCryptoStorageProvider.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/storage/RustSdkCryptoStorageProvider.ts b/src/storage/RustSdkCryptoStorageProvider.ts index f955bede..9e041e2f 100644 --- a/src/storage/RustSdkCryptoStorageProvider.ts +++ b/src/storage/RustSdkCryptoStorageProvider.ts @@ -11,6 +11,7 @@ import { StoreType as RustSdkCryptoStoreType } from "@matrix-org/matrix-sdk-cryp import { ICryptoStorageProvider } from "./ICryptoStorageProvider"; import { IAppserviceCryptoStorageProvider } from "./IAppserviceStorageProvider"; import { ICryptoRoomInformation } from "../e2ee/ICryptoRoomInformation"; +import { LogService } from "../logging/LogService"; export { RustSdkCryptoStoreType }; @@ -59,10 +60,12 @@ export class RustSdkCryptoStorageProvider implements ICryptoStorageProvider { // No machine files at all, we can skip. return newPath; } - + const legacyDeviceId = await this.getDeviceId(); // We need to move the file. - await mkdir(newPath); - await rename(legacyFilePath, path.join(newPath, 'matrix-sdk-crypto.sqlite3')); + const previousDevicePath = path.join(this.storagePath, sha256().update(legacyDeviceId).digest('hex')); + LogService.warn("RustSdkCryptoStorageProvider", `Migrating path for SDK database for legacy device ${legacyDeviceId}`); + await mkdir(previousDevicePath); + await rename(legacyFilePath, path.join(previousDevicePath, 'matrix-sdk-crypto.sqlite3')); return newPath; } From 1c031efd444aba4f7d1dd0443ee4338ed7d0c808 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Wed, 20 Dec 2023 12:36:28 +0000 Subject: [PATCH 12/13] Move all files but allow it to fail --- src/storage/RustSdkCryptoStorageProvider.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/storage/RustSdkCryptoStorageProvider.ts b/src/storage/RustSdkCryptoStorageProvider.ts index 9e041e2f..434d12bf 100644 --- a/src/storage/RustSdkCryptoStorageProvider.ts +++ b/src/storage/RustSdkCryptoStorageProvider.ts @@ -65,7 +65,16 @@ export class RustSdkCryptoStorageProvider implements ICryptoStorageProvider { const previousDevicePath = path.join(this.storagePath, sha256().update(legacyDeviceId).digest('hex')); LogService.warn("RustSdkCryptoStorageProvider", `Migrating path for SDK database for legacy device ${legacyDeviceId}`); await mkdir(previousDevicePath); - await rename(legacyFilePath, path.join(previousDevicePath, 'matrix-sdk-crypto.sqlite3')); + await rename(legacyFilePath, path.join(previousDevicePath, 'matrix-sdk-crypto.sqlite3')).catch((ex) => + LogService.warn("RustSdkCryptoStorageProvider", `Could not migrate matrix-sdk-crypto.sqlite3`, ex) + ); + await rename(legacyFilePath, path.join(previousDevicePath, 'matrix-sdk-crypto.sqlite3-shm')).catch((ex) => + LogService.warn("RustSdkCryptoStorageProvider", `Could not migrate matrix-sdk-crypto.sqlite3-shm`, ex) + ); + await rename(legacyFilePath, path.join(previousDevicePath, 'matrix-sdk-crypto.sqlite3-wal')).catch((ex) => + LogService.warn("RustSdkCryptoStorageProvider", `Could not migrate matrix-sdk-crypto.sqlite3-wal`, ex) + ); + return newPath; } From 583b1cd0aecd02fb18ce0155012d482a9d3b35b5 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Wed, 20 Dec 2023 12:37:38 +0000 Subject: [PATCH 13/13] trailing commas --- src/storage/RustSdkCryptoStorageProvider.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/storage/RustSdkCryptoStorageProvider.ts b/src/storage/RustSdkCryptoStorageProvider.ts index 434d12bf..ce67976b 100644 --- a/src/storage/RustSdkCryptoStorageProvider.ts +++ b/src/storage/RustSdkCryptoStorageProvider.ts @@ -66,13 +66,13 @@ export class RustSdkCryptoStorageProvider implements ICryptoStorageProvider { LogService.warn("RustSdkCryptoStorageProvider", `Migrating path for SDK database for legacy device ${legacyDeviceId}`); await mkdir(previousDevicePath); await rename(legacyFilePath, path.join(previousDevicePath, 'matrix-sdk-crypto.sqlite3')).catch((ex) => - LogService.warn("RustSdkCryptoStorageProvider", `Could not migrate matrix-sdk-crypto.sqlite3`, ex) + LogService.warn("RustSdkCryptoStorageProvider", `Could not migrate matrix-sdk-crypto.sqlite3`, ex), ); await rename(legacyFilePath, path.join(previousDevicePath, 'matrix-sdk-crypto.sqlite3-shm')).catch((ex) => - LogService.warn("RustSdkCryptoStorageProvider", `Could not migrate matrix-sdk-crypto.sqlite3-shm`, ex) + LogService.warn("RustSdkCryptoStorageProvider", `Could not migrate matrix-sdk-crypto.sqlite3-shm`, ex), ); await rename(legacyFilePath, path.join(previousDevicePath, 'matrix-sdk-crypto.sqlite3-wal')).catch((ex) => - LogService.warn("RustSdkCryptoStorageProvider", `Could not migrate matrix-sdk-crypto.sqlite3-wal`, ex) + LogService.warn("RustSdkCryptoStorageProvider", `Could not migrate matrix-sdk-crypto.sqlite3-wal`, ex), ); return newPath;