diff --git a/src/CrossSigningManager.js b/src/CrossSigningManager.js index 2184eaf347..5c254bbd00 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); }, @@ -159,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 = */ true); + + 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 @@ -215,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/DeviceListener.js b/src/DeviceListener.js index 6a506db496..5abddd7a58 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() { @@ -147,6 +156,19 @@ export default class DeviceListener { } } return; + } else if (await cli.secretStorageKeyNeedsUpgrade()) { + if (this._dismissedThisDeviceToast) { + ToastStore.sharedInstance().dismissToast(THIS_DEVICE_TOAST_KEY); + return; + } + + 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); } 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; diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index d583210c9a..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, }; } @@ -60,6 +61,10 @@ export default class CrossSigningPanel extends React.PureComponent { } }; + _onBootstrapClick = () => { + this._bootstrapSecureSecretStorage(false); + }; + onStatusChanged = () => { this._getUpdatedStatus(); }; @@ -74,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, }); } @@ -124,6 +131,7 @@ export default class CrossSigningPanel extends React.PureComponent { crossSigningPrivateKeysInStorage, secretStorageKeyInAccount, homeserverSupportsCrossSigning, + secretStorageKeyNeedsUpgrade, } = this.state; let errorSection; @@ -175,7 +183,7 @@ export default class CrossSigningPanel extends React.PureComponent { ) { bootstrapButton = (
- + {_t("Bootstrap cross-signing and secret storage")}
@@ -204,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 22a6a2f222..57b39309b0 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -586,6 +586,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",