refactor/feat: rewrite certs::fingerprint with generic data type…

…and allowing different bin2txt (getting ready for DANE)
This commit is contained in:
DarkCat09 2024-08-08 20:55:39 +04:00
parent 6f91e705d3
commit 1fc73d0cab
Signed by: DarkCat09
GPG key ID: 0A26CD5B3345D6E3
6 changed files with 73 additions and 52 deletions

7
Cargo.lock generated
View file

@ -38,6 +38,12 @@ dependencies = [
"rustc-demangle", "rustc-demangle",
] ]
[[package]]
name = "base16ct"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
[[package]] [[package]]
name = "base64ct" name = "base64ct"
version = "1.6.0" version = "1.6.0"
@ -489,6 +495,7 @@ dependencies = [
name = "tokio-gemini" name = "tokio-gemini"
version = "0.4.0" version = "0.4.0"
dependencies = [ dependencies = [
"base16ct",
"base64ct", "base64ct",
"bytes", "bytes",
"dashmap", "dashmap",

View file

@ -10,6 +10,7 @@ description = "Gemini protocol client and server implementation written in Rust
categories = ["network-programming"] categories = ["network-programming"]
[dependencies] [dependencies]
base16ct = "0.2.0"
base64ct = "1.6.0" base64ct = "1.6.0"
bytes = "1.7.1" bytes = "1.7.1"
dashmap = { version = "6.0.1", optional = true } dashmap = { version = "6.0.1", optional = true }

View file

@ -1,6 +1,6 @@
use tokio_gemini::{ use tokio_gemini::{
certs::{ certs::{
fingerprint::{generate_fingerprint, Algorithm}, fingerprint::{self, CertFingerprint},
SelfsignedCertVerifier, SelfsignedCertVerifier,
}, },
Client, LibError, Client, LibError,
@ -47,7 +47,7 @@ impl SelfsignedCertVerifier for CertVerifier {
eprintln!( eprintln!(
"Host = {}\nFingerprint = {}", "Host = {}\nFingerprint = {}",
host, host,
generate_fingerprint(cert, Algorithm::Sha512), CertFingerprint::<fingerprint::Sha256>::new(cert).base64(),
); );
Ok(true) Ok(true)
} }

View file

@ -1,4 +1,4 @@
use std::{io::Write, os::fd::AsFd, sync::Mutex}; use std::{borrow::Cow, io::Write, os::fd::AsFd, sync::Mutex};
use dashmap::DashMap; use dashmap::DashMap;
use tokio::io::AsyncBufReadExt; use tokio::io::AsyncBufReadExt;
@ -6,7 +6,7 @@ use tokio_rustls::rustls::pki_types::{CertificateDer, UnixTime};
use crate::{ use crate::{
certs::{ certs::{
fingerprint::{generate_fingerprint, Algorithm}, fingerprint::{self, CertFingerprint, HashAlgo},
SelfsignedCert, SelfsignedCertVerifier, SelfsignedCert, SelfsignedCertVerifier,
}, },
LibError, LibError,
@ -38,11 +38,8 @@ impl FileBasedCertVerifier {
// host <space> expires <space> hash-algo <space> fingerprint // host <space> expires <space> hash-algo <space> fingerprint
// Example: // Example:
// dc09.ru 1722930541 sha512 dGVzdHRlc3R0ZXN0Cg // dc09.ru 1722930541 sha512 dGVzdHRlc3R0ZXN0Cg
if let [host, expires, algo, fp] = buf if let [host, expires, algo, fp] =
.split_whitespace() *buf.split_whitespace().take(4).collect::<Cow<[&str]>>()
.take(4)
.collect::<Vec<&str>>()
.as_slice()
{ {
let expires = if let Ok(num) = expires.parse::<u64>() { let expires = if let Ok(num) = expires.parse::<u64>() {
num num
@ -51,9 +48,9 @@ impl FileBasedCertVerifier {
continue; continue;
}; };
let algo = match *algo { let algo = match algo {
"sha256" => Algorithm::Sha256, "sha256" => HashAlgo::Sha256,
"sha512" => Algorithm::Sha512, "sha512" => HashAlgo::Sha512,
_ => { _ => {
eprintln!("Unknown hash algorithm {:?}, skipping", algo); eprintln!("Unknown hash algorithm {:?}, skipping", algo);
continue; continue;
@ -61,10 +58,10 @@ impl FileBasedCertVerifier {
}; };
map.insert( map.insert(
(*host).to_owned(), host.to_owned(),
SelfsignedCert { SelfsignedCert {
algo, algo,
fingerprint: (*fp).to_owned(), fingerprint: fp.to_owned(),
expires, expires,
}, },
); );
@ -101,7 +98,10 @@ impl SelfsignedCertVerifier for FileBasedCertVerifier {
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 = generate_fingerprint(cert, known_cert.algo); let this_fp = match known_cert.algo {
HashAlgo::Sha256 => CertFingerprint::<fingerprint::Sha256>::new(cert).base64(),
HashAlgo::Sha512 => CertFingerprint::<fingerprint::Sha512>::new(cert).base64(),
};
if this_fp == known_cert.fingerprint { if this_fp == known_cert.fingerprint {
// current cert hash matches known cert hash // current cert hash matches known cert hash
eprintln!("Cert for {} matched: {}", &host, &this_fp); eprintln!("Cert for {} matched: {}", &host, &this_fp);
@ -116,7 +116,9 @@ impl SelfsignedCertVerifier for FileBasedCertVerifier {
} }
} else { } else {
// host is unknown, generate hash and add to known_hosts // host is unknown, generate hash and add to known_hosts
let this_fp = generate_fingerprint(cert, Algorithm::Sha512); let this_hash = CertFingerprint::<fingerprint::Sha256>::new(cert);
let this_fp = this_hash.base64();
// TODO: DANE cert check, use this_hash.hex() for this
eprintln!( eprintln!(
"Warning: updating known_hosts with cert {} for {}", "Warning: updating known_hosts with cert {} for {}",
&this_fp, &host, &this_fp, &host,
@ -127,7 +129,7 @@ impl SelfsignedCertVerifier for FileBasedCertVerifier {
// because we are not allowed to mutate &self // because we are not allowed to mutate &self
let mut f = std::fs::File::from(self.fd.lock().unwrap().try_clone()?); let mut f = std::fs::File::from(self.fd.lock().unwrap().try_clone()?);
f.write_all(host.as_bytes())?; f.write_all(host.as_bytes())?;
f.write_all(b" 0 sha512 ")?; // TODO after implementing `expires` f.write_all(b" 0 sha256 ")?; // TODO after implementing `expires`
f.write_all(this_fp.as_bytes())?; f.write_all(this_fp.as_bytes())?;
f.write_all(b"\n")?; f.write_all(b"\n")?;
Ok::<(), std::io::Error>(()) Ok::<(), std::io::Error>(())
@ -139,7 +141,7 @@ impl SelfsignedCertVerifier for FileBasedCertVerifier {
self.map.insert( self.map.insert(
host.to_owned(), host.to_owned(),
SelfsignedCert { SelfsignedCert {
algo: Algorithm::Sha512, algo: HashAlgo::Sha256,
fingerprint: this_fp, fingerprint: this_fp,
expires: 0, // TODO after implementing cert parsing in tokio-gemini expires: 0, // TODO after implementing cert parsing in tokio-gemini
}, },

View file

@ -1,49 +1,60 @@
//! Helpers for TLS cert fingerprint generating //! TLS cert fingerprint generators
use base64ct::{Base64Unpadded, Encoding}; pub use sha2::{Digest, Sha256, Sha512};
use sha2::{Digest, Sha256, Sha512};
use base16ct::upper as b16;
use base64ct::{Base64Unpadded as b64, Encoding};
use super::verifier::CertificateDer; use super::verifier::CertificateDer;
pub const SHA256_HEX_LEN: usize = 64_; // (256 / 8) * 2
pub const SHA512_HEX_LEN: usize = 128; // (512 / 8) * 2
pub const SHA256_B64_LEN: usize = 44; // 4 * ((256 / 8) as f64 / 3 as f64).ceil() pub const SHA256_B64_LEN: usize = 44; // 4 * ((256 / 8) as f64 / 3 as f64).ceil()
pub const SHA512_B64_LEN: usize = 88; // 4 * ((512 / 8) as f64 / 3 as f64).ceil() pub const SHA512_B64_LEN: usize = 88; // 4 * ((512 / 8) as f64 / 3 as f64).ceil()
/// Certificate hashing algorithms
/// supported in this library
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum Algorithm { pub enum HashAlgo {
Sha256, Sha256,
Sha512, Sha512,
} }
/// Generate a fingerprint for the provided certificate pub struct CertFingerprint<T: Digest> {
/// using the specified algorithm + base64 hash: sha2::digest::Output<T>,
pub fn generate_fingerprint(cert: &CertificateDer, algo: Algorithm) -> String { }
match algo {
Algorithm::Sha256 => { impl<T: Digest> CertFingerprint<T> {
let mut hasher = Sha256::new(); pub fn new(cert: &CertificateDer) -> Self {
let mut hasher = T::new();
for chunk in cert.chunks(128) { for chunk in cert.chunks(128) {
hasher.update(chunk); hasher.update(chunk);
} }
let bin = hasher.finalize(); CertFingerprint {
let mut buf = [0; SHA256_B64_LEN]; hash: hasher.finalize(),
// Note on unwrap:
// Encoder returns error only if buffer length is insufficient.
// SHA-256 is *always* 256 bits (32 bytes),
// after we apply base64 formula we get 44 bytes in output including padding.
// See also comment near const SHA256_B64_LEN
Base64Unpadded::encode(&bin, &mut buf).unwrap().to_owned()
}
Algorithm::Sha512 => {
let mut hasher = Sha512::new();
for chunk in cert.chunks(128) {
hasher.update(chunk);
}
let bin = hasher.finalize();
let mut buf = [0; SHA512_B64_LEN];
// Same explanation for unwrap, see above
// SHA-512 is always 512 bits or 64 bytes
Base64Unpadded::encode(&bin, &mut buf).unwrap().to_owned()
} }
} }
} }
impl CertFingerprint<Sha256> {
pub fn hex(&self) -> String {
let mut buf = [0u8; SHA256_HEX_LEN];
b16::encode_str(&self.hash, &mut buf).unwrap().to_owned()
}
pub fn base64(&self) -> String {
let mut buf = [0u8; SHA256_B64_LEN];
b64::encode(&self.hash, &mut buf).unwrap().to_owned()
}
}
impl CertFingerprint<Sha512> {
pub fn hex(&self) -> String {
let mut buf = [0u8; SHA512_HEX_LEN];
b16::encode_str(&self.hash, &mut buf).unwrap().to_owned()
}
pub fn base64(&self) -> String {
let mut buf = [0u8; SHA512_B64_LEN];
b64::encode(&self.hash, &mut buf).unwrap().to_owned()
}
}

View file

@ -28,7 +28,7 @@ pub trait SelfsignedCertVerifier: Send + Sync {
/// suggested for using in a [`SelfsignedCertVerifier`] cert storage /// suggested for using in a [`SelfsignedCertVerifier`] cert storage
/// (like `HashMap<String, SelfsignedCert>`, as a known_hosts parsing result) /// (like `HashMap<String, SelfsignedCert>`, as a known_hosts parsing result)
pub struct SelfsignedCert { pub struct SelfsignedCert {
pub algo: crate::certs::fingerprint::Algorithm, pub algo: fingerprint::HashAlgo,
pub fingerprint: String, pub fingerprint: String,
pub expires: u64, pub expires: u64,
} }