Struct apple_codesign::CodeResourcesBuilder
source · pub struct CodeResourcesBuilder { /* private fields */ }
Expand description
Interface for constructing a CodeResources
instance.
This type is used during bundle signing to construct a CodeResources
instance.
It contains logic for validating a file against registered processing rules and
handling it accordingly.
Implementations§
source§impl CodeResourcesBuilder
impl CodeResourcesBuilder
sourcepub fn default_resources_rules() -> Result<Self, AppleCodesignError>
pub fn default_resources_rules() -> Result<Self, AppleCodesignError>
Obtain an instance with default rules for a bundle with a Resources/
directory.
Examples found in repository?
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
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)
}
sourcepub fn default_no_resources_rules() -> Result<Self, AppleCodesignError>
pub fn default_no_resources_rules() -> Result<Self, AppleCodesignError>
Obtain an instance with default rules for a bundle without a Resources/
directory.
Examples found in repository?
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
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)
}
sourcepub fn set_digests(&mut self, digests: impl Iterator<Item = DigestType>)
pub fn set_digests(&mut self, digests: impl Iterator<Item = DigestType>)
Set the digests to record in this instance.
Examples found in repository?
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
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)
}
sourcepub fn add_rule(&mut self, rule: CodeResourcesRule)
pub fn add_rule(&mut self, rule: CodeResourcesRule)
Add a rule to this instance in the <rules>
section.
Examples found in repository?
961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049
pub fn default_resources_rules() -> Result<Self, AppleCodesignError> {
let mut slf = Self::default();
slf.add_rule(CodeResourcesRule::new("^version.plist$")?);
slf.add_rule(CodeResourcesRule::new("^Resources/")?);
slf.add_rule(
CodeResourcesRule::new("^Resources/.*\\.lproj/")?
.optional()
.weight(1000),
);
slf.add_rule(CodeResourcesRule::new("^Resources/Base\\.lproj/")?.weight(1010));
slf.add_rule(
CodeResourcesRule::new("^Resources/.*\\.lproj/locversion.plist$")?
.omit()
.weight(1100),
);
slf.add_rule2(CodeResourcesRule::new("^.*")?);
slf.add_rule2(CodeResourcesRule::new("^[^/]+$")?.nested().weight(10));
slf.add_rule2(CodeResourcesRule::new("^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/")?
.nested().weight(10));
slf.add_rule2(CodeResourcesRule::new(".*\\.dSYM($|/)")?.weight(11));
slf.add_rule2(
CodeResourcesRule::new("^(.*/)?\\.DS_Store$")?
.omit()
.weight(2000),
);
slf.add_rule2(CodeResourcesRule::new("^Info\\.plist$")?.omit().weight(20));
slf.add_rule2(CodeResourcesRule::new("^version\\.plist$")?.weight(20));
slf.add_rule2(CodeResourcesRule::new("^embedded\\.provisionprofile$")?.weight(20));
slf.add_rule2(CodeResourcesRule::new("^PkgInfo$")?.omit().weight(20));
slf.add_rule2(CodeResourcesRule::new("^Resources/")?.weight(20));
slf.add_rule2(
CodeResourcesRule::new("^Resources/.*\\.lproj/")?
.optional()
.weight(1000),
);
slf.add_rule2(CodeResourcesRule::new("^Resources/Base\\.lproj/")?.weight(1010));
slf.add_rule2(
CodeResourcesRule::new("^Resources/.*\\.lproj/locversion.plist$")?
.omit()
.weight(1100),
);
Ok(slf)
}
/// Obtain an instance with default rules for a bundle without a `Resources/` directory.
pub fn default_no_resources_rules() -> Result<Self, AppleCodesignError> {
let mut slf = Self::default();
slf.add_rule(CodeResourcesRule::new("^version.plist$")?);
slf.add_rule(CodeResourcesRule::new("^.*")?);
slf.add_rule(
CodeResourcesRule::new("^.*\\.lproj")?
.optional()
.weight(1000),
);
slf.add_rule(CodeResourcesRule::new("^Base\\.lproj")?.weight(1010));
slf.add_rule(
CodeResourcesRule::new("^.*\\.lproj/locversion.plist$")?
.omit()
.weight(1100),
);
slf.add_rule2(CodeResourcesRule::new("^.*")?);
slf.add_rule2(CodeResourcesRule::new(".*\\.dSYM($|/)")?.weight(11));
slf.add_rule2(
CodeResourcesRule::new("^(.*/)?\\.DS_Store$")?
.omit()
.weight(2000),
);
slf.add_rule2(CodeResourcesRule::new("^Info\\.plist$")?.omit().weight(20));
slf.add_rule2(CodeResourcesRule::new("^version\\.plist$")?.weight(20));
slf.add_rule2(CodeResourcesRule::new("^embedded\\.provisionprofile$")?.weight(20));
slf.add_rule2(CodeResourcesRule::new("^PkgInfo$")?.omit().weight(20));
slf.add_rule2(
CodeResourcesRule::new("^.*\\.lproj/")?
.optional()
.weight(1000),
);
slf.add_rule2(CodeResourcesRule::new("^Base\\.lproj")?.weight(1010));
slf.add_rule2(
CodeResourcesRule::new("^.*\\.lproj/locversion.plist$")?
.omit()
.weight(1100),
);
Ok(slf)
}
sourcepub fn add_rule2(&mut self, rule: CodeResourcesRule)
pub fn add_rule2(&mut self, rule: CodeResourcesRule)
Add a rule to this instance in the <rules2>
section.
Examples found in repository?
961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049
pub fn default_resources_rules() -> Result<Self, AppleCodesignError> {
let mut slf = Self::default();
slf.add_rule(CodeResourcesRule::new("^version.plist$")?);
slf.add_rule(CodeResourcesRule::new("^Resources/")?);
slf.add_rule(
CodeResourcesRule::new("^Resources/.*\\.lproj/")?
.optional()
.weight(1000),
);
slf.add_rule(CodeResourcesRule::new("^Resources/Base\\.lproj/")?.weight(1010));
slf.add_rule(
CodeResourcesRule::new("^Resources/.*\\.lproj/locversion.plist$")?
.omit()
.weight(1100),
);
slf.add_rule2(CodeResourcesRule::new("^.*")?);
slf.add_rule2(CodeResourcesRule::new("^[^/]+$")?.nested().weight(10));
slf.add_rule2(CodeResourcesRule::new("^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/")?
.nested().weight(10));
slf.add_rule2(CodeResourcesRule::new(".*\\.dSYM($|/)")?.weight(11));
slf.add_rule2(
CodeResourcesRule::new("^(.*/)?\\.DS_Store$")?
.omit()
.weight(2000),
);
slf.add_rule2(CodeResourcesRule::new("^Info\\.plist$")?.omit().weight(20));
slf.add_rule2(CodeResourcesRule::new("^version\\.plist$")?.weight(20));
slf.add_rule2(CodeResourcesRule::new("^embedded\\.provisionprofile$")?.weight(20));
slf.add_rule2(CodeResourcesRule::new("^PkgInfo$")?.omit().weight(20));
slf.add_rule2(CodeResourcesRule::new("^Resources/")?.weight(20));
slf.add_rule2(
CodeResourcesRule::new("^Resources/.*\\.lproj/")?
.optional()
.weight(1000),
);
slf.add_rule2(CodeResourcesRule::new("^Resources/Base\\.lproj/")?.weight(1010));
slf.add_rule2(
CodeResourcesRule::new("^Resources/.*\\.lproj/locversion.plist$")?
.omit()
.weight(1100),
);
Ok(slf)
}
/// Obtain an instance with default rules for a bundle without a `Resources/` directory.
pub fn default_no_resources_rules() -> Result<Self, AppleCodesignError> {
let mut slf = Self::default();
slf.add_rule(CodeResourcesRule::new("^version.plist$")?);
slf.add_rule(CodeResourcesRule::new("^.*")?);
slf.add_rule(
CodeResourcesRule::new("^.*\\.lproj")?
.optional()
.weight(1000),
);
slf.add_rule(CodeResourcesRule::new("^Base\\.lproj")?.weight(1010));
slf.add_rule(
CodeResourcesRule::new("^.*\\.lproj/locversion.plist$")?
.omit()
.weight(1100),
);
slf.add_rule2(CodeResourcesRule::new("^.*")?);
slf.add_rule2(CodeResourcesRule::new(".*\\.dSYM($|/)")?.weight(11));
slf.add_rule2(
CodeResourcesRule::new("^(.*/)?\\.DS_Store$")?
.omit()
.weight(2000),
);
slf.add_rule2(CodeResourcesRule::new("^Info\\.plist$")?.omit().weight(20));
slf.add_rule2(CodeResourcesRule::new("^version\\.plist$")?.weight(20));
slf.add_rule2(CodeResourcesRule::new("^embedded\\.provisionprofile$")?.weight(20));
slf.add_rule2(CodeResourcesRule::new("^PkgInfo$")?.omit().weight(20));
slf.add_rule2(
CodeResourcesRule::new("^.*\\.lproj/")?
.optional()
.weight(1000),
);
slf.add_rule2(CodeResourcesRule::new("^Base\\.lproj")?.weight(1010));
slf.add_rule2(
CodeResourcesRule::new("^.*\\.lproj/locversion.plist$")?
.omit()
.weight(1100),
);
Ok(slf)
}
sourcepub fn add_exclusion_rule(&mut self, rule: CodeResourcesRule)
pub fn add_exclusion_rule(&mut self, rule: CodeResourcesRule)
Add an exclusion rule to the processing rules.
Exclusion rules are not added to the CodeResources because they are for building only.
Examples found in repository?
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
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)
}
sourcepub fn process_file(
&mut self,
file: &DirectoryBundleFile<'_>,
file_handler: &dyn BundleFileHandler
) -> Result<(), AppleCodesignError>
pub fn process_file(
&mut self,
file: &DirectoryBundleFile<'_>,
file_handler: &dyn BundleFileHandler
) -> Result<(), AppleCodesignError>
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.
Examples found in repository?
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
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)
}
sourcepub fn process_nested_bundle(
&mut self,
relative_path: &str,
bundle: &DirectoryBundle
) -> Result<(), AppleCodesignError>
pub fn process_nested_bundle(
&mut self,
relative_path: &str,
bundle: &DirectoryBundle
) -> Result<(), AppleCodesignError>
Process a nested bundle for inclusion in resource handling.
This will attempt to seal the main digest of the bundle into this resources file.
Examples found in repository?
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
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)
}
sourcepub fn write_code_resources(
&self,
writer: impl Write
) -> Result<(), AppleCodesignError>
pub fn write_code_resources(
&self,
writer: impl Write
) -> Result<(), AppleCodesignError>
Write CodeResources XML content to a writer.
Examples found in repository?
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
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)
}
Trait Implementations§
source§impl Clone for CodeResourcesBuilder
impl Clone for CodeResourcesBuilder
source§fn clone(&self) -> CodeResourcesBuilder
fn clone(&self) -> CodeResourcesBuilder
1.0.0 · source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source
. Read moresource§impl Debug for CodeResourcesBuilder
impl Debug for CodeResourcesBuilder
Auto Trait Implementations§
impl RefUnwindSafe for CodeResourcesBuilder
impl Send for CodeResourcesBuilder
impl Sync for CodeResourcesBuilder
impl Unpin for CodeResourcesBuilder
impl UnwindSafe for CodeResourcesBuilder
Blanket Implementations§
§impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
§impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
§impl<T> Conv for T
impl<T> Conv for T
§impl<T> FmtForward for T
impl<T> FmtForward for T
§fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
self
to use its Binary
implementation when Debug
-formatted.§fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
self
to use its Display
implementation when
Debug
-formatted.§fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
self
to use its LowerExp
implementation when
Debug
-formatted.§fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
self
to use its LowerHex
implementation when
Debug
-formatted.§fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
self
to use its Octal
implementation when Debug
-formatted.§fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
self
to use its Pointer
implementation when
Debug
-formatted.§fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
self
to use its UpperExp
implementation when
Debug
-formatted.§fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
self
to use its UpperHex
implementation when
Debug
-formatted.§fn fmt_list(self) -> FmtList<Self>where
&'a Self: for<'a> IntoIterator,
fn fmt_list(self) -> FmtList<Self>where
&'a Self: for<'a> IntoIterator,
source§impl<T> Instrument for T
impl<T> Instrument for T
source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
§impl<T> Pipe for Twhere
T: ?Sized,
impl<T> Pipe for Twhere
T: ?Sized,
§fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
§fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
self
and passes that borrow into the pipe function. Read more§fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
self
and passes that borrow into the pipe function. Read more§fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> Rwhere
Self: Borrow<B>,
B: 'a + ?Sized,
R: 'a,
fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> Rwhere
Self: Borrow<B>,
B: 'a + ?Sized,
R: 'a,
§fn pipe_borrow_mut<'a, B, R>(
&'a mut self,
func: impl FnOnce(&'a mut B) -> R
) -> Rwhere
Self: BorrowMut<B>,
B: 'a + ?Sized,
R: 'a,
fn pipe_borrow_mut<'a, B, R>(
&'a mut self,
func: impl FnOnce(&'a mut B) -> R
) -> Rwhere
Self: BorrowMut<B>,
B: 'a + ?Sized,
R: 'a,
§fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> Rwhere
Self: AsRef<U>,
U: 'a + ?Sized,
R: 'a,
fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> Rwhere
Self: AsRef<U>,
U: 'a + ?Sized,
R: 'a,
self
, then passes self.as_ref()
into the pipe function.§fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> Rwhere
Self: AsMut<U>,
U: 'a + ?Sized,
R: 'a,
fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> Rwhere
Self: AsMut<U>,
U: 'a + ?Sized,
R: 'a,
self
, then passes self.as_mut()
into the pipe
function.§impl<T> Pointable for T
impl<T> Pointable for T
§impl<T> Tap for T
impl<T> Tap for T
§fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Selfwhere
Self: Borrow<B>,
B: ?Sized,
fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Selfwhere
Self: Borrow<B>,
B: ?Sized,
Borrow<B>
of a value. Read more§fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Selfwhere
Self: BorrowMut<B>,
B: ?Sized,
fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Selfwhere
Self: BorrowMut<B>,
B: ?Sized,
BorrowMut<B>
of a value. Read more§fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Selfwhere
Self: AsRef<R>,
R: ?Sized,
fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Selfwhere
Self: AsRef<R>,
R: ?Sized,
AsRef<R>
view of a value. Read more§fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Selfwhere
Self: AsMut<R>,
R: ?Sized,
fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Selfwhere
Self: AsMut<R>,
R: ?Sized,
AsMut<R>
view of a value. Read more§fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Selfwhere
Self: Deref<Target = T>,
T: ?Sized,
fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Selfwhere
Self: Deref<Target = T>,
T: ?Sized,
Deref::Target
of a value. Read more§fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Selfwhere
Self: DerefMut<Target = T> + Deref,
T: ?Sized,
fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Selfwhere
Self: DerefMut<Target = T> + Deref,
T: ?Sized,
Deref::Target
of a value. Read more§fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
.tap()
only in debug builds, and is erased in release builds.§fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
.tap_mut()
only in debug builds, and is erased in release
builds.§fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Selfwhere
Self: Borrow<B>,
B: ?Sized,
fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Selfwhere
Self: Borrow<B>,
B: ?Sized,
.tap_borrow()
only in debug builds, and is erased in release
builds.§fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Selfwhere
Self: BorrowMut<B>,
B: ?Sized,
fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Selfwhere
Self: BorrowMut<B>,
B: ?Sized,
.tap_borrow_mut()
only in debug builds, and is erased in release
builds.§fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Selfwhere
Self: AsRef<R>,
R: ?Sized,
fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Selfwhere
Self: AsRef<R>,
R: ?Sized,
.tap_ref()
only in debug builds, and is erased in release
builds.§fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Selfwhere
Self: AsMut<R>,
R: ?Sized,
fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Selfwhere
Self: AsMut<R>,
R: ?Sized,
.tap_ref_mut()
only in debug builds, and is erased in release
builds.