Merge branch 'odin' of git.hitchhiker-linux.org:jeang3nie/dory into odin

This commit is contained in:
Nathan Fisher 2023-05-25 18:52:08 -04:00
commit c57bb83c5c
25 changed files with 626 additions and 337 deletions

2
.gitignore vendored
View file

@ -2,3 +2,5 @@
/Cargo.lock
tags
tags.lock
tags.temp

View file

@ -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

36
src/certificate_store.rs Normal file
View file

@ -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<String>;
fn insert_certificate(&mut self, host: &str, fingerprint: &str) -> Option<String>;
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,3 +0,0 @@
pub mod store;
pub mod verifier;

View file

@ -1,5 +0,0 @@
pub trait CertificateStore {
fn get(&self, host: &str) -> Option<String>;
fn insert(&mut self, host: &str, fingerprint: &str);
}

View file

@ -1,39 +0,0 @@
use crate::fingerprint::Fingerprint;
use rustls::{client::{ServerCertVerified, ServerCertVerifier}, Certificate};
use super::store::CertificateStore;
pub struct Verifier<'a, T: CertificateStore> {
store: &'a T,
}
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<Item = &[u8]>,
_ocsp_response: &[u8],
_now: std::time::SystemTime,
) -> Result<ServerCertVerified, rustls::Error> {
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!()
};
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()),
_ => todo!(),
} {
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<Mutex<T>> for interior mutability
}
return Err(rustls::Error::General("Unrecognized certificate".to_string()));
}
}

View file

@ -1,66 +0,0 @@
use digest::Digest;
use rustls::Certificate;
use sha2::Sha256;
use std::fmt::{Write, self};
use x509_parser::prelude::*;
pub trait Fingerprint {
type Error;
fn fingerprint(&self) -> Result<(String, String), Self::Error>;
}
#[derive(Clone, Debug, PartialEq)]
pub enum Error {
Fmt,
X509(X509Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Fmt => write!(f, "FingerPrint: format error"),
Self::X509(e) => write!(f, "FingerPrint: {e}"),
}
}
}
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),
}
}
}
impl From<fmt::Error> for Error {
fn from(_value: fmt::Error) -> Self {
Self::Fmt
}
}
impl From<x509_parser::nom::Err<x509_parser::error::X509Error>> for Error {
fn from(value: x509_parser::nom::Err<x509_parser::error::X509Error>) -> Self {
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))
}
}

40
src/fingerprint/error.rs Normal file
View file

@ -0,0 +1,40 @@
use {std::fmt, x509_parser::prelude::X509Error};
#[derive(Debug)]
/// Errors which can occur when fingerprinting a certificate
pub enum Error {
Fmt,
InvalidForDate,
X509(X509Error),
}
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}"),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::X509(e) => Some(e),
_ => None,
}
}
}
impl From<fmt::Error> for Error {
fn from(_value: fmt::Error) -> Self {
Self::Fmt
}
}
impl From<x509_parser::nom::Err<x509_parser::error::X509Error>> for Error {
fn from(value: x509_parser::nom::Err<x509_parser::error::X509Error>) -> Self {
Self::X509(value.into())
}
}

54
src/fingerprint/mod.rs Normal file
View file

@ -0,0 +1,54 @@
use {
digest::Digest,
rustls::Certificate,
sha2::Sha256,
std::{fmt::Write, io::Read},
x509_parser::prelude::*,
};
mod error;
pub use error::Error;
/// Creates an sha256 fingerprint for a certificate
pub trait GetFingerprint {
type Error;
fn fingerprint(&self) -> Result<Fingerprint, Self::Error>;
}
pub struct Fingerprint {
pub names: Vec<String>,
pub fingerprint: String,
}
impl GetFingerprint for Certificate {
type Error = Error;
fn fingerprint(&self) -> Result<Fingerprint, Self::Error> {
let (_, pk) = X509Certificate::from_der(self.as_ref())?;
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);
let res = hasher.finalize();
let mut s = String::with_capacity(res.len());
for c in res {
write!(s, "{c:02x}")?;
}
Ok(Fingerprint {
names,
fingerprint: s,
})
}
}

19
src/host/error.rs Normal file
View file

@ -0,0 +1,19 @@
use std::fmt;
#[derive(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 {}

