verifyos_cli/parsers/
macho_parser.rs1use apple_codesign::{CodeSignature, MachFile, SignatureEntity, SignatureReader};
2use std::path::Path;
3
4#[derive(Debug, thiserror::Error, miette::Diagnostic)]
5pub enum MachOError {
6 #[error("Failed to parse Mach-O file: {0}")]
7 ParseError(#[from] Box<apple_codesign::AppleCodesignError>),
8 #[error("Failed to read code signature data: {0}")]
9 SignatureRead(#[source] Box<apple_codesign::AppleCodesignError>),
10 #[error("No code signature found")]
11 NoSignature,
12 #[error("Entitlements differ across Mach-O architecture slices")]
13 MismatchedEntitlements,
14 #[error("Team identifiers differ across Mach-O architecture slices")]
15 MismatchedTeamId,
16}
17
18pub struct MachOExecutable {
19 pub entitlements: Option<String>,
20}
21
22#[derive(Debug, Clone)]
23pub struct MachOSignatureSummary {
24 pub team_id: Option<String>,
25 pub signed_slices: usize,
26 pub total_slices: usize,
27}
28
29impl MachOExecutable {
30 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, MachOError> {
31 let file_data =
32 std::fs::read(path).map_err(|e| Box::new(apple_codesign::AppleCodesignError::Io(e)))?;
33 let mach_file = MachFile::parse(&file_data).map_err(Box::new)?;
34
35 let mut first_entitlements: Option<Option<String>> = None;
36
37 for macho in mach_file.iter_macho() {
38 let mut current_ent = None;
39 if let Some(sig) = macho.code_signature().map_err(Box::new)? {
40 if let Some(ent) = sig.entitlements().map_err(Box::new)? {
41 current_ent = Some(ent.as_str().to_string());
42 }
43 }
44
45 match &first_entitlements {
46 None => {
47 first_entitlements = Some(current_ent);
48 }
49 Some(first) => {
50 if first != ¤t_ent {
51 return Err(MachOError::MismatchedEntitlements);
52 }
53 }
54 }
55 }
56
57 Ok(Self {
58 entitlements: first_entitlements.flatten(),
59 })
60 }
61}
62
63pub fn read_macho_signature_summary<P: AsRef<Path>>(
64 path: P,
65) -> Result<MachOSignatureSummary, MachOError> {
66 let reader = SignatureReader::from_path(path.as_ref())
67 .map_err(|e| MachOError::SignatureRead(Box::new(e)))?;
68 let entities = reader
69 .entities()
70 .map_err(|e| MachOError::SignatureRead(Box::new(e)))?;
71
72 let mut team_ids = std::collections::BTreeSet::new();
73 let mut total_slices = 0usize;
74 let mut signed_slices = 0usize;
75
76 for entity in entities {
77 if let SignatureEntity::MachO(macho) = entity.entity {
78 total_slices += 1;
79 if let Some(signature) = macho.signature {
80 signed_slices += 1;
81 if let Some(team_id) = extract_team_id(&signature) {
82 team_ids.insert(team_id);
83 }
84 }
85 }
86 }
87
88 if team_ids.len() > 1 {
89 return Err(MachOError::MismatchedTeamId);
90 }
91
92 Ok(MachOSignatureSummary {
93 team_id: team_ids.into_iter().next(),
94 signed_slices,
95 total_slices,
96 })
97}
98
99fn extract_team_id(signature: &CodeSignature) -> Option<String> {
100 if let Some(code_directory) = &signature.code_directory {
101 if let Some(team_id) = &code_directory.team_name {
102 return Some(team_id.clone());
103 }
104 }
105
106 if let Some(cms) = &signature.cms {
107 for cert in &cms.certificates {
108 if let Some(team_id) = &cert.apple_team_id {
109 return Some(team_id.clone());
110 }
111 }
112 }
113
114 None
115}