UNPKG

matrix-js-sdk

Version:
259 lines (244 loc) 10.2 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.RustBackupManager = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _logger = require("../logger"); var _httpApi = require("../http-api"); var _crypto = require("../crypto"); var _typedEventEmitter = require("../models/typed-event-emitter"); var _utils = require("../utils"); /* Copyright 2023 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ /** * @internal */ class RustBackupManager extends _typedEventEmitter.TypedEventEmitter { constructor(olmMachine, http, outgoingRequestProcessor) { super(); this.olmMachine = olmMachine; this.http = http; this.outgoingRequestProcessor = outgoingRequestProcessor; /** Have we checked if there is a backup on the server which we can use */ (0, _defineProperty2.default)(this, "checkedForBackup", false); (0, _defineProperty2.default)(this, "activeBackupVersion", null); (0, _defineProperty2.default)(this, "stopped", false); /** whether {@link backupKeysLoop} is currently running */ (0, _defineProperty2.default)(this, "backupKeysLoopRunning", false); (0, _defineProperty2.default)(this, "keyBackupCheckInProgress", null); } /** * Tells the RustBackupManager to stop. * The RustBackupManager is scheduling background uploads of keys to the backup, this * call allows to cancel the process when the client is stoppped. */ stop() { this.stopped = true; } /** * Get the backup version we are currently backing up to, if any */ async getActiveBackupVersion() { if (!this.olmMachine.isBackupEnabled()) return null; return this.activeBackupVersion; } /** * Determine if a key backup can be trusted. * * @param info - key backup info dict from {@link MatrixClient#getKeyBackupVersion}. */ async isKeyBackupTrusted(info) { var _backupKeys$decryptio, _info$auth_data; const signatureVerification = await this.olmMachine.verifyBackup(info); const backupKeys = await this.olmMachine.getBackupKeys(); const pubKeyForSavedPrivateKey = backupKeys === null || backupKeys === void 0 || (_backupKeys$decryptio = backupKeys.decryptionKey) === null || _backupKeys$decryptio === void 0 ? void 0 : _backupKeys$decryptio.megolmV1PublicKey; const backupMatchesSavedPrivateKey = info.algorithm === (pubKeyForSavedPrivateKey === null || pubKeyForSavedPrivateKey === void 0 ? void 0 : pubKeyForSavedPrivateKey.algorithm) && ((_info$auth_data = info.auth_data) === null || _info$auth_data === void 0 ? void 0 : _info$auth_data.public_key) === pubKeyForSavedPrivateKey.publicKeyBase64; return { matchesDecryptionKey: backupMatchesSavedPrivateKey, trusted: signatureVerification.trusted() }; } /** * Re-check the key backup and enable/disable it as appropriate. * * @param force - whether we should force a re-check even if one has already happened. */ checkKeyBackupAndEnable(force) { if (!force && this.checkedForBackup) { return Promise.resolve(null); } // make sure there is only one check going on at a time if (!this.keyBackupCheckInProgress) { this.keyBackupCheckInProgress = this.doCheckKeyBackup().finally(() => { this.keyBackupCheckInProgress = null; }); } return this.keyBackupCheckInProgress; } /** Helper for `checkKeyBackup` */ async doCheckKeyBackup() { _logger.logger.log("Checking key backup status..."); let backupInfo = null; try { backupInfo = await this.requestKeyBackupVersion(); } catch (e) { _logger.logger.warn("Error checking for active key backup", e); return null; } this.checkedForBackup = true; if (backupInfo && !backupInfo.version) { _logger.logger.warn("active backup lacks a useful 'version'; ignoring it"); } const activeVersion = await this.getActiveBackupVersion(); if (!backupInfo) { if (activeVersion !== null) { _logger.logger.log("No key backup present on server: disabling key backup"); await this.disableKeyBackup(); } else { _logger.logger.log("No key backup present on server: not enabling key backup"); } return null; } const trustInfo = await this.isKeyBackupTrusted(backupInfo); if (!trustInfo.trusted) { if (activeVersion !== null) { _logger.logger.log("Key backup present on server but not trusted: disabling key backup"); await this.disableKeyBackup(); } else { _logger.logger.log("Key backup present on server but not trusted: not enabling key backup"); } } else { if (activeVersion === null) { _logger.logger.log(`Found usable key backup v${backupInfo.version}: enabling key backups`); await this.enableKeyBackup(backupInfo); } else if (activeVersion !== backupInfo.version) { _logger.logger.log(`On backup version ${activeVersion} but found version ${backupInfo.version}: switching.`); // This will remove any pending backup request, remove the backup key and reset the backup state of each room key we have. await this.disableKeyBackup(); // Enabling will now trigger re-upload of all the keys await this.enableKeyBackup(backupInfo); } else { _logger.logger.log(`Backup version ${backupInfo.version} still current`); } } return { backupInfo, trustInfo }; } async enableKeyBackup(backupInfo) { // we know for certain it must be a Curve25519 key, because we have verified it and only Curve25519 // keys can be verified. // // we also checked it has a valid `version`. await this.olmMachine.enableBackupV1(backupInfo.auth_data.public_key, backupInfo.version); this.activeBackupVersion = backupInfo.version; this.emit(_crypto.CryptoEvent.KeyBackupStatus, true); this.backupKeysLoop(); } /** * Restart the backup key loop if there is an active trusted backup. * Doesn't try to check the backup server side. To be called when a new * megolm key is known locally. */ async maybeUploadKey() { if (this.activeBackupVersion != null) { this.backupKeysLoop(); } } async disableKeyBackup() { await this.olmMachine.disableBackup(); this.activeBackupVersion = null; this.emit(_crypto.CryptoEvent.KeyBackupStatus, false); } async backupKeysLoop(maxDelay = 10000) { if (this.backupKeysLoopRunning) { _logger.logger.log(`Backup loop already running`); return; } this.backupKeysLoopRunning = true; _logger.logger.log(`Starting loop for ${this.activeBackupVersion}.`); // wait between 0 and `maxDelay` seconds, to avoid backup // requests from different clients hitting the server all at // the same time when a new key is sent const delay = Math.random() * maxDelay; await (0, _utils.sleep)(delay); try { let numFailures = 0; // number of consecutive network failures for exponential backoff while (!this.stopped) { // Get a batch of room keys to upload const request = await this.olmMachine.backupRoomKeys(); if (!request || this.stopped || !this.activeBackupVersion) { _logger.logger.log(`Ending loop for ${this.activeBackupVersion}.`); return; } try { await this.outgoingRequestProcessor.makeOutgoingRequest(request); numFailures = 0; const keyCount = await this.olmMachine.roomKeyCounts(); const remaining = keyCount.total - keyCount.backedUp; this.emit(_crypto.CryptoEvent.KeyBackupSessionsRemaining, remaining); } catch (err) { numFailures++; _logger.logger.error("Error processing backup request for rust crypto-sdk", err); if (err instanceof _httpApi.MatrixError) { const errCode = err.data.errcode; if (errCode == "M_NOT_FOUND" || errCode == "M_WRONG_ROOM_KEYS_VERSION") { await this.disableKeyBackup(); this.emit(_crypto.CryptoEvent.KeyBackupFailed, err.data.errcode); // There was an active backup and we are out of sync with the server // force a check server side this.backupKeysLoopRunning = false; this.checkKeyBackupAndEnable(true); return; } else if (errCode == "M_LIMIT_EXCEEDED") { // wait for that and then continue? const waitTime = err.data.retry_after_ms; if (waitTime > 0) { (0, _utils.sleep)(waitTime); continue; } // else go to the normal backoff } } // Some other errors (mx, network, or CORS or invalid urls?) anyhow backoff // exponential backoff if we have failures await (0, _utils.sleep)(1000 * Math.pow(2, Math.min(numFailures - 1, 4))); } } } finally { this.backupKeysLoopRunning = false; } } /** * Get information about the current key backup from the server * * @returns Information object from API or null if there is no active backup. */ async requestKeyBackupVersion() { try { return await this.http.authedRequest(_httpApi.Method.Get, "/room_keys/version", undefined, undefined, { prefix: _httpApi.ClientPrefix.V3 }); } catch (e) { if (e.errcode === "M_NOT_FOUND") { return null; } else { throw e; } } } } exports.RustBackupManager = RustBackupManager; //# sourceMappingURL=backup.js.map