From a71a234622036928d6c69d43b7ebd97df106e422 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 24 Feb 2020 15:04:34 +0000 Subject: [PATCH] Check for cross-signing homeserver support This checks the homeserver to ensure it supports cross-signing (via the versions feature flag) before attempting bootstrapping or offering user verification. Fixes https://github.com/vector-im/riot-web/issues/11863 --- .../keybackup/CreateKeyBackupDialog.js | 26 +++++------- src/components/views/right_panel/UserInfo.js | 35 ++++++++++------ .../views/settings/CrossSigningPanel.js | 42 +++++++++++++------ .../views/settings/KeyBackupPanel.js | 1 - src/hooks/useAsyncMemo.js | 25 +++++++++++ src/i18n/strings/en_EN.json | 5 ++- 6 files changed, 92 insertions(+), 42 deletions(-) create mode 100644 src/hooks/useAsyncMemo.js diff --git a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js index f89fb17448..3a480a2579 100644 --- a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js +++ b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js @@ -53,7 +53,6 @@ function selectText(target) { */ export default class CreateKeyBackupDialog extends React.PureComponent { static propTypes = { - secureSecretStorage: PropTypes.bool, onFinished: PropTypes.func.isRequired, } @@ -65,7 +64,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent { this._setZxcvbnResultTimeout = null; this.state = { - secureSecretStorage: props.secureSecretStorage, + secureSecretStorage: null, phase: PHASE_PASSPHRASE, passPhrase: '', passPhraseConfirm: '', @@ -73,23 +72,20 @@ export default class CreateKeyBackupDialog extends React.PureComponent { downloaded: false, zxcvbnResult: null, }; - - if (this.state.secureSecretStorage === undefined) { - this.state.secureSecretStorage = - SettingsStore.isFeatureEnabled("feature_cross_signing"); - } - - // If we're using secret storage, skip ahead to the backing up step, as - // `accessSecretStorage` will handle passphrases as needed. - if (this.state.secureSecretStorage) { - this.state.phase = PHASE_BACKINGUP; - } } - componentDidMount() { + async componentDidMount() { + const cli = MatrixClientPeg.get(); + const secureSecretStorage = ( + SettingsStore.isFeatureEnabled("feature_cross_signing") && + await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing") + ); + this.setState({ secureSecretStorage }); + // If we're using secret storage, skip ahead to the backing up step, as // `accessSecretStorage` will handle passphrases as needed. - if (this.state.secureSecretStorage) { + if (secureSecretStorage) { + this.setState({ phase: PHASE_BACKINGUP }); this._createBackup(); } } diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index 1174f45640..44770d9ccc 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -42,6 +42,7 @@ import {textualPowerLevel} from '../../../Roles'; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases"; import EncryptionPanel from "./EncryptionPanel"; +import { useAsyncMemo } from '../../../hooks/useAsyncMemo'; const _disambiguateDevices = (devices) => { const names = Object.create(null); @@ -916,6 +917,12 @@ const useIsSynapseAdmin = (cli) => { return isAdmin; }; +const useHomeserverSupportsCrossSigning = (cli) => { + return useAsyncMemo(async () => { + return cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing"); + }, [cli], false); +}; + function useRoomPermissions(cli, room, user) { const [roomPermissions, setRoomPermissions] = useState({ // modifyLevelMax is the max PL we can set this user to, typically min(their PL, our PL) && canSetPL @@ -1315,19 +1322,23 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => { text = _t("Messages in this room are end-to-end encrypted."); } - const userTrust = cli.checkUserTrust(member.userId); - const userVerified = SettingsStore.isFeatureEnabled("feature_cross_signing") ? - userTrust.isCrossSigningVerified() : - userTrust.isVerified(); - const isMe = member.userId === cli.getUserId(); - let verifyButton; - if (isRoomEncrypted && !userVerified && !isMe) { - verifyButton = ( - verifyUser(member)}> - {_t("Verify")} - - ); + const homeserverSupportsCrossSigning = useHomeserverSupportsCrossSigning(cli); + if ( + SettingsStore.isFeatureEnabled("feature_cross_signing") && + homeserverSupportsCrossSigning + ) { + const userTrust = cli.checkUserTrust(member.userId); + const userVerified = userTrust.isCrossSigningVerified(); + const isMe = member.userId === cli.getUserId(); + + if (isRoomEncrypted && !userVerified && !isMe) { + verifyButton = ( + verifyUser(member)}> + {_t("Verify")} + + ); + } } let devicesSection; diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index 6e69227844..aba2d03ad2 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -72,11 +72,14 @@ export default class CrossSigningPanel extends React.PureComponent { const crossSigningPublicKeysOnDevice = crossSigning.getId(); const crossSigningPrivateKeysInStorage = await crossSigning.isStoredInSecretStorage(secretStorage); const secretStorageKeyInAccount = await secretStorage.hasKey(); + const homeserverSupportsCrossSigning = + await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing"); this.setState({ crossSigningPublicKeysOnDevice, crossSigningPrivateKeysInStorage, secretStorageKeyInAccount, + homeserverSupportsCrossSigning, }); } @@ -120,6 +123,7 @@ export default class CrossSigningPanel extends React.PureComponent { crossSigningPublicKeysOnDevice, crossSigningPrivateKeysInStorage, secretStorageKeyInAccount, + homeserverSupportsCrossSigning, } = this.state; let errorSection; @@ -127,13 +131,19 @@ export default class CrossSigningPanel extends React.PureComponent { errorSection =
{error.toString()}
; } - const enabled = ( + // Whether the various keys exist on your account (but not necessarily + // on this device). + const enabledForAccount = ( crossSigningPrivateKeysInStorage && secretStorageKeyInAccount ); let summarisedStatus; - if (enabled && crossSigningPublicKeysOnDevice) { + if (!homeserverSupportsCrossSigning) { + summarisedStatus =

{_t( + "Your homeserver does not support cross-signing.", + )}

; + } else if (enabledForAccount && crossSigningPublicKeysOnDevice) { summarisedStatus =

✅ {_t( "Cross-signing and secret storage are enabled.", )}

; @@ -149,18 +159,18 @@ export default class CrossSigningPanel extends React.PureComponent { } let bootstrapButton; - if (!enabled) { - bootstrapButton =
- - {_t("Bootstrap cross-signing and secret storage")} - -
; - } else { - bootstrapButton =
+ if (enabledForAccount) { + bootstrapButton = ( {_t("Reset cross-signing and secret storage")} -
; + ); + } else if (!enabledForAccount && homeserverSupportsCrossSigning) { + bootstrapButton = ( + + {_t("Bootstrap cross-signing and secret storage")} + + ); } return ( @@ -181,10 +191,16 @@ export default class CrossSigningPanel extends React.PureComponent { {_t("Secret storage public key:")} {secretStorageKeyInAccount ? _t("in account data") : _t("not found")} - + + {_t("Homeserver feature support:")} + {homeserverSupportsCrossSigning ? _t("exists") : _t("not found")} + + {errorSection} - {bootstrapButton} +
+ {bootstrapButton} +
); } diff --git a/src/components/views/settings/KeyBackupPanel.js b/src/components/views/settings/KeyBackupPanel.js index f3fe171e63..73be2bad9f 100644 --- a/src/components/views/settings/KeyBackupPanel.js +++ b/src/components/views/settings/KeyBackupPanel.js @@ -127,7 +127,6 @@ export default class KeyBackupPanel extends React.PureComponent { Modal.createTrackedDialogAsync('Key Backup', 'Key Backup', import('../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog'), { - secureSecretStorage: SettingsStore.isFeatureEnabled("feature_cross_signing"), onFinished: () => { this._loadBackupStatus(); }, diff --git a/src/hooks/useAsyncMemo.js b/src/hooks/useAsyncMemo.js new file mode 100644 index 0000000000..ef7d256b04 --- /dev/null +++ b/src/hooks/useAsyncMemo.js @@ -0,0 +1,25 @@ +/* +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 { useState, useEffect } from 'react'; + +export const useAsyncMemo = (fn, deps, initialValue) => { + const [value, setValue] = useState(initialValue); + useEffect(() => { + fn().then(setValue); + }, deps); // eslint-disable-line react-hooks/exhaustive-deps + return value; +}; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b55b28ce97..1ddea1149f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -562,11 +562,12 @@ "New Password": "New Password", "Confirm password": "Confirm password", "Change Password": "Change Password", + "Your homeserver does not support cross-signing.": "Your homeserver does not support cross-signing.", "Cross-signing and secret storage are enabled.": "Cross-signing and secret storage are enabled.", "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", + "Bootstrap cross-signing and secret storage": "Bootstrap cross-signing and secret storage", "Cross-signing public keys:": "Cross-signing public keys:", "in memory": "in memory", "not found": "not found", @@ -574,6 +575,8 @@ "in secret storage": "in secret storage", "Secret storage public key:": "Secret storage public key:", "in account data": "in account data", + "Homeserver feature support:": "Homeserver feature support:", + "exists": "exists", "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",