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 && let Some(team_name) = &self.team_name {
518 cursor.write_all(team_name.as_bytes())?;
519 cursor.write_all(b"\0")?;
520 }
521
522 for slot_index in (1..highest_slot + 1).rev() {
527 let slot = CodeSigningSlot::from(slot_index);
528 assert!(
529 slot.is_code_directory_specials_expressible(),
530 "slot is expressible in code directory special digests"
531 );
532
533 if let Some(digest) = self.special_digests.get(&slot) {
534 assert_eq!(
535 digest.data.len(),
536 self.digest_size as usize,
537 "special slot digest length matches expected length"
538 );
539 cursor.write_all(&digest.data)?;
540 } else {
541 cursor.write_all(&b"\0".repeat(self.digest_size as usize))?;
542 }
543 }
544
545 let code_digests_start_offset = cursor.position();
546
547 for digest in &self.code_digests {
548 cursor.write_all(&digest.data)?;
549 }
550
551 cursor.set_position(digest_offset_cursor_position);
556 cursor.iowrite_with(code_digests_start_offset as u32 + 8, scroll::BE)?;
557
558 cursor.set_position(ident_offset_cursor_position);
559 cursor.iowrite_with(identity_offset as u32 + 8, scroll::BE)?;
560
561 if scatter_offset_cursor_position.is_some() && self.scatter_offset.is_some() {
562 return Err(AppleCodesignError::Unimplemented("scatter offset"));
563 }
564
565 if let Some(offset) = team_offset_cursor_position
566 && self.team_name.is_some() {
567 cursor.set_position(offset);
568 cursor.iowrite_with(team_offset as u32 + 8, scroll::BE)?;
569 }
570
571 Ok(cursor.into_inner())
572 }
573}
574
575impl<'a> CodeDirectoryBlob<'a> {
576 pub fn slot_digests(&self) -> &BTreeMap<CodeSigningSlot, Digest<'a>> {
578 &self.special_digests
579 }
580
581 pub fn slot_digest(&self, slot: CodeSigningSlot) -> Option<&Digest<'a>> {
583 self.special_digests.get(&slot)
584 }
585
586 pub fn set_slot_digest(
588 &mut self,
589 slot: CodeSigningSlot,
590 digest: impl Into<Digest<'a>>,
591 ) -> Result<(), AppleCodesignError> {
592 if !slot.is_code_directory_specials_expressible() {
593 return Err(AppleCodesignError::LogicError(format!(
594 "slot {slot:?} cannot have its digest expressed on code directories"
595 )));
596 }
597
598 let digest = digest.into();
599
600 if digest.data.len() != self.digest_size as usize {
601 return Err(AppleCodesignError::LogicError(format!(
602 "attempt to assign digest for slot {:?} whose length {} does not match code directory digest length {}",
603 slot, digest.data.len(), self.digest_size
604
605 )));
606 }
607
608 self.special_digests.insert(slot, digest);
609
610 Ok(())
611 }
612
613 pub fn adjust_version(&mut self, target: Option<MachoTarget>) -> u32 {
617 let old_version = self.version;
618
619 let mut minimum_version = CodeDirectoryVersion::Initial;
620
621 if self.scatter_offset.is_some() {
622 minimum_version = CodeDirectoryVersion::SupportsScatter;
623 }
624 if self.team_name.is_some() {
625 minimum_version = CodeDirectoryVersion::SupportsTeamId;
626 }
627 if self.spare3.is_some() || self.code_limit_64.is_some() {
628 minimum_version = CodeDirectoryVersion::SupportsCodeLimit64;
629 }
630 if self.exec_seg_base.is_some()
631 || self.exec_seg_limit.is_some()
632 || self.exec_seg_flags.is_some()
633 {
634 minimum_version = CodeDirectoryVersion::SupportsExecutableSegment;
635 }
636 if self.runtime.is_some() || self.pre_encrypt_offset.is_some() {
637 minimum_version = CodeDirectoryVersion::SupportsRuntime;
638 }
639 if self.linkage_hash_type.is_some()
640 || self.linkage_truncated.is_some()
641 || self.spare4.is_some()
642 || self.linkage_offset.is_some()
643 || self.linkage_size.is_some()
644 {
645 minimum_version = CodeDirectoryVersion::SupportsLinkage;
646 }
647
648 if let Some(target) = target {
651 let target_minimum = match target.platform {
652 Platform::IOs | Platform::IosSimulator => {
654 if target.minimum_os_version >= Version::new(15, 0, 0) {
655 CodeDirectoryVersion::SupportsExecutableSegment
656 } else {
657 CodeDirectoryVersion::Initial
658 }
659 }
660 Platform::MacOs => {
662 if target.minimum_os_version >= Version::new(12, 0, 0) {
663 CodeDirectoryVersion::SupportsExecutableSegment
664 } else {
665 CodeDirectoryVersion::Initial
666 }
667 }
668 _ => CodeDirectoryVersion::Initial,
669 };
670
671 if target_minimum as u32 > minimum_version as u32 {
672 minimum_version = target_minimum;
673 }
674 }
675
676 self.version = minimum_version as u32;
677
678 old_version
679 }
680
681 pub fn clear_newer_fields(&mut self) {
691 if self.version < CodeDirectoryVersion::SupportsScatter as u32 {
692 self.scatter_offset = None;
693 }
694 if self.version < CodeDirectoryVersion::SupportsTeamId as u32 {
695 self.team_name = None;
696 }
697 if self.version < CodeDirectoryVersion::SupportsCodeLimit64 as u32 {
698 self.spare3 = None;
699 self.code_limit_64 = None;
700 }
701 if self.version < CodeDirectoryVersion::SupportsExecutableSegment as u32 {
702 self.exec_seg_base = None;
703 self.exec_seg_limit = None;
704 self.exec_seg_flags = None;
705 }
706 if self.version < CodeDirectoryVersion::SupportsRuntime as u32 {
707 self.runtime = None;
708 self.pre_encrypt_offset = None;
709 }
710 if self.version < CodeDirectoryVersion::SupportsLinkage as u32 {
711 self.linkage_hash_type = None;
712 self.linkage_truncated = None;
713 self.spare4 = None;
714 self.linkage_offset = None;
715 self.linkage_size = None;
716 }
717 }
718
719 pub fn to_owned(&self) -> CodeDirectoryBlob<'static> {
720 CodeDirectoryBlob {
721 version: self.version,
722 flags: self.flags,
723 code_limit: self.code_limit,
724 digest_size: self.digest_size,
725 digest_type: self.digest_type,
726 platform: self.platform,
727 page_size: self.page_size,
728 spare2: self.spare2,
729 scatter_offset: self.scatter_offset,
730 spare3: self.spare3,
731 code_limit_64: self.code_limit_64,
732 exec_seg_base: self.exec_seg_base,
733 exec_seg_limit: self.exec_seg_limit,
734 exec_seg_flags: self.exec_seg_flags,
735 runtime: self.runtime,
736 pre_encrypt_offset: self.pre_encrypt_offset,
737 linkage_hash_type: self.linkage_hash_type,
738 linkage_truncated: self.linkage_truncated,
739 spare4: self.spare4,
740 linkage_offset: self.linkage_offset,
741 linkage_size: self.linkage_size,
742 ident: Cow::Owned(self.ident.clone().into_owned()),
743 team_name: self
744 .team_name
745 .as_ref()
746 .map(|x| Cow::Owned(x.clone().into_owned())),
747 code_digests: self
748 .code_digests
749 .iter()
750 .map(|h| h.to_owned())
751 .collect::<Vec<_>>(),
752 special_digests: self
753 .special_digests
754 .iter()
755 .map(|(k, v)| (k.to_owned(), v.to_owned()))
756 .collect::<BTreeMap<_, _>>(),
757 }
758 }
759}
760
761#[cfg(test)]
762mod tests {
763 use super::*;
764
765 #[test]
766 fn code_signature_flags_from_str() {
767 assert_eq!(
768 CodeSignatureFlags::from_str("host").unwrap(),
769 CodeSignatureFlags::HOST
770 );
771 assert_eq!(
772 CodeSignatureFlags::from_str("hard").unwrap(),
773 CodeSignatureFlags::FORCE_HARD
774 );
775 assert_eq!(
776 CodeSignatureFlags::from_str("kill").unwrap(),
777 CodeSignatureFlags::FORCE_KILL
778 );
779 assert_eq!(
780 CodeSignatureFlags::from_str("expires").unwrap(),
781 CodeSignatureFlags::FORCE_EXPIRATION
782 );
783 assert_eq!(
784 CodeSignatureFlags::from_str("library").unwrap(),
785 CodeSignatureFlags::LIBRARY_VALIDATION
786 );
787 assert_eq!(
788 CodeSignatureFlags::from_str("runtime").unwrap(),
789 CodeSignatureFlags::RUNTIME
790 );
791 assert_eq!(
792 CodeSignatureFlags::from_str("linker-signed").unwrap(),
793 CodeSignatureFlags::LINKER_SIGNED
794 );
795 }
796}