From 6d125028f54cb214dc522de9a8d44dd993aa881f Mon Sep 17 00:00:00 2001 From: link2xt Date: Sun, 5 Dec 2021 12:18:48 +0000 Subject: [PATCH] Validate detached signatures --- src/e2ee.rs | 40 +++++++++++++++++++++++++++++++++++++--- src/pgp.rs | 29 ++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/src/e2ee.rs b/src/e2ee.rs index c624615e6..9236c60a2 100644 --- a/src/e2ee.rs +++ b/src/e2ee.rs @@ -295,6 +295,31 @@ async fn decrypt_if_autocrypt_message( .await } +/// Validates signatures of Multipart/Signed message part, as defined in RFC 1847. +/// +/// Returns `None` if the part is not a Multipart/Signed part, otherwise retruns the set of key +/// fingerprints for which there is a valid signature. +async fn validate_detached_signature( + mail: &ParsedMail<'_>, + public_keyring_for_validate: &Keyring, +) -> Result, HashSet)>> { + if mail.ctype.mimetype != "multipart/signed" { + return Ok(None); + } + + if let [first_part, second_part] = &mail.subparts[..] { + // First part is the content, second part is the signature. + let content = first_part.raw_bytes; + let signature = second_part.get_body_raw()?; + let ret_valid_signatures = + pgp::pk_validate(content, &signature, public_keyring_for_validate).await?; + + Ok(Some((content.to_vec(), ret_valid_signatures))) + } else { + Ok(None) + } +} + /// Returns Ok(None) if nothing encrypted was found. async fn decrypt_part( mail: &ParsedMail<'_>, @@ -307,10 +332,19 @@ async fn decrypt_part( let (plain, ret_valid_signatures) = pgp::pk_decrypt(data, private_keyring, &public_keyring_for_validate).await?; - // If the message was wrongly or not signed, still return the plain text. - // The caller has to check the signatures then. + // Check for detached signatures. + // If decrypted part is a multipart/signed, then there is a detached signature. + let decrypted_part = mailparse::parse_mail(&plain)?; + if let Some((content, valid_detached_signatures)) = + validate_detached_signature(&decrypted_part, &public_keyring_for_validate).await? + { + return Ok(Some((content, valid_detached_signatures))); + } else { + // If the message was wrongly or not signed, still return the plain text. + // The caller has to check the signatures then. - return Ok(Some((plain, ret_valid_signatures))); + return Ok(Some((plain, ret_valid_signatures))); + } } Ok(None) diff --git a/src/pgp.rs b/src/pgp.rs index e09f5bd39..7a6f86c27 100644 --- a/src/pgp.rs +++ b/src/pgp.rs @@ -8,7 +8,7 @@ use anyhow::{bail, ensure, format_err, Result}; use pgp::armor::BlockType; use pgp::composed::{ Deserializable, KeyType as PgpKeyType, Message, SecretKeyParamsBuilder, SignedPublicKey, - SignedPublicSubKey, SignedSecretKey, SubkeyParamsBuilder, + SignedPublicSubKey, SignedSecretKey, StandaloneSignature, SubkeyParamsBuilder, }; use pgp::crypto::{HashAlgorithm, SymmetricKeyAlgorithm}; use pgp::types::{ @@ -330,6 +330,33 @@ pub async fn pk_decrypt( } } +/// Validates detached signature. +pub async fn pk_validate( + content: &[u8], + signature: &[u8], + public_keys_for_validation: &Keyring, +) -> Result> { + let mut ret: HashSet = Default::default(); + + let standalone_signature = StandaloneSignature::from_armor_single(Cursor::new(signature))?.0; + let pkeys = public_keys_for_validation.keys(); + + // Remove trailing CRLF before the delimiter. + // According to RFC 3156 it is considered to be part of the MIME delimiter for the purpose of + // OpenPGP signature calculation. + let content = content + .get(..content.len().saturating_sub(2)) + .ok_or_else(|| format_err!("index is out of range"))?; + + for pkey in pkeys { + if standalone_signature.verify(pkey, content).is_ok() { + let fp = DcKey::fingerprint(pkey); + ret.insert(fp); + } + } + Ok(ret) +} + /// Symmetric encryption. pub async fn symm_encrypt(passphrase: &str, plain: &[u8]) -> Result { let lit_msg = Message::new_literal_bytes("", plain);