Skip to content

Commit 77d05a4

Browse files
committed
feat(sdk-crypto): Add Identity local tofu trust flag.
1 parent 51a9441 commit 77d05a4

File tree

3 files changed

+354
-8
lines changed

3 files changed

+354
-8
lines changed

crates/matrix-sdk-crypto/src/identities/manager.rs

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ impl IdentityManager {
542542
*changed_private_identity = self.check_private_identity(&identity).await;
543543
Ok(identity.into())
544544
} else {
545-
let identity = ReadOnlyUserIdentity::new(master_key, self_signing)?;
545+
let identity = ReadOnlyUserIdentity::new(master_key, self_signing, true)?;
546546
Ok(identity.into())
547547
}
548548
}
@@ -1329,7 +1329,7 @@ pub(crate) mod tests {
13291329
use std::ops::Deref;
13301330

13311331
use futures_util::pin_mut;
1332-
use matrix_sdk_test::{async_test, response_from_file};
1332+
use matrix_sdk_test::{async_test, response_from_file, test_json};
13331333
use ruma::{
13341334
api::{client::keys::get_keys::v3::Response as KeysQueryResponse, IncomingResponse},
13351335
device_id, user_id, TransactionId,
@@ -1893,4 +1893,92 @@ pub(crate) mod tests {
18931893
.unwrap()
18941894
.unwrap();
18951895
}
1896+
1897+
#[async_test]
1898+
async fn test_manager_identity_updates() {
1899+
use test_json::keys_query_sets::IdentityChangeDataSet as DataSet;
1900+
1901+
let manager = manager_test_helper(user_id(), device_id()).await;
1902+
let other_user = DataSet::user_id();
1903+
let devices = manager.store.get_user_devices(other_user).await.unwrap();
1904+
assert_eq!(devices.devices().count(), 0);
1905+
1906+
let identity = manager.store.get_user_identity(other_user).await.unwrap();
1907+
assert!(identity.is_none());
1908+
1909+
manager
1910+
.receive_keys_query_response(
1911+
&TransactionId::new(),
1912+
&DataSet::key_query_with_identity_a(),
1913+
)
1914+
.await
1915+
.unwrap();
1916+
1917+
let identity = manager.store.get_user_identity(other_user).await.unwrap().unwrap();
1918+
let other_identity = identity.other().unwrap();
1919+
1920+
// There should be now an identity and it should be tofu trusted as it is the
1921+
// first time we see an identity for that user
1922+
assert!(other_identity.is_tofu_trusted());
1923+
let first_device = manager
1924+
.store
1925+
.get_readonly_device(other_user, DataSet::first_device_id())
1926+
.await
1927+
.unwrap()
1928+
.unwrap();
1929+
assert!(first_device.is_cross_signed_by_owner(&identity));
1930+
1931+
// We receive a new keys update for that user, with a new identity
1932+
manager
1933+
.receive_keys_query_response(
1934+
&TransactionId::new(),
1935+
&DataSet::key_query_with_identity_b(),
1936+
)
1937+
.await
1938+
.unwrap();
1939+
1940+
let identity = manager.store.get_user_identity(other_user).await.unwrap().unwrap();
1941+
let other_identity = identity.other().unwrap();
1942+
1943+
// The previous tofu identity has been replaced, it is not tofu trusted until
1944+
// validated by the user
1945+
assert!(!other_identity.is_tofu_trusted());
1946+
1947+
let second_device = manager
1948+
.store
1949+
.get_readonly_device(other_user, DataSet::second_device_id())
1950+
.await
1951+
.unwrap()
1952+
.unwrap();
1953+
// There is a new device signed by the new identity
1954+
assert!(second_device.is_cross_signed_by_owner(&identity));
1955+
1956+
// The first device should not be signed by the new identity
1957+
let first_device = manager
1958+
.store
1959+
.get_readonly_device(other_user, DataSet::first_device_id())
1960+
.await
1961+
.unwrap()
1962+
.unwrap();
1963+
assert!(!first_device.is_cross_signed_by_owner(&identity));
1964+
1965+
let remember_previous_identity = other_identity.clone();
1966+
// We receive a new keys update for that user, with no identity anymore
1967+
// Notice that there is no server API to delete identity, but we want to test
1968+
// here that a home server cannot clear the identity and serve a new one
1969+
// after that would get automatically tofu trusted.
1970+
manager
1971+
.receive_keys_query_response(
1972+
&TransactionId::new(),
1973+
&DataSet::key_query_with_identity_no_identity(),
1974+
)
1975+
.await
1976+
.unwrap();
1977+
1978+
let identity = manager.store.get_user_identity(other_user).await.unwrap().unwrap();
1979+
let other_identity = identity.other().unwrap();
1980+
1981+
assert_eq!(other_identity, &remember_previous_identity);
1982+
assert!(!other_identity.is_tofu_trusted());
1983+
}
18961984
}

crates/matrix-sdk-crypto/src/identities/user.rs

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,24 @@ pub struct ReadOnlyUserIdentity {
375375
user_id: OwnedUserId,
376376
pub(crate) master_key: Arc<MasterPubkey>,
377377
self_signing_key: Arc<SelfSigningPubkey>,
378+
/// The first time an identity is seen for a given user it will be marked as
379+
/// tofu trusted. If a new identity is detected for that user, we must
380+
/// ensure that this change has been shown and validated by the user. If
381+
/// this boolean is set to `false`, sharing room keys to this user might
382+
/// fail depending on the room key sharing strategy.
383+
#[serde(
384+
default = "tofu_trusted_default",
385+
serialize_with = "atomic_bool_serializer",
386+
deserialize_with = "atomic_bool_deserializer"
387+
)]
388+
tofu_trusted: Arc<AtomicBool>,
389+
}
390+
391+
// The tofu_trusted field introduced a new field in the database
392+
// schema, for backwards compatibility we assume that if the identity is
393+
// already in the database we considered it as tofu trusted.
394+
fn tofu_trusted_default() -> Arc<AtomicBool> {
395+
Arc::new(true.into())
378396
}
379397

