1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-07-31 15:24:23 +03:00

Rearrange the verification integration tests, again (#3504)

* Element-R: Implement `CryptoApi.getVerificationRequestsToDeviceInProgress`

* Element-R: Implement `requestOwnUserVerification`

* init aliceClient *after* the fetch interceptors

* Initialise the test client separately for each test

* Avoid running all the tests twice

Currently all of these tests are running twice, with different client
configurations. That's not really adding much value; we just need to run
specific tests that way.

* Factor out functions for building responses
This commit is contained in:
Richard van der Hoff
2023-06-26 15:44:42 +01:00
committed by GitHub
parent e8fb47fdca
commit 326a13bcfe

View File

@ -20,7 +20,7 @@ import { MockResponse } from "fetch-mock";
import fetchMock from "fetch-mock-jest"; import fetchMock from "fetch-mock-jest";
import { IDBFactory } from "fake-indexeddb"; import { IDBFactory } from "fake-indexeddb";
import { createClient, CryptoEvent, MatrixClient } from "../../../src"; import { createClient, CryptoEvent, ICreateClientOpts, MatrixClient } from "../../../src";
import { import {
canAcceptVerificationRequest, canAcceptVerificationRequest,
ShowQrCodeCallbacks, ShowQrCodeCallbacks,
@ -88,6 +88,9 @@ afterAll(() => {
} }
}); });
/** The homeserver url that we give to the test client, and where we intercept /sync, /keys, etc requests. */
const TEST_HOMESERVER_URL = "https://alice-server.com";
/** /**
* Integration tests for verification functionality. * Integration tests for verification functionality.
* *
@ -96,16 +99,6 @@ afterAll(() => {
*/ */
// we test with both crypto stacks... // we test with both crypto stacks...
describe.each(Object.entries(CRYPTO_BACKENDS))("verification (%s)", (backend: string, initCrypto: InitCrypto) => { describe.each(Object.entries(CRYPTO_BACKENDS))("verification (%s)", (backend: string, initCrypto: InitCrypto) => {
// and with (1) the default verification method list, (2) a custom verification method list.
describe.each([undefined, ["m.sas.v1", "m.qr_code.show.v1", "m.reciprocate.v1"]])(
"supported methods=%s",
(methods) => {
runTests(backend, initCrypto, methods);
},
);
});
function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | undefined) {
// oldBackendOnly is an alternative to `it` or `test` which will skip the test if we are running against the // oldBackendOnly is an alternative to `it` or `test` which will skip the test if we are running against the
// Rust backend. Once we have full support in the rust sdk, it will go away. // Rust backend. Once we have full support in the rust sdk, it will go away.
const oldBackendOnly = backend === "rust-sdk" ? test.skip : test; const oldBackendOnly = backend === "rust-sdk" ? test.skip : test;
@ -113,13 +106,13 @@ function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | u
/** the client under test */ /** the client under test */
let aliceClient: MatrixClient; let aliceClient: MatrixClient;
/** an object which intercepts `/sync` requests from {@link #aliceClient} */ /** an object which intercepts `/sync` requests on the test homeserver */
let syncResponder: SyncResponder; let syncResponder: SyncResponder;
/** an object which intercepts `/keys/query` requests from {@link #aliceClient} */ /** an object which intercepts `/keys/query` requests on the test homeserver */
let e2eKeyResponder: E2EKeyResponder; let e2eKeyResponder: E2EKeyResponder;
/** an object which intercepts `/keys/upload` requests from {@link #aliceClient} */ /** an object which intercepts `/keys/upload` requests on the test homeserver */
let e2eKeyReceiver: E2EKeyReceiver; let e2eKeyReceiver: E2EKeyReceiver;
beforeEach(async () => { beforeEach(async () => {
@ -127,28 +120,18 @@ function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | u
fetchMock.catch(404); fetchMock.catch(404);
fetchMock.config.warnOnFallback = false; fetchMock.config.warnOnFallback = false;
const homeserverUrl = "https://alice-server.com"; e2eKeyReceiver = new E2EKeyReceiver(TEST_HOMESERVER_URL);
aliceClient = createClient({ e2eKeyResponder = new E2EKeyResponder(TEST_HOMESERVER_URL);
baseUrl: homeserverUrl,
userId: TEST_USER_ID,
accessToken: "akjgkrgjs",
deviceId: "device_under_test",
verificationMethods: methods,
});
await initCrypto(aliceClient);
e2eKeyReceiver = new E2EKeyReceiver(aliceClient.getHomeserverUrl());
e2eKeyResponder = new E2EKeyResponder(aliceClient.getHomeserverUrl());
e2eKeyResponder.addKeyReceiver(TEST_USER_ID, e2eKeyReceiver); e2eKeyResponder.addKeyReceiver(TEST_USER_ID, e2eKeyReceiver);
syncResponder = new SyncResponder(TEST_HOMESERVER_URL);
syncResponder = new SyncResponder(aliceClient.getHomeserverUrl()); mockInitialApiRequests(TEST_HOMESERVER_URL);
mockInitialApiRequests(aliceClient.getHomeserverUrl());
await aliceClient.startClient();
}); });
afterEach(async () => { afterEach(async () => {
await aliceClient.stopClient(); if (aliceClient !== undefined) {
await aliceClient.stopClient();
}
// Allow in-flight things to complete before we tear down the test // Allow in-flight things to complete before we tear down the test
await jest.runAllTimersAsync(); await jest.runAllTimersAsync();
@ -162,7 +145,10 @@ function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | u
e2eKeyResponder.addDeviceKeys(TEST_USER_ID, TEST_DEVICE_ID, SIGNED_TEST_DEVICE_DATA); e2eKeyResponder.addDeviceKeys(TEST_USER_ID, TEST_DEVICE_ID, SIGNED_TEST_DEVICE_DATA);
}); });
it("can verify another device via SAS", async () => { // test with (1) the default verification method list, (2) a custom verification method list.
const TEST_METHODS = ["m.sas.v1", "m.qr_code.show.v1", "m.reciprocate.v1"];
it.each([undefined, TEST_METHODS])("can verify via SAS (supported methods=%s)", async (methods) => {
aliceClient = await startTestClient({ verificationMethods: methods });
await waitForDeviceList(); await waitForDeviceList();
// initially there should be no verifications in progress // initially there should be no verifications in progress
@ -176,7 +162,7 @@ function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | u
expectSendToDeviceMessage("m.key.verification.request"), expectSendToDeviceMessage("m.key.verification.request"),
aliceClient.getCrypto()!.requestDeviceVerification(TEST_USER_ID, TEST_DEVICE_ID), aliceClient.getCrypto()!.requestDeviceVerification(TEST_USER_ID, TEST_DEVICE_ID),
]); ]);
const transactionId = request.transactionId; const transactionId = request.transactionId!;
expect(transactionId).toBeDefined(); expect(transactionId).toBeDefined();
expect(request.phase).toEqual(VerificationPhase.Requested); expect(request.phase).toEqual(VerificationPhase.Requested);
expect(request.roomId).toBeUndefined(); expect(request.roomId).toBeUndefined();
@ -202,32 +188,14 @@ function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | u
} }
// The dummy device replies with an m.key.verification.ready... // The dummy device replies with an m.key.verification.ready...
returnToDeviceMessageFromSync({ returnToDeviceMessageFromSync(buildReadyMessage(transactionId, ["m.sas.v1"]));
type: "m.key.verification.ready",
content: {
from_device: TEST_DEVICE_ID,
methods: ["m.sas.v1"],
transaction_id: transactionId,
},
});
await waitForVerificationRequestChanged(request); await waitForVerificationRequestChanged(request);
expect(request.phase).toEqual(VerificationPhase.Ready); expect(request.phase).toEqual(VerificationPhase.Ready);
expect(request.otherDeviceId).toEqual(TEST_DEVICE_ID); expect(request.otherDeviceId).toEqual(TEST_DEVICE_ID);
// ... and picks a method with m.key.verification.start // ... and picks a method with m.key.verification.start
returnToDeviceMessageFromSync({ returnToDeviceMessageFromSync(buildSasStartMessage(transactionId));
type: "m.key.verification.start",
content: {
from_device: TEST_DEVICE_ID,
method: "m.sas.v1",
transaction_id: transactionId,
hashes: ["sha256"],
key_agreement_protocols: ["curve25519-hkdf-sha256"],
message_authentication_codes: ["hkdf-hmac-sha256.v2"],
// we have to include "decimal" per the spec.
short_authentication_string: ["decimal", "emoji"],
},
});
// as soon as the Changed event arrives, `verifier` should be defined // as soon as the Changed event arrives, `verifier` should be defined
const verifier = await new Promise<Verifier>((resolve) => { const verifier = await new Promise<Verifier>((resolve) => {
function onChange() { function onChange() {
@ -327,6 +295,7 @@ function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | u
}); });
it("Can make a verification request to *all* devices", async () => { it("Can make a verification request to *all* devices", async () => {
aliceClient = await startTestClient();
// we need an existing cross-signing key for this // we need an existing cross-signing key for this
e2eKeyResponder.addCrossSigningData(SIGNED_CROSS_SIGNING_KEYS_DATA); e2eKeyResponder.addCrossSigningData(SIGNED_CROSS_SIGNING_KEYS_DATA);
await waitForDeviceList(); await waitForDeviceList();
@ -356,6 +325,7 @@ function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | u
}); });
oldBackendOnly("can verify another via QR code with an untrusted cross-signing key", async () => { oldBackendOnly("can verify another via QR code with an untrusted cross-signing key", async () => {
aliceClient = await startTestClient();
// QRCode fails if we don't yet have the cross-signing keys, so make sure we have them now. // QRCode fails if we don't yet have the cross-signing keys, so make sure we have them now.
e2eKeyResponder.addCrossSigningData(SIGNED_CROSS_SIGNING_KEYS_DATA); e2eKeyResponder.addCrossSigningData(SIGNED_CROSS_SIGNING_KEYS_DATA);
await waitForDeviceList(); await waitForDeviceList();
@ -366,26 +336,17 @@ function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | u
expectSendToDeviceMessage("m.key.verification.request"), expectSendToDeviceMessage("m.key.verification.request"),
aliceClient.getCrypto()!.requestDeviceVerification(TEST_USER_ID, TEST_DEVICE_ID), aliceClient.getCrypto()!.requestDeviceVerification(TEST_USER_ID, TEST_DEVICE_ID),
]); ]);
const transactionId = request.transactionId; const transactionId = request.transactionId!;
const toDeviceMessage = requestBody.messages[TEST_USER_ID][TEST_DEVICE_ID]; const toDeviceMessage = requestBody.messages[TEST_USER_ID][TEST_DEVICE_ID];
expect(toDeviceMessage.methods).toContain("m.qr_code.show.v1"); expect(toDeviceMessage.methods).toContain("m.qr_code.show.v1");
expect(toDeviceMessage.methods).toContain("m.reciprocate.v1"); expect(toDeviceMessage.methods).toContain("m.reciprocate.v1");
if (methods === undefined) { expect(toDeviceMessage.methods).toContain("m.qr_code.scan.v1");
expect(toDeviceMessage.methods).toContain("m.qr_code.scan.v1");
}
expect(toDeviceMessage.from_device).toEqual(aliceClient.deviceId); expect(toDeviceMessage.from_device).toEqual(aliceClient.deviceId);
expect(toDeviceMessage.transaction_id).toEqual(transactionId); expect(toDeviceMessage.transaction_id).toEqual(transactionId);
// The dummy device replies with an m.key.verification.ready, with an indication we can scan the QR code // The dummy device replies with an m.key.verification.ready, with an indication we can scan the QR code
returnToDeviceMessageFromSync({ returnToDeviceMessageFromSync(buildReadyMessage(transactionId, ["m.qr_code.scan.v1"]));
type: "m.key.verification.ready",
content: {
from_device: TEST_DEVICE_ID,
methods: ["m.qr_code.scan.v1"],
transaction_id: transactionId,
},
});
await waitForVerificationRequestChanged(request); await waitForVerificationRequestChanged(request);
expect(request.phase).toEqual(VerificationPhase.Ready); expect(request.phase).toEqual(VerificationPhase.Ready);
@ -447,6 +408,7 @@ function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | u
}); });
it("can cancel during the SAS phase", async () => { it("can cancel during the SAS phase", async () => {
aliceClient = await startTestClient();
await waitForDeviceList(); await waitForDeviceList();
// have alice initiate a verification. She should send a m.key.verification.request // have alice initiate a verification. She should send a m.key.verification.request
@ -454,33 +416,14 @@ function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | u
expectSendToDeviceMessage("m.key.verification.request"), expectSendToDeviceMessage("m.key.verification.request"),
aliceClient.getCrypto()!.requestDeviceVerification(TEST_USER_ID, TEST_DEVICE_ID), aliceClient.getCrypto()!.requestDeviceVerification(TEST_USER_ID, TEST_DEVICE_ID),
]); ]);
const transactionId = request.transactionId; const transactionId = request.transactionId!;
// The dummy device replies with an m.key.verification.ready... // The dummy device replies with an m.key.verification.ready...
returnToDeviceMessageFromSync({ returnToDeviceMessageFromSync(buildReadyMessage(transactionId, ["m.sas.v1"]));
type: "m.key.verification.ready",
content: {
from_device: TEST_DEVICE_ID,
methods: ["m.sas.v1"],
transaction_id: transactionId,
},
});
await waitForVerificationRequestChanged(request); await waitForVerificationRequestChanged(request);
// ... and picks a method with m.key.verification.start // ... and picks a method with m.key.verification.start
returnToDeviceMessageFromSync({ returnToDeviceMessageFromSync(buildSasStartMessage(transactionId));
type: "m.key.verification.start",
content: {
from_device: TEST_DEVICE_ID,
method: "m.sas.v1",
transaction_id: transactionId,
hashes: ["sha256"],
key_agreement_protocols: ["curve25519-hkdf-sha256"],
message_authentication_codes: ["hkdf-hmac-sha256.v2"],
// we have to include "decimal" per the spec.
short_authentication_string: ["decimal", "emoji"],
},
});
await waitForVerificationRequestChanged(request); await waitForVerificationRequestChanged(request);
expect(request.phase).toEqual(VerificationPhase.Started); expect(request.phase).toEqual(VerificationPhase.Started);
@ -514,6 +457,7 @@ function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | u
}); });
oldBackendOnly("Incoming verification: can accept", async () => { oldBackendOnly("Incoming verification: can accept", async () => {
aliceClient = await startTestClient();
const TRANSACTION_ID = "abcd"; const TRANSACTION_ID = "abcd";
// Initiate the request by sending a to-device message // Initiate the request by sending a to-device message
@ -551,6 +495,19 @@ function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | u
}); });
}); });
async function startTestClient(opts: Partial<ICreateClientOpts> = {}): Promise<MatrixClient> {
const client = createClient({
baseUrl: TEST_HOMESERVER_URL,
userId: TEST_USER_ID,
accessToken: "akjgkrgjs",
deviceId: "device_under_test",
...opts,
});
await initCrypto(client);
await client.startClient();
return client;
}
/** make sure that the client knows about the dummy device */ /** make sure that the client knows about the dummy device */
async function waitForDeviceList(): Promise<void> { async function waitForDeviceList(): Promise<void> {
// Completing the initial sync will make the device list download outdated device lists (of which our own // Completing the initial sync will make the device list download outdated device lists (of which our own
@ -568,7 +525,7 @@ function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | u
ev.sender ??= TEST_USER_ID; ev.sender ??= TEST_USER_ID;
syncResponder.sendOrQueueSyncResponse({ to_device: { events: [ev] } }); syncResponder.sendOrQueueSyncResponse({ to_device: { events: [ev] } });
} }
} });
/** /**
* Wait for the client under test to send a to-device message of the given type. * Wait for the client under test to send a to-device message of the given type.
@ -613,3 +570,32 @@ function calculateMAC(olmSAS: Olm.SAS, input: string, info: string): string {
function encodeUnpaddedBase64(uint8Array: ArrayBuffer | Uint8Array): string { function encodeUnpaddedBase64(uint8Array: ArrayBuffer | Uint8Array): string {
return Buffer.from(uint8Array).toString("base64").replace(/=+$/g, ""); return Buffer.from(uint8Array).toString("base64").replace(/=+$/g, "");
} }
/** build an m.key.verification.ready to-device message originating from the dummy device */
function buildReadyMessage(transactionId: string, methods: string[]): { type: string; content: object } {
return {
type: "m.key.verification.ready",
content: {
from_device: TEST_DEVICE_ID,
methods: methods,
transaction_id: transactionId,
},
};
}
/** build an m.key.verification.start to-device message suitable for the SAS flow, originating from the dummy device */
function buildSasStartMessage(transactionId: string): { type: string; content: object } {
return {
type: "m.key.verification.start",
content: {
from_device: TEST_DEVICE_ID,
method: "m.sas.v1",
transaction_id: transactionId,
hashes: ["sha256"],
key_agreement_protocols: ["curve25519-hkdf-sha256"],
message_authentication_codes: ["hkdf-hmac-sha256.v2"],
// we have to include "decimal" per the spec.
short_authentication_string: ["decimal", "emoji"],
},
};
}