#[cfg(target_os = "macos")]
use std::collections::HashMap;
#[cfg(target_os = "macos")]
use std::sync::{Mutex, OnceLock};
use crate::types::{AccessPolicy, BackendKind};
pub use crate::internal::core::signing::is_binary_signed;
pub fn has_keychain_entitlement(group: &str) -> bool {
#[cfg(target_os = "macos")]
{
static CACHE: OnceLock<Mutex<HashMap<String, bool>>> = OnceLock::new();
let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new()));
let mut guard = cache.lock().unwrap_or_else(|e| e.into_inner());
if let Some(&result) = guard.get(group) {
return result;
}
let result = check_entitlement_macos(group);
guard.insert(group.to_string(), result);
result
}
#[cfg(not(target_os = "macos"))]
{
let _ = group;
false
}
}
#[cfg(target_os = "macos")]
fn check_entitlement_macos(group: &str) -> bool {
let exe = match std::env::current_exe() {
Ok(e) => e,
Err(_) => return false,
};
let output = std::process::Command::new("/usr/bin/codesign")
.args(["-d", "--entitlements", "-", "--xml"])
.arg(&exe)
.output();
match output {
Ok(o) => {
let stdout = String::from_utf8_lossy(&o.stdout);
let stderr = String::from_utf8_lossy(&o.stderr);
stdout.contains(group) || stderr.contains(group)
}
Err(_) => false,
}
}
#[derive(Debug, Clone)]
pub struct SecurityCapabilities {
pub binary_signed: bool,
pub backend: BackendKind,
pub effective_keychain_group: Option<String>,
pub code_signature_binding: bool,
pub keychain_user_presence: bool,
pub hardware_presence: bool,
pub presence_caching: bool,
pub effective_app_name: String,
pub downgraded_features: Vec<String>,
pub recommended_access_policy: AccessPolicy,
}
pub fn security_capabilities(app_name: &str) -> SecurityCapabilities {
let signed = is_binary_signed();
let effective_app_name = crate::internal::core::signing::ensure_safe_app_name(app_name);
let backend = detect_backend();
#[cfg(target_os = "macos")]
let hardware_presence = crate::internal::apple::touch_id_available();
#[cfg(target_os = "windows")]
let hardware_presence = true;
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
let hardware_presence = false;
let presence_caching = cfg!(target_os = "macos");
let recommended_access_policy = if signed {
AccessPolicy::None
} else {
AccessPolicy::Any
};
SecurityCapabilities {
binary_signed: signed,
backend,
effective_keychain_group: None,
code_signature_binding: false,
keychain_user_presence: false,
hardware_presence,
presence_caching,
effective_app_name,
downgraded_features: Vec::new(),
recommended_access_policy,
}
}
#[allow(clippy::needless_return, unreachable_code)]
fn detect_backend() -> BackendKind {
#[cfg(target_os = "macos")]
{
return BackendKind::SecureEnclave;
}
#[cfg(target_os = "windows")]
{
return BackendKind::Tpm;
}
#[cfg(target_os = "linux")]
{
if crate::internal::wsl::is_wsl() {
return BackendKind::TpmBridge;
}
return BackendKind::Keyring;
}
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
BackendKind::Keyring
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn is_binary_signed_returns_false_in_cargo_test() {
assert!(!is_binary_signed());
}
#[test]
fn has_keychain_entitlement_returns_false_for_unknown_group() {
assert!(!has_keychain_entitlement("com.example.nonexistent.group"));
}
#[test]
fn security_capabilities_does_not_panic() {
let caps = security_capabilities("testapp");
assert!(!caps.effective_app_name.is_empty());
assert!(
caps.effective_app_name.ends_with("-unsigned"),
"unsigned binary should have -unsigned suffix, got: {}",
caps.effective_app_name
);
assert!(!caps.binary_signed);
}
}