Skip to content

Commit 96b3ef2

Browse files
committed
feat: MimeMessage: Put intended recipient fingerprints into signature
1 parent 448c0d2 commit 96b3ef2

File tree

4 files changed

+42
-16
lines changed

4 files changed

+42
-16
lines changed

src/mimeparser.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,12 @@ pub(crate) struct MimeMessage {
8989
pub decrypting_failed: bool,
9090

9191
/// Valid signature fingerprint if a message is an
92-
/// Autocrypt encrypted and signed message.
92+
/// Autocrypt encrypted and signed message and corresponding intended recipient fingerprints
93+
/// (<https://www.rfc-editor.org/rfc/rfc9580.html#name-intended-recipient-fingerpr>) if any.
9394
///
9495
/// If a message is not encrypted or the signature is not valid,
9596
/// this is `None`.
96-
pub signature: Option<Fingerprint>,
97+
pub signature: Option<(Fingerprint, HashSet<Fingerprint>)>,
9798

9899
/// The addresses for which there was a gossip header
99100
/// and their respective gossiped keys.
@@ -529,12 +530,16 @@ impl MimeMessage {
529530
let mut signatures = if let Some(ref decrypted_msg) = decrypted_msg {
530531
crate::pgp::valid_signature_fingerprints(decrypted_msg, &public_keyring)
531532
} else {
532-
HashSet::new()
533+
HashMap::new()
533534
};
534535

535536
let mail = mail.as_ref().map(|mail| {
536537
let (content, signatures_detached) = validate_detached_signature(mail, &public_keyring)
537538
.unwrap_or((mail, Default::default()));
539+
let signatures_detached = signatures_detached
540+
.into_iter()
541+
.map(|fp| (fp, Vec::new()))
542+
.collect::<HashMap<_, _>>();
538543
signatures.extend(signatures_detached);
539544
content
540545
});
@@ -640,6 +645,10 @@ impl MimeMessage {
640645
};
641646
}
642647

648+
let signature = signatures
649+
.into_iter()
650+
.last()
651+
.map(|(fp, recipient_fps)| (fp, recipient_fps.into_iter().collect::<HashSet<_>>()));
643652
let mut parser = MimeMessage {
644653
parts: Vec::new(),
645654
headers,
@@ -655,7 +664,7 @@ impl MimeMessage {
655664
decrypting_failed: mail.is_err(),
656665

657666
// only non-empty if it was a valid autocrypt message
658-
signature: signatures.into_iter().last(),
667+
signature,
659668
autocrypt_fingerprint,
660669
gossiped_keys,
661670
is_forwarded: false,

src/pgp.rs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! OpenPGP helper module using [rPGP facilities](https://github.com/rpgp/rpgp).
22
3-
use std::collections::{BTreeMap, HashSet};
3+
use std::collections::{BTreeMap, HashMap, HashSet};
44
use std::io::{BufRead, Cursor};
55

66
use anyhow::{Context as _, Result, bail};
@@ -370,19 +370,28 @@ fn check_symmetric_encryption(msg: &Message<'_>) -> std::result::Result<(), &'st
370370

371371
/// Returns fingerprints
372372
/// of all keys from the `public_keys_for_validation` keyring that
373-
/// have valid signatures there.
373+
/// have valid signatures in `msg` and corresponding intended recipient fingerprints
374+
/// (<https://www.rfc-editor.org/rfc/rfc9580.html#name-intended-recipient-fingerpr>) if any.
374375
///
375-
/// If the message is wrongly signed, HashSet will be empty.
376+
/// If the message is wrongly signed, returns an empty map.
376377
pub fn valid_signature_fingerprints(
377378
msg: &pgp::composed::Message,
378379
public_keys_for_validation: &[SignedPublicKey],
379-
) -> HashSet<Fingerprint> {
380-
let mut ret_signature_fingerprints: HashSet<Fingerprint> = Default::default();
380+
) -> HashMap<Fingerprint, Vec<Fingerprint>> {
381+
let mut ret_signature_fingerprints = HashMap::new();
381382
if msg.is_signed() {
382383
for pkey in public_keys_for_validation {
383-
if msg.verify(&pkey.primary_key).is_ok() {
384+
if let Ok(signature) = msg.verify(&pkey.primary_key) {
384385
let fp = pkey.dc_fingerprint();
385-
ret_signature_fingerprints.insert(fp);
386+
let mut recipient_fps = Vec::new();
387+
if let Some(cfg) = signature.config() {
388+
for subpkt in &cfg.hashed_subpackets {
389+
if let SubpacketData::IntendedRecipientFingerprint(fp) = &subpkt.data {
390+
recipient_fps.push(fp.clone().into());
391+
}
392+
}
393+
}
394+
ret_signature_fingerprints.insert(fp, recipient_fps);
386395
}
387396
}
388397
}
@@ -497,13 +506,14 @@ mod tests {
497506
use pgp::composed::Esk;
498507
use pgp::packet::PublicKeyEncryptedSessionKey;
499508

509+
#[expect(clippy::type_complexity)]
500510
fn pk_decrypt_and_validate<'a>(
501511
ctext: &'a [u8],
502512
private_keys_for_decryption: &'a [SignedSecretKey],
503513
public_keys_for_validation: &[SignedPublicKey],
504514
) -> Result<(
505515
pgp::composed::Message<'static>,
506-
HashSet<Fingerprint>,
516+
HashMap<Fingerprint, Vec<Fingerprint>>,
507517
Vec<u8>,
508518
)> {
509519
let mut msg = decrypt(ctext.to_vec(), private_keys_for_decryption, &[])?;
@@ -611,7 +621,7 @@ mod tests {
611621
}
612622

613623
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
614-
async fn test_decrypt_singed() {
624+
async fn test_decrypt_signed() {
615625
// Check decrypting as Alice
616626
let decrypt_keyring = vec![KEYS.alice_secret.clone()];
617627
let sig_check_keyring = vec![KEYS.alice_public.clone()];
@@ -623,6 +633,10 @@ mod tests {
623633
.unwrap();
624634
assert_eq!(content, CLEARTEXT);
625635
assert_eq!(valid_signatures.len(), 1);
636+
for recipient_fps in valid_signatures.values() {
637+
// Intended Recipient Fingerprint subpackets aren't added currently.
638+
assert_eq!(recipient_fps.len(), 0);
639+
}
626640

627641
// Check decrypting as Bob
628642
let decrypt_keyring = vec![KEYS.bob_secret.clone()];
@@ -635,6 +649,9 @@ mod tests {
635649
.unwrap();
636650
assert_eq!(content, CLEARTEXT);
637651
assert_eq!(valid_signatures.len(), 1);
652+
for recipient_fps in valid_signatures.values() {
653+
assert_eq!(recipient_fps.len(), 0);
654+
}
638655
}
639656

640657
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]

src/receive_imf.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,7 @@ pub(crate) async fn receive_imf_inner(
577577
// For example, GitHub sends messages from `notifications@github.com`,
578578
// but uses display name of the user whose action generated the notification
579579
// as the display name.
580-
let fingerprint = mime_parser.signature.as_ref();
580+
let fingerprint = mime_parser.signature.as_ref().map(|(fp, _)| fp);
581581
let (from_id, _from_id_blocked, incoming_origin) = match from_field_to_contact_id(
582582
context,
583583
&mime_parser.from,
@@ -3901,7 +3901,7 @@ async fn has_verified_encryption(
39013901
let signed_with_verified_key = mimeparser
39023902
.signature
39033903
.as_ref()
3904-
.is_some_and(|signature| *signature == fingerprint);
3904+
.is_some_and(|(signature, _)| *signature == fingerprint);
39053905
if signed_with_verified_key {
39063906
Ok(Verified)
39073907
} else {

src/securejoin.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -784,7 +784,7 @@ fn encrypted_and_signed(
784784
mimeparser: &MimeMessage,
785785
expected_fingerprint: &Fingerprint,
786786
) -> bool {
787-
if let Some(signature) = mimeparser.signature.as_ref() {
787+
if let Some((signature, _)) = mimeparser.signature.as_ref() {
788788
if signature == expected_fingerprint {
789789
true
790790
} else {

0 commit comments

Comments
 (0)