Add Certificate struct and ClientCertificateStore trait

This commit is contained in:
Nathan Fisher 2023-05-27 01:18:30 -04:00
parent 680ca4e9b9
commit 54a099bb44
8 changed files with 139 additions and 54 deletions

103
src/certificate.rs Normal file
View file

@ -0,0 +1,103 @@
use crate::mailuser::Mailuser;
use std::collections::{BTreeMap, HashMap};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct Certificate {
pub der: Vec<u8>,
pub key: Vec<u8>,
}
/// An item which stores known certificates. For convenience, this trait is implemented for
/// `std::collections::HashMap<String, String>` and `std::collections::BTreeMap<String, String>`
pub trait CertificateStore: Send + Sync {
/// Retrieves a certificate fingerprint using the hostname as it's key if
/// it has previously been saved
fn get_certificate(&self, host: &str) -> Option<String>;
/// Inserts a certificate fingerprint with the hostname as the key
fn insert_certificate(&mut self, host: &str, fingerprint: &str) -> Option<String>;
/// Returns true if a fingerprint has been stored for this host
fn contains_certificate(&self, host: &str) -> bool;
}
/// An item which stores client certificates. For convenience, this trait is implemented for
/// `std::collections::HashMap<String, Certificate>` and `std::collections::BTreeMap<String, Certificate>`
pub trait ClientCertificateStore: Send + Sync {
/// Retrieves a client certificate using the sender's address as the key
fn get_certificate(&self, user: &Mailuser) -> Option<Certificate>;
/// Inserts a client certificate with the sender's address as the key
fn insert_certificate(
&mut self,
user: &Mailuser,
certificate: Certificate,
) -> Option<Certificate>;
/// Returns true if the sender has a certificate in this store
fn contains_certificate(&self, user: &Mailuser) -> bool;
}
impl CertificateStore for HashMap<String, String> {
fn get_certificate(&self, host: &str) -> Option<String> {
self.get(host).cloned()
}
fn insert_certificate(&mut self, host: &str, fingerprint: &str) -> Option<String> {
self.insert(host.to_string(), fingerprint.to_string())
}
fn contains_certificate(&self, host: &str) -> bool {
self.contains_key(host)
}
}
impl CertificateStore for BTreeMap<String, String> {
fn get_certificate(&self, host: &str) -> Option<String> {
self.get(host).cloned()
}
fn insert_certificate(&mut self, host: &str, fingerprint: &str) -> Option<String> {
self.insert(host.to_string(), fingerprint.to_string())
}
fn contains_certificate(&self, host: &str) -> bool {
self.contains_key(host)
}
}
impl ClientCertificateStore for HashMap<String, Certificate> {
fn get_certificate(&self, user: &Mailuser) -> Option<Certificate> {
self.get(&user.to_string()).cloned()
}
fn insert_certificate(
&mut self,
user: &Mailuser,
certificate: Certificate,
) -> Option<Certificate> {
self.insert(user.to_string(), certificate)
}
fn contains_certificate(&self, user: &Mailuser) -> bool {
self.contains_key(&user.to_string())
}
}
impl ClientCertificateStore for BTreeMap<String, Certificate> {
fn get_certificate(&self, user: &Mailuser) -> Option<Certificate> {
self.get(&user.to_string()).cloned()
}
fn insert_certificate(
&mut self,
user: &Mailuser,
certificate: Certificate,
) -> Option<Certificate> {
self.insert(user.to_string(), certificate)
}
fn contains_certificate(&self, user: &Mailuser) -> bool {
self.contains_key(&user.to_string())
}
}

View file

@ -1,41 +0,0 @@
use std::collections::{BTreeMap, HashMap};
/// An item which stores known certificates. For convenience, this trait is implemented for
/// `std::collections::HashMap<String, String>` and `std::collections::BTreeMap<String, String>`
pub trait CertificateStore: Send + Sync {
/// Retrieves a certificate fingerprint using the hostname as it's key if
/// it has previously been saved
fn get_certificate(&self, host: &str) -> Option<String>;
/// Inserts a certificate fingerprint with the hostname as the key
fn insert_certificate(&mut self, host: &str, fingerprint: &str) -> Option<String>;
/// Returns true if a fingerprint has been stored for this host
fn contains_certificate(&self, host: &str) -> bool;
}
impl CertificateStore for HashMap<String, String> {
fn get_certificate(&self, host: &str) -> Option<String> {
self.get(host).cloned()
}
fn insert_certificate(&mut self, host: &str, fingerprint: &str) -> Option<String> {
self.insert(host.to_string(), fingerprint.to_string())
}
fn contains_certificate(&self, host: &str) -> bool {
self.contains_key(host)
}
}
impl CertificateStore for BTreeMap<String, String> {
fn get_certificate(&self, host: &str) -> Option<String> {
self.get(host).cloned()
}
fn insert_certificate(&mut self, host: &str, fingerprint: &str) -> Option<String> {
self.insert(host.to_string(), fingerprint.to_string())
}
fn contains_certificate(&self, host: &str) -> bool {
self.contains_key(host)
}
}

