From 8d4f50e3f682aa1ad73a2fc1107e2beae1deb37f Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Wed, 24 May 2023 02:07:41 -0400 Subject: [PATCH 01/10] Renamed server to receiver, client to sender --- .gitignore | 2 ++ src/lib.rs | 4 ++-- src/{server.rs => receiver.rs} | 0 src/{client.rs => sender.rs} | 0 src/{client => sender}/store.rs | 0 src/{client => sender}/verifier.rs | 0 6 files changed, 4 insertions(+), 2 deletions(-) rename src/{server.rs => receiver.rs} (100%) rename src/{client.rs => sender.rs} (100%) rename src/{client => sender}/store.rs (100%) rename src/{client => sender}/verifier.rs (100%) diff --git a/.gitignore b/.gitignore index 65a366f..c05fedb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ /Cargo.lock tags tags.lock +tags.temp + diff --git a/src/lib.rs b/src/lib.rs index cc24ec2..a88ea23 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,8 @@ #![warn(clippy::all, clippy::pedantic)] -pub mod client; pub mod fingerprint; pub mod host; +pub mod receiver; pub mod request; pub mod response; -pub mod server; +pub mod sender; pub mod status; diff --git a/src/server.rs b/src/receiver.rs similarity index 100% rename from src/server.rs rename to src/receiver.rs diff --git a/src/client.rs b/src/sender.rs similarity index 100% rename from src/client.rs rename to src/sender.rs diff --git a/src/client/store.rs b/src/sender/store.rs similarity index 100% rename from src/client/store.rs rename to src/sender/store.rs diff --git a/src/client/verifier.rs b/src/sender/verifier.rs similarity index 100% rename from src/client/verifier.rs rename to src/sender/verifier.rs From e25ebfa3534feee5e73aeed339c1aea40f566ef2 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Wed, 24 May 2023 03:13:49 -0400 Subject: [PATCH 02/10] Add `Sender` struct --- src/sender.rs | 77 ++++++++++++++++++++++++++++++++++++++++++ src/sender/verifier.rs | 7 ++++ 2 files changed, 84 insertions(+) diff --git a/src/sender.rs b/src/sender.rs index cf78c92..7ae3617 100644 --- a/src/sender.rs +++ b/src/sender.rs @@ -1,3 +1,80 @@ +use std::{io::{Read, Write, self}, fmt}; +use crate::{request::{Request, ParseRequestError}, response::{Response, ParseResponseError}}; +use self::{verifier::Verifier, store::CertificateStore}; + pub mod store; pub mod verifier; +#[derive(Debug)] +pub struct Sender<'a, S: CertificateStore, C: Sized, T: Read + Write + Sized> { + pub request: Request, + pub verifier: Verifier<'a, S>, + pub stream: rustls::StreamOwned, +} + +#[derive(Debug)] +pub enum Error { + TlsError(rustls::Error), + RequestError(ParseRequestError), + ResponseError(ParseResponseError), + IoError(io::Error), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::TlsError(e) => write!(f, "{e}"), + Self::RequestError(e) => write!(f, "{e}"), + Self::ResponseError(e) => write!(f, "{e}"), + Self::IoError(e) => write!(f, "{e}"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::RequestError(e) => Some(e), + Self::ResponseError(e) => Some(e), + Self::TlsError(e) => Some(e), + Self::IoError(e) => Some(e), + } + } +} + +impl From for Error { + fn from(value: rustls::Error) -> Self { + Self::TlsError(value) + } +} + +impl From for Error { + fn from(value: ParseRequestError) -> Self { + Self::RequestError(value) + } +} + +impl From for Error { + fn from(value: ParseResponseError) -> Self { + Self::ResponseError(value) + } +} + +impl From for Error { + fn from(value: io::Error) -> Self { + Self::IoError(value) + } +} + +impl<'a, S, C, T> Sender<'a, S, C, T> +where S: CertificateStore + Sync, C: Sized, T: Read + Write + Sized { + pub fn new(request_str: &str, store: &'a S) -> Result { + let request: Request = request_str.parse()?; + let verifier = Verifier::new(store); + unimplemented!(); + } + + pub fn send(&mut self) -> Result { + unimplemented!(); + } +} diff --git a/src/sender/verifier.rs b/src/sender/verifier.rs index d341622..3bf1cae 100644 --- a/src/sender/verifier.rs +++ b/src/sender/verifier.rs @@ -2,6 +2,7 @@ use crate::fingerprint::Fingerprint; use rustls::{client::{ServerCertVerified, ServerCertVerifier}, Certificate}; use super::store::CertificateStore; +#[derive(Debug)] pub struct Verifier<'a, T: CertificateStore> { store: &'a T, } @@ -37,3 +38,9 @@ impl<'a, T: CertificateStore + Sync> ServerCertVerifier for Verifier<'a, T> { return Err(rustls::Error::General("Unrecognized certificate".to_string())); } } + +impl<'a, T: CertificateStore + Sync> Verifier<'a, T> { + pub fn new(store: &'a T) -> Self { + Self { store } + } +} From d2802ced83d2c29e65e7be95baefd638ec41de14 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Wed, 24 May 2023 03:15:26 -0400 Subject: [PATCH 03/10] Ran cargo fmt --- src/fingerprint.rs | 3 +-- src/receiver.rs | 1 + src/sender.rs | 18 ++++++++++++++---- src/sender/store.rs | 1 - src/sender/verifier.rs | 33 ++++++++++++++++++++------------- 5 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/fingerprint.rs b/src/fingerprint.rs index b5e6dce..f07e864 100644 --- a/src/fingerprint.rs +++ b/src/fingerprint.rs @@ -1,7 +1,7 @@ use digest::Digest; use rustls::Certificate; use sha2::Sha256; -use std::fmt::{Write, self}; +use std::fmt::{self, Write}; use x509_parser::prelude::*; pub trait Fingerprint { @@ -63,4 +63,3 @@ impl Fingerprint for Certificate { Ok((subject[3..].to_string(), s)) } } - diff --git a/src/receiver.rs b/src/receiver.rs index e69de29..8b13789 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -0,0 +1 @@ + diff --git a/src/sender.rs b/src/sender.rs index 7ae3617..520e35f 100644 --- a/src/sender.rs +++ b/src/sender.rs @@ -1,6 +1,12 @@ -use std::{io::{Read, Write, self}, fmt}; -use crate::{request::{Request, ParseRequestError}, response::{Response, ParseResponseError}}; -use self::{verifier::Verifier, store::CertificateStore}; +use self::{store::CertificateStore, verifier::Verifier}; +use crate::{ + request::{ParseRequestError, Request}, + response::{ParseResponseError, Response}, +}; +use std::{ + fmt, + io::{self, Read, Write}, +}; pub mod store; pub mod verifier; @@ -67,7 +73,11 @@ impl From for Error { } impl<'a, S, C, T> Sender<'a, S, C, T> -where S: CertificateStore + Sync, C: Sized, T: Read + Write + Sized { +where + S: CertificateStore + Sync, + C: Sized, + T: Read + Write + Sized, +{ pub fn new(request_str: &str, store: &'a S) -> Result { let request: Request = request_str.parse()?; let verifier = Verifier::new(store); diff --git a/src/sender/store.rs b/src/sender/store.rs index 8b55a63..03cf53c 100644 --- a/src/sender/store.rs +++ b/src/sender/store.rs @@ -2,4 +2,3 @@ pub trait CertificateStore { fn get(&self, host: &str) -> Option; fn insert(&mut self, host: &str, fingerprint: &str); } - diff --git a/src/sender/verifier.rs b/src/sender/verifier.rs index 3bf1cae..7506505 100644 --- a/src/sender/verifier.rs +++ b/src/sender/verifier.rs @@ -1,6 +1,9 @@ -use crate::fingerprint::Fingerprint; -use rustls::{client::{ServerCertVerified, ServerCertVerifier}, Certificate}; use super::store::CertificateStore; +use crate::fingerprint::Fingerprint; +use rustls::{ + client::{ServerCertVerified, ServerCertVerifier}, + Certificate, +}; #[derive(Debug)] pub struct Verifier<'a, T: CertificateStore> { @@ -9,19 +12,21 @@ pub struct Verifier<'a, T: CertificateStore> { impl<'a, T: CertificateStore + Sync> ServerCertVerifier for Verifier<'a, T> { fn verify_server_cert( - &self, - end_entity: &Certificate, - _intermediates: &[Certificate], - server_name: &rustls::ServerName, - _scts: &mut dyn Iterator, - _ocsp_response: &[u8], - _now: std::time::SystemTime, - ) -> Result { - let fp = end_entity.fingerprint().map_err(|e| rustls::Error::General(e.to_string()))?; + &self, + end_entity: &Certificate, + _intermediates: &[Certificate], + server_name: &rustls::ServerName, + _scts: &mut dyn Iterator, + _ocsp_response: &[u8], + _now: std::time::SystemTime, + ) -> Result { + let fp = end_entity + .fingerprint() + .map_err(|e| rustls::Error::General(e.to_string()))?; let name = match server_name { rustls::ServerName::DnsName(n) => n.as_ref().to_string(), rustls::ServerName::IpAddress(ip) => ip.to_string(), - _ => todo!() + _ => todo!(), }; if let Some(fingerprint) = match server_name { rustls::ServerName::DnsName(n) => self.store.get(n.as_ref()), @@ -35,7 +40,9 @@ impl<'a, T: CertificateStore + Sync> ServerCertVerifier for Verifier<'a, T> { // todo: need a way to update `self.store`. Probably will require // an Arc> for interior mutability } - return Err(rustls::Error::General("Unrecognized certificate".to_string())); + return Err(rustls::Error::General( + "Unrecognized certificate".to_string(), + )); } } From 02de65564080bd8d3205da6f1527e3c3063bf947 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Wed, 24 May 2023 12:07:18 -0400 Subject: [PATCH 04/10] Verifier: wrap `self.store` in Arc>; Refactoring: * Add prelude * Move some error types into modules * make most modules private and re-export their items --- src/lib.rs | 13 +++--- src/prelude.rs | 9 +++++ src/response.rs | 58 +++++--------------------- src/response/error.rs | 45 +++++++++++++++++++++ src/sender.rs | 92 +++++++++--------------------------------- src/sender/error.rs | 59 +++++++++++++++++++++++++++ src/sender/store.rs | 4 -- src/sender/verifier.rs | 34 ++++++++++------ 8 files changed, 171 insertions(+), 143 deletions(-) create mode 100644 src/prelude.rs create mode 100644 src/response/error.rs create mode 100644 src/sender/error.rs delete mode 100644 src/sender/store.rs diff --git a/src/lib.rs b/src/lib.rs index a88ea23..779971d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,9 @@ #![warn(clippy::all, clippy::pedantic)] -pub mod fingerprint; -pub mod host; +mod fingerprint; +mod host; +pub mod prelude; pub mod receiver; -pub mod request; -pub mod response; -pub mod sender; -pub mod status; +mod request; +mod response; +mod sender; +mod status; diff --git a/src/prelude.rs b/src/prelude.rs new file mode 100644 index 0000000..a42fb1e --- /dev/null +++ b/src/prelude.rs @@ -0,0 +1,9 @@ +pub use super::{ + fingerprint::{Fingerprint, Error as FingerprintError}, + host::{Host, ParseHostError}, + receiver, + response::{Response, Error as ParseResponseError}, + request::{Request, ParseRequestError}, + sender::{CertificateStore, Error as SenderError, Sender, Verifier}, + status::*, +}; diff --git a/src/response.rs b/src/response.rs index 77821f4..a1c377f 100644 --- a/src/response.rs +++ b/src/response.rs @@ -1,6 +1,8 @@ -use std::{fmt, num::ParseIntError, str::FromStr}; +use std::{fmt, str::FromStr}; +use crate::prelude::Status; -use crate::status::{ParseStatusError, Status}; +mod error; +pub use error::Error; #[derive(Clone, Debug, PartialEq)] pub struct Response { @@ -14,58 +16,18 @@ impl fmt::Display for Response { } } -#[derive(Clone, Debug, PartialEq)] -pub enum ParseResponseError { - TooLong, - ParseInt(ParseIntError), - StatusError, - Malformed, -} - -impl fmt::Display for ParseResponseError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::TooLong => write!(f, "ParseResponseError: too long"), - Self::ParseInt(e) => write!(f, "ParseResponseError: {e}"), - Self::StatusError => write!(f, "ParseResponseError: Invalid Status"), - Self::Malformed => write!(f, "ParseResponseError: Malformed"), - } - } -} - -impl std::error::Error for ParseResponseError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::ParseInt(e) => Some(e), - _ => None, - } - } -} - -impl From for ParseResponseError { - fn from(value: ParseIntError) -> Self { - Self::ParseInt(value) - } -} - -impl From for ParseResponseError { - fn from(_value: ParseStatusError) -> Self { - Self::StatusError - } -} - impl FromStr for Response { - type Err = ParseResponseError; + type Err = Error; fn from_str(s: &str) -> Result { if s.len() > 2048 { - return Err(ParseResponseError::TooLong); + return Err(Error::TooLong); } if !s.ends_with("\r\n") { - return Err(ParseResponseError::Malformed); + return Err(Error::Malformed); } let Some((status, meta)) = s.split_once(' ') else { - return Err(ParseResponseError::Malformed); + return Err(Error::Malformed); }; let status: u8 = status.parse()?; let status: Status = status.try_into()?; @@ -90,14 +52,14 @@ mod tests { #[test] fn parse_badend() { let response = "20 message delivered\n".parse::(); - assert_eq!(response, Err(ParseResponseError::Malformed)); + assert_eq!(response, Err(Error::Malformed)); } #[test] fn parse_badint() { let response = "twenty message deliverred\r\n".parse::(); match response { - Err(ParseResponseError::ParseInt(_)) => {} + Err(Error::ParseInt(_)) => {} _ => panic!(), } } diff --git a/src/response/error.rs b/src/response/error.rs new file mode 100644 index 0000000..7783943 --- /dev/null +++ b/src/response/error.rs @@ -0,0 +1,45 @@ +use { + crate::prelude::ParseStatusError, + std::{fmt, num::ParseIntError}, +}; + +#[derive(Clone, Debug, PartialEq)] +pub enum Error { + TooLong, + ParseInt(ParseIntError), + StatusError, + Malformed, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::TooLong => write!(f, "ParseResponseError: too long"), + Self::ParseInt(e) => write!(f, "ParseResponseError: {e}"), + Self::StatusError => write!(f, "ParseResponseError: Invalid Status"), + Self::Malformed => write!(f, "ParseResponseError: Malformed"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::ParseInt(e) => Some(e), + _ => None, + } + } +} + +impl From for Error { + fn from(value: ParseIntError) -> Self { + Self::ParseInt(value) + } +} + +impl From for Error { + fn from(_value: ParseStatusError) -> Self { + Self::StatusError + } +} + diff --git a/src/sender.rs b/src/sender.rs index 520e35f..979c175 100644 --- a/src/sender.rs +++ b/src/sender.rs @@ -1,84 +1,32 @@ -use self::{store::CertificateStore, verifier::Verifier}; use crate::{ - request::{ParseRequestError, Request}, - response::{ParseResponseError, Response}, -}; -use std::{ - fmt, - io::{self, Read, Write}, + request::Request, + response::Response, }; +use std::io::{Read, Write}; +pub use self::{error::Error, verifier::{CertificateStore, Verifier}}; -pub mod store; -pub mod verifier; +mod error; +mod verifier; #[derive(Debug)] -pub struct Sender<'a, S: CertificateStore, C: Sized, T: Read + Write + Sized> { - pub request: Request, - pub verifier: Verifier<'a, S>, - pub stream: rustls::StreamOwned, -} - -#[derive(Debug)] -pub enum Error { - TlsError(rustls::Error), - RequestError(ParseRequestError), - ResponseError(ParseResponseError), - IoError(io::Error), -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::TlsError(e) => write!(f, "{e}"), - Self::RequestError(e) => write!(f, "{e}"), - Self::ResponseError(e) => write!(f, "{e}"), - Self::IoError(e) => write!(f, "{e}"), - } - } -} - -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::RequestError(e) => Some(e), - Self::ResponseError(e) => Some(e), - Self::TlsError(e) => Some(e), - Self::IoError(e) => Some(e), - } - } -} - -impl From for Error { - fn from(value: rustls::Error) -> Self { - Self::TlsError(value) - } -} - -impl From for Error { - fn from(value: ParseRequestError) -> Self { - Self::RequestError(value) - } -} - -impl From for Error { - fn from(value: ParseResponseError) -> Self { - Self::ResponseError(value) - } -} - -impl From for Error { - fn from(value: io::Error) -> Self { - Self::IoError(value) - } -} - -impl<'a, S, C, T> Sender<'a, S, C, T> +pub struct Sender where - S: CertificateStore + Sync, + S: CertificateStore, C: Sized, T: Read + Write + Sized, { - pub fn new(request_str: &str, store: &'a S) -> Result { + pub request: Request, + pub verifier: Verifier, + pub stream: rustls::StreamOwned, +} + +impl Sender +where + S: CertificateStore, + C: Sized, + T: Read + Write + Sized, +{ + pub fn new(request_str: &str, store: S) -> Result { let request: Request = request_str.parse()?; let verifier = Verifier::new(store); unimplemented!(); diff --git a/src/sender/error.rs b/src/sender/error.rs new file mode 100644 index 0000000..ae7e573 --- /dev/null +++ b/src/sender/error.rs @@ -0,0 +1,59 @@ +use { + crate::prelude::{ParseRequestError, ParseResponseError}, + std::{fmt, io}, +}; + +#[derive(Debug)] +pub enum Error { + TlsError(rustls::Error), + RequestError(ParseRequestError), + ResponseError(ParseResponseError), + IoError(io::Error), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::TlsError(e) => write!(f, "{e}"), + Self::RequestError(e) => write!(f, "{e}"), + Self::ResponseError(e) => write!(f, "{e}"), + Self::IoError(e) => write!(f, "{e}"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::RequestError(e) => Some(e), + Self::ResponseError(e) => Some(e), + Self::TlsError(e) => Some(e), + Self::IoError(e) => Some(e), + } + } +} + +impl From for Error { + fn from(value: rustls::Error) -> Self { + Self::TlsError(value) + } +} + +impl From for Error { + fn from(value: ParseRequestError) -> Self { + Self::RequestError(value) + } +} + +impl From for Error { + fn from(value: ParseResponseError) -> Self { + Self::ResponseError(value) + } +} + +impl From for Error { + fn from(value: io::Error) -> Self { + Self::IoError(value) + } +} + diff --git a/src/sender/store.rs b/src/sender/store.rs deleted file mode 100644 index 03cf53c..0000000 --- a/src/sender/store.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub trait CertificateStore { - fn get(&self, host: &str) -> Option; - fn insert(&mut self, host: &str, fingerprint: &str); -} diff --git a/src/sender/verifier.rs b/src/sender/verifier.rs index 7506505..29e1a08 100644 --- a/src/sender/verifier.rs +++ b/src/sender/verifier.rs @@ -1,16 +1,21 @@ -use super::store::CertificateStore; use crate::fingerprint::Fingerprint; use rustls::{ client::{ServerCertVerified, ServerCertVerifier}, Certificate, }; +use std::sync::{Arc, Mutex}; -#[derive(Debug)] -pub struct Verifier<'a, T: CertificateStore> { - store: &'a T, +pub trait CertificateStore: Send + Sync { + fn get(&self, host: &str) -> Option; + fn insert(&mut self, host: &str, fingerprint: &str); } -impl<'a, T: CertificateStore + Sync> ServerCertVerifier for Verifier<'a, T> { +#[derive(Debug)] +pub struct Verifier { + store: Arc>, +} + +impl ServerCertVerifier for Verifier { fn verify_server_cert( &self, end_entity: &Certificate, @@ -29,8 +34,8 @@ impl<'a, T: CertificateStore + Sync> ServerCertVerifier for Verifier<'a, T> { _ => todo!(), }; if let Some(fingerprint) = match server_name { - rustls::ServerName::DnsName(n) => self.store.get(n.as_ref()), - rustls::ServerName::IpAddress(ip) => self.store.get(&ip.to_string()), + rustls::ServerName::DnsName(n) => self.store.lock().unwrap().get(n.as_ref()), + rustls::ServerName::IpAddress(ip) => self.store.lock().unwrap().get(&ip.to_string()), _ => todo!(), } { if fingerprint == fp.1 && name == fp.0 { @@ -38,16 +43,19 @@ impl<'a, T: CertificateStore + Sync> ServerCertVerifier for Verifier<'a, T> { } } else { // todo: need a way to update `self.store`. Probably will require - // an Arc> for interior mutability + // an Arc> for interior mutability. + // UPDATE: Now wrapped in Arc> } - return Err(rustls::Error::General( + Err(rustls::Error::General( "Unrecognized certificate".to_string(), - )); + )) } } -impl<'a, T: CertificateStore + Sync> Verifier<'a, T> { - pub fn new(store: &'a T) -> Self { - Self { store } +impl Verifier { + pub fn new(store: T) -> Self { + Self { + store: Arc::new(Mutex::new(store)), + } } } From 4a40603efc7fa20e69d2839894700b8837996e29 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Wed, 24 May 2023 13:14:14 -0400 Subject: [PATCH 05/10] Finish refactor and add a number of doc comments --- src/{fingerprint.rs => fingerprint/error.rs} | 31 +------- src/fingerprint/mod.rs | 33 ++++++++ src/host/error.rs | 19 +++++ src/{host.rs => host/mod.rs} | 52 ++++--------- src/prelude.rs | 10 +-- src/{receiver.rs => receiver/mod.rs} | 0 src/request/error.rs | 36 +++++++++ src/{request.rs => request/mod.rs} | 82 +++++++------------- src/response/error.rs | 8 +- src/{response.rs => response/mod.rs} | 3 +- src/sender/error.rs | 2 +- src/{sender.rs => sender/mod.rs} | 12 ++- src/sender/verifier.rs | 6 +- src/status/error.rs | 12 +++ src/{status.rs => status/mod.rs} | 42 +++++----- 15 files changed, 193 insertions(+), 155 deletions(-) rename src/{fingerprint.rs => fingerprint/error.rs} (51%) create mode 100644 src/fingerprint/mod.rs create mode 100644 src/host/error.rs rename src/{host.rs => host/mod.rs} (71%) rename src/{receiver.rs => receiver/mod.rs} (100%) create mode 100644 src/request/error.rs rename src/{request.rs => request/mod.rs} (59%) rename src/{response.rs => response/mod.rs} (96%) rename src/{sender.rs => sender/mod.rs} (66%) create mode 100644 src/status/error.rs rename src/{status.rs => status/mod.rs} (91%) diff --git a/src/fingerprint.rs b/src/fingerprint/error.rs similarity index 51% rename from src/fingerprint.rs rename to src/fingerprint/error.rs index f07e864..1e31d82 100644 --- a/src/fingerprint.rs +++ b/src/fingerprint/error.rs @@ -1,16 +1,7 @@ -use digest::Digest; -use rustls::Certificate; -use sha2::Sha256; -use std::fmt::{self, Write}; -use x509_parser::prelude::*; - -pub trait Fingerprint { - type Error; - - fn fingerprint(&self) -> Result<(String, String), Self::Error>; -} +use {std::fmt, x509_parser::prelude::X509Error}; #[derive(Clone, Debug, PartialEq)] +/// Errors which can occur when fingerprinting a certificate pub enum Error { Fmt, X509(X509Error), @@ -45,21 +36,3 @@ impl From> for Error { Self::X509(value.into()) } } - -impl Fingerprint for Certificate { - type Error = Error; - - fn fingerprint(&self) -> Result<(String, String), Self::Error> { - let (_, pk) = X509Certificate::from_der(self.as_ref())?; - let subject = pk.subject().to_string(); - let key = pk.public_key().subject_public_key.as_ref(); - let mut hasher = Sha256::new(); - hasher.update(key); - let res = hasher.finalize(); - let mut s = String::with_capacity(res.len()); - for c in res { - write!(s, "{c:02x}")?; - } - Ok((subject[3..].to_string(), s)) - } -} diff --git a/src/fingerprint/mod.rs b/src/fingerprint/mod.rs new file mode 100644 index 0000000..2d339ed --- /dev/null +++ b/src/fingerprint/mod.rs @@ -0,0 +1,33 @@ +use digest::Digest; +use rustls::Certificate; +use sha2::Sha256; +use std::fmt::Write; +use x509_parser::prelude::*; + +mod error; +pub use error::Error; + +/// Creates an sha256 fingerprint for a certificate +pub trait Fingerprint { + type Error; + + fn fingerprint(&self) -> Result<(String, String), Self::Error>; +} + +impl Fingerprint for Certificate { + type Error = Error; + + fn fingerprint(&self) -> Result<(String, String), Self::Error> { + let (_, pk) = X509Certificate::from_der(self.as_ref())?; + let subject = pk.subject().to_string(); + let key = pk.public_key().subject_public_key.as_ref(); + let mut hasher = Sha256::new(); + hasher.update(key); + let res = hasher.finalize(); + let mut s = String::with_capacity(res.len()); + for c in res { + write!(s, "{c:02x}")?; + } + Ok((subject[3..].to_string(), s)) + } +} diff --git a/src/host/error.rs b/src/host/error.rs new file mode 100644 index 0000000..e1d8d9e --- /dev/null +++ b/src/host/error.rs @@ -0,0 +1,19 @@ +use std::fmt; + +#[derive(Clone, Debug, PartialEq)] +/// Errors which can occur when parsing a host from a string +pub enum Error { + MissingSeparator, + EmptyDomain, + EmptyTld, + EmptySubdomain, + IllegalWhitespace, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self:?}") + } +} + +impl std::error::Error for Error {} diff --git a/src/host.rs b/src/host/mod.rs similarity index 71% rename from src/host.rs rename to src/host/mod.rs index d510cfe..3ac93d2 100644 --- a/src/host.rs +++ b/src/host/mod.rs @@ -1,6 +1,9 @@ use std::{fmt, str::FromStr}; +mod error; +pub use error::Error; #[derive(Clone, Debug, Default, PartialEq)] +/// Represents the fully qualified domain name for this host pub struct Host { pub subdomain: Option, pub domain: String, @@ -17,40 +20,23 @@ impl fmt::Display for Host { } } -#[derive(Clone, Debug, PartialEq)] -pub enum ParseHostError { - MissingSeparator, - EmptyDomain, - EmptyTld, - EmptySubdomain, - IllegalWhitespace, -} - -impl fmt::Display for ParseHostError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{self:?}") - } -} - -impl std::error::Error for ParseHostError {} - impl FromStr for Host { - type Err = ParseHostError; + type Err = Error; fn from_str(s: &str) -> Result { if s.contains(char::is_whitespace) { - return Err(ParseHostError::IllegalWhitespace); + return Err(Error::IllegalWhitespace); } if let Some((domain, tld)) = s.rsplit_once('.') { if domain.is_empty() { - Err(ParseHostError::EmptyDomain) + Err(Error::EmptyDomain) } else if tld.is_empty() { - Err(ParseHostError::EmptyTld) + Err(Error::EmptyTld) } else if let Some((subdomain, domain)) = domain.rsplit_once('.') { if subdomain.is_empty() { - Err(ParseHostError::EmptySubdomain) + Err(Error::EmptySubdomain) } else if domain.is_empty() { - Err(ParseHostError::EmptyDomain) + Err(Error::EmptyDomain) } else { Ok(Host { subdomain: Some(subdomain.to_string()), @@ -66,7 +52,7 @@ impl FromStr for Host { }) } } else { - Err(ParseHostError::MissingSeparator) + Err(Error::MissingSeparator) } } } @@ -79,37 +65,31 @@ mod tests { fn parse_missing_separator() { assert_eq!( "exampledotcom".parse::(), - Err(ParseHostError::MissingSeparator) + Err(Error::MissingSeparator) ); } #[test] fn parse_empty_tld() { - assert_eq!("example.".parse::(), Err(ParseHostError::EmptyTld)); + assert_eq!("example.".parse::(), Err(Error::EmptyTld)); } #[test] fn parse_empty_domain() { - assert_eq!(".com".parse::(), Err(ParseHostError::EmptyDomain)); - assert_eq!( - "example..com".parse::(), - Err(ParseHostError::EmptyDomain) - ); + assert_eq!(".com".parse::(), Err(Error::EmptyDomain)); + assert_eq!("example..com".parse::(), Err(Error::EmptyDomain)); } #[test] fn parse_empty_subdomain() { - assert_eq!( - ".example.com".parse::(), - Err(ParseHostError::EmptySubdomain) - ); + assert_eq!(".example.com".parse::(), Err(Error::EmptySubdomain)); } #[test] fn parse_illegal_whitespace() { assert_eq!( "exam\tple.com".parse::(), - Err(ParseHostError::IllegalWhitespace) + Err(Error::IllegalWhitespace) ); } diff --git a/src/prelude.rs b/src/prelude.rs index a42fb1e..b2c36a3 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,9 +1,9 @@ pub use super::{ - fingerprint::{Fingerprint, Error as FingerprintError}, - host::{Host, ParseHostError}, + fingerprint::{Error as FingerprintError, Fingerprint}, + host::{Error as ParseHostError, Host}, receiver, - response::{Response, Error as ParseResponseError}, - request::{Request, ParseRequestError}, + request::{Error as ParseRequestError, Request}, + response::{Error as ParseResponseError, Response}, sender::{CertificateStore, Error as SenderError, Sender, Verifier}, - status::*, + status::{Error as ParseStatusError, Status, Redirect, TemporaryFailure, PermanentFailure, AuthenticationFailure}, }; diff --git a/src/receiver.rs b/src/receiver/mod.rs similarity index 100% rename from src/receiver.rs rename to src/receiver/mod.rs diff --git a/src/request/error.rs b/src/request/error.rs new file mode 100644 index 0000000..63b807c --- /dev/null +++ b/src/request/error.rs @@ -0,0 +1,36 @@ +use {crate::prelude::ParseHostError, std::fmt}; + +#[derive(Clone, Debug, PartialEq)] +/// Errors which can occur when parsing a request +pub enum Error { + MissingSeparator, + EmptyUser, + EmptyHost, + EmptyMessage, + Malformed, + ParseHostError(ParseHostError), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::ParseHostError(e) => write!(f, "{e}"), + _ => write!(f, "{self:?}"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::ParseHostError(e) => Some(e), + _ => None, + } + } +} + +impl From for Error { + fn from(value: ParseHostError) -> Self { + Self::ParseHostError(value) + } +} diff --git a/src/request.rs b/src/request/mod.rs similarity index 59% rename from src/request.rs rename to src/request/mod.rs index 606464f..9844a35 100644 --- a/src/request.rs +++ b/src/request/mod.rs @@ -1,11 +1,18 @@ -use crate::host::{Host, ParseHostError}; +use crate::prelude::Host; use std::{fmt, str::FromStr}; +mod error; +pub use error::Error; + #[derive(Clone, Debug, PartialEq)] +/// The full request as sent by the `Sender` and received by the `Receiver` pub struct Request { - user: String, - host: Host, - message: String, + /// The username of the sender + pub user: String, + /// The fully qualified domain name of the sending server + pub host: Host, + /// The message body + pub message: String, } impl fmt::Display for Request { @@ -18,60 +25,26 @@ impl fmt::Display for Request { } } -#[derive(Clone, Debug, PartialEq)] -pub enum ParseRequestError { - MissingSeparator, - EmptyUser, - EmptyHost, - EmptyMessage, - Malformed, - ParseHostError(ParseHostError), -} - -impl fmt::Display for ParseRequestError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::ParseHostError(e) => write!(f, "{e}"), - _ => write!(f, "{self:?}"), - } - } -} - -impl std::error::Error for ParseRequestError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::ParseHostError(e) => Some(e), - _ => None, - } - } -} - -impl From for ParseRequestError { - fn from(value: ParseHostError) -> Self { - Self::ParseHostError(value) - } -} - impl FromStr for Request { - type Err = ParseRequestError; + type Err = Error; fn from_str(s: &str) -> Result { if let Some((user, message)) = s.split_once(' ') { let Some(message) = message.strip_suffix("\r\n").map(ToString::to_string) else { - return Err(ParseRequestError::Malformed); + return Err(Error::Malformed); }; if message.is_empty() { - return Err(ParseRequestError::EmptyMessage); + return Err(Error::EmptyMessage); } if let Some((user, host)) = user.rsplit_once('@') { if host.is_empty() { - return Err(ParseRequestError::EmptyHost); + return Err(Error::EmptyHost); } else if user == "misfin://" { - return Err(ParseRequestError::EmptyUser); + return Err(Error::EmptyUser); } let host = host.parse()?; let Some(user) = user.strip_prefix("misfin://").map(ToString::to_string) else { - return Err(ParseRequestError::Malformed); + return Err(Error::Malformed); }; Ok(Request { user, @@ -79,10 +52,10 @@ impl FromStr for Request { message, }) } else { - Err(ParseRequestError::MissingSeparator) + Err(Error::MissingSeparator) } } else { - Err(ParseRequestError::MissingSeparator) + Err(Error::MissingSeparator) } } } @@ -90,6 +63,7 @@ impl FromStr for Request { #[cfg(test)] mod tests { use super::*; + use crate::prelude::ParseHostError; const REQ_STR: &'static str = "misfin://john@misfin.example.com Anyone seen Jane?\r\n"; @@ -118,33 +92,33 @@ mod tests { #[test] fn parse_missing_sep() { let req = "misfin://john@example.comHelloWorld!\r\n".parse::(); - assert_eq!(req, Err(ParseRequestError::MissingSeparator)); + assert_eq!(req, Err(Error::MissingSeparator)); } #[test] fn parse_malformed() { let req = "misfin://john@example.com Hello World!\n".parse::(); - assert_eq!(req, Err(ParseRequestError::Malformed)); + assert_eq!(req, Err(Error::Malformed)); let req = "mail://john@example.com Hello World!\r\n".parse::(); - assert_eq!(req, Err(ParseRequestError::Malformed)); + assert_eq!(req, Err(Error::Malformed)); } #[test] fn parse_empty_user() { let req = "misfin://@example.com Hello World!\r\n".parse::(); - assert_eq!(req, Err(ParseRequestError::EmptyUser)); + assert_eq!(req, Err(Error::EmptyUser)); } #[test] fn parse_empty_host() { let req = "misfin://john@ Hello World!\r\n".parse::(); - assert_eq!(req, Err(ParseRequestError::EmptyHost)); + assert_eq!(req, Err(Error::EmptyHost)); } #[test] fn parse_empty_msg() { let req = "misfin://john@example.com \r\n".parse::(); - assert_eq!(req, Err(ParseRequestError::EmptyMessage)); + assert_eq!(req, Err(Error::EmptyMessage)); } #[test] @@ -152,9 +126,7 @@ mod tests { let req = "misfin://john@example\tfairy.com Hello World!\r\n".parse::(); assert_eq!( req, - Err(ParseRequestError::ParseHostError( - ParseHostError::IllegalWhitespace - )) + Err(Error::ParseHostError(ParseHostError::IllegalWhitespace)) ); } } diff --git a/src/response/error.rs b/src/response/error.rs index 7783943..639dd4d 100644 --- a/src/response/error.rs +++ b/src/response/error.rs @@ -4,10 +4,17 @@ use { }; #[derive(Clone, Debug, PartialEq)] +/// Errors which can occur when parsing the response sent by the receving server +/// back to the sender pub enum Error { + /// The message was too long. The Misfin spec allows a maximum length of + /// 2048 bytes for the message body. TooLong, + /// An error occurred parsing the status code as a number. ParseInt(ParseIntError), + /// The server sent an invalid or unrecognized status code StatusError, + /// The response was malformed Malformed, } @@ -42,4 +49,3 @@ impl From for Error { Self::StatusError } } - diff --git a/src/response.rs b/src/response/mod.rs similarity index 96% rename from src/response.rs rename to src/response/mod.rs index a1c377f..b47b985 100644 --- a/src/response.rs +++ b/src/response/mod.rs @@ -1,10 +1,11 @@ -use std::{fmt, str::FromStr}; use crate::prelude::Status; +use std::{fmt, str::FromStr}; mod error; pub use error::Error; #[derive(Clone, Debug, PartialEq)] +/// Sent from the receiving server back to the sending server pub struct Response { pub status: Status, pub meta: String, diff --git a/src/sender/error.rs b/src/sender/error.rs index ae7e573..13a9ce4 100644 --- a/src/sender/error.rs +++ b/src/sender/error.rs @@ -4,6 +4,7 @@ use { }; #[derive(Debug)] +/// Errors which might occur when sending a message pub enum Error { TlsError(rustls::Error), RequestError(ParseRequestError), @@ -56,4 +57,3 @@ impl From for Error { Self::IoError(value) } } - diff --git a/src/sender.rs b/src/sender/mod.rs similarity index 66% rename from src/sender.rs rename to src/sender/mod.rs index 979c175..0fd84d9 100644 --- a/src/sender.rs +++ b/src/sender/mod.rs @@ -1,22 +1,26 @@ -use crate::{ - request::Request, - response::Response, +pub use self::{ + error::Error, + verifier::{CertificateStore, Verifier}, }; +use crate::{request::Request, response::Response}; use std::io::{Read, Write}; -pub use self::{error::Error, verifier::{CertificateStore, Verifier}}; mod error; mod verifier; #[derive(Debug)] +/// Sends a piece of mail from the sending server to the receiving server pub struct Sender where S: CertificateStore, C: Sized, T: Read + Write + Sized, { + /// The full message text to be sent pub request: Request, + /// Verifies the receiving server's certificate pub verifier: Verifier, + /// The TLS stream used for the connection pub stream: rustls::StreamOwned, } diff --git a/src/sender/verifier.rs b/src/sender/verifier.rs index 29e1a08..4478f08 100644 --- a/src/sender/verifier.rs +++ b/src/sender/verifier.rs @@ -5,14 +5,18 @@ use rustls::{ }; use std::sync::{Arc, Mutex}; +/// An item which stores known certificates pub trait CertificateStore: Send + Sync { fn get(&self, host: &str) -> Option; fn insert(&mut self, host: &str, fingerprint: &str); } #[derive(Debug)] +/// A verifier is used to verify certificates sent by the receiving server +/// during the tls handshake. pub struct Verifier { - store: Arc>, + /// An item which serves as storage for certificates + pub store: Arc>, } impl ServerCertVerifier for Verifier { diff --git a/src/status/error.rs b/src/status/error.rs new file mode 100644 index 0000000..e64f26d --- /dev/null +++ b/src/status/error.rs @@ -0,0 +1,12 @@ +use std::fmt; + +#[derive(Debug, Clone, PartialEq)] +pub struct Error; + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self:?}") + } +} + +impl std::error::Error for Error {} diff --git a/src/status.rs b/src/status/mod.rs similarity index 91% rename from src/status.rs rename to src/status/mod.rs index a0991d6..ee24b2b 100644 --- a/src/status.rs +++ b/src/status/mod.rs @@ -1,18 +1,10 @@ -use std::fmt; - -#[derive(Debug, Clone, PartialEq)] -pub struct ParseStatusError; - -impl fmt::Display for ParseStatusError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{self:?}") - } -} - -impl std::error::Error for ParseStatusError {} +mod error; +pub use error::Error; #[derive(Debug, Clone, PartialEq)] #[repr(u8)] +/// Status codes sent back to the sender representing how a receiving server has +/// processed a message. pub enum Status { /// These codes are reserved, and must not be sent by a Misfin server. Input = 10, @@ -46,7 +38,7 @@ impl From for u8 { } impl TryFrom for Status { - type Error = ParseStatusError; + type Error = Error; fn try_from(value: u8) -> Result { match value / 10 { @@ -56,12 +48,13 @@ impl TryFrom for Status { 4 => Ok(Self::TemporaryFailure((value % 40).try_into()?)), 5 => Ok(Self::PermanentFailure((value % 50).try_into()?)), 6 => Ok(Self::AuthenticationFailure((value % 60).try_into()?)), - _ => Err(ParseStatusError), + _ => Err(Error), } } } #[derive(Debug, Clone, PartialEq)] +/// Status codes representing that a redirect is required pub enum Redirect { /// The mailbox has moved to a different address, and this message /// should be resent to that address. @@ -73,19 +66,21 @@ pub enum Redirect { } impl TryFrom for Redirect { - type Error = ParseStatusError; + type Error = Error; fn try_from(value: u8) -> Result { match value { 0 => Ok(Self::Temporary), 1 => Ok(Self::Permanent), n if n < 10 => Ok(Self::Other), - _ => Err(ParseStatusError), + _ => Err(Error), } } } #[derive(Debug, Clone, PartialEq)] +/// Status codes representing that a temporary failure has occurred. The sending server should +/// retry sending the message. pub enum TemporaryFailure { /// The mailserver experienced a transient issue, and the message /// should be resent. @@ -105,7 +100,7 @@ pub enum TemporaryFailure { } impl TryFrom for TemporaryFailure { - type Error = ParseStatusError; + type Error = Error; fn try_from(value: u8) -> Result { match value { @@ -116,12 +111,14 @@ impl TryFrom for TemporaryFailure { 4 => Ok(Self::RateLimit), 5 => Ok(Self::MailboxFull), n if n < 10 => Ok(Self::Other), - _ => Err(ParseStatusError), + _ => Err(Error), } } } #[derive(Debug, Clone, PartialEq)] +/// Status codes representing that a permanent failure has occurred and the sending server should +/// not resend the message. pub enum PermanentFailure { PermanentError = 0, MailboxNonexistent = 1, @@ -132,7 +129,7 @@ pub enum PermanentFailure { } impl TryFrom for PermanentFailure { - type Error = ParseStatusError; + type Error = Error; fn try_from(value: u8) -> Result { match value { @@ -142,12 +139,13 @@ impl TryFrom for PermanentFailure { 3 => Ok(Self::DomainNotServiced), 9 => Ok(Self::BadRequest), n if n < 10 => Ok(Self::Other), - _ => Err(ParseStatusError), + _ => Err(Error), } } } #[derive(Debug, Clone, PartialEq)] +/// Status codes representing an authentication failure pub enum AuthenticationFailure { CertificateRequired = 0, UnauthorizedSender = 1, @@ -158,7 +156,7 @@ pub enum AuthenticationFailure { } impl TryFrom for AuthenticationFailure { - type Error = ParseStatusError; + type Error = Error; fn try_from(value: u8) -> Result { match value { @@ -168,7 +166,7 @@ impl TryFrom for AuthenticationFailure { 3 => Ok(Self::IdentityMismatch), 4 => Ok(Self::ProofRequired), n if n < 10 => Ok(Self::Other), - _ => Err(ParseStatusError), + _ => Err(Error), } } } From 73375abec3ce05ea9a1d836ba210d0bb696d915f Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Wed, 24 May 2023 13:22:47 -0400 Subject: [PATCH 06/10] Add doc comments for all status subcodes --- src/status/error.rs | 1 + src/status/mod.rs | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/status/error.rs b/src/status/error.rs index e64f26d..0ba7b82 100644 --- a/src/status/error.rs +++ b/src/status/error.rs @@ -1,6 +1,7 @@ use std::fmt; #[derive(Debug, Clone, PartialEq)] +/// The receiving server sent an unrecognized or invalid status code pub struct Error; impl fmt::Display for Error { diff --git a/src/status/mod.rs b/src/status/mod.rs index ee24b2b..4508ea9 100644 --- a/src/status/mod.rs +++ b/src/status/mod.rs @@ -117,13 +117,20 @@ impl TryFrom for TemporaryFailure { } #[derive(Debug, Clone, PartialEq)] -/// Status codes representing that a permanent failure has occurred and the sending server should -/// not resend the message. +/// Status codes representing that a permanent failure has occurred and the sending +/// server should not resend the message. pub enum PermanentFailure { + /// Something is wrong with the mailserver, and you should not try to resend + /// your message. PermanentError = 0, + /// The mailbox you are trying to send to doesn't exist, and the mailserver + /// won't accept your message. MailboxNonexistent = 1, + /// The mailbox you are trying to send to existed once, but doesn't anymore. MailboxGone = 2, + /// This mailserver doesn't serve mail for the hostname you provided. DomainNotServiced = 3, + /// Your request is malformed, and won't be accepted by the mailserver. BadRequest = 9, Other, } @@ -147,10 +154,20 @@ impl TryFrom for PermanentFailure { #[derive(Debug, Clone, PartialEq)] /// Status codes representing an authentication failure pub enum AuthenticationFailure { + /// This mailserver doesn't accept anonymous mail, and you need to repeat your + /// request with a certificate. CertificateRequired = 0, + /// Your certificate was validated, but you are not allowed to send mail to + /// that mailbox. UnauthorizedSender = 1, + /// Your certificate might be legitimate, but it has a problem - it is expired, + /// or it doesn't point to a valid Misfin identity, etc. CertificateInvalid = 2, + /// Your certificate matches an identity that the mailserver recognizes, but + /// the fingerprint has changed, so it is rejecting your message. IdentityMismatch = 3, + /// The mailserver needs you to complete a task to confirm that you are a + /// legitimate sender. (This is reserved for a Hashcash style anti-spam measure). ProofRequired = 4, Other, } From ac5f2c21eb4fe431bc238d25f4e4897120d4ed22 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Wed, 24 May 2023 15:59:15 -0400 Subject: [PATCH 07/10] Add optional serde support; Remove `Clone` from most error types; --- Cargo.toml | 5 +++++ src/fingerprint/error.rs | 2 +- src/host/error.rs | 2 +- src/host/mod.rs | 4 ++++ src/prelude.rs | 5 ++++- src/request/error.rs | 2 +- src/request/mod.rs | 4 ++++ src/response/error.rs | 2 +- src/response/mod.rs | 4 ++++ src/status/error.rs | 2 +- src/status/mod.rs | 8 ++++++++ 11 files changed, 34 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 46e28e5..75649b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,8 @@ x509-parser = "0.15.0" version = "0.21.1" features = [ "dangerous_configuration" ] +[dependencies.serde] +version = "1.0" +features = ["derive"] +optional = true + diff --git a/src/fingerprint/error.rs b/src/fingerprint/error.rs index 1e31d82..2b08cd9 100644 --- a/src/fingerprint/error.rs +++ b/src/fingerprint/error.rs @@ -1,6 +1,6 @@ use {std::fmt, x509_parser::prelude::X509Error}; -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug)] /// Errors which can occur when fingerprinting a certificate pub enum Error { Fmt, diff --git a/src/host/error.rs b/src/host/error.rs index e1d8d9e..ad38fce 100644 --- a/src/host/error.rs +++ b/src/host/error.rs @@ -1,6 +1,6 @@ use std::fmt; -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug, PartialEq)] /// Errors which can occur when parsing a host from a string pub enum Error { MissingSeparator, diff --git a/src/host/mod.rs b/src/host/mod.rs index 3ac93d2..47bb75a 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -2,7 +2,11 @@ use std::{fmt, str::FromStr}; mod error; pub use error::Error; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + #[derive(Clone, Debug, Default, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] /// Represents the fully qualified domain name for this host pub struct Host { pub subdomain: Option, diff --git a/src/prelude.rs b/src/prelude.rs index b2c36a3..f772101 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -5,5 +5,8 @@ pub use super::{ request::{Error as ParseRequestError, Request}, response::{Error as ParseResponseError, Response}, sender::{CertificateStore, Error as SenderError, Sender, Verifier}, - status::{Error as ParseStatusError, Status, Redirect, TemporaryFailure, PermanentFailure, AuthenticationFailure}, + status::{ + AuthenticationFailure, Error as ParseStatusError, PermanentFailure, Redirect, Status, + TemporaryFailure, + }, }; diff --git a/src/request/error.rs b/src/request/error.rs index 63b807c..9620f09 100644 --- a/src/request/error.rs +++ b/src/request/error.rs @@ -1,6 +1,6 @@ use {crate::prelude::ParseHostError, std::fmt}; -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug, PartialEq)] /// Errors which can occur when parsing a request pub enum Error { MissingSeparator, diff --git a/src/request/mod.rs b/src/request/mod.rs index 9844a35..27e0a57 100644 --- a/src/request/mod.rs +++ b/src/request/mod.rs @@ -1,10 +1,14 @@ use crate::prelude::Host; use std::{fmt, str::FromStr}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + mod error; pub use error::Error; #[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] /// The full request as sent by the `Sender` and received by the `Receiver` pub struct Request { /// The username of the sender diff --git a/src/response/error.rs b/src/response/error.rs index 639dd4d..5783cfa 100644 --- a/src/response/error.rs +++ b/src/response/error.rs @@ -3,7 +3,7 @@ use { std::{fmt, num::ParseIntError}, }; -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug, PartialEq)] /// Errors which can occur when parsing the response sent by the receving server /// back to the sender pub enum Error { diff --git a/src/response/mod.rs b/src/response/mod.rs index b47b985..fc5cef7 100644 --- a/src/response/mod.rs +++ b/src/response/mod.rs @@ -4,7 +4,11 @@ use std::{fmt, str::FromStr}; mod error; pub use error::Error; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + #[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] /// Sent from the receiving server back to the sending server pub struct Response { pub status: Status, diff --git a/src/status/error.rs b/src/status/error.rs index 0ba7b82..ecb3e4c 100644 --- a/src/status/error.rs +++ b/src/status/error.rs @@ -1,6 +1,6 @@ use std::fmt; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug)] /// The receiving server sent an unrecognized or invalid status code pub struct Error; diff --git a/src/status/mod.rs b/src/status/mod.rs index 4508ea9..ca88b1d 100644 --- a/src/status/mod.rs +++ b/src/status/mod.rs @@ -1,7 +1,11 @@ mod error; pub use error::Error; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + #[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[repr(u8)] /// Status codes sent back to the sender representing how a receiving server has /// processed a message. @@ -54,6 +58,7 @@ impl TryFrom for Status { } #[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] /// Status codes representing that a redirect is required pub enum Redirect { /// The mailbox has moved to a different address, and this message @@ -79,6 +84,7 @@ impl TryFrom for Redirect { } #[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] /// Status codes representing that a temporary failure has occurred. The sending server should /// retry sending the message. pub enum TemporaryFailure { @@ -117,6 +123,7 @@ impl TryFrom for TemporaryFailure { } #[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] /// Status codes representing that a permanent failure has occurred and the sending /// server should not resend the message. pub enum PermanentFailure { @@ -152,6 +159,7 @@ impl TryFrom for PermanentFailure { } #[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] /// Status codes representing an authentication failure pub enum AuthenticationFailure { /// This mailserver doesn't accept anonymous mail, and you need to repeat your From 0d15b8d24beed26c9c5f992e8e91ba0ee67a592a Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Wed, 24 May 2023 17:37:44 -0400 Subject: [PATCH 08/10] Finish implementing TOFU for Verifier; TODO: additional checks for certificate validity; --- src/certificate_store.rs | 36 +++++++++++++++++++++++++++++ src/fingerprint/mod.rs | 6 +---- src/lib.rs | 3 ++- src/prelude.rs | 5 ++-- src/sender/mod.rs | 9 ++++---- src/sender/verifier.rs | 50 ++++++++++++++++++++++------------------ 6 files changed, 73 insertions(+), 36 deletions(-) create mode 100644 src/certificate_store.rs diff --git a/src/certificate_store.rs b/src/certificate_store.rs new file mode 100644 index 0000000..c258680 --- /dev/null +++ b/src/certificate_store.rs @@ -0,0 +1,36 @@ +use std::collections::{BTreeMap, HashMap}; + +/// An item which stores known certificates +pub trait CertificateStore: Send + Sync { + fn get_certificate(&self, host: &str) -> Option; + fn insert_certificate(&mut self, host: &str, fingerprint: &str) -> Option; + fn contains_certificate(&self, host: &str) -> bool; +} + +impl CertificateStore for HashMap { + fn get_certificate(&self, host: &str) -> Option { + self.get(host).cloned() + } + + fn insert_certificate(&mut self, host: &str, fingerprint: &str) -> Option { + self.insert(host.to_string(), fingerprint.to_string()) + } + + fn contains_certificate(&self, host: &str) -> bool { + self.contains_key(host) + } +} + +impl CertificateStore for BTreeMap { + fn get_certificate(&self, host: &str) -> Option { + self.get(host).cloned() + } + + fn insert_certificate(&mut self, host: &str, fingerprint: &str) -> Option { + self.insert(host.to_string(), fingerprint.to_string()) + } + + fn contains_certificate(&self, host: &str) -> bool { + self.contains_key(host) + } +} diff --git a/src/fingerprint/mod.rs b/src/fingerprint/mod.rs index 2d339ed..d25c7c8 100644 --- a/src/fingerprint/mod.rs +++ b/src/fingerprint/mod.rs @@ -1,8 +1,4 @@ -use digest::Digest; -use rustls::Certificate; -use sha2::Sha256; -use std::fmt::Write; -use x509_parser::prelude::*; +use {digest::Digest, rustls::Certificate, sha2::Sha256, std::fmt::Write, x509_parser::prelude::*}; mod error; pub use error::Error; diff --git a/src/lib.rs b/src/lib.rs index 779971d..6a4b595 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,9 @@ #![warn(clippy::all, clippy::pedantic)] +mod certificate_store; mod fingerprint; mod host; pub mod prelude; -pub mod receiver; +mod receiver; mod request; mod response; mod sender; diff --git a/src/prelude.rs b/src/prelude.rs index f772101..3b93bf8 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,10 +1,11 @@ pub use super::{ + certificate_store::CertificateStore, fingerprint::{Error as FingerprintError, Fingerprint}, host::{Error as ParseHostError, Host}, - receiver, + //receiver, request::{Error as ParseRequestError, Request}, response::{Error as ParseResponseError, Response}, - sender::{CertificateStore, Error as SenderError, Sender, Verifier}, + sender::{Error as SenderError, Sender, Verifier}, status::{ AuthenticationFailure, Error as ParseStatusError, PermanentFailure, Redirect, Status, TemporaryFailure, diff --git a/src/sender/mod.rs b/src/sender/mod.rs index 0fd84d9..69d9fbb 100644 --- a/src/sender/mod.rs +++ b/src/sender/mod.rs @@ -1,9 +1,8 @@ -pub use self::{ - error::Error, - verifier::{CertificateStore, Verifier}, +pub use self::{error::Error, verifier::Verifier}; +use { + crate::prelude::{CertificateStore, Request, Response}, + std::io::{Read, Write}, }; -use crate::{request::Request, response::Response}; -use std::io::{Read, Write}; mod error; mod verifier; diff --git a/src/sender/verifier.rs b/src/sender/verifier.rs index 4478f08..f1893b4 100644 --- a/src/sender/verifier.rs +++ b/src/sender/verifier.rs @@ -1,15 +1,15 @@ -use crate::fingerprint::Fingerprint; -use rustls::{ - client::{ServerCertVerified, ServerCertVerifier}, - Certificate, +use { + crate::prelude::{CertificateStore, Fingerprint}, + rustls::{ + client::{ServerCertVerified, ServerCertVerifier}, + Certificate, + }, + std::{ + borrow::BorrowMut, + sync::{Arc, Mutex}, + time, + }, }; -use std::sync::{Arc, Mutex}; - -/// An item which stores known certificates -pub trait CertificateStore: Send + Sync { - fn get(&self, host: &str) -> Option; - fn insert(&mut self, host: &str, fingerprint: &str); -} #[derive(Debug)] /// A verifier is used to verify certificates sent by the receiving server @@ -27,7 +27,7 @@ impl ServerCertVerifier for Verifier { server_name: &rustls::ServerName, _scts: &mut dyn Iterator, _ocsp_response: &[u8], - _now: std::time::SystemTime, + _now: time::SystemTime, ) -> Result { let fp = end_entity .fingerprint() @@ -37,18 +37,16 @@ impl ServerCertVerifier for Verifier { rustls::ServerName::IpAddress(ip) => ip.to_string(), _ => todo!(), }; - if let Some(fingerprint) = match server_name { - rustls::ServerName::DnsName(n) => self.store.lock().unwrap().get(n.as_ref()), - rustls::ServerName::IpAddress(ip) => self.store.lock().unwrap().get(&ip.to_string()), - _ => todo!(), - } { + let mut store = self.store.lock().unwrap(); + if let Some(fingerprint) = store.get_certificate(&name) { + // TODO: needs a lot more checking for certificate validity if fingerprint == fp.1 && name == fp.0 { return Ok(ServerCertVerified::assertion()); } } else { - // todo: need a way to update `self.store`. Probably will require - // an Arc> for interior mutability. - // UPDATE: Now wrapped in Arc> + if !store.contains_certificate(&name) { + let _key = store.borrow_mut().insert_certificate(&name, &fp.1); + } } Err(rustls::Error::General( "Unrecognized certificate".to_string(), @@ -56,10 +54,16 @@ impl ServerCertVerifier for Verifier { } } -impl Verifier { - pub fn new(store: T) -> Self { +impl From for Verifier { + fn from(value: T) -> Self { Self { - store: Arc::new(Mutex::new(store)), + store: Arc::new(Mutex::new(value)), } } } + +impl Verifier { + pub fn new(store: T) -> Self { + store.into() + } +} From 2b7119610c53e60dca357646702617aa486de6ab Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Thu, 25 May 2023 10:22:26 -0400 Subject: [PATCH 09/10] Add handling of certs with multiple common names; Check certs for validity against current date; --- src/fingerprint/error.rs | 4 +++- src/fingerprint/mod.rs | 30 +++++++++++++++++++++++------- src/prelude.rs | 2 +- src/sender/verifier.rs | 19 ++++++++++++------- 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/src/fingerprint/error.rs b/src/fingerprint/error.rs index 2b08cd9..8149a75 100644 --- a/src/fingerprint/error.rs +++ b/src/fingerprint/error.rs @@ -4,6 +4,7 @@ use {std::fmt, x509_parser::prelude::X509Error}; /// Errors which can occur when fingerprinting a certificate pub enum Error { Fmt, + InvalidForDate, X509(X509Error), } @@ -11,6 +12,7 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Fmt => write!(f, "FingerPrint: format error"), + Self::InvalidForDate => write!(f, "FingerPrint: invalid for date"), Self::X509(e) => write!(f, "FingerPrint: {e}"), } } @@ -19,8 +21,8 @@ impl fmt::Display for Error { impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { - Self::Fmt => None, Self::X509(e) => Some(e), + _ => None, } } } diff --git a/src/fingerprint/mod.rs b/src/fingerprint/mod.rs index d25c7c8..7fc4fe4 100644 --- a/src/fingerprint/mod.rs +++ b/src/fingerprint/mod.rs @@ -1,21 +1,37 @@ -use {digest::Digest, rustls::Certificate, sha2::Sha256, std::fmt::Write, x509_parser::prelude::*}; +use {digest::Digest, rustls::Certificate, sha2::Sha256, std::{io::Read, fmt::Write}, x509_parser::prelude::*}; mod error; pub use error::Error; /// Creates an sha256 fingerprint for a certificate -pub trait Fingerprint { +pub trait GetFingerprint { type Error; - fn fingerprint(&self) -> Result<(String, String), Self::Error>; + fn fingerprint(&self) -> Result; } -impl Fingerprint for Certificate { +pub struct Fingerprint { + pub names: Vec, + pub fingerprint: String, +} + +impl GetFingerprint for Certificate { type Error = Error; - fn fingerprint(&self) -> Result<(String, String), Self::Error> { + fn fingerprint(&self) -> Result { let (_, pk) = X509Certificate::from_der(self.as_ref())?; - let subject = pk.subject().to_string(); + let subject = pk.subject(); + let mut names = vec![]; + subject.iter_common_name().for_each(|n| { + let mut val = n.attr_value().data; + let mut name = String::new(); + if let Ok(_) = val.read_to_string(&mut name) { + names.push(name); + } + }); + if !pk.validity().is_valid() { + return Err(Error::InvalidForDate); + } let key = pk.public_key().subject_public_key.as_ref(); let mut hasher = Sha256::new(); hasher.update(key); @@ -24,6 +40,6 @@ impl Fingerprint for Certificate { for c in res { write!(s, "{c:02x}")?; } - Ok((subject[3..].to_string(), s)) + Ok(Fingerprint { names, fingerprint: s }) } } diff --git a/src/prelude.rs b/src/prelude.rs index 3b93bf8..8f57e98 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,6 +1,6 @@ pub use super::{ certificate_store::CertificateStore, - fingerprint::{Error as FingerprintError, Fingerprint}, + fingerprint::{Error as FingerprintError, Fingerprint, GetFingerprint}, host::{Error as ParseHostError, Host}, //receiver, request::{Error as ParseRequestError, Request}, diff --git a/src/sender/verifier.rs b/src/sender/verifier.rs index f1893b4..a0bda39 100644 --- a/src/sender/verifier.rs +++ b/src/sender/verifier.rs @@ -1,5 +1,5 @@ use { - crate::prelude::{CertificateStore, Fingerprint}, + crate::prelude::{CertificateStore, Fingerprint, GetFingerprint}, rustls::{ client::{ServerCertVerified, ServerCertVerifier}, Certificate, @@ -40,17 +40,22 @@ impl ServerCertVerifier for Verifier { let mut store = self.store.lock().unwrap(); if let Some(fingerprint) = store.get_certificate(&name) { // TODO: needs a lot more checking for certificate validity - if fingerprint == fp.1 && name == fp.0 { - return Ok(ServerCertVerified::assertion()); + if fingerprint == fp.fingerprint { + for n in fp.names { + if n == name { + return Ok(ServerCertVerified::assertion()); + } + } + Err(rustls::Error::InvalidCertificate(rustls::CertificateError::NotValidForName)) + } else { + Err(rustls::Error::InvalidCertificate(rustls::CertificateError::NotValidForName)) } } else { if !store.contains_certificate(&name) { - let _key = store.borrow_mut().insert_certificate(&name, &fp.1); + let _key = store.borrow_mut().insert_certificate(&name, &fp.fingerprint); } + return Ok(ServerCertVerified::assertion()); } - Err(rustls::Error::General( - "Unrecognized certificate".to_string(), - )) } } From 102130eb75836306baf200bdcc0612fb0fbe7362 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Thu, 25 May 2023 10:24:32 -0400 Subject: [PATCH 10/10] Run `cargo fmt`; Remove an unused import (verifier.rs) --- src/fingerprint/mod.rs | 13 +++++++++++-- src/sender/verifier.rs | 14 ++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/fingerprint/mod.rs b/src/fingerprint/mod.rs index 7fc4fe4..14bd46f 100644 --- a/src/fingerprint/mod.rs +++ b/src/fingerprint/mod.rs @@ -1,4 +1,10 @@ -use {digest::Digest, rustls::Certificate, sha2::Sha256, std::{io::Read, fmt::Write}, x509_parser::prelude::*}; +use { + digest::Digest, + rustls::Certificate, + sha2::Sha256, + std::{fmt::Write, io::Read}, + x509_parser::prelude::*, +}; mod error; pub use error::Error; @@ -40,6 +46,9 @@ impl GetFingerprint for Certificate { for c in res { write!(s, "{c:02x}")?; } - Ok(Fingerprint { names, fingerprint: s }) + Ok(Fingerprint { + names, + fingerprint: s, + }) } } diff --git a/src/sender/verifier.rs b/src/sender/verifier.rs index a0bda39..7e9c041 100644 --- a/src/sender/verifier.rs +++ b/src/sender/verifier.rs @@ -1,5 +1,5 @@ use { - crate::prelude::{CertificateStore, Fingerprint, GetFingerprint}, + crate::prelude::{CertificateStore, GetFingerprint}, rustls::{ client::{ServerCertVerified, ServerCertVerifier}, Certificate, @@ -46,13 +46,19 @@ impl ServerCertVerifier for Verifier { return Ok(ServerCertVerified::assertion()); } } - Err(rustls::Error::InvalidCertificate(rustls::CertificateError::NotValidForName)) + Err(rustls::Error::InvalidCertificate( + rustls::CertificateError::NotValidForName, + )) } else { - Err(rustls::Error::InvalidCertificate(rustls::CertificateError::NotValidForName)) + Err(rustls::Error::InvalidCertificate( + rustls::CertificateError::NotValidForName, + )) } } else { if !store.contains_certificate(&name) { - let _key = store.borrow_mut().insert_certificate(&name, &fp.fingerprint); + let _key = store + .borrow_mut() + .insert_certificate(&name, &fp.fingerprint); } return Ok(ServerCertVerified::assertion()); }