1use base64::Engine;
2use bitflags::bitflags;
3use serde_json::Value;
4use std::collections::BTreeMap;
5use std::path::{Path, PathBuf};
6
7use crate::bridge;
8use crate::error::Result;
9
10bitflags! {
11 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12 pub struct CodeSigningFlags: u32 {
13 const CHECK_ALL_ARCHITECTURES = 1 << 0;
14 const DO_NOT_VALIDATE_EXECUTABLE = 1 << 1;
15 const DO_NOT_VALIDATE_RESOURCES = 1 << 2;
16 const BASIC_VALIDATE_ONLY = Self::DO_NOT_VALIDATE_EXECUTABLE.bits() | Self::DO_NOT_VALIDATE_RESOURCES.bits();
17 const CHECK_NESTED_CODE = 1 << 3;
18 const STRICT_VALIDATE = 1 << 4;
19 const FULL_REPORT = 1 << 5;
20 const CHECK_GATEKEEPER_ARCHITECTURES = (1 << 6) | Self::CHECK_ALL_ARCHITECTURES.bits();
21 const RESTRICT_SYMLINKS = 1 << 7;
22 const RESTRICT_TO_APP_LIKE = 1 << 8;
23 const RESTRICT_SIDEBAND_DATA = 1 << 9;
24 const USE_SOFTWARE_SIGNING_CERT = 1 << 10;
25 const VALIDATE_PEH = 1 << 11;
26 const SINGLE_THREADED = 1 << 12;
27 const ALLOW_NETWORK_ACCESS = 1 << 16;
28 const FAST_EXECUTABLE_VALIDATION = 1 << 17;
29
30 const SIGNING_INFORMATION = 1 << 1;
31 const DYNAMIC_INFORMATION = 1 << 3;
32 const USE_ALL_ARCHITECTURES = 1 << 0;
33 }
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
37#[non_exhaustive]
38pub enum SigningValue {
39 Boolean(bool),
40 Integer(i64),
41 String(String),
42 Data(Vec<u8>),
43 Array(Vec<Self>),
44 Dictionary(BTreeMap<String, Self>),
45 Unknown(String),
46}
47
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct SigningInformation {
50 pub identifier: Option<String>,
51 pub team_identifier: Option<String>,
52 pub entitlements: BTreeMap<String, SigningValue>,
53 pub sandboxed: bool,
54 pub status: Option<u32>,
55}
56
57impl SigningInformation {
58 pub const fn is_signed(&self) -> bool {
59 self.identifier.is_some()
60 }
61}
62
63#[derive(Debug)]
64pub struct Code {
65 handle: bridge::Handle,
66}
67
68impl Code {
69 pub fn type_id() -> usize {
70 unsafe { bridge::security_code_get_type_id() }
71 }
72
73 pub fn current() -> Result<Self> {
74 let mut status = 0;
75 let mut error = std::ptr::null_mut();
76 let raw = unsafe { bridge::security_code_copy_self(&mut status, &mut error) };
77 bridge::required_handle("security_code_copy_self", raw, status, error)
78 .map(|handle| Self { handle })
79 }
80
81 pub fn host(&self) -> Result<Self> {
82 let mut status = 0;
83 let mut error = std::ptr::null_mut();
84 let raw = unsafe {
85 bridge::security_code_copy_host(self.handle.as_ptr(), &mut status, &mut error)
86 };
87 bridge::required_handle("security_code_copy_host", raw, status, error)
88 .map(|handle| Self { handle })
89 }
90
91 pub fn guest_with_attributes(
92 host: Option<&Self>,
93 attributes: Option<&Value>,
94 flags: CodeSigningFlags,
95 ) -> Result<Self> {
96 let attributes = attributes.map(bridge::json_cstring).transpose()?;
97 let mut status = 0;
98 let mut error = std::ptr::null_mut();
99 let raw = unsafe {
100 bridge::security_code_copy_guest_with_attributes(
101 host.map_or(std::ptr::null_mut(), |value| value.handle.as_ptr()),
102 attributes
103 .as_ref()
104 .map_or(std::ptr::null(), |value| value.as_ptr()),
105 flags.bits(),
106 &mut status,
107 &mut error,
108 )
109 };
110 bridge::required_handle(
111 "security_code_copy_guest_with_attributes",
112 raw,
113 status,
114 error,
115 )
116 .map(|handle| Self { handle })
117 }
118
119 pub fn static_code(&self) -> Result<StaticCode> {
120 let mut status = 0;
121 let mut error = std::ptr::null_mut();
122 let raw = unsafe {
123 bridge::security_code_copy_static_code(self.handle.as_ptr(), &mut status, &mut error)
124 };
125 bridge::required_handle("security_code_copy_static_code", raw, status, error)
126 .map(StaticCode::from_handle)
127 }
128
129 pub fn signing_information(&self) -> Result<SigningInformation> {
130 self.static_code()?.signing_information()
131 }
132
133 pub fn task(&self) -> Result<Task> {
134 Task::current()
135 }
136}
137
138#[derive(Debug)]
139pub struct Requirement {
140 handle: bridge::Handle,
141}
142
143impl Requirement {
144 fn from_handle(handle: bridge::Handle) -> Self {
145 Self { handle }
146 }
147
148 fn handle(&self) -> &bridge::Handle {
149 &self.handle
150 }
151
152 pub fn type_id() -> usize {
153 unsafe { bridge::security_requirement_get_type_id() }
154 }
155
156 pub fn from_data(data: &[u8]) -> Result<Self> {
157 let mut status = 0;
158 let mut error = std::ptr::null_mut();
159 let raw = unsafe {
160 bridge::security_requirement_create_with_data(
161 data.as_ptr().cast(),
162 bridge::len_to_isize(data.len())?,
163 &mut status,
164 &mut error,
165 )
166 };
167 bridge::required_handle("security_requirement_create_with_data", raw, status, error)
168 .map(Self::from_handle)
169 }
170
171 pub fn from_string(text: &str) -> Result<Self> {
172 let text = bridge::cstring(text)?;
173 let mut status = 0;
174 let mut error = std::ptr::null_mut();
175 let raw = unsafe {
176 bridge::security_requirement_create_with_string(text.as_ptr(), &mut status, &mut error)
177 };
178 bridge::required_handle(
179 "security_requirement_create_with_string",
180 raw,
181 status,
182 error,
183 )
184 .map(Self::from_handle)
185 }
186
187 pub fn from_string_with_errors(text: &str) -> Result<Self> {
188 let text = bridge::cstring(text)?;
189 let mut status = 0;
190 let mut error = std::ptr::null_mut();
191 let raw = unsafe {
192 bridge::security_requirement_create_with_string_and_errors(
193 text.as_ptr(),
194 &mut status,
195 &mut error,
196 )
197 };
198 bridge::required_handle(
199 "security_requirement_create_with_string_and_errors",
200 raw,
201 status,
202 error,
203 )
204 .map(Self::from_handle)
205 }
206
207 pub fn data(&self) -> Result<Vec<u8>> {
208 let mut status = 0;
209 let mut error = std::ptr::null_mut();
210 let raw = unsafe {
211 bridge::security_requirement_copy_data(self.handle.as_ptr(), &mut status, &mut error)
212 };
213 bridge::required_data("security_requirement_copy_data", raw, status, error)
214 }
215
216 pub fn string(&self) -> Result<String> {
217 let mut status = 0;
218 let mut error = std::ptr::null_mut();
219 let raw = unsafe {
220 bridge::security_requirement_copy_string(self.handle.as_ptr(), &mut status, &mut error)
221 };
222 bridge::required_string("security_requirement_copy_string", raw, status, error)
223 }
224}
225
226#[derive(Debug)]
227pub struct StaticCode {
228 handle: bridge::Handle,
229}
230
231impl StaticCode {
232 fn from_handle(handle: bridge::Handle) -> Self {
233 Self { handle }
234 }
235
236 pub fn type_id() -> usize {
237 unsafe { bridge::security_static_code_get_type_id() }
238 }
239
240 pub fn from_path(path: impl AsRef<Path>) -> Result<Self> {
241 let path = bridge::cstring(&path.as_ref().to_string_lossy())?;
242 let mut status = 0;
243 let mut error = std::ptr::null_mut();
244 let raw = unsafe {
245 bridge::security_static_code_create_with_path(path.as_ptr(), &mut status, &mut error)
246 };
247 bridge::required_handle("security_static_code_create_with_path", raw, status, error)
248 .map(Self::from_handle)
249 }
250
251 pub fn from_path_with_attributes(path: impl AsRef<Path>, attributes: &Value) -> Result<Self> {
252 let path = bridge::cstring(&path.as_ref().to_string_lossy())?;
253 let attributes = bridge::json_cstring(attributes)?;
254 let mut status = 0;
255 let mut error = std::ptr::null_mut();
256 let raw = unsafe {
257 bridge::security_static_code_create_with_path_and_attributes(
258 path.as_ptr(),
259 attributes.as_ptr(),
260 &mut status,
261 &mut error,
262 )
263 };
264 bridge::required_handle(
265 "security_static_code_create_with_path_and_attributes",
266 raw,
267 status,
268 error,
269 )
270 .map(Self::from_handle)
271 }
272
273 pub fn check_validity(&self) -> Result<()> {
274 let mut error = std::ptr::null_mut();
275 let status = unsafe {
276 bridge::security_static_code_check_validity(self.handle.as_ptr(), &mut error)
277 };
278 bridge::status_result("security_static_code_check_validity", status, error)
279 }
280
281 pub fn check_validity_with_errors(
282 &self,
283 flags: CodeSigningFlags,
284 requirement: Option<&Requirement>,
285 ) -> Result<()> {
286 let mut error = std::ptr::null_mut();
287 let status = unsafe {
288 bridge::security_static_code_check_validity_with_errors(
289 self.handle.as_ptr(),
290 flags.bits(),
291 requirement.map_or(std::ptr::null_mut(), |value| value.handle().as_ptr()),
292 &mut error,
293 )
294 };
295 bridge::status_result(
296 "security_static_code_check_validity_with_errors",
297 status,
298 error,
299 )
300 }
301
302 pub fn check_static_validity(
303 &self,
304 flags: CodeSigningFlags,
305 requirement: Option<&Requirement>,
306 ) -> Result<()> {
307 let mut error = std::ptr::null_mut();
308 let status = unsafe {
309 bridge::security_static_code_check_static_validity(
310 self.handle.as_ptr(),
311 flags.bits(),
312 requirement.map_or(std::ptr::null_mut(), |value| value.handle().as_ptr()),
313 &mut error,
314 )
315 };
316 bridge::status_result("security_static_code_check_static_validity", status, error)
317 }
318
319 pub fn check_static_validity_with_errors(
320 &self,
321 flags: CodeSigningFlags,
322 requirement: Option<&Requirement>,
323 ) -> Result<()> {
324 let mut error = std::ptr::null_mut();
325 let status = unsafe {
326 bridge::security_static_code_check_static_validity_with_errors(
327 self.handle.as_ptr(),
328 flags.bits(),
329 requirement.map_or(std::ptr::null_mut(), |value| value.handle().as_ptr()),
330 &mut error,
331 )
332 };
333 bridge::status_result(
334 "security_static_code_check_static_validity_with_errors",
335 status,
336 error,
337 )
338 }
339
340 pub fn path(&self) -> Result<PathBuf> {
341 let mut status = 0;
342 let mut error = std::ptr::null_mut();
343 let raw = unsafe {
344 bridge::security_static_code_copy_path(self.handle.as_ptr(), &mut status, &mut error)
345 };
346 bridge::required_string("security_static_code_copy_path", raw, status, error)
347 .map(PathBuf::from)
348 }
349
350 pub fn designated_requirement(&self) -> Result<String> {
351 let mut status = 0;
352 let mut error = std::ptr::null_mut();
353 let raw = unsafe {
354 bridge::security_static_code_copy_designated_requirement(
355 self.handle.as_ptr(),
356 &mut status,
357 &mut error,
358 )
359 };
360 bridge::required_string(
361 "security_static_code_copy_designated_requirement",
362 raw,
363 status,
364 error,
365 )
366 }
367
368 pub fn signing_information(&self) -> Result<SigningInformation> {
369 let mut status = 0;
370 let mut error = std::ptr::null_mut();
371 let raw = unsafe {
372 bridge::security_static_code_copy_signing_information(
373 self.handle.as_ptr(),
374 &mut status,
375 &mut error,
376 )
377 };
378 let value: Value = bridge::required_json(
379 "security_static_code_copy_signing_information",
380 raw,
381 status,
382 error,
383 )?;
384 Ok(SigningInformation {
385 identifier: find_string(&value, &["identifier", "Identifier"]),
386 team_identifier: find_string(&value, &["teamid", "TeamIdentifier", "teamIdentifier"]),
387 entitlements: find_object(
388 &value,
389 &[
390 "entitlements-dict",
391 "EntitlementsDict",
392 "entitlements",
393 "Entitlements",
394 ],
395 )
396 .map(json_object_to_map)
397 .unwrap_or_default(),
398 sandboxed: matches!(
399 find_object(
400 &value,
401 &[
402 "entitlements-dict",
403 "EntitlementsDict",
404 "entitlements",
405 "Entitlements",
406 ],
407 )
408 .and_then(|value| value.get("com.apple.security.app-sandbox")),
409 Some(Value::Bool(true))
410 ),
411 status: find_integer(&value, &["status", "Status"])
412 .and_then(|value| u32::try_from(value).ok()),
413 })
414 }
415
416 pub fn validate_file_resource(
417 &self,
418 relative_path: &str,
419 data: &[u8],
420 flags: CodeSigningFlags,
421 ) -> Result<()> {
422 let relative_path = bridge::cstring(relative_path)?;
423 let mut error = std::ptr::null_mut();
424 let status = unsafe {
425 bridge::security_static_code_validate_file_resource(
426 self.handle.as_ptr(),
427 relative_path.as_ptr(),
428 data.as_ptr().cast(),
429 bridge::len_to_isize(data.len())?,
430 flags.bits(),
431 &mut error,
432 )
433 };
434 bridge::status_result("security_static_code_validate_file_resource", status, error)
435 }
436
437 pub fn map_memory(&self, flags: CodeSigningFlags) -> Result<()> {
438 let mut error = std::ptr::null_mut();
439 let status = unsafe {
440 bridge::security_static_code_map_memory(self.handle.as_ptr(), flags.bits(), &mut error)
441 };
442 bridge::status_result("security_static_code_map_memory", status, error)
443 }
444}
445
446#[derive(Debug)]
447pub struct Task {
448 handle: bridge::Handle,
449}
450
451impl Task {
452 pub fn type_id() -> usize {
453 unsafe { bridge::security_task_get_type_id() }
454 }
455
456 pub fn current() -> Result<Self> {
457 let mut status = 0;
458 let mut error = std::ptr::null_mut();
459 let raw = unsafe { bridge::security_task_create_from_self(&mut status, &mut error) };
460 bridge::required_handle("security_task_create_from_self", raw, status, error)
461 .map(|handle| Self { handle })
462 }
463
464 pub fn current_with_audit_token() -> Result<Self> {
465 let mut status = 0;
466 let mut error = std::ptr::null_mut();
467 let raw = unsafe {
468 bridge::security_task_create_from_current_audit_token(&mut status, &mut error)
469 };
470 bridge::required_handle(
471 "security_task_create_from_current_audit_token",
472 raw,
473 status,
474 error,
475 )
476 .map(|handle| Self { handle })
477 }
478
479 pub fn signing_identifier(&self) -> Result<Option<String>> {
480 let mut status = 0;
481 let mut error = std::ptr::null_mut();
482 let raw = unsafe {
483 bridge::security_task_copy_signing_identifier(
484 self.handle.as_ptr(),
485 &mut status,
486 &mut error,
487 )
488 };
489 if raw.is_null() && status == 0 {
490 Ok(None)
491 } else {
492 bridge::required_string("security_task_copy_signing_identifier", raw, status, error)
493 .map(Some)
494 }
495 }
496
497 pub fn entitlement(&self, entitlement: &str) -> Result<Option<Value>> {
498 let entitlement = bridge::cstring(entitlement)?;
499 let mut status = 0;
500 let mut error = std::ptr::null_mut();
501 let raw = unsafe {
502 bridge::security_task_copy_value_for_entitlement(
503 self.handle.as_ptr(),
504 entitlement.as_ptr(),
505 &mut status,
506 &mut error,
507 )
508 };
509 if raw.is_null() && status == 0 {
510 return Ok(None);
511 }
512 let value: Value = bridge::required_json(
513 "security_task_copy_value_for_entitlement",
514 raw,
515 status,
516 error,
517 )?;
518 Ok(Some(value))
519 }
520
521 pub fn entitlements(&self, entitlements: &[&str]) -> Result<Value> {
522 let entitlements = bridge::json_cstring(&entitlements)?;
523 let mut status = 0;
524 let mut error = std::ptr::null_mut();
525 let raw = unsafe {
526 bridge::security_task_copy_values_for_entitlements(
527 self.handle.as_ptr(),
528 entitlements.as_ptr(),
529 &mut status,
530 &mut error,
531 )
532 };
533 bridge::required_json(
534 "security_task_copy_values_for_entitlements",
535 raw,
536 status,
537 error,
538 )
539 }
540}
541
542fn find_object<'a>(value: &'a Value, keys: &[&str]) -> Option<&'a serde_json::Map<String, Value>> {
543 keys.iter()
544 .find_map(|key| value.get(*key))
545 .and_then(Value::as_object)
546}
547
548fn find_string(value: &Value, keys: &[&str]) -> Option<String> {
549 keys.iter()
550 .find_map(|key| value.get(*key))
551 .and_then(Value::as_str)
552 .map(ToOwned::to_owned)
553}
554
555fn find_integer(value: &Value, keys: &[&str]) -> Option<i64> {
556 keys.iter()
557 .find_map(|key| value.get(*key))
558 .and_then(Value::as_i64)
559}
560
561fn json_object_to_map(object: &serde_json::Map<String, Value>) -> BTreeMap<String, SigningValue> {
562 object
563 .iter()
564 .map(|(key, value)| (key.clone(), signing_value(value)))
565 .collect()
566}
567
568fn signing_value(value: &Value) -> SigningValue {
569 match value {
570 Value::Bool(value) => SigningValue::Boolean(*value),
571 Value::Number(value) => value.as_i64().map_or_else(
572 || SigningValue::Unknown(value.to_string()),
573 SigningValue::Integer,
574 ),
575 Value::String(value) => SigningValue::String(value.clone()),
576 Value::Array(values) => {
577 if let Some(data) = data_from_wrapped_json(value) {
578 SigningValue::Data(data)
579 } else {
580 SigningValue::Array(values.iter().map(signing_value).collect())
581 }
582 }
583 Value::Object(object) => {
584 if let Some(data) = data_from_wrapped_json(value) {
585 SigningValue::Data(data)
586 } else {
587 SigningValue::Dictionary(json_object_to_map(object))
588 }
589 }
590 Value::Null => SigningValue::Unknown("null".to_owned()),
591 }
592}
593
594fn data_from_wrapped_json(value: &Value) -> Option<Vec<u8>> {
595 let object = value.as_object()?;
596 if object.get("_type")?.as_str()? != "data" {
597 return None;
598 }
599 let base64 = object.get("base64")?.as_str()?;
600 base64::engine::general_purpose::STANDARD
601 .decode(base64)
602 .ok()
603}