// // Husk milter // // cryptographic functions // use std::io::Write; use std::sync::Arc; use anyhow; use bytes::Bytes; use sequoia_openpgp::parse::{PacketParser, PacketParserResult, Parse}; use sequoia_openpgp::policy::StandardPolicy; use sequoia_openpgp::serialize::stream::{Message, Armorer, Encryptor, LiteralWriter}; 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}; use wot::store::Store as _; use wot::{Depth, Path}; use crate::types::errors::HuskError; use crate::types::husk_context::HuskContext; use crate::types::introducer::Introducer; use crate::types::recipient::Recipient; pub fn get_local_trust_root(cert_store: &CertStore) -> anyhow::Result { let root = cert_store.certd() .and_then(|certd| { match certd.certd().get(sequoia_cert_store::store::openpgp_cert_d::TRUST_ROOT) { Ok(Some((_tag, bytes))) => Some(bytes), Ok(None) => None, Err(_) => None } }) .and_then(|bytes| { match RawCertParser::from_bytes(&bytes[..]) { Ok(mut parser) => { match parser.next() { Some(Ok(cert)) => { match Cert::from_bytes(cert.as_bytes()) { Ok(c) => Some(c), Err(_) => None, } }, Some(Err(_)) | None => None } }, Err(_) => None, } }); match root { Some(r) => Ok(r), None => Err(HuskError::NoLocalTrustRoot.into()) } } pub fn get_certificates<'hc>(context: &HuskContext<'hc>, email: &str) -> Vec>> { let cert_store = &context.cert_store; let trust_roots = vec![(context.local_trust_root.fingerprint(), wot::FULLY_TRUSTED)]; let wot_store = wot::store::CertStore::from_store(cert_store, &context.policy, None); let nb = wot::NetworkBuilder::rooted(&wot_store, &*trust_roots); let n = nb.build(); let bindings: Vec<_> = n.lookup_synopses_by_email(email) .into_iter() .map(|(fp, userid)| (fp, Some(userid))) .collect(); let mut result = vec![]; for (fpr, some_userid) in bindings { if let Some(userid) = some_userid { let paths = n.authenticate(userid, &fpr, wot::FULLY_TRUSTED); if paths.len() > 0 { log::debug!("{} authenticated!!!", email); for (path, _) in paths.iter() { if let Ok(cert) = cert_store.lookup_by_cert_fpr(&path.target().fingerprint()) { result.push(cert); } } } } } result } pub fn get_introducers<'c>(cert_store: &CertStore<'c>, policy: &StandardPolicy, local_trust_root: Fingerprint) -> Vec { let mut result = Vec::new(); let trust_roots = vec![(local_trust_root, wot::FULLY_TRUSTED)]; let wot_store = wot::store::CertStore::from_store(cert_store, policy, None); let n = wot::NetworkBuilder::rooted(&wot_store, &*trust_roots) .build(); n.certified_userids().iter().for_each(|(fpr, user_id)| { let paths = n.authenticate(user_id, fpr, wot::FULLY_TRUSTED); // find the longest path (highest trust depth) let mut depth = Depth::Limit(0); let mut target: Option<&Path> = None; paths.iter().for_each(|(path, amount)| { if *amount == wot::FULLY_TRUSTED { let d = path.residual_depth(); if d > depth { depth = d; target = Some(path); } } }); if depth > Depth::Limit(0) { if let Some(p) = target { if let Some(c) = p.certifications().last() { result.push( Introducer { cert: p.target().clone(), certification: c.clone(), }); } } } }); 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 } pub fn encrypt(context: &HuskContext<'_>, body: &Bytes, recipients: &Vec) -> anyhow::Result { let mut keys = Vec::new(); // XXX: handle the case where a certificate misses an encryption subkey for rcpt in recipients { if rcpt.can_encrypt() { for cert in &rcpt.certs { cert.with_policy(&context.policy, None)? .keys() .supported() .alive() .revoked(false) .for_transport_encryption() .for_each(|k| { keys.push(k); }); } } } if keys.len() > 0 { let mut sink = Vec::new(); let message = Message::new(&mut sink); let message = Armorer::new(message).build()?; let message = Encryptor::for_recipients(message, keys).build()?; let mut message = LiteralWriter::new(message).build()?; message.write_all(&body[..])?; message.finalize()?; Ok(Bytes::from(sink)) } else { Err(HuskError::NoEncryptionKeys.into()) } } #[cfg(test)] pub mod tests { use bytes::Bytes; #[test] pub fn test_is_encrypted() { use crate::crypto::is_encrypted; // cleartext let body = b"Hello world"; let body = Bytes::from(&body[..]); assert!(! is_encrypted(&body)); // a valid encrypted message let body = b"-----BEGIN PGP MESSAGE----- wWwGFQRhclhA/Qg3CiL81NAswyOWkyk8jxIBB0AmlBkVEVbQst60C/1+mcmIxLbG m0w+qv2aP5NQhAetCzDgA/KEhtim0Ebzf06jJG7bITPkYmfN2mizdChgv1f3y2eQ 80hYpgbD930RtKXTD1TSWgIJAgai2MelZrDAQXIaFKlRRY9u7R1YIXQmMa6uDced W/g9mJDd0ral30VL0FfuKRR2Je2TRfizqMb4tL4EIRFqrfSBesb9WcniEfBekG3B jk6lh01Ej9QTGQ== -----END PGP MESSAGE----- "; let body = Bytes::from(&body[..]); assert!(is_encrypted(&body)); // a PUBLIC KEY BLOCK is an artifact, but it's not encrypted let body = b"-----BEGIN PGP PUBLIC KEY BLOCK----- xjMEaUE/aRYJKwYBBAHaRw8BAQdAXScnsTjHmy1yZ/SmddPq7tmY4SZwsyWmRNei EX60UJDCwBEEHxYKAIMFgmlBP2kFiQWkj70DCwkHCRC9/0MioyzN/0cUAAAAAAAe ACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmfUHGvbmmp7vFwpMzGj36fZ Px+taozYHxyGljxsJnlV3AMVCggCmwECHgkWIQSQO2vdjfwfK4J6FaK9/0MioyzN /wAATokBAP9abr1BeV2qrvwF9TsQDlvgnaNPtzhPy00oDi17/8x5AP99DLK8hAJ3 FtejeAJ0/2+BfUZzRlF8tlSGe5/Iln9DCM0TPGFsaWNlQGV4YW1wbGUuY29tPsLA FAQTFgoAhgWCaUE/aQWJBaSPvQMLCQcJEL3/QyKjLM3/RxQAAAAAAB4AIHNhbHRA bm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZyWqUJWT+0+sGq1ci9IkqHvnsUN+c5LR B921ZbGlCwLXAxUKCAKZAQKbAQIeCRYhBJA7a92N/B8rgnoVor3/QyKjLM3/AAB5 +AEAvMCVVynlwsaFQvACga9NTfyZGQ7IjhFhjbCXY+CZbHoA/0FaajzhUF6mKQH7 RMwMK6Dp0YD2A34/DGb5KbDkLjIOzjMEaUE/aRYJKwYBBAHaRw8BAQdAd4Y3kt2s NszsrkMELS0FC2viTw+NEU0ZnAT/aD5eydLCwMUEGBYKATcFgmlBP2kFiQWkj70J EL3/QyKjLM3/RxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9y Z1R29gVOHk9hllft18uHrwEV0MgvzykG3yWKF1B+vT9hApsCvqAEGRYKAG8FgmlB P2kJEHx7uc2lNPhjRxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdw Lm9yZ9Q8ycbljQVzg2YapbQX+IJ9H+1KSgSLXQtVtCsaJh8RFiEEerJu6BkdLdse 21ngfHu5zaU0+GMAAD3BAQCzmPr7q3Wf1BFKO95WBhe66Cg+lAnoJZKbgHBpUsIB 6wEAmGzOcL1zYXRBWpquzFKS2bNxuFdpbewkxkf0axvMnA8WIQSQO2vdjfwfK4J6 FaK9/0MioyzN/wAAoD8A/2a7+DrKEjL7fAt7UZnPYZOXPEOpYXxUE3ZGQbnsKrdw AP0ae/B4LYXd89UwHpQUBEJHbY113W8NRUfpM/EJUQRdBM4zBGlBP2kWCSsGAQQB 2kcPAQEHQNhqbQtL5hS4w0n9H0hHMiD84WY39ukclu1yCtBii/+xwsDFBBgWCgE3 BYJpQT9pBYkFpI+9CRC9/0MioyzN/0cUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5z ZXF1b2lhLXBncC5vcmd6G9Mcoj01NqY/TIgPlhe4L5OMUS/R4cIc2wWl2EmQTAKb IL6gBBkWCgBvBYJpQT9pCRAR5pEu+j17CUcUAAAAAAAeACBzYWx0QG5vdGF0aW9u cy5zZXF1b2lhLXBncC5vcmeJ47SGs3Kf1A20PTM/BFkWz2zd2dfaiVx8zKNbXoEZ 9BYhBK5UVQP/gdjl2RIb3RHmkS76PXsJAACjYgD/SfQ8WoCnBDnywpVknkW8FtHd akewRjkT17Q5gK1NEWUA/jOMj8chhN4R6jnnWEb0ClujkkVMBWP7h2N5HWGN4DoD FiEEkDtr3Y38HyuCehWivf9DIqMszf8AAC7NAQCNZWDduo/vseZGLDNOK9y+krFZ fDSGVBmvBHGiGjX6agD/SHcsROm0C/HXe+KxS/SAhHob2KdZ8FQ+dCtr3oY/egXO OARpQT9pEgorBgEEAZdVAQUBAQdA1dxpaBPWnqr8lhzucvP2F746IIdfYk+UVImg pe3GD1UDAQgHwsAGBBgWCgB4BYJpQT9pBYkFpI+9CRC9/0MioyzN/0cUAAAAAAAe ACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmeDJU2fBZAzwn32lcgdEyjB /LtqMEQ7Qv5HwfJFRlseCAKbDBYhBJA7a92N/B8rgnoVor3/QyKjLM3/AADjHQEA yvN5w7wxf50yriNlrHCwRzQ4MljfJawXfAfOthJlh8gBANycfude13YmUeFvwuyQ Vmac4mRS69ivzHMLDNXUsMwB =WPEh -----END PGP PUBLIC KEY BLOCK----- "; let body = Bytes::from(&body[..]); assert!(! is_encrypted(&body)); // broken message let body = b"-----BEGIN PGP MESSAGE----- wWwGFQRhclhA/Qg3CiL81NAswyOWkyk8jxIBB0AmlBkVEVbQst60C/1+mcmIxLbG W/g9mJDd0ral30VL0FfuKRR2Je2TRfizqMb4tL4EIRFqrfSBesb9WcniEfBekG3B jk6lh01Ej9QTGQ== -----END PGP MESSAGE----- "; let body = Bytes::from(&body[..]); assert!(! is_encrypted(&body)); } }