apple_codesign/
code_directory.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

//! Code directory data structure and related types.

use {
    crate::{
        cryptography::{Digest, DigestType},
        embedded_signature::{
            read_and_validate_blob_header, Blob, CodeSigningMagic, CodeSigningSlot,
        },
        error::AppleCodesignError,
        macho::{MachoTarget, Platform},
    },
    scroll::{IOwrite, Pread},
    semver::Version,
    std::{borrow::Cow, collections::BTreeMap, io::Write, str::FromStr},
};

bitflags::bitflags! {
    /// Code signature flags.
    ///
    /// These flags are embedded in the Code Directory and govern use of the embedded
    /// signature.
    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
    pub struct CodeSignatureFlags: u32 {
        /// Code may act as a host that controls and supervises guest code.
        const HOST = 0x0001;
        /// The code has been sealed without a signing identity.
        const ADHOC = 0x0002;
        /// Set the "hard" status bit for the code when it starts running.
        const FORCE_HARD = 0x0100;
        /// Implicitly set the "kill" status bit for the code when it starts running.
        const FORCE_KILL = 0x0200;
        /// Force certificate expiration checks.
        const FORCE_EXPIRATION = 0x0400;
        /// Restrict dyld loading.
        const RESTRICT = 0x0800;
        /// Enforce code signing.
        const ENFORCEMENT = 0x1000;
        /// Library validation required.
        const LIBRARY_VALIDATION = 0x2000;
        /// Apply runtime hardening policies.
        const RUNTIME = 0x10000;
        /// The code was automatically signed by the linker.
        ///
        /// This signature should be ignored in any new signing operation.
        const LINKER_SIGNED = 0x20000;
    }
}

impl FromStr for CodeSignatureFlags {
    type Err = AppleCodesignError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "host" => Ok(Self::HOST),
            "hard" => Ok(Self::FORCE_HARD),
            "kill" => Ok(Self::FORCE_KILL),
            "expires" => Ok(Self::FORCE_EXPIRATION),
            "library" => Ok(Self::LIBRARY_VALIDATION),
            "runtime" => Ok(Self::RUNTIME),
            "linker-signed" => Ok(Self::LINKER_SIGNED),
            _ => Err(AppleCodesignError::CodeSignatureUnknownFlag(s.to_string())),
        }
    }
}

impl CodeSignatureFlags {
    /// Obtain all flags that can be set by the user.
    ///
    /// Maps to variants that have a `from_str()` implementation.
    pub fn all_user_configurable() -> [&'static str; 7] {
        [
            "host",
            "hard",
            "kill",
            "expires",
            "library",
            "runtime",
            "linker-signed",
        ]
    }

    /// Attempt to convert a series of strings into a [CodeSignatureFlags].
    pub fn from_strs(s: &[&str]) -> Result<CodeSignatureFlags, AppleCodesignError> {
        let mut flags = CodeSignatureFlags::empty();

        for s in s {
            flags |= Self::from_str(s)?;
        }

        Ok(flags)
    }
}

bitflags::bitflags! {
    /// Flags that influence behavior of executable segment.
    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
    pub struct ExecutableSegmentFlags: u64 {
        /// Executable segment belongs to main binary.
        const MAIN_BINARY = 0x0001;
        /// Allow unsigned pages (for debugging).
        const ALLOW_UNSIGNED = 0x0010;
        /// Main binary is debugger.
        const DEBUGGER = 0x0020;
        /// JIT enabled.
        const JIT = 0x0040;
        /// Skip library validation (obsolete).
        const SKIP_LIBRARY_VALIDATION = 0x0080;
        /// Can bless code directory hash for execution.
        const CAN_LOAD_CD_HASH = 0x0100;
        /// Can execute blessed code directory hash.
        const CAN_EXEC_CD_HASH = 0x0200;
    }
}

