use base64::Engine;
use bitflags::bitflags;
use serde_json::Value;
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use crate::bridge;
use crate::error::Result;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CodeSigningFlags: u32 {
const CHECK_ALL_ARCHITECTURES = 1 << 0;
const DO_NOT_VALIDATE_EXECUTABLE = 1 << 1;
const DO_NOT_VALIDATE_RESOURCES = 1 << 2;
const BASIC_VALIDATE_ONLY = Self::DO_NOT_VALIDATE_EXECUTABLE.bits() | Self::DO_NOT_VALIDATE_RESOURCES.bits();
const CHECK_NESTED_CODE = 1 << 3;
const STRICT_VALIDATE = 1 << 4;
const FULL_REPORT = 1 << 5;
const CHECK_GATEKEEPER_ARCHITECTURES = (1 << 6) | Self::CHECK_ALL_ARCHITECTURES.bits();
const RESTRICT_SYMLINKS = 1 << 7;
const RESTRICT_TO_APP_LIKE = 1 << 8;
const RESTRICT_SIDEBAND_DATA = 1 << 9;
const USE_SOFTWARE_SIGNING_CERT = 1 << 10;
const VALIDATE_PEH = 1 << 11;
const SINGLE_THREADED = 1 << 12;
const ALLOW_NETWORK_ACCESS = 1 << 16;
const FAST_EXECUTABLE_VALIDATION = 1 << 17;
const SIGNING_INFORMATION = 1 << 1;
const DYNAMIC_INFORMATION = 1 << 3;
const USE_ALL_ARCHITECTURES = 1 << 0;
}
}
#[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 {
pub const fn is_signed(&self) -> bool {
self.identifier.is_some()
}
}
#[derive(Debug)]
pub struct Code {
handle: bridge::Handle,
}
impl Code {
pub fn type_id() -> usize {
unsafe { bridge::security_code_get_type_id() }
}
pub fn current() -> Result<Self> {
let mut status = 0;
let mut error = std::ptr::null_mut();
let raw = unsafe { bridge::security_code_copy_self(&mut status, &mut error) };
bridge::required_handle("security_code_copy_self", raw, status, error)
.map(|handle| Self { handle })
}
pub fn host(&self) -> Result<Self> {
let mut status = 0;
let mut error = std::ptr::null_mut();
let raw = unsafe {
bridge::security_code_copy_host(self.handle.as_ptr(), &mut status, &mut error)
};
bridge::required_handle("security_code_copy_host", raw, status, error)
.map(|handle| Self { handle })
}
pub fn guest_with_attributes(
host: Option<&Self>,
attributes: Option<&Value>,
flags: CodeSigningFlags,
) -> Result<Self> {
let attributes = attributes.map(bridge::json_cstring).transpose()?;
let mut status = 0;
let mut error = std::ptr::null_mut();
let raw = unsafe {
bridge::security_code_copy_guest_with_attributes(
host.map_or(std::ptr::null_mut(), |value| value.handle.as_ptr()),
attributes
.as_ref()
.map_or(std::ptr::null(), |value| value.as_ptr()),
flags.bits(),
&mut status,
&mut error,
)
};
bridge::required_handle(
"security_code_copy_guest_with_attributes",
raw,
status,
error,
)
.map(|handle| Self { handle })
}
pub fn static_code(&self) -> Result<StaticCode> {
let mut status = 0;
let mut error = std::ptr::null_mut();
let raw = unsafe {
bridge::security_code_copy_static_code(self.handle.as_ptr(), &mut status, &mut error)
};
bridge::required_handle("security_code_copy_static_code", raw, status, error)
.map(StaticCode::from_handle)
}
pub fn signing_information(&self) -> Result<SigningInformation> {
self.static_code()?.signing_information()
}
pub fn task(&self) -> Result<Task> {
Task::current()
}
}
#[derive(Debug)]
pub struct Requirement {
handle: bridge::Handle,
}
impl Requirement {
fn from_handle(handle: bridge::Handle) -> Self {
Self { handle }
}
fn handle(&self) -> &bridge::Handle {
&self.handle
}
pub fn type_id() -> usize {
unsafe { bridge::security_requirement_get_type_id() }
}
pub fn from_data(data: &[u8]) -> Result<Self> {
let mut status = 0;
let mut error = std::ptr::null_mut();
let raw = unsafe {
bridge::security_requirement_create_with_data(
data.as_ptr().cast(),
bridge::len_to_isize(data.len())?,
&mut status,
&mut error,
)
};
bridge::required_handle("security_requirement_create_with_data", raw, status, error)
.map(Self::from_handle)
}
pub fn from_string(text: &str) -> Result<Self> {
let text = bridge::cstring(text)?;
let mut status = 0;
let mut error = std::ptr::null_mut();
let raw = unsafe {
bridge::security_requirement_create_with_string(text.as_ptr(), &mut status, &mut error)
};
bridge::required_handle(
"security_requirement_create_with_string",
raw,
status,
error,
)
.map(Self::from_handle)
}
pub fn from_string_with_errors(text: &str) -> Result<Self> {
let text = bridge::cstring(text)?;
let mut status = 0;
let mut error = std::ptr::null_mut();
let raw = unsafe {
bridge::security_requirement_create_with_string_and_errors(
text.as_ptr(),
&mut status,
&mut error,
)
};
bridge::required_handle(
"security_requirement_create_with_string_and_errors",
raw,
status,
error,
)
.map(Self::from_handle)
}
pub fn data(&self) -> Result<Vec<u8>> {
let mut status = 0;
let mut error = std::ptr::null_mut();
let raw = unsafe {
bridge::security_requirement_copy_data(self.handle.as_ptr(), &mut status, &mut error)
};
bridge::required_data("security_requirement_copy_data", raw, status, error)
}
pub fn string(&self) -> Result<String> {
let mut status = 0;
let mut error = std::ptr::null_mut();
let raw = unsafe {
bridge::security_requirement_copy_string(self.handle.as_ptr(), &mut status, &mut error)
};
bridge::required_string("security_requirement_copy_string", raw, status, error)
}
}
#[derive(Debug)]
pub struct StaticCode {
handle: bridge::Handle,
}
impl StaticCode {
fn from_handle(handle: bridge::Handle) -> Self {
Self { handle }
}
pub fn type_id() -> usize {
unsafe { bridge::security_static_code_get_type_id() }
}
pub fn from_path(path: impl AsRef<Path>) -> Result<Self> {
let path = bridge::cstring(&path.as_ref().to_string_lossy())?;
let mut status = 0;
let mut error = std::ptr::null_mut();
let raw = unsafe {
bridge::security_static_code_create_with_path(path.as_ptr(), &mut status, &mut error)
};
bridge::required_handle("security_static_code_create_with_path", raw, status, error)
.map(Self::from_handle)
}
pub fn from_path_with_attributes(path: impl AsRef<Path>, attributes: &Value) -> Result<Self> {
let path = bridge::cstring(&path.as_ref().to_string_lossy())?;
let attributes = bridge::json_cstring(attributes)?;
let mut status = 0;
let mut error = std::ptr::null_mut();
let raw = unsafe {
bridge::security_static_code_create_with_path_and_attributes(
path.as_ptr(),
attributes.as_ptr(),
&mut status,
&mut error,
)
};
bridge::required_handle(
"security_static_code_create_with_path_and_attributes",
raw,
status,
error,
)
.map(Self::from_handle)
}
pub fn check_validity(&self) -> Result<()> {
let mut error = std::ptr::null_mut();
let status = unsafe {
bridge::security_static_code_check_validity(self.handle.as_ptr(), &mut error)
};
bridge::status_result("security_static_code_check_validity", status, error)
}
pub fn check_validity_with_errors(
&self,
flags: CodeSigningFlags,
requirement: Option<&Requirement>,
) -> Result<()> {
let mut error = std::ptr::null_mut();
let status = unsafe {
bridge::security_static_code_check_validity_with_errors(
self.handle.as_ptr(),
flags.bits(),
requirement.map_or(std::ptr::null_mut(), |value| value.handle().as_ptr()),
&mut error,
)
};
bridge::status_result(
"security_static_code_check_validity_with_errors",
status,
error,
)
}
pub fn check_static_validity(
&self,
flags: CodeSigningFlags,
requirement: Option<&Requirement>,
) -> Result<()> {
let mut error = std::ptr::null_mut();
let status = unsafe {
bridge::security_static_code_check_static_validity(
self.handle.as_ptr(),
flags.bits(),
requirement.map_or(std::ptr::null_mut(), |value| value.handle().as_ptr()),
&mut error,
)
};
bridge::status_result("security_static_code_check_static_validity", status, error)
}
pub fn check_static_validity_with_errors(
&self,
flags: CodeSigningFlags,
requirement: Option<&Requirement>,
) -> Result<()> {
let mut error = std::ptr::null_mut();
let status = unsafe {
bridge::security_static_code_check_static_validity_with_errors(
self.handle.as_ptr(),
flags.bits(),
requirement.map_or(std::ptr::null_mut(), |value| value.handle().as_ptr()),
&mut error,
)
};
bridge::status_result(
"security_static_code_check_static_validity_with_errors",
status,
error,
)
}
pub fn path(&self) -> Result<PathBuf> {
let mut status = 0;
let mut error = std::ptr::null_mut();
let raw = unsafe {
bridge::security_static_code_copy_path(self.handle.as_ptr(), &mut status, &mut error)
};
bridge::required_string("security_static_code_copy_path", raw, status, error)
.map(PathBuf::from)
}
pub fn designated_requirement(&self) -> Result<String> {
let mut status = 0;
let mut error = std::ptr::null_mut();
let raw = unsafe {
bridge::security_static_code_copy_designated_requirement(
self.handle.as_ptr(),
&mut status,
&mut error,
)
};
bridge::required_string(
"security_static_code_copy_designated_requirement",
raw,
status,
error,
)
}
pub fn signing_information(&self) -> Result<SigningInformation> {
let mut status = 0;
let mut error = std::ptr::null_mut();
let raw = unsafe {
bridge::security_static_code_copy_signing_information(
self.handle.as_ptr(),
&mut status,
&mut error,
)
};
let value: Value = bridge::required_json(
"security_static_code_copy_signing_information",
raw,
status,
error,
)?;
Ok(SigningInformation {
identifier: find_string(&value, &["identifier", "Identifier"]),
team_identifier: find_string(&value, &["teamid", "TeamIdentifier", "teamIdentifier"]),
entitlements: find_object(
&value,
&[
"entitlements-dict",
"EntitlementsDict",
"entitlements",
"Entitlements",
],
)
.map(json_object_to_map)
.unwrap_or_default(),
sandboxed: matches!(
find_object(
&value,
&[
"entitlements-dict",
"EntitlementsDict",
"entitlements",
"Entitlements",
],
)
.and_then(|value| value.get("com.apple.security.app-sandbox")),
Some(Value::Bool(true))
),
status: find_integer(&value, &["status", "Status"])
.and_then(|value| u32::try_from(value).ok()),
})
}
pub fn validate_file_resource(
&self,
relative_path: &str,
data: &[u8],
flags: CodeSigningFlags,
) -> Result<()> {
let relative_path = bridge::cstring(relative_path)?;
let mut error = std::ptr::null_mut();
let status = unsafe {
bridge::security_static_code_validate_file_resource(
self.handle.as_ptr(),
relative_path.as_ptr(),
data.as_ptr().cast(),
bridge::len_to_isize(data.len())?,
flags.bits(),
&mut error,
)
};
bridge::status_result("security_static_code_validate_file_resource", status, error)
}
pub fn map_memory(&self, flags: CodeSigningFlags) -> Result<()> {
let mut error = std::ptr::null_mut();
let status = unsafe {
bridge::security_static_code_map_memory(self.handle.as_ptr(), flags.bits(), &mut error)
};
bridge::status_result("security_static_code_map_memory", status, error)
}
}
#[derive(Debug)]
pub struct Task {
handle: bridge::Handle,
}
impl Task {
pub fn type_id() -> usize {
unsafe { bridge::security_task_get_type_id() }
}
pub fn current() -> Result<Self> {
let mut status = 0;
let mut error = std::ptr::null_mut();
let raw = unsafe { bridge::security_task_create_from_self(&mut status, &mut error) };
bridge::required_handle("security_task_create_from_self", raw, status, error)
.map(|handle| Self { handle })
}
pub fn current_with_audit_token() -> Result<Self> {
let mut status = 0;
let mut error = std::ptr::null_mut();
let raw = unsafe {
bridge::security_task_create_from_current_audit_token(&mut status, &mut error)
};
bridge::required_handle(
"security_task_create_from_current_audit_token",
raw,
status,
error,
)
.map(|handle| Self { handle })
}
pub fn signing_identifier(&self) -> Result<Option<String>> {
let mut status = 0;
let mut error = std::ptr::null_mut();
let raw = unsafe {
bridge::security_task_copy_signing_identifier(
self.handle.as_ptr(),
&mut status,
&mut error,
)
};
if raw.is_null() && status == 0 {
Ok(None)
} else {
bridge::required_string("security_task_copy_signing_identifier", raw, status, error)
.map(Some)
}
}
pub fn entitlement(&self, entitlement: &str) -> Result<Option<Value>> {
let entitlement = bridge::cstring(entitlement)?;
let mut status = 0;
let mut error = std::ptr::null_mut();
let raw = unsafe {
bridge::security_task_copy_value_for_entitlement(
self.handle.as_ptr(),
entitlement.as_ptr(),
&mut status,
&mut error,
)
};
if raw.is_null() && status == 0 {
return Ok(None);
}
let value: Value = bridge::required_json(
"security_task_copy_value_for_entitlement",
raw,
status,
error,
)?;
Ok(Some(value))
}
pub fn entitlements(&self, entitlements: &[&str]) -> Result<Value> {
let entitlements = bridge::json_cstring(&entitlements)?;
let mut status = 0;
let mut error = std::ptr::null_mut();
let raw = unsafe {
bridge::security_task_copy_values_for_entitlements(
self.handle.as_ptr(),
entitlements.as_ptr(),
&mut status,
&mut error,
)
};
bridge::required_json(
"security_task_copy_values_for_entitlements",
raw,
status,
error,
)
}
}
fn find_object<'a>(value: &'a Value, keys: &[&str]) -> Option<&'a serde_json::Map<String, Value>> {
keys.iter()
.find_map(|key| value.get(*key))
.and_then(Value::as_object)
}
fn find_string(value: &Value, keys: &[&str]) -> Option<String> {
keys.iter()
.find_map(|key| value.get(*key))
.and_then(Value::as_str)
.map(ToOwned::to_owned)
}
fn find_integer(value: &Value, keys: &[&str]) -> Option<i64> {
keys.iter()
.find_map(|key| value.get(*key))
.and_then(Value::as_i64)
}
fn json_object_to_map(object: &serde_json::Map<String, Value>) -> BTreeMap<String, SigningValue> {
object
.iter()
.map(|(key, value)| (key.clone(), signing_value(value)))
.collect()
}
fn signing_value(value: &Value) -> SigningValue {
match value {
Value::Bool(value) => SigningValue::Boolean(*value),
Value::Number(value) => value.as_i64().map_or_else(
|| SigningValue::Unknown(value.to_string()),
SigningValue::Integer,
),
Value::String(value) => SigningValue::String(value.clone()),
Value::Array(values) => {
if let Some(data) = data_from_wrapped_json(value) {
SigningValue::Data(data)
} else {
SigningValue::Array(values.iter().map(signing_value).collect())
}
}
Value::Object(object) => {
if let Some(data) = data_from_wrapped_json(value) {
SigningValue::Data(data)
} else {
SigningValue::Dictionary(json_object_to_map(object))
}
}
Value::Null => SigningValue::Unknown("null".to_owned()),
}
}
fn data_from_wrapped_json(value: &Value) -> Option<Vec<u8>> {
let object = value.as_object()?;
if object.get("_type")?.as_str()? != "data" {
return None;
}
let base64 = object.get("base64")?.as_str()?;
base64::engine::general_purpose::STANDARD
.decode(base64)
.ok()
}