Skip to content

Commit 2c7d51f

Browse files
r10sHocuri
andauthored
feat: add "e2ee encrypted" info message to all e2ee chats (#7008)
this PR adds a info message "messages are end-to-end-encrypted" also for chats created by eg. vcards. by the removal of lock icons, this is a good place to hint for that in addition; this is also what eg. whatsapp and others are doing the wording itself is tweaked at deltachat/deltachat-android#3817 (and there is also the rough idea to make the message a little more outstanding, by some more dedicated colors) ~~did not test in practise, if this leads to double "e2ee info messages" on secure join, tests look good, however.~~ EDIT: did lots of practise tests meanwhile :) most of the changes in this PR are about test ... ftr, in another PR, after 2.0 reeases, there could probably quite some code cleanup wrt set-protection, protection-disabled etc. --------- Co-authored-by: Hocuri <[email protected]>
1 parent a2df295 commit 2c7d51f

File tree

29 files changed

+261
-122
lines changed

29 files changed

+261
-122
lines changed

deltachat-ffi/deltachat.h

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4535,12 +4535,12 @@ int dc_msg_is_info (const dc_msg_t* msg);
45354535
* - DC_INFO_MEMBER_ADDED_TO_GROUP (4) - "Member CONTACT added by OTHER_CONTACT"
45364536
* - DC_INFO_MEMBER_REMOVED_FROM_GROUP (5) - "Member CONTACT removed by OTHER_CONTACT"
45374537
* - DC_INFO_EPHEMERAL_TIMER_CHANGED (10) - "Disappearing messages CHANGED_TO by CONTACT"
4538-
* - DC_INFO_PROTECTION_ENABLED (11) - Info-message for "Chat is now protected"
4539-
* - DC_INFO_PROTECTION_DISABLED (12) - Info-message for "Chat is no longer protected"
4538+
* - DC_INFO_PROTECTION_ENABLED (11) - Info-message for "Chat is protected"
45404539
* - DC_INFO_INVALID_UNENCRYPTED_MAIL (13) - Info-message for "Provider requires end-to-end encryption which is not setup yet",
45414540
* the UI should change the corresponding string using #DC_STR_INVALID_UNENCRYPTED_MAIL
45424541
* and also offer a way to fix the encryption, eg. by a button offering a QR scan
45434542
* - DC_INFO_WEBXDC_INFO_MESSAGE (32) - Info-message created by webxdc app sending `update.info`
4543+
* - DC_INFO_CHAT_E2EE (50) - Info-message for "Chat is end-to-end-encrypted"
45444544
*
45454545
* For the messages that refer to a CONTACT,
45464546
* dc_msg_get_info_contact_id() returns the contact ID.
@@ -4593,9 +4593,10 @@ uint32_t dc_msg_get_info_contact_id (const dc_msg_t* msg);
45934593
#define DC_INFO_LOCATION_ONLY 9
45944594
#define DC_INFO_EPHEMERAL_TIMER_CHANGED 10
45954595
#define DC_INFO_PROTECTION_ENABLED 11
4596-
#define DC_INFO_PROTECTION_DISABLED 12
4596+
#define DC_INFO_PROTECTION_DISABLED 12 // deprecated 2025-07
45974597
#define DC_INFO_INVALID_UNENCRYPTED_MAIL 13
45984598
#define DC_INFO_WEBXDC_INFO_MESSAGE 32
4599+
#define DC_INFO_CHAT_E2EE 50
45994600

46004601

46014602
/**
@@ -6898,9 +6899,7 @@ void dc_event_unref(dc_event_t* event);
68986899
/// Used in summaries.
68996900
#define DC_STR_GIF 23
69006901

6901-
/// "Encrypted message"
6902-
///
6903-
/// Used in subjects of outgoing messages.
6902+
/// @deprecated 2025-07, this string is no longer needed.
69046903
#define DC_STR_ENCRYPTEDMSG 24
69056904

69066905
/// "End-to-end encryption available."
@@ -7605,14 +7604,15 @@ void dc_event_unref(dc_event_t* event);
76057604
/// Used as a device message after a successful backup transfer.
76067605
#define DC_STR_BACKUP_TRANSFER_MSG_BODY 163
76077606

7608-
/// "Messages are guaranteed to be end-to-end encrypted from now on."
7607+
/// "Messages are end-to-end encrypted."
76097608
///
76107609
/// Used in info messages.
76117610
#define DC_STR_CHAT_PROTECTION_ENABLED 170
76127611

76137612
/// "%1$s sent a message from another device."
76147613
///
76157614
/// Used in info messages.
7615+
/// @deprecated 2025-07
76167616
#define DC_STR_CHAT_PROTECTION_DISABLED 171
76177617

76187618
/// "Others will only see this group after you sent a first message."

deltachat-jsonrpc/src/api/types/message.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,9 @@ pub enum SystemMessageType {
416416
/// Chat ephemeral message timer is changed.
417417
EphemeralTimerChanged,
418418

419+
// Chat is e2ee
420+
ChatE2ee,
421+
419422
// Chat protection state changed
420423
ChatProtectionEnabled,
421424
ChatProtectionDisabled,
@@ -450,6 +453,7 @@ impl From<deltachat::mimeparser::SystemMessage> for SystemMessageType {
450453
SystemMessage::LocationStreamingEnabled => SystemMessageType::LocationStreamingEnabled,
451454
SystemMessage::LocationOnly => SystemMessageType::LocationOnly,
452455
SystemMessage::EphemeralTimerChanged => SystemMessageType::EphemeralTimerChanged,
456+
SystemMessage::ChatE2ee => SystemMessageType::ChatE2ee,
453457
SystemMessage::ChatProtectionEnabled => SystemMessageType::ChatProtectionEnabled,
454458
SystemMessage::ChatProtectionDisabled => SystemMessageType::ChatProtectionDisabled,
455459
SystemMessage::MultiDeviceSync => SystemMessageType::MultiDeviceSync,

deltachat-jsonrpc/typescript/test/online.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,10 @@ describe("online tests", function () {
9595
false,
9696
);
9797

98-
expect(messageList).have.length(1);
99-
const message = await dc.rpc.getMessage(accountId2, messageList[0]);
98+
// There are 2 messages in the chat:
99+
// 'Messages are end-to-end encrypted' (info message) and 'Hello'
100+
expect(messageList).have.length(2);
101+
const message = await dc.rpc.getMessage(accountId2, messageList[1]);
100102
expect(message.text).equal("Hello");
101103
expect(message.showPadlock).equal(true);
102104
});

deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@
1313
from ._utils import futuremethod
1414
from .rpc import Rpc
1515

16+
E2EE_INFO_MSGS = 1
17+
"""
18+
The number of info messages added to new e2ee chats.
19+
Currently this is "End-to-end encryption available".
20+
"""
21+
1622

1723
class ACFactory:
1824
"""Test account factory."""

deltachat-rpc-client/tests/test_multidevice.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ def test_one_account_send_bcc_setting(acfactory, log, direct_imap):
3636
assert ac1.get_config("bcc_self") == "1"
3737

3838
# Second client receives only second message, but not the first.
39+
ev_msg = ac1_clone.wait_for_event(EventType.MSGS_CHANGED)
40+
assert ac1_clone.get_message_by_id(ev_msg.msg_id).get_snapshot().text == "Messages are end-to-end encrypted."
41+
3942
ev_msg = ac1_clone.wait_for_event(EventType.MSGS_CHANGED)
4043
assert ac1_clone.get_message_by_id(ev_msg.msg_id).get_snapshot().text == msg_out.get_snapshot().text
4144

deltachat-rpc-client/tests/test_something.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from deltachat_rpc_client import Contact, EventType, Message, events
1414
from deltachat_rpc_client.const import ChatType, DownloadState, MessageState
15+
from deltachat_rpc_client.pytestplugin import E2EE_INFO_MSGS
1516
from deltachat_rpc_client.rpc import JsonRpcError
1617

1718

@@ -457,8 +458,12 @@ def test_wait_next_messages(acfactory) -> None:
457458
alice_chat_bot.send_text("Hello!")
458459

459460
next_messages = next_messages_task.result()
460-
assert len(next_messages) == 1
461-
snapshot = next_messages[0].get_snapshot()
461+
462+
if len(next_messages) == E2EE_INFO_MSGS:
463+
next_messages += bot.wait_next_messages()
464+
465+
assert len(next_messages) == 1 + E2EE_INFO_MSGS
466+
snapshot = next_messages[0 + E2EE_INFO_MSGS].get_snapshot()
462467
assert snapshot.text == "Hello!"
463468

464469

python/src/deltachat/testplugin.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@
2020
from . import Account, account_hookimpl, const, get_core_info
2121
from .events import FFIEventLogger, FFIEventTracker
2222

23+
E2EE_INFO_MSGS = 1
24+
"""
25+
The number of info messages added to new e2ee chats.
26+
Currently this is "End-to-end encryption available".
27+
"""
28+
2329

2430
def pytest_addoption(parser):
2531
group = parser.getgroup("deltachat testplugin options")
@@ -606,7 +612,7 @@ def get_protected_chat(self, ac1: Account, ac2: Account):
606612
ev = ac2._evtracker.get_matching("DC_EVENT_MSGS_CHANGED")
607613
msg = ac2.get_message_by_id(ev.data2)
608614
assert msg is not None
609-
assert msg.text == "Messages are guaranteed to be end-to-end encrypted from now on."
615+
assert msg.text == "Messages are end-to-end encrypted."
610616
msg = ac2._evtracker.wait_next_incoming_message()
611617
assert msg is not None
612618
assert "Member Me " in msg.text and " added by " in msg.text

python/tests/test_0_complex_or_slow.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,7 @@ def test_qr_verified_group_and_chatting(acfactory, lp):
133133
assert "added" in msg.text.lower()
134134

135135
assert any(
136-
m.is_system_message() and m.text == "Messages are guaranteed to be end-to-end encrypted from now on."
137-
for m in msg.chat.get_messages()
136+
m.is_system_message() and m.text == "Messages are end-to-end encrypted." for m in msg.chat.get_messages()
138137
)
139138
lp.sec("ac1: send message")
140139
msg_out = chat1.send_text("hello")
@@ -338,7 +337,7 @@ def test_use_new_verified_group_after_going_online(acfactory, data, tmp_path, lp
338337
assert contact.addr == ac1.get_config("addr")
339338
chat2 = msg_in.chat
340339
assert chat2.is_protected()
341-
assert chat2.get_messages()[0].text == "Messages are guaranteed to be end-to-end encrypted from now on."
340+
assert chat2.get_messages()[0].text == "Messages are end-to-end encrypted."
342341
assert open(contact.get_profile_image(), "rb").read() == open(avatar_path, "rb").read()
343342

344343
lp.sec("ac2_offl: sending message")
@@ -412,7 +411,7 @@ def test_verified_group_vs_delete_server_after(acfactory, tmp_path, lp):
412411
ev = ac2_offl._evtracker.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
413412
msg_in = ac2_offl.get_message_by_id(ev.data2)
414413
assert msg_in.is_system_message()
415-
assert msg_in.text == "Messages are guaranteed to be end-to-end encrypted from now on."
414+
assert msg_in.text == "Messages are end-to-end encrypted."
416415

417416
# We need to consume one event that has data2=0
418417
ev = ac2_offl._evtracker.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")

python/tests/test_1_online.py

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import deltachat as dc
1111
from deltachat import account_hookimpl, Message
1212
from deltachat.tracker import ImexTracker
13+
from deltachat.testplugin import E2EE_INFO_MSGS
1314

1415

1516
def test_basic_imap_api(acfactory, tmp_path):
@@ -408,6 +409,10 @@ def test_forward_messages(acfactory, lp):
408409
msg_out = chat.send_text("message2")
409410

410411
lp.sec("ac2: wait for receive")
412+
ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
413+
msg_in = ac2.get_message_by_id(ev.data2)
414+
assert msg_in.text == "Messages are end-to-end encrypted."
415+
411416
ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
412417
assert ev.data2 == msg_out.id
413418
msg_in = ac2.get_message_by_id(msg_out.id)
@@ -622,6 +627,11 @@ def test_moved_markseen(acfactory):
622627

623628
with ac2.direct_imap.idle() as idle2:
624629
ac2.start_io()
630+
631+
ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
632+
msg = ac2.get_message_by_id(ev.data2)
633+
assert msg.text == "Messages are end-to-end encrypted."
634+
625635
ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
626636
msg = ac2.get_message_by_id(ev.data2)
627637

@@ -738,15 +748,15 @@ def test_mdn_asymmetric(acfactory, lp):
738748
lp.sec("sending text message from ac1 to ac2")
739749
msg_out = chat.send_text("message1")
740750

741-
assert len(chat.get_messages()) == 1
751+
assert len(chat.get_messages()) == 1 + E2EE_INFO_MSGS
742752

743753
lp.sec("disable ac1 MDNs")
744754
ac1.set_config("mdns_enabled", "0")
745755

746756
lp.sec("wait for ac2 to receive message")
747757
msg = ac2._evtracker.wait_next_incoming_message()
748758

749-
assert len(msg.chat.get_messages()) == 1
759+
assert len(msg.chat.get_messages()) == 1 + E2EE_INFO_MSGS
750760

751761
lp.sec("ac2: mark incoming message as seen")
752762
ac2.mark_seen_messages([msg])
@@ -755,7 +765,7 @@ def test_mdn_asymmetric(acfactory, lp):
755765
# MDN should be moved even though MDNs are already disabled
756766
ac1._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
757767

758-
assert len(chat.get_messages()) == 1
768+
assert len(chat.get_messages()) == 1 + E2EE_INFO_MSGS
759769

760770
# Wait for the message to be marked as seen on IMAP.
761771
ac1._evtracker.get_info_contains("Marked messages [0-9]+ in folder DeltaChat as seen.")
@@ -1123,6 +1133,11 @@ def ac_outgoing_message(self, message):
11231133
assert m == msg_out
11241134

11251135
lp.sec("wait for ac2 to receive message")
1136+
1137+
ev = ac2._evtracker.get_matching("DC_EVENT_MSGS_CHANGED|DC_EVENT_INCOMING_MSG")
1138+
msg_in = ac2.get_message_by_id(ev.data2)
1139+
assert msg_in.text == "Messages are end-to-end encrypted."
1140+
11261141
ev = ac2._evtracker.get_matching("DC_EVENT_MSGS_CHANGED|DC_EVENT_INCOMING_MSG")
11271142
assert ev.data2 == msg_out.id
11281143
msg_in = ac2.get_message_by_id(msg_out.id)
@@ -1158,10 +1173,10 @@ def assert_account_is_proper(ac):
11581173
assert contact2.addr == some1_addr
11591174
chat2 = contact2.create_chat()
11601175
messages = chat2.get_messages()
1161-
assert len(messages) == 3
1162-
assert messages[0].text == "msg1"
1163-
assert messages[1].filemime == "image/png"
1164-
assert os.stat(messages[1].filename).st_size == os.stat(original_image_path).st_size
1176+
assert len(messages) == 3 + E2EE_INFO_MSGS
1177+
assert messages[0 + E2EE_INFO_MSGS].text == "msg1"
1178+
assert messages[1 + E2EE_INFO_MSGS].filemime == "image/png"
1179+
assert os.stat(messages[1 + E2EE_INFO_MSGS].filename).st_size == os.stat(original_image_path).st_size
11651180
ac.set_config("displayname", "new displayname")
11661181
assert ac.get_config("displayname") == "new displayname"
11671182

@@ -1414,8 +1429,8 @@ def test_connectivity(acfactory, lp):
14141429
ac1.maybe_network()
14151430
ac1._evtracker.wait_for_connectivity(dc.const.DC_CONNECTIVITY_CONNECTED)
14161431
msgs = ac1.create_chat(ac2).get_messages()
1417-
assert len(msgs) == 1
1418-
assert msgs[0].text == "Hi"
1432+
assert len(msgs) == 1 + E2EE_INFO_MSGS
1433+
assert msgs[0 + E2EE_INFO_MSGS].text == "Hi"
14191434

14201435
lp.sec("Test that the connectivity changes to WORKING while new messages are fetched")
14211436

@@ -1425,8 +1440,8 @@ def test_connectivity(acfactory, lp):
14251440
ac1._evtracker.wait_for_connectivity_change(dc.const.DC_CONNECTIVITY_WORKING, dc.const.DC_CONNECTIVITY_CONNECTED)
14261441

14271442
msgs = ac1.create_chat(ac2).get_messages()
1428-
assert len(msgs) == 2
1429-
assert msgs[1].text == "Hi 2"
1443+
assert len(msgs) == 2 + E2EE_INFO_MSGS
1444+
assert msgs[1 + E2EE_INFO_MSGS].text == "Hi 2"
14301445

14311446

14321447
def test_fetch_deleted_msg(acfactory, lp):

python/tests/test_3_offline.py

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import deltachat as dc
77
from deltachat.tracker import ImexFailed
88
from deltachat import Account, Message
9+
from deltachat.testplugin import E2EE_INFO_MSGS
910

1011

1112
class TestOfflineAccountBasic:
@@ -461,9 +462,9 @@ def test_import_export_on_unencrypted_acct(self, acfactory, tmp_path):
461462
assert contact2.addr == ac_contact.get_config("addr")
462463
chat2 = contact2.create_chat()
463464
messages = chat2.get_messages()
464-
assert len(messages) == 2
465-
assert messages[0].text == "msg1"
466-
assert os.path.exists(messages[1].filename)
465+
assert len(messages) == 2 + E2EE_INFO_MSGS
466+
assert messages[0 + E2EE_INFO_MSGS].text == "msg1"
467+
assert os.path.exists(messages[1 + E2EE_INFO_MSGS].filename)
467468

468469
def test_import_export_on_encrypted_acct(self, acfactory, tmp_path):
469470
passphrase1 = "passphrase1"
@@ -500,9 +501,9 @@ def test_import_export_on_encrypted_acct(self, acfactory, tmp_path):
500501
contact2_addr = contact2.addr
501502
chat2 = contact2.create_chat()
502503
messages = chat2.get_messages()
503-
assert len(messages) == 2
504-
assert messages[0].text == "msg1"
505-
assert os.path.exists(messages[1].filename)
504+
assert len(messages) == 2 + E2EE_INFO_MSGS
505+
assert messages[0 + E2EE_INFO_MSGS].text == "msg1"
506+
assert os.path.exists(messages[1 + E2EE_INFO_MSGS].filename)
506507

507508
ac2.shutdown()
508509

@@ -517,9 +518,9 @@ def test_import_export_on_encrypted_acct(self, acfactory, tmp_path):
517518
assert contact2.addr == contact2_addr
518519
chat2 = contact2.create_chat()
519520
messages = chat2.get_messages()
520-
assert len(messages) == 2
521-
assert messages[0].text == "msg1"
522-
assert os.path.exists(messages[1].filename)
521+
assert len(messages) == 2 + E2EE_INFO_MSGS
522+
assert messages[0 + E2EE_INFO_MSGS].text == "msg1"
523+
assert os.path.exists(messages[1 + E2EE_INFO_MSGS].filename)
523524

524525
def test_import_export_with_passphrase(self, acfactory, tmp_path):
525526
passphrase = "test_passphrase"
@@ -557,9 +558,9 @@ def test_import_export_with_passphrase(self, acfactory, tmp_path):
557558
assert contact2.addr == ac_contact.get_config("addr")
558559
chat2 = contact2.create_chat()
559560
messages = chat2.get_messages()
560-
assert len(messages) == 2
561-
assert messages[0].text == "msg1"
562-
assert os.path.exists(messages[1].filename)
561+
assert len(messages) == 2 + E2EE_INFO_MSGS
562+
assert messages[0 + E2EE_INFO_MSGS].text == "msg1"
563+
assert os.path.exists(messages[1 + E2EE_INFO_MSGS].filename)
563564

564565
def test_import_encrypted_bak_into_encrypted_acct(self, acfactory, tmp_path):
565566
"""
@@ -603,9 +604,9 @@ def test_import_encrypted_bak_into_encrypted_acct(self, acfactory, tmp_path):
603604
assert contact2.addr == ac_contact.get_config("addr")
604605
chat2 = contact2.create_chat()
605606
messages = chat2.get_messages()
606-
assert len(messages) == 2
607-
assert messages[0].text == "msg1"
608-
assert os.path.exists(messages[1].filename)
607+
assert len(messages) == 2 + E2EE_INFO_MSGS
608+
assert messages[0 + E2EE_INFO_MSGS].text == "msg1"
609+
assert os.path.exists(messages[1 + E2EE_INFO_MSGS].filename)
609610

610611
ac2.shutdown()
611612

@@ -620,9 +621,9 @@ def test_import_encrypted_bak_into_encrypted_acct(self, acfactory, tmp_path):
620621
assert contact2.addr == ac_contact.get_config("addr")
621622
chat2 = contact2.create_chat()
622623
messages = chat2.get_messages()
623-
assert len(messages) == 2
624-
assert messages[0].text == "msg1"
625-
assert os.path.exists(messages[1].filename)
624+
assert len(messages) == 2 + E2EE_INFO_MSGS
625+
assert messages[0 + E2EE_INFO_MSGS].text == "msg1"
626+
assert os.path.exists(messages[1 + E2EE_INFO_MSGS].filename)
626627

627628
def test_set_get_draft(self, chat1):
628629
msg1 = Message.new_empty(chat1.account, "text")

0 commit comments

Comments
 (0)