impl FromStr for ExecutableSegmentFlags {
    type Err = AppleCodesignError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "main-binary" => Ok(Self::MAIN_BINARY),
            "allow-unsigned" => Ok(Self::ALLOW_UNSIGNED),
            "debugger" => Ok(Self::DEBUGGER),
            "jit" => Ok(Self::JIT),
            "skip-library-validation" => Ok(Self::SKIP_LIBRARY_VALIDATION),
            "can-load-cd-hash" => Ok(Self::CAN_LOAD_CD_HASH),
            "can-exec-cd-hash" => Ok(Self::CAN_EXEC_CD_HASH),
            _ => Err(AppleCodesignError::ExecutableSegmentUnknownFlag(
                s.to_string(),
            )),
        }
    }
}

/// Version of Code Directory data structure.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u32)]
pub enum CodeDirectoryVersion {
    Initial = 0x20000,
    SupportsScatter = 0x20100,
    SupportsTeamId = 0x20200,
    SupportsCodeLimit64 = 0x20300,
    SupportsExecutableSegment = 0x20400,
    SupportsRuntime = 0x20500,
    SupportsLinkage = 0x20600,
}

#[repr(C)]
pub struct Scatter {
    /// Number of pages. 0 for sentinel only.
    count: u32,
    /// First page number.
    base: u32,
    /// Offset in target.
    target_offset: u64,
    /// Reserved.
    spare: u64,
}

fn get_hashes(data: &[u8], offset: usize, count: usize, hash_size: usize) -> Vec<Digest<'_>> {
    data[offset..offset + (count * hash_size)]
        .chunks(hash_size)
        .map(|data| Digest { data: data.into() })
        .collect()
}

/// Represents a code directory blob entry.
///
/// This struct is versioned and has been extended over time.
///
/// The struct here represents a superset of all fields in all versions.
///
/// The parser will set `Option<T>` fields to `None` for instances
/// where the version is lower than the version that field was introduced in.
#[derive(Debug, Default)]
pub struct CodeDirectoryBlob<'a> {
    /// Compatibility version.
    pub version: u32,
    /// Setup and mode flags.
    pub flags: CodeSignatureFlags,
    // digest_offset, ident_offset, n_special_slots, and n_code_slots not stored
    // explicitly because they are redundant with derived fields.
    /// Limit to main image signature range.
    ///
    /// This is the file-level offset to stop digesting code data at.
    /// It likely corresponds to the file-offset offset where the
    /// embedded signature data starts in the `__LINKEDIT` segment.
    pub code_limit: u32,
    /// Size of each slot/code digest in bytes.
    pub digest_size: u8,
    /// Type of content digest being used.
    pub digest_type: DigestType,
    /// Platform identifier. 0 if not platform binary.
    pub platform: u8,
    /// Page size in bytes. (stored as log u8)
    pub page_size: u32,
    /// Unused (must be 0).
    pub spare2: u32,
    // Version 0x20100
    /// Offset of optional scatter vector.
    pub scatter_offset: Option<u32>,
    // Version 0x20200
    // team_offset not stored because it is redundant with derived stored str.
    // Version 0x20300
    /// Unused (must be 0).
    pub spare3: Option<u32>,
    /// Limit to main image signature range, 64 bits.
    pub code_limit_64: Option<u64>,
    // Version 0x20400
    /// Offset of executable segment.
    pub exec_seg_base: Option<u64>,
    /// Limit of executable segment.
    pub exec_seg_limit: Option<u64>,
    /// Executable segment flags.
    pub exec_seg_flags: Option<ExecutableSegmentFlags>,
    // Version 0x20500
    pub runtime: Option<u32>,
    pub pre_encrypt_offset: Option<u32>,
    // Version 0x20600
    pub linkage_hash_type: Option<u8>,
    pub linkage_truncated: Option<u8>,
    pub spare4: Option<u16>,
    pub linkage_offset: Option<u32>,
    pub linkage_size: Option<u32>,

    // End of blob header data / start of derived data.
    pub ident: Cow<'a, str>,
    pub team_name: Option<Cow<'a, str>>,
    pub code_digests: Vec<Digest<'a>>,
    pub special_digests: BTreeMap<CodeSigningSlot, Digest<'a>>,
}

