Function apple_codesign::write_macho_file
source · pub fn write_macho_file(
input_path: &Path,
output_path: &Path,
macho_data: &[u8]
) -> Result<(), AppleCodesignError>
Expand description
Write Mach-O file content to an output file.
Examples found in repository?
src/signing.rs (line 95)
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
pub fn sign_macho(
&self,
input_path: impl AsRef<Path>,
output_path: impl AsRef<Path>,
) -> Result<(), AppleCodesignError> {
let input_path = input_path.as_ref();
let output_path = output_path.as_ref();
warn!("signing {} as a Mach-O binary", input_path.display());
let macho_data = std::fs::read(input_path)?;
let mut settings = self.settings.clone();
settings.import_settings_from_macho(&macho_data)?;
if settings.binary_identifier(SettingsScope::Main).is_none() {
let identifier = input_path
.file_name()
.ok_or_else(|| {
AppleCodesignError::CliGeneralError(
"unable to resolve file name of binary".into(),
)
})?
.to_string_lossy();
warn!("setting binary identifier to {}", identifier);
settings.set_binary_identifier(SettingsScope::Main, identifier);
}
warn!("parsing Mach-O");
let signer = MachOSigner::new(&macho_data)?;
let mut macho_data = vec![];
signer.write_signed_binary(&settings, &mut macho_data)?;
warn!("writing Mach-O to {}", output_path.display());
write_macho_file(input_path, output_path, &macho_data)?;
Ok(())
}
More examples
src/bundle_signing.rs (line 372)
330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633
fn sign_and_install_macho(
&self,
file: &DirectoryBundleFile,
) -> Result<SignedMachOInfo, AppleCodesignError> {
info!("signing Mach-O file {}", file.relative_path().display());
let macho_data = std::fs::read(file.absolute_path())?;
let signer = MachOSigner::new(&macho_data)?;
let mut settings = self
.settings
.as_bundle_macho_settings(file.relative_path().to_string_lossy().as_ref());
settings.import_settings_from_macho(&macho_data)?;
// If there isn't a defined binary identifier, derive one from the file name so one is set
// and we avoid a signing error due to missing identifier.
// TODO do we need to check the nested Mach-O settings?
if settings.binary_identifier(SettingsScope::Main).is_none() {
let identifier = file
.relative_path()
.file_name()
.expect("failure to extract filename (this should never happen)")
.to_string_lossy();
let identifier = identifier
.strip_suffix(".dylib")
.unwrap_or_else(|| identifier.as_ref());
info!(
"Mach-O is missing binary identifier; setting to {} based on file name",
identifier
);
settings.set_binary_identifier(SettingsScope::Main, identifier);
}
let mut new_data = Vec::<u8>::with_capacity(macho_data.len() + 2_usize.pow(17));
signer.write_signed_binary(&settings, &mut new_data)?;
let dest_path = self.dest_dir.join(file.relative_path());
info!("writing Mach-O to {}", dest_path.display());
write_macho_file(file.absolute_path(), &dest_path, &new_data)?;
SignedMachOInfo::parse_data(&new_data)
}
}
/// A primitive for signing a single Apple bundle.
///
/// Unlike [BundleSigner], this type only signs a single bundle and is ignorant
/// about nested bundles. You probably want to use [BundleSigner] as the interface
/// for signing bundles, as failure to account for nested bundles can result in
/// signature verification errors.
pub struct SingleBundleSigner {
/// The bundle being signed.
bundle: DirectoryBundle,
}
impl SingleBundleSigner {
/// Construct a new instance.
pub fn new(bundle: DirectoryBundle) -> Self {
Self { bundle }
}
/// Write a signed bundle to the given directory.
pub fn write_signed_bundle(
&self,
dest_dir: impl AsRef<Path>,
settings: &SigningSettings,
) -> Result<DirectoryBundle, AppleCodesignError> {
let dest_dir = dest_dir.as_ref();
warn!(
"signing bundle at {} into {}",
self.bundle.root_dir().display(),
dest_dir.display()
);
// Frameworks are a bit special.
//
// Modern frameworks typically have a `Versions/` directory containing directories
// with the actual frameworks. These are the actual directories that are signed - not
// the top-most directory. In fact, the top-most `.framework` directory doesn't have any
// code signature elements at all and can effectively be ignored as far as signing
// is concerned.
//
// But even if there is a `Versions/` directory with nested bundles to sign, the top-level
// directory may have some symlinks. And those need to be preserved. In addition, there
// may be symlinks in `Versions/`. `Versions/Current` is common.
//
// Of course, if there is no `Versions/` directory, the top-level directory could be
// a valid framework warranting signing.
if self.bundle.package_type() == BundlePackageType::Framework {
if self.bundle.root_dir().join("Versions").is_dir() {
warn!("found a versioned framework; each version will be signed as its own bundle");
// But we still need to preserve files (hopefully just symlinks) outside the
// nested bundles under `Versions/`. Since we don't nest into child bundles
// here, it should be safe to handle each encountered file.
let handler = SingleBundleHandler {
dest_dir: dest_dir.to_path_buf(),
settings,
};
for file in self
.bundle
.files(false)
.map_err(AppleCodesignError::DirectoryBundle)?
{
handler.install_file(&file)?;
}
return DirectoryBundle::new_from_path(dest_dir)
.map_err(AppleCodesignError::DirectoryBundle);
} else {
warn!("found an unversioned framework; signing like normal");
}
}
let dest_dir_root = dest_dir.to_path_buf();
let dest_dir = if self.bundle.shallow() {
dest_dir_root.clone()
} else {
dest_dir.join("Contents")
};
self.bundle
.identifier()
.map_err(AppleCodesignError::DirectoryBundle)?
.ok_or_else(|| AppleCodesignError::BundleNoIdentifier(self.bundle.info_plist_path()))?;
let mut resources_digests = settings.all_digests(SettingsScope::Main);
// State in the main executable can influence signing settings of the bundle. So examine
// it first.
let main_exe = self
.bundle
.files(false)
.map_err(AppleCodesignError::DirectoryBundle)?
.into_iter()
.find(|f| matches!(f.is_main_executable(), Ok(true)));
if let Some(exe) = &main_exe {
let macho_data = std::fs::read(exe.absolute_path())?;
let mach = MachFile::parse(&macho_data)?;
for macho in mach.iter_macho() {
if let Some(targeting) = macho.find_targeting()? {
let sha256_version = targeting.platform.sha256_digest_support()?;
if !sha256_version.matches(&targeting.minimum_os_version)
&& resources_digests != vec![DigestType::Sha1, DigestType::Sha256]
{
info!("main executable targets OS requiring SHA-1 signatures; activating SHA-1 + SHA-256 signing");
resources_digests = vec![DigestType::Sha1, DigestType::Sha256];
break;
}
}
}
}
warn!("collecting code resources files");
// The set of rules to use is determined by whether the bundle *can* have a
// `Resources/`, not whether it necessarily does. The exact rules for this are not
// known. Essentially we want to test for the result of CFBundleCopyResourcesDirectoryURL().
// We assume that we can use the resources rules when there is a `Resources` directory
// (this seems obvious!) or when the bundle isn't shallow, as a non-shallow bundle should
// be an app bundle and app bundles can always have resources (we think).
let mut resources_builder =
if self.bundle.resolve_path("Resources").is_dir() || !self.bundle.shallow() {
CodeResourcesBuilder::default_resources_rules()?
} else {
CodeResourcesBuilder::default_no_resources_rules()?
};
// Ensure emitted digests match what we're configured to emit.
resources_builder.set_digests(resources_digests.into_iter());
// Exclude code signature files we'll write.
resources_builder.add_exclusion_rule(CodeResourcesRule::new("^_CodeSignature/")?.exclude());
// Ignore notarization ticket.
resources_builder.add_exclusion_rule(CodeResourcesRule::new("^CodeResources$")?.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)?;
}
}
// 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)?;
}
}
// The resources are now sealed. Write out that XML file.
let code_resources_path = dest_dir.join("_CodeSignature").join("CodeResources");
warn!(
"writing sealed resources to {}",
code_resources_path.display()
);
std::fs::create_dir_all(code_resources_path.parent().unwrap())?;
let mut resources_data = Vec::<u8>::new();
resources_builder.write_code_resources(&mut resources_data)?;
{
let mut fh = std::fs::File::create(&code_resources_path)?;
fh.write_all(&resources_data)?;
}
// Seal the main executable.
if let Some(exe) = main_exe {
warn!("signing main executable {}", exe.relative_path().display());
let macho_data = std::fs::read(exe.absolute_path())?;
let signer = MachOSigner::new(&macho_data)?;
let mut settings = settings.clone();
// The identifier for the main executable is defined in the bundle's Info.plist.
if let Some(ident) = self
.bundle
.identifier()
.map_err(AppleCodesignError::DirectoryBundle)?
{
info!("setting main executable binary identifier to {} (derived from CFBundleIdentifier in Info.plist)", ident);
settings.set_binary_identifier(SettingsScope::Main, ident);
} else {
info!("unable to determine binary identifier from bundle's Info.plist (CFBundleIdentifier not set?)");
}
settings.import_settings_from_macho(&macho_data)?;
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);
}
let mut new_data = Vec::<u8>::with_capacity(macho_data.len() + 2_usize.pow(17));
signer.write_signed_binary(&settings, &mut new_data)?;
let dest_path = dest_dir_root.join(exe.relative_path());
info!("writing signed main executable to {}", dest_path.display());
write_macho_file(exe.absolute_path(), &dest_path, &new_data)?;
} else {
warn!("bundle has no main executable to sign specially");
}
DirectoryBundle::new_from_path(&dest_dir_root).map_err(AppleCodesignError::DirectoryBundle)
}