Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Add support for device dehydration v2 #12316

Merged
merged 22 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
},
});
}
initializeDehydration(true);
await initializeDehydration(true);

this.setState({
phase: Phase.Stored,
Expand Down
5 changes: 5 additions & 0 deletions src/components/views/right_panel/UserInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,11 @@ function DevicesSection({
dehydratedDeviceIds.push(device.deviceId);
}
}
// If the user has exactly one device marked as dehydrated, we consider
// that as the dehydrated device, and hide it as a normal device (but
// indicate that the user is using a dehydrated device). If the user has
// more than one, that is anomalous, and we show all the devices so that
// nothing is hidden.
const dehydratedDeviceId: string | undefined = dehydratedDeviceIds.length == 1 ? dehydratedDeviceIds[0] : undefined;
richvdh marked this conversation as resolved.
Show resolved Hide resolved
let dehydratedDeviceInExpandSection = false;

Expand Down
6 changes: 5 additions & 1 deletion src/components/views/settings/devices/useOwnDevices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,14 @@ export const useOwnDevices = (): DevicesState => {
const dehydratedDeviceIds: string[] = [];
for (const device of userDevices?.values() ?? []) {
if (device.dehydrated) {
logger.debug("Found dehydrated device", device.deviceId);
dehydratedDeviceIds.push(device.deviceId);
}
}
// If the user has exactly one device marked as dehydrated, we consider
// that as the dehydrated device, and hide it as a normal device (but
// indicate that the user is using a dehydrated device). If the user has
// more than one, that is anomalous, and we show all the devices so that
// nothing is hidden.
setDehydratedDeviceId(dehydratedDeviceIds.length == 1 ? dehydratedDeviceIds[0] : undefined);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, how should we handle >1 dehydrated devices?


setIsLoadingDeviceList(false);
Expand Down
9 changes: 5 additions & 4 deletions src/stores/SetupEncryptionStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,12 @@ export class SetupEncryptionStore extends EventEmitter {
const userDevices: Iterable<Device> =
(await crypto.getUserDeviceInfo([ownUserId])).get(ownUserId)?.values() ?? [];
this.hasDevicesToVerifyAgainst = await asyncSome(userDevices, async (device) => {
// ignore the dehydrated device
// Ignore dehydrated devices. `dehydratedDevice` is set by the
// implementation of MSC2697, whereas MSC3814 proposes that devices
// should set a `dehydrated` flag in the device key. We ignore
// both types of dehydrated devices.
if (dehydratedDevice && device.deviceId == dehydratedDevice?.device_id) return false;
if (device.dehydrated) {
return false;
}
if (device.dehydrated) return false;

// ignore devices without an identity key
if (!device.getIdentityKey()) return false;
Expand Down
39 changes: 18 additions & 21 deletions src/utils/device/dehydration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,40 +18,37 @@ import { logger } from "matrix-js-sdk/src/logger";

import { MatrixClientPeg } from "../../MatrixClientPeg";

// the interval between creating dehydrated devices
const DEHYDRATION_INTERVAL = 7 * 24 * 60 * 60 * 1000;

// check if device dehydration is enabled
export async function deviceDehydrationEnabled(): Promise<boolean> {
/**
* Check if device dehydration is enabled.
*
* Dehydration can only be enabled if encryption is available, and the crypto
* backend supports dehydration.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Dehydration can only be enabled if encryption is available, and the crypto
* backend supports dehydration.
* Note that this doesn't necessarily mean that device dehydration has been initialised
* (yet) on this client; rather, it means that the server supports it, the crypto backend
* supports it, and the application configuration suggests that it *should* be
* initialised on this device.

*
* Dehydration can currently only enabled by setting a flag in the .well-known file.
uhoreg marked this conversation as resolved.
Show resolved Hide resolved
*/
async function deviceDehydrationEnabled(): Promise<boolean> {
const crypto = MatrixClientPeg.safeGet().getCrypto();
if (!crypto) {
return false;
}
if (!(await crypto.isDehydrationSupported())) {
return false;
}
if (await crypto.isDehydrationKeyStored()) {
return true;
}
const wellknown = await MatrixClientPeg.safeGet().waitForClientWellKnown();
return !!wellknown?.["org.matrix.msc3814"];
}

// if dehydration is enabled, rehydrate a device (if available) and create
// a new dehydrated device
export async function initializeDehydration(reset?: boolean): Promise<void> {
/**
* If dehydration is enabled, rehydrate a device (if available) and create
richvdh marked this conversation as resolved.
Show resolved Hide resolved
* a new dehydrated device.
*
* @param createNewKey: force a new dehydration key to be created, even if one
* already exists. This is used when we reset secret storage.
*/
export async function initializeDehydration(createNewKey: boolean = false): Promise<void> {
richvdh marked this conversation as resolved.
Show resolved Hide resolved
const crypto = MatrixClientPeg.safeGet().getCrypto();
if (crypto && (await deviceDehydrationEnabled())) {
richvdh marked this conversation as resolved.
Show resolved Hide resolved
logger.log("Device dehydration enabled");
if (reset) {
await crypto.resetDehydrationKey();
} else {
try {
await crypto.rehydrateDeviceIfAvailable();
} catch (e) {
logger.error("Error rehydrating device:", e);
}
}
await crypto.scheduleDeviceDehydration(DEHYDRATION_INTERVAL);
await crypto.startDehydration(createNewKey);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
mockClientMethodsDevice,
mockPlatformPeg,
} from "../../../../../test-utils";
import { SDKContext, SdkContextClass } from "../../../../../../src/contexts/SDKContext";

describe("<SecurityUserSettingsTab />", () => {
const defaultProps = {
Expand All @@ -44,9 +45,14 @@ describe("<SecurityUserSettingsTab />", () => {
getKeyBackupVersion: jest.fn(),
});

const sdkContext = new SdkContextClass();
sdkContext.client = mockClient;

const getComponent = () => (
<MatrixClientContext.Provider value={mockClient}>
<SecurityUserSettingsTab {...defaultProps} />
<SDKContext.Provider value={sdkContext}>
<SecurityUserSettingsTab {...defaultProps} />
</SDKContext.Provider>
</MatrixClientContext.Provider>
);

Expand Down
Loading