You've already forked matrix-js-sdk
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:
committed by
GitHub
parent
e8fb47fdca
commit
326a13bcfe
@ -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"],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user