summaryrefslogtreecommitdiffstats
path: root/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous
diff options
context:
space:
mode:
Diffstat (limited to 'comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous')
-rw-r--r--comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/MSC3906Rendezvous.js240
-rw-r--r--comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousChannel.js5
-rw-r--r--comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousCode.js5
-rw-r--r--comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousError.js29
-rw-r--r--comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousFailureReason.js36
-rw-r--r--comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousIntent.js27
-rw-r--r--comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousTransport.js5
-rw-r--r--comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/channels/MSC3903ECDHv2RendezvousChannel.js194
-rw-r--r--comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/channels/index.js16
-rw-r--r--comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/index.js82
-rw-r--r--comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/transports/MSC3886SimpleHttpRendezvousTransport.js176
-rw-r--r--comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/transports/index.js16
12 files changed, 831 insertions, 0 deletions
diff --git a/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/MSC3906Rendezvous.js b/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/MSC3906Rendezvous.js
new file mode 100644
index 0000000000..50da6ab883
--- /dev/null
+++ b/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/MSC3906Rendezvous.js
@@ -0,0 +1,240 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.MSC3906Rendezvous = void 0;
+var _matrixEventsSdk = require("matrix-events-sdk");
+var _ = require(".");
+var _client = require("../client");
+var _feature = require("../feature");
+var _logger = require("../logger");
+var _utils = require("../utils");
+function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
+function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } /*
+ 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.
+ */
+var PayloadType = /*#__PURE__*/function (PayloadType) {
+ PayloadType["Start"] = "m.login.start";
+ PayloadType["Finish"] = "m.login.finish";
+ PayloadType["Progress"] = "m.login.progress";
+ return PayloadType;
+}(PayloadType || {});
+var Outcome = /*#__PURE__*/function (Outcome) {
+ Outcome["Success"] = "success";
+ Outcome["Failure"] = "failure";
+ Outcome["Verified"] = "verified";
+ Outcome["Declined"] = "declined";
+ Outcome["Unsupported"] = "unsupported";
+ return Outcome;
+}(Outcome || {});
+const LOGIN_TOKEN_PROTOCOL = new _matrixEventsSdk.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.
+ */
+class MSC3906Rendezvous {
+ /**
+ * @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
+ */
+ constructor(channel, client, onFailure) {
+ this.channel = channel;
+ this.client = client;
+ this.onFailure = onFailure;
+ _defineProperty(this, "newDeviceId", void 0);
+ _defineProperty(this, "newDeviceKey", void 0);
+ _defineProperty(this, "ourIntent", _.RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE);
+ _defineProperty(this, "_code", void 0);
+ }
+
+ /**
+ * Returns the code representing the rendezvous suitable for rendering in a QR code or undefined if not generated yet.
+ */
+ get code() {
+ return this._code;
+ }
+
+ /**
+ * Generate the code including doing partial set up of the channel where required.
+ */
+ async generateCode() {
+ if (this._code) {
+ return;
+ }
+ this._code = JSON.stringify(await this.channel.generateCode(this.ourIntent));
+ }
+ async startAfterShowingCode() {
+ const checksum = await this.channel.connect();
+ _logger.logger.info(`Connected to secure channel with checksum: ${checksum} our intent is ${this.ourIntent}`);
+
+ // in r1 of MSC3882 the availability is exposed as a capability
+ const capabilities = await this.client.getCapabilities();
+ // in r0 of MSC3882 the availability is exposed as a feature flag
+ const features = await (0, _feature.buildFeatureSupportMap)(await this.client.getVersions());
+ const capability = _client.UNSTABLE_MSC3882_CAPABILITY.findIn(capabilities);
+
+ // determine available protocols
+ if (!capability?.enabled && features.get(_feature.Feature.LoginTokenRequest) === _feature.ServerSupport.Unsupported) {
+ _logger.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.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;
+ }
+ async receive() {
+ return await this.channel.receive();
+ }
+ async send(payload) {
+ await this.channel.send(payload);
+ }
+ async declineLoginOnExistingDevice() {
+ _logger.logger.info("User declined sign in");
+ await this.send({
+ type: PayloadType.Finish,
+ outcome: Outcome.Declined
+ });
+ }
+ async approveLoginOnExistingDevice(loginToken) {
+ // eslint-disable-next-line camelcase
+ await this.send({
+ type: PayloadType.Progress,
+ login_token: loginToken,
+ homeserver: this.client.baseUrl
+ });
+ _logger.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;
+ }
+ async verifyAndCrossSignDevice(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.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
+ */
+ async verifyNewDeviceOnExistingDevice(timeout = 10 * 1000) {
+ if (!this.newDeviceId) {
+ throw new Error("No new device to sign");
+ }
+ if (!this.newDeviceKey) {
+ _logger.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.logger.info("Going to wait for new device to be online");
+ await (0, _utils.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");
+ }
+ async cancel(reason) {
+ this.onFailure?.(reason);
+ await this.channel.cancel(reason);
+ }
+ async close() {
+ await this.channel.close();
+ }
+}
+exports.MSC3906Rendezvous = MSC3906Rendezvous; \ No newline at end of file
diff --git a/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousChannel.js b/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousChannel.js
new file mode 100644
index 0000000000..430afc16cd
--- /dev/null
+++ b/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousChannel.js
@@ -0,0 +1,5 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+}); \ No newline at end of file
diff --git a/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousCode.js b/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousCode.js
new file mode 100644
index 0000000000..430afc16cd
--- /dev/null
+++ b/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousCode.js
@@ -0,0 +1,5 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+}); \ No newline at end of file
diff --git a/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousError.js b/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousError.js
new file mode 100644
index 0000000000..5190ebbb76
--- /dev/null
+++ b/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousError.js
@@ -0,0 +1,29 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.RendezvousError = void 0;
+/*
+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.
+*/
+
+class RendezvousError extends Error {
+ constructor(message, code) {
+ super(message);
+ this.code = code;
+ }
+}
+exports.RendezvousError = RendezvousError; \ No newline at end of file
diff --git a/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousFailureReason.js b/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousFailureReason.js
new file mode 100644
index 0000000000..ee6a9b987f
--- /dev/null
+++ b/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousFailureReason.js
@@ -0,0 +1,36 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.RendezvousFailureReason = void 0;
+/*
+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.
+*/
+let RendezvousFailureReason = /*#__PURE__*/function (RendezvousFailureReason) {
+ RendezvousFailureReason["UserDeclined"] = "user_declined";
+ RendezvousFailureReason["OtherDeviceNotSignedIn"] = "other_device_not_signed_in";
+ RendezvousFailureReason["OtherDeviceAlreadySignedIn"] = "other_device_already_signed_in";
+ RendezvousFailureReason["Unknown"] = "unknown";
+ RendezvousFailureReason["Expired"] = "expired";
+ RendezvousFailureReason["UserCancelled"] = "user_cancelled";
+ RendezvousFailureReason["InvalidCode"] = "invalid_code";
+ RendezvousFailureReason["UnsupportedAlgorithm"] = "unsupported_algorithm";
+ RendezvousFailureReason["DataMismatch"] = "data_mismatch";
+ RendezvousFailureReason["UnsupportedTransport"] = "unsupported_transport";
+ RendezvousFailureReason["HomeserverLacksSupport"] = "homeserver_lacks_support";
+ return RendezvousFailureReason;
+}({});
+exports.RendezvousFailureReason = RendezvousFailureReason; \ No newline at end of file
diff --git a/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousIntent.js b/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousIntent.js
new file mode 100644
index 0000000000..5393ac57b9
--- /dev/null
+++ b/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousIntent.js
@@ -0,0 +1,27 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.RendezvousIntent = void 0;
+/*
+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.
+*/
+let RendezvousIntent = /*#__PURE__*/function (RendezvousIntent) {
+ RendezvousIntent["LOGIN_ON_NEW_DEVICE"] = "login.start";
+ RendezvousIntent["RECIPROCATE_LOGIN_ON_EXISTING_DEVICE"] = "login.reciprocate";
+ return RendezvousIntent;
+}({});
+exports.RendezvousIntent = RendezvousIntent; \ No newline at end of file
diff --git a/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousTransport.js b/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousTransport.js
new file mode 100644
index 0000000000..430afc16cd
--- /dev/null
+++ b/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousTransport.js
@@ -0,0 +1,5 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+}); \ No newline at end of file
diff --git a/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/channels/MSC3903ECDHv2RendezvousChannel.js b/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/channels/MSC3903ECDHv2RendezvousChannel.js
new file mode 100644
index 0000000000..3c9e5793bc
--- /dev/null
+++ b/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/channels/MSC3903ECDHv2RendezvousChannel.js
@@ -0,0 +1,194 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.MSC3903ECDHv2RendezvousChannel = void 0;
+var _ = require("..");
+var _olmlib = require("../../crypto/olmlib");
+var _crypto = require("../../crypto/crypto");
+var _SASDecimal = require("../../crypto/verification/SASDecimal");
+var _NamespacedValue = require("../../NamespacedValue");
+function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
+function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } /*
+ 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.
+ */
+const ECDH_V2 = new _NamespacedValue.UnstableValue("m.rendezvous.v2.curve25519-aes-sha256", "org.matrix.msc3903.rendezvous.v2.curve25519-aes-sha256");
+async function importKey(key) {
+ if (!_crypto.subtleCrypto) {
+ throw new Error("Web Crypto is not available");
+ }
+ const imported = _crypto.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.
+ */
+class MSC3903ECDHv2RendezvousChannel {
+ constructor(transport, theirPublicKey, onFailure) {
+ this.transport = transport;
+ this.theirPublicKey = theirPublicKey;
+ this.onFailure = onFailure;
+ _defineProperty(this, "olmSAS", void 0);
+ _defineProperty(this, "ourPublicKey", void 0);
+ _defineProperty(this, "aesKey", void 0);
+ _defineProperty(this, "connected", false);
+ this.olmSAS = new global.Olm.SAS();
+ this.ourPublicKey = (0, _olmlib.decodeBase64)(this.olmSAS.get_pubkey());
+ }
+ async generateCode(intent) {
+ if (this.transport.ready) {
+ throw new Error("Code already generated");
+ }
+ await this.transport.send({
+ algorithm: ECDH_V2.name
+ });
+ const rendezvous = {
+ rendezvous: {
+ algorithm: ECDH_V2.name,
+ key: (0, _olmlib.encodeUnpaddedBase64)(this.ourPublicKey),
+ transport: await this.transport.details()
+ },
+ intent
+ };
+ return rendezvous;
+ }
+ async connect() {
+ 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;
+ const {
+ key,
+ algorithm
+ } = res;
+ if (!algorithm || !ECDH_V2.matches(algorithm) || !key) {
+ throw new _.RendezvousError("Unsupported algorithm: " + algorithm, _.RendezvousFailureReason.UnsupportedAlgorithm);
+ }
+ this.theirPublicKey = (0, _olmlib.decodeBase64)(key);
+ } else {
+ // send our public key unencrypted
+ await this.transport.send({
+ algorithm: ECDH_V2.name,
+ key: (0, _olmlib.encodeUnpaddedBase64)(this.ourPublicKey)
+ });
+ }
+ this.connected = true;
+ this.olmSAS.set_their_key((0, _olmlib.encodeUnpaddedBase64)(this.theirPublicKey));
+ const initiatorKey = isInitiator ? this.ourPublicKey : this.theirPublicKey;
+ const recipientKey = isInitiator ? this.theirPublicKey : this.ourPublicKey;
+ let aesInfo = ECDH_V2.name;
+ aesInfo += `|${(0, _olmlib.encodeUnpaddedBase64)(initiatorKey)}`;
+ aesInfo += `|${(0, _olmlib.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 (0, _SASDecimal.generateDecimalSas)(Array.from(rawChecksum)).join("-");
+ }
+ async encrypt(data) {
+ if (!_crypto.subtleCrypto) {
+ throw new Error("Web Crypto is not available");
+ }
+ const iv = new Uint8Array(32);
+ _crypto.crypto.getRandomValues(iv);
+ const encodedData = new _crypto.TextEncoder().encode(JSON.stringify(data));
+ const ciphertext = await _crypto.subtleCrypto.encrypt({
+ name: "AES-GCM",
+ iv,
+ tagLength: 128
+ }, this.aesKey, encodedData);
+ return {
+ iv: (0, _olmlib.encodeUnpaddedBase64)(iv),
+ ciphertext: (0, _olmlib.encodeUnpaddedBase64)(ciphertext)
+ };
+ }
+ async send(payload) {
+ 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));
+ }
+ async decrypt({
+ iv,
+ ciphertext
+ }) {
+ if (!ciphertext || !iv) {
+ throw new Error("Missing ciphertext and/or iv");
+ }
+ const ciphertextBytes = (0, _olmlib.decodeBase64)(ciphertext);
+ if (!_crypto.subtleCrypto) {
+ throw new Error("Web Crypto is not available");
+ }
+ const plaintext = await _crypto.subtleCrypto.decrypt({
+ name: "AES-GCM",
+ iv: (0, _olmlib.decodeBase64)(iv),
+ tagLength: 128
+ }, this.aesKey, ciphertextBytes);
+ return JSON.parse(new TextDecoder().decode(new Uint8Array(plaintext)));
+ }
+ async receive() {
+ 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;
+ if (data.ciphertext && data.iv) {
+ return this.decrypt(data);
+ }
+ throw new Error("Data received but no ciphertext");
+ }
+ async close() {
+ if (this.olmSAS) {
+ this.olmSAS.free();
+ this.olmSAS = undefined;
+ }
+ }
+ async cancel(reason) {
+ try {
+ await this.transport.cancel(reason);
+ } finally {
+ await this.close();
+ }
+ }
+}
+exports.MSC3903ECDHv2RendezvousChannel = MSC3903ECDHv2RendezvousChannel; \ No newline at end of file
diff --git a/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/channels/index.js b/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/channels/index.js
new file mode 100644
index 0000000000..e2d30513fd
--- /dev/null
+++ b/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/channels/index.js
@@ -0,0 +1,16 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+var _MSC3903ECDHv2RendezvousChannel = require("./MSC3903ECDHv2RendezvousChannel");
+Object.keys(_MSC3903ECDHv2RendezvousChannel).forEach(function (key) {
+ if (key === "default" || key === "__esModule") return;
+ if (key in exports && exports[key] === _MSC3903ECDHv2RendezvousChannel[key]) return;
+ Object.defineProperty(exports, key, {
+ enumerable: true,
+ get: function () {
+ return _MSC3903ECDHv2RendezvousChannel[key];
+ }
+ });
+}); \ No newline at end of file
diff --git a/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/index.js b/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/index.js
new file mode 100644
index 0000000000..141c9da4fc
--- /dev/null
+++ b/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/index.js
@@ -0,0 +1,82 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+var _MSC3906Rendezvous = require("./MSC3906Rendezvous");
+Object.keys(_MSC3906Rendezvous).forEach(function (key) {
+ if (key === "default" || key === "__esModule") return;
+ if (key in exports && exports[key] === _MSC3906Rendezvous[key]) return;
+ Object.defineProperty(exports, key, {
+ enumerable: true,
+ get: function () {
+ return _MSC3906Rendezvous[key];
+ }
+ });
+});
+var _RendezvousChannel = require("./RendezvousChannel");
+Object.keys(_RendezvousChannel).forEach(function (key) {
+ if (key === "default" || key === "__esModule") return;
+ if (key in exports && exports[key] === _RendezvousChannel[key]) return;
+ Object.defineProperty(exports, key, {
+ enumerable: true,
+ get: function () {
+ return _RendezvousChannel[key];
+ }
+ });
+});
+var _RendezvousCode = require("./RendezvousCode");
+Object.keys(_RendezvousCode).forEach(function (key) {
+ if (key === "default" || key === "__esModule") return;
+ if (key in exports && exports[key] === _RendezvousCode[key]) return;
+ Object.defineProperty(exports, key, {
+ enumerable: true,
+ get: function () {
+ return _RendezvousCode[key];
+ }
+ });
+});
+var _RendezvousError = require("./RendezvousError");
+Object.keys(_RendezvousError).forEach(function (key) {
+ if (key === "default" || key === "__esModule") return;
+ if (key in exports && exports[key] === _RendezvousError[key]) return;
+ Object.defineProperty(exports, key, {
+ enumerable: true,
+ get: function () {
+ return _RendezvousError[key];
+ }
+ });
+});
+var _RendezvousFailureReason = require("./RendezvousFailureReason");
+Object.keys(_RendezvousFailureReason).forEach(function (key) {
+ if (key === "default" || key === "__esModule") return;
+ if (key in exports && exports[key] === _RendezvousFailureReason[key]) return;
+ Object.defineProperty(exports, key, {
+ enumerable: true,
+ get: function () {
+ return _RendezvousFailureReason[key];
+ }
+ });
+});
+var _RendezvousIntent = require("./RendezvousIntent");
+Object.keys(_RendezvousIntent).forEach(function (key) {
+ if (key === "default" || key === "__esModule") return;
+ if (key in exports && exports[key] === _RendezvousIntent[key]) return;
+ Object.defineProperty(exports, key, {
+ enumerable: true,
+ get: function () {
+ return _RendezvousIntent[key];
+ }
+ });
+});
+var _RendezvousTransport = require("./RendezvousTransport");
+Object.keys(_RendezvousTransport).forEach(function (key) {
+ if (key === "default" || key === "__esModule") return;
+ if (key in exports && exports[key] === _RendezvousTransport[key]) return;
+ Object.defineProperty(exports, key, {
+ enumerable: true,
+ get: function () {
+ return _RendezvousTransport[key];
+ }
+ });
+}); \ No newline at end of file
diff --git a/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/transports/MSC3886SimpleHttpRendezvousTransport.js b/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/transports/MSC3886SimpleHttpRendezvousTransport.js
new file mode 100644
index 0000000000..6347229aca
--- /dev/null
+++ b/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/transports/MSC3886SimpleHttpRendezvousTransport.js
@@ -0,0 +1,176 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.MSC3886SimpleHttpRendezvousTransport = void 0;
+var _matrixEventsSdk = require("matrix-events-sdk");
+var _logger = require("../../logger");
+var _utils = require("../../utils");
+var _ = require("..");
+var _httpApi = require("../../http-api");
+function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
+function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } /*
+ 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.
+ */
+const TYPE = new _matrixEventsSdk.UnstableValue("http.v1", "org.matrix.msc3886.http.v1");
+/**
+ * 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.
+ */
+class MSC3886SimpleHttpRendezvousTransport {
+ constructor({
+ onFailure,
+ client,
+ fallbackRzServer,
+ fetchFn
+ }) {
+ _defineProperty(this, "uri", void 0);
+ _defineProperty(this, "etag", void 0);
+ _defineProperty(this, "expiresAt", void 0);
+ _defineProperty(this, "client", void 0);
+ _defineProperty(this, "fallbackRzServer", void 0);
+ _defineProperty(this, "fetchFn", void 0);
+ _defineProperty(this, "cancelled", false);
+ _defineProperty(this, "_ready", false);
+ _defineProperty(this, "onFailure", void 0);
+ this.fetchFn = fetchFn;
+ this.onFailure = onFailure;
+ this.client = client;
+ this.fallbackRzServer = fallbackRzServer;
+ }
+ get ready() {
+ return this._ready;
+ }
+ async details() {
+ if (!this.uri) {
+ throw new Error("Rendezvous not set up");
+ }
+ return {
+ type: TYPE.name,
+ uri: this.uri
+ };
+ }
+ fetch(resource, options) {
+ if (this.fetchFn) {
+ return this.fetchFn(resource, options);
+ }
+ return global.fetch(resource, options);
+ }
+ async getPostEndpoint() {
+ try {
+ if (await this.client.doesServerSupportUnstableFeature("org.matrix.msc3886")) {
+ return `${this.client.baseUrl}${_httpApi.ClientPrefix.Unstable}/org.matrix.msc3886/rendezvous`;
+ }
+ } catch (err) {
+ _logger.logger.warn("Failed to get unstable features", err);
+ }
+ return this.fallbackRzServer;
+ }
+ async send(data) {
+ 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 = {
+ "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;
+ }
+ }
+ async receive() {
+ 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 = {};
+ 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 (0, _utils.sleep)(1000);
+ }
+ }
+ async cancel(reason) {
+ 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.logger.warn(e);
+ }
+ }
+ }
+}
+exports.MSC3886SimpleHttpRendezvousTransport = MSC3886SimpleHttpRendezvousTransport; \ No newline at end of file
diff --git a/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/transports/index.js b/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/transports/index.js
new file mode 100644
index 0000000000..a1a4c56c64
--- /dev/null
+++ b/comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/transports/index.js
@@ -0,0 +1,16 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+var _MSC3886SimpleHttpRendezvousTransport = require("./MSC3886SimpleHttpRendezvousTransport");
+Object.keys(_MSC3886SimpleHttpRendezvousTransport).forEach(function (key) {
+ if (key === "default" || key === "__esModule") return;
+ if (key in exports && exports[key] === _MSC3886SimpleHttpRendezvousTransport[key]) return;
+ Object.defineProperty(exports, key, {
+ enumerable: true,
+ get: function () {
+ return _MSC3886SimpleHttpRendezvousTransport[key];
+ }
+ });
+}); \ No newline at end of file