Skip to main content

verifyos_cli/parsers/
macho_parser.rs

1use 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 != &current_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}