You've already forked matrix-react-sdk
							
							
				mirror of
				https://github.com/matrix-org/matrix-react-sdk.git
				synced 2025-11-03 00:33:22 +03:00 
			
		
		
		
	Merge pull request #5182 from matrix-org/jryans/defer-cross-signing-setup
Split up cross-signing and secure backup settings
This commit is contained in:
		@@ -202,10 +202,10 @@
 | 
				
			|||||||
@import "./views/settings/_E2eAdvancedPanel.scss";
 | 
					@import "./views/settings/_E2eAdvancedPanel.scss";
 | 
				
			||||||
@import "./views/settings/_EmailAddresses.scss";
 | 
					@import "./views/settings/_EmailAddresses.scss";
 | 
				
			||||||
@import "./views/settings/_IntegrationManager.scss";
 | 
					@import "./views/settings/_IntegrationManager.scss";
 | 
				
			||||||
@import "./views/settings/_KeyBackupPanel.scss";
 | 
					 | 
				
			||||||
@import "./views/settings/_Notifications.scss";
 | 
					@import "./views/settings/_Notifications.scss";
 | 
				
			||||||
@import "./views/settings/_PhoneNumbers.scss";
 | 
					@import "./views/settings/_PhoneNumbers.scss";
 | 
				
			||||||
@import "./views/settings/_ProfileSettings.scss";
 | 
					@import "./views/settings/_ProfileSettings.scss";
 | 
				
			||||||
 | 
					@import "./views/settings/_SecureBackupPanel.scss";
 | 
				
			||||||
@import "./views/settings/_SetIdServer.scss";
 | 
					@import "./views/settings/_SetIdServer.scss";
 | 
				
			||||||
@import "./views/settings/_SetIntegrationManager.scss";
 | 
					@import "./views/settings/_SetIntegrationManager.scss";
 | 
				
			||||||
@import "./views/settings/_UpdateCheckButton.scss";
 | 
					@import "./views/settings/_UpdateCheckButton.scss";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
