Fix enabling key backup not working if there is an untrusted key backup (#30707)

* Fix enabling key backup not working if there is an untrusted key backup on the server.

* lint

* Add test for trust situations.

* remove conditional

* Update src/components/viewmodels/settings/encryption/KeyStoragePanelViewModel.ts

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>

* Update src/components/viewmodels/settings/encryption/KeyStoragePanelViewModel.ts

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>

---------

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
This commit is contained in:
Will Hunt
2025-09-15 13:18:34 +01:00
committed by GitHub
parent 2b5dc7bfd5
commit 08487aa945
2 changed files with 63 additions and 7 deletions

View File

@@ -79,12 +79,30 @@ export function useKeyStoragePanelViewModel(): KeyStoragePanelState {
return;
}
if (enable) {
// If there is no existing key backup on the server, create one.
// `resetKeyBackup` will delete any existing backup, so we only do this if there is no existing backup.
const currentKeyBackup = await crypto.checkKeyBackupAndEnable();
const childLogger = logger.getChild("[enable key storage]");
childLogger.info("User requested enabling key storage");
let currentKeyBackup = await crypto.checkKeyBackupAndEnable();
if (currentKeyBackup) {
logger.info(
`Existing key backup is present. version: ${currentKeyBackup.backupInfo.version}`,
currentKeyBackup.trustInfo,
);
// Check if the current key backup can be used. Either of these properties causes the key backup to be used.
if (currentKeyBackup.trustInfo.trusted || currentKeyBackup.trustInfo.matchesDecryptionKey) {
logger.info("Existing key backup can be used");
} else {
logger.warn("Existing key backup cannot be used, creating new backup");
// There aren't any *usable* backups, so we need to create a new one.
currentKeyBackup = null;
}
} else {
logger.info("No existing key backup versions are present, creating new backup");
}
// If there is no usable key backup on the server, create one.
// `resetKeyBackup` will delete any existing backup, so we only do this if there is no usable backup.
if (currentKeyBackup === null) {
await crypto.resetKeyBackup();
// resetKeyBackup fires this off in the background without waiting, so we need to do it
// explicitly and wait for it, otherwise it won't be enabled yet when we check again.
await crypto.checkKeyBackupAndEnable();
@@ -93,6 +111,7 @@ export function useKeyStoragePanelViewModel(): KeyStoragePanelState {
// Set the flag so that EX no longer thinks the user wants backup disabled
await matrixClient.setAccountData(BACKUP_DISABLED_ACCOUNT_DATA_KEY, { disabled: false });
} else {
logger.info("User requested disabling key backup");
// This method will delete the key backup as well as server side recovery keys and other
// server-side crypto data.
await crypto.disableKeyStorage();

View File

@@ -10,7 +10,7 @@ import { act } from "react";
import { mocked } from "jest-mock";
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
import type { KeyBackupCheck, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
import type { BackupTrustInfo, KeyBackupCheck, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
import { useKeyStoragePanelViewModel } from "../../../../../../src/components/viewmodels/settings/encryption/KeyStoragePanelViewModel";
import { createTestClient, withClientContextRenderOptions } from "../../../../../test-utils";
@@ -49,8 +49,21 @@ describe("KeyStoragePanelViewModel", () => {
expect(mocked(matrixClient.getCrypto()!.resetKeyBackup)).toHaveBeenCalled();
});
it("should not call resetKeyBackup if there is a backup currently", async () => {
mocked(matrixClient.getCrypto()!.checkKeyBackupAndEnable).mockResolvedValue({} as KeyBackupCheck);
it.each<BackupTrustInfo>([
{ trusted: true, matchesDecryptionKey: false },
{ trusted: false, matchesDecryptionKey: true },
{ trusted: true, matchesDecryptionKey: true },
])("should not call resetKeyBackup if there is a backup currently and it is trusted", async (trustInfo) => {
mocked(matrixClient.getCrypto()!.checkKeyBackupAndEnable).mockResolvedValue({
backupInfo: {
version: "1",
algorithm: "foobar",
auth_data: {
public_key: "foobar",
},
},
trustInfo,
});
const { result } = renderHook(
() => useKeyStoragePanelViewModel(),
@@ -61,6 +74,30 @@ describe("KeyStoragePanelViewModel", () => {
expect(mocked(matrixClient.getCrypto()!.resetKeyBackup)).not.toHaveBeenCalled();
});
it("should call resetKeyBackup if there is a backup currently but it is not trusted", async () => {
mocked(matrixClient.getCrypto()!.checkKeyBackupAndEnable).mockResolvedValue({
backupInfo: {
version: "1",
algorithm: "foobar",
auth_data: {
public_key: "foobar",
},
},
trustInfo: {
trusted: false,
matchesDecryptionKey: false,
},
});
const { result } = renderHook(
() => useKeyStoragePanelViewModel(),
withClientContextRenderOptions(matrixClient),
);
await result.current.setEnabled(true);
expect(mocked(matrixClient.getCrypto()!.resetKeyBackup)).toHaveBeenCalled();
});
it("should set account data flag when enabling", async () => {
mocked(matrixClient.getCrypto()!.checkKeyBackupAndEnable).mockResolvedValue(null);