From a732c55797599446c131e46ccc937859978ce5c6 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 21 Aug 2020 18:22:05 +0100 Subject: [PATCH 1/4] Add secret storage readiness checks This visits all places that were checking for cross-siging readiness and adapts them to also check for secret storage readiness if needed. Part of https://github.com/vector-im/element-web/issues/13895 --- src/DeviceListener.ts | 6 +++++- .../views/settings/CrossSigningPanel.js | 16 ++++++++++++---- src/i18n/strings/en_EN.json | 3 ++- src/rageshake/submit-rageshake.ts | 1 + 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts index 6b667ae54d..b05f0fcd68 100644 --- a/src/DeviceListener.ts +++ b/src/DeviceListener.ts @@ -207,9 +207,13 @@ export default class DeviceListener { // (we add a listener on sync to do once check after the initial sync is done) if (!cli.isInitialSyncComplete()) return; + // JRS: This will change again in the next PR which moves secret storage + // later in the process. const crossSigningReady = await cli.isCrossSigningReady(); + const secretStorageReady = await cli.isSecretStorageReady(); + const allSystemsReady = crossSigningReady && secretStorageReady; - if (this.dismissedThisDeviceToast || crossSigningReady) { + if (this.dismissedThisDeviceToast || allSystemsReady) { hideSetupEncryptionToast(); } else if (this.shouldShowSetupEncryptionToast()) { // make sure our keys are finished downloading diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index 1c6baee9af..847bcf3da3 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -89,6 +89,7 @@ export default class CrossSigningPanel extends React.PureComponent { const homeserverSupportsCrossSigning = await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing"); const crossSigningReady = await cli.isCrossSigningReady(); + const secretStorageReady = await cli.isSecretStorageReady(); this.setState({ crossSigningPublicKeysOnDevice, @@ -101,6 +102,7 @@ export default class CrossSigningPanel extends React.PureComponent { secretStorageKeyInAccount, homeserverSupportsCrossSigning, crossSigningReady, + secretStorageReady, }); } @@ -151,6 +153,7 @@ export default class CrossSigningPanel extends React.PureComponent { secretStorageKeyInAccount, homeserverSupportsCrossSigning, crossSigningReady, + secretStorageReady, } = this.state; let errorSection; @@ -166,14 +169,19 @@ export default class CrossSigningPanel extends React.PureComponent { summarisedStatus =

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

