From b26376f50bfb13a024938004531474d40676e578 Mon Sep 17 00:00:00 2001 From: Gregory Szorc Date: Sun, 5 Nov 2023 18:14:02 +0800 Subject: [PATCH] apple-codesign: rerite bundle signing This commit overhauls how file traversal during bundle signing works to hopefully align more closely with how Apple's tooling does it. Before, we used code in the apple-bundles crate for walking only files in a directory. And our integration with the CodeResources rules flags was not super robust because we didn't fully understand how they worked. One of the limitations of this approach is that we didn't apply rules to directories. So we relied on our bundle detection code to seal nested bundles. In the new code, we perform a full filesystem walk of the bundle directory and the CodeResources rules are canonical. Rules are applied to directories. And the nested flag is used as the canonical source for whether a path should be sealed. As part of this, we also clean up handling of the main executable. We add an exclusion rule for the main executable so it is excluded from the sealing traversal. While we're here, we also add code for detecting when a regular file is a Mach-O binary and print a warning message about it. This clearly exposes some gaps in recursive bundle signing, as evidenced by test output changing. As the `sign-bundle-multiple-macho` test changes demonstrate, this change appears to fix a bug where we were installing non main exe nested Mach-O binaries into the wrong directory. This may be enough on its own to close a few GitHub issues. But since we still fail to sign Mach-O binaries without the nested flag set, I'm going to hold off referencing issues in this commit message. --- Cargo.lock | 1 + apple-codesign/CHANGELOG.md | 3 + apple-codesign/Cargo.toml | 1 + apple-codesign/src/bundle_signing.rs | 86 +-- apple-codesign/src/code_resources.rs | 531 +++++++++--------- apple-codesign/src/error.rs | 3 + .../tests/cmd/sign-bundle-electron.trycmd | 19 +- .../cmd/sign-bundle-framework-shallow.trycmd | 3 +- .../tests/cmd/sign-bundle-framework.trycmd | 3 +- .../cmd/sign-bundle-multiple-macho.trycmd | 186 +++--- .../sign-bundle-with-nested-framework.trycmd | 8 +- apple-codesign/tests/cmd/sign-bundle.trycmd | 2 - 12 files changed, 413 insertions(+), 433 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3fd007607..88e0bfc77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -249,6 +249,7 @@ dependencies = [ "trycmd", "tungstenite", "uuid", + "walkdir", "x509", "x509-certificate", "xml-rs", diff --git a/apple-codesign/CHANGELOG.md b/apple-codesign/CHANGELOG.md index cfe19262e..153953d2f 100644 --- a/apple-codesign/CHANGELOG.md +++ b/apple-codesign/CHANGELOG.md @@ -59,6 +59,9 @@ Released on ReleaseDate. `--pem-unified-filename` argument to write a PEM encoded file containing both the private key and public certificate. * Fixed a bug where files would be identified as Mach-O when they weren't. +* Bundle signing logic has been significantly overhauled to hopefully make + it conform with Apple tooling's behavior. This likely fixed several bugs + with bundle signing. * aws crates 0.53 -> 0.57. * bitflags 1.3 -> 2.0. * cryptographic-message-syntax 0.19 -> 0.25. diff --git a/apple-codesign/Cargo.toml b/apple-codesign/Cargo.toml index 0e8acf5dc..ae13ef1c2 100644 --- a/apple-codesign/Cargo.toml +++ b/apple-codesign/Cargo.toml @@ -74,6 +74,7 @@ thiserror = "1.0.50" tokio = { version = "1.33.0", features = ["rt"] } tungstenite = { version = "0.20.1", features = ["rustls-tls-native-roots"] } uuid = { version = "1.5.0", features = ["v4"] } +walkdir = "2.4.0" x509 = "0.2.0" x509-certificate = "0.22.1" xml-rs = "0.8.19" diff --git a/apple-codesign/src/bundle_signing.rs b/apple-codesign/src/bundle_signing.rs index 1e3fdc438..e0851439e 100644 --- a/apple-codesign/src/bundle_signing.rs +++ b/apple-codesign/src/bundle_signing.rs @@ -8,7 +8,7 @@ use { crate::{ code_directory::CodeDirectoryBlob, code_requirement::{CodeRequirementExpression, RequirementType}, - code_resources::{CodeResourcesBuilder, CodeResourcesRule}, + code_resources::{normalized_resources_path, CodeResourcesBuilder, CodeResourcesRule}, embedded_signature::{Blob, BlobData, DigestType}, error::AppleCodesignError, macho::MachFile, @@ -265,6 +265,8 @@ impl SignedMachOInfo { /// This abstraction lets entities like [CodeResourcesBuilder] drive the /// installation of files into a new bundle. pub trait BundleFileHandler { + fn dest_dir(&self) -> &Path; + /// Ensures a file (regular or symlink) is installed. fn install_file( &self, @@ -289,6 +291,10 @@ struct SingleBundleHandler<'a, 'key> { } impl<'a, 'key> BundleFileHandler for SingleBundleHandler<'a, 'key> { + fn dest_dir(&self) -> &Path { + &self.dest_dir + } + fn install_file( &self, source_path: &Path, @@ -325,6 +331,7 @@ impl<'a, 'key> BundleFileHandler for SingleBundleHandler<'a, 'key> { source_path.display(), dest_path.display() ); + // TODO consider stripping XATTR_RESOURCEFORK_NAME and XATTR_FINDERINFO_NAME. std::fs::copy(source_path, &dest_path)?; filetime::set_file_mtime(&dest_path, mtime)?; } @@ -525,67 +532,32 @@ impl SingleBundleSigner { resources_builder.add_exclusion_rule(CodeResourcesRule::new("^_CodeSignature/")?.exclude()); // Ignore notarization ticket. resources_builder.add_exclusion_rule(CodeResourcesRule::new("^CodeResources$")?.exclude()); + // Ignore store manifest directory. + resources_builder.add_exclusion_rule(CodeResourcesRule::new("^_MASReceipt$")?.exclude()); + + // The bundle's main executable file's code directory needs to hold a + // digest of the CodeResources file for the bundle. Therefore it needs to + // be handled last. We add an exclusion rule to prevent the directory walker + // from touching this file. + if let Some(main_exe) = &main_exe { + // Also seal the resources normalized path, just in case it is different. + resources_builder.add_exclusion_rule( + CodeResourcesRule::new(format!( + "^{}$", + regex::escape(&normalized_resources_path(main_exe.relative_path())) + ))? + .exclude(), + ); + } let handler = SingleBundleHandler { dest_dir: dest_dir_root.clone(), settings, }; - let mut info_plist_data = None; - - // Iterate files in this bundle and register as code resources. - // - // Traversing into nested bundles seems wrong but it is correct. The resources builder - // has rules to determine whether to process a path and assuming the rules and evaluation - // of them is correct, it is able to decide for itself how to handle a path. - // - // Furthermore, this behavior is needed as bundles can encapsulate signatures for nested - // bundles. For example, you could have a framework bundle with an embedded app bundle in - // `Resources/MyApp.app`! In this case, the framework's CodeResources encapsulates the - // content of `Resources/My.app` per the processing rules. - for file in self - .bundle - .files(true) - .map_err(AppleCodesignError::DirectoryBundle)? - { - // The main executable is special and handled below. - if file - .is_main_executable() - .map_err(AppleCodesignError::DirectoryBundle)? - { - continue; - } else if file.is_info_plist() { - // The Info.plist is digested specially. But it may also be handled by - // the resources handler. So always feed it through. - info!( - "{} is the Info.plist file; handling specially", - file.relative_path().display() - ); - resources_builder.process_file(&file, &handler)?; - info_plist_data = Some(std::fs::read(file.absolute_path())?); - } else { - resources_builder.process_file(&file, &handler)?; - } - } + resources_builder.walk_and_seal_directory(self.bundle.root_dir(), &handler)?; - // Seal code directory digests of any nested bundles. - // - // Apple's tooling seems to only do this for some bundle type combinations. I'm - // not yet sure what the complete heuristic is. But we observed that frameworks - // don't appear to include digests of any nested app bundles. So we add that - // exclusion. iOS bundles don't seem to include digests for nested bundles either. - // We should figure out what the actual rules here... - if !self.bundle.shallow() { - let dest_bundle = DirectoryBundle::new_from_path(&dest_dir) - .map_err(AppleCodesignError::DirectoryBundle)?; - - for (rel_path, nested_bundle) in dest_bundle - .nested_bundles(false) - .map_err(AppleCodesignError::DirectoryBundle)? - { - resources_builder.process_nested_bundle(&rel_path, &nested_bundle)?; - } - } + let info_plist_data = std::fs::read(self.bundle.info_plist_path())?; // The resources are now sealed. Write out that XML file. let code_resources_path = dest_dir.join("_CodeSignature").join("CodeResources"); @@ -627,9 +599,7 @@ impl SingleBundleSigner { settings.set_code_resources_data(SettingsScope::Main, resources_data); - if let Some(info_plist_data) = info_plist_data { - settings.set_info_plist_data(SettingsScope::Main, info_plist_data); - } + settings.set_info_plist_data(SettingsScope::Main, info_plist_data); let mut new_data = Vec::::with_capacity(macho_data.len() + 2_usize.pow(17)); signer.write_signed_binary(&settings, &mut new_data)?; diff --git a/apple-codesign/src/code_resources.rs b/apple-codesign/src/code_resources.rs index 96ede9109..1b9f9cdf2 100644 --- a/apple-codesign/src/code_resources.rs +++ b/apple-codesign/src/code_resources.rs @@ -21,14 +21,14 @@ use { embedded_signature::DigestType, error::AppleCodesignError, }, - apple_bundles::{DirectoryBundle, DirectoryBundleFile}, + apple_bundles::DirectoryBundle, log::{debug, info, warn}, plist::{Dictionary, Value}, std::{ cmp::Ordering, - collections::BTreeMap, + collections::{BTreeMap, BTreeSet}, io::Write, - path::{Path, PathBuf}, + path::Path, }, }; @@ -492,14 +492,24 @@ pub struct CodeResourcesRule { /// The `` in the `` or `` dict. pub pattern: String, - /// Whether this is an exclusion rule. + /// Matched paths are excluded from processing completely. + /// + /// If any rule with this flag matches a path, the path is excluded. pub exclude: bool, + /// The matched path is a signable entity. + /// + /// The path should be signed before sealing. And its seal may be + /// stored specially. pub nested: bool, + /// Whether to omit the path from sealing. + /// + /// Paths matching this rule can exist in a bundle. But their content + /// isn't captured in the `CodeResources` file. pub omit: bool, - /// Whether the rule is optional. + /// Unknown. Best guess is whether the file's presence is optional. pub optional: bool, /// Weighting to apply to the rule. @@ -535,10 +545,10 @@ impl Ord for CodeResourcesRule { // Exclusion rules always take priority over inclusion rules. // The smaller the weight, the less important it is. - match self.exclude.cmp(&other.exclude) { - Ordering::Equal => their_weight.cmp(&our_weight), - Ordering::Greater => Ordering::Less, - Ordering::Less => Ordering::Greater, + match (self.exclude, other.exclude) { + (true, false) => Ordering::Less, + (false, true) => Ordering::Greater, + _ => their_weight.cmp(&our_weight), } } } @@ -816,6 +826,7 @@ impl CodeResources { /// /// `path` is the path of the symlink and `target` is the path it points to. pub fn seal_symlink(&mut self, path: impl ToString, target: impl ToString) { + // Version 1 doesn't support sealing symlinks. self.files2.insert( path.to_string(), Files2Value { @@ -906,31 +917,29 @@ impl From<&CodeResources> for Value { } } -#[derive(Clone, Debug)] -enum RulesEvaluation { - /// File should be ignored completely. - Exclude, +/// Convert a relative filesystem path to its `CodeResources` normalized form. +pub fn normalized_resources_path(path: impl AsRef) -> String { + // Always use UNIX style directory separators. + let path = path.as_ref().to_string_lossy().replace('\\', "/"); - /// File isn't sealed but it is installed. - Omit, + // The Contents/ prefix is also removed for pattern matching and references in the + // resources file. + let path = path.strip_prefix("Contents/").unwrap_or(&path).to_string(); - /// Seal a symlink. - /// - /// Members are the relative path and the target path. - SealSymlink(String, String), - - /// Seal a nested Mach-O binary. - /// - /// Members are the relative path and whether the rule is optional. - SealNested(String, bool), - - /// Seal a regular file. - /// - /// Members are the relative path and whether the rule is optional. - SealRegularFile(String, bool), + path +} - /// File doesn't match any rules. - NoRule, +/// Find the first rule matching a given path. +/// +/// Internally, rules are sorted by decreasing priority, with exclusion +/// rules having highest priority. So the first pattern that matches is +/// rule we use. +/// +/// Pattern matches are always against the normalized filename. (e.g. +/// `Contents/` is stripped.) +fn find_rule(rules: &[CodeResourcesRule], path: impl AsRef) -> Option { + let path = normalized_resources_path(path); + rules.iter().find(|rule| rule.re.is_match(&path)).cloned() } /// Interface for constructing a `CodeResources` instance. @@ -1071,7 +1080,8 @@ impl CodeResourcesBuilder { /// Add an exclusion rule to the processing rules. /// /// Exclusion rules are not added to the [CodeResources] because they are - /// for building only. + /// implicit and used for filesystem traversal to influence which entities + /// are skipped. pub fn add_exclusion_rule(&mut self, rule: CodeResourcesRule) { self.rules.push(rule.clone()); self.rules.sort(); @@ -1079,273 +1089,274 @@ impl CodeResourcesBuilder { self.rules2.sort(); } - /// Find the first rule matching a given path. + /// Recursively seal a bundle directory. /// - /// Rule processing is a bit complicated. Internally, rules are sorted by - /// decreasing priority. So the first pattern that matches is the rule we use. - /// However, there are a few special cases. + /// This function does the heavy lifting of walking a bundle directory + /// and sealing the content inside. /// - /// If a path begins with `Contents/`, that prefix is ignored when performing the - /// pattern match. + /// For each filesystem entry, it finds the most appropriate registered + /// rule that applies to it. Then using that rule it takes actions. /// - /// Directories are special. If an exclusion rule matches a directory, that directory - /// tree should be ignored. There are also default rules for handling nested bundles. - /// These rules take precedence over directory exclusion rules. - fn find_rule(rules: &[CodeResourcesRule], path: &str) -> Option { - let parts = path.split('/').collect::>(); - - let mut exclude_override = false; - - let rule = rules.iter().find(|rule| { - // Nested rules matching leaf-most directory with `.` result in match. - // But we treat as exclusion, as these are treated as nested bundles, - // which are handled externally. - if rule.nested { - for last_part in 1..parts.len() - 1 { - let parent = parts[0..last_part].join("/"); - - if rule.re.is_match(&parent) && parts[last_part - 1].contains('.') { - exclude_override = true; - return true; - } - } - } + /// Typically, each file entity has its digest recorded/sealed. + /// + /// As a side-effect, files are copied/installed into the destination + /// directory as part of sealing. + pub fn walk_and_seal_directory( + &mut self, + root: &Path, + file_handler: &dyn BundleFileHandler, + ) -> Result<(), AppleCodesignError> { + let mut skipping_rel_dirs = BTreeSet::new(); - // Directory exclusions match entire directory tree. So walk the parents and yield - // this rule if matches. - if rule.exclude { - for last_part in 1..parts.len() - 1 { - let parent = parts[0..last_part].join("/"); + for entry in walkdir::WalkDir::new(root).sort_by_file_name() { + let entry = entry?; + let path = entry.path(); - if rule.re.is_match(&parent) { - return true; - } - } + if path == root { + continue; } - rule.re.is_match(path) - }); + let rel_path = path + .strip_prefix(root) + .expect("stripping path prefix should always work"); + let rel_path_normalized = normalized_resources_path(rel_path); - if let Some(rule) = rule { - let mut rule = rule.clone(); + let file_name = rel_path + .file_name() + .expect("should have final path component") + .to_string_lossy() + .to_string(); - if exclude_override { - rule.exclude = true; + //debug!("{}:{}:{}", root.display(), rel_path_normalized, file_name); + + // We're excluding a parent directory. Do nothing. + if skipping_rel_dirs.iter().any(|p| rel_path.starts_with(p)) { + debug!("{} ignored because marked as skipped", rel_path.display()); + continue; } - Some(rule) - } else { - None - } - } + // Rules version 2. + if let Some(rule) = find_rule(&self.rules2, rel_path) { + if entry.file_type().is_dir() { + if rule.nested { + // Only treat as a nested bundle iff it has a dot in its name. + if file_name.contains('.') { + // We assume the bundle has already been signed because that's + // how our bundle walker works. So all we need to do here is + // seal the bundle. We can skip handling all files in this + // directory since they've already been processed. + self.seal_rules2_nested_bundle( + path, + rel_path, + &rel_path_normalized, + rule.optional, + file_handler.dest_dir(), + )?; + + skipping_rel_dirs.insert(rel_path.to_path_buf()); + } + } else if rule.exclude { + info!( + "{} marked as excluded in resource rules", + rel_path_normalized + ); + skipping_rel_dirs.insert(rel_path.to_path_buf()); + } - fn evaluate_rules( - rules: &[CodeResourcesRule], - relative_path: impl AsRef, - symlink_target: Option, - ) -> Result { - // Always use UNIX style directory separators. - let relative_path = relative_path.as_ref().to_string_lossy().replace('\\', "/"); - - // The Contents/ prefix is also removed for pattern matching and references in the - // resources file. - let relative_path = relative_path - .strip_prefix("Contents/") - .unwrap_or(&relative_path) - .to_string(); - - match Self::find_rule(rules, relative_path.as_ref()) { - Some(rule) => { - debug!( - "{} matches {} rule {}", - relative_path, - if rule.exclude || rule.omit { - "exclusion" + // No need to do anything else since we'll walk into directory + // to handle files. + } else if entry.file_type().is_file() { + if rule.exclude { + debug!("{} ignoring file due to exclude rule", rel_path_normalized); + continue; + } + + // Nested flag means the file should itself be signable. + if rule.nested { + if crate::reader::path_is_macho(path)? { + info!("sealing nested Mach-O binary: {}", rel_path.display()); + + let macho_info = file_handler.sign_and_install_macho(path, rel_path)?; + + self.resources.seal_macho( + &rel_path_normalized, + &macho_info, + rule.optional, + )?; + } else { + // TODO what should we do here? + info!("would seal nested file: {}", rel_path.display()); + } } else { - "inclusion" - }, - rule.pattern - ); + self.seal_rules2_file( + path, + rel_path, + &rel_path_normalized, + rule.omit, + rule.optional, + file_handler, + )?; + } + } else if entry.file_type().is_symlink() { + if rule.exclude { + info!( + "{} ignoring symlink due to exclude rule", + rel_path_normalized + ); + continue; + } - if rule.exclude { - Ok(RulesEvaluation::Exclude) - } else if rule.omit { - Ok(RulesEvaluation::Omit) - } else if rule.nested && symlink_target.is_some() { - // Symlinks in nested bundles can be excluded since they should have - // been processed by the nested bundle. - Ok(RulesEvaluation::Exclude) - } else if let Some(target) = symlink_target { - let target = target.to_string_lossy().replace('\\', "/"); - - Ok(RulesEvaluation::SealSymlink(relative_path, target)) - } else if rule.nested { - Ok(RulesEvaluation::SealNested(relative_path, rule.optional)) + self.seal_rules2_symlink( + path, + rel_path, + &rel_path_normalized, + rule.omit, + file_handler, + )?; } else { - Ok(RulesEvaluation::SealRegularFile( - relative_path, - rule.optional, - )) + warn!( + "{} unexpected file type encountering during bundle signing", + rel_path_normalized + ); } } - None => { - debug!("{} doesn't match any rule", relative_path); - Ok(RulesEvaluation::NoRule) + + // Now rules version 1. Only regular files can be sealed. Version + // 1 does not support nested signatures nor symlinks. + if let Some(rule) = find_rule(&self.rules, rel_path) { + if entry.file_type().is_file() { + if rule.exclude { + continue; + } + + self.seal_rules1_file(path, &rel_path_normalized, rule)?; + } } } + + Ok(()) } - /// Process the `` set for a given file. - fn process_file_rules2( + /// Seal a nested bundle for rules version 2. + fn seal_rules2_nested_bundle( &mut self, - file: &DirectoryBundleFile, - file_handler: &dyn BundleFileHandler, + full_path: &Path, + rel_path: &Path, + rel_path_normalized: &str, + optional: bool, + dest_dir: &Path, ) -> Result<(), AppleCodesignError> { - match Self::evaluate_rules( - &self.rules2, - file.relative_path(), - file.symlink_target() - .map_err(AppleCodesignError::DirectoryBundle)?, - )? { - RulesEvaluation::Exclude => { - // Excluded files are hard ignored. These files are likely handled out-of-band - // from this builder. - Ok(()) - } - RulesEvaluation::Omit => { - // Omitted files aren't sealed. But they are installed. - file_handler.install_file(file.absolute_path(), file.relative_path()) - } - RulesEvaluation::NoRule => { - // No rule match is assumed to mean full ignore. - Ok(()) - } - RulesEvaluation::SealSymlink(relative_path, target) => { - info!("sealing symlink {} -> {}", relative_path, target); - self.resources.seal_symlink(relative_path, target); - file_handler.install_file(file.absolute_path(), file.relative_path()) - } - RulesEvaluation::SealNested(relative_path, optional) => { - // The assumption that a nested match means Mach-O may not be correct. - info!("sealing Mach-O file {}", relative_path); - let macho_info = file_handler - .sign_and_install_macho(file.absolute_path(), &PathBuf::from(&relative_path))?; - - self.resources - .seal_macho(relative_path, &macho_info, optional) - } - RulesEvaluation::SealRegularFile(relative_path, optional) => { - info!("sealing regular file {}", relative_path); - let data = std::fs::read(file.absolute_path())?; + info!( + "sealing nested directory as a bundle: {}", + rel_path.display() + ); + let bundle = DirectoryBundle::new_from_path(full_path)?; - let flavor = if self.digests.contains(&DigestType::Sha1) { - FilesFlavor::Rules2WithSha1 - } else { - FilesFlavor::Rules2 - }; + if let Some(nested_exe) = bundle + .files(false)? + .into_iter() + .find(|f| matches!(f.is_main_executable(), Ok(true))) + { + let nested_exe = dest_dir.join(rel_path).join(nested_exe.relative_path()); - self.resources - .seal_regular_file(flavor, relative_path, data, optional)?; - file_handler.install_file(file.absolute_path(), file.relative_path()) - } - } - } + info!("reading Mach-O signature from {}", nested_exe.display()); + let macho_data = std::fs::read(&nested_exe)?; + let macho_info = SignedMachOInfo::parse_data(&macho_data)?; - /// Process the `` set for a given file. - /// - /// Since `` handling actually does the file installs, the only role of this - /// handler is to record the SHA-1 seals in ``. Keep in mind that `` can't - /// handle symlinks or nested Mach-O binaries. So we only care about regular files here. - fn process_file_rules(&mut self, file: &DirectoryBundleFile) -> Result<(), AppleCodesignError> { - match Self::evaluate_rules( - &self.rules, - file.relative_path(), - file.symlink_target() - .map_err(AppleCodesignError::DirectoryBundle)?, - )? { - RulesEvaluation::Exclude - | RulesEvaluation::Omit - | RulesEvaluation::NoRule - | RulesEvaluation::SealSymlink(..) - | RulesEvaluation::SealNested(..) => Ok(()), - RulesEvaluation::SealRegularFile(relative_path, optional) => { - let data = std::fs::read(file.absolute_path())?; - - self.resources - .seal_regular_file(FilesFlavor::Rules, relative_path, data, optional) - } + self.resources + .seal_macho(rel_path_normalized, &macho_info, optional)?; + } else { + warn!( + "could not find main executable of presumed nested bundle: {}", + rel_path.display() + ); } - } - /// Process a file for resource handling. - /// - /// This determines whether a file is relevant for inclusion in the CodeResources - /// file and takes actions to process it, if necessary. - pub fn process_file( - &mut self, - file: &DirectoryBundleFile, - file_handler: &dyn BundleFileHandler, - ) -> Result<(), AppleCodesignError> { - self.process_file_rules2(file, file_handler)?; - self.process_file_rules(file) + Ok(()) } - /// Process a nested bundle for inclusion in resource handling. - /// - /// This will attempt to seal the main digest of the bundle into this resources file. - pub fn process_nested_bundle( + /// Seal a file for version 2 rules. + fn seal_rules2_file( &mut self, - relative_path: &str, - bundle: &DirectoryBundle, + full_path: &Path, + rel_path: &Path, + rel_path_normalized: &str, + omit: bool, + optional: bool, + handler: &dyn BundleFileHandler, ) -> Result<(), AppleCodesignError> { - let main_exe = match bundle - .files(false) - .map_err(AppleCodesignError::DirectoryBundle)? - .into_iter() - .find(|file| matches!(file.is_main_executable(), Ok(true))) - { - Some(path) => path, - None => { + // Only seal if the omit flag is unset. But install unconditionally + // in all cases. + if !omit { + // It could be a Mach-O binary. In which case sign it. But seal as + // a regular file. + info!("sealing regular file {}", rel_path_normalized); + + if crate::reader::path_is_macho(full_path)? { warn!( - "nested bundle at {} does not have main executable; nothing to seal", - relative_path + "non-nested file is a Mach-O binary; it is NOT signed automatically: {}", + rel_path.display() ); - return Ok(()); } - }; - let (relative_path, optional) = - match Self::evaluate_rules(&self.rules2, relative_path, None)? { - RulesEvaluation::SealRegularFile(relative_path, optional) => { - (relative_path, optional) - } - RulesEvaluation::SealNested(relative_path, optional) => (relative_path, optional), - RulesEvaluation::Exclude => { - info!( - "excluding signing nested bundle {} because of matched resources rule", - relative_path - ); - return Ok(()); - } - res => { - warn!( - "unexpected resource rules evaluation result for nested bundle {}: {:?}", - relative_path, res - ); - return Err(AppleCodesignError::BundleUnexpectedResourceRuleResult); - } + let data = std::fs::read(full_path)?; + + let flavor = if self.digests.contains(&DigestType::Sha1) { + FilesFlavor::Rules2WithSha1 + } else { + FilesFlavor::Rules2 }; - info!( - "reading code signature from main executable {}", - main_exe.relative_path().display() - ); - let macho_data = std::fs::read(main_exe.absolute_path())?; - let macho_info = SignedMachOInfo::parse_data(&macho_data)?; + self.resources + .seal_regular_file(flavor, rel_path_normalized, data, optional)?; + } + + handler.install_file(full_path, rel_path)?; - info!("sealing nested bundle at {}", relative_path); - self.resources - .seal_macho(relative_path, &macho_info, optional)?; + Ok(()) + } + + fn seal_rules2_symlink( + &mut self, + full_path: &Path, + rel_path: &Path, + rel_path_normalized: &str, + omit: bool, + handler: &dyn BundleFileHandler, + ) -> Result<(), AppleCodesignError> { + let link_target = std::fs::read_link(full_path)? + .to_string_lossy() + .replace('\\', "/"); + + if !omit { + info!("sealing symlink {} -> {}", rel_path_normalized, link_target); + self.resources + .seal_symlink(rel_path_normalized, link_target); + } + handler.install_file(full_path, rel_path)?; + + Ok(()) + } + + /// Perform sealing activity for an entry in rules v1. + fn seal_rules1_file( + &mut self, + full_path: &Path, + rel_path_normalized: &str, + rule: CodeResourcesRule, + ) -> Result<(), AppleCodesignError> { + // Version 1 doesn't handle symlinks nor nested Mach-O binaries. + // And version 2's handler installed files. So all we have to do here + // is record SHA-1 digests in ``. + + let data = std::fs::read(full_path)?; + + self.resources.seal_regular_file( + FilesFlavor::Rules, + rel_path_normalized, + data, + rule.optional, + )?; Ok(()) } diff --git a/apple-codesign/src/error.rs b/apple-codesign/src/error.rs index 2f70cd3d0..ecdcd73e6 100644 --- a/apple-codesign/src/error.rs +++ b/apple-codesign/src/error.rs @@ -259,6 +259,9 @@ pub enum AppleCodesignError { #[error("error producing universal Mach-O binary: {0}")] UniversalMachO(#[from] UniversalMachOError), + #[error("walkdir error: {0}")] + WalkDir(#[from] walkdir::Error), + #[error("zip error: {0}")] ZipError(#[from] zip::result::ZipError), diff --git a/apple-codesign/tests/cmd/sign-bundle-electron.trycmd b/apple-codesign/tests/cmd/sign-bundle-electron.trycmd index 8a2e2401a..f7603485f 100644 --- a/apple-codesign/tests/cmd/sign-bundle-electron.trycmd +++ b/apple-codesign/tests/cmd/sign-bundle-electron.trycmd @@ -81,7 +81,7 @@ entering nested bundle Electron.app/Contents/Frameworks/Electron Framework.frame signing bundle at Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A into Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Versions/A found an unversioned framework; signing like normal collecting code resources files -sealing Mach-O file Helpers/chrome_crashpad_handler +sealing nested Mach-O binary: Helpers/chrome_crashpad_handler signing Mach-O file Helpers/chrome_crashpad_handler inferring default signing settings from Mach-O binary Mach-O is missing binary identifier; setting to chrome_crashpad_handler based on file name @@ -92,10 +92,10 @@ code directory version: 132096 total signature size: 392 bytes writing Mach-O to Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Versions/A/Helpers/chrome_crashpad_handler sealing regular file Libraries/libEGL.dylib +non-nested file is a Mach-O binary; it is NOT signed automatically: Libraries/libEGL.dylib copying file Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libEGL.dylib -> Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libEGL.dylib sealing regular file Libraries/vk_swiftshader_icd.json copying file Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/vk_swiftshader_icd.json -> Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/vk_swiftshader_icd.json -Resources/Info.plist is the Info.plist file; handling specially sealing regular file Resources/Info.plist copying file Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Info.plist -> Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Info.plist sealing regular file Resources/MainMenu.nib @@ -134,7 +134,6 @@ entering nested bundle Electron.app/Contents/Frameworks/Mantle.framework/Version signing bundle at Electron.app/Contents/Frameworks/Mantle.framework/Versions/A into Electron.app.signed/Contents/Frameworks/Mantle.framework/Versions/A found an unversioned framework; signing like normal collecting code resources files -Resources/Info.plist is the Info.plist file; handling specially sealing regular file Resources/Info.plist copying file Electron.app/Contents/Frameworks/Mantle.framework/Versions/A/Resources/Info.plist -> Electron.app.signed/Contents/Frameworks/Mantle.framework/Versions/A/Resources/Info.plist writing sealed resources to Electron.app.signed/Contents/Frameworks/Mantle.framework/Versions/A/_CodeSignature/CodeResources @@ -151,7 +150,6 @@ leaving nested bundle Electron.app/Contents/Frameworks/Mantle.framework/Versions entering nested bundle Electron.app/Contents/Frameworks/Electron Helper (GPU).app signing bundle at Electron.app/Contents/Frameworks/Electron Helper (GPU).app into Electron.app.signed/Contents/Frameworks/Electron Helper (GPU).app collecting code resources files -Contents/Info.plist is the Info.plist file; handling specially copying file Electron.app/Contents/Frameworks/Electron Helper (GPU).app/Contents/Info.plist -> Electron.app.signed/Contents/Frameworks/Electron Helper (GPU).app/Contents/Info.plist writing sealed resources to Electron.app.signed/Contents/Frameworks/Electron Helper (GPU).app/Contents/_CodeSignature/CodeResources signing main executable Contents/MacOS/Electron Helper (GPU) @@ -173,7 +171,12 @@ replicating symlink Electron.app.signed/Contents/Frameworks/Mantle.framework/Ver leaving nested bundle Electron.app/Contents/Frameworks/Mantle.framework signing bundle at Electron.app into Electron.app.signed collecting code resources files -Contents/Info.plist is the Info.plist file; handling specially +sealing nested directory as a bundle: Contents/Frameworks/Electron Framework.framework +reading Mach-O signature from Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Electron Framework +sealing nested directory as a bundle: Contents/Frameworks/Electron Helper (GPU).app +reading Mach-O signature from Electron.app.signed/Contents/Frameworks/Electron Helper (GPU).app/Contents/MacOS/Electron Helper (GPU) +sealing nested directory as a bundle: Contents/Frameworks/Mantle.framework +reading Mach-O signature from Electron.app.signed/Contents/Frameworks/Mantle.framework/Mantle copying file Electron.app/Contents/Info.plist -> Electron.app.signed/Contents/Info.plist sealing regular file Resources/default_app.asar copying file Electron.app/Contents/Resources/default_app.asar -> Electron.app.signed/Contents/Resources/default_app.asar @@ -181,12 +184,6 @@ sealing regular file Resources/electron.icns copying file Electron.app/Contents/Resources/electron.icns -> Electron.app.signed/Contents/Resources/electron.icns sealing regular file Resources/en.lproj copying file Electron.app/Contents/Resources/en.lproj -> Electron.app.signed/Contents/Resources/en.lproj -reading code signature from main executable Electron Framework -sealing nested bundle at Frameworks/Electron Framework.framework -reading code signature from main executable Contents/MacOS/Electron Helper (GPU) -sealing nested bundle at Frameworks/Electron Helper (GPU).app -reading code signature from main executable Mantle -sealing nested bundle at Frameworks/Mantle.framework writing sealed resources to Electron.app.signed/Contents/_CodeSignature/CodeResources signing main executable Contents/MacOS/Electron setting main executable binary identifier to com.github.Electron (derived from CFBundleIdentifier in Info.plist) diff --git a/apple-codesign/tests/cmd/sign-bundle-framework-shallow.trycmd b/apple-codesign/tests/cmd/sign-bundle-framework-shallow.trycmd index cb6681923..d8acee478 100644 --- a/apple-codesign/tests/cmd/sign-bundle-framework-shallow.trycmd +++ b/apple-codesign/tests/cmd/sign-bundle-framework-shallow.trycmd @@ -15,12 +15,11 @@ signing 0 nested bundles in the following order: signing bundle at Shallow.framework into Shallow.framework.signed found an unversioned framework; signing like normal collecting code resources files -Resources/Info.plist is the Info.plist file; handling specially sealing regular file Resources/Info.plist copying file Shallow.framework/Resources/Info.plist -> Shallow.framework.signed/Resources/Info.plist sealing regular file Resources/root-00.txt copying file Shallow.framework/Resources/root-00.txt -> Shallow.framework.signed/Resources/root-00.txt -sealing Mach-O file Shallow +sealing nested Mach-O binary: Shallow signing Mach-O file Shallow inferring default signing settings from Mach-O binary Mach-O is missing binary identifier; setting to Shallow based on file name diff --git a/apple-codesign/tests/cmd/sign-bundle-framework.trycmd b/apple-codesign/tests/cmd/sign-bundle-framework.trycmd index 0ba51a5d9..db6247302 100644 --- a/apple-codesign/tests/cmd/sign-bundle-framework.trycmd +++ b/apple-codesign/tests/cmd/sign-bundle-framework.trycmd @@ -20,7 +20,7 @@ entering nested bundle MyFramework.framework/Versions/A signing bundle at MyFramework.framework/Versions/A into MyFramework.framework.signed/Versions/A found an unversioned framework; signing like normal collecting code resources files -sealing Mach-O file MyFramework +sealing nested Mach-O binary: MyFramework signing Mach-O file MyFramework inferring default signing settings from Mach-O binary Mach-O is missing binary identifier; setting to MyFramework based on file name @@ -30,7 +30,6 @@ creating ad-hoc signature code directory version: 132096 total signature size: 380 bytes writing Mach-O to MyFramework.framework.signed/Versions/A/MyFramework -Resources/Info.plist is the Info.plist file; handling specially sealing regular file Resources/Info.plist copying file MyFramework.framework/Versions/A/Resources/Info.plist -> MyFramework.framework.signed/Versions/A/Resources/Info.plist sealing regular file Resources/root-A-00.txt diff --git a/apple-codesign/tests/cmd/sign-bundle-multiple-macho.trycmd b/apple-codesign/tests/cmd/sign-bundle-multiple-macho.trycmd index cd35a1768..6e2ccf03c 100644 --- a/apple-codesign/tests/cmd/sign-bundle-multiple-macho.trycmd +++ b/apple-codesign/tests/cmd/sign-bundle-multiple-macho.trycmd @@ -26,10 +26,9 @@ signing bundle at MyApp.app signing 0 nested bundles in the following order: signing bundle at MyApp.app into MyApp.app.signed collecting code resources files -Contents/Info.plist is the Info.plist file; handling specially copying file MyApp.app/Contents/Info.plist -> MyApp.app.signed/Contents/Info.plist -sealing Mach-O file MacOS/bin -signing Mach-O file MacOS/bin +sealing nested Mach-O binary: Contents/MacOS/bin +signing Mach-O file Contents/MacOS/bin inferring default signing settings from Mach-O binary Mach-O is missing binary identifier; setting to bin based on file name signing Mach-O binary at index 0 @@ -37,9 +36,9 @@ binary targets macOS >= 11.0.0 with SDK 11.0.0 creating ad-hoc signature code directory version: 132096 total signature size: 372 bytes -writing Mach-O to MyApp.app.signed/MacOS/bin -sealing Mach-O file MacOS/lib.dylib -signing Mach-O file MacOS/lib.dylib +writing Mach-O to MyApp.app.signed/Contents/MacOS/bin +sealing nested Mach-O binary: Contents/MacOS/lib.dylib +signing Mach-O file Contents/MacOS/lib.dylib inferring default signing settings from Mach-O binary Mach-O is missing binary identifier; setting to lib based on file name signing Mach-O binary at index 0 @@ -47,8 +46,9 @@ binary targets macOS >= 11.0.0 with SDK 11.0.0 creating ad-hoc signature code directory version: 132096 total signature size: 372 bytes -writing Mach-O to MyApp.app.signed/MacOS/lib.dylib +writing Mach-O to MyApp.app.signed/Contents/MacOS/lib.dylib sealing regular file Resources/non-nested-bin +non-nested file is a Mach-O binary; it is NOT signed automatically: Contents/Resources/non-nested-bin copying file MyApp.app/Contents/Resources/non-nested-bin -> MyApp.app.signed/Contents/Resources/non-nested-bin writing sealed resources to MyApp.app.signed/Contents/_CodeSignature/CodeResources signing main executable Contents/MacOS/MyApp @@ -110,6 +110,92 @@ $ rcodesign print-signature-info MyApp.app.signed - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' - 'Resources (3): 29ca54092b8a1ee42bd378889eae9382dd64f94f6ac99e093d5aff76af6ea2bf' cms: null +- path: Contents/MacOS/bin + file_size: 22544 + file_sha256: 222272e624fadf178495f7eeabdac248a951a0fb1e49002f494dde7067e456c8 + entity: + mach_o: + linkedit_segment_file_start_offset: 16384 + linkedit_segment_file_end_offset: 22544 + signature_file_start_offset: 16400 + signature_file_end_offset: 22544 + signature_linkedit_start_offset: 16 + signature_linkedit_end_offset: 6160 + signature: + superblob_length: 372 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 316 + sha1: c86136679b8fb8b73c260c3f5143eb4787ba7408 + sha256: 319e12d5056d6b83506f2a51858ddfd99a244ed7b1bb261d9f7a1befa55239db + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: bin + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null +- path: Contents/MacOS/lib.dylib + file_size: 22544 + file_sha256: f5bf39926f898f9d8b10749c2c2e02d89e6ca1ab85e5210df86a711afc35f1bd + entity: + mach_o: + linkedit_segment_file_start_offset: 16384 + linkedit_segment_file_end_offset: 22544 + signature_file_start_offset: 16400 + signature_file_end_offset: 22544 + signature_linkedit_start_offset: 16 + signature_linkedit_end_offset: 6160 + signature: + superblob_length: 372 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 316 + sha1: af401622e3c8ad117ef8e8048542a0f6ce3e0d7c + sha256: df488d463c798ba6e7afbb55d1f86959aefc12753467b49d5a984611e11ec8d0 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: lib + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(0x0) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null - path: Contents/Resources/non-nested-bin file_size: 16386 file_sha256: 4cfaf70bc9fb6827fcf7751deaf65f8b54d46fecb6f39cb2ba8fbcf36912430c @@ -274,91 +360,5 @@ $ rcodesign print-signature-info MyApp.app.signed - - - '' -- path: MacOS/bin - file_size: 22544 - file_sha256: 222272e624fadf178495f7eeabdac248a951a0fb1e49002f494dde7067e456c8 - entity: - mach_o: - linkedit_segment_file_start_offset: 16384 - linkedit_segment_file_end_offset: 22544 - signature_file_start_offset: 16400 - signature_file_end_offset: 22544 - signature_linkedit_start_offset: 16 - signature_linkedit_end_offset: 6160 - signature: - superblob_length: 372 - blob_count: 3 - blobs: - - slot: CodeDirectory (0) - magic: fade0c02 - length: 316 - sha1: c86136679b8fb8b73c260c3f5143eb4787ba7408 - sha256: 319e12d5056d6b83506f2a51858ddfd99a244ed7b1bb261d9f7a1befa55239db - - slot: RequirementSet (2) - magic: fade0c01 - length: 12 - sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 - sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 - - slot: CMS Signature (65536) - magic: fade0b01 - length: 8 - sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b - sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 - code_directory: - version: '0x20400' - flags: CodeSignatureFlags(ADHOC) - identifier: bin - digest_type: sha256 - platform: 0 - signed_entity_size: 16400 - executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) - code_digests_count: 5 - slot_digests: - - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' - - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' - cms: null -- path: MacOS/lib.dylib - file_size: 22544 - file_sha256: f5bf39926f898f9d8b10749c2c2e02d89e6ca1ab85e5210df86a711afc35f1bd - entity: - mach_o: - linkedit_segment_file_start_offset: 16384 - linkedit_segment_file_end_offset: 22544 - signature_file_start_offset: 16400 - signature_file_end_offset: 22544 - signature_linkedit_start_offset: 16 - signature_linkedit_end_offset: 6160 - signature: - superblob_length: 372 - blob_count: 3 - blobs: - - slot: CodeDirectory (0) - magic: fade0c02 - length: 316 - sha1: af401622e3c8ad117ef8e8048542a0f6ce3e0d7c - sha256: df488d463c798ba6e7afbb55d1f86959aefc12753467b49d5a984611e11ec8d0 - - slot: RequirementSet (2) - magic: fade0c01 - length: 12 - sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 - sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 - - slot: CMS Signature (65536) - magic: fade0b01 - length: 8 - sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b - sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 - code_directory: - version: '0x20400' - flags: CodeSignatureFlags(ADHOC) - identifier: lib - digest_type: sha256 - platform: 0 - signed_entity_size: 16400 - executable_segment_flags: ExecutableSegmentFlags(0x0) - code_digests_count: 5 - slot_digests: - - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' - - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' - cms: null ``` diff --git a/apple-codesign/tests/cmd/sign-bundle-with-nested-framework.trycmd b/apple-codesign/tests/cmd/sign-bundle-with-nested-framework.trycmd index ccf8496b8..6820611cc 100644 --- a/apple-codesign/tests/cmd/sign-bundle-with-nested-framework.trycmd +++ b/apple-codesign/tests/cmd/sign-bundle-with-nested-framework.trycmd @@ -53,7 +53,6 @@ Contents/Frameworks/Sparkle.framework entering nested bundle MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app signing bundle at MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app into MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app collecting code resources files -Contents/Info.plist is the Info.plist file; handling specially copying file MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Info.plist -> MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Info.plist sealing regular file Resources/en.lproj/Sparkle.strings copying file MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/en.lproj/Sparkle.strings -> MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/en.lproj/Sparkle.strings @@ -79,12 +78,12 @@ copying file MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Modules/ sealing regular file Resources/Autoupdate.app/Contents/Info.plist copying file MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Info.plist -> MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Info.plist sealing regular file Resources/Autoupdate.app/Contents/MacOS/Autoupdate +non-nested file is a Mach-O binary; it is NOT signed automatically: Resources/Autoupdate.app/Contents/MacOS/Autoupdate copying file MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/MacOS/Autoupdate -> MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/MacOS/Autoupdate sealing regular file Resources/Autoupdate.app/Contents/Resources/en.lproj/Sparkle.strings copying file MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/en.lproj/Sparkle.strings -> MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/en.lproj/Sparkle.strings sealing regular file Resources/DarkAqua.css copying file MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/DarkAqua.css -> MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/DarkAqua.css -Resources/Info.plist is the Info.plist file; handling specially sealing regular file Resources/Info.plist copying file MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Info.plist -> MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Info.plist sealing regular file Resources/en.lproj/Sparkle.strings @@ -112,12 +111,11 @@ replicating symlink MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versi leaving nested bundle MyApp.app/Contents/Frameworks/Sparkle.framework signing bundle at MyApp.app into MyApp.app.signed collecting code resources files -Contents/Info.plist is the Info.plist file; handling specially +sealing nested directory as a bundle: Contents/Frameworks/Sparkle.framework +reading Mach-O signature from MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Sparkle copying file MyApp.app/Contents/Info.plist -> MyApp.app.signed/Contents/Info.plist sealing regular file Resources/AppIcon.icns copying file MyApp.app/Contents/Resources/AppIcon.icns -> MyApp.app.signed/Contents/Resources/AppIcon.icns -reading code signature from main executable Sparkle -sealing nested bundle at Frameworks/Sparkle.framework writing sealed resources to MyApp.app.signed/Contents/_CodeSignature/CodeResources signing main executable Contents/MacOS/MyApp setting main executable binary identifier to com.example.mybundle (derived from CFBundleIdentifier in Info.plist) diff --git a/apple-codesign/tests/cmd/sign-bundle.trycmd b/apple-codesign/tests/cmd/sign-bundle.trycmd index b7aab6d91..4aa46d2ab 100644 --- a/apple-codesign/tests/cmd/sign-bundle.trycmd +++ b/apple-codesign/tests/cmd/sign-bundle.trycmd @@ -25,7 +25,6 @@ signing bundle at MyApp.app signing 0 nested bundles in the following order: signing bundle at MyApp.app into MyApp.app.signed collecting code resources files -Contents/Info.plist is the Info.plist file; handling specially copying file MyApp.app/Contents/Info.plist -> MyApp.app.signed/Contents/Info.plist sealing regular file Resources/file-00.txt copying file MyApp.app/Resources/file-00.txt -> MyApp.app.signed/Resources/file-00.txt @@ -264,7 +263,6 @@ signing 0 nested bundles in the following order: signing bundle at MyApp.app into MyApp.app.signed activating SHA-1 + SHA-256 signing due to requirements of main executable collecting code resources files -Contents/Info.plist is the Info.plist file; handling specially copying file MyApp.app/Contents/Info.plist -> MyApp.app.signed/Contents/Info.plist sealing regular file Resources/file-00.txt copying file MyApp.app/Resources/file-00.txt -> MyApp.app.signed/Resources/file-00.txt