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

@@ -11,9 +11,7 @@ use percent_encoding::{percent_decode_str, percent_encode, NON_ALPHANUMERIC};
use serde::Deserialize;
pub(crate) use self::dclogin_scheme::configure_from_login_qr;
use crate::chat::ChatIdBlocked;
use crate::config::Config;
use crate::constants::Blocked;
use crate::contact::{Contact, ContactId, Origin};
use crate::context::Context;
use crate::events::EventType;
@@ -21,7 +19,6 @@ use crate::key::Fingerprint;
use crate::message::Message;
use crate::net::http::post_empty;
use crate::net::proxy::{ProxyConfig, DEFAULT_SOCKS_PORT};
use crate::peerstate::Peerstate;
use crate::token;
use crate::tools::validate_id;
@@ -44,7 +41,7 @@ pub(crate) const DCBACKUP_SCHEME_PREFIX: &str = "DCBACKUP";
/// Version written to Backups and Backup-QR-Codes.
/// Imports will fail when they have a larger version.
pub(crate) const DCBACKUP_VERSION: i32 = 2;
pub(crate) const DCBACKUP_VERSION: i32 = 3;
/// Scanned QR code.
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -457,17 +454,17 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
None
};
// retrieve known state for this fingerprint
let peerstate = Peerstate::from_fingerprint(context, &fingerprint)
.await
.context("Can't load peerstate")?;
if let (Some(addr), Some(invitenumber), Some(authcode)) = (&addr, invitenumber, authcode) {
let addr = ContactAddress::new(addr)?;
let (contact_id, _) =
Contact::add_or_lookup(context, &name, &addr, Origin::UnhandledSecurejoinQrScan)
.await
.with_context(|| format!("failed to add or lookup contact for address {addr:?}"))?;
let (contact_id, _) = Contact::add_or_lookup_ex(
context,
&name,
&addr,
&fingerprint.hex(),
Origin::UnhandledSecurejoinQrScan,
)
.await
.with_context(|| format!("failed to add or lookup contact for address {addr:?}"))?;
if let (Some(grpid), Some(grpname)) = (grpid, grpname) {
if context
@@ -529,21 +526,18 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
})
}
} else if let Some(addr) = addr {
if let Some(peerstate) = peerstate {
let peerstate_addr = ContactAddress::new(&peerstate.addr)?;
let (contact_id, _) =
Contact::add_or_lookup(context, &name, &peerstate_addr, Origin::UnhandledQrScan)
.await
.context("add_or_lookup")?;
ChatIdBlocked::get_for_contact(context, contact_id, Blocked::Request)
.await
.context("Failed to create (new) chat for contact")?;
let fingerprint = fingerprint.hex();
let (contact_id, _) =
Contact::add_or_lookup_ex(context, "", &addr, &fingerprint, Origin::UnhandledQrScan)
.await?;
let contact = Contact::get_by_id(context, contact_id).await?;
if contact.public_key(context).await?.is_some() {
Ok(Qr::FprOk { contact_id })
} else {
let contact_id = Contact::lookup_id_by_addr(context, &addr, Origin::Unknown)
.await
.with_context(|| format!("Error looking up contact {addr:?}"))?;
Ok(Qr::FprMismatch { contact_id })
Ok(Qr::FprMismatch {
contact_id: Some(contact_id),
})
}
} else {
Ok(Qr::FprWithoutAddr {