Skip to content

Commit 285d80a

Browse files
link2xtHocuriiequidoor10s
committed
feat: key-contacts (#6796)
This change introduces a new type of contacts identified by their public key fingerprint rather than an e-mail address. Encrypted chats now stay encrypted and unencrypted chats stay unencrypted. For example, 1:1 chats with key-contacts are encrypted and 1:1 chats with address-contacts are unencrypted. Groups that have a group ID are encrypted and can only contain key-contacts while groups that don't have a group ID ("adhoc groups") are unencrypted and can only contain address-contacts. JSON-RPC API `reset_contact_encryption` is removed. Python API `Contact.reset_encryption` is removed. "Group tracking plugin" in legacy Python API was removed because it relied on parsing email addresses from system messages with regexps. Co-authored-by: Hocuri <[email protected]> Co-authored-by: iequidoo <[email protected]> Co-authored-by: B. Petersen <[email protected]>
1 parent 7ac04d0 commit 285d80a

File tree

84 files changed

+4696
-6299
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+4696
-6299
lines changed

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,8 +289,8 @@
289289

290290
- Use vCard in TestContext.add_or_lookup_contact().
291291
- Remove test_group_with_removed_message_id.
292-
- Use add_or_lookup_email_contact() in get_chat().
293-
- Use add_or_lookup_email_contact in test_setup_contact_ex.
292+
- Use add_or_lookup_address_contact() in get_chat().
293+
- Use add_or_lookup_address_contact in test_setup_contact_ex.
294294
- Use vCards more in Python tests.
295295
- Use TestContextManager in more tests.
296296
- Use vCards to create contacts in more Rust tests.

assets/icon-address-contact.png

2.23 KB
Loading

assets/icon-address-contact.svg

Lines changed: 47 additions & 0 deletions
Loading

assets/self-reporting-bot.vcf

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
BEGIN:VCARD
2+
VERSION:4.0
3+
4+
FN:Statistics bot
5+
KEY:data:application/pgp-keys;base64,xjMEZbfBlBYJKwYBBAHaRw8BAQdABpLWS2PUIGGo4pslVt4R8sylP5wZihmhf1DTDr3oCMPNHDxzZWxmX3JlcG9ydGluZ0B0ZXN0cnVuLm9yZz7CiwQQFggAMwIZAQUCZbfBlAIbAwQLCQgHBhUICQoLAgMWAgEWIQTS2i16sHeYTckGn284K3M5Z4oohAAKCRA4K3M5Z4oohD8dAQCQV7CoH6UP4PD+NqI4kW5tbbqdh2AnDROg60qotmLExAEAxDfd3QHAK9f8b9qQUbLmHIztCLxhEuVbWPBEYeVW0gvOOARlt8GUEgorBgEEAZdVAQUBAQdAMBUhYoAAcI625vGZqnM5maPX4sGJ7qvJxPAFILPy6AcDAQgHwngEGBYIACAFAmW3wZQCGwwWIQTS2i16sHeYTckGn284K3M5Z4oohAAKCRA4K3M5Z4oohPwCAQCvzk1ObIkj2GqsuIfaULlgdnfdZY8LNary425CEfHZDQD5AblXVrlMO1frdlc/Vo9z3pEeCrfYdD7ITD3/OeVoiQ4=
6+
REV:20250412T195751Z
7+
END:VCARD

deltachat-ffi/deltachat.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3838,6 +3838,21 @@ int dc_chat_can_send (const dc_chat_t* chat);
38383838
int dc_chat_is_protected (const dc_chat_t* chat);
38393839

38403840

3841+
/**
3842+
* Check if the chat is encrypted.
3843+
*
3844+
* 1:1 chats with key-contacts and group chats with key-contacts
3845+
* are encrypted.
3846+
* 1:1 chats with emails contacts and ad-hoc groups
3847+
* created for email threads are not encrypted.
3848+
*
3849+
* @memberof dc_chat_t
3850+
* @param chat The chat object.
3851+
* @return 1=chat is encrypted, 0=chat is not encrypted.
3852+
*/
3853+
int dc_chat_is_encrypted (const dc_chat_t *chat);
3854+
3855+
38413856
/**
38423857
* Checks if the chat was protected, and then an incoming message broke this protection.
38433858
*
@@ -6886,6 +6901,7 @@ void dc_event_unref(dc_event_t* event);
68866901
/// "End-to-end encryption preferred."
68876902
///
68886903
/// Used to build the string returned by dc_get_contact_encrinfo().
6904+
/// @deprecated 2025-06-05
68896905
#define DC_STR_E2E_PREFERRED 34
68906906

68916907
/// "%1$s verified"
@@ -6898,12 +6914,14 @@ void dc_event_unref(dc_event_t* event);
68986914
///
68996915
/// Used in status messages.
69006916
/// - %1$s will be replaced by the name of the contact that cannot be verified
6917+
/// @deprecated 2025-06-05
69016918
#define DC_STR_CONTACT_NOT_VERIFIED 36
69026919

69036920
/// "Changed setup for %1$s."
69046921
///
69056922
/// Used in status messages.
69066923
/// - %1$s will be replaced by the name of the contact with the changed setup
6924+
/// @deprecated 2025-06-05
69076925
#define DC_STR_CONTACT_SETUP_CHANGED 37
69086926

69096927
/// "Archived chats"
@@ -7293,6 +7311,7 @@ void dc_event_unref(dc_event_t* event);
72937311
/// "%1$s changed their address from %2$s to %3$s"
72947312
///
72957313
/// Used as an info message to chats with contacts that changed their address.
7314+
/// @deprecated 2025-06-05
72967315
#define DC_STR_AEAP_ADDR_CHANGED 122
72977316

72987317
/// "You changed your email address from %1$s to %2$s.
@@ -7599,6 +7618,7 @@ void dc_event_unref(dc_event_t* event);
75997618
/// "The contact must be online to proceed. This process will continue automatically in background."
76007619
///
76017620
/// Used as info message.
7621+
/// @deprecated 2025-06-05
76027622
#define DC_STR_SECUREJOIN_TAKES_LONGER 192
76037623

76047624
/// "Contact". Deprecated, currently unused.

deltachat-ffi/src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3153,6 +3153,18 @@ pub unsafe extern "C" fn dc_chat_is_protected(chat: *mut dc_chat_t) -> libc::c_i
31533153
ffi_chat.chat.is_protected() as libc::c_int
31543154
}
31553155

3156+
#[no_mangle]
3157+
pub unsafe extern "C" fn dc_chat_is_encrypted(chat: *mut dc_chat_t) -> libc::c_int {
3158+
if chat.is_null() {
3159+
eprintln!("ignoring careless call to dc_chat_is_encrypted()");
3160+
return 0;
3161+
}
3162+
let ffi_chat = &*chat;
3163+
3164+
block_on(ffi_chat.chat.is_encrypted(&ffi_chat.context))
3165+
.unwrap_or_log_default(&ffi_chat.context, "Failed dc_chat_is_encrypted") as libc::c_int
3166+
}
3167+
31563168
#[no_mangle]
31573169
pub unsafe extern "C" fn dc_chat_is_protection_broken(chat: *mut dc_chat_t) -> libc::c_int {
31583170
if chat.is_null() {
@@ -4303,6 +4315,7 @@ pub unsafe extern "C" fn dc_contact_get_verifier_id(contact: *mut dc_contact_t)
43034315
.context("failed to get verifier")
43044316
.log_err(ctx)
43054317
.unwrap_or_default()
4318+
.unwrap_or_default()
43064319
.unwrap_or_default();
43074320

43084321
verifier_contact_id.to_u32()

deltachat-jsonrpc/src/api.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,20 @@ impl CommandApi {
354354
Ok(ctx.get_blobdir().to_str().map(|s| s.to_owned()))
355355
}
356356

357+
/// If there was an error while the account was opened
358+
/// and migrated to the current version,
359+
/// then this function returns it.
360+
///
361+
/// This function is useful because the key-contacts migration could fail due to bugs
362+
/// and then the account will not work properly.
363+
///
364+
/// After opening an account, the UI should call this function
365+
/// and show the error string if one is returned.
366+
async fn get_migration_error(&self, account_id: u32) -> Result<Option<String>> {
367+
let ctx = self.get_context(account_id).await?;
368+
Ok(ctx.get_migration_error())
369+
}
370+
357371
/// Copy file to blob dir.
358372
async fn copy_to_blob_dir(&self, account_id: u32, path: String) -> Result<PathBuf> {
359373
let ctx = self.get_context(account_id).await?;
@@ -1542,15 +1556,6 @@ impl CommandApi {
15421556
Ok(())
15431557
}
15441558

1545-
/// Resets contact encryption.
1546-
async fn reset_contact_encryption(&self, account_id: u32, contact_id: u32) -> Result<()> {
1547-
let ctx = self.get_context(account_id).await?;
1548-
let contact_id = ContactId::new(contact_id);
1549-
1550-
contact_id.reset_encryption(&ctx).await?;
1551-
Ok(())
1552-
}
1553-
15541559
/// Sets display name for existing contact.
15551560
async fn change_contact_name(
15561561
&self,

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

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,29 @@ pub struct FullChat {
3030
/// in the contact profile
3131
/// if 1:1 chat with this contact exists and is protected.
3232
is_protected: bool,
33+
/// True if the chat is encrypted.
34+
/// This means that all messages in the chat are encrypted,
35+
/// and all contacts in the chat are "key-contacts",
36+
/// i.e. identified by the PGP key fingerprint.
37+
///
38+
/// False if the chat is unencrypted.
39+
/// This means that all messages in the chat are unencrypted,
40+
/// and all contacts in the chat are "address-contacts",
41+
/// i.e. identified by the email address.
42+
/// The UI should mark this chat e.g. with a mail-letter icon.
43+
///
44+
/// Unencrypted groups are called "ad-hoc groups"
45+
/// and the user can't add/remove members,
46+
/// create a QR invite code,
47+
/// or set an avatar.
48+
/// These options should therefore be disabled in the UI.
49+
///
50+
/// Note that it can happen that an encrypted chat
51+
/// contains unencrypted messages that were received in core <= v1.159.*
52+
/// and vice versa.
53+
///
54+
/// See also `is_key_contact` on `Contact`.
55+
is_encrypted: bool,
3356
profile_image: Option<String>, //BLOBS ?
3457
archived: bool,
3558
pinned: bool,
@@ -108,6 +131,7 @@ impl FullChat {
108131
id: chat_id,
109132
name: chat.name.clone(),
110133
is_protected: chat.is_protected(),
134+
is_encrypted: chat.is_encrypted(context).await?,
111135
profile_image, //BLOBS ?
112136
archived: chat.get_visibility() == chat::ChatVisibility::Archived,
113137
pinned: chat.get_visibility() == chat::ChatVisibility::Pinned,
@@ -159,6 +183,30 @@ pub struct BasicChat {
159183
/// in the contact profile
160184
/// if 1:1 chat with this contact exists and is protected.
161185
is_protected: bool,
186+
187+
/// True if the chat is encrypted.
188+
/// This means that all messages in the chat are encrypted,
189+
/// and all contacts in the chat are "key-contacts",
190+
/// i.e. identified by the PGP key fingerprint.
191+
///
192+
/// False if the chat is unencrypted.
193+
/// This means that all messages in the chat are unencrypted,
194+
/// and all contacts in the chat are "address-contacts",
195+
/// i.e. identified by the email address.
196+
/// The UI should mark this chat e.g. with a mail-letter icon.
197+
///
198+
/// Unencrypted groups are called "ad-hoc groups"
199+
/// and the user can't add/remove members,
200+
/// create a QR invite code,
201+
/// or set an avatar.
202+
/// These options should therefore be disabled in the UI.
203+
///
204+
/// Note that it can happen that an encrypted chat
205+
/// contains unencrypted messages that were received in core <= v1.159.*
206+
/// and vice versa.
207+
///
208+
/// See also `is_key_contact` on `Contact`.
209+
is_encrypted: bool,
162210
profile_image: Option<String>, //BLOBS ?
163211
archived: bool,
164212
pinned: bool,
@@ -187,6 +235,7 @@ impl BasicChat {
187235
id: chat_id,
188236
name: chat.name.clone(),
189237
is_protected: chat.is_protected(),
238+
is_encrypted: chat.is_encrypted(context).await?,
190239
profile_image, //BLOBS ?
191240
archived: chat.get_visibility() == chat::ChatVisibility::Archived,
192241
pinned: chat.get_visibility() == chat::ChatVisibility::Pinned,

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,30 @@ pub enum ChatListItemFetchResult {
3030
/// showing preview if last chat message is image
3131
summary_preview_image: Option<String>,
3232
is_protected: bool,
33+
34+
/// True if the chat is encrypted.
35+
/// This means that all messages in the chat are encrypted,
36+
/// and all contacts in the chat are "key-contacts",
37+
/// i.e. identified by the PGP key fingerprint.
38+
///
39+
/// False if the chat is unencrypted.
40+
/// This means that all messages in the chat are unencrypted,
41+
/// and all contacts in the chat are "address-contacts",
42+
/// i.e. identified by the email address.
43+
/// The UI should mark this chat e.g. with a mail-letter icon.
44+
///
45+
/// Unencrypted groups are called "ad-hoc groups"
46+
/// and the user can't add/remove members,
47+
/// create a QR invite code,
48+
/// or set an avatar.
49+
/// These options should therefore be disabled in the UI.
50+
///
51+
/// Note that it can happen that an encrypted chat
52+
/// contains unencrypted messages that were received in core <= v1.159.*
53+
/// and vice versa.
54+
///
55+
/// See also `is_key_contact` on `Contact`.
56+
is_encrypted: bool,
3357
is_group: bool,
3458
fresh_message_counter: usize,
3559
is_self_talk: bool,
@@ -137,6 +161,7 @@ pub(crate) async fn get_chat_list_item_by_id(
137161
summary_status: summary.state.to_u32().expect("impossible"), // idea and a function to transform the constant to strings? or return string enum
138162
summary_preview_image,
139163
is_protected: chat.is_protected(),
164+
is_encrypted: chat.is_encrypted(ctx).await?,
140165
is_group: chat.get_type() == Chattype::Group,
141166
fresh_message_counter,
142167
is_self_talk: chat.is_self_talk(),

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@ pub struct ContactObject {
1919
profile_image: Option<String>, // BLOBS
2020
name_and_addr: String,
2121
is_blocked: bool,
22+
23+
/// Is the contact a key contact.
24+
is_key_contact: bool,
25+
26+
/// Is encryption available for this contact.
27+
///
28+
/// This can only be true for key-contacts.
29+
/// However, it is possible to have a key-contact
30+
/// for which encryption is not available because we don't have a key yet,
31+
/// e.g. if we just scanned the fingerprint from a QR code.
2232
e2ee_avail: bool,
2333

2434
/// True if the contact can be added to verified groups.
@@ -67,6 +77,7 @@ impl ContactObject {
6777
let verifier_id = contact
6878
.get_verifier_id(context)
6979
.await?
80+
.flatten()
7081
.map(|contact_id| contact_id.to_u32());
7182

7283
Ok(ContactObject {
@@ -80,6 +91,7 @@ impl ContactObject {
8091
profile_image, //BLOBS
8192
name_and_addr: contact.get_name_n_addr(),
8293
is_blocked: contact.is_blocked(),
94+
is_key_contact: contact.is_key_contact(),
8395
e2ee_avail: contact.e2ee_avail(context).await?,
8496
is_verified,
8597
is_profile_verified,

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ pub struct MessageObject {
5959

6060
// summary - use/create another function if you need it
6161
subject: String,
62+
63+
/// True if the message was correctly encrypted&signed, false otherwise.
64+
/// Historically, UIs showed a small padlock on the message then.
65+
///
66+
/// Today, the UIs should instead show a small email-icon on the message
67+
/// if `show_padlock` is `false`,
68+
/// and nothing if it is `true`.
6269
show_padlock: bool,
6370
is_setupmessage: bool,
6471
is_info: bool,

0 commit comments

Comments
 (0)