Support accounts with cross signing but no SSSS

Port https://github.com/matrix-org/matrix-react-sdk/pull/4717 to release
This commit is contained in:
J. Ryan Stinnett 2020-06-29 13:55:06 +01:00
parent 5256a86545
commit 518db90b69
5 changed files with 79 additions and 41 deletions

View file

@ -1870,42 +1870,35 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.accountPasswordTimer = null; this.accountPasswordTimer = null;
}, 60 * 5 * 1000); }, 60 * 5 * 1000);
// Wait for the client to be logged in (but not started) // Create and start the client
// which is enough to ask the server about account data. await Lifecycle.setLoggedIn(credentials);
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;
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
// We're checking `isCryptoAvailable` here instead of `isCryptoEnabled` const cryptoEnabled = cli.isCryptoEnabled();
// because the client hasn't been started yet. if (!cryptoEnabled) {
const cryptoAvailable = isCryptoAvailable();
if (!cryptoAvailable) {
this.onLoggedIn(); this.onLoggedIn();
} }
this.setState({ pendingInitialSync: true }); const promisesList = [this.firstSyncPromise.promise];
await this.firstSyncPromise.promise; if (cryptoEnabled) {
// wait for the client to finish downloading cross-signing keys for us so we
if (!cryptoAvailable) { // know whether or not we have keys set up on this account
this.setState({ pendingInitialSync: false }); promisesList.push(cli.downloadKeys([cli.getUserId()]));
return setLoggedInPromise;
} }
// Test for the master cross-signing key in SSSS as a quick proxy for // Now update the state to say we're waiting for the first sync to complete rather
// whether cross-signing has been set up on the account. // than for the login to finish.
const masterKeyInStorage = !!cli.getAccountData("m.cross_signing.master"); this.setState({ pendingInitialSync: true });
if (masterKeyInStorage) {
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 }); this.setStateForNewView({ view: Views.COMPLETE_SECURITY });
} else if (await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")) { } else if (await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")) {
this.setStateForNewView({ view: Views.E2E_SETUP }); this.setStateForNewView({ view: Views.E2E_SETUP });
@ -1913,8 +1906,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.onLoggedIn(); this.onLoggedIn();
} }
this.setState({ pendingInitialSync: false }); this.setState({ pendingInitialSync: false });
return setLoggedInPromise;
}; };
// complete security / e2e setup has finished // complete security / e2e setup has finished

View file

@ -378,7 +378,7 @@ export default createReactClass({
} }
if (response.access_token) { if (response.access_token) {
const cli = await this.props.onLoggedIn({ await this.props.onLoggedIn({
userId: response.user_id, userId: response.user_id,
deviceId: response.device_id, deviceId: response.device_id,
homeserverUrl: this.state.matrixClient.getHomeserverUrl(), homeserverUrl: this.state.matrixClient.getHomeserverUrl(),
@ -386,7 +386,7 @@ export default createReactClass({
accessToken: response.access_token, accessToken: response.access_token,
}, this.state.formVals.password); }, this.state.formVals.password);
this._setupPushers(cli); this._setupPushers();
// we're still busy until we get unmounted: don't show the registration form again // we're still busy until we get unmounted: don't show the registration form again
newState.busy = true; newState.busy = true;
} else { } else {
@ -397,10 +397,11 @@ export default createReactClass({
this.setState(newState); this.setState(newState);
}, },
_setupPushers: function(matrixClient) { _setupPushers: function() {
if (!this.props.brand) { if (!this.props.brand) {
return Promise.resolve(); return Promise.resolve();
} }
const matrixClient = MatrixClientPeg.get();
return matrixClient.getPushers().then((resp)=>{ return matrixClient.getPushers().then((resp)=>{
const pushers = resp.pushers; const pushers = resp.pushers;
for (let i = 0; i < pushers.length; ++i) { for (let i = 0; i < pushers.length; ++i) {

View file

@ -28,6 +28,14 @@ import {
PHASE_FINISHED, PHASE_FINISHED,
} from '../../../stores/SetupEncryptionStore'; } from '../../../stores/SetupEncryptionStore';
function keyHasPassphrase(keyInfo) {
return (
keyInfo.passphrase &&
keyInfo.passphrase.salt &&
keyInfo.passphrase.iterations
);
}
export default class SetupEncryptionBody extends React.Component { export default class SetupEncryptionBody extends React.Component {
static propTypes = { static propTypes = {
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
@ -108,6 +116,21 @@ export default class SetupEncryptionBody extends React.Component {
member={MatrixClientPeg.get().getUser(this.state.verificationRequest.otherUserId)} member={MatrixClientPeg.get().getUser(this.state.verificationRequest.otherUserId)}
/>; />;
} else if (phase === PHASE_INTRO) { } 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 = <AccessibleButton kind="link" onClick={this._onUsePassphraseClick}>
{recoveryKeyPrompt}
</AccessibleButton>;
}
return ( return (
<div> <div>
<p>{_t( <p>{_t(
@ -131,9 +154,7 @@ export default class SetupEncryptionBody extends React.Component {
</div> </div>
<div className="mx_CompleteSecurity_actionRow"> <div className="mx_CompleteSecurity_actionRow">
<AccessibleButton kind="link" onClick={this._onUsePassphraseClick}> {useRecoveryKeyButton}
{_t("Use Recovery Passphrase or Key")}
</AccessibleButton>
<AccessibleButton kind="danger" onClick={this.onSkipClick}> <AccessibleButton kind="danger" onClick={this.onSkipClick}>
{_t("Skip")} {_t("Skip")}
</AccessibleButton> </AccessibleButton>

View file

@ -2121,10 +2121,11 @@
"You can now close this window or <a>log in</a> to your new account.": "You can now close this window or <a>log in</a> to your new account.", "You can now close this window or <a>log in</a> to your new account.": "You can now close this window or <a>log in</a> to your new account.",
"Registration Successful": "Registration Successful", "Registration Successful": "Registration Successful",
"Create your account": "Create your account", "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.", "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:", "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", "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. 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.", "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 wont have access to encrypted messages.": "Without completing security on this session, it wont have access to encrypted messages.", "Without completing security on this session, it wont have access to encrypted messages.": "Without completing security on this session, it wont have access to encrypted messages.",

View file

@ -36,11 +36,20 @@ export class SetupEncryptionStore extends EventEmitter {
return; return;
} }
this._started = true; this._started = true;
this.phase = PHASE_INTRO; this.phase = PHASE_BUSY;
this.verificationRequest = null; this.verificationRequest = null;
this.backupInfo = 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() { 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() { async usePassPhrase() {
this.phase = PHASE_BUSY; this.phase = PHASE_BUSY;
this.emit("update"); this.emit("update");