View file

@ -12,8 +12,15 @@
//! assert_eq!(host.tld.as_str(), "com");
//! ```
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<String>,
pub domain: String,
@ -30,40 +37,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<Self, Self::Err> {
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()),
@ -79,7 +69,7 @@ impl FromStr for Host {
})
}
} else {
Err(ParseHostError::MissingSeparator)
Err(Error::MissingSeparator)
}
}
}
@ -92,37 +82,31 @@ mod tests {
fn parse_missing_separator() {
assert_eq!(
"exampledotcom".parse::<Host>(),
Err(ParseHostError::MissingSeparator)
Err(Error::MissingSeparator)
);
}
#[test]
fn parse_empty_tld() {
assert_eq!("example.".parse::<Host>(), Err(ParseHostError::EmptyTld));
assert_eq!("example.".parse::<Host>(), Err(Error::EmptyTld));
}
#[test]
fn parse_empty_domain() {
assert_eq!(".com".parse::<Host>(), Err(ParseHostError::EmptyDomain));
assert_eq!(
"example..com".parse::<Host>(),
Err(ParseHostError::EmptyDomain)
);
assert_eq!(".com".parse::<Host>(), Err(Error::EmptyDomain));
assert_eq!("example..com".parse::<Host>(), Err(Error::EmptyDomain));
}
#[test]
fn parse_empty_subdomain() {
assert_eq!(
".example.com".parse::<Host>(),
Err(ParseHostError::EmptySubdomain)
);
assert_eq!(".example.com".parse::<Host>(), Err(Error::EmptySubdomain));
}
#[test]
fn parse_illegal_whitespace() {
assert_eq!(
"exam\tple.com".parse::<Host>(),
Err(ParseHostError::IllegalWhitespace)
Err(Error::IllegalWhitespace)
);
}

View file

@ -1,8 +1,10 @@
#![warn(clippy::all, clippy::pedantic)]
pub mod client;
pub mod fingerprint;
pub mod host;
pub mod request;
pub mod response;
pub mod server;
pub mod status;
mod certificate_store;
mod fingerprint;
mod host;
pub mod prelude;
mod receiver;
mod request;
mod response;
mod sender;
mod status;

13
src/prelude.rs Normal file
View file

@ -0,0 +1,13 @@
pub use super::{
certificate_store::CertificateStore,
fingerprint::{Error as FingerprintError, Fingerprint, GetFingerprint},
host::{Error as ParseHostError, Host},
//receiver,
request::{Error as ParseRequestError, Request},
response::{Error as ParseResponseError, Response},
sender::{Error as SenderError, Sender, Verifier},
status::{
AuthenticationFailure, Error as ParseStatusError, PermanentFailure, Redirect, Status,
TemporaryFailure,
},
};

1
src/receiver/mod.rs Normal file
View file

@ -0,0 +1 @@

36
src/request/error.rs Normal file
View file

@ -0,0 +1,36 @@
use {crate::prelude::ParseHostError, std::fmt};
#[derive(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<ParseHostError> for Error {
fn from(value: ParseHostError) -> Self {
Self::ParseHostError(value)
}
}

View file

@ -1,11 +1,22 @@
use crate::host::{Host, ParseHostError};
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 {
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 +29,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<ParseHostError> 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<Self, Self::Err> {
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 +56,10 @@ impl FromStr for Request {
message,
})
} else {
Err(ParseRequestError::MissingSeparator)
Err(Error::MissingSeparator)
}
} else {
Err(ParseRequestError::MissingSeparator)
Err(Error::MissingSeparator)
}
}
}
@ -90,6 +67,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 +96,33 @@ mod tests {
#[test]
fn parse_missing_sep() {
let req = "misfin://john@example.comHelloWorld!\r\n".parse::<Request>();
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::<Request>();
assert_eq!(req, Err(ParseRequestError::Malformed));
assert_eq!(req, Err(Error::Malformed));
let req = "mail://john@example.com Hello World!\r\n".parse::<Request>();
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::<Request>();
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::<Request>();
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::<Request>();
assert_eq!(req, Err(ParseRequestError::EmptyMessage));
assert_eq!(req, Err(Error::EmptyMessage));
}
#[test]
@ -152,9 +130,7 @@ mod tests {
let req = "misfin://john@example\tfairy.com Hello World!\r\n".parse::<Request>();
assert_eq!(
req,
Err(ParseRequestError::ParseHostError(
ParseHostError::IllegalWhitespace
))
Err(Error::ParseHostError(ParseHostError::IllegalWhitespace))
);
}
}

View file

@ -1,104 +0,0 @@
use std::{fmt, num::ParseIntError, str::FromStr};
use crate::status::{ParseStatusError, Status};
#[derive(Clone, Debug, PartialEq)]
pub struct Response {
pub status: Status,
pub meta: String,
}
impl fmt::Display for Response {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {}\r\n", u8::from(self.status.clone()), self.meta)
}
}
#[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<ParseIntError> for ParseResponseError {
fn from(value: ParseIntError) -> Self {
Self::ParseInt(value)
}
}
impl From<ParseStatusError> for ParseResponseError {
fn from(_value: ParseStatusError) -> Self {
Self::StatusError
}
}
impl FromStr for Response {
type Err = ParseResponseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() > 2048 {
return Err(ParseResponseError::TooLong);
}
if !s.ends_with("\r\n") {
return Err(ParseResponseError::Malformed);
}
let Some((status, meta)) = s.split_once(' ') else {
return Err(ParseResponseError::Malformed);
};
let status: u8 = status.parse()?;
let status: Status = status.try_into()?;
Ok(Self {
status,
meta: meta.trim_end().to_string(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_success() {
let response: Response = "20 message delivered\r\n".parse().unwrap();
assert_eq!(response.status, Status::Success);
assert_eq!(response.meta.as_str(), "message delivered");
}
#[test]
fn parse_badend() {
let response = "20 message delivered\n".parse::<Response>();
assert_eq!(response, Err(ParseResponseError::Malformed));
}
#[test]
fn parse_badint() {
let response = "twenty message deliverred\r\n".parse::<Response>();
match response {
Err(ParseResponseError::ParseInt(_)) => {}
_ => panic!(),
}
}
}

51
src/response/error.rs Normal file
View file

@ -0,0 +1,51 @@
use {
crate::prelude::ParseStatusError,
std::{fmt, num::ParseIntError},
};
#[derive(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,
}
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<ParseIntError> for Error {
fn from(value: ParseIntError) -> Self {
Self::ParseInt(value)
}
}
impl From<ParseStatusError> for Error {
fn from(_value: ParseStatusError) -> Self {
Self::StatusError
}
}

71
src/response/mod.rs Normal file
View file

@ -0,0 +1,71 @@
use crate::prelude::Status;
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,
pub meta: String,
}
impl fmt::Display for Response {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {}\r\n", u8::from(self.status.clone()), self.meta)
}
}
impl FromStr for Response {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() > 2048 {
return Err(Error::TooLong);
}
if !s.ends_with("\r\n") {
return Err(Error::Malformed);
}
let Some((status, meta)) = s.split_once(' ') else {
return Err(Error::Malformed);
};
let status: u8 = status.parse()?;
let status: Status = status.try_into()?;
Ok(Self {
status,
meta: meta.trim_end().to_string(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_success() {
let response: Response = "20 message delivered\r\n".parse().unwrap();
assert_eq!(response.status, Status::Success);
assert_eq!(response.meta.as_str(), "message delivered");
}
#[test]
fn parse_badend() {
let response = "20 message delivered\n".parse::<Response>();
assert_eq!(response, Err(Error::Malformed));
}
#[test]
fn parse_badint() {
let response = "twenty message deliverred\r\n".parse::<Response>();
match response {
Err(Error::ParseInt(_)) => {}
_ => panic!(),
}
}
}

59
src/sender/error.rs Normal file
View file

@ -0,0 +1,59 @@
use {
crate::prelude::{ParseRequestError, ParseResponseError},
std::{fmt, io},
};
#[derive(Debug)]
/// Errors which might occur when sending a message
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<rustls::Error> for Error {
fn from(value: rustls::Error) -> Self {
Self::TlsError(value)
}
}
impl From<ParseRequestError> for Error {
fn from(value: ParseRequestError) -> Self {
Self::RequestError(value)
}
}
impl From<ParseResponseError> for Error {
fn from(value: ParseResponseError) -> Self {
Self::ResponseError(value)
}
}
impl From<io::Error> for Error {
fn from(value: io::Error) -> Self {
Self::IoError(value)
}
}

41
src/sender/mod.rs Normal file
View file

@ -0,0 +1,41 @@
pub use self::{error::Error, verifier::Verifier};
use {
crate::prelude::{CertificateStore, Request, Response},
std::io::{Read, Write},
};
mod error;
mod verifier;
#[derive(Debug)]
/// Sends a piece of mail from the sending server to the receiving server
pub struct Sender<S, C, T>
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<S>,
/// The TLS stream used for the connection
pub stream: rustls::StreamOwned<C, T>,
}
impl<S, C, T> Sender<S, C, T>
where
S: CertificateStore,
C: Sized,
T: Read + Write + Sized,
{
pub fn new(request_str: &str, store: S) -> Result<Self, Error> {
let request: Request = request_str.parse()?;
let verifier = Verifier::new(store);
unimplemented!();
}
pub fn send(&mut self) -> Result<Response, Error> {
unimplemented!();
}
}

80
src/sender/verifier.rs Normal file
View file

@ -0,0 +1,80 @@
use {
crate::prelude::{CertificateStore, GetFingerprint},
rustls::{
client::{ServerCertVerified, ServerCertVerifier},
Certificate,
},
std::{
borrow::BorrowMut,
sync::{Arc, Mutex},
time,
},
};
#[derive(Debug)]
/// A verifier is used to verify certificates sent by the receiving server
/// during the tls handshake.
pub struct Verifier<S: CertificateStore> {
/// An item which serves as storage for certificates
pub store: Arc<Mutex<S>>,
}
impl<S: CertificateStore> ServerCertVerifier for Verifier<S> {
fn verify_server_cert(
&self,
end_entity: &Certificate,
_intermediates: &[Certificate],
server_name: &rustls::ServerName,
_scts: &mut dyn Iterator<Item = &[u8]>,
_ocsp_response: &[u8],
_now: time::SystemTime,
) -> Result<ServerCertVerified, rustls::Error> {
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!(),
};
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.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.fingerprint);
}
return Ok(ServerCertVerified::assertion());
}
}
}
impl<T: CertificateStore> From<T> for Verifier<T> {
fn from(value: T) -> Self {
Self {
store: Arc::new(Mutex::new(value)),
}
}
}
impl<T: CertificateStore> Verifier<T> {
pub fn new(store: T) -> Self {
store.into()
}
}

View file

13
src/status/error.rs Normal file
View file

@ -0,0 +1,13 @@
use std::fmt;
#[derive(Debug)]
/// The receiving server sent an unrecognized or invalid status code
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 {}

View file

@ -1,18 +1,14 @@
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;
#[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.
pub enum Status {
/// These codes are reserved, and must not be sent by a Misfin server.
Input = 10,
@ -46,7 +42,7 @@ impl From<Status> for u8 {
}
impl TryFrom<u8> for Status {
type Error = ParseStatusError;
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value / 10 {
@ -56,12 +52,14 @@ impl TryFrom<u8> 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)]
#[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
/// should be resent to that address.
@ -73,19 +71,22 @@ pub enum Redirect {
}
impl TryFrom<u8> for Redirect {
type Error = ParseStatusError;
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
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)]
#[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 {
/// The mailserver experienced a transient issue, and the message
/// should be resent.
@ -105,7 +106,7 @@ pub enum TemporaryFailure {
}
impl TryFrom<u8> for TemporaryFailure {
type Error = ParseStatusError;
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
@ -116,23 +117,33 @@ impl TryFrom<u8> 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)]
#[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 {
/// 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,
}
impl TryFrom<u8> for PermanentFailure {
type Error = ParseStatusError;
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
@ -142,23 +153,35 @@ impl TryFrom<u8> 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)]
#[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
/// 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,
}
impl TryFrom<u8> for AuthenticationFailure {
type Error = ParseStatusError;
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
@ -168,7 +191,7 @@ impl TryFrom<u8> for AuthenticationFailure {
3 => Ok(Self::IdentityMismatch),
4 => Ok(Self::ProofRequired),
n if n < 10 => Ok(Self::Other),
_ => Err(ParseStatusError),
_ => Err(Error),
}
}
}