From 518db90b6981bb6775fb211461552140f7c93ce3 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 29 Jun 2020 13:55:06 +0100 Subject: [PATCH] Support accounts with cross signing but no SSSS Port https://github.com/matrix-org/matrix-react-sdk/pull/4717 to release --- src/components/structures/MatrixChat.tsx | 53 ++++++++----------- .../structures/auth/Registration.js | 7 +-- .../structures/auth/SetupEncryptionBody.js | 27 ++++++++-- src/i18n/strings/en_EN.json | 3 +- src/stores/SetupEncryptionStore.js | 30 +++++++++-- 5 files changed, 79 insertions(+), 41 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 634e13b103..9cb6ed078c 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1870,42 +1870,35 @@ export default class MatrixChat extends React.PureComponent { this.accountPasswordTimer = null; }, 60 * 5 * 1000); - // Wait for the client to be logged in (but not started) - // which is enough to ask the server about account data. - const loggedIn = new Promise(resolve => { - const actionHandlerRef = dis.register(payload => { - if (payload.action !== "on_logged_in") { - return; - } - dis.unregister(actionHandlerRef); - resolve(); - }); - }); - - // Create and start the client in the background - const setLoggedInPromise = Lifecycle.setLoggedIn(credentials); - await loggedIn; + // Create and start the client + await Lifecycle.setLoggedIn(credentials); const cli = MatrixClientPeg.get(); - // We're checking `isCryptoAvailable` here instead of `isCryptoEnabled` - // because the client hasn't been started yet. - const cryptoAvailable = isCryptoAvailable(); - if (!cryptoAvailable) { + const cryptoEnabled = cli.isCryptoEnabled(); + if (!cryptoEnabled) { this.onLoggedIn(); } - this.setState({ pendingInitialSync: true }); - await this.firstSyncPromise.promise; - - if (!cryptoAvailable) { - this.setState({ pendingInitialSync: false }); - return setLoggedInPromise; + const promisesList = [this.firstSyncPromise.promise]; + if (cryptoEnabled) { + // wait for the client to finish downloading cross-signing keys for us so we + // know whether or not we have keys set up on this account + promisesList.push(cli.downloadKeys([cli.getUserId()])); } - // Test for the master cross-signing key in SSSS as a quick proxy for - // whether cross-signing has been set up on the account. - const masterKeyInStorage = !!cli.getAccountData("m.cross_signing.master"); - if (masterKeyInStorage) { + // Now update the state to say we're waiting for the first sync to complete rather + // than for the login to finish. + this.setState({ pendingInitialSync: true }); + + await Promise.all(promisesList); + + if (!cryptoEnabled) { + this.setState({ pendingInitialSync: false }); + return; + } + + const crossSigningIsSetUp = cli.getStoredCrossSigningForUser(cli.getUserId()); + if (crossSigningIsSetUp) { this.setStateForNewView({ view: Views.COMPLETE_SECURITY }); } else if (await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")) { this.setStateForNewView({ view: Views.E2E_SETUP }); @@ -1913,8 +1906,6 @@ export default class MatrixChat extends React.PureComponent { this.onLoggedIn(); } this.setState({ pendingInitialSync: false }); - - return setLoggedInPromise; }; // complete security / e2e setup has finished diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js index 6349614d72..3b5f5676dc 100644 --- a/src/components/structures/auth/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -378,7 +378,7 @@ export default createReactClass({ } if (response.access_token) { - const cli = await this.props.onLoggedIn({ + await this.props.onLoggedIn({ userId: response.user_id, deviceId: response.device_id, homeserverUrl: this.state.matrixClient.getHomeserverUrl(), @@ -386,7 +386,7 @@ export default createReactClass({ accessToken: response.access_token, }, this.state.formVals.password); - this._setupPushers(cli); + this._setupPushers(); // we're still busy until we get unmounted: don't show the registration form again newState.busy = true; } else { @@ -397,10 +397,11 @@ export default createReactClass({ this.setState(newState); }, - _setupPushers: function(matrixClient) { + _setupPushers: function() { if (!this.props.brand) { return Promise.resolve(); } + const matrixClient = MatrixClientPeg.get(); return matrixClient.getPushers().then((resp)=>{ const pushers = resp.pushers; for (let i = 0; i < pushers.length; ++i) { diff --git a/src/components/structures/auth/SetupEncryptionBody.js b/src/components/structures/auth/SetupEncryptionBody.js index 26534c6e02..f2e702c8cb 100644 --- a/src/components/structures/auth/SetupEncryptionBody.js +++ b/src/components/structures/auth/SetupEncryptionBody.js @@ -28,6 +28,14 @@ import { PHASE_FINISHED, } from '../../../stores/SetupEncryptionStore'; +function keyHasPassphrase(keyInfo) { + return ( + keyInfo.passphrase && + keyInfo.passphrase.salt && + keyInfo.passphrase.iterations + ); +} + export default class SetupEncryptionBody extends React.Component { static propTypes = { onFinished: PropTypes.func.isRequired, @@ -108,6 +116,21 @@ export default class SetupEncryptionBody extends React.Component { member={MatrixClientPeg.get().getUser(this.state.verificationRequest.otherUserId)} />; } else if (phase === PHASE_INTRO) { + const store = SetupEncryptionStore.sharedInstance(); + let recoveryKeyPrompt; + if (store.keyInfo && keyHasPassphrase(store.keyInfo)) { + recoveryKeyPrompt = _t("Use Recovery Key or Passphrase"); + } else if (store.keyInfo) { + recoveryKeyPrompt = _t("Use Recovery Key"); + } + + let useRecoveryKeyButton; + if (recoveryKeyPrompt) { + useRecoveryKeyButton = + {recoveryKeyPrompt} + ; + } + return (

