secenv 0.2.0

Secure environments.
use {
    anyhow::{
        Context,
        Result,
    },
    std::process::{
        Command,
        Stdio,
    },
};

#[derive(Debug, Clone)]
pub struct GpgKeySpec {
    pub fingerprint: String,
}

pub struct GpgManager;

impl GpgManager {
    pub fn new() -> Result<Self> {
        Ok(Self)
    }

    pub fn export_private_key(&self, spec: &GpgKeySpec) -> Result<String> {
        let mut cmd = Command::new("gpg");
        cmd.args([
            "--export-secret-keys",
            "--armor",
            "--batch",
            "--yes",
            "--export-options",
            "export-minimal,export-clean",
            "--rfc4880",
            &spec.fingerprint,
        ]);

        let output = cmd
            .stdin(Stdio::null())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .output()
            .context("Failed to execute gpg to export private key")?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(anyhow::anyhow!("gpg failed to export private key: {}", stderr));
        }

        let private_key = String::from_utf8(output.stdout).context("GPG private key output is not valid UTF-8")?;

        if private_key.trim().is_empty() {
            return Err(anyhow::anyhow!(
                "No private key found for fingerprint: {}. Make sure the key exists in your GPG keyring.",
                spec.fingerprint
            ));
        }

        Ok(private_key)
    }

    pub fn decrypt_data(&self, encrypted_data: &str) -> Result<String> {
        let mut cmd = Command::new("gpg");
        cmd.args(["--decrypt", "--batch", "--quiet"]);

        let mut child = cmd
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn()
            .context("Failed to spawn gpg process for decryption")?;

        if let Some(stdin) = child.stdin.take() {
            use std::io::Write;
            let mut stdin = stdin;
            stdin
                .write_all(encrypted_data.as_bytes())
                .context("Failed to write encrypted data to gpg stdin")?;
        }

        let output = child
            .wait_with_output()
            .context("Failed to wait for gpg decryption process")?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(anyhow::anyhow!("GPG failed to decrypt data: {}", stderr));
        }

        let decrypted_data = String::from_utf8(output.stdout).context("GPG decrypted output is not valid UTF-8")?;

        Ok(decrypted_data)
    }
}