mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-06-21 22:58:32 -04:00
Merge pull request #3795 from matrix-org/valere/invisible_crypto/verification_pin
Crypto | Add support for verified identities change detection.
This commit is contained in:
@@ -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 = [
|
||||
|
||||
@@ -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<OwnedTransactionId>,
|
||||
}
|
||||
|
||||
// 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<PrivateCrossSigningIdentity>,
|
||||
) -> Result<UserIdentityData, SignatureError> {
|
||||
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<PrivateCrossSigningIdentity>,
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<MasterPubkey>,
|
||||
self_signing_key: Arc<SelfSigningPubkey>,
|
||||
pinned_master_key: Arc<RwLock<MasterPubkey>>,
|
||||
/// 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<AtomicBool>,
|
||||
}
|
||||
|
||||
/// 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<OtherUserIdentityDataSerializer> for OtherUserIdentityData {
|
||||
type Error = serde_json::Error;
|
||||
fn try_from(
|
||||
@@ -475,6 +538,7 @@ impl TryFrom<OtherUserIdentityDataSerializer> 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<OtherUserIdentityDataSerializer> 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<OtherUserIdentityDataSerializer> for OtherUserIdentityData {
|
||||
|
||||
impl From<OtherUserIdentityData> 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<bool, SignatureError> {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<UserIdentityData> = 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(())
|
||||
|
||||
@@ -83,7 +83,7 @@ pub struct MemoryStore {
|
||||
tracked_users: StdRwLock<HashMap<OwnedUserId, TrackedUser>>,
|
||||
olm_hashes: StdRwLock<HashMap<String, HashSet<String>>>,
|
||||
devices: DeviceStore,
|
||||
identities: StdRwLock<HashMap<OwnedUserId, UserIdentityData>>,
|
||||
identities: StdRwLock<HashMap<OwnedUserId, String>>,
|
||||
outgoing_key_requests: StdRwLock<HashMap<OwnedTransactionId, GossipRequest>>,
|
||||
key_requests_by_info: StdRwLock<HashMap<String, OwnedTransactionId>>,
|
||||
direct_withheld_info: StdRwLock<HashMap<OwnedRoomId, HashMap<String, RoomKeyWithheldEvent>>>,
|
||||
@@ -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<Option<UserIdentityData>> {
|
||||
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<bool> {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user