obnam-benchmark 0.1.0

a backup program
Documentation
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)
}