Merge pull request #4242 from matrix-org/dbkr/symmetric-ssss-migrate

Migration to symmetric SSSS
This commit is contained in:
David Baker 2020-03-24 13:12:24 +00:00 committed by GitHub
commit 96ec18a662
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 74 additions and 1 deletions

View file

@ -96,6 +96,9 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) {
{ {
keyInfo: info, keyInfo: info,
checkPrivateKey: async (input) => { checkPrivateKey: async (input) => {
if (!info.pubkey) {
return true;
}
const key = await inputToKey(input); const key = await inputToKey(input);
return MatrixClientPeg.get().checkSecretStoragePrivateKey(key, info.pubkey); return MatrixClientPeg.get().checkSecretStoragePrivateKey(key, info.pubkey);
}, },
@ -159,6 +162,20 @@ export const crossSigningCallbacks = {
onSecretRequested, 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 * 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 * 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"); throw new Error("Cross-signing key upload auth canceled");
} }
}, },
getBackupPassphrase: promptForBackupPassphrase,
}); });
} }

View file

@ -50,6 +50,7 @@ export default class DeviceListener {
MatrixClientPeg.get().on('crypto.devicesUpdated', this._onDevicesUpdated); MatrixClientPeg.get().on('crypto.devicesUpdated', this._onDevicesUpdated);
MatrixClientPeg.get().on('deviceVerificationChanged', this._onDeviceVerificationChanged); MatrixClientPeg.get().on('deviceVerificationChanged', this._onDeviceVerificationChanged);
MatrixClientPeg.get().on('userTrustStatusChanged', this._onUserTrustStatusChanged); MatrixClientPeg.get().on('userTrustStatusChanged', this._onUserTrustStatusChanged);
MatrixClientPeg.get().on('accountData', this._onAccountData);
this._recheck(); this._recheck();
} }
@ -58,6 +59,7 @@ export default class DeviceListener {
MatrixClientPeg.get().removeListener('crypto.devicesUpdated', this._onDevicesUpdated); MatrixClientPeg.get().removeListener('crypto.devicesUpdated', this._onDevicesUpdated);
MatrixClientPeg.get().removeListener('deviceVerificationChanged', this._onDeviceVerificationChanged); MatrixClientPeg.get().removeListener('deviceVerificationChanged', this._onDeviceVerificationChanged);
MatrixClientPeg.get().removeListener('userTrustStatusChanged', this._onUserTrustStatusChanged); MatrixClientPeg.get().removeListener('userTrustStatusChanged', this._onUserTrustStatusChanged);
MatrixClientPeg.get().removeListener('accountData', this._onAccountData);
} }
this._dismissed.clear(); this._dismissed.clear();
} }
@ -87,6 +89,13 @@ export default class DeviceListener {
this._recheck(); 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 // The server doesn't tell us when key backup is set up, so we poll
// & cache the result // & cache the result
async _getKeyBackupInfo() { async _getKeyBackupInfo() {
@ -147,6 +156,19 @@ export default class DeviceListener {
} }
} }
return; 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 { } else {
ToastStore.sharedInstance().dismissToast(THIS_DEVICE_TOAST_KEY); ToastStore.sharedInstance().dismissToast(THIS_DEVICE_TOAST_KEY);
} }

View file

