use std::fmt;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tokio_rustls::rustls::crypto::ring;
use tokio_rustls::rustls::pki_types::pem::PemObject;
use tokio_rustls::rustls::pki_types::CertificateDer;
use tokio_rustls::rustls::server::danger::ClientCertVerifier;
use tokio_rustls::rustls::server::WebPkiClientVerifier;
use tokio_rustls::rustls::RootCertStore;
#[derive(Debug)]
pub enum MtlsError {
Io { path: PathBuf, source: std::io::Error },
NoRoots(PathBuf),
Verifier(tokio_rustls::rustls::server::VerifierBuilderError),
}
impl fmt::Display for MtlsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io { path, source } =>
write!(f, "cannot read client-CA file {}: {source}", path.display()),
Self::NoRoots(p) =>
write!(f, "no usable CA certificates in {}", p.display()),
Self::Verifier(e) => write!(f, "client-cert verifier build error: {e}"),
}
}
}
impl std::error::Error for MtlsError {}
pub fn verifier_from_ca_file(ca_path: &Path) -> Result<Arc<dyn ClientCertVerifier>, MtlsError> {
let data = std::fs::read(ca_path)
.map_err(|source| MtlsError::Io { path: ca_path.to_path_buf(), source })?;
let mut roots = RootCertStore::empty();
for cert in CertificateDer::pem_slice_iter(&data) {
let cert = cert.map_err(|e| MtlsError::Io {
path: ca_path.to_path_buf(),
source: std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()),
})?;
let _ = roots.add(cert);
}
if roots.is_empty() {
return Err(MtlsError::NoRoots(ca_path.to_path_buf()));
}
WebPkiClientVerifier::builder_with_provider(Arc::new(roots), Arc::new(ring::default_provider()))
.build()
.map_err(MtlsError::Verifier)
}