shimpz-cli 0.5.26

Fast local tooling for Shimpz Assistants
//! Fail-closed supported-host classification and managed-disk evidence parsing.

use super::super::graph::StorageProfile;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum HostProfile {
    Linux,
    MacOs,
    Wsl,
}

pub(crate) fn classify(
    os: &str,
    arch: &str,
    microsoft_kernel: bool,
    wsl_interop: bool,
    pid_one: &str,
) -> Result<HostProfile, String> {
    match (os, arch, microsoft_kernel, wsl_interop) {
        ("linux", "x86_64", false, false) => Ok(HostProfile::Linux),
        ("linux", "x86_64", true, true) if pid_one.trim() == "systemd" => Ok(HostProfile::Wsl),
        ("macos", "aarch64", false, false) => Ok(HostProfile::MacOs),
        ("linux", "x86_64", true, true) => Err("WSL2 must run systemd as PID 1".into()),
        _ => Err("the host profile is unsupported or ambiguous".into()),
    }
}

impl HostProfile {
    pub(crate) const fn storage(self) -> StorageProfile {
        match self {
            Self::Linux => StorageProfile::LinuxLuks,
            Self::MacOs | Self::Wsl => StorageProfile::ManagedDisk,
        }
    }

    pub(crate) const fn name(self) -> &'static str {
        match self {
            Self::Linux => "linux-luks",
            Self::MacOs => "macos-filevault",
            Self::Wsl => "windows-wsl",
        }
    }
}

pub(crate) fn bitlocker_record_valid(value: &str) -> bool {
    value.trim_end_matches(['\r', '\n']) == "shimpz-bitlocker-v1|FullyEncrypted|On|100"
}

pub(crate) fn luks_dump_valid(value: &str) -> bool {
    exactly_one(value, "Version:")
        && exactly_one_trimmed(value, "cipher:")
        && exactly_one_trimmed(value, "Key:")
        && exactly_one_trimmed(value, "PBKDF:")
        && exactly_one_trimmed(value, "Time cost:")
        && exactly_one_trimmed(value, "Memory:")
        && exactly_one_trimmed(value, "Threads:")
        && has_exact(value, "Version:", "2")
        && has_exact_trimmed(value, "cipher:", "aes-xts-plain64")
        && has_exact_trimmed(value, "Key:", "512 bits")
        && has_exact_trimmed(value, "PBKDF:", "argon2id")
        && has_exact_trimmed(value, "Time cost:", "8")
        && has_exact_trimmed(value, "Memory:", "262144")
        && has_exact_trimmed(value, "Threads:", "4")
}

fn exactly_one(value: &str, prefix: &str) -> bool {
    value
        .lines()
        .filter(|line| line.starts_with(prefix))
        .count()
        == 1
}

fn exactly_one_trimmed(value: &str, prefix: &str) -> bool {
    value
        .lines()
        .filter(|line| line.trim_start().starts_with(prefix))
        .count()
        == 1
}

fn has_exact(value: &str, prefix: &str, expected: &str) -> bool {
    value.lines().any(|line| {
        line.strip_prefix(prefix)
            .is_some_and(|rest| rest.trim() == expected)
    })
}

fn has_exact_trimmed(value: &str, prefix: &str, expected: &str) -> bool {
    value.lines().any(|line| {
        line.trim_start()
            .strip_prefix(prefix)
            .is_some_and(|rest| rest.trim() == expected)
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    const LUKS: &str = "Version:       2\n  cipher:     aes-xts-plain64\n  Key:        512 bits\n  PBKDF:      argon2id\n  Time cost:  8\n  Memory:     262144\n  Threads:    4\n";

    #[test]
    fn classifies_only_supported_unambiguous_hosts() {
        assert_eq!(
            classify("linux", "x86_64", false, false, "systemd"),
            Ok(HostProfile::Linux)
        );
        assert_eq!(
            classify("linux", "x86_64", true, true, "systemd\n"),
            Ok(HostProfile::Wsl)
        );
        assert_eq!(
            classify("macos", "aarch64", false, false, "launchd"),
            Ok(HostProfile::MacOs)
        );
        assert_eq!(HostProfile::Linux.storage(), StorageProfile::LinuxLuks);
        assert_eq!(HostProfile::Linux.name(), "linux-luks");
        assert_eq!(HostProfile::Wsl.storage(), StorageProfile::ManagedDisk);
        assert_eq!(HostProfile::Wsl.name(), "windows-wsl");
        assert_eq!(HostProfile::MacOs.name(), "macos-filevault");
        for evidence in [
            ("linux", "aarch64", false, false, "systemd"),
            ("macos", "x86_64", false, false, "launchd"),
            ("linux", "x86_64", true, false, "systemd"),
            ("linux", "x86_64", false, true, "systemd"),
            ("windows", "x86_64", false, false, ""),
        ] {
            assert!(classify(evidence.0, evidence.1, evidence.2, evidence.3, evidence.4).is_err());
        }
        assert!(classify("linux", "x86_64", true, true, "init").is_err());
    }

    #[test]
    fn parses_only_exact_bitlocker_evidence() {
        assert!(bitlocker_record_valid(
            "shimpz-bitlocker-v1|FullyEncrypted|On|100\r\n"
        ));
        for invalid in [
            "shimpz-bitlocker-v1|EncryptionInProgress|On|99",
            "shimpz-bitlocker-v1|FullyEncrypted|Off|100",
            "FullyEncrypted|On|100",
            "shimpz-bitlocker-v1|FullyEncrypted|On|100|extra",
        ] {
            assert!(!bitlocker_record_valid(invalid));
        }
    }

    #[test]
    fn parses_only_exact_luks2_parameters() {
        assert!(luks_dump_valid(LUKS));
        for invalid in [
            LUKS.replace("Version:       2", "Version:       1"),
            LUKS.replace("aes-xts-plain64", "aes-cbc"),
            LUKS.replace("512 bits", "256 bits"),
            LUKS.replace("argon2id", "pbkdf2"),
            LUKS.replace("Time cost:  8", "Time cost:  7"),
            LUKS.replace("Memory:     262144", "Memory:     131072"),
            LUKS.replace("Threads:    4", "Threads:    2"),
            format!("{LUKS}Version:       2\n"),
            format!("{LUKS}  Memory:     262144\n"),
        ] {
            assert!(!luks_dump_valid(&invalid));
        }
    }
}