Skip to content

PGP contacts #6796

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 263 commits into
base: main
Choose a base branch
from
Open

PGP contacts #6796

wants to merge 263 commits into from

Conversation

link2xt
Copy link
Collaborator

@link2xt link2xt commented Apr 12, 2025

JSON-RPC: reset_contact_encryption API is removed.
In deltachat-rpc-client 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.

Known multi-device (non-)issue: pinning a 1:1 chat from legacy client pins email chat via sync message.

Still missing, not all of this should be fixed before merging but at least CI should pass:

  • Return an error when API user tries to add email contact to encrypted chats.
  • Same for PGP-contacts, they should not be added to unencrypted chats.
  • Fix AEAP tests.
  • Chat-Group-Member-Fpr header.
  • Ignore Chat-Group-ID if the message is not encrypted+signed.
  • Complete Thunderbird tests.
  • Tests for 1:1 chat assignment. If we have multiple PGP-contacts with the same email address, can assign outgoing messages without Autocrypt-Gossip to the most recent one or using In-Reply-To or References. If there are no PGP-contacts, the message can go to email contact or trash. EDIT: we have tests for incomplete message assignment, they pass. This is not comprehensive and does not use Message-IDs and References, but tying chat assignment to contact lookup is complicated.
  • Migration. Started at [WIP] feat: Migration for PGP-contacts #6818
  • Do not list email-contacts in the contact list by default. For email contacts maybe add a flag.
  • Open issues for Thunderbird: test_prefer_encrypt_mutual_if_encrypted has TODOs for assigning to chat by issuer fingerprint. This should also work if the key is attached and no Autocrypt header is sent. Signed-only messages should probably go to PGP-chat without a padlock. Fixing this is out of scope of this PR. Signed-only messages to to email-contact. If Thunderbird is configured to send Autocrypt header, chatting should work, this is already tested.
  • SHA-256 fingerprints (API to get SHA2-256 fingerprints for any keys rpgp/rpgp#531, wip: "imprint" fn rpgp/rpgp#541). If we can, we should use SHA-256 fingerprints for v4 keys instead of having SHA-1 fingerprints as the primary key. Invite links with SHA-1 should still be supported. Adding a column can be done later and we will likely need two columns anyway with standard fingerprint and SHA-256 to support openpgp4fpr and invite links, but Chat-Group-Member-Fpr format should be fixed before release.

@link2xt link2xt mentioned this pull request Apr 12, 2025
@link2xt link2xt changed the title Link2xt/pgp contacts WIP: PGP contacts Apr 12, 2025
@link2xt link2xt force-pushed the link2xt/pgp-contacts branch 2 times, most recently from 7acd920 to 280e5dd Compare April 13, 2025 05:49
@link2xt link2xt force-pushed the link2xt/pgp-contacts branch from 7e5dab4 to 479879a Compare April 14, 2025 19:13
Copy link
Collaborator

@Hocuri Hocuri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just 3 small things I was wondering while skimming over

@link2xt link2xt force-pushed the link2xt/pgp-contacts branch 2 times, most recently from ebe2a09 to cac8d20 Compare April 16, 2025 16:13
@adbenitez
Copy link
Collaborator

a way to know if a chat is an encrypted chat is missing, currently there is only "isPgpContact" that can be used to mark contacts/1:1 chat but for unencrypted groups/threads of classic email non-pgp contacts there is no API to recognize such chats and also put the "classic email" marker as for classic email contacts

@link2xt link2xt force-pushed the link2xt/pgp-contacts branch 2 times, most recently from 625f8f4 to c87c241 Compare April 21, 2025 21:24
@@ -187,6 +191,7 @@ impl BasicChat {
id: chat_id,
name: chat.name.clone(),
is_protected: chat.is_protected(),
is_encrypted: chat.is_encrypted(context).await?,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds up to 2 additional database calls to loading BasicChat, though I can't think of a good way to avoid it, so maybe it's fine. Or maybe it should only be added to FullChat, idk.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One idea might be to have a separate chattype, SingleUnencrypted.

Comment on lines +850 to +858
if !fingerprint.is_empty() {
let fingerprint_self = load_self_public_key(context).await?.dc_fingerprint().hex();
if fingerprint == fingerprint_self {
return Ok((ContactId::SELF, sth_modified));
}
}
Copy link
Collaborator

@Hocuri Hocuri Apr 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Loading the self fingerprint everytime a contact is looked up, just to check whether it's ContactId::SELF, seems unnecessarily slow to me.

I wonder if we can simplify & speed up things by not using the kepairs table anymore, and instead storing the private and public key in configs. This would mean that it's not possible to have multiple self keys anymore, but I'm not sure there is anyone who is actually making use of the fact that DC theoretically supports using multiple self keys. It would also mean that we have to store the key as base64, not sure about the consequences there.

An alternative would be to store the self fingerprint in a OnceCell on Context, since we don't allow changing the primary key anyway.

But we can open an issue for this, and do it in a follow-up PR.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@link2xt agrees with the second alternative (caching fingerprint in memory).

@link2xt link2xt force-pushed the link2xt/pgp-contacts branch 2 times, most recently from 1cff5d4 to 907628e Compare May 2, 2025 17:13
@link2xt link2xt marked this pull request as ready for review May 2, 2025 17:53
@link2xt link2xt force-pushed the link2xt/pgp-contacts branch 2 times, most recently from dbade62 to b7284c0 Compare May 6, 2025 22:43
@link2xt link2xt changed the title WIP: PGP contacts PGP contacts May 7, 2025
Copy link
Collaborator

@Hocuri Hocuri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another day, another set of review comments

Comment on lines +337 to +356
let to_ids: Vec<Option<ContactId>>;
let past_ids: Vec<Option<ContactId>>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a lot of code to calculate the to and past ids, would be nice to extract a function here:

async fn get_to_and_past_contacts(
    context: &Context,
    mime_parser: &MimeMessage,
    incoming_origin: Origin,
) -> Result<(Vec<Option<ContactId>>, Vec<Option<ContactId>>)> {
    let chat_id = if let Some(grpid) = mime_parser.get_chat_group_id() {
    ...
        past_ids = add_or_lookup_contacts_by_address_list(
            context,
            &mime_parser.past_members,
            Origin::Hidden,
        )
        .await?;
    };
    Ok((to_ids, past_ids))
}
    let (to_ids, past_ids) =
        get_to_and_past_contacts(context, &mime_parser, incoming_origin).await?;

Comment on lines +702 to +732
/// * `is_partial_download`: the message is partially downloaded.
/// We only know the email address and not the contact fingerprint,
/// but try to assign the message to some PGP-contact.
/// If we get it wrong, the message will be placed into the correct
/// chat after downloading.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about unencrypted emails that are partially downloaded? They should be assigned to the email contact, not to some PGP contact with this email address. Maybe there is some code for this already and I just haven't seen it, otherwise we need a check for whether Content-Type is multipart/encrypted.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't know if partially downloaded email is encrypted or not, we don't even prefetch content-type. Messages are assigned to some PGP-contact. After downloading, they will go to email-contact.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this is not about prefetch but about partial downloads? And for partial downloads, we fetch the full headers, which includes Content-Type:

const BODY_PARTIAL: &str = "(FLAGS RFC822.SIZE BODY.PEEK[HEADER])";

Copy link
Collaborator

@Hocuri Hocuri May 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, wait, this is both about prefetch and about actual partial downloading.

What about this:

  • Rename is_partial_download parameter to find_pgp_contact_by_addr
  • In receive_imf_inner(), replace is_partial_download.is_some() with (pseudocode) is_partial_download.is_some() && headers.get("Content-Type") == "multipart/encrypted"

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@link2xt agrees if it's easy to do

@link2xt
Copy link
Collaborator Author

link2xt commented May 7, 2025

There is a problem with 1:1 chat assignment. If there is no Autocrypt-Gossip on outgoing message, then to_id gets converted to email contact. If there is also a Chat-Verified header, receive_imf fails with an error like DeltaChat: [accId=1] src/imap.rs:1456: receive_imf error: Non-PGP contact Contact#10 cannot be verified.. and the message does not show up at all. So we should assign by References or In-Reply-To and 1:1 chat before converting to_ids.

@link2xt link2xt force-pushed the link2xt/pgp-contacts branch from ac64daa to 55e6ecf Compare May 17, 2025 21:53
@link2xt link2xt force-pushed the link2xt/pgp-contacts branch from 55e6ecf to af4e3fe Compare May 17, 2025 22:14
…6856)

Particularly, this fixes a scenario when the second device sends a message to the 1:1 chat right
after SecureJoin.
@nicodh
Copy link
Contributor

nicodh commented May 22, 2025

With this branch Instant Onboarding with group invite link does not work any more.

At least with desktop when pasting a group invite code in
Add Profile => Create Profile => Use other server => Scan Invitation Code BackendRemote.rpc.checkQr(accountId, url) throws an error:
"failed to decode https://i.delta.chat/# QR code: failed to add or lookup contact for address ContactAddress("lhnjqt2jh@nine.testrun.org"): No self addr configured"

Scanning the invite code with an existing user works fine.

@Hocuri @link2xt

@link2xt link2xt force-pushed the link2xt/pgp-contacts branch 2 times, most recently from 1cc8e8b to dff076c Compare May 24, 2025 21:42
@link2xt link2xt force-pushed the link2xt/pgp-contacts branch from dff076c to 72614b0 Compare May 26, 2025 05:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants