soth-mitm 0.3.0

Rust intercepting proxy crate with deterministic handler/event contracts for SOTH.
Documentation
use std::path::{Path, PathBuf};

use crate::{CaError, CertificateAuthority};

use super::backend_common::{
    clear_state, operation_error, read_state, run_command, write_staged_cert, write_state,
};

#[derive(Debug, Default)]
pub(crate) struct PlatformTrustBackend;

impl PlatformTrustBackend {
    pub(crate) fn install(&self, ca: &CertificateAuthority) -> Result<(), CaError> {
        let cert_path = write_staged_cert("linux", &ca.cert_pem)?;

        let trust_store_path = system_ca_path();
        if let Some(parent) = trust_store_path.parent() {
            std::fs::create_dir_all(parent)?;
        }
        std::fs::write(&trust_store_path, &ca.cert_pem).map_err(|error| {
            if error.kind() == std::io::ErrorKind::PermissionDenied {
                return CaError::PermissionDenied {
                    operation: "install_ca_trust".to_string(),
                    detail: error.to_string(),
                };
            }
            CaError::Io(error)
        })?;

        let outcome = run_command("install_ca_trust", "update-ca-certificates", ["--fresh"])?;
        if !outcome.success {
            return Err(operation_error("install_ca_trust", outcome.stderr));
        }

        write_state("linux", &ca.fingerprint, &cert_path)?;
        Ok(())
    }

    pub(crate) fn uninstall(&self) -> Result<(), CaError> {
        let trust_store_path = system_ca_path();
        match std::fs::remove_file(&trust_store_path) {
            Ok(()) => {}
            Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
            Err(error) if error.kind() == std::io::ErrorKind::PermissionDenied => {
                return Err(CaError::PermissionDenied {
                    operation: "uninstall_ca_trust".to_string(),
                    detail: error.to_string(),
                });
            }
            Err(error) => return Err(CaError::Io(error)),
        }

        let outcome = run_command("uninstall_ca_trust", "update-ca-certificates", ["--fresh"])?;
        if !outcome.success {
            return Err(operation_error("uninstall_ca_trust", outcome.stderr));
        }
        clear_state("linux")
    }

    pub(crate) fn is_trusted(&self, fingerprint: &str) -> Result<bool, CaError> {
        let Some((stored_fingerprint, cert_path)) = read_state("linux")? else {
            return Ok(false);
        };
        if stored_fingerprint != fingerprint {
            return Ok(false);
        }

        let ca_bundle = default_ca_bundle();
        let args = [
            "verify",
            "-CAfile",
            ca_bundle
                .to_str()
                .ok_or_else(|| operation_error("is_ca_trusted", "invalid CA bundle path"))?,
            cert_path
                .to_str()
                .ok_or_else(|| operation_error("is_ca_trusted", "invalid staged cert path"))?,
        ];
        let outcome = run_command("is_ca_trusted", "openssl", args)?;
        Ok(outcome.success)
    }
}

fn system_ca_path() -> PathBuf {
    if let Some(path) = std::env::var_os("SOTH_MITM_LINUX_CA_PATH") {
        return PathBuf::from(path);
    }
    PathBuf::from("/usr/local/share/ca-certificates/soth-mitm-local-ca.crt")
}

fn default_ca_bundle() -> &'static Path {
    Path::new("/etc/ssl/certs/ca-certificates.crt")
}