Skip to content

Commit 69a4bef

Browse files
committed
feat(devtools): Add crypto information in devtools
1 parent bc96a41 commit 69a4bef

File tree

5 files changed

+319
-0
lines changed

5 files changed

+319
-0
lines changed

res/css/_components.pcss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@
134134
@import "./views/dialogs/_ConfirmUserActionDialog.pcss";
135135
@import "./views/dialogs/_CreateRoomDialog.pcss";
136136
@import "./views/dialogs/_CreateSubspaceDialog.pcss";
137+
@import "./views/dialogs/_Crypto.pcss";
137138
@import "./views/dialogs/_DeactivateAccountDialog.pcss";
138139
@import "./views/dialogs/_DevtoolsDialog.pcss";
139140
@import "./views/dialogs/_ExportDialog.pcss";

res/css/views/dialogs/_Crypto.pcss

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
.mx_Crypto {
9+
.mx_KeyStorage,
10+
.mx_CrossSigning {
11+
margin: var(--cpd-space-4x) 0;
12+
text-align: left;
13+
14+
thead {
15+
font: var(--cpd-font-heading-sm-semibold);
16+
}
17+
18+
th {
19+
padding-right: var(--cpd-space-2x);
20+
}
21+
}
22+
}

src/components/views/dialogs/DevtoolsDialog.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { SettingLevel } from "../../../settings/SettingLevel";
2424
import ServerInfo from "./devtools/ServerInfo";
2525
import CopyableText from "../elements/CopyableText";
2626
import RoomNotifications from "./devtools/RoomNotifications";
27+
import { Crypto } from "./devtools/Crypto";
2728

