Skip to content

Commit e49a0a5

Browse files
authored
Delete the dehydrated device when resetEncryption is called (#4727)
* delete the dehydrated device when resetEncryption is called * add more tests to improve coverage
1 parent 5b93928 commit e49a0a5

File tree

4 files changed

+83
-1
lines changed

4 files changed

+83
-1
lines changed

spec/unit/rust-crypto/rust-crypto.spec.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2058,6 +2058,41 @@ describe("RustCrypto", () => {
20582058
expect(await secretStorage.get("org.matrix.msc3814")).not.toEqual(origDehydrationKey);
20592059
});
20602060
});
2061+
2062+
it("should handle errors when deleting a dehydrated device", async () => {
2063+
const rustCrypto = await makeTestRustCrypto(makeMatrixHttpApi());
2064+
const dehydratedDeviceManager = rustCrypto["dehydratedDeviceManager"];
2065+
fetchMock.config.overwriteRoutes = true;
2066+
// if the server doesn't support dehydrated devices, delete should succeed without throwing an error
2067+
fetchMock.delete("path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device", {
2068+
status: 404,
2069+
body: {
2070+
errcode: "M_UNRECOGNIZED",
2071+
error: "Unknown endpoint",
2072+
},
2073+
});
2074+
await dehydratedDeviceManager.delete();
2075+
2076+
// if there is no dehydrated device, delete should succeed without throwing an error
2077+
fetchMock.delete("path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device", {
2078+
status: 404,
2079+
body: {
2080+
errcode: "M_NOT_FOUND",
2081+
error: "Not found",
2082+
},
2083+
});
2084+
await dehydratedDeviceManager.delete();
2085+
2086+
// for any other error response, delete should throw an error
2087+
fetchMock.delete("path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device", {
2088+
status: 400,
2089+
body: {
2090+
errcode: "M_UNKNOWN",
2091+
error: "Unknown error",
2092+
},
2093+
});
2094+
await expect(dehydratedDeviceManager.delete()).rejects.toThrow();
2095+
});
20612096
});
20622097

20632098
describe("import & export secrets bundle", () => {
@@ -2231,7 +2266,7 @@ describe("RustCrypto", () => {
22312266
fetchMock.post("path:/_matrix/client/v3/keys/signatures/upload", {});
22322267
});
22332268

2234-
it("reset should reset 4S, backup and cross-signing", async () => {
2269+
it("reset should reset 4S, backup, cross-signing, and dehydrated device", async () => {
22352270
// When we will delete the key backup
22362271
let backupIsDeleted = false;
22372272
fetchMock.delete("path:/_matrix/client/v3/room_keys/version/1", () => {
@@ -2243,6 +2278,12 @@ describe("RustCrypto", () => {
22432278
return backupIsDeleted ? {} : testData.SIGNED_BACKUP_DATA;
22442279
});
22452280

2281+
let dehydratedDeviceIsDeleted = false;
2282+
fetchMock.delete("path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device", () => {
2283+
dehydratedDeviceIsDeleted = true;
2284+
return { device_id: "ADEVICEID" };
2285+
});
2286+
22462287
// A new key backup should be created after the reset
22472288
let newKeyBackupInfo!: KeyBackupInfo;
22482289
fetchMock.post("path:/_matrix/client/v3/room_keys/version", (res, options) => {
@@ -2273,6 +2314,8 @@ describe("RustCrypto", () => {
22732314
expect(newKeyBackupInfo.auth_data).toBeTruthy();
22742315
// The new cross signing keys should be uploaded
22752316
expect(authUploadDeviceSigningKeys).toHaveBeenCalledWith(expect.any(Function));
2317+
// The dehydrated device was deleted
2318+
expect(dehydratedDeviceIsDeleted).toBeTruthy();
22762319
});
22772320
});
22782321
});

src/crypto-api/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,11 +436,17 @@ export interface CryptoApi {
436436

437437
/**
438438
* Reset the encryption of the user by going through the following steps:
439+
* - Remove the dehydrated device and stop the periodic creation of dehydrated devices.
439440
* - Disable backing up room keys and delete any existing backups.
440441
* - Remove the default secret storage key from the account data (ie: the recovery key).
441442
* - Reset the cross-signing keys.
442443
* - Create a new key backup.
443444
*
445+
* Note that the dehydrated device will be removed, but will not be replaced
446+
* and it will not schedule creating new dehydrated devices. To do this,
447+
* {@link startDehydration} should be called after a new secret storage key
448+
* is created.
449+
*
444450
* @param authUploadDeviceSigningKeys - Callback to authenticate the upload of device signing keys.
445451
* Used when resetting the cross signing keys.
446452
* See {@link BootstrapCrossSigningOpts#authUploadDeviceSigningKeys}.

src/rust-crypto/DehydratedDeviceManager.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,35 @@ export class DehydratedDeviceManager extends TypedEventEmitter<DehydratedDevices
340340
this.intervalId = undefined;
341341
}
342342
}
343+
344+
/**
345+
* Delete the current dehydrated device and stop the dehydrated device manager.
346+
*/
347+
public async delete(): Promise<void> {
348+
this.stop();
349+
try {
350+
await this.http.authedRequest(
351+
Method.Delete,
352+
"/dehydrated_device",
353+
undefined,
354+
{},
355+
{
356+
prefix: UnstablePrefix,
357+
},
358+
);
359+
} catch (error) {
360+
const err = error as MatrixError;
361+
// If dehydrated devices aren't supported, or no dehydrated device
362+
// is found, we don't consider it an error, because we we'll end up
363+
// with no dehydrated device.
364+
if (err.errcode === "M_UNRECOGNIZED") {
365+
return;
366+
} else if (err.errcode === "M_NOT_FOUND") {
367+
return;
368+
}
369+
throw error;
370+
}
371+
}
343372
}
344373

345374
/**

src/rust-crypto/rust-crypto.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1440,6 +1440,10 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, CryptoEventH
14401440
public async resetEncryption(authUploadDeviceSigningKeys: UIAuthCallback<void>): Promise<void> {
14411441
this.logger.debug("resetEncryption: resetting encryption");
14421442

1443+
// Delete the dehydrated device, since any existing one will be signed
1444+
// by the wrong cross-signing key
1445+
this.dehydratedDeviceManager.delete();
1446+
14431447
// Disable backup, and delete all the backups from the server
14441448
await this.backupManager.deleteAllKeyBackupVersions();
14451449

0 commit comments

Comments
 (0)