diff --git a/Cargo.toml b/Cargo.toml index 7f6fbed..46e28e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,14 @@ name = "dory" version = "0.1.0" edition = "2021" +[dependencies] +digest = "0.10.7" +sha2 = "0.10.6" +x509-parser = "0.15.0" + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[dependencies] -rustls = "0.21.1" +[dependencies.rustls] +version = "0.21.1" +features = [ "dangerous_configuration" ] + diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 0000000..cf78c92 --- /dev/null +++ b/src/client.rs @@ -0,0 +1,3 @@ +pub mod store; +pub mod verifier; + diff --git a/src/client/store.rs b/src/client/store.rs new file mode 100644 index 0000000..8b55a63 --- /dev/null +++ b/src/client/store.rs @@ -0,0 +1,5 @@ +pub trait CertificateStore { + fn get(&self, host: &str) -> Option; + fn insert(&mut self, host: &str, fingerprint: &str); +} + diff --git a/src/client/verifier.rs b/src/client/verifier.rs new file mode 100644 index 0000000..d341622 --- /dev/null +++ b/src/client/verifier.rs @@ -0,0 +1,39 @@ +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, + _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!() + }; + 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> for interior mutability + } + return Err(rustls::Error::General("Unrecognized certificate".to_string())); + } +} diff --git a/src/fingerprint.rs b/src/fingerprint.rs new file mode 100644 index 0000000..b5e6dce --- /dev/null +++ b/src/fingerprint.rs @@ -0,0 +1,66 @@ +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 for Error { + fn from(_value: fmt::Error) -> Self { + Self::Fmt + } +} + +impl From> for Error { + fn from(value: x509_parser::nom::Err) -> 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)) + } +} + diff --git a/src/lib.rs b/src/lib.rs index 54d0cf0..cc24ec2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,8 @@ #![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; diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..e69de29