mirror of
https://github.com/chatmail/core.git
synced 2026-05-08 09:26:29 +03:00
feat: MimeMessage: Put intended recipient fingerprints into signature
This commit is contained in:
@@ -89,11 +89,12 @@ pub(crate) struct MimeMessage {
|
|||||||
pub decrypting_failed: bool,
|
pub decrypting_failed: bool,
|
||||||
|
|
||||||
/// Valid signature fingerprint if a message is an
|
/// Valid signature fingerprint if a message is an
|
||||||
/// Autocrypt encrypted and signed message.
|
/// Autocrypt encrypted and signed message and corresponding intended recipient fingerprints
|
||||||
|
/// (<https://www.rfc-editor.org/rfc/rfc9580.html#name-intended-recipient-fingerpr>) if any.
|
||||||
///
|
///
|
||||||
/// If a message is not encrypted or the signature is not valid,
|
/// If a message is not encrypted or the signature is not valid,
|
||||||
/// this is `None`.
|
/// this is `None`.
|
||||||
pub signature: Option<Fingerprint>,
|
pub signature: Option<(Fingerprint, HashSet<Fingerprint>)>,
|
||||||
|
|
||||||
/// The addresses for which there was a gossip header
|
/// The addresses for which there was a gossip header
|
||||||
/// and their respective gossiped keys.
|
/// and their respective gossiped keys.
|
||||||
@@ -529,12 +530,16 @@ impl MimeMessage {
|
|||||||
let mut signatures = if let Some(ref decrypted_msg) = decrypted_msg {
|
let mut signatures = if let Some(ref decrypted_msg) = decrypted_msg {
|
||||||
crate::pgp::valid_signature_fingerprints(decrypted_msg, &public_keyring)
|
crate::pgp::valid_signature_fingerprints(decrypted_msg, &public_keyring)
|
||||||
} else {
|
} else {
|
||||||
HashSet::new()
|
HashMap::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mail = mail.as_ref().map(|mail| {
|
let mail = mail.as_ref().map(|mail| {
|
||||||
let (content, signatures_detached) = validate_detached_signature(mail, &public_keyring)
|
let (content, signatures_detached) = validate_detached_signature(mail, &public_keyring)
|
||||||
.unwrap_or((mail, Default::default()));
|
.unwrap_or((mail, Default::default()));
|
||||||
|
let signatures_detached = signatures_detached
|
||||||
|
.into_iter()
|
||||||
|
.map(|fp| (fp, Vec::new()))
|
||||||
|
.collect::<HashMap<_, _>>();
|
||||||
signatures.extend(signatures_detached);
|
signatures.extend(signatures_detached);
|
||||||
content
|
content
|
||||||
});
|
});
|
||||||
@@ -640,6 +645,10 @@ impl MimeMessage {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let signature = signatures
|
||||||
|
.into_iter()
|
||||||
|
.last()
|
||||||
|
.map(|(fp, recipient_fps)| (fp, recipient_fps.into_iter().collect::<HashSet<_>>()));
|
||||||
let mut parser = MimeMessage {
|
let mut parser = MimeMessage {
|
||||||
parts: Vec::new(),
|
parts: Vec::new(),
|
||||||
headers,
|
headers,
|
||||||
@@ -655,7 +664,7 @@ impl MimeMessage {
|
|||||||
decrypting_failed: mail.is_err(),
|
decrypting_failed: mail.is_err(),
|
||||||
|
|
||||||
// only non-empty if it was a valid autocrypt message
|
// only non-empty if it was a valid autocrypt message
|
||||||
signature: signatures.into_iter().last(),
|
signature,
|
||||||
autocrypt_fingerprint,
|
autocrypt_fingerprint,
|
||||||
gossiped_keys,
|
gossiped_keys,
|
||||||
is_forwarded: false,
|
is_forwarded: false,
|
||||||
|
|||||||
35
src/pgp.rs
35
src/pgp.rs
@@ -1,6 +1,6 @@
|
|||||||
//! OpenPGP helper module using [rPGP facilities](https://github.com/rpgp/rpgp).
|
//! OpenPGP helper module using [rPGP facilities](https://github.com/rpgp/rpgp).
|
||||||
|
|
||||||
use std::collections::{BTreeMap, HashSet};
|
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||||
use std::io::{BufRead, Cursor};
|
use std::io::{BufRead, Cursor};
|
||||||
|
|
||||||
use anyhow::{Context as _, Result, bail};
|
use anyhow::{Context as _, Result, bail};
|
||||||
@@ -370,19 +370,28 @@ fn check_symmetric_encryption(msg: &Message<'_>) -> std::result::Result<(), &'st
|
|||||||
|
|
||||||
/// Returns fingerprints
|
/// Returns fingerprints
|
||||||
/// of all keys from the `public_keys_for_validation` keyring that
|
/// of all keys from the `public_keys_for_validation` keyring that
|
||||||
/// have valid signatures there.
|
/// have valid signatures in `msg` and corresponding intended recipient fingerprints
|
||||||
|
/// (<https://www.rfc-editor.org/rfc/rfc9580.html#name-intended-recipient-fingerpr>) if any.
|
||||||
///
|
///
|
||||||
/// If the message is wrongly signed, HashSet will be empty.
|
/// If the message is wrongly signed, returns an empty map.
|
||||||
pub fn valid_signature_fingerprints(
|
pub fn valid_signature_fingerprints(
|
||||||
msg: &pgp::composed::Message,
|
msg: &pgp::composed::Message,
|
||||||
public_keys_for_validation: &[SignedPublicKey],
|
public_keys_for_validation: &[SignedPublicKey],
|
||||||
) -> HashSet<Fingerprint> {
|
) -> HashMap<Fingerprint, Vec<Fingerprint>> {
|
||||||
let mut ret_signature_fingerprints: HashSet<Fingerprint> = Default::default();
|
let mut ret_signature_fingerprints = HashMap::new();
|
||||||
if msg.is_signed() {
|
if msg.is_signed() {
|
||||||
for pkey in public_keys_for_validation {
|
for pkey in public_keys_for_validation {
|
||||||
if msg.verify(&pkey.primary_key).is_ok() {
|
if let Ok(signature) = msg.verify(&pkey.primary_key) {
|
||||||
let fp = pkey.dc_fingerprint();
|
let fp = pkey.dc_fingerprint();
|
||||||
ret_signature_fingerprints.insert(fp);
|
let mut recipient_fps = Vec::new();
|
||||||
|
if let Some(cfg) = signature.config() {
|
||||||
|
for subpkt in &cfg.hashed_subpackets {
|
||||||
|
if let SubpacketData::IntendedRecipientFingerprint(fp) = &subpkt.data {
|
||||||
|
recipient_fps.push(fp.clone().into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret_signature_fingerprints.insert(fp, recipient_fps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -497,13 +506,14 @@ mod tests {
|
|||||||
use pgp::composed::Esk;
|
use pgp::composed::Esk;
|
||||||
use pgp::packet::PublicKeyEncryptedSessionKey;
|
use pgp::packet::PublicKeyEncryptedSessionKey;
|
||||||
|
|
||||||
|
#[expect(clippy::type_complexity)]
|
||||||
fn pk_decrypt_and_validate<'a>(
|
fn pk_decrypt_and_validate<'a>(
|
||||||
ctext: &'a [u8],
|
ctext: &'a [u8],
|
||||||
private_keys_for_decryption: &'a [SignedSecretKey],
|
private_keys_for_decryption: &'a [SignedSecretKey],
|
||||||
public_keys_for_validation: &[SignedPublicKey],
|
public_keys_for_validation: &[SignedPublicKey],
|
||||||
) -> Result<(
|
) -> Result<(
|
||||||
pgp::composed::Message<'static>,
|
pgp::composed::Message<'static>,
|
||||||
HashSet<Fingerprint>,
|
HashMap<Fingerprint, Vec<Fingerprint>>,
|
||||||
Vec<u8>,
|
Vec<u8>,
|
||||||
)> {
|
)> {
|
||||||
let mut msg = decrypt(ctext.to_vec(), private_keys_for_decryption, &[])?;
|
let mut msg = decrypt(ctext.to_vec(), private_keys_for_decryption, &[])?;
|
||||||
@@ -611,7 +621,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn test_decrypt_singed() {
|
async fn test_decrypt_signed() {
|
||||||
// Check decrypting as Alice
|
// Check decrypting as Alice
|
||||||
let decrypt_keyring = vec![KEYS.alice_secret.clone()];
|
let decrypt_keyring = vec![KEYS.alice_secret.clone()];
|
||||||
let sig_check_keyring = vec![KEYS.alice_public.clone()];
|
let sig_check_keyring = vec![KEYS.alice_public.clone()];
|
||||||
@@ -623,6 +633,10 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(content, CLEARTEXT);
|
assert_eq!(content, CLEARTEXT);
|
||||||
assert_eq!(valid_signatures.len(), 1);
|
assert_eq!(valid_signatures.len(), 1);
|
||||||
|
for recipient_fps in valid_signatures.values() {
|
||||||
|
// Intended Recipient Fingerprint subpackets aren't added currently.
|
||||||
|
assert_eq!(recipient_fps.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
// Check decrypting as Bob
|
// Check decrypting as Bob
|
||||||
let decrypt_keyring = vec![KEYS.bob_secret.clone()];
|
let decrypt_keyring = vec![KEYS.bob_secret.clone()];
|
||||||
@@ -635,6 +649,9 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(content, CLEARTEXT);
|
assert_eq!(content, CLEARTEXT);
|
||||||
assert_eq!(valid_signatures.len(), 1);
|
assert_eq!(valid_signatures.len(), 1);
|
||||||
|
for recipient_fps in valid_signatures.values() {
|
||||||
|
assert_eq!(recipient_fps.len(), 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
|||||||
@@ -577,7 +577,7 @@ pub(crate) async fn receive_imf_inner(
|
|||||||
// For example, GitHub sends messages from `notifications@github.com`,
|
// For example, GitHub sends messages from `notifications@github.com`,
|
||||||
// but uses display name of the user whose action generated the notification
|
// but uses display name of the user whose action generated the notification
|
||||||
// as the display name.
|
// as the display name.
|
||||||
let fingerprint = mime_parser.signature.as_ref();
|
let fingerprint = mime_parser.signature.as_ref().map(|(fp, _)| fp);
|
||||||
let (from_id, _from_id_blocked, incoming_origin) = match from_field_to_contact_id(
|
let (from_id, _from_id_blocked, incoming_origin) = match from_field_to_contact_id(
|
||||||
context,
|
context,
|
||||||
&mime_parser.from,
|
&mime_parser.from,
|
||||||
@@ -3901,7 +3901,7 @@ async fn has_verified_encryption(
|
|||||||
let signed_with_verified_key = mimeparser
|
let signed_with_verified_key = mimeparser
|
||||||
.signature
|
.signature
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|signature| *signature == fingerprint);
|
.is_some_and(|(signature, _)| *signature == fingerprint);
|
||||||
if signed_with_verified_key {
|
if signed_with_verified_key {
|
||||||
Ok(Verified)
|
Ok(Verified)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -784,7 +784,7 @@ fn encrypted_and_signed(
|
|||||||
mimeparser: &MimeMessage,
|
mimeparser: &MimeMessage,
|
||||||
expected_fingerprint: &Fingerprint,
|
expected_fingerprint: &Fingerprint,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if let Some(signature) = mimeparser.signature.as_ref() {
|
if let Some((signature, _)) = mimeparser.signature.as_ref() {
|
||||||
if signature == expected_fingerprint {
|
if signature == expected_fingerprint {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user