1use base64::Engine;
2use std::collections::BTreeMap;
3use std::path::PathBuf;
4
5use serde_json::Value;
6
7use crate::bridge;
8use crate::error::Result;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11#[non_exhaustive]
12pub enum SigningValue {
13 Boolean(bool),
14 Integer(i64),
15 String(String),
16 Data(Vec<u8>),
17 Array(Vec<Self>),
18 Dictionary(BTreeMap<String, Self>),
19 Unknown(String),
20}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct SigningInformation {
24 pub identifier: Option<String>,
25 pub team_identifier: Option<String>,
26 pub entitlements: BTreeMap<String, SigningValue>,
27 pub sandboxed: bool,
28 pub status: Option<u32>,
29}
30
31impl SigningInformation {
32 pub const fn is_signed(&self) -> bool {
33 self.identifier.is_some()
34 }
35}
36
37#[derive(Debug)]
38pub struct Code {
39 handle: bridge::Handle,
40}
41
42impl Code {
43 pub fn current() -> Result<Self> {
44 let mut status = 0;
45 let mut error = std::ptr::null_mut();
46 let raw = unsafe { bridge::security_code_copy_self(&mut status, &mut error) };
47 bridge::required_handle("security_code_copy_self", raw, status, error).map(|handle| Self {
48 handle,
49 })
50 }
51
52 pub fn static_code(&self) -> Result<StaticCode> {
53 let mut status = 0;
54 let mut error = std::ptr::null_mut();
55 let raw = unsafe {
56 bridge::security_code_copy_static_code(self.handle.as_ptr(), &mut status, &mut error)
57 };
58 bridge::required_handle("security_code_copy_static_code", raw, status, error)
59 .map(StaticCode::from_handle)
60 }
61
62 pub fn signing_information(&self) -> Result<SigningInformation> {
63 self.static_code()?.signing_information()
64 }
65
66 pub fn task(&self) -> Result<Task> {
67 Task::current()
68 }
69}
70
71#[derive(Debug)]
72pub struct StaticCode {
73 handle: bridge::Handle,
74}
75
76impl StaticCode {
77 fn from_handle(handle: bridge::Handle) -> Self {
78 Self { handle }
79 }
80
81 pub fn check_validity(&self) -> Result<()> {
82 let mut error = std::ptr::null_mut();
83 let status = unsafe {
84 bridge::security_static_code_check_validity(self.handle.as_ptr(), &mut error)
85 };
86 bridge::status_result("security_static_code_check_validity", status, error)
87 }
88
89 pub fn path(&self) -> Result<PathBuf> {
90 let mut status = 0;
91 let mut error = std::ptr::null_mut();
92 let raw = unsafe {
93 bridge::security_static_code_copy_path(self.handle.as_ptr(), &mut status, &mut error)
94 };
95 bridge::required_string("security_static_code_copy_path", raw, status, error)
96 .map(PathBuf::from)
97 }
98
99 pub fn designated_requirement(&self) -> Result<String> {
100 let mut status = 0;
101 let mut error = std::ptr::null_mut();
102 let raw = unsafe {
103 bridge::security_static_code_copy_designated_requirement(
104 self.handle.as_ptr(),
105 &mut status,
106 &mut error,
107 )
108 };
109 bridge::required_string(
110 "security_static_code_copy_designated_requirement",
111 raw,
112 status,
113 error,
114 )
115 }
116
117 pub fn signing_information(&self) -> Result<SigningInformation> {
118 let mut status = 0;
119 let mut error = std::ptr::null_mut();
120 let raw = unsafe {
121 bridge::security_static_code_copy_signing_information(
122 self.handle.as_ptr(),
123 &mut status,
124 &mut error,
125 )
126 };
127 let value: Value = bridge::required_json(
128 "security_static_code_copy_signing_information",
129 raw,
130 status,
131 error,
132 )?;
133 Ok(SigningInformation {
134 identifier: find_string(&value, &["identifier", "Identifier"]),
135 team_identifier: find_string(&value, &["teamid", "TeamIdentifier", "teamIdentifier"]),
136 entitlements: find_object(
137 &value,
138 &[
139 "entitlements-dict",
140 "EntitlementsDict",
141 "entitlements",
142 "Entitlements",
143 ],
144 )
145 .map(json_object_to_map)
146 .unwrap_or_default(),
147 sandboxed: matches!(
148 find_object(
149 &value,
150 &[
151 "entitlements-dict",
152 "EntitlementsDict",
153 "entitlements",
154 "Entitlements",
155 ],
156 )
157 .and_then(|value| value.get("com.apple.security.app-sandbox")),
158 Some(Value::Bool(true))
159 ),
160 status: find_integer(&value, &["status", "Status"]).and_then(|value| u32::try_from(value).ok()),
161 })
162 }
163}
164
165#[derive(Debug)]
166pub struct Task {
167 handle: bridge::Handle,
168}
169
170impl Task {
171 pub fn current() -> Result<Self> {
172 let mut status = 0;
173 let mut error = std::ptr::null_mut();
174 let raw = unsafe { bridge::security_task_create_from_self(&mut status, &mut error) };
175 bridge::required_handle("security_task_create_from_self", raw, status, error).map(|handle| Self {
176 handle,
177 })
178 }
179
180 pub fn signing_identifier(&self) -> Result<Option<String>> {
181 let mut status = 0;
182 let mut error = std::ptr::null_mut();
183 let raw = unsafe {
184 bridge::security_task_copy_signing_identifier(self.handle.as_ptr(), &mut status, &mut error)
185 };
186 if raw.is_null() && status == 0 {
187 Ok(None)
188 } else {
189 bridge::required_string("security_task_copy_signing_identifier", raw, status, error)
190 .map(Some)
191 }
192 }
193
194 pub fn entitlement(&self, entitlement: &str) -> Result<Option<Value>> {
195 let entitlement = bridge::cstring(entitlement)?;
196 let mut status = 0;
197 let mut error = std::ptr::null_mut();
198 let raw = unsafe {
199 bridge::security_task_copy_value_for_entitlement(
200 self.handle.as_ptr(),
201 entitlement.as_ptr(),
202 &mut status,
203 &mut error,
204 )
205 };
206 if raw.is_null() && status == 0 {
207 return Ok(None);
208 }
209 let value: Value = bridge::required_json(
210 "security_task_copy_value_for_entitlement",
211 raw,
212 status,
213 error,
214 )?;
215 Ok(Some(value))
216 }
217}
218
219fn find_object<'a>(value: &'a Value, keys: &[&str]) -> Option<&'a serde_json::Map<String, Value>> {
220 keys.iter()
221 .find_map(|key| value.get(*key))
222 .and_then(Value::as_object)
223}
224
225fn find_string(value: &Value, keys: &[&str]) -> Option<String> {
226 keys.iter()
227 .find_map(|key| value.get(*key))
228 .and_then(Value::as_str)
229 .map(ToOwned::to_owned)
230}
231
232fn find_integer(value: &Value, keys: &[&str]) -> Option<i64> {
233 keys.iter()
234 .find_map(|key| value.get(*key))
235 .and_then(Value::as_i64)
236}
237
238fn json_object_to_map(object: &serde_json::Map<String, Value>) -> BTreeMap<String, SigningValue> {
239 object
240 .iter()
241 .map(|(key, value)| (key.clone(), signing_value(value)))
242 .collect()
243}
244
245fn signing_value(value: &Value) -> SigningValue {
246 match value {
247 Value::Bool(value) => SigningValue::Boolean(*value),
248 Value::Number(value) => value
249 .as_i64()
250 .map_or_else(|| SigningValue::Unknown(value.to_string()), SigningValue::Integer),
251 Value::String(value) => SigningValue::String(value.clone()),
252 Value::Array(values) => {
253 if let Some(data) = data_from_wrapped_json(value) {
254 SigningValue::Data(data)
255 } else {
256 SigningValue::Array(values.iter().map(signing_value).collect())
257 }
258 }
259 Value::Object(object) => {
260 if let Some(data) = data_from_wrapped_json(value) {
261 SigningValue::Data(data)
262 } else {
263 SigningValue::Dictionary(json_object_to_map(object))
264 }
265 }
266 Value::Null => SigningValue::Unknown("null".to_owned()),
267 }
268}
269
270fn data_from_wrapped_json(value: &Value) -> Option<Vec<u8>> {
271 let object = value.as_object()?;
272 if object.get("_type")?.as_str()? != "data" {
273 return None;
274 }
275 let base64 = object.get("base64")?.as_str()?;
276 base64::engine::general_purpose::STANDARD.decode(base64).ok()
277}