use super::models::SecureBootScanState;
#[cfg(not(target_os = "windows"))]
pub fn scan_device() -> Result<SecureBootScanState, crate::error::AppError> {
Err(crate::error::AppError::PlatformUnsupported(
"Secure Boot device scan is only supported on Windows".to_string(),
))
}
#[cfg(target_os = "windows")]
pub fn scan_device() -> Result<SecureBootScanState, crate::error::AppError> {
let mut state = SecureBootScanState::default();
read_registry(&mut state);
check_filesystem(&mut state);
run_powershell_checks(&mut state);
Ok(state)
}
#[cfg(target_os = "windows")]
fn read_registry(state: &mut SecureBootScanState) {
use winreg::enums::{HKEY_LOCAL_MACHINE, KEY_READ};
use winreg::RegKey;
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
if let Ok(key) = hklm.open_subkey_with_flags(
r"SYSTEM\CurrentControlSet\Control\SecureBoot\State",
KEY_READ,
) {
state.secure_boot_enabled = key
.get_value::<u32, _>("UEFISecureBootEnabled")
.ok()
.map(|v| v != 0);
}
let secureboot_key_path = r"SYSTEM\CurrentControlSet\Control\Secureboot";
if let Ok(key) = hklm.open_subkey_with_flags(secureboot_key_path, KEY_READ) {
state.managed_opt_in = key.get_value::<u32, _>("MicrosoftUpdateManagedOptIn").ok();
state.available_updates = key.get_value::<u32, _>("AvailableUpdates").ok();
state.uefi_ca2023_status = key.get_value::<u32, _>("UEFICA2023Status").ok();
state.uefi_ca2023_error = key.get_value::<u32, _>("UEFICA2023Error").ok();
}
let servicing_key_path = r"SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing";
if let Ok(key) = hklm.open_subkey_with_flags(servicing_key_path, KEY_READ) {
state.uefi_ca2023_capable = key.get_value::<u32, _>("WindowsUEFICA2023Capable").ok();
}
let device_attr_path =
r"SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing\DeviceAttributes";
if let Ok(key) = hklm.open_subkey_with_flags(device_attr_path, KEY_READ) {
state.oem_manufacturer = key
.get_value::<String, _>("OEMManufacturerName")
.ok()
.or_else(|| key.get_value::<String, _>("OEMManufacturer").ok());
state.oem_model = key
.get_value::<String, _>("OEMModelNumber")
.ok()
.or_else(|| key.get_value::<String, _>("OEMModel").ok());
state.firmware_version = key.get_value::<String, _>("FirmwareVersion").ok();
state.firmware_date = key
.get_value::<String, _>("FirmwareReleaseDate")
.ok()
.or_else(|| key.get_value::<String, _>("FirmwareDate").ok());
}
if let Ok(key) = hklm.open_subkey_with_flags(
r"SOFTWARE\Policies\Microsoft\Windows\DataCollection",
KEY_READ,
) {
state.telemetry_level = key.get_value::<u32, _>("AllowTelemetry").ok();
}
if let Ok(key) = hklm.open_subkey_with_flags(r"SOFTWARE\Mindcore\Secureboot", KEY_READ) {
state.managed_opt_in_date = key.get_value::<String, _>("ManagedOptInDate").ok();
}
let mut reboot_sources: Vec<String> = Vec::new();
if hklm
.open_subkey_with_flags(
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending",
KEY_READ,
)
.is_ok()
{
reboot_sources.push("CBS".to_string());
}
if hklm
.open_subkey_with_flags(
r"SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired",
KEY_READ,
)
.is_ok()
{
reboot_sources.push("WindowsUpdate".to_string());
}
if let Ok(key) = hklm.open_subkey_with_flags(
r"SYSTEM\CurrentControlSet\Control\Session Manager",
KEY_READ,
) {
if let Ok(values) = key.get_value::<Vec<String>, _>("PendingFileRenameOperations") {
if !values.is_empty() {
reboot_sources.push("PendingFileRename".to_string());
}
}
}
if hklm
.open_subkey_with_flags(
r"SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\PostRebootReporting",
KEY_READ,
)
.is_ok()
{
reboot_sources.push("WUPostReboot".to_string());
}
state.pending_reboot_sources = reboot_sources;
state.raw_registry_dump = Some(build_raw_registry_dump(&hklm));
}
#[cfg(target_os = "windows")]
fn build_raw_registry_dump(hklm: &winreg::RegKey) -> String {
use winreg::enums::KEY_READ;
let mut lines: Vec<String> = Vec::new();
let paths = [
r"SYSTEM\CurrentControlSet\Control\Secureboot",
r"SYSTEM\CurrentControlSet\Control\SecureBoot\State",
r"SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing",
r"SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing\DeviceAttributes",
];
for path in &paths {
lines.push(format!("[HKLM\\{}]", path));
match hklm.open_subkey_with_flags(path, KEY_READ) {
Ok(key) => {
for value_res in key.enum_values() {
match value_res {
Ok((name, data)) => {
lines.push(format!(" {} = {:?}", name, data));
}
Err(e) => {
lines.push(format!(" <error enumerating value: {}>", e));
}
}
}
}
Err(e) => {
lines.push(format!(" <key not found: {}>", e));
}
}
lines.push(String::new());
}
lines.join("\n")
}
#[cfg(target_os = "windows")]
fn check_filesystem(state: &mut SecureBootScanState) {
use std::path::Path;
let system_root = std::env::var("SystemRoot").unwrap_or_else(|_| r"C:\Windows".to_string());
let payload_dir = Path::new(&system_root).join(r"System32\SecureBootUpdates");
if payload_dir.exists() && payload_dir.is_dir() {
state.payload_folder_exists = Some(true);
let bin_count = std::fs::read_dir(&payload_dir)
.map(|entries| {
entries
.filter_map(|e| e.ok())
.filter(|e| {
e.path()
.extension()
.and_then(|ext| ext.to_str())
.map(|ext| ext.eq_ignore_ascii_case("bin"))
.unwrap_or(false)
})
.count() as u32
})
.unwrap_or(0);
state.payload_bin_count = Some(bin_count);
} else {
state.payload_folder_exists = Some(false);
state.payload_bin_count = Some(0);
}
let wincs_path = Path::new(&system_root).join(r"System32\WinCsFlags.exe");
state.wincs_available = Some(wincs_path.exists());
}
#[cfg(target_os = "windows")]
fn run_powershell_checks(state: &mut SecureBootScanState) {
if let Some(json) = run_powershell(
r#"Get-Service -Name DiagTrack -ErrorAction SilentlyContinue | Select-Object Status,StartType | ConvertTo-Json -Compress"#,
) {
if let Ok(val) = serde_json::from_str::<serde_json::Value>(&json) {
state.diagtrack_running = val["Status"]
.as_str()
.map(|s| s.eq_ignore_ascii_case("Running"));
state.diagtrack_start_type = val["StartType"].as_str().map(|s| s.to_string());
}
}
if let Some(json) = run_powershell(
r#"Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction SilentlyContinue | Select-Object Caption,BuildNumber | ConvertTo-Json -Compress"#,
) {
if let Ok(val) = serde_json::from_str::<serde_json::Value>(&json) {
state.os_caption = val["Caption"].as_str().map(|s| s.to_string());
state.os_build = val["BuildNumber"].as_str().map(|s| s.to_string());
}
}
let task_name = r#"Microsoft\Windows\PI\Secure-Boot-Update"#;
let ps_task = r#"Get-ScheduledTaskInfo -TaskPath '\Microsoft\Windows\PI\' -TaskName 'Secure-Boot-Update' -ErrorAction SilentlyContinue | Select-Object LastRunTime,LastTaskResult | ConvertTo-Json -Compress"#.to_string();
if let Some(json) = run_powershell(&ps_task) {
state.scheduled_task_exists = Some(true);
if let Ok(val) = serde_json::from_str::<serde_json::Value>(&json) {
state.scheduled_task_last_run = val["LastRunTime"].as_str().map(|s| s.to_string());
state.scheduled_task_last_result = val["LastTaskResult"]
.as_u64()
.map(|n| format!("{:#010x}", n));
}
} else {
state.scheduled_task_exists = Some(false);
}
if let Some(json) = run_powershell(
r#"Get-CimInstance -Namespace root/cimv2/Security/MicrosoftTpm -ClassName Win32_Tpm -ErrorAction SilentlyContinue | Select-Object IsPresent,IsEnabled_InitialValue,IsActivated_InitialValue,SpecVersion | ConvertTo-Json -Compress"#,
) {
if let Ok(val) = serde_json::from_str::<serde_json::Value>(&json) {
state.tpm_present = val["IsPresent"].as_bool();
state.tpm_enabled = val["IsEnabled_InitialValue"].as_bool();
state.tpm_activated = val["IsActivated_InitialValue"].as_bool();
state.tpm_spec_version = val["SpecVersion"].as_str().map(|s| s.to_string());
}
}
if let Some(json) = run_powershell(
r#"$v=Get-BitLockerVolume -MountPoint $env:SystemDrive -ErrorAction SilentlyContinue; if($v){$v|Select-Object ProtectionStatus,EncryptionPercentage,KeyProtector|ConvertTo-Json -Compress}else{'null'}"#,
) {
if json != "null" {
if let Ok(val) = serde_json::from_str::<serde_json::Value>(&json) {
state.bitlocker_protection_on = val["ProtectionStatus"]
.as_str()
.map(|s| s.eq_ignore_ascii_case("On"));
state.bitlocker_encryption_status =
val["EncryptionPercentage"].as_str().map(|s| s.to_string());
if let Some(protectors) = val["KeyProtector"].as_array() {
state.bitlocker_key_protectors = protectors
.iter()
.filter_map(|p| p["KeyProtectorType"].as_str().map(|s| s.to_string()))
.collect();
}
}
}
}
if let Some(json) = run_powershell(
r#"$disk=Get-Disk|Where-Object{$_.IsBoot -eq $true}|Select-Object -First 1 PartitionStyle; if($disk){$disk|ConvertTo-Json -Compress}else{'null'}"#,
) {
if json != "null" {
if let Ok(val) = serde_json::from_str::<serde_json::Value>(&json) {
state.disk_partition_style = val["PartitionStyle"].as_str().map(|s| s.to_string());
}
}
}
if let Some(json) =
run_powershell(r#"[PSCustomObject]@{Name=$env:COMPUTERNAME}|ConvertTo-Json -Compress"#)
{
if let Ok(val) = serde_json::from_str::<serde_json::Value>(&json) {
state.device_name = val["Name"].as_str().map(|s| s.to_string());
}
}
let _ = task_name;
}
#[cfg(target_os = "windows")]
fn run_powershell(command: &str) -> Option<String> {
let output = crate::process_util::hidden_command("powershell.exe")
.args([
"-NoProfile",
"-NonInteractive",
"-WindowStyle",
"Hidden",
"-Command",
command,
])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
if stdout.is_empty() || stdout == "null" {
None
} else {
Some(stdout)
}
}