security_framework/os/macos/
code_signing.rs

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