use anyhow::Result; use pretty_assertions::assert_eq; use crate::chat::{self, add_contact_to_chat, Chat, ProtectionStatus}; use crate::chatlist::Chatlist; use crate::config::Config; use crate::constants::{Chattype, DC_GCL_FOR_FORWARDING}; use crate::contact::{Contact, ContactId, Origin}; use crate::message::Message; use crate::mimefactory::MimeFactory; use crate::mimeparser::SystemMessage; use crate::receive_imf::receive_imf; use crate::stock_str; use crate::test_utils::{get_chat_msg, mark_as_verified, TestContext, TestContextManager}; use crate::tools::SystemTime; use crate::{e2ee, message}; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_verified_oneonone_chat_broken_by_classical() { check_verified_oneonone_chat(true).await; } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_verified_oneonone_chat_broken_by_device_change() { check_verified_oneonone_chat(false).await; } async fn check_verified_oneonone_chat(broken_by_classical_email: bool) { let mut tcm = TestContextManager::new(); let alice = tcm.alice().await; let bob = tcm.bob().await; enable_verified_oneonone_chats(&[&alice, &bob]).await; tcm.execute_securejoin(&alice, &bob).await; assert_verified(&alice, &bob, ProtectionStatus::Protected).await; assert_verified(&bob, &alice, ProtectionStatus::Protected).await; if broken_by_classical_email { tcm.section("Bob uses a classical MUA to send a message to Alice"); receive_imf( &alice, b"Subject: Re: Message from alice\r\n\ From: \r\n\ To: \r\n\ Date: Mon, 12 Dec 3000 14:33:39 +0000\r\n\ Message-ID: \r\n\ \r\n\ Heyho!\r\n", false, ) .await .unwrap() .unwrap(); // Bob's contact is still verified, but the chat isn't marked as protected anymore let contact = alice.add_or_lookup_contact(&bob).await; assert_eq!(contact.is_verified(&alice).await.unwrap(), true); assert_verified(&alice, &bob, ProtectionStatus::ProtectionBroken).await; } else { tcm.section("Bob sets up another Delta Chat device"); let bob2 = TestContext::new().await; bob2.set_name("bob2"); bob2.configure_addr("bob@example.net").await; SystemTime::shift(std::time::Duration::from_secs(3600)); tcm.send_recv(&bob2, &alice, "Using another device now") .await; let contact = alice.add_or_lookup_contact(&bob).await; assert_eq!(contact.is_verified(&alice).await.unwrap(), false); assert_verified(&alice, &bob, ProtectionStatus::ProtectionBroken).await; } tcm.section("Bob sends another message from DC"); SystemTime::shift(std::time::Duration::from_secs(3600)); tcm.send_recv(&bob, &alice, "Using DC again").await; // Bob's chat is marked as verified again assert_verified(&alice, &bob, ProtectionStatus::Protected).await; } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_create_verified_oneonone_chat() -> Result<()> { let mut tcm = TestContextManager::new(); let alice = tcm.alice().await; let bob = tcm.bob().await; let fiona = tcm.fiona().await; enable_verified_oneonone_chats(&[&alice, &bob, &fiona]).await; tcm.execute_securejoin(&alice, &bob).await; tcm.execute_securejoin(&bob, &fiona).await; assert_verified(&alice, &bob, ProtectionStatus::Protected).await; assert_verified(&bob, &alice, ProtectionStatus::Protected).await; assert_verified(&bob, &fiona, ProtectionStatus::Protected).await; assert_verified(&fiona, &bob, ProtectionStatus::Protected).await; let group_id = bob .create_group_with_members( ProtectionStatus::Protected, "Group with everyone", &[&alice, &fiona], ) .await; assert_eq!( get_chat_msg(&bob, group_id, 0, 1).await.get_info_type(), SystemMessage::ChatProtectionEnabled ); { let sent = bob.send_text(group_id, "Heyho").await; alice.recv_msg(&sent).await; let msg = fiona.recv_msg(&sent).await; assert_eq!( get_chat_msg(&fiona, msg.chat_id, 0, 2) .await .get_info_type(), SystemMessage::ChatProtectionEnabled ); } // Alice and Fiona should now be verified because of gossip let alice_fiona_contact = alice.add_or_lookup_contact(&fiona).await; assert!(alice_fiona_contact.is_verified(&alice).await.unwrap(),); // Alice should have a hidden protected chat with Fiona { let chat = alice.get_chat(&fiona).await; assert!(chat.is_protected()); let msg = get_chat_msg(&alice, chat.id, 0, 1).await; let expected_text = stock_str::chat_protection_enabled(&alice).await; assert_eq!(msg.text, expected_text); } // Fiona should have a hidden protected chat with Alice { let chat = fiona.get_chat(&alice).await; assert!(chat.is_protected()); let msg0 = get_chat_msg(&fiona, chat.id, 0, 1).await; let expected_text = stock_str::chat_protection_enabled(&fiona).await; assert_eq!(msg0.text, expected_text); } tcm.section("Fiona reinstalls DC"); drop(fiona); let fiona_new = tcm.unconfigured().await; enable_verified_oneonone_chats(&[&fiona_new]).await; fiona_new.configure_addr("fiona@example.net").await; e2ee::ensure_secret_key_exists(&fiona_new).await?; tcm.send_recv(&fiona_new, &alice, "I have a new device") .await; // The chat should be and stay unprotected { let chat = alice.get_chat(&fiona_new).await; assert!(!chat.is_protected()); assert!(chat.is_protection_broken()); let msg1 = get_chat_msg(&alice, chat.id, 0, 3).await; assert_eq!(msg1.get_info_type(), SystemMessage::ChatProtectionEnabled); let msg2 = get_chat_msg(&alice, chat.id, 1, 3).await; assert_eq!(msg2.get_info_type(), SystemMessage::ChatProtectionDisabled); let msg2 = get_chat_msg(&alice, chat.id, 2, 3).await; assert_eq!(msg2.text, "I have a new device"); // After recreating the chat, it should still be unprotected chat.id.delete(&alice).await?; let chat = alice.create_chat(&fiona_new).await; assert!(!chat.is_protected()); assert!(!chat.is_protection_broken()); } Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_missing_peerstate_reexecute_securejoin() -> Result<()> { let mut tcm = TestContextManager::new(); let alice = &tcm.alice().await; let alice_addr = alice.get_config(Config::Addr).await?.unwrap(); let bob = &tcm.bob().await; enable_verified_oneonone_chats(&[alice, bob]).await; tcm.execute_securejoin(bob, alice).await; let chat = bob.get_chat(alice).await; assert!(chat.is_protected()); bob.sql .execute("DELETE FROM acpeerstates WHERE addr=?", (&alice_addr,)) .await?; tcm.execute_securejoin(bob, alice).await; let chat = bob.get_chat(alice).await; assert!(chat.is_protected()); assert!(!chat.is_protection_broken()); Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_create_unverified_oneonone_chat() -> Result<()> { let mut tcm = TestContextManager::new(); let alice = tcm.alice().await; let bob = tcm.bob().await; enable_verified_oneonone_chats(&[&alice, &bob]).await; // A chat with an unknown contact should be created unprotected let chat = alice.create_chat(&bob).await; assert!(!chat.is_protected()); assert!(!chat.is_protection_broken()); receive_imf( &alice, b"From: Bob \n\ To: alice@example.org\n\ Message-ID: <1234-2@example.org>\n\ \n\ hello\n", false, ) .await?; chat.id.delete(&alice).await.unwrap(); // Now Bob is a known contact, new chats should still be created unprotected let chat = alice.create_chat(&bob).await; assert!(!chat.is_protected()); assert!(!chat.is_protection_broken()); tcm.send_recv(&bob, &alice, "hi").await; chat.id.delete(&alice).await.unwrap(); // Now we have a public key, new chats should still be created unprotected let chat = alice.create_chat(&bob).await; assert!(!chat.is_protected()); assert!(!chat.is_protection_broken()); Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_degrade_verified_oneonone_chat() -> Result<()> { let mut tcm = TestContextManager::new(); let alice = tcm.alice().await; let bob = tcm.bob().await; enable_verified_oneonone_chats(&[&alice, &bob]).await; mark_as_verified(&alice, &bob).await; let alice_chat = alice.create_chat(&bob).await; assert!(alice_chat.is_protected()); receive_imf( &alice, b"From: Bob \n\ To: alice@example.org\n\ Message-ID: <1234-2@example.org>\n\ \n\ hello\n", false, ) .await?; let contact_id = Contact::lookup_id_by_addr(&alice, "bob@example.net", Origin::Hidden) .await? .unwrap(); let msg0 = get_chat_msg(&alice, alice_chat.id, 0, 3).await; let enabled = stock_str::chat_protection_enabled(&alice).await; assert_eq!(msg0.text, enabled); assert_eq!(msg0.param.get_cmd(), SystemMessage::ChatProtectionEnabled); let msg1 = get_chat_msg(&alice, alice_chat.id, 1, 3).await; let disabled = stock_str::chat_protection_disabled(&alice, contact_id).await; assert_eq!(msg1.text, disabled); assert_eq!(msg1.param.get_cmd(), SystemMessage::ChatProtectionDisabled); let msg2 = get_chat_msg(&alice, alice_chat.id, 2, 3).await; assert_eq!(msg2.text, "hello".to_string()); assert!(!msg2.is_system_message()); Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_verified_oneonone_chat_enable_disable() -> Result<()> { let mut tcm = TestContextManager::new(); let alice = tcm.alice().await; let bob = tcm.bob().await; enable_verified_oneonone_chats(&[&alice, &bob]).await; // Alice & Bob verify each other mark_as_verified(&alice, &bob).await; mark_as_verified(&bob, &alice).await; let chat = alice.create_chat(&bob).await; assert!(chat.is_protected()); for alice_accepts_breakage in [true, false] { SystemTime::shift(std::time::Duration::from_secs(300)); // Bob uses Thunderbird to send a message receive_imf( &alice, format!( "From: Bob \n\ To: alice@example.org\n\ Message-ID: <1234-2{alice_accepts_breakage}@example.org>\n\ \n\ Message from Thunderbird\n" ) .as_bytes(), false, ) .await?; let chat = alice.get_chat(&bob).await; assert!(!chat.is_protected()); assert!(chat.is_protection_broken()); if alice_accepts_breakage { tcm.section("Alice clicks 'Accept' on the input-bar-dialog"); chat.id.accept(&alice).await?; let chat = alice.get_chat(&bob).await; assert!(!chat.is_protected()); assert!(!chat.is_protection_broken()); } // Bob sends a message from DC again tcm.send_recv(&bob, &alice, "Hello from DC").await; let chat = alice.get_chat(&bob).await; assert!(chat.is_protected()); assert!(!chat.is_protection_broken()); } alice .golden_test_chat(chat.id, "test_verified_oneonone_chat_enable_disable") .await; Ok(()) } /// Messages with old timestamps are difficult for verified chats: /// - They must not be sorted over a protection-changed info message. /// That's what `test_old_message_2` tests /// - If they change the protection, then they must not be sorted over existing other messages, /// because then the protection-changed info message would also be above these existing messages. /// That's what `test_old_message_3` tests. /// /// `test_old_message_1` tests the case where both the old and the new message /// change verification #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_old_message_1() -> Result<()> { let mut tcm = TestContextManager::new(); let alice = tcm.alice().await; let bob = tcm.bob().await; enable_verified_oneonone_chats(&[&alice, &bob]).await; mark_as_verified(&alice, &bob).await; let chat = alice.create_chat(&bob).await; // This creates a protection-changed info message assert!(chat.is_protected()); // This creates protection-changed info message #2; // even though the date is old, info message and email must be sorted below the original info message. receive_imf( &alice, b"From: Bob \n\ To: alice@example.org\n\ Message-ID: <1234-2-3@example.org>\n\ Date: Sat, 07 Dec 2019 19:00:27 +0000\n\ \n\ Message from Thunderbird\n", true, ) .await?; alice.golden_test_chat(chat.id, "test_old_message_1").await; Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_old_message_2() -> Result<()> { let mut tcm = TestContextManager::new(); let alice = tcm.alice().await; let bob = tcm.bob().await; enable_verified_oneonone_chats(&[&alice, &bob]).await; mark_as_verified(&alice, &bob).await; // This creates protection-changed info message #1: let chat = alice.create_chat(&bob).await; assert!(chat.is_protected()); let protection_msg = alice.get_last_msg().await; assert_eq!( protection_msg.param.get_cmd(), SystemMessage::ChatProtectionEnabled ); // This creates protection-changed info message #2 with `timestamp_sort` greater by 1. let first_email = receive_imf( &alice, b"From: Bob \n\ To: alice@example.org\n\ Message-ID: <1234-2-3@example.org>\n\ Date: Sun, 08 Dec 2019 19:00:27 +0000\n\ \n\ Somewhat old message\n", false, ) .await? .unwrap(); // Both messages will get the same timestamp, so this one will be sorted under the previous one // even though it has an older timestamp. let second_email = receive_imf( &alice, b"From: Bob \n\ To: alice@example.org\n\ Message-ID: <2319-2-3@example.org>\n\ Date: Sat, 07 Dec 2019 19:00:27 +0000\n\ \n\ Even older message, that must NOT be shown before the info message\n", true, ) .await? .unwrap(); assert_eq!(first_email.sort_timestamp, second_email.sort_timestamp); assert_eq!( first_email.sort_timestamp, protection_msg.timestamp_sort + 1 ); alice.golden_test_chat(chat.id, "test_old_message_2").await; Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_old_message_3() -> Result<()> { let mut tcm = TestContextManager::new(); let alice = tcm.alice().await; let bob = tcm.bob().await; enable_verified_oneonone_chats(&[&alice, &bob]).await; mark_as_verified(&alice, &bob).await; mark_as_verified(&bob, &alice).await; tcm.send_recv_accept(&bob, &alice, "Heyho from my verified device!") .await; // This unverified message must not be sorted over the message sent in the previous line: receive_imf( &alice, b"From: Bob \n\ To: alice@example.org\n\ Message-ID: <1234-2-3@example.org>\n\ Date: Sat, 07 Dec 2019 19:00:27 +0000\n\ \n\ Old, unverified message\n", true, ) .await?; alice .golden_test_chat(alice.get_chat(&bob).await.id, "test_old_message_3") .await; Ok(()) } /// Alice is offline for some time. /// When she comes online, first her inbox is synced and then her sentbox. /// This test tests that the messages are still in the right order. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_old_message_4() -> Result<()> { let alice = TestContext::new_alice().await; let msg_incoming = receive_imf( &alice, b"From: Bob \n\ To: alice@example.org\n\ Message-ID: <1234-2-3@example.org>\n\ Date: Sun, 08 Dec 2019 19:00:27 +0000\n\ \n\ Thanks, Alice!\n", true, ) .await? .unwrap(); let msg_sent = receive_imf( &alice, b"From: alice@example.org\n\ To: Bob \n\ Message-ID: <1234-2-4@example.org>\n\ Date: Sat, 07 Dec 2019 19:00:27 +0000\n\ \n\ Happy birthday, Bob!\n", true, ) .await? .unwrap(); // The "Happy birthday" message should be shown first, and then the "Thanks" message assert!(msg_sent.sort_timestamp < msg_incoming.sort_timestamp); Ok(()) } /// Alice is offline for some time. /// When they come online, first their sentbox is synced and then their inbox. /// This test tests that the messages are still in the right order. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_old_message_5() -> Result<()> { let alice = TestContext::new_alice().await; let msg_sent = receive_imf( &alice, b"From: alice@example.org\n\ To: Bob \n\ Message-ID: <1234-2-4@example.org>\n\ Date: Sat, 07 Dec 2019 19:00:27 +0000\n\ \n\ Happy birthday, Bob!\n", true, ) .await? .unwrap(); let msg_incoming = receive_imf( &alice, b"From: Bob \n\ To: alice@example.org\n\ Message-ID: <1234-2-3@example.org>\n\ Date: Sun, 07 Dec 2019 19:00:26 +0000\n\ \n\ Happy birthday to me, Alice!\n", false, ) .await? .unwrap(); assert!(msg_sent.sort_timestamp == msg_incoming.sort_timestamp); alice .golden_test_chat(msg_sent.chat_id, "test_old_message_5") .await; Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_mdn_doesnt_disable_verification() -> Result<()> { let mut tcm = TestContextManager::new(); let alice = tcm.alice().await; let bob = tcm.bob().await; enable_verified_oneonone_chats(&[&alice, &bob]).await; bob.set_config_bool(Config::MdnsEnabled, true).await?; // Alice & Bob verify each other mark_as_verified(&alice, &bob).await; mark_as_verified(&bob, &alice).await; let rcvd = tcm.send_recv_accept(&alice, &bob, "Heyho").await; message::markseen_msgs(&bob, vec![rcvd.id]).await?; let mimefactory = MimeFactory::from_mdn(&bob, rcvd.from_id, rcvd.rfc724_mid, vec![]).await?; let rendered_msg = mimefactory.render(&bob).await?; let body = rendered_msg.message; receive_imf(&alice, body.as_bytes(), false).await.unwrap(); assert_verified(&alice, &bob, ProtectionStatus::Protected).await; Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_outgoing_mua_msg() -> Result<()> { let mut tcm = TestContextManager::new(); let alice = tcm.alice().await; let bob = tcm.bob().await; enable_verified_oneonone_chats(&[&alice, &bob]).await; mark_as_verified(&alice, &bob).await; mark_as_verified(&bob, &alice).await; tcm.send_recv_accept(&bob, &alice, "Heyho from DC").await; assert_verified(&alice, &bob, ProtectionStatus::Protected).await; let sent = receive_imf( &alice, b"From: alice@example.org\n\ To: bob@example.net\n\ \n\ One classical MUA message", false, ) .await? .unwrap(); tcm.send_recv(&alice, &bob, "Sending with DC again").await; alice .golden_test_chat(sent.chat_id, "test_outgoing_mua_msg") .await; Ok(()) } /// If Bob answers unencrypted from another address with a classical MUA, /// the message is under some circumstances still assigned to the original /// chat (see lookup_chat_by_reply()); this is meant to make aliases /// work nicely. /// However, if the original chat is verified, the unencrypted message /// must NOT be assigned to it (it would be replaced by an error /// message in the verified chat, so, this would just be a usability issue, /// not a security issue). #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_reply() -> Result<()> { for verified in [false, true] { let mut tcm = TestContextManager::new(); let alice = tcm.alice().await; let bob = tcm.bob().await; enable_verified_oneonone_chats(&[&alice, &bob]).await; if verified { mark_as_verified(&alice, &bob).await; mark_as_verified(&bob, &alice).await; } tcm.send_recv_accept(&bob, &alice, "Heyho from DC").await; let encrypted_msg = tcm.send_recv(&alice, &bob, "Heyho back").await; let unencrypted_msg = receive_imf( &alice, format!( "From: bob@someotherdomain.org\n\ To: some-alias-forwarding-to-alice@example.org\n\ In-Reply-To: {}\n\ \n\ Weird reply", encrypted_msg.rfc724_mid ) .as_bytes(), false, ) .await? .unwrap(); let unencrypted_msg = Message::load_from_db(&alice, unencrypted_msg.msg_ids[0]).await?; assert_eq!(unencrypted_msg.text, "Weird reply"); if verified { assert_ne!(unencrypted_msg.chat_id, encrypted_msg.chat_id); } else { assert_eq!(unencrypted_msg.chat_id, encrypted_msg.chat_id); } } Ok(()) } /// Regression test for the following bug: /// /// - Scan your chat partner's QR Code /// - They change devices /// - They send you a message /// - Without accepting the encryption downgrade, scan your chat partner's QR Code again /// /// -> The re-verification fails. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_break_protection_then_verify_again() -> Result<()> { let mut tcm = TestContextManager::new(); let alice = tcm.alice().await; let bob = tcm.bob().await; enable_verified_oneonone_chats(&[&alice, &bob]).await; // Cave: Bob can't write a message to Alice here. // If he did, alice would increase his peerstate's last_seen timestamp. // Then, after Bob reinstalls DC, alice's `if message_time > last_seen*` // checks would return false (there are many checks of this form in peerstate.rs). // Therefore, during the securejoin, Alice wouldn't accept the new key // and reject the securejoin. mark_as_verified(&alice, &bob).await; mark_as_verified(&bob, &alice).await; alice.create_chat(&bob).await; assert_verified(&alice, &bob, ProtectionStatus::Protected).await; let chats = Chatlist::try_load(&alice, DC_GCL_FOR_FORWARDING, None, None).await?; assert_eq!(chats.len(), 1); tcm.section("Bob reinstalls DC"); drop(bob); let bob_new = tcm.unconfigured().await; enable_verified_oneonone_chats(&[&bob_new]).await; bob_new.configure_addr("bob@example.net").await; e2ee::ensure_secret_key_exists(&bob_new).await?; tcm.send_recv(&bob_new, &alice, "I have a new device").await; let contact = alice.add_or_lookup_contact(&bob_new).await; assert_eq!( contact.is_verified(&alice).await.unwrap(), // Bob sent a message with a new key, so he most likely doesn't have // the old key anymore. This means that Alice's device should show // him as unverified: false ); let chat = alice.get_chat(&bob_new).await; assert_eq!(chat.is_protected(), false); assert_eq!(chat.is_protection_broken(), true); let chats = Chatlist::try_load(&alice, DC_GCL_FOR_FORWARDING, None, None).await?; assert_eq!(chats.len(), 1); { let alice_bob_chat = alice.get_chat(&bob_new).await; assert!(!alice_bob_chat.can_send(&alice).await?); // Alice's UI should still be able to save a draft, which Alice started to type right when she got Bob's message: let mut msg = Message::new_text("Draftttt".to_string()); alice_bob_chat.id.set_draft(&alice, Some(&mut msg)).await?; assert_eq!( alice_bob_chat.id.get_draft(&alice).await?.unwrap().text, "Draftttt" ); } tcm.execute_securejoin(&alice, &bob_new).await; assert_verified(&alice, &bob_new, ProtectionStatus::Protected).await; Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_message_from_old_dc_setup() -> Result<()> { let mut tcm = TestContextManager::new(); let alice = &tcm.alice().await; let bob_old = &tcm.unconfigured().await; enable_verified_oneonone_chats(&[alice, bob_old]).await; mark_as_verified(bob_old, alice).await; bob_old.configure_addr("bob@example.net").await; let chat = bob_old.create_chat(alice).await; let sent_old = bob_old .send_text(chat.id, "Soon i'll have a new device") .await; SystemTime::shift(std::time::Duration::from_secs(3600)); tcm.section("Bob reinstalls DC"); let bob = &tcm.bob().await; enable_verified_oneonone_chats(&[bob]).await; mark_as_verified(alice, bob).await; mark_as_verified(bob, alice).await; tcm.send_recv(bob, alice, "Now i have it!").await; assert_verified(alice, bob, ProtectionStatus::Protected).await; let msg = alice.recv_msg(&sent_old).await; assert!(!msg.get_showpadlock()); let contact = alice.add_or_lookup_contact(bob).await; // The outdated Bob's Autocrypt header isn't applied, so the verification preserves. assert!(contact.is_verified(alice).await.unwrap()); let chat = alice.get_chat(bob).await; assert!(chat.is_protected()); assert_eq!(chat.is_protection_broken(), false); let protection_msg = alice.get_last_msg().await; assert_eq!( protection_msg.param.get_cmd(), SystemMessage::ChatProtectionEnabled ); assert!(protection_msg.timestamp_sort >= msg.timestamp_rcvd); alice .golden_test_chat(msg.chat_id, "verified_chats_message_from_old_dc_setup") .await; Ok(()) } /// Regression test for the following bug: /// /// - Scan your chat partner's QR Code /// - They change devices /// - Scan their QR code again /// /// -> The re-verification fails. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_verify_then_verify_again() -> Result<()> { let mut tcm = TestContextManager::new(); let alice = tcm.alice().await; let bob = tcm.bob().await; enable_verified_oneonone_chats(&[&alice, &bob]).await; mark_as_verified(&alice, &bob).await; mark_as_verified(&bob, &alice).await; alice.create_chat(&bob).await; assert_verified(&alice, &bob, ProtectionStatus::Protected).await; tcm.section("Bob reinstalls DC"); drop(bob); let bob_new = tcm.unconfigured().await; enable_verified_oneonone_chats(&[&bob_new]).await; bob_new.configure_addr("bob@example.net").await; e2ee::ensure_secret_key_exists(&bob_new).await?; tcm.execute_securejoin(&bob_new, &alice).await; assert_verified(&alice, &bob_new, ProtectionStatus::Protected).await; Ok(()) } /// Regression test: /// - Verify a contact /// - The contact stops using DC and sends a message from a classical MUA instead /// - Delete the 1:1 chat /// - Create a 1:1 chat /// - Check that the created chat is not marked as protected #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_create_oneonone_chat_with_former_verified_contact() -> Result<()> { let mut tcm = TestContextManager::new(); let alice = tcm.alice().await; let bob = tcm.bob().await; enable_verified_oneonone_chats(&[&alice]).await; mark_as_verified(&alice, &bob).await; receive_imf( &alice, b"Subject: Message from bob\r\n\ From: \r\n\ To: \r\n\ Date: Mon, 12 Dec 2022 14:33:39 +0000\r\n\ Message-ID: \r\n\ \r\n\ Heyho!\r\n", false, ) .await .unwrap() .unwrap(); alice.create_chat(&bob).await; assert_verified(&alice, &bob, ProtectionStatus::Unprotected).await; Ok(()) } /// Tests that on the second device of a protected group creator the first message is /// `SystemMessage::ChatProtectionEnabled` and the second one is the message populating the group. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_create_protected_grp_multidev() -> Result<()> { let mut tcm = TestContextManager::new(); let alice = &tcm.alice().await; let alice1 = &tcm.alice().await; let group_id = alice .create_group_with_members(ProtectionStatus::Protected, "Group", &[]) .await; assert_eq!( get_chat_msg(alice, group_id, 0, 1).await.get_info_type(), SystemMessage::ChatProtectionEnabled ); let sent = alice.send_text(group_id, "Hey").await; // This time shift is necessary to reproduce the bug when the original message is sorted over // the "protection enabled" message so that these messages have different timestamps. SystemTime::shift(std::time::Duration::from_secs(3600)); let msg = alice1.recv_msg(&sent).await; let group1 = Chat::load_from_db(alice1, msg.chat_id).await?; assert_eq!(group1.get_type(), Chattype::Group); assert!(group1.is_protected()); assert_eq!( chat::get_chat_contacts(alice1, group1.id).await?, vec![ContactId::SELF] ); assert_eq!( get_chat_msg(alice1, group1.id, 0, 2).await.get_info_type(), SystemMessage::ChatProtectionEnabled ); assert_eq!(get_chat_msg(alice1, group1.id, 1, 2).await.id, msg.id); Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_verified_member_added_reordering() -> Result<()> { let mut tcm = TestContextManager::new(); let alice = &tcm.alice().await; let bob = &tcm.bob().await; let fiona = &tcm.fiona().await; enable_verified_oneonone_chats(&[alice, bob, fiona]).await; let alice_fiona_contact_id = Contact::create(alice, "Fiona", "fiona@example.net").await?; // Bob and Fiona scan Alice's QR code. tcm.execute_securejoin(bob, alice).await; tcm.execute_securejoin(fiona, alice).await; // Alice creates protected group with Bob. let alice_chat_id = alice .create_group_with_members(ProtectionStatus::Protected, "Group", &[bob]) .await; let alice_sent_group_promotion = alice.send_text(alice_chat_id, "I created a group").await; let msg = bob.recv_msg(&alice_sent_group_promotion).await; let bob_chat_id = msg.chat_id; // Alice adds Fiona. add_contact_to_chat(alice, alice_chat_id, alice_fiona_contact_id).await?; let alice_sent_member_added = alice.pop_sent_msg().await; // Bob receives "Alice added Fiona" message. bob.recv_msg(&alice_sent_member_added).await; // Bob sends a message to the group. let bob_sent_message = bob.send_text(bob_chat_id, "Hi").await; // Fiona receives message from Bob before receiving // "Member added" message. let fiona_received_message = fiona.recv_msg(&bob_sent_message).await; assert_eq!( fiona_received_message.get_text(), "[The message was sent with non-verified encryption. See 'Info' for more details]" ); Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_no_unencrypted_name_if_encrypted() -> Result<()> { let mut tcm = TestContextManager::new(); for verified in [false, true] { let alice = tcm.alice().await; let bob = tcm.bob().await; bob.set_config(Config::Displayname, Some("Bob Smith")) .await?; if verified { enable_verified_oneonone_chats(&[&bob]).await; mark_as_verified(&bob, &alice).await; } else { tcm.send_recv_accept(&alice, &bob, "hi").await; } let chat_id = bob.create_chat(&alice).await.id; let msg = &bob.send_text(chat_id, "hi").await; assert_eq!(msg.payload.contains("Bob Smith"), false); assert!(msg.payload.contains("BEGIN PGP MESSAGE")); let msg = alice.recv_msg(msg).await; let contact = Contact::get_by_id(&alice, msg.from_id).await?; assert_eq!(Contact::get_display_name(&contact), "Bob Smith"); } Ok(()) } // ============== Helper Functions ============== async fn assert_verified(this: &TestContext, other: &TestContext, protected: ProtectionStatus) { if protected != ProtectionStatus::ProtectionBroken { let contact = this.add_or_lookup_contact(other).await; assert_eq!(contact.is_verified(this).await.unwrap(), true); } let chat = this.get_chat(other).await; let (expect_protected, expect_broken) = match protected { ProtectionStatus::Unprotected => (false, false), ProtectionStatus::Protected => (true, false), ProtectionStatus::ProtectionBroken => (false, true), }; assert_eq!(chat.is_protected(), expect_protected); assert_eq!(chat.is_protection_broken(), expect_broken); } async fn enable_verified_oneonone_chats(test_contexts: &[&TestContext]) { for t in test_contexts { t.set_config_bool(Config::VerifiedOneOnOneChats, true) .await .unwrap() } }