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
Fix resetEncryption to remove secrets in 4S (#4683)
* fix(crypto): `resetEncryption` remove secrets in 4S Remove the cross signing keys and the backup decryption key of the 4S when calling `resetEncryption` * test(crypto): expect secrets to be deleted in 4S when `resetEncryption` is called * test(secret storage): add test case when the secret is set at null * fix(crypto): remove default key in 4S * test(crypto): default key should be removed from 4S
This commit is contained in:
@@ -2247,6 +2247,8 @@ describe("RustCrypto", () => {
|
|||||||
setDefaultKeyId: jest.fn(),
|
setDefaultKeyId: jest.fn(),
|
||||||
hasKey: jest.fn().mockResolvedValue(false),
|
hasKey: jest.fn().mockResolvedValue(false),
|
||||||
getKey: jest.fn().mockResolvedValue(null),
|
getKey: jest.fn().mockResolvedValue(null),
|
||||||
|
store: jest.fn(),
|
||||||
|
getDefaultKeyId: jest.fn().mockResolvedValue("defaultKeyId"),
|
||||||
} as unknown as ServerSideSecretStorage;
|
} as unknown as ServerSideSecretStorage;
|
||||||
|
|
||||||
fetchMock.post("path:/_matrix/client/v3/keys/upload", { one_time_key_counts: {} });
|
fetchMock.post("path:/_matrix/client/v3/keys/upload", { one_time_key_counts: {} });
|
||||||
@@ -2285,6 +2287,12 @@ describe("RustCrypto", () => {
|
|||||||
const authUploadDeviceSigningKeys = jest.fn();
|
const authUploadDeviceSigningKeys = jest.fn();
|
||||||
await rustCrypto.resetEncryption(authUploadDeviceSigningKeys);
|
await rustCrypto.resetEncryption(authUploadDeviceSigningKeys);
|
||||||
|
|
||||||
|
// The secrets in 4S should be deleted
|
||||||
|
expect(secretStorage.store).toHaveBeenCalledWith("m.cross_signing.master", null);
|
||||||
|
expect(secretStorage.store).toHaveBeenCalledWith("m.cross_signing.self_signing", null);
|
||||||
|
expect(secretStorage.store).toHaveBeenCalledWith("m.cross_signing.user_signing", null);
|
||||||
|
expect(secretStorage.store).toHaveBeenCalledWith("m.megolm_backup.v1", null);
|
||||||
|
expect(secretStorage.store).toHaveBeenCalledWith("m.secret_storage.key.defaultKeyId", null);
|
||||||
// A new key backup should be created
|
// A new key backup should be created
|
||||||
expect(newKeyBackupInfo.auth_data).toBeTruthy();
|
expect(newKeyBackupInfo.auth_data).toBeTruthy();
|
||||||
// The new cross signing keys should be uploaded
|
// The new cross signing keys should be uploaded
|
||||||
|
|||||||
@@ -243,11 +243,16 @@ describe("ServerSideSecretStorageImpl", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("store", () => {
|
describe("store", () => {
|
||||||
it("should ignore keys with unknown algorithm", async function () {
|
let secretStorage: ServerSideSecretStorage;
|
||||||
const accountDataAdapter = mockAccountDataClient();
|
let accountDataAdapter: Mocked<AccountDataClient>;
|
||||||
const mockCallbacks = { getSecretStorageKey: jest.fn() } as Mocked<SecretStorageCallbacks>;
|
|
||||||
const secretStorage = new ServerSideSecretStorageImpl(accountDataAdapter, mockCallbacks);
|
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
accountDataAdapter = mockAccountDataClient();
|
||||||
|
const mockCallbacks = { getSecretStorageKey: jest.fn() } as Mocked<SecretStorageCallbacks>;
|
||||||
|
secretStorage = new ServerSideSecretStorageImpl(accountDataAdapter, mockCallbacks);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should ignore keys with unknown algorithm", async function () {
|
||||||
// stub out getAccountData to return a key with an unknown algorithm
|
// stub out getAccountData to return a key with an unknown algorithm
|
||||||
const storedKey = { algorithm: "badalg" } as SecretStorageKeyDescriptionCommon;
|
const storedKey = { algorithm: "badalg" } as SecretStorageKeyDescriptionCommon;
|
||||||
async function mockGetAccountData<K extends keyof AccountDataEvents>(
|
async function mockGetAccountData<K extends keyof AccountDataEvents>(
|
||||||
@@ -274,6 +279,11 @@ describe("ServerSideSecretStorageImpl", function () {
|
|||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
expect(console.warn).toHaveBeenCalledWith(expect.stringContaining("unknown algorithm"));
|
expect(console.warn).toHaveBeenCalledWith(expect.stringContaining("unknown algorithm"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should set the secret with an empty object when the value is null", async function () {
|
||||||
|
await secretStorage.store("mySecret", null);
|
||||||
|
expect(accountDataAdapter.setAccountData).toHaveBeenCalledWith("mySecret", {});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("setDefaultKeyId", function () {
|
describe("setDefaultKeyId", function () {
|
||||||
|
|||||||
@@ -1508,6 +1508,15 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, CryptoEventH
|
|||||||
// Disable backup, and delete all the backups from the server
|
// Disable backup, and delete all the backups from the server
|
||||||
await this.backupManager.deleteAllKeyBackupVersions();
|
await this.backupManager.deleteAllKeyBackupVersions();
|
||||||
|
|
||||||
|
// Remove the stored secrets in the secret storage
|
||||||
|
await this.secretStorage.store("m.cross_signing.master", null);
|
||||||
|
await this.secretStorage.store("m.cross_signing.self_signing", null);
|
||||||
|
await this.secretStorage.store("m.cross_signing.user_signing", null);
|
||||||
|
await this.secretStorage.store("m.megolm_backup.v1", null);
|
||||||
|
|
||||||
|
// Remove the recovery key
|
||||||
|
const defaultKeyId = await this.secretStorage.getDefaultKeyId();
|
||||||
|
if (defaultKeyId) await this.secretStorage.store(`m.secret_storage.key.${defaultKeyId}`, null);
|
||||||
// Disable the recovery key and the secret storage
|
// Disable the recovery key and the secret storage
|
||||||
await this.secretStorage.setDefaultKeyId(null);
|
await this.secretStorage.setDefaultKeyId(null);
|
||||||
|
|
||||||
|
|||||||
@@ -280,14 +280,18 @@ export interface ServerSideSecretStorage {
|
|||||||
* Store an encrypted secret on the server.
|
* Store an encrypted secret on the server.
|
||||||
*
|
*
|
||||||
* Details of the encryption keys to be used must previously have been stored in account data
|
* Details of the encryption keys to be used must previously have been stored in account data
|
||||||
* (for example, via {@link ServerSideSecretStorage#addKey}.
|
* (for example, via {@link ServerSideSecretStorageImpl#addKey}. {@link SecretStorageCallbacks#getSecretStorageKey} will be called to obtain a secret storage
|
||||||
|
* key to decrypt the secret.
|
||||||
|
*
|
||||||
|
* If the secret is `null`, the secret value in the account data will be set to an empty object.
|
||||||
|
* This is considered as "removing" the secret.
|
||||||
*
|
*
|
||||||
* @param name - The name of the secret - i.e., the "event type" to be stored in the account data
|
* @param name - The name of the secret - i.e., the "event type" to be stored in the account data
|
||||||
* @param secret - The secret contents.
|
* @param secret - The secret contents.
|
||||||
* @param keys - The IDs of the keys to use to encrypt the secret, or null/undefined to use the default key
|
* @param keys - The IDs of the keys to use to encrypt the secret, or null/undefined to use the default key
|
||||||
* (will throw if no default key is set).
|
* (will throw if no default key is set).
|
||||||
*/
|
*/
|
||||||
store(name: string, secret: string, keys?: string[] | null): Promise<void>;
|
store(name: string, secret: string | null, keys?: string[] | null): Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a secret from storage, and decrypt it.
|
* Get a secret from storage, and decrypt it.
|
||||||
@@ -504,17 +508,15 @@ export class ServerSideSecretStorageImpl implements ServerSideSecretStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store an encrypted secret on the server.
|
* Implementation of {@link ServerSideSecretStorage#store}.
|
||||||
*
|
|
||||||
* Details of the encryption keys to be used must previously have been stored in account data
|
|
||||||
* (for example, via {@link ServerSideSecretStorageImpl#addKey}. {@link SecretStorageCallbacks#getSecretStorageKey} will be called to obtain a secret storage
|
|
||||||
* key to decrypt the secret.
|
|
||||||
*
|
|
||||||
* @param name - The name of the secret - i.e., the "event type" to be stored in the account data
|
|
||||||
* @param secret - The secret contents.
|
|
||||||
* @param keys - The IDs of the keys to use to encrypt the secret, or null/undefined to use the default key.
|
|
||||||
*/
|
*/
|
||||||
public async store(name: SecretStorageKey, secret: string, keys?: string[] | null): Promise<void> {
|
public async store(name: SecretStorageKey, secret: string | null, keys?: string[] | null): Promise<void> {
|
||||||
|
if (secret === null) {
|
||||||
|
// remove secret
|
||||||
|
await this.accountDataAdapter.setAccountData(name, {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const encrypted: Record<string, AESEncryptedSecretStoragePayload> = {};
|
const encrypted: Record<string, AESEncryptedSecretStoragePayload> = {};
|
||||||
|
|
||||||
if (!keys) {
|
if (!keys) {
|
||||||
|
|||||||
Reference in New Issue
Block a user