feat: custom cert verifier to support self-signed, fingerprint generator

This commit is contained in:
DarkCat09 2024-08-02 21:22:19 +04:00
parent 86fb310e71
commit 19e1148989
Signed by: DarkCat09
GPG key ID: 0A26CD5B3345D6E3
7 changed files with 304 additions and 0 deletions

79
Cargo.lock generated
View file

@ -32,6 +32,21 @@ dependencies = [
"rustc-demangle",
]
[[package]]
name = "base64ct"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "bytes"
version = "1.6.1"
@ -50,6 +65,35 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cpufeatures"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "equivalent"
version = "1.0.1"
@ -65,6 +109,16 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.15"
@ -280,6 +334,17 @@ dependencies = [
"untrusted",
]
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "socket2"
version = "0.5.7"
@ -348,8 +413,10 @@ dependencies = [
name = "tokio-gemini"
version = "0.1.0"
dependencies = [
"base64ct",
"mime",
"num_enum",
"sha2",
"tokio",
"tokio-rustls",
"url",
@ -395,6 +462,12 @@ dependencies = [
"winnow",
]
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicode-bidi"
version = "0.3.15"
@ -433,6 +506,12 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"

View file

@ -10,8 +10,10 @@ description = "Gemini protocol client & server written in Rust with Tokio"
categories = ["network-programming"]
[dependencies]
base64ct = "1.6.0"
mime = "0.3.17"
num_enum = "0.7.3"
sha2 = "0.10.8"
tokio = { version = "1.39.2", features = ["io-util", "net"] }
tokio-rustls = { version = "0.26.0", default-features = false, features = ["ring"] }
url = "2.5.2"

38
src/certs/fingerprint.rs Normal file
View file

@ -0,0 +1,38 @@
use base64ct::{Base64Unpadded, Encoding};
use sha2::{Digest, Sha256, Sha512};
use super::verifier::CertificateDer;
const SHA256_B64_LEN: usize = 44; // 4 * ((256 / 8) as f64 / 3 as f64).ceil()
const SHA512_B64_LEN: usize = 88; // 4 * ((512 / 8) as f64 / 3 as f64).ceil()
pub enum Algorithm {
Sha256,
Sha512,
}
pub fn generate_fingerprint(
cert: &CertificateDer,
algo: Algorithm,
) -> Result<String, base64ct::InvalidLengthError> {
match algo {
Algorithm::Sha256 => {
let mut hasher = Sha256::new();
for chunk in cert.chunks(128) {
hasher.update(chunk);
}
let bin = hasher.finalize();
let mut buf = [0; SHA256_B64_LEN];
Base64Unpadded::encode(&bin, &mut buf).map(|hash| hash.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];
Base64Unpadded::encode(&bin, &mut buf).map(|hash| hash.to_owned())
}
}
}

59
src/certs/insecure.rs Normal file
View file

@ -0,0 +1,59 @@
use tokio_rustls::rustls::{
self,
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
crypto::CryptoProvider,
};
#[derive(Debug)]
pub struct AllowAllCertVerifier(CryptoProvider);
impl AllowAllCertVerifier {
fn yes_i_know_what_i_am_doing(provider: CryptoProvider) -> Self {
AllowAllCertVerifier(provider)
}
}
impl ServerCertVerifier for AllowAllCertVerifier {
fn verify_server_cert(
&self,
_end_entity: &rustls::pki_types::CertificateDer<'_>,
_intermediates: &[rustls::pki_types::CertificateDer<'_>],
_server_name: &rustls::pki_types::ServerName<'_>,
_ocsp_response: &[u8],
_now: rustls::pki_types::UnixTime,
) -> Result<ServerCertVerified, rustls::Error> {
Ok(ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
message: &[u8],
cert: &rustls::pki_types::CertificateDer<'_>,
dss: &rustls::DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
rustls::crypto::verify_tls12_signature(
message,
cert,
dss,
&self.0.signature_verification_algorithms,
)
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &rustls::pki_types::CertificateDer<'_>,
dss: &rustls::DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
rustls::crypto::verify_tls13_signature(
message,
cert,
dss,
&self.0.signature_verification_algorithms,
)
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
self.0.signature_verification_algorithms.supported_schemes()
}
}

3
src/certs/mod.rs Normal file
View file

@ -0,0 +1,3 @@
pub mod fingerprint;
pub mod insecure;
pub mod verifier;

122
src/certs/verifier.rs Normal file
View file

@ -0,0 +1,122 @@
pub use tokio_rustls::rustls::pki_types::{CertificateDer, ServerName, UnixTime};
use tokio_rustls::rustls::{
self,
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
};
pub trait SelfsignedCertVerifier: Send + Sync {
fn verify(
&self,
cert: &CertificateDer,
host: &str,
now: UnixTime,
) -> Result<bool, rustls::Error>;
}
pub struct SelfsignedCert {
algo: super::fingerprint::Algorithm,
fingerprint: String,
expires: u64,
}
pub struct CustomCertVerifier {
provider: rustls::crypto::CryptoProvider,
webpki_verifier: Option<rustls::client::WebPkiServerVerifier>,
ss_allowed: bool,
ss_verifier: dyn SelfsignedCertVerifier,
}
impl ServerCertVerifier for CustomCertVerifier {
fn verify_server_cert(
&self,
end_entity: &CertificateDer<'_>,
intermediates: &[CertificateDer<'_>],
server_name: &ServerName<'_>,
ocsp_response: &[u8],
now: UnixTime,
) -> Result<ServerCertVerified, rustls::Error> {
// if webpki CA certs enabled
if let Some(wv) = &self.webpki_verifier {
match wv.verify_server_cert(end_entity, intermediates, server_name, ocsp_response, now)
{
Ok(verified) => {
return Ok(verified);
}
Err(
e @ rustls::Error::InvalidCertificate(rustls::CertificateError::UnknownIssuer),
) => {
if !self.ss_allowed {
return Err(e);
}
// go ahead, verify as self-signed
}
Err(e) => {
// any other error, probably related to invalid cert
return Err(e);
}
}
}
// TODO: certificate validation when webpki_verifier is not used
// if self-signed certs enabled
if self.ss_allowed {
// TODO: check if expired or provide handy API to check it
// (probably with rustls-webpki's webpki::Cert)
if self
.ss_verifier
.verify(end_entity, &server_name.to_str().as_ref(), now)?
{
return Ok(ServerCertVerified::assertion());
}
}
// both disabled (shouldn't happen)
Err(rustls::Error::UnsupportedNameType) // not sure if chosen correct enum item
}
fn verify_tls12_signature(
&self,
message: &[u8],
cert: &CertificateDer<'_>,
dss: &rustls::DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
rustls::crypto::verify_tls12_signature(
message,
cert,
dss,
&self.provider.signature_verification_algorithms,
)
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &CertificateDer<'_>,
dss: &rustls::DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
rustls::crypto::verify_tls13_signature(
message,
cert,
dss,
&self.provider.signature_verification_algorithms,
)
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
self.provider
.signature_verification_algorithms
.supported_schemes()
}
}
impl std::fmt::Debug for CustomCertVerifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"CustomCertVerifier {{ provider: {:?}, webpki_verifier: {:?} }}",
self.provider, self.webpki_verifier
)
}
}

View file

@ -1,3 +1,4 @@
pub mod certs;
pub mod client;
pub mod error;
pub mod response;