apple_security_framework/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 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    /// Values that can be used in the flags parameter to most code signing
32    /// functions.
33    #[derive(Debug, Clone)]
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 arbitrary 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    #[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    /// Asks a code host to identify one of its guests given
231    /// the type and value of specific attributes of the guest code.
232    ///
233    /// If `host` is `None` then the code signing root of trust (currently, the
234    // system kernel) should be used as the code host.
235    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    /// Retrieves the location on disk of signed code, given a code or static
260    /// code object.
261    pub fn path(&self, flags: Flags) -> Result<CFURL> {
262        let mut url = MaybeUninit::uninit();
263
264        // The docs say we can pass a SecCodeRef instead of a SecStaticCodeRef.
265        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    /// A static code object representing signed code on disk.
279    SecStaticCode, SecStaticCodeRef
280}
281impl_TCFType!(SecStaticCode, SecStaticCodeRef, SecStaticCodeGetTypeID);
282
283impl SecStaticCode {
284    /// Creates a static code object representing the code at a specified file
285    /// system path.
286    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    /// Retrieves the location on disk of signed code, given a code or static
301    /// code object.
302    pub fn path(&self, flags: Flags) -> Result<CFURL> {
303        let mut url = MaybeUninit::uninit();
304
305        // The docs say we can pass a SecCodeRef instead of a SecStaticCodeRef.
306        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    /// Performs dynamic validation of signed code.
318    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            // "code failed to satisfy specified code requirement(s)"
369            -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            // "code object is not signed at all"
384            -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            // "host has no guest with the requested attributes"
416            -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            // "UNIX[No such process]"
430            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            // "UNIX[No such process]"
493            100003
494        );
495    }
496}