codesign_verify/macos/
mod.rs

1mod context;
2#[allow(non_upper_case_globals)]
3mod sec_sys;
4
5use super::Error;
6use sec_sys::*;
7
8pub(crate) struct Verifier(SecCodeKind);
9pub(crate) use context::Context;
10
11#[derive(Debug)]
12enum SecCodeKind {
13    Static(SecStaticCode), // Static code is created for files on disk
14    Dynamic(SecCode),      // Regular code is created for a guest pid
15}
16
17impl Verifier {
18    /// Retrieve the code object for the process with the given pid
19    pub fn for_pid(pid: i32) -> Result<Self, Error> {
20        let mut sec: SecCodeRef = std::ptr::null_mut();
21
22        let attributes = unsafe {
23            CFDictionary::from_CFType_pairs(&[(
24                CFString::wrap_under_get_rule(kSecGuestAttributePid),
25                CFNumber::from(pid),
26            )])
27        };
28
29        unsafe {
30            match SecCodeCopyGuestWithAttributes(
31                std::ptr::null_mut(),
32                attributes.as_concrete_TypeRef(),
33                SecCSFlags::kSecCSDefaultFlags,
34                Some(&mut sec),
35            ) {
36                sec_sys::errSecSuccess if !sec.is_null() => Ok(Verifier(SecCodeKind::Dynamic(
37                    SecCode::wrap_under_create_rule(sec),
38                ))),
39                err => Err(Error::OsError(err)),
40            }
41        }
42    }
43
44    /// Retrieve the code object for the file at the target location
45    pub fn for_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self, Error> {
46        let mut sec: SecStaticCodeRef = std::ptr::null_mut();
47        let url = CFURL::from_path(path.as_ref(), false).ok_or(Error::InvalidPath)?;
48
49        unsafe {
50            match SecStaticCodeCreateWithPath(
51                url.as_concrete_TypeRef(),
52                SecCSFlags::kSecCSDefaultFlags,
53                Some(&mut sec),
54            ) {
55                sec_sys::errSecSuccess if !sec.is_null() => Ok(Verifier(SecCodeKind::Static(
56                    SecStaticCode::wrap_under_create_rule(sec),
57                ))),
58                err => Err(Error::OsError(err)),
59            }
60        }
61    }
62
63    pub fn verify(&self) -> Result<Context, Error> {
64        self.check_validity("anchor trusted")?; // This is the most generic verification
65        let sec_info = self.get_code_singing_info()?;
66        let cert_key = unsafe { CFString::wrap_under_get_rule(kSecCodeInfoCertificates) };
67
68        let certs_ref = sec_info
69            .find(cert_key.as_CFTypeRef())
70            .ok_or(Error::LeafCertNotFound)?;
71
72        let certs = unsafe { CFArray::<SecCertificate>::wrap_under_get_rule(*certs_ref as _) };
73        let leaf_cert = certs.get(0).ok_or(Error::LeafCertNotFound)?;
74
75        Ok(Context::new(leaf_cert.as_concrete_TypeRef()))
76    }
77
78    /// Retreive a dictionary of various pieces of information from a code signature.
79    fn get_code_singing_info(&self) -> Result<CFDictionary, Error> {
80        let mut dict: CFDictionaryRef = std::ptr::null_mut();
81
82        let sec = match &self.0 {
83            SecCodeKind::Static(sec) => sec.as_concrete_TypeRef(),
84            SecCodeKind::Dynamic(sec) => sec.as_CFTypeRef() as _, // Dynamic will be implicitly converted to static
85        };
86
87        unsafe {
88            match SecCodeCopySigningInformation(
89                sec,
90                SecCSFlags::kSecCSSigningInformation,
91                Some(&mut dict),
92            ) {
93                sec_sys::errSecSuccess if !dict.is_null() => {
94                    Ok(CFDictionary::wrap_under_create_rule(dict))
95                }
96                err => Err(Error::OsError(err)),
97            }
98        }
99    }
100
101    fn check_validity(&self, requirement: &str) -> Result<(), Error> {
102        let mut req: SecRequirementRef = std::ptr::null_mut();
103        let mut err: CFErrorRef = std::ptr::null_mut();
104
105        // Generate a new requirement object using the Apple [Code Signing Requirement Language](https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/RequirementLang/RequirementLang.html#//apple_ref/doc/uid/TP40005929-CH5-SW1)
106        let req = unsafe {
107            match SecRequirementCreateWithStringAndErrors(
108                CFString::new(requirement).as_concrete_TypeRef(),
109                SecCSFlags::kSecCSDefaultFlags,
110                Some(&mut err),
111                Some(&mut req),
112            ) {
113                sec_sys::errSecSuccess if !req.is_null() => {
114                    SecRequirement::wrap_under_create_rule(req)
115                }
116                status => {
117                    if !err.is_null() {
118                        return Err(err.into());
119                    } else {
120                        return Err(Error::OsError(status));
121                    }
122                }
123            }
124        };
125
126        let status = match &self.0 {
127            SecCodeKind::Static(sec) => unsafe {
128                SecStaticCodeCheckValidityWithErrors(
129                    sec.as_concrete_TypeRef(),
130                    SecCSFlags::kSecCSDefaultFlags,
131                    req.as_concrete_TypeRef(),
132                    Some(&mut err),
133                )
134            },
135            SecCodeKind::Dynamic(sec) => unsafe {
136                SecCodeCheckValidityWithErrors(
137                    sec.as_concrete_TypeRef(),
138                    SecCSFlags::kSecCSDefaultFlags,
139                    req.as_concrete_TypeRef(),
140                    Some(&mut err),
141                )
142            },
143        };
144
145        match status {
146            sec_sys::errSecSuccess => Ok(()),
147            sec_sys::errSecCSUnsigned => Err(Error::Unsigned),
148            status => {
149                if !err.is_null() {
150                    Err(err.into())
151                } else {
152                    Err(Error::OsError(status))
153                }
154            }
155        }
156    }
157}
158
159#[allow(clippy::not_unsafe_ptr_arg_deref)]
160impl From<CFErrorRef> for Error {
161    fn from(err: CFErrorRef) -> Self {
162        if err.is_null() {
163            panic!()
164        }
165
166        unsafe {
167            let err = CFError::wrap_under_get_rule(err);
168
169            let err_dict = CFDictionary::<CFString, CFString>::wrap_under_create_rule(
170                CFErrorCopyUserInfo(err.as_concrete_TypeRef()),
171            );
172
173            if err.code() == -67050 {
174                // code failed to satisfy specified code requirement(s)
175                // in this case code could be unsigned, so check for that
176
177                if err_dict.len() == 1
178                    && err_dict.contains_key(&CFString::from_static_string("SecCSArchitecture"))
179                {
180                    return Error::Unsigned;
181                }
182            }
183
184            Error::CFError(format!("{err_dict:?}"))
185        }
186    }
187}