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",
|
"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]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.6.1"
|
version = "1.6.1"
|
||||||
|
@ -50,6 +65,35 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
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]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
@ -65,6 +109,16 @@ dependencies = [
|
||||||
"percent-encoding",
|
"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]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
@ -280,6 +334,17 @@ dependencies = [
|
||||||
"untrusted",
|
"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]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.5.7"
|
version = "0.5.7"
|
||||||
|
@ -348,8 +413,10 @@ dependencies = [
|
||||||
name = "tokio-gemini"
|
name = "tokio-gemini"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
"mime",
|
"mime",
|
||||||
"num_enum",
|
"num_enum",
|
||||||
|
"sha2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
"url",
|
"url",
|
||||||
|
@ -395,6 +462,12 @@ dependencies = [
|
||||||
"winnow",
|
"winnow",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.3.15"
|
version = "0.3.15"
|
||||||
|
@ -433,6 +506,12 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.11.0+wasi-snapshot-preview1"
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
|
|
@ -10,8 +10,10 @@ description = "Gemini protocol client & server written in Rust with Tokio"
|
||||||
categories = ["network-programming"]
|
categories = ["network-programming"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
base64ct = "1.6.0"
|
||||||
mime = "0.3.17"
|
mime = "0.3.17"
|
||||||
num_enum = "0.7.3"
|
num_enum = "0.7.3"
|
||||||
|
sha2 = "0.10.8"
|
||||||
tokio = { version = "1.39.2", features = ["io-util", "net"] }
|
tokio = { version = "1.39.2", features = ["io-util", "net"] }
|
||||||
tokio-rustls = { version = "0.26.0", default-features = false, features = ["ring"] }
|
tokio-rustls = { version = "0.26.0", default-features = false, features = ["ring"] }
|
||||||
url = "2.5.2"
|
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 client;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod response;
|
pub mod response;
|
||||||
|
|
Loading…
Add table
Reference in a new issue