380398
impl PartialEq for ReadOnlyUserIdentity {
@@ -395,6 +413,7 @@ impl PartialEq for ReadOnlyUserIdentity {
395413
&& self.master_key == other.master_key
396414
&& self.self_signing_key == other.self_signing_key
397415
&& self.master_key.signatures() == other.master_key.signatures()
416+
&& self.is_tofu_trusted() == other.is_tofu_trusted()
398417
}
399418
}
400419

@@ -406,19 +425,22 @@ impl ReadOnlyUserIdentity {
406425
/// * `master_key` - The master key of the user identity.
407426
///
408427
/// * `self signing key` - The self signing key of user identity.
428+
/// * `tofu_trusted` - Is this identity tofu trusted.
409429
///
410430
/// Returns a `SignatureError` if the self signing key fails to be correctly
411431
/// verified by the given master key.
412432
pub(crate) fn new(
413433
master_key: MasterPubkey,
414434
self_signing_key: SelfSigningPubkey,
435+
tofu_trusted: bool,
415436
) -> Result<Self, SignatureError> {
416437
master_key.verify_subkey(&self_signing_key)?;
417438

418439
Ok(Self {
419440
user_id: master_key.user_id().into(),
420441
master_key: master_key.into(),
421442
self_signing_key: self_signing_key.into(),
443+
tofu_trusted: AtomicBool::new(tofu_trusted).into(),
422444
})
423445
}
424446

@@ -429,7 +451,12 @@ impl ReadOnlyUserIdentity {
429451
let self_signing_key =
430452
identity.self_signing_key.lock().await.as_ref().unwrap().public_key().clone().into();
431453

432-
Self { user_id: identity.user_id().into(), master_key, self_signing_key }
454+
Self {
455+
user_id: identity.user_id().into(),
456+
master_key,
457+
self_signing_key,
458+
tofu_trusted: AtomicBool::new(true).into(),
459+
}
433460
}
434461

435462
/// Get the user id of this identity.
@@ -447,6 +474,21 @@ impl ReadOnlyUserIdentity {
447474
&self.self_signing_key
448475
}
449476

477+
/// Check if our identity is verified.
478+
pub fn is_tofu_trusted(&self) -> bool {
479+
self.tofu_trusted.load(Ordering::SeqCst)
480+
}
481+
482+
/// Mark this identity as tofu trusted by the user
483+
pub fn mark_as_tofu_trusted(&self) {
484+
self.tofu_trusted.store(true, Ordering::SeqCst)
485+
}
486+
487+
#[cfg(test)]
488+
pub fn mark_as_not_tofu_trusted(&self) {
489+
self.tofu_trusted.store(false, Ordering::SeqCst)
490+
}
491+
450492
/// Update the identity with a new master key and self signing key.
451493
///
452494
/// # Arguments
@@ -465,7 +507,7 @@ impl ReadOnlyUserIdentity {
465507
) -> Result<bool, SignatureError> {
466508
master_key.verify_subkey(&self_signing_key)?;
467509

468-
let new = Self::new(master_key, self_signing_key)?;
510+
let new = Self::new(master_key, self_signing_key, false)?;
469511
let changed = new != *self;
470512

471513
*self = new;
@@ -776,8 +818,12 @@ pub(crate) mod testing {
776818
let self_signing: CrossSigningKey =
777819
response.self_signing_keys.get(user_id).unwrap().deserialize_as().unwrap();
778820

779-
ReadOnlyUserIdentity::new(master_key.try_into().unwrap(), self_signing.try_into().unwrap())
780-
.unwrap()
821+
ReadOnlyUserIdentity::new(
822+
master_key.try_into().unwrap(),
823+
self_signing.try_into().unwrap(),
824+
true,
825+
)
826+
.unwrap()
781827
}
782828
}
783829

0 commit comments

Comments
 (0)