apple_security/os/macos/
code_signing.rs

1//! Code signing services.
2
3use 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    /// Values that can be used in the flags parameter to most code signing
33    /// functions.
34    pub struct Flags: u32 {
35        /// Use the default behaviour.
36        const NONE = 0;
37
38        /// For multi-architecture (universal) Mach-O programs, validate all
39        /// architectures included.
40        const CHECK_ALL_ARCHITECTURES = kSecCSCheckAllArchitectures;
41
42        /// Do not validate the contents of the main executable.
43        const DO_NOT_VALIDATE_EXECUTABLE = kSecCSDoNotValidateExecutable;
44
45        /// Do not validate the presence and contents of all bundle resources
46        /// if any.
47        const DO_NOT_VALIDATE_RESOURCES = kSecCSDoNotValidateResources;
48
49        /// Do not validate either the main executable or the bundle resources,
50        /// if any.
51        const BASIC_VALIDATE_ONLY = kSecCSBasicValidateOnly;
52
53        /// For code in bundle form, locate and recursively check embedded code.
54        const CHECK_NESTED_CODE = kSecCSCheckNestedCode;
55
56        /// Perform additional checks to ensure the validity of code in bundle
57        /// form.
58        const STRICT_VALIDATE = kSecCSStrictValidate;
59
60        /// Apple have not documented this flag.
61        const FULL_REPORT = kSecCSFullReport;
62
63        /// Apple have not documented this flag.
64        const CHECK_GATEKEEPER_ARCHITECTURES = kSecCSCheckGatekeeperArchitectures;
65
66        /// Apple have not documented this flag.
67        const RESTRICT_SYMLINKS = kSecCSRestrictSymlinks;
68
69        /// Apple have not documented this flag.
70        const RESTRICT_TO_APP_LIKE = kSecCSRestrictToAppLike;
71
72        /// Apple have not documented this flag.
73        const RESTRICT_SIDEBAND_DATA = kSecCSRestrictSidebandData;
74
75        /// Apple have not documented this flag.
76        const USE_SOFTWARE_SIGNING_CERT = kSecCSUseSoftwareSigningCert;
77
78        /// Apple have not documented this flag.
79        const VALIDATE_PEH = kSecCSValidatePEH;
80
81        /// Apple have not documented this flag.
82        const SINGLE_THREADED = kSecCSSingleThreaded;
83
84        /// Apple have not documented this flag.
85        const QUICK_CHECK = kSecCSQuickCheck;
86
87        /// Apple have not documented this flag.
88        const CHECK_TRUSTED_ANCHORS = kSecCSCheckTrustedAnchors;
89
90        /// Apple have not documented this flag.
91        const REPORT_PROGRESS = kSecCSReportProgress;
92
93        /// Apple have not documented this flag.
94        const NO_NETWORK_ACCESS = kSecCSNoNetworkAccess;
95
96        /// Apple have not documented this flag.
97        const ENFORCE_REVOCATION_CHECKS = kSecCSEnforceRevocationChecks;
98
99        /// Apple have not documented this flag.
100        const CONSIDER_EXPIRATION = kSecCSConsiderExpiration;
101    }
102}
103
104impl Default for Flags {
105    #[inline(always)]
106    fn default() -> Self {
107        Self::NONE
108    }
109}
110
111/// A helper to create guest attributes, which are normally passed as a
112/// `CFDictionary` with varying types.
113pub struct GuestAttributes {
114    inner: CFMutableDictionary,
115}
116
117impl GuestAttributes {
118    // Not implemented:
119    // - architecture
120    // - canonical
121    // - dynamic code
122    // - dynamic code info plist
123    // - hash
124    // - mach port
125    // - sub-architecture
126
127    /// Creates a new, empty `GuestAttributes`. You must add values to it in
128    /// order for it to be of any use.
129    #[must_use]
130    pub fn new() -> Self {
131        Self {
132            inner: CFMutableDictionary::new(),
133        }
134    }
135
136    /// The guest's audit token.
137    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    /// The guest's pid.
143    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    /// Support for arbirtary guest attributes.
150    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    /// A code object representing signed code running on the system.
163    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    /// A code object representing signed code running on the system.
188    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    /// Retrieves the code object for the code making the call.
200    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    /// Performs dynamic validation of signed code.
210    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    /// Asks a code host to identify one of its guests given
221    /// the type and value of specific attributes of the guest code.
222    ///
223    /// If `host` is `None` then the code signing root of trust (currently, the
224    // system kernel) should be used as the code host.
225    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    /// Retrieves the location on disk of signed code, given a code or static
250    /// code object.
251    pub fn path(&self, flags: Flags) -> Result<CFURL> {
252        let mut url = MaybeUninit::uninit();
253
254        // The docs say we can pass a SecCodeRef instead of a SecStaticCodeRef.
255        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    /// A static code object representing signed code on disk.
269    SecStaticCode, SecStaticCodeRef
270}
271impl_TCFType!(SecStaticCode, SecStaticCodeRef, SecStaticCodeGetTypeID);
272
273impl SecStaticCode {
274    /// Creates a static code object representing the code at a specified file
275    /// system path.
276    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    /// Retrieves the location on disk of signed code, given a code or static
291    /// code object.
292    pub fn path(&self, flags: Flags) -> Result<CFURL> {
293        let mut url = MaybeUninit::uninit();
294
295        // The docs say we can pass a SecCodeRef instead of a SecStaticCodeRef.
296        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    /// Performs dynamic validation of signed code.
308    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            // "code failed to satisfy specified code requirement(s)"
358            -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            // "code object is not signed at all"
373            -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            // "host has no guest with the requested attributes"
405            -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            // "UNIX[No such process]"
419            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            // "UNIX[No such process]"
482            100003
483        );
484    }
485}