@ -23,6 +23,7 @@ import { scorePassword } from '../../../../utils/PasswordScorer';
import FileSaver from 'file-saver'; import FileSaver from 'file-saver';
import { _t } from '../../../../languageHandler'; import { _t } from '../../../../languageHandler';
import Modal from '../../../../Modal'; import Modal from '../../../../Modal';
import { promptForBackupPassphrase } from '../../../../CrossSigningManager';
const PHASE_LOADING = 0; const PHASE_LOADING = 0;
const PHASE_MIGRATE = 1; const PHASE_MIGRATE = 1;
@ -243,6 +244,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
createSecretStorageKey: async () => this._keyInfo, createSecretStorageKey: async () => this._keyInfo,
keyBackupInfo: this.state.backupInfo, keyBackupInfo: this.state.backupInfo,
setupNewKeyBackup: !this.state.backupInfo && this.state.useKeyBackup, setupNewKeyBackup: !this.state.backupInfo && this.state.useKeyBackup,
getKeyBackupPassphrase: promptForBackupPassphrase,
}); });
} }
this.setState({ this.setState({

View file

@ -36,6 +36,9 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
// if false, will close the dialog as soon as the restore completes succesfully // if false, will close the dialog as soon as the restore completes succesfully
// default: true // default: true
showSummary: PropTypes.bool, 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 = { static defaultProps = {
@ -103,9 +106,18 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
restoreType: RESTORE_TYPE_PASSPHRASE, restoreType: RESTORE_TYPE_PASSPHRASE,
}); });
try { 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( const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithPassword(
this.state.passPhrase, undefined, undefined, this.state.backupInfo, 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) { if (!this.props.showSummary) {
this.props.onFinished(true); this.props.onFinished(true);
return; return;
@ -135,6 +147,10 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithRecoveryKey( const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithRecoveryKey(
this.state.recoveryKey, undefined, undefined, this.state.backupInfo, 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) { if (!this.props.showSummary) {
this.props.onFinished(true); this.props.onFinished(true);
return; return;

View file

@ -33,6 +33,7 @@ export default class CrossSigningPanel extends React.PureComponent {
crossSigningPublicKeysOnDevice: false, crossSigningPublicKeysOnDevice: false,
crossSigningPrivateKeysInStorage: false, crossSigningPrivateKeysInStorage: false,
secretStorageKeyInAccount: false, secretStorageKeyInAccount: false,
secretStorageKeyNeedsUpgrade: null,
}; };
} }
@ -60,6 +61,10 @@ export default class CrossSigningPanel extends React.PureComponent {
} }
}; };
_onBootstrapClick = () => {
this._bootstrapSecureSecretStorage(false);
};
onStatusChanged = () => { onStatusChanged = () => {
this._getUpdatedStatus(); this._getUpdatedStatus();
}; };
@ -74,12 +79,14 @@ export default class CrossSigningPanel extends React.PureComponent {
const secretStorageKeyInAccount = await secretStorage.hasKey(); const secretStorageKeyInAccount = await secretStorage.hasKey();
const homeserverSupportsCrossSigning = const homeserverSupportsCrossSigning =
await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing"); await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing");
const secretStorageKeyNeedsUpgrade = await cli.secretStorageKeyNeedsUpgrade();
this.setState({ this.setState({
crossSigningPublicKeysOnDevice, crossSigningPublicKeysOnDevice,
crossSigningPrivateKeysInStorage, crossSigningPrivateKeysInStorage,
secretStorageKeyInAccount, secretStorageKeyInAccount,
homeserverSupportsCrossSigning, homeserverSupportsCrossSigning,
secretStorageKeyNeedsUpgrade,
}); });
} }
@ -124,6 +131,7 @@ export default class CrossSigningPanel extends React.PureComponent {
crossSigningPrivateKeysInStorage, crossSigningPrivateKeysInStorage,
secretStorageKeyInAccount, secretStorageKeyInAccount,
homeserverSupportsCrossSigning, homeserverSupportsCrossSigning,
secretStorageKeyNeedsUpgrade,
} = this.state; } = this.state;
let errorSection; let errorSection;
@ -175,7 +183,7 @@ export default class CrossSigningPanel extends React.PureComponent {
) { ) {
bootstrapButton = ( bootstrapButton = (
<div className="mx_CrossSigningPanel_buttonRow"> <div className="mx_CrossSigningPanel_buttonRow">
<AccessibleButton kind="primary" onClick={this._bootstrapSecureSecretStorage}> <AccessibleButton kind="primary" onClick={this._onBootstrapClick}>
{_t("Bootstrap cross-signing and secret storage")} {_t("Bootstrap cross-signing and secret storage")}
</AccessibleButton> </AccessibleButton>
</div> </div>
@ -204,6 +212,10 @@ export default class CrossSigningPanel extends React.PureComponent {
<td>{_t("Homeserver feature support:")}</td> <td>{_t("Homeserver feature support:")}</td>
<td>{homeserverSupportsCrossSigning ? _t("exists") : _t("not found")}</td> <td>{homeserverSupportsCrossSigning ? _t("exists") : _t("not found")}</td>
</tr> </tr>
<tr>
<td>{_t("Secret Storage key format:")}</td>
<td>{secretStorageKeyNeedsUpgrade ? _t("outdated") : _t("up to date")}</td>
</tr>
</tbody></table> </tbody></table>
</details> </details>
{errorSection} {errorSection}

View file

@ -586,6 +586,9 @@
"in account data": "in account data", "in account data": "in account data",
"Homeserver feature support:": "Homeserver feature support:", "Homeserver feature support:": "Homeserver feature support:",
"exists": "exists", "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.", "Your homeserver does not support session management.": "Your homeserver does not support session management.",
"Unable to load session list": "Unable to load session list", "Unable to load session list": "Unable to load session list",
"Authentication": "Authentication", "Authentication": "Authentication",