impl<'a> Blob<'a> for CodeDirectoryBlob<'a> {
    fn magic() -> u32 {
        u32::from(CodeSigningMagic::CodeDirectory)
    }

    fn from_blob_bytes(data: &'a [u8]) -> Result<Self, AppleCodesignError> {
        read_and_validate_blob_header(data, Self::magic(), "code directory blob")?;

        let offset = &mut 8;

        let version = data.gread_with(offset, scroll::BE)?;
        let flags = data.gread_with::<u32>(offset, scroll::BE)?;
        let flags = CodeSignatureFlags::from_bits_retain(flags);
        assert_eq!(*offset, 0x10);
        let digest_offset = data.gread_with::<u32>(offset, scroll::BE)?;
        let ident_offset = data.gread_with::<u32>(offset, scroll::BE)?;
        let n_special_slots = data.gread_with::<u32>(offset, scroll::BE)?;
        let n_code_slots = data.gread_with::<u32>(offset, scroll::BE)?;
        assert_eq!(*offset, 0x20);
        let code_limit = data.gread_with(offset, scroll::BE)?;
        let digest_size = data.gread_with(offset, scroll::BE)?;
        let digest_type = data.gread_with::<u8>(offset, scroll::BE)?.into();
        let platform = data.gread_with(offset, scroll::BE)?;
        let page_size = data.gread_with::<u8>(offset, scroll::BE)?;
        let page_size = 2u32.pow(page_size as u32);
        let spare2 = data.gread_with(offset, scroll::BE)?;

        let scatter_offset = if version >= CodeDirectoryVersion::SupportsScatter as u32 {
            let v = data.gread_with(offset, scroll::BE)?;

            if v != 0 {
                Some(v)
            } else {
                None
            }
        } else {
            None
        };
        let team_offset = if version >= CodeDirectoryVersion::SupportsTeamId as u32 {
            assert_eq!(*offset, 0x30);
            let v = data.gread_with::<u32>(offset, scroll::BE)?;

            if v != 0 {
                Some(v)
            } else {
                None
            }
        } else {
            None
        };

        let (spare3, code_limit_64) = if version >= CodeDirectoryVersion::SupportsCodeLimit64 as u32
        {
            (
                Some(data.gread_with(offset, scroll::BE)?),
                Some(data.gread_with(offset, scroll::BE)?),
            )
        } else {
            (None, None)
        };

        let (exec_seg_base, exec_seg_limit, exec_seg_flags) =
            if version >= CodeDirectoryVersion::SupportsExecutableSegment as u32 {
                assert_eq!(*offset, 0x40);
                (
                    Some(data.gread_with(offset, scroll::BE)?),
                    Some(data.gread_with(offset, scroll::BE)?),
                    Some(data.gread_with::<u64>(offset, scroll::BE)?),
                )
            } else {
                (None, None, None)
            };

        let exec_seg_flags = exec_seg_flags.map(ExecutableSegmentFlags::from_bits_retain);

        let (runtime, pre_encrypt_offset) =
            if version >= CodeDirectoryVersion::SupportsRuntime as u32 {
                assert_eq!(*offset, 0x58);
                (
                    Some(data.gread_with(offset, scroll::BE)?),
                    Some(data.gread_with(offset, scroll::BE)?),
                )
            } else {
                (None, None)
            };

        let (linkage_hash_type, linkage_truncated, spare4, linkage_offset, linkage_size) =
            if version >= CodeDirectoryVersion::SupportsLinkage as u32 {
                assert_eq!(*offset, 0x60);
                (
                    Some(data.gread_with(offset, scroll::BE)?),
                    Some(data.gread_with(offset, scroll::BE)?),
                    Some(data.gread_with(offset, scroll::BE)?),
                    Some(data.gread_with(offset, scroll::BE)?),
                    Some(data.gread_with(offset, scroll::BE)?),
                )
            } else {
                (None, None, None, None, None)
            };

        // Find trailing null in identifier string.
        let ident = match data[ident_offset as usize..]
            .split(|&b| b == 0)
            .map(std::str::from_utf8)
            .next()
        {
            Some(res) => {
                Cow::from(res.map_err(|_| AppleCodesignError::CodeDirectoryMalformedIdentifier)?)
            }
            None => {
                return Err(AppleCodesignError::CodeDirectoryMalformedIdentifier);
            }
        };

        let team_name = if let Some(team_offset) = team_offset {
            match data[team_offset as usize..]
                .split(|&b| b == 0)
                .map(std::str::from_utf8)
                .next()
            {
                Some(res) => {
                    Some(Cow::from(res.map_err(|_| {
                        AppleCodesignError::CodeDirectoryMalformedTeam
                    })?))
                }
                None => {
                    return Err(AppleCodesignError::CodeDirectoryMalformedTeam);
                }
            }
        } else {
            None
        };

        let code_digests = get_hashes(
            data,
            digest_offset as usize,
            n_code_slots as usize,
            digest_size as usize,
        );

        let special_digests = get_hashes(
            data,
            (digest_offset - (digest_size as u32 * n_special_slots)) as usize,
            n_special_slots as usize,
            digest_size as usize,
        )
        .into_iter()
        .enumerate()
        .map(|(i, h)| (CodeSigningSlot::from(n_special_slots - i as u32), h))
        .collect();

        Ok(Self {
            version,
            flags,
            code_limit,
            digest_size,
            digest_type,
            platform,
            page_size,
            spare2,
            scatter_offset,
            spare3,
            code_limit_64,
            exec_seg_base,
            exec_seg_limit,
            exec_seg_flags,
            runtime,
            pre_encrypt_offset,
            linkage_hash_type,
            linkage_truncated,
            spare4,
            linkage_offset,
            linkage_size,
            ident,
            team_name,
            code_digests,
            special_digests,
        })
    }

    fn serialize_payload(&self) -> Result<Vec<u8>, AppleCodesignError> {
        let mut cursor = std::io::Cursor::new(Vec::<u8>::new());

        // We need to do this in 2 phases because we don't know the length until
        // we build up the data structure.

        cursor.iowrite_with(self.version, scroll::BE)?;
        cursor.iowrite_with(self.flags.bits(), scroll::BE)?;
        let digest_offset_cursor_position = cursor.position();
        cursor.iowrite_with(0u32, scroll::BE)?;
        let ident_offset_cursor_position = cursor.position();
        cursor.iowrite_with(0u32, scroll::BE)?;
        assert_eq!(cursor.position(), 0x10);

        // Digest offsets and counts are wonky. The recorded digest offset is the beginning
        // of code digests and special digests are in "negative" indices before
        // that offset. Digests are also at the index of their CodeSigningSlot constant.
        // e.g. Code Directory is the first element in the specials array because
        // it is slot 0. This means we need to write out empty digests for missing
        // special slots. Our local specials HashMap may not have all entries. So compute
        // how many specials there should be and write that here. We'll insert placeholder
        // digests later.
        let highest_slot = self
            .special_digests
            .keys()
            .map(|slot| u32::from(*slot))
            .max()
            .unwrap_or(0);

        cursor.iowrite_with(highest_slot, scroll::BE)?;
        cursor.iowrite_with(self.code_digests.len() as u32, scroll::BE)?;
        cursor.iowrite_with(self.code_limit, scroll::BE)?;
        cursor.iowrite_with(self.digest_size, scroll::BE)?;
        cursor.iowrite_with(u8::from(self.digest_type), scroll::BE)?;
        cursor.iowrite_with(self.platform, scroll::BE)?;
        cursor.iowrite_with(self.page_size.trailing_zeros() as u8, scroll::BE)?;
        assert_eq!(cursor.position(), 0x20);
        cursor.iowrite_with(self.spare2, scroll::BE)?;

        let mut scatter_offset_cursor_position = None;
        let mut team_offset_cursor_position = None;

        if self.version >= CodeDirectoryVersion::SupportsScatter as u32 {
            scatter_offset_cursor_position = Some(cursor.position());
            cursor.iowrite_with(self.scatter_offset.unwrap_or(0), scroll::BE)?;

            if self.version >= CodeDirectoryVersion::SupportsTeamId as u32 {
                team_offset_cursor_position = Some(cursor.position());
                cursor.iowrite_with(0u32, scroll::BE)?;

                if self.version >= CodeDirectoryVersion::SupportsCodeLimit64 as u32 {
                    cursor.iowrite_with(self.spare3.unwrap_or(0), scroll::BE)?;
                    assert_eq!(cursor.position(), 0x30);
                    cursor.iowrite_with(self.code_limit_64.unwrap_or(0), scroll::BE)?;

                    if self.version >= CodeDirectoryVersion::SupportsExecutableSegment as u32 {
                        cursor.iowrite_with(self.exec_seg_base.unwrap_or(0), scroll::BE)?;
                        assert_eq!(cursor.position(), 0x40);
                        cursor.iowrite_with(self.exec_seg_limit.unwrap_or(0), scroll::BE)?;
                        cursor.iowrite_with(
                            self.exec_seg_flags
                                .unwrap_or_else(ExecutableSegmentFlags::empty)
                                .bits(),
                            scroll::BE,
                        )?;

                        if self.version >= CodeDirectoryVersion::SupportsRuntime as u32 {
                            assert_eq!(cursor.position(), 0x50);
                            cursor.iowrite_with(self.runtime.unwrap_or(0), scroll::BE)?;
                            cursor
                                .iowrite_with(self.pre_encrypt_offset.unwrap_or(0), scroll::BE)?;

                            if self.version >= CodeDirectoryVersion::SupportsLinkage as u32 {
                                cursor.iowrite_with(
                                    self.linkage_hash_type.unwrap_or(0),
                                    scroll::BE,
                                )?;
                                cursor.iowrite_with(
                                    self.linkage_truncated.unwrap_or(0),
                                    scroll::BE,
                                )?;
                                cursor.iowrite_with(self.spare4.unwrap_or(0), scroll::BE)?;
                                cursor
                                    .iowrite_with(self.linkage_offset.unwrap_or(0), scroll::BE)?;
                                assert_eq!(cursor.position(), 0x60);
                                cursor.iowrite_with(self.linkage_size.unwrap_or(0), scroll::BE)?;
                            }
                        }
                    }
                }
            }
        }

        // We've written all the struct fields. Now write variable length fields.

        let identity_offset = cursor.position();
        cursor.write_all(self.ident.as_bytes())?;
        cursor.write_all(b"\0")?;

        let team_offset = cursor.position();
        if team_offset_cursor_position.is_some() {
            if let Some(team_name) = &self.team_name {
                cursor.write_all(team_name.as_bytes())?;
                cursor.write_all(b"\0")?;
            }
        }

        // TODO consider aligning cursor on page boundary here for performance?

        // The boundary conditions are a bit wonky here. We want to go from greatest
        // to smallest, not writing index 0 because that's the first code digest.
        for slot_index in (1..highest_slot + 1).rev() {
            let slot = CodeSigningSlot::from(slot_index);
            assert!(
                slot.is_code_directory_specials_expressible(),
                "slot is expressible in code directory special digests"
            );

            if let Some(digest) = self.special_digests.get(&slot) {
                assert_eq!(
                    digest.data.len(),
                    self.digest_size as usize,
                    "special slot digest length matches expected length"
                );
                cursor.write_all(&digest.data)?;
            } else {
                cursor.write_all(&b"\0".repeat(self.digest_size as usize))?;
            }
        }

        let code_digests_start_offset = cursor.position();

        for digest in &self.code_digests {
            cursor.write_all(&digest.data)?;
        }

        // TODO write out scatter vector.

        // Now go back and update the placeholder offsets. We need to add 8 to account
        // for the blob header, which isn't present in this buffer.
        cursor.set_position(digest_offset_cursor_position);
        cursor.iowrite_with(code_digests_start_offset as u32 + 8, scroll::BE)?;

        cursor.set_position(ident_offset_cursor_position);
        cursor.iowrite_with(identity_offset as u32 + 8, scroll::BE)?;

        if scatter_offset_cursor_position.is_some() && self.scatter_offset.is_some() {
            return Err(AppleCodesignError::Unimplemented("scatter offset"));
        }

        if let Some(offset) = team_offset_cursor_position {
            if self.team_name.is_some() {
                cursor.set_position(offset);
                cursor.iowrite_with(team_offset as u32 + 8, scroll::BE)?;
            }
        }

        Ok(cursor.into_inner())
    }
}

