mirror of
https://github.com/chatmail/core.git
synced 2026-04-27 02:16:29 +03:00
feat: use SEIPDv2 if all recipients support it
This commit is contained in:
@@ -32,7 +32,7 @@ use crate::message::{Message, MsgId, Viewtype};
|
||||
use crate::mimeparser::{SystemMessage, is_hidden};
|
||||
use crate::param::Param;
|
||||
use crate::peer_channels::{create_iroh_header, get_iroh_topic_for_msg};
|
||||
use crate::pgp::{SeipdVersion, addresses_from_public_key};
|
||||
use crate::pgp::{SeipdVersion, addresses_from_public_key, pubkey_supports_seipdv2};
|
||||
use crate::simplify::escape_message_footer_marks;
|
||||
use crate::stock_str;
|
||||
use crate::tools::{
|
||||
@@ -1176,14 +1176,13 @@ impl MimeFactory {
|
||||
} else {
|
||||
// Asymmetric encryption
|
||||
|
||||
let seipd_version = if encryption_pubkeys.is_empty() {
|
||||
// If message is sent only to self,
|
||||
// use v2 SEIPD.
|
||||
// Use SEIPDv2 if all recipients support it.
|
||||
let seipd_version = if encryption_pubkeys
|
||||
.iter()
|
||||
.all(|(_addr, pubkey)| pubkey_supports_seipdv2(pubkey))
|
||||
{
|
||||
SeipdVersion::V2
|
||||
} else {
|
||||
// If message is sent to others,
|
||||
// they may not support v2 SEIPD yet,
|
||||
// so use v1 SEIPD.
|
||||
SeipdVersion::V1
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use deltachat_contact_tools::ContactAddress;
|
||||
use mail_builder::headers::Header;
|
||||
use mailparse::{MailHeaderMap, addrparse_header};
|
||||
use pgp::armor;
|
||||
use pgp::packet::{Packet, PacketParser};
|
||||
use std::io::BufReader;
|
||||
use std::str;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -11,7 +14,7 @@ use crate::chat::{
|
||||
};
|
||||
use crate::chatlist::Chatlist;
|
||||
use crate::constants;
|
||||
use crate::contact::Origin;
|
||||
use crate::contact::{Origin, import_vcard};
|
||||
use crate::headerdef::HeaderDef;
|
||||
use crate::message;
|
||||
use crate::mimeparser::MimeMessage;
|
||||
@@ -877,3 +880,85 @@ async fn test_no_empty_to_header() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses ASCII-armored message and checks that it only has PKESK and SEIPD packets.
|
||||
///
|
||||
/// Panics if SEIPD packets are not of expected version.
|
||||
fn assert_seipd_version(payload: &str, version: usize) {
|
||||
let cursor = Cursor::new(payload);
|
||||
let dearmor = armor::Dearmor::new(cursor);
|
||||
let packet_parser = PacketParser::new(BufReader::new(dearmor));
|
||||
for packet in packet_parser {
|
||||
match packet.unwrap() {
|
||||
Packet::PublicKeyEncryptedSessionKey(_pkesk) => {}
|
||||
Packet::SymEncryptedProtectedData(seipd) => {
|
||||
assert_eq!(seipd.version(), version);
|
||||
}
|
||||
packet => {
|
||||
panic!("Unexpected packet {:?}", packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that messages between two test accounts use SEIPDv2 and not SEIPDv1.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_use_seipdv2() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
let alice_chat_id = alice.create_chat_id(bob).await;
|
||||
let sent = alice.send_text(alice_chat_id, "Hello!").await;
|
||||
assert_seipd_version(&sent.payload, 2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that messages to keys that don't advertise SEIPDv2 support
|
||||
/// are sent using SEIPDv1.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_fallback_to_seipdv1() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let charlie = &tcm.charlie().await;
|
||||
|
||||
// vCard of Alice with no SEIPDv2 feature advertised in the key.
|
||||
let alice_vcard = "BEGIN:VCARD
|
||||
VERSION:4.0
|
||||
EMAIL:alice@example.org
|
||||
FN:Alice
|
||||
KEY:data:application/pgp-keys;base64,mDMEXlh13RYJKwYBBAHaRw8BAQdAzfVIAleCXMJrq8VeLlEVof6ITCviMktKjmcBKAu4m5C0GUFsaWNlIDxhbGljZUBleGFtcGxlLm9yZz6IkAQTFggAOBYhBC5vossjtTLXKGNLWGSwj2Gp7ZRDBQJeWHXdAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEGSwj2Gp7ZRDE3oA/i4MCyDMTsjWqDZoQwX/A/GoTO2/V0wKPhjJJy/8m2pMAPkBjOnGOtx2SZpQvJGTa9h804RY6iDrRuI8A/8tEEXAA7g4BF5Ydd0SCisGAQQBl1UBBQEBB0AG7cjWy2SFAU8KnltlubVW67rFiyfp01JrRe6Xqy22HQMBCAeIeAQYFggAIBYhBC5vossjtTLXKGNLWGSwj2Gp7ZRDBQJeWHXdAhsMAAoJEGSwj2Gp7ZRDLo8BAObE8GnsGVwKzNqCvHeWgJsqhjS3C6gvSlV3tEm9XmF6AQDXucIyVfoBwoyMh2h6cSn/ATn5QJb35pgo+ivp3jsMAg==
|
||||
REV:20250412T195751Z
|
||||
END:VCARD";
|
||||
let contact_ids = import_vcard(bob, alice_vcard).await.unwrap();
|
||||
let alice_contact_id = contact_ids[0];
|
||||
let chat_id = ChatId::create_for_contact(bob, alice_contact_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Bob sends a message to Alice with SEIPDv1 packet.
|
||||
let sent = bob.send_text(chat_id, "Hello!").await;
|
||||
assert_seipd_version(&sent.payload, 1);
|
||||
|
||||
// Bob creates a group with Alice and Charlie.
|
||||
// Sending a message there should also use SEIPDv1
|
||||
// because for Bob it looks like Alice does not support SEIPDv2.
|
||||
let charlie_contact_id = bob.add_or_lookup_contact_id(charlie).await;
|
||||
let group_id = create_group(bob, "groupname").await.unwrap();
|
||||
chat::add_contact_to_chat(bob, group_id, alice_contact_id).await?;
|
||||
chat::add_contact_to_chat(bob, group_id, charlie_contact_id).await?;
|
||||
|
||||
let sent = bob.send_text(group_id, "Hello!").await;
|
||||
assert_seipd_version(&sent.payload, 1);
|
||||
|
||||
// Bob gets a new key of Alice via new vCard
|
||||
// and learns that Alice supports SEIPDv2.
|
||||
assert_eq!(bob.add_or_lookup_contact_id(alice).await, alice_contact_id);
|
||||
|
||||
let sent = bob.send_text(group_id, "Hello again with SEIPDv2!").await;
|
||||
assert_seipd_version(&sent.payload, 2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
29
src/pgp.rs
29
src/pgp.rs
@@ -495,6 +495,35 @@ pub(crate) fn addresses_from_public_key(public_key: &SignedPublicKey) -> Option<
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns true if public key advertises SEIPDv2 feature.
|
||||
pub(crate) fn pubkey_supports_seipdv2(public_key: &SignedPublicKey) -> bool {
|
||||
// If any Direct Key Signature or any User ID signature has SEIPDv2 feature,
|
||||
// assume that recipient can handle SEIPDv2.
|
||||
//
|
||||
// Third-party User ID signatures are dropped during certificate merging.
|
||||
// We don't check if the User ID is primary User ID.
|
||||
// Primary User ID is preferred during merging
|
||||
// and if some key has only non-primary User ID
|
||||
// it is acceptable. It is anyway unlikely that SEIPDv2
|
||||
// is advertised in a key without DKS or primary User ID.
|
||||
public_key
|
||||
.details
|
||||
.direct_signatures
|
||||
.iter()
|
||||
.chain(
|
||||
public_key
|
||||
.details
|
||||
.users
|
||||
.iter()
|
||||
.flat_map(|user| user.signatures.iter()),
|
||||
)
|
||||
.any(|signature| {
|
||||
signature
|
||||
.features()
|
||||
.is_some_and(|features| features.seipd_v2())
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::LazyLock;
|
||||
|
||||
Reference in New Issue
Block a user