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