use std::collections::BTreeMap;
use apple_cf::CFError;
use crate::error::{Result, SecurityError};
use crate::ffi;
use crate::private::{
cf_boolean_to_bool, cf_data_to_vec, cf_dictionary_entries, cf_dictionary_get_value,
cf_number_to_i64, cf_string_to_string, cf_type_id, sec_error_message, OwnedCf,
};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum SigningValue {
Boolean(bool),
Integer(i64),
String(String),
Data(Vec<u8>),
Array(Vec<Self>),
Dictionary(BTreeMap<String, Self>),
Unknown(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SigningInformation {
pub identifier: Option<String>,
pub team_identifier: Option<String>,
pub entitlements: BTreeMap<String, SigningValue>,
pub sandboxed: bool,
pub status: Option<u32>,
}
impl SigningInformation {
#[must_use]
pub const fn is_signed(&self) -> bool {
self.identifier.is_some()
}
}
pub struct Code {
raw: ffi::SecCodeRef,
}
impl Code {
pub fn current() -> Result<Self> {
let mut raw = std::ptr::null();
let status = unsafe { ffi::SecCodeCopySelf(ffi::kSecCSDefaultFlags, &mut raw) };
if status != ffi::status::SUCCESS {
return Err(SecurityError::from_status(
"SecCodeCopySelf",
status,
sec_error_message(status),
));
}
if raw.is_null() {
return Err(SecurityError::CoreFoundation(CFError::new(
"SecCodeCopySelf",
)));
}
Ok(Self { raw })
}
pub fn signing_information(&self) -> Result<SigningInformation> {
let mut info = std::ptr::null();
let flags = ffi::kSecCSSigningInformation | ffi::kSecCSDynamicInformation;
let status =
unsafe { ffi::SecCodeCopySigningInformation(self.raw.cast(), flags, &mut info) };
if status != ffi::status::SUCCESS {
return Err(SecurityError::from_status(
"SecCodeCopySigningInformation",
status,
sec_error_message(status),
));
}
if info.is_null() {
return Err(SecurityError::CoreFoundation(CFError::new(
"SecCodeCopySigningInformation",
)));
}
let info = OwnedCf::new(info.cast());
if cf_type_id(info.as_ptr()) != unsafe { ffi::CFDictionaryGetTypeID() } {
return Err(SecurityError::UnexpectedType {
operation: "SecCodeCopySigningInformation",
expected: "CFDictionary",
});
}
let identifier = string_value(info.as_dictionary(), unsafe { ffi::kSecCodeInfoIdentifier });
let team_identifier =
string_value(info.as_dictionary(), unsafe { ffi::kSecCodeInfoTeamIdentifier });
let entitlements =
dictionary_value(info.as_dictionary(), unsafe { ffi::kSecCodeInfoEntitlementsDict })
.map(cf_dictionary_to_map)
.unwrap_or_default();
let sandboxed = matches!(
entitlements.get("com.apple.security.app-sandbox"),
Some(SigningValue::Boolean(true))
);
let status = cf_dictionary_get_value(info.as_dictionary(), unsafe { ffi::kSecCodeInfoStatus });
let status = cf_number_to_i64(status.cast()).and_then(|value| u32::try_from(value).ok());
Ok(SigningInformation {
identifier,
team_identifier,
entitlements,
sandboxed,
status,
})
}
#[must_use]
pub const fn as_raw(&self) -> ffi::SecCodeRef {
self.raw
}
}
impl Drop for Code {
fn drop(&mut self) {
if !self.raw.is_null() {
unsafe { ffi::CFRelease(self.raw.cast()) };
}
}
}
impl core::fmt::Debug for Code {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Code").field("raw", &self.raw).finish()
}
}
fn string_value(dictionary: ffi::CFDictionaryRef, key: ffi::CFStringRef) -> Option<String> {
let value = cf_dictionary_get_value(dictionary, key);
if value.is_null() || cf_type_id(value) != unsafe { ffi::CFStringGetTypeID() } {
return None;
}
cf_string_to_string(value.cast())
}
fn dictionary_value(
dictionary: ffi::CFDictionaryRef,
key: ffi::CFStringRef,
) -> Option<ffi::CFDictionaryRef> {
let value = cf_dictionary_get_value(dictionary, key);
if value.is_null() || cf_type_id(value) != unsafe { ffi::CFDictionaryGetTypeID() } {
return None;
}
Some(value.cast())
}
fn cf_dictionary_to_map(dictionary: ffi::CFDictionaryRef) -> BTreeMap<String, SigningValue> {
cf_dictionary_entries(dictionary)
.into_iter()
.filter_map(|(key, value)| {
if key.is_null() || cf_type_id(key) != unsafe { ffi::CFStringGetTypeID() } {
return None;
}
let key = cf_string_to_string(key.cast())?;
Some((key, cf_type_to_value(value.cast())))
})
.collect()
}
fn cf_type_to_value(value: ffi::CFTypeRef) -> SigningValue {
if value.is_null() {
return SigningValue::Unknown("null".to_owned());
}
let type_id = cf_type_id(value);
if type_id == unsafe { ffi::CFBooleanGetTypeID() } {
return SigningValue::Boolean(cf_boolean_to_bool(value.cast()));
}
if type_id == unsafe { ffi::CFNumberGetTypeID() } {
return cf_number_to_i64(value.cast()).map_or_else(
|| SigningValue::Unknown("number".to_owned()),
SigningValue::Integer,
);
}
if type_id == unsafe { ffi::CFStringGetTypeID() } {
return cf_string_to_string(value.cast()).map_or_else(
|| SigningValue::Unknown("string".to_owned()),
SigningValue::String,
);
}
if type_id == unsafe { ffi::CFDataGetTypeID() } {
return SigningValue::Data(cf_data_to_vec(value.cast()));
}
if type_id == unsafe { ffi::CFArrayGetTypeID() } {
let count = unsafe { ffi::CFArrayGetCount(value.cast()) };
let count = usize::try_from(count).unwrap_or_default();
let values = (0..count)
.filter_map(|index| isize::try_from(index).ok())
.map(|index| unsafe { ffi::CFArrayGetValueAtIndex(value.cast(), index) })
.filter(|value| !value.is_null())
.map(|value| cf_type_to_value(value.cast()))
.collect();
return SigningValue::Array(values);
}
if type_id == unsafe { ffi::CFDictionaryGetTypeID() } {
return SigningValue::Dictionary(cf_dictionary_to_map(value.cast()));
}
SigningValue::Unknown(format!("CFTypeID({type_id})"))
}