diff options
Diffstat (limited to 'includes/external/matrix/node_modules/matrix-js-sdk/lib/crypto/index.js')
-rw-r--r-- | includes/external/matrix/node_modules/matrix-js-sdk/lib/crypto/index.js | 3290 |
1 files changed, 0 insertions, 3290 deletions
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/crypto/index.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/crypto/index.js deleted file mode 100644 index d7d8551..0000000 --- a/includes/external/matrix/node_modules/matrix-js-sdk/lib/crypto/index.js +++ /dev/null @@ -1,3290 +0,0 @@ -"use strict"; - -var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.IncomingRoomKeyRequest = exports.CryptoEvent = exports.Crypto = void 0; -exports.fixBackupKey = fixBackupKey; -exports.isCryptoAvailable = isCryptoAvailable; -exports.verificationMethods = void 0; -var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); -var _anotherJson = _interopRequireDefault(require("another-json")); -var _uuid = require("uuid"); -var _event = require("../@types/event"); -var _ReEmitter = require("../ReEmitter"); -var _logger = require("../logger"); -var _OlmDevice = require("./OlmDevice"); -var olmlib = _interopRequireWildcard(require("./olmlib")); -var _DeviceList = require("./DeviceList"); -var _deviceinfo = require("./deviceinfo"); -var algorithms = _interopRequireWildcard(require("./algorithms")); -var _CrossSigning = require("./CrossSigning"); -var _EncryptionSetup = require("./EncryptionSetup"); -var _SecretStorage = require("./SecretStorage"); -var _OutgoingRoomKeyRequestManager = require("./OutgoingRoomKeyRequestManager"); -var _indexeddbCryptoStore = require("./store/indexeddb-crypto-store"); -var _QRCode = require("./verification/QRCode"); -var _SAS = require("./verification/SAS"); -var _key_passphrase = require("./key_passphrase"); -var _recoverykey = require("./recoverykey"); -var _VerificationRequest = require("./verification/request/VerificationRequest"); -var _InRoomChannel = require("./verification/request/InRoomChannel"); -var _ToDeviceChannel = require("./verification/request/ToDeviceChannel"); -var _IllegalMethod = require("./verification/IllegalMethod"); -var _errors = require("../errors"); -var _aes = require("./aes"); -var _dehydration = require("./dehydration"); -var _backup = require("./backup"); -var _room = require("../models/room"); -var _roomMember = require("../models/room-member"); -var _event2 = require("../models/event"); -var _client = require("../client"); -var _typedEventEmitter = require("../models/typed-event-emitter"); -var _roomState = require("../models/room-state"); -var _utils = require("../utils"); -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 ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } -function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } -const DeviceVerification = _deviceinfo.DeviceInfo.DeviceVerification; -const defaultVerificationMethods = { - [_QRCode.ReciprocateQRCode.NAME]: _QRCode.ReciprocateQRCode, - [_SAS.SAS.NAME]: _SAS.SAS, - // These two can't be used for actual verification, but we do - // need to be able to define them here for the verification flows - // to start. - [_QRCode.SHOW_QR_CODE_METHOD]: _IllegalMethod.IllegalMethod, - [_QRCode.SCAN_QR_CODE_METHOD]: _IllegalMethod.IllegalMethod -}; - -/** - * verification method names - */ -// legacy export identifier -const verificationMethods = { - RECIPROCATE_QR_CODE: _QRCode.ReciprocateQRCode.NAME, - SAS: _SAS.SAS.NAME -}; -exports.verificationMethods = verificationMethods; -function isCryptoAvailable() { - return Boolean(global.Olm); -} -const MIN_FORCE_SESSION_INTERVAL_MS = 60 * 60 * 1000; -let CryptoEvent; -exports.CryptoEvent = CryptoEvent; -(function (CryptoEvent) { - CryptoEvent["DeviceVerificationChanged"] = "deviceVerificationChanged"; - CryptoEvent["UserTrustStatusChanged"] = "userTrustStatusChanged"; - CryptoEvent["UserCrossSigningUpdated"] = "userCrossSigningUpdated"; - CryptoEvent["RoomKeyRequest"] = "crypto.roomKeyRequest"; - CryptoEvent["RoomKeyRequestCancellation"] = "crypto.roomKeyRequestCancellation"; - CryptoEvent["KeyBackupStatus"] = "crypto.keyBackupStatus"; - CryptoEvent["KeyBackupFailed"] = "crypto.keyBackupFailed"; - CryptoEvent["KeyBackupSessionsRemaining"] = "crypto.keyBackupSessionsRemaining"; - CryptoEvent["KeySignatureUploadFailure"] = "crypto.keySignatureUploadFailure"; - CryptoEvent["VerificationRequest"] = "crypto.verification.request"; - CryptoEvent["Warning"] = "crypto.warning"; - CryptoEvent["WillUpdateDevices"] = "crypto.willUpdateDevices"; - CryptoEvent["DevicesUpdated"] = "crypto.devicesUpdated"; - CryptoEvent["KeysChanged"] = "crossSigning.keysChanged"; -})(CryptoEvent || (exports.CryptoEvent = CryptoEvent = {})); -class Crypto extends _typedEventEmitter.TypedEventEmitter { - /** - * @returns The version of Olm. - */ - static getOlmVersion() { - return _OlmDevice.OlmDevice.getOlmVersion(); - } - /** - * Cryptography bits - * - * This module is internal to the js-sdk; the public API is via MatrixClient. - * - * @internal - * - * @param baseApis - base matrix api interface - * - * @param userId - The user ID for the local user - * - * @param deviceId - The identifier for this device. - * - * @param clientStore - the MatrixClient data store. - * - * @param cryptoStore - storage for the crypto layer. - * - * @param roomList - An initialised RoomList object - * - * @param verificationMethods - Array of verification methods to use. - * Each element can either be a string from MatrixClient.verificationMethods - * or a class that implements a verification method. - */ - constructor(baseApis, userId, deviceId, clientStore, cryptoStore, roomList, verificationMethods) { - super(); - this.baseApis = baseApis; - this.userId = userId; - this.deviceId = deviceId; - this.clientStore = clientStore; - this.cryptoStore = cryptoStore; - this.roomList = roomList; - (0, _defineProperty2.default)(this, "backupManager", void 0); - (0, _defineProperty2.default)(this, "crossSigningInfo", void 0); - (0, _defineProperty2.default)(this, "olmDevice", void 0); - (0, _defineProperty2.default)(this, "deviceList", void 0); - (0, _defineProperty2.default)(this, "dehydrationManager", void 0); - (0, _defineProperty2.default)(this, "secretStorage", void 0); - (0, _defineProperty2.default)(this, "reEmitter", void 0); - (0, _defineProperty2.default)(this, "verificationMethods", void 0); - (0, _defineProperty2.default)(this, "supportedAlgorithms", void 0); - (0, _defineProperty2.default)(this, "outgoingRoomKeyRequestManager", void 0); - (0, _defineProperty2.default)(this, "toDeviceVerificationRequests", void 0); - (0, _defineProperty2.default)(this, "inRoomVerificationRequests", void 0); - (0, _defineProperty2.default)(this, "trustCrossSignedDevices", true); - (0, _defineProperty2.default)(this, "lastOneTimeKeyCheck", null); - (0, _defineProperty2.default)(this, "oneTimeKeyCheckInProgress", false); - (0, _defineProperty2.default)(this, "roomEncryptors", new Map()); - (0, _defineProperty2.default)(this, "roomDecryptors", new Map()); - (0, _defineProperty2.default)(this, "deviceKeys", {}); - (0, _defineProperty2.default)(this, "globalBlacklistUnverifiedDevices", false); - (0, _defineProperty2.default)(this, "globalErrorOnUnknownDevices", true); - (0, _defineProperty2.default)(this, "receivedRoomKeyRequests", []); - (0, _defineProperty2.default)(this, "receivedRoomKeyRequestCancellations", []); - (0, _defineProperty2.default)(this, "processingRoomKeyRequests", false); - (0, _defineProperty2.default)(this, "lazyLoadMembers", false); - (0, _defineProperty2.default)(this, "roomDeviceTrackingState", {}); - (0, _defineProperty2.default)(this, "lastNewSessionForced", new _utils.MapWithDefault(() => new _utils.MapWithDefault(() => 0))); - (0, _defineProperty2.default)(this, "sendKeyRequestsImmediately", false); - (0, _defineProperty2.default)(this, "oneTimeKeyCount", void 0); - (0, _defineProperty2.default)(this, "needsNewFallback", void 0); - (0, _defineProperty2.default)(this, "fallbackCleanup", void 0); - (0, _defineProperty2.default)(this, "onDeviceListUserCrossSigningUpdated", async userId => { - if (userId === this.userId) { - // An update to our own cross-signing key. - // Get the new key first: - const newCrossSigning = this.deviceList.getStoredCrossSigningForUser(userId); - const seenPubkey = newCrossSigning ? newCrossSigning.getId() : null; - const currentPubkey = this.crossSigningInfo.getId(); - const changed = currentPubkey !== seenPubkey; - if (currentPubkey && seenPubkey && !changed) { - // If it's not changed, just make sure everything is up to date - await this.checkOwnCrossSigningTrust(); - } else { - // We'll now be in a state where cross-signing on the account is not trusted - // because our locally stored cross-signing keys will not match the ones - // on the server for our account. So we clear our own stored cross-signing keys, - // effectively disabling cross-signing until the user gets verified by the device - // that reset the keys - this.storeTrustedSelfKeys(null); - // emit cross-signing has been disabled - this.emit(CryptoEvent.KeysChanged, {}); - // as the trust for our own user has changed, - // also emit an event for this - this.emit(CryptoEvent.UserTrustStatusChanged, this.userId, this.checkUserTrust(userId)); - } - } else { - await this.checkDeviceVerifications(userId); - - // Update verified before latch using the current state and save the new - // latch value in the device list store. - const crossSigning = this.deviceList.getStoredCrossSigningForUser(userId); - if (crossSigning) { - crossSigning.updateCrossSigningVerifiedBefore(this.checkUserTrust(userId).isCrossSigningVerified()); - this.deviceList.setRawStoredCrossSigningForUser(userId, crossSigning.toStorage()); - } - this.emit(CryptoEvent.UserTrustStatusChanged, userId, this.checkUserTrust(userId)); - } - }); - (0, _defineProperty2.default)(this, "onMembership", (event, member, oldMembership) => { - try { - this.onRoomMembership(event, member, oldMembership); - } catch (e) { - _logger.logger.error("Error handling membership change:", e); - } - }); - (0, _defineProperty2.default)(this, "onToDeviceEvent", event => { - try { - _logger.logger.log(`received to-device ${event.getType()} from: ` + `${event.getSender()} id: ${event.getContent()[_event.ToDeviceMessageId]}`); - if (event.getType() == "m.room_key" || event.getType() == "m.forwarded_room_key") { - this.onRoomKeyEvent(event); - } else if (event.getType() == "m.room_key_request") { - this.onRoomKeyRequestEvent(event); - } else if (event.getType() === "m.secret.request") { - this.secretStorage.onRequestReceived(event); - } else if (event.getType() === "m.secret.send") { - this.secretStorage.onSecretReceived(event); - } else if (event.getType() === "m.room_key.withheld") { - this.onRoomKeyWithheldEvent(event); - } else if (event.getContent().transaction_id) { - this.onKeyVerificationMessage(event); - } else if (event.getContent().msgtype === "m.bad.encrypted") { - this.onToDeviceBadEncrypted(event); - } else if (event.isBeingDecrypted() || event.shouldAttemptDecryption()) { - if (!event.isBeingDecrypted()) { - event.attemptDecryption(this); - } - // once the event has been decrypted, try again - event.once(_event2.MatrixEventEvent.Decrypted, ev => { - this.onToDeviceEvent(ev); - }); - } - } catch (e) { - _logger.logger.error("Error handling toDeviceEvent:", e); - } - }); - (0, _defineProperty2.default)(this, "onTimelineEvent", (event, room, atStart, removed, { - liveEvent = true - } = {}) => { - if (!_InRoomChannel.InRoomChannel.validateEvent(event, this.baseApis)) { - return; - } - const createRequest = event => { - const channel = new _InRoomChannel.InRoomChannel(this.baseApis, event.getRoomId()); - return new _VerificationRequest.VerificationRequest(channel, this.verificationMethods, this.baseApis); - }; - this.handleVerificationEvent(event, this.inRoomVerificationRequests, createRequest, liveEvent); - }); - this.reEmitter = new _ReEmitter.TypedReEmitter(this); - if (verificationMethods) { - this.verificationMethods = new Map(); - for (const method of verificationMethods) { - if (typeof method === "string") { - if (defaultVerificationMethods[method]) { - this.verificationMethods.set(method, defaultVerificationMethods[method]); - } - } else if (method["NAME"]) { - this.verificationMethods.set(method["NAME"], method); - } else { - _logger.logger.warn(`Excluding unknown verification method ${method}`); - } - } - } else { - this.verificationMethods = new Map(Object.entries(defaultVerificationMethods)); - } - this.backupManager = new _backup.BackupManager(baseApis, async () => { - // try to get key from cache - const cachedKey = await this.getSessionBackupPrivateKey(); - if (cachedKey) { - return cachedKey; - } - - // try to get key from secret storage - const storedKey = await this.getSecret("m.megolm_backup.v1"); - if (storedKey) { - // ensure that the key is in the right format. If not, fix the key and - // store the fixed version - const fixedKey = fixBackupKey(storedKey); - if (fixedKey) { - const keys = await this.getSecretStorageKey(); - await this.storeSecret("m.megolm_backup.v1", fixedKey, [keys[0]]); - } - return olmlib.decodeBase64(fixedKey || storedKey); - } - - // try to get key from app - if (this.baseApis.cryptoCallbacks && this.baseApis.cryptoCallbacks.getBackupKey) { - return this.baseApis.cryptoCallbacks.getBackupKey(); - } - throw new Error("Unable to get private key"); - }); - this.olmDevice = new _OlmDevice.OlmDevice(cryptoStore); - this.deviceList = new _DeviceList.DeviceList(baseApis, cryptoStore, this.olmDevice); - - // XXX: This isn't removed at any point, but then none of the event listeners - // this class sets seem to be removed at any point... :/ - this.deviceList.on(CryptoEvent.UserCrossSigningUpdated, this.onDeviceListUserCrossSigningUpdated); - this.reEmitter.reEmit(this.deviceList, [CryptoEvent.DevicesUpdated, CryptoEvent.WillUpdateDevices]); - this.supportedAlgorithms = Array.from(algorithms.DECRYPTION_CLASSES.keys()); - this.outgoingRoomKeyRequestManager = new _OutgoingRoomKeyRequestManager.OutgoingRoomKeyRequestManager(baseApis, this.deviceId, this.cryptoStore); - this.toDeviceVerificationRequests = new _ToDeviceChannel.ToDeviceRequests(); - this.inRoomVerificationRequests = new _InRoomChannel.InRoomRequests(); - const cryptoCallbacks = this.baseApis.cryptoCallbacks || {}; - const cacheCallbacks = (0, _CrossSigning.createCryptoStoreCacheCallbacks)(cryptoStore, this.olmDevice); - this.crossSigningInfo = new _CrossSigning.CrossSigningInfo(userId, cryptoCallbacks, cacheCallbacks); - // Yes, we pass the client twice here: see SecretStorage - this.secretStorage = new _SecretStorage.SecretStorage(baseApis, cryptoCallbacks, baseApis); - this.dehydrationManager = new _dehydration.DehydrationManager(this); - - // Assuming no app-supplied callback, default to getting from SSSS. - if (!cryptoCallbacks.getCrossSigningKey && cryptoCallbacks.getSecretStorageKey) { - cryptoCallbacks.getCrossSigningKey = async type => { - return _CrossSigning.CrossSigningInfo.getFromSecretStorage(type, this.secretStorage); - }; - } - } - - /** - * Initialise the crypto module so that it is ready for use - * - * Returns a promise which resolves once the crypto module is ready for use. - * - * @param exportedOlmDevice - (Optional) data from exported device - * that must be re-created. - */ - async init({ - exportedOlmDevice, - pickleKey - } = {}) { - _logger.logger.log("Crypto: initialising Olm..."); - await global.Olm.init(); - _logger.logger.log(exportedOlmDevice ? "Crypto: initialising Olm device from exported device..." : "Crypto: initialising Olm device..."); - await this.olmDevice.init({ - fromExportedDevice: exportedOlmDevice, - pickleKey - }); - _logger.logger.log("Crypto: loading device list..."); - await this.deviceList.load(); - - // build our device keys: these will later be uploaded - this.deviceKeys["ed25519:" + this.deviceId] = this.olmDevice.deviceEd25519Key; - this.deviceKeys["curve25519:" + this.deviceId] = this.olmDevice.deviceCurve25519Key; - _logger.logger.log("Crypto: fetching own devices..."); - let myDevices = this.deviceList.getRawStoredDevicesForUser(this.userId); - if (!myDevices) { - myDevices = {}; - } - if (!myDevices[this.deviceId]) { - // add our own deviceinfo to the cryptoStore - _logger.logger.log("Crypto: adding this device to the store..."); - const deviceInfo = { - keys: this.deviceKeys, - algorithms: this.supportedAlgorithms, - verified: DeviceVerification.VERIFIED, - known: true - }; - myDevices[this.deviceId] = deviceInfo; - this.deviceList.storeDevicesForUser(this.userId, myDevices); - this.deviceList.saveIfDirty(); - } - await this.cryptoStore.doTxn("readonly", [_indexeddbCryptoStore.IndexedDBCryptoStore.STORE_ACCOUNT], txn => { - this.cryptoStore.getCrossSigningKeys(txn, keys => { - // can be an empty object after resetting cross-signing keys, see storeTrustedSelfKeys - if (keys && Object.keys(keys).length !== 0) { - _logger.logger.log("Loaded cross-signing public keys from crypto store"); - this.crossSigningInfo.setKeys(keys); - } - }); - }); - // make sure we are keeping track of our own devices - // (this is important for key backups & things) - this.deviceList.startTrackingDeviceList(this.userId); - _logger.logger.log("Crypto: checking for key backup..."); - this.backupManager.checkAndStart(); - } - - /** - * Whether to trust a others users signatures of their devices. - * If false, devices will only be considered 'verified' if we have - * verified that device individually (effectively disabling cross-signing). - * - * Default: true - * - * @returns True if trusting cross-signed devices - */ - getCryptoTrustCrossSignedDevices() { - return this.trustCrossSignedDevices; - } - - /** - * See getCryptoTrustCrossSignedDevices - * This may be set before initCrypto() is called to ensure no races occur. - * - * @param val - True to trust cross-signed devices - */ - setCryptoTrustCrossSignedDevices(val) { - this.trustCrossSignedDevices = val; - for (const userId of this.deviceList.getKnownUserIds()) { - const devices = this.deviceList.getRawStoredDevicesForUser(userId); - for (const deviceId of Object.keys(devices)) { - const deviceTrust = this.checkDeviceTrust(userId, deviceId); - // If the device is locally verified then isVerified() is always true, - // so this will only have caused the value to change if the device is - // cross-signing verified but not locally verified - if (!deviceTrust.isLocallyVerified() && deviceTrust.isCrossSigningVerified()) { - const deviceObj = this.deviceList.getStoredDevice(userId, deviceId); - this.emit(CryptoEvent.DeviceVerificationChanged, userId, deviceId, deviceObj); - } - } - } - } - - /** - * Create a recovery key from a user-supplied passphrase. - * - * @param password - Passphrase string that can be entered by the user - * when restoring the backup as an alternative to entering the recovery key. - * Optional. - * @returns Object with public key metadata, encoded private - * recovery key which should be disposed of after displaying to the user, - * and raw private key to avoid round tripping if needed. - */ - async createRecoveryKeyFromPassphrase(password) { - const decryption = new global.Olm.PkDecryption(); - try { - const keyInfo = {}; - if (password) { - const derivation = await (0, _key_passphrase.keyFromPassphrase)(password); - keyInfo.passphrase = { - algorithm: "m.pbkdf2", - iterations: derivation.iterations, - salt: derivation.salt - }; - keyInfo.pubkey = decryption.init_with_private_key(derivation.key); - } else { - keyInfo.pubkey = decryption.generate_key(); - } - const privateKey = decryption.get_private_key(); - const encodedPrivateKey = (0, _recoverykey.encodeRecoveryKey)(privateKey); - return { - keyInfo: keyInfo, - encodedPrivateKey, - privateKey - }; - } finally { - decryption === null || decryption === void 0 ? void 0 : decryption.free(); - } - } - - /** - * Checks if the user has previously published cross-signing keys - * - * This means downloading the devicelist for the user and checking if the list includes - * the cross-signing pseudo-device. - * - * @internal - */ - async userHasCrossSigningKeys() { - await this.downloadKeys([this.userId]); - return this.deviceList.getStoredCrossSigningForUser(this.userId) !== null; - } - - /** - * Checks whether cross signing: - * - is enabled on this account and trusted by this device - * - has private keys either cached locally or stored in secret storage - * - * If this function returns false, bootstrapCrossSigning() can be used - * to fix things such that it returns true. That is to say, after - * bootstrapCrossSigning() completes successfully, this function should - * return true. - * - * The cross-signing API is currently UNSTABLE and may change without notice. - * - * @returns True if cross-signing is ready to be used on this device - */ - async isCrossSigningReady() { - const publicKeysOnDevice = this.crossSigningInfo.getId(); - const privateKeysExistSomewhere = (await this.crossSigningInfo.isStoredInKeyCache()) || (await this.crossSigningInfo.isStoredInSecretStorage(this.secretStorage)); - return !!(publicKeysOnDevice && privateKeysExistSomewhere); - } - - /** - * Checks whether secret storage: - * - is enabled on this account - * - is storing cross-signing private keys - * - is storing session backup key (if enabled) - * - * If this function returns false, bootstrapSecretStorage() can be used - * to fix things such that it returns true. That is to say, after - * bootstrapSecretStorage() completes successfully, this function should - * return true. - * - * The Secure Secret Storage API is currently UNSTABLE and may change without notice. - * - * @returns True if secret storage is ready to be used on this device - */ - async isSecretStorageReady() { - const secretStorageKeyInAccount = await this.secretStorage.hasKey(); - const privateKeysInStorage = await this.crossSigningInfo.isStoredInSecretStorage(this.secretStorage); - const sessionBackupInStorage = !this.backupManager.getKeyBackupEnabled() || (await this.baseApis.isKeyBackupKeyStored()); - return !!(secretStorageKeyInAccount && privateKeysInStorage && sessionBackupInStorage); - } - - /** - * Bootstrap cross-signing by creating keys if needed. If everything is already - * set up, then no changes are made, so this is safe to run to ensure - * cross-signing is ready for use. - * - * This function: - * - creates new cross-signing keys if they are not found locally cached nor in - * secret storage (if it has been setup) - * - * The cross-signing API is currently UNSTABLE and may change without notice. - * - * @param authUploadDeviceSigningKeys - Function - * called to await an interactive auth flow when uploading device signing keys. - * @param setupNewCrossSigning - Optional. Reset even if keys - * already exist. - * Args: - * A function that makes the request requiring auth. Receives the - * auth data as an object. Can be called multiple times, first with an empty - * authDict, to obtain the flows. - */ - async bootstrapCrossSigning({ - authUploadDeviceSigningKeys, - setupNewCrossSigning - } = {}) { - _logger.logger.log("Bootstrapping cross-signing"); - const delegateCryptoCallbacks = this.baseApis.cryptoCallbacks; - const builder = new _EncryptionSetup.EncryptionSetupBuilder(this.baseApis.store.accountData, delegateCryptoCallbacks); - const crossSigningInfo = new _CrossSigning.CrossSigningInfo(this.userId, builder.crossSigningCallbacks, builder.crossSigningCallbacks); - - // Reset the cross-signing keys - const resetCrossSigning = async () => { - crossSigningInfo.resetKeys(); - // Sign master key with device key - await this.signObject(crossSigningInfo.keys.master); - - // Store auth flow helper function, as we need to call it when uploading - // to ensure we handle auth errors properly. - builder.addCrossSigningKeys(authUploadDeviceSigningKeys, crossSigningInfo.keys); - - // Cross-sign own device - const device = this.deviceList.getStoredDevice(this.userId, this.deviceId); - const deviceSignature = await crossSigningInfo.signDevice(this.userId, device); - builder.addKeySignature(this.userId, this.deviceId, deviceSignature); - - // Sign message key backup with cross-signing master key - if (this.backupManager.backupInfo) { - await crossSigningInfo.signObject(this.backupManager.backupInfo.auth_data, "master"); - builder.addSessionBackup(this.backupManager.backupInfo); - } - }; - const publicKeysOnDevice = this.crossSigningInfo.getId(); - const privateKeysInCache = await this.crossSigningInfo.isStoredInKeyCache(); - const privateKeysInStorage = await this.crossSigningInfo.isStoredInSecretStorage(this.secretStorage); - const privateKeysExistSomewhere = privateKeysInCache || privateKeysInStorage; - - // Log all relevant state for easier parsing of debug logs. - _logger.logger.log({ - setupNewCrossSigning, - publicKeysOnDevice, - privateKeysInCache, - privateKeysInStorage, - privateKeysExistSomewhere - }); - if (!privateKeysExistSomewhere || setupNewCrossSigning) { - _logger.logger.log("Cross-signing private keys not found locally or in secret storage, " + "creating new keys"); - // If a user has multiple devices, it important to only call bootstrap - // as part of some UI flow (and not silently during startup), as they - // may have setup cross-signing on a platform which has not saved keys - // to secret storage, and this would reset them. In such a case, you - // should prompt the user to verify any existing devices first (and - // request private keys from those devices) before calling bootstrap. - await resetCrossSigning(); - } else if (publicKeysOnDevice && privateKeysInCache) { - _logger.logger.log("Cross-signing public keys trusted and private keys found locally"); - } else if (privateKeysInStorage) { - _logger.logger.log("Cross-signing private keys not found locally, but they are available " + "in secret storage, reading storage and caching locally"); - await this.checkOwnCrossSigningTrust({ - allowPrivateKeyRequests: true - }); - } - - // Assuming no app-supplied callback, default to storing new private keys in - // secret storage if it exists. If it does not, it is assumed this will be - // done as part of setting up secret storage later. - const crossSigningPrivateKeys = builder.crossSigningCallbacks.privateKeys; - if (crossSigningPrivateKeys.size && !this.baseApis.cryptoCallbacks.saveCrossSigningKeys) { - const secretStorage = new _SecretStorage.SecretStorage(builder.accountDataClientAdapter, builder.ssssCryptoCallbacks, undefined); - if (await secretStorage.hasKey()) { - _logger.logger.log("Storing new cross-signing private keys in secret storage"); - // This is writing to in-memory account data in - // builder.accountDataClientAdapter so won't fail - await _CrossSigning.CrossSigningInfo.storeInSecretStorage(crossSigningPrivateKeys, secretStorage); - } - } - const operation = builder.buildOperation(); - await operation.apply(this); - // This persists private keys and public keys as trusted, - // only do this if apply succeeded for now as retry isn't in place yet - await builder.persist(this); - _logger.logger.log("Cross-signing ready"); - } - - /** - * Bootstrap Secure Secret Storage if needed by creating a default key. If everything is - * already set up, then no changes are made, so this is safe to run to ensure secret - * storage is ready for use. - * - * This function - * - creates a new Secure Secret Storage key if no default key exists - * - if a key backup exists, it is migrated to store the key in the Secret - * Storage - * - creates a backup if none exists, and one is requested - * - migrates Secure Secret Storage to use the latest algorithm, if an outdated - * algorithm is found - * - * The Secure Secret Storage API is currently UNSTABLE and may change without notice. - * - * @param createSecretStorageKey - Optional. Function - * called to await a secret storage key creation flow. - * Returns a Promise which resolves to an object with public key metadata, encoded private - * recovery key which should be disposed of after displaying to the user, - * and raw private key to avoid round tripping if needed. - * @param keyBackupInfo - The current key backup object. If passed, - * the passphrase and recovery key from this backup will be used. - * @param setupNewKeyBackup - If true, a new key backup version will be - * created and the private key stored in the new SSSS store. Ignored if keyBackupInfo - * is supplied. - * @param setupNewSecretStorage - Optional. Reset even if keys already exist. - * @param getKeyBackupPassphrase - Optional. Function called to get the user's - * current key backup passphrase. Should return a promise that resolves with a Buffer - * containing the key, or rejects if the key cannot be obtained. - * Returns: - * A promise which resolves to key creation data for - * SecretStorage#addKey: an object with `passphrase` etc fields. - */ - // TODO this does not resolve with what it says it does - async bootstrapSecretStorage({ - createSecretStorageKey = async () => ({}), - keyBackupInfo, - setupNewKeyBackup, - setupNewSecretStorage, - getKeyBackupPassphrase - } = {}) { - _logger.logger.log("Bootstrapping Secure Secret Storage"); - const delegateCryptoCallbacks = this.baseApis.cryptoCallbacks; - const builder = new _EncryptionSetup.EncryptionSetupBuilder(this.baseApis.store.accountData, delegateCryptoCallbacks); - const secretStorage = new _SecretStorage.SecretStorage(builder.accountDataClientAdapter, builder.ssssCryptoCallbacks, undefined); - - // the ID of the new SSSS key, if we create one - let newKeyId = null; - - // create a new SSSS key and set it as default - const createSSSS = async (opts, privateKey) => { - if (privateKey) { - opts.key = privateKey; - } - const { - keyId, - keyInfo - } = await secretStorage.addKey(_SecretStorage.SECRET_STORAGE_ALGORITHM_V1_AES, opts); - if (privateKey) { - // make the private key available to encrypt 4S secrets - builder.ssssCryptoCallbacks.addPrivateKey(keyId, keyInfo, privateKey); - } - await secretStorage.setDefaultKeyId(keyId); - return keyId; - }; - const ensureCanCheckPassphrase = async (keyId, keyInfo) => { - if (!keyInfo.mac) { - var _this$baseApis$crypto, _this$baseApis$crypto2; - const key = await ((_this$baseApis$crypto = (_this$baseApis$crypto2 = this.baseApis.cryptoCallbacks).getSecretStorageKey) === null || _this$baseApis$crypto === void 0 ? void 0 : _this$baseApis$crypto.call(_this$baseApis$crypto2, { - keys: { - [keyId]: keyInfo - } - }, "")); - if (key) { - const privateKey = key[1]; - builder.ssssCryptoCallbacks.addPrivateKey(keyId, keyInfo, privateKey); - const { - iv, - mac - } = await (0, _aes.calculateKeyCheck)(privateKey); - keyInfo.iv = iv; - keyInfo.mac = mac; - await builder.setAccountData(`m.secret_storage.key.${keyId}`, keyInfo); - } - } - }; - const signKeyBackupWithCrossSigning = async keyBackupAuthData => { - if (this.crossSigningInfo.getId() && (await this.crossSigningInfo.isStoredInKeyCache("master"))) { - try { - _logger.logger.log("Adding cross-signing signature to key backup"); - await this.crossSigningInfo.signObject(keyBackupAuthData, "master"); - } catch (e) { - // This step is not critical (just helpful), so we catch here - // and continue if it fails. - _logger.logger.error("Signing key backup with cross-signing keys failed", e); - } - } else { - _logger.logger.warn("Cross-signing keys not available, skipping signature on key backup"); - } - }; - const oldSSSSKey = await this.getSecretStorageKey(); - const [oldKeyId, oldKeyInfo] = oldSSSSKey || [null, null]; - const storageExists = !setupNewSecretStorage && oldKeyInfo && oldKeyInfo.algorithm === _SecretStorage.SECRET_STORAGE_ALGORITHM_V1_AES; - - // Log all relevant state for easier parsing of debug logs. - _logger.logger.log({ - keyBackupInfo, - setupNewKeyBackup, - setupNewSecretStorage, - storageExists, - oldKeyInfo - }); - if (!storageExists && !keyBackupInfo) { - // either we don't have anything, or we've been asked to restart - // from scratch - _logger.logger.log("Secret storage does not exist, creating new storage key"); - - // if we already have a usable default SSSS key and aren't resetting - // SSSS just use it. otherwise, create a new one - // Note: we leave the old SSSS key in place: there could be other - // secrets using it, in theory. We could move them to the new key but a) - // that would mean we'd need to prompt for the old passphrase, and b) - // it's not clear that would be the right thing to do anyway. - const { - keyInfo = {}, - privateKey - } = await createSecretStorageKey(); - newKeyId = await createSSSS(keyInfo, privateKey); - } else if (!storageExists && keyBackupInfo) { - // we have an existing backup, but no SSSS - _logger.logger.log("Secret storage does not exist, using key backup key"); - - // if we have the backup key already cached, use it; otherwise use the - // callback to prompt for the key - const backupKey = (await this.getSessionBackupPrivateKey()) || (await (getKeyBackupPassphrase === null || getKeyBackupPassphrase === void 0 ? void 0 : getKeyBackupPassphrase())); - - // create a new SSSS key and use the backup key as the new SSSS key - const opts = {}; - if (keyBackupInfo.auth_data.private_key_salt && keyBackupInfo.auth_data.private_key_iterations) { - // FIXME: ??? - opts.passphrase = { - algorithm: "m.pbkdf2", - iterations: keyBackupInfo.auth_data.private_key_iterations, - salt: keyBackupInfo.auth_data.private_key_salt, - bits: 256 - }; - } - newKeyId = await createSSSS(opts, backupKey); - - // store the backup key in secret storage - await secretStorage.store("m.megolm_backup.v1", olmlib.encodeBase64(backupKey), [newKeyId]); - - // The backup is trusted because the user provided the private key. - // Sign the backup with the cross-signing key so the key backup can - // be trusted via cross-signing. - await signKeyBackupWithCrossSigning(keyBackupInfo.auth_data); - builder.addSessionBackup(keyBackupInfo); - } else { - // 4S is already set up - _logger.logger.log("Secret storage exists"); - if (oldKeyInfo && oldKeyInfo.algorithm === _SecretStorage.SECRET_STORAGE_ALGORITHM_V1_AES) { - // make sure that the default key has the information needed to - // check the passphrase - await ensureCanCheckPassphrase(oldKeyId, oldKeyInfo); - } - } - - // If we have cross-signing private keys cached, store them in secret - // storage if they are not there already. - if (!this.baseApis.cryptoCallbacks.saveCrossSigningKeys && (await this.isCrossSigningReady()) && (newKeyId || !(await this.crossSigningInfo.isStoredInSecretStorage(secretStorage)))) { - _logger.logger.log("Copying cross-signing private keys from cache to secret storage"); - const crossSigningPrivateKeys = await this.crossSigningInfo.getCrossSigningKeysFromCache(); - // This is writing to in-memory account data in - // builder.accountDataClientAdapter so won't fail - await _CrossSigning.CrossSigningInfo.storeInSecretStorage(crossSigningPrivateKeys, secretStorage); - } - if (setupNewKeyBackup && !keyBackupInfo) { - _logger.logger.log("Creating new message key backup version"); - const info = await this.baseApis.prepareKeyBackupVersion(null /* random key */, - // don't write to secret storage, as it will write to this.secretStorage. - // Here, we want to capture all the side-effects of bootstrapping, - // and want to write to the local secretStorage object - { - secureSecretStorage: false - }); - // write the key ourselves to 4S - const privateKey = (0, _recoverykey.decodeRecoveryKey)(info.recovery_key); - await secretStorage.store("m.megolm_backup.v1", olmlib.encodeBase64(privateKey)); - - // create keyBackupInfo object to add to builder - const data = { - algorithm: info.algorithm, - auth_data: info.auth_data - }; - - // Sign with cross-signing master key - await signKeyBackupWithCrossSigning(data.auth_data); - - // sign with the device fingerprint - await this.signObject(data.auth_data); - builder.addSessionBackup(data); - } - - // Cache the session backup key - const sessionBackupKey = await secretStorage.get("m.megolm_backup.v1"); - if (sessionBackupKey) { - _logger.logger.info("Got session backup key from secret storage: caching"); - // fix up the backup key if it's in the wrong format, and replace - // in secret storage - const fixedBackupKey = fixBackupKey(sessionBackupKey); - if (fixedBackupKey) { - const keyId = newKeyId || oldKeyId; - await secretStorage.store("m.megolm_backup.v1", fixedBackupKey, keyId ? [keyId] : null); - } - const decodedBackupKey = new Uint8Array(olmlib.decodeBase64(fixedBackupKey || sessionBackupKey)); - builder.addSessionBackupPrivateKeyToCache(decodedBackupKey); - } else if (this.backupManager.getKeyBackupEnabled()) { - // key backup is enabled but we don't have a session backup key in SSSS: see if we have one in - // the cache or the user can provide one, and if so, write it to SSSS - const backupKey = (await this.getSessionBackupPrivateKey()) || (await (getKeyBackupPassphrase === null || getKeyBackupPassphrase === void 0 ? void 0 : getKeyBackupPassphrase())); - if (!backupKey) { - // This will require user intervention to recover from since we don't have the key - // backup key anywhere. The user should probably just set up a new key backup and - // the key for the new backup will be stored. If we hit this scenario in the wild - // with any frequency, we should do more than just log an error. - _logger.logger.error("Key backup is enabled but couldn't get key backup key!"); - return; - } - _logger.logger.info("Got session backup key from cache/user that wasn't in SSSS: saving to SSSS"); - await secretStorage.store("m.megolm_backup.v1", olmlib.encodeBase64(backupKey)); - } - const operation = builder.buildOperation(); - await operation.apply(this); - // this persists private keys and public keys as trusted, - // only do this if apply succeeded for now as retry isn't in place yet - await builder.persist(this); - _logger.logger.log("Secure Secret Storage ready"); - } - addSecretStorageKey(algorithm, opts, keyID) { - return this.secretStorage.addKey(algorithm, opts, keyID); - } - hasSecretStorageKey(keyID) { - return this.secretStorage.hasKey(keyID); - } - getSecretStorageKey(keyID) { - return this.secretStorage.getKey(keyID); - } - storeSecret(name, secret, keys) { - return this.secretStorage.store(name, secret, keys); - } - getSecret(name) { - return this.secretStorage.get(name); - } - isSecretStored(name) { - return this.secretStorage.isStored(name); - } - requestSecret(name, devices) { - if (!devices) { - devices = Object.keys(this.deviceList.getRawStoredDevicesForUser(this.userId)); - } - return this.secretStorage.request(name, devices); - } - getDefaultSecretStorageKeyId() { - return this.secretStorage.getDefaultKeyId(); - } - setDefaultSecretStorageKeyId(k) { - return this.secretStorage.setDefaultKeyId(k); - } - checkSecretStorageKey(key, info) { - return this.secretStorage.checkKey(key, info); - } - - /** - * Checks that a given secret storage private key matches a given public key. - * This can be used by the getSecretStorageKey callback to verify that the - * private key it is about to supply is the one that was requested. - * - * @param privateKey - The private key - * @param expectedPublicKey - The public key - * @returns true if the key matches, otherwise false - */ - checkSecretStoragePrivateKey(privateKey, expectedPublicKey) { - let decryption = null; - try { - decryption = new global.Olm.PkDecryption(); - const gotPubkey = decryption.init_with_private_key(privateKey); - // make sure it agrees with the given pubkey - return gotPubkey === expectedPublicKey; - } finally { - var _decryption; - (_decryption = decryption) === null || _decryption === void 0 ? void 0 : _decryption.free(); - } - } - - /** - * Fetches the backup private key, if cached - * @returns the key, if any, or null - */ - async getSessionBackupPrivateKey() { - let key = await new Promise(resolve => { - // TODO types - this.cryptoStore.doTxn("readonly", [_indexeddbCryptoStore.IndexedDBCryptoStore.STORE_ACCOUNT], txn => { - this.cryptoStore.getSecretStorePrivateKey(txn, resolve, "m.megolm_backup.v1"); - }); - }); - - // make sure we have a Uint8Array, rather than a string - if (key && typeof key === "string") { - key = new Uint8Array(olmlib.decodeBase64(fixBackupKey(key) || key)); - await this.storeSessionBackupPrivateKey(key); - } - if (key && key.ciphertext) { - const pickleKey = Buffer.from(this.olmDevice.pickleKey); - const decrypted = await (0, _aes.decryptAES)(key, pickleKey, "m.megolm_backup.v1"); - key = olmlib.decodeBase64(decrypted); - } - return key; - } - - /** - * Stores the session backup key to the cache - * @param key - the private key - * @returns a promise so you can catch failures - */ - async storeSessionBackupPrivateKey(key) { - if (!(key instanceof Uint8Array)) { - // eslint-disable-next-line @typescript-eslint/no-base-to-string - throw new Error(`storeSessionBackupPrivateKey expects Uint8Array, got ${key}`); - } - const pickleKey = Buffer.from(this.olmDevice.pickleKey); - const encryptedKey = await (0, _aes.encryptAES)(olmlib.encodeBase64(key), pickleKey, "m.megolm_backup.v1"); - return this.cryptoStore.doTxn("readwrite", [_indexeddbCryptoStore.IndexedDBCryptoStore.STORE_ACCOUNT], txn => { - this.cryptoStore.storeSecretStorePrivateKey(txn, "m.megolm_backup.v1", encryptedKey); - }); - } - - /** - * Checks that a given cross-signing private key matches a given public key. - * This can be used by the getCrossSigningKey callback to verify that the - * private key it is about to supply is the one that was requested. - * - * @param privateKey - The private key - * @param expectedPublicKey - The public key - * @returns true if the key matches, otherwise false - */ - checkCrossSigningPrivateKey(privateKey, expectedPublicKey) { - let signing = null; - try { - signing = new global.Olm.PkSigning(); - const gotPubkey = signing.init_with_seed(privateKey); - // make sure it agrees with the given pubkey - return gotPubkey === expectedPublicKey; - } finally { - var _signing; - (_signing = signing) === null || _signing === void 0 ? void 0 : _signing.free(); - } - } - - /** - * Run various follow-up actions after cross-signing keys have changed locally - * (either by resetting the keys for the account or by getting them from secret - * storage), such as signing the current device, upgrading device - * verifications, etc. - */ - async afterCrossSigningLocalKeyChange() { - _logger.logger.info("Starting cross-signing key change post-processing"); - - // sign the current device with the new key, and upload to the server - const device = this.deviceList.getStoredDevice(this.userId, this.deviceId); - const signedDevice = await this.crossSigningInfo.signDevice(this.userId, device); - _logger.logger.info(`Starting background key sig upload for ${this.deviceId}`); - const upload = ({ - shouldEmit = false - }) => { - return this.baseApis.uploadKeySignatures({ - [this.userId]: { - [this.deviceId]: signedDevice - } - }).then(response => { - const { - failures - } = response || {}; - if (Object.keys(failures || []).length > 0) { - if (shouldEmit) { - this.baseApis.emit(CryptoEvent.KeySignatureUploadFailure, failures, "afterCrossSigningLocalKeyChange", upload // continuation - ); - } - - throw new _errors.KeySignatureUploadError("Key upload failed", { - failures - }); - } - _logger.logger.info(`Finished background key sig upload for ${this.deviceId}`); - }).catch(e => { - _logger.logger.error(`Error during background key sig upload for ${this.deviceId}`, e); - }); - }; - upload({ - shouldEmit: true - }); - const shouldUpgradeCb = this.baseApis.cryptoCallbacks.shouldUpgradeDeviceVerifications; - if (shouldUpgradeCb) { - _logger.logger.info("Starting device verification upgrade"); - - // Check all users for signatures if upgrade callback present - // FIXME: do this in batches - const users = {}; - for (const [userId, crossSigningInfo] of Object.entries(this.deviceList.crossSigningInfo)) { - const upgradeInfo = await this.checkForDeviceVerificationUpgrade(userId, _CrossSigning.CrossSigningInfo.fromStorage(crossSigningInfo, userId)); - if (upgradeInfo) { - users[userId] = upgradeInfo; - } - } - if (Object.keys(users).length > 0) { - _logger.logger.info(`Found ${Object.keys(users).length} verif users to upgrade`); - try { - const usersToUpgrade = await shouldUpgradeCb({ - users: users - }); - if (usersToUpgrade) { - for (const userId of usersToUpgrade) { - if (userId in users) { - await this.baseApis.setDeviceVerified(userId, users[userId].crossSigningInfo.getId()); - } - } - } - } catch (e) { - _logger.logger.log("shouldUpgradeDeviceVerifications threw an error: not upgrading", e); - } - } - _logger.logger.info("Finished device verification upgrade"); - } - _logger.logger.info("Finished cross-signing key change post-processing"); - } - - /** - * Check if a user's cross-signing key is a candidate for upgrading from device - * verification. - * - * @param userId - the user whose cross-signing information is to be checked - * @param crossSigningInfo - the cross-signing information to check - */ - async checkForDeviceVerificationUpgrade(userId, crossSigningInfo) { - // only upgrade if this is the first cross-signing key that we've seen for - // them, and if their cross-signing key isn't already verified - const trustLevel = this.crossSigningInfo.checkUserTrust(crossSigningInfo); - if (crossSigningInfo.firstUse && !trustLevel.isVerified()) { - const devices = this.deviceList.getRawStoredDevicesForUser(userId); - const deviceIds = await this.checkForValidDeviceSignature(userId, crossSigningInfo.keys.master, devices); - if (deviceIds.length) { - return { - devices: deviceIds.map(deviceId => _deviceinfo.DeviceInfo.fromStorage(devices[deviceId], deviceId)), - crossSigningInfo - }; - } - } - } - - /** - * Check if the cross-signing key is signed by a verified device. - * - * @param userId - the user ID whose key is being checked - * @param key - the key that is being checked - * @param devices - the user's devices. Should be a map from device ID - * to device info - */ - async checkForValidDeviceSignature(userId, key, devices) { - const deviceIds = []; - if (devices && key.signatures && key.signatures[userId]) { - for (const signame of Object.keys(key.signatures[userId])) { - const [, deviceId] = signame.split(":", 2); - if (deviceId in devices && devices[deviceId].verified === DeviceVerification.VERIFIED) { - try { - await olmlib.verifySignature(this.olmDevice, key, userId, deviceId, devices[deviceId].keys[signame]); - deviceIds.push(deviceId); - } catch (e) {} - } - } - } - return deviceIds; - } - - /** - * Get the user's cross-signing key ID. - * - * @param type - The type of key to get the ID of. One of - * "master", "self_signing", or "user_signing". Defaults to "master". - * - * @returns the key ID - */ - getCrossSigningId(type) { - return this.crossSigningInfo.getId(type); - } - - /** - * Get the cross signing information for a given user. - * - * @param userId - the user ID to get the cross-signing info for. - * - * @returns the cross signing information for the user. - */ - getStoredCrossSigningForUser(userId) { - return this.deviceList.getStoredCrossSigningForUser(userId); - } - - /** - * Check whether a given user is trusted. - * - * @param userId - The ID of the user to check. - * - * @returns - */ - checkUserTrust(userId) { - const userCrossSigning = this.deviceList.getStoredCrossSigningForUser(userId); - if (!userCrossSigning) { - return new _CrossSigning.UserTrustLevel(false, false, false); - } - return this.crossSigningInfo.checkUserTrust(userCrossSigning); - } - - /** - * Check whether a given device is trusted. - * - * @param userId - The ID of the user whose devices is to be checked. - * @param deviceId - The ID of the device to check - * - * @returns - */ - checkDeviceTrust(userId, deviceId) { - const device = this.deviceList.getStoredDevice(userId, deviceId); - return this.checkDeviceInfoTrust(userId, device); - } - - /** - * Check whether a given deviceinfo is trusted. - * - * @param userId - The ID of the user whose devices is to be checked. - * @param device - The device info object to check - * - * @returns - */ - checkDeviceInfoTrust(userId, device) { - const trustedLocally = !!(device !== null && device !== void 0 && device.isVerified()); - const userCrossSigning = this.deviceList.getStoredCrossSigningForUser(userId); - if (device && userCrossSigning) { - // The trustCrossSignedDevices only affects trust of other people's cross-signing - // signatures - const trustCrossSig = this.trustCrossSignedDevices || userId === this.userId; - return this.crossSigningInfo.checkDeviceTrust(userCrossSigning, device, trustedLocally, trustCrossSig); - } else { - return new _CrossSigning.DeviceTrustLevel(false, false, trustedLocally, false); - } - } - - /** - * Check whether one of our own devices is cross-signed by our - * user's stored keys, regardless of whether we trust those keys yet. - * - * @param deviceId - The ID of the device to check - * - * @returns true if the device is cross-signed - */ - checkIfOwnDeviceCrossSigned(deviceId) { - var _userCrossSigning$che; - const device = this.deviceList.getStoredDevice(this.userId, deviceId); - if (!device) return false; - const userCrossSigning = this.deviceList.getStoredCrossSigningForUser(this.userId); - return (_userCrossSigning$che = userCrossSigning === null || userCrossSigning === void 0 ? void 0 : userCrossSigning.checkDeviceTrust(userCrossSigning, device, false, true).isCrossSigningVerified()) !== null && _userCrossSigning$che !== void 0 ? _userCrossSigning$che : false; - } - - /* - * Event handler for DeviceList's userNewDevices event - */ - - /** - * Check the copy of our cross-signing key that we have in the device list and - * see if we can get the private key. If so, mark it as trusted. - */ - async checkOwnCrossSigningTrust({ - allowPrivateKeyRequests = false - } = {}) { - const userId = this.userId; - - // Before proceeding, ensure our cross-signing public keys have been - // downloaded via the device list. - await this.downloadKeys([this.userId]); - - // Also check which private keys are locally cached. - const crossSigningPrivateKeys = await this.crossSigningInfo.getCrossSigningKeysFromCache(); - - // If we see an update to our own master key, check it against the master - // key we have and, if it matches, mark it as verified - - // First, get the new cross-signing info - const newCrossSigning = this.deviceList.getStoredCrossSigningForUser(userId); - if (!newCrossSigning) { - _logger.logger.error("Got cross-signing update event for user " + userId + " but no new cross-signing information found!"); - return; - } - const seenPubkey = newCrossSigning.getId(); - const masterChanged = this.crossSigningInfo.getId() !== seenPubkey; - const masterExistsNotLocallyCached = newCrossSigning.getId() && !crossSigningPrivateKeys.has("master"); - if (masterChanged) { - _logger.logger.info("Got new master public key", seenPubkey); - } - if (allowPrivateKeyRequests && (masterChanged || masterExistsNotLocallyCached)) { - _logger.logger.info("Attempting to retrieve cross-signing master private key"); - let signing = null; - // It's important for control flow that we leave any errors alone for - // higher levels to handle so that e.g. cancelling access properly - // aborts any larger operation as well. - try { - const ret = await this.crossSigningInfo.getCrossSigningKey("master", seenPubkey); - signing = ret[1]; - _logger.logger.info("Got cross-signing master private key"); - } finally { - var _signing2; - (_signing2 = signing) === null || _signing2 === void 0 ? void 0 : _signing2.free(); - } - } - const oldSelfSigningId = this.crossSigningInfo.getId("self_signing"); - const oldUserSigningId = this.crossSigningInfo.getId("user_signing"); - - // Update the version of our keys in our cross-signing object and the local store - this.storeTrustedSelfKeys(newCrossSigning.keys); - const selfSigningChanged = oldSelfSigningId !== newCrossSigning.getId("self_signing"); - const userSigningChanged = oldUserSigningId !== newCrossSigning.getId("user_signing"); - const selfSigningExistsNotLocallyCached = newCrossSigning.getId("self_signing") && !crossSigningPrivateKeys.has("self_signing"); - const userSigningExistsNotLocallyCached = newCrossSigning.getId("user_signing") && !crossSigningPrivateKeys.has("user_signing"); - const keySignatures = {}; - if (selfSigningChanged) { - _logger.logger.info("Got new self-signing key", newCrossSigning.getId("self_signing")); - } - if (allowPrivateKeyRequests && (selfSigningChanged || selfSigningExistsNotLocallyCached)) { - _logger.logger.info("Attempting to retrieve cross-signing self-signing private key"); - let signing = null; - try { - const ret = await this.crossSigningInfo.getCrossSigningKey("self_signing", newCrossSigning.getId("self_signing")); - signing = ret[1]; - _logger.logger.info("Got cross-signing self-signing private key"); - } finally { - var _signing3; - (_signing3 = signing) === null || _signing3 === void 0 ? void 0 : _signing3.free(); - } - const device = this.deviceList.getStoredDevice(this.userId, this.deviceId); - const signedDevice = await this.crossSigningInfo.signDevice(this.userId, device); - keySignatures[this.deviceId] = signedDevice; - } - if (userSigningChanged) { - _logger.logger.info("Got new user-signing key", newCrossSigning.getId("user_signing")); - } - if (allowPrivateKeyRequests && (userSigningChanged || userSigningExistsNotLocallyCached)) { - _logger.logger.info("Attempting to retrieve cross-signing user-signing private key"); - let signing = null; - try { - const ret = await this.crossSigningInfo.getCrossSigningKey("user_signing", newCrossSigning.getId("user_signing")); - signing = ret[1]; - _logger.logger.info("Got cross-signing user-signing private key"); - } finally { - var _signing4; - (_signing4 = signing) === null || _signing4 === void 0 ? void 0 : _signing4.free(); - } - } - if (masterChanged) { - const masterKey = this.crossSigningInfo.keys.master; - await this.signObject(masterKey); - const deviceSig = masterKey.signatures[this.userId]["ed25519:" + this.deviceId]; - // Include only the _new_ device signature in the upload. - // We may have existing signatures from deleted devices, which will cause - // the entire upload to fail. - keySignatures[this.crossSigningInfo.getId()] = Object.assign({}, masterKey, { - signatures: { - [this.userId]: { - ["ed25519:" + this.deviceId]: deviceSig - } - } - }); - } - const keysToUpload = Object.keys(keySignatures); - if (keysToUpload.length) { - const upload = ({ - shouldEmit = false - }) => { - _logger.logger.info(`Starting background key sig upload for ${keysToUpload}`); - return this.baseApis.uploadKeySignatures({ - [this.userId]: keySignatures - }).then(response => { - const { - failures - } = response || {}; - _logger.logger.info(`Finished background key sig upload for ${keysToUpload}`); - if (Object.keys(failures || []).length > 0) { - if (shouldEmit) { - this.baseApis.emit(CryptoEvent.KeySignatureUploadFailure, failures, "checkOwnCrossSigningTrust", upload); - } - throw new _errors.KeySignatureUploadError("Key upload failed", { - failures - }); - } - }).catch(e => { - _logger.logger.error(`Error during background key sig upload for ${keysToUpload}`, e); - }); - }; - upload({ - shouldEmit: true - }); - } - this.emit(CryptoEvent.UserTrustStatusChanged, userId, this.checkUserTrust(userId)); - if (masterChanged) { - this.emit(CryptoEvent.KeysChanged, {}); - await this.afterCrossSigningLocalKeyChange(); - } - - // Now we may be able to trust our key backup - await this.backupManager.checkKeyBackup(); - // FIXME: if we previously trusted the backup, should we automatically sign - // the backup with the new key (if not already signed)? - } - - /** - * Store a set of keys as our own, trusted, cross-signing keys. - * - * @param keys - The new trusted set of keys - */ - async storeTrustedSelfKeys(keys) { - if (keys) { - this.crossSigningInfo.setKeys(keys); - } else { - this.crossSigningInfo.clearKeys(); - } - await this.cryptoStore.doTxn("readwrite", [_indexeddbCryptoStore.IndexedDBCryptoStore.STORE_ACCOUNT], txn => { - this.cryptoStore.storeCrossSigningKeys(txn, this.crossSigningInfo.keys); - }); - } - - /** - * Check if the master key is signed by a verified device, and if so, prompt - * the application to mark it as verified. - * - * @param userId - the user ID whose key should be checked - */ - async checkDeviceVerifications(userId) { - const shouldUpgradeCb = this.baseApis.cryptoCallbacks.shouldUpgradeDeviceVerifications; - if (!shouldUpgradeCb) { - // Upgrading skipped when callback is not present. - return; - } - _logger.logger.info(`Starting device verification upgrade for ${userId}`); - if (this.crossSigningInfo.keys.user_signing) { - const crossSigningInfo = this.deviceList.getStoredCrossSigningForUser(userId); - if (crossSigningInfo) { - const upgradeInfo = await this.checkForDeviceVerificationUpgrade(userId, crossSigningInfo); - if (upgradeInfo) { - const usersToUpgrade = await shouldUpgradeCb({ - users: { - [userId]: upgradeInfo - } - }); - if (usersToUpgrade.includes(userId)) { - await this.baseApis.setDeviceVerified(userId, crossSigningInfo.getId()); - } - } - } - } - _logger.logger.info(`Finished device verification upgrade for ${userId}`); - } - - /** - */ - enableLazyLoading() { - this.lazyLoadMembers = true; - } - - /** - * Tell the crypto module to register for MatrixClient events which it needs to - * listen for - * - * @param eventEmitter - event source where we can register - * for event notifications - */ - registerEventHandlers(eventEmitter) { - eventEmitter.on(_roomMember.RoomMemberEvent.Membership, this.onMembership); - eventEmitter.on(_client.ClientEvent.ToDeviceEvent, this.onToDeviceEvent); - eventEmitter.on(_room.RoomEvent.Timeline, this.onTimelineEvent); - eventEmitter.on(_event2.MatrixEventEvent.Decrypted, this.onTimelineEvent); - } - - /** - * @deprecated this does nothing and will be removed in a future version - */ - start() { - _logger.logger.warn("MatrixClient.crypto.start() is deprecated"); - } - - /** Stop background processes related to crypto */ - stop() { - this.outgoingRoomKeyRequestManager.stop(); - this.deviceList.stop(); - this.dehydrationManager.stop(); - } - - /** - * Get the Ed25519 key for this device - * - * @returns base64-encoded ed25519 key. - */ - getDeviceEd25519Key() { - return this.olmDevice.deviceEd25519Key; - } - - /** - * Get the Curve25519 key for this device - * - * @returns base64-encoded curve25519 key. - */ - getDeviceCurve25519Key() { - return this.olmDevice.deviceCurve25519Key; - } - - /** - * Set the global override for whether the client should ever send encrypted - * messages to unverified devices. This provides the default for rooms which - * do not specify a value. - * - * @param value - whether to blacklist all unverified devices by default - * - * @deprecated For external code, use {@link MatrixClient#setGlobalBlacklistUnverifiedDevices}. For - * internal code, set {@link MatrixClient#globalBlacklistUnverifiedDevices} directly. - */ - setGlobalBlacklistUnverifiedDevices(value) { - this.globalBlacklistUnverifiedDevices = value; - } - - /** - * @returns whether to blacklist all unverified devices by default - * - * @deprecated For external code, use {@link MatrixClient#getGlobalBlacklistUnverifiedDevices}. For - * internal code, reference {@link MatrixClient#globalBlacklistUnverifiedDevices} directly. - */ - getGlobalBlacklistUnverifiedDevices() { - return this.globalBlacklistUnverifiedDevices; - } - - /** - * Upload the device keys to the homeserver. - * @returns A promise that will resolve when the keys are uploaded. - */ - uploadDeviceKeys() { - const deviceKeys = { - algorithms: this.supportedAlgorithms, - device_id: this.deviceId, - keys: this.deviceKeys, - user_id: this.userId - }; - return this.signObject(deviceKeys).then(() => { - return this.baseApis.uploadKeysRequest({ - device_keys: deviceKeys - }); - }); - } - - /** - * Stores the current one_time_key count which will be handled later (in a call of - * onSyncCompleted). The count is e.g. coming from a /sync response. - * - * @param currentCount - The current count of one_time_keys to be stored - */ - updateOneTimeKeyCount(currentCount) { - if (isFinite(currentCount)) { - this.oneTimeKeyCount = currentCount; - } else { - throw new TypeError("Parameter for updateOneTimeKeyCount has to be a number"); - } - } - setNeedsNewFallback(needsNewFallback) { - this.needsNewFallback = needsNewFallback; - } - getNeedsNewFallback() { - return !!this.needsNewFallback; - } - - // check if it's time to upload one-time keys, and do so if so. - maybeUploadOneTimeKeys() { - // frequency with which to check & upload one-time keys - const uploadPeriod = 1000 * 60; // one minute - - // max number of keys to upload at once - // Creating keys can be an expensive operation so we limit the - // number we generate in one go to avoid blocking the application - // for too long. - const maxKeysPerCycle = 5; - if (this.oneTimeKeyCheckInProgress) { - return; - } - const now = Date.now(); - if (this.lastOneTimeKeyCheck !== null && now - this.lastOneTimeKeyCheck < uploadPeriod) { - // we've done a key upload recently. - return; - } - this.lastOneTimeKeyCheck = now; - - // We need to keep a pool of one time public keys on the server so that - // other devices can start conversations with us. But we can only store - // a finite number of private keys in the olm Account object. - // To complicate things further then can be a delay between a device - // claiming a public one time key from the server and it sending us a - // message. We need to keep the corresponding private key locally until - // we receive the message. - // But that message might never arrive leaving us stuck with duff - // private keys clogging up our local storage. - // So we need some kind of engineering compromise to balance all of - // these factors. - - // Check how many keys we can store in the Account object. - const maxOneTimeKeys = this.olmDevice.maxNumberOfOneTimeKeys(); - // Try to keep at most half that number on the server. This leaves the - // rest of the slots free to hold keys that have been claimed from the - // server but we haven't received a message for. - // If we run out of slots when generating new keys then olm will - // discard the oldest private keys first. This will eventually clean - // out stale private keys that won't receive a message. - const keyLimit = Math.floor(maxOneTimeKeys / 2); - const uploadLoop = async keyCount => { - while (keyLimit > keyCount || this.getNeedsNewFallback()) { - // Ask olm to generate new one time keys, then upload them to synapse. - if (keyLimit > keyCount) { - _logger.logger.info("generating oneTimeKeys"); - const keysThisLoop = Math.min(keyLimit - keyCount, maxKeysPerCycle); - await this.olmDevice.generateOneTimeKeys(keysThisLoop); - } - if (this.getNeedsNewFallback()) { - const fallbackKeys = await this.olmDevice.getFallbackKey(); - // if fallbackKeys is non-empty, we've already generated a - // fallback key, but it hasn't been published yet, so we - // can use that instead of generating a new one - if (!fallbackKeys.curve25519 || Object.keys(fallbackKeys.curve25519).length == 0) { - _logger.logger.info("generating fallback key"); - if (this.fallbackCleanup) { - // cancel any pending fallback cleanup because generating - // a new fallback key will already drop the old fallback - // that would have been dropped, and we don't want to kill - // the current key - clearTimeout(this.fallbackCleanup); - delete this.fallbackCleanup; - } - await this.olmDevice.generateFallbackKey(); - } - } - _logger.logger.info("calling uploadOneTimeKeys"); - const res = await this.uploadOneTimeKeys(); - if (res.one_time_key_counts && res.one_time_key_counts.signed_curve25519) { - // if the response contains a more up to date value use this - // for the next loop - keyCount = res.one_time_key_counts.signed_curve25519; - } else { - throw new Error("response for uploading keys does not contain " + "one_time_key_counts.signed_curve25519"); - } - } - }; - this.oneTimeKeyCheckInProgress = true; - Promise.resolve().then(() => { - if (this.oneTimeKeyCount !== undefined) { - // We already have the current one_time_key count from a /sync response. - // Use this value instead of asking the server for the current key count. - return Promise.resolve(this.oneTimeKeyCount); - } - // ask the server how many keys we have - return this.baseApis.uploadKeysRequest({}).then(res => { - return res.one_time_key_counts.signed_curve25519 || 0; - }); - }).then(keyCount => { - // Start the uploadLoop with the current keyCount. The function checks if - // we need to upload new keys or not. - // If there are too many keys on the server then we don't need to - // create any more keys. - return uploadLoop(keyCount); - }).catch(e => { - _logger.logger.error("Error uploading one-time keys", e.stack || e); - }).finally(() => { - // reset oneTimeKeyCount to prevent start uploading based on old data. - // it will be set again on the next /sync-response - this.oneTimeKeyCount = undefined; - this.oneTimeKeyCheckInProgress = false; - }); - } - - // returns a promise which resolves to the response - async uploadOneTimeKeys() { - const promises = []; - let fallbackJson; - if (this.getNeedsNewFallback()) { - fallbackJson = {}; - const fallbackKeys = await this.olmDevice.getFallbackKey(); - for (const [keyId, key] of Object.entries(fallbackKeys.curve25519)) { - const k = { - key, - fallback: true - }; - fallbackJson["signed_curve25519:" + keyId] = k; - promises.push(this.signObject(k)); - } - this.setNeedsNewFallback(false); - } - const oneTimeKeys = await this.olmDevice.getOneTimeKeys(); - const oneTimeJson = {}; - for (const keyId in oneTimeKeys.curve25519) { - if (oneTimeKeys.curve25519.hasOwnProperty(keyId)) { - const k = { - key: oneTimeKeys.curve25519[keyId] - }; - oneTimeJson["signed_curve25519:" + keyId] = k; - promises.push(this.signObject(k)); - } - } - await Promise.all(promises); - const requestBody = { - one_time_keys: oneTimeJson - }; - if (fallbackJson) { - requestBody["org.matrix.msc2732.fallback_keys"] = fallbackJson; - requestBody["fallback_keys"] = fallbackJson; - } - const res = await this.baseApis.uploadKeysRequest(requestBody); - if (fallbackJson) { - this.fallbackCleanup = setTimeout(() => { - delete this.fallbackCleanup; - this.olmDevice.forgetOldFallbackKey(); - }, 60 * 60 * 1000); - } - await this.olmDevice.markKeysAsPublished(); - return res; - } - - /** - * Download the keys for a list of users and stores the keys in the session - * store. - * @param userIds - The users to fetch. - * @param forceDownload - Always download the keys even if cached. - * - * @returns A promise which resolves to a map `userId->deviceId->{@link DeviceInfo}`. - */ - downloadKeys(userIds, forceDownload) { - return this.deviceList.downloadKeys(userIds, !!forceDownload); - } - - /** - * Get the stored device keys for a user id - * - * @param userId - the user to list keys for. - * - * @returns list of devices, or null if we haven't - * managed to get a list of devices for this user yet. - */ - getStoredDevicesForUser(userId) { - return this.deviceList.getStoredDevicesForUser(userId); - } - - /** - * Get the stored keys for a single device - * - * - * @returns device, or undefined - * if we don't know about this device - */ - getStoredDevice(userId, deviceId) { - return this.deviceList.getStoredDevice(userId, deviceId); - } - - /** - * Save the device list, if necessary - * - * @param delay - Time in ms before which the save actually happens. - * By default, the save is delayed for a short period in order to batch - * multiple writes, but this behaviour can be disabled by passing 0. - * - * @returns true if the data was saved, false if - * it was not (eg. because no changes were pending). The promise - * will only resolve once the data is saved, so may take some time - * to resolve. - */ - saveDeviceList(delay) { - return this.deviceList.saveIfDirty(delay); - } - - /** - * Update the blocked/verified state of the given device - * - * @param userId - owner of the device - * @param deviceId - unique identifier for the device or user's - * cross-signing public key ID. - * - * @param verified - whether to mark the device as verified. Null to - * leave unchanged. - * - * @param blocked - whether to mark the device as blocked. Null to - * leave unchanged. - * - * @param known - whether to mark that the user has been made aware of - * the existence of this device. Null to leave unchanged - * - * @param keys - The list of keys that was present - * during the device verification. This will be double checked with the list - * of keys the given device has currently. - * - * @returns updated DeviceInfo - */ - async setDeviceVerification(userId, deviceId, verified = null, blocked = null, known = null, keys) { - // Check if the 'device' is actually a cross signing key - // The js-sdk's verification treats cross-signing keys as devices - // and so uses this method to mark them verified. - const xsk = this.deviceList.getStoredCrossSigningForUser(userId); - if (xsk && xsk.getId() === deviceId) { - if (blocked !== null || known !== null) { - throw new Error("Cannot set blocked or known for a cross-signing key"); - } - if (!verified) { - throw new Error("Cannot set a cross-signing key as unverified"); - } - const gotKeyId = keys ? Object.values(keys)[0] : null; - if (keys && (Object.values(keys).length !== 1 || gotKeyId !== xsk.getId())) { - throw new Error(`Key did not match expected value: expected ${xsk.getId()}, got ${gotKeyId}`); - } - if (!this.crossSigningInfo.getId() && userId === this.crossSigningInfo.userId) { - this.storeTrustedSelfKeys(xsk.keys); - // This will cause our own user trust to change, so emit the event - this.emit(CryptoEvent.UserTrustStatusChanged, this.userId, this.checkUserTrust(userId)); - } - - // Now sign the master key with our user signing key (unless it's ourself) - if (userId !== this.userId) { - _logger.logger.info("Master key " + xsk.getId() + " for " + userId + " marked verified. Signing..."); - const device = await this.crossSigningInfo.signUser(xsk); - if (device) { - const upload = async ({ - shouldEmit = false - }) => { - _logger.logger.info("Uploading signature for " + userId + "..."); - const response = await this.baseApis.uploadKeySignatures({ - [userId]: { - [deviceId]: device - } - }); - const { - failures - } = response || {}; - if (Object.keys(failures || []).length > 0) { - if (shouldEmit) { - this.baseApis.emit(CryptoEvent.KeySignatureUploadFailure, failures, "setDeviceVerification", upload); - } - /* Throwing here causes the process to be cancelled and the other - * user to be notified */ - throw new _errors.KeySignatureUploadError("Key upload failed", { - failures - }); - } - }; - await upload({ - shouldEmit: true - }); - - // This will emit events when it comes back down the sync - // (we could do local echo to speed things up) - } - - return device; // TODO types - } else { - return xsk; - } - } - const devices = this.deviceList.getRawStoredDevicesForUser(userId); - if (!devices || !devices[deviceId]) { - throw new Error("Unknown device " + userId + ":" + deviceId); - } - const dev = devices[deviceId]; - let verificationStatus = dev.verified; - if (verified) { - if (keys) { - for (const [keyId, key] of Object.entries(keys)) { - if (dev.keys[keyId] !== key) { - throw new Error(`Key did not match expected value: expected ${key}, got ${dev.keys[keyId]}`); - } - } - } - verificationStatus = DeviceVerification.VERIFIED; - } else if (verified !== null && verificationStatus == DeviceVerification.VERIFIED) { - verificationStatus = DeviceVerification.UNVERIFIED; - } - if (blocked) { - verificationStatus = DeviceVerification.BLOCKED; - } else if (blocked !== null && verificationStatus == DeviceVerification.BLOCKED) { - verificationStatus = DeviceVerification.UNVERIFIED; - } - let knownStatus = dev.known; - if (known !== null) { - knownStatus = known; - } - if (dev.verified !== verificationStatus || dev.known !== knownStatus) { - dev.verified = verificationStatus; - dev.known = knownStatus; - this.deviceList.storeDevicesForUser(userId, devices); - this.deviceList.saveIfDirty(); - } - - // do cross-signing - if (verified && userId === this.userId) { - _logger.logger.info("Own device " + deviceId + " marked verified: signing"); - - // Signing only needed if other device not already signed - let device; - const deviceTrust = this.checkDeviceTrust(userId, deviceId); - if (deviceTrust.isCrossSigningVerified()) { - _logger.logger.log(`Own device ${deviceId} already cross-signing verified`); - } else { - device = await this.crossSigningInfo.signDevice(userId, _deviceinfo.DeviceInfo.fromStorage(dev, deviceId)); - } - if (device) { - const upload = async ({ - shouldEmit = false - }) => { - _logger.logger.info("Uploading signature for " + deviceId); - const response = await this.baseApis.uploadKeySignatures({ - [userId]: { - [deviceId]: device - } - }); - const { - failures - } = response || {}; - if (Object.keys(failures || []).length > 0) { - if (shouldEmit) { - this.baseApis.emit(CryptoEvent.KeySignatureUploadFailure, failures, "setDeviceVerification", upload // continuation - ); - } - - throw new _errors.KeySignatureUploadError("Key upload failed", { - failures - }); - } - }; - await upload({ - shouldEmit: true - }); - // XXX: we'll need to wait for the device list to be updated - } - } - - const deviceObj = _deviceinfo.DeviceInfo.fromStorage(dev, deviceId); - this.emit(CryptoEvent.DeviceVerificationChanged, userId, deviceId, deviceObj); - return deviceObj; - } - findVerificationRequestDMInProgress(roomId) { - return this.inRoomVerificationRequests.findRequestInProgress(roomId); - } - getVerificationRequestsToDeviceInProgress(userId) { - return this.toDeviceVerificationRequests.getRequestsInProgress(userId); - } - requestVerificationDM(userId, roomId) { - const existingRequest = this.inRoomVerificationRequests.findRequestInProgress(roomId); - if (existingRequest) { - return Promise.resolve(existingRequest); - } - const channel = new _InRoomChannel.InRoomChannel(this.baseApis, roomId, userId); - return this.requestVerificationWithChannel(userId, channel, this.inRoomVerificationRequests); - } - requestVerification(userId, devices) { - if (!devices) { - devices = Object.keys(this.deviceList.getRawStoredDevicesForUser(userId)); - } - const existingRequest = this.toDeviceVerificationRequests.findRequestInProgress(userId, devices); - if (existingRequest) { - return Promise.resolve(existingRequest); - } - const channel = new _ToDeviceChannel.ToDeviceChannel(this.baseApis, userId, devices, _ToDeviceChannel.ToDeviceChannel.makeTransactionId()); - return this.requestVerificationWithChannel(userId, channel, this.toDeviceVerificationRequests); - } - async requestVerificationWithChannel(userId, channel, requestsMap) { - let request = new _VerificationRequest.VerificationRequest(channel, this.verificationMethods, this.baseApis); - // if transaction id is already known, add request - if (channel.transactionId) { - requestsMap.setRequestByChannel(channel, request); - } - await request.sendRequest(); - // don't replace the request created by a racing remote echo - const racingRequest = requestsMap.getRequestByChannel(channel); - if (racingRequest) { - request = racingRequest; - } else { - _logger.logger.log(`Crypto: adding new request to ` + `requestsByTxnId with id ${channel.transactionId} ${channel.roomId}`); - requestsMap.setRequestByChannel(channel, request); - } - return request; - } - beginKeyVerification(method, userId, deviceId, transactionId = null) { - let request; - if (transactionId) { - request = this.toDeviceVerificationRequests.getRequestBySenderAndTxnId(userId, transactionId); - if (!request) { - throw new Error(`No request found for user ${userId} with ` + `transactionId ${transactionId}`); - } - } else { - transactionId = _ToDeviceChannel.ToDeviceChannel.makeTransactionId(); - const channel = new _ToDeviceChannel.ToDeviceChannel(this.baseApis, userId, [deviceId], transactionId, deviceId); - request = new _VerificationRequest.VerificationRequest(channel, this.verificationMethods, this.baseApis); - this.toDeviceVerificationRequests.setRequestBySenderAndTxnId(userId, transactionId, request); - } - return request.beginKeyVerification(method, { - userId, - deviceId - }); - } - async legacyDeviceVerification(userId, deviceId, method) { - const transactionId = _ToDeviceChannel.ToDeviceChannel.makeTransactionId(); - const channel = new _ToDeviceChannel.ToDeviceChannel(this.baseApis, userId, [deviceId], transactionId, deviceId); - const request = new _VerificationRequest.VerificationRequest(channel, this.verificationMethods, this.baseApis); - this.toDeviceVerificationRequests.setRequestBySenderAndTxnId(userId, transactionId, request); - const verifier = request.beginKeyVerification(method, { - userId, - deviceId - }); - // either reject by an error from verify() while sending .start - // or resolve when the request receives the - // local (fake remote) echo for sending the .start event - await Promise.race([verifier.verify(), request.waitFor(r => r.started)]); - return request; - } - - /** - * Get information on the active olm sessions with a user - * <p> - * Returns a map from device id to an object with keys 'deviceIdKey' (the - * device's curve25519 identity key) and 'sessions' (an array of objects in the - * same format as that returned by - * {@link OlmDevice#getSessionInfoForDevice}). - * <p> - * This method is provided for debugging purposes. - * - * @param userId - id of user to inspect - */ - async getOlmSessionsForUser(userId) { - const devices = this.getStoredDevicesForUser(userId) || []; - const result = {}; - for (const device of devices) { - const deviceKey = device.getIdentityKey(); - const sessions = await this.olmDevice.getSessionInfoForDevice(deviceKey); - result[device.deviceId] = { - deviceIdKey: deviceKey, - sessions: sessions - }; - } - return result; - } - - /** - * Get the device which sent an event - * - * @param event - event to be checked - */ - getEventSenderDeviceInfo(event) { - const senderKey = event.getSenderKey(); - const algorithm = event.getWireContent().algorithm; - if (!senderKey || !algorithm) { - return null; - } - if (event.isKeySourceUntrusted()) { - // we got the key for this event from a source that we consider untrusted - return null; - } - - // senderKey is the Curve25519 identity key of the device which the event - // was sent from. In the case of Megolm, it's actually the Curve25519 - // identity key of the device which set up the Megolm session. - - const device = this.deviceList.getDeviceByIdentityKey(algorithm, senderKey); - if (device === null) { - // we haven't downloaded the details of this device yet. - return null; - } - - // so far so good, but now we need to check that the sender of this event - // hadn't advertised someone else's Curve25519 key as their own. We do that - // by checking the Ed25519 claimed by the event (or, in the case of megolm, - // the event which set up the megolm session), to check that it matches the - // fingerprint of the purported sending device. - // - // (see https://github.com/vector-im/vector-web/issues/2215) - - const claimedKey = event.getClaimedEd25519Key(); - if (!claimedKey) { - _logger.logger.warn("Event " + event.getId() + " claims no ed25519 key: " + "cannot verify sending device"); - return null; - } - if (claimedKey !== device.getFingerprint()) { - _logger.logger.warn("Event " + event.getId() + " claims ed25519 key " + claimedKey + " but sender device has key " + device.getFingerprint()); - return null; - } - return device; - } - - /** - * Get information about the encryption of an event - * - * @param event - event to be checked - * - * @returns An object with the fields: - * - encrypted: whether the event is encrypted (if not encrypted, some of the - * other properties may not be set) - * - senderKey: the sender's key - * - algorithm: the algorithm used to encrypt the event - * - authenticated: whether we can be sure that the owner of the senderKey - * sent the event - * - sender: the sender's device information, if available - * - mismatchedSender: if the event's ed25519 and curve25519 keys don't match - * (only meaningful if `sender` is set) - */ - getEventEncryptionInfo(event) { - var _event$getSenderKey, _this$deviceList$getD; - const ret = {}; - ret.senderKey = (_event$getSenderKey = event.getSenderKey()) !== null && _event$getSenderKey !== void 0 ? _event$getSenderKey : undefined; - ret.algorithm = event.getWireContent().algorithm; - if (!ret.senderKey || !ret.algorithm) { - ret.encrypted = false; - return ret; - } - ret.encrypted = true; - if (event.isKeySourceUntrusted()) { - // we got the key this event from somewhere else - // TODO: check if we can trust the forwarders. - ret.authenticated = false; - } else { - ret.authenticated = true; - } - - // senderKey is the Curve25519 identity key of the device which the event - // was sent from. In the case of Megolm, it's actually the Curve25519 - // identity key of the device which set up the Megolm session. - - ret.sender = (_this$deviceList$getD = this.deviceList.getDeviceByIdentityKey(ret.algorithm, ret.senderKey)) !== null && _this$deviceList$getD !== void 0 ? _this$deviceList$getD : undefined; - - // so far so good, but now we need to check that the sender of this event - // hadn't advertised someone else's Curve25519 key as their own. We do that - // by checking the Ed25519 claimed by the event (or, in the case of megolm, - // the event which set up the megolm session), to check that it matches the - // fingerprint of the purported sending device. - // - // (see https://github.com/vector-im/vector-web/issues/2215) - - const claimedKey = event.getClaimedEd25519Key(); - if (!claimedKey) { - _logger.logger.warn("Event " + event.getId() + " claims no ed25519 key: " + "cannot verify sending device"); - ret.mismatchedSender = true; - } - if (ret.sender && claimedKey !== ret.sender.getFingerprint()) { - _logger.logger.warn("Event " + event.getId() + " claims ed25519 key " + claimedKey + "but sender device has key " + ret.sender.getFingerprint()); - ret.mismatchedSender = true; - } - return ret; - } - - /** - * Forces the current outbound group session to be discarded such - * that another one will be created next time an event is sent. - * - * @param roomId - The ID of the room to discard the session for - * - * This should not normally be necessary. - */ - forceDiscardSession(roomId) { - const alg = this.roomEncryptors.get(roomId); - if (alg === undefined) throw new Error("Room not encrypted"); - if (alg.forceDiscardSession === undefined) { - throw new Error("Room encryption algorithm doesn't support session discarding"); - } - alg.forceDiscardSession(); - return Promise.resolve(); - } - - /** - * Configure a room to use encryption (ie, save a flag in the cryptoStore). - * - * @param roomId - The room ID to enable encryption in. - * - * @param config - The encryption config for the room. - * - * @param inhibitDeviceQuery - true to suppress device list query for - * users in the room (for now). In case lazy loading is enabled, - * the device query is always inhibited as the members are not tracked. - * - * @deprecated It is normally incorrect to call this method directly. Encryption - * is enabled by receiving an `m.room.encryption` event (which we may have sent - * previously). - */ - async setRoomEncryption(roomId, config, inhibitDeviceQuery) { - const room = this.clientStore.getRoom(roomId); - if (!room) { - throw new Error(`Unable to enable encryption tracking devices in unknown room ${roomId}`); - } - await this.setRoomEncryptionImpl(room, config); - if (!this.lazyLoadMembers && !inhibitDeviceQuery) { - this.deviceList.refreshOutdatedDeviceLists(); - } - } - - /** - * Set up encryption for a room. - * - * This is called when an <tt>m.room.encryption</tt> event is received. It saves a flag - * for the room in the cryptoStore (if it wasn't already set), sets up an "encryptor" for - * the room, and enables device-list tracking for the room. - * - * It does <em>not</em> initiate a device list query for the room. That is normally - * done once we finish processing the sync, in onSyncCompleted. - * - * @param room - The room to enable encryption in. - * @param config - The encryption config for the room. - */ - async setRoomEncryptionImpl(room, config) { - const roomId = room.roomId; - - // ignore crypto events with no algorithm defined - // This will happen if a crypto event is redacted before we fetch the room state - // It would otherwise just throw later as an unknown algorithm would, but we may - // as well catch this here - if (!config.algorithm) { - _logger.logger.log("Ignoring setRoomEncryption with no algorithm"); - return; - } - - // if state is being replayed from storage, we might already have a configuration - // for this room as they are persisted as well. - // We just need to make sure the algorithm is initialized in this case. - // However, if the new config is different, - // we should bail out as room encryption can't be changed once set. - const existingConfig = this.roomList.getRoomEncryption(roomId); - if (existingConfig) { - if (JSON.stringify(existingConfig) != JSON.stringify(config)) { - _logger.logger.error("Ignoring m.room.encryption event which requests " + "a change of config in " + roomId); - return; - } - } - // if we already have encryption in this room, we should ignore this event, - // as it would reset the encryption algorithm. - // This is at least expected to be called twice, as sync calls onCryptoEvent - // for both the timeline and state sections in the /sync response, - // the encryption event would appear in both. - // If it's called more than twice though, - // it signals a bug on client or server. - const existingAlg = this.roomEncryptors.get(roomId); - if (existingAlg) { - return; - } - - // _roomList.getRoomEncryption will not race with _roomList.setRoomEncryption - // because it first stores in memory. We should await the promise only - // after all the in-memory state (roomEncryptors and _roomList) has been updated - // to avoid races when calling this method multiple times. Hence keep a hold of the promise. - let storeConfigPromise = null; - if (!existingConfig) { - storeConfigPromise = this.roomList.setRoomEncryption(roomId, config); - } - const AlgClass = algorithms.ENCRYPTION_CLASSES.get(config.algorithm); - if (!AlgClass) { - throw new Error("Unable to encrypt with " + config.algorithm); - } - const alg = new AlgClass({ - userId: this.userId, - deviceId: this.deviceId, - crypto: this, - olmDevice: this.olmDevice, - baseApis: this.baseApis, - roomId, - config - }); - this.roomEncryptors.set(roomId, alg); - if (storeConfigPromise) { - await storeConfigPromise; - } - _logger.logger.log(`Enabling encryption in ${roomId}`); - - // we don't want to force a download of the full membership list of this room, but as soon as we have that - // list we can start tracking the device list. - if (room.membersLoaded()) { - await this.trackRoomDevicesImpl(room); - } else { - // wait for the membership list to be loaded - const onState = _state => { - room.off(_roomState.RoomStateEvent.Update, onState); - if (room.membersLoaded()) { - this.trackRoomDevicesImpl(room).catch(e => { - _logger.logger.error(`Error enabling device tracking in ${roomId}`, e); - }); - } - }; - room.on(_roomState.RoomStateEvent.Update, onState); - } - } - - /** - * Make sure we are tracking the device lists for all users in this room. - * - * @param roomId - The room ID to start tracking devices in. - * @returns when all devices for the room have been fetched and marked to track - * @deprecated there's normally no need to call this function: device list tracking - * will be enabled as soon as we have the full membership list. - */ - trackRoomDevices(roomId) { - const room = this.clientStore.getRoom(roomId); - if (!room) { - throw new Error(`Unable to start tracking devices in unknown room ${roomId}`); - } - return this.trackRoomDevicesImpl(room); - } - - /** - * Make sure we are tracking the device lists for all users in this room. - * - * This is normally called when we are about to send an encrypted event, to make sure - * we have all the devices in the room; but it is also called when processing an - * m.room.encryption state event (if lazy-loading is disabled), or when members are - * loaded (if lazy-loading is enabled), to prepare the device list. - * - * @param room - Room to enable device-list tracking in - */ - trackRoomDevicesImpl(room) { - const roomId = room.roomId; - const trackMembers = async () => { - // not an encrypted room - if (!this.roomEncryptors.has(roomId)) { - return; - } - _logger.logger.log(`Starting to track devices for room ${roomId} ...`); - const members = await room.getEncryptionTargetMembers(); - members.forEach(m => { - this.deviceList.startTrackingDeviceList(m.userId); - }); - }; - let promise = this.roomDeviceTrackingState[roomId]; - if (!promise) { - promise = trackMembers(); - this.roomDeviceTrackingState[roomId] = promise.catch(err => { - delete this.roomDeviceTrackingState[roomId]; - throw err; - }); - } - return promise; - } - - /** - * Try to make sure we have established olm sessions for all known devices for - * the given users. - * - * @param users - list of user ids - * @param force - If true, force a new Olm session to be created. Default false. - * - * @returns resolves once the sessions are complete, to - * an Object mapping from userId to deviceId to - * {@link OlmSessionResult} - */ - ensureOlmSessionsForUsers(users, force) { - // map user Id → DeviceInfo[] - const devicesByUser = new Map(); - for (const userId of users) { - const userDevices = []; - devicesByUser.set(userId, userDevices); - const devices = this.getStoredDevicesForUser(userId) || []; - for (const deviceInfo of devices) { - const key = deviceInfo.getIdentityKey(); - if (key == this.olmDevice.deviceCurve25519Key) { - // don't bother setting up session to ourself - continue; - } - if (deviceInfo.verified == DeviceVerification.BLOCKED) { - // don't bother setting up sessions with blocked users - continue; - } - userDevices.push(deviceInfo); - } - } - return olmlib.ensureOlmSessionsForDevices(this.olmDevice, this.baseApis, devicesByUser, force); - } - - /** - * Get a list containing all of the room keys - * - * @returns a list of session export objects - */ - async exportRoomKeys() { - const exportedSessions = []; - await this.cryptoStore.doTxn("readonly", [_indexeddbCryptoStore.IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS], txn => { - this.cryptoStore.getAllEndToEndInboundGroupSessions(txn, s => { - if (s === null) return; - const sess = this.olmDevice.exportInboundGroupSession(s.senderKey, s.sessionId, s.sessionData); - delete sess.first_known_index; - sess.algorithm = olmlib.MEGOLM_ALGORITHM; - exportedSessions.push(sess); - }); - }); - return exportedSessions; - } - - /** - * Import a list of room keys previously exported by exportRoomKeys - * - * @param keys - a list of session export objects - * @returns a promise which resolves once the keys have been imported - */ - importRoomKeys(keys, opts = {}) { - let successes = 0; - let failures = 0; - const total = keys.length; - function updateProgress() { - var _opts$progressCallbac; - (_opts$progressCallbac = opts.progressCallback) === null || _opts$progressCallbac === void 0 ? void 0 : _opts$progressCallbac.call(opts, { - stage: "load_keys", - successes, - failures, - total - }); - } - return Promise.all(keys.map(key => { - if (!key.room_id || !key.algorithm) { - _logger.logger.warn("ignoring room key entry with missing fields", key); - failures++; - if (opts.progressCallback) { - updateProgress(); - } - return null; - } - const alg = this.getRoomDecryptor(key.room_id, key.algorithm); - return alg.importRoomKey(key, opts).finally(() => { - successes++; - if (opts.progressCallback) { - updateProgress(); - } - }); - })).then(); - } - - /** - * Counts the number of end to end session keys that are waiting to be backed up - * @returns Promise which resolves to the number of sessions requiring backup - */ - countSessionsNeedingBackup() { - return this.backupManager.countSessionsNeedingBackup(); - } - - /** - * Perform any background tasks that can be done before a message is ready to - * send, in order to speed up sending of the message. - * - * @param room - the room the event is in - */ - prepareToEncrypt(room) { - const alg = this.roomEncryptors.get(room.roomId); - if (alg) { - alg.prepareToEncrypt(room); - } - } - - /** - * Encrypt an event according to the configuration of the room. - * - * @param event - event to be sent - * - * @param room - destination room. - * - * @returns Promise which resolves when the event has been - * encrypted, or null if nothing was needed - */ - async encryptEvent(event, room) { - const roomId = event.getRoomId(); - const alg = this.roomEncryptors.get(roomId); - if (!alg) { - // MatrixClient has already checked that this room should be encrypted, - // so this is an unexpected situation. - throw new Error("Room " + roomId + " was previously configured to use encryption, but is " + "no longer. Perhaps the homeserver is hiding the " + "configuration event."); - } - - // wait for all the room devices to be loaded - await this.trackRoomDevicesImpl(room); - let content = event.getContent(); - // If event has an m.relates_to then we need - // to put this on the wrapping event instead - const mRelatesTo = content["m.relates_to"]; - if (mRelatesTo) { - // Clone content here so we don't remove `m.relates_to` from the local-echo - content = Object.assign({}, content); - delete content["m.relates_to"]; - } - - // Treat element's performance metrics the same as `m.relates_to` (when present) - const elementPerfMetrics = content["io.element.performance_metrics"]; - if (elementPerfMetrics) { - content = Object.assign({}, content); - delete content["io.element.performance_metrics"]; - } - const encryptedContent = await alg.encryptMessage(room, event.getType(), content); - if (mRelatesTo) { - encryptedContent["m.relates_to"] = mRelatesTo; - } - if (elementPerfMetrics) { - encryptedContent["io.element.performance_metrics"] = elementPerfMetrics; - } - event.makeEncrypted("m.room.encrypted", encryptedContent, this.olmDevice.deviceCurve25519Key, this.olmDevice.deviceEd25519Key); - } - - /** - * Decrypt a received event - * - * - * @returns resolves once we have - * finished decrypting. Rejects with an `algorithms.DecryptionError` if there - * is a problem decrypting the event. - */ - async decryptEvent(event) { - if (event.isRedacted()) { - // Try to decrypt the redaction event, to support encrypted - // redaction reasons. If we can't decrypt, just fall back to using - // the original redacted_because. - const redactionEvent = new _event2.MatrixEvent(_objectSpread({ - room_id: event.getRoomId() - }, event.getUnsigned().redacted_because)); - let redactedBecause = event.getUnsigned().redacted_because; - if (redactionEvent.isEncrypted()) { - try { - const decryptedEvent = await this.decryptEvent(redactionEvent); - redactedBecause = decryptedEvent.clearEvent; - } catch (e) { - _logger.logger.warn("Decryption of redaction failed. Falling back to unencrypted event.", e); - } - } - return { - clearEvent: { - room_id: event.getRoomId(), - type: "m.room.message", - content: {}, - unsigned: { - redacted_because: redactedBecause - } - } - }; - } else { - const content = event.getWireContent(); - const alg = this.getRoomDecryptor(event.getRoomId(), content.algorithm); - return alg.decryptEvent(event); - } - } - - /** - * Handle the notification from /sync or /keys/changes that device lists have - * been changed. - * - * @param syncData - Object containing sync tokens associated with this sync - * @param syncDeviceLists - device_lists field from /sync, or response from - * /keys/changes - */ - async handleDeviceListChanges(syncData, syncDeviceLists) { - // Initial syncs don't have device change lists. We'll either get the complete list - // of changes for the interval or will have invalidated everything in willProcessSync - if (!syncData.oldSyncToken) return; - - // Here, we're relying on the fact that we only ever save the sync data after - // sucessfully saving the device list data, so we're guaranteed that the device - // list store is at least as fresh as the sync token from the sync store, ie. - // any device changes received in sync tokens prior to the 'next' token here - // have been processed and are reflected in the current device list. - // If we didn't make this assumption, we'd have to use the /keys/changes API - // to get key changes between the sync token in the device list and the 'old' - // sync token used here to make sure we didn't miss any. - await this.evalDeviceListChanges(syncDeviceLists); - } - - /** - * Send a request for some room keys, if we have not already done so - * - * @param resend - whether to resend the key request if there is - * already one - * - * @returns a promise that resolves when the key request is queued - */ - requestRoomKey(requestBody, recipients, resend = false) { - return this.outgoingRoomKeyRequestManager.queueRoomKeyRequest(requestBody, recipients, resend).then(() => { - if (this.sendKeyRequestsImmediately) { - this.outgoingRoomKeyRequestManager.sendQueuedRequests(); - } - }).catch(e => { - // this normally means we couldn't talk to the store - _logger.logger.error("Error requesting key for event", e); - }); - } - - /** - * Cancel any earlier room key request - * - * @param requestBody - parameters to match for cancellation - */ - cancelRoomKeyRequest(requestBody) { - this.outgoingRoomKeyRequestManager.cancelRoomKeyRequest(requestBody).catch(e => { - _logger.logger.warn("Error clearing pending room key requests", e); - }); - } - - /** - * Re-send any outgoing key requests, eg after verification - * @returns - */ - async cancelAndResendAllOutgoingKeyRequests() { - await this.outgoingRoomKeyRequestManager.cancelAndResendAllOutgoingRequests(); - } - - /** - * handle an m.room.encryption event - * - * @param room - in which the event was received - * @param event - encryption event to be processed - */ - async onCryptoEvent(room, event) { - const content = event.getContent(); - await this.setRoomEncryptionImpl(room, content); - } - - /** - * Called before the result of a sync is processed - * - * @param syncData - the data from the 'MatrixClient.sync' event - */ - async onSyncWillProcess(syncData) { - if (!syncData.oldSyncToken) { - // If there is no old sync token, we start all our tracking from - // scratch, so mark everything as untracked. onCryptoEvent will - // be called for all e2e rooms during the processing of the sync, - // at which point we'll start tracking all the users of that room. - _logger.logger.log("Initial sync performed - resetting device tracking state"); - this.deviceList.stopTrackingAllDeviceLists(); - // we always track our own device list (for key backups etc) - this.deviceList.startTrackingDeviceList(this.userId); - this.roomDeviceTrackingState = {}; - } - this.sendKeyRequestsImmediately = false; - } - - /** - * handle the completion of a /sync - * - * This is called after the processing of each successful /sync response. - * It is an opportunity to do a batch process on the information received. - * - * @param syncData - the data from the 'MatrixClient.sync' event - */ - async onSyncCompleted(syncData) { - var _syncData$nextSyncTok; - this.deviceList.setSyncToken((_syncData$nextSyncTok = syncData.nextSyncToken) !== null && _syncData$nextSyncTok !== void 0 ? _syncData$nextSyncTok : null); - this.deviceList.saveIfDirty(); - - // we always track our own device list (for key backups etc) - this.deviceList.startTrackingDeviceList(this.userId); - this.deviceList.refreshOutdatedDeviceLists(); - - // we don't start uploading one-time keys until we've caught up with - // to-device messages, to help us avoid throwing away one-time-keys that we - // are about to receive messages for - // (https://github.com/vector-im/element-web/issues/2782). - if (!syncData.catchingUp) { - this.maybeUploadOneTimeKeys(); - this.processReceivedRoomKeyRequests(); - - // likewise don't start requesting keys until we've caught up - // on to_device messages, otherwise we'll request keys that we're - // just about to get. - this.outgoingRoomKeyRequestManager.sendQueuedRequests(); - - // Sync has finished so send key requests straight away. - this.sendKeyRequestsImmediately = true; - } - } - - /** - * Trigger the appropriate invalidations and removes for a given - * device list - * - * @param deviceLists - device_lists field from /sync, or response from - * /keys/changes - */ - async evalDeviceListChanges(deviceLists) { - if (Array.isArray(deviceLists === null || deviceLists === void 0 ? void 0 : deviceLists.changed)) { - deviceLists.changed.forEach(u => { - this.deviceList.invalidateUserDeviceList(u); - }); - } - if (Array.isArray(deviceLists === null || deviceLists === void 0 ? void 0 : deviceLists.left) && deviceLists.left.length) { - // Check we really don't share any rooms with these users - // any more: the server isn't required to give us the - // exact correct set. - const e2eUserIds = new Set(await this.getTrackedE2eUsers()); - deviceLists.left.forEach(u => { - if (!e2eUserIds.has(u)) { - this.deviceList.stopTrackingDeviceList(u); - } - }); - } - } - - /** - * Get a list of all the IDs of users we share an e2e room with - * for which we are tracking devices already - * - * @returns List of user IDs - */ - async getTrackedE2eUsers() { - const e2eUserIds = []; - for (const room of this.getTrackedE2eRooms()) { - const members = await room.getEncryptionTargetMembers(); - for (const member of members) { - e2eUserIds.push(member.userId); - } - } - return e2eUserIds; - } - - /** - * Get a list of the e2e-enabled rooms we are members of, - * and for which we are already tracking the devices - * - * @returns - */ - getTrackedE2eRooms() { - return this.clientStore.getRooms().filter(room => { - // check for rooms with encryption enabled - const alg = this.roomEncryptors.get(room.roomId); - if (!alg) { - return false; - } - if (!this.roomDeviceTrackingState[room.roomId]) { - return false; - } - - // ignore any rooms which we have left - const myMembership = room.getMyMembership(); - return myMembership === "join" || myMembership === "invite"; - }); - } - - /** - * Encrypts and sends a given object via Olm to-device messages to a given - * set of devices. - * @param userDeviceInfoArr - the devices to send to - * @param payload - fields to include in the encrypted payload - * @returns Promise which - * resolves once the message has been encrypted and sent to the given - * userDeviceMap, and returns the `{ contentMap, deviceInfoByDeviceId }` - * of the successfully sent messages. - */ - async encryptAndSendToDevices(userDeviceInfoArr, payload) { - const toDeviceBatch = { - eventType: _event.EventType.RoomMessageEncrypted, - batch: [] - }; - try { - await Promise.all(userDeviceInfoArr.map(async ({ - userId, - deviceInfo - }) => { - const deviceId = deviceInfo.deviceId; - const encryptedContent = { - algorithm: olmlib.OLM_ALGORITHM, - sender_key: this.olmDevice.deviceCurve25519Key, - ciphertext: {}, - [_event.ToDeviceMessageId]: (0, _uuid.v4)() - }; - toDeviceBatch.batch.push({ - userId, - deviceId, - payload: encryptedContent - }); - await olmlib.ensureOlmSessionsForDevices(this.olmDevice, this.baseApis, new Map([[userId, [deviceInfo]]])); - await olmlib.encryptMessageForDevice(encryptedContent.ciphertext, this.userId, this.deviceId, this.olmDevice, userId, deviceInfo, payload); - })); - - // prune out any devices that encryptMessageForDevice could not encrypt for, - // in which case it will have just not added anything to the ciphertext object. - // There's no point sending messages to devices if we couldn't encrypt to them, - // since that's effectively a blank message. - toDeviceBatch.batch = toDeviceBatch.batch.filter(msg => { - if (Object.keys(msg.payload.ciphertext).length > 0) { - return true; - } else { - _logger.logger.log(`No ciphertext for device ${msg.userId}:${msg.deviceId}: pruning`); - return false; - } - }); - try { - await this.baseApis.queueToDevice(toDeviceBatch); - } catch (e) { - _logger.logger.error("sendToDevice failed", e); - throw e; - } - } catch (e) { - _logger.logger.error("encryptAndSendToDevices promises failed", e); - throw e; - } - } - async preprocessToDeviceMessages(events) { - // all we do here is filter out encrypted to-device messages with the wrong algorithm. Decryption - // happens later in decryptEvent, via the EventMapper - return events.filter(toDevice => { - var _toDevice$content; - if (toDevice.type === _event.EventType.RoomMessageEncrypted && !["m.olm.v1.curve25519-aes-sha2"].includes((_toDevice$content = toDevice.content) === null || _toDevice$content === void 0 ? void 0 : _toDevice$content.algorithm)) { - _logger.logger.log("Ignoring invalid encrypted to-device event from " + toDevice.sender); - return false; - } - return true; - }); - } - preprocessOneTimeKeyCounts(oneTimeKeysCounts) { - const currentCount = oneTimeKeysCounts.get("signed_curve25519") || 0; - this.updateOneTimeKeyCount(currentCount); - return Promise.resolve(); - } - preprocessUnusedFallbackKeys(unusedFallbackKeys) { - this.setNeedsNewFallback(!unusedFallbackKeys.has("signed_curve25519")); - return Promise.resolve(); - } - /** - * Handle a key event - * - * @internal - * @param event - key event - */ - onRoomKeyEvent(event) { - const content = event.getContent(); - if (!content.room_id || !content.algorithm) { - _logger.logger.error("key event is missing fields"); - return; - } - if (!this.backupManager.checkedForBackup) { - // don't bother awaiting on this - the important thing is that we retry if we - // haven't managed to check before - this.backupManager.checkAndStart(); - } - const alg = this.getRoomDecryptor(content.room_id, content.algorithm); - alg.onRoomKeyEvent(event); - } - - /** - * Handle a key withheld event - * - * @internal - * @param event - key withheld event - */ - onRoomKeyWithheldEvent(event) { - const content = event.getContent(); - if (content.code !== "m.no_olm" && (!content.room_id || !content.session_id) || !content.algorithm || !content.sender_key) { - _logger.logger.error("key withheld event is missing fields"); - return; - } - _logger.logger.info(`Got room key withheld event from ${event.getSender()} ` + `for ${content.algorithm} session ${content.sender_key}|${content.session_id} ` + `in room ${content.room_id} with code ${content.code} (${content.reason})`); - const alg = this.getRoomDecryptor(content.room_id, content.algorithm); - if (alg.onRoomKeyWithheldEvent) { - alg.onRoomKeyWithheldEvent(event); - } - if (!content.room_id) { - // retry decryption for all events sent by the sender_key. This will - // update the events to show a message indicating that the olm session was - // wedged. - const roomDecryptors = this.getRoomDecryptors(content.algorithm); - for (const decryptor of roomDecryptors) { - decryptor.retryDecryptionFromSender(content.sender_key); - } - } - } - - /** - * Handle a general key verification event. - * - * @internal - * @param event - verification start event - */ - onKeyVerificationMessage(event) { - if (!_ToDeviceChannel.ToDeviceChannel.validateEvent(event, this.baseApis)) { - return; - } - const createRequest = event => { - if (!_ToDeviceChannel.ToDeviceChannel.canCreateRequest(_ToDeviceChannel.ToDeviceChannel.getEventType(event))) { - return; - } - const content = event.getContent(); - const deviceId = content && content.from_device; - if (!deviceId) { - return; - } - const userId = event.getSender(); - const channel = new _ToDeviceChannel.ToDeviceChannel(this.baseApis, userId, [deviceId]); - return new _VerificationRequest.VerificationRequest(channel, this.verificationMethods, this.baseApis); - }; - this.handleVerificationEvent(event, this.toDeviceVerificationRequests, createRequest); - } - - /** - * Handle key verification requests sent as timeline events - * - * @internal - * @param event - the timeline event - * @param room - not used - * @param atStart - not used - * @param removed - not used - * @param whether - this is a live event - */ - - async handleVerificationEvent(event, requestsMap, createRequest, isLiveEvent = true) { - // Wait for event to get its final ID with pendingEventOrdering: "chronological", since DM channels depend on it. - if (event.isSending() && event.status != _event2.EventStatus.SENT) { - let eventIdListener; - let statusListener; - try { - await new Promise((resolve, reject) => { - eventIdListener = resolve; - statusListener = () => { - if (event.status == _event2.EventStatus.CANCELLED) { - reject(new Error("Event status set to CANCELLED.")); - } - }; - event.once(_event2.MatrixEventEvent.LocalEventIdReplaced, eventIdListener); - event.on(_event2.MatrixEventEvent.Status, statusListener); - }); - } catch (err) { - _logger.logger.error("error while waiting for the verification event to be sent: ", err); - return; - } finally { - event.removeListener(_event2.MatrixEventEvent.LocalEventIdReplaced, eventIdListener); - event.removeListener(_event2.MatrixEventEvent.Status, statusListener); - } - } - let request = requestsMap.getRequest(event); - let isNewRequest = false; - if (!request) { - request = createRequest(event); - // a request could not be made from this event, so ignore event - if (!request) { - _logger.logger.log(`Crypto: could not find VerificationRequest for ` + `${event.getType()}, and could not create one, so ignoring.`); - return; - } - isNewRequest = true; - requestsMap.setRequest(event, request); - } - event.setVerificationRequest(request); - try { - await request.channel.handleEvent(event, request, isLiveEvent); - } catch (err) { - _logger.logger.error("error while handling verification event", err); - } - const shouldEmit = isNewRequest && !request.initiatedByMe && !request.invalid && - // check it has enough events to pass the UNSENT stage - !request.observeOnly; - if (shouldEmit) { - this.baseApis.emit(CryptoEvent.VerificationRequest, request); - } - } - - /** - * Handle a toDevice event that couldn't be decrypted - * - * @internal - * @param event - undecryptable event - */ - async onToDeviceBadEncrypted(event) { - const content = event.getWireContent(); - const sender = event.getSender(); - const algorithm = content.algorithm; - const deviceKey = content.sender_key; - this.baseApis.emit(_client.ClientEvent.UndecryptableToDeviceEvent, event); - - // retry decryption for all events sent by the sender_key. This will - // update the events to show a message indicating that the olm session was - // wedged. - const retryDecryption = () => { - const roomDecryptors = this.getRoomDecryptors(olmlib.MEGOLM_ALGORITHM); - for (const decryptor of roomDecryptors) { - decryptor.retryDecryptionFromSender(deviceKey); - } - }; - if (sender === undefined || deviceKey === undefined || deviceKey === undefined) { - return; - } - - // check when we last forced a new session with this device: if we've already done so - // recently, don't do it again. - const lastNewSessionDevices = this.lastNewSessionForced.getOrCreate(sender); - const lastNewSessionForced = lastNewSessionDevices.getOrCreate(deviceKey); - if (lastNewSessionForced + MIN_FORCE_SESSION_INTERVAL_MS > Date.now()) { - _logger.logger.debug("New session already forced with device " + sender + ":" + deviceKey + " at " + lastNewSessionForced + ": not forcing another"); - await this.olmDevice.recordSessionProblem(deviceKey, "wedged", true); - retryDecryption(); - return; - } - - // establish a new olm session with this device since we're failing to decrypt messages - // on a current session. - // Note that an undecryptable message from another device could easily be spoofed - - // is there anything we can do to mitigate this? - let device = this.deviceList.getDeviceByIdentityKey(algorithm, deviceKey); - if (!device) { - // if we don't know about the device, fetch the user's devices again - // and retry before giving up - await this.downloadKeys([sender], false); - device = this.deviceList.getDeviceByIdentityKey(algorithm, deviceKey); - if (!device) { - _logger.logger.info("Couldn't find device for identity key " + deviceKey + ": not re-establishing session"); - await this.olmDevice.recordSessionProblem(deviceKey, "wedged", false); - retryDecryption(); - return; - } - } - const devicesByUser = new Map([[sender, [device]]]); - await olmlib.ensureOlmSessionsForDevices(this.olmDevice, this.baseApis, devicesByUser, true); - lastNewSessionDevices.set(deviceKey, Date.now()); - - // Now send a blank message on that session so the other side knows about it. - // (The keyshare request is sent in the clear so that won't do) - // We send this first such that, as long as the toDevice messages arrive in the - // same order we sent them, the other end will get this first, set up the new session, - // then get the keyshare request and send the key over this new session (because it - // is the session it has most recently received a message on). - const encryptedContent = { - algorithm: olmlib.OLM_ALGORITHM, - sender_key: this.olmDevice.deviceCurve25519Key, - ciphertext: {}, - [_event.ToDeviceMessageId]: (0, _uuid.v4)() - }; - await olmlib.encryptMessageForDevice(encryptedContent.ciphertext, this.userId, this.deviceId, this.olmDevice, sender, device, { - type: "m.dummy" - }); - await this.olmDevice.recordSessionProblem(deviceKey, "wedged", true); - retryDecryption(); - await this.baseApis.sendToDevice("m.room.encrypted", new Map([[sender, new Map([[device.deviceId, encryptedContent]])]])); - - // Most of the time this probably won't be necessary since we'll have queued up a key request when - // we failed to decrypt the message and will be waiting a bit for the key to arrive before sending - // it. This won't always be the case though so we need to re-send any that have already been sent - // to avoid races. - const requestsToResend = await this.outgoingRoomKeyRequestManager.getOutgoingSentRoomKeyRequest(sender, device.deviceId); - for (const keyReq of requestsToResend) { - this.requestRoomKey(keyReq.requestBody, keyReq.recipients, true); - } - } - - /** - * Handle a change in the membership state of a member of a room - * - * @internal - * @param event - event causing the change - * @param member - user whose membership changed - * @param oldMembership - previous membership - */ - onRoomMembership(event, member, oldMembership) { - // this event handler is registered on the *client* (as opposed to the room - // member itself), which means it is only called on changes to the *live* - // membership state (ie, it is not called when we back-paginate, nor when - // we load the state in the initialsync). - // - // Further, it is automatically registered and called when new members - // arrive in the room. - - const roomId = member.roomId; - const alg = this.roomEncryptors.get(roomId); - if (!alg) { - // not encrypting in this room - return; - } - // only mark users in this room as tracked if we already started tracking in this room - // this way we don't start device queries after sync on behalf of this room which we won't use - // the result of anyway, as we'll need to do a query again once all the members are fetched - // by calling _trackRoomDevices - if (roomId in this.roomDeviceTrackingState) { - var _this$clientStore$get; - if (member.membership == "join") { - _logger.logger.log("Join event for " + member.userId + " in " + roomId); - // make sure we are tracking the deviceList for this user - this.deviceList.startTrackingDeviceList(member.userId); - } else if (member.membership == "invite" && (_this$clientStore$get = this.clientStore.getRoom(roomId)) !== null && _this$clientStore$get !== void 0 && _this$clientStore$get.shouldEncryptForInvitedMembers()) { - _logger.logger.log("Invite event for " + member.userId + " in " + roomId); - this.deviceList.startTrackingDeviceList(member.userId); - } - } - alg.onRoomMembership(event, member, oldMembership); - } - - /** - * Called when we get an m.room_key_request event. - * - * @internal - * @param event - key request event - */ - onRoomKeyRequestEvent(event) { - const content = event.getContent(); - if (content.action === "request") { - // Queue it up for now, because they tend to arrive before the room state - // events at initial sync, and we want to see if we know anything about the - // room before passing them on to the app. - const req = new IncomingRoomKeyRequest(event); - this.receivedRoomKeyRequests.push(req); - } else if (content.action === "request_cancellation") { - const req = new IncomingRoomKeyRequestCancellation(event); - this.receivedRoomKeyRequestCancellations.push(req); - } - } - - /** - * Process any m.room_key_request events which were queued up during the - * current sync. - * - * @internal - */ - async processReceivedRoomKeyRequests() { - if (this.processingRoomKeyRequests) { - // we're still processing last time's requests; keep queuing new ones - // up for now. - return; - } - this.processingRoomKeyRequests = true; - try { - // we need to grab and clear the queues in the synchronous bit of this method, - // so that we don't end up racing with the next /sync. - const requests = this.receivedRoomKeyRequests; - this.receivedRoomKeyRequests = []; - const cancellations = this.receivedRoomKeyRequestCancellations; - this.receivedRoomKeyRequestCancellations = []; - - // Process all of the requests, *then* all of the cancellations. - // - // This makes sure that if we get a request and its cancellation in the - // same /sync result, then we process the request before the - // cancellation (and end up with a cancelled request), rather than the - // cancellation before the request (and end up with an outstanding - // request which should have been cancelled.) - await Promise.all(requests.map(req => this.processReceivedRoomKeyRequest(req))); - await Promise.all(cancellations.map(cancellation => this.processReceivedRoomKeyRequestCancellation(cancellation))); - } catch (e) { - _logger.logger.error(`Error processing room key requsts: ${e}`); - } finally { - this.processingRoomKeyRequests = false; - } - } - - /** - * Helper for processReceivedRoomKeyRequests - * - */ - async processReceivedRoomKeyRequest(req) { - const userId = req.userId; - const deviceId = req.deviceId; - const body = req.requestBody; - const roomId = body.room_id; - const alg = body.algorithm; - _logger.logger.log(`m.room_key_request from ${userId}:${deviceId}` + ` for ${roomId} / ${body.session_id} (id ${req.requestId})`); - if (userId !== this.userId) { - if (!this.roomEncryptors.get(roomId)) { - _logger.logger.debug(`room key request for unencrypted room ${roomId}`); - return; - } - const encryptor = this.roomEncryptors.get(roomId); - const device = this.deviceList.getStoredDevice(userId, deviceId); - if (!device) { - _logger.logger.debug(`Ignoring keyshare for unknown device ${userId}:${deviceId}`); - return; - } - try { - await encryptor.reshareKeyWithDevice(body.sender_key, body.session_id, userId, device); - } catch (e) { - _logger.logger.warn("Failed to re-share keys for session " + body.session_id + " with device " + userId + ":" + device.deviceId, e); - } - return; - } - if (deviceId === this.deviceId) { - // We'll always get these because we send room key requests to - // '*' (ie. 'all devices') which includes the sending device, - // so ignore requests from ourself because apart from it being - // very silly, it won't work because an Olm session cannot send - // messages to itself. - // The log here is probably superfluous since we know this will - // always happen, but let's log anyway for now just in case it - // causes issues. - _logger.logger.log("Ignoring room key request from ourselves"); - return; - } - - // todo: should we queue up requests we don't yet have keys for, - // in case they turn up later? - - // if we don't have a decryptor for this room/alg, we don't have - // the keys for the requested events, and can drop the requests. - if (!this.roomDecryptors.has(roomId)) { - _logger.logger.log(`room key request for unencrypted room ${roomId}`); - return; - } - const decryptor = this.roomDecryptors.get(roomId).get(alg); - if (!decryptor) { - _logger.logger.log(`room key request for unknown alg ${alg} in room ${roomId}`); - return; - } - if (!(await decryptor.hasKeysForKeyRequest(req))) { - _logger.logger.log(`room key request for unknown session ${roomId} / ` + body.session_id); - return; - } - req.share = () => { - decryptor.shareKeysWithDevice(req); - }; - - // if the device is verified already, share the keys - if (this.checkDeviceTrust(userId, deviceId).isVerified()) { - _logger.logger.log("device is already verified: sharing keys"); - req.share(); - return; - } - this.emit(CryptoEvent.RoomKeyRequest, req); - } - - /** - * Helper for processReceivedRoomKeyRequests - * - */ - async processReceivedRoomKeyRequestCancellation(cancellation) { - _logger.logger.log(`m.room_key_request cancellation for ${cancellation.userId}:` + `${cancellation.deviceId} (id ${cancellation.requestId})`); - - // we should probably only notify the app of cancellations we told it - // about, but we don't currently have a record of that, so we just pass - // everything through. - this.emit(CryptoEvent.RoomKeyRequestCancellation, cancellation); - } - - /** - * Get a decryptor for a given room and algorithm. - * - * If we already have a decryptor for the given room and algorithm, return - * it. Otherwise try to instantiate it. - * - * @internal - * - * @param roomId - room id for decryptor. If undefined, a temporary - * decryptor is instantiated. - * - * @param algorithm - crypto algorithm - * - * @throws {@link DecryptionError} if the algorithm is unknown - */ - getRoomDecryptor(roomId, algorithm) { - let decryptors; - let alg; - if (roomId) { - decryptors = this.roomDecryptors.get(roomId); - if (!decryptors) { - decryptors = new Map(); - this.roomDecryptors.set(roomId, decryptors); - } - alg = decryptors.get(algorithm); - if (alg) { - return alg; - } - } - const AlgClass = algorithms.DECRYPTION_CLASSES.get(algorithm); - if (!AlgClass) { - throw new algorithms.DecryptionError("UNKNOWN_ENCRYPTION_ALGORITHM", 'Unknown encryption algorithm "' + algorithm + '".'); - } - alg = new AlgClass({ - userId: this.userId, - crypto: this, - olmDevice: this.olmDevice, - baseApis: this.baseApis, - roomId: roomId !== null && roomId !== void 0 ? roomId : undefined - }); - if (decryptors) { - decryptors.set(algorithm, alg); - } - return alg; - } - - /** - * Get all the room decryptors for a given encryption algorithm. - * - * @param algorithm - The encryption algorithm - * - * @returns An array of room decryptors - */ - getRoomDecryptors(algorithm) { - const decryptors = []; - for (const d of this.roomDecryptors.values()) { - if (d.has(algorithm)) { - decryptors.push(d.get(algorithm)); - } - } - return decryptors; - } - - /** - * sign the given object with our ed25519 key - * - * @param obj - Object to which we will add a 'signatures' property - */ - async signObject(obj) { - const sigs = new Map(Object.entries(obj.signatures || {})); - const unsigned = obj.unsigned; - delete obj.signatures; - delete obj.unsigned; - const userSignatures = sigs.get(this.userId) || {}; - sigs.set(this.userId, userSignatures); - userSignatures["ed25519:" + this.deviceId] = await this.olmDevice.sign(_anotherJson.default.stringify(obj)); - obj.signatures = (0, _utils.recursiveMapToObject)(sigs); - if (unsigned !== undefined) obj.unsigned = unsigned; - } -} - -/** - * Fix up the backup key, that may be in the wrong format due to a bug in a - * migration step. Some backup keys were stored as a comma-separated list of - * integers, rather than a base64-encoded byte array. If this function is - * passed a string that looks like a list of integers rather than a base64 - * string, it will attempt to convert it to the right format. - * - * @param key - the key to check - * @returns If the key is in the wrong format, then the fixed - * key will be returned. Otherwise null will be returned. - * - */ -exports.Crypto = Crypto; -function fixBackupKey(key) { - if (typeof key !== "string" || key.indexOf(",") < 0) { - return null; - } - const fixedKey = Uint8Array.from(key.split(","), x => parseInt(x)); - return olmlib.encodeBase64(fixedKey); -} - -/** - * Represents a received m.room_key_request event - */ -class IncomingRoomKeyRequest { - /** user requesting the key */ - - /** device requesting the key */ - - /** unique id for the request */ - - /** - * callback which, when called, will ask - * the relevant crypto algorithm implementation to share the keys for - * this request. - */ - - constructor(event) { - (0, _defineProperty2.default)(this, "userId", void 0); - (0, _defineProperty2.default)(this, "deviceId", void 0); - (0, _defineProperty2.default)(this, "requestId", void 0); - (0, _defineProperty2.default)(this, "requestBody", void 0); - (0, _defineProperty2.default)(this, "share", void 0); - const content = event.getContent(); - this.userId = event.getSender(); - this.deviceId = content.requesting_device_id; - this.requestId = content.request_id; - this.requestBody = content.body || {}; - this.share = () => { - throw new Error("don't know how to share keys for this request yet"); - }; - } -} - -/** - * Represents a received m.room_key_request cancellation - */ -exports.IncomingRoomKeyRequest = IncomingRoomKeyRequest; -class IncomingRoomKeyRequestCancellation { - /** user requesting the cancellation */ - - /** device requesting the cancellation */ - - /** unique id for the request to be cancelled */ - - constructor(event) { - (0, _defineProperty2.default)(this, "userId", void 0); - (0, _defineProperty2.default)(this, "deviceId", void 0); - (0, _defineProperty2.default)(this, "requestId", void 0); - const content = event.getContent(); - this.userId = event.getSender(); - this.deviceId = content.requesting_device_id; - this.requestId = content.request_id; - } -} - -// a number of types are re-exported for backwards compatibility, in case any applications are referencing it. -//# sourceMappingURL=index.js.map
\ No newline at end of file |