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<u8>,
cert: Vec<u8>,
}
impl Tls {
pub fn new() -> Result<Self, TlsError> {
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<u8>, Vec<u8>), 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<Vec<u8>, 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<Vec<u8>, 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)
}