Skip to main content

security/
code.rs

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}