use std::fs::read; use std::path::Path; use std::process::{Command, Stdio}; use tempfile::NamedTempFile; #[derive(Debug, thiserror::Error)] pub enum TlsError { #[error("failed to create temporary file: {0}")] TempFile(std::io::Error), #[error("failed to read temporary file: {0}")] ReadTemp(std::io::Error), #[error("failed to run openssl {0}: {1}")] RunOpenSsl(String, std::io::Error), #[error("openssl {0} failed: {1}")] OpenSsl(String, String), } #[derive(Debug)] pub struct Tls { key: Vec, cert: Vec, } impl Tls { pub fn new() -> Result { let (key, cert) = generate()?; Ok(Self { key, cert }) } pub fn key(&self) -> &[u8] { &self.key } pub fn cert(&self) -> &[u8] { &self.cert } } fn generate() -> Result<(Vec, Vec), TlsError> { let key = NamedTempFile::new().map_err(TlsError::TempFile)?; let csr = NamedTempFile::new().map_err(TlsError::TempFile)?; genrsa(key.path())?; let key_data = rsa(key.path())?; req(key.path(), csr.path())?; let cert_data = x509(key.path(), csr.path())?; Ok((key_data, cert_data)) } fn openssl() -> Command { let mut command = Command::new("openssl"); command .stdin(Stdio::null()) .stdout(Stdio::piped()) .stderr(Stdio::piped()); command } fn genrsa(filename: &Path) -> Result<(), TlsError> { let output = openssl() .arg("genrsa") .arg("-out") .arg(filename) .arg("2048") .output() .map_err(|err| TlsError::RunOpenSsl("genrsa".to_string(), err))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr).into_owned(); return Err(TlsError::OpenSsl("genrsa".to_string(), stderr)); } Ok(()) } fn rsa(filename: &Path) -> Result, TlsError> { let output = openssl() .arg("rsa") .arg("-in") .arg(filename) .arg("-out") .arg(filename) .output() .map_err(|err| TlsError::RunOpenSsl("rsa".to_string(), err))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr).into_owned(); return Err(TlsError::OpenSsl("rsa".to_string(), stderr)); } read(filename).map_err(TlsError::ReadTemp) } fn req(key: &Path, csr: &Path) -> Result<(), TlsError> { let output = openssl() .arg("req") .arg("-sha256") .arg("-new") .arg("-key") .arg(key) .arg("-out") .arg(csr) .arg("-subj") .arg("/CN=localhost") .output() .map_err(|err| TlsError::RunOpenSsl("req".to_string(), err))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr).into_owned(); return Err(TlsError::OpenSsl("req".to_string(), stderr)); } Ok(()) } fn x509(key: &Path, csr: &Path) -> Result, TlsError> { let output = openssl() .arg("x509") .arg("-req") .arg("-sha256") .arg("-days") .arg("1") .arg("-in") .arg(csr) .arg("-signkey") .arg(key) .output() .map_err(|err| TlsError::RunOpenSsl("req".to_string(), err))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr).into_owned(); return Err(TlsError::OpenSsl("req".to_string(), stderr)); } Ok(output.stdout) }