{_t( @@ -131,9 +154,7 @@ export default class SetupEncryptionBody extends React.Component {

- - {_t("Use Recovery Passphrase or Key")} - + {useRecoveryKeyButton} {_t("Skip")} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9a41517664..e927f0202b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2121,10 +2121,11 @@ "You can now close this window or log in to your new account.": "You can now close this window or log in to your new account.", "Registration Successful": "Registration Successful", "Create your account": "Create your account", + "Use Recovery Key or Passphrase": "Use Recovery Key or Passphrase", + "Use Recovery Key": "Use Recovery Key", "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.": "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.", "This requires the latest Riot on your other devices:": "This requires the latest Riot on your other devices:", "or another cross-signing capable Matrix client": "or another cross-signing capable Matrix client", - "Use Recovery Passphrase or Key": "Use Recovery Passphrase or Key", "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.", "Your new session is now verified. Other users will see it as trusted.": "Your new session is now verified. Other users will see it as trusted.", "Without completing security on this session, it won’t have access to encrypted messages.": "Without completing security on this session, it won’t have access to encrypted messages.", diff --git a/src/stores/SetupEncryptionStore.js b/src/stores/SetupEncryptionStore.js index ae1f998b02..b55b5c223c 100644 --- a/src/stores/SetupEncryptionStore.js +++ b/src/stores/SetupEncryptionStore.js @@ -36,11 +36,20 @@ export class SetupEncryptionStore extends EventEmitter { return; } this._started = true; - this.phase = PHASE_INTRO; + this.phase = PHASE_BUSY; this.verificationRequest = null; this.backupInfo = null; - MatrixClientPeg.get().on("crypto.verification.request", this.onVerificationRequest); - MatrixClientPeg.get().on('userTrustStatusChanged', this._onUserTrustStatusChanged); + + // ID of the key that the secrets we want are encrypted with + this.keyId = null; + // Descriptor of the key that the secrets we want are encrypted with + this.keyInfo = null; + + const cli = MatrixClientPeg.get(); + cli.on("crypto.verification.request", this.onVerificationRequest); + cli.on('userTrustStatusChanged', this._onUserTrustStatusChanged); + + this.fetchKeyInfo(); } stop() { @@ -57,6 +66,21 @@ export class SetupEncryptionStore extends EventEmitter { } } + async fetchKeyInfo() { + const keys = await MatrixClientPeg.get().isSecretStored('m.cross_signing.master', false); + if (keys === null || Object.keys(keys).length === 0) { + this.keyId = null; + this.keyInfo = null; + } else { + // If the secret is stored under more than one key, we just pick an arbitrary one + this.keyId = Object.keys(keys)[0]; + this.keyInfo = keys[this.keyId]; + } + + this.phase = PHASE_INTRO; + this.emit("update"); + } + async usePassPhrase() { this.phase = PHASE_BUSY; this.emit("update");