Skip to content

Commit

Permalink
Fix crypto migration for megolm sessions with no sender key (#4024)
Browse files Browse the repository at this point in the history
Fixes element-hq/element-web#26894

Requires matrix-org/matrix-rust-sdk-crypto-wasm#89 (or
rather, an update to a version of matrix-rust-sdk-crypto-wasm) which includes
it).
  • Loading branch information
richvdh authored Jan 24, 2024
1 parent ab217bd commit 19494e0
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 15 deletions.
73 changes: 62 additions & 11 deletions spec/unit/rust-crypto/rust-crypto.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,21 @@ describe("initRustCrypto", () => {
});

describe("libolm migration", () => {
let mockStore: RustSdkCryptoJs.StoreHandle;

beforeEach(() => {
// Stub out a bunch of stuff in the Rust library
mockStore = { free: jest.fn() } as unknown as StoreHandle;
jest.spyOn(StoreHandle, "open").mockResolvedValue(mockStore);

jest.spyOn(Migration, "migrateBaseData").mockResolvedValue(undefined);
jest.spyOn(Migration, "migrateOlmSessions").mockResolvedValue(undefined);
jest.spyOn(Migration, "migrateMegolmSessions").mockResolvedValue(undefined);

const testOlmMachine = makeTestOlmMachine();
jest.spyOn(OlmMachine, "initFromStore").mockResolvedValue(testOlmMachine);
});

it("migrates data from a legacy crypto store", async () => {
const PICKLE_KEY = "pickle1234";
const legacyStore = new MemoryCryptoStore();
Expand All @@ -186,17 +201,6 @@ describe("initRustCrypto", () => {
createMegolmSessions(legacyStore, nDevices, nSessionsPerDevice);
await legacyStore.markSessionsNeedingBackup([{ senderKey: pad43("device5"), sessionId: "session5" }]);

// Stub out a bunch of stuff in the Rust library
const mockStore = { free: jest.fn() } as unknown as StoreHandle;
jest.spyOn(StoreHandle, "open").mockResolvedValue(mockStore);

jest.spyOn(Migration, "migrateBaseData").mockResolvedValue(undefined);
jest.spyOn(Migration, "migrateOlmSessions").mockResolvedValue(undefined);
jest.spyOn(Migration, "migrateMegolmSessions").mockResolvedValue(undefined);

const testOlmMachine = makeTestOlmMachine();
jest.spyOn(OlmMachine, "initFromStore").mockResolvedValue(testOlmMachine);

fetchMock.get("path:/_matrix/client/v3/room_keys/version", { version: "45" });

function legacyMigrationProgressListener(progress: number, total: number): void {
Expand Down Expand Up @@ -275,12 +279,59 @@ describe("initRustCrypto", () => {
expect(session.senderKey).toEqual(pad43(`device${i}`));
expect(session.pickle).toEqual("sessionPickle");
expect(session.roomId!.toString()).toEqual("!room:id");
expect(session.senderSigningKey).toEqual("sender_signing_key");

// only one of the sessions needs backing up
expect(session.backedUp).toEqual(i !== 5 || j !== 5);
}
}
}, 10000);

it("handles megolm sessions with no `keysClaimed`", async () => {
const legacyStore = new MemoryCryptoStore();
legacyStore.storeAccount({}, "not a real account");

legacyStore.storeEndToEndInboundGroupSession(
pad43(`device1`),
`session1`,
{
forwardingCurve25519KeyChain: [],
room_id: "!room:id",
session: "sessionPickle",
},
undefined,
);

const PICKLE_KEY = "pickle1234";
await initRustCrypto({
logger,
http: makeMatrixHttpApi(),
userId: TEST_USER,
deviceId: TEST_DEVICE_ID,
secretStorage: {} as ServerSideSecretStorage,
cryptoCallbacks: {} as CryptoCallbacks,
storePrefix: "storePrefix",
storePassphrase: "storePassphrase",
legacyCryptoStore: legacyStore,
legacyPickleKey: PICKLE_KEY,
});

expect(Migration.migrateMegolmSessions).toHaveBeenCalledTimes(1);
expect(Migration.migrateMegolmSessions).toHaveBeenCalledWith(
expect.any(Array),
new Uint8Array(Buffer.from(PICKLE_KEY)),
mockStore,
);
const megolmSessions: PickledInboundGroupSession[] = mocked(Migration.migrateMegolmSessions).mock
.calls[0][0];
expect(megolmSessions.length).toEqual(1);
const session = megolmSessions[0];
expect(session.senderKey).toEqual(pad43(`device1`));
expect(session.pickle).toEqual("sessionPickle");
expect(session.roomId!.toString()).toEqual("!room:id");
expect(session.senderSigningKey).toBe(undefined);
}, 10000);

async function encryptAndStoreSecretKey(type: string, key: Uint8Array, pickleKey: string, store: CryptoStore) {
const encryptedKey = await encryptAES(encodeBase64(key), Buffer.from(pickleKey), type);
store.storeSecretStorePrivateKey(undefined, type as keyof SecretStorePrivateKeys, encryptedKey);
Expand Down
2 changes: 1 addition & 1 deletion src/crypto/OlmDevice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export interface InboundGroupSessionData {
room_id: string; // eslint-disable-line camelcase
/** pickled Olm.InboundGroupSession */
session: string;
keysClaimed: Record<string, string>;
keysClaimed?: Record<string, string>;
/** Devices involved in forwarding this session to us (normally empty). */
forwardingCurve25519KeyChain: string[];
/** whether this session is untrusted. */
Expand Down
8 changes: 5 additions & 3 deletions src/rust-crypto/libolm_migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,11 +232,13 @@ async function migrateMegolmSessions(
logger.debug(`Migrating batch of ${batch.length} megolm sessions`);
const migrationData: RustSdkCryptoJs.PickledInboundGroupSession[] = [];
for (const session of batch) {
const sessionData = session.sessionData!;

const pickledSession = new RustSdkCryptoJs.PickledInboundGroupSession();
pickledSession.pickle = session.sessionData!.session;
pickledSession.roomId = new RustSdkCryptoJs.RoomId(session.sessionData!.room_id);
pickledSession.pickle = sessionData.session;
pickledSession.roomId = new RustSdkCryptoJs.RoomId(sessionData.room_id);
pickledSession.senderKey = session.senderKey;
pickledSession.senderSigningKey = session.sessionData!.keysClaimed["ed25519"];
pickledSession.senderSigningKey = sessionData.keysClaimed?.["ed25519"];
pickledSession.backedUp = !session.needsBackup;

// Not sure if we can reliably distinguish imported vs not-imported sessions in the libolm database.
Expand Down

0 comments on commit 19494e0

Please sign in to comment.