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 * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-wasm";
|
||||||
import { Mocked } from "jest-mock";
|
import { Mocked } from "jest-mock";
|
||||||
|
|
||||||
import { isVerificationEvent, RustVerificationRequest } from "../../../src/rust-crypto/verification";
|
import {
|
||||||
import { OutgoingRequestProcessor } from "../../../src/rust-crypto/OutgoingRequestProcessor";
|
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";
|
import { EventType, MatrixEvent, MsgType } from "../../../src";
|
||||||
|
|
||||||
describe("VerificationRequest", () => {
|
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", () => {
|
describe("isVerificationEvent", () => {
|
||||||
@@ -152,3 +511,148 @@ function makeMockedInner(): Mocked<RustSdkCryptoJs.VerificationRequest> {
|
|||||||
},
|
},
|
||||||
} as unknown as 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 { TypedReEmitter } from "../ReEmitter";
|
||||||
import { MatrixEvent } from "../models/event";
|
import { MatrixEvent } from "../models/event";
|
||||||
import { EventType, MsgType } from "../@types/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.
|
* 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 onChange = async (): Promise<void> => {
|
||||||
const verification: RustSdkCryptoJs.Qr | RustSdkCryptoJs.Sas | undefined = this.inner.getVerification();
|
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,
|
// Set the _verifier object (wrapping the rust `Verification` as a js-sdk Verifier) if:
|
||||||
// wrap the new rust Verification as a js-sdk Verifier.
|
// - 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 (verification instanceof RustSdkCryptoJs.Sas) {
|
||||||
if (this._verifier === undefined || this._verifier instanceof RustQrCodeVerifier) {
|
if (this._verifier === undefined || this._verifier instanceof RustQrCodeVerifier) {
|
||||||
this.setVerifier(new RustSASVerifier(verification, this, outgoingRequestProcessor));
|
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) {
|
} else if (verification instanceof RustSdkCryptoJs.Qr && this._verifier === undefined) {
|
||||||
this.setVerifier(new RustQrCodeVerifier(verification, outgoingRequestProcessor));
|
this.setVerifier(new RustQrCodeVerifier(verification, outgoingRequestProcessor));
|
||||||
@@ -456,46 +462,45 @@ abstract class BaseRustVerifer<InnerType extends RustSdkCryptoJs.Qr | RustSdkCry
|
|||||||
VerifierEvent | VerificationRequestEvent,
|
VerifierEvent | VerificationRequestEvent,
|
||||||
VerifierEventHandlerMap & VerificationRequestEventHandlerMap
|
VerifierEventHandlerMap & VerificationRequestEventHandlerMap
|
||||||
> {
|
> {
|
||||||
/** A promise which completes when the verification completes (or rejects when it is cancelled/fails) */
|
/** A deferred which completes when the verification completes (or rejects when it is cancelled/fails) */
|
||||||
protected readonly completionPromise: Promise<void>;
|
protected readonly completionDeferred: IDeferred<void>;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
protected readonly inner: InnerType,
|
protected inner: InnerType,
|
||||||
protected readonly outgoingRequestProcessor: OutgoingRequestProcessor,
|
protected readonly outgoingRequestProcessor: OutgoingRequestProcessor,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.completionPromise = new Promise<void>((resolve, reject) => {
|
this.completionDeferred = defer();
|
||||||
const onChange = async (): Promise<void> => {
|
inner.registerChangesCallback(async () => {
|
||||||
this.onChange();
|
this.onChange();
|
||||||
|
|
||||||
if (this.inner.isDone()) {
|
|
||||||
resolve(undefined);
|
|
||||||
} else if (this.inner.isCancelled()) {
|
|
||||||
const cancelInfo = this.inner.cancelInfo()!;
|
|
||||||
reject(
|
|
||||||
new Error(
|
|
||||||
`Verification cancelled by ${
|
|
||||||
cancelInfo.cancelledbyUs() ? "us" : "them"
|
|
||||||
} with code ${cancelInfo.cancelCode()}: ${cancelInfo.reason()}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.emit(VerificationRequestEvent.Change);
|
|
||||||
};
|
|
||||||
inner.registerChangesCallback(onChange);
|
|
||||||
});
|
});
|
||||||
// stop the runtime complaining if nobody catches a failure
|
// stop the runtime complaining if nobody catches a failure
|
||||||
this.completionPromise.catch(() => null);
|
this.completionDeferred.promise.catch(() => null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook which is called when the underlying rust class notifies us that there has been a change.
|
* 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.
|
* 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 {}
|
protected onChange(): void {
|
||||||
|
if (this.inner.isDone()) {
|
||||||
|
this.completionDeferred.resolve(undefined);
|
||||||
|
} else if (this.inner.isCancelled()) {
|
||||||
|
const cancelInfo = this.inner.cancelInfo()!;
|
||||||
|
this.completionDeferred.reject(
|
||||||
|
new Error(
|
||||||
|
`Verification cancelled by ${
|
||||||
|
cancelInfo.cancelledbyUs() ? "us" : "them"
|
||||||
|
} with code ${cancelInfo.cancelCode()}: ${cancelInfo.reason()}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit(VerificationRequestEvent.Change);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the verification has been cancelled, either by us or the other side.
|
* 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(),
|
cancel: () => this.cancel(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super.onChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -580,7 +587,7 @@ export class RustQrCodeVerifier extends BaseRustVerifer<RustSdkCryptoJs.Qr> impl
|
|||||||
this.emit(VerifierEvent.ShowReciprocateQr, this.callbacks);
|
this.emit(VerifierEvent.ShowReciprocateQr, this.callbacks);
|
||||||
}
|
}
|
||||||
// Nothing to do here but wait.
|
// 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.
|
* or times out.
|
||||||
*/
|
*/
|
||||||
public async verify(): Promise<void> {
|
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();
|
const req: undefined | OutgoingRequest = this.inner.accept();
|
||||||
if (req) {
|
if (req) {
|
||||||
await this.outgoingRequestProcessor.makeOutgoingRequest(req);
|
await this.outgoingRequestProcessor.makeOutgoingRequest(req);
|
||||||
}
|
}
|
||||||
await this.completionPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** if we can now show the callbacks, do so */
|
/** if we can now show the callbacks, do so */
|
||||||
protected onChange(): void {
|
protected onChange(): void {
|
||||||
|
super.onChange();
|
||||||
|
|
||||||
if (this.callbacks === null) {
|
if (this.callbacks === null) {
|
||||||
const emoji = this.inner.emoji();
|
const emoji = this.inner.emoji();
|
||||||
const decimal = this.inner.decimals();
|
const decimal = this.inner.decimals();
|
||||||
@@ -717,6 +733,25 @@ export class RustSASVerifier extends BaseRustVerifer<RustSdkCryptoJs.Sas> implem
|
|||||||
public getShowSasCallbacks(): ShowSasCallbacks | null {
|
public getShowSasCallbacks(): ShowSasCallbacks | null {
|
||||||
return this.callbacks;
|
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 */
|
/** For each specced verification method, the rust-side `VerificationMethod` corresponding to it */
|
||||||
|
Reference in New Issue
Block a user