product-os-proxy 0.0.19

Product OS : Proxy builds on the work of hudsucker, taking it to the next level with a man-in-the-middle proxy server that can tunnel traffic through a VPN utilising Product OS : VPN.
//! Trust store probing for managed MITM root CAs.

use product_os_security::certificates::ManagedCa;

use super::trust_native;

pub use trust_native::check_system_native;

/// Whether a CA fingerprint is present and trusted in a target store.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TrustStatus {
    /// Expected fingerprint found and trusted for TLS.
    Trusted,
    /// Expected fingerprint present but not fully trusted for SSL.
    InstalledNotTrusted,
    /// Expected fingerprint not found in the target store.
    NotInstalled,
    /// A different fingerprint is installed for the same CA subject (stale CA).
    FingerprintMismatch,
    /// Certificate errors bypassed via browser flags.
    BypassedViaFlag,
    /// Probe failed or store unavailable.
    Unknown(String),
}

/// Whether a target should be uninstalled and reinstalled.
#[must_use]
pub fn needs_reinstall(status: &TrustStatus) -> bool {
    matches!(
        status,
        TrustStatus::NotInstalled
            | TrustStatus::InstalledNotTrusted
            | TrustStatus::FingerprintMismatch
    )
}

/// Whether uninstall should run before install (skip when nothing is present).
#[must_use]
pub fn needs_uninstall_before_install(status: &TrustStatus) -> bool {
    matches!(
        status,
        TrustStatus::InstalledNotTrusted | TrustStatus::FingerprintMismatch
    )
}

/// Probe OS and browser trust stores for a managed CA fingerprint.
pub struct TrustProbe;

impl TrustProbe {
    /// Check trust for the given target name (`system`, `firefox`, `chromium-profile`, `macos-user`).
    pub fn check_target(managed: &ManagedCa, target: &str) -> TrustStatus {
        match target.to_ascii_lowercase().as_str() {
            "system" => Self::check_system(managed),
            "firefox" => trust_native::check_nss_script(managed, "install-linux-nss.sh"),
            "chromium-profile" | "chromium" => {
                trust_native::check_nss_script(managed, "install-chromium-nss.sh")
            }
            "macos-user" | "user-keychain" => {
                if let Some(status) = trust_native::check_macos_user_native(managed) {
                    return status;
                }
                TrustStatus::Unknown("macos-user probe unavailable on this platform".into())
            }
            other => TrustStatus::Unknown(format!("unknown trust target: {other}")),
        }
    }

    /// Check all configured targets; returns true if every probe is `Trusted`.
    pub fn all_trusted(managed: &ManagedCa, targets: &[String]) -> bool {
        if targets.is_empty() {
            return Self::check_target(managed, "system") == TrustStatus::Trusted;
        }
        targets
            .iter()
            .all(|t| Self::check_target(managed, t) == TrustStatus::Trusted)
    }

    fn check_system(managed: &ManagedCa) -> TrustStatus {
        if let Some(status) = check_system_native(managed) {
            return status;
        }

        #[cfg(target_os = "macos")]
        {
            return Self::check_macos_system_shell(managed);
        }
        #[cfg(target_os = "windows")]
        {
            return Self::check_windows_shell(managed);
        }
        #[cfg(target_os = "linux")]
        {
            return Self::check_linux_shell(managed);
        }
        #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
        {
            TrustStatus::Unknown("unsupported platform for system trust probe".into())
        }
    }

    #[cfg(target_os = "macos")]
    fn check_macos_system_shell(managed: &ManagedCa) -> TrustStatus {
        let Ok(fp) = managed.fingerprint_sha256_compact() else {
            return TrustStatus::Unknown("fingerprint unavailable".into());
        };
        shell_fingerprint_probe(
            "security",
            &[
                "find-certificate",
                "-a",
                "-Z",
                "/Library/Keychains/System.keychain",
            ],
            &fp,
            managed,
            Some("/Library/Keychains/System.keychain"),
        )
    }

    #[cfg(target_os = "windows")]
    fn check_windows_shell(managed: &ManagedCa) -> TrustStatus {
        let Ok(fp) = managed.fingerprint_sha256_compact() else {
            return TrustStatus::Unknown("fingerprint unavailable".into());
        };
        shell_fingerprint_probe("certutil", &["-store", "Root"], &fp, managed, None)
    }

    #[cfg(target_os = "linux")]
    fn check_linux_shell(managed: &ManagedCa) -> TrustStatus {
        let Ok(fp) = managed.fingerprint_sha256_compact() else {
            return TrustStatus::Unknown("fingerprint unavailable".into());
        };
        shell_fingerprint_probe(
            "sh",
            &["-c", "openssl crl2pkcs7 -nocrl -certfile /etc/ssl/certs/ca-certificates.crt 2>/dev/null | openssl pkcs7 -print_certs -noout 2>/dev/null || true"],
            &fp,
            managed,
            None,
        )
    }
}

#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
fn shell_fingerprint_probe(
    cmd: &str,
    args: &[&str],
    fingerprint_compact: &str,
    managed: &ManagedCa,
    keychain: Option<&str>,
) -> TrustStatus {
    use std::process::Command;

    let output = Command::new(cmd).args(args).output();
    match output {
        Ok(out) if out.status.success() || cmd == "sh" => {
            let text = String::from_utf8_lossy(&out.stdout).to_uppercase();
            let needle = fingerprint_compact.to_ascii_uppercase();
            if text.contains(&needle) {
                #[cfg(target_os = "macos")]
                if let Some(kc) = keychain {
                    return match trust_native::verify_macos_ssl_trust(managed, Some(kc)) {
                        Ok(true) => TrustStatus::Trusted,
                        Ok(false) => TrustStatus::InstalledNotTrusted,
                        Err(e) => TrustStatus::Unknown(e),
                    };
                }
                TrustStatus::Trusted
            } else {
                TrustStatus::NotInstalled
            }
        }
        Ok(out) => TrustStatus::Unknown(format!(
            "{cmd} probe failed: {}",
            String::from_utf8_lossy(&out.stderr)
        )),
        Err(e) => TrustStatus::Unknown(format!("failed to run {cmd}: {e}")),
    }
}

impl std::fmt::Display for TrustStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Trusted => f.write_str("trusted"),
            Self::InstalledNotTrusted => f.write_str("installed-not-trusted"),
            Self::NotInstalled => f.write_str("not-installed"),
            Self::FingerprintMismatch => f.write_str("fingerprint-mismatch"),
            Self::BypassedViaFlag => f.write_str("bypassed-via-flag"),
            Self::Unknown(msg) => write!(f, "unknown({msg})"),
        }
    }
}