From 7b6a78bfb86a14196573342928bc41a49320b13f Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Mon, 16 Mar 2020 17:31:06 -0400 Subject: [PATCH 1/9] don't check public key if there is none --- src/CrossSigningManager.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/CrossSigningManager.js b/src/CrossSigningManager.js index 2184eaf347..f428697e74 100644 --- a/src/CrossSigningManager.js +++ b/src/CrossSigningManager.js @@ -96,6 +96,9 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) { { keyInfo: info, checkPrivateKey: async (input) => { + if (!info.pubkey) { + return true; + } const key = await inputToKey(input); return MatrixClientPeg.get().checkSecretStoragePrivateKey(key, info.pubkey); }, From f7dddfc1a7c773e2e66f82735b56e4d9f9dfa8d1 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Mon, 16 Mar 2020 17:31:26 -0400 Subject: [PATCH 2/9] show encryption upgrade when SSSS needs upgrading --- src/DeviceListener.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/DeviceListener.js b/src/DeviceListener.js index 4e7bc8470d..28f0d49c0e 100644 --- a/src/DeviceListener.js +++ b/src/DeviceListener.js @@ -143,6 +143,15 @@ export default class DeviceListener { } } return; + } else if (await cli.secretStorageKeyNeedsUpgrade()) { + // FIXME: do we a different message? + ToastStore.sharedInstance().addOrReplaceToast({ + key: THIS_DEVICE_TOAST_KEY, + title: _t("Encryption upgrade available"), + icon: "verification_warning", + props: {kind: 'upgrade_encryption'}, + component: sdk.getComponent("toasts.SetupEncryptionToast"), + }); } else { ToastStore.sharedInstance().dismissToast(THIS_DEVICE_TOAST_KEY); } From 9bfc19f367acac92a3d998034899f26f324d0b3f Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 19 Mar 2020 20:40:08 +0000 Subject: [PATCH 3/9] Fix bug where cross-signing keys would be unintentionally reset Don't pass the boostrap function directly as a an event handler as that gets used as the force argument. --- src/components/views/settings/CrossSigningPanel.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index d583210c9a..20e2ca01e9 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -60,6 +60,10 @@ export default class CrossSigningPanel extends React.PureComponent { } }; + _onBootstrapClick = () => { + this._bootstrapSecureSecretStorage(false); + }; + onStatusChanged = () => { this._getUpdatedStatus(); }; @@ -175,7 +179,7 @@ export default class CrossSigningPanel extends React.PureComponent { ) { bootstrapButton = (
- + {_t("Bootstrap cross-signing and secret storage")}
From c8691c73ff6089246087a56c3c9dfedb973488a9 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 19 Mar 2020 20:42:16 +0000 Subject: [PATCH 4/9] Allow key backup restore to get the key backup passphrase And pass this in as the new callback to bootstrap to get the old key backup passphrase. --- src/CrossSigningManager.js | 15 +++++++++++++++ .../secretstorage/CreateSecretStorageDialog.js | 2 ++ .../dialogs/keybackup/RestoreKeyBackupDialog.js | 16 ++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/src/CrossSigningManager.js b/src/CrossSigningManager.js index f428697e74..b43af7b8b2 100644 --- a/src/CrossSigningManager.js +++ b/src/CrossSigningManager.js @@ -162,6 +162,20 @@ export const crossSigningCallbacks = { onSecretRequested, }; +export async function promptForBackupPassphrase() { + let key; + + const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog'); + const { finished } = Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, { + showSummary: false, keyCallback: k => key = k, + }, null, /* priority = */ false, /* static = */ false); + + const success = await finished; + if (!success) throw new Error("Key backup prompt cancelled"); + + return key; +} + /** * This helper should be used whenever you need to access secret storage. It * ensures that secret storage (and also cross-signing since they each depend on @@ -218,6 +232,7 @@ export async function accessSecretStorage(func = async () => { }, force = false) throw new Error("Cross-signing key upload auth canceled"); } }, + getBackupPassphrase: promptForBackupPassphrase, }); } diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 49b103ecf7..35529fbc5b 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -23,6 +23,7 @@ import { scorePassword } from '../../../../utils/PasswordScorer'; import FileSaver from 'file-saver'; import { _t } from '../../../../languageHandler'; import Modal from '../../../../Modal'; +import { promptForBackupPassphrase } from '../../../../CrossSigningManager'; const PHASE_LOADING = 0; const PHASE_MIGRATE = 1; @@ -243,6 +244,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { createSecretStorageKey: async () => this._keyInfo, keyBackupInfo: this.state.backupInfo, setupNewKeyBackup: !this.state.backupInfo && this.state.useKeyBackup, + getKeyBackupPassphrase: promptForBackupPassphrase, }); } this.setState({ diff --git a/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js b/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js index 438806bf82..8e4a4e1e60 100644 --- a/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js +++ b/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js @@ -36,6 +36,9 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { // if false, will close the dialog as soon as the restore completes succesfully // default: true showSummary: PropTypes.bool, + // If specified, gather the key from the user but then call the function with the backup + // key rather than actually (necessarily) restoring the backup. + keyCallback: PropTypes.func, }; static defaultProps = { @@ -103,9 +106,18 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { restoreType: RESTORE_TYPE_PASSPHRASE, }); try { + // We do still restore the key backup: we must ensure that the key backup key + // is the right one and restoring it is currently the only way we can do this. const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithPassword( this.state.passPhrase, undefined, undefined, this.state.backupInfo, ); + if (this.props.keyCallback) { + const key = await MatrixClientPeg.get().keyBackupKeyFromPassword( + this.state.passPhrase, this.state.backupInfo, + ); + this.props.keyCallback(key); + } + if (!this.props.showSummary) { this.props.onFinished(true); return; @@ -135,6 +147,10 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithRecoveryKey( this.state.recoveryKey, undefined, undefined, this.state.backupInfo, ); + if (this.props.keyCallback) { + const key = MatrixClientPeg.get().keyBackupKeyFromRecoveryKey(this.state.recoveryKey); + this.props.keyCallback(key); + } if (!this.props.showSummary) { this.props.onFinished(true); return; From 252972e159899a96c79e18e27223dd36edb637f4 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 20 Mar 2020 18:53:31 +0000 Subject: [PATCH 5/9] Dismiss SSSS migration toast once migration done --- src/DeviceListener.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/DeviceListener.js b/src/DeviceListener.js index 03a701d715..1835347838 100644 --- a/src/DeviceListener.js +++ b/src/DeviceListener.js @@ -50,6 +50,7 @@ export default class DeviceListener { MatrixClientPeg.get().on('crypto.devicesUpdated', this._onDevicesUpdated); MatrixClientPeg.get().on('deviceVerificationChanged', this._onDeviceVerificationChanged); MatrixClientPeg.get().on('userTrustStatusChanged', this._onUserTrustStatusChanged); + MatrixClientPeg.get().on('accountData', this._onAccountData); this._recheck(); } @@ -58,6 +59,7 @@ export default class DeviceListener { MatrixClientPeg.get().removeListener('crypto.devicesUpdated', this._onDevicesUpdated); MatrixClientPeg.get().removeListener('deviceVerificationChanged', this._onDeviceVerificationChanged); MatrixClientPeg.get().removeListener('userTrustStatusChanged', this._onUserTrustStatusChanged); + MatrixClientPeg.get().removeListener('accountData', this._onAccountData); } this._dismissed.clear(); } @@ -87,6 +89,13 @@ export default class DeviceListener { this._recheck(); } + _onAccountData = (ev) => { + // User may have migrated SSSS to symmetric, in which case we can dismiss that toast + if (ev.getType().startsWith('m.secret_storage.key.')) { + this._recheck(); + } + } + // The server doesn't tell us when key backup is set up, so we poll // & cache the result async _getKeyBackupInfo() { From ba1d245af92b2235864253f263145fbe7eacaa74 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 20 Mar 2020 19:01:26 +0000 Subject: [PATCH 6/9] Make SSSS migration toast dismissable --- src/DeviceListener.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/DeviceListener.js b/src/DeviceListener.js index 1835347838..a03119f37c 100644 --- a/src/DeviceListener.js +++ b/src/DeviceListener.js @@ -157,6 +157,11 @@ export default class DeviceListener { } return; } else if (await cli.secretStorageKeyNeedsUpgrade()) { + if (this._dismissedThisDeviceToast) { + ToastStore.sharedInstance().dismissToast(THIS_DEVICE_TOAST_KEY); + return; + } + // FIXME: do we a different message? ToastStore.sharedInstance().addOrReplaceToast({ key: THIS_DEVICE_TOAST_KEY, From 629d85c9388208bce0c539afc60beabc92263dfc Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 23 Mar 2020 19:01:30 +0000 Subject: [PATCH 7/9] No, it's fine --- src/DeviceListener.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/DeviceListener.js b/src/DeviceListener.js index a03119f37c..5abddd7a58 100644 --- a/src/DeviceListener.js +++ b/src/DeviceListener.js @@ -162,7 +162,6 @@ export default class DeviceListener { return; } - // FIXME: do we a different message? ToastStore.sharedInstance().addOrReplaceToast({ key: THIS_DEVICE_TOAST_KEY, title: _t("Encryption upgrade available"), From b05e704524029a589db56207af7ccae23be846db Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 23 Mar 2020 19:04:59 +0000 Subject: [PATCH 8/9] Use staic dialog --- src/CrossSigningManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CrossSigningManager.js b/src/CrossSigningManager.js index b43af7b8b2..5c254bbd00 100644 --- a/src/CrossSigningManager.js +++ b/src/CrossSigningManager.js @@ -168,7 +168,7 @@ export async function promptForBackupPassphrase() { const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog'); const { finished } = Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, { showSummary: false, keyCallback: k => key = k, - }, null, /* priority = */ false, /* static = */ false); + }, null, /* priority = */ false, /* static = */ true); const success = await finished; if (!success) throw new Error("Key backup prompt cancelled"); From d6d15bc5c70d41b9e3306f6b56679293715ea852 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 24 Mar 2020 11:17:33 +0000 Subject: [PATCH 9/9] Add secret storage outdated section to advanced --- src/components/views/settings/CrossSigningPanel.js | 8 ++++++++ src/i18n/strings/en_EN.json | 3 +++ 2 files changed, 11 insertions(+) diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index 20e2ca01e9..c2aba6aee7 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -33,6 +33,7 @@ export default class CrossSigningPanel extends React.PureComponent { crossSigningPublicKeysOnDevice: false, crossSigningPrivateKeysInStorage: false, secretStorageKeyInAccount: false, + secretStorageKeyNeedsUpgrade: null, }; } @@ -78,12 +79,14 @@ export default class CrossSigningPanel extends React.PureComponent { const secretStorageKeyInAccount = await secretStorage.hasKey(); const homeserverSupportsCrossSigning = await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing"); + const secretStorageKeyNeedsUpgrade = await cli.secretStorageKeyNeedsUpgrade(); this.setState({ crossSigningPublicKeysOnDevice, crossSigningPrivateKeysInStorage, secretStorageKeyInAccount, homeserverSupportsCrossSigning, + secretStorageKeyNeedsUpgrade, }); } @@ -128,6 +131,7 @@ export default class CrossSigningPanel extends React.PureComponent { crossSigningPrivateKeysInStorage, secretStorageKeyInAccount, homeserverSupportsCrossSigning, + secretStorageKeyNeedsUpgrade, } = this.state; let errorSection; @@ -208,6 +212,10 @@ export default class CrossSigningPanel extends React.PureComponent { {_t("Homeserver feature support:")} {homeserverSupportsCrossSigning ? _t("exists") : _t("not found")} + + {_t("Secret Storage key format:")} + {secretStorageKeyNeedsUpgrade ? _t("outdated") : _t("up to date")} + {errorSection} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c3befa2298..98183e8fb4 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -585,6 +585,9 @@ "in account data": "in account data", "Homeserver feature support:": "Homeserver feature support:", "exists": "exists", + "Secret Storage key format:": "Secret Storage key format:", + "outdated": "outdated", + "up to date": "up to date", "Your homeserver does not support session management.": "Your homeserver does not support session management.", "Unable to load session list": "Unable to load session list", "Authentication": "Authentication",