diff --git a/deltachat-rpc-client/tests/test_securejoin.py b/deltachat-rpc-client/tests/test_securejoin.py index d77108c59..dfb9f0f19 100644 --- a/deltachat-rpc-client/tests/test_securejoin.py +++ b/deltachat-rpc-client/tests/test_securejoin.py @@ -279,8 +279,10 @@ def test_verified_group_member_added_recovery(acfactory) -> None: ac1_contact_ac2 = ac1.create_contact(ac2) ac1_contact_ac3 = ac1.create_contact(ac3) ac1_contact_ac2_snapshot = ac1_contact_ac2.get_snapshot() - assert ac1_contact_ac2_snapshot.is_verified - assert ac1_contact_ac2_snapshot.verifier_id == ac1_contact_ac3.id + # Until we reset verifications and then send the _verified header, + # verification is not gossiped here: + assert not ac1_contact_ac2_snapshot.is_verified + assert ac1_contact_ac2_snapshot.verifier_id != ac1_contact_ac3.id def test_qr_join_chat_with_pending_bobstate_issue4894(acfactory): @@ -442,7 +444,9 @@ def test_gossip_verification(acfactory) -> None: # Group propagates verification using Autocrypt-Gossip header. carol_contact_alice_snapshot = carol_contact_alice.get_snapshot() - assert carol_contact_alice_snapshot.is_verified + # Until we reset verifications and then send the _verified header, + # verification is not gossiped here: + assert not carol_contact_alice_snapshot.is_verified logging.info("Bob creates a Securejoin group") bob_group_chat = bob.create_group("Securejoin Group") @@ -456,7 +460,9 @@ def test_gossip_verification(acfactory) -> None: # Securejoin propagates verification. carol_contact_alice_snapshot = carol_contact_alice.get_snapshot() - assert carol_contact_alice_snapshot.is_verified + # Until we reset verifications and then send the _verified header, + # verification is not gossiped here: + assert not carol_contact_alice_snapshot.is_verified def test_securejoin_after_contact_resetup(acfactory) -> None: diff --git a/python/tests/test_0_complex_or_slow.py b/python/tests/test_0_complex_or_slow.py index 3915e3653..5a370e0f2 100644 --- a/python/tests/test_0_complex_or_slow.py +++ b/python/tests/test_0_complex_or_slow.py @@ -116,7 +116,6 @@ class TestGroupStressTests: def test_qr_verified_group_and_chatting(acfactory, lp): ac1, ac2, ac3 = acfactory.get_online_accounts(3) - ac1_addr = ac1.get_self_contact().addr lp.sec("ac1: create verified-group QR, ac2 scans and joins") chat1 = ac1.create_group_chat("hello") qr = chat1.get_join_qr() @@ -174,7 +173,9 @@ def test_qr_verified_group_and_chatting(acfactory, lp): for ac2_contact in chat2.get_contacts(): if ac2_contact == ac2_ac1_contact or ac2_contact.id == dc.const.DC_CONTACT_ID_SELF: continue - assert ac2.get_self_contact().get_verifier(ac2_contact).addr == ac1_addr + # Until we reset verifications and then send the _verified header, + # verification is not gossiped here: + assert ac2.get_self_contact().get_verifier(ac2_contact) is None lp.sec("ac2: send message and let ac3 read it") chat2.send_text("hi") @@ -409,7 +410,9 @@ def test_verified_group_vs_delete_server_after(acfactory, tmp_path, lp): assert msg_in.text == "hi2" assert msg_in.chat == chat2_offl assert msg_in.get_sender_contact().addr == ac2.get_config("addr") - assert ac2_offl_ac1_contact.is_verified() + # Until we reset verifications and then send the _verified header, + # verification is not gossiped here: + assert not ac2_offl_ac1_contact.is_verified() def test_deleted_msgs_dont_reappear(acfactory): diff --git a/python/tests/test_1_online.py b/python/tests/test_1_online.py index 65b3ef46a..d8a687f32 100644 --- a/python/tests/test_1_online.py +++ b/python/tests/test_1_online.py @@ -1238,8 +1238,11 @@ def test_qr_email_capitalization(acfactory, lp): # ac1 should see both ac3 and ac2 as verified. assert len(ac1_chat.get_contacts()) == 3 + # Until we reset verifications and then send the _verified header, + # the verification of ac2 is not gossiped here: for contact in ac1_chat.get_contacts(): - assert contact.is_verified() + is_ac2 = contact.addr == ac2.get_config("addr") + assert contact.is_verified() != is_ac2 def test_set_get_contact_avatar(acfactory, data, lp): diff --git a/src/aheader.rs b/src/aheader.rs index db3ee389b..7f84ca0f4 100644 --- a/src/aheader.rs +++ b/src/aheader.rs @@ -61,9 +61,11 @@ impl fmt::Display for Aheader { if self.prefer_encrypt == EncryptPreference::Mutual { write!(fmt, " prefer-encrypt=mutual;")?; } - if self.verified { - write!(fmt, " _verified=1;")?; - } + // TODO After we reset all existing verifications, + // we want to start sending the _verified attribute + // if self.verified { + // write!(fmt, " _verified=1;")?; + // } // adds a whitespace every 78 characters, this allows // email crate to wrap the lines according to RFC 5322 @@ -282,8 +284,9 @@ mod tests { .contains("test@example.com") ); + // We don't send the _verified header yet: assert!( - format!( + !format!( "{}", Aheader { addr: "test@example.com".to_string(), diff --git a/src/mimefactory.rs b/src/mimefactory.rs index ac84bb4f1..330e99747 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -1352,6 +1352,43 @@ impl MimeFactory { let command = msg.param.get_cmd(); let mut placeholdertext = None; + let send_verified_headers = match chat.typ { + Chattype::Single => true, + Chattype::Group => true, + // Mailinglists and broadcast channels can actually never be verified: + Chattype::Mailinglist => false, + Chattype::OutBroadcast | Chattype::InBroadcast => false, + }; + + if send_verified_headers { + let was_protected: bool = context + .sql + .query_get_value("SELECT protected FROM chats WHERE id=?", (chat.id,)) + .await? + .unwrap_or_default(); + + if was_protected { + let unverified_member_exists = context + .sql + .exists( + "SELECT COUNT(*) + FROM contacts, chats_contacts + WHERE chats_contacts.contact_id=contacts.id AND chats_contacts.chat_id=? + AND contacts.id>9 + AND contacts.verifier=0", + (chat.id,), + ) + .await?; + + if !unverified_member_exists { + headers.push(( + "Chat-Verified", + mail_builder::headers::raw::Raw::new("1").into(), + )); + } + } + } + if chat.typ == Chattype::Group { // Send group ID unless it is an ad hoc group that has no ID. if !chat.grpid.is_empty() { diff --git a/src/receive_imf.rs b/src/receive_imf.rs index 0dbec21e7..5e6e18252 100644 --- a/src/receive_imf.rs +++ b/src/receive_imf.rs @@ -3554,10 +3554,16 @@ async fn mark_recipients_as_verified( mimeparser: &MimeMessage, ) -> Result<()> { let verifier_id = Some(from_id).filter(|&id| id != ContactId::SELF); + + // We don't yet send the _verified property in autocrypt headers. + // Until we do, we instead accept the Chat-Verified header as indication all contacts are verified. + // TODO: Ignore ChatVerified header once we reset existing verifications. + let chat_verified = mimeparser.get_header(HeaderDef::ChatVerified).is_some(); + for gossiped_key in mimeparser .gossiped_keys .values() - .filter(|gossiped_key| gossiped_key.verified) + .filter(|gossiped_key| gossiped_key.verified || chat_verified) { let fingerprint = gossiped_key.public_key.dc_fingerprint().hex(); let Some(to_id) = lookup_key_contact_by_fingerprint(context, &fingerprint).await? else { diff --git a/src/receive_imf/receive_imf_tests.rs b/src/receive_imf/receive_imf_tests.rs index fb250533d..c2d33a067 100644 --- a/src/receive_imf/receive_imf_tests.rs +++ b/src/receive_imf/receive_imf_tests.rs @@ -5133,6 +5133,7 @@ async fn test_dont_reverify_by_self_on_outgoing_msg() -> Result<()> { let fiona = &tcm.fiona().await; let bob_chat_id = chat::create_group(bob, "Group").await?; + bob.set_chat_protected(bob_chat_id).await; let qr = get_securejoin_qr(bob, Some(bob_chat_id)).await?; tcm.exec_securejoin_qr(fiona, bob, &qr).await; tcm.exec_securejoin_qr(a0, bob, &qr).await; diff --git a/src/test_utils.rs b/src/test_utils.rs index d7ca71156..fdbddddf7 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -1121,6 +1121,18 @@ impl TestContext { chat_id } + + /// Set the legacy `protected` column in the chats table to 1, + /// because for now, only these chats that were once protected can be used + /// to gossip verifications. + // TODO remove the next statement + // when we send the _verified header for all verified contacts + pub(crate) async fn set_chat_protected(self: &TestContext, chat_id: chat::ChatId) { + self.sql + .execute("UPDATE chats SET protected=1 WHERE id=?", (chat_id,)) + .await + .unwrap(); + } } impl Deref for TestContext { diff --git a/src/tests/verified_chats.rs b/src/tests/verified_chats.rs index 41807fff0..fd0c4b08b 100644 --- a/src/tests/verified_chats.rs +++ b/src/tests/verified_chats.rs @@ -97,6 +97,7 @@ async fn test_create_verified_oneonone_chat() -> Result<()> { let group_id = bob .create_group_with_members("Group with everyone", &[&alice, &fiona]) .await; + bob.set_chat_protected(group_id).await; assert_eq!( get_chat_msg(&bob, group_id, 0, 1).await.get_info_type(), SystemMessage::ChatE2ee @@ -735,6 +736,7 @@ async fn test_no_reverification() -> Result<()> { let alice_chat_id = alice .create_group_with_members("Group", &[bob, charlie, fiona]) .await; + alice.set_chat_protected(alice_chat_id).await; let alice_sent = alice.send_text(alice_chat_id, "Hi!").await; let bob_rcvd_msg = bob.recv_msg(&alice_sent).await; let bob_alice_id = bob_rcvd_msg.from_id; @@ -802,6 +804,7 @@ async fn test_no_direct_verification_via_bcc() -> Result<()> { mark_as_verified(alice, bob).await; let alice_chat_id = alice.create_chat_id(bob).await; + alice.set_chat_protected(alice_chat_id).await; let alice_sent_msg = alice.send_text(alice_chat_id, "Hello!").await; alice2.recv_msg(&alice_sent_msg).await;