Skip to main content

pwr_server/
cert.rs

1//! TLS certificate generation for pwr-server.
2//!
3//! Generates self-signed ECDSA P-256 certificates for TLS 1.3.
4//! The certificate fingerprint is printed so the user can pin it
5//! in the client config for MITM protection.
6
7use rcgen::{CertificateParams, DistinguishedName, DnType, KeyPair};
8use std::fs;
9use std::path::Path;
10
11/// Generate a self-signed TLS certificate and private key.
12///
13/// Returns (cert_pem, key_pem, fingerprint_sha256).
14/// The certificate is valid for 365 days from issuance.
15pub fn generate_certificate(
16    common_name: &str,
17) -> Result<(String, String, String), String> {
18    let mut params = CertificateParams::new(vec![common_name.to_string()])
19        .map_err(|e| format!("cert params: {}", e))?;
20
21    // Set distinguished name
22    let mut dn = DistinguishedName::new();
23    dn.push(DnType::CommonName, common_name);
24    params.distinguished_name = dn;
25
26    // Generate ECDSA P-256 key pair
27    let key_pair = KeyPair::generate()
28        .map_err(|e| format!("key gen: {}", e))?;
29
30    let cert = params
31        .self_signed(&key_pair)
32        .map_err(|e| format!("self-sign: {}", e))?;
33
34    let cert_pem = cert.pem();
35    let key_pem = key_pair.serialize_pem();
36
37    // Compute SHA-256 fingerprint for certificate pinning
38    let fingerprint = pwr_core::crypto::sha256_hex(cert_pem.as_bytes());
39
40    Ok((cert_pem, key_pem, fingerprint))
41}
42
43/// Write certificate and key to files with appropriate permissions.
44pub fn save_certificate(
45    cert_path: &Path,
46    key_path: &Path,
47    cert_pem: &str,
48    key_pem: &str,
49) -> Result<(), String> {
50    // Create parent directories
51    if let Some(parent) = cert_path.parent() {
52        fs::create_dir_all(parent)
53            .map_err(|e| format!("mkdir {}: {}", parent.display(), e))?;
54    }
55    if let Some(parent) = key_path.parent() {
56        fs::create_dir_all(parent)
57            .map_err(|e| format!("mkdir {}: {}", parent.display(), e))?;
58    }
59
60    // Write certificate (world-readable)
61    fs::write(cert_path, cert_pem)
62        .map_err(|e| format!("write cert: {}", e))?;
63
64    // Write private key (owner-only)
65    fs::write(key_path, key_pem)
66        .map_err(|e| format!("write key: {}", e))?;
67
68    #[cfg(unix)]
69    {
70        use std::os::unix::fs::PermissionsExt;
71        let mut perms = fs::metadata(key_path)
72            .map_err(|e| format!("stat key: {}", e))?
73            .permissions();
74        perms.set_mode(0o600);
75        fs::set_permissions(key_path, perms)
76            .map_err(|e| format!("chmod key: {}", e))?;
77    }
78
79    Ok(())
80}
81
82/// Generate a fresh server configuration with TLS certificates and PSK.
83///
84/// `config_dir` is the base directory for pwr-server files (e.g.
85/// `/etc/pwr` for system installs or `~/.config/pwr` for user installs).
86/// Cert and key files are written under this directory; the config file
87/// is written at `config_dir/server.toml`.
88///
89/// `storage_dir` is where project archives will be stored (e.g.
90/// `/srv/pwr/projects` or `~/.local/share/pwr/projects`).
91pub fn init_server(
92    config_dir: &Path,
93    storage_dir: &Path,
94    hostname: &str,
95) -> Result<(), String> {
96    use crate::config::{save_config, ServerConfig};
97
98    let (cert_pem, key_pem, fingerprint) = generate_certificate(hostname)?;
99
100    let cert_path = config_dir.join("server.crt");
101    let key_path = config_dir.join("server.key");
102    let config_path = config_dir.join("server.toml");
103
104    save_certificate(&cert_path, &key_path, &cert_pem, &key_pem)?;
105
106    // Generate PSK
107    let psk = pwr_core::crypto::generate_psk();
108    let psk_hex = pwr_core::crypto::psk_to_hex(&psk);
109
110    let mut config = ServerConfig::default();
111    config.auth_token = psk_hex.clone();
112    config.tls_cert_path = cert_path.clone();
113    config.tls_key_path = key_path.clone();
114    config.storage_base_path = storage_dir.to_path_buf();
115
116    save_config(&config, &config_path)?;
117
118    println!("Server initialized successfully.");
119    println!("  Config:      {}", config_path.display());
120    println!("  Certificate: {}", cert_path.display());
121    println!("  Private key: {}", key_path.display());
122    println!("  Storage:     {}", storage_dir.display());
123    println!("  PSK:         {}", psk_hex);
124    println!("  Fingerprint: {}", fingerprint);
125    println!();
126    println!("Copy the PSK to your client config:");
127    println!("  pwr init --server-host {} --psk {}", hostname, psk_hex);
128    println!();
129    println!("To start the server:");
130    println!("  pwr-server --config {} start", config_path.display());
131
132    Ok(())
133}