//! First-time TLS identity generation for the server. use std::path::{Path, PathBuf}; use std::process::Command; #[derive(Debug)] pub struct SetupReport { pub identity: PathBuf, pub key: PathBuf, pub cert: PathBuf, } /// Generate a self-signed RSA identity in `dir`. /// /// Produces `identity.p12` (empty password, matching `native_tls`), `key.pem`, /// and `cert.pem`. Refuses to overwrite an existing identity. pub fn init_identity(dir: &Path, common_name: &str) -> Result { let common_name = if common_name.trim().is_empty() { "localhost" } else { common_name.trim() }; let identity = dir.join("identity.p12"); let key = dir.join("key.pem"); let cert = dir.join("cert.pem"); let existing: Vec = [&identity, &key, &cert] .into_iter() .filter(|path| path.exists()) .map(|path| path.display().to_string()) .collect(); if !existing.is_empty() { return Err(format!( "Refusing to overwrite existing TLS files: {}\n\ Remove them first if you really want a new identity.", existing.join(", ") )); } std::fs::create_dir_all(dir) .map_err(|error| format!("Could not create {}: {error}", dir.display()))?; let manual = manual_instructions(common_name); let subject = format!("/CN={common_name}"); run_openssl( dir, &[ "req", "-x509", "-newkey", "rsa:2048", "-keyout", "key.pem", "-out", "cert.pem", "-days", "3650", "-nodes", "-subj", &subject, ], "generate the certificate", &manual, )?; run_openssl( dir, &[ "pkcs12", "-export", "-out", "identity.p12", "-inkey", "key.pem", "-in", "cert.pem", "-passout", "pass:", "-keypbe", "PBE-SHA1-3DES", "-certpbe", "PBE-SHA1-3DES", "-macalg", "sha1", ], "build identity.p12", &manual, )?; #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; for path in [&key, &identity] { let metadata = std::fs::metadata(path) .map_err(|error| format!("Could not inspect {}: {error}", path.display()))?; let mut permissions = metadata.permissions(); permissions.set_mode(0o600); std::fs::set_permissions(path, permissions) .map_err(|error| format!("Could not protect {}: {error}", path.display()))?; } } Ok(SetupReport { identity, key, cert, }) } fn run_openssl(dir: &Path, args: &[&str], action: &str, manual: &str) -> Result<(), String> { let output = Command::new("openssl") .current_dir(dir) .args(args) .output() .map_err(|error| format!("Could not run openssl to {action}: {error}\n\n{manual}"))?; if output.status.success() { return Ok(()); } let stderr = String::from_utf8_lossy(&output.stderr); Err(format!( "openssl failed to {action}:\n{}\n\n{manual}", stderr.trim() )) } fn manual_instructions(common_name: &str) -> String { format!( "Install openssl, or run these commands manually:\n\ \x20 openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem \\\n\ \x20 -days 3650 -nodes -subj \"/CN={common_name}\"\n\ \x20 openssl pkcs12 -export -out identity.p12 -inkey key.pem -in cert.pem \\\n\ \x20 -passout pass:" ) }