Skip to main content

agentis_pay_shared/
tls.rs

1use anyhow::{Context, Result};
2use tonic::transport::ClientTlsConfig;
3
4/// Embedded mTLS client certificate (PEM).
5///
6/// At build time CI writes the real cert to `shared/certs/client.pem`.
7/// For local dev builds the file contains a placeholder that disables mTLS.
8const CLIENT_CERT_PEM: &[u8] = include_bytes!("../certs/client.pem");
9
10/// Embedded mTLS client private key (PEM).
11const CLIENT_KEY_PEM: &[u8] = include_bytes!("../certs/client.key");
12
13/// Returns `true` when the embedded cert is a real PEM certificate
14/// (not the local-dev placeholder).
15fn has_embedded_cert() -> bool {
16    CLIENT_CERT_PEM.windows(5).any(|w| w == b"BEGIN")
17}
18
19/// Build a `ClientTlsConfig` for the given domain.
20///
21/// When real mTLS certs are embedded the config includes a client identity.
22/// Otherwise only server-TLS verification is configured (using system roots).
23pub fn client_tls_config(domain: &str) -> Result<ClientTlsConfig> {
24    let mut tls = ClientTlsConfig::new()
25        .domain_name(domain.to_string())
26        .with_native_roots();
27
28    if has_embedded_cert() {
29        let identity = tonic::transport::Identity::from_pem(CLIENT_CERT_PEM, CLIENT_KEY_PEM);
30        tls = tls.identity(identity);
31    }
32
33    // Check for filesystem override (~/.agentis-pay/client.{pem,key}).
34    // This lets operators rotate certs without rebuilding.
35    // Intentionally always uses ~/.agentis-pay/ (not the -dev variant)
36    // because mTLS certs are environment-specific and shared across builds.
37    if let Some(dir) = dirs::home_dir().map(|h| h.join(".agentis-pay")) {
38        let cert_path = dir.join("client.pem");
39        let key_path = dir.join("client.key");
40        if cert_path.exists() && key_path.exists() {
41            let cert = std::fs::read(&cert_path)
42                .with_context(|| format!("reading {}", cert_path.display()))?;
43            let key = std::fs::read(&key_path)
44                .with_context(|| format!("reading {}", key_path.display()))?;
45            let identity = tonic::transport::Identity::from_pem(cert, key);
46            tls = tls.identity(identity);
47        }
48    }
49
50    Ok(tls)
51}