tests: Assert room key rotated on leave event after being offline
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
This commit is contained in:
@@ -37,6 +37,7 @@ use matrix_sdk_ui::{
|
||||
},
|
||||
};
|
||||
use similar_asserts::assert_eq;
|
||||
use tempfile::tempdir;
|
||||
use tracing::{Instrument, info};
|
||||
|
||||
use crate::{
|
||||
@@ -1236,6 +1237,146 @@ async fn test_history_share_on_invite_room_key_rotation() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A variant of the above test that verifies the room key is rotated on member
|
||||
/// leave even when the client shuts down for a short while, potentially
|
||||
/// resulting in state deltas appearing in the `/sync` response, as opposed to
|
||||
/// `timeline`.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||
async fn test_history_share_on_invite_room_key_rotation_with_shutdown() -> Result<()> {
|
||||
let alice_span = tracing::info_span!("alice");
|
||||
let bob_span = tracing::info_span!("bob");
|
||||
let charlie_span = tracing::info_span!("charlie");
|
||||
|
||||
let alice =
|
||||
create_encryption_enabled_client("alice", false).instrument(alice_span.clone()).await?;
|
||||
|
||||
let bob_sqlite_dir = tempdir()?;
|
||||
let bob = SyncTokenAwareClient::new(
|
||||
TestClientBuilder::new("bob")
|
||||
.use_sqlite_dir(bob_sqlite_dir.path())
|
||||
.encryption_settings(EncryptionSettings {
|
||||
auto_enable_cross_signing: true,
|
||||
..Default::default()
|
||||
})
|
||||
.enable_share_history_on_invite(true)
|
||||
.build()
|
||||
.await?,
|
||||
);
|
||||
bob.encryption().wait_for_e2ee_initialization_tasks().await;
|
||||
|
||||
let charlie = create_encryption_enabled_client("charlie", false)
|
||||
.await
|
||||
.expect("Failed to create Charlie's client");
|
||||
|
||||
// 1. Alice creates a room with `shared` history visibility and invites Bob.
|
||||
let alice_room = alice
|
||||
.create_room(assign!(CreateRoomRequest::new(), {
|
||||
preset: Some(RoomPreset::PublicChat),
|
||||
}))
|
||||
.instrument(alice_span.clone())
|
||||
.await?;
|
||||
alice_room.enable_encryption().instrument(alice_span.clone()).await?;
|
||||
|
||||
alice_room.invite_user_by_id(bob.user_id().unwrap()).instrument(alice_span.clone()).await?;
|
||||
|
||||
bob.sync_once().instrument(bob_span.clone()).await?;
|
||||
let bob_room = bob.join_room_by_id(alice_room.room_id()).instrument(bob_span.clone()).await?;
|
||||
|
||||
alice.sync_once().instrument(alice_span.clone()).await?;
|
||||
|
||||
// 2. Bob sends M1, which Charlie should be able to read later as Alice will
|
||||
// send them a key bundle.
|
||||
let event_id_a = bob_room
|
||||
.send(RoomMessageEventContent::text_plain("Charlie is cool!"))
|
||||
.into_future()
|
||||
.instrument(bob_span.clone())
|
||||
.await?
|
||||
.response
|
||||
.event_id;
|
||||
|
||||
// Store the session ID for later comparison.
|
||||
let event_m1 = bob_room.event(&event_id_a, None).instrument(bob_span.clone()).await?;
|
||||
let event_m1_session_id = event_m1
|
||||
.encryption_info()
|
||||
.and_then(|info| info.session_id())
|
||||
.expect("Bob should be able to check the session ID of event M1");
|
||||
|
||||
// 4. Alice invites Charlie; Charlie joins and receives the keys for M1.
|
||||
alice.sync_once().instrument(alice_span.clone()).await?;
|
||||
alice_room.invite_user_by_id(charlie.user_id().unwrap()).instrument(alice_span.clone()).await?;
|
||||
|
||||
let sync_response = charlie.sync_once().instrument(charlie_span.clone()).await?;
|
||||
assert_received_room_key_bundle(sync_response);
|
||||
|
||||
let charlie_room =
|
||||
charlie.join_room_by_id(alice_room.room_id()).instrument(charlie_span.clone()).await?;
|
||||
|
||||
charlie.sync_once().instrument(charlie_span.clone()).await?;
|
||||
|
||||
// Sanity check: Charlie can decrypt message M1 via the bundle.
|
||||
let event_a = charlie_room.event(&event_id_a, None).instrument(charlie_span.clone()).await?;
|
||||
assert!(
|
||||
event_a.encryption_info().is_some(),
|
||||
"Charlie should be able to decrypt message M1 via the key bundle"
|
||||
);
|
||||
|
||||
// 5. Charlie leaves the room.
|
||||
charlie_room.leave().instrument(charlie_span.clone()).await?;
|
||||
|
||||
// 6. Bob starts back up, and syncs to learn about Charlie's departure, which
|
||||
// should trigger key rotation.
|
||||
let bob = SyncTokenAwareClient::new(
|
||||
TestClientBuilder::new("bob")
|
||||
.use_sqlite_dir(bob_sqlite_dir.path())
|
||||
.encryption_settings(EncryptionSettings {
|
||||
auto_enable_cross_signing: true,
|
||||
..Default::default()
|
||||
})
|
||||
.enable_share_history_on_invite(true)
|
||||
.duplicate(&bob)
|
||||
.instrument(bob_span.clone())
|
||||
.await?,
|
||||
);
|
||||
bob.encryption().wait_for_e2ee_initialization_tasks().await;
|
||||
bob.sync_once().instrument(bob_span.clone()).await?;
|
||||
|
||||
let bob_room = bob.join_room_by_id(alice_room.room_id()).instrument(bob_span.clone()).await?;
|
||||
|
||||
// 7. Bob sends M2. Because key rotation should have been performed, this should
|
||||
// be using a fresh session that hasn't been shared with Charlie.
|
||||
let event_id_b = bob_room
|
||||
.send(RoomMessageEventContent::text_plain("Charlie is mean!"))
|
||||
.into_future()
|
||||
.instrument(bob_span.clone())
|
||||
.await?
|
||||
.response
|
||||
.event_id;
|
||||
|
||||
// Ensure the two session IDs of M1 and M2 are different
|
||||
let event_b = bob_room.event(&event_id_b, None).instrument(bob_span.clone()).await?;
|
||||
let event_m2_session_id = event_b
|
||||
.encryption_info()
|
||||
.and_then(|info| info.session_id())
|
||||
.expect("Bob should be able to check the session ID of event M2");
|
||||
|
||||
assert_ne!(event_m1_session_id, event_m2_session_id, "Session was not rotated");
|
||||
|
||||
// 8. Charlie rejoins the room via ID.
|
||||
charlie.sync_once().instrument(charlie_span.clone()).await?;
|
||||
let charlie_room =
|
||||
charlie.join_room_by_id(alice_room.room_id()).instrument(charlie_span.clone()).await?;
|
||||
|
||||
// 9. Charlie attempts to decrypt M2. He should not be able to, because the
|
||||
// session was rotated after he left the room.
|
||||
let event_b = charlie_room.event(&event_id_b, None).instrument(charlie_span.clone()).await?;
|
||||
assert!(
|
||||
event_b.encryption_info().is_none(),
|
||||
"Charlie should not be able to decrypt message M2 after rejoining"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates a new encryption-enabled client with the given username and
|
||||
/// settings.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user