diff --git a/.typos.toml b/.typos.toml index 1a60955fc52..27a86bffb0c 100644 --- a/.typos.toml +++ b/.typos.toml @@ -28,6 +28,7 @@ Abl = "Abl" Som = "Som" Ba = "Ba" Yur = "Yur" # as found in crates/matrix-sdk-indexeddb/src/crypto_store/migrations/mod.rs +TYE = "TYE" # as found in testing/matrix-sdk-test/src/test_json/keys_query_sets.rs [files] extend-exclude = [ diff --git a/crates/matrix-sdk-crypto/src/identities/manager.rs b/crates/matrix-sdk-crypto/src/identities/manager.rs index 5f95853815f..44c67f94de3 100644 --- a/crates/matrix-sdk-crypto/src/identities/manager.rs +++ b/crates/matrix-sdk-crypto/src/identities/manager.rs @@ -39,7 +39,7 @@ use crate::{ Result as StoreResult, Store, StoreCache, UserKeyQueryResult, }, types::{CrossSigningKey, DeviceKeys, MasterPubkey, SelfSigningPubkey, UserSigningPubkey}, - CryptoStoreError, LocalTrust, SignatureError, + CryptoStoreError, LocalTrust, OwnUserIdentity, SignatureError, UserIdentities, }; enum DeviceChange { @@ -85,6 +85,13 @@ struct KeysQueryRequestDetails { request_ids: HashSet, } +// Helper type to handle key query response +struct KeySetInfo { + user_id: OwnedUserId, + master_key: MasterPubkey, + self_signing: SelfSigningPubkey, +} + impl IdentityManager { const MAX_KEY_QUERY_USERS: usize = 250; @@ -444,6 +451,7 @@ impl IdentityManager { async fn handle_changed_identity( &self, response: &KeysQueryResponse, + maybe_verified_own_identity: Option<&OwnUserIdentity>, master_key: MasterPubkey, self_signing: SelfSigningPubkey, i: UserIdentityData, @@ -461,7 +469,12 @@ impl IdentityManager { } } UserIdentityData::Other(mut identity) => { - let has_changed = identity.update(master_key, self_signing)?; + let has_changed = identity.update( + master_key, + self_signing, + maybe_verified_own_identity.map(|o| o.user_signing_key()), + )?; + if has_changed { Ok(IdentityUpdateResult::Updated(identity.into())) } else { @@ -502,11 +515,13 @@ impl IdentityManager { async fn handle_new_identity( &self, response: &KeysQueryResponse, + maybe_verified_own_identity: Option<&OwnUserIdentity>, master_key: MasterPubkey, self_signing: SelfSigningPubkey, changed_private_identity: &mut Option, ) -> Result { if master_key.user_id() == self.user_id() { + // Own identity let user_signing = self.get_user_signing_key_from_response(response)?; let identity = OwnUserIdentityData::new(master_key, self_signing, user_signing)?; *changed_private_identity = self.check_private_identity(&identity).await; @@ -514,6 +529,13 @@ impl IdentityManager { } else { // First time seen, create the identity. The current MSK will be pinned. let identity = OtherUserIdentityData::new(master_key, self_signing)?; + let is_verified = maybe_verified_own_identity.map_or(false, |own_user_identity| { + own_user_identity.is_identity_signed(&identity).is_ok() + }); + if is_verified { + identity.mark_as_previously_verified(); + } + Ok(identity.into()) } } @@ -616,10 +638,9 @@ impl IdentityManager { /// * `changed_identity` - Output parameter: Unchanged if the identity is /// that of another user. If it is our own, set to `None` or `Some` /// depending on whether our stored private identity needs updating. - /// * `user_id` - The user id of the user whose identity is being processed. - /// * `master_key` - The public master cross-signing key for this user from - /// the `/keys/query` response. - /// * `self_signing` - The public self-signing key from the `/keys/query` + /// * `maybe_verified_own_identity` - Own verified identity if any to check + /// verification status of updated identity. + /// * `key_set_info` - The identity info as returned by the `/keys/query` /// response. #[instrument(skip_all, fields(user_id))] async fn update_or_create_identity( @@ -627,17 +648,18 @@ impl IdentityManager { response: &KeysQueryResponse, changes: &mut IdentityChanges, changed_private_identity: &mut Option, - user_id: &UserId, - master_key: MasterPubkey, - self_signing: SelfSigningPubkey, + maybe_verified_own_identity: Option<&OwnUserIdentity>, + key_set_info: KeySetInfo, ) -> StoreResult<()> { + let KeySetInfo { user_id, master_key, self_signing } = key_set_info; if master_key.user_id() != user_id || self_signing.user_id() != user_id { warn!(?user_id, "User ID mismatch in one of the cross signing keys"); - } else if let Some(i) = self.store.get_user_identity(user_id).await? { + } else if let Some(i) = self.store.get_user_identity(&user_id).await? { // an identity we knew about before, which is being updated match self .handle_changed_identity( response, + maybe_verified_own_identity, master_key, self_signing, i, @@ -660,7 +682,13 @@ impl IdentityManager { } else { // an identity we did not know about before match self - .handle_new_identity(response, master_key, self_signing, changed_private_identity) + .handle_new_identity( + response, + maybe_verified_own_identity, + master_key, + self_signing, + changed_private_identity, + ) .await { Ok(identity) => { @@ -698,6 +726,15 @@ impl IdentityManager { let mut changes = IdentityChanges::default(); let mut changed_identity = None; + // We want to check if the updated/new other identities are trusted by us or + // not. This is based on the current verified state of the own identity. + let maybe_own_verified_identity = self + .store + .get_identity(self.user_id()) + .await? + .and_then(UserIdentities::own) + .filter(|own| own.is_verified()); + for (user_id, master_key) in &response.master_keys { // Get the master and self-signing key for each identity; those are required for // every user identity type. If we don't have those we skip over. @@ -707,13 +744,14 @@ impl IdentityManager { continue; }; + let key_set_info = KeySetInfo { user_id: user_id.clone(), master_key, self_signing }; + self.update_or_create_identity( response, &mut changes, &mut changed_identity, - user_id, - master_key, - self_signing, + maybe_own_verified_identity.as_ref(), + key_set_info, ) .await?; } @@ -1353,6 +1391,7 @@ pub(crate) mod tests { use crate::{ identities::manager::testing::{other_key_query_cross_signed, own_key_query}, olm::PrivateCrossSigningIdentity, + CrossSigningKeyExport, OlmMachine, }; fn key_query_with_failures() -> KeysQueryResponse { @@ -2023,4 +2062,269 @@ pub(crate) mod tests { assert!(!other_identity.has_pin_violation()); } + + // Set up a machine do initial own key query and import cross-signing secret to + // make the current session verified. + async fn common_verified_identity_changes_machine_setup() -> OlmMachine { + use test_json::keys_query_sets::PreviouslyVerifiedTestData as DataSet; + + let machine = OlmMachine::new(DataSet::own_id(), device_id!("LOCAL")).await; + + let keys_query = DataSet::own_keys_query_response_1(); + let txn_id = TransactionId::new(); + machine.mark_request_as_sent(&txn_id, &keys_query).await.unwrap(); + + machine + .import_cross_signing_keys(CrossSigningKeyExport { + master_key: DataSet::MASTER_KEY_PRIVATE_EXPORT.to_owned().into(), + self_signing_key: DataSet::SELF_SIGNING_KEY_PRIVATE_EXPORT.to_owned().into(), + user_signing_key: DataSet::USER_SIGNING_KEY_PRIVATE_EXPORT.to_owned().into(), + }) + .await + .unwrap(); + machine + } + #[async_test] + async fn test_manager_verified_latch_setup_on_new_identities() { + use test_json::keys_query_sets::PreviouslyVerifiedTestData as DataSet; + + let machine = common_verified_identity_changes_machine_setup().await; + + // ###### + // First test: Assert that the latch is properly set on new identities + // ###### + let keys_query = DataSet::bob_keys_query_response_signed(); + let txn_id = TransactionId::new(); + machine.mark_request_as_sent(&txn_id, &keys_query).await.unwrap(); + + let own_identity = + machine.get_identity(DataSet::own_id(), None).await.unwrap().unwrap().own().unwrap(); + // For sanity check that own identity is trusted + assert!(own_identity.is_verified()); + + let bob_identity = + machine.get_identity(DataSet::bob_id(), None).await.unwrap().unwrap().other().unwrap(); + // The verified latch should be true + assert!(bob_identity.was_previously_verified()); + // And bob is verified + assert!(bob_identity.is_verified()); + + // ###### + // Second test: Assert that the local latch stays on if the identity is rotated + // ###### + let keys_query = DataSet::bob_keys_query_response_rotated(); + let txn_id = TransactionId::new(); + machine.mark_request_as_sent(&txn_id, &keys_query).await.unwrap(); + + let bob_identity = + machine.get_identity(DataSet::bob_id(), None).await.unwrap().unwrap().other().unwrap(); + // Bob is not verified anymore + assert!(!bob_identity.is_verified()); + // The verified latch should still be true + assert!(bob_identity.was_previously_verified()); + // Bob device_2 is self-signed even if there is this verification latch + // violation + let bob_device = machine + .get_device(DataSet::bob_id(), DataSet::bob_device_2_id(), None) + .await + .unwrap() + .unwrap(); + assert!(bob_identity.is_device_signed(&bob_device).is_ok()); + // there is also a pin violation + assert!(bob_identity.has_pin_violation()); + // Fixing the pin violation won't fix the verification latch violation + bob_identity.pin_current_master_key().await.unwrap(); + assert!(!bob_identity.has_pin_violation()); + let has_latch_violation = + bob_identity.was_previously_verified() && !bob_identity.is_verified(); + assert!(has_latch_violation); + } + + #[async_test] + async fn test_manager_verified_identity_changes_setup_on_updated_identities() { + use test_json::keys_query_sets::PreviouslyVerifiedTestData as DataSet; + + let machine = common_verified_identity_changes_machine_setup().await; + + // ###### + // Get the Carol identity for the first time + // ###### + let keys_query = DataSet::carol_keys_query_response_unsigned(); + let txn_id = TransactionId::new(); + machine.mark_request_as_sent(&txn_id, &keys_query).await.unwrap(); + + let carol_identity = machine + .get_identity(DataSet::carol_id(), None) + .await + .unwrap() + .unwrap() + .other() + .unwrap(); + // The identity is not verified + assert!(!carol_identity.is_verified()); + // The verified latch is off + assert!(!carol_identity.was_previously_verified()); + + // Carol is verified, likely from another session. Ensure the latch is updated + // when the key query response is processed + let keys_query = DataSet::carol_keys_query_response_signed(); + let txn_id = TransactionId::new(); + machine.mark_request_as_sent(&txn_id, &keys_query).await.unwrap(); + + let carol_identity = machine + .get_identity(DataSet::carol_id(), None) + .await + .unwrap() + .unwrap() + .other() + .unwrap(); + assert!(carol_identity.is_verified()); + // This should have updated the latch + assert!(carol_identity.was_previously_verified()); + // It is the same identity, it's just signed now so no pin violation + assert!(!carol_identity.has_pin_violation()); + } + + // Set up a machine do initial own key query. + // The cross signing secrets are not yet uploaded. + // Then query keys for carol and bob (both signed by own identity) + async fn common_verified_identity_changes_own_trust_change_machine_setup() -> OlmMachine { + use test_json::keys_query_sets::PreviouslyVerifiedTestData as DataSet; + + // Start on a non-verified session + let machine = OlmMachine::new(DataSet::own_id(), device_id!("LOCAL")).await; + + let keys_query = DataSet::own_keys_query_response_1(); + let txn_id = TransactionId::new(); + machine.mark_request_as_sent(&txn_id, &keys_query).await.unwrap(); + + // For sanity check that own identity is not trusted + let own_identity = + machine.get_identity(DataSet::own_id(), None).await.unwrap().unwrap().own().unwrap(); + assert!(!own_identity.is_verified()); + + let keys_query = DataSet::own_keys_query_response_1(); + let txn_id = TransactionId::new(); + machine.mark_request_as_sent(&txn_id, &keys_query).await.unwrap(); + + // Get Bob and Carol already signed + let keys_query = DataSet::bob_keys_query_response_signed(); + let txn_id = TransactionId::new(); + machine.mark_request_as_sent(&txn_id, &keys_query).await.unwrap(); + + let keys_query = DataSet::carol_keys_query_response_signed(); + let txn_id = TransactionId::new(); + machine.mark_request_as_sent(&txn_id, &keys_query).await.unwrap(); + + machine.update_tracked_users(vec![DataSet::bob_id(), DataSet::carol_id()]).await.unwrap(); + + machine + } + + #[async_test] + async fn test_manager_verified_identity_changes_setup_on_own_identity_trust_change() { + use test_json::keys_query_sets::PreviouslyVerifiedTestData as DataSet; + let machine = common_verified_identity_changes_own_trust_change_machine_setup().await; + + let own_identity = + machine.get_identity(DataSet::own_id(), None).await.unwrap().unwrap().own().unwrap(); + + let bob_identity = + machine.get_identity(DataSet::bob_id(), None).await.unwrap().unwrap().other().unwrap(); + // Carol is verified by our identity but our own identity is not yet trusted + assert!(own_identity.is_identity_signed(&bob_identity).is_ok()); + assert!(!bob_identity.was_previously_verified()); + + let carol_identity = machine + .get_identity(DataSet::carol_id(), None) + .await + .unwrap() + .unwrap() + .other() + .unwrap(); + // Carol is verified by our identity but our own identity is not yet trusted + assert!(own_identity.is_identity_signed(&carol_identity).is_ok()); + assert!(!carol_identity.was_previously_verified()); + + // Marking our own identity as trusted should update the existing identities + let _ = own_identity.verify().await; + + let own_identity = + machine.get_identity(DataSet::own_id(), None).await.unwrap().unwrap().own().unwrap(); + assert!(own_identity.is_verified()); + + let carol_identity = machine + .get_identity(DataSet::carol_id(), None) + .await + .unwrap() + .unwrap() + .other() + .unwrap(); + assert!(carol_identity.is_verified()); + // The latch should be set now + assert!(carol_identity.was_previously_verified()); + + let bob_identity = + machine.get_identity(DataSet::bob_id(), None).await.unwrap().unwrap().other().unwrap(); + assert!(bob_identity.is_verified()); + // The latch should be set now + assert!(bob_identity.was_previously_verified()); + } + + #[async_test] + async fn test_manager_verified_identity_change_setup_on_import_secrets() { + use test_json::keys_query_sets::PreviouslyVerifiedTestData as DataSet; + let machine = common_verified_identity_changes_own_trust_change_machine_setup().await; + + let own_identity = + machine.get_identity(DataSet::own_id(), None).await.unwrap().unwrap().own().unwrap(); + + let bob_identity = + machine.get_identity(DataSet::bob_id(), None).await.unwrap().unwrap().other().unwrap(); + // Carol is verified by our identity but our own identity is not yet trusted + assert!(own_identity.is_identity_signed(&bob_identity).is_ok()); + assert!(!bob_identity.was_previously_verified()); + + let carol_identity = machine + .get_identity(DataSet::carol_id(), None) + .await + .unwrap() + .unwrap() + .other() + .unwrap(); + // Carol is verified by our identity but our own identity is not yet trusted + assert!(own_identity.is_identity_signed(&carol_identity).is_ok()); + assert!(!carol_identity.was_previously_verified()); + + // Marking our own identity as trusted should update the existing identities + machine + .import_cross_signing_keys(CrossSigningKeyExport { + master_key: DataSet::MASTER_KEY_PRIVATE_EXPORT.to_owned().into(), + self_signing_key: DataSet::SELF_SIGNING_KEY_PRIVATE_EXPORT.to_owned().into(), + user_signing_key: DataSet::USER_SIGNING_KEY_PRIVATE_EXPORT.to_owned().into(), + }) + .await + .unwrap(); + + let own_identity = + machine.get_identity(DataSet::own_id(), None).await.unwrap().unwrap().own().unwrap(); + assert!(own_identity.is_verified()); + + let carol_identity = machine + .get_identity(DataSet::carol_id(), None) + .await + .unwrap() + .unwrap() + .other() + .unwrap(); + assert!(carol_identity.is_verified()); + // The latch should be set now + assert!(carol_identity.was_previously_verified()); + + let bob_identity = + machine.get_identity(DataSet::bob_id(), None).await.unwrap().unwrap().other().unwrap(); + assert!(bob_identity.is_verified()); + // The latch should be set now + assert!(bob_identity.was_previously_verified()); + } } diff --git a/crates/matrix-sdk-crypto/src/identities/user.rs b/crates/matrix-sdk-crypto/src/identities/user.rs index 2cfa0c0dc06..f325b2602d2 100644 --- a/crates/matrix-sdk-crypto/src/identities/user.rs +++ b/crates/matrix-sdk-crypto/src/identities/user.rs @@ -332,6 +332,48 @@ impl UserIdentity { // higher priority than pinning. self.inner.has_pin_violation() } + + /// Remove the requirement for this identity to be verified. + pub async fn withdraw_verification(&self) -> Result<(), CryptoStoreError> { + self.inner.withdraw_verification(); + let to_save = UserIdentityData::Other(self.inner.clone()); + let changes = Changes { + identities: IdentityChanges { changed: vec![to_save], ..Default::default() }, + ..Default::default() + }; + self.verification_machine.store.inner().save_changes(changes).await?; + Ok(()) + } + + // Test helper + #[cfg(test)] + pub async fn mark_as_previously_verified(&self) -> Result<(), CryptoStoreError> { + self.inner.mark_as_previously_verified(); + let to_save = UserIdentityData::Other(self.inner.clone()); + let changes = Changes { + identities: IdentityChanges { changed: vec![to_save], ..Default::default() }, + ..Default::default() + }; + self.verification_machine.store.inner().save_changes(changes).await?; + Ok(()) + } + + /// Was this identity verified since initial observation and is not anymore? + /// + /// Such a violation should be reported to the local user by the + /// application, and resolved by + /// + /// - Verifying the new identity with [`UserIdentity::request_verification`] + /// - Or by withdrawing the verification requirement + /// [`UserIdentity::withdraw_verification`]. + pub fn has_verification_violation(&self) -> bool { + if !self.inner.was_previously_verified() { + // If that identity has never been verified it cannot be in violation. + return false; + }; + + !self.is_verified() + } } /// Enum over the different user identity types we can have. @@ -415,7 +457,8 @@ impl UserIdentityData { /// signatures can be checked with this identity. /// /// This struct also contains the currently pinned user identity (public master -/// key) for that user. +/// key) for that user and a local flag that serves as a latch to remember if an +/// identity was verified once. /// /// The first time a cryptographic user identity is seen for a given user, it /// will be associated with that user ("pinned"). Future interactions @@ -424,6 +467,12 @@ impl UserIdentityData { /// /// The user can explicitly pin the new identity to allow for legitimate /// identity changes (for example, in case of key material or device loss). +/// +/// As soon as the cryptographic identity is verified (i.e. signed by our own +/// trusted identity), a flag is set to remember it (`previously_verified`). +/// Future interactions will expect this user to stay verified, in case of +/// violation the user should be notified with a blocking warning when sending a +/// message. #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(try_from = "OtherUserIdentityDataSerializer", into = "OtherUserIdentityDataSerializer")] pub struct OtherUserIdentityData { @@ -431,6 +480,11 @@ pub struct OtherUserIdentityData { pub(crate) master_key: Arc, self_signing_key: Arc, pinned_master_key: Arc>, + /// This tracks whether this olm machine has already seen this user as + /// verified. To use it in the future to detect cases where the user has + /// become unverified for any reason. This can be reset using + /// [`OtherUserIdentityData::withdraw_verification()`]. + previously_verified: Arc, } /// Intermediate struct to help serialize OtherUserIdentityData and support @@ -460,6 +514,15 @@ struct OtherUserIdentityDataSerializerV1 { pinned_master_key: MasterPubkey, } +#[derive(Debug, Deserialize, Serialize)] +struct OtherUserIdentityDataSerializerV2 { + user_id: OwnedUserId, + master_key: MasterPubkey, + self_signing_key: SelfSigningPubkey, + pinned_master_key: MasterPubkey, + previously_verified: bool, +} + impl TryFrom for OtherUserIdentityData { type Error = serde_json::Error; fn try_from( @@ -475,6 +538,7 @@ impl TryFrom for OtherUserIdentityData { self_signing_key: Arc::new(v0.self_signing_key), // We migrate by pinning the current master key pinned_master_key: Arc::new(RwLock::new(v0.master_key)), + previously_verified: Arc::new(false.into()), }) } Some(v) if v == "1" => { @@ -484,6 +548,19 @@ impl TryFrom for OtherUserIdentityData { master_key: Arc::new(v1.master_key.clone()), self_signing_key: Arc::new(v1.self_signing_key), pinned_master_key: Arc::new(RwLock::new(v1.pinned_master_key)), + // Put it to false. There will be a migration to mark all users as dirty, so we + // will receive an update for the identity that will correctly set up the value. + previously_verified: Arc::new(false.into()), + }) + } + Some(v) if v == "2" => { + let v2: OtherUserIdentityDataSerializerV2 = serde_json::from_value(value.other)?; + Ok(OtherUserIdentityData { + user_id: v2.user_id, + master_key: Arc::new(v2.master_key.clone()), + self_signing_key: Arc::new(v2.self_signing_key), + pinned_master_key: Arc::new(RwLock::new(v2.pinned_master_key)), + previously_verified: Arc::new(v2.previously_verified.into()), }) } _ => Err(serde::de::Error::custom(format!("Unsupported Version {:?}", value.version))), @@ -493,15 +570,16 @@ impl TryFrom for OtherUserIdentityData { impl From for OtherUserIdentityDataSerializer { fn from(value: OtherUserIdentityData) -> Self { - let v1 = OtherUserIdentityDataSerializerV1 { + let v2 = OtherUserIdentityDataSerializerV2 { user_id: value.user_id.clone(), master_key: value.master_key().to_owned(), self_signing_key: value.self_signing_key().to_owned(), pinned_master_key: value.pinned_master_key.read().unwrap().clone(), + previously_verified: value.previously_verified.load(Ordering::SeqCst), }; OtherUserIdentityDataSerializer { - version: Some("1".to_owned()), - other: serde_json::to_value(v1).unwrap(), + version: Some("2".to_owned()), + other: serde_json::to_value(v2).unwrap(), } } } @@ -549,6 +627,7 @@ impl OtherUserIdentityData { master_key: master_key.clone().into(), self_signing_key: self_signing_key.into(), pinned_master_key: RwLock::new(master_key).into(), + previously_verified: Arc::new(false.into()), }) } @@ -563,6 +642,7 @@ impl OtherUserIdentityData { master_key: Arc::new(master_key.clone()), self_signing_key, pinned_master_key: Arc::new(RwLock::new(master_key.clone())), + previously_verified: Arc::new(false.into()), } } @@ -587,6 +667,29 @@ impl OtherUserIdentityData { *m = self.master_key.as_ref().clone() } + /// Remember that this identity used to be verified at some point. + pub(crate) fn mark_as_previously_verified(&self) { + self.previously_verified.store(true, Ordering::SeqCst) + } + + /// True if we verified this identity (with any own identity, at any + /// point). + /// + /// To pass this latch back to false, one must call + /// [`OtherUserIdentityData::withdraw_verification()`]. + pub fn was_previously_verified(&self) -> bool { + self.previously_verified.load(Ordering::SeqCst) + } + + /// Remove the requirement for this identity to be verified. + /// + /// If an identity was previously verified and is not anymore it will be + /// reported to the user. In order to remove this notice users have to + /// verify again or to withdraw the verification requirement. + pub fn withdraw_verification(&self) { + self.previously_verified.store(false, Ordering::SeqCst) + } + /// Returns true if the identity has changed since we last pinned it. /// /// Key pinning acts as a trust on first use mechanism, the first time an @@ -609,6 +712,9 @@ impl OtherUserIdentityData { /// /// * `self_signing_key` - The new self signing key of user identity. /// + /// * `maybe_verified_own_user_signing_key` - Our own user_signing_key if it + /// is verified to check the identity trust status after update. + /// /// Returns a `SignatureError` if we failed to update the identity. /// Otherwise, returns `true` if there was a change to the identity and /// `false` if the identity is unchanged. @@ -616,6 +722,7 @@ impl OtherUserIdentityData { &mut self, master_key: MasterPubkey, self_signing_key: SelfSigningPubkey, + maybe_verified_own_user_signing_key: Option<&UserSigningPubkey>, ) -> Result { master_key.verify_subkey(&self_signing_key)?; @@ -625,11 +732,21 @@ impl OtherUserIdentityData { // (see `has_pin_violation()`). let pinned_master_key = self.pinned_master_key.read().unwrap().clone(); + // Check if the new master_key is signed by our own **verified** + // user_signing_key. If the identity was verified we remember it. + let updated_is_verified = maybe_verified_own_user_signing_key + .map_or(false, |own_user_signing_key| { + own_user_signing_key.verify_master_key(&master_key).is_ok() + }); + let new = Self { user_id: master_key.user_id().into(), master_key: master_key.clone().into(), self_signing_key: self_signing_key.into(), pinned_master_key: RwLock::new(pinned_master_key).into(), + previously_verified: Arc::new( + (self.was_previously_verified() || updated_is_verified).into(), + ), }; let changed = new != *self; @@ -961,19 +1078,17 @@ pub(crate) mod tests { use super::{ testing::{device, get_other_identity, get_own_identity}, - OwnUserIdentityData, UserIdentityData, + OtherUserIdentityDataSerializerV2, OwnUserIdentityData, UserIdentityData, }; use crate::{ identities::{ - manager::testing::own_key_query, - user::{OtherUserIdentityDataSerializer, OtherUserIdentityDataSerializerV1}, - Device, + manager::testing::own_key_query, user::OtherUserIdentityDataSerializer, Device, }, olm::{Account, PrivateCrossSigningIdentity}, store::{CryptoStoreWrapper, MemoryStore}, types::{CrossSigningKey, MasterPubkey, SelfSigningPubkey, Signatures, UserSigningPubkey}, verification::VerificationMachine, - OlmMachine, OtherUserIdentityData, + CrossSigningKeyExport, OlmMachine, OtherUserIdentityData, }; #[test] @@ -1076,12 +1191,12 @@ pub(crate) mod tests { let value = serde_json::to_value(migrated.clone()).unwrap(); // Should be serialized with latest version - let _: OtherUserIdentityDataSerializerV1 = - serde_json::from_value(value.clone()).expect("Should deserialize as version 1"); + let _: OtherUserIdentityDataSerializerV2 = + serde_json::from_value(value.clone()).expect("Should deserialize as version 2"); let with_serializer: OtherUserIdentityDataSerializer = serde_json::from_value(value).unwrap(); - assert_eq!("1", with_serializer.version.unwrap()); + assert_eq!("2", with_serializer.version.unwrap()); } #[test] @@ -1315,4 +1430,83 @@ pub(crate) mod tests { // But there is still a pin violation assert!(other_identity.inner.has_pin_violation()); } + + #[async_test] + async fn resolve_identity_verification_violation_with_withdraw() { + use test_json::keys_query_sets::PreviouslyVerifiedTestData as DataSet; + + let machine = OlmMachine::new(DataSet::own_id(), device_id!("LOCAL")).await; + + let keys_query = DataSet::own_keys_query_response_1(); + let txn_id = TransactionId::new(); + machine.mark_request_as_sent(&txn_id, &keys_query).await.unwrap(); + + machine + .import_cross_signing_keys(CrossSigningKeyExport { + master_key: DataSet::MASTER_KEY_PRIVATE_EXPORT.to_owned().into(), + self_signing_key: DataSet::SELF_SIGNING_KEY_PRIVATE_EXPORT.to_owned().into(), + user_signing_key: DataSet::USER_SIGNING_KEY_PRIVATE_EXPORT.to_owned().into(), + }) + .await + .unwrap(); + + let keys_query = DataSet::bob_keys_query_response_rotated(); + let txn_id = TransactionId::new(); + machine.mark_request_as_sent(&txn_id, &keys_query).await.unwrap(); + + let bob_identity = + machine.get_identity(DataSet::bob_id(), None).await.unwrap().unwrap().other().unwrap(); + + // For testing purpose mark it as previously verified + bob_identity.mark_as_previously_verified().await.unwrap(); + + assert!(bob_identity.has_verification_violation()); + + // withdraw + bob_identity.withdraw_verification().await.unwrap(); + + let bob_identity = + machine.get_identity(DataSet::bob_id(), None).await.unwrap().unwrap().other().unwrap(); + + assert!(!bob_identity.has_verification_violation()); + } + + #[async_test] + async fn reset_own_keys_creates_verification_violation() { + use test_json::keys_query_sets::PreviouslyVerifiedTestData as DataSet; + + let machine = OlmMachine::new(DataSet::own_id(), device_id!("LOCAL")).await; + + let keys_query = DataSet::own_keys_query_response_1(); + let txn_id = TransactionId::new(); + machine.mark_request_as_sent(&txn_id, &keys_query).await.unwrap(); + + machine + .import_cross_signing_keys(CrossSigningKeyExport { + master_key: DataSet::MASTER_KEY_PRIVATE_EXPORT.to_owned().into(), + self_signing_key: DataSet::SELF_SIGNING_KEY_PRIVATE_EXPORT.to_owned().into(), + user_signing_key: DataSet::USER_SIGNING_KEY_PRIVATE_EXPORT.to_owned().into(), + }) + .await + .unwrap(); + + let keys_query = DataSet::bob_keys_query_response_signed(); + let txn_id = TransactionId::new(); + machine.mark_request_as_sent(&txn_id, &keys_query).await.unwrap(); + + let bob_identity = + machine.get_identity(DataSet::bob_id(), None).await.unwrap().unwrap().other().unwrap(); + + // For testing purpose mark it as previously verified + bob_identity.mark_as_previously_verified().await.unwrap(); + + assert!(!bob_identity.has_verification_violation()); + + let _ = machine.bootstrap_cross_signing(true).await.unwrap(); + + let bob_identity = + machine.get_identity(DataSet::bob_id(), None).await.unwrap().unwrap().other().unwrap(); + + assert!(bob_identity.has_verification_violation()); + } } diff --git a/crates/matrix-sdk-crypto/src/machine.rs b/crates/matrix-sdk-crypto/src/machine.rs index 3a2c5a5a195..451c7d5491c 100644 --- a/crates/matrix-sdk-crypto/src/machine.rs +++ b/crates/matrix-sdk-crypto/src/machine.rs @@ -147,6 +147,7 @@ impl std::fmt::Debug for OlmMachine { impl OlmMachine { const CURRENT_GENERATION_STORE_KEY: &'static str = "generation-counter"; + const HAS_MIGRATED_VERIFICATION_LATCH: &'static str = "HAS_MIGRATED_VERIFICATION_LATCH"; /// Create a new memory based OlmMachine. /// @@ -358,9 +359,40 @@ impl OlmMachine { let identity = Arc::new(Mutex::new(identity)); let store = Arc::new(CryptoStoreWrapper::new(user_id, device_id, store)); + + // FIXME: We might want in the future a more generic high-level data migration + // mechanism (at the store wrapper layer). + Self::migration_post_verified_latch_support(&store).await?; + Ok(OlmMachine::new_helper(device_id, store, static_account, identity, maybe_backup_key)) } + // The sdk now support verified identity change detection. + // This introduces a new local flag (`verified_latch` on + // `OtherUserIdentityData`). In order to ensure that this flag is up-to-date and + // for the sake of simplicity we force a re-download of tracked users by marking + // them as dirty. + // + // pub(crate) visibility for testing. + pub(crate) async fn migration_post_verified_latch_support( + store: &CryptoStoreWrapper, + ) -> Result<(), CryptoStoreError> { + let maybe_migrate_for_identity_verified_latch = + store.get_custom_value(Self::HAS_MIGRATED_VERIFICATION_LATCH).await?.is_none(); + if maybe_migrate_for_identity_verified_latch { + // We want to mark all tracked users as dirty to ensure the verified latch is + // set up correctly. + let tracked_user = store.load_tracked_users().await?; + let mut store_updates = Vec::with_capacity(tracked_user.len()); + tracked_user.iter().for_each(|tu| { + store_updates.push((tu.user_id.as_ref(), true)); + }); + store.save_tracked_users(&store_updates).await?; + store.set_custom_value(Self::HAS_MIGRATED_VERIFICATION_LATCH, vec![0]).await? + } + Ok(()) + } + /// Get the crypto store associated with this `OlmMachine` instance. pub fn store(&self) -> &Store { &self.inner.store @@ -805,11 +837,11 @@ impl OlmMachine { } #[instrument( - skip_all, - // This function is only ever called by add_room_key via - // handle_decrypted_to_device_event, so sender, sender_key, and algorithm are - // already recorded. - fields(room_id = ?content.room_id, session_id) + skip_all, + // This function is only ever called by add_room_key via + // handle_decrypted_to_device_event, so sender, sender_key, and algorithm are + // already recorded. + fields(room_id = ? content.room_id, session_id) )] async fn handle_key( &self, @@ -5009,4 +5041,39 @@ pub(crate) mod tests { .unwrap(); assert_matches!(thread_encryption_result, UnsignedDecryptionResult::Decrypted(_)); } + + #[async_test] + async fn test_verified_latch_migration() { + let store = MemoryStore::new(); + let account = vodozemac::olm::Account::new(); + + // put some tracked users + let bob_id = user_id!("@bob:localhost"); + let carol_id = user_id!("@carol:localhost"); + + // Mark them as not dirty + let to_track_not_dirty = vec![(bob_id, false), (carol_id, false)]; + store.save_tracked_users(&to_track_not_dirty).await.unwrap(); + + let alice = OlmMachine::with_store(user_id(), alice_device_id(), store, Some(account)) + .await + .unwrap(); + + // A migration should have occurred and all users should be marked as dirty + alice.store().load_tracked_users().await.unwrap().iter().for_each(|tu| { + assert!(tu.dirty); + }); + + // Ensure it does so only once + alice.store().save_tracked_users(&to_track_not_dirty).await.unwrap(); + + OlmMachine::migration_post_verified_latch_support(alice.store().crypto_store().as_ref()) + .await + .unwrap(); + + // Migration already done, so user should not be marked as dirty + alice.store().load_tracked_users().await.unwrap().iter().for_each(|tu| { + assert!(!tu.dirty); + }); + } } diff --git a/crates/matrix-sdk-crypto/src/store/crypto_store_wrapper.rs b/crates/matrix-sdk-crypto/src/store/crypto_store_wrapper.rs index 96ea8e23720..9cd51a37dda 100644 --- a/crates/matrix-sdk-crypto/src/store/crypto_store_wrapper.rs +++ b/crates/matrix-sdk-crypto/src/store/crypto_store_wrapper.rs @@ -6,13 +6,14 @@ use matrix_sdk_common::store_locks::CrossProcessStoreLock; use ruma::{DeviceId, OwnedDeviceId, OwnedUserId, UserId}; use tokio::sync::{broadcast, Mutex}; use tokio_stream::wrappers::{errors::BroadcastStreamRecvError, BroadcastStream}; -use tracing::warn; +use tracing::{debug, trace, warn}; use super::{caches::SessionStore, DeviceChanges, IdentityChanges, LockableCryptoStore}; use crate::{ olm::InboundGroupSession, - store::{self, Changes, DynCryptoStore, IntoCryptoStore, RoomKeyInfo, RoomKeyWithheldInfo}, - GossippedSecret, OwnUserIdentityData, Session, + store, + store::{Changes, DynCryptoStore, IntoCryptoStore, RoomKeyInfo, RoomKeyWithheldInfo}, + CryptoStoreError, GossippedSecret, OwnUserIdentityData, Session, UserIdentityData, }; /// A wrapper for crypto store implementations that adds update notifiers. @@ -92,6 +93,17 @@ impl CryptoStoreWrapper { }) .collect(); + // If our own identity verified status changes we need to do some checks on + // other identities. So remember the verification status before + // processing the changes + let own_identity_was_verified_before_change = self + .store + .get_user_identity(self.user_id.as_ref()) + .await? + .as_ref() + .and_then(|i| i.own()) + .map_or(false, |own| own.is_verified()); + let secrets = changes.secrets.to_owned(); let devices = changes.devices.to_owned(); let identities = changes.identities.to_owned(); @@ -131,10 +143,71 @@ impl CryptoStoreWrapper { // Mapping the devices and user identities from the read-only variant to one's // that contain side-effects requires our own identity. This is // guaranteed to be up-to-date since we just persisted it. - let own_identity = + let maybe_own_identity = self.store.get_user_identity(&self.user_id).await?.and_then(|i| i.into_own()); - let _ = self.identities_broadcaster.send((own_identity, identities, devices)); + // If our identity was not verified before the change and is now, that means + // this could impact the verification chain of other known + // identities. + if let Some(own_identity_after) = maybe_own_identity.as_ref() { + // Only do this if our identity is passing from not verified to verified, + // the previously_verified can only change in that case. + if !own_identity_was_verified_before_change && own_identity_after.is_verified() { + debug!("Own identity is now verified, check all known identities for verification status changes"); + // We need to review all the other identities to see if they are verified now + // and mark them as such + self.check_all_identities_and_update_was_previously_verified_flag_if_needed( + own_identity_after, + ) + .await?; + } + } + + let _ = self.identities_broadcaster.send((maybe_own_identity, identities, devices)); + } + + Ok(()) + } + + async fn check_all_identities_and_update_was_previously_verified_flag_if_needed( + &self, + own_identity_after: &OwnUserIdentityData, + ) -> Result<(), CryptoStoreError> { + let tracked_users = self.store.load_tracked_users().await?; + let mut updated_identities: Vec = Default::default(); + for tracked_user in tracked_users { + if let Some(other_identity) = self + .store + .get_user_identity(tracked_user.user_id.as_ref()) + .await? + .as_ref() + .and_then(|i| i.other()) + { + if !other_identity.was_previously_verified() + && own_identity_after.is_identity_signed(other_identity).is_ok() + { + trace!(?tracked_user.user_id, "Marking set verified_latch to true."); + other_identity.mark_as_previously_verified(); + updated_identities.push(other_identity.clone().into()); + } + } + } + + if !updated_identities.is_empty() { + let identity_changes = + IdentityChanges { changed: updated_identities, ..Default::default() }; + self.store + .save_changes(Changes { + identities: identity_changes.clone(), + ..Default::default() + }) + .await?; + + let _ = self.identities_broadcaster.send(( + Some(own_identity_after.clone()), + identity_changes, + DeviceChanges::default(), + )); } Ok(()) diff --git a/crates/matrix-sdk-crypto/src/store/memorystore.rs b/crates/matrix-sdk-crypto/src/store/memorystore.rs index 01405be7a1d..fe378a84099 100644 --- a/crates/matrix-sdk-crypto/src/store/memorystore.rs +++ b/crates/matrix-sdk-crypto/src/store/memorystore.rs @@ -83,7 +83,7 @@ pub struct MemoryStore { tracked_users: StdRwLock>, olm_hashes: StdRwLock>>, devices: DeviceStore, - identities: StdRwLock>, + identities: StdRwLock>, outgoing_key_requests: StdRwLock>, key_requests_by_info: StdRwLock>, direct_withheld_info: StdRwLock>>, @@ -231,7 +231,10 @@ impl CryptoStore for MemoryStore { { let mut identities = self.identities.write().unwrap(); for identity in changes.identities.new.into_iter().chain(changes.identities.changed) { - identities.insert(identity.user_id().to_owned(), identity.clone()); + identities.insert( + identity.user_id().to_owned(), + serde_json::to_string(&identity).unwrap(), + ); } } @@ -481,7 +484,14 @@ impl CryptoStore for MemoryStore { } async fn get_user_identity(&self, user_id: &UserId) -> Result> { - Ok(self.identities.read().unwrap().get(user_id).cloned()) + let serialized = self.identities.read().unwrap().get(user_id).cloned(); + match serialized { + None => Ok(None), + Some(serialized) => { + let id: UserIdentityData = serde_json::from_str(serialized.as_str()).unwrap(); + Ok(Some(id)) + } + } } async fn is_message_known(&self, message_hash: &crate::olm::OlmMessageHash) -> Result { diff --git a/testing/matrix-sdk-test/src/test_json/keys_query_sets.rs b/testing/matrix-sdk-test/src/test_json/keys_query_sets.rs index bf5c1a3321e..cfa290427dc 100644 --- a/testing/matrix-sdk-test/src/test_json/keys_query_sets.rs +++ b/testing/matrix-sdk-test/src/test_json/keys_query_sets.rs @@ -667,3 +667,424 @@ impl IdentityChangeDataSet { .expect("Can't parse the `/keys/upload` response") } } + +pub struct PreviouslyVerifiedTestData {} + +#[allow(dead_code)] +impl PreviouslyVerifiedTestData { + pub const MASTER_KEY_PRIVATE_EXPORT: &'static str = + "bSa0nVTocZArMzL7OLmeFUIVF4ycp64rrkVMgqOYg6Y"; + pub const SELF_SIGNING_KEY_PRIVATE_EXPORT: &'static str = + "MQ7b3MDXvOEMDvIOWkuH1XCNUyqBLqbdd1bT00p8HPU"; + pub const USER_SIGNING_KEY_PRIVATE_EXPORT: &'static str = + "v77s+TlT5/NbcQym2B7Rwf20HOAhyInF2p1ZUYDPtow"; + + pub fn own_id() -> &'static UserId { + user_id!("@alice:localhost") + } + pub fn bob_id() -> &'static UserId { + user_id!("@bob:localhost") + } + + pub fn carol_id() -> &'static UserId { + user_id!("@carol:localhost") + } + + /// Current user keys query response containing the cross-signing keys + pub fn own_keys_query_response_1() -> KeyQueryResponse { + let data = json!({ + "master_keys": { + "@alice:localhost": { + "keys": { + "ed25519:EPVg/QLG9+FmNvKjNXfycZEpQLtfHDaTN+rENAURZSk": "EPVg/QLG9+FmNvKjNXfycZEpQLtfHDaTN+rENAURZSk" + }, + "signatures": { + "@alice:localhost": { + "ed25519:EPVg/QLG9+FmNvKjNXfycZEpQLtfHDaTN+rENAURZSk": "FX+srrw9SRmi12fexYHH1jrlEIWgOfre1aPNzDZWcAlaP9WKRdhcQGh70/3F9hk/PGr51I+ux62YgU4xnRTqAA", + "ed25519:PWVCNMMGCT": "teLq0rCYKX9h8WXu6kH8UE6HPKAtkF/DwCncxJGvVBCyZRtLHD8W1yYEzJXjTNynn+4fibQZBhR3th1RGLn4Ag" + } + }, + "usage": [ + "master" + ], + "user_id": "@alice:localhost" + } + }, + "self_signing_keys": { + "@alice:localhost": { + "keys": { + "ed25519:WXLer0esHUanp8DCeu2Be0xB5ms9aKFFBrCFl50COjw": "WXLer0esHUanp8DCeu2Be0xB5ms9aKFFBrCFl50COjw" + }, + "signatures": { + "@alice:localhost": { + "ed25519:EPVg/QLG9+FmNvKjNXfycZEpQLtfHDaTN+rENAURZSk": "lCV9R1xjD34arzq/CAuej1XBv+Ip4dFfAGHfe7znbW7rnwKDaX5PaX3MHk+EIC7nXvUYEAn502WcUFme5c0cCQ" + } + }, + "usage": [ + "self_signing" + ], + "user_id": "@alice:localhost" + } + }, + "user_signing_keys": { + "@alice:localhost": { + "keys": { + "ed25519:MXob/N/bYI7U2655O1/AI9NOX1245RnE03Nl4Hvf+u0": "MXob/N/bYI7U2655O1/AI9NOX1245RnE03Nl4Hvf+u0" + }, + "signatures": { + "@alice:localhost": { + "ed25519:EPVg/QLG9+FmNvKjNXfycZEpQLtfHDaTN+rENAURZSk": "A73QfZ5Dzhh7abdal/sEaq1bfgxzPFU8Bvwa9Y5TIe/a5jTmLVubNmsMSsO5tOT+b6aVJg1G4FtId0Q/cb1aAA" + } + }, + "usage": [ + "user_signing" + ], + "user_id": "@alice:localhost" + } + } + }); + + let data = response_from_file(&data); + + KeyQueryResponse::try_from_http_response(data) + .expect("Can't parse the `/keys/upload` response") + } + + pub fn device_keys_payload_bob_unsigned_device() -> Value { + json!({ + "algorithms": [ + "m.olm.v1.curve25519-aes-sha2", + "m.megolm.v1.aes-sha2" + ], + "device_id": "XCYNVRMTER", + "keys": { + "curve25519:XCYNVRMTER": "xGKYkFcHGlJ+I1yiefPyZu1EY8i2h1eed5uk3PAW6GA", + "ed25519:XCYNVRMTER": "EsU8MJzTYE+/VJs1K9HkGqb8UXCByPioynGrV28WocU" + }, + "signatures": { + "@bob:localhost": { + "ed25519:XCYNVRMTER": "yZ7cpaoA+0rRx+bmklsP1iAd0eGPH6gsdywC11VE98/mrcbeFuxjQVn39Ds7h+vmciu5GRzwWgDgv+6go6FHAQ", + // Remove the cross-signature + // "ed25519:e8JFSrW8LW3UK6SSXh2ZESUzptFbapr28/+WqndD+Xk": "xYnGmU9FEdoavB5P743gx3xbEy29tlfRX5lT3JO0dWhHdsP+muqBXUYMBl1RRFeZtIE0GYc9ORb6Yf88EdeoCw" + } + }, + "user_id": "@bob:localhost", + "unsigned": {} + }) + } + + pub fn bob_keys_query_response_signed() -> KeyQueryResponse { + let data = json!({ + "device_keys": { + "@bob:localhost": { + "RLZGZIHKMP": { + "algorithms": [ + "m.olm.v1.curve25519-aes-sha2", + "m.megolm.v1.aes-sha2" + ], + "device_id": "RLZGZIHKMP", + "keys": { + "curve25519:RLZGZIHKMP": "Zd8uO9Rr1PtqNno3//ybeUZ3JuqFtm17TQTWW0f47AU", + "ed25519:RLZGZIHKMP": "kH+Zn2m7LPES/XLOyVvnf8t4Byfj3mAbngHptHZFzk0" + }, + "signatures": { + "@bob:localhost": { + "ed25519:RLZGZIHKMP": "w4MOkDiD+4XatQrRzGrcaqwVmiZrAjxmaIA8aSuzQveD2SJ2eVZq3OSpqx6QRUbG/gkkZxGmY13PkS/iAOv0AA", + "ed25519:e8JFSrW8LW3UK6SSXh2ZESUzptFbapr28/+WqndD+Xk": "ki+cV0EVe5cYXnzqU078qy1qu2rnaxaBQU+KwyvPpEUotNTXjWKUOJfxast42d5tjI5vsI5aiQ6XkYfjBJ74Bw" + } + }, + "user_id": "@bob:localhost", + "unsigned": {} + }, + "XCYNVRMTER": Self::device_keys_payload_bob_unsigned_device(), + } + }, + "failures": {}, + "master_keys": { + "@bob:localhost": { + "keys": { + "ed25519:xZPyb4hxM8zaedDFz5m8HsDpX1fknd/V/69THLhNX9I": "xZPyb4hxM8zaedDFz5m8HsDpX1fknd/V/69THLhNX9I" + }, + "signatures": { + "@bob:localhost": { + "ed25519:RLZGZIHKMP": "5bHLrx0HwYsNRtd65s1a1wVGlwgJU8yb8cq/Qbq04o9nVdQuY8+woQVWq9nxk59u6QFZIpFdVjXsuTPkDJLsBA", + "ed25519:xZPyb4hxM8zaedDFz5m8HsDpX1fknd/V/69THLhNX9I": "NA+cLNIPpmECcBIcmAH5l1K4IDXI6Xss1VmU8TZ04AYQSAh/2sv7NixEBO1/Raz0nErzkOl8gpRswHbHv1p7Dw" + }, + "@alice:localhost": { + "ed25519:MXob/N/bYI7U2655O1/AI9NOX1245RnE03Nl4Hvf+u0": "n3X6afWYoSywqBpPlaDfQ2BNjl3ez5AzxEVwaB5/KEAzgwsq5B2qBW9N5uZaNWEq5M3JBrh0doj1FgUg4R3yBQ" + } + }, + "usage": [ + "master" + ], + "user_id": "@bob:localhost" + } + }, + "self_signing_keys": { + "@bob:localhost": { + "keys": { + "ed25519:e8JFSrW8LW3UK6SSXh2ZESUzptFbapr28/+WqndD+Xk": "e8JFSrW8LW3UK6SSXh2ZESUzptFbapr28/+WqndD+Xk" + }, + "signatures": { + "@bob:localhost": { + "ed25519:xZPyb4hxM8zaedDFz5m8HsDpX1fknd/V/69THLhNX9I": "kkGZHLY18jyqXs412VB31u6vxijbaBgVrIMR/LBAFULhTZk6HGH951N6NxMZnYHyH0sFaQhsl4DUqt7XthBHBQ" + } + }, + "usage": [ + "self_signing" + ], + "user_id": "@bob:localhost" + } + }, + "user_signing_keys": {} + }); + + let data = response_from_file(&data); + + KeyQueryResponse::try_from_http_response(data) + .expect("Can't parse the `/keys/upload` response") + } + + pub fn bob_device_1_id() -> &'static DeviceId { + device_id!("RLZGZIHKMP") + } + pub fn bob_device_2_id() -> &'static DeviceId { + device_id!("XCYNVRMTER") + } + + // Bob has a new identity, the two devices are properly self-signed + pub fn bob_keys_query_response_rotated() -> KeyQueryResponse { + let data = json!({ + "device_keys": { + "@bob:localhost": { + "RLZGZIHKMP": { + "algorithms": [ + "m.olm.v1.curve25519-aes-sha2", + "m.megolm.v1.aes-sha2" + ], + "device_id": "RLZGZIHKMP", + "keys": { + "curve25519:RLZGZIHKMP": "Zd8uO9Rr1PtqNno3//ybeUZ3JuqFtm17TQTWW0f47AU", + "ed25519:RLZGZIHKMP": "kH+Zn2m7LPES/XLOyVvnf8t4Byfj3mAbngHptHZFzk0" + }, + "signatures": { + "@bob:localhost": { + "ed25519:RLZGZIHKMP": "w4MOkDiD+4XatQrRzGrcaqwVmiZrAjxmaIA8aSuzQveD2SJ2eVZq3OSpqx6QRUbG/gkkZxGmY13PkS/iAOv0AA", + // "ed25519:At1ai1VUZrCncCI7V7fEAJmBShfpqZ30xRzqcEjTjdc": "rg3b3DovN3VztdcKyOcOlIGQxmm+8VC9+ImuXdgug/kPSi7QcljwOtjnk4LMkHexB3xVzB0ANcyNjbJ2cJuYBg", + "ed25519:e8JFSrW8LW3UK6SSXh2ZESUzptFbapr28/+WqndD+Xk": "ki+cV0EVe5cYXnzqU078qy1qu2rnaxaBQU+KwyvPpEUotNTXjWKUOJfxast42d5tjI5vsI5aiQ6XkYfjBJ74Bw" + } + }, + "user_id": "@bob:localhost", + "unsigned": { + "device_display_name": "develop.element.io: Chrome on macOS" + } + }, + "XCYNVRMTER": { + "algorithms": [ + "m.olm.v1.curve25519-aes-sha2", + "m.megolm.v1.aes-sha2" + ], + "device_id": "XCYNVRMTER", + "keys": { + "curve25519:XCYNVRMTER": "xGKYkFcHGlJ+I1yiefPyZu1EY8i2h1eed5uk3PAW6GA", + "ed25519:XCYNVRMTER": "EsU8MJzTYE+/VJs1K9HkGqb8UXCByPioynGrV28WocU" + }, + "signatures": { + "@bob:localhost": { + "ed25519:XCYNVRMTER": "yZ7cpaoA+0rRx+bmklsP1iAd0eGPH6gsdywC11VE98/mrcbeFuxjQVn39Ds7h+vmciu5GRzwWgDgv+6go6FHAQ", + "ed25519:e8JFSrW8LW3UK6SSXh2ZESUzptFbapr28/+WqndD+Xk": "xYnGmU9FEdoavB5P743gx3xbEy29tlfRX5lT3JO0dWhHdsP+muqBXUYMBl1RRFeZtIE0GYc9ORb6Yf88EdeoCw", + "ed25519:NWoyMF4Ox8PEj+8l1e70zuIUg0D+wL9wtcj1KhWL0Bc": "2ieX8z+oW9JhdyIIkTDsQ2o5VWxcO6dOgeyPbRwbAL6Q8J6xujzYSIi568UAlPt+wg+RkNLshneexCPNMgSiDQ" + } + }, + "user_id": "@bob:localhost", + "unsigned": { + "device_display_name": "app.element.io: Chrome on mac" + } + } + } + }, + "failures": {}, + "master_keys": { + "@bob:localhost": { + "keys": { + "ed25519:xaFlsDqlDRRy7Idtt1dW9mdhH/gvvax34q+HxepjNWY": "xaFlsDqlDRRy7Idtt1dW9mdhH/gvvax34q+HxepjNWY" + }, + "signatures": { + "@bob:localhost": { + "ed25519:XCYNVRMTER": "K1aPl+GtcNi8yDqn1zvKIJMg3PFLQkwoXJeFJMmct4SA2SiQIl1S2x1bDTC3kQ4/LA7ULiQgKlxkXdQVf2GZDw", + "ed25519:xaFlsDqlDRRy7Idtt1dW9mdhH/gvvax34q+HxepjNWY": "S5vw8moiPudKhmF1qIv3/ehbZ7uohJbcQaLcOV+DDh9iC/YX0UqnaGn1ZYWJpIN7Kxe2ZWCBwzp35DOVZKfxBw" + } + }, + "usage": [ + "master" + ], + "user_id": "@bob:localhost" + } + }, + "self_signing_keys": { + "@bob:localhost": { + "keys": { + "ed25519:NWoyMF4Ox8PEj+8l1e70zuIUg0D+wL9wtcj1KhWL0Bc": "NWoyMF4Ox8PEj+8l1e70zuIUg0D+wL9wtcj1KhWL0Bc" + }, + "signatures": { + "@bob:localhost": { + "ed25519:xaFlsDqlDRRy7Idtt1dW9mdhH/gvvax34q+HxepjNWY": "rwQIkR7JbZOrwGrmkW9QzFlK+lMjRDHVcGVlYNS/zVeDyvWxD0WFHcmy4p/LSgJDyrVt+th7LH7Bj+Ed/EGvCw" + } + }, + "usage": [ + "self_signing" + ], + "user_id": "@bob:localhost" + } + }, + "user_signing_keys": {} + }); + + let data = response_from_file(&data); + + KeyQueryResponse::try_from_http_response(data) + .expect("Can't parse the `/keys/upload` response") + } + + pub fn device_1_keys_payload_carol() -> Value { + json!({ + // Not self signed + "algorithms": [ + "m.olm.v1.curve25519-aes-sha2", + "m.megolm.v1.aes-sha2" + ], + "device_id": "BAZAPVEHGA", + "keys": { + "curve25519:BAZAPVEHGA": "/mCcWJb5mtNGPC7m4iQeW8gVJB4nG8z/z2QQXzzNijw", + "ed25519:BAZAPVEHGA": "MLSoOlk27qcS/2O9Etp6XwgF8j+UT06yy/ypSeE9JRA" + }, + "signatures": { + "@carol:localhost": { + "ed25519:BAZAPVEHGA": "y2+Z0ofRRoNMj864SoAcNEXRToYVeiARu39CO0Vj2GcSIxlpR7B8K1wDYV4luP4gOL1t1tPgJPXL1WO//AHHCw", + } + }, + "user_id": "@carol:localhost" + }) + } + + pub fn device_2_keys_payload_carol() -> Value { + // Self-signed device + json!({ + "algorithms": [ + "m.olm.v1.curve25519-aes-sha2", + "m.megolm.v1.aes-sha2" + ], + "device_id": "JBRBCHOFDZ", + "keys": { + "curve25519:JBRBCHOFDZ": "900HdrlfxlH8yMSmEQ3C32uVyXCuxKs5oPKS/wUgzVQ", + "ed25519:JBRBCHOFDZ": "BOINY06uroLYscHUq0e0FmUo/W0LC4/fsIPkZQe71NY" + }, + "signatures": { + "@carol:localhost": { + "ed25519:JBRBCHOFDZ": "MmSJS3yEdeuseiLTDCQwImZBPNFMdhhkAFjRZZrIONoGFR0AMSzgLtx/nSgXP8RwVxpycvb6OAqvSk2toK3PDg", + "ed25519:ZOMWgk5LAogkwDEdZl9Rv7FRGu0nGbeLtMHx6anzhQs": "VtoxmPn/BQVDlpEHPEI2wPUlruUX9m2zV3FChNkRyEEWur4St27WA1He8BwjVRiiT0bdUnVH3xfmucoV9UnbDA" + } + }, + "user_id": "@carol:localhost", + }) + } + + pub fn ssk_payload_carol() -> Value { + json!({ + "@carol:localhost": { + "keys": { + "ed25519:ZOMWgk5LAogkwDEdZl9Rv7FRGu0nGbeLtMHx6anzhQs": "ZOMWgk5LAogkwDEdZl9Rv7FRGu0nGbeLtMHx6anzhQs" + }, + "signatures": { + "@carol:localhost": { + "ed25519:itnwUCRfBPW08IrmBks9MTp/Qm5AJ2WNca13ptIZF8U": "thjR1/kxHADXqLqxc4Q3OZhAaLq7SPL96LNCGVGN64OYAJ5yG1cpqAXBiBCUaBUTdRTb0ys601RR8djPdTK/BQ" + } + }, + "usage": [ + "self_signing" + ], + "user_id": "@carol:localhost" + } + }) + } + + // Carol key query response with one signed and one unsigned device. + // Bob has not verified Carol yet + pub fn carol_keys_query_response_unsigned() -> KeyQueryResponse { + let data = json!({ + "device_keys": { + "@carol:localhost": { + "BAZAPVEHGA": Self::device_1_keys_payload_carol(), + "JBRBCHOFDZ": Self::device_2_keys_payload_carol() + } + }, + "failures": {}, + "master_keys": { + "@carol:localhost": { + "keys": { + "ed25519:itnwUCRfBPW08IrmBks9MTp/Qm5AJ2WNca13ptIZF8U": "itnwUCRfBPW08IrmBks9MTp/Qm5AJ2WNca13ptIZF8U" + }, + "signatures": { + "@carol:localhost": { + "ed25519:JBRBCHOFDZ": "eRA4jRSszQVuYpMtHTBuWGLEzcdUojyCW4/XKHRIQ2solv7iTC/MWES6I20YrHJa7H82CVoyNxS1Y3AwttBbCg", + "ed25519:itnwUCRfBPW08IrmBks9MTp/Qm5AJ2WNca13ptIZF8U": "e3r5L+JLv6FB8+Tt4BlIbz4wk2qPeMoKL1uR079qZzYMvtKoWGK9p000cZIhA5R1Tl7buQ9ODUfizued8g3TAg" + }, + // "@alice:localhost": { + // "ed25519:MXob/N/bYI7U2655O1/AI9NOX1245RnE03Nl4Hvf+u0": "yfRUvkaVg3KizC/HDXcuP4+gtYhxgzr8X916Wt4GRXjj4qhDjsCkf8mYZ7x4lcEXzRkYql5KelabgVzP12qmAA" + // } + }, + "usage": [ + "master" + ], + "user_id": "@carol:localhost" + } + }, + "self_signing_keys": Self::ssk_payload_carol(), + "user_signing_keys": {} + }); + + let data = response_from_file(&data); + + KeyQueryResponse::try_from_http_response(data) + .expect("Can't parse the `/keys/upload` response") + } + + pub fn carol_keys_query_response_signed() -> KeyQueryResponse { + let data = json!({ + "device_keys": { + "@carol:localhost": { + "BAZAPVEHGA": Self::device_1_keys_payload_carol(), + "JBRBCHOFDZ": Self::device_2_keys_payload_carol() + } + }, + "failures": {}, + "master_keys": { + "@carol:localhost": { + "keys": { + "ed25519:itnwUCRfBPW08IrmBks9MTp/Qm5AJ2WNca13ptIZF8U": "itnwUCRfBPW08IrmBks9MTp/Qm5AJ2WNca13ptIZF8U" + }, + "signatures": { + "@carol:localhost": { + "ed25519:JBRBCHOFDZ": "eRA4jRSszQVuYpMtHTBuWGLEzcdUojyCW4/XKHRIQ2solv7iTC/MWES6I20YrHJa7H82CVoyNxS1Y3AwttBbCg", + "ed25519:itnwUCRfBPW08IrmBks9MTp/Qm5AJ2WNca13ptIZF8U": "e3r5L+JLv6FB8+Tt4BlIbz4wk2qPeMoKL1uR079qZzYMvtKoWGK9p000cZIhA5R1Tl7buQ9ODUfizued8g3TAg" + }, + "@alice:localhost": { + "ed25519:MXob/N/bYI7U2655O1/AI9NOX1245RnE03Nl4Hvf+u0": "yfRUvkaVg3KizC/HDXcuP4+gtYhxgzr8X916Wt4GRXjj4qhDjsCkf8mYZ7x4lcEXzRkYql5KelabgVzP12qmAA" + } + }, + "usage": [ + "master" + ], + "user_id": "@carol:localhost" + } + }, + "self_signing_keys": Self::ssk_payload_carol(), + "user_signing_keys": {} + }); + + let data = response_from_file(&data); + + KeyQueryResponse::try_from_http_response(data) + .expect("Can't parse the `/keys/upload` response") + } +}