impl<'a> CodeDirectoryBlob<'a> {
    /// Obtain the mapping of slots to digests.
    pub fn slot_digests(&self) -> &BTreeMap<CodeSigningSlot, Digest<'a>> {
        &self.special_digests
    }

    /// Obtain the recorded digest for a given [CodeSigningSlot].
    pub fn slot_digest(&self, slot: CodeSigningSlot) -> Option<&Digest<'a>> {
        self.special_digests.get(&slot)
    }

    /// Set the digest for a given slot.
    pub fn set_slot_digest(
        &mut self,
        slot: CodeSigningSlot,
        digest: impl Into<Digest<'a>>,
    ) -> Result<(), AppleCodesignError> {
        if !slot.is_code_directory_specials_expressible() {
            return Err(AppleCodesignError::LogicError(format!(
                "slot {slot:?} cannot have its digest expressed on code directories"
            )));
        }

        let digest = digest.into();

        if digest.data.len() != self.digest_size as usize {
            return Err(AppleCodesignError::LogicError(format!(
                "attempt to assign digest for slot {:?} whose length {} does not match code directory digest length {}",
                slot, digest.data.len(), self.digest_size

            )));
        }

        self.special_digests.insert(slot, digest);

        Ok(())
    }

    /// Adjust the version of the data structure according to what fields are set.
    ///
    /// Returns the old version.
    pub fn adjust_version(&mut self, target: Option<MachoTarget>) -> u32 {
        let old_version = self.version;

        let mut minimum_version = CodeDirectoryVersion::Initial;

        if self.scatter_offset.is_some() {
            minimum_version = CodeDirectoryVersion::SupportsScatter;
        }
        if self.team_name.is_some() {
            minimum_version = CodeDirectoryVersion::SupportsTeamId;
        }
        if self.spare3.is_some() || self.code_limit_64.is_some() {
            minimum_version = CodeDirectoryVersion::SupportsCodeLimit64;
        }
        if self.exec_seg_base.is_some()
            || self.exec_seg_limit.is_some()
            || self.exec_seg_flags.is_some()
        {
            minimum_version = CodeDirectoryVersion::SupportsExecutableSegment;
        }
        if self.runtime.is_some() || self.pre_encrypt_offset.is_some() {
            minimum_version = CodeDirectoryVersion::SupportsRuntime;
        }
        if self.linkage_hash_type.is_some()
            || self.linkage_truncated.is_some()
            || self.spare4.is_some()
            || self.linkage_offset.is_some()
            || self.linkage_size.is_some()
        {
            minimum_version = CodeDirectoryVersion::SupportsLinkage;
        }

        // Some platforms have hard requirements for the minimum version. If
        // targeting settings are in effect, we raise the minimum version accordingly.
        if let Some(target) = target {
            let target_minimum = match target.platform {
                // iOS >= 15 requires a modern code signature format.
                Platform::IOs | Platform::IosSimulator => {
                    if target.minimum_os_version >= Version::new(15, 0, 0) {
                        CodeDirectoryVersion::SupportsExecutableSegment
                    } else {
                        CodeDirectoryVersion::Initial
                    }
                }
                // Let's bump the minimum version for macOS 12 out of principle.
                Platform::MacOs => {
                    if target.minimum_os_version >= Version::new(12, 0, 0) {
                        CodeDirectoryVersion::SupportsExecutableSegment
                    } else {
                        CodeDirectoryVersion::Initial
                    }
                }
                _ => CodeDirectoryVersion::Initial,
            };

            if target_minimum as u32 > minimum_version as u32 {
                minimum_version = target_minimum;
            }
        }

        self.version = minimum_version as u32;

        old_version
    }

    /// Clears optional fields that are newer than the current version.
    ///
    /// The C structure is versioned and our Rust struct is a superset of
    /// all versions. While our serializer should omit too new fields for
    /// a given version, it is possible for some optional fields to be set
    /// when they wouldn't get serialized.
    ///
    /// Calling this function will set fields not present in the current
    /// version to None.
    pub fn clear_newer_fields(&mut self) {
        if self.version < CodeDirectoryVersion::SupportsScatter as u32 {
            self.scatter_offset = None;
        }
        if self.version < CodeDirectoryVersion::SupportsTeamId as u32 {
            self.team_name = None;
        }
        if self.version < CodeDirectoryVersion::SupportsCodeLimit64 as u32 {
            self.spare3 = None;
            self.code_limit_64 = None;
        }
        if self.version < CodeDirectoryVersion::SupportsExecutableSegment as u32 {
            self.exec_seg_base = None;
            self.exec_seg_limit = None;
            self.exec_seg_flags = None;
        }
        if self.version < CodeDirectoryVersion::SupportsRuntime as u32 {
            self.runtime = None;
            self.pre_encrypt_offset = None;
        }
        if self.version < CodeDirectoryVersion::SupportsLinkage as u32 {
            self.linkage_hash_type = None;
            self.linkage_truncated = None;
            self.spare4 = None;
            self.linkage_offset = None;
            self.linkage_size = None;
        }
    }

    pub fn to_owned(&self) -> CodeDirectoryBlob<'static> {
        CodeDirectoryBlob {
            version: self.version,
            flags: self.flags,
            code_limit: self.code_limit,
            digest_size: self.digest_size,
            digest_type: self.digest_type,
            platform: self.platform,
            page_size: self.page_size,
            spare2: self.spare2,
            scatter_offset: self.scatter_offset,
            spare3: self.spare3,
            code_limit_64: self.code_limit_64,
            exec_seg_base: self.exec_seg_base,
            exec_seg_limit: self.exec_seg_limit,
            exec_seg_flags: self.exec_seg_flags,
            runtime: self.runtime,
            pre_encrypt_offset: self.pre_encrypt_offset,
            linkage_hash_type: self.linkage_hash_type,
            linkage_truncated: self.linkage_truncated,
            spare4: self.spare4,
            linkage_offset: self.linkage_offset,
            linkage_size: self.linkage_size,
            ident: Cow::Owned(self.ident.clone().into_owned()),
            team_name: self
                .team_name
                .as_ref()
                .map(|x| Cow::Owned(x.clone().into_owned())),
            code_digests: self
                .code_digests
                .iter()
                .map(|h| h.to_owned())
                .collect::<Vec<_>>(),
            special_digests: self
                .special_digests
                .iter()
                .map(|(k, v)| (k.to_owned(), v.to_owned()))
                .collect::<BTreeMap<_, _>>(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn code_signature_flags_from_str() {
        assert_eq!(
            CodeSignatureFlags::from_str("host").unwrap(),
            CodeSignatureFlags::HOST
        );
        assert_eq!(
            CodeSignatureFlags::from_str("hard").unwrap(),
            CodeSignatureFlags::FORCE_HARD
        );
        assert_eq!(
            CodeSignatureFlags::from_str("kill").unwrap(),
            CodeSignatureFlags::FORCE_KILL
        );
        assert_eq!(
            CodeSignatureFlags::from_str("expires").unwrap(),
            CodeSignatureFlags::FORCE_EXPIRATION
        );
        assert_eq!(
            CodeSignatureFlags::from_str("library").unwrap(),
            CodeSignatureFlags::LIBRARY_VALIDATION
        );
        assert_eq!(
            CodeSignatureFlags::from_str("runtime").unwrap(),
            CodeSignatureFlags::RUNTIME
        );
        assert_eq!(
            CodeSignatureFlags::from_str("linker-signed").unwrap(),
            CodeSignatureFlags::LINKER_SIGNED
        );
    }
}