apple_codesign/
code_directory.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Code directory data structure and related types.
6
7use {
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    /// Code signature flags.
23    ///
24    /// These flags are embedded in the Code Directory and govern use of the embedded
25    /// signature.
26    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
27    pub struct CodeSignatureFlags: u32 {
28        /// Code may act as a host that controls and supervises guest code.
29        const HOST = 0x0001;
30        /// The code has been sealed without a signing identity.
31        const ADHOC = 0x0002;
32        /// Set the "hard" status bit for the code when it starts running.
33        const FORCE_HARD = 0x0100;
34        /// Implicitly set the "kill" status bit for the code when it starts running.
35        const FORCE_KILL = 0x0200;
36        /// Force certificate expiration checks.
37        const FORCE_EXPIRATION = 0x0400;
38        /// Restrict dyld loading.
39        const RESTRICT = 0x0800;
40        /// Enforce code signing.
41        const ENFORCEMENT = 0x1000;
42        /// Library validation required.
43        const LIBRARY_VALIDATION = 0x2000;
44        /// Apply runtime hardening policies.
45        const RUNTIME = 0x10000;
46        /// The code was automatically signed by the linker.
47        ///
48        /// This signature should be ignored in any new signing operation.
49        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    /// Obtain all flags that can be set by the user.
72    ///
73    /// Maps to variants that have a `from_str()` implementation.
74    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    /// Attempt to convert a series of strings into a [CodeSignatureFlags].
87    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    /// Flags that influence behavior of executable segment.
100    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
101    pub struct ExecutableSegmentFlags: u64 {
102        /// Executable segment belongs to main binary.
103        const MAIN_BINARY = 0x0001;
104        /// Allow unsigned pages (for debugging).
105        const ALLOW_UNSIGNED = 0x0010;
106        /// Main binary is debugger.
107        const DEBUGGER = 0x0020;
108        /// JIT enabled.
109        const JIT = 0x0040;
110        /// Skip library validation (obsolete).
111        const SKIP_LIBRARY_VALIDATION = 0x0080;
112        /// Can bless code directory hash for execution.
113        const CAN_LOAD_CD_HASH = 0x0100;
114        /// Can execute blessed code directory hash.
115        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/// Version of Code Directory data structure.
139#[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    /// Number of pages. 0 for sentinel only.
154    count: u32,
155    /// First page number.
156    base: u32,
157    /// Offset in target.
158    target_offset: u64,
159    /// Reserved.
160    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/// Represents a code directory blob entry.
171///
172/// This struct is versioned and has been extended over time.
173///
174/// The struct here represents a superset of all fields in all versions.
175///
176/// The parser will set `Option<T>` fields to `None` for instances
177/// where the version is lower than the version that field was introduced in.
178#[derive(Debug, Default)]
179pub struct CodeDirectoryBlob<'a> {
180    /// Compatibility version.
181    pub version: u32,
182    /// Setup and mode flags.
183    pub flags: CodeSignatureFlags,
184    // digest_offset, ident_offset, n_special_slots, and n_code_slots not stored
185    // explicitly because they are redundant with derived fields.
186    /// Limit to main image signature range.
187    ///
188    /// This is the file-level offset to stop digesting code data at.
189    /// It likely corresponds to the file-offset offset where the
190    /// embedded signature data starts in the `__LINKEDIT` segment.
191    pub code_limit: u32,
192    /// Size of each slot/code digest in bytes.
193    pub digest_size: u8,
194    /// Type of content digest being used.
195    pub digest_type: DigestType,
196    /// Platform identifier. 0 if not platform binary.
197    pub platform: u8,
198    /// Page size in bytes. (stored as log u8)
199    pub page_size: u32,
200    /// Unused (must be 0).
201    pub spare2: u32,
202    // Version 0x20100
203    /// Offset of optional scatter vector.
204    pub scatter_offset: Option<u32>,
205    // Version 0x20200
206    // team_offset not stored because it is redundant with derived stored str.
207    // Version 0x20300
208    /// Unused (must be 0).
209    pub spare3: Option<u32>,
210    /// Limit to main image signature range, 64 bits.
211    pub code_limit_64: Option<u64>,
212    // Version 0x20400
213    /// Offset of executable segment.
214    pub exec_seg_base: Option<u64>,
215    /// Limit of executable segment.
216    pub exec_seg_limit: Option<u64>,
217    /// Executable segment flags.
218    pub exec_seg_flags: Option<ExecutableSegmentFlags>,
219    // Version 0x20500
220    pub runtime: Option<u32>,
221    pub pre_encrypt_offset: Option<u32>,
222    // Version 0x20600
223    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    // End of blob header data / start of derived data.
230    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        // Find trailing null in identifier string.
337        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        // We need to do this in 2 phases because we don't know the length until
420        // we build up the data structure.
421
422        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        // Digest offsets and counts are wonky. The recorded digest offset is the beginning
431        // of code digests and special digests are in "negative" indices before
432        // that offset. Digests are also at the index of their CodeSigningSlot constant.
433        // e.g. Code Directory is the first element in the specials array because
434        // it is slot 0. This means we need to write out empty digests for missing
435        // special slots. Our local specials HashMap may not have all entries. So compute
436        // how many specials there should be and write that here. We'll insert placeholder
437        // digests later.
438        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        // We've written all the struct fields. Now write variable length fields.
510
511        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        // TODO consider aligning cursor on page boundary here for performance?
524
525        // The boundary conditions are a bit wonky here. We want to go from greatest
526        // to smallest, not writing index 0 because that's the first code digest.
527        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        // TODO write out scatter vector.
553
554        // Now go back and update the placeholder offsets. We need to add 8 to account
555        // for the blob header, which isn't present in this buffer.
556        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    /// Obtain the mapping of slots to digests.
579    pub fn slot_digests(&self) -> &BTreeMap<CodeSigningSlot, Digest<'a>> {
580        &self.special_digests
581    }
582
583    /// Obtain the recorded digest for a given [CodeSigningSlot].
584    pub fn slot_digest(&self, slot: CodeSigningSlot) -> Option<&Digest<'a>> {
585        self.special_digests.get(&slot)
586    }
587
588    /// Set the digest for a given slot.
589    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    /// Adjust the version of the data structure according to what fields are set.
616    ///
617    /// Returns the old version.
618    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        // Some platforms have hard requirements for the minimum version. If
651        // targeting settings are in effect, we raise the minimum version accordingly.
652        if let Some(target) = target {
653            let target_minimum = match target.platform {
654                // iOS >= 15 requires a modern code signature format.
655                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                // Let's bump the minimum version for macOS 12 out of principle.
663                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    /// Clears optional fields that are newer than the current version.
684    ///
685    /// The C structure is versioned and our Rust struct is a superset of
686    /// all versions. While our serializer should omit too new fields for
687    /// a given version, it is possible for some optional fields to be set
688    /// when they wouldn't get serialized.
689    ///
690    /// Calling this function will set fields not present in the current
691    /// version to None.
692    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}