From ba16c4e809d892e967b9a265ef12e920e2b670ab Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Thu, 18 May 2023 19:05:39 +0100 Subject: [PATCH 1/4] Move IReciprocateQr to `crypto-api` and rename --- src/crypto-api.ts | 2 ++ src/crypto-api/verification.ts | 29 +++++++++++++++++++++++++++++ src/crypto/verification/QRCode.ts | 10 +++------- 3 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 src/crypto-api/verification.ts diff --git a/src/crypto-api.ts b/src/crypto-api.ts index 6467ba1835f..f52b7d3aae7 100644 --- a/src/crypto-api.ts +++ b/src/crypto-api.ts @@ -260,3 +260,5 @@ export class DeviceVerificationStatus { return this.localVerified || (this.trustCrossSignedDevices && this.crossSigningVerified); } } + +export * from "./crypto-api/verification"; diff --git a/src/crypto-api/verification.ts b/src/crypto-api/verification.ts new file mode 100644 index 00000000000..bd953922843 --- /dev/null +++ b/src/crypto-api/verification.ts @@ -0,0 +1,29 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/** + * Callbacks for user actions while a QR code is displayed. + * + * This is exposed as the payload of a `VerifierEvent.ShowReciprocateQr` event, or can be retrieved directly from the + * verifier as `reciprocateQREvent`. + */ +export interface ShowQrCodeCallbacks { + /** The user confirms that the verification data matches */ + confirm(): void; + + /** Cancel the verification flow */ + cancel(): void; +} diff --git a/src/crypto/verification/QRCode.ts b/src/crypto/verification/QRCode.ts index bfb532e4223..c25740f4410 100644 --- a/src/crypto/verification/QRCode.ts +++ b/src/crypto/verification/QRCode.ts @@ -26,25 +26,21 @@ import { VerificationRequest } from "./request/VerificationRequest"; import { MatrixClient } from "../../client"; import { IVerificationChannel } from "./request/Channel"; import { MatrixEvent } from "../../models/event"; +import { ShowQrCodeCallbacks } from "../../crypto-api/verification"; export const SHOW_QR_CODE_METHOD = "m.qr_code.show.v1"; export const SCAN_QR_CODE_METHOD = "m.qr_code.scan.v1"; -interface IReciprocateQr { - confirm(): void; - cancel(): void; -} - export enum QrCodeEvent { ShowReciprocateQr = "show_reciprocate_qr", } type EventHandlerMap = { - [QrCodeEvent.ShowReciprocateQr]: (qr: IReciprocateQr) => void; + [QrCodeEvent.ShowReciprocateQr]: (qr: ShowQrCodeCallbacks) => void; } & VerificationEventHandlerMap; export class ReciprocateQRCode extends Base { - public reciprocateQREvent?: IReciprocateQr; + public reciprocateQREvent?: ShowQrCodeCallbacks; public static factory( channel: IVerificationChannel, From 7de5c6fc95a94c84e0549468de781e0b0e6d26bb Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Thu, 18 May 2023 18:05:34 +0100 Subject: [PATCH 2/4] Move ISasEvent to `crypto-api`, and rename MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ... and add some ✨comments✨ --- src/crypto-api/verification.ts | 49 ++++++++++++++++++++++++++++++++++ src/crypto/verification/SAS.ts | 30 +++++++++------------ 2 files changed, 61 insertions(+), 18 deletions(-) diff --git a/src/crypto-api/verification.ts b/src/crypto-api/verification.ts index bd953922843..cbc719b9603 100644 --- a/src/crypto-api/verification.ts +++ b/src/crypto-api/verification.ts @@ -27,3 +27,52 @@ export interface ShowQrCodeCallbacks { /** Cancel the verification flow */ cancel(): void; } + +/** + * Callbacks for user actions while a SAS is displayed. + * + * This is exposed as the payload of a `VerifierEvent.ShowSas` event, or directly from the verifier as `sasEvent`. + */ +export interface ShowSasCallbacks { + /** The generated SAS to be shown to the user */ + sas: GeneratedSas; + + /** Function to call if the user confirms that the SAS matches. + * + * @returns A Promise that completes once the m.key.verification.mac is queued. + */ + confirm(): Promise; + + /** + * Function to call if the user finds the SAS does not match. + * + * Sends an `m.key.verification.cancel` event with a `m.mismatched_sas` error code. + */ + mismatch(): void; + + /** Cancel the verification flow */ + cancel(): void; +} + +/** A generated SAS to be shown to the user, in alternative formats */ +export interface GeneratedSas { + /** + * The SAS as three numbers between 0 and 8191. + * + * Only populated if the `decimal` SAS method was negotiated. + */ + decimal?: [number, number, number]; + + /** + * The SAS as seven emojis. + * + * Only populated if the `emoji` SAS method was negotiated. + */ + emoji?: EmojiMapping[]; +} + +/** + * An emoji for the generated SAS. A tuple `[emoji, name]` where `emoji` is the emoji itself and `name` is the + * English name. + */ +export type EmojiMapping = [emoji: string, name: string]; diff --git a/src/crypto/verification/SAS.ts b/src/crypto/verification/SAS.ts index a8d237d2da1..2f79a66bf91 100644 --- a/src/crypto/verification/SAS.ts +++ b/src/crypto/verification/SAS.ts @@ -33,6 +33,14 @@ import { logger } from "../../logger"; import { IContent, MatrixEvent } from "../../models/event"; import { generateDecimalSas } from "./SASDecimal"; import { EventType } from "../../@types/event"; +import { EmojiMapping, GeneratedSas, ShowSasCallbacks } from "../../crypto-api/verification"; + +// backwards-compatibility exports +export { + ShowSasCallbacks as ISasEvent, + GeneratedSas as IGeneratedSas, + EmojiMapping, +} from "../../crypto-api/verification"; const START_TYPE = EventType.KeyVerificationStart; @@ -44,8 +52,6 @@ const newMismatchedSASError = errorFactory("m.mismatched_sas", "Mismatched short const newMismatchedCommitmentError = errorFactory("m.mismatched_commitment", "Mismatched commitment"); -type EmojiMapping = [emoji: string, name: string]; - const emojiMapping: EmojiMapping[] = [ ["🐶", "dog"], // 0 ["🐱", "cat"], // 1 @@ -133,20 +139,8 @@ const sasGenerators = { emoji: generateEmojiSas, } as const; -export interface IGeneratedSas { - decimal?: [number, number, number]; - emoji?: EmojiMapping[]; -} - -export interface ISasEvent { - sas: IGeneratedSas; - confirm(): Promise; - cancel(): void; - mismatch(): void; -} - -function generateSas(sasBytes: Uint8Array, methods: string[]): IGeneratedSas { - const sas: IGeneratedSas = {}; +function generateSas(sasBytes: Uint8Array, methods: string[]): GeneratedSas { + const sas: GeneratedSas = {}; for (const method of methods) { if (method in sasGenerators) { // @ts-ignore - ts doesn't like us mixing types like this @@ -225,14 +219,14 @@ export enum SasEvent { } type EventHandlerMap = { - [SasEvent.ShowSas]: (sas: ISasEvent) => void; + [SasEvent.ShowSas]: (sas: ShowSasCallbacks) => void; } & VerificationEventHandlerMap; export class SAS extends Base { private waitingForAccept?: boolean; public ourSASPubKey?: string; public theirSASPubKey?: string; - public sasEvent?: ISasEvent; + public sasEvent?: ShowSasCallbacks; // eslint-disable-next-line @typescript-eslint/naming-convention public static get NAME(): string { From 32c7ef64896327285a173b0960fe74f67d2af4c5 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Thu, 18 May 2023 18:21:12 +0100 Subject: [PATCH 3/4] Combine QrCodeEvent, SasEvent and VerificationEvent together ... as a precursor to extracting a single `Verifier` interface for `SAS` and `ReciprocateQRCode`. `enum`s are slightly magical things that have both a type and a value, so we have to re-export their backwards-compatibility fudges twice. --- src/crypto-api/verification.ts | 34 +++++++++++++++++++++++++++++++ src/crypto/verification/Base.ts | 19 ++++++++++------- src/crypto/verification/QRCode.ts | 17 +++++++--------- src/crypto/verification/SAS.ts | 17 +++++++--------- 4 files changed, 60 insertions(+), 27 deletions(-) diff --git a/src/crypto-api/verification.ts b/src/crypto-api/verification.ts index cbc719b9603..b78c713eeb2 100644 --- a/src/crypto-api/verification.ts +++ b/src/crypto-api/verification.ts @@ -14,6 +14,40 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { MatrixEvent } from "../models/event"; + +/** Events emitted by `Verifier`. */ +export enum VerifierEvent { + /** + * The verification has been cancelled, by us or the other side. + * + * The payload is either an {@link Error}, or an (incoming or outgoing) {@link MatrixEvent}, depending on + * unspecified reasons. + */ + Cancel = "cancel", + + /** + * SAS data has been exchanged and should be displayed to the user. + * + * The payload is the {@link ShowQrCodeCallbacks} object. + */ + ShowSas = "show_sas", + + /** + * QR code data should be displayed to the user. + * + * The payload is the {@link ShowQrCodeCallbacks} object. + */ + ShowReciprocateQr = "show_reciprocate_qr", +} + +/** Listener type map for {@link VerifierEvent}s. */ +export type VerifierEventHandlerMap = { + [VerifierEvent.Cancel]: (e: Error | MatrixEvent) => void; + [VerifierEvent.ShowSas]: (sas: ShowSasCallbacks) => void; + [VerifierEvent.ShowReciprocateQr]: (qr: ShowQrCodeCallbacks) => void; +}; + /** * Callbacks for user actions while a QR code is displayed. * diff --git a/src/crypto/verification/Base.ts b/src/crypto/verification/Base.ts index 89c700c231c..cf1b9ceeebd 100644 --- a/src/crypto/verification/Base.ts +++ b/src/crypto/verification/Base.ts @@ -28,7 +28,8 @@ import { KeysDuringVerification, requestKeysDuringVerification } from "../CrossS import { IVerificationChannel } from "./request/Channel"; import { MatrixClient } from "../../client"; import { VerificationRequest } from "./request/VerificationRequest"; -import { ListenerMap, TypedEventEmitter } from "../../models/typed-event-emitter"; +import { TypedEventEmitter } from "../../models/typed-event-emitter"; +import { VerifierEvent, VerifierEventHandlerMap } from "../../crypto-api/verification"; const timeoutException = new Error("Verification timed out"); @@ -40,18 +41,22 @@ export class SwitchStartEventError extends Error { export type KeyVerifier = (keyId: string, device: DeviceInfo, keyInfo: string) => void; -export enum VerificationEvent { - Cancel = "cancel", -} +/** @deprecated use VerifierEvent */ +export type VerificationEvent = VerifierEvent; +/** @deprecated use VerifierEvent */ +export const VerificationEvent = VerifierEvent; +/** @deprecated use VerifierEventHandlerMap */ export type VerificationEventHandlerMap = { [VerificationEvent.Cancel]: (e: Error | MatrixEvent) => void; }; export class VerificationBase< - Events extends string, - Arguments extends ListenerMap, -> extends TypedEventEmitter { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + Events extends string = VerifierEvent, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + Arguments = VerifierEventHandlerMap, +> extends TypedEventEmitter { private cancelled = false; private _done = false; private promise: Promise | null = null; diff --git a/src/crypto/verification/QRCode.ts b/src/crypto/verification/QRCode.ts index c25740f4410..38feade12db 100644 --- a/src/crypto/verification/QRCode.ts +++ b/src/crypto/verification/QRCode.ts @@ -18,7 +18,7 @@ limitations under the License. * QR code key verification. */ -import { VerificationBase as Base, VerificationEventHandlerMap } from "./Base"; +import { VerificationBase as Base } from "./Base"; import { newKeyMismatchError, newUserCancelledError } from "./Error"; import { decodeBase64, encodeUnpaddedBase64 } from "../olmlib"; import { logger } from "../../logger"; @@ -26,20 +26,17 @@ import { VerificationRequest } from "./request/VerificationRequest"; import { MatrixClient } from "../../client"; import { IVerificationChannel } from "./request/Channel"; import { MatrixEvent } from "../../models/event"; -import { ShowQrCodeCallbacks } from "../../crypto-api/verification"; +import { ShowQrCodeCallbacks, VerifierEvent } from "../../crypto-api/verification"; export const SHOW_QR_CODE_METHOD = "m.qr_code.show.v1"; export const SCAN_QR_CODE_METHOD = "m.qr_code.scan.v1"; -export enum QrCodeEvent { - ShowReciprocateQr = "show_reciprocate_qr", -} - -type EventHandlerMap = { - [QrCodeEvent.ShowReciprocateQr]: (qr: ShowQrCodeCallbacks) => void; -} & VerificationEventHandlerMap; +/** @deprecated use VerifierEvent */ +export type QrCodeEvent = VerifierEvent; +/** @deprecated use VerifierEvent */ +export const QrCodeEvent = VerifierEvent; -export class ReciprocateQRCode extends Base { +export class ReciprocateQRCode extends Base { public reciprocateQREvent?: ShowQrCodeCallbacks; public static factory( diff --git a/src/crypto/verification/SAS.ts b/src/crypto/verification/SAS.ts index 2f79a66bf91..e8feaddca47 100644 --- a/src/crypto/verification/SAS.ts +++ b/src/crypto/verification/SAS.ts @@ -21,7 +21,7 @@ limitations under the License. import anotherjson from "another-json"; import { Utility, SAS as OlmSAS } from "@matrix-org/olm"; -import { VerificationBase as Base, SwitchStartEventError, VerificationEventHandlerMap } from "./Base"; +import { VerificationBase as Base, SwitchStartEventError } from "./Base"; import { errorFactory, newInvalidMessageError, @@ -33,7 +33,7 @@ import { logger } from "../../logger"; import { IContent, MatrixEvent } from "../../models/event"; import { generateDecimalSas } from "./SASDecimal"; import { EventType } from "../../@types/event"; -import { EmojiMapping, GeneratedSas, ShowSasCallbacks } from "../../crypto-api/verification"; +import { EmojiMapping, GeneratedSas, ShowSasCallbacks, VerifierEvent } from "../../crypto-api/verification"; // backwards-compatibility exports export { @@ -214,15 +214,12 @@ function intersection(anArray: T[], aSet: Set): T[] { return Array.isArray(anArray) ? anArray.filter((x) => aSet.has(x)) : []; } -export enum SasEvent { - ShowSas = "show_sas", -} - -type EventHandlerMap = { - [SasEvent.ShowSas]: (sas: ShowSasCallbacks) => void; -} & VerificationEventHandlerMap; +/** @deprecated use VerifierEvent */ +export type SasEvent = VerifierEvent; +/** @deprecated use VerifierEvent */ +export const SasEvent = VerifierEvent; -export class SAS extends Base { +export class SAS extends Base { private waitingForAccept?: boolean; public ourSASPubKey?: string; public theirSASPubKey?: string; From 7068fc7e7f377fe610e439d6a0e4d3c6e2581c86 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Mon, 22 May 2023 10:34:54 +0100 Subject: [PATCH 4/4] Update src/crypto/verification/Base.ts --- src/crypto/verification/Base.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/crypto/verification/Base.ts b/src/crypto/verification/Base.ts index cf1b9ceeebd..89bead3aa3e 100644 --- a/src/crypto/verification/Base.ts +++ b/src/crypto/verification/Base.ts @@ -51,6 +51,8 @@ export type VerificationEventHandlerMap = { [VerificationEvent.Cancel]: (e: Error | MatrixEvent) => void; }; +// The type parameters of VerificationBase are no longer used, but we need some placeholders to maintain +// backwards compatibility with applications that reference the class. export class VerificationBase< // eslint-disable-next-line @typescript-eslint/no-unused-vars Events extends string = VerifierEvent,