refactor/feat: rewrite certs::fingerprint with generic data type…
…and allowing different bin2txt (getting ready for DANE)
This commit is contained in:
parent
6f91e705d3
commit
1fc73d0cab
6 changed files with 73 additions and 52 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
},
|
},
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue