apple_codesign/
macho_signing.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//! Signing mach-o binaries.
6//!
7//! This module contains code for signing mach-o binaries.
8
9use {
10    crate::{
11        code_directory::{CodeDirectoryBlob, CodeSignatureFlags, ExecutableSegmentFlags},
12        code_requirement::{CodeRequirementExpression, CodeRequirements, RequirementType},
13        cryptography::Digest,
14        embedded_signature::{
15            Blob, BlobData, CodeSigningSlot, ConstraintsDerBlob, EntitlementsBlob,
16            EntitlementsDerBlob, RequirementSetBlob,
17        },
18        embedded_signature_builder::EmbeddedSignatureBuilder,
19        entitlements::plist_to_executable_segment_flags,
20        error::AppleCodesignError,
21        macho::{semver_to_macho_target_version, MachFile, MachOBinary},
22        macho_universal::create_universal_macho,
23        policy::derive_designated_requirements,
24        signing_settings::{DesignatedRequirementMode, SettingsScope, SigningSettings},
25    },
26    goblin::mach::{
27        constants::{SEG_LINKEDIT, SEG_PAGEZERO},
28        load_command::{
29            CommandVariant, LinkeditDataCommand, SegmentCommand32, SegmentCommand64,
30            LC_CODE_SIGNATURE, SIZEOF_LINKEDIT_DATA_COMMAND,
31        },
32        parse_magic_and_ctx,
33    },
34    log::{debug, info, warn},
35    scroll::{ctx::SizeWith, IOwrite},
36    std::{borrow::Cow, cmp::Ordering, collections::HashMap, io::Write, path::Path},
37};
38
39/// Derive a new Mach-O binary with new signature data.
40fn create_macho_with_signature(
41    macho: &MachOBinary,
42    signature_data: &[u8],
43) -> Result<Vec<u8>, AppleCodesignError> {
44    // This should have already been called. But we do it again out of paranoia.
45    macho.check_signing_capability()?;
46
47    // The assumption made by checking_signing_capability() is that signature data
48    // is at the end of the __LINKEDIT segment. So the replacement segment is the
49    // existing segment truncated at the signature start followed by the new signature
50    // data.
51    //
52    // Code signature data is aligned on 16 byte boundary by Apple convention.
53    //
54    // Typically segment data is aligned on pages, which are multiples of 16 bytes. So
55    // it doesn't matter if we align based on Mach-O file-level or __LINKEDIT
56    // segment-level offsets: the end result is 16 byte alignment in both.
57
58    let linkedit_data_before_signature = macho
59        .linkedit_data_before_signature()
60        .ok_or(AppleCodesignError::MissingLinkedit)?;
61
62    let signature_file_offset = macho.code_limit_binary_offset()?;
63    let remainder = (signature_file_offset % 16) as usize;
64    let signature_padding_length = if remainder == 0 { 0 } else { 16 - remainder };
65
66    let signature_file_offset = signature_file_offset + signature_padding_length as u64;
67
68    let new_linkedit_segment_size =
69        linkedit_data_before_signature.len() + signature_padding_length + signature_data.len();
70
71    // `codesign` rounds up the segment's vmsize to the nearest 16kb boundary.
72    // We emulate that behavior.
73    let remainder = new_linkedit_segment_size % 16384;
74    let new_linkedit_segment_vmsize = if remainder == 0 {
75        new_linkedit_segment_size
76    } else {
77        new_linkedit_segment_size + 16384 - remainder
78    };
79
80    assert!(new_linkedit_segment_vmsize >= new_linkedit_segment_size);
81    assert_eq!(new_linkedit_segment_vmsize % 16384, 0);
82
83    let mut cursor = std::io::Cursor::new(Vec::<u8>::new());
84
85    // Mach-O data structures are variable endian. So use the endian defined
86    // by the magic when writing.
87    let ctx = parse_magic_and_ctx(macho.data, 0)?
88        .1
89        .expect("context should have been parsed before");
90
91    // If there isn't a code signature presently, we'll need to introduce a load
92    // command for it.
93    let mut header = macho.macho.header;
94    if macho.code_signature_load_command().is_none() {
95        header.ncmds += 1;
96        header.sizeofcmds += SIZEOF_LINKEDIT_DATA_COMMAND as u32;
97    }
98
99    cursor.iowrite_with(header, ctx)?;
100
101    // Following the header are load commands. We need to update load commands
102    // to reflect changes to the signature size and __LINKEDIT segment size.
103
104    let mut seen_signature_load_command = false;
105
106    for load_command in &macho.macho.load_commands {
107        let original_command_data =
108            &macho.data[load_command.offset..load_command.offset + load_command.command.cmdsize()];
109
110        let written_len = match &load_command.command {
111            CommandVariant::CodeSignature(command) => {
112                seen_signature_load_command = true;
113
114                let mut command = *command;
115                command.dataoff = signature_file_offset as _;
116                command.datasize = signature_data.len() as _;
117
118                cursor.iowrite_with(command, ctx.le)?;
119
120                LinkeditDataCommand::size_with(&ctx.le)
121            }
122            CommandVariant::Segment32(segment) => {
123                let segment = match segment.name() {
124                    Ok(SEG_LINKEDIT) => {
125                        let mut segment = *segment;
126                        segment.filesize = new_linkedit_segment_size as _;
127                        segment.vmsize = new_linkedit_segment_vmsize as _;
128
129                        segment
130                    }
131                    _ => *segment,
132                };
133
134                cursor.iowrite_with(segment, ctx.le)?;
135
136                SegmentCommand32::size_with(&ctx.le)
137            }
138            CommandVariant::Segment64(segment) => {
139                let segment = match segment.name() {
140                    Ok(SEG_LINKEDIT) => {
141                        let mut segment = *segment;
142                        segment.filesize = new_linkedit_segment_size as _;
143                        segment.vmsize = new_linkedit_segment_vmsize as _;
144
145                        segment
146                    }
147                    _ => *segment,
148                };
149
150                cursor.iowrite_with(segment, ctx.le)?;
151
152                SegmentCommand64::size_with(&ctx.le)
153            }
154            _ => {
155                // Reflect the original bytes.
156                cursor.write_all(original_command_data)?;
157                original_command_data.len()
158            }
159        };
160
161        // For the commands we mutated ourselves, there may be more data after the
162        // load command header. Write it out if present.
163        cursor.write_all(&original_command_data[written_len..])?;
164    }
165
166    // If we didn't see a signature load command, write one out now.
167    // Note: we're assuming that there's enough space between the end of
168    // the original load commands and the beginning of the first section.
169    // All this intermediate data should be 0s and we shouldn't be
170    // interfering with anything here. But you never know.
171    // TODO validate the added load command doesn't overflow into a section
172    // or otherwise clobber data in the binary.
173    if !seen_signature_load_command {
174        let command = LinkeditDataCommand {
175            cmd: LC_CODE_SIGNATURE,
176            cmdsize: SIZEOF_LINKEDIT_DATA_COMMAND as _,
177            dataoff: signature_file_offset as _,
178            datasize: signature_data.len() as _,
179        };
180
181        cursor.iowrite_with(command, ctx.le)?;
182    }
183
184    let mut wrote_non_empty_segment = false;
185
186    // Write out segments, updating the __LINKEDIT segment when we encounter it.
187    for segment in macho.segments_by_file_offset() {
188        // The initial __PAGEZERO segment contains no data (it is the magic and load
189        // commands) and overlaps with the __TEXT segment, so we ignore it.
190        if matches!(segment.name(), Ok(SEG_PAGEZERO)) {
191            continue;
192        }
193
194        match cursor.position().cmp(&segment.fileoff) {
195            // Mach-O segments may have padding between them. In this case, copy these
196            // bytes (presumably NULLs but that isn't guaranteed) to the output.
197            Ordering::Less => {
198                let padding = &macho.data[cursor.position() as usize..segment.fileoff as usize];
199                debug!(
200                    "copying {} bytes outside segment boundaries before segment {}",
201                    padding.len(),
202                    segment.name().unwrap_or("<unknown>")
203                );
204                cursor.write_all(padding)?;
205            }
206
207            // The __TEXT segment usually has .fileoff = 0, which has it overlapping with
208            // already written data. Allow this special case through.
209            Ordering::Greater if segment.fileoff == 0 => {}
210
211            // The initial non-empty segment is special because it can overlap
212            // we the already written load commands.
213            //
214            // Usually the first non-empty segment is __TEXT and its file start
215            // offset is 0x0. But we've seen binaries in the wild where the
216            // offset is > 0x0. As long as the current cursor is before the first
217            // section data, there should be no data corruption and we're good.
218            Ordering::Greater if !wrote_non_empty_segment => {}
219
220            // The writer has overran into this segment. That means we screwed up on a
221            // previous loop iteration.
222            Ordering::Greater => {
223                return Err(AppleCodesignError::MachOWrite(format!(
224                    "Mach-O segment corruption: cursor at 0x{:x} but segment begins at 0x{:x} (please report this bug)",
225                    cursor.position(),
226                    segment.fileoff
227                )));
228            }
229            Ordering::Equal => {}
230        }
231
232        match segment.name() {
233            Ok(SEG_LINKEDIT) => {
234                cursor.write_all(
235                    macho
236                        .linkedit_data_before_signature()
237                        .expect("__LINKEDIT segment data should resolve"),
238                )?;
239
240                let padding = vec![0u8; signature_padding_length];
241                cursor.write_all(&padding)?;
242
243                assert_eq!(cursor.position(), signature_file_offset);
244                assert_eq!(cursor.position() % 16, 0);
245                cursor.write_all(signature_data)?;
246            }
247            _ => {
248                // At least the __TEXT segment has .fileoff = 0, which has it
249                // overlapping with already written data. So only write segment
250                // data new to the writer.
251                if segment.fileoff < cursor.position() {
252                    if segment.data.is_empty() {
253                        continue;
254                    }
255                    let remaining =
256                        &segment.data[cursor.position() as usize..segment.filesize as usize];
257                    cursor.write_all(remaining)?;
258                } else {
259                    cursor.write_all(segment.data)?;
260                }
261            }
262        }
263
264        wrote_non_empty_segment = true;
265    }
266
267    Ok(cursor.into_inner())
268}
269
270/// Write Mach-O file content to an output file.
271pub fn write_macho_file(
272    input_path: &Path,
273    output_path: &Path,
274    macho_data: &[u8],
275) -> Result<(), AppleCodesignError> {
276    // Read permissions first in case we overwrite the original file.
277    let permissions = std::fs::metadata(input_path)?.permissions();
278
279    if let Some(parent) = output_path.parent() {
280        std::fs::create_dir_all(parent)?;
281    }
282
283    {
284        let mut fh = std::fs::File::create(output_path)?;
285        fh.write_all(macho_data)?;
286    }
287
288    std::fs::set_permissions(output_path, permissions)?;
289
290    Ok(())
291}
292
293/// Mach-O binary signer.
294///
295/// This type provides a high-level interface for signing Mach-O binaries.
296/// It handles parsing and rewriting Mach-O binaries and contains most of the
297/// functionality for producing signatures for individual Mach-O binaries.
298///
299/// Signing of both single architecture and fat/universal binaries is supported.
300///
301/// # Circular Dependency
302///
303/// There is a circular dependency between the generation of the Code Directory
304/// present in the embedded signature and the Mach-O binary. See the note
305/// in [crate::specification] for the gory details. The tl;dr is the Mach-O
306/// data up to the signature data needs to be digested. But that digested data
307/// contains load commands that reference the signature data and its size, which
308/// can't be known until the Code Directory, CMS blob, and SuperBlob are all
309/// created.
310///
311/// Our solution to this problem is to estimate the size of the embedded
312/// signature data and then pad the unused data will 0s.
313pub struct MachOSigner<'data> {
314    /// Parsed Mach-O binaries.
315    machos: Vec<MachOBinary<'data>>,
316}
317
318impl<'data> MachOSigner<'data> {
319    /// Construct a new instance from unparsed data representing a Mach-O binary.
320    ///
321    /// The data will be parsed as a Mach-O binary (either single arch or fat/universal)
322    /// and validated that we are capable of signing it.
323    pub fn new(macho_data: &'data [u8]) -> Result<Self, AppleCodesignError> {
324        let machos = MachFile::parse(macho_data)?.into_iter().collect::<Vec<_>>();
325
326        Ok(Self { machos })
327    }
328
329    /// Write signed Mach-O data to the given writer using signing settings.
330    pub fn write_signed_binary(
331        &self,
332        settings: &SigningSettings,
333        writer: &mut impl Write,
334    ) -> Result<(), AppleCodesignError> {
335        // Implementing a true streaming writer requires calculating final sizes
336        // of all binaries so fat header offsets and sizes can be written first. We take
337        // the easy road and buffer individual Mach-O binaries internally.
338
339        let binaries = self
340            .machos
341            .iter()
342            .enumerate()
343            .map(|(index, original_macho)| {
344                info!("signing Mach-O binary at index {}", index);
345                let settings = settings
346                    .as_universal_macho_settings(index, original_macho.macho.header.cputype());
347
348                let signature_len =
349                    self.estimate_embedded_signature_size(original_macho, &settings)?;
350
351                // Derive an intermediate Mach-O with placeholder NULLs for signature
352                // data so Code Directory digests over the load commands are correct.
353                let placeholder_signature_data = b"\0".repeat(signature_len);
354
355                let intermediate_macho_data =
356                    create_macho_with_signature(original_macho, &placeholder_signature_data)?;
357
358                // A nice side-effect of this is that it catches bugs if we write malformed Mach-O!
359                let intermediate_macho = MachOBinary::parse(&intermediate_macho_data)?;
360
361                let mut signature_data = self.create_superblob(&settings, &intermediate_macho)?;
362                info!("total signature size: {} bytes", signature_data.len());
363
364                // The Mach-O writer adjusts load commands based on the signature length. So pad
365                // with NULLs to get to our placeholder length.
366                match signature_data.len().cmp(&placeholder_signature_data.len()) {
367                    Ordering::Greater => {
368                        return Err(AppleCodesignError::SignatureDataTooLarge);
369                    }
370                    Ordering::Equal => {}
371                    Ordering::Less => {
372                        signature_data.extend_from_slice(
373                            &b"\0".repeat(placeholder_signature_data.len() - signature_data.len()),
374                        );
375                    }
376                }
377
378                create_macho_with_signature(&intermediate_macho, &signature_data)
379            })
380            .collect::<Result<Vec<_>, AppleCodesignError>>()?;
381
382        if binaries.len() > 1 {
383            create_universal_macho(writer, binaries.iter().map(|x| x.as_slice()))?;
384        } else {
385            writer.write_all(&binaries[0])?;
386        }
387
388        Ok(())
389    }
390
391    /// Create data constituting the SuperBlob to be embedded in the `__LINKEDIT` segment.
392    ///
393    /// The superblob contains the code directory, any extra blobs, and an optional
394    /// CMS structure containing a cryptographic signature.
395    ///
396    /// This takes an explicit Mach-O to operate on due to a circular dependency
397    /// between writing out the Mach-O and digesting its content. See the note
398    /// in [MachOSigner] for details.
399    pub fn create_superblob(
400        &self,
401        settings: &SigningSettings,
402        macho: &MachOBinary,
403    ) -> Result<Vec<u8>, AppleCodesignError> {
404        let mut builder = EmbeddedSignatureBuilder::default();
405
406        for (slot, blob) in self.create_special_blobs(settings, macho.is_executable())? {
407            builder.add_blob(slot, blob)?;
408        }
409
410        let code_directory = self.create_code_directory(settings, macho)?;
411        info!("code directory version: {}", code_directory.version);
412
413        builder.add_code_directory(CodeSigningSlot::CodeDirectory, code_directory)?;
414
415        if let Some(digests) = settings.extra_digests(SettingsScope::Main) {
416            for digest_type in digests {
417                // Since everything consults settings for the digest to use, just make a new settings
418                // with a different digest.
419                let mut alt_settings = settings.clone();
420                alt_settings.set_digest_type(SettingsScope::Main, *digest_type);
421
422                info!(
423                    "adding alternative code directory using digest {:?}",
424                    digest_type
425                );
426                let cd = self.create_code_directory(&alt_settings, macho)?;
427
428                builder.add_alternative_code_directory(cd)?;
429            }
430        }
431
432        if let Some((signing_key, signing_cert)) = settings.signing_key() {
433            builder.create_cms_signature(
434                signing_key,
435                signing_cert,
436                settings.time_stamp_url(),
437                settings.certificate_chain().iter().cloned(),
438                settings.signing_time(),
439            )?;
440        } else {
441            builder.create_empty_cms_signature()?;
442        }
443
444        builder.create_superblob()
445    }
446
447    /// Create the `CodeDirectory` for the current configuration.
448    ///
449    /// This takes an explicit Mach-O to operate on due to a circular dependency
450    /// between writing out the Mach-O and digesting its content. See the note
451    /// in [MachOSigner] for details.
452    pub fn create_code_directory(
453        &self,
454        settings: &SigningSettings,
455        macho: &MachOBinary,
456    ) -> Result<CodeDirectoryBlob<'static>, AppleCodesignError> {
457        // TODO support defining or filling in proper values for fields with
458        // static values.
459
460        let target = macho.find_targeting()?;
461
462        if let Some(target) = &target {
463            info!(
464                "binary targets {} >= {} with SDK {}",
465                target.platform, target.minimum_os_version, target.sdk_version,
466            );
467        }
468
469        let mut flags = CodeSignatureFlags::empty();
470
471        if let Some(additional) = settings.code_signature_flags(SettingsScope::Main) {
472            info!(
473                "adding code signature flags from signing settings: {:?}",
474                additional
475            );
476            flags |= additional;
477        }
478
479        // The adhoc flag is set when there is no CMS signature.
480        if settings.signing_key().is_none() {
481            info!("creating ad-hoc signature");
482            flags |= CodeSignatureFlags::ADHOC;
483        } else if flags.contains(CodeSignatureFlags::ADHOC) {
484            info!("removing ad-hoc code signature flag");
485            flags -= CodeSignatureFlags::ADHOC;
486        }
487
488        // Remove linker signed flag because we're not a linker.
489        if flags.contains(CodeSignatureFlags::LINKER_SIGNED) {
490            info!("removing linker signed flag from code signature (we're not a linker)");
491            flags -= CodeSignatureFlags::LINKER_SIGNED;
492        }
493
494        // Code limit fields hold the file offset at which code digests stop. This
495        // is the file offset in the `__LINKEDIT` segment when the embedded signature
496        // SuperBlob begins.
497        let (code_limit, code_limit_64) = match macho.code_limit_binary_offset()? {
498            x if x > u32::MAX as u64 => (0, Some(x)),
499            x => (x as u32, None),
500        };
501
502        let platform = 0;
503        let page_size = 4096u32;
504
505        let (exec_seg_base, exec_seg_limit) = macho.executable_segment_boundary()?;
506        let (exec_seg_base, exec_seg_limit) = (Some(exec_seg_base), Some(exec_seg_limit));
507
508        // Executable segment flags are wonky.
509        //
510        // Foremost, these flags are only present if the Mach-O binary is an executable. So not
511        // matter what the settings say, we don't set these flags unless the Mach-O file type
512        // is proper.
513        //
514        // Executable segment flags are also derived from an associated entitlements plist.
515        let exec_seg_flags = if macho.is_executable() {
516            if let Some(entitlements) = settings.entitlements_plist(SettingsScope::Main) {
517                let flags = plist_to_executable_segment_flags(entitlements);
518
519                if !flags.is_empty() {
520                    info!("entitlements imply executable segment flags: {:?}", flags);
521                }
522
523                Some(flags | ExecutableSegmentFlags::MAIN_BINARY)
524            } else {
525                Some(ExecutableSegmentFlags::MAIN_BINARY)
526            }
527        } else {
528            None
529        };
530
531        // The runtime version is the SDK version from the targeting loader commands. Same
532        // u32 with nibbles encoding the version.
533        //
534        // If the runtime code signature flag is set, we also need to set the runtime version
535        // or else the activation of the hardened runtime is incomplete.
536
537        // If the settings defines a runtime version override, use it.
538        let runtime = match settings.runtime_version(SettingsScope::Main) {
539            Some(version) => {
540                info!(
541                    "using hardened runtime version {} from signing settings",
542                    version
543                );
544                Some(semver_to_macho_target_version(version))
545            }
546            None => None,
547        };
548
549        // If we still don't have a runtime but need one, derive from the target SDK.
550        let runtime = if runtime.is_none() && flags.contains(CodeSignatureFlags::RUNTIME) {
551            if let Some(target) = &target {
552                info!(
553                    "using hardened runtime version {} derived from SDK version",
554                    target.sdk_version
555                );
556                Some(semver_to_macho_target_version(&target.sdk_version))
557            } else {
558                warn!("hardened runtime version required but unable to derive suitable version; signature will likely fail Apple checks");
559                None
560            }
561        } else {
562            runtime
563        };
564
565        let digest_type = settings.digest_type(SettingsScope::Main);
566
567        let code_hashes = macho
568            .code_digests(digest_type, page_size as _)?
569            .into_iter()
570            .map(|v| Digest { data: v.into() })
571            .collect::<Vec<_>>();
572
573        let mut special_hashes = HashMap::new();
574
575        // There is no corresponding blob for the info plist data since it is provided
576        // externally to the embedded signature.
577        if let Some(data) = settings.info_plist_data(SettingsScope::Main) {
578            special_hashes.insert(
579                CodeSigningSlot::Info,
580                Digest {
581                    data: digest_type.digest_data(data)?.into(),
582                },
583            );
584        }
585
586        // There is no corresponding blob for resources data since it is provided
587        // externally to the embedded signature.
588        if let Some(data) = settings.code_resources_data(SettingsScope::Main) {
589            special_hashes.insert(
590                CodeSigningSlot::ResourceDir,
591                Digest {
592                    data: digest_type.digest_data(data)?.into(),
593                }
594                .to_owned(),
595            );
596        }
597
598        let ident = Cow::Owned(
599            settings
600                .binary_identifier(SettingsScope::Main)
601                .ok_or(AppleCodesignError::NoIdentifier)?
602                .to_string(),
603        );
604
605        // Team should only be included when signing with an Apple signed
606        // certificate. This logic is handled in [SigningSettings]. But emit
607        // a warning if the constraint is violated.
608        let team_name = settings.team_id().map(|x| Cow::Owned(x.to_string()));
609
610        if team_name.is_some() && !settings.signing_certificate_apple_signed() {
611            warn!("signing without an Apple signed certificate but signing settings contain a team name; signature varies from Apple's tooling");
612        }
613
614        let mut cd = CodeDirectoryBlob {
615            flags,
616            code_limit,
617            digest_size: digest_type.hash_len()? as u8,
618            digest_type,
619            platform,
620            page_size,
621            code_limit_64,
622            exec_seg_base,
623            exec_seg_limit,
624            exec_seg_flags,
625            runtime,
626            ident,
627            team_name,
628            code_digests: code_hashes,
629            ..Default::default()
630        };
631
632        for (slot, digest) in special_hashes {
633            cd.set_slot_digest(slot, digest)?;
634        }
635
636        cd.adjust_version(target);
637        cd.clear_newer_fields();
638
639        Ok(cd)
640    }
641
642    /// Create blobs that need to be written given the current configuration.
643    ///
644    /// This emits all blobs except `CodeDirectory` and `Signature`, which are
645    /// special since they are derived from the blobs emitted here.
646    ///
647    /// The goal of this function is to emit data to facilitate the creation of
648    /// a `CodeDirectory`, which requires hashing blobs.
649    pub fn create_special_blobs(
650        &self,
651        settings: &SigningSettings,
652        is_executable: bool,
653    ) -> Result<Vec<(CodeSigningSlot, BlobData<'static>)>, AppleCodesignError> {
654        let mut res = Vec::new();
655
656        let mut requirements = CodeRequirements::default();
657
658        match settings.designated_requirement(SettingsScope::Main) {
659            DesignatedRequirementMode::Auto => {
660                // If we are using an Apple-issued cert, this should automatically
661                // derive appropriate designated requirements.
662                if let Some((_, cert)) = settings.signing_key() {
663                    info!("deriving code requirements from signing certificate");
664                    let identifier = Some(
665                        settings
666                            .binary_identifier(SettingsScope::Main)
667                            .ok_or(AppleCodesignError::NoIdentifier)?
668                            .to_string(),
669                    );
670
671                    let expr = derive_designated_requirements(
672                        cert,
673                        settings.certificate_chain(),
674                        identifier,
675                    )?;
676                    requirements.push(expr);
677                }
678            }
679            DesignatedRequirementMode::Explicit(exprs) => {
680                info!("using provided code requirements");
681                for expr in exprs {
682                    requirements.push(CodeRequirementExpression::from_bytes(expr)?.0);
683                }
684            }
685        }
686
687        // Always emit a RequirementSet blob, even if empty. Without it, validation fails
688        // with `the sealed resource directory is invalid`.
689        let mut blob = RequirementSetBlob::default();
690
691        if !requirements.is_empty() {
692            requirements.add_to_requirement_set(&mut blob, RequirementType::Designated)?;
693        }
694
695        res.push((CodeSigningSlot::RequirementSet, blob.into()));
696
697        if let Some(entitlements) = settings.entitlements_xml(SettingsScope::Main)? {
698            let blob = EntitlementsBlob::from_string(&entitlements);
699
700            res.push((CodeSigningSlot::Entitlements, blob.into()));
701        }
702
703        // The DER encoded entitlements weren't always present in the signature. The feature
704        // appears to have been introduced in macOS 10.14 and is the default behavior as of
705        // macOS 12 "when signing for all platforms." `codesign` appears to add the DER
706        // representation whenever entitlements are present, but only if the current binary is
707        // an executable (.filetype == MH_EXECUTE).
708        if is_executable {
709            if let Some(value) = settings.entitlements_plist(SettingsScope::Main) {
710                let blob = EntitlementsDerBlob::from_plist(value)?;
711
712                res.push((CodeSigningSlot::EntitlementsDer, blob.into()));
713            }
714        }
715
716        if let Some(constraints) = settings.launch_constraints_self(SettingsScope::Main) {
717            let blob = ConstraintsDerBlob::from_encoded_constraints(constraints)?;
718            res.push((CodeSigningSlot::LaunchConstraintsSelf, blob.into()));
719        }
720
721        if let Some(constraints) = settings.launch_constraints_parent(SettingsScope::Main) {
722            let blob = ConstraintsDerBlob::from_encoded_constraints(constraints)?;
723            res.push((CodeSigningSlot::LaunchConstraintsParent, blob.into()));
724        }
725
726        if let Some(constraints) = settings.launch_constraints_responsible(SettingsScope::Main) {
727            let blob = ConstraintsDerBlob::from_encoded_constraints(constraints)?;
728            res.push((
729                CodeSigningSlot::LaunchConstraintsResponsibleProcess,
730                blob.into(),
731            ));
732        }
733
734        if let Some(constraints) = settings.library_constraints(SettingsScope::Main) {
735            let blob = ConstraintsDerBlob::from_encoded_constraints(constraints)?;
736            res.push((CodeSigningSlot::LibraryConstraints, blob.into()));
737        }
738
739        Ok(res)
740    }
741
742    /// Estimate the size in bytes of an embedded code signature.
743    pub fn estimate_embedded_signature_size(
744        &self,
745        macho: &MachOBinary,
746        settings: &SigningSettings,
747    ) -> Result<usize, AppleCodesignError> {
748        let code_directory_count = 1 + settings
749            .extra_digests(SettingsScope::Main)
750            .map(|x| x.len())
751            .unwrap_or_default();
752
753        // Assume the common data structures are 1024 bytes.
754        let mut size = 1024 * code_directory_count;
755
756        // Reserve room for the code digests, which are proportional to binary size.
757        size += macho.code_digests_size(settings.digest_type(SettingsScope::Main), 4096)?;
758
759        if let Some(digests) = settings.extra_digests(SettingsScope::Main) {
760            for digest in digests {
761                size += macho.code_digests_size(*digest, 4096)?;
762            }
763        }
764
765        // Add in sizes of all encoded blobs, as many blobs are variable size.
766        for (_, blob) in self.create_special_blobs(settings, true)? {
767            size += blob.to_blob_bytes()?.len();
768        }
769
770        // Assume the CMS data will take a fixed size.
771        size += 4096;
772
773        // Long certificate chains could blow up the size. Account for those.
774        for cert in settings.certificate_chain() {
775            size += cert.constructed_data().len();
776        }
777
778        // Resize space for CMS timestamp token, if being generated.
779        //
780        // We used to actually call out to a remote server here and obtain a
781        // placeholder token. But this seemed excessive, especially since we did
782        // it on every signing operation.
783        //
784        // Apple's TSTs are ~4200 bytes in size. We approximately double that
785        // to give us some buffer.
786        if settings.time_stamp_url().is_some() {
787            size += 8192;
788        }
789
790        // Align on 1k boundaries just because.
791        size += 1024 - size % 1024;
792
793        Ok(size)
794    }
795}