2829
enum Category {
2930
Room,
@@ -49,6 +50,7 @@ const Tools: Record<Category, [label: TranslationKey, tool: Tool][]> = {
4950
[_td("devtools|explore_account_data"), AccountDataExplorer],
5051
[_td("devtools|settings_explorer"), SettingExplorer],
5152
[_td("devtools|server_info"), ServerInfo],
53+
[_td("devtools|crypto|title"), Crypto],
5254
],
5355
};
5456

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
import React, { JSX } from "react";
9+
import { InlineSpinner } from "@vector-im/compound-web";
10+
11+
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
12+
import BaseTool from "./BaseTool";
13+
import { useAsyncMemo } from "../../../../hooks/useAsyncMemo";
14+
import { _t } from "../../../../languageHandler";
15+
16+
interface KeyBackupProps {
17+
/**
18+
* Callback to invoke when the back button is clicked.
19+
*/
20+
onBack(): void;
21+
}
22+
23+
/**
24+
* A component that displays information about the key storage and cross-signing.
25+
*/
26+
export function Crypto({ onBack }: KeyBackupProps): JSX.Element {
27+
return (
28+
<BaseTool onBack={onBack} className="mx_Crypto">
29+
<KeyStorage />
30+
<CrossSigning />
31+
</BaseTool>
32+
);
33+
}
34+
35+
/**
36+
* A component that displays information about the key storage.
37+
*/
38+
function KeyStorage(): JSX.Element {
39+
const matrixClient = useMatrixClientContext();
40+
const keyStorageData = useAsyncMemo(async () => {
41+
const crypto = matrixClient.getCrypto();
42+
if (!crypto) return null;
43+
44+
// Get all the key storage data that we will display
45+
const backupInfo = await crypto.getKeyBackupInfo();
46+
const backupKeyStored = Boolean(await matrixClient.isKeyBackupKeyStored());
47+
const backupKeyFromCache = await crypto.getSessionBackupPrivateKey();
48+
const backupKeyCached = Boolean(backupKeyFromCache);
49+
const backupKeyWellFormed = backupKeyFromCache instanceof Uint8Array;
50+
const activeBackupVersion = await crypto.getActiveSessionBackupVersion();
51+
const secretStorageKeyInAccount = await matrixClient.secretStorage.hasKey();
52+
const secretStorageReady = await crypto.isSecretStorageReady();
53+
54+
return {
55+
backupInfo,
56+
backupKeyStored,
57+
backupKeyCached,
58+
backupKeyWellFormed,
59+
activeBackupVersion,
60+
secretStorageKeyInAccount,
61+
secretStorageReady,
62+
};
63+
}, [matrixClient]);
64+
65+
// Show a spinner while loading
66+
if (keyStorageData === undefined) return <InlineSpinner aria-label={_t("common|loading")} />;
67+
// Show a message if crypto is not available
68+
if (keyStorageData === null) return <span>{_t("devtools|crypto|crypto_not_available")}</span>;
69+
70+
const {
71+
backupInfo,
72+
backupKeyStored,
73+
backupKeyCached,
74+
backupKeyWellFormed,
75+
activeBackupVersion,
76+
secretStorageKeyInAccount,
77+
secretStorageReady,
78+
} = keyStorageData;
79+
80+
return (
81+
<table className="mx_KeyStorage">
82+
<thead>{_t("devtools|crypto|key_storage")}</thead>
83+
<tbody>
84+
<tr>
85+
<th scope="row">{_t("devtools|crypto|key_backup_latest_version")}</th>
86+
<td>
87+
{backupInfo
88+
? `${backupInfo.version} (${_t("settings|security|key_backup_algorithm")} ${backupInfo.algorithm})`
89+
: _t("devtools|crypto|key_backup_inactive_warning")}
90+
</td>
91+
</tr>
92+
<tr>
93+
<th scope="row">{_t("devtools|crypto|backup_key_stored_status")}</th>
94+
<td>
95+
{backupKeyStored
96+
? _t("devtools|crypto|backup_key_stored")
97+
: _t("devtools|crypto|backup_key_not_stored")}
98+
</td>
99+
</tr>
100+
<tr>
101+
<th scope="row">{_t("devtools|crypto|key_backup_active_version")}</th>
102+
<td>
103+
{activeBackupVersion === null
104+
? _t("devtools|crypto|key_backup_active_version_none")
105+
: activeBackupVersion}
106+
</td>
107+
</tr>
108+
<tr>
109+
<th scope="row">{_t("devtools|crypto|backup_key_cached_status")}</th>
110+
<td>
111+
{`${
112+
backupKeyCached
113+
? _t("devtools|crypto|backup_key_cached")
114+
: _t("devtools|crypto|not_found_locally")
115+
},
116+
${
117+
backupKeyWellFormed
118+
? _t("devtools|crypto|backup_key_well_formed")
119+
: _t("devtools|crypto|backup_key_unexpected_type")
120+
}`}
121+
</td>
122+
</tr>
123+
<tr>
124+
<th scope="row">{_t("devtools|crypto|4s_public_key_status")}</th>
125+
<td>
126+
{secretStorageKeyInAccount
127+
? _t("devtools|crypto|4s_public_key_in_account_data")
128+
: _t("devtools|crypto|4s_public_key_not_in_account_data")}
129+
</td>
130+
</tr>
131+
<tr>
132+
<th scope="row">{_t("devtools|crypto|secret_storage_status")}</th>
133+
<td>
134+
{secretStorageReady
135+
? _t("devtools|crypto|secret_storage_ready")
136+
: _t("devtools|crypto|secret_storage_not_ready")}
137+
</td>
138+
</tr>
139+
</tbody>
140+
</table>
141+
);
142+
}
143+
144+
/**
145+
* A component that displays information about cross-signing.
146+
*/
147+
function CrossSigning(): JSX.Element {
148+
const matrixClient = useMatrixClientContext();
149+
const crossSigningData = useAsyncMemo(async () => {
150+
const crypto = matrixClient.getCrypto();
151+
if (!crypto) return null;
152+
153+
// Get all the cross-signing data that we will display
154+
const crossSigningStatus = await crypto.getCrossSigningStatus();
155+
const crossSigningPublicKeysOnDevice = crossSigningStatus.publicKeysOnDevice;
156+
const crossSigningPrivateKeysInStorage = crossSigningStatus.privateKeysInSecretStorage;
157+
const masterPrivateKeyCached = crossSigningStatus.privateKeysCachedLocally.masterKey;
158+
const selfSigningPrivateKeyCached = crossSigningStatus.privateKeysCachedLocally.selfSigningKey;
159+
const userSigningPrivateKeyCached = crossSigningStatus.privateKeysCachedLocally.userSigningKey;
160+
const crossSigningReady = await crypto.isCrossSigningReady();
161+
162+
return {
163+
crossSigningPublicKeysOnDevice,
164+
crossSigningPrivateKeysInStorage,
165+
masterPrivateKeyCached,
166+
selfSigningPrivateKeyCached,
167+
userSigningPrivateKeyCached,
168+
crossSigningReady,
169+
};
170+
}, [matrixClient]);
171+
172+
// Show a spinner while loading
173+
if (crossSigningData === undefined) return <InlineSpinner aria-label={_t("common|loading")} />;
174+
// Show a message if crypto is not available
175+
if (crossSigningData === null) return <span>{_t("devtools|crypto|crypto_not_available")}</span>;
176+
177+
const {
178+
crossSigningPublicKeysOnDevice,
179+
crossSigningPrivateKeysInStorage,
180+
masterPrivateKeyCached,
181+
selfSigningPrivateKeyCached,
182+
userSigningPrivateKeyCached,
183+
crossSigningReady,
184+
} = crossSigningData;
185+
186+
return (
187+
<table className="mx_CrossSigning">
188+
<thead>{_t("devtools|crypto|cross_signing")}</thead>
189+
<tbody>
190+
<tr>
191+
<th scope="row">{_t("devtools|crypto|cross_signing_status")}</th>
192+
<td>{getCrossSigningStatus(crossSigningReady, crossSigningPrivateKeysInStorage)}</td>
193+
</tr>
194+
<tr>
195+
<th scope="row">{_t("devtools|crypto|cross_signing_public_keys_on_device_status")}</th>
196+
<td>
197+
{crossSigningPublicKeysOnDevice
198+
? _t("devtools|crypto|cross_signing_public_keys_on_device")
199+
: _t("devtools|crypto|not_found")}
200+
</td>
201+
</tr>
202+
<tr>
203+
<th scope="row">{_t("devtools|crypto|cross_signing_private_keys_in_storage_status")}</th>
204+
<td>
205+
{crossSigningPrivateKeysInStorage
206+
? _t("devtools|crypto|cross_signing_private_keys_in_storage")
207+
: _t("devtools|crypto|cross_signing_private_keys_not_in_storage")}
208+
</td>
209+
</tr>
210+
<tr>
211+
<th scope="row">{_t("devtools|crypto|master_private_key_cached_status")}</th>
212+
<td>
213+
{masterPrivateKeyCached
214+
? _t("devtools|crypto|cross_signing_cached")
215+
: _t("devtools|crypto|not_found_locally")}
216+
</td>
217+
</tr>
218+
<tr>
219+
<th scope="row">{_t("devtools|crypto|self_signing_private_key_cached_status")}</th>
220+
<td>
221+
{selfSigningPrivateKeyCached
222+
? _t("devtools|crypto|cross_signing_cached")
223+
: _t("devtools|crypto|not_found_locally")}
224+
</td>
225+
</tr>
226+
<tr>
227+
<th scope="row">{_t("devtools|crypto|user_signing_private_key_cached_status")}</th>
228+
<td>
229+
{userSigningPrivateKeyCached
230+
? _t("devtools|crypto|cross_signing_cached")
231+
: _t("devtools|crypto|not_found_locally")}
232+
</td>
233+
</tr>
234+
</tbody>
235+
</table>
236+
);
237+
}
238+
239+
/**
240+
* Get the cross-signing status.
241+
* @param crossSigningReady Whether cross-signing is ready.
242+
* @param crossSigningPrivateKeysInStorage Whether cross-signing private keys are in secret storage.
243+
*/
244+
function getCrossSigningStatus(crossSigningReady: boolean, crossSigningPrivateKeysInStorage: boolean): string {
245+
if (crossSigningReady) {
246+
return crossSigningPrivateKeysInStorage
247+
? _t("devtools|crypto|cross_signing_ready")
248+
: _t("devtools|crypto|cross_signing_untrusted");
249+
}
250+
251+
if (crossSigningPrivateKeysInStorage) {
252+
return _t("devtools|crypto|cross_signing_not_ready");
253+
}
254+
255+
return _t("devtools|crypto|cross_signing_not_ready");
256+
}

