use std::path::Path;
#[cfg(target_os = "macos")]
use std::fs;
#[cfg(target_os = "macos")]
use std::process::Command;
#[cfg(target_os = "macos")]
use crate::PackError;
use crate::Result;
#[cfg(target_os = "macos")]
const HYPERVISOR_ENTITLEMENTS: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.hypervisor</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
</dict>
</plist>
"#;
#[cfg(target_os = "macos")]
pub fn sign_with_hypervisor_entitlements(binary_path: &Path) -> Result<()> {
unsafe {
libc::signal(libc::SIGCHLD, libc::SIG_DFL);
}
let temp_dir = tempfile::tempdir()?;
let entitlements_path = temp_dir.path().join("entitlements.plist");
fs::write(&entitlements_path, HYPERVISOR_ENTITLEMENTS)?;
let output = Command::new("codesign")
.args([
"--force",
"--sign",
"-", "--entitlements",
])
.arg(&entitlements_path)
.arg(binary_path)
.output()?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(PackError::Signing(format!(
"codesign failed: {}",
stderr.trim()
)));
}
Ok(())
}
#[cfg(not(target_os = "macos"))]
pub fn sign_with_hypervisor_entitlements(_binary_path: &Path) -> Result<()> {
Ok(())
}
#[cfg(target_os = "macos")]
pub fn is_signed(binary_path: &Path) -> Result<bool> {
let output = Command::new("codesign")
.args(["--verify", "--verbose"])
.arg(binary_path)
.output()?;
Ok(output.status.success())
}
#[cfg(not(target_os = "macos"))]
pub fn is_signed(_binary_path: &Path) -> Result<bool> {
Ok(false)
}
#[cfg(target_os = "macos")]
pub fn get_signature_info(binary_path: &Path) -> Result<Option<SignatureInfo>> {
let output = Command::new("codesign")
.args(["--display", "--verbose=2"])
.arg(binary_path)
.output()?;
if !output.status.success() {
return Ok(None);
}
let stderr = String::from_utf8_lossy(&output.stderr);
let is_adhoc = stderr.contains("Signature=adhoc");
Ok(Some(SignatureInfo {
is_adhoc,
raw_output: stderr.to_string(),
}))
}
#[cfg(not(target_os = "macos"))]
pub fn get_signature_info(_binary_path: &Path) -> Result<Option<SignatureInfo>> {
Ok(None)
}
#[derive(Debug, Clone)]
pub struct SignatureInfo {
pub is_adhoc: bool,
pub raw_output: String,
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
#[test]
#[cfg(target_os = "macos")]
fn test_sign_binary() {
let temp_dir = tempfile::tempdir().unwrap();
let binary_path = temp_dir.path().join("test_binary");
let mut file = fs::File::create(&binary_path).unwrap();
let macho_header: [u8; 32] = [
0xCF, 0xFA, 0xED, 0xFE, 0x0C, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
file.write_all(&macho_header).unwrap();
drop(file);
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&binary_path).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(&binary_path, perms).unwrap();
}
let result = sign_with_hypervisor_entitlements(&binary_path);
if let Err(e) = &result {
eprintln!("Signing failed (expected for minimal test binary): {}", e);
}
}
#[test]
#[cfg(target_os = "macos")]
fn test_entitlements_format() {
assert!(HYPERVISOR_ENTITLEMENTS.contains("com.apple.security.hypervisor"));
assert!(HYPERVISOR_ENTITLEMENTS.contains("cs.disable-library-validation"));
assert!(HYPERVISOR_ENTITLEMENTS.contains("<true/>"));
}
}