use std::time::Duration;
use windows::core::w;
use crate::{
error::ElevateError,
security::{Privilege, ProcessSnapshot, ServiceManager, Token, TokenType},
strategy::ElevationStrategy,
};
const LOCAL_SYSTEM_SID: &str = "S-1-5-18";
pub struct TrustedInstallerStrategy {
service_timeout: Duration,
}
impl Default for TrustedInstallerStrategy {
fn default() -> Self {
Self {
service_timeout: Duration::from_secs(30),
}
}
}
impl TrustedInstallerStrategy {
pub const fn with_timeout(service_timeout: Duration) -> Self {
Self { service_timeout }
}
}
impl ElevationStrategy for TrustedInstallerStrategy {
fn acquire_primary_token(&self) -> Result<Token, ElevateError> {
let cur_token = Token::current()?;
cur_token.enable_privileges(&[Privilege::Debug, Privilege::Impersonate])?;
let caller_session_id = cur_token.session_id()?;
let ti_pid = ServiceManager::open()?
.service(w!("TrustedInstaller"))?
.start_and_wait("TrustedInstaller", self.service_timeout)?;
let winlogon_pid = find_system_process("winlogon.exe", caller_session_id)?;
let _impersonation_guard = Token::from_pid(winlogon_pid)?
.duplicate(TokenType::Impersonation)?
.impersonate()?;
let ti_primary_token = Token::from_pid(ti_pid)?.duplicate(TokenType::Primary)?;
ti_primary_token.set_session_id(caller_session_id)?;
Ok(ti_primary_token)
}
}
fn find_system_process(process_name: &str, session_id: u32) -> Result<u32, ElevateError> {
for (pid, name) in ProcessSnapshot::new()? {
if !name.eq_ignore_ascii_case(process_name) {
continue;
}
if let Ok(token) = Token::from_pid(pid) {
let is_matching_session = token.session_id().is_ok_and(|s| s == session_id);
let is_system_user = token.user_sid().is_ok_and(|sid| sid == LOCAL_SYSTEM_SID);
if is_matching_session && is_system_user {
return Ok(pid);
}
}
}
Err(ElevateError::ProcessNotFound(process_name.to_string()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_strategy_default_timeout() {
let strategy = TrustedInstallerStrategy::default();
assert_eq!(strategy.service_timeout, Duration::from_secs(30));
}
#[test]
fn test_strategy_custom_timeout() {
let strategy = TrustedInstallerStrategy::with_timeout(Duration::from_secs(10));
assert_eq!(strategy.service_timeout, Duration::from_secs(10));
}
}