/*
 | 
					/*
 | 
				
			||||||
Copyright 2018 New Vector Ltd
 | 
					Copyright 2018 New Vector Ltd
 | 
				
			||||||
Copyright 2019 The Matrix.org Foundation C.I.C.
 | 
					Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
you may not use this file except in compliance with the License.
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
@@ -15,23 +15,39 @@ See the License for the specific language governing permissions and
 | 
				
			|||||||
limitations under the License.
 | 
					limitations under the License.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.mx_KeyBackupPanel_sigValid, .mx_KeyBackupPanel_sigInvalid,
 | 
					.mx_SecureBackupPanel_sigValid, .mx_SecureBackupPanel_sigInvalid,
 | 
				
			||||||
.mx_KeyBackupPanel_deviceVerified, .mx_KeyBackupPanel_deviceNotVerified {
 | 
					.mx_SecureBackupPanel_deviceVerified, .mx_SecureBackupPanel_deviceNotVerified {
 | 
				
			||||||
    font-weight: bold;
 | 
					    font-weight: bold;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.mx_KeyBackupPanel_sigValid, .mx_KeyBackupPanel_deviceVerified {
 | 
					.mx_SecureBackupPanel_sigValid, .mx_SecureBackupPanel_deviceVerified {
 | 
				
			||||||
    color: $e2e-verified-color;
 | 
					    color: $e2e-verified-color;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.mx_KeyBackupPanel_sigInvalid, .mx_KeyBackupPanel_deviceNotVerified {
 | 
					.mx_SecureBackupPanel_sigInvalid, .mx_SecureBackupPanel_deviceNotVerified {
 | 
				
			||||||
    color: $e2e-warning-color;
 | 
					    color: $e2e-warning-color;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.mx_KeyBackupPanel_deviceName {
 | 
					.mx_SecureBackupPanel_deviceName {
 | 
				
			||||||
    font-style: italic;
 | 
					    font-style: italic;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.mx_KeyBackupPanel_buttonRow {
 | 
					.mx_SecureBackupPanel_buttonRow {
 | 
				
			||||||
    margin: 1em 0;
 | 
					    margin: 1em 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :nth-child(n + 1) {
 | 
				
			||||||
 | 
					        margin-inline-end: 10px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.mx_SecureBackupPanel_statusList {
 | 
				
			||||||
 | 
					    border-spacing: 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    td {
 | 
				
			||||||
 | 
					        padding: 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        &:first-of-type {
 | 
				
			||||||
 | 
					            padding-inline-end: 1em;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -14,6 +14,10 @@ See the License for the specific language governing permissions and
 | 
				
			|||||||
limitations under the License.
 | 
					limitations under the License.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.mx_SettingsTab {
 | 
				
			||||||
 | 
					    color: $muted-fg-color;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.mx_SettingsTab_warningText {
 | 
					.mx_SettingsTab_warningText {
 | 
				
			||||||
    color: $warning-color;
 | 
					    color: $warning-color;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -250,7 +250,7 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
 | 
				
			|||||||
                        'Cross-signing keys dialog', '', InteractiveAuthDialog,
 | 
					                        'Cross-signing keys dialog', '', InteractiveAuthDialog,
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
                            title: _t("Setting up keys"),
 | 
					                            title: _t("Setting up keys"),
 | 
				
			||||||
                            matrixClient: MatrixClientPeg.get(),
 | 
					                            matrixClient: cli,
 | 
				
			||||||
                            makeRequest,
 | 
					                            makeRequest,
 | 
				
			||||||
                        },
 | 
					                        },
 | 
				
			||||||
                    );
 | 
					                    );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -280,6 +280,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
 | 
				
			|||||||
        const { forceReset } = this.props;
 | 
					        const { forceReset } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
 | 
					            // JRS: In an upcoming change, the cross-signing steps will be
 | 
				
			||||||
 | 
					            // removed from here and this will instead be about secret storage
 | 
				
			||||||
 | 
					            // only.
 | 
				
			||||||
            if (forceReset) {
 | 
					            if (forceReset) {
 | 
				
			||||||
                console.log("Forcing cross-signing and secret storage reset");
 | 
					                console.log("Forcing cross-signing and secret storage reset");
 | 
				
			||||||
                await cli.bootstrapSecretStorage({
 | 
					                await cli.bootstrapSecretStorage({
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,9 +19,9 @@ import React from 'react';
 | 
				
			|||||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
 | 
					import {MatrixClientPeg} from '../../../MatrixClientPeg';
 | 
				
			||||||
import { _t } from '../../../languageHandler';
 | 
					import { _t } from '../../../languageHandler';
 | 
				
			||||||
import * as sdk from '../../../index';
 | 
					import * as sdk from '../../../index';
 | 
				
			||||||
import { accessSecretStorage } from '../../../SecurityManager';
 | 
					 | 
				
			||||||
import Modal from '../../../Modal';
 | 
					import Modal from '../../../Modal';
 | 
				
			||||||
import Spinner from '../elements/Spinner';
 | 
					import Spinner from '../elements/Spinner';
 | 
				
			||||||
 | 
					import InteractiveAuthDialog from '../dialogs/InteractiveAuthDialog';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class CrossSigningPanel extends React.PureComponent {
 | 
					export default class CrossSigningPanel extends React.PureComponent {
 | 
				
			||||||
    constructor(props) {
 | 
					    constructor(props) {
 | 
				
			||||||
@@ -31,13 +31,13 @@ export default class CrossSigningPanel extends React.PureComponent {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        this.state = {
 | 
					        this.state = {
 | 
				
			||||||
            error: null,
 | 
					            error: null,
 | 
				
			||||||
            crossSigningPublicKeysOnDevice: false,
 | 
					            crossSigningPublicKeysOnDevice: null,
 | 
				
			||||||
            crossSigningPrivateKeysInStorage: false,
 | 
					            crossSigningPrivateKeysInStorage: null,
 | 
				
			||||||
            masterPrivateKeyCached: false,
 | 
					            masterPrivateKeyCached: null,
 | 
				
			||||||
            selfSigningPrivateKeyCached: false,
 | 
					            selfSigningPrivateKeyCached: null,
 | 
				
			||||||
            userSigningPrivateKeyCached: false,
 | 
					            userSigningPrivateKeyCached: null,
 | 
				
			||||||
            sessionBackupKeyCached: false,
 | 
					            homeserverSupportsCrossSigning: null,
 | 
				
			||||||
            secretStorageKeyInAccount: false,
 | 
					            crossSigningReady: null,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -66,7 +66,7 @@ export default class CrossSigningPanel extends React.PureComponent {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _onBootstrapClick = () => {
 | 
					    _onBootstrapClick = () => {
 | 
				
			||||||
        this._bootstrapSecureSecretStorage(false);
 | 
					        this._bootstrapCrossSigning({ forceReset: false });
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    onStatusChanged = () => {
 | 
					    onStatusChanged = () => {
 | 
				
			||||||
@@ -83,14 +83,9 @@ export default class CrossSigningPanel extends React.PureComponent {
 | 
				
			|||||||
        const masterPrivateKeyCached = !!(pkCache && await pkCache.getCrossSigningKeyCache("master"));
 | 
					        const masterPrivateKeyCached = !!(pkCache && await pkCache.getCrossSigningKeyCache("master"));
 | 
				
			||||||
        const selfSigningPrivateKeyCached = !!(pkCache && await pkCache.getCrossSigningKeyCache("self_signing"));
 | 
					        const selfSigningPrivateKeyCached = !!(pkCache && await pkCache.getCrossSigningKeyCache("self_signing"));
 | 
				
			||||||
        const userSigningPrivateKeyCached = !!(pkCache && await pkCache.getCrossSigningKeyCache("user_signing"));
 | 
					        const userSigningPrivateKeyCached = !!(pkCache && await pkCache.getCrossSigningKeyCache("user_signing"));
 | 
				
			||||||
        const sessionBackupKeyFromCache = await cli._crypto.getSessionBackupPrivateKey();
 | 
					 | 
				
			||||||
        const sessionBackupKeyCached = !!(sessionBackupKeyFromCache);
 | 
					 | 
				
			||||||
        const sessionBackupKeyWellFormed = sessionBackupKeyFromCache instanceof Uint8Array;
 | 
					 | 
				
			||||||
        const secretStorageKeyInAccount = await secretStorage.hasKey();
 | 
					 | 
				
			||||||
        const homeserverSupportsCrossSigning =
 | 
					        const homeserverSupportsCrossSigning =
 | 
				
			||||||
            await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing");
 | 
					            await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing");
 | 
				
			||||||
        const crossSigningReady = await cli.isCrossSigningReady();
 | 
					        const crossSigningReady = await cli.isCrossSigningReady();
 | 
				
			||||||
        const secretStorageReady = await cli.isSecretStorageReady();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.setState({
 | 
					        this.setState({
 | 
				
			||||||
            crossSigningPublicKeysOnDevice,
 | 
					            crossSigningPublicKeysOnDevice,
 | 
				
			||||||
@@ -98,45 +93,56 @@ export default class CrossSigningPanel extends React.PureComponent {
 | 
				
			|||||||
            masterPrivateKeyCached,
 | 
					            masterPrivateKeyCached,
 | 
				
			||||||
            selfSigningPrivateKeyCached,
 | 
					            selfSigningPrivateKeyCached,
 | 
				
			||||||
            userSigningPrivateKeyCached,
 | 
					            userSigningPrivateKeyCached,
 | 
				
			||||||
            sessionBackupKeyCached,
 | 
					 | 
				
			||||||
            sessionBackupKeyWellFormed,
 | 
					 | 
				
			||||||
            secretStorageKeyInAccount,
 | 
					 | 
				
			||||||
            homeserverSupportsCrossSigning,
 | 
					            homeserverSupportsCrossSigning,
 | 
				
			||||||
            crossSigningReady,
 | 
					            crossSigningReady,
 | 
				
			||||||
            secretStorageReady,
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Bootstrapping secret storage may take one of these paths:
 | 
					     * Bootstrapping cross-signing take one of these paths:
 | 
				
			||||||
     * 1. Create secret storage from a passphrase and store cross-signing keys
 | 
					     * 1. Create cross-signing keys locally and store in secret storage (if it
 | 
				
			||||||
     *    in secret storage.
 | 
					     *    already exists on the account).
 | 
				
			||||||
     * 2. Access existing secret storage by requesting passphrase and accessing
 | 
					     * 2. Access existing secret storage by requesting passphrase and accessing
 | 
				
			||||||
     *    cross-signing keys as needed.
 | 
					     *    cross-signing keys as needed.
 | 
				
			||||||
     * 3. All keys are loaded and there's nothing to do.
 | 
					     * 3. All keys are loaded and there's nothing to do.
 | 
				
			||||||
     * @param {bool} [forceReset] Bootstrap again even if keys already present
 | 
					     * @param {bool} [forceReset] Bootstrap again even if keys already present
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    _bootstrapSecureSecretStorage = async (forceReset=false) => {
 | 
					    _bootstrapCrossSigning = async ({ forceReset = false }) => {
 | 
				
			||||||
        this.setState({ error: null });
 | 
					        this.setState({ error: null });
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            await accessSecretStorage(() => undefined, forceReset);
 | 
					            const cli = MatrixClientPeg.get();
 | 
				
			||||||
 | 
					            await cli.bootstrapCrossSigning({
 | 
				
			||||||
 | 
					                authUploadDeviceSigningKeys: async (makeRequest) => {
 | 
				
			||||||
 | 
					                    const { finished } = Modal.createTrackedDialog(
 | 
				
			||||||
 | 
					                        'Cross-signing keys dialog', '', InteractiveAuthDialog,
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            title: _t("Setting up keys"),
 | 
				
			||||||
 | 
					                            matrixClient: cli,
 | 
				
			||||||
 | 
					                            makeRequest,
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    const [confirmed] = await finished;
 | 
				
			||||||
 | 
					                    if (!confirmed) {
 | 
				
			||||||
 | 
					                        throw new Error("Cross-signing key upload auth canceled");
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                setupNewCrossSigning: forceReset,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
        } catch (e) {
 | 
					        } catch (e) {
 | 
				
			||||||
            this.setState({ error: e });
 | 
					            this.setState({ error: e });
 | 
				
			||||||
            console.error("Error bootstrapping secret storage", e);
 | 
					            console.error("Error bootstrapping cross-signing", e);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (this._unmounted) return;
 | 
					        if (this._unmounted) return;
 | 
				
			||||||
        this._getUpdatedStatus();
 | 
					        this._getUpdatedStatus();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    onDestroyStorage = (act) => {
 | 
					    _resetCrossSigning = () => {
 | 
				
			||||||
        if (!act) return;
 | 
					 | 
				
			||||||
        this._bootstrapSecureSecretStorage(true);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _destroySecureSecretStorage = () => {
 | 
					 | 
				
			||||||
        const ConfirmDestroyCrossSigningDialog = sdk.getComponent("dialogs.ConfirmDestroyCrossSigningDialog");
 | 
					        const ConfirmDestroyCrossSigningDialog = sdk.getComponent("dialogs.ConfirmDestroyCrossSigningDialog");
 | 
				
			||||||
        Modal.createDialog(ConfirmDestroyCrossSigningDialog, {
 | 
					        Modal.createDialog(ConfirmDestroyCrossSigningDialog, {
 | 
				
			||||||
            onFinished: this.onDestroyStorage,
 | 
					            onFinished: (act) => {
 | 
				
			||||||
 | 
					                if (!act) return;
 | 
				
			||||||
 | 
					                this._bootstrapCrossSigning({ forceReset: true });
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -149,12 +155,8 @@ export default class CrossSigningPanel extends React.PureComponent {
 | 
				
			|||||||
            masterPrivateKeyCached,
 | 
					            masterPrivateKeyCached,
 | 
				
			||||||
            selfSigningPrivateKeyCached,
 | 
					            selfSigningPrivateKeyCached,
 | 
				
			||||||
            userSigningPrivateKeyCached,
 | 
					            userSigningPrivateKeyCached,
 | 
				
			||||||
            sessionBackupKeyCached,
 | 
					 | 
				
			||||||
            sessionBackupKeyWellFormed,
 | 
					 | 
				
			||||||
            secretStorageKeyInAccount,
 | 
					 | 
				
			||||||
            homeserverSupportsCrossSigning,
 | 
					            homeserverSupportsCrossSigning,
 | 
				
			||||||
            crossSigningReady,
 | 
					            crossSigningReady,
 | 
				
			||||||
            secretStorageReady,
 | 
					 | 
				
			||||||
        } = this.state;
 | 
					        } = this.state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let errorSection;
 | 
					        let errorSection;
 | 
				
			||||||
@@ -169,14 +171,9 @@ export default class CrossSigningPanel extends React.PureComponent {
 | 
				
			|||||||
            summarisedStatus = <p>{_t(
 | 
					            summarisedStatus = <p>{_t(
 | 
				
			||||||
                "Your homeserver does not support cross-signing.",
 | 
					                "Your homeserver does not support cross-signing.",
 | 
				
			||||||
            )}</p>;
 | 
					            )}</p>;
 | 
				
			||||||
        } else if (crossSigningReady && secretStorageReady) {
 | 
					        } else if (crossSigningReady) {
 | 
				
			||||||
            summarisedStatus = <p>✅ {_t(
 | 
					            summarisedStatus = <p>✅ {_t(
 | 
				
			||||||
                "Cross-signing and secret storage are ready for use.",
 | 
					                "Cross-signing is ready for use.",
 | 
				
			||||||
            )}</p>;
 | 
					 | 
				
			||||||
        } else if (crossSigningReady && !secretStorageReady) {
 | 
					 | 
				
			||||||
            summarisedStatus = <p>✅ {_t(
 | 
					 | 
				
			||||||
                "Cross-signing is ready for use, but secret storage is " +
 | 
					 | 
				
			||||||
                "currently not being used to backup your keys.",
 | 
					 | 
				
			||||||
            )}</p>;
 | 
					            )}</p>;
 | 
				
			||||||
        } else if (crossSigningPrivateKeysInStorage) {
 | 
					        } else if (crossSigningPrivateKeysInStorage) {
 | 
				
			||||||
            summarisedStatus = <p>{_t(
 | 
					            summarisedStatus = <p>{_t(
 | 
				
			||||||
@@ -185,17 +182,15 @@ export default class CrossSigningPanel extends React.PureComponent {
 | 
				
			|||||||
            )}</p>;
 | 
					            )}</p>;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            summarisedStatus = <p>{_t(
 | 
					            summarisedStatus = <p>{_t(
 | 
				
			||||||
                "Cross-signing and secret storage are not yet set up.",
 | 
					                "Cross-signing is not set up.",
 | 
				
			||||||
            )}</p>;
 | 
					            )}</p>;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const keysExistAnywhere = (
 | 
					        const keysExistAnywhere = (
 | 
				
			||||||
            secretStorageKeyInAccount ||
 | 
					 | 
				
			||||||
            crossSigningPrivateKeysInStorage ||
 | 
					            crossSigningPrivateKeysInStorage ||
 | 
				
			||||||
            crossSigningPublicKeysOnDevice
 | 
					            crossSigningPublicKeysOnDevice
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        const keysExistEverywhere = (
 | 
					        const keysExistEverywhere = (
 | 
				
			||||||
            secretStorageKeyInAccount &&
 | 
					 | 
				
			||||||
            crossSigningPrivateKeysInStorage &&
 | 
					            crossSigningPrivateKeysInStorage &&
 | 
				
			||||||
            crossSigningPublicKeysOnDevice
 | 
					            crossSigningPublicKeysOnDevice
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
@@ -204,8 +199,8 @@ export default class CrossSigningPanel extends React.PureComponent {
 | 
				
			|||||||
        if (keysExistAnywhere) {
 | 
					        if (keysExistAnywhere) {
 | 
				
			||||||
            resetButton = (
 | 
					            resetButton = (
 | 
				
			||||||
                <div className="mx_CrossSigningPanel_buttonRow">
 | 
					                <div className="mx_CrossSigningPanel_buttonRow">
 | 
				
			||||||
                    <AccessibleButton kind="danger" onClick={this._destroySecureSecretStorage}>
 | 
					                    <AccessibleButton kind="danger" onClick={this._resetCrossSigning}>
 | 
				
			||||||
                        {_t("Reset cross-signing and secret storage")}
 | 
					                        {_t("Reset")}
 | 
				
			||||||
                    </AccessibleButton>
 | 
					                    </AccessibleButton>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
@@ -217,22 +212,12 @@ export default class CrossSigningPanel extends React.PureComponent {
 | 
				
			|||||||
            bootstrapButton = (
 | 
					            bootstrapButton = (
 | 
				
			||||||
                <div className="mx_CrossSigningPanel_buttonRow">
 | 
					                <div className="mx_CrossSigningPanel_buttonRow">
 | 
				
			||||||
                    <AccessibleButton kind="primary" onClick={this._onBootstrapClick}>
 | 
					                    <AccessibleButton kind="primary" onClick={this._onBootstrapClick}>
 | 
				
			||||||
                        {_t("Bootstrap cross-signing and secret storage")}
 | 
					                        {_t("Set up")}
 | 
				
			||||||
                    </AccessibleButton>
 | 
					                    </AccessibleButton>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let sessionBackupKeyWellFormedText = "";
 | 
					 | 
				
			||||||
        if (sessionBackupKeyCached) {
 | 
					 | 
				
			||||||
            sessionBackupKeyWellFormedText = ", ";
 | 
					 | 
				
			||||||
            if (sessionBackupKeyWellFormed) {
 | 
					 | 
				
			||||||
                sessionBackupKeyWellFormedText += _t("well formed");
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                sessionBackupKeyWellFormedText += _t("unexpected type");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
            <div>
 | 
					            <div>
 | 
				
			||||||
                {summarisedStatus}
 | 
					                {summarisedStatus}
 | 
				
			||||||
@@ -259,17 +244,6 @@ export default class CrossSigningPanel extends React.PureComponent {
 | 
				
			|||||||
                            <td>{_t("User signing private key:")}</td>
 | 
					                            <td>{_t("User signing private key:")}</td>
 | 
				
			||||||
                            <td>{userSigningPrivateKeyCached ? _t("cached locally") : _t("not found locally")}</td>
 | 
					                            <td>{userSigningPrivateKeyCached ? _t("cached locally") : _t("not found locally")}</td>
 | 
				
			||||||
                        </tr>
 | 
					                        </tr>
 | 
				
			||||||
                        <tr>
 | 
					 | 
				
			||||||
                            <td>{_t("Session backup key:")}</td>
 | 
					 | 
				
			||||||
                            <td>
 | 
					 | 
				
			||||||
                                {sessionBackupKeyCached ? _t("cached locally") : _t("not found locally")}
 | 
					 | 
				
			||||||
                                {sessionBackupKeyWellFormedText}
 | 
					 | 
				
			||||||
                            </td>
 | 
					 | 
				
			||||||
                        </tr>
 | 
					 | 
				
			||||||
                        <tr>
 | 
					 | 
				
			||||||
                            <td>{_t("Secret storage public key:")}</td>
 | 
					 | 
				
			||||||
                            <td>{secretStorageKeyInAccount ? _t("in account data") : _t("not found")}</td>
 | 
					 | 
				
			||||||
                        </tr>
 | 
					 | 
				
			||||||
                        <tr>
 | 
					                        <tr>
 | 
				
			||||||
                            <td>{_t("Homeserver feature support:")}</td>
 | 
					                            <td>{_t("Homeserver feature support:")}</td>
 | 
				
			||||||
                            <td>{homeserverSupportsCrossSigning ? _t("exists") : _t("not found")}</td>
 | 
					                            <td>{homeserverSupportsCrossSigning ? _t("exists") : _t("not found")}</td>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,13 +17,17 @@ limitations under the License.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import React from 'react';
 | 
					import React from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import * as sdk from '../../../index';
 | 
					 | 
				
			||||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
 | 
					import {MatrixClientPeg} from '../../../MatrixClientPeg';
 | 
				
			||||||
import { _t } from '../../../languageHandler';
 | 
					import { _t } from '../../../languageHandler';
 | 
				
			||||||
import Modal from '../../../Modal';
 | 
					import Modal from '../../../Modal';
 | 
				
			||||||
import { isSecureBackupRequired } from '../../../utils/WellKnownUtils';
 | 
					import { isSecureBackupRequired } from '../../../utils/WellKnownUtils';
 | 
				
			||||||
 | 
					import Spinner from '../elements/Spinner';
 | 
				
			||||||
 | 
					import AccessibleButton from '../elements/AccessibleButton';
 | 
				
			||||||
 | 
					import QuestionDialog from '../dialogs/QuestionDialog';
 | 
				
			||||||
 | 
					import RestoreKeyBackupDialog from '../dialogs/keybackup/RestoreKeyBackupDialog';
 | 
				
			||||||
 | 
					import { accessSecretStorage } from '../../../SecurityManager';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class KeyBackupPanel extends React.PureComponent {
 | 
					export default class SecureBackupPanel extends React.PureComponent {
 | 
				
			||||||
    constructor(props) {
 | 
					    constructor(props) {
 | 
				
			||||||
        super(props);
 | 
					        super(props);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -31,9 +35,13 @@ export default class KeyBackupPanel extends React.PureComponent {
 | 
				
			|||||||
        this.state = {
 | 
					        this.state = {
 | 
				
			||||||
            loading: true,
 | 
					            loading: true,
 | 
				
			||||||
            error: null,
 | 
					            error: null,
 | 
				
			||||||
 | 
					            backupKeyStored: null,
 | 
				
			||||||
 | 
					            backupKeyCached: null,
 | 
				
			||||||
 | 
					            backupKeyWellFormed: null,
 | 
				
			||||||
 | 
					            secretStorageKeyInAccount: null,
 | 
				
			||||||
 | 
					            secretStorageReady: null,
 | 
				
			||||||
            backupInfo: null,
 | 
					            backupInfo: null,
 | 
				
			||||||
            backupSigStatus: null,
 | 
					            backupSigStatus: null,
 | 
				
			||||||
            backupKeyStored: null,
 | 
					 | 
				
			||||||
            sessionsRemaining: 0,
 | 
					            sessionsRemaining: 0,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -73,56 +81,73 @@ export default class KeyBackupPanel extends React.PureComponent {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async _checkKeyBackupStatus() {
 | 
					    async _checkKeyBackupStatus() {
 | 
				
			||||||
 | 
					        this._getUpdatedDiagnostics();
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            const {backupInfo, trustInfo} = await MatrixClientPeg.get().checkKeyBackup();
 | 
					            const {backupInfo, trustInfo} = await MatrixClientPeg.get().checkKeyBackup();
 | 
				
			||||||
            const backupKeyStored = Boolean(await MatrixClientPeg.get().isKeyBackupKeyStored());
 | 
					 | 
				
			||||||
            this.setState({
 | 
					            this.setState({
 | 
				
			||||||
 | 
					                loading: false,
 | 
				
			||||||
 | 
					                error: null,
 | 
				
			||||||
                backupInfo,
 | 
					                backupInfo,
 | 
				
			||||||
                backupSigStatus: trustInfo,
 | 
					                backupSigStatus: trustInfo,
 | 
				
			||||||
                backupKeyStored,
 | 
					 | 
				
			||||||
                error: null,
 | 
					 | 
				
			||||||
                loading: false,
 | 
					 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        } catch (e) {
 | 
					        } catch (e) {
 | 
				
			||||||
            console.log("Unable to fetch check backup status", e);
 | 
					            console.log("Unable to fetch check backup status", e);
 | 
				
			||||||
            if (this._unmounted) return;
 | 
					            if (this._unmounted) return;
 | 
				
			||||||
            this.setState({
 | 
					            this.setState({
 | 
				
			||||||
 | 
					                loading: false,
 | 
				
			||||||
                error: e,
 | 
					                error: e,
 | 
				
			||||||
                backupInfo: null,
 | 
					                backupInfo: null,
 | 
				
			||||||
                backupSigStatus: null,
 | 
					                backupSigStatus: null,
 | 
				
			||||||
                backupKeyStored: null,
 | 
					 | 
				
			||||||
                loading: false,
 | 
					 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async _loadBackupStatus() {
 | 
					    async _loadBackupStatus() {
 | 
				
			||||||
        this.setState({loading: true});
 | 
					        this.setState({ loading: true });
 | 
				
			||||||
 | 
					        this._getUpdatedDiagnostics();
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
 | 
					            const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
 | 
				
			||||||
            const backupSigStatus = await MatrixClientPeg.get().isKeyBackupTrusted(backupInfo);
 | 
					            const backupSigStatus = await MatrixClientPeg.get().isKeyBackupTrusted(backupInfo);
 | 
				
			||||||
            const backupKeyStored = await MatrixClientPeg.get().isKeyBackupKeyStored();
 | 
					 | 
				
			||||||
            if (this._unmounted) return;
 | 
					            if (this._unmounted) return;
 | 
				
			||||||
            this.setState({
 | 
					            this.setState({
 | 
				
			||||||
 | 
					                loading: false,
 | 
				
			||||||
                error: null,
 | 
					                error: null,
 | 
				
			||||||
                backupInfo,
 | 
					                backupInfo,
 | 
				
			||||||
                backupSigStatus,
 | 
					                backupSigStatus,
 | 
				
			||||||
                backupKeyStored,
 | 
					 | 
				
			||||||
                loading: false,
 | 
					 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        } catch (e) {
 | 
					        } catch (e) {
 | 
				
			||||||
            console.log("Unable to fetch key backup status", e);
 | 
					            console.log("Unable to fetch key backup status", e);
 | 
				
			||||||
            if (this._unmounted) return;
 | 
					            if (this._unmounted) return;
 | 
				
			||||||
            this.setState({
 | 
					            this.setState({
 | 
				
			||||||
 | 
					                loading: false,
 | 
				
			||||||
                error: e,
 | 
					                error: e,
 | 
				
			||||||
                backupInfo: null,
 | 
					                backupInfo: null,
 | 
				
			||||||
                backupSigStatus: null,
 | 
					                backupSigStatus: null,
 | 
				
			||||||
                backupKeyStored: null,
 | 
					 | 
				
			||||||
                loading: false,
 | 
					 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async _getUpdatedDiagnostics() {
 | 
				
			||||||
 | 
					        const cli = MatrixClientPeg.get();
 | 
				
			||||||
 | 
					        const secretStorage = cli._crypto._secretStorage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const backupKeyStored = await cli.isKeyBackupKeyStored();
 | 
				
			||||||
 | 
					        const backupKeyFromCache = await cli._crypto.getSessionBackupPrivateKey();
 | 
				
			||||||
 | 
					        const backupKeyCached = !!(backupKeyFromCache);
 | 
				
			||||||
 | 
					        const backupKeyWellFormed = backupKeyFromCache instanceof Uint8Array;
 | 
				
			||||||
 | 
					        const secretStorageKeyInAccount = await secretStorage.hasKey();
 | 
				
			||||||
 | 
					        const secretStorageReady = await cli.isSecretStorageReady();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this._unmounted) return;
 | 
				
			||||||
 | 
					        this.setState({
 | 
				
			||||||
 | 
					            backupKeyStored,
 | 
				
			||||||
 | 
					            backupKeyCached,
 | 
				
			||||||
 | 
					            backupKeyWellFormed,
 | 
				
			||||||
 | 
					            secretStorageKeyInAccount,
 | 
				
			||||||
 | 
					            secretStorageReady,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _startNewBackup = () => {
 | 
					    _startNewBackup = () => {
 | 
				
			||||||
        Modal.createTrackedDialogAsync('Key Backup', 'Key Backup',
 | 
					        Modal.createTrackedDialogAsync('Key Backup', 'Key Backup',
 | 
				
			||||||
            import('../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog'),
 | 
					            import('../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog'),
 | 
				
			||||||
@@ -135,7 +160,6 @@ export default class KeyBackupPanel extends React.PureComponent {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _deleteBackup = () => {
 | 
					    _deleteBackup = () => {
 | 
				
			||||||
        const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog');
 | 
					 | 
				
			||||||
        Modal.createTrackedDialog('Delete Backup', '', QuestionDialog, {
 | 
					        Modal.createTrackedDialog('Delete Backup', '', QuestionDialog, {
 | 
				
			||||||
            title: _t('Delete Backup'),
 | 
					            title: _t('Delete Backup'),
 | 
				
			||||||
            description: _t(
 | 
					            description: _t(
 | 
				
			||||||
@@ -155,41 +179,58 @@ export default class KeyBackupPanel extends React.PureComponent {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _restoreBackup = async () => {
 | 
					    _restoreBackup = async () => {
 | 
				
			||||||
        const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
 | 
					 | 
				
			||||||
        Modal.createTrackedDialog(
 | 
					        Modal.createTrackedDialog(
 | 
				
			||||||
            'Restore Backup', '', RestoreKeyBackupDialog, null, null,
 | 
					            'Restore Backup', '', RestoreKeyBackupDialog, null, null,
 | 
				
			||||||
            /* priority = */ false, /* static = */ true,
 | 
					            /* priority = */ false, /* static = */ true,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    render() {
 | 
					    _resetSecretStorage = async () => {
 | 
				
			||||||
        const Spinner = sdk.getComponent("elements.Spinner");
 | 
					        this.setState({ error: null });
 | 
				
			||||||
        const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
 | 
					        try {
 | 
				
			||||||
        const encryptedMessageAreEncrypted = _t(
 | 
					            await accessSecretStorage(() => { }, /* forceReset = */ true);
 | 
				
			||||||
            "Encrypted messages are secured with end-to-end encryption. " +
 | 
					        } catch (e) {
 | 
				
			||||||
            "Only you and the recipient(s) have the keys to read these messages.",
 | 
					            console.error("Error resetting secret storage", e);
 | 
				
			||||||
        );
 | 
					            if (this._unmounted) return;
 | 
				
			||||||
 | 
					            this.setState({ error: e });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (this._unmounted) return;
 | 
				
			||||||
 | 
					        this._loadBackupStatus();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (this.state.error) {
 | 
					    render() {
 | 
				
			||||||
            return (
 | 
					        const {
 | 
				
			||||||
 | 
					            loading,
 | 
				
			||||||
 | 
					            error,
 | 
				
			||||||
 | 
					            backupKeyStored,
 | 
				
			||||||
 | 
					            backupKeyCached,
 | 
				
			||||||
 | 
					            backupKeyWellFormed,
 | 
				
			||||||
 | 
					            secretStorageKeyInAccount,
 | 
				
			||||||
 | 
					            secretStorageReady,
 | 
				
			||||||
 | 
					            backupInfo,
 | 
				
			||||||
 | 
					            backupSigStatus,
 | 
				
			||||||
 | 
					            sessionsRemaining,
 | 
				
			||||||
 | 
					        } = this.state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let statusDescription;
 | 
				
			||||||
 | 
					        let extraDetailsTableRows;
 | 
				
			||||||
 | 
					        let extraDetails;
 | 
				
			||||||
 | 
					        const actions = [];
 | 
				
			||||||
 | 
					        if (error) {
 | 
				
			||||||
 | 
					            statusDescription = (
 | 
				
			||||||
                <div className="error">
 | 
					                <div className="error">
 | 
				
			||||||
                    {_t("Unable to load key backup status")}
 | 
					                    {_t("Unable to load key backup status")}
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        } else if (this.state.loading) {
 | 
					        } else if (loading) {
 | 
				
			||||||
            return <Spinner />;
 | 
					            statusDescription = <Spinner />;
 | 
				
			||||||
        } else if (this.state.backupInfo) {
 | 
					        } else if (backupInfo) {
 | 
				
			||||||
            let clientBackupStatus;
 | 
					 | 
				
			||||||
            let restoreButtonCaption = _t("Restore from Backup");
 | 
					            let restoreButtonCaption = _t("Restore from Backup");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (MatrixClientPeg.get().getKeyBackupEnabled()) {
 | 
					            if (MatrixClientPeg.get().getKeyBackupEnabled()) {
 | 
				
			||||||
                clientBackupStatus = <div>
 | 
					                statusDescription = <p>✅ {_t("This session is backing up your keys. ")}</p>;
 | 
				
			||||||
                    <p>{encryptedMessageAreEncrypted}</p>
 | 
					 | 
				
			||||||
                    <p>✅ {_t("This session is backing up your keys. ")}</p>
 | 
					 | 
				
			||||||
                </div>;
 | 
					 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                clientBackupStatus = <div>
 | 
					                statusDescription = <>
 | 
				
			||||||
                    <p>{encryptedMessageAreEncrypted}</p>
 | 
					 | 
				
			||||||
                    <p>{_t(
 | 
					                    <p>{_t(
 | 
				
			||||||
                        "This session is <b>not backing up your keys</b>, " +
 | 
					                        "This session is <b>not backing up your keys</b>, " +
 | 
				
			||||||
                        "but you do have an existing backup you can restore from " +
 | 
					                        "but you do have an existing backup you can restore from " +
 | 
				
			||||||
@@ -200,19 +241,11 @@ export default class KeyBackupPanel extends React.PureComponent {
 | 
				
			|||||||
                        "Connect this session to key backup before signing out to avoid " +
 | 
					                        "Connect this session to key backup before signing out to avoid " +
 | 
				
			||||||
                        "losing any keys that may only be on this session.",
 | 
					                        "losing any keys that may only be on this session.",
 | 
				
			||||||
                    )}</p>
 | 
					                    )}</p>
 | 
				
			||||||
                </div>;
 | 
					                </>;
 | 
				
			||||||
                restoreButtonCaption = _t("Connect this session to Key Backup");
 | 
					                restoreButtonCaption = _t("Connect this session to Key Backup");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let keyStatus;
 | 
					 | 
				
			||||||
            if (this.state.backupKeyStored === true) {
 | 
					 | 
				
			||||||
                keyStatus = _t("in secret storage");
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                keyStatus = _t("not stored");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let uploadStatus;
 | 
					            let uploadStatus;
 | 
				
			||||||
            const { sessionsRemaining } = this.state;
 | 
					 | 
				
			||||||
            if (!MatrixClientPeg.get().getKeyBackupEnabled()) {
 | 
					            if (!MatrixClientPeg.get().getKeyBackupEnabled()) {
 | 
				
			||||||
                // No upload status to show when backup disabled.
 | 
					                // No upload status to show when backup disabled.
 | 
				
			||||||
                uploadStatus = "";
 | 
					                uploadStatus = "";
 | 
				
			||||||
@@ -226,17 +259,17 @@ export default class KeyBackupPanel extends React.PureComponent {
 | 
				
			|||||||
                </div>;
 | 
					                </div>;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let backupSigStatuses = this.state.backupSigStatus.sigs.map((sig, i) => {
 | 
					            let backupSigStatuses = backupSigStatus.sigs.map((sig, i) => {
 | 
				
			||||||
                const deviceName = sig.device ? (sig.device.getDisplayName() || sig.device.deviceId) : null;
 | 
					                const deviceName = sig.device ? (sig.device.getDisplayName() || sig.device.deviceId) : null;
 | 
				
			||||||
                const validity = sub =>
 | 
					                const validity = sub =>
 | 
				
			||||||
                    <span className={sig.valid ? 'mx_KeyBackupPanel_sigValid' : 'mx_KeyBackupPanel_sigInvalid'}>
 | 
					                    <span className={sig.valid ? 'mx_SecureBackupPanel_sigValid' : 'mx_SecureBackupPanel_sigInvalid'}>
 | 
				
			||||||
                        {sub}
 | 
					                        {sub}
 | 
				
			||||||
                    </span>;
 | 
					                    </span>;
 | 
				
			||||||
                const verify = sub =>
 | 
					                const verify = sub =>
 | 
				
			||||||
                    <span className={sig.device && sig.deviceTrust.isVerified() ? 'mx_KeyBackupPanel_deviceVerified' : 'mx_KeyBackupPanel_deviceNotVerified'}>
 | 
					                    <span className={sig.device && sig.deviceTrust.isVerified() ? 'mx_SecureBackupPanel_deviceVerified' : 'mx_SecureBackupPanel_deviceNotVerified'}>
 | 
				
			||||||
                        {sub}
 | 
					                        {sub}
 | 
				
			||||||
                    </span>;
 | 
					                    </span>;
 | 
				
			||||||
                const device = sub => <span className="mx_KeyBackupPanel_deviceName">{deviceName}</span>;
 | 
					                const device = sub => <span className="mx_SecureBackupPanel_deviceName">{deviceName}</span>;
 | 
				
			||||||
                const fromThisDevice = (
 | 
					                const fromThisDevice = (
 | 
				
			||||||
                    sig.device &&
 | 
					                    sig.device &&
 | 
				
			||||||
                    sig.device.getFingerprint() === MatrixClientPeg.get().getDeviceEd25519Key()
 | 
					                    sig.device.getFingerprint() === MatrixClientPeg.get().getDeviceEd25519Key()
 | 
				
			||||||
@@ -307,60 +340,123 @@ export default class KeyBackupPanel extends React.PureComponent {
 | 
				
			|||||||
                    {sigStatus}
 | 
					                    {sigStatus}
 | 
				
			||||||
                </div>;
 | 
					                </div>;
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
            if (this.state.backupSigStatus.sigs.length === 0) {
 | 
					            if (backupSigStatus.sigs.length === 0) {
 | 
				
			||||||
                backupSigStatuses = _t("Backup is not signed by any of your sessions");
 | 
					                backupSigStatuses = _t("Backup is not signed by any of your sessions");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let trustedLocally;
 | 
					            let trustedLocally;
 | 
				
			||||||
            if (this.state.backupSigStatus.trusted_locally) {
 | 
					            if (backupSigStatus.trusted_locally) {
 | 
				
			||||||
                trustedLocally = _t("This backup is trusted because it has been restored on this session");
 | 
					                trustedLocally = _t("This backup is trusted because it has been restored on this session");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let deleteBackupButton;
 | 
					            extraDetailsTableRows = <>
 | 
				
			||||||
            if (!isSecureBackupRequired()) {
 | 
					                <tr>
 | 
				
			||||||
                deleteBackupButton = <AccessibleButton kind="danger" onClick={this._deleteBackup}>
 | 
					                    <td>{_t("Backup version:")}</td>
 | 
				
			||||||
                    {_t("Delete Backup")}
 | 
					                    <td>{backupInfo.version}</td>
 | 
				
			||||||
                </AccessibleButton>;
 | 
					                </tr>
 | 
				
			||||||
            }
 | 
					                <tr>
 | 
				
			||||||
 | 
					                    <td>{_t("Algorithm:")}</td>
 | 
				
			||||||
 | 
					                    <td>{backupInfo.algorithm}</td>
 | 
				
			||||||
 | 
					                </tr>
 | 
				
			||||||
 | 
					            </>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const buttonRow = (
 | 
					            extraDetails = <>
 | 
				
			||||||
                <div className="mx_KeyBackupPanel_buttonRow">
 | 
					                {uploadStatus}
 | 
				
			||||||
                    <AccessibleButton kind="primary" onClick={this._restoreBackup}>
 | 
					                <div>{backupSigStatuses}</div>
 | 
				
			||||||
                        {restoreButtonCaption}
 | 
					                <div>{trustedLocally}</div>
 | 
				
			||||||
                    </AccessibleButton>   
 | 
					            </>;
 | 
				
			||||||
                    {deleteBackupButton}
 | 
					
 | 
				
			||||||
                </div>
 | 
					            actions.push(
 | 
				
			||||||
 | 
					                <AccessibleButton kind="primary" onClick={this._restoreBackup}>
 | 
				
			||||||
 | 
					                    {restoreButtonCaption}
 | 
				
			||||||
 | 
					                </AccessibleButton>,
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return <div>
 | 
					            if (!isSecureBackupRequired()) {
 | 
				
			||||||
                <div>{clientBackupStatus}</div>
 | 
					                actions.push(
 | 
				
			||||||
                <details>
 | 
					                    <AccessibleButton kind="danger" onClick={this._deleteBackup}>
 | 
				
			||||||
                    <summary>{_t("Advanced")}</summary>
 | 
					                        {_t("Delete Backup")}
 | 
				
			||||||
                    <div>{_t("Backup version: ")}{this.state.backupInfo.version}</div>
 | 
					                    </AccessibleButton>,
 | 
				
			||||||
                    <div>{_t("Algorithm: ")}{this.state.backupInfo.algorithm}</div>
 | 
					                );
 | 
				
			||||||
                    <div>{_t("Backup key stored: ")}{keyStatus}</div>
 | 
					            }
 | 
				
			||||||
                    {uploadStatus}
 | 
					 | 
				
			||||||
                    <div>{backupSigStatuses}</div>
 | 
					 | 
				
			||||||
                    <div>{trustedLocally}</div>
 | 
					 | 
				
			||||||
                </details>
 | 
					 | 
				
			||||||
                {buttonRow}
 | 
					 | 
				
			||||||
            </div>;
 | 
					 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            return <div>
 | 
					            statusDescription = <>
 | 
				
			||||||
                <div>
 | 
					                <p>{_t(
 | 
				
			||||||
                    <p>{_t(
 | 
					                    "Your keys are <b>not being backed up from this session</b>.", {},
 | 
				
			||||||
                        "Your keys are <b>not being backed up from this session</b>.", {},
 | 
					                    {b: sub => <b>{sub}</b>},
 | 
				
			||||||
                        {b: sub => <b>{sub}</b>},
 | 
					                )}</p>
 | 
				
			||||||
                    )}</p>
 | 
					                <p>{_t("Back up your keys before signing out to avoid losing them.")}</p>
 | 
				
			||||||
                    <p>{encryptedMessageAreEncrypted}</p>
 | 
					            </>;
 | 
				
			||||||
                    <p>{_t("Back up your keys before signing out to avoid losing them.")}</p>
 | 
					            actions.push(
 | 
				
			||||||
                </div>
 | 
					                <AccessibleButton kind="primary" onClick={this._startNewBackup}>
 | 
				
			||||||
                <div className="mx_KeyBackupPanel_buttonRow">
 | 
					                    {_t("Set up")}
 | 
				
			||||||
                    <AccessibleButton kind="primary" onClick={this._startNewBackup}>
 | 
					                </AccessibleButton>,
 | 
				
			||||||
                        {_t("Start using Key Backup")}
 | 
					            );
 | 
				
			||||||
                    </AccessibleButton>
 | 
					        }
 | 
				
			||||||
                </div>
 | 
					
 | 
				
			||||||
 | 
					        if (secretStorageKeyInAccount) {
 | 
				
			||||||
 | 
					            actions.push(
 | 
				
			||||||
 | 
					                <AccessibleButton kind="danger" onClick={this._resetSecretStorage}>
 | 
				
			||||||
 | 
					                    {_t("Reset")}
 | 
				
			||||||
 | 
					                </AccessibleButton>,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let backupKeyWellFormedText = "";
 | 
				
			||||||
 | 
					        if (backupKeyCached) {
 | 
				
			||||||
 | 
					            backupKeyWellFormedText = ", ";
 | 
				
			||||||
 | 
					            if (backupKeyWellFormed) {
 | 
				
			||||||
 | 
					                backupKeyWellFormedText += _t("well formed");
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                backupKeyWellFormedText += _t("unexpected type");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let actionRow;
 | 
				
			||||||
 | 
					        if (actions.length) {
 | 
				
			||||||
 | 
					            actionRow = <div className="mx_SecureBackupPanel_buttonRow">
 | 
				
			||||||
 | 
					                {actions}
 | 
				
			||||||
            </div>;
 | 
					            </div>;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					                <p>{_t(
 | 
				
			||||||
 | 
					                    "Back up your encryption keys with your account data in case you " +
 | 
				
			||||||
 | 
					                    "lose access to your sessions. Your keys will be secured with a " +
 | 
				
			||||||
 | 
					                    "unique Recovery Key.",
 | 
				
			||||||
 | 
					                )}</p>
 | 
				
			||||||
 | 
					                {statusDescription}
 | 
				
			||||||
 | 
					                <details>
 | 
				
			||||||
 | 
					                    <summary>{_t("Advanced")}</summary>
 | 
				
			||||||
 | 
					                    <table className="mx_SecureBackupPanel_statusList"><tbody>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>{_t("Backup key stored:")}</td>
 | 
				
			||||||
 | 
					                            <td>{
 | 
				
			||||||
 | 
					                                backupKeyStored === true ? _t("in secret storage") : _t("not stored")
 | 
				
			||||||
 | 
					                            }</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>{_t("Backup key cached:")}</td>
 | 
				
			||||||
 | 
					                            <td>
 | 
				
			||||||
 | 
					                                {backupKeyCached ? _t("cached locally") : _t("not found locally")}
 | 
				
			||||||
 | 
					                                {backupKeyWellFormedText}
 | 
				
			||||||
 | 
					                            </td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>{_t("Secret storage public key:")}</td>
 | 
				
			||||||
 | 
					                            <td>{secretStorageKeyInAccount ? _t("in account data") : _t("not found")}</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td>{_t("Secret storage:")}</td>
 | 
				
			||||||
 | 
					                            <td>{secretStorageReady ? _t("ready") : _t("not ready")}</td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                        {extraDetailsTableRows}
 | 
				
			||||||
 | 
					                    </tbody></table>
 | 
				
			||||||
 | 
					                    {extraDetails}
 | 
				
			||||||
 | 
					                </details>
 | 
				
			||||||
 | 
					                {actionRow}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -29,6 +29,7 @@ import {sleep} from "../../../../../utils/promise";
 | 
				
			|||||||
import dis from "../../../../../dispatcher/dispatcher";
 | 
					import dis from "../../../../../dispatcher/dispatcher";
 | 
				
			||||||
import {privateShouldBeEncrypted} from "../../../../../createRoom";
 | 
					import {privateShouldBeEncrypted} from "../../../../../createRoom";
 | 
				
			||||||
import {SettingLevel} from "../../../../../settings/SettingLevel";
 | 
					import {SettingLevel} from "../../../../../settings/SettingLevel";
 | 
				
			||||||
 | 
					import SecureBackupPanel from "../../SecureBackupPanel";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class IgnoredUser extends React.Component {
 | 
					export class IgnoredUser extends React.Component {
 | 
				
			||||||
    static propTypes = {
 | 
					    static propTypes = {
 | 
				
			||||||
@@ -288,12 +289,11 @@ export default class SecurityUserSettingsTab extends React.Component {
 | 
				
			|||||||
        const SettingsFlag = sdk.getComponent('views.elements.SettingsFlag');
 | 
					        const SettingsFlag = sdk.getComponent('views.elements.SettingsFlag');
 | 
				
			||||||
        const EventIndexPanel = sdk.getComponent('views.settings.EventIndexPanel');
 | 
					        const EventIndexPanel = sdk.getComponent('views.settings.EventIndexPanel');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const KeyBackupPanel = sdk.getComponent('views.settings.KeyBackupPanel');
 | 
					        const secureBackup = (
 | 
				
			||||||
        const keyBackup = (
 | 
					 | 
				
			||||||
            <div className='mx_SettingsTab_section'>
 | 
					            <div className='mx_SettingsTab_section'>
 | 
				
			||||||
                <span className="mx_SettingsTab_subheading">{_t("Key backup")}</span>
 | 
					                <span className="mx_SettingsTab_subheading">{_t("Secure Backup")}</span>
 | 
				
			||||||
                <div className='mx_SettingsTab_subsectionText'>
 | 
					                <div className='mx_SettingsTab_subsectionText'>
 | 
				
			||||||
                    <KeyBackupPanel />
 | 
					                    <SecureBackupPanel />
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
@@ -352,7 +352,7 @@ export default class SecurityUserSettingsTab extends React.Component {
 | 
				
			|||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div className="mx_SettingsTab_heading">{_t("Encryption")}</div>
 | 
					                <div className="mx_SettingsTab_heading">{_t("Encryption")}</div>
 | 
				
			||||||
                <div className="mx_SettingsTab_section">
 | 
					                <div className="mx_SettingsTab_section">
 | 
				
			||||||
                    {keyBackup}
 | 
					                    {secureBackup}
 | 
				
			||||||
                    {eventIndex}
 | 
					                    {eventIndex}
 | 
				
			||||||
                    {crossSigning}
 | 
					                    {crossSigning}
 | 
				
			||||||
                    {this._renderCurrentDeviceInfo()}
 | 
					                    {this._renderCurrentDeviceInfo()}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -645,14 +645,10 @@
 | 
				
			|||||||
    "Confirm password": "Confirm password",
 | 
					    "Confirm password": "Confirm password",
 | 
				
			||||||
    "Change Password": "Change Password",
 | 
					    "Change Password": "Change Password",
 | 
				
			||||||
    "Your homeserver does not support cross-signing.": "Your homeserver does not support cross-signing.",
 | 
					    "Your homeserver does not support cross-signing.": "Your homeserver does not support cross-signing.",
 | 
				
			||||||
    "Cross-signing and secret storage are ready for use.": "Cross-signing and secret storage are ready for use.",
 | 
					    "Cross-signing is ready for use.": "Cross-signing is ready for use.",
 | 
				
			||||||
    "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.",
 | 
					 | 
				
			||||||
    "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.",
 | 
					    "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.",
 | 
				
			||||||
    "Cross-signing and secret storage are not yet set up.": "Cross-signing and secret storage are not yet set up.",
 | 
					    "Cross-signing is not set up.": "Cross-signing is not set up.",
 | 
				
			||||||
    "Reset cross-signing and secret storage": "Reset cross-signing and secret storage",
 | 
					    "Reset": "Reset",
 | 
				
			||||||
    "Bootstrap cross-signing and secret storage": "Bootstrap cross-signing and secret storage",
 | 
					 | 
				
			||||||
    "well formed": "well formed",
 | 
					 | 
				
			||||||
    "unexpected type": "unexpected type",
 | 
					 | 
				
			||||||
    "Cross-signing public keys:": "Cross-signing public keys:",
 | 
					    "Cross-signing public keys:": "Cross-signing public keys:",
 | 
				
			||||||
    "in memory": "in memory",
 | 
					    "in memory": "in memory",
 | 
				
			||||||
    "not found": "not found",
 | 
					    "not found": "not found",
 | 
				
			||||||
@@ -663,9 +659,6 @@
 | 
				
			|||||||
    "not found locally": "not found locally",
 | 
					    "not found locally": "not found locally",
 | 
				
			||||||
    "Self signing private key:": "Self signing private key:",
 | 
					    "Self signing private key:": "Self signing private key:",
 | 
				
			||||||
    "User signing private key:": "User signing private key:",
 | 
					    "User signing private key:": "User signing private key:",
 | 
				
			||||||
    "Session backup key:": "Session backup key:",
 | 
					 | 
				
			||||||
    "Secret storage public key:": "Secret storage public key:",
 | 
					 | 
				
			||||||
    "in account data": "in account data",
 | 
					 | 
				
			||||||
    "Homeserver feature support:": "Homeserver feature support:",
 | 
					    "Homeserver feature support:": "Homeserver feature support:",
 | 
				
			||||||
    "exists": "exists",
 | 
					    "exists": "exists",
 | 
				
			||||||
    "Your homeserver does not support session management.": "Your homeserver does not support session management.",
 | 
					    "Your homeserver does not support session management.": "Your homeserver does not support session management.",
 | 
				
			||||||
@@ -697,36 +690,6 @@
 | 
				
			|||||||
    "Connecting to integration manager...": "Connecting to integration manager...",
 | 
					    "Connecting to integration manager...": "Connecting to integration manager...",
 | 
				
			||||||
    "Cannot connect to integration manager": "Cannot connect to integration manager",
 | 
					    "Cannot connect to integration manager": "Cannot connect to integration manager",
 | 
				
			||||||
    "The integration manager is offline or it cannot reach your homeserver.": "The integration manager is offline or it cannot reach your homeserver.",
 | 
					    "The integration manager is offline or it cannot reach your homeserver.": "The integration manager is offline or it cannot reach your homeserver.",
 | 
				
			||||||
    "Delete Backup": "Delete Backup",
 | 
					 | 
				
			||||||
    "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.": "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.",
 | 
					 | 
				
			||||||
    "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.",
 | 
					 | 
				
			||||||
    "Unable to load key backup status": "Unable to load key backup status",
 | 
					 | 
				
			||||||
    "Restore from Backup": "Restore from Backup",
 | 
					 | 
				
			||||||
    "This session is backing up your keys. ": "This session is backing up your keys. ",
 | 
					 | 
				
			||||||
    "This session is <b>not backing up your keys</b>, but you do have an existing backup you can restore from and add to going forward.": "This session is <b>not backing up your keys</b>, but you do have an existing backup you can restore from and add to going forward.",
 | 
					 | 
				
			||||||
    "Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.": "Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.",
 | 
					 | 
				
			||||||
    "Connect this session to Key Backup": "Connect this session to Key Backup",
 | 
					 | 
				
			||||||
    "not stored": "not stored",
 | 
					 | 
				
			||||||
    "Backing up %(sessionsRemaining)s keys...": "Backing up %(sessionsRemaining)s keys...",
 | 
					 | 
				
			||||||
    "All keys backed up": "All keys backed up",
 | 
					 | 
				
			||||||
    "Backup has a <validity>valid</validity> signature from this user": "Backup has a <validity>valid</validity> signature from this user",
 | 
					 | 
				
			||||||
    "Backup has a <validity>invalid</validity> signature from this user": "Backup has a <validity>invalid</validity> signature from this user",
 | 
					 | 
				
			||||||
    "Backup has a signature from <verify>unknown</verify> user with ID %(deviceId)s": "Backup has a signature from <verify>unknown</verify> user with ID %(deviceId)s",
 | 
					 | 
				
			||||||
    "Backup has a signature from <verify>unknown</verify> session with ID %(deviceId)s": "Backup has a signature from <verify>unknown</verify> session with ID %(deviceId)s",
 | 
					 | 
				
			||||||
    "Backup has a <validity>valid</validity> signature from this session": "Backup has a <validity>valid</validity> signature from this session",
 | 
					 | 
				
			||||||
    "Backup has an <validity>invalid</validity> signature from this session": "Backup has an <validity>invalid</validity> signature from this session",
 | 
					 | 
				
			||||||
    "Backup has a <validity>valid</validity> signature from <verify>verified</verify> session <device></device>": "Backup has a <validity>valid</validity> signature from <verify>verified</verify> session <device></device>",
 | 
					 | 
				
			||||||
    "Backup has a <validity>valid</validity> signature from <verify>unverified</verify> session <device></device>": "Backup has a <validity>valid</validity> signature from <verify>unverified</verify> session <device></device>",
 | 
					 | 
				
			||||||
    "Backup has an <validity>invalid</validity> signature from <verify>verified</verify> session <device></device>": "Backup has an <validity>invalid</validity> signature from <verify>verified</verify> session <device></device>",
 | 
					 | 
				
			||||||
    "Backup has an <validity>invalid</validity> signature from <verify>unverified</verify> session <device></device>": "Backup has an <validity>invalid</validity> signature from <verify>unverified</verify> session <device></device>",
 | 
					 | 
				
			||||||
    "Backup is not signed by any of your sessions": "Backup is not signed by any of your sessions",
 | 
					 | 
				
			||||||
    "This backup is trusted because it has been restored on this session": "This backup is trusted because it has been restored on this session",
 | 
					 | 
				
			||||||
    "Backup version: ": "Backup version: ",
 | 
					 | 
				
			||||||
    "Algorithm: ": "Algorithm: ",
 | 
					 | 
				
			||||||
    "Backup key stored: ": "Backup key stored: ",
 | 
					 | 
				
			||||||
    "Your keys are <b>not being backed up from this session</b>.": "Your keys are <b>not being backed up from this session</b>.",
 | 
					 | 
				
			||||||
    "Back up your keys before signing out to avoid losing them.": "Back up your keys before signing out to avoid losing them.",
 | 
					 | 
				
			||||||
    "Start using Key Backup": "Start using Key Backup",
 | 
					 | 
				
			||||||
    "Error saving email notification preferences": "Error saving email notification preferences",
 | 
					    "Error saving email notification preferences": "Error saving email notification preferences",
 | 
				
			||||||
    "An error occurred whilst saving your email notification preferences.": "An error occurred whilst saving your email notification preferences.",
 | 
					    "An error occurred whilst saving your email notification preferences.": "An error occurred whilst saving your email notification preferences.",
 | 
				
			||||||
    "Keywords": "Keywords",
 | 
					    "Keywords": "Keywords",
 | 
				
			||||||
@@ -758,6 +721,43 @@
 | 
				
			|||||||
    "Display Name": "Display Name",
 | 
					    "Display Name": "Display Name",
 | 
				
			||||||
    "Profile picture": "Profile picture",
 | 
					    "Profile picture": "Profile picture",
 | 
				
			||||||
    "Save": "Save",
 | 
					    "Save": "Save",
 | 
				
			||||||
 | 
					    "Delete Backup": "Delete Backup",
 | 
				
			||||||
 | 
					    "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.": "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.",
 | 
				
			||||||
 | 
					    "Unable to load key backup status": "Unable to load key backup status",
 | 
				
			||||||
 | 
					    "Restore from Backup": "Restore from Backup",
 | 
				
			||||||
 | 
					    "This session is backing up your keys. ": "This session is backing up your keys. ",
 | 
				
			||||||
 | 
					    "This session is <b>not backing up your keys</b>, but you do have an existing backup you can restore from and add to going forward.": "This session is <b>not backing up your keys</b>, but you do have an existing backup you can restore from and add to going forward.",
 | 
				
			||||||
 | 
					    "Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.": "Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.",
 | 
				
			||||||
 | 
					    "Connect this session to Key Backup": "Connect this session to Key Backup",
 | 
				
			||||||
 | 
					    "Backing up %(sessionsRemaining)s keys...": "Backing up %(sessionsRemaining)s keys...",
 | 
				
			||||||
 | 
					    "All keys backed up": "All keys backed up",
 | 
				
			||||||
 | 
					    "Backup has a <validity>valid</validity> signature from this user": "Backup has a <validity>valid</validity> signature from this user",
 | 
				
			||||||
 | 
					    "Backup has a <validity>invalid</validity> signature from this user": "Backup has a <validity>invalid</validity> signature from this user",
 | 
				
			||||||
 | 
					    "Backup has a signature from <verify>unknown</verify> user with ID %(deviceId)s": "Backup has a signature from <verify>unknown</verify> user with ID %(deviceId)s",
 | 
				
			||||||
 | 
					    "Backup has a signature from <verify>unknown</verify> session with ID %(deviceId)s": "Backup has a signature from <verify>unknown</verify> session with ID %(deviceId)s",
 | 
				
			||||||
 | 
					    "Backup has a <validity>valid</validity> signature from this session": "Backup has a <validity>valid</validity> signature from this session",
 | 
				
			||||||
 | 
					    "Backup has an <validity>invalid</validity> signature from this session": "Backup has an <validity>invalid</validity> signature from this session",
 | 
				
			||||||
 | 
					    "Backup has a <validity>valid</validity> signature from <verify>verified</verify> session <device></device>": "Backup has a <validity>valid</validity> signature from <verify>verified</verify> session <device></device>",
 | 
				
			||||||
 | 
					    "Backup has a <validity>valid</validity> signature from <verify>unverified</verify> session <device></device>": "Backup has a <validity>valid</validity> signature from <verify>unverified</verify> session <device></device>",
 | 
				
			||||||
 | 
					    "Backup has an <validity>invalid</validity> signature from <verify>verified</verify> session <device></device>": "Backup has an <validity>invalid</validity> signature from <verify>verified</verify> session <device></device>",
 | 
				
			||||||
 | 
					    "Backup has an <validity>invalid</validity> signature from <verify>unverified</verify> session <device></device>": "Backup has an <validity>invalid</validity> signature from <verify>unverified</verify> session <device></device>",
 | 
				
			||||||
 | 
					    "Backup is not signed by any of your sessions": "Backup is not signed by any of your sessions",
 | 
				
			||||||
 | 
					    "This backup is trusted because it has been restored on this session": "This backup is trusted because it has been restored on this session",
 | 
				
			||||||
 | 
					    "Backup version:": "Backup version:",
 | 
				
			||||||
 | 
					    "Algorithm:": "Algorithm:",
 | 
				
			||||||
 | 
					    "Your keys are <b>not being backed up from this session</b>.": "Your keys are <b>not being backed up from this session</b>.",
 | 
				
			||||||
 | 
					    "Back up your keys before signing out to avoid losing them.": "Back up your keys before signing out to avoid losing them.",
 | 
				
			||||||
 | 
					    "well formed": "well formed",
 | 
				
			||||||
 | 
					    "unexpected type": "unexpected type",
 | 
				
			||||||
 | 
					    "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.",
 | 
				
			||||||
 | 
					    "Backup key stored:": "Backup key stored:",
 | 
				
			||||||
 | 
					    "not stored": "not stored",
 | 
				
			||||||
 | 
					    "Backup key cached:": "Backup key cached:",
 | 
				
			||||||
 | 
					    "Secret storage public key:": "Secret storage public key:",
 | 
				
			||||||
 | 
					    "in account data": "in account data",
 | 
				
			||||||
 | 
					    "Secret storage:": "Secret storage:",
 | 
				
			||||||
 | 
					    "ready": "ready",
 | 
				
			||||||
 | 
					    "not ready": "not ready",
 | 
				
			||||||
    "Identity Server URL must be HTTPS": "Identity Server URL must be HTTPS",
 | 
					    "Identity Server URL must be HTTPS": "Identity Server URL must be HTTPS",
 | 
				
			||||||
    "Not a valid Identity Server (status code %(code)s)": "Not a valid Identity Server (status code %(code)s)",
 | 
					    "Not a valid Identity Server (status code %(code)s)": "Not a valid Identity Server (status code %(code)s)",
 | 
				
			||||||
    "Could not connect to Identity Server": "Could not connect to Identity Server",
 | 
					    "Could not connect to Identity Server": "Could not connect to Identity Server",
 | 
				
			||||||
@@ -904,7 +904,7 @@
 | 
				
			|||||||
    "Bulk options": "Bulk options",
 | 
					    "Bulk options": "Bulk options",
 | 
				
			||||||
    "Accept all %(invitedRooms)s invites": "Accept all %(invitedRooms)s invites",
 | 
					    "Accept all %(invitedRooms)s invites": "Accept all %(invitedRooms)s invites",
 | 
				
			||||||
    "Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites",
 | 
					    "Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites",
 | 
				
			||||||
    "Key backup": "Key backup",
 | 
					    "Secure Backup": "Secure Backup",
 | 
				
			||||||
    "Message search": "Message search",
 | 
					    "Message search": "Message search",
 | 
				
			||||||
    "Cross-signing": "Cross-signing",
 | 
					    "Cross-signing": "Cross-signing",
 | 
				
			||||||
    "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.",
 | 
					    "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.",
 | 
				
			||||||
@@ -946,7 +946,6 @@
 | 
				
			|||||||
    "Uploaded sound": "Uploaded sound",
 | 
					    "Uploaded sound": "Uploaded sound",
 | 
				
			||||||
    "Sounds": "Sounds",
 | 
					    "Sounds": "Sounds",
 | 
				
			||||||
    "Notification sound": "Notification sound",
 | 
					    "Notification sound": "Notification sound",
 | 
				
			||||||
    "Reset": "Reset",
 | 
					 | 
				
			||||||
    "Set a new custom sound": "Set a new custom sound",
 | 
					    "Set a new custom sound": "Set a new custom sound",
 | 
				
			||||||
    "Browse": "Browse",
 | 
					    "Browse": "Browse",
 | 
				
			||||||
    "Change room avatar": "Change room avatar",
 | 
					    "Change room avatar": "Change room avatar",
 | 
				
			||||||
@@ -1168,6 +1167,7 @@
 | 
				
			|||||||
    "%(roomName)s is not accessible at this time.": "%(roomName)s is not accessible at this time.",
 | 
					    "%(roomName)s is not accessible at this time.": "%(roomName)s is not accessible at this time.",
 | 
				
			||||||
    "Try again later, or ask a room admin to check if you have access.": "Try again later, or ask a room admin to check if you have access.",
 | 
					    "Try again later, or ask a room admin to check if you have access.": "Try again later, or ask a room admin to check if you have access.",
 | 
				
			||||||
    "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please <issueLink>submit a bug report</issueLink>.": "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please <issueLink>submit a bug report</issueLink>.",
 | 
					    "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please <issueLink>submit a bug report</issueLink>.": "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please <issueLink>submit a bug report</issueLink>.",
 | 
				
			||||||
 | 
					    "Start using Key Backup": "Start using Key Backup",
 | 
				
			||||||
    "Never lose encrypted messages": "Never lose encrypted messages",
 | 
					    "Never lose encrypted messages": "Never lose encrypted messages",
 | 
				
			||||||
    "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.",
 | 
					    "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.",
 | 
				
			||||||
    "Securely back up your keys to avoid losing them. <a>Learn more.</a>": "Securely back up your keys to avoid losing them. <a>Learn more.</a>",
 | 
					    "Securely back up your keys to avoid losing them. <a>Learn more.</a>": "Securely back up your keys to avoid losing them. <a>Learn more.</a>",
 | 
				
			||||||
@@ -1747,6 +1747,7 @@
 | 
				
			|||||||
    "Clear cache and resync": "Clear cache and resync",
 | 
					    "Clear cache and resync": "Clear cache and resync",
 | 
				
			||||||
    "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!",
 | 
					    "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!",
 | 
				
			||||||
    "Updating %(brand)s": "Updating %(brand)s",
 | 
					    "Updating %(brand)s": "Updating %(brand)s",
 | 
				
			||||||
 | 
					    "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.",
 | 
				
			||||||
    "I don't want my encrypted messages": "I don't want my encrypted messages",
 | 
					    "I don't want my encrypted messages": "I don't want my encrypted messages",
 | 
				
			||||||
    "Manually export keys": "Manually export keys",
 | 
					    "Manually export keys": "Manually export keys",
 | 
				
			||||||
    "You'll lose access to your encrypted messages": "You'll lose access to your encrypted messages",
 | 
					    "You'll lose access to your encrypted messages": "You'll lose access to your encrypted messages",
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user