Skip to main content

trustee_api/
tls.rs

1//! TLS support for the Trustee API server.
2//!
3//! Provides self-signed certificate auto-generation and rustls server config
4//! loading. Certificates are stored at `~/.trustee/certs/{cert.pem,key.pem}`
5//! and generated on first run if missing.
6
7use std::fs;
8use std::path::{Path, PathBuf};
9
10use anyhow::{anyhow, Result};
11use rustls::pki_types::{CertificateDer, PrivateKeyDer};
12
13/// Resolve the default certificate directory: `~/.trustee/certs/`
14pub fn default_cert_dir() -> PathBuf {
15    let home = std::env::var("HOME")
16        .or_else(|_| std::env::var("USERPROFILE"))
17        .unwrap_or_else(|_| ".".to_string());
18    PathBuf::from(home).join(".trustee").join("certs")
19}
20
21/// Ensure that cert.pem and key.pem exist in `cert_dir`.
22///
23/// If both files exist, returns their paths immediately.
24/// If either is missing, generates a new self-signed certificate pair.
25pub fn ensure_certs(cert_dir: &Path) -> Result<(PathBuf, PathBuf)> {
26    let cert_path = cert_dir.join("cert.pem");
27    let key_path = cert_dir.join("key.pem");
28
29    if cert_path.exists() && key_path.exists() {
30        tracing::debug!("Using existing certificates at {}", cert_dir.display());
31        return Ok((cert_path, key_path));
32    }
33
34    fs::create_dir_all(cert_dir)?;
35
36    // Generate self-signed certificate with rcgen
37    let cert = rcgen::generate_simple_self_signed(vec![
38        "localhost".into(),
39        "127.0.0.1".into(),
40        "::1".into(),
41    ])?;
42
43    let cert_pem = cert.cert.pem();
44    let key_pem = cert.signing_key.serialize_pem();
45
46    fs::write(&cert_path, cert_pem)?;
47    fs::write(&key_path, &key_pem)?;
48    // Set restrictive permissions on key file (Unix only)
49    #[cfg(unix)]
50    {
51        use std::os::unix::fs::PermissionsExt;
52        fs::set_permissions(&key_path, fs::Permissions::from_mode(0o600))?;
53    }
54
55    tracing::info!(
56        "Generated self-signed certificate at {}",
57        cert_dir.display()
58    );
59
60    Ok((cert_path, key_path))
61}
62
63/// Load a rustls `ServerConfig` from PEM-encoded cert and key files.
64pub fn load_tls_config(cert_path: &Path, key_path: &Path) -> Result<rustls::ServerConfig> {
65    let cert_bytes = fs::read(cert_path)?;
66    let key_bytes = fs::read(key_path)?;
67
68    // Parse certificates from PEM
69    let certs: Vec<CertificateDer<'static>> = rustls_pemfile::certs(&mut cert_bytes.as_slice())
70        .collect::<Result<Vec<_>, _>>()
71        .map_err(|e| anyhow!("Failed to parse certificate PEM: {}", e))?;
72
73    if certs.is_empty() {
74        return Err(anyhow!("No certificates found in {}", cert_path.display()));
75    }
76
77    // Parse private key from PEM
78    let key: PrivateKeyDer<'static> = rustls_pemfile::private_key(&mut key_bytes.as_slice())?
79        .ok_or_else(|| anyhow!("No private key found in {}", key_path.display()))?;
80
81    // Build rustls server config with ring crypto provider
82    let config = rustls::ServerConfig::builder()
83        .with_no_client_auth()
84        .with_single_cert(certs, key)
85        .map_err(|e| anyhow!("Failed to build TLS config: {}", e))?;
86
87    Ok(config)
88}