283 lines
9.5 KiB
Rust
283 lines
9.5 KiB
Rust
//
|
|
// 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<Cert> {
|
|
|
|
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<Arc<LazyCert<'hc>>> {
|
|
|
|
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<Introducer> {
|
|
|
|
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<Recipient>)
|
|
-> anyhow::Result<Bytes> {
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
}
|