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
ElementR: fix emoji verification stalling when both ends hit start at the same time (#4004)
* Rust crypto: handle the SAS verifier being replaced * lint * make changes from review * apply changes from code review * remove useless assertions * wrap acceptance inside a try-catch, and factor out acceptance into a function * fix bugs * we don't actually need the .accept variable * move setInner to inside SAS class, and rename to replaceInner * use defer to avoid using a closure * lint * prettier * use the right name Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * combine onChangeCallback with onChange * apply changes from review * add test for QR code verification, and try changing order in onChange * lint --------- Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
This commit is contained in:
@@ -17,8 +17,19 @@ limitations under the License.
|
||||
import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-wasm";
|
||||
import { Mocked } from "jest-mock";
|
||||
|
||||
import { isVerificationEvent, RustVerificationRequest } from "../../../src/rust-crypto/verification";
|
||||
import { OutgoingRequestProcessor } from "../../../src/rust-crypto/OutgoingRequestProcessor";
|
||||
import {
|
||||
isVerificationEvent,
|
||||
RustVerificationRequest,
|
||||
verificationMethodIdentifierToMethod,
|
||||
} from "../../../src/rust-crypto/verification";
|
||||
import {
|
||||
ShowSasCallbacks,
|
||||
VerificationRequestEvent,
|
||||
Verifier,
|
||||
VerifierEvent,
|
||||
} from "../../../src/crypto-api/verification";
|
||||
import { OutgoingRequest, OutgoingRequestProcessor } from "../../../src/rust-crypto/OutgoingRequestProcessor";
|
||||
import { IDeviceKeys } from "../../../src/@types/crypto";
|
||||
import { EventType, MatrixEvent, MsgType } from "../../../src";
|
||||
|
||||
describe("VerificationRequest", () => {
|
||||
@@ -91,6 +102,354 @@ describe("VerificationRequest", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("can verify with SAS", async () => {
|
||||
const aliceUserId = "@alice:example.org";
|
||||
const aliceDeviceId = "ABCDEFG";
|
||||
const bobUserId = "@bob:example.org";
|
||||
const bobDeviceId = "HIJKLMN";
|
||||
const [aliceOlmMachine, aliceDeviceKeys, aliceCrossSigningKeys] = await initOlmMachineAndKeys(
|
||||
aliceUserId,
|
||||
aliceDeviceId,
|
||||
);
|
||||
const [bobOlmMachine, bobDeviceKeys, bobCrossSigningKeys] = await initOlmMachineAndKeys(bobUserId, bobDeviceId);
|
||||
|
||||
const aliceRequestLoop = makeRequestLoop(
|
||||
aliceOlmMachine,
|
||||
aliceDeviceKeys,
|
||||
aliceCrossSigningKeys,
|
||||
bobOlmMachine,
|
||||
bobDeviceKeys,
|
||||
bobCrossSigningKeys,
|
||||
);
|
||||
const bobRequestLoop = makeRequestLoop(
|
||||
bobOlmMachine,
|
||||
bobDeviceKeys,
|
||||
bobCrossSigningKeys,
|
||||
aliceOlmMachine,
|
||||
aliceDeviceKeys,
|
||||
aliceCrossSigningKeys,
|
||||
);
|
||||
|
||||
try {
|
||||
await aliceOlmMachine.updateTrackedUsers([new RustSdkCryptoJs.UserId(bobUserId)]);
|
||||
await bobOlmMachine.updateTrackedUsers([new RustSdkCryptoJs.UserId(aliceUserId)]);
|
||||
|
||||
// Alice requests verification
|
||||
const bobUserIdentity = await aliceOlmMachine.getIdentity(new RustSdkCryptoJs.UserId(bobUserId));
|
||||
|
||||
const roomId = new RustSdkCryptoJs.RoomId("!roomId:example.org");
|
||||
const methods = [verificationMethodIdentifierToMethod("m.sas.v1")];
|
||||
const innerVerificationRequest = await bobUserIdentity.requestVerification(
|
||||
roomId,
|
||||
new RustSdkCryptoJs.EventId("$m.key.verification.request"),
|
||||
methods,
|
||||
);
|
||||
const aliceVerificationRequest = new RustVerificationRequest(
|
||||
aliceOlmMachine,
|
||||
innerVerificationRequest,
|
||||
aliceRequestLoop as unknown as OutgoingRequestProcessor,
|
||||
["m.sas.v1"],
|
||||
);
|
||||
|
||||
const verificationRequestContent = JSON.parse(await bobUserIdentity.verificationRequestContent(methods));
|
||||
await bobOlmMachine.receiveVerificationEvent(
|
||||
JSON.stringify({
|
||||
type: "m.room.message",
|
||||
sender: aliceUserId,
|
||||
event_id: "$m.key.verification.request",
|
||||
content: verificationRequestContent,
|
||||
origin_server_ts: Date.now(),
|
||||
unsigned: {
|
||||
age: 0,
|
||||
},
|
||||
}),
|
||||
roomId,
|
||||
);
|
||||
|
||||
// Bob accepts
|
||||
const bobInnerVerificationRequest = bobOlmMachine.getVerificationRequest(
|
||||
new RustSdkCryptoJs.UserId(aliceUserId),
|
||||
"$m.key.verification.request",
|
||||
)!;
|
||||
const bobVerificationRequest = new RustVerificationRequest(
|
||||
bobOlmMachine,
|
||||
bobInnerVerificationRequest,
|
||||
bobRequestLoop as unknown as OutgoingRequestProcessor,
|
||||
["m.sas.v1"],
|
||||
);
|
||||
|
||||
await bobVerificationRequest.accept();
|
||||
|
||||
// Alice starts the verification
|
||||
const bobVerifierPromise: Promise<Verifier> = new Promise((resolve, reject) => {
|
||||
bobVerificationRequest.on(VerificationRequestEvent.Change, () => {
|
||||
const verifier = bobVerificationRequest.verifier;
|
||||
if (verifier) {
|
||||
resolve(verifier);
|
||||
}
|
||||
});
|
||||
});
|
||||
const aliceVerifier = await aliceVerificationRequest.startVerification("m.sas.v1");
|
||||
const bobVerifier = await bobVerifierPromise;
|
||||
|
||||
// create a function to compare the SAS, and then let the verification run
|
||||
let otherCallbacks: ShowSasCallbacks | undefined;
|
||||
const compareSas = (callbacks: ShowSasCallbacks): void => {
|
||||
if (otherCallbacks) {
|
||||
const ourDecimal = callbacks.sas.decimal!;
|
||||
const theirDecimal = otherCallbacks.sas.decimal!;
|
||||
if (ourDecimal.every((el, idx) => el == theirDecimal[idx])) {
|
||||
otherCallbacks.confirm();
|
||||
callbacks.confirm();
|
||||
} else {
|
||||
otherCallbacks.mismatch();
|
||||
callbacks.mismatch();
|
||||
}
|
||||
} else {
|
||||
otherCallbacks = callbacks;
|
||||
}
|
||||
};
|
||||
aliceVerifier.on(VerifierEvent.ShowSas, compareSas);
|
||||
bobVerifier.on(VerifierEvent.ShowSas, compareSas);
|
||||
|
||||
await Promise.all([aliceVerifier.verify(), await bobVerifier.verify()]);
|
||||
} finally {
|
||||
await aliceRequestLoop.stop();
|
||||
await bobRequestLoop.stop();
|
||||
}
|
||||
});
|
||||
|
||||
it("can handle simultaneous starts in SAS", async () => {
|
||||
const aliceUserId = "@alice:example.org";
|
||||
const aliceDeviceId = "ABCDEFG";
|
||||
const bobUserId = "@bob:example.org";
|
||||
const bobDeviceId = "HIJKLMN";
|
||||
const [aliceOlmMachine, aliceDeviceKeys, aliceCrossSigningKeys] = await initOlmMachineAndKeys(
|
||||
aliceUserId,
|
||||
aliceDeviceId,
|
||||
);
|
||||
const [bobOlmMachine, bobDeviceKeys, bobCrossSigningKeys] = await initOlmMachineAndKeys(bobUserId, bobDeviceId);
|
||||
|
||||
let aliceStartRequest: RustSdkCryptoJs.RoomMessageRequest | undefined;
|
||||
const aliceRequestLoop = makeRequestLoop(
|
||||
aliceOlmMachine,
|
||||
aliceDeviceKeys,
|
||||
aliceCrossSigningKeys,
|
||||
bobOlmMachine,
|
||||
bobDeviceKeys,
|
||||
bobCrossSigningKeys,
|
||||
async (request): Promise<any> => {
|
||||
// If the request is sending the m.key.verification.start
|
||||
// event, we delay sending it until after Bob has also started
|
||||
// a verification
|
||||
if (
|
||||
!aliceStartRequest &&
|
||||
request instanceof RustSdkCryptoJs.RoomMessageRequest &&
|
||||
request.event_type == "m.key.verification.start"
|
||||
) {
|
||||
aliceStartRequest = request;
|
||||
return { event_id: "$m.key.verification.start" };
|
||||
}
|
||||
},
|
||||
);
|
||||
const bobRequestLoop = makeRequestLoop(
|
||||
bobOlmMachine,
|
||||
bobDeviceKeys,
|
||||
bobCrossSigningKeys,
|
||||
aliceOlmMachine,
|
||||
aliceDeviceKeys,
|
||||
aliceCrossSigningKeys,
|
||||
);
|
||||
|
||||
try {
|
||||
await aliceOlmMachine.updateTrackedUsers([new RustSdkCryptoJs.UserId(bobUserId)]);
|
||||
await bobOlmMachine.updateTrackedUsers([new RustSdkCryptoJs.UserId(aliceUserId)]);
|
||||
|
||||
// Alice requests verification
|
||||
const bobUserIdentity = await aliceOlmMachine.getIdentity(new RustSdkCryptoJs.UserId(bobUserId));
|
||||
|
||||
const roomId = new RustSdkCryptoJs.RoomId("!roomId:example.org");
|
||||
const methods = [verificationMethodIdentifierToMethod("m.sas.v1")];
|
||||
const innerVerificationRequest = await bobUserIdentity.requestVerification(
|
||||
roomId,
|
||||
new RustSdkCryptoJs.EventId("$m.key.verification.request"),
|
||||
methods,
|
||||
);
|
||||
const aliceVerificationRequest = new RustVerificationRequest(
|
||||
aliceOlmMachine,
|
||||
innerVerificationRequest,
|
||||
aliceRequestLoop as unknown as OutgoingRequestProcessor,
|
||||
["m.sas.v1"],
|
||||
);
|
||||
|
||||
const verificationRequestContent = JSON.parse(await bobUserIdentity.verificationRequestContent(methods));
|
||||
await bobOlmMachine.receiveVerificationEvent(
|
||||
JSON.stringify({
|
||||
type: "m.room.message",
|
||||
sender: aliceUserId,
|
||||
event_id: "$m.key.verification.request",
|
||||
content: verificationRequestContent,
|
||||
origin_server_ts: Date.now(),
|
||||
unsigned: {
|
||||
age: 0,
|
||||
},
|
||||
}),
|
||||
roomId,
|
||||
);
|
||||
|
||||
// Bob accepts
|
||||
const bobInnerVerificationRequest = bobOlmMachine.getVerificationRequest(
|
||||
new RustSdkCryptoJs.UserId(aliceUserId),
|
||||
"$m.key.verification.request",
|
||||
)!;
|
||||
const bobVerificationRequest = new RustVerificationRequest(
|
||||
bobOlmMachine,
|
||||
bobInnerVerificationRequest,
|
||||
bobRequestLoop as unknown as OutgoingRequestProcessor,
|
||||
["m.sas.v1"],
|
||||
);
|
||||
|
||||
await bobVerificationRequest.accept();
|
||||
|
||||
// Alice and Bob both start the verification
|
||||
const aliceVerifier = await aliceVerificationRequest.startVerification("m.sas.v1");
|
||||
const bobVerifier = await bobVerificationRequest.startVerification("m.sas.v1");
|
||||
// We can now send Alice's start message to Bob
|
||||
await aliceRequestLoop.makeOutgoingRequest(aliceStartRequest!);
|
||||
|
||||
// create a function to compare the SAS, and then let the verification run
|
||||
let otherCallbacks: ShowSasCallbacks | undefined;
|
||||
const compareSas = (callbacks: ShowSasCallbacks) => {
|
||||
if (otherCallbacks) {
|
||||
const ourDecimal = callbacks.sas.decimal!;
|
||||
const theirDecimal = otherCallbacks.sas.decimal!;
|
||||
if (ourDecimal.every((el, idx) => el == theirDecimal[idx])) {
|
||||
otherCallbacks.confirm();
|
||||
callbacks.confirm();
|
||||
} else {
|
||||
otherCallbacks.mismatch();
|
||||
callbacks.mismatch();
|
||||
}
|
||||
} else {
|
||||
otherCallbacks = callbacks;
|
||||
}
|
||||
};
|
||||
aliceVerifier.on(VerifierEvent.ShowSas, compareSas);
|
||||
bobVerifier.on(VerifierEvent.ShowSas, compareSas);
|
||||
|
||||
await Promise.all([aliceVerifier.verify(), await bobVerifier.verify()]);
|
||||
} finally {
|
||||
await aliceRequestLoop.stop();
|
||||
await bobRequestLoop.stop();
|
||||
}
|
||||
});
|
||||
|
||||
it("can verify by QR code", async () => {
|
||||
const aliceUserId = "@alice:example.org";
|
||||
const aliceDeviceId = "ABCDEFG";
|
||||
const bobUserId = "@bob:example.org";
|
||||
const bobDeviceId = "HIJKLMN";
|
||||
const [aliceOlmMachine, aliceDeviceKeys, aliceCrossSigningKeys] = await initOlmMachineAndKeys(
|
||||
aliceUserId,
|
||||
aliceDeviceId,
|
||||
);
|
||||
const [bobOlmMachine, bobDeviceKeys, bobCrossSigningKeys] = await initOlmMachineAndKeys(bobUserId, bobDeviceId);
|
||||
|
||||
const aliceRequestLoop = makeRequestLoop(
|
||||
aliceOlmMachine,
|
||||
aliceDeviceKeys,
|
||||
aliceCrossSigningKeys,
|
||||
bobOlmMachine,
|
||||
bobDeviceKeys,
|
||||
bobCrossSigningKeys,
|
||||
);
|
||||
const bobRequestLoop = makeRequestLoop(
|
||||
bobOlmMachine,
|
||||
bobDeviceKeys,
|
||||
bobCrossSigningKeys,
|
||||
aliceOlmMachine,
|
||||
aliceDeviceKeys,
|
||||
aliceCrossSigningKeys,
|
||||
);
|
||||
|
||||
try {
|
||||
await aliceOlmMachine.updateTrackedUsers([new RustSdkCryptoJs.UserId(bobUserId)]);
|
||||
await bobOlmMachine.updateTrackedUsers([new RustSdkCryptoJs.UserId(aliceUserId)]);
|
||||
|
||||
// Alice requests verification
|
||||
const bobUserIdentity = await aliceOlmMachine.getIdentity(new RustSdkCryptoJs.UserId(bobUserId));
|
||||
|
||||
const roomId = new RustSdkCryptoJs.RoomId("!roomId:example.org");
|
||||
const methods = [
|
||||
verificationMethodIdentifierToMethod("m.reciprocate.v1"),
|
||||
verificationMethodIdentifierToMethod("m.qr_code.show.v1"),
|
||||
];
|
||||
const innerVerificationRequest = await bobUserIdentity.requestVerification(
|
||||
roomId,
|
||||
new RustSdkCryptoJs.EventId("$m.key.verification.request"),
|
||||
methods,
|
||||
);
|
||||
const aliceVerificationRequest = new RustVerificationRequest(
|
||||
aliceOlmMachine,
|
||||
innerVerificationRequest,
|
||||
aliceRequestLoop as unknown as OutgoingRequestProcessor,
|
||||
["m.reciprocate.v1", "m.qr_code.show.v1"],
|
||||
);
|
||||
|
||||
const verificationRequestContent = JSON.parse(await bobUserIdentity.verificationRequestContent(methods));
|
||||
await bobOlmMachine.receiveVerificationEvent(
|
||||
JSON.stringify({
|
||||
type: "m.room.message",
|
||||
sender: aliceUserId,
|
||||
event_id: "$m.key.verification.request",
|
||||
content: verificationRequestContent,
|
||||
origin_server_ts: Date.now(),
|
||||
unsigned: {
|
||||
age: 0,
|
||||
},
|
||||
}),
|
||||
roomId,
|
||||
);
|
||||
|
||||
// Bob accepts
|
||||
const bobInnerVerificationRequest = bobOlmMachine.getVerificationRequest(
|
||||
new RustSdkCryptoJs.UserId(aliceUserId),
|
||||
"$m.key.verification.request",
|
||||
)!;
|
||||
const bobVerificationRequest = new RustVerificationRequest(
|
||||
bobOlmMachine,
|
||||
bobInnerVerificationRequest,
|
||||
bobRequestLoop as unknown as OutgoingRequestProcessor,
|
||||
["m.reciprocate.v1", "m.qr_code.show.v1", "m.qr_code.scan.v1"],
|
||||
);
|
||||
|
||||
await bobVerificationRequest.accept();
|
||||
|
||||
// Bob scans
|
||||
const qrCode = await aliceVerificationRequest.generateQRCode();
|
||||
|
||||
const aliceVerifierPromise: Promise<Verifier> = new Promise((resolve, reject) => {
|
||||
aliceVerificationRequest.on(VerificationRequestEvent.Change, () => {
|
||||
const verifier = aliceVerificationRequest.verifier;
|
||||
if (verifier) {
|
||||
resolve(verifier);
|
||||
}
|
||||
});
|
||||
});
|
||||
const bobVerifier = await bobVerificationRequest.scanQRCode(qrCode!);
|
||||
|
||||
const aliceVerifier = await aliceVerifierPromise;
|
||||
aliceVerifier.on(VerifierEvent.ShowReciprocateQr, (showQrCodeCallbacks) => {
|
||||
showQrCodeCallbacks.confirm();
|
||||
});
|
||||
|
||||
await Promise.all([aliceVerifier.verify(), await bobVerifier.verify()]);
|
||||
} finally {
|
||||
await aliceRequestLoop.stop();
|
||||
await bobRequestLoop.stop();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("isVerificationEvent", () => {
|
||||
@@ -152,3 +511,148 @@ function makeMockedInner(): Mocked<RustSdkCryptoJs.VerificationRequest> {
|
||||
},
|
||||
} as unknown as Mocked<RustSdkCryptoJs.VerificationRequest>;
|
||||
}
|
||||
|
||||
interface CrossSigningKeys {
|
||||
master_key: any;
|
||||
self_signing_key: any;
|
||||
user_signing_key: any;
|
||||
}
|
||||
|
||||
/** create an Olm machine and device/cross-signing keys for a user */
|
||||
async function initOlmMachineAndKeys(
|
||||
userId: string,
|
||||
deviceId: string,
|
||||
): Promise<[RustSdkCryptoJs.OlmMachine, IDeviceKeys, CrossSigningKeys]> {
|
||||
const olmMachine = await RustSdkCryptoJs.OlmMachine.initialize(
|
||||
new RustSdkCryptoJs.UserId(userId),
|
||||
new RustSdkCryptoJs.DeviceId(deviceId),
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
const { uploadKeysRequest, uploadSignaturesRequest, uploadSigningKeysRequest } =
|
||||
await olmMachine.bootstrapCrossSigning(true);
|
||||
const deviceKeys = JSON.parse(uploadKeysRequest.body).device_keys;
|
||||
await olmMachine.markRequestAsSent(
|
||||
uploadKeysRequest.id,
|
||||
uploadKeysRequest.type,
|
||||
'{"one_time_key_counts":{"signed_curve25519":100}}',
|
||||
);
|
||||
const crossSigningSignatures = JSON.parse(uploadSignaturesRequest.body);
|
||||
for (const [keyId, signature] of Object.entries(crossSigningSignatures[userId][deviceId]["signatures"][userId])) {
|
||||
deviceKeys["signatures"][userId][keyId] = signature;
|
||||
}
|
||||
const crossSigningKeys = JSON.parse(uploadSigningKeysRequest.body);
|
||||
// note: the upload signatures request and upload signing keys requests
|
||||
// don't need to be marked as sent in the Olm machine
|
||||
|
||||
return [olmMachine, deviceKeys, crossSigningKeys];
|
||||
}
|
||||
|
||||
type CustomRequestHandler = (request: OutgoingRequest | RustSdkCryptoJs.UploadSigningKeysRequest) => Promise<any>;
|
||||
|
||||
/** Loop for handling outgoing requests from an Olm machine.
|
||||
*
|
||||
* Simulates a server with two users: "us" and "them". Handles key query
|
||||
* requests, querying either our keys or the other user's keys. Room messages
|
||||
* are sent as incoming verification events to the other user. A custom
|
||||
* handler can be added to override default request processing (the handler
|
||||
* should return a response body to inhibit default processing).
|
||||
*
|
||||
* Can also be used as an OutgoingRequestProcessor. */
|
||||
function makeRequestLoop(
|
||||
ourOlmMachine: RustSdkCryptoJs.OlmMachine,
|
||||
ourDeviceKeys: IDeviceKeys,
|
||||
ourCrossSigningKeys: CrossSigningKeys,
|
||||
theirOlmMachine: RustSdkCryptoJs.OlmMachine,
|
||||
theirDeviceKeys: IDeviceKeys,
|
||||
theirCrossSigningKeys: CrossSigningKeys,
|
||||
customHandler?: CustomRequestHandler,
|
||||
) {
|
||||
let stopRequestLoop = false;
|
||||
const ourUserId = ourOlmMachine.userId.toString();
|
||||
const ourDeviceId = ourOlmMachine.deviceId.toString();
|
||||
const theirUserId = theirOlmMachine.userId.toString();
|
||||
const theirDeviceId = theirOlmMachine.deviceId.toString();
|
||||
|
||||
function defaultHandler(request: OutgoingRequest | RustSdkCryptoJs.UploadSigningKeysRequest): any {
|
||||
if (request instanceof RustSdkCryptoJs.KeysQueryRequest) {
|
||||
const resp: Record<string, any> = {
|
||||
device_keys: {},
|
||||
};
|
||||
const body = JSON.parse(request.body);
|
||||
const query = body.device_keys;
|
||||
const masterKeys: Record<string, any> = {};
|
||||
const selfSigningKeys: Record<string, any> = {};
|
||||
if (ourUserId in query) {
|
||||
resp.device_keys[ourUserId] = { [ourDeviceId]: ourDeviceKeys };
|
||||
masterKeys[ourUserId] = ourCrossSigningKeys.master_key;
|
||||
selfSigningKeys[ourUserId] = ourCrossSigningKeys.self_signing_key;
|
||||
resp.user_signing_keys = {
|
||||
[ourUserId]: ourCrossSigningKeys.user_signing_key,
|
||||
};
|
||||
}
|
||||
if (theirUserId in query) {
|
||||
resp.device_keys[theirUserId] = {
|
||||
[theirDeviceId]: theirDeviceKeys,
|
||||
};
|
||||
masterKeys[theirUserId] = theirCrossSigningKeys.master_key;
|
||||
selfSigningKeys[theirUserId] = theirCrossSigningKeys.self_signing_key;
|
||||
}
|
||||
if (Object.keys(masterKeys).length) {
|
||||
resp.master_keys = masterKeys;
|
||||
}
|
||||
if (Object.keys(selfSigningKeys).length) {
|
||||
resp.self_signing_keys = selfSigningKeys;
|
||||
}
|
||||
return resp;
|
||||
} else if (request instanceof RustSdkCryptoJs.RoomMessageRequest) {
|
||||
theirOlmMachine.receiveVerificationEvent(
|
||||
JSON.stringify({
|
||||
type: request.event_type,
|
||||
sender: ourUserId,
|
||||
event_id: "$" + request.event_type,
|
||||
content: JSON.parse(request.body),
|
||||
origin_server_ts: Date.now(),
|
||||
unsigned: {
|
||||
age: 0,
|
||||
},
|
||||
}),
|
||||
new RustSdkCryptoJs.RoomId(request.room_id),
|
||||
);
|
||||
return { event_id: "$" + request.event_type };
|
||||
} else if (request instanceof RustSdkCryptoJs.SignatureUploadRequest) {
|
||||
// this only gets called at the end after the verification
|
||||
// succeeds, so we don't actually have to do anything.
|
||||
return { failures: {} };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
async function makeOutgoingRequest(
|
||||
request: OutgoingRequest | RustSdkCryptoJs.UploadSigningKeysRequest,
|
||||
): Promise<any> {
|
||||
const resp = (await customHandler?.(request)) ?? defaultHandler(request);
|
||||
if (!(request instanceof RustSdkCryptoJs.UploadSigningKeysRequest) && request.id) {
|
||||
await ourOlmMachine.markRequestAsSent(request.id!, request.type, JSON.stringify(resp));
|
||||
}
|
||||
}
|
||||
|
||||
async function runLoop() {
|
||||
while (!stopRequestLoop) {
|
||||
const requests = await ourOlmMachine.outgoingRequests();
|
||||
for (const request of requests) {
|
||||
await makeOutgoingRequest(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const loopCompletedPromise = runLoop();
|
||||
|
||||
return {
|
||||
makeOutgoingRequest,
|
||||
stop: async () => {
|
||||
stopRequestLoop = true;
|
||||
await loopCompletedPromise;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@@ -34,6 +34,7 @@ import { OutgoingRequest, OutgoingRequestProcessor } from "./OutgoingRequestProc
|
||||
import { TypedReEmitter } from "../ReEmitter";
|
||||
import { MatrixEvent } from "../models/event";
|
||||
import { EventType, MsgType } from "../@types/event";
|
||||
import { defer, IDeferred } from "../utils";
|
||||
|
||||
/**
|
||||
* An incoming, or outgoing, request to verify a user or a device via cross-signing.
|
||||
@@ -76,11 +77,16 @@ export class RustVerificationRequest
|
||||
const onChange = async (): Promise<void> => {
|
||||
const verification: RustSdkCryptoJs.Qr | RustSdkCryptoJs.Sas | undefined = this.inner.getVerification();
|
||||
|
||||
// If we now have a `Verification` where we lacked one before, or we have transitioned from QR to SAS,
|
||||
// wrap the new rust Verification as a js-sdk Verifier.
|
||||
// Set the _verifier object (wrapping the rust `Verification` as a js-sdk Verifier) if:
|
||||
// - we now have a `Verification` where we lacked one before
|
||||
// - we have transitioned from QR to SAS
|
||||
// - we are verifying with SAS, but we need to replace our verifier with a new one because both parties
|
||||
// tried to start verification at the same time, and we lost the tie breaking
|
||||
if (verification instanceof RustSdkCryptoJs.Sas) {
|
||||
if (this._verifier === undefined || this._verifier instanceof RustQrCodeVerifier) {
|
||||
this.setVerifier(new RustSASVerifier(verification, this, outgoingRequestProcessor));
|
||||
} else if (this._verifier instanceof RustSASVerifier) {
|
||||
this._verifier.replaceInner(verification);
|
||||
}
|
||||
} else if (verification instanceof RustSdkCryptoJs.Qr && this._verifier === undefined) {
|
||||
this.setVerifier(new RustQrCodeVerifier(verification, outgoingRequestProcessor));
|
||||
@@ -456,24 +462,35 @@ abstract class BaseRustVerifer<InnerType extends RustSdkCryptoJs.Qr | RustSdkCry
|
||||
VerifierEvent | VerificationRequestEvent,
|
||||
VerifierEventHandlerMap & VerificationRequestEventHandlerMap
|
||||
> {
|
||||
/** A promise which completes when the verification completes (or rejects when it is cancelled/fails) */
|
||||
protected readonly completionPromise: Promise<void>;
|
||||
/** A deferred which completes when the verification completes (or rejects when it is cancelled/fails) */
|
||||
protected readonly completionDeferred: IDeferred<void>;
|
||||
|
||||
public constructor(
|
||||
protected readonly inner: InnerType,
|
||||
protected inner: InnerType,
|
||||
protected readonly outgoingRequestProcessor: OutgoingRequestProcessor,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.completionPromise = new Promise<void>((resolve, reject) => {
|
||||
const onChange = async (): Promise<void> => {
|
||||
this.completionDeferred = defer();
|
||||
inner.registerChangesCallback(async () => {
|
||||
this.onChange();
|
||||
});
|
||||
// stop the runtime complaining if nobody catches a failure
|
||||
this.completionDeferred.promise.catch(() => null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook which is called when the underlying rust class notifies us that there has been a change.
|
||||
*
|
||||
* Can be overridden by subclasses to see if we can notify the application about an update. The overriding method
|
||||
* must call `super.onChange()`.
|
||||
*/
|
||||
protected onChange(): void {
|
||||
if (this.inner.isDone()) {
|
||||
resolve(undefined);
|
||||
this.completionDeferred.resolve(undefined);
|
||||
} else if (this.inner.isCancelled()) {
|
||||
const cancelInfo = this.inner.cancelInfo()!;
|
||||
reject(
|
||||
this.completionDeferred.reject(
|
||||
new Error(
|
||||
`Verification cancelled by ${
|
||||
cancelInfo.cancelledbyUs() ? "us" : "them"
|
||||
@@ -483,20 +500,8 @@ abstract class BaseRustVerifer<InnerType extends RustSdkCryptoJs.Qr | RustSdkCry
|
||||
}
|
||||
|
||||
this.emit(VerificationRequestEvent.Change);
|
||||
};
|
||||
inner.registerChangesCallback(onChange);
|
||||
});
|
||||
// stop the runtime complaining if nobody catches a failure
|
||||
this.completionPromise.catch(() => null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook which is called when the underlying rust class notifies us that there has been a change.
|
||||
*
|
||||
* Can be overridden by subclasses to see if we can notify the application about an update.
|
||||
*/
|
||||
protected onChange(): void {}
|
||||
|
||||
/**
|
||||
* Returns true if the verification has been cancelled, either by us or the other side.
|
||||
*/
|
||||
@@ -565,6 +570,8 @@ export class RustQrCodeVerifier extends BaseRustVerifer<RustSdkCryptoJs.Qr> impl
|
||||
cancel: () => this.cancel(),
|
||||
};
|
||||
}
|
||||
|
||||
super.onChange();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -580,7 +587,7 @@ export class RustQrCodeVerifier extends BaseRustVerifer<RustSdkCryptoJs.Qr> impl
|
||||
this.emit(VerifierEvent.ShowReciprocateQr, this.callbacks);
|
||||
}
|
||||
// Nothing to do here but wait.
|
||||
await this.completionPromise;
|
||||
await this.completionDeferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -657,15 +664,24 @@ export class RustSASVerifier extends BaseRustVerifer<RustSdkCryptoJs.Sas> implem
|
||||
* or times out.
|
||||
*/
|
||||
public async verify(): Promise<void> {
|
||||
await this.sendAccept();
|
||||
await this.completionDeferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the accept or start event, if it hasn't already been sent
|
||||
*/
|
||||
private async sendAccept(): Promise<void> {
|
||||
const req: undefined | OutgoingRequest = this.inner.accept();
|
||||
if (req) {
|
||||
await this.outgoingRequestProcessor.makeOutgoingRequest(req);
|
||||
}
|
||||
await this.completionPromise;
|
||||
}
|
||||
|
||||
/** if we can now show the callbacks, do so */
|
||||
protected onChange(): void {
|
||||
super.onChange();
|
||||
|
||||
if (this.callbacks === null) {
|
||||
const emoji = this.inner.emoji();
|
||||
const decimal = this.inner.decimals();
|
||||
@@ -717,6 +733,25 @@ export class RustSASVerifier extends BaseRustVerifer<RustSdkCryptoJs.Sas> implem
|
||||
public getShowSasCallbacks(): ShowSasCallbacks | null {
|
||||
return this.callbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the inner Rust verifier with a different one.
|
||||
*
|
||||
* @param inner - the new Rust verifier
|
||||
* @internal
|
||||
*/
|
||||
public replaceInner(inner: RustSdkCryptoJs.Sas): void {
|
||||
if (this.inner != inner) {
|
||||
this.inner = inner;
|
||||
inner.registerChangesCallback(async () => {
|
||||
this.onChange();
|
||||
});
|
||||
// replaceInner will only get called if we started the verification at the same time as the other side, and we lost
|
||||
// the tie breaker. So we need to re-accept their verification.
|
||||
this.sendAccept();
|
||||
this.onChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** For each specced verification method, the rust-side `VerificationMethod` corresponding to it */
|
||||
|
Reference in New Issue
Block a user