feat: key-contacts

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 <hocuri@gmx.de>
Co-authored-by: iequidoo <dgreshilov@gmail.com>
Co-authored-by: B. Petersen <r10s@b44t.com>
This commit is contained in:
link2xt
2025-06-26 14:07:39 +00:00
parent 7ac04d0204
commit 416131b4a2
84 changed files with 4735 additions and 6338 deletions

View File

@@ -3,14 +3,9 @@
use std::collections::HashSet;
use anyhow::Result;
use deltachat_contact_tools::addr_cmp;
use mailparse::ParsedMail;
use crate::aheader::Aheader;
use crate::context::Context;
use crate::key::{DcKey, Fingerprint, SignedPublicKey, SignedSecretKey};
use crate::log::info;
use crate::peerstate::Peerstate;
use crate::key::{Fingerprint, SignedPublicKey, SignedSecretKey};
use crate::pgp;
/// Tries to decrypt a message, but only if it is structured as an Autocrypt message.
@@ -144,83 +139,6 @@ pub(crate) fn validate_detached_signature<'a, 'b>(
}
}
/// Returns public keyring for `peerstate`.
pub(crate) fn keyring_from_peerstate(peerstate: Option<&Peerstate>) -> Vec<SignedPublicKey> {
let mut public_keyring_for_validate = Vec::new();
if let Some(peerstate) = peerstate {
if let Some(key) = &peerstate.public_key {
public_keyring_for_validate.push(key.clone());
} else if let Some(key) = &peerstate.gossip_key {
public_keyring_for_validate.push(key.clone());
}
}
public_keyring_for_validate
}
/// Applies Autocrypt header to Autocrypt peer state and saves it into the database.
///
/// If we already know this fingerprint from another contact's peerstate, return that
/// peerstate in order to make AEAP work, but don't save it into the db yet.
///
/// Returns updated peerstate.
pub(crate) async fn get_autocrypt_peerstate(
context: &Context,
from: &str,
autocrypt_header: Option<&Aheader>,
message_time: i64,
allow_aeap: bool,
) -> Result<Option<Peerstate>> {
let allow_change = !context.is_self_addr(from).await?;
let mut peerstate;
// Apply Autocrypt header
if let Some(header) = autocrypt_header {
if allow_aeap {
// If we know this fingerprint from another addr,
// we may want to do a transition from this other addr
// (and keep its peerstate)
// For security reasons, for now, we only do a transition
// if the fingerprint is verified.
peerstate = Peerstate::from_verified_fingerprint_or_addr(
context,
&header.public_key.dc_fingerprint(),
from,
)
.await?;
} else {
peerstate = Peerstate::from_addr(context, from).await?;
}
if let Some(ref mut peerstate) = peerstate {
if addr_cmp(&peerstate.addr, from) {
if allow_change {
peerstate.apply_header(context, header, message_time);
peerstate.save_to_db(&context.sql).await?;
} else {
info!(
context,
"Refusing to update existing peerstate of {}", &peerstate.addr
);
}
}
// If `peerstate.addr` and `from` differ, this means that
// someone is using the same key but a different addr, probably
// because they made an AEAP transition.
// But we don't know if that's legit until we checked the
// signatures, so wait until then with writing anything
// to the database.
} else {
let p = Peerstate::from_header(header, message_time);
p.save_to_db(&context.sql).await?;
peerstate = Some(p);
}
} else {
peerstate = Peerstate::from_addr(context, from).await?;
}
Ok(peerstate)
}
#[cfg(test)]
mod tests {
use super::*;