You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-06 12:02:40 +03:00
Clean up integ tests for incoming user verification (#3758)
Move the tests into verification.spec.ts, enable for both stacks, and other cleanups.
This commit is contained in:
committed by
GitHub
parent
126352afd5
commit
ff53557957
@@ -22,23 +22,21 @@ import { IDBFactory } from "fake-indexeddb";
|
|||||||
import { MockResponse, MockResponseFunction } from "fetch-mock";
|
import { MockResponse, MockResponseFunction } from "fetch-mock";
|
||||||
import Olm from "@matrix-org/olm";
|
import Olm from "@matrix-org/olm";
|
||||||
|
|
||||||
import type { IDeviceKeys } from "../../../src/@types/crypto";
|
|
||||||
import * as testUtils from "../../test-utils/test-utils";
|
import * as testUtils from "../../test-utils/test-utils";
|
||||||
import { CRYPTO_BACKENDS, getSyncResponse, InitCrypto, syncPromise } from "../../test-utils/test-utils";
|
import { CRYPTO_BACKENDS, getSyncResponse, InitCrypto, syncPromise } from "../../test-utils/test-utils";
|
||||||
|
import * as testData from "../../test-utils/test-data";
|
||||||
import {
|
import {
|
||||||
BOB_SIGNED_CROSS_SIGNING_KEYS_DATA,
|
BOB_SIGNED_CROSS_SIGNING_KEYS_DATA,
|
||||||
BOB_SIGNED_TEST_DEVICE_DATA,
|
BOB_SIGNED_TEST_DEVICE_DATA,
|
||||||
BOB_TEST_USER_ID,
|
BOB_TEST_USER_ID,
|
||||||
SIGNED_CROSS_SIGNING_KEYS_DATA,
|
SIGNED_CROSS_SIGNING_KEYS_DATA,
|
||||||
SIGNED_TEST_DEVICE_DATA,
|
SIGNED_TEST_DEVICE_DATA,
|
||||||
TEST_ROOM_ID,
|
|
||||||
TEST_ROOM_ID as ROOM_ID,
|
TEST_ROOM_ID as ROOM_ID,
|
||||||
TEST_USER_ID,
|
TEST_USER_ID,
|
||||||
} from "../../test-utils/test-data";
|
} from "../../test-utils/test-data";
|
||||||
import { TestClient } from "../../TestClient";
|
import { TestClient } from "../../TestClient";
|
||||||
import { logger } from "../../../src/logger";
|
import { logger } from "../../../src/logger";
|
||||||
import {
|
import {
|
||||||
Category,
|
|
||||||
ClientEvent,
|
ClientEvent,
|
||||||
createClient,
|
createClient,
|
||||||
CryptoEvent,
|
CryptoEvent,
|
||||||
@@ -47,7 +45,6 @@ import {
|
|||||||
IDownloadKeyResult,
|
IDownloadKeyResult,
|
||||||
IEvent,
|
IEvent,
|
||||||
IndexedDBCryptoStore,
|
IndexedDBCryptoStore,
|
||||||
IRoomEvent,
|
|
||||||
IStartClientOpts,
|
IStartClientOpts,
|
||||||
MatrixClient,
|
MatrixClient,
|
||||||
MatrixEvent,
|
MatrixEvent,
|
||||||
@@ -58,8 +55,7 @@ import {
|
|||||||
RoomStateEvent,
|
RoomStateEvent,
|
||||||
} from "../../../src/matrix";
|
} from "../../../src/matrix";
|
||||||
import { DeviceInfo } from "../../../src/crypto/deviceinfo";
|
import { DeviceInfo } from "../../../src/crypto/deviceinfo";
|
||||||
import * as testData from "../../test-utils/test-data";
|
import { E2EKeyReceiver } from "../../test-utils/E2EKeyReceiver";
|
||||||
import { E2EKeyReceiver, IE2EKeyReceiver } from "../../test-utils/E2EKeyReceiver";
|
|
||||||
import { ISyncResponder, SyncResponder } from "../../test-utils/SyncResponder";
|
import { ISyncResponder, SyncResponder } from "../../test-utils/SyncResponder";
|
||||||
import { escapeRegExp } from "../../../src/utils";
|
import { escapeRegExp } from "../../../src/utils";
|
||||||
import { downloadDeviceToJsDevice } from "../../../src/rust-crypto/device-converter";
|
import { downloadDeviceToJsDevice } from "../../../src/rust-crypto/device-converter";
|
||||||
@@ -74,6 +70,16 @@ import { CrossSigningKey, CryptoCallbacks, KeyBackupInfo } from "../../../src/cr
|
|||||||
import { E2EKeyResponder } from "../../test-utils/E2EKeyResponder";
|
import { E2EKeyResponder } from "../../test-utils/E2EKeyResponder";
|
||||||
import { DecryptionError } from "../../../src/crypto/algorithms";
|
import { DecryptionError } from "../../../src/crypto/algorithms";
|
||||||
import { IKeyBackup } from "../../../src/crypto/backup";
|
import { IKeyBackup } from "../../../src/crypto/backup";
|
||||||
|
import {
|
||||||
|
createOlmSession,
|
||||||
|
createOlmAccount,
|
||||||
|
encryptGroupSessionKey,
|
||||||
|
encryptMegolmEvent,
|
||||||
|
encryptMegolmEventRawPlainText,
|
||||||
|
encryptOlmEvent,
|
||||||
|
establishOlmSession,
|
||||||
|
getTestOlmAccountKeys,
|
||||||
|
} from "./olm-utils";
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
// reset fake-indexeddb after each test, to make sure we don't leak connections
|
// reset fake-indexeddb after each test, to make sure we don't leak connections
|
||||||
@@ -82,197 +88,6 @@ afterEach(() => {
|
|||||||
indexedDB = new IDBFactory();
|
indexedDB = new IDBFactory();
|
||||||
});
|
});
|
||||||
|
|
||||||
// start an Olm session with a given recipient
|
|
||||||
async function createOlmSession(olmAccount: Olm.Account, recipientTestClient: IE2EKeyReceiver): Promise<Olm.Session> {
|
|
||||||
const keys = await recipientTestClient.awaitOneTimeKeyUpload();
|
|
||||||
const otkId = Object.keys(keys)[0];
|
|
||||||
const otk = keys[otkId];
|
|
||||||
|
|
||||||
const session = new global.Olm.Session();
|
|
||||||
session.create_outbound(olmAccount, recipientTestClient.getDeviceKey(), otk.key);
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
|
|
||||||
// IToDeviceEvent isn't exported by src/sync-accumulator.ts
|
|
||||||
interface ToDeviceEvent {
|
|
||||||
content: IContent;
|
|
||||||
sender: string;
|
|
||||||
type: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** encrypt an event with an existing olm session */
|
|
||||||
function encryptOlmEvent(opts: {
|
|
||||||
/** the sender's user id */
|
|
||||||
sender?: string;
|
|
||||||
/** the sender's curve25519 key */
|
|
||||||
senderKey: string;
|
|
||||||
/** the sender's ed25519 key */
|
|
||||||
senderSigningKey: string;
|
|
||||||
/** the olm session to use for encryption */
|
|
||||||
p2pSession: Olm.Session;
|
|
||||||
/** the recipient's user id */
|
|
||||||
recipient: string;
|
|
||||||
/** the recipient's curve25519 key */
|
|
||||||
recipientCurve25519Key: string;
|
|
||||||
/** the recipient's ed25519 key */
|
|
||||||
recipientEd25519Key: string;
|
|
||||||
/** the payload of the message */
|
|
||||||
plaincontent?: object;
|
|
||||||
/** the event type of the payload */
|
|
||||||
plaintype?: string;
|
|
||||||
}): ToDeviceEvent {
|
|
||||||
expect(opts.senderKey).toBeTruthy();
|
|
||||||
expect(opts.p2pSession).toBeTruthy();
|
|
||||||
expect(opts.recipient).toBeTruthy();
|
|
||||||
|
|
||||||
const plaintext = {
|
|
||||||
content: opts.plaincontent || {},
|
|
||||||
recipient: opts.recipient,
|
|
||||||
recipient_keys: {
|
|
||||||
ed25519: opts.recipientEd25519Key,
|
|
||||||
},
|
|
||||||
keys: {
|
|
||||||
ed25519: opts.senderSigningKey,
|
|
||||||
},
|
|
||||||
sender: opts.sender || "@bob:xyz",
|
|
||||||
type: opts.plaintype || "m.test",
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: {
|
|
||||||
algorithm: "m.olm.v1.curve25519-aes-sha2",
|
|
||||||
ciphertext: {
|
|
||||||
[opts.recipientCurve25519Key]: opts.p2pSession.encrypt(JSON.stringify(plaintext)),
|
|
||||||
},
|
|
||||||
sender_key: opts.senderKey,
|
|
||||||
},
|
|
||||||
sender: opts.sender || "@bob:xyz",
|
|
||||||
type: "m.room.encrypted",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// encrypt an event with megolm
|
|
||||||
function encryptMegolmEvent(opts: {
|
|
||||||
senderKey: string;
|
|
||||||
groupSession: Olm.OutboundGroupSession;
|
|
||||||
plaintext?: Partial<IEvent>;
|
|
||||||
room_id?: string;
|
|
||||||
}): IEvent {
|
|
||||||
expect(opts.senderKey).toBeTruthy();
|
|
||||||
expect(opts.groupSession).toBeTruthy();
|
|
||||||
|
|
||||||
const plaintext = opts.plaintext || {};
|
|
||||||
if (!plaintext.content) {
|
|
||||||
plaintext.content = {
|
|
||||||
body: "42",
|
|
||||||
msgtype: "m.text",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (!plaintext.type) {
|
|
||||||
plaintext.type = "m.room.message";
|
|
||||||
}
|
|
||||||
if (!plaintext.room_id) {
|
|
||||||
expect(opts.room_id).toBeTruthy();
|
|
||||||
plaintext.room_id = opts.room_id;
|
|
||||||
}
|
|
||||||
return encryptMegolmEventRawPlainText({
|
|
||||||
senderKey: opts.senderKey,
|
|
||||||
groupSession: opts.groupSession,
|
|
||||||
plaintext,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function encryptMegolmEventRawPlainText(opts: {
|
|
||||||
senderKey: string;
|
|
||||||
groupSession: Olm.OutboundGroupSession;
|
|
||||||
plaintext: Partial<IEvent>;
|
|
||||||
origin_server_ts?: number;
|
|
||||||
}): IEvent {
|
|
||||||
return {
|
|
||||||
event_id: "$test_megolm_event_" + Math.random(),
|
|
||||||
sender: opts.plaintext.sender ?? "@not_the_real_sender:example.com",
|
|
||||||
origin_server_ts: opts.plaintext.origin_server_ts ?? 1672944778000,
|
|
||||||
content: {
|
|
||||||
algorithm: "m.megolm.v1.aes-sha2",
|
|
||||||
ciphertext: opts.groupSession.encrypt(JSON.stringify(opts.plaintext)),
|
|
||||||
device_id: "testDevice",
|
|
||||||
sender_key: opts.senderKey,
|
|
||||||
session_id: opts.groupSession.session_id(),
|
|
||||||
},
|
|
||||||
type: "m.room.encrypted",
|
|
||||||
unsigned: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** build an encrypted room_key event to share a group session, using an existing olm session */
|
|
||||||
function encryptGroupSessionKey(opts: {
|
|
||||||
/** recipient's user id */
|
|
||||||
recipient: string;
|
|
||||||
/** the recipient's curve25519 key */
|
|
||||||
recipientCurve25519Key: string;
|
|
||||||
/** the recipient's ed25519 key */
|
|
||||||
recipientEd25519Key: string;
|
|
||||||
/** sender's olm account */
|
|
||||||
olmAccount: Olm.Account;
|
|
||||||
/** sender's olm session with the recipient */
|
|
||||||
p2pSession: Olm.Session;
|
|
||||||
groupSession: Olm.OutboundGroupSession;
|
|
||||||
room_id?: string;
|
|
||||||
}): Partial<IEvent> {
|
|
||||||
const senderKeys = JSON.parse(opts.olmAccount.identity_keys());
|
|
||||||
return encryptOlmEvent({
|
|
||||||
senderKey: senderKeys.curve25519,
|
|
||||||
senderSigningKey: senderKeys.ed25519,
|
|
||||||
recipient: opts.recipient,
|
|
||||||
recipientCurve25519Key: opts.recipientCurve25519Key,
|
|
||||||
recipientEd25519Key: opts.recipientEd25519Key,
|
|
||||||
p2pSession: opts.p2pSession,
|
|
||||||
plaincontent: {
|
|
||||||
algorithm: "m.megolm.v1.aes-sha2",
|
|
||||||
room_id: opts.room_id,
|
|
||||||
session_id: opts.groupSession.session_id(),
|
|
||||||
session_key: opts.groupSession.session_key(),
|
|
||||||
},
|
|
||||||
plaintype: "m.room_key",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Establish an Olm Session with the test user
|
|
||||||
*
|
|
||||||
* Waits for the test user to upload their keys, then sends a /sync response with a to-device message which will
|
|
||||||
* establish an Olm session.
|
|
||||||
*
|
|
||||||
* @param testClient - the MatrixClient under test, which we expect to upload account keys, and to make a
|
|
||||||
* /sync request which we will respond to.
|
|
||||||
* @param keyReceiver - an IE2EKeyReceiver which will intercept the /keys/upload request from the client under test
|
|
||||||
* @param syncResponder - an ISyncResponder which will intercept /sync requests from the client under test
|
|
||||||
* @param peerOlmAccount: an OlmAccount which will be used to initiate the Olm session.
|
|
||||||
*/
|
|
||||||
async function establishOlmSession(
|
|
||||||
testClient: MatrixClient,
|
|
||||||
keyReceiver: IE2EKeyReceiver,
|
|
||||||
syncResponder: ISyncResponder,
|
|
||||||
peerOlmAccount: Olm.Account,
|
|
||||||
): Promise<Olm.Session> {
|
|
||||||
const peerE2EKeys = JSON.parse(peerOlmAccount.identity_keys());
|
|
||||||
const p2pSession = await createOlmSession(peerOlmAccount, keyReceiver);
|
|
||||||
const olmEvent = encryptOlmEvent({
|
|
||||||
senderKey: peerE2EKeys.curve25519,
|
|
||||||
senderSigningKey: peerE2EKeys.ed25519,
|
|
||||||
recipient: testClient.getUserId()!,
|
|
||||||
recipientCurve25519Key: keyReceiver.getDeviceKey(),
|
|
||||||
recipientEd25519Key: keyReceiver.getSigningKey(),
|
|
||||||
p2pSession: p2pSession,
|
|
||||||
});
|
|
||||||
syncResponder.sendOrQueueSyncResponse({
|
|
||||||
next_batch: 1,
|
|
||||||
to_device: { events: [olmEvent] },
|
|
||||||
});
|
|
||||||
await syncPromise(testClient);
|
|
||||||
return p2pSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expect that the client shares keys with the given recipient
|
* Expect that the client shares keys with the given recipient
|
||||||
*
|
*
|
||||||
@@ -461,20 +276,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
|||||||
* @returns The fake query response
|
* @returns The fake query response
|
||||||
*/
|
*/
|
||||||
function getTestKeysQueryResponse(userId: string): IDownloadKeyResult {
|
function getTestKeysQueryResponse(userId: string): IDownloadKeyResult {
|
||||||
const testE2eKeys = JSON.parse(testOlmAccount.identity_keys());
|
const testDeviceKeys = getTestOlmAccountKeys(testOlmAccount, userId, "DEVICE_ID");
|
||||||
const testDeviceKeys: IDeviceKeys = {
|
|
||||||
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
|
|
||||||
device_id: "DEVICE_ID",
|
|
||||||
keys: {
|
|
||||||
"curve25519:DEVICE_ID": testE2eKeys.curve25519,
|
|
||||||
"ed25519:DEVICE_ID": testE2eKeys.ed25519,
|
|
||||||
},
|
|
||||||
user_id: userId,
|
|
||||||
};
|
|
||||||
const j = anotherjson.stringify(testDeviceKeys);
|
|
||||||
const sig = testOlmAccount.sign(j);
|
|
||||||
testDeviceKeys.signatures = { [userId]: { "ed25519:DEVICE_ID": sig } };
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
device_keys: { [userId]: { DEVICE_ID: testDeviceKeys } },
|
device_keys: { [userId]: { DEVICE_ID: testDeviceKeys } },
|
||||||
failures: {},
|
failures: {},
|
||||||
@@ -552,9 +354,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
|||||||
await initCrypto(aliceClient);
|
await initCrypto(aliceClient);
|
||||||
|
|
||||||
// create a test olm device which we will use to communicate with alice. We use libolm to implement this.
|
// create a test olm device which we will use to communicate with alice. We use libolm to implement this.
|
||||||
await Olm.init();
|
testOlmAccount = await createOlmAccount();
|
||||||
testOlmAccount = new Olm.Account();
|
|
||||||
testOlmAccount.create();
|
|
||||||
const testE2eKeys = JSON.parse(testOlmAccount.identity_keys());
|
const testE2eKeys = JSON.parse(testOlmAccount.identity_keys());
|
||||||
testSenderKey = testE2eKeys.curve25519;
|
testSenderKey = testE2eKeys.curve25519;
|
||||||
},
|
},
|
||||||
@@ -2770,195 +2570,6 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Incoming verification in a DM", () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
// anything that we don't have a specific matcher for silently returns a 404
|
|
||||||
fetchMock.catch(404);
|
|
||||||
|
|
||||||
keyResponder = new E2EKeyResponder(aliceClient.getHomeserverUrl());
|
|
||||||
keyResponder.addKeyReceiver(TEST_USER_ID, keyReceiver);
|
|
||||||
|
|
||||||
expectAliceKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
|
|
||||||
await startClientAndAwaitFirstSync();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.useRealTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a verification request event from Bob
|
|
||||||
* @see https://spec.matrix.org/v1.7/client-server-api/#mkeyverificationrequest
|
|
||||||
*/
|
|
||||||
function createVerificationRequestEvent(): IRoomEvent {
|
|
||||||
return {
|
|
||||||
content: {
|
|
||||||
body: "Verification request from Bob to Alice",
|
|
||||||
from_device: "BobDevice",
|
|
||||||
methods: ["m.sas.v1"],
|
|
||||||
msgtype: "m.key.verification.request",
|
|
||||||
to: aliceClient.getUserId()!,
|
|
||||||
},
|
|
||||||
event_id: "$143273582443PhrSn:example.org",
|
|
||||||
origin_server_ts: Date.now(),
|
|
||||||
room_id: TEST_ROOM_ID,
|
|
||||||
sender: "@bob:xyz",
|
|
||||||
type: "m.room.message",
|
|
||||||
unsigned: {
|
|
||||||
age: 1234,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a to-device event
|
|
||||||
* @param groupSession
|
|
||||||
* @param p2pSession
|
|
||||||
*/
|
|
||||||
function createToDeviceEvent(groupSession: Olm.OutboundGroupSession, p2pSession: Olm.Session): Partial<IEvent> {
|
|
||||||
return encryptGroupSessionKey({
|
|
||||||
recipient: aliceClient.getUserId()!,
|
|
||||||
recipientCurve25519Key: keyReceiver.getDeviceKey(),
|
|
||||||
recipientEd25519Key: keyReceiver.getSigningKey(),
|
|
||||||
olmAccount: testOlmAccount,
|
|
||||||
p2pSession: p2pSession,
|
|
||||||
groupSession: groupSession,
|
|
||||||
room_id: ROOM_ID,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create and encrypt a verification request event
|
|
||||||
* @param groupSession
|
|
||||||
*/
|
|
||||||
function createEncryptedMessage(groupSession: Olm.OutboundGroupSession): IEvent {
|
|
||||||
return encryptMegolmEvent({
|
|
||||||
senderKey: testSenderKey,
|
|
||||||
groupSession: groupSession,
|
|
||||||
room_id: ROOM_ID,
|
|
||||||
plaintext: createVerificationRequestEvent(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
newBackendOnly("Verification request from Bob to Alice", async () => {
|
|
||||||
// Tell alice she is sharing a room with bob
|
|
||||||
const syncResponse = getSyncResponse(["@bob:xyz"]);
|
|
||||||
|
|
||||||
// Add verification request from Bob to Alice in the DM between them
|
|
||||||
syncResponse.rooms[Category.Join][TEST_ROOM_ID].timeline.events.push(createVerificationRequestEvent());
|
|
||||||
syncResponder.sendOrQueueSyncResponse(syncResponse);
|
|
||||||
// Wait for the sync response to be processed
|
|
||||||
await syncPromise(aliceClient);
|
|
||||||
|
|
||||||
const request = aliceClient.getCrypto()!.findVerificationRequestDMInProgress(TEST_ROOM_ID, "@bob:xyz");
|
|
||||||
// Expect to find the verification request received during the sync
|
|
||||||
expect(request?.roomId).toBe(TEST_ROOM_ID);
|
|
||||||
expect(request?.isSelfVerification).toBe(false);
|
|
||||||
expect(request?.otherUserId).toBe("@bob:xyz");
|
|
||||||
});
|
|
||||||
|
|
||||||
newBackendOnly("Verification request not found", async () => {
|
|
||||||
// Tell alice she is sharing a room with bob
|
|
||||||
syncResponder.sendOrQueueSyncResponse(getSyncResponse(["@bob:xyz"]));
|
|
||||||
// Wait for the sync response to be processed
|
|
||||||
await syncPromise(aliceClient);
|
|
||||||
|
|
||||||
// Expect to not find any verification request
|
|
||||||
const request = aliceClient.getCrypto()!.findVerificationRequestDMInProgress(TEST_ROOM_ID, "@bob:xyz");
|
|
||||||
expect(request).not.toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
newBackendOnly("Process encrypted verification request", async () => {
|
|
||||||
const p2pSession = await createOlmSession(testOlmAccount, keyReceiver);
|
|
||||||
const groupSession = new Olm.OutboundGroupSession();
|
|
||||||
groupSession.create();
|
|
||||||
|
|
||||||
// make the room_key event, but don't send it yet
|
|
||||||
const toDeviceEvent = createToDeviceEvent(groupSession, p2pSession);
|
|
||||||
|
|
||||||
// Add verification request from Bob to Alice in the DM between them
|
|
||||||
syncResponder.sendOrQueueSyncResponse({
|
|
||||||
next_batch: 1,
|
|
||||||
rooms: { join: { [ROOM_ID]: { timeline: { events: [createEncryptedMessage(groupSession)] } } } },
|
|
||||||
});
|
|
||||||
// Wait for the sync response to be processed
|
|
||||||
await syncPromise(aliceClient);
|
|
||||||
|
|
||||||
const room = aliceClient.getRoom(ROOM_ID)!;
|
|
||||||
const matrixEvent = room.getLiveTimeline().getEvents()[0];
|
|
||||||
|
|
||||||
// wait for a first attempt at decryption: should fail
|
|
||||||
await testUtils.awaitDecryption(matrixEvent);
|
|
||||||
expect(matrixEvent.getContent().msgtype).toEqual("m.bad.encrypted");
|
|
||||||
|
|
||||||
// Send the Bob's keys
|
|
||||||
syncResponder.sendOrQueueSyncResponse({
|
|
||||||
next_batch: 2,
|
|
||||||
to_device: {
|
|
||||||
events: [toDeviceEvent],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
await syncPromise(aliceClient);
|
|
||||||
|
|
||||||
// Wait for the message to be decrypted
|
|
||||||
await testUtils.awaitDecryption(matrixEvent, { waitOnDecryptionFailure: true });
|
|
||||||
|
|
||||||
const request = aliceClient.getCrypto()!.findVerificationRequestDMInProgress(TEST_ROOM_ID, "@bob:xyz");
|
|
||||||
// Expect to find the verification request received during the sync
|
|
||||||
expect(request?.roomId).toBe(TEST_ROOM_ID);
|
|
||||||
expect(request?.isSelfVerification).toBe(false);
|
|
||||||
expect(request?.otherUserId).toBe("@bob:xyz");
|
|
||||||
});
|
|
||||||
|
|
||||||
newBackendOnly(
|
|
||||||
"If Bob keys are not received in the 5mins after the verification request, the request is ignored",
|
|
||||||
async () => {
|
|
||||||
const p2pSession = await createOlmSession(testOlmAccount, keyReceiver);
|
|
||||||
const groupSession = new Olm.OutboundGroupSession();
|
|
||||||
groupSession.create();
|
|
||||||
|
|
||||||
// make the room_key event, but don't send it yet
|
|
||||||
const toDeviceEvent = createToDeviceEvent(groupSession, p2pSession);
|
|
||||||
|
|
||||||
jest.useFakeTimers();
|
|
||||||
|
|
||||||
// Add verification request from Bob to Alice in the DM between them
|
|
||||||
syncResponder.sendOrQueueSyncResponse({
|
|
||||||
next_batch: 1,
|
|
||||||
rooms: { join: { [ROOM_ID]: { timeline: { events: [createEncryptedMessage(groupSession)] } } } },
|
|
||||||
});
|
|
||||||
// Wait for the sync response to be processed
|
|
||||||
await syncPromise(aliceClient);
|
|
||||||
|
|
||||||
const room = aliceClient.getRoom(ROOM_ID)!;
|
|
||||||
const matrixEvent = room.getLiveTimeline().getEvents()[0];
|
|
||||||
|
|
||||||
// wait for a first attempt at decryption: should fail
|
|
||||||
await testUtils.awaitDecryption(matrixEvent);
|
|
||||||
expect(matrixEvent.getContent().msgtype).toEqual("m.bad.encrypted");
|
|
||||||
|
|
||||||
// Advance time by 5mins, the verification request should be ignored after that
|
|
||||||
jest.advanceTimersByTime(5 * 60 * 1000);
|
|
||||||
|
|
||||||
// Send the Bob's keys
|
|
||||||
syncResponder.sendOrQueueSyncResponse({
|
|
||||||
next_batch: 2,
|
|
||||||
to_device: {
|
|
||||||
events: [toDeviceEvent],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
await syncPromise(aliceClient);
|
|
||||||
|
|
||||||
// Wait for the message to be decrypted
|
|
||||||
await testUtils.awaitDecryption(matrixEvent, { waitOnDecryptionFailure: true });
|
|
||||||
|
|
||||||
const request = aliceClient.getCrypto()!.findVerificationRequestDMInProgress(TEST_ROOM_ID, "@bob:xyz");
|
|
||||||
// the request should not be present
|
|
||||||
expect(request).not.toBeDefined();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Check if the cross signing keys are available for a user", () => {
|
describe("Check if the cross signing keys are available for a user", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
// anything that we don't have a specific matcher for silently returns a 404
|
// anything that we don't have a specific matcher for silently returns a 404
|
||||||
|
255
spec/integ/crypto/olm-utils.ts
Normal file
255
spec/integ/crypto/olm-utils.ts
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016-2023 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 Olm from "@matrix-org/olm";
|
||||||
|
import anotherjson from "another-json";
|
||||||
|
|
||||||
|
import { IContent, IDeviceKeys, IEvent, MatrixClient } from "../../../src";
|
||||||
|
import { IE2EKeyReceiver } from "../../test-utils/E2EKeyReceiver";
|
||||||
|
import { ISyncResponder } from "../../test-utils/SyncResponder";
|
||||||
|
import { syncPromise } from "../../test-utils/test-utils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @module
|
||||||
|
*
|
||||||
|
* A set of utilities for creating Olm accounts and sessions, and encrypting/decrypting with Olm/Megolm.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Create an Olm Account object */
|
||||||
|
export async function createOlmAccount(): Promise<Olm.Account> {
|
||||||
|
await Olm.init();
|
||||||
|
const testOlmAccount = new Olm.Account();
|
||||||
|
testOlmAccount.create();
|
||||||
|
return testOlmAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the device keys for the test Olm Account
|
||||||
|
*
|
||||||
|
* @param olmAccount - Test olm account
|
||||||
|
* @param userId - The user ID to present the keys as belonging to
|
||||||
|
*/
|
||||||
|
export function getTestOlmAccountKeys(olmAccount: Olm.Account, userId: string, deviceId: string): IDeviceKeys {
|
||||||
|
const testE2eKeys = JSON.parse(olmAccount.identity_keys());
|
||||||
|
const testDeviceKeys: IDeviceKeys = {
|
||||||
|
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
|
||||||
|
device_id: deviceId,
|
||||||
|
keys: {
|
||||||
|
[`curve25519:${deviceId}`]: testE2eKeys.curve25519,
|
||||||
|
[`ed25519:${deviceId}`]: testE2eKeys.ed25519,
|
||||||
|
},
|
||||||
|
user_id: userId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const j = anotherjson.stringify(testDeviceKeys);
|
||||||
|
const sig = olmAccount.sign(j);
|
||||||
|
testDeviceKeys.signatures = { [userId]: { [`ed25519:${deviceId}`]: sig } };
|
||||||
|
return testDeviceKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** start an Olm session with a given recipient */
|
||||||
|
export async function createOlmSession(
|
||||||
|
olmAccount: Olm.Account,
|
||||||
|
recipientTestClient: IE2EKeyReceiver,
|
||||||
|
): Promise<Olm.Session> {
|
||||||
|
const keys = await recipientTestClient.awaitOneTimeKeyUpload();
|
||||||
|
const otkId = Object.keys(keys)[0];
|
||||||
|
const otk = keys[otkId];
|
||||||
|
|
||||||
|
const session = new global.Olm.Session();
|
||||||
|
session.create_outbound(olmAccount, recipientTestClient.getDeviceKey(), otk.key);
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IToDeviceEvent isn't exported by src/sync-accumulator.ts
|
||||||
|
export interface ToDeviceEvent {
|
||||||
|
content: IContent;
|
||||||
|
sender: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** encrypt an event with an existing olm session */
|
||||||
|
export function encryptOlmEvent(opts: {
|
||||||
|
/** the sender's user id */
|
||||||
|
sender?: string;
|
||||||
|
/** the sender's curve25519 key */
|
||||||
|
senderKey: string;
|
||||||
|
/** the sender's ed25519 key */
|
||||||
|
senderSigningKey: string;
|
||||||
|
/** the olm session to use for encryption */
|
||||||
|
p2pSession: Olm.Session;
|
||||||
|
/** the recipient's user id */
|
||||||
|
recipient: string;
|
||||||
|
/** the recipient's curve25519 key */
|
||||||
|
recipientCurve25519Key: string;
|
||||||
|
/** the recipient's ed25519 key */
|
||||||
|
recipientEd25519Key: string;
|
||||||
|
/** the payload of the message */
|
||||||
|
plaincontent?: object;
|
||||||
|
/** the event type of the payload */
|
||||||
|
plaintype?: string;
|
||||||
|
}): ToDeviceEvent {
|
||||||
|
expect(opts.senderKey).toBeTruthy();
|
||||||
|
expect(opts.p2pSession).toBeTruthy();
|
||||||
|
expect(opts.recipient).toBeTruthy();
|
||||||
|
|
||||||
|
const plaintext = {
|
||||||
|
content: opts.plaincontent || {},
|
||||||
|
recipient: opts.recipient,
|
||||||
|
recipient_keys: {
|
||||||
|
ed25519: opts.recipientEd25519Key,
|
||||||
|
},
|
||||||
|
keys: {
|
||||||
|
ed25519: opts.senderSigningKey,
|
||||||
|
},
|
||||||
|
sender: opts.sender || "@bob:xyz",
|
||||||
|
type: opts.plaintype || "m.test",
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: {
|
||||||
|
algorithm: "m.olm.v1.curve25519-aes-sha2",
|
||||||
|
ciphertext: {
|
||||||
|
[opts.recipientCurve25519Key]: opts.p2pSession.encrypt(JSON.stringify(plaintext)),
|
||||||
|
},
|
||||||
|
sender_key: opts.senderKey,
|
||||||
|
},
|
||||||
|
sender: opts.sender || "@bob:xyz",
|
||||||
|
type: "m.room.encrypted",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// encrypt an event with megolm
|
||||||
|
export function encryptMegolmEvent(opts: {
|
||||||
|
senderKey: string;
|
||||||
|
groupSession: Olm.OutboundGroupSession;
|
||||||
|
plaintext?: Partial<IEvent>;
|
||||||
|
room_id?: string;
|
||||||
|
}): IEvent {
|
||||||
|
expect(opts.senderKey).toBeTruthy();
|
||||||
|
expect(opts.groupSession).toBeTruthy();
|
||||||
|
|
||||||
|
const plaintext = opts.plaintext || {};
|
||||||
|
if (!plaintext.content) {
|
||||||
|
plaintext.content = {
|
||||||
|
body: "42",
|
||||||
|
msgtype: "m.text",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!plaintext.type) {
|
||||||
|
plaintext.type = "m.room.message";
|
||||||
|
}
|
||||||
|
if (!plaintext.room_id) {
|
||||||
|
expect(opts.room_id).toBeTruthy();
|
||||||
|
plaintext.room_id = opts.room_id;
|
||||||
|
}
|
||||||
|
return encryptMegolmEventRawPlainText({
|
||||||
|
senderKey: opts.senderKey,
|
||||||
|
groupSession: opts.groupSession,
|
||||||
|
plaintext,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encryptMegolmEventRawPlainText(opts: {
|
||||||
|
senderKey: string;
|
||||||
|
groupSession: Olm.OutboundGroupSession;
|
||||||
|
plaintext: Partial<IEvent>;
|
||||||
|
origin_server_ts?: number;
|
||||||
|
}): IEvent {
|
||||||
|
return {
|
||||||
|
event_id: "$test_megolm_event_" + Math.random(),
|
||||||
|
sender: opts.plaintext.sender ?? "@not_the_real_sender:example.com",
|
||||||
|
origin_server_ts: opts.plaintext.origin_server_ts ?? 1672944778000,
|
||||||
|
content: {
|
||||||
|
algorithm: "m.megolm.v1.aes-sha2",
|
||||||
|
ciphertext: opts.groupSession.encrypt(JSON.stringify(opts.plaintext)),
|
||||||
|
device_id: "testDevice",
|
||||||
|
sender_key: opts.senderKey,
|
||||||
|
session_id: opts.groupSession.session_id(),
|
||||||
|
},
|
||||||
|
type: "m.room.encrypted",
|
||||||
|
unsigned: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** build an encrypted room_key event to share a group session, using an existing olm session */
|
||||||
|
export function encryptGroupSessionKey(opts: {
|
||||||
|
/** recipient's user id */
|
||||||
|
recipient: string;
|
||||||
|
/** the recipient's curve25519 key */
|
||||||
|
recipientCurve25519Key: string;
|
||||||
|
/** the recipient's ed25519 key */
|
||||||
|
recipientEd25519Key: string;
|
||||||
|
/** sender's olm account */
|
||||||
|
olmAccount: Olm.Account;
|
||||||
|
/** sender's olm session with the recipient */
|
||||||
|
p2pSession: Olm.Session;
|
||||||
|
groupSession: Olm.OutboundGroupSession;
|
||||||
|
room_id?: string;
|
||||||
|
}): ToDeviceEvent {
|
||||||
|
const senderKeys = JSON.parse(opts.olmAccount.identity_keys());
|
||||||
|
return encryptOlmEvent({
|
||||||
|
senderKey: senderKeys.curve25519,
|
||||||
|
senderSigningKey: senderKeys.ed25519,
|
||||||
|
recipient: opts.recipient,
|
||||||
|
recipientCurve25519Key: opts.recipientCurve25519Key,
|
||||||
|
recipientEd25519Key: opts.recipientEd25519Key,
|
||||||
|
p2pSession: opts.p2pSession,
|
||||||
|
plaincontent: {
|
||||||
|
algorithm: "m.megolm.v1.aes-sha2",
|
||||||
|
room_id: opts.room_id,
|
||||||
|
session_id: opts.groupSession.session_id(),
|
||||||
|
session_key: opts.groupSession.session_key(),
|
||||||
|
},
|
||||||
|
plaintype: "m.room_key",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Establish an Olm Session with the test user
|
||||||
|
*
|
||||||
|
* Waits for the test user to upload their keys, then sends a /sync response with a to-device message which will
|
||||||
|
* establish an Olm session.
|
||||||
|
*
|
||||||
|
* @param testClient - the MatrixClient under test, which we expect to upload account keys, and to make a
|
||||||
|
* /sync request which we will respond to.
|
||||||
|
* @param keyReceiver - an IE2EKeyReceiver which will intercept the /keys/upload request from the client under test
|
||||||
|
* @param syncResponder - an ISyncResponder which will intercept /sync requests from the client under test
|
||||||
|
* @param peerOlmAccount: an OlmAccount which will be used to initiate the Olm session.
|
||||||
|
*/
|
||||||
|
export async function establishOlmSession(
|
||||||
|
testClient: MatrixClient,
|
||||||
|
keyReceiver: IE2EKeyReceiver,
|
||||||
|
syncResponder: ISyncResponder,
|
||||||
|
peerOlmAccount: Olm.Account,
|
||||||
|
): Promise<Olm.Session> {
|
||||||
|
const peerE2EKeys = JSON.parse(peerOlmAccount.identity_keys());
|
||||||
|
const p2pSession = await createOlmSession(peerOlmAccount, keyReceiver);
|
||||||
|
const olmEvent = encryptOlmEvent({
|
||||||
|
senderKey: peerE2EKeys.curve25519,
|
||||||
|
senderSigningKey: peerE2EKeys.ed25519,
|
||||||
|
recipient: testClient.getUserId()!,
|
||||||
|
recipientCurve25519Key: keyReceiver.getDeviceKey(),
|
||||||
|
recipientEd25519Key: keyReceiver.getSigningKey(),
|
||||||
|
p2pSession: p2pSession,
|
||||||
|
});
|
||||||
|
syncResponder.sendOrQueueSyncResponse({
|
||||||
|
next_batch: 1,
|
||||||
|
to_device: { events: [olmEvent] },
|
||||||
|
});
|
||||||
|
await syncPromise(testClient);
|
||||||
|
return p2pSession;
|
||||||
|
}
|
@@ -21,12 +21,14 @@ 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 { createHash } from "crypto";
|
import { createHash } from "crypto";
|
||||||
|
import Olm from "@matrix-org/olm";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createClient,
|
createClient,
|
||||||
CryptoEvent,
|
CryptoEvent,
|
||||||
IContent,
|
IContent,
|
||||||
ICreateClientOpts,
|
ICreateClientOpts,
|
||||||
|
IEvent,
|
||||||
MatrixClient,
|
MatrixClient,
|
||||||
MatrixEvent,
|
MatrixEvent,
|
||||||
MatrixEventEvent,
|
MatrixEventEvent,
|
||||||
@@ -42,9 +44,17 @@ import {
|
|||||||
VerifierEvent,
|
VerifierEvent,
|
||||||
} from "../../../src/crypto-api/verification";
|
} from "../../../src/crypto-api/verification";
|
||||||
import { escapeRegExp } from "../../../src/utils";
|
import { escapeRegExp } from "../../../src/utils";
|
||||||
import { CRYPTO_BACKENDS, emitPromise, getSyncResponse, InitCrypto, syncPromise } from "../../test-utils/test-utils";
|
import {
|
||||||
|
awaitDecryption,
|
||||||
|
CRYPTO_BACKENDS,
|
||||||
|
emitPromise,
|
||||||
|
getSyncResponse,
|
||||||
|
InitCrypto,
|
||||||
|
syncPromise,
|
||||||
|
} from "../../test-utils/test-utils";
|
||||||
import { SyncResponder } from "../../test-utils/SyncResponder";
|
import { SyncResponder } from "../../test-utils/SyncResponder";
|
||||||
import {
|
import {
|
||||||
|
BOB_ONE_TIME_KEYS,
|
||||||
BOB_SIGNED_CROSS_SIGNING_KEYS_DATA,
|
BOB_SIGNED_CROSS_SIGNING_KEYS_DATA,
|
||||||
BOB_SIGNED_TEST_DEVICE_DATA,
|
BOB_SIGNED_TEST_DEVICE_DATA,
|
||||||
BOB_TEST_USER_ID,
|
BOB_TEST_USER_ID,
|
||||||
@@ -55,11 +65,11 @@ import {
|
|||||||
TEST_DEVICE_PUBLIC_ED25519_KEY_BASE64,
|
TEST_DEVICE_PUBLIC_ED25519_KEY_BASE64,
|
||||||
TEST_ROOM_ID,
|
TEST_ROOM_ID,
|
||||||
TEST_USER_ID,
|
TEST_USER_ID,
|
||||||
BOB_ONE_TIME_KEYS,
|
|
||||||
} from "../../test-utils/test-data";
|
} from "../../test-utils/test-data";
|
||||||
import { mockInitialApiRequests } from "../../test-utils/mockEndpoints";
|
import { mockInitialApiRequests } from "../../test-utils/mockEndpoints";
|
||||||
import { E2EKeyResponder } from "../../test-utils/E2EKeyResponder";
|
import { E2EKeyResponder } from "../../test-utils/E2EKeyResponder";
|
||||||
import { E2EKeyReceiver } from "../../test-utils/E2EKeyReceiver";
|
import { E2EKeyReceiver } from "../../test-utils/E2EKeyReceiver";
|
||||||
|
import { createOlmSession, encryptGroupSessionKey, encryptMegolmEvent, ToDeviceEvent } from "./olm-utils";
|
||||||
|
|
||||||
// The verification flows use javascript timers to set timeouts. We tell jest to use mock timer implementations
|
// The verification flows use javascript timers to set timeouts. We tell jest to use mock timer implementations
|
||||||
// to ensure that we don't end up with dangling timeouts.
|
// to ensure that we don't end up with dangling timeouts.
|
||||||
@@ -897,6 +907,175 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("verification (%s)", (backend: st
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Incoming verification in a DM", () => {
|
||||||
|
let testOlmAccount: Olm.Account;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
// create a test olm device which we will use to communicate with alice. We use libolm to implement this.
|
||||||
|
await Olm.init();
|
||||||
|
testOlmAccount = new Olm.Account();
|
||||||
|
testOlmAccount.create();
|
||||||
|
|
||||||
|
aliceClient = await startTestClient();
|
||||||
|
aliceClient.setGlobalErrorOnUnknownDevices(false);
|
||||||
|
syncResponder.sendOrQueueSyncResponse(getSyncResponse([BOB_TEST_USER_ID]));
|
||||||
|
await syncPromise(aliceClient);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a plaintext verification request event from Bob to Alice
|
||||||
|
* @see https://spec.matrix.org/v1.7/client-server-api/#mkeyverificationrequest
|
||||||
|
*/
|
||||||
|
function createVerificationRequestEvent(): IEvent {
|
||||||
|
return {
|
||||||
|
content: {
|
||||||
|
body: "Verification request from Bob to Alice",
|
||||||
|
from_device: "BobDevice",
|
||||||
|
methods: ["m.sas.v1"],
|
||||||
|
msgtype: "m.key.verification.request",
|
||||||
|
to: aliceClient.getUserId()!,
|
||||||
|
},
|
||||||
|
event_id: "$143273582443PhrSn:example.org",
|
||||||
|
origin_server_ts: Date.now(),
|
||||||
|
room_id: TEST_ROOM_ID,
|
||||||
|
sender: "@bob:xyz",
|
||||||
|
type: "m.room.message",
|
||||||
|
unsigned: {
|
||||||
|
age: 1234,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a to-device event from Bob to Alice, sharing the group session key
|
||||||
|
* @param groupSession - group session key to share
|
||||||
|
* @param p2pSession - test Olm session to encrypt the key with
|
||||||
|
*/
|
||||||
|
function encryptGroupSessionKeyForAlice(
|
||||||
|
groupSession: Olm.OutboundGroupSession,
|
||||||
|
p2pSession: Olm.Session,
|
||||||
|
): ToDeviceEvent {
|
||||||
|
return encryptGroupSessionKey({
|
||||||
|
recipient: aliceClient.getUserId()!,
|
||||||
|
recipientCurve25519Key: e2eKeyReceiver.getDeviceKey(),
|
||||||
|
recipientEd25519Key: e2eKeyReceiver.getSigningKey(),
|
||||||
|
olmAccount: testOlmAccount,
|
||||||
|
p2pSession: p2pSession,
|
||||||
|
groupSession: groupSession,
|
||||||
|
room_id: TEST_ROOM_ID,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and encrypt a verification request event
|
||||||
|
* @param groupSession
|
||||||
|
*/
|
||||||
|
function createEncryptedVerificationRequest(groupSession: Olm.OutboundGroupSession): IEvent {
|
||||||
|
const testOlmAccountKeys = JSON.parse(testOlmAccount.identity_keys());
|
||||||
|
return encryptMegolmEvent({
|
||||||
|
senderKey: testOlmAccountKeys.curve25519,
|
||||||
|
groupSession: groupSession,
|
||||||
|
room_id: TEST_ROOM_ID,
|
||||||
|
plaintext: createVerificationRequestEvent(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it("Plaintext verification request from Bob to Alice", async () => {
|
||||||
|
// Add verification request from Bob to Alice in the DM between them
|
||||||
|
returnRoomMessageFromSync(TEST_ROOM_ID, createVerificationRequestEvent());
|
||||||
|
|
||||||
|
// Wait for the sync response to be processed
|
||||||
|
await syncPromise(aliceClient);
|
||||||
|
|
||||||
|
const request = aliceClient.getCrypto()!.findVerificationRequestDMInProgress(TEST_ROOM_ID, "@bob:xyz");
|
||||||
|
// Expect to find the verification request received during the sync
|
||||||
|
expect(request?.roomId).toBe(TEST_ROOM_ID);
|
||||||
|
expect(request?.isSelfVerification).toBe(false);
|
||||||
|
expect(request?.otherUserId).toBe("@bob:xyz");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Verification request not found", async () => {
|
||||||
|
// Expect to not find any verification request
|
||||||
|
const request = aliceClient.getCrypto()!.findVerificationRequestDMInProgress(TEST_ROOM_ID, "@bob:xyz");
|
||||||
|
expect(request).not.toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Encrypted verification request from Bob to Alice", async () => {
|
||||||
|
const p2pSession = await createOlmSession(testOlmAccount, e2eKeyReceiver);
|
||||||
|
const groupSession = new Olm.OutboundGroupSession();
|
||||||
|
groupSession.create();
|
||||||
|
|
||||||
|
// make the room_key event, but don't send it yet
|
||||||
|
const toDeviceEvent = encryptGroupSessionKeyForAlice(groupSession, p2pSession);
|
||||||
|
|
||||||
|
// Add verification request from Bob to Alice in the DM between them
|
||||||
|
returnRoomMessageFromSync(TEST_ROOM_ID, createEncryptedVerificationRequest(groupSession));
|
||||||
|
|
||||||
|
// Wait for the sync response to be processed
|
||||||
|
await syncPromise(aliceClient);
|
||||||
|
|
||||||
|
const room = aliceClient.getRoom(TEST_ROOM_ID)!;
|
||||||
|
const matrixEvent = room.getLiveTimeline().getEvents()[0];
|
||||||
|
|
||||||
|
// wait for a first attempt at decryption: should fail
|
||||||
|
await awaitDecryption(matrixEvent);
|
||||||
|
expect(matrixEvent.getContent().msgtype).toEqual("m.bad.encrypted");
|
||||||
|
|
||||||
|
// Send Bob the room keys
|
||||||
|
returnToDeviceMessageFromSync(toDeviceEvent);
|
||||||
|
|
||||||
|
// advance the clock, because the devicelist likes to sleep for 5ms during key downloads
|
||||||
|
await jest.advanceTimersByTimeAsync(10);
|
||||||
|
|
||||||
|
// Wait for the message to be decrypted
|
||||||
|
await awaitDecryption(matrixEvent, { waitOnDecryptionFailure: true });
|
||||||
|
|
||||||
|
const request = aliceClient.getCrypto()!.findVerificationRequestDMInProgress(TEST_ROOM_ID, "@bob:xyz");
|
||||||
|
// Expect to find the verification request received during the sync
|
||||||
|
expect(request?.roomId).toBe(TEST_ROOM_ID);
|
||||||
|
expect(request?.isSelfVerification).toBe(false);
|
||||||
|
expect(request?.otherUserId).toBe("@bob:xyz");
|
||||||
|
});
|
||||||
|
|
||||||
|
newBackendOnly(
|
||||||
|
"If the verification request is not decrypted within 5 minutes, the request is ignored",
|
||||||
|
async () => {
|
||||||
|
const p2pSession = await createOlmSession(testOlmAccount, e2eKeyReceiver);
|
||||||
|
const groupSession = new Olm.OutboundGroupSession();
|
||||||
|
groupSession.create();
|
||||||
|
|
||||||
|
// make the room_key event, but don't send it yet
|
||||||
|
const toDeviceEvent = encryptGroupSessionKeyForAlice(groupSession, p2pSession);
|
||||||
|
|
||||||
|
// Add verification request from Bob to Alice in the DM between them
|
||||||
|
returnRoomMessageFromSync(TEST_ROOM_ID, createEncryptedVerificationRequest(groupSession));
|
||||||
|
|
||||||
|
// Wait for the sync response to be processed
|
||||||
|
await syncPromise(aliceClient);
|
||||||
|
|
||||||
|
const room = aliceClient.getRoom(TEST_ROOM_ID)!;
|
||||||
|
const matrixEvent = room.getLiveTimeline().getEvents()[0];
|
||||||
|
|
||||||
|
// wait for a first attempt at decryption: should fail
|
||||||
|
await awaitDecryption(matrixEvent);
|
||||||
|
expect(matrixEvent.getContent().msgtype).toEqual("m.bad.encrypted");
|
||||||
|
|
||||||
|
// Advance time by 5mins, the verification request should be ignored after that
|
||||||
|
jest.advanceTimersByTime(5 * 60 * 1000);
|
||||||
|
|
||||||
|
// Send Bob the room keys
|
||||||
|
returnToDeviceMessageFromSync(toDeviceEvent);
|
||||||
|
|
||||||
|
// Wait for the message to be decrypted
|
||||||
|
await awaitDecryption(matrixEvent, { waitOnDecryptionFailure: true });
|
||||||
|
|
||||||
|
const request = aliceClient.getCrypto()!.findVerificationRequestDMInProgress(TEST_ROOM_ID, "@bob:xyz");
|
||||||
|
// the request should not be present
|
||||||
|
expect(request).not.toBeDefined();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
async function startTestClient(opts: Partial<ICreateClientOpts> = {}): Promise<MatrixClient> {
|
async function startTestClient(opts: Partial<ICreateClientOpts> = {}): Promise<MatrixClient> {
|
||||||
const client = createClient({
|
const client = createClient({
|
||||||
baseUrl: TEST_HOMESERVER_URL,
|
baseUrl: TEST_HOMESERVER_URL,
|
||||||
@@ -927,6 +1106,17 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("verification (%s)", (backend: st
|
|||||||
ev.sender ??= TEST_USER_ID;
|
ev.sender ??= TEST_USER_ID;
|
||||||
syncResponder.sendOrQueueSyncResponse({ to_device: { events: [ev] } });
|
syncResponder.sendOrQueueSyncResponse({ to_device: { events: [ev] } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function returnRoomMessageFromSync(roomId: string, ev: IEvent): void {
|
||||||
|
syncResponder.sendOrQueueSyncResponse({
|
||||||
|
next_batch: 1,
|
||||||
|
rooms: {
|
||||||
|
join: {
|
||||||
|
[roomId]: { timeline: { events: [ev] } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user