summaryrefslogtreecommitdiff
path: root/vendor/security-framework/src/os/macos/code_signing.rs
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-07-10 13:11:11 -0600
committermo khan <mo@mokhan.ca>2025-07-10 13:11:11 -0600
commit01959b16a21b22b5df5f16569c2a8e8f92beecef (patch)
tree32afa5d747c5466345c59ec52161a7cba3d6d755 /vendor/security-framework/src/os/macos/code_signing.rs
parentff30574117a996df332e23d1fb6f65259b316b5b (diff)
chore: vendor dependencies
Diffstat (limited to 'vendor/security-framework/src/os/macos/code_signing.rs')
-rw-r--r--vendor/security-framework/src/os/macos/code_signing.rs486
1 files changed, 486 insertions, 0 deletions
diff --git a/vendor/security-framework/src/os/macos/code_signing.rs b/vendor/security-framework/src/os/macos/code_signing.rs
new file mode 100644
index 00000000..b42917c1
--- /dev/null
+++ b/vendor/security-framework/src/os/macos/code_signing.rs
@@ -0,0 +1,486 @@
+//! Code signing services.
+
+use std::{fmt::Debug, mem::MaybeUninit, str::FromStr};
+
+use core_foundation::{
+ base::{TCFType, TCFTypeRef, ToVoid},
+ data::CFDataRef,
+ dictionary::CFMutableDictionary,
+ number::CFNumber,
+ string::{CFString, CFStringRef},
+ url::CFURL,
+};
+use libc::pid_t;
+use security_framework_sys::code_signing::{
+ kSecCSBasicValidateOnly, kSecCSCheckAllArchitectures, kSecCSCheckGatekeeperArchitectures,
+ kSecCSCheckNestedCode, kSecCSCheckTrustedAnchors, kSecCSConsiderExpiration,
+ kSecCSDoNotValidateExecutable, kSecCSDoNotValidateResources, kSecCSEnforceRevocationChecks,
+ kSecCSFullReport, kSecCSNoNetworkAccess, kSecCSQuickCheck, kSecCSReportProgress,
+ kSecCSRestrictSidebandData, kSecCSRestrictSymlinks, kSecCSRestrictToAppLike,
+ kSecCSSingleThreaded, kSecCSStrictValidate, kSecCSUseSoftwareSigningCert, kSecCSValidatePEH,
+ kSecGuestAttributeAudit, kSecGuestAttributePid, SecCodeCheckValidity,
+ SecCodeCopyGuestWithAttributes, SecCodeCopyPath, SecCodeCopySelf, SecCodeGetTypeID, SecCodeRef,
+ SecRequirementCreateWithString, SecRequirementGetTypeID, SecRequirementRef,
+ SecStaticCodeCheckValidity, SecStaticCodeCreateWithPath, SecStaticCodeGetTypeID,
+ SecStaticCodeRef,
+};
+
+use crate::{cvt, Result};
+
+bitflags::bitflags! {
+
+ /// Values that can be used in the flags parameter to most code signing
+ /// functions.
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+ pub struct Flags: u32 {
+ /// Use the default behaviour.
+ const NONE = 0;
+
+ /// For multi-architecture (universal) Mach-O programs, validate all
+ /// architectures included.
+ const CHECK_ALL_ARCHITECTURES = kSecCSCheckAllArchitectures;
+
+ /// Do not validate the contents of the main executable.
+ const DO_NOT_VALIDATE_EXECUTABLE = kSecCSDoNotValidateExecutable;
+
+ /// Do not validate the presence and contents of all bundle resources
+ /// if any.
+ const DO_NOT_VALIDATE_RESOURCES = kSecCSDoNotValidateResources;
+
+ /// Do not validate either the main executable or the bundle resources,
+ /// if any.
+ const BASIC_VALIDATE_ONLY = kSecCSBasicValidateOnly;
+
+ /// For code in bundle form, locate and recursively check embedded code.
+ const CHECK_NESTED_CODE = kSecCSCheckNestedCode;
+
+ /// Perform additional checks to ensure the validity of code in bundle
+ /// form.
+ const STRICT_VALIDATE = kSecCSStrictValidate;
+
+ /// Apple have not documented this flag.
+ const FULL_REPORT = kSecCSFullReport;
+
+ /// Apple have not documented this flag.
+ const CHECK_GATEKEEPER_ARCHITECTURES = kSecCSCheckGatekeeperArchitectures;
+
+ /// Apple have not documented this flag.
+ const RESTRICT_SYMLINKS = kSecCSRestrictSymlinks;
+
+ /// Apple have not documented this flag.
+ const RESTRICT_TO_APP_LIKE = kSecCSRestrictToAppLike;
+
+ /// Apple have not documented this flag.
+ const RESTRICT_SIDEBAND_DATA = kSecCSRestrictSidebandData;
+
+ /// Apple have not documented this flag.
+ const USE_SOFTWARE_SIGNING_CERT = kSecCSUseSoftwareSigningCert;
+
+ /// Apple have not documented this flag.
+ const VALIDATE_PEH = kSecCSValidatePEH;
+
+ /// Apple have not documented this flag.
+ const SINGLE_THREADED = kSecCSSingleThreaded;
+
+ /// Apple have not documented this flag.
+ const QUICK_CHECK = kSecCSQuickCheck;
+
+ /// Apple have not documented this flag.
+ const CHECK_TRUSTED_ANCHORS = kSecCSCheckTrustedAnchors;
+
+ /// Apple have not documented this flag.
+ const REPORT_PROGRESS = kSecCSReportProgress;
+
+ /// Apple have not documented this flag.
+ const NO_NETWORK_ACCESS = kSecCSNoNetworkAccess;
+
+ /// Apple have not documented this flag.
+ const ENFORCE_REVOCATION_CHECKS = kSecCSEnforceRevocationChecks;
+
+ /// Apple have not documented this flag.
+ const CONSIDER_EXPIRATION = kSecCSConsiderExpiration;
+ }
+}
+
+impl Default for Flags {
+ #[inline(always)]
+ fn default() -> Self {
+ Self::NONE
+ }
+}
+
+/// A helper to create guest attributes, which are normally passed as a
+/// `CFDictionary` with varying types.
+pub struct GuestAttributes {
+ inner: CFMutableDictionary,
+}
+
+impl GuestAttributes {
+ // Not implemented:
+ // - architecture
+ // - canonical
+ // - dynamic code
+ // - dynamic code info plist
+ // - hash
+ // - mach port
+ // - sub-architecture
+
+ /// Creates a new, empty `GuestAttributes`. You must add values to it in
+ /// order for it to be of any use.
+ #[must_use]
+ pub fn new() -> Self {
+ Self {
+ inner: CFMutableDictionary::new(),
+ }
+ }
+
+ /// The guest's audit token.
+ pub fn set_audit_token(&mut self, token: CFDataRef) {
+ let key = unsafe { CFString::wrap_under_get_rule(kSecGuestAttributeAudit) };
+ self.inner.add(&key.as_CFTypeRef(), &token.to_void());
+ }
+
+ /// The guest's pid.
+ pub fn set_pid(&mut self, pid: pid_t) {
+ let key = unsafe { CFString::wrap_under_get_rule(kSecGuestAttributePid) };
+ let pid = CFNumber::from(pid);
+ self.inner.add(&key.as_CFTypeRef(), &pid.as_CFTypeRef());
+ }
+
+ /// Support for arbirtary guest attributes.
+ pub fn set_other<V: ToVoid<V>>(&mut self, key: CFStringRef, value: V) {
+ self.inner.add(&key.as_void_ptr(), &value.to_void());
+ }
+}
+
+impl Default for GuestAttributes {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+declare_TCFType! {
+ /// A code object representing signed code running on the system.
+ SecRequirement, SecRequirementRef
+}
+impl_TCFType!(SecRequirement, SecRequirementRef, SecRequirementGetTypeID);
+
+impl FromStr for SecRequirement {
+ type Err = crate::base::Error;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ let text = CFString::new(s);
+ let mut requirement = MaybeUninit::uninit();
+
+ unsafe {
+ cvt(SecRequirementCreateWithString(
+ text.as_concrete_TypeRef(),
+ 0,
+ requirement.as_mut_ptr(),
+ ))?;
+
+ Ok(Self::wrap_under_create_rule(requirement.assume_init()))
+ }
+ }
+}
+
+declare_TCFType! {
+ /// A code object representing signed code running on the system.
+ SecCode, SecCodeRef
+}
+impl_TCFType!(SecCode, SecCodeRef, SecCodeGetTypeID);
+
+impl Debug for SecCode {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_str("SecCode")
+ }
+}
+
+impl SecCode {
+ /// Retrieves the code object for the code making the call.
+ pub fn for_self(flags: Flags) -> Result<Self> {
+ let mut code = MaybeUninit::uninit();
+
+ unsafe {
+ cvt(SecCodeCopySelf(flags.bits(), code.as_mut_ptr()))?;
+ Ok(Self::wrap_under_create_rule(code.assume_init()))
+ }
+ }
+
+ /// Performs dynamic validation of signed code.
+ pub fn check_validity(&self, flags: Flags, requirement: &SecRequirement) -> Result<()> {
+ unsafe {
+ cvt(SecCodeCheckValidity(
+ self.as_concrete_TypeRef(),
+ flags.bits(),
+ requirement.as_concrete_TypeRef(),
+ ))
+ }
+ }
+
+ /// Asks a code host to identify one of its guests given
+ /// the type and value of specific attributes of the guest code.
+ ///
+ /// If `host` is `None` then the code signing root of trust (currently, the
+ // system kernel) should be used as the code host.
+ pub fn copy_guest_with_attribues(
+ host: Option<&SecCode>,
+ attrs: &GuestAttributes,
+ flags: Flags,
+ ) -> Result<SecCode> {
+ let mut code = MaybeUninit::uninit();
+
+ let host = match host {
+ Some(host) => host.as_concrete_TypeRef(),
+ None => std::ptr::null_mut(),
+ };
+
+ unsafe {
+ cvt(SecCodeCopyGuestWithAttributes(
+ host,
+ attrs.inner.as_concrete_TypeRef(),
+ flags.bits(),
+ code.as_mut_ptr(),
+ ))?;
+
+ Ok(SecCode::wrap_under_create_rule(code.assume_init()))
+ }
+ }
+
+ /// Retrieves the location on disk of signed code, given a code or static
+ /// code object.
+ pub fn path(&self, flags: Flags) -> Result<CFURL> {
+ let mut url = MaybeUninit::uninit();
+
+ // The docs say we can pass a SecCodeRef instead of a SecStaticCodeRef.
+ unsafe {
+ cvt(SecCodeCopyPath(
+ self.as_CFTypeRef() as _,
+ flags.bits(),
+ url.as_mut_ptr(),
+ ))?;
+
+ Ok(CFURL::wrap_under_create_rule(url.assume_init()))
+ }
+ }
+}
+
+declare_TCFType! {
+ /// A static code object representing signed code on disk.
+ SecStaticCode, SecStaticCodeRef
+}
+impl_TCFType!(SecStaticCode, SecStaticCodeRef, SecStaticCodeGetTypeID);
+
+impl SecStaticCode {
+ /// Creates a static code object representing the code at a specified file
+ /// system path.
+ pub fn from_path(path: &CFURL, flags: Flags) -> Result<Self> {
+ let mut code = MaybeUninit::uninit();
+
+ unsafe {
+ cvt(SecStaticCodeCreateWithPath(
+ path.as_concrete_TypeRef(),
+ flags.bits(),
+ code.as_mut_ptr(),
+ ))?;
+
+ Ok(Self::wrap_under_create_rule(code.assume_init()))
+ }
+ }
+
+ /// Retrieves the location on disk of signed code, given a code or static
+ /// code object.
+ pub fn path(&self, flags: Flags) -> Result<CFURL> {
+ let mut url = MaybeUninit::uninit();
+
+ // The docs say we can pass a SecCodeRef instead of a SecStaticCodeRef.
+ unsafe {
+ cvt(SecCodeCopyPath(
+ self.as_concrete_TypeRef(),
+ flags.bits(),
+ url.as_mut_ptr(),
+ ))?;
+
+ Ok(CFURL::wrap_under_create_rule(url.assume_init()))
+ }
+ }
+
+ /// Performs dynamic validation of signed code.
+ pub fn check_validity(&self, flags: Flags, requirement: &SecRequirement) -> Result<()> {
+ unsafe {
+ cvt(SecStaticCodeCheckValidity(
+ self.as_concrete_TypeRef(),
+ flags.bits(),
+ requirement.as_concrete_TypeRef(),
+ ))
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use core_foundation::data::CFData;
+ use libc::{c_uint, c_void, KERN_SUCCESS};
+
+ #[test]
+ fn path_to_static_code_and_back() {
+ let path = CFURL::from_path("/bin/bash", false).unwrap();
+ let code = SecStaticCode::from_path(&path, Flags::NONE).unwrap();
+ assert_eq!(code.path(Flags::NONE).unwrap(), path);
+ }
+
+ #[test]
+ fn self_to_path() {
+ let path = CFURL::from_path(std::env::current_exe().unwrap(), false).unwrap();
+ let code = SecCode::for_self(Flags::NONE).unwrap();
+ assert_eq!(code.path(Flags::NONE).unwrap(), path);
+ }
+
+ #[test]
+ fn bash_is_signed_by_apple() {
+ let path = CFURL::from_path("/bin/bash", false).unwrap();
+ let code = SecStaticCode::from_path(&path, Flags::NONE).unwrap();
+ let requirement: SecRequirement = "anchor apple".parse().unwrap();
+ code.check_validity(Flags::NONE, &requirement).unwrap();
+ }
+
+ #[cfg(target_arch = "aarch64")]
+ #[test]
+ fn self_is_not_signed_by_apple() {
+ let code = SecCode::for_self(Flags::NONE).unwrap();
+ let requirement: SecRequirement = "anchor apple".parse().unwrap();
+
+ assert_eq!(
+ code.check_validity(Flags::NONE, &requirement)
+ .unwrap_err()
+ .code(),
+ // "code failed to satisfy specified code requirement(s)"
+ -67050
+ );
+ }
+
+ #[cfg(not(target_arch = "aarch64"))]
+ #[test]
+ fn self_is_not_signed_by_apple() {
+ let code = SecCode::for_self(Flags::NONE).unwrap();
+ let requirement: SecRequirement = "anchor apple".parse().unwrap();
+
+ assert_eq!(
+ code.check_validity(Flags::NONE, &requirement)
+ .unwrap_err()
+ .code(),
+ // "code object is not signed at all"
+ -67062
+ );
+ }
+
+ #[test]
+ fn copy_kernel_guest_with_launchd_pid() {
+ let mut attrs = GuestAttributes::new();
+ attrs.set_pid(1);
+
+ assert_eq!(
+ SecCode::copy_guest_with_attribues(None, &attrs, Flags::NONE)
+ .unwrap()
+ .path(Flags::NONE)
+ .unwrap()
+ .get_string()
+ .to_string(),
+ "file:///sbin/launchd"
+ );
+ }
+
+ #[test]
+ fn copy_current_guest_with_launchd_pid() {
+ let host_code = SecCode::for_self(Flags::NONE).unwrap();
+
+ let mut attrs = GuestAttributes::new();
+ attrs.set_pid(1);
+
+ assert_eq!(
+ SecCode::copy_guest_with_attribues(Some(&host_code), &attrs, Flags::NONE)
+ .unwrap_err()
+ .code(),
+ // "host has no guest with the requested attributes"
+ -67065
+ );
+ }
+
+ #[test]
+ fn copy_kernel_guest_with_unmatched_pid() {
+ let mut attrs = GuestAttributes::new();
+ attrs.set_pid(999_999_999);
+
+ assert_eq!(
+ SecCode::copy_guest_with_attribues(None, &attrs, Flags::NONE)
+ .unwrap_err()
+ .code(),
+ // "UNIX[No such process]"
+ 100003
+ );
+ }
+
+ #[test]
+ fn copy_kernel_guest_with_current_token() {
+ let mut token: [u8; 32] = [0; 32];
+ let mut token_len = 32u32;
+
+ enum OpaqueTaskName {}
+
+ extern "C" {
+ fn mach_task_self() -> *const OpaqueTaskName;
+ fn task_info(
+ task_name: *const OpaqueTaskName,
+ task_flavor: u32,
+ out: *mut c_void,
+ out_len: *mut u32,
+ ) -> i32;
+ }
+
+ const TASK_AUDIT_TOKEN: c_uint = 15;
+
+ let result = unsafe {
+ task_info(
+ mach_task_self(),
+ TASK_AUDIT_TOKEN,
+ token.as_mut_ptr().cast::<c_void>(),
+ &mut token_len,
+ )
+ };
+
+ assert_eq!(result, KERN_SUCCESS);
+
+ let token_data = CFData::from_buffer(&token);
+
+ let mut attrs = GuestAttributes::new();
+ attrs.set_audit_token(token_data.as_concrete_TypeRef());
+
+ assert_eq!(
+ SecCode::copy_guest_with_attribues(None, &attrs, Flags::NONE)
+ .unwrap()
+ .path(Flags::NONE)
+ .unwrap()
+ .to_path()
+ .unwrap(),
+ std::env::current_exe().unwrap()
+ );
+ }
+
+ #[test]
+ fn copy_kernel_guest_with_unmatched_token() {
+ let token: [u8; 32] = [0; 32];
+ let token_data = CFData::from_buffer(&token);
+
+ let mut attrs = GuestAttributes::new();
+ attrs.set_audit_token(token_data.as_concrete_TypeRef());
+
+ assert_eq!(
+ SecCode::copy_guest_with_attribues(None, &attrs, Flags::NONE)
+ .unwrap_err()
+ .code(),
+ // "UNIX[No such process]"
+ 100003
+ );
+ }
+}