1use std::path::Path;
7
8#[cfg(target_os = "macos")]
9use std::fs;
10#[cfg(target_os = "macos")]
11use std::process::Command;
12
13#[cfg(target_os = "macos")]
14use crate::PackError;
15use crate::Result;
16
17#[cfg(target_os = "macos")]
28const HYPERVISOR_ENTITLEMENTS: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
29<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
30<plist version="1.0">
31<dict>
32 <key>com.apple.security.hypervisor</key>
33 <true/>
34 <key>com.apple.security.cs.disable-library-validation</key>
35 <true/>
36 <key>com.apple.security.cs.allow-jit</key>
37 <true/>
38</dict>
39</plist>
40"#;
41
42#[cfg(target_os = "macos")]
47pub fn sign_with_hypervisor_entitlements(binary_path: &Path) -> Result<()> {
48 unsafe {
53 libc::signal(libc::SIGCHLD, libc::SIG_DFL);
54 }
55
56 let temp_dir = tempfile::tempdir()?;
58 let entitlements_path = temp_dir.path().join("entitlements.plist");
59 fs::write(&entitlements_path, HYPERVISOR_ENTITLEMENTS)?;
60
61 let output = Command::new("codesign")
63 .args([
64 "--force",
65 "--sign",
66 "-", "--entitlements",
68 ])
69 .arg(&entitlements_path)
70 .arg(binary_path)
71 .output()?;
72
73 if !output.status.success() {
74 let stderr = String::from_utf8_lossy(&output.stderr);
75 return Err(PackError::Signing(format!(
76 "codesign failed: {}",
77 stderr.trim()
78 )));
79 }
80
81 Ok(())
82}
83
84#[cfg(not(target_os = "macos"))]
86pub fn sign_with_hypervisor_entitlements(_binary_path: &Path) -> Result<()> {
87 Ok(())
89}
90
91#[cfg(target_os = "macos")]
93pub fn is_signed(binary_path: &Path) -> Result<bool> {
94 let output = Command::new("codesign")
95 .args(["--verify", "--verbose"])
96 .arg(binary_path)
97 .output()?;
98
99 Ok(output.status.success())
100}
101
102#[cfg(not(target_os = "macos"))]
104pub fn is_signed(_binary_path: &Path) -> Result<bool> {
105 Ok(false)
106}
107
108#[cfg(target_os = "macos")]
110pub fn get_signature_info(binary_path: &Path) -> Result<Option<SignatureInfo>> {
111 let output = Command::new("codesign")
112 .args(["--display", "--verbose=2"])
113 .arg(binary_path)
114 .output()?;
115
116 if !output.status.success() {
117 return Ok(None);
118 }
119
120 let stderr = String::from_utf8_lossy(&output.stderr);
121 let is_adhoc = stderr.contains("Signature=adhoc");
122
123 Ok(Some(SignatureInfo {
124 is_adhoc,
125 raw_output: stderr.to_string(),
126 }))
127}
128
129#[cfg(not(target_os = "macos"))]
131pub fn get_signature_info(_binary_path: &Path) -> Result<Option<SignatureInfo>> {
132 Ok(None)
133}
134
135#[derive(Debug, Clone)]
137pub struct SignatureInfo {
138 pub is_adhoc: bool,
140 pub raw_output: String,
142}
143
144#[cfg(all(test, target_os = "macos"))]
147mod tests {
148 use super::*;
149 use std::io::Write;
150
151 #[test]
152 fn test_sign_binary() {
153 let temp_dir = tempfile::tempdir().unwrap();
154 let binary_path = temp_dir.path().join("test_binary");
155
156 let mut file = fs::File::create(&binary_path).unwrap();
159
160 let macho_header: [u8; 32] = [
162 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, ];
171 file.write_all(&macho_header).unwrap();
172 drop(file);
173
174 #[cfg(unix)]
176 {
177 use std::os::unix::fs::PermissionsExt;
178 let mut perms = fs::metadata(&binary_path).unwrap().permissions();
179 perms.set_mode(0o755);
180 fs::set_permissions(&binary_path, perms).unwrap();
181 }
182
183 let result = sign_with_hypervisor_entitlements(&binary_path);
185 if let Err(e) = &result {
188 eprintln!("Signing failed (expected for minimal test binary): {}", e);
189 }
190 }
191
192 #[test]
193 fn test_entitlements_format() {
194 assert!(HYPERVISOR_ENTITLEMENTS.contains("com.apple.security.hypervisor"));
196 assert!(HYPERVISOR_ENTITLEMENTS.contains("cs.disable-library-validation"));
197 assert!(HYPERVISOR_ENTITLEMENTS.contains("<true/>"));
198 }
199}