diff options
Diffstat (limited to 'comm/chat/protocols/matrix/lib/matrix-sdk/crypto/SecretSharing.js')
-rw-r--r-- | comm/chat/protocols/matrix/lib/matrix-sdk/crypto/SecretSharing.js | 199 |
1 files changed, 199 insertions, 0 deletions
diff --git a/comm/chat/protocols/matrix/lib/matrix-sdk/crypto/SecretSharing.js b/comm/chat/protocols/matrix/lib/matrix-sdk/crypto/SecretSharing.js new file mode 100644 index 0000000000..805fd64471 --- /dev/null +++ b/comm/chat/protocols/matrix/lib/matrix-sdk/crypto/SecretSharing.js @@ -0,0 +1,199 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.SecretSharing = void 0; +var _uuid = require("uuid"); +var _utils = require("../utils"); +var _event = require("../@types/event"); +var _logger = require("../logger"); +var olmlib = _interopRequireWildcard(require("./olmlib")); +function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } +function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } +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 2019-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. + */ +class SecretSharing { + constructor(baseApis, cryptoCallbacks) { + this.baseApis = baseApis; + this.cryptoCallbacks = cryptoCallbacks; + _defineProperty(this, "requests", new Map()); + } + + /** + * Request a secret from another device + * + * @param name - the name of the secret to request + * @param devices - the devices to request the secret from + */ + request(name, devices) { + const requestId = this.baseApis.makeTxnId(); + const deferred = (0, _utils.defer)(); + this.requests.set(requestId, { + name, + devices, + deferred + }); + const cancel = reason => { + // send cancellation event + const cancelData = { + action: "request_cancellation", + requesting_device_id: this.baseApis.deviceId, + request_id: requestId + }; + const toDevice = new Map(); + for (const device of devices) { + toDevice.set(device, cancelData); + } + this.baseApis.sendToDevice("m.secret.request", new Map([[this.baseApis.getUserId(), toDevice]])); + + // and reject the promise so that anyone waiting on it will be + // notified + deferred.reject(new Error(reason || "Cancelled")); + }; + + // send request to devices + const requestData = { + name, + action: "request", + requesting_device_id: this.baseApis.deviceId, + request_id: requestId, + [_event.ToDeviceMessageId]: (0, _uuid.v4)() + }; + const toDevice = new Map(); + for (const device of devices) { + toDevice.set(device, requestData); + } + _logger.logger.info(`Request secret ${name} from ${devices}, id ${requestId}`); + this.baseApis.sendToDevice("m.secret.request", new Map([[this.baseApis.getUserId(), toDevice]])); + return { + requestId, + promise: deferred.promise, + cancel + }; + } + async onRequestReceived(event) { + const sender = event.getSender(); + const content = event.getContent(); + if (sender !== this.baseApis.getUserId() || !(content.name && content.action && content.requesting_device_id && content.request_id)) { + // ignore requests from anyone else, for now + return; + } + const deviceId = content.requesting_device_id; + // check if it's a cancel + if (content.action === "request_cancellation") { + /* + Looks like we intended to emit events when we got cancelations, but + we never put anything in the _incomingRequests object, and the request + itself doesn't use events anyway so if we were to wire up cancellations, + they probably ought to use the same callback interface. I'm leaving them + disabled for now while converting this file to typescript. + if (this._incomingRequests[deviceId] + && this._incomingRequests[deviceId][content.request_id]) { + logger.info( + "received request cancellation for secret (" + sender + + ", " + deviceId + ", " + content.request_id + ")", + ); + this.baseApis.emit("crypto.secrets.requestCancelled", { + user_id: sender, + device_id: deviceId, + request_id: content.request_id, + }); + } + */ + } else if (content.action === "request") { + if (deviceId === this.baseApis.deviceId) { + // no point in trying to send ourself the secret + return; + } + + // check if we have the secret + _logger.logger.info("received request for secret (" + sender + ", " + deviceId + ", " + content.request_id + ")"); + if (!this.cryptoCallbacks.onSecretRequested) { + return; + } + const secret = await this.cryptoCallbacks.onSecretRequested(sender, deviceId, content.request_id, content.name, this.baseApis.checkDeviceTrust(sender, deviceId)); + if (secret) { + _logger.logger.info(`Preparing ${content.name} secret for ${deviceId}`); + const payload = { + type: "m.secret.send", + content: { + request_id: content.request_id, + secret: secret + } + }; + const encryptedContent = { + algorithm: olmlib.OLM_ALGORITHM, + sender_key: this.baseApis.crypto.olmDevice.deviceCurve25519Key, + ciphertext: {}, + [_event.ToDeviceMessageId]: (0, _uuid.v4)() + }; + await olmlib.ensureOlmSessionsForDevices(this.baseApis.crypto.olmDevice, this.baseApis, new Map([[sender, [this.baseApis.getStoredDevice(sender, deviceId)]]])); + await olmlib.encryptMessageForDevice(encryptedContent.ciphertext, this.baseApis.getUserId(), this.baseApis.deviceId, this.baseApis.crypto.olmDevice, sender, this.baseApis.getStoredDevice(sender, deviceId), payload); + const contentMap = new Map([[sender, new Map([[deviceId, encryptedContent]])]]); + _logger.logger.info(`Sending ${content.name} secret for ${deviceId}`); + this.baseApis.sendToDevice("m.room.encrypted", contentMap); + } else { + _logger.logger.info(`Request denied for ${content.name} secret for ${deviceId}`); + } + } + } + onSecretReceived(event) { + if (event.getSender() !== this.baseApis.getUserId()) { + // we shouldn't be receiving secrets from anyone else, so ignore + // because someone could be trying to send us bogus data + return; + } + if (!olmlib.isOlmEncrypted(event)) { + _logger.logger.error("secret event not properly encrypted"); + return; + } + const content = event.getContent(); + const senderKeyUser = this.baseApis.crypto.deviceList.getUserByIdentityKey(olmlib.OLM_ALGORITHM, event.getSenderKey() || ""); + if (senderKeyUser !== event.getSender()) { + _logger.logger.error("sending device does not belong to the user it claims to be from"); + return; + } + _logger.logger.log("got secret share for request", content.request_id); + const requestControl = this.requests.get(content.request_id); + if (requestControl) { + // make sure that the device that sent it is one of the devices that + // we requested from + const deviceInfo = this.baseApis.crypto.deviceList.getDeviceByIdentityKey(olmlib.OLM_ALGORITHM, event.getSenderKey()); + if (!deviceInfo) { + _logger.logger.log("secret share from unknown device with key", event.getSenderKey()); + return; + } + if (!requestControl.devices.includes(deviceInfo.deviceId)) { + _logger.logger.log("unsolicited secret share from device", deviceInfo.deviceId); + return; + } + // unsure that the sender is trusted. In theory, this check is + // unnecessary since we only accept secret shares from devices that + // we requested from, but it doesn't hurt. + const deviceTrust = this.baseApis.crypto.checkDeviceInfoTrust(event.getSender(), deviceInfo); + if (!deviceTrust.isVerified()) { + _logger.logger.log("secret share from unverified device"); + return; + } + _logger.logger.log(`Successfully received secret ${requestControl.name} ` + `from ${deviceInfo.deviceId}`); + requestControl.deferred.resolve(content.secret); + } + } +} +exports.SecretSharing = SecretSharing;
\ No newline at end of file |