feat: custom cert verifier to support self-signed, fingerprint generator
This commit is contained in:
parent
86fb310e71
commit
19e1148989
7 changed files with 304 additions and 0 deletions
79
Cargo.lock
generated
79
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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
38
src/certs/fingerprint.rs
Normal 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
59
src/certs/insecure.rs
Normal 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
3
src/certs/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
pub mod fingerprint;
|
||||
pub mod insecure;
|
||||
pub mod verifier;
|
122
src/certs/verifier.rs
Normal file
122
src/certs/verifier.rs
Normal 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
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
pub mod certs;
|
||||
pub mod client;
|
||||
pub mod error;
|
||||
pub mod response;
|
||||
|
|
Loading…
Reference in a new issue