Skip to main content

mtop_client/net/
tls.rs

1use crate::core::MtopError;
2use rustls_pki_types::pem::PemObject;
3use rustls_pki_types::{CertificateDer, PrivateKeyDer, ServerName, TrustAnchor};
4use std::path::PathBuf;
5use tokio::fs;
6use tokio_rustls::rustls::{ClientConfig, RootCertStore};
7
8/// Configuration for establishing a TLS connection to server with optional mTLS.
9#[derive(Debug, Clone, Default)]
10pub struct TlsConfig {
11    /// Path to a custom certificate authority. If not supplied, default root certificates
12    /// from the `webpki_roots` crate are used.
13    pub ca_path: Option<PathBuf>,
14
15    /// Path to a PEM format client certificate for mTLS. If not supplied, no client authentication
16    /// is used when connecting to the server.
17    pub cert_path: Option<PathBuf>,
18
19    /// Path to a PEM format client key for mTLS. If not supplied, no client authentication is used
20    /// when connecting to the server.
21    pub key_path: Option<PathBuf>,
22
23    /// Name of the server for validating its certificate. If not supplied, the hostname
24    /// of the server is used instead.
25    pub server_name: Option<ServerName<'static>>,
26}
27
28pub(crate) async fn tls_client_config(config: TlsConfig) -> Result<ClientConfig, MtopError> {
29    let client_cert = if let Some(p) = &config.cert_path {
30        Some(load_cert(p).await?)
31    } else {
32        None
33    };
34
35    let client_key = if let Some(p) = &config.key_path {
36        Some(load_key(p).await?)
37    } else {
38        None
39    };
40
41    let root = if let Some(p) = &config.ca_path {
42        custom_root_store(load_cert(p).await?)?
43    } else {
44        default_root_store()
45    };
46
47    let builder = ClientConfig::builder().with_root_certificates(root);
48    let client_config = match (client_cert, client_key, config.cert_path, config.key_path) {
49        (Some(cert), Some(key), Some(cert_path), Some(key_path)) => {
50            tracing::debug!(message = "using key and cert for client authentication", key = ?key_path, cert = ?cert_path);
51            builder
52                .with_client_auth_cert(cert, key)
53                .map_err(|e| MtopError::configuration_cause("unable to use client cert or key", e))?
54        }
55        _ => {
56            tracing::debug!(message = "not using any client authentication");
57            builder.with_no_client_auth()
58        }
59    };
60
61    Ok(client_config)
62}
63
64async fn load_cert(path: &PathBuf) -> Result<Vec<CertificateDer<'static>>, MtopError> {
65    let contents = fs::read(path)
66        .await
67        .map_err(|e| MtopError::configuration_cause(format!("unable to load cert {}", path.display()), e))?;
68
69    let mut out = Vec::new();
70    for res in CertificateDer::pem_slice_iter(&contents) {
71        out.push(
72            res.map_err(|e| MtopError::configuration_cause(format!("unable to parse cert {}", path.display()), e))?,
73        );
74    }
75
76    Ok(out)
77}
78
79async fn load_key(path: &PathBuf) -> Result<PrivateKeyDer<'static>, MtopError> {
80    let contents = fs::read(path)
81        .await
82        .map_err(|e| MtopError::configuration_cause(format!("unable to load key {}", path.display()), e))?;
83
84    PrivateKeyDer::from_pem_slice(&contents)
85        .map_err(|e| MtopError::configuration_cause(format!("unable to parse key {}", path.display()), e))
86}
87
88fn custom_root_store(ca: Vec<CertificateDer<'static>>) -> Result<RootCertStore, MtopError> {
89    let mut store = RootCertStore::empty();
90    for cert in ca {
91        store
92            .add(cert)
93            .map_err(|e| MtopError::configuration_cause("unable to parse CA cert", e))?;
94    }
95
96    Ok(store)
97}
98
99fn default_root_store() -> RootCertStore {
100    let mut store = RootCertStore::empty();
101    store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().map(TrustAnchor::to_owned));
102    store
103}