View file

@ -1,5 +1,5 @@
#![warn(clippy::all, clippy::pedantic)] #![warn(clippy::all, clippy::pedantic)]
mod certificate_store; mod certificate;
mod fingerprint; mod fingerprint;
mod host; mod host;
mod mailbox; mod mailbox;

View file

@ -1,4 +1,4 @@
use crate::prelude::Host; use crate::{mailuser::Mailuser, prelude::Host};
use std::{fmt, str::FromStr}; use std::{fmt, str::FromStr};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
@ -64,3 +64,13 @@ impl FromStr for Mailbox {
} }
} }
} }
impl From<Mailuser> for Mailbox {
fn from(value: Mailuser) -> Self {
Self {
username: value.username,
host: value.host,
blurb: None,
}
}
}

View file

@ -1,4 +1,4 @@
use crate::prelude::{Host, ParseMailboxError as Error}; use crate::prelude::{Host, Mailbox, ParseMailboxError as Error};
use std::{fmt, str::FromStr}; use std::{fmt, str::FromStr};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
@ -36,3 +36,12 @@ impl FromStr for Mailuser {
} }
} }
} }
impl From<Mailbox> for Mailuser {
fn from(value: Mailbox) -> Self {
Self {
username: value.username,
host: value.host,
}
}
}

View file

@ -1,5 +1,5 @@
pub use super::{ pub use super::{
certificate_store::CertificateStore, certificate::{Certificate, CertificateStore, ClientCertificateStore},
fingerprint::{Error as FingerprintError, Fingerprint, GetFingerprint}, fingerprint::{Error as FingerprintError, Fingerprint, GetFingerprint},
host::{Error as ParseHostError, Host}, host::{Error as ParseHostError, Host},
mailbox::{Error as ParseMailboxError, Mailbox}, mailbox::{Error as ParseMailboxError, Mailbox},

View file

@ -32,11 +32,15 @@ where
impl<S> Sender<S> impl<S> Sender<S>
where where
S: CertificateStore + 'static S: CertificateStore + 'static,
{ {
pub fn new(request_str: &str, store: S, client_store: S) -> Result<Self, Error> { pub fn new(request_str: &str, store: S, client_store: S) -> Result<Self, Error> {
let request: Request = request_str.parse()?; let request: Request = request_str.parse()?;
Ok(Self { request, store, client_store }) Ok(Self {
request,
store,
client_store,
})
} }
fn host_string(&self) -> String { fn host_string(&self) -> String {
@ -50,7 +54,9 @@ where
.try_into() .try_into()
.map_err(|_| Error::DnsError)?; .map_err(|_| Error::DnsError)?;
let mut it = self.request.sender.host.to_socket_addrs()?; let mut it = self.request.sender.host.to_socket_addrs()?;
let client_cert = self.client_store.get_certificate(&self.request.sender.to_string()); let client_cert = self
.client_store
.get_certificate(&self.request.sender.to_string());
let verifier = Arc::new(Verifier::new(self.store)); let verifier = Arc::new(Verifier::new(self.store));
let Some(socket_addrs) = it.next() else { let Some(socket_addrs) = it.next() else {
return Err(io::Error::new(io::ErrorKind::Other, "no data retrieved").into()); return Err(io::Error::new(io::ErrorKind::Other, "no data retrieved").into());
@ -61,13 +67,15 @@ where
.with_custom_certificate_verifier(verifier); .with_custom_certificate_verifier(verifier);
let cfg = match client_cert { let cfg = match client_cert {
None => cfg.with_no_client_auth(), None => cfg.with_no_client_auth(),
Some(_) => cfg.with_no_client_auth(),// todo: cfg.with_single_cert(cert_chain, key_der) Some(_) => cfg.with_no_client_auth(), // todo: cfg.with_single_cert(cert_chain, key_der)
}; };
let client = ClientConnection::new(Arc::new(cfg), dnsname)?; let client = ClientConnection::new(Arc::new(cfg), dnsname)?;
let mut stream = StreamOwned::new(client, tcp_stream); let mut stream = StreamOwned::new(client, tcp_stream);
stream.write_all(self.request.to_string().as_bytes())?; stream.write_all(self.request.to_string().as_bytes())?;
let mut buf = vec![]; let mut buf = vec![];
stream.read_to_end(&mut buf)?; stream.read_to_end(&mut buf)?;
stream.conn.send_close_notify();
drop(stream);
Ok(buf) Ok(buf)
} }
} }

View file

@ -4,11 +4,7 @@ use {
client::{ServerCertVerified, ServerCertVerifier}, client::{ServerCertVerified, ServerCertVerifier},
Certificate, Certificate,
}, },
std::{ std::{borrow::BorrowMut, sync::Mutex, time},
borrow::BorrowMut,
sync::Mutex,
time,
},
}; };
#[derive(Debug)] #[derive(Debug)]