summaryrefslogtreecommitdiff
path: root/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous
diff options
context:
space:
mode:
authorRaindropsSys <contact@minteck.org>2023-04-24 14:03:36 +0200
committerRaindropsSys <contact@minteck.org>2023-04-24 14:03:36 +0200
commit633c92eae865e957121e08de634aeee11a8b3992 (patch)
tree09d881bee1dae0b6eee49db1dfaf0f500240606c /includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous
parentc4657e4509733699c0f26a3c900bab47e915d5a0 (diff)
downloadpluralconnect-633c92eae865e957121e08de634aeee11a8b3992.tar.gz
pluralconnect-633c92eae865e957121e08de634aeee11a8b3992.tar.bz2
pluralconnect-633c92eae865e957121e08de634aeee11a8b3992.zip
Updated 18 files, added 1692 files and deleted includes/system/compare.inc (automated)
Diffstat (limited to 'includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous')
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/MSC3906Rendezvous.ts264
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/RendezvousChannel.ts48
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/RendezvousCode.ts25
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/RendezvousError.ts23
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/RendezvousFailureReason.ts31
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/RendezvousIntent.ts20
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/RendezvousTransport.ts58
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/channels/MSC3903ECDHv2RendezvousChannel.ts259
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/channels/index.ts17
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/index.ts23
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/transports/MSC3886SimpleHttpRendezvousTransport.ts193
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/transports/index.ts17
12 files changed, 978 insertions, 0 deletions
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/MSC3906Rendezvous.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/MSC3906Rendezvous.ts
new file mode 100644
index 0000000..f431c83
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/MSC3906Rendezvous.ts
@@ -0,0 +1,264 @@
+/*
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { UnstableValue } from "matrix-events-sdk";
+
+import { RendezvousChannel, RendezvousFailureListener, RendezvousFailureReason, RendezvousIntent } from ".";
+import { MatrixClient } from "../client";
+import { CrossSigningInfo } from "../crypto/CrossSigning";
+import { DeviceInfo } from "../crypto/deviceinfo";
+import { buildFeatureSupportMap, Feature, ServerSupport } from "../feature";
+import { logger } from "../logger";
+import { sleep } from "../utils";
+
+enum PayloadType {
+ Start = "m.login.start",
+ Finish = "m.login.finish",
+ Progress = "m.login.progress",
+}
+
+enum Outcome {
+ Success = "success",
+ Failure = "failure",
+ Verified = "verified",
+ Declined = "declined",
+ Unsupported = "unsupported",
+}
+
+export interface MSC3906RendezvousPayload {
+ type: PayloadType;
+ intent?: RendezvousIntent;
+ outcome?: Outcome;
+ device_id?: string;
+ device_key?: string;
+ verifying_device_id?: string;
+ verifying_device_key?: string;
+ master_key?: string;
+ protocols?: string[];
+ protocol?: string;
+ login_token?: string;
+ homeserver?: string;
+}
+
+const LOGIN_TOKEN_PROTOCOL = new UnstableValue("login_token", "org.matrix.msc3906.login_token");
+
+/**
+ * Implements MSC3906 to allow a user to sign in on a new device using QR code.
+ * This implementation only supports generating a QR code on a device that is already signed in.
+ * Note that this is UNSTABLE and may have breaking changes without notice.
+ */
+export class MSC3906Rendezvous {
+ private newDeviceId?: string;
+ private newDeviceKey?: string;
+ private ourIntent: RendezvousIntent = RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE;
+ private _code?: string;
+
+ /**
+ * @param channel - The secure channel used for communication
+ * @param client - The Matrix client in used on the device already logged in
+ * @param onFailure - Callback for when the rendezvous fails
+ */
+ public constructor(
+ private channel: RendezvousChannel<MSC3906RendezvousPayload>,
+ private client: MatrixClient,
+ public onFailure?: RendezvousFailureListener,
+ ) {}
+
+ /**
+ * Returns the code representing the rendezvous suitable for rendering in a QR code or undefined if not generated yet.
+ */
+ public get code(): string | undefined {
+ return this._code;
+ }
+
+ /**
+ * Generate the code including doing partial set up of the channel where required.
+ */
+ public async generateCode(): Promise<void> {
+ if (this._code) {
+ return;
+ }
+
+ this._code = JSON.stringify(await this.channel.generateCode(this.ourIntent));
+ }
+
+ public async startAfterShowingCode(): Promise<string | undefined> {
+ const checksum = await this.channel.connect();
+
+ logger.info(`Connected to secure channel with checksum: ${checksum} our intent is ${this.ourIntent}`);
+
+ const features = await buildFeatureSupportMap(await this.client.getVersions());
+ // determine available protocols
+ if (features.get(Feature.LoginTokenRequest) === ServerSupport.Unsupported) {
+ logger.info("Server doesn't support MSC3882");
+ await this.send({ type: PayloadType.Finish, outcome: Outcome.Unsupported });
+ await this.cancel(RendezvousFailureReason.HomeserverLacksSupport);
+ return undefined;
+ }
+
+ await this.send({ type: PayloadType.Progress, protocols: [LOGIN_TOKEN_PROTOCOL.name] });
+
+ logger.info("Waiting for other device to chose protocol");
+ const { type, protocol, outcome } = await this.receive();
+
+ if (type === PayloadType.Finish) {
+ // new device decided not to complete
+ switch (outcome ?? "") {
+ case "unsupported":
+ await this.cancel(RendezvousFailureReason.UnsupportedAlgorithm);
+ break;
+ default:
+ await this.cancel(RendezvousFailureReason.Unknown);
+ }
+ return undefined;
+ }
+
+ if (type !== PayloadType.Progress) {
+ await this.cancel(RendezvousFailureReason.Unknown);
+ return undefined;
+ }
+
+ if (!protocol || !LOGIN_TOKEN_PROTOCOL.matches(protocol)) {
+ await this.cancel(RendezvousFailureReason.UnsupportedAlgorithm);
+ return undefined;
+ }
+
+ return checksum;
+ }
+
+ private async receive(): Promise<MSC3906RendezvousPayload> {
+ return (await this.channel.receive()) as MSC3906RendezvousPayload;
+ }
+
+ private async send(payload: MSC3906RendezvousPayload): Promise<void> {
+ await this.channel.send(payload);
+ }
+
+ public async declineLoginOnExistingDevice(): Promise<void> {
+ logger.info("User declined sign in");
+ await this.send({ type: PayloadType.Finish, outcome: Outcome.Declined });
+ }
+
+ public async approveLoginOnExistingDevice(loginToken: string): Promise<string | undefined> {
+ // eslint-disable-next-line camelcase
+ await this.send({ type: PayloadType.Progress, login_token: loginToken, homeserver: this.client.baseUrl });
+
+ logger.info("Waiting for outcome");
+ const res = await this.receive();
+ if (!res) {
+ return undefined;
+ }
+ const { outcome, device_id: deviceId, device_key: deviceKey } = res;
+
+ if (outcome !== "success") {
+ throw new Error("Linking failed");
+ }
+
+ this.newDeviceId = deviceId;
+ this.newDeviceKey = deviceKey;
+
+ return deviceId;
+ }
+
+ private async verifyAndCrossSignDevice(deviceInfo: DeviceInfo): Promise<CrossSigningInfo | DeviceInfo> {
+ if (!this.client.crypto) {
+ throw new Error("Crypto not available on client");
+ }
+
+ if (!this.newDeviceId) {
+ throw new Error("No new device ID set");
+ }
+
+ // check that keys received from the server for the new device match those received from the device itself
+ if (deviceInfo.getFingerprint() !== this.newDeviceKey) {
+ throw new Error(
+ `New device has different keys than expected: ${this.newDeviceKey} vs ${deviceInfo.getFingerprint()}`,
+ );
+ }
+
+ const userId = this.client.getUserId();
+
+ if (!userId) {
+ throw new Error("No user ID set");
+ }
+ // mark the device as verified locally + cross sign
+ logger.info(`Marking device ${this.newDeviceId} as verified`);
+ const info = await this.client.crypto.setDeviceVerification(userId, this.newDeviceId, true, false, true);
+
+ const masterPublicKey = this.client.crypto.crossSigningInfo.getId("master")!;
+
+ await this.send({
+ type: PayloadType.Finish,
+ outcome: Outcome.Verified,
+ verifying_device_id: this.client.getDeviceId()!,
+ verifying_device_key: this.client.getDeviceEd25519Key()!,
+ master_key: masterPublicKey,
+ });
+
+ return info;
+ }
+
+ /**
+ * Verify the device and cross-sign it.
+ * @param timeout - time in milliseconds to wait for device to come online
+ * @returns the new device info if the device was verified
+ */
+ public async verifyNewDeviceOnExistingDevice(
+ timeout = 10 * 1000,
+ ): Promise<DeviceInfo | CrossSigningInfo | undefined> {
+ if (!this.newDeviceId) {
+ throw new Error("No new device to sign");
+ }
+
+ if (!this.newDeviceKey) {
+ logger.info("No new device key to sign");
+ return undefined;
+ }
+
+ if (!this.client.crypto) {
+ throw new Error("Crypto not available on client");
+ }
+
+ const userId = this.client.getUserId();
+
+ if (!userId) {
+ throw new Error("No user ID set");
+ }
+
+ let deviceInfo = this.client.crypto.getStoredDevice(userId, this.newDeviceId);
+
+ if (!deviceInfo) {
+ logger.info("Going to wait for new device to be online");
+ await sleep(timeout);
+ deviceInfo = this.client.crypto.getStoredDevice(userId, this.newDeviceId);
+ }
+
+ if (deviceInfo) {
+ return await this.verifyAndCrossSignDevice(deviceInfo);
+ }
+
+ throw new Error("Device not online within timeout");
+ }
+
+ public async cancel(reason: RendezvousFailureReason): Promise<void> {
+ this.onFailure?.(reason);
+ await this.channel.cancel(reason);
+ }
+
+ public async close(): Promise<void> {
+ await this.channel.close();
+ }
+}
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/RendezvousChannel.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/RendezvousChannel.ts
new file mode 100644
index 0000000..549ebc8
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/RendezvousChannel.ts
@@ -0,0 +1,48 @@
+/*
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { RendezvousCode, RendezvousIntent, RendezvousFailureReason } from ".";
+
+export interface RendezvousChannel<T> {
+ /**
+ * @returns the checksum/confirmation digits to be shown to the user
+ */
+ connect(): Promise<string>;
+
+ /**
+ * Send a payload via the channel.
+ * @param data - payload to send
+ */
+ send(data: T): Promise<void>;
+
+ /**
+ * Receive a payload from the channel.
+ * @returns the received payload
+ */
+ receive(): Promise<Partial<T> | undefined>;
+
+ /**
+ * Close the channel and clear up any resources.
+ */
+ close(): Promise<void>;
+
+ /**
+ * @returns a representation of the channel that can be encoded in a QR or similar
+ */
+ generateCode(intent: RendezvousIntent): Promise<RendezvousCode>;
+
+ cancel(reason: RendezvousFailureReason): Promise<void>;
+}
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/RendezvousCode.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/RendezvousCode.ts
new file mode 100644
index 0000000..86608aa
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/RendezvousCode.ts
@@ -0,0 +1,25 @@
+/*
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { RendezvousTransportDetails, RendezvousIntent } from ".";
+
+export interface RendezvousCode {
+ intent: RendezvousIntent;
+ rendezvous?: {
+ transport: RendezvousTransportDetails;
+ algorithm: string;
+ };
+}
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/RendezvousError.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/RendezvousError.ts
new file mode 100644
index 0000000..8b76fc1
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/RendezvousError.ts
@@ -0,0 +1,23 @@
+/*
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { RendezvousFailureReason } from ".";
+
+export class RendezvousError extends Error {
+ public constructor(message: string, public readonly code: RendezvousFailureReason) {
+ super(message);
+ }
+}
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/RendezvousFailureReason.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/RendezvousFailureReason.ts
new file mode 100644
index 0000000..b19a91c
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/RendezvousFailureReason.ts
@@ -0,0 +1,31 @@
+/*
+Copyright 2022 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.
+*/
+
+export type RendezvousFailureListener = (reason: RendezvousFailureReason) => void;
+
+export enum RendezvousFailureReason {
+ UserDeclined = "user_declined",
+ OtherDeviceNotSignedIn = "other_device_not_signed_in",
+ OtherDeviceAlreadySignedIn = "other_device_already_signed_in",
+ Unknown = "unknown",
+ Expired = "expired",
+ UserCancelled = "user_cancelled",
+ InvalidCode = "invalid_code",
+ UnsupportedAlgorithm = "unsupported_algorithm",
+ DataMismatch = "data_mismatch",
+ UnsupportedTransport = "unsupported_transport",
+ HomeserverLacksSupport = "homeserver_lacks_support",
+}
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/RendezvousIntent.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/RendezvousIntent.ts
new file mode 100644
index 0000000..db53ef9
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/RendezvousIntent.ts
@@ -0,0 +1,20 @@
+/*
+Copyright 2022 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.
+*/
+
+export enum RendezvousIntent {
+ LOGIN_ON_NEW_DEVICE = "login.start",
+ RECIPROCATE_LOGIN_ON_EXISTING_DEVICE = "login.reciprocate",
+}
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/RendezvousTransport.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/RendezvousTransport.ts
new file mode 100644
index 0000000..08905be
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/RendezvousTransport.ts
@@ -0,0 +1,58 @@
+/*
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { RendezvousFailureListener, RendezvousFailureReason } from ".";
+
+export interface RendezvousTransportDetails {
+ type: string;
+}
+
+/**
+ * Interface representing a generic rendezvous transport.
+ */
+export interface RendezvousTransport<T> {
+ /**
+ * Ready state of the transport. This is set to true when the transport is ready to be used.
+ */
+ readonly ready: boolean;
+
+ /**
+ * Listener for cancellation events. This is called when the rendezvous is cancelled or fails.
+ */
+ onFailure?: RendezvousFailureListener;
+
+ /**
+ * @returns the transport details that can be encoded in a QR or similar
+ */
+ details(): Promise<RendezvousTransportDetails>;
+
+ /**
+ * Send data via the transport.
+ * @param data - the data itself
+ */
+ send(data: T): Promise<void>;
+
+ /**
+ * Receive data from the transport.
+ */
+ receive(): Promise<Partial<T> | undefined>;
+
+ /**
+ * Cancel the rendezvous. This will call `onCancelled()` if it is set.
+ * @param reason - the reason for the cancellation/failure
+ */
+ cancel(reason: RendezvousFailureReason): Promise<void>;
+}
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/channels/MSC3903ECDHv2RendezvousChannel.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/channels/MSC3903ECDHv2RendezvousChannel.ts
new file mode 100644
index 0000000..be60ee5
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/channels/MSC3903ECDHv2RendezvousChannel.ts
@@ -0,0 +1,259 @@
+/*
+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.
+*/
+
+import { SAS } from "@matrix-org/olm";
+
+import {
+ RendezvousError,
+ RendezvousCode,
+ RendezvousIntent,
+ RendezvousChannel,
+ RendezvousTransportDetails,
+ RendezvousTransport,
+ RendezvousFailureReason,
+} from "..";
+import { encodeUnpaddedBase64, decodeBase64 } from "../../crypto/olmlib";
+import { crypto, subtleCrypto, TextEncoder } from "../../crypto/crypto";
+import { generateDecimalSas } from "../../crypto/verification/SASDecimal";
+import { UnstableValue } from "../../NamespacedValue";
+
+const ECDH_V2 = new UnstableValue(
+ "m.rendezvous.v2.curve25519-aes-sha256",
+ "org.matrix.msc3903.rendezvous.v2.curve25519-aes-sha256",
+);
+
+export interface ECDHv2RendezvousCode extends RendezvousCode {
+ rendezvous: {
+ transport: RendezvousTransportDetails;
+ algorithm: typeof ECDH_V2.name | typeof ECDH_V2.altName;
+ key: string;
+ };
+}
+
+export type MSC3903ECDHPayload = PlainTextPayload | EncryptedPayload;
+
+export interface PlainTextPayload {
+ algorithm: typeof ECDH_V2.name | typeof ECDH_V2.altName;
+ key?: string;
+}
+
+export interface EncryptedPayload {
+ iv: string;
+ ciphertext: string;
+}
+
+async function importKey(key: Uint8Array): Promise<CryptoKey> {
+ if (!subtleCrypto) {
+ throw new Error("Web Crypto is not available");
+ }
+
+ const imported = subtleCrypto.importKey("raw", key, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
+
+ return imported;
+}
+
+/**
+ * Implementation of the unstable [MSC3903](https://github.com/matrix-org/matrix-spec-proposals/pull/3903)
+ * X25519/ECDH key agreement based secure rendezvous channel.
+ * Note that this is UNSTABLE and may have breaking changes without notice.
+ */
+export class MSC3903ECDHv2RendezvousChannel<T> implements RendezvousChannel<T> {
+ private olmSAS?: SAS;
+ private ourPublicKey: Uint8Array;
+ private aesKey?: CryptoKey;
+ private connected = false;
+
+ public constructor(
+ private transport: RendezvousTransport<MSC3903ECDHPayload>,
+ private theirPublicKey?: Uint8Array,
+ public onFailure?: (reason: RendezvousFailureReason) => void,
+ ) {
+ this.olmSAS = new global.Olm.SAS();
+ this.ourPublicKey = decodeBase64(this.olmSAS.get_pubkey());
+ }
+
+ public async generateCode(intent: RendezvousIntent): Promise<ECDHv2RendezvousCode> {
+ if (this.transport.ready) {
+ throw new Error("Code already generated");
+ }
+
+ await this.transport.send({ algorithm: ECDH_V2.name });
+
+ const rendezvous: ECDHv2RendezvousCode = {
+ rendezvous: {
+ algorithm: ECDH_V2.name,
+ key: encodeUnpaddedBase64(this.ourPublicKey),
+ transport: await this.transport.details(),
+ },
+ intent,
+ };
+
+ return rendezvous;
+ }
+
+ public async connect(): Promise<string> {
+ if (this.connected) {
+ throw new Error("Channel already connected");
+ }
+
+ if (!this.olmSAS) {
+ throw new Error("Channel closed");
+ }
+
+ const isInitiator = !this.theirPublicKey;
+
+ if (isInitiator) {
+ // wait for the other side to send us their public key
+ const rawRes = await this.transport.receive();
+ if (!rawRes) {
+ throw new Error("No response from other device");
+ }
+ const res = rawRes as Partial<PlainTextPayload>;
+ const { key, algorithm } = res;
+ if (!algorithm || !ECDH_V2.matches(algorithm) || !key) {
+ throw new RendezvousError(
+ "Unsupported algorithm: " + algorithm,
+ RendezvousFailureReason.UnsupportedAlgorithm,
+ );
+ }
+
+ this.theirPublicKey = decodeBase64(key);
+ } else {
+ // send our public key unencrypted
+ await this.transport.send({
+ algorithm: ECDH_V2.name,
+ key: encodeUnpaddedBase64(this.ourPublicKey),
+ });
+ }
+
+ this.connected = true;
+
+ this.olmSAS.set_their_key(encodeUnpaddedBase64(this.theirPublicKey!));
+
+ const initiatorKey = isInitiator ? this.ourPublicKey : this.theirPublicKey!;
+ const recipientKey = isInitiator ? this.theirPublicKey! : this.ourPublicKey;
+ let aesInfo = ECDH_V2.name;
+ aesInfo += `|${encodeUnpaddedBase64(initiatorKey)}`;
+ aesInfo += `|${encodeUnpaddedBase64(recipientKey)}`;
+
+ const aesKeyBytes = this.olmSAS.generate_bytes(aesInfo, 32);
+
+ this.aesKey = await importKey(aesKeyBytes);
+
+ // blank the bytes out to make sure not kept in memory
+ aesKeyBytes.fill(0);
+
+ const rawChecksum = this.olmSAS.generate_bytes(aesInfo, 5);
+ return generateDecimalSas(Array.from(rawChecksum)).join("-");
+ }
+
+ private async encrypt(data: T): Promise<MSC3903ECDHPayload> {
+ if (!subtleCrypto) {
+ throw new Error("Web Crypto is not available");
+ }
+
+ const iv = new Uint8Array(32);
+ crypto.getRandomValues(iv);
+
+ const encodedData = new TextEncoder().encode(JSON.stringify(data));
+
+ const ciphertext = await subtleCrypto.encrypt(
+ {
+ name: "AES-GCM",
+ iv,
+ tagLength: 128,
+ },
+ this.aesKey as CryptoKey,
+ encodedData,
+ );
+
+ return {
+ iv: encodeUnpaddedBase64(iv),
+ ciphertext: encodeUnpaddedBase64(ciphertext),
+ };
+ }
+
+ public async send(payload: T): Promise<void> {
+ if (!this.olmSAS) {
+ throw new Error("Channel closed");
+ }
+
+ if (!this.aesKey) {
+ throw new Error("Shared secret not set up");
+ }
+
+ return this.transport.send(await this.encrypt(payload));
+ }
+
+ private async decrypt({ iv, ciphertext }: EncryptedPayload): Promise<Partial<T>> {
+ if (!ciphertext || !iv) {
+ throw new Error("Missing ciphertext and/or iv");
+ }
+
+ const ciphertextBytes = decodeBase64(ciphertext);
+
+ if (!subtleCrypto) {
+ throw new Error("Web Crypto is not available");
+ }
+
+ const plaintext = await subtleCrypto.decrypt(
+ {
+ name: "AES-GCM",
+ iv: decodeBase64(iv),
+ tagLength: 128,
+ },
+ this.aesKey as CryptoKey,
+ ciphertextBytes,
+ );
+
+ return JSON.parse(new TextDecoder().decode(new Uint8Array(plaintext)));
+ }
+
+ public async receive(): Promise<Partial<T> | undefined> {
+ if (!this.olmSAS) {
+ throw new Error("Channel closed");
+ }
+ if (!this.aesKey) {
+ throw new Error("Shared secret not set up");
+ }
+
+ const rawData = await this.transport.receive();
+ if (!rawData) {
+ return undefined;
+ }
+ const data = rawData as Partial<EncryptedPayload>;
+ if (data.ciphertext && data.iv) {
+ return this.decrypt(data as EncryptedPayload);
+ }
+
+ throw new Error("Data received but no ciphertext");
+ }
+
+ public async close(): Promise<void> {
+ if (this.olmSAS) {
+ this.olmSAS.free();
+ this.olmSAS = undefined;
+ }
+ }
+
+ public async cancel(reason: RendezvousFailureReason): Promise<void> {
+ try {
+ await this.transport.cancel(reason);
+ } finally {
+ await this.close();
+ }
+ }
+}
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/channels/index.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/channels/index.ts
new file mode 100644
index 0000000..f157bbe
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/channels/index.ts
@@ -0,0 +1,17 @@
+/*
+Copyright 2022 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.
+*/
+
+export * from "./MSC3903ECDHv2RendezvousChannel";
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/index.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/index.ts
new file mode 100644
index 0000000..379b133
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/index.ts
@@ -0,0 +1,23 @@
+/*
+Copyright 2022 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.
+*/
+
+export * from "./MSC3906Rendezvous";
+export * from "./RendezvousChannel";
+export * from "./RendezvousCode";
+export * from "./RendezvousError";
+export * from "./RendezvousFailureReason";
+export * from "./RendezvousIntent";
+export * from "./RendezvousTransport";
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/transports/MSC3886SimpleHttpRendezvousTransport.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/transports/MSC3886SimpleHttpRendezvousTransport.ts
new file mode 100644
index 0000000..430ee92
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/transports/MSC3886SimpleHttpRendezvousTransport.ts
@@ -0,0 +1,193 @@
+/*
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { UnstableValue } from "matrix-events-sdk";
+
+import { logger } from "../../logger";
+import { sleep } from "../../utils";
+import {
+ RendezvousFailureListener,
+ RendezvousFailureReason,
+ RendezvousTransport,
+ RendezvousTransportDetails,
+} from "..";
+import { MatrixClient } from "../../matrix";
+import { ClientPrefix } from "../../http-api";
+
+const TYPE = new UnstableValue("http.v1", "org.matrix.msc3886.http.v1");
+
+export interface MSC3886SimpleHttpRendezvousTransportDetails extends RendezvousTransportDetails {
+ uri: string;
+}
+
+/**
+ * Implementation of the unstable [MSC3886](https://github.com/matrix-org/matrix-spec-proposals/pull/3886)
+ * simple HTTP rendezvous protocol.
+ * Note that this is UNSTABLE and may have breaking changes without notice.
+ */
+export class MSC3886SimpleHttpRendezvousTransport<T extends {}> implements RendezvousTransport<T> {
+ private uri?: string;
+ private etag?: string;
+ private expiresAt?: Date;
+ private client: MatrixClient;
+ private fallbackRzServer?: string;
+ private fetchFn?: typeof global.fetch;
+ private cancelled = false;
+ private _ready = false;
+ public onFailure?: RendezvousFailureListener;
+
+ public constructor({
+ onFailure,
+ client,
+ fallbackRzServer,
+ fetchFn,
+ }: {
+ fetchFn?: typeof global.fetch;
+ onFailure?: RendezvousFailureListener;
+ client: MatrixClient;
+ fallbackRzServer?: string;
+ }) {
+ this.fetchFn = fetchFn;
+ this.onFailure = onFailure;
+ this.client = client;
+ this.fallbackRzServer = fallbackRzServer;
+ }
+
+ public get ready(): boolean {
+ return this._ready;
+ }
+
+ public async details(): Promise<MSC3886SimpleHttpRendezvousTransportDetails> {
+ if (!this.uri) {
+ throw new Error("Rendezvous not set up");
+ }
+
+ return {
+ type: TYPE.name,
+ uri: this.uri,
+ };
+ }
+
+ private fetch(resource: URL | string, options?: RequestInit): ReturnType<typeof global.fetch> {
+ if (this.fetchFn) {
+ return this.fetchFn(resource, options);
+ }
+ return global.fetch(resource, options);
+ }
+
+ private async getPostEndpoint(): Promise<string | undefined> {
+ try {
+ if (await this.client.doesServerSupportUnstableFeature("org.matrix.msc3886")) {
+ return `${this.client.baseUrl}${ClientPrefix.Unstable}/org.matrix.msc3886/rendezvous`;
+ }
+ } catch (err) {
+ logger.warn("Failed to get unstable features", err);
+ }
+
+ return this.fallbackRzServer;
+ }
+
+ public async send(data: T): Promise<void> {
+ if (this.cancelled) {
+ return;
+ }
+ const method = this.uri ? "PUT" : "POST";
+ const uri = this.uri ?? (await this.getPostEndpoint());
+
+ if (!uri) {
+ throw new Error("Invalid rendezvous URI");
+ }
+
+ const headers: Record<string, string> = { "content-type": "application/json" };
+ if (this.etag) {
+ headers["if-match"] = this.etag;
+ }
+
+ const res = await this.fetch(uri, { method, headers, body: JSON.stringify(data) });
+ if (res.status === 404) {
+ return this.cancel(RendezvousFailureReason.Unknown);
+ }
+ this.etag = res.headers.get("etag") ?? undefined;
+
+ if (method === "POST") {
+ const location = res.headers.get("location");
+ if (!location) {
+ throw new Error("No rendezvous URI given");
+ }
+ const expires = res.headers.get("expires");
+ if (expires) {
+ this.expiresAt = new Date(expires);
+ }
+ // we would usually expect the final `url` to be set by a proper fetch implementation.
+ // however, if a polyfill based on XHR is used it won't be set, we we use existing URI as fallback
+ const baseUrl = res.url ?? uri;
+ // resolve location header which could be relative or absolute
+ this.uri = new URL(location, `${baseUrl}${baseUrl.endsWith("/") ? "" : "/"}`).href;
+ this._ready = true;
+ }
+ }
+
+ public async receive(): Promise<Partial<T> | undefined> {
+ if (!this.uri) {
+ throw new Error("Rendezvous not set up");
+ }
+ // eslint-disable-next-line no-constant-condition
+ while (true) {
+ if (this.cancelled) {
+ return undefined;
+ }
+
+ const headers: Record<string, string> = {};
+ if (this.etag) {
+ headers["if-none-match"] = this.etag;
+ }
+ const poll = await this.fetch(this.uri, { method: "GET", headers });
+
+ if (poll.status === 404) {
+ this.cancel(RendezvousFailureReason.Unknown);
+ return undefined;
+ }
+
+ // rely on server expiring the channel rather than checking ourselves
+
+ if (poll.headers.get("content-type") !== "application/json") {
+ this.etag = poll.headers.get("etag") ?? undefined;
+ } else if (poll.status === 200) {
+ this.etag = poll.headers.get("etag") ?? undefined;
+ return poll.json();
+ }
+ await sleep(1000);
+ }
+ }
+
+ public async cancel(reason: RendezvousFailureReason): Promise<void> {
+ if (reason === RendezvousFailureReason.Unknown && this.expiresAt && this.expiresAt.getTime() < Date.now()) {
+ reason = RendezvousFailureReason.Expired;
+ }
+
+ this.cancelled = true;
+ this._ready = false;
+ this.onFailure?.(reason);
+
+ if (this.uri && reason === RendezvousFailureReason.UserDeclined) {
+ try {
+ await this.fetch(this.uri, { method: "DELETE" });
+ } catch (e) {
+ logger.warn(e);
+ }
+ }
+ }
+}
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/transports/index.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/transports/index.ts
new file mode 100644
index 0000000..6d8d642
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/rendezvous/transports/index.ts
@@ -0,0 +1,17 @@
+/*
+Copyright 2022 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.
+*/
+
+export * from "./MSC3886SimpleHttpRendezvousTransport";