Files
matrix-rust-sdk/testing/matrix-sdk-integration-testing/src/tests/e2ee.rs
Richard van der Hoff 07cfe3da94 timeline: make TimelineEvent fields private
... and add accessors instead.

Give `TimelineEvent` the same treatment we just gave `SyncTimelineEvent`: make
the fields private, and use accessors where we previously used direct access.
2024-10-09 15:19:26 +01:00

969 lines
35 KiB
Rust

use std::sync::{Arc, Mutex};
use anyhow::Result;
use assert_matches::assert_matches;
use assert_matches2::assert_let;
use assign::assign;
use matrix_sdk::{
crypto::{format_emojis, SasState},
encryption::{
backups::BackupState,
recovery::RecoveryState,
verification::{
QrVerificationData, QrVerificationState, Verification, VerificationRequestState,
},
BackupDownloadStrategy, EncryptionSettings, LocalTrust,
},
ruma::{
api::client::room::create_room::v3::Request as CreateRoomRequest,
events::{
key::verification::{request::ToDeviceKeyVerificationRequestEvent, VerificationMethod},
room::message::{
MessageType, OriginalSyncRoomMessageEvent, RoomMessageEventContent,
SyncRoomMessageEvent,
},
OriginalSyncMessageLikeEvent,
},
},
Client,
};
use similar_asserts::assert_eq;
use tracing::warn;
use crate::helpers::{SyncTokenAwareClient, TestClientBuilder};
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn test_mutual_sas_verification() -> Result<()> {
let encryption_settings =
EncryptionSettings { auto_enable_cross_signing: true, ..Default::default() };
let alice = SyncTokenAwareClient::new(
TestClientBuilder::new("alice")
.use_sqlite()
.encryption_settings(encryption_settings)
.build()
.await?,
);
let bob = SyncTokenAwareClient::new(
TestClientBuilder::new("bob")
.use_sqlite()
.encryption_settings(encryption_settings)
.build()
.await?,
);
warn!("alice's device: {}", alice.device_id().unwrap());
warn!("bob's device: {}", bob.device_id().unwrap());
let invite = vec![bob.user_id().unwrap().to_owned()];
let request = assign!(CreateRoomRequest::new(), {
invite,
is_direct: true,
});
let alice_room = alice.create_room(request).await?;
alice_room.enable_encryption().await?;
let room_id = alice_room.room_id();
warn!("alice has created and enabled encryption in the room");
bob.sync_once().await?;
bob.get_room(room_id).unwrap().join().await?;
alice.sync_once().await?;
warn!("alice and bob are both aware of each other in the e2ee room");
// Bob adds the verification listeners.
let bob_verification_request = Arc::new(Mutex::new(None));
{
let bvr = bob_verification_request.clone();
bob.add_event_handler(
|ev: ToDeviceKeyVerificationRequestEvent, client: Client| async move {
let request = client
.encryption()
.get_verification_request(&ev.sender, &ev.content.transaction_id)
.await
.expect("Request object wasn't created");
*bvr.lock().unwrap() = Some(request);
},
);
let bvr = bob_verification_request.clone();
bob.add_event_handler(|ev: OriginalSyncRoomMessageEvent, client: Client| async move {
if let MessageType::VerificationRequest(_) = &ev.content.msgtype {
let request = client
.encryption()
.get_verification_request(&ev.sender, &ev.event_id)
.await
.expect("Request object wasn't created");
*bvr.lock().unwrap() = Some(request);
}
});
}
warn!("bob has set up verification listeners");
let alice_bob_identity = alice
.encryption()
.get_user_identity(bob.user_id().unwrap())
.await?
.expect("alice knows bob's identity");
warn!("alice has found bob's identity");
let alice_verification_request = alice_bob_identity.request_verification().await?;
assert!(!alice_verification_request.is_passive());
assert!(alice_verification_request.we_started());
assert_eq!(alice_verification_request.room_id(), Some(room_id));
warn!("alice has started verification");
bob.sync_once().await?;
let bob_verification_request = bob_verification_request
.lock()
.unwrap()
.take()
.expect("bob received a verification request");
warn!("bob has received the verification request");
assert_eq!(bob_verification_request.room_id(), Some(room_id));
assert!(!bob_verification_request.is_done());
assert!(!bob_verification_request.is_cancelled());
assert!(bob_verification_request.cancel_info().is_none());
assert!(!bob_verification_request.is_ready());
assert!(!bob_verification_request.is_passive());
assert!(!bob_verification_request.we_started());
assert_eq!(bob_verification_request.own_user_id(), bob.user_id().unwrap());
assert_eq!(bob_verification_request.other_user_id(), alice.user_id().unwrap());
assert!(!bob_verification_request.is_self_verification());
assert_matches!(bob_verification_request.state(), VerificationRequestState::Requested { .. });
let _flow_id = bob_verification_request.flow_id();
// Bob notifies Alice he accepts the verification process.
bob_verification_request.accept().await.unwrap();
let our_methods = assert_matches!(bob_verification_request.state(), VerificationRequestState::Ready { our_methods, .. } => our_methods);
assert!(bob_verification_request.is_ready());
assert_eq!(bob_verification_request.their_supported_methods(), Some(our_methods));
warn!("bob has accepted the verification request");
// Alice receives the accept, and moves to the ready state.
assert_matches!(alice_verification_request.state(), VerificationRequestState::Created { .. });
alice.sync_once().await.unwrap();
assert_matches!(alice_verification_request.state(), VerificationRequestState::Ready { .. });
let alice_sas =
alice_verification_request.start_sas().await?.expect("must have a sas verification");
assert_eq!(alice_sas.own_user_id(), alice.user_id().unwrap());
assert_eq!(alice_sas.other_user_id(), bob.user_id().unwrap());
assert_eq!(alice_sas.room_id(), Some(room_id));
assert!(alice_sas.started_from_request());
assert!(!alice_sas.is_self_verification());
assert!(alice_sas.we_started());
assert!(!alice_sas.is_done());
assert!(!alice_sas.is_cancelled());
assert!(alice_sas.cancel_info().is_none());
assert!(!alice_sas.supports_emoji());
assert!(alice_sas.emoji().is_none());
assert!(alice_sas.decimals().is_none());
bob.sync_once().await?;
let bob_verification = assert_matches!(bob_verification_request.state(), VerificationRequestState::Transitioned { verification } => verification);
assert!(!bob_verification.is_done());
assert!(!bob_verification.is_cancelled());
assert!(!bob_verification.is_self_verification());
assert!(!bob_verification.we_started());
assert!(bob_verification.cancel_info().is_none());
assert_eq!(bob_verification.own_user_id(), bob.user_id().unwrap());
assert_eq!(bob_verification.other_user_id(), alice.user_id().unwrap());
assert_eq!(bob_verification.room_id(), Some(room_id));
let bob_sas = bob_verification.sas().unwrap();
assert_matches!(alice_sas.state(), SasState::Created { .. });
assert_matches!(bob_sas.state(), SasState::Started { .. });
bob_sas.accept().await?;
assert_matches!(bob_sas.state(), SasState::Accepted { .. });
alice.sync_once().await?;
assert_matches!(alice_sas.state(), SasState::Accepted { .. });
assert!(alice_sas.supports_emoji());
assert!(!alice_sas.can_be_presented());
assert!(!bob_sas.can_be_presented());
// Let a little crypto messages dance happen.
alice.sync_once().await?;
bob.sync_once().await?;
assert_matches!(alice_sas.state(), SasState::Accepted { .. });
assert_matches!(bob_sas.state(), SasState::KeysExchanged { .. });
alice.sync_once().await?;
let alice_emojis =
assert_matches!(alice_sas.state(), SasState::KeysExchanged { emojis, .. } => emojis)
.expect("alice received emojis");
let bob_emojis =
assert_matches!(bob_sas.state(), SasState::KeysExchanged { emojis, .. } => emojis)
.expect("bob received emojis");
assert!(alice_sas.can_be_presented());
assert!(bob_sas.can_be_presented());
assert_eq!(format_emojis(alice_emojis.emojis), format_emojis(bob_emojis.emojis));
alice_sas.confirm().await?;
bob_sas.confirm().await?;
assert_matches!(alice_sas.state(), SasState::Confirmed);
assert_matches!(bob_sas.state(), SasState::Confirmed);
// Moar crypto dancing.
alice.sync_once().await?;
bob.sync_once().await?;
assert_matches!(alice_sas.state(), SasState::Confirmed);
assert_matches!(bob_sas.state(), SasState::Done { .. });
alice.sync_once().await?;
assert_matches!(alice_sas.state(), SasState::Done { .. });
assert_matches!(bob_sas.state(), SasState::Done { .. });
// Wait for remote echos for verification status requests.
alice.sync_once().await?;
bob.sync_once().await?;
assert!(!bob_verification_request.is_cancelled());
assert!(!alice_verification_request.is_cancelled());
assert!(bob_verification_request.is_done());
assert!(alice_verification_request.is_done());
assert!(bob_sas.is_done());
assert!(alice_sas.is_done());
// Both users appear as verified to each other.
let alice_bob_ident =
alice.encryption().get_user_identity(bob.user_id().unwrap()).await?.unwrap();
assert!(alice_bob_ident.is_verified());
let bob_alice_ident =
bob.encryption().get_user_identity(alice.user_id().unwrap()).await?.unwrap();
assert!(bob_alice_ident.is_verified());
// Both user devices appear as verified to the other user.
let alice_bob_device = alice_sas.other_device();
assert_eq!(alice_bob_device.user_id(), bob.user_id().unwrap());
assert_eq!(alice_bob_device.device_id(), bob.device_id().unwrap());
assert_eq!(alice_bob_device.local_trust_state(), LocalTrust::Unset);
let alice_bob_device = alice
.encryption()
.get_device(bob.user_id().unwrap(), bob.device_id().unwrap())
.await?
.unwrap();
assert!(alice_bob_device.is_verified());
assert_eq!(alice_bob_device.local_trust_state(), LocalTrust::Verified);
assert!(alice_bob_device.is_locally_trusted());
assert!(!alice_bob_device.is_blacklisted());
let bob_alice_device = bob_sas.other_device();
assert_eq!(bob_alice_device.user_id(), alice.user_id().unwrap());
assert_eq!(bob_alice_device.device_id(), alice.device_id().unwrap());
assert_eq!(bob_alice_device.local_trust_state(), LocalTrust::Unset);
let bob_alice_device = bob
.encryption()
.get_device(alice.user_id().unwrap(), alice.device_id().unwrap())
.await?
.unwrap();
assert!(bob_alice_device.is_verified());
assert_eq!(bob_alice_device.local_trust_state(), LocalTrust::Verified);
assert!(bob_alice_device.is_locally_trusted());
assert!(!bob_alice_device.is_blacklisted());
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn test_mutual_qrcode_verification() -> Result<()> {
let encryption_settings =
EncryptionSettings { auto_enable_cross_signing: true, ..Default::default() };
let alice = SyncTokenAwareClient::new(
TestClientBuilder::new("alice")
.use_sqlite()
.encryption_settings(encryption_settings)
.build()
.await?,
);
let bob = SyncTokenAwareClient::new(
TestClientBuilder::new("bob")
.use_sqlite()
.encryption_settings(encryption_settings)
.build()
.await?,
);
warn!("alice's device: {}", alice.device_id().unwrap());
warn!("bob's device: {}", bob.device_id().unwrap());
let invite = vec![bob.user_id().unwrap().to_owned()];
let request = assign!(CreateRoomRequest::new(), {
invite,
is_direct: true,
});
let alice_room = alice.create_room(request).await?;
alice_room.enable_encryption().await?;
let room_id = alice_room.room_id();
warn!("alice has created and enabled encryption in the room");
bob.sync_once().await?;
bob.get_room(room_id).unwrap().join().await?;
alice.sync_once().await?;
warn!("alice and bob are both aware of each other in the e2ee room");
// Bob adds the verification listeners.
let bob_verification_request = Arc::new(Mutex::new(None));
{
let bvr = bob_verification_request.clone();
bob.add_event_handler(
|ev: ToDeviceKeyVerificationRequestEvent, client: Client| async move {
let request = client
.encryption()
.get_verification_request(&ev.sender, &ev.content.transaction_id)
.await
.expect("Request object wasn't created");
*bvr.lock().unwrap() = Some(request);
},
);
let bvr = bob_verification_request.clone();
bob.add_event_handler(|ev: OriginalSyncRoomMessageEvent, client: Client| async move {
if let MessageType::VerificationRequest(_) = &ev.content.msgtype {
let request = client
.encryption()
.get_verification_request(&ev.sender, &ev.event_id)
.await
.expect("Request object wasn't created");
*bvr.lock().unwrap() = Some(request);
}
});
}
warn!("bob has set up verification listeners");
let alice_bob_identity = alice
.encryption()
.get_user_identity(bob.user_id().unwrap())
.await?
.expect("alice knows bob's identity");
warn!("alice has found bob's identity");
let alice_verification_request = alice_bob_identity
.request_verification_with_methods(vec![
VerificationMethod::SasV1,
VerificationMethod::QrCodeScanV1,
VerificationMethod::QrCodeShowV1,
VerificationMethod::ReciprocateV1,
])
.await?;
assert!(!alice_verification_request.is_passive());
assert!(alice_verification_request.we_started());
assert_eq!(alice_verification_request.room_id(), Some(room_id));
warn!("alice has started verification");
bob.sync_once().await?;
let bob_verification_request = bob_verification_request
.lock()
.unwrap()
.take()
.expect("bob received a verification request");
warn!("bob has received the verification request");
assert_eq!(bob_verification_request.room_id(), Some(room_id));
assert!(!bob_verification_request.is_done());
assert!(!bob_verification_request.is_cancelled());
assert!(bob_verification_request.cancel_info().is_none());
assert!(!bob_verification_request.is_ready());
assert!(!bob_verification_request.is_passive());
assert!(!bob_verification_request.we_started());
assert_eq!(bob_verification_request.own_user_id(), bob.user_id().unwrap());
assert_eq!(bob_verification_request.other_user_id(), alice.user_id().unwrap());
assert!(!bob_verification_request.is_self_verification());
assert_matches!(bob_verification_request.state(), VerificationRequestState::Requested { .. });
let _flow_id = bob_verification_request.flow_id();
// Bob notifies Alice he accepts the verification process.
bob_verification_request.accept().await.unwrap();
assert_matches!(bob_verification_request.state(), VerificationRequestState::Ready { .. });
assert!(bob_verification_request.is_ready());
warn!("bob has accepted the verification request");
// Alice receives the accept, and moves to the ready state.
assert_matches!(alice_verification_request.state(), VerificationRequestState::Created { .. });
alice.sync_once().await.unwrap();
assert_matches!(alice_verification_request.state(), VerificationRequestState::Ready { .. });
let bob_qr =
bob_verification_request.generate_qr_code().await?.expect("must have a qr verification");
assert_matches!(bob_qr.state(), QrVerificationState::Started);
warn!("bob has generated the QR code");
let bob_verification = assert_matches!(bob_verification_request.state(), VerificationRequestState::Transitioned { verification } => verification);
assert!(!bob_verification.is_done());
assert!(!bob_verification.is_cancelled());
assert!(!bob_verification.is_self_verification());
assert!(!bob_verification.we_started());
assert!(bob_verification.cancel_info().is_none());
assert_eq!(bob_verification.own_user_id(), bob.user_id().unwrap());
assert_eq!(bob_verification.other_user_id(), alice.user_id().unwrap());
assert_eq!(bob_verification.room_id(), Some(room_id));
let qr_code_bytes = bob_qr.to_bytes()?;
let alice_qr = alice_verification_request
.scan_qr_code(QrVerificationData::from_bytes(qr_code_bytes)?)
.await?
.expect("must have a qr verification");
warn!("alice has scanned the QR code");
assert_eq!(alice_qr.own_user_id(), alice.user_id().unwrap());
assert_eq!(alice_qr.other_user_id(), bob.user_id().unwrap());
assert_eq!(alice_qr.room_id(), Some(room_id));
assert!(!alice_qr.is_self_verification());
assert!(alice_qr.we_started());
assert!(!alice_qr.is_done());
assert!(!alice_qr.is_cancelled());
assert!(alice_qr.cancel_info().is_none());
bob.sync_once().await?;
assert_matches!(alice_qr.state(), QrVerificationState::Reciprocated);
assert_matches!(bob_qr.state(), QrVerificationState::Scanned);
bob_qr.confirm().await?;
warn!("bob has confirmed the QR code scanning");
alice.sync_once().await?;
assert_matches!(bob_qr.state(), QrVerificationState::Confirmed);
assert_matches!(alice_qr.state(), QrVerificationState::Done { .. });
// Crypto dancing.
alice.sync_once().await?;
bob.sync_once().await?;
assert_matches!(bob_qr.state(), QrVerificationState::Done { .. });
assert_matches!(alice_qr.state(), QrVerificationState::Done { .. });
// Wait for remote echos for verification status requests.
alice.sync_once().await?;
bob.sync_once().await?;
assert!(!bob_verification_request.is_cancelled());
assert!(!alice_verification_request.is_cancelled());
assert!(bob_verification_request.is_done());
assert!(alice_verification_request.is_done());
assert!(bob_qr.is_done());
assert!(alice_qr.is_done());
// Both users appear as verified to each other.
let alice_bob_ident =
alice.encryption().get_user_identity(bob.user_id().unwrap()).await?.unwrap();
assert!(alice_bob_ident.is_verified());
let bob_alice_ident =
bob.encryption().get_user_identity(alice.user_id().unwrap()).await?.unwrap();
assert!(bob_alice_ident.is_verified());
// Both user devices appear as verified to the other user.
let alice_bob_device = alice_qr.other_device();
assert_eq!(alice_bob_device.user_id(), bob.user_id().unwrap());
assert_eq!(alice_bob_device.device_id(), bob.device_id().unwrap());
assert_eq!(alice_bob_device.local_trust_state(), LocalTrust::Unset);
let alice_bob_device = alice
.encryption()
.get_device(bob.user_id().unwrap(), bob.device_id().unwrap())
.await?
.unwrap();
assert!(alice_bob_device.is_verified());
assert!(!alice_bob_device.is_blacklisted());
let bob_alice_device = bob_qr.other_device();
assert_eq!(bob_alice_device.user_id(), alice.user_id().unwrap());
assert_eq!(bob_alice_device.device_id(), alice.device_id().unwrap());
let bob_alice_device = bob
.encryption()
.get_device(alice.user_id().unwrap(), alice.device_id().unwrap())
.await?
.unwrap();
assert!(bob_alice_device.is_verified());
assert!(!bob_alice_device.is_blacklisted());
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn test_encryption_missing_member_keys() -> Result<()> {
let alice =
SyncTokenAwareClient::new(TestClientBuilder::new("alice").use_sqlite().build().await?);
let bob = SyncTokenAwareClient::new(TestClientBuilder::new("bob").use_sqlite().build().await?);
let invite = vec![bob.user_id().unwrap().to_owned()];
let request = assign!(CreateRoomRequest::new(), {
invite,
is_direct: true,
});
let alice_room = alice.create_room(request).await?;
alice_room.enable_encryption().await?;
alice.sync_once().await?;
warn!("alice has created and enabled encryption in the room");
bob.sync_once().await?;
bob.get_room(alice_room.room_id()).unwrap().join().await?;
warn!("bob has joined");
// New person joins the room.
let carl =
SyncTokenAwareClient::new(TestClientBuilder::new("carl").use_sqlite().build().await?);
alice_room.invite_user_by_id(carl.user_id().unwrap()).await?;
carl.sync_once().await?;
carl.get_room(alice_room.room_id()).unwrap().join().await?;
carl.sync_once().await?;
warn!("carl has joined");
// Bob sends message WITHOUT syncing.
warn!("bob sends message...");
let bob_room = bob.get_room(alice_room.room_id()).unwrap();
let message = "Hello world!";
let bob_message_content = Arc::new(Mutex::new(message));
bob_room.send(RoomMessageEventContent::text_plain(message)).await?;
warn!("bob is done sending the message");
// Alice was in the room when Bob sent the message, so they'll see it.
let alice_found_event = Arc::new(Mutex::new(false));
{
warn!("alice is looking for the decrypted message");
let found_event_handler = alice_found_event.clone();
let bob_message_content = bob_message_content.clone();
alice.add_event_handler(move |event: SyncRoomMessageEvent| async move {
warn!("Found a message \\o/ {event:?}");
let MessageType::Text(text_content) = &event.as_original().unwrap().content.msgtype
else {
return;
};
if text_content.body == *bob_message_content.lock().unwrap() {
*found_event_handler.lock().unwrap() = true;
}
});
alice.sync_once().await?;
let found = *alice_found_event.lock().unwrap();
assert!(found, "event has not been found for alice");
}
// Bob wasn't aware of Carl's presence (no sync() since Carl joined), so Carl
// won't see it first.
let carl_found_event = Arc::new(Mutex::new(false));
{
warn!("carl is looking for the decrypted message");
let found_event_handler = carl_found_event.clone();
let bob_message_content = bob_message_content.clone();
carl.add_event_handler(move |event: SyncRoomMessageEvent| async move {
warn!("Found a message \\o/ {event:?}");
let MessageType::Text(text_content) = &event.as_original().unwrap().content.msgtype
else {
return;
};
if text_content.body == *bob_message_content.lock().unwrap() {
*found_event_handler.lock().unwrap() = true;
}
});
carl.sync_once().await?;
let found = *carl_found_event.lock().unwrap();
assert!(!found, "event has been unexpectedly found for carl");
}
// Now Bob syncs, thus notices the presence of Carl.
bob.sync_once().await?;
warn!("bob sends another message...");
let bob_room = bob.get_room(alice_room.room_id()).unwrap();
let message = "Wassup";
*bob_message_content.lock().unwrap() = message;
bob_room.send(RoomMessageEventContent::text_plain(message)).await?;
warn!("bob is done sending another message");
{
*alice_found_event.lock().unwrap() = false;
alice.sync_once().await?;
let found = *alice_found_event.lock().unwrap();
assert!(found, "second message has not been found for alice");
}
{
*carl_found_event.lock().unwrap() = false;
carl.sync_once().await?;
let found = *carl_found_event.lock().unwrap();
assert!(found, "second message has not been found for carl");
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn test_failed_members_response() -> Result<()> {
let alice =
SyncTokenAwareClient::new(TestClientBuilder::new("alice").use_sqlite().build().await?);
let bob = SyncTokenAwareClient::new(TestClientBuilder::new("bob").use_sqlite().build().await?);
let invite = vec![bob.user_id().unwrap().to_owned()];
let request = assign!(CreateRoomRequest::new(), {
invite,
is_direct: true,
});
let alice_room = alice.create_room(request).await?;
alice_room.enable_encryption().await?;
alice.sync_once().await?;
warn!("alice has created and enabled encryption in the room");
bob.sync_once().await?;
// Cause a failure of a sync_members request by asking for members before
// joining. Since this is a private DM room, it will fail with a 401, as
// we're not authorized to look at state history.
let result = bob.get_room(alice_room.room_id()).unwrap().sync_members().await;
assert!(result.is_err());
bob.get_room(alice_room.room_id()).unwrap().join().await?;
warn!("bob has joined");
// Bob sends message WITHOUT syncing.
warn!("bob sends message...");
let bob_room = bob.get_room(alice_room.room_id()).unwrap();
let message = "Hello world!";
let bob_message_content = Arc::new(Mutex::new(message));
bob_room.send(RoomMessageEventContent::text_plain(message)).await?;
warn!("bob is done sending the message");
// Alice sees the message.
let alice_found_event = Arc::new(Mutex::new(false));
warn!("alice is looking for the decrypted message");
let found_event_handler = alice_found_event.clone();
let bob_message_content = bob_message_content.clone();
alice.add_event_handler(move |event: SyncRoomMessageEvent| async move {
warn!("Found a message \\o/ {event:?}");
let MessageType::Text(text_content) = &event.as_original().unwrap().content.msgtype else {
return;
};
if text_content.body == *bob_message_content.lock().unwrap() {
*found_event_handler.lock().unwrap() = true;
}
});
alice.sync_once().await?;
let found = *alice_found_event.lock().unwrap();
assert!(found, "event has not been found for alice");
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn test_backup_enable_new_user() -> Result<()> {
let encryption_settings =
EncryptionSettings { auto_enable_backups: true, ..Default::default() };
let alice = SyncTokenAwareClient::new(
TestClientBuilder::new("alice")
.use_sqlite()
.encryption_settings(encryption_settings)
.build()
.await?,
);
alice.encryption().wait_for_e2ee_initialization_tasks().await;
assert!(
alice.encryption().backups().are_enabled().await,
"Backups should have been enabled automatically."
);
assert_eq!(
alice.encryption().backups().state(),
BackupState::Enabled,
"The backup state should now be BackupState::Enabled"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn test_cross_signing_bootstrap() -> Result<()> {
let encryption_settings =
EncryptionSettings { auto_enable_cross_signing: true, ..Default::default() };
let alice = SyncTokenAwareClient::new(
TestClientBuilder::new("alice")
.use_sqlite()
.encryption_settings(encryption_settings)
.build()
.await?,
);
alice.encryption().wait_for_e2ee_initialization_tasks().await;
let status = alice
.encryption()
.cross_signing_status()
.await
.expect("We should know our cross-signing status by now.");
assert!(status.is_complete(), "We should have all private cross-signing keys available.");
// We need to sync to get the remote echo of the upload of the device keys.
alice.sync_once().await?;
let own_device = alice.encryption().get_own_device().await?.unwrap();
assert!(
own_device.is_cross_signed_by_owner(),
"Since we bootstrapped cross-signing, our own device should have been \
signed by the cross-signing keys."
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn test_secret_gossip_after_interactive_verification() -> Result<()> {
let encryption_settings = EncryptionSettings {
auto_enable_cross_signing: true,
auto_enable_backups: true,
backup_download_strategy: BackupDownloadStrategy::OneShot,
};
let first_client = SyncTokenAwareClient::new(
TestClientBuilder::new("alice_gossip_test")
.encryption_settings(encryption_settings)
.build()
.await?,
);
let user_id = first_client.user_id().expect("We should have access to the user id now");
let request = CreateRoomRequest::new();
let room_first_client = first_client.create_room(request).await?;
room_first_client.enable_encryption().await?;
first_client.sync_once().await?;
first_client.encryption().recovery().enable().await?;
assert_eq!(first_client.encryption().recovery().state(), RecoveryState::Enabled);
let response = room_first_client
.send(RoomMessageEventContent::text_plain("It's a secret to everybody"))
.await?;
let event_id = response.event_id;
warn!("The first device has created and enabled encryption in the room and sent an event");
let second_client = SyncTokenAwareClient::new(
TestClientBuilder::with_exact_username(user_id.localpart().to_owned())
.encryption_settings(encryption_settings)
.build()
.await?,
);
second_client.encryption().wait_for_e2ee_initialization_tasks().await;
// The second client doesn't have access to the backup, nor is recovery in the
// enabled state.
assert_eq!(second_client.encryption().recovery().state(), RecoveryState::Incomplete);
assert_eq!(second_client.encryption().backups().state(), BackupState::Unknown);
warn!("The first device: {}", first_client.device_id().unwrap());
warn!("The second device: {}", second_client.device_id().unwrap());
assert_ne!(first_client.device_id().unwrap(), second_client.device_id().unwrap());
second_client.sync_once().await?;
let seconds_first_device = second_client
.encryption()
.get_device(second_client.user_id().unwrap(), first_client.device_id().unwrap())
.await?
.expect("We should have access to the first device once we have synced");
// The first client is not verified from the point of view of the second client.
assert!(!seconds_first_device.is_verified());
// Let's send out a request to verify with each other.
let seconds_verification_request = seconds_first_device.request_verification().await?;
let flow_id = seconds_verification_request.flow_id();
first_client.sync_once().await?;
let firsts_verification_request = first_client
.encryption()
.get_verification_request(user_id, flow_id)
.await
.expect("The verification should have been requested");
assert_matches!(seconds_verification_request.state(), VerificationRequestState::Created { .. });
assert_matches!(
firsts_verification_request.state(),
VerificationRequestState::Requested { .. }
);
warn!("The first device is accepting the verification request");
firsts_verification_request.accept().await?;
first_client.sync_once().await?;
assert_matches!(firsts_verification_request.state(), VerificationRequestState::Ready { .. });
let firsts_sas = firsts_verification_request
.start_sas()
.await?
.expect("We should be able to start the SAS verification");
second_client.sync_once().await?;
assert_let!(
VerificationRequestState::Transitioned { verification: Verification::SasV1(seconds_sas) } =
seconds_verification_request.state()
);
seconds_sas.accept().await?;
// We need to sync a couple of times so the clients exchange the shared secret.
first_client.sync_once().await?;
second_client.sync_once().await?;
first_client.sync_once().await?;
assert_eq!(
firsts_sas.emoji().expect("The firsts sas should be presentable"),
seconds_sas.emoji().expect("The seconds sas should be presentable"),
"The emojis should match"
);
// Confirm that the emojis match.
firsts_sas.confirm().await?;
seconds_sas.confirm().await?;
// After both sides confirm, we need a couple more syncs to exchange the final
// verification events.
second_client.sync_once().await?;
first_client.sync_once().await?;
second_client.sync_once().await?;
// And we're done, the verification dance is completed.
assert!(seconds_sas.is_done());
assert!(firsts_sas.is_done());
let second_device = second_client.encryption().get_own_device().await?.unwrap();
// The first device has signed the second one.
assert!(second_device.is_cross_signed_by_owner());
assert!(
!second_client.encryption().cross_signing_status().await.unwrap().is_complete(),
"We should not have received our cross-signing keys yet."
);
assert_eq!(
second_client.encryption().backups().state(),
BackupState::Unknown,
"The backup should not have been enabled yet."
);
assert_eq!(
second_client.encryption().recovery().state(),
RecoveryState::Incomplete,
"The recovery state should be in the Incomplete state, since we have not yet received all secrets"
);
// We still need to gossip the secrets from one device to the other, the first
// device syncs to receive the gossip requests, then the second device syncs
// to receive the secrets.
first_client.sync_once().await?;
warn!("The second client is doing its final sync");
second_client.sync_once().await?;
assert!(
second_client.encryption().cross_signing_status().await.unwrap().is_complete(),
"We should have received all the cross-signing keys from the first device"
);
assert_eq!(
second_client.encryption().backups().state(),
BackupState::Enabled,
"We should have enabled the backup after we received the backup key from the first device"
);
assert_eq!(
second_client.encryption().recovery().state(),
RecoveryState::Enabled,
"The recovery state should be in the Enabled state, since we have all the secrets"
);
// Let's now check if we can decrypt the event that was sent before our
// device was created.
let room = second_client
.get_room(room_first_client.room_id())
.expect("The second client should know about the room as well");
let timeline_event = room.event(&event_id, None).await?;
timeline_event
.encryption_info()
.expect("The event should have been encrypted and successfully decrypted.");
let event: OriginalSyncMessageLikeEvent<RoomMessageEventContent> =
timeline_event.raw().deserialize_as()?;
let message = event.content.msgtype;
assert_let!(MessageType::Text(message) = message);
assert_eq!(
message.body, "It's a secret to everybody",
"The decrypted message should match the text we encrypted."
);
Ok(())
}