Partially implement ClientCertificateStore for Filesystem

This commit is contained in:
Nathan Fisher 2023-06-22 19:04:51 -04:00
parent b957138048
commit e1239fea58
2 changed files with 111 additions and 15 deletions

View file

@ -5,6 +5,7 @@ edition = "2021"
[dependencies]
digest = "0.10"
rustls-pemfile = "1.0"
sha2 = "0.10"
time = "0.3"
x509-parser = "0.15"

View file

@ -1,10 +1,19 @@
use std::io::BufReader;
use crate::prelude::Certificate;
use {
super::*,
crate::{message::Parser as MessageParser, prelude::ParseMailboxError},
crate::{
message::Parser as MessageParser,
prelude::{ClientCertificateStore, ParseMailboxError},
},
rustls_pemfile::{read_one, Item},
std::{
ffi::OsString,
fs::{self, File},
io::{self, BufWriter, Write},
iter,
os::unix::fs::DirBuilderExt,
path::{Path, PathBuf},
},
@ -141,11 +150,8 @@ impl MailStore for Filesystem {
}
fn get_message(&self, user: &str, folder: &str, id: &str) -> Option<Message> {
self.get_folder(user, folder).and_then(|f| {
f.messages
.get(id)
.cloned()
})
self.get_folder(user, folder)
.and_then(|f| f.messages.get(id).cloned())
}
fn add_message(
@ -288,6 +294,77 @@ impl MultiDomain for Filesystem {
}
}
impl ClientCertificateStore for Filesystem {
fn get_certificate(&self, user: &Mailuser) -> Option<Certificate> {
let mut path = self.path.clone();
path.push(&user.host.to_string());
path.push(&user.username);
path.push("certificate.pem");
if path.exists() {
let Ok(fd) = File::open(&path) else {
return None;
};
let mut reader = BufReader::new(fd);
let mut certificate = Certificate {
der: vec![],
key: vec![],
};
for item in iter::from_fn(|| read_one(&mut reader).transpose()) {
match item {
Ok(Item::X509Certificate(cert)) => {
if certificate.der.is_empty() {
certificate.der = cert;
} else {
return None;
}
}
Ok(Item::RSAKey(key)) => {
if certificate.key.is_empty() {
certificate.key = key;
} else {
return None;
}
}
Ok(Item::PKCS8Key(key)) => {
if certificate.key.is_empty() {
certificate.key = key;
} else {
return None;
}
}
Ok(Item::ECKey(key)) => {
if certificate.key.is_empty() {
certificate.key = key;
} else {
return None;
}
}
_ => {}
}
}
if certificate.der.is_empty() || certificate.key.is_empty() {
None
} else {
Some(certificate)
}
} else {
None
}
}
fn insert_certificate(
&mut self,
user: &Mailuser,
certificate: Certificate,
) -> Option<Certificate> {
todo!()
}
fn contains_certificate(&self, user: &Mailuser) -> bool {
self.get_certificate(user).is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -299,10 +376,18 @@ mod tests {
#[test]
fn users() {
let users = store().users();
assert!(users.iter().any(|u| u.host.to_string() == "mail.gmi.org" && u.username == "jane" ));
assert!(users.iter().any(|u| u.host.to_string() == "mail.gmi.org" && u.username == "dick" ));
assert!(users.iter().any(|u| u.host.to_string() == "misfin.example.org" && u.username == "jane" ));
assert!(users.iter().any(|u| u.host.to_string() == "misfin.example.org" && u.username == "dick" ));
assert!(users
.iter()
.any(|u| u.host.to_string() == "mail.gmi.org" && u.username == "jane"));
assert!(users
.iter()
.any(|u| u.host.to_string() == "mail.gmi.org" && u.username == "dick"));
assert!(users
.iter()
.any(|u| u.host.to_string() == "misfin.example.org" && u.username == "jane"));
assert!(users
.iter()
.any(|u| u.host.to_string() == "misfin.example.org" && u.username == "dick"));
assert!(users.len() >= 4 && users.len() < 6);
}
@ -336,7 +421,9 @@ mod tests {
#[test]
fn get_message() {
let msg = store().get_message("jane@mail.gmi.org", "Inbox", "867017").unwrap();
let msg = store()
.get_message("jane@mail.gmi.org", "Inbox", "867017")
.unwrap();
assert_eq!(msg.from.username, "april");
assert_eq!(msg.from.host.subdomain.unwrap(), "we.wear");
}
@ -352,18 +439,26 @@ mod tests {
title: Some(String::from("Get a look at 'is 'orse")),
body: String::from("Gorgeous, innit?"),
};
store().add_message("dick@mail.gmi.org", "Inbox", msg).unwrap();
store()
.add_message("dick@mail.gmi.org", "Inbox", msg)
.unwrap();
assert!(PathBuf::from("test/mailstore/mail.gmi.org/dick/Inbox/1687407844.gmi").exists());
store().move_message("dick@mail.gmi.org", "1687407844", "Inbox", "Lists").unwrap();
store()
.move_message("dick@mail.gmi.org", "1687407844", "Inbox", "Lists")
.unwrap();
assert!(!PathBuf::from("test/mailstore/mail.gmi.org/dick/Inbox/1687407844.gmi").exists());
assert!(PathBuf::from("test/mailstore/mail.gmi.org/dick/Lists/1687407844.gmi").exists());
store().delete_message("dick@mail.gmi.org", "Lists", "1687407844").unwrap();
store()
.delete_message("dick@mail.gmi.org", "Lists", "1687407844")
.unwrap();
assert!(!PathBuf::from("test/mailstore/mail.gmi.org/dick/Lists/1687407844.gmi").exists());
}
#[test]
fn add_remove_user() {
store().add_user("rob@misfin.example.org Rob Zombie").unwrap();
store()
.add_user("rob@misfin.example.org Rob Zombie")
.unwrap();
assert!(store().has_mailuser("rob@misfin.example.org"));
assert!(store().remove_user("rob@misfin.example.org"));
assert!(!store().has_mailuser("rob@misfin.example.org"));