-
Notifications
You must be signed in to change notification settings - Fork 362
fix(recovery): Delete the known secrets from 4s when disabling recovery #4629
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -95,10 +95,13 @@ use futures_util::StreamExt as _; | |
| use ruma::{ | ||
| api::client::keys::get_keys, | ||
| events::{ | ||
| secret::send::ToDeviceSecretSendEvent, | ||
| secret_storage::default_key::SecretStorageDefaultKeyEvent, | ||
| secret::{request::SecretName, send::ToDeviceSecretSendEvent}, | ||
| secret_storage::{default_key::SecretStorageDefaultKeyEvent, secret::SecretEventContent}, | ||
| GlobalAccountDataEventType, | ||
| }, | ||
| serde::Raw, | ||
| }; | ||
| use serde_json::{json, value::to_raw_value}; | ||
| use tracing::{error, info, instrument, warn}; | ||
|
|
||
| #[cfg(doc)] | ||
|
|
@@ -124,6 +127,15 @@ pub struct Recovery { | |
| } | ||
|
|
||
| impl Recovery { | ||
| /// The list of known secrets that are contained in secret storage once | ||
| /// recover is enabled. | ||
| pub const KNOWN_SECRETS: &[SecretName] = &[ | ||
| SecretName::CrossSigningMasterKey, | ||
| SecretName::CrossSigningUserSigningKey, | ||
| SecretName::CrossSigningSelfSigningKey, | ||
| SecretName::RecoveryKey, | ||
| ]; | ||
|
|
||
| /// Get the current [`RecoveryState`] for this [`Client`]. | ||
| pub fn state(&self) -> RecoveryState { | ||
| self.client.inner.e2ee.recovery_state.get() | ||
|
|
@@ -285,11 +297,36 @@ impl Recovery { | |
| #[instrument(skip_all)] | ||
| pub async fn disable(&self) -> Result<()> { | ||
| self.client.encryption().backups().disable().await?; | ||
|
|
||
| // Why oh why, can't we delete account data events? | ||
| // | ||
| // Alright, let's attempt to "delete" the content of our current default key, | ||
| // for this we first need to check if there is a default key, then | ||
| // deserialize the content and find out the key ID. | ||
| // | ||
| // Then we finally set the event to an empty JSON content. | ||
| if let Ok(Some(default_event)) = | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As an aside, I think that referring to account data as an "event" which can be "deleted" or changed in any way, is a confusing anti-pattern. One of the defining properties of an "event" in most environments is that it's something that happened in the past, so changing it is nonsense. (I know that the spec uses that term, but I'd like that to change (matrix-org/matrix-spec#2030), and we can avoid spreading the mess any further in the meantime.)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, what should we call them?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "Account data". Or possibly, "Account data entries". |
||
| self.client.encryption().secret_storage().fetch_default_key_id().await | ||
| { | ||
| if let Ok(default_event) = default_event.deserialize() { | ||
| let key_id = default_event.key_id; | ||
| let event_type = GlobalAccountDataEventType::SecretStorageKey(key_id); | ||
|
|
||
| self.client | ||
| .account() | ||
| .set_account_data_raw(event_type, Raw::new(&json!({})).expect("").cast()) | ||
| .await?; | ||
| } | ||
| } | ||
|
|
||
| // Now let's "delete" the actual `m.secret.storage.default_key` event. | ||
| self.client.account().set_account_data(SecretStorageDisabledContent {}).await?; | ||
| // Make sure that we don't re-enable backups automatically. | ||
| self.client.account().set_account_data(BackupDisabledContent { disabled: true }).await?; | ||
| // Finally, "delete" all the known secrets we have in the account data. | ||
| self.delete_all_known_secrets().await?; | ||
poljar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| self.update_recovery_state().await?; | ||
| // TODO: Do we want to "delete" the known secrets as well? | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
@@ -527,6 +564,28 @@ impl Recovery { | |
| Ok(()) | ||
| } | ||
|
|
||
| /// Delete all the known secrets we are keeping in secret storage. | ||
| /// | ||
| /// The exact list of secrets is defined in [`Recovery::KNOWN_SECRETS`] and | ||
| /// might change over time. | ||
| /// | ||
| /// Since account data events can't actually be deleted, due to a missing | ||
| /// DELETE API, we're replacing the events with an empty | ||
| /// [`SecretEventContent`]. | ||
| async fn delete_all_known_secrets(&self) -> Result<()> { | ||
| for secret_name in Self::KNOWN_SECRETS { | ||
| let event_type = GlobalAccountDataEventType::from(secret_name.to_owned()); | ||
| let content = SecretEventContent::new(Default::default()); | ||
| let secret_content = Raw::from_json( | ||
| to_raw_value(&content) | ||
| .expect("We should be able to serialize a raw empty secret event content"), | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any chance we could use a plain
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As the recovery module has a specialized error type we can't just use But I don't think it makes much sense, serializing the default event content should never fail.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense to keep it like this, then 👍 |
||
| ); | ||
| self.client.account().set_account_data_raw(event_type, secret_content).await?; | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| /// Run a network request to figure whether backups have been disabled at | ||
| /// the account level. | ||
| async fn are_backups_marked_as_disabled(&self) -> Result<bool> { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.