; - } else if (crossSigningReady) { + } else if (crossSigningReady && secretStorageReady) { summarisedStatus =

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

; + } else if (crossSigningReady && !secretStorageReady) { + summarisedStatus =

✅ {_t( + "Cross-signing is ready for use, but secret storage is " + + "currently not being used to backup your keys.", )}

; } else if (crossSigningPrivateKeysInStorage) { summarisedStatus =

{_t( - "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.", )}

; } else { summarisedStatus =

{_t( diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c12b57c033..dc2669e20f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -645,7 +645,8 @@ "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.", + "Cross-signing and secret storage are ready for use.": "Cross-signing and secret storage are ready for use.", + "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.", "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.", "Reset cross-signing and secret storage": "Reset cross-signing and secret storage", diff --git a/src/rageshake/submit-rageshake.ts b/src/rageshake/submit-rageshake.ts index 74292749b9..448562b68a 100644 --- a/src/rageshake/submit-rageshake.ts +++ b/src/rageshake/submit-rageshake.ts @@ -115,6 +115,7 @@ async function collectBugReport(opts: IOpts = {}, gzipLogs = true) { body.append("cross_signing_supported_by_hs", String(await client.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing"))); body.append("cross_signing_ready", String(await client.isCrossSigningReady())); + body.append("secret_storage_ready", String(await client.isSecretStorageReady())); } } From 4a807f9385fd3e755b46b897761f77cac1107e63 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 27 Aug 2020 13:41:03 +0100 Subject: [PATCH 2/4] Migrate to new, separate APIs for cross-signing and secret storage This migrates to the new JS SDK APIs, which now use separate paths for cross-signing and secret storage setup. There should be no functional change here. Part of https://github.com/vector-im/element-web/issues/13895 --- src/CrossSigningManager.js | 6 ++++-- .../dialogs/secretstorage/CreateSecretStorageDialog.js | 9 +++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/CrossSigningManager.js b/src/CrossSigningManager.js index da09a436e9..a7b494dc26 100644 --- a/src/CrossSigningManager.js +++ b/src/CrossSigningManager.js @@ -239,7 +239,7 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f } } else { const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog"); - await cli.bootstrapSecretStorage({ + await cli.bootstrapCrossSigning({ authUploadDeviceSigningKeys: async (makeRequest) => { const { finished } = Modal.createTrackedDialog( 'Cross-signing keys dialog', '', InteractiveAuthDialog, @@ -254,7 +254,9 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f throw new Error("Cross-signing key upload auth canceled"); } }, - getBackupPassphrase: promptForBackupPassphrase, + }); + await cli.bootstrapSecretStorage({ + getKeyBackupPassphrase: promptForBackupPassphrase, }); } diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 47faa35df4..19c0c79448 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -282,15 +282,20 @@ export default class CreateSecretStorageDialog extends React.PureComponent { try { if (force) { console.log("Forcing secret storage reset"); // log something so we can debug this later - await cli.bootstrapSecretStorage({ + await cli.bootstrapCrossSigning({ authUploadDeviceSigningKeys: this._doBootstrapUIAuth, + setupNewCrossSigning: true, + }); + await cli.bootstrapSecretStorage({ createSecretStorageKey: async () => this._recoveryKey, setupNewKeyBackup: true, setupNewSecretStorage: true, }); } else { - await cli.bootstrapSecretStorage({ + await cli.bootstrapCrossSigning({ authUploadDeviceSigningKeys: this._doBootstrapUIAuth, + }); + await cli.bootstrapSecretStorage({ createSecretStorageKey: async () => this._recoveryKey, keyBackupInfo: this.state.backupInfo, setupNewKeyBackup: !this.state.backupInfo, From 3a98b4b4e948b9993d8f063049c5338e42fefd21 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 27 Aug 2020 13:50:50 +0100 Subject: [PATCH 3/4] Rename reset secret storage prop The bare word `force` has bothered me, so this adds a tiny amount more meaning. --- src/CrossSigningManager.js | 2 +- .../secretstorage/CreateSecretStorageDialog.js | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/CrossSigningManager.js b/src/CrossSigningManager.js index a7b494dc26..b15290b9c3 100644 --- a/src/CrossSigningManager.js +++ b/src/CrossSigningManager.js @@ -218,7 +218,7 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f const { finished } = Modal.createTrackedDialogAsync('Create Secret Storage dialog', '', import("./async-components/views/dialogs/secretstorage/CreateSecretStorageDialog"), { - force: forceReset, + forceReset, }, null, /* priority = */ false, diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 19c0c79448..00216e3765 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -56,12 +56,12 @@ export default class CreateSecretStorageDialog extends React.PureComponent { static propTypes = { hasCancel: PropTypes.bool, accountPassword: PropTypes.string, - force: PropTypes.bool, + forceReset: PropTypes.bool, }; static defaultProps = { hasCancel: true, - force: false, + forceReset: false, }; constructor(props) { @@ -118,8 +118,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent { MatrixClientPeg.get().isCryptoEnabled() && await MatrixClientPeg.get().isKeyBackupTrusted(backupInfo) ); - const { force } = this.props; - const phase = (backupInfo && !force) ? PHASE_MIGRATE : PHASE_CHOOSE_KEY_PASSPHRASE; + const { forceReset } = this.props; + const phase = (backupInfo && !forceReset) ? PHASE_MIGRATE : PHASE_CHOOSE_KEY_PASSPHRASE; this.setState({ phase, @@ -277,11 +277,11 @@ export default class CreateSecretStorageDialog extends React.PureComponent { const cli = MatrixClientPeg.get(); - const { force } = this.props; + const { forceReset } = this.props; try { - if (force) { - console.log("Forcing secret storage reset"); // log something so we can debug this later + if (forceReset) { + console.log("Forcing cross-signing and secret storage reset"); await cli.bootstrapCrossSigning({ authUploadDeviceSigningKeys: this._doBootstrapUIAuth, setupNewCrossSigning: true, From f634c3a71e5ae049c890a814eb9459b5db263b17 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 28 Aug 2020 12:10:17 +0100 Subject: [PATCH 4/4] Add secret storage cache callback to avoid prompts This supplies a cache callback to the JS SDK so that we can be notified if a new storage key is created e.g. by resetting secret storage. This allows it to be supplied automatically in case it's needed in the same user operation, as it is when resetting both secret storage and cross-signing together. --- src/CrossSigningManager.js | 27 +++++++++++-------- .../CreateSecretStorageDialog.js | 8 +++--- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/CrossSigningManager.js b/src/CrossSigningManager.js index b15290b9c3..0353bfc5ae 100644 --- a/src/CrossSigningManager.js +++ b/src/CrossSigningManager.js @@ -69,19 +69,19 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) { if (keyInfoEntries.length > 1) { throw new Error("Multiple storage key requests not implemented"); } - const [name, info] = keyInfoEntries[0]; + const [keyId, keyInfo] = keyInfoEntries[0]; // Check the in-memory cache - if (isCachingAllowed() && secretStorageKeys[name]) { - return [name, secretStorageKeys[name]]; + if (isCachingAllowed() && secretStorageKeys[keyId]) { + return [keyId, secretStorageKeys[keyId]]; } const inputToKey = async ({ passphrase, recoveryKey }) => { if (passphrase) { return deriveKey( passphrase, - info.passphrase.salt, - info.passphrase.iterations, + keyInfo.passphrase.salt, + keyInfo.passphrase.iterations, ); } else { return decodeRecoveryKey(recoveryKey); @@ -93,10 +93,10 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) { AccessSecretStorageDialog, /* props= */ { - keyInfo: info, + keyInfo, checkPrivateKey: async (input) => { const key = await inputToKey(input); - return await MatrixClientPeg.get().checkSecretStorageKey(key, info); + return await MatrixClientPeg.get().checkSecretStorageKey(key, keyInfo); }, }, /* className= */ null, @@ -118,11 +118,15 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) { const key = await inputToKey(input); // Save to cache to avoid future prompts in the current session - if (isCachingAllowed()) { - secretStorageKeys[name] = key; - } + cacheSecretStorageKey(keyId, key); - return [name, key]; + return [keyId, key]; +} + +function cacheSecretStorageKey(keyId, key) { + if (isCachingAllowed()) { + secretStorageKeys[keyId] = key; + } } const onSecretRequested = async function({ @@ -170,6 +174,7 @@ const onSecretRequested = async function({ export const crossSigningCallbacks = { getSecretStorageKey, + cacheSecretStorageKey, onSecretRequested, }; diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 00216e3765..0a1a0b02b3 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -282,15 +282,15 @@ export default class CreateSecretStorageDialog extends React.PureComponent { try { if (forceReset) { console.log("Forcing cross-signing and secret storage reset"); - await cli.bootstrapCrossSigning({ - authUploadDeviceSigningKeys: this._doBootstrapUIAuth, - setupNewCrossSigning: true, - }); await cli.bootstrapSecretStorage({ createSecretStorageKey: async () => this._recoveryKey, setupNewKeyBackup: true, setupNewSecretStorage: true, }); + await cli.bootstrapCrossSigning({ + authUploadDeviceSigningKeys: this._doBootstrapUIAuth, + setupNewCrossSigning: true, + }); } else { await cli.bootstrapCrossSigning({ authUploadDeviceSigningKeys: this._doBootstrapUIAuth,