Skip to content
This repository was archived by the owner on Oct 22, 2024. It is now read-only.

Crypto: fix display of device key #86

Merged
merged 3 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
67 changes: 49 additions & 18 deletions src/components/views/settings/CryptographyPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/

import React from "react";
import { logger } from "matrix-js-sdk/src/logger";

import type ExportE2eKeysDialog from "../../../async-components/views/dialogs/security/ExportE2eKeysDialog";
import type ImportE2eKeysDialog from "../../../async-components/views/dialogs/security/ImportE2eKeysDialog";
Expand All @@ -22,25 +23,53 @@ import SettingsSubsection, { SettingsSubsectionText } from "./shared/SettingsSub

interface IProps {}

interface IState {}
interface IState {
/** The device's base64-encoded Ed25519 identity key, or:
*
* * `undefined`: not yet loaded
* * `null`: encryption is not supported (or the crypto stack was not correctly initialized)
*/
deviceIdentityKey: string | undefined | null;
}

export default class CryptographyPanel extends React.Component<IProps, IState> {
public constructor(props: IProps) {
super(props);

const client = MatrixClientPeg.safeGet();
const crypto = client.getCrypto();
if (!crypto) {
this.state = { deviceIdentityKey: null };
} else {
this.state = { deviceIdentityKey: undefined };
crypto
.getOwnDeviceKeys()
.then((keys) => {
this.setState({ deviceIdentityKey: keys.ed25519 });
})
.catch((e) => {
logger.error(`CryptographyPanel: Error fetching own device keys: ${e}`);
this.setState({ deviceIdentityKey: null });
});
}
}

public render(): React.ReactNode {
const client = MatrixClientPeg.safeGet();
const deviceId = client.deviceId;
let identityKey = client.getDeviceEd25519Key();
if (!identityKey) {
let identityKey = this.state.deviceIdentityKey;
if (identityKey === undefined) {
// Should show a spinner here really, but since this will be very transitional, I can't be doing with the
// necessary styling.
identityKey = "...";
} else if (identityKey === null) {
identityKey = _t("encryption|not_supported");
} else {
identityKey = FormattingUtils.formatCryptoKey(identityKey);
}

let importExportButtons: JSX.Element | undefined;
if (client.isCryptoEnabled()) {
if (client.getCrypto()) {
importExportButtons = (
<div className="mx_CryptographyPanel_importExportButtons">
<AccessibleButton kind="primary_outline" onClick={this.onExportE2eKeysClicked}>
Expand Down Expand Up @@ -68,20 +97,22 @@ export default class CryptographyPanel extends React.Component<IProps, IState> {
<SettingsSubsection heading={_t("settings|security|cryptography_section")}>
<SettingsSubsectionText>
<table className="mx_CryptographyPanel_sessionInfo">
<tr>
<th scope="row">{_t("settings|security|session_id")}</th>
<td>
<code>{deviceId}</code>
</td>
</tr>
<tr>
<th scope="row">{_t("settings|security|session_key")}</th>
<td>
<code>
<b>{identityKey}</b>
</code>
</td>
</tr>
<tbody>
<tr>
<th scope="row">{_t("settings|security|session_id")}</th>
<td>
<code>{deviceId}</code>
</td>
</tr>
<tr>
<th scope="row">{_t("settings|security|session_key")}</th>
<td>
<code>
<b>{identityKey}</b>
</code>
</td>
</tr>
</tbody>
</table>
</SettingsSubsectionText>
{importExportButtons}
Expand Down
36 changes: 34 additions & 2 deletions test/components/views/settings/CryptographyPanel-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,24 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import { render } from "@testing-library/react";
import { MatrixClient } from "matrix-js-sdk/src/matrix";
import { mocked } from "jest-mock";

import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import * as TestUtils from "../../../test-utils";
import CryptographyPanel from "../../../../src/components/views/settings/CryptographyPanel";
import { flushPromises } from "../../../test-utils";

describe("CryptographyPanel", () => {
it("shows the session ID and key", () => {
it("shows the session ID and key", async () => {
const sessionId = "ABCDEFGHIJ";
const sessionKey = "AbCDeFghIJK7L/m4nOPqRSTUVW4xyzaBCDef6gHIJkl";
const sessionKeyFormatted = "<b>AbCD eFgh IJK7 L/m4 nOPq RSTU VW4x yzaB CDef 6gHI Jkl</b>";

TestUtils.stubClient();
const client: MatrixClient = MatrixClientPeg.safeGet();
client.deviceId = sessionId;
client.getDeviceEd25519Key = () => sessionKey;

mocked(client.getCrypto()!.getOwnDeviceKeys).mockResolvedValue({ ed25519: sessionKey, curve25519: "1234" });

// When we render the CryptographyPanel
const rendered = render(<CryptographyPanel />);
Expand All @@ -32,6 +35,35 @@ describe("CryptographyPanel", () => {
const codes = rendered.container.querySelectorAll("code");
expect(codes.length).toEqual(2);
expect(codes[0].innerHTML).toEqual(sessionId);

// Initially a placeholder
expect(codes[1].innerHTML).toEqual("<b>...</b>");

// Then the actual key
await flushPromises();
expect(codes[1].innerHTML).toEqual(sessionKeyFormatted);
});

it("handles errors fetching session key", async () => {
const sessionId = "ABCDEFGHIJ";

TestUtils.stubClient();
const client: MatrixClient = MatrixClientPeg.safeGet();
client.deviceId = sessionId;

mocked(client.getCrypto()!.getOwnDeviceKeys).mockRejectedValue(new Error("bleh"));

// When we render the CryptographyPanel
const rendered = render(<CryptographyPanel />);

// Then it displays info about the user's session
const codes = rendered.container.querySelectorAll("code");

// Initially a placeholder
expect(codes[1].innerHTML).toEqual("<b>...</b>");

// Then "not supported key
await flushPromises();
expect(codes[1].innerHTML).toEqual("<b>&lt;not supported&gt;</b>");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -314,32 +314,52 @@ exports[`<SecurityUserSettingsTab /> renders security section 1`] = `
<table
class="mx_CryptographyPanel_sessionInfo"
>
<tr>
<th
scope="row"
>
Session ID:
</th>
<td>
<code />
</td>
</tr>
<tr>
<th
scope="row"
>
Session key:
</th>
<td>
<code>
<b>
&lt;not supported&gt;
</b>
</code>
</td>
</tr>
<tbody>
<tr>
<th
scope="row"
>
Session ID:
</th>
<td>
<code />
</td>
</tr>
<tr>
<th
scope="row"
>
Session key:
</th>
<td>
<code>
<b>
...
</b>
</code>
</td>
</tr>
</tbody>
</table>
</div>
<div
class="mx_CryptographyPanel_importExportButtons"
>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
role="button"
tabindex="0"
>
Export E2E room keys
</div>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
role="button"
tabindex="0"
>
Import E2E room keys
</div>
</div>
<div
class="mx_SettingsFlag"
>
Expand Down
4 changes: 1 addition & 3 deletions test/test-utils/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,6 @@ export const mockClientMethodsDevice = (
deviceId = "test-device-id",
): Partial<Record<MethodLikeKeys<MatrixClient>, unknown>> => ({
getDeviceId: jest.fn().mockReturnValue(deviceId),
getDeviceEd25519Key: jest.fn(),
getDevices: jest.fn().mockResolvedValue({ devices: [] }),
});

Expand Down Expand Up @@ -164,10 +163,9 @@ export const mockClientMethodsCrypto = (): Partial<
isSecretStorageReady: jest.fn(),
getSessionBackupPrivateKey: jest.fn(),
getVersion: jest.fn().mockReturnValue("Version 0"),
getOwnDeviceKeys: jest.fn(),
getOwnDeviceKeys: jest.fn().mockReturnValue(new Promise(() => {})),
getCrossSigningKeyId: jest.fn(),
}),
getDeviceEd25519Key: jest.fn(),
});

export const mockClientMethodsRooms = (rooms: Room[] = []): Partial<Record<MethodLikeKeys<MatrixClient>, unknown>> => ({
Expand Down
1 change: 1 addition & 0 deletions test/test-utils/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export function createTestClient(): MatrixClient {
},
},
getCrypto: jest.fn().mockReturnValue({
getOwnDeviceKeys: jest.fn(),
getUserDeviceInfo: jest.fn(),
getUserVerificationStatus: jest.fn(),
getDeviceVerificationStatus: jest.fn(),
Expand Down
Loading