/* Copyright 2019, 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ import React from "react"; import { ClientEvent, MatrixEvent } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; import { CryptoEvent } from "matrix-js-sdk/src/crypto"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { _t } from "../../../languageHandler"; import Modal from "../../../Modal"; import Spinner from "../elements/Spinner"; import InteractiveAuthDialog from "../dialogs/InteractiveAuthDialog"; import ConfirmDestroyCrossSigningDialog from "../dialogs/security/ConfirmDestroyCrossSigningDialog"; import SetupEncryptionDialog from "../dialogs/security/SetupEncryptionDialog"; import { accessSecretStorage, withSecretStorageKeyCache } from "../../../SecurityManager"; import AccessibleButton from "../elements/AccessibleButton"; import { SettingsSubsectionText } from "./shared/SettingsSubsection"; interface IState { error: boolean; crossSigningPublicKeysOnDevice?: boolean; crossSigningPrivateKeysInStorage?: boolean; masterPrivateKeyCached?: boolean; selfSigningPrivateKeyCached?: boolean; userSigningPrivateKeyCached?: boolean; homeserverSupportsCrossSigning?: boolean; crossSigningReady?: boolean; } export default class CrossSigningPanel extends React.PureComponent<{}, IState> { private unmounted = false; public constructor(props: {}) { super(props); this.state = { error: false, }; } public componentDidMount(): void { const cli = MatrixClientPeg.safeGet(); cli.on(ClientEvent.AccountData, this.onAccountData); cli.on(CryptoEvent.UserTrustStatusChanged, this.onStatusChanged); cli.on(CryptoEvent.KeysChanged, this.onStatusChanged); this.getUpdatedStatus(); } public componentWillUnmount(): void { this.unmounted = true; const cli = MatrixClientPeg.get(); if (!cli) return; cli.removeListener(ClientEvent.AccountData, this.onAccountData); cli.removeListener(CryptoEvent.UserTrustStatusChanged, this.onStatusChanged); cli.removeListener(CryptoEvent.KeysChanged, this.onStatusChanged); } private onAccountData = (event: MatrixEvent): void => { const type = event.getType(); if (type.startsWith("m.cross_signing") || type.startsWith("m.secret_storage")) { this.getUpdatedStatus(); } }; private onBootstrapClick = (): void => { if (this.state.crossSigningPrivateKeysInStorage) { Modal.createDialog(SetupEncryptionDialog, {}, undefined, /* priority = */ false, /* static = */ true); } else { // Trigger the flow to set up secure backup, which is what this will do when in // the appropriate state. accessSecretStorage(); } }; private onStatusChanged = (): void => { this.getUpdatedStatus(); }; private async getUpdatedStatus(): Promise { const cli = MatrixClientPeg.safeGet(); const crypto = cli.getCrypto(); if (!crypto) return; const crossSigningStatus = await crypto.getCrossSigningStatus(); const crossSigningPublicKeysOnDevice = crossSigningStatus.publicKeysOnDevice; const crossSigningPrivateKeysInStorage = crossSigningStatus.privateKeysInSecretStorage; const masterPrivateKeyCached = crossSigningStatus.privateKeysCachedLocally.masterKey; const selfSigningPrivateKeyCached = crossSigningStatus.privateKeysCachedLocally.selfSigningKey; const userSigningPrivateKeyCached = crossSigningStatus.privateKeysCachedLocally.userSigningKey; const homeserverSupportsCrossSigning = await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing"); const crossSigningReady = await crypto.isCrossSigningReady(); this.setState({ crossSigningPublicKeysOnDevice, crossSigningPrivateKeysInStorage, masterPrivateKeyCached, selfSigningPrivateKeyCached, userSigningPrivateKeyCached, homeserverSupportsCrossSigning, crossSigningReady, }); } /** * Reset the user's cross-signing keys. */ private async resetCrossSigning(): Promise { this.setState({ error: false }); try { const cli = MatrixClientPeg.safeGet(); await withSecretStorageKeyCache(async () => { await cli.getCrypto()!.bootstrapCrossSigning({ authUploadDeviceSigningKeys: async (makeRequest): Promise => { const { finished } = Modal.createDialog(InteractiveAuthDialog, { title: _t("encryption|bootstrap_title"), matrixClient: cli, makeRequest, }); const [confirmed] = await finished; if (!confirmed) { throw new Error("Cross-signing key upload auth canceled"); } }, setupNewCrossSigning: true, }); }); } catch (e) { this.setState({ error: true }); logger.error("Error bootstrapping cross-signing", e); } if (this.unmounted) return; this.getUpdatedStatus(); } /** * Callback for when the user clicks the "reset cross signing" button. * * Shows a confirmation dialog, and then does the reset if confirmed. */ private onResetCrossSigningClick = (): void => { Modal.createDialog(ConfirmDestroyCrossSigningDialog, { onFinished: async (act) => { if (!act) return; this.resetCrossSigning(); }, }); }; public render(): React.ReactNode { const { error, crossSigningPublicKeysOnDevice, crossSigningPrivateKeysInStorage, masterPrivateKeyCached, selfSigningPrivateKeyCached, userSigningPrivateKeyCached, homeserverSupportsCrossSigning, crossSigningReady, } = this.state; let errorSection; if (error) { errorSection =
{error.toString()}
; } let summarisedStatus; if (homeserverSupportsCrossSigning === undefined) { summarisedStatus = ; } else if (!homeserverSupportsCrossSigning) { summarisedStatus = ( {_t("encryption|cross_signing_unsupported")} ); } else if (crossSigningReady && crossSigningPrivateKeysInStorage) { summarisedStatus = ( ✅ {_t("encryption|cross_signing_ready")} ); } else if (crossSigningReady && !crossSigningPrivateKeysInStorage) { summarisedStatus = ( ⚠️ {_t("encryption|cross_signing_ready_no_backup")} ); } else if (crossSigningPrivateKeysInStorage) { summarisedStatus = ( {_t("encryption|cross_signing_untrusted")} ); } else { summarisedStatus = ( {_t("encryption|cross_signing_not_ready")} ); } const keysExistAnywhere = crossSigningPublicKeysOnDevice || crossSigningPrivateKeysInStorage || masterPrivateKeyCached || selfSigningPrivateKeyCached || userSigningPrivateKeyCached; const keysExistEverywhere = crossSigningPublicKeysOnDevice && crossSigningPrivateKeysInStorage && masterPrivateKeyCached && selfSigningPrivateKeyCached && userSigningPrivateKeyCached; const actions: JSX.Element[] = []; // TODO: determine how better to expose this to users in addition to prompts at login/toast if (!keysExistEverywhere && homeserverSupportsCrossSigning) { let buttonCaption = _t("encryption|set_up_toast_title"); if (crossSigningPrivateKeysInStorage) { buttonCaption = _t("encryption|verify_toast_title"); } actions.push( {buttonCaption} , ); } if (keysExistAnywhere) { actions.push( {_t("action|reset")} , ); } let actionRow; if (actions.length) { actionRow =
{actions}
; } return ( <> {summarisedStatus}
{_t("common|advanced")}
{_t("settings|security|cross_signing_public_keys")} {crossSigningPublicKeysOnDevice ? _t("settings|security|cross_signing_in_memory") : _t("settings|security|cross_signing_not_found")}
{_t("settings|security|cross_signing_private_keys")} {crossSigningPrivateKeysInStorage ? _t("settings|security|cross_signing_in_4s") : _t("settings|security|cross_signing_not_in_4s")}
{_t("settings|security|cross_signing_master_private_Key")} {masterPrivateKeyCached ? _t("settings|security|cross_signing_cached") : _t("settings|security|cross_signing_not_cached")}
{_t("settings|security|cross_signing_self_signing_private_key")} {selfSigningPrivateKeyCached ? _t("settings|security|cross_signing_cached") : _t("settings|security|cross_signing_not_cached")}
{_t("settings|security|cross_signing_user_signing_private_key")} {userSigningPrivateKeyCached ? _t("settings|security|cross_signing_cached") : _t("settings|security|cross_signing_not_cached")}
{_t("settings|security|cross_signing_homeserver_support")} {homeserverSupportsCrossSigning ? _t("settings|security|cross_signing_homeserver_support_exists") : _t("settings|security|cross_signing_not_found")}
{errorSection} {actionRow} ); } }