1use {
6 crate::{
7 cli::{CliCommand, Context},
8 code_requirement::CodeRequirements,
9 cryptography::DigestType,
10 error::{AppleCodesignError, Result},
11 },
12 clap::{Parser, ValueEnum},
13 log::warn,
14 std::{ops::Deref, path::PathBuf},
15};
16
17#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
18pub enum MachOArch {
19 Aarch64,
20 X86_64,
21}
22
23#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
24pub enum MachOFileType {
25 Executable,
26 Dylib,
27}
28
29impl MachOFileType {
30 fn to_header_filetype(&self) -> u32 {
31 match self {
32 Self::Executable => object::macho::MH_EXECUTE,
33 Self::Dylib => object::macho::MH_DYLIB,
34 }
35 }
36}
37
38#[derive(Parser)]
39pub struct DebugCreateCodeRequirements {
40 #[arg(long, value_enum)]
42 code_requirement: crate::policy::ExecutionPolicy,
43
44 path: PathBuf,
46}
47
48impl CliCommand for DebugCreateCodeRequirements {
49 fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> {
50 let expression = self.code_requirement.deref();
51
52 let mut reqs = CodeRequirements::default();
53 reqs.push(expression.clone());
54
55 let data = reqs.to_blob_data()?;
56
57 println!("writing code requirements to {}", self.path.display());
58
59 if let Some(parent) = self.path.parent() {
60 std::fs::create_dir_all(parent)?;
61 }
62
63 std::fs::write(&self.path, data)?;
64
65 Ok(())
66 }
67}
68
69#[derive(Parser)]
70pub struct DebugCreateConstraints {
71 #[arg(long)]
73 team_id: Option<String>,
74
75 path: PathBuf,
77}
78
79impl CliCommand for DebugCreateConstraints {
80 fn run(&self, _context: &Context) -> Result<()> {
81 let mut v = plist::Dictionary::default();
82
83 if let Some(id) = &self.team_id {
84 v.insert("team-identifier".into(), id.to_string().into());
85 }
86
87 let mut reqs = plist::Dictionary::default();
88
89 reqs.insert("$or".into(), v.into());
90
91 let v = plist::Value::Dictionary(reqs);
92
93 println!("writing constraints plist to {}", self.path.display());
94 v.to_file_xml(&self.path)?;
95
96 Ok(())
97 }
98}
99
100#[derive(Parser)]
101pub struct DebugCreateEntitlements {
102 #[arg(long)]
104 get_task_allow: bool,
105
106 #[arg(long)]
108 run_unsigned_code: bool,
109
110 #[arg(long)]
112 debugger: bool,
113
114 #[arg(long)]
116 dynamic_code_signing: bool,
117
118 #[arg(long)]
120 skip_library_validation: bool,
121
122 #[arg(long)]
124 can_load_cd_hash: bool,
125
126 #[arg(long)]
128 can_execute_cd_hash: bool,
129
130 output_path: PathBuf,
132}
133
134impl CliCommand for DebugCreateEntitlements {
135 fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> {
136 let mut d = plist::Dictionary::default();
137
138 if self.get_task_allow {
139 d.insert("get-task-allow".into(), true.into());
140 }
141 if self.run_unsigned_code {
142 d.insert("run-unsigned-code".into(), true.into());
143 }
144 if self.debugger {
145 d.insert("com.apple.private.cs.debugger".into(), true.into());
146 }
147 if self.dynamic_code_signing {
148 d.insert("dynamic-codesigning".into(), true.into());
149 }
150 if self.skip_library_validation {
151 d.insert(
152 "com.apple.private.skip-library-validation".into(),
153 true.into(),
154 );
155 }
156 if self.can_load_cd_hash {
157 d.insert("com.apple.private.amfi.can-load-cdhash".into(), true.into());
158 }
159 if self.can_execute_cd_hash {
160 d.insert(
161 "com.apple.private.amfi.can-execute-cdhash".into(),
162 true.into(),
163 );
164 }
165
166 let value = plist::Value::from(d);
167 let mut xml = vec![];
168 value.to_writer_xml(&mut xml)?;
169
170 warn!("writing {}", self.output_path.display());
171 if let Some(parent) = self.output_path.parent() {
172 std::fs::create_dir_all(parent)?;
173 }
174
175 std::fs::write(&self.output_path, &xml)?;
176
177 Ok(())
178 }
179}
180
181#[derive(Parser)]
182pub struct DebugCreateInfoPlist {
183 #[arg(long)]
185 bundle_name: String,
186
187 #[arg(long, default_value = "APPL")]
189 package_type: String,
190
191 #[arg(long)]
193 bundle_executable: Option<String>,
194
195 #[arg(long, default_value = "com.example.mybundle")]
197 bundle_identifier: String,
198
199 #[arg(long, default_value = "1.0.0")]
201 bundle_version: String,
202
203 output_path: PathBuf,
205
206 #[arg(long)]
208 empty: bool,
209}
210
211impl CliCommand for DebugCreateInfoPlist {
212 fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> {
213 let mut d = plist::Dictionary::default();
214
215 if !self.empty {
216 d.insert("CFBundleName".into(), self.bundle_name.clone().into());
217 d.insert(
218 "CFBundlePackageType".into(),
219 self.package_type.clone().into(),
220 );
221 d.insert(
222 "CFBundleDisplayName".into(),
223 self.bundle_name.clone().into(),
224 );
225 if let Some(exe) = &self.bundle_executable {
226 d.insert("CFBundleExecutable".into(), exe.clone().into());
227 }
228 d.insert(
229 "CFBundleIdentifier".into(),
230 self.bundle_identifier.clone().into(),
231 );
232 d.insert("CFBundleVersion".into(), self.bundle_version.clone().into());
233 d.insert("CFBundleSignature".into(), "sig".into());
234 d.insert("CFBundleExecutable".into(), self.bundle_name.clone().into());
235 }
236
237 let value = plist::Value::from(d);
238
239 let mut xml = vec![];
240 value.to_writer_xml(&mut xml)?;
241
242 println!("writing {}", self.output_path.display());
243 if let Some(parent) = self.output_path.parent() {
244 std::fs::create_dir_all(parent)?;
245 }
246
247 std::fs::write(&self.output_path, &xml)?;
248
249 Ok(())
250 }
251}
252
253#[derive(Parser)]
254pub struct DebugCreateMachO {
255 #[arg(long, value_enum, default_value_t = MachOArch::Aarch64)]
257 architecture: MachOArch,
258
259 #[arg(long, value_enum, default_value_t = MachOFileType::Executable)]
261 file_type: MachOFileType,
262
263 #[arg(long)]
265 no_targeting: bool,
266
267 #[arg(long)]
269 minimum_os_version: Option<semver::Version>,
270
271 #[arg(long)]
273 sdk_version: Option<semver::Version>,
274
275 #[arg(long)]
277 text_segment_start_offset: Option<usize>,
278
279 output_path: PathBuf,
281}
282
283impl CliCommand for DebugCreateMachO {
284 fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> {
285 let mut builder = match self.architecture {
286 MachOArch::Aarch64 => {
287 crate::macho_builder::MachOBuilder::new_aarch64(self.file_type.to_header_filetype())
288 }
289 MachOArch::X86_64 => {
290 crate::macho_builder::MachOBuilder::new_x86_64(self.file_type.to_header_filetype())
291 }
292 };
293
294 let target = match (
295 self.no_targeting,
296 &self.minimum_os_version,
297 &self.sdk_version,
298 ) {
299 (true, _, _) => None,
300 (false, None, None) => {
301 warn!("assuming default minimum version 11.0.0");
302
303 Some(crate::macho::MachoTarget {
304 platform: crate::Platform::MacOs,
305 minimum_os_version: semver::Version::new(11, 0, 0),
306 sdk_version: semver::Version::new(11, 0, 0),
307 })
308 }
309 (false, _, _) => {
310 let minimum_os_version = self
311 .minimum_os_version
312 .clone()
313 .unwrap_or_else(|| self.sdk_version.clone().unwrap());
314 let sdk_version = self
315 .sdk_version
316 .clone()
317 .unwrap_or_else(|| self.minimum_os_version.clone().unwrap());
318
319 Some(crate::macho::MachoTarget {
320 platform: crate::Platform::MacOs,
321 minimum_os_version,
322 sdk_version,
323 })
324 }
325 };
326
327 if let Some(target) = target {
328 builder = builder.macho_target(target);
329 }
330
331 if let Some(offset) = self.text_segment_start_offset {
332 builder = builder.text_segment_start_offset(offset);
333 }
334
335 let data = builder.write_macho()?;
336
337 warn!("writing Mach-O to {}", self.output_path.display());
338 if let Some(parent) = self.output_path.parent() {
339 std::fs::create_dir_all(parent)?;
340 }
341
342 std::fs::write(&self.output_path, data)?;
343
344 Ok(())
345 }
346}
347
348#[derive(Parser)]
349pub struct DebugFileTree {
350 path: PathBuf,
352}
353
354impl CliCommand for DebugFileTree {
355 fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> {
356 let root = self
357 .path
358 .components()
359 .last()
360 .expect("should have final component")
361 .as_os_str()
362 .to_string_lossy()
363 .to_string();
364
365 for entry in walkdir::WalkDir::new(&self.path).sort_by_file_name() {
366 let entry = entry?;
367
368 let path = entry.path();
369
370 let rel_path = if let Ok(p) = path.strip_prefix(&self.path) {
371 format!("{}/{}", root, p.to_string_lossy().replace('\\', "/"))
372 } else {
373 root.clone()
374 };
375
376 let metadata = entry.metadata()?;
377
378 let entry_type = if metadata.is_symlink() {
379 'l'
380 } else if metadata.is_dir() {
381 'd'
382 } else if metadata.is_file() {
383 'f'
384 } else {
385 'u'
386 };
387
388 let sha256 = if entry_type == 'f' {
389 let data = std::fs::read(path)?;
390 hex::encode(DigestType::Sha256.digest_data(&data)?)[0..20].to_string()
391 } else {
392 " ".repeat(20)
393 };
394
395 let link_target = if entry_type == 'l' {
396 format!(" -> {}", std::fs::read_link(path)?.to_string_lossy())
397 } else {
398 "".to_string()
399 };
400
401 println!("{} {} {}{}", entry_type, sha256, rel_path, link_target);
402 }
403
404 Ok(())
405 }
406}