diff options
Diffstat (limited to 'includes/external/matrix/node_modules/matrix-js-sdk/src/crypto/backup.ts')
-rw-r--r-- | includes/external/matrix/node_modules/matrix-js-sdk/src/crypto/backup.ts | 813 |
1 files changed, 0 insertions, 813 deletions
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/crypto/backup.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/crypto/backup.ts deleted file mode 100644 index d240bda..0000000 --- a/includes/external/matrix/node_modules/matrix-js-sdk/src/crypto/backup.ts +++ /dev/null @@ -1,813 +0,0 @@ -/* -Copyright 2021 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. -*/ - -/** - * Classes for dealing with key backup. - */ - -import type { IMegolmSessionData } from "../@types/crypto"; -import { MatrixClient } from "../client"; -import { logger } from "../logger"; -import { MEGOLM_ALGORITHM, verifySignature } from "./olmlib"; -import { DeviceInfo } from "./deviceinfo"; -import { DeviceTrustLevel } from "./CrossSigning"; -import { keyFromPassphrase } from "./key_passphrase"; -import { safeSet, sleep } from "../utils"; -import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store"; -import { encodeRecoveryKey } from "./recoverykey"; -import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from "./aes"; -import { - Curve25519SessionData, - IAes256AuthData, - ICurve25519AuthData, - IKeyBackupInfo, - IKeyBackupSession, -} from "./keybackup"; -import { UnstableValue } from "../NamespacedValue"; -import { CryptoEvent } from "./index"; -import { crypto } from "./crypto"; -import { HTTPError, MatrixError } from "../http-api"; - -const KEY_BACKUP_KEYS_PER_REQUEST = 200; -const KEY_BACKUP_CHECK_RATE_LIMIT = 5000; // ms - -type AuthData = IKeyBackupInfo["auth_data"]; - -type SigInfo = { - deviceId: string; - valid?: boolean | null; // true: valid, false: invalid, null: cannot attempt validation - device?: DeviceInfo | null; - crossSigningId?: boolean; - deviceTrust?: DeviceTrustLevel; -}; - -export type TrustInfo = { - usable: boolean; // is the backup trusted, true iff there is a sig that is valid & from a trusted device - sigs: SigInfo[]; - // eslint-disable-next-line camelcase - trusted_locally?: boolean; -}; - -export interface IKeyBackupCheck { - backupInfo?: IKeyBackupInfo; - trustInfo: TrustInfo; -} - -/* eslint-disable camelcase */ -export interface IPreparedKeyBackupVersion { - algorithm: string; - auth_data: AuthData; - recovery_key: string; - privateKey: Uint8Array; -} -/* eslint-enable camelcase */ - -/** A function used to get the secret key for a backup. - */ -type GetKey = () => Promise<ArrayLike<number>>; - -interface BackupAlgorithmClass { - algorithmName: string; - // initialize from an existing backup - init(authData: AuthData, getKey: GetKey): Promise<BackupAlgorithm>; - - // prepare a brand new backup - prepare(key?: string | Uint8Array | null): Promise<[Uint8Array, AuthData]>; - - checkBackupVersion(info: IKeyBackupInfo): void; -} - -interface BackupAlgorithm { - untrusted: boolean; - encryptSession(data: Record<string, any>): Promise<Curve25519SessionData | IEncryptedPayload>; - decryptSessions(ciphertexts: Record<string, IKeyBackupSession>): Promise<IMegolmSessionData[]>; - authData: AuthData; - keyMatches(key: ArrayLike<number>): Promise<boolean>; - free(): void; -} - -export interface IKeyBackup { - rooms: { - [roomId: string]: { - sessions: { - [sessionId: string]: IKeyBackupSession; - }; - }; - }; -} - -/** - * Manages the key backup. - */ -export class BackupManager { - private algorithm: BackupAlgorithm | undefined; - public backupInfo: IKeyBackupInfo | undefined; // The info dict from /room_keys/version - public checkedForBackup: boolean; // Have we checked the server for a backup we can use? - private sendingBackups: boolean; // Are we currently sending backups? - private sessionLastCheckAttemptedTime: Record<string, number> = {}; // When did we last try to check the server for a given session id? - - public constructor(private readonly baseApis: MatrixClient, public readonly getKey: GetKey) { - this.checkedForBackup = false; - this.sendingBackups = false; - } - - public get version(): string | undefined { - return this.backupInfo && this.backupInfo.version; - } - - /** - * Performs a quick check to ensure that the backup info looks sane. - * - * Throws an error if a problem is detected. - * - * @param info - the key backup info - */ - public static checkBackupVersion(info: IKeyBackupInfo): void { - const Algorithm = algorithmsByName[info.algorithm]; - if (!Algorithm) { - throw new Error("Unknown backup algorithm: " + info.algorithm); - } - if (typeof info.auth_data !== "object") { - throw new Error("Invalid backup data returned"); - } - return Algorithm.checkBackupVersion(info); - } - - public static makeAlgorithm(info: IKeyBackupInfo, getKey: GetKey): Promise<BackupAlgorithm> { - const Algorithm = algorithmsByName[info.algorithm]; - if (!Algorithm) { - throw new Error("Unknown backup algorithm"); - } - return Algorithm.init(info.auth_data, getKey); - } - - public async enableKeyBackup(info: IKeyBackupInfo): Promise<void> { - this.backupInfo = info; - if (this.algorithm) { - this.algorithm.free(); - } - - this.algorithm = await BackupManager.makeAlgorithm(info, this.getKey); - - this.baseApis.emit(CryptoEvent.KeyBackupStatus, true); - - // There may be keys left over from a partially completed backup, so - // schedule a send to check. - this.scheduleKeyBackupSend(); - } - - /** - * Disable backing up of keys. - */ - public disableKeyBackup(): void { - if (this.algorithm) { - this.algorithm.free(); - } - this.algorithm = undefined; - - this.backupInfo = undefined; - - this.baseApis.emit(CryptoEvent.KeyBackupStatus, false); - } - - public getKeyBackupEnabled(): boolean | null { - if (!this.checkedForBackup) { - return null; - } - return Boolean(this.algorithm); - } - - public async prepareKeyBackupVersion( - key?: string | Uint8Array | null, - algorithm?: string | undefined, - ): Promise<IPreparedKeyBackupVersion> { - const Algorithm = algorithm ? algorithmsByName[algorithm] : DefaultAlgorithm; - if (!Algorithm) { - throw new Error("Unknown backup algorithm"); - } - - const [privateKey, authData] = await Algorithm.prepare(key); - const recoveryKey = encodeRecoveryKey(privateKey)!; - return { - algorithm: Algorithm.algorithmName, - auth_data: authData, - recovery_key: recoveryKey, - privateKey, - }; - } - - public async createKeyBackupVersion(info: IKeyBackupInfo): Promise<void> { - this.algorithm = await BackupManager.makeAlgorithm(info, this.getKey); - } - - /** - * Check the server for an active key backup and - * if one is present and has a valid signature from - * one of the user's verified devices, start backing up - * to it. - */ - public async checkAndStart(): Promise<IKeyBackupCheck | null> { - logger.log("Checking key backup status..."); - if (this.baseApis.isGuest()) { - logger.log("Skipping key backup check since user is guest"); - this.checkedForBackup = true; - return null; - } - let backupInfo: IKeyBackupInfo | undefined; - try { - backupInfo = (await this.baseApis.getKeyBackupVersion()) ?? undefined; - } catch (e) { - logger.log("Error checking for active key backup", e); - if ((<HTTPError>e).httpStatus === 404) { - // 404 is returned when the key backup does not exist, so that - // counts as successfully checking. - this.checkedForBackup = true; - } - return null; - } - this.checkedForBackup = true; - - const trustInfo = await this.isKeyBackupTrusted(backupInfo); - - if (trustInfo.usable && !this.backupInfo) { - logger.log(`Found usable key backup v${backupInfo!.version}: enabling key backups`); - await this.enableKeyBackup(backupInfo!); - } else if (!trustInfo.usable && this.backupInfo) { - logger.log("No usable key backup: disabling key backup"); - this.disableKeyBackup(); - } else if (!trustInfo.usable && !this.backupInfo) { - logger.log("No usable key backup: not enabling key backup"); - } else if (trustInfo.usable && this.backupInfo) { - // may not be the same version: if not, we should switch - if (backupInfo!.version !== this.backupInfo.version) { - logger.log( - `On backup version ${this.backupInfo.version} but ` + - `found version ${backupInfo!.version}: switching.`, - ); - this.disableKeyBackup(); - await this.enableKeyBackup(backupInfo!); - // We're now using a new backup, so schedule all the keys we have to be - // uploaded to the new backup. This is a bit of a workaround to upload - // keys to a new backup in *most* cases, but it won't cover all cases - // because we don't remember what backup version we uploaded keys to: - // see https://github.com/vector-im/element-web/issues/14833 - await this.scheduleAllGroupSessionsForBackup(); - } else { - logger.log(`Backup version ${backupInfo!.version} still current`); - } - } - - return { backupInfo, trustInfo }; - } - - /** - * Forces a re-check of the key backup and enables/disables it - * as appropriate. - * - * @returns Object with backup info (as returned by - * getKeyBackupVersion) in backupInfo and - * trust information (as returned by isKeyBackupTrusted) - * in trustInfo. - */ - public async checkKeyBackup(): Promise<IKeyBackupCheck | null> { - this.checkedForBackup = false; - return this.checkAndStart(); - } - - /** - * Attempts to retrieve a session from a key backup, if enough time - * has elapsed since the last check for this session id. - */ - public async queryKeyBackupRateLimited( - targetRoomId: string | undefined, - targetSessionId: string | undefined, - ): Promise<void> { - if (!this.backupInfo) { - return; - } - - const now = new Date().getTime(); - if ( - !this.sessionLastCheckAttemptedTime[targetSessionId!] || - now - this.sessionLastCheckAttemptedTime[targetSessionId!] > KEY_BACKUP_CHECK_RATE_LIMIT - ) { - this.sessionLastCheckAttemptedTime[targetSessionId!] = now; - await this.baseApis.restoreKeyBackupWithCache(targetRoomId!, targetSessionId!, this.backupInfo, {}); - } - } - - /** - * Check if the given backup info is trusted. - * - * @param backupInfo - key backup info dict from /room_keys/version - */ - public async isKeyBackupTrusted(backupInfo?: IKeyBackupInfo): Promise<TrustInfo> { - const ret = { - usable: false, - trusted_locally: false, - sigs: [] as SigInfo[], - }; - - if (!backupInfo || !backupInfo.algorithm || !backupInfo.auth_data || !backupInfo.auth_data.signatures) { - logger.info("Key backup is absent or missing required data"); - return ret; - } - - const userId = this.baseApis.getUserId()!; - const privKey = await this.baseApis.crypto!.getSessionBackupPrivateKey(); - if (privKey) { - let algorithm: BackupAlgorithm | null = null; - try { - algorithm = await BackupManager.makeAlgorithm(backupInfo, async () => privKey); - - if (await algorithm.keyMatches(privKey)) { - logger.info("Backup is trusted locally"); - ret.trusted_locally = true; - } - } catch { - // do nothing -- if we have an error, then we don't mark it as - // locally trusted - } finally { - algorithm?.free(); - } - } - - const mySigs = backupInfo.auth_data.signatures[userId] || {}; - - for (const keyId of Object.keys(mySigs)) { - const keyIdParts = keyId.split(":"); - if (keyIdParts[0] !== "ed25519") { - logger.log("Ignoring unknown signature type: " + keyIdParts[0]); - continue; - } - // Could be a cross-signing master key, but just say this is the device - // ID for backwards compat - const sigInfo: SigInfo = { deviceId: keyIdParts[1] }; - - // first check to see if it's from our cross-signing key - const crossSigningId = this.baseApis.crypto!.crossSigningInfo.getId(); - if (crossSigningId === sigInfo.deviceId) { - sigInfo.crossSigningId = true; - try { - await verifySignature( - this.baseApis.crypto!.olmDevice, - backupInfo.auth_data, - userId, - sigInfo.deviceId, - crossSigningId, - ); - sigInfo.valid = true; - } catch (e) { - logger.warn("Bad signature from cross signing key " + crossSigningId, e); - sigInfo.valid = false; - } - ret.sigs.push(sigInfo); - continue; - } - - // Now look for a sig from a device - // At some point this can probably go away and we'll just support - // it being signed by the cross-signing master key - const device = this.baseApis.crypto!.deviceList.getStoredDevice(userId, sigInfo.deviceId); - if (device) { - sigInfo.device = device; - sigInfo.deviceTrust = this.baseApis.checkDeviceTrust(userId, sigInfo.deviceId); - try { - await verifySignature( - this.baseApis.crypto!.olmDevice, - backupInfo.auth_data, - userId, - device.deviceId, - device.getFingerprint(), - ); - sigInfo.valid = true; - } catch (e) { - logger.info( - "Bad signature from key ID " + - keyId + - " userID " + - this.baseApis.getUserId() + - " device ID " + - device.deviceId + - " fingerprint: " + - device.getFingerprint(), - backupInfo.auth_data, - e, - ); - sigInfo.valid = false; - } - } else { - sigInfo.valid = null; // Can't determine validity because we don't have the signing device - logger.info("Ignoring signature from unknown key " + keyId); - } - ret.sigs.push(sigInfo); - } - - ret.usable = ret.sigs.some((s) => { - return s.valid && ((s.device && s.deviceTrust?.isVerified()) || s.crossSigningId); - }); - return ret; - } - - /** - * Schedules sending all keys waiting to be sent to the backup, if not already - * scheduled. Retries if necessary. - * - * @param maxDelay - Maximum delay to wait in ms. 0 means no delay. - */ - public async scheduleKeyBackupSend(maxDelay = 10000): Promise<void> { - if (this.sendingBackups) return; - - this.sendingBackups = true; - - try { - // 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 sleep(delay); - let numFailures = 0; // number of consecutive failures - for (;;) { - if (!this.algorithm) { - return; - } - try { - const numBackedUp = await this.backupPendingKeys(KEY_BACKUP_KEYS_PER_REQUEST); - if (numBackedUp === 0) { - // no sessions left needing backup: we're done - return; - } - numFailures = 0; - } catch (err) { - numFailures++; - logger.log("Key backup request failed", err); - if ((<MatrixError>err).data) { - if ( - (<MatrixError>err).data.errcode == "M_NOT_FOUND" || - (<MatrixError>err).data.errcode == "M_WRONG_ROOM_KEYS_VERSION" - ) { - // Re-check key backup status on error, so we can be - // sure to present the current situation when asked. - await this.checkKeyBackup(); - // Backup version has changed or this backup version - // has been deleted - this.baseApis.crypto!.emit(CryptoEvent.KeyBackupFailed, (<MatrixError>err).data.errcode!); - throw err; - } - } - } - if (numFailures) { - // exponential backoff if we have failures - await sleep(1000 * Math.pow(2, Math.min(numFailures - 1, 4))); - } - } - } finally { - this.sendingBackups = false; - } - } - - /** - * Take some e2e keys waiting to be backed up and send them - * to the backup. - * - * @param limit - Maximum number of keys to back up - * @returns Number of sessions backed up - */ - public async backupPendingKeys(limit: number): Promise<number> { - const sessions = await this.baseApis.crypto!.cryptoStore.getSessionsNeedingBackup(limit); - if (!sessions.length) { - return 0; - } - - let remaining = await this.baseApis.crypto!.cryptoStore.countSessionsNeedingBackup(); - this.baseApis.crypto!.emit(CryptoEvent.KeyBackupSessionsRemaining, remaining); - - const rooms: IKeyBackup["rooms"] = {}; - for (const session of sessions) { - const roomId = session.sessionData!.room_id; - safeSet(rooms, roomId, rooms[roomId] || { sessions: {} }); - - const sessionData = this.baseApis.crypto!.olmDevice.exportInboundGroupSession( - session.senderKey, - session.sessionId, - session.sessionData!, - ); - sessionData.algorithm = MEGOLM_ALGORITHM; - - const forwardedCount = (sessionData.forwarding_curve25519_key_chain || []).length; - - const userId = this.baseApis.crypto!.deviceList.getUserByIdentityKey(MEGOLM_ALGORITHM, session.senderKey); - const device = - this.baseApis.crypto!.deviceList.getDeviceByIdentityKey(MEGOLM_ALGORITHM, session.senderKey) ?? - undefined; - const verified = this.baseApis.crypto!.checkDeviceInfoTrust(userId!, device).isVerified(); - - safeSet(rooms[roomId]["sessions"], session.sessionId, { - first_message_index: sessionData.first_known_index, - forwarded_count: forwardedCount, - is_verified: verified, - session_data: await this.algorithm!.encryptSession(sessionData), - }); - } - - await this.baseApis.sendKeyBackup(undefined, undefined, this.backupInfo!.version, { rooms }); - - await this.baseApis.crypto!.cryptoStore.unmarkSessionsNeedingBackup(sessions); - remaining = await this.baseApis.crypto!.cryptoStore.countSessionsNeedingBackup(); - this.baseApis.crypto!.emit(CryptoEvent.KeyBackupSessionsRemaining, remaining); - - return sessions.length; - } - - public async backupGroupSession(senderKey: string, sessionId: string): Promise<void> { - await this.baseApis.crypto!.cryptoStore.markSessionsNeedingBackup([ - { - senderKey: senderKey, - sessionId: sessionId, - }, - ]); - - if (this.backupInfo) { - // don't wait for this to complete: it will delay so - // happens in the background - this.scheduleKeyBackupSend(); - } - // if this.backupInfo is not set, then the keys will be backed up when - // this.enableKeyBackup is called - } - - /** - * Marks all group sessions as needing to be backed up and schedules them to - * upload in the background as soon as possible. - */ - public async scheduleAllGroupSessionsForBackup(): Promise<void> { - await this.flagAllGroupSessionsForBackup(); - - // Schedule keys to upload in the background as soon as possible. - this.scheduleKeyBackupSend(0 /* maxDelay */); - } - - /** - * Marks all group sessions as needing to be backed up without scheduling - * them to upload in the background. - * @returns Promise which resolves to the number of sessions now requiring a backup - * (which will be equal to the number of sessions in the store). - */ - public async flagAllGroupSessionsForBackup(): Promise<number> { - await this.baseApis.crypto!.cryptoStore.doTxn( - "readwrite", - [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS, IndexedDBCryptoStore.STORE_BACKUP], - (txn) => { - this.baseApis.crypto!.cryptoStore.getAllEndToEndInboundGroupSessions(txn, (session) => { - if (session !== null) { - this.baseApis.crypto!.cryptoStore.markSessionsNeedingBackup([session], txn); - } - }); - }, - ); - - const remaining = await this.baseApis.crypto!.cryptoStore.countSessionsNeedingBackup(); - this.baseApis.emit(CryptoEvent.KeyBackupSessionsRemaining, remaining); - return remaining; - } - - /** - * 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 - */ - public countSessionsNeedingBackup(): Promise<number> { - return this.baseApis.crypto!.cryptoStore.countSessionsNeedingBackup(); - } -} - -export class Curve25519 implements BackupAlgorithm { - public static algorithmName = "m.megolm_backup.v1.curve25519-aes-sha2"; - - public constructor( - public authData: ICurve25519AuthData, - private publicKey: any, // FIXME: PkEncryption - private getKey: () => Promise<Uint8Array>, - ) {} - - public static async init(authData: AuthData, getKey: () => Promise<Uint8Array>): Promise<Curve25519> { - if (!authData || !("public_key" in authData)) { - throw new Error("auth_data missing required information"); - } - const publicKey = new global.Olm.PkEncryption(); - publicKey.set_recipient_key(authData.public_key); - return new Curve25519(authData as ICurve25519AuthData, publicKey, getKey); - } - - public static async prepare(key?: string | Uint8Array | null): Promise<[Uint8Array, AuthData]> { - const decryption = new global.Olm.PkDecryption(); - try { - const authData: Partial<ICurve25519AuthData> = {}; - if (!key) { - authData.public_key = decryption.generate_key(); - } else if (key instanceof Uint8Array) { - authData.public_key = decryption.init_with_private_key(key); - } else { - const derivation = await keyFromPassphrase(key); - authData.private_key_salt = derivation.salt; - authData.private_key_iterations = derivation.iterations; - authData.public_key = decryption.init_with_private_key(derivation.key); - } - const publicKey = new global.Olm.PkEncryption(); - publicKey.set_recipient_key(authData.public_key); - - return [decryption.get_private_key(), authData as AuthData]; - } finally { - decryption.free(); - } - } - - public static checkBackupVersion(info: IKeyBackupInfo): void { - if (!("public_key" in info.auth_data)) { - throw new Error("Invalid backup data returned"); - } - } - - public get untrusted(): boolean { - return true; - } - - public async encryptSession(data: Record<string, any>): Promise<Curve25519SessionData> { - const plainText: Record<string, any> = Object.assign({}, data); - delete plainText.session_id; - delete plainText.room_id; - delete plainText.first_known_index; - return this.publicKey.encrypt(JSON.stringify(plainText)); - } - - public async decryptSessions( - sessions: Record<string, IKeyBackupSession<Curve25519SessionData>>, - ): Promise<IMegolmSessionData[]> { - const privKey = await this.getKey(); - const decryption = new global.Olm.PkDecryption(); - try { - const backupPubKey = decryption.init_with_private_key(privKey); - - if (backupPubKey !== this.authData.public_key) { - throw new MatrixError({ errcode: MatrixClient.RESTORE_BACKUP_ERROR_BAD_KEY }); - } - - const keys: IMegolmSessionData[] = []; - - for (const [sessionId, sessionData] of Object.entries(sessions)) { - try { - const decrypted = JSON.parse( - decryption.decrypt( - sessionData.session_data.ephemeral, - sessionData.session_data.mac, - sessionData.session_data.ciphertext, - ), - ); - decrypted.session_id = sessionId; - keys.push(decrypted); - } catch (e) { - logger.log("Failed to decrypt megolm session from backup", e, sessionData); - } - } - return keys; - } finally { - decryption.free(); - } - } - - public async keyMatches(key: Uint8Array): Promise<boolean> { - const decryption = new global.Olm.PkDecryption(); - let pubKey: string; - try { - pubKey = decryption.init_with_private_key(key); - } finally { - decryption.free(); - } - - return pubKey === this.authData.public_key; - } - - public free(): void { - this.publicKey.free(); - } -} - -function randomBytes(size: number): Uint8Array { - const buf = new Uint8Array(size); - crypto.getRandomValues(buf); - return buf; -} - -const UNSTABLE_MSC3270_NAME = new UnstableValue( - "m.megolm_backup.v1.aes-hmac-sha2", - "org.matrix.msc3270.v1.aes-hmac-sha2", -); - -export class Aes256 implements BackupAlgorithm { - public static algorithmName = UNSTABLE_MSC3270_NAME.name; - - public constructor(public readonly authData: IAes256AuthData, private readonly key: Uint8Array) {} - - public static async init(authData: IAes256AuthData, getKey: () => Promise<Uint8Array>): Promise<Aes256> { - if (!authData) { - throw new Error("auth_data missing"); - } - const key = await getKey(); - if (authData.mac) { - const { mac } = await calculateKeyCheck(key, authData.iv); - if (authData.mac.replace(/=+$/g, "") !== mac.replace(/=+/g, "")) { - throw new Error("Key does not match"); - } - } - return new Aes256(authData, key); - } - - public static async prepare(key?: string | Uint8Array | null): Promise<[Uint8Array, AuthData]> { - let outKey: Uint8Array; - const authData: Partial<IAes256AuthData> = {}; - if (!key) { - outKey = randomBytes(32); - } else if (key instanceof Uint8Array) { - outKey = new Uint8Array(key); - } else { - const derivation = await keyFromPassphrase(key); - authData.private_key_salt = derivation.salt; - authData.private_key_iterations = derivation.iterations; - outKey = derivation.key; - } - - const { iv, mac } = await calculateKeyCheck(outKey); - authData.iv = iv; - authData.mac = mac; - - return [outKey, authData as AuthData]; - } - - public static checkBackupVersion(info: IKeyBackupInfo): void { - if (!("iv" in info.auth_data && "mac" in info.auth_data)) { - throw new Error("Invalid backup data returned"); - } - } - - public get untrusted(): boolean { - return false; - } - - public encryptSession(data: Record<string, any>): Promise<IEncryptedPayload> { - const plainText: Record<string, any> = Object.assign({}, data); - delete plainText.session_id; - delete plainText.room_id; - delete plainText.first_known_index; - return encryptAES(JSON.stringify(plainText), this.key, data.session_id); - } - - public async decryptSessions( - sessions: Record<string, IKeyBackupSession<IEncryptedPayload>>, - ): Promise<IMegolmSessionData[]> { - const keys: IMegolmSessionData[] = []; - - for (const [sessionId, sessionData] of Object.entries(sessions)) { - try { - const decrypted = JSON.parse(await decryptAES(sessionData.session_data, this.key, sessionId)); - decrypted.session_id = sessionId; - keys.push(decrypted); - } catch (e) { - logger.log("Failed to decrypt megolm session from backup", e, sessionData); - } - } - return keys; - } - - public async keyMatches(key: Uint8Array): Promise<boolean> { - if (this.authData.mac) { - const { mac } = await calculateKeyCheck(key, this.authData.iv); - return this.authData.mac.replace(/=+$/g, "") === mac.replace(/=+/g, ""); - } else { - // if we have no information, we have to assume the key is right - return true; - } - } - - public free(): void { - this.key.fill(0); - } -} - -export const algorithmsByName: Record<string, BackupAlgorithmClass> = { - [Curve25519.algorithmName]: Curve25519, - [Aes256.algorithmName]: Aes256, -}; - -export const DefaultAlgorithm: BackupAlgorithmClass = Curve25519; |