Skip to main content

cgn_tls/
lib.rs

1//! TLS / mTLS helpers for inter-service gRPC.
2//!
3//! Cognitora speaks gRPC across hosts and uses mutual TLS for everything
4//! that crosses a network boundary. This crate exposes a couple of small
5//! helpers built on top of `rustls` and `tonic`:
6//!
7//! * [`load_identity`]  – read a PEM cert + key into a tonic `Identity`.
8//! * [`server_tls`]     – assemble a tonic `ServerTlsConfig` requiring mTLS.
9//! * [`client_tls`]     – assemble a tonic `ClientTlsConfig` against a CA.
10//! * [`generate_dev_pki`] – bootstrap a self-signed CA + leaf for `cgn-ctl pki`.
11
12#![forbid(unsafe_code)]
13
14use std::path::Path;
15
16use cgn_core::{Error, Result};
17use tonic::transport::{Certificate, ClientTlsConfig, Identity, ServerTlsConfig};
18
19/// Load a PEM-encoded certificate + private key from disk.
20pub fn load_identity(cert_path: &Path, key_path: &Path) -> Result<Identity> {
21    let cert = std::fs::read(cert_path)
22        .map_err(|e| Error::Tls(format!("read {}: {e}", cert_path.display())))?;
23    let key = std::fs::read(key_path)
24        .map_err(|e| Error::Tls(format!("read {}: {e}", key_path.display())))?;
25    Ok(Identity::from_pem(cert, key))
26}
27
28/// Server-side TLS config requiring client certs (mTLS).
29///
30/// `ca_path` is the trust root used to verify peer certs; `cert_path` /
31/// `key_path` are the server's own identity.
32pub fn server_tls(ca_path: &Path, cert_path: &Path, key_path: &Path) -> Result<ServerTlsConfig> {
33    let identity = load_identity(cert_path, key_path)?;
34    let ca = std::fs::read(ca_path)
35        .map_err(|e| Error::Tls(format!("read {}: {e}", ca_path.display())))?;
36    Ok(ServerTlsConfig::new()
37        .identity(identity)
38        .client_ca_root(Certificate::from_pem(ca)))
39}
40
41/// Client-side TLS config that trusts `ca_path` and presents `cert/key`.
42pub fn client_tls(
43    ca_path: &Path,
44    cert_path: &Path,
45    key_path: &Path,
46    domain: impl Into<String>,
47) -> Result<ClientTlsConfig> {
48    let identity = load_identity(cert_path, key_path)?;
49    let ca = std::fs::read(ca_path)
50        .map_err(|e| Error::Tls(format!("read {}: {e}", ca_path.display())))?;
51    Ok(ClientTlsConfig::new()
52        .domain_name(domain)
53        .ca_certificate(Certificate::from_pem(ca))
54        .identity(identity))
55}
56
57/// Generate a self-signed CA + a leaf cert valid for `subject_alt_names`,
58/// returning PEM bytes for `(ca_cert, ca_key, leaf_cert, leaf_key)`.
59///
60/// This is a developer convenience used by `cgn-ctl pki bootstrap`. Use a
61/// real PKI (Vault / cert-manager / step-ca) in production.
62pub fn generate_dev_pki(common_name: &str, subject_alt_names: Vec<String>) -> Result<DevPki> {
63    use rcgen::{CertificateParams, IsCa, KeyUsagePurpose};
64
65    let mut ca_params = CertificateParams::new(vec!["cognitora-dev-ca".into()])
66        .map_err(|e| Error::Tls(format!("ca params: {e}")))?;
67    ca_params.is_ca = IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
68    ca_params
69        .distinguished_name
70        .push(rcgen::DnType::CommonName, "Cognitora Dev CA");
71    ca_params.key_usages = vec![
72        KeyUsagePurpose::KeyCertSign,
73        KeyUsagePurpose::CrlSign,
74        KeyUsagePurpose::DigitalSignature,
75    ];
76    let ca_key = rcgen::KeyPair::generate().map_err(|e| Error::Tls(format!("ca keypair: {e}")))?;
77    let ca_cert = ca_params
78        .self_signed(&ca_key)
79        .map_err(|e| Error::Tls(format!("ca self-sign: {e}")))?;
80
81    let mut leaf_params = CertificateParams::new(subject_alt_names)
82        .map_err(|e| Error::Tls(format!("leaf params: {e}")))?;
83    leaf_params
84        .distinguished_name
85        .push(rcgen::DnType::CommonName, common_name);
86    let leaf_key =
87        rcgen::KeyPair::generate().map_err(|e| Error::Tls(format!("leaf keypair: {e}")))?;
88    let leaf_cert = leaf_params
89        .signed_by(&leaf_key, &ca_cert, &ca_key)
90        .map_err(|e| Error::Tls(format!("leaf sign: {e}")))?;
91
92    Ok(DevPki {
93        ca_cert_pem: ca_cert.pem(),
94        ca_key_pem: ca_key.serialize_pem(),
95        leaf_cert_pem: leaf_cert.pem(),
96        leaf_key_pem: leaf_key.serialize_pem(),
97    })
98}
99
100/// Output of [`generate_dev_pki`].
101#[derive(Debug, Clone)]
102pub struct DevPki {
103    pub ca_cert_pem: String,
104    pub ca_key_pem: String,
105    pub leaf_cert_pem: String,
106    pub leaf_key_pem: String,
107}