You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-23 17:02:25 +03:00
Implement CryptoApi.checkKeyBackupAndEnable (#3633)
* Implement `CryptoApi.checkKeyBackup` * Deprecate `MatrixClient.enableKeyBackup`. * fix integ test * more tests --------- Co-authored-by: valere <valeref@matrix.org>
This commit is contained in:
committed by
GitHub
parent
16ddcb0ed0
commit
3f7af189e4
@@ -176,7 +176,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("megolm-keys backup (%s)", (backe
|
|||||||
expect(event.getContent()).toEqual("testytest");
|
expect(event.getContent()).toEqual("testytest");
|
||||||
});
|
});
|
||||||
|
|
||||||
oldBackendOnly("getActiveSessionBackupVersion() should give correct result", async function () {
|
it("getActiveSessionBackupVersion() should give correct result", async function () {
|
||||||
// 404 means that there is no active backup
|
// 404 means that there is no active backup
|
||||||
fetchMock.get("express:/_matrix/client/v3/room_keys/version", 404);
|
fetchMock.get("express:/_matrix/client/v3/room_keys/version", 404);
|
||||||
|
|
||||||
@@ -187,7 +187,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("megolm-keys backup (%s)", (backe
|
|||||||
// tell Alice to trust the dummy device that signed the backup
|
// tell Alice to trust the dummy device that signed the backup
|
||||||
await waitForDeviceList();
|
await waitForDeviceList();
|
||||||
await aliceCrypto.setDeviceVerified(testData.TEST_USER_ID, testData.TEST_DEVICE_ID);
|
await aliceCrypto.setDeviceVerified(testData.TEST_USER_ID, testData.TEST_DEVICE_ID);
|
||||||
await aliceClient.checkKeyBackup();
|
await aliceCrypto.checkKeyBackupAndEnable();
|
||||||
|
|
||||||
// At this point there is no backup
|
// At this point there is no backup
|
||||||
let backupStatus: string | null;
|
let backupStatus: string | null;
|
||||||
@@ -201,9 +201,9 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("megolm-keys backup (%s)", (backe
|
|||||||
overwriteRoutes: true,
|
overwriteRoutes: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const checked = await aliceClient.checkKeyBackup();
|
const checked = await aliceCrypto.checkKeyBackupAndEnable();
|
||||||
expect(checked?.backupInfo?.version).toStrictEqual(unsignedBackup.version);
|
expect(checked?.backupInfo?.version).toStrictEqual(unsignedBackup.version);
|
||||||
expect(checked?.trustInfo?.usable).toBeFalsy();
|
expect(checked?.trustInfo?.trusted).toBeFalsy();
|
||||||
|
|
||||||
backupStatus = await aliceCrypto.getActiveSessionBackupVersion();
|
backupStatus = await aliceCrypto.getActiveSessionBackupVersion();
|
||||||
expect(backupStatus).toBeNull();
|
expect(backupStatus).toBeNull();
|
||||||
@@ -222,8 +222,8 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("megolm-keys backup (%s)", (backe
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const validCheck = await aliceClient.checkKeyBackup();
|
const validCheck = await aliceCrypto.checkKeyBackupAndEnable();
|
||||||
expect(validCheck?.trustInfo?.usable).toStrictEqual(true);
|
expect(validCheck?.trustInfo?.trusted).toStrictEqual(true);
|
||||||
|
|
||||||
await backupPromise;
|
await backupPromise;
|
||||||
|
|
||||||
@@ -286,6 +286,128 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("megolm-keys backup (%s)", (backe
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("checkKeyBackupAndEnable", () => {
|
||||||
|
it("enables a backup signed by a trusted device", async () => {
|
||||||
|
aliceClient = await initTestClient();
|
||||||
|
const aliceCrypto = aliceClient.getCrypto()!;
|
||||||
|
|
||||||
|
// tell Alice to trust the dummy device that signed the backup
|
||||||
|
await aliceClient.startClient();
|
||||||
|
await waitForDeviceList();
|
||||||
|
await aliceCrypto.setDeviceVerified(testData.TEST_USER_ID, testData.TEST_DEVICE_ID);
|
||||||
|
|
||||||
|
fetchMock.get("path:/_matrix/client/v3/room_keys/version", testData.SIGNED_BACKUP_DATA);
|
||||||
|
|
||||||
|
const result = await aliceCrypto.checkKeyBackupAndEnable();
|
||||||
|
expect(result).toBeTruthy();
|
||||||
|
expect(result!.trustInfo).toEqual({ trusted: true, matchesDecryptionKey: false });
|
||||||
|
expect(await aliceCrypto.getActiveSessionBackupVersion()).toEqual(testData.SIGNED_BACKUP_DATA.version);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not enable a backup signed by an untrusted device", async () => {
|
||||||
|
aliceClient = await initTestClient();
|
||||||
|
const aliceCrypto = aliceClient.getCrypto()!;
|
||||||
|
|
||||||
|
// download the device list, to match the trusted case
|
||||||
|
await aliceClient.startClient();
|
||||||
|
await waitForDeviceList();
|
||||||
|
|
||||||
|
fetchMock.get("path:/_matrix/client/v3/room_keys/version", testData.SIGNED_BACKUP_DATA);
|
||||||
|
|
||||||
|
const result = await aliceCrypto.checkKeyBackupAndEnable();
|
||||||
|
expect(result).toBeTruthy();
|
||||||
|
expect(result!.trustInfo).toEqual({ trusted: false, matchesDecryptionKey: false });
|
||||||
|
expect(await aliceCrypto.getActiveSessionBackupVersion()).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disables backup when a new untrusted backup is available", async () => {
|
||||||
|
aliceClient = await initTestClient();
|
||||||
|
const aliceCrypto = aliceClient.getCrypto()!;
|
||||||
|
|
||||||
|
// tell Alice to trust the dummy device that signed the backup
|
||||||
|
await aliceClient.startClient();
|
||||||
|
await waitForDeviceList();
|
||||||
|
await aliceCrypto.setDeviceVerified(testData.TEST_USER_ID, testData.TEST_DEVICE_ID);
|
||||||
|
|
||||||
|
fetchMock.get("path:/_matrix/client/v3/room_keys/version", testData.SIGNED_BACKUP_DATA);
|
||||||
|
|
||||||
|
const result = await aliceCrypto.checkKeyBackupAndEnable();
|
||||||
|
expect(result).toBeTruthy();
|
||||||
|
expect(await aliceCrypto.getActiveSessionBackupVersion()).toEqual(testData.SIGNED_BACKUP_DATA.version);
|
||||||
|
|
||||||
|
const unsignedBackup = JSON.parse(JSON.stringify(testData.SIGNED_BACKUP_DATA));
|
||||||
|
delete unsignedBackup.auth_data.signatures;
|
||||||
|
unsignedBackup.version = "2";
|
||||||
|
|
||||||
|
fetchMock.get("path:/_matrix/client/v3/room_keys/version", unsignedBackup, {
|
||||||
|
overwriteRoutes: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await aliceCrypto.checkKeyBackupAndEnable();
|
||||||
|
expect(await aliceCrypto.getActiveSessionBackupVersion()).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("switches backup when a new trusted backup is available", async () => {
|
||||||
|
aliceClient = await initTestClient();
|
||||||
|
const aliceCrypto = aliceClient.getCrypto()!;
|
||||||
|
|
||||||
|
// tell Alice to trust the dummy device that signed the backup
|
||||||
|
await aliceClient.startClient();
|
||||||
|
await waitForDeviceList();
|
||||||
|
await aliceCrypto.setDeviceVerified(testData.TEST_USER_ID, testData.TEST_DEVICE_ID);
|
||||||
|
|
||||||
|
fetchMock.get("path:/_matrix/client/v3/room_keys/version", testData.SIGNED_BACKUP_DATA);
|
||||||
|
|
||||||
|
const result = await aliceCrypto.checkKeyBackupAndEnable();
|
||||||
|
expect(result).toBeTruthy();
|
||||||
|
expect(await aliceCrypto.getActiveSessionBackupVersion()).toEqual(testData.SIGNED_BACKUP_DATA.version);
|
||||||
|
|
||||||
|
const newBackupVersion = "2";
|
||||||
|
const unsignedBackup = JSON.parse(JSON.stringify(testData.SIGNED_BACKUP_DATA));
|
||||||
|
unsignedBackup.version = newBackupVersion;
|
||||||
|
|
||||||
|
fetchMock.get("path:/_matrix/client/v3/room_keys/version", unsignedBackup, {
|
||||||
|
overwriteRoutes: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await aliceCrypto.checkKeyBackupAndEnable();
|
||||||
|
expect(await aliceCrypto.getActiveSessionBackupVersion()).toEqual(newBackupVersion);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Disables when backup is deleted", async () => {
|
||||||
|
aliceClient = await initTestClient();
|
||||||
|
const aliceCrypto = aliceClient.getCrypto()!;
|
||||||
|
|
||||||
|
// tell Alice to trust the dummy device that signed the backup
|
||||||
|
await aliceClient.startClient();
|
||||||
|
await waitForDeviceList();
|
||||||
|
await aliceCrypto.setDeviceVerified(testData.TEST_USER_ID, testData.TEST_DEVICE_ID);
|
||||||
|
|
||||||
|
fetchMock.get("path:/_matrix/client/v3/room_keys/version", testData.SIGNED_BACKUP_DATA);
|
||||||
|
|
||||||
|
const result = await aliceCrypto.checkKeyBackupAndEnable();
|
||||||
|
expect(result).toBeTruthy();
|
||||||
|
expect(await aliceCrypto.getActiveSessionBackupVersion()).toEqual(testData.SIGNED_BACKUP_DATA.version);
|
||||||
|
|
||||||
|
fetchMock.get(
|
||||||
|
"path:/_matrix/client/v3/room_keys/version",
|
||||||
|
{
|
||||||
|
status: 404,
|
||||||
|
body: {
|
||||||
|
errcode: "M_NOT_FOUND",
|
||||||
|
error: "No backup found",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
overwriteRoutes: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const noResult = await aliceCrypto.checkKeyBackupAndEnable();
|
||||||
|
expect(noResult).toBeNull();
|
||||||
|
expect(await aliceCrypto.getActiveSessionBackupVersion()).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
/** make sure that the client knows about the dummy device */
|
/** make sure that the client knows about the dummy device */
|
||||||
async function waitForDeviceList(): Promise<void> {
|
async function waitForDeviceList(): Promise<void> {
|
||||||
// Completing the initial sync will make the device list download outdated device lists (of which our own
|
// Completing the initial sync will make the device list download outdated device lists (of which our own
|
||||||
|
|||||||
@@ -2252,6 +2252,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
this.reEmitter.reEmit(rustCrypto, [
|
this.reEmitter.reEmit(rustCrypto, [
|
||||||
CryptoEvent.VerificationRequestReceived,
|
CryptoEvent.VerificationRequestReceived,
|
||||||
CryptoEvent.UserTrustStatusChanged,
|
CryptoEvent.UserTrustStatusChanged,
|
||||||
|
CryptoEvent.KeyBackupStatus,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3251,6 +3252,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
* getKeyBackupVersion) in backupInfo and
|
* getKeyBackupVersion) in backupInfo and
|
||||||
* trust information (as returned by isKeyBackupTrusted)
|
* trust information (as returned by isKeyBackupTrusted)
|
||||||
* in trustInfo.
|
* in trustInfo.
|
||||||
|
*
|
||||||
|
* @deprecated Prefer {@link CryptoApi.checkKeyBackupAndEnable}.
|
||||||
*/
|
*/
|
||||||
public checkKeyBackup(): Promise<IKeyBackupCheck | null> {
|
public checkKeyBackup(): Promise<IKeyBackupCheck | null> {
|
||||||
if (!this.crypto) {
|
if (!this.crypto) {
|
||||||
@@ -3320,6 +3323,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
*
|
*
|
||||||
* @param info - Backup information object as returned by getKeyBackupVersion
|
* @param info - Backup information object as returned by getKeyBackupVersion
|
||||||
* @returns Promise which resolves when complete.
|
* @returns Promise which resolves when complete.
|
||||||
|
*
|
||||||
|
* @deprecated Do not call this directly. Instead call {@link CryptoApi.checkKeyBackupAndEnable}.
|
||||||
*/
|
*/
|
||||||
public enableKeyBackup(info: IKeyBackupInfo): Promise<void> {
|
public enableKeyBackup(info: IKeyBackupInfo): Promise<void> {
|
||||||
if (!this.crypto) {
|
if (!this.crypto) {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { DeviceMap } from "./models/device";
|
|||||||
import { UIAuthCallback } from "./interactive-auth";
|
import { UIAuthCallback } from "./interactive-auth";
|
||||||
import { AddSecretStorageKeyOpts, SecretStorageCallbacks, SecretStorageKeyDescription } from "./secret-storage";
|
import { AddSecretStorageKeyOpts, SecretStorageCallbacks, SecretStorageKeyDescription } from "./secret-storage";
|
||||||
import { VerificationRequest } from "./crypto-api/verification";
|
import { VerificationRequest } from "./crypto-api/verification";
|
||||||
import { BackupTrustInfo, KeyBackupInfo } from "./crypto-api/keybackup";
|
import { BackupTrustInfo, KeyBackupCheck, KeyBackupInfo } from "./crypto-api/keybackup";
|
||||||
import { ISignatures } from "./@types/signed";
|
import { ISignatures } from "./@types/signed";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -350,6 +350,17 @@ export interface CryptoApi {
|
|||||||
* @param info - key backup info dict from {@link MatrixClient#getKeyBackupVersion}.
|
* @param info - key backup info dict from {@link MatrixClient#getKeyBackupVersion}.
|
||||||
*/
|
*/
|
||||||
isKeyBackupTrusted(info: KeyBackupInfo): Promise<BackupTrustInfo>;
|
isKeyBackupTrusted(info: KeyBackupInfo): Promise<BackupTrustInfo>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force a re-check of the key backup and enable/disable it as appropriate.
|
||||||
|
*
|
||||||
|
* Fetches the current backup information from the server. If there is a backup, and it is trusted, starts
|
||||||
|
* backing up to it; otherwise, disables backups.
|
||||||
|
*
|
||||||
|
* @returns `null` if there is no backup on the server. Otherwise, data on the backup as returned by the server,
|
||||||
|
* and trust information (as returned by {@link isKeyBackupTrusted}).
|
||||||
|
*/
|
||||||
|
checkKeyBackupAndEnable(): Promise<KeyBackupCheck | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -60,3 +60,11 @@ export interface BackupTrustInfo {
|
|||||||
*/
|
*/
|
||||||
readonly matchesDecryptionKey: boolean;
|
readonly matchesDecryptionKey: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of {@link CryptoApi.checkKeyBackupAndEnable}.
|
||||||
|
*/
|
||||||
|
export interface KeyBackupCheck {
|
||||||
|
backupInfo: KeyBackupInfo;
|
||||||
|
trustInfo: BackupTrustInfo;
|
||||||
|
}
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ import {
|
|||||||
CrossSigningStatus,
|
CrossSigningStatus,
|
||||||
DeviceVerificationStatus,
|
DeviceVerificationStatus,
|
||||||
ImportRoomKeysOpts,
|
ImportRoomKeysOpts,
|
||||||
|
KeyBackupCheck,
|
||||||
KeyBackupInfo,
|
KeyBackupInfo,
|
||||||
VerificationRequest as CryptoApiVerificationRequest,
|
VerificationRequest as CryptoApiVerificationRequest,
|
||||||
} from "../crypto-api";
|
} from "../crypto-api";
|
||||||
@@ -1304,6 +1305,20 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
|||||||
return backupTrustInfoFromLegacyTrustInfo(trustInfo);
|
return backupTrustInfoFromLegacyTrustInfo(trustInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force a re-check of the key backup and enable/disable it as appropriate.
|
||||||
|
*
|
||||||
|
* Implementation of {@link CryptoApi.checkKeyBackupAndEnable}.
|
||||||
|
*/
|
||||||
|
public async checkKeyBackupAndEnable(): Promise<KeyBackupCheck | null> {
|
||||||
|
const checkResult = await this.backupManager.checkKeyBackup();
|
||||||
|
if (!checkResult || !checkResult.backupInfo) return null;
|
||||||
|
return {
|
||||||
|
backupInfo: checkResult.backupInfo,
|
||||||
|
trustInfo: backupTrustInfoFromLegacyTrustInfo(checkResult.trustInfo),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks that a given cross-signing private key matches a given public key.
|
* 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
|
* This can be used by the getCrossSigningKey callback to verify that the
|
||||||
|
|||||||
@@ -17,20 +17,33 @@ limitations under the License.
|
|||||||
import { OlmMachine, SignatureVerification } from "@matrix-org/matrix-sdk-crypto-wasm";
|
import { OlmMachine, SignatureVerification } from "@matrix-org/matrix-sdk-crypto-wasm";
|
||||||
import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-wasm";
|
import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-wasm";
|
||||||
|
|
||||||
import { BackupTrustInfo, Curve25519AuthData, KeyBackupInfo } from "../crypto-api/keybackup";
|
import { BackupTrustInfo, Curve25519AuthData, KeyBackupCheck, KeyBackupInfo } from "../crypto-api/keybackup";
|
||||||
|
import { logger } from "../logger";
|
||||||
|
import { ClientPrefix, IHttpOpts, MatrixError, MatrixHttpApi, Method } from "../http-api";
|
||||||
|
import { CryptoEvent } from "../crypto";
|
||||||
|
import { TypedEventEmitter } from "../models/typed-event-emitter";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export class RustBackupManager {
|
export class RustBackupManager extends TypedEventEmitter<RustBackupCryptoEvents, RustBackupCryptoEventMap> {
|
||||||
public constructor(private readonly olmMachine: OlmMachine) {}
|
/** Have we checked if there is a backup on the server which we can use */
|
||||||
|
private checkedForBackup = false;
|
||||||
|
private activeBackupVersion: string | null = null;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private readonly olmMachine: OlmMachine,
|
||||||
|
private readonly http: MatrixHttpApi<IHttpOpts & { onlyData: true }>,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the backup version we are currently backing up to, if any
|
* Get the backup version we are currently backing up to, if any
|
||||||
*/
|
*/
|
||||||
public async getActiveBackupVersion(): Promise<string | null> {
|
public async getActiveBackupVersion(): Promise<string | null> {
|
||||||
// TODO stub
|
if (!this.olmMachine.isBackupEnabled()) return null;
|
||||||
return null;
|
return this.activeBackupVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,4 +65,138 @@ export class RustBackupManager {
|
|||||||
trusted: signatureVerification.trusted(),
|
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.
|
||||||
|
*/
|
||||||
|
public checkKeyBackupAndEnable(force: boolean): Promise<KeyBackupCheck | null> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
private keyBackupCheckInProgress: Promise<KeyBackupCheck | null> | null = null;
|
||||||
|
|
||||||
|
/** Helper for `checkKeyBackup` */
|
||||||
|
private async doCheckKeyBackup(): Promise<KeyBackupCheck | null> {
|
||||||
|
logger.log("Checking key backup status...");
|
||||||
|
let backupInfo: KeyBackupInfo | null = null;
|
||||||
|
try {
|
||||||
|
backupInfo = await this.requestKeyBackupVersion();
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn("Error checking for active key backup", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
this.checkedForBackup = true;
|
||||||
|
|
||||||
|
if (backupInfo && !backupInfo.version) {
|
||||||
|
logger.warn("active backup lacks a useful 'version'; ignoring it");
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeVersion = await this.getActiveBackupVersion();
|
||||||
|
|
||||||
|
if (!backupInfo) {
|
||||||
|
if (activeVersion !== null) {
|
||||||
|
logger.log("No key backup present on server: disabling key backup");
|
||||||
|
await this.disableKeyBackup();
|
||||||
|
} else {
|
||||||
|
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.log("Key backup present on server but not trusted: disabling key backup");
|
||||||
|
await this.disableKeyBackup();
|
||||||
|
} else {
|
||||||
|
logger.log("Key backup present on server but not trusted: not enabling key backup");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (activeVersion === null) {
|
||||||
|
logger.log(`Found usable key backup v${backupInfo.version}: enabling key backups`);
|
||||||
|
await this.enableKeyBackup(backupInfo);
|
||||||
|
} else if (activeVersion !== backupInfo.version) {
|
||||||
|
logger.log(`On backup version ${activeVersion} but found version ${backupInfo.version}: switching.`);
|
||||||
|
await 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 };
|
||||||
|
}
|
||||||
|
|
||||||
|
private async enableKeyBackup(backupInfo: KeyBackupInfo): Promise<void> {
|
||||||
|
// 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 as Curve25519AuthData).public_key,
|
||||||
|
backupInfo.version!,
|
||||||
|
);
|
||||||
|
this.activeBackupVersion = backupInfo.version!;
|
||||||
|
|
||||||
|
this.emit(CryptoEvent.KeyBackupStatus, true);
|
||||||
|
|
||||||
|
// TODO: kick off an upload loop
|
||||||
|
}
|
||||||
|
|
||||||
|
private async disableKeyBackup(): Promise<void> {
|
||||||
|
await this.olmMachine.disableBackup();
|
||||||
|
this.activeBackupVersion = null;
|
||||||
|
this.emit(CryptoEvent.KeyBackupStatus, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async scheduleAllGroupSessionsForBackup(): Promise<void> {
|
||||||
|
// TODO stub
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get information about the current key backup from the server
|
||||||
|
*
|
||||||
|
* @returns Information object from API or null if there is no active backup.
|
||||||
|
*/
|
||||||
|
private async requestKeyBackupVersion(): Promise<KeyBackupInfo | null> {
|
||||||
|
try {
|
||||||
|
return await this.http.authedRequest<KeyBackupInfo>(
|
||||||
|
Method.Get,
|
||||||
|
"/room_keys/version",
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
prefix: ClientPrefix.V3,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
if ((<MatrixError>e).errcode === "M_NOT_FOUND") {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RustBackupCryptoEvents = CryptoEvent.KeyBackupStatus;
|
||||||
|
|
||||||
|
export type RustBackupCryptoEventMap = {
|
||||||
|
[CryptoEvent.KeyBackupStatus]: (enabled: boolean) => void;
|
||||||
|
};
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import {
|
|||||||
GeneratedSecretStorageKey,
|
GeneratedSecretStorageKey,
|
||||||
ImportRoomKeyProgressData,
|
ImportRoomKeyProgressData,
|
||||||
ImportRoomKeysOpts,
|
ImportRoomKeysOpts,
|
||||||
|
KeyBackupCheck,
|
||||||
KeyBackupInfo,
|
KeyBackupInfo,
|
||||||
VerificationRequest,
|
VerificationRequest,
|
||||||
} from "../crypto-api";
|
} from "../crypto-api";
|
||||||
@@ -58,7 +59,8 @@ import { RustVerificationRequest, verificationMethodIdentifierToMethod } from ".
|
|||||||
import { EventType, MsgType } from "../@types/event";
|
import { EventType, MsgType } from "../@types/event";
|
||||||
import { CryptoEvent } from "../crypto";
|
import { CryptoEvent } from "../crypto";
|
||||||
import { TypedEventEmitter } from "../models/typed-event-emitter";
|
import { TypedEventEmitter } from "../models/typed-event-emitter";
|
||||||
import { RustBackupManager } from "./backup";
|
import { RustBackupCryptoEventMap, RustBackupCryptoEvents, RustBackupManager } from "./backup";
|
||||||
|
import { TypedReEmitter } from "../ReEmitter";
|
||||||
|
|
||||||
const ALL_VERIFICATION_METHODS = ["m.sas.v1", "m.qr_code.scan.v1", "m.qr_code.show.v1", "m.reciprocate.v1"];
|
const ALL_VERIFICATION_METHODS = ["m.sas.v1", "m.qr_code.scan.v1", "m.qr_code.show.v1", "m.reciprocate.v1"];
|
||||||
|
|
||||||
@@ -84,8 +86,9 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
|
|||||||
private keyClaimManager: KeyClaimManager;
|
private keyClaimManager: KeyClaimManager;
|
||||||
private outgoingRequestProcessor: OutgoingRequestProcessor;
|
private outgoingRequestProcessor: OutgoingRequestProcessor;
|
||||||
private crossSigningIdentity: CrossSigningIdentity;
|
private crossSigningIdentity: CrossSigningIdentity;
|
||||||
|
private readonly backupManager: RustBackupManager;
|
||||||
|
|
||||||
public readonly backupManager: RustBackupManager;
|
private readonly reemitter = new TypedReEmitter<RustCryptoEvents, RustCryptoEventMap>(this);
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
/** The `OlmMachine` from the underlying rust crypto sdk. */
|
/** The `OlmMachine` from the underlying rust crypto sdk. */
|
||||||
@@ -114,7 +117,9 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
|
|||||||
this.outgoingRequestProcessor = new OutgoingRequestProcessor(olmMachine, http);
|
this.outgoingRequestProcessor = new OutgoingRequestProcessor(olmMachine, http);
|
||||||
this.keyClaimManager = new KeyClaimManager(olmMachine, this.outgoingRequestProcessor);
|
this.keyClaimManager = new KeyClaimManager(olmMachine, this.outgoingRequestProcessor);
|
||||||
this.eventDecryptor = new EventDecryptor(olmMachine);
|
this.eventDecryptor = new EventDecryptor(olmMachine);
|
||||||
this.backupManager = new RustBackupManager(olmMachine);
|
|
||||||
|
this.backupManager = new RustBackupManager(olmMachine, http);
|
||||||
|
this.reemitter.reEmit(this.backupManager, [CryptoEvent.KeyBackupStatus]);
|
||||||
|
|
||||||
// Fire if the cross signing keys are imported from the secret storage
|
// Fire if the cross signing keys are imported from the secret storage
|
||||||
const onCrossSigningKeysImport = (): void => {
|
const onCrossSigningKeysImport = (): void => {
|
||||||
@@ -819,6 +824,15 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
|
|||||||
return await this.backupManager.isKeyBackupTrusted(info);
|
return await this.backupManager.isKeyBackupTrusted(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force a re-check of the key backup and enable/disable it as appropriate.
|
||||||
|
*
|
||||||
|
* Implementation of {@link Crypto.CryptoApi.checkKeyBackupAndEnable}.
|
||||||
|
*/
|
||||||
|
public async checkKeyBackupAndEnable(): Promise<KeyBackupCheck | null> {
|
||||||
|
return await this.backupManager.checkKeyBackupAndEnable(true);
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// SyncCryptoCallbacks implementation
|
// SyncCryptoCallbacks implementation
|
||||||
@@ -1231,7 +1245,10 @@ class EventDecryptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type RustCryptoEvents = CryptoEvent.VerificationRequestReceived | CryptoEvent.UserTrustStatusChanged;
|
type RustCryptoEvents =
|
||||||
|
| CryptoEvent.VerificationRequestReceived
|
||||||
|
| CryptoEvent.UserTrustStatusChanged
|
||||||
|
| RustBackupCryptoEvents;
|
||||||
|
|
||||||
type RustCryptoEventMap = {
|
type RustCryptoEventMap = {
|
||||||
/**
|
/**
|
||||||
@@ -1243,4 +1260,4 @@ type RustCryptoEventMap = {
|
|||||||
* Fires when the cross signing keys are imported during {@link CryptoApi#bootstrapCrossSigning}
|
* Fires when the cross signing keys are imported during {@link CryptoApi#bootstrapCrossSigning}
|
||||||
*/
|
*/
|
||||||
[CryptoEvent.UserTrustStatusChanged]: (userId: string, userTrustLevel: UserTrustLevel) => void;
|
[CryptoEvent.UserTrustStatusChanged]: (userId: string, userTrustLevel: UserTrustLevel) => void;
|
||||||
};
|
} & RustBackupCryptoEventMap;
|
||||||
|
|||||||
Reference in New Issue
Block a user