diff --git a/src/CrossSigningManager.js b/src/CrossSigningManager.js index 694b2b0a25..f19be03574 100644 --- a/src/CrossSigningManager.js +++ b/src/CrossSigningManager.js @@ -148,18 +148,21 @@ export const crossSigningCallbacks = { * * @param {Function} [func] An operation to perform once secret storage has been * bootstrapped. Optional. + * @param {bool} [force] Reset secret storage even if it's already set up */ -export async function accessSecretStorage(func = async () => { }) { +export async function accessSecretStorage(func = async () => { }, force = false) { const cli = MatrixClientPeg.get(); secretStorageBeingAccessed = true; - try { - if (!await cli.hasSecretStorageKey()) { + if (!await cli.hasSecretStorageKey() || force) { // This dialog calls bootstrap itself after guiding the user through // passphrase creation. const { finished } = Modal.createTrackedDialogAsync('Create Secret Storage dialog', '', import("./async-components/views/dialogs/secretstorage/CreateSecretStorageDialog"), - null, null, /* priority = */ false, /* static = */ true, + { + force, + }, + null, /* priority = */ false, /* static = */ true, ); const [confirmed] = await finished; if (!confirmed) { diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index f68ee32a15..db2f4c35cc 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -55,10 +55,12 @@ export default class CreateSecretStorageDialog extends React.PureComponent { static propTypes = { hasCancel: PropTypes.bool, accountPassword: PropTypes.string, + force: PropTypes.bool, }; static defaultProps = { hasCancel: true, + force: false, }; constructor(props) { @@ -107,7 +109,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent { MatrixClientPeg.get().isCryptoEnabled() && await MatrixClientPeg.get().isKeyBackupTrusted(backupInfo) ); - const phase = backupInfo ? PHASE_MIGRATE : PHASE_PASSPHRASE; + const { force } = this.props; + const phase = (backupInfo && !force) ? PHASE_MIGRATE : PHASE_PASSPHRASE; this.setState({ phase, @@ -219,12 +222,15 @@ export default class CreateSecretStorageDialog extends React.PureComponent { const cli = MatrixClientPeg.get(); + const { force } = this.props; + try { await cli.bootstrapSecretStorage({ + setupNewSecretStorage: force, authUploadDeviceSigningKeys: this._doBootstrapUIAuth, createSecretStorageKey: async () => this._keyInfo, keyBackupInfo: this.state.backupInfo, - setupNewKeyBackup: !this.state.backupInfo && this.state.useKeyBackup, + setupNewKeyBackup: force || !this.state.backupInfo && this.state.useKeyBackup, }); this.setState({ phase: PHASE_DONE, diff --git a/src/components/views/dialogs/ConfirmDestroyCrossSigningDialog.js b/src/components/views/dialogs/ConfirmDestroyCrossSigningDialog.js new file mode 100644 index 0000000000..9e1980e98d --- /dev/null +++ b/src/components/views/dialogs/ConfirmDestroyCrossSigningDialog.js @@ -0,0 +1,65 @@ +/* +Copyright 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 PropTypes from 'prop-types'; +import {_t} from "../../../languageHandler"; +import * as sdk from "../../../index"; + +export default class ConfirmDestroyCrossSigningDialog extends React.Component { + static propTypes = { + onFinished: PropTypes.func.isRequired, + }; + + _onConfirm = () => { + this.props.onFinished(true); + }; + + _onDecline = () => { + this.props.onFinished(false); + }; + + render() { + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); + + return ( + +
+

+ {_t( + "Deleting cross-signing keys is permanent. " + + "Anyone you have verified with will see security alerts. " + + "You almost certainly don't want to do this, unless " + + "you've lost every device you can cross-sign from.", + )} +

+
+ +
+ ); + } +} diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index f99968c44f..95b9f76858 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -20,6 +20,7 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg'; import { _t } from '../../../languageHandler'; import * as sdk from '../../../index'; import { accessSecretStorage } from '../../../CrossSigningManager'; +import Modal from '../../../Modal'; export default class CrossSigningPanel extends React.PureComponent { constructor(props) { @@ -86,11 +87,12 @@ export default class CrossSigningPanel extends React.PureComponent { * 2. Access existing secret storage by requesting passphrase and accessing * cross-signing keys as needed. * 3. All keys are loaded and there's nothing to do. + * @param {bool} [force] Bootstrap again even if keys already present */ - _bootstrapSecureSecretStorage = async () => { + _bootstrapSecureSecretStorage = async (force=false) => { this.setState({ error: null }); try { - await accessSecretStorage(); + await accessSecretStorage(() => undefined, force); } catch (e) { this.setState({ error: e }); console.error("Error bootstrapping secret storage", e); @@ -99,6 +101,19 @@ export default class CrossSigningPanel extends React.PureComponent { this._getUpdatedStatus(); } + onDestroyStorage = (act) => { + if (!act) return; + console.log("Destroy secret storage:", act); + this._bootstrapSecureSecretStorage(true); + } + + _destroySecureSecretStorage = () => { + const ConfirmDestoryCrossSigningDialog = sdk.getComponent("dialogs.ConfirmDestroyCrossSigningDialog"); + Modal.createDialog(ConfirmDestoryCrossSigningDialog, { + onFinished: this.onDestroyStorage, + }); + } + render() { const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); const { @@ -142,6 +157,12 @@ export default class CrossSigningPanel extends React.PureComponent { {_t("Bootstrap cross-signing and secret storage")} ; + } else { + bootstrapButton =
+ + {_t("Reset cross-signing and secret storage")} + +
; } return ( diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 83c15fc385..f45dba3cb2 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -558,6 +558,7 @@ "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.", "Bootstrap cross-signing and secret storage": "Bootstrap cross-signing and secret storage", + "Reset cross-signing and secret storage": "Reset cross-signing and secret storage", "Cross-signing public keys:": "Cross-signing public keys:", "in memory": "in memory", "not found": "not found", @@ -1435,6 +1436,9 @@ "Changelog": "Changelog", "You cannot delete this message. (%(code)s)": "You cannot delete this message. (%(code)s)", "Removing…": "Removing…", + "Destroy cross-signing keys?": "Destroy cross-signing keys?", + "Deleting cross-signing keys is permanent. Anyone you have verified with will see security alerts. You almost certainly don't want to do this, unless you've lost every device you can cross-sign from.": "Deleting cross-signing keys is permanent. Anyone you have verified with will see security alerts. You almost certainly don't want to do this, unless you've lost every device you can cross-sign from.", + "Clear cross-signing keys": "Clear cross-signing keys", "Confirm Removal": "Confirm Removal", "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.", "Clear all data in this session?": "Clear all data in this session?",