Add Store
, Verifier
and Fingerprint
types for handling
certificates
This commit is contained in:
parent
8d6c21325f
commit
7bbdb1381d
7 changed files with 125 additions and 2 deletions
11
Cargo.toml
11
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" ]
|
||||
|
||||
|
|
3
src/client.rs
Normal file
3
src/client.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
pub mod store;
|
||||
pub mod verifier;
|
||||
|
5
src/client/store.rs
Normal file
5
src/client/store.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
pub trait CertificateStore {
|
||||
fn get(&self, host: &str) -> Option<String>;
|
||||
fn insert(&mut self, host: &str, fingerprint: &str);
|
||||
}
|
||||
|
39
src/client/verifier.rs
Normal file
39
src/client/verifier.rs
Normal file
|
@ -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<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()));
|
||||
}
|
||||
}
|
66
src/fingerprint.rs
Normal file
66
src/fingerprint.rs
Normal file
|
@ -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<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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
0
src/server.rs
Normal file
0
src/server.rs
Normal file
Loading…
Add table
Reference in a new issue