1use {
8 crate::{
9 cryptography::{Digest, DigestType},
10 embedded_signature::{
11 read_and_validate_blob_header, Blob, CodeSigningMagic, CodeSigningSlot,
12 },
13 error::AppleCodesignError,
14 macho::{MachoTarget, Platform},
15 },
16 scroll::{IOwrite, Pread},
17 semver::Version,
18 std::{borrow::Cow, collections::BTreeMap, io::Write, str::FromStr},
19};
20
21bitflags::bitflags! {
22 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
27 pub struct CodeSignatureFlags: u32 {
28 const HOST = 0x0001;
30 const ADHOC = 0x0002;
32 const FORCE_HARD = 0x0100;
34 const FORCE_KILL = 0x0200;
36 const FORCE_EXPIRATION = 0x0400;
38 const RESTRICT = 0x0800;
40 const ENFORCEMENT = 0x1000;
42 const LIBRARY_VALIDATION = 0x2000;
44 const RUNTIME = 0x10000;
46 const LINKER_SIGNED = 0x20000;
50 }
51}
52
53impl FromStr for CodeSignatureFlags {
54 type Err = AppleCodesignError;
55
56 fn from_str(s: &str) -> Result<Self, Self::Err> {
57 match s {
58 "host" => Ok(Self::HOST),
59 "hard" => Ok(Self::FORCE_HARD),
60 "kill" => Ok(Self::FORCE_KILL),
61 "expires" => Ok(Self::FORCE_EXPIRATION),
62 "library" => Ok(Self::LIBRARY_VALIDATION),
63 "runtime" => Ok(Self::RUNTIME),
64 "linker-signed" => Ok(Self::LINKER_SIGNED),
65 _ => Err(AppleCodesignError::CodeSignatureUnknownFlag(s.to_string())),
66 }
67 }
68}
69
70impl CodeSignatureFlags {
71 pub fn all_user_configurable() -> [&'static str; 7] {
75 [
76 "host",
77 "hard",
78 "kill",
79 "expires",
80 "library",
81 "runtime",
82 "linker-signed",
83 ]
84 }
85
86 pub fn from_strs(s: &[&str]) -> Result<CodeSignatureFlags, AppleCodesignError> {
88 let mut flags = CodeSignatureFlags::empty();
89
90 for s in s {
91 flags |= Self::from_str(s)?;
92 }
93
94 Ok(flags)
95 }
96}
97
98bitflags::bitflags! {
99 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
101 pub struct ExecutableSegmentFlags: u64 {
102 const MAIN_BINARY = 0x0001;
104 const ALLOW_UNSIGNED = 0x0010;
106 const DEBUGGER = 0x0020;
108 const JIT = 0x0040;
110 const SKIP_LIBRARY_VALIDATION = 0x0080;
112 const CAN_LOAD_CD_HASH = 0x0100;
114 const CAN_EXEC_CD_HASH = 0x0200;
116 }
117}
118
119impl FromStr for ExecutableSegmentFlags {
120 type Err = AppleCodesignError;
121
122 fn from_str(s: &str) -> Result<Self, Self::Err> {
123 match s {
124 "main-binary" => Ok(Self::MAIN_BINARY),
125 "allow-unsigned" => Ok(Self::ALLOW_UNSIGNED),
126 "debugger" => Ok(Self::DEBUGGER),
127 "jit" => Ok(Self::JIT),
128 "skip-library-validation" => Ok(Self::SKIP_LIBRARY_VALIDATION),
129 "can-load-cd-hash" => Ok(Self::CAN_LOAD_CD_HASH),
130 "can-exec-cd-hash" => Ok(Self::CAN_EXEC_CD_HASH),
131 _ => Err(AppleCodesignError::ExecutableSegmentUnknownFlag(
132 s.to_string(),
133 )),
134 }
135 }
136}
137
138#[derive(Clone, Copy, Debug, Eq, PartialEq)]
140#[repr(u32)]
141pub enum CodeDirectoryVersion {
142 Initial = 0x20000,
143 SupportsScatter = 0x20100,
144 SupportsTeamId = 0x20200,
145 SupportsCodeLimit64 = 0x20300,
146 SupportsExecutableSegment = 0x20400,
147 SupportsRuntime = 0x20500,
148 SupportsLinkage = 0x20600,
149}
150
151#[repr(C)]
152pub struct Scatter {
153 count: u32,
155 base: u32,
157 target_offset: u64,
159 spare: u64,
161}
162
163fn get_hashes(data: &[u8], offset: usize, count: usize, hash_size: usize) -> Vec<Digest<'_>> {
164 data[offset..offset + (count * hash_size)]
165 .chunks(hash_size)
166 .map(|data| Digest { data: data.into() })
167 .collect()
168}
169
170#[derive(Debug, Default)]
179pub struct CodeDirectoryBlob<'a> {
180 pub version: u32,
182 pub flags: CodeSignatureFlags,
184 pub code_limit: u32,
192 pub digest_size: u8,
194 pub digest_type: DigestType,
196 pub platform: u8,
198 pub page_size: u32,
200 pub spare2: u32,
202 pub scatter_offset: Option<u32>,
205 pub spare3: Option<u32>,
210 pub code_limit_64: Option<u64>,
212 pub exec_seg_base: Option<u64>,
215 pub exec_seg_limit: Option<u64>,
217 pub exec_seg_flags: Option<ExecutableSegmentFlags>,
219 pub runtime: Option<u32>,
221 pub pre_encrypt_offset: Option<u32>,
222 pub linkage_hash_type: Option<u8>,
224 pub linkage_truncated: Option<u8>,
225 pub spare4: Option<u16>,
226 pub linkage_offset: Option<u32>,
227 pub linkage_size: Option<u32>,
228
229 pub ident: Cow<'a, str>,
231 pub team_name: Option<Cow<'a, str>>,
232 pub code_digests: Vec<Digest<'a>>,
233 pub special_digests: BTreeMap<CodeSigningSlot, Digest<'a>>,
234}
235
236impl<'a> Blob<'a> for CodeDirectoryBlob<'a> {
237 fn magic() -> u32 {
238 u32::from(CodeSigningMagic::CodeDirectory)
239 }
240
241 fn from_blob_bytes(data: &'a [u8]) -> Result<Self, AppleCodesignError> {
242 read_and_validate_blob_header(data, Self::magic(), "code directory blob")?;
243
244 let offset = &mut 8;
245
246 let version = data.gread_with(offset, scroll::BE)?;
247 let flags = data.gread_with::<u32>(offset, scroll::BE)?;
248 let flags = CodeSignatureFlags::from_bits_retain(flags);
249 assert_eq!(*offset, 0x10);
250 let digest_offset = data.gread_with::<u32>(offset, scroll::BE)?;
251 let ident_offset = data.gread_with::<u32>(offset, scroll::BE)?;
252 let n_special_slots = data.gread_with::<u32>(offset, scroll::BE)?;
253 let n_code_slots = data.gread_with::<u32>(offset, scroll::BE)?;
254 assert_eq!(*offset, 0x20);
255 let code_limit = data.gread_with(offset, scroll::BE)?;
256 let digest_size = data.gread_with(offset, scroll::BE)?;
257 let digest_type = data.gread_with::<u8>(offset, scroll::BE)?.into();
258 let platform = data.gread_with(offset, scroll::BE)?;
259 let page_size = data.gread_with::<u8>(offset, scroll::BE)?;
260 let page_size = 2u32.pow(page_size as u32);
261 let spare2 = data.gread_with(offset, scroll::BE)?;
262
263 let scatter_offset = if version >= CodeDirectoryVersion::SupportsScatter as u32 {
264 let v = data.gread_with(offset, scroll::BE)?;
265
266 if v != 0 {
267 Some(v)
268 } else {
269 None
270 }
271 } else {
272 None
273 };
274 let team_offset = if version >= CodeDirectoryVersion::SupportsTeamId as u32 {
275 assert_eq!(*offset, 0x30);
276 let v = data.gread_with::<u32>(offset, scroll::BE)?;
277
278 if v != 0 {
279 Some(v)
280 } else {
281 None
282 }
283 } else {
284 None
285 };
286
287 let (spare3, code_limit_64) = if version >= CodeDirectoryVersion::SupportsCodeLimit64 as u32
288 {
289 (
290 Some(data.gread_with(offset, scroll::BE)?),
291 Some(data.gread_with(offset, scroll::BE)?),
292 )
293 } else {
294 (None, None)
295 };
296
297 let (exec_seg_base, exec_seg_limit, exec_seg_flags) =
298 if version >= CodeDirectoryVersion::SupportsExecutableSegment as u32 {
299 assert_eq!(*offset, 0x40);
300 (
301 Some(data.gread_with(offset, scroll::BE)?),
302 Some(data.gread_with(offset, scroll::BE)?),
303 Some(data.gread_with::<u64>(offset, scroll::BE)?),
304 )
305 } else {
306 (None, None, None)
307 };
308
309 let exec_seg_flags = exec_seg_flags.map(ExecutableSegmentFlags::from_bits_retain);
310
311 let (runtime, pre_encrypt_offset) =
312 if version >= CodeDirectoryVersion::SupportsRuntime as u32 {
313 assert_eq!(*offset, 0x58);
314 (
315 Some(data.gread_with(offset, scroll::BE)?),
316 Some(data.gread_with(offset, scroll::BE)?),
317 )
318 } else {
319 (None, None)
320 };
321
322 let (linkage_hash_type, linkage_truncated, spare4, linkage_offset, linkage_size) =
323 if version >= CodeDirectoryVersion::SupportsLinkage as u32 {
324 assert_eq!(*offset, 0x60);
325 (
326 Some(data.gread_with(offset, scroll::BE)?),
327 Some(data.gread_with(offset, scroll::BE)?),
328 Some(data.gread_with(offset, scroll::BE)?),
329 Some(data.gread_with(offset, scroll::BE)?),
330 Some(data.gread_with(offset, scroll::BE)?),
331 )
332 } else {
333 (None, None, None, None, None)
334 };
335
336 let ident = match data[ident_offset as usize..]
338 .split(|&b| b == 0)
339 .map(std::str::from_utf8)
340 .next()
341 {
342 Some(res) => {
343 Cow::from(res.map_err(|_| AppleCodesignError::CodeDirectoryMalformedIdentifier)?)
344 }
345 None => {
346 return Err(AppleCodesignError::CodeDirectoryMalformedIdentifier);
347 }
348 };
349
350 let team_name = if let Some(team_offset) = team_offset {
351 match data[team_offset as usize..]
352 .split(|&b| b == 0)
353 .map(std::str::from_utf8)
354 .next()
355 {
356 Some(res) => {
357 Some(Cow::from(res.map_err(|_| {
358 AppleCodesignError::CodeDirectoryMalformedTeam
359 })?))
360 }
361 None => {
362 return Err(AppleCodesignError::CodeDirectoryMalformedTeam);
363 }
364 }
365 } else {
366 None
367 };
368
369 let code_digests = get_hashes(
370 data,
371 digest_offset as usize,
372 n_code_slots as usize,
373 digest_size as usize,
374 );
375
376 let special_digests = get_hashes(
377 data,
378 (digest_offset - (digest_size as u32 * n_special_slots)) as usize,
379 n_special_slots as usize,
380 digest_size as usize,
381 )
382 .into_iter()
383 .enumerate()
384 .map(|(i, h)| (CodeSigningSlot::from(n_special_slots - i as u32), h))
385 .collect();
386
387 Ok(Self {
388 version,
389 flags,
390 code_limit,
391 digest_size,
392 digest_type,
393 platform,
394 page_size,
395 spare2,
396 scatter_offset,
397 spare3,
398 code_limit_64,
399 exec_seg_base,
400 exec_seg_limit,
401 exec_seg_flags,
402 runtime,
403 pre_encrypt_offset,
404 linkage_hash_type,
405 linkage_truncated,
406 spare4,
407 linkage_offset,
408 linkage_size,
409 ident,
410 team_name,
411 code_digests,
412 special_digests,
413 })
414 }
415
416 fn serialize_payload(&self) -> Result<Vec<u8>, AppleCodesignError> {
417 let mut cursor = std::io::Cursor::new(Vec::<u8>::new());
418
419 cursor.iowrite_with(self.version, scroll::BE)?;
423 cursor.iowrite_with(self.flags.bits(), scroll::BE)?;
424 let digest_offset_cursor_position = cursor.position();
425 cursor.iowrite_with(0u32, scroll::BE)?;
426 let ident_offset_cursor_position = cursor.position();
427 cursor.iowrite_with(0u32, scroll::BE)?;
428 assert_eq!(cursor.position(), 0x10);
429
430 let highest_slot = self
439 .special_digests
440 .keys()
441 .map(|slot| u32::from(*slot))
442 .max()
443 .unwrap_or(0);
444
445 cursor.iowrite_with(highest_slot, scroll::BE)?;
446 cursor.iowrite_with(self.code_digests.len() as u32, scroll::BE)?;
447 cursor.iowrite_with(self.code_limit, scroll::BE)?;
448 cursor.iowrite_with(self.digest_size, scroll::BE)?;
449 cursor.iowrite_with(u8::from(self.digest_type), scroll::BE)?;
450 cursor.iowrite_with(self.platform, scroll::BE)?;
451 cursor.iowrite_with(self.page_size.trailing_zeros() as u8, scroll::BE)?;
452 assert_eq!(cursor.position(), 0x20);
453 cursor.iowrite_with(self.spare2, scroll::BE)?;
454
455 let mut scatter_offset_cursor_position = None;
456 let mut team_offset_cursor_position = None;
457
458 if self.version >= CodeDirectoryVersion::SupportsScatter as u32 {
459 scatter_offset_cursor_position = Some(cursor.position());
460 cursor.iowrite_with(self.scatter_offset.unwrap_or(0), scroll::BE)?;
461
462 if self.version >= CodeDirectoryVersion::SupportsTeamId as u32 {
463 team_offset_cursor_position = Some(cursor.position());
464 cursor.iowrite_with(0u32, scroll::BE)?;
465
466 if self.version >= CodeDirectoryVersion::SupportsCodeLimit64 as u32 {
467 cursor.iowrite_with(self.spare3.unwrap_or(0), scroll::BE)?;
468 assert_eq!(cursor.position(), 0x30);
469 cursor.iowrite_with(self.code_limit_64.unwrap_or(0), scroll::BE)?;
470
471 if self.version >= CodeDirectoryVersion::SupportsExecutableSegment as u32 {
472 cursor.iowrite_with(self.exec_seg_base.unwrap_or(0), scroll::BE)?;
473 assert_eq!(cursor.position(), 0x40);
474 cursor.iowrite_with(self.exec_seg_limit.unwrap_or(0), scroll::BE)?;
475 cursor.iowrite_with(
476 self.exec_seg_flags
477 .unwrap_or_else(ExecutableSegmentFlags::empty)
478 .bits(),
479 scroll::BE,
480 )?;
481
482 if self.version >= CodeDirectoryVersion::SupportsRuntime as u32 {
483 assert_eq!(cursor.position(), 0x50);
484 cursor.iowrite_with(self.runtime.unwrap_or(0), scroll::BE)?;
485 cursor
486 .iowrite_with(self.pre_encrypt_offset.unwrap_or(0), scroll::BE)?;
487
488 if self.version >= CodeDirectoryVersion::SupportsLinkage as u32 {
489 cursor.iowrite_with(
490 self.linkage_hash_type.unwrap_or(0),
491 scroll::BE,
492 )?;
493 cursor.iowrite_with(
494 self.linkage_truncated.unwrap_or(0),
495 scroll::BE,
496 )?;
497 cursor.iowrite_with(self.spare4.unwrap_or(0), scroll::BE)?;
498 cursor
499 .iowrite_with(self.linkage_offset.unwrap_or(0), scroll::BE)?;
500 assert_eq!(cursor.position(), 0x60);
501 cursor.iowrite_with(self.linkage_size.unwrap_or(0), scroll::BE)?;
502 }
503 }
504 }
505 }
506 }
507 }
508
509 let identity_offset = cursor.position();
512 cursor.write_all(self.ident.as_bytes())?;
513 cursor.write_all(b"\0")?;
514
515 let team_offset = cursor.position();
516 if team_offset_cursor_position.is_some() {
517 if let Some(team_name) = &self.team_name {
518 cursor.write_all(team_name.as_bytes())?;
519 cursor.write_all(b"\0")?;
520 }
521 }
522
523 for slot_index in (1..highest_slot + 1).rev() {
528 let slot = CodeSigningSlot::from(slot_index);
529 assert!(
530 slot.is_code_directory_specials_expressible(),
531 "slot is expressible in code directory special digests"
532 );
533
534 if let Some(digest) = self.special_digests.get(&slot) {
535 assert_eq!(
536 digest.data.len(),
537 self.digest_size as usize,
538 "special slot digest length matches expected length"
539 );
540 cursor.write_all(&digest.data)?;
541 } else {
542 cursor.write_all(&b"\0".repeat(self.digest_size as usize))?;
543 }
544 }
545
546 let code_digests_start_offset = cursor.position();
547
548 for digest in &self.code_digests {
549 cursor.write_all(&digest.data)?;
550 }
551
552 cursor.set_position(digest_offset_cursor_position);
557 cursor.iowrite_with(code_digests_start_offset as u32 + 8, scroll::BE)?;
558
559 cursor.set_position(ident_offset_cursor_position);
560 cursor.iowrite_with(identity_offset as u32 + 8, scroll::BE)?;
561
562 if scatter_offset_cursor_position.is_some() && self.scatter_offset.is_some() {
563 return Err(AppleCodesignError::Unimplemented("scatter offset"));
564 }
565
566 if let Some(offset) = team_offset_cursor_position {
567 if self.team_name.is_some() {
568 cursor.set_position(offset);
569 cursor.iowrite_with(team_offset as u32 + 8, scroll::BE)?;
570 }
571 }
572
573 Ok(cursor.into_inner())
574 }
575}
576
577impl<'a> CodeDirectoryBlob<'a> {
578 pub fn slot_digests(&self) -> &BTreeMap<CodeSigningSlot, Digest<'a>> {
580 &self.special_digests
581 }
582
583 pub fn slot_digest(&self, slot: CodeSigningSlot) -> Option<&Digest<'a>> {
585 self.special_digests.get(&slot)
586 }
587
588 pub fn set_slot_digest(
590 &mut self,
591 slot: CodeSigningSlot,
592 digest: impl Into<Digest<'a>>,
593 ) -> Result<(), AppleCodesignError> {
594 if !slot.is_code_directory_specials_expressible() {
595 return Err(AppleCodesignError::LogicError(format!(
596 "slot {slot:?} cannot have its digest expressed on code directories"
597 )));
598 }
599
600 let digest = digest.into();
601
602 if digest.data.len() != self.digest_size as usize {
603 return Err(AppleCodesignError::LogicError(format!(
604 "attempt to assign digest for slot {:?} whose length {} does not match code directory digest length {}",
605 slot, digest.data.len(), self.digest_size
606
607 )));
608 }
609
610 self.special_digests.insert(slot, digest);
611
612 Ok(())
613 }
614
615 pub fn adjust_version(&mut self, target: Option<MachoTarget>) -> u32 {
619 let old_version = self.version;
620
621 let mut minimum_version = CodeDirectoryVersion::Initial;
622
623 if self.scatter_offset.is_some() {
624 minimum_version = CodeDirectoryVersion::SupportsScatter;
625 }
626 if self.team_name.is_some() {
627 minimum_version = CodeDirectoryVersion::SupportsTeamId;
628 }
629 if self.spare3.is_some() || self.code_limit_64.is_some() {
630 minimum_version = CodeDirectoryVersion::SupportsCodeLimit64;
631 }
632 if self.exec_seg_base.is_some()
633 || self.exec_seg_limit.is_some()
634 || self.exec_seg_flags.is_some()
635 {
636 minimum_version = CodeDirectoryVersion::SupportsExecutableSegment;
637 }
638 if self.runtime.is_some() || self.pre_encrypt_offset.is_some() {
639 minimum_version = CodeDirectoryVersion::SupportsRuntime;
640 }
641 if self.linkage_hash_type.is_some()
642 || self.linkage_truncated.is_some()
643 || self.spare4.is_some()
644 || self.linkage_offset.is_some()
645 || self.linkage_size.is_some()
646 {
647 minimum_version = CodeDirectoryVersion::SupportsLinkage;
648 }
649
650 if let Some(target) = target {
653 let target_minimum = match target.platform {
654 Platform::IOs | Platform::IosSimulator => {
656 if target.minimum_os_version >= Version::new(15, 0, 0) {
657 CodeDirectoryVersion::SupportsExecutableSegment
658 } else {
659 CodeDirectoryVersion::Initial
660 }
661 }
662 Platform::MacOs => {
664 if target.minimum_os_version >= Version::new(12, 0, 0) {
665 CodeDirectoryVersion::SupportsExecutableSegment
666 } else {
667 CodeDirectoryVersion::Initial
668 }
669 }
670 _ => CodeDirectoryVersion::Initial,
671 };
672
673 if target_minimum as u32 > minimum_version as u32 {
674 minimum_version = target_minimum;
675 }
676 }
677
678 self.version = minimum_version as u32;
679
680 old_version
681 }
682
683 pub fn clear_newer_fields(&mut self) {
693 if self.version < CodeDirectoryVersion::SupportsScatter as u32 {
694 self.scatter_offset = None;
695 }
696 if self.version < CodeDirectoryVersion::SupportsTeamId as u32 {
697 self.team_name = None;
698 }
699 if self.version < CodeDirectoryVersion::SupportsCodeLimit64 as u32 {
700 self.spare3 = None;
701 self.code_limit_64 = None;
702 }
703 if self.version < CodeDirectoryVersion::SupportsExecutableSegment as u32 {
704 self.exec_seg_base = None;
705 self.exec_seg_limit = None;
706 self.exec_seg_flags = None;
707 }
708 if self.version < CodeDirectoryVersion::SupportsRuntime as u32 {
709 self.runtime = None;
710 self.pre_encrypt_offset = None;
711 }
712 if self.version < CodeDirectoryVersion::SupportsLinkage as u32 {
713 self.linkage_hash_type = None;
714 self.linkage_truncated = None;
715 self.spare4 = None;
716 self.linkage_offset = None;
717 self.linkage_size = None;
718 }
719 }
720
721 pub fn to_owned(&self) -> CodeDirectoryBlob<'static> {
722 CodeDirectoryBlob {
723 version: self.version,
724 flags: self.flags,
725 code_limit: self.code_limit,
726 digest_size: self.digest_size,
727 digest_type: self.digest_type,
728 platform: self.platform,
729 page_size: self.page_size,
730 spare2: self.spare2,
731 scatter_offset: self.scatter_offset,
732 spare3: self.spare3,
733 code_limit_64: self.code_limit_64,
734 exec_seg_base: self.exec_seg_base,
735 exec_seg_limit: self.exec_seg_limit,
736 exec_seg_flags: self.exec_seg_flags,
737 runtime: self.runtime,
738 pre_encrypt_offset: self.pre_encrypt_offset,
739 linkage_hash_type: self.linkage_hash_type,
740 linkage_truncated: self.linkage_truncated,
741 spare4: self.spare4,
742 linkage_offset: self.linkage_offset,
743 linkage_size: self.linkage_size,
744 ident: Cow::Owned(self.ident.clone().into_owned()),
745 team_name: self
746 .team_name
747 .as_ref()
748 .map(|x| Cow::Owned(x.clone().into_owned())),
749 code_digests: self
750 .code_digests
751 .iter()
752 .map(|h| h.to_owned())
753 .collect::<Vec<_>>(),
754 special_digests: self
755 .special_digests
756 .iter()
757 .map(|(k, v)| (k.to_owned(), v.to_owned()))
758 .collect::<BTreeMap<_, _>>(),
759 }
760 }
761}
762
763#[cfg(test)]
764mod tests {
765 use super::*;
766
767 #[test]
768 fn code_signature_flags_from_str() {
769 assert_eq!(
770 CodeSignatureFlags::from_str("host").unwrap(),
771 CodeSignatureFlags::HOST
772 );
773 assert_eq!(
774 CodeSignatureFlags::from_str("hard").unwrap(),
775 CodeSignatureFlags::FORCE_HARD
776 );
777 assert_eq!(
778 CodeSignatureFlags::from_str("kill").unwrap(),
779 CodeSignatureFlags::FORCE_KILL
780 );
781 assert_eq!(
782 CodeSignatureFlags::from_str("expires").unwrap(),
783 CodeSignatureFlags::FORCE_EXPIRATION
784 );
785 assert_eq!(
786 CodeSignatureFlags::from_str("library").unwrap(),
787 CodeSignatureFlags::LIBRARY_VALIDATION
788 );
789 assert_eq!(
790 CodeSignatureFlags::from_str("runtime").unwrap(),
791 CodeSignatureFlags::RUNTIME
792 );
793 assert_eq!(
794 CodeSignatureFlags::from_str("linker-signed").unwrap(),
795 CodeSignatureFlags::LINKER_SIGNED
796 );
797 }
798}