You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-07-30 04:23:07 +03:00
ElementR: Add CryptoApi.findVerificationRequestDMInProgress
(#3601)
* Add `CryptoApi.findVerificationRequestDMInProgress` * Fix linting and missing parameters * Move `ROOM_ID` into `test-data` * Remove verification request from `EventDecryptor` pending list * Fix duplicate timeline event processing * Add extra documentation * Try to fix sonar error * Use `roomId` * Fix typo * Review changes * Review changes * Fix `initRustCrypto` jsdoc * Listen to `ClientEvent.Event` instead of `RoomEvent.Timeline` * Fix missing room id in `generate-test-data.py` * Review changes * Review changes * Handle encrypted event * Fix linting * Comments and run timers * Ignore 404 * Fix test
This commit is contained in:
@ -20,22 +20,23 @@ import fetchMock from "fetch-mock-jest";
|
|||||||
import "fake-indexeddb/auto";
|
import "fake-indexeddb/auto";
|
||||||
import { IDBFactory } from "fake-indexeddb";
|
import { IDBFactory } from "fake-indexeddb";
|
||||||
import { MockResponse, MockResponseFunction } from "fetch-mock";
|
import { MockResponse, MockResponseFunction } from "fetch-mock";
|
||||||
|
import Olm from "@matrix-org/olm";
|
||||||
|
|
||||||
import type { IDeviceKeys } from "../../../src/@types/crypto";
|
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, InitCrypto, syncPromise } from "../../test-utils/test-utils";
|
import { CRYPTO_BACKENDS, getSyncResponse, InitCrypto, syncPromise } from "../../test-utils/test-utils";
|
||||||
|
import { TEST_ROOM_ID, TEST_ROOM_ID as ROOM_ID, TEST_USER_ID } 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,
|
||||||
createClient,
|
createClient,
|
||||||
IClaimOTKsResult,
|
IClaimOTKsResult,
|
||||||
IContent,
|
IContent,
|
||||||
IDownloadKeyResult,
|
IDownloadKeyResult,
|
||||||
IEvent,
|
IEvent,
|
||||||
IJoinedRoom,
|
|
||||||
IndexedDBCryptoStore,
|
IndexedDBCryptoStore,
|
||||||
IStartClientOpts,
|
IStartClientOpts,
|
||||||
ISyncResponse,
|
|
||||||
MatrixClient,
|
MatrixClient,
|
||||||
MatrixEvent,
|
MatrixEvent,
|
||||||
MatrixEventEvent,
|
MatrixEventEvent,
|
||||||
@ -43,6 +44,7 @@ import {
|
|||||||
Room,
|
Room,
|
||||||
RoomMember,
|
RoomMember,
|
||||||
RoomStateEvent,
|
RoomStateEvent,
|
||||||
|
IRoomEvent,
|
||||||
} from "../../../src/matrix";
|
} from "../../../src/matrix";
|
||||||
import { DeviceInfo } from "../../../src/crypto/deviceinfo";
|
import { DeviceInfo } from "../../../src/crypto/deviceinfo";
|
||||||
import { E2EKeyReceiver, IE2EKeyReceiver } from "../../test-utils/E2EKeyReceiver";
|
import { E2EKeyReceiver, IE2EKeyReceiver } from "../../test-utils/E2EKeyReceiver";
|
||||||
@ -53,8 +55,7 @@ import { flushPromises } from "../../test-utils/flushPromises";
|
|||||||
import { mockInitialApiRequests, mockSetupCrossSigningRequests } from "../../test-utils/mockEndpoints";
|
import { mockInitialApiRequests, mockSetupCrossSigningRequests } from "../../test-utils/mockEndpoints";
|
||||||
import { AddSecretStorageKeyOpts, SECRET_STORAGE_ALGORITHM_V1_AES } from "../../../src/secret-storage";
|
import { AddSecretStorageKeyOpts, SECRET_STORAGE_ALGORITHM_V1_AES } from "../../../src/secret-storage";
|
||||||
import { CryptoCallbacks } from "../../../src/crypto-api";
|
import { CryptoCallbacks } from "../../../src/crypto-api";
|
||||||
|
import { E2EKeyResponder } from "../../test-utils/E2EKeyResponder";
|
||||||
const ROOM_ID = "!room:id";
|
|
||||||
|
|
||||||
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
|
||||||
@ -156,18 +157,23 @@ function encryptMegolmEvent(opts: {
|
|||||||
expect(opts.room_id).toBeTruthy();
|
expect(opts.room_id).toBeTruthy();
|
||||||
plaintext.room_id = opts.room_id;
|
plaintext.room_id = opts.room_id;
|
||||||
}
|
}
|
||||||
return encryptMegolmEventRawPlainText({ senderKey: opts.senderKey, groupSession: opts.groupSession, plaintext });
|
return encryptMegolmEventRawPlainText({
|
||||||
|
senderKey: opts.senderKey,
|
||||||
|
groupSession: opts.groupSession,
|
||||||
|
plaintext,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function encryptMegolmEventRawPlainText(opts: {
|
function encryptMegolmEventRawPlainText(opts: {
|
||||||
senderKey: string;
|
senderKey: string;
|
||||||
groupSession: Olm.OutboundGroupSession;
|
groupSession: Olm.OutboundGroupSession;
|
||||||
plaintext: Partial<IEvent>;
|
plaintext: Partial<IEvent>;
|
||||||
|
origin_server_ts?: number;
|
||||||
}): IEvent {
|
}): IEvent {
|
||||||
return {
|
return {
|
||||||
event_id: "$test_megolm_event_" + Math.random(),
|
event_id: "$test_megolm_event_" + Math.random(),
|
||||||
sender: "@not_the_real_sender:example.com",
|
sender: opts.plaintext.sender ?? "@not_the_real_sender:example.com",
|
||||||
origin_server_ts: 1672944778000,
|
origin_server_ts: opts.plaintext.origin_server_ts ?? 1672944778000,
|
||||||
content: {
|
content: {
|
||||||
algorithm: "m.megolm.v1.aes-sha2",
|
algorithm: "m.megolm.v1.aes-sha2",
|
||||||
ciphertext: opts.groupSession.encrypt(JSON.stringify(opts.plaintext)),
|
ciphertext: opts.groupSession.encrypt(JSON.stringify(opts.plaintext)),
|
||||||
@ -213,55 +219,6 @@ function encryptGroupSessionKey(opts: {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// get a /sync response which contains a single room (ROOM_ID), with the members given
|
|
||||||
function getSyncResponse(roomMembers: string[]): ISyncResponse {
|
|
||||||
const roomResponse: IJoinedRoom = {
|
|
||||||
summary: {
|
|
||||||
"m.heroes": [],
|
|
||||||
"m.joined_member_count": roomMembers.length,
|
|
||||||
"m.invited_member_count": roomMembers.length,
|
|
||||||
},
|
|
||||||
state: {
|
|
||||||
events: [
|
|
||||||
testUtils.mkEventCustom({
|
|
||||||
sender: roomMembers[0],
|
|
||||||
type: "m.room.encryption",
|
|
||||||
state_key: "",
|
|
||||||
content: {
|
|
||||||
algorithm: "m.megolm.v1.aes-sha2",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
timeline: {
|
|
||||||
events: [],
|
|
||||||
prev_batch: "",
|
|
||||||
},
|
|
||||||
ephemeral: { events: [] },
|
|
||||||
account_data: { events: [] },
|
|
||||||
unread_notifications: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let i = 0; i < roomMembers.length; i++) {
|
|
||||||
roomResponse.state.events.push(
|
|
||||||
testUtils.mkMembershipCustom({
|
|
||||||
membership: "join",
|
|
||||||
sender: roomMembers[i],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
next_batch: "1",
|
|
||||||
rooms: {
|
|
||||||
join: { [ROOM_ID]: roomResponse },
|
|
||||||
invite: {},
|
|
||||||
leave: {},
|
|
||||||
},
|
|
||||||
account_data: { events: [] },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Establish an Olm Session with the test user
|
* Establish an Olm Session with the test user
|
||||||
*
|
*
|
||||||
@ -415,7 +372,10 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
|||||||
let aliceClient: MatrixClient;
|
let aliceClient: MatrixClient;
|
||||||
|
|
||||||
/** an object which intercepts `/keys/upload` requests from {@link #aliceClient} to catch the uploaded keys */
|
/** an object which intercepts `/keys/upload` requests from {@link #aliceClient} to catch the uploaded keys */
|
||||||
let keyReceiver: IE2EKeyReceiver;
|
let keyReceiver: E2EKeyReceiver;
|
||||||
|
|
||||||
|
/** an object which intercepts `/keys/query` requests on the test homeserver */
|
||||||
|
let keyResponder: E2EKeyResponder;
|
||||||
|
|
||||||
/** an object which intercepts `/sync` requests from {@link #aliceClient} */
|
/** an object which intercepts `/sync` requests from {@link #aliceClient} */
|
||||||
let syncResponder: ISyncResponder;
|
let syncResponder: ISyncResponder;
|
||||||
@ -586,6 +546,10 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
|||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await aliceClient.stopClient();
|
await aliceClient.stopClient();
|
||||||
|
|
||||||
|
// Allow in-flight things to complete before we tear down the test
|
||||||
|
await jest.runAllTimersAsync();
|
||||||
|
|
||||||
fetchMock.mockReset();
|
fetchMock.mockReset();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -708,6 +672,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
|||||||
await syncPromise(aliceClient);
|
await syncPromise(aliceClient);
|
||||||
|
|
||||||
await testUtils.awaitDecryption(event, { waitOnDecryptionFailure: true });
|
await testUtils.awaitDecryption(event, { waitOnDecryptionFailure: true });
|
||||||
|
expect(event.isDecryptionFailure()).toBeFalsy();
|
||||||
expect(event.getContent().body).toEqual("42");
|
expect(event.getContent().body).toEqual("42");
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -2411,4 +2376,193 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
|||||||
expect(selfSigningKey[secretStorageKey]).toBeDefined();
|
expect(selfSigningKey[secretStorageKey]).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -34,6 +34,7 @@ from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
|
|||||||
# input data
|
# input data
|
||||||
TEST_USER_ID = "@alice:localhost"
|
TEST_USER_ID = "@alice:localhost"
|
||||||
TEST_DEVICE_ID = "test_device"
|
TEST_DEVICE_ID = "test_device"
|
||||||
|
TEST_ROOM_ID = "!room:id"
|
||||||
# any 32-byte string can be an ed25519 private key.
|
# any 32-byte string can be an ed25519 private key.
|
||||||
TEST_DEVICE_PRIVATE_KEY_BYTES = b"deadbeefdeadbeefdeadbeefdeadbeef"
|
TEST_DEVICE_PRIVATE_KEY_BYTES = b"deadbeefdeadbeefdeadbeefdeadbeef"
|
||||||
|
|
||||||
@ -130,6 +131,7 @@ import {{ KeyBackupInfo }} from "../../../src/crypto-api";
|
|||||||
|
|
||||||
export const TEST_USER_ID = "{TEST_USER_ID}";
|
export const TEST_USER_ID = "{TEST_USER_ID}";
|
||||||
export const TEST_DEVICE_ID = "{TEST_DEVICE_ID}";
|
export const TEST_DEVICE_ID = "{TEST_DEVICE_ID}";
|
||||||
|
export const TEST_ROOM_ID = "{TEST_ROOM_ID}";
|
||||||
|
|
||||||
/** The base64-encoded public ed25519 key for this device */
|
/** The base64-encoded public ed25519 key for this device */
|
||||||
export const TEST_DEVICE_PUBLIC_ED25519_KEY_BASE64 = "{b64_public_key}";
|
export const TEST_DEVICE_PUBLIC_ED25519_KEY_BASE64 = "{b64_public_key}";
|
||||||
|
@ -11,6 +11,7 @@ import { KeyBackupInfo } from "../../../src/crypto-api";
|
|||||||
|
|
||||||
export const TEST_USER_ID = "@alice:localhost";
|
export const TEST_USER_ID = "@alice:localhost";
|
||||||
export const TEST_DEVICE_ID = "test_device";
|
export const TEST_DEVICE_ID = "test_device";
|
||||||
|
export const TEST_ROOM_ID = "!room:id";
|
||||||
|
|
||||||
/** The base64-encoded public ed25519 key for this device */
|
/** The base64-encoded public ed25519 key for this device */
|
||||||
export const TEST_DEVICE_PUBLIC_ED25519_KEY_BASE64 = "YI/7vbGVLpGdYtuceQR8MSsKB/QjgfMXM1xqnn+0NWU";
|
export const TEST_DEVICE_PUBLIC_ED25519_KEY_BASE64 = "YI/7vbGVLpGdYtuceQR8MSsKB/QjgfMXM1xqnn+0NWU";
|
||||||
|
@ -6,9 +6,19 @@ import "../olm-loader";
|
|||||||
|
|
||||||
import { logger } from "../../src/logger";
|
import { logger } from "../../src/logger";
|
||||||
import { IContent, IEvent, IEventRelation, IUnsigned, MatrixEvent, MatrixEventEvent } from "../../src/models/event";
|
import { IContent, IEvent, IEventRelation, IUnsigned, MatrixEvent, MatrixEventEvent } from "../../src/models/event";
|
||||||
import { ClientEvent, EventType, IPusher, MatrixClient, MsgType, RelationType } from "../../src";
|
import {
|
||||||
|
ClientEvent,
|
||||||
|
EventType,
|
||||||
|
IJoinedRoom,
|
||||||
|
IPusher,
|
||||||
|
ISyncResponse,
|
||||||
|
MatrixClient,
|
||||||
|
MsgType,
|
||||||
|
RelationType,
|
||||||
|
} from "../../src";
|
||||||
import { SyncState } from "../../src/sync";
|
import { SyncState } from "../../src/sync";
|
||||||
import { eventMapperFor } from "../../src/event-mapper";
|
import { eventMapperFor } from "../../src/event-mapper";
|
||||||
|
import { TEST_ROOM_ID } from "./test-data";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a promise that is resolved when the client next emits a
|
* Return a promise that is resolved when the client next emits a
|
||||||
@ -39,6 +49,61 @@ export function syncPromise(client: MatrixClient, count = 1): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a sync response which contains a single room (by default TEST_ROOM_ID), with the members given
|
||||||
|
* @param roomMembers
|
||||||
|
* @param roomId
|
||||||
|
*
|
||||||
|
* @returns the sync response
|
||||||
|
*/
|
||||||
|
export function getSyncResponse(roomMembers: string[], roomId = TEST_ROOM_ID): ISyncResponse {
|
||||||
|
const roomResponse: IJoinedRoom = {
|
||||||
|
summary: {
|
||||||
|
"m.heroes": [],
|
||||||
|
"m.joined_member_count": roomMembers.length,
|
||||||
|
"m.invited_member_count": roomMembers.length,
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
events: [
|
||||||
|
mkEventCustom({
|
||||||
|
sender: roomMembers[0],
|
||||||
|
type: "m.room.encryption",
|
||||||
|
state_key: "",
|
||||||
|
content: {
|
||||||
|
algorithm: "m.megolm.v1.aes-sha2",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
timeline: {
|
||||||
|
events: [],
|
||||||
|
prev_batch: "",
|
||||||
|
},
|
||||||
|
ephemeral: { events: [] },
|
||||||
|
account_data: { events: [] },
|
||||||
|
unread_notifications: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = 0; i < roomMembers.length; i++) {
|
||||||
|
roomResponse.state.events.push(
|
||||||
|
mkMembershipCustom({
|
||||||
|
membership: "join",
|
||||||
|
sender: roomMembers[i],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
next_batch: "1",
|
||||||
|
rooms: {
|
||||||
|
join: { [roomId]: roomResponse },
|
||||||
|
invite: {},
|
||||||
|
leave: {},
|
||||||
|
},
|
||||||
|
account_data: { events: [] },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a spy for an object and automatically spy its methods.
|
* Create a spy for an object and automatically spy its methods.
|
||||||
* @param constr - The class constructor (used with 'new')
|
* @param constr - The class constructor (used with 'new')
|
||||||
|
@ -576,6 +576,13 @@ describe("RustCrypto", () => {
|
|||||||
expect(await rustCrypto.getActiveSessionBackupVersion()).toBeNull();
|
expect(await rustCrypto.getActiveSessionBackupVersion()).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("findVerificationRequestDMInProgress", () => {
|
||||||
|
it("returns undefined if the userId is not provided", async () => {
|
||||||
|
const rustCrypto = await makeTestRustCrypto();
|
||||||
|
expect(rustCrypto.findVerificationRequestDMInProgress(testData.TEST_ROOM_ID)).not.toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/** build a basic RustCrypto instance for testing
|
/** build a basic RustCrypto instance for testing
|
||||||
|
@ -2243,6 +2243,9 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
|
|
||||||
// attach the event listeners needed by RustCrypto
|
// attach the event listeners needed by RustCrypto
|
||||||
this.on(RoomMemberEvent.Membership, rustCrypto.onRoomMembership.bind(rustCrypto));
|
this.on(RoomMemberEvent.Membership, rustCrypto.onRoomMembership.bind(rustCrypto));
|
||||||
|
this.on(ClientEvent.Event, (event) => {
|
||||||
|
rustCrypto.onLiveEventFromSync(event);
|
||||||
|
});
|
||||||
|
|
||||||
// re-emit the events emitted by the crypto impl
|
// re-emit the events emitted by the crypto impl
|
||||||
this.reEmitter.reEmit(rustCrypto, [
|
this.reEmitter.reEmit(rustCrypto, [
|
||||||
|
@ -267,9 +267,20 @@ export interface CryptoApi {
|
|||||||
* @param roomId - the room to use for verification
|
* @param roomId - the room to use for verification
|
||||||
*
|
*
|
||||||
* @returns the VerificationRequest that is in progress, if any
|
* @returns the VerificationRequest that is in progress, if any
|
||||||
|
* @deprecated prefer `userId` parameter variant.
|
||||||
*/
|
*/
|
||||||
findVerificationRequestDMInProgress(roomId: string): VerificationRequest | undefined;
|
findVerificationRequestDMInProgress(roomId: string): VerificationRequest | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a DM verification request that is already in progress for the given room and user.
|
||||||
|
*
|
||||||
|
* @param roomId - the room to use for verification.
|
||||||
|
* @param userId - search for a verification request for the given user.
|
||||||
|
*
|
||||||
|
* @returns the VerificationRequest that is in progress, if any.
|
||||||
|
*/
|
||||||
|
findVerificationRequestDMInProgress(roomId: string, userId?: string): VerificationRequest | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a verification request to our other devices.
|
* Send a verification request to our other devices.
|
||||||
*
|
*
|
||||||
|
@ -2380,8 +2380,8 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
|||||||
return deviceObj;
|
return deviceObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
public findVerificationRequestDMInProgress(roomId: string): VerificationRequest | undefined {
|
public findVerificationRequestDMInProgress(roomId: string, userId?: string): VerificationRequest | undefined {
|
||||||
return this.inRoomVerificationRequests.findRequestInProgress(roomId);
|
return this.inRoomVerificationRequests.findRequestInProgress(roomId, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getVerificationRequestsToDeviceInProgress(userId: string): VerificationRequest[] {
|
public getVerificationRequestsToDeviceInProgress(userId: string): VerificationRequest[] {
|
||||||
|
@ -350,11 +350,11 @@ export class InRoomRequests implements IRequestsMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public findRequestInProgress(roomId: string): VerificationRequest | undefined {
|
public findRequestInProgress(roomId: string, userId?: string): VerificationRequest | undefined {
|
||||||
const requestsByTxnId = this.requestsByRoomId.get(roomId);
|
const requestsByTxnId = this.requestsByRoomId.get(roomId);
|
||||||
if (requestsByTxnId) {
|
if (requestsByTxnId) {
|
||||||
for (const request of requestsByTxnId.values()) {
|
for (const request of requestsByTxnId.values()) {
|
||||||
if (request.pending) {
|
if (request.pending && (userId === undefined || request.requestingUserId === userId)) {
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-wasm";
|
|||||||
import type { IEventDecryptionResult, IMegolmSessionData } from "../@types/crypto";
|
import type { IEventDecryptionResult, IMegolmSessionData } from "../@types/crypto";
|
||||||
import type { IDeviceLists, IToDeviceEvent } from "../sync-accumulator";
|
import type { IDeviceLists, IToDeviceEvent } from "../sync-accumulator";
|
||||||
import type { IEncryptedEventInfo } from "../crypto/api";
|
import type { IEncryptedEventInfo } from "../crypto/api";
|
||||||
import { IContent, MatrixEvent } from "../models/event";
|
import { IContent, MatrixEvent, MatrixEventEvent } from "../models/event";
|
||||||
import { Room } from "../models/room";
|
import { Room } from "../models/room";
|
||||||
import { RoomMember } from "../models/room-member";
|
import { RoomMember } from "../models/room-member";
|
||||||
import { CryptoBackend, OnSyncCompletedData } from "../common-crypto/CryptoBackend";
|
import { CryptoBackend, OnSyncCompletedData } from "../common-crypto/CryptoBackend";
|
||||||
@ -35,6 +35,7 @@ import {
|
|||||||
BootstrapCrossSigningOpts,
|
BootstrapCrossSigningOpts,
|
||||||
CreateSecretStorageOpts,
|
CreateSecretStorageOpts,
|
||||||
CrossSigningKey,
|
CrossSigningKey,
|
||||||
|
CrossSigningKeyInfo,
|
||||||
CrossSigningStatus,
|
CrossSigningStatus,
|
||||||
CryptoCallbacks,
|
CryptoCallbacks,
|
||||||
DeviceVerificationStatus,
|
DeviceVerificationStatus,
|
||||||
@ -43,7 +44,6 @@ import {
|
|||||||
ImportRoomKeysOpts,
|
ImportRoomKeysOpts,
|
||||||
KeyBackupInfo,
|
KeyBackupInfo,
|
||||||
VerificationRequest,
|
VerificationRequest,
|
||||||
CrossSigningKeyInfo,
|
|
||||||
} from "../crypto-api";
|
} from "../crypto-api";
|
||||||
import { deviceKeysToDeviceMap, rustDeviceToJsDevice } from "./device-converter";
|
import { deviceKeysToDeviceMap, rustDeviceToJsDevice } from "./device-converter";
|
||||||
import { IDownloadKeyResult, IQueryKeysRequest } from "../client";
|
import { IDownloadKeyResult, IQueryKeysRequest } from "../client";
|
||||||
@ -55,7 +55,7 @@ import { keyFromPassphrase } from "../crypto/key_passphrase";
|
|||||||
import { encodeRecoveryKey } from "../crypto/recoverykey";
|
import { encodeRecoveryKey } from "../crypto/recoverykey";
|
||||||
import { crypto } from "../crypto/crypto";
|
import { crypto } from "../crypto/crypto";
|
||||||
import { RustVerificationRequest, verificationMethodIdentifierToMethod } from "./verification";
|
import { RustVerificationRequest, verificationMethodIdentifierToMethod } from "./verification";
|
||||||
import { EventType } from "../@types/event";
|
import { EventType, MsgType } from "../@types/event";
|
||||||
import { CryptoEvent } from "../crypto";
|
import { CryptoEvent } from "../crypto";
|
||||||
import { TypedEventEmitter } from "../models/typed-event-emitter";
|
import { TypedEventEmitter } from "../models/typed-event-emitter";
|
||||||
import { RustBackupManager } from "./backup";
|
import { RustBackupManager } from "./backup";
|
||||||
@ -680,13 +680,29 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
|
|||||||
* Implementation of {@link CryptoApi#findVerificationRequestDMInProgress}
|
* Implementation of {@link CryptoApi#findVerificationRequestDMInProgress}
|
||||||
*
|
*
|
||||||
* @param roomId - the room to use for verification
|
* @param roomId - the room to use for verification
|
||||||
|
* @param userId - search the verification request for the given user
|
||||||
*
|
*
|
||||||
* @returns the VerificationRequest that is in progress, if any
|
* @returns the VerificationRequest that is in progress, if any
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public findVerificationRequestDMInProgress(roomId: string): undefined {
|
public findVerificationRequestDMInProgress(roomId: string, userId?: string): VerificationRequest | undefined {
|
||||||
// TODO
|
// TODO raise an error
|
||||||
return;
|
if (!userId) return;
|
||||||
|
|
||||||
|
const requests: RustSdkCryptoJs.VerificationRequest[] = this.olmMachine.getVerificationRequests(
|
||||||
|
new RustSdkCryptoJs.UserId(userId),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Search for the verification request for the given room id
|
||||||
|
const request = requests.find((request) => request.roomId?.toString() === roomId);
|
||||||
|
|
||||||
|
if (request) {
|
||||||
|
return new RustVerificationRequest(
|
||||||
|
request,
|
||||||
|
this.outgoingRequestProcessor,
|
||||||
|
this._supportedVerificationMethods,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1018,6 +1034,73 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a live event received via /sync.
|
||||||
|
* See {@link ClientEventHandlerMap#event}
|
||||||
|
*
|
||||||
|
* @param event - live event
|
||||||
|
*/
|
||||||
|
public async onLiveEventFromSync(event: MatrixEvent): Promise<void> {
|
||||||
|
// Ignore state event
|
||||||
|
if (event.isState()) return;
|
||||||
|
|
||||||
|
const processEvent = async (evt: MatrixEvent): Promise<void> => {
|
||||||
|
// Process only key validation request
|
||||||
|
if (
|
||||||
|
evt.getType() === EventType.RoomMessage &&
|
||||||
|
evt.getContent().msgtype === MsgType.KeyVerificationRequest
|
||||||
|
) {
|
||||||
|
await this.onKeyVerificationRequest(evt);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the event is encrypted of in failure, we wait for decryption
|
||||||
|
if (event.isDecryptionFailure() || event.isEncrypted()) {
|
||||||
|
// 5 mins
|
||||||
|
const TIMEOUT_DELAY = 5 * 60 * 1000;
|
||||||
|
|
||||||
|
const timeoutId = setTimeout(() => event.off(MatrixEventEvent.Decrypted, onDecrypted), TIMEOUT_DELAY);
|
||||||
|
|
||||||
|
const onDecrypted = (decryptedEvent: MatrixEvent, error?: Error): void => {
|
||||||
|
if (error) return;
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
event.off(MatrixEventEvent.Decrypted, onDecrypted);
|
||||||
|
processEvent(decryptedEvent);
|
||||||
|
};
|
||||||
|
// After 5mins, we are not expecting the event to be decrypted
|
||||||
|
|
||||||
|
event.on(MatrixEventEvent.Decrypted, onDecrypted);
|
||||||
|
} else {
|
||||||
|
await processEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle key verification request.
|
||||||
|
*
|
||||||
|
* @param event - a key validation request event.
|
||||||
|
*/
|
||||||
|
private async onKeyVerificationRequest(event: MatrixEvent): Promise<void> {
|
||||||
|
const roomId = event.getRoomId();
|
||||||
|
|
||||||
|
if (!roomId) {
|
||||||
|
throw new Error("missing roomId in the event");
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.olmMachine.receiveVerificationEvent(
|
||||||
|
JSON.stringify({
|
||||||
|
event_id: event.getId(),
|
||||||
|
type: event.getType(),
|
||||||
|
sender: event.getSender(),
|
||||||
|
state_key: event.getStateKey(),
|
||||||
|
content: event.getContent(),
|
||||||
|
origin_server_ts: event.getTs(),
|
||||||
|
}),
|
||||||
|
new RustSdkCryptoJs.RoomId(roomId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Outgoing requests
|
// Outgoing requests
|
||||||
|
Reference in New Issue
Block a user