Skip to content

Commit 11a27af

Browse files
committed
tests: Add integration test for key rotation on member leave.
Signed-off-by: Skye Elliot <[email protected]>
1 parent 8a26249 commit 11a27af

File tree

1 file changed

+132
-0
lines changed

1 file changed

+132
-0
lines changed

testing/matrix-sdk-integration-testing/src/tests/e2ee/shared_history.rs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ use matrix_sdk_ui::{
3737
},
3838
};
3939
use similar_asserts::assert_eq;
40+
use tempfile::tempdir;
4041
use tracing::{Instrument, info};
4142

4243
use crate::{
@@ -1127,6 +1128,137 @@ async fn test_history_share_on_invite_respects_history_visibility() -> Result<()
11271128
Ok(())
11281129
}
11291130

1131+
/// Test that when a user leaves a room that uses history sharing, the room key
1132+
/// is rotated so they cannot decrypt future messages.
1133+
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
1134+
async fn test_history_sharing_room_key_rotation() -> Result<()> {
1135+
let alice_span = tracing::info_span!("alice");
1136+
let bob_span = tracing::info_span!("bob");
1137+
let charlie_span = tracing::info_span!("charlie");
1138+
1139+
let alice =
1140+
create_encryption_enabled_client("alice", false).instrument(alice_span.clone()).await?;
1141+
let bob = create_encryption_enabled_client("bob", false).instrument(bob_span.clone()).await?;
1142+
1143+
// We create Charlie's client manually to allow us to recreate their client
1144+
// later.
1145+
let charlie_sqlite_dir = tempdir().unwrap();
1146+
let charlie = SyncTokenAwareClient::new(
1147+
TestClientBuilder::new("charlie")
1148+
.use_sqlite_dir(charlie_sqlite_dir.path())
1149+
.encryption_settings(EncryptionSettings {
1150+
auto_enable_cross_signing: true,
1151+
..Default::default()
1152+
})
1153+
.enable_share_history_on_invite(true)
1154+
.build()
1155+
.await?,
1156+
);
1157+
1158+
// 1. Alice creates a room with `shared` history visibility and invites Bob.
1159+
let alice_room = alice
1160+
.create_room(assign!(CreateRoomRequest::new(), {
1161+
preset: Some(RoomPreset::PublicChat),
1162+
}))
1163+
.instrument(alice_span.clone())
1164+
.await?;
1165+
alice_room.enable_encryption().instrument(alice_span.clone()).await?;
1166+
1167+
// Allow regular users to send invites so Bob can invite Charlie.
1168+
alice.sync_once().instrument(alice_span.clone()).await?;
1169+
alice_room
1170+
.apply_power_level_changes(RoomPowerLevelChanges { invite: Some(0), ..Default::default() })
1171+
.instrument(alice_span.clone())
1172+
.await?;
1173+
1174+
alice_room.invite_user_by_id(bob.user_id().unwrap()).instrument(alice_span.clone()).await?;
1175+
1176+
bob.sync_once().instrument(bob_span.clone()).await?;
1177+
let bob_room = bob.join_room_by_id(alice_room.room_id()).instrument(bob_span.clone()).await?;
1178+
1179+
alice.sync_once().instrument(alice_span.clone()).await?;
1180+
1181+
// 2. Alice sends message A, which Charlie should be able to read later as Bob
1182+
// will send them a key bundle.
1183+
let event_id_a = alice_room
1184+
.send(RoomMessageEventContent::text_plain("Charlie is cool!"))
1185+
.into_future()
1186+
.instrument(alice_span.clone())
1187+
.await?
1188+
.response
1189+
.event_id;
1190+
1191+
// 3. Bob invites Charlie; Charlie joins and receives message A via the bundle.
1192+
bob.sync_once().instrument(bob_span.clone()).await?;
1193+
bob_room.invite_user_by_id(charlie.user_id().unwrap()).instrument(bob_span.clone()).await?;
1194+
1195+
let sync_response = charlie.sync_once().instrument(charlie_span.clone()).await?;
1196+
assert_received_room_key_bundle(sync_response);
1197+
1198+
let charlie_room =
1199+
charlie.join_room_by_id(alice_room.room_id()).instrument(charlie_span.clone()).await?;
1200+
1201+
charlie.sync_once().instrument(charlie_span.clone()).await?;
1202+
1203+
// Sanity check: Charlie can decrypt message A via the bundle.
1204+
let event_a = charlie_room.event(&event_id_a, None).instrument(charlie_span.clone()).await?;
1205+
assert!(
1206+
event_a.encryption_info().is_some(),
1207+
"Charlie should be able to decrypt message A via the key bundle"
1208+
);
1209+
1210+
// 4. Charlie leaves the room.
1211+
charlie_room.leave().instrument(charlie_span.clone()).await?;
1212+
1213+
// Alice syncs to learn about Charlie's departure, which should trigger key
1214+
// rotation.
1215+
alice.sync_once().instrument(alice_span.clone()).await?;
1216+
1217+
// 5. Alice sends message B. Because key rotation should have been performed,
1218+
// this should be using a fresh session that hasn't been shared with Charlie.
1219+
let event_id_b = alice_room
1220+
.send(RoomMessageEventContent::text_plain("Charlie is mean!"))
1221+
.into_future()
1222+
.instrument(alice_span.clone())
1223+
.await?
1224+
.response
1225+
.event_id;
1226+
1227+
// 6. We re-create Charlie, since we can't disable history sharing on an
1228+
// existing client.
1229+
let charlie = SyncTokenAwareClient::new(
1230+
TestClientBuilder::new("charlie")
1231+
.use_sqlite_dir(charlie_sqlite_dir.path())
1232+
.encryption_settings(EncryptionSettings {
1233+
auto_enable_cross_signing: true,
1234+
..Default::default()
1235+
})
1236+
// Explicitly disable history sharing so we test only against keys received
1237+
// in the first key bundle.
1238+
.enable_share_history_on_invite(false)
1239+
.duplicate(&*charlie)
1240+
.await?,
1241+
);
1242+
1243+
// 7. Bob re-invites Charlie to the room, who joins.
1244+
bob.sync_once().instrument(bob_span.clone()).await?;
1245+
bob_room.invite_user_by_id(charlie.user_id().unwrap()).instrument(bob_span.clone()).await?;
1246+
1247+
charlie.sync_once().instrument(charlie_span.clone()).await?;
1248+
let charlie_room =
1249+
charlie.join_room_by_id(alice_room.room_id()).instrument(charlie_span.clone()).await?;
1250+
1251+
// 8. Charlie attempts to decrypt message B. He should not be able to, because
1252+
// the session was rotated after he left the room.
1253+
let event_b = charlie_room.event(&event_id_b, None).instrument(charlie_span.clone()).await?;
1254+
assert!(
1255+
event_b.encryption_info().is_none(),
1256+
"Charlie should not be able to decrypt message B after being re-invited"
1257+
);
1258+
1259+
Ok(())
1260+
}
1261+
11301262
/// Creates a new encryption-enabled client with the given username and
11311263
/// settings.
11321264
///

0 commit comments

Comments
 (0)