security/code_signing/
mod.rs1use std::collections::BTreeMap;
4
5use apple_cf::CFError;
6
7use crate::error::{Result, SecurityError};
8use crate::ffi;
9use crate::private::{
10 cf_boolean_to_bool, cf_data_to_vec, cf_dictionary_entries, cf_dictionary_get_value,
11 cf_number_to_i64, cf_string_to_string, cf_type_id, sec_error_message, OwnedCf,
12};
13
14#[derive(Debug, Clone, PartialEq, Eq)]
16#[non_exhaustive]
17pub enum SigningValue {
18 Boolean(bool),
19 Integer(i64),
20 String(String),
21 Data(Vec<u8>),
22 Array(Vec<Self>),
23 Dictionary(BTreeMap<String, Self>),
24 Unknown(String),
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct SigningInformation {
30 pub identifier: Option<String>,
32 pub team_identifier: Option<String>,
34 pub entitlements: BTreeMap<String, SigningValue>,
36 pub sandboxed: bool,
38 pub status: Option<u32>,
40}
41
42impl SigningInformation {
43 #[must_use]
45 pub const fn is_signed(&self) -> bool {
46 self.identifier.is_some()
47 }
48}
49
50pub struct Code {
52 raw: ffi::SecCodeRef,
53}
54
55impl Code {
56 pub fn current() -> Result<Self> {
62 let mut raw = std::ptr::null();
63 let status = unsafe { ffi::SecCodeCopySelf(ffi::kSecCSDefaultFlags, &mut raw) };
64 if status != ffi::status::SUCCESS {
65 return Err(SecurityError::from_status(
66 "SecCodeCopySelf",
67 status,
68 sec_error_message(status),
69 ));
70 }
71 if raw.is_null() {
72 return Err(SecurityError::CoreFoundation(CFError::new(
73 "SecCodeCopySelf",
74 )));
75 }
76 Ok(Self { raw })
77 }
78
79 pub fn signing_information(&self) -> Result<SigningInformation> {
85 let mut info = std::ptr::null();
86 let flags = ffi::kSecCSSigningInformation | ffi::kSecCSDynamicInformation;
87 let status =
88 unsafe { ffi::SecCodeCopySigningInformation(self.raw.cast(), flags, &mut info) };
89 if status != ffi::status::SUCCESS {
90 return Err(SecurityError::from_status(
91 "SecCodeCopySigningInformation",
92 status,
93 sec_error_message(status),
94 ));
95 }
96 if info.is_null() {
97 return Err(SecurityError::CoreFoundation(CFError::new(
98 "SecCodeCopySigningInformation",
99 )));
100 }
101 let info = OwnedCf::new(info.cast());
102 if cf_type_id(info.as_ptr()) != unsafe { ffi::CFDictionaryGetTypeID() } {
103 return Err(SecurityError::UnexpectedType {
104 operation: "SecCodeCopySigningInformation",
105 expected: "CFDictionary",
106 });
107 }
108
109 let identifier = string_value(info.as_dictionary(), unsafe { ffi::kSecCodeInfoIdentifier });
110 let team_identifier =
111 string_value(info.as_dictionary(), unsafe { ffi::kSecCodeInfoTeamIdentifier });
112 let entitlements =
113 dictionary_value(info.as_dictionary(), unsafe { ffi::kSecCodeInfoEntitlementsDict })
114 .map(cf_dictionary_to_map)
115 .unwrap_or_default();
116 let sandboxed = matches!(
117 entitlements.get("com.apple.security.app-sandbox"),
118 Some(SigningValue::Boolean(true))
119 );
120 let status = cf_dictionary_get_value(info.as_dictionary(), unsafe { ffi::kSecCodeInfoStatus });
121 let status = cf_number_to_i64(status.cast()).and_then(|value| u32::try_from(value).ok());
122
123 Ok(SigningInformation {
124 identifier,
125 team_identifier,
126 entitlements,
127 sandboxed,
128 status,
129 })
130 }
131
132 #[must_use]
134 pub const fn as_raw(&self) -> ffi::SecCodeRef {
135 self.raw
136 }
137}
138
139impl Drop for Code {
140 fn drop(&mut self) {
141 if !self.raw.is_null() {
142 unsafe { ffi::CFRelease(self.raw.cast()) };
143 }
144 }
145}
146
147impl core::fmt::Debug for Code {
148 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
149 f.debug_struct("Code").field("raw", &self.raw).finish()
150 }
151}
152
153fn string_value(dictionary: ffi::CFDictionaryRef, key: ffi::CFStringRef) -> Option<String> {
154 let value = cf_dictionary_get_value(dictionary, key);
155 if value.is_null() || cf_type_id(value) != unsafe { ffi::CFStringGetTypeID() } {
156 return None;
157 }
158 cf_string_to_string(value.cast())
159}
160
161fn dictionary_value(
162 dictionary: ffi::CFDictionaryRef,
163 key: ffi::CFStringRef,
164) -> Option<ffi::CFDictionaryRef> {
165 let value = cf_dictionary_get_value(dictionary, key);
166 if value.is_null() || cf_type_id(value) != unsafe { ffi::CFDictionaryGetTypeID() } {
167 return None;
168 }
169 Some(value.cast())
170}
171
172fn cf_dictionary_to_map(dictionary: ffi::CFDictionaryRef) -> BTreeMap<String, SigningValue> {
173 cf_dictionary_entries(dictionary)
174 .into_iter()
175 .filter_map(|(key, value)| {
176 if key.is_null() || cf_type_id(key) != unsafe { ffi::CFStringGetTypeID() } {
177 return None;
178 }
179 let key = cf_string_to_string(key.cast())?;
180 Some((key, cf_type_to_value(value.cast())))
181 })
182 .collect()
183}
184
185fn cf_type_to_value(value: ffi::CFTypeRef) -> SigningValue {
186 if value.is_null() {
187 return SigningValue::Unknown("null".to_owned());
188 }
189
190 let type_id = cf_type_id(value);
191 if type_id == unsafe { ffi::CFBooleanGetTypeID() } {
192 return SigningValue::Boolean(cf_boolean_to_bool(value.cast()));
193 }
194 if type_id == unsafe { ffi::CFNumberGetTypeID() } {
195 return cf_number_to_i64(value.cast()).map_or_else(
196 || SigningValue::Unknown("number".to_owned()),
197 SigningValue::Integer,
198 );
199 }
200 if type_id == unsafe { ffi::CFStringGetTypeID() } {
201 return cf_string_to_string(value.cast()).map_or_else(
202 || SigningValue::Unknown("string".to_owned()),
203 SigningValue::String,
204 );
205 }
206 if type_id == unsafe { ffi::CFDataGetTypeID() } {
207 return SigningValue::Data(cf_data_to_vec(value.cast()));
208 }
209 if type_id == unsafe { ffi::CFArrayGetTypeID() } {
210 let count = unsafe { ffi::CFArrayGetCount(value.cast()) };
211 let count = usize::try_from(count).unwrap_or_default();
212 let values = (0..count)
213 .filter_map(|index| isize::try_from(index).ok())
214 .map(|index| unsafe { ffi::CFArrayGetValueAtIndex(value.cast(), index) })
215 .filter(|value| !value.is_null())
216 .map(|value| cf_type_to_value(value.cast()))
217 .collect();
218 return SigningValue::Array(values);
219 }
220 if type_id == unsafe { ffi::CFDictionaryGetTypeID() } {
221 return SigningValue::Dictionary(cf_dictionary_to_map(value.cast()));
222 }
223
224 SigningValue::Unknown(format!("CFTypeID({type_id})"))
225}