diff --git a/deltachat-repl/src/main.rs b/deltachat-repl/src/main.rs index 484c97486..82734215d 100644 --- a/deltachat-repl/src/main.rs +++ b/deltachat-repl/src/main.rs @@ -173,7 +173,7 @@ const DB_COMMANDS: [&str; 10] = [ "housekeeping", ]; -const CHAT_COMMANDS: [&str; 39] = [ +const CHAT_COMMANDS: [&str; 38] = [ "listchats", "listarchived", "start-realtime", @@ -182,7 +182,6 @@ const CHAT_COMMANDS: [&str; 39] = [ "createchat", "creategroup", "createbroadcast", - "createprotected", "addmember", "removemember", "groupname", diff --git a/deltachat-rpc-client/tests/test_cross_core.py b/deltachat-rpc-client/tests/test_cross_core.py index 367119318..1353b69bd 100644 --- a/deltachat-rpc-client/tests/test_cross_core.py +++ b/deltachat-rpc-client/tests/test_cross_core.py @@ -25,13 +25,12 @@ def test_qr_setup_contact(acf, alice_and_remote_bob, version) -> None: remote_eval(f"bob.secure_join({qr_code!r})") alice.wait_for_securejoin_inviter_success() - # Test that Alice verified Bob's profile. alice_contact_bob_snapshot = alice_contact_bob.get_snapshot() - assert alice_contact_bob_snapshot.is_verified + assert alice_contact_bob_snapshot.e2ee_avail remote_eval("bob.wait_for_securejoin_joiner_success()") - # Test that Bob verified Alice's profile. + # The old core still marks Alice as verified, so the handshake is unchanged on the wire. assert remote_eval("bob_contact_alice.get_snapshot().is_verified") # Test that Bob can also scan a QR code @@ -56,13 +55,12 @@ def test_qr_setup_contact_multitransport(acf, alice_and_remote_bob, version) -> remote_eval(f"bob.secure_join({qr_code!r})") alice.wait_for_securejoin_inviter_success() - # Test that Alice verified Bob's profile. alice_contact_bob_snapshot = alice_contact_bob.get_snapshot() - assert alice_contact_bob_snapshot.is_verified + assert alice_contact_bob_snapshot.e2ee_avail remote_eval("bob.wait_for_securejoin_joiner_success()") - # Test that Bob verified Alice's profile. + # The old core still marks Alice as verified, so the handshake is unchanged on the wire. assert remote_eval("bob_contact_alice.get_snapshot().is_verified") diff --git a/deltachat-rpc-client/tests/test_securejoin.py b/deltachat-rpc-client/tests/test_securejoin.py index f7b3adf7a..47d79d8e5 100644 --- a/deltachat-rpc-client/tests/test_securejoin.py +++ b/deltachat-rpc-client/tests/test_securejoin.py @@ -4,10 +4,9 @@ import pytest from deltachat_rpc_client import Chat, EventType, SpecialContactId from deltachat_rpc_client.const import ChatType -from deltachat_rpc_client.rpc import JsonRpcError -def test_qr_setup_contact(acf, tmp_path) -> None: +def test_qr_setup_contact(acf) -> None: alice, bob = acf.get_online_accounts(2) qr_code = alice.get_qr_code() @@ -15,33 +14,15 @@ def test_qr_setup_contact(acf, tmp_path) -> None: alice.wait_for_securejoin_inviter_success() - # Test that Alice verified Bob's profile. alice_contact_bob = alice.create_contact(bob) alice_contact_bob_snapshot = alice_contact_bob.get_snapshot() - assert alice_contact_bob_snapshot.is_verified + assert alice_contact_bob_snapshot.e2ee_avail bob.wait_for_securejoin_joiner_success() - # Test that Bob verified Alice's profile. bob_contact_alice = bob.create_contact(alice) bob_contact_alice_snapshot = bob_contact_alice.get_snapshot() - assert bob_contact_alice_snapshot.is_verified - - # Test that if Bob imports a key, - # backwards verification is not lost - # because default key is not changed. - logging.info("Bob 2 is created") - bob2 = acf.new_configured_account() - bob2.export_self_keys(tmp_path) - - logging.info("Bob tries to import a key") - # Importing a second key is not allowed. - with pytest.raises(JsonRpcError): - bob.import_self_keys(tmp_path) - - assert bob.get_config("key_id") == "1" - bob_contact_alice_snapshot = bob_contact_alice.get_snapshot() - assert bob_contact_alice_snapshot.is_verified + assert bob_contact_alice_snapshot.e2ee_avail def test_qr_setup_contact_svg(acf) -> None: @@ -81,26 +62,24 @@ def test_qr_securejoin(acf): ac.wait_for_event(EventType.IMAP_MESSAGE_DELETED) bob.wait_for_securejoin_joiner_success() - # Test that Alice verified Bob's profile. alice_contact_bob = alice.create_contact(bob) alice_contact_bob_snapshot = alice_contact_bob.get_snapshot() - assert alice_contact_bob_snapshot.is_verified + assert alice_contact_bob_snapshot.e2ee_avail snapshot = bob.wait_for_incoming_msg().get_snapshot() assert snapshot.text == "You were added by {}.".format(alice.get_config("addr")) - # Test that Bob verified Alice's profile. bob_contact_alice = bob.create_contact(alice) bob_contact_alice_snapshot = bob_contact_alice.get_snapshot() - assert bob_contact_alice_snapshot.is_verified + assert bob_contact_alice_snapshot.e2ee_avail # Start second Alice device. - # Alice observes securejoin protocol and verifies Bob on second device. + # Alice observes the securejoin protocol on the second device. alice2.start_io() alice2.wait_for_securejoin_inviter_success() alice2_contact_bob = alice2.create_contact(bob) alice2_contact_bob_snapshot = alice2_contact_bob.get_snapshot() - assert alice2_contact_bob_snapshot.is_verified + assert alice2_contact_bob_snapshot.e2ee_avail # The QR code token is synced, so alice2 must be able to handle join requests. logging.info("Fiona joins the group via alice2") @@ -151,9 +130,9 @@ def test_qr_securejoin_broadcast(acf, all_devices_online): assert snapshot2.chat_id == chat.id def check_account(ac, contact, inviter_side, please_wait_info_msg=False): - # Check that the chat partner is verified. + # Check that the chat partner's key is known. contact_snapshot = contact.get_snapshot() - assert contact_snapshot.is_verified + assert contact_snapshot.e2ee_avail chat = get_broadcast(ac) chat_msgs = chat.get_messages() @@ -350,8 +329,8 @@ def test_setup_contact_resetup(acf) -> None: bob.wait_for_securejoin_joiner_success() -def test_verified_group_member_added_recovery(acf) -> None: - """Tests verified group recovery by reverifying then removing and adding a member back.""" +def test_group_member_added_recovery(acf) -> None: + """Tests group recovery after a member resets its key.""" ac1, ac2, ac3 = acf.get_online_accounts(3) logging.info("ac1 creates a group") @@ -362,11 +341,7 @@ def test_verified_group_member_added_recovery(acf) -> None: ac2.secure_join(qr_code) ac2.wait_for_securejoin_joiner_success() - # ac1 has ac2 directly verified. - ac1_contact_ac2 = ac1.create_contact(ac2) - assert ac1_contact_ac2.get_snapshot().verifier_id == SpecialContactId.SELF - - logging.info("ac3 joins verified group") + logging.info("ac3 joins the group") ac3_chat = ac3.secure_join(qr_code) ac3.wait_for_securejoin_joiner_success() ac3.wait_for_incoming_msg_event() # Member added @@ -376,7 +351,7 @@ def test_verified_group_member_added_recovery(acf) -> None: logging.info("ac2 logs in on a new device") ac2 = acf.resetup_account(ac2) - logging.info("ac2 reverifies with ac3") + logging.info("ac2 scans ac3's QR code again") qr_code = ac3.get_qr_code() ac2.secure_join(qr_code) ac2.wait_for_securejoin_joiner_success() @@ -416,14 +391,6 @@ def test_verified_group_member_added_recovery(acf) -> None: snapshot = ac1.wait_for_incoming_msg().get_snapshot() assert snapshot.text == "Works again!" - ac1_contact_ac2 = ac1.create_contact(ac2) - ac1_contact_ac3 = ac1.create_contact(ac3) - ac1_contact_ac2_snapshot = ac1_contact_ac2.get_snapshot() - # 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(acf): """Regression test for @@ -431,13 +398,13 @@ def test_qr_join_chat_with_pending_bobstate_issue4894(acf): """ ac1, ac2, ac3, ac4 = acf.get_online_accounts(4) - logging.info("ac3: verify with ac2") + logging.info("ac3: set up contact with ac2") qr_code = ac2.get_qr_code() ac3.secure_join(qr_code) ac2.wait_for_securejoin_inviter_success() - # in order for ac2 to have pending bobstate with a verified group - # we first create a fully joined verified group, and then start + # in order for ac2 to have pending bobstate with a group + # we first create a fully joined group, and then start # joining a second time but interrupt it, to create pending bob state logging.info("ac1: create a group that ac2 fully joins") @@ -446,7 +413,7 @@ def test_qr_join_chat_with_pending_bobstate_issue4894(acf): ac2.secure_join(qr_code) ac1.wait_for_securejoin_inviter_success() - # ensure ac1 can write and ac2 receives messages in verified chat + # ensure ac1 can write and ac2 receives messages in the chat ch1.send_text("ac1 says hello") while 1: snapshot = ac2.wait_for_incoming_msg().get_snapshot() @@ -459,11 +426,11 @@ def test_qr_join_chat_with_pending_bobstate_issue4894(acf): ac1.remove() logging.info("ac2 now has pending bobstate but ac1 is shutoff") - # we meanwhile expect ac3/ac2 verification started in the beginning to have completed - assert ac3.create_contact(ac2).get_snapshot().is_verified - assert ac2.create_contact(ac3).get_snapshot().is_verified + # we meanwhile expect the ac3/ac2 setup-contact started in the beginning to have completed + assert ac3.create_contact(ac2).get_snapshot().e2ee_avail + assert ac2.create_contact(ac3).get_snapshot().e2ee_avail - logging.info("ac3: create a verified group VG with ac2") + logging.info("ac3: create a group VG with ac2") vg = ac3.create_group("ac3-created") vg.add_contact(ac3.create_contact(ac2)) @@ -486,7 +453,7 @@ def test_qr_join_chat_with_pending_bobstate_issue4894(acf): def test_qr_new_group_unblocked(acf): """Regression test for a bug introduced in core v1.113.0. - ac2 scans a verified group QR code created by ac1. + ac2 scans a group QR code created by ac1. This results in creation of a blocked single chat with ac1 on ac2, but ac1 contact is not blocked on ac2. Then ac1 creates a group, adds ac2 there and promotes it by sending a message. @@ -513,13 +480,13 @@ def test_qr_new_group_unblocked(acf): @pytest.mark.skip(reason="AEAP is disabled for now") -def test_aeap_flow_verified(acf): +def test_aeap_flow(acf): """Test that a new address is added to a contact when it changes its address.""" ac1, ac2 = acf.get_online_accounts(2) addr, password = acf.get_credentials() - logging.info("ac1: create verified-group QR, ac2 scans and joins") + logging.info("ac1: create group QR, ac2 scans and joins") chat = ac1.create_group("hello") qr_code = chat.get_qr_code() logging.info("ac2: start QR-code based join-group protocol") @@ -555,65 +522,15 @@ def test_aeap_flow_verified(acf): assert addr in [contact.get_snapshot().address for contact in msg_in_2_snapshot.chat.get_contacts()] -def test_gossip_verification(acf) -> None: - alice, bob, carol = acf.get_online_accounts(3) - - # Bob verifies Alice. - qr_code = alice.get_qr_code() - bob.secure_join(qr_code) - bob.wait_for_securejoin_joiner_success() - - # Bob verifies Carol. - qr_code = carol.get_qr_code() - bob.secure_join(qr_code) - bob.wait_for_securejoin_joiner_success() - - bob_contact_alice = bob.create_contact(alice, "Alice") - bob_contact_carol = bob.create_contact(carol, "Carol") - carol_contact_alice = carol.create_contact(alice, "Alice") - - logging.info("Bob creates an Autocrypt group") - bob_group_chat = bob.create_group("Autocrypt Group") - bob_group_chat.add_contact(bob_contact_alice) - bob_group_chat.add_contact(bob_contact_carol) - bob_group_chat.send_message(text="Hello Autocrypt group") - - snapshot = carol.wait_for_incoming_msg().get_snapshot() - assert snapshot.text == "Hello Autocrypt group" - assert snapshot.show_padlock - - # Group propagates verification using Autocrypt-Gossip header. - carol_contact_alice_snapshot = carol_contact_alice.get_snapshot() - # 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") - bob_group_chat.add_contact(bob_contact_alice) - bob_group_chat.add_contact(bob_contact_carol) - bob_group_chat.send_message(text="Hello Securejoin group") - - snapshot = carol.wait_for_incoming_msg().get_snapshot() - assert snapshot.text == "Hello Securejoin group" - assert snapshot.show_padlock - - # Securejoin propagates verification. - carol_contact_alice_snapshot = carol_contact_alice.get_snapshot() - # 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(acf) -> None: """ - Regression test for a bug that prevented joining verified group with a QR code - if the group is already created and contains - a contact with inconsistent (Autocrypt and verified keys exist but don't match) key state. + Regression test for a bug that prevented joining a group with a QR code + if the group already contains a contact with an inconsistent key state + while a securejoin with that contact is still pending. """ ac1, ac2, ac3 = acf.get_online_accounts(3) - # ac3 creates protected group with ac1. + # ac3 creates a group with ac1. ac3_chat = ac3.create_group("Group") # ac1 joins ac3 group. @@ -626,28 +543,24 @@ def test_securejoin_after_contact_resetup(acf) -> None: assert snapshot.text == "You were added by {}.".format(ac3.get_config("addr")) ac1_qr_code = snapshot.chat.get_qr_code() - # ac2 verifies ac1 + # ac2 sets up contact with ac1 qr_code = ac1.get_qr_code() ac2.secure_join(qr_code) ac2.wait_for_securejoin_joiner_success() - # ac1 is verified for ac2. ac2_contact_ac1 = ac2.create_contact(ac1, "") - assert ac2_contact_ac1.get_snapshot().is_verified + assert ac2_contact_ac1.get_snapshot().e2ee_avail # ac1 resetups the account. ac1 = acf.resetup_account(ac1) - ac2_contact_ac1 = ac2.create_contact(ac1, "") - assert not ac2_contact_ac1.get_snapshot().is_verified # ac1 goes offline. ac1.remove() - # Scanning a QR code results in creating an unprotected group with an inviter. - # In this case inviter is ac1 which has an inconsistent key state. - # Normally inviter becomes verified as a result of Securejoin protocol - # and then the group chat becomes verified when "Member added" is received, - # but in this case ac1 is offline and this Securejoin process will never finish. + # Scanning a QR code creates a group with the inviter, here ac1, + # which has an inconsistent key state. Normally the securejoin protocol + # would complete and "Member added" would arrive, + # but ac1 is offline so it never finishes. logging.info("ac2 scans ac1 QR code, this is not expected to finish") ac2.secure_join(ac1_qr_code) @@ -664,16 +577,13 @@ def test_securejoin_after_contact_resetup(acf) -> None: ac2_chat = snapshot.chat assert len(ac2_chat.get_contacts()) == 3 - # ac1 is still "not verified" for ac2 due to inconsistent state. - assert not ac2_contact_ac1.get_snapshot().is_verified - def test_withdraw_securejoin_qr(acf): alice, bob = acf.get_online_accounts(2) logging.info("Alice creates a group") alice_chat = alice.create_group("Group") - logging.info("Bob joins verified group") + logging.info("Bob joins the group") qr_code = alice_chat.get_qr_code() bob_chat = bob.secure_join(qr_code) diff --git a/docs/schema.sql b/docs/schema.sql index ac042ec85..46c10c9c8 100644 --- a/docs/schema.sql +++ b/docs/schema.sql @@ -72,8 +72,7 @@ CREATE TABLE contacts ( -- empty string for "address-contacts". fingerprint TEXT NOT NULL DEFAULT '', - -- ID of the contact that has "introduced" us to this contact - -- by sharing the key with a verified attribute or in a "verified" chat. + -- Unused. Was the ID of the contact that introduced this contact's key. verifier INTEGER NOT NULL DEFAULT 0 ); CREATE INDEX contacts_index1 ON contacts (name COLLATE NOCASE); @@ -131,9 +130,7 @@ CREATE TABLE chats ( -- 0 means the timer is disabled. ephemeral_timer INTEGER, - -- Deprecated, but still used to send Chat-Verified headers - -- for existing protected chats. - -- All new chats are created as "not protected". + -- Unused. Was 1 for protected chats. protected INTEGER DEFAULT 0, gossiped_timestamp INTEGER DEFAULT 0, -- deprecated 2025-04-08, replaced with gossip_timestamp table @@ -730,13 +727,13 @@ CREATE TABLE stats_securejoin_uipaths( ) STRICT; CREATE TABLE stats_securejoin_invites( already_existed INTEGER NOT NULL, - already_verified INTEGER NOT NULL, + already_verified INTEGER NOT NULL, -- unused, always 0 type TEXT NOT NULL ) STRICT; CREATE TABLE stats_msgs( chattype INTEGER PRIMARY KEY, - verified INTEGER NOT NULL DEFAULT 0, - unverified_encrypted INTEGER NOT NULL DEFAULT 0, + verified INTEGER NOT NULL DEFAULT 0, -- unused, always 0 + unverified_encrypted INTEGER NOT NULL DEFAULT 0, -- counts all encrypted messages unencrypted INTEGER NOT NULL DEFAULT 0, only_to_self INTEGER NOT NULL DEFAULT 0, last_counted_msg_id INTEGER NOT NULL DEFAULT 0 diff --git a/python/tests/test_0_complex_or_slow.py b/python/tests/test_0_complex_or_slow.py index 9b25df187..3873ae33a 100644 --- a/python/tests/test_0_complex_or_slow.py +++ b/python/tests/test_0_complex_or_slow.py @@ -1,7 +1,5 @@ import time -import deltachat as dc - class TestGroupStressTests: def test_group_many_members_add_leave_remove(self, acfactory, lp): @@ -63,9 +61,9 @@ class TestGroupStressTests: assert msg.is_encrypted() -def test_qr_verified_group_and_chatting(acfactory, lp): +def test_qr_group_join_and_chatting(acfactory, lp): ac1, ac2, ac3 = acfactory.get_online_accounts(3) - lp.sec("ac1: create verified-group QR, ac2 scans and joins") + lp.sec("ac1: create group QR, ac2 scans and joins") chat1 = ac1.create_group_chat("hello") qr = chat1.get_join_qr() lp.sec("ac2: start QR-code based join-group protocol") @@ -86,15 +84,11 @@ def test_qr_verified_group_and_chatting(acfactory, lp): msg_out = chat1.send_text("hello") assert msg_out.is_encrypted() - lp.sec("ac2: read message and check that it's a verified chat") + lp.sec("ac2: read message and check that it is encrypted") msg = ac2._evtracker.wait_next_incoming_message() assert msg.text == "hello" assert msg.is_encrypted() - lp.sec("ac2: Check that ac2 verified ac1") - ac2_ac1_contact = ac2.get_contacts()[0] - assert ac2.get_self_contact().get_verifier(ac2_ac1_contact).id == dc.const.DC_CONTACT_ID_SELF - lp.sec("ac2: send message and let ac1 read it") chat2.send_text("world") msg = ac1._evtracker.wait_next_incoming_message() @@ -109,23 +103,13 @@ def test_qr_verified_group_and_chatting(acfactory, lp): assert ch.id >= 10 ac1._evtracker.wait_securejoin_inviter_progress(1000) - lp.sec("ac1: add ac3 to verified group") + lp.sec("ac1: add ac3 to the group") chat1.add_contact(ac3) msg = ac2._evtracker.wait_next_incoming_message() assert msg.is_encrypted() assert msg.is_system_message() assert not msg.error - lp.sec("ac2: Check that ac1 verified ac3 for ac2") - ac2_ac1_contact = ac2.get_contacts()[0] - assert ac2.get_self_contact().get_verifier(ac2_ac1_contact).id == dc.const.DC_CONTACT_ID_SELF - for ac2_contact in chat2.get_contacts(): - if ac2_contact == ac2_ac1_contact or ac2_contact.id == dc.const.DC_CONTACT_ID_SELF: - continue - # 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") # System message about the added member. @@ -195,10 +179,10 @@ def test_ephemeral_timer(acfactory, lp): assert chat1.get_ephemeral_timer() == 0 -def test_see_new_verified_member_after_going_online(acfactory, tmp_path, lp): +def test_see_new_member_after_going_online(acfactory, tmp_path, lp): """The test for the bug #3836: - Alice has two devices, the second is offline. - - Alice creates a verified group and sends a QR invitation to Bob. + - Alice creates a group and sends a QR invitation to Bob. - Bob joins the group and sends a message there. Alice sees it. - Alice's second devices goes online, but doesn't see Bob in the group. """ @@ -215,7 +199,7 @@ def test_see_new_verified_member_after_going_online(acfactory, tmp_path, lp): ac1_offl.import_self_keys(str(dir)) ac1_offl.stop_io() - lp.sec("ac1: create verified-group QR, ac2 scans and joins") + lp.sec("ac1: create group QR, ac2 scans and joins") chat = ac1.create_group_chat("hello") qr = chat.get_join_qr() lp.sec("ac2: start QR-code based join-group protocol") @@ -242,12 +226,12 @@ def test_see_new_verified_member_after_going_online(acfactory, tmp_path, lp): assert msg_in.get_sender_contact().addr == ac2_addr -def test_use_new_verified_group_after_going_online(acfactory, data, tmp_path, lp): +def test_use_new_group_after_going_online(acfactory, data, tmp_path, lp): """Another test for the bug #3836: - Bob has two devices, the second is offline. - - Alice creates a verified group and sends a QR invitation to Bob. + - Alice creates a group and sends a QR invitation to Bob. - Bob joins the group. - - Bob's second devices goes online, but sees a contact request instead of the verified group. + - Bob's second devices goes online, but sees a contact request instead of the group. - The "member added" message is not a system message but a plain text message. - Bob's second device doesn't display the Alice's avatar (bug #5354). - Sending a message fails as the key is missing -- message info says "proper enc-key for @@ -269,7 +253,7 @@ def test_use_new_verified_group_after_going_online(acfactory, data, tmp_path, lp avatar_path = data.get_path("d.png") ac1.set_avatar(avatar_path) - lp.sec("ac1: create verified-group QR, ac2 scans and joins") + lp.sec("ac1: create group QR, ac2 scans and joins") chat = ac1.create_group_chat("hello") qr = chat.get_join_qr() lp.sec("ac2: start QR-code based join-group protocol") diff --git a/python/tests/test_1_online.py b/python/tests/test_1_online.py index 2d0292af1..8be97e522 100644 --- a/python/tests/test_1_online.py +++ b/python/tests/test_1_online.py @@ -796,9 +796,8 @@ def test_send_and_receive_image(acfactory, lp, data): def test_qr_email_capitalization(acfactory, lp): - """Regression test for a bug - that resulted in failure to propagate verification - when the database already contained the contact with a different email address capitalization. + """Tests joining a group via QR code + when the database already contains a contact with a different email address capitalization. """ ac1, ac2, ac3 = acfactory.get_online_accounts(3) @@ -822,13 +821,7 @@ def test_qr_email_capitalization(acfactory, lp): ac2.qr_join_chat(qr) ac1._evtracker.wait_next_incoming_message() - # 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(): - 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/spec.md b/spec.md index 219001188..d5139d9f0 100644 --- a/spec.md +++ b/spec.md @@ -596,24 +596,6 @@ and e.g. simply search for the line starting with `EMAIL` in order to get the email address. -# Verifications - -Keys obtained using [SecureJoin](https://securejoin.readthedocs.io) protocol -and corresponding contacts -are considered "verified". - -As an extension to `Autocrypt-Gossip` header, -chatmail clients can add `_verified=1` attribute -(underscore marks the attribute as non-critical) -to indicate that they have the gossiped key -and the corresponding contact marked as verified. - -When receiving such `Autocrypt-Gossip` header -in a message signed by a verified key, -chatmail clients mark the gossiped key -as indirectly verified. - - # Miscellaneous Messengers SHOULD use the header `In-Reply-To` as usual. diff --git a/src/aheader.rs b/src/aheader.rs index 218d08a58..fdf6dc393 100644 --- a/src/aheader.rs +++ b/src/aheader.rs @@ -43,13 +43,6 @@ pub struct Aheader { pub addr: String, pub public_key: SignedPublicKey, pub prefer_encrypt: EncryptPreference, - - /// Whether `_verified` attribute is present. - /// - /// `_verified` attribute is an extension to `Autocrypt-Gossip` - /// header that is used to tell that the sender - /// marked this key as verified. - pub verified: bool, } impl fmt::Display for Aheader { @@ -58,12 +51,6 @@ impl fmt::Display for Aheader { if self.prefer_encrypt == EncryptPreference::Mutual { write!(fmt, " prefer-encrypt=mutual;")?; } - // 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 // (which may insert a linebreak before every whitespace) @@ -114,8 +101,6 @@ impl Aheader { .and_then(|raw| EncryptPreference::new(&raw).ok()) .unwrap_or_default(); - let verified = attributes.remove("_verified").is_some(); - // Autocrypt-Level0: unknown attributes starting with an underscore can be safely ignored // Autocrypt-Level0: unknown attribute, treat the header as invalid if attributes.keys().any(|k| !k.starts_with('_')) { @@ -126,7 +111,6 @@ impl Aheader { addr, public_key, prefer_encrypt, - verified, }) } } @@ -145,7 +129,6 @@ mod tests { assert_eq!(h.addr, "me@mail.com"); assert_eq!(h.prefer_encrypt, EncryptPreference::Mutual); - assert_eq!(h.verified, false); Ok(()) } @@ -243,7 +226,6 @@ mod tests { addr: "test@example.com".to_string(), public_key: SignedPublicKey::from_base64(RAWKEY).unwrap(), prefer_encrypt: EncryptPreference::Mutual, - verified: false } ) .contains("prefer-encrypt=mutual;") @@ -259,7 +241,6 @@ mod tests { addr: "test@example.com".to_string(), public_key: SignedPublicKey::from_base64(RAWKEY).unwrap(), prefer_encrypt: EncryptPreference::NoPreference, - verified: false } ) .contains("prefer-encrypt") @@ -273,24 +254,9 @@ mod tests { addr: "TeSt@eXaMpLe.cOm".to_string(), public_key: SignedPublicKey::from_base64(RAWKEY).unwrap(), prefer_encrypt: EncryptPreference::Mutual, - verified: false } ) .contains("test@example.com") ); - - // We don't send the _verified header yet: - assert!( - !format!( - "{}", - Aheader { - addr: "test@example.com".to_string(), - public_key: SignedPublicKey::from_base64(RAWKEY).unwrap(), - prefer_encrypt: EncryptPreference::NoPreference, - verified: true - } - ) - .contains("_verified") - ); } } diff --git a/src/chat.rs b/src/chat.rs index 6b6859369..8101b34dc 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -2683,7 +2683,7 @@ async fn prepare_send_msg( let skip_fn = |reason: &CantSendReason| match reason { CantSendReason::ContactRequest => { - // Allow securejoin messages, they are supposed to repair the verification. + // Allow securejoin messages. // If the chat is a contact request, let the user accept it later. msg.param.get_cmd() == SystemMessage::SecurejoinMessage } diff --git a/src/chat/chat_tests.rs b/src/chat/chat_tests.rs index 3ca296c27..6bd9aebff 100644 --- a/src/chat/chat_tests.rs +++ b/src/chat/chat_tests.rs @@ -3759,25 +3759,6 @@ async fn test_broadcast_joining_golden() -> Result<()> { ) .await; - assert_eq!( - alice_bob_contact - .get_verifier_id(alice) - .await? - .unwrap() - .unwrap(), - ContactId::SELF - ); - - let bob_alice_contact = bob.add_or_lookup_contact_no_key(alice).await; - assert_eq!( - bob_alice_contact - .get_verifier_id(bob) - .await? - .unwrap() - .unwrap(), - ContactId::SELF - ); - Ok(()) } @@ -4060,10 +4041,9 @@ async fn test_leave_broadcast_multidevice() -> Result<()> { bob1.recv_msg(&member_added).await; // The single chat should not be visible to the user on any of the devices. - // The contact should be marked as verified. - check_single_chat_is_hidden_and_contact_is_verified(alice, bob0).await; - check_single_chat_is_hidden_and_contact_is_verified(bob0, alice).await; - check_single_chat_is_hidden_and_contact_is_verified(bob1, alice).await; + check_single_chat_is_hidden(alice, bob0).await; + check_single_chat_is_hidden(bob0, alice).await; + check_single_chat_is_hidden(bob1, alice).await; tcm.section("Alice sends first message to broadcast."); let sent_msg = alice.send_text(alice_chat_id, "Hello!").await; @@ -4090,10 +4070,7 @@ async fn test_leave_broadcast_multidevice() -> Result<()> { Ok(()) } -async fn check_single_chat_is_hidden_and_contact_is_verified( - t: &TestContext, - contact: &TestContext, -) { +async fn check_single_chat_is_hidden(t: &TestContext, contact: &TestContext) { let contact = t.add_or_lookup_contact_no_key(contact).await; if let Some(single_chat) = ChatIdBlocked::lookup_by_contact(t, contact.id) .await @@ -4101,7 +4078,6 @@ async fn check_single_chat_is_hidden_and_contact_is_verified( { assert_eq!(single_chat.blocked, Blocked::Yes); } - assert!(contact.is_verified(t).await.unwrap()); } /// Test that only the owner of the broadcast channel diff --git a/src/contact.rs b/src/contact.rs index 130374f91..ae0dc5ecb 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -38,9 +38,7 @@ use crate::param::{Param, Params}; use crate::pgp::{addresses_from_public_key, merge_openpgp_certificates}; use crate::sync::{self, Sync::*}; use crate::tools::{SystemTime, duration_to_str, get_abs_path, normalize_text, time, to_lowercase}; -use crate::{ - chat, chatlist_events, ensure_and_debug_assert, ensure_and_debug_assert_ne, stock_str, -}; +use crate::{chat, chatlist_events, ensure_and_debug_assert, stock_str}; /// Time during which a contact is considered as seen recently. const SEEN_RECENTLY_SECONDS: i64 = 600; @@ -1997,68 +1995,6 @@ pub(crate) async fn update_last_seen( Ok(()) } -/// Marks contact `contact_id` as verified by `verifier_id`. -/// -/// `verifier_id == None` means that the verifier is unknown. -pub(crate) async fn mark_contact_id_as_verified( - context: &Context, - contact_id: ContactId, - verifier_id: Option, -) -> Result<()> { - ensure_and_debug_assert_ne!(contact_id, ContactId::SELF,); - ensure_and_debug_assert_ne!( - Some(contact_id), - verifier_id, - "Contact cannot be verified by self", - ); - let by_self = verifier_id == Some(ContactId::SELF); - let mut verifier_id = verifier_id.unwrap_or(contact_id); - context - .sql - .transaction(|transaction| { - let contact_fingerprint: String = transaction.query_row( - "SELECT fingerprint FROM contacts WHERE id=?", - (contact_id,), - |row| row.get(0), - )?; - if contact_fingerprint.is_empty() { - bail!("Non-key-contact {contact_id} cannot be verified"); - } - if verifier_id != ContactId::SELF { - let (verifier_fingerprint, verifier_verifier_id): (String, ContactId) = transaction - .query_row( - "SELECT fingerprint, verifier FROM contacts WHERE id=?", - (verifier_id,), - |row| Ok((row.get(0)?, row.get(1)?)), - )?; - if verifier_fingerprint.is_empty() { - bail!( - "Contact {contact_id} cannot be verified by non-key-contact {verifier_id}" - ); - } - ensure!( - verifier_id == contact_id || verifier_verifier_id != ContactId::UNDEFINED, - "Contact {contact_id} cannot be verified by unverified contact {verifier_id}", - ); - if verifier_verifier_id == verifier_id { - // Avoid introducing incorrect reverse chains: if the verifier itself has an - // unknown verifier, it may be `contact_id` actually (directly or indirectly) on - // the other device (which is needed for getting "verified by unknown contact" - // in the first place). - verifier_id = contact_id; - } - } - transaction.execute( - "UPDATE contacts SET verifier=?1 - WHERE id=?2 AND (verifier=0 OR verifier=id OR ?3)", - (verifier_id, contact_id, by_self), - )?; - Ok(()) - }) - .await?; - Ok(()) -} - fn cat_fingerprint(ret: &mut String, name: &str, addr: &str, fingerprint: &str) { *ret += &format!("\n\n{name} ({addr}):\n{fingerprint}"); } diff --git a/src/contact/contact_tests.rs b/src/contact/contact_tests.rs index 98606d46a..0c47fc173 100644 --- a/src/contact/contact_tests.rs +++ b/src/contact/contact_tests.rs @@ -1171,27 +1171,6 @@ async fn test_lookup_id_by_addr_recent_accepted() -> Result<()> { test_lookup_id_by_addr_recent_ext(accept_unencrypted_chat).await } -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_verified_by_none() -> Result<()> { - let mut tcm = TestContextManager::new(); - let alice = tcm.alice().await; - let bob = tcm.bob().await; - - let contact_id = Contact::create(&alice, "Bob", "bob@example.net").await?; - let contact = Contact::get_by_id(&alice, contact_id).await?; - assert!(contact.get_verifier_id(&alice).await?.is_none()); - - // Receive a message from Bob to save the public key. - let chat = bob.create_chat(&alice).await; - let sent_msg = bob.send_text(chat.id, "moin").await; - alice.recv_msg(&sent_msg).await; - - let contact = Contact::get_by_id(&alice, contact_id).await?; - assert!(contact.get_verifier_id(&alice).await?.is_none()); - - Ok(()) -} - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_sync_create() -> Result<()> { let alice0 = &TestContext::new_alice().await; @@ -1391,13 +1370,11 @@ async fn test_import_vcard_key_change() -> Result<()> { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_self_is_verified() -> Result<()> { +async fn test_self_is_key_contact() -> Result<()> { let mut tcm = TestContextManager::new(); let alice = tcm.alice().await; let contact = Contact::get_by_id(&alice, ContactId::SELF).await?; - assert_eq!(contact.is_verified(&alice).await?, true); - assert!(contact.get_verifier_id(&alice).await?.is_none()); assert!(contact.is_key_contact()); Ok(()) diff --git a/src/events/payload.rs b/src/events/payload.rs index 446ccf88e..196a8999c 100644 --- a/src/events/payload.rs +++ b/src/events/payload.rs @@ -214,7 +214,6 @@ pub enum EventType { }, /// Chat changed. The name or the image of a chat group was changed or members were added or removed. - /// Or the verify state of a chat has changed. /// See dc_set_chat_name(), dc_set_chat_profile_image(), dc_add_contact_to_chat() /// and dc_remove_contact_from_chat(). /// @@ -302,8 +301,7 @@ pub enum EventType { contact_id: ContactId, /// Progress as: - /// 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself." - /// (Bob has verified alice and waits until Alice does the same for him) + /// 400=vg-/vc-request-with-auth sent, typically shown as "introducing myself." /// 1000=vg-member-added/vc-contact-confirm received progress: u16, }, diff --git a/src/headerdef.rs b/src/headerdef.rs index d9b1924c7..78f4e9429 100644 --- a/src/headerdef.rs +++ b/src/headerdef.rs @@ -63,7 +63,6 @@ pub enum HeaderDef { ChatGroupDescription, ChatGroupDescriptionChanged, ChatGroupDescriptionTimestamp, - ChatVerified, ChatGroupAvatar, ChatUserAvatar, ChatVoiceMessage, diff --git a/src/mimefactory.rs b/src/mimefactory.rs index 52c822091..2bf376ddf 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -436,7 +436,6 @@ pub(crate) fn render_queued_mail( addr: from_addr, public_key: public_key.clone(), prefer_encrypt: EncryptPreference::Mutual, - verified: false, }; let autocrypt_header = mail_builder::headers::raw::Raw::new(aheader.to_string()); add_header( @@ -1563,17 +1562,6 @@ impl MimeFactory { .is_none_or(|ts| now >= ts + gossip_period || now < ts) }; - let verifier_id: Option = context - .sql - .query_get_value( - "SELECT verifier FROM contacts WHERE fingerprint=?", - (&fingerprint,), - ) - .await?; - - let is_verified = - verifier_id.is_some_and(|verifier_id| verifier_id != 0); - if !should_do_gossip { continue; } @@ -1584,7 +1572,6 @@ impl MimeFactory { // Autocrypt 1.1.0 specification says that // `prefer-encrypt` attribute SHOULD NOT be included. prefer_encrypt: EncryptPreference::NoPreference, - verified: is_verified, } .to_string(); @@ -1728,43 +1715,6 @@ 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/mimeparser.rs b/src/mimeparser.rs index 3d0f2940a..c296a49b9 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -34,17 +34,6 @@ use crate::sync::SyncItems; use crate::tools::{get_filemeta, parse_receive_headers, time, truncate_msg_text, validate_id}; use crate::{chatlist_events, location, tools}; -/// Public key extracted from `Autocrypt-Gossip` -/// header with associated information. -#[derive(Debug)] -pub struct GossipedKey { - /// Public key extracted from `keydata` attribute. - pub public_key: SignedPublicKey, - - /// True if `Autocrypt-Gossip` has a `_verified` attribute. - pub verified: bool, -} - /// A parsed MIME message. /// /// This represents the relevant information of a parsed MIME message @@ -99,7 +88,7 @@ pub(crate) struct MimeMessage { /// The addresses for which there was a gossip header /// and their respective gossiped keys. - pub gossiped_keys: BTreeMap, + pub gossiped_keys: BTreeMap, /// Fingerprint of the key in the Autocrypt header. /// @@ -1048,7 +1037,7 @@ impl MimeMessage { }; self.gossiped_keys .values() - .map(|gossiped_key| gossiped_key.public_key.dc_fingerprint().hex()) + .map(|gossiped_key| gossiped_key.dc_fingerprint().hex()) .chain(sender_fingerprint) .collect() } @@ -2118,9 +2107,9 @@ fn remove_header( async fn parse_gossip_headers( context: &Context, gossip_headers: Vec, -) -> Result> { +) -> Result> { // XXX split the parsing from the modification part - let mut gossiped_keys: BTreeMap = Default::default(); + let mut gossiped_keys: BTreeMap = Default::default(); for value in &gossip_headers { let header = match Aheader::from_str(value) { @@ -2135,12 +2124,7 @@ async fn parse_gossip_headers( .await .context("Failed to import Autocrypt-Gossip key")?; - let gossiped_key = GossipedKey { - public_key: header.public_key, - - verified: header.verified, - }; - gossiped_keys.insert(header.addr.to_lowercase(), gossiped_key); + gossiped_keys.insert(header.addr.to_lowercase(), header.public_key); } Ok(gossiped_keys) diff --git a/src/qr.rs b/src/qr.rs index 165a1371d..2b52d7213 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -45,7 +45,7 @@ pub(crate) const DCBACKUP_VERSION: i32 = 5; /// Scanned QR code. #[derive(Debug, Clone, PartialEq, Eq)] pub enum Qr { - /// Ask the user whether to verify the contact. + /// Ask the user whether to start chatting with the contact. /// /// If the user agrees, pass this QR code to [`crate::securejoin::join_securejoin`]. AskVerifyContact { @@ -125,7 +125,7 @@ pub enum Qr { is_v3: bool, }, - /// Contact fingerprint is verified. + /// Contact fingerprint matches. /// /// Ask the user if they want to start chatting. FprOk { diff --git a/src/qr_code_generator.rs b/src/qr_code_generator.rs index 29a6b3da0..88601f0cf 100644 --- a/src/qr_code_generator.rs +++ b/src/qr_code_generator.rs @@ -85,15 +85,15 @@ pub fn create_qr_svg(qrcode_content: &str) -> Result { Ok(svg) } -/// Returns SVG of the QR code to join the group or verify contact. +/// Returns SVG of the QR code to join the group or set up a contact. /// -/// If `chat_id` is `None`, returns verification QR code. +/// If `chat_id` is `None`, returns setup contact QR code. /// Otherwise, returns secure join QR code. pub async fn get_securejoin_qr_svg(context: &Context, chat_id: Option) -> Result { if let Some(chat_id) = chat_id { generate_join_group_qr_code(context, chat_id).await } else { - generate_verification_qr(context).await + generate_setup_contact_qr(context).await } } @@ -127,7 +127,7 @@ async fn generate_join_group_qr_code(context: &Context, chat_id: ChatId) -> Resu ) } -async fn generate_verification_qr(context: &Context) -> Result { +async fn generate_setup_contact_qr(context: &Context) -> Result { let (avatar, displayname, addr, color) = self_info(context).await?; inner_generate_secure_join_qr_code( diff --git a/src/receive_imf.rs b/src/receive_imf.rs index 73a69c640..6570aec08 100644 --- a/src/receive_imf.rs +++ b/src/receive_imf.rs @@ -19,7 +19,7 @@ use crate::chat::{ }; use crate::config::Config; use crate::constants::{Blocked, Chattype, EDITED_PREFIX}; -use crate::contact::{self, Contact, ContactId, Origin, mark_contact_id_as_verified}; +use crate::contact::{self, Contact, ContactId, Origin}; use crate::context::Context; use crate::debug_logging::maybe_set_logging_xdc_inner; use crate::download::{DownloadState, msg_is_downloaded_for}; @@ -27,7 +27,7 @@ use crate::ephemeral::{Timer as EphemeralTimer, stock_ephemeral_timer_changed}; use crate::events::EventType; use crate::headerdef::HeaderDef; use crate::imap::{GENERATED_PREFIX, markseen_on_imap_table}; -use crate::key::{DcKey, Fingerprint}; +use crate::key::{DcKey, Fingerprint, SignedPublicKey}; use crate::key::{ load_self_public_key, load_self_public_key_opt, self_fingerprint, self_fingerprint_opt, }; @@ -36,7 +36,7 @@ use crate::message::{ self, Message, MessageState, MsgId, Viewtype, insert_tombstone, rfc724_mid_exists, }; use crate::mimeparser::{ - AvatarAction, GossipedKey, MimeMessage, PreMessageMode, SystemMessage, parse_message_ids, + AvatarAction, MimeMessage, PreMessageMode, SystemMessage, parse_message_ids, }; use crate::param::{Param, Params}; use crate::peer_channels::{add_gossip_peer_from_header, insert_topic_stub, iroh_topic_from_str}; @@ -654,7 +654,6 @@ pub(crate) async fn receive_imf_inner( ) })? } else if let Some(to_id) = to_ids.first().copied().flatten() { - // handshake may mark contacts as verified and must be processed before chats are created observe_securejoin_on_other_device(context, &mime_parser, to_id) .await .with_context(|| { @@ -687,12 +686,6 @@ pub(crate) async fn receive_imf_inner( received_msg = None; } - let verified_encryption = has_verified_encryption(context, &mime_parser, from_id).await?; - - if verified_encryption == VerifiedEncryption::Verified { - mark_recipients_as_verified(context, from_id, &mime_parser).await?; - } - let is_old_contact_request; let received_msg = if let Some(received_msg) = received_msg { is_old_contact_request = false; @@ -2992,7 +2985,7 @@ struct GroupChangesInfo { extra_msgs: Vec<(String, SystemMessage, Option)>, } -/// Apply group member list, name, avatar and protection status changes from the MIME message. +/// Apply group member list, name and avatar changes from the MIME message. /// /// Returns [GroupChangesInfo]. /// @@ -3060,7 +3053,7 @@ async fn apply_group_changes( // just like we look at ChatGroupMemberRemovedFpr. // The result of the error is that info message // may contain display name of the wrong contact. - let fingerprint = key.public_key.dc_fingerprint().hex(); + let fingerprint = key.dc_fingerprint().hex(); if let Some(contact_id) = lookup_key_contact_by_fingerprint(context, &fingerprint).await? { @@ -3967,89 +3960,6 @@ async fn create_adhoc_group( Ok(Some((new_chat_id, create_blocked))) } -#[derive(Debug, PartialEq, Eq)] -enum VerifiedEncryption { - Verified, - NotVerified(String), // The string contains the reason why it's not verified -} - -/// Checks whether the message is allowed to appear in a protected chat. -/// -/// This means that it is encrypted and signed with a verified key. -async fn has_verified_encryption( - context: &Context, - mimeparser: &MimeMessage, - from_id: ContactId, -) -> Result { - use VerifiedEncryption::*; - - if !mimeparser.was_encrypted() { - return Ok(NotVerified("This message is not encrypted".to_string())); - }; - - if from_id == ContactId::SELF { - return Ok(Verified); - } - - let from_contact = Contact::get_by_id(context, from_id).await?; - - let Some(fingerprint) = from_contact.fingerprint() else { - return Ok(NotVerified( - "The message was sent without encryption".to_string(), - )); - }; - - if from_contact.get_verifier_id(context).await?.is_none() { - return Ok(NotVerified( - "The message was sent by non-verified contact".to_string(), - )); - } - - let signed_with_verified_key = mimeparser - .signature - .as_ref() - .is_some_and(|(signature, _)| *signature == fingerprint); - if signed_with_verified_key { - Ok(Verified) - } else { - Ok(NotVerified( - "The message was sent with non-verified encryption".to_string(), - )) - } -} - -async fn mark_recipients_as_verified( - context: &Context, - from_id: ContactId, - 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 || chat_verified) - { - let fingerprint = gossiped_key.public_key.dc_fingerprint().hex(); - let Some(to_id) = lookup_key_contact_by_fingerprint(context, &fingerprint).await? else { - continue; - }; - - if to_id == ContactId::SELF || to_id == from_id { - continue; - } - - mark_contact_id_as_verified(context, to_id, verifier_id).await?; - } - - Ok(()) -} - /// Returns the last message referenced from `References` header if it is in the database. /// /// For Delta Chat messages it is the last message in the chat of the sender. @@ -4117,7 +4027,7 @@ async fn add_or_lookup_contacts_by_address_list( async fn add_or_lookup_key_contacts( context: &Context, address_list: &[SingleInfo], - gossiped_keys: &BTreeMap, + gossiped_keys: &BTreeMap, fingerprints: &[Fingerprint], origin: Origin, ) -> Result>> { @@ -4134,7 +4044,7 @@ async fn add_or_lookup_key_contacts( // Iterator has not ran out of fingerprints yet. fp.hex() } else if let Some(key) = gossiped_keys.get(addr) { - key.public_key.dc_fingerprint().hex() + key.dc_fingerprint().hex() } else if context.is_self_addr(addr).await? { contact_ids.push(Some(ContactId::SELF)); continue; diff --git a/src/receive_imf/receive_imf_tests.rs b/src/receive_imf/receive_imf_tests.rs index 9a2cf9f74..f42d35a63 100644 --- a/src/receive_imf/receive_imf_tests.rs +++ b/src/receive_imf/receive_imf_tests.rs @@ -19,9 +19,7 @@ use crate::mimefactory; use crate::securejoin::get_securejoin_qr; use crate::smtp; use crate::test_utils; -use crate::test_utils::{ - TestContext, TestContextManager, alice_keypair, get_chat_msg, mark_as_verified, -}; +use crate::test_utils::{TestContext, TestContextManager, alice_keypair, get_chat_msg}; use crate::tools::{SystemTime, time}; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] @@ -3166,13 +3164,11 @@ async fn test_auto_accept_group_for_bots() -> Result<()> { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_auto_accept_protected_group_for_bots() -> Result<()> { +async fn test_auto_accept_encrypted_group_for_bots() -> Result<()> { let mut tcm = TestContextManager::new(); let alice = &tcm.alice().await; let bob = &tcm.bob().await; bob.set_config(Config::Bot, Some("1")).await.unwrap(); - mark_as_verified(alice, bob).await; - mark_as_verified(bob, alice).await; let group_id = alice.create_group_with_members("Group", &[bob]).await; let sent = alice.send_text(group_id, "Hello!").await; let msg = bob.recv_msg(&sent).await; @@ -3182,21 +3178,13 @@ async fn test_auto_accept_protected_group_for_bots() -> Result<()> { } /// Regression test for a bug where receive_imf() failed -/// if the sender of a verification-gossiping message +/// if the sender of a gossiping message /// also put itself into the To header. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_verification_gossip() -> Result<()> { +async fn test_gossip_sender_in_to_header() -> Result<()> { let mut tcm = TestContextManager::new(); - let alice = &tcm.alice().await; let bob = &tcm.bob().await; - let fiona = &tcm.fiona().await; - mark_as_verified(alice, bob).await; - mark_as_verified(bob, alice).await; - - // This is message sent by Alice with verified encryption - // that gossips Fiona's verification, - // and for some reason, Alice also put herself into the To: header. let imf_raw = include_bytes!("../../test-data/message/verification-gossip-also-sent-to-from.eml"); @@ -3204,12 +3192,6 @@ async fn test_verification_gossip() -> Result<()> { let msg = receive_imf(bob, imf_raw, false).await?.unwrap(); let msg = Message::load_from_db(bob, msg.msg_ids[0]).await?; assert_eq!(msg.text, "Hello!"); - assert!( - bob.add_or_lookup_contact(fiona) - .await - .is_verified(bob) - .await? - ); Ok(()) } @@ -4628,11 +4610,10 @@ async fn test_pre_msg_group_consistency() -> Result<()> { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_protected_group_add_remove_member_missing_key() -> Result<()> { +async fn test_group_add_remove_member_missing_key() -> Result<()> { let mut tcm = TestContextManager::new(); let alice = &tcm.alice().await; let bob = &tcm.bob().await; - mark_as_verified(alice, bob).await; let group_id = create_group(alice, "Group").await?; let alice_bob_id = alice.add_or_lookup_contact(bob).await.id; add_contact_to_chat(alice, group_id, alice_bob_id).await?; @@ -4640,7 +4621,6 @@ async fn test_protected_group_add_remove_member_missing_key() -> Result<()> { alice.sql.execute("DELETE FROM public_keys", ()).await?; let fiona = &tcm.fiona().await; - mark_as_verified(alice, fiona).await; let alice_fiona_id = alice.add_or_lookup_contact(fiona).await.id; add_contact_to_chat(alice, group_id, alice_fiona_id).await?; @@ -5133,7 +5113,7 @@ async fn test_rename_chat_after_creating_invite() -> Result<()> { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_unverified_member_msg() -> Result<()> { +async fn test_member_msg_downloaded() -> Result<()> { let mut tcm = TestContextManager::new(); let alice = &tcm.alice().await; let bob = &tcm.bob().await; @@ -5148,97 +5128,12 @@ async fn test_unverified_member_msg() -> Result<()> { let fiona_chat_id = fiona.get_last_msg().await.chat_id; let fiona_sent_msg = fiona.send_text(fiona_chat_id, "Hi").await; - // The message is by non-verified member, - // but the checks have been removed - // and the message should be downloaded as usual. let bob_msg = bob.recv_msg(&fiona_sent_msg).await; assert_eq!(bob_msg.download_state, DownloadState::Done); assert_eq!(bob_msg.text, "Hi"); Ok(()) } -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_dont_reverify_by_self_on_outgoing_msg() -> Result<()> { - let mut tcm = TestContextManager::new(); - let a0 = &tcm.alice().await; - let a1 = &tcm.alice().await; - let bob = &tcm.bob().await; - 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; - tcm.exec_securejoin_qr(a1, bob, &qr).await; - - // Shift time by one week to trigger gossip. - SystemTime::shift(Duration::from_secs(7 * 24 * 3600)); - - let a0_chat_id = a0.get_last_msg().await.chat_id; - let a0_sent_msg = a0.send_text(a0_chat_id, "Hi").await; - a1.recv_msg(&a0_sent_msg).await; - let a1_bob_id = a1.add_or_lookup_contact_id(bob).await; - let a1_fiona = a1.add_or_lookup_contact(fiona).await; - assert_eq!( - a1_fiona.get_verifier_id(a1).await?.unwrap().unwrap(), - a1_bob_id - ); - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_dont_verify_by_verified_by_unknown() -> Result<()> { - let mut tcm = TestContextManager::new(); - let a0 = &tcm.alice().await; - let a1 = &tcm.alice().await; - let bob = &tcm.bob().await; - 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(a0, bob, &qr).await; - - let qr = get_securejoin_qr(bob, None).await?; - tcm.exec_securejoin_qr(fiona, bob, &qr).await; - - // Bob verifies Fiona for Alice#0. - let bob_fiona_id = bob.add_or_lookup_contact_id(fiona).await; - add_contact_to_chat(bob, bob_chat_id, bob_fiona_id).await?; - let sent_msg = bob.pop_sent_msg().await; - a0.recv_msg(&sent_msg).await; - fiona.recv_msg(&sent_msg).await; - let a0_bob = a0.add_or_lookup_contact(bob).await; - let a0_fiona = a0.add_or_lookup_contact(fiona).await; - assert_eq!(a0_fiona.get_verifier_id(a0).await?, Some(Some(a0_bob.id))); - - let chat_id = a0.create_group_with_members("group", &[fiona]).await; - a0.set_chat_protected(chat_id).await; - a1.recv_msg(&a0.send_text(chat_id, "Hi").await).await; - let a1_fiona = a1.add_or_lookup_contact(fiona).await; - assert_eq!(a1_fiona.get_verifier_id(a1).await?, Some(None)); - - let some_time_to_regossip = Duration::from_secs(20 * 24 * 3600); - SystemTime::shift(some_time_to_regossip); - let fiona_chat_id = fiona.get_last_msg().await.chat_id; - fiona.set_chat_protected(fiona_chat_id).await; - a1.recv_msg(&fiona.send_text(fiona_chat_id, "Hi").await) - .await; - let a1_bob = a1.add_or_lookup_contact(bob).await; - // There was a bug that Bob is verified by Fiona on Alice's other device. - assert_eq!(a1_bob.get_verifier_id(a1).await?, Some(None)); - - SystemTime::shift(some_time_to_regossip); - tcm.execute_securejoin(a1, fiona).await; - a1.recv_msg(&fiona.send_text(fiona_chat_id, "Hi").await) - .await; - // But now Bob's verifier id must be updated because Fiona is verified by a known verifier - // (moreover, directly), so Alice has reverse verification chains on her devices. - assert_eq!(a1_bob.get_verifier_id(a1).await?, Some(Some(a1_fiona.id))); - Ok(()) -} - /// Tests that second device assigns outgoing encrypted messages /// to a single chat with key-contact even if the key of the contact is unknown. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] diff --git a/src/securejoin.rs b/src/securejoin.rs index 5bd9a2ca4..4e4ce9b31 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -7,7 +7,6 @@ use percent_encoding::{AsciiSet, utf8_percent_encode}; use crate::chat::{self, Chat, ChatId, ChatIdBlocked, get_chat_id_by_grpid, load_broadcast_secret}; use crate::config::Config; use crate::constants::{Blocked, Chattype, NON_ALPHANUMERIC_WITHOUT_DOT}; -use crate::contact::mark_contact_id_as_verified; use crate::contact::{Contact, ContactId, Origin}; use crate::context::Context; use crate::events::EventType; @@ -34,15 +33,6 @@ pub(crate) use qrinvite::QrInvite; use crate::token::Namespace; -/// Only new QR codes cause a verification on Alice's side. -/// When a QR code is too old, it is assumed that there was no direct QR scan, -/// and that the QR code was potentially published on a website, -/// so, Alice doesn't mark Bob as verified. -// TODO For backwards compatibility reasons, this is still using a rather large value. -// Set this to a lower value (e.g. 10 minutes) -// when Delta Chat v2.22.0 is sufficiently rolled out -const VERIFICATION_TIMEOUT_SECONDS: i64 = 7 * 24 * 3600; - const DISALLOWED_CHARACTERS: &AsciiSet = &NON_ALPHANUMERIC_WITHOUT_DOT.remove(b'_'); fn inviter_progress( @@ -86,7 +76,7 @@ fn shorten_name(name: &str, length: usize) -> String { pub async fn get_securejoin_qr(context: &Context, chat: Option) -> Result { /*======================================================= ==== Alice - the inviter side ==== - ==== Step 1 in "Setup verified contact" protocol ==== + ==== Step 1 in the Setup-Contact protocol ==== =======================================================*/ key::ensure_secret_key_exists(context).await.ok(); @@ -123,15 +113,9 @@ pub async fn get_securejoin_qr(context: &Context, chat: Option) -> Resul // Invite number is used to request the inviter key. let invitenumber = token::lookup_or_new(context, Namespace::InviteNumber, grpid).await?; - // Auth token is used to verify the key-contact - // if the token is not old - // and add the contact to the group + // Auth token authenticates the joiner + // and adds the contact to the group // if there is an associated group ID. - // - // We always generate a new auth token - // because auth tokens "expire" - // and can only be used to join groups - // without verification afterwards. let auth = create_id(); token::save(context, Namespace::Auth, grpid, &auth, time()).await?; @@ -255,7 +239,7 @@ pub async fn join_securejoin_with_ux_info( async fn securejoin(context: &Context, qr: &str) -> Result { /*======================================================== ==== Bob - the joiner's side ===== - ==== Step 2 in "Setup verified contact" protocol ===== + ==== Step 2 in the Setup-Contact protocol ===== ========================================================*/ info!(context, "Requesting secure-join ...",); @@ -303,8 +287,7 @@ async fn info_chat_id(context: &Context, contact_id: ContactId) -> Result Option { /*======================================================= ==== Alice - the inviter side ==== - ==== Step 3 in "Setup verified contact" protocol ==== + ==== Step 3 in the Setup-Contact protocol ==== =======================================================*/ // this message may be unencrypted (Bob, the joiner and the sender, might not have Alice's key yet) @@ -515,7 +493,7 @@ pub(crate) async fn handle_securejoin_handshake( SecureJoinStep::AuthRequired => { /*======================================================== ==== Bob - the joiner's side ===== - ==== Step 4 in "Setup verified contact" protocol ===== + ==== Step 4 in the Setup-Contact protocol ===== ========================================================*/ bob::handle_auth_required_or_pubkey(context, mime_message).await } @@ -585,8 +563,8 @@ pub(crate) async fn handle_securejoin_handshake( SecureJoinStep::RequestWithAuth => { /*========================================================== ==== Alice - the inviter side ==== - ==== Steps 5+6 in "Setup verified contact" protocol ==== - ==== Step 6 in "Out-of-band verified groups" protocol ==== + ==== Steps 5+6 in the Setup-Contact protocol ==== + ==== Step 6 in the Join-Group protocol ==== ==========================================================*/ // verify that Secure-Join-Fingerprint:-header matches the fingerprint of Bob @@ -613,15 +591,14 @@ pub(crate) async fn handle_securejoin_handshake( ); return Ok(HandshakeMessage::Ignore); }; - let Some((grpid, timestamp)) = context + let Some(grpid) = context .sql .query_row_optional( - "SELECT foreign_key, timestamp FROM tokens WHERE namespc=? AND token=?", + "SELECT foreign_key FROM tokens WHERE namespc=? AND token=?", (Namespace::Auth, auth), |row| { let foreign_key: String = row.get(0)?; - let timestamp: i64 = row.get(1)?; - Ok((foreign_key, timestamp)) + Ok(foreign_key) }, ) .await? @@ -654,12 +631,8 @@ pub(crate) async fn handle_securejoin_handshake( ); return Ok(HandshakeMessage::Ignore); } - info!(context, "Fingerprint verified via Auth code.",); + info!(context, "Auth code and fingerprint match.",); - // Mark the contact as verified if auth code is less than VERIFICATION_TIMEOUT_SECONDS seconds old. - if time() < timestamp + VERIFICATION_TIMEOUT_SECONDS { - mark_contact_id_as_verified(context, contact_id, Some(ContactId::SELF)).await?; - } if sender_contact.blocked { warn!(context, "Ignoring {step} message: {contact_id} is blocked."); return Ok(HandshakeMessage::Ignore); @@ -692,7 +665,7 @@ pub(crate) async fn handle_securejoin_handshake( Ok(HandshakeMessage::Done) } else { let chat_id = info_chat_id(context, contact_id).await?; - // Setup verified contact. + // Setup contact. send_alice_handshake_msg(context, contact_id, "vc-contact-confirm") .await .context("failed sending vc-contact-confirm message")?; @@ -703,7 +676,7 @@ pub(crate) async fn handle_securejoin_handshake( } /*======================================================= ==== Bob - the joiner's side ==== - ==== Step 7 in "Setup verified contact" protocol ==== + ==== Step 7 in the Setup-Contact protocol ==== =======================================================*/ SecureJoinStep::ContactConfirm => { context.emit_event(EventType::SecurejoinJoinerProgress { @@ -753,16 +726,14 @@ pub(crate) async fn handle_securejoin_handshake( /// we can make some conclusions of it. /// /// If we see self-sent {vc,vg}-request-with-auth, -/// we know that we are Bob (joiner-observer) -/// that just marked peer (Alice) as verified +/// we know that we are Bob (joiner-observer), /// either after receiving {vc,vg}-auth-required /// or immediately after scanning the QR-code /// if the key was already known. /// /// If we see self-sent vc-contact-confirm or vg-member-added message, /// we know that we are Alice (inviter-observer) -/// that just marked peer (Bob) as verified -/// in response to correct vc-request-with-auth message. +/// responding to a correct vc-request-with-auth message. pub(crate) async fn observe_securejoin_on_other_device( context: &Context, mime_message: &MimeMessage, @@ -810,13 +781,12 @@ pub(crate) async fn observe_securejoin_on_other_device( return Ok(HandshakeMessage::Ignore); }; - if key.public_key.dc_fingerprint() != contact_fingerprint { + if key.dc_fingerprint() != contact_fingerprint { // Fingerprint does not match, ignore. warn!(context, "Fingerprint does not match."); return Ok(HandshakeMessage::Ignore); } - mark_contact_id_as_verified(context, contact_id, Some(ContactId::SELF)).await?; if contact.blocked && step != SecureJoinStep::MemberAdded { // Contact might be blocked after another device had issued the message. Still, to avoid // membership inconsistency on devices, don't ignore "vg-member-added". diff --git a/src/securejoin/bob.rs b/src/securejoin/bob.rs index 4e3d98d6a..91076c529 100644 --- a/src/securejoin/bob.rs +++ b/src/securejoin/bob.rs @@ -106,7 +106,6 @@ pub(super) async fn start_protocol(context: &Context, invite: QrInvite) -> Resul // If QR code is a group invite // and we are already in the chat, // nothing needs to be done. - // Even if Alice is not verified, we don't send anything. context.emit_event(EventType::SecurejoinJoinerProgress { contact_id: invite.contact_id(), progress: JoinerProgress::Succeeded.into_u16(), @@ -270,7 +269,7 @@ pub(super) async fn handle_auth_required_or_pubkey( continue; } - info!(context, "Fingerprint verified.",); + info!(context, "Fingerprint matches.",); let chat_id = private_chat_id(context, &invite).await?; delete_securejoin_wait_msg(context, chat_id) .await @@ -449,8 +448,8 @@ async fn private_chat_id(context: &Context, invite: &QrInvite) -> Result /// /// This is the chat in which you want to notify the user as well. /// -/// When joining a group this is the [`ChatId`] of the group chat, when verifying a -/// contact this is the [`ChatId`] of the single chat. +/// When joining a group this is the [`ChatId`] of the group chat, +/// when setting up a contact this is the [`ChatId`] of the single chat. /// The group chat will be created if it does not yet exist. async fn joining_chat_id( context: &Context, @@ -496,7 +495,7 @@ async fn joining_chat_id( pub(crate) enum JoinerProgress { /// vg-vc-request-with-auth sent. /// - /// Typically shows as "alice@addr verified, introducing myself." + /// Typically shows as "introducing myself." RequestWithAuthSent, /// Completed securejoin. Succeeded, diff --git a/src/securejoin/securejoin_tests.rs b/src/securejoin/securejoin_tests.rs index 777c0a1d3..daa373bbc 100644 --- a/src/securejoin/securejoin_tests.rs +++ b/src/securejoin/securejoin_tests.rs @@ -1,5 +1,3 @@ -use std::time::Duration; - use deltachat_contact_tools::EmailAddress; use regex::Regex; @@ -8,7 +6,6 @@ use crate::chat::{CantSendReason, ChatId, add_contact_to_chat, remove_contact_fr use crate::chatlist::Chatlist; use crate::constants::Chattype; use crate::key::self_fingerprint; -use crate::mimeparser::{GossipedKey, SystemMessage}; use crate::qr::Qr; use crate::receive_imf::receive_imf; use crate::stock_str::{self, messages_e2ee_info_msg}; @@ -16,7 +13,6 @@ use crate::test_utils::{ AVATAR_64x64_BYTES, AVATAR_64x64_DEDUPLICATED, TestContext, TestContextManager, TimeShiftFalsePositiveNote, get_chat_msg, sync, }; -use crate::tools::SystemTime; #[derive(PartialEq)] enum SetupContactCase { @@ -193,10 +189,7 @@ async fn test_setup_contact_ext(case: SetupContactCase) -> (TestContext, TestCon ); if case == SetupContactCase::WrongAliceGossip { - let wrong_pubkey = GossipedKey { - public_key: load_self_public_key(&bob).await.unwrap(), - verified: false, - }; + let wrong_pubkey = load_self_public_key(&bob).await.unwrap(); let alice_pubkey = msg .gossiped_keys .insert(alice_addr.to_string(), wrong_pubkey) @@ -206,7 +199,6 @@ async fn test_setup_contact_ext(case: SetupContactCase) -> (TestContext, TestCon .await .unwrap(); assert_eq!(handshake_msg, HandshakeMessage::Ignore); - assert_eq!(contact_bob.is_verified(&alice).await.unwrap(), false); msg.gossiped_keys .insert(alice_addr.to_string(), alice_pubkey) @@ -215,20 +207,17 @@ async fn test_setup_contact_ext(case: SetupContactCase) -> (TestContext, TestCon .await .unwrap(); assert_eq!(handshake_msg, HandshakeMessage::Ignore); - assert!(contact_bob.is_verified(&alice).await.unwrap()); return (alice, bob); } - // Alice should not yet have Bob verified let contact_bob = alice.add_or_lookup_contact_no_key(&bob).await; let contact_bob_id = contact_bob.id; assert_eq!(contact_bob.is_key_contact(), true); - assert_eq!(contact_bob.is_verified(&alice).await.unwrap(), false); assert_eq!(contact_bob.get_authname(), ""); tcm.section("Step 5+6: Alice receives vc-request-with-auth, sends vc-contact-confirm"); alice.recv_msg_trash(&sent).await; - assert_eq!(contact_bob.is_verified(&alice).await.unwrap(), true); + assert_eq!(contact_bob.e2ee_avail(&alice).await.unwrap(), true); let contact_bob = Contact::get_by_id(&alice, contact_bob_id).await.unwrap(); assert_eq!(contact_bob.get_authname(), "Bob Examplenet"); assert!(contact_bob.get_name().is_empty()); @@ -248,7 +237,7 @@ async fn test_setup_contact_ext(case: SetupContactCase) -> (TestContext, TestCon 1 ); - // Check Alice got the verified message in her single chat. + // Check Alice got the message in her single chat. { let chat = alice.get_chat(&bob).await; let msg = get_chat_msg(&alice, chat.get_id(), 0, 1).await; @@ -280,14 +269,14 @@ async fn test_setup_contact_ext(case: SetupContactCase) -> (TestContext, TestCon "vc-contact-confirm" ); - // Bob has verified Alice already. + // Bob has Alice's key already. // - // Alice may not have verified Bob yet. - assert_eq!(contact_alice.is_verified(&bob.ctx).await.unwrap(), true); + // Alice may not have Bob's key yet. + assert_eq!(contact_alice.e2ee_avail(&bob.ctx).await.unwrap(), true); // Step 7: Bob receives vc-contact-confirm bob.recv_msg_trash(&sent).await; - assert_eq!(contact_alice.is_verified(&bob.ctx).await.unwrap(), true); + assert_eq!(contact_alice.e2ee_avail(&bob.ctx).await.unwrap(), true); let contact_alice = Contact::get_by_id(&bob.ctx, contact_alice_id) .await .unwrap(); @@ -359,13 +348,11 @@ async fn test_setup_contact_bob_knows_alice() -> Result<()> { bob_fp.hex() ); - // Alice should not yet have Bob verified let contact_bob = alice.add_or_lookup_contact_no_key(bob).await; - assert_eq!(contact_bob.is_verified(alice).await?, false); tcm.section("Step 5+6: Alice receives vc-request-with-auth, sends vc-contact-confirm"); alice.recv_msg_trash(&sent).await; - assert_eq!(contact_bob.is_verified(alice).await?, true); + assert_eq!(contact_bob.e2ee_avail(alice).await?, true); // Check Alice signalled success via the SecurejoinInviterProgress event. let event = alice @@ -399,16 +386,14 @@ async fn test_setup_contact_bob_knows_alice() -> Result<()> { "vc-contact-confirm" ); - // Bob has verified Alice already. + // Bob has Alice's key already. let contact_alice = bob.add_or_lookup_contact_no_key(alice).await; - assert_eq!(contact_alice.is_verified(bob).await?, true); + assert_eq!(contact_alice.e2ee_avail(bob).await?, true); - // Alice confirms that Bob is now verified. - // // This does not change anything for Bob. tcm.section("Step 7: Bob receives vc-contact-confirm"); bob.recv_msg_trash(&sent).await; - assert_eq!(contact_alice.is_verified(bob).await?, true); + assert_eq!(contact_alice.e2ee_avail(bob).await?, true); Ok(()) } @@ -567,13 +552,11 @@ async fn test_secure_join_group_ext(v3: bool, remove_invite: bool) -> Result<()> bob_fp ); - // Alice should not yet have Bob verified let contact_bob = alice.add_or_lookup_contact_no_key(&bob).await; - assert_eq!(contact_bob.is_verified(&alice).await?, false); tcm.section("Step 5+6: Alice receives vg-request-with-auth, sends vg-member-added"); alice.recv_msg_trash(&sent).await; - assert_eq!(contact_bob.is_verified(&alice).await?, true); + assert_eq!(contact_bob.e2ee_avail(&alice).await?, true); // Check Alice signalled success via the SecurejoinInviterProgress event. let event = alice @@ -615,7 +598,7 @@ async fn test_secure_join_group_ext(v3: bool, remove_invite: bool) -> Result<()> assert!(msg.get_header(HeaderDef::AutocryptGossip).is_some()); { - // Now Alice's chat with Bob should still be hidden, the verified message should + // Now Alice's chat with Bob should still be hidden, the member-added message should // appear in the group chat. if v3 { assert!( @@ -641,17 +624,17 @@ async fn test_secure_join_group_ext(v3: bool, remove_invite: bool) -> Result<()> assert_eq!(msg.get_text(), expected_text); } - // Bob has verified Alice already. + // Bob has Alice's key already. // - // Alice may not have verified Bob yet. + // Alice may not have Bob's key yet. let contact_alice = bob.add_or_lookup_contact_no_key(&alice).await; - assert_eq!(contact_alice.is_verified(&bob).await?, true); + assert_eq!(contact_alice.e2ee_avail(&bob).await?, true); tcm.section("Step 7: Bob receives vg-member-added"); bob.recv_msg(&sent).await; { - // Bob has Alice verified, message shows up in the group chat. - assert_eq!(contact_alice.is_verified(&bob).await?, true); + // The message shows up in the group chat. + assert_eq!(contact_alice.e2ee_avail(&bob).await?, true); let chat = bob.get_chat(&alice).await; assert_eq!( chat.blocked, @@ -832,11 +815,7 @@ async fn test_unknown_sender() -> Result<()> { Ok(()) } -/// Tests that Bob gets Alice as verified -/// if `vc-contact-confirm` is lost. -/// Previously `vc-contact-confirm` was used -/// to confirm backward verification, -/// but backward verification is not tracked anymore. +/// Tests that Bob gets Alice's key if `vc-contact-confirm` is lost. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_lost_contact_confirm() { let mut tcm = TestContextManager::new(); @@ -858,16 +837,16 @@ async fn test_lost_contact_confirm() { let sent = bob.pop_sent_msg().await; alice.recv_msg_trash(&sent).await; - // Alice has Bob verified now. + // Alice has Bob's key now. let contact_bob = alice.add_or_lookup_contact_no_key(&bob).await; - assert_eq!(contact_bob.is_verified(&alice).await.unwrap(), true); + assert_eq!(contact_bob.e2ee_avail(&alice).await.unwrap(), true); // Alice sends vc-contact-confirm, but it gets lost. let _sent_vc_contact_confirm = alice.pop_sent_msg().await; - // Bob has alice as verified too, even though vc-contact-confirm is lost. + // Bob has Alice's key too, even though vc-contact-confirm is lost. let contact_alice = bob.add_or_lookup_contact_no_key(&alice).await; - assert_eq!(contact_alice.is_verified(&bob).await.unwrap(), true); + assert_eq!(contact_alice.e2ee_avail(&bob).await.unwrap(), true); } /// Tests Bob joining two groups by scanning two QR codes @@ -974,7 +953,7 @@ async fn test_parallel_setup_contact(bob_deletes_fiona_contact: bool) -> Result< bob.recv_msg_trash(&sent_fiona_vc_contact_confirm).await; let bob_fiona_contact = Contact::get_by_id(bob, bob_fiona_contact_id).await.unwrap(); - assert_eq!(bob_fiona_contact.is_verified(bob).await.unwrap(), true); + assert_eq!(bob_fiona_contact.e2ee_avail(bob).await.unwrap(), true); } // Alice gets online and previously started SecureJoin process finishes. @@ -990,7 +969,7 @@ async fn test_parallel_setup_contact(bob_deletes_fiona_contact: bool) -> Result< bob.recv_msg_trash(&sent_alice_vc_contact_confirm).await; let bob_alice_contact_id = bob.add_or_lookup_contact_id(alice).await; let bob_alice_contact = Contact::get_by_id(bob, bob_alice_contact_id).await.unwrap(); - assert_eq!(bob_alice_contact.is_verified(bob).await.unwrap(), true); + assert_eq!(bob_alice_contact.e2ee_avail(bob).await.unwrap(), true); bob.assert_warn("Message does not match expected fingerprint") .await; @@ -1027,8 +1006,6 @@ async fn test_wrong_auth_token() -> Result<()> { alice.recv_msg_trash(&sent).await; - let alice_bob_contact = alice.add_or_lookup_contact(bob).await; - assert!(!alice_bob_contact.is_verified(alice).await?); alice.assert_warn("invalid auth code").await; Ok(()) } @@ -1100,123 +1077,6 @@ async fn test_send_avatar_in_securejoin() -> Result<()> { Ok(()) } -/// Tests that scanning a QR code week later -/// allows Bob to establish a contact with Alice, -/// but does not mark Bob as verified for Alice. -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_expired_contact_auth_token() -> Result<()> { - let mut tcm = TestContextManager::new(); - let alice = &tcm.alice().await; - let bob = &tcm.bob().await; - - // Alice creates a QR code. - let qr = get_securejoin_qr(alice, None).await?; - - // One week passes, QR code expires. - SystemTime::shift(Duration::from_secs(7 * 24 * 3600)); - - // Bob scans the QR code. - join_securejoin(bob, &qr).await?; - - // vc-request - alice.recv_msg_trash(&bob.pop_sent_msg().await).await; - - // vc-auth-requried - bob.recv_msg_trash(&alice.pop_sent_msg().await).await; - - // vc-request-with-auth - alice.recv_msg_trash(&bob.pop_sent_msg().await).await; - - // Bob should not be verified for Alice. - let contact_bob = alice.add_or_lookup_contact_no_key(bob).await; - assert_eq!(contact_bob.is_verified(alice).await.unwrap(), false); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_expired_group_auth_token() -> Result<()> { - let mut tcm = TestContextManager::new(); - let alice = &tcm.alice().await; - let bob = &tcm.bob().await; - - let alice_chat_id = chat::create_group(alice, "Group").await?; - - // Alice creates a group QR code. - let qr = get_securejoin_qr(alice, Some(alice_chat_id)).await.unwrap(); - - // One week passes, QR code expires. - SystemTime::shift(Duration::from_secs(7 * 24 * 3600)); - - // Bob scans the QR code. - join_securejoin(bob, &qr).await?; - - // vg-request - alice.recv_msg_trash(&bob.pop_sent_msg().await).await; - - // vg-auth-requried - bob.recv_msg_trash(&alice.pop_sent_msg().await).await; - - // vg-request-with-auth - alice.recv_msg_trash(&bob.pop_sent_msg().await).await; - - // vg-member-added - let bob_member_added_msg = bob.recv_msg(&alice.pop_sent_msg().await).await; - assert!(bob_member_added_msg.is_info()); - assert_eq!( - bob_member_added_msg.get_info_type(), - SystemMessage::MemberAddedToGroup - ); - - // Bob should not be verified for Alice. - let contact_bob = alice.add_or_lookup_contact_no_key(bob).await; - assert_eq!(contact_bob.is_verified(alice).await.unwrap(), false); - - Ok(()) -} - -/// Tests that old token is considered expired -/// even if sync message just arrived. -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_expired_synced_auth_token() -> Result<()> { - let mut tcm = TestContextManager::new(); - let alice = &tcm.alice().await; - let alice2 = &tcm.alice().await; - let bob = &tcm.bob().await; - - alice.set_config_bool(Config::SyncMsgs, true).await?; - alice2.set_config_bool(Config::SyncMsgs, true).await?; - - // Alice creates a QR code on the second device. - let qr = get_securejoin_qr(alice2, None).await?; - - alice2.send_sync_msg().await.unwrap(); - let sync_msg = alice2.pop_sent_msg().await; - - // One week passes, QR code expires. - SystemTime::shift(Duration::from_secs(7 * 24 * 3600)); - - alice.recv_msg_trash(&sync_msg).await; - - // Bob scans the QR code. - join_securejoin(bob, &qr).await?; - - // vc-request - alice.recv_msg_trash(&bob.pop_sent_msg().await).await; - - // vc-auth-requried - bob.recv_msg_trash(&alice.pop_sent_msg().await).await; - - // vc-request-with-auth - alice.recv_msg_trash(&bob.pop_sent_msg().await).await; - - // Bob should not be verified for Alice. - let contact_bob = alice.add_or_lookup_contact_no_key(bob).await; - assert_eq!(contact_bob.is_verified(alice).await.unwrap(), false); - - Ok(()) -} - /// Tests that attempting to join already joined group does nothing. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_rejoin_group() -> Result<()> { diff --git a/src/sql/migrations/migrations_tests.rs b/src/sql/migrations/migrations_tests.rs index a1bb6bbdb..ed684002b 100644 --- a/src/sql/migrations/migrations_tests.rs +++ b/src/sql/migrations/migrations_tests.rs @@ -110,7 +110,6 @@ async fn test_key_contacts_migration_autocrypt() -> Result<()> { pgp_bob.fingerprint().unwrap(), pgp_bob.public_key(&t).await?.unwrap().dc_fingerprint() ); - assert_eq!(pgp_bob.get_verifier_id(&t).await?, None); // Hidden address-contact can't be looked up by name. assert!( @@ -149,7 +148,6 @@ async fn test_key_contacts_migration_email1() -> Result<()> { assert_eq!(email_bob.origin, Origin::OutgoingTo); assert_eq!(email_bob.e2ee_avail(&t).await?, false); assert_eq!(email_bob.fingerprint(), None); - assert_eq!(email_bob.get_verifier_id(&t).await?, None); Ok(()) } @@ -183,7 +181,6 @@ async fn test_key_contacts_migration_email2() -> Result<()> { assert_eq!(email_bob.origin, Origin::OutgoingTo); assert_eq!(email_bob.e2ee_avail(&t).await?, false); assert_eq!(email_bob.fingerprint(), None); - assert_eq!(email_bob.get_verifier_id(&t).await?, None); Ok(()) } @@ -227,7 +224,6 @@ async fn test_key_contacts_migration_verified() -> Result<()> { pgp_bob.fingerprint().unwrap(), pgp_bob.public_key(&t).await?.unwrap().dc_fingerprint() ); - assert_eq!(pgp_bob.get_verifier_id(&t).await?, Some(None)); Ok(()) } diff --git a/src/stats.rs b/src/stats.rs index 65b4f4981..e23736e63 100644 --- a/src/stats.rs +++ b/src/stats.rs @@ -3,7 +3,7 @@ //! If this is enabled, a JSON file with some anonymous statistics //! will be sent to a bot once a week. -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeMap; use anyhow::{Context as _, Result}; use deltachat_derive::FromSql; @@ -16,7 +16,7 @@ use serde::Serialize; use crate::chat::{self, ChatId, MuteDuration}; use crate::config::Config; use crate::constants::{Chattype, DC_VERSION_STR}; -use crate::contact::{Contact, ContactId, Origin, import_vcard, mark_contact_id_as_verified}; +use crate::contact::{Contact, ContactId, Origin, import_vcard}; use crate::context::Context; use crate::key::{DcKey, load_self_public_key}; use crate::log::LogExt; @@ -52,21 +52,12 @@ struct Statistics { sending_disabled_timestamps: Vec, } -#[derive(Serialize, PartialEq)] -enum VerifiedStatus { - Direct, - Transitive, - TransitiveViaBot, - Opportunistic, - Unencrypted, -} - #[derive(Serialize)] struct ContactStat { #[serde(skip_serializing)] id: ContactId, - verified: VerifiedStatus, + encrypted: bool, // If one of the boolean properties is false, // we leave them away. @@ -79,9 +70,6 @@ struct ContactStat { last_seen: u64, - #[serde(skip_serializing_if = "Option::is_none")] - transitive_chain: Option, - /// Whether the contact was established after stats-sending was enabled #[serde(skip_serializing_if = "is_false")] new: bool, @@ -93,8 +81,7 @@ fn is_false(b: &bool) -> bool { #[derive(Serialize, Default)] struct MessageStats { - verified: u32, - unverified_encrypted: u32, + encrypted: u32, unencrypted: u32, only_to_self: u32, } @@ -159,14 +146,10 @@ struct JoinedInvite { /// Whether the contact already existed before. /// If this is false, then a contact was newly created. already_existed: bool, - /// If a contact already existed, - /// this tells us whether the contact was verified already. - already_verified: bool, /// The type of the invite: - /// "contact" for single chat invites that setup a verified contact, + /// "contact" for single chat invites, /// "group" for invites that invite to a group, /// "broadcast" for invites that invite to a broadcast channel. - /// The invite also performs the contact verification 'along the way'. typ: String, } @@ -423,7 +406,6 @@ async fn get_stats_chat_id(context: &Context) -> Result { .await? .first() .context("Statistics bot vCard does not contain a contact")?; - mark_contact_id_as_verified(context, contact_id, Some(ContactId::SELF)).await?; let chat_id = if let Some(res) = ChatId::lookup_by_contact(context, contact_id).await? { // Already exists, no need to create. @@ -438,84 +420,30 @@ async fn get_stats_chat_id(context: &Context) -> Result { } async fn get_contact_stats(context: &Context, last_old_contact: u32) -> Result> { - let mut verified_by_map: BTreeMap = BTreeMap::new(); - let mut bot_ids: BTreeSet = BTreeSet::new(); - let mut contacts = context .sql .query_map_vec( - "SELECT id, fingerprint<>'', verifier, last_seen, is_bot FROM contacts c + "SELECT id, fingerprint<>'', last_seen, is_bot FROM contacts c WHERE id>9 AND origin>? AND addr<>?", (Origin::Hidden, STATISTICS_BOT_EMAIL), |row| { let id = row.get(0)?; - let is_encrypted: bool = row.get(1)?; - let verifier: ContactId = row.get(2)?; - let last_seen: u64 = row.get(3)?; - let bot: bool = row.get(4)?; - - let verified = match (is_encrypted, verifier) { - (true, ContactId::SELF) => VerifiedStatus::Direct, - (true, ContactId::UNDEFINED) => VerifiedStatus::Opportunistic, - (true, _) => VerifiedStatus::Transitive, // TransitiveViaBot will be filled later - (false, _) => VerifiedStatus::Unencrypted, - }; - - if verifier != ContactId::UNDEFINED { - verified_by_map.insert(id, verifier); - } - - if bot { - bot_ids.insert(id); - } + let encrypted: bool = row.get(1)?; + let last_seen: u64 = row.get(2)?; + let bot: bool = row.get(3)?; Ok(ContactStat { id, - verified, + encrypted, bot, single_chat: false, // will be filled later last_seen, - transitive_chain: None, // will be filled later new: id.to_u32() > last_old_contact, }) }, ) .await?; - // Fill TransitiveViaBot and transitive_chain - for contact in &mut contacts { - if contact.verified == VerifiedStatus::Transitive { - let mut transitive_chain: u32 = 0; - let mut has_bot = false; - let mut current_verifier_id = contact.id; - - while current_verifier_id != ContactId::SELF && transitive_chain < 100 { - current_verifier_id = match verified_by_map.get(¤t_verifier_id) { - Some(id) => *id, - None => { - // The chain ends here, probably because some verification was done - // before we started recording verifiers. - // It's unclear how long the chain really is. - transitive_chain = 0; - break; - } - }; - if bot_ids.contains(¤t_verifier_id) { - has_bot = true; - } - transitive_chain = transitive_chain.saturating_add(1); - } - - if transitive_chain > 0 { - contact.transitive_chain = Some(transitive_chain); - } - - if has_bot { - contact.verified = VerifiedStatus::TransitiveViaBot; - } - } - } - // Fill single_chat for contact in &mut contacts { let single_chat = context @@ -537,18 +465,16 @@ async fn get_message_stats(context: &Context) -> Result = context .sql .query_map_collect( - "SELECT chattype, verified, unverified_encrypted, unencrypted, only_to_self + "SELECT chattype, unverified_encrypted, unencrypted, only_to_self FROM stats_msgs", (), |row| { let chattype: Chattype = row.get(0)?; - let verified: u32 = row.get(1)?; - let unverified_encrypted: u32 = row.get(2)?; - let unencrypted: u32 = row.get(3)?; - let only_to_self: u32 = row.get(4)?; + let encrypted: u32 = row.get(1)?; + let unencrypted: u32 = row.get(2)?; + let only_to_self: u32 = row.get(3)?; let message_stats = MessageStats { - verified, - unverified_encrypted, + encrypted, unencrypted, only_to_self, }; @@ -620,32 +546,6 @@ async fn update_message_stats_inner(context: &Context, chattype: Chattype) -> Re (), )?; - // This table will hold all verified chats, - // i.e. all chats that only contain verified contacts. - t.execute( - "CREATE TEMP TABLE temp.verified_chats ( - id INTEGER PRIMARY KEY - ) STRICT", - (), - )?; - - // Verified chats are chats that are not empty, - // and do not contain any unverified contacts - t.execute( - "INSERT INTO temp.verified_chats - SELECT id FROM chats - WHERE id>9 - AND id NOT IN (SELECT id FROM temp.empty_chats) - AND NOT EXISTS( - SELECT * - FROM contacts, chats_contacts - WHERE chats_contacts.contact_id=contacts.id AND chats_contacts.chat_id=chats.id - AND contacts.id>9 - AND contacts.verifier=0 - )", - (), - )?; - // This table will hold all single chats. t.execute( "CREATE TEMP TABLE temp.chat_with_correct_type ( @@ -672,21 +572,11 @@ async fn update_message_stats_inner(context: &Context, chattype: Chattype) -> Re .to_string(); let params = (last_counted_msg_id, ContactId::SELF, stats_bot_chat_id); - let verified: u32 = t.query_row( - &format!( - "SELECT COUNT(*) FROM msgs - WHERE chat_id IN temp.verified_chats - AND {general_requirements}" - ), - params, - |row| row.get(0), - )?; - - let unverified_encrypted: u32 = t.query_row( + let encrypted: u32 = t.query_row( &format!( // (param GLOB '*\nc=1*' OR param GLOB 'c=1*') matches all messages that are end-to-end encrypted "SELECT COUNT(*) FROM msgs - WHERE chat_id NOT IN temp.verified_chats AND chat_id NOT IN temp.empty_chats + WHERE chat_id NOT IN temp.empty_chats AND (param GLOB '*\nc=1*' OR param GLOB 'c=1*') AND {general_requirements}" ), @@ -697,7 +587,7 @@ async fn update_message_stats_inner(context: &Context, chattype: Chattype) -> Re let unencrypted: u32 = t.query_row( &format!( "SELECT COUNT(*) FROM msgs - WHERE chat_id NOT IN temp.verified_chats AND chat_id NOT IN temp.empty_chats + WHERE chat_id NOT IN temp.empty_chats AND NOT (param GLOB '*\nc=1*' OR param GLOB 'c=1*') AND {general_requirements}" ), @@ -715,7 +605,6 @@ async fn update_message_stats_inner(context: &Context, chattype: Chattype) -> Re |row| row.get(0), )?; - t.execute("DROP TABLE temp.verified_chats", ())?; t.execute("DROP TABLE temp.empty_chats", ())?; t.execute("DROP TABLE temp.chat_with_correct_type", ())?; @@ -726,18 +615,11 @@ async fn update_message_stats_inner(context: &Context, chattype: Chattype) -> Re )?; t.execute( "UPDATE stats_msgs SET - verified=verified+?, unverified_encrypted=unverified_encrypted+?, unencrypted=unencrypted+?, only_to_self=only_to_self+? WHERE chattype=?", - ( - verified, - unverified_encrypted, - unencrypted, - only_to_self, - chattype, - ), + (encrypted, unencrypted, only_to_self, chattype), )?; Ok(()) @@ -851,9 +733,6 @@ pub(crate) async fn count_securejoin_invite(context: &Context, invite: &QrInvite // then its origin is UnhandledSecurejoinQrScan. let already_existed = contact.origin > Origin::UnhandledSecurejoinQrScan; - // Check whether the contact was verified already before the QR scan. - let already_verified = contact.is_verified(context).await?; - let typ = match invite { QrInvite::Contact { .. } => "contact", QrInvite::Group { .. } => "group", @@ -863,9 +742,10 @@ pub(crate) async fn count_securejoin_invite(context: &Context, invite: &QrInvite context .sql .execute( + // already_verified is unused but NOT NULL without a default. "INSERT INTO stats_securejoin_invites (already_existed, already_verified, type) - VALUES (?, ?, ?)", - (already_existed, already_verified, typ), + VALUES (?, 0, ?)", + (already_existed, typ), ) .await?; @@ -876,16 +756,14 @@ async fn get_securejoin_invite_stats(context: &Context) -> Result Result<()> { assert!(chat.is_encrypted(alice).await?); let contacts = get_chat_contacts(alice, chat_id).await?; assert_eq!(contacts.len(), 1); - let contact = Contact::get_by_id(alice, contacts[0]).await?; - assert!(contact.is_verified(alice).await?); - let msg = get_chat_msg(alice, chat_id, 1, 2).await; assert_eq!(msg.get_filename().unwrap(), "statistics.txt"); @@ -113,10 +110,9 @@ async fn test_stats_one_contact() -> Result<()> { contact_info.get("direct_chat").unwrap(), &serde_json::Value::Bool(true) ); - assert!(contact_info.get("transitive_chain").is_none(),); assert_eq!( - contact_info.get("verified").unwrap(), - &serde_json::Value::String("Opportunistic".to_string()) + contact_info.get("encrypted").unwrap(), + &serde_json::Value::Bool(true) ); assert_eq!( contact_info.get("new").unwrap(), @@ -168,25 +164,16 @@ async fn test_message_stats() -> Result<()> { check_stats(&update_get_stats(alice).await, &expected); alice.send_text(encrypted_chat.id, "foo").await; - expected - .get_mut(&Chattype::Single) - .unwrap() - .unverified_encrypted += 1; + expected.get_mut(&Chattype::Single).unwrap().encrypted += 1; check_stats(&update_get_stats(alice).await, &expected); alice.send_text(encrypted_chat.id, "foo").await; - expected - .get_mut(&Chattype::Single) - .unwrap() - .unverified_encrypted += 1; + expected.get_mut(&Chattype::Single).unwrap().encrypted += 1; check_stats(&update_get_stats(alice).await, &expected); let group = alice.create_group_with_members("Pizza", &[bob]).await; alice.send_text(group, "foo").await; - expected - .get_mut(&Chattype::Group) - .unwrap() - .unverified_encrypted += 1; + expected.get_mut(&Chattype::Group).unwrap().encrypted += 1; check_stats(&update_get_stats(alice).await, &expected); tcm.execute_securejoin(alice, bob).await; @@ -208,7 +195,7 @@ async fn test_message_stats() -> Result<()> { let group = alice.create_group_with_members("Pizza 2", &[bob]).await; alice.send_text(group, "foo").await; - expected.get_mut(&Chattype::Group).unwrap().verified += 1; + expected.get_mut(&Chattype::Group).unwrap().encrypted += 1; check_stats(&update_get_stats(alice).await, &expected); let empty_broadcast = create_broadcast(alice, "Channel".to_string()).await?; @@ -245,13 +232,13 @@ async fn test_message_stats() -> Result<()> { SystemTime::shift(Duration::from_secs(8 * 24 * 3600)); tcm.send_recv(alice, bob, "Hi").await; - expected.get_mut(&Chattype::Single).unwrap().verified += 1; + expected.get_mut(&Chattype::Single).unwrap().encrypted += 1; update_message_stats(alice).await?; update_message_stats(alice).await?; tcm.send_recv(alice, bob, "Hi").await; - expected.get_mut(&Chattype::Single).unwrap().verified += 1; + expected.get_mut(&Chattype::Single).unwrap().encrypted += 1; tcm.send_recv(alice, bob, "Hi").await; - expected.get_mut(&Chattype::Single).unwrap().verified += 1; + expected.get_mut(&Chattype::Single).unwrap().encrypted += 1; check_stats(&send_and_read_stats(alice).await, &expected); alice.assert_warn("Missing securejoin source").await; @@ -428,7 +415,6 @@ async fn test_stats_securejoin_invites() -> Result<()> { tcm.exec_securejoin_qr(alice, bob, &qr).await; expected.push(JoinedInvite { already_existed: false, - already_verified: false, typ: "contact".to_string(), }); check_stats(alice, &expected).await; @@ -437,7 +423,6 @@ async fn test_stats_securejoin_invites() -> Result<()> { tcm.exec_securejoin_qr(alice, bob, &qr).await; expected.push(JoinedInvite { already_existed: true, - already_verified: true, typ: "contact".to_string(), }); check_stats(alice, &expected).await; @@ -449,7 +434,6 @@ async fn test_stats_securejoin_invites() -> Result<()> { tcm.exec_securejoin_qr(alice, bob, &qr).await; expected.push(JoinedInvite { already_existed: true, - already_verified: true, typ: "group".to_string(), }); check_stats(alice, &expected).await; @@ -463,7 +447,6 @@ async fn test_stats_securejoin_invites() -> Result<()> { tcm.exec_securejoin_qr(alice, bob, &qr).await; expected.push(JoinedInvite { already_existed: true, - already_verified: false, typ: "group".to_string(), }); check_stats(alice, &expected).await; diff --git a/src/stock_str.rs b/src/stock_str.rs index 16b3ce315..4d5b6ed7e 100644 --- a/src/stock_str.rs +++ b/src/stock_str.rs @@ -69,6 +69,7 @@ pub enum StockMessage { #[strum(props(fallback = "Fingerprints"))] FingerPrints = 30, + /// Unused. Was used in status messages. #[strum(props(fallback = "%1$s verified."))] ContactVerified = 35, @@ -873,13 +874,6 @@ pub(crate) fn secure_join_broadcast_qr_description(context: &Context, chat: &Cha translated(context, StockMessage::SecureJoinBrodcastQRDescription).replace1(chat.get_name()) } -/// Stock string: `%1$s verified.`. -#[allow(dead_code)] -pub(crate) fn contact_verified(context: &Context, contact: &Contact) -> String { - let addr = contact.get_display_name(); - translated(context, StockMessage::ContactVerified).replace1(addr) -} - /// Stock string: `Archived chats`. pub(crate) fn archived_chats(context: &Context) -> String { translated(context, StockMessage::ArchivedChats) diff --git a/src/stock_str/stock_str_tests.rs b/src/stock_str/stock_str_tests.rs index 8f501c64f..a20c96d78 100644 --- a/src/stock_str/stock_str_tests.rs +++ b/src/stock_str/stock_str_tests.rs @@ -48,12 +48,11 @@ async fn test_stock_str() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_stock_string_repl_str() { let t = TestContext::new().await; - let contact_id = Contact::create(&t.ctx, "Someone", "someone@example.org") - .await - .unwrap(); - let contact = Contact::get_by_id(&t.ctx, contact_id).await.unwrap(); // uses %1$s substitution - assert_eq!(contact_verified(&t, &contact), "Someone verified."); + assert_eq!( + subject_for_new_contact(&t, "Someone"), + "Message from Someone" + ); // We have no string using %1$d to test... } diff --git a/src/test_utils.rs b/src/test_utils.rs index 21d8ef172..d31cc8321 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -30,9 +30,7 @@ use crate::chat::{ use crate::chatlist::Chatlist; use crate::config::Config; use crate::constants::{Blocked, Chattype, DC_GCL_NO_SPECIALS}; -use crate::contact::{ - Contact, ContactId, Modifier, Origin, import_vcard, make_vcard, mark_contact_id_as_verified, -}; +use crate::contact::{Contact, ContactId, Modifier, Origin, import_vcard, make_vcard}; use crate::context::Context; use crate::events::{Event, EventEmitter, EventType, Events}; use crate::key::{self, DcKey, self_fingerprint}; @@ -942,27 +940,6 @@ ORDER BY id" contact_id } - /// Returns a single [`Chat`] with another account address-contact. - /// Panics if it doesn't exist. - /// May return a blocked chat. - /// - /// This first creates a contact using the configured details on the other account, then - /// gets the single chat with this contact. - pub async fn get_email_chat(&self, other: &TestContext) -> Chat { - let contact = self.add_or_lookup_address_contact(other).await; - - let chat_id = ChatIdBlocked::lookup_by_contact(&self.ctx, contact.id) - .await - .unwrap() - .map(|chat_id_blocked| chat_id_blocked.id) - .expect( - "There is no chat with this contact. \ - Hint: Use create_email_chat() instead of get_email_chat() if this is expected.", - ); - - Chat::load_from_db(&self.ctx, chat_id).await.unwrap() - } - /// Returns a single [`Chat`] with another account key-contact. /// Panics if the chat does not exist. /// @@ -1207,18 +1184,6 @@ ORDER BY id" 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(); - } - /// Allow reception of unencrypted messages. pub async fn allow_unencrypted(&self) -> Result<()> { self.set_config_bool(Config::ForceEncryption, false).await?; @@ -1263,7 +1228,6 @@ pub async fn encrypt_raw_message( addr: context.get_primary_self_addr().await?, public_key: public_key.clone(), prefer_encrypt: EncryptPreference::Mutual, - verified: false, }; let mut encryption_keyring = vec![public_key.clone()]; @@ -1762,14 +1726,6 @@ pub(crate) async fn get_chat_msg( Message::load_from_db(&t.ctx, msg_id).await.unwrap() } -/// Saves the other account's public key as verified -pub(crate) async fn mark_as_verified(this: &TestContext, other: &TestContext) { - let contact_id = this.add_or_lookup_contact_id(other).await; - mark_contact_id_as_verified(this, contact_id, Some(ContactId::SELF)) - .await - .unwrap(); -} - /// Pops a sync message from alice0 and receives it on alice1. Should be used after an action on /// alice0's side that implies sending a sync message. pub(crate) async fn sync(alice0: &TestContext, alice1: &TestContext) { diff --git a/src/tests/aeap.rs b/src/tests/aeap.rs index dc2dbb127..d24ec1dce 100644 --- a/src/tests/aeap.rs +++ b/src/tests/aeap.rs @@ -16,7 +16,6 @@ use crate::receive_imf::receive_imf; use crate::securejoin::get_securejoin_qr; use crate::test_utils::TestContext; use crate::test_utils::TestContextManager; -use crate::test_utils::mark_as_verified; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_change_primary_self_addr() -> Result<()> { @@ -46,35 +45,21 @@ async fn test_change_primary_self_addr() -> Result<()> { enum ChatForTransition { Single, GroupChat, - VerifiedGroup, } use ChatForTransition::*; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_aeap_transition_0() { - check_aeap_transition(Single, false).await; + check_aeap_transition(Single).await; } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_aeap_transition_1() { - check_aeap_transition(GroupChat, false).await; -} -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_aeap_transition_0_verified() { - check_aeap_transition(Single, true).await; -} -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_aeap_transition_1_verified() { - check_aeap_transition(GroupChat, true).await; -} -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_aeap_transition_2_verified() { - check_aeap_transition(VerifiedGroup, true).await; + check_aeap_transition(GroupChat).await; } /// Happy path test for AEAP. /// - `chat_for_transition`: Which chat the transition message should be sent in -/// - `verified`: Whether Alice and Bob verified each other -async fn check_aeap_transition(chat_for_transition: ChatForTransition, verified: bool) { +async fn check_aeap_transition(chat_for_transition: ChatForTransition) { const ALICE_NEW_ADDR: &str = "alice2@example.net"; let mut tcm = TestContextManager::new(); @@ -84,19 +69,10 @@ async fn check_aeap_transition(chat_for_transition: ChatForTransition, verified: tcm.send_recv_accept(alice, bob, "Hi").await; tcm.send_recv(bob, alice, "Hi back").await; - if verified { - mark_as_verified(alice, bob).await; - mark_as_verified(bob, alice).await; - } - - let mut groups = vec![ + let groups = vec![ chat::create_group(bob, "Group 0").await.unwrap(), chat::create_group(bob, "Group 1").await.unwrap(), ]; - if verified { - groups.push(chat::create_group(bob, "Group 2").await.unwrap()); - groups.push(chat::create_group(bob, "Group 3").await.unwrap()); - } let alice_contact = bob.add_or_lookup_contact_id(alice).await; for group in &groups { @@ -111,13 +87,6 @@ async fn check_aeap_transition(chat_for_transition: ChatForTransition, verified: let sent = bob.send_text(groups[1], "group created").await; let group1_alice = alice.recv_msg(&sent).await.chat_id; - let mut group3_alice = None; - if verified { - tcm.section("Promoting group 3"); - let sent = bob.send_text(groups[3], "group created").await; - group3_alice = Some(alice.recv_msg(&sent).await.chat_id); - } - tcm.change_addr(alice, ALICE_NEW_ADDR).await; tcm.section("Alice sends another message to Bob, this time from her new addr"); @@ -125,7 +94,6 @@ async fn check_aeap_transition(chat_for_transition: ChatForTransition, verified: let chat_to_send = match chat_for_transition { Single => alice.create_chat(bob).await.id, GroupChat => group1_alice, - VerifiedGroup => group3_alice.expect("No verified group"), }; let sent = alice .send_text(chat_to_send, "Hello from my new addr!") @@ -240,16 +208,13 @@ async fn test_write_to_alice_after_aeap() -> Result<()> { let alice_grp_id = chat::create_group(alice, "Group").await?; let qr = get_securejoin_qr(alice, Some(alice_grp_id)).await?; tcm.exec_securejoin_qr(bob, alice, &qr).await; - let bob_alice_contact = bob.add_or_lookup_contact(alice).await; - assert!(bob_alice_contact.is_verified(bob).await?); + let _bob_alice_contact = bob.add_or_lookup_contact(alice).await; let bob_alice_chat = bob.create_chat(alice).await; let bob_unprotected_grp_id = bob.create_group_with_members("Group", &[alice]).await; tcm.change_addr(alice, "alice@someotherdomain.xyz").await; let sent = alice.send_text(alice_grp_id, "Hello!").await; bob.recv_msg(&sent).await; - - assert!(bob_alice_contact.is_verified(bob).await?); let bob_alice_chat = Chat::load_from_db(bob, bob_alice_chat.id).await?; let mut msg = Message::new_text("hi".to_string()); chat::send_msg(bob, bob_alice_chat.id, &mut msg).await?; diff --git a/src/tests/verified_chats.rs b/src/tests/verified_chats.rs index 07557b45e..d42e6cebb 100644 --- a/src/tests/verified_chats.rs +++ b/src/tests/verified_chats.rs @@ -6,144 +6,14 @@ use crate::chat::{self, Chat, add_contact_to_chat, remove_contact_from_chat, sen use crate::config::Config; use crate::constants::Chattype; use crate::contact::{Contact, ContactId}; -use crate::key; use crate::key::self_fingerprint; -use crate::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; -use crate::test_utils::{ - E2EE_INFO_MSGS, TestContext, TestContextManager, get_chat_msg, mark_as_verified, -}; +use crate::test_utils::{TestContextManager, get_chat_msg}; use crate::tools::SystemTime; -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_verified_single_chat_not_broken_by_classical() { - check_verified_single_chat_protection_not_broken(true).await; -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_verified_single_chat_not_broken_by_device_change() { - check_verified_single_chat_protection_not_broken(false).await; -} - -async fn check_verified_single_chat_protection_not_broken(by_classical_email: bool) { - let mut tcm = TestContextManager::new(); - let alice = tcm.alice().await; - let bob = tcm.bob().await; - - tcm.execute_securejoin(&alice, &bob).await; - - assert_verified(&alice, &bob).await; - assert_verified(&bob, &alice).await; - - if 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(); - alice.assert_warn("unencrypted message").await; - let contact = alice.add_or_lookup_contact(&bob).await; - assert_eq!(contact.is_verified(&alice).await.unwrap(), true); - assert_verified(&alice, &bob).await; - } else { - tcm.section("Bob sets up another Delta Chat device"); - let bob2 = tcm.unconfigured().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(&bob2).await; - assert_eq!(contact.is_verified(&alice).await.unwrap(), false); - assert_verified(&alice, &bob).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).await; -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_create_verified_single_chat() -> Result<()> { - let mut tcm = TestContextManager::new(); - let alice = tcm.alice().await; - let bob = tcm.bob().await; - let fiona = tcm.fiona().await; - - tcm.execute_securejoin(&alice, &bob).await; - tcm.execute_securejoin(&bob, &fiona).await; - assert_verified(&alice, &bob).await; - assert_verified(&bob, &alice).await; - assert_verified(&bob, &fiona).await; - assert_verified(&fiona, &bob).await; - - 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 - ); - - { - 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::ChatE2ee - ); - } - - // 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(),); - - tcm.section("Fiona reinstalls DC"); - drop(fiona); - - let fiona_new = tcm.unconfigured().await; - fiona_new.configure_addr("fiona@example.net").await; - key::ensure_secret_key_exists(&fiona_new).await?; - - tcm.send_recv(&fiona_new, &alice, "I have a new device") - .await; - - // Alice gets a new chat with new Fiona contact. - { - let chat = alice.get_chat(&fiona_new).await; - - let msg = get_chat_msg(&alice, chat.id, 1, E2EE_INFO_MSGS + 1).await; - assert_eq!(msg.text, "I have a new device"); - } - - Ok(()) -} - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_missing_key_reexecute_securejoin() -> Result<()> { let mut tcm = TestContextManager::new(); @@ -168,108 +38,6 @@ async fn test_missing_key_reexecute_securejoin() -> Result<()> { Ok(()) } -/// Tests that receiving unencrypted message -/// does not disable protection of single chat. -/// -/// Instead, an email-chat is created. -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_degrade_verified_single_chat() -> Result<()> { - let mut tcm = TestContextManager::new(); - let alice = &tcm.alice().await; - let bob = &tcm.bob().await; - alice.allow_unencrypted().await?; - - mark_as_verified(alice, bob).await; - - let alice_chat = alice.create_chat(bob).await; - - receive_imf( - alice, - b"From: Bob \r\n\ - To: alice@example.org\r\n\ - Message-ID: <1234-2@example.net>\r\n\ - \r\n\ - hello\r\n", - false, - ) - .await?; - - let msg0 = get_chat_msg(alice, alice_chat.id, 0, 1).await; - let enabled = stock_str::messages_e2ee_info_msg(alice); - assert_eq!(msg0.text, enabled); - assert_eq!(msg0.param.get_cmd(), SystemMessage::ChatE2ee); - - let email_chat = alice.get_email_chat(bob).await; - assert!(!email_chat.is_encrypted(alice).await?); - let email_msg = get_chat_msg(alice, email_chat.id, 0, 1).await; - assert_eq!(email_msg.text, "hello".to_string()); - assert!(!email_msg.is_system_message()); - - Ok(()) -} - -/// Alice is offline for some time. mvbox_move is 0. -/// When she comes online, first her inbox is synced and then her mvbox. -/// 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 mut tcm = TestContextManager::new(); - let alice = &tcm.alice().await; - let bob = &tcm.bob().await; - let msg_incoming = test_utils::receive_encrypted_imf( - alice, - bob, - b"From: Bob \r\n\ - To: alice@example.org\r\n\ - Message-ID: <1234-2-3@example.org>\r\n\ - Date: Sun, 08 Dec 2019 19:00:27 +0000\r\n\ - \r\n\ - Thanks, Alice!\r\n", - ) - .await?; - - let msg_sent = test_utils::receive_encrypted_imf( - alice, - alice, - b"From: alice@example.org\r\n\ - To: Bob \r\n\ - Message-ID: <1234-2-4@example.org>\r\n\ - Date: Sat, 07 Dec 2019 19:00:27 +0000\r\n\ - \r\n\ - Happy birthday, Bob!\r\n", - ) - .await?; - - // The "Happy birthday" message should be shown first, and then the "Thanks" message - assert!(msg_sent.sort_timestamp < msg_incoming.sort_timestamp); - - 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; - 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).await; - - Ok(()) -} - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_outgoing_mua_msg() -> Result<()> { let mut tcm = TestContextManager::new(); @@ -277,11 +45,7 @@ async fn test_outgoing_mua_msg() -> Result<()> { let bob = &tcm.bob().await; alice.allow_unencrypted().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).await; let sent = receive_imf( alice, @@ -313,7 +77,6 @@ async fn test_outgoing_encrypted_msg() -> Result<()> { let alice = &tcm.alice().await; let bob = &tcm.bob().await; - mark_as_verified(alice, bob).await; let chat_id = alice.create_chat(bob).await.id; let raw = include_bytes!("../../test-data/message/thunderbird_with_autocrypt.eml"); receive_imf(alice, raw, false).await?; @@ -327,23 +90,15 @@ async fn test_outgoing_encrypted_msg() -> Result<()> { /// 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). +/// However, the unencrypted message must NOT be assigned to an encrypted chat. #[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; alice.allow_unencrypted().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; @@ -372,8 +127,7 @@ async fn test_reply() -> Result<()> { Ok(()) } -/// Tests that message from old DC setup does not break -/// new verified chat. +/// Tests that a message from an old DC setup does not break the new chat. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_message_from_old_dc_setup() -> Result<()> { let mut tcm = TestContextManager::new(); @@ -381,7 +135,6 @@ async fn test_message_from_old_dc_setup() -> Result<()> { let bob_old = &tcm.unconfigured().await; bob_old.configure_addr("bob@example.net").await; - mark_as_verified(bob_old, alice).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") @@ -391,55 +144,20 @@ async fn test_message_from_old_dc_setup() -> Result<()> { tcm.section("Bob reinstalls DC"); let bob = &tcm.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).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 - // and the message goes to another chat, so the verification preserves. - assert!(contact.is_verified(alice).await.unwrap()); + // The outdated Bob's Autocrypt header isn't applied, + // so the message goes to another chat. + assert_ne!(contact.id, msg.from_id); 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; - - mark_as_verified(&alice, &bob).await; - mark_as_verified(&bob, &alice).await; - - alice.create_chat(&bob).await; - assert_verified(&alice, &bob).await; - - tcm.section("Bob reinstalls DC"); - drop(bob); - let bob_new = tcm.unconfigured().await; - bob_new.configure_addr("bob@example.net").await; - key::ensure_secret_key_exists(&bob_new).await?; - - tcm.execute_securejoin(&bob_new, &alice).await; - assert_verified(&alice, &bob_new).await; - - Ok(()) -} - -/// Tests that on the second device of a group creator the first message is -/// `SystemMessage::ChatE2ee` and the second one is the message populating the group. +/// Tests that on a second device the e2ee info message +/// sorts before the older message that created the group. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_create_grp_multidev() -> Result<()> { let mut tcm = TestContextManager::new(); @@ -473,7 +191,7 @@ async fn test_create_grp_multidev() -> Result<()> { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_verified_member_added_reordering() -> Result<()> { +async fn test_member_added_reordering() -> Result<()> { let mut tcm = TestContextManager::new(); let alice = &tcm.alice().await; let bob = &tcm.bob().await; @@ -502,7 +220,7 @@ 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, so unverified group is created. + // the "Member added" message, so she cannot send yet. let fiona_received_message = fiona.recv_msg(&bob_sent_message).await; let fiona_chat = Chat::load_from_db(fiona, fiona_received_message.chat_id).await?; assert!(!fiona_chat.can_send(fiona).await?); @@ -518,16 +236,12 @@ async fn test_verified_member_added_reordering() -> Result<()> { #[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 { - mark_as_verified(&bob, &alice).await; - } else { - tcm.send_recv_accept(&alice, &bob, "hi").await; - } + 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; @@ -544,7 +258,7 @@ async fn test_no_unencrypted_name_if_encrypted() -> Result<()> { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_verified_lost_member_added() -> Result<()> { +async fn test_lost_member_added() -> Result<()> { let mut tcm = TestContextManager::new(); let alice = &tcm.alice().await; let bob = &tcm.bob().await; @@ -609,10 +323,9 @@ async fn test_verified_lost_member_added() -> Result<()> { Ok(()) } -/// Tests handling of resent .xdc arriving before "Member added" -/// in a verified group +/// Tests handling of resent .xdc arriving before "Member added". #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_verified_chat_editor_reordering() -> Result<()> { +async fn test_chat_editor_reordering() -> Result<()> { let mut tcm = TestContextManager::new(); let alice = &tcm.alice().await; let bob = &tcm.bob().await; @@ -620,7 +333,7 @@ async fn test_verified_chat_editor_reordering() -> Result<()> { tcm.execute_securejoin(alice, bob).await; - tcm.section("Alice creates a protected group with Bob"); + tcm.section("Alice creates a group with Bob"); let alice_chat_id = alice.create_group_with_members("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; @@ -667,9 +380,6 @@ async fn test_verified_chat_editor_reordering() -> Result<()> { 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"); @@ -677,122 +387,8 @@ async fn test_verified_chat_editor_reordering() -> Result<()> { charlie .golden_test_chat( charlie_received_xdc.chat_id, - "verified_chats_editor_reordering", + "encrypted_chats_editor_reordering", ) .await; Ok(()) } - -/// Tests that already verified contact -/// does not get a new "verifier" -/// via gossip. -/// -/// Directly verifying is still possible. -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_no_reverification() -> Result<()> { - let mut tcm = TestContextManager::new(); - let alice = &tcm.alice().await; - let bob = &tcm.bob().await; - let charlie = &tcm.charlie().await; - let fiona = &tcm.fiona().await; - - tcm.execute_securejoin(alice, bob).await; - tcm.execute_securejoin(alice, charlie).await; - tcm.execute_securejoin(alice, fiona).await; - - tcm.section("Alice creates a protected group with Bob, Charlie and Fiona"); - 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; - - // Charlie is verified by Alice for Bob. - let bob_charlie_contact = bob.add_or_lookup_contact(charlie).await; - assert_eq!( - bob_charlie_contact - .get_verifier_id(bob) - .await? - .unwrap() - .unwrap(), - bob_alice_id - ); - - let fiona_rcvd_msg = fiona.recv_msg(&alice_sent).await; - let fiona_chat_id = fiona_rcvd_msg.chat_id; - let fiona_sent = fiona.send_text(fiona_chat_id, "Post by Fiona").await; - bob.recv_msg(&fiona_sent).await; - - // Charlie should still be verified by Alice, not by Fiona. - let bob_charlie_contact = bob.add_or_lookup_contact(charlie).await; - assert_eq!( - bob_charlie_contact - .get_verifier_id(bob) - .await? - .unwrap() - .unwrap(), - bob_alice_id - ); - - // Bob can still verify Charlie directly. - tcm.execute_securejoin(bob, charlie).await; - let bob_charlie_contact = bob.add_or_lookup_contact(charlie).await; - assert_eq!( - bob_charlie_contact - .get_verifier_id(bob) - .await? - .unwrap() - .unwrap(), - ContactId::SELF - ); - - Ok(()) -} - -/// Tests that if our second device observes -/// us gossiping a verification, -/// it is not treated as direct verification. -/// -/// Direct verifications should only happen -/// as a result of SecureJoin. -/// If we see our second device gossiping -/// a verification of some contact, -/// it may be indirect verification, -/// so we should mark the contact as verified, -/// but with unknown verifier. -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_no_single_verification_via_bcc() -> Result<()> { - let mut tcm = TestContextManager::new(); - let alice = &tcm.alice().await; - let alice2 = &tcm.alice().await; - let bob = &tcm.bob().await; - - 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; - - // Alice 2 observes Alice 1 gossiping verification for Bob. - // Alice 2 does not know if Alice 1 has verified Bob directly though. - let alice2_bob_contact = alice2.add_or_lookup_contact(bob).await; - assert_eq!(alice2_bob_contact.is_verified(alice2).await?, true); - - // There is some verifier, but it is unknown to Alice's second device. - assert_eq!( - alice2_bob_contact.get_verifier_id(alice2).await?, - Some(None) - ); - - Ok(()) -} - -// ============== Helper Functions ============== - -async fn assert_verified(this: &TestContext, other: &TestContext) { - let contact = this.add_or_lookup_contact(other).await; - assert_eq!(contact.is_verified(this).await.unwrap(), true); -} diff --git a/src/token.rs b/src/token.rs index 26db09c2e..7d75f35f7 100644 --- a/src/token.rs +++ b/src/token.rs @@ -2,7 +2,7 @@ //! //! Functions to read/write token from/to the database. A token is any string associated with a key. //! -//! Tokens are used in SecureJoin verification protocols. +//! Tokens are used in SecureJoin protocols. use anyhow::Result; use deltachat_derive::{FromSql, ToSql}; diff --git a/standards.md b/standards.md index 649eec2ee..bb81e76b6 100644 --- a/standards.md +++ b/standards.md @@ -15,7 +15,7 @@ Push | IMAP IDLE ([RFC 2177][]) Quota | IMAP QUOTA extension ([RFC 2087][]) Client/server identification | IMAP ID extension ([RFC 2971][]) End-to-end encryption | [Autocrypt Level 1][], OpenPGP ([RFC 4880][]), Security Multiparts for MIME ([RFC 1847][]) and [“Mixed Up” Encryption repairing](https://datatracker.ietf.org/doc/html/draft-dkg-openpgp-pgpmime-message-mangling-00) -Detect/prevent active attacks | [securejoin][] protocols +Invite links for chats | [securejoin][] protocols Compare public keys | [openpgp4fpr][] URI Scheme Metadata minimization | Header Protection for Cryptographically Protected Email ([RFC 9788][]) Configuration assistance | [Autoconfigure](https://web.archive.org/web/20210402044801/https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration) and [Autodiscover][] diff --git a/test-data/golden/verified_chats_editor_reordering b/test-data/golden/encrypted_chats_editor_reordering similarity index 100% rename from test-data/golden/verified_chats_editor_reordering rename to test-data/golden/encrypted_chats_editor_reordering