1use std::{fmt::Debug, mem::MaybeUninit, str::FromStr};
4
5use core_foundation::{
6 base::{TCFType, TCFTypeRef, ToVoid},
7 data::CFDataRef,
8 dictionary::CFMutableDictionary,
9 number::CFNumber,
10 string::{CFString, CFStringRef},
11 url::CFURL,
12};
13use libc::pid_t;
14use apple_security_sys::code_signing::{
15 kSecCSBasicValidateOnly, kSecCSCheckAllArchitectures, kSecCSCheckGatekeeperArchitectures,
16 kSecCSCheckNestedCode, kSecCSCheckTrustedAnchors, kSecCSConsiderExpiration,
17 kSecCSDoNotValidateExecutable, kSecCSDoNotValidateResources, kSecCSEnforceRevocationChecks,
18 kSecCSFullReport, kSecCSNoNetworkAccess, kSecCSQuickCheck, kSecCSReportProgress,
19 kSecCSRestrictSidebandData, kSecCSRestrictSymlinks, kSecCSRestrictToAppLike,
20 kSecCSSingleThreaded, kSecCSStrictValidate, kSecCSUseSoftwareSigningCert, kSecCSValidatePEH,
21 kSecGuestAttributeAudit, kSecGuestAttributePid, SecCodeCheckValidity,
22 SecCodeCopyGuestWithAttributes, SecCodeCopyPath, SecCodeCopySelf, SecCodeGetTypeID, SecCodeRef,
23 SecRequirementCreateWithString, SecRequirementGetTypeID, SecRequirementRef,
24 SecStaticCodeCheckValidity, SecStaticCodeCreateWithPath, SecStaticCodeGetTypeID,
25 SecStaticCodeRef,
26};
27
28use crate::{cvt, Result};
29
30bitflags::bitflags! {
31
32 pub struct Flags: u32 {
35 const NONE = 0;
37
38 const CHECK_ALL_ARCHITECTURES = kSecCSCheckAllArchitectures;
41
42 const DO_NOT_VALIDATE_EXECUTABLE = kSecCSDoNotValidateExecutable;
44
45 const DO_NOT_VALIDATE_RESOURCES = kSecCSDoNotValidateResources;
48
49 const BASIC_VALIDATE_ONLY = kSecCSBasicValidateOnly;
52
53 const CHECK_NESTED_CODE = kSecCSCheckNestedCode;
55
56 const STRICT_VALIDATE = kSecCSStrictValidate;
59
60 const FULL_REPORT = kSecCSFullReport;
62
63 const CHECK_GATEKEEPER_ARCHITECTURES = kSecCSCheckGatekeeperArchitectures;
65
66 const RESTRICT_SYMLINKS = kSecCSRestrictSymlinks;
68
69 const RESTRICT_TO_APP_LIKE = kSecCSRestrictToAppLike;
71
72 const RESTRICT_SIDEBAND_DATA = kSecCSRestrictSidebandData;
74
75 const USE_SOFTWARE_SIGNING_CERT = kSecCSUseSoftwareSigningCert;
77
78 const VALIDATE_PEH = kSecCSValidatePEH;
80
81 const SINGLE_THREADED = kSecCSSingleThreaded;
83
84 const QUICK_CHECK = kSecCSQuickCheck;
86
87 const CHECK_TRUSTED_ANCHORS = kSecCSCheckTrustedAnchors;
89
90 const REPORT_PROGRESS = kSecCSReportProgress;
92
93 const NO_NETWORK_ACCESS = kSecCSNoNetworkAccess;
95
96 const ENFORCE_REVOCATION_CHECKS = kSecCSEnforceRevocationChecks;
98
99 const CONSIDER_EXPIRATION = kSecCSConsiderExpiration;
101 }
102}
103
104impl Default for Flags {
105 #[inline(always)]
106 fn default() -> Self {
107 Self::NONE
108 }
109}
110
111pub struct GuestAttributes {
114 inner: CFMutableDictionary,
115}
116
117impl GuestAttributes {
118 #[must_use]
130 pub fn new() -> Self {
131 Self {
132 inner: CFMutableDictionary::new(),
133 }
134 }
135
136 pub fn set_audit_token(&mut self, token: CFDataRef) {
138 let key = unsafe { CFString::wrap_under_get_rule(kSecGuestAttributeAudit) };
139 self.inner.add(&key.as_CFTypeRef(), &token.to_void());
140 }
141
142 pub fn set_pid(&mut self, pid: pid_t) {
144 let key = unsafe { CFString::wrap_under_get_rule(kSecGuestAttributePid) };
145 let pid = CFNumber::from(pid);
146 self.inner.add(&key.as_CFTypeRef(), &pid.as_CFTypeRef());
147 }
148
149 pub fn set_other<V: ToVoid<V>>(&mut self, key: CFStringRef, value: V) {
151 self.inner.add(&key.as_void_ptr(), &value.to_void());
152 }
153}
154
155impl Default for GuestAttributes {
156 fn default() -> Self {
157 Self::new()
158 }
159}
160
161declare_TCFType! {
162 SecRequirement, SecRequirementRef
164}
165impl_TCFType!(SecRequirement, SecRequirementRef, SecRequirementGetTypeID);
166
167impl FromStr for SecRequirement {
168 type Err = crate::base::Error;
169
170 fn from_str(s: &str) -> Result<Self, Self::Err> {
171 let text = CFString::new(s);
172 let mut requirement = MaybeUninit::uninit();
173
174 unsafe {
175 cvt(SecRequirementCreateWithString(
176 text.as_concrete_TypeRef(),
177 0,
178 requirement.as_mut_ptr(),
179 ))?;
180
181 Ok(Self::wrap_under_create_rule(requirement.assume_init()))
182 }
183 }
184}
185
186declare_TCFType! {
187 SecCode, SecCodeRef
189}
190impl_TCFType!(SecCode, SecCodeRef, SecCodeGetTypeID);
191
192impl Debug for SecCode {
193 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194 f.write_str("SecCode")
195 }
196}
197
198impl SecCode {
199 pub fn for_self(flags: Flags) -> Result<Self> {
201 let mut code = MaybeUninit::uninit();
202
203 unsafe {
204 cvt(SecCodeCopySelf(flags.bits(), code.as_mut_ptr()))?;
205 Ok(Self::wrap_under_create_rule(code.assume_init()))
206 }
207 }
208
209 pub fn check_validity(&self, flags: Flags, requirement: &SecRequirement) -> Result<()> {
211 unsafe {
212 cvt(SecCodeCheckValidity(
213 self.as_concrete_TypeRef(),
214 flags.bits(),
215 requirement.as_concrete_TypeRef(),
216 ))
217 }
218 }
219
220 pub fn copy_guest_with_attribues(
226 host: Option<&SecCode>,
227 attrs: &GuestAttributes,
228 flags: Flags,
229 ) -> Result<SecCode> {
230 let mut code = MaybeUninit::uninit();
231
232 let host = match host {
233 Some(host) => host.as_concrete_TypeRef(),
234 None => std::ptr::null_mut(),
235 };
236
237 unsafe {
238 cvt(SecCodeCopyGuestWithAttributes(
239 host,
240 attrs.inner.as_concrete_TypeRef(),
241 flags.bits(),
242 code.as_mut_ptr(),
243 ))?;
244
245 Ok(SecCode::wrap_under_create_rule(code.assume_init()))
246 }
247 }
248
249 pub fn path(&self, flags: Flags) -> Result<CFURL> {
252 let mut url = MaybeUninit::uninit();
253
254 unsafe {
256 cvt(SecCodeCopyPath(
257 self.as_CFTypeRef() as _,
258 flags.bits(),
259 url.as_mut_ptr(),
260 ))?;
261
262 Ok(CFURL::wrap_under_create_rule(url.assume_init()))
263 }
264 }
265}
266
267declare_TCFType! {
268 SecStaticCode, SecStaticCodeRef
270}
271impl_TCFType!(SecStaticCode, SecStaticCodeRef, SecStaticCodeGetTypeID);
272
273impl SecStaticCode {
274 pub fn from_path(path: &CFURL, flags: Flags) -> Result<Self> {
277 let mut code = MaybeUninit::uninit();
278
279 unsafe {
280 cvt(SecStaticCodeCreateWithPath(
281 path.as_concrete_TypeRef(),
282 flags.bits(),
283 code.as_mut_ptr(),
284 ))?;
285
286 Ok(Self::wrap_under_get_rule(code.assume_init()))
287 }
288 }
289
290 pub fn path(&self, flags: Flags) -> Result<CFURL> {
293 let mut url = MaybeUninit::uninit();
294
295 unsafe {
297 cvt(SecCodeCopyPath(
298 self.as_concrete_TypeRef(),
299 flags.bits(),
300 url.as_mut_ptr(),
301 ))?;
302
303 Ok(CFURL::wrap_under_create_rule(url.assume_init()))
304 }
305 }
306
307 pub fn check_validity(&self, flags: Flags, requirement: &SecRequirement) -> Result<()> {
309 unsafe {
310 cvt(SecStaticCodeCheckValidity(
311 self.as_concrete_TypeRef(),
312 flags.bits(),
313 requirement.as_concrete_TypeRef(),
314 ))
315 }
316 }
317}
318
319#[cfg(test)]
320mod test {
321 use super::*;
322 use core_foundation::data::CFData;
323 use libc::{c_uint, c_void, KERN_SUCCESS};
324
325 #[test]
326 fn path_to_static_code_and_back() {
327 let path = CFURL::from_path("/bin/bash", false).unwrap();
328 let code = SecStaticCode::from_path(&path, Flags::NONE).unwrap();
329 assert_eq!(code.path(Flags::NONE).unwrap(), path);
330 }
331
332 #[test]
333 fn self_to_path() {
334 let path = CFURL::from_path(std::env::current_exe().unwrap(), false).unwrap();
335 let code = SecCode::for_self(Flags::NONE).unwrap();
336 assert_eq!(code.path(Flags::NONE).unwrap(), path);
337 }
338
339 #[test]
340 fn bash_is_signed_by_apple() {
341 let path = CFURL::from_path("/bin/bash", false).unwrap();
342 let code = SecStaticCode::from_path(&path, Flags::NONE).unwrap();
343 let requirement: SecRequirement = "anchor apple".parse().unwrap();
344 code.check_validity(Flags::NONE, &requirement).unwrap();
345 }
346
347 #[cfg(target_arch = "aarch64")]
348 #[test]
349 fn self_is_not_signed_by_apple() {
350 let code = SecCode::for_self(Flags::NONE).unwrap();
351 let requirement: SecRequirement = "anchor apple".parse().unwrap();
352
353 assert_eq!(
354 code.check_validity(Flags::NONE, &requirement)
355 .unwrap_err()
356 .code(),
357 -67050
359 );
360 }
361
362 #[cfg(not(target_arch = "aarch64"))]
363 #[test]
364 fn self_is_not_signed_by_apple() {
365 let code = SecCode::for_self(Flags::NONE).unwrap();
366 let requirement: SecRequirement = "anchor apple".parse().unwrap();
367
368 assert_eq!(
369 code.check_validity(Flags::NONE, &requirement)
370 .unwrap_err()
371 .code(),
372 -67062
374 );
375 }
376
377 #[test]
378 fn copy_kernel_guest_with_launchd_pid() {
379 let mut attrs = GuestAttributes::new();
380 attrs.set_pid(1);
381
382 assert_eq!(
383 SecCode::copy_guest_with_attribues(None, &attrs, Flags::NONE)
384 .unwrap()
385 .path(Flags::NONE)
386 .unwrap()
387 .get_string()
388 .to_string(),
389 "file:///sbin/launchd"
390 );
391 }
392
393 #[test]
394 fn copy_current_guest_with_launchd_pid() {
395 let host_code = SecCode::for_self(Flags::NONE).unwrap();
396
397 let mut attrs = GuestAttributes::new();
398 attrs.set_pid(1);
399
400 assert_eq!(
401 SecCode::copy_guest_with_attribues(Some(&host_code), &attrs, Flags::NONE)
402 .unwrap_err()
403 .code(),
404 -67065
406 );
407 }
408
409 #[test]
410 fn copy_kernel_guest_with_unmatched_pid() {
411 let mut attrs = GuestAttributes::new();
412 attrs.set_pid(999_999_999);
413
414 assert_eq!(
415 SecCode::copy_guest_with_attribues(None, &attrs, Flags::NONE)
416 .unwrap_err()
417 .code(),
418 100003
420 );
421 }
422
423 #[test]
424 fn copy_kernel_guest_with_current_token() {
425 let mut token: [u8; 32] = [0; 32];
426 let mut token_len = 32u32;
427
428 enum OpaqueTaskName {}
429
430 extern "C" {
431 fn mach_task_self() -> *const OpaqueTaskName;
432 fn task_info(
433 task_name: *const OpaqueTaskName,
434 task_flavor: u32,
435 out: *mut c_void,
436 out_len: *mut u32,
437 ) -> i32;
438 }
439
440 const TASK_AUDIT_TOKEN: c_uint = 15;
441
442 let result = unsafe {
443 task_info(
444 mach_task_self(),
445 TASK_AUDIT_TOKEN,
446 token.as_mut_ptr() as *mut c_void,
447 &mut token_len,
448 )
449 };
450
451 assert_eq!(result, KERN_SUCCESS);
452
453 let token_data = CFData::from_buffer(&token);
454
455 let mut attrs = GuestAttributes::new();
456 attrs.set_audit_token(token_data.as_concrete_TypeRef());
457
458 assert_eq!(
459 SecCode::copy_guest_with_attribues(None, &attrs, Flags::NONE)
460 .unwrap()
461 .path(Flags::NONE)
462 .unwrap()
463 .to_path()
464 .unwrap(),
465 std::env::current_exe().unwrap()
466 );
467 }
468
469 #[test]
470 fn copy_kernel_guest_with_unmatched_token() {
471 let token: [u8; 32] = [0; 32];
472 let token_data = CFData::from_buffer(&token);
473
474 let mut attrs = GuestAttributes::new();
475 attrs.set_audit_token(token_data.as_concrete_TypeRef());
476
477 assert_eq!(
478 SecCode::copy_guest_with_attribues(None, &attrs, Flags::NONE)
479 .unwrap_err()
480 .code(),
481 100003
483 );
484 }
485}