Merge pull request #4242 from matrix-org/dbkr/symmetric-ssss-migrate
Migration to symmetric SSSS
This commit is contained in:
commit
96ec18a662
6 changed files with 74 additions and 1 deletions
|
@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in a new issue