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/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