src/i18n/strings/en_EN.json

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,44 @@
734734
"category_room": "Room",
735735
"caution_colon": "Caution:",
736736
"client_versions": "Client Versions",
737+
"crypto": {
738+
"4s_public_key_in_account_data": "in account data",
739+
"4s_public_key_not_in_account_data": "not found",
740+
"4s_public_key_status": "Secret storage public key:",
741+
"backup_key_cached": "cached locally",
742+
"backup_key_cached_status": "Backup key cached:",
743+
"backup_key_not_stored": "not stored",
744+
"backup_key_stored": "in secret storage",
745+
"backup_key_stored_status": "Backup key stored:",
746+
"backup_key_unexpected_type": "unexpected type",
747+
"backup_key_well_formed": "well formed",
748+
"cross_signing": "Cross-signing",
749+
"cross_signing_cached": "cached locally",
750+
"cross_signing_not_ready": "Cross-signing is not set up.",
751+
"cross_signing_private_keys_in_storage": "in secret storage",
752+
"cross_signing_private_keys_in_storage_status": "Cross-signing private keys:",
753+
"cross_signing_private_keys_not_in_storage": "not found in storage",
754+
"cross_signing_public_keys_on_device": "in memory",
755+
"cross_signing_public_keys_on_device_status": "Cross-signing public keys:",
756+
"cross_signing_ready": "Cross-signing is ready for use.",
757+
"cross_signing_status": "Cross-signing status:",
758+
"cross_signing_untrusted": "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.",
759+
"crypto_not_available": "Cryptographic module is not available",
760+
"key_backup_active_version": "Active backup version:",
761+
"key_backup_active_version_none": "None",
762+
"key_backup_inactive_warning": "Your keys are not being backed up from this session.",
763+
"key_backup_latest_version": "Latest backup version on server:",
764+
"key_storage": "Key Storage",
765+
"master_private_key_cached_status": "Master private key:",
766+
"not_found": "not found",
767+
"not_found_locally": "not found locally",
768+
"secret_storage_not_ready": "not ready",
769+
"secret_storage_ready": "ready",
770+
"secret_storage_status": "Secret storage:",
771+
"self_signing_private_key_cached_status": "Self signing private key:",
772+
"title": "Crypto",
773+
"user_signing_private_key_cached_status": "User signing private key:"
774+
},
737775
"developer_mode": "Developer mode",
738776
"developer_tools": "Developer Tools",
739777
"edit_setting": "Edit setting",

0 commit comments

Comments
 (0)