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"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
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
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies.rustls]
|
||||||
rustls = "0.21.1"
|
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)]
|
#![warn(clippy::all, clippy::pedantic)]
|
||||||
|
pub mod client;
|
||||||
|
pub mod fingerprint;
|
||||||
pub mod host;
|
pub mod host;
|
||||||
pub mod request;
|
pub mod request;
|
||||||
pub mod response;
|
pub mod response;
|
||||||
|
pub mod server;
|
||||||
pub mod status;
|
pub mod status;
|
||||||
|
|
0
src/server.rs
Normal file
0
src/server.rs
Normal file
Loading…
Add table
Reference in a new issue