apple_codesign/
embedded_signature.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//! Common embedded signature data structures (superblobs, magic values, etc).
6//!
7//! This module defines types and data structures that are common to Apple's
8//! embedded signature format.
9//!
10//! Within this module are constants for header magic, definitions of
11//! serialized data structures like superblobs and blobs, and some common
12//! enumerations.
13//!
14//! There is no official specification of the Mach-O structure for various
15//! code signing primitives. So the definitions in here could diverge from
16//! what is actually implemented.
17//!
18//! The best source of the specification comes from Apple's open source headers,
19//! notably cs_blobs.h (e.g.
20//! <https://opensource.apple.com/source/xnu/xnu-7195.81.3/osfmk/kern/cs_blobs.h.auto.html>).
21//! (Go to <https://opensource.apple.com/source/xnu> and check for newer versions of xnu
22//! to look for new features.)
23//!
24//! The high-level format of embedded signature data is roughly as follows:
25//!
26//! * A `SuperBlob` header describes the total length of data and the number of
27//!   *blob* sections that follow.
28//! * An array of `BlobIndex` describing the type and offset of all *blob* sections
29//!   that follow. The *type* here is a *slot* and describes what type of data the
30//!   *blob* contains (code directory, entitlements, embedded signature, etc).
31//! * N *blob* sections of varying formats and lengths.
32//!
33//! We only support the [CodeSigningMagic::EmbeddedSignature] magic in the `SuperBlob`,
34//! as this is what is used in the wild. (It is even unclear if other magic values
35//! can occur in `SuperBlob` headers.)
36//!
37//! The `EmbeddedSignature` type represents a lightly parsed `SuperBlob`. It
38//! provides access to `BlobEntry` which describe the *blob* sections within the
39//! super blob. A `BlobEntry` can be parsed into the more concrete `ParsedBlob`,
40//! which allows some access to data within each specific blob type.
41
42use {
43    crate::{
44        code_directory::CodeDirectoryBlob,
45        code_requirement::{CodeRequirements, RequirementType},
46        cryptography::DigestType,
47        environment_constraints::EncodedEnvironmentConstraints,
48        AppleCodesignError, Result,
49    },
50    cryptographic_message_syntax::SignedData,
51    scroll::{IOwrite, Pread},
52    std::{borrow::Cow, cmp::Ordering, collections::HashMap, io::Write},
53};
54
55/// Defines header magic for various payloads.
56#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
57pub enum CodeSigningMagic {
58    /// Code requirement blob.
59    Requirement,
60    /// Code requirements blob.
61    RequirementSet,
62    /// CodeDirectory blob.
63    CodeDirectory,
64    /// Embedded signature.
65    ///
66    /// This is often the magic of the SuperBlob.
67    EmbeddedSignature,
68    /// Old embedded signature.
69    EmbeddedSignatureOld,
70    /// Entitlements blob.
71    Entitlements,
72    /// DER encoded entitlements blob.
73    EntitlementsDer,
74    /// DER encoded environment constraints.
75    ///
76    /// This is how launch constraints and library constraints are encoded.
77    EnvironmentContraintsDer,
78    /// Multi-arch collection of embedded signatures.
79    DetachedSignature,
80    /// Generic blob wrapper.
81    ///
82    /// The CMS signature is stored in this type.
83    BlobWrapper,
84    /// Unknown magic.
85    Unknown(u32),
86}
87
88impl From<u32> for CodeSigningMagic {
89    fn from(v: u32) -> Self {
90        match v {
91            0xfade0c00 => Self::Requirement,
92            0xfade0c01 => Self::RequirementSet,
93            0xfade0c02 => Self::CodeDirectory,
94            0xfade0cc0 => Self::EmbeddedSignature,
95            0xfade0b02 => Self::EmbeddedSignatureOld,
96            0xfade7171 => Self::Entitlements,
97            0xfade7172 => Self::EntitlementsDer,
98            0xfade8181 => Self::EnvironmentContraintsDer,
99            0xfade0cc1 => Self::DetachedSignature,
100            0xfade0b01 => Self::BlobWrapper,
101            _ => Self::Unknown(v),
102        }
103    }
104}
105
106impl From<CodeSigningMagic> for u32 {
107    fn from(magic: CodeSigningMagic) -> u32 {
108        match magic {
109            CodeSigningMagic::Requirement => 0xfade0c00,
110            CodeSigningMagic::RequirementSet => 0xfade0c01,
111            CodeSigningMagic::CodeDirectory => 0xfade0c02,
112            CodeSigningMagic::EmbeddedSignature => 0xfade0cc0,
113            CodeSigningMagic::EmbeddedSignatureOld => 0xfade0b02,
114            CodeSigningMagic::Entitlements => 0xfade7171,
115            CodeSigningMagic::EntitlementsDer => 0xfade7172,
116            CodeSigningMagic::EnvironmentContraintsDer => 0xfade8181,
117            CodeSigningMagic::DetachedSignature => 0xfade0cc1,
118            CodeSigningMagic::BlobWrapper => 0xfade0b01,
119            CodeSigningMagic::Unknown(v) => v,
120        }
121    }
122}
123
124// The digest formats are encoded as byte values. Implement conversions.
125
126impl From<u8> for DigestType {
127    fn from(v: u8) -> Self {
128        match v {
129            0 => Self::None,
130            1 => Self::Sha1,
131            2 => Self::Sha256,
132            3 => Self::Sha256Truncated,
133            4 => Self::Sha384,
134            5 => Self::Sha512,
135            _ => Self::Unknown(v),
136        }
137    }
138}
139
140impl From<DigestType> for u8 {
141    fn from(v: DigestType) -> u8 {
142        match v {
143            DigestType::None => 0,
144            DigestType::Sha1 => 1,
145            DigestType::Sha256 => 2,
146            DigestType::Sha256Truncated => 3,
147            DigestType::Sha384 => 4,
148            DigestType::Sha512 => 5,
149            DigestType::Unknown(v) => v,
150        }
151    }
152}
153
154/// A well-known slot within code signing data.
155#[derive(Clone, Copy, PartialEq, Eq, Hash)]
156pub enum CodeSigningSlot {
157    CodeDirectory,
158    /// Info.plist.
159    Info,
160    /// Designated requirements.
161    RequirementSet,
162    /// Digest of `CodeRequirements` file (used in bundles).
163    ResourceDir,
164    /// Application specific slot.
165    Application,
166    /// Entitlements XML plist.
167    Entitlements,
168    /// Reserved for disk images.
169    RepSpecific,
170    /// Entitlements DER encoded plist.
171    EntitlementsDer,
172    /// DER launch constraints on self.
173    LaunchConstraintsSelf,
174    /// DER launch constraints on parent.
175    LaunchConstraintsParent,
176    /// DER launch constraints on responsible process.
177    LaunchConstraintsResponsibleProcess,
178    /// DER launch constraints on libraries loaded in the process.
179    LibraryConstraints,
180
181    // Everything from here is a slot not encoded in the code directory hashes list.
182    // REMEMBER TO UPDATE is_code_directory_specials_expressible() if adding a new slot
183    // here!
184    /// Alternative code directory slot #0.
185    ///
186    /// Used for expressing a code directory using an alternate digest type.
187    AlternateCodeDirectory0,
188    AlternateCodeDirectory1,
189    AlternateCodeDirectory2,
190    AlternateCodeDirectory3,
191    AlternateCodeDirectory4,
192    /// CMS signature.
193    Signature,
194    Identification,
195    /// Notarization ticket.
196    Ticket,
197    Unknown(u32),
198}
199
200impl std::fmt::Debug for CodeSigningSlot {
201    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202        match self {
203            Self::CodeDirectory => {
204                f.write_fmt(format_args!("CodeDirectory ({})", u32::from(*self)))
205            }
206            Self::Info => f.write_fmt(format_args!("Info ({})", u32::from(*self))),
207            Self::RequirementSet => {
208                f.write_fmt(format_args!("RequirementSet ({})", u32::from(*self)))
209            }
210            Self::ResourceDir => f.write_fmt(format_args!("Resources ({})", u32::from(*self))),
211            Self::Application => f.write_fmt(format_args!("Application ({})", u32::from(*self))),
212            Self::Entitlements => f.write_fmt(format_args!("Entitlements ({})", u32::from(*self))),
213            Self::RepSpecific => f.write_fmt(format_args!("Rep Specific ({})", u32::from(*self))),
214            Self::EntitlementsDer => {
215                f.write_fmt(format_args!("DER Entitlements ({})", u32::from(*self)))
216            }
217            Self::LaunchConstraintsSelf => f.write_fmt(format_args!(
218                "DER Launch Constraints on Self ({})",
219                u32::from(*self)
220            )),
221            Self::LaunchConstraintsParent => f.write_fmt(format_args!(
222                "DER Launch Constraints on Parent ({})",
223                u32::from(*self)
224            )),
225            Self::LaunchConstraintsResponsibleProcess => f.write_fmt(format_args!(
226                "DER Launch Constraints on Responsible Process ({})",
227                u32::from(*self)
228            )),
229            Self::LibraryConstraints => f.write_fmt(format_args!(
230                "DER Launch Constraints on Loaded Libraries ({})",
231                u32::from(*self)
232            )),
233            Self::AlternateCodeDirectory0 => f.write_fmt(format_args!(
234                "CodeDirectory Alternate #0 ({})",
235                u32::from(*self)
236            )),
237            Self::AlternateCodeDirectory1 => f.write_fmt(format_args!(
238                "CodeDirectory Alternate #1 ({})",
239                u32::from(*self)
240            )),
241            Self::AlternateCodeDirectory2 => f.write_fmt(format_args!(
242                "CodeDirectory Alternate #2 ({})",
243                u32::from(*self)
244            )),
245            Self::AlternateCodeDirectory3 => f.write_fmt(format_args!(
246                "CodeDirectory Alternate #3 ({})",
247                u32::from(*self)
248            )),
249            Self::AlternateCodeDirectory4 => f.write_fmt(format_args!(
250                "CodeDirectory Alternate #4 ({})",
251                u32::from(*self)
252            )),
253            Self::Signature => f.write_fmt(format_args!("CMS Signature ({})", u32::from(*self))),
254            Self::Identification => {
255                f.write_fmt(format_args!("Identification ({})", u32::from(*self)))
256            }
257            Self::Ticket => f.write_fmt(format_args!("Ticket ({})", u32::from(*self))),
258            Self::Unknown(value) => f.write_fmt(format_args!("Unknown ({value})")),
259        }
260    }
261}
262
263impl From<u32> for CodeSigningSlot {
264    fn from(v: u32) -> Self {
265        match v {
266            0 => Self::CodeDirectory,
267            1 => Self::Info,
268            2 => Self::RequirementSet,
269            3 => Self::ResourceDir,
270            4 => Self::Application,
271            5 => Self::Entitlements,
272            6 => Self::RepSpecific,
273            7 => Self::EntitlementsDer,
274            8 => Self::LaunchConstraintsSelf,
275            9 => Self::LaunchConstraintsParent,
276            10 => Self::LaunchConstraintsResponsibleProcess,
277            11 => Self::LibraryConstraints,
278            0x1000 => Self::AlternateCodeDirectory0,
279            0x1001 => Self::AlternateCodeDirectory1,
280            0x1002 => Self::AlternateCodeDirectory2,
281            0x1003 => Self::AlternateCodeDirectory3,
282            0x1004 => Self::AlternateCodeDirectory4,
283            0x10000 => Self::Signature,
284            0x10001 => Self::Identification,
285            0x10002 => Self::Ticket,
286            _ => Self::Unknown(v),
287        }
288    }
289}
290
291impl From<CodeSigningSlot> for u32 {
292    fn from(v: CodeSigningSlot) -> Self {
293        match v {
294            CodeSigningSlot::CodeDirectory => 0,
295            CodeSigningSlot::Info => 1,
296            CodeSigningSlot::RequirementSet => 2,
297            CodeSigningSlot::ResourceDir => 3,
298            CodeSigningSlot::Application => 4,
299            CodeSigningSlot::Entitlements => 5,
300            CodeSigningSlot::RepSpecific => 6,
301            CodeSigningSlot::EntitlementsDer => 7,
302            CodeSigningSlot::LaunchConstraintsSelf => 8,
303            CodeSigningSlot::LaunchConstraintsParent => 9,
304            CodeSigningSlot::LaunchConstraintsResponsibleProcess => 10,
305            CodeSigningSlot::LibraryConstraints => 11,
306            CodeSigningSlot::AlternateCodeDirectory0 => 0x1000,
307            CodeSigningSlot::AlternateCodeDirectory1 => 0x1001,
308            CodeSigningSlot::AlternateCodeDirectory2 => 0x1002,
309            CodeSigningSlot::AlternateCodeDirectory3 => 0x1003,
310            CodeSigningSlot::AlternateCodeDirectory4 => 0x1004,
311            CodeSigningSlot::Signature => 0x10000,
312            CodeSigningSlot::Identification => 0x10001,
313            CodeSigningSlot::Ticket => 0x10002,
314            CodeSigningSlot::Unknown(v) => v,
315        }
316    }
317}
318
319impl PartialOrd for CodeSigningSlot {
320    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
321        Some(self.cmp(other))
322    }
323}
324
325impl Ord for CodeSigningSlot {
326    fn cmp(&self, other: &Self) -> Ordering {
327        u32::from(*self).cmp(&u32::from(*other))
328    }
329}
330
331impl CodeSigningSlot {
332    /// Whether this slot has external data (as opposed to provided via a blob).
333    pub fn has_external_content(&self) -> bool {
334        matches!(self, Self::Info | Self::ResourceDir)
335    }
336
337    /// Whether this slot is for holding an alternative code directory.
338    pub fn is_alternative_code_directory(&self) -> bool {
339        matches!(
340            self,
341            CodeSigningSlot::AlternateCodeDirectory0
342                | CodeSigningSlot::AlternateCodeDirectory1
343                | CodeSigningSlot::AlternateCodeDirectory2
344                | CodeSigningSlot::AlternateCodeDirectory3
345                | CodeSigningSlot::AlternateCodeDirectory4
346        )
347    }
348
349    /// Whether this slot's digest is expressed in code directories list of special slot digests.
350    pub fn is_code_directory_specials_expressible(&self) -> bool {
351        *self >= Self::Info && *self <= Self::LibraryConstraints
352    }
353}
354
355#[repr(C)]
356#[derive(Clone, Pread)]
357struct BlobIndex {
358    /// Corresponds to a [CodeSigningSlot] variant.
359    typ: u32,
360    offset: u32,
361}
362
363impl std::fmt::Debug for BlobIndex {
364    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
365        f.debug_struct("BlobIndex")
366            .field("type", &CodeSigningSlot::from(self.typ))
367            .field("offset", &self.offset)
368            .finish()
369    }
370}
371
372/// Read the header from a Blob.
373///
374/// Blobs begin with a u32 magic and u32 length, inclusive.
375fn read_blob_header(data: &[u8]) -> Result<(u32, usize, &[u8]), scroll::Error> {
376    let magic = data.pread_with(0, scroll::BE)?;
377    let length = data.pread_with::<u32>(4, scroll::BE)?;
378
379    Ok((magic, length as usize, &data[8..]))
380}
381
382pub(crate) fn read_and_validate_blob_header<'a>(
383    data: &'a [u8],
384    expected_magic: u32,
385    what: &'static str,
386) -> Result<&'a [u8], AppleCodesignError> {
387    let (magic, _, data) = read_blob_header(data)?;
388
389    if magic != expected_magic {
390        Err(AppleCodesignError::BadMagic(what))
391    } else {
392        Ok(data)
393    }
394}
395
396/// Create the binary content for a SuperBlob.
397pub fn create_superblob<'a>(
398    magic: CodeSigningMagic,
399    blobs: impl Iterator<Item = &'a (CodeSigningSlot, Vec<u8>)>,
400) -> Result<Vec<u8>, AppleCodesignError> {
401    // Makes offset calculation easier.
402    let blobs = blobs.collect::<Vec<_>>();
403
404    let mut cursor = std::io::Cursor::new(Vec::<u8>::new());
405
406    let mut blob_data = Vec::new();
407    // magic + total length + blob count.
408    let mut total_length: u32 = 4 + 4 + 4;
409    // 8 bytes for each blob index.
410    total_length += 8 * blobs.len() as u32;
411
412    let mut indices = Vec::with_capacity(blobs.len());
413
414    for (slot, blob) in blobs {
415        blob_data.push(blob);
416
417        indices.push(BlobIndex {
418            typ: u32::from(*slot),
419            offset: total_length,
420        });
421
422        total_length += blob.len() as u32;
423    }
424
425    cursor.iowrite_with(u32::from(magic), scroll::BE)?;
426    cursor.iowrite_with(total_length, scroll::BE)?;
427    cursor.iowrite_with(indices.len() as u32, scroll::BE)?;
428    for index in indices {
429        cursor.iowrite_with(index.typ, scroll::BE)?;
430        cursor.iowrite_with(index.offset, scroll::BE)?;
431    }
432    for data in blob_data {
433        cursor.write_all(data)?;
434    }
435
436    Ok(cursor.into_inner())
437}
438
439/// Represents a single blob as defined by a SuperBlob index entry.
440///
441/// Instances have copies of their own index info, including the relative
442/// order, slot type, and start offset within the `SuperBlob`.
443///
444/// The blob data is unparsed in this type. The blob payloads can be
445/// turned into [ParsedBlob] via `.try_into()`.
446#[derive(Clone)]
447pub struct BlobEntry<'a> {
448    /// Our blob index within the `SuperBlob`.
449    pub index: usize,
450
451    /// The slot type.
452    pub slot: CodeSigningSlot,
453
454    /// Our start offset within the `SuperBlob`.
455    ///
456    /// First byte is start of our magic.
457    pub offset: usize,
458
459    /// The magic value appearing at the beginning of the blob.
460    pub magic: CodeSigningMagic,
461
462    /// The length of the blob payload.
463    pub length: usize,
464
465    /// The raw data in this blob, including magic and length.
466    pub data: &'a [u8],
467}
468
469impl<'a> std::fmt::Debug for BlobEntry<'a> {
470    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
471        f.debug_struct("BlobEntry")
472            .field("index", &self.index)
473            .field("slot", &self.slot)
474            .field("offset", &self.offset)
475            .field("length", &self.length)
476            .field("magic", &self.magic)
477            // .field("data", &self.data)
478            .finish()
479    }
480}
481
482impl<'a> BlobEntry<'a> {
483    /// Attempt to convert to a [ParsedBlob].
484    pub fn into_parsed_blob(self) -> Result<ParsedBlob<'a>, AppleCodesignError> {
485        self.try_into()
486    }
487
488    /// Obtain the payload of this blob.
489    ///
490    /// This is the data in the blob without the blob header.
491    pub fn payload(&self) -> Result<&'a [u8], AppleCodesignError> {
492        Ok(read_blob_header(self.data)?.2)
493    }
494
495    /// Compute the content digest of this blob using the specified hash type.
496    pub fn digest_with(&self, hash: DigestType) -> Result<Vec<u8>, AppleCodesignError> {
497        hash.digest_data(self.data)
498    }
499}
500
501/// Provides common features for a parsed blob type.
502pub trait Blob<'a>
503where
504    Self: Sized,
505{
506    /// The header magic that identifies this format.
507    fn magic() -> u32;
508
509    /// Attempt to construct an instance by parsing a bytes slice.
510    ///
511    /// The slice begins with the 8 byte blob header denoting the magic
512    /// and length.
513    fn from_blob_bytes(data: &'a [u8]) -> Result<Self, AppleCodesignError>;
514
515    /// Serialize the payload of this blob to bytes.
516    ///
517    /// Does not include the magic or length header fields common to blobs.
518    fn serialize_payload(&self) -> Result<Vec<u8>, AppleCodesignError>;
519
520    /// Serialize this blob to bytes.
521    ///
522    /// This is [Blob::serialize_payload] with the blob magic and length
523    /// prepended.
524    fn to_blob_bytes(&self) -> Result<Vec<u8>, AppleCodesignError> {
525        let mut res = Vec::new();
526        res.iowrite_with(Self::magic(), scroll::BE)?;
527
528        let payload = self.serialize_payload()?;
529        // Length includes our own header.
530        res.iowrite_with(payload.len() as u32 + 8, scroll::BE)?;
531
532        res.extend(payload);
533
534        Ok(res)
535    }
536
537    /// Obtain the digest of the blob using the specified hasher.
538    ///
539    /// Default implementation calls [Blob::to_blob_bytes] and digests that, which
540    /// should always be correct.
541    fn digest_with(&self, hash_type: DigestType) -> Result<Vec<u8>, AppleCodesignError> {
542        hash_type.digest_data(&self.to_blob_bytes()?)
543    }
544}
545
546/// Represents a Requirement blob.
547///
548/// `csreq -b` will emit instances of this blob, header magic and all. So data generated
549/// by `csreq -b` can be fed into [RequirementBlob.from_blob_bytes] to obtain an instance.
550pub struct RequirementBlob<'a> {
551    pub data: Cow<'a, [u8]>,
552}
553
554impl<'a> Blob<'a> for RequirementBlob<'a> {
555    fn magic() -> u32 {
556        u32::from(CodeSigningMagic::Requirement)
557    }
558
559    fn from_blob_bytes(data: &'a [u8]) -> Result<Self, AppleCodesignError> {
560        let data = read_and_validate_blob_header(data, Self::magic(), "requirement blob")?;
561
562        Ok(Self { data: data.into() })
563    }
564
565    fn serialize_payload(&self) -> Result<Vec<u8>, AppleCodesignError> {
566        Ok(self.data.to_vec())
567    }
568}
569
570impl<'a> std::fmt::Debug for RequirementBlob<'a> {
571    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
572        f.write_fmt(format_args!("RequirementBlob({})", hex::encode(&self.data)))
573    }
574}
575
576impl<'a> RequirementBlob<'a> {
577    pub fn to_owned(&self) -> RequirementBlob<'static> {
578        RequirementBlob {
579            data: Cow::Owned(self.data.clone().into_owned()),
580        }
581    }
582
583    /// Parse the binary data in this blob into Code Requirement expressions.
584    pub fn parse_expressions(&self) -> Result<CodeRequirements, AppleCodesignError> {
585        Ok(CodeRequirements::parse_binary(&self.data)?.0)
586    }
587}
588
589/// Represents a Requirement set blob.
590///
591/// A Requirement set blob contains nested Requirement blobs.
592#[derive(Debug, Default)]
593pub struct RequirementSetBlob<'a> {
594    pub requirements: HashMap<RequirementType, RequirementBlob<'a>>,
595}
596
597impl<'a> Blob<'a> for RequirementSetBlob<'a> {
598    fn magic() -> u32 {
599        u32::from(CodeSigningMagic::RequirementSet)
600    }
601
602    fn from_blob_bytes(data: &'a [u8]) -> Result<Self, AppleCodesignError> {
603        read_and_validate_blob_header(data, Self::magic(), "requirement set blob")?;
604
605        // There are other blobs nested within. A u32 denotes how many there are.
606        // Then there is an array of N (u32, u32) denoting the type and
607        // offset of each.
608        let offset = &mut 8;
609        let count = data.gread_with::<u32>(offset, scroll::BE)?;
610
611        let mut indices = Vec::with_capacity(count as usize);
612        for _ in 0..count {
613            indices.push((
614                data.gread_with::<u32>(offset, scroll::BE)?,
615                data.gread_with::<u32>(offset, scroll::BE)?,
616            ));
617        }
618
619        let mut requirements = HashMap::with_capacity(indices.len());
620
621        for (i, (flavor, offset)) in indices.iter().enumerate() {
622            let typ = RequirementType::from(*flavor);
623
624            let end_offset = if i == indices.len() - 1 {
625                data.len()
626            } else {
627                indices[i + 1].1 as usize
628            };
629
630            let requirement_data = &data[*offset as usize..end_offset];
631
632            requirements.insert(typ, RequirementBlob::from_blob_bytes(requirement_data)?);
633        }
634
635        Ok(Self { requirements })
636    }
637
638    fn serialize_payload(&self) -> Result<Vec<u8>, AppleCodesignError> {
639        let mut res = Vec::new();
640
641        // The index contains blob relative offsets. To know what the start offset will
642        // be, we calculate the total index size.
643        let data_start_offset = 8 + 4 + (8 * self.requirements.len() as u32);
644        let mut written_requirements_data = 0;
645
646        res.iowrite_with(self.requirements.len() as u32, scroll::BE)?;
647
648        // Write an index of all nested requirement blobs.
649        for (typ, requirement) in &self.requirements {
650            res.iowrite_with(u32::from(*typ), scroll::BE)?;
651            res.iowrite_with(data_start_offset + written_requirements_data, scroll::BE)?;
652            written_requirements_data += requirement.to_blob_bytes()?.len() as u32;
653        }
654
655        // Now write every requirement's raw data.
656        for requirement in self.requirements.values() {
657            res.write_all(&requirement.to_blob_bytes()?)?;
658        }
659
660        Ok(res)
661    }
662}
663
664impl<'a> RequirementSetBlob<'a> {
665    pub fn to_owned(&self) -> RequirementSetBlob<'static> {
666        RequirementSetBlob {
667            requirements: self
668                .requirements
669                .iter()
670                .map(|(flavor, blob)| (*flavor, blob.to_owned()))
671                .collect::<HashMap<_, _>>(),
672        }
673    }
674
675    /// Set the requirements for a given [RequirementType].
676    pub fn set_requirements(&mut self, slot: RequirementType, blob: RequirementBlob<'a>) {
677        self.requirements.insert(slot, blob);
678    }
679}
680
681/// Represents an embedded signature.
682#[derive(Debug)]
683pub struct EmbeddedSignatureBlob<'a> {
684    data: &'a [u8],
685}
686
687impl<'a> Blob<'a> for EmbeddedSignatureBlob<'a> {
688    fn magic() -> u32 {
689        u32::from(CodeSigningMagic::EmbeddedSignature)
690    }
691
692    fn from_blob_bytes(data: &'a [u8]) -> Result<Self, AppleCodesignError> {
693        Ok(Self {
694            data: read_and_validate_blob_header(data, Self::magic(), "embedded signature blob")?,
695        })
696    }
697
698    fn serialize_payload(&self) -> Result<Vec<u8>, AppleCodesignError> {
699        Ok(self.data.to_vec())
700    }
701}
702
703/// An old embedded signature.
704#[derive(Debug)]
705pub struct EmbeddedSignatureOldBlob<'a> {
706    data: &'a [u8],
707}
708
709impl<'a> Blob<'a> for EmbeddedSignatureOldBlob<'a> {
710    fn magic() -> u32 {
711        u32::from(CodeSigningMagic::EmbeddedSignatureOld)
712    }
713
714    fn from_blob_bytes(data: &'a [u8]) -> Result<Self, AppleCodesignError> {
715        Ok(Self {
716            data: read_and_validate_blob_header(
717                data,
718                Self::magic(),
719                "old embedded signature blob",
720            )?,
721        })
722    }
723
724    fn serialize_payload(&self) -> Result<Vec<u8>, AppleCodesignError> {
725        Ok(self.data.to_vec())
726    }
727}
728
729/// Represents an Entitlements blob.
730///
731/// An entitlements blob contains an XML plist with a dict. Keys are
732/// strings of the entitlements being requested and values appear to be
733/// simple bools.
734#[derive(Debug)]
735pub struct EntitlementsBlob<'a> {
736    plist: Cow<'a, str>,
737}
738
739impl<'a> Blob<'a> for EntitlementsBlob<'a> {
740    fn magic() -> u32 {
741        u32::from(CodeSigningMagic::Entitlements)
742    }
743
744    fn from_blob_bytes(data: &'a [u8]) -> Result<Self, AppleCodesignError> {
745        let data = read_and_validate_blob_header(data, Self::magic(), "entitlements blob")?;
746        let s = std::str::from_utf8(data).map_err(AppleCodesignError::EntitlementsBadUtf8)?;
747
748        Ok(Self { plist: s.into() })
749    }
750
751    fn serialize_payload(&self) -> Result<Vec<u8>, AppleCodesignError> {
752        Ok(self.plist.as_bytes().to_vec())
753    }
754}
755
756impl<'a> EntitlementsBlob<'a> {
757    /// Construct an instance using any string as the payload.
758    pub fn from_string(s: &(impl ToString + ?Sized)) -> Self {
759        Self {
760            plist: s.to_string().into(),
761        }
762    }
763
764    /// Obtain the plist representation as a string.
765    pub fn as_str(&self) -> &str {
766        &self.plist
767    }
768}
769
770impl<'a> std::fmt::Display for EntitlementsBlob<'a> {
771    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
772        f.write_str(&self.plist)
773    }
774}
775
776#[derive(Debug)]
777pub struct EntitlementsDerBlob<'a> {
778    der: Cow<'a, [u8]>,
779}
780
781impl<'a> Blob<'a> for EntitlementsDerBlob<'a> {
782    fn magic() -> u32 {
783        u32::from(CodeSigningMagic::EntitlementsDer)
784    }
785
786    fn from_blob_bytes(data: &'a [u8]) -> Result<Self, AppleCodesignError> {
787        let der = read_and_validate_blob_header(data, Self::magic(), "DER entitlements blob")?;
788
789        Ok(Self { der: der.into() })
790    }
791
792    fn serialize_payload(&self) -> Result<Vec<u8>, AppleCodesignError> {
793        Ok(self.der.to_vec())
794    }
795}
796
797impl<'a> EntitlementsDerBlob<'a> {
798    /// Construct an instance from a [plist::Value].
799    ///
800    /// Not all plists can be encoded to this blob as not all plist value types can
801    /// be encoded to DER. If a plist with an illegal value is passed in, this
802    /// function will error, as DER encoding is performed immediately.
803    ///
804    /// The outermost plist value should be a dictionary.
805    pub fn from_plist(v: &plist::Value) -> Result<Self, AppleCodesignError> {
806        let der = crate::plist_der::der_encode_plist(v)?;
807
808        Ok(Self { der: der.into() })
809    }
810
811    /// Attempt to parse and resolve the DER data into a plist.
812    pub fn parse_der(&self) -> Result<plist::Value, AppleCodesignError> {
813        crate::plist_der::der_decode_plist(self.der.as_ref())
814    }
815
816    /// Parse the plist from DER and format to XML.
817    pub fn plist_xml(&self) -> Result<Vec<u8>, AppleCodesignError> {
818        let mut buffer = vec![];
819        self.parse_der()?.to_writer_xml(&mut buffer)?;
820
821        Ok(buffer)
822    }
823}
824
825/// A blob holding DER encoded launch/library constraints.
826///
827/// The inner data is a plist.
828#[derive(Debug)]
829pub struct ConstraintsDerBlob<'a> {
830    der: Cow<'a, [u8]>,
831}
832
833impl<'a> Blob<'a> for ConstraintsDerBlob<'a> {
834    fn magic() -> u32 {
835        u32::from(CodeSigningMagic::EnvironmentContraintsDer)
836    }
837
838    fn from_blob_bytes(data: &'a [u8]) -> Result<Self> {
839        let der = read_and_validate_blob_header(
840            data,
841            Self::magic(),
842            "DER encoded environment constraints blob",
843        )?;
844
845        Ok(Self { der: der.into() })
846    }
847
848    fn serialize_payload(&self) -> Result<Vec<u8>> {
849        Ok(self.der.to_vec())
850    }
851}
852
853impl<'a> ConstraintsDerBlob<'a> {
854    /// Construct an instance from a [EncodedEnvironmentConstraints] instance.
855    pub fn from_encoded_constraints(v: &EncodedEnvironmentConstraints) -> Result<Self> {
856        let der = v.der_encode()?;
857
858        Ok(Self { der: der.into() })
859    }
860
861    /// Attempt to parse and resolve the DER data into a plist.
862    pub fn parse_der_plist(&self) -> Result<plist::Value> {
863        crate::plist_der::der_decode_plist(self.der.as_ref())
864    }
865
866    /// Attempt to parse DER into an [EncodedEnvironmentConstraints] instance.
867    pub fn parse_encoded_constraints(&self) -> Result<EncodedEnvironmentConstraints> {
868        EncodedEnvironmentConstraints::from_der(self.der.as_ref())
869    }
870
871    /// Parse the plist from DER and format to XML.
872    pub fn plist_xml(&self) -> Result<Vec<u8>> {
873        let mut buffer = vec![];
874        self.parse_der_plist()?.to_writer_xml(&mut buffer)?;
875
876        Ok(buffer)
877    }
878}
879
880/// A detached signature.
881#[derive(Debug)]
882pub struct DetachedSignatureBlob<'a> {
883    data: &'a [u8],
884}
885
886impl<'a> Blob<'a> for DetachedSignatureBlob<'a> {
887    fn magic() -> u32 {
888        u32::from(CodeSigningMagic::DetachedSignature)
889    }
890
891    fn from_blob_bytes(data: &'a [u8]) -> Result<Self, AppleCodesignError> {
892        Ok(Self {
893            data: read_and_validate_blob_header(data, Self::magic(), "detached signature blob")?,
894        })
895    }
896
897    fn serialize_payload(&self) -> Result<Vec<u8>, AppleCodesignError> {
898        Ok(self.data.to_vec())
899    }
900}
901
902/// Represents a generic blob wrapper.
903pub struct BlobWrapperBlob<'a> {
904    data: Cow<'a, [u8]>,
905}
906
907impl<'a> Blob<'a> for BlobWrapperBlob<'a> {
908    fn magic() -> u32 {
909        u32::from(CodeSigningMagic::BlobWrapper)
910    }
911
912    fn from_blob_bytes(data: &'a [u8]) -> Result<Self, AppleCodesignError> {
913        Ok(Self {
914            data: read_and_validate_blob_header(data, Self::magic(), "blob wrapper blob")?.into(),
915        })
916    }
917
918    fn serialize_payload(&self) -> Result<Vec<u8>, AppleCodesignError> {
919        Ok(self.data.to_vec())
920    }
921}
922
923impl<'a> std::fmt::Debug for BlobWrapperBlob<'a> {
924    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
925        f.write_fmt(format_args!("{}", hex::encode(&self.data)))
926    }
927}
928
929impl<'a> BlobWrapperBlob<'a> {
930    /// Construct an instance where the payload (post blob header) is given data.
931    pub fn from_data_borrowed(data: &'a [u8]) -> BlobWrapperBlob<'a> {
932        Self { data: data.into() }
933    }
934}
935
936impl BlobWrapperBlob<'static> {
937    /// Construct an instance with payload data.
938    pub fn from_data_owned(data: Vec<u8>) -> BlobWrapperBlob<'static> {
939        Self { data: data.into() }
940    }
941}
942
943/// Represents an unknown blob type.
944pub struct OtherBlob<'a> {
945    pub magic: u32,
946    pub data: &'a [u8],
947}
948
949impl<'a> Blob<'a> for OtherBlob<'a> {
950    fn magic() -> u32 {
951        // Use a placeholder magic value because there is no self bind here.
952        u32::MAX
953    }
954
955    fn from_blob_bytes(data: &'a [u8]) -> Result<Self, AppleCodesignError> {
956        let (magic, _, data) = read_blob_header(data)?;
957
958        Ok(Self { magic, data })
959    }
960
961    fn serialize_payload(&self) -> Result<Vec<u8>, AppleCodesignError> {
962        Ok(self.data.to_vec())
963    }
964
965    // We need to implement this for custom magic serialization.
966    fn to_blob_bytes(&self) -> Result<Vec<u8>, AppleCodesignError> {
967        let mut res = Vec::with_capacity(self.data.len() + 8);
968        res.iowrite_with(self.magic, scroll::BE)?;
969        res.iowrite_with(self.data.len() as u32 + 8, scroll::BE)?;
970        res.write_all(self.data)?;
971
972        Ok(res)
973    }
974}
975
976impl<'a> std::fmt::Debug for OtherBlob<'a> {
977    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
978        f.write_fmt(format_args!("{}", hex::encode(self.data)))
979    }
980}
981
982/// Represents a single, parsed Blob entry/slot.
983///
984/// Each variant corresponds to a [CodeSigningMagic] blob type.
985#[derive(Debug)]
986pub enum BlobData<'a> {
987    Requirement(Box<RequirementBlob<'a>>),
988    RequirementSet(Box<RequirementSetBlob<'a>>),
989    CodeDirectory(Box<CodeDirectoryBlob<'a>>),
990    EmbeddedSignature(Box<EmbeddedSignatureBlob<'a>>),
991    EmbeddedSignatureOld(Box<EmbeddedSignatureOldBlob<'a>>),
992    Entitlements(Box<EntitlementsBlob<'a>>),
993    EntitlementsDer(Box<EntitlementsDerBlob<'a>>),
994    ConstraintsDer(Box<ConstraintsDerBlob<'a>>),
995    DetachedSignature(Box<DetachedSignatureBlob<'a>>),
996    BlobWrapper(Box<BlobWrapperBlob<'a>>),
997    Other(Box<OtherBlob<'a>>),
998}
999
1000impl<'a> Blob<'a> for BlobData<'a> {
1001    fn magic() -> u32 {
1002        u32::MAX
1003    }
1004
1005    /// Parse blob data by reading its magic and feeding into magic-specific parser.
1006    fn from_blob_bytes(data: &'a [u8]) -> Result<Self, AppleCodesignError> {
1007        let (magic, length, _) = read_blob_header(data)?;
1008
1009        // This should be a no-op. But it could (correctly) cause a panic if the
1010        // advertised length is incorrect and we would incur a buffer overrun.
1011        let data = &data[0..length];
1012
1013        let magic = CodeSigningMagic::from(magic);
1014
1015        Ok(match magic {
1016            CodeSigningMagic::Requirement => {
1017                Self::Requirement(Box::new(RequirementBlob::from_blob_bytes(data)?))
1018            }
1019            CodeSigningMagic::RequirementSet => {
1020                Self::RequirementSet(Box::new(RequirementSetBlob::from_blob_bytes(data)?))
1021            }
1022            CodeSigningMagic::CodeDirectory => {
1023                Self::CodeDirectory(Box::new(CodeDirectoryBlob::from_blob_bytes(data)?))
1024            }
1025            CodeSigningMagic::EmbeddedSignature => {
1026                Self::EmbeddedSignature(Box::new(EmbeddedSignatureBlob::from_blob_bytes(data)?))
1027            }
1028            CodeSigningMagic::EmbeddedSignatureOld => Self::EmbeddedSignatureOld(Box::new(
1029                EmbeddedSignatureOldBlob::from_blob_bytes(data)?,
1030            )),
1031            CodeSigningMagic::Entitlements => {
1032                Self::Entitlements(Box::new(EntitlementsBlob::from_blob_bytes(data)?))
1033            }
1034            CodeSigningMagic::EntitlementsDer => {
1035                Self::EntitlementsDer(Box::new(EntitlementsDerBlob::from_blob_bytes(data)?))
1036            }
1037            CodeSigningMagic::EnvironmentContraintsDer => {
1038                Self::ConstraintsDer(Box::new(ConstraintsDerBlob::from_blob_bytes(data)?))
1039            }
1040            CodeSigningMagic::DetachedSignature => {
1041                Self::DetachedSignature(Box::new(DetachedSignatureBlob::from_blob_bytes(data)?))
1042            }
1043            CodeSigningMagic::BlobWrapper => {
1044                Self::BlobWrapper(Box::new(BlobWrapperBlob::from_blob_bytes(data)?))
1045            }
1046            _ => Self::Other(Box::new(OtherBlob::from_blob_bytes(data)?)),
1047        })
1048    }
1049
1050    fn serialize_payload(&self) -> Result<Vec<u8>, AppleCodesignError> {
1051        match self {
1052            Self::Requirement(b) => b.serialize_payload(),
1053            Self::RequirementSet(b) => b.serialize_payload(),
1054            Self::CodeDirectory(b) => b.serialize_payload(),
1055            Self::EmbeddedSignature(b) => b.serialize_payload(),
1056            Self::EmbeddedSignatureOld(b) => b.serialize_payload(),
1057            Self::Entitlements(b) => b.serialize_payload(),
1058            Self::EntitlementsDer(b) => b.serialize_payload(),
1059            Self::ConstraintsDer(b) => b.serialize_payload(),
1060            Self::DetachedSignature(b) => b.serialize_payload(),
1061            Self::BlobWrapper(b) => b.serialize_payload(),
1062            Self::Other(b) => b.serialize_payload(),
1063        }
1064    }
1065
1066    fn to_blob_bytes(&self) -> Result<Vec<u8>, AppleCodesignError> {
1067        match self {
1068            Self::Requirement(b) => b.to_blob_bytes(),
1069            Self::RequirementSet(b) => b.to_blob_bytes(),
1070            Self::CodeDirectory(b) => b.to_blob_bytes(),
1071            Self::EmbeddedSignature(b) => b.to_blob_bytes(),
1072            Self::EmbeddedSignatureOld(b) => b.to_blob_bytes(),
1073            Self::Entitlements(b) => b.to_blob_bytes(),
1074            Self::EntitlementsDer(b) => b.to_blob_bytes(),
1075            Self::ConstraintsDer(b) => b.to_blob_bytes(),
1076            Self::DetachedSignature(b) => b.to_blob_bytes(),
1077            Self::BlobWrapper(b) => b.to_blob_bytes(),
1078            Self::Other(b) => b.to_blob_bytes(),
1079        }
1080    }
1081}
1082
1083impl<'a> From<RequirementBlob<'a>> for BlobData<'a> {
1084    fn from(b: RequirementBlob<'a>) -> Self {
1085        Self::Requirement(Box::new(b))
1086    }
1087}
1088
1089impl<'a> From<RequirementSetBlob<'a>> for BlobData<'a> {
1090    fn from(b: RequirementSetBlob<'a>) -> Self {
1091        Self::RequirementSet(Box::new(b))
1092    }
1093}
1094
1095impl<'a> From<CodeDirectoryBlob<'a>> for BlobData<'a> {
1096    fn from(b: CodeDirectoryBlob<'a>) -> Self {
1097        Self::CodeDirectory(Box::new(b))
1098    }
1099}
1100
1101impl<'a> From<EmbeddedSignatureBlob<'a>> for BlobData<'a> {
1102    fn from(b: EmbeddedSignatureBlob<'a>) -> Self {
1103        Self::EmbeddedSignature(Box::new(b))
1104    }
1105}
1106
1107impl<'a> From<EmbeddedSignatureOldBlob<'a>> for BlobData<'a> {
1108    fn from(b: EmbeddedSignatureOldBlob<'a>) -> Self {
1109        Self::EmbeddedSignatureOld(Box::new(b))
1110    }
1111}
1112
1113impl<'a> From<EntitlementsBlob<'a>> for BlobData<'a> {
1114    fn from(b: EntitlementsBlob<'a>) -> Self {
1115        Self::Entitlements(Box::new(b))
1116    }
1117}
1118
1119impl<'a> From<EntitlementsDerBlob<'a>> for BlobData<'a> {
1120    fn from(b: EntitlementsDerBlob<'a>) -> Self {
1121        Self::EntitlementsDer(Box::new(b))
1122    }
1123}
1124
1125impl<'a> From<ConstraintsDerBlob<'a>> for BlobData<'a> {
1126    fn from(b: ConstraintsDerBlob<'a>) -> Self {
1127        Self::ConstraintsDer(Box::new(b))
1128    }
1129}
1130
1131impl<'a> From<DetachedSignatureBlob<'a>> for BlobData<'a> {
1132    fn from(b: DetachedSignatureBlob<'a>) -> Self {
1133        Self::DetachedSignature(Box::new(b))
1134    }
1135}
1136
1137impl<'a> From<BlobWrapperBlob<'a>> for BlobData<'a> {
1138    fn from(b: BlobWrapperBlob<'a>) -> Self {
1139        Self::BlobWrapper(Box::new(b))
1140    }
1141}
1142
1143impl<'a> From<OtherBlob<'a>> for BlobData<'a> {
1144    fn from(b: OtherBlob<'a>) -> Self {
1145        Self::Other(Box::new(b))
1146    }
1147}
1148
1149/// Represents the parsed content of a blob entry.
1150#[derive(Debug)]
1151pub struct ParsedBlob<'a> {
1152    /// The blob record this blob came from.
1153    pub blob_entry: BlobEntry<'a>,
1154
1155    /// The parsed blob data.
1156    pub blob: BlobData<'a>,
1157}
1158
1159impl<'a> ParsedBlob<'a> {
1160    /// Compute the content digest of this blob using the specified hash type.
1161    pub fn digest_with(&self, hash: DigestType) -> Result<Vec<u8>, AppleCodesignError> {
1162        hash.digest_data(self.blob_entry.data)
1163    }
1164}
1165
1166impl<'a> TryFrom<BlobEntry<'a>> for ParsedBlob<'a> {
1167    type Error = AppleCodesignError;
1168
1169    fn try_from(blob_entry: BlobEntry<'a>) -> Result<Self, Self::Error> {
1170        let blob = BlobData::from_blob_bytes(blob_entry.data)?;
1171
1172        Ok(Self { blob_entry, blob })
1173    }
1174}
1175
1176/// Represents Apple's common embedded code signature data structures.
1177///
1178/// This type represents a lightly parsed `SuperBlob` with [CodeSigningMagic::EmbeddedSignature].
1179/// It is the most common embedded signature data format you are likely to encounter.
1180pub struct EmbeddedSignature<'a> {
1181    /// Magic value from header.
1182    pub magic: CodeSigningMagic,
1183    /// Length of this super blob.
1184    pub length: u32,
1185    /// Number of blobs in this super blob.
1186    pub count: u32,
1187
1188    /// Raw data backing this super blob.
1189    pub data: &'a [u8],
1190
1191    /// All the blobs within this super blob.
1192    pub blobs: Vec<BlobEntry<'a>>,
1193}
1194
1195impl<'a> std::fmt::Debug for EmbeddedSignature<'a> {
1196    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1197        f.debug_struct("SuperBlob")
1198            .field("magic", &self.magic)
1199            .field("length", &self.length)
1200            .field("count", &self.count)
1201            .field("blobs", &self.blobs)
1202            .finish()
1203    }
1204}
1205
1206// There are other impl blocks for this structure in other modules.
1207impl<'a> EmbeddedSignature<'a> {
1208    /// Attempt to parse an embedded signature super blob from data.
1209    ///
1210    /// The argument to this function is likely the subset of the
1211    /// `__LINKEDIT` Mach-O section that the `LC_CODE_SIGNATURE` load instructions
1212    /// points it.
1213    pub fn from_bytes(data: &'a [u8]) -> Result<Self, AppleCodesignError> {
1214        let offset = &mut 0;
1215
1216        // Parse the 3 fields from the SuperBlob.
1217        let magic = data.gread_with::<u32>(offset, scroll::BE)?.into();
1218
1219        if magic != CodeSigningMagic::EmbeddedSignature {
1220            return Err(AppleCodesignError::BadMagic(
1221                "embedded signature super blob",
1222            ));
1223        }
1224
1225        let length = data.gread_with(offset, scroll::BE)?;
1226        let count = data.gread_with(offset, scroll::BE)?;
1227
1228        // Following the SuperBlob header is an array of .count BlobIndex defining
1229        // the Blob that follow.
1230        //
1231        // The BlobIndex doesn't declare the length of each Blob. However, it appears
1232        // the first 8 bytes of each blob contain the u32 magic and u32 length.
1233        // We do parse those here and set the blob length/slice accordingly. However,
1234        // we take an extra level of precaution by first computing a slice that doesn't
1235        // overrun into the next blob or past the end of the input buffer. This
1236        // helps detect invalid length advertisements in the blob payload.
1237        let mut blob_indices = Vec::with_capacity(count as usize);
1238        for _ in 0..count {
1239            blob_indices.push(data.gread_with::<BlobIndex>(offset, scroll::BE)?);
1240        }
1241
1242        let mut blobs = Vec::with_capacity(blob_indices.len());
1243
1244        for (i, index) in blob_indices.iter().enumerate() {
1245            let end_offset = if i == blob_indices.len() - 1 {
1246                data.len()
1247            } else {
1248                blob_indices[i + 1].offset as usize
1249            };
1250
1251            let full_slice = &data[index.offset as usize..end_offset];
1252            let (magic, blob_length, _) = read_blob_header(full_slice)?;
1253
1254            // Self-reported length can't be greater than the data we have.
1255            let blob_data = match blob_length.cmp(&full_slice.len()) {
1256                Ordering::Greater => {
1257                    return Err(AppleCodesignError::SuperblobMalformed);
1258                }
1259                Ordering::Equal => full_slice,
1260                Ordering::Less => &full_slice[0..blob_length],
1261            };
1262
1263            blobs.push(BlobEntry {
1264                index: i,
1265                slot: index.typ.into(),
1266                offset: index.offset as usize,
1267                magic: magic.into(),
1268                length: blob_length,
1269                data: blob_data,
1270            });
1271        }
1272
1273        Ok(Self {
1274            magic,
1275            length,
1276            count,
1277            data,
1278            blobs,
1279        })
1280    }
1281
1282    /// Find the first occurrence of the specified slot.
1283    pub fn find_slot(&self, slot: CodeSigningSlot) -> Option<&BlobEntry<'a>> {
1284        self.blobs.iter().find(|e| e.slot == slot)
1285    }
1286
1287    pub fn find_slot_parsed(
1288        &self,
1289        slot: CodeSigningSlot,
1290    ) -> Result<Option<ParsedBlob<'a>>, AppleCodesignError> {
1291        if let Some(entry) = self.find_slot(slot) {
1292            Ok(Some(entry.clone().into_parsed_blob()?))
1293        } else {
1294            Ok(None)
1295        }
1296    }
1297
1298    /// Attempt to resolve the primary `CodeDirectoryBlob` for this signature data.
1299    ///
1300    /// Returns Err on data parsing error or if the blob slot didn't contain a code
1301    /// directory.
1302    ///
1303    /// Returns `Ok(None)` if there is no code directory slot.
1304    pub fn code_directory(&self) -> Result<Option<Box<CodeDirectoryBlob<'a>>>, AppleCodesignError> {
1305        if let Some(parsed) = self.find_slot_parsed(CodeSigningSlot::CodeDirectory)? {
1306            if let BlobData::CodeDirectory(cd) = parsed.blob {
1307                Ok(Some(cd))
1308            } else {
1309                Err(AppleCodesignError::BadMagic("code directory blob"))
1310            }
1311        } else {
1312            Ok(None)
1313        }
1314    }
1315
1316    /// Obtain code directories occupying alternative slots.
1317    ///
1318    /// Embedded signatures set aside a few slots for alternate code directory data structures.
1319    /// This method will resolve any that are present.
1320    pub fn alternate_code_directories(
1321        &self,
1322    ) -> Result<Vec<(CodeSigningSlot, Box<CodeDirectoryBlob<'a>>)>, AppleCodesignError> {
1323        let slots = [
1324            CodeSigningSlot::AlternateCodeDirectory0,
1325            CodeSigningSlot::AlternateCodeDirectory1,
1326            CodeSigningSlot::AlternateCodeDirectory2,
1327            CodeSigningSlot::AlternateCodeDirectory3,
1328            CodeSigningSlot::AlternateCodeDirectory4,
1329        ];
1330
1331        let mut res = vec![];
1332
1333        for slot in slots {
1334            if let Some(parsed) = self.find_slot_parsed(slot)? {
1335                if let BlobData::CodeDirectory(cd) = parsed.blob {
1336                    res.push((slot, cd));
1337                } else {
1338                    return Err(AppleCodesignError::BadMagic(
1339                        "wrong blob magic in alternative code directory slot",
1340                    ));
1341                }
1342            }
1343        }
1344
1345        Ok(res)
1346    }
1347
1348    /// Resolve all code directories in this signature.
1349    pub fn all_code_directories(
1350        &self,
1351    ) -> Result<Vec<(CodeSigningSlot, Box<CodeDirectoryBlob<'a>>)>, AppleCodesignError> {
1352        let mut res = vec![];
1353
1354        if let Some(cd) = self.code_directory()? {
1355            res.push((CodeSigningSlot::CodeDirectory, cd));
1356        }
1357
1358        res.extend(self.alternate_code_directories()?);
1359
1360        Ok(res)
1361    }
1362
1363    /// Attempt to resolve a code directory containing digests of the specified type.
1364    pub fn code_directory_for_digest(
1365        &self,
1366        digest: DigestType,
1367    ) -> Result<Option<Box<CodeDirectoryBlob<'a>>>, AppleCodesignError> {
1368        for (_, cd) in self.all_code_directories()? {
1369            if cd.digest_type == digest {
1370                return Ok(Some(cd));
1371            }
1372        }
1373
1374        Ok(None)
1375    }
1376
1377    /// Attempt to resolve the preferred code directory for this binary.
1378    ///
1379    /// Attempts to resolve the SHA-256 variant first, falling back to SHA-1 on failure, and
1380    /// falling back to the primary CD slot before erroring if no CD is present.
1381    pub fn preferred_code_directory(
1382        &self,
1383    ) -> Result<Box<CodeDirectoryBlob<'a>>, AppleCodesignError> {
1384        if let Some(cd) = self.code_directory_for_digest(DigestType::Sha256)? {
1385            Ok(cd)
1386        } else if let Some(cd) = self.code_directory_for_digest(DigestType::Sha1)? {
1387            Ok(cd)
1388        } else if let Some(cd) = self.code_directory()? {
1389            Ok(cd)
1390        } else {
1391            Err(AppleCodesignError::BinaryNoCodeDirectory)
1392        }
1393    }
1394
1395    /// Attempt to resolve a parsed [EntitlementsBlob] for this signature data.
1396    ///
1397    /// Returns Err on data parsing error or if the blob slot didn't contain an entitlments
1398    /// blob.
1399    ///
1400    /// Returns `Ok(None)` if there is no entitlements slot.
1401    pub fn entitlements(&self) -> Result<Option<Box<EntitlementsBlob<'a>>>, AppleCodesignError> {
1402        if let Some(parsed) = self.find_slot_parsed(CodeSigningSlot::Entitlements)? {
1403            if let BlobData::Entitlements(entitlements) = parsed.blob {
1404                Ok(Some(entitlements))
1405            } else {
1406                Err(AppleCodesignError::BadMagic("entitlements blob"))
1407            }
1408        } else {
1409            Ok(None)
1410        }
1411    }
1412
1413    /// Attempt to resolve a parsed [EntitlementsDerBlob] for this signature.
1414    pub fn entitlements_der(
1415        &self,
1416    ) -> Result<Option<Box<EntitlementsDerBlob<'a>>>, AppleCodesignError> {
1417        if let Some(parsed) = self.find_slot_parsed(CodeSigningSlot::EntitlementsDer)? {
1418            if let BlobData::EntitlementsDer(entitlements) = parsed.blob {
1419                Ok(Some(entitlements))
1420            } else {
1421                Err(AppleCodesignError::BadMagic("DER entitlements blob"))
1422            }
1423        } else {
1424            Ok(None)
1425        }
1426    }
1427
1428    /// Attempt to resolve a parsed [RequirementSetBlob] for this signature data.
1429    ///
1430    /// Returns Err on data parsing error or if the blob slot didn't contain a requirements
1431    /// blob.
1432    ///
1433    /// Returns `Ok(None)` if there is no requirements slot.
1434    pub fn code_requirements(
1435        &self,
1436    ) -> Result<Option<Box<RequirementSetBlob<'a>>>, AppleCodesignError> {
1437        if let Some(parsed) = self.find_slot_parsed(CodeSigningSlot::RequirementSet)? {
1438            if let BlobData::RequirementSet(reqs) = parsed.blob {
1439                Ok(Some(reqs))
1440            } else {
1441                Err(AppleCodesignError::BadMagic("requirements blob"))
1442            }
1443        } else {
1444            Ok(None)
1445        }
1446    }
1447
1448    /// Obtain the launch constraints on self blob.
1449    pub fn launch_constraints_self(&self) -> Result<Option<Box<ConstraintsDerBlob<'a>>>> {
1450        if let Some(parsed) = self.find_slot_parsed(CodeSigningSlot::LaunchConstraintsSelf)? {
1451            if let BlobData::ConstraintsDer(blob) = parsed.blob {
1452                Ok(Some(blob))
1453            } else {
1454                Err(AppleCodesignError::BadMagic("self launch constraints blob"))
1455            }
1456        } else {
1457            Ok(None)
1458        }
1459    }
1460
1461    /// Obtain the launch constraints on parent blob.
1462    pub fn launch_constraints_parent(&self) -> Result<Option<Box<ConstraintsDerBlob<'a>>>> {
1463        if let Some(parsed) = self.find_slot_parsed(CodeSigningSlot::LaunchConstraintsParent)? {
1464            if let BlobData::ConstraintsDer(blob) = parsed.blob {
1465                Ok(Some(blob))
1466            } else {
1467                Err(AppleCodesignError::BadMagic(
1468                    "parent launch constraints blob",
1469                ))
1470            }
1471        } else {
1472            Ok(None)
1473        }
1474    }
1475
1476    /// Obtain the launch constraints on responsible process blob.
1477    pub fn launch_constraints_responsible(&self) -> Result<Option<Box<ConstraintsDerBlob<'a>>>> {
1478        if let Some(parsed) =
1479            self.find_slot_parsed(CodeSigningSlot::LaunchConstraintsResponsibleProcess)?
1480        {
1481            if let BlobData::ConstraintsDer(blob) = parsed.blob {
1482                Ok(Some(blob))
1483            } else {
1484                Err(AppleCodesignError::BadMagic(
1485                    "responsible process launch constraints blob",
1486                ))
1487            }
1488        } else {
1489            Ok(None)
1490        }
1491    }
1492
1493    /// Obtain the library constraints blob.
1494    pub fn library_constraints(&self) -> Result<Option<Box<ConstraintsDerBlob<'a>>>> {
1495        if let Some(parsed) = self.find_slot_parsed(CodeSigningSlot::LibraryConstraints)? {
1496            if let BlobData::ConstraintsDer(blob) = parsed.blob {
1497                Ok(Some(blob))
1498            } else {
1499                Err(AppleCodesignError::BadMagic("library constraints blob"))
1500            }
1501        } else {
1502            Ok(None)
1503        }
1504    }
1505
1506    /// Attempt to resolve raw CMS signature data.
1507    ///
1508    /// The returned data is likely DER PKCS#7 with the root object
1509    /// pkcs7-signedData (1.2.840.113549.1.7.2).
1510    pub fn signature_data(&self) -> Result<Option<&'a [u8]>, AppleCodesignError> {
1511        if let Some(parsed) = self.find_slot(CodeSigningSlot::Signature) {
1512            // Make sure it validates.
1513            ParsedBlob::try_from(parsed.clone())?;
1514
1515            Ok(Some(parsed.payload()?))
1516        } else {
1517            Ok(None)
1518        }
1519    }
1520
1521    /// Obtain the parsed CMS [SignedData].
1522    pub fn signed_data(&self) -> Result<Option<SignedData>, AppleCodesignError> {
1523        if let Some(data) = self.signature_data()? {
1524            // Sometime we get an empty data slice. This has been observed on DMG signatures.
1525            // In that scenario, pretend there is no CMS data at all.
1526            if data.is_empty() {
1527                Ok(None)
1528            } else {
1529                let signed_data = SignedData::parse_ber(data)?;
1530
1531                Ok(Some(signed_data))
1532            }
1533        } else {
1534            Ok(None)
1535        }
1536    }
1537}