Check for encryption

- collect body of incoming mail.
- check if incoming mail is already encrypted - dont touch it if it is.
- check if recipients of that mail can encrypt.
This commit is contained in:
Malte Meiboom
2025-12-16 12:04:26 +01:00
parent e56eb8eb9a
commit 9b41457b8e
4 changed files with 132 additions and 17 deletions
+32 -3
View File
@@ -7,9 +7,10 @@
use std::sync::Arc;
use anyhow;
use sequoia_openpgp::parse::Parse;
use bytes::Bytes;
use sequoia_openpgp::parse::{PacketParser, PacketParserResult, Parse};
use sequoia_openpgp::policy::StandardPolicy;
use sequoia_openpgp::{Fingerprint, Cert};
use sequoia_openpgp::{Fingerprint, Cert, Packet};
use sequoia_openpgp::cert::raw::RawCertParser;
use sequoia_cert_store::{Store, CertStore, LazyCert};
use sequoia_wot::{self as wot};
@@ -55,7 +56,7 @@ pub fn get_local_trust_root(cert_store: &CertStore) -> anyhow::Result<Cert> {
}
pub fn get_certificates<'hc>(context: &HuskContext<'hc>, email: &str)
pub fn get_certificates<'hc>(context: &HuskContext<'hc>, email: &str)
-> Vec<Arc<LazyCert<'hc>>> {
let cert_store = &context.cert_store;
@@ -125,3 +126,31 @@ pub fn get_introducers<'c>(cert_store: &CertStore<'c>, policy: &StandardPolicy,
result
}
pub fn is_encrypted(body: &Bytes) -> bool {
let mut ppr = match PacketParser::from_bytes(body) {
Ok(pp) => pp,
Err(_) => return false, // no OpenPGP artifacts
};
let mut encrypted: bool = false;
while let PacketParserResult::Some(pp) = ppr {
if let Packet::SEIP(_) = pp.packet {
encrypted = true;
}
// parse the whole message, don't stop at first SEIP
ppr = match pp.recurse() {
Ok((_, next_ppr)) => next_ppr,
Err(_) => return false, // broken artifact -> not encrypted
}
}
if let PacketParserResult::EOF(eof) = ppr {
if eof.is_message().is_ok() && encrypted {
return true;
}
}
false
}
+43 -12
View File
@@ -17,16 +17,18 @@ use indymilter::{
EomContext,
Macros,
NegotiateContext,
ProtoOpts,
ProtoOpts,
SocketInfo,
Status,
Config,
Status,
Config,
ContextActions,
};
use crate::types::husk_context::HuskContext;
use crate::config::{HuskConfig, HuskConfigContainer};
use crate::mail;
use crate::crypto;
use crate::types::mail_context::ProtectionPossibility;
pub struct Daemon { }
@@ -101,7 +103,7 @@ impl Daemon {
log::error!("{}", e);
Status::Tempfail
}
}
}
}
async fn handle_mail(cx: &mut Context<HuskContext<'_>>, args: Vec<CString>) -> Status {
@@ -110,7 +112,7 @@ impl Daemon {
let mut sender: Option<String> = None;
for arg in args {
if let Some(s) = mail::to_email(&arg) {
if let Some(s) = mail::to_email(&arg) {
sender = Some(s);
break;
}
@@ -133,18 +135,18 @@ impl Daemon {
let mut rcpt: Option<String> = None;
for arg in args {
if let Some(s) = mail::to_email(&arg) {
if let Some(s) = mail::to_email(&arg) {
rcpt = Some(s);
break;
}
}
if rcpt.is_none() {
Status::Tempfail
if rcpt.is_none() {
Status::Tempfail
} else {
context.add_recipient(rcpt.unwrap()).unwrap();
Status::Continue
}
} else {
Status::Tempfail
}
@@ -153,7 +155,7 @@ impl Daemon {
async fn handle_data(cx: &mut Context<HuskContext<'_>>) -> Status {
log::debug!("DATA");
if let Some(ref mut context) = cx.data {
if let Some(ref mut _context) = cx.data {
// XXX: check for early accepting
// if there are no recipients with authenticated certificates
// tell the MTA that this milter is done and the mail can be further
@@ -176,10 +178,15 @@ impl Daemon {
Status::Continue
}
async fn handle_body(_cx: &mut Context<HuskContext<'_>>, chunk: Bytes) -> Status {
async fn handle_body(cx: &mut Context<HuskContext<'_>>, chunk: Bytes) -> Status {
log::debug!("BODY: chunk with {:?} bytes received", &chunk.len());
Status::Continue
if let Some(ref mut context) = cx.data {
context.mail.add_chunk(chunk);
Status::Continue
} else {
Status::Tempfail
}
}
async fn handle_eom(cx: &mut EomContext<HuskContext<'_>>) -> Status {
@@ -188,6 +195,30 @@ impl Daemon {
if let Some(ref mut context) = cx.data {
log::debug!("Mail from {:?} complete", context.mail.sender);
log::debug!("recipients: {:?}", context.mail.recipients);
log::debug!("body size: {}", context.mail.body_size);
let body = context.mail.collect_body();
// check if mail is already encrypted. If it is, keep it as such.
if crypto::is_encrypted(&body) {
log::info!("encrypted mail detected");
return Status::Accept;
}
// possible protection
// none -> accept
// partial -> accept (later: split)
// full -> encrypt
// check mailbody
// is encrypted -> accept
// else -> encrypt
let protection = ProtectionPossibility::from(&context.mail);
if protection == ProtectionPossibility::Full {
// encrypt
}
}
Status::Continue
+48 -2
View File
@@ -5,16 +5,25 @@
//
use std::sync::Arc;
use bytes::{Bytes, BytesMut};
use sequoia_cert_store::LazyCert;
use crate::types::recipient::Recipient;
#[derive(PartialEq)]
pub enum ProtectionPossibility {
NoProtection,
Partial,
Full
}
pub struct MailContext<'mc> {
pub sender: Option<String>,
pub recipients: Vec<Recipient<'mc>>,
// header
// body
// body size
body: Vec<Bytes>,
pub body_size: usize,
}
impl<'mc> MailContext<'mc> {
@@ -23,6 +32,8 @@ impl<'mc> MailContext<'mc> {
MailContext {
sender: None,
recipients: Vec::new(),
body: Vec::new(),
body_size: 0,
}
}
@@ -39,4 +50,39 @@ impl<'mc> MailContext<'mc> {
Ok(())
}
pub fn add_chunk(&mut self, chunk: Bytes) {
self.body_size += chunk.len();
self.body.push(chunk);
}
pub fn collect_body(&self) -> Bytes {
let mut buffer = BytesMut::with_capacity(self.body_size);
for chunk in &self.body {
buffer.extend_from_slice(&chunk[..]);
}
buffer.into()
}
}
impl From<&MailContext<'_>> for ProtectionPossibility {
fn from(mail_context: &MailContext<'_>) -> ProtectionPossibility {
let mut can_encrypt = false;
let mut cannot_encrypt = false;
for rcpt in mail_context.recipients.iter() {
if rcpt.can_encrypt() {
can_encrypt = true;
} else {
cannot_encrypt = true;
}
}
match (can_encrypt, cannot_encrypt) {
(true, false) => ProtectionPossibility::Full,
(true, true) => ProtectionPossibility::Partial,
(false, true) => ProtectionPossibility::NoProtection,
(false, false) => ProtectionPossibility::NoProtection, // this should not occure
}
}
}
+9
View File
@@ -36,4 +36,13 @@ impl TryFrom<CString> for Recipient<'_> {
}
}
impl Recipient<'_> {
pub fn can_encrypt(&self) -> bool {
if self.certs.len() == 0 {
return false;
}
true
}
}