1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-06-08 15:21:53 +03:00
matrix-js-sdk/spec/integ/crypto/device-dehydration.spec.ts
Florian D 810f7142e6
Remove legacy crypto (#4653)
* Remove deprecated calls in `webrtc/call.ts`

* Throw error when legacy call was used

* Remove `MatrixClient.initLegacyCrypto` (#4620)

* Remove `MatrixClient.initLegacyCrypto`

* Remove `MatrixClient.initLegacyCrypto` in README.md

* Remove tests using `MatrixClient.initLegacyCrypto`

* Remove legacy crypto support in `sync` api (#4622)

* Remove deprecated `DeviceInfo` in `webrtc/call.ts` (#4654)

* chore(legacy call): Remove `DeviceInfo` usage

* refactor(legacy call): throw `GroupCallUnknownDeviceError` at the end of `initOpponentCrypto`

* Remove deprecated methods and attributes of `MatrixClient` (#4659)

* feat(legacy crypto)!: remove deprecated methods of `MatrixClient`

* test(legacy crypto): update existing tests to not use legacy crypto

- `Embedded.spec.ts`: casting since `encryptAndSendToDevices` is removed from `MatrixClient`.
- `room.spec.ts`: remove deprecated usage of `MatrixClient.crypto`
- `matrix-client.spec.ts` & `matrix-client-methods.spec.ts`: remove calls of deprecated methods of `MatrixClient`

* test(legacy crypto): remove test files using `MatrixClient` deprecated methods

* test(legacy crypto): update existing integ tests to run successfully

* feat(legacy crypto!): remove `ICreateClientOpts.deviceToImport`.

`ICreateClientOpts.deviceToImport` was used in the legacy cryto. The rust crypto doesn't support to import devices in this way.

* feat(legacy crypto!): remove `{get,set}GlobalErrorOnUnknownDevices`

`globalErrorOnUnknownDevices` is not used in the rust-crypto. The API is marked as unstable, we can remove it.

* Remove usage of legacy crypto in `event.ts` (#4666)

* feat(legacy crypto!): remove legacy crypto usage in `event.ts`

* test(legacy crypto): update event.spec.ts to not use legacy crypto types

* Remove legacy crypto export in `matrix.ts` (#4667)

* feat(legacy crypto!): remove legacy crypto export in `matrix.ts`

* test(legacy crypto): update `megolm-backup.spec.ts` to import directly `CryptoApi`

* Remove usage of legacy crypto in integ tests (#4669)

* Clean up legacy stores (#4663)

* feat(legacy crypto!): keep legacy methods used in lib olm migration

The rust cryto needs these legacy stores in order to do the migration from the legacy crypto to the rust crypto. We keep the following methods of the stores:
- Used in `libolm_migration.ts`.
- Needed in the legacy store tests.
- Needed in the rust crypto test migration.

* feat(legacy crypto): extract legacy crypto types in legacy stores

In order to be able to delete the legacy crypto, these stores shouldn't rely on the legacy crypto. We need to extract the used types.

* feat(crypto store): remove `CryptoStore` functions used only by tests

* test(crypto store): use legacy `MemoryStore` type

* Remove deprecated methods of `CryptoBackend` (#4671)

* feat(CryptoBackend)!: remove deprecated methods

* feat(rust-crypto)!: remove deprecated methods of `CryptoBackend`

* test(rust-crypto): remove tests of deprecated methods of `CryptoBackend`

* Remove usage of legacy crypto in `embedded.ts` (#4668)

The interface of `encryptAndSendToDevices` changes because `DeviceInfo` is from the legacy crypto. In fact `encryptAndSendToDevices` only need pairs of userId and deviceId.

* Remove legacy crypto files (#4672)

* fix(legacy store): fix legacy store typing

In https://github.com/matrix-org/matrix-js-sdk/pull/4663, the storeXXX methods were removed of the CryptoStore interface but they are used internally by IndexedDBCryptoStore.

* feat(legacy crypto)!: remove content of `crypto/*` except legacy stores

* test(legacy crypto): remove `spec/unit/crypto/*` except legacy store tests

* refactor: remove unused types

* doc: fix broken link

* doc: remove link tag when typedoc is unable to find the CryptoApi

* Clean up integ test after legacy crypto removal (#4682)

* test(crypto): remove `newBackendOnly` test closure

* test(crypto): fix duplicate test name

* test(crypto): remove `oldBackendOnly` test closure

* test(crypto): remove `rust-sdk` comparison

* test(crypto): remove iteration on `CRYPTO_BACKEND`

* test(crypto): remove old legacy comments and tests

* test(crypto): fix documentations and removed unused expect

* Restore broken link to `CryptoApi` (#4692)

* chore: fix linting and formatting due to merge

* Remove unused crypto type and missing doc (#4696)

* chore(crypto): remove unused types

* doc(crypto): add missing link

* test(call): add test when crypto is enabled
2025-02-07 12:31:40 +00:00

228 lines
9.5 KiB
TypeScript

/*
Copyright 2024 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 "fake-indexeddb/auto";
import fetchMock from "fetch-mock-jest";
import { ClientEvent, createClient, type MatrixClient, MatrixEvent } from "../../../src";
import { CryptoEvent } from "../../../src/crypto-api/index";
import { type RustCrypto } from "../../../src/rust-crypto/rust-crypto";
import { type AddSecretStorageKeyOpts } from "../../../src/secret-storage";
import { E2EKeyReceiver } from "../../test-utils/E2EKeyReceiver";
import { E2EKeyResponder } from "../../test-utils/E2EKeyResponder";
import { emitPromise, EventCounter } from "../../test-utils/test-utils";
describe("Device dehydration", () => {
it("should rehydrate and dehydrate a device", async () => {
jest.useFakeTimers({ doNotFake: ["queueMicrotask"] });
const matrixClient = createClient({
baseUrl: "http://test.server",
userId: "@alice:localhost",
deviceId: "aliceDevice",
cryptoCallbacks: {
getSecretStorageKey: async (keys: any, name: string) => {
return [[...Object.keys(keys.keys)][0], new Uint8Array(32)];
},
},
});
await initializeSecretStorage(matrixClient, "@alice:localhost", "http://test.server");
const creationEventCounter = new EventCounter(matrixClient, CryptoEvent.DehydratedDeviceCreated);
const dehydrationKeyCachedEventCounter = new EventCounter(matrixClient, CryptoEvent.DehydrationKeyCached);
const rehydrationStartedCounter = new EventCounter(matrixClient, CryptoEvent.RehydrationStarted);
const rehydrationCompletedCounter = new EventCounter(matrixClient, CryptoEvent.RehydrationCompleted);
const rehydrationProgressCounter = new EventCounter(matrixClient, CryptoEvent.RehydrationProgress);
// count the number of times the dehydration key gets set
let setDehydrationCount = 0;
matrixClient.on(ClientEvent.AccountData, (event: MatrixEvent) => {
if (event.getType() === "org.matrix.msc3814") {
setDehydrationCount++;
}
});
const crypto = matrixClient.getCrypto()!;
fetchMock.config.overwriteRoutes = true;
// start dehydration -- we start with no dehydrated device, and we
// store the dehydrated device that we create
fetchMock.get("path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device", {
status: 404,
body: {
errcode: "M_NOT_FOUND",
error: "Not found",
},
});
let dehydratedDeviceBody: any;
let dehydrationCount = 0;
let resolveDehydrationPromise: () => void;
fetchMock.put("path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device", (_, opts) => {
dehydratedDeviceBody = JSON.parse(opts.body as string);
dehydrationCount++;
if (resolveDehydrationPromise) {
resolveDehydrationPromise();
}
return {};
});
await crypto.startDehydration();
expect(dehydrationCount).toEqual(1);
expect(creationEventCounter.counter).toEqual(1);
expect(dehydrationKeyCachedEventCounter.counter).toEqual(1);
// a week later, we should have created another dehydrated device
const dehydrationPromise = new Promise<void>((resolve, reject) => {
resolveDehydrationPromise = resolve;
});
jest.advanceTimersByTime(7 * 24 * 60 * 60 * 1000);
await dehydrationPromise;
expect(dehydrationKeyCachedEventCounter.counter).toEqual(1);
expect(dehydrationCount).toEqual(2);
expect(creationEventCounter.counter).toEqual(2);
// restart dehydration -- rehydrate the device that we created above,
// and create a new dehydrated device. We also set `createNewKey`, so
// a new dehydration key will be set
fetchMock.get("path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device", {
device_id: dehydratedDeviceBody.device_id,
device_data: dehydratedDeviceBody.device_data,
});
const eventsResponse = jest.fn((url, opts) => {
// rehydrating should make two calls to the /events endpoint.
// The first time will return a single event, and the second
// time will return no events (which will signal to the
// rehydration function that it can stop)
const body = JSON.parse(opts.body as string);
const nextBatch = body.next_batch ?? "0";
const events = nextBatch === "0" ? [{ sender: "@alice:localhost", type: "m.dummy", content: {} }] : [];
return {
events,
next_batch: nextBatch + "1",
};
});
fetchMock.post(
`path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device/${encodeURIComponent(dehydratedDeviceBody.device_id)}/events`,
eventsResponse,
);
await crypto.startDehydration(true);
expect(dehydrationCount).toEqual(3);
expect(setDehydrationCount).toEqual(2);
expect(eventsResponse.mock.calls).toHaveLength(2);
expect(rehydrationStartedCounter.counter).toEqual(1);
expect(rehydrationCompletedCounter.counter).toEqual(1);
expect(creationEventCounter.counter).toEqual(3);
expect(rehydrationProgressCounter.counter).toEqual(1);
expect(dehydrationKeyCachedEventCounter.counter).toEqual(2);
// test that if we get an error when we try to rotate, it emits an event
fetchMock.put("path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device", {
status: 500,
body: {
errcode: "M_UNKNOWN",
error: "Unknown error",
},
});
const rotationErrorEventPromise = emitPromise(matrixClient, CryptoEvent.DehydratedDeviceRotationError);
jest.advanceTimersByTime(7 * 24 * 60 * 60 * 1000);
await rotationErrorEventPromise;
// Restart dehydration, but return an error for GET /dehydrated_device so that rehydration fails.
fetchMock.get("path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device", {
status: 500,
body: {
errcode: "M_UNKNOWN",
error: "Unknown error",
},
});
fetchMock.put("path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device", (_, opts) => {
return {};
});
const rehydrationErrorEventPromise = emitPromise(matrixClient, CryptoEvent.RehydrationError);
await crypto.startDehydration(true);
await rehydrationErrorEventPromise;
matrixClient.stopClient();
});
});
/** create a new secret storage and cross-signing keys */
async function initializeSecretStorage(
matrixClient: MatrixClient,
userId: string,
homeserverUrl: string,
): Promise<void> {
fetchMock.get("path:/_matrix/client/v3/room_keys/version", {
status: 404,
body: {
errcode: "M_NOT_FOUND",
error: "Not found",
},
});
const e2eKeyReceiver = new E2EKeyReceiver(homeserverUrl);
const e2eKeyResponder = new E2EKeyResponder(homeserverUrl);
e2eKeyResponder.addKeyReceiver(userId, e2eKeyReceiver);
fetchMock.post("path:/_matrix/client/v3/keys/device_signing/upload", {});
fetchMock.post("path:/_matrix/client/v3/keys/signatures/upload", {});
const accountData: Map<string, object> = new Map();
fetchMock.get("glob:http://*/_matrix/client/v3/user/*/account_data/*", (url, opts) => {
const name = url.split("/").pop()!;
const value = accountData.get(name);
if (value) {
return value;
} else {
return {
status: 404,
body: {
errcode: "M_NOT_FOUND",
error: "Not found",
},
};
}
});
fetchMock.put("glob:http://*/_matrix/client/v3/user/*/account_data/*", (url, opts) => {
const name = url.split("/").pop()!;
const value = JSON.parse(opts.body as string);
accountData.set(name, value);
matrixClient.emit(ClientEvent.AccountData, new MatrixEvent({ type: name, content: value }));
return {};
});
await matrixClient.initRustCrypto();
const crypto = matrixClient.getCrypto()! as RustCrypto;
// we need to process a sync so that the OlmMachine will upload keys
await crypto.preprocessToDeviceMessages([]);
await crypto.onSyncCompleted({});
// create initial secret storage
async function createSecretStorageKey() {
return {
keyInfo: {} as AddSecretStorageKeyOpts,
privateKey: new Uint8Array(32),
};
}
await matrixClient.getCrypto()!.bootstrapCrossSigning({ setupNewCrossSigning: true });
await matrixClient.getCrypto()!.bootstrapSecretStorage({
createSecretStorageKey,
setupNewSecretStorage: true,
setupNewKeyBackup: false,
});
}