Skip to content

fix: ignore verification error if the chat is not protected yet #6883

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions python/tests/test_0_complex_or_slow.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,9 +434,10 @@ def test_verified_group_vs_delete_server_after(acfactory, tmp_path, lp):
- First device of the user downloads "member added" from the group.
- First device removes "member added" from the server.
- Some new messages are sent to the group.
- Second device comes online, receives these new messages. The result is a verified group with unverified members.
- Second device comes online, receives these new messages.
The result is an unverified group with unverified members.
- First device re-gossips Autocrypt keys to the group.
- Now the seconds device has all members verified.
- Now the second device has all members and group verified.
"""
ac1, ac2 = acfactory.get_online_accounts(2)
acfactory.remove_preconfigured_keys()
Expand Down Expand Up @@ -474,12 +475,12 @@ def test_verified_group_vs_delete_server_after(acfactory, tmp_path, lp):
ac2_offl.start_io()
msg_in = ac2_offl._evtracker.wait_next_incoming_message()
assert not msg_in.is_system_message()
assert msg_in.text.startswith("[The message was sent with non-verified encryption")
assert msg_in.text == "hi"
ac2_offl_ac1_contact = msg_in.get_sender_contact()
assert ac2_offl_ac1_contact.addr == ac1.get_config("addr")
assert not ac2_offl_ac1_contact.is_verified()
chat2_offl = msg_in.chat
assert chat2_offl.is_protected()
assert not chat2_offl.is_protected()

lp.sec("ac2: sending message re-gossiping Autocrypt keys")
chat2.send_text("hi2")
Expand All @@ -500,6 +501,7 @@ 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 msg_in.chat.is_protected()
assert ac2_offl_ac1_contact.is_verified()


Expand Down
28 changes: 18 additions & 10 deletions src/receive_imf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2138,11 +2138,14 @@ async fn create_group(

let create_protected = if mime_parser.get_header(HeaderDef::ChatVerified).is_some() {
if let VerifiedEncryption::NotVerified(err) = verified_encryption {
warn!(context, "Verification problem: {err:#}.");
let s = format!("{err}. See 'Info' for more details");
mime_parser.replace_msg_by_error(&s);
warn!(
context,
"Creating unprotected group because of the verification problem: {err:#}."
);
ProtectionStatus::Unprotected
} else {
ProtectionStatus::Protected
}
ProtectionStatus::Protected
} else {
ProtectionStatus::Unprotected
};
Expand Down Expand Up @@ -2381,12 +2384,17 @@ async fn apply_group_changes(

if mime_parser.get_header(HeaderDef::ChatVerified).is_some() {
if let VerifiedEncryption::NotVerified(err) = verified_encryption {
warn!(context, "Verification problem: {err:#}.");
let s = format!("{err}. See 'Info' for more details");
mime_parser.replace_msg_by_error(&s);
}

if !chat.is_protected() {
if chat.is_protected() {
warn!(context, "Verification problem: {err:#}.");
let s = format!("{err}. See 'Info' for more details");
mime_parser.replace_msg_by_error(&s);
} else {
warn!(
context,
"Not marking chat {chat_id} as protected due to verification problem: {err:#}."
);
}
} else if !chat.is_protected() {
chat_id
.set_protection(
context,
Expand Down
91 changes: 85 additions & 6 deletions src/tests/verified_chats.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
use anyhow::Result;
use pretty_assertions::assert_eq;

use crate::chat::resend_msgs;
use crate::chat::{
self, add_contact_to_chat, remove_contact_from_chat, send_msg, 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::message::{Message, Viewtype};
use crate::mimefactory::MimeFactory;
use crate::mimeparser::SystemMessage;
use crate::receive_imf::receive_imf;
use crate::securejoin::{get_securejoin_qr, join_securejoin};
use crate::stock_str;
use crate::test_utils::{get_chat_msg, mark_as_verified, TestContext, TestContextManager};
use crate::tools::SystemTime;
Expand Down Expand Up @@ -916,12 +918,18 @@ async fn test_verified_member_added_reordering() -> Result<()> {
let bob_sent_message = bob.send_text(bob_chat_id, "Hi").await;

// Fiona receives message from Bob before receiving
// "Member added" message.
// "Member added" message, so unverified group is created.
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]"
);
let fiona_chat = Chat::load_from_db(fiona, fiona_received_message.chat_id).await?;

assert_eq!(fiona_received_message.get_text(), "Hi");
assert_eq!(fiona_chat.is_protected(), false);

// Fiona receives late "Member added" message
// and the chat becomes protected.
fiona.recv_msg(&alice_sent_member_added).await;
let fiona_chat = Chat::load_from_db(fiona, fiona_received_message.chat_id).await?;
assert_eq!(fiona_chat.is_protected(), true);

Ok(())
}
Expand Down Expand Up @@ -1016,6 +1024,77 @@ async fn test_verified_lost_member_added() -> Result<()> {
Ok(())
}

/// Tests handling of resent .xdc arriving before "Member added"
/// in a verified group
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_verified_chat_editor_reordering() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = &tcm.alice().await;
let bob = &tcm.bob().await;
let charlie = &tcm.charlie().await;

tcm.execute_securejoin(alice, bob).await;

tcm.section("Alice creates a protected group with Bob");
let alice_chat_id = alice
.create_group_with_members(ProtectionStatus::Protected, "Group", &[bob])
.await;
let alice_sent = alice.send_text(alice_chat_id, "Hi!").await;
let bob_chat_id = bob.recv_msg(&alice_sent).await.chat_id;

tcm.section("Bob sends an .xdc to the chat");

let mut webxdc_instance = Message::new(Viewtype::File);
webxdc_instance.set_file_from_bytes(
bob,
"editor.xdc",
include_bytes!("../../test-data/webxdc/minimal.xdc"),
None,
)?;
let bob_instance_msg_id = send_msg(bob, bob_chat_id, &mut webxdc_instance).await?;
let bob_sent_instance_msg = bob.pop_sent_msg().await;

tcm.section("Alice receives .xdc");
alice.recv_msg(&bob_sent_instance_msg).await;

tcm.section("Alice creates a group QR code");
let qr = get_securejoin_qr(alice, Some(alice_chat_id)).await.unwrap();

tcm.section("Charlie scans SecureJoin QR code");
join_securejoin(charlie, &qr).await?;

// vg-request
alice.recv_msg_trash(&charlie.pop_sent_msg().await).await;

// vg-auth-required
charlie.recv_msg_trash(&alice.pop_sent_msg().await).await;

// vg-request-with-auth
alice.recv_msg_trash(&charlie.pop_sent_msg().await).await;

// vg-member-added
let sent_member_added_msg = alice.pop_sent_msg().await;

tcm.section("Bob receives member added message");
bob.recv_msg(&sent_member_added_msg).await;

tcm.section("Bob resends webxdc");
resend_msgs(bob, &[bob_instance_msg_id]).await?;

tcm.section("Charlie receives resent webxdc before member added");
let charlie_received_xdc = charlie.recv_msg(&bob.pop_sent_msg().await).await;

// The message should not be replaced with
// "The message was sent with non-verified encryption." text
// just because it was reordered.
assert_eq!(charlie_received_xdc.viewtype, Viewtype::Webxdc);

tcm.section("Charlie receives member added message");
charlie.recv_msg(&sent_member_added_msg).await;

Ok(())
}

// ============== Helper Functions ==============

async fn assert_verified(this: &TestContext, other: &TestContext, protected: ProtectionStatus) {
Expand Down