apple_security_framework/os/macos/
code_signing.rs1use 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 security_framework_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 #[derive(Debug, Clone)]
34 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 #[allow(deprecated, missing_docs)]
221 #[deprecated(note = "Renamed to `.copy_guest_with_attributes()`.")]
222 pub fn copy_guest_with_attribues(
223 host: Option<&SecCode>,
224 attrs: &GuestAttributes,
225 flags: Flags,
226 ) -> Result<SecCode> {
227 Self::copy_guest_with_attributes(host, attrs, flags)
228 }
229
230 pub fn copy_guest_with_attributes(
236 host: Option<&SecCode>,
237 attrs: &GuestAttributes,
238 flags: Flags,
239 ) -> Result<SecCode> {
240 let mut code = MaybeUninit::uninit();
241
242 let host = match host {
243 Some(host) => host.as_concrete_TypeRef(),
244 None => std::ptr::null_mut(),
245 };
246
247 unsafe {
248 cvt(SecCodeCopyGuestWithAttributes(
249 host,
250 attrs.inner.as_concrete_TypeRef(),
251 flags.bits(),
252 code.as_mut_ptr(),
253 ))?;
254
255 Ok(SecCode::wrap_under_create_rule(code.assume_init()))
256 }
257 }
258
259 pub fn path(&self, flags: Flags) -> Result<CFURL> {
262 let mut url = MaybeUninit::uninit();
263
264 unsafe {
266 cvt(SecCodeCopyPath(
267 self.as_CFTypeRef() as _,
268 flags.bits(),
269 url.as_mut_ptr(),
270 ))?;
271
272 Ok(CFURL::wrap_under_create_rule(url.assume_init()))
273 }
274 }
275}
276
277declare_TCFType! {
278 SecStaticCode, SecStaticCodeRef
280}
281impl_TCFType!(SecStaticCode, SecStaticCodeRef, SecStaticCodeGetTypeID);
282
283impl SecStaticCode {
284 pub fn from_path(path: &CFURL, flags: Flags) -> Result<Self> {
287 let mut code = MaybeUninit::uninit();
288
289 unsafe {
290 cvt(SecStaticCodeCreateWithPath(
291 path.as_concrete_TypeRef(),
292 flags.bits(),
293 code.as_mut_ptr(),
294 ))?;
295
296 Ok(Self::wrap_under_get_rule(code.assume_init()))
297 }
298 }
299
300 pub fn path(&self, flags: Flags) -> Result<CFURL> {
303 let mut url = MaybeUninit::uninit();
304
305 unsafe {
307 cvt(SecCodeCopyPath(
308 self.as_concrete_TypeRef(),
309 flags.bits(),
310 url.as_mut_ptr(),
311 ))?;
312
313 Ok(CFURL::wrap_under_create_rule(url.assume_init()))
314 }
315 }
316
317 pub fn check_validity(&self, flags: Flags, requirement: &SecRequirement) -> Result<()> {
319 unsafe {
320 cvt(SecStaticCodeCheckValidity(
321 self.as_concrete_TypeRef(),
322 flags.bits(),
323 requirement.as_concrete_TypeRef(),
324 ))
325 }
326 }
327}
328
329#[cfg(test)]
330mod test {
331 use core_foundation::data::CFData;
332 use libc::{c_uint, c_void, KERN_SUCCESS};
333
334 use super::*;
335
336 #[test]
337 fn path_to_static_code_and_back() {
338 let path = CFURL::from_path("/bin/bash", false).unwrap();
339 let code = SecStaticCode::from_path(&path, Flags::NONE).unwrap();
340 assert_eq!(code.path(Flags::NONE).unwrap(), path);
341 }
342
343 #[test]
344 fn self_to_path() {
345 let path = CFURL::from_path(std::env::current_exe().unwrap(), false).unwrap();
346 let code = SecCode::for_self(Flags::NONE).unwrap();
347 assert_eq!(code.path(Flags::NONE).unwrap(), path);
348 }
349
350 #[test]
351 fn bash_is_signed_by_apple() {
352 let path = CFURL::from_path("/bin/bash", false).unwrap();
353 let code = SecStaticCode::from_path(&path, Flags::NONE).unwrap();
354 let requirement: SecRequirement = "anchor apple".parse().unwrap();
355 code.check_validity(Flags::NONE, &requirement).unwrap();
356 }
357
358 #[cfg(target_arch = "aarch64")]
359 #[test]
360 fn self_is_not_signed_by_apple() {
361 let code = SecCode::for_self(Flags::NONE).unwrap();
362 let requirement: SecRequirement = "anchor apple".parse().unwrap();
363
364 assert_eq!(
365 code.check_validity(Flags::NONE, &requirement)
366 .unwrap_err()
367 .code(),
368 -67050
370 );
371 }
372
373 #[cfg(not(target_arch = "aarch64"))]
374 #[test]
375 fn self_is_not_signed_by_apple() {
376 let code = SecCode::for_self(Flags::NONE).unwrap();
377 let requirement: SecRequirement = "anchor apple".parse().unwrap();
378
379 assert_eq!(
380 code.check_validity(Flags::NONE, &requirement)
381 .unwrap_err()
382 .code(),
383 -67062
385 );
386 }
387
388 #[test]
389 fn copy_kernel_guest_with_launchd_pid() {
390 let mut attrs = GuestAttributes::new();
391 attrs.set_pid(1);
392
393 assert_eq!(
394 SecCode::copy_guest_with_attributes(None, &attrs, Flags::NONE)
395 .unwrap()
396 .path(Flags::NONE)
397 .unwrap()
398 .get_string()
399 .to_string(),
400 "file:///sbin/launchd"
401 );
402 }
403
404 #[test]
405 fn copy_current_guest_with_launchd_pid() {
406 let host_code = SecCode::for_self(Flags::NONE).unwrap();
407
408 let mut attrs = GuestAttributes::new();
409 attrs.set_pid(1);
410
411 assert_eq!(
412 SecCode::copy_guest_with_attributes(Some(&host_code), &attrs, Flags::NONE)
413 .unwrap_err()
414 .code(),
415 -67065
417 );
418 }
419
420 #[test]
421 fn copy_kernel_guest_with_unmatched_pid() {
422 let mut attrs = GuestAttributes::new();
423 attrs.set_pid(999_999_999);
424
425 assert_eq!(
426 SecCode::copy_guest_with_attributes(None, &attrs, Flags::NONE)
427 .unwrap_err()
428 .code(),
429 100003
431 );
432 }
433
434 #[test]
435 fn copy_kernel_guest_with_current_token() {
436 let mut token: [u8; 32] = [0; 32];
437 let mut token_len = 32u32;
438
439 enum OpaqueTaskName {}
440
441 extern "C" {
442 fn mach_task_self() -> *const OpaqueTaskName;
443 fn task_info(
444 task_name: *const OpaqueTaskName,
445 task_flavor: u32,
446 out: *mut c_void,
447 out_len: *mut u32,
448 ) -> i32;
449 }
450
451 const TASK_AUDIT_TOKEN: c_uint = 15;
452
453 let result = unsafe {
454 task_info(
455 mach_task_self(),
456 TASK_AUDIT_TOKEN,
457 token.as_mut_ptr() as *mut c_void,
458 &mut token_len,
459 )
460 };
461
462 assert_eq!(result, KERN_SUCCESS);
463
464 let token_data = CFData::from_buffer(&token);
465
466 let mut attrs = GuestAttributes::new();
467 attrs.set_audit_token(token_data.as_concrete_TypeRef());
468
469 assert_eq!(
470 SecCode::copy_guest_with_attributes(None, &attrs, Flags::NONE)
471 .unwrap()
472 .path(Flags::NONE)
473 .unwrap()
474 .to_path()
475 .unwrap(),
476 std::env::current_exe().unwrap()
477 );
478 }
479
480 #[test]
481 fn copy_kernel_guest_with_unmatched_token() {
482 let token: [u8; 32] = [0; 32];
483 let token_data = CFData::from_buffer(&token);
484
485 let mut attrs = GuestAttributes::new();
486 attrs.set_audit_token(token_data.as_concrete_TypeRef());
487
488 assert_eq!(
489 SecCode::copy_guest_with_attributes(None, &attrs, Flags::NONE)
490 .unwrap_err()
491 .code(),
492 100003
494 );
495 }
496}