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