use std::fmt;
use std::sync::{Arc, Mutex};
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
use rustls::crypto::{CryptoProvider, verify_tls12_signature, verify_tls13_signature};
use rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName, UnixTime};
use rustls::{ClientConfig, DigitallySignedStruct, SignatureScheme};
use sha1::{Digest, Sha1};
use x509_parser::prelude::FromDer;
use x509_parser::prelude::X509Certificate;
#[derive(Clone, PartialEq, Eq)]
pub struct ServerCert {
pub chain: Vec<CertificateDer<'static>>,
pub sha1_hex: String,
pub sha256_hex: String,
pub issuer: Option<String>,
pub valid_from: Option<String>,
pub valid_until: Option<String>,
pub pem: String,
}
impl fmt::Debug for ServerCert {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ServerCert")
.field("sha1_hex", &self.sha1_hex)
.field("sha256_hex", &self.sha256_hex)
.field("issuer", &self.issuer)
.field("valid_from", &self.valid_from)
.field("valid_until", &self.valid_until)
.finish_non_exhaustive()
}
}
impl ServerCert {
fn new(end_entity: &CertificateDer<'_>, intermediates: &[CertificateDer<'_>]) -> Self {
let der = end_entity.as_ref();
let details = parse_cert_details(der);
let mut chain = vec![end_entity.clone().into_owned()];
chain.extend(intermediates.iter().map(|c| c.clone().into_owned()));
Self {
chain,
sha1_hex: hex(&Sha1::digest(der)),
sha256_hex: hex(&sha256(der)),
issuer: details.issuer,
valid_from: details.valid_from,
valid_until: details.valid_until,
pem: pem(der),
}
}
}
struct CertDetails {
issuer: Option<String>,
valid_from: Option<String>,
valid_until: Option<String>,
}
fn parse_cert_details(der: &[u8]) -> CertDetails {
let Ok((_, cert)) = X509Certificate::from_der(der) else {
return CertDetails {
issuer: None,
valid_from: None,
valid_until: None,
};
};
let validity = cert.validity();
CertDetails {
issuer: Some(cert.issuer().to_string()),
valid_from: validity.not_before.to_rfc2822().ok(),
valid_until: validity.not_after.to_rfc2822().ok(),
}
}
fn pem(der: &[u8]) -> String {
use base64::Engine as _;
let encoded = base64::engine::general_purpose::STANDARD.encode(der);
let mut out = String::with_capacity(encoded.len() + 64);
out.push_str("-----BEGIN CERTIFICATE-----\n");
for chunk in encoded.as_bytes().chunks(64) {
out.push_str(std::str::from_utf8(chunk).unwrap_or_default());
out.push('\n');
}
out.push_str("-----END CERTIFICATE-----\n");
out
}
pub type CertVerifier = Arc<dyn Fn(&ServerCert) -> bool + Send + Sync>;
pub struct ClientIdentity {
pub chain: Vec<CertificateDer<'static>>,
pub key: PrivateKeyDer<'static>,
}
impl fmt::Debug for ClientIdentity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ClientIdentity")
.field("chain_len", &self.chain.len())
.finish_non_exhaustive()
}
}
pub fn cert_hash(der: &[u8]) -> String {
hex(&Sha1::digest(der))
}
fn hex(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{b:02x}")).collect()
}
fn sha256(data: &[u8]) -> Vec<u8> {
let suite = rustls::crypto::ring::cipher_suite::TLS13_AES_128_GCM_SHA256;
match suite.tls13() {
Some(tls13) => tls13.common.hash_provider.hash(data).as_ref().to_vec(),
None => unreachable!("TLS13_AES_128_GCM_SHA256 is a TLS 1.3 suite"),
}
}
#[derive(Debug)]
struct Verifier {
decide: CertVerifierDebug,
provider: Arc<CryptoProvider>,
seen: Mutex<Option<ServerCert>>,
}
struct CertVerifierDebug(CertVerifier);
impl fmt::Debug for CertVerifierDebug {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("CertVerifier")
}
}
impl ServerCertVerifier for Verifier {
fn verify_server_cert(
&self,
end_entity: &CertificateDer<'_>,
intermediates: &[CertificateDer<'_>],
_server_name: &ServerName<'_>,
_ocsp_response: &[u8],
_now: UnixTime,
) -> Result<ServerCertVerified, rustls::Error> {
let cert = ServerCert::new(end_entity, intermediates);
let accepted = (self.decide.0)(&cert);
if let Ok(mut seen) = self.seen.lock() {
*seen = Some(cert);
}
if accepted {
Ok(ServerCertVerified::assertion())
} else {
Err(rustls::Error::InvalidCertificate(
rustls::CertificateError::ApplicationVerificationFailure,
))
}
}
fn verify_tls12_signature(
&self,
message: &[u8],
cert: &CertificateDer<'_>,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
verify_tls12_signature(
message,
cert,
dss,
&self.provider.signature_verification_algorithms,
)
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &CertificateDer<'_>,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
verify_tls13_signature(
message,
cert,
dss,
&self.provider.signature_verification_algorithms,
)
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
self.provider
.signature_verification_algorithms
.supported_schemes()
}
}
pub(crate) struct SeenCert(Arc<Verifier>);
impl SeenCert {
pub(crate) fn take(&self) -> Option<ServerCert> {
self.0.seen.lock().ok().and_then(|mut s| s.take())
}
}
pub(crate) fn client_config(
identity: Option<ClientIdentity>,
decide: CertVerifier,
) -> Result<(Arc<ClientConfig>, SeenCert), rustls::Error> {
let provider = Arc::new(rustls::crypto::ring::default_provider());
let verifier = Arc::new(Verifier {
decide: CertVerifierDebug(decide),
provider: provider.clone(),
seen: Mutex::new(None),
});
let builder = ClientConfig::builder_with_provider(provider)
.with_safe_default_protocol_versions()?
.dangerous()
.with_custom_certificate_verifier(verifier.clone());
let config = match identity {
Some(id) => builder.with_client_auth_cert(id.chain, id.key)?,
None => builder.with_no_client_auth(),
};
Ok((Arc::new(config), SeenCert(verifier)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hashes_are_hex() {
assert_eq!(
cert_hash(b"abc"),
"a9993e364706816aba3e25717850c26c9cd0d89d"
);
assert_eq!(
hex(&sha256(b"abc")),
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
);
}
#[test]
fn parses_certificate_details() {
let generated =
rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).expect("cert");
let der = generated.cert.der();
let details = parse_cert_details(der.as_ref());
assert!(details.issuer.is_some());
assert!(details.valid_from.is_some());
assert!(details.valid_until.is_some());
let pem = pem(der.as_ref());
assert!(pem.starts_with("-----BEGIN CERTIFICATE-----\n"));
assert!(pem.ends_with("-----END CERTIFICATE-----\n"));
}
}