feat/refactor: add DANE verification
should work, but most probably does not
This commit is contained in:
parent
05f22493be
commit
dc126d2e0f
3 changed files with 146 additions and 32 deletions
|
@ -1,4 +1,10 @@
|
||||||
use std::{borrow::Cow, io::Write, os::fd::AsFd, path::Path, sync::Mutex};
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
io::{BufWriter, Write},
|
||||||
|
os::fd::AsFd,
|
||||||
|
path::Path,
|
||||||
|
sync::Mutex,
|
||||||
|
};
|
||||||
|
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use tokio::io::AsyncBufReadExt;
|
use tokio::io::AsyncBufReadExt;
|
||||||
|
@ -12,6 +18,8 @@ use crate::{
|
||||||
pub struct FileBasedCertVerifier {
|
pub struct FileBasedCertVerifier {
|
||||||
fd: Mutex<std::os::fd::OwnedFd>,
|
fd: Mutex<std::os::fd::OwnedFd>,
|
||||||
map: DashMap<String, SelfsignedCert>,
|
map: DashMap<String, SelfsignedCert>,
|
||||||
|
#[cfg(feature = "hickory")]
|
||||||
|
dns: Option<crate::dns::DnsClient>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileBasedCertVerifier {
|
impl FileBasedCertVerifier {
|
||||||
|
@ -85,7 +93,103 @@ impl FileBasedCertVerifier {
|
||||||
.try_clone_to_owned()?,
|
.try_clone_to_owned()?,
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(FileBasedCertVerifier { fd, map })
|
Ok(FileBasedCertVerifier {
|
||||||
|
fd,
|
||||||
|
map,
|
||||||
|
#[cfg(feature = "hickory")]
|
||||||
|
dns: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileBasedCertVerifier {
|
||||||
|
pub fn add_trusted_cert(
|
||||||
|
&self,
|
||||||
|
host: &str,
|
||||||
|
hash: CertFingerprint,
|
||||||
|
) -> Result<(), std::io::Error> {
|
||||||
|
let fp = hash.base64();
|
||||||
|
let ft = hash.fingerprint_type_str();
|
||||||
|
// TODO: remove eprintln!()
|
||||||
|
eprintln!("Warning: adding {} cert with FP {}", &host, &fp);
|
||||||
|
|
||||||
|
self.map.insert(
|
||||||
|
host.to_owned(),
|
||||||
|
SelfsignedCert {
|
||||||
|
fingerprint: hash,
|
||||||
|
expires: 0, // TODO after implementing cert parsing in tokio-gemini
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// trick with cloning file descriptor
|
||||||
|
// because we are not allowed to mutate &self
|
||||||
|
let f = std::fs::File::from(self.fd.lock().unwrap().try_clone()?);
|
||||||
|
let mut bw = BufWriter::new(f);
|
||||||
|
bw.write_all(host.as_bytes())?;
|
||||||
|
bw.write_all(b" 0 ")?; // TODO after implementing `expires`
|
||||||
|
bw.write_all(ft.as_bytes())?;
|
||||||
|
bw.write_all(b" ")?;
|
||||||
|
bw.write_all(fp.as_bytes())?;
|
||||||
|
bw.write_all(b"\n")?;
|
||||||
|
bw.flush()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "hickory")]
|
||||||
|
pub fn dane(
|
||||||
|
&self,
|
||||||
|
cert: &CertificateDer,
|
||||||
|
host: &str,
|
||||||
|
port: u16,
|
||||||
|
) -> Result<CertFingerprint, LibError> {
|
||||||
|
let mut dns = if let Some(dns) = &self.dns {
|
||||||
|
dns.clone()
|
||||||
|
} else {
|
||||||
|
return Err(LibError::HostLookupError);
|
||||||
|
};
|
||||||
|
|
||||||
|
let rt = tokio::runtime::Handle::try_current()?;
|
||||||
|
|
||||||
|
let mut sha256: Option<CertFingerprint> = None;
|
||||||
|
let mut sha512: Option<CertFingerprint> = None;
|
||||||
|
|
||||||
|
for tlsa_fp in rt.block_on(dns.query_tlsa(host, port))? {
|
||||||
|
match tlsa_fp {
|
||||||
|
CertFingerprint::Sha256(_) => {
|
||||||
|
if sha256.is_none() {
|
||||||
|
sha256 = Some(CertFingerprint::new_sha256(cert));
|
||||||
|
}
|
||||||
|
let this_fp = sha256.as_ref().unwrap();
|
||||||
|
if this_fp == &tlsa_fp {
|
||||||
|
return Ok(sha256.unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CertFingerprint::Sha512(_) => {
|
||||||
|
if sha512.is_none() {
|
||||||
|
sha512 = Some(CertFingerprint::new_sha512(cert));
|
||||||
|
}
|
||||||
|
let this_fp = sha512.as_ref().unwrap();
|
||||||
|
if this_fp == &tlsa_fp {
|
||||||
|
return Ok(sha512.unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CertFingerprint::Raw(_) => {
|
||||||
|
let this_fp = CertFingerprint::new_raw(cert);
|
||||||
|
if this_fp == tlsa_fp {
|
||||||
|
return Ok(CertFingerprint::new_sha256(cert));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(sha256) = sha256 {
|
||||||
|
Ok(sha256)
|
||||||
|
} else if let Some(sha512) = sha512 {
|
||||||
|
Ok(sha512)
|
||||||
|
} else {
|
||||||
|
Ok(CertFingerprint::new_sha256(cert))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,49 +201,35 @@ impl SelfsignedCertVerifier for FileBasedCertVerifier {
|
||||||
_now: UnixTime,
|
_now: UnixTime,
|
||||||
) -> Result<bool, tokio_rustls::rustls::Error> {
|
) -> Result<bool, tokio_rustls::rustls::Error> {
|
||||||
//
|
//
|
||||||
// TODO: remove eprintln!()s and do overall code cleanup
|
// TODO: remove eprintln!()s
|
||||||
//
|
//
|
||||||
|
|
||||||
if let Some(known_cert) = self.map.get(host) {
|
if let Some(known_cert) = self.map.get(host) {
|
||||||
// if host is found in known_hosts, compare certs
|
// if host is found in known_hosts, compare certs
|
||||||
let this_fp = match known_cert.fingerprint {
|
let this_hash = match known_cert.fingerprint {
|
||||||
CertFingerprint::Sha256(_) => CertFingerprint::new_sha256(cert),
|
CertFingerprint::Sha256(_) => CertFingerprint::new_sha256(cert),
|
||||||
CertFingerprint::Sha512(_) => CertFingerprint::new_sha512(cert),
|
CertFingerprint::Sha512(_) => CertFingerprint::new_sha512(cert),
|
||||||
_ => unreachable!(),
|
CertFingerprint::Raw(_) => CertFingerprint::new_raw(cert),
|
||||||
};
|
};
|
||||||
Ok(this_fp == known_cert.fingerprint)
|
Ok(this_hash == known_cert.fingerprint)
|
||||||
} else {
|
} else {
|
||||||
// host is unknown, generate hash and add to known_hosts
|
// host is unknown, generate hash and add to known_hosts
|
||||||
|
#[cfg(feature = "hickory")]
|
||||||
|
let this_hash = match self.dane(cert, host, 1965) {
|
||||||
|
Ok(hash) => hash,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("DANE verification failed: {:?}", e);
|
||||||
|
CertFingerprint::new_sha256(cert)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "hickory"))]
|
||||||
let this_hash = CertFingerprint::new_sha256(cert);
|
let this_hash = CertFingerprint::new_sha256(cert);
|
||||||
let this_fp = this_hash.base64();
|
|
||||||
// TODO: DANE cert check, use this_hash.hex() for this
|
|
||||||
eprintln!(
|
|
||||||
"Warning: updating known_hosts with cert {} for {}",
|
|
||||||
&this_fp, &host,
|
|
||||||
);
|
|
||||||
|
|
||||||
(|| {
|
self.add_trusted_cert(host, this_hash).unwrap_or_else(|e| {
|
||||||
// trick with cloning file descriptor
|
eprintln!("Unable to add new cert: {:?}", e);
|
||||||
// because we are not allowed to mutate &self
|
|
||||||
let mut f = std::fs::File::from(self.fd.lock().unwrap().try_clone()?);
|
|
||||||
f.write_all(host.as_bytes())?;
|
|
||||||
f.write_all(b" 0 sha256 ")?; // TODO after implementing `expires`
|
|
||||||
f.write_all(this_fp.as_bytes())?;
|
|
||||||
f.write_all(b"\n")?;
|
|
||||||
Ok::<(), std::io::Error>(())
|
|
||||||
})()
|
|
||||||
.unwrap_or_else(|e| {
|
|
||||||
eprintln!("Could not add cert to file: {:?}", e);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
self.map.insert(
|
|
||||||
host.to_owned(),
|
|
||||||
SelfsignedCert {
|
|
||||||
fingerprint: this_hash,
|
|
||||||
expires: 0, // TODO after implementing cert parsing in tokio-gemini
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,3 +125,13 @@ impl CertFingerprint {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CertFingerprint {
|
||||||
|
pub fn fingerprint_type_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Sha256(_) => "sha256",
|
||||||
|
Self::Sha512(_) => "sha512",
|
||||||
|
Self::Raw(_) => "raw",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
14
src/error.rs
14
src/error.rs
|
@ -4,6 +4,8 @@
|
||||||
use hickory_client::{
|
use hickory_client::{
|
||||||
error::ClientError as HickoryClientError, proto::error::ProtoError as HickoryProtoError,
|
error::ClientError as HickoryClientError, proto::error::ProtoError as HickoryProtoError,
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "hickory")]
|
||||||
|
use tokio::runtime::TryCurrentError;
|
||||||
|
|
||||||
/// Main error structure, also a wrapper for everything else
|
/// Main error structure, also a wrapper for everything else
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -29,6 +31,10 @@ pub enum LibError {
|
||||||
/// Hickory Proto error
|
/// Hickory Proto error
|
||||||
#[cfg(feature = "hickory")]
|
#[cfg(feature = "hickory")]
|
||||||
DnsProtoError(HickoryProtoError),
|
DnsProtoError(HickoryProtoError),
|
||||||
|
/// Could not get Tokio runtime handle
|
||||||
|
/// inside Rustls cert verifier
|
||||||
|
#[cfg(feature = "hickory")]
|
||||||
|
NoTokioRuntime(TryCurrentError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<std::io::Error> for LibError {
|
impl From<std::io::Error> for LibError {
|
||||||
|
@ -89,6 +95,14 @@ impl From<HickoryProtoError> for LibError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "hickory")]
|
||||||
|
impl From<TryCurrentError> for LibError {
|
||||||
|
#[inline]
|
||||||
|
fn from(err: TryCurrentError) -> Self {
|
||||||
|
Self::NoTokioRuntime(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// URL parse or check error
|
/// URL parse or check error
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum InvalidUrl {
|
pub enum InvalidUrl {
|
||||||
|
|
Loading…
Reference in a new issue