diff --git a/.cargo/config.toml b/.cargo/config.toml index ebbaa05d..ab656177 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,6 +3,3 @@ rustflags = ["-C", "link-arg=-mmacosx-version-min=10.13"] [target.aarch64-apple-darwin] rustflags = ["-C", "link-arg=-mmacosx-version-min=10.13"] - -[build] -rustc-wrapper = "./.cargo/rustc-wrapper.sh" diff --git a/.cargo/rustc-wrapper.sh b/.cargo/rustc-wrapper.sh deleted file mode 100755 index a6265ada..00000000 --- a/.cargo/rustc-wrapper.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/bash - -# Redirect all output to log file except for the final exec'd command -LOG_FILE="/tmp/patch-log.txt" -exec 3>&1 4>&2 -exec >>"$LOG_FILE" 2>&1 - -set -eu - -# --- Constants and Paths --- -SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/../" && pwd) -PATCHES_DIR="$SCRIPT_DIR/patches" -TARGET_PATCHED_DIR="$SCRIPT_DIR/target/patched-crates" - -# --- No patching needed - run original args --- -if [ -z "${CARGO_PKG_NAME:-}" ] || [ -z "${CARGO_MANIFEST_DIR:-}" ]; then - exec 1>&3 2>&4 - exec "$@" -fi - -ORIGINAL_DIR_NAME=$(basename "$CARGO_MANIFEST_DIR") -PATCH_DIR="$PATCHES_DIR/$ORIGINAL_DIR_NAME" - -# --- Check for matching patch directory --- -if [ -d "$PATCH_DIR" ]; then - PATCHED_SRC="$TARGET_PATCHED_DIR/$ORIGINAL_DIR_NAME" - - echo "Applying patches to $CARGO_PKG_NAME..." - - mkdir -p "$TARGET_PATCHED_DIR" - rm -rf -- "$PATCHED_SRC" - cp -RL -- "$CARGO_MANIFEST_DIR" "$PATCHED_SRC" - - for PATCH_FILE in "$PATCH_DIR"/*; do - [ -f "$PATCH_FILE" ] || continue - if [ -x "$PATCH_FILE" ]; then - echo "Executing: $PATCH_FILE" - (cd "$PATCHED_SRC" && "$PATCH_FILE") - elif [ "${PATCH_FILE##*.}" = "patch" ]; then - echo "Applying patch: $PATCH_FILE" - patch -s -p1 -d "$PATCHED_SRC" < "$PATCH_FILE" - else - echo "Not executable nor patch file: $PATCH_FILE" - fi - done - - new_args=() - for arg in "$@"; do - new_args+=("${arg//$CARGO_MANIFEST_DIR/$PATCHED_SRC}") - done - - exec 1>&3 2>&4 - exec "${new_args[@]}" -else - exec 1>&3 2>&4 - exec "$@" -fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index abcb28fe..45c987ad 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -66,14 +66,23 @@ jobs: sudo apt-get update sudo apt-get install -y mingw-w64 nasm - - name: Build - shell: bash + - name: Build binaries (Linux cross-compile) + if: matrix.arch != 'arm64' run: | cargo build --bins --workspace --release --target ${{ matrix.target }} mkdir -p dist cp target/${{ matrix.target }}/release/plumeimpactor.exe dist/Impactor-windows-${{ matrix.arch }}-portable.exe cp target/${{ matrix.target }}/release/plumesign.exe dist/plumesign-windows-${{ matrix.arch }}.exe + - name: Build binaries (Windows ARM64 native) + if: matrix.arch == 'arm64' + shell: pwsh + run: | + cargo build --bins --workspace --release --target ${{ matrix.target }} + New-Item -ItemType Directory -Force -Path dist + Copy-Item "target/${{ matrix.target }}/release/plumeimpactor.exe" "dist/Impactor-windows-${{ matrix.arch }}-portable.exe" + Copy-Item "target/${{ matrix.target }}/release/plumesign.exe" "dist/plumesign-windows-${{ matrix.arch }}.exe" + - name: Upload Bundles uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: diff --git a/.gitignore b/.gitignore index f3fc7f29..f0916ac5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ /shared-modules /.direnv +*.exe *.DS_Store state.plist .zsign_cache diff --git a/3rdparty/apple-codesign-0.29.0/.cargo-ok b/3rdparty/apple-codesign-0.29.0/.cargo-ok new file mode 100644 index 00000000..5f8b7958 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/3rdparty/apple-codesign-0.29.0/.cargo_vcs_info.json b/3rdparty/apple-codesign-0.29.0/.cargo_vcs_info.json new file mode 100644 index 00000000..01953a83 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "2312b1e5bd77322c019613e0ca624e222c6c6e08" + }, + "path_in_vcs": "apple-codesign" +} \ No newline at end of file diff --git a/3rdparty/apple-codesign-0.29.0/CHANGELOG.md b/3rdparty/apple-codesign-0.29.0/CHANGELOG.md new file mode 100644 index 00000000..6e00b571 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/CHANGELOG.md @@ -0,0 +1,765 @@ +# `apple-codesign` History + + + +## Unreleased + +Released on ReleaseDate. + +## 0.29.0 + +Released on 2024-11-29. + +* When signing a bundle in `--shallow` mode, we no longer sign Mach-O binaries + that aren't the *main* bundle binary. The new behavior is compatible with the + behavior of Apple's `codesign`. (#148) +* Fixed a bug where signing of a bundle containing child bundles could sign and + install certain files multiple times. This could result in a child bundle having + an incorrect signature. (#149) +* MSRV 1.78 -> 1.81. +* `aws-sdk-s3` 1.24 -> 1.59. +* `clap` 4.4 -> 4.5. +* `minicbor` 0.24 -> 0.25. +* `thiserror` 1.0 -> 2.0. + +## 0.28.0 + +Released on 2024-11-03. + +* Fixed `env_logger` construction so `RUST_LOG` environment variable is + respected. (#162) +* MSRV 1.70 -> 1.78. +* Improve logging of S3 upload failures. We should now hopefully print something + more useful than `s3 upload error: unhandled error` on failures. +* `Info.plist` path handling should be more robust. This should fix errors + like `I/O error: No such file or directory` when signing Frameworks. (#163) +* Enabled `http2` feature of `reqwest` crate. This may provide better HTTP/2.0 + compatibility. +* `aws-config` 1.1 -> 1.5. +* `aws-sdk-s3` 1.12 -> 1.24. +* `aws-smithy-types` 1.1 -> 1.2. +* `base64` 0.21 -> 0.22. +* `bitflags` 2.4 -> 2.6. +* `bytes` 1.5 -> 1.8. +* `cryptographic-message-syntax` 0.26 -> 0.27. +* `env_logger` 0.10 -> 0.11. +* `goblin` 0.8 -> 0.9. +* `minicbor` 0.20 -> 0.24. +* `object` 0.32 -> 0.36. +* `oid-registry` 0.6 -> 0.7. +* `once_cell` 1.19 -> 1.20. +* `plist` 1.6 -> 1.7. +* `rasn` 0.12 -> 0.20. +* `rayon` 1.8 -> 1.10. +* `regex` 1.10 -> 1.11. +* `reqwest` 0.11 -> 0.12. +* `security-framework` 2.9 -> 2.11. +* `subtle` 2.5 -> 2.6. +* `tempfile` 3.9 -> 3.13. +* `tokio` 1.35 -> 1.41. +* `tungstenite` 0.21 -> 0.24. +* `uuid` 1.6 -> 1.11. +* `walkdir` 2.4 -> 2.5. +* `widestring` 1.0 -> 1.1. +* `x509-certificate` 0.23 -> 0.24. +* `zeroize` 1.7 -> 1.8. +* `zip` 0.6 -> 2.2. + +## 0.27.0 + +Released on 2024-01-17. + +* Published a + [GitHub Action for code signing and notarization](https://github.com/marketplace/actions/apple-code-signing) + and wrote project documentation for how to use it. (#6) +* Fix to restore working builds with `--no-default-features`. +* Added `notary-list` command to print information about recently submitted + notarizations to Apple. (#124) +* Fixed a bug where `.dSYM/` directories were incorrectly signed as + bundles. (#128) +* The `sign` command has gained a `--shallow` argument to prevent traversing into + nested entities when signing. It currently only prevents traversal into nested + bundles. In the future, behavior may be expanded to also exclude signing of + additional Mach-O binaries inside bundles, among other potential changes. + Ultimately we want this signing mode to converge with the default behavior of + Apple's tooling. +* The `sign` command has gained a `--for-notarization` argument that attempts to + engage and enforce signing settings required for Apple notarization. The goal + of the feature is to cut down on notarization failures after successful + signing operations. If you encounter a notarization failure when using this + new flag, consider filing a bug report. +* (API) `BundleSigner` now requires calling `collect_nested_bundles()` to register + child bundles for signing instead of signing all nested bundles by default. +* aws-config 0.57 -> 1.1. +* aws-sdk-s3 0.36 -> 1.10. +* aws-smithy-http 0.57 -> 0.60. +* aws-smithy-types 0.57 -> 1.1. +* goblin 0.7 -> 0.8. +* scroll 0.11 -> 0.12. +* tungstenite 0.20 -> 0.21. +* windows-sys 0.48 -> 0.52. + +## 0.26.0 + +Released on 2023-11-17. + +* (New feature) On Windows, it is now possible to sign with code signing + certificates stored in the Windows Certificate Store. The `sign` command + (and other commands taking certificate sources) gained `--windows-store-name` + and `--windows-store-sha1-fingerprint` arguments to specify a certificate in + the Windows Certificate Store to use. New commands + `windows-store-print-certificates` and + `windows-store-export-certificate-chain` can discover and export certificates + in the Windows Certificate Store. Feature contributed by El Mostafa Idrassi + in #111. +* Fixed a bug where a `signing without an Apple signed certificate but signing + settings contain a team name` warning was printed incorrectly. +* We now print a warning when signing using an expired certificate. +* Fixed a bug where `sign --code-signature-flags` could not be scoped. (#116) + +## 0.25.1 + +Released on 2023-11-16. + +* (Breaking change) The `sign --remote-signer` argument has been removed. It + is now implicitly assumed via presence of a remote session initialization + argument. +* Fixed a regression in 0.25.0 where remote signing didn't work due argument + parsing errors. + +## 0.25.0 + +Released on 2023-11-15. + +(Binary assets for this release were never formally published due to a +regression in remote signing CLI argument handling.) + +* (Breaking change) The `--extra-digest` argument has been removed. + `--digest` can now be specified multiple times. `--digest` is now a + scoped value. +* (Breaking change) Various signing settings no longer inherit to nested + entities: `--entitlements-xml-file`, `--code-requirements-file`, + `--code-resources-file`, `--code-signature-flags`, and `--info-plist-file`. + The new behavior is much more conservative about which signing settings + can be inherited and prevents unexpected results, such as all binaries + in a bundle sharing the same entitlements or signing flags. Previous signers + of bundles may find various signing settings disappearing from nested + bundles or the non-main Mach-O binary within a bundle. It is highly encouraged + to use the `rcodesign diff-signatures` command to compare results. If settings + were dropped, add new scoped CLI arguments or use the new configuration + file feature to add settings back in to specific paths. +* (New feature) Configuration file support added. TOML based configuration + files can now define signers and signing settings in named *profiles*, + allowing for automatic and near effortless reuse of common configurations. + See the documentation for more. +* (New feature) Environment constraints support. We now support defining launch + constraints and library constraints. We don't yet fully understand the + interactions of constraints and code signing. If using constraints, we + highly recommend comparing signature output with Apple's tooling to validate + similar behavior. If you notice discrepancies, please file a GitHub issue! + (#83) +* Detection of nested bundles now looks for `CFBundlePackageType` or + `CFBundleIdentifier` in bundle `Info.plist` and ignores *bundles* + lacking these. As a result, we no longer attempt signing of storybook + *bundles* and other non-signable bundle-looking directories and no + longer likely encounter errors in the process. (#38) +* CLI arguments for paths are now consistently named `--foo-file` + instead of using a mix of `--foo-path`, `--foo-filename`, and + potentially other variants. The old names are still recognized as + aliases to maintain backwards compatibility. +* Changed heuristic for naming a binary identifier from its path to be + more similar to Apple's. e.g. `foo1.2.dylib` will now resolve to `foo1` + instead of `foo1.2`. We still don't use the binary UUID or digest of its + load commands to compute the binary identifier like Apple does. +* When signing nested Mach-O binaries in a bundle, we now set the binary + identifier from the filename rather than preserving the identifier in an + existing signature. This helps ensure identifiers stay in sync and prevents + bad signatures. (#109) +* `print-signature-info` now prints the entitlements plist decoded from DER. + (#75) +* We no longer obtain placeholder time-stamp tokens when estimating the size + of embedded signatures. Instead, we statically reserve 8192 bytes for the + token. This may cause signatures to increase in size by a few kilobytes, + as Apple's TSTs are ~4200 bytes. Signing should now be faster since we avoid + an excessive network roundtrip. (#4) + +## 0.24.0 + +Released on 2023-11-09. + +* Add a `macho-universal-create` command to assemble single-arch Mach-O + binaries into a single multi-arch / universal / fat binary. The command + can be used as a replacement for Apple's `lipo -create`. +* When signing bundles, the `CodeResources` file for nested Mach-O binaries + now emits the code directory hashes for every code directory. Before, if + a Mach-O contained both SHA-1 and SHA-256 code directories, only the + SHA-256 hash would be emitted. The new behavior matches Apple's tooling. + (#95) +* The `generate-self-signed-certificate` command has gained the `--p12-file` + and `--p12-password` arguments to write a self-signed certificate to a + PKCS#12 / p12 / PFX file. +* The `generate-self-signed-certificate` command now supports generating + RSA certificates. RSA certificates are now the default, to match what + Apple uses by default. +* Reworked how code requirements expressions are automatically derived. + This should result in self-signed certificates having correct requirements + expressions that no longer imply they were signed by Apple's CAs. In + addition, some Apple signing certificates should now opt into using a + more appropriate code requirements expression than before. This may have + fixed validation errors with some signatures. (#99) +* Team name is no longer included in signature when signing with a non + Apple signed certificate. This matches the behavior of Apple's tools. (#101) +* Fixed a bug where the `AnchorCertificateHash` code requirements expression + was being incorrectly formatted as `anchor H""` instead of + `certificate = H""`. +* Added awareness of new Apple CA certificates: + `Apple Application Integration CA 7 - G1 Certificate`, + `Worldwide Developer Relations - G7`, and `Worldwide Developer Relations - G8`. +* `print-signature-info` now prints some integer values as strings containing + both the integer and hex forms. Additional fields are added to help debug + signature writing. +* Conflicting binary identifiers within a universal Mach-O are now reconciled + to the initially seen value. This matches the behavior of Apple's tooling + and fixes a bug where drift between the values could cause bundle validation + to fail. (#103) +* Fixed a bug where bundle signing would fail to overwrite preexisting state + in Mach-O binaries, leading to failed signature verification. This likely + only occurred when attempting to re-sign already signed binaries. (#104) +* When signing bundles, non Mach-O resources files are no longer fully buffered + in memory to compute their content digests. This can drastically cut down + on memory usage when signing large resources files. Mach-O binaries are + still fully buffered in memory. (#45) +* Removed `verify` warning about insecure code digests. The warning was spurious + and didn't take into account the nuanced logic for emitting SHA-1 digests. + (#50) +* cryptographic-message-syntax 0.25 -> 0.26. +* x509-certificate 0.22 -> 0.23. + +## 0.23.0 + +Released on 2023-11-06. + +* Notarization features are now optional and can be controlled via the + enabled-by-default `notarize` crate feature. (#78) +* Minimum supported Rust version changed from 1.62.1 to 1.70.0. +* CLI argument parsing has been rewritten to use clap's derive mode + instead of the builder mode. The intent was to mostly preserve existing + CLI behavior. However, some minor changes - possibly bugs - may have + occurred as a result of this refactor. +* `AppleCodesignError::AwsS3Error` now stores a `Box`. +* Added a hidden `debug-create-macho` command for generating Mach-O files. + The command (and new code behind it) is intended to facilitate writing + tests of Mach-O signing. +* Added a hidden `debug-create-info-plist` command for generating Info.plist + files. The command is intended to be used to facilitate testing. +* The `--code-signature-flags` argument of the `sign` command now correctly + applies multiple values. Before, flags were set to the final specified + value. +* Added several trycmd based tests for testing CLI and signing behaviors. + The trycmd tests may download a prebuilt Rust coreutils binary from + github.com when executing on platforms with prebuilt binaries. +* The `--data` argument of the `extract` command is now a positional argument. +* Added a hidden `debug-create-code-requirements` command for generating + binary code requirements files. The command is intended to facilitate testing. +* The `print-signature-info` command should now work on bundles. It may have + stopped working as part of an upgrade to `serde_yaml`. The YAML output may + have changed slightly. +* `CodeResources` files now emit `"` instead of `"` for parity with Apple + tooling. +* SHA-1 digests are now automatically enabled when signing a Mach-O binary + without platform targeting. This mimics the behavior of Apple's tooling. + Before, we would only automatically activate SHA-1 digests when there was + a Mach-O load command targeting a too-old platform version which didn't + support SHA-256 digests. +* An empty CMS blob is now automatically added when signing in ad-hoc mode. + Before, no CMS blob would be present. The new behavior matches that of + Apple's tooling. +* Code signature data is now aligned to 16 byte boundaries in Mach-O binaries. + This matches the behavior of Apple tooling. +* HTTP requests now use the operating system's trusted X.509 certificates + instead of a default set (based off Mozilla's maintained list). This should + allow connections to HTTP proxies using custom/private certificate authorities + to work, assuming certificates are installed on the local system. (#85) +* Added a hidden `debug-create-entitlements` command for generating entitlements + plist files. The command is intended to facilitate testing. +* The `print-signature-info` command YAML output now encodes entitlements XML + as an array of strings for easier readability. +* A custom signing time can now be specified to force using a specific + time instead of the current time. The CMS signing and settings APIs have + changed accordingly. The `sign` command now accepts a `--signing-time` + argument to control the signing time. +* The `generate-self-signed-certificate` command gained a + `--pem-unified-filename` argument to write a PEM encoded file containing + both the private key and public certificate. +* Fixed a bug where files would be identified as Mach-O when they weren't. +* Bundle signing logic has been significantly overhauled to hopefully make + it conform with Apple tooling's behavior. This likely fixed several bugs + with bundle signing. +* Fixed a bundle signing bug where overwriting symlinks would incorrectly + result in an `Error: I/O error: File exists (os error 17)` or similar. +* When signing bundles, symlinks in directories marked as *nested* should + now get properly sealed and installed. (#10) +* When signing bundles, Mach-O binaries outside of *nested* directories + (e.g. `Libraries/libFoo.dylib`) are automatically detected as Mach-O + binaries and signed. This behavior conforms with our stated behavior of + recursively signing all signable entities. However, it is incompatible + with Apple's tooling, which only signs Mach-O binaries located in + specific directories having the *nested* flag set. This change should + result in *it just works* single command signing of many complex + bundles. +* Added a hidden `debug-file-tree` command to print simple directory + trees. The command is used by snapshot tests to validate bundle signing + behavior. +* The CLI default log level has been changed to `warn`. As a result, + command output is less verbose. `-v` restores the prior behavior. And + `-vvv` is now needed to activate `trace` logging (previously `-vv` was + the highest log level). +* The `sign --exclude` argument is now honored for Mach-O binaries within + bundles. Previously, it only applied to bundle paths. +* The default `CodeResources` rules for bundles lacking a `Resources/` + now properly have trailing `/` on rules referencing `.lproj` directories. + Previously, these directories were likely not handled correctly. (#42) +* Fixed a bug where attempting to sign Mach-O binaries having a `__TEXT` segment + whose start offset was >0 resulted in a `Mach-O segment corruption` error. + We can now properly sign such files. (#91) +* `verify` command now errors if not given the path of a Mach-O binary. +* `verify` command now prints a warning that its known to be buggy. +* aws crates 0.53 -> 0.57. +* bitflags 1.3 -> 2.0. +* cryptographic-message-syntax 0.19 -> 0.25. +* dialoguer 0.10 -> 0.11. +* dirs 4.0 -> 5.0. +* elliptic-curve 0.12 -> 0.13. +* goblin 0.6 -> 0.7. +* minicbor 0.19 -> 0.20. +* once_cell 1.16 -> 1.17. +* pkcs1 0.4 -> 0.7. +* p256 0.11 -> 0.13. +* pem 1.1 -> 3.0. +* pkcs8 0.9 -> 0.10. +* rasn 0.6 -> 0.11. +* ring 0.16 -> 0.17. +* rsa 0.7 -> 0.9. +* signature 1.6 -> 2.0. +* spake2 0.3 -> 0.4. +* spki 0.6 -> 0.7. +* tungstenite 0.18 -> 0.20. +* x509-certificate 0.16 -> 0.22. +* yubikey 0.7 -> 0.8. + +## 0.22.0 + +Released on 2022-12-21. + +* Cargo.toml now defines patch version for all dependencies. +* goblin crate upgraded from 0.5 to 0.6. +* App Store Connect API code extracted to its own crate, `app-store-connect`. + The new crate lives in the same repository as this one. (#54) + +## 0.21.0 + +Released on 2022-12-18. + +* Embedded entitlements XML is now used when estimating the size of signatures. + Previously, this data could cause us to not reserve enough space for the + signature, causing signing to fail. (#32, #40) +* Bundle stapling is now capable of stapling any bundle with a main executable, + not just app bundles with a main executable. (#41) +* The `smartcard-scan`, `smartcard-generate-key`, and `smartcard-import` + commons are now always present, even when compiled without the `smartcard` + crate feature enabled. The commands will error at runtime if smartcard support + is not enabled. +* Minimum supported Rust version changed from 1.61.0 to 1.62.1. +* Changed handling of code requirements around bundle signing to hopefully fix + `the sealed resource directory is invalid` errors. This should hopefully + enable signing adhoc app bundles with frameworks. Before, if a Mach-O inside + a bundle contained no designated requirements, no designated requirements + were emitted. After, designated requirements are derived automatically from + the digests of code directories in Mach-O binaries. Additionally, an empty + designated requirements blob can be emitted. (#44) +* Shallow framework bundles are now properly recognized as such. This fixes + a common issue with signing iOS bundles. (#46) + +## 0.20.0 + +Released on 2022-10-02. + +* Zip notarization support. APIs and the `notary-submit` CLI command now recognize + zip files and will upload them to the Notary API without modifications. Neither + zip file signing nor stapling are supported. Feature contributed by @deansheather. + (#20) +* When signing the main binary in a bundle, we now prefer the identifier from + the bundle's `Info.plist` over the identifier already present in the Mach-O. + This ensures that the identifier is consistent across multiple Mach-O in a + fat/universal binary and is consistent with the value advertised in the + `Info.plist`. (#12, #22) +* It is now possible to sign Mach-O binaries where the `__LINKEDIT` segment + wasn't the final advertised segment in Mach-O headers. Previously, a + `__LINKEDIT isn't final Mach-O segment` error would occur when attempting to + sign a Mach-O whose headers declared a `__LINKEDIT` segment before other + segments, even if `__LINKEDIT` was truly at the highest file offset. (This + scenario is common in Go binaries.) (#17) +* The `--pem-source` argument can now decode PKCS#1 private keys as encoded + with `RSA PRIVATE KEY`. Previously, an `unhandled PEM tag RSA PRIVATE KEY; + ignoring` warning would have been printed. (#26) +* Most code from `main.rs` has been moved into `cli.rs` so it is part of the + library. +* `aws-config`, `aws-smithy-http` upgraded from 0.47 -> 0.49. +* `aws-sdk-s3` upgraded from 0.17 -> 0.19. +* `clap` upgraded from 3.1 -> 4.0. This entailed a lot of code changes to + argument parsing. Argument parsing behavior should be backwards compatible + (unless otherwise documented in this section) and any change in behavior is + a bug. + +## 0.19.0 + +(Released 2022-09-18) + +* Canonical home of project moved from https://github.com/indygreg/PyOxidizer to + https://github.com/indygreg/apple-platform-rs. +* Universal Mach-O creation logic inlined from `tugger-apple` crate to remove + crate dependency. +* Switched from `tugger-file-manifest` crate to `simple-file-manifest`. (The + crate was effectively renamed.) + +## 0.18.0 + +(Released 2022-09-17) + +* Mach-O digesting code now digests file-level data without looking at segment + boundaries. This fixes a bug where we were computing the incorrect digests when + Mach-O segments weren't aligned at 4096 byte boundaries. (Go binaries commonly + don't have 4k aligned segment boundaries.) (#634) +* Optimizations to computing cryptographic digests of binaries. We eliminate a + a redundant digest that was used to compute the final size of the code digests. + The `rayon` crate is now used to perform digests in parallel, yielding a + ~linear speedup with the number of CPUs available. +* (API) `app_store_connect` module has been split up into multiple modules + to facilitate better grouping. +* (API) Various changes for upgrades of crates related to cryptography. +* der crate upgraded from 0.5 to 0.6. +* elliptic-curve crate upgraded from 0.11 to 0.12. +* oid-registry crate upgraded from 0.5 to 0.6. +* p256 crate upgraded from 0.10 to 0.11. +* pkcs1 crate upgraded from 0.3 to 0.4. +* pkcs8 crate upgraded from 0.8 to 0.9. +* spki crate upgraded from 0.5 to 0.6. +* yubikey crate upgraded from 0.4 to 0.6. +* (API) The `code_hash` module had its content folded into the new function + `MachOBinary::code_digests()`. + +## 0.17.0 + +(Released 2022-08-07) + +* **Major feature**: Notarization is now implemented in Rust and no longer + requires Apple's *Transporter* application. Going forward, you only need + the `rcodesign` executable (or this crate embedded as a library) and an + App Store Connect API Key to notarize. Major thanks to Robin Lambertz + (@roblabla) for contributing the bulk of the implementation in #593. +* As a result of native notarization, integration with Apple's *Transporter* + has been removed. The `find-transporter` command has been removed. Rust + APIs related to Transporter, the *app metadata* XML format it used, and App + Store Connect APIs previously used have been removed. +* As a result of native notarization, UI and implementation details of + notarization have changed. The output when uploading assets is much more + concise. Before, code existed to normalize uploaded assets to a data format + required by Transporter. As a side-effect, assets were somewhat validated + locally before upload. In the new world, minimal checks are performed locally. + This can result in errors (such as attempting to upload an asset without a + code signature) occurring later than they did previously. +* A new `encode-app-store-connect-api-key` command can be used to encode an + App Store Connect API Key in a single JSON object. These keys are used for + notarization and having all the API Key metadata in a single file / JSON + blob means you have 1 entity to define your App Store Connect API Key instead + of 3, making UI simpler. +* The `notarize` command has been renamed to `notary-submit`. This follows + the terminology of Apple's `notarytool` and mimics the nomenclature used + by the Notary API. The old `notarize` command is an alias to + `notary-submit`. +* The `notary-submit` command now has an `--api-key-path` argument defining the + path to a JSON file containing the unified App Store Connect API Key emitted + by the `encode-app-store-connect-api-key` command. We recommend using this + method for specifying the API Key going forward, as it is simpler. The old + method was required for use with Apple's Transporter application, which we + no longer use so we're no longer bound by its requirements. The old method + will likely be dropped from a future release. +* A new `notary-wait` command can be used to wait on a previous notary + submission to complete and to view its log info. This command can be useful if + `notary-submit` times out or otherwise fails and you want to query the + status of a previous notarization. +* A new `notary-log` command will fetch the notarization log of a previous + submission from the Notary API server. +* Fixed signing of Mach-O binaries having a gap between segments. (This is known + to commonly occur in Go binaries.) In previous versions, we would compute + digests of the file incorrectly and would encounter an assertion when copying + Mach-O data to the output binary. Both of these issues should now be fixed. + (#588 and #616) +* minicbor crate upgraded from version 0.15. This created API differences in + remote signing code. +* The APIs around Mach-O file parsing have been significantly overhauled. It + is probably best to diff the `macho` module to see the full differences. + There are now `MachFile` and `MachOBinary` types serving as interfaces + to custom Mach-O functionality. Most code interfacing with a Mach-O file now + uses these types. The `AppleSignable` trait has been deleted as it is no + longer needed since we have the dedicated `MachOBinary` type. + +## 0.16.0 + +(Released 2022-06-05) + +* Distributed macOS binaries no longer dynamically link `liblzma.5.dylib`. + +## 0.15.0 + +(Released 2022-06-04) + +* XAR files are now always signed through a temporary file in order to avoid + corruption of the XAR file. + +## 0.14.0 + +(Released 2022-04-24) + +* Fixed a bug where symlinks weren't been written in notarization zip file + files properly. This prevented bundles containing symlinks from notarizing + correctly. +* The filename used in notarization uploads is now normalized to avoid + rejection due to spaces and colons. +* Support for remote signing. The feature is documented extensively in the + Sphinx documentation. Essentially, 2 independent machines communicate with + each other with end-to-end encrypted messages via a websocket bridged through + a central server. Signing requests are sent to a remote machine which is in + possession of the signing key. Signatures are made on the remote machine and + transmitted back to the originating machine. Remote signing enables signing + to be performed more securely by facilitating signing without having to give + the initiating machine access to the signing key. +* Default log output format has changed. Lines are no longer prefixed with the + time, log level, or logging module by default. A `-v/--verbose` global flag + has been added to increase the verbosity of logging. This can restore the + printing of the prefixes. This crate uses + `env_logger `_, so it is possible + to customize default behavior via environment variables. +* The possible values for the `--code-signature-flags` are now advertised in + help output. +* Written Mach-O files should now always have their filesystem permissions + preserved. Before, we may not have preserved file permissions in all code + paths writing Mach-O files. +* A new `keychain-print-certificates` command can be used to print + certificates available in macOS keychains. +* Initial support for using macOS keychain certificates for code signing. + Previously, we required that certificates be exported from keychain in + order to sign. We now support signing using SecurityFramework APIs so + keys don't have to leave the keychain. Due to a limitation in the Rust + bindings to SecurityFramework, decryption using keychain keys is not + supported. So the *public key agreement* method of remote code signing + will not yet work with keychain-based keys. The new `--keychain-domain` + and `--keychain-fingerprint` arguments can be used to specify how to + search for and use keychain hosted keys. + +## 0.13.0 + +(Released 2022-04-10) + +* Restores behavior of <= 0.10.0 where the binary identifier of non main + executable Mach-O files in bundles is automatically derived from the file name + if the Mach-O doesn't already have a binary identifier. This fixes a regression + in 0.11 and 0.12. +* When signing a Mach-O, `Info.plist` data embedded in the Mach-O is now + automatically used when no `Info.plist` data is provided externally. +* The handling of preserving metadata from previous Mach-O signatures has been + refactored. In the new world, existing Mach-O state is imported into the + signing settings data structure at signing time and the signing operation + largely uses the settings data structure as the canonical source for state. + Explicitly set signing settings should take precedence over a previous Mach-O + signature. +* Fixed a bug where empty Mach-O segments could result in an error when writing + signed Mach-O files. (#544) +* Mach-O and bundle signing now automatically use OS targeting metadata embedded + in Mach-O binaries to activate SHA-1 + SHA-256 digests when necessary. If a + Mach-O binary indicates it targets an older OS version that lacks support for + SHA-256 digests (e.g. macOS <10.11.4), we will automatically use SHA-1 as the + primary digest method and include SHA-256 digests for modern operating systems. + As a result of this change, binaries and bundles that were targeting macOS + <10.11.4, iOS/tvOS <11, and watchOS now properly contain SHA-1 digests as the + primary digest type. +* In bundle signing, `CodeResources` files now capture the `cdhash` of the + SHA-256 code directory. Before, they would always use the primary code + directory, which might be using SHA-1. The `cdhash` value must be from the + SHA-256 code directory to be valid. This change should result in more bundles + having working signatures. +* DER encoded entitlements are now only added when signing executable files. + Previously, we added DER encoded entitlements whenever entitlements data + was present. It appears DER encoded entitlements are only written on Mach-O + binaries that are executables. +* Executable segment flags are now derived from the Mach-O file type and + entitlements plist data. We no longer blindly copy executable segment flags + from previous signatures. We no longer have CLI arguments to define executable + segment flags. This ensures that the entitlements plist and executable + segment flags are always in sync. +* CMS signatures are now properly constructed when there are multiple code + directories. Before, the CMS signed attributes didn't capture all code + directories and the signatures would be incomplete. This resulted in Apple's + tooling rejecting the CMS signatures as invalid. + +## 0.12.0 + +* Binary identifier strings are now always enclosed in double quotes when + serializing code requirements expressions to strings. Previously, the lack of + double quotes could result in malformed strings that might fail to parse. +* Fixed a bundle signing bug where the digests of nested bundles were taken from the + source directory and not the destination directory. This would result in digests + of nested bundles being incorrect if signing bundles to a different output directory + than from the input. + +## 0.11.0 + +* The `--pfx-file`, `--pfx-password`, and `--pfx-password-file` arguments + have been renamed to `--p12-file`, `--p12-password`, and + `--p12-password-file`, respectively. The old names are aliases and should + continue to work. +* Initial support for using smartcards for signing. Smartcard integration may only + work with YubiKeys due to how the integration is implemented. +* A new `rcodesign smartcard-scan` command can be used to scan attached + smartcards and certificates they have available for code signing. +* `rcodesign sign` now accepts a `--smartcard-slot` argument to specify the + slot number of a certificate to use when code signing. +* A new `rcodesign smartcard-import` command can be used to import a code signing + certificate into a smartcard. It can import private-public key pair or just import + a public certificate (and use an existing private key on the smartcard device). +* A new `rcodesign generate-certificate-signing-request` command can be used + to generate a Certificate Signing Request (CSR) which can be uploaded to Apple + and exchanged for a code signing certificate signed by Apple. +* A new `rcodesign smartcard-generate-key` command for generating a new private + key on a smartcard. +* Fixed bug where `--code-signature-flags`, `--executable-segment-flags`, + `--runtime-version`, and `--info-plist-path` could only be specified once. +* `rcodesign sign` now accepts an `--extra-digest` argument to provide an + extra digest type to include in signatures. This facilitates signing with + multiple digest types via e.g. `--digest sha1 --extra-digest sha256`. +* Fixed an embarrassing number of bugs in bundle signing. Bundle signing was + broken in several ways before: resource files in shallow app bundles (e.g. iOS + app bundles) weren't handled correctly; symlinks weren't preserved correctly; + framework signing was completely busted; nested bundles weren't signed in the + correct order; entitlements in Mach-O binaries weren't preserved during + signing; `CodeResources` files had extra entries in `` that shouldn't + have been there, and likely a few more. +* Add `--exclude` argument to `rcodesign sign` to allow excluding nested + bundles from signing. +* Notarizing bundles containing symlinks no longer fails with a cryptic I/O + error message. We now produce zip files with symlink entries. However, there + may still be issues getting Apple to notarize bundles with symlinks. +* Fixed a bug where we could silently write a softly corrupt code signature + by copying digests that were too short. Previously, if you attempted to re-sign + a Mach-O having SHA-1 digests, those SHA-1 digests could get copied to the + new signature using SHA-256 digests and the bytes belonging to each digest + would get mangled and wouldn't be correct. We now prevent writing digests + that don't match the expected digest length and when copying digests we + look for alternate code directories having the digest of the new signature. + +## 0.10.0 + +* Support for signing, notarizing, and stapling `.dmg` files. +* Support for signing, notarizing, and stapling flat packages (`.pkg` installers). +* Various symbols related to common code signature data structures have been moved from the + `macho` module to the new `embedded_signature` module. +* Signing settings types have been moved from the `signing` module to the new + `signing_settings` module. +* `rcodesign sign` no longer requires an output path and will now sign an entity + in place if only a single positional argument is given. +* The new `rcodesign print-signature-info` command prints out easy-to-read YAML + describing code signatures detected in a given path. Just point it at a file with + code signatures and it can print out details about the code signatures within. +* The new `rcodesign diff-signatures` command prints a diff of the signature content + of 2 filesystem paths. It is essentially a built-in diffing mechanism for the output + of `rcodesign print-signature-info`. The intended use of the command is to aid + in debugging differences between this tool and Apple's canonical tools. + +## 0.9.0 + +* Imported new Apple certificates. `Developer ID - G2 (Expiring 09/17/2031 00:00:00 UTC)`, + `Worldwide Developer Relations - G4 (Expiring 12/10/2030 00:00:00 UTC)`, + `Worldwide Developer Relations - G5 (Expiring 12/10/2030 00:00:00 UTC)`, + and `Worldwide Developer Relations - G6 (Expiring 03/19/2036 00:00:00 UTC)`. +* Changed names of enum variants on `apple_codesign::apple_certificates::KnownCertificate` + to reflect latest naming from https://www.apple.com/certificateauthority/. +* Refreshed content of Apple certificates `AppleAAICA.cer`, `AppleISTCA8G1.cer`, and + `AppleTimestampCA.cer`. +* Renamed `apple_codesign::macho::CodeSigningSlot::SecuritySettings` to + `EntitlementsDer`. +* Add `apple_codesign::macho::CodeSigningSlot::RepSpecific`. +* `rcodesign extract` has learned a `macho-target` output to display information + about targeting settings of a Mach-O binary. +* The code signature data structure version is now automatically modernized when + signing a Mach-O binary targeting iOS >= 15 or macOS >= 12. This fixes an issue + where signatures of iOS 15+ binaries didn't meet Apple's requirements for this + platform. +* Logging switched to `log` crate. This changes program output slightly and removed + an `&slog::Logger` argument from various functions. +* `SigningSettings` now internally stores entitlements as a parsed plist. Its + `set_entitlements_xml()` now returns `Result<()>` in order to reflect errors + parsing plist XML. Its `entitlements_xml()` now returns `Result>` + instead of `Option<&str>` because XML serialization is fallible and the resulting + XML is owned instead of a reference to a stored value. As a result of this change, + the embedded entitlements XML specified via `rcodesign sign --entitlement-xml-path` + may be encoded differently than it was previously. Before, the content of the + specified file was embedded verbatim. After, the file is parsed as plist XML and + re-serialized to XML. This can result in encoding differences of the XML. This + should hopefully not matter, as valid XML should be valid XML. +* Support for DER encoded entitlements in code signatures. Apple code signatures + encode entitlements both in plist XML form and DER. Previously, we only supported + the former. Now, if entitlements are being written, they are written in both XML + and DER. This should match the default behavior of `codesign` as of macOS 12. + (#513, #515) +* When signing, the entitlements plist associated with the signing operation + is now parsed and keys like `get-task-allow` and + `com.apple.private.skip-library-validation` are now automatically propagated + to the code directory's executable segment flags. Previously, no such propagation + occurred and special entitlements would not be fully reflected in the code + signature. The new behavior matches that of `codesign`. +* Fixed a bug in `rcodesign verify` where code directory verification was + complaining about `slot digest contains digest for slot not in signature` + for the `Info (1)` and `Resources (3)` slots. The condition it was + complaining about was actually valid. (#512) +* Better supported for setting the hardened runtime version. Previously, we + only set the hardened runtime version in a code signature if it was present + in the prior code signature. When signing unsigned binaries, this could + result in the hardened runtime version not being set, which would cause + Apple tools to complain about the hardened runtime not being enabled. Now, + if the `runtime` code signature flag is set on the signing operation and + no runtime version is present, we derive the runtime version from the version + of the Apple SDK used to build the binary. This matches the behavior of + `codesign`. There is also a new `--runtime-version` argument to + `rcodesign sign` that can be used to override the runtime version. +* When signing, code requirements are now printed in their human friendly + code requirements language rather than using Rust's default serialization. +* `rcodesign sign` will now automatically set the team ID when the signing + certificate contains one. +* Added the `rcodesign find-transporter` command for finding the path to + Apple's *Transporter* program (which is used for notarization). +* Initial support for stapling. The `rcodesign staple` command can be used + to staple a notarization ticket to an entity. It currently only supports + stapling app bundles (`.app` directories). The command will automatically + contact Apple's servers to obtain a notarization ticket and then staple + any found ticket to the requested entity. +* Initial support for notarizing. The `rcodesign notarize` command can + be used to upload an entity to Apple. The command can optionally wait on + notarization to finish and staple the notarization ticket if notarization + is successful. The command currently only supports macOS app bundles + (`.app` directories). + +## 0.8.0 + +* Crate renamed from `tugger-apple-codesign` to `apple-codesign`. +* Fixed bug where signing failed to update the `vmsize` field of the + `__LINKEDIT` mach-o segment. Previously, a malformed mach-o file could + be produced. (#514) +* Added `x509-oids` command for printing Apple OIDs related to code signing. +* Added `analyze-certificate` command for printing information about + certificates that is relevant to code signing. +* Added the `tutorial` crate with some end-user documentation. +* Crate dependencies updated to newer versions. + +## 0.7.0 and Earlier + +* Crate was published as `tugger-apple-codesign`. No history kept in this file. diff --git a/3rdparty/apple-codesign-0.29.0/Cargo.lock b/3rdparty/apple-codesign-0.29.0/Cargo.lock new file mode 100644 index 00000000..5cb1ce4f --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/Cargo.lock @@ -0,0 +1,4884 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +dependencies = [ + "anstyle", + "windows-sys 0.59.0", +] + +[[package]] +name = "anyhow" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" + +[[package]] +name = "app-store-connect" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed88de4349fc3eb58529530edeb7177516c94712d3dc8bf11bd76ac1715695c4" +dependencies = [ + "anyhow", + "base64 0.22.1", + "clap", + "dirs", + "env_logger", + "jsonwebtoken", + "log", + "pem", + "rand", + "reqwest", + "rsa", + "serde", + "serde_json", + "thiserror 2.0.3", + "x509-certificate", +] + +[[package]] +name = "apple-bundles" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f40bb8f844cec39fa3aceae717808c2ac3d2b6c474a9dffbeba07a4a945d10" +dependencies = [ + "anyhow", + "plist", + "simple-file-manifest", + "walkdir", +] + +[[package]] +name = "apple-codesign" +version = "0.29.0" +dependencies = [ + "anyhow", + "app-store-connect", + "apple-bundles", + "apple-flat-package", + "apple-xar", + "aws-config", + "aws-sdk-s3", + "aws-smithy-http", + "aws-smithy-types", + "base64 0.22.1", + "bcder", + "bitflags 2.6.0", + "bytes", + "chrono", + "clap", + "cryptographic-message-syntax", + "der 0.7.9", + "dialoguer", + "difference", + "digest", + "dirs", + "elliptic-curve 0.13.8", + "env_logger", + "figment", + "filetime", + "flate2", + "glob", + "goblin", + "hex", + "indoc", + "log", + "md-5", + "minicbor", + "num-traits", + "object", + "oid-registry", + "once_cell", + "p12", + "p256 0.13.2", + "pem", + "pkcs1", + "pkcs8 0.10.2", + "plist", + "rand", + "rasn", + "rayon", + "regex", + "reqwest", + "ring", + "rsa", + "scroll", + "security-framework 2.11.1", + "security-framework-sys", + "semver", + "serde", + "serde_json", + "serde_yaml", + "sha2", + "signature 2.2.0", + "simple-file-manifest", + "spake2", + "spki 0.7.3", + "subtle", + "tar", + "tempfile", + "thiserror 2.0.3", + "tokio", + "trycmd-indygreg-fork", + "tungstenite", + "uuid", + "walkdir", + "widestring", + "windows-sys 0.59.0", + "x509", + "x509-certificate", + "xml-rs", + "yasna", + "yubikey", + "zeroize", + "zip", + "zip_structs", +] + +[[package]] +name = "apple-flat-package" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9d5a1fd8af4a376cc33d7e816a13f8ce127d52101f5dbc8061fb595397bea0" +dependencies = [ + "apple-xar", + "cpio-archive", + "flate2", + "scroll", + "serde", + "serde-xml-rs", + "thiserror 2.0.3", +] + +[[package]] +name = "apple-xar" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9631e781df71ebd049d7b4988cdae88712324cb20eb127fd79026bc8f1335d93" +dependencies = [ + "base64 0.22.1", + "bcder", + "bzip2", + "chrono", + "cryptographic-message-syntax", + "digest", + "flate2", + "log", + "md-5", + "rand", + "reqwest", + "scroll", + "serde", + "serde-xml-rs", + "sha1", + "sha2", + "signature 2.2.0", + "thiserror 2.0.3", + "url", + "x509-certificate", + "xml-rs", + "xz2", +] + +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "asn1-rs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 1.0.69", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "aws-config" +version = "1.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b49afaa341e8dd8577e1a2200468f98956d6eda50bcf4a53246cc00174ba924" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sso", + "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "hex", + "http 0.2.12", + "ring", + "time", + "tokio", + "tracing", + "url", + "zeroize", +] + +[[package]] +name = "aws-credential-types" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60e8f6b615cb5fc60a98132268508ad104310f0cfb25a1c22eee76efdf9154da" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + +[[package]] +name = "aws-runtime" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a10d5c055aa540164d9561a0e2e74ad30f0dcf7393c3a92f6733ddf9c5762468" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", +] + +[[package]] +name = "aws-sdk-s3" +version = "1.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43850204a109a5eea1ea93951cf0440268cef98b0d27dfef4534949e23735f7" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-checksums", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "bytes", + "fastrand", + "hex", + "hmac", + "http 0.2.12", + "http-body 0.4.6", + "lru", + "once_cell", + "percent-encoding", + "regex-lite", + "sha2", + "tracing", + "url", +] + +[[package]] +name = "aws-sdk-sso" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09677244a9da92172c8dc60109b4a9658597d4d298b188dd0018b6a66b410ca4" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-ssooidc" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fea2f3a8bb3bd10932ae7ad59cc59f65f270fc9183a7e91f501dc5efbef7ee" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sts" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ada54e5f26ac246dc79727def52f7f8ed38915cb47781e2a72213957dc3a7d5" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5619742a0d8f253be760bfbb8e8e8368c69e3587e4637af5754e488a611499b1" +dependencies = [ + "aws-credential-types", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "crypto-bigint 0.5.5", + "form_urlencoded", + "hex", + "hmac", + "http 0.2.12", + "http 1.1.0", + "once_cell", + "p256 0.11.1", + "percent-encoding", + "ring", + "sha2", + "subtle", + "time", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62220bc6e97f946ddd51b5f1361f78996e704677afc518a4ff66b7a72ea1378c" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-checksums" +version = "0.60.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1a71073fca26775c8b5189175ea8863afb1c9ea2cceb02a5de5ad9dfbaa795" +dependencies = [ + "aws-smithy-http", + "aws-smithy-types", + "bytes", + "crc32c", + "crc32fast", + "hex", + "http 0.2.12", + "http-body 0.4.6", + "md-5", + "pin-project-lite", + "sha1", + "sha2", + "tracing", +] + +[[package]] +name = "aws-smithy-eventstream" +version = "0.60.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cef7d0a272725f87e51ba2bf89f8c21e4df61b9e49ae1ac367a6d69916ef7c90" +dependencies = [ + "aws-smithy-types", + "bytes", + "crc32fast", +] + +[[package]] +name = "aws-smithy-http" +version = "0.60.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8bc3e8fdc6b8d07d976e301c02fe553f72a39b7a9fea820e023268467d7ab6" +dependencies = [ + "aws-smithy-eventstream", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4683df9469ef09468dad3473d129960119a0d3593617542b7d52086c8486f2d6" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-query" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fbd61ceb3fe8a1cb7352e42689cec5335833cd9f94103a61e98f9bb61c64bb" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be28bd063fa91fd871d131fc8b68d7cd4c5fa0869bea68daca50dcb1cbd76be2" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "http-body 1.0.1", + "httparse", + "hyper 0.14.31", + "hyper-rustls 0.24.2", + "once_cell", + "pin-project-lite", + "pin-utils", + "rustls 0.21.12", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92165296a47a812b267b4f41032ff8069ab7ff783696d217f0994a0d7ab585cd" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.1.0", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-types" +version = "1.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fbd94a32b3a7d55d3806fe27d98d3ad393050439dd05eb53ece36ec5e3d3510" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.1.0", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", + "tokio", + "tokio-util", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab0b0166827aa700d3dc519f72f8b3a91c35d0b8d042dc5d643a91e6f80648fc" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5221b91b3e441e6675310829fd8984801b772cb1546ef6c0e54dec9f1ac13fef" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version", + "tracing", +] + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bcder" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c627747a6774aab38beb35990d88309481378558875a41da1a4b2e373c906ef0" +dependencies = [ + "bytes", + "smallvec", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "bitvec-nom2" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d988fcc40055ceaa85edc55875a08f8abd29018582647fd82ad6128dba14a5f0" +dependencies = [ + "bitvec", + "nom", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytemuck" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + +[[package]] +name = "bytesize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" + +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets 0.52.6", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clap" +version = "4.5.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.52.0", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const_panic" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "013b6c2c3a14d678f38cd23994b02da3a1a1b6a5d1eedddfe63a5a5f11b13a81" + +[[package]] +name = "content_inspector" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38" +dependencies = [ + "memchr", +] + +[[package]] +name = "cookie-factory" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" +dependencies = [ + "futures", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpio-archive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f11d34b07689c21889fc89bd7cc885b3244b0157bbededf4a1c159832cd0df05" +dependencies = [ + "chrono", + "is_executable", + "simple-file-manifest", + "thiserror 1.0.69", +] + +[[package]] +name = "cpufeatures" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32c" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crypto-bigint" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cryptographic-message-syntax" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a99e58d7755c646cb3f2a138d99f90da4c495282e1700b82daff8a48759ce0" +dependencies = [ + "bcder", + "bytes", + "chrono", + "hex", + "pem", + "reqwest", + "ring", + "signature 2.2.0", + "x509-certificate", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "rand_core", + "rustc_version", + "subtle", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "der_derive", + "flagset", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "der_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "des" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" +dependencies = [ + "cipher", +] + +[[package]] +name = "dialoguer" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" +dependencies = [ + "console", + "shell-words", + "tempfile", + "thiserror 1.0.69", + "zeroize", +] + +[[package]] +name = "difference" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "ecdsa" +version = "0.14.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" +dependencies = [ + "der 0.6.1", + "elliptic-curve 0.12.3", + "rfc6979 0.3.1", + "signature 1.6.4", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der 0.7.9", + "digest", + "elliptic-curve 0.13.8", + "rfc6979 0.4.0", + "signature 2.2.0", + "spki 0.7.3", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "elliptic-curve" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +dependencies = [ + "base16ct 0.1.1", + "crypto-bigint 0.4.9", + "der 0.6.1", + "digest", + "ff 0.12.1", + "generic-array", + "group 0.12.1", + "pkcs8 0.9.0", + "rand_core", + "sec1 0.3.0", + "subtle", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct 0.2.0", + "crypto-bigint 0.5.5", + "digest", + "ff 0.13.0", + "generic-array", + "group 0.13.0", + "hkdf", + "pem-rfc7468", + "pkcs8 0.10.2", + "rand_core", + "sec1 0.7.3", + "subtle", + "zeroize", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "env_filter" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fastrand" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" + +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "figment" +version = "0.10.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" +dependencies = [ + "atomic", + "pear", + "serde", + "toml", + "uncased", + "version_check", +] + +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + +[[package]] +name = "flagset" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3ea1ec5f8307826a5b71094dd91fc04d4ae75d5709b20ad351c7fb4815c86ec" + +[[package]] +name = "flate2" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "goblin" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53ab3f32d1d77146981dea5d6b1e8fe31eedcb7013e5e00d6ccd1259a4b4d923" +dependencies = [ + "log", + "plain", + "scroll", +] + +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff 0.12.1", + "rand_core", + "subtle", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff 0.13.0", + "rand_core", + "subtle", +] + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "humantime-serde" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" +dependencies = [ + "humantime", + "serde", +] + +[[package]] +name = "hyper" +version = "0.14.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.7", + "http 1.1.0", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.31", + "log", + "rustls 0.21.12", + "rustls-native-certs 0.6.3", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.5.1", + "hyper-util", + "rustls 0.23.19", + "rustls-native-certs 0.8.1", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "hyper 1.5.1", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown 0.15.2", +] + +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + +[[package]] +name = "inlinable_string" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + +[[package]] +name = "is_executable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a1b5bad6f9072935961dfbf1cced2f3d129963d091b6f69f007fe04e758ae2" +dependencies = [ + "winapi", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "js-sys" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb15147158e79fd8b8afd0252522769c4f48725460b37338544d8379d94fc8f9" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jsonwebtoken" +version = "9.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" +dependencies = [ + "base64 0.21.7", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] +name = "konst" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65f00fb3910881e52bf0850ae2a82aea411488a557e1c02820ceaa60963dce3" +dependencies = [ + "const_panic", + "konst_kernel", + "typewit", +] + +[[package]] +name = "konst_kernel" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "599c1232f55c72c7fc378335a3efe1c878c92720838c8e6a4fd87784ef7764de" +dependencies = [ + "typewit", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "libc" +version = "0.2.167" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" + +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", + "redox_syscall", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.2", +] + +[[package]] +name = "lzma-sys" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minicbor" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0452a60c1863c1f50b5f77cd295e8d2786849f35883f0b9e18e7e6e1b5691b0" +dependencies = [ + "minicbor-derive", +] + +[[package]] +name = "minicbor-derive" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd2209fff77f705b00c737016a48e73733d7fbccb8b007194db148f03561fb70" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "serde", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "crc32fast", + "flate2", + "hashbrown 0.15.2", + "indexmap", + "memchr", + "ruzstd", +] + +[[package]] +name = "oid-registry" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" +dependencies = [ + "asn1-rs", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "os_pipe" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "outref" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" + +[[package]] +name = "p12" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4873306de53fe82e7e484df31e1e947d61514b6ea2ed6cd7b45d63006fd9224" +dependencies = [ + "cbc", + "cipher", + "des", + "getrandom", + "hmac", + "lazy_static", + "rc2", + "sha1", + "yasna", +] + +[[package]] +name = "p256" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" +dependencies = [ + "ecdsa 0.14.8", + "elliptic-curve 0.12.3", + "sha2", +] + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "primeorder", + "sha2", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "pcsc" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ed9d7f816b7d9ce9ddb0062dd2f393b3af31411a95a35411809b4b9116ea08" +dependencies = [ + "bitflags 1.3.2", + "pcsc-sys", +] + +[[package]] +name = "pcsc-sys" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09e9ba80f2c4d167f936d27594f7248bca3295921ffbfa44a24b339b6cb7403" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "pear" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467" +dependencies = [ + "inlinable_string", + "pear_codegen", + "yansi", +] + +[[package]] +name = "pear_codegen" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn", +] + +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.1", + "serde", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der 0.7.9", + "pkcs8 0.10.2", + "spki 0.7.3", +] + +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der 0.6.1", + "spki 0.6.0", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der 0.7.9", + "spki 0.7.3", +] + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "plist" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" +dependencies = [ + "base64 0.22.1", + "indexmap", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve 0.13.8", +] + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "version_check", + "yansi", +] + +[[package]] +name = "quick-xml" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.23.19", + "socket2", + "thiserror 2.0.3", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +dependencies = [ + "bytes", + "getrandom", + "rand", + "ring", + "rustc-hash", + "rustls 0.23.19", + "rustls-pki-types", + "slab", + "thiserror 2.0.3", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rasn" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e442690f86da40561d5548e7ffb4a18af90d1c1b3536090de847ca2d5a3a6426" +dependencies = [ + "arrayvec", + "bitvec", + "bitvec-nom2", + "bytes", + "chrono", + "either", + "hashbrown 0.14.5", + "konst", + "nom", + "num-bigint", + "num-integer", + "num-traits", + "once_cell", + "rasn-derive", + "serde_json", + "snafu", +] + +[[package]] +name = "rasn-derive" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0d374c7e4e985e6bc97ca7e7ad1d9642a8415db2017777d6e383002edaab2" +dependencies = [ + "either", + "itertools", + "proc-macro2", + "quote", + "rayon", + "syn", + "uuid", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rc2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62c64daa8e9438b84aaae55010a93f396f8e60e3911590fcba770d04643fc1dd" +dependencies = [ + "cipher", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "reqwest" +version = "0.12.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.4.7", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.5.1", + "hyper-rustls 0.27.3", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.19", + "rustls-native-certs 0.8.1", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "windows-registry", +] + +[[package]] +name = "rfc6979" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +dependencies = [ + "crypto-bigint 0.4.9", + "hmac", + "zeroize", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rsa" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8 0.10.2", + "rand_core", + "sha2", + "signature 2.2.0", + "spki 0.7.3", + "subtle", + "zeroize", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "0.38.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile 1.0.4", + "schannel", + "security-framework 2.11.1", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "schannel", + "security-framework 2.11.1", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.0.1", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +dependencies = [ + "web-time", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "ruzstd" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad02996bfc73da3e301efe90b1837be9ed8f4a462b6ed410aa35d00381de89f" +dependencies = [ + "twox-hash", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scroll" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sec1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +dependencies = [ + "base16ct 0.1.1", + "der 0.6.1", + "generic-array", + "pkcs8 0.9.0", + "subtle", + "zeroize", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct 0.2.0", + "der 0.7.9", + "generic-array", + "pkcs8 0.10.2", + "subtle", + "zeroize", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1415a607e92bec364ea2cf9264646dcce0f91e6d65281bd6f2819cca3bf39c8" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.10.0", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-xml-rs" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb3aa78ecda1ebc9ec9847d5d3aba7d618823446a049ba2491940506da6e2782" +dependencies = [ + "log", + "serde", + "thiserror 1.0.69", + "xml-rs", +] + +[[package]] +name = "serde_derive" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "similar" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" + +[[package]] +name = "simple-file-manifest" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd19be0257552dd56d1bb6946f89f193c6e5b9f13cc9327c4bc84a357507c74" + +[[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "snafu" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "223891c85e2a29c3fe8fb900c1fae5e69c2e42415e3177752e8718475efa5019" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c3c6b7927ffe7ecaa769ee0e3994da3b8cafc8f444578982c83ecb161af917" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "snapbox" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b831b6e80fbcd2889efa75b185d24005f85981431495f995292b25836519d84" +dependencies = [ + "anstream", + "anstyle", + "content_inspector", + "dunce", + "filetime", + "libc", + "normalize-line-endings", + "os_pipe", + "similar", + "snapbox-macros", + "tempfile", + "wait-timeout", + "walkdir", + "windows-sys 0.52.0", +] + +[[package]] +name = "snapbox-macros" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16569f53ca23a41bb6f62e0a5084aa1661f4814a67fa33696a79073e03a664af" +dependencies = [ + "anstream", +] + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spake2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5482afe85a0b6ce956c945401598dbc527593c77ba51d0a87a586938b1b893a" +dependencies = [ + "curve25519-dalek", + "hkdf", + "rand_core", + "sha2", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der 0.6.1", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der 0.7.9", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tar" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tempfile" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +dependencies = [ + "thiserror-impl 2.0.3", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tls_codec" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e78c9c330f8c85b2bae7c8368f2739157db9991235123aa1b15ef9502bfb6a" +dependencies = [ + "tls_codec_derive", + "zeroize", +] + +[[package]] +name = "tls_codec_derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d9ef545650e79f30233c0003bcc2504d7efac6dad25fca40744de773fe2049c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio" +version = "1.41.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.19", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.22", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.6.20", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "trycmd-indygreg-fork" +version = "0.14.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea50b5f4225e7fcfbeaf36babe97acd166db5d4352febc0152b52b070025e45a" +dependencies = [ + "glob", + "humantime", + "humantime-serde", + "rayon", + "serde", + "shlex", + "snapbox", + "toml_edit 0.20.7", +] + +[[package]] +name = "tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.1.0", + "httparse", + "log", + "rand", + "rustls 0.23.19", + "rustls-native-certs 0.7.3", + "rustls-pki-types", + "sha1", + "thiserror 1.0.69", + "utf-8", +] + +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "static_assertions", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "typewit" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51dbd25812f740f45e2a9769f84711982e000483b13b73a8a1852e092abac8c" +dependencies = [ + "typewit_proc_macros", +] + +[[package]] +name = "typewit_proc_macros" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36a83ea2b3c704935a01b4642946aadd445cea40b10935e3f8bd8052b8193d6" + +[[package]] +name = "uncased" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +dependencies = [ + "getrandom", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21d3b25c3ea1126a2ad5f4f9068483c2af1e64168f847abe863a526b8dbfe00b" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52857d4c32e496dc6537646b5b117081e71fd2ff06de792e3577a150627db283" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951fe82312ed48443ac78b66fa43eded9999f738f6022e67aead7b708659e49a" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "920b0ffe069571ebbfc9ddc0b36ba305ef65577c94b06262ed793716a1afd981" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf59002391099644be3524e23b781fa43d2be0c5aa0719a18c0731b9d195cab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5047c5392700766601942795a436d7d2599af60dcc3cc1248c9120bfb0827b0" + +[[package]] +name = "web-sys" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "476364ff87d0ae6bfb661053a9104ab312542658c3d8f963b7ace80b6f9b26b9" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.26.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "widestring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "x509" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3cec94c3999f31341553f358ef55f65fc031291a022cd42ec0ce7219560c76" +dependencies = [ + "chrono", + "cookie-factory", +] + +[[package]] +name = "x509-cert" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94" +dependencies = [ + "const-oid", + "der 0.7.9", + "sha1", + "signature 2.2.0", + "spki 0.7.3", + "tls_codec", +] + +[[package]] +name = "x509-certificate" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57b9f8bcae7c1f36479821ae826d75050c60ce55146fd86d3553ed2573e2762" +dependencies = [ + "bcder", + "bytes", + "chrono", + "der 0.7.9", + "hex", + "pem", + "ring", + "signature 2.2.0", + "spki 0.7.3", + "thiserror 1.0.69", + "zeroize", +] + +[[package]] +name = "xattr" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +dependencies = [ + "libc", + "linux-raw-sys", + "rustix", +] + +[[package]] +name = "xml-rs" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af310deaae937e48a26602b730250b4949e125f468f11e6990be3e5304ddd96f" + +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "yubikey" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d1efb43c1e3edd4cf871c8dc500d900abfa083c1f2bab10b781ea8ffcadedcb" +dependencies = [ + "base16ct 0.2.0", + "der 0.7.9", + "des", + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "hmac", + "log", + "nom", + "num-bigint-dig", + "num-integer", + "num-traits", + "p256 0.13.2", + "p384", + "pbkdf2", + "pcsc", + "rand_core", + "rsa", + "secrecy", + "sha1", + "sha2", + "signature 2.2.0", + "subtle", + "uuid", + "x509-cert", + "zeroize", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zip" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d52293fc86ea7cf13971b3bb81eb21683636e7ae24c729cdaf1b7c4157a352" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "flate2", + "indexmap", + "memchr", + "thiserror 2.0.3", + "zopfli", +] + +[[package]] +name = "zip_structs" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce824a6bfffe8942820fa36d24973b7c83a40896749a42e33de0abdd11750ee5" +dependencies = [ + "byteorder", + "bytesize", + "thiserror 1.0.69", +] + +[[package]] +name = "zopfli" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +dependencies = [ + "bumpalo", + "crc32fast", + "lockfree-object-pool", + "log", + "once_cell", + "simd-adler32", +] diff --git a/3rdparty/apple-codesign-0.29.0/Cargo.toml b/3rdparty/apple-codesign-0.29.0/Cargo.toml new file mode 100644 index 00000000..eae1477b --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/Cargo.toml @@ -0,0 +1,365 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +rust-version = "1.81" +name = "apple-codesign" +version = "0.29.0" +authors = ["Gregory Szorc "] +build = false +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Pure Rust interface to code signing on Apple platforms" +homepage = "https://github.com/indygreg/apple-platform-rs" +readme = "README.md" +keywords = [ + "apple", + "macos", + "codesign", +] +license = "MPL-2.0" +repository = "https://github.com/indygreg/apple-platform-rs.git" + +[lib] +name = "apple_codesign" +path = "src/lib.rs" + +[[bin]] +name = "rcodesign" +path = "src/main.rs" + +[[test]] +name = "cli_tests" +path = "tests/cli_tests.rs" + +[dependencies.anyhow] +version = "1.0.93" + +[dependencies.app-store-connect] +version = "0.7.0" +optional = true + +[dependencies.apple-bundles] +version = "0.21.0" + +[dependencies.apple-flat-package] +version = "0.20.0" + +[dependencies.apple-xar] +version = "0.20.0" + +[dependencies.aws-config] +version = "1.5.10" +optional = true + +[dependencies.aws-sdk-s3] +version = "1.63.0" +optional = true + +[dependencies.aws-smithy-http] +version = "0.60.11" +optional = true + +[dependencies.aws-smithy-types] +version = "1.2.9" +optional = true + +[dependencies.base64] +version = "0.22.1" + +[dependencies.bcder] +version = "0.7.4" + +[dependencies.bitflags] +version = "2.6.0" + +[dependencies.bytes] +version = "1.9.0" + +[dependencies.chrono] +version = "0.4.38" + +[dependencies.clap] +version = "4.5.21" +features = ["derive"] + +[dependencies.cryptographic-message-syntax] +version = "0.27.0" + +[dependencies.der] +version = "0.7.9" +features = ["alloc"] + +[dependencies.dialoguer] +version = "0.11.0" + +[dependencies.difference] +version = "2.0.0" + +[dependencies.digest] +version = "0.10.7" + +[dependencies.dirs] +version = "5.0.1" + +[dependencies.elliptic-curve] +version = "0.13.8" +features = [ + "arithmetic", + "pkcs8", +] + +[dependencies.env_logger] +version = "0.11.5" + +[dependencies.figment] +version = "0.10.19" +features = [ + "env", + "toml", +] + +[dependencies.filetime] +version = "0.2.25" + +[dependencies.glob] +version = "0.3.1" + +[dependencies.goblin] +version = "0.9.2" + +[dependencies.hex] +version = "0.4.3" + +[dependencies.log] +version = "0.4.22" + +[dependencies.md-5] +version = "0.10.6" + +[dependencies.minicbor] +version = "0.25.1" +features = [ + "derive", + "std", +] + +[dependencies.num-traits] +version = "0.2.19" + +[dependencies.object] +version = "0.36.5" +features = ["write"] + +[dependencies.oid-registry] +version = "0.7.1" + +[dependencies.once_cell] +version = "1.20.2" + +[dependencies.p12] +version = "0.6.3" + +[dependencies.p256] +version = "0.13.2" +features = [ + "arithmetic", + "pkcs8", + "std", +] +default-features = false + +[dependencies.pem] +version = "3.0.4" + +[dependencies.pkcs1] +version = "0.7.5" +features = [ + "alloc", + "std", + "pkcs8", +] + +[dependencies.pkcs8] +version = "0.10.2" +features = [ + "alloc", + "std", +] + +[dependencies.plist] +version = "1.7.0" + +[dependencies.rand] +version = "0.8.5" + +[dependencies.rasn] +version = "0.20.2" + +[dependencies.rayon] +version = "1.10.0" + +[dependencies.regex] +version = "1.11.1" + +[dependencies.reqwest] +version = "0.12.9" +features = [ + "blocking", + "http2", + "json", + "rustls-tls-native-roots", +] +default-features = false + +[dependencies.ring] +version = "0.17.8" + +[dependencies.rsa] +version = "0.9.7" + +[dependencies.scroll] +version = "0.12.0" + +[dependencies.semver] +version = "1.0.23" + +[dependencies.serde] +version = "1.0.215" +features = ["derive"] + +[dependencies.serde_json] +version = "1.0.133" + +[dependencies.serde_yaml] +version = "0.9.34" + +[dependencies.sha2] +version = "0.10.8" + +[dependencies.signature] +version = "2.2.0" +features = ["std"] + +[dependencies.simple-file-manifest] +version = "0.11.0" + +[dependencies.spake2] +version = "0.4.0" + +[dependencies.spki] +version = "0.7.3" +features = ["pem"] + +[dependencies.subtle] +version = "2.6.1" + +[dependencies.tempfile] +version = "3.14.0" + +[dependencies.thiserror] +version = "2.0.3" + +[dependencies.tokio] +version = "1.41.1" +features = ["rt"] + +[dependencies.tungstenite] +version = "0.24.0" +features = ["rustls-tls-native-roots"] + +[dependencies.uuid] +version = "1.11.0" +features = ["v4"] + +[dependencies.walkdir] +version = "2.5.0" + +[dependencies.x509] +version = "0.2.0" + +[dependencies.x509-certificate] +version = "0.24.0" + +[dependencies.xml-rs] +version = "0.8.23" + +[dependencies.yasna] +version = "0.5.2" + +[dependencies.yubikey] +version = "0.8.0" +features = ["untested"] +optional = true + +[dependencies.zeroize] +version = "1.8.1" +features = ["zeroize_derive"] + +[dependencies.zip] +version = "2.2.1" +features = ["deflate"] +default-features = false + +[dependencies.zip_structs] +version = "0.2.1" + +[dev-dependencies.flate2] +version = "1.0.35" + +[dev-dependencies.indoc] +version = "2.0.5" + +[dev-dependencies.simple-file-manifest] +version = "0.11.0" + +[dev-dependencies.tar] +version = "0.4.43" + +[dev-dependencies.trycmd-indygreg-fork] +version = "0.14.20" + +[dev-dependencies.zip] +version = "2.2.1" +default-features = false + +[features] +default = ["notarize"] +notarize = [ + "app-store-connect", + "aws-config", + "aws-sdk-s3", + "aws-smithy-http", + "aws-smithy-types", +] +smartcard = ["yubikey"] + +[target.'cfg(target_os = "macos")'.dependencies.security-framework] +version = "2.11.1" +features = ["OSX_10_12"] + +[target.'cfg(target_os = "macos")'.dependencies.security-framework-sys] +version = "2.12.1" +features = ["OSX_10_12"] + +[target.'cfg(target_os = "windows")'.dependencies.widestring] +version = "1.1.0" + +[target.'cfg(target_os = "windows")'.dependencies.windows-sys] +version = "0.59.0" +features = [ + "Win32_Foundation", + "Win32_Security_Cryptography", +] diff --git a/3rdparty/apple-codesign-0.29.0/Cargo.toml.orig b/3rdparty/apple-codesign-0.29.0/Cargo.toml.orig new file mode 100644 index 00000000..cc851f92 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/Cargo.toml.orig @@ -0,0 +1,131 @@ +[package] +name = "apple-codesign" +version = "0.29.0" +authors = ["Gregory Szorc "] +edition = "2021" +rust-version = "1.81" +license = "MPL-2.0" +description = "Pure Rust interface to code signing on Apple platforms" +keywords = ["apple", "macos", "codesign"] +homepage = "https://github.com/indygreg/apple-platform-rs" +repository = "https://github.com/indygreg/apple-platform-rs.git" +readme = "README.md" + +[[bin]] +name = "rcodesign" +path = "src/main.rs" + +[dependencies] +anyhow = "1.0.93" +aws-config = { version = "1.5.10", optional = true } +aws-sdk-s3 = { version = "1.63.0", optional = true } +aws-smithy-http = { version = "0.60.11", optional = true } +aws-smithy-types = { version = "1.2.9", optional = true } +base64 = "0.22.1" +bcder = "0.7.4" +bitflags = "2.6.0" +bytes = "1.9.0" +clap = { version = "4.5.21", features = ["derive"] } +chrono = "0.4.38" +cryptographic-message-syntax = "0.27.0" +der = { version = "0.7.9", features = ["alloc"] } +dialoguer = "0.11.0" +difference = "2.0.0" +digest = "0.10.7" +dirs = "5.0.1" +elliptic-curve = { version = "0.13.8", features = ["arithmetic", "pkcs8"] } +env_logger = "0.11.5" +figment = { version = "0.10.19", features = ["env", "toml"] } +filetime = "0.2.25" +glob = "0.3.1" +goblin = "0.9.2" +hex = "0.4.3" +log = "0.4.22" +md-5 = "0.10.6" +minicbor = { version = "0.25.1", features = ["derive", "std"] } +num-traits = "0.2.19" +object = { version = "0.36.5", features = ["write"] } +oid-registry = "0.7.1" +once_cell = "1.20.2" +p12 = "0.6.3" +p256 = { version = "0.13.2", default-features = false, features = ["arithmetic", "pkcs8", "std"] } +pem = "3.0.4" +pkcs1 = { version = "0.7.5", features = ["alloc", "std", "pkcs8"] } +pkcs8 = { version = "0.10.2", features = ["alloc", "std"] } +plist = "1.7.0" +rand = "0.8.5" +rasn = "0.20.2" +rayon = "1.10.0" +regex = "1.11.1" +reqwest = { version = "0.12.9", default-features = false, features = ["blocking", "http2", "json", "rustls-tls-native-roots"] } +ring = "0.17.8" +rsa = "0.9.7" +scroll = "0.12.0" +sha2 = "0.10.8" +semver = "1.0.23" +serde = { version = "1.0.215", features = ["derive"] } +serde_json = "1.0.133" +serde_yaml = "0.9.34" +signature = { version = "2.2.0", features = ["std"] } +simple-file-manifest = "0.11.0" +spake2 = "0.4.0" +spki = { version = "0.7.3", features = ["pem"] } +subtle = "2.6.1" +tempfile = "3.14.0" +thiserror = "2.0.3" +tokio = { version = "1.41.1", features = ["rt"] } +tungstenite = { version = "0.24.0", features = ["rustls-tls-native-roots"] } +uuid = { version = "1.11.0", features = ["v4"] } +walkdir = "2.5.0" +x509 = "0.2.0" +x509-certificate = "0.24.0" +xml-rs = "0.8.23" +yasna = "0.5.2" +yubikey = { version = "0.8.0", optional = true, features = ["untested"] } +zeroize = { version = "1.8.1", features = ["zeroize_derive"] } +zip = { version = "2.2.1", default-features = false, features = ["deflate"] } +zip_structs = "0.2.1" + +[dependencies.app-store-connect] +path = "../app-store-connect" +version = "0.7.0" +optional = true + +[dependencies.apple-bundles] +path = "../apple-bundles" +version = "0.21.0" + +[dependencies.apple-flat-package] +path = "../apple-flat-package" +version = "0.20.0" + +[dependencies.apple-xar] +path = "../apple-xar" +version = "0.20.0" + +[target.'cfg(target_os = "macos")'.dependencies] +security-framework = { version = "2.11.1", features = ["OSX_10_12"] } +security-framework-sys = { version = "2.12.1", features = ["OSX_10_12"] } + +[target.'cfg(target_os = "windows")'.dependencies] +widestring = { version = "1.1.0" } +windows-sys = { version = "0.59.0", features = ["Win32_Foundation", "Win32_Security_Cryptography"] } + +[dev-dependencies] +flate2 = "1.0.35" +indoc = "2.0.5" +simple-file-manifest = "0.11.0" +tar = "0.4.43" +trycmd-indygreg-fork = "0.14.20" +zip = { version = "2.2.1", default-features = false } + +[features] +default = ["notarize"] +notarize = [ + "app-store-connect", + "aws-config", + "aws-sdk-s3", + "aws-smithy-http", + "aws-smithy-types", +] +smartcard = ["yubikey"] diff --git a/3rdparty/apple-codesign-0.29.0/LICENSE b/3rdparty/apple-codesign-0.29.0/LICENSE new file mode 100644 index 00000000..d0a1fa14 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/3rdparty/apple-codesign-0.29.0/README.md b/3rdparty/apple-codesign-0.29.0/README.md new file mode 100644 index 00000000..3eed2f3b --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/README.md @@ -0,0 +1,39 @@ +# apple-codesign + +`apple-codesign` is a crate implementing functionality related to code signing +on Apple platforms. + +All functionality is implemented in pure Rust and doesn't require any 3rd party +or proprietary software nor do we require running on Apple platforms. + +We believe this crate provides the most comprehensive implementation of Apple +code signing outside the canonical Apple tools. We have support for the following +features: + +* Signing Mach-O binaries (the executable file format on Apple operating systems). +* Signing, notarizing, and stapling directory bundles (e.g. `.app` directories). +* Signing, notarizing, and stapling XAR archives / `.pkg` installers. +* Signing, notarizing, and stapling DMG disk images. + +What this all means is that you can sign, notarize, and release Apple software +from anywhere you can get the Rust crate to compile. Linux, Windows, and macOS +are officially supported by other operating systems (like BSDs) should work as +well. + +See the crate documentation at https://docs.rs/apple-codesign/latest/apple_codesign/ +and the end-user documentation at +https://gregoryszorc.com/docs/apple-codesign/main/ for more. + +# `rcodesign` CLI + +This crate defines an `rcodesign` binary which provides a CLI interface to +some of the crate's capabilities. To install: + +```bash +# From a Git checkout +$ cargo run --bin rcodesign -- --help +$ cargo install --bin rcodesign + +# Remote install. +$ cargo install --git https://github.com/indygreg/apple-platform-rs --branch main --bin rcodesign apple-codesign +``` diff --git a/3rdparty/apple-codesign-0.29.0/docs/Makefile b/3rdparty/apple-codesign-0.29.0/docs/Makefile new file mode 100644 index 00000000..fd652946 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= -W -n +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/3rdparty/apple-codesign-0.29.0/docs/apple_codesign.rst b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign.rst new file mode 100644 index 00000000..8cc263aa --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign.rst @@ -0,0 +1,70 @@ +.. _apple_codesign: + +================== +Apple Code Signing +================== + +The ``apple-codesign`` Rust crate and its corresponding ``rcodesign`` CLI +tool implement code signing for Apple platforms. + +We believe this crate provides the most comprehensive implementation of Apple +code signing outside the canonical Apple tools. We have support for the following +features: + +* Signing Mach-O binaries (the executable file format on Apple operating systems). +* Signing, notarizing, and stapling directory bundles (e.g. ``.app`` directories). +* Signing, notarizing, and stapling XAR archives / ``.pkg`` installers. +* Signing, notarizing, and stapling disk images / ``.dmg`` files. +* Notarizing zip files. + +**What this all means is that you can sign, notarize, and release Apple software +from non-Apple operating systems (like Linux, Windows, and BSDs) without needing +access to proprietary Apple software!** + +Other features include: + +* Built-in support for using :ref:`smart cards ` (e.g. + YubiKeys) for signing and key/certificate management. +* A *remote signing* mode that enables you to delegate just the low-level + cryptographic signature generation to a remote machine. This allows you to + do things like have a CI job initiate signing but use a YubiKey on a remote + machine to create cryptographic signatures. See + :ref:`apple_codesign_remote_signing` for more. +* Certificate Signing Request (CSR) support to enable arbitrary private keys + (including those generated on smart card devices) to be easily exchanged for + Apple-issued code signing certificates. +* Support for dumping and diffing data structures related to Apple code + signatures. +* Awareness of Apple's public PKI infrastructure, including CA certificates + and custom X.509 extensions and OIDs used by Apple. +* Documentation and code that are likely a treasure trove for others wanting + to learn and experiment with Apple code signing. + +Canonical project links: + +* Source code: https://github.com/indygreg/apple-platform-rs/tree/main/apple-codesign +* Documentation https://gregoryszorc.com/docs/apple-codesign/ +* Rust crate: https://crates.io/crates/apple-codesign +* Changelog: https://github.com/indygreg/apple-platform-rs/blob/main/apple-codesign/CHANGELOG.rst +* Bugs and feature requests: https://github.com/indygreg/apple-platform-rs/issues?q=is%3Aopen+is%3Aissue+label%3Aapple-codesign + +While this project is developed inside a larger monorepository, it is designed +to be used as a standalone project. + +.. toctree:: + :maxdepth: 2 + + apple_codesign_getting_started + apple_codesign_rcodesign + apple_codesign_certificate_management + apple_codesign_smartcard + apple_codesign_github_actions + apple_codesign_concepts + apple_codesign_quirks + apple_codesign_debugging + apple_codesign_remote_signing + apple_codesign_remote_signing_protocol + apple_codesign_remote_signing_design + apple_codesign_gatekeeper + apple_codesign_custom_assessment_policies + apple_codesign_developer_guide diff --git a/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_actions_initiator_output.png b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_actions_initiator_output.png new file mode 100755 index 00000000..692fd4ec Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_actions_initiator_output.png differ diff --git a/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_actions_signer_output.png b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_actions_signer_output.png new file mode 100755 index 00000000..eb4ba58c Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_actions_signer_output.png differ diff --git a/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_actions_sjs_join.png b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_actions_sjs_join.png new file mode 100755 index 00000000..83ac1be7 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_actions_sjs_join.png differ diff --git a/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_certificate_management.rst b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_certificate_management.rst new file mode 100644 index 00000000..0d612479 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_certificate_management.rst @@ -0,0 +1,255 @@ +.. _apple_codesign_certificate_management: + +================================== +Managing Code Signing Certificates +================================== + +In order to add cryptographic signatures using this tool, you'll need to use +a :ref:`apple_codesign_code_signing_certificate`. (Follow the link for what +that means.) + +In order to perform code signing in a way that is recognized and trusted by Apple +operating systems, you will need to obtain a code signing certificate that is +signed/issued by Apple. This requires joining the +`Apple Developer Program `_, which has an +annual membership fee. + +Once you are a member, there are various ways to generate and manage your +certificates. But first, a primer about flavors of Apple code signing +certificates. + +Apple Code Signing Certificate Flavors +====================================== + +Apple issues different types/flavors of code signing certificates. Each one is +used to sign a different class of software. + +If you are logged into your Apple Developer account, you can see Apple's +description for these at https://developer.apple.com/account/resources/certificates/add. +Here's our concise definitions: + +*Apple Development* + Sign applications for Apple operating systems that aren't distributed publicly. + +*Apple Distribution* + Sign applications for submission to the App Store or for Ad Hoc distribution. + +*iOS App Development* + Legacy version of *Apple Development* just for iOS apps. (We think.) + +*iOS Distribution* + Legacy version of *Apple Distribution* just for iOS apps. (We think.) + +*Mac Development* + Legacy version of *Apple Development* just for macOS apps. (We think.) + +*Mac App Distribution* + Sign macOS applications and configure a Distribution Provisioning Profile + for distribution through Mac App Store. + +*Mac Installer Distribution* + Sign package installers (e.g. ``.pkg`` files) which will be distributed via the + Mac App Store. + +*Developer ID Installer* + Sign package installers (e.g. ``.pkg`` files) which will be distributed outside + the Mac App Store. i.e. if users fetch your installer via your website, you sign + with this. + +*Developer ID Application* + Sign applications which will be distributed outside the Mac App Store. Used for + signing Mach-O binaries, ``.app`` bundles, and ``.dmg`` files. + +Essentially, if you are distributing macOS software to end-users via non-Apple +channels like your website, you need *Developer ID Application* and/or *Developer ID +Installer*. + +If you are distributing via Apple's App stores, you need *Apple Distribution* or one +of the other types having *Distribution* in the name. + +.. tip:: + + The ``rcodesign analyze-certificate`` command can be used to print information + about Apple code signing certificates. Look for a line with ``Certificate Profile`` + in its output to see which flavor of certificate this software thinks it is. + +Generating Certificates with Xcode +================================== + +Using Xcode from macOS is probably the easiest way to create and manage +your certificates as Xcode has built-in UI to facilitate this. + +Apple keeps thorough +`documentation about how to do this `_. +Please follow Apple's documentation to generate a certificate. + +Obtaining a Certificate via a Certificate Signing Request +========================================================= + +You can obtain a code signing certificate by uploading a *Certificate Signing +Request (CSR)* to Apple. Essentially, you generate a CSR, send it to Apple, +and Apple will issue a new code signing certificate which you can download. + +A CSR is produced by creating a cryptographic signature (using a *private +key*) over a small set of metadata describing the *private key* for which +a certificate shall be issued. + +In order to generate a CSR, you need a *private key*. As of April 2022, Apple +appears to require the use of RSA 2048 private keys. + +If you have access to macOS, the easiest way to generate a private key and +CSR is to use ``Keychain Access`` using the +`procedure outlined here `_. + +If you want to generate your own CSR using ``rcodesign``, follow instructions +in one of the following sections. If you already have a CSR, skip ahead to +:ref:`apple_codesign_exchange_csr`. + +.. _apple_codesign_generate_csr_from_p12: + +Generating a CSR from a ``.p12`` / ``.pfx`` File +------------------------------------------------ + +If you've exported a ``.p12`` / ``.pfx`` / PKCS#12 file from +``KeyChain Access.app`` or some other source, you can use ``rcodesign`` +to turn it into a CSR:: + + rcodesign generate-certificate-signing-request --p12-file cert.p12 --p12-password my-password + +This command will print the CSR to stdout. e.g.:: + + -----BEGIN CERTIFICATE REQUEST----- + MIHeMIGDAgEAMCExHzAdBgNVBAMMFkFwcGxlIENvZGUgU2lnbmluZyBDU1IwWTAT + BgcqhkjOPQIBBggqhkjOPQMBBwNCAAQxluBlPIv/HgBDz0O3GLPhhna/NJU7menq + GzUc9sZFOgZ7XmpR9vQTxHPEyg5D6huBapVQZsDG9IgAXjvSOmimoAAwDAYIKoZI + zj0EAwIFAANIADBFAiEAoZpbfrlm7HgQXByfwuoPt7/V+QM7DCIILcTKCBrkIZUC + IEIp8yA9bSg7bM9XJl8bgFesTjermlSYQI/2JY834/z7 + -----END CERTIFICATE REQUEST----- + +You probably want to use ``--csr-pem-file`` to write that to a file automatically:: + + rcodesign generate-certificate-signing-request --p12-file cert.p12 --p12-password my-password --csr-pem-file csr.pem + +Generating a CSR From a YubiKey or Other SmartCard Device +--------------------------------------------------------- + +See the instructions at :ref:`apple_codesign_smartcard_key_generation`. + +.. _apple_codesign_generate_rsa_key_and_csr: + +Generating an RSA Private Key and CSR +------------------------------------- + +To generate an RSA 2048 private key using OpenSSL:: + + openssl genrsa -out private.pem 2048 + +.. warning:: + + The RSA private key will be in plain text on your filesystem. This is not + very secure! + +Then once you have a private key, we can generate a CSR using ``rcodesign``:: + + rcodesign generate-certificate-signing-request --pem-file private.pem + +Like the instructions above, you probably want to use ``--csr-pem-file`` to save the +CSR data to a file for submission to Apple. + +.. _apple_codesign_exchange_csr: + +Exchanging a CSR for a Code Signing Certificate +----------------------------------------------- + +Once you have a CSR file, you can attempt to exchange it for a code signing +certificate. + +1. Go to https://developer.apple.com/account/resources/certificates/add (you must be + logged into Apple's website) +2. Select the certificate *flavor* you want to issue. +3. Click ``Continue`` to advance to the next form. +4. Select the ``G2 Sub-CA (Xcode 11.4.1 or later)`` *Profile Type* (we support it). +5. Choose the file containing your CSR. +6. Click ``Continue``. +7. If all goes according to plan, you should see a page saying ``Download Your + Certificate``. +8. Click the ``Download`` button. +9. Save the certificate somewhere. (The file content is likely not sensitive and + doesn't need to be kept secret because this content will be copied to everything + you sign with it!) + +At this point, you have both a *private key* and a *public certificate*: you can +sign Apple software! + +Exporting a Code Signing Certificate to a File +============================================== + +``rcodesign`` supports consuming code signing certificates from multiple +sources, including hardware devices. But sometimes it is desirable to have +your code signing certificate exist as a file. + +Use the instructions in one of the following sections to export a code signing +certificate. + +.. danger:: + + It is generally accepted that private keys stored in files are less + secure than stored in special operating system enclaves like keychains. + This is because the operating system has protections around accessing + the private keys and these protections are often much stronger than + those on a file on the filesystem. + + This tool has support for using certificates / keys directly from + macOS keychains. So exporting to a file is not always necessary. + +Using Keychain Access +--------------------- + +(macOS) + +1. Open the ``Keychain Access`` application. +2. Find the certificate you want to export and command click or right click on it. +3. Select the ``Export`` option. +4. Choose the ``Personal Information Exchange (.p12)`` format and select a + file destination. +5. Enter a password used to protect the contents of the certificate. +6. If prompted to enter your system password to unlock your keychain, do so. + +The exported certificate is in the PKCS#12 / PFX / p12 file format. Command +arguments with these labels in the same can be used to interact with the +exported certificate. + +Using Xcode +----------- + +(macOS) + +See `Apple's Xcode documentation `_. + +Using ``security`` +------------------ + +(macOS) + +1. Run ``security find-identity`` to locate certificates available for export. +2. Run ``security export -t identities -f pkcs12 -o keys.p12`` + +If you have multiple identifies (which is common), ``security export`` will export +all of them. ``security`` doesn't seem to have a command to export just a single +certificate pair. You will need to invoke some ``openssl`` command to extract +just the certificate you care about. Please contribute back a fix for this +documentation once you figure it out! + +Using a Self-Signed Certificate +=============================== + +If you want to cut some corners and play around with certificates not +signed by Apple, you can run ``rcodesign generate-self-signed-certificate`` +to generate a self-signed code signing certificate. + +This command will include special attributes in the certificate that indicate +compatibility with Apple code signing. However, since the certificate isn't +signed by Apple, its signatures won't confer the same trust that Apple signed +certificates would. + +These certificates can be useful for debugging and testing. diff --git a/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_concepts.rst b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_concepts.rst new file mode 100644 index 00000000..635a4587 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_concepts.rst @@ -0,0 +1,158 @@ +.. _apple_codesign_concepts: + +======== +Concepts +======== + +Code signing on Apple platforms is complex and has many parts. This +document aims to shed some light on things. + +Cryptographic Signatures +======================== + +At the heart of code signing is the use of cryptographic signatures. + +The Wikipedia article on +`digital signatures `_ explains +the concept in far more detail than we care to go into. + +Essentially, mathematics is used to prove that an entity in possession of a +secret *key* digitally attested to the existence of some *signed* entity. + +More concretely, an X.509 code signing certificate can be proved to have +signed some piece of software by inspecting the cryptographic signature it +produced. + +Apple's cryptographic signatures use RFC 5652 / Cryptographic Message Syntax +(CMS) for representing signatures. This standardized format is used outside +the Apple ecosystem and libraries and tools like OpenSSL are capable of +interfacing with it. + +Code Signing +============ + +*Code signing* (or just *signing*) is the mechanism of producing (and then +attaching) a signature to some entity. + +Typically signing entails producing a cryptographic signature using a code +signing certificate. However, Mach-O files (the binary file format for +Apple platforms) has a concept of *ad-hoc* signing where the binary has +data structures describing the content of the binary but without the +cryptographic signature present. + +Notarization +============ + +*Notarization* is the term Apple gives to the process of uploading an asset +to Apple for inspection. + +In order to help safeguard and control their software ecosystems, Apple +imposes requirements that applications and installers be inspected by Apple +before they are allowed to run on Apple operating systems - either at all +or without scary warning signs. + +When you notarize software, you are essentially asking for Apple's blessing +to distribute that software. If Apple's systems are appeased, they will +issue a *notarization ticket*. + +Notarization Ticket +=================== + +A *notarization ticket* is a blob of data that essentially proves that Apple +notarized a piece of software. + +The exact format and content of *notarization tickets* is not well known. But +they do contain some DER-encoded ASN.1 with data structures that common appear +in X.509 certificates. All that matters is that Apple's operating systems know +how to read and validate a notarization ticket. + +Stapling +======== + +*Stapling* is the term Apple gives to the process of attaching a *notarization +ticket* to some entity. It is literally just fetching a *notarization ticket* +from Apple's servers and then making that ticket available on the entity that +was notarized. + +You can think of notarization and stapling as Apple-issued cryptographic +signatures. It establishes a chain of trust between some entity to you +that also had to be inspected by Apple first. + +Mach-O Binaries +=============== + +`Mach-O `_ is the binary executable +file format used on Apple operating systems. + +When you run an executable like ``/usr/bin/zsh`` on macOS, you are running +a Mach-O file. + +Mach-O binaries are either *thin* or *fat*. A *thin* Mach-O contains code +for a single architecture, like x86-64 or aarch64 / arm64. A *fat* or +*universal* binary contains code for multiple architectures. At run-time, +the operating system will decide which one to execute. + +Bundles +======= + +`Bundles `_ +are a filesystem based mechanism for encapsulating code and resources. + +On macOS, you commonly encounter bundles as ``.app`` and ``.framework`` +directories in ``/Applications`` and ``/System/Library/Frameworks``. + +Bundles are essentially a well-defined set of files that the operating +system knows how to interact with. For example, macOS knows that to +execute an ``.app`` bundle it should look for a ``Contents/Info.plist`` +to resolve basic application metadata, such as the name of the main +binary for the bundle, which resides in ``Contents/MacOS/`` within the +bundle. + +DMGs / Disk Images +================== + +`Apple Disk Images `_ are a +self-contained file format for holding filesystems. Think of DMGs +as standalone hard drives that Apple operating systems can recognize. + +DMGs are often used to distribute macOS applications. + +XARs / Flat Packages / ``.pkg`` Installers +========================================== + +*Flat packages* is a mechanism for installing software. + +They take the form of ``.pkg`` files, which are actually XAR archives +(a tar-like format for storing content for multiple files within a single +file). + +.. _apple_codesign_code_signing_certificate: + +Code Signing Certificate +======================== + +A code signing certificate is used to produce cryptographic signatures over +some signed entity. + +A code signing certificate consists of a private/secret key (essentially a bunch +of large numbers or parameters) and a public certificate which describes it. + +Code signing certificates are X.509 certificates. X.509 certificates are the +same technology used to secure communication with https:// websites. However, +the certificates are used for signing content instead of encrypting it. + +The X.509 public certificate contains a bunch of metadata describing the +certificate. This includes the name of the person or entity it belongs to, +a date range for when it is valid, and a cryptographic signature attesting +to its origination. + +Apple's operating systems look for special metadata on code signing +certificates to authenticate and trust them. There are special properties +on certificates indicating what Apple software distribution they are allowed +to perform. For example, a ``Developer ID Application`` certificate is required +for signed Mach-O binaries, bundles, and DMG files to be trusted and a +``Developer ID Installer`` certificate is required to sign ``.pkg`` installers +in order for them to be trusted. + +In addition, different Apple code signing certificates are cryptographically +signed by different Apple Certificate Authorities (CAs). diff --git a/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_custom_assessment_policies.rst b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_custom_assessment_policies.rst new file mode 100644 index 00000000..b3b929c7 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_custom_assessment_policies.rst @@ -0,0 +1,160 @@ +.. _apple_codesign_custom_assessment_policies: + +================================================================ +Selectively Bypassing Gatekeeper with Custom Assessment Policies +================================================================ + +By default, Apple locks down their operating systems such that the default +assessment policies enforced by Gatekeeper restrict what can be run. The +restrictions vary by operating system (iOS is more locked down than macOS for +example). + +On macOS, it is possible to change the system assessment policies via +the ``spctl`` tool. By injecting your own rules, you can allow binaries +through meeting criteria expressible via *code requirements language +expressions*. This allows you to allow binaries having: + +* A specific *code directory hash* (uniquely identifies the binary). +* A specific code signing certificate identified by its certificate hash. +* Any code signing certificate whose trust/signing chain leads to a trusted + certificate. +* Any code signing certificate signed by a certificate containing a + certain X.509 extension OID. +* A code signing certificate with specific values in its subject field. +* And many more possibilities. See + `Apple's docs `_ + on the requirements language for more possibilities. + +Defining custom rules is possible via the under-documented +``spctl --add --requirement`` mode. In this mode, you can register a code +requirements expression into the system database for Gatekeeper to +utilize. The following sections give some examples of this. + +Verifying Assessment Policies +============================= + +The sections below document how to define custom assessment policies +to allow execution of binaries/installers/etc signed by certificates +that aren't normally supported. + +When doing this, you probably want a way to verify things work as +expected. + +The ``spctl --assess`` mode puts ``spctl`` in *assessment mode* and tells you +what verdict Gatekeeper would render. e.g.:: + + $ spctl --assess --type execute -vv /Applications/Firefox.app + /Applications/Firefox.app: accepted + source=Notarized Developer ID + +Do note that this only works on app bundles (not standalone executable +binaries)! If you run ``spctl --assess`` on a standalone executable, you +get an error:: + + $ spctl --assess -vv /usr/bin/ssh + /usr/bin/ssh: rejected (the code is valid but does not seem to be an app) + origin=Software Signing + +In addition, macOS uses the ``com.apple.quarantine`` extended file attribute +to *quarantine* files and prevent them from running via the graphical UI. +It can sometimes be handy to add this attribute back to a file to simulate +a fresh quarantine. You can do this by running a command like the following:: + + xattr -w com.apple.quarantine "0001;$(printf %x $(date +%s));manual;$(/usr/bin/uuidgen)" /path/to/file + +(This extended attribute isn't added to files downloaded by tools like ``curl`` +or ``wget`` which is why you can execute binaries obtained via these tools but +can't run the same binary downloaded via a web browser.) + +Allowing Execution of Binaries Signed by a Specific Certificate +=============================================================== + +Say you have a single code signing certificate and want to be able to +run all binaries signed by that certificate. We can construct a +*code requirement expression* that refers to this specific certificate. + +The most reliable way to specify a single certificate is via a +digest of its content. Assuming no two certificates have the same +digest, this uniquely identifies a certificate. + +You can use ``rcodesign analyze-certificate`` to locate a certificate's +content digest.:: + + rcodesign analyze-certificate --pem-file path/to/cert | grep fingerprint + SHA-1 fingerprint: 0b724bcd713c9f3691b0a8b0926ae0ecf9e7edd8 + SHA-256 fingerprint: ac5c4b5936677942e017bca1570aaa9e763674c4b66709231b15118e5842aeca + +The *code requirement* language only supports SHA-1 hashes. So we +construct our expression referring to this certificate as +``certificate leaf H"0b724bcd713c9f3691b0a8b0926ae0ecf9e7edd8"``. + +Now, we define an assessment rule to allow execution of binaries +signed with this certificate:: + + sudo spctl --add --type execute --label 'My Cert' --requirement \ + 'certificate leaf H"0b724bcd713c9f3691b0a8b0926ae0ecf9e7edd8"' + +Now Gatekeeper should allow execution of all binaries signed with this +exact code signing certificate! + +If the signing certificate hash is registered in the system assessment +policy database, there is no need to register the certificate in a +*keychain* or mark that certificate as *trusted* in a keychain. The signing +certificate also does not need to chain back to an Apple certificate. +And since the requirement expression doesn't say ``and notarized``, binaries +don't need to be notarized by Apple either. **This effectively allows you +to sidestep the default requirement that binaries be signed and notarized +by certificates that Apple is aware of.** Congratulations, you've just +escaped Apple's walled garden (at your own risk of course). + +Do note that for files with the ``com.apple.quarantine`` extended attribute, +you may see a dialog the first time you run this file. You can prevent that +by removing the extended attribute via +``xattr -d com.apple.quarantine /path/to/file``. + +Allowing Execution of Binaries Signed by a Trusted CA +===================================================== + +Say you are an enterprise or distributed organization and want to have +multiple code signing certificates. Using the approach in the section +above you could individually register each code signing certificate you +want to allow. However, the number of certificates can quickly grow and +become unmanageable. + +To solve this problem, you can employ the strategy that Apple itself uses +for code signing certificates associated with Developer ID accounts: trust +code signing certificates themselves issued/signed by a trusted certificate +authority (CA). + +To do this, we'll again craft a *code requirement expression* referring to +our trusted CA certificate. + +This looks very similar to above except we change the position of the +trusted certificate:: + + sudo spctl --add --type execute --label 'My Trusted CA' --requirement \ + 'certificate 1 H"0b724bcd713c9f3691b0a8b0926ae0ecf9e7edd8"' + +That ``certificate 1`` says to apply to the certificate that signed the +certificate that produced the code signature. By trusting the CA certificate, +you implicitly trust all certificates signed by that CA certificate. + +Note that if you use a custom CA for signing code signing certificates, +you'll probably want to follow some best practices for running your own +Public Key Infrastructure (PKI) like publishing a Certificate Revocation List +(CRL). This is a complex topic outside the scope of this documentation. Ask +someone with *Security* in their job title for assistance. + +For CA certificates issuing/signing code signing certificates, you'll +want to enable a few X.509 certificate extensions: + +* Key Usage (``2.5.29.15``): *Digital Signature* and *Key Cert Sign* +* Basic Constraints (``2.5.29.19``): CA=yes +* Extended Key Usage (``2.5.29.37``): Code Signing (``1.3.6.1.5.5.7.3.3``); critical=true + +You can create CA certificates in the ``Keychain Access`` macOS application. +If you create CA certificates another way, you may want to compare certificate +extensions and other fields against those produced via ``Keychain Access`` to +make sure they align. It is unknown how much Apple's operating systems +enforce requirements on the X.509 certificates. But it is a good idea to +keep things as similar as possible. diff --git a/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_debugging.rst b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_debugging.rst new file mode 100644 index 00000000..da898030 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_debugging.rst @@ -0,0 +1,49 @@ +.. _apple_codesign_debugging: + +================================ +How to Debug and Report Problems +================================ + +Apple code signing is complex and there will be cases where this tool +behaves differently from Apple's, possibly to the point where Apple rejects +the output of this tool. + +.. important:: + + If Apple software rejects the output of this tool, we consider that a bug. + We encourage end-users to report these bugs to the + `GitHub issue tracker `_. + +Commands to Print Signature Info +================================ + +The ``rcodesign print-signature-info`` command can be used to dump YAML +describing any signable file entity. Just point it at a Mach-O, bundle, DMG, +or ``.pkg`` installer and it will tell you what it knows about the entity. + +The ``rcodesign diff-signatures`` command will internally execute +``print-signature-info`` against 2 paths and print the differences between them. + +``rcodesign diff-signatures`` is exceptionally useful at understanding +differences in behavior between this tool and Apple's. If Apple is rejecting +the output of this tool, comparing the output of the same operation with Apple's +tooling against this tool's is a good way to find the source of the problem. + +Reporting Actionable Bugs +========================= + +Please include the following in bug reports to improve chances for action: + +* The released version or Git commit that this tool was built from. +* The command line used. +* The full output of the command. +* The output of ``rcodesign diff-signatures`` comparing similar operations + between Apple's tooling and ours. +* A copy of the entity you were attempting to sign. +* Text copy or screenshot of error from Apple tooling indicating what failed. + +It is understandable that some people may not desire to file publish issue +reports or submit a copy of their application to be seen by the world. If +you send a polite email to gregory.szorc@gmail.com with ``apple-codesign`` or +``rcodesign`` in the subject line along with more private/sensitive details, +support can be given over email. diff --git a/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_developer_guide.rst b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_developer_guide.rst new file mode 100644 index 00000000..dfcc710f --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_developer_guide.rst @@ -0,0 +1,46 @@ +.. _apple_codesign_developer_guide: + +=============== +Developer Guide +=============== + +If you are familiar with Rust, hacking on the ``apple-codesign`` crate should +feel like any other Rust crate. But there are a few things to watch out for: + +* The ``smartcard`` crate feature isn't enabled by default because it pulls in + library dependencies on Linux that aren't always present. We recommend testing + with ``--all-features`` to ensure all code in the crate is exercised. +* There is some conditional code when running on macOS. We've tried to isolate + that code to the ``macos`` file/module so changes are more obvious. +* When running tests on macOS, some tests call out to Apple tools (like + ``csreq``). Non-standard system installs or 3rd party tools on PATH may + confuse tests. (We generally consider these bugs in the tests and if you + see a test failure due to a runtime environment issue, please file a bug or + send a patch to fix.) + +Desire for Determinism and Reproducibility +========================================== + +As much code and behavior should be deterministic and bit-for-bit reproducible as +possible. There are some obvious cases where bits will disagree (such as time-stamp +tokens from remote timestamp servers). But we strive for the same data inputs to +produce the same output as much as possible. Unless it can't be avoided due to the +fundamental nature of an operation (such as a random/remote operation), we consider +non-determinism to be a bug. + +Desire for Compliance with Apple Tooling +======================================== + +We bias towards Apple's official tooling to define behavior. + +Generally, if our implementation behaves differently from Apple's official +tooling in a meaningful way (definition of *meaningful* is subject to +interpretation), the default behavior should be to treat this as a bug. +If deviation is desirable, the justifications should be documented. Ideally +in this documentation tree or in inline code comments. But commit messages +are also fine. The important thing is we leave a breadcrumb trail of known +deviations and why they exist. + +There are plenty of valid reasons to deviate from behavior of Apple's tooling. +We purposefully let present and future project maintainers interpret *valid +reasons* as they want. diff --git a/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_gatekeeper.rst b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_gatekeeper.rst new file mode 100644 index 00000000..ac40f09c --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_gatekeeper.rst @@ -0,0 +1,151 @@ +.. _apple_codesign_gatekeeper: + +====================== +A Primer on Gatekeeper +====================== + +*Gatekeeper* is the name Apple gives to a set of technologies that enforce +application execution policies at the operating system level. Essentially, +Gatekeeper answers the question *is this software allowed to run*. + +When Gatekeeper runs, it performs a *security assessment* against the +binary and the currently configured system policies from the system policy +database (see ``man syspolicyd``). If the binary fails to meet the requirements, +Gatekeeper prevents the binary from running. + +The ``spctl`` Tool +================== + +The ``spctl`` program distributed with macOS allows you to query and +manipulate the assessment policies. + +If you run ``sudo spctl --list``, it will print a list of rules. e.g.:: + + $ sudo spctl --list + 8[Apple System] P20 allow lsopen + anchor apple + 3[Apple System] P20 allow execute + anchor apple + 2[Apple Installer] P20 allow install + anchor apple generic and certificate 1[subject.CN] = "Apple Software Update Certification Authority" + 17[Testflight] P10 allow execute + anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.1] exists and certificate leaf[field.1.2.840.113635.100.6.1.25.1] exists + 10[Mac App Store] P10 allow install + anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.10] exists + 5[Mac App Store] P10 allow install + anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.10] exists + 4[Mac App Store] P10 allow execute + anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.9] exists + 16[Notarized Developer ID] P5 allow lsopen + anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists and certificate leaf[field.1.2.840.113635.100.6.1.13] exists and notarized + 12[Notarized Developer ID] P5 allow install + anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists and (certificate leaf[field.1.2.840.113635.100.6.1.14] or certificate leaf[field.1.2.840.113635.100.6.1.13]) and notarized + 11[Notarized Developer ID] P5 allow execute + anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists and certificate leaf[field.1.2.840.113635.100.6.1.13] exists and notarized + 9[Developer ID] P4 allow lsopen + anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists and certificate leaf[field.1.2.840.113635.100.6.1.13] exists and legacy + 7[Developer ID] P4 allow install + anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists and (certificate leaf[field.1.2.840.113635.100.6.1.14] or certificate leaf[field.1.2.840.113635.100.6.1.13]) and legacy + 6[Developer ID] P4 allow execute + anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists and certificate leaf[field.1.2.840.113635.100.6.1.13] exists and (certificate leaf[timestamp.1.2.840.113635.100.6.1.33] absent or certificate leaf[timestamp.1.2.840.113635.100.6.1.33] < timestamp "20190408000000Z") + 2718[GKE] P0 allow lsopen [(gke)] + cdhash H"975d9247503b596784dd8a9665fd3ff43eb7722f" + 2717[GKE] P0 allow execute [(gke)] + cdhash H"cf782d6467be86b73a83d86cd6d8c9f87d9d9ce5" + ... + 18[GKE] P0 allow lsopen [(gke)] + cdhash H"cf5f88b3b2ff4d8612aabb915f6d1f712e16b6f2" + 15[Unnotarized Developer ID] P0 deny lsopen + anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists and certificate leaf[field.1.2.840.113635.100.6.1.13] exists + 14[Unnotarized Developer ID] P0 deny install + anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists and (certificate leaf[field.1.2.840.113635.100.6.1.14] or certificate leaf[field.1.2.840.113635.100.6.1.13]) + 13[Unnotarized Developer ID] P0 deny execute + anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists and certificate leaf[field.1.2.840.113635.100.6.1.13] exists and (certificate leaf[timestamp.1.2.840.113635.100.6.1.33] exists and certificate leaf[timestamp.1.2.840.113635.100.6.1.33] >= timestamp "20190408000000Z") + ``` + +The first line of each item identifies the policy. The second line is a +*code requirement language expression*. This is a DSL that compiles to a +binary expression tree for representing a test to perform against a binary. +See ``man csreq`` for more. + +Some of these expressions are pretty straightforward. For example, +the following entry says to allow executing a binary with a code signature +whose *code directory* hash is ``cf782d6467be86b73a83d86cd6d8c9f87d9d9ce5``:: + + 2717[GKE] P0 allow execute [(gke)] + cdhash H"cf782d6467be86b73a83d86cd6d8c9f87d9d9ce5" + +The *code directory* refers to a data structure within the code +signature that contains (among other things) content digests of the binary. The +hash/digest of the code directory itself is effectively a chained digest to the +actual binary content and theoretically a unique way of identifying a binary. So +``cdhash H"cf782d6467be86b73a83d86cd6d8c9f87d9d9ce5"`` is a very convoluted +way of saying *allow this specific binary (specified by its content hash) +to execute*. + +Other rules are more interesting. For example:: + + 11[Notarized Developer ID] P5 allow execute + anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists + and certificate leaf[field.1.2.840.113635.100.6.1.13] exists and notarized + +We see the description (``Notarized Developer ID``) but what does that +expression mean? + +Well, first this expression parses into a tree. We won't attempt to format +the tree here. But essentially the following conditions must ``all`` be true: + +* ``anchor apple generic`` +* ``certificate 1[field.1.2.840.113635.100.6.2.6] exists`` +* ``certificate leaf[field.1.2.840.113635.100.6.1.13] exists`` +* ``notarized`` + +``anchor apple generic`` and ``notarized`` are essentially special expressions +that expand to mean *the certificate signing chain leads back to an Apple +root certificate authority (CA)* and *there is a supplemental code signature +from Apple that can only come from Apple's notarization service*. + +But what about those ``certificate`` expressions? That +``certificate [field.*]`` syntax essentially says *the code signature +certificate at ```` in the certificate chain has an X.509 certificate +extension with OID ``X``* (where ``X`` is a value like ``A.B.C.D.E.F``). + +This is all pretty low level. But essentially X.509 certificates can have +a series of *extensions* that further describe the certificate. Apple code +signing uses these extensions to convey metadata about the certificate. And +since code signing certificates are signed, whoever signed those certificates +is effectively also approving of whatever is conveyed by the extensions +within. + +But what do these extensions actually mean? Running ``rcodesign x509-oids`` +may give us some help:: + + $ rcodesign x509-oids` + ... + Code Signing Certificate Extension OIDs + ... + 1.2.840.113635.100.6.1.13 DeveloperIdApplication + ... + Certificate Authority Certificate Extension OIDs + ... + 1.2.840.113635.100.6.2.6 DeveloperId + +We see ``1.2.840.113635.100.6.2.6`` is the OID of an extension on +certificate authorities indicating they act as the *Apple Developer +ID* certificate authority. We also see that ``1.2.840.113635.100.6.1.13`` +is the OID of an extension saying the certificate acts as a code signing +certificate for *applications* associated with an *Apple Developer ID*. + +So, what this expression translates to is essentially: + +* Trust code signatures whose certificate signing chain leads back to an + Apple CA. +* The signer of the code signing certificate must have the extension that + identifies it as the *Apple Developer ID* certificate authority. +* The code signing certificate itself must have the extension that says + it is an *Apple Developer ID* for use with *application* signing. +* The binary is *notarized*. + +In simple terms, this is saying *allow execution of binaries that +were signed by a Developer ID code signing certificate which was signed +by Apple's Developer ID certificate authority and are also notarized*. diff --git a/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_getting_started.rst b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_getting_started.rst new file mode 100644 index 00000000..52ffc839 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_getting_started.rst @@ -0,0 +1,117 @@ +.. _apple_codesign_getting_started: + +=============== +Getting Started +=============== + +Installing +========== + +Pre-built binaries are published as GitHub Releases. Go to +https://github.com/indygreg/apple-platform-rs/releases and look for the latest +release of ``Apple Codesign``. + +To install the latest release version of the ``rcodesign`` executable using Cargo +(Rust's package manager): + +.. code-block:: bash + + cargo install apple-codesign + +To enable smart card integration (i.e. use a YubiKey for signing): + +.. code-block:: bash + + cargo install --features smartcard apple-codesign + +To compile and run from a Git checkout of its canonical repository (developer mode): + +.. code-block:: bash + + cargo run --bin rcodesign -- --help + +To install from a Git checkout of its canonical repository: + +.. code-block:: bash + + cargo install --bin rcodesign + +To install from the latest commit in the canonical Git repository: + +.. code-block:: bash + + cargo install --git https://github.com/indygreg/apple-platform-rs --branch main rcodesign + +Obtaining a Code Signing Certificate +==================================== + +Follow the instructions at :ref:`apple_codesign_certificate_management` to obtain +a code signing certificate. This is required if signing software for +distribution to other machines. + +If you just want to play around, you can use +``rcodesign generate-self-signed-certificate`` to create a self-signed +certificate. + +.. _apple_codesign_app_store_connect_api_key: + +Obtaining an App Store Connect API Key +====================================== + +To notarize and staple, you'll need an App Store Connect API Key to +authenticate connections to Apple's servers. + +You can generate one at https://appstoreconnect.apple.com/access/api. + +This requires joining the Apple Developer Program, which has an annual +fee. + +See +https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api +for Apple's official documentation on creating these API Keys. + +.. important:: + + For the *Access Role*, ``Developer`` should be sufficient. + + Other roles may or may not work for notarization. + +App Store Connect API Keys have 3 components: + +* An *Issuer ID* (likely a UUID). +* A *Key ID* (an alphanumeric string like ``DEADBEEF42``). +* A PEM encoded ECDSA private key (a file beginning with + ``-----BEGIN PRIVATE KEY-----`` that you can download at most + once when you create an API Key). + +All 3 of these components are required to talk to the App Store Connect +API server. To make management of these keys simpler, we provide the +``encode-app-store-connect-api-key`` command to write out a JSON document +holding all the key info. + +.. important:: + + We highly recommend using our JSON keys created with + ``encode-app-store-connect-api-key`` as it is simpler to manage a single + entity instead of 3. + +You can perform an encode of your key as follows: + +.. code-block:: bash + + rcodesign encode-app-store-connect-api-key -o ~/.appstoreconnect/key.json \ + /path/to/downloaded/private_key + +e.g. + +.. code-block:: bash + + rcodesign encode-app-store-connect-api-key -o ~/.appstoreconnect/key.json \ + 11dda589-8632-49a8-a432-03b5e17fe1d2 DEADBEEF42 ~/Downloads/AuthKey_DEADBEAF42.p8 + +Next Steps +========== + +Once you have a code signing certificate and/or App Store Connect API Key, +read :ref:`apple_codesign_rcodesign` to learn how to sign and/or notarize +software. diff --git a/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_github_actions.rst b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_github_actions.rst new file mode 100644 index 00000000..2a9b29eb --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_github_actions.rst @@ -0,0 +1,125 @@ +.. _apple_codesign_github_actions: + +========================================== +Signing and Notarizing with GitHub Actions +========================================== + +The `indygreg/apple-code-sign-action `_ +GitHub Action provides a relatively turnkey way to sign and notarize using +``rcodesign``. + +Signing +======= + +You will need to make a signing certificate available to GitHub Actions. + +You can either install the signing certificate *locally* in the GitHub Actions +runner/workflow or you can use the :ref:`apple_codesign_remote_signing` feature. + +Local Certificate Signing +------------------------- + +There are multiple ways to make a local code signing certificate available to +GitHub Actions. Each have various security / convenience trade-offs. + +We recommend storing the certificate private key in a GitHub Actions Secret. +Storing the private key this way prevents offline attacks. + +Find the PEM representation of your signing certificate. It will look something +like: + +.. code-block:: + + -----BEGIN PRIVATE KEY----- + MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCkdCzwAgHcNbpH + awCPZISFqL6vPHstX1F9FjjGiOqQZ60xtXMsj1vpfxhpBZwxO/Q3RDn1ogvCluE5 + ... + -----END PRIVATE KEY----- + +Use the instructions at +https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository +to create a secret with this private key content. + +Assuming the secret is named ``PRIVATE_KEY``, you can write the private key to +a file and pass it to the GitHub Action doing something like the following: + +.. code-block:: yaml + + steps: + - name: Write PEM encoded private key data to a file + env: + PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} + run: | + echo $PRIVATE_KEY | tr ' ' '\n' > key.pem + + - name: Code signing + uses: indygreg/apple-code-sign-action@v1 + with: + pem_file: | + key.pem + cert.pem + +Remote Signing +-------------- + +In *remote signing* mode, a remote machine has access to the code signing +certificate so that GitHub Actions never has access to it. This is theoretically +more secure since if GitHub gets hacked, nobody has an offline copy of your +signing certificate! + +See :ref:`apple_codesign_remote_signing_session_agreement` for an overview +of the mechanisms for initiating remote signing. + +We recommend use of public key agreement over shared secrets because it should +be more secure. + +You can even use your code signing certificate's public key as the public key +to use. + +.. code-block:: yaml + + steps: + - name: Code signing + uses: indygreg/apple-code-sign-action@v1 + with: + remote_sign_public_key: | + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt2CB7Q9oBDpA6Pkd4spG + CWF+LbOnJUGkUPeCn7frIv8CT6HMxaCE8KokTuNo8nVqJW9Ocy/oFHO2SiJ0H2EM + FgaWIVgfiJuZKIMwzDzIEtgV48VE9V+9ARaI5JOFm+buivAtlCdTzpUASscIqVb1 + 00Lqyf8oAd679bywsxEyigVTxAFQ+qHFfyk0/D8Z8tg7e+osoXAFoH/E6fdKaUMv + EUwoMvpulvT/+gqAS9qnnYd2ugbHNtjIrD1YK5JF5oi2JePDS37uF4QmuEXGAh3e + DlIRDozAqC0Oeg0zPVuFBFZy1iVy4NS8aYY9NiaKH3EMDVkzz077znw/cJp9+wHZ + WQIDAQAB + +Notarizing +========== + +Notarizing requires you to have an App Store Connect API Key. + +Follow the instructions at :ref:`apple_codesign_app_store_connect_api_key`. + +Assuming you ran ``rcodesign encode-app-store-connect-api-key`` to obtain a +unified JSON file, we recommend copying that JSON data into a GitHub Actions +secret. + +Then simply write out that secret to a file and reference it from the GitHub +Actions config: + +.. code-block:: yaml + + steps: + - name: Write API Key to file + env: + API_KEY: ${{ secrets.APP_STORE_API_KEY }} + run: echo $API_KEY > app_store_key.json + + - name: Notarize + uses: indygreg/apple-code-sign-action@v1 + with: + app_store_connect_api_key_json_file: app_store_key.json + # Remember to enable notarization and to disable signing if you just + # want to notarize. + sign: false + notarize: true + staple: true + input_path: MyApp.app diff --git a/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_quirks.rst b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_quirks.rst new file mode 100644 index 00000000..574171cf --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_quirks.rst @@ -0,0 +1,157 @@ +.. _apple_codesign_quirks: + +============================ +Known Issues and Limitations +============================ + +Apple code signing is complex. While this project strives to provide +all the features and compatibility that Apple's official tooling provides, +we won't always get it right. This document captures some of the areas where +we know we fall short. + +Bundle Handling in General +========================== + +Bundle signing is complex for a few reasons: + +* The types and layouts of bundles are highly varied. Application bundles. + Frameworks. Kernel extensions. macOS flavored vs iOS flavored bundles. The + list goes on. +* Bundles can be nested. +* Signatures in nested bundles often need to propagate to their parent bundle. +* Bundles encapsulate other signable entities, notably Mach-O binaries. + +All this complexity means bundle signing is susceptible to a lot of subtle +bugs and variation from how Apple's tooling does it. + +If you find bugs in bundle signing or have suggestions for improving its +ergonomics, please `file a GitHub issue `_! + +Cannot Sign File Contents of DMGs +================================= + +We support signing DMGs. But we can't recursively inspect the files within +DMGs and sign those. e.g. if a DMG contains a Mach-O binary, we can't +sign that Mach-O by unpacking it from the DMG and writing a new DMG. + +The reason we can't do this is because DMGs contain a nested filesystem +(likely HFS+) and we don't (yet) have a cross-platform mechanism for reading +and writing HFS+ filesystems. + +On macOS, we could call out to ``hdiutil`` to mount a DMG to see its +contents and again to create a new DMG. However, this isn't implemented +because we don't perceive there to be value in it: if you have access to +macOS you should probably just use Apple's official signing tooling! + +There are open source libraries for reading and writing HFS+ filesystems. +We could potentially integrate those to support reading and writing the +contents of DMGs. We could also potentially leverage a pure Rust HFS+ +implementation (this is a preferred solution). + +DMG also supports multiple embedded filesystem types and it is possible +we could leverage one that isn't HFS+ (or APFS) and produce working DMGs. +This is an area we haven't yet explored. + +If you want to distribute DMGs signed with this tool that themselves have +signed files, you'll need to sign the files inside the DMG before the DMG +is created. Then you'll need to create the DMG (using ``hdiutil`` or +whatever tool you have access to) then feed that DMG into this tool for +signing. + +https://github.com/indygreg/apple-platform-rs/issues/2 is our tracking issue +for DMG writing support. If you have ideas, please comment there! + +Cannot Recursively Sign Flat Packages (``.pkg`` Installers) +=========================================================== + +Flat Packages (``.pkg`` installers) are a complex file format. + +We have support for signing ``.pkg`` installers by reading the files +within a flat package. And we are capable of recursively extracting +and signing the ``.pkg`` installers that themselves are often embedded +in ``.pkg`` installers. + +What we don't yet have support for is mutating the file content within +flat packages / ``.pkg`` installers. This means we can't recursively sign +nested ``.pkg`` installers or bundles or Mach-O binaries within. + +The main blocker to implementing ``.pkg`` writing is support for +reading and writing Apple's *Bill of Materials* file format. These are +the ``Bom`` files within flat packages. The author of this project +has an unpublished Rust crate to read and write bom files but he +encountered issues getting it to write files that validate with Apple's +implementation. + +So if you want to sign ``.pkg`` files that themselves containable signable +entities, you need to sign files going into the ``.pkg`` before creating +the ``.pkg``. Then you need to create the ``.pkg`` and invoke this tool to +sign the ``.pkg``. For installers that contained nested ``.pkg`` installers, +this process will be quite tedious. Invoking ``componentbuild`` and +``productbuild`` will likely be much simpler. + +https://github.com/indygreg/apple-platform-rs/issues/3 is our tracking issue +for flat packages writing support. + +Extra Signing or Time-Stamp Token Operations +============================================ + +Signatures often need to encapsulate the size of the resulting signature. +This creates a chicken-and-egg problem because how can we know the size of +the resulting signature before we actually produce it! + +In some cases, this tool will create a *fake* signature and obtain an +actual time-stamp token from a server in order to resolve the size of +the data so we can better estimate the size of the real signature. + +We are not sure if Apple's tooling does this. But ours does and the +extra operations can be annoying because they may require extra unlocks +of signing keys or communications with a time-stamp token server. + +We can likely eliminate the extra use of the signing key for generating +these stand-in signatures and we can probably only make 1 request to the +time-stamp token server to obtain the size of its signatures. But we +haven't implemented this throughout the code base yet. + +https://github.com/indygreg/apple-platform-rs/issues/4 and +https://github.com/indygreg/apple-platform-rs/issues/19 track improvements here. + +Launch Constraints and Library Constraints +========================================== + +While we support embedding launch constraints and library constraints in code +signatures, there's a lot about these constraints we don't yet fully understand. +For example, there are ``ccat``, ``comp``, and ``vers`` fields in the encoded +data whose purpose we haven't figured out yet. + +We suspect that use of constraints can result in invalid signatures in some +cases. If using this feature, it would be wise to compare signatures against +Apple's tooling to ensure things behave similarly. + +Long Tail of Random Discrepancies from Apple's Tooling +====================================================== + +Apple's code signature format is really, really complex. There are tons of +data structures and fields with complex values. + +There is likely a long tail of minor differences in implementation that +result in variations between the behavior of our implementation and Apple's. + +In general, we consider differences in behavior in our implementation to +be bugs worth filing. Please follow the instructions at +:ref:`apple_codesign_debugging` to file GitHub issues with meaningful +details to debug the differences! + +Known areas where discrepancies are likely include: + +* The *code requirements* expression embedded into Mach-O binaries. We attempt + to derive one based on the signing key. The expression may not be exactly what + Apple's tools derive automatically. We consider this a bug. +* Executable segment flags and code signing flags. The exact logic for + determining what flags to set when is complex. In general, we consider + differences in behavior here to be bugs. +* Size of embedded signatures. You often need to estimate the size of the produced + embedded signature before signing because the signature encapsulates its own + size. Our estimation method varies from Apple's and can result in signatures + with more or less padded null bytes. This difference should be mostly harmless. + Improvements to make our signatures use fewer wasteful extra padding are + appreciated. diff --git a/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_rcodesign.rst b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_rcodesign.rst new file mode 100644 index 00000000..d2a2e503 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_rcodesign.rst @@ -0,0 +1,16 @@ +.. _apple_codesign_rcodesign: + +=================== +Using ``rcodesign`` +=================== + +The ``rcodesign`` executable provided by this project provides a command +mechanism to interact with Apple code signing. + +.. toctree:: + :maxdepth: 2 + + apple_codesign_rcodesign_signing + apple_codesign_rcodesign_notarizing + apple_codesign_rcodesign_config_files + apple_codesign_settings_scope diff --git a/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_rcodesign_config_files.rst b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_rcodesign_config_files.rst new file mode 100644 index 00000000..54727daf --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_rcodesign_config_files.rst @@ -0,0 +1,435 @@ +.. _apple_codesign_config_files: + +================================= +``rcodesign`` Configuration Files +================================= + +``rcodesign`` supports TOML configuration files. + +.. _apple_codesign_config_files_loading_behavior: + +File Loading Behavior +===================== + +``rcodesign`` will automatically load configuration files in the +following order: + +#. An ``rcodesign.toml`` file in the user's configuration directory. + (``$XDG_CONFIG_HOME/rcodesign/`` or ``$HOME/.config/rcodesign/`` on + Linux, ``FOLDERID_RoamingAppData/rcodesign/`` on Windows, and + ``$HOME/Library/Application Support/rcodesign/`` on macOS.) +#. An ``rcodesign.toml`` in the directory ``rcodesign`` was invoked + from. + +The ``-C`` / ``--config-file`` global CLI argument forcefully loads +an alternative config file. If specified, the default configuration +files are not loaded. + +.. _apple_codesign_config_files_environment_variables: + +Config Settings From Environment Variables +========================================== + +Every config setting can also be set via an environment variable. + +Environment variables beginning with ``RCODESIGN_`` are mapped to +configuration settings. Dots (``.``) in config settings names are +converted to underscores (``_``) in environment variable names. + +Profile names are not included in the environment variable name. + +For example, for the config setting ``foo.bar`` corresponds to the environment +variable ``RCODESIGN_FOO_BAR``. + +.. _apple_codesign_config_files_setting_precedence: + +Config Setting Precedence +========================= + +Configuration settings are loaded from multiple sources and last write +wins. Configurations are loaded in the following order: + +1. Configuration files (the default file search paths or the explicit + ``-C`` / ``--config-file`` file). +2. Environment variables. +3. CLI arguments. + +In other words, explicit takes precedence over implicit. + +Configuration sources are merged. Simple scalars and arrays are replaced. +Tables/dicts are union merged (each key is merged independently). + +.. _apple_codesign_config_files_toml_structure: + +TOML Structure +============== + +Configuration files are `TOML `_. + +Top-level keys/sections in config files correspond to named *profiles*. + +.. code-block:: toml + + # A profile named "default" + [default] + foo = "bar" + + # A profile named "test" + [test] + foo = "baz" + +There are two special named profiles with special semantics: + +``global`` + Settings in this profile override explicitly set settings in other profiles. + If a value is set in this section it applies, well, globally to the entire + file. + +``default`` + Settings in this profile are inherited by all other profiles. Unlike + ``[global]``, settings in this profile can be overwritten by other + sections/profiles. + +The default loaded profile is ``default``. An alternative profile can be +selected using the ``-P`` / ``--profile`` CLI argument. + +Within each profile are TOML tables/dicts/maps/sections roughly corresponding +to per-command settings. The following sub-sections denote those keys. + +``sign`` Command Settings +------------------------- + +The ``sign`` table denotes settings for the ``rcodesign sign`` command. + +This table can have the following keys: + +``signer`` + Denotes the key/certificate used for signing. Is an instance of the + :ref:`apple_codesign_rcodesign_config_files_signer` data structure. + + If not specified, ad-hoc code signing is performed. + +``path`` + A table of per-path signing settings. + + Keys are paths/scopes the settings apply to. Values are instances of the + :ref:`apple_codesign_rcodesign_config_files_path_settings` data structure. + +.. code-block:: toml + + [default.sign] + # Sign with a smartcard certificate at slot 9c. + signer.smartcard = { slot = "9c" } + + # Sign the path at `Contents/MacOS/extra-binary` with a custom entitlements + # plist. + [default.sign.path."Contents/MacOS/extra-binary"] + entitlements_xml_file = "extra-binary-entitlements.plist" + + # Enable the hardened runtime flag on the `Contents/MacOS/secure-binary` + # Mach-O binary. + [default.sign.path."Contents/MacOS/secure-binary"] + code_signature_flags = ["runtime"] + + # Skip signing the nested `Electron Framework.framework` bundle. + [default.sign.path."Contents/Frameworks/Electron Framework.framework/**"] + exclude = true + +``remote-sign`` Command Settings +-------------------------------- + +The ``remote-sign`` table denotes settings for the ``rcodesign remote-sign`` +command. + +This table can have the following keys: + +``signer`` + Denotes the key/certificate used for signing. Is an instance of the + :ref:`apple_codesign_rcodesign_config_files_signer` data structure. + +.. code-block:: toml + + # Attempt to remote sign using a certificate in the macOS keychain with the + # specified SHA-256 digest. + [default.remote-sign] + signer.macos_keychain = { sha256_fingerprint = "deadbeef..." } + +.. _apple_codesign_rcodesign_config_files_data_structures: + +Config Data Structures +====================== + +.. _apple_codesign_rcodesign_config_files_signer: + +Signer +------ + +A *signer* denotes a source for a private signing key and its public +certificate(s). + +Specific flavors of signer sources are defined in the sections below. + +Typically you define a single source. However, multiple signer sources +can be combined and are effectively unioned together in an undefined order. + +Smartcard Source +^^^^^^^^^^^^^^^^ + +The ``signer.smartcard`` key declares a key/certificate source in a smartcard, +like a YubiKey. + +This key is a table/dict/map with the following keys: + +``slot`` + The smartcard slot name. + + 9c is the slot typically reserved for code signing. But other slots can + be used. + + Run ``rcodesign smartcard-scan`` to see available certificates in your + smartcard. + +``pin`` + PIN string used to unlock the certificate. + + The smartcard slot settings may require a PIN to unlock the slot for key + operations. If this is the case, this setting can be defined to provide said + PIN. + + If not defined and a PIN is required, you will be prompted for the PIN as + necessary. + +.. code-block:: toml + + [default.sign] + + # Sign using the certificate in slot 9c. Prompt for PIN as necessary. + signer.smartcard = { slot = "9c" } + + # Sign using the certificate in slot 9c and use the specified PIN value + # instead of prompting. + signer.smartcard = { slot = "9c", pin = "123456" } + +.. important:: + + The PIN is a secret and storing it in the config file in plain text can be + dangerous. You may want to consider passing the PIN via an environment + variable instead. + +MacOS KeyChain Source +^^^^^^^^^^^^^^^^^^^^^ + +The ``signer.macos_keychain`` key declares a key/certificate source in a macOS +Keychain. + +This key is a table/dict/map with the following keys: + +``domains`` + Array of strings denoting the Keychain *domains* to search. + + Valid values are ``user``, ``system``, ``common``, ``dynamic``. + + The default is ``["user"]``. + +``sha256_fingerprint`` + SHA-256 fingerprint of the certificate to use. + + You can find these by running ``rcodesign keychain-print-certificates``, + locating the certificate you want to use, and copying the ``SHA-256 + fingerprint`` value. + + Strings should be 64 characters long. + +.. code-block:: toml + + [default.sign] + # Try to use a certificate in the user keychain having a SHA-256 fingerprint + # of ``deadbeef...``. + signer.macos_keychain = { sha256_fingerprint = "deadbeef..." } + +PKCS#12 / P12 / PFX +^^^^^^^^^^^^^^^^^^^ + +The ``signer.p12`` key declares a key/certificate source in a PKCS#12 / P12 / +PFX file. These files commonly have the extensions ``.pfx`` and ``.p12``. + +This key is a table/dict/map with the following keys: + +``path`` + Path to the PKCS#12 / P12 / PFX file to load. + +``password`` + Password to use to open the file. + +``password_path`` + Path to a file containing the password to use. + +If a password is not specified, you will be prompted to enter a password. + +Examples: + +.. code-block:: toml + + [default.sign] + + # Load the key/certificates from the file `signing.p12`. Don't supply + # a password. + signer.p12 = { path = "signing.p12" } + + # Same as the above but provide the path to a file containing the password. + signer.p12 = { path = "signing.p12", "password_path" = "path/to/password/file" } + +PEM Encoded Files Source +^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``signer.pem`` key declares key/certificate sources in PEM encoded +files. + +PEM files are text files with base64 content surrounded by +``-----BEGIN CERTIFICATE-----``, ``-----BEGIN PRIVATE KEY-----``, etc. Common +filename extensions are ``.pem``, ``.crt``, and ``.key``. + +This key is a table/dict/map with the following keys: + +``files`` + Array of paths to PEM files. + +.. code-block:: toml + + [default.sign] + signer.pem.files = ["cert.crt", "key.key"] + +DER Encoded Certificate Source +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``signer.certificate_der_files`` key declares DER encoded X.509 public +certificates to load from files. + +.. code-block:: toml + + signer.certificate_der_files = ["cert1.crt", "cert2.crt"] + +Remote Code Signer Source +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``signer.remote`` key declares a remote signing source. + +This source engages the :ref:`apple_codesign_remote_signing` feature and +delegates code signing to a remote peer. + +This key is a table/dict/map with the following keys: + +``url`` + URL of a remote code signing relay server. + + Leave blank to use the default. + +``public_key`` + Base64 encoded public key data used to encrypt a message to the remote + signer. + +``public_key_pem_path`` + File containing PEM encoded public key data used to encrypt a message to + the remote signer. + +``shared_secret`` + A shared secret value (i.e. a password/passphrase) used to encrypt a message + to the remote signer. + +Communication between initiating and signer peers is sent through a *relay +server*. All messages are encrypted in a way that the relay server cannot read +them. + +Messages are encrypted either by using public key encryption (recommended) or a +shared secret value. + +If using public key encryption, the public key data likely begins with ``MII``. + +Windows Store Signer Source +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``signer.windows_store`` key declares key/certificate sources in the Windows +certificate store. + +This key is a table/dict/map with the following keys: + +``stores`` + Array of strings denoting Windows Store names. + + Valid values are ``user``, ``machine``, and ``service``. + + Defaults to ``["user"]``. + +``sha1_fingerprint`` + SHA-1 fingerprint of certificate in store to use. + +.. _apple_codesign_rcodesign_config_files_path_settings: + +Path Signing Settings +--------------------- + +This table/dict/map defines a collection of signing settings that apply to +a path or set of paths defined by a matching expression. + +This table consists of the following keys: + +``binary_identifier`` + Binary identifier for Mach-O binaries. + + This will typically be derived automatically using reasonable heuristics + (mainly the file name). But it can be forced to a specific value using this + setting. + + Note: it is possible to produce invalid bundle signatures when the identifier + is manually set. + +``code_requirements_file`` + Path to a file containing a code signing requirements expression. + + The requirements must be compiled to the binary form. Use the ``csreq`` + tool to do this from a macOS machine. + +``code_signature_flags`` + Array of flags to add to the code signature. + +``digests`` + Array of content digests to include in signatures. + + Typically reasonable defaults are derived automatically based on the + targeting settings of the signed binary / bundle. But specific digests + can be forced using this setting. + + If specifying multiple digests, ``sha1`` should be the first or signatures + may not be valid on older operating systems. + +``entitlements_xml_file`` + Path to a file containing plist XML entitlements to embed in a binary. + +``info_plist_file`` + Path to an ``Info.plist`` file whose contents to capture in the code signature. + + The ``Info.plist`` is typically found automatically when signing bundles. + When signing standalone Mach-O binaries you may need to provide it + explicitly. + +``launch_constraints_self_file`` + Path to a plist - either XML or binary - containing launch constraints to + impose on the current executable. + +``launch_constraints_parent_file`` + Path to a plist - either XML or binary - containing launch constraints to + impose on the parent process. + +``launch_constraints_responsible_file`` + Path to a plist - either XML or binary - containing launch constraints to + impose on the responsible process. + +``library_constraints_file`` + Path to a plist - either XML or binary - containing constraints to + impose on loaded libraries. + +``runtime_version`` + Apple operating system version representing the minimum version this binary + can run on. + + This is typically derived automatically from metadata in the Mach-O binary. diff --git a/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_rcodesign_notarizing.rst b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_rcodesign_notarizing.rst new file mode 100644 index 00000000..43f7b994 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_rcodesign_notarizing.rst @@ -0,0 +1,189 @@ +.. _apple_codesign_rcodesign_notarizing: + +========================================== +Notarizing and Stapling with ``rcodesign`` +========================================== + +Submit Notarizations with ``notary-submit`` +=========================================== + +You can notarize a signed asset via ``rcodesign notary-submit``. + +Notarization requires an App Store Connect API Key. See +:ref:`apple_codesign_app_store_connect_api_key` for instructions on how +to obtain one. + +Assuming you used ``rcodesign encode-app-store-connect-api-key`` to produce +a JSON file with all the API Key information, simply specify ``--api-key-file`` +to define the path to this JSON file. + +To notarize an already signed asset:: + + rcodesign notary-submit \ + --api-key-file ~/.appstoreconnect/key.json \ + path/to/file/to/notarize + +By default ``notarize-submit`` just uploads the asset to Apple. To wait +on its notarization result, add ``--wait``:: + + rcodesign notary-submit \ + --api-key-file ~/.appstoreconnect/key.json \ + --wait \ + path/to/file/to/notarize + +Or to wait and automatically staple the file if notarization was successful:: + + rcodesign notary-submit \ + --api-key-file ~/.appstoreconnect/key.json \ + --staple \ + path/to/file/to/notarize + +Stapling With ``staple`` +======================== + +If an asset was already notarized, you can attempt to *staple* (read: attach) +the *notarization ticket* to that entity via the ``staple`` command:: + + rcodesign staple path/to/file/to/staple + +.. tip:: + + It is possible to staple any asset, not just those notarized by you. + +Checking on Submitted Notarizations +=================================== + +Notarization is an asynchronous process: you first submit an asset to Apple then +you wait for an indefinite amount of time (often a few dozen seconds) for +Apple's servers to scan the asset and issue a notarization ticket. + +If a notarization operation is interrupted or if you want to check on its +status, there are a few support commands to query Apple's servers. + +``notary-list`` will list the most recent submitted notarization requests. +This command will print a list of submission IDs, dates, and a brief status +of each one.:: + + rcodesign notary-list --api-key-file ~/.appstoreconnect/key.json + +``notary-wait`` can be used to wait on a previously submitted notarization +request to finish:: + + rcodesign notary-wait \ + --api-key-file ~/.appstoreconnect/key.json \ + + +Here, ```` is an identifier issued by Apple and printed when +running ``rcodesign notary-list`` or ``rcodesign notary-submit``. + +``notary-log`` can be used to retrieve the notarization log for a submission +identifier:: + + rcodesign notary-log \ + --api-key-file ~/.appstoreconnect/key.json \ + + +.. _apple_codesign_notarization_problems: + +Common Notarization Problems +============================ + +Notarization can fail for a myriad of reasons. On software that hasn't been +successfully notarized before and on untested release pipeline, notarization +failures before success are somewhat expected. + +Apple's requirements for notarization are enumerated at +`Notarizing macOS software before distribution `_. + +The sections below document common notarization failures and how to mitigate +them. They somewhat mirror Apple's official guidance at +`Resolving common notarization issues `_, +which we highly recommend you read before this documentation. + +.. _apple_codesign_notarization_for_notarization: + +Using `sign --for-notarization` +------------------------------- + +The ``rcodesign sign`` command has a ``--for-notarization`` argument that +attempts to engage *Apple notarization compatibility mode*. + +When this flag is used: + +* The signing configuration is validated for notarization compatibility. +* Signing settings are automatically changed to help ensure notarization compatibility. + +This flag is best effort. If you encounter notarization failures when using +this flag that you think could be automatically detected or prevented, +please consider filing a bug report. + +Usage example:: + + rcodesign sign \ + --for-notarization \ + --pem-source developer-id-application.pem \ + MyApp.app + +.. _apple_codesign_notarization_problem_apple_first: + +Notarize with Apple Tooling First +--------------------------------- + +For applications that haven't been notarized before or when debugging issues +with notarization, we highly recommend using Apple's official tooling and +workflows for notarizing from a macOS machine before attempting to debug +notarization with ``rcodesign``. + +This is because notarization and its errors can be challenging to work through. +Having an existence proof that a piece of software can be notarized with Apple's +tooling proves that software is capable of passing notarization. It means that +failures in ``rcodesign`` notarization are due to invoking ``rcodesign`` +incorrectly or due to a bug in ``rcodesign``. + +We highly recommend reading Apple's notarization documentation (linked above) +when debugging notarization failures. + +.. _apple_codesign_notarization_problem_hardened_runtime: + +Hardened Runtime Not Enabled +---------------------------- + +The hardened runtime needs to be enabled to pass notarization. If you don't +have the hardened runtime enabled, notarization has been known to fail with +the error: ``The executable does not have the hardened runtime enabled.`` + +To enable the hardened runtime with ``rcodesign sign``, the ``runtime`` +code signature flag must be enabled via the ``--code-signature-flags`` argument. +e.g.:: + + rcodesign sign \ + --code-signature-flags runtime \ + MyApp.app + +``--code-signature-flags`` only applies to the _main_ entity being signed by +default. If you are signing an application bundle with multiple binaries, for +example, you will need to use the _scoped_ syntax to ``--code-signature-flags`` +to specify code signature flags for each additional path being signed. e.g.:: + + rcodesign sign \ + --code-signature-flags runtime \ + --code-signature-flags Contents/MacOS/additional-binary:runtime \ + MyApp.app + +For complex bundles consisting of several binaries or nested bundles, this +can grow quite cumbersome and it is easy to forget to annotate a binary, +especially if new files appear in the bundles. For complex signing scenarios, +we recommend using :ref:`configuration files ` to +define the signing settings. + +.. _apple_codesign_notarization_problem_signing_key: + +Incorrect Signing Certificate +----------------------------- + +Another common notarization problem is not signing with an Apple issued signing +certificate or not using the appropriate certificate for signing a particular +entity. + +See Apple's `Use a valid Developer ID certificate `_ +for more. diff --git a/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_rcodesign_signing.rst b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_rcodesign_signing.rst new file mode 100644 index 00000000..5616b91c --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_rcodesign_signing.rst @@ -0,0 +1,55 @@ +.. _apple_codesign_rcodesign_signing: + +=============================== +Signing with ``rcodesign sign`` +=============================== + +The ``rcodesign sign`` command is used to sign a filesystem path. + +If you simply ``rcodesign sign ``, it will attempt to create an ad-hoc +signature (read: no code signing certificate), rewriting the file/directory +in place. Arguments like ``--p12-file``, ``pem-file``, and ``--smartcard-slot`` +can be used to sign with a code signing certificate/key. + +Nested Signing By Default +========================= + +One of the areas where ``rcodesign sign`` varies from Apple's ``codesign`` is +that we recursively sign entities by default. e.g. if you sign a bundle, we'll +recursively sign nested bundles/frameworks and Mach-O binaries inside that bundle +unless told otherwise. + +Unlike Apple's ``codesign``, ``rcodesign`` has a signing settings mechanism +that allows you to scope settings to particular paths. This gives you low-level +control over how every binary, bundle, and even individual Macho-O within a +universal Macho-O binary are signed. Whereas ``codesign`` requires N invocations +with N different settings configurations, ``rcodesign`` can perform the same +operation in a single invocation. + +Simple Examples +=============== + +To sign a Mach-O executable:: + + rcodesign sign \ + --p12-file developer-id.p12 --p12-password-file ~/.certificate-password \ + --code-signature-flags runtime \ + path/to/executable + +To sign an ``.app`` bundle (and all Mach-O binaries inside):: + + rcodesign sign \ + --p12-file developer-id.p12 --p12-password-file ~/.certificate-password \ + path/to/My.app + +To sign a DMG image:: + + rcodesign sign \ + --p12-file developer-id.p12 --p12-password-file ~/.certificate-password \ + path/to/app.dmg + +To sign a ``.pkg`` installer:: + + rcodesign sign \ + --p12-file developer-id-installer.p12 --p12-password-file ~/.certificate-password \ + path/to/installer.pkg \ No newline at end of file diff --git a/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_remote_signing.rst b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_remote_signing.rst new file mode 100644 index 00000000..8a36aea3 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_remote_signing.rst @@ -0,0 +1,352 @@ +.. _apple_codesign_remote_signing: + +=================== +Remote Code Signing +=================== + +This project has support for *remote signing*. This is a feature where +cryptographic signature operations (requiring access to the private key) +are delegated to a remote machine. + +From a high level, two machines establish a secure communications bridge with +each other through a central server. The *initiating* machine starts signing +operations like normal. But when it gets to an operation that requires producing +a cryptographic signature, it sends an end-to-end encrypted message to the +bound *signer* peer with the message to sign. The *signer* then uses its +private key to create a signature, which it sends back to the *initiator*, +who incorporates it into the code signature. + +Remote signing is essentially peer-to-peer, not client-server. The central +server exists for relaying encrypted messages between peers and not for +performing signing operations itself. Each signing *session* is ephemeral +and short-lived. Since the signing keys are offline by default and a human must +take action to join a signing session and use the signing keys, remote signing +is theoretically more secure than solutions like giving a (CI) machine +unlimited access to a code signing certificate or HSM. + +Remote signing is intended for use cases where the machine initiating signing +must not or can not have access to the private key material or unlimited +access to it. Popular scenarios include: + +* CI environments where you don't want a CI worker to have unlimited access + to the signing key because CI workers are notoriously difficult to secure. + (If someone can run arbitrary jobs on your CI they can likely exfiltrate any + CI secrets with ease.) +* When hardware security devices are used and machines initiating the signing + don't have direct access to this device. Think a remote CI machine or + coworker wanting to sign with a certificate in a YubiKey or HSM whose + access is entrusted to a specific person (or group of people in the case of + an HSM). + +.. important:: + + This feature is considered alpha and will likely change in future versions. + +.. danger:: + + The custom cryptosystem for remote signing has not yet undergone an audit. + The end-to-end message encryption and tampering resistance claims we've + made may be undermined by weaknesses in the design of the cryptosystem and + its implementation and interaction in code. + + In other words, use this feature at your own risk. + + `Issue 5 `_ tracks + performing an audit of this feature. + +How It Works +============ + +A full overview of the protocol and cryptography involved is available at +:ref:`apple_codesign_remote_signing_protocol` and you can read more about the +design and security at :ref:`apple_codesign_remote_signing_design`. + +From a high-level, signing operations involve 2 parties: + +* The *initiator* of the signing request. This is the entity that wants + something to be signed but doesn't having the signing certificate / key. +* The *signer*. This is the entity who has access to the private signing key. + +The signing procedure is essentially: + +1. *Initiator* opens a persistent websocket to a central server and publishes + details about that session and how to connect to it. +2. *Signer* follows the instructions from *initiator* and joins the *signing + session* by opening a websocket to the same server as the *initiator*. + Cryptography is employed to derive encryption keys so all subsequently + exchanged messages are end-to-end encrypted, preventing the server or any + privileged network actors from eavesdropping on signing operations or forging + a signing request. +3. *Initiator* sends a request to *signer* asking them to sign a message. +4. *Signer* inspects the request and issues a cryptographic signature, which it + sends back to *initiator*. +5. Steps 3-4 are repeated as long as necessary. + +Using +===== + +The *initiator* begins a remote signing *session* via ``rcodesign sign +--remote-signer``. (Some additional arguments are required - see below.) + +This command will print out an ``rcodesign`` command that the *signer* must +subsequently run to *join* the signing session. e.g.:: + + $ rcodesign sign --remote-signer --remote-shared-secret-env SHARED_SECRET + ... + connecting to wss://ws.codesign.gregoryszorc.com/ + session successfully created on server + Run the following command to join this signing session: + + rcodesign remote-sign gm1zaGFyZWRzZWNyZXQwg... + + (waiting for remote signer to join) + +At this point, that long opaque string - which we call a *session join string* - +needs to be copied or entered on the *signer*. e.g.:: + + $ rcodesign remote-sign --p12-file developer_id.p12 --remote-shared-secret-env SHARED_SECRET \ + gm1zaGFyZWRzZWNyZXQwg... + +If everything goes according to plan, the 2 processes will communicate with +each other and *initiator* will delegate all of its signing operations to +*signer*, who will issue cryptographic signatures which it sends back to the +*initiator*. + +.. _apple_codesign_remote_signing_session_agreement: + +Session Agreement +================= + +Remote signing currently requires that the *initiator* and *signer* exchange +and agree about *something* before signing operations. This ahead-of-time +exchange improves the security of signing operations by helping to prevent +signers from creating unwanted signatures. + +The sections below detail the different types of agreement and how they are +used. + +Public Key Agreement +==================== + +.. important:: + + This is the most secure and preferred method to use. + +In this operating mode, the *signer* possesses a private key that can decrypt +messages. When the *initiator* begins a signing operation, it encrypts a message +that only the *signer*'s private key can decrypt. This encrypted message is +encapsulated in the *session join string* exchanged between the *initiator* and +*signer*. + +This mode can be activated by passing one of the following arguments defining +the public key: + +``--remote-public-key`` + Accepts base64 encoded public key data. + + Specifically, the value is the DER encoded SubjectPublicKeyInfo (SPKI) + data structure defined by RFC 5280. + +``--remote-public-key-pem-file`` + The path to a file containing the PEM encoded public key data. + + The file can begin with ``-----BEGIN PUBLIC KEY-----`` or + ``-----BEGIN CERTIFICATE-----``. The former defines just the SPKI data + structure. The latter an X.509 certificate (which has the SPKI data + inside of it). + +Both the public key and certificate data can be obtained by running the +``rcodesign analyze-certificate`` command against a (code signing) certificate. + +The *signer* needs to use the corresponding private key specified by the +*initiator* in order to join the signing session. By default, ``rcodesign +remote-sign`` attempts to use the in-use code signing certificate for +decryption. + +So, an end-to-end workflow might look like the following: + +1. Run ``rcodesign analyze-certificate`` and locate the + ``-----BEGIN PUBLIC KEY-----`` block. +2. Save this to a file, ``signing_public_key.pem``. You can check this file into + source control - the contents aren't secret. +3. On the initiator, run ``rcodesign sign --remote-signer + --remote-public-key-pem-file signing_public_key.pem /path/to/input + /path/to/output``. +4. On the signer, run ``rcodesign remote-sign --smartcard-slot 9c + ````. + +We believe this method to be the most secure for establishing sessions because: + +* The state required to bootstrap the secure session is encrypted and can only + be decrypted by the private key it is encrypted for. If you are practicing + proper key management, there is exactly 1 copy of the private key and access + to the private key is limited. This means you need access to the private key + in order to compromise the security of the signing session. +* The session ID is encrypted and can't be discovered if the session join string + is observed. This eliminates a denial of service vector. + +Shared Secret Agreement +======================= + +.. important:: + + This method is less secure than the preferred *public key agreement* method. + +In this operating mode, *initiator* and *signer* agree on some shared secret +value. A password, passphrase, or some random value, such as a type 4 UUID. + +This mode is activated by passing one of the following arguments defining the +shared secret: + +``--remote-shared-secret-env`` + Defines the environment variable holding the value of a shared secret. + +``--remote-shared-secret`` + Accepts the raw shared secret string. + + This method is not very secure since the secret value is captured in plain + text in process arguments! + +An end-to-end workflow might look like the following: + +1. A secure, random password is generated using a password manager. +2. The secret value is stored in a password manager, registered as a CI secret, + etc. +3. The initiator runs ``rcodesign sign --remote-signer --remote-shared-secret-env + REMOTE_SIGNING_SECRET /path/to/input /path/to/output``. +4. The signer runs ``rcodesign remote-sign --remote-shared-secret-env + REMOTE_SIGNING_SECRET --smartcard-slot 9c``. + +Important security considerations: + +* Anybody who obtains the shared password could coerce the signer into signing + unwanted content. +* Weak password will undermine guarantees of secure message exchange and could + make it easier to decrypt or forge communications. + +Because the password exists in multiple locations, must be known by both +parties, and the process for generating it are not well defined, the overall +security of this solution is not as strong as the preferred *public key +agreement* method. However, this method is easier to use and may be preferred +by some users. + +.. _apple_codesign_remote_signing_github_actions: + +Using with GitHub Actions +========================= + +It is pretty simple to initiate remote code signing from GitHub Actions! In +fact, this scenario is one of the primary use cases for the design of the +feature. + +Here are the general steps. + +Configuring a Workflow / Actions +-------------------------------- + +First, export the public key data of the signing certificate to a file +checked into source control. Use ``rcodesign analyze-certificate`` and +copy the ``-----BEGIN PUBLIC KEY----`` block to a file in your +repository. e.g. https://github.com/indygreg/apple-platform-rs/blob/main/ci/developer-id-application.pem +defines the ``Developer ID Application`` public key data for the maintainer +of this project. + +.. note:: + + The public key data is included in the code signatures embedded in signed + artifacts so there is generally not a concern with making the public key + data widely available in the repository. + +Next, create a GitHub workflow or action that invokes ``rcodesign sign``. + +The `indygreg/apple-code-sign-action `_ +action provides a turnkey way to do this. But implementing this action yourself isn't +too much work either! See +https://github.com/indygreg/apple-platform-rs/blob/main/.github/workflows/sign-apple-exe.yml +for an example of such a workflow. ()This particular workflow is using +``on.workflow_dispatch`` so the workflow is only triggered manually. See +the `workflow_dispatch documentation `_ +and `Manually running a workflow `_ +docs for more.) + +.. important:: + + A manually triggered workflow is strongly recommended because a signer must + take manual action to perform remote signing and an automated trigger will + likely hang unless a person is around to attend to it. + +.. important:: + + For security reasons, you should set ``timeout-minutes`` on either the job + or step initiating remote signing to limit how long a signer will wait. + +The important steps in a remote signing action/workflow are: + +1. Securely obtain ``rcodesign``. We recommend downloading a release artifact + from https://github.com/indygreg/apple-platform-rs/releases and pinning/verifying + the SHA-256 digest on download. +2. Download the artifact you want signed. The + `Download workflow artifact `_ + action can be useful for downloading artifacts from other workflows in the + current repository (since the official ``download-artifact`` action limits + you to artifacts in the current workflow). +3. Invoke ``rcodesign sign --remote-signer + --remote-public-key-pem-file path/to/public_key.pem``. +4. Do something with the signed result (like upload it as an artifact). + +Running the Workflow / Action +----------------------------- + +Now that you have a GitHub workflow or action in place, here's how you use it. + +If you followed the recommendations from above, the workflow is manually +triggered via ``on.workflow_dispatch``. You can trigger the workflow via +the GitHub web UI or via API. For API, the path of least resistance is likely +the ``gh`` `GitHub CLI `_ tool. e.g.:: + + gh workflow run sign-apple-exe.yml \ + --ref ci-main \ + -f workflow=rcodesign.yml \ + -f run_id=2214520041 \ + -f artifact=exe-rcodesign-macos-universal \ + -f exe_name=rcodesign + +If your workflow is highly parameterized (like this one), you may want to +script its invocation to make it more turnkey. + +When ``rcodesign sign --remote-signer`` runs in GitHub Actions, it will print +instructions on how to join the signing session. You will need to follow +these instructions in a timely manner to complete the code signing operation. + +Here is what you are looking for in the job output: + +.. image:: apple_codesign_actions_sjs_join.png + :alt: Screenshot of GitHub Actions run showing session join string + +Then, simply follow instructions on the machine with the signing key +to commence signing! + +.. important:: + + When you view the logs of a running GitHub Actions job, only the output + from after the point you started viewing them is visible. This means that + if you are *too late* you may not see the printed instructions for joining + the signing session! + + There are definitely some mitigations we can take for this. For the moment, + you need to be quick to open the job output in your browser. Or you can do + things like add a ``sleep`` before running ``rcodesign sign``. + +If all goes according to plan, you should see progress being printed +both in the signing process and from the near real time output from +GitHub Actions. + +Here is the output from the GitHub Actions (Linux) machine: + +.. image:: apple_codesign_actions_initiator_output.png + :alt: Signing output from GitHub Actions worker + +And from the signing Windows machine using a YubiKey for signing: + +.. image:: apple_codesign_actions_signer_output.png + :alt: Signing output from signing machine diff --git a/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_remote_signing_design.rst b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_remote_signing_design.rst new file mode 100644 index 00000000..bb5f2980 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_remote_signing_design.rst @@ -0,0 +1,195 @@ +.. _apple_codesign_remote_signing_design: + +====================================================== +Remote Code Signing Design and Security Considerations +====================================================== + +Design Goals and Constraints +============================ + +The design of remote signing is influenced with the following primary goals in +mind: + +* The initiating machine MUST NOT have direct access to the private signing + key. Ever. The private key (or ability to create signatures with it) is only + ever in possession of the signer. +* The private key cannot be used without the signer's knowledge (and optional + consent to each use). +* The initiating machine must be able to run remotely / non-interactively. + +We also imposed the following constraints when considering designs: + +* The initiating machine is partially trusted. We assume that if you trust the + initiating machine to invoke a signing operation then you trust that machine + to e.g. not lie about the signing requests it subsequently presents to the + signer. +* We should place minimal trust in any 3rd party servers or machines. Assume + all 3rd parties are malicious and will attempt to coerce signers into signing + arbitrary content. +* 3rd party servers should have access to as little information about signing + activity as possible. e.g. 3rd party servers should not be able to observe + the messages that are signed, the produced signatures, or the certificates + used to sign. They may observe details that leak through side channels, such + as the number of messages exchanged and the sizes of encrypted ciphertexts. +* We assume the existence of an out-of-band side-channel for 2 peers to exchange + information at signing time. This means we require some synchronous activity + by the signer in order to fulfill signing requests. (The signer isn't just + running an always-running server that responds to signing requests.) + +Threat Models +============= + +The following threat models dictate some design choices: + +* A malicious brokering server or man-in-the-middle could coerce the signer into + signing unwanted content. +* A malicious 3rd party could disrupt signing operations by sending garbage + messages to the brokering server, either in general or directed at established + sessions. i.e. DoS against the server. +* A malicious brokering server or man-in-the-middle could fulfill signature + requests using the *wrong* certificate. + +If signing sessions were conducted without any prior knowledge of the peer, +neither peer would be able to trust or authenticate the other. You could +securely exchange end-to-end encrypted messages with a peer. But the *initiator* +wouldn't be able to answer the question *is this signed by who I want it to be +signed by*. And more importantly, the *signer* wouldn't be able to answer +*do I trust the initiator to send me content that I want to sign*. + +You can't establish a trust relationship without a trust anchor. **So in order +to establish trust we require that peers share pre-existing knowledge of the +other before signing operations.** The exact mechanism can vary. But *some* +pre-existing knowledge needs to be conveyed to the other peer in order to serve +as a trust anchor. + +Since all designs rule out the possibility of the private key being directly +accessed or used by the *initiator*, the next best attack vector is tricking +the *signer* into signing untrusted/malicious content. + +The easiest way to conduct this attack is for a malicious server or +man-in-the-middle to intercept communications and/or issue a malicious signing +request. There are a few mitigations for this. + +First, *signers* must have presence in order to create signatures. When signers +go offline, they can't produce signatures. So attacks against signers must occur +when the signer is online. + +Second, we employ end-to-end encryption of peer-to-peer messages using +ephemeral encryption keys unique to the session and logically derived from a +pre-existing trust anchor. A malicious 3rd party would need access to data +never transmitted in plaintext through the server in order to decrypt messages +or issue fake/malicious messages. + +Security Analysis in the Bigger Picture +======================================= + +When considering the overall security of remote code signing, we have to +consider the broader ecosystem in which it exists. + +Without remote code signing, the following are all commonly true: + +* Signing keys are copied to multiple machines to make it easier to access + them. +* Signing keys are made available as secrets on CI workers. +* Access to perform operations on the signing key is always on. e.g. + anybody who can talk to the HSM can create a signature. +* Security conscious people (those who want to minimize risk for private + keys) need to impose a more complicated release pipeline - one that + typically entails copying assets to a separate machine, signing them, + then copying elsewhere. These steps are often tedious and effectively + constitute a barrier to good security hygiene. + +There are general principles of private key management: + +* You should have as few copies of the private key as possible. Ideally 1. +* Keys should be as short lived as possible or access to them should be + limited in time duration. + +Traditional solutions to code signing violate these principles because +there's not an easy-to-use / viable alternative. So in the absence of +remote code signing, commonly practiced code signing key management is +generally not great. + +We believe that our design of remote code signing is intrinsically more +secure than what is commonly practiced because: + +* The signer in possession of the private key must be present. There is + no unlimited access to the private key outside an active signing session. +* You can have exactly 1 copy of the private key without compromising on + usability. The urge to make copies to streamline CI/CD is largely mitigated + via an easy-to-use remote signing UI. + +In addition, the design and implementation of the relay server further +bolsters security by: + +* Purging sessions after a maximum time to live (measured in minutes). +* Refusing to allow N>2 peers from sending messages to a session. +* Requiring active presence for message exchange. The server doesn't store + a copy of relayed signing messages so there isn't a potential for someone + to deposit a malicious message for later retrieval. + +And these security properties are delivered without even factoring in +end-to-end message encryption! The end-to-end encryption is effectively +protections against a malicious server or man-in-the-middle. These are +arguably necessary protections - especially when using a server hosted by +an (untrusted) 3rd party. But for scenarios where you run your own server +and you trust the network, end-to-end encryption isn't buying you much beyond +what signer presence requirements and server design already deliver. + +Default Remote Code Signing Server +================================== + +By default, this project uses the remote code signing server at +``wss://ws.codesign.gregoryszorc.com/``. + +This service is operated by the maintainer of this project and is provided +for free for use by the community. However, there is no formal or legal +agreement around the availability of its service or its operation. + +The service is hosted on AWS and uses API Gateway + Lambda + DynamoDB +and should be highly reliable, as these services rarely experience outages. + +The :ref:`apple_codesign_remote_signing_protocol` and implementation of the +server have been purposefully designed to be respectful of privacy of its +users. + +Meaningful messages between clients are end-to-end encrypted and the server +is unable to determine the contents of those messages. The server only has +access to protocol-level details, such as which APIs are being invoked and +the sizes of the payloads. + +The server does have access to client IPs and any additional metadata +in HTTP requests and websocket frames. However, IPs or other identifying +information is not read by our custom code powering the websocket server or +retained in any logs to the best of our knowledge. (We believe user data +to be toxic and don't want anything to do with it.) + +Some metrics to monitor the health of the service and help prevent abuse +are recorded. These include the counts of different API invocations and +the sizes of message payloads. + +The code powering the server and the Terraform for deploying it on AWS +are open source and available to audit. See +:ref:`apple_codesign_remote_signing_running_your_own_server` for details. +Of course, there's no way to prove that ``ws.codesign.gregoryszorc.com`` +is running the same configuration as the provided open source code. You +*just* have to trust that the maintainer of this project values the privacy +of his users. + +.. _apple_codesign_remote_signing_running_your_own_server: + +Running Your Own Server +======================= + +If you are unable or unwilling to use the default remote signing server +operated by the maintainer of this project, it is possible to deploy your +own server instance. + +The source code for the server and a Terraform module for deploying it into +AWS are available in this repository in the +``terraform-modules/remote-code-signing`` directory. The canonical location +is https://github.com/indygreg/apple-platform-rs/tree/main/terraform-modules/remote-code-signing. + +See its README for instructions on how to use. Once deployed at a different +hostname, you'll need to provide the ``--remote-signing-url`` argument to +relevant commands to override the default signing server URL. diff --git a/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_remote_signing_protocol.rst b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_remote_signing_protocol.rst new file mode 100644 index 00000000..4cd3e1f8 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_remote_signing_protocol.rst @@ -0,0 +1,836 @@ +.. _apple_codesign_remote_signing_protocol: + +============================ +Remote Code Signing Protocol +============================ + +Overview +======== + +The remote signing protocol facilitates the cryptographic signing of messages +involving 2 discrete network peers. + +The peer that wants something signed is the **initiator**. + +The peer with access to the signing key that produces cryptographic +signatures is the **signer**. + +Peers establish persistent websocket connections to a central server to +enable them to speak with each through firewalls and NATs. + +Peers register an ephemeral *session* with the server, which is essentially +a binding between 2 connected websocket clients. + +Peers derive session-specific encryption keys using mutually agreed upon +ahead of time data. They then relay end-to-end encrypted messages through +the central server and perform cryptographic signing operations. + +Wire Protocol +============= + +The protocol entails the exchange of JSON encoded objects via websockets. + +The JSON objects sent from clients to the server have the following keys: + +``request_id`` + (string) (required) A unique identifier for this request. + +``api`` + (string) (required) The name of the API / method to invoke on the server. + +``payload`` + (object) (optional) Parameters passed to this API invocation. + +The JSON objects sent from servers to clients have the following keys: + +``request_id`` + (string) (optional) Echo of ``request_id`` from the message that generated + this one. The value could be unknown to the receiver if this message was + generated from the other peer in the session. + +``type`` + (string) (required) The message type. + +``ttl`` + (number) (optional) Integer number of seconds remaining before the session + expires and will be automatically deleted by the server. + +``payload`` + (object) (optional) Payload further describing this message. + +All other fields in the top-level object are reserved for future use. + +Messages sent from the client to server ALWAYS result in the server responding +to that API request. + +It is also possible for servers to send messages to clients asynchronously +of any client-initiated message. + +Initial Connection Protocol +=========================== + +When a client connects to the server, it SHOULD issue a ``hello`` API +message and wait for the server's response. + +If the response contains a *message of the day* string, it MUST be displayed +to the end-user. + +Clients SHOULD also make a best effort attempt to validate the server's +advertised capabilities and make a determination about compatibility and +error or print warnings if incompatibility is detected. + +.. _apple_codesign_remote_signing_sessions: + +Session Negotiation +=================== + +The *initiator* and *signer* pair with each other by forming a *session*. + +From the server's perspective, a *session* is an opaque identifier string +with associated state, such as the unique websocket connection IDs of the +*initiator* and *signer* clients. + +Sessions are ephemeral and expire automatically after a duration specified +by the initiating client. (The server can impose a maximum duration to prevent +service abuse.) + +Sessions are generally created by the *initiator*. + +The *initiator* creates a unique session ID, ``SessionId``. ``SessionId`` MUST +be randomly chosen. It SHOULD have sufficient entropy to prevent server-side +collisions. The use of type 4 UUIDs for session IDs is recommended. + +Once a server-side session is created, the *initiator* then shares a +*session join string* with the signer via an out-of-band mechanism. +See :ref:`apple_codesign_remote_session_join_strings` for more. + +At this point, mechanisms diverge based on the session joining mechanism +employed. But generally speaking, the *signer* sends a +:ref:`apple_codesign_remote_api_client_join_session` to the server +to register itself as the other peer in the session. At this point, both +peers derive encryption keys and communicate with each other by issuing +:ref:`apple_codesign_remote_api_client_send_message` messages. See +:ref:`apple_codesign_remote_signing_protocol_encrypted_protocol` for more. + +.. _apple_codesign_remote_session_join_strings: + +Session Join Strings +==================== + +The *initiator* and *signer* need to leverage an out-of-band mechanism for +communicating metadata with each other in order to join a server-established +session. There are various potential solutions for this and we've purposefully +designed the mechanism to be extensible. + +Generically, the mechanism to join a session is expressed through a +**session join string**, or SJS. + +The SJS is ultimately a CBOR encoded array of length 2. The array's elements +are: + +* (string) The scheme being used. +* (varied) The payload for that scheme. + +But to end-users it is an opaque string. + +The SJS can be encoded as: + +* Base64 using the RFC 3548 *URL safe* character set with optional ``=`` + padding. +* PEM using ``SESSION JOIN STRING`` as the armoring tag. + +In general, the *session join string* is shared out-of-band with the other +peer, who uses it to join the session. + +In general, *session join strings* are designed such that a 3rd party +becoming aware of the SJS will not jeopardize the security of the current or +future signing operations. However, denial of service could occur if the SJS +exposes the session ID and a 3rd party joins the session before the *intended* +peer. + +The following sections denote the defined *session join string* schemes. +Sections names are the ``scheme`` value. + +``publickey0`` +-------------- + +The ``publickey0`` session joining mechanism relies on public key cryptography +to authenticate the 2nd peer in a session by leveraging knowledge of the +2nd peer's public encryption key. + +The initiating peer, ``A``, MUST know the public key of the joining peer, +``B``. + +``A`` generates a random value at least 32 bytes long, ``ChallengeSecret``. + +``A`` generates a new RFC 7748 Curve 25519 private key. Its private / +public components are ``AAgreementPrivate`` and ``AAgreementPublic``, +respectively. + +``A`` generates a new random 16 byte value, ``SharedAESKey``. + +``A`` loads the public key of ``B``, ``BPublic``. It usually does so by +extracting the X.509 SubjectPublicKeyInfo (SPKI) (RFC 5280 Section 4.1.2.7) +from an X.509 certificate or DER/PEM fragment of just the SPKI. + +``A`` prepares a plaintext message to be sent to ``B``, ``AJoinPlaintext``. +This message is a CBOR array with the following elements: + +``serverUrl`` + (Index 0) (optional string) URL of the server to connect to. + +``sessionId`` + (Index 1) (string) The session identifier created on the server. + +``challenge`` + (Index 2) (bytes) The content of ``ChallengeSecret``. + +``agreementPublic`` + (Index 3) (bytes) ``SubjectPublicKeyInfo`` for ``AAgreementPublic``. + +``A`` encrypts ``AJoinPlaintext`` using AES-128 in GCM with ``SharedAESKey``, +yielding ``AJoinCiphertext``. A 12 byte nonce is used where the bytes are all +``0x42``. The 16 byte authentication tag is appended to the raw ciphertext +and constitutes the final bytes of ``AJoinCiphertext``. + +``A`` encrypts ``SharedAESKey`` using asymmetric encryption targeting +``BPublic``, yielding ``SharedAESCiphertext``. + +For RSA, OAEP padding with SHA-256 digests MUST be used. + +The payload of the *session join string* is a CBOR array with the following +elements: + +``aes_ciphertext`` + (Index 0) (bytes) The ``SharedAESCiphertext`` generated above. + +``bPublic`` + (Index 1) (bytes) The SPKI describing which public key was used to + encrypt ``SharedAESCiphertext``. + +``message_ciphertext`` + (Index 2) (bytes) The ``AJoinCiphertext`` generated above. + +So, the final *session join string* is +``["publickey0", [SharedAESCiphertext, BSPKI, AJoinCiphertext]]``. + +The *session join string* is summarily CBOR and base64 encoded and made +available to ``B``. + +``B`` receives and decodes the SJS. + +``B`` locates the decryption key from the provided SPKI structure. (``B`` +may want to impose restrictions here to prevent clients from fishing for +specific keys.) + +``B`` decrypts ``SharedAESCiphertext`` using ``BPrivate``, yielding back +``SharedAESKey``. + +Using ``SharedAESKey``, ``B`` verifies and decrypts ``AJoinCiphertext``, +yielding ``AJoinPlaintext``. + +On success, ``B`` generates a new RFC 7748 Curve 25519 private key, +``BAgreementPrivate`` and ``BAgreementPublic``. + +``B`` connects to the server and sends a +:ref:`apple_codesign_remote_api_client_join_session` message with ``context`` +set to ``BAgreementPublic``. + +At this point, ``A`` and ``B`` both perform key agreement using their +ephemeral ED25519 private key and the public key of the other peer, each +mutually deriving ``SessionSharedKey``. + +At this point, the procedure described in +:ref:`apple_codesign_remote_signing_aead_keys` is used to derive new symmetric +encryption keys. ``ChallengeSecret`` is used as the additional value to +derive ``IdentifierA`` and ``IdentifierB``. + +Security Considerations +^^^^^^^^^^^^^^^^^^^^^^^ + +The *session join string* consists of 2 discrete encrypted payloads and is +generally safe against offline attacks. Unless ciphers are broken, the +private key is required to obtain for anything beyond side-channels (like +total payload size). + +``SessionId`` is encrypted, so compromise of the SJS can't easily lead to a +DoS by an unwanted peer joining the session. + +The server doesn't see anything: the encrypted AES key and AES encrypted +peer metadata are both encapsulated in the SJS. We could potentially move +some of these to the server to reduce the length of the SJS. + +Open Questions for Security Audit +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* We don't sign / HMAC the asymmetrically encrypted AES key. Nor do we + include an IV or other prepended message. This seems to go against + best practices. Does it matter? Does the additional layer of AEAD feeding + into the key agreement compensate for this? +* Is the use of a constant nonce for the ``SharedAES`` -> ``AJoinCiphertext`` + acceptable? The AES key is randomly generated and is used exactly once, so + do the nonces even matter? +* Is AES-128 in GCM mode a sufficient key/cipher for encrypting the main + message? +* We currently generate 2 distinct private keys: 1 for key agreement and 1 + for AES encryption. They are generated independently. Does this make sense + or should perhaps HKDF be used against a common key? +* Right now there is no explicit trust anchoring between the asymmetric + encryption targeting ``B`` and the derived shared secret key. Should ``B`` + produce a cryptographic signature using ``BPrivate`` so ``A`` doesn't assume + that *ability to decrypt* authenticates ``B``? Or is *ability to decrypt* + along with the assumption that only ``B`` possesses ``agreementPublic`` + sufficient? + +``sharedsecret0`` +----------------- + +The ``sharedsecret0`` session joining mechanism uses SPAKE2 to derive a shared +encryption key using an ahead-of-time mutually agreed upon shared secret, +``SharedSecret``. + +The peer creating the session, henceforth ``A``, generates unique/random +``SessionId`` and ``Identifier`` values. These values are used to construct +the SPAKE2 identifier strings: ``A:{SessionId}:{Identifier}`` and +``B:{SessionId}:{Identifier}``. + +``A`` begins SPAKE2 role A initialization using ``SharedSecret`` and role A's +identifier string. This produces ``SpakeAInit``. + +``A`` calls :ref:`apple_codesign_remote_api_client_create_session` to +register the new session with the server. Its ``context`` field is empty. + +The *session join string* value is a CBOR array with the following elements: + +``sessionId`` + (Index 0) (string) The session identifier string. + +``identifier`` + (Index 1) (bytes) The random ``Identifier`` value produced earlier. + +``spakeAInit`` + (Index 2) (bytes) The SPAKE2 Role A initialization message. + +The final CBOR *session join string* is +``["sharedsecret0", [SessionId, Identifier, SpakeAInit]]``. + +The *session join string* is summarily CBOR and base64 encoded and made +available to ``B``. + +``B`` receives and decodes the SJS. + +``B`` performs SPAKE2 Role B initialization, producing ``SpakeBInit``. + +``B`` sends a :ref:`apple_codesign_remote_api_client_join_session` message +to the server with ``context`` set to the base64 encoding of ``SpakeBInit``. +``SpakeBInit`` is relayed to ``A`` via the server. + +At this point, both ``A`` and ``B`` are able to finalize SPAKE2 using +``SpakeBInit`` and ``SpakeAInit``, respectively. They should mutually derive +a shared encryption key, ``SessionSharedKey``. + +At this point, the procedure described in +:ref:`apple_codesign_remote_signing_aead_keys` is used to derive new symmetric +encryption keys. ``Identifier`` is used as the additional value used to +derive ``IdentifierA`` and ``IdentifierB``. + +Security Considerations +^^^^^^^^^^^^^^^^^^^^^^^ + +The *session join string* containing the plaintext ``SessionId``, +``Identifier``, and ``SpakeAInit`` generally does not need to be highly +secure or made secret. + +``SharedSecret`` cannot be derived from knowledge of the *session join string*. + +The server does not directly observe the value for ``Identifier``, only +``SpakeBInit``. So it would need knowledge of the *session join string* +and ``SharedSecret`` to decrypt messages. + +A 3rd party in a privileged network position (including the server) with +knowledge of ``SharedSecret``, ``SessionId``, and ``Identifier`` would be +able to decrypt and forge messages, as it would be able to derive ``RoleAKey`` +and ``RoleBKey``. So it is important to use transport-level encryption, +a trusted server, and keep ``SharedSecret`` a secret value. + +Open Questions for Security Audit +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* Is SPAKE2 the best mechanism for deriving session encryption keys from a + shared secret? +* Should ``SpakeAInit`` be in the *session join string* or stored on the server + and hidden from plaintext view? What are the tradeoffs with each approach? +* As proposed, the SPAKE2 identifier contains ``SessionId`` and yet another + random value. That random value is not sent to the server but is possibly + world readable in the *session join string*. Is this second source of entropy + necessary? Does attempting to prevent the server from having access to it buy + us any security value? Or is just the client-chosen ``SessionId`` string good + enough? +* The SPAKE2 specification seems to insist on the use of key confirmation + messages. Since we're using HKDF into AEAD, which has built-in authentication, + do we need to perform the SPAKE2 key confirmation since any failures in SPAKE2 + land would lead to AEAD failures anyway? +* How sensitive is SPAKE2 to the entropy of ``SharedSecret``? While we want to + encourage a relatively strong ``SharedSecret``, we can't guarantee this. + Should we be doing e.g. PBKDF2 on ``SharedSecret`` before feeding it into + SPAKE2 or will SPAKE2 do sufficient *key stretching* on its own? + +.. _apple_codesign_remote_signing_aead_keys: + +AEAD Key Derivation +------------------- + +The schemes above commonly detail the steps to enable 2 peers to mutually +derive a session-ephemeral shared encryption key, ``SessionSharedKey``. + +Rather than use ``SessionSharedKey`` directly for subsequent message exchange, +we instead derive additional keys from it for use with Authenticated Encryption +and Additional Data (AEAD) encryption / message exchange. + +An identifier value is associated with peers assuming roles ``A`` (the session +initiator) and ``B`` (the session joiner). The value is a bytes concatenation +of: + +* The role name. e.g. ``A`` / ``0x41`` or ``B`` / ``0x42``. +* A colon (``:`` / ``0x3a``) +* The ``SessionId`` identifier, UTF-8 encoded. +* A colon (``:`` / ``0x3a``) +* An additional value communicated in the session join string. e.g. + ``ChallengeSecret``. + +These values are known as ``IdentifierA`` and ``IdentifierB``. + +HKDF is used to derive new keys. + +Step 1 / HKDF-Extract uses an empty salt and ``SessionSharedKey`` to produce +a pseudorandom key, ``PRK``. + +Step 2 / HKDF-Expand is performed twice to derive 2 new keys. The first +invocation uses ``IdentifierA`` for ``info`` and ``32`` for ``L``, producing +``RoleAKey``. The second invocation uses ``IdentifierB`` for ``info`` and ``32`` +for ``L``, producing ``RoleBKey``. + +``RoleAKey`` and ``RoleBKey`` are used to empower AEAD encryption / message +exchange. ChaCha20+Poly1305 is used. Nonces are 12 bytes where the first 4 +bytes are a little-endian u32 counter whose initial used value is ``0`` and +the subsequent 8 bytes are always ``0``. Additionally authenticated data +(``AAD``) is generally not used. + +``RoleAKey`` is used by ``A`` to encrypt messages and by ``B`` to +verify/decrypt messages from ``A``. ``RoleBKey`` is used by ``B`` to +encrypt messages and by ``A`` to verify/decrypt messages from ``B``. + +Open Questions for Security Audit +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* Is ChaCha20+Poly1305 a reasonable cipher choice? Or should we be using + block ciphers (e.g. AES)? +* Using a simple, easily guessable counter for nonces seems wrong. Using a + random value seems more appropriate. But both parties need to know what the + nonce we be. Do we use a random value for the nonce but encode the nonce in + plaintext next to the exchanged ciphertext messages? Or do we need something + else entirely? +* We could potentially use additionally authenticated data (AAD) to encapsulate + more details of the request, such as the request ID. Does that buy us + security benefits? + + +.. _apple_codesign_remote_signing_protocol_encrypted_protocol: + +Signing Protocol +================ + +Once 2 peers have established a session and derived encryption keys to +facilitate end-to-end encrypted communication, they communicate with each +other using :ref:`peer to peer messages ` +by invoking the :ref:`apple_codesign_remote_api_client_send_message` API. + +This process generally involves a handshake: + +1. Both peers simultaneously send :ref:`apple_codesign_remote_api_peer_ping` + messages. +2. Upon receipt, each peer sends a :ref:`apple_codesign_remote_api_peer_pong` + in response. This dance confirms peer presence and that the derived + encryption keys work. +3. The *initiator* sends a + :ref:`apple_codesign_remote_api_peer_request_signing_certificate` to request + information about the signer's public certificate. This is necessary in + order to allow the signer to do things like estimate the sizes of signatures + and to derive additional details needed for signing. +4. The *signer* sends a + :ref:`apple_codesign_remote_api_peer_signing_certificate` in response. + +At this point, both peers are ready to commence signing. + +5. The *initiator* sends a :ref:`apple_codesign_remote_api_peer_sign_request`. +6. The *signer* receives the request, assesses it, creates a cryptographic + signature, and sends a :ref:`apple_codesign_remote_api_peer_signature` + in reply. +7. Steps 5-6 are repeated as necessary. + +Finally, + +8. Either peer sends a :ref:`apple_codesign_remote_api_client_goodbye` to + finalize the session. + +Client Issued Messages +====================== + +The following sections denote the types of messages issued from clients to +servers. + +Section names denote the value of the ``api`` key in the messages. + +.. _apple_codesign_remote_api_client_hello: + +``hello`` +--------- + +Greets the server and obtains information about the server. + +This message type has no payload. + +Servers respond to this message with a +:ref:`apple_codesign_remote_api_server_greeting`. + +.. _apple_codesign_remote_api_client_create_session: + +``create-session`` +------------------ + +Requests the creation of a new session on the server. + +Sent by the *initiator* as part of session negotiation. + +Fields: + +``session_id`` + (string) (required) Unique identifier to use for this session. + +``ttl`` + (number) (required) Requested session duration, in seconds. + +``context`` + (string) (optional) Additional context to be passed to the peer when it + joins the session. + +Servers SHOULD automatically expire the server-side session state after its +TTL duration expires. Servers MAY close connections to connected clients when +their session expires. Servers MAY impose a shorter TTL if the requested TTL +is too long. + +Servers respond to this message with a +:ref:`apple_codesign_remote_api_server_session_created`. + +.. _apple_codesign_remote_api_client_join_session: + +``join-session`` +---------------- + +Attempts to join an existing session. + +Sent by the *signer* as part of session negotiation. + +Fields: + +``session_id`` + (string) (required) Identifier of session to join. + +``context`` + (string) (optional) Additional context to pass through to the other + peer. + +Servers respond to this message with a +:ref:`apple_codesign_remote_api_server_session_joined`. + +.. _apple_codesign_remote_api_client_send_message: + +``send-message`` +---------------- + +Sends an (encrypted) message to the other peer in this session. + +Fields: + +``session_id`` + (string) (required) Identifier of session to use for peer lookup. + +``message`` + (string) (required) Base64 encoded ciphertext of an AEAD encrypted + message to send to the peer. + +Server implementations MUST ensure that the client issuing this request +are bound to the session they are attempting to send a message to. + +Servers react to this message by sending a +:ref:`apple_codesign_remote_api_server_peer_message` to the other peer +in the specified session. + +Servers respond to this message with a +:ref:`apple_codesign_remote_api_server_message_sent`. + +.. _apple_codesign_remote_api_client_goodbye: + +``goodbye`` +----------- + +Indicates the client is finished and will be disconnecting. + +Fields: + +``session_id`` + (string) (required) Identifier of session to use for peer lookup. + +``reason`` + (string) (option) Reason the client is disconnecting. + +Server implementations MUST ensure that the client issuing this request +is bound to the session they are attempting to close. + +Servers react to this message by sending a +:ref:`apple_codesign_remote_api_server_session_closed` to the other peer +in the specified session. + +Servers respond to this message with a +:ref:`apple_codesign_remote_api_server_session_closed`. + +Server Sent Messages +==================== + +The following sections denote the types of messages sent from the server +to clients. + +Section names denote the value of the ``type`` field in the message. + +.. _apple_codesign_remote_api_server_greeting: + +``error`` +--------- + +Conveys information about a server-side error. + +Could be sent in reply to any API request or sent asynchronously if some +error occurred (such as the peer disconnecting unexpectedly). + +Fields: + +``code`` + (string) (required) Value that uniquely identifies this error type. + +``message`` + (string) (required) Human readable error message. + +``greeting`` +------------ + +Conveys information about the server. + +Sent in reply to a :ref:`apple_codesign_remote_api_client_hello` request. + +Fields: + +``apis`` + (array of strings) (required) Names of APIs that the server supports. + +``motd`` + (string) (optional) *Message of the day* conveying messaging that the + server operator wishes clients to know about. + +.. _apple_codesign_remote_api_server_session_created: + +``session-created`` +------------------- + +Conveys the successful creation of a session. + +Sent in reply to a :ref:`apple_codesign_remote_api_client_create_session` +request. + +.. _apple_codesign_remote_api_server_session_joined: + +``session-joined`` +------------------ + +Conveys the successful joining into a session. + +Sent in reply to a :ref:`apple_codesign_remote_api_client_join_session` +request. + +Sent asynchronously by servers in response to a +:ref:`apple_codesign_remote_api_client_join_session` issued by the joining +peer. + +Fields: + +``context`` + (string) (optional) Data from the peer required to finish initializing + the session. + + If this message was sent in reply to a + :ref:`apple_codesign_remote_api_client_join_session`, the value will be + from the initiating peer. + + If this message was sent to the pre-existing peer in reaction to a + :ref:`apple_codesign_remote_api_client_join_session`, the value will be + from the joining peer. + +.. _apple_codesign_remote_api_server_message_sent: + +``message-sent`` +---------------- + +Conveys the successful sending of a message to the session peer. + +Sent in reply to a :ref:`apple_codesign_remote_api_client_send_message` +request. + +.. _apple_codesign_remote_api_server_peer_message: + +``peer-message`` +---------------- + +Delivers an (encrypted) message from the peer in this session. + +Sent asynchronously by servers in response to a +:ref:`apple_codesign_remote_api_client_send_message` issued by the +other peer in a session. + +Fields: + +``message`` + (string) (required) Base64 encoded AEAD message. + +.. _apple_codesign_remote_api_server_session_closed: + +``session-closed`` +------------------ + +Conveys that the session has been finalized and can no longer be used. + +Sent in reply to a :ref:`apple_codesign_remote_api_client_goodbye` request +as well as asynchronously to the peer in its session. + +Fields: + +``reason`` + (string) (optional) Provides further context on why the session was closed. + +.. _apple_codesign_remote_api_peer_messages: + +Peer to Peer Messages +===================== + +Peers within a session communicate with each other by sending and receiving +:ref:`apple_codesign_remote_api_client_send_message` and +:ref:`apple_codesign_remote_api_server_peer_message`, respectively. + +The ``message`` field denotes a base64 encoded AEAD encrypted message. The +message consists of the ciphertext with the authentication tag appended. The +plaintext of these messages is the JSON encoding of an object having the +following keys: + +``type`` + (string) (required) The message type. This is unique message namespace from + server-sent messages. + +``payload`` + (object) (optional) Payload for this message. + +The following sections denote the types of peer-to-peer messages. The section +names denote the value for the ``type`` field. + +.. _apple_codesign_remote_api_peer_ping: + +``ping`` +-------- + +Check on the status of the peer. + +Receivers should send a :ref:`apple_codesign_remote_api_peer_pong` in response. + +.. _apple_codesign_remote_api_peer_pong: + +``pong`` +-------- + +Respond to a status check from a peer. + +Sent in response to a :ref:`apple_codesign_remote_api_peer_ping` message. + +.. _apple_codesign_remote_api_peer_request_signing_certificate: + +``request-signing-certificate`` +------------------------------- + +Requests the peer to send it information about its signing certificate. + +Receivers should send a +:ref:`apple_codesign_remote_api_peer_signing_certificate` in response. + +Should only be sent by the *initiator*. + +.. _apple_codesign_remote_api_peer_signing_certificate: + +``signing-certificate`` +----------------------- + +Describes the signing certificate(s) that is being used by the signer. + +Sent in response to a +:ref:`apple_codesign_remote_api_peer_request_signing_certificate`. + +Fields: + +``certificates`` + (array of object) (required) Contains a list of signing certificates that + will potentially be used. + + Each entry is an object described below. + + Today, there is likely a single certificate in this array. We've + left the door open for supporting the use of multiple signing + certificates in the future. + +Each entry in the ``certificatess`` array is an object with the following +fields: + +``certificate`` + (string) (required) Base64 encoded DER of the public X.509 certificate. + +``chain`` + (array of strings) (optional) Base64 encoded DER of additional public + X.509 certificates in the signing chain for this certificate. + +.. _apple_codesign_remote_api_peer_sign_request: + +``sign-request`` +---------------- + +Requests the cryptographic signing of a message. + +Fields: + +``message`` + (string) (required) Base64 encoded message to be signed. + +.. _apple_codesign_remote_api_peer_signature: + +``signature`` +------------- + +Conveys the cryptographic signature over a message. + +Sent in response to a +:ref:`apple_codesign_remote_api_peer_sign_request`. + +Fields: + +``message`` + (string) (required) Base64 encoded message that was signed. + +``signature`` + (string) (required) Base64 encoded signature data. + +``algorithm_oid`` + (string) (required) Base64 encoded DER encoding of OID denoting the + signature algorithm. diff --git a/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_settings_scope.rst b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_settings_scope.rst new file mode 100644 index 00000000..81368348 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_settings_scope.rst @@ -0,0 +1,58 @@ +.. _apple_codesign_settings_scope: + +=============== +Settings Scopes +=============== + +Various signing settings and configuration settings can be *scoped* to a +specific path or pattern. This is accomplished using a mini language/syntax, +which is described by this document. + +A *scoping string* is syntax that denotes a path or entity to apply +a setting to. + +The following *scoping string* syntax is defined: + +```` + e.g. ``path/to.file``. Applies to content at a given path. + + This is probably the most common scoping syntax. + + The string is a bundle-relative path to a signable entity (a Mach-O + binary, a nested bundle, etc). e.g. ``Contents/MacOS/extra-bin``. + + If the path belongs to a nested bundle, settings with this scope will + apply to all signable entities in the bundle. + +``main`` + Applies to the main entity being signed and to nested/children entities. + +``@`` + e.g. ``@0`` or ``@1``. Applies to Mach-O binaries within a universal/fat + binary at the specified index. ``0`` means the first Mach-O in a universal + binary. + +``@[cpu_type=]`` + e.g. ``@[cpu_type=7]``. Applies to a Mach-O within a universal binary targeting + a numbered CPU architecture, using the numeric constants as defined by Mach-O. + +``@[cpu_type=]`` + e.g. ``@[cpu_type=x86_64]``. Applies to a Mach-O within a universal binary + targeting a CPU architecture identified by a string. See below for the set of + recognized architecture names. + +``@`` ``@[cpu_type=]`` + These syntax are an extension of the ```` and various ``@*`` syntax + above. They allow you to target a specified Mach-O binary within a universal + Mach-O at a given path. + + Like the ```` syntax, if the path matches a bundle, the setting applies + to all Mach-O binaries in that bundle. + +Architecture Names +------------------ + +* ``arm`` +* ``arm64`` +* ``arm64_32`` +* ``x86_64`` diff --git a/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_smartcard.rst b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_smartcard.rst new file mode 100644 index 00000000..fa669079 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/docs/apple_codesign_smartcard.rst @@ -0,0 +1,164 @@ +.. _apple_codesign_smartcard: + +================== +Smart Card Support +================== + +This project has some support for integrating with Smart Cards. This +enables you to perform cryptographic signing using a certificate that +is stored in a hardware device. + +Certificates stored this way are more secure, as it typically requires +that a physical device be unlocked in order to use the private key. And +access to the raw private key matter is typically not allowed. + +Cargo Feature +============= + +Smart card integration requires the optional and disabled-by-default +``smartcard`` Cargo feature to be enabled. + +On macOS and Windows, this feature should *just work*. + +On Linux, you'll need a package providing ``pcsclite`` installed or you may +get a cryptic build error due to missing dependencies. On Debian based distros, +you want to ``apt install libpcsclite1 libpcsclite-dev`` (or something of that +nature). + +Limitations +=========== + +We currently use `yubikey.rs `_ for +smart card integration. This likely means that only YubiKeys currently work. + +However, we would like to switch to a more generic interface (such as +`pcsc `_ in the future to allow more flexible +usage. + +There is currently no support for setting the management key. If you have +set a custom management key, you won't be able to import certificates onto +your smart card. However, signing should still work. + +Validating Smart Card Integration +================================= + +To see if your smart card device is recognized and certificates can be found:: + + $ rcodesign smartcard-scan + Device 0: Yubico YubiKey OTP+FIDO+CCID 0 + Device 0: Serial: 12345678 + Device 0: Version: 5.2.7 + Device 0: Certificate in slot Signature / 9c + Subject CN: gps + Issuer CN: gps + Subject is Issuer?: true + Team ID: + SHA-1 fingerprint: c847e830c01845517d7e3775805ab56313aa11c8 + SHA-256 fingerprint: 7c0bc8fe1a2d7831ca0b0787dc6d5c28c6f562c2723a7eaaab42d39e7a3b7924 + Signed by Apple?: false + Guessed Certificate Profile: none + Is Apple Root CA?: false + Is Apple Intermediate CA?: false + Apple CA Extension: none + Apple Extended Key Usage Purpose Extensions: + Apple Code Signing Extensions: + +Pointing Commands at a Smart Card Certificate +============================================= + +``rcodesign`` command that operate against certificates expose a +``--smartcard-slot`` argument to specify which smartcard slot to use. + +Slot ``9c`` is the standard slot for holding certificates used for +signing. + +To sign with your smart card certificate at slot ``9c``, do something like:: + + rcodesign sign \ + --smartcard-slot 9c \ + path/to/entity/to/sign + +Smartcards often require a PIN on signing operations. You should be prompted +for your PIN value if the signing operation is initially unauthenticated. + +Importing Certificates Into a Smart Card +======================================== + +The ``rcodesign smartcard-import`` command can be used to import an existing +code signing certificate into your smart card. + +Let's assume you created an Apple code signing certificate and exported it +to the file ``developer_id.p12``. You can import this certificate by doing +the following:: + + $ rcodesign smartcard-import \ + --smartcard-slot 9c \ + --p12-file developer_id.p12 --p12-password password + + $ rcodesign smartcard-scan + Device 0: Yubico YubiKey OTP+FIDO+CCID 0 + Device 0: Serial: 1234567 + Device 0: Version: 5.2.7 + Device 0: Certificate in slot Signature / 9c + Subject CN: Developer ID Application: Gregory Szorc (MK22MZP987) + Issuer CN: Developer ID Certification Authority + Subject is Issuer?: false + Team ID: MK22MZP987 + SHA-1 fingerprint: 44d7155bcabf3b9a9221b01b8e198040ae04e0ad + SHA-256 fingerprint: 8f610de4caea4bc138e85b56726ed4d330f7464d99cfa5957568904b6a6375ec + Signed by Apple?: true + Apple Issuing Chain: + - Developer ID Certification Authority + - Apple Root CA + - Apple Root Certificate Authority + Guessed Certificate Profile: DeveloperIdApplication + Is Apple Root CA?: false + Is Apple Intermediate CA?: false + Apple CA Extension: none + Apple Extended Key Usage Purpose Extensions: + - 1.3.6.1.5.5.7.3.3 (CodeSigning) + Apple Code Signing Extensions: + - 1.2.840.113635.100.6.1.33 (DeveloperIdDate) + - 1.2.840.113635.100.6.1.13 (DeveloperIdApplication) + +.. _apple_codesign_smartcard_key_generation: + +Creating a Certificate with a Private Key Exclusive to the Smart Card +===================================================================== + +It is possible to generate a private key directly on the smart card and create +a code signing certificate derived from this private key. + +Code signing certificates created this way are theoretically much more secure +than other private key generation methods because most smart cards never allow the +private key content to be exported/viewed. Assuming operations involving the +private key are protected with the appropriate access protections (like pin or +touch policies), compromise of the machine or even the smart key itself may not +result in unwanted access to the private key. + +To create a code signing certificate whose private key has never left the +smart card device itself, do something like the following. + +First, generate a new private key on the smart card:: + + rcodesign smartcard-generate-key --smartcard-slot 9c + +Then create a certificate signing request (CSR):: + + rcodesign generate-certificate-signing-request \ + --smartcard-slot 9c \ + --csr-pem-file csr.pem + +Then follow the instructions at :ref:`apple_codesign_exchange_csr` to submit the +CSR file to Apple and obtain a *public certificate*. + +Finally, import the Apple-issued public certificate into the smart card:: + + rcodesign smartcard-import \ + --certificate-der-file developerID_application.cer \ + --existing-key \ + --smartcard-slot 9c + +At this point, the smart card is ready to sign using an Apple issued certificate +and the private key never has - and probably never will - leave the smart card +itself. diff --git a/3rdparty/apple-codesign-0.29.0/docs/conf.py b/3rdparty/apple-codesign-0.29.0/docs/conf.py new file mode 100644 index 00000000..41bf2c44 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/docs/conf.py @@ -0,0 +1,34 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +import os +import pathlib +import re + +HERE = pathlib.Path(os.path.dirname(__file__)) +ROOT = pathlib.Path(os.path.dirname(HERE)) + +release = "unknown" + +with (ROOT / "Cargo.toml").open("r") as fh: + for line in fh: + m = re.match('^version = "([^"]+)"', line) + if m: + release = m.group(1) + break + + +project = "Apple Codesign" +copyright = "2022, Gregory Szorc" +author = "Gregory Szorc" +extensions = ["sphinx.ext.intersphinx"] +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] +html_theme = "alabaster" +master_doc = "index" +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), + "setuptools": ("https://setuptools.pypa.io/en/latest", None), +} +tags.add("apple_codesign") diff --git a/3rdparty/apple-codesign-0.29.0/docs/index.rst b/3rdparty/apple-codesign-0.29.0/docs/index.rst new file mode 100644 index 00000000..4e4e7556 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/docs/index.rst @@ -0,0 +1,8 @@ +================== +Apple Code Signing +================== + +.. toctree:: + :maxdepth: 2 + + apple_codesign diff --git a/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleAAI2CA.cer b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleAAI2CA.cer new file mode 100644 index 00000000..0038b604 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleAAI2CA.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleAAICA.cer b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleAAICA.cer new file mode 100644 index 00000000..e21d7dfa Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleAAICA.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleAAICAG3.cer b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleAAICAG3.cer new file mode 100644 index 00000000..0f3cdf74 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleAAICAG3.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleApplicationIntegrationCA5G1.cer b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleApplicationIntegrationCA5G1.cer new file mode 100644 index 00000000..20158210 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleApplicationIntegrationCA5G1.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleApplicationIntegrationCA7G1.cer b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleApplicationIntegrationCA7G1.cer new file mode 100644 index 00000000..2caaea82 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleApplicationIntegrationCA7G1.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleComputerRootCertificate.cer b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleComputerRootCertificate.cer new file mode 100644 index 00000000..8ccb85c5 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleComputerRootCertificate.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleISTCA2G1.cer b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleISTCA2G1.cer new file mode 100644 index 00000000..46711ce4 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleISTCA2G1.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleISTCA8G1.cer b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleISTCA8G1.cer new file mode 100644 index 00000000..ecddd53d Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleISTCA8G1.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleIncRootCertificate.cer b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleIncRootCertificate.cer new file mode 100644 index 00000000..8a9ff247 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleIncRootCertificate.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleRootCA-G2.cer b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleRootCA-G2.cer new file mode 100644 index 00000000..739b8141 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleRootCA-G2.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleRootCA-G3.cer b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleRootCA-G3.cer new file mode 100644 index 00000000..228bfa39 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleRootCA-G3.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleSoftwareUpdateCertificationAuthority.cer b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleSoftwareUpdateCertificationAuthority.cer new file mode 100644 index 00000000..564896b3 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleSoftwareUpdateCertificationAuthority.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleTimestampCA.cer b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleTimestampCA.cer new file mode 100644 index 00000000..dc0538f0 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleTimestampCA.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCA.cer b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCA.cer new file mode 100644 index 00000000..d2bb1da6 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCA.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG2.cer b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG2.cer new file mode 100644 index 00000000..b77e1e9e Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG2.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG3.cer b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG3.cer new file mode 100644 index 00000000..32f96f81 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG3.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG4.cer b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG4.cer new file mode 100644 index 00000000..b9f0bf29 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG4.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG5.cer b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG5.cer new file mode 100644 index 00000000..8b564c76 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG5.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG6.cer b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG6.cer new file mode 100644 index 00000000..424a70bd Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG6.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG7.cer b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG7.cer new file mode 100644 index 00000000..df350fd3 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG7.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG8.cer b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG8.cer new file mode 100644 index 00000000..2899edb9 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG8.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/apple-certs/DevAuthCA.cer b/3rdparty/apple-codesign-0.29.0/src/apple-certs/DevAuthCA.cer new file mode 100644 index 00000000..3d8fb276 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/apple-certs/DevAuthCA.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/apple-certs/DeveloperIDCA.cer b/3rdparty/apple-codesign-0.29.0/src/apple-certs/DeveloperIDCA.cer new file mode 100644 index 00000000..d3337393 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/apple-certs/DeveloperIDCA.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/apple-certs/DeveloperIDG2CA.cer b/3rdparty/apple-codesign-0.29.0/src/apple-certs/DeveloperIDG2CA.cer new file mode 100644 index 00000000..8cbcf6f4 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/apple-certs/DeveloperIDG2CA.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/apple-codesign-testuser.p12 b/3rdparty/apple-codesign-0.29.0/src/apple-codesign-testuser.p12 new file mode 100644 index 00000000..cfc99a85 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/apple-codesign-testuser.p12 differ diff --git a/3rdparty/apple-codesign-0.29.0/src/apple_certificates.rs b/3rdparty/apple-codesign-0.29.0/src/apple_certificates.rs new file mode 100644 index 00000000..bbee7e5d --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/apple_certificates.rs @@ -0,0 +1,613 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Apple X.509 certificates. +//! +//! This module defines well-known Apple X.509 certificates. +//! +//! The canonical source of this data is . +//! +//! Note that some certificates are commented out and not available +//! because the official DER-encoded certificates provided by Apple +//! do not conform to the encoding standards in RFC 5280. + +use {once_cell::sync::Lazy, std::ops::Deref, x509_certificate::CapturedX509Certificate}; + +/// Apple Inc. Root Certificate +static APPLE_INC_ROOT_CERTIFICATE: Lazy = Lazy::new(|| { + CapturedX509Certificate::from_der( + include_bytes!("apple-certs/AppleIncRootCertificate.cer").to_vec(), + ) + .unwrap() +}); + +/// Apple Computer, Inc. Root Certificate. +static APPLE_COMPUTER_INC_ROOT_CERTIFICATE: Lazy = Lazy::new(|| { + CapturedX509Certificate::from_der( + include_bytes!("apple-certs/AppleComputerRootCertificate.cer").to_vec(), + ) + .unwrap() +}); + +/// Apple Root CA - G2 Root Certificate +static APPLE_ROOT_CA_G2_ROOT_CERTIFICATE: Lazy = Lazy::new(|| { + CapturedX509Certificate::from_der(include_bytes!("apple-certs/AppleRootCA-G2.cer").to_vec()) + .unwrap() +}); + +/// Apple Root CA - G3 Root Certificate +static APPLE_ROOT_CA_G3_ROOT_CERTIFICATE: Lazy = Lazy::new(|| { + CapturedX509Certificate::from_der(include_bytes!("apple-certs/AppleRootCA-G3.cer").to_vec()) + .unwrap() +}); + +/// Apple IST CA 2 - G1 Certificate +static APPLE_IST_CA_2_G1_CERTIFICATE: Lazy = Lazy::new(|| { + CapturedX509Certificate::from_der(include_bytes!("apple-certs/AppleISTCA2G1.cer").to_vec()) + .unwrap() +}); + +/// Apple IST CA 8 - G1 Certificate +static APPLE_IST_CA_8_G1_CERTIFICATE: Lazy = Lazy::new(|| { + CapturedX509Certificate::from_der(include_bytes!("apple-certs/AppleISTCA8G1.cer").to_vec()) + .unwrap() +}); + +/// Application Integration Certificate +static APPLICATION_INTEGRATION_CERTIFICATE: Lazy = Lazy::new(|| { + CapturedX509Certificate::from_der(include_bytes!("apple-certs/AppleAAICA.cer").to_vec()) + .unwrap() +}); + +/// Application Integration 2 Certificate +static APPLICATION_INTEGRATION_2_CERTIFICATE: Lazy = Lazy::new(|| { + CapturedX509Certificate::from_der(include_bytes!("apple-certs/AppleAAI2CA.cer").to_vec()) + .unwrap() +}); + +/// Application Integration - G3 Certificate +static APPLICATION_INTEGRATION_G3_CERTIFICATE: Lazy = Lazy::new(|| { + CapturedX509Certificate::from_der(include_bytes!("apple-certs/AppleAAICAG3.cer").to_vec()) + .unwrap() +}); + +/// Apple Application Integration CA 5 - G1 Certificate +static APPLE_APPLICATION_INTEGRATION_CA_5_G1_CERTIFICATE: Lazy = + Lazy::new(|| { + CapturedX509Certificate::from_der( + include_bytes!("apple-certs/AppleApplicationIntegrationCA5G1.cer").to_vec(), + ) + .unwrap() + }); + +/// Apple Application Integration CA 7 - G1 Certificate +static APPLE_APPLICATION_INTEGRATION_CA_7_G1_CERTIFICATE: Lazy = + Lazy::new(|| { + CapturedX509Certificate::from_der( + include_bytes!("apple-certs/AppleApplicationIntegrationCA7G1.cer").to_vec(), + ) + .unwrap() + }); + +/// Developer Authentication Certificate +static DEVELOPER_AUTHENTICATION_CERTIFICATE: Lazy = Lazy::new(|| { + CapturedX509Certificate::from_der(include_bytes!("apple-certs/DevAuthCA.cer").to_vec()).unwrap() +}); + +/// Developer ID - G1 (Expiring 02/01/2027 22:12:15 UTC) Certificate +static DEVELOPER_ID_G1_CERTIFICATE: Lazy = Lazy::new(|| { + CapturedX509Certificate::from_der(include_bytes!("apple-certs/DeveloperIDCA.cer").to_vec()) + .unwrap() +}); + +/// Developer ID - G2 (Expiring 09/17/2031 00:00:00 UTC) Certificate +static DEVELOPER_ID_G2_CERTIFICATE: Lazy = Lazy::new(|| { + CapturedX509Certificate::from_der(include_bytes!("apple-certs/DeveloperIDG2CA.cer").to_vec()) + .unwrap() +}); + +/// Software Update Certificate +static SOFTWARE_UPDATE_CERTIFICATE: Lazy = Lazy::new(|| { + CapturedX509Certificate::from_der( + include_bytes!("apple-certs/AppleSoftwareUpdateCertificationAuthority.cer").to_vec(), + ) + .unwrap() +}); + +/// Timestamp Certificate +static TIMESTAMP_CERTIFICATE: Lazy = Lazy::new(|| { + CapturedX509Certificate::from_der(include_bytes!("apple-certs/AppleTimestampCA.cer").to_vec()) + .unwrap() +}); + +/// Worldwide Developer Relations - G1 (Expiring 02/07/2023 21:48:47 UTC) Certificate +static WORLD_WIDE_DEVELOPER_RELATIONS_G1_CERTIFICATE: Lazy = + Lazy::new(|| { + CapturedX509Certificate::from_der(include_bytes!("apple-certs/AppleWWDRCA.cer").to_vec()) + .unwrap() + }); + +/// Worldwide Developer Relations - G2 (Expiring 05/06/2029 23:43:24 UTC) Certificate +static WORLD_WIDE_DEVELOPER_RELATIONS_G2_CERTIFICATE: Lazy = + Lazy::new(|| { + CapturedX509Certificate::from_der(include_bytes!("apple-certs/AppleWWDRCAG2.cer").to_vec()) + .unwrap() + }); + +/// Worldwide Developer Relations - G3 (Expiring 02/20/2030 00:00:00 UTC) Certificate +static WORLD_WIDE_DEVELOPER_RELATIONS_G3_CERTIFICATE: Lazy = + Lazy::new(|| { + CapturedX509Certificate::from_der(include_bytes!("apple-certs/AppleWWDRCAG3.cer").to_vec()) + .unwrap() + }); + +/// Worldwide Developer Relations - G4 (Expiring 12/10/2030 00:00:00 UTC) Certificate +static WORLD_WIDE_DEVELOPER_RELATIONS_G4_CERTIFICATE: Lazy = + Lazy::new(|| { + CapturedX509Certificate::from_der(include_bytes!("apple-certs/AppleWWDRCAG4.cer").to_vec()) + .unwrap() + }); + +/// Worldwide Developer Relations - G5 (Expiring 12/10/2030 00:00:00 UTC) Certificate +static WORLD_WIDE_DEVELOPER_RELATIONS_G5_CERTIFICATE: Lazy = + Lazy::new(|| { + CapturedX509Certificate::from_der(include_bytes!("apple-certs/AppleWWDRCAG5.cer").to_vec()) + .unwrap() + }); + +/// Worldwide Developer Relations - G6 (Expiring 03/19/2036 00:00:00 UTC) Certificate +static WORLD_WIDE_DEVELOPER_RELATIONS_G6_CERTIFICATE: Lazy = + Lazy::new(|| { + CapturedX509Certificate::from_der(include_bytes!("apple-certs/AppleWWDRCAG6.cer").to_vec()) + .unwrap() + }); + +/// Worldwide Developer Relations - G7 (Expiring 11/17/2023 20:40:52 UTC) Certificate +static WORLD_WIDE_DEVELOPER_RELATIONS_G7_CERTIFICATE: Lazy = + Lazy::new(|| { + CapturedX509Certificate::from_der(include_bytes!("apple-certs/AppleWWDRCAG7.cer").to_vec()) + .unwrap() + }); + +/// Worldwide Developer Relations - G8 (Expiring 01/24/2025 00:00:00 UTC) Certificate +static WORLD_WIDE_DEVELOPER_RELATIONS_G8_CERTIFICATE: Lazy = + Lazy::new(|| { + CapturedX509Certificate::from_der(include_bytes!("apple-certs/AppleWWDRCAG8.cer").to_vec()) + .unwrap() + }); + +/// All known Apple certificates. +static KNOWN_CERTIFICATES: Lazy> = Lazy::new(|| { + vec![ + // We put the 4 roots first, newest to oldest. + APPLE_ROOT_CA_G3_ROOT_CERTIFICATE.deref(), + APPLE_ROOT_CA_G2_ROOT_CERTIFICATE.deref(), + APPLE_INC_ROOT_CERTIFICATE.deref(), + APPLE_COMPUTER_INC_ROOT_CERTIFICATE.deref(), + APPLE_IST_CA_2_G1_CERTIFICATE.deref(), + APPLE_IST_CA_8_G1_CERTIFICATE.deref(), + APPLICATION_INTEGRATION_CERTIFICATE.deref(), + APPLICATION_INTEGRATION_2_CERTIFICATE.deref(), + APPLICATION_INTEGRATION_G3_CERTIFICATE.deref(), + APPLE_APPLICATION_INTEGRATION_CA_5_G1_CERTIFICATE.deref(), + APPLE_APPLICATION_INTEGRATION_CA_7_G1_CERTIFICATE.deref(), + DEVELOPER_AUTHENTICATION_CERTIFICATE.deref(), + DEVELOPER_ID_G1_CERTIFICATE.deref(), + DEVELOPER_ID_G2_CERTIFICATE.deref(), + SOFTWARE_UPDATE_CERTIFICATE.deref(), + TIMESTAMP_CERTIFICATE.deref(), + WORLD_WIDE_DEVELOPER_RELATIONS_G1_CERTIFICATE.deref(), + WORLD_WIDE_DEVELOPER_RELATIONS_G2_CERTIFICATE.deref(), + WORLD_WIDE_DEVELOPER_RELATIONS_G3_CERTIFICATE.deref(), + WORLD_WIDE_DEVELOPER_RELATIONS_G4_CERTIFICATE.deref(), + WORLD_WIDE_DEVELOPER_RELATIONS_G5_CERTIFICATE.deref(), + WORLD_WIDE_DEVELOPER_RELATIONS_G6_CERTIFICATE.deref(), + WORLD_WIDE_DEVELOPER_RELATIONS_G7_CERTIFICATE.deref(), + WORLD_WIDE_DEVELOPER_RELATIONS_G8_CERTIFICATE.deref(), + ] +}); + +static KNOWN_ROOTS: Lazy> = Lazy::new(|| { + vec![ + APPLE_ROOT_CA_G3_ROOT_CERTIFICATE.deref(), + APPLE_ROOT_CA_G2_ROOT_CERTIFICATE.deref(), + APPLE_INC_ROOT_CERTIFICATE.deref(), + APPLE_COMPUTER_INC_ROOT_CERTIFICATE.deref(), + ] +}); + +/// Defines all known Apple certificates. +/// +/// This crate embeds the raw certificate data for the various known +/// Apple certificate authorities, as advertised at +/// . +/// +/// This enumeration defines all the ones we know about. Instances can +/// be dereferenced into concrete [CapturedX509Certificate] to get at the underlying +/// certificate and access its metadata. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum KnownCertificate { + /// Apple Computer, Inc. Root Certificate. + /// + /// C = US, O = "Apple Computer, Inc.", OU = Apple Computer Certificate Authority, CN = Apple Root Certificate Authority + AppleComputerIncRoot, + + /// Apple Inc. Root Certificate + /// + /// C = US, O = Apple Inc., OU = Apple Certification Authority, CN = Apple Root CA + AppleRootCa, + + /// Apple Root CA - G2 Root Certificate + /// + /// CN = Apple Root CA - G2, OU = Apple Certification Authority, O = Apple Inc., C = US + AppleRootCaG2Root, + + /// Apple Root CA - G3 Root Certificate + /// + /// CN = Apple Root CA - G3, OU = Apple Certification Authority, O = Apple Inc., C = US + AppleRootCaG3Root, + + /// Apple IST CA 2 - G1 Certificate + /// + /// CN = Apple IST CA 2 - G1, OU = Certification Authority, O = Apple Inc., C = US + AppleIstCa2G1, + + /// Apple IST CA 8 - G1 Certificate + /// + /// CN = Apple IST CA 8 - G1, OU = Certification Authority, O = Apple Inc., C = US + AppleIstCa8G1, + + /// Application Integration Certificate + /// + /// C = US, O = Apple Inc., OU = Apple Certification Authority, CN = Apple Application Integration Certification Authority + ApplicationIntegration, + + /// Application Integration 2 Certificate + /// + /// CN = Apple Application Integration 2 Certification Authority, OU = Apple Certification Authority, O = Apple Inc., C = US + ApplicationIntegration2, + + /// Application Integration - G3 Certificate + /// + /// CN = Apple Application Integration CA - G3, OU = Apple Certification Authority, O = Apple Inc., C = US + ApplicationIntegrationG3, + + /// Apple Application Integration CA 5 - G1 Certificate + /// + /// CN = Apple Application Integration CA 5 - G1, OU = Apple Certification Authority, O = Apple Inc., C = US + AppleApplicationIntegrationCa5G1, + + /// Apple Application Integration CA 7 - G1 Certificate + /// + /// CN=Apple Application Integration CA 7 - G1, OU=Apple Certification Authority, O=Apple Inc., C=US + AppleApplicationIntegrationCa7G1, + + /// Developer Authentication Certificate + /// + /// CN = Developer Authentication Certification Authority, OU = Apple Worldwide Developer Relations, O = Apple Inc., C = US + DeveloperAuthentication, + + /// Developer ID - G1 Certificate + /// + /// CN = Developer ID Certification Authority, OU = Apple Certification Authority, O = Apple Inc., C = US + DeveloperIdG1, + + /// Developer ID - G2 Certificate. + /// + /// CN = Developer ID Certification Authority, OU = G2, O = Apple Inc., C = US + DeveloperIdG2, + + /// Software Update Certificate + /// + /// CN = Apple Software Update Certification Authority, OU = Certification Authority, O = Apple Inc., C = US + SoftwareUpdate, + + /// Timestamp Certificate + /// + /// CN = Apple Timestamp Certification Authority, OU = Apple Certification Authority, O = Apple Inc., C = US + Timestamp, + + /// Worldwide Developer Relations - G1 (Expiring 02/07/2023 21:48:47 UTC) Certificate + /// + /// C = US, O = Apple Inc., OU = Apple Worldwide Developer Relations, CN = Apple Worldwide Developer Relations Certification Authority + WwdrG1, + + /// Worldwide Developer Relations - G2 (Expiring 05/06/2029 23:43:24 UTC) Certificate + /// + /// CN = Apple Worldwide Developer Relations CA - G2, OU = Apple Certification Authority, O = Apple Inc., C = US + WwdrG2, + + /// Worldwide Developer Relations - G3 (Expiring 02/20/2030 00:00:00 UTC) Certificate + /// + /// CN = Apple Worldwide Developer Relations Certification Authority, OU = G3, O = Apple Inc., C = US + WwdrG3, + + /// Worldwide Developer Relations - G4 (Expiring 12/10/2030 00:00:00 UTC) Certificate + /// + /// CN = Apple Worldwide Developer Relations Certification Authority, OU = G4, O = Apple Inc., C = US + WwdrG4, + + /// Worldwide Developer Relations - G5 (Expiring 12/10/2030 00:00:00 UTC) Certificate + /// + /// CN = Apple Worldwide Developer Relations Certification Authority, OU = G5, O = Apple Inc., C = US + WwdrG5, + + /// Worldwide Developer Relations - G6 (Expiring 03/19/2036 00:00:00 UTC) Certificate + /// + /// CN = Apple Worldwide Developer Relations Certification Authority, OU = G6, O = Apple Inc., C = US + WwdrG6, + + /// Worldwide Developer Relations - G7 (Expiring 11/17/2023 20:40:52 UTC) + /// + /// C=US, O=Apple Inc., OU=G7, CN=Apple Worldwide Developer Relations Certification Authority + WwdrG7, + + /// Worldwide Developer Relations - G8 (Expiring 01/24/2025 00:00:00 UTC) + /// + /// C=US, O=Apple Inc., OU=G8, CN=Apple Worldwide Developer Relations Certification Authority + WwdrG8, +} + +impl Deref for KnownCertificate { + type Target = CapturedX509Certificate; + + fn deref(&self) -> &Self::Target { + match self { + Self::AppleComputerIncRoot => APPLE_COMPUTER_INC_ROOT_CERTIFICATE.deref(), + Self::AppleRootCa => APPLE_INC_ROOT_CERTIFICATE.deref(), + Self::AppleRootCaG2Root => APPLE_ROOT_CA_G2_ROOT_CERTIFICATE.deref(), + Self::AppleRootCaG3Root => APPLE_ROOT_CA_G3_ROOT_CERTIFICATE.deref(), + Self::AppleIstCa2G1 => APPLE_IST_CA_2_G1_CERTIFICATE.deref(), + Self::AppleIstCa8G1 => APPLE_IST_CA_8_G1_CERTIFICATE.deref(), + Self::ApplicationIntegration => APPLICATION_INTEGRATION_CERTIFICATE.deref(), + Self::ApplicationIntegration2 => APPLICATION_INTEGRATION_2_CERTIFICATE.deref(), + Self::ApplicationIntegrationG3 => APPLICATION_INTEGRATION_G3_CERTIFICATE.deref(), + Self::AppleApplicationIntegrationCa5G1 => { + APPLE_APPLICATION_INTEGRATION_CA_5_G1_CERTIFICATE.deref() + } + Self::AppleApplicationIntegrationCa7G1 => { + APPLE_APPLICATION_INTEGRATION_CA_7_G1_CERTIFICATE.deref() + } + Self::DeveloperAuthentication => DEVELOPER_AUTHENTICATION_CERTIFICATE.deref(), + Self::DeveloperIdG1 => DEVELOPER_ID_G1_CERTIFICATE.deref(), + Self::DeveloperIdG2 => DEVELOPER_ID_G2_CERTIFICATE.deref(), + Self::SoftwareUpdate => SOFTWARE_UPDATE_CERTIFICATE.deref(), + Self::Timestamp => TIMESTAMP_CERTIFICATE.deref(), + Self::WwdrG1 => WORLD_WIDE_DEVELOPER_RELATIONS_G1_CERTIFICATE.deref(), + Self::WwdrG2 => WORLD_WIDE_DEVELOPER_RELATIONS_G2_CERTIFICATE.deref(), + Self::WwdrG3 => WORLD_WIDE_DEVELOPER_RELATIONS_G3_CERTIFICATE.deref(), + Self::WwdrG4 => WORLD_WIDE_DEVELOPER_RELATIONS_G4_CERTIFICATE.deref(), + Self::WwdrG5 => WORLD_WIDE_DEVELOPER_RELATIONS_G5_CERTIFICATE.deref(), + Self::WwdrG6 => WORLD_WIDE_DEVELOPER_RELATIONS_G6_CERTIFICATE.deref(), + Self::WwdrG7 => WORLD_WIDE_DEVELOPER_RELATIONS_G7_CERTIFICATE.deref(), + Self::WwdrG8 => WORLD_WIDE_DEVELOPER_RELATIONS_G8_CERTIFICATE.deref(), + } + } +} + +impl AsRef for KnownCertificate { + fn as_ref(&self) -> &CapturedX509Certificate { + self.deref() + } +} + +impl TryFrom<&CapturedX509Certificate> for KnownCertificate { + type Error = &'static str; + + fn try_from(cert: &CapturedX509Certificate) -> Result { + let want = cert.constructed_data(); + + match cert.constructed_data() { + _ if APPLE_ROOT_CA_G3_ROOT_CERTIFICATE.constructed_data() == want => { + Ok(Self::AppleRootCaG3Root) + } + _ if APPLE_ROOT_CA_G2_ROOT_CERTIFICATE.constructed_data() == want => { + Ok(Self::AppleRootCaG2Root) + } + _ if APPLE_INC_ROOT_CERTIFICATE.constructed_data() == want => Ok(Self::AppleRootCa), + _ if APPLE_COMPUTER_INC_ROOT_CERTIFICATE.constructed_data() == want => { + Ok(Self::AppleComputerIncRoot) + } + _ if APPLE_IST_CA_2_G1_CERTIFICATE.constructed_data() == want => { + Ok(Self::AppleIstCa2G1) + } + _ if APPLE_IST_CA_8_G1_CERTIFICATE.constructed_data() == want => { + Ok(Self::AppleIstCa8G1) + } + _ if APPLICATION_INTEGRATION_CERTIFICATE.constructed_data() == want => { + Ok(Self::ApplicationIntegration) + } + _ if APPLICATION_INTEGRATION_2_CERTIFICATE.constructed_data() == want => { + Ok(Self::ApplicationIntegration2) + } + _ if APPLICATION_INTEGRATION_G3_CERTIFICATE.constructed_data() == want => { + Ok(Self::ApplicationIntegrationG3) + } + _ if APPLE_APPLICATION_INTEGRATION_CA_5_G1_CERTIFICATE.constructed_data() == want => { + Ok(Self::AppleApplicationIntegrationCa5G1) + } + _ if APPLE_APPLICATION_INTEGRATION_CA_7_G1_CERTIFICATE.constructed_data() == want => { + Ok(Self::AppleApplicationIntegrationCa7G1) + } + _ if DEVELOPER_AUTHENTICATION_CERTIFICATE.constructed_data() == want => { + Ok(Self::DeveloperAuthentication) + } + _ if DEVELOPER_ID_G1_CERTIFICATE.constructed_data() == want => Ok(Self::DeveloperIdG1), + _ if DEVELOPER_ID_G2_CERTIFICATE.constructed_data() == want => Ok(Self::DeveloperIdG2), + _ if SOFTWARE_UPDATE_CERTIFICATE.constructed_data() == want => Ok(Self::SoftwareUpdate), + _ if TIMESTAMP_CERTIFICATE.constructed_data() == want => Ok(Self::Timestamp), + _ if WORLD_WIDE_DEVELOPER_RELATIONS_G1_CERTIFICATE.constructed_data() == want => { + Ok(Self::WwdrG1) + } + _ if WORLD_WIDE_DEVELOPER_RELATIONS_G2_CERTIFICATE.constructed_data() == want => { + Ok(Self::WwdrG2) + } + _ if WORLD_WIDE_DEVELOPER_RELATIONS_G3_CERTIFICATE.constructed_data() == want => { + Ok(Self::WwdrG3) + } + _ if WORLD_WIDE_DEVELOPER_RELATIONS_G4_CERTIFICATE.constructed_data() == want => { + Ok(Self::WwdrG4) + } + _ if WORLD_WIDE_DEVELOPER_RELATIONS_G5_CERTIFICATE.constructed_data() == want => { + Ok(Self::WwdrG5) + } + _ if WORLD_WIDE_DEVELOPER_RELATIONS_G6_CERTIFICATE.constructed_data() == want => { + Ok(Self::WwdrG6) + } + _ if WORLD_WIDE_DEVELOPER_RELATIONS_G7_CERTIFICATE.constructed_data() == want => { + Ok(Self::WwdrG7) + } + _ if WORLD_WIDE_DEVELOPER_RELATIONS_G8_CERTIFICATE.constructed_data() == want => { + Ok(Self::WwdrG8) + } + _ => Err("certificate not found"), + } + } +} + +impl KnownCertificate { + /// Obtain a slice of all known [KnownCertificate]. + /// + /// If you want to iterate over all certificates and find one, you can use + /// this. + pub fn all() -> &'static [&'static CapturedX509Certificate] { + KNOWN_CERTIFICATES.deref().as_ref() + } + + /// All of Apple's known root certificate authority certificates. + pub fn all_roots() -> &'static [&'static CapturedX509Certificate] { + KNOWN_ROOTS.deref() + } +} + +#[cfg(test)] +mod test { + use { + super::*, + crate::certificate::{AppleCertificate, CertificateAuthorityExtension}, + }; + + #[test] + fn all() { + for cert in KnownCertificate::all() { + assert!(cert.subject_common_name().is_some()); + assert!(KnownCertificate::try_from(*cert).is_ok()); + } + } + + #[test] + fn apple_root_ca() { + assert!(APPLE_INC_ROOT_CERTIFICATE.is_apple_root_ca()); + assert!(!APPLE_INC_ROOT_CERTIFICATE.is_apple_intermediate_ca()); + assert!(APPLE_COMPUTER_INC_ROOT_CERTIFICATE.is_apple_root_ca()); + assert!(!APPLE_COMPUTER_INC_ROOT_CERTIFICATE.is_apple_intermediate_ca()); + assert!(APPLE_ROOT_CA_G2_ROOT_CERTIFICATE.is_apple_root_ca()); + assert!(!APPLE_ROOT_CA_G2_ROOT_CERTIFICATE.is_apple_intermediate_ca()); + assert!(APPLE_ROOT_CA_G3_ROOT_CERTIFICATE.is_apple_root_ca()); + assert!(!APPLE_ROOT_CA_G3_ROOT_CERTIFICATE.is_apple_intermediate_ca()); + + assert!(!WORLD_WIDE_DEVELOPER_RELATIONS_G3_CERTIFICATE.is_apple_root_ca()); + assert!(WORLD_WIDE_DEVELOPER_RELATIONS_G3_CERTIFICATE.is_apple_intermediate_ca()); + + let wanted = [APPLE_INC_ROOT_CERTIFICATE.deref(), + APPLE_COMPUTER_INC_ROOT_CERTIFICATE.deref(), + APPLE_ROOT_CA_G2_ROOT_CERTIFICATE.deref(), + APPLE_ROOT_CA_G3_ROOT_CERTIFICATE.deref()]; + + for cert in KnownCertificate::all() { + if wanted.contains(cert) { + continue; + } + + assert!(!cert.is_apple_root_ca()); + assert!(cert.is_apple_intermediate_ca()); + } + } + + #[test] + fn intermediate_have_apple_ca_extension() { + // All intermediate certs should have OIDs identifying them as such. + for cert in KnownCertificate::all() + .iter() + .filter(|cert| !cert.is_apple_root_ca()) + // There are some intermediate certificates signed by GeoTrust. Filter them out + // as well. + .filter(|cert| { + cert.issuer_name() + .iter_common_name() + .all(|atv| !atv.to_string().unwrap().contains("GeoTrust")) + }) + { + assert!(!cert.apple_ca_extensions().is_empty()); + } + + // Let's spot check a few. + assert_eq!( + KnownCertificate::DeveloperIdG1 + .apple_ca_extensions() + .first(), + Some(&CertificateAuthorityExtension::DeveloperId) + ); + assert_eq!( + KnownCertificate::DeveloperIdG2 + .apple_ca_extensions() + .first(), + Some(&CertificateAuthorityExtension::DeveloperId) + ); + assert_eq!( + KnownCertificate::WwdrG1.apple_ca_extensions().first(), + Some(&CertificateAuthorityExtension::AppleWorldwideDeveloperRelations) + ); + assert_eq!( + KnownCertificate::WwdrG2.apple_ca_extensions().first(), + Some(&CertificateAuthorityExtension::AppleWorldwideDeveloperRelationsG2) + ); + assert_eq!( + KnownCertificate::WwdrG3.apple_ca_extensions().first(), + Some(&CertificateAuthorityExtension::AppleWorldwideDeveloperRelations) + ); + assert_eq!( + KnownCertificate::WwdrG4.apple_ca_extensions().first(), + Some(&CertificateAuthorityExtension::AppleWorldwideDeveloperRelations) + ); + assert_eq!( + KnownCertificate::WwdrG5.apple_ca_extensions().first(), + Some(&CertificateAuthorityExtension::AppleWorldwideDeveloperRelations) + ); + assert_eq!( + KnownCertificate::WwdrG6.apple_ca_extensions().first(), + Some(&CertificateAuthorityExtension::AppleWorldwideDeveloperRelations) + ); + assert_eq!( + KnownCertificate::WwdrG7.apple_ca_extensions().first(), + Some(&CertificateAuthorityExtension::AppleWorldwideDeveloperRelations) + ); + assert_eq!( + KnownCertificate::WwdrG8.apple_ca_extensions().first(), + Some(&CertificateAuthorityExtension::AppleWorldwideDeveloperRelations) + ); + } + + #[test] + fn chaining() { + let relevant = KnownCertificate::all() + .iter() + .filter(|cert| { + cert.issuer_name() + .iter_common_name() + .all(|atv| !atv.to_string().unwrap().contains("GeoTrust")) + }) + .filter(|cert| { + cert.constructed_data() != APPLICATION_INTEGRATION_G3_CERTIFICATE.constructed_data() + && cert.constructed_data() + != APPLE_APPLICATION_INTEGRATION_CA_5_G1_CERTIFICATE.constructed_data() + }); + + for cert in relevant { + let chain = cert.resolve_signing_chain(KnownCertificate::all().iter().copied()); + let apple_chain = cert.apple_issuing_chain(); + assert_eq!(chain.len(), apple_chain.len()); + } + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/bundle_signing.rs b/3rdparty/apple-codesign-0.29.0/src/bundle_signing.rs new file mode 100644 index 00000000..b6e3e864 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/bundle_signing.rs @@ -0,0 +1,786 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Functionality for signing Apple bundles. + +use { + crate::{ + code_directory::CodeDirectoryBlob, + code_requirement::{CodeRequirementExpression, RequirementType}, + code_resources::{normalized_resources_path, CodeResourcesBuilder, CodeResourcesRule}, + cryptography::DigestType, + embedded_signature::{Blob, BlobData}, + error::AppleCodesignError, + macho::MachFile, + macho_signing::{write_macho_file, MachOSigner}, + signing::path_identifier, + signing_settings::{SettingsScope, SigningSettings}, + }, + apple_bundles::{BundlePackageType, DirectoryBundle}, + log::{debug, info, warn}, + simple_file_manifest::create_symlink, + std::{ + borrow::Cow, + collections::{BTreeMap, BTreeSet}, + io::Write, + path::{Path, PathBuf}, + }, +}; + +/// Copy a bundle's contents to a destination directory. +/// +/// This does not use the CodeResources rules for a bundle. Rather, it +/// blindly copies all files in the bundle. This means that excluded files +/// can be copied. +/// +/// Returns the set of bundle-relative paths that are installed. +pub fn copy_bundle( + bundle: &DirectoryBundle, + dest_dir: &Path, +) -> Result, AppleCodesignError> { + let settings = SigningSettings::default(); + + let mut context = BundleSigningContext { + dest_dir: dest_dir.to_path_buf(), + settings: &settings, + previously_installed_paths: Default::default(), + installed_paths: Default::default(), + }; + + for file in bundle + .files(false) + .map_err(AppleCodesignError::DirectoryBundle)? + { + context.install_file(file.absolute_path(), file.relative_path())?; + } + + Ok(context.installed_paths) +} + +/// A primitive for signing an Apple bundle. +/// +/// This type handles the high-level logic of signing an Apple bundle (e.g. +/// a `.app` or `.framework` directory with a well-defined structure). +/// +/// This type handles the signing of nested bundles (if present) such that +/// they chain to the main bundle's signature. +pub struct BundleSigner { + /// All the bundles being signed, indexed by relative path. + bundles: BTreeMap, SingleBundleSigner>, +} + +impl BundleSigner { + /// Construct a new instance given the path to an on-disk bundle. + /// + /// The path should be the root directory of the bundle. e.g. `MyApp.app`. + pub fn new_from_path(path: impl AsRef) -> Result { + let main_bundle = DirectoryBundle::new_from_path(path.as_ref()) + .map_err(AppleCodesignError::DirectoryBundle)?; + let root_bundle_path = main_bundle.root_dir().to_path_buf(); + + let mut bundles = BTreeMap::default(); + + bundles.insert(None, SingleBundleSigner::new(root_bundle_path, main_bundle)); + + Ok(Self { bundles }) + } + + /// Find bundles in subdirectories of the main bundle and mark them for signing. + pub fn collect_nested_bundles(&mut self) -> Result<(), AppleCodesignError> { + let (root_bundle_path, nested) = { + let main = self.bundles.get(&None).expect("main bundle should exist"); + + let nested = main + .bundle + .nested_bundles(true) + .map_err(AppleCodesignError::DirectoryBundle)?; + + (main.root_bundle_path.clone(), nested) + }; + + self.bundles.extend( + nested.into_iter() + .filter(|(k, bundle)| { + // Our bundle classifier is very aggressive about annotating directories + // as bundles. Pretty much anything with an Info.plist can get through. + // We apply additional filtering here so we only emit bundles that can + // be signed. + // + // A better solution here is to use the CodeResources rule + // based file walker to look for directories with the "nested" flag. + // If a bundle-looking directory exists outside of a "nested" rule, + // it probably shouldn't be signed. + + let (has_package_type, has_signable_package_type) = if let Ok(Some(pt)) = bundle.info_plist_key_string("CFBundlePackageType") { + (true, pt != "dSYM") + } else { + (false, false) + }; + + let has_bundle_identifier = matches!(bundle.info_plist_key_string("CFBundleIdentifier"), Ok(Some(_))); + + match (has_package_type, has_signable_package_type, has_bundle_identifier) { + (true, false, _) =>{ + debug!("{k} discarded because its CFBundlePackageType is not signable"); + false + } + (true, _, true) => { + // It quacks like a bundle. + true + } + (false, _, false) => { + // This looks like a naked Info.plist. + debug!("{k} discarded as a signable bundle because its Info.plist lacks CFBundlePackageType and CFBundleIdentifier"); + false + } + (true, _, false) => { + info!("{k} has an Info.plist with a CFBundlePackageType but not a CFBundleIdentifier; we'll try to sign it but we recommend adding a CFBundleIdentifier"); + true + } + (false, _, true) => { + info!("{k} has an Info.plist with a CFBundleIdentifier but without a CFBundlePackageType; we'll try to sign it but we recommend adding a CFBundlePackageType"); + true + } + } + }) + .map(|(k, bundle)| { + ( + Some(k), + SingleBundleSigner::new(root_bundle_path.clone(), bundle), + ) + }) + ); + + Ok(()) + } + + /// Write a signed bundle to the given destination directory. + /// + /// The destination directory can be the same as the source directory. However, + /// if this is done and an error occurs in the middle of signing, the bundle + /// may be left in an inconsistent or corrupted state and may not be usable. + pub fn write_signed_bundle( + &self, + dest_dir: impl AsRef, + settings: &SigningSettings, + ) -> Result { + let dest_dir = dest_dir.as_ref(); + + // We need to sign the leaf-most bundles first since a parent bundle may need + // to record information about the child in its signature. + let mut bundles = self + .bundles + .iter() + .filter_map(|(rel, bundle)| rel.as_ref().map(|rel| (rel, bundle))) + .collect::>(); + + // This won't preserve alphabetical order. But since the input was stable, output + // should be deterministic. + bundles.sort_by(|(a, _), (b, _)| b.len().cmp(&a.len())); + + if !bundles.is_empty() { + if settings.shallow() { + warn!("{} nested bundles will be copied instead of signed because shallow signing enabled:", bundles.len()); + } else { + warn!( + "signing {} nested bundles in the following order:", + bundles.len() + ); + } + for bundle in &bundles { + warn!("{}", bundle.0); + } + } + + // We keep track of root relative input paths that have been installed so we can + // skip installing in case we encounter the path again when signing a parent + // bundle. + // + // If we fail to do this, during non-shallow signing operations we may descend + // into a child bundle that is outside a directory with the "nested" flag set. + // Files in non-"nested" directories need to be sealed in CodeResources files + // as regular files, not bundles. So we need to walk into the child bundle in + // this scenario. But during the walk we want to prevent already installed files + // from being processed again. + // + // In the case of Mach-O binaries in the above non-"nested" directory scenario, + // excluding already signed files prevents the Mach-O from being signed again. + // Signing the Mach-O twice could invalidate the bundle's signature and/or result + // in incorrect signing settings since a bundle's main binary wouldn't be + // recognized as such since we're outside the context of that bundle. + // + // In all cases, we prevent redundant work installing files if a file is seen + // twice. + let mut installed_rel_paths = BTreeSet::::new(); + + for (rel, nested) in bundles { + let rel_path = PathBuf::from(rel); + + let nested_dest_dir = dest_dir.join(rel); + warn!("entering nested bundle {}", rel,); + + let bundle_installed_rel_paths = if settings.shallow() { + warn!("shallow signing enabled; bundle will be copied instead of signed"); + copy_bundle(&nested.bundle, &nested_dest_dir)? + } else if settings.path_exclusion_pattern_matches(rel) { + // If we excluded this bundle from signing, just copy all the files. + warn!("bundle is in exclusion list; it will be copied instead of signed"); + copy_bundle(&nested.bundle, &nested_dest_dir)? + } else { + let bundle_installed = installed_rel_paths + .iter() + .filter_map(|p| { + if let Ok(p) = p.strip_prefix(&rel_path) { + Some(p.to_path_buf()) + } else { + None + } + }) + .collect::>(); + + let info = nested.write_signed_bundle( + nested_dest_dir, + &settings.as_nested_bundle_settings(rel), + bundle_installed, + )?; + + info.installed_rel_paths + }; + + for p in bundle_installed_rel_paths { + installed_rel_paths.insert(rel_path.join(p).to_path_buf()); + } + + warn!("leaving nested bundle {}", rel); + } + + let main = self + .bundles + .get(&None) + .expect("main bundle should have a key"); + + Ok(main + .write_signed_bundle(dest_dir, settings, installed_rel_paths)? + .bundle) + } +} + +/// Metadata about a signed Mach-O file or bundle. +/// +/// If referring to a bundle, the metadata refers to the 1st Mach-O in the +/// bundle's main executable. +/// +/// This contains enough metadata to construct references to the file/bundle +/// in [crate::code_resources::CodeResources] files. +pub struct SignedMachOInfo { + /// Raw data constituting the code directory blob. + /// + /// Is typically digested to construct a . + pub code_directory_blob: Vec, + + /// Designated code requirements string. + /// + /// Typically occupies a `requirement` in a + /// [crate::code_resources::CodeResources] file. + pub designated_code_requirement: Option, +} + +impl SignedMachOInfo { + /// Parse Mach-O data to obtain an instance. + pub fn parse_data(data: &[u8]) -> Result { + // Initial Mach-O's signature data is used. + let mach = MachFile::parse(data)?; + let macho = mach.nth_macho(0)?; + + let signature = macho + .code_signature()? + .ok_or(AppleCodesignError::BinaryNoCodeSignature)?; + + let code_directory_blob = signature.preferred_code_directory()?.to_blob_bytes()?; + + let designated_code_requirement = if let Some(requirements) = + signature.code_requirements()? + { + if let Some(designated) = requirements.requirements.get(&RequirementType::Designated) { + let req = designated.parse_expressions()?; + + Some(format!("{}", req[0])) + } else { + // In case no explicit requirements has been set, we use current file cdhashes. + let mut requirement_expr = None; + + // We record the 20 byte digests of every code directory in every + // Mach-O. + // Note: Apple's tooling appears to always record the x86-64 Mach-O + // first, even if it isn't first in the universal binary. Since we're + // dealing with a bunch of OR'd code requirements expressions, we don't + // believe this difference is worth caring about. + for macho in mach.iter_macho() { + for (_, cd) in macho + .code_signature()? + .ok_or(AppleCodesignError::BinaryNoCodeSignature)? + .all_code_directories()? + { + let digest_type = if cd.digest_type == DigestType::Sha256 { + DigestType::Sha256Truncated + } else { + cd.digest_type + }; + + let digest = digest_type.digest_data(&cd.to_blob_bytes()?)?; + let expression = Box::new(CodeRequirementExpression::CodeDirectoryHash( + Cow::from(digest), + )); + + if let Some(left_part) = requirement_expr { + requirement_expr = Some(Box::new(CodeRequirementExpression::Or( + left_part, expression, + ))) + } else { + requirement_expr = Some(expression); + } + } + } + + Some(format!( + "{}", + requirement_expr.expect("a Mach-O should have been present") + )) + } + } else { + None + }; + + Ok(SignedMachOInfo { + code_directory_blob, + designated_code_requirement, + }) + } + + /// Resolve the parsed code directory from stored data. + pub fn code_directory(&self) -> Result>, AppleCodesignError> { + let blob = BlobData::from_blob_bytes(&self.code_directory_blob)?; + + if let BlobData::CodeDirectory(cd) = blob { + Ok(cd) + } else { + Err(AppleCodesignError::BinaryNoCodeSignature) + } + } + + /// Resolve the notarization ticket record name for this Mach-O file. + pub fn notarization_ticket_record_name(&self) -> Result { + let cd = self.code_directory()?; + + let digest_type: u8 = cd.digest_type.into(); + + let mut digest = cd.digest_with(cd.digest_type)?; + + // Digests appear to be truncated at 20 bytes / 40 characters. + digest.truncate(20); + + let digest = hex::encode(digest); + + // Unsure what the leading `2/` means. + Ok(format!("2/{digest_type}/{digest}")) + } +} + +/// Holds state and helper methods to facilitate signing a bundle. +pub struct BundleSigningContext<'a, 'key> { + /// Settings for this bundle. + pub settings: &'a SigningSettings<'key>, + /// Where the bundle is getting installed to. + pub dest_dir: PathBuf, + /// Bundle relative paths of files that have already been installed. + /// + /// The already-present destination file content should be used for sealing. + pub previously_installed_paths: BTreeSet, + /// Bundle relative paths of files that are installed by this signing operation. + pub installed_paths: BTreeSet, +} + +impl<'a, 'key> BundleSigningContext<'a, 'key> { + /// Install a file (regular or symlink) in the destination directory. + pub fn install_file( + &mut self, + source_path: &Path, + bundle_rel_path: &Path, + ) -> Result { + let dest_path = self.dest_dir.join(bundle_rel_path); + + if source_path != dest_path { + // Remove an existing file before installing the replacement. In + // the case of symlinks this is required due to how symlink creation + // works. + if dest_path.symlink_metadata().is_ok() { + std::fs::remove_file(&dest_path)?; + } + + if let Some(parent) = dest_path.parent() { + std::fs::create_dir_all(parent)?; + } + + let metadata = source_path.symlink_metadata()?; + let mtime = filetime::FileTime::from_last_modification_time(&metadata); + + if metadata.file_type().is_symlink() { + let target = std::fs::read_link(source_path)?; + info!( + "replicating symlink {} -> {}", + dest_path.display(), + target.display() + ); + create_symlink(&dest_path, target)?; + filetime::set_symlink_file_times( + &dest_path, + filetime::FileTime::from_last_access_time(&metadata), + mtime, + )?; + } else { + info!( + "copying file {} -> {}", + source_path.display(), + dest_path.display() + ); + // TODO consider stripping XATTR_RESOURCEFORK_NAME and XATTR_FINDERINFO_NAME. + std::fs::copy(source_path, &dest_path)?; + filetime::set_file_mtime(&dest_path, mtime)?; + } + } + + // Always record the installation even if we no-op. The intent of the + // annotation is to mark files that are already present in the destination + // bundle. + self.installed_paths.insert(bundle_rel_path.to_path_buf()); + + Ok(dest_path) + } + + /// Sign a Mach-O file and ensure its new content is installed. + /// + /// Returns Mach-O metadata which can be recorded in a CodeResources file. + + pub fn sign_and_install_macho( + &mut self, + source_path: &Path, + bundle_rel_path: &Path, + ) -> Result<(PathBuf, SignedMachOInfo), AppleCodesignError> { + warn!("signing Mach-O file {}", bundle_rel_path.display()); + + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let mut perms = std::fs::metadata(source_path)?.permissions(); + perms.set_mode(0o755); + std::fs::set_permissions(source_path, perms)?; + } + + let macho_data = std::fs::read(source_path)?; + let signer = MachOSigner::new(&macho_data)?; + + let mut settings = self + .settings + .as_bundle_macho_settings(bundle_rel_path.to_string_lossy().as_ref()); + + // When signing a Mach-O in the context of a bundle, always define the + // binary identifier from the filename so everything is consistent. + // Unless an existing setting overrides it, of course. + if settings.binary_identifier(SettingsScope::Main).is_none() { + let identifier = path_identifier(bundle_rel_path)?; + info!("setting binary identifier based on path: {}", identifier); + + settings.set_binary_identifier(SettingsScope::Main, &identifier); + } + + settings.import_settings_from_macho(&macho_data)?; + + let mut new_data = Vec::::with_capacity(macho_data.len() + 2_usize.pow(17)); + signer.write_signed_binary(&settings, &mut new_data)?; + + let dest_path = self.dest_dir.join(bundle_rel_path); + + info!("writing Mach-O to {}", dest_path.display()); + write_macho_file(source_path, &dest_path, &new_data)?; + + let info = SignedMachOInfo::parse_data(&new_data)?; + + self.installed_paths.insert(bundle_rel_path.to_path_buf()); + + Ok((dest_path, info)) + } +} + +/// Holds metadata describing the result of a bundle signing operation. +pub struct BundleSigningInfo { + /// The signed bundle. + pub bundle: DirectoryBundle, + + /// Bundle relative paths of files that are installed by this signing operation. + pub installed_rel_paths: BTreeSet, +} + +/// 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 { + /// Path of the root bundle being signed. + root_bundle_path: PathBuf, + + /// The bundle being signed. + bundle: DirectoryBundle, +} + +impl SingleBundleSigner { + /// Construct a new instance. + pub fn new(root_bundle_path: PathBuf, bundle: DirectoryBundle) -> Self { + Self { + root_bundle_path, + bundle, + } + } + + /// Write a signed bundle to the given directory. + pub fn write_signed_bundle( + &self, + dest_dir: impl AsRef, + settings: &SigningSettings, + previously_installed_paths: BTreeSet, + ) -> Result { + 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() { + info!("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 mut context = BundleSigningContext { + dest_dir: dest_dir.to_path_buf(), + settings, + previously_installed_paths, + installed_paths: Default::default(), + }; + + for file in self + .bundle + .files(false) + .map_err(AppleCodesignError::DirectoryBundle)? + { + context.install_file(file.absolute_path(), file.relative_path())?; + } + + let bundle = DirectoryBundle::new_from_path(dest_dir) + .map_err(AppleCodesignError::DirectoryBundle)?; + + return Ok(BundleSigningInfo { + bundle, + installed_rel_paths: context.installed_paths, + }); + } else { + info!("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") + }; + + 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() { + let need_sha1_sha256 = if let Some(targeting) = macho.find_targeting()? { + let sha256_version = targeting.platform.sha256_digest_support()?; + + !sha256_version.matches(&targeting.minimum_os_version) + } else { + true + }; + + if need_sha1_sha256 + && resources_digests != vec![DigestType::Sha1, DigestType::Sha256] + { + info!( + "activating SHA-1 + SHA-256 signing due to requirements of main executable" + ); + resources_digests = vec![DigestType::Sha1, DigestType::Sha256]; + break; + } + } + } + + info!("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()); + // Ignore store manifest directory. + resources_builder.add_exclusion_rule(CodeResourcesRule::new("^_MASReceipt$")?.exclude()); + + // The bundle's main executable file's code directory needs to hold a + // digest of the CodeResources file for the bundle. Therefore it needs to + // be handled last. We add an exclusion rule to prevent the directory walker + // from touching this file. + if let Some(main_exe) = &main_exe { + // Also seal the resources normalized path, just in case it is different. + resources_builder.add_exclusion_rule( + CodeResourcesRule::new(format!( + "^{}$", + regex::escape(&normalized_resources_path(main_exe.relative_path())) + ))? + .exclude(), + ); + } + + let mut context = BundleSigningContext { + dest_dir: dest_dir_root.clone(), + settings, + previously_installed_paths, + installed_paths: Default::default(), + }; + + resources_builder.walk_and_seal_directory( + &self.root_bundle_path, + self.bundle.root_dir(), + &mut context, + )?; + + let info_plist_data = std::fs::read(self.bundle.info_plist_path())?; + + // The resources are now sealed. Write out that XML file. + let code_resources_path = dest_dir.join("_CodeSignature").join("CodeResources"); + info!( + "writing sealed resources to {}", + code_resources_path.display() + ); + std::fs::create_dir_all(code_resources_path.parent().unwrap())?; + let mut resources_data = Vec::::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()); + + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let mut perms = std::fs::metadata(exe.absolute_path())?.permissions(); + perms.set_mode(0o755); + std::fs::set_permissions(exe.absolute_path(), perms)?; + } + + let macho_data = std::fs::read(exe.absolute_path())?; + let signer = MachOSigner::new(&macho_data)?; + + let mut settings = settings + .as_bundle_main_executable_settings(exe.relative_path().to_string_lossy().as_ref()); + + // 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.set_code_resources_data(SettingsScope::Main, resources_data); + settings.set_info_plist_data(SettingsScope::Main, info_plist_data); + + // Important: manually override all settings before calling this so that + // explicitly set settings are always used and we don't get misleading logs. + // If we set settings after the fact, we may fail to define settings on a + // sub-scope, leading the overwrite to not being used. + settings.import_settings_from_macho(&macho_data)?; + + let mut new_data = Vec::::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)?; + + context + .installed_paths + .insert(exe.relative_path().to_path_buf()); + } else { + warn!("bundle has no main executable to sign specially"); + } + + let bundle = DirectoryBundle::new_from_path(&dest_dir_root) + .map_err(AppleCodesignError::DirectoryBundle)?; + + Ok(BundleSigningInfo { + bundle, + installed_rel_paths: context.installed_paths, + }) + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/certificate.rs b/3rdparty/apple-codesign-0.29.0/src/certificate.rs new file mode 100644 index 00000000..197032fe --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/certificate.rs @@ -0,0 +1,1824 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Functionality related to certificates. + +use { + crate::{apple_certificates::KnownCertificate, error::AppleCodesignError}, + bcder::{ + encode::{PrimitiveContent, Values}, + ConstOid, Oid, + }, + bytes::Bytes, + pkcs8::EncodePrivateKey, + std::{ + fmt::{Display, Formatter}, + str::FromStr, + }, + x509_certificate::{ + certificate::KeyUsage, rfc4519::OID_COUNTRY_NAME, CapturedX509Certificate, + InMemorySigningKeyPair, KeyAlgorithm, X509CertificateBuilder, + }, +}; + +/// Extended Key Usage extension. +/// +/// 2.5.29.37 +const OID_EXTENDED_KEY_USAGE: ConstOid = Oid(&[85, 29, 37]); + +/// Extended Key Usage purpose for code signing. +/// +/// 1.3.6.1.5.5.7.3.3 +const OID_EKU_PURPOSE_CODE_SIGNING: ConstOid = Oid(&[43, 6, 1, 5, 5, 7, 3, 3]); + +/// Extended Key Usage for purpose of `Safari Developer`. +/// +/// 1.2.840.113635.100.4.8 +const OID_EKU_PURPOSE_SAFARI_DEVELOPER: ConstOid = Oid(&[42, 134, 72, 134, 247, 99, 100, 4, 8]); + +/// Extended Key Usage for purpose of `3rd Party Mac Developer Installer`. +/// +/// 1.2.840.113635.100.4.9 +const OID_EKU_PURPOSE_3RD_PARTY_MAC_DEVELOPER_INSTALLER: ConstOid = + Oid(&[42, 134, 72, 134, 247, 99, 100, 4, 9]); + +/// Extended Key Usage for purpose of `Developer ID Installer`. +/// +/// 1.2.840.113635.100.4.13 +const OID_EKU_PURPOSE_DEVELOPER_ID_INSTALLER: ConstOid = + Oid(&[42, 134, 72, 134, 247, 99, 100, 4, 13]); + +/// All OIDs known for extended key usage. +const ALL_OID_EKUS: &[&ConstOid; 4] = &[ + &OID_EKU_PURPOSE_CODE_SIGNING, + &OID_EKU_PURPOSE_SAFARI_DEVELOPER, + &OID_EKU_PURPOSE_3RD_PARTY_MAC_DEVELOPER_INSTALLER, + &OID_EKU_PURPOSE_DEVELOPER_ID_INSTALLER, +]; + +/// Extension for `Apple Signing`. +/// +/// 1.2.840.113635.100.6.1.1 +const OID_EXTENSION_APPLE_SIGNING: ConstOid = Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 1]); + +/// Extension for `iPhone Developer`. +/// +/// 1.2.840.113635.100.6.1.2 +const OID_EXTENSION_IPHONE_DEVELOPER: ConstOid = Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 2]); + +/// Extension for `Apple iPhone OS Application Signing` +/// +/// 1.2.840.113635.100.6.1.3 +const OID_EXTENSION_IPHONE_OS_APPLICATION_SIGNING: ConstOid = + Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 3]); + +/// Extension for `Apple Developer Certificate (Submission)`. +/// +/// May also be referred to as `iPhone Distribution`. +/// +/// 1.2.840.113635.100.6.1.4 +const OID_EXTENSION_APPLE_DEVELOPER_CERTIFICATE_SUBMISSION: ConstOid = + Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 4]); + +/// Extension for `Safari Developer`. +/// +/// 1.2.840.113635.100.6.1.5 +const OID_EXTENSION_SAFARI_DEVELOPER: ConstOid = Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 5]); + +/// Extension for `Apple iPhone OS VPN Signing` +/// +/// 1.2.840.113635.100.6.1.6 +const OID_EXTENSION_IPHONE_OS_VPN_SIGNING: ConstOid = + Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 6]); + +/// Extension for `Apple Mac App Signing (Development)`. +/// +/// May also appear as `3rd Party Mac Developer Application`. +/// +/// 1.2.840.113635.100.6.1.7 +const OID_EXTENSION_APPLE_MAC_APP_SIGNING_DEVELOPMENT: ConstOid = + Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 7]); + +/// Extension for `Apple Mac App Signing Submission`. +/// +/// 1.2.840.113635.100.6.1.8 +const OID_EXTENSION_APPLE_MAC_APP_SIGNING_SUBMISSION: ConstOid = + Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 8]); + +/// Extension for `Mac App Store Code Signing`. +/// +/// 1.2.840.113635.100.6.1.9 +const OID_EXTENSION_APPLE_MAC_APP_STORE_CODE_SIGNING: ConstOid = + Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 9]); + +/// Extension for `Mac App Store Installer Signing`. +/// +/// 1.2.840.113635.100.6.1.10 +const OID_EXTENSION_APPLE_MAC_APP_STORE_INSTALLER_SIGNING: ConstOid = + Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 10]); + +// 1.2.840.113635.100.6.1.11 is unknown. + +/// Extension for `Mac Developer`. +/// +/// 1.2.840.113635.100.6.1.12 +const OID_EXTENSION_MAC_DEVELOPER: ConstOid = Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 12]); + +/// Extension for `Developer ID Application`. +/// +/// 1.2.840.113635.100.6.1.13 +const OID_EXTENSION_DEVELOPER_ID_APPLICATION: ConstOid = + Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 13]); + +/// Extension for `Developer ID Installer`. +/// +/// 1.2.840.113635.100.6.1.14 +const OID_EXTENSION_DEVELOPER_ID_INSTALLER: ConstOid = + Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 14]); + +// 1.2.840.113635.100.6.1.15 looks to have something to do with core OS functionality, +// as it appears in search results for hacking Apple OS booting. + +/// Extension for `Apple Pay Passbook Signing` +/// +/// 1.2.840.113635.100.6.1.16 +const OID_EXTENSION_PASSBOOK_SIGNING: ConstOid = Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 16]); + +/// Extension for `Web Site Push Notifications Signing` +/// +/// 1.2.840.113635.100.6.1.17 +const OID_EXTENSION_WEBSITE_PUSH_NOTIFICATION_SIGNING: ConstOid = + Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 17]); + +/// Extension for `Developer ID Kernel`. +/// +/// 1.2.840.113635.100.6.1.18 +const OID_EXTENSION_DEVELOPER_ID_KERNEL: ConstOid = + Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 18]); + +/// Extension for `Developer ID Date`. +/// +/// This OID doesn't have a description in Apple tooling. But it +/// holds a UtcDate (with hours, minutes, and seconds all set to 0) and seems to +/// denote a date constraint to apply to validation. This is likely used +/// to validating timestamping constrains for certificate validity. +/// +/// 1.2.840.113635.100.6.1.33 +const OID_EXTENSION_DEVELOPER_ID_DATE: ConstOid = Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 33]); + +/// Extension for `TestFlight`. +/// +/// 1.2.840.113635.100.6.1.25.1 +const OID_EXTENSION_TEST_FLIGHT: ConstOid = Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 25, 1]); + +/// All OIDs associated with non Certificate Authority extensions. +const ALL_OID_NON_CA_EXTENSIONS: &[&ConstOid; 18] = &[ + &OID_EXTENSION_APPLE_SIGNING, + &OID_EXTENSION_IPHONE_DEVELOPER, + &OID_EXTENSION_IPHONE_OS_APPLICATION_SIGNING, + &OID_EXTENSION_APPLE_DEVELOPER_CERTIFICATE_SUBMISSION, + &OID_EXTENSION_SAFARI_DEVELOPER, + &OID_EXTENSION_IPHONE_OS_VPN_SIGNING, + &OID_EXTENSION_APPLE_MAC_APP_SIGNING_DEVELOPMENT, + &OID_EXTENSION_APPLE_MAC_APP_SIGNING_SUBMISSION, + &OID_EXTENSION_APPLE_MAC_APP_STORE_CODE_SIGNING, + &OID_EXTENSION_APPLE_MAC_APP_STORE_INSTALLER_SIGNING, + &OID_EXTENSION_MAC_DEVELOPER, + &OID_EXTENSION_DEVELOPER_ID_APPLICATION, + &OID_EXTENSION_DEVELOPER_ID_INSTALLER, + &OID_EXTENSION_PASSBOOK_SIGNING, + &OID_EXTENSION_WEBSITE_PUSH_NOTIFICATION_SIGNING, + &OID_EXTENSION_DEVELOPER_ID_KERNEL, + &OID_EXTENSION_DEVELOPER_ID_DATE, + &OID_EXTENSION_TEST_FLIGHT, +]; + +/// UserID. +/// +/// 0.9.2342.19200300.100.1.1 +pub const OID_USER_ID: ConstOid = Oid(&[9, 146, 38, 137, 147, 242, 44, 100, 1, 1]); + +/// OID used for email address in RDN in Apple generated code signing certificates. +const OID_EMAIL_ADDRESS: ConstOid = Oid(&[42, 134, 72, 134, 247, 13, 1, 9, 1]); + +/// Apple Worldwide Developer Relations. +/// +/// 1.2.840.113635.100.6.2.1 +const OID_CA_EXTENSION_APPLE_WORLDWIDE_DEVELOPER_RELATIONS: ConstOid = + Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 2, 1]); + +/// Apple Application Integration. +/// +/// 1.2.840.113635.100.6.2.3 +const OID_CA_EXTENSION_APPLE_APPLICATION_INTEGRATION: ConstOid = + Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 2, 3]); + +/// Developer ID Certification Authority +/// +/// 1.2.840.113635.100.6.2.6 +const OID_CA_EXTENSION_DEVELOPER_ID: ConstOid = Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 2, 6]); + +/// Apple Timestamp. +/// +/// 1.2.840.113635.100.6.2.9 +const OID_CA_EXTENSION_APPLE_TIMESTAMP: ConstOid = Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 2, 9]); + +/// Developer Authentication Certification Authority. +/// +/// 1.2.840.113635.100.6.2.11 +const OID_CA_EXTENSION_DEVELOPER_AUTHENTICATION: ConstOid = + Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 2, 11]); + +/// Apple Application Integration CA - G3 +/// +/// 1.2.840.113635.100.6.2.14 +const OID_CA_EXTENSION_APPLE_APPLICATION_INTEGRATION_G3: ConstOid = + Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 2, 14]); + +/// Apple Worldwide Developer Relations CA - G2 +/// +/// 1.2.840.113635.100.6.2.15 +const OID_CA_EXTENSION_APPLE_WORLDWIDE_DEVELOPER_RELATIONS_G2: ConstOid = + Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 2, 15]); + +/// Apple Software Update Certification. +/// +/// 1.2.840.113635.100.6.2.19 +const OID_CA_EXTENSION_APPLE_SOFTWARE_UPDATE_CERTIFICATION: ConstOid = + Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 2, 19]); + +/// Apple Application Integration CA - G1. +/// +/// This was introduced in `Apple Application Integration CA 7 - G1` +/// The previous `Apple Application Integration CA 5 - G1` certificate +/// had the legacy 1.2.840.113635.100.6.2.3 extension. +/// +/// 1.2.840.113635.100.6.2.31 +const OID_CA_EXTENSION_APPLE_APPLICATION_INTEGRATION_G1: ConstOid = + Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 2, 31]); + +const ALL_OID_CA_EXTENSIONS: &[&ConstOid; 9] = &[ + &OID_CA_EXTENSION_APPLE_WORLDWIDE_DEVELOPER_RELATIONS, + &OID_CA_EXTENSION_APPLE_APPLICATION_INTEGRATION, + &OID_CA_EXTENSION_DEVELOPER_ID, + &OID_CA_EXTENSION_APPLE_TIMESTAMP, + &OID_CA_EXTENSION_DEVELOPER_AUTHENTICATION, + &OID_CA_EXTENSION_APPLE_APPLICATION_INTEGRATION_G3, + &OID_CA_EXTENSION_APPLE_WORLDWIDE_DEVELOPER_RELATIONS_G2, + &OID_CA_EXTENSION_APPLE_SOFTWARE_UPDATE_CERTIFICATION, + &OID_CA_EXTENSION_APPLE_APPLICATION_INTEGRATION_G1, +]; + +/// Describes the type of code signing that a certificate is authorized to perform. +/// +/// Code signing certificates are issued with extended key usage (EKU) attributes +/// denoting what that certificate will be used for. They basically say *I'm authorized +/// to sign X*. +/// +/// This type describes the different code signing key usages defined on Apple +/// platforms. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ExtendedKeyUsagePurpose { + /// Code signing. + CodeSigning, + + /// Safari Developer. + SafariDeveloper, + + /// 3rd Party Mac Developer Installer Packaging Signing. + /// + /// The certificate can be used to sign Mac installer packages. + ThirdPartyMacDeveloperInstaller, + + /// Developer ID Installer. + DeveloperIdInstaller, +} + +impl ExtendedKeyUsagePurpose { + /// Obtain all variants of this enumeration. + pub fn all() -> Vec { + vec![ + Self::CodeSigning, + Self::SafariDeveloper, + Self::ThirdPartyMacDeveloperInstaller, + Self::DeveloperIdInstaller, + ] + } + + pub fn all_oids() -> &'static [&'static ConstOid] { + ALL_OID_EKUS + } + + pub fn as_oid(&self) -> ConstOid { + match self { + Self::CodeSigning => OID_EKU_PURPOSE_CODE_SIGNING, + Self::SafariDeveloper => OID_EKU_PURPOSE_SAFARI_DEVELOPER, + Self::ThirdPartyMacDeveloperInstaller => { + OID_EKU_PURPOSE_3RD_PARTY_MAC_DEVELOPER_INSTALLER + } + Self::DeveloperIdInstaller => OID_EKU_PURPOSE_DEVELOPER_ID_INSTALLER, + } + } +} + +impl Display for ExtendedKeyUsagePurpose { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + ExtendedKeyUsagePurpose::CodeSigning => f.write_str("Code Signing"), + ExtendedKeyUsagePurpose::SafariDeveloper => f.write_str("Safari Developer"), + ExtendedKeyUsagePurpose::ThirdPartyMacDeveloperInstaller => { + f.write_str("3rd Party Mac Developer Installer Packaging Signing") + } + ExtendedKeyUsagePurpose::DeveloperIdInstaller => f.write_str("Developer ID Installer"), + } + } +} + +impl TryFrom<&Oid> for ExtendedKeyUsagePurpose { + type Error = AppleCodesignError; + + fn try_from(oid: &Oid) -> Result { + // Surely there is a way to use `match`. But the `Oid` type is a bit wonky. + if oid.as_ref() == OID_EKU_PURPOSE_CODE_SIGNING.as_ref() { + Ok(Self::CodeSigning) + } else if oid.as_ref() == OID_EKU_PURPOSE_SAFARI_DEVELOPER.as_ref() { + Ok(Self::SafariDeveloper) + } else if oid.as_ref() == OID_EKU_PURPOSE_3RD_PARTY_MAC_DEVELOPER_INSTALLER.as_ref() { + Ok(Self::ThirdPartyMacDeveloperInstaller) + } else if oid.as_ref() == OID_EKU_PURPOSE_DEVELOPER_ID_INSTALLER.as_ref() { + Ok(Self::DeveloperIdInstaller) + } else { + Err(AppleCodesignError::OidIsntCertificateAuthority) + } + } +} + +/// Describes one of the many X.509 certificate extensions found on Apple code signing certificates. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum CodeSigningCertificateExtension { + /// Apple Signing. + /// + /// (Appears to be deprecated). + AppleSigning, + + /// iPhone Developer. + IPhoneDeveloper, + + /// Apple iPhone OS Application Signing. + IPhoneOsApplicationSigning, + + /// Apple Developer Certificate (Submission). + /// + /// May also be referred to as `iPhone Distribution`. + AppleDeveloperCertificateSubmission, + + /// Safari Developer. + SafariDeveloper, + + /// Apple iPhone OS VPN Signing. + IPhoneOsVpnSigning, + + /// Apple Mac App Signing (Development). + /// + /// Also known as `3rd Party Mac Developer Application`. + AppleMacAppSigningDevelopment, + + /// Apple Mac App Signing Submission. + AppleMacAppSigningSubmission, + + /// Mac App Store Code Signing. + AppleMacAppStoreCodeSigning, + + /// Mac App Store Installer Signing. + AppleMacAppStoreInstallerSigning, + + /// Mac Developer. + MacDeveloper, + + /// Developer ID Application. + DeveloperIdApplication, + + /// Developer ID Date. + DeveloperIdDate, + + /// Developer ID Installer. + DeveloperIdInstaller, + + /// Apple Pay Passbook Signing. + ApplePayPassbookSigning, + + /// Web Site Push Notifications Signing. + WebsitePushNotificationSigning, + + /// Developer ID Kernel. + DeveloperIdKernel, + + /// TestFlight. + TestFlight, +} + +impl CodeSigningCertificateExtension { + /// Obtain all variants of this enumeration. + pub fn all() -> Vec { + vec![ + Self::AppleSigning, + Self::IPhoneDeveloper, + Self::IPhoneOsApplicationSigning, + Self::AppleDeveloperCertificateSubmission, + Self::SafariDeveloper, + Self::IPhoneOsVpnSigning, + Self::AppleMacAppSigningDevelopment, + Self::AppleMacAppSigningSubmission, + Self::AppleMacAppStoreCodeSigning, + Self::AppleMacAppStoreInstallerSigning, + Self::MacDeveloper, + Self::DeveloperIdApplication, + Self::DeveloperIdDate, + Self::DeveloperIdInstaller, + Self::ApplePayPassbookSigning, + Self::WebsitePushNotificationSigning, + Self::DeveloperIdKernel, + Self::TestFlight, + ] + } + + /// All OIDs known to be extensions in code signing certificates. + pub fn all_oids() -> &'static [&'static ConstOid] { + ALL_OID_NON_CA_EXTENSIONS + } + + pub fn as_oid(&self) -> ConstOid { + match self { + Self::AppleSigning => OID_EXTENSION_APPLE_SIGNING, + Self::IPhoneDeveloper => OID_EXTENSION_IPHONE_DEVELOPER, + Self::IPhoneOsApplicationSigning => OID_EXTENSION_IPHONE_OS_APPLICATION_SIGNING, + Self::AppleDeveloperCertificateSubmission => { + OID_EXTENSION_APPLE_DEVELOPER_CERTIFICATE_SUBMISSION + } + Self::SafariDeveloper => OID_EXTENSION_SAFARI_DEVELOPER, + Self::IPhoneOsVpnSigning => OID_EXTENSION_IPHONE_OS_VPN_SIGNING, + Self::AppleMacAppSigningDevelopment => OID_EXTENSION_APPLE_MAC_APP_SIGNING_DEVELOPMENT, + Self::AppleMacAppSigningSubmission => OID_EXTENSION_APPLE_MAC_APP_SIGNING_SUBMISSION, + Self::AppleMacAppStoreCodeSigning => OID_EXTENSION_APPLE_MAC_APP_STORE_CODE_SIGNING, + Self::AppleMacAppStoreInstallerSigning => { + OID_EXTENSION_APPLE_MAC_APP_STORE_INSTALLER_SIGNING + } + Self::MacDeveloper => OID_EXTENSION_MAC_DEVELOPER, + Self::DeveloperIdApplication => OID_EXTENSION_DEVELOPER_ID_APPLICATION, + Self::DeveloperIdDate => OID_EXTENSION_DEVELOPER_ID_DATE, + Self::DeveloperIdInstaller => OID_EXTENSION_DEVELOPER_ID_INSTALLER, + Self::ApplePayPassbookSigning => OID_EXTENSION_PASSBOOK_SIGNING, + Self::WebsitePushNotificationSigning => OID_EXTENSION_WEBSITE_PUSH_NOTIFICATION_SIGNING, + Self::DeveloperIdKernel => OID_EXTENSION_DEVELOPER_ID_KERNEL, + Self::TestFlight => OID_EXTENSION_TEST_FLIGHT, + } + } +} + +impl Display for CodeSigningCertificateExtension { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + CodeSigningCertificateExtension::AppleSigning => f.write_str("Apple Signing"), + CodeSigningCertificateExtension::IPhoneDeveloper => f.write_str("iPhone Developer"), + CodeSigningCertificateExtension::IPhoneOsApplicationSigning => { + f.write_str("Apple iPhone OS Application Signing") + } + CodeSigningCertificateExtension::AppleDeveloperCertificateSubmission => { + f.write_str("Apple Developer Certificate (Submission)") + } + CodeSigningCertificateExtension::SafariDeveloper => f.write_str("Safari Developer"), + CodeSigningCertificateExtension::IPhoneOsVpnSigning => { + f.write_str("Apple iPhone OS VPN Signing") + } + CodeSigningCertificateExtension::AppleMacAppSigningDevelopment => { + f.write_str("Apple Mac App Signing (Development)") + } + CodeSigningCertificateExtension::AppleMacAppSigningSubmission => { + f.write_str("Apple Mac App Signing Submission") + } + CodeSigningCertificateExtension::AppleMacAppStoreCodeSigning => { + f.write_str("Mac App Store Code Signing") + } + CodeSigningCertificateExtension::AppleMacAppStoreInstallerSigning => { + f.write_str("Mac App Store Installer Signing") + } + CodeSigningCertificateExtension::MacDeveloper => f.write_str("Mac Developer"), + CodeSigningCertificateExtension::DeveloperIdApplication => { + f.write_str("Developer ID Application") + } + CodeSigningCertificateExtension::DeveloperIdDate => f.write_str("Developer ID Date"), + CodeSigningCertificateExtension::DeveloperIdInstaller => { + f.write_str("Developer ID Installer") + } + CodeSigningCertificateExtension::ApplePayPassbookSigning => { + f.write_str("Apple Pay Passbook Signing") + } + CodeSigningCertificateExtension::WebsitePushNotificationSigning => { + f.write_str("Web Site Push Notifications Signing") + } + CodeSigningCertificateExtension::DeveloperIdKernel => { + f.write_str("Developer ID Kernel") + } + CodeSigningCertificateExtension::TestFlight => f.write_str("TestFlight"), + } + } +} + +impl TryFrom<&Oid> for CodeSigningCertificateExtension { + type Error = AppleCodesignError; + + fn try_from(oid: &Oid) -> Result { + // Surely there is a way to use `match`. But the `Oid` type is a bit wonky. + let o = oid.as_ref(); + + if o == OID_EXTENSION_APPLE_SIGNING.as_ref() { + Ok(Self::AppleSigning) + } else if o == OID_EXTENSION_IPHONE_DEVELOPER.as_ref() { + Ok(Self::IPhoneDeveloper) + } else if o == OID_EXTENSION_IPHONE_OS_APPLICATION_SIGNING.as_ref() { + Ok(Self::IPhoneOsApplicationSigning) + } else if o == OID_EXTENSION_APPLE_DEVELOPER_CERTIFICATE_SUBMISSION.as_ref() { + Ok(Self::AppleDeveloperCertificateSubmission) + } else if o == OID_EXTENSION_SAFARI_DEVELOPER.as_ref() { + Ok(Self::SafariDeveloper) + } else if o == OID_EXTENSION_IPHONE_OS_VPN_SIGNING.as_ref() { + Ok(Self::IPhoneOsVpnSigning) + } else if o == OID_EXTENSION_APPLE_MAC_APP_SIGNING_DEVELOPMENT.as_ref() { + Ok(Self::AppleMacAppSigningDevelopment) + } else if o == OID_EXTENSION_APPLE_MAC_APP_SIGNING_SUBMISSION.as_ref() { + Ok(Self::AppleMacAppSigningSubmission) + } else if o == OID_EXTENSION_APPLE_MAC_APP_STORE_CODE_SIGNING.as_ref() { + Ok(Self::AppleMacAppStoreCodeSigning) + } else if o == OID_EXTENSION_APPLE_MAC_APP_STORE_INSTALLER_SIGNING.as_ref() { + Ok(Self::AppleMacAppStoreInstallerSigning) + } else if o == OID_EXTENSION_MAC_DEVELOPER.as_ref() { + Ok(Self::MacDeveloper) + } else if o == OID_EXTENSION_DEVELOPER_ID_APPLICATION.as_ref() { + Ok(Self::DeveloperIdApplication) + } else if o == OID_EXTENSION_DEVELOPER_ID_INSTALLER.as_ref() { + Ok(Self::DeveloperIdInstaller) + } else if o == OID_EXTENSION_PASSBOOK_SIGNING.as_ref() { + Ok(Self::ApplePayPassbookSigning) + } else if o == OID_EXTENSION_WEBSITE_PUSH_NOTIFICATION_SIGNING.as_ref() { + Ok(Self::WebsitePushNotificationSigning) + } else if o == OID_EXTENSION_DEVELOPER_ID_KERNEL.as_ref() { + Ok(Self::DeveloperIdKernel) + } else if o == OID_EXTENSION_DEVELOPER_ID_DATE.as_ref() { + Ok(Self::DeveloperIdDate) + } else if o == OID_EXTENSION_TEST_FLIGHT.as_ref() { + Ok(Self::TestFlight) + } else { + Err(AppleCodesignError::OidIsntCodeSigningExtension) + } + } +} + +/// Denotes specific certificate extensions on Apple certificate authority certificates. +/// +/// Apple's CA certificates have extensions that appear to identify the role of +/// that CA. This enumeration defines those. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum CertificateAuthorityExtension { + /// Apple Worldwide Developer Relations. + /// + /// An intermediate CA. + AppleWorldwideDeveloperRelations, + + /// Apple Application Integration. + AppleApplicationIntegration, + + /// Developer ID Certification Authority. + DeveloperId, + + /// Apple Timestamp. + AppleTimestamp, + + /// Developer Authentication Certification Authority. + DeveloperAuthentication, + + /// Application Application Integration CA - G3. + AppleApplicationIntegrationG3, + + /// Apple Worldwide Developer Relations CA - G2. + AppleWorldwideDeveloperRelationsG2, + + /// Apple Software Update Certification. + AppleSoftwareUpdateCertification, + + /// Apple Application Integration CA - G1. + AppleApplicationIntegrationG1, +} + +impl CertificateAuthorityExtension { + /// Obtain all variants of this enumeration. + pub fn all() -> Vec { + vec![ + Self::AppleWorldwideDeveloperRelations, + Self::AppleApplicationIntegration, + Self::DeveloperId, + Self::AppleTimestamp, + Self::DeveloperAuthentication, + Self::AppleApplicationIntegrationG3, + Self::AppleWorldwideDeveloperRelationsG2, + Self::AppleSoftwareUpdateCertification, + Self::AppleApplicationIntegrationG1, + ] + } + + /// All the known OIDs constituting Apple CA extensions. + pub fn all_oids() -> &'static [&'static ConstOid] { + ALL_OID_CA_EXTENSIONS + } + + pub fn as_oid(&self) -> ConstOid { + match self { + Self::AppleWorldwideDeveloperRelations => { + OID_CA_EXTENSION_APPLE_WORLDWIDE_DEVELOPER_RELATIONS + } + Self::AppleApplicationIntegration => OID_CA_EXTENSION_APPLE_APPLICATION_INTEGRATION, + Self::DeveloperId => OID_CA_EXTENSION_DEVELOPER_ID, + Self::AppleTimestamp => OID_CA_EXTENSION_APPLE_TIMESTAMP, + Self::DeveloperAuthentication => OID_CA_EXTENSION_DEVELOPER_AUTHENTICATION, + Self::AppleApplicationIntegrationG3 => { + OID_CA_EXTENSION_APPLE_APPLICATION_INTEGRATION_G3 + } + Self::AppleWorldwideDeveloperRelationsG2 => { + OID_CA_EXTENSION_APPLE_WORLDWIDE_DEVELOPER_RELATIONS_G2 + } + Self::AppleSoftwareUpdateCertification => { + OID_CA_EXTENSION_APPLE_SOFTWARE_UPDATE_CERTIFICATION + } + Self::AppleApplicationIntegrationG1 => { + OID_CA_EXTENSION_APPLE_APPLICATION_INTEGRATION_G1 + } + } + } +} + +impl Display for CertificateAuthorityExtension { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + CertificateAuthorityExtension::AppleWorldwideDeveloperRelations => { + f.write_str("Apple Worldwide Developer Relations") + } + CertificateAuthorityExtension::AppleApplicationIntegration => { + f.write_str("Apple Application Integration") + } + CertificateAuthorityExtension::DeveloperId => { + f.write_str("Developer ID Certification Authority") + } + CertificateAuthorityExtension::AppleTimestamp => f.write_str("Apple Timestamp"), + CertificateAuthorityExtension::DeveloperAuthentication => { + f.write_str("Developer Authentication Certification Authority") + } + CertificateAuthorityExtension::AppleApplicationIntegrationG3 => { + f.write_str("Apple Application Integration CA - G3") + } + CertificateAuthorityExtension::AppleWorldwideDeveloperRelationsG2 => { + f.write_str("Apple Worldwide Developer Relations CA - G2") + } + CertificateAuthorityExtension::AppleSoftwareUpdateCertification => { + f.write_str("Apple Software Update Certification") + } + CertificateAuthorityExtension::AppleApplicationIntegrationG1 => { + f.write_str("Apple Application Integration CA - G1") + } + } + } +} + +impl TryFrom<&Oid> for CertificateAuthorityExtension { + type Error = AppleCodesignError; + + fn try_from(oid: &Oid) -> Result { + // Surely there is a way to use `match`. But the `Oid` type is a bit wonky. + if oid.as_ref() == OID_CA_EXTENSION_APPLE_WORLDWIDE_DEVELOPER_RELATIONS.as_ref() { + Ok(Self::AppleWorldwideDeveloperRelations) + } else if oid.as_ref() == OID_CA_EXTENSION_APPLE_APPLICATION_INTEGRATION.as_ref() { + Ok(Self::AppleApplicationIntegration) + } else if oid.as_ref() == OID_CA_EXTENSION_DEVELOPER_ID.as_ref() { + Ok(Self::DeveloperId) + } else if oid.as_ref() == OID_CA_EXTENSION_APPLE_TIMESTAMP.as_ref() { + Ok(Self::AppleTimestamp) + } else if oid.as_ref() == OID_CA_EXTENSION_DEVELOPER_AUTHENTICATION.as_ref() { + Ok(Self::DeveloperAuthentication) + } else if oid.as_ref() == OID_CA_EXTENSION_APPLE_APPLICATION_INTEGRATION_G3.as_ref() { + Ok(Self::AppleApplicationIntegrationG3) + } else if oid.as_ref() == OID_CA_EXTENSION_APPLE_WORLDWIDE_DEVELOPER_RELATIONS_G2.as_ref() { + Ok(Self::AppleWorldwideDeveloperRelationsG2) + } else if oid.as_ref() == OID_CA_EXTENSION_APPLE_SOFTWARE_UPDATE_CERTIFICATION.as_ref() { + Ok(Self::AppleSoftwareUpdateCertification) + } else if oid.as_ref() == OID_CA_EXTENSION_APPLE_APPLICATION_INTEGRATION_G1.as_ref() { + Ok(Self::AppleApplicationIntegrationG1) + } else { + Err(AppleCodesignError::OidIsntCertificateAuthority) + } + } +} + +/// Describes combinations of certificate extensions for Apple code signing certificates. +/// +/// Code signing certificates contain various X.509 extensions denoting them for +/// code signing. +/// +/// This type represents various common extensions as used on Apple platforms. +/// +/// Typically, you'll want to apply at most one of these extensions to a +/// new certificate in order to mark it as compatible for code signing. +/// +/// This type essentially encapsulates the logic for handling of different +/// "profiles" attached to the different code signing certificates that Apple +/// issues. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum CertificateProfile { + /// Mac Installer Distribution. + /// + /// In `Keychain Access.app`, this might render as `3rd Party Mac Developer Installer`. + /// + /// Certificates are marked for EKU with `3rd Party Developer Installer Package + /// Signing`. + /// + /// They also have the `Apple Mac App Signing (Submission)` extension. + /// + /// Typically issued by `Apple Worldwide Developer Relations Certificate + /// Authority`. + MacInstallerDistribution, + + /// Apple Distribution. + /// + /// Certificates are marked for EKU with `Code Signing`. They also have + /// extensions `Apple Mac App Signing (Development)` and + /// `Apple Developer Certificate (Submission)`. + /// + /// Typically issued by `Apple Worldwide Developer Relations Certificate Authority`. + AppleDistribution, + + /// Apple Development. + /// + /// Certificates are marked for EKU with `Code Signing`. They also have + /// extensions `Apple Developer Certificate (Development)` and + /// `Mac Developer`. + /// + /// Typically issued by `Apple Worldwide Developer Relations Certificate + /// Authority`. + AppleDevelopment, + + /// Developer ID Application. + /// + /// Certificates are marked for EKU with `Code Signing`. They also have + /// extensions for `Developer ID Application` and `Developer ID Date`. + DeveloperIdApplication, + + /// Developer ID Installer. + /// + /// Certificates are marked for EKU with `Developer ID Application`. They also + /// have extensions `Developer ID Installer` and `Developer ID Date`. + DeveloperIdInstaller, +} + +impl CertificateProfile { + pub fn all() -> &'static [Self] { + &[ + Self::MacInstallerDistribution, + Self::AppleDistribution, + Self::AppleDevelopment, + Self::DeveloperIdApplication, + Self::DeveloperIdInstaller, + ] + } + + /// Obtain the string values that variants are recognized as. + pub fn str_names() -> [&'static str; 5] { + [ + "mac-installer-distribution", + "apple-distribution", + "apple-development", + "developer-id-application", + "developer-id-installer", + ] + } +} + +impl Display for CertificateProfile { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + CertificateProfile::MacInstallerDistribution => { + f.write_str("mac-installer-distribution") + } + CertificateProfile::AppleDistribution => f.write_str("apple-distribution"), + CertificateProfile::AppleDevelopment => f.write_str("apple-development"), + CertificateProfile::DeveloperIdApplication => f.write_str("developer-id-application"), + CertificateProfile::DeveloperIdInstaller => f.write_str("developer-id-installer"), + } + } +} + +impl FromStr for CertificateProfile { + type Err = AppleCodesignError; + + fn from_str(s: &str) -> Result { + match s { + "apple-distribution" => Ok(Self::AppleDistribution), + "apple-development" => Ok(Self::AppleDevelopment), + "developer-id-application" => Ok(Self::DeveloperIdApplication), + "developer-id-installer" => Ok(Self::DeveloperIdInstaller), + "mac-installer-distribution" => Ok(Self::MacInstallerDistribution), + _ => Err(AppleCodesignError::UnknownCertificateProfile(s.to_string())), + } + } +} + +/// Extends functionality of [CapturedX509Certificate] with Apple specific certificate knowledge. +pub trait AppleCertificate: Sized { + /// Whether this is a known Apple root certificate authority. + /// + /// We define this criteria as a certificate in our built-in list of known + /// Apple certificates that has the same subject and issuer Names. + fn is_apple_root_ca(&self) -> bool; + + /// Whether this is a known Apple intermediate certificate authority. + /// + /// This is similar to [Self::is_apple_root_ca] except it doesn't match against + /// known self-signed Apple certificates. + fn is_apple_intermediate_ca(&self) -> bool; + + /// Find [CertificateAuthorityExtension] present on this certificate. + /// + /// If this is non-empty, the certificate says it is an Apple certificate + /// whose role is issuing other certificates using for signing things. + /// + /// This function does not perform trust validation that the underlying + /// certificate is a legitimate Apple issued certificate: just that it has + /// the desired property. + fn apple_ca_extensions(&self) -> Vec; + + /// Obtain all of Apple's [ExtendedKeyUsagePurpose] in this certificate. + fn apple_extended_key_usage_purposes(&self) -> Vec; + + /// Obtain all of Apple's [CodeSigningCertificateExtension] in this certificate. + fn apple_code_signing_extensions(&self) -> Vec; + + /// Attempt to guess the [CertificateProfile] associated with this certificate. + /// + /// This keys off present certificate extensions to guess which profile it + /// belongs to. Incorrect guesses are possible, which is why *guess* is in the + /// function name. + /// + /// Returns `None` if we don't think a [CertificateProfile] is associated with + /// this extension. + fn apple_guess_profile(&self) -> Option; + + /// Attempt to resolve the certificate issuer chain back to [AppleCertificate]. + /// + /// This is a glorified wrapper around [CapturedX509Certificate::resolve_signing_chain] + /// that filters matches against certificates in our known set of Apple + /// certificates and maps them back to our [KnownCertificate] Rust enumeration. + /// + /// False negatives (read: missing certificates) can be encountered if + /// we don't know about an Apple CA certificate. + fn apple_issuing_chain(&self) -> Vec; + + /// Whether this certificate chains back to a known Apple root certificate authority. + /// + /// This is true if the resolved certificate issuance chain (which is + /// confirmed via verifying the cryptographic signatures on certificates) + /// ands in a certificate that is known to be an Apple root CA. + fn chains_to_apple_root_ca(&self) -> bool; + + /// Obtain the chain of issuing certificates, back to a known Apple root. + /// + /// The returned chain starts with this certificate and ends with a known + /// Apple root certificate authority. None is returned if this certificate + /// doesn't appear to chain to a known Apple root CA. + fn apple_root_certificate_chain(&self) -> Option>; + + /// Attempt to resolve the *team id* of an Apple issued certificate. + /// + /// The *team id* is a value like `AB42XYZ789` that is attached to your + /// Apple Developer account. It seems to always be embedded in signing + /// certificates as the Organizational Unit field of the subject. So this + /// function is just a shortcut for retrieving that. + fn apple_team_id(&self) -> Option; + + /// Whether this is a certificate pretending to be signed by an Apple CA but isn't really. + fn is_test_apple_signed_certificate(&self) -> bool; +} + +impl AppleCertificate for CapturedX509Certificate { + fn is_apple_root_ca(&self) -> bool { + KnownCertificate::all_roots().contains(&self) + } + + fn is_apple_intermediate_ca(&self) -> bool { + KnownCertificate::all().contains(&self) && !KnownCertificate::all_roots().contains(&self) + } + + fn apple_ca_extensions(&self) -> Vec { + let cert: &x509_certificate::rfc5280::Certificate = self.as_ref(); + + cert.iter_extensions() + .filter_map(|extension| CertificateAuthorityExtension::try_from(&extension.id).ok()) + .collect::>() + } + + fn apple_extended_key_usage_purposes(&self) -> Vec { + let cert: &x509_certificate::rfc5280::Certificate = self.as_ref(); + + cert.iter_extensions() + .filter_map(|extension| { + if extension.id.as_ref() == OID_EXTENDED_KEY_USAGE.as_ref() { + if let Some(oid) = extension.try_decode_sequence_single_oid() { + if let Ok(purpose) = ExtendedKeyUsagePurpose::try_from(&oid) { + Some(purpose) + } else { + None + } + } else { + None + } + } else { + None + } + }) + .collect::>() + } + + fn apple_code_signing_extensions(&self) -> Vec { + let cert: &x509_certificate::rfc5280::Certificate = self.as_ref(); + + cert.iter_extensions() + .filter_map(|extension| { + if let Ok(value) = CodeSigningCertificateExtension::try_from(&extension.id) { + Some(value) + } else { + None + } + }) + .collect::>() + } + + fn apple_guess_profile(&self) -> Option { + let ekus = self.apple_extended_key_usage_purposes(); + let signing = self.apple_code_signing_extensions(); + + // Some EKUs uniquely identify the certificate profile. We don't yet handle + // all EKUs because we don't have profiles defined for them. + // + // Ideally this logic stays in sync with apple_certificate_profile(). + if ekus.contains(&ExtendedKeyUsagePurpose::DeveloperIdInstaller) { + Some(CertificateProfile::DeveloperIdInstaller) + } else if ekus.contains(&ExtendedKeyUsagePurpose::ThirdPartyMacDeveloperInstaller) { + Some(CertificateProfile::MacInstallerDistribution) + // That's all the EKUs that have a 1:1 to CertificateProfile. Now look at + // code signing extensions. + } else if signing.contains(&CodeSigningCertificateExtension::DeveloperIdApplication) { + Some(CertificateProfile::DeveloperIdApplication) + } else if signing.contains(&CodeSigningCertificateExtension::IPhoneDeveloper) + && signing.contains(&CodeSigningCertificateExtension::MacDeveloper) + { + Some(CertificateProfile::AppleDevelopment) + } else if signing.contains(&CodeSigningCertificateExtension::AppleMacAppSigningDevelopment) + && signing + .contains(&CodeSigningCertificateExtension::AppleDeveloperCertificateSubmission) + { + Some(CertificateProfile::AppleDistribution) + } else { + None + } + } + + fn apple_issuing_chain(&self) -> Vec { + self.resolve_signing_chain(KnownCertificate::all().iter().copied()) + .into_iter() + .filter_map(|cert| KnownCertificate::try_from(cert).ok()) + .collect::>() + } + + fn chains_to_apple_root_ca(&self) -> bool { + if self.is_apple_root_ca() { + true + } else { + self.resolve_signing_chain(KnownCertificate::all().iter().copied()) + .into_iter() + .any(|cert| cert.is_apple_root_ca()) + } + } + + fn apple_root_certificate_chain(&self) -> Option> { + let mut chain = vec![self.clone()]; + + for cert in self.resolve_signing_chain(KnownCertificate::all().iter().copied()) { + chain.push(cert.clone()); + + if cert.is_apple_root_ca() { + break; + } + } + + if chain.last().unwrap().is_apple_root_ca() { + Some(chain) + } else { + None + } + } + + fn apple_team_id(&self) -> Option { + self.subject_name() + .find_first_attribute_string(Oid( + x509_certificate::rfc4519::OID_ORGANIZATIONAL_UNIT_NAME + .as_ref() + .into(), + )) + .unwrap_or(None) + } + + fn is_test_apple_signed_certificate(&self) -> bool { + if let Ok(digest) = self.sha256_fingerprint() { + hex::encode(digest) + == "5939ad5770d8b977b38d07533754371314744e87a8d606433f689e9bc6b980a0" + } else { + false + } + } +} + +/// Extensions to [X509CertificateBuilder] specializing in Apple certificate behavior. +/// +/// Most callers should call [Self::apple_certificate_profile] to configure +/// a preset profile for the certificate being generated. After that - and it is +/// important it is after - call [Self::apple_subject] to define the subject +/// field. If you call this after registering code signing extensions, it +/// detects the appropriate format for the Common Name field. +pub trait AppleCertificateBuilder: Sized { + /// This functions defines common attributes on the certificate subject. + /// + /// `team_id` is your Apple team id. It is a short alphanumeric string. You + /// can find this at . + fn apple_subject( + &mut self, + team_id: &str, + person_name: &str, + country: &str, + ) -> Result<(), AppleCodesignError>; + + /// Add an email address to the certificate's subject name. + fn apple_email_address(&mut self, address: &str) -> Result<(), AppleCodesignError>; + + /// Add an [ExtendedKeyUsagePurpose] to this certificate. + fn apple_extended_key_usage( + &mut self, + usage: ExtendedKeyUsagePurpose, + ) -> Result<(), AppleCodesignError>; + + /// Add a certificate extension as defined by a [CodeSigningCertificateExtension] instance. + fn apple_code_signing_certificate_extension( + &mut self, + extension: CodeSigningCertificateExtension, + ) -> Result<(), AppleCodesignError>; + + /// Add a [CertificateProfile] to this builder. + /// + /// All certificate extensions relevant to this profile are added. + /// + /// This should be the first function you call after creating an instance + /// because other functions rely on the state that it sets. + fn apple_certificate_profile( + &mut self, + profile: CertificateProfile, + ) -> Result<(), AppleCodesignError>; + + /// Find code signing extensions that are currently registered. + fn apple_code_signing_extensions(&self) -> Vec; +} + +impl AppleCertificateBuilder for X509CertificateBuilder { + fn apple_subject( + &mut self, + team_id: &str, + person_name: &str, + country: &str, + ) -> Result<(), AppleCodesignError> { + // TODO the subject schema here isn't totally accurate. While OU does always + // appear to be the team id, the user id attribute can be something else. + // For example, for Apple Development, there are a similarly formatted yet + // different value. But the team id does still appear. + self.subject() + .append_utf8_string(Oid(OID_USER_ID.as_ref().into()), team_id) + .map_err(|e| AppleCodesignError::CertificateBuildError(format!("{e:?}")))?; + + // Common Name is derived from the profile in use. + + let extensions = self.apple_code_signing_extensions(); + + let common_name = + if extensions.contains(&CodeSigningCertificateExtension::DeveloperIdApplication) { + format!("Developer ID Application: {person_name} ({team_id})") + } else if extensions.contains(&CodeSigningCertificateExtension::DeveloperIdInstaller) { + format!("Developer ID Installer: {person_name} ({team_id})") + } else if extensions + .contains(&CodeSigningCertificateExtension::AppleDeveloperCertificateSubmission) + { + format!("Apple Distribution: {person_name} ({team_id})") + } else if extensions + .contains(&CodeSigningCertificateExtension::AppleMacAppSigningSubmission) + { + format!("3rd Party Mac Developer Installer: {person_name} ({team_id})") + } else if extensions.contains(&CodeSigningCertificateExtension::MacDeveloper) { + format!("Apple Development: {person_name} ({team_id})") + } else { + format!("{person_name} ({team_id})") + }; + + self.subject() + .append_common_name_utf8_string(&common_name) + .map_err(|e| AppleCodesignError::CertificateBuildError(format!("{e:?}")))?; + + self.subject() + .append_organizational_unit_utf8_string(team_id) + .map_err(|e| AppleCodesignError::CertificateBuildError(format!("{e:?}")))?; + + self.subject() + .append_organization_utf8_string(person_name) + .map_err(|e| AppleCodesignError::CertificateBuildError(format!("{e:?}")))?; + + self.subject() + .append_printable_string(Oid(OID_COUNTRY_NAME.as_ref().into()), country) + .map_err(|e| AppleCodesignError::CertificateBuildError(format!("{e:?}")))?; + + Ok(()) + } + + fn apple_email_address(&mut self, address: &str) -> Result<(), AppleCodesignError> { + self.subject() + .append_utf8_string(Oid(OID_EMAIL_ADDRESS.as_ref().into()), address) + .map_err(|e| AppleCodesignError::CertificateBuildError(format!("{e:?}")))?; + + Ok(()) + } + + fn apple_extended_key_usage( + &mut self, + usage: ExtendedKeyUsagePurpose, + ) -> Result<(), AppleCodesignError> { + let payload = + bcder::encode::sequence(Oid(Bytes::copy_from_slice(usage.as_oid().as_ref())).encode()) + .to_captured(bcder::Mode::Der); + + self.add_extension_der_data( + Oid(OID_EXTENDED_KEY_USAGE.as_ref().into()), + true, + payload.as_slice(), + ); + + Ok(()) + } + + fn apple_code_signing_certificate_extension( + &mut self, + extension: CodeSigningCertificateExtension, + ) -> Result<(), AppleCodesignError> { + let (critical, payload) = match extension { + CodeSigningCertificateExtension::IPhoneDeveloper => { + // SEQUENCE (3 elem) + // OBJECT IDENTIFIER 1.2.840.113635.100.6.1.2 + // BOOLEAN true + // OCTET STRING (2 byte) 0500 + // NULL + (true, Bytes::copy_from_slice(&[0x05, 0x00])) + } + CodeSigningCertificateExtension::AppleDeveloperCertificateSubmission => { + // SEQUENCE (3 elem) + // OBJECT IDENTIFIER 1.2.840.113635.100.6.1.4 + // BOOLEAN true + // OCTET STRING (2 byte) 0500 + // NULL + (true, Bytes::copy_from_slice(&[0x05, 0x00])) + } + CodeSigningCertificateExtension::AppleMacAppSigningDevelopment => { + // SEQUENCE (3 elem) + // OBJECT IDENTIFIER 1.2.840.113635.100.6.1.7 + // BOOLEAN true + // OCTET STRING (2 byte) 0500 + // NULL + (true, Bytes::copy_from_slice(&[0x05, 0x00])) + } + CodeSigningCertificateExtension::AppleMacAppSigningSubmission => { + // SEQUENCE (3 elem) + // OBJECT IDENTIFIER 1.2.840.113635.100.6.1.8 + // BOOLEAN true + // OCTET STRING (2 byte) 0500 + // NULL + (true, Bytes::copy_from_slice(&[0x05, 0x00])) + } + CodeSigningCertificateExtension::MacDeveloper => { + // SEQUENCE (3 elem) + // OBJECT IDENTIFIER 1.2.840.113635.100.6.1.12 + // BOOLEAN true + // OCTET STRING (2 byte) 0500 + // NULL + (true, Bytes::copy_from_slice(&[0x05, 0x00])) + } + CodeSigningCertificateExtension::DeveloperIdApplication => { + // SEQUENCE (3 elem) + // OBJECT IDENTIFIER 1.2.840.113635.100.6.1.13 + // BOOLEAN true + // OCTET STRING (2 byte) 0500 + // NULL + (true, Bytes::copy_from_slice(&[0x05, 0x00])) + } + CodeSigningCertificateExtension::DeveloperIdInstaller => { + // SEQUENCE (3 elem) + // OBJECT IDENTIFIER 1.2.840.113635.100.6.1.14 + // BOOLEAN true + // OCTET STRING (2 byte) 0500 + // NULL + (true, Bytes::copy_from_slice(&[0x05, 0x00])) + } + + // The rest of these probably have the same payload. But until we see + // them, don't take chances. + _ => { + return Err(AppleCodesignError::CertificateBuildError(format!( + "don't know how to handle code signing extension {extension:?}" + ))); + } + }; + + self.add_extension_der_data( + Oid(Bytes::copy_from_slice(extension.as_oid().as_ref())), + critical, + payload, + ); + + Ok(()) + } + + fn apple_certificate_profile( + &mut self, + profile: CertificateProfile, + ) -> Result<(), AppleCodesignError> { + // Try to keep this logic in sync with apple_guess_profile(). + match profile { + CertificateProfile::DeveloperIdApplication => { + self.constraint_not_ca(); + self.apple_extended_key_usage(ExtendedKeyUsagePurpose::CodeSigning)?; + self.key_usage(KeyUsage::DigitalSignature); + + // OID_EXTENSION_DEVELOPER_ID_DATE comes next. But we don't know what + // that should be. It is a UTF8String instead of an ASN.1 time type + // because who knows. + + self.apple_code_signing_certificate_extension( + CodeSigningCertificateExtension::DeveloperIdApplication, + )?; + } + CertificateProfile::DeveloperIdInstaller => { + self.constraint_not_ca(); + self.apple_extended_key_usage(ExtendedKeyUsagePurpose::DeveloperIdInstaller)?; + self.key_usage(KeyUsage::DigitalSignature); + + // OID_EXTENSION_DEVELOPER_ID_DATE comes next. + + self.apple_code_signing_certificate_extension( + CodeSigningCertificateExtension::DeveloperIdInstaller, + )?; + } + CertificateProfile::AppleDevelopment => { + self.constraint_not_ca(); + self.apple_extended_key_usage(ExtendedKeyUsagePurpose::CodeSigning)?; + self.key_usage(KeyUsage::DigitalSignature); + self.apple_code_signing_certificate_extension( + CodeSigningCertificateExtension::IPhoneDeveloper, + )?; + self.apple_code_signing_certificate_extension( + CodeSigningCertificateExtension::MacDeveloper, + )?; + } + CertificateProfile::AppleDistribution => { + self.constraint_not_ca(); + self.apple_extended_key_usage(ExtendedKeyUsagePurpose::CodeSigning)?; + self.key_usage(KeyUsage::DigitalSignature); + + // OID_EXTENSION_DEVELOPER_ID_DATE comes next. + + self.apple_code_signing_certificate_extension( + CodeSigningCertificateExtension::AppleMacAppSigningDevelopment, + )?; + self.apple_code_signing_certificate_extension( + CodeSigningCertificateExtension::AppleDeveloperCertificateSubmission, + )?; + } + CertificateProfile::MacInstallerDistribution => { + self.constraint_not_ca(); + self.apple_extended_key_usage( + ExtendedKeyUsagePurpose::ThirdPartyMacDeveloperInstaller, + )?; + self.key_usage(KeyUsage::DigitalSignature); + + self.apple_code_signing_certificate_extension( + CodeSigningCertificateExtension::AppleMacAppSigningSubmission, + )?; + } + } + + Ok(()) + } + + fn apple_code_signing_extensions(&self) -> Vec { + self.extensions() + .iter() + .filter_map(|ext| { + if let Ok(e) = CodeSigningCertificateExtension::try_from(&ext.id) { + Some(e) + } else { + None + } + }) + .collect::>() + } +} + +/// Create a new self-signed X.509 certificate suitable for signing code. +/// +/// The created certificate contains all the extensions needed to convey +/// that it is used for code signing and should resemble certificates. +/// +/// However, because the certificate isn't signed by Apple or another +/// trusted certificate authority, binaries signed with the certificate +/// may not pass Apple's verification requirements and the OS may refuse +/// to proceed. Needless to say, only use certificates generated with this +/// function for testing purposes only. +pub fn create_self_signed_code_signing_certificate( + algorithm: KeyAlgorithm, + profile: CertificateProfile, + team_id: &str, + person_name: &str, + country: &str, + validity_duration: chrono::Duration, +) -> Result<(CapturedX509Certificate, InMemorySigningKeyPair), AppleCodesignError> { + let mut builder = X509CertificateBuilder::default(); + + builder.apple_certificate_profile(profile)?; + builder.apple_subject(team_id, person_name, country)?; + builder.validity_duration(validity_duration); + + // x509-certificate crate doesn't support RSA key generation. So do + // that ourselves. + if matches!(algorithm, KeyAlgorithm::Rsa) { + let private_key = rsa::RsaPrivateKey::new(&mut rand::thread_rng(), 2048).map_err(|e| { + AppleCodesignError::CertificateBuildError(format!("error generating RSA key: {}", e)) + })?; + let key_pair = InMemorySigningKeyPair::from_pkcs8_der( + private_key + .to_pkcs8_der() + .map_err(|e| { + AppleCodesignError::CertificateGeneric(format!( + "error converting RSA key to DER: {}", + e + )) + })? + .as_bytes(), + )?; + + let cert = builder.create_with_key_pair(&key_pair)?; + + Ok((cert, key_pair)) + } else { + Ok(builder.create_with_random_keypair(algorithm)?) + } +} + +#[cfg(test)] +mod tests { + use { + super::*, + cryptographic_message_syntax::{SignedData, SignedDataBuilder, SignerBuilder}, + x509_certificate::EcdsaCurve, + }; + + #[test] + fn generate_self_signed_certificate_ecdsa() { + for curve in EcdsaCurve::all() { + create_self_signed_code_signing_certificate( + KeyAlgorithm::Ecdsa(*curve), + CertificateProfile::DeveloperIdInstaller, + "team1", + "Joe Developer", + "US", + chrono::Duration::hours(1), + ) + .unwrap(); + } + } + + #[test] + fn generate_self_signed_certificate_ed25519() { + create_self_signed_code_signing_certificate( + KeyAlgorithm::Ed25519, + CertificateProfile::DeveloperIdInstaller, + "team2", + "Joe Developer", + "US", + chrono::Duration::hours(1), + ) + .unwrap(); + } + + #[test] + fn generate_all_profiles() { + for profile in CertificateProfile::all() { + create_self_signed_code_signing_certificate( + KeyAlgorithm::Ed25519, + *profile, + "team", + "Joe Developer", + "Wakanda", + chrono::Duration::hours(1), + ) + .unwrap(); + } + } + + #[test] + fn cms_self_signed_certificate_signing_ecdsa() { + for curve in EcdsaCurve::all() { + let (cert, signing_key) = create_self_signed_code_signing_certificate( + KeyAlgorithm::Ecdsa(*curve), + CertificateProfile::DeveloperIdInstaller, + "team", + "Joe Developer", + "US", + chrono::Duration::hours(1), + ) + .unwrap(); + + let plaintext = "hello, world"; + + let cms = SignedDataBuilder::default() + .certificate(cert.clone()) + .content_inline(plaintext.as_bytes().to_vec()) + .signer(SignerBuilder::new(&signing_key, cert.clone())) + .build_der() + .unwrap(); + + let signed_data = SignedData::parse_ber(&cms).unwrap(); + + for signer in signed_data.signers() { + signer + .verify_signature_with_signed_data(&signed_data) + .unwrap(); + } + } + } + + #[test] + fn cms_self_signed_certificate_signing_ed25519() { + let (cert, signing_key) = create_self_signed_code_signing_certificate( + KeyAlgorithm::Ed25519, + CertificateProfile::DeveloperIdInstaller, + "team", + "Joe Developer", + "US", + chrono::Duration::hours(1), + ) + .unwrap(); + + let plaintext = "hello, world"; + + let cms = SignedDataBuilder::default() + .certificate(cert.clone()) + .content_inline(plaintext.as_bytes().to_vec()) + .signer(SignerBuilder::new(&signing_key, cert)) + .build_der() + .unwrap(); + + let signed_data = SignedData::parse_ber(&cms).unwrap(); + + for signer in signed_data.signers() { + signer + .verify_signature_with_signed_data(&signed_data) + .unwrap(); + } + } + + #[test] + fn third_mac_mac() { + let der = include_bytes!("testdata/apple-signed-3rd-party-mac.cer"); + let cert = CapturedX509Certificate::from_der(der.to_vec()).unwrap(); + + assert_eq!( + cert.apple_extended_key_usage_purposes(), + vec![ExtendedKeyUsagePurpose::ThirdPartyMacDeveloperInstaller] + ); + assert_eq!( + cert.apple_code_signing_extensions(), + vec![CodeSigningCertificateExtension::AppleMacAppSigningSubmission] + ); + assert_eq!( + cert.apple_guess_profile(), + Some(CertificateProfile::MacInstallerDistribution) + ); + assert_eq!( + cert.apple_issuing_chain(), + vec![ + KnownCertificate::WwdrG3, + KnownCertificate::AppleRootCa, + KnownCertificate::AppleComputerIncRoot + ] + ); + assert!(cert.chains_to_apple_root_ca()); + assert_eq!( + cert.apple_root_certificate_chain(), + Some(vec![ + cert.clone(), + (*KnownCertificate::WwdrG3).clone(), + (*KnownCertificate::AppleRootCa).clone() + ]) + ); + assert_eq!(cert.apple_team_id(), Some("MK22MZP987".into())); + + let mut builder = X509CertificateBuilder::default(); + builder + .apple_certificate_profile(CertificateProfile::MacInstallerDistribution) + .unwrap(); + + let built = builder + .create_with_random_keypair(KeyAlgorithm::Ecdsa(EcdsaCurve::Secp256r1)) + .unwrap() + .0; + + assert_eq!( + built.apple_extended_key_usage_purposes(), + cert.apple_extended_key_usage_purposes() + ); + assert_eq!( + built.apple_code_signing_extensions(), + cert.apple_code_signing_extensions() + ); + assert_eq!(built.apple_guess_profile(), cert.apple_guess_profile()); + assert_eq!(built.apple_issuing_chain(), vec![]); + assert!(!built.chains_to_apple_root_ca()); + assert!(built.apple_root_certificate_chain().is_none()); + } + + #[test] + fn apple_development() { + let der = include_bytes!("testdata/apple-signed-apple-development.cer"); + let cert = CapturedX509Certificate::from_der(der.to_vec()).unwrap(); + + assert_eq!( + cert.apple_extended_key_usage_purposes(), + vec![ExtendedKeyUsagePurpose::CodeSigning] + ); + assert_eq!( + cert.apple_code_signing_extensions(), + vec![ + CodeSigningCertificateExtension::IPhoneDeveloper, + CodeSigningCertificateExtension::MacDeveloper + ] + ); + assert_eq!( + cert.apple_guess_profile(), + Some(CertificateProfile::AppleDevelopment) + ); + assert_eq!( + cert.apple_issuing_chain(), + vec![ + KnownCertificate::WwdrG3, + KnownCertificate::AppleRootCa, + KnownCertificate::AppleComputerIncRoot + ], + ); + assert!(cert.chains_to_apple_root_ca()); + assert_eq!( + cert.apple_root_certificate_chain(), + Some(vec![ + cert.clone(), + (*KnownCertificate::WwdrG3).clone(), + (*KnownCertificate::AppleRootCa).clone() + ]) + ); + assert_eq!(cert.apple_team_id(), Some("MK22MZP987".into())); + + let mut builder = X509CertificateBuilder::default(); + builder + .apple_certificate_profile(CertificateProfile::AppleDevelopment) + .unwrap(); + + let built = builder + .create_with_random_keypair(KeyAlgorithm::Ecdsa(EcdsaCurve::Secp256r1)) + .unwrap() + .0; + + assert_eq!( + built.apple_extended_key_usage_purposes(), + cert.apple_extended_key_usage_purposes() + ); + assert_eq!( + built.apple_code_signing_extensions(), + cert.apple_code_signing_extensions() + ); + assert_eq!(built.apple_guess_profile(), cert.apple_guess_profile()); + assert_eq!(built.apple_issuing_chain(), vec![]); + assert!(!built.chains_to_apple_root_ca()); + assert!(built.apple_root_certificate_chain().is_none()); + } + + #[test] + fn apple_distribution() { + let der = include_bytes!("testdata/apple-signed-apple-distribution.cer"); + let cert = CapturedX509Certificate::from_der(der.to_vec()).unwrap(); + + assert_eq!( + cert.apple_extended_key_usage_purposes(), + vec![ExtendedKeyUsagePurpose::CodeSigning] + ); + assert_eq!( + cert.apple_code_signing_extensions(), + vec![ + CodeSigningCertificateExtension::AppleMacAppSigningDevelopment, + CodeSigningCertificateExtension::AppleDeveloperCertificateSubmission + ] + ); + assert_eq!( + cert.apple_guess_profile(), + Some(CertificateProfile::AppleDistribution) + ); + assert_eq!( + cert.apple_issuing_chain(), + vec![ + KnownCertificate::WwdrG3, + KnownCertificate::AppleRootCa, + KnownCertificate::AppleComputerIncRoot + ], + ); + assert!(cert.chains_to_apple_root_ca()); + assert_eq!( + cert.apple_root_certificate_chain(), + Some(vec![ + cert.clone(), + (*KnownCertificate::WwdrG3).clone(), + (*KnownCertificate::AppleRootCa).clone() + ]) + ); + assert_eq!(cert.apple_team_id(), Some("MK22MZP987".into())); + + let mut builder = X509CertificateBuilder::default(); + builder + .apple_certificate_profile(CertificateProfile::AppleDistribution) + .unwrap(); + + let built = builder + .create_with_random_keypair(KeyAlgorithm::Ecdsa(EcdsaCurve::Secp256r1)) + .unwrap() + .0; + + assert_eq!( + built.apple_extended_key_usage_purposes(), + cert.apple_extended_key_usage_purposes() + ); + assert_eq!( + built.apple_code_signing_extensions(), + cert.apple_code_signing_extensions() + ); + assert_eq!(built.apple_guess_profile(), cert.apple_guess_profile()); + assert_eq!(built.apple_issuing_chain(), vec![]); + assert!(!built.chains_to_apple_root_ca()); + assert!(built.apple_root_certificate_chain().is_none()); + } + + #[test] + fn apple_developer_id_application() { + let der = include_bytes!("testdata/apple-signed-developer-id-application.cer"); + let cert = CapturedX509Certificate::from_der(der.to_vec()).unwrap(); + + assert_eq!( + cert.apple_extended_key_usage_purposes(), + vec![ExtendedKeyUsagePurpose::CodeSigning] + ); + assert_eq!( + cert.apple_code_signing_extensions(), + vec![ + CodeSigningCertificateExtension::DeveloperIdDate, + CodeSigningCertificateExtension::DeveloperIdApplication + ] + ); + assert_eq!( + cert.apple_guess_profile(), + Some(CertificateProfile::DeveloperIdApplication) + ); + assert_eq!( + cert.apple_issuing_chain(), + vec![ + KnownCertificate::DeveloperIdG1, + KnownCertificate::AppleRootCa, + KnownCertificate::AppleComputerIncRoot + ] + ); + assert!(cert.chains_to_apple_root_ca()); + assert_eq!( + cert.apple_root_certificate_chain(), + Some(vec![ + cert.clone(), + (*KnownCertificate::DeveloperIdG1).clone(), + (*KnownCertificate::AppleRootCa).clone() + ]) + ); + assert_eq!(cert.apple_team_id(), Some("MK22MZP987".into())); + + let mut builder = X509CertificateBuilder::default(); + builder + .apple_certificate_profile(CertificateProfile::DeveloperIdApplication) + .unwrap(); + + let built = builder + .create_with_random_keypair(KeyAlgorithm::Ecdsa(EcdsaCurve::Secp256r1)) + .unwrap() + .0; + + assert_eq!( + built.apple_extended_key_usage_purposes(), + cert.apple_extended_key_usage_purposes() + ); + assert_eq!( + built.apple_code_signing_extensions(), + // We don't write out the date extension. + cert.apple_code_signing_extensions() + .into_iter() + .filter(|e| !matches!(e, CodeSigningCertificateExtension::DeveloperIdDate)) + .collect::>() + ); + assert_eq!(built.apple_guess_profile(), cert.apple_guess_profile()); + assert_eq!(built.apple_issuing_chain(), vec![]); + assert!(!built.chains_to_apple_root_ca()); + assert!(built.apple_root_certificate_chain().is_none()); + } + + #[test] + fn apple_developer_id_installer() { + let der = include_bytes!("testdata/apple-signed-developer-id-installer.cer"); + let cert = CapturedX509Certificate::from_der(der.to_vec()).unwrap(); + + assert_eq!( + cert.apple_extended_key_usage_purposes(), + vec![ExtendedKeyUsagePurpose::DeveloperIdInstaller] + ); + assert_eq!( + cert.apple_code_signing_extensions(), + vec![ + CodeSigningCertificateExtension::DeveloperIdDate, + CodeSigningCertificateExtension::DeveloperIdInstaller + ] + ); + assert_eq!( + cert.apple_guess_profile(), + Some(CertificateProfile::DeveloperIdInstaller) + ); + assert_eq!( + cert.apple_issuing_chain(), + vec![ + KnownCertificate::DeveloperIdG1, + KnownCertificate::AppleRootCa, + KnownCertificate::AppleComputerIncRoot + ] + ); + assert!(cert.chains_to_apple_root_ca()); + assert_eq!( + cert.apple_root_certificate_chain(), + Some(vec![ + cert.clone(), + (*KnownCertificate::DeveloperIdG1).clone(), + (*KnownCertificate::AppleRootCa).clone() + ]) + ); + assert_eq!(cert.apple_team_id(), Some("MK22MZP987".into())); + + let mut builder = X509CertificateBuilder::default(); + builder + .apple_certificate_profile(CertificateProfile::DeveloperIdInstaller) + .unwrap(); + + let built = builder + .create_with_random_keypair(KeyAlgorithm::Ecdsa(EcdsaCurve::Secp256r1)) + .unwrap() + .0; + + assert_eq!( + built.apple_extended_key_usage_purposes(), + cert.apple_extended_key_usage_purposes() + ); + assert_eq!( + built.apple_code_signing_extensions(), + // We don't write out the date extension. + cert.apple_code_signing_extensions() + .into_iter() + .filter(|e| !matches!(e, CodeSigningCertificateExtension::DeveloperIdDate)) + .collect::>() + ); + assert_eq!(built.apple_guess_profile(), cert.apple_guess_profile()); + assert_eq!(built.apple_issuing_chain(), vec![]); + assert!(!built.chains_to_apple_root_ca()); + assert!(built.apple_root_certificate_chain().is_none()); + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/cli/certificate_source.rs b/3rdparty/apple-codesign-0.29.0/src/cli/certificate_source.rs new file mode 100644 index 00000000..c2565452 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/cli/certificate_source.rs @@ -0,0 +1,706 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use { + crate::{ + cli::get_pkcs12_password, + cryptography::{parse_pfx_data, InMemoryPrivateKey, PrivateKey}, + error::AppleCodesignError, + remote_signing::{ + session_negotiation::{PublicKeyInitiator, SessionInitiatePeer, SharedSecretInitiator}, + RemoteSignError, UnjoinedSigningClient, + }, + signing_settings::SigningSettings, + }, + base64::{engine::general_purpose::STANDARD as STANDARD_ENGINE, Engine}, + clap::Args, + log::{error, info, warn}, + serde::{Deserialize, Serialize}, + spki::EncodePublicKey, + std::path::PathBuf, + x509_certificate::CapturedX509Certificate, +}; + +#[cfg(feature = "yubikey")] +use { + crate::{cli::prompt_smartcard_pin, yubikey::YubiKey}, + std::str::FromStr, +}; + +#[cfg(target_os = "macos")] +use crate::macos::{keychain_find_code_signing_certificates, KeychainDomain}; + +#[cfg(target_os = "windows")] +use crate::windows::{windows_store_find_code_signing_certificates, StoreName}; + +/// Represents a set of keys and certificates. +#[derive(Default)] + +pub struct SigningCertificates { + pub keys: Vec>, + pub certs: Vec, +} + +impl SigningCertificates { + pub fn extend(&mut self, other: Self) { + self.keys.extend(other.keys); + self.certs.extend(other.certs); + } + + pub fn is_empty(&self) -> bool { + self.keys.is_empty() && self.certs.is_empty() + } + + /// Resolve a private key in this collection. + /// + /// Errors unless the number of keys is exactly one. + pub fn private_key(&self) -> Result<&dyn PrivateKey, AppleCodesignError> { + self.private_key_optional()? + .ok_or_else(|| AppleCodesignError::CliGeneralError("no private key found".into())) + } + + /// Resolve an optional private key in this collection. + /// + /// Errors if there are more than 1 key. + pub fn private_key_optional(&self) -> Result, AppleCodesignError> { + match self.keys.len() { + 0 => Ok(None), + 1 => Ok(Some(self.keys[0].as_ref())), + n => Err(AppleCodesignError::CliGeneralError(format!( + "at most 1 private keys can be present (found {n})" + ))), + } + } + + /// Loads the instance into a [SigningSettings]. + pub fn load_into_signing_settings<'settings, 'slf: 'settings>( + &'slf self, + settings: &'settings mut SigningSettings<'slf>, + ) -> Result<(), AppleCodesignError> { + let private = self.private_key_optional()?; + + let mut public_certificates = self.certs.clone(); + + if let Some(signing_key) = &private { + if public_certificates.is_empty() { + error!("a PRIVATE KEY requires a corresponding CERTIFICATE to pair with it"); + return Err(AppleCodesignError::CliBadArgument); + } + + let cert = public_certificates.remove(0); + + warn!("registering signing key"); + + if !cert.time_constraints_valid(None) { + warn!( + "signing certificate expired as of {}; signatures may not be valid", + cert.validity_not_after().to_rfc3339() + ); + } + + settings.set_signing_key(signing_key.as_key_info_signer(), cert); + if let Some(certs) = settings.chain_apple_certificates() { + for cert in certs { + warn!( + "automatically registered Apple CA certificate: {}", + cert.subject_common_name() + .unwrap_or_else(|| "default".into()) + ); + } + } + } + + for cert in public_certificates { + warn!("registering extra X.509 certificate"); + settings.chain_certificate(cert); + } + + Ok(()) + } +} + +pub trait KeySource { + /// Obtain a bag of private keys and certificates from the instance. + fn resolve_certificates(&self) -> Result; + + /// Whether key source is the lone/exclusive source of keys + certs. + fn exclusive(&self) -> bool { + false + } +} + +#[derive(Args, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct SmartcardSigningKey { + /// Smartcard slot number of signing certificate to use (9c is common) + #[arg(long = "smartcard-slot", value_name = "SLOT")] + pub slot: Option, + + /// Smartcard PIN used to unlock certificate + /// + /// If not provided, you will be prompted for a PIN as necessary. + #[arg(long = "smartcard-pin", value_name = "SECRET")] + pub pin: Option, + + /// Environment variable holding the smartcard PIN + #[arg(long = "smartcard-pin-env", value_name = "STRING")] + #[serde(skip)] + pub pin_env: Option, +} + +impl KeySource for SmartcardSigningKey { + #[cfg(feature = "yubikey")] + fn resolve_certificates(&self) -> Result { + if let Some(slot) = &self.slot { + let slot_id = ::yubikey::piv::SlotId::from_str(slot)?; + let formatted = hex::encode([u8::from(slot_id)]); + let mut yk = YubiKey::new()?; + + if let Some(pin) = &self.pin { + let pin = pin.clone(); + yk.set_pin_callback(move || Ok(pin.as_bytes().to_vec())); + } else if let Some(pin_var) = &self.pin_env { + let pin_var = pin_var.to_owned(); + + yk.set_pin_callback(move || { + if let Ok(pin) = std::env::var(&pin_var) { + eprintln!("using PIN from {} environment variable", &pin_var); + Ok(pin.as_bytes().to_vec()) + } else { + prompt_smartcard_pin() + } + }); + } else { + yk.set_pin_callback(prompt_smartcard_pin); + } + + if let Some(signer) = yk.get_certificate_signer(slot_id)? { + warn!("using certificate in smartcard slot {}", formatted); + + let cert = signer.certificate().clone(); + + Ok(SigningCertificates { + keys: vec![Box::new(signer)], + certs: vec![cert], + }) + } else { + Err(AppleCodesignError::SmartcardNoCertificate(formatted)) + } + } else { + Ok(Default::default()) + } + } + + #[cfg(not(feature = "yubikey"))] + fn resolve_certificates(&self) -> Result { + if self.slot.is_some() { + error!("smartcard support not available; ignoring --smartcard-slot"); + } + + Ok(Default::default()) + } +} + +#[derive(Args, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct MacosKeychainSigningKey { + /// (macOS only) Keychain domain to operate on + #[arg(long = "keychain-domain", group = "keychain", value_parser = crate::cli::KEYCHAIN_DOMAINS, value_name = "DOMAIN")] + #[serde(default)] + pub domains: Vec, + + /// (macOS only) SHA-256 fingerprint of certificate in Keychain to use + #[arg( + long = "keychain-fingerprint", + group = "keychain", + value_name = "SHA256 FINGERPRINT" + )] + pub sha256_fingerprint: Option, +} + +impl KeySource for MacosKeychainSigningKey { + #[cfg(target_os = "macos")] + fn resolve_certificates(&self) -> Result { + // No arguments pertinent to keychains. Don't even speak to the + // keychain API since this could only error. + if self.domains.is_empty() && self.sha256_fingerprint.is_none() { + return Ok(Default::default()); + } + + // Collect all the keychain domains to search. + let domains = if self.domains.is_empty() { + vec!["user".to_string()] + } else { + self.domains.clone() + }; + + let domains = domains + .into_iter() + .map(|domain| { + KeychainDomain::try_from(domain.as_str()) + .expect("clap should have validated domain values") + }) + .collect::>(); + + // Now iterate all the keychains and try to find requested certificates. + let mut res = SigningCertificates::default(); + + for domain in domains { + for cert in keychain_find_code_signing_certificates(domain, None)? { + let matches = if let Some(wanted_fingerprint) = &self.sha256_fingerprint { + let got_fingerprint = hex::encode(cert.sha256_fingerprint()?.as_ref()); + + wanted_fingerprint.to_ascii_lowercase() == got_fingerprint.to_ascii_lowercase() + } else { + false + }; + + if matches { + res.certs.push(cert.as_captured_x509_certificate()); + res.keys.push(Box::new(cert)); + } + } + } + + Ok(res) + } + + #[cfg(not(target_os = "macos"))] + fn resolve_certificates(&self) -> Result { + if !self.domains.is_empty() || self.sha256_fingerprint.is_some() { + error!( + "--keychain* arguments only supported on macOS and will be ignored on this platform" + ); + } + + Ok(Default::default()) + } +} + +#[derive(Args, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct WindowsStoreSigningKey { + /// (Windows only) Windows Store to operate on + #[arg(long = "windows-store-name", value_parser = crate::cli::WINDOWS_STORE_NAMES, value_name = "STORE")] + pub stores: Vec, + + /// (Windows only) SHA-1 fingerprint of certificate in Windows Store to use + #[arg( + long = "windows-store-sha1-fingerprint", + value_name = "SHA1 FINGERPRINT" + )] + pub sha1_fingerprint: Option, +} + +impl KeySource for WindowsStoreSigningKey { + #[cfg(target_os = "windows")] + fn resolve_certificates(&self) -> Result { + // No arguments pertinent to store. Don't even speak to the + // Windows API since this could only error. + if self.stores.is_empty() && self.sha1_fingerprint.is_none() { + return Ok(Default::default()); + } + + // Collect all the store names to search. + let stores = if self.stores.is_empty() { + vec!["user".to_string()] + } else { + self.stores.clone() + }; + + let stores = stores + .into_iter() + .map(|store| { + StoreName::try_from(store.as_str()) + .expect("clap should have validated store name values") + }) + .collect::>(); + + // Now iterate all the stores and try to find requested certificates. + let mut res = SigningCertificates::default(); + + for store in stores { + for cert in windows_store_find_code_signing_certificates(store)? { + let matches = if let Some(wanted_fingerprint) = &self.sha1_fingerprint { + let got_fingerprint = hex::encode(cert.sha1_fingerprint()?.as_ref()); + + wanted_fingerprint.to_ascii_lowercase() == got_fingerprint.to_ascii_lowercase() + } else { + false + }; + + if matches { + res.certs.push(cert.as_captured_x509_certificate()); + res.keys.push(Box::new(cert)); + } + } + } + + Ok(res) + } + + #[cfg(not(target_os = "windows"))] + fn resolve_certificates(&self) -> Result { + if !self.stores.is_empty() || self.sha1_fingerprint.is_some() { + error!( + "--windows-store* arguments only supported on Windows and will be ignored on this platform" + ); + } + + Ok(Default::default()) + } +} + +#[derive(Args, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct P12SigningKey { + /// Path to a .p12/PFX file containing a certificate key pair + #[arg(long = "p12-file", alias = "pfx-file", value_name = "PATH")] + pub path: Option, + + /// The password to use to open the --p12-file file + #[arg( + long = "p12-password", + alias = "pfx-password", + group = "p12-password", + value_name = "SECRET" + )] + pub password: Option, + + // TODO conflicts with p12_password + /// Path to file containing password for opening --p12-file file + #[arg( + long = "p12-password-file", + alias = "pfx-password-file", + group = "p12-password", + value_name = "PATH" + )] + pub password_path: Option, +} + +impl KeySource for P12SigningKey { + fn resolve_certificates(&self) -> Result { + if let Some(path) = &self.path { + let p12_data = std::fs::read(path)?; + + let p12_password = + get_pkcs12_password(self.password.clone(), self.password_path.clone())?; + + let (cert, key) = parse_pfx_data(&p12_data, &p12_password)?; + + Ok(SigningCertificates { + keys: vec![Box::new(key)], + certs: vec![cert], + }) + } else { + Ok(Default::default()) + } + } +} + +#[derive(Args, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct PemSigningKey { + /// Path to file containing PEM encoded certificate/key data + #[arg(long = "pem-file", alias = "pem-source", value_name = "PATH")] + #[serde(rename = "files")] + pub paths: Vec, +} + +impl KeySource for PemSigningKey { + fn resolve_certificates(&self) -> Result { + let mut res = SigningCertificates::default(); + + for path in &self.paths { + warn!("reading PEM data from {}", path.display()); + let pem_data = std::fs::read(path)?; + + for pem in pem::parse_many(pem_data).map_err(AppleCodesignError::CertificatePem)? { + match pem.tag() { + "CERTIFICATE" => { + info!("adding certificate from {}", path.display()); + res.certs + .push(CapturedX509Certificate::from_der(pem.contents())?); + } + "PRIVATE KEY" => { + info!("adding private key from {}", path.display()); + res.keys.push(Box::new(InMemoryPrivateKey::from_pkcs8_der( + pem.contents(), + )?)); + } + "RSA PRIVATE KEY" => { + info!("adding RSA private key from {}", path.display()); + res.keys.push(Box::new(InMemoryPrivateKey::from_pkcs1_der( + pem.contents(), + )?)); + } + tag => warn!("(unhandled PEM tag {}; ignoring)", tag), + } + } + } + + Ok(res) + } +} + +#[derive(Args, Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct RemoteSigningKey { + /// URL of a remote code signing server + #[arg(long = "remote-signing-url", value_name = "URL")] + pub url: Option, + + /// Base64 encoded public key data describing the signer + #[arg( + long = "remote-public-key", + group = "remote-initialization", + value_name = "BASE64 ENCODED PUBLIC KEY" + )] + pub public_key: Option, + + /// PEM encoded public key data describing the signer + #[arg( + long = "remote-public-key-pem-file", + group = "remote-initialization", + group = "remote-initialization", + value_name = "PATH" + )] + pub public_key_pem_path: Option, + + /// Shared secret used for remote signing + #[arg( + long = "remote-shared-secret", + group = "remote-initialization", + value_name = "SECRET" + )] + pub shared_secret: Option, + + /// Environment variable holding the shared secret used for remote signing + #[arg( + long = "remote-shared-secret-env", + group = "remote-initialization", + value_name = "ENV VAR NAME" + )] + pub shared_secret_env: Option, +} + +impl KeySource for RemoteSigningKey { + fn resolve_certificates(&self) -> Result { + if let Some(initiator) = self.remote_signing_initiator()? { + let client = UnjoinedSigningClient::new_initiator( + self.url(), + initiator, + Some(super::print_session_join), + )?; + + let mut certs = vec![client.signing_certificate().clone()]; + certs.extend(client.certificate_chain().iter().cloned()); + + Ok(SigningCertificates { + keys: vec![Box::new(client)], + certs, + }) + } else { + Ok(Default::default()) + } + } + + fn exclusive(&self) -> bool { + true + } +} + +impl RemoteSigningKey { + /// Obtain the URL of the relay server. + pub fn url(&self) -> String { + self.url + .clone() + .unwrap_or_else(|| crate::remote_signing::DEFAULT_SERVER_URL.to_string()) + } + + fn remote_signing_initiator( + &self, + ) -> Result>, RemoteSignError> { + let server_url = self.url(); + + if let Some(public_key_data) = &self.public_key { + let public_key_data = STANDARD_ENGINE.decode(public_key_data)?; + + Ok(Some(Box::new(PublicKeyInitiator::new( + public_key_data, + Some(server_url), + )?))) + } else if let Some(path) = &self.public_key_pem_path { + let pem_data = std::fs::read(path)?; + let doc = pem::parse(pem_data)?; + + let spki_der = match doc.tag() { + "PUBLIC KEY" => doc.contents().to_vec(), + "CERTIFICATE" => { + let cert = CapturedX509Certificate::from_der(doc.contents())?; + cert.to_public_key_der()?.as_ref().to_vec() + } + tag => { + error!( + "unknown PEM format: {}; only `PUBLIC KEY` and `CERTIFICATE` are parsed", + tag + ); + return Err(RemoteSignError::Crypto("invalid public key data".into())); + } + }; + + Ok(Some(Box::new(PublicKeyInitiator::new( + spki_der, + Some(server_url), + )?))) + } else if let Some(env) = &self.shared_secret_env { + let secret = std::env::var(env).map_err(|_| { + RemoteSignError::ClientState( + "failed reading from shared secret environment variable", + ) + })?; + + Ok(Some(Box::new(SharedSecretInitiator::new( + secret.as_bytes().to_vec(), + )?))) + } else if let Some(value) = &self.shared_secret { + Ok(Some(Box::new(SharedSecretInitiator::new( + value.as_bytes().to_vec(), + )?))) + } else { + Ok(None) + } + } +} + +#[derive(Args, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct CertificateDerSigningKey { + /// Path to file containing DER encoded certificate data + #[arg( + id = "certificate_der_paths", + long = "certificate-der-file", + alias = "der-source", + alias = "der-file", + value_name = "PATH" + )] + pub paths: Vec, +} + +impl KeySource for CertificateDerSigningKey { + fn resolve_certificates(&self) -> Result { + let mut res = SigningCertificates::default(); + + for path in &self.paths { + warn!("reading DER file {}", path.display()); + let der_data = std::fs::read(path)?; + + res.certs.push(CapturedX509Certificate::from_der(der_data)?); + } + + Ok(res) + } +} + +#[derive(Args, Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct CertificateSource { + #[command(flatten)] + #[serde(default, rename = "smartcard", skip_serializing_if = "Option::is_none")] + pub smartcard_key: Option, + + #[command(flatten)] + #[serde( + default, + rename = "macos_keychain", + skip_serializing_if = "Option::is_none" + )] + pub macos_keychain_key: Option, + + #[command(flatten)] + #[serde( + default, + rename = "windows_store", + skip_serializing_if = "Option::is_none" + )] + pub windows_store_key: Option, + + #[command(flatten)] + #[serde(default, rename = "pem", skip_serializing_if = "Option::is_none")] + pub pem_path_key: Option, + + #[command(flatten)] + #[serde(default, rename = "p12", skip_serializing_if = "Option::is_none")] + pub p12_key: Option, + + #[command(flatten)] + #[serde(default, rename = "remote", skip_serializing_if = "Option::is_none")] + pub remote_signing_key: Option, + + #[command(flatten)] + #[serde( + default, + rename = "certificate_der", + skip_serializing_if = "Option::is_none" + )] + pub certificate_der_key: Option, +} + +impl CertificateSource { + /// Obtain a reference to all [KeySource] present. + pub fn key_sources(&self, scan_smartcard: bool) -> Vec<&dyn KeySource> { + let mut res = vec![]; + + if scan_smartcard { + if let Some(key) = &self.smartcard_key { + res.push(key as &dyn KeySource); + } + } + + if let Some(key) = &self.macos_keychain_key { + res.push(key as &dyn KeySource); + } + + if let Some(key) = &self.windows_store_key { + res.push(key as &dyn KeySource); + } + + if let Some(key) = &self.pem_path_key { + res.push(key as &dyn KeySource); + } + + if let Some(key) = &self.p12_key { + res.push(key as &dyn KeySource); + } + + if let Some(key) = &self.remote_signing_key { + res.push(key as &dyn KeySource); + } + + if let Some(key) = &self.certificate_der_key { + res.push(key as &dyn KeySource); + } + + res + } + + pub fn resolve_certificates( + &self, + scan_smartcard: bool, + ) -> Result { + let mut res = SigningCertificates::default(); + + for key in self.key_sources(scan_smartcard) { + let certs = key.resolve_certificates()?; + + if key.exclusive() && !certs.is_empty() { + return Ok(certs); + } + + res.extend(certs); + } + + Ok(res) + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/cli/config.rs b/3rdparty/apple-codesign-0.29.0/src/cli/config.rs new file mode 100644 index 00000000..6c6c88bf --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/cli/config.rs @@ -0,0 +1,450 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use { + crate::{ + cli::{certificate_source::CertificateSource, ScopedSigningSettingsValues}, + error::AppleCodesignError, + }, + figment::{ + providers::{Env, Format, Serialized, Toml}, + Figment, + }, + log::debug, + serde::{Deserialize, Serialize}, + std::{ + collections::BTreeMap, + ops::{Deref, DerefMut}, + path::Path, + }, +}; + +/// Configuration file profile definition. +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields, rename_all = "kebab-case")] +pub struct Config { + /// Configuration for the sign command. + #[serde(default)] + pub sign: SignConfig, + + #[serde(default)] + pub remote_sign: RemoteSignConfig, +} + +/// Configuration for the sign command. +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct SignConfig { + /// Defines a source for the cryptographic signing key. + #[serde(default)] + pub signer: CertificateSource, + + /// Keys are scope paths. Values are per-path configs. + #[serde(default, rename = "path", skip_serializing_if = "BTreeMap::is_empty")] + pub paths: BTreeMap, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct RemoteSignConfig { + /// Defines a source for the cryptographic signing key. + #[serde(default)] + pub signer: CertificateSource, +} + +/// Used to instantiate [Config] instances. +#[derive(Clone)] +pub struct ConfigBuilder { + loader: Figment, +} + +impl Default for ConfigBuilder { + fn default() -> Self { + Self { + loader: Figment::new(), + } + } +} + +impl Deref for ConfigBuilder { + type Target = Figment; + + fn deref(&self) -> &Self::Target { + &self.loader + } +} + +impl DerefMut for ConfigBuilder { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.loader + } +} + +impl ConfigBuilder { + /// Add the $XDG_CONFIG/rcodesign/rcodesign.toml user config file if it exists. + pub fn with_user_config_file(mut self) -> Self { + if let Some(base) = dirs::config_dir() { + let p = base.join("rcodesign").join("rcodesign.toml"); + debug!("registering user config file: {}", p.display()); + + self.loader = self.loader.merge(Toml::file(p).nested()); + } + + self + } + + /// Merge a config file from `pwd`/rcodesign.toml. + pub fn with_cwd_config_file(mut self) -> Self { + if let Ok(cwd) = std::env::current_dir() { + let p = cwd.join("rcodesign.toml"); + debug!("registering cwd config file: {}", p.display()); + + self.loader = self.loader.merge(Toml::file(p).nested()); + } + + self + } + + /// Merge with environment variables. + /// + /// Must be called after [profile()] to ensure environment variables are + /// mapped to the current profile. + pub fn with_env_prefix(mut self) -> Self { + debug!("registering RCODESIGN_ environment variable config source"); + let env = Env::prefixed("RCODESIGN_") + .split("_") + .profile(self.loader.profile().to_string()); + + self.loader = self.loader.merge(env); + self + } + + /// Add a TOML config file to this instance. + pub fn toml_file(mut self, path: impl AsRef) -> Self { + let path = path.as_ref(); + debug!("registering custom config file: {}", path.display()); + self.loader = self.loader.merge(Toml::file(path).nested()); + self + } + + /// Add a TOML string config to this instance. + pub fn toml_string(mut self, data: &str) -> Self { + debug!("registering TOML string config data"); + self.loader = self.loader.merge(Toml::string(data).nested()); + self + } + + /// Merge a [Config] struct into this builder + pub fn with_config_struct(mut self, config: Config) -> Self { + debug!("registering config struct"); + let serialized = Serialized::defaults(config).profile(self.loader.profile().to_string()); + + self.loader = self.loader.merge(serialized); + self + } + + /// Load the named profile instead of the `[default]` profile. + pub fn profile(mut self, profile: String) -> Self { + self.loader = self.loader.select(profile); + self + } + + /// Obtain a config profile. + pub fn config(self) -> Result { + Ok(self.loader.extract()?) + } +} + +#[cfg(test)] +mod test { + use super::*; + use { + crate::cli::certificate_source::{ + MacosKeychainSigningKey, P12SigningKey, PemSigningKey, RemoteSigningKey, + SmartcardSigningKey, WindowsStoreSigningKey, + }, + std::path::PathBuf, + }; + + #[test] + fn default_config() { + let c = ConfigBuilder::default().config().unwrap(); + + assert_eq!(c, Config::default()); + } + + #[test] + fn smartcard_signer() { + let c = ConfigBuilder::default() + .toml_string( + r#" + [default.sign] + signer.smartcard = { slot = "9c" } + "#, + ) + .config() + .unwrap(); + + assert_eq!( + c.sign.signer, + CertificateSource { + smartcard_key: Some(SmartcardSigningKey { + slot: Some("9c".into()), + pin: None, + pin_env: None, + }), + ..Default::default() + } + ); + + let c = ConfigBuilder::default() + .toml_string( + r#" + [default.sign] + signer.smartcard = { slot = "9c", pin = "1234" } + "#, + ) + .config() + .unwrap(); + assert_eq!( + c.sign.signer, + CertificateSource { + smartcard_key: Some(SmartcardSigningKey { + slot: Some("9c".into()), + pin: Some("1234".into()), + pin_env: None, + }), + ..Default::default() + } + ); + } + + #[test] + fn macos_keychain_signer() { + assert_eq!( + ConfigBuilder::default() + .toml_string( + r#" + [default.sign] + signer.macos_keychain = { sha256_fingerprint = "deadbeef" } + "#, + ) + .config() + .unwrap() + .sign + .signer, + CertificateSource { + macos_keychain_key: Some(MacosKeychainSigningKey { + domains: vec![], + sha256_fingerprint: Some("deadbeef".into()), + }), + ..Default::default() + } + ); + } + + #[test] + fn pem_signer() { + assert_eq!( + ConfigBuilder::default() + .toml_string( + r#" + [default.sign] + signer.pem.files = ["key.pem", "cert.pem"] + "# + ) + .config() + .unwrap() + .sign + .signer, + CertificateSource { + pem_path_key: Some(PemSigningKey { + paths: vec![PathBuf::from("key.pem"), PathBuf::from("cert.pem")] + }), + ..Default::default() + } + ); + } + + #[test] + fn p12_signer() { + assert_eq!( + ConfigBuilder::default() + .toml_string( + r#" + [default.sign] + signer.p12 = { path = "key.p12", password = "password" } + "# + ) + .config() + .unwrap() + .sign + .signer, + CertificateSource { + p12_key: Some(P12SigningKey { + path: Some(PathBuf::from("key.p12")), + password: Some("password".into()), + password_path: None + }), + ..Default::default() + } + ); + assert_eq!( + ConfigBuilder::default() + .toml_string( + r#" + [default.sign] + signer.p12 = { path = "key.p12", password_path = "path/to/file" } + "# + ) + .config() + .unwrap() + .sign + .signer, + CertificateSource { + p12_key: Some(P12SigningKey { + path: Some(PathBuf::from("key.p12")), + password: None, + password_path: Some("path/to/file".into()), + }), + ..Default::default() + } + ); + } + + #[test] + fn remote_signer() { + assert_eq!( + ConfigBuilder::default() + .toml_string( + r#" + [default.sign] + signer.remote.public_key = "DEADBEEF" + "# + ) + .config() + .unwrap() + .sign + .signer, + CertificateSource { + remote_signing_key: Some(RemoteSigningKey { + public_key: Some("DEADBEEF".into()), + ..Default::default() + }), + ..Default::default() + } + ); + + assert_eq!( + ConfigBuilder::default() + .toml_string( + r#" + [default.sign] + signer.remote.public_key_pem_path = "path/to/cert.pem" + "# + ) + .config() + .unwrap() + .sign + .signer, + CertificateSource { + remote_signing_key: Some(RemoteSigningKey { + public_key_pem_path: Some("path/to/cert.pem".into()), + ..Default::default() + }), + ..Default::default() + } + ); + + assert_eq!( + ConfigBuilder::default() + .toml_string( + r#" + [default.sign] + signer.remote.shared_secret = "SECRET" + "# + ) + .config() + .unwrap() + .sign + .signer, + CertificateSource { + remote_signing_key: Some(RemoteSigningKey { + shared_secret: Some("SECRET".into()), + ..Default::default() + }), + ..Default::default() + } + ); + } + + #[test] + fn windows_store() { + assert_eq!( + ConfigBuilder::default() + .toml_string( + r#" + [default.sign] + signer.windows_store = { stores = ["user"], sha1_fingerprint = "DEADBEEF" } + "# + ) + .config() + .unwrap() + .sign + .signer, + CertificateSource { + windows_store_key: Some(WindowsStoreSigningKey { + stores: vec!["user".into()], + sha1_fingerprint: Some("DEADBEEF".into()), + }), + ..Default::default() + } + ); + } + + #[test] + fn paths_toml() { + assert_eq!( + ConfigBuilder::default() + .toml_string( + r#" + [default.sign.path."Contents/MacOS/extra-bin"] + binary_identifier = "ident" + code_requirements_file = "reqs" + code_resources_file = "code-resources" + code_signature_flags = ["runtime"] + digests = ["sha1", "sha256"] + entitlements_xml_file = "entitlements.plist" + launch_constraints_self_file = "lc-self" + launch_constraints_parent_file = "lc-parent" + launch_constraints_responsible_file = "lc-responsible" + library_constraints_file = "lc-library" + runtime_version = "11.0.0" + info_plist_file = "Info.plist" + "# + ) + .config() + .unwrap() + .sign + .paths, + BTreeMap::from_iter([( + "Contents/MacOS/extra-bin".into(), + ScopedSigningSettingsValues { + binary_identifier: Some("ident".into()), + code_requirements_file: Some("reqs".into()), + code_resources_file: Some("code-resources".into()), + code_signature_flags: vec!["runtime".into()], + digests: vec!["sha1".into(), "sha256".into()], + entitlements_xml_file: Some("entitlements.plist".into()), + launch_constraints_self_file: Some("lc-self".into()), + launch_constraints_parent_file: Some("lc-parent".into()), + launch_constraints_responsible_file: Some("lc-responsible".into()), + library_constraints_file: Some("lc-library".into()), + runtime_version: Some("11.0.0".into()), + info_plist_file: Some("Info.plist".into()), + } + )]) + ); + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/cli/debug_commands.rs b/3rdparty/apple-codesign-0.29.0/src/cli/debug_commands.rs new file mode 100644 index 00000000..880807db --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/cli/debug_commands.rs @@ -0,0 +1,406 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use { + crate::{ + cli::{CliCommand, Context}, + code_requirement::CodeRequirements, + cryptography::DigestType, + error::{AppleCodesignError, Result}, + }, + clap::{Parser, ValueEnum}, + log::warn, + std::{ops::Deref, path::PathBuf}, +}; + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +pub enum MachOArch { + Aarch64, + X86_64, +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +pub enum MachOFileType { + Executable, + Dylib, +} + +impl MachOFileType { + fn to_header_filetype(&self) -> u32 { + match self { + Self::Executable => object::macho::MH_EXECUTE, + Self::Dylib => object::macho::MH_DYLIB, + } + } +} + +#[derive(Parser)] +pub struct DebugCreateCodeRequirements { + /// Code requirement expression to emit. + #[arg(long, value_enum)] + code_requirement: crate::policy::ExecutionPolicy, + + /// Path to write binary requirements to. + path: PathBuf, +} + +impl CliCommand for DebugCreateCodeRequirements { + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let expression = self.code_requirement.deref(); + + let mut reqs = CodeRequirements::default(); + reqs.push(expression.clone()); + + let data = reqs.to_blob_data()?; + + println!("writing code requirements to {}", self.path.display()); + + if let Some(parent) = self.path.parent() { + std::fs::create_dir_all(parent)?; + } + + std::fs::write(&self.path, data)?; + + Ok(()) + } +} + +#[derive(Parser)] +pub struct DebugCreateConstraints { + /// Team identifier constraint. + #[arg(long)] + team_id: Option, + + /// Path to write plist XML to. + path: PathBuf, +} + +impl CliCommand for DebugCreateConstraints { + fn run(&self, _context: &Context) -> Result<()> { + let mut v = plist::Dictionary::default(); + + if let Some(id) = &self.team_id { + v.insert("team-identifier".into(), id.to_string().into()); + } + + let mut reqs = plist::Dictionary::default(); + + reqs.insert("$or".into(), v.into()); + + let v = plist::Value::Dictionary(reqs); + + println!("writing constraints plist to {}", self.path.display()); + v.to_file_xml(&self.path)?; + + Ok(()) + } +} + +#[derive(Parser)] +pub struct DebugCreateEntitlements { + /// Add the `get-task-allow` entitlement. + #[arg(long)] + get_task_allow: bool, + + /// Add the `run-unsigned-code` entitlement. + #[arg(long)] + run_unsigned_code: bool, + + /// Add the `com.apple.private.cs.debugger` entitlement. + #[arg(long)] + debugger: bool, + + /// Add the `dynamic-codesigning` entitlement. + #[arg(long)] + dynamic_code_signing: bool, + + /// Add the `com.apple.private.skip-library-validation` entitlement. + #[arg(long)] + skip_library_validation: bool, + + /// Add the `com.apple.private.amfi.can-load-cdhash` entitlement. + #[arg(long)] + can_load_cd_hash: bool, + + /// Add the `com.apple.private.amfi.can-execute-cdhash` entitlement. + #[arg(long)] + can_execute_cd_hash: bool, + + /// Path to write entitlements to. + output_path: PathBuf, +} + +impl CliCommand for DebugCreateEntitlements { + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let mut d = plist::Dictionary::default(); + + if self.get_task_allow { + d.insert("get-task-allow".into(), true.into()); + } + if self.run_unsigned_code { + d.insert("run-unsigned-code".into(), true.into()); + } + if self.debugger { + d.insert("com.apple.private.cs.debugger".into(), true.into()); + } + if self.dynamic_code_signing { + d.insert("dynamic-codesigning".into(), true.into()); + } + if self.skip_library_validation { + d.insert( + "com.apple.private.skip-library-validation".into(), + true.into(), + ); + } + if self.can_load_cd_hash { + d.insert("com.apple.private.amfi.can-load-cdhash".into(), true.into()); + } + if self.can_execute_cd_hash { + d.insert( + "com.apple.private.amfi.can-execute-cdhash".into(), + true.into(), + ); + } + + let value = plist::Value::from(d); + let mut xml = vec![]; + value.to_writer_xml(&mut xml)?; + + warn!("writing {}", self.output_path.display()); + if let Some(parent) = self.output_path.parent() { + std::fs::create_dir_all(parent)?; + } + + std::fs::write(&self.output_path, &xml)?; + + Ok(()) + } +} + +#[derive(Parser)] +pub struct DebugCreateInfoPlist { + /// Name of the bundle. + #[arg(long)] + bundle_name: String, + + /// Bundle package type. + #[arg(long, default_value = "APPL")] + package_type: String, + + /// CFBundleExecutable value. + #[arg(long)] + bundle_executable: Option, + + /// Bundle identifier. + #[arg(long, default_value = "com.example.mybundle")] + bundle_identifier: String, + + /// Bundle version. + #[arg(long, default_value = "1.0.0")] + bundle_version: String, + + /// Path to write Info.plist to. + output_path: PathBuf, + + /// Write an empty Info.plist file. Other arguments ignored. + #[arg(long)] + empty: bool, +} + +impl CliCommand for DebugCreateInfoPlist { + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let mut d = plist::Dictionary::default(); + + if !self.empty { + d.insert("CFBundleName".into(), self.bundle_name.clone().into()); + d.insert( + "CFBundlePackageType".into(), + self.package_type.clone().into(), + ); + d.insert( + "CFBundleDisplayName".into(), + self.bundle_name.clone().into(), + ); + if let Some(exe) = &self.bundle_executable { + d.insert("CFBundleExecutable".into(), exe.clone().into()); + } + d.insert( + "CFBundleIdentifier".into(), + self.bundle_identifier.clone().into(), + ); + d.insert("CFBundleVersion".into(), self.bundle_version.clone().into()); + d.insert("CFBundleSignature".into(), "sig".into()); + d.insert("CFBundleExecutable".into(), self.bundle_name.clone().into()); + } + + let value = plist::Value::from(d); + + let mut xml = vec![]; + value.to_writer_xml(&mut xml)?; + + println!("writing {}", self.output_path.display()); + if let Some(parent) = self.output_path.parent() { + std::fs::create_dir_all(parent)?; + } + + std::fs::write(&self.output_path, &xml)?; + + Ok(()) + } +} + +#[derive(Parser)] +pub struct DebugCreateMachO { + /// Architecture of Mach-O binary. + #[arg(long, value_enum, default_value_t = MachOArch::Aarch64)] + architecture: MachOArch, + + /// The Mach-O file type. + #[arg(long, value_enum, default_value_t = MachOFileType::Executable)] + file_type: MachOFileType, + + /// Do not write platform targeting to Mach-O binary. + #[arg(long)] + no_targeting: bool, + + /// The minimum operating system version the binary will run on. + #[arg(long)] + minimum_os_version: Option, + + /// The platform SDK version used to build the binary. + #[arg(long)] + sdk_version: Option, + + /// Set the file start offset of the __TEXT segment. + #[arg(long)] + text_segment_start_offset: Option, + + /// Filename of Mach-O binary to write. + output_path: PathBuf, +} + +impl CliCommand for DebugCreateMachO { + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let mut builder = match self.architecture { + MachOArch::Aarch64 => { + crate::macho_builder::MachOBuilder::new_aarch64(self.file_type.to_header_filetype()) + } + MachOArch::X86_64 => { + crate::macho_builder::MachOBuilder::new_x86_64(self.file_type.to_header_filetype()) + } + }; + + let target = match ( + self.no_targeting, + &self.minimum_os_version, + &self.sdk_version, + ) { + (true, _, _) => None, + (false, None, None) => { + warn!("assuming default minimum version 11.0.0"); + + Some(crate::macho::MachoTarget { + platform: crate::Platform::MacOs, + minimum_os_version: semver::Version::new(11, 0, 0), + sdk_version: semver::Version::new(11, 0, 0), + }) + } + (false, _, _) => { + let minimum_os_version = self + .minimum_os_version + .clone() + .unwrap_or_else(|| self.sdk_version.clone().unwrap()); + let sdk_version = self + .sdk_version + .clone() + .unwrap_or_else(|| self.minimum_os_version.clone().unwrap()); + + Some(crate::macho::MachoTarget { + platform: crate::Platform::MacOs, + minimum_os_version, + sdk_version, + }) + } + }; + + if let Some(target) = target { + builder = builder.macho_target(target); + } + + if let Some(offset) = self.text_segment_start_offset { + builder = builder.text_segment_start_offset(offset); + } + + let data = builder.write_macho()?; + + warn!("writing Mach-O to {}", self.output_path.display()); + if let Some(parent) = self.output_path.parent() { + std::fs::create_dir_all(parent)?; + } + + std::fs::write(&self.output_path, data)?; + + Ok(()) + } +} + +#[derive(Parser)] +pub struct DebugFileTree { + /// Directory to walk. + path: PathBuf, +} + +impl CliCommand for DebugFileTree { + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let root = self + .path + .components() + .last() + .expect("should have final component") + .as_os_str() + .to_string_lossy() + .to_string(); + + for entry in walkdir::WalkDir::new(&self.path).sort_by_file_name() { + let entry = entry?; + + let path = entry.path(); + + let rel_path = if let Ok(p) = path.strip_prefix(&self.path) { + format!("{}/{}", root, p.to_string_lossy().replace('\\', "/")) + } else { + root.clone() + }; + + let metadata = entry.metadata()?; + + let entry_type = if metadata.is_symlink() { + 'l' + } else if metadata.is_dir() { + 'd' + } else if metadata.is_file() { + 'f' + } else { + 'u' + }; + + let sha256 = if entry_type == 'f' { + let data = std::fs::read(path)?; + hex::encode(DigestType::Sha256.digest_data(&data)?)[0..20].to_string() + } else { + " ".repeat(20) + }; + + let link_target = if entry_type == 'l' { + format!(" -> {}", std::fs::read_link(path)?.to_string_lossy()) + } else { + "".to_string() + }; + + println!("{} {} {}{}", entry_type, sha256, rel_path, link_target); + } + + Ok(()) + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/cli/extract_commands.rs b/3rdparty/apple-codesign-0.29.0/src/cli/extract_commands.rs new file mode 100644 index 00000000..69741297 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/cli/extract_commands.rs @@ -0,0 +1,652 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use { + crate::{ + cli::{CliCommand, Context}, + code_directory::CodeDirectoryBlob, + cryptography::DigestType, + embedded_signature::{Blob, CodeSigningSlot, RequirementSetBlob}, + error::AppleCodesignError, + macho::MachFile, + }, + base64::{engine::general_purpose::STANDARD as STANDARD_ENGINE, Engine}, + clap::{Parser, Subcommand}, + cryptographic_message_syntax::SignedData, + std::{io::Write, path::PathBuf}, +}; + +fn print_signed_data( + prefix: &str, + signed_data: &SignedData, + external_content: Option>, +) -> Result<(), AppleCodesignError> { + println!( + "{}signed content (embedded): {:?}", + prefix, + signed_data.signed_content().map(hex::encode) + ); + println!( + "{}signed content (external): {:?}... ({} bytes)", + prefix, + external_content.as_ref().map(|x| hex::encode(&x[0..40])), + external_content.as_ref().map(|x| x.len()).unwrap_or(0), + ); + + let content = if let Some(v) = signed_data.signed_content() { + Some(v) + } else { + external_content.as_ref().map(|v| v.as_ref()) + }; + + if let Some(content) = content { + println!( + "{}signed content SHA-1: {}", + prefix, + hex::encode(DigestType::Sha1.digest_data(content)?) + ); + println!( + "{}signed content SHA-256: {}", + prefix, + hex::encode(DigestType::Sha256.digest_data(content)?) + ); + println!( + "{}signed content SHA-384: {}", + prefix, + hex::encode(DigestType::Sha384.digest_data(content)?) + ); + println!( + "{}signed content SHA-512: {}", + prefix, + hex::encode(DigestType::Sha512.digest_data(content)?) + ); + } + println!( + "{}certificate count: {}", + prefix, + signed_data.certificates().count() + ); + for (i, cert) in signed_data.certificates().enumerate() { + println!( + "{}certificate #{}: subject CN={}; self signed={}", + prefix, + i, + cert.subject_common_name() + .unwrap_or_else(|| "".to_string()), + cert.subject_is_issuer() + ); + } + println!("{}signer count: {}", prefix, signed_data.signers().count()); + for (i, signer) in signed_data.signers().enumerate() { + println!( + "{}signer #{}: digest algorithm: {:?}", + prefix, + i, + signer.digest_algorithm() + ); + println!( + "{}signer #{}: signature algorithm: {:?}", + prefix, + i, + signer.signature_algorithm() + ); + + if let Some(sa) = signer.signed_attributes() { + println!( + "{}signer #{}: content type: {}", + prefix, + i, + sa.content_type() + ); + println!( + "{}signer #{}: message digest: {}", + prefix, + i, + hex::encode(sa.message_digest()) + ); + println!( + "{}signer #{}: signing time: {:?}", + prefix, + i, + sa.signing_time() + ); + } + + let digested_data = signer.signed_content_with_signed_data(signed_data); + + println!( + "{}signer #{}: signature content SHA-1: {}", + prefix, + i, + hex::encode(DigestType::Sha1.digest_data(&digested_data)?) + ); + println!( + "{}signer #{}: signature content SHA-256: {}", + prefix, + i, + hex::encode(DigestType::Sha256.digest_data(&digested_data)?) + ); + println!( + "{}signer #{}: signature content SHA-384: {}", + prefix, + i, + hex::encode(DigestType::Sha384.digest_data(&digested_data)?) + ); + println!( + "{}signer #{}: signature content SHA-512: {}", + prefix, + i, + hex::encode(DigestType::Sha512.digest_data(&digested_data)?) + ); + + if signed_data.signed_content().is_some() { + println!( + "{}signer #{}: digest valid: {}", + prefix, + i, + signer + .verify_message_digest_with_signed_data(signed_data) + .is_ok() + ); + } + println!( + "{}signer #{}: signature valid: {}", + prefix, + i, + signer + .verify_signature_with_signed_data(signed_data) + .is_ok() + ); + + println!( + "{}signer #{}: time-stamp token present: {}", + prefix, + i, + signer.time_stamp_token_signed_data()?.is_some() + ); + + if let Some(tsp_signed_data) = signer.time_stamp_token_signed_data()? { + let prefix = format!("{prefix}signer #{i}: time-stamp token: "); + + print_signed_data(&prefix, &tsp_signed_data, None)?; + } + } + + Ok(()) +} + +#[derive(Clone, Parser)] +struct ExtractCommon { + /// Path to Mach-O binary to examine + path: PathBuf, +} + +#[derive(Clone, Subcommand)] +enum ExtractData { + /// Code directory blobs. + Blobs(ExtractCommon), + /// Information about cryptographic message syntax signature. + CmsInfo(ExtractCommon), + /// PEM encoded cryptographic message syntax signature. + CmsPem(ExtractCommon), + /// Binary cryptographic message syntax signature. Should be BER encoded ASN.1 data. + CmsRaw(ExtractCommon), + /// ASN.1 decoded cryptographic message syntax data. + Cms(ExtractCommon), + /// Information from the main code directory data structure. + CodeDirectory(ExtractCommon), + /// Raw binary data composing the code directory data structure. + CodeDirectoryRaw(ExtractCommon), + /// Reserialize the parsed code directory, parse it again, and then print it like `code-directory` would. + CodeDirectorySerialized(ExtractCommon), + /// Reserialize the parsed code directory and emit its binary. + /// + /// Useful for comparing round-tripping of code directory data. + CodeDirectorySerializedRaw(ExtractCommon), + /// Information about the __LINKEDIT Mach-O segment. + LinkeditInfo(ExtractCommon), + /// Complete content of the __LINKEDIT Mach-O segment. + LinkeditSegmentRaw(ExtractCommon), + /// Mach-O file header data. + MachoHeader(ExtractCommon), + /// High-level information about Mach-O load commands. + MachoLoadCommands(ExtractCommon), + /// Debug formatted Mach-O load command data structures. + MachoLoadCommandsRaw(ExtractCommon), + /// Information about Mach-O segments. + MachoSegments(ExtractCommon), + /// Mach-O targeting info. + MachoTarget(ExtractCommon), + /// Parsed code requirement statement/expression. + Requirements(ExtractCommon), + /// Raw binary data composing the requirements blob/slot. + RequirementsRaw(ExtractCommon), + /// Dump the internal Rust data structures representing the requirements expressions. + RequirementsRust(ExtractCommon), + /// Reserialize the code requirements blob, parse it again, and then print it like `requirements` would. + RequirementsSerialized(ExtractCommon), + /// Like `requirements-serialized` except emit the binary data representation. + RequirementsSerializedRaw(ExtractCommon), + /// Raw binary data constituting the signature data embedded in the binary. + SignatureRaw(ExtractCommon), + /// Show information about the SuperBlob record and high-level details of embedded Blob records. + Superblob(ExtractCommon), +} + +impl ExtractData { + fn common_args(&self) -> &ExtractCommon { + match self { + ExtractData::Blobs(x) => x, + ExtractData::CmsInfo(x) => x, + ExtractData::CmsPem(x) => x, + ExtractData::CmsRaw(x) => x, + ExtractData::Cms(x) => x, + ExtractData::CodeDirectoryRaw(x) => x, + ExtractData::CodeDirectorySerializedRaw(x) => x, + ExtractData::CodeDirectorySerialized(x) => x, + ExtractData::CodeDirectory(x) => x, + ExtractData::LinkeditInfo(x) => x, + ExtractData::LinkeditSegmentRaw(x) => x, + ExtractData::MachoHeader(x) => x, + ExtractData::MachoLoadCommands(x) => x, + ExtractData::MachoLoadCommandsRaw(x) => x, + ExtractData::MachoSegments(x) => x, + ExtractData::MachoTarget(x) => x, + ExtractData::RequirementsRaw(x) => x, + ExtractData::RequirementsRust(x) => x, + ExtractData::RequirementsSerializedRaw(x) => x, + ExtractData::RequirementsSerialized(x) => x, + ExtractData::Requirements(x) => x, + ExtractData::SignatureRaw(x) => x, + ExtractData::Superblob(x) => x, + } + } +} + +#[derive(Parser)] +pub struct Extract { + /// Index of Mach-O binary to operate on within a universal/fat binary + #[arg(long, global = true, default_value = "0")] + universal_index: usize, + + /// Which data to extract and how to format it + #[command(subcommand)] + data: ExtractData, +} + +impl CliCommand for Extract { + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let common = self.data.common_args(); + + let data = std::fs::read(&common.path)?; + let mach = MachFile::parse(&data)?; + let macho = mach.nth_macho(self.universal_index)?; + + match self.data { + ExtractData::Blobs(_) => { + let embedded = macho + .code_signature()? + .ok_or(AppleCodesignError::BinaryNoCodeSignature)?; + + for blob in embedded.blobs { + let parsed = blob.into_parsed_blob()?; + println!("{parsed:#?}"); + } + } + ExtractData::CmsInfo(_) => { + let embedded = macho + .code_signature()? + .ok_or(AppleCodesignError::BinaryNoCodeSignature)?; + + if let Some(cms) = embedded.signature_data()? { + let signed_data = SignedData::parse_ber(cms)?; + + let cd_data = if let Ok(Some(blob)) = embedded.code_directory() { + Some(blob.to_blob_bytes()?) + } else { + None + }; + + print_signed_data("", &signed_data, cd_data)?; + } else { + eprintln!("no CMS data"); + } + } + ExtractData::CmsPem(_) => { + let embedded = macho + .code_signature()? + .ok_or(AppleCodesignError::BinaryNoCodeSignature)?; + + if let Some(cms) = embedded.signature_data()? { + print!("{}", pem::encode(&pem::Pem::new("PKCS7", cms.to_vec()))); + } else { + eprintln!("no CMS data"); + } + } + ExtractData::CmsRaw(_) => { + let embedded = macho + .code_signature()? + .ok_or(AppleCodesignError::BinaryNoCodeSignature)?; + + if let Some(cms) = embedded.signature_data()? { + std::io::stdout().write_all(cms)?; + } else { + eprintln!("no CMS data"); + } + } + ExtractData::Cms(_) => { + let embedded = macho + .code_signature()? + .ok_or(AppleCodesignError::BinaryNoCodeSignature)?; + + if let Some(signed_data) = embedded.signed_data()? { + println!("{signed_data:#?}"); + } else { + eprintln!("no CMS data"); + } + } + ExtractData::CodeDirectoryRaw(_) => { + let embedded = macho + .code_signature()? + .ok_or(AppleCodesignError::BinaryNoCodeSignature)?; + + if let Some(blob) = embedded.find_slot(CodeSigningSlot::CodeDirectory) { + std::io::stdout().write_all(blob.data)?; + } else { + eprintln!("no code directory"); + } + } + ExtractData::CodeDirectorySerializedRaw(_) => { + let embedded = macho + .code_signature()? + .ok_or(AppleCodesignError::BinaryNoCodeSignature)?; + + if let Ok(Some(cd)) = embedded.code_directory() { + std::io::stdout().write_all(&cd.to_blob_bytes()?)?; + } else { + eprintln!("no code directory"); + } + } + ExtractData::CodeDirectorySerialized(_) => { + let embedded = macho + .code_signature()? + .ok_or(AppleCodesignError::BinaryNoCodeSignature)?; + + if let Ok(Some(cd)) = embedded.code_directory() { + let serialized = cd.to_blob_bytes()?; + println!("{:#?}", CodeDirectoryBlob::from_blob_bytes(&serialized)?); + } + } + ExtractData::CodeDirectory(_) => { + let embedded = macho + .code_signature()? + .ok_or(AppleCodesignError::BinaryNoCodeSignature)?; + + if let Some(cd) = embedded.code_directory()? { + println!("{cd:#?}"); + } else { + eprintln!("no code directory"); + } + } + ExtractData::LinkeditInfo(_) => { + let sig = macho + .find_signature_data()? + .ok_or(AppleCodesignError::BinaryNoCodeSignature)?; + println!("__LINKEDIT segment index: {}", sig.linkedit_segment_index); + println!( + "__LINKEDIT segment start offset: {}", + sig.linkedit_segment_start_offset + ); + println!( + "__LINKEDIT segment end offset: {}", + sig.linkedit_segment_end_offset + ); + println!( + "__LINKEDIT segment size: {}", + sig.linkedit_segment_data.len() + ); + println!( + "__LINKEDIT signature global start offset: {}", + sig.signature_file_start_offset + ); + println!( + "__LINKEDIT signature global end offset: {}", + sig.signature_file_end_offset + ); + println!( + "__LINKEDIT signature local segment start offset: {}", + sig.signature_segment_start_offset + ); + println!( + "__LINKEDIT signature local segment end offset: {}", + sig.signature_segment_end_offset + ); + println!("__LINKEDIT signature size: {}", sig.signature_data.len()); + } + ExtractData::LinkeditSegmentRaw(_) => { + let sig = macho + .find_signature_data()? + .ok_or(AppleCodesignError::BinaryNoCodeSignature)?; + std::io::stdout().write_all(sig.linkedit_segment_data)?; + } + ExtractData::MachoHeader(_) => { + println!("{:#?}", macho.macho.header); + } + ExtractData::MachoLoadCommands(_) => { + println!("load command count: {}", macho.macho.load_commands.len()); + + for command in &macho.macho.load_commands { + println!( + "{}; offsets=0x{:x}-0x{:x} ({}-{}); size={}", + goblin::mach::load_command::cmd_to_str(command.command.cmd()), + command.offset, + command.offset + command.command.cmdsize(), + command.offset, + command.offset + command.command.cmdsize(), + command.command.cmdsize(), + ); + } + } + ExtractData::MachoLoadCommandsRaw(_) => { + for command in &macho.macho.load_commands { + println!("{:?}", command); + } + } + ExtractData::MachoSegments(_) => { + println!("segments count: {}", macho.macho.segments.len()); + for (segment_index, segment) in macho.macho.segments.iter().enumerate() { + let sections = segment.sections()?; + + println!( + "segment #{}; {}; offsets=0x{:x}-0x{:x} ({}-{}); addresses=0x{:x}-0x{:x}; vm/file size {}/{}; section count {}", + segment_index, + segment.name()?, + segment.fileoff, + segment.fileoff as usize + segment.data.len(), + segment.fileoff, + segment.fileoff as usize + segment.data.len(), + segment.vmaddr, + segment.vmaddr + segment.vmsize, + segment.vmsize, + segment.filesize, + sections.len() + ); + for (section_index, (section, _)) in sections.into_iter().enumerate() { + println!( + "segment #{}; section #{}: {}; offsets=0x{:x}-0x{:x} ({}-{}); addresses=0x{:x}-0x{:x}; size {}; align={}; flags={}", + segment_index, + section_index, + section.name()?, + section.offset, + section.offset as u64 + section.size, + section.offset, + section.offset as u64 + section.size, + section.addr, + section.addr + section.size, + section.size, + section.align, + section.flags, + ); + } + } + } + ExtractData::MachoTarget(_) => { + if let Some(target) = macho.find_targeting()? { + println!("Platform: {}", target.platform); + println!("Minimum OS: {}", target.minimum_os_version); + println!("SDK: {}", target.sdk_version); + } else { + println!("Unable to resolve Mach-O targeting from load commands"); + } + } + ExtractData::RequirementsRaw(_) => { + let embedded = macho + .code_signature()? + .ok_or(AppleCodesignError::BinaryNoCodeSignature)?; + + if let Some(blob) = embedded.find_slot(CodeSigningSlot::RequirementSet) { + std::io::stdout().write_all(blob.data)?; + } else { + eprintln!("no requirements"); + } + } + ExtractData::RequirementsRust(_) => { + let embedded = macho + .code_signature()? + .ok_or(AppleCodesignError::BinaryNoCodeSignature)?; + + if let Some(reqs) = embedded.code_requirements()? { + for (typ, req) in &reqs.requirements { + for expr in req.parse_expressions()?.iter() { + println!("{typ} => {expr:#?}"); + } + } + } else { + eprintln!("no requirements"); + } + } + ExtractData::RequirementsSerializedRaw(_) => { + let embedded = macho + .code_signature()? + .ok_or(AppleCodesignError::BinaryNoCodeSignature)?; + + if let Some(reqs) = embedded.code_requirements()? { + std::io::stdout().write_all(&reqs.to_blob_bytes()?)?; + } else { + eprintln!("no requirements"); + } + } + ExtractData::RequirementsSerialized(_) => { + let embedded = macho + .code_signature()? + .ok_or(AppleCodesignError::BinaryNoCodeSignature)?; + + if let Some(reqs) = embedded.code_requirements()? { + let serialized = reqs.to_blob_bytes()?; + println!("{:#?}", RequirementSetBlob::from_blob_bytes(&serialized)?); + } else { + eprintln!("no requirements"); + } + } + ExtractData::Requirements(_) => { + let embedded = macho + .code_signature()? + .ok_or(AppleCodesignError::BinaryNoCodeSignature)?; + + if let Some(reqs) = embedded.code_requirements()? { + for (typ, req) in &reqs.requirements { + for expr in req.parse_expressions()?.iter() { + println!("{typ} => {expr}"); + } + } + } else { + eprintln!("no requirements"); + } + } + ExtractData::SignatureRaw(_) => { + let sig = macho + .find_signature_data()? + .ok_or(AppleCodesignError::BinaryNoCodeSignature)?; + std::io::stdout().write_all(sig.signature_data)?; + } + ExtractData::Superblob(_) => { + let sig = macho + .find_signature_data()? + .ok_or(AppleCodesignError::BinaryNoCodeSignature)?; + let embedded = macho + .code_signature()? + .ok_or(AppleCodesignError::BinaryNoCodeSignature)?; + + println!("file start offset: {}", sig.signature_file_start_offset); + println!("file end offset: {}", sig.signature_file_end_offset); + println!( + "__LINKEDIT start offset: {}", + sig.signature_segment_start_offset + ); + println!( + "__LINKEDIT end offset: {}", + sig.signature_segment_end_offset + ); + println!("length: {}", embedded.length); + println!("blob count: {}", embedded.count); + println!("blobs:"); + for blob in embedded.blobs { + println!("- index: {}", blob.index); + println!( + " offsets: 0x{:x}-0x{:x} ({}-{})", + blob.offset, + blob.offset + blob.length - 1, + blob.offset, + blob.offset + blob.length - 1 + ); + println!(" length: {}", blob.length); + println!(" slot: {:?}", blob.slot); + println!(" magic: {:?} (0x{:x})", blob.magic, u32::from(blob.magic)); + println!( + " sha1: {}", + hex::encode(blob.digest_with(DigestType::Sha1)?) + ); + println!( + " sha256: {}", + hex::encode(blob.digest_with(DigestType::Sha256)?) + ); + println!( + " sha256-truncated: {}", + hex::encode(blob.digest_with(DigestType::Sha256Truncated)?) + ); + println!( + " sha384: {}", + hex::encode(blob.digest_with(DigestType::Sha384)?), + ); + println!( + " sha512: {}", + hex::encode(blob.digest_with(DigestType::Sha512)?), + ); + println!( + " sha1-base64: {}", + STANDARD_ENGINE.encode(blob.digest_with(DigestType::Sha1)?) + ); + println!( + " sha256-base64: {}", + STANDARD_ENGINE.encode(blob.digest_with(DigestType::Sha256)?) + ); + println!( + " sha256-truncated-base64: {}", + STANDARD_ENGINE.encode(blob.digest_with(DigestType::Sha256Truncated)?) + ); + println!( + " sha384-base64: {}", + STANDARD_ENGINE.encode(blob.digest_with(DigestType::Sha384)?) + ); + println!( + " sha512-base64: {}", + STANDARD_ENGINE.encode(blob.digest_with(DigestType::Sha512)?) + ); + } + } + } + + Ok(()) + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/cli/mod.rs b/3rdparty/apple-codesign-0.29.0/src/cli/mod.rs new file mode 100644 index 00000000..82d4d061 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/cli/mod.rs @@ -0,0 +1,2547 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +pub mod certificate_source; +pub mod config; +pub mod debug_commands; +pub mod extract_commands; + +use { + crate::{ + certificate::{ + create_self_signed_code_signing_certificate, AppleCertificate, CertificateProfile, + }, + cli::{ + certificate_source::CertificateSource, + config::{Config, ConfigBuilder}, + }, + code_directory::CodeSignatureFlags, + code_requirement::CodeRequirements, + cryptography::DigestType, + environment_constraints::EncodedEnvironmentConstraints, + error::AppleCodesignError, + macho::MachFile, + reader::SignatureReader, + remote_signing::{ + session_negotiation::{create_session_joiner, SessionJoinState}, + RemoteSignError, UnjoinedSigningClient, + }, + signing::UnifiedSigner, + signing_settings::{SettingsScope, SigningSettings}, + }, + base64::{engine::general_purpose::STANDARD as STANDARD_ENGINE, Engine}, + clap::{ArgAction, Args, Parser, Subcommand}, + difference::{Changeset, Difference}, + log::{error, warn, LevelFilter}, + serde::{Deserialize, Serialize}, + spki::EncodePublicKey, + std::{ + collections::BTreeMap, + path::{Path, PathBuf}, + str::FromStr, + }, + x509_certificate::{CapturedX509Certificate, EcdsaCurve, KeyAlgorithm, X509CertificateBuilder}, +}; + +#[cfg(feature = "notarize")] +use crate::notarization::Notarizer; + +#[cfg(feature = "yubikey")] +use { + crate::yubikey::YubiKey, + yubikey::{PinPolicy, TouchPolicy}, +}; + +#[cfg(target_os = "macos")] +use crate::macos::{ + keychain_find_code_signing_certificates, macos_keychain_find_certificate_chain, KeychainDomain, +}; + +#[cfg(target_os = "windows")] +use crate::windows::{ + windows_store_find_certificate_chain, windows_store_find_code_signing_certificates, StoreName, +}; + +pub const KEYCHAIN_DOMAINS: [&str; 4] = ["user", "system", "common", "dynamic"]; +pub const WINDOWS_STORE_NAMES: [&str; 3] = ["user", "machine", "service"]; + +const APPLE_TIMESTAMP_URL: &str = "http://timestamp.apple.com/ts01"; + +/// Holds state to pass to CLI commands. +pub struct Context { + pub config: Config, +} + +pub trait CliCommand { + /// Obtain the current command arguments normalized to a [Config] instance. + fn as_config(&self) -> Result, AppleCodesignError> { + Ok(None) + } + + /// Runs the command. + fn run(&self, context: &Context) -> Result<(), AppleCodesignError>; +} + +#[allow(unused)] +pub fn prompt_smartcard_pin() -> Result, AppleCodesignError> { + let pin = dialoguer::Password::new() + .with_prompt("Please enter device PIN") + .interact()?; + + Ok(pin.as_bytes().to_vec()) +} + +pub fn get_pkcs12_password( + password: Option, + password_file: Option>, +) -> Result { + if let Some(password) = password { + Ok(password.to_string()) + } else if let Some(path) = password_file { + Ok(std::fs::read_to_string(path.as_ref())? + .lines() + .next() + .ok_or_else(|| { + AppleCodesignError::CliGeneralError("password file appears to be empty".into()) + })? + .to_string()) + } else { + Ok(dialoguer::Password::new() + .with_prompt("Please enter password for p12 file") + .interact()?) + } +} + +#[cfg(feature = "notarize")] +#[derive(Args)] +struct NotaryApi { + /// Path to a JSON file containing the API Key + #[arg( + long = "api-key-file", + alias = "api-key-path", + group = "source", + value_name = "PATH" + )] + api_key_path: Option, + + /// App Store Connect Issuer ID (likely a UUID) + #[arg(long, requires = "api_key")] + api_issuer: Option, + + #[arg(long, requires = "api_issuer")] + /// App Store Connect API Key ID + api_key: Option, +} + +#[cfg(feature = "notarize")] +impl NotaryApi { + /// Resolve a notarizer from arguments. + fn notarizer(&self) -> Result { + if let Some(api_key_path) = &self.api_key_path { + Notarizer::from_api_key(api_key_path) + } else if let (Some(issuer), Some(key)) = (&self.api_issuer, &self.api_key) { + Notarizer::from_api_key_id(issuer, key) + } else { + Err(AppleCodesignError::NotarizeNoAuthCredentials) + } + } +} + +#[derive(Args)] +struct YubikeyPolicy { + /// Smartcard touch policy to protect key access + #[arg(long, value_parser = ["default", "always", "never", "cached"], default_value = "default")] + touch_policy: String, + + /// Smartcard pin prompt policy to protect key access + #[arg(long, value_parser = ["default", "never", "once", "always"], default_value = "default")] + pin_policy: String, +} + +#[cfg(feature = "yubikey")] +fn str_to_touch_policy(s: &str) -> Result { + match s { + "default" => Ok(TouchPolicy::Default), + "never" => Ok(TouchPolicy::Never), + "always" => Ok(TouchPolicy::Always), + "cached" => Ok(TouchPolicy::Cached), + _ => Err(AppleCodesignError::CliBadArgument), + } +} + +#[cfg(feature = "yubikey")] +fn str_to_pin_policy(s: &str) -> Result { + match s { + "default" => Ok(PinPolicy::Default), + "never" => Ok(PinPolicy::Never), + "once" => Ok(PinPolicy::Once), + "always" => Ok(PinPolicy::Always), + _ => Err(AppleCodesignError::CliBadArgument), + } +} + +fn print_certificate_info(cert: &CapturedX509Certificate) -> Result<(), AppleCodesignError> { + println!( + "Subject CN: {}", + cert.subject_common_name() + .unwrap_or_else(|| "".to_string()) + ); + println!( + "Issuer CN: {}", + cert.issuer_common_name() + .unwrap_or_else(|| "".to_string()) + ); + println!("Subject is Issuer?: {}", cert.subject_is_issuer()); + println!( + "Team ID: {}", + cert.apple_team_id() + .unwrap_or_else(|| "".to_string()) + ); + println!( + "SHA-1 fingerprint: {}", + hex::encode(cert.sha1_fingerprint()?) + ); + println!( + "SHA-256 fingerprint: {}", + hex::encode(cert.sha256_fingerprint()?) + ); + println!( + "Not Valid Before: {}", + cert.validity_not_before().to_rfc3339() + ); + println!( + "Not Valid After: {}", + cert.validity_not_after().to_rfc3339() + ); + if let Some(alg) = cert.key_algorithm() { + println!("Key Algorithm: {alg}"); + } + if let Some(alg) = cert.signature_algorithm() { + println!("Signature Algorithm: {alg}"); + } + println!( + "Public Key Data: {}", + STANDARD_ENGINE.encode( + cert.to_public_key_der() + .map_err(|e| AppleCodesignError::X509Parse(format!( + "error constructing SPKI: {e}" + )))? + ) + ); + println!( + "Signed by Apple?: {}", + cert.chains_to_apple_root_ca() + ); + if cert.chains_to_apple_root_ca() { + println!("Apple Issuing Chain:"); + for signer in cert.apple_issuing_chain() { + println!( + " - {}", + signer + .subject_common_name() + .unwrap_or_else(|| "".to_string()) + ); + } + } + + println!( + "Guessed Certificate Profile: {}", + if let Some(profile) = cert.apple_guess_profile() { + format!("{profile:?}") + } else { + "none".to_string() + } + ); + println!("Is Apple Root CA?: {}", cert.is_apple_root_ca()); + println!( + "Is Apple Intermediate CA?: {}", + cert.is_apple_intermediate_ca() + ); + + if !cert.apple_ca_extensions().is_empty() { + println!("Apple CA Extensions:"); + for ext in cert.apple_ca_extensions() { + println!(" - {} ({:?})", ext.as_oid(), ext); + } + } + + println!("Apple Extended Key Usage Purpose Extensions:"); + for purpose in cert.apple_extended_key_usage_purposes() { + println!(" - {} ({:?})", purpose.as_oid(), purpose); + } + println!("Apple Code Signing Extensions:"); + for ext in cert.apple_code_signing_extensions() { + println!(" - {} ({:?})", ext.as_oid(), ext); + } + print!( + "\n{}", + cert.to_public_key_pem(Default::default()) + .map_err(|e| AppleCodesignError::X509Parse(format!("error constructing SPKI: {e}")))? + ); + print!("\n{}", cert.encode_pem()); + + Ok(()) +} + +pub fn print_session_join(sjs_base64: &str, sjs_pem: &str) -> Result<(), RemoteSignError> { + error!(""); + error!("Run the following command to join this signing session:"); + error!(""); + error!(" rcodesign remote-sign {}", sjs_base64); + error!(""); + error!("Or if this output is too long, paste the following output:"); + error!(""); + for line in sjs_pem.lines() { + error!("{}", line); + } + error!(""); + error!("Into an interactive editor using:"); + error!(""); + error!(" rcodesign remote-sign --editor"); + error!(""); + error!("Or into a new file whose path you define with:"); + error!(""); + error!(" rcodesign remote-sign --sjs-path /path/to/file/you/just/saved"); + error!(""); + error!("(waiting for remote signer to join)"); + + Ok(()) +} + +#[derive(Parser)] +struct AnalyzeCertificate { + #[command(flatten)] + certificate: CertificateSource, +} + +impl CliCommand for AnalyzeCertificate { + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let certs = self.certificate.resolve_certificates(true)?.certs; + + for (i, cert) in certs.into_iter().enumerate() { + println!("# Certificate {i}"); + println!(); + print_certificate_info(&cert)?; + println!(); + } + + Ok(()) + } +} + +#[derive(Parser)] +struct ComputeCodeHashes { + /// Path to Mach-O binary to examine. + path: PathBuf, + + /// Hashing algorithm to use. + #[arg(long, default_value_t = DigestType::Sha256)] + hash: DigestType, + + /// Chunk size to digest over. + #[arg(long, default_value = "4096")] + page_size: usize, + + /// Index of Mach-O binary to operate on within a universal/fat binary + #[arg(long, default_value = "0")] + universal_index: usize, +} + +impl CliCommand for ComputeCodeHashes { + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let data = std::fs::read(&self.path)?; + let mach = MachFile::parse(&data)?; + let macho = mach.nth_macho(self.universal_index)?; + + let hashes = macho.code_digests(self.hash, self.page_size)?; + + for hash in hashes { + println!("{}", hex::encode(hash)); + } + + Ok(()) + } +} + +#[derive(Parser)] +struct DiffSignatures { + /// The first path to compare + path0: PathBuf, + + /// The second path to compare + path1: PathBuf, +} + +impl CliCommand for DiffSignatures { + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let reader = SignatureReader::from_path(&self.path0)?; + + let a_entities = reader.entities()?; + + let reader = SignatureReader::from_path(&self.path1)?; + let b_entities = reader.entities()?; + + let a = serde_yaml::to_string(&a_entities)?; + let b = serde_yaml::to_string(&b_entities)?; + + let Changeset { diffs, .. } = Changeset::new(&a, &b, "\n"); + + for item in diffs { + match item { + Difference::Same(ref x) => { + for line in x.lines() { + println!(" {line}"); + } + } + Difference::Add(ref x) => { + for line in x.lines() { + println!("+{line}"); + } + } + Difference::Rem(ref x) => { + for line in x.lines() { + println!("-{line}"); + } + } + } + } + + Ok(()) + } +} + +#[cfg(feature = "notarize")] +#[derive(Parser)] +struct EncodeAppStoreConnectApiKey { + /// Path to a JSON file to create the output to + #[arg(short = 'o', long)] + output_path: Option, + + /// The issuer of the API Token. Likely a UUID + issuer_id: String, + + /// The Key ID. A short alphanumeric string like DEADBEEF42 + key_id: String, + + /// Path to a file containing the private key downloaded from Apple + private_key_path: PathBuf, +} + +#[cfg(feature = "notarize")] +impl CliCommand for EncodeAppStoreConnectApiKey { + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let unified = app_store_connect::UnifiedApiKey::from_ecdsa_pem_path( + &self.issuer_id, + &self.key_id, + &self.private_key_path, + )?; + + if let Some(output_path) = &self.output_path { + eprintln!("writing unified key JSON to {}", output_path.display()); + unified.write_json_file(output_path)?; + eprintln!( + "consider auditing the file's access permissions to ensure its content remains secure" + ); + } else { + println!("{}", unified.to_json_string()?); + } + + Ok(()) + } +} + +#[derive(Parser)] +struct GenerateCertificateSigningRequest { + /// Path to file to write PEM encoded CSR to + #[arg(long = "csr-pem-file", alias = "csr-pem-path")] + csr_pem_path: Option, + + #[command(flatten)] + certificate: CertificateSource, +} + +impl CliCommand for GenerateCertificateSigningRequest { + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let signing_certs = self.certificate.resolve_certificates(true)?; + + let private_key = signing_certs.private_key()?; + + let mut builder = X509CertificateBuilder::default(); + builder + .subject() + .append_common_name_utf8_string("Apple Code Signing CSR") + .map_err(|e| AppleCodesignError::CertificateBuildError(format!("{e:?}")))?; + + warn!("generating CSR; you may be prompted to enter credentials to unlock the signing key"); + let pem = builder + .create_certificate_signing_request(private_key.as_key_info_signer())? + .encode_pem()?; + + if let Some(dest_path) = &self.csr_pem_path { + if let Some(parent) = dest_path.parent() { + std::fs::create_dir_all(parent)?; + } + + warn!("writing PEM encoded CSR to {}", dest_path.display()); + std::fs::write(dest_path, pem.as_bytes())?; + } + + print!("{pem}"); + + Ok(()) + } +} + +#[derive(Parser)] +struct GenerateSelfSignedCertificate { + /// Which key type to use + #[arg(long, value_parser = ["ecdsa", "ed25519", "rsa"], default_value = "rsa")] + algorithm: String, + + #[arg(long, value_parser = CertificateProfile::str_names(), default_value = "apple-development")] + profile: String, + + /// Team ID (this is a short string attached to your Apple Developer account) + #[arg(long, default_value = "unset")] + team_id: String, + + /// The name of the person this certificate is for + #[arg(long)] + person_name: String, + + /// Country Name (C) value for certificate identifier + #[arg(long, default_value = "XX")] + country_name: String, + + /// How many days the certificate should be valid for + #[arg(long, default_value = "365")] + validity_days: i64, + + /// Base name of files to write PEM encoded certificate to + #[arg(long)] + pem_filename: Option, + + /// Filename to write PEM encoded private key and public certificate to. + #[arg( + long = "pem-unified-file", + alias = "pem-unified-filename", + value_name = "PATH" + )] + pem_unified_path: Option, + + /// Filename to write a PKCS#12 / p12 / PFX encoded certificate to. + #[arg(long = "p12-file", alias = "pfx-file", value_name = "PATH")] + p12_path: Option, + + /// Password to use to encrypt --p12-path. + /// + /// If not provided you will be prompted for a password. + #[arg(long)] + p12_password: Option, +} + +impl CliCommand for GenerateSelfSignedCertificate { + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let algorithm = match self.algorithm.as_str() { + "ecdsa" => KeyAlgorithm::Ecdsa(EcdsaCurve::Secp256r1), + "ed25519" => KeyAlgorithm::Ed25519, + "rsa" => KeyAlgorithm::Rsa, + value => panic!("algorithm values should have been validated by arg parser: {value}"), + }; + + let profile = CertificateProfile::from_str(self.profile.as_str())?; + + let validity_duration = chrono::Duration::days(self.validity_days); + + let (cert, key_pair) = create_self_signed_code_signing_certificate( + algorithm, + profile, + &self.team_id, + &self.person_name, + &self.country_name, + validity_duration, + )?; + + let cert_pem = cert.encode_pem(); + let key_pem = pem::encode(&pem::Pem::new( + "PRIVATE KEY", + key_pair.to_pkcs8_one_asymmetric_key_der().to_vec(), + )); + + let mut wrote_file = false; + + if let Some(pem_filename) = &self.pem_filename { + let cert_path = PathBuf::from(format!("{pem_filename}.crt")); + let key_path = PathBuf::from(format!("{pem_filename}.key")); + + if let Some(parent) = cert_path.parent() { + std::fs::create_dir_all(parent)?; + } + + println!("writing public certificate to {}", cert_path.display()); + std::fs::write(&cert_path, cert_pem.as_bytes())?; + println!("writing private signing key to {}", key_path.display()); + std::fs::write(&key_path, key_pem.as_bytes())?; + + wrote_file = true; + } + + if let Some(path) = &self.pem_unified_path { + let content = format!("{}{}", key_pem, cert_pem); + + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent)?; + } + + println!("writing unified PEM to {}", path.display()); + std::fs::write(path, content.as_bytes())?; + + wrote_file = true; + } + + if let Some(path) = &self.p12_path { + let password = get_pkcs12_password(self.p12_password.clone(), None::)?; + + let pfx = p12::PFX::new( + &cert.encode_der()?, + &key_pair.to_pkcs8_one_asymmetric_key_der(), + None, + &password, + "code-signing", + ) + .ok_or_else(|| { + AppleCodesignError::CliGeneralError("failed to create PFX structure".into()) + })?; + + println!("writing PKCS#12 certificate to {}", path.display()); + + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent)?; + } + std::fs::write(path, pfx.to_der())?; + + wrote_file = true; + } + + if !wrote_file { + print!("{cert_pem}"); + print!("{key_pem}"); + } + + Ok(()) + } +} + +#[derive(Parser)] +struct KeychainExportCertificateChain { + /// Keychain domain to operate on + #[arg(long, value_parser = KEYCHAIN_DOMAINS, default_value = "user")] + domain: String, + + /// Password to unlock the Keychain + #[arg(long, group = "unlock-password")] + password: Option, + + /// File containing password to use to unlock the Keychain + #[arg(long = "password-file", group = "unlock-password")] + password_path: Option, + + /// Print only the issuing certificate chain, not the subject certificate + #[arg(long)] + no_print_self: bool, + + /// User ID value of code signing certificate to find and whose CA chain to export + #[arg(long)] + user_id: String, +} + +impl CliCommand for KeychainExportCertificateChain { + #[cfg(target_os = "macos")] + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let domain = KeychainDomain::try_from(self.domain.as_str()) + .expect("clap should have validated domain values"); + + let password = if let Some(path) = &self.password_path { + let data = std::fs::read_to_string(path)?; + + Some( + data.lines() + .next() + .expect("should get a single line") + .to_string(), + ) + } else { + self.password.as_ref().map(|password| password.to_string()) + }; + + let certs = + macos_keychain_find_certificate_chain(domain, password.as_deref(), &self.user_id)?; + + for (i, cert) in certs.iter().enumerate() { + if self.no_print_self && i == 0 { + continue; + } + + print!("{}", cert.encode_pem()); + } + + Ok(()) + } + + #[cfg(not(target_os = "macos"))] + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + Err(AppleCodesignError::CliGeneralError( + "macOS Keychain export only supported on macOS".to_string(), + )) + } +} + +#[derive(Parser)] +struct KeychainPrintCertificates { + /// Keychain domain to operate on + #[arg(long, value_parser = KEYCHAIN_DOMAINS, default_value = "user")] + domain: String, +} + +impl CliCommand for KeychainPrintCertificates { + #[cfg(target_os = "macos")] + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let domain = KeychainDomain::try_from(self.domain.as_str()) + .expect("clap should have validated domain values"); + + let certs = keychain_find_code_signing_certificates(domain, None)?; + + for (i, cert) in certs.into_iter().enumerate() { + println!("# Certificate {}", i); + println!(); + print_certificate_info(&cert)?; + println!(); + } + + Ok(()) + } + + #[cfg(not(target_os = "macos"))] + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + Err(AppleCodesignError::CliGeneralError( + "macOS Keychain integration supported on macOS".to_string(), + )) + } +} + +#[derive(Parser)] +struct MachoUniversalCreate { + /// Input Mach-O binaries to combine. + input: Vec, + + /// Output file to write. + #[arg(short = 'o', long)] + output: PathBuf, +} + +impl CliCommand for MachoUniversalCreate { + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let mut builder = crate::macho_universal::UniversalBinaryBuilder::default(); + + for path in &self.input { + eprintln!("adding {}", path.display()); + let data = std::fs::read(path)?; + builder.add_binary(data)?; + } + + eprintln!("writing {}", self.output.display()); + + if let Some(parent) = self.output.parent() { + std::fs::create_dir_all(parent)?; + } + + let mut fh = std::fs::File::create(&self.output)?; + simple_file_manifest::set_executable(&mut fh)?; + builder.write(&mut fh)?; + + Ok(()) + } +} + +#[cfg(feature = "notarize")] +#[derive(Parser)] +struct NotaryList { + #[command(flatten)] + api: NotaryApi, +} + +#[cfg(feature = "notarize")] +impl CliCommand for NotaryList { + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let notarizer = self.api.notarizer()?; + + let submissions = notarizer.list_submissions()?; + + for entry in &submissions.data { + println!( + "{} {} {} {} {}", + entry.id, + entry.attributes.created_date, + entry.attributes.name, + entry.r#type, + entry.attributes.status + ); + } + + Ok(()) + } +} + +#[cfg(feature = "notarize")] +#[derive(Parser)] +struct NotaryLog { + /// The ID of the previous submission to wait on + submission_id: String, + + #[command(flatten)] + api: NotaryApi, +} + +#[cfg(feature = "notarize")] +impl CliCommand for NotaryLog { + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let notarizer = self.api.notarizer()?; + + let log = notarizer.fetch_notarization_log(&self.submission_id)?; + + for line in serde_json::to_string_pretty(&log)?.lines() { + println!("{line}"); + } + + Ok(()) + } +} + +#[cfg(feature = "notarize")] +#[derive(Parser)] +struct NotarySubmit { + /// Whether to wait for upload processing to complete + #[arg(long)] + wait: bool, + + /// Maximum time in seconds to wait for the upload result + #[arg(long, default_value = "600")] + max_wait_seconds: u64, + + /// Staple the notarization ticket after successful upload (implies --wait) + #[arg(long)] + staple: bool, + + /// Path to asset to upload + path: PathBuf, + + #[command(flatten)] + api: NotaryApi, +} + +#[cfg(feature = "notarize")] +impl CliCommand for NotarySubmit { + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let wait = self.wait || self.staple; + + let wait_limit = if wait { + Some(std::time::Duration::from_secs(self.max_wait_seconds)) + } else { + None + }; + let notarizer = self.api.notarizer()?; + + let upload = notarizer.notarize_path(&self.path, wait_limit)?; + + if self.staple { + match upload { + crate::notarization::NotarizationUpload::UploadId(_) => { + panic!( + "NotarizationUpload::UploadId should not be returned if we waited successfully" + ); + } + crate::notarization::NotarizationUpload::NotaryResponse(_) => { + let stapler = crate::stapling::Stapler::new()?; + stapler.staple_path(&self.path)?; + } + } + } + + Ok(()) + } +} + +#[cfg(feature = "notarize")] +#[derive(Parser)] +struct NotaryWait { + /// Maximum time in seconds to wait for the upload result + #[arg(long, default_value = "600")] + max_wait_seconds: u64, + + /// The ID of the previous submission to wait on + submission_id: String, + + #[command(flatten)] + api: NotaryApi, +} + +#[cfg(feature = "notarize")] +impl CliCommand for NotaryWait { + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let wait_duration = std::time::Duration::from_secs(self.max_wait_seconds); + let notarizer = self.api.notarizer()?; + + notarizer.wait_on_notarization_and_fetch_log(&self.submission_id, wait_duration)?; + + Ok(()) + } +} + +#[derive(Parser)] +struct ParseCodeSigningRequirement { + /// Output format + #[arg(long, value_parser = ["csrl", "expression-tree"], default_value = "csrl")] + format: String, + + /// Path to file to parse + input_path: PathBuf, +} + +impl CliCommand for ParseCodeSigningRequirement { + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let data = std::fs::read(&self.input_path)?; + + let requirements = CodeRequirements::parse_blob(&data)?.0; + + for requirement in requirements.iter() { + match self.format.as_str() { + "csrl" => { + println!("{requirement}"); + } + "expression-tree" => { + println!("{requirement:#?}"); + } + format => panic!("unhandled format: {format}"), + } + } + + Ok(()) + } +} + +#[derive(Parser)] +struct PrintSignatureInfo { + /// Filesystem path to entity whose info to print + path: PathBuf, +} + +impl CliCommand for PrintSignatureInfo { + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let reader = SignatureReader::from_path(&self.path)?; + + let entities = reader.entities()?; + serde_yaml::to_writer(std::io::stdout(), &entities)?; + + Ok(()) + } +} + +#[derive(Args)] +#[group(required = true, multiple = false)] +struct SessionJoinString { + /// Open an editor to input the session join string + #[arg(long = "editor")] + session_join_string_editor: bool, + + /// Path to file containing session join string + #[arg(long = "sjs-file", alias = "sjs-path")] + session_join_string_path: Option, + + /// Session join string (provided by the signing initiator) + session_join_string: Option, +} + +#[derive(Parser)] +struct RemoteSign { + #[command(flatten)] + session_join_string: SessionJoinString, + + #[command(flatten)] + certificate: CertificateSource, +} + +impl CliCommand for RemoteSign { + fn as_config(&self) -> Result, AppleCodesignError> { + Ok(Some(Config { + remote_sign: config::RemoteSignConfig { + signer: self.certificate.clone(), + }, + ..Default::default() + })) + } + + fn run(&self, context: &Context) -> Result<(), AppleCodesignError> { + let c = &context.config.remote_sign; + + let session_join_string = if self.session_join_string.session_join_string_editor { + let mut value = None; + + for _ in 0..3 { + if let Some(content) = dialoguer::Editor::new() + .require_save(true) + .edit("# Please enter the -----BEGIN SESSION JOIN STRING---- content below.\n# Remember to save the file!")? + { + value = Some(content); + break; + } + } + + value.ok_or_else(|| { + AppleCodesignError::CliGeneralError( + "session join string not entered in editor".into(), + ) + })? + } else if let Some(path) = &self.session_join_string.session_join_string_path { + std::fs::read_to_string(path)? + } else if let Some(value) = &self.session_join_string.session_join_string { + value.to_string() + } else { + return Err(AppleCodesignError::CliGeneralError( + "session join string argument parsing failure".into(), + )); + }; + + let mut joiner = create_session_joiner(session_join_string)?; + + let url = if let Some(key) = &c.signer.remote_signing_key { + if let Some(env) = &key.shared_secret_env { + let secret = std::env::var(env).map_err(|_| AppleCodesignError::CliBadArgument)?; + joiner + .register_state(SessionJoinState::SharedSecret(secret.as_bytes().to_vec()))?; + } else if let Some(secret) = &key.shared_secret { + joiner + .register_state(SessionJoinState::SharedSecret(secret.as_bytes().to_vec()))?; + } + + key.url() + } else { + crate::remote_signing::DEFAULT_SERVER_URL.to_string() + }; + + let signing_certs = c.signer.resolve_certificates(true)?; + + let private = signing_certs.private_key()?; + + let mut public_certificates = signing_certs.certs.clone(); + let cert = public_certificates.remove(0); + + let certificates = if let Some(chain) = cert.apple_root_certificate_chain() { + // The chain starts with self. + chain.into_iter().skip(1).collect::>() + } else { + public_certificates + }; + + joiner.register_state(SessionJoinState::PublicKeyDecrypt( + private.to_public_key_peer_decrypt()?, + ))?; + + let client = UnjoinedSigningClient::new_signer( + joiner, + private.as_key_info_signer(), + cert, + certificates, + url, + )?; + client.run()?; + + Ok(()) + } +} + +/// Signing arguments that can be scoped. +#[derive(Args, Clone, Debug, Eq, PartialEq)] +pub struct ScopedSigningArgs { + /// Identifier string for binary. The value normally used by CFBundleIdentifier + #[arg(long = "binary-identifier", value_name = "IDENTIFIER")] + binary_identifiers: Vec, + + /// Path to a file containing binary code requirements data to be used as designated requirements + #[arg( + long = "code-requirements-file", + alias = "code-requirements-path", + value_name = "PATH" + )] + code_requirements_paths: Vec, + + /// Path to an XML plist file containing code resources + #[arg( + long = "code-resources-file", + alias = "code-resources", + value_name = "PATH" + )] + code_resources_paths: Vec, + + /// Code signature flags to set. + /// + /// Valid values: host, hard, kill, expires, library, runtime, linker-signed + #[arg(long)] + code_signature_flags: Vec, + + /// Digest algorithms to use. + /// + /// This typically doesn't need to be set since the OS targeting information + /// from signed binaries implicitly derives appropriate digests to sign with. + /// + /// However, there are special cases where you may want to force use of + /// specific digests. + /// + /// The first provided value will become the "primary" digest. Subsequent + /// values will become alternative digests. The "primary" digest should be + /// "older" to ensure compatibility with older clients. + /// + /// When targeting older Apple OS versions, SHA-1 should be the primary digest + /// and SHA-256 should also be present for compatibility with newer OS versions. + /// + /// When targeting new OS versions, it is sufficient to only provide SHA-256 + /// digests. + /// + /// The following values are accepted: none, sha1, sha256, sha384, sha512. + /// + /// Important: only "sha1" and "sha256" are widely used and use of other + /// algorithms may cause problems. + #[arg(long = "digest", value_name = "DIGEST")] + digests: Vec, + + /// Path to a plist file containing entitlements + #[arg( + short = 'e', + long = "entitlements-xml-file", + alias = "entitlements-xml-path", + value_name = "PATH" + )] + entitlements_xml_paths: Vec, + + /// Launch constraints on the current executable. + /// + /// Specify the path to a plist XML file defining launch constraints. + #[arg(long = "launch-constraints-self-file", value_name = "PATH")] + launch_constraints_self_paths: Vec, + + /// Launch constraints on the parent process. + /// + /// Specify the path to a plist XML file defining launch constraints. + #[arg(long = "launch-constraints-parent-file", value_name = "PATH")] + launch_constraints_parent_paths: Vec, + + /// Launch constraints on the responsible process. + /// + /// Specify the path to a plist XML file defining launch constraints. + #[arg(long = "launch-constraints-responsible-file", value_name = "PATH")] + launch_constraints_responsible_paths: Vec, + + /// Constraints on loaded libraries. + /// + /// Specify the path to a plist XML file defining launch constraints. + #[arg(long = "library-constraints-file", value_name = "PATH")] + library_constraints_paths: Vec, + + /// Hardened runtime version to use (defaults to SDK version used to build binary) + #[arg(long = "runtime-version", value_name = "VERSION")] + runtime_versions: Vec, + + /// Path to an Info.plist file whose digest to include in Mach-O signature + #[arg( + long = "info-plist-file", + alias = "info-plist-path", + value_name = "PATH" + )] + info_plist_paths: Vec, +} + +/// Represents the set of scopable signing settings for a given scope. +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ScopedSigningSettingsValues { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub binary_identifier: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub code_requirements_file: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub code_resources_file: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub code_signature_flags: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub digests: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub entitlements_xml_file: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub launch_constraints_self_file: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub launch_constraints_parent_file: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub launch_constraints_responsible_file: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub library_constraints_file: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub runtime_version: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub info_plist_file: Option, +} + +pub fn split_scoped_value(s: &str) -> (String, &str) { + let parts = s.splitn(2, ':').collect::>(); + + match parts.len() { + 1 => ("@main".into(), s), + 2 => (parts[0].to_string(), parts[1]), + _ => { + panic!("error splitting scoped value; this should not occur"); + } + } +} + +/// A mapping of scopes to collections of signing settings. +/// +/// This abstraction exists to make it easier to load config files. +pub struct ScopedSigningSettings(pub BTreeMap); + +impl TryFrom<&ScopedSigningArgs> for ScopedSigningSettings { + type Error = AppleCodesignError; + + fn try_from(args: &ScopedSigningArgs) -> Result { + let mut res = BTreeMap::::default(); + + for value in &args.binary_identifiers { + let (scope, value) = split_scoped_value(value); + res.entry(scope).or_default().binary_identifier = Some(value.into()); + } + + for value in &args.code_requirements_paths { + let (scope, value) = split_scoped_value(value); + res.entry(scope).or_default().code_requirements_file = Some(value.into()); + } + + for value in &args.code_resources_paths { + let (scope, value) = split_scoped_value(value); + res.entry(scope).or_default().code_resources_file = Some(value.into()); + } + + for value in &args.code_signature_flags { + let (scope, value) = split_scoped_value(value); + res.entry(scope) + .or_default() + .code_signature_flags + .push(value.into()); + } + + for value in &args.digests { + let (scope, value) = split_scoped_value(value); + res.entry(scope).or_default().digests.push(value.into()); + } + + for value in &args.entitlements_xml_paths { + let (scope, value) = split_scoped_value(value); + res.entry(scope).or_default().entitlements_xml_file = Some(value.into()); + } + + for value in &args.launch_constraints_self_paths { + let (scope, value) = split_scoped_value(value); + res.entry(scope).or_default().launch_constraints_self_file = Some(value.into()); + } + + for value in &args.launch_constraints_parent_paths { + let (scope, value) = split_scoped_value(value); + res.entry(scope).or_default().launch_constraints_parent_file = Some(value.into()); + } + + for value in &args.launch_constraints_responsible_paths { + let (scope, value) = split_scoped_value(value); + res.entry(scope) + .or_default() + .launch_constraints_responsible_file = Some(value.into()); + } + + for value in &args.library_constraints_paths { + let (scope, value) = split_scoped_value(value); + res.entry(scope).or_default().library_constraints_file = Some(value.into()); + } + + for value in &args.runtime_versions { + let (scope, value) = split_scoped_value(value); + res.entry(scope).or_default().runtime_version = Some(value.into()); + } + + for value in &args.info_plist_paths { + let (scope, value) = split_scoped_value(value); + res.entry(scope).or_default().info_plist_file = Some(value.into()); + } + + Ok(Self(res)) + } +} + +impl ScopedSigningSettings { + pub fn load_into_settings( + self, + settings: &mut SigningSettings, + ) -> Result<(), AppleCodesignError> { + for (scope, values) in self.0 { + let scope = SettingsScope::try_from(scope.as_str())?; + + if let Some(v) = values.binary_identifier { + settings.set_binary_identifier(scope.clone(), v); + } + + if let Some(v) = values.code_requirements_file { + let code_requirements_data = std::fs::read(v)?; + let reqs = CodeRequirements::parse_blob(&code_requirements_data)?.0; + for expr in reqs.iter() { + warn!( + "setting designated code requirements for {}: {}", + scope, expr + ); + + settings.set_designated_requirement_expression(scope.clone(), expr)?; + } + } + + if let Some(path) = values.code_resources_file { + warn!( + "setting code resources data for {} from path {}", + scope, + path.display() + ); + let code_resources_data = std::fs::read(path)?; + settings.set_code_resources_data(scope.clone(), code_resources_data); + } + + // If code signature flags are specified, they overwrite defaults. So reset + // current values on the scope before setting anything. + if !values.code_signature_flags.is_empty() { + if let Some(existing) = settings.code_signature_flags(&scope) { + if existing != CodeSignatureFlags::empty() { + warn!( + "removing code signature flags {:?} from {}", + existing, scope + ); + } + } + + settings.set_code_signature_flags(scope.clone(), CodeSignatureFlags::empty()); + } + + for value in values.code_signature_flags { + let flags = CodeSignatureFlags::from_str(&value)?; + warn!("adding code signature flag {:?} to {}", flags, scope); + settings.add_code_signature_flags(scope.clone(), flags); + } + + for (i, value) in values.digests.into_iter().enumerate() { + let digest_type = DigestType::try_from(value.as_str())?; + + if i == 0 { + settings.set_digest_type(scope.clone(), digest_type); + } else { + settings.add_extra_digest(scope.clone(), digest_type); + } + } + + if let Some(path) = values.entitlements_xml_file { + warn!( + "setting entitlements XML for {} from path {}", + scope, + path.display() + ); + let entitlements_data = std::fs::read_to_string(path)?; + settings.set_entitlements_xml(scope.clone(), entitlements_data)?; + } + + if let Some(path) = values.launch_constraints_self_file { + warn!( + "setting self launch constraints for {} from path {}", + scope, + path.display() + ); + settings.set_launch_constraints_self( + scope.clone(), + EncodedEnvironmentConstraints::from_requirements_plist_file(path)?, + ); + } + + if let Some(path) = values.launch_constraints_parent_file { + warn!( + "setting parent process launch constraints for {} from path {}", + scope, + path.display() + ); + settings.set_launch_constraints_parent( + scope.clone(), + EncodedEnvironmentConstraints::from_requirements_plist_file(path)?, + ); + } + + if let Some(path) = values.launch_constraints_responsible_file { + warn!( + "setting responsible process launch constraints for {} from path {}", + scope, + path.display() + ); + settings.set_launch_constraints_responsible( + scope.clone(), + EncodedEnvironmentConstraints::from_requirements_plist_file(path)?, + ); + } + + if let Some(path) = values.library_constraints_file { + warn!( + "setting loaded library constraints for {} from path {}", + scope, + path.display() + ); + settings.set_library_constraints( + scope.clone(), + EncodedEnvironmentConstraints::from_requirements_plist_file(path)?, + ); + } + + if let Some(value) = values.runtime_version { + let version = semver::Version::parse(&value)?; + settings.set_runtime_version(scope.clone(), version); + } + + if let Some(path) = values.info_plist_file { + let data = std::fs::read(path)?; + settings.set_info_plist_data(scope, data); + } + } + + Ok(()) + } +} + +#[derive(Parser)] +struct Sign { + #[command(flatten)] + scoped: ScopedSigningArgs, + + /// Team name/identifier to include in code signature + #[arg(long, value_name = "NAME")] + team_name: Option, + + /// An RFC 3339 date and time string to be used in signatures. + /// + /// e.g. 2023-11-05T10:42:00Z. + /// + /// If not specified, the current time will be used. + /// + /// Setting is only used when signing with a signing certificate. + /// + /// This setting is typically not necessary. It was added to facilitate + /// deterministic signing behavior. + #[arg(long)] + signing_time: Option, + + /// URL of time-stamp server to use to obtain a token of the CMS signature + /// + /// Can be set to the special value `none` to disable the generation of time-stamp + /// tokens and use of a time-stamp server. + #[arg(long, default_value = APPLE_TIMESTAMP_URL)] + timestamp_url: String, + + /// Glob expression of paths to exclude from signing + #[arg(long)] + exclude: Vec, + + /// Do not traverse into nested entities when signing. + /// + /// Some signable entities (like directory bundles) have child/nested entities + /// that can be signed. By default, signing traversed into these entities and + /// signs all entities recursively. + /// + /// Activating shallow signing mode using this flag overrides the default behavior. + /// + /// The behavior of this flag is subject to change. As currently implemented it + /// will: + /// + /// * Prevent signing nested bundles when signing a bundle. e.g. if an app + /// bundle contains a framework, only the app bundle will be signed. Additional + /// Mach-O binaries within a bundle may still be signed with this flag set. + /// + /// Activating shallow signing mode can result in signing failures if the skipped + /// nested entities aren't signed. For example, when signing an application bundle + /// containing an unsigned nested bundle/framework, signing will fail with an + /// error about a missing code signature. Always be sure to sign nested entities + /// before their parents when this mode is activated. + #[arg(long)] + shallow: bool, + + /// Indicate that the entity being signed will later be notarized. + /// + /// Notarized software is subject to specific requirements, such as enabling the + /// hardened runtime. + /// + /// The presence of this flag influences signing settings and engages additional + /// checks to help ensure that signed software can be successfully notarized. + /// + /// This flag is best effort. Notarization failures of software signed with + /// this flag may be indicative of bugs in this software. + /// + /// The behavior of this flag is subject to change. As currently implemented, + /// it will: + /// + /// * Require the use of a "Developer ID" signing certificate issued by Apple. + /// * Require the use of a time-stamp server. + /// * Enable the hardened runtime code signature flag on all Mach-O binaries + /// (equivalent to `--code-signature-flags runtime` for all signed paths). + #[arg(long)] + for_notarization: bool, + + /// Path to Mach-O binary to sign + input_path: PathBuf, + + /// Path to signed Mach-O binary to write + output_path: Option, + + #[command(flatten)] + certificate: CertificateSource, +} + +impl CliCommand for Sign { + fn as_config(&self) -> Result, AppleCodesignError> { + let paths = ScopedSigningSettings::try_from(&self.scoped)?; + + Ok(Some(Config { + sign: config::SignConfig { + signer: self.certificate.clone(), + paths: paths.0, + }, + ..Default::default() + })) + } + + fn run(&self, context: &Context) -> Result<(), AppleCodesignError> { + let c = &context.config.sign; + + let mut settings = SigningSettings::default(); + + let certs = c.signer.resolve_certificates(true)?; + certs.load_into_signing_settings(&mut settings)?; + + // Doesn't make sense to set a time-stamp server URL unless we're generating + // CMS signatures. + if settings.signing_key().is_some() && self.timestamp_url != "none" { + warn!("using time-stamp protocol server {}", self.timestamp_url); + settings.set_time_stamp_url(&self.timestamp_url)?; + } + + if let Some(time) = &self.signing_time { + let time = chrono::DateTime::parse_from_rfc3339(time).map_err(|e| { + AppleCodesignError::CliGeneralError(format!("invalid signing time format: {}", e)) + })?; + let time = time.with_timezone(&chrono::Utc); + settings.set_signing_time(time); + } + + if let Some(team_id) = settings.set_team_id_from_signing_certificate() { + warn!( + "automatically setting team ID from signing certificate: {}", + team_id + ); + } + + if let Some(team_name) = &self.team_name { + settings.set_team_id(team_name); + } + + settings.set_shallow(self.shallow); + settings.set_for_notarization(self.for_notarization); + + for pattern in &self.exclude { + settings.add_path_exclusion(pattern)?; + } + + ScopedSigningSettings(c.paths.clone()).load_into_settings(&mut settings)?; + + settings.ensure_for_notarization_settings()?; + + // Settings are locked in. Proceed to sign. + + let signer = UnifiedSigner::new(settings); + + if let Some(output_path) = &self.output_path { + warn!( + "signing {} to {}", + self.input_path.display(), + output_path.display() + ); + + signer.sign_path(&self.input_path, output_path)?; + } else { + warn!("signing {} in place", self.input_path.display()); + signer.sign_path_in_place(&self.input_path)?; + } + + if let Some(private) = certs.private_key_optional()? { + private.finish()?; + } + + Ok(()) + } +} + +#[derive(Parser)] +struct SmartcardScan {} + +impl CliCommand for SmartcardScan { + #[cfg(feature = "yubikey")] + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let mut ctx = ::yubikey::reader::Context::open()?; + for (index, reader) in ctx.iter()?.enumerate() { + println!("Device {}: {}", index, reader.name()); + + if let Ok(yk) = reader.open() { + let mut yk = crate::yubikey::YubiKey::from(yk); + println!("Device {}: Serial: {}", index, yk.inner()?.serial()); + println!("Device {}: Version: {}", index, yk.inner()?.version()); + + for (slot, cert) in yk.find_certificates()? { + println!( + "Device {}: Certificate in slot {:?} / {}", + index, + slot, + hex::encode([u8::from(slot)]) + ); + print_certificate_info(&cert)?; + println!(); + } + } + } + + Ok(()) + } + + #[cfg(not(feature = "yubikey"))] + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + eprintln!("smartcard reading requires the `yubikey` crate feature, which isn't enabled."); + eprintln!("recompile the crate with `cargo build --features yubikey` to enable support"); + std::process::exit(1); + } +} + +#[derive(Parser)] +struct SmartcardGenerateKey { + /// Smartcard slot number to store key in (9c is common) + #[arg(long)] + smartcard_slot: String, + + #[command(flatten)] + policy: YubikeyPolicy, +} + +impl CliCommand for SmartcardGenerateKey { + #[cfg(feature = "yubikey")] + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let slot_id = ::yubikey::piv::SlotId::from_str(&self.smartcard_slot)?; + + let touch_policy = str_to_touch_policy(self.policy.touch_policy.as_str())?; + let pin_policy = str_to_pin_policy(self.policy.pin_policy.as_str())?; + + let mut yk = YubiKey::new()?; + yk.set_pin_callback(prompt_smartcard_pin); + + yk.generate_key(slot_id, touch_policy, pin_policy)?; + + Ok(()) + } + + #[cfg(not(feature = "yubikey"))] + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + eprintln!( + "smartcard integration requires the `yubikey` crate feature, which isn't enabled." + ); + eprintln!("recompile the crate with `cargo build --features yubikey` to enable support"); + std::process::exit(1); + } +} + +#[derive(Parser)] +struct SmartcardImport { + /// Re-use the existing private key in the smartcard slot + #[arg(long)] + existing_key: bool, + + /// Don't actually perform the import + #[arg(long)] + dry_run: bool, + + #[command(flatten)] + certificate: CertificateSource, + + #[command(flatten)] + policy: YubikeyPolicy, +} + +impl CliCommand for SmartcardImport { + #[cfg(feature = "yubikey")] + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let signing_certs = self.certificate.resolve_certificates(false)?; + + let slot_id = ::yubikey::piv::SlotId::from_str( + self.certificate + .smartcard_key + .as_ref() + .unwrap() + .slot + .as_ref() + .ok_or_else(|| { + error!("--smartcard-slot is required"); + AppleCodesignError::CliBadArgument + })?, + )?; + let touch_policy = str_to_touch_policy(self.policy.touch_policy.as_str())?; + let pin_policy = str_to_pin_policy(self.policy.pin_policy.as_str())?; + + println!( + "found {} private keys and {} public certificates", + signing_certs.keys.len(), + signing_certs.certs.len() + ); + + let key = if self.existing_key { + println!("using existing private key in smartcard"); + + if !signing_certs.keys.is_empty() { + println!( + "ignoring {} private keys specified via arguments", + signing_certs.keys.len() + ); + } + + None + } else { + Some(signing_certs.private_key()?) + }; + + let cert = signing_certs + .certs + .clone() + .into_iter() + .next() + .ok_or_else(|| { + println!("no public certificates found"); + AppleCodesignError::CliBadArgument + })?; + + println!( + "Will import the following certificate into slot {}", + hex::encode([u8::from(slot_id)]) + ); + print_certificate_info(&cert)?; + + let mut yk = YubiKey::new()?; + yk.set_pin_callback(prompt_smartcard_pin); + + if self.dry_run { + println!("dry run mode enabled; stopping"); + return Ok(()); + } + + if let Some(key) = key { + yk.import_key( + slot_id, + key.as_key_info_signer(), + &cert, + touch_policy, + pin_policy, + )?; + } else { + yk.import_certificate(slot_id, &cert)?; + } + + Ok(()) + } + + #[cfg(not(feature = "yubikey"))] + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + eprintln!("smartcard import requires `yubikey` crate feature, which isn't enabled."); + eprintln!("recompile the crate with `cargo build --features yubikey` to enable support"); + std::process::exit(1); + } +} + +#[derive(Parser)] +struct Staple { + /// Path to entity to attempt to staple + path: PathBuf, +} + +impl CliCommand for Staple { + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let stapler = crate::stapling::Stapler::new()?; + stapler.staple_path(&self.path)?; + + Ok(()) + } +} + +#[derive(Parser)] +struct Verify { + /// Path of Mach-O binary to examine + path: PathBuf, +} + +impl CliCommand for Verify { + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let path_type = crate::PathType::from_path(&self.path)?; + + if path_type != crate::PathType::MachO { + return Err(AppleCodesignError::CliGeneralError(format!( + "verify command only works on Mach-O binaries; provided path is a {:?}", + path_type + ))); + } + + warn!("(the verify command is known to be buggy and gives misleading results; we highly recommend using Apple's tooling until this message is removed)"); + let data = std::fs::read(&self.path)?; + + let problems = crate::verify::verify_macho_data(data); + + for problem in &problems { + println!("{problem}"); + } + + if problems.is_empty() { + eprintln!("no problems detected!"); + eprintln!("(we do not verify everything so please do not assume that the signature meets Apple standards)"); + Ok(()) + } else { + Err(AppleCodesignError::VerificationProblems) + } + } +} + +#[derive(Parser)] +struct WindowsStoreExportCertificateChain { + /// Windows Store to operate on + #[arg(long, value_parser = WINDOWS_STORE_NAMES, default_value = "user", value_name = "STORE")] + windows_store_name: String, + + /// Print only the issuing certificate chain, not the subject certificate + #[arg(long)] + no_print_self: bool, + + /// SHA-1 thumbprint of code signing certificate to find and whose CA chain to export + #[arg(long)] + thumbprint: String, +} + +impl CliCommand for WindowsStoreExportCertificateChain { + #[cfg(target_os = "windows")] + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let store_name = StoreName::try_from(self.windows_store_name.as_str()) + .expect("clap should have validated store name values"); + + let certs = windows_store_find_certificate_chain(store_name, &self.thumbprint)?; + + for (i, cert) in certs.iter().enumerate() { + if self.no_print_self && i == 0 { + continue; + } + + print!("{}", cert.encode_pem()); + } + + Ok(()) + } + + #[cfg(not(target_os = "windows"))] + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + Err(AppleCodesignError::CliGeneralError( + "Windows Store export only supported on Windows".to_string(), + )) + } +} + +#[derive(Parser)] +struct WindowsStorePrintCertificates { + /// Windows Store name to operate on + #[arg(long, value_parser = WINDOWS_STORE_NAMES, default_value = "user", value_name = "STORE")] + windows_store_name: String, +} + +impl CliCommand for WindowsStorePrintCertificates { + #[cfg(target_os = "windows")] + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + let store_name = StoreName::try_from(self.windows_store_name.as_str()) + .expect("clap should have validated store name values"); + + let certs = windows_store_find_code_signing_certificates(store_name)?; + + for (i, cert) in certs.into_iter().enumerate() { + println!("# Certificate {}", i); + println!(); + print_certificate_info(&cert)?; + println!(); + } + + Ok(()) + } + + #[cfg(not(target_os = "windows"))] + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + Err(AppleCodesignError::CliGeneralError( + "Windows Store integration only supported on Windows".to_string(), + )) + } +} + +#[derive(Parser)] +struct X509Oids {} + +impl CliCommand for X509Oids { + fn run(&self, _context: &Context) -> Result<(), AppleCodesignError> { + println!("# Extended Key Usage (EKU) Extension OIDs"); + println!(); + for ekup in crate::certificate::ExtendedKeyUsagePurpose::all() { + println!("{}\t{:?}", ekup.as_oid(), ekup); + } + println!(); + println!("# Code Signing Certificate Extension OIDs"); + println!(); + for ext in crate::certificate::CodeSigningCertificateExtension::all() { + println!("{}\t{:?}", ext.as_oid(), ext); + } + println!(); + println!("# Certificate Authority Certificate Extension OIDs"); + println!(); + for ext in crate::certificate::CertificateAuthorityExtension::all() { + println!("{}\t{:?}", ext.as_oid(), ext); + } + + Ok(()) + } +} + +#[derive(Subcommand)] +#[allow(clippy::large_enum_variant)] +enum Subcommands { + /// Analyze an X.509 certificate for Apple code signing properties. + /// + /// Given the path to a PEM encoded X.509 certificate, this command will read + /// the certificate and print information about it relevant to Apple code + /// signing. + /// + /// The output of the command can be useful to learn about X.509 certificate + /// extensions used by code signing certificates and to debug low-level + /// properties related to certificates. + AnalyzeCertificate(AnalyzeCertificate), + + /// Compute code hashes for a binary + ComputeCodeHashes(ComputeCodeHashes), + + /// Create a binary code requirements file. + #[command(hide = true)] + DebugCreateCodeRequirements(debug_commands::DebugCreateCodeRequirements), + + /// Create a (launch or library) constraints file. + #[command(hide = true)] + DebugCreateConstraints(debug_commands::DebugCreateConstraints), + + /// Create an entitlements file. + #[command(hide = true)] + DebugCreateEntitlements(debug_commands::DebugCreateEntitlements), + + /// Create an Info.plist file. + #[command(hide = true)] + DebugCreateInfoPlist(debug_commands::DebugCreateInfoPlist), + + /// Create a Mach-O binary from parameters. + #[command(hide = true)] + DebugCreateMacho(debug_commands::DebugCreateMachO), + + /// Print a filesystem tree with basic metadata. + #[command(hide = true)] + DebugFileTree(debug_commands::DebugFileTree), + + /// Print a diff between the signature content of two paths + DiffSignatures(DiffSignatures), + + /// Encode App Store Connect API Key metadata to JSON + /// + /// App Store Connect API Keys + /// (https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api) + /// are defined by 3 components: + /// + /// * The Issuer ID (likely a UUID) + /// * A Key ID (an alphanumeric value like `DEADBEEF42`) + /// * A PEM encoded ECDSA private key (typically a file beginning with + /// `-----BEGIN PRIVATE KEY-----`). + /// + /// This command is used to encode all API Key components into a single JSON + /// object so you only have to refer to a single entity when performing + /// operations (like notarization) using these API Keys. + /// + /// The API Key components are specified as positional arguments. + /// + /// By default, the JSON encoded unified representation is printed to stdout. + /// You can write to a file instead by passing `--output-path `. + /// + /// # Security Considerations + /// + /// The App Store Connect API Key contains a private key and its value should be + /// treated as sensitive: if an unwanted party obtains your private key, they + /// effectively have access to your App Store Connect account. + /// + /// When this command writes JSON files, an attempt is made to limit access + /// to the file. However, file access restrictions may not be as secure as you + /// want. Security conscious individuals should audit the permissions of the + /// file and adjust accordingly. + #[cfg(feature = "notarize")] + #[command(verbatim_doc_comment)] + EncodeAppStoreConnectApiKey(EncodeAppStoreConnectApiKey), + + /// Print/extract various information from a Mach-O binary. + /// + /// Given the path to a Mach-O binary (including fat/universal binaries), this + /// command will attempt to locate and format the requested data. + #[command(override_usage = "rcodesign extract [OPTIONS] ")] + Extract(extract_commands::Extract), + + /// Generates a certificate signing request that can be sent to Apple and exchanged for a signing certificate + GenerateCertificateSigningRequest(GenerateCertificateSigningRequest), + + /// Generate a self-signed certificate for code signing + /// + /// This command will generate a new key pair using the algorithm of choice + /// then create an X.509 certificate wrapper for it that is signed with the + /// just-generated private key. The created X.509 certificate has extensions + /// that mark it as appropriate for code signing. + /// + /// Certificates generated with this command can be useful for local testing. + /// However, because it is a self-signed certificate and isn't signed by a + /// trusted certificate authority, Apple operating systems may refuse to + /// load binaries signed with it. + /// + /// By default the command prints 2 PEM encoded blocks. One block is for the + /// X.509 public certificate. The other is for the PKCS#8 private key (which + /// can include the public key). + /// + /// The `--pem-filename` argument can be specified to write the generated + /// certificate pair to a pair of files. The destination files will have + /// `.crt` and `.key` appended to the value provided. + /// + /// When the certificate is written to a file, it isn't printed to stdout. + GenerateSelfSignedCertificate(GenerateSelfSignedCertificate), + + /// Export Apple CA certificates from the macOS Keychain + KeychainExportCertificateChain(KeychainExportCertificateChain), + + /// Print information about certificates in the macOS keychain + KeychainPrintCertificates(KeychainPrintCertificates), + + /// Create a universal ("fat") Mach-O binary. + /// + /// This is similar to the `lipo -create` command. Use it to stitch + /// multiple single architecture Mach-O binaries into a single multi-arch + /// binary. + MachoUniversalCreate(MachoUniversalCreate), + + #[cfg(feature = "notarize")] + /// List notarization submissions + NotaryList(NotaryList), + + #[cfg(feature = "notarize")] + /// Fetch the notarization log for a previous submission + NotaryLog(NotaryLog), + + /// Upload an asset to Apple for notarization and possibly staple it + /// + /// This command is used to submit an asset to Apple for notarization. Given + /// a path to an asset with a code signature, this command will connect to Apple's + /// Notary API and upload the asset. It will then optionally wait on the submission + /// to finish processing (which typically takes a few dozen seconds). If the + /// asset validates Apple's requirements, Apple will issue a *notarization ticket* + /// as proof that they approved of it. This ticket is then added to the asset in a + /// process called *stapling*, which this command can do automatically if the + /// `--staple` argument is passed. + /// + /// # App Store Connect API Key + /// + /// In order to communicate with Apple's servers, you need an App Store Connect + /// API Key. This requires an Apple Developer account. You can generate an + /// API Key at https://appstoreconnect.apple.com/access/api. + /// + /// The recommended mechanism to define the API Key is via `--api-key-path`, + /// which takes the path to a file containing JSON produced by the + /// `encode-app-store-connect-api-key` command. See that command's help for + /// more details. + /// + /// If you don't wish to use `--api-key-path`, you can define the key components + /// via the `--api-issuer` and `--api-key` arguments. You will need a file named + /// `AuthKey_.p8` in one of the following locations: `$(pwd)/private_keys/`, + /// `~/private_keys/`, '~/.private_keys/`, and `~/.appstoreconnect/private_keys/` + /// (searched in that order). The name of the file is derived from the value of + /// `--api-key`. + /// + /// In all cases, App Store Connect API Keys can be managed at + /// https://appstoreconnect.apple.com/access/api. + /// + /// # Modes of Operation + /// + /// By default, the `notarize` command will initiate an upload to Apple and exit + /// once the upload is complete. + /// + /// Once an upload is performed, Apple will asynchronously process the uploaded + /// content. This can take seconds to minutes. + /// + /// To poll Apple's servers and wait on the server-side processing to finish, + /// specify `--wait`. This will query the state of the processing every few seconds + /// until it is finished, the max wait time is reached, or an error occurs. + /// + /// To automatically staple an asset after server-side processing has finished, + /// specify `--staple`. This implies `--wait`. + #[cfg(feature = "notarize")] + #[command(alias = "notarize")] + NotarySubmit(NotarySubmit), + + /// Wait for completion of a previous submission + #[cfg(feature = "notarize")] + NotaryWait(NotaryWait), + + /// Parse binary Code Signing Requirement data into a human readable string + /// + /// This command can be used to parse binary code signing requirement data and + /// print it in various formats. + /// + /// The source input format is the binary code requirement serialization. This + /// is the format generated by Apple's `csreq` tool via `csreq -b`. The binary + /// data begins with header magic `0xfade0c00`. + /// + /// The default output format is the Code Signing Requirement Language. But the + /// output format can be changed via the --format argument. + /// + /// Our Code Signing Requirement Language output may differ from Apple's. For + /// example, `and` and `or` expressions always have their sub-expressions surrounded + /// by parentheses (e.g. `(a) and (b)` instead of `a and b`) and strings are always + /// quoted. The differences, however, should not matter to the parser or result + /// in a different binary serialization. + ParseCodeSigningRequirement(ParseCodeSigningRequirement), + + /// Print signature information for a filesystem path + PrintSignatureInfo(PrintSignatureInfo), + + /// Create signatures initiated from a remote signing operation + RemoteSign(RemoteSign), + + /// Adds code signatures to a signable entity. + /// + /// This command can sign the following entities: + /// + /// * A single Mach-O binary (specified by its file path) + /// * A bundle (specified by its directory path) + /// * A DMG disk image (specified by its path) + /// * A XAR archive (commonly a .pkg installer file) + /// + /// If the input is Mach-O binary, it can be a single or multiple/fat/universal + /// Mach-O binary. If a fat binary is given, each Mach-O within that binary will + /// be signed. + /// + /// If the input is a bundle, the bundle will be recursively signed. If the + /// bundle contains nested bundles or Mach-O binaries, those will be signed + /// automatically. + /// + /// # Settings Scope + /// + /// The following signing settings are global and apply to all signed entities: + /// + /// * --pem-source + /// * --team-name + /// * --timestamp-url + /// + /// The following signing settings can be scoped so they only apply to certain + /// entities: + /// + /// * --digest + /// * --binary-identifier + /// * --code-requirements-files + /// * --code-resources-file + /// * --code-signature-flags + /// * --entitlements-xml-file + /// * --info-plist-file + /// + /// Scoped settings take the form or :. If the 2nd form + /// is used, the string before the first colon is parsed as a \"scoping string\". + /// It can have the following values: + /// + /// * `main` - Applies to the main entity being signed and all nested entities. + /// * `@` - e.g. `@0`. Applies to a Mach-O within a fat binary at the + /// specified index. 0 means the first Mach-O in a fat binary. + /// * `@[cpu_type=` - e.g. `@[cpu_type=7]`. Applies to a Mach-O within a fat + /// binary targeting a numbered CPU architecture (using numeric constants + /// as defined by Mach-O). + /// * `@[cpu_type=` - e.g. `@[cpu_type=x86_64]`. Applies to a Mach-O within + /// a fat binary targeting a CPU architecture identified by a string. See below + /// for the list of recognized values. + /// * `` - e.g. `path/to/file`. Applies to content at a given path. This + /// should be the bundle-relative path to a Mach-O binary, a nested bundle, or + /// a Mach-O binary within a nested bundle. If a nested bundle is referenced, + /// settings apply to everything within that bundle. + /// * `@` - e.g. `path/to/file@0`. Applies to a Mach-O within a + /// fat binary at the given path. If the path is to a bundle, the setting applies + /// to all Mach-O binaries in that bundle. + /// * `@[cpu_type=]` e.g. `Contents/MacOS/binary@[cpu_type=7]` + /// or `Contents/MacOS/binary@[cpu_type=arm64]`. Applies to a Mach-O within a + /// fat binary targeting a CPU architecture identified by its integer constant + /// or string name. If the path is to a bundle, the setting applies to all + /// Mach-O binaries in that bundle. + /// + /// The following named CPU architectures are recognized: + /// + /// * arm + /// * arm64 + /// * arm64_32 + /// * x86_64 + /// + /// Signing will traverse into nested entities: + /// + /// * A fat Mach-O binary will traverse into the multiple Mach-O binaries within. + /// * A bundle will traverse into nested bundles. + /// * A bundle will traverse non-code "resource" files and sign their digests. + /// * A bundle will traverse non-main Mach-O binaries and sign them, adding their + /// metadata to the signed resources file. + /// + /// When signing nested entities, only some signing settings will be copied + /// automatically: + /// + /// * All settings related to the signing certificate/key. + /// * --timestamp-url + /// * --signing-time + /// * --exclude + /// * --digest + /// * --runtime-version + /// + /// All other settings only apply to the main entity being signed or the + /// scoped path being annotated. + /// + /// # Bundle Signing Overrides Settings + /// + /// When signing bundles, some settings specified on the command line will be + /// ignored. This is to ensure that the produced signing data is correct. The + /// settings ignored include (but may not be limited to): + /// + /// * --binary-identifier for the main executable. The `CFBundleIdentifier` value + /// from the bundle's `Info.plist` will be used instead. + /// * --code-resources-path. The code resources data will be computed automatically + /// as part of signing the bundle. + /// * --info-plist-path. The `Info.plist` from the bundle will be used instead. + /// * --digest + /// + /// # Designated Code Requirements + /// + /// When using Apple issued code signing certificates, we will attempt to apply + /// an appropriate designated requirement automatically during signing which + /// matches the behavior of what `codesign` would do. We do not yet support all + /// signing certificates and signing targets for this, however. So you may + /// need to provide your own requirements. + /// + /// Designated code requirements can be specified via --code-requirements-path. + /// + /// This file MUST contain a binary/compiled code requirements expression. We do + /// not (yet) support parsing the human-friendly code requirements DSL. A + /// binary/compiled file can be produced via Apple's `csreq` tool. e.g. + /// `csreq -r '=' -b /output/path`. If code requirements data is + /// specified, it will be parsed and displayed as part of signing to ensure it + /// is well-formed. + /// + /// # Code Signing Key Pair + /// + /// By default, the embedded code signature will only contain digests of the + /// binary and other important entities (such as entitlements and resources). + /// This is often referred to as \"ad-hoc\" signing. + /// + /// To use a code signing key/certificate to derive a cryptographic signature, + /// you must specify a source certificate to use. This can be done in the following + /// ways: + /// + /// * The --p12-file denotes the location to a PFX formatted file. These are + /// often .pfx or .p12 files. A password is required to open these files. + /// Specify one via --p12-password or --p12-password-file or enter a password + /// when prompted. + /// * The --pem-file argument defines paths to files containing PEM encoded + /// certificate/key data. (e.g. files with \"===== BEGIN CERTIFICATE =====\"). + /// * The --certificate-der-file argument defines paths to files containing DER + /// encoded certificate/key data. + /// * The --keychain-domain and --keychain-fingerprint arguments can be used to + /// load code signing certificates from macOS keychains. These arguments are + /// ignored on non-macOS platforms. + /// * The --windows-store-name and --windows-store-cert-fingerprint arguments can be used to + /// load code signing certificates from the Windows store. These arguments are + /// ignored on non-Windows platforms. + /// * The --smartcard-slot argument defines the name of a slot in a connected + /// smartcard device to read from. `9c` is common. + /// * Arguments beginning with --remote activate *remote signing mode* and can + /// be used to delegate cryptographic signing operations to a separate machine. + /// It is strongly advised to read the user documentation on remote signing + /// mode at https://gregoryszorc.com/docs/apple-codesign/main/. + /// + /// If you export a code signing certificate from the macOS keychain via the + /// `Keychain Access` application as a .p12 file, we should be able to read these + /// files via --p12-file. + /// + /// When using --pem-file, certificates and public keys are parsed from + /// `BEGIN CERTIFICATE` and `BEGIN PRIVATE KEY` sections in the files. + /// + /// The way certificate discovery works is that --p12-file is read followed by + /// all values to --pem-file. The seen signing keys and certificates are + /// collected. After collection, there must be 0 or 1 signing keys present, or + /// an error occurs. The first encountered public certificate is assigned + /// to be paired with the signing key. All remaining certificates are assumed + /// to constitute the CA issuing chain and will be added to the signature + /// data to facilitate validation. + /// + /// If you are using an Apple-issued code signing certificate, we detect this + /// and automatically register the Apple CA certificate chain so it is included + /// in the digital signature. This matches the behavior of the `codesign` tool. + /// + /// For best results, put your private key and its corresponding X.509 certificate + /// in a single file, either a PFX or PEM formatted file. Then add any additional + /// certificates constituting the signing chain in a separate PEM file. + /// + /// When using a code signing key/certificate, a Time-Stamp Protocol server URL + /// can be specified via --timestamp-url. By default, Apple's server is used. The + /// special value \"none\" can disable using a timestamp server. + /// + /// # Selecting What to Sign + /// + /// By default, this command attempts to recursively sign everything in the source + /// path. This applies to: + /// + /// * Bundles. If the specified bundle has nested bundles, those nested bundles + /// will be signed automatically. + /// + /// It is possible to exclude nested items from signing using --exclude. This + /// argument takes a glob expression that matches *relative paths* from the + /// source path. Glob expressions can be literal string compares. Or the + /// following special syntax is recognized: + /// + /// * `?` matches any single character. + /// * `*` matches any (possibly empty) sequence of characters. + /// * `**` matches the current directory and arbitrary subdirectories. This sequence + /// must form a single path component, so both **a and b** are invalid and will + /// result in an error. A sequence of more than two consecutive * characters is + /// also invalid. + /// * `[...]` matches any character inside the brackets. Character sequences can also + /// specify ranges of characters, as ordered by Unicode, so e.g. [0-9] specifies any + /// character between 0 and 9 inclusive. An unclosed bracket is invalid. + /// * `[!...]` is the negation of `[...]`, i.e. it matches any characters not in the + /// brackets. + /// * The metacharacters `?`, `*`, `[`, `]` can be matched by using brackets (e.g. + /// `[?]`). When a `]` occurs immediately following `[` or `[!` then it is + /// interpreted as being part of, rather then ending, the character set, so `]` and + /// `NOT ]` can be matched by `[]]` and `[!]]` respectively. The `-` character can + /// be specified inside a character sequence pattern by placing it at the start or + /// the end, e.g. `[abc-]`. + /// + /// Currently, --exclude only applies to the relative path of nested bundles within + /// the main bundle to sign. e.g. if you sign `MyApp.app` and it has a + /// `Contents/Frameworks/MyFramework.framework` that you wish to exclude, you would + /// `--exclude Contents/Frameworks/MyFramework.framework` or even + /// `--exclude Contents/Frameworks/**` to exclude the entire directory tree. + /// + /// Exclusions will still be copied and parents that need to reference exclude + /// entities will continue to do so. If you wish to make a file or directory + /// disappear, create a new directory without the file(s) and sign that. + /// + /// To exclude all nested bundles from being signed and only sign the main bundle + /// (the default behavior of ``codesign`` without ``--deep``), use `--exclude '**'`. + #[command(verbatim_doc_comment)] + Sign(Sign), + + /// Generate a new private key on a smartcard + SmartcardGenerateKey(SmartcardGenerateKey), + + /// Import a code signing certificate and key into a smartcard + SmartcardImport(SmartcardImport), + + /// Show information about available smartcard (SC) devices + SmartcardScan(SmartcardScan), + + /// Staples a notarization ticket to an entity + Staple(Staple), + + /// Verifies code signature data + Verify(Verify), + + /// Export CA certificates from the Windows Store + WindowsStoreExportCertificateChain(WindowsStoreExportCertificateChain), + + /// Print information about certificates in the Windows Store + WindowsStorePrintCertificates(WindowsStorePrintCertificates), + + /// Print information about X.509 OIDs related to Apple code signing + X509Oids(X509Oids), +} + +impl Subcommands { + fn as_cli_command(&self) -> &dyn CliCommand { + match self { + Subcommands::AnalyzeCertificate(c) => c, + Subcommands::ComputeCodeHashes(c) => c, + Subcommands::DebugCreateCodeRequirements(c) => c, + Subcommands::DebugCreateConstraints(c) => c, + Subcommands::DebugCreateEntitlements(c) => c, + Subcommands::DebugCreateInfoPlist(c) => c, + Subcommands::DebugCreateMacho(c) => c, + Subcommands::DebugFileTree(c) => c, + Subcommands::DiffSignatures(c) => c, + #[cfg(feature = "notarize")] + Subcommands::EncodeAppStoreConnectApiKey(c) => c, + Subcommands::Extract(c) => c, + Subcommands::GenerateCertificateSigningRequest(c) => c, + Subcommands::GenerateSelfSignedCertificate(c) => c, + Subcommands::KeychainExportCertificateChain(c) => c, + Subcommands::KeychainPrintCertificates(c) => c, + Subcommands::MachoUniversalCreate(c) => c, + #[cfg(feature = "notarize")] + Subcommands::NotaryLog(c) => c, + #[cfg(feature = "notarize")] + Subcommands::NotaryList(c) => c, + #[cfg(feature = "notarize")] + Subcommands::NotarySubmit(c) => c, + #[cfg(feature = "notarize")] + Subcommands::NotaryWait(c) => c, + Subcommands::ParseCodeSigningRequirement(c) => c, + Subcommands::PrintSignatureInfo(c) => c, + Subcommands::RemoteSign(c) => c, + Subcommands::Sign(c) => c, + Subcommands::SmartcardGenerateKey(c) => c, + Subcommands::SmartcardImport(c) => c, + Subcommands::SmartcardScan(c) => c, + Subcommands::Staple(c) => c, + Subcommands::Verify(c) => c, + Subcommands::WindowsStoreExportCertificateChain(c) => c, + Subcommands::WindowsStorePrintCertificates(c) => c, + Subcommands::X509Oids(c) => c, + } + } +} + +/// Sign and notarize Apple programs. See https://gregoryszorc.com/docs/apple-codesign/main/ for more docs +#[derive(Parser)] +#[command(author, version, arg_required_else_help = true)] +struct Cli { + /// Explicit configuration file to load. + /// + /// If provided, the default configuration files are not loaded, even + /// if they exist. + /// + /// Can be specified multiple times. Files are loaded/merged in the order + /// given. + /// + /// The special value `/dev/null` can be used to specify an empty/null + /// config file. It can be used to short-circuit loading of default config + /// files. + #[arg(short = 'C', long = "config-file", global = true)] + config_path: Vec, + + /// Configuration profile to load. + /// + /// If not specified, the implicit "default" profile is loaded. + #[arg(short = 'P', long, global = true)] + profile: Option, + + /// Increase logging verbosity. Can be specified multiple times + #[arg(short = 'v', long, global = true, action = ArgAction::Count)] + verbose: u8, + + #[command(subcommand)] + command: Subcommands, +} + +impl Cli { + pub fn config_builder(&self) -> ConfigBuilder { + let mut config = ConfigBuilder::default(); + + config = if self.config_path.is_empty() { + config.with_user_config_file().with_cwd_config_file() + } else { + for path in &self.config_path { + if path.display().to_string() == "/dev/null" { + break; + } + + config = config.toml_file(path); + } + + config + }; + + if let Some(profile) = &self.profile { + config = config.profile(profile.to_string()); + } + + // Environment variables override everything. + config = config.with_env_prefix(); + + config + } +} + +pub fn main_impl() -> Result<(), AppleCodesignError> { + let cli = Cli::parse(); + + let log_level = match cli.verbose { + 0 => LevelFilter::Warn, + 1 => LevelFilter::Info, + 2 => LevelFilter::Debug, + _ => LevelFilter::Trace, + }; + + let mut builder = env_logger::Builder::new(); + + builder + .filter_level(log_level) + .parse_default_env(); + + // Disable log context except at higher log levels. + if log_level <= LevelFilter::Info { + builder + .format_timestamp(None) + .format_level(false) + .format_target(false); + } + + // This spews unwanted output at default level. Nerf it by default. + if log_level == LevelFilter::Info { + builder.filter_module("rustls", LevelFilter::Error); + } + + builder.init(); + + let mut config_builder = cli.config_builder(); + + let command = cli.command.as_cli_command(); + + if let Some(config) = command.as_config()? { + config_builder = config_builder.with_config_struct(config); + } + + let config = config_builder.config()?; + + let context = Context { config }; + + command.run(&context) +} + +#[cfg(test)] +mod test { + use super::*; + use clap::CommandFactory; + + #[test] + fn verify_cli() { + Cli::command().debug_assert(); + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/code_directory.rs b/3rdparty/apple-codesign-0.29.0/src/code_directory.rs new file mode 100644 index 00000000..eb9f71fd --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/code_directory.rs @@ -0,0 +1,798 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Code directory data structure and related types. + +use { + crate::{ + cryptography::{Digest, DigestType}, + embedded_signature::{ + read_and_validate_blob_header, Blob, CodeSigningMagic, CodeSigningSlot, + }, + error::AppleCodesignError, + macho::{MachoTarget, Platform}, + }, + scroll::{IOwrite, Pread}, + semver::Version, + std::{borrow::Cow, collections::BTreeMap, io::Write, str::FromStr}, +}; + +bitflags::bitflags! { + /// Code signature flags. + /// + /// These flags are embedded in the Code Directory and govern use of the embedded + /// signature. + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] + pub struct CodeSignatureFlags: u32 { + /// Code may act as a host that controls and supervises guest code. + const HOST = 0x0001; + /// The code has been sealed without a signing identity. + const ADHOC = 0x0002; + /// Set the "hard" status bit for the code when it starts running. + const FORCE_HARD = 0x0100; + /// Implicitly set the "kill" status bit for the code when it starts running. + const FORCE_KILL = 0x0200; + /// Force certificate expiration checks. + const FORCE_EXPIRATION = 0x0400; + /// Restrict dyld loading. + const RESTRICT = 0x0800; + /// Enforce code signing. + const ENFORCEMENT = 0x1000; + /// Library validation required. + const LIBRARY_VALIDATION = 0x2000; + /// Apply runtime hardening policies. + const RUNTIME = 0x10000; + /// The code was automatically signed by the linker. + /// + /// This signature should be ignored in any new signing operation. + const LINKER_SIGNED = 0x20000; + } +} + +impl FromStr for CodeSignatureFlags { + type Err = AppleCodesignError; + + fn from_str(s: &str) -> Result { + match s { + "host" => Ok(Self::HOST), + "hard" => Ok(Self::FORCE_HARD), + "kill" => Ok(Self::FORCE_KILL), + "expires" => Ok(Self::FORCE_EXPIRATION), + "library" => Ok(Self::LIBRARY_VALIDATION), + "runtime" => Ok(Self::RUNTIME), + "linker-signed" => Ok(Self::LINKER_SIGNED), + _ => Err(AppleCodesignError::CodeSignatureUnknownFlag(s.to_string())), + } + } +} + +impl CodeSignatureFlags { + /// Obtain all flags that can be set by the user. + /// + /// Maps to variants that have a `from_str()` implementation. + pub fn all_user_configurable() -> [&'static str; 7] { + [ + "host", + "hard", + "kill", + "expires", + "library", + "runtime", + "linker-signed", + ] + } + + /// Attempt to convert a series of strings into a [CodeSignatureFlags]. + pub fn from_strs(s: &[&str]) -> Result { + let mut flags = CodeSignatureFlags::empty(); + + for s in s { + flags |= Self::from_str(s)?; + } + + Ok(flags) + } +} + +bitflags::bitflags! { + /// Flags that influence behavior of executable segment. + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] + pub struct ExecutableSegmentFlags: u64 { + /// Executable segment belongs to main binary. + const MAIN_BINARY = 0x0001; + /// Allow unsigned pages (for debugging). + const ALLOW_UNSIGNED = 0x0010; + /// Main binary is debugger. + const DEBUGGER = 0x0020; + /// JIT enabled. + const JIT = 0x0040; + /// Skip library validation (obsolete). + const SKIP_LIBRARY_VALIDATION = 0x0080; + /// Can bless code directory hash for execution. + const CAN_LOAD_CD_HASH = 0x0100; + /// Can execute blessed code directory hash. + const CAN_EXEC_CD_HASH = 0x0200; + } +} + +impl FromStr for ExecutableSegmentFlags { + type Err = AppleCodesignError; + + fn from_str(s: &str) -> Result { + match s { + "main-binary" => Ok(Self::MAIN_BINARY), + "allow-unsigned" => Ok(Self::ALLOW_UNSIGNED), + "debugger" => Ok(Self::DEBUGGER), + "jit" => Ok(Self::JIT), + "skip-library-validation" => Ok(Self::SKIP_LIBRARY_VALIDATION), + "can-load-cd-hash" => Ok(Self::CAN_LOAD_CD_HASH), + "can-exec-cd-hash" => Ok(Self::CAN_EXEC_CD_HASH), + _ => Err(AppleCodesignError::ExecutableSegmentUnknownFlag( + s.to_string(), + )), + } + } +} + +/// Version of Code Directory data structure. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(u32)] +pub enum CodeDirectoryVersion { + Initial = 0x20000, + SupportsScatter = 0x20100, + SupportsTeamId = 0x20200, + SupportsCodeLimit64 = 0x20300, + SupportsExecutableSegment = 0x20400, + SupportsRuntime = 0x20500, + SupportsLinkage = 0x20600, +} + +#[repr(C)] +pub struct Scatter { + /// Number of pages. 0 for sentinel only. + count: u32, + /// First page number. + base: u32, + /// Offset in target. + target_offset: u64, + /// Reserved. + spare: u64, +} + +fn get_hashes(data: &[u8], offset: usize, count: usize, hash_size: usize) -> Vec> { + data[offset..offset + (count * hash_size)] + .chunks(hash_size) + .map(|data| Digest { data: data.into() }) + .collect() +} + +/// Represents a code directory blob entry. +/// +/// This struct is versioned and has been extended over time. +/// +/// The struct here represents a superset of all fields in all versions. +/// +/// The parser will set `Option` fields to `None` for instances +/// where the version is lower than the version that field was introduced in. +#[derive(Debug, Default)] +pub struct CodeDirectoryBlob<'a> { + /// Compatibility version. + pub version: u32, + /// Setup and mode flags. + pub flags: CodeSignatureFlags, + // digest_offset, ident_offset, n_special_slots, and n_code_slots not stored + // explicitly because they are redundant with derived fields. + /// Limit to main image signature range. + /// + /// This is the file-level offset to stop digesting code data at. + /// It likely corresponds to the file-offset offset where the + /// embedded signature data starts in the `__LINKEDIT` segment. + pub code_limit: u32, + /// Size of each slot/code digest in bytes. + pub digest_size: u8, + /// Type of content digest being used. + pub digest_type: DigestType, + /// Platform identifier. 0 if not platform binary. + pub platform: u8, + /// Page size in bytes. (stored as log u8) + pub page_size: u32, + /// Unused (must be 0). + pub spare2: u32, + // Version 0x20100 + /// Offset of optional scatter vector. + pub scatter_offset: Option, + // Version 0x20200 + // team_offset not stored because it is redundant with derived stored str. + // Version 0x20300 + /// Unused (must be 0). + pub spare3: Option, + /// Limit to main image signature range, 64 bits. + pub code_limit_64: Option, + // Version 0x20400 + /// Offset of executable segment. + pub exec_seg_base: Option, + /// Limit of executable segment. + pub exec_seg_limit: Option, + /// Executable segment flags. + pub exec_seg_flags: Option, + // Version 0x20500 + pub runtime: Option, + pub pre_encrypt_offset: Option, + // Version 0x20600 + pub linkage_hash_type: Option, + pub linkage_truncated: Option, + pub spare4: Option, + pub linkage_offset: Option, + pub linkage_size: Option, + + // End of blob header data / start of derived data. + pub ident: Cow<'a, str>, + pub team_name: Option>, + pub code_digests: Vec>, + pub special_digests: BTreeMap>, +} + +impl<'a> Blob<'a> for CodeDirectoryBlob<'a> { + fn magic() -> u32 { + u32::from(CodeSigningMagic::CodeDirectory) + } + + fn from_blob_bytes(data: &'a [u8]) -> Result { + read_and_validate_blob_header(data, Self::magic(), "code directory blob")?; + + let offset = &mut 8; + + let version = data.gread_with(offset, scroll::BE)?; + let flags = data.gread_with::(offset, scroll::BE)?; + let flags = CodeSignatureFlags::from_bits_retain(flags); + assert_eq!(*offset, 0x10); + let digest_offset = data.gread_with::(offset, scroll::BE)?; + let ident_offset = data.gread_with::(offset, scroll::BE)?; + let n_special_slots = data.gread_with::(offset, scroll::BE)?; + let n_code_slots = data.gread_with::(offset, scroll::BE)?; + assert_eq!(*offset, 0x20); + let code_limit = data.gread_with(offset, scroll::BE)?; + let digest_size = data.gread_with(offset, scroll::BE)?; + let digest_type = data.gread_with::(offset, scroll::BE)?.into(); + let platform = data.gread_with(offset, scroll::BE)?; + let page_size = data.gread_with::(offset, scroll::BE)?; + let page_size = 2u32.pow(page_size as u32); + let spare2 = data.gread_with(offset, scroll::BE)?; + + let scatter_offset = if version >= CodeDirectoryVersion::SupportsScatter as u32 { + let v = data.gread_with(offset, scroll::BE)?; + + if v != 0 { + Some(v) + } else { + None + } + } else { + None + }; + let team_offset = if version >= CodeDirectoryVersion::SupportsTeamId as u32 { + assert_eq!(*offset, 0x30); + let v = data.gread_with::(offset, scroll::BE)?; + + if v != 0 { + Some(v) + } else { + None + } + } else { + None + }; + + let (spare3, code_limit_64) = if version >= CodeDirectoryVersion::SupportsCodeLimit64 as u32 + { + ( + Some(data.gread_with(offset, scroll::BE)?), + Some(data.gread_with(offset, scroll::BE)?), + ) + } else { + (None, None) + }; + + let (exec_seg_base, exec_seg_limit, exec_seg_flags) = + if version >= CodeDirectoryVersion::SupportsExecutableSegment as u32 { + assert_eq!(*offset, 0x40); + ( + Some(data.gread_with(offset, scroll::BE)?), + Some(data.gread_with(offset, scroll::BE)?), + Some(data.gread_with::(offset, scroll::BE)?), + ) + } else { + (None, None, None) + }; + + let exec_seg_flags = exec_seg_flags.map(ExecutableSegmentFlags::from_bits_retain); + + let (runtime, pre_encrypt_offset) = + if version >= CodeDirectoryVersion::SupportsRuntime as u32 { + assert_eq!(*offset, 0x58); + ( + Some(data.gread_with(offset, scroll::BE)?), + Some(data.gread_with(offset, scroll::BE)?), + ) + } else { + (None, None) + }; + + let (linkage_hash_type, linkage_truncated, spare4, linkage_offset, linkage_size) = + if version >= CodeDirectoryVersion::SupportsLinkage as u32 { + assert_eq!(*offset, 0x60); + ( + Some(data.gread_with(offset, scroll::BE)?), + Some(data.gread_with(offset, scroll::BE)?), + Some(data.gread_with(offset, scroll::BE)?), + Some(data.gread_with(offset, scroll::BE)?), + Some(data.gread_with(offset, scroll::BE)?), + ) + } else { + (None, None, None, None, None) + }; + + // Find trailing null in identifier string. + let ident = match data[ident_offset as usize..] + .split(|&b| b == 0) + .map(std::str::from_utf8) + .next() + { + Some(res) => { + Cow::from(res.map_err(|_| AppleCodesignError::CodeDirectoryMalformedIdentifier)?) + } + None => { + return Err(AppleCodesignError::CodeDirectoryMalformedIdentifier); + } + }; + + let team_name = if let Some(team_offset) = team_offset { + match data[team_offset as usize..] + .split(|&b| b == 0) + .map(std::str::from_utf8) + .next() + { + Some(res) => { + Some(Cow::from(res.map_err(|_| { + AppleCodesignError::CodeDirectoryMalformedTeam + })?)) + } + None => { + return Err(AppleCodesignError::CodeDirectoryMalformedTeam); + } + } + } else { + None + }; + + let code_digests = get_hashes( + data, + digest_offset as usize, + n_code_slots as usize, + digest_size as usize, + ); + + let special_digests = get_hashes( + data, + (digest_offset - (digest_size as u32 * n_special_slots)) as usize, + n_special_slots as usize, + digest_size as usize, + ) + .into_iter() + .enumerate() + .map(|(i, h)| (CodeSigningSlot::from(n_special_slots - i as u32), h)) + .collect(); + + Ok(Self { + version, + flags, + code_limit, + digest_size, + digest_type, + platform, + page_size, + spare2, + scatter_offset, + spare3, + code_limit_64, + exec_seg_base, + exec_seg_limit, + exec_seg_flags, + runtime, + pre_encrypt_offset, + linkage_hash_type, + linkage_truncated, + spare4, + linkage_offset, + linkage_size, + ident, + team_name, + code_digests, + special_digests, + }) + } + + fn serialize_payload(&self) -> Result, AppleCodesignError> { + let mut cursor = std::io::Cursor::new(Vec::::new()); + + // We need to do this in 2 phases because we don't know the length until + // we build up the data structure. + + cursor.iowrite_with(self.version, scroll::BE)?; + cursor.iowrite_with(self.flags.bits(), scroll::BE)?; + let digest_offset_cursor_position = cursor.position(); + cursor.iowrite_with(0u32, scroll::BE)?; + let ident_offset_cursor_position = cursor.position(); + cursor.iowrite_with(0u32, scroll::BE)?; + assert_eq!(cursor.position(), 0x10); + + // Digest offsets and counts are wonky. The recorded digest offset is the beginning + // of code digests and special digests are in "negative" indices before + // that offset. Digests are also at the index of their CodeSigningSlot constant. + // e.g. Code Directory is the first element in the specials array because + // it is slot 0. This means we need to write out empty digests for missing + // special slots. Our local specials HashMap may not have all entries. So compute + // how many specials there should be and write that here. We'll insert placeholder + // digests later. + let highest_slot = self + .special_digests + .keys() + .map(|slot| u32::from(*slot)) + .max() + .unwrap_or(0); + + cursor.iowrite_with(highest_slot, scroll::BE)?; + cursor.iowrite_with(self.code_digests.len() as u32, scroll::BE)?; + cursor.iowrite_with(self.code_limit, scroll::BE)?; + cursor.iowrite_with(self.digest_size, scroll::BE)?; + cursor.iowrite_with(u8::from(self.digest_type), scroll::BE)?; + cursor.iowrite_with(self.platform, scroll::BE)?; + cursor.iowrite_with(self.page_size.trailing_zeros() as u8, scroll::BE)?; + assert_eq!(cursor.position(), 0x20); + cursor.iowrite_with(self.spare2, scroll::BE)?; + + let mut scatter_offset_cursor_position = None; + let mut team_offset_cursor_position = None; + + if self.version >= CodeDirectoryVersion::SupportsScatter as u32 { + scatter_offset_cursor_position = Some(cursor.position()); + cursor.iowrite_with(self.scatter_offset.unwrap_or(0), scroll::BE)?; + + if self.version >= CodeDirectoryVersion::SupportsTeamId as u32 { + team_offset_cursor_position = Some(cursor.position()); + cursor.iowrite_with(0u32, scroll::BE)?; + + if self.version >= CodeDirectoryVersion::SupportsCodeLimit64 as u32 { + cursor.iowrite_with(self.spare3.unwrap_or(0), scroll::BE)?; + assert_eq!(cursor.position(), 0x30); + cursor.iowrite_with(self.code_limit_64.unwrap_or(0), scroll::BE)?; + + if self.version >= CodeDirectoryVersion::SupportsExecutableSegment as u32 { + cursor.iowrite_with(self.exec_seg_base.unwrap_or(0), scroll::BE)?; + assert_eq!(cursor.position(), 0x40); + cursor.iowrite_with(self.exec_seg_limit.unwrap_or(0), scroll::BE)?; + cursor.iowrite_with( + self.exec_seg_flags + .unwrap_or_else(ExecutableSegmentFlags::empty) + .bits(), + scroll::BE, + )?; + + if self.version >= CodeDirectoryVersion::SupportsRuntime as u32 { + assert_eq!(cursor.position(), 0x50); + cursor.iowrite_with(self.runtime.unwrap_or(0), scroll::BE)?; + cursor + .iowrite_with(self.pre_encrypt_offset.unwrap_or(0), scroll::BE)?; + + if self.version >= CodeDirectoryVersion::SupportsLinkage as u32 { + cursor.iowrite_with( + self.linkage_hash_type.unwrap_or(0), + scroll::BE, + )?; + cursor.iowrite_with( + self.linkage_truncated.unwrap_or(0), + scroll::BE, + )?; + cursor.iowrite_with(self.spare4.unwrap_or(0), scroll::BE)?; + cursor + .iowrite_with(self.linkage_offset.unwrap_or(0), scroll::BE)?; + assert_eq!(cursor.position(), 0x60); + cursor.iowrite_with(self.linkage_size.unwrap_or(0), scroll::BE)?; + } + } + } + } + } + } + + // We've written all the struct fields. Now write variable length fields. + + let identity_offset = cursor.position(); + cursor.write_all(self.ident.as_bytes())?; + cursor.write_all(b"\0")?; + + let team_offset = cursor.position(); + if team_offset_cursor_position.is_some() { + if let Some(team_name) = &self.team_name { + cursor.write_all(team_name.as_bytes())?; + cursor.write_all(b"\0")?; + } + } + + // TODO consider aligning cursor on page boundary here for performance? + + // The boundary conditions are a bit wonky here. We want to go from greatest + // to smallest, not writing index 0 because that's the first code digest. + for slot_index in (1..highest_slot + 1).rev() { + let slot = CodeSigningSlot::from(slot_index); + assert!( + slot.is_code_directory_specials_expressible(), + "slot is expressible in code directory special digests" + ); + + if let Some(digest) = self.special_digests.get(&slot) { + assert_eq!( + digest.data.len(), + self.digest_size as usize, + "special slot digest length matches expected length" + ); + cursor.write_all(&digest.data)?; + } else { + cursor.write_all(&b"\0".repeat(self.digest_size as usize))?; + } + } + + let code_digests_start_offset = cursor.position(); + + for digest in &self.code_digests { + cursor.write_all(&digest.data)?; + } + + // TODO write out scatter vector. + + // Now go back and update the placeholder offsets. We need to add 8 to account + // for the blob header, which isn't present in this buffer. + cursor.set_position(digest_offset_cursor_position); + cursor.iowrite_with(code_digests_start_offset as u32 + 8, scroll::BE)?; + + cursor.set_position(ident_offset_cursor_position); + cursor.iowrite_with(identity_offset as u32 + 8, scroll::BE)?; + + if scatter_offset_cursor_position.is_some() && self.scatter_offset.is_some() { + return Err(AppleCodesignError::Unimplemented("scatter offset")); + } + + if let Some(offset) = team_offset_cursor_position { + if self.team_name.is_some() { + cursor.set_position(offset); + cursor.iowrite_with(team_offset as u32 + 8, scroll::BE)?; + } + } + + Ok(cursor.into_inner()) + } +} + +impl<'a> CodeDirectoryBlob<'a> { + /// Obtain the mapping of slots to digests. + pub fn slot_digests(&self) -> &BTreeMap> { + &self.special_digests + } + + /// Obtain the recorded digest for a given [CodeSigningSlot]. + pub fn slot_digest(&self, slot: CodeSigningSlot) -> Option<&Digest<'a>> { + self.special_digests.get(&slot) + } + + /// Set the digest for a given slot. + pub fn set_slot_digest( + &mut self, + slot: CodeSigningSlot, + digest: impl Into>, + ) -> Result<(), AppleCodesignError> { + if !slot.is_code_directory_specials_expressible() { + return Err(AppleCodesignError::LogicError(format!( + "slot {slot:?} cannot have its digest expressed on code directories" + ))); + } + + let digest = digest.into(); + + if digest.data.len() != self.digest_size as usize { + return Err(AppleCodesignError::LogicError(format!( + "attempt to assign digest for slot {:?} whose length {} does not match code directory digest length {}", + slot, digest.data.len(), self.digest_size + + ))); + } + + self.special_digests.insert(slot, digest); + + Ok(()) + } + + /// Adjust the version of the data structure according to what fields are set. + /// + /// Returns the old version. + pub fn adjust_version(&mut self, target: Option) -> u32 { + let old_version = self.version; + + let mut minimum_version = CodeDirectoryVersion::Initial; + + if self.scatter_offset.is_some() { + minimum_version = CodeDirectoryVersion::SupportsScatter; + } + if self.team_name.is_some() { + minimum_version = CodeDirectoryVersion::SupportsTeamId; + } + if self.spare3.is_some() || self.code_limit_64.is_some() { + minimum_version = CodeDirectoryVersion::SupportsCodeLimit64; + } + if self.exec_seg_base.is_some() + || self.exec_seg_limit.is_some() + || self.exec_seg_flags.is_some() + { + minimum_version = CodeDirectoryVersion::SupportsExecutableSegment; + } + if self.runtime.is_some() || self.pre_encrypt_offset.is_some() { + minimum_version = CodeDirectoryVersion::SupportsRuntime; + } + if self.linkage_hash_type.is_some() + || self.linkage_truncated.is_some() + || self.spare4.is_some() + || self.linkage_offset.is_some() + || self.linkage_size.is_some() + { + minimum_version = CodeDirectoryVersion::SupportsLinkage; + } + + // Some platforms have hard requirements for the minimum version. If + // targeting settings are in effect, we raise the minimum version accordingly. + if let Some(target) = target { + let target_minimum = match target.platform { + // iOS >= 15 requires a modern code signature format. + Platform::IOs | Platform::IosSimulator => { + if target.minimum_os_version >= Version::new(15, 0, 0) { + CodeDirectoryVersion::SupportsExecutableSegment + } else { + CodeDirectoryVersion::Initial + } + } + // Let's bump the minimum version for macOS 12 out of principle. + Platform::MacOs => { + if target.minimum_os_version >= Version::new(12, 0, 0) { + CodeDirectoryVersion::SupportsExecutableSegment + } else { + CodeDirectoryVersion::Initial + } + } + _ => CodeDirectoryVersion::Initial, + }; + + if target_minimum as u32 > minimum_version as u32 { + minimum_version = target_minimum; + } + } + + self.version = minimum_version as u32; + + old_version + } + + /// Clears optional fields that are newer than the current version. + /// + /// The C structure is versioned and our Rust struct is a superset of + /// all versions. While our serializer should omit too new fields for + /// a given version, it is possible for some optional fields to be set + /// when they wouldn't get serialized. + /// + /// Calling this function will set fields not present in the current + /// version to None. + pub fn clear_newer_fields(&mut self) { + if self.version < CodeDirectoryVersion::SupportsScatter as u32 { + self.scatter_offset = None; + } + if self.version < CodeDirectoryVersion::SupportsTeamId as u32 { + self.team_name = None; + } + if self.version < CodeDirectoryVersion::SupportsCodeLimit64 as u32 { + self.spare3 = None; + self.code_limit_64 = None; + } + if self.version < CodeDirectoryVersion::SupportsExecutableSegment as u32 { + self.exec_seg_base = None; + self.exec_seg_limit = None; + self.exec_seg_flags = None; + } + if self.version < CodeDirectoryVersion::SupportsRuntime as u32 { + self.runtime = None; + self.pre_encrypt_offset = None; + } + if self.version < CodeDirectoryVersion::SupportsLinkage as u32 { + self.linkage_hash_type = None; + self.linkage_truncated = None; + self.spare4 = None; + self.linkage_offset = None; + self.linkage_size = None; + } + } + + pub fn to_owned(&self) -> CodeDirectoryBlob<'static> { + CodeDirectoryBlob { + version: self.version, + flags: self.flags, + code_limit: self.code_limit, + digest_size: self.digest_size, + digest_type: self.digest_type, + platform: self.platform, + page_size: self.page_size, + spare2: self.spare2, + scatter_offset: self.scatter_offset, + spare3: self.spare3, + code_limit_64: self.code_limit_64, + exec_seg_base: self.exec_seg_base, + exec_seg_limit: self.exec_seg_limit, + exec_seg_flags: self.exec_seg_flags, + runtime: self.runtime, + pre_encrypt_offset: self.pre_encrypt_offset, + linkage_hash_type: self.linkage_hash_type, + linkage_truncated: self.linkage_truncated, + spare4: self.spare4, + linkage_offset: self.linkage_offset, + linkage_size: self.linkage_size, + ident: Cow::Owned(self.ident.clone().into_owned()), + team_name: self + .team_name + .as_ref() + .map(|x| Cow::Owned(x.clone().into_owned())), + code_digests: self + .code_digests + .iter() + .map(|h| h.to_owned()) + .collect::>(), + special_digests: self + .special_digests + .iter() + .map(|(k, v)| (k.to_owned(), v.to_owned())) + .collect::>(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn code_signature_flags_from_str() { + assert_eq!( + CodeSignatureFlags::from_str("host").unwrap(), + CodeSignatureFlags::HOST + ); + assert_eq!( + CodeSignatureFlags::from_str("hard").unwrap(), + CodeSignatureFlags::FORCE_HARD + ); + assert_eq!( + CodeSignatureFlags::from_str("kill").unwrap(), + CodeSignatureFlags::FORCE_KILL + ); + assert_eq!( + CodeSignatureFlags::from_str("expires").unwrap(), + CodeSignatureFlags::FORCE_EXPIRATION + ); + assert_eq!( + CodeSignatureFlags::from_str("library").unwrap(), + CodeSignatureFlags::LIBRARY_VALIDATION + ); + assert_eq!( + CodeSignatureFlags::from_str("runtime").unwrap(), + CodeSignatureFlags::RUNTIME + ); + assert_eq!( + CodeSignatureFlags::from_str("linker-signed").unwrap(), + CodeSignatureFlags::LINKER_SIGNED + ); + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/code_requirement.rs b/3rdparty/apple-codesign-0.29.0/src/code_requirement.rs new file mode 100644 index 00000000..2cfb41af --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/code_requirement.rs @@ -0,0 +1,2050 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +/*! Code requirement language primitives. + +Code signatures contain a binary encoded expression tree denoting requirements. +There is a human friendly DSL that can be turned into these binary expressions +using the `csreq` Apple tool. This module reimplements that language. + +# Binary Encoding + +Requirement expressions consist of opcodes. An opcode is defined by a u32 where +the high byte contains flags and the lower 3 bytes denote the opcode value. + +Some opcodes have payloads and the payload varies by opcode. A common pattern +is to length encode arbitrary data via a u32 denoting the length and N bytes +to follow. + +String data is not guaranteed to be terminated by a NULL. However, variable +length data is padded will NULL bytes so the next opcode is always aligned +on 4 byte boundaries. + +*/ + +use { + crate::{ + embedded_signature::{ + read_and_validate_blob_header, CodeSigningMagic, RequirementBlob, RequirementSetBlob, + }, + error::AppleCodesignError, + }, + bcder::Oid, + chrono::TimeZone, + scroll::{IOwrite, Pread}, + std::{ + borrow::Cow, + cmp::Ordering, + fmt::{Debug, Display}, + io::Write, + ops::{Deref, DerefMut}, + }, +}; + +const OPCODE_FLAG_MASK: u32 = 0xff000000; +const OPCODE_VALUE_MASK: u32 = 0x00ffffff; + +/// Opcode flag meaning has size field, okay to default to false. +#[allow(unused)] +const OPCODE_FLAG_DEFAULT_FALSE: u32 = 0x80000000; + +/// Opcode flag meaning has size field, skip and continue. +#[allow(unused)] +const OPCODE_FLAG_SKIP: u32 = 0x40000000; + +/// Denotes type of code requirements. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[repr(u32)] +pub enum RequirementType { + /// What hosts may run on us. + Host, + /// What guests we may run. + Guest, + /// Designated requirement. + Designated, + /// What libraries we may link against. + Library, + /// What plug-ins we may load. + Plugin, + /// Unknown requirement type. + Unknown(u32), +} + +impl From for RequirementType { + fn from(v: u32) -> Self { + match v { + 1 => Self::Host, + 2 => Self::Guest, + 3 => Self::Designated, + 4 => Self::Library, + 5 => Self::Plugin, + _ => Self::Unknown(v), + } + } +} + +impl From for u32 { + fn from(t: RequirementType) -> Self { + match t { + RequirementType::Host => 1, + RequirementType::Guest => 2, + RequirementType::Designated => 3, + RequirementType::Library => 4, + RequirementType::Plugin => 5, + RequirementType::Unknown(v) => v, + } + } +} + +impl PartialOrd for RequirementType { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for RequirementType { + fn cmp(&self, other: &Self) -> Ordering { + u32::from(*self).cmp(&u32::from(*other)) + } +} + +impl std::fmt::Display for RequirementType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Host => f.write_str("host(1)"), + Self::Guest => f.write_str("guest(2)"), + Self::Designated => f.write_str("designated(3)"), + Self::Library => f.write_str("library(4)"), + Self::Plugin => f.write_str("plugin(5)"), + Self::Unknown(v) => f.write_fmt(format_args!("unknown({v})")), + } + } +} + +fn read_data(data: &[u8]) -> Result<(&[u8], &[u8]), AppleCodesignError> { + let length = data.pread_with::(0, scroll::BE)?; + let value = &data[4..4 + length as usize]; + + // Next element is aligned on next 4 byte boundary. + let offset = 4 + length as usize; + + let offset = match offset % 4 { + 0 => offset, + extra => offset + 4 - extra, + }; + + let remaining = &data[offset..]; + + Ok((value, remaining)) +} + +fn write_data(dest: &mut impl Write, data: &[u8]) -> Result<(), AppleCodesignError> { + dest.iowrite_with(data.len() as u32, scroll::BE)?; + dest.write_all(data)?; + + match data.len() % 4 { + 0 => {} + pad => { + for _ in 0..4 - pad { + dest.iowrite(0u8)?; + } + } + } + + Ok(()) +} + +/// Format a certificate slot's value to human form. +fn format_certificate_slot(slot: i32) -> String { + match slot { + -1 => "root".to_string(), + 0 => "leaf".to_string(), + _ => format!("{slot}"), + } +} + +/// A value in a code requirement expression. +/// +/// The value can be various primitive types. This type exists to make it +/// easier to work with and format values in code requirement expressions. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum CodeRequirementValue<'a> { + String(Cow<'a, str>), + Bytes(Cow<'a, [u8]>), +} + +impl<'a> From<&'a [u8]> for CodeRequirementValue<'a> { + fn from(value: &'a [u8]) -> Self { + let is_ascii_printable = |c: &u8| -> bool { + c.is_ascii_alphanumeric() || c.is_ascii_whitespace() || c.is_ascii_punctuation() + }; + + if value.iter().all(is_ascii_printable) { + Self::String(unsafe { std::str::from_utf8_unchecked(value) }.into()) + } else { + Self::Bytes(value.into()) + } + } +} + +impl<'a> From<&'a str> for CodeRequirementValue<'a> { + fn from(s: &'a str) -> Self { + Self::String(s.into()) + } +} + +impl<'a> From> for CodeRequirementValue<'a> { + fn from(v: Cow<'a, str>) -> Self { + Self::String(v) + } +} + +impl From for CodeRequirementValue<'static> { + fn from(v: String) -> Self { + Self::String(Cow::Owned(v)) + } +} + +impl<'a> Display for CodeRequirementValue<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::String(s) => f.write_str(s), + Self::Bytes(data) => f.write_fmt(format_args!("{}", hex::encode(data))), + } + } +} + +impl<'a> CodeRequirementValue<'a> { + /// Write the encoded version of this value somewhere. + /// + /// Binary encoding is u32 of length, then raw bytes, then NULL padding to next u32. + fn write_encoded(&self, dest: &mut impl Write) -> Result<(), AppleCodesignError> { + match self { + Self::Bytes(data) => write_data(dest, data), + Self::String(s) => write_data(dest, s.as_bytes()), + } + } +} + +/// An opcode representing a code requirement expression. +#[derive(Clone, Copy, Debug, PartialEq)] +#[repr(u32)] +enum RequirementOpCode { + False = 0, + True = 1, + Identifier = 2, + AnchorApple = 3, + AnchorCertificateHash = 4, + InfoKeyValueLegacy = 5, + And = 6, + Or = 7, + CodeDirectoryHash = 8, + Not = 9, + InfoPlistExpression = 10, + CertificateField = 11, + CertificateTrusted = 12, + AnchorTrusted = 13, + CertificateGeneric = 14, + AnchorAppleGeneric = 15, + EntitlementsField = 16, + CertificatePolicy = 17, + NamedAnchor = 18, + NamedCode = 19, + Platform = 20, + Notarized = 21, + CertificateFieldDate = 22, + LegacyDeveloperId = 23, +} + +impl TryFrom for RequirementOpCode { + type Error = AppleCodesignError; + + fn try_from(v: u32) -> Result { + match v { + 0 => Ok(Self::False), + 1 => Ok(Self::True), + 2 => Ok(Self::Identifier), + 3 => Ok(Self::AnchorApple), + 4 => Ok(Self::AnchorCertificateHash), + 5 => Ok(Self::InfoKeyValueLegacy), + 6 => Ok(Self::And), + 7 => Ok(Self::Or), + 8 => Ok(Self::CodeDirectoryHash), + 9 => Ok(Self::Not), + 10 => Ok(Self::InfoPlistExpression), + 11 => Ok(Self::CertificateField), + 12 => Ok(Self::CertificateTrusted), + 13 => Ok(Self::AnchorTrusted), + 14 => Ok(Self::CertificateGeneric), + 15 => Ok(Self::AnchorAppleGeneric), + 16 => Ok(Self::EntitlementsField), + 17 => Ok(Self::CertificatePolicy), + 18 => Ok(Self::NamedAnchor), + 19 => Ok(Self::NamedCode), + 20 => Ok(Self::Platform), + 21 => Ok(Self::Notarized), + 22 => Ok(Self::CertificateFieldDate), + 23 => Ok(Self::LegacyDeveloperId), + _ => Err(AppleCodesignError::RequirementUnknownOpcode(v)), + } + } +} + +impl RequirementOpCode { + /// Parse the payload of an opcode. + /// + /// On successful parse, returns an [CodeRequirementExpression] and remaining data in + /// the input slice. + pub fn parse_payload<'a>( + &self, + data: &'a [u8], + ) -> Result<(CodeRequirementExpression<'a>, &'a [u8]), AppleCodesignError> { + match self { + Self::False => Ok((CodeRequirementExpression::False, data)), + Self::True => Ok((CodeRequirementExpression::True, data)), + Self::Identifier => { + let (value, data) = read_data(data)?; + let s = std::str::from_utf8(value).map_err(|_| { + AppleCodesignError::RequirementMalformed("identifier value not a UTF-8 string") + })?; + + Ok((CodeRequirementExpression::Identifier(Cow::from(s)), data)) + } + Self::AnchorApple => Ok((CodeRequirementExpression::AnchorApple, data)), + Self::AnchorCertificateHash => { + let slot = data.pread_with::(0, scroll::BE)?; + let digest_length = data.pread_with::(4, scroll::BE)?; + let digest = &data[8..8 + digest_length as usize]; + + Ok(( + CodeRequirementExpression::AnchorCertificateHash(slot, digest.into()), + &data[8 + digest_length as usize..], + )) + } + Self::InfoKeyValueLegacy => { + let (key, data) = read_data(data)?; + + let key = std::str::from_utf8(key).map_err(|_| { + AppleCodesignError::RequirementMalformed("info key not a UTF-8 string") + })?; + + let (value, data) = read_data(data)?; + + let value = std::str::from_utf8(value).map_err(|_| { + AppleCodesignError::RequirementMalformed("info value not a UTF-8 string") + })?; + + Ok(( + CodeRequirementExpression::InfoKeyValueLegacy(key.into(), value.into()), + data, + )) + } + Self::And => { + let (a, data) = CodeRequirementExpression::from_bytes(data)?; + let (b, data) = CodeRequirementExpression::from_bytes(data)?; + + Ok(( + CodeRequirementExpression::And(Box::new(a), Box::new(b)), + data, + )) + } + Self::Or => { + let (a, data) = CodeRequirementExpression::from_bytes(data)?; + let (b, data) = CodeRequirementExpression::from_bytes(data)?; + + Ok(( + CodeRequirementExpression::Or(Box::new(a), Box::new(b)), + data, + )) + } + Self::CodeDirectoryHash => { + let (value, data) = read_data(data)?; + + Ok(( + CodeRequirementExpression::CodeDirectoryHash(value.into()), + data, + )) + } + Self::Not => { + let (expr, data) = CodeRequirementExpression::from_bytes(data)?; + + Ok((CodeRequirementExpression::Not(Box::new(expr)), data)) + } + Self::InfoPlistExpression => { + let (key, data) = read_data(data)?; + + let key = std::str::from_utf8(key).map_err(|_| { + AppleCodesignError::RequirementMalformed("key is not valid UTF-8") + })?; + + let (expr, data) = CodeRequirementMatchExpression::from_bytes(data)?; + + Ok(( + CodeRequirementExpression::InfoPlistKeyField(key.into(), expr), + data, + )) + } + Self::CertificateField => { + let slot = data.pread_with::(0, scroll::BE)?; + + let (field, data) = read_data(&data[4..])?; + + let field = std::str::from_utf8(field).map_err(|_| { + AppleCodesignError::RequirementMalformed("certificate field is not valid UTF-8") + })?; + + let (expr, data) = CodeRequirementMatchExpression::from_bytes(data)?; + + Ok(( + CodeRequirementExpression::CertificateField(slot, field.into(), expr), + data, + )) + } + Self::CertificateTrusted => { + let slot = data.pread_with::(0, scroll::BE)?; + + Ok(( + CodeRequirementExpression::CertificateTrusted(slot), + &data[4..], + )) + } + Self::AnchorTrusted => Ok((CodeRequirementExpression::AnchorTrusted, data)), + Self::CertificateGeneric => { + let slot = data.pread_with::(0, scroll::BE)?; + + let (oid, data) = read_data(&data[4..])?; + + let (expr, data) = CodeRequirementMatchExpression::from_bytes(data)?; + + Ok(( + CodeRequirementExpression::CertificateGeneric(slot, Oid(oid), expr), + data, + )) + } + Self::AnchorAppleGeneric => Ok((CodeRequirementExpression::AnchorAppleGeneric, data)), + Self::EntitlementsField => { + let (key, data) = read_data(data)?; + + let key = std::str::from_utf8(key).map_err(|_| { + AppleCodesignError::RequirementMalformed("entitlement key is not UTF-8") + })?; + + let (expr, data) = CodeRequirementMatchExpression::from_bytes(data)?; + + Ok(( + CodeRequirementExpression::EntitlementsKey(key.into(), expr), + data, + )) + } + Self::CertificatePolicy => { + let slot = data.pread_with::(0, scroll::BE)?; + + let (oid, data) = read_data(&data[4..])?; + + let (expr, data) = CodeRequirementMatchExpression::from_bytes(data)?; + + Ok(( + CodeRequirementExpression::CertificatePolicy(slot, Oid(oid), expr), + data, + )) + } + Self::NamedAnchor => { + let (name, data) = read_data(data)?; + + let name = std::str::from_utf8(name).map_err(|_| { + AppleCodesignError::RequirementMalformed("named anchor isn't UTF-8") + })?; + + Ok((CodeRequirementExpression::NamedAnchor(name.into()), data)) + } + Self::NamedCode => { + let (name, data) = read_data(data)?; + + let name = std::str::from_utf8(name).map_err(|_| { + AppleCodesignError::RequirementMalformed("named code isn't UTF-8") + })?; + + Ok((CodeRequirementExpression::NamedCode(name.into()), data)) + } + Self::Platform => { + let value = data.pread_with::(0, scroll::BE)?; + + Ok((CodeRequirementExpression::Platform(value), &data[4..])) + } + Self::Notarized => Ok((CodeRequirementExpression::Notarized, data)), + Self::CertificateFieldDate => { + let slot = data.pread_with::(0, scroll::BE)?; + + let (oid, data) = read_data(&data[4..])?; + + let (expr, data) = CodeRequirementMatchExpression::from_bytes(data)?; + + Ok(( + CodeRequirementExpression::CertificateFieldDate(slot, Oid(oid), expr), + data, + )) + } + Self::LegacyDeveloperId => Ok((CodeRequirementExpression::LegacyDeveloperId, data)), + } + } +} + +/// Defines a code requirement expression. +#[derive(Clone, Debug, PartialEq)] +pub enum CodeRequirementExpression<'a> { + /// False + /// + /// `false` + /// + /// No payload. + False, + + /// True + /// + /// `true` + /// + /// No payload. + True, + + /// Signing identifier. + /// + /// `identifier ` + /// + /// 4 bytes length followed by C string. + Identifier(Cow<'a, str>), + + /// The certificate chain must lead to an Apple root. + /// + /// `anchor apple` + /// + /// No payload. + AnchorApple, + + /// The certificate chain must anchor to a certificate with specified SHA-1 hash. + /// + /// `certificate = H""` + /// + /// 4 bytes slot number, 4 bytes hash length, hash value. + AnchorCertificateHash(i32, Cow<'a, [u8]>), + + /// Info.plist key value (legacy). + /// + /// `info[] = ` + /// + /// 2 pairs of (length + value). + InfoKeyValueLegacy(Cow<'a, str>, Cow<'a, str>), + + /// Logical and. + /// + /// `expr0 and expr1` + /// + /// Payload consists of 2 sub-expressions with no additional encoding. + And( + Box>, + Box>, + ), + + /// Logical or. + /// + /// `expr0 or expr1` + /// + /// Payload consists of 2 sub-expressions with no additional encoding. + Or( + Box>, + Box>, + ), + + /// Code directory hash. + /// + /// `cdhash H"" + /// + /// 4 bytes length followed by raw digest value. + CodeDirectoryHash(Cow<'a, [u8]>), + + /// Logical not. + /// + /// `!expr` + /// + /// Payload is 1 sub-expression. + Not(Box>), + + /// Info plist key field. + /// + /// `info [key] match expression` + /// + /// e.g. `info [CFBundleName] exists` + /// + /// 4 bytes key length, key string, then match expression. + InfoPlistKeyField(Cow<'a, str>, CodeRequirementMatchExpression<'a>), + + /// Certificate field matches. + /// + /// `certificate [] match expression` + /// + /// Slot i32, 4 bytes field length, field string, then match expression. + CertificateField(i32, Cow<'a, str>, CodeRequirementMatchExpression<'a>), + + /// Certificate in position is trusted for code signing. + /// + /// `certificate trusted` + /// + /// 4 bytes certificate position. + CertificateTrusted(i32), + + /// The certificate chain must lead to a trusted root. + /// + /// `anchor trusted` + /// + /// No payload. + AnchorTrusted, + + /// Certificate field matches by OID. + /// + /// `certificate [field.] match expression` + /// + /// Slot i32, 4 bytes OID length, OID raw bytes, match expression. + CertificateGeneric(i32, Oid<&'a [u8]>, CodeRequirementMatchExpression<'a>), + + /// For code signed by Apple, including from code signing certificates issued by Apple. + /// + /// `anchor apple generic` + /// + /// No payload. + AnchorAppleGeneric, + + /// Value associated with specified key in signature's embedded entitlements dictionary. + /// + /// `entitlement [] match expression` + /// + /// 4 bytes key length, key bytes, match expression. + EntitlementsKey(Cow<'a, str>, CodeRequirementMatchExpression<'a>), + + /// OID associated with certificate in a given slot. + /// + /// It is unknown what the OID means. + /// + /// `certificate [policy.] match expression` + CertificatePolicy(i32, Oid<&'a [u8]>, CodeRequirementMatchExpression<'a>), + + /// A named Apple anchor. + /// + /// `anchor apple ` + /// + /// 4 bytes name length, name bytes. + NamedAnchor(Cow<'a, str>), + + /// Named code. + /// + /// `()` + /// + /// 4 bytes name length, name bytes. + NamedCode(Cow<'a, str>), + + /// Platform value. + /// + /// `platform = ` + /// + /// Payload is a u32. + Platform(u32), + + /// Binary is notarized. + /// + /// `notarized` + /// + /// No Payload. + Notarized, + + /// Certificate field date. + /// + /// Unknown what the OID corresponds to. + /// + /// `certificate [timestamp.] match expression` + CertificateFieldDate(i32, Oid<&'a [u8]>, CodeRequirementMatchExpression<'a>), + + /// Legacy developer ID used. + LegacyDeveloperId, +} + +impl<'a> Display for CodeRequirementExpression<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::False => f.write_str("never"), + Self::True => f.write_str("always"), + Self::Identifier(value) => f.write_fmt(format_args!("identifier \"{value}\"")), + Self::AnchorApple => f.write_str("anchor apple"), + Self::AnchorCertificateHash(slot, digest) => f.write_fmt(format_args!( + "certificate {} = H\"{}\"", + format_certificate_slot(*slot), + hex::encode(digest) + )), + Self::InfoKeyValueLegacy(key, value) => { + f.write_fmt(format_args!("info[{key}] = \"{value}\"")) + } + Self::And(a, b) => f.write_fmt(format_args!("({a}) and ({b})")), + Self::Or(a, b) => f.write_fmt(format_args!("({a}) or ({b})")), + Self::CodeDirectoryHash(digest) => { + f.write_fmt(format_args!("cdhash H\"{}\"", hex::encode(digest))) + } + Self::Not(expr) => f.write_fmt(format_args!("!({expr})")), + Self::InfoPlistKeyField(key, expr) => f.write_fmt(format_args!("info [{key}] {expr}")), + Self::CertificateField(slot, field, expr) => f.write_fmt(format_args!( + "certificate {}[{}] {}", + format_certificate_slot(*slot), + field, + expr + )), + Self::CertificateTrusted(slot) => { + f.write_fmt(format_args!("certificate {slot} trusted")) + } + Self::AnchorTrusted => f.write_str("anchor trusted"), + Self::CertificateGeneric(slot, oid, expr) => f.write_fmt(format_args!( + "certificate {}[field.{}] {}", + format_certificate_slot(*slot), + oid, + expr + )), + Self::AnchorAppleGeneric => f.write_str("anchor apple generic"), + Self::EntitlementsKey(key, expr) => { + f.write_fmt(format_args!("entitlement [{key}] {expr}")) + } + Self::CertificatePolicy(slot, oid, expr) => f.write_fmt(format_args!( + "certificate {}[policy.{}] {}", + format_certificate_slot(*slot), + oid, + expr + )), + Self::NamedAnchor(name) => f.write_fmt(format_args!("anchor apple {name}")), + Self::NamedCode(name) => f.write_fmt(format_args!("({name})")), + Self::Platform(platform) => f.write_fmt(format_args!("platform = {platform}")), + Self::Notarized => f.write_str("notarized"), + Self::CertificateFieldDate(slot, oid, expr) => f.write_fmt(format_args!( + "certificate {}[timestamp.{}] {}", + format_certificate_slot(*slot), + oid, + expr + )), + Self::LegacyDeveloperId => f.write_str("legacy"), + } + } +} + +impl<'a> From<&CodeRequirementExpression<'a>> for RequirementOpCode { + fn from(e: &CodeRequirementExpression) -> Self { + match e { + CodeRequirementExpression::False => RequirementOpCode::False, + CodeRequirementExpression::True => RequirementOpCode::True, + CodeRequirementExpression::Identifier(_) => RequirementOpCode::Identifier, + CodeRequirementExpression::AnchorApple => RequirementOpCode::AnchorApple, + CodeRequirementExpression::AnchorCertificateHash(_, _) => { + RequirementOpCode::AnchorCertificateHash + } + CodeRequirementExpression::InfoKeyValueLegacy(_, _) => { + RequirementOpCode::InfoKeyValueLegacy + } + CodeRequirementExpression::And(_, _) => RequirementOpCode::And, + CodeRequirementExpression::Or(_, _) => RequirementOpCode::Or, + CodeRequirementExpression::CodeDirectoryHash(_) => RequirementOpCode::CodeDirectoryHash, + CodeRequirementExpression::Not(_) => RequirementOpCode::Not, + CodeRequirementExpression::InfoPlistKeyField(_, _) => { + RequirementOpCode::InfoPlistExpression + } + CodeRequirementExpression::CertificateField(_, _, _) => { + RequirementOpCode::CertificateField + } + CodeRequirementExpression::CertificateTrusted(_) => { + RequirementOpCode::CertificateTrusted + } + CodeRequirementExpression::AnchorTrusted => RequirementOpCode::AnchorTrusted, + CodeRequirementExpression::CertificateGeneric(_, _, _) => { + RequirementOpCode::CertificateGeneric + } + CodeRequirementExpression::AnchorAppleGeneric => RequirementOpCode::AnchorAppleGeneric, + CodeRequirementExpression::EntitlementsKey(_, _) => { + RequirementOpCode::EntitlementsField + } + CodeRequirementExpression::CertificatePolicy(_, _, _) => { + RequirementOpCode::CertificatePolicy + } + CodeRequirementExpression::NamedAnchor(_) => RequirementOpCode::NamedAnchor, + CodeRequirementExpression::NamedCode(_) => RequirementOpCode::NamedCode, + CodeRequirementExpression::Platform(_) => RequirementOpCode::Platform, + CodeRequirementExpression::Notarized => RequirementOpCode::Notarized, + CodeRequirementExpression::CertificateFieldDate(_, _, _) => { + RequirementOpCode::CertificateFieldDate + } + CodeRequirementExpression::LegacyDeveloperId => RequirementOpCode::LegacyDeveloperId, + } + } +} + +impl<'a> CodeRequirementExpression<'a> { + /// Construct an expression element by reading from a slice. + /// + /// Returns the newly constructed element and remaining data in the slice. + pub fn from_bytes(data: &'a [u8]) -> Result<(Self, &'a [u8]), AppleCodesignError> { + let opcode_raw = data.pread_with::(0, scroll::BE)?; + + let _flags = opcode_raw & OPCODE_FLAG_MASK; + let opcode = opcode_raw & OPCODE_VALUE_MASK; + + let data = &data[4..]; + + let opcode = RequirementOpCode::try_from(opcode)?; + + opcode.parse_payload(data) + } + + /// Write binary representation of this expression to a destination. + pub fn write_to(&self, dest: &mut impl Write) -> Result<(), AppleCodesignError> { + dest.iowrite_with(RequirementOpCode::from(self) as u32, scroll::BE)?; + + match self { + Self::False => {} + Self::True => {} + Self::Identifier(s) => { + write_data(dest, s.as_bytes())?; + } + Self::AnchorApple => {} + Self::AnchorCertificateHash(slot, hash) => { + dest.iowrite_with(*slot, scroll::BE)?; + write_data(dest, hash)?; + } + Self::InfoKeyValueLegacy(key, value) => { + write_data(dest, key.as_bytes())?; + write_data(dest, value.as_bytes())?; + } + Self::And(a, b) => { + a.write_to(dest)?; + b.write_to(dest)?; + } + Self::Or(a, b) => { + a.write_to(dest)?; + b.write_to(dest)?; + } + Self::CodeDirectoryHash(hash) => { + write_data(dest, hash)?; + } + Self::Not(expr) => { + expr.write_to(dest)?; + } + Self::InfoPlistKeyField(key, m) => { + write_data(dest, key.as_bytes())?; + m.write_to(dest)?; + } + Self::CertificateField(slot, field, m) => { + dest.iowrite_with(*slot, scroll::BE)?; + write_data(dest, field.as_bytes())?; + m.write_to(dest)?; + } + Self::CertificateTrusted(slot) => { + dest.iowrite_with(*slot, scroll::BE)?; + } + Self::AnchorTrusted => {} + Self::CertificateGeneric(slot, oid, m) => { + dest.iowrite_with(*slot, scroll::BE)?; + write_data(dest, oid.as_ref())?; + m.write_to(dest)?; + } + Self::AnchorAppleGeneric => {} + Self::EntitlementsKey(key, m) => { + write_data(dest, key.as_bytes())?; + m.write_to(dest)?; + } + Self::CertificatePolicy(slot, oid, m) => { + dest.iowrite_with(*slot, scroll::BE)?; + write_data(dest, oid.as_ref())?; + m.write_to(dest)?; + } + Self::NamedAnchor(value) => { + write_data(dest, value.as_bytes())?; + } + Self::NamedCode(value) => { + write_data(dest, value.as_bytes())?; + } + Self::Platform(value) => { + dest.iowrite_with(*value, scroll::BE)?; + } + Self::Notarized => {} + Self::CertificateFieldDate(slot, oid, m) => { + dest.iowrite_with(*slot, scroll::BE)?; + write_data(dest, oid.as_ref())?; + m.write_to(dest)?; + } + Self::LegacyDeveloperId => {} + } + + Ok(()) + } + + /// Produce the binary serialization of this expression. + /// + /// The blob header/magic is not included. + pub fn to_bytes(&self) -> Result, AppleCodesignError> { + let mut res = vec![]; + + self.write_to(&mut res)?; + + Ok(res) + } +} + +/// A code requirement match expression type. +#[derive(Clone, Copy, Debug, PartialEq)] +#[repr(u32)] +enum MatchType { + Exists = 0, + Equal = 1, + Contains = 2, + BeginsWith = 3, + EndsWith = 4, + LessThan = 5, + GreaterThan = 6, + LessThanEqual = 7, + GreaterThanEqual = 8, + On = 9, + Before = 10, + After = 11, + OnOrBefore = 12, + OnOrAfter = 13, + Absent = 14, +} + +impl TryFrom for MatchType { + type Error = AppleCodesignError; + + fn try_from(v: u32) -> Result { + match v { + 0 => Ok(Self::Exists), + 1 => Ok(Self::Equal), + 2 => Ok(Self::Contains), + 3 => Ok(Self::BeginsWith), + 4 => Ok(Self::EndsWith), + 5 => Ok(Self::LessThan), + 6 => Ok(Self::GreaterThan), + 7 => Ok(Self::LessThanEqual), + 8 => Ok(Self::GreaterThanEqual), + 9 => Ok(Self::On), + 10 => Ok(Self::Before), + 11 => Ok(Self::After), + 12 => Ok(Self::OnOrBefore), + 13 => Ok(Self::OnOrAfter), + 14 => Ok(Self::Absent), + _ => Err(AppleCodesignError::RequirementUnknownMatchExpression(v)), + } + } +} + +impl MatchType { + /// Parse the payload of a match expression. + pub fn parse_payload<'a>( + &self, + data: &'a [u8], + ) -> Result<(CodeRequirementMatchExpression<'a>, &'a [u8]), AppleCodesignError> { + match self { + Self::Exists => Ok((CodeRequirementMatchExpression::Exists, data)), + Self::Equal => { + let (value, data) = read_data(data)?; + + Ok((CodeRequirementMatchExpression::Equal(value.into()), data)) + } + Self::Contains => { + let (value, data) = read_data(data)?; + + Ok((CodeRequirementMatchExpression::Contains(value.into()), data)) + } + Self::BeginsWith => { + let (value, data) = read_data(data)?; + + Ok(( + CodeRequirementMatchExpression::BeginsWith(value.into()), + data, + )) + } + Self::EndsWith => { + let (value, data) = read_data(data)?; + + Ok((CodeRequirementMatchExpression::EndsWith(value.into()), data)) + } + Self::LessThan => { + let (value, data) = read_data(data)?; + + Ok((CodeRequirementMatchExpression::LessThan(value.into()), data)) + } + Self::GreaterThan => { + let (value, data) = read_data(data)?; + + Ok(( + CodeRequirementMatchExpression::GreaterThan(value.into()), + data, + )) + } + Self::LessThanEqual => { + let (value, data) = read_data(data)?; + + Ok(( + CodeRequirementMatchExpression::LessThanEqual(value.into()), + data, + )) + } + Self::GreaterThanEqual => { + let (value, data) = read_data(data)?; + + Ok(( + CodeRequirementMatchExpression::GreaterThanEqual(value.into()), + data, + )) + } + Self::On => { + let value = data.pread_with::(0, scroll::BE)?; + + Ok(( + CodeRequirementMatchExpression::On( + chrono::Utc + .timestamp_opt(value, 0) + .single() + .ok_or(AppleCodesignError::BadTime)?, + ), + &data[8..], + )) + } + Self::Before => { + let value = data.pread_with::(0, scroll::BE)?; + + Ok(( + CodeRequirementMatchExpression::Before( + chrono::Utc + .timestamp_opt(value, 0) + .single() + .ok_or(AppleCodesignError::BadTime)?, + ), + &data[8..], + )) + } + Self::After => { + let value = data.pread_with::(0, scroll::BE)?; + + Ok(( + CodeRequirementMatchExpression::After( + chrono::Utc + .timestamp_opt(value, 0) + .single() + .ok_or(AppleCodesignError::BadTime)?, + ), + &data[8..], + )) + } + Self::OnOrBefore => { + let value = data.pread_with::(0, scroll::BE)?; + + Ok(( + CodeRequirementMatchExpression::OnOrBefore( + chrono::Utc + .timestamp_opt(value, 0) + .single() + .ok_or(AppleCodesignError::BadTime)?, + ), + &data[8..], + )) + } + Self::OnOrAfter => { + let value = data.pread_with::(0, scroll::BE)?; + + Ok(( + CodeRequirementMatchExpression::OnOrAfter( + chrono::Utc + .timestamp_opt(value, 0) + .single() + .ok_or(AppleCodesignError::BadTime)?, + ), + &data[8..], + )) + } + Self::Absent => Ok((CodeRequirementMatchExpression::Absent, data)), + } + } +} + +/// An instance of a match expression in a [CodeRequirementExpression]. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum CodeRequirementMatchExpression<'a> { + /// Entity exists. + /// + /// `exists` + /// + /// No payload. + Exists, + + /// Equality. + /// + /// `= ` + /// + /// 4 bytes length, raw data. + Equal(CodeRequirementValue<'a>), + + /// Contains. + /// + /// `~ ` + /// + /// 4 bytes length, raw data. + Contains(CodeRequirementValue<'a>), + + /// Begins with. + /// + /// `= *` + /// + /// 4 bytes length, raw data. + BeginsWith(CodeRequirementValue<'a>), + + /// Ends with. + /// + /// `= *` + /// + /// 4 bytes length, raw data. + EndsWith(CodeRequirementValue<'a>), + + /// Less than. + /// + /// `< ` + /// + /// 4 bytes length, raw data. + LessThan(CodeRequirementValue<'a>), + + /// Greater than. + /// + /// `> ` + GreaterThan(CodeRequirementValue<'a>), + + /// Less than or equal to. + /// + /// `<= ` + /// + /// 4 bytes length, raw data. + LessThanEqual(CodeRequirementValue<'a>), + + /// Greater than or equal to. + /// + /// `>= ` + /// + /// 4 bytes length, raw data. + GreaterThanEqual(CodeRequirementValue<'a>), + + /// Timestamp value equivalent. + /// + /// `= timestamp ""` + On(chrono::DateTime), + + /// Timestamp value before. + /// + /// `< timestamp ""` + Before(chrono::DateTime), + + /// Timestamp value after. + /// + /// `> timestamp ""` + After(chrono::DateTime), + + /// Timestamp value equivalent or before. + /// + /// `<= timestamp ""` + OnOrBefore(chrono::DateTime), + + /// Timestamp value equivalent or after. + /// + /// `>= timestamp ""` + OnOrAfter(chrono::DateTime), + + /// Value is absent. + /// + /// `` + /// + /// No payload. + Absent, +} + +impl<'a> Display for CodeRequirementMatchExpression<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Exists => f.write_str("/* exists */"), + Self::Equal(value) => f.write_fmt(format_args!("= \"{value}\"")), + Self::Contains(value) => f.write_fmt(format_args!("~ \"{value}\"")), + Self::BeginsWith(value) => f.write_fmt(format_args!("= \"{value}*\"")), + Self::EndsWith(value) => f.write_fmt(format_args!("= \"*{value}\"")), + Self::LessThan(value) => f.write_fmt(format_args!("< \"{value}\"")), + Self::GreaterThan(value) => f.write_fmt(format_args!("> \"{value}\"")), + Self::LessThanEqual(value) => f.write_fmt(format_args!("<= \"{value}\"")), + Self::GreaterThanEqual(value) => f.write_fmt(format_args!(">= \"{value}\"")), + Self::On(value) => f.write_fmt(format_args!("= \"{value}\"")), + Self::Before(value) => f.write_fmt(format_args!("< \"{value}\"")), + Self::After(value) => f.write_fmt(format_args!("> \"{value}\"")), + Self::OnOrBefore(value) => f.write_fmt(format_args!("<= \"{value}\"")), + Self::OnOrAfter(value) => f.write_fmt(format_args!(">= \"{value}\"")), + Self::Absent => f.write_str("absent"), + } + } +} + +impl<'a> From<&CodeRequirementMatchExpression<'a>> for MatchType { + fn from(m: &CodeRequirementMatchExpression<'a>) -> Self { + match m { + CodeRequirementMatchExpression::Exists => MatchType::Exists, + CodeRequirementMatchExpression::Equal(_) => MatchType::Equal, + CodeRequirementMatchExpression::Contains(_) => MatchType::Contains, + CodeRequirementMatchExpression::BeginsWith(_) => MatchType::BeginsWith, + CodeRequirementMatchExpression::EndsWith(_) => MatchType::EndsWith, + CodeRequirementMatchExpression::LessThan(_) => MatchType::LessThan, + CodeRequirementMatchExpression::GreaterThan(_) => MatchType::GreaterThan, + CodeRequirementMatchExpression::LessThanEqual(_) => MatchType::LessThanEqual, + CodeRequirementMatchExpression::GreaterThanEqual(_) => MatchType::GreaterThanEqual, + CodeRequirementMatchExpression::On(_) => MatchType::On, + CodeRequirementMatchExpression::Before(_) => MatchType::Before, + CodeRequirementMatchExpression::After(_) => MatchType::After, + CodeRequirementMatchExpression::OnOrBefore(_) => MatchType::OnOrBefore, + CodeRequirementMatchExpression::OnOrAfter(_) => MatchType::OnOrAfter, + CodeRequirementMatchExpression::Absent => MatchType::Absent, + } + } +} + +impl<'a> CodeRequirementMatchExpression<'a> { + /// Parse a match expression from bytes. + /// + /// The slice should begin with the match type u32. + pub fn from_bytes(data: &'a [u8]) -> Result<(Self, &'a [u8]), AppleCodesignError> { + let typ = data.pread_with::(0, scroll::BE)?; + + let typ = MatchType::try_from(typ)?; + + typ.parse_payload(&data[4..]) + } + + /// Write binary representation of this match expression to a destination. + pub fn write_to(&self, dest: &mut impl Write) -> Result<(), AppleCodesignError> { + dest.iowrite_with(MatchType::from(self) as u32, scroll::BE)?; + + match self { + Self::Exists => {} + Self::Equal(value) => value.write_encoded(dest)?, + Self::Contains(value) => value.write_encoded(dest)?, + Self::BeginsWith(value) => value.write_encoded(dest)?, + Self::EndsWith(value) => value.write_encoded(dest)?, + Self::LessThan(value) => value.write_encoded(dest)?, + Self::GreaterThan(value) => value.write_encoded(dest)?, + Self::LessThanEqual(value) => value.write_encoded(dest)?, + Self::GreaterThanEqual(value) => value.write_encoded(dest)?, + Self::On(value) => dest.iowrite_with(value.timestamp(), scroll::BE)?, + Self::Before(value) => dest.iowrite_with(value.timestamp(), scroll::BE)?, + Self::After(value) => dest.iowrite_with(value.timestamp(), scroll::BE)?, + Self::OnOrBefore(value) => dest.iowrite_with(value.timestamp(), scroll::BE)?, + Self::OnOrAfter(value) => dest.iowrite_with(value.timestamp(), scroll::BE)?, + Self::Absent => {} + } + + Ok(()) + } +} + +/// Represents a series of [CodeRequirementExpression]. +#[derive(Clone, Debug, Default, PartialEq)] +pub struct CodeRequirements<'a>(Vec>); + +impl<'a> Deref for CodeRequirements<'a> { + type Target = Vec>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a> DerefMut for CodeRequirements<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<'a> Display for CodeRequirements<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for (i, expr) in self.0.iter().enumerate() { + f.write_fmt(format_args!("{i}: {expr};"))?; + } + + Ok(()) + } +} + +impl<'a> From>> for CodeRequirements<'a> { + fn from(v: Vec>) -> Self { + Self(v) + } +} + +impl<'a> CodeRequirements<'a> { + /// Parse the binary serialization of code requirements. + /// + /// This parses the data that follows the requirement blob header/magic that + /// usually accompanies the binary representation of code requirements. + pub fn parse_binary(data: &'a [u8]) -> Result<(Self, &'a [u8]), AppleCodesignError> { + let count = data.pread_with::(0, scroll::BE)?; + let mut data = &data[4..]; + + let mut elements = Vec::with_capacity(count as usize); + + for _ in 0..count { + let res = CodeRequirementExpression::from_bytes(data)?; + + elements.push(res.0); + data = res.1; + } + + Ok((Self(elements), data)) + } + + /// Parse a code requirement blob, which begins with header magic. + /// + /// This can be used to parse the output generated by `csreq -b`. + pub fn parse_blob(data: &'a [u8]) -> Result<(Self, &'a [u8]), AppleCodesignError> { + let data = read_and_validate_blob_header( + data, + u32::from(CodeSigningMagic::Requirement), + "code requirement blob", + ) + .map_err(|_| AppleCodesignError::RequirementMalformed("blob header"))?; + + Self::parse_binary(data) + } + + /// Write binary representation of these expressions to a destination. + /// + /// The blob header/magic is not written. + pub fn write_to(&self, dest: &mut impl Write) -> Result<(), AppleCodesignError> { + dest.iowrite_with(self.0.len() as u32, scroll::BE)?; + for e in &self.0 { + e.write_to(dest)?; + } + + Ok(()) + } + + /// Obtain the blob representation of these expressions. + /// + /// This is like [CodeRequirements.write_to] except it will return an owned Vec + /// and will prepend the blob header identifying the data as code requirements. + /// + /// The generated data should be equivalent to what `csreq -b` would produce. + pub fn to_blob_data(&self) -> Result, AppleCodesignError> { + let mut payload = vec![]; + self.write_to(&mut payload)?; + + let mut dest = Vec::with_capacity(payload.len() + 8); + dest.iowrite_with(u32::from(CodeSigningMagic::Requirement), scroll::BE)?; + dest.iowrite_with(dest.capacity() as u32, scroll::BE)?; + dest.write_all(&payload)?; + + Ok(dest) + } + + /// Have this instance occupy a slot in a [RequirementSetBlob] instance. + pub fn add_to_requirement_set( + &self, + requirements_set: &mut RequirementSetBlob, + slot: RequirementType, + ) -> Result<(), AppleCodesignError> { + let blob = RequirementBlob::try_from(self)?; + + requirements_set.set_requirements(slot, blob); + + Ok(()) + } +} + +impl<'a> TryFrom<&CodeRequirements<'a>> for RequirementBlob<'static> { + type Error = AppleCodesignError; + + fn try_from(requirements: &CodeRequirements<'a>) -> Result { + let mut data = Vec::::new(); + requirements.write_to(&mut data)?; + + Ok(Self { + data: Cow::Owned(data), + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + + fn verify_roundtrip(reqs: &CodeRequirements, source: &[u8]) { + let mut dest = Vec::::new(); + reqs.write_to(&mut dest).unwrap(); + assert_eq!(dest.as_slice(), source); + } + + #[test] + fn parse_false() { + let source = hex::decode("0000000100000000").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::False]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_true() { + let source = hex::decode("0000000100000001").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!(els, CodeRequirements(vec![CodeRequirementExpression::True])); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_identifier() { + let source = hex::decode("000000010000000200000007666f6f2e62617200").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::Identifier( + "foo.bar".into() + )]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_anchor_apple() { + let source = hex::decode("0000000100000003").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::AnchorApple]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_anchor_certificate_hash() { + let source = + hex::decode("0000000100000004ffffffff00000014deadbeefdeadbeefdeadbeefdeadbeefdeadbeef") + .unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::AnchorCertificateHash( + -1, + hex::decode("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef") + .unwrap() + .into() + )]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_and() { + let source = hex::decode("00000001000000060000000100000000").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::And( + Box::new(CodeRequirementExpression::True), + Box::new(CodeRequirementExpression::False) + )]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_or() { + let source = hex::decode("00000001000000070000000100000000").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::Or( + Box::new(CodeRequirementExpression::True), + Box::new(CodeRequirementExpression::False) + )]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_code_directory_hash() { + let source = + hex::decode("000000010000000800000014deadbeefdeadbeefdeadbeefdeadbeefdeadbeef") + .unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::CodeDirectoryHash( + hex::decode("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef") + .unwrap() + .into() + )]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_not() { + let source = hex::decode("000000010000000900000001").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::Not(Box::new( + CodeRequirementExpression::True + ))]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_info_plist_key_field() { + let source = hex::decode("000000010000000a000000036b65790000000000").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField( + "key".into(), + CodeRequirementMatchExpression::Exists + )]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_certificate_field() { + let source = + hex::decode("000000010000000bffffffff0000000a7375626a6563742e434e000000000000") + .unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::CertificateField( + -1, + "subject.CN".into(), + CodeRequirementMatchExpression::Exists + )]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_certificate_trusted() { + let source = hex::decode("000000010000000cffffffff").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::CertificateTrusted(-1)]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_anchor_trusted() { + let source = hex::decode("000000010000000d").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::AnchorTrusted]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_certificate_generic() { + let source = hex::decode("000000010000000effffffff000000035504030000000000").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::CertificateGeneric( + -1, + Oid(&[0x55, 4, 3]), + CodeRequirementMatchExpression::Exists + )]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_anchor_apple_generic() { + let source = hex::decode("000000010000000f").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::AnchorAppleGeneric]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_entitlements_key() { + let source = hex::decode("0000000100000010000000036b65790000000000").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::EntitlementsKey( + "key".into(), + CodeRequirementMatchExpression::Exists + )]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_certificate_policy() { + let source = hex::decode("0000000100000011ffffffff000000035504030000000000").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::CertificatePolicy( + -1, + Oid(&[0x55, 4, 3]), + CodeRequirementMatchExpression::Exists + )]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_named_anchor() { + let source = hex::decode("000000010000001200000003666f6f00").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::NamedAnchor("foo".into())]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_named_code() { + let source = hex::decode("000000010000001300000003666f6f00").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::NamedCode("foo".into())]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_platform() { + let source = hex::decode("00000001000000140000000a").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::Platform(10)]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_notarized() { + let source = hex::decode("0000000100000015").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::Notarized]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_certificate_field_date() { + let source = hex::decode("0000000100000016ffffffff000000035504030000000000").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::CertificateFieldDate( + -1, + Oid(&[0x55, 4, 3]), + CodeRequirementMatchExpression::Exists, + )]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_legacy() { + let source = hex::decode("0000000100000017").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::LegacyDeveloperId]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_blob() { + let source = hex::decode("fade0c00000000100000000100000000").unwrap(); + + let (els, data) = CodeRequirements::parse_blob(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::False]) + ); + assert!(data.is_empty()); + + let dest = els.to_blob_data().unwrap(); + assert_eq!(source, dest); + } + + #[test] + fn parse_match_exists() { + let source = hex::decode("000000010000000a000000036b65790000000000").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField( + "key".into(), + CodeRequirementMatchExpression::Exists + )]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_match_absent() { + let source = hex::decode("000000010000000a000000036b6579000000000e").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField( + "key".into(), + CodeRequirementMatchExpression::Absent + )]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_match_equal() { + let source = + hex::decode("000000010000000a000000036b657900000000010000000576616c7565000000") + .unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField( + "key".into(), + CodeRequirementMatchExpression::Equal(b"value".as_ref().into()) + )]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_match_contains() { + let source = + hex::decode("000000010000000a000000036b657900000000020000000576616c7565000000") + .unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField( + "key".into(), + CodeRequirementMatchExpression::Contains(b"value".as_ref().into()) + )]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_match_begins_with() { + let source = + hex::decode("000000010000000a000000036b657900000000030000000576616c7565000000") + .unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField( + "key".into(), + CodeRequirementMatchExpression::BeginsWith(b"value".as_ref().into()) + )]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_match_ends_with() { + let source = + hex::decode("000000010000000a000000036b657900000000040000000576616c7565000000") + .unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField( + "key".into(), + CodeRequirementMatchExpression::EndsWith(b"value".as_ref().into()) + )]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_match_less_than() { + let source = + hex::decode("000000010000000a000000036b657900000000050000000576616c7565000000") + .unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField( + "key".into(), + CodeRequirementMatchExpression::LessThan(b"value".as_ref().into()) + )]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_match_greater_than() { + let source = + hex::decode("000000010000000a000000036b657900000000060000000576616c7565000000") + .unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField( + "key".into(), + CodeRequirementMatchExpression::GreaterThan(b"value".as_ref().into()) + )]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_match_less_than_equal() { + let source = + hex::decode("000000010000000a000000036b657900000000070000000576616c7565000000") + .unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField( + "key".into(), + CodeRequirementMatchExpression::LessThanEqual(b"value".as_ref().into()) + )]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_match_greater_than_equal() { + let source = + hex::decode("000000010000000a000000036b657900000000080000000576616c7565000000") + .unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField( + "key".into(), + CodeRequirementMatchExpression::GreaterThanEqual(b"value".as_ref().into()) + )]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_match_on() { + let source = + hex::decode("000000010000000a000000036b6579000000000900000000605fca30").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField( + "key".into(), + CodeRequirementMatchExpression::On( + chrono::Utc.timestamp_opt(1616890416, 0).unwrap() + ), + )]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_match_before() { + let source = + hex::decode("000000010000000a000000036b6579000000000a00000000605fca30").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField( + "key".into(), + CodeRequirementMatchExpression::Before( + chrono::Utc.timestamp_opt(1616890416, 0).unwrap() + ), + )]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_match_after() { + let source = + hex::decode("000000010000000a000000036b6579000000000b00000000605fca30").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField( + "key".into(), + CodeRequirementMatchExpression::After( + chrono::Utc.timestamp_opt(1616890416, 0).unwrap() + ), + )]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_match_on_or_before() { + let source = + hex::decode("000000010000000a000000036b6579000000000c00000000605fca30").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField( + "key".into(), + CodeRequirementMatchExpression::OnOrBefore( + chrono::Utc.timestamp_opt(1616890416, 0).unwrap() + ), + )]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } + + #[test] + fn parse_match_on_or_after() { + let source = + hex::decode("000000010000000a000000036b6579000000000d00000000605fca30").unwrap(); + + let (els, data) = CodeRequirements::parse_binary(&source).unwrap(); + + assert_eq!( + els, + CodeRequirements(vec![CodeRequirementExpression::InfoPlistKeyField( + "key".into(), + CodeRequirementMatchExpression::OnOrAfter( + chrono::Utc.timestamp_opt(1616890416, 0).unwrap() + ), + )]) + ); + assert!(data.is_empty()); + verify_roundtrip(&els, &source); + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/code_resources.rs b/3rdparty/apple-codesign-0.29.0/src/code_resources.rs new file mode 100644 index 00000000..514e78a7 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/code_resources.rs @@ -0,0 +1,1577 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Functionality related to "code resources," external resources captured in signatures. +//! +//! Bundles can contain a `_CodeSignature/CodeResources` XML plist file +//! denoting signatures for resources not in the binary. The signature data +//! in the binary can record the digest of this file so integrity is transitively +//! verified. +//! +//! We've implemented our own (de)serialization code in this module because +//! the default derived Deserialize provided by the `plist` crate doesn't +//! handle enums correctly. We attempted to implement our own `Deserialize` +//! and `Visitor` traits to get things to parse, but we couldn't make it work. +//! We gave up and decided to just coerce the [plist::Value] instances instead. + +use { + crate::{ + bundle_signing::{BundleSigningContext, SignedMachOInfo}, + cryptography::{DigestType, MultiDigest}, + error::AppleCodesignError, + }, + apple_bundles::DirectoryBundle, + log::{debug, error, info, warn}, + plist::{Dictionary, Value}, + std::{ + cmp::Ordering, + collections::{BTreeMap, BTreeSet}, + io::Write, + path::Path, + }, +}; + +#[derive(Clone, PartialEq)] +enum FilesValue { + Required(Vec), + Optional(Vec), +} + +impl std::fmt::Debug for FilesValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Required(digest) => f + .debug_struct("FilesValue") + .field("required", &true) + .field("digest", &hex::encode(digest)) + .finish(), + Self::Optional(digest) => f + .debug_struct("FilesValue") + .field("required", &false) + .field("digest", &hex::encode(digest)) + .finish(), + } + } +} + +impl std::fmt::Display for FilesValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Required(digest) => { + f.write_fmt(format_args!("{} (required)", hex::encode(digest))) + } + Self::Optional(digest) => { + f.write_fmt(format_args!("{} (optional)", hex::encode(digest))) + } + } + } +} + +impl TryFrom<&Value> for FilesValue { + type Error = AppleCodesignError; + + fn try_from(v: &Value) -> Result { + match v { + Value::Data(digest) => Ok(Self::Required(digest.to_vec())), + Value::Dictionary(dict) => { + let mut digest = None; + let mut optional = None; + + for (key, value) in dict.iter() { + match key.as_str() { + "hash" => { + let data = value.as_data().ok_or_else(|| { + AppleCodesignError::ResourcesPlistParse(format!( + "expected for files entry, got {value:?}" + )) + })?; + + digest = Some(data.to_vec()); + } + "optional" => { + let v = value.as_boolean().ok_or_else(|| { + AppleCodesignError::ResourcesPlistParse(format!( + "expected boolean for optional key, got {value:?}" + )) + })?; + + optional = Some(v); + } + key => { + return Err(AppleCodesignError::ResourcesPlistParse(format!( + "unexpected key in files dict: {key}" + ))); + } + } + } + + match (digest, optional) { + (Some(digest), Some(true)) => Ok(Self::Optional(digest)), + (Some(digest), Some(false)) => Ok(Self::Required(digest)), + _ => Err(AppleCodesignError::ResourcesPlistParse( + "missing hash or optional key".to_string(), + )), + } + } + _ => Err(AppleCodesignError::ResourcesPlistParse(format!( + "bad value in files ; expected or , got {v:?}" + ))), + } + } +} + +impl From<&FilesValue> for Value { + fn from(v: &FilesValue) -> Self { + match v { + FilesValue::Required(digest) => Self::Data(digest.to_vec()), + FilesValue::Optional(digest) => { + let mut dict = Dictionary::new(); + dict.insert("hash".to_string(), Value::Data(digest.to_vec())); + dict.insert("optional".to_string(), Value::Boolean(true)); + + Self::Dictionary(dict) + } + } + } +} + +#[derive(Clone, PartialEq)] +struct Files2Value { + cdhash: Option>, + hash: Option>, + hash2: Option>, + optional: Option, + requirement: Option, + symlink: Option, +} + +impl std::fmt::Debug for Files2Value { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Files2Value") + .field( + "cdhash", + &format_args!("{:?}", self.cdhash.as_ref().map(hex::encode)), + ) + .field( + "hash", + &format_args!("{:?}", self.hash.as_ref().map(hex::encode)), + ) + .field( + "hash2", + &format_args!("{:?}", self.hash2.as_ref().map(hex::encode)), + ) + .field("optional", &format_args!("{:?}", self.optional)) + .field("requirement", &format_args!("{:?}", self.requirement)) + .field("symlink", &format_args!("{:?}", self.symlink)) + .finish() + } +} + +impl TryFrom<&Value> for Files2Value { + type Error = AppleCodesignError; + + fn try_from(v: &Value) -> Result { + let dict = v.as_dictionary().ok_or_else(|| { + AppleCodesignError::ResourcesPlistParse("files2 value should be a dict".to_string()) + })?; + + let mut hash = None; + let mut hash2 = None; + let mut cdhash = None; + let mut optional = None; + let mut requirement = None; + let mut symlink = None; + + for (key, value) in dict.iter() { + match key.as_str() { + "cdhash" => { + let data = value.as_data().ok_or_else(|| { + AppleCodesignError::ResourcesPlistParse(format!( + "expected for files2 cdhash entry, got {value:?}" + )) + })?; + + cdhash = Some(data.to_vec()); + } + "hash" => { + let data = value.as_data().ok_or_else(|| { + AppleCodesignError::ResourcesPlistParse(format!( + "expected for files2 hash entry, got {value:?}" + )) + })?; + + hash = Some(data.to_vec()); + } + "hash2" => { + let data = value.as_data().ok_or_else(|| { + AppleCodesignError::ResourcesPlistParse(format!( + "expected for files2 hash2 entry, got {value:?}" + )) + })?; + + hash2 = Some(data.to_vec()); + } + "optional" => { + let v = value.as_boolean().ok_or_else(|| { + AppleCodesignError::ResourcesPlistParse(format!( + "expected bool for optional key, got {value:?}" + )) + })?; + + optional = Some(v); + } + "requirement" => { + let v = value.as_string().ok_or_else(|| { + AppleCodesignError::ResourcesPlistParse(format!( + "expected string for requirement key, got {value:?}" + )) + })?; + + requirement = Some(v.to_string()); + } + "symlink" => { + symlink = Some( + value + .as_string() + .ok_or_else(|| { + AppleCodesignError::ResourcesPlistParse(format!( + "expected string for symlink key, got {value:?}" + )) + })? + .to_string(), + ); + } + key => { + return Err(AppleCodesignError::ResourcesPlistParse(format!( + "unexpected key in files2 dict entry: {key}" + ))); + } + } + } + + Ok(Self { + cdhash, + hash, + hash2, + optional, + requirement, + symlink, + }) + } +} + +impl From<&Files2Value> for Value { + fn from(v: &Files2Value) -> Self { + let mut dict = Dictionary::new(); + + if let Some(cdhash) = &v.cdhash { + dict.insert("cdhash".to_string(), Value::Data(cdhash.to_vec())); + } + + if let Some(hash) = &v.hash { + dict.insert("hash".to_string(), Value::Data(hash.to_vec())); + } + + if let Some(hash2) = &v.hash2 { + dict.insert("hash2".to_string(), Value::Data(hash2.to_vec())); + } + + if let Some(optional) = &v.optional { + dict.insert("optional".to_string(), Value::Boolean(*optional)); + } + + if let Some(requirement) = &v.requirement { + dict.insert( + "requirement".to_string(), + Value::String(requirement.to_string()), + ); + } + + if let Some(symlink) = &v.symlink { + dict.insert("symlink".to_string(), Value::String(symlink.to_string())); + } + + Value::Dictionary(dict) + } +} + +#[derive(Clone, Debug, PartialEq)] +struct RulesValue { + omit: bool, + required: bool, + weight: Option, +} + +impl TryFrom<&Value> for RulesValue { + type Error = AppleCodesignError; + + fn try_from(v: &Value) -> Result { + match v { + Value::Boolean(true) => Ok(Self { + omit: false, + required: true, + weight: None, + }), + Value::Dictionary(dict) => { + let mut omit = None; + let mut optional = None; + let mut weight = None; + + for (key, value) in dict { + match key.as_str() { + "omit" => { + omit = Some(value.as_boolean().ok_or_else(|| { + AppleCodesignError::ResourcesPlistParse(format!( + "rules omit key value not a boolean; got {value:?}" + )) + })?); + } + "optional" => { + optional = Some(value.as_boolean().ok_or_else(|| { + AppleCodesignError::ResourcesPlistParse(format!( + "rules optional key value not a boolean, got {value:?}" + )) + })?); + } + "weight" => { + weight = Some(value.as_real().ok_or_else(|| { + AppleCodesignError::ResourcesPlistParse(format!( + "rules weight key value not a real, got {value:?}" + )) + })?); + } + key => { + return Err(AppleCodesignError::ResourcesPlistParse(format!( + "extra key in rules dict: {key}" + ))); + } + } + } + + Ok(Self { + omit: omit.unwrap_or(false), + required: !optional.unwrap_or(false), + weight, + }) + } + _ => Err(AppleCodesignError::ResourcesPlistParse( + "invalid value for rules entry".to_string(), + )), + } + } +} + +impl From<&RulesValue> for Value { + fn from(v: &RulesValue) -> Self { + if v.required && !v.omit && v.weight.is_none() { + Value::Boolean(true) + } else { + let mut dict = Dictionary::new(); + + if v.omit { + dict.insert("omit".to_string(), Value::Boolean(true)); + } + if !v.required { + dict.insert("optional".to_string(), Value::Boolean(true)); + } + + if let Some(weight) = v.weight { + dict.insert("weight".to_string(), Value::Real(weight)); + } + + Value::Dictionary(dict) + } + } +} + +#[derive(Clone, Debug, PartialEq)] +struct Rules2Value { + nested: Option, + omit: Option, + optional: Option, + weight: Option, +} + +impl TryFrom<&Value> for Rules2Value { + type Error = AppleCodesignError; + + fn try_from(v: &Value) -> Result { + let dict = v.as_dictionary().ok_or_else(|| { + AppleCodesignError::ResourcesPlistParse("rules2 value should be a dict".to_string()) + })?; + + let mut nested = None; + let mut omit = None; + let mut optional = None; + let mut weight = None; + + for (key, value) in dict.iter() { + match key.as_str() { + "nested" => { + nested = Some(value.as_boolean().ok_or_else(|| { + AppleCodesignError::ResourcesPlistParse(format!( + "expected bool for rules2 nested key, got {value:?}" + )) + })?); + } + "omit" => { + omit = Some(value.as_boolean().ok_or_else(|| { + AppleCodesignError::ResourcesPlistParse(format!( + "expected bool for rules2 omit key, got {value:?}" + )) + })?); + } + "optional" => { + optional = Some(value.as_boolean().ok_or_else(|| { + AppleCodesignError::ResourcesPlistParse(format!( + "expected bool for rules2 optional key, got {value:?}" + )) + })?); + } + "weight" => { + weight = Some(value.as_real().ok_or_else(|| { + AppleCodesignError::ResourcesPlistParse(format!( + "expected real for rules2 weight key, got {value:?}" + )) + })?); + } + key => { + return Err(AppleCodesignError::ResourcesPlistParse(format!( + "unexpected key in rules dict entry: {key}" + ))); + } + } + } + + Ok(Self { + nested, + omit, + optional, + weight, + }) + } +} + +impl From<&Rules2Value> for Value { + fn from(v: &Rules2Value) -> Self { + let mut dict = Dictionary::new(); + + if let Some(true) = v.nested { + dict.insert("nested".to_string(), Value::Boolean(true)); + } + + if let Some(true) = v.omit { + dict.insert("omit".to_string(), Value::Boolean(true)); + } + + if let Some(true) = v.optional { + dict.insert("optional".to_string(), Value::Boolean(true)); + } + + if let Some(weight) = v.weight { + dict.insert("weight".to_string(), Value::Real(weight)); + } + + if dict.is_empty() { + Value::Boolean(true) + } else { + Value::Dictionary(dict) + } + } +} + +/// Represents an abstract rule in a `CodeResources` XML plist. +/// +/// This type represents both `` and `` entries. It contains a +/// superset of all fields for these entries. +#[derive(Clone, Debug)] +pub struct CodeResourcesRule { + /// The rule pattern. + /// + /// The `` in the `` or `` dict. + pub pattern: String, + + /// Matched paths are excluded from processing completely. + /// + /// If any rule with this flag matches a path, the path is excluded. + pub exclude: bool, + + /// The matched path is a signable entity. + /// + /// The path should be signed before sealing. And its seal may be + /// stored specially. + pub nested: bool, + + /// Whether to omit the path from sealing. + /// + /// Paths matching this rule can exist in a bundle. But their content + /// isn't captured in the `CodeResources` file. + pub omit: bool, + + /// Unknown. Best guess is whether the file's presence is optional. + pub optional: bool, + + /// Weighting to apply to the rule. + pub weight: Option, + + re: regex::Regex, +} + +impl PartialEq for CodeResourcesRule { + fn eq(&self, other: &Self) -> bool { + self.pattern == other.pattern + && self.exclude == other.exclude + && self.nested == other.nested + && self.omit == other.omit + && self.optional == other.optional + && self.weight == other.weight + } +} + +impl Eq for CodeResourcesRule {} + +impl PartialOrd for CodeResourcesRule { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for CodeResourcesRule { + fn cmp(&self, other: &Self) -> Ordering { + // Default weight is 1 if not specified. + let our_weight = self.weight.unwrap_or(1); + let their_weight = other.weight.unwrap_or(1); + + // Exclusion rules always take priority over inclusion rules. + // The smaller the weight, the less important it is. + match (self.exclude, other.exclude) { + (true, false) => Ordering::Less, + (false, true) => Ordering::Greater, + _ => their_weight.cmp(&our_weight), + } + } +} + +impl CodeResourcesRule { + pub fn new(pattern: impl ToString) -> Result { + Ok(Self { + pattern: pattern.to_string(), + exclude: false, + nested: false, + omit: false, + optional: false, + weight: None, + re: regex::Regex::new(&pattern.to_string()) + .map_err(|e| AppleCodesignError::ResourcesBadRegex(pattern.to_string(), e))?, + }) + } + + /// Mark this as an exclusion rule. + /// + /// Exclusion rules are internal to the builder and not materialized in the + /// `CodeResources` file. + #[must_use] + pub fn exclude(mut self) -> Self { + self.exclude = true; + self + } + + /// Mark the rule as nested. + #[must_use] + pub fn nested(mut self) -> Self { + self.nested = true; + self + } + + /// Set the omit field. + #[must_use] + pub fn omit(mut self) -> Self { + self.omit = true; + self + } + + /// Mark the files matched by this rule are optional. + #[must_use] + pub fn optional(mut self) -> Self { + self.optional = true; + self + } + + /// Set the weight of this rule. + #[must_use] + pub fn weight(mut self, v: u32) -> Self { + self.weight = Some(v); + self + } +} + +/// Which files section we are operating on and how to digest. +#[derive(Clone, Copy, Debug)] +pub enum FilesFlavor { + /// ``. + Rules, + /// ``. + Rules2, + /// `` and also include the SHA-1 digest. + Rules2WithSha1, +} + +/// Represents a `_CodeSignature/CodeResources` XML plist. +/// +/// This file/type represents a collection of file-based resources whose +/// content is digested and captured in this file. +#[derive(Clone, Debug, Default, PartialEq)] +pub struct CodeResources { + files: BTreeMap, + files2: BTreeMap, + rules: BTreeMap, + rules2: BTreeMap, +} + +impl CodeResources { + /// Construct an instance by parsing an XML plist. + pub fn from_xml(xml: &[u8]) -> Result { + let plist = Value::from_reader_xml(xml).map_err(AppleCodesignError::ResourcesPlist)?; + + let dict = plist.into_dictionary().ok_or_else(|| { + AppleCodesignError::ResourcesPlistParse( + "plist root element should be a ".to_string(), + ) + })?; + + let mut files = BTreeMap::new(); + let mut files2 = BTreeMap::new(); + let mut rules = BTreeMap::new(); + let mut rules2 = BTreeMap::new(); + + for (key, value) in dict.iter() { + match key.as_ref() { + "files" => { + let dict = value.as_dictionary().ok_or_else(|| { + AppleCodesignError::ResourcesPlistParse(format!( + "expecting files to be a dict, got {value:?}" + )) + })?; + + for (key, value) in dict { + files.insert(key.to_string(), FilesValue::try_from(value)?); + } + } + "files2" => { + let dict = value.as_dictionary().ok_or_else(|| { + AppleCodesignError::ResourcesPlistParse(format!( + "expecting files2 to be a dict, got {value:?}" + )) + })?; + + for (key, value) in dict { + files2.insert(key.to_string(), Files2Value::try_from(value)?); + } + } + "rules" => { + let dict = value.as_dictionary().ok_or_else(|| { + AppleCodesignError::ResourcesPlistParse(format!( + "expecting rules to be a dict, got {value:?}" + )) + })?; + + for (key, value) in dict { + rules.insert(key.to_string(), RulesValue::try_from(value)?); + } + } + "rules2" => { + let dict = value.as_dictionary().ok_or_else(|| { + AppleCodesignError::ResourcesPlistParse(format!( + "expecting rules2 to be a dict, got {value:?}" + )) + })?; + + for (key, value) in dict { + rules2.insert(key.to_string(), Rules2Value::try_from(value)?); + } + } + key => { + return Err(AppleCodesignError::ResourcesPlistParse(format!( + "unexpected key in root dict: {key}" + ))); + } + } + } + + Ok(Self { + files, + files2, + rules, + rules2, + }) + } + + /// Serialize an instance to XML. + pub fn to_writer_xml(&self, mut writer: impl Write) -> Result<(), AppleCodesignError> { + let value = Value::from(self); + + // Ideally we'd write direct to the output. However, Apple's XML writer doesn't + // emit a space for empty elements. e.g. we do `` and Apple does ``. + // In addition, our writer doesn't emit a trailing newline. To make it easier to + // diff generated files with the canonical output, we normalize to Apple's format. + let mut data = Vec::::new(); + value + .to_writer_xml(&mut data) + .map_err(AppleCodesignError::ResourcesPlist)?; + + let data = String::from_utf8(data).expect("XML should be valid UTF-8"); + let data = data.replace("", ""); + let data = data.replace("", ""); + let data = data.replace(""", "\""); + + writer.write_all(data.as_bytes())?; + writer.write_all(b"\n")?; + + Ok(()) + } + + /// Add a rule to this instance in the `` section. + pub fn add_rule(&mut self, rule: CodeResourcesRule) { + self.rules.insert( + rule.pattern, + RulesValue { + omit: rule.omit, + required: !rule.optional, + weight: rule.weight.map(|x| x as f64), + }, + ); + } + + /// Add a rule to this instance in the `` section. + pub fn add_rule2(&mut self, rule: CodeResourcesRule) { + self.rules2.insert( + rule.pattern, + Rules2Value { + nested: if rule.nested { Some(true) } else { None }, + omit: if rule.omit { Some(true) } else { None }, + optional: if rule.optional { Some(true) } else { None }, + weight: rule.weight.map(|x| x as f64), + }, + ); + } + + /// Seal a regular file. + /// + /// This will digest the content specified and record that digest in the files or + /// files2 list. + /// + /// To seal a symlink, call [CodeResources::seal_symlink] instead. If the file + /// is a Mach-O file, call [CodeResources::seal_macho] instead. + pub fn seal_regular_file( + &mut self, + files_flavor: FilesFlavor, + path: impl ToString, + digests: MultiDigest, + optional: bool, + ) -> Result<(), AppleCodesignError> { + match files_flavor { + FilesFlavor::Rules => { + self.files.insert( + path.to_string(), + if optional { + FilesValue::Optional(digests.sha1.to_vec()) + } else { + FilesValue::Required(digests.sha1.to_vec()) + }, + ); + + Ok(()) + } + FilesFlavor::Rules2 => { + let hash2 = Some(digests.sha256.to_vec()); + + self.files2.insert( + path.to_string(), + Files2Value { + cdhash: None, + hash: None, + hash2, + optional: if optional { Some(true) } else { None }, + requirement: None, + symlink: None, + }, + ); + + Ok(()) + } + FilesFlavor::Rules2WithSha1 => { + let hash = Some(digests.sha1.to_vec()); + let hash2 = Some(digests.sha256.to_vec()); + + self.files2.insert( + path.to_string(), + Files2Value { + cdhash: None, + hash, + hash2, + optional: if optional { Some(true) } else { None }, + requirement: None, + symlink: None, + }, + ); + + Ok(()) + } + } + } + + /// Seal a symlink file. + /// + /// `path` is the path of the symlink and `target` is the path it points to. + pub fn seal_symlink(&mut self, path: impl ToString, target: impl ToString) { + // Version 1 doesn't support sealing symlinks. + self.files2.insert( + path.to_string(), + Files2Value { + cdhash: None, + hash: None, + hash2: None, + optional: None, + requirement: None, + symlink: Some(target.to_string()), + }, + ); + } + + /// Record metadata of a previously signed Mach-O binary. + /// + /// If sealing a fat/universal binary, pass in metadata for the first Mach-O within in. + pub fn seal_macho( + &mut self, + path: impl ToString, + info: &SignedMachOInfo, + optional: bool, + ) -> Result<(), AppleCodesignError> { + self.files2.insert( + path.to_string(), + Files2Value { + cdhash: Some(DigestType::Sha256Truncated.digest_data(&info.code_directory_blob)?), + hash: None, + hash2: None, + optional: if optional { Some(true) } else { None }, + requirement: info.designated_code_requirement.clone(), + symlink: None, + }, + ); + + Ok(()) + } +} + +impl From<&CodeResources> for Value { + fn from(cr: &CodeResources) -> Self { + let mut dict = Dictionary::new(); + + dict.insert( + "files".to_string(), + Value::Dictionary( + cr.files + .iter() + .map(|(key, value)| (key.to_string(), Value::from(value))) + .collect::(), + ), + ); + + dict.insert( + "files2".to_string(), + Value::Dictionary( + cr.files2 + .iter() + .map(|(key, value)| (key.to_string(), Value::from(value))) + .collect::(), + ), + ); + + if !cr.rules.is_empty() { + dict.insert( + "rules".to_string(), + Value::Dictionary( + cr.rules + .iter() + .map(|(key, value)| (key.to_string(), Value::from(value))) + .collect::(), + ), + ); + } + + if !cr.rules2.is_empty() { + dict.insert( + "rules2".to_string(), + Value::Dictionary( + cr.rules2 + .iter() + .map(|(key, value)| (key.to_string(), Value::from(value))) + .collect::(), + ), + ); + } + + Value::Dictionary(dict) + } +} + +/// Convert a relative filesystem path to its `CodeResources` normalized form. +pub fn normalized_resources_path(path: impl AsRef) -> String { + // Always use UNIX style directory separators. + let path = path.as_ref().to_string_lossy().replace('\\', "/"); + + // The Contents/ prefix is also removed for pattern matching and references in the + // resources file. + let path = path.strip_prefix("Contents/").unwrap_or(&path).to_string(); + + path +} + +/// Find the first rule matching a given path. +/// +/// Internally, rules are sorted by decreasing priority, with exclusion +/// rules having highest priority. So the first pattern that matches is +/// rule we use. +/// +/// Pattern matches are always against the normalized filename. (e.g. +/// `Contents/` is stripped.) +fn find_rule(rules: &[CodeResourcesRule], path: impl AsRef) -> Option { + let path = normalized_resources_path(path); + rules.iter().find(|rule| rule.re.is_match(&path)).cloned() +} + +/// Interface for constructing a `CodeResources` instance. +/// +/// 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. +#[derive(Clone, Debug)] +pub struct CodeResourcesBuilder { + rules: Vec, + rules2: Vec, + resources: CodeResources, + digests: Vec, +} + +impl Default for CodeResourcesBuilder { + fn default() -> Self { + Self { + rules: vec![], + rules2: vec![], + resources: CodeResources::default(), + digests: vec![DigestType::Sha256], + } + } +} + +impl CodeResourcesBuilder { + /// Obtain an instance with default rules for a bundle with a `Resources/` directory. + pub fn default_resources_rules() -> Result { + 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 { + 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) + } + + /// Set the digests to record in this instance. + pub fn set_digests(&mut self, digests: impl Iterator) { + self.digests = digests.collect::>(); + } + + /// Add a rule to this instance in the `` section. + pub fn add_rule(&mut self, rule: CodeResourcesRule) { + self.rules.push(rule.clone()); + self.rules.sort(); + self.resources.add_rule(rule); + } + + /// Add a rule to this instance in the `` section. + pub fn add_rule2(&mut self, rule: CodeResourcesRule) { + self.rules2.push(rule.clone()); + self.rules2.sort(); + self.resources.add_rule2(rule); + } + + /// Add an exclusion rule to the processing rules. + /// + /// Exclusion rules are not added to the [CodeResources] because they are + /// implicit and used for filesystem traversal to influence which entities + /// are skipped. + pub fn add_exclusion_rule(&mut self, rule: CodeResourcesRule) { + self.rules.push(rule.clone()); + self.rules.sort(); + self.rules2.push(rule); + self.rules2.sort(); + } + + /// Recursively seal a bundle directory. + /// + /// This function does the heavy lifting of walking a bundle directory + /// and sealing the content inside. + /// + /// For each filesystem entry, it finds the most appropriate registered + /// rule that applies to it. Then using that rule it takes actions. + /// + /// Typically, each file entity has its digest recorded/sealed. + /// + /// As a side-effect, files are copied/installed into the destination + /// directory as part of sealing. + pub fn walk_and_seal_directory( + &mut self, + root_bundle_path: &Path, + bundle_root: &Path, + context: &mut BundleSigningContext, + ) -> Result<(), AppleCodesignError> { + let mut skipping_rel_dirs = BTreeSet::new(); + + for entry in walkdir::WalkDir::new(bundle_root).sort_by_file_name() { + let entry = entry?; + let path = entry.path(); + + if path == bundle_root { + continue; + } + + let rel_path = path + .strip_prefix(bundle_root) + .expect("stripping path prefix should always work"); + let root_rel_path_normalized = path + .strip_prefix(root_bundle_path) + .expect("stripping root prefix should always work") + .to_string_lossy() + .replace('\\', "/"); + let rel_path_normalized = normalized_resources_path(rel_path); + + let file_name = rel_path + .file_name() + .expect("should have final path component") + .to_string_lossy() + .to_string(); + + // We're excluding a parent directory. Do nothing. + if skipping_rel_dirs.iter().any(|p| rel_path.starts_with(p)) { + debug!("{} ignored because marked as skipped", rel_path.display()); + continue; + } + + // Rules version 2. + if let Some(rule) = find_rule(&self.rules2, rel_path) { + debug!( + "{}:{} matches rules2 {:?}", + bundle_root.display(), + rel_path.display(), + rule + ); + + if entry.file_type().is_dir() { + if rule.nested { + // Only treat as a nested bundle iff it has a dot in its name. + if file_name.contains('.') { + // We assume the bundle has already been signed because that's + // how our bundle walker works. So all we need to do here is + // seal the bundle. We can skip handling all files in this + // directory since they've already been processed. + self.seal_rules2_nested_bundle( + path, + rel_path, + &rel_path_normalized, + rule.optional, + &context.dest_dir, + )?; + + skipping_rel_dirs.insert(rel_path.to_path_buf()); + } + } else if rule.exclude { + info!( + "{} marked as excluded in resource rules", + rel_path_normalized + ); + skipping_rel_dirs.insert(rel_path.to_path_buf()); + } + + // No need to do anything else since we'll walk into directory + // to handle files. + } else if entry.file_type().is_file() { + if rule.exclude { + debug!("{} ignoring file due to exclude rule", rel_path_normalized); + continue; + } + + // Nested flag means the file should itself be signable. + if rule.nested { + if crate::reader::path_is_macho(path)? { + info!("sealing nested Mach-O binary: {}", rel_path.display()); + + self.seal_rules2_nested_macho( + path, + rel_path, + &rel_path_normalized, + &root_rel_path_normalized, + context, + rule.optional, + )?; + } else { + // TODO implement this? + // The logical intent is to sign and seal the nested entity. + // But if we're not a directory bundle and not a Mach-O, I'm + // unsure how to convey that seal. Maybe other entities like + // DMG and pkg installers can have their signature digest + // encapsulated in a cdhash? + error!( + "encountered a non Mach-O file with a nested rule: {}", + rel_path.display() + ); + error!("we do not know how to handle this scenario; either your bundle layout is invalid or you found a bug in this program"); + error!("if the bundle signs and verifies with Apple's tooling, consider reporting this issue"); + } + } else { + self.seal_rules2_file( + path, + rel_path, + &rel_path_normalized, + &root_rel_path_normalized, + rule.omit, + rule.optional, + context, + )?; + } + } else if entry.file_type().is_symlink() { + if rule.exclude { + info!( + "{} ignoring symlink due to exclude rule", + rel_path_normalized + ); + continue; + } + + self.seal_rules2_symlink( + path, + rel_path, + &rel_path_normalized, + rule.omit, + context, + )?; + } else { + warn!( + "{} unexpected file type encountering during bundle signing", + rel_path_normalized + ); + } + } else { + debug!( + "{}:{} doesn't match any rules2 rule", + bundle_root.display(), + rel_path.display() + ); + } + + // Now rules version 1. Only regular files can be sealed. Version + // 1 does not support nested signatures nor symlinks. + if let Some(rule) = find_rule(&self.rules, rel_path) { + debug!( + "{}:{} matches rules rule {:?}", + bundle_root.display(), + rel_path.display(), + rule + ); + + if entry.file_type().is_file() { + if rule.exclude { + continue; + } + + self.seal_rules1_file(path, &rel_path_normalized, rule)?; + } + } + } + + Ok(()) + } + + /// Seal a nested bundle for rules version 2. + fn seal_rules2_nested_bundle( + &mut self, + full_path: &Path, + rel_path: &Path, + rel_path_normalized: &str, + optional: bool, + dest_dir: &Path, + ) -> Result<(), AppleCodesignError> { + info!( + "sealing nested directory as a bundle: {}", + rel_path.display() + ); + let bundle = DirectoryBundle::new_from_path(full_path)?; + + if let Some(nested_exe) = bundle + .files(false)? + .into_iter() + .find(|f| matches!(f.is_main_executable(), Ok(true))) + { + let nested_exe = dest_dir.join(rel_path).join(nested_exe.relative_path()); + + info!("reading Mach-O signature from {}", nested_exe.display()); + let macho_data = std::fs::read(&nested_exe)?; + let macho_info = SignedMachOInfo::parse_data(&macho_data)?; + + self.resources + .seal_macho(rel_path_normalized, &macho_info, optional)?; + } else { + warn!( + "could not find main executable of presumed nested bundle: {}", + rel_path.display() + ); + } + + Ok(()) + } + + /// Seal a Mach-O binary matching a nested rule. + fn seal_rules2_nested_macho( + &mut self, + full_path: &Path, + rel_path: &Path, + rel_path_normalized: &str, + root_rel_path: &str, + context: &mut BundleSigningContext, + optional: bool, + ) -> Result<(), AppleCodesignError> { + let macho_info = if context + .settings + .path_exclusion_pattern_matches(root_rel_path) + { + warn!( + "skipping signing of nested Mach-O binary because excluded by settings: {}", + rel_path.display() + ); + warn!("(an error will occur if this binary is not already signed)"); + warn!("(if you see an error, sign that Mach-O explicitly or remove it from the exclusion settings)"); + + let dest_path = context.install_file(full_path, rel_path)?; + let data = std::fs::read(dest_path)?; + + SignedMachOInfo::parse_data(&data)? + } else { + context.sign_and_install_macho(full_path, rel_path)?.1 + }; + + self.resources + .seal_macho(rel_path_normalized, &macho_info, optional) + } + + /// Seal a file for version 2 rules. + fn seal_rules2_file( + &mut self, + full_path: &Path, + rel_path: &Path, + rel_path_normalized: &str, + root_rel_path: &str, + omit: bool, + optional: bool, + context: &mut BundleSigningContext, + ) -> Result<(), AppleCodesignError> { + let mut need_install = !context.previously_installed_paths.contains(rel_path); + + // Only seal if the omit flag is unset. + if !omit { + // Unlike Apple's tooling, we recognize Mach-O binaries when the nested + // flag isn't set and we automatically sign. + // + // Unless the path is marked for exclusion or shallow signing mode is + // active. + // + // The reason we exclude in shallow mode is that shallow mode is supposed + // to behave like Apple's `codesign` and that tool only signs the bundle's + // "main" Mach-O binary, not other binaries. + let sign_macho = need_install + && crate::reader::path_is_macho(full_path)? + && !context + .settings + .path_exclusion_pattern_matches(root_rel_path) + && !context.settings.shallow(); + + let read_path = if sign_macho { + info!( + "non-nested file is a Mach-O binary; signing accordingly {}", + rel_path.display() + ); + need_install = false; + // We need to read the signed/installed version of the file since + // signing will change its content. + context.sign_and_install_macho(full_path, rel_path)?.0 + } else { + info!("sealing regular file {}", rel_path_normalized); + + // If we need to install the file, seal the source file. Else since the + // file is already installed, seal the destination file. + // + // For regular files this distinction doesn't matter. But for Mach-O + // binaries it ensures we pick up the final signature, not the source + // file. + if need_install { + full_path.to_path_buf() + } else { + context.dest_dir.join(rel_path) + } + }; + + let digests = MultiDigest::from_path(read_path)?; + + let flavor = if self.digests.contains(&DigestType::Sha1) { + FilesFlavor::Rules2WithSha1 + } else { + FilesFlavor::Rules2 + }; + + // When we seal the file, we treat it as a regular file since the + // nested flag isn't set. + self.resources + .seal_regular_file(flavor, rel_path_normalized, digests, optional)?; + } + + if need_install { + context.install_file(full_path, rel_path)?; + } + + Ok(()) + } + + fn seal_rules2_symlink( + &mut self, + full_path: &Path, + rel_path: &Path, + rel_path_normalized: &str, + omit: bool, + context: &mut BundleSigningContext, + ) -> Result<(), AppleCodesignError> { + let link_target = std::fs::read_link(full_path)? + .to_string_lossy() + .replace('\\', "/"); + + if !omit { + info!("sealing symlink {} -> {}", rel_path_normalized, link_target); + self.resources + .seal_symlink(rel_path_normalized, link_target); + } + context.install_file(full_path, rel_path)?; + + Ok(()) + } + + /// Perform sealing activity for an entry in rules v1. + fn seal_rules1_file( + &mut self, + full_path: &Path, + rel_path_normalized: &str, + rule: CodeResourcesRule, + ) -> Result<(), AppleCodesignError> { + // Version 1 doesn't handle symlinks nor nested Mach-O binaries. + // And version 2's handler installed files. So all we have to do here + // is record SHA-1 digests in ``. + + let digests = MultiDigest::from_path(full_path)?; + + self.resources.seal_regular_file( + FilesFlavor::Rules, + rel_path_normalized, + digests, + rule.optional, + )?; + + Ok(()) + } + + /// Write CodeResources XML content to a writer. + pub fn write_code_resources(&self, writer: impl Write) -> Result<(), AppleCodesignError> { + self.resources.to_writer_xml(writer) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const FIREFOX_SNIPPET: &str = r#" + + + + + files + + Resources/XUL.sig + Y0SEPxyC6hCQ+rl4LTRmXy7F9DQ= + Resources/en.lproj/InfoPlist.strings + + hash + U8LTYe+cVqPcBu9aLvcyyfp+dAg= + optional + + + Resources/firefox-bin.sig + ZvZ3yDciAF4kB9F06Xr3gKi3DD4= + + files2 + + Library/LaunchServices/org.mozilla.updater + + hash2 + iMnDHpWkKTI6xLi9Av93eNuIhxXhv3C18D4fljCfw2Y= + + TestOptional + + hash2 + iMnDHpWkKTI6xLi9Av93eNuIhxXhv3C18D4fljCfw2Y= + optional + + + MacOS/XUL + + cdhash + NevNMzQBub9OjomMUAk2xBumyHM= + requirement + anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "43AQ936H96" + + MacOS/SafariForWebKitDevelopment + + symlink + /Library/Application Support/Apple/Safari/SafariForWebKitDevelopment + + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^[^/]+$ + + nested + + weight + 10 + + optional + + optional + + + + + "#; + + #[test] + fn parse_firefox() { + let resources = CodeResources::from_xml(FIREFOX_SNIPPET.as_bytes()).unwrap(); + + // Serialize back to XML. + let mut buffer = Vec::::new(); + resources.to_writer_xml(&mut buffer).unwrap(); + let resources2 = CodeResources::from_xml(&buffer).unwrap(); + + assert_eq!(resources, resources2); + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/cryptography.rs b/3rdparty/apple-codesign-0.29.0/src/cryptography.rs new file mode 100644 index 00000000..580ac406 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/cryptography.rs @@ -0,0 +1,1027 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Common cryptography primitives. + +use { + crate::{ + remote_signing::{session_negotiation::PublicKeyPeerDecrypt, RemoteSignError}, + AppleCodesignError, + }, + apple_xar::table_of_contents::ChecksumType as XarChecksumType, + bytes::Bytes, + clap::ValueEnum, + der::{asn1, Decode, Document, Encode, SecretDocument}, + digest::DynDigest, + elliptic_curve::{ + sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint}, + AffinePoint, Curve, CurveArithmetic, FieldBytesSize, SecretKey as ECSecretKey, + }, + oid_registry::{ + OID_EC_P256, OID_KEY_TYPE_EC_PUBLIC_KEY, OID_PKCS1_RSAENCRYPTION, OID_SIG_ED25519, + }, + p256::NistP256, + pkcs1::RsaPrivateKey, + pkcs8::{EncodePrivateKey, ObjectIdentifier, PrivateKeyInfo}, + ring::signature::{Ed25519KeyPair, KeyPair}, + rsa::{pkcs1::DecodeRsaPrivateKey, BigUint, Oaep, RsaPrivateKey as RsaConstructedKey}, + signature::Signer, + spki::AlgorithmIdentifier, + std::{ + borrow::Cow, + cmp::Ordering, + fmt::{Display, Formatter}, + path::Path, + }, + subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}, + x509_certificate::{ + CapturedX509Certificate, DigestAlgorithm, EcdsaCurve, InMemorySigningKeyPair, KeyAlgorithm, + KeyInfoSigner, Sign, Signature, SignatureAlgorithm, X509CertificateError, + }, + zeroize::Zeroizing, +}; + +/// A supertrait generically describing a private key capable of signing and possibly decryption. +pub trait PrivateKey: KeyInfoSigner { + fn as_key_info_signer(&self) -> &dyn KeyInfoSigner; + + fn to_public_key_peer_decrypt( + &self, + ) -> Result, AppleCodesignError>; + + /// Signals the end of operations on the private key. + /// + /// Implementations can use this to do things like destroy private key matter, disconnect + /// from a hardware device, etc. + fn finish(&self) -> Result<(), AppleCodesignError>; +} + +#[derive(Clone, Debug)] +pub struct InMemoryRsaKey { + // Validated at construction time to be DER for an RsaPrivateKey. + private_key: SecretDocument, +} + +impl InMemoryRsaKey { + /// Construct a new instance from DER data, validating DER in process. + fn from_der(der_data: &[u8]) -> Result { + RsaPrivateKey::from_der(der_data)?; + + let private_key = Document::from_der(der_data)?.into_secret(); + + Ok(Self { private_key }) + } + + fn rsa_private_key(&self) -> RsaPrivateKey<'_> { + RsaPrivateKey::from_der(self.private_key.as_bytes()) + .expect("internal content should be PKCS#1 DER private key data") + } +} + +impl From<&InMemoryRsaKey> for RsaConstructedKey { + fn from(key: &InMemoryRsaKey) -> Self { + let key = key.rsa_private_key(); + + let n = BigUint::from_bytes_be(key.modulus.as_bytes()); + let e = BigUint::from_bytes_be(key.public_exponent.as_bytes()); + let d = BigUint::from_bytes_be(key.private_exponent.as_bytes()); + let prime1 = BigUint::from_bytes_be(key.prime1.as_bytes()); + let prime2 = BigUint::from_bytes_be(key.prime2.as_bytes()); + let primes = vec![prime1, prime2]; + + Self::from_components(n, e, d, primes).expect("inputs valid") + } +} + +impl TryFrom for InMemorySigningKeyPair { + type Error = AppleCodesignError; + + fn try_from(value: InMemoryRsaKey) -> Result { + Ok(Self::from_pkcs8_der( + value + .to_pkcs8_der() + .map_err(|e| { + AppleCodesignError::CertificateGeneric(format!( + "error converting RSA key to DER: {}", + e + )) + })? + .as_bytes(), + )?) + } +} + +impl EncodePrivateKey for InMemoryRsaKey { + fn to_pkcs8_der(&self) -> pkcs8::Result { + // We don't need to store the public key because it can always be + // derived from the private key for RSA keys. + let raw = PrivateKeyInfo::new(pkcs1::ALGORITHM_ID, self.private_key.as_bytes()).to_der()?; + + Ok(Document::from_der(&raw)?.into_secret()) + } +} + +impl PublicKeyPeerDecrypt for InMemoryRsaKey { + fn decrypt(&self, ciphertext: &[u8]) -> Result, RemoteSignError> { + let key = RsaConstructedKey::from_pkcs1_der(self.private_key.as_bytes()) + .map_err(|e| RemoteSignError::Crypto(format!("failed to parse RSA key: {e}")))?; + + let padding = Oaep::new::(); + + let plaintext = key + .decrypt(padding, ciphertext) + .map_err(|e| RemoteSignError::Crypto(format!("RSA decryption failure: {e}")))?; + + Ok(plaintext) + } +} + +#[derive(Clone, Debug)] +pub struct InMemoryEcdsaKey +where + C: Curve + CurveArithmetic, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + curve: ObjectIdentifier, + secret_key: ECSecretKey, +} + +impl InMemoryEcdsaKey +where + C: Curve + CurveArithmetic, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + pub fn curve(&self) -> Result { + match self.curve.as_bytes() { + x if x == OID_EC_P256.as_bytes() => Ok(EcdsaCurve::Secp256r1), + _ => Err(AppleCodesignError::CertificateGeneric(format!( + "unknown ECDSA curve: {}", + self.curve + ))), + } + } +} + +impl TryFrom> for InMemorySigningKeyPair +where + C: Curve + CurveArithmetic, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + type Error = AppleCodesignError; + + fn try_from(key: InMemoryEcdsaKey) -> Result { + Ok(Self::from_pkcs8_der( + key.to_pkcs8_der() + .map_err(|e| { + AppleCodesignError::CertificateGeneric(format!( + "error converting ECDSA key to DER: {}", + e + )) + })? + .as_bytes(), + )?) + } +} + +impl EncodePrivateKey for InMemoryEcdsaKey +where + C: Curve + CurveArithmetic, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + fn to_pkcs8_der(&self) -> pkcs8::Result { + let private_key = self.secret_key.to_sec1_der()?; + + PrivateKeyInfo { + algorithm: AlgorithmIdentifier { + oid: ObjectIdentifier::from_bytes(OID_KEY_TYPE_EC_PUBLIC_KEY.as_bytes()) + .expect("OID construction should work"), + parameters: Some(asn1::AnyRef::from(&self.curve)), + }, + private_key: private_key.as_ref(), + public_key: None, + } + .try_into() + } +} + +impl PublicKeyPeerDecrypt for InMemoryEcdsaKey +where + C: Curve + CurveArithmetic, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + fn decrypt(&self, _ciphertext: &[u8]) -> Result, RemoteSignError> { + Err(RemoteSignError::Crypto( + "decryption using ECDSA keys is not yet implemented".into(), + )) + } +} + +#[derive(Clone, Debug)] +pub struct InMemoryEd25519Key { + private_key: Zeroizing>, +} + +impl TryFrom for InMemorySigningKeyPair { + type Error = AppleCodesignError; + + fn try_from(key: InMemoryEd25519Key) -> Result { + Ok(Self::from_pkcs8_der( + key.to_pkcs8_der() + .map_err(|e| { + AppleCodesignError::CertificateGeneric(format!( + "error converting ED25519 key to DER: {}", + e + )) + })? + .as_bytes(), + )?) + } +} + +impl EncodePrivateKey for InMemoryEd25519Key { + fn to_pkcs8_der(&self) -> pkcs8::Result { + let algorithm = AlgorithmIdentifier { + oid: ObjectIdentifier::from_bytes(OID_SIG_ED25519.as_bytes()).expect("OID is valid"), + parameters: None, + }; + + let key_ref: &[u8] = self.private_key.as_ref(); + let value = Zeroizing::new(asn1::OctetString::new(key_ref)?.to_der()?); + + let mut pki = PrivateKeyInfo::new(algorithm, value.as_ref()); + + let public_key = + if let Ok(key) = Ed25519KeyPair::from_seed_unchecked(self.private_key.as_ref()) { + Bytes::copy_from_slice(key.public_key().as_ref()) + } else { + Bytes::new() + }; + + pki.public_key = Some(public_key.as_ref()); + + pki.try_into() + } +} + +impl PublicKeyPeerDecrypt for InMemoryEd25519Key { + fn decrypt(&self, _ciphertext: &[u8]) -> Result, RemoteSignError> { + Err(RemoteSignError::Crypto( + "decryption using ED25519 keys is not yet implemented".into(), + )) + } +} + +/// Holds a private key in memory. +#[derive(Clone, Debug)] +pub enum InMemoryPrivateKey { + /// ECDSA private key using Nist P256 curve. + EcdsaP256(InMemoryEcdsaKey), + /// ED25519 private key. + Ed25519(InMemoryEd25519Key), + /// RSA private key. + Rsa(InMemoryRsaKey), +} + +impl<'a> TryFrom> for InMemoryPrivateKey { + type Error = pkcs8::Error; + + fn try_from(value: PrivateKeyInfo<'a>) -> Result { + match value.algorithm.oid { + x if x.as_bytes() == OID_PKCS1_RSAENCRYPTION.as_bytes() => { + Ok(Self::Rsa(InMemoryRsaKey::from_der(value.private_key)?)) + } + x if x.as_bytes() == OID_KEY_TYPE_EC_PUBLIC_KEY.as_bytes() => { + let curve_oid = value.algorithm.parameters_oid()?; + + match curve_oid.as_bytes() { + x if x == OID_EC_P256.as_bytes() => { + let secret_key = ECSecretKey::::try_from(value)?; + + Ok(Self::EcdsaP256(InMemoryEcdsaKey { + curve: curve_oid, + secret_key, + })) + } + _ => Err(pkcs8::Error::ParametersMalformed), + } + } + x if x.as_bytes() == OID_SIG_ED25519.as_bytes() => { + // The private key seed should start at byte offset 2. + Ok(Self::Ed25519(InMemoryEd25519Key { + private_key: Zeroizing::new((value.private_key[2..]).to_vec()), + })) + } + _ => Err(pkcs8::Error::KeyMalformed), + } + } +} + +impl TryFrom for InMemorySigningKeyPair { + type Error = AppleCodesignError; + + fn try_from(key: InMemoryPrivateKey) -> Result { + match key { + InMemoryPrivateKey::Rsa(key) => key.try_into(), + InMemoryPrivateKey::EcdsaP256(key) => key.try_into(), + InMemoryPrivateKey::Ed25519(key) => key.try_into(), + } + } +} + +impl EncodePrivateKey for InMemoryPrivateKey { + fn to_pkcs8_der(&self) -> pkcs8::Result { + match self { + Self::EcdsaP256(key) => key.to_pkcs8_der(), + Self::Ed25519(key) => key.to_pkcs8_der(), + Self::Rsa(key) => key.to_pkcs8_der(), + } + } +} + +impl Signer for InMemoryPrivateKey { + fn try_sign(&self, msg: &[u8]) -> Result { + let key_pair = InMemorySigningKeyPair::try_from(self.clone()) + .map_err(signature::Error::from_source)?; + + key_pair.try_sign(msg) + } +} + +impl Sign for InMemoryPrivateKey { + fn sign(&self, message: &[u8]) -> Result<(Vec, SignatureAlgorithm), X509CertificateError> { + let algorithm = self.signature_algorithm()?; + + Ok((self.try_sign(message)?.into(), algorithm)) + } + + fn key_algorithm(&self) -> Option { + Some(match self { + Self::EcdsaP256(_) => KeyAlgorithm::Ecdsa(EcdsaCurve::Secp256r1), + Self::Ed25519(_) => KeyAlgorithm::Ed25519, + Self::Rsa(_) => KeyAlgorithm::Rsa, + }) + } + + fn public_key_data(&self) -> Bytes { + match self { + Self::EcdsaP256(key) => Bytes::copy_from_slice( + key.secret_key + .public_key() + .to_encoded_point(false) + .as_bytes(), + ), + Self::Ed25519(key) => { + if let Ok(key) = Ed25519KeyPair::from_seed_unchecked(key.private_key.as_ref()) { + Bytes::copy_from_slice(key.public_key().as_ref()) + } else { + Bytes::new() + } + } + Self::Rsa(key) => { + let key = key.rsa_private_key(); + + Bytes::copy_from_slice( + key.public_key() + .to_der() + .expect("RSA public key DER encoding should not fail") + .as_ref(), + ) + } + } + } + + fn signature_algorithm(&self) -> Result { + Ok(match self { + Self::EcdsaP256(_) => SignatureAlgorithm::EcdsaSha256, + Self::Ed25519(_) => SignatureAlgorithm::Ed25519, + Self::Rsa(_) => SignatureAlgorithm::RsaSha256, + }) + } + + fn private_key_data(&self) -> Option>> { + match self { + Self::EcdsaP256(key) => Some(Zeroizing::new(key.secret_key.to_bytes().to_vec())), + Self::Ed25519(key) => Some(Zeroizing::new((*key.private_key).clone())), + Self::Rsa(key) => Some(Zeroizing::new(key.private_key.as_bytes().to_vec())), + } + } + + fn rsa_primes( + &self, + ) -> Result>, Zeroizing>)>, X509CertificateError> { + if let Self::Rsa(key) = self { + let key = key.rsa_private_key(); + + Ok(Some(( + Zeroizing::new(key.prime1.as_bytes().to_vec()), + Zeroizing::new(key.prime2.as_bytes().to_vec()), + ))) + } else { + Ok(None) + } + } +} + +impl KeyInfoSigner for InMemoryPrivateKey {} + +impl PublicKeyPeerDecrypt for InMemoryPrivateKey { + fn decrypt(&self, ciphertext: &[u8]) -> Result, RemoteSignError> { + match self { + Self::Rsa(key) => key.decrypt(ciphertext), + Self::EcdsaP256(key) => key.decrypt(ciphertext), + Self::Ed25519(key) => key.decrypt(ciphertext), + } + } +} + +impl PrivateKey for InMemoryPrivateKey { + fn as_key_info_signer(&self) -> &dyn KeyInfoSigner { + self + } + + fn to_public_key_peer_decrypt( + &self, + ) -> Result, AppleCodesignError> { + Ok(Box::new(self.clone())) + } + + fn finish(&self) -> Result<(), AppleCodesignError> { + Ok(()) + } +} + +impl InMemoryPrivateKey { + /// Construct an instance by parsing PKCS#1 DER data. + pub fn from_pkcs1_der(data: impl AsRef<[u8]>) -> Result { + let key = InMemoryRsaKey::from_der(data.as_ref()).map_err(|e| { + AppleCodesignError::CertificateGeneric(format!("when parsing PKCS#1 data: {e}")) + })?; + + Ok(Self::Rsa(key)) + } + + /// Construct an instance by parsing PKCS#8 DER data. + pub fn from_pkcs8_der(data: impl AsRef<[u8]>) -> Result { + let pki = PrivateKeyInfo::try_from(data.as_ref()).map_err(|e| { + AppleCodesignError::CertificateGeneric(format!("when parsing PKCS#8 data: {e}")) + })?; + + pki.try_into().map_err(|e| { + AppleCodesignError::CertificateGeneric(format!( + "when converting parsed PKCS#8 to a private key: {e}" + )) + }) + } +} + +/// Represents a digest type encountered in code signature data structures. +#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)] +pub enum DigestType { + None, + Sha1, + Sha256, + Sha256Truncated, + Sha384, + Sha512, + #[value(skip)] + Unknown(u8), +} + +impl Default for DigestType { + fn default() -> Self { + Self::Sha256 + } +} + +impl TryFrom for DigestAlgorithm { + type Error = AppleCodesignError; + + fn try_from(value: DigestType) -> Result { + match value { + DigestType::Sha1 => Ok(DigestAlgorithm::Sha1), + DigestType::Sha256 => Ok(DigestAlgorithm::Sha256), + DigestType::Sha256Truncated => Ok(DigestAlgorithm::Sha256), + DigestType::Sha384 => Ok(DigestAlgorithm::Sha384), + DigestType::Sha512 => Ok(DigestAlgorithm::Sha512), + DigestType::Unknown(_) => Err(AppleCodesignError::DigestUnknownAlgorithm), + DigestType::None => Err(AppleCodesignError::DigestUnsupportedAlgorithm), + } + } +} + +impl PartialOrd for DigestType { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for DigestType { + fn cmp(&self, other: &Self) -> Ordering { + u8::from(*self).cmp(&u8::from(*other)) + } +} + +impl Display for DigestType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + DigestType::None => f.write_str("none"), + DigestType::Sha1 => f.write_str("sha1"), + DigestType::Sha256 => f.write_str("sha256"), + DigestType::Sha256Truncated => f.write_str("sha256-truncated"), + DigestType::Sha384 => f.write_str("sha384"), + DigestType::Sha512 => f.write_str("sha512"), + DigestType::Unknown(v) => f.write_fmt(format_args!("unknown: {v}")), + } + } +} + +impl TryFrom<&str> for DigestType { + type Error = AppleCodesignError; + + fn try_from(s: &str) -> Result { + match s { + "none" => Ok(Self::None), + "sha1" => Ok(Self::Sha1), + "sha256" => Ok(Self::Sha256), + "sha256-truncated" => Ok(Self::Sha256Truncated), + "sha384" => Ok(Self::Sha384), + "sha512" => Ok(Self::Sha512), + _ => Err(AppleCodesignError::DigestUnknownAlgorithm), + } + } +} + +impl TryFrom for DigestType { + type Error = AppleCodesignError; + + fn try_from(c: XarChecksumType) -> Result { + match c { + XarChecksumType::None => Ok(Self::None), + XarChecksumType::Sha1 => Ok(Self::Sha1), + XarChecksumType::Sha256 => Ok(Self::Sha256), + XarChecksumType::Sha512 => Ok(Self::Sha512), + XarChecksumType::Md5 => Err(AppleCodesignError::DigestUnsupportedAlgorithm), + } + } +} + +impl DigestType { + /// Obtain the size of hashes for this hash type. + pub fn hash_len(&self) -> Result { + Ok(self.digest_data(&[])?.len()) + } + + /// Obtain a hasher for this digest type. + pub fn as_hasher(&self) -> Result { + match self { + Self::None => Err(AppleCodesignError::DigestUnknownAlgorithm), + Self::Sha1 => Ok(ring::digest::Context::new( + &ring::digest::SHA1_FOR_LEGACY_USE_ONLY, + )), + Self::Sha256 | Self::Sha256Truncated => { + Ok(ring::digest::Context::new(&ring::digest::SHA256)) + } + Self::Sha384 => Ok(ring::digest::Context::new(&ring::digest::SHA384)), + Self::Sha512 => Ok(ring::digest::Context::new(&ring::digest::SHA512)), + Self::Unknown(_) => Err(AppleCodesignError::DigestUnknownAlgorithm), + } + } + + /// Digest data given the configured hasher. + pub fn digest_data(&self, data: &[u8]) -> Result, AppleCodesignError> { + let mut hasher = self.as_hasher()?; + + hasher.update(data); + let mut hash = hasher.finish().as_ref().to_vec(); + + if matches!(self, Self::Sha256Truncated) { + hash.truncate(20); + } + + Ok(hash) + } +} + +pub struct Digest<'a> { + pub data: Cow<'a, [u8]>, +} + +impl<'a> Digest<'a> { + /// Whether this is the null hash (all 0s). + pub fn is_null(&self) -> bool { + self.data.iter().all(|b| *b == 0) + } + + pub fn to_vec(&self) -> Vec { + self.data.to_vec() + } + + pub fn to_owned(&self) -> Digest<'static> { + Digest { + data: Cow::Owned(self.data.clone().into_owned()), + } + } + + pub fn as_hex(&self) -> String { + hex::encode(&self.data) + } +} + +impl<'a> std::fmt::Debug for Digest<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&hex::encode(&self.data)) + } +} + +impl<'a> From> for Digest<'a> { + fn from(v: Vec) -> Self { + Self { data: v.into() } + } +} + +/// Holds multiple computed digests for content. +pub struct MultiDigest { + pub sha1: Digest<'static>, + pub sha256: Digest<'static>, +} + +impl MultiDigest { + /// Compute the multi digests for any stream reader. + /// + /// This will read the stream until EOF. + pub fn from_reader(mut reader: impl std::io::Read) -> Result { + let mut sha1 = DigestType::Sha1.as_hasher()?; + let mut sha256 = DigestType::Sha256.as_hasher()?; + + let mut buffer = [0u8; 16384]; + + loop { + let read = reader.read(&mut buffer)?; + if read == 0 { + break; + } + + sha1.update(&buffer[0..read]); + sha256.update(&buffer[0..read]); + } + + let sha1 = sha1.finish().as_ref().to_vec(); + let sha256 = sha256.finish().as_ref().to_vec(); + + Ok(Self { + sha1: sha1.into(), + sha256: sha256.into(), + }) + } + + /// Compute the multi digest of a filesystem path. + pub fn from_path(path: impl AsRef) -> Result { + let fh = std::fs::File::open(path.as_ref())?; + Self::from_reader(fh) + } +} + +fn bmp_string(s: &str) -> Vec { + let utf16: Vec = s.encode_utf16().collect(); + + let mut bytes = Vec::with_capacity(utf16.len() * 2 + 2); + for c in utf16 { + bytes.push((c / 256) as u8); + bytes.push((c % 256) as u8); + } + bytes.push(0x00); + bytes.push(0x00); + + bytes +} + +/// Parse PFX data into a key pair. +/// +/// PFX data is commonly encountered in `.p12` files, such as those created +/// when exporting certificates from Apple's `Keychain Access` application. +/// +/// The contents of the PFX file require a password to decrypt. However, if +/// no password was provided to create the PFX data, this password may be the +/// empty string. +pub fn parse_pfx_data( + data: &[u8], + password: &str, +) -> Result<(CapturedX509Certificate, InMemoryPrivateKey), AppleCodesignError> { + let pfx = p12::PFX::parse(data).map_err(|e| { + AppleCodesignError::PfxParseError(format!("data does not appear to be PFX: {e:?}")) + })?; + + if !pfx.verify_mac(password) { + return Err(AppleCodesignError::PfxBadPassword); + } + + // Apple's certificate export format consists of regular data content info + // with inner ContentInfo components holding the key and certificate. + let data = match pfx.auth_safe { + p12::ContentInfo::Data(data) => data, + _ => { + return Err(AppleCodesignError::PfxParseError( + "unexpected PFX content info".to_string(), + )); + } + }; + + let content_infos = yasna::parse_der(&data, |reader| { + reader.collect_sequence_of(p12::ContentInfo::parse) + }) + .map_err(|e| { + AppleCodesignError::PfxParseError(format!("failed parsing inner ContentInfo: {e:?}")) + })?; + + let bmp_password = bmp_string(password); + + let mut certificate = None; + let mut signing_key = None; + + for content in content_infos { + let bags_data = match content { + p12::ContentInfo::Data(inner) => inner, + p12::ContentInfo::EncryptedData(encrypted) => { + encrypted.data(&bmp_password).ok_or_else(|| { + AppleCodesignError::PfxParseError( + "failed decrypting inner EncryptedData".to_string(), + ) + })? + } + p12::ContentInfo::OtherContext(_) => { + return Err(AppleCodesignError::PfxParseError( + "unexpected OtherContent content in inner PFX data".to_string(), + )); + } + }; + + let bags = yasna::parse_ber(&bags_data, |reader| { + reader.collect_sequence_of(p12::SafeBag::parse) + }) + .map_err(|e| { + AppleCodesignError::PfxParseError(format!( + "failed parsing SafeBag within inner Data: {e:?}" + )) + })?; + + for bag in bags { + match bag.bag { + p12::SafeBagKind::CertBag(cert_bag) => match cert_bag { + p12::CertBag::X509(cert_data) => { + certificate = Some(CapturedX509Certificate::from_der(cert_data)?); + } + p12::CertBag::SDSI(_) => { + return Err(AppleCodesignError::PfxParseError( + "unexpected SDSI certificate data".to_string(), + )); + } + }, + p12::SafeBagKind::Pkcs8ShroudedKeyBag(key_bag) => { + let decrypted = key_bag.decrypt(&bmp_password).ok_or_else(|| { + AppleCodesignError::PfxParseError( + "error decrypting PKCS8 shrouded key bag; is the password correct?" + .to_string(), + ) + })?; + + signing_key = Some(InMemoryPrivateKey::from_pkcs8_der(decrypted)?); + } + p12::SafeBagKind::OtherBagKind(_) => { + return Err(AppleCodesignError::PfxParseError( + "unexpected bag type in inner PFX content".to_string(), + )); + } + } + } + } + + match (certificate, signing_key) { + (Some(certificate), Some(signing_key)) => Ok((certificate, signing_key)), + (None, Some(_)) => Err(AppleCodesignError::PfxParseError( + "failed to find x509 certificate in PFX data".to_string(), + )), + (_, None) => Err(AppleCodesignError::PfxParseError( + "failed to find signing key in PFX data".to_string(), + )), + } +} + +/// RSA OAEP post decrypt depadding. +/// +/// This implements the procedure described by RFC 3447 Section 7.1.2 +/// starting at Step 3 (after the ciphertext has been fed into the low-level +/// RSA decryption. +/// +/// This implementation has NOT been audited and shouldn't be used. It only +/// exists here because we need it to support RSA decryption using YubiKeys. +/// https://github.com/RustCrypto/RSA/issues/159 is fixed to hopefully get this +/// exposed as an API on the rsa crate. +#[allow(unused)] +pub(crate) fn rsa_oaep_post_decrypt_decode( + modulus_length_bytes: usize, + mut em: Vec, + digest: &mut dyn digest::DynDigest, + mgf_digest: &mut dyn digest::DynDigest, + label: Option, +) -> Result, rsa::errors::Error> { + let k = modulus_length_bytes; + let digest_len = digest.output_size(); + + // 3. EME_OAEP decoding. + + // 3a. + let label = label.unwrap_or_default(); + digest.update(label.as_bytes()); + let label_digest = digest.finalize_reset(); + + // 3b. + let (y, remaining) = em.split_at_mut(1); + let (masked_seed, masked_db) = remaining.split_at_mut(digest_len); + + if masked_seed.len() != digest_len || masked_db.len() != k - digest_len - 1 { + return Err(rsa::errors::Error::Decryption); + } + + // 3c - 3f. + mgf1_xor(masked_seed, mgf_digest, masked_db); + mgf1_xor(masked_db, mgf_digest, masked_seed); + + // 3g. + // + // We need to split into padding string (all zeroes) and message M with a + // 0x01 between them. The padding string should be all zeroes. And this should + // execute in constant time, which makes it tricky. + + let digests_equivalent = masked_db[0..digest_len].ct_eq(label_digest.as_ref()); + + let mut looking_for_index = Choice::from(1u8); + let mut index = 0u32; + let mut padding_invalid = Choice::from(0u8); + + for (i, value) in masked_db.iter().skip(digest_len).enumerate() { + let is_zero = value.ct_eq(&0u8); + let is_one = value.ct_eq(&1u8); + + index.conditional_assign(&(i as u32), looking_for_index & is_one); + looking_for_index &= !is_one; + padding_invalid |= looking_for_index & !is_zero; + } + + let y_is_zero = y[0].ct_eq(&0u8); + + let valid = y_is_zero & digests_equivalent & !padding_invalid & !looking_for_index; + + let res = CtOption::new((em, index + 2 + (digest_len * 2) as u32), valid); + + if res.is_none().into() { + return Err(rsa::errors::Error::Decryption); + } + + let (out, index) = res.unwrap(); + + Ok(out[index as usize..].to_vec()) +} + +fn inc_counter(counter: &mut [u8; 4]) { + for i in (0..4).rev() { + counter[i] = counter[i].wrapping_add(1); + if counter[i] != 0 { + // No overflow + return; + } + } +} + +fn mgf1_xor(out: &mut [u8], digest: &mut dyn DynDigest, seed: &[u8]) { + let mut counter = [0u8; 4]; + let mut i = 0; + + const MAX_LEN: u64 = core::u32::MAX as u64 + 1; + assert!(out.len() as u64 <= MAX_LEN); + + while i < out.len() { + let mut digest_input = vec![0u8; seed.len() + 4]; + digest_input[0..seed.len()].copy_from_slice(seed); + digest_input[seed.len()..].copy_from_slice(&counter); + + digest.update(digest_input.as_slice()); + let digest_output = &*digest.finalize_reset(); + let mut j = 0; + loop { + if j >= digest_output.len() || i >= out.len() { + break; + } + + out[i] ^= digest_output[j]; + j += 1; + i += 1; + } + inc_counter(&mut counter); + } +} + +#[cfg(test)] +mod test { + use { + super::*, + ring::signature::{EcdsaKeyPair, KeyPair, RsaKeyPair}, + x509_certificate::Sign, + }; + + const RSA_2048_PKCS8_DER: &[u8] = include_bytes!("testdata/rsa-2048.pk8"); + const ED25519_PKCS8_DER: &[u8] = include_bytes!("testdata/ed25519.pk8"); + const SECP256_PKCS8_DER: &[u8] = include_bytes!("testdata/secp256r1.pk8"); + + #[test] + fn parse_keychain_p12_export() { + let data = include_bytes!("apple-codesign-testuser.p12"); + + let err = parse_pfx_data(data, "bad-password").unwrap_err(); + assert!(matches!(err, AppleCodesignError::PfxBadPassword)); + + parse_pfx_data(data, "password123").unwrap(); + } + + #[test] + fn rsa_key_operations() -> Result<(), AppleCodesignError> { + let ring_key = RsaKeyPair::from_pkcs8(RSA_2048_PKCS8_DER).unwrap(); + let ring_public_key_data = ring_key.public_key().as_ref(); + + let pki = PrivateKeyInfo::from_der(RSA_2048_PKCS8_DER).unwrap(); + let key = InMemoryPrivateKey::try_from(pki).unwrap(); + + assert_eq!(key.to_pkcs8_der().unwrap().as_bytes(), RSA_2048_PKCS8_DER); + + let our_key = InMemorySigningKeyPair::try_from(key)?; + let our_public_key = our_key.public_key_data(); + + assert_eq!(our_public_key.as_ref(), ring_public_key_data); + + InMemoryPrivateKey::from_pkcs8_der(RSA_2048_PKCS8_DER)?; + + let random_key = rsa::RsaPrivateKey::new(&mut rand::thread_rng(), 2048).unwrap(); + let random_key_pkcs8 = random_key.to_pkcs8_der().unwrap(); + InMemorySigningKeyPair::from_pkcs8_der(random_key_pkcs8.as_bytes())?; + + Ok(()) + } + + #[test] + fn ed25519_key_operations() -> Result<(), AppleCodesignError> { + let pki = PrivateKeyInfo::from_der(ED25519_PKCS8_DER).unwrap(); + let seed = &pki.private_key[2..]; + let key = InMemoryPrivateKey::try_from(pki).unwrap(); + + assert!( + InMemorySigningKeyPair::from_pkcs8_der(ED25519_PKCS8_DER).is_err(), + "stored key doesn't have public key, which ring rejects loading" + ); + + // But out PKCS#8 export includes it so it can round trip. + InMemorySigningKeyPair::from_pkcs8_der(key.to_pkcs8_der().unwrap().as_bytes()).unwrap(); + + let our_key = InMemorySigningKeyPair::try_from(key)?; + let our_public_key = our_key.public_key_data(); + + let ring_key = Ed25519KeyPair::from_seed_unchecked(seed).unwrap(); + let ring_public_key_data = ring_key.public_key().as_ref(); + + assert_eq!(our_public_key.as_ref(), ring_public_key_data); + + InMemoryPrivateKey::from_pkcs8_der(ED25519_PKCS8_DER)?; + + Ok(()) + } + + #[test] + fn ecdsa_key_operations_secp256() -> Result<(), AppleCodesignError> { + let ring_key = EcdsaKeyPair::from_pkcs8( + &ring::signature::ECDSA_P256_SHA256_ASN1_SIGNING, + SECP256_PKCS8_DER, + &ring::rand::SystemRandom::new(), + ) + .unwrap(); + let ring_public_key_data = ring_key.public_key().as_ref(); + + let pki = PrivateKeyInfo::from_der(SECP256_PKCS8_DER).unwrap(); + let key = InMemoryPrivateKey::try_from(pki).unwrap(); + + assert_eq!(key.to_pkcs8_der().unwrap().as_bytes(), SECP256_PKCS8_DER); + + InMemorySigningKeyPair::from_pkcs8_der(SECP256_PKCS8_DER)?; + let our_key = InMemorySigningKeyPair::try_from(key)?; + let our_public_key = our_key.public_key_data(); + + assert_eq!(our_public_key.as_ref(), ring_public_key_data); + + InMemoryPrivateKey::from_pkcs8_der(SECP256_PKCS8_DER)?; + + Ok(()) + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/dmg.rs b/3rdparty/apple-codesign-0.29.0/src/dmg.rs new file mode 100644 index 00000000..3001fcf8 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/dmg.rs @@ -0,0 +1,418 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +/*! DMG file handling. + +DMG files can have code signatures as well. However, the mechanism is a bit different +from Mach-O files. + +The last 512 bytes of a DMG are a "koly" structure, which we represent by +[KolyTrailer]. Within the [KolyTrailer] are a pair of [u64] denoting the +file offset and size of an embedded code signature. + +The embedded code signature is a signature superblob, as represented by our +[EmbeddedSignature]. + +Apple's `codesign` appears to write the Code Directory, Requirement Set, and +CMS Signature slots. However, Requirement Set is empty and the CMS blob may +have no data (just a blob header). + +Within the Code Directory, the code limit field is the offset of the start of +code signature superblob and there is exactly a single code digest. Unlike +Mach-O files which digest in 4kb chunks, the full content of the DMG up to the +superblob are digested in full. However, the page size is advertised as `1`, +which `codesign` reports as `none`. + +The Code Directory also contains a digest in the Rep Specific slot. This digest +is over the "koly" trailer, but with the u64 for the code signature size field +zeroed out. This is likely zeroed to prevent a circular dependency: you won't +know the size of the CMS payload until the signature is created so you can't +fill in a known value ahead of time. It's worth noting that for Mach-O, the +superblob is padded with zeroes so the size of the __LINKEDIT segment can be +known before the signature is made. DMG can likely get away without padding +because the "koly" trailer is at the end of the file and any junk between +the code signature and trailer will be ignored or corrupt one of the data +structures. + +The Code Directory version is 0x20100. + +DMGs are stapled by adding an additional ticket slot to the superblob. However, +this slot's digest is not recorded in the code directory, as stapling occurs +after signing and modifying the code directory would modify the code directory +and invalidate prior signatures. +*/ + +use { + crate::{ + code_directory::{CodeDirectoryBlob, CodeSignatureFlags}, + cryptography::{Digest, DigestType}, + embedded_signature::{BlobData, CodeSigningSlot, EmbeddedSignature, RequirementSetBlob}, + embedded_signature_builder::EmbeddedSignatureBuilder, + AppleCodesignError, SettingsScope, SigningSettings, + }, + log::warn, + scroll::{Pread, Pwrite, SizeWith}, + std::{ + borrow::Cow, + fs::File, + io::{Read, Seek, SeekFrom, Write}, + path::Path, + }, +}; + +const KOLY_SIZE: i64 = 512; + +/// DMG trailer describing file content. +/// +/// This is the main structure defining a DMG. +#[derive(Clone, Debug, Eq, Pread, PartialEq, Pwrite, SizeWith)] +pub struct KolyTrailer { + /// "koly" + pub signature: [u8; 4], + pub version: u32, + pub header_size: u32, + pub flags: u32, + pub running_data_fork_offset: u64, + pub data_fork_offset: u64, + pub data_fork_length: u64, + pub rsrc_fork_offset: u64, + pub rsrc_fork_length: u64, + pub segment_number: u32, + pub segment_count: u32, + pub segment_id: [u32; 4], + pub data_fork_digest_type: u32, + pub data_fork_digest_size: u32, + pub data_fork_digest: [u32; 32], + pub plist_offset: u64, + pub plist_length: u64, + pub reserved1: [u64; 8], + pub code_signature_offset: u64, + pub code_signature_size: u64, + pub reserved2: [u64; 5], + pub main_digest_type: u32, + pub main_digest_size: u32, + pub main_digest: [u32; 32], + pub image_variant: u32, + pub sector_count: u64, +} + +impl KolyTrailer { + /// Construct an instance by reading from a seekable reader. + /// + /// The trailer is the final 512 bytes of the seekable stream. + pub fn read_from(reader: &mut R) -> Result { + reader.seek(SeekFrom::End(-KOLY_SIZE))?; + + // We can't use IOread with structs larger than 256 bytes. + let mut data = vec![]; + reader.read_to_end(&mut data)?; + + let koly = data.pread_with::(0, scroll::BE)?; + + if &koly.signature != b"koly" { + return Err(AppleCodesignError::DmgBadMagic); + } + + Ok(koly) + } + + /// Obtain the offset byte after the plist data. + /// + /// This is the offset at which an embedded signature superblob would be present. + /// If no embedded signature is present, this is likely the start of [KolyTrailer]. + pub fn offset_after_plist(&self) -> u64 { + self.plist_offset + self.plist_length + } + + /// Obtain the digest of the trailer in a way compatible with code directory digesting. + /// + /// This will compute the digest of the current values but with the code signature + /// size set to 0. + pub fn digest_for_code_directory( + &self, + digest: DigestType, + ) -> Result, AppleCodesignError> { + let mut koly = self.clone(); + koly.code_signature_size = 0; + koly.code_signature_offset = self.offset_after_plist(); + + let mut buf = [0u8; KOLY_SIZE as usize]; + buf.pwrite_with(koly, 0, scroll::BE)?; + + digest.digest_data(&buf) + } +} + +/// An entity for reading DMG files. +/// +/// It only implements enough to create code signatures over the DMG. +pub struct DmgReader { + koly: KolyTrailer, + + /// Caches the embedded code signature data. + code_signature_data: Option>, +} + +impl DmgReader { + /// Construct a new instance from a reader. + pub fn new(reader: &mut R) -> Result { + let koly = KolyTrailer::read_from(reader)?; + + let code_signature_offset = koly.code_signature_offset; + let code_signature_size = koly.code_signature_size; + + let code_signature_data = if code_signature_offset != 0 && code_signature_size != 0 { + reader.seek(SeekFrom::Start(code_signature_offset))?; + let mut data = vec![]; + reader.take(code_signature_size).read_to_end(&mut data)?; + + Some(data) + } else { + None + }; + + Ok(Self { + koly, + code_signature_data, + }) + } + + /// Obtain the main data structure describing this DMG. + pub fn koly(&self) -> &KolyTrailer { + &self.koly + } + + /// Obtain the embedded code signature superblob. + pub fn embedded_signature(&self) -> Result>, AppleCodesignError> { + if let Some(data) = &self.code_signature_data { + Ok(Some(EmbeddedSignature::from_bytes(data)?)) + } else { + Ok(None) + } + } + + /// Digest an arbitrary slice of the file. + fn digest_slice_with( + &self, + digest: DigestType, + reader: &mut R, + offset: u64, + length: u64, + ) -> Result, AppleCodesignError> { + reader.seek(SeekFrom::Start(offset))?; + + let mut reader = reader.take(length); + + let mut d = digest.as_hasher()?; + + loop { + let mut buffer = [0u8; 16384]; + let count = reader.read(&mut buffer)?; + + d.update(&buffer[0..count]); + + if count == 0 { + break; + } + } + + Ok(Digest { + data: d.finish().as_ref().to_vec().into(), + }) + } + + /// Digest the content of the DMG up to the code signature or [KolyTrailer]. + /// + /// This digest is used as the code digest in the code directory. + pub fn digest_content_with( + &self, + digest: DigestType, + reader: &mut R, + ) -> Result, AppleCodesignError> { + if self.koly.code_signature_offset != 0 { + self.digest_slice_with(digest, reader, 0, self.koly.code_signature_offset) + } else { + reader.seek(SeekFrom::End(-KOLY_SIZE))?; + let size = reader.stream_position()?; + + self.digest_slice_with(digest, reader, 0, size) + } + } +} + +/// Determines whether a filesystem path is a DMG. +/// +/// Returns true if the path has a DMG trailer. +pub fn path_is_dmg(path: impl AsRef) -> Result { + let mut fh = File::open(path.as_ref())?; + + Ok(KolyTrailer::read_from(&mut fh).is_ok()) +} + +/// Entity for signing DMG files. +#[derive(Clone, Debug, Default)] +pub struct DmgSigner {} + +impl DmgSigner { + /// Sign a DMG. + /// + /// Parameters controlling the signing operation are specified by `settings`. + /// + /// `file` is a readable and writable file. The DMG signature will be written + /// into the source file. + pub fn sign_file( + &self, + settings: &SigningSettings, + fh: &mut File, + ) -> Result<(), AppleCodesignError> { + warn!("signing DMG"); + + let koly = DmgReader::new(fh)?.koly().clone(); + let signature = self.create_superblob(settings, fh)?; + + Self::write_embedded_signature(fh, koly, &signature) + } + + /// Staple a notarization ticket to a DMG. + pub fn staple_file( + &self, + fh: &mut File, + ticket_data: Vec, + ) -> Result<(), AppleCodesignError> { + warn!( + "stapling DMG with {} byte notarization ticket", + ticket_data.len() + ); + + let reader = DmgReader::new(fh)?; + let koly = reader.koly().clone(); + let signature = reader + .embedded_signature()? + .ok_or(AppleCodesignError::DmgStapleNoSignature)?; + + let mut builder = EmbeddedSignatureBuilder::new_for_stapling(signature)?; + builder.add_notarization_ticket(ticket_data)?; + + let signature = builder.create_superblob()?; + + Self::write_embedded_signature(fh, koly, &signature) + } + + fn write_embedded_signature( + fh: &mut File, + mut koly: KolyTrailer, + signature: &[u8], + ) -> Result<(), AppleCodesignError> { + warn!("writing {} byte signature", signature.len()); + fh.seek(SeekFrom::Start(koly.offset_after_plist()))?; + fh.write_all(signature)?; + + koly.code_signature_offset = koly.offset_after_plist(); + koly.code_signature_size = signature.len() as _; + + let mut trailer = [0u8; KOLY_SIZE as usize]; + trailer.pwrite_with(&koly, 0, scroll::BE)?; + + fh.write_all(&trailer)?; + + fh.set_len(koly.code_signature_offset + koly.code_signature_size + KOLY_SIZE as u64)?; + + Ok(()) + } + + /// Create the embedded signature superblob content. + pub fn create_superblob( + &self, + settings: &SigningSettings, + fh: &mut F, + ) -> Result, AppleCodesignError> { + let mut builder = EmbeddedSignatureBuilder::default(); + + for (slot, blob) in self.create_special_blobs()? { + builder.add_blob(slot, blob)?; + } + + builder.add_code_directory( + CodeSigningSlot::CodeDirectory, + self.create_code_directory(settings, fh)?, + )?; + + if let Some((signing_key, signing_cert)) = settings.signing_key() { + builder.create_cms_signature( + signing_key, + signing_cert, + settings.time_stamp_url(), + settings.certificate_chain().iter().cloned(), + settings.signing_time(), + )?; + } + + builder.create_superblob() + } + + /// Create the code directory data structure that is part of the embedded signature. + /// + /// This won't be the final data structure state that is serialized, as it may be + /// amended to in other functions. + pub fn create_code_directory( + &self, + settings: &SigningSettings, + fh: &mut F, + ) -> Result, AppleCodesignError> { + let reader = DmgReader::new(fh)?; + + let mut flags = settings + .code_signature_flags(SettingsScope::Main) + .unwrap_or_else(CodeSignatureFlags::empty); + + if settings.signing_key().is_some() { + flags -= CodeSignatureFlags::ADHOC; + } else { + flags |= CodeSignatureFlags::ADHOC; + } + + warn!("using code signature flags: {:?}", flags); + + let ident = Cow::Owned( + settings + .binary_identifier(SettingsScope::Main) + .ok_or(AppleCodesignError::NoIdentifier)? + .to_string(), + ); + + warn!("using identifier {}", ident); + + let digest_type = settings.digest_type(SettingsScope::Main); + + let code_hashes = vec![reader.digest_content_with(digest_type, fh)?]; + + let koly_digest = reader.koly().digest_for_code_directory(digest_type)?; + + let mut cd = CodeDirectoryBlob { + version: 0x20100, + flags, + code_limit: reader.koly().offset_after_plist() as u32, + digest_size: digest_type.hash_len()? as u8, + digest_type, + page_size: 1, + ident, + code_digests: code_hashes, + ..Default::default() + }; + + cd.set_slot_digest(CodeSigningSlot::RepSpecific, koly_digest)?; + + Ok(cd) + } + + /// Create special blobs that are added to the superblob. + pub fn create_special_blobs( + &self, + ) -> Result, AppleCodesignError> { + Ok(vec![( + CodeSigningSlot::RequirementSet, + RequirementSetBlob::default().into(), + )]) + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/embedded_signature.rs b/3rdparty/apple-codesign-0.29.0/src/embedded_signature.rs new file mode 100644 index 00000000..f993eac7 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/embedded_signature.rs @@ -0,0 +1,1537 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Common embedded signature data structures (superblobs, magic values, etc). +//! +//! This module defines types and data structures that are common to Apple's +//! embedded signature format. +//! +//! Within this module are constants for header magic, definitions of +//! serialized data structures like superblobs and blobs, and some common +//! enumerations. +//! +//! There is no official specification of the Mach-O structure for various +//! code signing primitives. So the definitions in here could diverge from +//! what is actually implemented. +//! +//! The best source of the specification comes from Apple's open source headers, +//! notably cs_blobs.h (e.g. +//! ). +//! (Go to and check for newer versions of xnu +//! to look for new features.) +//! +//! The high-level format of embedded signature data is roughly as follows: +//! +//! * A `SuperBlob` header describes the total length of data and the number of +//! *blob* sections that follow. +//! * An array of `BlobIndex` describing the type and offset of all *blob* sections +//! that follow. The *type* here is a *slot* and describes what type of data the +//! *blob* contains (code directory, entitlements, embedded signature, etc). +//! * N *blob* sections of varying formats and lengths. +//! +//! We only support the [CodeSigningMagic::EmbeddedSignature] magic in the `SuperBlob`, +//! as this is what is used in the wild. (It is even unclear if other magic values +//! can occur in `SuperBlob` headers.) +//! +//! The `EmbeddedSignature` type represents a lightly parsed `SuperBlob`. It +//! provides access to `BlobEntry` which describe the *blob* sections within the +//! super blob. A `BlobEntry` can be parsed into the more concrete `ParsedBlob`, +//! which allows some access to data within each specific blob type. + +use { + crate::{ + code_directory::CodeDirectoryBlob, + code_requirement::{CodeRequirements, RequirementType}, + cryptography::DigestType, + environment_constraints::EncodedEnvironmentConstraints, + AppleCodesignError, Result, + }, + cryptographic_message_syntax::SignedData, + scroll::{IOwrite, Pread}, + std::{borrow::Cow, cmp::Ordering, collections::HashMap, io::Write}, +}; + +/// Defines header magic for various payloads. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum CodeSigningMagic { + /// Code requirement blob. + Requirement, + /// Code requirements blob. + RequirementSet, + /// CodeDirectory blob. + CodeDirectory, + /// Embedded signature. + /// + /// This is often the magic of the SuperBlob. + EmbeddedSignature, + /// Old embedded signature. + EmbeddedSignatureOld, + /// Entitlements blob. + Entitlements, + /// DER encoded entitlements blob. + EntitlementsDer, + /// DER encoded environment constraints. + /// + /// This is how launch constraints and library constraints are encoded. + EnvironmentContraintsDer, + /// Multi-arch collection of embedded signatures. + DetachedSignature, + /// Generic blob wrapper. + /// + /// The CMS signature is stored in this type. + BlobWrapper, + /// Unknown magic. + Unknown(u32), +} + +impl From for CodeSigningMagic { + fn from(v: u32) -> Self { + match v { + 0xfade0c00 => Self::Requirement, + 0xfade0c01 => Self::RequirementSet, + 0xfade0c02 => Self::CodeDirectory, + 0xfade0cc0 => Self::EmbeddedSignature, + 0xfade0b02 => Self::EmbeddedSignatureOld, + 0xfade7171 => Self::Entitlements, + 0xfade7172 => Self::EntitlementsDer, + 0xfade8181 => Self::EnvironmentContraintsDer, + 0xfade0cc1 => Self::DetachedSignature, + 0xfade0b01 => Self::BlobWrapper, + _ => Self::Unknown(v), + } + } +} + +impl From for u32 { + fn from(magic: CodeSigningMagic) -> u32 { + match magic { + CodeSigningMagic::Requirement => 0xfade0c00, + CodeSigningMagic::RequirementSet => 0xfade0c01, + CodeSigningMagic::CodeDirectory => 0xfade0c02, + CodeSigningMagic::EmbeddedSignature => 0xfade0cc0, + CodeSigningMagic::EmbeddedSignatureOld => 0xfade0b02, + CodeSigningMagic::Entitlements => 0xfade7171, + CodeSigningMagic::EntitlementsDer => 0xfade7172, + CodeSigningMagic::EnvironmentContraintsDer => 0xfade8181, + CodeSigningMagic::DetachedSignature => 0xfade0cc1, + CodeSigningMagic::BlobWrapper => 0xfade0b01, + CodeSigningMagic::Unknown(v) => v, + } + } +} + +// The digest formats are encoded as byte values. Implement conversions. + +impl From for DigestType { + fn from(v: u8) -> Self { + match v { + 0 => Self::None, + 1 => Self::Sha1, + 2 => Self::Sha256, + 3 => Self::Sha256Truncated, + 4 => Self::Sha384, + 5 => Self::Sha512, + _ => Self::Unknown(v), + } + } +} + +impl From for u8 { + fn from(v: DigestType) -> u8 { + match v { + DigestType::None => 0, + DigestType::Sha1 => 1, + DigestType::Sha256 => 2, + DigestType::Sha256Truncated => 3, + DigestType::Sha384 => 4, + DigestType::Sha512 => 5, + DigestType::Unknown(v) => v, + } + } +} + +/// A well-known slot within code signing data. +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub enum CodeSigningSlot { + CodeDirectory, + /// Info.plist. + Info, + /// Designated requirements. + RequirementSet, + /// Digest of `CodeRequirements` file (used in bundles). + ResourceDir, + /// Application specific slot. + Application, + /// Entitlements XML plist. + Entitlements, + /// Reserved for disk images. + RepSpecific, + /// Entitlements DER encoded plist. + EntitlementsDer, + /// DER launch constraints on self. + LaunchConstraintsSelf, + /// DER launch constraints on parent. + LaunchConstraintsParent, + /// DER launch constraints on responsible process. + LaunchConstraintsResponsibleProcess, + /// DER launch constraints on libraries loaded in the process. + LibraryConstraints, + + // Everything from here is a slot not encoded in the code directory hashes list. + // REMEMBER TO UPDATE is_code_directory_specials_expressible() if adding a new slot + // here! + /// Alternative code directory slot #0. + /// + /// Used for expressing a code directory using an alternate digest type. + AlternateCodeDirectory0, + AlternateCodeDirectory1, + AlternateCodeDirectory2, + AlternateCodeDirectory3, + AlternateCodeDirectory4, + /// CMS signature. + Signature, + Identification, + /// Notarization ticket. + Ticket, + Unknown(u32), +} + +impl std::fmt::Debug for CodeSigningSlot { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::CodeDirectory => { + f.write_fmt(format_args!("CodeDirectory ({})", u32::from(*self))) + } + Self::Info => f.write_fmt(format_args!("Info ({})", u32::from(*self))), + Self::RequirementSet => { + f.write_fmt(format_args!("RequirementSet ({})", u32::from(*self))) + } + Self::ResourceDir => f.write_fmt(format_args!("Resources ({})", u32::from(*self))), + Self::Application => f.write_fmt(format_args!("Application ({})", u32::from(*self))), + Self::Entitlements => f.write_fmt(format_args!("Entitlements ({})", u32::from(*self))), + Self::RepSpecific => f.write_fmt(format_args!("Rep Specific ({})", u32::from(*self))), + Self::EntitlementsDer => { + f.write_fmt(format_args!("DER Entitlements ({})", u32::from(*self))) + } + Self::LaunchConstraintsSelf => f.write_fmt(format_args!( + "DER Launch Constraints on Self ({})", + u32::from(*self) + )), + Self::LaunchConstraintsParent => f.write_fmt(format_args!( + "DER Launch Constraints on Parent ({})", + u32::from(*self) + )), + Self::LaunchConstraintsResponsibleProcess => f.write_fmt(format_args!( + "DER Launch Constraints on Responsible Process ({})", + u32::from(*self) + )), + Self::LibraryConstraints => f.write_fmt(format_args!( + "DER Launch Constraints on Loaded Libraries ({})", + u32::from(*self) + )), + Self::AlternateCodeDirectory0 => f.write_fmt(format_args!( + "CodeDirectory Alternate #0 ({})", + u32::from(*self) + )), + Self::AlternateCodeDirectory1 => f.write_fmt(format_args!( + "CodeDirectory Alternate #1 ({})", + u32::from(*self) + )), + Self::AlternateCodeDirectory2 => f.write_fmt(format_args!( + "CodeDirectory Alternate #2 ({})", + u32::from(*self) + )), + Self::AlternateCodeDirectory3 => f.write_fmt(format_args!( + "CodeDirectory Alternate #3 ({})", + u32::from(*self) + )), + Self::AlternateCodeDirectory4 => f.write_fmt(format_args!( + "CodeDirectory Alternate #4 ({})", + u32::from(*self) + )), + Self::Signature => f.write_fmt(format_args!("CMS Signature ({})", u32::from(*self))), + Self::Identification => { + f.write_fmt(format_args!("Identification ({})", u32::from(*self))) + } + Self::Ticket => f.write_fmt(format_args!("Ticket ({})", u32::from(*self))), + Self::Unknown(value) => f.write_fmt(format_args!("Unknown ({value})")), + } + } +} + +impl From for CodeSigningSlot { + fn from(v: u32) -> Self { + match v { + 0 => Self::CodeDirectory, + 1 => Self::Info, + 2 => Self::RequirementSet, + 3 => Self::ResourceDir, + 4 => Self::Application, + 5 => Self::Entitlements, + 6 => Self::RepSpecific, + 7 => Self::EntitlementsDer, + 8 => Self::LaunchConstraintsSelf, + 9 => Self::LaunchConstraintsParent, + 10 => Self::LaunchConstraintsResponsibleProcess, + 11 => Self::LibraryConstraints, + 0x1000 => Self::AlternateCodeDirectory0, + 0x1001 => Self::AlternateCodeDirectory1, + 0x1002 => Self::AlternateCodeDirectory2, + 0x1003 => Self::AlternateCodeDirectory3, + 0x1004 => Self::AlternateCodeDirectory4, + 0x10000 => Self::Signature, + 0x10001 => Self::Identification, + 0x10002 => Self::Ticket, + _ => Self::Unknown(v), + } + } +} + +impl From for u32 { + fn from(v: CodeSigningSlot) -> Self { + match v { + CodeSigningSlot::CodeDirectory => 0, + CodeSigningSlot::Info => 1, + CodeSigningSlot::RequirementSet => 2, + CodeSigningSlot::ResourceDir => 3, + CodeSigningSlot::Application => 4, + CodeSigningSlot::Entitlements => 5, + CodeSigningSlot::RepSpecific => 6, + CodeSigningSlot::EntitlementsDer => 7, + CodeSigningSlot::LaunchConstraintsSelf => 8, + CodeSigningSlot::LaunchConstraintsParent => 9, + CodeSigningSlot::LaunchConstraintsResponsibleProcess => 10, + CodeSigningSlot::LibraryConstraints => 11, + CodeSigningSlot::AlternateCodeDirectory0 => 0x1000, + CodeSigningSlot::AlternateCodeDirectory1 => 0x1001, + CodeSigningSlot::AlternateCodeDirectory2 => 0x1002, + CodeSigningSlot::AlternateCodeDirectory3 => 0x1003, + CodeSigningSlot::AlternateCodeDirectory4 => 0x1004, + CodeSigningSlot::Signature => 0x10000, + CodeSigningSlot::Identification => 0x10001, + CodeSigningSlot::Ticket => 0x10002, + CodeSigningSlot::Unknown(v) => v, + } + } +} + +impl PartialOrd for CodeSigningSlot { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for CodeSigningSlot { + fn cmp(&self, other: &Self) -> Ordering { + u32::from(*self).cmp(&u32::from(*other)) + } +} + +impl CodeSigningSlot { + /// Whether this slot has external data (as opposed to provided via a blob). + pub fn has_external_content(&self) -> bool { + matches!(self, Self::Info | Self::ResourceDir) + } + + /// Whether this slot is for holding an alternative code directory. + pub fn is_alternative_code_directory(&self) -> bool { + matches!( + self, + CodeSigningSlot::AlternateCodeDirectory0 + | CodeSigningSlot::AlternateCodeDirectory1 + | CodeSigningSlot::AlternateCodeDirectory2 + | CodeSigningSlot::AlternateCodeDirectory3 + | CodeSigningSlot::AlternateCodeDirectory4 + ) + } + + /// Whether this slot's digest is expressed in code directories list of special slot digests. + pub fn is_code_directory_specials_expressible(&self) -> bool { + *self >= Self::Info && *self <= Self::LibraryConstraints + } +} + +#[repr(C)] +#[derive(Clone, Pread)] +struct BlobIndex { + /// Corresponds to a [CodeSigningSlot] variant. + typ: u32, + offset: u32, +} + +impl std::fmt::Debug for BlobIndex { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_struct("BlobIndex") + .field("type", &CodeSigningSlot::from(self.typ)) + .field("offset", &self.offset) + .finish() + } +} + +/// Read the header from a Blob. +/// +/// Blobs begin with a u32 magic and u32 length, inclusive. +fn read_blob_header(data: &[u8]) -> Result<(u32, usize, &[u8]), scroll::Error> { + let magic = data.pread_with(0, scroll::BE)?; + let length = data.pread_with::(4, scroll::BE)?; + + Ok((magic, length as usize, &data[8..])) +} + +pub(crate) fn read_and_validate_blob_header<'a>( + data: &'a [u8], + expected_magic: u32, + what: &'static str, +) -> Result<&'a [u8], AppleCodesignError> { + let (magic, _, data) = read_blob_header(data)?; + + if magic != expected_magic { + Err(AppleCodesignError::BadMagic(what)) + } else { + Ok(data) + } +} + +/// Create the binary content for a SuperBlob. +pub fn create_superblob<'a>( + magic: CodeSigningMagic, + blobs: impl Iterator)>, +) -> Result, AppleCodesignError> { + // Makes offset calculation easier. + let blobs = blobs.collect::>(); + + let mut cursor = std::io::Cursor::new(Vec::::new()); + + let mut blob_data = Vec::new(); + // magic + total length + blob count. + let mut total_length: u32 = 4 + 4 + 4; + // 8 bytes for each blob index. + total_length += 8 * blobs.len() as u32; + + let mut indices = Vec::with_capacity(blobs.len()); + + for (slot, blob) in blobs { + blob_data.push(blob); + + indices.push(BlobIndex { + typ: u32::from(*slot), + offset: total_length, + }); + + total_length += blob.len() as u32; + } + + cursor.iowrite_with(u32::from(magic), scroll::BE)?; + cursor.iowrite_with(total_length, scroll::BE)?; + cursor.iowrite_with(indices.len() as u32, scroll::BE)?; + for index in indices { + cursor.iowrite_with(index.typ, scroll::BE)?; + cursor.iowrite_with(index.offset, scroll::BE)?; + } + for data in blob_data { + cursor.write_all(data)?; + } + + Ok(cursor.into_inner()) +} + +/// Represents a single blob as defined by a SuperBlob index entry. +/// +/// Instances have copies of their own index info, including the relative +/// order, slot type, and start offset within the `SuperBlob`. +/// +/// The blob data is unparsed in this type. The blob payloads can be +/// turned into [ParsedBlob] via `.try_into()`. +#[derive(Clone)] +pub struct BlobEntry<'a> { + /// Our blob index within the `SuperBlob`. + pub index: usize, + + /// The slot type. + pub slot: CodeSigningSlot, + + /// Our start offset within the `SuperBlob`. + /// + /// First byte is start of our magic. + pub offset: usize, + + /// The magic value appearing at the beginning of the blob. + pub magic: CodeSigningMagic, + + /// The length of the blob payload. + pub length: usize, + + /// The raw data in this blob, including magic and length. + pub data: &'a [u8], +} + +impl<'a> std::fmt::Debug for BlobEntry<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_struct("BlobEntry") + .field("index", &self.index) + .field("slot", &self.slot) + .field("offset", &self.offset) + .field("length", &self.length) + .field("magic", &self.magic) + // .field("data", &self.data) + .finish() + } +} + +impl<'a> BlobEntry<'a> { + /// Attempt to convert to a [ParsedBlob]. + pub fn into_parsed_blob(self) -> Result, AppleCodesignError> { + self.try_into() + } + + /// Obtain the payload of this blob. + /// + /// This is the data in the blob without the blob header. + pub fn payload(&self) -> Result<&'a [u8], AppleCodesignError> { + Ok(read_blob_header(self.data)?.2) + } + + /// Compute the content digest of this blob using the specified hash type. + pub fn digest_with(&self, hash: DigestType) -> Result, AppleCodesignError> { + hash.digest_data(self.data) + } +} + +/// Provides common features for a parsed blob type. +pub trait Blob<'a> +where + Self: Sized, +{ + /// The header magic that identifies this format. + fn magic() -> u32; + + /// Attempt to construct an instance by parsing a bytes slice. + /// + /// The slice begins with the 8 byte blob header denoting the magic + /// and length. + fn from_blob_bytes(data: &'a [u8]) -> Result; + + /// Serialize the payload of this blob to bytes. + /// + /// Does not include the magic or length header fields common to blobs. + fn serialize_payload(&self) -> Result, AppleCodesignError>; + + /// Serialize this blob to bytes. + /// + /// This is [Blob::serialize_payload] with the blob magic and length + /// prepended. + fn to_blob_bytes(&self) -> Result, AppleCodesignError> { + let mut res = Vec::new(); + res.iowrite_with(Self::magic(), scroll::BE)?; + + let payload = self.serialize_payload()?; + // Length includes our own header. + res.iowrite_with(payload.len() as u32 + 8, scroll::BE)?; + + res.extend(payload); + + Ok(res) + } + + /// Obtain the digest of the blob using the specified hasher. + /// + /// Default implementation calls [Blob::to_blob_bytes] and digests that, which + /// should always be correct. + fn digest_with(&self, hash_type: DigestType) -> Result, AppleCodesignError> { + hash_type.digest_data(&self.to_blob_bytes()?) + } +} + +/// Represents a Requirement blob. +/// +/// `csreq -b` will emit instances of this blob, header magic and all. So data generated +/// by `csreq -b` can be fed into [RequirementBlob.from_blob_bytes] to obtain an instance. +pub struct RequirementBlob<'a> { + pub data: Cow<'a, [u8]>, +} + +impl<'a> Blob<'a> for RequirementBlob<'a> { + fn magic() -> u32 { + u32::from(CodeSigningMagic::Requirement) + } + + fn from_blob_bytes(data: &'a [u8]) -> Result { + let data = read_and_validate_blob_header(data, Self::magic(), "requirement blob")?; + + Ok(Self { data: data.into() }) + } + + fn serialize_payload(&self) -> Result, AppleCodesignError> { + Ok(self.data.to_vec()) + } +} + +impl<'a> std::fmt::Debug for RequirementBlob<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("RequirementBlob({})", hex::encode(&self.data))) + } +} + +impl<'a> RequirementBlob<'a> { + pub fn to_owned(&self) -> RequirementBlob<'static> { + RequirementBlob { + data: Cow::Owned(self.data.clone().into_owned()), + } + } + + /// Parse the binary data in this blob into Code Requirement expressions. + pub fn parse_expressions(&self) -> Result { + Ok(CodeRequirements::parse_binary(&self.data)?.0) + } +} + +/// Represents a Requirement set blob. +/// +/// A Requirement set blob contains nested Requirement blobs. +#[derive(Debug, Default)] +pub struct RequirementSetBlob<'a> { + pub requirements: HashMap>, +} + +impl<'a> Blob<'a> for RequirementSetBlob<'a> { + fn magic() -> u32 { + u32::from(CodeSigningMagic::RequirementSet) + } + + fn from_blob_bytes(data: &'a [u8]) -> Result { + read_and_validate_blob_header(data, Self::magic(), "requirement set blob")?; + + // There are other blobs nested within. A u32 denotes how many there are. + // Then there is an array of N (u32, u32) denoting the type and + // offset of each. + let offset = &mut 8; + let count = data.gread_with::(offset, scroll::BE)?; + + let mut indices = Vec::with_capacity(count as usize); + for _ in 0..count { + indices.push(( + data.gread_with::(offset, scroll::BE)?, + data.gread_with::(offset, scroll::BE)?, + )); + } + + let mut requirements = HashMap::with_capacity(indices.len()); + + for (i, (flavor, offset)) in indices.iter().enumerate() { + let typ = RequirementType::from(*flavor); + + let end_offset = if i == indices.len() - 1 { + data.len() + } else { + indices[i + 1].1 as usize + }; + + let requirement_data = &data[*offset as usize..end_offset]; + + requirements.insert(typ, RequirementBlob::from_blob_bytes(requirement_data)?); + } + + Ok(Self { requirements }) + } + + fn serialize_payload(&self) -> Result, AppleCodesignError> { + let mut res = Vec::new(); + + // The index contains blob relative offsets. To know what the start offset will + // be, we calculate the total index size. + let data_start_offset = 8 + 4 + (8 * self.requirements.len() as u32); + let mut written_requirements_data = 0; + + res.iowrite_with(self.requirements.len() as u32, scroll::BE)?; + + // Write an index of all nested requirement blobs. + for (typ, requirement) in &self.requirements { + res.iowrite_with(u32::from(*typ), scroll::BE)?; + res.iowrite_with(data_start_offset + written_requirements_data, scroll::BE)?; + written_requirements_data += requirement.to_blob_bytes()?.len() as u32; + } + + // Now write every requirement's raw data. + for requirement in self.requirements.values() { + res.write_all(&requirement.to_blob_bytes()?)?; + } + + Ok(res) + } +} + +impl<'a> RequirementSetBlob<'a> { + pub fn to_owned(&self) -> RequirementSetBlob<'static> { + RequirementSetBlob { + requirements: self + .requirements + .iter() + .map(|(flavor, blob)| (*flavor, blob.to_owned())) + .collect::>(), + } + } + + /// Set the requirements for a given [RequirementType]. + pub fn set_requirements(&mut self, slot: RequirementType, blob: RequirementBlob<'a>) { + self.requirements.insert(slot, blob); + } +} + +/// Represents an embedded signature. +#[derive(Debug)] +pub struct EmbeddedSignatureBlob<'a> { + data: &'a [u8], +} + +impl<'a> Blob<'a> for EmbeddedSignatureBlob<'a> { + fn magic() -> u32 { + u32::from(CodeSigningMagic::EmbeddedSignature) + } + + fn from_blob_bytes(data: &'a [u8]) -> Result { + Ok(Self { + data: read_and_validate_blob_header(data, Self::magic(), "embedded signature blob")?, + }) + } + + fn serialize_payload(&self) -> Result, AppleCodesignError> { + Ok(self.data.to_vec()) + } +} + +/// An old embedded signature. +#[derive(Debug)] +pub struct EmbeddedSignatureOldBlob<'a> { + data: &'a [u8], +} + +impl<'a> Blob<'a> for EmbeddedSignatureOldBlob<'a> { + fn magic() -> u32 { + u32::from(CodeSigningMagic::EmbeddedSignatureOld) + } + + fn from_blob_bytes(data: &'a [u8]) -> Result { + Ok(Self { + data: read_and_validate_blob_header( + data, + Self::magic(), + "old embedded signature blob", + )?, + }) + } + + fn serialize_payload(&self) -> Result, AppleCodesignError> { + Ok(self.data.to_vec()) + } +} + +/// Represents an Entitlements blob. +/// +/// An entitlements blob contains an XML plist with a dict. Keys are +/// strings of the entitlements being requested and values appear to be +/// simple bools. +#[derive(Debug)] +pub struct EntitlementsBlob<'a> { + plist: Cow<'a, str>, +} + +impl<'a> Blob<'a> for EntitlementsBlob<'a> { + fn magic() -> u32 { + u32::from(CodeSigningMagic::Entitlements) + } + + fn from_blob_bytes(data: &'a [u8]) -> Result { + let data = read_and_validate_blob_header(data, Self::magic(), "entitlements blob")?; + let s = std::str::from_utf8(data).map_err(AppleCodesignError::EntitlementsBadUtf8)?; + + Ok(Self { plist: s.into() }) + } + + fn serialize_payload(&self) -> Result, AppleCodesignError> { + Ok(self.plist.as_bytes().to_vec()) + } +} + +impl<'a> EntitlementsBlob<'a> { + /// Construct an instance using any string as the payload. + pub fn from_string(s: &(impl ToString + ?Sized)) -> Self { + Self { + plist: s.to_string().into(), + } + } + + /// Obtain the plist representation as a string. + pub fn as_str(&self) -> &str { + &self.plist + } +} + +impl<'a> std::fmt::Display for EntitlementsBlob<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.plist) + } +} + +#[derive(Debug)] +pub struct EntitlementsDerBlob<'a> { + der: Cow<'a, [u8]>, +} + +impl<'a> Blob<'a> for EntitlementsDerBlob<'a> { + fn magic() -> u32 { + u32::from(CodeSigningMagic::EntitlementsDer) + } + + fn from_blob_bytes(data: &'a [u8]) -> Result { + let der = read_and_validate_blob_header(data, Self::magic(), "DER entitlements blob")?; + + Ok(Self { der: der.into() }) + } + + fn serialize_payload(&self) -> Result, AppleCodesignError> { + Ok(self.der.to_vec()) + } +} + +impl<'a> EntitlementsDerBlob<'a> { + /// Construct an instance from a [plist::Value]. + /// + /// Not all plists can be encoded to this blob as not all plist value types can + /// be encoded to DER. If a plist with an illegal value is passed in, this + /// function will error, as DER encoding is performed immediately. + /// + /// The outermost plist value should be a dictionary. + pub fn from_plist(v: &plist::Value) -> Result { + let der = crate::plist_der::der_encode_plist(v)?; + + Ok(Self { der: der.into() }) + } + + /// Attempt to parse and resolve the DER data into a plist. + pub fn parse_der(&self) -> Result { + crate::plist_der::der_decode_plist(self.der.as_ref()) + } + + /// Parse the plist from DER and format to XML. + pub fn plist_xml(&self) -> Result, AppleCodesignError> { + let mut buffer = vec![]; + self.parse_der()?.to_writer_xml(&mut buffer)?; + + Ok(buffer) + } +} + +/// A blob holding DER encoded launch/library constraints. +/// +/// The inner data is a plist. +#[derive(Debug)] +pub struct ConstraintsDerBlob<'a> { + der: Cow<'a, [u8]>, +} + +impl<'a> Blob<'a> for ConstraintsDerBlob<'a> { + fn magic() -> u32 { + u32::from(CodeSigningMagic::EnvironmentContraintsDer) + } + + fn from_blob_bytes(data: &'a [u8]) -> Result { + let der = read_and_validate_blob_header( + data, + Self::magic(), + "DER encoded environment constraints blob", + )?; + + Ok(Self { der: der.into() }) + } + + fn serialize_payload(&self) -> Result> { + Ok(self.der.to_vec()) + } +} + +impl<'a> ConstraintsDerBlob<'a> { + /// Construct an instance from a [EncodedEnvironmentConstraints] instance. + pub fn from_encoded_constraints(v: &EncodedEnvironmentConstraints) -> Result { + let der = v.der_encode()?; + + Ok(Self { der: der.into() }) + } + + /// Attempt to parse and resolve the DER data into a plist. + pub fn parse_der_plist(&self) -> Result { + crate::plist_der::der_decode_plist(self.der.as_ref()) + } + + /// Attempt to parse DER into an [EncodedEnvironmentConstraints] instance. + pub fn parse_encoded_constraints(&self) -> Result { + EncodedEnvironmentConstraints::from_der(self.der.as_ref()) + } + + /// Parse the plist from DER and format to XML. + pub fn plist_xml(&self) -> Result> { + let mut buffer = vec![]; + self.parse_der_plist()?.to_writer_xml(&mut buffer)?; + + Ok(buffer) + } +} + +/// A detached signature. +#[derive(Debug)] +pub struct DetachedSignatureBlob<'a> { + data: &'a [u8], +} + +impl<'a> Blob<'a> for DetachedSignatureBlob<'a> { + fn magic() -> u32 { + u32::from(CodeSigningMagic::DetachedSignature) + } + + fn from_blob_bytes(data: &'a [u8]) -> Result { + Ok(Self { + data: read_and_validate_blob_header(data, Self::magic(), "detached signature blob")?, + }) + } + + fn serialize_payload(&self) -> Result, AppleCodesignError> { + Ok(self.data.to_vec()) + } +} + +/// Represents a generic blob wrapper. +pub struct BlobWrapperBlob<'a> { + data: Cow<'a, [u8]>, +} + +impl<'a> Blob<'a> for BlobWrapperBlob<'a> { + fn magic() -> u32 { + u32::from(CodeSigningMagic::BlobWrapper) + } + + fn from_blob_bytes(data: &'a [u8]) -> Result { + Ok(Self { + data: read_and_validate_blob_header(data, Self::magic(), "blob wrapper blob")?.into(), + }) + } + + fn serialize_payload(&self) -> Result, AppleCodesignError> { + Ok(self.data.to_vec()) + } +} + +impl<'a> std::fmt::Debug for BlobWrapperBlob<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("{}", hex::encode(&self.data))) + } +} + +impl<'a> BlobWrapperBlob<'a> { + /// Construct an instance where the payload (post blob header) is given data. + pub fn from_data_borrowed(data: &'a [u8]) -> BlobWrapperBlob<'a> { + Self { data: data.into() } + } +} + +impl BlobWrapperBlob<'static> { + /// Construct an instance with payload data. + pub fn from_data_owned(data: Vec) -> BlobWrapperBlob<'static> { + Self { data: data.into() } + } +} + +/// Represents an unknown blob type. +pub struct OtherBlob<'a> { + pub magic: u32, + pub data: &'a [u8], +} + +impl<'a> Blob<'a> for OtherBlob<'a> { + fn magic() -> u32 { + // Use a placeholder magic value because there is no self bind here. + u32::MAX + } + + fn from_blob_bytes(data: &'a [u8]) -> Result { + let (magic, _, data) = read_blob_header(data)?; + + Ok(Self { magic, data }) + } + + fn serialize_payload(&self) -> Result, AppleCodesignError> { + Ok(self.data.to_vec()) + } + + // We need to implement this for custom magic serialization. + fn to_blob_bytes(&self) -> Result, AppleCodesignError> { + let mut res = Vec::with_capacity(self.data.len() + 8); + res.iowrite_with(self.magic, scroll::BE)?; + res.iowrite_with(self.data.len() as u32 + 8, scroll::BE)?; + res.write_all(self.data)?; + + Ok(res) + } +} + +impl<'a> std::fmt::Debug for OtherBlob<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("{}", hex::encode(self.data))) + } +} + +/// Represents a single, parsed Blob entry/slot. +/// +/// Each variant corresponds to a [CodeSigningMagic] blob type. +#[derive(Debug)] +pub enum BlobData<'a> { + Requirement(Box>), + RequirementSet(Box>), + CodeDirectory(Box>), + EmbeddedSignature(Box>), + EmbeddedSignatureOld(Box>), + Entitlements(Box>), + EntitlementsDer(Box>), + ConstraintsDer(Box>), + DetachedSignature(Box>), + BlobWrapper(Box>), + Other(Box>), +} + +impl<'a> Blob<'a> for BlobData<'a> { + fn magic() -> u32 { + u32::MAX + } + + /// Parse blob data by reading its magic and feeding into magic-specific parser. + fn from_blob_bytes(data: &'a [u8]) -> Result { + let (magic, length, _) = read_blob_header(data)?; + + // This should be a no-op. But it could (correctly) cause a panic if the + // advertised length is incorrect and we would incur a buffer overrun. + let data = &data[0..length]; + + let magic = CodeSigningMagic::from(magic); + + Ok(match magic { + CodeSigningMagic::Requirement => { + Self::Requirement(Box::new(RequirementBlob::from_blob_bytes(data)?)) + } + CodeSigningMagic::RequirementSet => { + Self::RequirementSet(Box::new(RequirementSetBlob::from_blob_bytes(data)?)) + } + CodeSigningMagic::CodeDirectory => { + Self::CodeDirectory(Box::new(CodeDirectoryBlob::from_blob_bytes(data)?)) + } + CodeSigningMagic::EmbeddedSignature => { + Self::EmbeddedSignature(Box::new(EmbeddedSignatureBlob::from_blob_bytes(data)?)) + } + CodeSigningMagic::EmbeddedSignatureOld => Self::EmbeddedSignatureOld(Box::new( + EmbeddedSignatureOldBlob::from_blob_bytes(data)?, + )), + CodeSigningMagic::Entitlements => { + Self::Entitlements(Box::new(EntitlementsBlob::from_blob_bytes(data)?)) + } + CodeSigningMagic::EntitlementsDer => { + Self::EntitlementsDer(Box::new(EntitlementsDerBlob::from_blob_bytes(data)?)) + } + CodeSigningMagic::EnvironmentContraintsDer => { + Self::ConstraintsDer(Box::new(ConstraintsDerBlob::from_blob_bytes(data)?)) + } + CodeSigningMagic::DetachedSignature => { + Self::DetachedSignature(Box::new(DetachedSignatureBlob::from_blob_bytes(data)?)) + } + CodeSigningMagic::BlobWrapper => { + Self::BlobWrapper(Box::new(BlobWrapperBlob::from_blob_bytes(data)?)) + } + _ => Self::Other(Box::new(OtherBlob::from_blob_bytes(data)?)), + }) + } + + fn serialize_payload(&self) -> Result, AppleCodesignError> { + match self { + Self::Requirement(b) => b.serialize_payload(), + Self::RequirementSet(b) => b.serialize_payload(), + Self::CodeDirectory(b) => b.serialize_payload(), + Self::EmbeddedSignature(b) => b.serialize_payload(), + Self::EmbeddedSignatureOld(b) => b.serialize_payload(), + Self::Entitlements(b) => b.serialize_payload(), + Self::EntitlementsDer(b) => b.serialize_payload(), + Self::ConstraintsDer(b) => b.serialize_payload(), + Self::DetachedSignature(b) => b.serialize_payload(), + Self::BlobWrapper(b) => b.serialize_payload(), + Self::Other(b) => b.serialize_payload(), + } + } + + fn to_blob_bytes(&self) -> Result, AppleCodesignError> { + match self { + Self::Requirement(b) => b.to_blob_bytes(), + Self::RequirementSet(b) => b.to_blob_bytes(), + Self::CodeDirectory(b) => b.to_blob_bytes(), + Self::EmbeddedSignature(b) => b.to_blob_bytes(), + Self::EmbeddedSignatureOld(b) => b.to_blob_bytes(), + Self::Entitlements(b) => b.to_blob_bytes(), + Self::EntitlementsDer(b) => b.to_blob_bytes(), + Self::ConstraintsDer(b) => b.to_blob_bytes(), + Self::DetachedSignature(b) => b.to_blob_bytes(), + Self::BlobWrapper(b) => b.to_blob_bytes(), + Self::Other(b) => b.to_blob_bytes(), + } + } +} + +impl<'a> From> for BlobData<'a> { + fn from(b: RequirementBlob<'a>) -> Self { + Self::Requirement(Box::new(b)) + } +} + +impl<'a> From> for BlobData<'a> { + fn from(b: RequirementSetBlob<'a>) -> Self { + Self::RequirementSet(Box::new(b)) + } +} + +impl<'a> From> for BlobData<'a> { + fn from(b: CodeDirectoryBlob<'a>) -> Self { + Self::CodeDirectory(Box::new(b)) + } +} + +impl<'a> From> for BlobData<'a> { + fn from(b: EmbeddedSignatureBlob<'a>) -> Self { + Self::EmbeddedSignature(Box::new(b)) + } +} + +impl<'a> From> for BlobData<'a> { + fn from(b: EmbeddedSignatureOldBlob<'a>) -> Self { + Self::EmbeddedSignatureOld(Box::new(b)) + } +} + +impl<'a> From> for BlobData<'a> { + fn from(b: EntitlementsBlob<'a>) -> Self { + Self::Entitlements(Box::new(b)) + } +} + +impl<'a> From> for BlobData<'a> { + fn from(b: EntitlementsDerBlob<'a>) -> Self { + Self::EntitlementsDer(Box::new(b)) + } +} + +impl<'a> From> for BlobData<'a> { + fn from(b: ConstraintsDerBlob<'a>) -> Self { + Self::ConstraintsDer(Box::new(b)) + } +} + +impl<'a> From> for BlobData<'a> { + fn from(b: DetachedSignatureBlob<'a>) -> Self { + Self::DetachedSignature(Box::new(b)) + } +} + +impl<'a> From> for BlobData<'a> { + fn from(b: BlobWrapperBlob<'a>) -> Self { + Self::BlobWrapper(Box::new(b)) + } +} + +impl<'a> From> for BlobData<'a> { + fn from(b: OtherBlob<'a>) -> Self { + Self::Other(Box::new(b)) + } +} + +/// Represents the parsed content of a blob entry. +#[derive(Debug)] +pub struct ParsedBlob<'a> { + /// The blob record this blob came from. + pub blob_entry: BlobEntry<'a>, + + /// The parsed blob data. + pub blob: BlobData<'a>, +} + +impl<'a> ParsedBlob<'a> { + /// Compute the content digest of this blob using the specified hash type. + pub fn digest_with(&self, hash: DigestType) -> Result, AppleCodesignError> { + hash.digest_data(self.blob_entry.data) + } +} + +impl<'a> TryFrom> for ParsedBlob<'a> { + type Error = AppleCodesignError; + + fn try_from(blob_entry: BlobEntry<'a>) -> Result { + let blob = BlobData::from_blob_bytes(blob_entry.data)?; + + Ok(Self { blob_entry, blob }) + } +} + +/// Represents Apple's common embedded code signature data structures. +/// +/// This type represents a lightly parsed `SuperBlob` with [CodeSigningMagic::EmbeddedSignature]. +/// It is the most common embedded signature data format you are likely to encounter. +pub struct EmbeddedSignature<'a> { + /// Magic value from header. + pub magic: CodeSigningMagic, + /// Length of this super blob. + pub length: u32, + /// Number of blobs in this super blob. + pub count: u32, + + /// Raw data backing this super blob. + pub data: &'a [u8], + + /// All the blobs within this super blob. + pub blobs: Vec>, +} + +impl<'a> std::fmt::Debug for EmbeddedSignature<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_struct("SuperBlob") + .field("magic", &self.magic) + .field("length", &self.length) + .field("count", &self.count) + .field("blobs", &self.blobs) + .finish() + } +} + +// There are other impl blocks for this structure in other modules. +impl<'a> EmbeddedSignature<'a> { + /// Attempt to parse an embedded signature super blob from data. + /// + /// The argument to this function is likely the subset of the + /// `__LINKEDIT` Mach-O section that the `LC_CODE_SIGNATURE` load instructions + /// points it. + pub fn from_bytes(data: &'a [u8]) -> Result { + let offset = &mut 0; + + // Parse the 3 fields from the SuperBlob. + let magic = data.gread_with::(offset, scroll::BE)?.into(); + + if magic != CodeSigningMagic::EmbeddedSignature { + return Err(AppleCodesignError::BadMagic( + "embedded signature super blob", + )); + } + + let length = data.gread_with(offset, scroll::BE)?; + let count = data.gread_with(offset, scroll::BE)?; + + // Following the SuperBlob header is an array of .count BlobIndex defining + // the Blob that follow. + // + // The BlobIndex doesn't declare the length of each Blob. However, it appears + // the first 8 bytes of each blob contain the u32 magic and u32 length. + // We do parse those here and set the blob length/slice accordingly. However, + // we take an extra level of precaution by first computing a slice that doesn't + // overrun into the next blob or past the end of the input buffer. This + // helps detect invalid length advertisements in the blob payload. + let mut blob_indices = Vec::with_capacity(count as usize); + for _ in 0..count { + blob_indices.push(data.gread_with::(offset, scroll::BE)?); + } + + let mut blobs = Vec::with_capacity(blob_indices.len()); + + for (i, index) in blob_indices.iter().enumerate() { + let end_offset = if i == blob_indices.len() - 1 { + data.len() + } else { + blob_indices[i + 1].offset as usize + }; + + let full_slice = &data[index.offset as usize..end_offset]; + let (magic, blob_length, _) = read_blob_header(full_slice)?; + + // Self-reported length can't be greater than the data we have. + let blob_data = match blob_length.cmp(&full_slice.len()) { + Ordering::Greater => { + return Err(AppleCodesignError::SuperblobMalformed); + } + Ordering::Equal => full_slice, + Ordering::Less => &full_slice[0..blob_length], + }; + + blobs.push(BlobEntry { + index: i, + slot: index.typ.into(), + offset: index.offset as usize, + magic: magic.into(), + length: blob_length, + data: blob_data, + }); + } + + Ok(Self { + magic, + length, + count, + data, + blobs, + }) + } + + /// Find the first occurrence of the specified slot. + pub fn find_slot(&self, slot: CodeSigningSlot) -> Option<&BlobEntry<'a>> { + self.blobs.iter().find(|e| e.slot == slot) + } + + pub fn find_slot_parsed( + &self, + slot: CodeSigningSlot, + ) -> Result>, AppleCodesignError> { + if let Some(entry) = self.find_slot(slot) { + Ok(Some(entry.clone().into_parsed_blob()?)) + } else { + Ok(None) + } + } + + /// Attempt to resolve the primary `CodeDirectoryBlob` for this signature data. + /// + /// Returns Err on data parsing error or if the blob slot didn't contain a code + /// directory. + /// + /// Returns `Ok(None)` if there is no code directory slot. + pub fn code_directory(&self) -> Result>>, AppleCodesignError> { + if let Some(parsed) = self.find_slot_parsed(CodeSigningSlot::CodeDirectory)? { + if let BlobData::CodeDirectory(cd) = parsed.blob { + Ok(Some(cd)) + } else { + Err(AppleCodesignError::BadMagic("code directory blob")) + } + } else { + Ok(None) + } + } + + /// Obtain code directories occupying alternative slots. + /// + /// Embedded signatures set aside a few slots for alternate code directory data structures. + /// This method will resolve any that are present. + pub fn alternate_code_directories( + &self, + ) -> Result>)>, AppleCodesignError> { + let slots = [ + CodeSigningSlot::AlternateCodeDirectory0, + CodeSigningSlot::AlternateCodeDirectory1, + CodeSigningSlot::AlternateCodeDirectory2, + CodeSigningSlot::AlternateCodeDirectory3, + CodeSigningSlot::AlternateCodeDirectory4, + ]; + + let mut res = vec![]; + + for slot in slots { + if let Some(parsed) = self.find_slot_parsed(slot)? { + if let BlobData::CodeDirectory(cd) = parsed.blob { + res.push((slot, cd)); + } else { + return Err(AppleCodesignError::BadMagic( + "wrong blob magic in alternative code directory slot", + )); + } + } + } + + Ok(res) + } + + /// Resolve all code directories in this signature. + pub fn all_code_directories( + &self, + ) -> Result>)>, AppleCodesignError> { + let mut res = vec![]; + + if let Some(cd) = self.code_directory()? { + res.push((CodeSigningSlot::CodeDirectory, cd)); + } + + res.extend(self.alternate_code_directories()?); + + Ok(res) + } + + /// Attempt to resolve a code directory containing digests of the specified type. + pub fn code_directory_for_digest( + &self, + digest: DigestType, + ) -> Result>>, AppleCodesignError> { + for (_, cd) in self.all_code_directories()? { + if cd.digest_type == digest { + return Ok(Some(cd)); + } + } + + Ok(None) + } + + /// Attempt to resolve the preferred code directory for this binary. + /// + /// Attempts to resolve the SHA-256 variant first, falling back to SHA-1 on failure, and + /// falling back to the primary CD slot before erroring if no CD is present. + pub fn preferred_code_directory( + &self, + ) -> Result>, AppleCodesignError> { + if let Some(cd) = self.code_directory_for_digest(DigestType::Sha256)? { + Ok(cd) + } else if let Some(cd) = self.code_directory_for_digest(DigestType::Sha1)? { + Ok(cd) + } else if let Some(cd) = self.code_directory()? { + Ok(cd) + } else { + Err(AppleCodesignError::BinaryNoCodeDirectory) + } + } + + /// Attempt to resolve a parsed [EntitlementsBlob] for this signature data. + /// + /// Returns Err on data parsing error or if the blob slot didn't contain an entitlments + /// blob. + /// + /// Returns `Ok(None)` if there is no entitlements slot. + pub fn entitlements(&self) -> Result>>, AppleCodesignError> { + if let Some(parsed) = self.find_slot_parsed(CodeSigningSlot::Entitlements)? { + if let BlobData::Entitlements(entitlements) = parsed.blob { + Ok(Some(entitlements)) + } else { + Err(AppleCodesignError::BadMagic("entitlements blob")) + } + } else { + Ok(None) + } + } + + /// Attempt to resolve a parsed [EntitlementsDerBlob] for this signature. + pub fn entitlements_der( + &self, + ) -> Result>>, AppleCodesignError> { + if let Some(parsed) = self.find_slot_parsed(CodeSigningSlot::EntitlementsDer)? { + if let BlobData::EntitlementsDer(entitlements) = parsed.blob { + Ok(Some(entitlements)) + } else { + Err(AppleCodesignError::BadMagic("DER entitlements blob")) + } + } else { + Ok(None) + } + } + + /// Attempt to resolve a parsed [RequirementSetBlob] for this signature data. + /// + /// Returns Err on data parsing error or if the blob slot didn't contain a requirements + /// blob. + /// + /// Returns `Ok(None)` if there is no requirements slot. + pub fn code_requirements( + &self, + ) -> Result>>, AppleCodesignError> { + if let Some(parsed) = self.find_slot_parsed(CodeSigningSlot::RequirementSet)? { + if let BlobData::RequirementSet(reqs) = parsed.blob { + Ok(Some(reqs)) + } else { + Err(AppleCodesignError::BadMagic("requirements blob")) + } + } else { + Ok(None) + } + } + + /// Obtain the launch constraints on self blob. + pub fn launch_constraints_self(&self) -> Result>>> { + if let Some(parsed) = self.find_slot_parsed(CodeSigningSlot::LaunchConstraintsSelf)? { + if let BlobData::ConstraintsDer(blob) = parsed.blob { + Ok(Some(blob)) + } else { + Err(AppleCodesignError::BadMagic("self launch constraints blob")) + } + } else { + Ok(None) + } + } + + /// Obtain the launch constraints on parent blob. + pub fn launch_constraints_parent(&self) -> Result>>> { + if let Some(parsed) = self.find_slot_parsed(CodeSigningSlot::LaunchConstraintsParent)? { + if let BlobData::ConstraintsDer(blob) = parsed.blob { + Ok(Some(blob)) + } else { + Err(AppleCodesignError::BadMagic( + "parent launch constraints blob", + )) + } + } else { + Ok(None) + } + } + + /// Obtain the launch constraints on responsible process blob. + pub fn launch_constraints_responsible(&self) -> Result>>> { + if let Some(parsed) = + self.find_slot_parsed(CodeSigningSlot::LaunchConstraintsResponsibleProcess)? + { + if let BlobData::ConstraintsDer(blob) = parsed.blob { + Ok(Some(blob)) + } else { + Err(AppleCodesignError::BadMagic( + "responsible process launch constraints blob", + )) + } + } else { + Ok(None) + } + } + + /// Obtain the library constraints blob. + pub fn library_constraints(&self) -> Result>>> { + if let Some(parsed) = self.find_slot_parsed(CodeSigningSlot::LibraryConstraints)? { + if let BlobData::ConstraintsDer(blob) = parsed.blob { + Ok(Some(blob)) + } else { + Err(AppleCodesignError::BadMagic("library constraints blob")) + } + } else { + Ok(None) + } + } + + /// Attempt to resolve raw CMS signature data. + /// + /// The returned data is likely DER PKCS#7 with the root object + /// pkcs7-signedData (1.2.840.113549.1.7.2). + pub fn signature_data(&self) -> Result, AppleCodesignError> { + if let Some(parsed) = self.find_slot(CodeSigningSlot::Signature) { + // Make sure it validates. + ParsedBlob::try_from(parsed.clone())?; + + Ok(Some(parsed.payload()?)) + } else { + Ok(None) + } + } + + /// Obtain the parsed CMS [SignedData]. + pub fn signed_data(&self) -> Result, AppleCodesignError> { + if let Some(data) = self.signature_data()? { + // Sometime we get an empty data slice. This has been observed on DMG signatures. + // In that scenario, pretend there is no CMS data at all. + if data.is_empty() { + Ok(None) + } else { + let signed_data = SignedData::parse_ber(data)?; + + Ok(Some(signed_data)) + } + } else { + Ok(None) + } + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/embedded_signature_builder.rs b/3rdparty/apple-codesign-0.29.0/src/embedded_signature_builder.rs new file mode 100644 index 00000000..28097bb3 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/embedded_signature_builder.rs @@ -0,0 +1,363 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Provides primitives for constructing embeddable signature data structures. + +use { + crate::{ + code_directory::CodeDirectoryBlob, + embedded_signature::{ + create_superblob, Blob, BlobData, BlobWrapperBlob, CodeSigningMagic, CodeSigningSlot, + EmbeddedSignature, + }, + error::AppleCodesignError, + }, + bcder::{encode::PrimitiveContent, Oid}, + bytes::Bytes, + cryptographic_message_syntax::{asn1::rfc5652::OID_ID_DATA, SignedDataBuilder, SignerBuilder}, + log::{info, warn}, + reqwest::Url, + std::collections::BTreeMap, + x509_certificate::{ + rfc5652::AttributeValue, CapturedX509Certificate, DigestAlgorithm, KeyInfoSigner, + }, +}; + +/// OID for signed attribute containing plist of code directory digests. +/// +/// 1.2.840.113635.100.9.1. +pub const CD_DIGESTS_PLIST_OID: bcder::ConstOid = Oid(&[42, 134, 72, 134, 247, 99, 100, 9, 1]); + +/// OID for signed attribute containing the digests of code directories. +/// +/// 1.2.840.113635.100.9.2 +pub const CD_DIGESTS_OID: bcder::ConstOid = Oid(&[42, 134, 72, 134, 247, 99, 100, 9, 2]); + +#[derive(Clone, Copy, Debug, PartialEq)] +enum BlobsState { + Empty, + SpecialAdded, + CodeDirectoryAdded, + SignatureAdded, + TicketAdded, +} + +impl Default for BlobsState { + fn default() -> Self { + Self::Empty + } +} + +/// An entity for producing and writing [EmbeddedSignature]. +/// +/// This entity can be used to incrementally build up super blob data. +#[derive(Debug, Default)] +pub struct EmbeddedSignatureBuilder<'a> { + state: BlobsState, + blobs: BTreeMap>, +} + +impl<'a> EmbeddedSignatureBuilder<'a> { + /// Create a new instance suitable for stapling a notarization ticket. + /// + /// This starts with an existing [EmbeddedSignature] / superblob because stapling + /// a notarization ticket just adds a new ticket slot without modifying existing + /// slots. + pub fn new_for_stapling(signature: EmbeddedSignature<'a>) -> Result { + let blobs = signature + .blobs + .into_iter() + .map(|blob| { + let parsed = blob.into_parsed_blob()?; + + Ok((parsed.blob_entry.slot, parsed.blob)) + }) + .collect::, AppleCodesignError>>()?; + + Ok(Self { + state: BlobsState::CodeDirectoryAdded, + blobs, + }) + } + + /// Obtain the code directory registered with this instance. + pub fn code_directory(&self) -> Option<&CodeDirectoryBlob> { + self.blobs.get(&CodeSigningSlot::CodeDirectory).map(|blob| { + if let BlobData::CodeDirectory(cd) = blob { + (*cd).as_ref() + } else { + panic!("a non code directory should never be stored in the code directory slot"); + } + }) + } + + /// Register a blob into a slot. + /// + /// There can only be a single blob per slot. Last write wins. + /// + /// The code directory and embedded signature cannot be added using this method. + /// + /// Blobs cannot be registered after a code directory or signature are added, as this + /// would invalidate the signature. + pub fn add_blob( + &mut self, + slot: CodeSigningSlot, + blob: BlobData<'a>, + ) -> Result<(), AppleCodesignError> { + match self.state { + BlobsState::Empty | BlobsState::SpecialAdded => {} + BlobsState::CodeDirectoryAdded + | BlobsState::SignatureAdded + | BlobsState::TicketAdded => { + return Err(AppleCodesignError::SignatureBuilder( + "cannot add blobs after code directory or signature is registered", + )); + } + } + + if matches!( + blob, + BlobData::CodeDirectory(_) + | BlobData::EmbeddedSignature(_) + | BlobData::EmbeddedSignatureOld(_) + ) { + return Err(AppleCodesignError::SignatureBuilder( + "cannot register code directory or signature blob via add_blob()", + )); + } + + self.blobs.insert(slot, blob); + + self.state = BlobsState::SpecialAdded; + + Ok(()) + } + + /// Register a [CodeDirectoryBlob] with this builder. + /// + /// This is the recommended mechanism to register a Code Directory with this instance. + /// + /// When a code directory is registered, this method will automatically ensure digests + /// of previously registered blobs/slots are present in the code directory. This + /// removes the burden from callers of having to keep the code directory in sync with + /// other registered blobs. + /// + /// This function accepts the slot to add the code directory to because alternative + /// slots can be registered. + pub fn add_code_directory( + &mut self, + cd_slot: CodeSigningSlot, + mut cd: CodeDirectoryBlob<'a>, + ) -> Result<&CodeDirectoryBlob, AppleCodesignError> { + if matches!(self.state, BlobsState::SignatureAdded) { + return Err(AppleCodesignError::SignatureBuilder( + "cannot add code directory after signature data added", + )); + } + + for (slot, blob) in &self.blobs { + // Not all slots are expressible in the cd specials list! + if !slot.is_code_directory_specials_expressible() { + continue; + } + + let digest = blob.digest_with(cd.digest_type)?; + + cd.set_slot_digest(*slot, digest)?; + } + + self.blobs.insert(cd_slot, cd.into()); + self.state = BlobsState::CodeDirectoryAdded; + + Ok(self.code_directory().expect("we just inserted this key")) + } + + /// Add an alternative code directory. + /// + /// This is a wrapper for [Self::add_code_directory()] that has logic for determining the + /// appropriate slot for the code directory. + pub fn add_alternative_code_directory( + &mut self, + cd: CodeDirectoryBlob<'a>, + ) -> Result<&CodeDirectoryBlob, AppleCodesignError> { + let mut our_slot = CodeSigningSlot::AlternateCodeDirectory0; + + for slot in self.blobs.keys() { + if slot.is_alternative_code_directory() { + our_slot = CodeSigningSlot::from(u32::from(*slot) + 1); + + if !our_slot.is_alternative_code_directory() { + return Err(AppleCodesignError::SignatureBuilder( + "no more available alternative code directory slots", + )); + } + } + } + + self.add_code_directory(our_slot, cd) + } + + /// The a CMS signature and register its signature blob. + /// + /// `signing_key` and `signing_cert` denote the keypair being used to produce a + /// cryptographic signature. + /// + /// `time_stamp_url` is an optional time-stamp protocol server to use to record + /// the signature in. + /// + /// `certificates` are extra X.509 certificates to register in the signing chain. + /// + /// `signing_time` defines the signing time to use. If not defined, the + /// current time is used. + /// + /// This method errors if called before a code directory is registered. + pub fn create_cms_signature( + &mut self, + signing_key: &dyn KeyInfoSigner, + signing_cert: &CapturedX509Certificate, + time_stamp_url: Option<&Url>, + certificates: impl Iterator, + signing_time: Option>, + ) -> Result<(), AppleCodesignError> { + let main_cd = self + .code_directory() + .ok_or(AppleCodesignError::SignatureBuilder( + "cannot create CMS signature unless code directory is present", + ))?; + + if let Some(cn) = signing_cert.subject_common_name() { + warn!("creating cryptographic signature with certificate {}", cn); + } + + let mut cdhashes = vec![]; + let mut attributes = vec![]; + + for (slot, blob) in &self.blobs { + if *slot == CodeSigningSlot::CodeDirectory || slot.is_alternative_code_directory() { + if let BlobData::CodeDirectory(cd) = blob { + // plist digests use the native digest of the code directory but always + // truncated at 20 bytes. + let mut digest = cd.digest_with(cd.digest_type)?; + digest.truncate(20); + cdhashes.push(plist::Value::Data(digest)); + + // ASN.1 values are a SEQUENCE of (OID, OctetString) with the native + // digest. + let digest = cd.digest_with(cd.digest_type)?; + let alg = DigestAlgorithm::try_from(cd.digest_type)?; + + attributes.push(AttributeValue::new(bcder::Captured::from_values( + bcder::Mode::Der, + bcder::encode::sequence(( + Oid::from(alg).encode_ref(), + bcder::OctetString::new(digest.into()).encode_ref(), + )), + ))); + } else { + return Err(AppleCodesignError::SignatureBuilder( + "unexpected blob type in code directory slot", + )); + } + } + } + + let mut plist_dict = plist::Dictionary::new(); + plist_dict.insert("cdhashes".to_string(), plist::Value::Array(cdhashes)); + + let mut plist_xml = vec![]; + plist::Value::from(plist_dict) + .to_writer_xml(&mut plist_xml) + .map_err(AppleCodesignError::CodeDirectoryPlist)?; + // We also need to include a trailing newline to conform with Apple's XML + // writer. + plist_xml.push(b'\n'); + + let signer = SignerBuilder::new(signing_key, signing_cert.clone()) + .message_id_content(main_cd.to_blob_bytes()?) + .signed_attribute_octet_string( + Oid(Bytes::copy_from_slice(CD_DIGESTS_PLIST_OID.as_ref())), + &plist_xml, + ); + + let signer = signer.signed_attribute(Oid(CD_DIGESTS_OID.as_ref().into()), attributes); + + let signer = if let Some(time_stamp_url) = time_stamp_url { + info!("Using time-stamp server {}", time_stamp_url); + signer.time_stamp_url(time_stamp_url.clone())? + } else { + signer + }; + + let builder = SignedDataBuilder::default() + // The default is `signed-data`. But Apple appears to use the `data` content-type, + // in violation of RFC 5652 Section 5, which says `signed-data` should be + // used when there are signatures. + .content_type(Oid(OID_ID_DATA.as_ref().into())) + .signer(signer) + .certificates(certificates); + + let builder = if let Some(time) = signing_time { + info!("Using signing time {}", time.to_rfc3339()); + builder.signing_time(time.into()) + } else { + builder + }; + + let der = builder.build_der()?; + + self.blobs.insert( + CodeSigningSlot::Signature, + BlobData::BlobWrapper(Box::new(BlobWrapperBlob::from_data_owned(der))), + ); + self.state = BlobsState::SignatureAdded; + + Ok(()) + } + + pub fn create_empty_cms_signature(&mut self) -> Result<(), AppleCodesignError> { + self.blobs.insert( + CodeSigningSlot::Signature, + BlobData::BlobWrapper(Box::new(BlobWrapperBlob::from_data_owned(Vec::new()))), + ); + self.state = BlobsState::SignatureAdded; + Ok(()) + } + + /// Add notarization ticket data. + /// + /// This will register a new ticket slot holding the notarization ticket data. + pub fn add_notarization_ticket( + &mut self, + ticket_data: Vec, + ) -> Result<(), AppleCodesignError> { + self.blobs.insert( + CodeSigningSlot::Ticket, + BlobData::BlobWrapper(Box::new(BlobWrapperBlob::from_data_owned(ticket_data))), + ); + self.state = BlobsState::TicketAdded; + + Ok(()) + } + + /// Create the embedded signature "superblob" data. + pub fn create_superblob(&self) -> Result, AppleCodesignError> { + if matches!(self.state, BlobsState::Empty | BlobsState::SpecialAdded) { + return Err(AppleCodesignError::SignatureBuilder( + "code directory required in order to materialize superblob", + )); + } + + let blobs = self + .blobs + .iter() + .map(|(slot, blob)| { + let data = blob.to_blob_bytes()?; + + Ok((*slot, data)) + }) + .collect::, AppleCodesignError>>()?; + + create_superblob(CodeSigningMagic::EmbeddedSignature, blobs.iter()) + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/entitlements.rs b/3rdparty/apple-codesign-0.29.0/src/entitlements.rs new file mode 100644 index 00000000..6b67b9cf --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/entitlements.rs @@ -0,0 +1,53 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +/*! Code entitlements handling. */ + +use {crate::code_directory::ExecutableSegmentFlags, plist::Value}; + +/// Convert an entitlements plist to [ExecutableSegmentFlags]. +/// +/// Some entitlements plist values imply features in executable segment flags. +/// This function resolves those implied features. +pub fn plist_to_executable_segment_flags(value: &Value) -> ExecutableSegmentFlags { + let mut flags = ExecutableSegmentFlags::empty(); + + if let Value::Dictionary(d) = value { + if matches!(d.get("get-task-allow"), Some(Value::Boolean(true))) { + flags |= ExecutableSegmentFlags::ALLOW_UNSIGNED; + } + if matches!(d.get("run-unsigned-code"), Some(Value::Boolean(true))) { + flags |= ExecutableSegmentFlags::ALLOW_UNSIGNED; + } + if matches!( + d.get("com.apple.private.cs.debugger"), + Some(Value::Boolean(true)) + ) { + flags |= ExecutableSegmentFlags::DEBUGGER; + } + if matches!(d.get("dynamic-codesigning"), Some(Value::Boolean(true))) { + flags |= ExecutableSegmentFlags::JIT; + } + if matches!( + d.get("com.apple.private.skip-library-validation"), + Some(Value::Boolean(true)) + ) { + flags |= ExecutableSegmentFlags::SKIP_LIBRARY_VALIDATION; + } + if matches!( + d.get("com.apple.private.amfi.can-load-cdhash"), + Some(Value::Boolean(true)) + ) { + flags |= ExecutableSegmentFlags::CAN_LOAD_CD_HASH; + } + if matches!( + d.get("com.apple.private.amfi.can-execute-cdhash"), + Some(Value::Boolean(true)) + ) { + flags |= ExecutableSegmentFlags::CAN_EXEC_CD_HASH; + } + } + + flags +} diff --git a/3rdparty/apple-codesign-0.29.0/src/environment_constraints.rs b/3rdparty/apple-codesign-0.29.0/src/environment_constraints.rs new file mode 100644 index 00000000..93b78e8e --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/environment_constraints.rs @@ -0,0 +1,188 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Launch constraints and library constraints. + +use { + crate::{ + plist_der::{der_decode_plist, der_encode_plist}, + AppleCodesignError, Result, + }, + plist::{Dictionary, Value}, + std::path::Path, +}; + +/// Represents the DER encoded form of environment constraints. +/// +/// Instances can be converted into a [Value] using `.into()`. +#[derive(Clone, Debug)] +pub struct EncodedEnvironmentConstraints { + /// We're not sure what this is. + /// + /// Value always appears to be 0. + pub ccat: u64, + + /// We're not sure what this is. + /// + /// Value always appears to be 1. + /// + /// We hypothesize it might be a compatibility version number. + pub comp: u64, + + /// The user-provided constraints, as a mapping. + pub requirements: Dictionary, + + /// We're not sure what this is. + /// + /// Value always appears to be 1. + /// + /// We hypothesize it is a version number. + pub vers: u64, +} + +impl Default for EncodedEnvironmentConstraints { + fn default() -> Self { + Self { + ccat: 0, + comp: 1, + requirements: Default::default(), + vers: 1, + } + } +} + +impl From for Value { + fn from(value: EncodedEnvironmentConstraints) -> Self { + let mut dict = Dictionary::default(); + + dict.insert("ccat".into(), value.ccat.into()); + dict.insert("comp".into(), value.comp.into()); + dict.insert("reqs".into(), value.requirements.into()); + dict.insert("vers".into(), value.vers.into()); + + dict.into() + } +} + +impl TryFrom for EncodedEnvironmentConstraints { + type Error = AppleCodesignError; + + fn try_from(value: Value) -> Result { + let mut res = Self::default(); + + match value { + Value::Dictionary(dict) => { + for (k, v) in dict { + match k.as_str() { + "ccat" => match v { + Value::Integer(v) => { + res.ccat = v.as_signed().ok_or_else(|| { + AppleCodesignError::EnvironmentConstraint( + "failed to convert ccat to i64".into(), + ) + })? as u64; + } + _ => { + return Err(AppleCodesignError::EnvironmentConstraint( + "ccat is not an integer".into(), + )); + } + }, + "comp" => match v { + Value::Integer(v) => { + res.comp = v.as_signed().ok_or_else(|| { + AppleCodesignError::EnvironmentConstraint( + "failed to convert comp to i64".into(), + ) + })? as u64; + } + _ => { + return Err(AppleCodesignError::EnvironmentConstraint( + "comp is not an integer".into(), + )); + } + }, + "reqs" => match v { + Value::Dictionary(v) => { + res.requirements = v; + } + _ => { + return Err(AppleCodesignError::EnvironmentConstraint( + "reqs is not a dictionary".into(), + )); + } + }, + "vers" => match v { + Value::Integer(v) => { + res.vers = v.as_signed().ok_or_else(|| { + AppleCodesignError::EnvironmentConstraint( + "failed to convert vers to i64".into(), + ) + })? as u64; + } + _ => { + return Err(AppleCodesignError::EnvironmentConstraint( + "vers is not an integer".into(), + )); + } + }, + _ => { + return Err(AppleCodesignError::EnvironmentConstraint(format!( + "unknown key in plist: {}", + k + ))); + } + } + } + + Ok(res) + } + _ => Err(AppleCodesignError::EnvironmentConstraint( + "plist value is not a dictionary".to_string(), + )), + } + } +} + +impl EncodedEnvironmentConstraints { + /// Attempt to decode an instance from DER. + pub fn from_der(data: impl AsRef<[u8]>) -> Result { + let value = der_decode_plist(data)?; + + Self::try_from(value) + } + + /// Obtain an instance from a requirements plist. + pub fn from_requirements_plist(value: Value) -> Result { + match value { + Value::Dictionary(v) => Ok(Self { + requirements: v, + ..Default::default() + }), + _ => Err(AppleCodesignError::EnvironmentConstraint( + "supplied plist is not a dictionary".into(), + )), + } + } + + /// Attempt to construct an instance by reading requirements plist data from a file. + /// + /// Source file can be XML or binary encoding. + pub fn from_requirements_plist_file(path: impl AsRef) -> Result { + let value = Value::from_file(path.as_ref())?; + Self::from_requirements_plist(value) + } + + /// Encode the instance to DER. + pub fn der_encode(&self) -> Result> { + let value = Value::from(self.clone()); + + der_encode_plist(&value) + } + + /// Obtain just the requirements as a plist [Value]. + pub fn requirements_plist(&self) -> Value { + Value::Dictionary(self.requirements.clone()) + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/error.rs b/3rdparty/apple-codesign-0.29.0/src/error.rs new file mode 100644 index 00000000..6682b09a --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/error.rs @@ -0,0 +1,403 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use { + crate::{macho_universal::UniversalMachOError, remote_signing::RemoteSignError}, + cryptographic_message_syntax::CmsError, + std::path::PathBuf, + thiserror::Error, + x509_certificate::{KeyAlgorithm, X509CertificateError}, +}; + +/// Unified error type for Apple code signing. +#[derive(Debug, Error)] +pub enum AppleCodesignError { + #[error("unknown command")] + CliUnknownCommand, + + #[error("bad argument")] + CliBadArgument, + + #[error("{0}")] + CliGeneralError(String), + + #[error("I/O error: {0}")] + Io(#[from] std::io::Error), + + #[error("CLI error: {0}")] + CliDialoguer(#[from] dialoguer::Error), + + #[error("binary parsing error: {0}")] + Goblin(#[from] goblin::error::Error), + + #[error("invalid Mach-O binary: {0}")] + InvalidBinary(String), + + #[error("invalid binary index within Mach-O: {0}")] + InvalidMachOIndex(usize), + + #[error("binary does not have code signature data")] + BinaryNoCodeSignature, + + #[error("binary does not have code directory blob")] + BinaryNoCodeDirectory, + + #[error("X.509 certificate handler error: {0}")] + X509(#[from] X509CertificateError), + + #[error("CMS error: {0}")] + Cms(#[from] CmsError), + + #[error("JSON serialization error: {0}")] + SerdeJson(#[from] serde_json::Error), + + #[error("YAML serialization error: {0}")] + SerdeYaml(#[from] serde_yaml::Error), + + #[error("glob error: {0}")] + GlobPattern(#[from] glob::PatternError), + + #[error("problems reported during verification")] + VerificationProblems, + + #[error("certificate error: {0}")] + CertificateGeneric(String), + + #[error("certificate decode error: {0}")] + CertificateDecode(bcder::decode::DecodeError), + + #[error("PEM error: {0}")] + CertificatePem(pem::PemError), + + #[error("X.509 certificate parsing error: {0}")] + X509Parse(String), + + #[error("unsupported key algorithm in certificate: {0:?}")] + CertificateUnsupportedKeyAlgorithm(KeyAlgorithm), + + #[error("unspecified cryptography error in certificate")] + CertificateRing(ring::error::Unspecified), + + #[error("bad string value in certificate: {0:?}")] + CertificateCharset(bcder::string::CharSetError), + + #[error("DER: {0}")] + Der(#[from] der::Error), + + #[error("error parsing version string: {0}")] + VersionParse(#[from] semver::Error), + + #[error("XAR error: {0}")] + Xar(#[from] apple_xar::Error), + + #[error("Apple flat package error: {0}")] + FlatPackage(#[from] apple_flat_package::Error), + + #[error("unable to locate __TEXT segment")] + MissingText, + + #[error("unable to locate __LINKEDIT segment")] + MissingLinkedit, + + #[error("bad header magic in {0}")] + BadMagic(&'static str), + + #[error("data structure parse error: {0}")] + Scroll(#[from] scroll::Error), + + #[error("error parsing plist XML: {0}")] + PlistParseXml(plist::Error), + + #[error("error serializing plist to XML: {0}")] + PlistSerializeXml(plist::Error), + + #[error("malformed identifier string in code directory")] + CodeDirectoryMalformedIdentifier, + + #[error("malformed team name string in code directory")] + CodeDirectoryMalformedTeam, + + #[error("plist error in code directory: {0}")] + CodeDirectoryPlist(plist::Error), + + #[error("SuperBlob data is malformed")] + SuperblobMalformed, + + #[error("specified path is not of a recognized type")] + UnrecognizedPathType, + + #[error("functionality not implemented: {0}")] + Unimplemented(&'static str), + + #[error("unknown code signature flag: {0}")] + CodeSignatureUnknownFlag(String), + + #[error("entitlements data not valid UTF-8: {0}")] + EntitlementsBadUtf8(std::str::Utf8Error), + + #[error("error with plist DER encoding: {0}")] + PlistDer(String), + + #[error("unknown executable segment flag: {0}")] + ExecutableSegmentUnknownFlag(String), + + #[error("unknown code requirement opcode: {0}")] + RequirementUnknownOpcode(u32), + + #[error("unknown code requirement match expression: {0}")] + RequirementUnknownMatchExpression(u32), + + #[error("code requirement data malformed: {0}")] + RequirementMalformed(&'static str), + + #[error("plist error in code resources: {0}")] + ResourcesPlist(plist::Error), + + #[error("base64 error in code resources: {0}")] + ResourcesBase64(base64::DecodeError), + + #[error("plist parse error in code resources: {0}")] + ResourcesPlistParse(String), + + #[error("bad regular expression in code resources: {0}; {1}")] + ResourcesBadRegex(String, regex::Error), + + #[error("__LINKEDIT isn't final Mach-O segment")] + LinkeditNotLast, + + #[error("__LINKEDIT segment contains data after signature")] + DataAfterSignature, + + #[error("insufficient room to write code signature load command")] + LoadCommandNoRoom, + + #[error("error writing Mach-O: {0}")] + MachOWrite(String), + + #[error("no identifier string provided")] + NoIdentifier, + + #[error("no signing certificate")] + NoSigningCertificate, + + #[error("signature data too large (please report this issue)")] + SignatureDataTooLarge, + + #[error("invalid builder operation: {0}")] + SignatureBuilder(&'static str), + + #[error("HTTP error: {0}")] + Reqwest(#[from] reqwest::Error), + + #[error("unknown digest algorithm")] + DigestUnknownAlgorithm, + + #[error("unsupported digest algorithm")] + DigestUnsupportedAlgorithm, + + #[error("unspecified digest error")] + DigestUnspecified, + + #[error("deriving identifier from path: {0}")] + PathIdentifier(String), + + #[error("error interfacing with directory-based bundle: {0}")] + DirectoryBundle(anyhow::Error), + + #[error("nested bundle does not exist: {0}")] + BundleUnknown(String), + + #[error("bundle Info.plist does not define CFBundleIdentifier: {0}")] + BundleNoIdentifier(PathBuf), + + #[error("bundle Info.plist does not define CFBundleExecutable: {0}")] + BundleNoMainExecutable(PathBuf), + + #[error( + "unexpected resource rule evaluation when signing nested bundle (please report this issue)" + )] + BundleUnexpectedResourceRuleResult, + + #[error("unable to parse settings scope: {0}")] + ParseSettingsScope(String), + + #[error("incorrect password given when decrypting PFX data")] + PfxBadPassword, + + #[error("error parsing PFX data: {0}")] + PfxParseError(String), + + #[cfg(target_os = "macos")] + #[error("SecurityFramework error: {0}")] + SecurityFramework(#[from] security_framework::base::Error), + + #[error("error interfacing with macOS keychain: {0}")] + KeychainError(String), + + #[error("error interfacing with Windows certificate store: {0}")] + WindowsStoreError(String), + + #[error("failed to find certificate satisfying requirements: {0}")] + CertificateNotFound(String), + + #[error("the given OID does not match a recognized Apple certificate authority extension")] + OidIsntCertificateAuthority, + + #[error("the given OID does not match a recognized Apple extended key usage extension")] + OidIsntExtendedKeyUsage, + + #[error("the given OID does not match a recognized Apple code signing extension")] + OidIsntCodeSigningExtension, + + #[error("error building certificate: {0}")] + CertificateBuildError(String), + + #[error("unknown certificate profile: {0}")] + UnknownCertificateProfile(String), + + #[error("unknown code execution policy: {0}")] + UnknownPolicy(String), + + #[error("unable to generate code requirement policy: {0}")] + PolicyFormulationError(String), + + #[error("error producing universal Mach-O binary: {0}")] + UniversalMachO(#[from] UniversalMachOError), + + #[error("walkdir error: {0}")] + WalkDir(#[from] walkdir::Error), + + #[error("zip error: {0}")] + ZipError(#[from] zip::result::ZipError), + + #[error("error writing app metadata XML: {0}")] + AppMetadataXml(xml::writer::Error), + + #[error("error writing XML: {0}")] + XmlWrite(xml::writer::Error), + + #[error("signing XAR archives requires a signing certificate")] + XarNoAdhoc, + + #[error("App Store Connect API Key error: {0}")] + AppStoreConnectApiKey(String), + + #[error("Could not find App Store Connect API key in default search locations")] + AppStoreConnectApiKeyNotFound, + + #[error("signing settings are not compatible with notarization")] + ForNotarizationInvalidSettings, + + #[error("do not know how to notarize {0}")] + NotarizeUnsupportedPath(PathBuf), + + #[error("no authentication credentials to perform notarization request")] + NotarizeNoAuthCredentials, + + #[error("reached time limit waiting for notarization to complete")] + NotarizeWaitLimitReached, + + #[error("error interacting with Notary API")] + NotarizeServerError, + + #[error("notarization rejected: StatusCode={0}; StatusMessage={1}")] + NotarizeRejected(i64, String), + + #[error("notarization is incomplete (no status code and message)")] + NotarizeIncomplete, + + #[error("notarization package is invalid")] + NotarizeInvalid, + + #[error("notarization record not in response: {0}")] + NotarizationRecordNotInResponse(String), + + #[error("signed ticket data not found in ticket lookup response (this should not happen)")] + NotarizationRecordNoSignedTicket, + + #[error("signedTicket in notarization ticket lookup response is not BYTES: {0}")] + NotarizationRecordSignedTicketNotBytes(String), + + #[error("notarization ticket lookup failure: {0}: {1}")] + NotarizationLookupFailure(String, String), + + #[error("error decoding base64 in notarization ticket: {0}")] + NotarizationRecordDecodeFailure(base64::DecodeError), + + #[error("unable to determine app platform from bundle")] + BundleUnknownAppPlatform, + + #[error("do not support stapling {0:?} bundles")] + StapleUnsupportedBundleType(apple_bundles::BundlePackageType), + + #[error("XAR file is malformed; cannot staple")] + StapleMalformedXar, + + #[error("failed to find main executable in bundle")] + StapleMainExecutableNotFound, + + #[error("do not know how to staple {0}")] + StapleUnsupportedPath(PathBuf), + + #[error("bad header magic in DMG; not a DMG file?")] + DmgBadMagic, + + #[error("cannot notarize DMG without an embedded signature")] + DmgNotarizeNoSignature, + + #[error("cannot staple DMG without an embedded signature")] + DmgStapleNoSignature, + + #[error("failed to find certificate in smartcard slot {0}")] + SmartcardNoCertificate(String), + + #[error("failed to authenticate with smartcard device")] + SmartcardFailedAuthentication, + + #[cfg(feature = "yubikey")] + #[error("YubiKey error: {0}")] + YubiKey(#[from] yubikey::Error), + + #[error("poisoned lock")] + PoisonedLock, + + #[error("internal API / logic error: {0}")] + LogicError(String), + + #[error("zip structs error: {0}")] + ZipStructs(#[from] zip_structs::zip_error::ZipReadError), + + #[error("remote signing error: {0}")] + RemoteSign(#[from] RemoteSignError), + + #[cfg(feature = "notarize")] + #[error("bytestream creation error: {0}")] + AwsByteStream(#[from] aws_smithy_types::byte_stream::error::Error), + + #[cfg(feature = "notarize")] + #[error("s3 upload error: {0}")] + AwsS3PutObject( + aws_smithy_types::error::display::DisplayErrorContext< + aws_sdk_s3::error::SdkError, + >, + ), + + #[error("bad time value")] + BadTime, + + #[error("{0}")] + Anyhow(#[from] anyhow::Error), + + #[error("plist: {0}")] + Plist(#[from] plist::Error), + + #[error("config error: {0:?}")] + Figment(#[from] figment::Error), + + #[error("environment constraints: {0}")] + EnvironmentConstraint(String), +} + +/// Result type for this library. +pub type Result = std::result::Result; diff --git a/3rdparty/apple-codesign-0.29.0/src/lib.rs b/3rdparty/apple-codesign-0.29.0/src/lib.rs new file mode 100644 index 00000000..8c72bd29 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/lib.rs @@ -0,0 +1,169 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Binary code signing for Apple platforms. +//! +//! This crate implements application code signing for Apple operating systems +//! (like macOS and iOS). A goal of this crate is to serve as a stand-in +//! replacement for Apple's `codesign` (and similar tools) without a dependency +//! on an Apple hardware device or operating system: you should be able to +//! sign and release Apple binaries from Linux, Windows, or other non-Apple +//! environments if you want to. +//! +//! Apple code signing is complex and there are likely several areas where +//! this crate and Apple's implementations don't align. It is highly recommended +//! to validate output against what Apple's official tools produce. +//! +//! # Features and Capabilities +//! +//! This crate can: +//! +//! * Find code signature data embedded in Mach-O binaries (both single and +//! multi-arch/fat/universal binaries). (See [MachOBinary] struct.) +//! * Deeply parse code signature data into Rust structs. (See +//! [EmbeddedSignature], [BlobData], and e.g. [CodeDirectoryBlob]. +//! * Parse and verify the RFC 5652 Cryptographic Message Syntax (CMS) +//! signature data. This includes using a Time-Stamp Protocol (TSP) / RFC 3161 +//! server for including a signed time-stamp token for that signature. +//! (Functionality provided by the `cryptographic-message-syntax` crate, +//! developed in the same repository as this crate.) +//! * Generate new embedded signature data, including cryptographically +//! signing that data using any signing key and X.509 certificate chain +//! you provide. (See [MachOSigner] and [BundleSigner].) +//! * Writing a new Mach-O file containing new signature data. (See +//! [MachOSigner].) +//! * Parse `CodeResources` XML plist files defining information on nested/signed +//! resources within bundles. This includes parsing and applying the filtering +//! rules defining in these files. +//! * Sign bundles. Nested bundles will automatically be signed. Additional +//! Mach-O binaries outside the main executable will also be signed. Non +//! Mach-O/code files will be digested. A `CodeResources` XML file will be +//! produced. +//! * Submit notarization requests to Apple and query notarization status. (Bundles, +//! DMGs, and `.pkg` installers are all supported.) +//! * Retrieve notarization tickets from Apple and staple. All formats supporting +//! notarization can be stapled. +//! +//! There are a number of missing features and capabilities from this crate +//! that we hope are eventually implemented: +//! +//! * No parsing of the Code Signing Requirements DSL. We support parsing the binary +//! requirements to Rust structs, serializing back to binary, and rendering to the +//! human friendly DSL. You will need to use the `csreq` tool to compile an +//! expression to binary and then give that binary blob to this crate. Alternatively, +//! you can write Rust code to construct a code requirements expression and serialize +//! that to binary. +//! * No turnkey support for signing keys. We want to make it easier for obtaining +//! signing keys (and their X.509 certificate chain) for use with this crate. It +//! should be possible to easily integrate with the OS's key store or hardware +//! based stores (such as Yubikeys). We also don't look for necessary X.509 +//! certificate extensions that Apple's verification likely mandates, which we should +//! do and enforce. +//! * Some more advanced bundles or `.pkg` files may not sign, notarize, or staple +//! correctly. Problems here are considered bugs and should be reported. +//! +//! There is missing features and functionality that will likely never be implemented: +//! +//! * Binary verification compliant with Apple's operating systems. We are capable +//! of verifying the digests of code and other embedded signature data. We can also +//! verify that a cryptographic signature came from the annotated public key in +//! that signature. We can also write heuristics to look for certain common problems +//! with signatures. But we can't and likely never will implement all the rules Apple +//! uses to verify a binary for execution because we perceive there to be little +//! value in doing this. This crate could be used to build such functionality +//! elsewhere, however. +//! +//! # End-User Documentation +//! +//! The end-user documentation is maintained as a Sphinx docs tree in the `docs` +//! directory. The latest version of the documentation is published at +//! . +//! +//! # Getting Started +//! +//! The [UnifiedSigner] type is a good place to start to see how the high level API for +//! signing is implemented. +//! +//! To learn about the low-level data structures in embedded code signatures, read +//! [specification]. Or look at the code in [embedded_signature] and +//! [embedded_signature_builder]. +//! +//! [MachOSigner] is the type responsible for signing Mach-O files. +//! +//! [BundleSigner] is the type responsible for signing bundles. +//! +//! [dmg::DmgSigner] signs DMG files. +//! +//! The [EmbeddedSignature] represents a parsed Apple code signature and provides API +//! for data retrieval. +//! +//! # Accessing Apple Code Signing Certificates +//! +//! This crate doesn't yet support integrating with the macOS keychain to obtain +//! or use the code signing certificate private key. However, it does support +//! importing the certificate key from a `.p12` file exported from the `Keychain +//! Access` application. It also supports exporting the x509 certificate chain +//! for a given certificate by speaking directly to the macOS keychain APIs. +//! +//! See the `keychain-export-certificate-chain` CLI command for exporting a +//! code signing certificate's x509 chain as PEM. + +mod apple_certificates; +pub use apple_certificates::*; +mod bundle_signing; +pub use bundle_signing::*; +mod certificate; +pub use certificate::*; +pub mod cli; +mod code_directory; +pub use code_directory::*; +pub mod code_requirement; +pub use code_requirement::*; +mod code_resources; +pub use code_resources::*; +pub mod cryptography; +pub mod dmg; +pub mod embedded_signature; +pub use embedded_signature::*; +pub mod embedded_signature_builder; +pub use embedded_signature_builder::*; +pub mod entitlements; +pub mod environment_constraints; +mod error; +pub use error::*; +mod macho; +pub use macho::*; +pub mod macho_builder; +#[cfg(target_os = "macos")] +#[allow(non_upper_case_globals)] +mod macos; +#[cfg(target_os = "macos")] +pub use macos::*; +mod macho_signing; +pub use macho_signing::*; +mod macho_universal; +pub use macho_universal::*; +#[cfg(feature = "notarize")] +pub mod notarization; +#[cfg(feature = "notarize")] +pub use notarization::*; +pub mod plist_der; +mod policy; +pub use policy::*; +mod reader; +pub use reader::*; +pub mod remote_signing; +mod signing_settings; +pub use signing_settings::*; +mod signing; +pub use signing::*; +pub mod specification; +pub mod stapling; +pub mod ticket_lookup; +mod verify; +pub use verify::*; +#[cfg(target_os = "windows")] +pub mod windows; +#[cfg(feature = "yubikey")] +pub mod yubikey; diff --git a/3rdparty/apple-codesign-0.29.0/src/macho.rs b/3rdparty/apple-codesign-0.29.0/src/macho.rs new file mode 100644 index 00000000..ad6c7462 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/macho.rs @@ -0,0 +1,858 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +/*! Mach-O primitives related to code signing + +Code signing data is embedded within the named `__LINKEDIT` segment of +the Mach-O binary. An `LC_CODE_SIGNATURE` load command in the Mach-O header +will point you at this data. See `find_signature_data()` for this logic. + +Within the `__LINKEDIT` segment is a superblob defining embedded signature +data. +*/ + +use { + crate::{ + cryptography::DigestType, embedded_signature::EmbeddedSignature, error::AppleCodesignError, + }, + goblin::mach::{ + constants::{SEG_LINKEDIT, SEG_TEXT}, + header::MH_EXECUTE, + load_command::{ + CommandVariant, LinkeditDataCommand, LC_BUILD_VERSION, SIZEOF_LINKEDIT_DATA_COMMAND, + }, + parse_magic_and_ctx, + segment::Segment, + Mach, MachO, SingleArch, + }, + rayon::prelude::*, + scroll::Pread, +}; + +/// A Mach-O binary. +pub struct MachOBinary<'a> { + /// Index within a fat binary this Mach-O resides at. + /// + /// If `None`, this is not inside a fat binary. + pub index: Option, + + /// The parsed Mach-O binary. + pub macho: MachO<'a>, + + /// The raw data backing the Mach-O binary. + pub data: &'a [u8], +} + +impl<'a> MachOBinary<'a> { + /// Parse a non-universal Mach-O binary from raw data. + pub fn parse(data: &'a [u8]) -> Result { + let macho = MachO::parse(data, 0)?; + + Ok(Self { + index: None, + macho, + data, + }) + } +} + +impl<'a> MachOBinary<'a> { + /// Find the __LINKEDIT segment and its segment index. + pub fn linkedit_index_and_segment(&self) -> Option<(usize, &Segment<'a>)> { + self.macho + .segments + .iter() + .enumerate() + .find(|(_, segment)| matches!(segment.name(), Ok(SEG_LINKEDIT))) + } + + /// Find the __LINKEDIT segment. + pub fn linkedit_segment(&self) -> Option<&Segment<'a>> { + self.linkedit_index_and_segment().map(|(_, x)| x) + } + + /// Find the __LINKEDIT segment, asserting it exists and it is the final segment. + pub fn linkedit_segment_assert_last(&self) -> Result<&Segment<'a>, AppleCodesignError> { + let last_segment = self + .segments_by_file_offset() + .last() + .copied() + .ok_or(AppleCodesignError::MissingLinkedit)?; + + if !matches!(last_segment.name(), Ok(SEG_LINKEDIT)) { + Err(AppleCodesignError::LinkeditNotLast) + } else { + Ok(last_segment) + } + } + + /// Attempt to extract a reference to raw signature data in a Mach-O binary. + /// + /// An `LC_CODE_SIGNATURE` load command in the Mach-O file header points to + /// signature data in the `__LINKEDIT` segment. + /// + /// This function is used as part of parsing signature data. You probably want to + /// use a function that parses referenced data. + pub fn find_signature_data( + &self, + ) -> Result>, AppleCodesignError> { + if let Some(linkedit_data_command) = self.code_signature_load_command() { + // Now find the slice of data in the __LINKEDIT segment we need to parse. + let (linkedit_segment_index, linkedit) = self + .linkedit_index_and_segment() + .ok_or(AppleCodesignError::MissingLinkedit)?; + + let linkedit_segment_start_offset = linkedit.fileoff as usize; + let linkedit_segment_end_offset = linkedit_segment_start_offset + linkedit.data.len(); + let signature_file_start_offset = linkedit_data_command.dataoff as usize; + let signature_file_end_offset = + signature_file_start_offset + linkedit_data_command.datasize as usize; + let signature_segment_start_offset = + linkedit_data_command.dataoff as usize - linkedit.fileoff as usize; + let signature_segment_end_offset = + signature_segment_start_offset + linkedit_data_command.datasize as usize; + + let signature_data = + &linkedit.data[signature_segment_start_offset..signature_segment_end_offset]; + + Ok(Some(MachOSignatureData { + linkedit_segment_index, + linkedit_segment_start_offset, + linkedit_segment_end_offset, + signature_file_start_offset, + signature_file_end_offset, + signature_segment_start_offset, + signature_segment_end_offset, + linkedit_segment_data: linkedit.data, + signature_data, + })) + } else { + Ok(None) + } + } + + /// Obtain the code signature in the entity. + /// + /// Returns `Ok(None)` if no signature exists, `Ok(Some)` if it does, or + /// `Err` if there is a parse error. + pub fn code_signature(&self) -> Result, AppleCodesignError> { + if let Some(signature) = self.find_signature_data()? { + Ok(Some(EmbeddedSignature::from_bytes( + signature.signature_data, + )?)) + } else { + Ok(None) + } + } + + /// Determine the start and end offset of the executable segment of a binary. + pub fn executable_segment_boundary(&self) -> Result<(u64, u64), AppleCodesignError> { + let segment = self + .macho + .segments + .iter() + .find(|segment| matches!(segment.name(), Ok(SEG_TEXT))) + .ok_or_else(|| AppleCodesignError::InvalidBinary("no __TEXT segment".into()))?; + + Ok((segment.fileoff, segment.fileoff + segment.data.len() as u64)) + } + + /// Whether this is an executable Mach-O file. + pub fn is_executable(&self) -> bool { + self.macho.header.filetype == MH_EXECUTE + } + + /// The start offset of the code signature data within the __LINKEDIT segment. + pub fn code_signature_linkedit_start_offset(&self) -> Option { + let segment = self.linkedit_segment(); + + if let (Some(segment), Some(command)) = (segment, self.code_signature_load_command()) { + Some((command.dataoff as u64 - segment.fileoff) as u32) + } else { + None + } + } + + /// The end offset of the code signature data within the __LINKEDIT segment. + pub fn code_signature_linkedit_end_offset(&self) -> Option { + let start_offset = self.code_signature_linkedit_start_offset()?; + + self.code_signature_load_command() + .map(|command| start_offset + command.datasize) + } + + /// Obtain Mach-O segments by file offset order. + /// + /// The header-defined order may vary by the file layout order. This ensures the ordering + /// is by file layout. + pub fn segments_by_file_offset(&self) -> Vec<&Segment<'a>> { + let mut segments = self.macho.segments.iter().collect::>(); + + segments.sort_by(|a, b| a.fileoff.cmp(&b.fileoff)); + + segments + } + + /// The byte offset within the binary at which point "code" stops. + /// + /// If a signature is present, this is the offset of the start of the + /// signature. Else it represents the end of the binary. + pub fn code_limit_binary_offset(&self) -> Result { + let last_segment = self.linkedit_segment_assert_last()?; + + if let Some(offset) = self.code_signature_linkedit_start_offset() { + Ok(last_segment.fileoff + offset as u64) + } else { + Ok(last_segment.fileoff + last_segment.data.len() as u64) + } + } + + /// Obtain __LINKEDIT segment data before the signature data. + /// + /// If there is no signature, returns all the data for the __LINKEDIT segment. + pub fn linkedit_data_before_signature(&self) -> Option<&[u8]> { + let segment = self.linkedit_segment(); + + if let Some(segment) = segment { + if let Some(offset) = self.code_signature_linkedit_start_offset() { + Some(&segment.data[0..offset as usize]) + } else { + Some(segment.data) + } + } else { + None + } + } + + /// Obtain Mach-O binary data to be digested in code digests. + /// + /// Returns the raw data whose digests will be captured by the Code Directory code digests. + pub fn digested_code_data(&self) -> Result<&[u8], AppleCodesignError> { + let code_limit = self.code_limit_binary_offset()?; + + Ok(&self.data[0..code_limit as _]) + } + + /// Obtain the size in bytes of all code digests given a digest type and page size. + pub fn code_digests_size( + &self, + digest: DigestType, + page_size: usize, + ) -> Result { + let empty = digest.digest_data(b"")?; + + Ok(self.digested_code_data()?.chunks(page_size).count() * empty.len()) + } + + /// Compute digests over code in this binary. + pub fn code_digests( + &self, + digest: DigestType, + page_size: usize, + ) -> Result>, AppleCodesignError> { + let data = self.digested_code_data()?; + + // Premature parallelism can be slower due to overhead of having to spin up threads. + // So only do parallel digests if we have enough data to warrant it. + if data.len() > 64 * 1024 * 1024 { + data.par_chunks(page_size) + .map(|c| digest.digest_data(c)) + .collect::, AppleCodesignError>>() + } else { + self.digested_code_data()? + .chunks(page_size) + .map(|chunk| digest.digest_data(chunk)) + .collect::, AppleCodesignError>>() + } + } + + /// Resolve the load command for the code signature. + pub fn code_signature_load_command(&self) -> Option { + self.macho.load_commands.iter().find_map(|lc| { + if let CommandVariant::CodeSignature(command) = lc.command { + Some(command) + } else { + None + } + }) + } + + /// Attempt to locate embedded Info.plist data. + pub fn embedded_info_plist(&self) -> Result>, AppleCodesignError> { + // Mach-O binaries can have the Info.plist data in an `__info_plist` section + // within the __TEXT segment. + for segment in &self.macho.segments { + if matches!(segment.name(), Ok(SEG_TEXT)) { + for (section, data) in segment.sections()? { + if matches!(section.name(), Ok("__info_plist")) { + return Ok(Some(data.to_vec())); + } + } + } + } + + Ok(None) + } + + /// Determines whether this crate is capable of signing a given Mach-O binary. + /// + /// Code in this crate is limited in the amount of Mach-O binary manipulation + /// it can perform (supporting rewriting all valid Mach-O binaries effectively + /// requires low-level awareness of all Mach-O constructs in order to perform + /// offset manipulation). This function can be used to test signing + /// compatibility. + /// + /// We currently only support signing Mach-O files already containing an + /// embedded signature. Often linked binaries automatically contain an embedded + /// signature containing just the code directory (without a cryptographically + /// signed signature), so this limitation hopefully isn't impactful. + pub fn check_signing_capability(&self) -> Result<(), AppleCodesignError> { + let last_segment = self.linkedit_segment_assert_last()?; + + // Rules: + // + // 1. If there is an existing signature, there must be no data in + // the binary after it. (We don't know how to update references to + // other data to reflect offset changes.) + // 2. If there isn't an existing signature, there must be "room" between + // the last load command and the first section to write a new load + // command for the signature. + + if let Some(offset) = self.code_signature_linkedit_end_offset() { + if offset as usize == last_segment.data.len() { + Ok(()) + } else { + Err(AppleCodesignError::DataAfterSignature) + } + } else { + let last_load_command = self + .macho + .load_commands + .iter() + .last() + .ok_or_else(|| AppleCodesignError::InvalidBinary("no load commands".into()))?; + + let first_section = self + .macho + .segments + .iter() + .map(|segment| segment.sections()) + .collect::, _>>()? + .into_iter() + .flatten() + .next() + .ok_or_else(|| AppleCodesignError::InvalidBinary("no sections".into()))?; + + let load_commands_end_offset = + last_load_command.offset + last_load_command.command.cmdsize(); + + if first_section.0.offset as usize - load_commands_end_offset + >= SIZEOF_LINKEDIT_DATA_COMMAND + { + Ok(()) + } else { + Err(AppleCodesignError::LoadCommandNoRoom) + } + } + } + + /// Attempt to resolve the mach-o targeting settings. + pub fn find_targeting(&self) -> Result, AppleCodesignError> { + let ctx = parse_magic_and_ctx(self.data, 0)? + .1 + .expect("context should have been parsed before"); + + for lc in &self.macho.load_commands { + if lc.command.cmd() == LC_BUILD_VERSION { + let build_version = self + .data + .pread_with::(lc.offset, ctx.le)?; + + return Ok(Some(MachoTarget { + platform: build_version.platform.into(), + minimum_os_version: parse_version_nibbles(build_version.minos), + sdk_version: parse_version_nibbles(build_version.sdk), + })); + } + } + + for lc in &self.macho.load_commands { + let command = match lc.command { + CommandVariant::VersionMinMacosx(c) => Some((c, Platform::MacOs)), + CommandVariant::VersionMinIphoneos(c) => Some((c, Platform::IOs)), + CommandVariant::VersionMinTvos(c) => Some((c, Platform::TvOs)), + CommandVariant::VersionMinWatchos(c) => Some((c, Platform::WatchOs)), + _ => None, + }; + + if let Some((command, platform)) = command { + return Ok(Some(MachoTarget { + platform, + minimum_os_version: parse_version_nibbles(command.version), + sdk_version: parse_version_nibbles(command.sdk), + })); + } + } + + Ok(None) + } +} + +/// Describes signature data embedded within a Mach-O binary. +pub struct MachOSignatureData<'a> { + /// Which segment offset is the `__LINKEDIT` segment. + pub linkedit_segment_index: usize, + + /// Start offset of `__LINKEDIT` segment within the binary. + pub linkedit_segment_start_offset: usize, + + /// End offset of `__LINKEDIT` segment within the binary. + pub linkedit_segment_end_offset: usize, + + /// Start offset of signature data in `__LINKEDIT` within the binary. + pub signature_file_start_offset: usize, + + /// End offset of signature data in `__LINKEDIT` within the binary. + pub signature_file_end_offset: usize, + + /// The start offset of the signature data within the `__LINKEDIT` segment. + pub signature_segment_start_offset: usize, + + /// The end offset of the signature data within the `__LINKEDIT` segment. + pub signature_segment_end_offset: usize, + + /// Raw data in the `__LINKEDIT` segment. + pub linkedit_segment_data: &'a [u8], + + /// The signature data within the `__LINKEDIT` segment. + pub signature_data: &'a [u8], +} + +/// Content of an `LC_BUILD_VERSION` load command. +#[derive(Clone, Debug, Pread)] +pub struct BuildVersionCommand { + /// LC_BUILD_VERSION + pub cmd: u32, + /// Size of load command data. + /// + /// sizeof(self) + self.ntools * sizeof(BuildToolsVersion) + pub cmdsize: u32, + /// Platform identifier. + pub platform: u32, + /// Minimum operating system version. + /// + /// X.Y.Z encoded in nibbles as xxxx.yy.zz. + pub minos: u32, + /// SDK version. + /// + /// X.Y.Z encoded in nibbles as xxxx.yy.zz. + pub sdk: u32, + /// Number of tools entries following this structure. + pub ntools: u32, +} + +/// Represents `PLATFORM_` mach-o constants. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Platform { + MacOs, + IOs, + TvOs, + WatchOs, + BridgeOs, + MacCatalyst, + IosSimulator, + TvOsSimulator, + WatchOsSimulator, + DriverKit, + Unknown(u32), +} + +impl std::fmt::Display for Platform { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::MacOs => f.write_str("macOS"), + Self::IOs => f.write_str("iOS"), + Self::TvOs => f.write_str("tvOS"), + Self::WatchOs => f.write_str("watchOS"), + Self::BridgeOs => f.write_str("bridgeOS"), + Self::MacCatalyst => f.write_str("macCatalyst"), + Self::IosSimulator => f.write_str("iOSSimulator"), + Self::TvOsSimulator => f.write_str("tvOSSimulator"), + Self::WatchOsSimulator => f.write_str("watchOSSimulator"), + Self::DriverKit => f.write_str("driverKit"), + Self::Unknown(v) => f.write_fmt(format_args!("Unknown ({v})")), + } + } +} + +impl From for Platform { + fn from(v: u32) -> Self { + match v { + 1 => Self::MacOs, + 2 => Self::IOs, + 3 => Self::TvOs, + 4 => Self::WatchOs, + 5 => Self::BridgeOs, + 6 => Self::MacCatalyst, + 7 => Self::IosSimulator, + 8 => Self::TvOsSimulator, + 9 => Self::WatchOsSimulator, + 10 => Self::DriverKit, + _ => Self::Unknown(v), + } + } +} + +impl From for u32 { + fn from(val: Platform) -> Self { + match val { + Platform::MacOs => 1, + Platform::IOs => 2, + Platform::TvOs => 3, + Platform::WatchOs => 4, + Platform::BridgeOs => 5, + Platform::MacCatalyst => 6, + Platform::IosSimulator => 7, + Platform::TvOsSimulator => 8, + Platform::WatchOsSimulator => 9, + Platform::DriverKit => 10, + Platform::Unknown(v) => v, + } + } +} + +impl Platform { + /// Resolve SHA-256 digest/signatures support for a given platform type. + pub fn sha256_digest_support(&self) -> Result { + let version = match self { + // macOS 10.11.4 introduced support for SHA-256. + Self::MacOs => ">=10.11.4", + // 11.0+ support SHA-256. + Self::IOs | Self::TvOs => ">=11.0.0", + // WatchOS always uses SHA-1 it appears. + Self::WatchOs => ">9999", + // Assume no platform needs SHA-1. + Self::Unknown(0) => ">9999", + // Assume everything else is new and supports SHA-256. + _ => "*", + }; + + Ok(semver::VersionReq::parse(version)?) + } +} + +/// Targeting settings for a Mach-O binary. +pub struct MachoTarget { + /// The OS/platform being targeted. + pub platform: Platform, + /// Minimum required OS version. + pub minimum_os_version: semver::Version, + /// SDK version targeting. + pub sdk_version: semver::Version, +} + +impl MachoTarget { + /// Convert the instance to a LC_BUILD_VERSION load command. + pub fn to_build_version_command_vec(&self, endian: object::Endianness) -> Vec { + let command = object::macho::BuildVersionCommand { + cmd: object::U32::new(endian, object::macho::LC_BUILD_VERSION), + cmdsize: object::U32::new( + endian, + std::mem::size_of::>() as _, + ), + platform: object::U32::new(endian, self.platform.into()), + minos: object::U32::new( + endian, + semver_to_macho_target_version(&self.minimum_os_version), + ), + sdk: object::U32::new(endian, semver_to_macho_target_version(&self.sdk_version)), + ntools: object::U32::new(endian, 0), + }; + + object::bytes_of(&command).to_vec() + } +} + +/// Parses and integer with nibbles xxxx.yy.zz into a [semver::Version]. +pub fn parse_version_nibbles(v: u32) -> semver::Version { + let major = v >> 16; + let minor = v << 16 >> 24; + let patch = v & 0xff; + + semver::Version::new(major as _, minor as _, patch as _) +} + +/// Convert a [semver::Version] to a u32 with nibble encoding used by Mach-O. +pub fn semver_to_macho_target_version(version: &semver::Version) -> u32 { + let major = version.major as u32; + let minor = version.minor as u32; + let patch = version.patch as u32; + + (major << 16) | ((minor & 0xff) << 8) | (patch & 0xff) +} + +/// Represents a semi-parsed Mach[-O] binary. +pub struct MachFile<'a> { + #[allow(unused)] + data: &'a [u8], + + machos: Vec>, +} + +impl<'a> MachFile<'a> { + /// Construct an instance from data. + pub fn parse(data: &'a [u8]) -> Result { + let mach = Mach::parse(data)?; + + let machos = match mach { + Mach::Binary(macho) => vec![MachOBinary { + index: None, + macho, + data, + }], + Mach::Fat(multiarch) => { + let mut machos = vec![]; + + for (index, arch) in multiarch.arches()?.into_iter().enumerate() { + let macho = match multiarch.get(index)? { + SingleArch::MachO(m) => m, + SingleArch::Archive(_) => continue, + }; + + machos.push(MachOBinary { + index: Some(index), + macho, + data: arch.slice(data), + }); + } + + machos + } + }; + + Ok(Self { data, machos }) + } + + /// Whether this Mach-O data has multiple architectures. + pub fn is_fat(&self) -> bool { + self.machos.len() > 1 + } + + /// Iterate [MachO] instances in this data. + /// + /// The `Option` is `Some` if this is a universal Mach-O or `None` otherwise. + pub fn iter_macho(&self) -> impl Iterator { + self.machos.iter() + } + + pub fn iter_macho_mut(&mut self) -> impl Iterator> + '_ { + self.machos.iter_mut() + } + + pub fn nth_macho(&self, index: usize) -> Result<&MachOBinary<'a>, AppleCodesignError> { + self.machos + .get(index) + .ok_or(AppleCodesignError::InvalidMachOIndex(index)) + } + + pub fn nth_macho_mut( + &mut self, + index: usize, + ) -> Result<&mut MachOBinary<'a>, AppleCodesignError> { + self.machos + .get_mut(index) + .ok_or(AppleCodesignError::InvalidMachOIndex(index)) + } +} + +impl<'a> IntoIterator for MachFile<'a> { + type Item = MachOBinary<'a>; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.machos.into_iter() + } +} + +#[cfg(test)] +mod tests { + use { + super::*, + crate::embedded_signature::Blob, + std::{ + io::Read, + path::{Path, PathBuf}, + }, + }; + + const MACHO_UNIVERSAL_MAGIC: [u8; 4] = [0xca, 0xfe, 0xba, 0xbe]; + const MACHO_64BIT_MAGIC: [u8; 4] = [0xfe, 0xed, 0xfa, 0xcf]; + + /// Find files in a directory appearing to be Mach-O by sniffing magic. + /// + /// Ignores file I/O errors. + fn find_likely_macho_files(path: &Path) -> Vec { + let mut res = Vec::new(); + + let dir = std::fs::read_dir(path).unwrap(); + + for entry in dir { + let entry = entry.unwrap(); + + if let Ok(mut fh) = std::fs::File::open(entry.path()) { + let mut magic = [0; 4]; + + if let Ok(size) = fh.read(&mut magic) { + if size == 4 && (magic == MACHO_UNIVERSAL_MAGIC || magic == MACHO_64BIT_MAGIC) { + res.push(entry.path()); + } + } + } + } + + res + } + + fn find_apple_embedded_signature<'a>(macho: &'a MachOBinary) -> Option> { + if let Ok(Some(signature)) = macho.code_signature() { + Some(signature) + } else { + None + } + } + + fn validate_macho(path: &Path, macho: &MachOBinary) { + // We found signature data in the binary. + if let Some(signature) = find_apple_embedded_signature(macho) { + // Attempt a deep parse of all blobs. + for blob in &signature.blobs { + match blob.clone().into_parsed_blob() { + Ok(parsed) => { + // Attempt to roundtrip the blob data. + match parsed.blob.to_blob_bytes() { + Ok(serialized) => { + if serialized != blob.data { + println!("blob serialization roundtrip failure on {}: index {}, magic {:?}", + path.display(), + blob.index, + blob.magic, + ); + } + } + Err(e) => { + println!( + "blob serialization failure on {}; index {}, magic {:?}: {:?}", + path.display(), + blob.index, + blob.magic, + e + ); + } + } + } + Err(e) => { + println!( + "blob parse failure on {}; index {}, magic {:?}: {:?}", + path.display(), + blob.index, + blob.magic, + e + ); + } + } + } + + // Found a CMS signed data blob. + if matches!(signature.signature_data(), Ok(Some(_))) { + match signature.signed_data() { + Ok(Some(signed_data)) => { + for signer in signed_data.signers() { + if let Err(e) = signer.verify_signature_with_signed_data(&signed_data) { + println!( + "signature verification failed for {}: {}", + path.display(), + e + ); + } + + if let Ok(()) = + signer.verify_message_digest_with_signed_data(&signed_data) + { + println!( + "message digest verification unexpectedly correct for {}", + path.display() + ); + } + } + } + Ok(None) => { + // This has been observed to occur in the wild. But not from Apple + // signed binaries. Mostly ignore it. + eprintln!( + "{} has a signature blob without CMS data; weird", + path.display() + ); + } + Err(e) => { + println!("error performing CMS parse of {}: {:?}", path.display(), e); + } + } + } + } + } + + fn validate_macho_in_dir(dir: &Path) { + for path in find_likely_macho_files(dir).into_iter() { + if let Ok(file_data) = std::fs::read(&path) { + if let Ok(mach) = MachFile::parse(&file_data) { + for macho in mach.into_iter() { + validate_macho(&path, &macho); + } + } + } + } + } + + #[test] + fn parse_applications_macho_signatures() { + // This test scans common directories containing Mach-O files on macOS and + // verifies we can parse CMS blobs within. + + if let Ok(dir) = std::fs::read_dir("/Applications") { + for entry in dir { + let entry = entry.unwrap(); + + let search_dir = entry.path().join("Contents").join("MacOS"); + + if search_dir.exists() { + validate_macho_in_dir(&search_dir); + } + } + } + + for dir in &["/usr/bin", "/usr/local/bin", "/opt/homebrew/bin"] { + let dir = PathBuf::from(dir); + + if dir.exists() { + validate_macho_in_dir(&dir); + } + } + } + + #[test] + fn version_nibbles() { + assert_eq!( + parse_version_nibbles(12 << 16 | 1 << 8 | 2), + semver::Version::new(12, 1, 2) + ); + assert_eq!( + parse_version_nibbles(11 << 16 | 10 << 8 | 15), + semver::Version::new(11, 10, 15) + ); + assert_eq!( + semver_to_macho_target_version(&semver::Version::new(12, 1, 2)), + 12 << 16 | 1 << 8 | 2 + ); + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/macho_builder.rs b/3rdparty/apple-codesign-0.29.0/src/macho_builder.rs new file mode 100644 index 00000000..cdab8fb9 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/macho_builder.rs @@ -0,0 +1,688 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Mach-O writing. +//! +//! Initially authored to facilitate testing. + +use { + crate::{macho::MachoTarget, AppleCodesignError}, + object::{ + endian::{BigEndian, U32, U64}, + macho::*, + pod::bytes_of, + AddressSize, Architecture, Endian, Endianness, + }, +}; + +/// A Mach-O segment. +#[derive(Debug)] +pub struct Segment { + /// Name of the segment. Max of 16 bytes. + name: String, + /// Segment flags. + flags: u32, +} + +impl Segment { + /// Obtain the segment name as bytes. + fn name_bytes(&self) -> Result<[u8; 16], AppleCodesignError> { + let mut v = [0; 16]; + + v.get_mut(..self.name.len()) + .ok_or_else(|| { + AppleCodesignError::MachOWrite(format!("segment name too long: {}", self.name)) + })? + .copy_from_slice(self.name.as_bytes()); + + Ok(v) + } + + /// Obtain the bytes for the load command data. + /// + /// Just the segment load command. Does not include section header data. + #[allow(clippy::too_many_arguments)] + pub fn to_load_command_data( + &self, + address_size: AddressSize, + endian: Endianness, + section_count: usize, + vm_start: u64, + vm_length: u64, + file_offset: usize, + file_length: usize, + ) -> Result, AppleCodesignError> { + if address_size == AddressSize::U64 { + let segment = SegmentCommand64 { + cmd: U32::new(endian, LC_SEGMENT_64), + cmdsize: U32::new( + endian, + (std::mem::size_of::>() + + section_count * std::mem::size_of::>()) + as u32, + ), + segname: self.name_bytes()?, + vmaddr: U64::new(endian, vm_start), + vmsize: U64::new(endian, vm_length as _), + fileoff: U64::new(endian, file_offset as _), + filesize: U64::new(endian, file_length as _), + maxprot: U32::new(endian, 0), + initprot: U32::new(endian, 0), + nsects: U32::new(endian, section_count as _), + flags: U32::new(endian, self.flags), + }; + + Ok(bytes_of(&segment).to_vec()) + } else { + let segment = SegmentCommand32 { + cmd: U32::new(endian, LC_SEGMENT), + cmdsize: U32::new( + endian, + (std::mem::size_of::>() + + section_count * std::mem::size_of::>()) + as u32, + ), + segname: self.name_bytes()?, + vmaddr: U32::new(endian, vm_start as _), + vmsize: U32::new(endian, vm_length as _), + fileoff: U32::new(endian, file_offset as _), + filesize: U32::new(endian, file_length as _), + maxprot: U32::new(endian, 0), + initprot: U32::new(endian, 0), + nsects: U32::new(endian, section_count as _), + flags: U32::new(endian, self.flags), + }; + + Ok(bytes_of(&segment).to_vec()) + } + } +} + +#[derive(Debug)] +pub struct Section { + segment: String, + name: String, + align: usize, + data: Vec, + flags: u32, +} + +impl Section { + /// Obtain the segment name as bytes. + pub fn segment_name_bytes(&self) -> Result<[u8; 16], AppleCodesignError> { + let mut v = [0; 16]; + + v.get_mut(..self.segment.len()) + .ok_or_else(|| { + AppleCodesignError::MachOWrite(format!("segment name too long: {}", self.segment)) + })? + .copy_from_slice(self.segment.as_bytes()); + + Ok(v) + } + + /// Obtain the section name as bytes. + pub fn section_name_bytes(&self) -> Result<[u8; 16], AppleCodesignError> { + let mut v = [0; 16]; + + v.get_mut(..self.name.len()) + .ok_or_else(|| { + AppleCodesignError::MachOWrite(format!("section name too long: {}", self.name)) + })? + .copy_from_slice(self.name.as_bytes()); + + Ok(v) + } + + pub fn to_section_header_data( + &self, + address_size: AddressSize, + endian: Endianness, + address: u64, + size: usize, + offset: usize, + alignment: usize, + ) -> Result, AppleCodesignError> { + if address_size == AddressSize::U64 { + let header = Section64 { + sectname: self.section_name_bytes()?, + segname: self.segment_name_bytes()?, + addr: U64::new(endian, address), + size: U64::new(endian, size as _), + offset: U32::new(endian, offset as _), + align: U32::new(endian, alignment as _), + reloff: U32::new(endian, 0), + nreloc: U32::new(endian, 0), + flags: U32::new(endian, self.flags), + reserved1: U32::new(endian, 0), + reserved2: U32::new(endian, 0), + reserved3: U32::new(endian, 0), + }; + + Ok(bytes_of(&header).to_vec()) + } else { + let header = Section32 { + sectname: self.section_name_bytes()?, + segname: self.segment_name_bytes()?, + addr: U32::new(endian, address as _), + size: U32::new(endian, size as _), + offset: U32::new(endian, offset as _), + align: U32::new(endian, alignment as _), + reloff: U32::new(endian, 0), + nreloc: U32::new(endian, 0), + flags: U32::new(endian, self.flags), + reserved1: U32::new(endian, 0), + reserved2: U32::new(endian, 0), + }; + + Ok(bytes_of(&header).to_vec()) + } + } +} + +#[derive(Clone, Copy, Debug, Default)] +struct SegmentMetadata { + file_offset: usize, + file_size: usize, + vm_address: u64, + vm_size: u64, +} + +/// Describes a Mach-O section in the context of a larger file. +#[derive(Clone, Copy, Debug, Default)] +struct SectionMetadata { + /// File offset of start of section. + offset: usize, + /// Start address of section. + address: u64, +} + +fn align_u64(offset: u64, size: u64) -> u64 { + (offset + (size - 1)) & !(size - 1) +} + +fn align_usize(offset: usize, size: usize) -> usize { + (offset + (size - 1)) & !(size - 1) +} + +/// Constructor of Mach-O binaries. +/// +/// Originally written to facilitate testing so we can generate Mach-O binaries +/// for tests. Not intended to be a fully-functional linker! Use at your own +/// risk. +pub struct MachOBuilder { + architecture: Architecture, + endian: Endianness, + address_size: AddressSize, + page_size: usize, + file_type: u32, + macho_flags: u32, + /// Start offset for __TEXT segment. + text_segment_start_offset: usize, + segments: Vec, + /// Sections within the Mach-O. + /// + /// Sections are grouped by segment and each group is ordered by segment file order. + sections: Vec
, + + // Optional load commands. + /// Mach-O targeting. + /// + /// Turned into an LC_BUILD_VERSION load command. + macho_target: Option, +} + +impl MachOBuilder { + /// Create a new instance having the specified architecture and endianness. + pub fn new(architecture: Architecture, endianness: Endianness, file_type: u32) -> Self { + let page_size = match architecture { + Architecture::Aarch64 => 16384, + Architecture::X86_64 => 4096, + _ => 4096, + }; + + let segments = vec![ + Segment { + name: "__PAGEZERO".to_string(), + flags: 0, + }, + Segment { + name: "__TEXT".to_string(), + flags: 0, + }, + Segment { + name: "__DATA_CONST".to_string(), + flags: 0, + }, + Segment { + name: "__DATA".to_string(), + flags: 0, + }, + Segment { + name: "__LINKEDIT".to_string(), + flags: 0, + }, + ]; + + let sections = vec![ + Section { + segment: "__TEXT".to_string(), + name: "__text".to_string(), + align: page_size, + data: vec![], + flags: 0, + }, + Section { + segment: "__TEXT".to_string(), + name: "__const".to_string(), + align: page_size, + data: vec![], + flags: 0, + }, + Section { + segment: "__DATA_CONST".to_string(), + name: "__const".to_string(), + align: page_size, + data: vec![], + flags: 0, + }, + Section { + segment: "__DATA".to_string(), + name: "__data".to_string(), + align: page_size, + data: vec![], + flags: 0, + }, + ]; + + Self { + architecture, + endian: endianness, + address_size: architecture + .address_size() + .expect("address size should be known"), + file_type, + page_size, + macho_flags: 0, + text_segment_start_offset: 0, + segments, + sections, + macho_target: None, + } + } + + /// Create a new instance for x86-64. + pub fn new_x86_64(file_type: u32) -> Self { + Self::new(Architecture::X86_64, Endianness::Little, file_type) + } + + /// Create a new instance for aarch64. + pub fn new_aarch64(file_type: u32) -> Self { + Self::new(Architecture::Aarch64, Endianness::Little, file_type) + } + + /// Set the Mach-O targeting info for the binary. + /// + /// Will result in a LC_BUILD_VERSION load command being emitted. + pub fn macho_target(mut self, target: MachoTarget) -> Self { + self.macho_target = Some(target); + self + } + + /// Set the start offset for the __TEXT segment. + /// + /// Normally the __TEXT segment starts at 0x0. + /// + /// Very little validation is performed on the value. It may be possible + /// to write corrupted Mach-O by feeding this a sufficiently large number. + pub fn text_segment_start_offset(mut self, offset: usize) -> Self { + self.text_segment_start_offset = offset; + self + } + + fn mach_header( + &self, + number_commands: u32, + size_of_commands: u32, + ) -> Result, AppleCodesignError> { + let endian = self.endian; + + let (cpu_type, cpu_sub_type) = match self.architecture { + Architecture::Arm => (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_ALL), + Architecture::Aarch64 => (CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64_ALL), + Architecture::Aarch64_Ilp32 => (CPU_TYPE_ARM64_32, CPU_SUBTYPE_ARM64_32_V8), + Architecture::I386 => (CPU_TYPE_X86, CPU_SUBTYPE_I386_ALL), + Architecture::X86_64 => (CPU_TYPE_X86_64, CPU_SUBTYPE_X86_64_ALL), + Architecture::PowerPc => (CPU_TYPE_POWERPC, CPU_SUBTYPE_POWERPC_ALL), + Architecture::PowerPc64 => (CPU_TYPE_POWERPC64, CPU_SUBTYPE_POWERPC_ALL), + _ => { + return Err(AppleCodesignError::MachOWrite(format!( + "unhandled architecture: {:?}", + self.architecture + ))); + } + }; + + if self.address_size == AddressSize::U64 { + let magic = if endian.is_big_endian() { + MH_MAGIC_64 + } else { + MH_CIGAM_64 + }; + let header = MachHeader64 { + magic: U32::new(BigEndian, magic), + cputype: U32::new(endian, cpu_type), + cpusubtype: U32::new(endian, cpu_sub_type), + filetype: U32::new(endian, self.file_type), + ncmds: U32::new(endian, number_commands), + sizeofcmds: U32::new(endian, size_of_commands), + flags: U32::new(endian, self.macho_flags), + reserved: U32::default(), + }; + + Ok(bytes_of(&header).to_vec()) + } else { + let magic = if endian.is_big_endian() { + MH_MAGIC + } else { + MH_CIGAM + }; + let header = MachHeader32 { + magic: U32::new(BigEndian, magic), + cputype: U32::new(endian, cpu_type), + cpusubtype: U32::new(endian, cpu_sub_type), + filetype: U32::new(endian, self.file_type), + ncmds: U32::new(endian, number_commands), + sizeofcmds: U32::new(endian, size_of_commands), + flags: U32::new(endian, self.macho_flags), + }; + + Ok(bytes_of(&header).to_vec()) + } + } + + /// Length of segment load command header. + fn segment_header_size(&self) -> usize { + if self.address_size == AddressSize::U64 { + std::mem::size_of::>() + } else { + std::mem::size_of::>() + } + } + + /// Length of section header. + fn section_header_size(&self) -> usize { + if self.address_size == AddressSize::U64 { + std::mem::size_of::>() + } else { + std::mem::size_of::>() + } + } + + /// Get the sections in a named segment. + fn sections_in_segment<'a>( + &'a self, + segment_name: &'a str, + ) -> impl Iterator + 'a { + self.sections + .iter() + .filter(move |x| x.segment.as_str() == segment_name) + } + + /// Write Mach-O data to a memory buffer. + pub fn write_macho(&self) -> Result, AppleCodesignError> { + let endian = self.endian; + + // Before writing anything we do a pass to resolve metadata (lengths, file-level + // offsets, etc) for segments, sections, and other important data structures, as + // these all need to be expressed in the file header and load commands. + + let mut current_file_offset = 0; + let mut number_commands = 0; + + // Header is constant sized. So generate one with placeholder data. + current_file_offset += self.mach_header(0, 0)?.len(); + + let load_commands_offset = current_file_offset; + + // The segment load commands come first. Each has a fixed size header followed by + // section headers describing the sections within the segment. + for segment in &self.segments { + number_commands += 1; + current_file_offset += self.segment_header_size() + + self.sections_in_segment(&segment.name).count() * self.section_header_size(); + } + + // The next set of load commands describe data in the __LINKEDIT segment. + + // Symbol table. + number_commands += 1; + current_file_offset += std::mem::size_of::>(); + + // Now extra load commands. + if let Some(target) = &self.macho_target { + number_commands += 1; + current_file_offset += target.to_build_version_command_vec(endian).len(); + } + + // TODO support additional load commands. Build version, source version, minimum + // version, Uuid. Main, CodeSignature, etc. + + let load_command_size = current_file_offset - load_commands_offset; + + // After the load commands is the segment / section data. + + let start_address = if self.address_size == AddressSize::U64 { + 0x1_0000_0000 + } else { + 0x4000_0000 + }; + + let mut current_address = start_address; + + // Iterate through all the sections and collect metadata for them. + let mut section_metadata = vec![SectionMetadata::default(); self.sections.len()]; + + for (index, section) in self.sections.iter().enumerate() { + current_file_offset = align_usize(current_file_offset, section.align); + current_address = align_u64(current_address, section.align as _); + + section_metadata[index].offset = current_file_offset; + section_metadata[index].address = current_address; + + current_file_offset += section.data.len(); + current_address += section.data.len() as u64; + } + + // After the section data is the __LINKEDIT segment and all its special data. + current_file_offset = align_usize(current_file_offset, self.page_size); + current_address = align_u64(current_address, self.page_size as _); + + let linkedit_start_file_offset = current_file_offset; + let linkedit_start_address = current_address; + + let symbol_table_offset = current_file_offset; + let symbol_table_data = vec![0]; + current_file_offset += symbol_table_data.len(); + + let string_table_offset = current_file_offset; + // Need to write a null name for Mach-O. + let string_table_data = vec![0]; + current_file_offset += string_table_data.len(); + + // We're at the end of the file! + + // Derive segment metadata from section metadata and special rules. + let mut segment_metadata = vec![SegmentMetadata::default(); self.segments.len()]; + + for (segment_index, segment) in self.segments.iter().enumerate() { + let metadata = &mut segment_metadata[segment_index]; + + match segment.name.as_str() { + "__PAGEZERO" => { + // __PAGEZERO is empty in the file but is mapped to an empty virtual address + // outside the used memory address range in order to trigger a fault. + metadata.file_offset = 0; + metadata.file_size = 0; + metadata.vm_address = 0; + // A constant value is obviously incorrect for binaries larger than 4 GB. + metadata.vm_size = start_address; + } + "__LINKEDIT" => { + metadata.file_offset = linkedit_start_file_offset; + metadata.file_size = current_file_offset - linkedit_start_file_offset; + metadata.vm_address = linkedit_start_address; + metadata.vm_size = (current_file_offset - linkedit_start_file_offset) as _; + } + segment_name => { + // All the other segments are derived from section metadata. + let first_section_index = self + .sections + .iter() + .enumerate() + .find_map(|(index, section)| { + if section.segment == segment_name { + Some(index) + } else { + None + } + }) + .ok_or_else(|| { + AppleCodesignError::MachOWrite(format!( + "unable to find section in segment {}", + segment.name + )) + })?; + let last_section_index = self + .sections + .iter() + .enumerate() + .rfind(|(_, section)| section.segment == segment_name) + .map(|(index, _)| index) + .ok_or_else(|| { + AppleCodesignError::MachOWrite(format!( + "unable to find section in segment {}", + segment.name + )) + })?; + + let start_file_offset = section_metadata[first_section_index].offset; + let start_address = section_metadata[first_section_index].address; + let end_address = section_metadata[last_section_index].address + + self.sections[last_section_index].data.len() as u64; + + metadata.file_offset = start_file_offset; + metadata.vm_address = start_address; + metadata.vm_size = (end_address - start_address) as _; + + // End offset is next section start or start of __LINKEDIT. + metadata.file_size = + if let Some(next_section) = section_metadata.get(last_section_index + 1) { + next_section.offset - start_file_offset + } else { + linkedit_start_file_offset - start_file_offset + }; + + // But there's a special case for __TEXT, which starts at the beginning of the + // file and encompasses the header and load commands. + if segment_name == "__TEXT" { + metadata.file_offset = self.text_segment_start_offset; + + metadata.file_size = if let Some(next_section) = + section_metadata.get(last_section_index + 1) + { + next_section.offset + } else { + current_file_offset + } - self.text_segment_start_offset; + } + } + } + } + + // Now proceed with writing data. + + let mut buffer = Vec::with_capacity(current_file_offset); + + buffer.extend_from_slice( + self.mach_header(number_commands, load_command_size as _)? + .as_slice(), + ); + + for (index, segment) in self.segments.iter().enumerate() { + let metadata = &segment_metadata[index]; + + let segment_command_data = segment.to_load_command_data( + self.address_size, + endian, + self.sections_in_segment(&segment.name).count(), + metadata.vm_address, + metadata.vm_size, + metadata.file_offset, + metadata.file_size, + )?; + + buffer.extend_from_slice(segment_command_data.as_slice()); + + for (index, section) in self + .sections + .iter() + .enumerate() + .filter(|(_, x)| x.segment == segment.name) + { + let metadata = §ion_metadata[index]; + + let section_header_data = section.to_section_header_data( + self.address_size, + endian, + metadata.address, + section.data.len(), + metadata.offset, + section.align, + )?; + + buffer.extend_from_slice(section_header_data.as_slice()); + } + } + + let symtab_command = SymtabCommand { + cmd: U32::new(endian, LC_SYMTAB), + cmdsize: U32::new( + endian, + std::mem::size_of::>() as u32, + ), + symoff: U32::new(endian, symbol_table_offset as _), + nsyms: U32::new(endian, 0), + stroff: U32::new(endian, string_table_offset as _), + strsize: U32::new(endian, string_table_data.len() as _), + }; + buffer.extend_from_slice(bytes_of(&symtab_command)); + + if let Some(target) = &self.macho_target { + buffer.extend_from_slice(&target.to_build_version_command_vec(endian)); + } + + // Done with load commands. Start writing section data. + + for (index, section) in self.sections.iter().enumerate() { + let metadata = §ion_metadata[index]; + + // Pad zeroes until section start. + if metadata.offset > buffer.len() { + buffer.resize(metadata.offset, 0); + } + + if !section.data.is_empty() { + buffer.extend_from_slice(§ion.data); + } + } + + buffer.resize(linkedit_start_file_offset, 0); + + buffer.extend_from_slice(&symbol_table_data); + buffer.extend_from_slice(&string_table_data); + + Ok(buffer) + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/macho_signing.rs b/3rdparty/apple-codesign-0.29.0/src/macho_signing.rs new file mode 100644 index 00000000..ccd70b1e --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/macho_signing.rs @@ -0,0 +1,795 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Signing mach-o binaries. +//! +//! This module contains code for signing mach-o binaries. + +use { + crate::{ + code_directory::{CodeDirectoryBlob, CodeSignatureFlags, ExecutableSegmentFlags}, + code_requirement::{CodeRequirementExpression, CodeRequirements, RequirementType}, + cryptography::Digest, + embedded_signature::{ + Blob, BlobData, CodeSigningSlot, ConstraintsDerBlob, EntitlementsBlob, + EntitlementsDerBlob, RequirementSetBlob, + }, + embedded_signature_builder::EmbeddedSignatureBuilder, + entitlements::plist_to_executable_segment_flags, + error::AppleCodesignError, + macho::{semver_to_macho_target_version, MachFile, MachOBinary}, + macho_universal::create_universal_macho, + policy::derive_designated_requirements, + signing_settings::{DesignatedRequirementMode, SettingsScope, SigningSettings}, + }, + goblin::mach::{ + constants::{SEG_LINKEDIT, SEG_PAGEZERO}, + load_command::{ + CommandVariant, LinkeditDataCommand, SegmentCommand32, SegmentCommand64, + LC_CODE_SIGNATURE, SIZEOF_LINKEDIT_DATA_COMMAND, + }, + parse_magic_and_ctx, + }, + log::{debug, info, warn}, + scroll::{ctx::SizeWith, IOwrite}, + std::{borrow::Cow, cmp::Ordering, collections::HashMap, io::Write, path::Path}, +}; + +/// Derive a new Mach-O binary with new signature data. +fn create_macho_with_signature( + macho: &MachOBinary, + signature_data: &[u8], +) -> Result, AppleCodesignError> { + // This should have already been called. But we do it again out of paranoia. + macho.check_signing_capability()?; + + // The assumption made by checking_signing_capability() is that signature data + // is at the end of the __LINKEDIT segment. So the replacement segment is the + // existing segment truncated at the signature start followed by the new signature + // data. + // + // Code signature data is aligned on 16 byte boundary by Apple convention. + // + // Typically segment data is aligned on pages, which are multiples of 16 bytes. So + // it doesn't matter if we align based on Mach-O file-level or __LINKEDIT + // segment-level offsets: the end result is 16 byte alignment in both. + + let linkedit_data_before_signature = macho + .linkedit_data_before_signature() + .ok_or(AppleCodesignError::MissingLinkedit)?; + + let signature_file_offset = macho.code_limit_binary_offset()?; + let remainder = (signature_file_offset % 16) as usize; + let signature_padding_length = if remainder == 0 { 0 } else { 16 - remainder }; + + let signature_file_offset = signature_file_offset + signature_padding_length as u64; + + let new_linkedit_segment_size = + linkedit_data_before_signature.len() + signature_padding_length + signature_data.len(); + + // `codesign` rounds up the segment's vmsize to the nearest 16kb boundary. + // We emulate that behavior. + let remainder = new_linkedit_segment_size % 16384; + let new_linkedit_segment_vmsize = if remainder == 0 { + new_linkedit_segment_size + } else { + new_linkedit_segment_size + 16384 - remainder + }; + + assert!(new_linkedit_segment_vmsize >= new_linkedit_segment_size); + assert_eq!(new_linkedit_segment_vmsize % 16384, 0); + + let mut cursor = std::io::Cursor::new(Vec::::new()); + + // Mach-O data structures are variable endian. So use the endian defined + // by the magic when writing. + let ctx = parse_magic_and_ctx(macho.data, 0)? + .1 + .expect("context should have been parsed before"); + + // If there isn't a code signature presently, we'll need to introduce a load + // command for it. + let mut header = macho.macho.header; + if macho.code_signature_load_command().is_none() { + header.ncmds += 1; + header.sizeofcmds += SIZEOF_LINKEDIT_DATA_COMMAND as u32; + } + + cursor.iowrite_with(header, ctx)?; + + // Following the header are load commands. We need to update load commands + // to reflect changes to the signature size and __LINKEDIT segment size. + + let mut seen_signature_load_command = false; + + for load_command in &macho.macho.load_commands { + let original_command_data = + &macho.data[load_command.offset..load_command.offset + load_command.command.cmdsize()]; + + let written_len = match &load_command.command { + CommandVariant::CodeSignature(command) => { + seen_signature_load_command = true; + + let mut command = *command; + command.dataoff = signature_file_offset as _; + command.datasize = signature_data.len() as _; + + cursor.iowrite_with(command, ctx.le)?; + + LinkeditDataCommand::size_with(&ctx.le) + } + CommandVariant::Segment32(segment) => { + let segment = match segment.name() { + Ok(SEG_LINKEDIT) => { + let mut segment = *segment; + segment.filesize = new_linkedit_segment_size as _; + segment.vmsize = new_linkedit_segment_vmsize as _; + + segment + } + _ => *segment, + }; + + cursor.iowrite_with(segment, ctx.le)?; + + SegmentCommand32::size_with(&ctx.le) + } + CommandVariant::Segment64(segment) => { + let segment = match segment.name() { + Ok(SEG_LINKEDIT) => { + let mut segment = *segment; + segment.filesize = new_linkedit_segment_size as _; + segment.vmsize = new_linkedit_segment_vmsize as _; + + segment + } + _ => *segment, + }; + + cursor.iowrite_with(segment, ctx.le)?; + + SegmentCommand64::size_with(&ctx.le) + } + _ => { + // Reflect the original bytes. + cursor.write_all(original_command_data)?; + original_command_data.len() + } + }; + + // For the commands we mutated ourselves, there may be more data after the + // load command header. Write it out if present. + cursor.write_all(&original_command_data[written_len..])?; + } + + // If we didn't see a signature load command, write one out now. + // Note: we're assuming that there's enough space between the end of + // the original load commands and the beginning of the first section. + // All this intermediate data should be 0s and we shouldn't be + // interfering with anything here. But you never know. + // TODO validate the added load command doesn't overflow into a section + // or otherwise clobber data in the binary. + if !seen_signature_load_command { + let command = LinkeditDataCommand { + cmd: LC_CODE_SIGNATURE, + cmdsize: SIZEOF_LINKEDIT_DATA_COMMAND as _, + dataoff: signature_file_offset as _, + datasize: signature_data.len() as _, + }; + + cursor.iowrite_with(command, ctx.le)?; + } + + let mut wrote_non_empty_segment = false; + + // Write out segments, updating the __LINKEDIT segment when we encounter it. + for segment in macho.segments_by_file_offset() { + // The initial __PAGEZERO segment contains no data (it is the magic and load + // commands) and overlaps with the __TEXT segment, so we ignore it. + if matches!(segment.name(), Ok(SEG_PAGEZERO)) { + continue; + } + + match cursor.position().cmp(&segment.fileoff) { + // Mach-O segments may have padding between them. In this case, copy these + // bytes (presumably NULLs but that isn't guaranteed) to the output. + Ordering::Less => { + let padding = &macho.data[cursor.position() as usize..segment.fileoff as usize]; + debug!( + "copying {} bytes outside segment boundaries before segment {}", + padding.len(), + segment.name().unwrap_or("") + ); + cursor.write_all(padding)?; + } + + // The __TEXT segment usually has .fileoff = 0, which has it overlapping with + // already written data. Allow this special case through. + Ordering::Greater if segment.fileoff == 0 => {} + + // The initial non-empty segment is special because it can overlap + // we the already written load commands. + // + // Usually the first non-empty segment is __TEXT and its file start + // offset is 0x0. But we've seen binaries in the wild where the + // offset is > 0x0. As long as the current cursor is before the first + // section data, there should be no data corruption and we're good. + Ordering::Greater if !wrote_non_empty_segment => {} + + // The writer has overran into this segment. That means we screwed up on a + // previous loop iteration. + Ordering::Greater => { + return Err(AppleCodesignError::MachOWrite(format!( + "Mach-O segment corruption: cursor at 0x{:x} but segment begins at 0x{:x} (please report this bug)", + cursor.position(), + segment.fileoff + ))); + } + Ordering::Equal => {} + } + + match segment.name() { + Ok(SEG_LINKEDIT) => { + cursor.write_all( + macho + .linkedit_data_before_signature() + .expect("__LINKEDIT segment data should resolve"), + )?; + + let padding = vec![0u8; signature_padding_length]; + cursor.write_all(&padding)?; + + assert_eq!(cursor.position(), signature_file_offset); + assert_eq!(cursor.position() % 16, 0); + cursor.write_all(signature_data)?; + } + _ => { + // At least the __TEXT segment has .fileoff = 0, which has it + // overlapping with already written data. So only write segment + // data new to the writer. + if segment.fileoff < cursor.position() { + if segment.data.is_empty() { + continue; + } + let remaining = + &segment.data[cursor.position() as usize..segment.filesize as usize]; + cursor.write_all(remaining)?; + } else { + cursor.write_all(segment.data)?; + } + } + } + + wrote_non_empty_segment = true; + } + + Ok(cursor.into_inner()) +} + +/// Write Mach-O file content to an output file. +pub fn write_macho_file( + input_path: &Path, + output_path: &Path, + macho_data: &[u8], +) -> Result<(), AppleCodesignError> { + // Read permissions first in case we overwrite the original file. + let permissions = std::fs::metadata(input_path)?.permissions(); + + if let Some(parent) = output_path.parent() { + std::fs::create_dir_all(parent)?; + } + + { + let mut fh = std::fs::File::create(output_path)?; + fh.write_all(macho_data)?; + } + + std::fs::set_permissions(output_path, permissions)?; + + Ok(()) +} + +/// Mach-O binary signer. +/// +/// This type provides a high-level interface for signing Mach-O binaries. +/// It handles parsing and rewriting Mach-O binaries and contains most of the +/// functionality for producing signatures for individual Mach-O binaries. +/// +/// Signing of both single architecture and fat/universal binaries is supported. +/// +/// # Circular Dependency +/// +/// There is a circular dependency between the generation of the Code Directory +/// present in the embedded signature and the Mach-O binary. See the note +/// in [crate::specification] for the gory details. The tl;dr is the Mach-O +/// data up to the signature data needs to be digested. But that digested data +/// contains load commands that reference the signature data and its size, which +/// can't be known until the Code Directory, CMS blob, and SuperBlob are all +/// created. +/// +/// Our solution to this problem is to estimate the size of the embedded +/// signature data and then pad the unused data will 0s. +pub struct MachOSigner<'data> { + /// Parsed Mach-O binaries. + machos: Vec>, +} + +impl<'data> MachOSigner<'data> { + /// Construct a new instance from unparsed data representing a Mach-O binary. + /// + /// The data will be parsed as a Mach-O binary (either single arch or fat/universal) + /// and validated that we are capable of signing it. + pub fn new(macho_data: &'data [u8]) -> Result { + let machos = MachFile::parse(macho_data)?.into_iter().collect::>(); + + Ok(Self { machos }) + } + + /// Write signed Mach-O data to the given writer using signing settings. + pub fn write_signed_binary( + &self, + settings: &SigningSettings, + writer: &mut impl Write, + ) -> Result<(), AppleCodesignError> { + // Implementing a true streaming writer requires calculating final sizes + // of all binaries so fat header offsets and sizes can be written first. We take + // the easy road and buffer individual Mach-O binaries internally. + + let binaries = self + .machos + .iter() + .enumerate() + .map(|(index, original_macho)| { + info!("signing Mach-O binary at index {}", index); + let settings = settings + .as_universal_macho_settings(index, original_macho.macho.header.cputype()); + + let signature_len = + self.estimate_embedded_signature_size(original_macho, &settings)?; + + // Derive an intermediate Mach-O with placeholder NULLs for signature + // data so Code Directory digests over the load commands are correct. + let placeholder_signature_data = b"\0".repeat(signature_len); + + let intermediate_macho_data = + create_macho_with_signature(original_macho, &placeholder_signature_data)?; + + // A nice side-effect of this is that it catches bugs if we write malformed Mach-O! + let intermediate_macho = MachOBinary::parse(&intermediate_macho_data)?; + + let mut signature_data = self.create_superblob(&settings, &intermediate_macho)?; + info!("total signature size: {} bytes", signature_data.len()); + + // The Mach-O writer adjusts load commands based on the signature length. So pad + // with NULLs to get to our placeholder length. + match signature_data.len().cmp(&placeholder_signature_data.len()) { + Ordering::Greater => { + return Err(AppleCodesignError::SignatureDataTooLarge); + } + Ordering::Equal => {} + Ordering::Less => { + signature_data.extend_from_slice( + &b"\0".repeat(placeholder_signature_data.len() - signature_data.len()), + ); + } + } + + create_macho_with_signature(&intermediate_macho, &signature_data) + }) + .collect::, AppleCodesignError>>()?; + + if binaries.len() > 1 { + create_universal_macho(writer, binaries.iter().map(|x| x.as_slice()))?; + } else { + writer.write_all(&binaries[0])?; + } + + Ok(()) + } + + /// Create data constituting the SuperBlob to be embedded in the `__LINKEDIT` segment. + /// + /// The superblob contains the code directory, any extra blobs, and an optional + /// CMS structure containing a cryptographic signature. + /// + /// This takes an explicit Mach-O to operate on due to a circular dependency + /// between writing out the Mach-O and digesting its content. See the note + /// in [MachOSigner] for details. + pub fn create_superblob( + &self, + settings: &SigningSettings, + macho: &MachOBinary, + ) -> Result, AppleCodesignError> { + let mut builder = EmbeddedSignatureBuilder::default(); + + for (slot, blob) in self.create_special_blobs(settings, macho.is_executable())? { + builder.add_blob(slot, blob)?; + } + + let code_directory = self.create_code_directory(settings, macho)?; + info!("code directory version: {}", code_directory.version); + + builder.add_code_directory(CodeSigningSlot::CodeDirectory, code_directory)?; + + if let Some(digests) = settings.extra_digests(SettingsScope::Main) { + for digest_type in digests { + // Since everything consults settings for the digest to use, just make a new settings + // with a different digest. + let mut alt_settings = settings.clone(); + alt_settings.set_digest_type(SettingsScope::Main, *digest_type); + + info!( + "adding alternative code directory using digest {:?}", + digest_type + ); + let cd = self.create_code_directory(&alt_settings, macho)?; + + builder.add_alternative_code_directory(cd)?; + } + } + + if let Some((signing_key, signing_cert)) = settings.signing_key() { + builder.create_cms_signature( + signing_key, + signing_cert, + settings.time_stamp_url(), + settings.certificate_chain().iter().cloned(), + settings.signing_time(), + )?; + } else { + builder.create_empty_cms_signature()?; + } + + builder.create_superblob() + } + + /// Create the `CodeDirectory` for the current configuration. + /// + /// This takes an explicit Mach-O to operate on due to a circular dependency + /// between writing out the Mach-O and digesting its content. See the note + /// in [MachOSigner] for details. + pub fn create_code_directory( + &self, + settings: &SigningSettings, + macho: &MachOBinary, + ) -> Result, AppleCodesignError> { + // TODO support defining or filling in proper values for fields with + // static values. + + let target = macho.find_targeting()?; + + if let Some(target) = &target { + info!( + "binary targets {} >= {} with SDK {}", + target.platform, target.minimum_os_version, target.sdk_version, + ); + } + + let mut flags = CodeSignatureFlags::empty(); + + if let Some(additional) = settings.code_signature_flags(SettingsScope::Main) { + info!( + "adding code signature flags from signing settings: {:?}", + additional + ); + flags |= additional; + } + + // The adhoc flag is set when there is no CMS signature. + if settings.signing_key().is_none() { + info!("creating ad-hoc signature"); + flags |= CodeSignatureFlags::ADHOC; + } else if flags.contains(CodeSignatureFlags::ADHOC) { + info!("removing ad-hoc code signature flag"); + flags -= CodeSignatureFlags::ADHOC; + } + + // Remove linker signed flag because we're not a linker. + if flags.contains(CodeSignatureFlags::LINKER_SIGNED) { + info!("removing linker signed flag from code signature (we're not a linker)"); + flags -= CodeSignatureFlags::LINKER_SIGNED; + } + + // Code limit fields hold the file offset at which code digests stop. This + // is the file offset in the `__LINKEDIT` segment when the embedded signature + // SuperBlob begins. + let (code_limit, code_limit_64) = match macho.code_limit_binary_offset()? { + x if x > u32::MAX as u64 => (0, Some(x)), + x => (x as u32, None), + }; + + let platform = 0; + let page_size = 4096u32; + + let (exec_seg_base, exec_seg_limit) = macho.executable_segment_boundary()?; + let (exec_seg_base, exec_seg_limit) = (Some(exec_seg_base), Some(exec_seg_limit)); + + // Executable segment flags are wonky. + // + // Foremost, these flags are only present if the Mach-O binary is an executable. So not + // matter what the settings say, we don't set these flags unless the Mach-O file type + // is proper. + // + // Executable segment flags are also derived from an associated entitlements plist. + let exec_seg_flags = if macho.is_executable() { + if let Some(entitlements) = settings.entitlements_plist(SettingsScope::Main) { + let flags = plist_to_executable_segment_flags(entitlements); + + if !flags.is_empty() { + info!("entitlements imply executable segment flags: {:?}", flags); + } + + Some(flags | ExecutableSegmentFlags::MAIN_BINARY) + } else { + Some(ExecutableSegmentFlags::MAIN_BINARY) + } + } else { + None + }; + + // The runtime version is the SDK version from the targeting loader commands. Same + // u32 with nibbles encoding the version. + // + // If the runtime code signature flag is set, we also need to set the runtime version + // or else the activation of the hardened runtime is incomplete. + + // If the settings defines a runtime version override, use it. + let runtime = match settings.runtime_version(SettingsScope::Main) { + Some(version) => { + info!( + "using hardened runtime version {} from signing settings", + version + ); + Some(semver_to_macho_target_version(version)) + } + None => None, + }; + + // If we still don't have a runtime but need one, derive from the target SDK. + let runtime = if runtime.is_none() && flags.contains(CodeSignatureFlags::RUNTIME) { + if let Some(target) = &target { + info!( + "using hardened runtime version {} derived from SDK version", + target.sdk_version + ); + Some(semver_to_macho_target_version(&target.sdk_version)) + } else { + warn!("hardened runtime version required but unable to derive suitable version; signature will likely fail Apple checks"); + None + } + } else { + runtime + }; + + let digest_type = settings.digest_type(SettingsScope::Main); + + let code_hashes = macho + .code_digests(digest_type, page_size as _)? + .into_iter() + .map(|v| Digest { data: v.into() }) + .collect::>(); + + let mut special_hashes = HashMap::new(); + + // There is no corresponding blob for the info plist data since it is provided + // externally to the embedded signature. + if let Some(data) = settings.info_plist_data(SettingsScope::Main) { + special_hashes.insert( + CodeSigningSlot::Info, + Digest { + data: digest_type.digest_data(data)?.into(), + }, + ); + } + + // There is no corresponding blob for resources data since it is provided + // externally to the embedded signature. + if let Some(data) = settings.code_resources_data(SettingsScope::Main) { + special_hashes.insert( + CodeSigningSlot::ResourceDir, + Digest { + data: digest_type.digest_data(data)?.into(), + } + .to_owned(), + ); + } + + let ident = Cow::Owned( + settings + .binary_identifier(SettingsScope::Main) + .ok_or(AppleCodesignError::NoIdentifier)? + .to_string(), + ); + + // Team should only be included when signing with an Apple signed + // certificate. This logic is handled in [SigningSettings]. But emit + // a warning if the constraint is violated. + let team_name = settings.team_id().map(|x| Cow::Owned(x.to_string())); + + if team_name.is_some() && !settings.signing_certificate_apple_signed() { + warn!("signing without an Apple signed certificate but signing settings contain a team name; signature varies from Apple's tooling"); + } + + let mut cd = CodeDirectoryBlob { + flags, + code_limit, + digest_size: digest_type.hash_len()? as u8, + digest_type, + platform, + page_size, + code_limit_64, + exec_seg_base, + exec_seg_limit, + exec_seg_flags, + runtime, + ident, + team_name, + code_digests: code_hashes, + ..Default::default() + }; + + for (slot, digest) in special_hashes { + cd.set_slot_digest(slot, digest)?; + } + + cd.adjust_version(target); + cd.clear_newer_fields(); + + Ok(cd) + } + + /// Create blobs that need to be written given the current configuration. + /// + /// This emits all blobs except `CodeDirectory` and `Signature`, which are + /// special since they are derived from the blobs emitted here. + /// + /// The goal of this function is to emit data to facilitate the creation of + /// a `CodeDirectory`, which requires hashing blobs. + pub fn create_special_blobs( + &self, + settings: &SigningSettings, + is_executable: bool, + ) -> Result)>, AppleCodesignError> { + let mut res = Vec::new(); + + let mut requirements = CodeRequirements::default(); + + match settings.designated_requirement(SettingsScope::Main) { + DesignatedRequirementMode::Auto => { + // If we are using an Apple-issued cert, this should automatically + // derive appropriate designated requirements. + if let Some((_, cert)) = settings.signing_key() { + info!("deriving code requirements from signing certificate"); + let identifier = Some( + settings + .binary_identifier(SettingsScope::Main) + .ok_or(AppleCodesignError::NoIdentifier)? + .to_string(), + ); + + let expr = derive_designated_requirements( + cert, + settings.certificate_chain(), + identifier, + )?; + requirements.push(expr); + } + } + DesignatedRequirementMode::Explicit(exprs) => { + info!("using provided code requirements"); + for expr in exprs { + requirements.push(CodeRequirementExpression::from_bytes(expr)?.0); + } + } + } + + // Always emit a RequirementSet blob, even if empty. Without it, validation fails + // with `the sealed resource directory is invalid`. + let mut blob = RequirementSetBlob::default(); + + if !requirements.is_empty() { + requirements.add_to_requirement_set(&mut blob, RequirementType::Designated)?; + } + + res.push((CodeSigningSlot::RequirementSet, blob.into())); + + if let Some(entitlements) = settings.entitlements_xml(SettingsScope::Main)? { + let blob = EntitlementsBlob::from_string(&entitlements); + + res.push((CodeSigningSlot::Entitlements, blob.into())); + } + + // The DER encoded entitlements weren't always present in the signature. The feature + // appears to have been introduced in macOS 10.14 and is the default behavior as of + // macOS 12 "when signing for all platforms." `codesign` appears to add the DER + // representation whenever entitlements are present, but only if the current binary is + // an executable (.filetype == MH_EXECUTE). + if is_executable { + if let Some(value) = settings.entitlements_plist(SettingsScope::Main) { + let blob = EntitlementsDerBlob::from_plist(value)?; + + res.push((CodeSigningSlot::EntitlementsDer, blob.into())); + } + } + + if let Some(constraints) = settings.launch_constraints_self(SettingsScope::Main) { + let blob = ConstraintsDerBlob::from_encoded_constraints(constraints)?; + res.push((CodeSigningSlot::LaunchConstraintsSelf, blob.into())); + } + + if let Some(constraints) = settings.launch_constraints_parent(SettingsScope::Main) { + let blob = ConstraintsDerBlob::from_encoded_constraints(constraints)?; + res.push((CodeSigningSlot::LaunchConstraintsParent, blob.into())); + } + + if let Some(constraints) = settings.launch_constraints_responsible(SettingsScope::Main) { + let blob = ConstraintsDerBlob::from_encoded_constraints(constraints)?; + res.push(( + CodeSigningSlot::LaunchConstraintsResponsibleProcess, + blob.into(), + )); + } + + if let Some(constraints) = settings.library_constraints(SettingsScope::Main) { + let blob = ConstraintsDerBlob::from_encoded_constraints(constraints)?; + res.push((CodeSigningSlot::LibraryConstraints, blob.into())); + } + + Ok(res) + } + + /// Estimate the size in bytes of an embedded code signature. + pub fn estimate_embedded_signature_size( + &self, + macho: &MachOBinary, + settings: &SigningSettings, + ) -> Result { + let code_directory_count = 1 + settings + .extra_digests(SettingsScope::Main) + .map(|x| x.len()) + .unwrap_or_default(); + + // Assume the common data structures are 1024 bytes. + let mut size = 1024 * code_directory_count; + + // Reserve room for the code digests, which are proportional to binary size. + size += macho.code_digests_size(settings.digest_type(SettingsScope::Main), 4096)?; + + if let Some(digests) = settings.extra_digests(SettingsScope::Main) { + for digest in digests { + size += macho.code_digests_size(*digest, 4096)?; + } + } + + // Add in sizes of all encoded blobs, as many blobs are variable size. + for (_, blob) in self.create_special_blobs(settings, true)? { + size += blob.to_blob_bytes()?.len(); + } + + // Assume the CMS data will take a fixed size. + size += 4096; + + // Long certificate chains could blow up the size. Account for those. + for cert in settings.certificate_chain() { + size += cert.constructed_data().len(); + } + + // Resize space for CMS timestamp token, if being generated. + // + // We used to actually call out to a remote server here and obtain a + // placeholder token. But this seemed excessive, especially since we did + // it on every signing operation. + // + // Apple's TSTs are ~4200 bytes in size. We approximately double that + // to give us some buffer. + if settings.time_stamp_url().is_some() { + size += 8192; + } + + // Align on 1k boundaries just because. + size += 1024 - size % 1024; + + Ok(size) + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/macho_universal.rs b/3rdparty/apple-codesign-0.29.0/src/macho_universal.rs new file mode 100644 index 00000000..45638753 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/macho_universal.rs @@ -0,0 +1,129 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use { + goblin::mach::{ + fat::{FatArch, FAT_MAGIC, SIZEOF_FAT_ARCH, SIZEOF_FAT_HEADER}, + Mach, + }, + scroll::{IOwrite, Pwrite}, + std::io::Write, + thiserror::Error, +}; + +#[derive(Debug, Error)] +pub enum UniversalMachOError { + #[error("I/O error: {0}")] + Io(#[from] std::io::Error), + + #[error("mach-o parse error: {0}")] + Goblin(#[from] goblin::error::Error), + + #[error("scroll error: {0}")] + Scroll(#[from] scroll::Error), +} + +/// Interface for constructing a universal Mach-O binary. +#[derive(Clone, Default)] +pub struct UniversalBinaryBuilder { + binaries: Vec>, +} + +impl UniversalBinaryBuilder { + pub fn add_binary(&mut self, data: impl AsRef<[u8]>) -> Result { + let data = data.as_ref(); + + match Mach::parse(data)? { + Mach::Binary(_) => { + self.binaries.push(data.to_vec()); + Ok(1) + } + Mach::Fat(multiarch) => { + for arch in multiarch.iter_arches() { + let arch = arch?; + + let data = + &data[arch.offset as usize..arch.offset as usize + arch.size as usize]; + self.binaries.push(data.to_vec()); + } + + Ok(multiarch.narches) + } + } + } + + /// Write a universal Mach-O to the given writer. + pub fn write(&self, writer: &mut impl Write) -> Result<(), UniversalMachOError> { + create_universal_macho(writer, self.binaries.iter().map(|x| x.as_slice())) + } +} + +/// Create a universal mach-o binary from existing mach-o binaries. +/// +/// The binaries will be parsed as Mach-O. +/// +/// Because the size of the individual Mach-O binaries must be written into a +/// header, all content is buffered internally. +pub fn create_universal_macho<'a>( + writer: &mut impl Write, + binaries: impl Iterator, +) -> Result<(), UniversalMachOError> { + // Binaries are aligned on page boundaries. x86-64 appears to use + // 4k. aarch64 16k. It really doesn't appear to matter unless you want + // to minimize binary size, so we always use 16k. + const ALIGN_VALUE: u32 = 14; + let align: u32 = 2u32.pow(ALIGN_VALUE); + + let mut records = vec![]; + + let mut offset: u32 = align; + + for binary in binaries { + let macho = goblin::mach::MachO::parse(binary, 0)?; + + // This will be 0 for the 1st binary. + let pad_bytes = match offset % align { + 0 => 0, + x => align - x, + }; + + offset += pad_bytes; + + let arch = FatArch { + cputype: macho.header.cputype, + cpusubtype: macho.header.cpusubtype, + offset, + size: binary.len() as u32, + align: ALIGN_VALUE, + }; + + offset += arch.size; + + records.push((arch, pad_bytes as usize, binary)); + } + + // Fat header is the magic plus the number of records. + writer.iowrite_with(FAT_MAGIC, scroll::BE)?; + writer.iowrite_with(records.len() as u32, scroll::BE)?; + + for (fat_arch, _, _) in &records { + let mut buffer = [0u8; SIZEOF_FAT_ARCH]; + buffer.pwrite_with(fat_arch, 0, scroll::BE)?; + writer.write_all(&buffer)?; + } + + // Pad NULL until first mach-o binary. + let current_offset = SIZEOF_FAT_HEADER + records.len() * SIZEOF_FAT_ARCH; + writer.write_all(&b"\0".repeat(align as usize - current_offset % align as usize))?; + + // This input would be nonsensical. Let's not even support it. + assert!(current_offset <= align as usize, "too many mach-o entries"); + + for (_, pad_bytes, macho_data) in records { + writer.write_all(&b"\0".repeat(pad_bytes))?; + writer.write_all(macho_data)?; + } + + Ok(()) +} diff --git a/3rdparty/apple-codesign-0.29.0/src/macos.rs b/3rdparty/apple-codesign-0.29.0/src/macos.rs new file mode 100644 index 00000000..bb812d0b --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/macos.rs @@ -0,0 +1,341 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Functionality that only works on macOS. + +use { + crate::{ + certificate::{AppleCertificate, OID_USER_ID}, + cryptography::PrivateKey, + error::AppleCodesignError, + remote_signing::{session_negotiation::PublicKeyPeerDecrypt, RemoteSignError}, + }, + bcder::Oid, + bytes::Bytes, + log::{error, warn}, + security_framework::{ + certificate::SecCertificate, + item::{ItemClass, ItemSearchOptions, Reference, SearchResult}, + key::SecKey, + os::macos::{ + item::ItemSearchOptionsExt, + keychain::{SecKeychain, SecPreferencesDomain}, + }, + }, + security_framework_sys::key::Algorithm as KeychainAlgorithm, + signature::Signer, + std::ops::Deref, + x509_certificate::{ + CapturedX509Certificate, KeyAlgorithm, KeyInfoSigner, Sign, Signature, SignatureAlgorithm, + X509CertificateError, + }, + zeroize::Zeroizing, +}; + +const SYSTEM_ROOTS_KEYCHAIN: &str = "/System/Library/Keychains/SystemRootCertificates.keychain"; + +/// A wrapper around [SecPreferencesDomain] so we can use crate local types. +#[derive(Clone, Copy, Debug)] +pub enum KeychainDomain { + User, + System, + Common, + Dynamic, +} + +impl From for SecPreferencesDomain { + fn from(v: KeychainDomain) -> Self { + match v { + KeychainDomain::User => Self::User, + KeychainDomain::System => Self::System, + KeychainDomain::Common => Self::Common, + KeychainDomain::Dynamic => Self::Dynamic, + } + } +} + +impl TryFrom<&str> for KeychainDomain { + type Error = String; + + fn try_from(v: &str) -> Result { + match v { + "user" => Ok(Self::User), + "system" => Ok(Self::System), + "common" => Ok(Self::Common), + "dynamic" => Ok(Self::Dynamic), + _ => Err(format!( + "{} is not a valid keychain domain; use user, system, common, or dynamic", + v + )), + } + } +} + +/// A certificate in a keychain. +#[derive(Clone)] +pub struct KeychainCertificate { + sec_cert: SecCertificate, + sec_key: SecKey, + captured: CapturedX509Certificate, +} + +impl Deref for KeychainCertificate { + type Target = CapturedX509Certificate; + + fn deref(&self) -> &Self::Target { + &self.captured + } +} + +impl Signer for KeychainCertificate { + fn try_sign(&self, message: &[u8]) -> Result { + let algorithm = self + .signature_algorithm() + .map_err(signature::Error::from_source)?; + + let algorithm = match algorithm { + SignatureAlgorithm::RsaSha1 => KeychainAlgorithm::RSASignatureMessagePKCS1v15SHA1, + SignatureAlgorithm::RsaSha256 => KeychainAlgorithm::RSASignatureMessagePKCS1v15SHA256, + SignatureAlgorithm::RsaSha384 => KeychainAlgorithm::RSASignatureMessagePKCS1v15SHA384, + SignatureAlgorithm::RsaSha512 => KeychainAlgorithm::RSASignatureMessagePKCS1v15SHA512, + SignatureAlgorithm::EcdsaSha256 => KeychainAlgorithm::ECDSASignatureMessageX962SHA256, + SignatureAlgorithm::EcdsaSha384 => KeychainAlgorithm::ECDSASignatureMessageX962SHA384, + SignatureAlgorithm::Ed25519 => KeychainAlgorithm::ECDSASignatureMessageX962SHA512, + SignatureAlgorithm::NoSignature(_) => { + return Err(signature::Error::from_source("digest only signature")); + } + }; + + warn!( + "attempting to create signature using keychain item: {}", + self.sec_cert.subject_summary() + ); + + let signature = self + .sec_key + .create_signature(algorithm, message) + .map_err(|e| { + signature::Error::from_source(format!( + "when attempting to create signature from keychain item: {}", + e + )) + })?; + + Ok(Signature::from(signature)) + } +} + +impl Sign for KeychainCertificate { + fn sign(&self, message: &[u8]) -> Result<(Vec, SignatureAlgorithm), X509CertificateError> { + let algorithm = self.signature_algorithm()?; + + Ok((self.try_sign(message)?.into(), algorithm)) + } + + fn key_algorithm(&self) -> Option { + self.captured.key_algorithm() + } + + fn public_key_data(&self) -> Bytes { + self.captured.public_key_data() + } + + fn signature_algorithm(&self) -> Result { + self.captured + .signature_algorithm() + .ok_or(X509CertificateError::UnknownSignatureAlgorithm(format!( + "{:?}", + self.captured.signature_algorithm_oid() + ))) + } + + fn private_key_data(&self) -> Option>> { + None + } + + fn rsa_primes( + &self, + ) -> Result>, Zeroizing>)>, X509CertificateError> { + Ok(None) + } +} + +impl KeyInfoSigner for KeychainCertificate {} + +impl PublicKeyPeerDecrypt for KeychainCertificate { + fn decrypt(&self, _ciphertext: &[u8]) -> Result, RemoteSignError> { + // It doesn't look like the Rust bindings expose the APIs we need to + // implement decryption. Sadness. Will probably need to contribute + // those upstream... + error!("missing feature along with workarounds tracked in https://github.com/indygreg/apple-platform-rs/issues/7"); + Err(RemoteSignError::Crypto( + "decryption not yet implemented for keychain stored keys".into(), + )) + } +} + +impl PrivateKey for KeychainCertificate { + fn as_key_info_signer(&self) -> &dyn KeyInfoSigner { + self + } + + fn to_public_key_peer_decrypt( + &self, + ) -> Result, AppleCodesignError> { + Ok(Box::new(self.clone())) + } + + fn finish(&self) -> Result<(), AppleCodesignError> { + Ok(()) + } +} + +impl KeychainCertificate { + /// Obtain a new [CapturedX509Certificate] for this item. + pub fn as_captured_x509_certificate(&self) -> CapturedX509Certificate { + self.captured.clone() + } +} + +fn find_certificates( + keychains: &[SecKeychain], +) -> Result, AppleCodesignError> { + let mut search = ItemSearchOptions::default(); + search.keychains(keychains); + // We fetch identities here because that gives us access to both the public + // cert and private key. The keychain doesn't need to be unlocked to get a + // handle on the private key: only when an operation on the private key is + // requested. + search.class(ItemClass::identity()); + search.limit(i32::MAX as i64); + + let mut certs = vec![]; + + for item in search.search()? { + match item { + SearchResult::Ref(reference) => match reference { + Reference::Identity(identity) => { + let cert = identity.certificate()?; + let private_key = identity.private_key()?; + + if let Ok(captured) = CapturedX509Certificate::from_der(cert.to_der()) { + certs.push(KeychainCertificate { + sec_cert: cert, + sec_key: private_key, + captured, + }); + } + } + + _ => { + return Err(AppleCodesignError::KeychainError( + "non-certificate reference from keychain search (this should not happen)" + .to_string(), + )); + } + }, + _ => { + return Err(AppleCodesignError::KeychainError( + "non-reference result from keychain search (this should not happen)" + .to_string(), + )); + } + } + } + + Ok(certs) +} + +/// Locate code signing certificates in the macOS keychain. +pub fn keychain_find_code_signing_certificates( + domain: KeychainDomain, + password: Option<&str>, +) -> Result, AppleCodesignError> { + let mut keychain = SecKeychain::default_for_domain(domain.into())?; + if password.is_some() { + keychain.unlock(password)?; + } + + let certs = find_certificates(&[keychain])?; + + Ok(certs + .into_iter() + .filter(|cert| !cert.captured.apple_code_signing_extensions().is_empty()) + .collect::>()) +} + +/// Find the x509 certificate chain for a certificate given search parameters. +/// +/// `domain` and `password` specify which keychain to operate on and whether +/// to attempt to unlock it via a password. +/// +/// `user_id` specifies the UID value in the certificate subject to search for. +/// You can find this in `Keychain Access` by clicking on the certificate in +/// question and looking for `User ID` under the `Subject Name` section. +pub fn macos_keychain_find_certificate_chain( + domain: KeychainDomain, + password: Option<&str>, + user_id: &str, +) -> Result, AppleCodesignError> { + let mut keychain = SecKeychain::default_for_domain(domain.into())?; + if password.is_some() { + keychain.unlock(password)?; + } + + // Find all certificates for the given keychain plus the system roots, which + // has the root CAs. + let keychains = vec![SecKeychain::open(SYSTEM_ROOTS_KEYCHAIN)?, keychain]; + + let certs = find_certificates(&keychains)?; + + // Now search for the requested start certificate and pull the thread until + // we get to a self-signed certificate. + let start_cert: &CapturedX509Certificate = certs + .iter() + .find_map(|cert| { + if let Ok(Some(value)) = cert + .captured + .subject_name() + .find_first_attribute_string(Oid(OID_USER_ID.as_ref().into())) + { + if value == user_id { + Some(&cert.captured) + } else { + None + } + } else { + None + } + }) + .ok_or_else(|| AppleCodesignError::CertificateNotFound(format!("UID={}", user_id)))?; + + let mut chain = vec![start_cert.clone()]; + let mut last_issuer_name = start_cert.issuer_name(); + + loop { + let issuer = certs.iter().find_map(|cert| { + if cert.captured.subject_name() == last_issuer_name { + Some(&cert.captured) + } else { + None + } + }); + + if let Some(issuer) = issuer { + chain.push(issuer.clone()); + + // Self signed. Stop the chain so we don't infinite loop. + if issuer.subject_name() == issuer.issuer_name() { + break; + } else { + last_issuer_name = issuer.issuer_name(); + } + } else { + // Couldn't find issuer. Stop the search. + break; + } + } + + Ok(chain) +} diff --git a/3rdparty/apple-codesign-0.29.0/src/main.rs b/3rdparty/apple-codesign-0.29.0/src/main.rs new file mode 100644 index 00000000..ed1dedba --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/main.rs @@ -0,0 +1,33 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use apple_codesign::AppleCodesignError; + +fn main() { + let exit_code = match apple_codesign::cli::main_impl() { + Ok(()) => 0, + Err(AppleCodesignError::Figment(err)) => { + eprintln!("configuration file error"); + + err.metadata.as_ref().map(|metadata| { + metadata.source.as_ref().map(|source| { + source.file_path().map(|path| { + eprintln!(" source path: {}", path.display()); + }) + }) + }); + if let Some(profile) = err.profile.as_ref() { eprintln!(" in profile: {}", profile) } + eprintln!(" problem key: {}", err.path.join(", ")); + eprintln!(" problem: {:?}", err.kind); + + 1 + } + Err(err) => { + eprintln!("Error: {err}"); + 1 + } + }; + + std::process::exit(exit_code) +} diff --git a/3rdparty/apple-codesign-0.29.0/src/notarization.rs b/3rdparty/apple-codesign-0.29.0/src/notarization.rs new file mode 100644 index 00000000..9116beef --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/notarization.rs @@ -0,0 +1,443 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +/*! Apple notarization functionality. + +Notarization works by uploading a payload to Apple servers and waiting for +Apple to scan the submitted content. If Apple is appeased by your submission, +they issue a notarization ticket, which can be downloaded and *stapled* (just +a fancy word for *attached*) to the content you upload. + +This module implements functionality for uploading content to Apple +and waiting on the availability of a notarization ticket. +*/ + +use { + crate::{reader::PathType, AppleCodesignError}, + app_store_connect::{notary_api, AppStoreConnectClient, ConnectTokenEncoder, UnifiedApiKey}, + apple_bundles::DirectoryBundle, + aws_sdk_s3::config::{Credentials, Region}, + aws_smithy_types::byte_stream::ByteStream, + log::warn, + sha2::Digest, + std::{ + fs::File, + io::{Read, Seek, SeekFrom, Write}, + path::{Path, PathBuf}, + time::Duration, + }, +}; + +fn digest(reader: &mut R) -> Result<(u64, Vec), AppleCodesignError> { + let mut hasher = H::new(); + let mut size = 0; + + loop { + let mut buffer = [0u8; 16384]; + let count = reader.read(&mut buffer)?; + + size += count as u64; + hasher.update(&buffer[0..count]); + + if count < buffer.len() { + break; + } + } + + Ok((size, hasher.finalize().to_vec())) +} + +fn digest_sha256(reader: &mut R) -> Result<(u64, Vec), AppleCodesignError> { + digest::(reader) +} + +/// Produce zip file data from a [DirectoryBundle]. +/// +/// The built zip file will contain all the files from the bundle under a directory +/// tree having the bundle name. e.g. if you pass `MyApp.app`, the zip will have +/// files like `MyApp.app/Contents/Info.plist`. +pub fn bundle_to_zip(bundle: &DirectoryBundle) -> Result, AppleCodesignError> { + let mut zf = zip::ZipWriter::new(std::io::Cursor::new(vec![])); + + let mut symlinks = vec![]; + + for file in bundle + .files(true) + .map_err(AppleCodesignError::DirectoryBundle)? + { + let entry = file + .as_file_entry() + .map_err(AppleCodesignError::DirectoryBundle)?; + + let name = + format!("{}/{}", bundle.name(), file.relative_path().display()).replace('\\', "/"); + + let options = zip::write::SimpleFileOptions::default(); + + let options = if entry.link_target().is_some() { + symlinks.push(name.as_bytes().to_vec()); + options.compression_method(zip::CompressionMethod::Stored) + } else if entry.is_executable() { + options.unix_permissions(0o755) + } else { + options.unix_permissions(0o644) + }; + + zf.start_file(name, options)?; + + if let Some(target) = entry.link_target() { + zf.write_all(target.to_string_lossy().replace('\\', "/").as_bytes())?; + } else { + zf.write_all(&entry.resolve_content()?)?; + } + } + + let mut writer = zf.finish()?; + + // Current versions of the zip crate don't support writing symlinks. We + // added that support upstream but it isn't released yet. + // TODO remove this hackery once we upgrade the zip crate. + let eocd = zip_structs::zip_eocd::ZipEOCD::from_reader(&mut writer)?; + let cd_entries = + zip_structs::zip_central_directory::ZipCDEntry::all_from_eocd(&mut writer, &eocd)?; + + for mut cd in cd_entries { + if symlinks.contains(&cd.file_name_raw) { + cd.external_file_attributes = + (0o120777 << 16) | (cd.external_file_attributes & 0x0000ffff); + writer.seek(SeekFrom::Start(cd.starting_position_with_signature))?; + cd.write(&mut writer)?; + } + } + + Ok(writer.into_inner()) +} + +/// Represents the result of a notarization upload. +pub enum NotarizationUpload { + /// We performed the upload and only have the upload ID / UUID for it. + /// + /// (We probably didn't wait for the upload to finish processing.) + UploadId(String), + + /// We performed an upload and have upload state from the server. + NotaryResponse(notary_api::SubmissionResponse), +} + +enum UploadKind { + Data(Vec), + Path(PathBuf), +} + +/// An entity for performing notarizations. +/// +/// Notarization works by uploading content to Apple, waiting for Apple to inspect +/// and react to that upload, then downloading a notarization "ticket" from Apple +/// and incorporating it into the entity being signed. +#[derive(Clone)] +pub struct Notarizer { + token_encoder: ConnectTokenEncoder, + + /// How long to wait between polling the server for upload status. + wait_poll_interval: Duration, +} + +impl Notarizer { + /// Construct a new instance. + fn new(token_encoder: ConnectTokenEncoder) -> Self { + Self { + token_encoder, + wait_poll_interval: Duration::from_secs(3), + } + } + + /// Construct an instance from an API issuer ID and API key. + pub fn from_api_key_id( + issuer_id: impl ToString, + key_id: impl ToString, + ) -> Result { + Ok(Self::new(ConnectTokenEncoder::from_api_key_id( + key_id.to_string(), + issuer_id.to_string(), + )?)) + } + + /// Construct an instance from a file containing a JSON encoded API key. + pub fn from_api_key(path: &Path) -> Result { + Ok(Self::new(UnifiedApiKey::from_json_path(path)?.try_into()?)) + } + + /// Attempt to notarize an asset defined by a filesystem path. + /// + /// The type of path is sniffed out and the appropriate notarization routine is called. + pub fn notarize_path( + &self, + path: &Path, + wait_limit: Option, + ) -> Result { + match PathType::from_path(path)? { + PathType::Bundle => { + let bundle = DirectoryBundle::new_from_path(path) + .map_err(AppleCodesignError::DirectoryBundle)?; + self.notarize_bundle(&bundle, wait_limit) + } + PathType::Xar => self.notarize_flat_package(path, wait_limit), + PathType::Zip => self.notarize_flat_package(path, wait_limit), + PathType::Dmg => self.notarize_dmg(path, wait_limit), + PathType::MachO | PathType::Other => Err(AppleCodesignError::NotarizeUnsupportedPath( + path.to_path_buf(), + )), + } + } + + /// Attempt to notarize an on-disk bundle. + /// + /// If `wait_limit` is provided, we will wait for the upload to finish processing. + /// Otherwise, this returns as soon as the upload is performed. + pub fn notarize_bundle( + &self, + bundle: &DirectoryBundle, + wait_limit: Option, + ) -> Result { + let zipfile = bundle_to_zip(bundle)?; + let digest = sha2::Sha256::digest(&zipfile); + + let submission = self.create_submission(&digest, &format!("{}.zip", bundle.name()))?; + + self.upload_s3_and_maybe_wait(submission, UploadKind::Data(zipfile), wait_limit) + } + + /// Attempt to notarize a DMG file. + pub fn notarize_dmg( + &self, + dmg_path: &Path, + wait_limit: Option, + ) -> Result { + let filename = dmg_path + .file_name() + .map(|x| x.to_string_lossy().to_string()) + .unwrap_or_else(|| "dmg".to_string()); + + let (_, digest) = digest_sha256(&mut File::open(dmg_path)?)?; + + let submission = self.create_submission(&digest, &filename)?; + + self.upload_s3_and_maybe_wait( + submission, + UploadKind::Path(dmg_path.to_path_buf()), + wait_limit, + ) + } + + /// Attempt to notarize a flat package (`.pkg`) installer or a .zip file. + pub fn notarize_flat_package( + &self, + pkg_path: &Path, + wait_limit: Option, + ) -> Result { + let filename = pkg_path + .file_name() + .map(|x| x.to_string_lossy().to_string()) + .unwrap_or_else(|| "pkg".to_string()); + + let (_, digest) = digest_sha256(&mut File::open(pkg_path)?)?; + + let submission = self.create_submission(&digest, &filename)?; + + self.upload_s3_and_maybe_wait( + submission, + UploadKind::Path(pkg_path.to_path_buf()), + wait_limit, + ) + } +} + +impl Notarizer { + fn client(&self) -> Result { + Ok(AppStoreConnectClient::new(self.token_encoder.clone())?) + } + + /// Tell the notary service to expect an upload to S3. + fn create_submission( + &self, + raw_digest: &[u8], + name: &str, + ) -> Result { + let client = self.client()?; + + let digest = hex::encode(raw_digest); + warn!( + "creating Notary API submission for {} (sha256: {})", + name, digest + ); + + let submission = client.create_submission(&digest, name)?; + + warn!("created submission ID: {}", submission.data.id); + + Ok(submission) + } + + fn upload_s3_package( + &self, + submission: ¬ary_api::NewSubmissionResponse, + upload: UploadKind, + ) -> Result<(), AppleCodesignError> { + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build()?; + let bytestream = match upload { + UploadKind::Data(data) => ByteStream::from(data), + UploadKind::Path(path) => rt.block_on(ByteStream::from_path(path))?, + }; + + // upload using s3 api + warn!("resolving AWS S3 configuration from Apple-provided credentials"); + let config = rt.block_on( + aws_config::defaults(aws_config::BehaviorVersion::latest()) + .credentials_provider(Credentials::new( + submission.data.attributes.aws_access_key_id.clone(), + submission.data.attributes.aws_secret_access_key.clone(), + Some(submission.data.attributes.aws_session_token.clone()), + None, + "apple-codesign", + )) + // The region is not given anywhere in the Apple documentation. From + // manually testing all available regions, it appears to be + // us-west-2. + .region(Region::new("us-west-2")) + .load(), + ); + + let s3_client = aws_sdk_s3::Client::new(&config); + + warn!( + "uploading asset to s3://{}/{}", + submission.data.attributes.bucket, submission.data.attributes.object + ); + warn!("(you may see additional log output from S3 client)"); + + // TODO: Support multi-part upload. + // Unfortunately, aws-sdk-s3 does not have a simple upload_file helper + // like it does in other languages. + // See https://github.com/awslabs/aws-sdk-rust/issues/494 + let fut = s3_client + .put_object() + .bucket(submission.data.attributes.bucket.clone()) + .key(submission.data.attributes.object.clone()) + .body(bytestream) + .send(); + + rt.block_on(fut).map_err(|e| { + AppleCodesignError::AwsS3PutObject( + aws_smithy_types::error::display::DisplayErrorContext(e), + ) + })?; + + warn!("S3 upload completed successfully"); + + Ok(()) + } + + fn upload_s3_and_maybe_wait( + &self, + submission: notary_api::NewSubmissionResponse, + upload_data: UploadKind, + wait_limit: Option, + ) -> Result { + self.upload_s3_package(&submission, upload_data)?; + + let status = if let Some(wait_limit) = wait_limit { + self.wait_on_notarization_and_fetch_log(&submission.data.id, wait_limit)? + } else { + return Ok(NotarizationUpload::UploadId(submission.data.id)); + }; + + // Make sure notarization was successful. + let status = status.into_result()?; + + Ok(NotarizationUpload::NotaryResponse(status)) + } + + pub fn get_submission( + &self, + submission_id: &str, + ) -> Result { + Ok(self.client()?.get_submission(submission_id)?) + } + + pub fn wait_on_notarization( + &self, + submission_id: &str, + wait_limit: Duration, + ) -> Result { + warn!( + "waiting up to {}s for package upload {} to finish processing", + wait_limit.as_secs(), + submission_id + ); + + let start_time = std::time::Instant::now(); + + loop { + let status = self.get_submission(submission_id)?; + + let elapsed = start_time.elapsed(); + + warn!( + "poll state after {}s: {:?}", + elapsed.as_secs(), + status.data.attributes.status + ); + + if status.data.attributes.status != notary_api::SubmissionResponseStatus::InProgress { + warn!("Notary API Server has finished processing the uploaded asset"); + + return Ok(status); + } + + if elapsed >= wait_limit { + warn!("reached wait limit after {}s", elapsed.as_secs()); + return Err(AppleCodesignError::NotarizeWaitLimitReached); + } + + std::thread::sleep(self.wait_poll_interval); + } + } + + /// Obtain the processing log from an upload. + pub fn fetch_notarization_log( + &self, + submission_id: &str, + ) -> Result { + warn!("fetching notarization log for {}", submission_id); + Ok(self.client()?.get_submission_log(submission_id)?) + } + + /// Waits on an app store package upload and fetches and logs the upload log. + /// + /// This is just a convenience around [Self::wait_on_app_store_package_upload()] and + /// [Self::fetch_upload_log()]. + pub fn wait_on_notarization_and_fetch_log( + &self, + submission_id: &str, + wait_limit: Duration, + ) -> Result { + let status = self.wait_on_notarization(submission_id, wait_limit)?; + + let log = self.fetch_notarization_log(submission_id)?; + + for line in serde_json::to_string_pretty(&log)?.lines() { + warn!("notary log> {}", line); + } + + Ok(status) + } + + pub fn list_submissions( + &self, + ) -> Result { + Ok(self.client()?.list_submissions()?) + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/plist_der.rs b/3rdparty/apple-codesign-0.29.0/src/plist_der.rs new file mode 100644 index 00000000..f6b5663d --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/plist_der.rs @@ -0,0 +1,769 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +/*! Plist DER encoding. */ + +use { + crate::error::AppleCodesignError, + num_traits::cast::ToPrimitive, + plist::Value, + rasn::{ + ber::de::DecodeError, + ber::enc::EncodeError, + de::Error as DeError, + enc::Error as EncError, + types::{fields::{Field, Fields}, Class, Constraints, Constructed, Integer, Tag}, + AsnType, Codec, Decode, Decoder, Encode, Encoder, + }, + std::collections::BTreeMap, +}; + +#[derive(AsnType, Debug, Decode)] +struct DictionaryEntry { + #[rasn(tag(universal, 12))] + key: String, + value: WrappedValue, +} + +/// Represents a plist dictionary in the rasn domain. +#[derive(Debug)] +struct Dictionary(plist::Dictionary); + +impl AsnType for Dictionary { + const TAG: Tag = Tag { + class: Class::Context, + value: 16, + }; +} + +impl Constructed for Dictionary { + // This is incorrect, but a required field is needed to be specified to + // communicate to rasn it needs to know that there is fields to decode. + const FIELDS: Fields = Fields::from_static(&[Field::new_optional_type::<()>("key")]); +} + +impl Encode for Dictionary { + fn encode_with_tag_and_constraints( + &self, + encoder: &mut E, + tag: Tag, + _constraints: Constraints, + ) -> Result<(), E::Error> { + // Sort it alphabetically. + let map = self.0.iter().collect::>(); + + encoder.encode_sequence::(tag, |encoder| { + for (k, v) in map { + let wrapped = WrappedValue::try_from(v.clone())?; + + encoder.encode_sequence::(Tag::SEQUENCE, |encoder| { + encoder.encode_utf8_string(Tag::UTF8_STRING, Constraints::NONE, k)?; + wrapped.encode(encoder)?; + Ok(()) + })?; + } + + Ok(()) + })?; + + Ok(()) + } +} + +impl Decode for Dictionary { + fn decode_with_tag_and_constraints( + decoder: &mut D, + tag: Tag, + _constraints: Constraints, + ) -> Result { + decoder.decode_sequence::(tag, Some(|| Self(plist::Dictionary::new())), |decoder| { + let mut dict = plist::Dictionary::new(); + + loop { + let entry = decoder.decode_optional::()?; + + if let Some(entry) = entry { + let value = plist::Value::try_from(entry.value)?; + + dict.insert(entry.key, value); + } else { + break; + } + } + + Ok(Self(dict)) + }) + } +} + +/// Represents a [Value] in the rasn domain. +#[derive(AsnType, Debug, Decode, Encode)] +#[rasn(choice)] +enum WrappedValue { + Array(Vec), + Dictionary(Dictionary), + #[rasn(tag(universal, 1))] + Boolean(bool), + #[rasn(tag(universal, 2))] + Integer(Integer), + #[rasn(tag(universal, 12))] + String(String), +} + +impl TryFrom for WrappedValue { + type Error = EncodeError; + + fn try_from(value: Value) -> Result { + match value { + Value::Array(v) => Ok(Self::Array( + v.into_iter() + .map(Self::try_from) + .collect::, _>>()?, + )), + Value::Dictionary(v) => Ok(Self::Dictionary(Dictionary(v))), + Value::Boolean(v) => Ok(Self::Boolean(v)), + Value::Integer(v) => { + let integer = Integer::from(v.as_signed().ok_or(EncodeError::custom( + "could not obtain integer representation from plist integer", + Codec::Der, + ))?); + + Ok(Self::Integer(integer)) + } + Value::String(v) => Ok(Self::String(v)), + Value::Data(_) => Err(EncodeError::custom( + "encoding of data values not supported", + Codec::Der, + )), + Value::Date(_) => Err(EncodeError::custom( + "encoding of date values not supported", + Codec::Der, + )), + Value::Real(_) => Err(EncodeError::custom( + "encoding of real values not supported", + Codec::Der, + )), + Value::Uid(_) => Err(EncodeError::custom( + "encoding of uid values not supported", + Codec::Der, + )), + _ => Err(EncodeError::custom( + "encoding of unknown value type not supported", + Codec::Der, + )), + } + } +} + +impl TryFrom for Value { + type Error = DecodeError; + + fn try_from(value: WrappedValue) -> Result { + match value { + WrappedValue::Array(v) => Ok(Self::Array( + v.into_iter() + .map(Self::try_from) + .collect::, _>>()?, + )), + WrappedValue::Dictionary(v) => Ok(Self::Dictionary(v.0)), + WrappedValue::Boolean(v) => Ok(Self::Boolean(v)), + WrappedValue::Integer(v) => { + let v = v.to_i64().ok_or(DecodeError::custom( + "could not convert BigInt to i64", + Codec::Der, + ))?; + + Ok(Self::Integer(plist::Integer::from(v))) + } + WrappedValue::String(v) => Ok(Self::String(v)), + } + } +} + +/// Represents a top-level plist in the rasn domain. +struct WrappedPlist(WrappedValue); + +impl AsnType for WrappedPlist { + const TAG: Tag = Tag { + class: Class::Application, + value: 16, + }; +} + +impl Constructed for WrappedPlist { + const FIELDS: Fields = Fields::from_static(&[ + Field::new_required_type::("key"), + Field::new_required_type::("value"), + ]); +} + +impl TryFrom for WrappedPlist { + type Error = EncodeError; + + fn try_from(value: Value) -> Result { + Ok(Self(value.try_into()?)) + } +} + +impl TryFrom for Value { + type Error = DecodeError; + + fn try_from(value: WrappedPlist) -> Result { + if let WrappedValue::Dictionary(d) = value.0 { + Ok(Self::Dictionary(d.0)) + } else { + Err(DecodeError::custom( + "wrapped value not a dictionary", + Codec::Der, + )) + } + } +} + +impl Encode for WrappedPlist { + fn encode_with_tag_and_constraints( + &self, + encoder: &mut E, + tag: Tag, + _constraints: Constraints, + ) -> Result<(), E::Error> { + encoder.encode_sequence::(tag, |encoder| { + encoder.encode_integer(Tag::INTEGER, Constraints::NONE, &Integer::from(1))?; + self.0.encode(encoder) + })?; + + Ok(()) + } +} + +impl Decode for WrappedPlist { + fn decode_with_tag_and_constraints( + decoder: &mut D, + tag: Tag, + _constraints: Constraints, + ) -> Result { + decoder.decode_sequence::(tag, None:: Self>, |decoder| { + let _: Integer = decoder.decode_integer(Tag::INTEGER, Constraints::NONE)?; + let value = WrappedValue::decode(decoder)?; + + Ok(Self(value)) + }) + } +} + +/// Encode a top-level plist [Value] to DER. +pub fn der_encode_plist(value: &Value) -> Result, AppleCodesignError> { + rasn::der::encode_scope(|encoder| { + let wrapped = WrappedPlist::try_from(value.clone())?; + wrapped.encode(encoder) + }) + .map_err(|e| AppleCodesignError::PlistDer(format!("{e}"))) +} + +/// Decode DER to a plist [Value]. +pub fn der_decode_plist(data: impl AsRef<[u8]>) -> Result { + rasn::der::decode::(data.as_ref()) + .and_then(Value::try_from) + .map_err(|e| AppleCodesignError::PlistDer(format!("{e}"))) +} + +#[cfg(test)] +mod test { + use { + super::*, + crate::{ + embedded_signature::{Blob, CodeSigningSlot}, + macho::MachFile, + }, + anyhow::{anyhow, Result}, + plist::{Date, Uid}, + std::{ + process::Command, + time::{Duration, SystemTime}, + }, + }; + + const DER_EMPTY_DICT: &[u8] = &[112, 5, 2, 1, 1, 176, 0]; + const DER_BOOL_FALSE: &[u8] = &[ + 112, 15, 2, 1, 1, 176, 10, 48, 8, 12, 3, 107, 101, 121, 1, 1, 0, + ]; + const DER_BOOL_TRUE: &[u8] = &[ + 112, 15, 2, 1, 1, 176, 10, 48, 8, 12, 3, 107, 101, 121, 1, 1, 255, + ]; + const DER_INTEGER_0: &[u8] = &[ + 112, 15, 2, 1, 1, 176, 10, 48, 8, 12, 3, 107, 101, 121, 2, 1, 0, + ]; + const DER_INTEGER_NEG1: &[u8] = &[ + 112, 15, 2, 1, 1, 176, 10, 48, 8, 12, 3, 107, 101, 121, 2, 1, 255, + ]; + const DER_INTEGER_1: &[u8] = &[ + 112, 15, 2, 1, 1, 176, 10, 48, 8, 12, 3, 107, 101, 121, 2, 1, 1, + ]; + const DER_INTEGER_42: &[u8] = &[ + 112, 15, 2, 1, 1, 176, 10, 48, 8, 12, 3, 107, 101, 121, 2, 1, 42, + ]; + const DER_STRING_EMPTY: &[u8] = &[112, 14, 2, 1, 1, 176, 9, 48, 7, 12, 3, 107, 101, 121, 12, 0]; + const DER_STRING_VALUE: &[u8] = &[ + 112, 19, 2, 1, 1, 176, 14, 48, 12, 12, 3, 107, 101, 121, 12, 5, 118, 97, 108, 117, 101, + ]; + const DER_ARRAY_EMPTY: &[u8] = &[112, 14, 2, 1, 1, 176, 9, 48, 7, 12, 3, 107, 101, 121, 48, 0]; + const DER_ARRAY_FALSE: &[u8] = &[ + 112, 17, 2, 1, 1, 176, 12, 48, 10, 12, 3, 107, 101, 121, 48, 3, 1, 1, 0, + ]; + const DER_ARRAY_TRUE_FOO: &[u8] = &[ + 112, 22, 2, 1, 1, 176, 17, 48, 15, 12, 3, 107, 101, 121, 48, 8, 1, 1, 255, 12, 3, 102, 111, + 111, + ]; + const DER_DICT_EMPTY: &[u8] = &[ + 112, 14, 2, 1, 1, 176, 9, 48, 7, 12, 3, 107, 101, 121, 176, 0, + ]; + const DER_DICT_BOOL: &[u8] = &[ + 112, 26, 2, 1, 1, 176, 21, 48, 19, 12, 3, 107, 101, 121, 176, 12, 48, 10, 12, 5, 105, 110, + 110, 101, 114, 1, 1, 0, + ]; + const DER_MULTIPLE_KEYS: &[u8] = &[ + 112, 37, 2, 1, 1, 176, 32, 48, 8, 12, 3, 107, 101, 121, 1, 1, 0, 48, 9, 12, 4, 107, 101, + 121, 50, 1, 1, 255, 48, 9, 12, 4, 107, 101, 121, 51, 2, 1, 42, + ]; + + /// Signs a binary with custom entitlements XML and retrieves the entitlements DER. + /// + /// This uses Apple's `codesign` executable to sign the current binary then uses + /// our library for extracting the entitlements DER that it generated. + #[allow(unused)] + fn sign_and_get_entitlements_der(value: &Value) -> Result> { + let this_exe = std::env::current_exe()?; + + let temp_dir = tempfile::tempdir()?; + + let in_path = temp_dir.path().join("original"); + let entitlements_path = temp_dir.path().join("entitlements.xml"); + std::fs::copy(this_exe, &in_path)?; + { + let mut fh = std::fs::File::create(&entitlements_path)?; + value.to_writer_xml(&mut fh)?; + } + + let args = vec![ + "--verbose".to_string(), + "--force".to_string(), + // ad-hoc signing since we don't care about a CMS signature. + "-s".to_string(), + "-".to_string(), + "--generate-entitlement-der".to_string(), + "--entitlements".to_string(), + format!("{}", entitlements_path.display()), + format!("{}", in_path.display()), + ]; + + let status = Command::new("codesign").args(args).output()?; + if !status.status.success() { + return Err(anyhow!("codesign invocation failure")); + } + + // Now extract the data from the Apple produced code signature. + + let signed_exe = std::fs::read(&in_path)?; + let mach = MachFile::parse(&signed_exe)?; + let macho = mach.nth_macho(0)?; + + let signature = macho + .code_signature()? + .expect("unable to find code signature"); + + let slot = signature + .find_slot(CodeSigningSlot::EntitlementsDer) + .expect("unable to find der entitlements blob"); + + match slot.clone().into_parsed_blob()?.blob { + crate::embedded_signature::BlobData::EntitlementsDer(der) => { + Ok(der.serialize_payload()?) + } + _ => Err(anyhow!( + "failed to obtain entitlements DER (this should never happen)" + )), + } + } + + // This test is failing in CI. Older versions of macOS / codesign likely have + // a different DER encoding mechanism. + // #[test] + #[cfg(target_os = "macos")] + #[allow(unused)] + fn apple_der_entitlements_encoding() -> Result<()> { + // `codesign` prints "unknown exception" if we attempt to serialize a plist where + // the root element isn't a dict. + let mut d = plist::Dictionary::new(); + + assert_eq!( + sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?, + DER_EMPTY_DICT + ); + + d.insert("key".into(), Value::Boolean(false)); + assert_eq!( + sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?, + DER_BOOL_FALSE + ); + + d.insert("key".into(), Value::Boolean(true)); + assert_eq!( + sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?, + DER_BOOL_TRUE + ); + + d.insert("key".into(), Value::Integer(0u32.into())); + assert_eq!( + sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?, + DER_INTEGER_0 + ); + + d.insert("key".into(), Value::Integer((-1i32).into())); + assert_eq!( + sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?, + DER_INTEGER_NEG1 + ); + + d.insert("key".into(), Value::Integer(1u32.into())); + assert_eq!( + sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?, + DER_INTEGER_1 + ); + + d.insert("key".into(), Value::Integer(42u32.into())); + assert_eq!( + sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?, + DER_INTEGER_42 + ); + + // Floats fail to encode to DER. + d.insert("key".into(), Value::Real(0.0f32.into())); + assert!(sign_and_get_entitlements_der(&Value::Dictionary(d.clone())).is_err()); + + d.insert("key".into(), Value::Real((-1.0f32).into())); + assert!(sign_and_get_entitlements_der(&Value::Dictionary(d.clone())).is_err()); + + d.insert("key".into(), Value::Real(1.0f32.into())); + assert!(sign_and_get_entitlements_der(&Value::Dictionary(d.clone())).is_err()); + + d.insert("key".into(), Value::String("".into())); + assert_eq!( + sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?, + DER_STRING_EMPTY + ); + + d.insert("key".into(), Value::String("value".into())); + assert_eq!( + sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?, + DER_STRING_VALUE + ); + + // Uids fail to encode with `UidNotSupportedInXmlPlist` message. + d.insert("key".into(), Value::Uid(Uid::new(0))); + assert!(sign_and_get_entitlements_der(&Value::Dictionary(d.clone())).is_err()); + + d.insert("key".into(), Value::Uid(Uid::new(1))); + assert!(sign_and_get_entitlements_der(&Value::Dictionary(d.clone())).is_err()); + + d.insert("key".into(), Value::Uid(Uid::new(42))); + assert!(sign_and_get_entitlements_der(&Value::Dictionary(d.clone())).is_err()); + + // Date doesn't appear to work due to + // `Failed to parse entitlements: AMFIUnserializeXML: syntax error near line 6`. Perhaps + // a bug in the plist crate? + d.insert( + "key".into(), + Value::Date(Date::from(SystemTime::UNIX_EPOCH)), + ); + assert!(sign_and_get_entitlements_der(&Value::Dictionary(d.clone())).is_err()); + d.insert( + "key".into(), + Value::Date(Date::from( + SystemTime::UNIX_EPOCH + Duration::from_secs(86400 * 365 * 30), + )), + ); + assert!(sign_and_get_entitlements_der(&Value::Dictionary(d.clone())).is_err()); + + // Data fails to encode to DER with `unknown exception`. + d.insert("key".into(), Value::Data(vec![])); + assert!(sign_and_get_entitlements_der(&Value::Dictionary(d.clone())).is_err()); + d.insert("key".into(), Value::Data(b"foo".to_vec())); + assert!(sign_and_get_entitlements_der(&Value::Dictionary(d.clone())).is_err()); + + d.insert("key".into(), Value::Array(vec![])); + assert_eq!( + sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?, + DER_ARRAY_EMPTY + ); + + d.insert("key".into(), Value::Array(vec![Value::Boolean(false)])); + assert_eq!( + sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?, + DER_ARRAY_FALSE + ); + + d.insert( + "key".into(), + Value::Array(vec![Value::Boolean(true), Value::String("foo".into())]), + ); + assert_eq!( + sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?, + DER_ARRAY_TRUE_FOO + ); + + let mut inner = plist::Dictionary::new(); + d.insert("key".into(), Value::Dictionary(inner.clone())); + assert_eq!( + sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?, + DER_DICT_EMPTY + ); + + inner.insert("inner".into(), Value::Boolean(false)); + d.insert("key".into(), Value::Dictionary(inner.clone())); + assert_eq!( + sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?, + DER_DICT_BOOL + ); + + d.insert("key".into(), Value::Boolean(false)); + d.insert("key2".into(), Value::Boolean(true)); + d.insert("key3".into(), Value::Integer(42i32.into())); + assert_eq!( + sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?, + DER_MULTIPLE_KEYS + ); + + Ok(()) + } + + #[test] + fn der_encoding() -> Result<()> { + let mut d = plist::Dictionary::new(); + + assert_eq!( + der_encode_plist(&Value::Dictionary(d.clone()))?, + DER_EMPTY_DICT + ); + assert_eq!( + der_decode_plist(DER_EMPTY_DICT)?, + Value::Dictionary(d.clone()) + ); + + d.insert("key".into(), Value::Boolean(false)); + assert_eq!( + der_encode_plist(&Value::Dictionary(d.clone()))?, + DER_BOOL_FALSE + ); + assert_eq!( + der_decode_plist(DER_BOOL_FALSE)?, + Value::Dictionary(d.clone()) + ); + + d.insert("key".into(), Value::Boolean(true)); + assert_eq!( + der_encode_plist(&Value::Dictionary(d.clone()))?, + DER_BOOL_TRUE + ); + assert_eq!( + der_decode_plist(DER_BOOL_TRUE)?, + Value::Dictionary(d.clone()) + ); + + d.insert("key".into(), Value::Integer(0u32.into())); + assert_eq!( + der_encode_plist(&Value::Dictionary(d.clone()))?, + DER_INTEGER_0 + ); + assert_eq!( + der_decode_plist(DER_INTEGER_0)?, + Value::Dictionary(d.clone()) + ); + + d.insert("key".into(), Value::Integer((-1i32).into())); + assert_eq!( + der_encode_plist(&Value::Dictionary(d.clone()))?, + DER_INTEGER_NEG1 + ); + assert_eq!( + der_decode_plist(DER_INTEGER_NEG1)?, + Value::Dictionary(d.clone()) + ); + + d.insert("key".into(), Value::Integer(1u32.into())); + assert_eq!( + der_encode_plist(&Value::Dictionary(d.clone()))?, + DER_INTEGER_1 + ); + assert_eq!( + der_decode_plist(DER_INTEGER_1)?, + Value::Dictionary(d.clone()) + ); + + d.insert("key".into(), Value::Integer(42u32.into())); + assert_eq!( + der_encode_plist(&Value::Dictionary(d.clone()))?, + DER_INTEGER_42 + ); + assert_eq!( + der_decode_plist(DER_INTEGER_42)?, + Value::Dictionary(d.clone()) + ); + + d.insert("key".into(), Value::Real(0.0f32.into())); + assert!(matches!( + der_encode_plist(&Value::Dictionary(d.clone())), + Err(AppleCodesignError::PlistDer(_)) + )); + + d.insert("key".into(), Value::Real((-1.0f32).into())); + assert!(matches!( + der_encode_plist(&Value::Dictionary(d.clone())), + Err(AppleCodesignError::PlistDer(_)) + )); + + d.insert("key".into(), Value::Real(1.0f32.into())); + assert!(matches!( + der_encode_plist(&Value::Dictionary(d.clone())), + Err(AppleCodesignError::PlistDer(_)) + )); + + d.insert("key".into(), Value::String("".into())); + assert_eq!( + der_encode_plist(&Value::Dictionary(d.clone()))?, + DER_STRING_EMPTY + ); + assert_eq!( + der_decode_plist(DER_STRING_EMPTY)?, + Value::Dictionary(d.clone()) + ); + + d.insert("key".into(), Value::String("value".into())); + assert_eq!( + der_encode_plist(&Value::Dictionary(d.clone()))?, + DER_STRING_VALUE + ); + assert_eq!( + der_decode_plist(DER_STRING_VALUE)?, + Value::Dictionary(d.clone()) + ); + + d.insert("key".into(), Value::Uid(Uid::new(0))); + assert!(matches!( + der_encode_plist(&Value::Dictionary(d.clone())), + Err(AppleCodesignError::PlistDer(_)) + )); + + d.insert("key".into(), Value::Uid(Uid::new(1))); + assert!(matches!( + der_encode_plist(&Value::Dictionary(d.clone())), + Err(AppleCodesignError::PlistDer(_)) + )); + + d.insert("key".into(), Value::Uid(Uid::new(42))); + assert!(matches!( + der_encode_plist(&Value::Dictionary(d.clone())), + Err(AppleCodesignError::PlistDer(_)) + )); + + d.insert( + "key".into(), + Value::Date(Date::from(SystemTime::UNIX_EPOCH)), + ); + assert!(matches!( + der_encode_plist(&Value::Dictionary(d.clone())), + Err(AppleCodesignError::PlistDer(_)) + )); + d.insert( + "key".into(), + Value::Date(Date::from( + SystemTime::UNIX_EPOCH + Duration::from_secs(86400 * 365 * 30), + )), + ); + assert!(matches!( + der_encode_plist(&Value::Dictionary(d.clone())), + Err(AppleCodesignError::PlistDer(_)) + )); + + // Data fails to encode to DER with `unknown exception`. + d.insert("key".into(), Value::Data(vec![])); + assert!(matches!( + der_encode_plist(&Value::Dictionary(d.clone())), + Err(AppleCodesignError::PlistDer(_)) + )); + d.insert("key".into(), Value::Data(b"foo".to_vec())); + assert!(matches!( + der_encode_plist(&Value::Dictionary(d.clone())), + Err(AppleCodesignError::PlistDer(_)) + )); + + d.insert("key".into(), Value::Array(vec![])); + assert_eq!( + der_encode_plist(&Value::Dictionary(d.clone()))?, + DER_ARRAY_EMPTY + ); + assert_eq!( + der_decode_plist(DER_ARRAY_EMPTY)?, + Value::Dictionary(d.clone()) + ); + + d.insert("key".into(), Value::Array(vec![Value::Boolean(false)])); + assert_eq!( + der_encode_plist(&Value::Dictionary(d.clone()))?, + DER_ARRAY_FALSE + ); + assert_eq!( + der_decode_plist(DER_ARRAY_FALSE)?, + Value::Dictionary(d.clone()) + ); + + d.insert( + "key".into(), + Value::Array(vec![Value::Boolean(true), Value::String("foo".into())]), + ); + assert_eq!( + der_encode_plist(&Value::Dictionary(d.clone()))?, + DER_ARRAY_TRUE_FOO + ); + assert_eq!( + der_decode_plist(DER_ARRAY_TRUE_FOO)?, + Value::Dictionary(d.clone()) + ); + + let mut inner = plist::Dictionary::new(); + d.insert("key".into(), Value::Dictionary(inner.clone())); + assert_eq!( + der_encode_plist(&Value::Dictionary(d.clone()))?, + DER_DICT_EMPTY + ); + assert_eq!( + der_decode_plist(DER_DICT_EMPTY)?, + Value::Dictionary(d.clone()) + ); + + inner.insert("inner".into(), Value::Boolean(false)); + d.insert("key".into(), Value::Dictionary(inner.clone())); + assert_eq!( + der_encode_plist(&Value::Dictionary(d.clone()))?, + DER_DICT_BOOL + ); + assert_eq!( + der_decode_plist(DER_DICT_BOOL)?, + Value::Dictionary(d.clone()) + ); + + d.insert("key".into(), Value::Boolean(false)); + d.insert("key2".into(), Value::Boolean(true)); + d.insert("key3".into(), Value::Integer(42i32.into())); + assert_eq!( + der_encode_plist(&Value::Dictionary(d.clone()))?, + DER_MULTIPLE_KEYS + ); + assert_eq!( + der_decode_plist(DER_MULTIPLE_KEYS)?, + Value::Dictionary(d.clone()) + ); + + Ok(()) + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/policy.rs b/3rdparty/apple-codesign-0.29.0/src/policy.rs new file mode 100644 index 00000000..211a3c67 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/policy.rs @@ -0,0 +1,570 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Apple trust policies. +//! +//! Apple operating systems have a number of pre-canned trust policies +//! that must be fulfilled in order to trust signed code. These are +//! often based off the presence of specific X.509 certificates in the +//! issuing chain and/or the presence of attributes in X.509 certificates. +//! +//! Trust policies are often engraved in code signatures as part of the +//! signed code requirements expression. +//! +//! This module defines a bunch of metadata for describing Apple trust +//! entities and also provides pre-canned policies that can be easily +//! constructed to match those employed by Apple's official signing tools. +//! +//! Apple's certificates can be found at +//! . + +use { + crate::{ + certificate::{ + AppleCertificate, CertificateAuthorityExtension, CodeSigningCertificateExtension, + }, + code_requirement::{CodeRequirementExpression, CodeRequirementMatchExpression}, + error::AppleCodesignError, + }, + once_cell::sync::Lazy, + std::ops::Deref, + x509_certificate::CapturedX509Certificate, +}; + +/// Code signing requirement for Mac Developer ID. +/// +/// `anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists and +/// (certificate leaf[field.1.2.840.113635.100.6.1.14] or certificate leaf[field.1.2.840.113635.100.6.1.13])` +static POLICY_MAC_DEVELOPER_ID: Lazy> = Lazy::new(|| { + CodeRequirementExpression::And( + Box::new(CodeRequirementExpression::And( + Box::new(CodeRequirementExpression::AnchorAppleGeneric), + Box::new(CodeRequirementExpression::CertificateGeneric( + 1, + CertificateAuthorityExtension::DeveloperId.as_oid(), + CodeRequirementMatchExpression::Exists, + )), + )), + Box::new(CodeRequirementExpression::Or( + Box::new(CodeRequirementExpression::CertificateGeneric( + 0, + CodeSigningCertificateExtension::DeveloperIdInstaller.as_oid(), + CodeRequirementMatchExpression::Exists, + )), + Box::new(CodeRequirementExpression::CertificateGeneric( + 0, + CodeSigningCertificateExtension::DeveloperIdApplication.as_oid(), + CodeRequirementMatchExpression::Exists, + )), + )), + ) +}); + +/// Notarized executable. +/// +/// `anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists and +/// certificate leaf[field.1.2.840.113635.100.6.1.13] exists and notarized'` +/// +static POLICY_NOTARIZED_EXECUTABLE: Lazy> = Lazy::new(|| { + CodeRequirementExpression::And( + Box::new(CodeRequirementExpression::And( + Box::new(CodeRequirementExpression::And( + Box::new(CodeRequirementExpression::AnchorAppleGeneric), + Box::new(CodeRequirementExpression::CertificateGeneric( + 1, + CertificateAuthorityExtension::DeveloperId.as_oid(), + CodeRequirementMatchExpression::Exists, + )), + )), + Box::new(CodeRequirementExpression::CertificateGeneric( + 0, + CodeSigningCertificateExtension::DeveloperIdApplication.as_oid(), + CodeRequirementMatchExpression::Exists, + )), + )), + Box::new(CodeRequirementExpression::Notarized), + ) +}); + +/// Notarized installer. +/// +/// `'anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists +/// and (certificate leaf[field.1.2.840.113635.100.6.1.14] or certificate +/// leaf[field.1.2.840.113635.100.6.1.13]) and notarized'` +static POLICY_NOTARIZED_INSTALLER: Lazy> = Lazy::new(|| { + CodeRequirementExpression::And( + Box::new(CodeRequirementExpression::And( + Box::new(CodeRequirementExpression::And( + Box::new(CodeRequirementExpression::AnchorAppleGeneric), + Box::new(CodeRequirementExpression::CertificateGeneric( + 1, + CertificateAuthorityExtension::DeveloperId.as_oid(), + CodeRequirementMatchExpression::Exists, + )), + )), + Box::new(CodeRequirementExpression::Or( + Box::new(CodeRequirementExpression::CertificateGeneric( + 0, + CodeSigningCertificateExtension::DeveloperIdInstaller.as_oid(), + CodeRequirementMatchExpression::Exists, + )), + Box::new(CodeRequirementExpression::CertificateGeneric( + 0, + CodeSigningCertificateExtension::DeveloperIdApplication.as_oid(), + CodeRequirementMatchExpression::Exists, + )), + )), + )), + Box::new(CodeRequirementExpression::Notarized), + ) +}); + +/// Defines well-known execution policies for signed code. +/// +/// Instances can be obtained from a human-readable string for convenience. Those +/// strings are: +/// +/// * `developer-id-signed` +/// * `developer-id-notarized-executable` +/// * `developer-id-notarized-installer` +#[allow(clippy::enum_variant_names)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, clap::ValueEnum)] +pub enum ExecutionPolicy { + /// Code is signed by a certificate authorized for signing Mac applications or + /// installers and that certificate was issued by + /// [crate::apple_certificates::KnownCertificate::DeveloperIdG1] or + /// [crate::apple_certificates::KnownCertificate::DeveloperIdG2]. + /// + /// This is the policy that applies when you get a `Developer ID Application` or + /// `Developer ID Installer` certificate from Apple. + DeveloperIdSigned, + + /// Like [Self::DeveloperIdSigned] but only applies to executables (not installers) + /// and the executable must be notarized. + /// + /// If you notarize an individual executable, you effectively convert the + /// [Self::DeveloperIdSigned] policy into this variant. + DeveloperIdNotarizedExecutable, + + /// Like [Self::DeveloperIdSigned] but only applies to installers (not executables) + /// and the installer must be notarized. + /// + /// If you notarize an individual installer, you effectively convert the + /// [Self::DeveloperIdSigned] policy into this variant. + DeveloperIdNotarizedInstaller, +} + +impl Deref for ExecutionPolicy { + type Target = CodeRequirementExpression<'static>; + + fn deref(&self) -> &Self::Target { + match self { + Self::DeveloperIdSigned => POLICY_MAC_DEVELOPER_ID.deref(), + Self::DeveloperIdNotarizedExecutable => POLICY_NOTARIZED_EXECUTABLE.deref(), + Self::DeveloperIdNotarizedInstaller => POLICY_NOTARIZED_INSTALLER.deref(), + } + } +} + +impl TryFrom<&str> for ExecutionPolicy { + type Error = AppleCodesignError; + + fn try_from(s: &str) -> Result { + match s { + "developer-id-signed" => Ok(Self::DeveloperIdSigned), + "developer-id-notarized-executable" => Ok(Self::DeveloperIdNotarizedExecutable), + "developer-id-notarized-installer" => Ok(Self::DeveloperIdNotarizedInstaller), + _ => Err(AppleCodesignError::UnknownPolicy(s.to_string())), + } + } +} + +/// Derive a designated requirements expression given a code signing certificate. +/// +/// The default expression is derived from properties of the signing +/// certificate. If it is an Apple signed certificate, extensions on the +/// issuer CA denote which expression to use. +/// +/// For non-Apple signed certificates, the expression self-references the +/// issuing certificate in the same Organization as the signing certificate. +pub fn derive_designated_requirements( + signing_cert: &CapturedX509Certificate, + chain: &[CapturedX509Certificate], + identifier: Option, +) -> Result, AppleCodesignError> { + let expr = if signing_cert.chains_to_apple_root_ca() { + let apple_chain = signing_cert.apple_issuing_chain(); + + assert!( + !apple_chain.is_empty(), + "we should be able to resolve the Apple CA chain if chains_to_apple_root_ca() is true" + ); + + let first = apple_chain[0]; + + if first + .apple_ca_extensions() + .into_iter() + .any(|ext| ext == CertificateAuthorityExtension::AppleWorldwideDeveloperRelations) + { + let cn = signing_cert.subject_common_name().ok_or_else(|| { + AppleCodesignError::PolicyFormulationError( + "certificate common name not available".to_string(), + ) + })?; + worldwide_developer_relations_signed_expression(cn) + } else if first + .apple_ca_extensions() + .into_iter() + .any(|ext| ext == CertificateAuthorityExtension::DeveloperId) + { + let team_id = signing_cert.apple_team_id().ok_or_else(|| { + AppleCodesignError::PolicyFormulationError( + "could not find team identifier in signing certificate".to_string(), + ) + })?; + + developer_id_signed_expression(team_id) + } else { + CodeRequirementExpression::AnchorApple + } + } else { + // Ensure the chain is sorted. + let chain = signing_cert + .resolve_signing_chain(chain.iter()) + .into_iter() + .cloned() + .collect::>(); + + non_apple_signed_expression(signing_cert, &chain)? + }; + + // Chain the expression with the identifier, if given. + Ok(if let Some(identifier) = identifier { + CodeRequirementExpression::And( + Box::new(CodeRequirementExpression::Identifier(identifier.into())), + Box::new(expr), + ) + } else { + expr + }) +} + +/// Derive a code requirements expression for a Developer ID issued certificate. +/// +/// The expression is pinned to the team ID / organization unit of the signing +/// certificate, which must be passed in. +pub fn developer_id_signed_expression( + team_id: impl ToString, +) -> CodeRequirementExpression<'static> { + CodeRequirementExpression::And( + // Chains to Apple root CA. + Box::new(CodeRequirementExpression::AnchorAppleGeneric), + Box::new(CodeRequirementExpression::And( + // Certificate issued by CA with Developer ID extension. + Box::new(CodeRequirementExpression::CertificateGeneric( + 1, + CertificateAuthorityExtension::DeveloperId.as_oid(), + CodeRequirementMatchExpression::Exists, + )), + Box::new(CodeRequirementExpression::And( + // A certificate entrusted with Developer ID Application signing rights. + Box::new(CodeRequirementExpression::CertificateGeneric( + 0, + CodeSigningCertificateExtension::DeveloperIdApplication.as_oid(), + CodeRequirementMatchExpression::Exists, + )), + // Signed by this team ID. + Box::new(CodeRequirementExpression::CertificateField( + 0, + "subject.OU".to_string().into(), + CodeRequirementMatchExpression::Equal(team_id.to_string().into()), + )), + )), + )), + ) +} + +/// Derive the requirements expression for a cert signed by the Worldwide Developer Relations CA. +/// +/// The expression is pinned to the Common Name (CN) field of the signing +/// certificate, which must be passed in. +pub fn worldwide_developer_relations_signed_expression( + leaf_common_name: impl ToString, +) -> CodeRequirementExpression<'static> { + // anchor apple generic and + CodeRequirementExpression::And( + Box::new(CodeRequirementExpression::AnchorAppleGeneric), + // leaf[subject.CN] = and + Box::new(CodeRequirementExpression::And( + Box::new(CodeRequirementExpression::CertificateField( + 0, + "subject.CN".to_string().into(), + CodeRequirementMatchExpression::Equal(leaf_common_name.to_string().into()), + )), + // certificate 1[field.1.2.840.113635.100.6.2.1] exists + Box::new(CodeRequirementExpression::CertificateGeneric( + 1, + CertificateAuthorityExtension::AppleWorldwideDeveloperRelations.as_oid(), + CodeRequirementMatchExpression::Exists, + )), + )), + ) +} + +/// Derive the requirements expression for non Apple signed certificates. +/// +/// The signing certificate should be the first certificate in the passed chain. +/// The chain should be sorted so the root CA is last. +pub fn non_apple_signed_expression( + signing_cert: &CapturedX509Certificate, + chain: &[CapturedX509Certificate], +) -> Result, AppleCodesignError> { + let leaf_raw: &x509_certificate::rfc5280::Certificate = signing_cert.as_ref(); + + let leaf_organization = leaf_raw + .tbs_certificate + .subject + .iter_organization() + .next() + .and_then(|o| o.to_string().ok()); + + // We pin the last certificate in the signing chain having the same + // organization as the signing certificate. + + let mut pin_index = 0i32; + + if let Some(leaf_organization) = leaf_organization { + for cert in chain.iter() { + let ca_raw: &x509_certificate::rfc5280::Certificate = cert.as_ref(); + + if let Some(org) = ca_raw + .tbs_certificate + .subject + .iter_organization() + .next() + .and_then(|o| o.to_string().ok()) + { + if org != leaf_organization { + break; + } + + pin_index += 1; + } + } + } + + // If the entire chain is signed by the same Organization, use the + // special cert index value to pin the root cert. + if pin_index as usize == chain.len() { + pin_index = -1; + } + + let digest = signing_cert + .fingerprint(x509_certificate::DigestAlgorithm::Sha1)? + .as_ref() + .to_vec(); + + Ok(CodeRequirementExpression::AnchorCertificateHash( + pin_index, + digest.into(), + )) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn get_policies() { + ExecutionPolicy::DeveloperIdSigned.to_bytes().unwrap(); + ExecutionPolicy::DeveloperIdNotarizedExecutable + .to_bytes() + .unwrap(); + ExecutionPolicy::DeveloperIdNotarizedInstaller + .to_bytes() + .unwrap(); + } + + const APPLE_SIGNED_CN: &str = "Apple Development: Gregory Szorc (DD5YMVP48D)"; + const DEVELOPER_ID_TEXT: &str = "(anchor apple generic) and ((certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */) and ((certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */) and (certificate leaf[subject.OU] = \"MK22MZP987\")))"; + const WWDR_TEXT: &str = "(anchor apple generic) and ((certificate leaf[subject.CN] = \"Apple Development: Gregory Szorc (DD5YMVP48D)\") and (certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */))"; + + fn load_unified_pem(pem_data: &[u8]) -> CapturedX509Certificate { + pem::parse_many(pem_data) + .unwrap() + .into_iter() + .filter_map(|doc| { + if doc.tag() == "CERTIFICATE" { + Some(doc.contents().to_vec()) + } else { + None + } + }) + .map(|der| CapturedX509Certificate::from_der(der).unwrap()) + .next() + .unwrap() + } + + #[test] + fn developer_id_requirements_derive() { + let der = include_bytes!("testdata/apple-signed-developer-id-application.cer"); + let cert = CapturedX509Certificate::from_der(der.to_vec()).unwrap(); + + assert_eq!( + developer_id_signed_expression(cert.apple_team_id().unwrap()).to_string(), + DEVELOPER_ID_TEXT + ); + assert_eq!( + derive_designated_requirements(&cert, &[], None) + .unwrap() + .to_string(), + DEVELOPER_ID_TEXT + ); + } + + #[test] + fn worldwide_developer_relations() { + assert_eq!( + worldwide_developer_relations_signed_expression(APPLE_SIGNED_CN).to_string(), + WWDR_TEXT + ); + } + + #[test] + fn non_apple_signed() { + let self_signed = load_unified_pem(include_bytes!( + "testdata/self-signed-rsa-apple-development.pem" + )); + + assert_eq!( + non_apple_signed_expression(&self_signed, &[]) + .unwrap() + .to_string(), + "certificate root = H\"e1c7216e46533c923b7cfc94e86c7043790b96e9\"" + ); + + // Now try with an Apple chain. The function doesn't care that it is + // operating on a non-Apple chain. + let apple_development = CapturedX509Certificate::from_der( + include_bytes!("testdata/apple-signed-apple-development.cer").to_vec(), + ) + .unwrap(); + let chain = apple_development.apple_root_certificate_chain().unwrap(); + + assert_eq!( + non_apple_signed_expression(&apple_development, &chain[1..]) + .unwrap() + .to_string(), + "certificate leaf = H\"5eeadb4befce055e06b4239ad4c5f0d1bfd6af8f\"" + ); + } + + #[test] + fn apple_signed_auto_derive() { + let apple_development = CapturedX509Certificate::from_der( + include_bytes!("testdata/apple-signed-apple-development.cer").to_vec(), + ) + .unwrap(); + let apple_distribution = CapturedX509Certificate::from_der( + include_bytes!("testdata/apple-signed-apple-distribution.cer").to_vec(), + ) + .unwrap(); + let developer_id_application = CapturedX509Certificate::from_der( + include_bytes!("testdata/apple-signed-developer-id-application.cer").to_vec(), + ) + .unwrap(); + let developer_id_installer = CapturedX509Certificate::from_der( + include_bytes!("testdata/apple-signed-developer-id-installer.cer").to_vec(), + ) + .unwrap(); + let mac_installer_distribution = CapturedX509Certificate::from_der( + include_bytes!("testdata/apple-signed-3rd-party-mac.cer").to_vec(), + ) + .unwrap(); + + assert_eq!( + derive_designated_requirements(&apple_development, &[], None) + .unwrap() + .to_string(), + WWDR_TEXT + ); + assert_eq!( + derive_designated_requirements(&apple_distribution, &[], None) + .unwrap() + .to_string(), + worldwide_developer_relations_signed_expression( + "Apple Distribution: Gregory Szorc (MK22MZP987)" + ) + .to_string() + ); + assert_eq!( + derive_designated_requirements(&developer_id_application, &[], None) + .unwrap() + .to_string(), + DEVELOPER_ID_TEXT + ); + assert_eq!( + derive_designated_requirements(&developer_id_installer, &[], None) + .unwrap() + .to_string(), + developer_id_signed_expression("MK22MZP987").to_string() + ); + assert_eq!( + derive_designated_requirements(&mac_installer_distribution, &[], None) + .unwrap() + .to_string(), + worldwide_developer_relations_signed_expression( + "3rd Party Mac Developer Installer: Gregory Szorc (MK22MZP987)" + ) + .to_string() + ); + } + + #[test] + fn self_signed_auto_derive() { + let apple_development = load_unified_pem(include_bytes!( + "testdata/self-signed-rsa-apple-development.pem" + )); + let apple_distribution = load_unified_pem(include_bytes!( + "testdata/self-signed-rsa-apple-distribution.pem" + )); + let developer_id_application = load_unified_pem(include_bytes!( + "testdata/self-signed-rsa-developer-id-application.pem" + )); + let developer_id_installer = load_unified_pem(include_bytes!( + "testdata/self-signed-rsa-developer-id-installer.pem" + )); + let mac_installer_distribution = load_unified_pem(include_bytes!( + "testdata/self-signed-rsa-mac-installer-distribution.pem" + )); + + let derive = |cert| -> String { + derive_designated_requirements(cert, &[], None) + .unwrap() + .to_string() + }; + + assert_eq!( + derive(&apple_development), + "certificate root = H\"e1c7216e46533c923b7cfc94e86c7043790b96e9\"" + ); + assert_eq!( + derive(&apple_distribution), + "certificate root = H\"0383efdf909250708bf2de4d43753836ccb3d608\"" + ); + assert_eq!( + derive(&developer_id_application), + "certificate root = H\"3acf1d302fe3a4bba06a3c16aadc908045bc9162\"" + ); + assert_eq!( + derive(&developer_id_installer), + "certificate root = H\"5c1314a89e5a486ac7b1da86b38e08777adca4af\"" + ); + assert_eq!( + derive(&mac_installer_distribution), + "certificate root = H\"58e39fe0fca55e7af4ca00027bc7c59e566e960a\"" + ); + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/reader.rs b/3rdparty/apple-codesign-0.29.0/src/reader.rs new file mode 100644 index 00000000..ee851eab --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/reader.rs @@ -0,0 +1,1106 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Functionality for reading signature data from files. + +use { + crate::{ + certificate::AppleCertificate, + code_directory::CodeDirectoryBlob, + cryptography::DigestType, + dmg::{path_is_dmg, DmgReader}, + embedded_signature::{BlobEntry, EmbeddedSignature}, + embedded_signature_builder::{CD_DIGESTS_OID, CD_DIGESTS_PLIST_OID}, + error::{AppleCodesignError, Result}, + macho::{MachFile, MachOBinary}, + }, + apple_bundles::{DirectoryBundle, DirectoryBundleFile}, + apple_xar::{ + reader::XarReader, + table_of_contents::{ + ChecksumType as XarChecksumType, File as XarTocFile, Signature as XarTocSignature, + }, + }, + cryptographic_message_syntax::{SignedData, SignerInfo}, + goblin::mach::{fat::FAT_MAGIC, parse_magic_and_ctx}, + serde::Serialize, + std::{ + fmt::Debug, + fs::File, + io::{BufWriter, Cursor, Read, Seek}, + ops::Deref, + path::{Path, PathBuf}, + }, + x509_certificate::{CapturedX509Certificate, DigestAlgorithm}, +}; + +enum MachOType { + Mach, + MachO, +} + +impl MachOType { + pub fn from_path(path: impl AsRef) -> Result, AppleCodesignError> { + let mut fh = File::open(path.as_ref())?; + + let mut header = vec![0u8; 4]; + let count = fh.read(&mut header)?; + + if count < 4 { + return Ok(None); + } + + let magic = goblin::mach::peek(&header, 0)?; + + if magic == FAT_MAGIC { + Ok(Some(Self::Mach)) + } else if let Ok((_, Some(_))) = parse_magic_and_ctx(&header, 0) { + Ok(Some(Self::MachO)) + } else { + Ok(None) + } + } +} + +/// Test whether a given path is likely a XAR file. +pub fn path_is_xar(path: impl AsRef) -> Result { + let mut fh = File::open(path.as_ref())?; + + let mut header = [0u8; 4]; + + let count = fh.read(&mut header)?; + if count < 4 { + Ok(false) + } else { + Ok(header.as_ref() == b"xar!") + } +} + +/// Test whether a given path is likely a ZIP file. +pub fn path_is_zip(path: impl AsRef) -> Result { + let mut fh = File::open(path.as_ref())?; + + let mut header = [0u8; 4]; + + let count = fh.read(&mut header)?; + if count < 4 { + Ok(false) + } else { + Ok(header.as_ref() == [0x50, 0x4b, 0x03, 0x04]) + } +} + +/// Whether the specified filesystem path is a Mach-O binary. +pub fn path_is_macho(path: impl AsRef) -> Result { + Ok(MachOType::from_path(path)?.is_some()) +} + +/// Describes the type of entity at a path. +/// +/// This represents a best guess. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum PathType { + MachO, + Dmg, + Bundle, + Xar, + Zip, + Other, +} + +impl PathType { + /// Attempt to classify the type of signable entity based on a filesystem path. + pub fn from_path(path: impl AsRef) -> Result { + let path = path.as_ref(); + + if path.is_file() { + if path_is_dmg(path)? { + Ok(Self::Dmg) + } else if path_is_xar(path)? { + Ok(Self::Xar) + } else if path_is_zip(path)? { + Ok(Self::Zip) + } else if path_is_macho(path)? { + Ok(Self::MachO) + } else { + Ok(Self::Other) + } + } else if path.is_dir() { + Ok(Self::Bundle) + } else { + Ok(Self::Other) + } + } +} + +fn format_integer(v: T) -> String { + format!("{} / 0x{:x}", v, v) +} + +fn pretty_print_xml(xml: &[u8]) -> Result, AppleCodesignError> { + let mut reader = xml::reader::EventReader::new(Cursor::new(xml)); + let mut emitter = xml::EmitterConfig::new() + .perform_indent(true) + .create_writer(BufWriter::new(Vec::with_capacity(xml.len() * 2))); + + while let Ok(event) = reader.next() { + match event { + xml::reader::XmlEvent::EndDocument => { + break; + } + xml::reader::XmlEvent::Whitespace(_) => {} + event => { + if let Some(event) = event.as_writer_event() { + emitter.write(event).map_err(AppleCodesignError::XmlWrite)?; + } + } + } + } + + let xml = emitter.into_inner().into_inner().map_err(|e| { + AppleCodesignError::Io(std::io::Error::new(std::io::ErrorKind::BrokenPipe, e)) + })?; + + Ok(xml) +} + +/// Pretty print XML and turn into a Vec of lines. +fn pretty_print_xml_lines(xml: &[u8]) -> Result> { + Ok(String::from_utf8_lossy(pretty_print_xml(xml)?.as_ref()) + .lines() + .map(|x| x.to_string()) + .collect::>()) +} + +#[derive(Clone, Debug, Serialize)] +pub struct BlobDescription { + pub slot: String, + pub magic: String, + pub length: u32, + pub sha1: String, + pub sha256: String, +} + +impl<'a> From<&BlobEntry<'a>> for BlobDescription { + fn from(entry: &BlobEntry<'a>) -> Self { + Self { + slot: format!("{:?}", entry.slot), + magic: format!("{:x}", u32::from(entry.magic)), + length: entry.length as _, + sha1: hex::encode( + entry + .digest_with(DigestType::Sha1) + .expect("sha-1 digest should always work"), + ), + sha256: hex::encode( + entry + .digest_with(DigestType::Sha256) + .expect("sha-256 digest should always work"), + ), + } + } +} + +#[derive(Clone, Debug, Serialize)] +pub struct CertificateInfo { + pub subject: String, + pub issuer: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub key_algorithm: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub signature_algorithm: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub signed_with_algorithm: Option, + pub is_apple_root_ca: bool, + pub is_apple_intermediate_ca: bool, + pub chains_to_apple_root_ca: bool, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub apple_ca_extensions: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub apple_extended_key_usages: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub apple_code_signing_extensions: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub apple_certificate_profile: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub apple_team_id: Option, +} + +impl TryFrom<&CapturedX509Certificate> for CertificateInfo { + type Error = AppleCodesignError; + + fn try_from(cert: &CapturedX509Certificate) -> Result { + Ok(Self { + subject: cert + .subject_name() + .user_friendly_str() + .map_err(AppleCodesignError::CertificateDecode)?, + issuer: cert + .issuer_name() + .user_friendly_str() + .map_err(AppleCodesignError::CertificateDecode)?, + key_algorithm: cert.key_algorithm().map(|x| x.to_string()), + signature_algorithm: cert.signature_algorithm().map(|x| x.to_string()), + signed_with_algorithm: cert.signature_signature_algorithm().map(|x| x.to_string()), + is_apple_root_ca: cert.is_apple_root_ca(), + is_apple_intermediate_ca: cert.is_apple_intermediate_ca(), + chains_to_apple_root_ca: cert.chains_to_apple_root_ca(), + apple_ca_extensions: cert + .apple_ca_extensions() + .into_iter() + .map(|x| x.to_string()) + .collect::>(), + apple_extended_key_usages: cert + .apple_extended_key_usage_purposes() + .into_iter() + .map(|x| x.to_string()) + .collect::>(), + apple_code_signing_extensions: cert + .apple_code_signing_extensions() + .into_iter() + .map(|x| x.to_string()) + .collect::>(), + apple_certificate_profile: cert.apple_guess_profile().map(|x| x.to_string()), + apple_team_id: cert.apple_team_id(), + }) + } +} + +#[derive(Clone, Debug, Serialize)] +pub struct CmsSigner { + pub issuer: String, + pub digest_algorithm: String, + pub signature_algorithm: String, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub attributes: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub content_type: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub message_digest: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub signing_time: Option>, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub cdhash_plist: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub cdhash_digests: Vec<(String, String)>, + pub signature_verifies: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub time_stamp_token: Option, +} + +impl CmsSigner { + pub fn from_signer_info_and_signed_data( + signer_info: &SignerInfo, + signed_data: &SignedData, + ) -> Result { + let mut attributes = vec![]; + let mut content_type = None; + let mut message_digest = None; + let mut signing_time = None; + let mut time_stamp_token = None; + let mut cdhash_plist = vec![]; + let mut cdhash_digests = vec![]; + + if let Some(sa) = signer_info.signed_attributes() { + content_type = Some(sa.content_type().to_string()); + message_digest = Some(hex::encode(sa.message_digest())); + if let Some(t) = sa.signing_time() { + signing_time = Some(*t); + } + + for attr in sa.attributes().iter() { + attributes.push(format!("{}", attr.typ)); + + if attr.typ == CD_DIGESTS_PLIST_OID { + if let Some(data) = attr.values.get(0) { + let data = data.deref().clone(); + + let plist = data + .decode(|cons| { + let v = bcder::OctetString::take_from(cons)?; + + Ok(v.into_bytes()) + }) + .map_err(|e| AppleCodesignError::Cms(e.into()))?; + + cdhash_plist = pretty_print_xml_lines(&plist)?; + } + } else if attr.typ == CD_DIGESTS_OID { + for value in &attr.values { + // Each value is a SEQUENECE of (OID, OctetString). + let data = value.deref().clone(); + + data.decode(|cons| { + loop { + let res = cons.take_opt_sequence(|cons| { + let oid = bcder::Oid::take_from(cons)?; + let value = bcder::OctetString::take_from(cons)?; + + cdhash_digests + .push((format!("{oid}"), hex::encode(value.into_bytes()))); + + Ok(()) + })?; + + if res.is_none() { + break; + } + } + + Ok(()) + }) + .map_err(|e| AppleCodesignError::Cms(e.into()))?; + } + } + } + } + + // The order should matter per RFC 5652 but Apple's CMS implementation doesn't + // conform to spec. + attributes.sort(); + + if let Some(tsk) = signer_info.time_stamp_token_signed_data()? { + time_stamp_token = Some(tsk.try_into()?); + } + + Ok(Self { + issuer: signer_info + .certificate_issuer_and_serial() + .expect("issuer should always be set") + .0 + .user_friendly_str() + .map_err(AppleCodesignError::CertificateDecode)?, + digest_algorithm: signer_info.digest_algorithm().to_string(), + signature_algorithm: signer_info.signature_algorithm().to_string(), + attributes, + content_type, + message_digest, + signing_time, + cdhash_plist, + cdhash_digests, + signature_verifies: signer_info + .verify_signature_with_signed_data(signed_data) + .is_ok(), + + time_stamp_token, + }) + } +} + +/// High-level representation of a CMS signature. +#[derive(Clone, Debug, Serialize)] +pub struct CmsSignature { + #[serde(skip_serializing_if = "Vec::is_empty")] + pub certificates: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub signers: Vec, +} + +impl TryFrom for CmsSignature { + type Error = AppleCodesignError; + + fn try_from(signed_data: SignedData) -> Result { + let certificates = signed_data + .certificates() + .map(|x| x.try_into()) + .collect::, _>>()?; + + let signers = signed_data + .signers() + .map(|x| CmsSigner::from_signer_info_and_signed_data(x, &signed_data)) + .collect::, _>>()?; + + Ok(Self { + certificates, + signers, + }) + } +} + +#[derive(Clone, Debug, Serialize)] +pub struct CodeDirectory { + pub version: String, + pub flags: String, + pub identifier: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub team_name: Option, + pub digest_type: String, + pub platform: u8, + pub signed_entity_size: u64, + #[serde(skip_serializing_if = "Option::is_none")] + pub executable_segment_flags: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub runtime_version: Option, + pub code_digests_count: usize, + #[serde(skip_serializing_if = "Vec::is_empty")] + slot_digests: Vec, +} + +impl<'a> TryFrom> for CodeDirectory { + type Error = AppleCodesignError; + + fn try_from(cd: CodeDirectoryBlob<'a>) -> Result { + let mut temp = cd + .slot_digests() + .iter() + .map(|(slot, digest)| (slot, digest.as_hex())) + .collect::>(); + temp.sort_by(|(a, _), (b, _)| a.cmp(b)); + + let slot_digests = temp + .into_iter() + .map(|(slot, digest)| format!("{slot:?}: {digest}")) + .collect::>(); + + Ok(Self { + version: format!("0x{:X}", cd.version), + flags: format!("{:?}", cd.flags), + identifier: cd.ident.to_string(), + team_name: cd.team_name.map(|x| x.to_string()), + signed_entity_size: cd.code_limit as _, + digest_type: format!("{}", cd.digest_type), + platform: cd.platform, + executable_segment_flags: cd.exec_seg_flags.map(|x| format!("{x:?}")), + runtime_version: cd + .runtime + .map(|x| format!("{}", crate::macho::parse_version_nibbles(x))), + code_digests_count: cd.code_digests.len(), + slot_digests, + }) + } +} + +/// High level representation of a code signature. +#[derive(Clone, Debug, Serialize)] +pub struct CodeSignature { + /// Length of the code signature data. + pub superblob_length: String, + pub blob_count: u32, + pub blobs: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub code_directory: Option, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub alternative_code_directories: Vec<(String, CodeDirectory)>, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub entitlements_plist: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub entitlements_der_plist: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub launch_constraints_self: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub launch_constraints_parent: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub launch_constraints_responsible: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub library_constraints: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub code_requirements: Vec, + pub cms: Option, +} + +impl<'a> TryFrom> for CodeSignature { + type Error = AppleCodesignError; + + fn try_from(sig: EmbeddedSignature<'a>) -> Result { + let mut entitlements_plist = vec![]; + let mut entitlements_der_plist = vec![]; + let mut launch_constraints_self = vec![]; + let mut launch_constraints_parent = vec![]; + let mut launch_constraints_responsible = vec![]; + let mut library_constraints = vec![]; + let mut code_requirements = vec![]; + let mut cms = None; + + let code_directory = if let Some(cd) = sig.code_directory()? { + Some(CodeDirectory::try_from(*cd)?) + } else { + None + }; + + let alternative_code_directories = sig + .alternate_code_directories()? + .into_iter() + .map(|(slot, cd)| Ok((format!("{slot:?}"), CodeDirectory::try_from(*cd)?))) + .collect::, AppleCodesignError>>()?; + + if let Some(blob) = sig.entitlements()? { + entitlements_plist = blob + .as_str() + .lines() + .map(|x| x.replace('\t', " ")) + .collect::>(); + } + + if let Some(blob) = sig.entitlements_der()? { + let xml = blob.plist_xml()?; + + entitlements_der_plist = pretty_print_xml_lines(&xml)?; + } + + if let Some(blob) = sig.launch_constraints_self()? { + launch_constraints_self = pretty_print_xml_lines(&blob.plist_xml()?)?; + } + + if let Some(blob) = sig.launch_constraints_parent()? { + launch_constraints_parent = pretty_print_xml_lines(&blob.plist_xml()?)?; + } + + if let Some(blob) = sig.launch_constraints_responsible()? { + launch_constraints_responsible = pretty_print_xml_lines(&blob.plist_xml()?)?; + } + + if let Some(blob) = sig.library_constraints()? { + library_constraints = pretty_print_xml_lines(&blob.plist_xml()?)?; + } + + if let Some(req) = sig.code_requirements()? { + let mut temp = vec![]; + + for (req, blob) in req.requirements { + let reqs = blob.parse_expressions()?; + temp.push((req, format!("{reqs}"))); + } + + temp.sort_by(|(a, _), (b, _)| a.cmp(b)); + + code_requirements = temp + .into_iter() + .map(|(req, value)| format!("{req}: {value}")) + .collect::>(); + } + + if let Some(signed_data) = sig.signed_data()? { + cms = Some(signed_data.try_into()?); + } + + Ok(Self { + superblob_length: format_integer(sig.length), + blob_count: sig.count, + blobs: sig + .blobs + .iter() + .map(BlobDescription::from) + .collect::>(), + code_directory, + alternative_code_directories, + entitlements_plist, + entitlements_der_plist, + launch_constraints_self, + launch_constraints_parent, + launch_constraints_responsible, + library_constraints, + code_requirements, + cms, + }) + } +} + +#[derive(Clone, Debug, Default, Serialize)] +pub struct MachOEntity { + pub macho_linkedit_start_offset: Option, + pub macho_signature_start_offset: Option, + pub macho_signature_end_offset: Option, + pub macho_linkedit_end_offset: Option, + pub macho_end_offset: Option, + pub linkedit_signature_start_offset: Option, + pub linkedit_signature_end_offset: Option, + pub linkedit_bytes_after_signature: Option, + pub signature: Option, +} + +#[derive(Clone, Debug, Serialize)] +pub struct DmgEntity { + pub code_signature_offset: u64, + pub code_signature_size: u64, + pub signature: Option, +} + +#[derive(Clone, Debug, Serialize)] +pub enum CodeSignatureFile { + ResourcesXml(Vec), + NotarizationTicket, + Other, +} + +#[derive(Clone, Debug, Serialize)] +pub struct XarTableOfContents { + pub toc_length_compressed: u64, + pub toc_length_uncompressed: u64, + pub checksum_offset: u64, + pub checksum_size: u64, + pub checksum_type: String, + pub toc_start_offset: u16, + pub heap_start_offset: u64, + pub creation_time: String, + pub toc_checksum_reported: String, + pub toc_checksum_reported_sha1_digest: String, + pub toc_checksum_reported_sha256_digest: String, + pub toc_checksum_actual_sha1: String, + pub toc_checksum_actual_sha256: String, + pub checksum_verifies: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub signature: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub x_signature: Option, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub xml: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub rsa_signature: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub rsa_signature_verifies: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub cms_signature: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub cms_signature_verifies: Option, +} + +impl XarTableOfContents { + pub fn from_xar( + xar: &mut XarReader, + ) -> Result { + let (digest_type, digest) = xar.checksum()?; + let _xml = xar.table_of_contents_decoded_data()?; + + let (rsa_signature, rsa_signature_verifies) = if let Some(sig) = xar.rsa_signature()? { + ( + Some(hex::encode(sig.0)), + Some(xar.verify_rsa_checksum_signature().unwrap_or(false)), + ) + } else { + (None, None) + }; + let (cms_signature, cms_signature_verifies) = + if let Some(signed_data) = xar.cms_signature()? { + ( + Some(CmsSignature::try_from(signed_data)?), + Some(xar.verify_cms_signature().unwrap_or(false)), + ) + } else { + (None, None) + }; + + let toc_checksum_actual_sha1 = xar.digest_table_of_contents_with(XarChecksumType::Sha1)?; + let toc_checksum_actual_sha256 = + xar.digest_table_of_contents_with(XarChecksumType::Sha256)?; + + let checksum_verifies = xar.verify_table_of_contents_checksum().unwrap_or(false); + + let header = xar.header(); + let toc = xar.table_of_contents(); + let checksum_offset = toc.checksum.offset; + let checksum_size = toc.checksum.size; + + // This can be useful for debugging. + //let xml = pretty_print_xml_lines(&xml)?; + let xml = vec![]; + + Ok(Self { + toc_length_compressed: header.toc_length_compressed, + toc_length_uncompressed: header.toc_length_uncompressed, + checksum_offset, + checksum_size, + checksum_type: apple_xar::format::XarChecksum::from(header.checksum_algorithm_id) + .to_string(), + toc_start_offset: header.size, + heap_start_offset: xar.heap_start_offset(), + creation_time: toc.creation_time.clone(), + toc_checksum_reported: format!("{}:{}", digest_type, hex::encode(&digest)), + toc_checksum_reported_sha1_digest: hex::encode(DigestType::Sha1.digest_data(&digest)?), + toc_checksum_reported_sha256_digest: hex::encode( + DigestType::Sha256.digest_data(&digest)?, + ), + toc_checksum_actual_sha1: hex::encode(toc_checksum_actual_sha1), + toc_checksum_actual_sha256: hex::encode(toc_checksum_actual_sha256), + checksum_verifies, + signature: if let Some(sig) = &toc.signature { + Some(sig.try_into()?) + } else { + None + }, + x_signature: if let Some(sig) = &toc.x_signature { + Some(sig.try_into()?) + } else { + None + }, + xml, + rsa_signature, + rsa_signature_verifies, + cms_signature, + cms_signature_verifies, + }) + } +} + +#[derive(Clone, Debug, Serialize)] +pub struct XarSignature { + pub style: String, + pub offset: u64, + pub size: u64, + pub end_offset: u64, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub certificates: Vec, +} + +impl TryFrom<&XarTocSignature> for XarSignature { + type Error = AppleCodesignError; + + fn try_from(sig: &XarTocSignature) -> Result { + Ok(Self { + style: sig.style.to_string(), + offset: sig.offset, + size: sig.size, + end_offset: sig.offset + sig.size, + certificates: sig + .x509_certificates()? + .into_iter() + .map(|cert| CertificateInfo::try_from(&cert)) + .collect::, AppleCodesignError>>()?, + }) + } +} + +#[derive(Clone, Debug, Default, Serialize)] +pub struct XarFile { + pub id: u64, + pub file_type: String, + pub data_size: Option, + pub data_length: Option, + pub data_extracted_checksum: Option, + pub data_archived_checksum: Option, + pub data_encoding: Option, +} + +impl TryFrom<&XarTocFile> for XarFile { + type Error = AppleCodesignError; + + fn try_from(file: &XarTocFile) -> Result { + let mut v = Self { + id: file.id, + file_type: file.file_type.to_string(), + ..Default::default() + }; + + if let Some(data) = &file.data { + v.populate_data(data); + } + + Ok(v) + } +} + +impl XarFile { + pub fn populate_data(&mut self, data: &apple_xar::table_of_contents::FileData) { + self.data_size = Some(data.size); + self.data_length = Some(data.length); + self.data_extracted_checksum = Some(format!( + "{}:{}", + data.extracted_checksum.style, data.extracted_checksum.checksum + )); + self.data_archived_checksum = Some(format!( + "{}:{}", + data.archived_checksum.style, data.archived_checksum.checksum + )); + self.data_encoding = Some(data.encoding.style.clone()); + } +} + +#[derive(Clone, Debug, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum SignatureEntity { + MachO(MachOEntity), + Dmg(DmgEntity), + BundleCodeSignatureFile(CodeSignatureFile), + XarTableOfContents(XarTableOfContents), + XarMember(XarFile), + Other, +} + +#[derive(Clone, Debug, Serialize)] +pub struct FileEntity { + pub path: PathBuf, + #[serde(skip_serializing_if = "Option::is_none")] + pub file_size: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub file_sha256: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub symlink_target: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub sub_path: Option, + #[serde(with = "serde_yaml::with::singleton_map")] + pub entity: SignatureEntity, +} + +impl FileEntity { + /// Construct an instance from a [Path]. + pub fn from_path(path: &Path, report_path: Option<&Path>) -> Result { + let metadata = std::fs::symlink_metadata(path)?; + + let report_path = if let Some(p) = report_path { + p.to_path_buf() + } else { + path.to_path_buf() + }; + + let (file_size, file_sha256, symlink_target) = if metadata.is_symlink() { + (None, None, Some(std::fs::read_link(path)?)) + } else { + ( + Some(metadata.len()), + Some(hex::encode(DigestAlgorithm::Sha256.digest_path(path)?)), + None, + ) + }; + + Ok(Self { + path: report_path, + file_size, + file_sha256, + symlink_target, + sub_path: None, + entity: SignatureEntity::Other, + }) + } +} + +/// Entity for reading Apple code signature data. +pub enum SignatureReader { + Dmg(PathBuf, Box), + MachO(PathBuf, Vec), + Bundle(Box), + FlatPackage(PathBuf), +} + +impl SignatureReader { + /// Construct a signature reader from a path. + pub fn from_path(path: impl AsRef) -> Result { + let path = path.as_ref(); + match PathType::from_path(path)? { + PathType::Bundle => Ok(Self::Bundle(Box::new( + DirectoryBundle::new_from_path(path) + .map_err(AppleCodesignError::DirectoryBundle)?, + ))), + PathType::Dmg => { + let mut fh = File::open(path)?; + Ok(Self::Dmg( + path.to_path_buf(), + Box::new(DmgReader::new(&mut fh)?), + )) + } + PathType::MachO => { + let data = std::fs::read(path)?; + MachFile::parse(&data)?; + + Ok(Self::MachO(path.to_path_buf(), data)) + } + PathType::Xar => Ok(Self::FlatPackage(path.to_path_buf())), + PathType::Zip | PathType::Other => Err(AppleCodesignError::UnrecognizedPathType), + } + } + + /// Obtain entities that are possibly relevant to code signing. + pub fn entities(&self) -> Result, AppleCodesignError> { + match self { + Self::Dmg(path, dmg) => { + let mut entity = FileEntity::from_path(path, None)?; + entity.entity = SignatureEntity::Dmg(Self::resolve_dmg_entity(dmg)?); + + Ok(vec![entity]) + } + Self::MachO(path, data) => Self::resolve_macho_entities_from_data(path, data, None), + Self::Bundle(bundle) => Self::resolve_bundle_entities(bundle), + Self::FlatPackage(path) => Self::resolve_flat_package_entities(path), + } + } + + fn resolve_dmg_entity(dmg: &DmgReader) -> Result { + let signature = if let Some(sig) = dmg.embedded_signature()? { + Some(sig.try_into()?) + } else { + None + }; + + Ok(DmgEntity { + code_signature_offset: dmg.koly().code_signature_offset, + code_signature_size: dmg.koly().code_signature_size, + signature, + }) + } + + fn resolve_macho_entities_from_data( + path: &Path, + data: &[u8], + report_path: Option<&Path>, + ) -> Result, AppleCodesignError> { + let mut entities = vec![]; + + let entity = FileEntity::from_path(path, report_path)?; + + for macho in MachFile::parse(data)?.into_iter() { + let mut entity = entity.clone(); + + if let Some(index) = macho.index { + entity.sub_path = Some(format!("macho-index:{index}")); + } + + entity.entity = SignatureEntity::MachO(Self::resolve_macho_entity(macho)?); + + entities.push(entity); + } + + Ok(entities) + } + + fn resolve_macho_entity(macho: MachOBinary) -> Result { + let mut entity = MachOEntity::default(); + + entity.macho_end_offset = Some(format_integer(macho.data.len())); + + if let Some(sig) = macho.find_signature_data()? { + entity.macho_linkedit_start_offset = + Some(format_integer(sig.linkedit_segment_start_offset)); + entity.macho_linkedit_end_offset = + Some(format_integer(sig.linkedit_segment_end_offset)); + entity.macho_signature_start_offset = + Some(format_integer(sig.signature_file_start_offset)); + entity.linkedit_signature_start_offset = + Some(format_integer(sig.signature_segment_start_offset)); + } + + if let Some(sig) = macho.code_signature()? { + if let Some(sig_info) = macho.find_signature_data()? { + entity.macho_signature_end_offset = Some(format_integer( + sig_info.signature_file_start_offset + sig.length as usize, + )); + entity.linkedit_signature_end_offset = Some(format_integer( + sig_info.signature_segment_start_offset + sig.length as usize, + )); + + let mut linkedit_remaining = + sig_info.linkedit_segment_end_offset - sig_info.linkedit_segment_start_offset; + linkedit_remaining -= sig_info.signature_segment_start_offset; + linkedit_remaining -= sig.length as usize; + entity.linkedit_bytes_after_signature = Some(format_integer(linkedit_remaining)); + } + + entity.signature = Some(sig.try_into()?); + } + + Ok(entity) + } + + fn resolve_bundle_entities( + bundle: &DirectoryBundle, + ) -> Result, AppleCodesignError> { + let mut entities = vec![]; + + for file in bundle + .files(true) + .map_err(AppleCodesignError::DirectoryBundle)? + { + entities.extend( + Self::resolve_bundle_file_entity(bundle.root_dir().to_path_buf(), file)? + .into_iter(), + ); + } + + Ok(entities) + } + + fn resolve_bundle_file_entity( + base_path: PathBuf, + file: DirectoryBundleFile, + ) -> Result, AppleCodesignError> { + let main_relative_path = match file.absolute_path().strip_prefix(&base_path) { + Ok(path) => path.to_path_buf(), + Err(_) => file.absolute_path().to_path_buf(), + }; + + let mut entities = vec![]; + + let mut default_entity = + FileEntity::from_path(file.absolute_path(), Some(&main_relative_path))?; + + let file_name = file + .absolute_path() + .file_name() + .expect("path should have file name") + .to_string_lossy(); + let parent_dir = file + .absolute_path() + .parent() + .expect("path should have parent directory"); + + // There may be bugs in the code identifying the role of files in bundles. + // So rely on our own heuristics to detect and report on the file type. + if default_entity.symlink_target.is_some() { + entities.push(default_entity); + } else if parent_dir.ends_with("_CodeSignature") { + if file_name == "CodeResources" { + let data = std::fs::read(file.absolute_path())?; + + default_entity.entity = + SignatureEntity::BundleCodeSignatureFile(CodeSignatureFile::ResourcesXml( + String::from_utf8_lossy(&data) + .split('\n') + .map(|x| x.replace('\t', " ")) + .collect::>(), + )); + + entities.push(default_entity); + } else { + default_entity.entity = + SignatureEntity::BundleCodeSignatureFile(CodeSignatureFile::Other); + + entities.push(default_entity); + } + } else if file_name == "CodeResources" { + default_entity.entity = + SignatureEntity::BundleCodeSignatureFile(CodeSignatureFile::NotarizationTicket); + + entities.push(default_entity); + } else { + let data = std::fs::read(file.absolute_path())?; + + match Self::resolve_macho_entities_from_data( + file.absolute_path(), + &data, + Some(&main_relative_path), + ) { + Ok(extra) => { + entities.extend(extra); + } + Err(_) => { + // Just some extra file. + entities.push(default_entity); + } + } + } + + Ok(entities) + } + + fn resolve_flat_package_entities(path: &Path) -> Result, AppleCodesignError> { + let mut xar = XarReader::new(File::open(path)?)?; + + let default_entity = FileEntity::from_path(path, None)?; + + let mut entities = vec![]; + + let mut entity = default_entity.clone(); + entity.sub_path = Some("toc".to_string()); + entity.entity = + SignatureEntity::XarTableOfContents(XarTableOfContents::from_xar(&mut xar)?); + entities.push(entity); + + // Now emit entries for all files in table of contents. + for (name, file) in xar.files()? { + let mut entity = default_entity.clone(); + entity.sub_path = Some(name); + entity.entity = SignatureEntity::XarMember(XarFile::try_from(&file)?); + entities.push(entity); + } + + Ok(entities) + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/remote_signing/mod.rs b/3rdparty/apple-codesign-0.29.0/src/remote_signing/mod.rs new file mode 100644 index 00000000..29708306 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/remote_signing/mod.rs @@ -0,0 +1,1093 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Remote signing support. + +pub mod session_negotiation; + +use { + crate::{ + cryptography::PrivateKey, + remote_signing::session_negotiation::{ + PeerKeys, PublicKeyPeerDecrypt, SessionInitiatePeer, SessionJoinContext, + SessionJoinPeerPreJoin, + }, + AppleCodesignError, + }, + base64::{engine::general_purpose::STANDARD as STANDARD_ENGINE, Engine}, + bcder::{ + encode::{PrimitiveContent, Values}, + Mode, Oid, + }, + bytes::Bytes, + log::{debug, error, warn}, + serde::{de::DeserializeOwned, Deserialize, Serialize}, + signature::Signer, + std::{ + cell::{RefCell, RefMut}, + net::TcpStream, + }, + thiserror::Error, + tungstenite::{ + client::IntoClientRequest, + protocol::{Message, WebSocket, WebSocketConfig}, + stream::MaybeTlsStream, + }, + x509_certificate::{ + CapturedX509Certificate, KeyAlgorithm, KeyInfoSigner, Sign, Signature, SignatureAlgorithm, + X509CertificateError, + }, + zeroize::Zeroizing, +}; + +/// URL of default server to use. +pub const DEFAULT_SERVER_URL: &str = "wss://ws.codesign.gregoryszorc.com/"; + +/// An error specific to remote signing. +#[derive(Debug, Error)] +pub enum RemoteSignError { + #[error("unexpected message received from relay server: {0}")] + ServerUnexpectedMessage(String), + + #[error("error reported from relay server: {0}")] + ServerError(String), + + #[error("not compatible with relay server; try upgrading to a new release?")] + ServerIncompatible, + + #[error("cryptography error: {0}")] + Crypto(String), + + #[error("bad client state: {0}")] + ClientState(&'static str), + + #[error("joining state not wanted for this session type: {0}")] + SessionJoinUnwantedState(String), + + #[error("session join string error: {0}")] + SessionJoinString(String), + + #[error("base64 decode error: {0}")] + Base64(#[from] base64::DecodeError), + + #[error("I/O error: {0}")] + Io(#[from] std::io::Error), + + #[error("PEM encoding error: {0}")] + Pem(#[from] pem::PemError), + + #[error("JSON serialization error: {0}")] + SerdeJson(#[from] serde_json::Error), + + #[error("SPAKE error: {0}")] + Spake(spake2::Error), + + #[error("SPKI error: {0}")] + Spki(#[from] spki::Error), + + #[error("websocket error: {0}")] + Websocket(#[from] tungstenite::Error), + + #[error("X.509 certificate handler error: {0}")] + X509(#[from] X509CertificateError), +} + +#[derive(Clone, Copy, Debug, PartialEq, Serialize)] +#[serde(rename_all = "kebab-case")] +enum ApiMethod { + Hello, + CreateSession, + JoinSession, + SendMessage, + Goodbye, +} + +/// A websocket message sent from the client to the server. +#[derive(Clone, Debug, Serialize)] +struct ClientMessage { + /// Unique ID for this request. + request_id: String, + /// API method being called. + api: ApiMethod, + /// Payload for this method. + payload: Option, +} + +/// Payload for a [ClientMessage]. +#[derive(Clone, Debug, Serialize)] +#[serde(untagged)] +enum ClientPayload { + CreateSession { + session_id: String, + ttl: u64, + context: Option, + }, + JoinSession { + session_id: String, + context: Option, + }, + SendMessage { + session_id: String, + message: String, + }, + Goodbye { + session_id: String, + reason: Option, + }, +} + +#[derive(Clone, Copy, Debug, Deserialize, PartialEq)] +#[serde(rename_all = "kebab-case")] +enum ServerMessageType { + Error, + Greeting, + SessionCreated, + SessionJoined, + MessageSent, + PeerMessage, + SessionClosed, +} + +/// Websocket message sent from server to client. +#[derive(Clone, Debug, Deserialize)] +struct ServerMessage { + /// ID of request responsible for this message. + request_id: Option, + /// The type of message. + #[serde(rename = "type")] + typ: ServerMessageType, + ttl: Option, + payload: Option, +} + +impl ServerMessage { + fn into_result(self) -> Result { + if self.typ == ServerMessageType::Error { + let error = self.as_error()?; + Err(RemoteSignError::ServerError(format!( + "{}: {}", + error.code, error.message + ))) + } else { + Ok(self) + } + } + + fn as_type( + &self, + message_type: ServerMessageType, + ) -> Result { + if self.typ == message_type { + if let Some(value) = &self.payload { + Ok(serde_json::from_value(value.clone())?) + } else { + Err(RemoteSignError::ClientState( + "no payload for requested type", + )) + } + } else { + Err(RemoteSignError::ClientState( + "requested payload for wrong message type", + )) + } + } + + fn as_error(&self) -> Result { + self.as_type::(ServerMessageType::Error) + } + + fn as_greeting(&self) -> Result { + self.as_type::(ServerMessageType::Greeting) + } + + fn as_session_joined(&self) -> Result { + self.as_type::(ServerMessageType::SessionJoined) + } + + fn as_peer_message(&self) -> Result { + self.as_type::(ServerMessageType::PeerMessage) + } + + fn as_session_closed(&self) -> Result { + self.as_type::(ServerMessageType::SessionClosed) + } +} + +/// Response messages seen from server. +#[derive(Clone, Debug, Deserialize)] +#[serde(untagged)] +enum ServerPayload { + Error(ServerError), + Greeting(ServerGreeting), + SessionJoined(ServerJoined), + PeerMessage(ServerPeerMessage), + SessionClosed(ServerSessionClosed), +} + +#[derive(Clone, Debug, Deserialize)] +struct ServerError { + code: String, + message: String, +} + +#[derive(Clone, Debug, Deserialize)] +struct ServerGreeting { + apis: Vec, + motd: Option, +} + +#[derive(Clone, Debug, Deserialize)] +struct ServerJoined { + context: Option, +} + +#[derive(Clone, Debug, Deserialize)] +struct ServerPeerMessage { + message: String, +} + +#[derive(Clone, Debug, Deserialize)] +struct ServerSessionClosed { + reason: Option, +} + +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "kebab-case")] +enum PeerMessageType { + Ping, + Pong, + RequestSigningCertificate, + SigningCertificate, + SignRequest, + Signature, +} + +/// A peer-to-peer message. +#[derive(Clone, Debug, Deserialize, Serialize)] +struct PeerMessage { + #[serde(rename = "type")] + typ: PeerMessageType, + payload: Option, +} + +impl PeerMessage { + fn require_type(self, typ: PeerMessageType) -> Result { + if self.typ == typ { + Ok(self) + } else { + Err(RemoteSignError::ServerUnexpectedMessage(format!( + "{:?}", + self.typ + ))) + } + } + + fn as_type( + &self, + message_type: PeerMessageType, + ) -> Result { + if self.typ == message_type { + if let Some(value) = &self.payload { + Ok(serde_json::from_value(value.clone())?) + } else { + Err(RemoteSignError::ClientState( + "no payload for requested type", + )) + } + } else { + Err(RemoteSignError::ClientState( + "requested payload for wrong message type", + )) + } + } + + fn as_signing_certificate(&self) -> Result { + self.as_type::(PeerMessageType::SigningCertificate) + } + + fn as_sign_request(&self) -> Result { + self.as_type::(PeerMessageType::SignRequest) + } + + fn as_signature(&self) -> Result { + self.as_type::(PeerMessageType::Signature) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +struct PeerCertificate { + certificate: String, + #[serde(skip_serializing_if = "Vec::is_empty")] + chain: Vec, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(untagged)] +enum PeerPayload { + SigningCertificate(PeerSigningCertificate), + SignRequest(PeerSignRequest), + Signature(PeerSignature), +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +struct PeerSigningCertificate { + certificates: Vec, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +struct PeerSignRequest { + message: String, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +struct PeerSignature { + message: String, + signature: String, + algorithm_oid: String, +} + +const REQUIRED_ACTIONS: [&str; 4] = ["create-session", "join-session", "send-message", "goodbye"]; + +/// Represents the response from the server. +enum ServerResponse { + /// Server closed the connection. + Closed, + + /// A parsed protocol message. + Message(ServerMessage), +} + +/// A function that receives session information. +pub type SessionInfoCallback = fn(sjs_base64: &str, sjs_pem: &str) -> Result<(), RemoteSignError>; + +fn create_websocket( + req: impl IntoClientRequest, +) -> Result>, RemoteSignError> { + let config = WebSocketConfig { + ..Default::default() + }; + + let req = req.into_client_request()?; + warn!("connecting to {}", req.uri()); + + let (ws, _) = tungstenite::client::connect_with_config(req, Some(config), 5)?; + + Ok(ws) +} + +fn wait_for_server_response( + ws: &mut WebSocket>, +) -> Result { + loop { + match ws.read()? { + Message::Text(text) => { + let message = serde_json::from_str::(&text)?; + debug!( + "received message; request-id: {}; type: {:?}", + message + .request_id + .as_ref() + .unwrap_or(&"(not set)".to_string()), + message.typ + ); + + return Ok(ServerResponse::Message(message)); + } + Message::Binary(_) => { + return Err(RemoteSignError::ServerUnexpectedMessage( + "binary websocket message".into(), + )) + } + // TODO return error for these? + Message::Pong(_) => {} + Message::Ping(_) => {} + Message::Frame(_) => {} + Message::Close(_) => { + return Ok(ServerResponse::Closed); + } + } + } +} + +fn wait_for_server_message( + ws: &mut WebSocket>, +) -> Result { + match wait_for_server_response(ws)? { + ServerResponse::Closed => Err(RemoteSignError::ClientState("server closed connection")), + ServerResponse::Message(m) => { + debug!( + "received server message {:?}; remaining session TTL: {}", + m.typ, + m.ttl.unwrap_or_default() + ); + Ok(m) + } + } +} + +fn wait_for_expected_server_message( + ws: &mut WebSocket>, + message_type: ServerMessageType, +) -> Result { + let res = wait_for_server_message(ws)?.into_result()?; + + if res.typ == message_type { + Ok(res) + } else { + Err(RemoteSignError::ServerUnexpectedMessage(format!( + "{:?}", + res.typ + ))) + } +} + +/// A client for the remote signing protocol that has not yet joined a session. +/// +/// Clients can perform both the initiator and signer roles. +pub struct UnjoinedSigningClient { + ws: WebSocket>, +} + +impl UnjoinedSigningClient { + fn new(req: impl IntoClientRequest) -> Result { + let ws = create_websocket(req)?; + + let mut slf = Self { ws }; + + slf.send_hello()?; + + Ok(slf) + } + + /// Create a new client in the initiator role. + pub fn new_initiator( + req: impl IntoClientRequest, + initiator: Box, + session_info_cb: Option, + ) -> Result { + let slf = Self::new(req)?; + slf.create_session_and_wait_for_signer(initiator, session_info_cb) + } + + /// Create a new client in the signer role. + pub fn new_signer( + joiner: Box, + signing_key: &dyn KeyInfoSigner, + signing_cert: CapturedX509Certificate, + certificates: Vec, + default_server_url: String, + ) -> Result { + // An error here could result in the peer hanging indefinitely because the session + // is unjoined. Ideally we'd recover from this by attempting to join with an error. + // However, we may not even be able to obtain the session ID since sometimes it is + // encrypted and the error could be from a decryption failure! So for now, just let + // the peer idle. + let join_context = joiner.join_context()?; + + let server_url = join_context + .server_url + .as_ref() + .unwrap_or(&default_server_url); + + let slf = Self::new(server_url)?; + slf.join_session(join_context, signing_key, signing_cert, certificates) + } + + /// Create a new signing session and wait for a signer to arrive. + fn create_session_and_wait_for_signer( + mut self, + initiator: Box, + session_info_cb: Option, + ) -> Result { + let session_id = initiator.session_id().to_string(); + + self.send_request( + ApiMethod::CreateSession, + Some(ClientPayload::CreateSession { + session_id: session_id.clone(), + ttl: 600, + context: initiator + .session_create_context() + .map(|x| STANDARD_ENGINE.encode(x)), + }), + )?; + + let sjs_base64 = initiator.session_join_string_base64()?; + let sjs_pem = initiator.session_join_string_pem()?; + + wait_for_expected_server_message(&mut self.ws, ServerMessageType::SessionCreated)?; + warn!("session successfully created on server"); + + if let Some(cb) = session_info_cb { + cb(&sjs_base64, &sjs_pem)?; + } + + let res = wait_for_expected_server_message(&mut self.ws, ServerMessageType::SessionJoined)?; + + let joined = res.as_session_joined()?; + warn!("signer joined session; deriving shared encryption key"); + + let context = if let Some(context) = joined.context { + Some(STANDARD_ENGINE.decode(context)?) + } else { + None + }; + + let keys = initiator.negotiate_session(context)?; + + let mut client = PairedClient { + ws: self.ws, + session_id, + keys, + }; + + client.send_ping()?; + + let (signing_cert, signing_chain) = client.request_signing_certificate()?; + + if let Some(name) = signing_cert.subject_common_name() { + warn!("remote signer will sign with certificate: {}", name); + } + + Ok(InitiatorClient { + client: RefCell::new(client), + signing_cert, + signing_chain, + }) + } + + /// Join a signing session. + /// + /// This should be called by signers once they have the session ID to join. + pub fn join_session( + mut self, + join_context: SessionJoinContext, + signing_key: &dyn KeyInfoSigner, + signing_cert: CapturedX509Certificate, + certificates: Vec, + ) -> Result { + let session_id = join_context.session_id.clone(); + + warn!("joining session..."); + self.send_request( + ApiMethod::JoinSession, + Some(ClientPayload::JoinSession { + session_id: session_id.clone(), + context: join_context.peer_context.map(|x| STANDARD_ENGINE.encode(x)), + }), + )?; + + wait_for_expected_server_message(&mut self.ws, ServerMessageType::SessionJoined)?; + + warn!("successfully joined signing session {}", session_id); + + let keys = join_context.peer_handshake.negotiate_session()?; + + let mut client = PairedClient { + ws: self.ws, + session_id, + keys, + }; + + warn!("verifying encrypted communications with peer"); + client.send_ping()?; + + Ok(SigningClient { + client: RefCell::new(client), + signing_key, + signing_cert, + certificates, + }) + } + + fn send_request( + &mut self, + api: ApiMethod, + payload: Option, + ) -> Result<(), RemoteSignError> { + let request_id = uuid::Uuid::new_v4().to_string(); + + let message = ClientMessage { + request_id, + api, + payload, + }; + + let body = serde_json::to_string(&message)?; + self.ws.send(body.into())?; + self.ws.flush()?; + + Ok(()) + } + + fn send_hello(&mut self) -> Result<(), RemoteSignError> { + self.send_request(ApiMethod::Hello, None)?; + + let res = wait_for_expected_server_message(&mut self.ws, ServerMessageType::Greeting)?; + let greeting = res.as_greeting()?; + + if let Some(motd) = &greeting.motd { + warn!("message from remote server: {}", motd); + } + + for required in REQUIRED_ACTIONS { + if !greeting.apis.contains(&required.to_string()) { + error!("server does not support required action {}", required); + return Err(RemoteSignError::ServerIncompatible); + } + } + + Ok(()) + } +} + +/// A remote signing client that has joined a session and is ready to exchange messages. +pub struct PairedClient { + ws: WebSocket>, + session_id: String, + keys: PeerKeys, +} + +impl Drop for PairedClient { + fn drop(&mut self) { + warn!("disconnecting from relay server"); + } +} + +impl PairedClient { + fn send_request( + &mut self, + api: ApiMethod, + payload: Option, + ) -> Result<(), RemoteSignError> { + let request_id = uuid::Uuid::new_v4().to_string(); + + let message = ClientMessage { + request_id, + api, + payload, + }; + + let body = serde_json::to_string(&message)?; + self.ws.send(body.into())?; + self.ws.flush()?; + + Ok(()) + } + + fn decrypt_peer_message( + &mut self, + message: &ServerPeerMessage, + ) -> Result { + let ciphertext = STANDARD_ENGINE.decode(&message.message)?; + + let plaintext = self.keys.open(ciphertext)?; + + Ok(serde_json::from_slice(&plaintext)?) + } + + fn send_encrypted_message( + &mut self, + message_type: PeerMessageType, + payload: Option, + ) -> Result<(), RemoteSignError> { + let message = PeerMessage { + typ: message_type, + payload: if let Some(payload) = payload { + Some(serde_json::to_value(payload)?) + } else { + None + }, + }; + + let ciphertext = self.keys.seal(&serde_json::to_vec(&message)?)?; + + self.send_request( + ApiMethod::SendMessage, + Some(ClientPayload::SendMessage { + session_id: self.session_id.clone(), + message: STANDARD_ENGINE.encode(ciphertext), + }), + )?; + + Ok(()) + } + + fn wait_for_peer_message(&mut self) -> Result, RemoteSignError> { + let res = wait_for_server_message(&mut self.ws)?.into_result()?; + + if let Ok(closed) = res.as_session_closed() { + warn!( + "signing session closed; reason: {}", + closed + .reason + .as_ref() + .unwrap_or(&"(none given)".to_string()) + ); + Ok(None) + } else { + let message = res.as_peer_message()?; + + Ok(Some(self.decrypt_peer_message(&message)?)) + } + } + + fn wait_for_server_and_peer_response(&mut self) -> Result { + let mut response = None; + + // We should get a server message acknowledging our request plus the response from + // the peer. The order they arrive in is random. + for _ in 0..2 { + let res = wait_for_server_message(&mut self.ws)?.into_result()?; + + match res.typ { + ServerMessageType::MessageSent => {} + ServerMessageType::PeerMessage => { + let message = res.as_peer_message()?; + + response = Some(self.decrypt_peer_message(&message)?); + } + m => return Err(RemoteSignError::ServerUnexpectedMessage(format!("{m:?}"))), + } + } + + if let Some(response) = response { + Ok(response) + } else { + Err(RemoteSignError::ClientState( + "failed to receive response from server or peer", + )) + } + } + + fn send_goodbye(&mut self, reason: Option) -> Result<(), RemoteSignError> { + warn!("terminating signing session on relay"); + self.send_request( + ApiMethod::Goodbye, + Some(ClientPayload::Goodbye { + session_id: self.session_id.clone(), + reason, + }), + )?; + + wait_for_server_message(&mut self.ws)?.into_result()?; + warn!("relay server confirmed session termination"); + + Ok(()) + } + + fn send_ping(&mut self) -> Result<(), RemoteSignError> { + // We should get a server message acknowledging our request plus a + // ping from the peer. The order may not be reliable. + self.send_encrypted_message(PeerMessageType::Ping, None)?; + let message = self.wait_for_server_and_peer_response()?; + if !matches!(message.typ, PeerMessageType::Ping) { + return Err(RemoteSignError::ServerUnexpectedMessage( + "unexpected response to ping message".into(), + )); + } + + self.send_encrypted_message(PeerMessageType::Pong, None)?; + let message = self.wait_for_server_and_peer_response()?; + if !matches!(message.typ, PeerMessageType::Pong) { + return Err(RemoteSignError::ServerUnexpectedMessage( + "unexpected response to ping message".into(), + )); + } + + Ok(()) + } + + /// Request the signing certificate from the peer. + pub fn request_signing_certificate( + &mut self, + ) -> Result<(CapturedX509Certificate, Vec), RemoteSignError> { + warn!("requesting signing certificate info from signer"); + self.send_encrypted_message(PeerMessageType::RequestSigningCertificate, None)?; + let res = self + .wait_for_server_and_peer_response()? + .require_type(PeerMessageType::SigningCertificate)?; + + let cert = res.as_signing_certificate()?; + + if let Some(cert) = cert.certificates.get(0) { + let cert_der = STANDARD_ENGINE.decode(&cert.certificate)?; + let chain_der = cert + .chain + .iter() + .map(|x| STANDARD_ENGINE.decode(x)) + .collect::, base64::DecodeError>>()?; + + let cert = CapturedX509Certificate::from_der(cert_der)?; + let chain = chain_der + .into_iter() + .map(CapturedX509Certificate::from_der) + .collect::, X509CertificateError>>()?; + + return Ok((cert, chain)); + } + + Err(RemoteSignError::ClientState( + "did not receive any signing certificates from peer", + )) + } +} + +/// A client fulfilling the role of the initiator. +pub struct InitiatorClient { + client: RefCell, + signing_cert: CapturedX509Certificate, + signing_chain: Vec, +} + +impl InitiatorClient { + /// The X.509 certificate that will be used to sign. + pub fn signing_certificate(&self) -> &CapturedX509Certificate { + &self.signing_cert + } + + /// Additional X.509 certificates in the signing chain. + pub fn certificate_chain(&self) -> &[CapturedX509Certificate] { + &self.signing_chain + } +} + +impl Signer for InitiatorClient { + fn try_sign(&self, message: &[u8]) -> Result { + let mut client = self.client.borrow_mut(); + + warn!("sending signing request to remote signer"); + + client + .send_encrypted_message( + PeerMessageType::SignRequest, + Some(PeerPayload::SignRequest(PeerSignRequest { + message: STANDARD_ENGINE.encode(message), + })), + ) + .map_err(signature::Error::from_source)?; + + let response = client + .wait_for_server_and_peer_response() + .map_err(signature::Error::from_source)? + .require_type(PeerMessageType::Signature) + .map_err(signature::Error::from_source)?; + + let peer_signature = response + .as_signature() + .map_err(signature::Error::from_source)?; + + warn!("received signature from remote signer"); + + let signature = STANDARD_ENGINE + .decode(&peer_signature.signature) + .map_err(signature::Error::from_source)?; + let oid_der = STANDARD_ENGINE + .decode(&peer_signature.algorithm_oid) + .map_err(signature::Error::from_source)?; + + bcder::decode::Constructed::decode(oid_der.as_ref(), Mode::Der, |cons| { + Oid::take_from(cons) + }) + .map_err(|_| { + signature::Error::from_source(RemoteSignError::Crypto( + "error parsing signature OID".into(), + )) + })?; + + // The peer could be acting maliciously (or just be buggy) and sign with a + // certificate from the initial one presented. So verify the signature we + // received is valid for the message we sent. + if let Err(e) = self.signing_cert.verify_signed_data(message, &signature) { + error!("Peer issued signature did not verify against the certificate they provided"); + error!("The peer could be acting maliciously. Or it could just be buggy."); + error!("Either way, it didn't issue a valid signature, so we're giving up."); + + return Err(signature::Error::from_source(e)); + } + + Ok(signature.into()) + } +} + +impl Sign for InitiatorClient { + fn sign(&self, message: &[u8]) -> Result<(Vec, SignatureAlgorithm), X509CertificateError> { + let algorithm = self.signature_algorithm()?; + + Ok((self.try_sign(message)?.into(), algorithm)) + } + + fn key_algorithm(&self) -> Option { + self.signing_cert.key_algorithm() + } + + fn public_key_data(&self) -> Bytes { + self.signing_cert.public_key_data() + } + + fn signature_algorithm(&self) -> Result { + if let Some(algorithm) = self.signing_cert.signature_algorithm() { + Ok(algorithm) + } else { + Err(X509CertificateError::UnknownSignatureAlgorithm(format!( + "{}", + self.signing_cert.signature_algorithm_oid() + ))) + } + } + + fn private_key_data(&self) -> Option>> { + // We never have access to private keys from the remote signer. + None + } + + fn rsa_primes( + &self, + ) -> Result>, Zeroizing>)>, X509CertificateError> { + // We never have access to private keys from the remote signer. + Ok(None) + } +} + +impl KeyInfoSigner for InitiatorClient {} + +impl PublicKeyPeerDecrypt for InitiatorClient { + fn decrypt(&self, _ciphertext: &[u8]) -> Result, RemoteSignError> { + Err(RemoteSignError::Crypto( + "a remote signer cannot be used to perform signing".into(), + )) + } +} + +impl PrivateKey for InitiatorClient { + fn as_key_info_signer(&self) -> &dyn KeyInfoSigner { + self + } + + fn to_public_key_peer_decrypt( + &self, + ) -> Result, AppleCodesignError> { + Err( + RemoteSignError::ClientState("cannot use remote signing initiator for decryption") + .into(), + ) + } + + fn finish(&self) -> Result<(), AppleCodesignError> { + // Tell the peer we're done so it disconnects + Ok(self + .client + .borrow_mut() + .send_goodbye(Some("signing operations completed".into()))?) + } +} + +pub struct SigningClient<'key> { + client: RefCell, + signing_key: &'key dyn KeyInfoSigner, + signing_cert: CapturedX509Certificate, + certificates: Vec, +} + +impl<'key> SigningClient<'key> { + fn send_signing_certificate( + &self, + mut client: RefMut, + ) -> Result<(), RemoteSignError> { + client.send_encrypted_message( + PeerMessageType::SigningCertificate, + Some(PeerPayload::SigningCertificate(PeerSigningCertificate { + certificates: vec![PeerCertificate { + certificate: STANDARD_ENGINE.encode(self.signing_cert.encode_der()?), + chain: self + .certificates + .iter() + .map(|cert| { + let der = cert.encode_der()?; + + Ok(STANDARD_ENGINE.encode(der)) + }) + .collect::, RemoteSignError>>()?, + }], + })), + )?; + + wait_for_expected_server_message(&mut client.ws, ServerMessageType::MessageSent)?; + + Ok(()) + } + + fn handle_sign_request( + &self, + mut client: RefMut, + request: PeerSignRequest, + ) -> Result<(), RemoteSignError> { + let message = STANDARD_ENGINE.decode(&request.message)?; + + warn!( + "creating signature for remote message: {}", + &request.message + ); + let signature = self + .signing_key + .try_sign(&message) + .map_err(|e| RemoteSignError::Crypto(format!("when creating signature: {e}")))?; + let algorithm = self.signing_key.signature_algorithm()?; + + let oid = Oid::from(algorithm); + let mut oid_der = vec![]; + oid.encode().write_encoded(Mode::Der, &mut oid_der)?; + + warn!("sending signature to peer"); + client.send_encrypted_message( + PeerMessageType::Signature, + Some(PeerPayload::Signature(PeerSignature { + message: STANDARD_ENGINE.encode(message), + signature: STANDARD_ENGINE.encode(signature), + algorithm_oid: STANDARD_ENGINE.encode(oid_der), + })), + )?; + + wait_for_expected_server_message(&mut client.ws, ServerMessageType::MessageSent)?; + warn!("relay acknowledged signature message received"); + + Ok(()) + } + + fn process_next_message(&self) -> Result { + let mut client = self.client.borrow_mut(); + + warn!("waiting for server to send us a message..."); + let res = if let Some(res) = client.wait_for_peer_message()? { + res + } else { + return Ok(false); + }; + + match res.typ { + PeerMessageType::RequestSigningCertificate => { + self.send_signing_certificate(client)?; + } + PeerMessageType::Ping => { + client.send_encrypted_message(PeerMessageType::Pong, None)?; + wait_for_expected_server_message(&mut client.ws, ServerMessageType::MessageSent)?; + } + PeerMessageType::Pong => {} + PeerMessageType::SignRequest => { + self.handle_sign_request(client, res.as_sign_request()?)?; + } + typ => { + warn!("unprocessed message: {:?}", typ); + } + } + + Ok(true) + } + + pub fn run(self) -> Result<(), RemoteSignError> { + while self.process_next_message()? {} + + Ok(()) + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/remote_signing/session_negotiation.rs b/3rdparty/apple-codesign-0.29.0/src/remote_signing/session_negotiation.rs new file mode 100644 index 00000000..59940e7f --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/remote_signing/session_negotiation.rs @@ -0,0 +1,891 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Session establishment and crypto code for remote signing protocol. +//! +//! The intent of this module / file is to isolate the code with the highest +//! sensitivity for security matters. + +use { + crate::remote_signing::RemoteSignError, + base64::Engine, + der::{Decode, Encode}, + minicbor::{encode::Write, Decode as CborDecode, Decoder, Encode as CborEncode, Encoder}, + oid_registry::OID_PKCS1_RSAENCRYPTION, + pkcs1::RsaPublicKey as RsaPublicKeyAsn1, + ring::{ + aead::{ + Aad, BoundKey, Nonce, NonceSequence, OpeningKey, SealingKey, UnboundKey, AES_128_GCM, + CHACHA20_POLY1305, NONCE_LEN, + }, + agreement::{agree_ephemeral, EphemeralPrivateKey, UnparsedPublicKey, X25519}, + hkdf::{Salt, HKDF_SHA256}, + rand::{SecureRandom, SystemRandom}, + }, + rsa::{BigUint, Oaep, RsaPublicKey}, + scroll::{Pwrite, LE}, + spake2::{Ed25519Group, Identity, Password, Spake2}, + spki::SubjectPublicKeyInfoRef, + std::fmt::{Display, Formatter}, +}; + +type Result = std::result::Result; + +fn base64_engine() -> impl Engine { + base64::engine::general_purpose::URL_SAFE_NO_PAD +} + +/// A generator of nonces that is a simple incrementing counter. +/// +/// Assumed use with ChaCha20+Poly1305. +#[derive(Default)] +struct RemoteSigningNonceSequence { + id: u32, +} + +impl NonceSequence for RemoteSigningNonceSequence { + fn advance(&mut self) -> ::std::result::Result { + let mut data = [0u8; NONCE_LEN]; + data.pwrite_with(self.id, 0, LE) + .map_err(|_| ring::error::Unspecified)?; + + self.id += 1; + + Ok(Nonce::assume_unique_for_key(data)) + } +} + +/// A nonce sequence that emits a constant value exactly once. +#[derive(Default)] +struct ConstantNonceSequence { + used: bool, +} + +impl NonceSequence for ConstantNonceSequence { + fn advance(&mut self) -> ::std::result::Result { + if self.used { + return Err(ring::error::Unspecified); + } + + self.used = true; + + Ok(Nonce::assume_unique_for_key([0x42; NONCE_LEN])) + } +} + +/// The role being assumed by a peer. +#[derive(Clone, Copy, Debug)] +pub enum Role { + /// Peer who initiated the session. + A, + /// Peer who joined the session. + B, +} + +impl Display for Role { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Self::A => "A", + Self::B => "B", + }) + } +} + +/// Derives the identifier / info value used for HKDF expansion. +fn derive_hkdf_info(role: Role, session_id: &str, extra_identifier: &[u8]) -> Vec { + role.to_string() + .as_bytes() + .iter() + .chain(std::iter::once(&b':')) + .chain(session_id.as_bytes().iter()) + .chain(std::iter::once(&b':')) + .chain(extra_identifier.iter()) + .copied() + .collect::>() +} + +pub struct PeerKeys { + sealing: SealingKey, + opening: OpeningKey, +} + +impl PeerKeys { + /// Encrypt / seal a plaintext message using AEAD. + /// + /// Receives the plaintext message to encrypt. + /// + /// Returns the encrypted ciphertext. + pub fn seal(&mut self, plaintext: &[u8]) -> Result> { + let mut output = plaintext.to_vec(); + self.sealing + .seal_in_place_append_tag(Aad::empty(), &mut output) + .map_err(|_| RemoteSignError::Crypto("AEAD sealing error".into()))?; + + Ok(output) + } + + /// Decrypt / open a ciphertext using AEAD. + /// + /// Receives the ciphertext message to decrypt. + /// + /// Returns the decrypted and verified plaintext. + pub fn open(&mut self, mut ciphertext: Vec) -> Result> { + let plaintext = self + .opening + .open_in_place(Aad::empty(), &mut ciphertext) + .map_err(|_| RemoteSignError::Crypto("failed to decrypt message".into()))?; + + Ok(plaintext.to_vec()) + } +} + +/// Derives a pair of AEAD keys from a shared encryption key. +/// +/// Returns a pair of keys. One key is used for sealing / encrypting and the +/// other for opening / decrypting. +/// +/// `role` is the role that the current peer is playing. The session initiator +/// generally uses `A` and the joiner / signer uses `B`. +/// +/// `shared_key` is a private key that is mutually derived and identical on both +/// peers. The mechanism for obtaining it varies. +/// +/// `session_id` is the server-registered session identifier. +/// +/// `extra_identifier` is an extra value to use when constructing identities for +/// HKDF extraction. +fn derive_aead_keys( + role: Role, + shared_key: Vec, + session_id: &str, + extra_identifier: &[u8], +) -> Result<( + SealingKey, + OpeningKey, +)> { + let salt = Salt::new(HKDF_SHA256, &[]); + let prk = salt.extract(&shared_key); + + let a_identifier = derive_hkdf_info(Role::A, session_id, extra_identifier); + let b_identifier = derive_hkdf_info(Role::B, session_id, extra_identifier); + + let a_info = [a_identifier.as_ref()]; + let b_info = [b_identifier.as_ref()]; + + let a_key = prk + .expand(&a_info, &CHACHA20_POLY1305) + .map_err(|_| RemoteSignError::Crypto("error performing HKDF key derivation".into()))?; + + let b_key = prk + .expand(&b_info, &CHACHA20_POLY1305) + .map_err(|_| RemoteSignError::Crypto("error performing HKDF key derivation".into()))?; + + let (sealing_key, opening_key) = match role { + Role::A => (a_key, b_key), + Role::B => (b_key, a_key), + }; + + let sealing_key = SealingKey::new(sealing_key.into(), RemoteSigningNonceSequence::default()); + let opening_key = OpeningKey::new(opening_key.into(), RemoteSigningNonceSequence::default()); + + Ok((sealing_key, opening_key)) +} + +fn encode_sjs( + scheme: &str, + payload: impl CborEncode<()>, +) -> ::std::result::Result, minicbor::encode::Error> { + let mut encoder = Encoder::new(Vec::::new()); + + { + let encoder = encoder.array(2)?; + encoder.str(scheme)?; + payload.encode(encoder, &mut ())?; + encoder.end()?; + } + + Ok(encoder.into_writer()) +} + +/// Common behaviors for a session join string. +/// +/// Implementations must also implement [Encode], which will emit the CBOR +/// encoding of the instance to an encoder. +pub trait SessionJoinString<'de>: CborDecode<'de, ()> + CborEncode<()> { + /// The scheme / name for this SJS implementation. + /// + /// This is advertised as the first component in the encoded SJS. + fn scheme() -> &'static str; + + /// Obtain the raw bytes constituting the session join string. + fn to_bytes(&self) -> Result> { + encode_sjs(Self::scheme(), self) + .map_err(|e| RemoteSignError::SessionJoinString(format!("CBOR encoding error: {e}"))) + } +} + +struct PublicKeySessionJoinString { + aes_ciphertext: Vec, + public_key: Vec, + message_ciphertext: Vec, +} + +impl<'de, C> CborDecode<'de, C> for PublicKeySessionJoinString { + fn decode( + d: &mut Decoder<'de>, + _ctx: &mut C, + ) -> std::result::Result { + if !matches!(d.array()?, Some(3)) { + return Err(minicbor::decode::Error::message( + "not an array of 3 elements", + )); + } + + let aes_ciphertext = d.bytes()?.to_vec(); + let public_key = d.bytes()?.to_vec(); + let message_ciphertext = d.bytes()?.to_vec(); + + Ok(Self { + aes_ciphertext, + public_key, + message_ciphertext, + }) + } +} + +impl CborEncode for PublicKeySessionJoinString { + fn encode( + &self, + e: &mut Encoder, + _ctx: &mut C, + ) -> ::std::result::Result<(), minicbor::encode::Error> { + e.array(3)?; + e.bytes(&self.aes_ciphertext)?; + e.bytes(&self.public_key)?; + e.bytes(&self.message_ciphertext)?; + e.end()?; + + Ok(()) + } +} + +impl SessionJoinString<'static> for PublicKeySessionJoinString { + fn scheme() -> &'static str { + "publickey0" + } +} + +struct SharedSecretSessionJoinString { + session_id: String, + extra_identifier: Vec, + role_a_init_message: Vec, +} + +impl<'de, C> CborDecode<'de, C> for SharedSecretSessionJoinString { + fn decode( + d: &mut Decoder<'de>, + _ctx: &mut C, + ) -> std::result::Result { + if !matches!(d.array()?, Some(3)) { + return Err(minicbor::decode::Error::message( + "not an array of 3 elements", + )); + } + + let session_id = d.str()?.to_string(); + let extra_identifier = d.bytes()?.to_vec(); + let role_a_init_message = d.bytes()?.to_vec(); + + Ok(Self { + session_id, + extra_identifier, + role_a_init_message, + }) + } +} + +impl CborEncode for SharedSecretSessionJoinString { + fn encode( + &self, + e: &mut Encoder, + _ctx: &mut C, + ) -> ::std::result::Result<(), minicbor::encode::Error> { + e.array(3)?; + e.str(&self.session_id)?; + e.bytes(&self.extra_identifier)?; + e.bytes(&self.role_a_init_message)?; + e.end()?; + + Ok(()) + } +} + +impl SessionJoinString<'static> for SharedSecretSessionJoinString { + fn scheme() -> &'static str { + "sharedsecret0" + } +} + +/// A peer that initiates a remote signing session. +pub trait SessionInitiatePeer { + /// Obtain the session ID to create / use. + fn session_id(&self) -> &str; + + /// Obtain additional session context to store with the server. + /// + /// This context will be sent to the peer when it joins. + fn session_create_context(&self) -> Option>; + + /// Obtain the raw bytes constituting the session join string. + fn session_join_string_bytes(&self) -> Result>; + + /// Obtain the base 64 encoded session join string. + fn session_join_string_base64(&self) -> Result { + Ok(base64_engine().encode(self.session_join_string_bytes()?)) + } + + /// Obtain the PEM encoded session join string. + fn session_join_string_pem(&self) -> Result { + Ok(pem::encode(&pem::Pem::new( + "SESSION JOIN STRING", + self.session_join_string_bytes()?, + ))) + } + + /// Finalize a peer joined session using optional context provided by the peer. + /// + /// Yields encryption keys for this peer. + fn negotiate_session(self: Box, peer_context: Option>) -> Result; +} + +pub enum SessionJoinState { + /// A generic shared secret value. + SharedSecret(Vec), + + /// An entity capable of decrypting messages encrypted by the peer. + PublicKeyDecrypt(Box), +} + +/// A peer that joins sessions in a state before it has spoken to the server. +pub trait SessionJoinPeerPreJoin { + /// Register additional state with the peer. + /// + /// This is used as a generic way to import implementation-specific state that + /// enables the peer join to complete. + fn register_state(&mut self, state: SessionJoinState) -> Result<()>; + + /// Obtain information needed to join to a session. + /// + /// Consumes self because joining should be a one-time operation. + fn join_context(self: Box) -> Result; +} + +pub trait SessionJoinPeerHandshake { + /// Finalize a peer joining session. + /// + /// Yields encryption keys for this peer. + fn negotiate_session(self: Box) -> Result; +} + +/// Holds data needs to enable a joining peer to join a session. +pub struct SessionJoinContext { + /// URL of server to join. + /// + /// If not set, the client default URL is used. + pub server_url: Option, + + /// The session ID to join. + pub session_id: String, + + /// Additional data to relay to the peer to enable it to finalize the session. + pub peer_context: Option>, + + /// Object that will finalize the peer handshake and derive encryption keys. + pub peer_handshake: Box, +} + +#[derive(CborDecode, CborEncode)] +#[cbor(array)] +struct PublicKeySecretMessage { + #[n(0)] + server_url: Option, + + #[n(1)] + session_id: String, + + #[n(2)] + challenge: Vec, + + #[n(3)] + agreement_public: Vec, +} + +pub struct PublicKeyInitiator { + session_id: String, + extra_identifier: Vec, + sjs: PublicKeySessionJoinString, + agreement_private: EphemeralPrivateKey, +} + +impl SessionInitiatePeer for PublicKeyInitiator { + fn session_id(&self) -> &str { + &self.session_id + } + + fn session_create_context(&self) -> Option> { + None + } + + fn session_join_string_bytes(&self) -> Result> { + self.sjs.to_bytes() + } + + fn negotiate_session(self: Box, peer_context: Option>) -> Result { + let public_key = peer_context.ok_or_else(|| { + RemoteSignError::Crypto( + "missing peer public key context in session join message".into(), + ) + })?; + + let public_key = UnparsedPublicKey::new(&X25519, public_key); + + let (sealing, opening) = + agree_ephemeral(self.agreement_private, &public_key, |agreement_key| { + derive_aead_keys( + Role::A, + agreement_key.to_vec(), + &self.session_id, + &self.extra_identifier, + ) + }) + .map_err(|_| RemoteSignError::Crypto("error deriving agreement key".into()))? + .map_err(|_| { + RemoteSignError::Crypto("error deriving AEAD keys from agreement key".into()) + })?; + + Ok(PeerKeys { sealing, opening }) + } +} + +impl PublicKeyInitiator { + /// Create a new initiator using public key agreement. + pub fn new(peer_public_key: impl AsRef<[u8]>, server_url: Option) -> Result { + let spki = SubjectPublicKeyInfoRef::from_der(peer_public_key.as_ref()) + .map_err(|e| RemoteSignError::Crypto(format!("when parsing SPKI data: {e}")))?; + + let session_id = uuid::Uuid::new_v4().to_string(); + + let rng = SystemRandom::new(); + + let mut challenge = [0u8; 32]; + rng.fill(&mut challenge) + .map_err(|_| RemoteSignError::Crypto("failed to generate random data".into()))?; + + let mut aes_key_data = [0u8; 16]; + rng.fill(&mut aes_key_data) + .map_err(|_| RemoteSignError::Crypto("failed to generate random data".into()))?; + + let agreement_private = EphemeralPrivateKey::generate(&X25519, &rng).map_err(|_| { + RemoteSignError::Crypto("failed to generate ephemeral agreement key".into()) + })?; + + let agreement_public = agreement_private.compute_public_key().map_err(|_| { + RemoteSignError::Crypto( + "failed to derive public key from ephemeral agreement key".into(), + ) + })?; + + let peer_message = PublicKeySecretMessage { + server_url, + session_id: session_id.clone(), + challenge: challenge.as_ref().to_vec(), + agreement_public: agreement_public.as_ref().to_vec(), + }; + + // The unique AES key is used to encrypt the main CBOR message. + let mut message_ciphertext = minicbor::to_vec(peer_message) + .map_err(|e| RemoteSignError::Crypto(format!("CBOR encode error: {e}")))?; + let aes_key = UnboundKey::new(&AES_128_GCM, &aes_key_data).map_err(|_| { + RemoteSignError::Crypto("failed to load AES encryption key into ring".into()) + })?; + let mut sealing_key = SealingKey::new(aes_key, ConstantNonceSequence::default()); + sealing_key + .seal_in_place_append_tag(Aad::empty(), &mut message_ciphertext) + .map_err(|_| RemoteSignError::Crypto("failed to AES encrypt message to peer".into()))?; + + // The AES encrypting key is encrypted using asymmetric encryption. + + let aes_ciphertext = match spki.algorithm.oid.as_ref() { + x if x == OID_PKCS1_RSAENCRYPTION.as_bytes() => { + let public_key = RsaPublicKeyAsn1::from_der(spki.subject_public_key.raw_bytes()) + .map_err(|e| { + RemoteSignError::Crypto(format!("when parsing RSA public key: {e}")) + })?; + + let n = BigUint::from_bytes_be(public_key.modulus.as_bytes()); + let e = BigUint::from_bytes_be(public_key.public_exponent.as_bytes()); + + let rsa_public = RsaPublicKey::new(n, e).map_err(|e| { + RemoteSignError::Crypto(format!("when constructing RSA public key: {e}")) + })?; + + let padding = Oaep::new::(); + + rsa_public + .encrypt(&mut rand::thread_rng(), padding, &aes_key_data) + .map_err(|e| { + RemoteSignError::Crypto(format!("RSA public key encryption error: {e}")) + })? + } + _ => { + return Err(RemoteSignError::Crypto(format!( + "do not know how to encrypt for algorithm {}", + spki.algorithm.oid + ))); + } + }; + + let public_key = spki + .to_der() + .map_err(|e| RemoteSignError::Crypto(format!("when encoding SPKI to DER: {e}")))?; + + let sjs = PublicKeySessionJoinString { + aes_ciphertext, + public_key, + message_ciphertext, + }; + + Ok(Self { + session_id, + extra_identifier: challenge.as_ref().to_vec(), + sjs, + agreement_private, + }) + } +} + +/// Describes a type that is capable of decrypting messages used during public key negotiation. +pub trait PublicKeyPeerDecrypt { + /// Decrypt an encrypted message. + fn decrypt(&self, ciphertext: &[u8]) -> Result>; +} + +/// A joining peer using public key encryption. +struct PublicKeyPeerPreJoined { + sjs: PublicKeySessionJoinString, + + decrypter: Option>, +} + +impl SessionJoinPeerPreJoin for PublicKeyPeerPreJoined { + fn register_state(&mut self, state: SessionJoinState) -> Result<()> { + match state { + SessionJoinState::PublicKeyDecrypt(decrypt) => { + self.decrypter = Some(decrypt); + Ok(()) + } + SessionJoinState::SharedSecret(_) => Ok(()), + } + } + + fn join_context(self: Box) -> Result { + let decrypter = self + .decrypter + .ok_or_else(|| RemoteSignError::Crypto("decryption key not registered".into()))?; + + let aes_key = decrypter.decrypt(&self.sjs.aes_ciphertext)?; + let aes_key = UnboundKey::new(&AES_128_GCM, &aes_key).map_err(|_| { + RemoteSignError::Crypto("failed to construct AES key from key data".into()) + })?; + let mut opening_key = OpeningKey::new(aes_key, ConstantNonceSequence::default()); + + let mut cbor_message = self.sjs.message_ciphertext.clone(); + let cbor_plaintext = opening_key + .open_in_place(Aad::empty(), &mut cbor_message) + .map_err(|_| { + RemoteSignError::Crypto("failed to decrypt using shared AES key".into()) + })?; + + // The plaintext is a CBOR encoded message. + let message = minicbor::decode::(cbor_plaintext) + .map_err(|e| RemoteSignError::Crypto(format!("CBOR decode error: {e}")))?; + + let agreement_private = EphemeralPrivateKey::generate(&X25519, &SystemRandom::new()) + .map_err(|_| { + RemoteSignError::Crypto("failed to generate ephemeral agreement key".into()) + })?; + let agreement_public = agreement_private.compute_public_key().map_err(|_| { + RemoteSignError::Crypto( + "failed to derive public key from ephemeral agreement key".into(), + ) + })?; + + let peer_handshake = Box::new(PublicKeyHandshakePeer { + session_id: message.session_id.clone(), + extra_identifier: message.challenge, + agreement_private, + agreement_public: message.agreement_public, + }); + + Ok(SessionJoinContext { + server_url: message.server_url, + session_id: message.session_id, + peer_context: Some(agreement_public.as_ref().to_vec()), + peer_handshake, + }) + } +} + +impl PublicKeyPeerPreJoined { + fn new(sjs: PublicKeySessionJoinString) -> Result { + Ok(Self { + sjs, + decrypter: None, + }) + } +} + +pub struct PublicKeyHandshakePeer { + session_id: String, + extra_identifier: Vec, + agreement_private: EphemeralPrivateKey, + agreement_public: Vec, +} + +impl SessionJoinPeerHandshake for PublicKeyHandshakePeer { + fn negotiate_session(self: Box) -> Result { + let peer_public_key = UnparsedPublicKey::new(&X25519, &self.agreement_public); + + let (sealing, opening) = + agree_ephemeral(self.agreement_private, &peer_public_key, |agreement_key| { + derive_aead_keys( + Role::B, + agreement_key.to_vec(), + &self.session_id, + &self.extra_identifier, + ) + }) + .map_err(|_| RemoteSignError::Crypto("error deriving agreement key".into()))? + .map_err(|_| { + RemoteSignError::Crypto("error deriving AEAD keys from agreement key".into()) + })?; + + Ok(PeerKeys { sealing, opening }) + } +} + +fn spake_identity(role: Role, session_id: &str, extra_identifier: &[u8]) -> Identity { + Identity::new(&derive_hkdf_info(role, session_id, extra_identifier)) +} + +pub struct SharedSecretInitiator { + sjs: SharedSecretSessionJoinString, + spake: Spake2, +} + +impl SessionInitiatePeer for SharedSecretInitiator { + fn session_id(&self) -> &str { + &self.sjs.session_id + } + + fn session_create_context(&self) -> Option> { + None + } + + fn session_join_string_bytes(&self) -> Result> { + self.sjs.to_bytes() + } + + fn negotiate_session(self: Box, peer_context: Option>) -> Result { + let spake_b = peer_context.ok_or_else(|| { + RemoteSignError::Crypto( + "missing SPAKE2 initialization context in session join message".into(), + ) + })?; + + let shared_key = self.spake.finish(&spake_b).map_err(|e| { + RemoteSignError::Crypto(format!("error finishing SPAKE2 key negotiation: {e}")) + })?; + + let (sealing, opening) = derive_aead_keys( + Role::A, + shared_key, + &self.sjs.session_id, + &self.sjs.extra_identifier, + )?; + + Ok(PeerKeys { sealing, opening }) + } +} + +impl SharedSecretInitiator { + pub fn new(shared_secret: Vec) -> Result { + let session_id = uuid::Uuid::new_v4().to_string(); + + let rng = SystemRandom::new(); + let mut extra_identifier = [0u8; 16]; + rng.fill(&mut extra_identifier) + .map_err(|_| RemoteSignError::Crypto("unable to generate random value".into()))?; + + let (spake, role_a_init_message) = Spake2::::start_a( + &Password::new(shared_secret), + &spake_identity(Role::A, &session_id, &extra_identifier), + &spake_identity(Role::B, &session_id, &extra_identifier), + ); + + Ok(Self { + sjs: SharedSecretSessionJoinString { + session_id, + extra_identifier: extra_identifier.as_ref().to_vec(), + role_a_init_message, + }, + spake, + }) + } +} + +/// A joining peer using shared secrets. +struct SharedSecretPeerPreJoined { + sjs: SharedSecretSessionJoinString, + shared_secret: Option>, +} + +impl SessionJoinPeerPreJoin for SharedSecretPeerPreJoined { + fn register_state(&mut self, state: SessionJoinState) -> Result<()> { + match state { + SessionJoinState::SharedSecret(secret) => { + self.shared_secret = Some(secret); + Ok(()) + } + SessionJoinState::PublicKeyDecrypt(_) => Ok(()), + } + } + + fn join_context(self: Box) -> Result { + let shared_secret = self + .shared_secret + .as_ref() + .ok_or_else(|| RemoteSignError::Crypto("shared secret not defined".into()))?; + + let (spake, init_message) = Spake2::::start_b( + &Password::new(shared_secret), + &spake_identity(Role::A, &self.sjs.session_id, &self.sjs.extra_identifier), + &spake_identity(Role::B, &self.sjs.session_id, &self.sjs.extra_identifier), + ); + + let peer_handshake = Box::new(SharedSecretHandshakePeer { + session_id: self.sjs.session_id.clone(), + extra_identifier: self.sjs.extra_identifier, + role_a_init_message: self.sjs.role_a_init_message, + spake, + }); + + Ok(SessionJoinContext { + // TODO set this field if not the default. + server_url: None, + session_id: self.sjs.session_id, + peer_context: Some(init_message), + peer_handshake, + }) + } +} + +impl SharedSecretPeerPreJoined { + fn new(sjs: SharedSecretSessionJoinString) -> Result { + Ok(Self { + sjs, + shared_secret: None, + }) + } +} + +pub struct SharedSecretHandshakePeer { + session_id: String, + extra_identifier: Vec, + role_a_init_message: Vec, + spake: Spake2, +} + +impl SessionJoinPeerHandshake for SharedSecretHandshakePeer { + fn negotiate_session(self: Box) -> Result { + let shared_key = self.spake.finish(&self.role_a_init_message).map_err(|e| { + RemoteSignError::Crypto(format!("error finishing SPAKE2 key negotiation: {e}")) + })?; + + let (sealing, opening) = derive_aead_keys( + Role::B, + shared_key, + &self.session_id, + &self.extra_identifier, + )?; + + Ok(PeerKeys { sealing, opening }) + } +} + +pub fn create_session_joiner( + session_join_string: impl ToString, +) -> Result> { + let input = session_join_string.to_string(); + + let trimmed = input.trim(); + + // Multiline is assumed to be PEM. + let sjs = if trimmed.contains('\n') { + let no_comments = trimmed + .lines() + .filter(|line| !line.starts_with('#')) + .collect::>() + .join("\n"); + + let doc = pem::parse(no_comments.as_bytes())?; + + if doc.tag() == "SESSION JOIN STRING" { + doc.contents().to_vec() + } else { + return Err(RemoteSignError::SessionJoinString( + "PEM does not define a SESSION JOIN STRING".into(), + )); + } + } else { + base64_engine().decode(trimmed.as_bytes())? + }; + + let mut decoder = Decoder::new(&sjs); + if !matches!( + decoder.array().map_err(|_| { + RemoteSignError::SessionJoinString("decode error: not a CBOR array".into()) + })?, + Some(2) + ) { + return Err(RemoteSignError::SessionJoinString( + "decode error: not a CBOR array with 2 elements".into(), + )); + } + + let scheme = decoder + .str() + .map_err(|_| RemoteSignError::SessionJoinString("failed to decode scheme name".into()))?; + + match scheme { + _ if scheme == PublicKeySessionJoinString::scheme() => { + let sjs = PublicKeySessionJoinString::decode(&mut decoder, &mut ()).map_err(|e| { + RemoteSignError::SessionJoinString(format!("error decoding payload: {e}")) + })?; + + Ok(Box::new(PublicKeyPeerPreJoined::new(sjs)?) as Box) + } + _ if scheme == SharedSecretSessionJoinString::scheme() => { + let sjs = + SharedSecretSessionJoinString::decode(&mut decoder, &mut ()).map_err(|e| { + RemoteSignError::SessionJoinString(format!("error decoding payload: {e}")) + })?; + + Ok(Box::new(SharedSecretPeerPreJoined::new(sjs)?) as Box) + } + _ => Err(RemoteSignError::SessionJoinString(format!( + "unknown scheme: {scheme}" + ))), + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/signing.rs b/3rdparty/apple-codesign-0.29.0/src/signing.rs new file mode 100644 index 00000000..6ea511b5 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/signing.rs @@ -0,0 +1,332 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! High level signing primitives. + +use { + crate::{ + bundle_signing::BundleSigner, + dmg::DmgSigner, + error::AppleCodesignError, + macho_signing::{write_macho_file, MachOSigner}, + reader::PathType, + signing_settings::{SettingsScope, SigningSettings}, + }, + apple_xar::{reader::XarReader, signing::XarSigner}, + log::{info, warn}, + std::{fs::File, path::Path}, +}; + +/// An entity for performing signing that is able to handle all supported target types. +pub struct UnifiedSigner<'key> { + settings: SigningSettings<'key>, +} + +impl<'key> UnifiedSigner<'key> { + /// Construct a new instance bound to a [SigningSettings]. + pub fn new(settings: SigningSettings<'key>) -> Self { + Self { settings } + } + + /// Signs `input_path` and writes the signed output to `output_path`. + pub fn sign_path( + &self, + input_path: impl AsRef, + output_path: impl AsRef, + ) -> Result<(), AppleCodesignError> { + let input_path = input_path.as_ref(); + + match PathType::from_path(input_path)? { + PathType::Bundle => self.sign_bundle(input_path, output_path), + PathType::Dmg => self.sign_dmg(input_path, output_path), + PathType::MachO => self.sign_macho(input_path, output_path), + PathType::Xar => self.sign_xar(input_path, output_path), + PathType::Zip | PathType::Other => Err(AppleCodesignError::UnrecognizedPathType), + } + } + + /// Sign a filesystem path in place. + /// + /// This is just a convenience wrapper for [Self::sign_path()] with the same path passed + /// to both the input and output path. + pub fn sign_path_in_place(&self, path: impl AsRef) -> Result<(), AppleCodesignError> { + let path = path.as_ref(); + + self.sign_path(path, path) + } + + /// Sign a Mach-O binary. + pub fn sign_macho( + &self, + input_path: impl AsRef, + output_path: impl AsRef, + ) -> 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()); + + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let mut perms = std::fs::metadata(input_path)?.permissions(); + perms.set_mode(0o755); + std::fs::set_permissions(input_path, perms)?; + } + + 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 = path_identifier(input_path)?; + + 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(()) + } + + /// Sign a `.dmg` file. + pub fn sign_dmg( + &self, + input_path: impl AsRef, + output_path: impl AsRef, + ) -> Result<(), AppleCodesignError> { + let input_path = input_path.as_ref(); + let output_path = output_path.as_ref(); + + warn!("signing {} as a DMG", input_path.display()); + + // There must be a binary identifier on the DMG. So try to derive one + // from the filename if one isn't present in the settings. + let mut settings = self.settings.clone(); + + if settings.binary_identifier(SettingsScope::Main).is_none() { + let file_name = input_path + .file_stem() + .ok_or_else(|| { + AppleCodesignError::CliGeneralError("unable to resolve file name of DMG".into()) + })? + .to_string_lossy(); + + warn!( + "setting binary identifier to {} (derived from file name)", + file_name + ); + settings.set_binary_identifier(SettingsScope::Main, file_name); + } + + // The DMG signer signs in place because it needs a `File` handle. So if + // the output path is different, copy the DMG first. + + // This is not robust same file detection. + if input_path != output_path { + info!( + "copying {} to {} in preparation for signing", + input_path.display(), + output_path.display() + ); + if let Some(parent) = output_path.parent() { + std::fs::create_dir_all(parent)?; + } + + std::fs::copy(input_path, output_path)?; + } + + let signer = DmgSigner::default(); + let mut fh = std::fs::File::options() + .read(true) + .write(true) + .open(output_path)?; + signer.sign_file(&settings, &mut fh)?; + + Ok(()) + } + + /// Sign a bundle. + pub fn sign_bundle( + &self, + input_path: impl AsRef, + output_path: impl AsRef, + ) -> Result<(), AppleCodesignError> { + let input_path = input_path.as_ref(); + warn!("signing bundle at {}", input_path.display()); + + let mut signer = BundleSigner::new_from_path(input_path)?; + signer.collect_nested_bundles()?; + signer.write_signed_bundle(output_path, &self.settings)?; + + Ok(()) + } + + pub fn sign_xar( + &self, + input_path: impl AsRef, + output_path: impl AsRef, + ) -> Result<(), AppleCodesignError> { + let input_path = input_path.as_ref(); + let output_path = output_path.as_ref(); + + // The XAR can get corrupted if we sign into place. So we always go through a temporary + // file. We could potentially avoid the overhead if we're not signing in place... + + let output_path_temp = + output_path.with_file_name(if let Some(file_name) = output_path.file_name() { + file_name.to_string_lossy().to_string() + ".tmp" + } else { + "xar.tmp".to_string() + }); + + warn!( + "signing XAR pkg installer at {} to {}", + input_path.display(), + output_path_temp.display() + ); + + let (signing_key, signing_cert) = self + .settings + .signing_key() + .ok_or(AppleCodesignError::XarNoAdhoc)?; + + { + let reader = XarReader::new(File::open(input_path)?)?; + let mut signer = XarSigner::new(reader); + + let mut fh = File::create(&output_path_temp)?; + signer.sign( + &mut fh, + signing_key, + signing_cert, + self.settings.time_stamp_url(), + self.settings.certificate_chain().iter().cloned(), + )?; + } + + if output_path.exists() { + warn!("removing existing {}", output_path.display()); + std::fs::remove_file(output_path)?; + } + + warn!( + "renaming {} -> {}", + output_path_temp.display(), + output_path.display() + ); + std::fs::rename(&output_path_temp, output_path)?; + + Ok(()) + } +} + +pub fn path_identifier(path: impl AsRef) -> Result { + let path = path.as_ref(); + + // We only care about the file name. + let file_name = path + .file_name() + .ok_or_else(|| { + AppleCodesignError::PathIdentifier(format!("path {} lacks a file name", path.display())) + })? + .to_string_lossy() + .to_string(); + + // Remove the final file extension unless it is numeric. + let id = if let Some((prefix, extension)) = file_name.rsplit_once('.') { + if extension.chars().all(|c| c.is_ascii_digit()) { + file_name.as_str() + } else { + prefix + } + } else { + file_name.as_str() + }; + + let is_digit_or_dot = |c: char| c == '.' || c.is_ascii_digit(); + + // If begins with digit or dot, use as is, handling empty string special + // case. + let id = match id.chars().next() { + Some(first) => { + if is_digit_or_dot(first) { + return Ok(id.to_string()); + } else { + id + } + } + None => { + return Ok(id.to_string()); + } + }; + + // Strip all components having numeric *suffixes* except the first + // one. This doesn't strip extension components but *suffixes*. So + // e.g. libFoo1.2.3 -> libFoo1. Logically, we strip trailing digits + // + dot after the first dot preceded by digits. + + let prefix = id.trim_end_matches(is_digit_or_dot); + let stripped = &id[prefix.len()..]; + + if stripped.is_empty() { + Ok(id.to_string()) + } else { + // If the next character is a dot, add it back in. + let (prefix, stripped) = if matches!(stripped.chars().next(), Some('.')) { + (&id[0..prefix.len() + 1], &stripped[1..]) + } else { + (prefix, stripped) + }; + + // Add back in any leading digits. + + let id = prefix + .chars() + .chain(stripped.chars().take_while(|c| c.is_ascii_digit())) + .collect::(); + + Ok(id) + } +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn path_identifier_normalization() { + assert_eq!(path_identifier("foo").unwrap(), "foo"); + assert_eq!(path_identifier("foo.dylib").unwrap(), "foo"); + assert_eq!(path_identifier("/etc/foo.dylib").unwrap(), "foo"); + assert_eq!(path_identifier("/etc/foo").unwrap(), "foo"); + + // Starts with digit or dot is preserved module final extension. + assert_eq!(path_identifier(".foo").unwrap(), ""); + assert_eq!(path_identifier("123").unwrap(), "123"); + assert_eq!(path_identifier(".foo.dylib").unwrap(), ".foo"); + assert_eq!(path_identifier("123.dylib").unwrap(), "123"); + assert_eq!(path_identifier("123.42").unwrap(), "123.42"); + + // Digit final extension preserved. + + assert_eq!(path_identifier("foo1").unwrap(), "foo1"); + assert_eq!(path_identifier("foo1.dylib").unwrap(), "foo1"); + assert_eq!(path_identifier("foo1.2.dylib").unwrap(), "foo1"); + assert_eq!(path_identifier("foo1.2").unwrap(), "foo1"); + assert_eq!(path_identifier("foo1.2.3.4.dylib").unwrap(), "foo1"); + assert_eq!(path_identifier("foo.1").unwrap(), "foo.1"); + assert_eq!(path_identifier("foo.1.2.3").unwrap(), "foo.1"); + assert_eq!(path_identifier("foo.1.2.dylib").unwrap(), "foo.1"); + assert_eq!(path_identifier("foo.1.dylib").unwrap(), "foo.1"); + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/signing_settings.rs b/3rdparty/apple-codesign-0.29.0/src/signing_settings.rs new file mode 100644 index 00000000..f74ac86c --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/signing_settings.rs @@ -0,0 +1,1697 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Code signing settings. + +use { + crate::{ + certificate::{AppleCertificate, CodeSigningCertificateExtension}, + code_directory::CodeSignatureFlags, + code_requirement::CodeRequirementExpression, + cryptography::DigestType, + embedded_signature::{Blob, RequirementBlob}, + environment_constraints::EncodedEnvironmentConstraints, + error::AppleCodesignError, + macho::{parse_version_nibbles, MachFile}, + }, + glob::Pattern, + goblin::mach::cputype::{ + CpuType, CPU_TYPE_ARM, CPU_TYPE_ARM64, CPU_TYPE_ARM64_32, CPU_TYPE_X86_64, + }, + log::{error, info}, + reqwest::{IntoUrl, Url}, + std::{ + collections::{BTreeMap, BTreeSet}, + fmt::Formatter, + }, + x509_certificate::{CapturedX509Certificate, KeyInfoSigner}, +}; + +/// Denotes the scope for a setting. +/// +/// Settings have an associated scope defined by this type. This allows settings +/// to apply to exactly what you want them to apply to. +/// +/// Scopes can be converted from a string representation. The following syntax is +/// recognized: +/// +/// * `@main` - Maps to [SettingsScope::Main] +/// * `@` - e.g. `@0`. Maps to [SettingsScope::MultiArchIndex].Index +/// * `@[cpu_type=]` - e.g. `@[cpu_type=7]`. Maps to [SettingsScope::MultiArchCpuType]. +/// * `@[cpu_type=]` - e.g. `@[cpu_type=x86_64]`. Maps to [SettingsScope::MultiArchCpuType] +/// for recognized string values (see below). +/// * `` - e.g. `path/to/file`. Maps to [SettingsScope::Path]. +/// * `@` - e.g. `path/to/file@0`. Maps to [SettingsScope::PathMultiArchIndex]. +/// * `@[cpu_type=]` - e.g. `path/to/file@[cpu_type=7]`. Maps to +/// [SettingsScope::PathMultiArchCpuType]. +/// * `@[cpu_type=]` - e.g. `path/to/file@[cpu_type=arm64]`. Maps to +/// [SettingsScope::PathMultiArchCpuType] for recognized string values (see below). +/// +/// # Recognized cpu_type String Values +/// +/// The following `cpu_type=` string values are recognized: +/// +/// * `arm` -> [CPU_TYPE_ARM] +/// * `arm64` -> [CPU_TYPE_ARM64] +/// * `arm64_32` -> [CPU_TYPE_ARM64_32] +/// * `x86_64` -> [CPU_TYPE_X86_64] +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub enum SettingsScope { + // The order of the variants is important. Instance cloning iterates keys in + // sorted order and last write wins. So the order here should be from widest to + // most granular. + /// The main entity being signed. + /// + /// Can be a Mach-O file, a bundle, or any other primitive this crate + /// supports signing. + /// + /// When signing a bundle or any primitive with nested elements (such as a + /// fat/universal Mach-O binary), settings can propagate to nested elements. + Main, + + /// Filesystem path. + /// + /// Can refer to a Mach-O file, a nested bundle, or any other filesystem + /// based primitive that can be traversed into when performing nested signing. + /// + /// The string value refers to the filesystem relative path of the entity + /// relative to the main entity being signed. + Path(String), + + /// A single Mach-O binary within a fat/universal Mach-O binary. + /// + /// The binary to operate on is defined by its 0-based index within the + /// fat/universal Mach-O container. + MultiArchIndex(usize), + + /// A single Mach-O binary within a fat/universal Mach-O binary. + /// + /// The binary to operate on is defined by its CPU architecture. + MultiArchCpuType(CpuType), + + /// Combination of [SettingsScope::Path] and [SettingsScope::MultiArchIndex]. + /// + /// This refers to a single Mach-O binary within a fat/universal binary at a + /// given relative path. + PathMultiArchIndex(String, usize), + + /// Combination of [SettingsScope::Path] and [SettingsScope::MultiArchCpuType]. + /// + /// This refers to a single Mach-O binary within a fat/universal binary at a + /// given relative path. + PathMultiArchCpuType(String, CpuType), +} + +impl std::fmt::Display for SettingsScope { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Main => f.write_str("main signing target"), + Self::Path(path) => f.write_fmt(format_args!("path {path}")), + Self::MultiArchIndex(index) => f.write_fmt(format_args!( + "fat/universal Mach-O binaries at index {index}" + )), + Self::MultiArchCpuType(cpu_type) => f.write_fmt(format_args!( + "fat/universal Mach-O binaries for CPU {cpu_type}" + )), + Self::PathMultiArchIndex(path, index) => f.write_fmt(format_args!( + "fat/universal Mach-O binaries at index {index} under path {path}" + )), + Self::PathMultiArchCpuType(path, cpu_type) => f.write_fmt(format_args!( + "fat/universal Mach-O binaries for CPU {cpu_type} under path {path}" + )), + } + } +} + +impl SettingsScope { + fn parse_at_expr( + at_expr: &str, + ) -> Result<(Option, Option), AppleCodesignError> { + match at_expr.parse::() { + Ok(index) => Ok((Some(index), None)), + Err(_) => { + if at_expr.starts_with('[') && at_expr.ends_with(']') { + let v = &at_expr[1..at_expr.len() - 1]; + let parts = v.split('=').collect::>(); + + if parts.len() == 2 { + let (key, value) = (parts[0], parts[1]); + + if key != "cpu_type" { + return Err(AppleCodesignError::ParseSettingsScope(format!( + "in '@{at_expr}', {key} not recognized; must be cpu_type" + ))); + } + + if let Some(cpu_type) = match value { + "arm" => Some(CPU_TYPE_ARM), + "arm64" => Some(CPU_TYPE_ARM64), + "arm64_32" => Some(CPU_TYPE_ARM64_32), + "x86_64" => Some(CPU_TYPE_X86_64), + _ => None, + } { + return Ok((None, Some(cpu_type))); + } + + match value.parse::() { + Ok(cpu_type) => Ok((None, Some(cpu_type as CpuType))), + Err(_) => Err(AppleCodesignError::ParseSettingsScope(format!( + "in '@{at_expr}', cpu_arch value {value} not recognized" + ))), + } + } else { + Err(AppleCodesignError::ParseSettingsScope(format!( + "'{v}' sub-expression isn't of form =" + ))) + } + } else { + Err(AppleCodesignError::ParseSettingsScope(format!( + "in '{at_expr}', @ expression not recognized" + ))) + } + } + } + } +} + +impl AsRef for SettingsScope { + fn as_ref(&self) -> &SettingsScope { + self + } +} + +impl TryFrom<&str> for SettingsScope { + type Error = AppleCodesignError; + + fn try_from(s: &str) -> Result { + if s == "@main" { + Ok(Self::Main) + } else if let Some(at_expr) = s.strip_prefix('@') { + match Self::parse_at_expr(at_expr)? { + (Some(index), None) => Ok(Self::MultiArchIndex(index)), + (None, Some(cpu_type)) => Ok(Self::MultiArchCpuType(cpu_type)), + _ => panic!("this shouldn't happen"), + } + } else { + // Looks like a path. + let parts = s.rsplitn(2, '@').collect::>(); + + match parts.len() { + 1 => Ok(Self::Path(s.to_string())), + 2 => { + // Parts are reversed since splitting at end. + let (at_expr, path) = (parts[0], parts[1]); + + match Self::parse_at_expr(at_expr)? { + (Some(index), None) => { + Ok(Self::PathMultiArchIndex(path.to_string(), index)) + } + (None, Some(cpu_type)) => { + Ok(Self::PathMultiArchCpuType(path.to_string(), cpu_type)) + } + _ => panic!("this shouldn't happen"), + } + } + _ => panic!("this shouldn't happen"), + } + } + } +} + +/// Describes how to derive designated requirements during signing. +#[derive(Clone, Debug)] +pub enum DesignatedRequirementMode { + /// Automatically attempt to derive an appropriate expression given the + /// code signing certificate and entity being signed. + Auto, + + /// Provide an explicit designated requirement. + Explicit(Vec>), +} + +/// Describes the type of a scoped setting. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ScopedSetting { + Digest, + BinaryIdentifier, + Entitlements, + DesignatedRequirements, + CodeSignatureFlags, + RuntimeVersion, + InfoPlist, + CodeResources, + ExtraDigests, + LaunchConstraintsSelf, + LaunchConstraintsParent, + LaunchConstraintsResponsible, + LibraryConstraints, +} + +impl ScopedSetting { + pub fn all() -> &'static [Self] { + &[ + Self::Digest, + Self::BinaryIdentifier, + Self::Entitlements, + Self::DesignatedRequirements, + Self::CodeSignatureFlags, + Self::RuntimeVersion, + Self::InfoPlist, + Self::CodeResources, + Self::ExtraDigests, + Self::LaunchConstraintsSelf, + Self::LaunchConstraintsParent, + Self::LaunchConstraintsResponsible, + Self::LibraryConstraints, + ] + } + + pub fn inherit_nested_bundle() -> &'static [Self] { + &[Self::Digest, Self::ExtraDigests, Self::RuntimeVersion] + } + + pub fn inherit_nested_macho() -> &'static [Self] { + &[Self::Digest, Self::ExtraDigests, Self::RuntimeVersion] + } +} + +/// Represents code signing settings. +/// +/// This type holds settings related to a single logical signing operation. +/// Some settings (such as the signing key-pair are global). Other settings +/// (such as the entitlements or designated requirement) can be applied on a +/// more granular, scoped basis. The scoping of these lower-level settings is +/// controlled via [SettingsScope]. If a setting is specified with a scope, it +/// only applies to that scope. See that type's documentation for more. +/// +/// An instance of this type is bound to a signing operation. When the +/// signing operation traverses into nested primitives (e.g. when traversing +/// into the individual Mach-O binaries in a fat/universal binary or when +/// traversing into nested bundles or non-main binaries within a bundle), a +/// new instance of this type is transparently constructed by merging global +/// settings with settings for the target scope. This allows granular control +/// over which signing settings apply to which entity and enables a signing +/// operation over a complex primitive to be configured/performed via a single +/// [SigningSettings] and signing operation. +#[derive(Clone, Default)] +pub struct SigningSettings<'key> { + // Global settings. + signing_key: Option<(&'key dyn KeyInfoSigner, CapturedX509Certificate)>, + certificates: Vec, + time_stamp_url: Option, + signing_time: Option>, + path_exclusion_patterns: Vec, + shallow: bool, + for_notarization: bool, + + // Scope-specific settings. + // These are BTreeMap so when we filter the keys, keys with higher precedence come + // last and last write wins. + digest_type: BTreeMap, + team_id: BTreeMap, + identifiers: BTreeMap, + entitlements: BTreeMap, + designated_requirement: BTreeMap, + code_signature_flags: BTreeMap, + runtime_version: BTreeMap, + info_plist_data: BTreeMap>, + code_resources_data: BTreeMap>, + extra_digests: BTreeMap>, + launch_constraints_self: BTreeMap, + launch_constraints_parent: BTreeMap, + launch_constraints_responsible: BTreeMap, + library_constraints: BTreeMap, +} + +impl<'key> SigningSettings<'key> { + /// Obtain the signing key to use. + pub fn signing_key(&self) -> Option<(&'key dyn KeyInfoSigner, &CapturedX509Certificate)> { + self.signing_key.as_ref().map(|(key, cert)| (*key, cert)) + } + + /// Set the signing key-pair for producing a cryptographic signature over code. + /// + /// If this is not called, signing will lack a cryptographic signature and will only + /// contain digests of content. This is known as "ad-hoc" mode. Binaries lacking a + /// cryptographic signature or signed without a key-pair issued/signed by Apple may + /// not run in all environments. + pub fn set_signing_key( + &mut self, + private: &'key dyn KeyInfoSigner, + public: CapturedX509Certificate, + ) { + self.signing_key = Some((private, public)); + } + + /// Obtain the certificate chain. + pub fn certificate_chain(&self) -> &[CapturedX509Certificate] { + &self.certificates + } + + /// Attempt to chain Apple CA certificates from a loaded Apple signed signing key. + /// + /// If you are calling `set_signing_key()`, you probably want to call this immediately + /// afterwards, as it will automatically register Apple CA certificates if you are + /// using an Apple signed code signing certificate. + pub fn chain_apple_certificates(&mut self) -> Option> { + if let Some((_, cert)) = &self.signing_key { + if let Some(chain) = cert.apple_root_certificate_chain() { + // The chain starts with self. + let chain = chain.into_iter().skip(1).collect::>(); + self.certificates.extend(chain.clone()); + Some(chain) + } else { + None + } + } else { + None + } + } + + /// Whether the signing certificate is signed by Apple. + pub fn signing_certificate_apple_signed(&self) -> bool { + if let Some((_, cert)) = &self.signing_key { + cert.chains_to_apple_root_ca() + } else { + false + } + } + + /// Add a parsed certificate to the signing certificate chain. + /// + /// When producing a cryptographic signature (see [SigningSettings::set_signing_key]), + /// information about the signing key-pair is included in the signature. The signing + /// key's public certificate is always included. This function can be used to define + /// additional X.509 public certificates to include. Typically, the signing chain + /// of the signing key-pair up until the root Certificate Authority (CA) is added + /// so clients have access to the full certificate chain for validation purposes. + /// + /// This setting has no effect if [SigningSettings::set_signing_key] is not called. + pub fn chain_certificate(&mut self, cert: CapturedX509Certificate) { + self.certificates.push(cert); + } + + /// Add a DER encoded X.509 public certificate to the signing certificate chain. + /// + /// This is like [Self::chain_certificate] except the certificate data is provided in + /// its binary, DER encoded form. + pub fn chain_certificate_der( + &mut self, + data: impl AsRef<[u8]>, + ) -> Result<(), AppleCodesignError> { + self.chain_certificate(CapturedX509Certificate::from_der(data.as_ref())?); + + Ok(()) + } + + /// Add a PEM encoded X.509 public certificate to the signing certificate chain. + /// + /// This is like [Self::chain_certificate] except the certificate is + /// specified as PEM encoded data. This is a human readable string like + /// `-----BEGIN CERTIFICATE-----` and is a common method for encoding certificate data. + /// (PEM is effectively base64 encoded DER data.) + /// + /// Only a single certificate is read from the PEM data. + pub fn chain_certificate_pem( + &mut self, + data: impl AsRef<[u8]>, + ) -> Result<(), AppleCodesignError> { + self.chain_certificate(CapturedX509Certificate::from_pem(data.as_ref())?); + + Ok(()) + } + + /// Obtain the Time-Stamp Protocol server URL. + pub fn time_stamp_url(&self) -> Option<&Url> { + self.time_stamp_url.as_ref() + } + + /// Set the Time-Stamp Protocol server URL to use to generate a Time-Stamp Token. + /// + /// When set and a signing key-pair is defined, the server will be contacted during + /// signing and a Time-Stamp Token will be embedded in the cryptographic signature. + /// This Time-Stamp Token is a cryptographic proof that someone in possession of + /// the signing key-pair produced the cryptographic signature at a given time. It + /// facilitates validation of the signing time via an independent (presumably trusted) + /// entity. + pub fn set_time_stamp_url(&mut self, url: impl IntoUrl) -> Result<(), AppleCodesignError> { + self.time_stamp_url = Some(url.into_url()?); + + Ok(()) + } + + /// Obtain the signing time to embed in signatures. + /// + /// If None, the current time at the time of signing is used. + pub fn signing_time(&self) -> Option> { + self.signing_time + } + + /// Set the signing time to embed in signatures. + /// + /// If not called, the current time at time of signing will be used. + pub fn set_signing_time(&mut self, time: chrono::DateTime) { + self.signing_time = Some(time); + } + + /// Obtain the team identifier for signed binaries. + pub fn team_id(&self) -> Option<&str> { + self.team_id.get(&SettingsScope::Main).map(|x| x.as_str()) + } + + /// Set the team identifier for signed binaries. + pub fn set_team_id(&mut self, value: impl ToString) { + self.team_id.insert(SettingsScope::Main, value.to_string()); + } + + /// Attempt to set the team ID from the signing certificate. + /// + /// Apple signing certificates have the team ID embedded within the certificate. + /// By calling this method, the team ID embedded within the certificate will + /// be propagated to the code signature. + /// + /// Callers will typically want to call this after registering the signing + /// certificate with [Self::set_signing_key()] but before specifying an explicit + /// team ID via [Self::set_team_id()]. + /// + /// Calling this will replace a registered team IDs if the signing + /// certificate contains a team ID. If no signing certificate is registered or + /// it doesn't contain a team ID, no changes will be made. + /// + /// Returns `Some` if a team ID was set from the signing certificate or `None` + /// otherwise. + pub fn set_team_id_from_signing_certificate(&mut self) -> Option<&str> { + // The team ID is only included for Apple signed certificates. + if !self.signing_certificate_apple_signed() { + None + } else if let Some((_, cert)) = &self.signing_key { + if let Some(team_id) = cert.apple_team_id() { + self.set_team_id(team_id); + Some( + self.team_id + .get(&SettingsScope::Main) + .expect("we just set a team id"), + ) + } else { + None + } + } else { + None + } + } + + /// Whether a given path matches a path exclusion pattern. + pub fn path_exclusion_pattern_matches(&self, path: &str) -> bool { + self.path_exclusion_patterns + .iter() + .any(|pattern| pattern.matches(path)) + } + + /// Add a path to the exclusions list. + pub fn add_path_exclusion(&mut self, v: &str) -> Result<(), AppleCodesignError> { + self.path_exclusion_patterns.push(Pattern::new(v)?); + Ok(()) + } + + /// Whether to perform a shallow, non-nested signing operation. + /// + /// Can mean different things to different entities. For bundle signing, shallow + /// mode means not to recurse into nested bundles. + pub fn shallow(&self) -> bool { + self.shallow + } + + /// Set whether to perform a shallow signing operation. + pub fn set_shallow(&mut self, v: bool) { + self.shallow = v; + } + + /// Whether the signed asset will later be notarized. + /// + /// This serves as a hint to engage additional signing settings that are required + /// for an asset to be successfully notarized by Apple. + pub fn for_notarization(&self) -> bool { + self.for_notarization + } + + /// Set whether to engage notarization compatibility mode. + pub fn set_for_notarization(&mut self, v: bool) { + self.for_notarization = v; + } + + /// Obtain the primary digest type to use. + pub fn digest_type(&self, scope: impl AsRef) -> DigestType { + self.digest_type + .get(scope.as_ref()) + .copied() + .unwrap_or_default() + } + + /// Set the content digest to use. + /// + /// The default is SHA-256. Changing this to SHA-1 can weaken security of digital + /// signatures and may prevent the binary from running in environments that enforce + /// more modern signatures. + pub fn set_digest_type(&mut self, scope: SettingsScope, digest_type: DigestType) { + self.digest_type.insert(scope, digest_type); + } + + /// Obtain the binary identifier string for a given scope. + pub fn binary_identifier(&self, scope: impl AsRef) -> Option<&str> { + self.identifiers.get(scope.as_ref()).map(|s| s.as_str()) + } + + /// Set the binary identifier string for a binary at a path. + /// + /// This only has an effect when signing an individual Mach-O file (use the `None` path) + /// or the non-main executable in a bundle: when signing the main executable in a bundle, + /// the binary's identifier is retrieved from the mandatory `CFBundleIdentifier` value in + /// the bundle's `Info.plist` file. + /// + /// The binary identifier should be a DNS-like name and should uniquely identify the + /// binary. e.g. `com.example.my_program` + pub fn set_binary_identifier(&mut self, scope: SettingsScope, value: impl ToString) { + self.identifiers.insert(scope, value.to_string()); + } + + /// Obtain the entitlements plist as a [plist::Value]. + /// + /// The value should be a [plist::Value::Dictionary] variant. + pub fn entitlements_plist(&self, scope: impl AsRef) -> Option<&plist::Value> { + self.entitlements.get(scope.as_ref()) + } + + /// Obtain the entitlements XML string for a given scope. + pub fn entitlements_xml( + &self, + scope: impl AsRef, + ) -> Result, AppleCodesignError> { + if let Some(value) = self.entitlements_plist(scope) { + let mut buffer = vec![]; + let writer = std::io::Cursor::new(&mut buffer); + value + .to_writer_xml(writer) + .map_err(AppleCodesignError::PlistSerializeXml)?; + + Ok(Some( + String::from_utf8(buffer).expect("plist XML serialization should produce UTF-8"), + )) + } else { + Ok(None) + } + } + + /// Set the entitlements to sign via an XML string. + /// + /// The value should be an XML plist. The value is parsed and stored as + /// a native plist value. + pub fn set_entitlements_xml( + &mut self, + scope: SettingsScope, + value: impl ToString, + ) -> Result<(), AppleCodesignError> { + let cursor = std::io::Cursor::new(value.to_string().into_bytes()); + let value = + plist::Value::from_reader_xml(cursor).map_err(AppleCodesignError::PlistParseXml)?; + + self.entitlements.insert(scope, value); + + Ok(()) + } + + /// Obtain the designated requirements for a given scope. + pub fn designated_requirement( + &self, + scope: impl AsRef, + ) -> &DesignatedRequirementMode { + self.designated_requirement + .get(scope.as_ref()) + .unwrap_or(&DesignatedRequirementMode::Auto) + } + + /// Set the designated requirement for a Mach-O binary given a [CodeRequirementExpression]. + /// + /// The designated requirement (also known as "code requirements") specifies run-time + /// requirements for the binary. e.g. you can stipulate that the binary must be + /// signed by a certificate issued/signed/chained to Apple. The designated requirement + /// is embedded in Mach-O binaries and signed. + pub fn set_designated_requirement_expression( + &mut self, + scope: SettingsScope, + expr: &CodeRequirementExpression, + ) -> Result<(), AppleCodesignError> { + self.designated_requirement.insert( + scope, + DesignatedRequirementMode::Explicit(vec![expr.to_bytes()?]), + ); + + Ok(()) + } + + /// Set the designated requirement expression for a Mach-O binary given serialized bytes. + /// + /// This is like [SigningSettings::set_designated_requirement_expression] except the + /// designated requirement expression is given as serialized bytes. The bytes passed are + /// the value that would be produced by compiling a code requirement expression via + /// `csreq -b`. + pub fn set_designated_requirement_bytes( + &mut self, + scope: SettingsScope, + data: impl AsRef<[u8]>, + ) -> Result<(), AppleCodesignError> { + let blob = RequirementBlob::from_blob_bytes(data.as_ref())?; + + self.designated_requirement.insert( + scope, + DesignatedRequirementMode::Explicit( + blob.parse_expressions()? + .iter() + .map(|x| x.to_bytes()) + .collect::, AppleCodesignError>>()?, + ), + ); + + Ok(()) + } + + /// Set the designated requirement mode to auto, which will attempt to derive requirements + /// automatically. + /// + /// This setting recognizes when code signing is being performed with Apple issued code signing + /// certificates and automatically applies appropriate settings for the certificate being + /// used and the entity being signed. + /// + /// Not all combinations may be supported. If you get an error, you will need to + /// provide your own explicit requirement expression. + pub fn set_auto_designated_requirement(&mut self, scope: SettingsScope) { + self.designated_requirement + .insert(scope, DesignatedRequirementMode::Auto); + } + + /// Obtain the code signature flags for a given scope. + pub fn code_signature_flags( + &self, + scope: impl AsRef, + ) -> Option { + let mut flags = self.code_signature_flags.get(scope.as_ref()).copied(); + + if self.for_notarization { + flags.get_or_insert(CodeSignatureFlags::default()); + + flags.as_mut().map(|flags| { + if !flags.contains(CodeSignatureFlags::RUNTIME) { + info!("adding hardened runtime flag because notarization mode enabled"); + } + + flags.insert(CodeSignatureFlags::RUNTIME); + }); + } + + flags + } + + /// Set code signature flags for signed Mach-O binaries. + /// + /// The incoming flags will replace any already-defined flags. + pub fn set_code_signature_flags(&mut self, scope: SettingsScope, flags: CodeSignatureFlags) { + self.code_signature_flags.insert(scope, flags); + } + + /// Add code signature flags. + /// + /// The incoming flags will be ORd with any existing flags for the path + /// specified. The new flags will be returned. + pub fn add_code_signature_flags( + &mut self, + scope: SettingsScope, + flags: CodeSignatureFlags, + ) -> CodeSignatureFlags { + let existing = self + .code_signature_flags + .get(&scope) + .copied() + .unwrap_or_else(CodeSignatureFlags::empty); + + let new = existing | flags; + + self.code_signature_flags.insert(scope, new); + + new + } + + /// Remove code signature flags. + /// + /// The incoming flags will be removed from any existing flags for the path + /// specified. The new flags will be returned. + pub fn remove_code_signature_flags( + &mut self, + scope: SettingsScope, + flags: CodeSignatureFlags, + ) -> CodeSignatureFlags { + let existing = self + .code_signature_flags + .get(&scope) + .copied() + .unwrap_or_else(CodeSignatureFlags::empty); + + let new = existing - flags; + + self.code_signature_flags.insert(scope, new); + + new + } + + /// Obtain the `Info.plist` data registered to a given scope. + pub fn info_plist_data(&self, scope: impl AsRef) -> Option<&[u8]> { + self.info_plist_data + .get(scope.as_ref()) + .map(|x| x.as_slice()) + } + + /// Obtain the runtime version for a given scope. + /// + /// The runtime version represents an OS version. + pub fn runtime_version(&self, scope: impl AsRef) -> Option<&semver::Version> { + self.runtime_version.get(scope.as_ref()) + } + + /// Set the runtime version to use in the code directory for a given scope. + /// + /// The runtime version corresponds to an OS version. The runtime version is usually + /// derived from the SDK version used to build the binary. + pub fn set_runtime_version(&mut self, scope: SettingsScope, version: semver::Version) { + self.runtime_version.insert(scope, version); + } + + /// Define the `Info.plist` content. + /// + /// Signatures can reference the digest of an external `Info.plist` file in + /// the bundle the binary is located in. + /// + /// This function registers the raw content of that file is so that the + /// content can be digested and the digest can be included in the code directory. + /// + /// The value passed here should be the raw content of the `Info.plist` XML file. + /// + /// When signing bundles, this function is called automatically with the `Info.plist` + /// from the bundle. This function exists for cases where you are signing + /// individual Mach-O binaries and the `Info.plist` cannot be automatically + /// discovered. + pub fn set_info_plist_data(&mut self, scope: SettingsScope, data: Vec) { + self.info_plist_data.insert(scope, data); + } + + /// Obtain the `CodeResources` XML file data registered to a given scope. + pub fn code_resources_data(&self, scope: impl AsRef) -> Option<&[u8]> { + self.code_resources_data + .get(scope.as_ref()) + .map(|x| x.as_slice()) + } + + /// Define the `CodeResources` XML file content for a given scope. + /// + /// Bundles may contain a `CodeResources` XML file which defines additional + /// resource files and binaries outside the bundle's main executable. The code + /// directory of the main executable contains a digest of this file to establish + /// a chain of trust of the content of this XML file. + /// + /// This function defines the content of this external file so that the content + /// can be digested and that digest included in the code directory of the + /// binary being signed. + /// + /// When signing bundles, this function is called automatically with the content + /// of the `CodeResources` XML file, if present. This function exists for cases + /// where you are signing individual Mach-O binaries and the `CodeResources` XML + /// file cannot be automatically discovered. + pub fn set_code_resources_data(&mut self, scope: SettingsScope, data: Vec) { + self.code_resources_data.insert(scope, data); + } + + /// Obtain extra digests to include in signatures. + pub fn extra_digests(&self, scope: impl AsRef) -> Option<&BTreeSet> { + self.extra_digests.get(scope.as_ref()) + } + + /// Register an addition content digest to use in signatures. + /// + /// Extra digests supplement the primary registered digest when the signer supports + /// it. Calling this likely results in an additional code directory being included + /// in embedded signatures. + /// + /// A common use case for this is to have the primary digest contain a legacy + /// digest type (namely SHA-1) but include stronger digests as well. This enables + /// signatures to have compatibility with older operating systems but still be modern. + pub fn add_extra_digest(&mut self, scope: SettingsScope, digest_type: DigestType) { + self.extra_digests + .entry(scope) + .or_default() + .insert(digest_type); + } + + /// Obtain all configured digests for a scope. + pub fn all_digests(&self, scope: SettingsScope) -> Vec { + let mut res = vec![self.digest_type(scope.clone())]; + + if let Some(extra) = self.extra_digests(scope) { + res.extend(extra.iter()); + } + + res + } + + /// Obtain the launch constraints on self. + pub fn launch_constraints_self( + &self, + scope: impl AsRef, + ) -> Option<&EncodedEnvironmentConstraints> { + self.launch_constraints_self.get(scope.as_ref()) + } + + /// Set the launch constraints on the current binary. + pub fn set_launch_constraints_self( + &mut self, + scope: SettingsScope, + constraints: EncodedEnvironmentConstraints, + ) { + self.launch_constraints_self.insert(scope, constraints); + } + + /// Obtain the launch constraints on the parent process. + pub fn launch_constraints_parent( + &self, + scope: impl AsRef, + ) -> Option<&EncodedEnvironmentConstraints> { + self.launch_constraints_parent.get(scope.as_ref()) + } + + /// Set the launch constraints on the parent process. + pub fn set_launch_constraints_parent( + &mut self, + scope: SettingsScope, + constraints: EncodedEnvironmentConstraints, + ) { + self.launch_constraints_parent.insert(scope, constraints); + } + + /// Obtain the launch constraints on the responsible process. + pub fn launch_constraints_responsible( + &self, + scope: impl AsRef, + ) -> Option<&EncodedEnvironmentConstraints> { + self.launch_constraints_responsible.get(scope.as_ref()) + } + + /// Set the launch constraints on the responsible process. + pub fn set_launch_constraints_responsible( + &mut self, + scope: SettingsScope, + constraints: EncodedEnvironmentConstraints, + ) { + self.launch_constraints_responsible + .insert(scope, constraints); + } + + /// Obtain the constraints on loaded libraries. + pub fn library_constraints( + &self, + scope: impl AsRef, + ) -> Option<&EncodedEnvironmentConstraints> { + self.library_constraints.get(scope.as_ref()) + } + + /// Set the constraints on loaded libraries. + pub fn set_library_constraints( + &mut self, + scope: SettingsScope, + constraints: EncodedEnvironmentConstraints, + ) { + self.library_constraints.insert(scope, constraints); + } + + /// Import existing state from Mach-O data. + /// + /// This will synchronize the signing settings with the state in the Mach-O file. + /// + /// If existing settings are explicitly set, they will be honored. Otherwise the state from + /// the Mach-O is imported into the settings. + pub fn import_settings_from_macho(&mut self, data: &[u8]) -> Result<(), AppleCodesignError> { + info!("inferring default signing settings from Mach-O binary"); + + let mut seen_identifier = None; + + for macho in MachFile::parse(data)?.into_iter() { + let index = macho.index.unwrap_or(0); + + let scope_main = SettingsScope::Main; + let scope_index = SettingsScope::MultiArchIndex(index); + let scope_arch = SettingsScope::MultiArchCpuType(macho.macho.header.cputype()); + + // Older operating system versions don't have support for SHA-256 in + // signatures. If the minimum version targeting in the binary doesn't + // support SHA-256, we automatically change the digest targeting settings + // so the binary will be signed correctly. + // + // And to maintain compatibility with Apple's tooling, if no targeting + // settings are present we also opt into SHA-1 + SHA-256. + let need_sha1_sha256 = if let Some(targeting) = macho.find_targeting()? { + let sha256_version = targeting.platform.sha256_digest_support()?; + + if !sha256_version.matches(&targeting.minimum_os_version) { + info!( + "activating SHA-1 digests because minimum OS target {} is not {}", + targeting.minimum_os_version, sha256_version + ); + true + } else { + false + } + } else { + info!("activating SHA-1 digests because no platform targeting in Mach-O"); + true + }; + + if need_sha1_sha256 { + // This logic is a bit wonky. We want SHA-1 to be present on all binaries + // within a fat binary. So if we need SHA-1 mode, we set the setting on the + // main scope and then clear any overrides on fat binary scopes so our + // settings are canonical. + self.set_digest_type(scope_main.clone(), DigestType::Sha1); + self.add_extra_digest(scope_main.clone(), DigestType::Sha256); + self.extra_digests.remove(&scope_arch); + self.extra_digests.remove(&scope_index); + } + + // The Mach-O can have embedded Info.plist data. Use it if available and not + // already defined in settings. + if let Some(info_plist) = macho.embedded_info_plist()? { + if self.info_plist_data(&scope_main).is_some() + || self.info_plist_data(&scope_index).is_some() + || self.info_plist_data(&scope_arch).is_some() + { + info!("using Info.plist data from settings"); + } else { + info!("preserving Info.plist data already present in Mach-O"); + self.set_info_plist_data(scope_index.clone(), info_plist); + } + } + + if let Some(sig) = macho.code_signature()? { + if let Some(cd) = sig.code_directory()? { + if self.binary_identifier(&scope_main).is_some() + || self.binary_identifier(&scope_index).is_some() + || self.binary_identifier(&scope_arch).is_some() + { + info!("using binary identifier from settings"); + } else if let Some(initial_identifier) = &seen_identifier { + // The binary identifier should agree between all Mach-O within a + // universal binary. If we've already seen an identifier, use it + // implicitly. + if initial_identifier != cd.ident.as_ref() { + info!("identifiers within Mach-O do not agree (initial: {initial_identifier}, subsequent: {}); reconciling to {initial_identifier}", + cd.ident); + self.set_binary_identifier(scope_index.clone(), initial_identifier); + } + } else { + info!( + "preserving existing binary identifier in Mach-O ({})", + cd.ident.to_string() + ); + self.set_binary_identifier(scope_index.clone(), cd.ident.to_string()); + seen_identifier = Some(cd.ident.to_string()); + } + + if self.team_id.contains_key(&scope_main) + || self.team_id.contains_key(&scope_index) + || self.team_id.contains_key(&scope_arch) + { + info!("using team ID from settings"); + } else if let Some(team_id) = cd.team_name { + // Team ID is only included when signing with an Apple signed + // certificate. + if self.signing_certificate_apple_signed() { + info!( + "preserving team ID in existing Mach-O signature ({})", + team_id + ); + self.team_id + .insert(scope_index.clone(), team_id.to_string()); + } else { + info!("dropping team ID {} because not signing with an Apple signed certificate", team_id); + } + } + + if self.code_signature_flags(&scope_main).is_some() + || self.code_signature_flags(&scope_index).is_some() + || self.code_signature_flags(&scope_arch).is_some() + { + info!("using code signature flags from settings"); + } else if !cd.flags.is_empty() { + info!( + "preserving code signature flags in existing Mach-O signature ({:?})", + cd.flags + ); + self.set_code_signature_flags(scope_index.clone(), cd.flags); + } + + if self.runtime_version(&scope_main).is_some() + || self.runtime_version(&scope_index).is_some() + || self.runtime_version(&scope_arch).is_some() + { + info!("using runtime version from settings"); + } else if let Some(version) = cd.runtime { + let version = parse_version_nibbles(version); + + info!( + "preserving runtime version in existing Mach-O signature ({})", + version + ); + self.set_runtime_version(scope_index.clone(), version); + } + } + + if let Some(entitlements) = sig.entitlements()? { + if self.entitlements_plist(&scope_main).is_some() + || self.entitlements_plist(&scope_index).is_some() + || self.entitlements_plist(&scope_arch).is_some() + { + info!("using entitlements from settings"); + } else { + info!("preserving existing entitlements in Mach-O"); + self.set_entitlements_xml( + SettingsScope::MultiArchIndex(index), + entitlements.as_str(), + )?; + } + } + + if let Some(constraints) = sig.launch_constraints_self()? { + if self.launch_constraints_self(&scope_main).is_some() + || self.launch_constraints_self(&scope_index).is_some() + || self.launch_constraints_self(&scope_arch).is_some() + { + info!("using self launch constraints from settings"); + } else { + info!("preserving existing self launch constraints in Mach-O"); + self.set_launch_constraints_self( + SettingsScope::MultiArchIndex(index), + constraints.parse_encoded_constraints()?, + ); + } + } + + if let Some(constraints) = sig.launch_constraints_parent()? { + if self.launch_constraints_parent(&scope_main).is_some() + || self.launch_constraints_parent(&scope_index).is_some() + || self.launch_constraints_parent(&scope_arch).is_some() + { + info!("using parent launch constraints from settings"); + } else { + info!("preserving existing parent launch constraints in Mach-O"); + self.set_launch_constraints_parent( + SettingsScope::MultiArchIndex(index), + constraints.parse_encoded_constraints()?, + ); + } + } + + if let Some(constraints) = sig.launch_constraints_responsible()? { + if self.launch_constraints_responsible(&scope_main).is_some() + || self.launch_constraints_responsible(&scope_index).is_some() + || self.launch_constraints_responsible(&scope_arch).is_some() + { + info!("using responsible process launch constraints from settings"); + } else { + info!( + "preserving existing responsible process launch constraints in Mach-O" + ); + self.set_launch_constraints_responsible( + SettingsScope::MultiArchIndex(index), + constraints.parse_encoded_constraints()?, + ); + } + } + + if let Some(constraints) = sig.library_constraints()? { + if self.library_constraints(&scope_main).is_some() + || self.library_constraints(&scope_index).is_some() + || self.library_constraints(&scope_arch).is_some() + { + info!("using library constraints from settings"); + } else { + info!("preserving existing library constraints in Mach-O"); + self.set_library_constraints( + SettingsScope::MultiArchIndex(index), + constraints.parse_encoded_constraints()?, + ); + } + } + } + } + + Ok(()) + } + + /// Convert this instance to settings appropriate for a nested bundle. + #[must_use] + pub fn as_nested_bundle_settings(&self, bundle_path: &str) -> Self { + self.clone_strip_prefix( + bundle_path, + format!("{bundle_path}/"), + ScopedSetting::inherit_nested_bundle(), + ) + } + + /// Obtain the settings for a bundle's main executable. + #[must_use] + pub fn as_bundle_main_executable_settings(&self, path: &str) -> Self { + self.clone_strip_prefix(path, path.to_string(), ScopedSetting::all()) + } + + /// Convert this instance to settings appropriate for a Mach-O binary in a bundle. + /// + /// Only some settings are inherited from the bundle. + #[must_use] + pub fn as_bundle_macho_settings(&self, path: &str) -> Self { + self.clone_strip_prefix( + path, + path.to_string(), + ScopedSetting::inherit_nested_macho(), + ) + } + + /// Convert this instance to settings appropriate for a Mach-O within a universal one. + /// + /// It is assumed the main scope of these settings is already targeted for + /// a Mach-O binary. Any scoped settings for the Mach-O binary index and CPU type + /// will be applied. CPU type settings take precedence over index scoped settings. + #[must_use] + pub fn as_universal_macho_settings(&self, index: usize, cpu_type: CpuType) -> Self { + self.clone_with_filter_map(|_, key| { + if key == SettingsScope::Main + || key == SettingsScope::MultiArchCpuType(cpu_type) + || key == SettingsScope::MultiArchIndex(index) + { + Some(SettingsScope::Main) + } else { + None + } + }) + } + + // Clones this instance, promoting `main_path` to the main scope and stripping + // a prefix from other keys. + fn clone_strip_prefix( + &self, + main_path: &str, + prefix: String, + preserve_settings: &[ScopedSetting], + ) -> Self { + self.clone_with_filter_map(|setting, key| match key { + SettingsScope::Main => { + if preserve_settings.contains(&setting) { + Some(SettingsScope::Main) + } else { + None + } + } + SettingsScope::Path(path) => { + if path == main_path { + Some(SettingsScope::Main) + } else { + path.strip_prefix(&prefix) + .map(|path| SettingsScope::Path(path.to_string())) + } + } + + // Top-level multiarch settings are a bit wonky: it doesn't really + // make much sense for them to propagate across binaries. But we do + // allow it. + SettingsScope::MultiArchIndex(index) => { + if preserve_settings.contains(&setting) { + Some(SettingsScope::MultiArchIndex(index)) + } else { + None + } + } + SettingsScope::MultiArchCpuType(cpu_type) => { + if preserve_settings.contains(&setting) { + Some(SettingsScope::MultiArchCpuType(cpu_type)) + } else { + None + } + } + + SettingsScope::PathMultiArchIndex(path, index) => { + if path == main_path { + Some(SettingsScope::MultiArchIndex(index)) + } else { + path.strip_prefix(&prefix) + .map(|path| SettingsScope::PathMultiArchIndex(path.to_string(), index)) + } + } + SettingsScope::PathMultiArchCpuType(path, cpu_type) => { + if path == main_path { + Some(SettingsScope::MultiArchCpuType(cpu_type)) + } else { + path.strip_prefix(&prefix) + .map(|path| SettingsScope::PathMultiArchCpuType(path.to_string(), cpu_type)) + } + } + }) + } + + fn clone_with_filter_map( + &self, + key_map: impl Fn(ScopedSetting, SettingsScope) -> Option, + ) -> Self { + Self { + signing_key: self.signing_key.clone(), + certificates: self.certificates.clone(), + time_stamp_url: self.time_stamp_url.clone(), + signing_time: self.signing_time, + team_id: self.team_id.clone(), + path_exclusion_patterns: self.path_exclusion_patterns.clone(), + shallow: self.shallow, + for_notarization: self.for_notarization, + digest_type: self + .digest_type + .clone() + .into_iter() + .filter_map(|(key, value)| { + key_map(ScopedSetting::Digest, key).map(|key| (key, value)) + }) + .collect::>(), + identifiers: self + .identifiers + .clone() + .into_iter() + .filter_map(|(key, value)| { + key_map(ScopedSetting::BinaryIdentifier, key).map(|key| (key, value)) + }) + .collect::>(), + entitlements: self + .entitlements + .clone() + .into_iter() + .filter_map(|(key, value)| { + key_map(ScopedSetting::Entitlements, key).map(|key| (key, value)) + }) + .collect::>(), + designated_requirement: self + .designated_requirement + .clone() + .into_iter() + .filter_map(|(key, value)| { + key_map(ScopedSetting::DesignatedRequirements, key).map(|key| (key, value)) + }) + .collect::>(), + code_signature_flags: self + .code_signature_flags + .clone() + .into_iter() + .filter_map(|(key, value)| { + key_map(ScopedSetting::CodeSignatureFlags, key).map(|key| (key, value)) + }) + .collect::>(), + runtime_version: self + .runtime_version + .clone() + .into_iter() + .filter_map(|(key, value)| { + key_map(ScopedSetting::RuntimeVersion, key).map(|key| (key, value)) + }) + .collect::>(), + info_plist_data: self + .info_plist_data + .clone() + .into_iter() + .filter_map(|(key, value)| { + key_map(ScopedSetting::InfoPlist, key).map(|key| (key, value)) + }) + .collect::>(), + code_resources_data: self + .code_resources_data + .clone() + .into_iter() + .filter_map(|(key, value)| { + key_map(ScopedSetting::CodeResources, key).map(|key| (key, value)) + }) + .collect::>(), + extra_digests: self + .extra_digests + .clone() + .into_iter() + .filter_map(|(key, value)| { + key_map(ScopedSetting::ExtraDigests, key).map(|key| (key, value)) + }) + .collect::>(), + launch_constraints_self: self + .launch_constraints_self + .clone() + .into_iter() + .filter_map(|(key, value)| { + key_map(ScopedSetting::LaunchConstraintsSelf, key).map(|key| (key, value)) + }) + .collect::>(), + launch_constraints_parent: self + .launch_constraints_parent + .clone() + .into_iter() + .filter_map(|(key, value)| { + key_map(ScopedSetting::LaunchConstraintsParent, key).map(|key| (key, value)) + }) + .collect::>(), + launch_constraints_responsible: self + .launch_constraints_responsible + .clone() + .into_iter() + .filter_map(|(key, value)| { + key_map(ScopedSetting::LaunchConstraintsResponsible, key) + .map(|key| (key, value)) + }) + .collect::>(), + library_constraints: self + .library_constraints + .clone() + .into_iter() + .filter_map(|(key, value)| { + key_map(ScopedSetting::LibraryConstraints, key).map(|key| (key, value)) + }) + .collect::>(), + } + } + + /// Attempt to validate the settings consistency when the `for notarization` flag is set. + /// + /// On error, logs errors at error level and returns an Err. + pub fn ensure_for_notarization_settings(&self) -> Result<(), AppleCodesignError> { + if !self.for_notarization { + return Ok(()); + } + + let mut have_error = false; + + if let Some((_, cert)) = self.signing_key() { + if !cert.chains_to_apple_root_ca() && !cert.is_test_apple_signed_certificate() { + error!("--for-notarization requires use of an Apple-issued signing certificate; current certificate is not signed by Apple"); + error!("hint: use a signing certificate issued by Apple that is signed by an Apple certificate authority"); + have_error = true; + } + + if !cert.apple_code_signing_extensions().into_iter().any(|e| { + e == CodeSigningCertificateExtension::DeveloperIdApplication + || e == CodeSigningCertificateExtension::DeveloperIdInstaller + || e == CodeSigningCertificateExtension::DeveloperIdKernel {} + }) { + error!("--for-notarization requires use of a Developer ID signing certificate; current certificate doesn't appear to be such a certificate"); + error!("hint: use a `Developer ID Application`, `Developer ID Installer`, or `Developer ID Kernel` certificate"); + have_error = true; + } + + if self.time_stamp_url().is_none() { + error!("--for-notarization requires use of a time-stamp protocol server; none configured"); + have_error = true; + } + } else { + error!("--for-notarization requires use of a Developer ID signing certificate; no signing certificate was provided"); + have_error = true; + } + + if have_error { + Err(AppleCodesignError::ForNotarizationInvalidSettings) + } else { + Ok(()) + } + } +} + +#[cfg(test)] +mod tests { + use {super::*, indoc::indoc}; + + const ENTITLEMENTS_XML: &str = indoc! {r#" + + + + + application-identifier + appid + com.apple.developer.team-identifier + ABCDEF + + + "#}; + + #[test] + fn parse_settings_scope() { + assert_eq!( + SettingsScope::try_from("@main").unwrap(), + SettingsScope::Main + ); + assert_eq!( + SettingsScope::try_from("@0").unwrap(), + SettingsScope::MultiArchIndex(0) + ); + assert_eq!( + SettingsScope::try_from("@42").unwrap(), + SettingsScope::MultiArchIndex(42) + ); + assert_eq!( + SettingsScope::try_from("@[cpu_type=7]").unwrap(), + SettingsScope::MultiArchCpuType(7) + ); + assert_eq!( + SettingsScope::try_from("@[cpu_type=arm]").unwrap(), + SettingsScope::MultiArchCpuType(CPU_TYPE_ARM) + ); + assert_eq!( + SettingsScope::try_from("@[cpu_type=arm64]").unwrap(), + SettingsScope::MultiArchCpuType(CPU_TYPE_ARM64) + ); + assert_eq!( + SettingsScope::try_from("@[cpu_type=arm64_32]").unwrap(), + SettingsScope::MultiArchCpuType(CPU_TYPE_ARM64_32) + ); + assert_eq!( + SettingsScope::try_from("@[cpu_type=x86_64]").unwrap(), + SettingsScope::MultiArchCpuType(CPU_TYPE_X86_64) + ); + assert_eq!( + SettingsScope::try_from("foo/bar").unwrap(), + SettingsScope::Path("foo/bar".into()) + ); + assert_eq!( + SettingsScope::try_from("foo/bar@0").unwrap(), + SettingsScope::PathMultiArchIndex("foo/bar".into(), 0) + ); + assert_eq!( + SettingsScope::try_from("foo/bar@[cpu_type=7]").unwrap(), + SettingsScope::PathMultiArchCpuType("foo/bar".into(), 7_u32) + ); + } + + #[test] + fn as_nested_macho_settings() { + let mut main_settings = SigningSettings::default(); + main_settings.set_binary_identifier(SettingsScope::Main, "ident"); + main_settings + .set_code_signature_flags(SettingsScope::Main, CodeSignatureFlags::FORCE_EXPIRATION); + + main_settings.set_code_signature_flags( + SettingsScope::MultiArchIndex(0), + CodeSignatureFlags::FORCE_HARD, + ); + main_settings.set_code_signature_flags( + SettingsScope::MultiArchCpuType(CPU_TYPE_X86_64), + CodeSignatureFlags::RESTRICT, + ); + main_settings.set_info_plist_data(SettingsScope::MultiArchIndex(0), b"index_0".to_vec()); + main_settings.set_info_plist_data( + SettingsScope::MultiArchCpuType(CPU_TYPE_X86_64), + b"cpu_x86_64".to_vec(), + ); + + let macho_settings = main_settings.as_universal_macho_settings(0, CPU_TYPE_ARM64); + assert_eq!( + macho_settings.binary_identifier(SettingsScope::Main), + Some("ident") + ); + assert_eq!( + macho_settings.code_signature_flags(SettingsScope::Main), + Some(CodeSignatureFlags::FORCE_HARD) + ); + assert_eq!( + macho_settings.info_plist_data(SettingsScope::Main), + Some(b"index_0".as_ref()) + ); + + let macho_settings = main_settings.as_universal_macho_settings(0, CPU_TYPE_X86_64); + assert_eq!( + macho_settings.binary_identifier(SettingsScope::Main), + Some("ident") + ); + assert_eq!( + macho_settings.code_signature_flags(SettingsScope::Main), + Some(CodeSignatureFlags::RESTRICT) + ); + assert_eq!( + macho_settings.info_plist_data(SettingsScope::Main), + Some(b"cpu_x86_64".as_ref()) + ); + } + + #[test] + fn as_bundle_macho_settings() { + let mut main_settings = SigningSettings::default(); + main_settings.set_info_plist_data(SettingsScope::Main, b"main".to_vec()); + main_settings.set_info_plist_data( + SettingsScope::Path("Contents/MacOS/main".into()), + b"main_exe".to_vec(), + ); + main_settings.set_info_plist_data( + SettingsScope::PathMultiArchIndex("Contents/MacOS/main".into(), 0), + b"main_exe_index_0".to_vec(), + ); + main_settings.set_info_plist_data( + SettingsScope::PathMultiArchCpuType("Contents/MacOS/main".into(), CPU_TYPE_X86_64), + b"main_exe_x86_64".to_vec(), + ); + + let macho_settings = main_settings.as_bundle_macho_settings("Contents/MacOS/main"); + assert_eq!( + macho_settings.info_plist_data(SettingsScope::Main), + Some(b"main_exe".as_ref()) + ); + assert_eq!( + macho_settings.info_plist_data, + [ + (SettingsScope::Main, b"main_exe".to_vec()), + ( + SettingsScope::MultiArchIndex(0), + b"main_exe_index_0".to_vec() + ), + ( + SettingsScope::MultiArchCpuType(CPU_TYPE_X86_64), + b"main_exe_x86_64".to_vec() + ), + ] + .iter() + .cloned() + .collect::>>() + ); + } + + #[test] + fn as_nested_bundle_settings() { + let mut main_settings = SigningSettings::default(); + main_settings.set_info_plist_data(SettingsScope::Main, b"main".to_vec()); + main_settings.set_info_plist_data( + SettingsScope::Path("Contents/MacOS/main".into()), + b"main_exe".to_vec(), + ); + main_settings.set_info_plist_data( + SettingsScope::Path("Contents/MacOS/nested.app".into()), + b"bundle".to_vec(), + ); + main_settings.set_info_plist_data( + SettingsScope::PathMultiArchIndex("Contents/MacOS/nested.app".into(), 0), + b"bundle_index_0".to_vec(), + ); + main_settings.set_info_plist_data( + SettingsScope::PathMultiArchCpuType( + "Contents/MacOS/nested.app".into(), + CPU_TYPE_X86_64, + ), + b"bundle_x86_64".to_vec(), + ); + main_settings.set_info_plist_data( + SettingsScope::Path("Contents/MacOS/nested.app/Contents/MacOS/nested".into()), + b"nested_main_exe".to_vec(), + ); + main_settings.set_info_plist_data( + SettingsScope::PathMultiArchIndex( + "Contents/MacOS/nested.app/Contents/MacOS/nested".into(), + 0, + ), + b"nested_main_exe_index_0".to_vec(), + ); + main_settings.set_info_plist_data( + SettingsScope::PathMultiArchCpuType( + "Contents/MacOS/nested.app/Contents/MacOS/nested".into(), + CPU_TYPE_X86_64, + ), + b"nested_main_exe_x86_64".to_vec(), + ); + + let bundle_settings = main_settings.as_nested_bundle_settings("Contents/MacOS/nested.app"); + assert_eq!( + bundle_settings.info_plist_data(SettingsScope::Main), + Some(b"bundle".as_ref()) + ); + assert_eq!( + bundle_settings.info_plist_data(SettingsScope::Path("Contents/MacOS/nested".into())), + Some(b"nested_main_exe".as_ref()) + ); + assert_eq!( + bundle_settings.info_plist_data, + [ + (SettingsScope::Main, b"bundle".to_vec()), + (SettingsScope::MultiArchIndex(0), b"bundle_index_0".to_vec()), + ( + SettingsScope::MultiArchCpuType(CPU_TYPE_X86_64), + b"bundle_x86_64".to_vec() + ), + ( + SettingsScope::Path("Contents/MacOS/nested".into()), + b"nested_main_exe".to_vec() + ), + ( + SettingsScope::PathMultiArchIndex("Contents/MacOS/nested".into(), 0), + b"nested_main_exe_index_0".to_vec() + ), + ( + SettingsScope::PathMultiArchCpuType( + "Contents/MacOS/nested".into(), + CPU_TYPE_X86_64 + ), + b"nested_main_exe_x86_64".to_vec() + ), + ] + .iter() + .cloned() + .collect::>>() + ); + } + + #[test] + fn entitlements_handling() -> Result<(), AppleCodesignError> { + let mut settings = SigningSettings::default(); + settings.set_entitlements_xml(SettingsScope::Main, ENTITLEMENTS_XML)?; + + let s = settings.entitlements_xml(SettingsScope::Main)?; + assert_eq!(s, Some("\n\n\n\n\tapplication-identifier\n\tappid\n\tcom.apple.developer.team-identifier\n\tABCDEF\n\n".into())); + + Ok(()) + } + + #[test] + fn for_notarization_handling() -> Result<(), AppleCodesignError> { + let mut settings = SigningSettings::default(); + settings.set_for_notarization(true); + + assert_eq!( + settings.code_signature_flags(SettingsScope::Main), + Some(CodeSignatureFlags::RUNTIME) + ); + + assert_eq!( + settings + .as_bundle_macho_settings("") + .code_signature_flags(SettingsScope::Main), + Some(CodeSignatureFlags::RUNTIME) + ); + + Ok(()) + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/specification.rs b/3rdparty/apple-codesign-0.29.0/src/specification.rs new file mode 100644 index 00000000..6174cc4d --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/specification.rs @@ -0,0 +1,324 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +/*! Apple code signing technical specifications + +This document outlines how Apple code signing is implemented at a technical +level. + +# High Level Overview + +Mach-O binaries embed an optional binary blob containing code signing +metadata. This binary blob contains content digests of various aspects +of the binary (such as the executable code) as well as an optional +cryptographic signature which effectively attests to the digested +content of the binary. + +At run-time, stored digests are used to help ensure file integrity. + +The cryptographic signature is used to verify the digests haven't +been tampered with as well as to validate trust with the entity that +produced that signature. + +See + +for an additional overview of how code signing works on Apple platforms. + +# The Important Data Structures + +Mach-O is the executable binary format used by Apple platforms. A +Mach-O binary contains (among other things), a series of named *segments* +holding arbitrary data and *load commands* instructing the loader how +to load/execute the binary. + +Code signing data is embedded within the `__LINKEDIT` segment in a Mach-O +binary. An `LC_CODE_SIGNATURE` load command identifies the offsets of +code signing data within `__LINKEDIT`. + +The code signing data within a `__LINKEDIT` segment is itself a collection +of sub-records. A *SuperBlob* header defines the signing data format, the +length of data to follow, and the number of sub-sections, or *Blob* within. +Each *Blob* occupies a defined *slot*. *Slots* are effectively well-known +pieces of signing data. These include a *Code Directory*, *Entitlements*, +and a *Signature*, among others. See the [crate::CodeSigningSlot] +enumeration for the known defined slots. + +Each *Blob* contains its own header magic effectively identifying the +content type within and how bytes should be interpreted. The magic +values are independent of the *slot* type. However, there appears to be +a relationship between the two. For example, the code directory slot +will have header magic identifying the payload as a code directory structure. + +The *Code Directory* blob/slot defines information about the binary +being signed. There are many fields to this data structure. But the most +important ones to understand are the hashes / content digests. The *Code +Directory* contains digests (e.g. SHA-256) of various content in the binary, +such as Mach-O segment data (i.e. the executable code) and other blobs/slots. + +The *Entitlements* blob/slot contains a *plist*. + +Additional file-based resources can also be signed. These are referred to as +*Code Resources*. *Code Resources* are captured in a +`_CodeSignature/CodeResources` XML plist file in the bundle and the digest +of this file is captured by the *Code Directory*. There is a defined +`RESOURCEDIR` slot to hold its digest. However, there is no explicit +magic constant for resources, implying that this data can only be provided +externally and not embedded within the *SuperBlob*. + +The *Signature* blob/slot contains a Cryptographic Message Syntax (CMS) +RFC 5652 defined `SignedData` BER encoded ASN.1 data structure. CMS is +a specification for cryptographically signing arbitrary content. The +`SignedData` structure contains an additional set of *signed attributes* +(think of it as arbitrary extra content to sign), a cryptographic signature +of the signed data, and likely the X.509 certificate of the signer and its +chain of certificate signers. + +# How Signing Works + +Code signing logically consists of the following steps: + +1. Collecting content that needs to be signed/attested/trusted. +2. Computing content digests. +3. Cryptographically signing a message derived from the content digests. +4. Adding signature data to Mach-O binary. + +## Collecting Content + +Embedded code signatures support signing a myriad of data formats. +These include but aren't limited to: + +* The Mach-O data outside the signature data in the `__LINKEDIT` segment. +* Requested entitlements for the binary. +* A code requirement statement / expression. +* Resource files. + +If your binary is already part of a *bundle*, content collection can +occur automatically using heuristics. e.g. the `Contents/Resources` +directory contains additional files whose content should be signed. + +## Computing Content Digests + +Once content has been assembled, a series of digests are computed. + +For the code digests, the Mach-O segments are iterated. The raw segment +data is chunked into *pages* and each hashed separately. This is to allow +code data to be lazily hashed as a page is loaded into the kernel. +(Otherwise you would have to hash often megabytes on process start, which +would add overhead.) + +Code hashes are a bit nuanced. A hash is emitted at segment boundaries. i.e. +hashes don't span across multiple segments. The `__PAGEZERO` segment is +not hashed. The `__LINKEDIT` segment is hashed, but only up to the start +offset of the embedded signature data, if present. + +Other content (such as the entitlements, code requirement statement, and +resource files) are serialized to *Blob* data. The mechanism for this +varies by type. e.g. the entitlements plist is embedded as UTF-8 +data and the code requirement statement is serialized into an expression +tree. The resulting *Blob* is then digested. + +The content digests are then assembled into a *Code Directory* data +structure. Digests of code data are referred to to *code slots* and +digests of other entitles (namely *Blob* data) occupy *special slots*. +The *Code Directory* also contains important other information, such +as describing the hash/digest mechanism used, the page size for code +hashing, and executable limits for the binary. + +The content of the *Code Directory* serialized to a *Blob* is then itself +digested. This value is known as the *code directory hash*. + +## Cryptographic Signing + +A cryptographic signature is produced using the Cryptographic Message +Syntax (CMS) signing mechanism. + +From a high level, CMS takes as inputs: + +* Optional content to sign. +* Optional set of additional attributes (effectively key-value data) to sign. +* A signing key. +* Information about the signing key (including its CA chain). + +From these, CMS will produce a BER encoded ASN.1 blob containing the +cryptographic signature and sufficient metadata to verify it (such +as the signed attributes and information about the signing certificate). + +In CMS speak, the *encapsulated content* being signed is not defined. +However, the `message-digest` signed attribute is the digest of the +*Code Directory* *Blob* data. (This appears to be not compliant with RFC 5652, +which says *encapsulated content* should be present in the *SignedObject* +structure. Omitting the data is likely done to avoid redundant storage +of this data in the Mach-O binary and/or to simplify parsing, as *Code +Directory* data wouldn't be embedded within an ASN.1 stream.) + +In addition, there is a signed attribute for the signing time. There is +also an XML plist defining an array of base64 encoded *Code Directory* +hashes. There are multiple *slots* in a *SuperBlob* for code directories +and the array in the signed XML plist appears to allow hashes of all of +them to be recorded. + +(TODO it isn't clear what the signed content is when there are multiple +*Code Directory* slots in use. Presumably `message-digest` is computed +over all of them.) + +CMS will concatenate the *Code Directory* data with the DER serialized +ASN.1 structures defining the *signed attributes*. This becomes the +*plaintext* message to be signed. + +This *plaintext* message is combined with a private key and cryptographically +signed (likely using RSA). This produces a *signature*. + +CMS then serializes the *signature*, *signed attributes*, signer +certificate info, and other important metadata to a BER encoded ASN.1 +data structure. This raw slice of bytes is referred to as the +*embedded signature*. + +## Adding Signature Data to Mach-O Binary + +The above steps have already materialized several *Blob* data +structures. The individual pieces like the entitlements and code requirement +*Blob* were materialized in order to compute their hashes for the *Code +Directory* data structure. And the *Code Directory* *Blob* was constructed +so it could be signed by CMS. + +The *embedded signature* data produced by CMS is assembled into a *Blob* +structure. At this point, we have all the *Blob* ready. + +All the *Blobs* are assembled together into a *SuperBlob*. The +*SuperBlob* is then written to the `__LINKEDIT` segment of the +Mach-O binary. An appropriate `LC_CODE_SIGNATURE` load command is +also written to the Mach-O binary to instruct where the *SuperBlob* +data resides. + +The `__LINKEDIT` segment is the last segment in the Mach-O binary and +the *SuperBlob* often occupies the final bytes of the `__LINKEDIT` +segment. So in many cases adding code signature data to a Mach-O +requires an optional truncation to remove the existing signature then +file appends for the `__LINKEDIT` data. + +However, insertion or removal of `LC_CODE_SIGNATURE` will require +rewriting the entire file and adjusting offsets in various Mach-O +data structures accordingly. In many cases, an existing code signature +can be replaced by truncating the `__LINKEDIT` section, writing the +replacement data, and updating sizes/offsets in-place in the segments +index and `LC_CODE_SIGNATURE` load command. + +Note that there is a chicken-and-egg problem related to writing the +Mach-O binary and computing the digests of that binary for the *Code +Directory*! The *Code Directory* needs to compute a digest over the +content of the Mach-O file up until the signature data. But this needs +to be done before a CMS signature is produced, as we need to digest +the *Code Directory* for a CMS signed attribute. We also need to know +the size of the CMS signature, as it is part of the signature data +embedded in the Mach-O binary and its size needs to be recorded in +the `LC_CODE_SIGNATURE` load command and segment definitions, which +are hashed by the *Code Directory*. This is a circular dependency. A +trick to working around it is to pad the Mach-O signature data with +extra NULLs and record this extra long value in `LC_CODE_SIGNATURE` +before code digests are computed. The *SuperBlob* parser appears to +be lenient about this solution. Further note that calculating the +exact final length before CMS signature generation may be impossible +due to the CMS signature being non-deterministic (due to the use of +signing times and timestamp servers tokens, which could be variable +length). + +# How Bundle Signing Works + +Signing bundles (e.g. `.app`, `.framework` directories) has its own +complexities beyond signing individual binaries. + +Bundles consist of multiple files, perhaps multiple binaries. These files +can be classified as: + +1. The main executable. +2. The `Info.plist` file. +3. Support/resources files. +4. Code signature files. + +When signing bundles, the high-level process is the following: + +1. Find and sign all nested binaries and bundles (bundles can contain + other bundles) except the main binary and bundle. +2. Identify support/resources files and calculate their hashes, capturing + this metadata in a `CodeResources` XML file. +3. Sign the main binary with an embedded reference to the digest of the + `CodeResources` file. + +# How Verification Works + +What happens when a binary is loaded? Read on to find out. + +Please note that we don't know for sure what all occurs when a binary is +loaded because the code is proprietary. We do have some high-level +documentation from Apple and we can empirically observe what occurs. +We can also infer what is happening based on the signing technical +implementation, assuming Apple follows correct practices. But some content +of this section is speculation and is merely what *likely* occurs. + +When a Mach-O binary is loaded, the loader looks for an +`LC_CODE_SIGNATURE` load command. If not found, there is no embedded +signature data and running the binary may be rejected. + +The associated code signature data is located in the `__LINKEDIT` section +and parsed so *Blob* are discovered. How deeply it is parsed at this stage, +we don't know. + +Data for the *Signature* slot/blob is obtained. This is the CMS *SignedData* +structure (BER encoded ASN.1). This structure is decoded and the cryptographic +signature, signed attributes, and X.509 certificates involved in the signing +are obtained from within. + +We do not know the full extent of trust verification that occurs. But +Apple will examine details of the signing certificate and ensure its use +is allowed. For example, if the signing certificate wasn't issued/signed +by Apple or doesn't have the appropriate extensions present (such as bits +indicating the certificate is appropriate for code signing), it may refuse +to proceed. This trust validation likely occurs immediately after the +CMS data is parsed, as soon as the signing certificate information becomes +available for scrutiny. + +The original *plaintext* message that was signed is assembled. This is +done by DER encoding the *signed attributes* from the CMS *SignedData* +structure. + +This *plaintext* message, the signature of it, and the public key used +to produce the signature are all used to verify the cryptographic integrity +of the *signed attributes*. This effectively answers the question *did +something with possession of certificate X sign exactly the signed attributes +in this message.* + +Successful signature verification ensures that the *signed attributes* +haven't been tampered with since they were signed. + +The CMS data may also contain *unsigned attributes*. There may be +a *time stamp token* here containing a signature of the time when the +signed message was produced. This may be validated as well. + +One of the signed attributes is `message-digest`. In this use of CMS, +`message-digest` is the digest of the *Code Directory* *Blob* data. This +digest is possibly verified: we don't know for sure. According to RFC 5652 +it should be verified. However, it may not need to be because the digest +of the *Code Directory* data is stored elsewhere... + +A signed attribute contains an XML plist containing an array of base64 encoded +hashes of *Code Directory* *blobs*. This plist is likely parsed and the hashes +within are compared to the hashes from the *Code Directory* blobs/slots from +the *SuperBlob* record. If the digests are identical, it means that the *Code +Directory* data structures in the Mach-O binary haven't been modified since the +signature was created. + +The *Code Directory* data structures contain digests of code data and +other *Blob* data from the *SuperBlob*. Since the digest of the *Code Directory* +data was verified via CMS and a trust relationship was (presumably) established +with the signer of that CMS data, verification and trust is transitively applied +to the other *Blob* data and code data (this is effectively a Merkle Tree). +This means that we can digest other *Blob* entries and code data and compare to +the digests within the *Code Directory* structures. If the digests are identical, +content hasn't changed since the signature was made. + +It is unclear in what order other *Blob* data is read. But presumably important +data like the embedded entitlements and code requirement statement are read very +early during binary loading so an appropriate trust policy can be applied to +the binary. +*/ diff --git a/3rdparty/apple-codesign-0.29.0/src/stapling.rs b/3rdparty/apple-codesign-0.29.0/src/stapling.rs new file mode 100644 index 00000000..c5ca0d61 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/stapling.rs @@ -0,0 +1,331 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +/*! Attach Apple notarization tickets to signed entities. + +Stapling refers to the act of taking an Apple issued notarization +ticket (generated after uploading content to Apple for inspection) +and attaching that ticket to the entity that was uploaded. The +mechanism varies, but stapling is literally just fetching a payload +from Apple and attaching it to something else. +*/ + +use { + crate::{ + bundle_signing::SignedMachOInfo, + cryptography::DigestType, + dmg::{DmgReader, DmgSigner}, + embedded_signature::Blob, + reader::PathType, + ticket_lookup::{default_client, lookup_notarization_ticket}, + AppleCodesignError, + }, + apple_bundles::DirectoryBundle, + apple_xar::reader::XarReader, + log::{error, info, warn}, + reqwest::blocking::Client, + scroll::{IOread, IOwrite, Pread, Pwrite, SizeWith}, + std::{ + fmt::Debug, + fs::File, + io::{Read, Seek, SeekFrom, Write}, + path::Path, + }, +}; + +/// Resolve the notarization ticket record name from a bundle. +/// +/// The record name is derived from the digest of the code directory of the +/// main binary within the bundle. +pub fn record_name_from_executable_bundle( + bundle: &DirectoryBundle, +) -> Result { + let main_exe = bundle + .files(false) + .map_err(AppleCodesignError::DirectoryBundle)? + .into_iter() + .find(|file| matches!(file.is_main_executable(), Ok(true))) + .ok_or(AppleCodesignError::StapleMainExecutableNotFound)?; + + // Now extract the code signature so we can resolve the code directory. + info!( + "resolving bundle's record name from {}", + main_exe.absolute_path().display() + ); + let macho_data = std::fs::read(main_exe.absolute_path())?; + + let signed = SignedMachOInfo::parse_data(&macho_data)?; + + let record_name = signed.notarization_ticket_record_name()?; + + Ok(record_name) +} + +/// Staple a ticket to a bundle as defined by the path to a directory. +/// +/// Stapling a bundle (e.g. `MyApp.app`) is literally just writing a +/// `Contents/CodeResources` file containing the raw ticket data. +pub fn staple_ticket_to_bundle( + bundle: &DirectoryBundle, + ticket_data: &[u8], +) -> Result<(), AppleCodesignError> { + let path = bundle.resolve_path("CodeResources"); + + warn!("writing notarization ticket to {}", path.display()); + std::fs::write(&path, ticket_data)?; + + Ok(()) +} + +/// Magic header for xar trailer struct. +/// +/// `t8lr`. +const XAR_NOTARIZATION_TRAILER_MAGIC: [u8; 4] = [0x74, 0x38, 0x6c, 0x72]; + +#[derive(Clone, Copy, Debug, IOread, IOwrite, Pread, Pwrite, SizeWith)] +pub struct XarNotarizationTrailer { + /// "t8lr" + pub magic: [u8; 4], + pub version: u16, + pub typ: u16, + pub length: u32, + pub unused: u32, +} + +#[derive(Clone, Copy, Debug)] +#[repr(u16)] +pub enum XarNotarizationTrailerType { + Invalid = 0, + Terminator = 1, + Ticket = 2, +} + +/// Obtain the notarization trailer data for a XAR archive. +/// +/// The trailer data consists of a [XarNotarizationTrailer] of type `Terminator` +/// to denote the end of XAR content followed by the raw ticket data followed by a +/// [XarNotarizationTrailer] with type `Ticket`. Essentially, a reader can look for +/// a ticket trailer at the end of the file then quickly seek to the beginning of +/// ticket data. +pub fn xar_notarization_trailer(ticket_data: &[u8]) -> Result, AppleCodesignError> { + let terminator = XarNotarizationTrailer { + magic: XAR_NOTARIZATION_TRAILER_MAGIC, + version: 1, + typ: XarNotarizationTrailerType::Terminator as u16, + length: 0, + unused: 0, + }; + let ticket = XarNotarizationTrailer { + magic: XAR_NOTARIZATION_TRAILER_MAGIC, + version: 1, + typ: XarNotarizationTrailerType::Ticket as u16, + length: ticket_data.len() as _, + unused: 0, + }; + + let mut cursor = std::io::Cursor::new(Vec::new()); + cursor.iowrite_with(terminator, scroll::LE)?; + cursor.write_all(ticket_data)?; + cursor.iowrite_with(ticket, scroll::LE)?; + + Ok(cursor.into_inner()) +} + +/// Handles stapling operations. +pub struct Stapler { + client: Client, +} + +impl Stapler { + /// Construct a new instance with defaults. + pub fn new() -> Result { + Ok(Self { + client: default_client()?, + }) + } + + /// Set the HTTP client to use for ticket lookups. + pub fn set_client(&mut self, client: Client) { + self.client = client; + } + + /// Look up a notarization ticket for an app bundle. + /// + /// This will resolve the notarization ticket record name from the contents + /// of the bundle then attempt to look up that notarization ticket against + /// Apple's servers. + /// + /// This errors if there is a problem deriving the notarization ticket record name + /// or if a failure occurs when looking up the notarization ticket. This can include + /// a notarization ticket not existing for the requested record. + pub fn lookup_ticket_for_executable_bundle( + &self, + bundle: &DirectoryBundle, + ) -> Result, AppleCodesignError> { + let record_name = record_name_from_executable_bundle(bundle)?; + + let response = lookup_notarization_ticket(&self.client, &record_name)?; + + let ticket_data = response.signed_ticket(&record_name)?; + + Ok(ticket_data) + } + + /// Attempt to staple a bundle by obtaining a notarization ticket automatically. + pub fn staple_bundle(&self, bundle: &DirectoryBundle) -> Result<(), AppleCodesignError> { + warn!( + "attempting to find notarization ticket for bundle at {}", + bundle.root_dir().display() + ); + let ticket_data = self.lookup_ticket_for_executable_bundle(bundle)?; + staple_ticket_to_bundle(bundle, &ticket_data)?; + + Ok(()) + } + + /// Look up ticket data for DMG file. + pub fn lookup_ticket_for_dmg(&self, dmg: &DmgReader) -> Result, AppleCodesignError> { + // The ticket is derived from the code directory digest from the signature in the + // DMG. + let signature = dmg + .embedded_signature()? + .ok_or(AppleCodesignError::DmgStapleNoSignature)?; + let cd = signature + .code_directory()? + .ok_or(AppleCodesignError::DmgStapleNoSignature)?; + + let mut digest = cd.digest_with(cd.digest_type)?; + digest.truncate(20); + let digest = hex::encode(digest); + + let digest_type: u8 = cd.digest_type.into(); + + let record_name = format!("2/{digest_type}/{digest}"); + + let response = lookup_notarization_ticket(&self.client, &record_name)?; + + response.signed_ticket(&record_name) + } + + /// Attempt to staple a DMG by obtaining a notarization ticket automatically. + pub fn staple_dmg(&self, path: &Path) -> Result<(), AppleCodesignError> { + let mut fh = File::options().read(true).write(true).open(path)?; + + warn!( + "attempting to find notarization ticket for DMG at {}", + path.display() + ); + let reader = DmgReader::new(&mut fh)?; + + let ticket_data = self.lookup_ticket_for_dmg(&reader)?; + warn!("found notarization ticket; proceeding with stapling"); + + let signer = DmgSigner::default(); + signer.staple_file(&mut fh, ticket_data)?; + + Ok(()) + } + + /// Lookup ticket data for a XAR archive (e.g. a `.pkg` file). + pub fn lookup_ticket_for_xar( + &self, + reader: &mut XarReader, + ) -> Result, AppleCodesignError> { + let mut digest = reader.checksum_data()?; + digest.truncate(20); + let digest = hex::encode(digest); + + let digest_type = DigestType::try_from(reader.table_of_contents().checksum.style)?; + let digest_type: u8 = digest_type.into(); + + let record_name = format!("2/{digest_type}/{digest}"); + + let response = lookup_notarization_ticket(&self.client, &record_name)?; + + response.signed_ticket(&record_name) + } + + /// Staple a XAR archive. + /// + /// Takes the handle to a readable, writable, and seekable object. + /// + /// The stream will be opened as a XAR file. If a ticket is found, that ticket + /// will be appended to the end of the file. + pub fn staple_xar( + &self, + mut xar: XarReader, + ) -> Result<(), AppleCodesignError> { + let ticket_data = self.lookup_ticket_for_xar(&mut xar)?; + + warn!("found notarization ticket; proceeding with stapling"); + + let mut fh = xar.into_inner(); + + // As a convenience, we look for an existing ticket trailer so we can tell + // the user we're effectively overwriting it. We could potentially try to + // delete or overwrite the old trailer. BUt it is just easier to append, + // as a writer likely only looks for the ticket trailer at the tail end + // of the file. + let trailer_size = 16; + fh.seek(SeekFrom::End(-trailer_size))?; + + let trailer = fh.ioread_with::(scroll::LE)?; + if trailer.magic == XAR_NOTARIZATION_TRAILER_MAGIC { + let trailer_type = match trailer.typ { + x if x == XarNotarizationTrailerType::Invalid as u16 => "invalid", + x if x == XarNotarizationTrailerType::Ticket as u16 => "ticket", + x if x == XarNotarizationTrailerType::Terminator as u16 => "terminator", + _ => "unknown", + }; + + warn!("found an existing XAR trailer of type {}", trailer_type); + warn!("this existing trailer will be preserved and will likely be ignored"); + } + + let trailer = xar_notarization_trailer(&ticket_data)?; + + warn!( + "stapling notarization ticket trailer ({} bytes) to end of XAR", + trailer.len() + ); + fh.write_all(&trailer)?; + + Ok(()) + } + + /// Attempt to staple an entity at a given filesystem path. + /// + /// The path will be modified on successful stapling operation. + pub fn staple_path(&self, path: impl AsRef) -> Result<(), AppleCodesignError> { + let path = path.as_ref(); + warn!("attempting to staple {}", path.display()); + + match PathType::from_path(path)? { + PathType::MachO => { + error!("cannot staple Mach-O binaries"); + Err(AppleCodesignError::StapleUnsupportedPath( + path.to_path_buf(), + )) + } + PathType::Dmg => { + warn!("activating DMG stapling mode"); + self.staple_dmg(path) + } + PathType::Bundle => { + warn!("activating bundle stapling mode"); + let bundle = DirectoryBundle::new_from_path(path) + .map_err(AppleCodesignError::DirectoryBundle)?; + self.staple_bundle(&bundle) + } + PathType::Xar => { + warn!("activating XAR stapling mode"); + let xar = XarReader::new(File::options().read(true).write(true).open(path)?)?; + self.staple_xar(xar) + } + PathType::Zip | PathType::Other => Err(AppleCodesignError::StapleUnsupportedPath( + path.to_path_buf(), + )), + } + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/apple-signed-3rd-party-mac.cer b/3rdparty/apple-codesign-0.29.0/src/testdata/apple-signed-3rd-party-mac.cer new file mode 100644 index 00000000..fe5bf80c Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/testdata/apple-signed-3rd-party-mac.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/apple-signed-apple-development.cer b/3rdparty/apple-codesign-0.29.0/src/testdata/apple-signed-apple-development.cer new file mode 100644 index 00000000..7c9bdf92 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/testdata/apple-signed-apple-development.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/apple-signed-apple-distribution.cer b/3rdparty/apple-codesign-0.29.0/src/testdata/apple-signed-apple-distribution.cer new file mode 100644 index 00000000..f3a9e317 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/testdata/apple-signed-apple-distribution.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/apple-signed-developer-id-application.cer b/3rdparty/apple-codesign-0.29.0/src/testdata/apple-signed-developer-id-application.cer new file mode 100644 index 00000000..dca1bfc0 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/testdata/apple-signed-developer-id-application.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/apple-signed-developer-id-application.pem b/3rdparty/apple-codesign-0.29.0/src/testdata/apple-signed-developer-id-application.pem new file mode 100644 index 00000000..d15b4ccc --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/testdata/apple-signed-developer-id-application.pem @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIFpjCCBI6gAwIBAgIIfTmR3fnRGfowDQYJKoZIhvcNAQELBQAweTEtMCsGA1UE +AwwkRGV2ZWxvcGVyIElEIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSYwJAYDVQQL +DB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUg +SW5jLjELMAkGA1UEBhMCVVMwHhcNMjEwNDIyMDEwODMyWhcNMjYwNDIzMDEwODMx +WjCBlTEaMBgGCgmSJomT8ixkAQEMCk1LMjJNWlA5ODcxPTA7BgNVBAMMNERldmVs +b3BlciBJRCBBcHBsaWNhdGlvbjogR3JlZ29yeSBTem9yYyAoTUsyMk1aUDk4Nykx +EzARBgNVBAsMCk1LMjJNWlA5ODcxFjAUBgNVBAoMDUdyZWdvcnkgU3pvcmMxCzAJ +BgNVBAYTAlVTMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs52TZuX8 +/9SVXNBr6Vz5CZOmis3lCpRsSP6pKPnIfK46DlOSoob6u/wALiPKOZJOYKnnbHuJ +1pjvFEHif/eJkdfovu82bwAMJnFrbCGBHmOsqfuURfc5cfaIcpred9P0mFUVpu19 +4n74ZR2sjxJIFIMxJXgh7dSE4dKKokf/o5Orlb3d84i1/yY/ePSdnFIMotxrv0lv +uZjdlIZE6ugoElueSyH1ZwF03UqQznJ1uuw1DSRyC0YD2l7paO+CKKpHAvsTSAZc +j4X6qwx+aVgxiYcfl1z6nVDVv1m6+ChAOGyo06KpGPxFeON/Dp704UJyfyrRF7xD +If/Cu+2ftMlLswIDAQABo4ICEzCCAg8wDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAW +gBRXF+2iz9x8mKEQ4Py+hy0s8uMXVDBABggrBgEFBQcBAQQ0MDIwMAYIKwYBBQUH +MAGGJGh0dHA6Ly9vY3NwLmFwcGxlLmNvbS9vY3NwMDMtZGV2aWQwNjCCAR0GA1Ud +IASCARQwggEQMIIBDAYJKoZIhvdjZAUBMIH+MIHDBggrBgEFBQcCAjCBtgyBs1Jl +bGlhbmNlIG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMg +YWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1z +IGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBj +ZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMDYGCCsGAQUFBwIBFipo +dHRwOi8vd3d3LmFwcGxlLmNvbS9jZXJ0aWZpY2F0ZWF1dGhvcml0eS8wFgYDVR0l +AQH/BAwwCgYIKwYBBQUHAwMwHQYDVR0OBBYEFJWxErKAkOUMhUHKIfurpWswr6OM +MA4GA1UdDwEB/wQEAwIHgDAfBgoqhkiG92NkBgEhBBEMDzIwMjEwNDIyMDAwMDAw +WjATBgoqhkiG92NkBgENAQH/BAIFADANBgkqhkiG9w0BAQsFAAOCAQEAGpHZefiX +l5n79MZM8GFVs5oGJOdspORMFa9SxWa59LaBAWpkUbVtgic25CQtaIZddb7vgMpq +uCqQIFiYz3MfdaPgKMqM1MGNVOw14Z4nM1z9CgLctBS7ie2ScKf9nJnbLCm2qCeS +5A13mUagb7lwdzI3Z5G6JP3+ea46Kg0bY9c4TCAZr8v/vpBWktnBimuQ9Rz3PPTT +HPCYuazSBKos0g3gNLgzGdQyZLDvyfyqJ3SvIAvGBYC1SxoGUB8RBeZuYLRQOylA +72DfBd+bt1wASaSNTosSAauo3Sd3cvIwAtlTtWAT3ISZ36ygnbgwfaarz8Q04MDc +4Y74EVg2IvFyLA== +-----END CERTIFICATE----- diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/apple-signed-developer-id-installer.cer b/3rdparty/apple-codesign-0.29.0/src/testdata/apple-signed-developer-id-installer.cer new file mode 100644 index 00000000..6a2de5fc Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/testdata/apple-signed-developer-id-installer.cer differ diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/ed25519.pk8 b/3rdparty/apple-codesign-0.29.0/src/testdata/ed25519.pk8 new file mode 100644 index 00000000..b7ffa380 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/testdata/ed25519.pk8 differ diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/rsa-2048.pk8 b/3rdparty/apple-codesign-0.29.0/src/testdata/rsa-2048.pk8 new file mode 100644 index 00000000..1a1ec9c2 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/testdata/rsa-2048.pk8 differ diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/secp256r1.pk8 b/3rdparty/apple-codesign-0.29.0/src/testdata/secp256r1.pk8 new file mode 100644 index 00000000..a6961708 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/testdata/secp256r1.pk8 differ diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-apple-development.p12 b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-apple-development.p12 new file mode 100644 index 00000000..35a55b2e Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-apple-development.p12 differ diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-apple-development.pem b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-apple-development.pem new file mode 100644 index 00000000..63a5b86e --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-apple-development.pem @@ -0,0 +1,19 @@ +-----BEGIN PRIVATE KEY----- +MFECAQEwBQYDK2VwBCIEIGuXFvz4GG/f9X29/6FpMjDQ9RDghIoEP03sLYZ8lVsI +gSEAflskRT0YQlTWM3izqmpkSzvrb3sTtRm8O2nhm0rTqao= +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIICQTCCAfGgAwIBAgIBATAHBgMrZXAFADCBlDEUMBIGCgmSJomT8ixkAQEMBHRl +c3QxPDA6BgNVBAMMM0FwcGxlIERldmVsb3BtZW50OiBFRDI1NTE5IEFwcGxlIERl +dmVsb3BtZW50ICh0ZXN0KTENMAsGA1UECwwEdGVzdDEiMCAGA1UECgwZRUQyNTUx +OSBBcHBsZSBEZXZlbG9wbWVudDELMAkGA1UEBhMCVVMwHhcNMjMxMTA1MDM1NzE2 +WhcNMjMxMTA2MDM1NzE2WjCBlDEUMBIGCgmSJomT8ixkAQEMBHRlc3QxPDA6BgNV +BAMMM0FwcGxlIERldmVsb3BtZW50OiBFRDI1NTE5IEFwcGxlIERldmVsb3BtZW50 +ICh0ZXN0KTENMAsGA1UECwwEdGVzdDEiMCAGA1UECgwZRUQyNTUxOSBBcHBsZSBE +ZXZlbG9wbWVudDELMAkGA1UEBhMCVVMwLDAHBgMrZW4FAAMhAH5bJEU9GEJU1jN4 +s6pqZEs76297E7UZvDtp4ZtK06mqo2IwYDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB +/wQMMAoGCCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIHgDATBgoqhkiG92NkBgECAQH/ +BAIFADATBgoqhkiG92NkBgEMAQH/BAIFADAHBgMrZXAFAANBAOF5ePNwa4MrpHHP +bGD5B2xJNw24F+skkT7LRQyN6eNWkBvDBFzpk81p5Fj0OL4fcqNj8amfIQ0Tfn48 +E0uu1g4= +-----END CERTIFICATE----- diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-apple-distribution.p12 b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-apple-distribution.p12 new file mode 100644 index 00000000..5c0b0de5 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-apple-distribution.p12 differ diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-apple-distribution.pem b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-apple-distribution.pem new file mode 100644 index 00000000..cb8576ee --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-apple-distribution.pem @@ -0,0 +1,19 @@ +-----BEGIN PRIVATE KEY----- +MFECAQEwBQYDK2VwBCIEIAz5g02pu9pb7fV8NrPFAwIn3MVKrpfR/vXtYLFcpQPU +gSEA9bMDV9wcZ9Q8+RCBUU5sx4i/qIlI1kCSYDiR1FVzV5w= +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIICRzCCAfegAwIBAgIBATAHBgMrZXAFADCBlzEUMBIGCgmSJomT8ixkAQEMBHRl +c3QxPjA8BgNVBAMMNUFwcGxlIERpc3RyaWJ1dGlvbjogRUQyNTUxOSBBcHBsZSBE +aXN0cmlidXRpb24gKHRlc3QpMQ0wCwYDVQQLDAR0ZXN0MSMwIQYDVQQKDBpFRDI1 +NTE5IEFwcGxlIERpc3RyaWJ1dGlvbjELMAkGA1UEBhMCVVMwHhcNMjMxMTA1MDM1 +NjQ5WhcNMjMxMTA2MDM1NjQ5WjCBlzEUMBIGCgmSJomT8ixkAQEMBHRlc3QxPjA8 +BgNVBAMMNUFwcGxlIERpc3RyaWJ1dGlvbjogRUQyNTUxOSBBcHBsZSBEaXN0cmli +dXRpb24gKHRlc3QpMQ0wCwYDVQQLDAR0ZXN0MSMwIQYDVQQKDBpFRDI1NTE5IEFw +cGxlIERpc3RyaWJ1dGlvbjELMAkGA1UEBhMCVVMwLDAHBgMrZW4FAAMhAPWzA1fc +HGfUPPkQgVFObMeIv6iJSNZAkmA4kdRVc1eco2IwYDAMBgNVHRMBAf8EAjAAMBYG +A1UdJQEB/wQMMAoGCCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIHgDATBgoqhkiG92Nk +BgEHAQH/BAIFADATBgoqhkiG92NkBgEEAQH/BAIFADAHBgMrZXAFAANBAMB8826I +1NnbGubUz6vpEFVN0hNXIJ2e6c4tDIll+2McKJWfq5JFIa7xMeAFWqa/zZu1Hioo +qFmgQ0yuVqLhEg8= +-----END CERTIFICATE----- diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-developer-id-application.p12 b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-developer-id-application.p12 new file mode 100644 index 00000000..743543dd Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-developer-id-application.p12 differ diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-developer-id-application.pem b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-developer-id-application.pem new file mode 100644 index 00000000..8be9e2c3 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-developer-id-application.pem @@ -0,0 +1,19 @@ +-----BEGIN PRIVATE KEY----- +MFECAQEwBQYDK2VwBCIEIKXbW6yNNNbWyegF2dAlZRS6lHhn/W9fGHbbD2ER0VkH +gSEA4jK8Um/Ky41zVRrhyqCzaPrdMJyKIQnqiw8RWu02/ro= +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIICVjCCAgagAwIBAgIBATAHBgMrZXAFADCBqTEUMBIGCgmSJomT8ixkAQEMBHRl +c3QxSjBIBgNVBAMMQURldmVsb3BlciBJRCBBcHBsaWNhdGlvbjogRUQyNTUxOSBE +ZXZlbG9wZXIgSUQgQXBwbGljYXRpb24gKHRlc3QpMQ0wCwYDVQQLDAR0ZXN0MSkw +JwYDVQQKDCBFRDI1NTE5IERldmVsb3BlciBJRCBBcHBsaWNhdGlvbjELMAkGA1UE +BhMCVVMwHhcNMjMxMTA1MDM1NzQ5WhcNMjMxMTA2MDM1NzQ5WjCBqTEUMBIGCgmS +JomT8ixkAQEMBHRlc3QxSjBIBgNVBAMMQURldmVsb3BlciBJRCBBcHBsaWNhdGlv +bjogRUQyNTUxOSBEZXZlbG9wZXIgSUQgQXBwbGljYXRpb24gKHRlc3QpMQ0wCwYD +VQQLDAR0ZXN0MSkwJwYDVQQKDCBFRDI1NTE5IERldmVsb3BlciBJRCBBcHBsaWNh +dGlvbjELMAkGA1UEBhMCVVMwLDAHBgMrZW4FAAMhAOIyvFJvysuNc1Ua4cqgs2j6 +3TCciiEJ6osPEVrtNv66o00wSzAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoG +CCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIHgDATBgoqhkiG92NkBgENAQH/BAIFADAH +BgMrZXAFAANBAGjwYupT1F7pZhJT8+1+A00ptDYN1nVItw6xxDAT6u4wa1uLBRQY +lSW2tpbFHr5e4IFv9jogGE1ky+xTpT2+5AE= +-----END CERTIFICATE----- diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-developer-id-installer.p12 b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-developer-id-installer.p12 new file mode 100644 index 00000000..cbfa49b0 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-developer-id-installer.p12 differ diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-developer-id-installer.pem b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-developer-id-installer.pem new file mode 100644 index 00000000..c178be2e --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-developer-id-installer.pem @@ -0,0 +1,19 @@ +-----BEGIN PRIVATE KEY----- +MFECAQEwBQYDK2VwBCIEIN0VFMWe0x0NUaWxDozef9o+C/Uuf6X+6/9XXgCjyWod +gSEA+sV9wFdfmSM2PF9Ko5A5tlXxELEGBUewpBtXoyx27KU= +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIICSzCCAfugAwIBAgIBATAHBgMrZXAFADCBozEUMBIGCgmSJomT8ixkAQEMBHRl +c3QxRjBEBgNVBAMMPURldmVsb3BlciBJRCBJbnN0YWxsZXI6IEVEMjU1MTkgRGV2 +ZWxvcGVyIElEIEluc3RhbGxlciAodGVzdCkxDTALBgNVBAsMBHRlc3QxJzAlBgNV +BAoMHkVEMjU1MTkgRGV2ZWxvcGVyIElEIEluc3RhbGxlcjELMAkGA1UEBhMCVVMw +HhcNMjMxMTA1MDM1ODExWhcNMjMxMTA2MDM1ODExWjCBozEUMBIGCgmSJomT8ixk +AQEMBHRlc3QxRjBEBgNVBAMMPURldmVsb3BlciBJRCBJbnN0YWxsZXI6IEVEMjU1 +MTkgRGV2ZWxvcGVyIElEIEluc3RhbGxlciAodGVzdCkxDTALBgNVBAsMBHRlc3Qx +JzAlBgNVBAoMHkVEMjU1MTkgRGV2ZWxvcGVyIElEIEluc3RhbGxlcjELMAkGA1UE +BhMCVVMwLDAHBgMrZW4FAAMhAPrFfcBXX5kjNjxfSqOQObZV8RCxBgVHsKQbV6Ms +duylo04wTDAMBgNVHRMBAf8EAjAAMBcGA1UdJQEB/wQNMAsGCSqGSIb3Y2QEDTAO +BgNVHQ8BAf8EBAMCB4AwEwYKKoZIhvdjZAYBDgEB/wQCBQAwBwYDK2VwBQADQQCt +WU2uTeONtMraXTtThH8m7O6JU1E31jlYNTkQxgQat3vdWGtA2JbwaK60Ffaxwl6m +/wBdUuTfHsHX+Uuz8HkG +-----END CERTIFICATE----- diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-mac-installer-distribution.p12 b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-mac-installer-distribution.p12 new file mode 100644 index 00000000..813f45bd Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-mac-installer-distribution.p12 differ diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-mac-installer-distribution.pem b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-mac-installer-distribution.pem new file mode 100644 index 00000000..68c16b25 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-mac-installer-distribution.pem @@ -0,0 +1,20 @@ +-----BEGIN PRIVATE KEY----- +MFECAQEwBQYDK2VwBCIEIBSRObN04oDxfLivENEIKVhb+9+OON6JDcBKwRBvAPl5 +gSEApOGf2wgy5BKE/nkY1JeXRCFuEW+/6FP0/ElHkzdlIik= +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIICcTCCAiGgAwIBAgIBATAHBgMrZXAFADCBtjEUMBIGCgmSJomT8ixkAQEMBHRl +c3QxVTBTBgNVBAMMTDNyZCBQYXJ0eSBNYWMgRGV2ZWxvcGVyIEluc3RhbGxlcjog +RUQyNTUxOSBNYWMgSW5zdGFsbGVyIERpc3RyaWJ1dGlvbiAodGVzdCkxDTALBgNV +BAsMBHRlc3QxKzApBgNVBAoMIkVEMjU1MTkgTWFjIEluc3RhbGxlciBEaXN0cmli +dXRpb24xCzAJBgNVBAYTAlVTMB4XDTIzMTEwNTAzNTYyMVoXDTIzMTEwNjAzNTYy +MVowgbYxFDASBgoJkiaJk/IsZAEBDAR0ZXN0MVUwUwYDVQQDDEwzcmQgUGFydHkg +TWFjIERldmVsb3BlciBJbnN0YWxsZXI6IEVEMjU1MTkgTWFjIEluc3RhbGxlciBE +aXN0cmlidXRpb24gKHRlc3QpMQ0wCwYDVQQLDAR0ZXN0MSswKQYDVQQKDCJFRDI1 +NTE5IE1hYyBJbnN0YWxsZXIgRGlzdHJpYnV0aW9uMQswCQYDVQQGEwJVUzAsMAcG +AytlbgUAAyEApOGf2wgy5BKE/nkY1JeXRCFuEW+/6FP0/ElHkzdlIimjTjBMMAwG +A1UdEwEB/wQCMAAwFwYDVR0lAQH/BA0wCwYJKoZIhvdjZAQJMA4GA1UdDwEB/wQE +AwIHgDATBgoqhkiG92NkBgEIAQH/BAIFADAHBgMrZXAFAANBAAgwBfaT1GXZmQGl +2LI9Qpi+rR3Cpsc9EW6KOxLLkiYp28H/GKNZYBCBKS0r/IsfOh7ja5acoEDVEHd1 +Pa+GEw4= +-----END CERTIFICATE----- diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-apple-development.p12 b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-apple-development.p12 new file mode 100644 index 00000000..d58cbe05 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-apple-development.p12 differ diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-apple-development.pem b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-apple-development.pem new file mode 100644 index 00000000..f063a1e5 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-apple-development.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDmyeKhXDIfutBE +LhC+u5gkxjGf2qev18InvaSoHhUrlyiFWjdeNCSfhfQScgemWLwJG0R7tJnx8IhO +7MmBh2MCAPJMTQjlV2JMM6QCGrVShYGpK5ArTUNsMDPMJ/ZyQvPv8XHtYvMW/A8T +jrkn6NBWiuzxH8UZSHYIUJA34TgtgZmMEve1kT3RS/nCaIC8wnDEa4cgI/g73Rui +OVs01oc8pf9wPf+85fVhtVNhpaO9Ab/toZ8gsaa8iarf+2Q9Po+vIylgnhGlh3Mv +hUBRAt5qLNdsL5XJXiLdeveum48jjSxgq60FlbP6nJa1Lpq5XvevRZYRLeb6WGKu +vHqiT+H7AgMBAAECggEASmRHFiZGze2E1oVWxnRnvWrZciKkLM1Ke07o9XwE7PEj +kaCb+lSqfXVLUGrLRnaR3gmZEJsNiGw1M+OlrIf8hRfTAn9OX8bEG7YFptv/GKOK +QQKWzS5xjj0XZTZ4fSpRwUU9qPxdSUpkfbRiwJeOGGddqvfHq7esvE9jvW9ukVP5 +cr+PNgmlVd6IrxECNln2M5nta7QnXHpPh5P1At/dO6fENpz/GRKn+ExCEnlhAnOf +pQ9xzqKdlKnP+AErThCUz/Zn15ZLuB0Mv5YTrPQohJW9SmuT9lvmIZu0CNcgJCF4 +mxLpNTXP24LDPVQNmt4ZbHFIdQmOnwSI3m60RZeOoQKBgQDt5qgqaT+qgWKt/uOk +gBVNqXBGPf8Lrk5X67J7nOYybO+BRqlvHM4BKecVSfoZzweRFQ3kjANDozSkLA1g +OKleAsIiYidYVEx8dIfG+B+xXca9QO5kNsHp407f4YXC2y5GklahggpGmRd/qZ9D +XV0M9y4o8QajMq8zxoF5CEG4CwKBgQD4WLU+0+BM/sruy/Ho7sPv6SMcRCaCwI4+ +SphRElwcTBh1h5FSGyJTpoc2Y3DLmdgUr7NoZKnHjn/nxFYYrUWBW2O1NRdyHfj4 +DkT+SPpge1bSj0sNp5nUTdPBLDAehtuJvH/2+Mb52lUOUZ/oZ6HvofFW68DkpNrn ++phPGFGD0QKBgQCHzniJXXO+vgW7FhqVuZhvsR4quxFxdZu7jQ1ii3rNpmpC/jeS ++nqPJ4CHIqfnO8wyAjbgFR136x8N6Sfpme71f9WbEzUqs1TGZy9rYhGVitb9CqgM +BUZFYkGQhIl7ZuvP1ZImuLls+8/yTL5iElYgJKrxLEaBu1lQ0SzwDsqVaQKBgGEi +SRmew086JONLj32czbQrSplGqo1fhQMmJ/clqDNFLBfkA1nK1R1EuAP01uw7awGE +SzackK9FtA9Rgp86PkI/HXuFnXr78CINarzOjGdqNmY6t49Kq2cXXahjgRqfgoSX +3rEZUrHszHHCSTocNoFEpOFralHDjP9Iy4O8Lj3RAoGAA4u4CSb36THpWZpVKILU +/i8EutBcbZq5LfhvnUKgAmstQHgTq+rqnMfdVp7o1OoxtO+7HPC/NpuxozLB2wC/ +H19AGPZ+ujKNi6SR55RzqWxwSW+sP67nwD07HorLDSuExZt6EjhCZaG3F0EXLBAV +kwgQ9PiQb2otSRlPkpUVNeY= +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIID9zCCAt+gAwIBAgIBATANBgkqhkiG9w0BAQsFADCBjDEUMBIGCgmSJomT8ixk +AQEMBHRlc3QxODA2BgNVBAMML0FwcGxlIERldmVsb3BtZW50OiBSU0EgQXBwbGUg +RGV2ZWxvcG1lbnQgKHRlc3QpMQ0wCwYDVQQLDAR0ZXN0MR4wHAYDVQQKDBVSU0Eg +QXBwbGUgRGV2ZWxvcG1lbnQxCzAJBgNVBAYTAlVTMB4XDTIzMTEwNzEwNDkyOFoX +DTM3MDcxNjEwNDkyOFowgYwxFDASBgoJkiaJk/IsZAEBDAR0ZXN0MTgwNgYDVQQD +DC9BcHBsZSBEZXZlbG9wbWVudDogUlNBIEFwcGxlIERldmVsb3BtZW50ICh0ZXN0 +KTENMAsGA1UECwwEdGVzdDEeMBwGA1UECgwVUlNBIEFwcGxlIERldmVsb3BtZW50 +MQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAObJ +4qFcMh+60EQuEL67mCTGMZ/ap6/Xwie9pKgeFSuXKIVaN140JJ+F9BJyB6ZYvAkb +RHu0mfHwiE7syYGHYwIA8kxNCOVXYkwzpAIatVKFgakrkCtNQ2wwM8wn9nJC8+/x +ce1i8xb8DxOOuSfo0FaK7PEfxRlIdghQkDfhOC2BmYwS97WRPdFL+cJogLzCcMRr +hyAj+DvdG6I5WzTWhzyl/3A9/7zl9WG1U2Glo70Bv+2hnyCxpryJqt/7ZD0+j68j +KWCeEaWHcy+FQFEC3mos12wvlcleIt16966bjyONLGCrrQWVs/qclrUumrle969F +lhEt5vpYYq68eqJP4fsCAwEAAaNiMGAwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8E +DDAKBggrBgEFBQcDAzAOBgNVHQ8BAf8EBAMCB4AwEwYKKoZIhvdjZAYBAgEB/wQC +BQAwEwYKKoZIhvdjZAYBDAEB/wQCBQAwDQYJKoZIhvcNAQELBQADggEBAJlWMwPG +RsYXKLl1jxbFWebhvi6uCITIU1g0BiuB8hyJqUyJwP+yW5O4hlNXSdgelEQPbSV0 +OOB8xwGn5pqoveynHD0vaGNX+ELGKiewRcZxtIfYn9PmlFiq4Z1pJ057LVT39zbi +UZZzgYXKsFzNcbTYoYBhDh93HP7uAZgEdpLsh/06TbrB20/4IF3ddEXWGEsR48pw +GNaklfpLRLwTJf3WgFC0Xd2t86mp6gV1pbbXqdY28FLzs9eXKb9HXvycldtBVPFN +KuWYyw3r1CTg8L/lnxFmjx6A5SQSrj9yK6sCZDmt3Pmge4HNF97HJK+lESjgkgOC +A8E39gLLFUOXwFo= +-----END CERTIFICATE----- diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-apple-distribution.p12 b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-apple-distribution.p12 new file mode 100644 index 00000000..a09ca1a3 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-apple-distribution.p12 differ diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-apple-distribution.pem b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-apple-distribution.pem new file mode 100644 index 00000000..5b20bd3a --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-apple-distribution.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCotqbn6o7PRv+0 +89js9h+ZWgVvsHyAJsOmIxwhKgnnxFTOS/Zv3kgIrLSPy+pyyf5oImlEsVNAnOS3 +YeA5QAp9RKUuHQNRANK6bil+rskFBUgxU6VD2X1lVFwel5UNS6/iOVz5/BPf0HVl +BhE/pr7CZ2cUNnwRDl5W7Nu5zZ/wyUHph+68FJiiAj9hOzbdgARG6uvJx+fDYZbx +9dgLtkfdNGdains8R5VXEEC3GtLloksFzg73Z3IGKdlJ6Jo0niAu/V1RtSdjYY/V +XFxKjiNf0jHjbo6tdumuvJohPUZO/wc6CenNqEf0uhWCiKwJwKNGkaEzuNdahMSo +pdkFBhxHAgMBAAECggEAJBzUpRej8eI0obsAV9hm8yA9waZ5P8UMY+doAgFJlX6E +2JOR8GgX6yNskssHKALsncWf2sBKHa53lnkw0ZBPrnifErvLFe+jK5yg7SjkhlqX +FVfeLCPFn4brIPE5SltFDptQt4Gpj2LDfhhKYOGEO4B+o+j1rYDx2JFihubosVVJ ++lGk95zCY/iWqbMP5pmbsB+mnfCnZNLGfs2T8q1dDdLqwGQNkkGVdDrN0t7a6p3z +psa7H3bNK4JCO9bwXxI9lxGHmQpp3tZxk52I8VAMUsV8u2j6m0E3BJsBJe/p6FOY +u3Vfafavg9met9Q1GP97ueuLfEfrpmZlC4npyp5EQQKBgQDPpr8jTvNVN5pauw9i +WiVzUkZpxIbudZ7LIHQqx23gmoCYyInVjkCP1swhCPcvP84nC+U2HUjcdniJxw8A +UYFKeMX3mkJwXccdin1+kTzqip58nBm5qw6D/07tYnG+Ah8cwc8JrtfEnp1uHFNq +pRjM7bRK2wUq6HzqQCOpmLZMaQKBgQDP/vonTnCTCrm/YQL+5NmcrTDUsmppZmTv +RBw75d+7E2ajg8o6jR8K8VVi8+ltmmzhroiHkQO1tfQSJ80wE/kN5T4Qe3shIvE7 +byqNvYhzZdlGKTm2o3EwN+VI3k9wynmOEqcmgF98pbsVTluP18LVOgAO4r/wN5iL +Bn4laa7NLwKBgQCOi7w4g9EdFc97K2BzNsjwsnEt2EB8X/gDHyM/3ql5/vX6a+fa +1w1Q8LYuk1YEdHuTaGIP1OiYlydGBYUxxcHImsHjqFylgGrYx6JAiXlU1JXZmts6 +DsgnKtNGuEa2lgQ/nHgBAKqUCgKufPlygyVUQHV80X9ppjFiKWeR3AiAyQKBgE3d +MByC2tXREBQ65voxBd4HX95gJEHs2SBRKRirR4QrESNpdM1Sgyp/ie2PTfV/9/7M +bcQCX5co1IPvbnrvHy86gG9/KmsPP6t2REHnkCtTF3GSgU6EBR1971HGF4sr4TF0 +fiqFqDlreYvSV6iTpxZXrinkbOIqjeqNta+fzpZ1AoGBAKBwZucL1L5rBDLKrb5S +SlmXHjnQdj9zMDInyt98Rx9ioLRN4gS5SsN285aYajInFXp5rQmWSRgjE5kDrOyr +HTbO4tdFhRnuvB25KIXfSDRG5MUTFF8WfFmvO8RQUjQEid6YOPZItUT+30q40iD1 +fIxpn1DnmltxBswkDdlr7JI1 +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIID/TCCAuWgAwIBAgIBATANBgkqhkiG9w0BAQsFADCBjzEUMBIGCgmSJomT8ixk +AQEMBHRlc3QxOjA4BgNVBAMMMUFwcGxlIERpc3RyaWJ1dGlvbjogUlNBIEFwcGxl +IERpc3RyaWJ1dGlvbiAodGVzdCkxDTALBgNVBAsMBHRlc3QxHzAdBgNVBAoMFlJT +QSBBcHBsZSBEaXN0cmlidXRpb24xCzAJBgNVBAYTAlVTMB4XDTIzMTEwNzEwNDgz +N1oXDTM3MDcxNjEwNDgzN1owgY8xFDASBgoJkiaJk/IsZAEBDAR0ZXN0MTowOAYD +VQQDDDFBcHBsZSBEaXN0cmlidXRpb246IFJTQSBBcHBsZSBEaXN0cmlidXRpb24g +KHRlc3QpMQ0wCwYDVQQLDAR0ZXN0MR8wHQYDVQQKDBZSU0EgQXBwbGUgRGlzdHJp +YnV0aW9uMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAKi2pufqjs9G/7Tz2Oz2H5laBW+wfIAmw6YjHCEqCefEVM5L9m/eSAistI/L +6nLJ/mgiaUSxU0Cc5Ldh4DlACn1EpS4dA1EA0rpuKX6uyQUFSDFTpUPZfWVUXB6X +lQ1Lr+I5XPn8E9/QdWUGET+mvsJnZxQ2fBEOXlbs27nNn/DJQemH7rwUmKICP2E7 +Nt2ABEbq68nH58NhlvH12Au2R900Z1qKezxHlVcQQLca0uWiSwXODvdncgYp2Uno +mjSeIC79XVG1J2Nhj9VcXEqOI1/SMeNujq126a68miE9Rk7/BzoJ6c2oR/S6FYKI +rAnAo0aRoTO411qExKil2QUGHEcCAwEAAaNiMGAwDAYDVR0TAQH/BAIwADAWBgNV +HSUBAf8EDDAKBggrBgEFBQcDAzAOBgNVHQ8BAf8EBAMCB4AwEwYKKoZIhvdjZAYB +BwEB/wQCBQAwEwYKKoZIhvdjZAYBBAEB/wQCBQAwDQYJKoZIhvcNAQELBQADggEB +AFRh1ZGg2rmbxsAN34LzSjimIkB1DXaY1qGekQps99ydb/Lb+V7pZYMo/D722IID +nbXBklumjVg+f5DupML2vOiz+zWKTBCAT9u9caTbmdvSl49+7TEqbNoj6OwgaWBi +DX+cwDHEtKz/z5EMnt9kG1/WBq6jMpKs2fRg8AwIrZJVU04IM3fAW1kBHKZ83qzE +kPYci/YpNc15sG/cWY+tqwG9QU49dFprt75AhQHJPfZEDeYe4UN/sKCEF8LWgRP1 +YPdfrR1OkSBy2SHUmx14nkOrDLyBbcHuFofytdE52hRPzEBmxS4HB90gLz1z4UqT +Jl4MAwduSrE9qqk6JuvqV1o= +-----END CERTIFICATE----- diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-developer-id-application.p12 b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-developer-id-application.p12 new file mode 100644 index 00000000..6c4fd78a Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-developer-id-application.p12 differ diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-developer-id-application.pem b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-developer-id-application.pem new file mode 100644 index 00000000..5f3294c1 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-developer-id-application.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCkdCzwAgHcNbpH +awCPZISFqL6vPHstX1F9FjjGiOqQZ60xtXMsj1vpfxhpBZwxO/Q3RDn1ogvCluE5 +accwRI4uYJa80i5Dre+3znMFjkC2dJhaiG8NdsGZ2sVvlrfBMVHgayQSxNer0eIl +A/NmnACLbYrfVfp4umd08QfZZLzSCIQi01JYgGXDLK9qqgU09Y/t8Wm+LdXgKCjG +f30WlABULQR2WelMUk9S3PQHItZANYqNSOBCA3WOn532Eqq04bWcXPyLrvGgYrYH +Gh6GdpAQIL+pL2ECZWegHvHdoWyFuuDbvvhV/VebaHIiIw5g1KF/X4Y9RvjYdaWM +SjMNFNQfAgMBAAECggEAW16gyQiUeADx6lQtD35NkuVabIox9deLsu/xw3y2tlyk +AYZK42sTKfwPV+piCYjB+yLRAQRzfD3QDNIUdWDhjirkFSzBv6CYG4t8pI2Qrs7B +ByveZ6CfmBfQslsO5Su9ze45MKRlH3WRK/ex+EScmNrX2ZYvf3wxluuD7Ojnb9mf +ND7Di6Sn5LTOsuuyTQP/PN1I23xg8QLf2YvDyS2zWx+gwdJlDPcRV3uIBlpoylyB +sF+CpDgL0I8GpclLS31JyHoUlZ+e7wQ5aj1ZYX8wQl6f41dXHu5m5gExBU1u/fpE +54MFo+QlB7uy+qutgVlax8UWRExQ4dkIhdq5M3VUYQKBgQDEEwJuX6ahqyCbqsXU +sxA2QobaDJ12+NJQ+dHXo4acgpPBm6e6j7hiUyIZDcD3yi2WJPTsk+wGHke+eo9n +ZOkI6goZr5El1iPeTE5rnOIYdgpzgVpOQ2b26AnKXsgKeZwnmf8ugtHIwVZy3yBn +pX4MYLFQC+5XgCeolwmy78k2tQKBgQDWtyjjOVWX0cONm1CtiRRByZaPocdKiHqm +PpsMQk4uPUmI0Cfwlxzux+aBTloHX7+1GO3uFea6Czj4yX9DDcRF88tMtyI8SjzC +MtTly1gaj7e7wqcTUheueTMq/kiT4n1dMvULYuRmv/enwXWAs+v/N+9/f6ZEuNYH +UKCoguJwAwKBgQCuqKxSu5vIiZLbd+0QAo4fd3V+iRw/nXhjr5Xwe/duNZb/MPPh +aSL7W0iVfr31PMEM7VDL6RynepO4Jp7VoHtBeJGUveMTDEUZQWndzHtPBN9ccs6J +xtrSeHI4NeQGCLxEPpakzN2o8iha3U2VZkL5LazlPCuNAFjTge+e2KCpvQKBgFdt +OtPSm5x2x/ZX4HDYmQv0hj6zs88QZUhdw4opUWYYhGGVyD15eklr0dqiyZupDAk0 +PmUsO8dTHH6IpS3rZBjLnOL+yozb+YNlaTSsKJKgJELqjlcanRPou8HsyiaVGVCi +mA5r1O5VigSfjDW8jQJdh0JV+qCO1m8iEFis+oB7AoGAXYeCuNJXMOpL/Lm+29tD +n0jbbnM9eVnomjzc67zCxwAQ6BsyuxAPoZ++4e+2jTDX0kDYa2AF8h00o8x0b+dR +VavZLjYNM8lZ6uTATsZH/fTjMhw5mWGltaIJnrwldz7nzmwpXkzvNqqs1k2qb64S +UhDxvyDeOLaOchP3itAAV9s= +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIEDDCCAvSgAwIBAgIBATANBgkqhkiG9w0BAQsFADCBoTEUMBIGCgmSJomT8ixk +AQEMBHRlc3QxRjBEBgNVBAMMPURldmVsb3BlciBJRCBBcHBsaWNhdGlvbjogUlNB +IERldmVsb3BlciBJRCBBcHBsaWNhdGlvbiAodGVzdCkxDTALBgNVBAsMBHRlc3Qx +JTAjBgNVBAoMHFJTQSBEZXZlbG9wZXIgSUQgQXBwbGljYXRpb24xCzAJBgNVBAYT +AlVTMB4XDTIzMTEwNzEwNTAzM1oXDTM3MDcxNjEwNTAzM1owgaExFDASBgoJkiaJ +k/IsZAEBDAR0ZXN0MUYwRAYDVQQDDD1EZXZlbG9wZXIgSUQgQXBwbGljYXRpb246 +IFJTQSBEZXZlbG9wZXIgSUQgQXBwbGljYXRpb24gKHRlc3QpMQ0wCwYDVQQLDAR0 +ZXN0MSUwIwYDVQQKDBxSU0EgRGV2ZWxvcGVyIElEIEFwcGxpY2F0aW9uMQswCQYD +VQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKR0LPACAdw1 +ukdrAI9khIWovq88ey1fUX0WOMaI6pBnrTG1cyyPW+l/GGkFnDE79DdEOfWiC8KW +4TlpxzBEji5glrzSLkOt77fOcwWOQLZ0mFqIbw12wZnaxW+Wt8ExUeBrJBLE16vR +4iUD82acAIttit9V+ni6Z3TxB9lkvNIIhCLTUliAZcMsr2qqBTT1j+3xab4t1eAo +KMZ/fRaUAFQtBHZZ6UxST1Lc9Aci1kA1io1I4EIDdY6fnfYSqrThtZxc/Iuu8aBi +tgcaHoZ2kBAgv6kvYQJlZ6Ae8d2hbIW64Nu++FX9V5tociIjDmDUoX9fhj1G+Nh1 +pYxKMw0U1B8CAwEAAaNNMEswDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggr +BgEFBQcDAzAOBgNVHQ8BAf8EBAMCB4AwEwYKKoZIhvdjZAYBDQEB/wQCBQAwDQYJ +KoZIhvcNAQELBQADggEBAH4q6KQrqpdColHYFzw/v0CJ2j5ENwOuObw3xg+Q9ZJJ +H2DskOsuZZeeMPpuasLwSk0Z5lWnzf7FRKNzmgb8PjTW+egPvGaulOEZ+p7vwYJH +uN8KJhRpGK1IU0QrpjmM9b7+zXumxfAYW9BYane2f11FzoAXM/4rBzIXa0EzDlyZ +3vna93Q6qyRuvh6h9PLbrbqxsd8yeyj0TaXQ/s21NymUB4kTtXcBaBi2kg1UTS9b +7v7L+TgsfZ0Z1TCKYEfIbdwTTRguFbJxOnQ5shkV1Z6mMV7/AfEAwLbk6q14wQMY +irQ7cH2zS2Ho8YDX+QI9pnRtGQJ7xivGxx/zJdQoZsw= +-----END CERTIFICATE----- diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-developer-id-application2.pem b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-developer-id-application2.pem new file mode 100644 index 00000000..91a75beb --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-developer-id-application2.pem @@ -0,0 +1,51 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCxB3u9PkOjy9K7 +vaqeAQ7OUmBEtx01NjzUn4LEe5BTL0/bh02KxG+edlaVOmdeMF8nff89yrP6k3L+ +lA7p19uzACxjEYowytUyeLf1RCF9z+6rEeIAqW7+PEYH+yeiOrdSzd5DIaf/PGYk +KxhHyp9OuAS8CDimfvXBRV7PmpKMj+ostOy0Tlj6po1+Nci+SepZRNP/YdFTi0Yd +q9B/FztslFhvX/Ffc8rhmP8GQ8lPJqJOBcLss+aKAz9slFCzVahqmqXu8rz1b3E/ +5JHv5RFFBxgvwuXWaiO1mLTn62tIWTiG5rmR0xqJ4VucXk9DvOEAt1CxOBVupXOd +5IDt+qZ7AgMBAAECggEAH8IU6704yzCsjGuZKSFNc6wJgypKfhpNzWMURYVZPeMV +828RdRyKXaYjIEBK/PW2jFIpMP+lTAWZspwDFOZZjoIwdFFYNiqdFqHbdo+TZouf +6Gab4byDoe5ULehbktnvu1YdUnO+PKasOD7W60IpVCjlCIp9Bzltgw+b06iKM9bt +KIPvBXDRgIp6eSBzJFxzj3zmdnSQn1OSF4V4oQt6gbR3NCJIcNyLXL1gEqtKg/AS +swbpOVrPpXWd2x3gND8zvHD90vEiIesDNoSXKlclvLpQ30tkhkFh78xSe5sW7XEL +hIKOExm+VCHoXTdjZnuCkXBkUIkek8T0KNs7A3LgeQKBgQDQXIDvGwzDhR+bdG5C +azKI7SSqvIVpSngfGDCL8OLkYcoOwSClPOwZbY+UCXLppjrkoCD6QwJQ6CyCJwCK +n9hAAWazOQPaerS3zbq2ig+HCQg12zZZ9/oXe7C/sRsq02phxV2+Km+v4uIyalQ9 +qGsywA1PN4/mFJ3H0w0QZXV3bwKBgQDZgRhegfulaWvK6x93EJ+UjLYv2d1oBBsn +/87Vif/q14Ms0RNSmUPy1c8WuF8ZYOD4d7S+jcc/92JZWwJK+8pm6pHazdIIPCAP +GCcp9fkS6gOu4twdC6oz34mJ3lqdmHKHz+0hhC2JqV1uwvPS5k/DcKfKR2+ifxQE +xCBocLObtQKBgGWLH17n3OGQiCXXqUB/Q6KNh9gZhh8ZJs9ol4grvje1HKbyIfnF +Zf7CcT2hGTqbQ4pWK5wref56F+7aGR515grTY/ymJaWdNWN6RKtfP0/8695rVeKk +wmIdarcRFf9aBzdc22GpBsM+HCSbwzBFWvDhvdrEZkGn/Hj89xntiEDLAoGAb1sR +r+kafkBv6I7iKCJBoVs9N1hya3uWr67fJSKm/IPj68ELBIHlcOEYSkiQn7yi0XLv +/ZM2zMAKATeAAAXTRUeY7w3rFz45J6E1A92j7JQU2KfbC5/aPv6WOxi1CfRvxqqk +fEFg0xb79+Yl0PcLJUN7FCvosqgfBqWm9fGlcvUCgYAYvALoZPz2z6zzqURDKPWk +lkrW8nQVFg0eeTJNBiU1ipXS28oEGQISdjlYCHD1Ds3W8JHav/i6QnCaAXb5PtHd +DlylI7U+tSZaC83+oPu/BaKVUO9n9e0mqj2oCD7W2gTBazac3d9F1ncfSkEhpDSB +i6+0+GL62SGnzAF52ajf0g== +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIID4DCCAsigAwIBAgIBATANBgkqhkiG9w0BAQsFADCBizEYMBYGCgmSJomT8ixk +AQEMCGRlYWRiZWVmMTkwNwYDVQQDDDBEZXZlbG9wZXIgSUQgQXBwbGljYXRpb246 +IEpvaG4gU2lnbmVyIChkZWFkYmVlZikxETAPBgNVBAsMCGRlYWRiZWVmMRQwEgYD +VQQKDAtKb2huIFNpZ25lcjELMAkGA1UEBhMCWFgwHhcNMjQwMTE3MDI0ODE2WhcN +MzcwOTI1MDI0ODE2WjCBizEYMBYGCgmSJomT8ixkAQEMCGRlYWRiZWVmMTkwNwYD +VQQDDDBEZXZlbG9wZXIgSUQgQXBwbGljYXRpb246IEpvaG4gU2lnbmVyIChkZWFk +YmVlZikxETAPBgNVBAsMCGRlYWRiZWVmMRQwEgYDVQQKDAtKb2huIFNpZ25lcjEL +MAkGA1UEBhMCWFgwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCxB3u9 +PkOjy9K7vaqeAQ7OUmBEtx01NjzUn4LEe5BTL0/bh02KxG+edlaVOmdeMF8nff89 +yrP6k3L+lA7p19uzACxjEYowytUyeLf1RCF9z+6rEeIAqW7+PEYH+yeiOrdSzd5D +Iaf/PGYkKxhHyp9OuAS8CDimfvXBRV7PmpKMj+ostOy0Tlj6po1+Nci+SepZRNP/ +YdFTi0Ydq9B/FztslFhvX/Ffc8rhmP8GQ8lPJqJOBcLss+aKAz9slFCzVahqmqXu +8rz1b3E/5JHv5RFFBxgvwuXWaiO1mLTn62tIWTiG5rmR0xqJ4VucXk9DvOEAt1Cx +OBVupXOd5IDt+qZ7AgMBAAGjTTBLMAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAww +CgYIKwYBBQUHAwMwDgYDVR0PAQH/BAQDAgeAMBMGCiqGSIb3Y2QGAQ0BAf8EAgUA +MA0GCSqGSIb3DQEBCwUAA4IBAQCGzLhwja5AWKrla/VNjhXdKFiendu1VnGBwFQr +WtMVSlXirqjG32WaayFJAjtp3MfXgLD6Yu2isB6I06LimuET4e2jR9nHcxtgZZ0B +iowzgoNi+OA9sUSuZyrgzHwO7+tRmMroaMSURrzwCsffFIiiL8thgvZFZvtCRKm8 +7pHzhvIGmjc5BYY8TB3BoWPZiqZAXY1cpw66gPVi2pQ6zitUtv1C3gzwLxQMDtnn +SWm4XWWeihFcL6KgHxPqgnJ9YsRGjDbJ5LcrYk+uinXxSgzhv2Jlq+KD0VxvM6xr +foxvH3jp8ISnTYjajhPQ8zekYE5iZN+GPtF0RjmtQ6RMlweR +-----END CERTIFICATE----- diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-developer-id-installer.p12 b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-developer-id-installer.p12 new file mode 100644 index 00000000..37783d77 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-developer-id-installer.p12 differ diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-developer-id-installer.pem b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-developer-id-installer.pem new file mode 100644 index 00000000..aa237fd9 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-developer-id-installer.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDIwOT4PWVnRWDa +B+ruSEvsTkamlVi6OcuuQ3c8E3YR/4A9cKzliE9o8XGK6h1uCP2MB8nj6vIVmm7B +1nHPSSdU3IlNO2s1Xmh1nJN9Q5LSXkCvh5yjMwsgxhyxcXKeeAALmDlmzVwZjAdZ +N2wQmTCtMUu45AWG5T3Yw/W4n49Xs/q8bw2BucWOtRcCsc7+evFwYfMpHeZIRGxH +HCDiRG0mkJ2VdX0Y1xRCYno+MvukeknAo/zF4Wle1YM99OgwBpRR33XwQE7B3SYN +XVjiF+h+F3zPuVHMsGnH0Hu5s4SIGwbYl7oD3WX08khlKpmvRw+n5FyoiK+rRtND +KkTMqDeFAgMBAAECggEBAJTGHum41kVKLRRnebtM6Lce60zYsQCRhFiItvxWj9GW +v7rIndkcw3zKMZL5HQZGs1/rBbtaij1oTVxpR76OQA+rwDT0K+dJJ7DBcKwMP+qW +3uk2XuazFTQcnXcC1CaMV7w/+4or9m0YUPnVEMjcPi6bsbo7gb0Odl8GGjvQQ7KB +5tQQN7wWkPdM1g7KatiYkfWjNXa9RkwUdiZzZg9IMtF/YA9b3ol2wNVNAV6T5Fst +OM862gcpe5TlInWFxYkRfF59GhPV84VAetc2u2ke5Lg3nZg86vGmoJ6ZkINsyVUz +xDNJ/uiWdEC5VDA05Dit8Hb0Q9wEF2hulnEWlyXL6gECgYEA3ISCJzmO8oFigvb3 +Q2GaNyqvj735mVDK3U58Biu9YnBYqKyBa8mp8ZUYEQwbuwG3QyCt0CI2xbLFC6wE +5N7Scu4+dX8AhF1txABi7TCdr15OHYu9nFpyRG+KVCf7bCE3IAY3kWgRLLyoZapy +CUo7b9gNagarPXMhhTqIig54SFUCgYEA6Q5EzJQ/Ki6FJrpifZjlZQ+RhfxaVUQ/ +ZcINYROklx8JLzwLkOw5GVYkNslRjg+BAy+QwSSuYLfo4AFisWQ59owEjoAXkOag +t/1zw/N1LKWwNaWWkushFJOidtw+X9LQCITL7XnTEkLirOpSlEl005wzk0ooEJ6U +4u30i6khInECgYAsJ/Bz8EeacaQLO26ptGqP72E2NEE9nPryM5wMFEgY5Qwrwlcs +ATahZExsZXNMD/zlWS7UxXUYQ0LHootcVO3pC6HAH004NAkdvUIR4rFAg2665ddy +7n2BDKCzV0o2DbSfGf+YgzElNyW1Ldsl1xJtw+Jzv6AcbuhgaCcdFeap/QKBgQCQ +TJdom/moInmrCwhkf8C5HDScYy2DUeh3Fvm1u7XTJBJJvsHij4CjIWT2zxvB+/OD +h3X3QMD/fZ+g4vq6nzYMY5GGseTlgQbOJQ4Cq8FHTaeW79oVSaSH2wli0ueD6UGJ +pL+nYCDCU8uKCOPskLbXNwXwEqBP+gBxqagauTOc4QKBgBH4DM5sC7LKOK0PjwuC +BIE0PhY96IJS3k+J8+wupfcE+xzrCPcD4rdNRzByYFrihVnpRJSjc/FlLlg2z9J1 +vD9NYQaFtkaHuWV+m2EHaKqmS1ymsoZU+2N4eC/34ZnuSDhE/WPJLaU/YfL17Rsi +axmxLymyrVblIh0KrU2AuJ/1 +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIEATCCAumgAwIBAgIBATANBgkqhkiG9w0BAQsFADCBmzEUMBIGCgmSJomT8ixk +AQEMBHRlc3QxQjBABgNVBAMMOURldmVsb3BlciBJRCBJbnN0YWxsZXI6IFJTQSBE +ZXZlbG9wZXIgSUQgSW5zdGFsbGVyICh0ZXN0KTENMAsGA1UECwwEdGVzdDEjMCEG +A1UECgwaUlNBIERldmVsb3BlciBJRCBJbnN0YWxsZXIxCzAJBgNVBAYTAlVTMB4X +DTIzMTEwNzEwNTEwN1oXDTM3MDcxNjEwNTEwN1owgZsxFDASBgoJkiaJk/IsZAEB +DAR0ZXN0MUIwQAYDVQQDDDlEZXZlbG9wZXIgSUQgSW5zdGFsbGVyOiBSU0EgRGV2 +ZWxvcGVyIElEIEluc3RhbGxlciAodGVzdCkxDTALBgNVBAsMBHRlc3QxIzAhBgNV +BAoMGlJTQSBEZXZlbG9wZXIgSUQgSW5zdGFsbGVyMQswCQYDVQQGEwJVUzCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMjA5Pg9ZWdFYNoH6u5IS+xORqaV +WLo5y65DdzwTdhH/gD1wrOWIT2jxcYrqHW4I/YwHyePq8hWabsHWcc9JJ1TciU07 +azVeaHWck31DktJeQK+HnKMzCyDGHLFxcp54AAuYOWbNXBmMB1k3bBCZMK0xS7jk +BYblPdjD9bifj1ez+rxvDYG5xY61FwKxzv568XBh8ykd5khEbEccIOJEbSaQnZV1 +fRjXFEJiej4y+6R6ScCj/MXhaV7Vgz306DAGlFHfdfBATsHdJg1dWOIX6H4XfM+5 +UcywacfQe7mzhIgbBtiXugPdZfTySGUqma9HD6fkXKiIr6tG00MqRMyoN4UCAwEA +AaNOMEwwDAYDVR0TAQH/BAIwADAXBgNVHSUBAf8EDTALBgkqhkiG92NkBA0wDgYD +VR0PAQH/BAQDAgeAMBMGCiqGSIb3Y2QGAQ4BAf8EAgUAMA0GCSqGSIb3DQEBCwUA +A4IBAQAQYlyBvfIXIrfTzOWv+Bp4DzbCmtOpTmPtDh9Kv0bE4lJIroyFjm+cf9vQ +3MYDclbe75lqRwJEN5EQM2Fakb1FJC7IFEkgO7S4QGpld6congNLGmba/+2teJJ7 +yH0IMpYyC2gV35PJt52r4V6RRHv4yQ2tAdj7UPOiqXuzqUc6TCsLIcOmXzd3kYux +jtAK3C+EWNB2zMUrstJ1Y1iGp4cny8jXLb4PCEEqVZBt1NCnGqCm5MfW2WLSj2DO +0UyJtqolyd4mTJmtK3MzWZK/KOE6q7Xum4phraVeCJqiWRYOE+art2gLqWDu5rif +AjgIK7uOqJrmYiVjDS40vlaCptGa +-----END CERTIFICATE----- diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-mac-installer-distribution.p12 b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-mac-installer-distribution.p12 new file mode 100644 index 00000000..c0333930 Binary files /dev/null and b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-mac-installer-distribution.p12 differ diff --git a/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-mac-installer-distribution.pem b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-mac-installer-distribution.pem new file mode 100644 index 00000000..a6942888 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-mac-installer-distribution.pem @@ -0,0 +1,53 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDA87L0C/PY6A74 +jFhmY4QtXInTWhBiGsNoUO9fXcs1/hAUKxZxf5AbsqZVAAo5LqkjaEt510MtnhW+ +NtMYY+K6A9PRmEYXrcIP75JesyPWJDGi49qQrd3wUrYULpmAzt0RzsMmfsGu0L36 +FrK0W4DALlnF9lz9jN6zdRMp7ArdgK0NzG/1lIsbkyVqyckR9CXQaloID5VbQYJe +NYpF3bdppNrs9CbuSRakInv3t0XjHKL3l6X1bvcTIPguUsrsMADGQJ6Uh409+EwK +XzYEkZBfMu2bEC3QbcYEgLAA6jHsHC9ooo+3Z+uwSb4v13xLLC9B5FrE+iOuZfHD +9bC87W8RAgMBAAECggEBAKOZPV8dD0kXITJuVLmjoTuQ3a9dCs0ToiE9lmhGr3j9 +DIb9LY9YOEizxcIGQaTQQkqqMGyDLwtroUXhWESy1FchzaRFtxB2nDdEytcOjtEP +aSKSjQvkz2vnxUgBZtfHDbv+kop+KRxfEuUL/8NaXiUUZ7uospmsqlqAZppB78hz +y9w/dN+s8i1XnYZkCcqdI2ifWIGQNS0HiW/mFP0iwGZMw70ORkos+p8LNDkimU5/ +4JapgeSneZmTllB1pferElUym+o4SRTW9aFLzWNE/odfw61CviE+2+XlnI7JttGq +q1p1e4nIxbcm2dbqq4EG11cWxdkR73DfhKSDT2gP/V0CgYEA23mD2YE3tzq3Pbze +cMjoTpgZ+OA2xU93I9Z3qT2YMQW+NIhIkh+DHMMs1U7auym3/iG0LmrFIR3BZ58j +imffTXT5JbPHQB5gY/9pf6C/GE69aWnsXxT6QYqKaF+9tjx9Ki7e/U558Ud9pDQh +hibFrFHu3y9wDwfKP/AuY18poEcCgYEA4RA2NaoWFRu3DJh8Fp43laTx3FSHBTJO +iMB5o9FCjjjUMtSvnr0KIQ6X3TJCJfs6IAClaXrtu7r1quI/+BL6HGYC3QuT0G7Z +KcLfDUT1Lx6BLTTDgSsAdwZjH7eERuReodZOxPUMCXAbJjmp/+kYzXrQng82bGlS +Hh7WOY3iOecCgYBJnxl7fL0T2b5eF10GuF40/xC3S38T8PQmMWsyelbzGtoTBSRS +3/87Rr1jUHBPGE+AEA5BA8/cq/6Uo+1oIC/n67Un0IamG4p6ANOC3Ik9viwLkFya +CI9qLO8A1BzvZJsX62Eh15FQPosG6fXU6mykwVc/xsnwQMy3Zfopm2J7QQKBgQCP +9m/GmfqwG99WJj/Rs/j4Nt8iwcrwTwKfRQdJ+3QoRz+tGBESZ/ePt6b6rchURUQj +7mXgd+qT1/6HBVxH0dO80J/qRxqRDCbLKMTG1yJCtq+IfCTGffw5JpPHWPs64Z+K +w+v03o6JhvVG2UHd2XutDG9fe3mjlSca7zy26gQYZwKBgQCksh2zE2sGzxArOfYN +LJd5BV74jtqhRhB11vvvhYOm8cvNoLimOBYiePF9ZO6ZcjCyyoNR4DURbTLwT6Rv +AOYb00OmLtsU2v2P+0schBDWELpt9vvt7QOif3h1rdp31icgA2f5mNCn85NrwjpZ +Tb1OwwrD2C1bj5j3a/e4//Dnkg== +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIEJzCCAw+gAwIBAgIBATANBgkqhkiG9w0BAQsFADCBrjEUMBIGCgmSJomT8ixk +AQEMBHRlc3QxUTBPBgNVBAMMSDNyZCBQYXJ0eSBNYWMgRGV2ZWxvcGVyIEluc3Rh +bGxlcjogUlNBIE1hYyBJbnN0YWxsZXIgRGlzdHJpYnV0aW9uICh0ZXN0KTENMAsG +A1UECwwEdGVzdDEnMCUGA1UECgweUlNBIE1hYyBJbnN0YWxsZXIgRGlzdHJpYnV0 +aW9uMQswCQYDVQQGEwJVUzAeFw0yMzExMDcxMDUxNTZaFw0zNzA3MTYxMDUxNTZa +MIGuMRQwEgYKCZImiZPyLGQBAQwEdGVzdDFRME8GA1UEAwxIM3JkIFBhcnR5IE1h +YyBEZXZlbG9wZXIgSW5zdGFsbGVyOiBSU0EgTWFjIEluc3RhbGxlciBEaXN0cmli +dXRpb24gKHRlc3QpMQ0wCwYDVQQLDAR0ZXN0MScwJQYDVQQKDB5SU0EgTWFjIElu +c3RhbGxlciBEaXN0cmlidXRpb24xCzAJBgNVBAYTAlVTMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAwPOy9Avz2OgO+IxYZmOELVyJ01oQYhrDaFDvX13L +Nf4QFCsWcX+QG7KmVQAKOS6pI2hLeddDLZ4VvjbTGGPiugPT0ZhGF63CD++SXrMj +1iQxouPakK3d8FK2FC6ZgM7dEc7DJn7BrtC9+haytFuAwC5ZxfZc/Yzes3UTKewK +3YCtDcxv9ZSLG5MlasnJEfQl0GpaCA+VW0GCXjWKRd23aaTa7PQm7kkWpCJ797dF +4xyi95el9W73EyD4LlLK7DAAxkCelIeNPfhMCl82BJGQXzLtmxAt0G3GBICwAOox +7BwvaKKPt2frsEm+L9d8SywvQeRaxPojrmXxw/WwvO1vEQIDAQABo04wTDAMBgNV +HRMBAf8EAjAAMBcGA1UdJQEB/wQNMAsGCSqGSIb3Y2QECTAOBgNVHQ8BAf8EBAMC +B4AwEwYKKoZIhvdjZAYBCAEB/wQCBQAwDQYJKoZIhvcNAQELBQADggEBAK6eDJi5 +ig1RyQrsMhy/K9OPsDL2//01iwJBUClkx/ajJZPlZFvipbrs1gA4+rIK01lx9qnL +pGugeTSRMMcBCrat+E/4675AFZftq/H5/plsJJ98XrGOXOTfXg40aF9rbv1tV8K0 +u37xaOGAAj7bZYPvazfTZ7XBKx0Fwr9JsskR6zM083BXRUhKONMSS3h60m6PRyRS +rSPgzxC2Zmso7h763PwFVZbKpz6cQ8l4AdWGDuXvzHl/7e94kiU9mEta0szQyJBU +bAxvajf/y1XUQDu9YQUDy67NCsc7jga6RvJBkwWLrLZpmD10tnOY0qUPmPY3Abk8 +gUISATBAl9H0xuY= +-----END CERTIFICATE----- diff --git a/3rdparty/apple-codesign-0.29.0/src/ticket_lookup.rs b/3rdparty/apple-codesign-0.29.0/src/ticket_lookup.rs new file mode 100644 index 00000000..f7acfe8c --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/ticket_lookup.rs @@ -0,0 +1,250 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +/*! Support for retrieving notarization tickets and stapling artifacts. */ + +use { + crate::AppleCodesignError, + base64::{engine::general_purpose::STANDARD as STANDARD_ENGINE, Engine}, + log::warn, + reqwest::blocking::{Client, ClientBuilder}, + serde::{Deserialize, Serialize}, + std::collections::HashMap, +}; + +/// URL of HTTP service where Apple publishes stapling tickets. +pub const APPLE_TICKET_LOOKUP_URL: &str = "https://api.apple-cloudkit.com/database/1/com.apple.gk.ticket-delivery/production/public/records/lookup"; + +/// Main JSON request object for ticket lookup requests. +#[derive(Clone, Debug, Serialize)] +pub struct TicketLookupRequest { + pub records: Vec, +} + +/// Represents a single record to look up in a ticket lookup request. +#[derive(Clone, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TicketLookupRequestRecord { + pub record_name: String, +} + +/// Main JSON response object to ticket lookup requests. +#[derive(Clone, Debug, Deserialize)] +pub struct TicketLookupResponse { + pub records: Vec, +} + +impl TicketLookupResponse { + /// Obtain the signed ticket for a given record name. + /// + /// `record_name` is of the form `2//`. e.g. + /// `2/2/deadbeefdeadbeef....`. + /// + /// Returns an `Err` if a signed ticket could not be found. + pub fn signed_ticket(&self, record_name: &str) -> Result, AppleCodesignError> { + let record = self + .records + .iter() + .find(|r| r.record_name() == record_name) + .ok_or_else(|| { + AppleCodesignError::NotarizationRecordNotInResponse(record_name.to_string()) + })?; + + match record { + TicketLookupResponseRecord::Success(r) => r + .signed_ticket_data() + .ok_or(AppleCodesignError::NotarizationRecordNoSignedTicket)?, + TicketLookupResponseRecord::Failure(r) => { + Err(AppleCodesignError::NotarizationLookupFailure( + r.server_error_code.clone(), + r.reason.clone(), + )) + } + } + } +} + +/// Describes the results of a ticket lookup for a specific record. +#[derive(Clone, Debug, Deserialize)] +#[serde(untagged)] +pub enum TicketLookupResponseRecord { + /// Ticket was found. + Success(TicketLookupResponseRecordSuccess), + + /// Some error occurred. + Failure(TicketLookupResponseRecordFailure), +} + +impl TicketLookupResponseRecord { + /// Obtain the record name associated with this record. + pub fn record_name(&self) -> &str { + match self { + Self::Success(r) => &r.record_name, + Self::Failure(r) => &r.record_name, + } + } +} + +/// Represents a successful ticket lookup response record. +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TicketLookupResponseRecordSuccess { + /// Name of record that was looked up. + pub record_name: String, + + pub created: TicketRecordEvent, + pub deleted: bool, + /// Holds data. + /// + /// The `signedTicket` key holds the ticket. + pub fields: HashMap, + pub modified: TicketRecordEvent, + // TODO pluginFields + pub record_change_tag: String, + + /// A value like `DeveloperIDTicket`. + /// + /// We could potentially turn this into an enumeration... + pub record_type: String, +} + +impl TicketLookupResponseRecordSuccess { + /// Obtain the raw signed ticket data in this record. + /// + /// Evaluates to `Some` if there appears to be a signed ticket and `None` + /// otherwise. + /// + /// There can be an inner `Err` if we don't know how to decode the response data + /// or there was an error decoding. + pub fn signed_ticket_data(&self) -> Option, AppleCodesignError>> { + match self.fields.get("signedTicket") { + Some(field) => { + if field.typ == "BYTES" { + Some( + STANDARD_ENGINE + .decode(&field.value) + .map_err(AppleCodesignError::NotarizationRecordDecodeFailure), + ) + } else { + Some(Err( + AppleCodesignError::NotarizationRecordSignedTicketNotBytes( + field.typ.clone(), + ), + )) + } + } + None => None, + } + } +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TicketLookupResponseRecordFailure { + pub record_name: String, + pub reason: String, + pub server_error_code: String, +} + +/// Represents an event in a ticket record. +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TicketRecordEvent { + #[serde(rename = "deviceID")] + pub device_id: String, + pub timestamp: u64, + pub user_record_name: String, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct Field { + #[serde(rename = "type")] + pub typ: String, + pub value: String, +} + +/// Obtain the default [Client] to use for HTTP requests. +pub fn default_client() -> Result { + Ok(ClientBuilder::default() + .user_agent("apple-codesign crate (https://crates.io/crates/apple-codesign)") + .build()?) +} + +/// Look up a notarization ticket given an HTTP client and an iterable of record names. +/// +/// The record name is of the form `2//`. +pub fn lookup_notarization_tickets<'a>( + client: &Client, + record_names: impl Iterator, +) -> Result { + let body = TicketLookupRequest { + records: record_names + .map(|x| { + warn!("looking up notarization ticket for {}", x); + TicketLookupRequestRecord { + record_name: x.to_string(), + } + }) + .collect::>(), + }; + + let req = client + .post(APPLE_TICKET_LOOKUP_URL) + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .json(&body); + + let response = req.send()?; + + let body = response.bytes()?; + + let response = serde_json::from_slice::(&body)?; + + Ok(response) +} + +/// Look up a single notarization ticket. +/// +/// This is just a convenience wrapper around [lookup_notarization_tickets()]. +pub fn lookup_notarization_ticket( + client: &Client, + record_name: &str, +) -> Result { + lookup_notarization_tickets(client, std::iter::once(record_name)) +} + +#[cfg(test)] +mod test { + use super::*; + + const PYOXIDIZER_APP_RECORD: &str = "2/2/1b747faf223750de74febed7929f14a73af8c933"; + const DEADBEEF: &str = "2/2/deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; + + #[test] + fn lookup_ticket() -> Result<(), AppleCodesignError> { + let client = default_client()?; + + let res = lookup_notarization_ticket(&client, PYOXIDIZER_APP_RECORD)?; + + assert!(matches!( + &res.records[0], + TicketLookupResponseRecord::Success(_) + )); + + let ticket = res.signed_ticket(PYOXIDIZER_APP_RECORD)?; + assert_eq!(&ticket[0..4], b"s8ch"); + + let res = lookup_notarization_ticket(&client, DEADBEEF)?; + assert!(matches!( + &res.records[0], + TicketLookupResponseRecord::Failure(_) + )); + assert!(matches!( + res.signed_ticket(DEADBEEF), + Err(AppleCodesignError::NotarizationLookupFailure(_, _)) + )); + + Ok(()) + } +} diff --git a/3rdparty/apple-codesign-0.29.0/src/verify.rs b/3rdparty/apple-codesign-0.29.0/src/verify.rs new file mode 100644 index 00000000..6b65bf1b --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/verify.rs @@ -0,0 +1,510 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Code signing verification. +//! +//! This module implements functionality for verifying code signatures on +//! Mach-O binaries. +//! +//! # Verification Caveats +//! +//! **Verification performed by this code will vary from what Apple tools +//! do. Do not use successful verification from this code as validation that +//! Apple software will accept a signature.** +//! +//! We aim for our verification code to be as comprehensive as possible. But +//! there are things it doesn't yet or won't ever do. For example, we have +//! no clue of the full extent of verification that Apple performs because +//! that code is proprietary. We know some of the things that are done and +//! we have verification for a subset of them. Read the code or the set of +//! verification problem types enumerated by [VerificationProblemType] to get +//! a sense of what we do. + +use { + crate::{ + code_directory::CodeDirectoryBlob, + embedded_signature::{CodeSigningSlot, EmbeddedSignature}, + error::AppleCodesignError, + macho::{MachFile, MachOBinary}, + }, + cryptographic_message_syntax::{CmsError, SignedData}, + std::path::PathBuf, + x509_certificate::{DigestAlgorithm, SignatureAlgorithm}, +}; + +/// Context for a verification issue. +#[derive(Clone, Debug)] +pub struct VerificationContext { + /// Path of binary. + pub path: Option, + + /// Index of Mach-O binary within a fat binary that is problematic. + pub fat_index: Option, +} + +/// Describes a problem with verification. +#[derive(Debug)] +pub enum VerificationProblemType { + IoError(std::io::Error), + MachOParseError(AppleCodesignError), + NoMachOSignatureData, + MachOSignatureError(AppleCodesignError), + LinkeditNotLastSegment, + SignatureNotLastLinkeditData, + NoCryptographicSignature, + CmsError(CmsError), + CmsOldDigestAlgorithm(DigestAlgorithm), + CmsOldSignatureAlgorithm(SignatureAlgorithm), + NoCodeDirectory, + CodeDigestError(AppleCodesignError), + CodeDigestMissingEntry(usize, Vec), + CodeDigestExtraEntry(usize, Vec), + CodeDigestMismatch(usize, Vec, Vec), + SlotDigestMissing(CodeSigningSlot), + ExtraSlotDigest(CodeSigningSlot, Vec), + SlotDigestMismatch(CodeSigningSlot, Vec, Vec), + SlotDigestError(AppleCodesignError), +} + +#[derive(Debug)] +pub struct VerificationProblem { + pub context: VerificationContext, + pub problem: VerificationProblemType, +} + +impl std::fmt::Display for VerificationProblem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let context = match (&self.context.path, &self.context.fat_index) { + (None, None) => None, + (Some(path), None) => Some(format!("{}", path.display())), + (None, Some(index)) => Some(format!("@{index}")), + (Some(path), Some(index)) => Some(format!("{}@{}", path.display(), index)), + }; + + let message = match &self.problem { + VerificationProblemType::IoError(e) => format!("I/O error: {e}"), + VerificationProblemType::MachOParseError(e) => format!("Mach-O parse failure: {e}"), + VerificationProblemType::NoMachOSignatureData => { + "Mach-O signature data not found".to_string() + } + VerificationProblemType::MachOSignatureError(e) => { + format!("error parsing Mach-O signature data: {e:?}") + } + VerificationProblemType::LinkeditNotLastSegment => { + "__LINKEDIT isn't last Mach-O segment".to_string() + } + VerificationProblemType::SignatureNotLastLinkeditData => { + "signature isn't last data in __LINKEDIT segment".to_string() + } + VerificationProblemType::NoCryptographicSignature => { + "no cryptographic signature present".to_string() + } + VerificationProblemType::CmsError(e) => format!("CMS error: {e}"), + VerificationProblemType::CmsOldDigestAlgorithm(alg) => { + format!("insecure digest algorithm used: {alg:?}") + } + VerificationProblemType::CmsOldSignatureAlgorithm(alg) => { + format!("insecure signature algorithm used: {alg:?}") + } + VerificationProblemType::NoCodeDirectory => "no code directory".to_string(), + VerificationProblemType::CodeDigestError(e) => { + format!("error computing code digests: {e:?}") + } + VerificationProblemType::CodeDigestMissingEntry(index, digest) => { + format!( + "code digest missing entry at index {} for digest {}", + index, + hex::encode(digest) + ) + } + VerificationProblemType::CodeDigestExtraEntry(index, digest) => { + format!( + "code digest contains extra entry index {} with digest {}", + index, + hex::encode(digest) + ) + } + VerificationProblemType::CodeDigestMismatch(index, cd_digest, actual_digest) => { + format!( + "code digest mismatch for entry {}; recorded digest {}, actual {}", + index, + hex::encode(cd_digest), + hex::encode(actual_digest) + ) + } + VerificationProblemType::SlotDigestMissing(slot) => { + format!("missing digest for slot {slot:?}") + } + VerificationProblemType::ExtraSlotDigest(slot, digest) => { + format!( + "slot digest contains digest for slot not in signature: {:?} with digest {}", + slot, + hex::encode(digest) + ) + } + VerificationProblemType::SlotDigestMismatch(slot, cd_digest, actual_digest) => { + format!( + "slot digest mismatch for slot {:?}; recorded digest {}, actual {}", + slot, + hex::encode(cd_digest), + hex::encode(actual_digest) + ) + } + VerificationProblemType::SlotDigestError(e) => { + format!("error computing slot digest: {e:?}") + } + }; + + match context { + Some(context) => f.write_fmt(format_args!("{context}: {message}")), + None => f.write_str(&message), + } + } +} + +/// Verifies unparsed Mach-O data. +/// +/// Returns a vector of problems detected. An empty vector means no +/// problems were found. +pub fn verify_macho_data(data: impl AsRef<[u8]>) -> Vec { + let context = VerificationContext { + path: None, + fat_index: None, + }; + + verify_macho_data_internal(data, context) +} + +fn verify_macho_data_internal( + data: impl AsRef<[u8]>, + context: VerificationContext, +) -> Vec { + match MachFile::parse(data.as_ref()) { + Ok(mach) => { + let mut problems = vec![]; + + for macho in mach.into_iter() { + let mut context = context.clone(); + context.fat_index = macho.index; + + problems.extend(verify_macho_internal(&macho, context)); + } + + problems + } + Err(e) => { + vec![VerificationProblem { + context, + problem: VerificationProblemType::MachOParseError(e), + }] + } + } +} + +/// Verifies a parsed Mach-O binary. +/// +/// Returns a vector of problems detected. An empty vector means no +/// problems were found. +pub fn verify_macho(macho: &MachOBinary) -> Vec { + verify_macho_internal( + macho, + VerificationContext { + path: None, + fat_index: None, + }, + ) +} + +fn verify_macho_internal( + macho: &MachOBinary, + context: VerificationContext, +) -> Vec { + let signature_data = match macho.find_signature_data() { + Ok(Some(data)) => data, + Ok(None) => { + return vec![VerificationProblem { + context, + problem: VerificationProblemType::NoMachOSignatureData, + }]; + } + Err(e) => { + return vec![VerificationProblem { + context, + problem: VerificationProblemType::MachOSignatureError(e), + }]; + } + }; + + let mut problems = vec![]; + + // __LINKEDIT segment should be the last segment. + if signature_data.linkedit_segment_index != macho.macho.segments.len() - 1 { + problems.push(VerificationProblem { + context: context.clone(), + problem: VerificationProblemType::LinkeditNotLastSegment, + }); + } + + // Signature data should be the last data in the __LINKEDIT segment. + if signature_data.signature_segment_end_offset != signature_data.linkedit_segment_data.len() { + problems.push(VerificationProblem { + context: context.clone(), + problem: VerificationProblemType::SignatureNotLastLinkeditData, + }); + } + + let signature = match macho.code_signature() { + Ok(Some(signature)) => signature, + Ok(None) => { + panic!("no signature should have been handled above"); + } + Err(e) => { + problems.push(VerificationProblem { + context, + problem: VerificationProblemType::MachOSignatureError(e), + }); + + // Can't do anything more if we couldn't parse the signature data. + return problems; + } + }; + + match signature.signature_data() { + Ok(Some(cms_blob)) => { + problems.extend(verify_cms_signature(cms_blob, context.clone())); + } + Ok(None) => problems.push(VerificationProblem { + context: context.clone(), + problem: VerificationProblemType::NoCryptographicSignature, + }), + Err(e) => { + problems.push(VerificationProblem { + context: context.clone(), + problem: VerificationProblemType::MachOSignatureError(e), + }); + } + } + + match signature.code_directory() { + Ok(Some(cd)) => { + problems.extend(verify_code_directory(macho, &signature, &cd, context)); + } + Ok(None) => { + problems.push(VerificationProblem { + context, + problem: VerificationProblemType::NoCodeDirectory, + }); + } + Err(e) => { + problems.push(VerificationProblem { + context, + problem: VerificationProblemType::MachOSignatureError(e), + }); + } + } + + problems +} + +fn verify_cms_signature(data: &[u8], context: VerificationContext) -> Vec { + let signed_data = match SignedData::parse_ber(data) { + Ok(signed_data) => signed_data, + Err(e) => { + return vec![VerificationProblem { + context, + problem: VerificationProblemType::CmsError(e), + }]; + } + }; + + let mut problems = vec![]; + + for signer in signed_data.signers() { + match signer.digest_algorithm() { + DigestAlgorithm::Sha1 => { + problems.push(VerificationProblem { + context: context.clone(), + problem: VerificationProblemType::CmsOldDigestAlgorithm( + signer.digest_algorithm(), + ), + }); + } + DigestAlgorithm::Sha384 => {} + DigestAlgorithm::Sha256 => {} + DigestAlgorithm::Sha512 => {} + } + + match signer.signature_algorithm() { + SignatureAlgorithm::RsaSha256 + | SignatureAlgorithm::RsaSha384 + | SignatureAlgorithm::RsaSha512 + | SignatureAlgorithm::EcdsaSha256 + | SignatureAlgorithm::EcdsaSha384 + | SignatureAlgorithm::Ed25519 + | SignatureAlgorithm::NoSignature(_) => {} + SignatureAlgorithm::RsaSha1 => { + problems.push(VerificationProblem { + context: context.clone(), + problem: VerificationProblemType::CmsOldSignatureAlgorithm( + signer.signature_algorithm(), + ), + }); + } + } + + match signer.verify_signature_with_signed_data(&signed_data) { + Ok(()) => {} + Err(e) => { + problems.push(VerificationProblem { + context: context.clone(), + problem: VerificationProblemType::CmsError(e), + }); + } + } + + // TODO verify key length meets standards. + // TODO verify CA chain is fully present. + // TODO verify signing cert chains to Apple? + } + + problems +} + +fn verify_code_directory( + macho: &MachOBinary, + signature: &EmbeddedSignature, + cd: &CodeDirectoryBlob, + context: VerificationContext, +) -> Vec { + let mut problems = vec![]; + + match macho.code_digests(cd.digest_type, cd.page_size as _) { + Ok(digests) => { + let mut cd_iter = cd.code_digests.iter().enumerate(); + let mut actual_iter = digests.iter().enumerate(); + + loop { + match (cd_iter.next(), actual_iter.next()) { + (None, None) => { + break; + } + (Some((cd_index, cd_digest)), Some((_, actual_digest))) => { + if &cd_digest.data != actual_digest { + problems.push(VerificationProblem { + context: context.clone(), + problem: VerificationProblemType::CodeDigestMismatch( + cd_index, + cd_digest.to_vec(), + actual_digest.clone(), + ), + }); + } + } + (None, Some((actual_index, actual_digest))) => { + problems.push(VerificationProblem { + context: context.clone(), + problem: VerificationProblemType::CodeDigestMissingEntry( + actual_index, + actual_digest.clone(), + ), + }); + } + (Some((cd_index, cd_digest)), None) => { + problems.push(VerificationProblem { + context: context.clone(), + problem: VerificationProblemType::CodeDigestExtraEntry( + cd_index, + cd_digest.to_vec(), + ), + }); + } + } + } + } + Err(e) => { + problems.push(VerificationProblem { + context: context.clone(), + problem: VerificationProblemType::CodeDigestError(e), + }); + } + } + + // All slots beneath some threshold should have a special hash. + // It isn't clear where this threshold is. But the alternate code directory and + // CMS slots appear to start at 0x1000. We set our limit at 32, which seems + // reasonable considering there are ~10 defined slots starting at value 0. + // + // The code directory doesn't have a digest because one cannot hash self. + for blob in &signature.blobs { + let slot = blob.slot; + + if u32::from(slot) < 32 + && !cd.slot_digests().contains_key(&slot) + && slot != CodeSigningSlot::CodeDirectory + { + problems.push(VerificationProblem { + context: context.clone(), + problem: VerificationProblemType::SlotDigestMissing(slot), + }); + } + } + + let max_slot = cd + .slot_digests() + .keys() + .map(|slot| u32::from(*slot)) + .filter(|slot| *slot < 32) + .max() + .unwrap_or(0); + + let null_digest = b"\0".repeat(cd.digest_size as usize); + + // Verify the special/slot digests we do have match reality. + for (slot, cd_digest) in cd.slot_digests().iter() { + match signature.find_slot(*slot) { + Some(entry) => match entry.digest_with(cd.digest_type) { + Ok(actual_digest) => { + if actual_digest != cd_digest.to_vec() { + problems.push(VerificationProblem { + context: context.clone(), + problem: VerificationProblemType::SlotDigestMismatch( + *slot, + cd_digest.to_vec(), + actual_digest, + ), + }); + } + } + Err(e) => { + problems.push(VerificationProblem { + context: context.clone(), + problem: VerificationProblemType::SlotDigestError(e), + }); + } + }, + None => { + // Some slots have external provided from somewhere that isn't a blob. + if slot.has_external_content() { + // TODO need to validate this external content somewhere. + } + // But slots with a null digest (all 0s) exist as placeholders when there + // is a higher numbered slot present. + else if u32::from(*slot) >= max_slot || cd_digest.to_vec() != null_digest { + problems.push(VerificationProblem { + context: context.clone(), + problem: VerificationProblemType::ExtraSlotDigest( + *slot, + cd_digest.to_vec(), + ), + }); + } + } + } + } + + // TODO verify code_limit[_64] is appropriate. + // TODO verify exec_seg_base is appropriate. + + problems +} diff --git a/3rdparty/apple-codesign-0.29.0/src/windows.rs b/3rdparty/apple-codesign-0.29.0/src/windows.rs new file mode 100644 index 00000000..5da65f66 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/windows.rs @@ -0,0 +1,661 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Functionality that only works on Windows. + +use { + crate::{ + certificate::AppleCertificate, + cryptography::PrivateKey, + error::AppleCodesignError, + remote_signing::{session_negotiation::PublicKeyPeerDecrypt, RemoteSignError}, + }, + bytes::Bytes, + log::{error, info, warn}, + signature::Signer, + std::ops::Deref, + std::ptr, + std::slice, + widestring::U16CString, + windows_sys::Win32::{ + Foundation::{GetLastError, BOOL}, + Security::Cryptography::*, + }, + x509_certificate::{ + CapturedX509Certificate, EcdsaCurve, KeyAlgorithm, KeyInfoSigner, Sign, Signature, + SignatureAlgorithm, X509CertificateError, + }, + zeroize::Zeroizing, +}; + +// A wrapper around GetLastError. +fn get_last_error() -> u32 { + unsafe { GetLastError() } +} + +/// A wrapper around [CERT_OPEN_STORE_FLAGS] so we can use crate local types. +#[derive(Clone, Copy, Debug)] +pub enum StoreName { + CurrentUser, + LocalMachine, + CurrentService, +} + +impl From for CERT_OPEN_STORE_FLAGS { + fn from(v: StoreName) -> Self { + match v { + StoreName::CurrentUser => { + CERT_SYSTEM_STORE_CURRENT_USER_ID << CERT_SYSTEM_STORE_LOCATION_SHIFT + } + StoreName::LocalMachine => { + CERT_SYSTEM_STORE_LOCAL_MACHINE_ID << CERT_SYSTEM_STORE_LOCATION_SHIFT + } + StoreName::CurrentService => { + CERT_SYSTEM_STORE_CURRENT_SERVICE_ID << CERT_SYSTEM_STORE_LOCATION_SHIFT + } + } + } +} + +impl From for &'static str { + fn from(v: StoreName) -> Self { + match v { + StoreName::CurrentUser => "user", + StoreName::LocalMachine => "machine", + StoreName::CurrentService => "service", + } + } +} + +impl TryFrom<&str> for StoreName { + type Error = String; + + fn try_from(v: &str) -> Result { + match v.to_lowercase().as_str() { + "user" => Ok(Self::CurrentUser), + "machine" => Ok(Self::LocalMachine), + "service" => Ok(Self::CurrentService), + _ => Err(format!( + "{} is not a valid windows store name; use user, machine or service", + v + )), + } + } +} + +#[derive(Clone, Copy, Debug)] +pub enum StoreType { + CA, + MY, + ROOT, + SPC, +} + +impl From for &'static str { + fn from(v: StoreType) -> Self { + match v { + StoreType::CA => "ca", + StoreType::MY => "my", + StoreType::ROOT => "root", + StoreType::SPC => "spc", + } + } +} + +impl TryFrom<&str> for StoreType { + type Error = String; + + fn try_from(v: &str) -> Result { + match v.to_lowercase().as_str() { + "ca" => Ok(Self::CA), + "my" => Ok(Self::MY), + "root" => Ok(Self::ROOT), + "spc" => Ok(Self::SPC), + _ => Err(format!( + "{} is not a valid windows store type; use ca, my, root or spc", + v + )), + } + } +} + +/// A certificate in a Windows store. +#[derive(Clone)] +pub struct StoreCertificate { + cert_context: *mut CERT_CONTEXT, + cert_thumbprint: String, + hkey: NCRYPT_KEY_HANDLE, + must_free_hkey: bool, + captured: CapturedX509Certificate, +} + +impl StoreCertificate { + fn new(cert_context: *mut CERT_CONTEXT) -> Result { + if cert_context.is_null() { + return Err(AppleCodesignError::WindowsStoreError( + "certificate context is null".into(), + )); + } + + let cert_der = unsafe { + slice::from_raw_parts( + (*cert_context).pbCertEncoded, + (*cert_context).cbCertEncoded as usize, + ) + } + .to_vec(); + + let captured = CapturedX509Certificate::from_der(cert_der)?; + let cert_thumbprint = hex::encode(captured.sha1_fingerprint()?.as_ref()); + + // We try to get either a CNG or a CryptoAPI handle. + // CryptAcquireCertificatePrivateKey can fail if the certificate does + // not have a private key (for example if it is a CA certificate). + // Therefore, we do not return an error if that happens. + + let mut hkey = 0; + let mut must_free_hkey = false; + let mut hprov_ncryptkey_handle = HCRYPTPROV_OR_NCRYPT_KEY_HANDLE::default(); + let mut key_spec = CERT_KEY_SPEC::default(); + let mut must_free_hprov_ncryptkey_handle = BOOL::default(); + let result = unsafe { + CryptAcquireCertificatePrivateKey( + cert_context, + CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG, // Prefer CNG keys, but accept CryptoAPI keys if CNG is not available. + ptr::null_mut(), + &mut hprov_ncryptkey_handle, + &mut key_spec, + &mut must_free_hprov_ncryptkey_handle, + ) + } != 0; + if result { + if key_spec != CERT_NCRYPT_KEY_SPEC { + // The key is linked to a CryptoAPI provider (CSP). + // Because the use of CryptoAPI providers is deprecated, and because + // most CryptoAPI providers do not support SHA-2, we need to translate + // the CryptoAPI handle to get a CNG key handle by using NCryptTranslateHandle. + // The translation will fail if there is no CNG provider that + // is registered with a name or alias that matches the name of the CryptoAPI + // provider. If that happens, then the certificate is simply unusable for + // signing / decryption. + + let result = unsafe { + NCryptTranslateHandle( + ptr::null_mut(), + &mut hkey, + hprov_ncryptkey_handle, + 0, + key_spec, + 0, + ) + }; + if result == 0 { + must_free_hkey = true; // Always true if we were able translate the handle. + } else { + info!( + "could not translate the private key for certificate {} (0x{:08X})", + cert_thumbprint, result + ); + } + + // We can now release the CryptoAPI handle if we are instructed to do so. + if must_free_hprov_ncryptkey_handle == 1 { + unsafe { + CryptReleaseContext(hprov_ncryptkey_handle, 0); + } + } + } else { + // The key is linked to a CNG provider (KSP). + // We can use the handle as is. + hkey = hprov_ncryptkey_handle; + must_free_hkey = must_free_hprov_ncryptkey_handle == 1; + } + } else { + info!( + "could not acquire the private key for certificate {} (0x{:08X})", + cert_thumbprint, + get_last_error() + ); + } + + Ok(StoreCertificate { + cert_context: unsafe { CertDuplicateCertificateContext(cert_context) }, + cert_thumbprint, + hkey, + must_free_hkey, + captured, + }) + } +} + +impl Drop for StoreCertificate { + fn drop(&mut self) { + if self.hkey != NCRYPT_KEY_HANDLE::default() && self.must_free_hkey { + unsafe { NCryptFreeObject(self.hkey) }; + } + if self.cert_context != ptr::null_mut() { + unsafe { CertFreeCertificateContext(self.cert_context) }; + } + } +} + +impl Deref for StoreCertificate { + type Target = CapturedX509Certificate; + + fn deref(&self) -> &Self::Target { + &self.captured + } +} + +impl Signer for StoreCertificate { + fn try_sign(&self, message: &[u8]) -> Result { + // First, we need to ensure that the signer has a private key. + if self.hkey == NCRYPT_KEY_HANDLE::default() { + return Err(signature::Error::from_source(format!( + "certificate {} does not have a private key", + self.cert_thumbprint + ))); + } + + if let Some(cn) = self.captured.subject_common_name() { + warn!( + "attempting to create signature using Windows store certificate: {} ({})", + cn, self.cert_thumbprint + ); + } else { + warn!( + "attempting to create signature using Windows store certificate: {}", + self.cert_thumbprint + ); + } + + let signature_algorithm = self + .signature_algorithm() + .map_err(signature::Error::from_source)?; + + // We need to set the padding for NcryptSignHash. + // Note that ECDSA signatures do not need + // padding, so it is fine to set this to null. + let (padding_info, flags) = match signature_algorithm { + SignatureAlgorithm::RsaSha1 => ( + &mut BCRYPT_PKCS1_PADDING_INFO { + pszAlgId: BCRYPT_SHA1_ALGORITHM as *mut u16, + } as *mut BCRYPT_PKCS1_PADDING_INFO, + NCRYPT_PAD_PKCS1_FLAG, + ), + SignatureAlgorithm::RsaSha256 => ( + &mut BCRYPT_PKCS1_PADDING_INFO { + pszAlgId: BCRYPT_SHA256_ALGORITHM as *mut u16, + } as *mut BCRYPT_PKCS1_PADDING_INFO, + NCRYPT_PAD_PKCS1_FLAG, + ), + SignatureAlgorithm::RsaSha384 => ( + &mut BCRYPT_PKCS1_PADDING_INFO { + pszAlgId: BCRYPT_SHA384_ALGORITHM as *mut u16, + } as *mut BCRYPT_PKCS1_PADDING_INFO, + NCRYPT_PAD_PKCS1_FLAG, + ), + SignatureAlgorithm::RsaSha512 => ( + &mut BCRYPT_PKCS1_PADDING_INFO { + pszAlgId: BCRYPT_SHA512_ALGORITHM as *mut u16, + } as *mut BCRYPT_PKCS1_PADDING_INFO, + NCRYPT_PAD_PKCS1_FLAG, + ), + SignatureAlgorithm::EcdsaSha256 => { + (ptr::null_mut() as *mut BCRYPT_PKCS1_PADDING_INFO, 0) + } + SignatureAlgorithm::EcdsaSha384 => { + (ptr::null_mut() as *mut BCRYPT_PKCS1_PADDING_INFO, 0) + } + SignatureAlgorithm::Ed25519 => { + return Err(signature::Error::from_source( + "ed25519 not supported on windows", + )); + } + SignatureAlgorithm::NoSignature(_) => { + return Err(signature::Error::from_source("digest only signature")); + } + }; + + let key_algorithm = self + .key_algorithm() + .ok_or(X509CertificateError::UnknownDigestAlgorithm( + "failed to resolve key algorithm for certificate".into(), + )) + .map_err(signature::Error::from_source)?; + + let digest_algorithm = signature_algorithm + .digest_algorithm() + .ok_or(X509CertificateError::UnknownDigestAlgorithm( + "unable to resolve digest algorithm from signature algorithm".into(), + )) + .map_err(signature::Error::from_source)?; + + // We need to create the hash over the message. + let hash = match key_algorithm { + KeyAlgorithm::Rsa => digest_algorithm.digest_data(message), + KeyAlgorithm::Ecdsa(EcdsaCurve::Secp256r1) => digest_algorithm.digest_data(message), + KeyAlgorithm::Ecdsa(EcdsaCurve::Secp384r1) => digest_algorithm.digest_data(message), + KeyAlgorithm::Ed25519 => { + return Err(signature::Error::from_source( + "ed25519 not supported on windows", + )); + } + }; + + // We sign using NCryptSignHash. + let mut signature: Vec = Vec::new(); + let mut signature_len: u32 = 0; + let result = unsafe { + NCryptSignHash( + self.hkey, + padding_info as *mut core::ffi::c_void, + hash.as_ptr(), + hash.len() as u32, + ptr::null_mut(), + 0, + &mut signature_len, + flags, + ) + }; + if result != 0 { + return Err(signature::Error::from_source(format!("error when attempting to create signature with certificate {} (NCryptSignHash 1): 0x{:08X}", self.cert_thumbprint, result))); + } + signature.resize(signature_len as usize, 0); + let result = unsafe { + NCryptSignHash( + self.hkey, + padding_info as *mut core::ffi::c_void, + hash.as_ptr(), + hash.len() as u32, + signature.as_mut_ptr(), + signature.len() as u32, + &mut signature_len, + flags, + ) + }; + if result != 0 { + return Err(signature::Error::from_source(format!("error when attempting to create signature with certificate {} (NCryptSignHash 2): 0x{:08X}", self.cert_thumbprint, result))); + } + signature.resize(signature_len as usize, 0); + + return Ok(Signature::from(signature)); + } +} + +impl Sign for StoreCertificate { + fn sign(&self, message: &[u8]) -> Result<(Vec, SignatureAlgorithm), X509CertificateError> { + let algorithm = self.signature_algorithm()?; + + Ok((self.try_sign(message)?.into(), algorithm)) + } + + fn key_algorithm(&self) -> Option { + self.captured.key_algorithm() + } + + fn public_key_data(&self) -> Bytes { + self.captured.public_key_data() + } + + fn signature_algorithm(&self) -> Result { + self.captured + .signature_algorithm() + .ok_or(X509CertificateError::UnknownSignatureAlgorithm(format!( + "{:?}", + self.captured.signature_algorithm_oid() + ))) + } + + fn private_key_data(&self) -> Option>> { + None + } + + fn rsa_primes( + &self, + ) -> Result>, Zeroizing>)>, X509CertificateError> { + Ok(None) + } +} + +impl KeyInfoSigner for StoreCertificate {} + +impl PublicKeyPeerDecrypt for StoreCertificate { + fn decrypt(&self, ciphertext: &[u8]) -> Result, RemoteSignError> { + // First, we need to check if the signer has a private key. + if self.hkey == NCRYPT_KEY_HANDLE::default() { + return Err(RemoteSignError::Crypto( + "certificate does not have a private key".into(), + )); + } + + if let Some(cn) = self.captured.subject_common_name() { + warn!( + "attempting to decrypt using Windows store certificate: {} ({})", + cn, self.cert_thumbprint + ); + } else { + warn!( + "attempting to decrypt using Windows store certificate: {}", + self.cert_thumbprint + ); + } + + // We set the OAEP padding info. + let padding_info: *mut BCRYPT_OAEP_PADDING_INFO = &mut BCRYPT_OAEP_PADDING_INFO { + pszAlgId: BCRYPT_SHA256_ALGORITHM as *mut u16, + cbLabel: 0, + pbLabel: ptr::null_mut(), + }; + + // We decrypt using NCryptDecrypt. + let mut plaintext: Vec = Vec::new(); + let mut plaintext_len: u32 = 0; + let result = unsafe { + NCryptDecrypt( + self.hkey, + ciphertext.as_ptr(), + ciphertext.len() as u32, + padding_info as *mut core::ffi::c_void, + ptr::null_mut(), + 0, + &mut plaintext_len, + BCRYPT_PAD_OAEP, + ) + }; + if result != 0 { + return Err(RemoteSignError::Crypto(format!("error when attempting to decrypt ciphertext with certificate {} (NCryptDecrypt 1): 0x{:08X}", self.cert_thumbprint, result))); + } + plaintext.resize(plaintext_len as usize, 0); + let result = unsafe { + NCryptDecrypt( + self.hkey, + ciphertext.as_ptr(), + ciphertext.len() as u32, + padding_info as *mut core::ffi::c_void, + plaintext.as_mut_ptr(), + plaintext.len() as u32, + &mut plaintext_len, + BCRYPT_PAD_OAEP, + ) + }; + if result != 0 { + return Err(RemoteSignError::Crypto(format!("error when attempting to decrypt ciphertext with certificate {} (NCryptDecrypt 2): 0x{:08X}", self.cert_thumbprint, result))); + } + plaintext.resize(plaintext_len as usize, 0); + + return Ok(plaintext); + } +} + +impl PrivateKey for StoreCertificate { + fn as_key_info_signer(&self) -> &dyn KeyInfoSigner { + self + } + + fn to_public_key_peer_decrypt( + &self, + ) -> Result, AppleCodesignError> { + Ok(Box::new(self.clone())) + } + + fn finish(&self) -> Result<(), AppleCodesignError> { + Ok(()) + } +} + +impl StoreCertificate { + /// Obtain a new [CapturedX509Certificate] for this item. + pub fn as_captured_x509_certificate(&self) -> CapturedX509Certificate { + self.captured.clone() + } +} + +fn find_certificates( + store_name: StoreName, + store_type: StoreType, +) -> Result, AppleCodesignError> { + let mut certs = vec![]; + + let store_type_str: &'static str = store_type.into(); + let store_name_str: &'static str = store_name.into(); + + let store_type_wstr = U16CString::from_str(store_type_str).map_err(|_| { + AppleCodesignError::WindowsStoreError(format!( + "could not convert store type {} to wide string (this should not happen)", + store_type_str + )) + })?; + + let dwflags = CERT_OPEN_STORE_FLAGS::from(store_name); + + // Open the certificate store. + let store_handle = unsafe { + CertOpenStore( + CERT_STORE_PROV_SYSTEM_W as _, + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + HCRYPTPROV_LEGACY::default(), + dwflags | CERT_STORE_OPEN_EXISTING_FLAG, + store_type_wstr.as_ptr() as _, + ) + }; + if store_handle.is_null() { + return Err(AppleCodesignError::WindowsStoreError(format!( + "could not open store {} with store type {} (this should not happen): 0x{:08X}", + store_name_str, + store_type_str, + get_last_error() + ))); + } + + // Enumerate the certificates. + let mut cert_context = ptr::null_mut(); + loop { + // Get the next certificate. + cert_context = unsafe { + CertFindCertificateInStore( + store_handle, + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + 0, + CERT_FIND_ANY, + ptr::null_mut(), + cert_context, + ) + }; + if cert_context.is_null() { + break; + } + + match StoreCertificate::new(cert_context) { + Ok(cert) => certs.push(cert), + Err(err) => { + error!( + "failed to create Windows store certificate for certificate ({})", + err + ); + } + }; + } + + // Close the store. + unsafe { CertCloseStore(store_handle, 0) }; + + Ok(certs) +} + +/// Locate code signing certificates in the Windows store. +/// Since end user certificates are normally located in the `MY` store, +/// we hard-code the store type to `MY`. +/// We also only return certificates that have the `Apple Code Signing` extension +/// and a valid private key. +pub fn windows_store_find_code_signing_certificates( + store_name: StoreName, +) -> Result, AppleCodesignError> { + let certs = find_certificates(store_name, StoreType::MY)?; + + Ok(certs + .into_iter() + .filter(|cert| { + !cert.captured.apple_code_signing_extensions().is_empty() + && cert.hkey != NCRYPT_KEY_HANDLE::default() + }) + .collect::>()) +} + +/// Find the x509 certificate chain for a certificate given search parameters. +/// +/// `store_name` specifies which store to operate on. +/// +/// `sha1_fingerprint` specifies the SHA1 digest of the certificate to search for. +/// You can find this in `certmgr.msc` or `certlm.msc` by clicking on the certificate in +/// question and looking for `Thumbprint` under the `Details` tab. +pub fn windows_store_find_certificate_chain( + store_name: StoreName, + sha1_fingerprint: &str, +) -> Result, AppleCodesignError> { + // We look for the code signing certificate in the MY store. + let user_certs = find_certificates(store_name, StoreType::MY)?; + + // Now search for the requested start certificate and pull the thread until + // we get to a self-signed certificate. + let start_cert: &CapturedX509Certificate = user_certs + .iter() + .find_map(|cert| { + if let Ok(digest) = cert.captured.sha1_fingerprint() { + // Convert the Digest into a byte array + let digest_bytes = digest.as_ref(); + + // Get the hex representation of the digest + let digest_hex: String = hex::encode(digest_bytes); + + if digest_hex.to_lowercase() == sha1_fingerprint.to_lowercase() { + Some(&cert.captured) + } else { + None + } + } else { + None + } + }) + .ok_or_else(|| { + AppleCodesignError::CertificateNotFound(format!("Thumbprint={}", sha1_fingerprint)) + })?; + + // We look for the certificate chain in the CA and ROOT Stores. + let ca_certs = find_certificates(store_name, StoreType::CA)? + .into_iter() + .chain(find_certificates(store_name, StoreType::ROOT)?.into_iter()) + .collect::>(); + + let chain = std::iter::once(start_cert.clone()) + .chain( + start_cert + .resolve_signing_chain(ca_certs.iter().map(|cert| &cert.captured)) + .into_iter() + .cloned(), + ) + .collect::>(); + + Ok(chain) +} diff --git a/3rdparty/apple-codesign-0.29.0/src/yubikey.rs b/3rdparty/apple-codesign-0.29.0/src/yubikey.rs new file mode 100644 index 00000000..a054249a --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/src/yubikey.rs @@ -0,0 +1,716 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Yubikey interaction. + +use { + crate::{ + cryptography::{rsa_oaep_post_decrypt_decode, PrivateKey}, + remote_signing::{session_negotiation::PublicKeyPeerDecrypt, RemoteSignError}, + AppleCodesignError, + }, + bcder::encode::Values, + bytes::Bytes, + der::Encode, + log::{error, warn}, + signature::Signer, + std::{ + ops::DerefMut, + sync::{Arc, Mutex, MutexGuard}, + }, + x509_certificate::{ + asn1time, rfc3280, rfc5280, CapturedX509Certificate, EcdsaCurve, KeyAlgorithm, + KeyInfoSigner, Sign, Signature, SignatureAlgorithm, X509CertificateError, + }, + yubikey::{ + certificate::{CertInfo, Certificate as YkCertificate}, + piv::{import_ecc_key, import_rsa_key, AlgorithmId, SlotId}, + Error as YkError, MgmKey, PinPolicy, TouchPolicy, YubiKey as RawYubiKey, + }, + zeroize::Zeroizing, +}; + +/// A function that will attempt to resolve the PIN to unlock a YubiKey. +pub trait PinCallback: Fn() -> Result, AppleCodesignError> + Send + Sync {} +impl Result, AppleCodesignError> + Send + Sync> PinCallback for T {} + +fn algorithm_from_certificate( + cert: &CapturedX509Certificate, +) -> Result { + let key_algorithm = cert + .key_algorithm() + .ok_or(X509CertificateError::UnknownKeyAlgorithm(format!( + "{:?}", + cert.key_algorithm_oid() + )))?; + + match key_algorithm { + KeyAlgorithm::Rsa => match cert.rsa_public_key_data()?.modulus.as_slice().len() { + 129 => Ok(AlgorithmId::Rsa1024), + 257 => Ok(AlgorithmId::Rsa2048), + _ => Err(X509CertificateError::Other( + "unable to determine RSA key algorithm".into(), + )), + }, + KeyAlgorithm::Ed25519 => Err(X509CertificateError::UnknownKeyAlgorithm( + "unable to use ed25519 keys with smartcards".into(), + )), + KeyAlgorithm::Ecdsa(curve) => match curve { + EcdsaCurve::Secp256r1 => Ok(AlgorithmId::EccP256), + EcdsaCurve::Secp384r1 => Ok(AlgorithmId::EccP384), + }, + } +} + +/// Describes the needed authentication for an operation. +pub enum RequiredAuthentication { + Pin, + ManagementKey, + ManagementKeyAndPin, +} + +impl RequiredAuthentication { + pub fn requires_pin(&self) -> bool { + match self { + Self::Pin | Self::ManagementKeyAndPin => true, + Self::ManagementKey => false, + } + } + + pub fn requires_management_key(&self) -> bool { + match self { + Self::ManagementKey | Self::ManagementKeyAndPin => true, + Self::Pin => false, + } + } +} + +fn attempt_authenticated_operation( + yk: &mut RawYubiKey, + op: impl Fn(&mut RawYubiKey) -> Result, + required_authentication: RequiredAuthentication, + get_device_pin: Option<&dyn PinCallback>, +) -> Result { + const MAX_ATTEMPTS: u8 = 3; + + for attempt in 1..MAX_ATTEMPTS + 1 { + warn!("attempt {}/{}", attempt, MAX_ATTEMPTS); + + match op(yk) { + Ok(x) => { + return Ok(x); + } + Err(AppleCodesignError::YubiKey(YkError::AuthenticationError)) => { + // This was our last attempt. Give up now. + if attempt == MAX_ATTEMPTS { + return Err(AppleCodesignError::SmartcardFailedAuthentication); + } + + warn!("device refused operation due to authentication error"); + + if required_authentication.requires_management_key() { + match yk.authenticate(MgmKey::default()) { + Ok(()) => { + warn!("management key authentication successful"); + } + Err(e) => { + error!("management key authentication failure: {}", e); + continue; + } + } + } + + if required_authentication.requires_pin() { + if let Some(pin_cb) = get_device_pin { + let pin = Zeroizing::new(pin_cb().map_err(|e| { + X509CertificateError::Other(format!( + "error retrieving device pin: {}", + e + )) + })?); + + match yk.verify_pin(&pin) { + Ok(()) => { + warn!("pin verification successful"); + } + Err(e) => { + error!("pin verification failure: {}", e); + continue; + } + } + } else { + warn!( + "unable to retrieve device pin; future attempts will fail; giving up" + ); + return Err(AppleCodesignError::SmartcardFailedAuthentication); + } + } + } + Err(e) => { + return Err(e); + } + } + } + + Err(AppleCodesignError::SmartcardFailedAuthentication) +} + +/// Represents a connection to a yubikey device. +pub struct YubiKey { + yk: Arc>, + pin_callback: Option>, +} + +impl From for YubiKey { + fn from(yk: RawYubiKey) -> Self { + Self { + yk: Arc::new(Mutex::new(yk)), + pin_callback: None, + } + } +} + +impl YubiKey { + /// Construct a new instance. + pub fn new() -> Result { + let yk = Arc::new(Mutex::new(RawYubiKey::open()?)); + + Ok(Self { + yk, + pin_callback: None, + }) + } + + /// Set a callback function to be used for retrieving the PIN. + pub fn set_pin_callback(&mut self, cb: impl PinCallback + 'static) { + self.pin_callback = Some(Arc::new(cb)); + } + + pub fn inner(&self) -> Result, AppleCodesignError> { + self.yk.lock().map_err(|_| AppleCodesignError::PoisonedLock) + } + + /// Find certificates in this device. + pub fn find_certificates( + &mut self, + ) -> Result, AppleCodesignError> { + let mut guard = self.inner()?; + let yk = guard.deref_mut(); + + let slots = yk + .piv_keys()? + .into_iter() + .map(|key| key.slot()) + .collect::>(); + + let mut res = vec![]; + + for slot in slots { + let cert = YkCertificate::read(yk, slot)?; + + let cert = CapturedX509Certificate::from_der(cert.cert.to_der()?)?; + + res.push((slot, cert)); + } + + Ok(res) + } + + /// Obtain an entity for creating signatures using a certificate at a slot. + pub fn get_certificate_signer( + &mut self, + slot_id: SlotId, + ) -> Result, AppleCodesignError> { + Ok(self + .find_certificates()? + .into_iter() + .find_map(|(slot, cert)| { + if slot == slot_id { + Some(CertificateSigner { + yk: self.yk.clone(), + slot: slot_id, + cert, + pin_callback: self.pin_callback.clone(), + }) + } else { + None + } + })) + } + + fn import_rsa_key( + &mut self, + p: &[u8], + q: &[u8], + cert: &CapturedX509Certificate, + slot: SlotId, + touch_policy: TouchPolicy, + pin_policy: PinPolicy, + ) -> Result<(), AppleCodesignError> { + let slot_pretty = hex::encode([u8::from(slot)]); + + let public_key_data = cert.rsa_public_key_data()?; + + let algorithm = match public_key_data.modulus.as_slice().len() { + 129 => AlgorithmId::Rsa1024, + 257 => AlgorithmId::Rsa2048, + _ => { + return Err(X509CertificateError::Other( + "unable to determine RSA key algorithm".into(), + ) + .into()); + } + }; + + warn!( + "attempting import of {:?} private key to slot {}", + algorithm, slot_pretty + ); + + let mut yk = self.inner()?; + + attempt_authenticated_operation( + yk.deref_mut(), + |yk| { + let rsa_key = ::yubikey::piv::RsaKeyData::new(p, q)?; + + import_rsa_key(yk, slot, algorithm, rsa_key, touch_policy, pin_policy)?; + + Ok(()) + }, + RequiredAuthentication::ManagementKeyAndPin, + self.pin_callback.as_deref(), + )?; + + Ok(()) + } + + fn import_ecdsa_key( + &mut self, + private_key: &[u8], + cert: &CapturedX509Certificate, + slot: SlotId, + touch_policy: TouchPolicy, + pin_policy: PinPolicy, + ) -> Result<(), AppleCodesignError> { + let slot_pretty = hex::encode([u8::from(slot)]); + + let algorithm = algorithm_from_certificate(cert)?; + + warn!( + "attempting import of ECDSA private key to slot {}", + slot_pretty + ); + + let mut yk = self.inner()?; + + attempt_authenticated_operation( + yk.deref_mut(), + |yk| { + import_ecc_key(yk, slot, algorithm, private_key, touch_policy, pin_policy)?; + + Ok(()) + }, + RequiredAuthentication::ManagementKeyAndPin, + self.pin_callback.as_deref(), + )?; + + Ok(()) + } + + /// Attempt to import a private key and certificate into the YubiKey. + pub fn import_key( + &mut self, + slot: SlotId, + key: &dyn KeyInfoSigner, + cert: &CapturedX509Certificate, + touch_policy: TouchPolicy, + pin_policy: PinPolicy, + ) -> Result<(), AppleCodesignError> { + let slot_pretty = hex::encode([u8::from(slot)]); + + match cert.key_algorithm() { + Some(KeyAlgorithm::Rsa) => { + let (p, q) = key.rsa_primes()?.ok_or_else(|| { + X509CertificateError::Other( + "could not locate RSA private key parameters".into(), + ) + })?; + + self.import_rsa_key(&p, &q, cert, slot, touch_policy, pin_policy)?; + } + Some(KeyAlgorithm::Ecdsa(_)) => { + let private_key = key.private_key_data().ok_or_else(|| { + X509CertificateError::Other("could not retrieve private key data".into()) + })?; + + self.import_ecdsa_key(&private_key, cert, slot, touch_policy, pin_policy)?; + } + Some(algorithm) => { + return Err(AppleCodesignError::CertificateUnsupportedKeyAlgorithm( + algorithm, + )); + } + None => { + return Err(X509CertificateError::UnknownKeyAlgorithm("unknown".into()).into()); + } + } + + warn!( + "successfully wrote private key to slot {}; proceeding to write certificate", + slot_pretty + ); + + // The key is imported! Now try to write the public certificate next to it. + self.import_certificate(slot, cert)?; + + warn!("successfully wrote certificate to slot {}", slot_pretty); + + Ok(()) + } + + /// Generate a new private key in the specified slot. + pub fn generate_key( + &mut self, + slot: SlotId, + touch_policy: TouchPolicy, + pin_policy: PinPolicy, + ) -> Result<(), AppleCodesignError> { + let slot_pretty = hex::encode([u8::from(slot)]); + + let mut yk = self.inner()?; + + // Apple seems to require RSA 2048 in their CSR requests. So hardcode until we + // have a reason to support others. + let algorithm = AlgorithmId::Rsa2048; + let key_algorithm = KeyAlgorithm::Rsa; + let signature_algorithm = SignatureAlgorithm::RsaSha256; + + // There's unfortunately some hackiness here. + // + // We don't have an API to access the public key info for a slot containing a private key + // and no certificate. In order to get around this limitation and allow our signer + // implementation to work (which is needed in order to issue a CSR with the new key), + // we import a fake certificate into the slot. The certificate has the signature and + // public key info of a "real" certificate. This enables us to sign using the private key. + // We don't even bother with self signing the certificate because we don't even want to + // give the illusion that the certificate is proper. + + warn!( + "attempting to generate {:?} key in slot {}", + algorithm, slot_pretty, + ); + + // Any existing certificate would stop working once its private key changes. + // So delete the certificate first to avoid false promises of a working certificate + // in the slot. + attempt_authenticated_operation( + yk.deref_mut(), + |yk| { + warn!("ensuring slot doesn't contain a certificate"); + Ok(YkCertificate::delete(yk, slot)?) + }, + RequiredAuthentication::ManagementKeyAndPin, + self.pin_callback.as_deref(), + )?; + + let key_info = attempt_authenticated_operation( + yk.deref_mut(), + |yk| { + warn!("generating new key on device..."); + Ok(yubikey::piv::generate( + yk, + slot, + algorithm, + pin_policy, + touch_policy, + )?) + }, + RequiredAuthentication::ManagementKeyAndPin, + self.pin_callback.as_deref(), + )?; + + warn!("private key successfully generated"); + + let mut subject = rfc3280::Name::default(); + subject + .append_common_name_utf8_string("unusable placeholder certificate") + .map_err(|e| AppleCodesignError::CertificateBuildError(format!("{:?}", e)))?; + + // We don't have an API to access the public key info for a slot containing a private key + // and no certificate. So we write a placeholder self-signed certificate to allow future + // operations to have access to public key metadata. + let tbs_certificate = rfc5280::TbsCertificate { + version: Some(rfc5280::Version::V3), + serial_number: 1.into(), + signature: signature_algorithm.into(), + issuer: subject.clone(), + validity: rfc5280::Validity { + not_before: asn1time::Time::UtcTime(asn1time::UtcTime::now()), + not_after: asn1time::Time::UtcTime(asn1time::UtcTime::now()), + }, + subject, + subject_public_key_info: rfc5280::SubjectPublicKeyInfo { + algorithm: key_algorithm.into(), + subject_public_key: bcder::BitString::new( + 0, + key_info.subject_public_key.raw_bytes().to_vec().into(), + ), + }, + issuer_unique_id: None, + subject_unique_id: None, + extensions: None, + raw_data: None, + }; + + // It appears the hardware doesn't validate the signature. That makes things + // easier! + + let temp_cert = rfc5280::Certificate { + tbs_certificate, + signature_algorithm: signature_algorithm.into(), + signature: bcder::BitString::new(0, Bytes::new()), + }; + + let mut temp_cert_der = vec![]; + temp_cert + .encode_ref() + .write_encoded(bcder::Mode::Der, &mut temp_cert_der)?; + + let fake_cert = YkCertificate::from_bytes(temp_cert_der)?; + + attempt_authenticated_operation( + yk.deref_mut(), + |yk| { + warn!("writing temp cert"); + Ok(fake_cert.write(yk, slot, CertInfo::Uncompressed)?) + }, + RequiredAuthentication::ManagementKeyAndPin, + self.pin_callback.as_deref(), + )?; + + Ok(()) + } + + /// Import a certificate into a PIV slot. + /// + /// This imports the public certificate only: the existing private key is untouched. + /// + /// No validation that the certificate matches the existing key is performed. + pub fn import_certificate( + &mut self, + slot: SlotId, + cert: &CapturedX509Certificate, + ) -> Result<(), AppleCodesignError> { + let slot_pretty = hex::encode([u8::from(slot)]); + + let cert = YkCertificate::from_bytes(cert.encode_der()?)?; + + let mut yk = self.inner()?; + + attempt_authenticated_operation( + yk.deref_mut(), + |yk| { + warn!("writing certificate to slot {}", slot_pretty); + Ok(cert.write(yk, slot, CertInfo::Uncompressed)?) + }, + RequiredAuthentication::ManagementKeyAndPin, + self.pin_callback.as_deref(), + )?; + + warn!("certificate import successful"); + + Ok(()) + } +} + +/// Entity for creating signatures using a certificate in a given PIV slot. +/// +/// This needs to be its own type so we can implement [Sign]. +#[derive(Clone)] +pub struct CertificateSigner { + yk: Arc>, + slot: SlotId, + cert: CapturedX509Certificate, + pin_callback: Option>, +} + +impl Signer for CertificateSigner { + fn try_sign(&self, message: &[u8]) -> Result { + let algorithm_id = + algorithm_from_certificate(&self.cert).map_err(signature::Error::from_source)?; + + let signature_algorithm = self + .cert + .signature_algorithm() + .ok_or(X509CertificateError::UnknownDigestAlgorithm( + "failed to resolve digest algorithm for certificate".into(), + )) + .map_err(signature::Error::from_source)?; + + // We need to feed the digest into the signing api, not the data to be + // digested. + let digest_algorithm = signature_algorithm + .digest_algorithm() + .ok_or(X509CertificateError::UnknownDigestAlgorithm( + "unable to resolve digest algorithm from signature algorithm".into(), + )) + .map_err(signature::Error::from_source)?; + + // Need to apply PKCS#1 padding for RSA. + let digest = match algorithm_id { + AlgorithmId::Rsa1024 => digest_algorithm + .rsa_pkcs1_encode(message, 1024 / 8) + .map_err(signature::Error::from_source)?, + AlgorithmId::Rsa2048 => digest_algorithm + .rsa_pkcs1_encode(message, 2048 / 8) + .map_err(signature::Error::from_source)?, + AlgorithmId::EccP256 => digest_algorithm.digest_data(message), + AlgorithmId::EccP384 => digest_algorithm.digest_data(message), + }; + + let mut guard = self + .yk + .lock() + .map_err(|_| signature::Error::from_source("unable to acquire lock on YubiKey"))?; + + let yk = guard.deref_mut(); + + warn!("initial signing attempt may fail if the certificate requires a pin to unlock"); + + attempt_authenticated_operation( + yk, + |yk| { + let signature = ::yubikey::piv::sign_data(yk, &digest, algorithm_id, self.slot) + .map_err(AppleCodesignError::YubiKey)?; + + Ok(Signature::from(signature.to_vec())) + }, + RequiredAuthentication::Pin, + self.pin_callback.as_deref(), + ) + .map_err(signature::Error::from_source) + } +} + +impl Sign for CertificateSigner { + fn sign(&self, message: &[u8]) -> Result<(Vec, SignatureAlgorithm), X509CertificateError> { + let algorithm = self.signature_algorithm()?; + + Ok((self.try_sign(message)?.into(), algorithm)) + } + + fn key_algorithm(&self) -> Option { + self.cert.key_algorithm() + } + + fn public_key_data(&self) -> Bytes { + self.cert.public_key_data() + } + + fn signature_algorithm(&self) -> Result { + self.cert + .signature_algorithm() + .ok_or(X509CertificateError::UnknownSignatureAlgorithm(format!( + "{:?}", + self.cert.signature_algorithm_oid() + ))) + } + + fn private_key_data(&self) -> Option>> { + // We never have access to private keys stored on hardware devices. + None + } + + fn rsa_primes( + &self, + ) -> Result>, Zeroizing>)>, X509CertificateError> { + Ok(None) + } +} + +impl KeyInfoSigner for CertificateSigner {} + +impl PublicKeyPeerDecrypt for CertificateSigner { + fn decrypt(&self, ciphertext: &[u8]) -> Result, RemoteSignError> { + let mut guard = self + .yk + .lock() + .map_err(|_| RemoteSignError::Crypto("unable to acquire lock on YubiKey".into()))?; + + let yk = guard.deref_mut(); + + let algorithm_id = algorithm_from_certificate(&self.cert)?; + + // The YubiKey's decrypt primitive is super low level. So we need to undo OAEP + // padding on RSA keys first. + + attempt_authenticated_operation( + yk, + |yk| { + let plaintext = + ::yubikey::piv::decrypt_data(yk, ciphertext, algorithm_id, self.slot)?; + + let rsa_modulus_length = match algorithm_id { + AlgorithmId::Rsa1024 => Some(1024 / 8), + AlgorithmId::Rsa2048 => Some(2048 / 8), + AlgorithmId::EccP256 | AlgorithmId::EccP384 => None, + }; + + let plaintext = match algorithm_id { + // The YubiKey only does RSA decrypt without padding awareness. So we need to decode + // padding ourselves. + AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => { + let mut digest = sha2::Sha256::default(); + let mut mgf_digest = sha2::Sha256::default(); + + rsa_oaep_post_decrypt_decode( + rsa_modulus_length.unwrap(), + plaintext.to_vec(), + &mut digest, + &mut mgf_digest, + None, + ) + .map_err(|e| { + RemoteSignError::Crypto(format!("error during OAEP decoding: {}", e)) + })? + } + + AlgorithmId::EccP256 | AlgorithmId::EccP384 => plaintext.to_vec(), + }; + + Ok(plaintext) + }, + RequiredAuthentication::Pin, + self.pin_callback.as_deref(), + ) + .map_err(|e| RemoteSignError::Crypto(format!("failed to decrypt using YubiKey: {}", e))) + } +} + +impl PrivateKey for CertificateSigner { + fn as_key_info_signer(&self) -> &dyn KeyInfoSigner { + self + } + + fn to_public_key_peer_decrypt( + &self, + ) -> Result, AppleCodesignError> { + Ok(Box::new(self.clone())) + } + + fn finish(&self) -> Result<(), AppleCodesignError> { + Ok(()) + } +} + +impl CertificateSigner { + pub fn slot(&self) -> SlotId { + self.slot + } + + pub fn certificate(&self) -> &CapturedX509Certificate { + &self.cert + } +} diff --git a/3rdparty/apple-codesign-0.29.0/tests/cli_tests.rs b/3rdparty/apple-codesign-0.29.0/tests/cli_tests.rs new file mode 100644 index 00000000..22166712 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cli_tests.rs @@ -0,0 +1,290 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use { + anyhow::anyhow, + reqwest::Url, + std::{ + io::Read, + path::{Path, PathBuf}, + }, + trycmd_indygreg_fork::{schema::TryCmd, Error, TestCases}, +}; + +const COREUTILS_VERSION: &str = "0.0.22"; +/// List of coreutils binaries to materialize in trycmd test environments. +const COREUTILS_BINARIES: [&str; 11] = [ + "cat", "cp", "hashsum", "ln", "ls", "mkdir", "mv", "rm", "sort", "test", "touch", +]; + +const COREUTILS_ARTIFACT_URL: &str = "https://github.com/uutils/coreutils/releases/download"; +const COREUTILS_TAR_TRIPLES: [&str; 4] = [ + "aarch64-unknown-linux-gnu", + "i686-unknown-linux-musl", + "x86_64-apple-darwin", + "x86_64-unknown-linux-musl", +]; + +const COREUTILS_ZIP_TRIPLES: [&str; 2] = ["i686-pc-windows-msvc", "x86_64-pc-windows-msvc"]; + +/// Ensures Rust coreutils multicall binary is available. +/// +/// Essentially runs `cargo install coreutils` into the `target/coreutils` directory +/// for the current Cargo workspace project. +fn ensure_coreutils_multicall() -> anyhow::Result { + let current_exe = std::env::current_exe()?; + + let target_dir = current_exe + .parent() + .ok_or_else(|| anyhow!("unable to determine current exe parent"))? + .parent() + .ok_or_else(|| anyhow!("unable to determine parent directory of current exe directory"))? + .parent() + .ok_or_else(|| anyhow!("unable to parent grandparent of current exe directory"))?; + + let coreutils_dir = target_dir.join("coreutils"); + + let coreutils_bin_dir = coreutils_dir.join("bin"); + + let multicall_bin = coreutils_bin_dir.join("coreutils"); + + let multicall_bin = materialize_coreutils(&coreutils_dir, &multicall_bin)?; + + Ok(multicall_bin) +} + +fn materialize_coreutils(coreutils_dir: &Path, multicall_bin: &Path) -> anyhow::Result { + let triple = if cfg!(all(target_os = "linux", target_arch = "x86")) { + Some("i686-unknown-linux-musl") + } else if cfg!(all(target_os = "linux", target_arch = "x86_64")) { + Some("x86_64-unknown-linux-musl") + } else if cfg!(all(target_os = "macos", target_arch = "x86_64")) { + Some("x86_64-apple-darwin") + } else if cfg!(all(target_os = "windows", target_arch = "x86")) { + Some("i686-pc-windows-msvc") + } else if cfg!(all(target_os = "windows", target_arch = "x86_64")) { + Some("x86_64-pc-windows-msvc") + } else { + None + }; + + let mut multicall_bin = multicall_bin.to_path_buf(); + + if let Some(triple) = triple { + if triple.contains("-windows-") { + multicall_bin.set_extension("exe"); + } + } + + if multicall_bin.exists() { + return Ok(multicall_bin); + } + + match triple { + Some(triple) => { + let suffix = if COREUTILS_TAR_TRIPLES.contains(&triple) { + "tar.gz" + } else if COREUTILS_ZIP_TRIPLES.contains(&triple) { + "zip" + } else { + panic!("unhandled triple") + }; + + let filename = multicall_bin + .file_name() + .unwrap() + .to_string_lossy() + .to_string(); + + let exe_data = download_coreutils_artifact(triple, suffix, &filename)?; + eprintln!("writing {}", multicall_bin.display()); + + if let Some(parent) = multicall_bin.parent() { + std::fs::create_dir_all(parent)?; + } + + std::fs::write(&multicall_bin, exe_data)?; + simple_file_manifest::set_executable(&mut std::fs::File::open(&multicall_bin)?)?; + + Ok(multicall_bin) + } + None => { + let cargo_bin = std::env::var_os("CARGO") + .ok_or_else(|| anyhow!("unable to resolve CARGO environment variable"))?; + + eprintln!("installing Rust coreutils to {}", coreutils_dir.display()); + let output = std::process::Command::new(cargo_bin) + .args(vec![ + "install".to_string(), + "--root".to_string(), + coreutils_dir.display().to_string(), + "--version".to_string(), + COREUTILS_VERSION.to_string(), + "coreutils".to_string(), + ]) + .output()?; + + if !output.status.success() { + return Err(anyhow!( + "error installing coreutils: stdout: {}; stderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + )); + } + + Ok(multicall_bin) + } + } +} + +fn download_coreutils_artifact( + triple: &str, + suffix: &str, + multicall_filename: &str, +) -> anyhow::Result> { + let url = format!("{COREUTILS_ARTIFACT_URL}/{COREUTILS_VERSION}/coreutils-{COREUTILS_VERSION}-{triple}.{suffix}"); + + let client = get_http_client()?; + eprintln!("downloading {}", url); + let mut res = client.get(url).send()?; + let mut data = vec![]; + res.read_to_end(&mut data)?; + + match suffix { + "tar.gz" => { + eprintln!("looking for {} in tar.gz", multicall_filename); + let d = flate2::read::GzDecoder::new(std::io::Cursor::new(data)); + + let mut ar = tar::Archive::new(d); + + for entry in ar.entries()? { + let mut entry = entry?; + let path = entry.path()?; + + if !path.display().to_string().ends_with(multicall_filename) { + continue; + } + + eprintln!("extracting {} from tar.gz", path.display()); + let mut buf = vec![]; + entry.read_to_end(&mut buf)?; + + return Ok(buf); + } + + Err(anyhow!("could not find multicall binary in archive")) + } + "zip" => { + eprintln!("looking for {} in zip file", multicall_filename); + let mut archive = zip::ZipArchive::new(std::io::Cursor::new(data))?; + + let archive_name = archive + .file_names() + .find(|f| f.ends_with(multicall_filename)) + .ok_or_else(|| anyhow!("could not find multicall binary in zip file"))? + .to_string(); + + eprintln!("extracting {} from zip file", archive_name); + let mut zf = archive.by_name(&archive_name)?; + let mut buf = vec![]; + zf.read_to_end(&mut buf)?; + + Ok(buf) + } + _ => panic!("unhandled coreutils file extension"), + } +} + +pub fn get_http_client() -> reqwest::Result { + let mut builder = reqwest::blocking::ClientBuilder::new(); + + for (key, value) in std::env::vars() { + let key = key.to_lowercase(); + if key.ends_with("_proxy") { + let end = key.len() - "_proxy".len(); + let schema = &key[..end]; + + if let Ok(url) = Url::parse(&value) { + if let Some(Ok(proxy)) = match schema { + "http" => Some(reqwest::Proxy::http(url.as_str())), + "https" => Some(reqwest::Proxy::https(url.as_str())), + _ => None, + } { + builder = builder.proxy(proxy); + } + } + } + } + + builder.build() +} + +#[cfg(unix)] +fn install_coreutils_bin(multicall_bin: &Path, bin: &Path) -> Result<(), std::io::Error> { + std::os::unix::fs::symlink(multicall_bin, bin) +} + +#[cfg(windows)] +fn install_coreutils_bin(multicall_bin: &Path, bin: &Path) -> Result<(), std::io::Error> { + std::fs::copy(multicall_bin, bin).map(|_| ()) +} + +/// Custom loader for .trycmd files. +fn load_trycmd(path: &Path) -> Result { + let mut cmd = TryCmd::load_trycmd(path)?; + + // CWD should be the crate root. + let cwd = std::env::current_dir().map_err(Error::new)?; + + // We set the test to execute from a sandboxed copy of the crate root. + // This allows tests to create their own files without disturbing the + // source checkout. + cmd.fs.base = Some(cwd.clone()); + cmd.fs.cwd = Some(cwd.clone()); + cmd.fs.sandbox = Some(true); + + Ok(cmd) +} + +#[test] +fn cli_tests() { + let coreutils_multicall = ensure_coreutils_multicall().unwrap(); + let coreutils_bin = coreutils_multicall.parent().unwrap(); + + let cases = TestCases::new(); + + for bin in COREUTILS_BINARIES { + let mut bin_path = coreutils_bin.join(bin); + if cfg!(windows) { + bin_path.set_extension("exe"); + } + + if bin_path.symlink_metadata().is_err() { + install_coreutils_bin(&coreutils_multicall, &bin_path).unwrap(); + } + + cases.register_bin(bin, bin_path); + } + + cases.file_extension_loader("trycmd", load_trycmd); + + cases.case("tests/cmd/*.trycmd").case("tests/cmd/*.toml"); + + // Help output breaks without notarize feature. + if cfg!(not(feature = "notarize")) { + cases.skip("tests/cmd/encode-app-store-connect-api-key.trycmd"); + cases.skip("tests/cmd/help.trycmd"); + cases.skip("tests/cmd/notary*.trycmd"); + } + + // Tests with `ln -s` may not work on Windows. So just skip them. + if cfg!(windows) { + cases.skip("tests/cmd/sign-bundle-framework.trycmd"); + cases.skip("tests/cmd/sign-bundle-with-nested-framework.trycmd"); + cases.skip("tests/cmd/sign-bundle-electron.trycmd"); + cases.skip("tests/cmd/sign-bundle-exclude.trycmd"); + cases.skip("tests/cmd/sign-bundle-nested-symlinks.trycmd"); + cases.skip("tests/cmd/sign-bundle-symlink-overwrite.trycmd"); + } +} diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/analyze-certificate.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/analyze-certificate.trycmd new file mode 100644 index 00000000..2336f7f2 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/analyze-certificate.trycmd @@ -0,0 +1,348 @@ +``` +$ rcodesign analyze-certificate --help +Analyze an X.509 certificate for Apple code signing properties. + +Given the path to a PEM encoded X.509 certificate, this command will read the certificate and print information about it relevant to Apple code signing. + +The output of the command can be useful to learn about X.509 certificate extensions used by code signing certificates and to debug low-level properties related to certificates. + +Usage: rcodesign[EXE] analyze-certificate [OPTIONS] + +Options: + -C, --config-file + Explicit configuration file to load. + + If provided, the default configuration files are not loaded, even if they exist. + + Can be specified multiple times. Files are loaded/merged in the order given. + + The special value `/dev/null` can be used to specify an empty/null config file. It can be used to short-circuit loading of default config files. + + --smartcard-slot + Smartcard slot number of signing certificate to use (9c is common) + + -P, --profile + Configuration profile to load. + + If not specified, the implicit "default" profile is loaded. + + --smartcard-pin + Smartcard PIN used to unlock certificate + + If not provided, you will be prompted for a PIN as necessary. + + --smartcard-pin-env + Environment variable holding the smartcard PIN + + -v, --verbose... + Increase logging verbosity. Can be specified multiple times + + --keychain-domain + (macOS only) Keychain domain to operate on + + [possible values: user, system, common, dynamic] + + --keychain-fingerprint + (macOS only) SHA-256 fingerprint of certificate in Keychain to use + + --windows-store-name + (Windows only) Windows Store to operate on + + [possible values: user, machine, service] + + --windows-store-sha1-fingerprint + (Windows only) SHA-1 fingerprint of certificate in Windows Store to use + + --pem-file + Path to file containing PEM encoded certificate/key data + + --p12-file + Path to a .p12/PFX file containing a certificate key pair + + --p12-password + The password to use to open the --p12-file file + + --p12-password-file + Path to file containing password for opening --p12-file file + + --remote-signing-url + URL of a remote code signing server + + --remote-public-key + Base64 encoded public key data describing the signer + + --remote-public-key-pem-file + PEM encoded public key data describing the signer + + --remote-shared-secret + Shared secret used for remote signing + + --remote-shared-secret-env + Environment variable holding the shared secret used for remote signing + + --certificate-der-file + Path to file containing DER encoded certificate data + + -h, --help + Print help (see a summary with '-h') + +``` + +``` +$ rcodesign analyze-certificate --keychain-domain user --keychain-fingerprint fingerprint +? 2 +error: the argument '--keychain-domain ' cannot be used with '--keychain-fingerprint ' + +Usage: rcodesign[EXE] analyze-certificate --keychain-domain + +For more information, try '--help'. + +``` + +``` +$ rcodesign analyze-certificate --p12-password foo --p12-password-file path +? 2 +error: the argument '--p12-password ' cannot be used with '--p12-password-file ' + +Usage: rcodesign[EXE] analyze-certificate --p12-password + +For more information, try '--help'. + +``` + +``` +$ rcodesign analyze-certificate --remote-public-key foo --remote-public-key-pem-file path +? 2 +error: the argument '--remote-public-key ' cannot be used with '--remote-public-key-pem-file ' + +Usage: rcodesign[EXE] analyze-certificate --remote-public-key + +For more information, try '--help'. + +``` + +``` +$ rcodesign analyze-certificate --remote-shared-secret secret --remote-shared-secret-env env +? 2 +error: the argument '--remote-shared-secret ' cannot be used with '--remote-shared-secret-env ' + +Usage: rcodesign[EXE] analyze-certificate --remote-shared-secret + +For more information, try '--help'. + +``` + +``` +$ rcodesign analyze-certificate --der-source src/testdata/apple-signed-developer-id-application.cer +reading DER file src/testdata/apple-signed-developer-id-application.cer +# Certificate 0 + +Subject CN: Developer ID Application: Gregory Szorc (MK22MZP987) +Issuer CN: Developer ID Certification Authority +Subject is Issuer?: false +Team ID: MK22MZP987 +SHA-1 fingerprint: d6b1f9320ce2cc552ad34f05b7fd29a62a047e87 +SHA-256 fingerprint: 7bf474b50849b231c4524731de63fa035c434ce68589db7b3c22e3d04f1dab7e +Not Valid Before: 2021-04-22T01:08:32+00:00 +Not Valid After: 2026-04-23T01:08:31+00:00 +Key Algorithm: RSA +Signature Algorithm: SHA-256 with RSA encryption +Public Key Data: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs52TZuX8/9SVXNBr6Vz5CZOmis3lCpRsSP6pKPnIfK46DlOSoob6u/wALiPKOZJOYKnnbHuJ1pjvFEHif/eJkdfovu82bwAMJnFrbCGBHmOsqfuURfc5cfaIcpred9P0mFUVpu194n74ZR2sjxJIFIMxJXgh7dSE4dKKokf/o5Orlb3d84i1/yY/ePSdnFIMotxrv0lvuZjdlIZE6ugoElueSyH1ZwF03UqQznJ1uuw1DSRyC0YD2l7paO+CKKpHAvsTSAZcj4X6qwx+aVgxiYcfl1z6nVDVv1m6+ChAOGyo06KpGPxFeON/Dp704UJyfyrRF7xDIf/Cu+2ftMlLswIDAQAB +Signed by Apple?: true +Apple Issuing Chain: + - Developer ID Certification Authority + - Apple Root CA + - Apple Root Certificate Authority +Guessed Certificate Profile: DeveloperIdApplication +Is Apple Root CA?: false +Is Apple Intermediate CA?: false +Apple Extended Key Usage Purpose Extensions: + - 1.3.6.1.5.5.7.3.3 (CodeSigning) +Apple Code Signing Extensions: + - 1.2.840.113635.100.6.1.33 (DeveloperIdDate) + - 1.2.840.113635.100.6.1.13 (DeveloperIdApplication) + +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs52TZuX8/9SVXNBr6Vz5 +CZOmis3lCpRsSP6pKPnIfK46DlOSoob6u/wALiPKOZJOYKnnbHuJ1pjvFEHif/eJ +kdfovu82bwAMJnFrbCGBHmOsqfuURfc5cfaIcpred9P0mFUVpu194n74ZR2sjxJI +FIMxJXgh7dSE4dKKokf/o5Orlb3d84i1/yY/ePSdnFIMotxrv0lvuZjdlIZE6ugo +ElueSyH1ZwF03UqQznJ1uuw1DSRyC0YD2l7paO+CKKpHAvsTSAZcj4X6qwx+aVgx +iYcfl1z6nVDVv1m6+ChAOGyo06KpGPxFeON/Dp704UJyfyrRF7xDIf/Cu+2ftMlL +swIDAQAB +-----END PUBLIC KEY----- + +-----BEGIN CERTIFICATE----- +MIIFpjCCBI6gAwIBAgIIfTmR3fnRGfowDQYJKoZIhvcNAQELBQAweTEtMCsGA1UE +AwwkRGV2ZWxvcGVyIElEIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSYwJAYDVQQL +DB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUg +SW5jLjELMAkGA1UEBhMCVVMwHhcNMjEwNDIyMDEwODMyWhcNMjYwNDIzMDEwODMx +WjCBlTEaMBgGCgmSJomT8ixkAQEMCk1LMjJNWlA5ODcxPTA7BgNVBAMMNERldmVs +b3BlciBJRCBBcHBsaWNhdGlvbjogR3JlZ29yeSBTem9yYyAoTUsyMk1aUDk4Nykx +EzARBgNVBAsMCk1LMjJNWlA5ODcxFjAUBgNVBAoMDUdyZWdvcnkgU3pvcmMxCzAJ +BgNVBAYTAlVTMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs52TZuX8 +/9SVXNBr6Vz5CZOmis3lCpRsSP6pKPnIfK46DlOSoob6u/wALiPKOZJOYKnnbHuJ +1pjvFEHif/eJkdfovu82bwAMJnFrbCGBHmOsqfuURfc5cfaIcpred9P0mFUVpu19 +4n74ZR2sjxJIFIMxJXgh7dSE4dKKokf/o5Orlb3d84i1/yY/ePSdnFIMotxrv0lv +uZjdlIZE6ugoElueSyH1ZwF03UqQznJ1uuw1DSRyC0YD2l7paO+CKKpHAvsTSAZc +j4X6qwx+aVgxiYcfl1z6nVDVv1m6+ChAOGyo06KpGPxFeON/Dp704UJyfyrRF7xD +If/Cu+2ftMlLswIDAQABo4ICEzCCAg8wDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAW +gBRXF+2iz9x8mKEQ4Py+hy0s8uMXVDBABggrBgEFBQcBAQQ0MDIwMAYIKwYBBQUH +MAGGJGh0dHA6Ly9vY3NwLmFwcGxlLmNvbS9vY3NwMDMtZGV2aWQwNjCCAR0GA1Ud +IASCARQwggEQMIIBDAYJKoZIhvdjZAUBMIH+MIHDBggrBgEFBQcCAjCBtgyBs1Jl +bGlhbmNlIG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMg +YWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1z +IGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBj +ZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMDYGCCsGAQUFBwIBFipo +dHRwOi8vd3d3LmFwcGxlLmNvbS9jZXJ0aWZpY2F0ZWF1dGhvcml0eS8wFgYDVR0l +AQH/BAwwCgYIKwYBBQUHAwMwHQYDVR0OBBYEFJWxErKAkOUMhUHKIfurpWswr6OM +MA4GA1UdDwEB/wQEAwIHgDAfBgoqhkiG92NkBgEhBBEMDzIwMjEwNDIyMDAwMDAw +WjATBgoqhkiG92NkBgENAQH/BAIFADANBgkqhkiG9w0BAQsFAAOCAQEAGpHZefiX +l5n79MZM8GFVs5oGJOdspORMFa9SxWa59LaBAWpkUbVtgic25CQtaIZddb7vgMpq +uCqQIFiYz3MfdaPgKMqM1MGNVOw14Z4nM1z9CgLctBS7ie2ScKf9nJnbLCm2qCeS +5A13mUagb7lwdzI3Z5G6JP3+ea46Kg0bY9c4TCAZr8v/vpBWktnBimuQ9Rz3PPTT +HPCYuazSBKos0g3gNLgzGdQyZLDvyfyqJ3SvIAvGBYC1SxoGUB8RBeZuYLRQOylA +72DfBd+bt1wASaSNTosSAauo3Sd3cvIwAtlTtWAT3ISZ36ygnbgwfaarz8Q04MDc +4Y74EVg2IvFyLA== +-----END CERTIFICATE----- + + +``` + +``` +$ rcodesign analyze-certificate --pem-source src/testdata/apple-signed-developer-id-application.pem +reading PEM data from src/testdata/apple-signed-developer-id-application.pem +# Certificate 0 + +Subject CN: Developer ID Application: Gregory Szorc (MK22MZP987) +Issuer CN: Developer ID Certification Authority +Subject is Issuer?: false +Team ID: MK22MZP987 +SHA-1 fingerprint: d6b1f9320ce2cc552ad34f05b7fd29a62a047e87 +SHA-256 fingerprint: 7bf474b50849b231c4524731de63fa035c434ce68589db7b3c22e3d04f1dab7e +Not Valid Before: 2021-04-22T01:08:32+00:00 +Not Valid After: 2026-04-23T01:08:31+00:00 +Key Algorithm: RSA +Signature Algorithm: SHA-256 with RSA encryption +Public Key Data: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs52TZuX8/9SVXNBr6Vz5CZOmis3lCpRsSP6pKPnIfK46DlOSoob6u/wALiPKOZJOYKnnbHuJ1pjvFEHif/eJkdfovu82bwAMJnFrbCGBHmOsqfuURfc5cfaIcpred9P0mFUVpu194n74ZR2sjxJIFIMxJXgh7dSE4dKKokf/o5Orlb3d84i1/yY/ePSdnFIMotxrv0lvuZjdlIZE6ugoElueSyH1ZwF03UqQznJ1uuw1DSRyC0YD2l7paO+CKKpHAvsTSAZcj4X6qwx+aVgxiYcfl1z6nVDVv1m6+ChAOGyo06KpGPxFeON/Dp704UJyfyrRF7xDIf/Cu+2ftMlLswIDAQAB +Signed by Apple?: true +Apple Issuing Chain: + - Developer ID Certification Authority + - Apple Root CA + - Apple Root Certificate Authority +Guessed Certificate Profile: DeveloperIdApplication +Is Apple Root CA?: false +Is Apple Intermediate CA?: false +Apple Extended Key Usage Purpose Extensions: + - 1.3.6.1.5.5.7.3.3 (CodeSigning) +Apple Code Signing Extensions: + - 1.2.840.113635.100.6.1.33 (DeveloperIdDate) + - 1.2.840.113635.100.6.1.13 (DeveloperIdApplication) + +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs52TZuX8/9SVXNBr6Vz5 +CZOmis3lCpRsSP6pKPnIfK46DlOSoob6u/wALiPKOZJOYKnnbHuJ1pjvFEHif/eJ +kdfovu82bwAMJnFrbCGBHmOsqfuURfc5cfaIcpred9P0mFUVpu194n74ZR2sjxJI +FIMxJXgh7dSE4dKKokf/o5Orlb3d84i1/yY/ePSdnFIMotxrv0lvuZjdlIZE6ugo +ElueSyH1ZwF03UqQznJ1uuw1DSRyC0YD2l7paO+CKKpHAvsTSAZcj4X6qwx+aVgx +iYcfl1z6nVDVv1m6+ChAOGyo06KpGPxFeON/Dp704UJyfyrRF7xDIf/Cu+2ftMlL +swIDAQAB +-----END PUBLIC KEY----- + +-----BEGIN CERTIFICATE----- +MIIFpjCCBI6gAwIBAgIIfTmR3fnRGfowDQYJKoZIhvcNAQELBQAweTEtMCsGA1UE +AwwkRGV2ZWxvcGVyIElEIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSYwJAYDVQQL +DB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUg +SW5jLjELMAkGA1UEBhMCVVMwHhcNMjEwNDIyMDEwODMyWhcNMjYwNDIzMDEwODMx +WjCBlTEaMBgGCgmSJomT8ixkAQEMCk1LMjJNWlA5ODcxPTA7BgNVBAMMNERldmVs +b3BlciBJRCBBcHBsaWNhdGlvbjogR3JlZ29yeSBTem9yYyAoTUsyMk1aUDk4Nykx +EzARBgNVBAsMCk1LMjJNWlA5ODcxFjAUBgNVBAoMDUdyZWdvcnkgU3pvcmMxCzAJ +BgNVBAYTAlVTMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs52TZuX8 +/9SVXNBr6Vz5CZOmis3lCpRsSP6pKPnIfK46DlOSoob6u/wALiPKOZJOYKnnbHuJ +1pjvFEHif/eJkdfovu82bwAMJnFrbCGBHmOsqfuURfc5cfaIcpred9P0mFUVpu19 +4n74ZR2sjxJIFIMxJXgh7dSE4dKKokf/o5Orlb3d84i1/yY/ePSdnFIMotxrv0lv +uZjdlIZE6ugoElueSyH1ZwF03UqQznJ1uuw1DSRyC0YD2l7paO+CKKpHAvsTSAZc +j4X6qwx+aVgxiYcfl1z6nVDVv1m6+ChAOGyo06KpGPxFeON/Dp704UJyfyrRF7xD +If/Cu+2ftMlLswIDAQABo4ICEzCCAg8wDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAW +gBRXF+2iz9x8mKEQ4Py+hy0s8uMXVDBABggrBgEFBQcBAQQ0MDIwMAYIKwYBBQUH +MAGGJGh0dHA6Ly9vY3NwLmFwcGxlLmNvbS9vY3NwMDMtZGV2aWQwNjCCAR0GA1Ud +IASCARQwggEQMIIBDAYJKoZIhvdjZAUBMIH+MIHDBggrBgEFBQcCAjCBtgyBs1Jl +bGlhbmNlIG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMg +YWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1z +IGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBj +ZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMDYGCCsGAQUFBwIBFipo +dHRwOi8vd3d3LmFwcGxlLmNvbS9jZXJ0aWZpY2F0ZWF1dGhvcml0eS8wFgYDVR0l +AQH/BAwwCgYIKwYBBQUHAwMwHQYDVR0OBBYEFJWxErKAkOUMhUHKIfurpWswr6OM +MA4GA1UdDwEB/wQEAwIHgDAfBgoqhkiG92NkBgEhBBEMDzIwMjEwNDIyMDAwMDAw +WjATBgoqhkiG92NkBgENAQH/BAIFADANBgkqhkiG9w0BAQsFAAOCAQEAGpHZefiX +l5n79MZM8GFVs5oGJOdspORMFa9SxWa59LaBAWpkUbVtgic25CQtaIZddb7vgMpq +uCqQIFiYz3MfdaPgKMqM1MGNVOw14Z4nM1z9CgLctBS7ie2ScKf9nJnbLCm2qCeS +5A13mUagb7lwdzI3Z5G6JP3+ea46Kg0bY9c4TCAZr8v/vpBWktnBimuQ9Rz3PPTT +HPCYuazSBKos0g3gNLgzGdQyZLDvyfyqJ3SvIAvGBYC1SxoGUB8RBeZuYLRQOylA +72DfBd+bt1wASaSNTosSAauo3Sd3cvIwAtlTtWAT3ISZ36ygnbgwfaarz8Q04MDc +4Y74EVg2IvFyLA== +-----END CERTIFICATE----- + + +``` + +``` +$ rcodesign analyze-certificate --p12-file src/apple-codesign-testuser.p12 --p12-password incorrect +? 1 +Error: incorrect password given when decrypting PFX data + +$ rcodesign analyze-certificate --p12-file src/apple-codesign-testuser.p12 --p12-password password123 +# Certificate 0 + +Subject CN: Test User +Issuer CN: Test User +Subject is Issuer?: true +Team ID: +SHA-1 fingerprint: b1c7f1807bb9eb61ab3d13b0ffc12a363311dbd2 +SHA-256 fingerprint: f2e635017332bcb96b44f8cc65c07f5141f5932599e706f66023314adf8b9d07 +Not Valid Before: 2021-04-22T21:51:28+00:00 +Not Valid After: 2022-04-22T21:51:28+00:00 +Key Algorithm: RSA +Signature Algorithm: SHA-256 with RSA encryption +Public Key Data: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApp0SntOtH7dgkQ1jIKrzgjW58VxqbRXpz/5sQp6AIulGS87IWMjLd/9k0+3X9+fKypMPADnbMb6CX3KgbKCJSNc2SI/g4tVg1HTo2wuVNpe1o/LaKMRZY+u/KvZBsN6gAtspayZAxYCSBxEQ7JndHq57Z+ZK4o/yT5LftOJ+LpJQk7pBMPbW6uHmYZWOMH119i7VBEtBNZhwwloAX7DlFGWBG3NtJ4HBTxwSvNkCNG04a+HK9OFuSO1vfYy5/6OqmQ5sKjgkEBWrud9TPp5hWCzrx0cGGYWprMDQ6ix2pCVp9dToecYiZOpNhgSAxioHU317M4Pf060tDUmsBBnykQIDAQAB +Signed by Apple?: false +Guessed Certificate Profile: none +Is Apple Root CA?: false +Is Apple Intermediate CA?: false +Apple Extended Key Usage Purpose Extensions: + - 1.3.6.1.5.5.7.3.3 (CodeSigning) +Apple Code Signing Extensions: + +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApp0SntOtH7dgkQ1jIKrz +gjW58VxqbRXpz/5sQp6AIulGS87IWMjLd/9k0+3X9+fKypMPADnbMb6CX3KgbKCJ +SNc2SI/g4tVg1HTo2wuVNpe1o/LaKMRZY+u/KvZBsN6gAtspayZAxYCSBxEQ7Jnd +Hq57Z+ZK4o/yT5LftOJ+LpJQk7pBMPbW6uHmYZWOMH119i7VBEtBNZhwwloAX7Dl +FGWBG3NtJ4HBTxwSvNkCNG04a+HK9OFuSO1vfYy5/6OqmQ5sKjgkEBWrud9TPp5h +WCzrx0cGGYWprMDQ6ix2pCVp9dToecYiZOpNhgSAxioHU317M4Pf060tDUmsBBny +kQIDAQAB +-----END PUBLIC KEY----- + +-----BEGIN CERTIFICATE----- +MIIDWTCCAkGgAwIBAgIBATANBgkqhkiG9w0BAQsFADBaMRIwEAYDVQQDDAlUZXN0 +IFVzZXIxEzARBgNVBAoMClB5T3hpZGl6ZXIxCzAJBgNVBAYTAlVTMSIwIAYJKoZI +hvcNAQkBFhNzb21lb25lQGV4YW1wbGUuY29tMB4XDTIxMDQyMjIxNTEyOFoXDTIy +MDQyMjIxNTEyOFowWjESMBAGA1UEAwwJVGVzdCBVc2VyMRMwEQYDVQQKDApQeU94 +aWRpemVyMQswCQYDVQQGEwJVUzEiMCAGCSqGSIb3DQEJARYTc29tZW9uZUBleGFt +cGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKadEp7TrR+3 +YJENYyCq84I1ufFcam0V6c/+bEKegCLpRkvOyFjIy3f/ZNPt1/fnysqTDwA52zG+ +gl9yoGygiUjXNkiP4OLVYNR06NsLlTaXtaPy2ijEWWPrvyr2QbDeoALbKWsmQMWA +kgcREOyZ3R6ue2fmSuKP8k+S37Tifi6SUJO6QTD21urh5mGVjjB9dfYu1QRLQTWY +cMJaAF+w5RRlgRtzbSeBwU8cErzZAjRtOGvhyvThbkjtb32Muf+jqpkObCo4JBAV +q7nfUz6eYVgs68dHBhmFqazA0OosdqQlafXU6HnGImTqTYYEgMYqB1N9ezOD39Ot +LQ1JrAQZ8pECAwEAAaMqMCgwDgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQMMAoG +CCsGAQUFBwMDMA0GCSqGSIb3DQEBCwUAA4IBAQASENQJdugbU/zcaCU/JjBMQF+L +IYlNqVRcV5c/CUo0sxMyEIbCQ+tRjsr6wS4Z/BqP4znveP8MChRQqTk+ldP9VtIF +SXtB/HtT9V9XNdJ/R0aoGi//WCQXzS2gzsn9JQKOAQAOkYJg71puHWj1M3CPxxzv +4beXq2t9J1hgtLOiM5AsbHRI8kTgM/J8GKGe0Dw/xgJgwaWPTZPmGtJhoEsFZUyY +ywiSsc83dsllkjFA4MiADfAHdnW48/KSeK6qGetUm4VQImFbcgA0cZTzYdggnaHO +YKYJwXPX2vI/4b+WyqrpQ3ToXGb66oowlD7e16zMfHFQ1Tp415bC3vjtKE/u +-----END CERTIFICATE----- + + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/compute-code-hashes.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/compute-code-hashes.trycmd new file mode 100644 index 00000000..5c6043d5 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/compute-code-hashes.trycmd @@ -0,0 +1,48 @@ +``` +$ rcodesign help compute-code-hashes +Compute code hashes for a binary + +Usage: rcodesign[EXE] compute-code-hashes [OPTIONS] + +Arguments: + + Path to Mach-O binary to examine + +Options: + -C, --config-file + Explicit configuration file to load. + + If provided, the default configuration files are not loaded, even if they exist. + + Can be specified multiple times. Files are loaded/merged in the order given. + + The special value `/dev/null` can be used to specify an empty/null config file. It can be used to short-circuit loading of default config files. + + --hash + Hashing algorithm to use + + [default: sha256] + [possible values: none, sha1, sha256, sha256-truncated, sha384, sha512] + + -P, --profile + Configuration profile to load. + + If not specified, the implicit "default" profile is loaded. + + --page-size + Chunk size to digest over + + [default: 4096] + + --universal-index + Index of Mach-O binary to operate on within a universal/fat binary + + [default: 0] + + -v, --verbose... + Increase logging verbosity. Can be specified multiple times + + -h, --help + Print help (see a summary with '-h') + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/debug-create-macho.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/debug-create-macho.trycmd new file mode 100644 index 00000000..8b7abb5b --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/debug-create-macho.trycmd @@ -0,0 +1,168 @@ +``` +$ rcodesign help debug-create-macho +Create a Mach-O binary from parameters + +Usage: rcodesign[EXE] debug-create-macho [OPTIONS] + +Arguments: + + Filename of Mach-O binary to write + +Options: + --architecture + Architecture of Mach-O binary + + [default: aarch64] + [possible values: aarch64, x86-64] + + -C, --config-file + Explicit configuration file to load. + + If provided, the default configuration files are not loaded, even if they exist. + + Can be specified multiple times. Files are loaded/merged in the order given. + + The special value `/dev/null` can be used to specify an empty/null config file. It can be used to short-circuit loading of default config files. + + --file-type + The Mach-O file type + + [default: executable] + [possible values: executable, dylib] + + -P, --profile + Configuration profile to load. + + If not specified, the implicit "default" profile is loaded. + + --no-targeting + Do not write platform targeting to Mach-O binary + + -v, --verbose... + Increase logging verbosity. Can be specified multiple times + + --minimum-os-version + The minimum operating system version the binary will run on + + --sdk-version + The platform SDK version used to build the binary + + --text-segment-start-offset + Set the file start offset of the __TEXT segment + + -h, --help + Print help (see a summary with '-h') + +``` + +``` +$ rcodesign debug-create-macho exe +assuming default minimum version 11.0.0 +writing Mach-O to exe + +$ rcodesign extract macho-header exe +Header { + magic: 0xfeedfacf, + cputype: 16777228, + cpusubtype: 0x0, + filetype: "EXECUTE", + ncmds: 7, + sizeofcmds: 728, + flags: 0x0, + reserved: 0x0, +} + +$ rcodesign extract macho-load-commands exe +load command count: 7 +LC_SEGMENT_64; offsets=0x20-0x68 (32-104); size=72 +LC_SEGMENT_64; offsets=0x68-0x150 (104-336); size=232 +LC_SEGMENT_64; offsets=0x150-0x1e8 (336-488); size=152 +LC_SEGMENT_64; offsets=0x1e8-0x280 (488-640); size=152 +LC_SEGMENT_64; offsets=0x280-0x2c8 (640-712); size=72 +LC_SYMTAB; offsets=0x2c8-0x2e0 (712-736); size=24 +LC_BUILD_VERSION; offsets=0x2e0-0x2f8 (736-760); size=24 + +$ rcodesign extract macho-load-commands-raw exe +LoadCommand { offset: 32, command: Segment64(SegmentCommand64 { cmd: 25, cmdsize: 72, segname: [95, 95, 80, 65, 71, 69, 90, 69, 82, 79, 0, 0, 0, 0, 0, 0], vmaddr: 0, vmsize: 4294967296, fileoff: 0, filesize: 0, maxprot: 0, initprot: 0, nsects: 0, flags: 0 }) } +LoadCommand { offset: 104, command: Segment64(SegmentCommand64 { cmd: 25, cmdsize: 232, segname: [95, 95, 84, 69, 88, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], vmaddr: 4294967296, vmsize: 0, fileoff: 0, filesize: 16384, maxprot: 0, initprot: 0, nsects: 2, flags: 0 }) } +LoadCommand { offset: 336, command: Segment64(SegmentCommand64 { cmd: 25, cmdsize: 152, segname: [95, 95, 68, 65, 84, 65, 95, 67, 79, 78, 83, 84, 0, 0, 0, 0], vmaddr: 4294967296, vmsize: 0, fileoff: 16384, filesize: 0, maxprot: 0, initprot: 0, nsects: 1, flags: 0 }) } +LoadCommand { offset: 488, command: Segment64(SegmentCommand64 { cmd: 25, cmdsize: 152, segname: [95, 95, 68, 65, 84, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], vmaddr: 4294967296, vmsize: 0, fileoff: 16384, filesize: 0, maxprot: 0, initprot: 0, nsects: 1, flags: 0 }) } +LoadCommand { offset: 640, command: Segment64(SegmentCommand64 { cmd: 25, cmdsize: 72, segname: [95, 95, 76, 73, 78, 75, 69, 68, 73, 84, 0, 0, 0, 0, 0, 0], vmaddr: 4294967296, vmsize: 2, fileoff: 16384, filesize: 2, maxprot: 0, initprot: 0, nsects: 0, flags: 0 }) } +LoadCommand { offset: 712, command: Symtab(SymtabCommand { cmd: 2, cmdsize: 24, symoff: 16384, nsyms: 0, stroff: 16385, strsize: 1 }) } +LoadCommand { offset: 736, command: BuildVersion(BuildVersionCommand { cmd: 50, cmdsize: 24, platform: 1, minos: 720896, sdk: 720896, ntools: 0 }) } + +$ rcodesign extract macho-segments exe +segments count: 5 +segment #0; __PAGEZERO; offsets=0x0-0x0 (0-0); addresses=0x0-0x100000000; vm/file size 4294967296/0; section count 0 +segment #1; __TEXT; offsets=0x0-0x4000 (0-16384); addresses=0x100000000-0x100000000; vm/file size 0/16384; section count 2 +segment #1; section #0: __text; offsets=0x4000-0x4000 (16384-16384); addresses=0x100000000-0x100000000; size 0; align=16384; flags=0 +segment #1; section #1: __const; offsets=0x4000-0x4000 (16384-16384); addresses=0x100000000-0x100000000; size 0; align=16384; flags=0 +segment #2; __DATA_CONST; offsets=0x4000-0x4000 (16384-16384); addresses=0x100000000-0x100000000; vm/file size 0/0; section count 1 +segment #2; section #0: __const; offsets=0x4000-0x4000 (16384-16384); addresses=0x100000000-0x100000000; size 0; align=16384; flags=0 +segment #3; __DATA; offsets=0x4000-0x4000 (16384-16384); addresses=0x100000000-0x100000000; vm/file size 0/0; section count 1 +segment #3; section #0: __data; offsets=0x4000-0x4000 (16384-16384); addresses=0x100000000-0x100000000; size 0; align=16384; flags=0 +segment #4; __LINKEDIT; offsets=0x4000-0x4002 (16384-16386); addresses=0x100000000-0x100000002; vm/file size 2/2; section count 0 + +``` + +Defining custom targeting settings works + +``` +$ rcodesign debug-create-macho --minimum-os-version 11.2.0 exe +writing Mach-O to exe + +$ rcodesign extract macho-load-commands-raw exe +LoadCommand { offset: 32, command: Segment64(SegmentCommand64 { cmd: 25, cmdsize: 72, segname: [95, 95, 80, 65, 71, 69, 90, 69, 82, 79, 0, 0, 0, 0, 0, 0], vmaddr: 0, vmsize: 4294967296, fileoff: 0, filesize: 0, maxprot: 0, initprot: 0, nsects: 0, flags: 0 }) } +LoadCommand { offset: 104, command: Segment64(SegmentCommand64 { cmd: 25, cmdsize: 232, segname: [95, 95, 84, 69, 88, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], vmaddr: 4294967296, vmsize: 0, fileoff: 0, filesize: 16384, maxprot: 0, initprot: 0, nsects: 2, flags: 0 }) } +LoadCommand { offset: 336, command: Segment64(SegmentCommand64 { cmd: 25, cmdsize: 152, segname: [95, 95, 68, 65, 84, 65, 95, 67, 79, 78, 83, 84, 0, 0, 0, 0], vmaddr: 4294967296, vmsize: 0, fileoff: 16384, filesize: 0, maxprot: 0, initprot: 0, nsects: 1, flags: 0 }) } +LoadCommand { offset: 488, command: Segment64(SegmentCommand64 { cmd: 25, cmdsize: 152, segname: [95, 95, 68, 65, 84, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], vmaddr: 4294967296, vmsize: 0, fileoff: 16384, filesize: 0, maxprot: 0, initprot: 0, nsects: 1, flags: 0 }) } +LoadCommand { offset: 640, command: Segment64(SegmentCommand64 { cmd: 25, cmdsize: 72, segname: [95, 95, 76, 73, 78, 75, 69, 68, 73, 84, 0, 0, 0, 0, 0, 0], vmaddr: 4294967296, vmsize: 2, fileoff: 16384, filesize: 2, maxprot: 0, initprot: 0, nsects: 0, flags: 0 }) } +LoadCommand { offset: 712, command: Symtab(SymtabCommand { cmd: 2, cmdsize: 24, symoff: 16384, nsyms: 0, stroff: 16385, strsize: 1 }) } +LoadCommand { offset: 736, command: BuildVersion(BuildVersionCommand { cmd: 50, cmdsize: 24, platform: 1, minos: 721408, sdk: 721408, ntools: 0 }) } + +$ rcodesign debug-create-macho --sdk-version 10.9.0 exe +writing Mach-O to exe + +$ rcodesign extract macho-load-commands-raw exe +LoadCommand { offset: 32, command: Segment64(SegmentCommand64 { cmd: 25, cmdsize: 72, segname: [95, 95, 80, 65, 71, 69, 90, 69, 82, 79, 0, 0, 0, 0, 0, 0], vmaddr: 0, vmsize: 4294967296, fileoff: 0, filesize: 0, maxprot: 0, initprot: 0, nsects: 0, flags: 0 }) } +LoadCommand { offset: 104, command: Segment64(SegmentCommand64 { cmd: 25, cmdsize: 232, segname: [95, 95, 84, 69, 88, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], vmaddr: 4294967296, vmsize: 0, fileoff: 0, filesize: 16384, maxprot: 0, initprot: 0, nsects: 2, flags: 0 }) } +LoadCommand { offset: 336, command: Segment64(SegmentCommand64 { cmd: 25, cmdsize: 152, segname: [95, 95, 68, 65, 84, 65, 95, 67, 79, 78, 83, 84, 0, 0, 0, 0], vmaddr: 4294967296, vmsize: 0, fileoff: 16384, filesize: 0, maxprot: 0, initprot: 0, nsects: 1, flags: 0 }) } +LoadCommand { offset: 488, command: Segment64(SegmentCommand64 { cmd: 25, cmdsize: 152, segname: [95, 95, 68, 65, 84, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], vmaddr: 4294967296, vmsize: 0, fileoff: 16384, filesize: 0, maxprot: 0, initprot: 0, nsects: 1, flags: 0 }) } +LoadCommand { offset: 640, command: Segment64(SegmentCommand64 { cmd: 25, cmdsize: 72, segname: [95, 95, 76, 73, 78, 75, 69, 68, 73, 84, 0, 0, 0, 0, 0, 0], vmaddr: 4294967296, vmsize: 2, fileoff: 16384, filesize: 2, maxprot: 0, initprot: 0, nsects: 0, flags: 0 }) } +LoadCommand { offset: 712, command: Symtab(SymtabCommand { cmd: 2, cmdsize: 24, symoff: 16384, nsyms: 0, stroff: 16385, strsize: 1 }) } +LoadCommand { offset: 736, command: BuildVersion(BuildVersionCommand { cmd: 50, cmdsize: 24, platform: 1, minos: 657664, sdk: 657664, ntools: 0 }) } + +$ rcodesign debug-create-macho --minimum-os-version 10.9.0 --sdk-version 12.0.0 exe +writing Mach-O to exe + +$ rcodesign extract macho-load-commands-raw exe +LoadCommand { offset: 32, command: Segment64(SegmentCommand64 { cmd: 25, cmdsize: 72, segname: [95, 95, 80, 65, 71, 69, 90, 69, 82, 79, 0, 0, 0, 0, 0, 0], vmaddr: 0, vmsize: 4294967296, fileoff: 0, filesize: 0, maxprot: 0, initprot: 0, nsects: 0, flags: 0 }) } +LoadCommand { offset: 104, command: Segment64(SegmentCommand64 { cmd: 25, cmdsize: 232, segname: [95, 95, 84, 69, 88, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], vmaddr: 4294967296, vmsize: 0, fileoff: 0, filesize: 16384, maxprot: 0, initprot: 0, nsects: 2, flags: 0 }) } +LoadCommand { offset: 336, command: Segment64(SegmentCommand64 { cmd: 25, cmdsize: 152, segname: [95, 95, 68, 65, 84, 65, 95, 67, 79, 78, 83, 84, 0, 0, 0, 0], vmaddr: 4294967296, vmsize: 0, fileoff: 16384, filesize: 0, maxprot: 0, initprot: 0, nsects: 1, flags: 0 }) } +LoadCommand { offset: 488, command: Segment64(SegmentCommand64 { cmd: 25, cmdsize: 152, segname: [95, 95, 68, 65, 84, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], vmaddr: 4294967296, vmsize: 0, fileoff: 16384, filesize: 0, maxprot: 0, initprot: 0, nsects: 1, flags: 0 }) } +LoadCommand { offset: 640, command: Segment64(SegmentCommand64 { cmd: 25, cmdsize: 72, segname: [95, 95, 76, 73, 78, 75, 69, 68, 73, 84, 0, 0, 0, 0, 0, 0], vmaddr: 4294967296, vmsize: 2, fileoff: 16384, filesize: 2, maxprot: 0, initprot: 0, nsects: 0, flags: 0 }) } +LoadCommand { offset: 712, command: Symtab(SymtabCommand { cmd: 2, cmdsize: 24, symoff: 16384, nsyms: 0, stroff: 16385, strsize: 1 }) } +LoadCommand { offset: 736, command: BuildVersion(BuildVersionCommand { cmd: 50, cmdsize: 24, platform: 1, minos: 657664, sdk: 786432, ntools: 0 }) } + +``` + +Setting a custom __TEXT start offset works + +``` +$ rcodesign debug-create-macho --text-segment-start-offset 4096 exe +assuming default minimum version 11.0.0 +writing Mach-O to exe + +$ rcodesign extract macho-segments exe +segments count: 5 +segment #0; __PAGEZERO; offsets=0x0-0x0 (0-0); addresses=0x0-0x100000000; vm/file size 4294967296/0; section count 0 +segment #1; __TEXT; offsets=0x1000-0x4000 (4096-16384); addresses=0x100000000-0x100000000; vm/file size 0/12288; section count 2 +segment #1; section #0: __text; offsets=0x4000-0x4000 (16384-16384); addresses=0x100000000-0x100000000; size 0; align=16384; flags=0 +segment #1; section #1: __const; offsets=0x4000-0x4000 (16384-16384); addresses=0x100000000-0x100000000; size 0; align=16384; flags=0 +segment #2; __DATA_CONST; offsets=0x4000-0x4000 (16384-16384); addresses=0x100000000-0x100000000; vm/file size 0/0; section count 1 +segment #2; section #0: __const; offsets=0x4000-0x4000 (16384-16384); addresses=0x100000000-0x100000000; size 0; align=16384; flags=0 +segment #3; __DATA; offsets=0x4000-0x4000 (16384-16384); addresses=0x100000000-0x100000000; vm/file size 0/0; section count 1 +segment #3; section #0: __data; offsets=0x4000-0x4000 (16384-16384); addresses=0x100000000-0x100000000; size 0; align=16384; flags=0 +segment #4; __LINKEDIT; offsets=0x4000-0x4002 (16384-16386); addresses=0x100000000-0x100000002; vm/file size 2/2; section count 0 + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/diff-signatures.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/diff-signatures.trycmd new file mode 100644 index 00000000..f0f577dd --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/diff-signatures.trycmd @@ -0,0 +1,35 @@ +``` +$ rcodesign help diff-signatures +Print a diff between the signature content of two paths + +Usage: rcodesign[EXE] diff-signatures [OPTIONS] + +Arguments: + + The first path to compare + + + The second path to compare + +Options: + -C, --config-file + Explicit configuration file to load. + + If provided, the default configuration files are not loaded, even if they exist. + + Can be specified multiple times. Files are loaded/merged in the order given. + + The special value `/dev/null` can be used to specify an empty/null config file. It can be used to short-circuit loading of default config files. + + -P, --profile + Configuration profile to load. + + If not specified, the implicit "default" profile is loaded. + + -v, --verbose... + Increase logging verbosity. Can be specified multiple times + + -h, --help + Print help (see a summary with '-h') + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/encode-app-store-connect-api-key.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/encode-app-store-connect-api-key.trycmd new file mode 100644 index 00000000..6e59c376 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/encode-app-store-connect-api-key.trycmd @@ -0,0 +1,70 @@ +``` +$ rcodesign help encode-app-store-connect-api-key +Encode App Store Connect API Key metadata to JSON + +App Store Connect API Keys +(https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api) +are defined by 3 components: + +* The Issuer ID (likely a UUID) +* A Key ID (an alphanumeric value like `DEADBEEF42`) +* A PEM encoded ECDSA private key (typically a file beginning with + `-----BEGIN PRIVATE KEY-----`). + +This command is used to encode all API Key components into a single JSON +object so you only have to refer to a single entity when performing +operations (like notarization) using these API Keys. + +The API Key components are specified as positional arguments. + +By default, the JSON encoded unified representation is printed to stdout. +You can write to a file instead by passing `--output-path `. + +# Security Considerations + +The App Store Connect API Key contains a private key and its value should be +treated as sensitive: if an unwanted party obtains your private key, they +effectively have access to your App Store Connect account. + +When this command writes JSON files, an attempt is made to limit access +to the file. However, file access restrictions may not be as secure as you +want. Security conscious individuals should audit the permissions of the +file and adjust accordingly. + +Usage: rcodesign[EXE] encode-app-store-connect-api-key [OPTIONS] + +Arguments: + + The issuer of the API Token. Likely a UUID + + + The Key ID. A short alphanumeric string like DEADBEEF42 + + + Path to a file containing the private key downloaded from Apple + +Options: + -C, --config-file + Explicit configuration file to load. + + If provided, the default configuration files are not loaded, even if they exist. + + Can be specified multiple times. Files are loaded/merged in the order given. + + The special value `/dev/null` can be used to specify an empty/null config file. It can be used to short-circuit loading of default config files. + + -o, --output-path + Path to a JSON file to create the output to + + -P, --profile + Configuration profile to load. + + If not specified, the implicit "default" profile is loaded. + + -v, --verbose... + Increase logging verbosity. Can be specified multiple times + + -h, --help + Print help (see a summary with '-h') + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/extract.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/extract.trycmd new file mode 100644 index 00000000..1417b699 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/extract.trycmd @@ -0,0 +1,369 @@ +``` +$ rcodesign help extract +Print/extract various information from a Mach-O binary. + +Given the path to a Mach-O binary (including fat/universal binaries), this command will attempt to locate and format the requested data. + +Usage: rcodesign extract [OPTIONS] + +Commands: + blobs Code directory blobs + cms-info Information about cryptographic message syntax signature + cms-pem PEM encoded cryptographic message syntax signature + cms-raw Binary cryptographic message syntax signature. Should be BER encoded ASN.1 data + cms ASN.1 decoded cryptographic message syntax data + code-directory Information from the main code directory data structure + code-directory-raw Raw binary data composing the code directory data structure + code-directory-serialized Reserialize the parsed code directory, parse it again, and then print it like `code-directory` would + code-directory-serialized-raw Reserialize the parsed code directory and emit its binary + linkedit-info Information about the __LINKEDIT Mach-O segment + linkedit-segment-raw Complete content of the __LINKEDIT Mach-O segment + macho-header Mach-O file header data + macho-load-commands High-level information about Mach-O load commands + macho-load-commands-raw Debug formatted Mach-O load command data structures + macho-segments Information about Mach-O segments + macho-target Mach-O targeting info + requirements Parsed code requirement statement/expression + requirements-raw Raw binary data composing the requirements blob/slot + requirements-rust Dump the internal Rust data structures representing the requirements expressions + requirements-serialized Reserialize the code requirements blob, parse it again, and then print it like `requirements` would + requirements-serialized-raw Like `requirements-serialized` except emit the binary data representation + signature-raw Raw binary data constituting the signature data embedded in the binary + superblob Show information about the SuperBlob record and high-level details of embedded Blob records + help Print this message or the help of the given subcommand(s) + +Options: + -C, --config-file + Explicit configuration file to load. + + If provided, the default configuration files are not loaded, even if they exist. + + Can be specified multiple times. Files are loaded/merged in the order given. + + The special value `/dev/null` can be used to specify an empty/null config file. It can be used to short-circuit loading of default config files. + + --universal-index + Index of Mach-O binary to operate on within a universal/fat binary + + [default: 0] + + -P, --profile + Configuration profile to load. + + If not specified, the implicit "default" profile is loaded. + + -v, --verbose... + Increase logging verbosity. Can be specified multiple times + + -h, --help + Print help (see a summary with '-h') + +``` + +``` +$ rcodesign debug-create-macho --minimum-os-version 11.2.0 exe +writing Mach-O to exe + +$ rcodesign sign exe +signing exe in place +signing exe as a Mach-O binary +setting binary identifier to exe +parsing Mach-O +writing Mach-O to exe + +$ rcodesign extract blobs exe +ParsedBlob { + blob_entry: BlobEntry { + index: 0, + slot: CodeDirectory (0), + offset: 36, + length: 316, + magic: CodeDirectory, + }, + blob: CodeDirectory( + CodeDirectoryBlob { + version: 132096, + flags: CodeSignatureFlags( + ADHOC, + ), + code_limit: 16400, + digest_size: 32, + digest_type: Sha256, + platform: 0, + page_size: 4096, + spare2: 0, + scatter_offset: None, + spare3: Some( + 0, + ), + code_limit_64: Some( + 0, + ), + exec_seg_base: Some( + 0, + ), + exec_seg_limit: Some( + 16384, + ), + exec_seg_flags: Some( + ExecutableSegmentFlags( + MAIN_BINARY, + ), + ), + runtime: None, + pre_encrypt_offset: None, + linkage_hash_type: None, + linkage_truncated: None, + spare4: None, + linkage_offset: None, + linkage_size: None, + ident: "exe", + team_name: None, + code_digests: [ + 4f3c762ac97e47d0e37922d5b276e7c751b66e37f4b410a0421e6d5aacf44415, + ad7facb2586fc6e966c004d7d1d16b024f5805ff7cb47c7a85dabd8b48892ca7, + ad7facb2586fc6e966c004d7d1d16b024f5805ff7cb47c7a85dabd8b48892ca7, + ad7facb2586fc6e966c004d7d1d16b024f5805ff7cb47c7a85dabd8b48892ca7, + 374708fff7719dd5979ec875d56cd2286f6d3cf7ec317a3b25632aab28ec37bb, + ], + special_digests: { + Info (1): 0000000000000000000000000000000000000000000000000000000000000000, + RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986, + }, + }, + ), +} +ParsedBlob { + blob_entry: BlobEntry { + index: 1, + slot: RequirementSet (2), + offset: 352, + length: 12, + magic: RequirementSet, + }, + blob: RequirementSet( + RequirementSetBlob { + requirements: {}, + }, + ), +} +ParsedBlob { + blob_entry: BlobEntry { + index: 2, + slot: CMS Signature (65536), + offset: 364, + length: 8, + magic: BlobWrapper, + }, + blob: BlobWrapper( + , + ), +} + +$ rcodesign extract code-directory exe +CodeDirectoryBlob { + version: 132096, + flags: CodeSignatureFlags( + ADHOC, + ), + code_limit: 16400, + digest_size: 32, + digest_type: Sha256, + platform: 0, + page_size: 4096, + spare2: 0, + scatter_offset: None, + spare3: Some( + 0, + ), + code_limit_64: Some( + 0, + ), + exec_seg_base: Some( + 0, + ), + exec_seg_limit: Some( + 16384, + ), + exec_seg_flags: Some( + ExecutableSegmentFlags( + MAIN_BINARY, + ), + ), + runtime: None, + pre_encrypt_offset: None, + linkage_hash_type: None, + linkage_truncated: None, + spare4: None, + linkage_offset: None, + linkage_size: None, + ident: "exe", + team_name: None, + code_digests: [ + 4f3c762ac97e47d0e37922d5b276e7c751b66e37f4b410a0421e6d5aacf44415, + ad7facb2586fc6e966c004d7d1d16b024f5805ff7cb47c7a85dabd8b48892ca7, + ad7facb2586fc6e966c004d7d1d16b024f5805ff7cb47c7a85dabd8b48892ca7, + ad7facb2586fc6e966c004d7d1d16b024f5805ff7cb47c7a85dabd8b48892ca7, + 374708fff7719dd5979ec875d56cd2286f6d3cf7ec317a3b25632aab28ec37bb, + ], + special_digests: { + Info (1): 0000000000000000000000000000000000000000000000000000000000000000, + RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986, + }, +} + +$ rcodesign extract code-directory-serialized exe +CodeDirectoryBlob { + version: 132096, + flags: CodeSignatureFlags( + ADHOC, + ), + code_limit: 16400, + digest_size: 32, + digest_type: Sha256, + platform: 0, + page_size: 4096, + spare2: 0, + scatter_offset: None, + spare3: Some( + 0, + ), + code_limit_64: Some( + 0, + ), + exec_seg_base: Some( + 0, + ), + exec_seg_limit: Some( + 16384, + ), + exec_seg_flags: Some( + ExecutableSegmentFlags( + MAIN_BINARY, + ), + ), + runtime: None, + pre_encrypt_offset: None, + linkage_hash_type: None, + linkage_truncated: None, + spare4: None, + linkage_offset: None, + linkage_size: None, + ident: "exe", + team_name: None, + code_digests: [ + 4f3c762ac97e47d0e37922d5b276e7c751b66e37f4b410a0421e6d5aacf44415, + ad7facb2586fc6e966c004d7d1d16b024f5805ff7cb47c7a85dabd8b48892ca7, + ad7facb2586fc6e966c004d7d1d16b024f5805ff7cb47c7a85dabd8b48892ca7, + ad7facb2586fc6e966c004d7d1d16b024f5805ff7cb47c7a85dabd8b48892ca7, + 374708fff7719dd5979ec875d56cd2286f6d3cf7ec317a3b25632aab28ec37bb, + ], + special_digests: { + Info (1): 0000000000000000000000000000000000000000000000000000000000000000, + RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986, + }, +} + +$ rcodesign extract linkedit-info exe +__LINKEDIT segment index: 4 +__LINKEDIT segment start offset: 16384 +__LINKEDIT segment end offset: 22544 +__LINKEDIT segment size: 6160 +__LINKEDIT signature global start offset: 16400 +__LINKEDIT signature global end offset: 22544 +__LINKEDIT signature local segment start offset: 16 +__LINKEDIT signature local segment end offset: 6160 +__LINKEDIT signature size: 6144 + +$ rcodesign extract macho-load-commands exe +load command count: 8 +LC_SEGMENT_64; offsets=0x20-0x68 (32-104); size=72 +LC_SEGMENT_64; offsets=0x68-0x150 (104-336); size=232 +LC_SEGMENT_64; offsets=0x150-0x1e8 (336-488); size=152 +LC_SEGMENT_64; offsets=0x1e8-0x280 (488-640); size=152 +LC_SEGMENT_64; offsets=0x280-0x2c8 (640-712); size=72 +LC_SYMTAB; offsets=0x2c8-0x2e0 (712-736); size=24 +LC_BUILD_VERSION; offsets=0x2e0-0x2f8 (736-760); size=24 +LC_CODE_SIGNATURE; offsets=0x2f8-0x308 (760-776); size=16 + +$ rcodesign extract macho-segments exe +segments count: 5 +segment #0; __PAGEZERO; offsets=0x0-0x0 (0-0); addresses=0x0-0x100000000; vm/file size 4294967296/0; section count 0 +segment #1; __TEXT; offsets=0x0-0x4000 (0-16384); addresses=0x100000000-0x100000000; vm/file size 0/16384; section count 2 +segment #1; section #0: __text; offsets=0x4000-0x4000 (16384-16384); addresses=0x100000000-0x100000000; size 0; align=16384; flags=0 +segment #1; section #1: __const; offsets=0x4000-0x4000 (16384-16384); addresses=0x100000000-0x100000000; size 0; align=16384; flags=0 +segment #2; __DATA_CONST; offsets=0x4000-0x4000 (16384-16384); addresses=0x100000000-0x100000000; vm/file size 0/0; section count 1 +segment #2; section #0: __const; offsets=0x4000-0x4000 (16384-16384); addresses=0x100000000-0x100000000; size 0; align=16384; flags=0 +segment #3; __DATA; offsets=0x4000-0x4000 (16384-16384); addresses=0x100000000-0x100000000; vm/file size 0/0; section count 1 +segment #3; section #0: __data; offsets=0x4000-0x4000 (16384-16384); addresses=0x100000000-0x100000000; size 0; align=16384; flags=0 +segment #4; __LINKEDIT; offsets=0x4000-0x5810 (16384-22544); addresses=0x100000000-0x100004000; vm/file size 16384/6160; section count 0 + +$ rcodesign extract macho-target exe +Platform: macOS +Minimum OS: 11.2.0 +SDK: 11.2.0 + +$ rcodesign extract requirements exe + +$ rcodesign extract requirements-rust exe + +$ rcodesign extract requirements-serialized exe +RequirementSetBlob { + requirements: {}, +} + +$ rcodesign extract superblob exe +file start offset: 16400 +file end offset: 22544 +__LINKEDIT start offset: 16 +__LINKEDIT end offset: 6160 +length: 372 +blob count: 3 +blobs: +- index: 0 + offsets: 0x24-0x15f (36-351) + length: 316 + slot: CodeDirectory (0) + magic: CodeDirectory (0xfade0c02) + sha1: ee23bf4fb629c06fd2a4c052e64e6d82cba191d6 + sha256: 504dc3f849477fdb537ed810992d02e008f5302d8cde77b0076bd6cbefb10de6 + sha256-truncated: 504dc3f849477fdb537ed810992d02e008f5302d + sha384: 38564345445744b17a61213dda9fd116bd5e0f0943cd7d6994d5f1e0fcf0d1b28e4646245c75aa51d5e43cd9e1e8a8c9 + sha512: ba96f00e6cc58310a76a99d51b5571e4beb9d73abc6678df02b942116d458f1be7d028eef670088aed2eeb1c1dcb055f6f8a51e587ac59bd7ad2cf0ef475673e + sha1-base64: 7iO/T7YpwG/SpMBS5k5tgsuhkdY= + sha256-base64: UE3D+ElHf9tTftgQmS0C4Aj1MC2M3newB2vWy++xDeY= + sha256-truncated-base64: UE3D+ElHf9tTftgQmS0C4Aj1MC0= + sha384-base64: OFZDRURXRLF6YSE92p/RFr1eDwlDzX1plNXx4Pzw0bKORkYkXHWqUdXkPNnh6KjJ + sha512-base64: upbwDmzFgxCnapnVG1Vx5L651zq8ZnjfArlCEW1Fjxvn0Cju9nAIiu0u6xwdywVfb4pR5YesWb160s8O9HVnPg== +- index: 1 + offsets: 0x160-0x16b (352-363) + length: 12 + slot: RequirementSet (2) + magic: RequirementSet (0xfade0c01) + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + sha256-truncated: 987920904eab650e75788c054aa0b0524e6a80bf + sha384: df9b77e787dcf0f66952973f309be7ac0718558792e9e10984a26231048034d31cdd208d65fd38143a8d96aecd32084d + sha512: a2038f9fd1ebade88b0f261db83eaef30ecf7c9ebe7c9212c62a30ba534dbf59e6423e922edfbdba969848663377ac4f9130ee8f4510144b4fa411398e3ae97c + sha1-base64: OnX22wWFKRSOFN1+obRynMCeyXM= + sha256-base64: mHkgkE6rZQ51eIwFSqCwUk5qgL/HGqMt+NI3phdD+YY= + sha256-truncated-base64: mHkgkE6rZQ51eIwFSqCwUk5qgL8= + sha384-base64: 35t354fc8PZpUpc/MJvnrAcYVYeS6eEJhKJiMQSANNMc3SCNZf04FDqNlq7NMghN + sha512-base64: ogOPn9HrreiLDyYduD6u8w7PfJ6+fJISxiowulNNv1nmQj6SLt+9upaYSGYzd6xPkTDuj0UQFEtPpBE5jjrpfA== +- index: 2 + offsets: 0x16c-0x173 (364-371) + length: 8 + slot: CMS Signature (65536) + magic: BlobWrapper (0xfade0b01) + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + sha256-truncated: e6c83bc98a10348492c7d4d2378a54572ef29e1a + sha384: 01415351c4e0230fa499def0260fa6ac175625f2b06f9f45e607ff3fd513c60dfbeefd85327e777f7ab19c5512da9a82 + sha512: 9968a4b379cdb74bfb92a9d24c90649e9252f8fe905b76927d0e435cc09deb6c370b63c83678e98d137589a62f5678657fb05d1f14328c4e4efd624f6192282a + sha1-base64: KnJUMTqkF5YHm7Dp0PBENF9p+Ys= + sha256-base64: 5sg7yYoQNISSx9TSN4pUVy7ynhpWkszQK14p9Ldi1qA= + sha256-truncated-base64: 5sg7yYoQNISSx9TSN4pUVy7ynho= + sha384-base64: AUFTUcTgIw+kmd7wJg+mrBdWJfKwb59F5gf/P9UTxg377v2FMn53f3qxnFUS2pqC + sha512-base64: mWiks3nNt0v7kqnSTJBknpJS+P6QW3aSfQ5DXMCd62w3C2PINnjpjRN1iaYvVnhlf7BdHxQyjE5O/WJPYZIoKg== + +``` \ No newline at end of file diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/generate-csr.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/generate-csr.trycmd new file mode 100644 index 00000000..341421b3 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/generate-csr.trycmd @@ -0,0 +1,88 @@ +``` +$ rcodesign generate-certificate-signing-request --help +Generates a certificate signing request that can be sent to Apple and exchanged for a signing certificate + +Usage: rcodesign[EXE] generate-certificate-signing-request [OPTIONS] + +Options: + -C, --config-file + Explicit configuration file to load. + + If provided, the default configuration files are not loaded, even if they exist. + + Can be specified multiple times. Files are loaded/merged in the order given. + + The special value `/dev/null` can be used to specify an empty/null config file. It can be used to short-circuit loading of default config files. + + --csr-pem-file + Path to file to write PEM encoded CSR to + + -P, --profile + Configuration profile to load. + + If not specified, the implicit "default" profile is loaded. + + --smartcard-slot + Smartcard slot number of signing certificate to use (9c is common) + + --smartcard-pin + Smartcard PIN used to unlock certificate + + If not provided, you will be prompted for a PIN as necessary. + + -v, --verbose... + Increase logging verbosity. Can be specified multiple times + + --smartcard-pin-env + Environment variable holding the smartcard PIN + + --keychain-domain + (macOS only) Keychain domain to operate on + + [possible values: user, system, common, dynamic] + + --keychain-fingerprint + (macOS only) SHA-256 fingerprint of certificate in Keychain to use + + --windows-store-name + (Windows only) Windows Store to operate on + + [possible values: user, machine, service] + + --windows-store-sha1-fingerprint + (Windows only) SHA-1 fingerprint of certificate in Windows Store to use + + --pem-file + Path to file containing PEM encoded certificate/key data + + --p12-file + Path to a .p12/PFX file containing a certificate key pair + + --p12-password + The password to use to open the --p12-file file + + --p12-password-file + Path to file containing password for opening --p12-file file + + --remote-signing-url + URL of a remote code signing server + + --remote-public-key + Base64 encoded public key data describing the signer + + --remote-public-key-pem-file + PEM encoded public key data describing the signer + + --remote-shared-secret + Shared secret used for remote signing + + --remote-shared-secret-env + Environment variable holding the shared secret used for remote signing + + --certificate-der-file + Path to file containing DER encoded certificate data + + -h, --help + Print help (see a summary with '-h') + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/generate-self-signed-cert.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/generate-self-signed-cert.trycmd new file mode 100644 index 00000000..10d04963 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/generate-self-signed-cert.trycmd @@ -0,0 +1,139 @@ +``` +$ rcodesign generate-self-signed-certificate --help +Generate a self-signed certificate for code signing + +This command will generate a new key pair using the algorithm of choice then create an X.509 certificate wrapper for it that is signed with the just-generated private key. The created X.509 certificate has extensions that mark it as appropriate for code signing. + +Certificates generated with this command can be useful for local testing. However, because it is a self-signed certificate and isn't signed by a trusted certificate authority, Apple operating systems may refuse to load binaries signed with it. + +By default the command prints 2 PEM encoded blocks. One block is for the X.509 public certificate. The other is for the PKCS#8 private key (which can include the public key). + +The `--pem-filename` argument can be specified to write the generated certificate pair to a pair of files. The destination files will have `.crt` and `.key` appended to the value provided. + +When the certificate is written to a file, it isn't printed to stdout. + +Usage: rcodesign[EXE] generate-self-signed-certificate [OPTIONS] --person-name + +Options: + --algorithm + Which key type to use + + [default: rsa] + [possible values: ecdsa, ed25519, rsa] + + -C, --config-file + Explicit configuration file to load. + + If provided, the default configuration files are not loaded, even if they exist. + + Can be specified multiple times. Files are loaded/merged in the order given. + + The special value `/dev/null` can be used to specify an empty/null config file. It can be used to short-circuit loading of default config files. + + --profile + [default: apple-development] + [possible values: mac-installer-distribution, apple-distribution, apple-development, developer-id-application, developer-id-installer] + + --team-id + Team ID (this is a short string attached to your Apple Developer account) + + [default: unset] + + -v, --verbose... + Increase logging verbosity. Can be specified multiple times + + --person-name + The name of the person this certificate is for + + --country-name + Country Name (C) value for certificate identifier + + [default: XX] + + --validity-days + How many days the certificate should be valid for + + [default: 365] + + --pem-filename + Base name of files to write PEM encoded certificate to + + --pem-unified-file + Filename to write PEM encoded private key and public certificate to + + --p12-file + Filename to write a PKCS#12 / p12 / PFX encoded certificate to + + --p12-password + Password to use to encrypt --p12-path. + + If not provided you will be prompted for a password. + + -h, --help + Print help (see a summary with '-h') + +``` + +``` +$ rcodesign generate-self-signed-certificate --profile apple-development --person-name 'Johnny Apple' +-----BEGIN CERTIFICATE----- +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +[..] +-----END PRIVATE KEY----- + +``` + +Try a PKCS#12 file + +``` +$ rcodesign generate-self-signed-certificate --profile apple-development --person-name 'Johnny Apple' --p12-file test.p12 --p12-password password +writing PKCS#12 certificate to test.p12 + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/help.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/help.trycmd new file mode 100644 index 00000000..f1bba784 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/help.trycmd @@ -0,0 +1,159 @@ +``` +$ rcodesign +? 2 +Sign and notarize Apple programs. See https://gregoryszorc.com/docs/apple-codesign/main/ for more docs + +Usage: rcodesign[EXE] [OPTIONS] + +Commands: + analyze-certificate + Analyze an X.509 certificate for Apple code signing properties + compute-code-hashes + Compute code hashes for a binary + diff-signatures + Print a diff between the signature content of two paths + encode-app-store-connect-api-key + Encode App Store Connect API Key metadata to JSON + extract + Print/extract various information from a Mach-O binary + generate-certificate-signing-request + Generates a certificate signing request that can be sent to Apple and exchanged for a signing certificate + generate-self-signed-certificate + Generate a self-signed certificate for code signing + keychain-export-certificate-chain + Export Apple CA certificates from the macOS Keychain + keychain-print-certificates + Print information about certificates in the macOS keychain + macho-universal-create + Create a universal ("fat") Mach-O binary + notary-list + List notarization submissions + notary-log + Fetch the notarization log for a previous submission + notary-submit + Upload an asset to Apple for notarization and possibly staple it + notary-wait + Wait for completion of a previous submission + parse-code-signing-requirement + Parse binary Code Signing Requirement data into a human readable string + print-signature-info + Print signature information for a filesystem path + remote-sign + Create signatures initiated from a remote signing operation + sign + Adds code signatures to a signable entity. + smartcard-generate-key + Generate a new private key on a smartcard + smartcard-import + Import a code signing certificate and key into a smartcard + smartcard-scan + Show information about available smartcard (SC) devices + staple + Staples a notarization ticket to an entity + verify + Verifies code signature data + windows-store-export-certificate-chain + Export CA certificates from the Windows Store + windows-store-print-certificates + Print information about certificates in the Windows Store + x509-oids + Print information about X.509 OIDs related to Apple code signing + help + Print this message or the help of the given subcommand(s) + +Options: + -C, --config-file Explicit configuration file to load + -P, --profile Configuration profile to load + -v, --verbose... Increase logging verbosity. Can be specified multiple times + -h, --help Print help (see more with '--help') + -V, --version Print version + +``` + +``` +$ rcodesign help +Sign and notarize Apple programs. See https://gregoryszorc.com/docs/apple-codesign/main/ for more docs + +Usage: rcodesign[EXE] [OPTIONS] + +Commands: + analyze-certificate + Analyze an X.509 certificate for Apple code signing properties + compute-code-hashes + Compute code hashes for a binary + diff-signatures + Print a diff between the signature content of two paths + encode-app-store-connect-api-key + Encode App Store Connect API Key metadata to JSON + extract + Print/extract various information from a Mach-O binary + generate-certificate-signing-request + Generates a certificate signing request that can be sent to Apple and exchanged for a signing certificate + generate-self-signed-certificate + Generate a self-signed certificate for code signing + keychain-export-certificate-chain + Export Apple CA certificates from the macOS Keychain + keychain-print-certificates + Print information about certificates in the macOS keychain + macho-universal-create + Create a universal ("fat") Mach-O binary + notary-list + List notarization submissions + notary-log + Fetch the notarization log for a previous submission + notary-submit + Upload an asset to Apple for notarization and possibly staple it + notary-wait + Wait for completion of a previous submission + parse-code-signing-requirement + Parse binary Code Signing Requirement data into a human readable string + print-signature-info + Print signature information for a filesystem path + remote-sign + Create signatures initiated from a remote signing operation + sign + Adds code signatures to a signable entity. + smartcard-generate-key + Generate a new private key on a smartcard + smartcard-import + Import a code signing certificate and key into a smartcard + smartcard-scan + Show information about available smartcard (SC) devices + staple + Staples a notarization ticket to an entity + verify + Verifies code signature data + windows-store-export-certificate-chain + Export CA certificates from the Windows Store + windows-store-print-certificates + Print information about certificates in the Windows Store + x509-oids + Print information about X.509 OIDs related to Apple code signing + help + Print this message or the help of the given subcommand(s) + +Options: + -C, --config-file + Explicit configuration file to load. + + If provided, the default configuration files are not loaded, even if they exist. + + Can be specified multiple times. Files are loaded/merged in the order given. + + The special value `/dev/null` can be used to specify an empty/null config file. It can be used to short-circuit loading of default config files. + + -P, --profile + Configuration profile to load. + + If not specified, the implicit "default" profile is loaded. + + -v, --verbose... + Increase logging verbosity. Can be specified multiple times + + -h, --help + Print help (see a summary with '-h') + + -V, --version + Print version + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/keychain-export-certificate-chain.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/keychain-export-certificate-chain.trycmd new file mode 100644 index 00000000..1722748d --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/keychain-export-certificate-chain.trycmd @@ -0,0 +1,46 @@ +``` +$ rcodesign help keychain-export-certificate-chain +Export Apple CA certificates from the macOS Keychain + +Usage: rcodesign[EXE] keychain-export-certificate-chain [OPTIONS] --user-id + +Options: + -C, --config-file + Explicit configuration file to load. + + If provided, the default configuration files are not loaded, even if they exist. + + Can be specified multiple times. Files are loaded/merged in the order given. + + The special value `/dev/null` can be used to specify an empty/null config file. It can be used to short-circuit loading of default config files. + + --domain + Keychain domain to operate on + + [default: user] + [possible values: user, system, common, dynamic] + + -P, --profile + Configuration profile to load. + + If not specified, the implicit "default" profile is loaded. + + --password + Password to unlock the Keychain + + --password-file + File containing password to use to unlock the Keychain + + -v, --verbose... + Increase logging verbosity. Can be specified multiple times + + --no-print-self + Print only the issuing certificate chain, not the subject certificate + + --user-id + User ID value of code signing certificate to find and whose CA chain to export + + -h, --help + Print help (see a summary with '-h') + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/keychain-print-certificates.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/keychain-print-certificates.trycmd new file mode 100644 index 00000000..c2afd9c3 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/keychain-print-certificates.trycmd @@ -0,0 +1,34 @@ +``` +$ rcodesign help keychain-print-certificates +Print information about certificates in the macOS keychain + +Usage: rcodesign[EXE] keychain-print-certificates [OPTIONS] + +Options: + -C, --config-file + Explicit configuration file to load. + + If provided, the default configuration files are not loaded, even if they exist. + + Can be specified multiple times. Files are loaded/merged in the order given. + + The special value `/dev/null` can be used to specify an empty/null config file. It can be used to short-circuit loading of default config files. + + --domain + Keychain domain to operate on + + [default: user] + [possible values: user, system, common, dynamic] + + -P, --profile + Configuration profile to load. + + If not specified, the implicit "default" profile is loaded. + + -v, --verbose... + Increase logging verbosity. Can be specified multiple times + + -h, --help + Print help (see a summary with '-h') + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/macho-universal-create.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/macho-universal-create.trycmd new file mode 100644 index 00000000..3bcc2056 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/macho-universal-create.trycmd @@ -0,0 +1,50 @@ +``` +$ rcodesign help macho-universal-create +Create a universal ("fat") Mach-O binary. + +This is similar to the `lipo -create` command. Use it to stitch multiple single architecture Mach-O binaries into a single multi-arch binary. + +Usage: rcodesign[EXE] macho-universal-create [OPTIONS] --output [INPUT]... + +Arguments: + [INPUT]... + Input Mach-O binaries to combine + +Options: + -C, --config-file + Explicit configuration file to load. + + If provided, the default configuration files are not loaded, even if they exist. + + Can be specified multiple times. Files are loaded/merged in the order given. + + The special value `/dev/null` can be used to specify an empty/null config file. It can be used to short-circuit loading of default config files. + + -o, --output + Output file to write + + -P, --profile + Configuration profile to load. + + If not specified, the implicit "default" profile is loaded. + + -v, --verbose... + Increase logging verbosity. Can be specified multiple times + + -h, --help + Print help (see a summary with '-h') + +$ rcodesign debug-create-macho --architecture aarch64 exe.aarch64 +assuming default minimum version 11.0.0 +writing Mach-O to exe.aarch64 + +$ rcodesign debug-create-macho --architecture x86-64 exe.x86-64 +assuming default minimum version 11.0.0 +writing Mach-O to exe.x86-64 + +$ rcodesign macho-universal-create -o exe exe.aarch64 exe.x86-64 +adding exe.aarch64 +adding exe.x86-64 +writing exe + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/notary-log.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/notary-log.trycmd new file mode 100644 index 00000000..e5edbfd5 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/notary-log.trycmd @@ -0,0 +1,41 @@ +``` +$ rcodesign help notary-log +Fetch the notarization log for a previous submission + +Usage: rcodesign[EXE] notary-log [OPTIONS] + +Arguments: + + The ID of the previous submission to wait on + +Options: + --api-key-file + Path to a JSON file containing the API Key + + -C, --config-file + Explicit configuration file to load. + + If provided, the default configuration files are not loaded, even if they exist. + + Can be specified multiple times. Files are loaded/merged in the order given. + + The special value `/dev/null` can be used to specify an empty/null config file. It can be used to short-circuit loading of default config files. + + --api-issuer + App Store Connect Issuer ID (likely a UUID) + + -P, --profile + Configuration profile to load. + + If not specified, the implicit "default" profile is loaded. + + --api-key + App Store Connect API Key ID + + -v, --verbose... + Increase logging verbosity. Can be specified multiple times + + -h, --help + Print help (see a summary with '-h') + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/notary-submit.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/notary-submit.trycmd new file mode 100644 index 00000000..a0ebced0 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/notary-submit.trycmd @@ -0,0 +1,74 @@ +``` +$ rcodesign help notary-submit +Upload an asset to Apple for notarization and possibly staple it + +This command is used to submit an asset to Apple for notarization. Given a path to an asset with a code signature, this command will connect to Apple's Notary API and upload the asset. It will then optionally wait on the submission to finish processing (which typically takes a few dozen seconds). If the asset validates Apple's requirements, Apple will issue a *notarization ticket* as proof that they approved of it. This ticket is then added to the asset in a process called *stapling*, which this command can do automatically if the `--staple` argument is passed. + +# App Store Connect API Key + +In order to communicate with Apple's servers, you need an App Store Connect API Key. This requires an Apple Developer account. You can generate an API Key at https://appstoreconnect.apple.com/access/api. + +The recommended mechanism to define the API Key is via `--api-key-path`, which takes the path to a file containing JSON produced by the `encode-app-store-connect-api-key` command. See that command's help for more details. + +If you don't wish to use `--api-key-path`, you can define the key components via the `--api-issuer` and `--api-key` arguments. You will need a file named `AuthKey_.p8` in one of the following locations: `$(pwd)/private_keys/`, `~/private_keys/`, '~/.private_keys/`, and `~/.appstoreconnect/private_keys/` (searched in that order). The name of the file is derived from the value of `--api-key`. + +In all cases, App Store Connect API Keys can be managed at https://appstoreconnect.apple.com/access/api. + +# Modes of Operation + +By default, the `notarize` command will initiate an upload to Apple and exit once the upload is complete. + +Once an upload is performed, Apple will asynchronously process the uploaded content. This can take seconds to minutes. + +To poll Apple's servers and wait on the server-side processing to finish, specify `--wait`. This will query the state of the processing every few seconds until it is finished, the max wait time is reached, or an error occurs. + +To automatically staple an asset after server-side processing has finished, specify `--staple`. This implies `--wait`. + +Usage: rcodesign[EXE] notary-submit [OPTIONS] + +Arguments: + + Path to asset to upload + +Options: + -C, --config-file + Explicit configuration file to load. + + If provided, the default configuration files are not loaded, even if they exist. + + Can be specified multiple times. Files are loaded/merged in the order given. + + The special value `/dev/null` can be used to specify an empty/null config file. It can be used to short-circuit loading of default config files. + + --wait + Whether to wait for upload processing to complete + + --max-wait-seconds + Maximum time in seconds to wait for the upload result + + [default: 600] + + -P, --profile + Configuration profile to load. + + If not specified, the implicit "default" profile is loaded. + + --staple + Staple the notarization ticket after successful upload (implies --wait) + + -v, --verbose... + Increase logging verbosity. Can be specified multiple times + + --api-key-file + Path to a JSON file containing the API Key + + --api-issuer + App Store Connect Issuer ID (likely a UUID) + + --api-key + App Store Connect API Key ID + + -h, --help + Print help (see a summary with '-h') + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/notary-wait.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/notary-wait.trycmd new file mode 100644 index 00000000..1e61a700 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/notary-wait.trycmd @@ -0,0 +1,46 @@ +``` +$ rcodesign help notary-wait +Wait for completion of a previous submission + +Usage: rcodesign[EXE] notary-wait [OPTIONS] + +Arguments: + + The ID of the previous submission to wait on + +Options: + -C, --config-file + Explicit configuration file to load. + + If provided, the default configuration files are not loaded, even if they exist. + + Can be specified multiple times. Files are loaded/merged in the order given. + + The special value `/dev/null` can be used to specify an empty/null config file. It can be used to short-circuit loading of default config files. + + --max-wait-seconds + Maximum time in seconds to wait for the upload result + + [default: 600] + + --api-key-file + Path to a JSON file containing the API Key + + -P, --profile + Configuration profile to load. + + If not specified, the implicit "default" profile is loaded. + + --api-issuer + App Store Connect Issuer ID (likely a UUID) + + -v, --verbose... + Increase logging verbosity. Can be specified multiple times + + --api-key + App Store Connect API Key ID + + -h, --help + Print help (see a summary with '-h') + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/parse-code-signing-requirement.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/parse-code-signing-requirement.trycmd new file mode 100644 index 00000000..b07f9c7b --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/parse-code-signing-requirement.trycmd @@ -0,0 +1,46 @@ +``` +$ rcodesign help parse-code-signing-requirement +Parse binary Code Signing Requirement data into a human readable string + +This command can be used to parse binary code signing requirement data and print it in various formats. + +The source input format is the binary code requirement serialization. This is the format generated by Apple's `csreq` tool via `csreq -b`. The binary data begins with header magic `0xfade0c00`. + +The default output format is the Code Signing Requirement Language. But the output format can be changed via the --format argument. + +Our Code Signing Requirement Language output may differ from Apple's. For example, `and` and `or` expressions always have their sub-expressions surrounded by parentheses (e.g. `(a) and (b)` instead of `a and b`) and strings are always quoted. The differences, however, should not matter to the parser or result in a different binary serialization. + +Usage: rcodesign[EXE] parse-code-signing-requirement [OPTIONS] + +Arguments: + + Path to file to parse + +Options: + -C, --config-file + Explicit configuration file to load. + + If provided, the default configuration files are not loaded, even if they exist. + + Can be specified multiple times. Files are loaded/merged in the order given. + + The special value `/dev/null` can be used to specify an empty/null config file. It can be used to short-circuit loading of default config files. + + --format + Output format + + [default: csrl] + [possible values: csrl, expression-tree] + + -P, --profile + Configuration profile to load. + + If not specified, the implicit "default" profile is loaded. + + -v, --verbose... + Increase logging verbosity. Can be specified multiple times + + -h, --help + Print help (see a summary with '-h') + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/print-signature-info.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/print-signature-info.trycmd new file mode 100644 index 00000000..8f5f4e14 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/print-signature-info.trycmd @@ -0,0 +1,32 @@ +``` +$ rcodesign help print-signature-info +Print signature information for a filesystem path + +Usage: rcodesign[EXE] print-signature-info [OPTIONS] + +Arguments: + + Filesystem path to entity whose info to print + +Options: + -C, --config-file + Explicit configuration file to load. + + If provided, the default configuration files are not loaded, even if they exist. + + Can be specified multiple times. Files are loaded/merged in the order given. + + The special value `/dev/null` can be used to specify an empty/null config file. It can be used to short-circuit loading of default config files. + + -P, --profile + Configuration profile to load. + + If not specified, the implicit "default" profile is loaded. + + -v, --verbose... + Increase logging verbosity. Can be specified multiple times + + -h, --help + Print help (see a summary with '-h') + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/remote-sign.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/remote-sign.trycmd new file mode 100644 index 00000000..3bde629f --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/remote-sign.trycmd @@ -0,0 +1,95 @@ +``` +$ rcodesign help remote-sign +Create signatures initiated from a remote signing operation + +Usage: rcodesign[EXE] remote-sign [OPTIONS] <--editor|--sjs-file |SESSION_JOIN_STRING> + +Arguments: + [SESSION_JOIN_STRING] + Session join string (provided by the signing initiator) + +Options: + -C, --config-file + Explicit configuration file to load. + + If provided, the default configuration files are not loaded, even if they exist. + + Can be specified multiple times. Files are loaded/merged in the order given. + + The special value `/dev/null` can be used to specify an empty/null config file. It can be used to short-circuit loading of default config files. + + --editor + Open an editor to input the session join string + + -P, --profile + Configuration profile to load. + + If not specified, the implicit "default" profile is loaded. + + --sjs-file + Path to file containing session join string + + --smartcard-slot + Smartcard slot number of signing certificate to use (9c is common) + + -v, --verbose... + Increase logging verbosity. Can be specified multiple times + + --smartcard-pin + Smartcard PIN used to unlock certificate + + If not provided, you will be prompted for a PIN as necessary. + + --smartcard-pin-env + Environment variable holding the smartcard PIN + + --keychain-domain + (macOS only) Keychain domain to operate on + + [possible values: user, system, common, dynamic] + + --keychain-fingerprint + (macOS only) SHA-256 fingerprint of certificate in Keychain to use + + --windows-store-name + (Windows only) Windows Store to operate on + + [possible values: user, machine, service] + + --windows-store-sha1-fingerprint + (Windows only) SHA-1 fingerprint of certificate in Windows Store to use + + --pem-file + Path to file containing PEM encoded certificate/key data + + --p12-file + Path to a .p12/PFX file containing a certificate key pair + + --p12-password + The password to use to open the --p12-file file + + --p12-password-file + Path to file containing password for opening --p12-file file + + --remote-signing-url + URL of a remote code signing server + + --remote-public-key + Base64 encoded public key data describing the signer + + --remote-public-key-pem-file + PEM encoded public key data describing the signer + + --remote-shared-secret + Shared secret used for remote signing + + --remote-shared-secret-env + Environment variable holding the shared secret used for remote signing + + --certificate-der-file + Path to file containing DER encoded certificate data + + -h, --help + Print help (see a summary with '-h') + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-binary-identifier.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-binary-identifier.trycmd new file mode 100644 index 00000000..bbc36bf4 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-binary-identifier.trycmd @@ -0,0 +1,113 @@ +``` +$ rcodesign debug-create-macho exe +assuming default minimum version 11.0.0 +writing Mach-O to exe + +$ rcodesign sign --binary-identifier my-binary exe exe.signed +signing exe to exe.signed +signing exe as a Mach-O binary +parsing Mach-O +writing Mach-O to exe.signed + +$ rcodesign print-signature-info exe.signed +- path: exe.signed + file_size: 22544 + file_sha256: 8ecbc87f8c751be9a6048b546f52eb817a34d1ac3839cc7763a14445a2068140 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16778 / 0x418a + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 394 / 0x18a + linkedit_bytes_after_signature: 5766 / 0x1686 + signature: + superblob_length: 378 / 0x17a + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 322 + sha1: c1f056da05543dc711b509c2d63e71ced1af1c9d + sha256: 8b710dbe31e6e2eecf6d8eaa6451a8f6afdf6a9cb0baa2f88cc0474ee5138889 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: my-binary + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null + +$ rcodesign sign exe.signed exe.signed.2 +signing exe.signed to exe.signed.2 +signing exe.signed as a Mach-O binary +setting binary identifier to exe +parsing Mach-O +writing Mach-O to exe.signed.2 + +$ rcodesign print-signature-info exe.signed.2 +- path: exe.signed.2 + file_size: 22544 + file_sha256: 8ecbc87f8c751be9a6048b546f52eb817a34d1ac3839cc7763a14445a2068140 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16778 / 0x418a + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 394 / 0x18a + linkedit_bytes_after_signature: 5766 / 0x1686 + signature: + superblob_length: 378 / 0x17a + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 322 + sha1: c1f056da05543dc711b509c2d63e71ced1af1c9d + sha256: 8b710dbe31e6e2eecf6d8eaa6451a8f6afdf6a9cb0baa2f88cc0474ee5138889 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: my-binary + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-dsym.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-dsym.trycmd new file mode 100644 index 00000000..62e4e1b2 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-dsym.trycmd @@ -0,0 +1,251 @@ +Sign an application bundle with debug symbols in a .dSYM directory. + +``` +$ rcodesign debug-create-info-plist --bundle-name MyApp MyApp.app/Contents/Info.plist +writing MyApp.app/Contents/Info.plist + +$ rcodesign debug-create-macho MyApp.app/Contents/MacOS/MyApp +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/MacOS/MyApp + +$ mkdir -p MyApp.app/Contents/MacOS/MyApp.app.dSYM/Contents/Resources/DWARF +$ mkdir -p MyApp.app/Contents/MacOS/MyApp.app.dSYM/Contents/Resources/Relocations/aarch64 + +$ rcodesign debug-create-info-plist --bundle-name MyApp.app.dSYM --package-type dSYM MyApp.app/Contents/MacOS/MyApp.app.dSYM/Contents/Info.plist +writing MyApp.app/Contents/MacOS/MyApp.app.dSYM/Contents/Info.plist + +$ touch MyApp.app/Contents/MacOS/MyApp.app.dSYM/Contents/Resources/DWARF/MyApp +$ touch MyApp.app/Contents/MacOS/MyApp.app.dSYM/Contents/Resources/Relocations/aarch64/MyApp.yml + +$ rcodesign sign MyApp.app MyApp.app.signed +signing MyApp.app to MyApp.app.signed +signing bundle at MyApp.app +signing bundle at MyApp.app into MyApp.app.signed +signing main executable Contents/MacOS/MyApp + +$ rcodesign debug-file-tree MyApp.app.signed +d MyApp.app.signed/ +d MyApp.app.signed/Contents +f 0a5902dc8e47f490d038 MyApp.app.signed/Contents/Info.plist +d MyApp.app.signed/Contents/MacOS +f 5ae6736f434bcc89d779 MyApp.app.signed/Contents/MacOS/MyApp +d MyApp.app.signed/Contents/MacOS/MyApp.app.dSYM +d MyApp.app.signed/Contents/MacOS/MyApp.app.dSYM/Contents +f 6708b8b2252c5817e214 MyApp.app.signed/Contents/MacOS/MyApp.app.dSYM/Contents/Info.plist +d MyApp.app.signed/Contents/MacOS/MyApp.app.dSYM/Contents/Resources +d MyApp.app.signed/Contents/MacOS/MyApp.app.dSYM/Contents/Resources/DWARF +f e3b0c44298fc1c149afb MyApp.app.signed/Contents/MacOS/MyApp.app.dSYM/Contents/Resources/DWARF/MyApp +d MyApp.app.signed/Contents/MacOS/MyApp.app.dSYM/Contents/Resources/Relocations +d MyApp.app.signed/Contents/MacOS/MyApp.app.dSYM/Contents/Resources/Relocations/aarch64 +f e3b0c44298fc1c149afb MyApp.app.signed/Contents/MacOS/MyApp.app.dSYM/Contents/Resources/Relocations/aarch64/MyApp.yml +d MyApp.app.signed/Contents/_CodeSignature +f cc2d27446655d0b25e58 MyApp.app.signed/Contents/_CodeSignature/CodeResources + +$ rcodesign print-signature-info MyApp.app.signed +- path: Contents/Info.plist + file_size: 576 + file_sha256: 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5 + entity: other +- path: Contents/MacOS/MyApp + file_size: 22544 + file_sha256: 5ae6736f434bcc89d7797606854610aec1618993e858fc5f8ba2a487ea5af576 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16821 / 0x41b5 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 437 / 0x1b5 + linkedit_bytes_after_signature: 5723 / 0x165b + signature: + superblob_length: 421 / 0x1a5 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 365 + sha1: c75393bcf61044e0d10cb03c81bc74c82966b595 + sha256: daa2ade41cf51bf9664f9a81e37d8b07f14af6eb62d2d649667ac65c3f80235f + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: com.example.mybundle + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + - 'Resources (3): cc2d27446655d0b25e58aa5da3dc69320fa09d259e0215f17e6bdee93b354043' + cms: null +- path: Contents/MacOS/MyApp.app.dSYM/Contents/Info.plist + file_size: 603 + file_sha256: 6708b8b2252c5817e2142a8c4a818b4463b822fd891e436583686c1b29cc061a + entity: other +- path: Contents/MacOS/MyApp.app.dSYM/Contents/Resources/DWARF/MyApp + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other +- path: Contents/MacOS/MyApp.app.dSYM/Contents/Resources/Relocations/aarch64/MyApp.yml + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other +- path: Contents/_CodeSignature/CodeResources + file_size: 2734 + file_sha256: cc2d27446655d0b25e58aa5da3dc69320fa09d259e0215f17e6bdee93b354043 + entity: + bundle_code_signature_file: !ResourcesXml + - + - + - + - + - ' files' + - ' ' + - ' files2' + - ' ' + - ' MacOS/MyApp.app.dSYM/Contents/Info.plist' + - ' ' + - ' hash2' + - ' ' + - ' Zwi4siUsWBfiFCqMSoGLRGO4Iv2JHkNlg2hsGynMBho=' + - ' ' + - ' ' + - ' MacOS/MyApp.app.dSYM/Contents/Resources/DWARF/MyApp' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' ' + - ' MacOS/MyApp.app.dSYM/Contents/Resources/Relocations/aarch64/MyApp.yml' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' ' + - ' ' + - ' rules' + - ' ' + - ' ^Resources/' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^version.plist$' + - ' ' + - ' ' + - ' rules2' + - ' ' + - ' .*/.dSYM($|/)' + - ' ' + - ' weight' + - ' 11' + - ' ' + - ' ^(.*/)?/.DS_Store$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 2000' + - ' ' + - ' ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^.*' + - ' ' + - ' ^Info/.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^PkgInfo$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^[^/]+$' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^embedded/.provisionprofile$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^version/.plist$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ' + - + - + - '' + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-electron.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-electron.trycmd new file mode 100644 index 00000000..bba0a4ee --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-electron.trycmd @@ -0,0 +1,1186 @@ +Sign Electron.app bundle + +``` +$ mkdir -p "Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Helpers" +$ mkdir -p "Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries" +$ mkdir -p "Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/en.lproj" + +$ rcodesign debug-create-macho --file-type dylib "Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework" +assuming default minimum version 11.0.0 +writing Mach-O to Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework + +$ rcodesign debug-create-macho "Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Helpers/chrome_crashpad_handler" +assuming default minimum version 11.0.0 +writing Mach-O to Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Helpers/chrome_crashpad_handler + +$ rcodesign debug-create-macho --file-type dylib "Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libEGL.dylib" +assuming default minimum version 11.0.0 +writing Mach-O to Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libEGL.dylib + +$ touch "Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/vk_swiftshader_icd.json" +$ touch "Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/chrome_100_percent.pak" +$ touch "Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/en.lproj/locale.pak" +$ touch "Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/icudtl.dat" + +$ rcodesign debug-create-info-plist --bundle-name "Electron Framework" --bundle-executable "Electron Framework" --package-type FMWK --bundle-identifier com.github.Electron.framework "Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Info.plist" +writing Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Info.plist + +$ touch "Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/MainMenu.nib" +$ touch "Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/resources.pak" +$ touch "Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/v8_context_snapshot.arm64.bin" + +$ ln -s A "Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/Current" +$ ln -s "Versions/Current/Electron Framework" "Electron.app/Contents/Frameworks/Electron Framework.framework/Electron Framework" +$ ln -s Versions/Current/Helpers "Electron.app/Contents/Frameworks/Electron Framework.framework/Helpers" +$ ln -s Versions/Current/Libraries "Electron.app/Contents/Frameworks/Electron Framework.framework/Libraries" +$ ln -s Versions/Current/Resources "Electron.app/Contents/Frameworks/Electron Framework.framework/Resources" + +$ mkdir -p "Electron.app/Contents/Frameworks/Electron Helper (GPU).app/Contents/MacOS" +$ rcodesign debug-create-macho "Electron.app/Contents/Frameworks/Electron Helper (GPU).app/Contents/MacOS/Electron Helper (GPU)" +assuming default minimum version 11.0.0 +writing Mach-O to Electron.app/Contents/Frameworks/Electron Helper (GPU).app/Contents/MacOS/Electron Helper (GPU) + +$ rcodesign debug-create-info-plist --bundle-name "Electron Helper (GPU)" --package-type APPL --bundle-identifier com.github.Electron.helper "Electron.app/Contents/Frameworks/Electron Helper (GPU).app/Contents/Info.plist" +writing Electron.app/Contents/Frameworks/Electron Helper (GPU).app/Contents/Info.plist + +$ mkdir -p Electron.app/Contents/Frameworks/Mantle.framework/Versions/A/Resources + +$ rcodesign debug-create-macho --file-type dylib Electron.app/Contents/Frameworks/Mantle.framework/Versions/A/Mantle +assuming default minimum version 11.0.0 +writing Mach-O to Electron.app/Contents/Frameworks/Mantle.framework/Versions/A/Mantle + +$ rcodesign debug-create-info-plist --bundle-name Mantle --bundle-executable Mantle --bundle-identifier org.mantle.Mantle --package-type FMWK Electron.app/Contents/Frameworks/Mantle.framework/Versions/A/Resources/Info.plist +writing Electron.app/Contents/Frameworks/Mantle.framework/Versions/A/Resources/Info.plist + +$ ln -s A Electron.app/Contents/Frameworks/Mantle.framework/Versions/Current +$ ln -s Versions/Current/Mantle Electron.app/Contents/Frameworks/Mantle.framework/Mantle +$ ln -s Versions/Current/Resources Electron.app/Contents/Frameworks/Mantle.framework/Resources + +$ rcodesign debug-create-macho Electron.app/Contents/MacOS/Electron +assuming default minimum version 11.0.0 +writing Mach-O to Electron.app/Contents/MacOS/Electron + +$ rcodesign debug-create-info-plist --bundle-name Electron --bundle-identifier com.github.Electron --bundle-executable Electron Electron.app/Contents/Info.plist +writing Electron.app/Contents/Info.plist + +$ mkdir -p Electron.app/Contents/Resources +$ touch Electron.app/Contents/Resources/default_app.asar +$ touch Electron.app/Contents/Resources/en.lproj +$ touch Electron.app/Contents/Resources/electron.icns + +$ rcodesign sign --code-signature-flags "Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework:runtime" Electron.app Electron.app.signed +adding code signature flag CodeSignatureFlags(RUNTIME) to path Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework +signing Electron.app to Electron.app.signed +signing bundle at Electron.app +signing 5 nested bundles in the following order: +Contents/Frameworks/Electron Framework.framework/Versions/A +Contents/Frameworks/Electron Framework.framework +Contents/Frameworks/Mantle.framework/Versions/A +Contents/Frameworks/Electron Helper (GPU).app +Contents/Frameworks/Mantle.framework +entering nested bundle Contents/Frameworks/Electron Framework.framework/Versions/A +signing bundle at Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A into Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Versions/A +signing Mach-O file Helpers/chrome_crashpad_handler +signing Mach-O file Libraries/libEGL.dylib +signing main executable Electron Framework +leaving nested bundle Contents/Frameworks/Electron Framework.framework/Versions/A +entering nested bundle Contents/Frameworks/Electron Framework.framework +signing bundle at Electron.app/Contents/Frameworks/Electron Framework.framework into Electron.app.signed/Contents/Frameworks/Electron Framework.framework +leaving nested bundle Contents/Frameworks/Electron Framework.framework +entering nested bundle Contents/Frameworks/Mantle.framework/Versions/A +signing bundle at Electron.app/Contents/Frameworks/Mantle.framework/Versions/A into Electron.app.signed/Contents/Frameworks/Mantle.framework/Versions/A +signing main executable Mantle +leaving nested bundle Contents/Frameworks/Mantle.framework/Versions/A +entering nested bundle Contents/Frameworks/Electron Helper (GPU).app +signing bundle at Electron.app/Contents/Frameworks/Electron Helper (GPU).app into Electron.app.signed/Contents/Frameworks/Electron Helper (GPU).app +signing main executable Contents/MacOS/Electron Helper (GPU) +leaving nested bundle Contents/Frameworks/Electron Helper (GPU).app +entering nested bundle Contents/Frameworks/Mantle.framework +signing bundle at Electron.app/Contents/Frameworks/Mantle.framework into Electron.app.signed/Contents/Frameworks/Mantle.framework +leaving nested bundle Contents/Frameworks/Mantle.framework +signing bundle at Electron.app into Electron.app.signed +signing main executable Contents/MacOS/Electron + +$ rcodesign debug-file-tree Electron.app.signed +d Electron.app.signed/ +d Electron.app.signed/Contents +d Electron.app.signed/Contents/Frameworks +d Electron.app.signed/Contents/Frameworks/Electron Framework.framework +l Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Electron Framework -> Versions/Current/Electron Framework +l Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Helpers -> Versions/Current/Helpers +l Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Libraries -> Versions/Current/Libraries +l Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Resources -> Versions/Current/Resources +d Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Versions +d Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Versions/A +f aecbcafc6b0d73a2b6a7 Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework +d Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Versions/A/Helpers +f 136b73cf509765caec58 Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Versions/A/Helpers/chrome_crashpad_handler +d Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries +f 554535fd2d43b0025065 Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libEGL.dylib +f e3b0c44298fc1c149afb Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/vk_swiftshader_icd.json +d Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources +f ca20386388c65cc79004 Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Info.plist +f e3b0c44298fc1c149afb Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/MainMenu.nib +f e3b0c44298fc1c149afb Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/chrome_100_percent.pak +d Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/en.lproj +f e3b0c44298fc1c149afb Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/en.lproj/locale.pak +f e3b0c44298fc1c149afb Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/icudtl.dat +f e3b0c44298fc1c149afb Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/resources.pak +f e3b0c44298fc1c149afb Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/v8_context_snapshot.arm64.bin +d Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Versions/A/_CodeSignature +f 0875b5aa7dfec9d4966a Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Versions/A/_CodeSignature/CodeResources +l Electron.app.signed/Contents/Frameworks/Electron Framework.framework/Versions/Current -> A +d Electron.app.signed/Contents/Frameworks/Electron Helper (GPU).app +d Electron.app.signed/Contents/Frameworks/Electron Helper (GPU).app/Contents +f 7d16bb3cf776fb0a7eb0 Electron.app.signed/Contents/Frameworks/Electron Helper (GPU).app/Contents/Info.plist +d Electron.app.signed/Contents/Frameworks/Electron Helper (GPU).app/Contents/MacOS +f 35677ddaf12c56b88860 Electron.app.signed/Contents/Frameworks/Electron Helper (GPU).app/Contents/MacOS/Electron Helper (GPU) +d Electron.app.signed/Contents/Frameworks/Electron Helper (GPU).app/Contents/_CodeSignature +f 6686de10a28a2fe11b36 Electron.app.signed/Contents/Frameworks/Electron Helper (GPU).app/Contents/_CodeSignature/CodeResources +d Electron.app.signed/Contents/Frameworks/Mantle.framework +l Electron.app.signed/Contents/Frameworks/Mantle.framework/Mantle -> Versions/Current/Mantle +l Electron.app.signed/Contents/Frameworks/Mantle.framework/Resources -> Versions/Current/Resources +d Electron.app.signed/Contents/Frameworks/Mantle.framework/Versions +d Electron.app.signed/Contents/Frameworks/Mantle.framework/Versions/A +f ce8d29746a1cc2fb4036 Electron.app.signed/Contents/Frameworks/Mantle.framework/Versions/A/Mantle +d Electron.app.signed/Contents/Frameworks/Mantle.framework/Versions/A/Resources +f 542d886f74e466a0958e Electron.app.signed/Contents/Frameworks/Mantle.framework/Versions/A/Resources/Info.plist +d Electron.app.signed/Contents/Frameworks/Mantle.framework/Versions/A/_CodeSignature +f 738650a98f84347da27c Electron.app.signed/Contents/Frameworks/Mantle.framework/Versions/A/_CodeSignature/CodeResources +l Electron.app.signed/Contents/Frameworks/Mantle.framework/Versions/Current -> A +f 863f967826aa4c32179d Electron.app.signed/Contents/Info.plist +d Electron.app.signed/Contents/MacOS +f 43225c3096a343375eaf Electron.app.signed/Contents/MacOS/Electron +d Electron.app.signed/Contents/Resources +f e3b0c44298fc1c149afb Electron.app.signed/Contents/Resources/default_app.asar +f e3b0c44298fc1c149afb Electron.app.signed/Contents/Resources/electron.icns +f e3b0c44298fc1c149afb Electron.app.signed/Contents/Resources/en.lproj +d Electron.app.signed/Contents/_CodeSignature +f 55ce55777af6a66c7737 Electron.app.signed/Contents/_CodeSignature/CodeResources + +$ rcodesign print-signature-info Electron.app.signed +- path: Contents/Frameworks/Electron Framework.framework/Electron Framework + symlink_target: Versions/Current/Electron Framework + entity: other +- path: Contents/Frameworks/Electron Framework.framework/Helpers + symlink_target: Versions/Current/Helpers + entity: other +- path: Contents/Frameworks/Electron Framework.framework/Libraries + symlink_target: Versions/Current/Libraries + entity: other +- path: Contents/Frameworks/Electron Framework.framework/Resources + symlink_target: Versions/Current/Resources + entity: other +- path: Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework + file_size: 22544 + file_sha256: aecbcafc6b0d73a2b6a790ab5fcc0cfff832f554bdd8a06908f75fc7b8a52ab6 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16838 / 0x41c6 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 454 / 0x1c6 + linkedit_bytes_after_signature: 5706 / 0x164a + signature: + superblob_length: 438 / 0x1b6 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 382 + sha1: 43ebaf4eed05cf2c196a5de7646d1d2318943c8d + sha256: 20a9e60c295ac2feaa3be81e5b995e8fcf0cc26913acb3ee7c21673f9c1d2895 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20500' + flags: CodeSignatureFlags(ADHOC | RUNTIME) + identifier: com.github.Electron.framework + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(0x0) + runtime_version: 11.0.0 + code_digests_count: 5 + slot_digests: + - 'Info (1): ca20386388c65cc7900433ebe743ff74f302160c0de829874df9a9839f318e4a' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + - 'Resources (3): 0875b5aa7dfec9d4966a7abff771ad8c7d8e4f3ec8b8d7390e571601076ae04c' + cms: null +- path: Contents/Frameworks/Electron Framework.framework/Versions/A/Helpers/chrome_crashpad_handler + file_size: 22544 + file_sha256: 136b73cf509765caec58c914847548ef1660ba6cf293c4bfa7ef51e8d417b8eb + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16792 / 0x4198 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 408 / 0x198 + linkedit_bytes_after_signature: 5752 / 0x1678 + signature: + superblob_length: 392 / 0x188 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 336 + sha1: 6c5f9e19c9aa10787bceb2128bac1531c942e117 + sha256: 70d3c12507e0ddc998a4843c10780f456fd2d95a56e6f23de7197841fb6d7395 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: chrome_crashpad_handler + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null +- path: Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libEGL.dylib + file_size: 22544 + file_sha256: 554535fd2d43b00250656c38f9944a1d18feb40a11e3631099749bc6c71d3c6b + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16775 / 0x4187 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 391 / 0x187 + linkedit_bytes_after_signature: 5769 / 0x1689 + signature: + superblob_length: 375 / 0x177 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 319 + sha1: a77fab6390bed2e3317940fb6c4a7f2935af5545 + sha256: 3226b5a3048d6276f4e9a797ad7066b44620ef7530bef8838205d64a966ac8cb + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: libEGL + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(0x0) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null +- path: Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/vk_swiftshader_icd.json + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other +- path: Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Info.plist + file_size: 624 + file_sha256: ca20386388c65cc7900433ebe743ff74f302160c0de829874df9a9839f318e4a + entity: other +- path: Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/MainMenu.nib + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other +- path: Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/chrome_100_percent.pak + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other +- path: Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/en.lproj/locale.pak + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other +- path: Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/icudtl.dat + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other +- path: Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/resources.pak + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other +- path: Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/v8_context_snapshot.arm64.bin + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other +- path: Contents/Frameworks/Electron Framework.framework/Versions/A/_CodeSignature/CodeResources + file_size: 4531 + file_sha256: 0875b5aa7dfec9d4966a7abff771ad8c7d8e4f3ec8b8d7390e571601076ae04c + entity: + bundle_code_signature_file: !ResourcesXml + - + - + - + - + - ' files' + - ' ' + - ' Resources/Info.plist' + - ' ' + - ' oGzbq3iJoIFhpLZNxk9WdYGW72I=' + - ' ' + - ' Resources/MainMenu.nib' + - ' ' + - ' 2jmj7l5rSw0yVb/vlWAYkK/YBwk=' + - ' ' + - ' Resources/chrome_100_percent.pak' + - ' ' + - ' 2jmj7l5rSw0yVb/vlWAYkK/YBwk=' + - ' ' + - ' Resources/en.lproj/locale.pak' + - ' ' + - ' hash' + - ' ' + - ' 2jmj7l5rSw0yVb/vlWAYkK/YBwk=' + - ' ' + - ' optional' + - ' ' + - ' ' + - ' Resources/icudtl.dat' + - ' ' + - ' 2jmj7l5rSw0yVb/vlWAYkK/YBwk=' + - ' ' + - ' Resources/resources.pak' + - ' ' + - ' 2jmj7l5rSw0yVb/vlWAYkK/YBwk=' + - ' ' + - ' Resources/v8_context_snapshot.arm64.bin' + - ' ' + - ' 2jmj7l5rSw0yVb/vlWAYkK/YBwk=' + - ' ' + - ' ' + - ' files2' + - ' ' + - ' Helpers/chrome_crashpad_handler' + - ' ' + - ' cdhash' + - ' ' + - ' cNPBJQfg3cmYpIQ8EHgPRW/S2Vo=' + - ' ' + - ' requirement' + - ' cdhash H"70d3c12507e0ddc998a4843c10780f456fd2d95a"' + - ' ' + - ' Libraries/libEGL.dylib' + - ' ' + - ' hash2' + - ' ' + - ' VUU1/S1DsAJQZWw4+ZRKHRj+tAoR42MQmXSbxscdPGs=' + - ' ' + - ' ' + - ' Libraries/vk_swiftshader_icd.json' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' ' + - ' Resources/Info.plist' + - ' ' + - ' hash2' + - ' ' + - ' yiA4Y4jGXMeQBDPr50P/dPMCFgwN6CmHTfmpg58xjko=' + - ' ' + - ' ' + - ' Resources/MainMenu.nib' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' ' + - ' Resources/chrome_100_percent.pak' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' ' + - ' Resources/en.lproj/locale.pak' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' optional' + - ' ' + - ' ' + - ' Resources/icudtl.dat' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' ' + - ' Resources/resources.pak' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' ' + - ' Resources/v8_context_snapshot.arm64.bin' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' ' + - ' ' + - ' rules' + - ' ' + - ' ^Resources/' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^version.plist$' + - ' ' + - ' ' + - ' rules2' + - ' ' + - ' .*/.dSYM($|/)' + - ' ' + - ' weight' + - ' 11' + - ' ' + - ' ^(.*/)?/.DS_Store$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 2000' + - ' ' + - ' ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^.*' + - ' ' + - ' ^Info/.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^PkgInfo$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^[^/]+$' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^embedded/.provisionprofile$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^version/.plist$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ' + - + - + - '' +- path: Contents/Frameworks/Electron Framework.framework/Versions/Current + symlink_target: A + entity: other +- path: Contents/Frameworks/Electron Helper (GPU).app/Contents/Info.plist + file_size: 630 + file_sha256: 7d16bb3cf776fb0a7eb0ee77fd040468fc1e8723546668b11cdb23896bff0c9e + entity: other +- path: Contents/Frameworks/Electron Helper (GPU).app/Contents/MacOS/Electron Helper (GPU) + file_size: 22544 + file_sha256: 35677ddaf12c56b888602e18d84d6f6ed83965f8124487a22b508494f202943d + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16827 / 0x41bb + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 443 / 0x1bb + linkedit_bytes_after_signature: 5717 / 0x1655 + signature: + superblob_length: 427 / 0x1ab + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 371 + sha1: efa4ded29dd90f2ab5534e24b57a5b05111110a8 + sha256: da49ede188bbfd7c14e387d2b33f498576a047e7c77b2874a5381328835ac8ec + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: com.github.Electron.helper + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 7d16bb3cf776fb0a7eb0ee77fd040468fc1e8723546668b11cdb23896bff0c9e' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + - 'Resources (3): 6686de10a28a2fe11b36cbb86dcbacc827cfc4ea116b4dabf1845e5aee629e9b' + cms: null +- path: Contents/Frameworks/Electron Helper (GPU).app/Contents/_CodeSignature/CodeResources + file_size: 2200 + file_sha256: 6686de10a28a2fe11b36cbb86dcbacc827cfc4ea116b4dabf1845e5aee629e9b + entity: + bundle_code_signature_file: !ResourcesXml + - + - + - + - + - ' files' + - ' ' + - ' files2' + - ' ' + - ' rules' + - ' ' + - ' ^Resources/' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^version.plist$' + - ' ' + - ' ' + - ' rules2' + - ' ' + - ' .*/.dSYM($|/)' + - ' ' + - ' weight' + - ' 11' + - ' ' + - ' ^(.*/)?/.DS_Store$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 2000' + - ' ' + - ' ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^.*' + - ' ' + - ' ^Info/.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^PkgInfo$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^[^/]+$' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^embedded/.provisionprofile$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^version/.plist$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ' + - + - + - '' +- path: Contents/Frameworks/Mantle.framework/Mantle + symlink_target: Versions/Current/Mantle + entity: other +- path: Contents/Frameworks/Mantle.framework/Resources + symlink_target: Versions/Current/Resources + entity: other +- path: Contents/Frameworks/Mantle.framework/Versions/A/Mantle + file_size: 22544 + file_sha256: ce8d29746a1cc2fb403658ca802576f05dd5ddc2a43ec20e966e52b08eef025e + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16818 / 0x41b2 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 434 / 0x1b2 + linkedit_bytes_after_signature: 5726 / 0x165e + signature: + superblob_length: 418 / 0x1a2 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 362 + sha1: 90daedaf7217c1c2058237690b3e7f3bc16fb5da + sha256: b53e751c8bbc19ce5c434090de9171fdbeaa5572b32c782d07c6d0dee50765ea + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: org.mantle.Mantle + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(0x0) + code_digests_count: 5 + slot_digests: + - 'Info (1): 542d886f74e466a0958e74db24ee7bf72f59a6813aacd5309e44b097c6806ea1' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + - 'Resources (3): 738650a98f84347da27c928a05de06a52d205cc1ba4ea1d8befe4d0656cc0dd4' + cms: null +- path: Contents/Frameworks/Mantle.framework/Versions/A/Resources/Info.plist + file_size: 576 + file_sha256: 542d886f74e466a0958e74db24ee7bf72f59a6813aacd5309e44b097c6806ea1 + entity: other +- path: Contents/Frameworks/Mantle.framework/Versions/A/_CodeSignature/CodeResources + file_size: 2442 + file_sha256: 738650a98f84347da27c928a05de06a52d205cc1ba4ea1d8befe4d0656cc0dd4 + entity: + bundle_code_signature_file: !ResourcesXml + - + - + - + - + - ' files' + - ' ' + - ' Resources/Info.plist' + - ' ' + - ' SfsfCgls/vD7J5tPNoHNVLGUdY8=' + - ' ' + - ' ' + - ' files2' + - ' ' + - ' Resources/Info.plist' + - ' ' + - ' hash2' + - ' ' + - ' VC2Ib3TkZqCVjnTbJO579y9ZpoE6rNUwnkSwl8aAbqE=' + - ' ' + - ' ' + - ' ' + - ' rules' + - ' ' + - ' ^Resources/' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^version.plist$' + - ' ' + - ' ' + - ' rules2' + - ' ' + - ' .*/.dSYM($|/)' + - ' ' + - ' weight' + - ' 11' + - ' ' + - ' ^(.*/)?/.DS_Store$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 2000' + - ' ' + - ' ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^.*' + - ' ' + - ' ^Info/.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^PkgInfo$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^[^/]+$' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^embedded/.provisionprofile$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^version/.plist$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ' + - + - + - '' +- path: Contents/Frameworks/Mantle.framework/Versions/Current + symlink_target: A + entity: other +- path: Contents/Info.plist + file_size: 584 + file_sha256: 863f967826aa4c32179d88ce7febeef529aed41c05ba2204c79dd1d2ab6b7296 + entity: other +- path: Contents/MacOS/Electron + file_size: 22544 + file_sha256: 43225c3096a343375eafa4cc06943b42078759fe5c646a47c339beee1d308916 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16820 / 0x41b4 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 436 / 0x1b4 + linkedit_bytes_after_signature: 5724 / 0x165c + signature: + superblob_length: 420 / 0x1a4 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 364 + sha1: 1aff0dbb9a326f29d800a20f379478279408ba8a + sha256: 1e456159572536196ec7ef24333775d60f574f9c41fd7b0bfc3ce6555a5ac67a + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: com.github.Electron + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 863f967826aa4c32179d88ce7febeef529aed41c05ba2204c79dd1d2ab6b7296' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + - 'Resources (3): 55ce55777af6a66c773763e8dd721846485283d9d6b3b0ac6b91a6e90eb6954c' + cms: null +- path: Contents/Resources/default_app.asar + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other +- path: Contents/Resources/electron.icns + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other +- path: Contents/Resources/en.lproj + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other +- path: Contents/_CodeSignature/CodeResources + file_size: 3622 + file_sha256: 55ce55777af6a66c773763e8dd721846485283d9d6b3b0ac6b91a6e90eb6954c + entity: + bundle_code_signature_file: !ResourcesXml + - + - + - + - + - ' files' + - ' ' + - ' Resources/default_app.asar' + - ' ' + - ' 2jmj7l5rSw0yVb/vlWAYkK/YBwk=' + - ' ' + - ' Resources/electron.icns' + - ' ' + - ' 2jmj7l5rSw0yVb/vlWAYkK/YBwk=' + - ' ' + - ' Resources/en.lproj' + - ' ' + - ' 2jmj7l5rSw0yVb/vlWAYkK/YBwk=' + - ' ' + - ' ' + - ' files2' + - ' ' + - ' Frameworks/Electron Framework.framework' + - ' ' + - ' cdhash' + - ' ' + - ' IKnmDClawv6qO+geW5lej88Mwmk=' + - ' ' + - ' requirement' + - ' cdhash H"20a9e60c295ac2feaa3be81e5b995e8fcf0cc269"' + - ' ' + - ' Frameworks/Electron Helper (GPU).app' + - ' ' + - ' cdhash' + - ' ' + - ' 2knt4Yi7/XwU44fSsz9JhXagR+c=' + - ' ' + - ' requirement' + - ' cdhash H"da49ede188bbfd7c14e387d2b33f498576a047e7"' + - ' ' + - ' Frameworks/Mantle.framework' + - ' ' + - ' cdhash' + - ' ' + - ' tT51HIu8Gc5cQ0CQ3pFx/b6qVXI=' + - ' ' + - ' requirement' + - ' cdhash H"b53e751c8bbc19ce5c434090de9171fdbeaa5572"' + - ' ' + - ' Resources/default_app.asar' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' ' + - ' Resources/electron.icns' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' ' + - ' Resources/en.lproj' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' ' + - ' ' + - ' rules' + - ' ' + - ' ^Resources/' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^version.plist$' + - ' ' + - ' ' + - ' rules2' + - ' ' + - ' .*/.dSYM($|/)' + - ' ' + - ' weight' + - ' 11' + - ' ' + - ' ^(.*/)?/.DS_Store$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 2000' + - ' ' + - ' ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^.*' + - ' ' + - ' ^Info/.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^PkgInfo$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^[^/]+$' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^embedded/.provisionprofile$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^version/.plist$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ' + - + - + - '' + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-exclude.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-exclude.trycmd new file mode 100644 index 00000000..82ac25ed --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-exclude.trycmd @@ -0,0 +1,277 @@ +--exclude skips signing a nested bundle + +``` +$ rcodesign debug-create-macho MyApp.app/Contents/MacOS/MyApp +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/MacOS/MyApp + +$ rcodesign debug-create-info-plist --bundle-name MyApp MyApp.app/Contents/Info.plist +writing MyApp.app/Contents/Info.plist + +$ rcodesign debug-create-macho --file-type dylib MyApp.app/Contents/Frameworks/MyFramework.framework/Versions/A/MyFramework +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/Frameworks/MyFramework.framework/Versions/A/MyFramework + +$ rcodesign debug-create-info-plist --bundle-name MyFramework MyApp.app/Contents/Frameworks/MyFramework.framework/Versions/A/Resources/Info.plist +writing MyApp.app/Contents/Frameworks/MyFramework.framework/Versions/A/Resources/Info.plist + +$ ln -s A MyApp.app/Contents/Frameworks/MyFramework.framework/Versions/Current +$ ln -s Versions/Current/Resources MyApp.app/Contents/Frameworks/MyFramework.framework/Resources + +$ rcodesign sign --exclude 'Contents/Frameworks/MyFramework.framework' MyApp.app MyApp.app.NoMyFramework +signing MyApp.app to MyApp.app.NoMyFramework +signing bundle at MyApp.app +signing 2 nested bundles in the following order: +Contents/Frameworks/MyFramework.framework/Versions/A +Contents/Frameworks/MyFramework.framework +entering nested bundle Contents/Frameworks/MyFramework.framework/Versions/A +signing bundle at MyApp.app/Contents/Frameworks/MyFramework.framework/Versions/A into MyApp.app.NoMyFramework/Contents/Frameworks/MyFramework.framework/Versions/A +signing main executable MyFramework +leaving nested bundle Contents/Frameworks/MyFramework.framework/Versions/A +entering nested bundle Contents/Frameworks/MyFramework.framework +bundle is in exclusion list; it will be copied instead of signed +leaving nested bundle Contents/Frameworks/MyFramework.framework +signing bundle at MyApp.app into MyApp.app.NoMyFramework +could not find main executable of presumed nested bundle: Contents/Frameworks/MyFramework.framework +signing main executable Contents/MacOS/MyApp + +$ rcodesign debug-file-tree MyApp.app.NoMyFramework +d MyApp.app.NoMyFramework/ +d MyApp.app.NoMyFramework/Contents +d MyApp.app.NoMyFramework/Contents/Frameworks +d MyApp.app.NoMyFramework/Contents/Frameworks/MyFramework.framework +l MyApp.app.NoMyFramework/Contents/Frameworks/MyFramework.framework/Resources -> Versions/Current/Resources +d MyApp.app.NoMyFramework/Contents/Frameworks/MyFramework.framework/Versions +d MyApp.app.NoMyFramework/Contents/Frameworks/MyFramework.framework/Versions/A +f 6249a5455b5d60d322dc MyApp.app.NoMyFramework/Contents/Frameworks/MyFramework.framework/Versions/A/MyFramework +d MyApp.app.NoMyFramework/Contents/Frameworks/MyFramework.framework/Versions/A/Resources +f 53c337af0bf7c0762126 MyApp.app.NoMyFramework/Contents/Frameworks/MyFramework.framework/Versions/A/Resources/Info.plist +d MyApp.app.NoMyFramework/Contents/Frameworks/MyFramework.framework/Versions/A/_CodeSignature +f 6d524dfa68ea926bcc0e MyApp.app.NoMyFramework/Contents/Frameworks/MyFramework.framework/Versions/A/_CodeSignature/CodeResources +l MyApp.app.NoMyFramework/Contents/Frameworks/MyFramework.framework/Versions/Current -> A +f 0a5902dc8e47f490d038 MyApp.app.NoMyFramework/Contents/Info.plist +d MyApp.app.NoMyFramework/Contents/MacOS +f 5320635ab22c67638660 MyApp.app.NoMyFramework/Contents/MacOS/MyApp +d MyApp.app.NoMyFramework/Contents/_CodeSignature +f 6686de10a28a2fe11b36 MyApp.app.NoMyFramework/Contents/_CodeSignature/CodeResources + +$ rcodesign sign --exclude 'Contents/Frameworks/MyFramework.framework/**' MyApp.app MyApp.app.NoMyFrameworkDoubleWild +signing MyApp.app to MyApp.app.NoMyFrameworkDoubleWild +signing bundle at MyApp.app +signing 2 nested bundles in the following order: +Contents/Frameworks/MyFramework.framework/Versions/A +Contents/Frameworks/MyFramework.framework +entering nested bundle Contents/Frameworks/MyFramework.framework/Versions/A +bundle is in exclusion list; it will be copied instead of signed +leaving nested bundle Contents/Frameworks/MyFramework.framework/Versions/A +entering nested bundle Contents/Frameworks/MyFramework.framework +signing bundle at MyApp.app/Contents/Frameworks/MyFramework.framework into MyApp.app.NoMyFrameworkDoubleWild/Contents/Frameworks/MyFramework.framework +leaving nested bundle Contents/Frameworks/MyFramework.framework +signing bundle at MyApp.app into MyApp.app.NoMyFrameworkDoubleWild +could not find main executable of presumed nested bundle: Contents/Frameworks/MyFramework.framework +signing main executable Contents/MacOS/MyApp + +$ rcodesign debug-file-tree MyApp.app.NoMyFrameworkDoubleWild +d MyApp.app.NoMyFrameworkDoubleWild/ +d MyApp.app.NoMyFrameworkDoubleWild/Contents +d MyApp.app.NoMyFrameworkDoubleWild/Contents/Frameworks +d MyApp.app.NoMyFrameworkDoubleWild/Contents/Frameworks/MyFramework.framework +l MyApp.app.NoMyFrameworkDoubleWild/Contents/Frameworks/MyFramework.framework/Resources -> Versions/Current/Resources +d MyApp.app.NoMyFrameworkDoubleWild/Contents/Frameworks/MyFramework.framework/Versions +d MyApp.app.NoMyFrameworkDoubleWild/Contents/Frameworks/MyFramework.framework/Versions/A +f 8d89209153a67993e6ee MyApp.app.NoMyFrameworkDoubleWild/Contents/Frameworks/MyFramework.framework/Versions/A/MyFramework +d MyApp.app.NoMyFrameworkDoubleWild/Contents/Frameworks/MyFramework.framework/Versions/A/Resources +f 53c337af0bf7c0762126 MyApp.app.NoMyFrameworkDoubleWild/Contents/Frameworks/MyFramework.framework/Versions/A/Resources/Info.plist +l MyApp.app.NoMyFrameworkDoubleWild/Contents/Frameworks/MyFramework.framework/Versions/Current -> A +f 0a5902dc8e47f490d038 MyApp.app.NoMyFrameworkDoubleWild/Contents/Info.plist +d MyApp.app.NoMyFrameworkDoubleWild/Contents/MacOS +f 5320635ab22c67638660 MyApp.app.NoMyFrameworkDoubleWild/Contents/MacOS/MyApp +d MyApp.app.NoMyFrameworkDoubleWild/Contents/_CodeSignature +f 6686de10a28a2fe11b36 MyApp.app.NoMyFrameworkDoubleWild/Contents/_CodeSignature/CodeResources + +$ rcodesign sign --exclude 'Contents/Frameworks/**' MyApp.app MyApp.app.NoFrameworksDoubleWild +signing MyApp.app to MyApp.app.NoFrameworksDoubleWild +signing bundle at MyApp.app +signing 2 nested bundles in the following order: +Contents/Frameworks/MyFramework.framework/Versions/A +Contents/Frameworks/MyFramework.framework +entering nested bundle Contents/Frameworks/MyFramework.framework/Versions/A +bundle is in exclusion list; it will be copied instead of signed +leaving nested bundle Contents/Frameworks/MyFramework.framework/Versions/A +entering nested bundle Contents/Frameworks/MyFramework.framework +bundle is in exclusion list; it will be copied instead of signed +leaving nested bundle Contents/Frameworks/MyFramework.framework +signing bundle at MyApp.app into MyApp.app.NoFrameworksDoubleWild +could not find main executable of presumed nested bundle: Contents/Frameworks/MyFramework.framework +signing main executable Contents/MacOS/MyApp + +$ rcodesign debug-file-tree MyApp.app.NoFrameworksDoubleWild +d MyApp.app.NoFrameworksDoubleWild/ +d MyApp.app.NoFrameworksDoubleWild/Contents +d MyApp.app.NoFrameworksDoubleWild/Contents/Frameworks +d MyApp.app.NoFrameworksDoubleWild/Contents/Frameworks/MyFramework.framework +l MyApp.app.NoFrameworksDoubleWild/Contents/Frameworks/MyFramework.framework/Resources -> Versions/Current/Resources +d MyApp.app.NoFrameworksDoubleWild/Contents/Frameworks/MyFramework.framework/Versions +d MyApp.app.NoFrameworksDoubleWild/Contents/Frameworks/MyFramework.framework/Versions/A +f 8d89209153a67993e6ee MyApp.app.NoFrameworksDoubleWild/Contents/Frameworks/MyFramework.framework/Versions/A/MyFramework +d MyApp.app.NoFrameworksDoubleWild/Contents/Frameworks/MyFramework.framework/Versions/A/Resources +f 53c337af0bf7c0762126 MyApp.app.NoFrameworksDoubleWild/Contents/Frameworks/MyFramework.framework/Versions/A/Resources/Info.plist +l MyApp.app.NoFrameworksDoubleWild/Contents/Frameworks/MyFramework.framework/Versions/Current -> A +f 0a5902dc8e47f490d038 MyApp.app.NoFrameworksDoubleWild/Contents/Info.plist +d MyApp.app.NoFrameworksDoubleWild/Contents/MacOS +f 5320635ab22c67638660 MyApp.app.NoFrameworksDoubleWild/Contents/MacOS/MyApp +d MyApp.app.NoFrameworksDoubleWild/Contents/_CodeSignature +f 6686de10a28a2fe11b36 MyApp.app.NoFrameworksDoubleWild/Contents/_CodeSignature/CodeResources + +$ rm -rf MyApp.app + +``` + +Validate exclusion of Mach-O binaries + +``` +$ rcodesign debug-create-macho MyApp.app/Contents/MacOS/MyApp +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/MacOS/MyApp + +$ rcodesign debug-create-macho MyApp.app/Contents/MacOS/macos-bin +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/MacOS/macos-bin + +$ rcodesign debug-create-macho MyApp.app/Contents/Resources/resource-bin +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/Resources/resource-bin + +$ rcodesign debug-create-info-plist --bundle-name MyApp MyApp.app/Contents/Info.plist +writing MyApp.app/Contents/Info.plist + +$ rcodesign sign --exclude Contents/MacOS/macos-bin MyApp.app MyApp.app.NoMacOSBin +? 1 +signing MyApp.app to MyApp.app.NoMacOSBin +signing bundle at MyApp.app +signing bundle at MyApp.app into MyApp.app.NoMacOSBin +skipping signing of nested Mach-O binary because excluded by settings: Contents/MacOS/macos-bin +(an error will occur if this binary is not already signed) +(if you see an error, sign that Mach-O explicitly or remove it from the exclusion settings) +Error: binary does not have code signature data + +$ rcodesign sign MyApp.app/Contents/MacOS/macos-bin +signing MyApp.app/Contents/MacOS/macos-bin in place +signing MyApp.app/Contents/MacOS/macos-bin as a Mach-O binary +setting binary identifier to macos-bin +parsing Mach-O +writing Mach-O to MyApp.app/Contents/MacOS/macos-bin + +$ rcodesign sign --exclude Contents/MacOS/macos-bin MyApp.app MyApp.app.NoMacOSBin +signing MyApp.app to MyApp.app.NoMacOSBin +signing bundle at MyApp.app +signing bundle at MyApp.app into MyApp.app.NoMacOSBin +skipping signing of nested Mach-O binary because excluded by settings: Contents/MacOS/macos-bin +(an error will occur if this binary is not already signed) +(if you see an error, sign that Mach-O explicitly or remove it from the exclusion settings) +signing Mach-O file Contents/Resources/resource-bin +signing main executable Contents/MacOS/MyApp + +$ rcodesign debug-file-tree MyApp.app.NoMacOSBin +d MyApp.app.NoMacOSBin/ +d MyApp.app.NoMacOSBin/Contents +f 0a5902dc8e47f490d038 MyApp.app.NoMacOSBin/Contents/Info.plist +d MyApp.app.NoMacOSBin/Contents/MacOS +f 83e61e1e1c3407ff46d1 MyApp.app.NoMacOSBin/Contents/MacOS/MyApp +f 90dd49841af359158311 MyApp.app.NoMacOSBin/Contents/MacOS/macos-bin +d MyApp.app.NoMacOSBin/Contents/Resources +f 30b6fef6ae310318e47f MyApp.app.NoMacOSBin/Contents/Resources/resource-bin +d MyApp.app.NoMacOSBin/Contents/_CodeSignature +f 0acb1d044d421846ebc7 MyApp.app.NoMacOSBin/Contents/_CodeSignature/CodeResources + +$ rcodesign sign --exclude Contents/Resources/resource-bin MyApp.app MyApp.app.NoResourcesBin +signing MyApp.app to MyApp.app.NoResourcesBin +signing bundle at MyApp.app +signing bundle at MyApp.app into MyApp.app.NoResourcesBin +signing Mach-O file Contents/MacOS/macos-bin +signing main executable Contents/MacOS/MyApp + +$ rcodesign debug-file-tree MyApp.app.NoResourcesBin +d MyApp.app.NoResourcesBin/ +d MyApp.app.NoResourcesBin/Contents +f 0a5902dc8e47f490d038 MyApp.app.NoResourcesBin/Contents/Info.plist +d MyApp.app.NoResourcesBin/Contents/MacOS +f 322195d604cd3061f59d MyApp.app.NoResourcesBin/Contents/MacOS/MyApp +f 90dd49841af359158311 MyApp.app.NoResourcesBin/Contents/MacOS/macos-bin +d MyApp.app.NoResourcesBin/Contents/Resources +f 4cfaf70bc9fb6827fcf7 MyApp.app.NoResourcesBin/Contents/Resources/resource-bin +d MyApp.app.NoResourcesBin/Contents/_CodeSignature +f a1c3ba13551ece11eda7 MyApp.app.NoResourcesBin/Contents/_CodeSignature/CodeResources + +$ rm -rf MyApp.app + +``` + +Exclude a Mach-O in a nested bundle + +``` +$ rcodesign debug-create-macho MyApp.app/Contents/MacOS/MyApp +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/MacOS/MyApp + +$ rcodesign debug-create-info-plist --bundle-name MyApp MyApp.app/Contents/Info.plist +writing MyApp.app/Contents/Info.plist + +$ rcodesign debug-create-macho MyApp.app/Contents/MacOS/Extra.app/Contents/MacOS/Extra +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/MacOS/Extra.app/Contents/MacOS/Extra + +$ rcodesign debug-create-macho MyApp.app/Contents/MacOS/Extra.app/Contents/MacOS/extra-bin +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/MacOS/Extra.app/Contents/MacOS/extra-bin + +$ rcodesign sign MyApp.app/Contents/MacOS/Extra.app/Contents/MacOS/extra-bin +signing MyApp.app/Contents/MacOS/Extra.app/Contents/MacOS/extra-bin in place +signing MyApp.app/Contents/MacOS/Extra.app/Contents/MacOS/extra-bin as a Mach-O binary +setting binary identifier to extra-bin +parsing Mach-O +writing Mach-O to MyApp.app/Contents/MacOS/Extra.app/Contents/MacOS/extra-bin + +$ rcodesign debug-create-macho MyApp.app/Contents/MacOS/Extra.app/Contents/Resources/resource-bin +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/MacOS/Extra.app/Contents/Resources/resource-bin + +$ rcodesign debug-create-info-plist --bundle-name Extra MyApp.app/Contents/MacOS/Extra.app/Contents/Info.plist +writing MyApp.app/Contents/MacOS/Extra.app/Contents/Info.plist + +$ rcodesign sign --exclude Contents/MacOS/Extra.app/Contents/MacOS/extra-bin --exclude Contents/MacOS/Extra.app/Contents/Resources/resource-bin MyApp.app MyApp.app.NoExtraBin +signing MyApp.app to MyApp.app.NoExtraBin +signing bundle at MyApp.app +signing 1 nested bundles in the following order: +Contents/MacOS/Extra.app +entering nested bundle Contents/MacOS/Extra.app +signing bundle at MyApp.app/Contents/MacOS/Extra.app into MyApp.app.NoExtraBin/Contents/MacOS/Extra.app +skipping signing of nested Mach-O binary because excluded by settings: Contents/MacOS/extra-bin +(an error will occur if this binary is not already signed) +(if you see an error, sign that Mach-O explicitly or remove it from the exclusion settings) +signing main executable Contents/MacOS/Extra +leaving nested bundle Contents/MacOS/Extra.app +signing bundle at MyApp.app into MyApp.app.NoExtraBin +signing main executable Contents/MacOS/MyApp + +$ rcodesign debug-file-tree MyApp.app.NoExtraBin +d MyApp.app.NoExtraBin/ +d MyApp.app.NoExtraBin/Contents +f 0a5902dc8e47f490d038 MyApp.app.NoExtraBin/Contents/Info.plist +d MyApp.app.NoExtraBin/Contents/MacOS +d MyApp.app.NoExtraBin/Contents/MacOS/Extra.app +d MyApp.app.NoExtraBin/Contents/MacOS/Extra.app/Contents +f 63154cc4f75820c926eb MyApp.app.NoExtraBin/Contents/MacOS/Extra.app/Contents/Info.plist +d MyApp.app.NoExtraBin/Contents/MacOS/Extra.app/Contents/MacOS +f 0c02af539846df8f4e94 MyApp.app.NoExtraBin/Contents/MacOS/Extra.app/Contents/MacOS/Extra +f c83cc7362ebbacb94ac0 MyApp.app.NoExtraBin/Contents/MacOS/Extra.app/Contents/MacOS/extra-bin +d MyApp.app.NoExtraBin/Contents/MacOS/Extra.app/Contents/Resources +f 4cfaf70bc9fb6827fcf7 MyApp.app.NoExtraBin/Contents/MacOS/Extra.app/Contents/Resources/resource-bin +d MyApp.app.NoExtraBin/Contents/MacOS/Extra.app/Contents/_CodeSignature +f 596d62f4669d89f6bc8e MyApp.app.NoExtraBin/Contents/MacOS/Extra.app/Contents/_CodeSignature/CodeResources +f 1efc495c2cb290e5b2d3 MyApp.app.NoExtraBin/Contents/MacOS/MyApp +d MyApp.app.NoExtraBin/Contents/_CodeSignature +f c9a63c1dbccfd48e50b5 MyApp.app.NoExtraBin/Contents/_CodeSignature/CodeResources + +$ rm -rf MyApp.app +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-framework-shallow.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-framework-shallow.trycmd new file mode 100644 index 00000000..d906eb84 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-framework-shallow.trycmd @@ -0,0 +1,236 @@ +``` +$ rcodesign debug-create-info-plist --bundle-name Shallow.framework --package-type FMWK Shallow.framework/Resources/Info.plist +writing Shallow.framework/Resources/Info.plist + +$ rcodesign debug-create-macho --file-type dylib Shallow.framework/Shallow +assuming default minimum version 11.0.0 +writing Mach-O to Shallow.framework/Shallow + +$ touch Shallow.framework/Resources/root-00.txt + +$ rcodesign sign Shallow.framework Shallow.framework.signed +signing Shallow.framework to Shallow.framework.signed +signing bundle at Shallow.framework +signing bundle at Shallow.framework into Shallow.framework.signed +signing Mach-O file Shallow +bundle has no main executable to sign specially + +$ rcodesign debug-file-tree Shallow.framework.signed +d Shallow.framework.signed/ +d Shallow.framework.signed/Resources +f 4ccd32815c007b1014a9 Shallow.framework.signed/Resources/Info.plist +f e3b0c44298fc1c149afb Shallow.framework.signed/Resources/root-00.txt +f 6b0a00ccc659f8758965 Shallow.framework.signed/Shallow +d Shallow.framework.signed/_CodeSignature +f 7c9c39f0c67dd8f067b9 Shallow.framework.signed/_CodeSignature/CodeResources + +$ rcodesign print-signature-info Shallow.framework.signed +- path: Resources/Info.plist + file_size: 612 + file_sha256: 4ccd32815c007b1014a9e9a626cd9ebc5ada1f96b7535f2e2bdc6fb74534eefa + entity: other +- path: Resources/root-00.txt + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other +- path: Shallow + file_size: 22544 + file_sha256: 6b0a00ccc659f875896592dd0974113fcff7d5bf588ac9cc43a8598083fc6639 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16776 / 0x4188 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 392 / 0x188 + linkedit_bytes_after_signature: 5768 / 0x1688 + signature: + superblob_length: 376 / 0x178 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 320 + sha1: 65759643d79669d331bc4e5db3269e8d4767abd5 + sha256: 93fce90cf45628debc178cc1ef869e5f24b035c649ed7fdd1adaa7d9f6f11f5f + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: Shallow + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(0x0) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null +- path: _CodeSignature/CodeResources + file_size: 2881 + file_sha256: 7c9c39f0c67dd8f067b908c37b1c05fe22010f377ab8f441753586c4e269df8f + entity: + bundle_code_signature_file: !ResourcesXml + - + - + - + - + - ' files' + - ' ' + - ' Resources/Info.plist' + - ' ' + - ' LviF5BS72euY+yGkNlp3uTcDLCY=' + - ' ' + - ' Resources/root-00.txt' + - ' ' + - ' 2jmj7l5rSw0yVb/vlWAYkK/YBwk=' + - ' ' + - ' ' + - ' files2' + - ' ' + - ' Resources/Info.plist' + - ' ' + - ' hash2' + - ' ' + - ' TM0ygVwAexAUqemmJs2evFraH5a3U18uK9xvt0U07vo=' + - ' ' + - ' ' + - ' Resources/root-00.txt' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' ' + - ' Shallow' + - ' ' + - ' cdhash' + - ' ' + - ' k/zpDPRWKN68F4zB74aeXySwNcY=' + - ' ' + - ' requirement' + - ' cdhash H"93fce90cf45628debc178cc1ef869e5f24b035c6"' + - ' ' + - ' ' + - ' rules' + - ' ' + - ' ^Resources/' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^version.plist$' + - ' ' + - ' ' + - ' rules2' + - ' ' + - ' .*/.dSYM($|/)' + - ' ' + - ' weight' + - ' 11' + - ' ' + - ' ^(.*/)?/.DS_Store$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 2000' + - ' ' + - ' ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^.*' + - ' ' + - ' ^Info/.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^PkgInfo$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^[^/]+$' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^embedded/.provisionprofile$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^version/.plist$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ' + - + - + - '' + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-framework.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-framework.trycmd new file mode 100644 index 00000000..cb2d70f2 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-framework.trycmd @@ -0,0 +1,254 @@ +``` +$ rcodesign debug-create-info-plist --bundle-name MyFramework.framework --package-type FMWK MyFramework.framework/Versions/A/Resources/Info.plist +writing MyFramework.framework/Versions/A/Resources/Info.plist + +$ rcodesign debug-create-macho --file-type dylib MyFramework.framework/Versions/A/MyFramework +assuming default minimum version 11.0.0 +writing Mach-O to MyFramework.framework/Versions/A/MyFramework + +$ ln -s A MyFramework.framework/Versions/Current +$ ln -s Versions/Current/Resources MyFramework.framework/Resources + +$ touch MyFramework.framework/Versions/A/Resources/root-A-00.txt + +$ rcodesign sign MyFramework.framework MyFramework.framework.signed +signing MyFramework.framework to MyFramework.framework.signed +signing bundle at MyFramework.framework +signing 1 nested bundles in the following order: +Versions/A +entering nested bundle Versions/A +signing bundle at MyFramework.framework/Versions/A into MyFramework.framework.signed/Versions/A +signing Mach-O file MyFramework +bundle has no main executable to sign specially +leaving nested bundle Versions/A +signing bundle at MyFramework.framework into MyFramework.framework.signed + +$ rcodesign debug-file-tree MyFramework.framework.signed +d MyFramework.framework.signed/ +l MyFramework.framework.signed/Resources -> Versions/Current/Resources +d MyFramework.framework.signed/Versions +d MyFramework.framework.signed/Versions/A +f cec50992ab98ca143999 MyFramework.framework.signed/Versions/A/MyFramework +d MyFramework.framework.signed/Versions/A/Resources +f 419720e3c25babc998d6 MyFramework.framework.signed/Versions/A/Resources/Info.plist +f e3b0c44298fc1c149afb MyFramework.framework.signed/Versions/A/Resources/root-A-00.txt +d MyFramework.framework.signed/Versions/A/_CodeSignature +f 7421218291c85cdd725f MyFramework.framework.signed/Versions/A/_CodeSignature/CodeResources +l MyFramework.framework.signed/Versions/Current -> A + +$ rcodesign print-signature-info MyFramework.framework.signed +- path: Resources + symlink_target: Versions/Current/Resources + entity: other +- path: Versions/A/MyFramework + file_size: 22544 + file_sha256: cec50992ab98ca1439994b302fc085703bd7f847a341e1e4a734ecace09fe858 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16780 / 0x418c + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 396 / 0x18c + linkedit_bytes_after_signature: 5764 / 0x1684 + signature: + superblob_length: 380 / 0x17c + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 324 + sha1: 6ffd7576e87ac1a1172debbdb6a8f39629f44f3a + sha256: 8b50ba2db36d187bae720987f1ff7d6228b18e422b95a42ac4c4fb4acf3546a9 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: MyFramework + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(0x0) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null +- path: Versions/A/Resources/Info.plist + file_size: 624 + file_sha256: 419720e3c25babc998d6deb1013359b45ea44ddc0cf0cc9ff27e421d81c3a082 + entity: other +- path: Versions/A/Resources/root-A-00.txt + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other +- path: Versions/A/_CodeSignature/CodeResources + file_size: 2889 + file_sha256: 7421218291c85cdd725f0e47e8103cba94351691e4cd082bcae89e391293ca22 + entity: + bundle_code_signature_file: !ResourcesXml + - + - + - + - + - ' files' + - ' ' + - ' Resources/Info.plist' + - ' ' + - ' Le4jzlE49nXeP8DTH2bzrQDpQ28=' + - ' ' + - ' Resources/root-A-00.txt' + - ' ' + - ' 2jmj7l5rSw0yVb/vlWAYkK/YBwk=' + - ' ' + - ' ' + - ' files2' + - ' ' + - ' MyFramework' + - ' ' + - ' cdhash' + - ' ' + - ' i1C6LbNtGHuucgmH8f99YiixjkI=' + - ' ' + - ' requirement' + - ' cdhash H"8b50ba2db36d187bae720987f1ff7d6228b18e42"' + - ' ' + - ' Resources/Info.plist' + - ' ' + - ' hash2' + - ' ' + - ' QZcg48Jbq8mY1t6xATNZtF6kTdwM8Myf8n5CHYHDoII=' + - ' ' + - ' ' + - ' Resources/root-A-00.txt' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' ' + - ' ' + - ' rules' + - ' ' + - ' ^Resources/' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^version.plist$' + - ' ' + - ' ' + - ' rules2' + - ' ' + - ' .*/.dSYM($|/)' + - ' ' + - ' weight' + - ' 11' + - ' ' + - ' ^(.*/)?/.DS_Store$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 2000' + - ' ' + - ' ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^.*' + - ' ' + - ' ^Info/.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^PkgInfo$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^[^/]+$' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^embedded/.provisionprofile$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^version/.plist$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ' + - + - + - '' +- path: Versions/Current + symlink_target: A + entity: other + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-macho-universal.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-macho-universal.trycmd new file mode 100644 index 00000000..24a1f041 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-macho-universal.trycmd @@ -0,0 +1,438 @@ +Sign a bundle containing a multi-arch Mach-O binary + +``` +$ rcodesign debug-create-macho --architecture aarch64 exe.aarch64 +assuming default minimum version 11.0.0 +writing Mach-O to exe.aarch64 + +$ rcodesign debug-create-macho --architecture x86-64 --minimum-os-version 10.9.0 exe.x86-64 +writing Mach-O to exe.x86-64 + +$ rcodesign macho-universal-create -o MyApp.app/Contents/MacOS/MyApp exe.aarch64 exe.x86-64 +adding exe.aarch64 +adding exe.x86-64 +writing MyApp.app/Contents/MacOS/MyApp + +$ rcodesign macho-universal-create -o MyApp.app/Contents/MacOS/extra-bin exe.aarch64 exe.x86-64 +adding exe.aarch64 +adding exe.x86-64 +writing MyApp.app/Contents/MacOS/extra-bin + +$ rcodesign debug-create-info-plist --bundle-name MyApp MyApp.app/Contents/Info.plist +writing MyApp.app/Contents/Info.plist + +$ rcodesign sign MyApp.app MyApp.app.signed +signing MyApp.app to MyApp.app.signed +signing bundle at MyApp.app +signing bundle at MyApp.app into MyApp.app.signed +signing Mach-O file Contents/MacOS/extra-bin +signing main executable Contents/MacOS/MyApp + +$ rcodesign debug-file-tree MyApp.app.signed +d MyApp.app.signed/ +d MyApp.app.signed/Contents +f 0a5902dc8e47f490d038 MyApp.app.signed/Contents/Info.plist +d MyApp.app.signed/Contents/MacOS +f 3eb5869588cab4d817b5 MyApp.app.signed/Contents/MacOS/MyApp +f f6e7481573cf18d78a0e MyApp.app.signed/Contents/MacOS/extra-bin +d MyApp.app.signed/Contents/_CodeSignature +f 730159f6d521ed5ac796 MyApp.app.signed/Contents/_CodeSignature/CodeResources + +$ rcodesign print-signature-info MyApp.app.signed +- path: Contents/Info.plist + file_size: 576 + file_sha256: 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5 + entity: other +- path: Contents/MacOS/MyApp + file_size: 60432 + file_sha256: 3eb5869588cab4d817b5b2c1b79ae621b2be48356fbbe6c58777721587af047b + sub_path: macho-index:0 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 17098 / 0x42ca + macho_linkedit_end_offset: 23568 / 0x5c10 + macho_end_offset: 23568 / 0x5c10 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 714 / 0x2ca + linkedit_bytes_after_signature: 6470 / 0x1946 + signature: + superblob_length: 698 / 0x2ba + blob_count: 4 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 269 + sha1: 32ab361d982640eead191422e662fb8d96622fe3 + sha256: 723ba236a1c66db54ac3d1b051cb604a1ce428ce042c2c79b30b4aec4b6cf68c + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: 'CodeDirectory Alternate #0 (4096)' + magic: fade0c02 + length: 365 + sha1: 7e45ead6a3793876c2aac70ead9510fc6a206b31 + sha256: df50890b3494d4e93fba241a2adf1667ab452210cfbc99269800b14ac9a6db4f + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: com.example.mybundle + digest_type: sha1 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 65bf1c26bc63ccdfe6688cc787b06f88f3435ef0' + - 'RequirementSet (2): 3a75f6db058529148e14dd7ea1b4729cc09ec973' + - 'Resources (3): b7902213e12d68fd98ce5d8f8129a6805d3eb4da' + alternative_code_directories: + - - 'CodeDirectory Alternate #0 (4096)' + - version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: com.example.mybundle + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + - 'Resources (3): 730159f6d521ed5ac796783ae0077005b1dc406a348af63b6be3c349f59881b7' + cms: null +- path: Contents/MacOS/MyApp + file_size: 60432 + file_sha256: 3eb5869588cab4d817b5b2c1b79ae621b2be48356fbbe6c58777721587af047b + sub_path: macho-index:1 + entity: + mach_o: + macho_linkedit_start_offset: 4096 / 0x1000 + macho_signature_start_offset: 4112 / 0x1010 + macho_signature_end_offset: 4654 / 0x122e + macho_linkedit_end_offset: 11280 / 0x2c10 + macho_end_offset: 11280 / 0x2c10 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 558 / 0x22e + linkedit_bytes_after_signature: 6626 / 0x19e2 + signature: + superblob_length: 542 / 0x21e + blob_count: 4 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 209 + sha1: a80bc480c310d72c4125bf1cf54b5f492e451cca + sha256: 231dbee72992a30c9ead07159d14153ea3b48292ed449b38135aca0190747f54 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: 'CodeDirectory Alternate #0 (4096)' + magic: fade0c02 + length: 269 + sha1: 84c1812f00ed687db767796c66190a076ea7976e + sha256: 34b18e9cc482750023d52141da175ceae315215cf4a7ebc2122459a0a10fbc93 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: com.example.mybundle + digest_type: sha1 + platform: 0 + signed_entity_size: 4112 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 2 + slot_digests: + - 'Info (1): 65bf1c26bc63ccdfe6688cc787b06f88f3435ef0' + - 'RequirementSet (2): 3a75f6db058529148e14dd7ea1b4729cc09ec973' + - 'Resources (3): b7902213e12d68fd98ce5d8f8129a6805d3eb4da' + alternative_code_directories: + - - 'CodeDirectory Alternate #0 (4096)' + - version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: com.example.mybundle + digest_type: sha256 + platform: 0 + signed_entity_size: 4112 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 2 + slot_digests: + - 'Info (1): 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + - 'Resources (3): 730159f6d521ed5ac796783ae0077005b1dc406a348af63b6be3c349f59881b7' + cms: null +- path: Contents/MacOS/extra-bin + file_size: 60432 + file_sha256: f6e7481573cf18d78a0ed99022d118a465b39b688217179ea80347de77a107a2 + sub_path: macho-index:0 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 17024 / 0x4280 + macho_linkedit_end_offset: 23568 / 0x5c10 + macho_end_offset: 23568 / 0x5c10 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 640 / 0x280 + linkedit_bytes_after_signature: 6544 / 0x1990 + signature: + superblob_length: 624 / 0x270 + blob_count: 4 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 238 + sha1: 33d0d81907883efd5fdd04af1ad6325db9ca0973 + sha256: 402627fdbaeea6373a2e5b5b56784a6aca549b9c1f39a8b5e4bffdda78dfd170 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: 'CodeDirectory Alternate #0 (4096)' + magic: fade0c02 + length: 322 + sha1: af7a5c530e0ee18a6425da22361915baf5482bdd + sha256: 639f6dcf3661837c14d59b907bc607cec675bc7eefa2df8c6819d8492b6da455 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: extra-bin + digest_type: sha1 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000' + - 'RequirementSet (2): 3a75f6db058529148e14dd7ea1b4729cc09ec973' + alternative_code_directories: + - - 'CodeDirectory Alternate #0 (4096)' + - version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: extra-bin + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null +- path: Contents/MacOS/extra-bin + file_size: 60432 + file_sha256: f6e7481573cf18d78a0ed99022d118a465b39b688217179ea80347de77a107a2 + sub_path: macho-index:1 + entity: + mach_o: + macho_linkedit_start_offset: 4096 / 0x1000 + macho_signature_start_offset: 4112 / 0x1010 + macho_signature_end_offset: 4580 / 0x11e4 + macho_linkedit_end_offset: 11280 / 0x2c10 + macho_end_offset: 11280 / 0x2c10 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 484 / 0x1e4 + linkedit_bytes_after_signature: 6700 / 0x1a2c + signature: + superblob_length: 468 / 0x1d4 + blob_count: 4 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 178 + sha1: 081cb913e277d2314f0cf29f09ad50d6706ee05c + sha256: 503220ed37549b1acb847a37ec37c68b393466a5c3581d6bdf60b7202e733e44 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: 'CodeDirectory Alternate #0 (4096)' + magic: fade0c02 + length: 226 + sha1: 08f6f638476c7f9e3538bd455889beb8852dcb92 + sha256: 3e8d1a5abf40765a1d15ff00407a8822f153afa966f9283e813b7b456a7c5888 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: extra-bin + digest_type: sha1 + platform: 0 + signed_entity_size: 4112 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 2 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000' + - 'RequirementSet (2): 3a75f6db058529148e14dd7ea1b4729cc09ec973' + alternative_code_directories: + - - 'CodeDirectory Alternate #0 (4096)' + - version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: extra-bin + digest_type: sha256 + platform: 0 + signed_entity_size: 4112 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 2 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null +- path: Contents/_CodeSignature/CodeResources + file_size: 2601 + file_sha256: 730159f6d521ed5ac796783ae0077005b1dc406a348af63b6be3c349f59881b7 + entity: + bundle_code_signature_file: !ResourcesXml + - + - + - + - + - ' files' + - ' ' + - ' files2' + - ' ' + - ' MacOS/extra-bin' + - ' ' + - ' cdhash' + - ' ' + - ' Y59tzzZhg3wU1ZuQe8YHzsZ1vH4=' + - ' ' + - ' requirement' + - ' (((cdhash H"33d0d81907883efd5fdd04af1ad6325db9ca0973") or (cdhash H"639f6dcf3661837c14d59b907bc607cec675bc7e")) or (cdhash H"081cb913e277d2314f0cf29f09ad50d6706ee05c")) or (cdhash H"3e8d1a5abf40765a1d15ff00407a8822f153afa9")' + - ' ' + - ' ' + - ' rules' + - ' ' + - ' ^Resources/' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^version.plist$' + - ' ' + - ' ' + - ' rules2' + - ' ' + - ' .*/.dSYM($|/)' + - ' ' + - ' weight' + - ' 11' + - ' ' + - ' ^(.*/)?/.DS_Store$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 2000' + - ' ' + - ' ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^.*' + - ' ' + - ' ^Info/.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^PkgInfo$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^[^/]+$' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^embedded/.provisionprofile$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^version/.plist$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ' + - + - + - '' + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-multiple-macho.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-multiple-macho.trycmd new file mode 100644 index 00000000..4bf83a00 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-multiple-macho.trycmd @@ -0,0 +1,767 @@ +Sign a bundle containing multiple Mach-O binaries. + +``` +$ rcodesign debug-create-macho MyApp.app/Contents/MacOS/MyApp +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/MacOS/MyApp + +$ rcodesign debug-create-macho MyApp.app/Contents/MacOS/bin +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/MacOS/bin + +$ rcodesign debug-create-macho --file-type dylib MyApp.app/Contents/MacOS/lib.dylib +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/MacOS/lib.dylib + +$ rcodesign debug-create-macho MyApp.app/Contents/Resources/non-nested-bin +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/Resources/non-nested-bin + +$ rcodesign debug-create-info-plist --bundle-name MyApp MyApp.app/Contents/Info.plist +writing MyApp.app/Contents/Info.plist + +$ rcodesign debug-create-entitlements --get-task-allow entitlements.plist +writing entitlements.plist + +$ rcodesign sign --entitlements-xml-file entitlements.plist MyApp.app MyApp.app.signed +setting entitlements XML for main signing target from path entitlements.plist +signing MyApp.app to MyApp.app.signed +signing bundle at MyApp.app +signing bundle at MyApp.app into MyApp.app.signed +signing Mach-O file Contents/MacOS/bin +signing Mach-O file Contents/MacOS/lib.dylib +signing Mach-O file Contents/Resources/non-nested-bin +signing main executable Contents/MacOS/MyApp + +$ rcodesign debug-file-tree MyApp.app.signed +d MyApp.app.signed/ +d MyApp.app.signed/Contents +f 0a5902dc8e47f490d038 MyApp.app.signed/Contents/Info.plist +d MyApp.app.signed/Contents/MacOS +f ed9b322079f477b95626 MyApp.app.signed/Contents/MacOS/MyApp +f 222272e624fadf178495 MyApp.app.signed/Contents/MacOS/bin +f f5bf39926f898f9d8b10 MyApp.app.signed/Contents/MacOS/lib.dylib +d MyApp.app.signed/Contents/Resources +f 17ee48591c2b454766b3 MyApp.app.signed/Contents/Resources/non-nested-bin +d MyApp.app.signed/Contents/_CodeSignature +f e9faf2afbb4ab5548d35 MyApp.app.signed/Contents/_CodeSignature/CodeResources + +$ rcodesign print-signature-info MyApp.app.signed +- path: Contents/Info.plist + file_size: 576 + file_sha256: 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5 + entity: other +- path: Contents/MacOS/MyApp + file_size: 22544 + file_sha256: ed9b322079f477b956269151b7f05966fe4c2522116e2c23b5ab40f518887fe3 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 17232 / 0x4350 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 848 / 0x350 + linkedit_bytes_after_signature: 5312 / 0x14c0 + signature: + superblob_length: 832 / 0x340 + blob_count: 5 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 493 + sha1: f342b48c4632318b35759a678a90f606463d44aa + sha256: 776f6ff24cb7986e98b41600c6f34ad9ef197b9d28c84531ae2cbdf7b965ac36 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: Entitlements (5) + magic: fade7171 + length: 231 + sha1: 609a70a1468d84bef2be0146d1f0a5ea1c839948 + sha256: adea2675562421d85cc35e2c909ae27f33846eeb3b2b7c68017abd1b4b02f624 + - slot: DER Entitlements (7) + magic: fade7172 + length: 36 + sha1: 1018e52606e45993b16da1c621ceec945b9d5226 + sha256: 4d9925d24f1357a00429379f31f567cedfaa8101d58442e7864f923bfb708794 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: com.example.mybundle + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY | ALLOW_UNSIGNED) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + - 'Resources (3): e9faf2afbb4ab5548d3531c4b40abdfd59a2d6c2b5834e1980993957ba5bec83' + - 'Application (4): 0000000000000000000000000000000000000000000000000000000000000000' + - 'Entitlements (5): adea2675562421d85cc35e2c909ae27f33846eeb3b2b7c68017abd1b4b02f624' + - 'Rep Specific (6): 0000000000000000000000000000000000000000000000000000000000000000' + - 'DER Entitlements (7): 4d9925d24f1357a00429379f31f567cedfaa8101d58442e7864f923bfb708794' + entitlements_plist: + - + - + - + - + - ' get-task-allow' + - ' ' + - + - + entitlements_der_plist: + - + - + - ' ' + - ' get-task-allow' + - ' ' + - ' ' + - + cms: null +- path: Contents/MacOS/bin + file_size: 22544 + file_sha256: 222272e624fadf178495f7eeabdac248a951a0fb1e49002f494dde7067e456c8 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16772 / 0x4184 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 388 / 0x184 + linkedit_bytes_after_signature: 5772 / 0x168c + signature: + superblob_length: 372 / 0x174 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 316 + sha1: c86136679b8fb8b73c260c3f5143eb4787ba7408 + sha256: 319e12d5056d6b83506f2a51858ddfd99a244ed7b1bb261d9f7a1befa55239db + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: bin + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null +- path: Contents/MacOS/lib.dylib + file_size: 22544 + file_sha256: f5bf39926f898f9d8b10749c2c2e02d89e6ca1ab85e5210df86a711afc35f1bd + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16772 / 0x4184 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 388 / 0x184 + linkedit_bytes_after_signature: 5772 / 0x168c + signature: + superblob_length: 372 / 0x174 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 316 + sha1: af401622e3c8ad117ef8e8048542a0f6ce3e0d7c + sha256: df488d463c798ba6e7afbb55d1f86959aefc12753467b49d5a984611e11ec8d0 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: lib + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(0x0) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null +- path: Contents/Resources/non-nested-bin + file_size: 22544 + file_sha256: 17ee48591c2b454766b3d38e00ba5b342b3695c635c9114aad839117f45e3b38 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16783 / 0x418f + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 399 / 0x18f + linkedit_bytes_after_signature: 5761 / 0x1681 + signature: + superblob_length: 383 / 0x17f + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 327 + sha1: c26826707603fb84e28487b5f936799d4edf6377 + sha256: 7b46bdc9c357e9a5ce1b15cd255623667b42772b0f42db78c8b630740caecc86 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: non-nested-bin + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null +- path: Contents/_CodeSignature/CodeResources + file_size: 2882 + file_sha256: e9faf2afbb4ab5548d3531c4b40abdfd59a2d6c2b5834e1980993957ba5bec83 + entity: + bundle_code_signature_file: !ResourcesXml + - + - + - + - + - ' files' + - ' ' + - ' Resources/non-nested-bin' + - ' ' + - ' apwGEW+W2ghwpHtZD2rJ1FcX9d8=' + - ' ' + - ' ' + - ' files2' + - ' ' + - ' MacOS/bin' + - ' ' + - ' cdhash' + - ' ' + - ' MZ4S1QVta4NQbypRhY3f2ZokTtc=' + - ' ' + - ' requirement' + - ' cdhash H"319e12d5056d6b83506f2a51858ddfd99a244ed7"' + - ' ' + - ' MacOS/lib.dylib' + - ' ' + - ' cdhash' + - ' ' + - ' 30iNRjx5i6bnr7tV0fhpWa78EnU=' + - ' ' + - ' requirement' + - ' cdhash H"df488d463c798ba6e7afbb55d1f86959aefc1275"' + - ' ' + - ' Resources/non-nested-bin' + - ' ' + - ' hash2' + - ' ' + - ' F+5IWRwrRUdms9OOALpbNCs2lcY1yRFKrYORF/ReOzg=' + - ' ' + - ' ' + - ' ' + - ' rules' + - ' ' + - ' ^Resources/' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^version.plist$' + - ' ' + - ' ' + - ' rules2' + - ' ' + - ' .*/.dSYM($|/)' + - ' ' + - ' weight' + - ' 11' + - ' ' + - ' ^(.*/)?/.DS_Store$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 2000' + - ' ' + - ' ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^.*' + - ' ' + - ' ^Info/.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^PkgInfo$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^[^/]+$' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^embedded/.provisionprofile$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^version/.plist$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ' + - + - + - '' + +$ rcodesign sign --shallow --entitlements-xml-file entitlements.plist MyApp.app MyApp.app.signed-shallow +setting entitlements XML for main signing target from path entitlements.plist +signing MyApp.app to MyApp.app.signed-shallow +signing bundle at MyApp.app +signing bundle at MyApp.app into MyApp.app.signed-shallow +signing Mach-O file Contents/MacOS/bin +signing Mach-O file Contents/MacOS/lib.dylib +signing main executable Contents/MacOS/MyApp + +$ rcodesign print-signature-info MyApp.app.signed-shallow +- path: Contents/Info.plist + file_size: 576 + file_sha256: 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5 + entity: other +- path: Contents/MacOS/MyApp + file_size: 22544 + file_sha256: b7c94c6c236fbf011c51b6a98221e648470a8bdb1e79179c5bed2a4229d9f33f + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 17232 / 0x4350 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 848 / 0x350 + linkedit_bytes_after_signature: 5312 / 0x14c0 + signature: + superblob_length: 832 / 0x340 + blob_count: 5 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 493 + sha1: f5f97459503ca59e782bc7e09b0a0e84fbe2c469 + sha256: eceb671be47c4515ac795ee6b4369f2c3e1ce3c92cd3b31826e3317afe78c8ae + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: Entitlements (5) + magic: fade7171 + length: 231 + sha1: 609a70a1468d84bef2be0146d1f0a5ea1c839948 + sha256: adea2675562421d85cc35e2c909ae27f33846eeb3b2b7c68017abd1b4b02f624 + - slot: DER Entitlements (7) + magic: fade7172 + length: 36 + sha1: 1018e52606e45993b16da1c621ceec945b9d5226 + sha256: 4d9925d24f1357a00429379f31f567cedfaa8101d58442e7864f923bfb708794 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: com.example.mybundle + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY | ALLOW_UNSIGNED) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + - 'Resources (3): 29ca54092b8a1ee42bd378889eae9382dd64f94f6ac99e093d5aff76af6ea2bf' + - 'Application (4): 0000000000000000000000000000000000000000000000000000000000000000' + - 'Entitlements (5): adea2675562421d85cc35e2c909ae27f33846eeb3b2b7c68017abd1b4b02f624' + - 'Rep Specific (6): 0000000000000000000000000000000000000000000000000000000000000000' + - 'DER Entitlements (7): 4d9925d24f1357a00429379f31f567cedfaa8101d58442e7864f923bfb708794' + entitlements_plist: + - + - + - + - + - ' get-task-allow' + - ' ' + - + - + entitlements_der_plist: + - + - + - ' ' + - ' get-task-allow' + - ' ' + - ' ' + - + cms: null +- path: Contents/MacOS/bin + file_size: 22544 + file_sha256: 222272e624fadf178495f7eeabdac248a951a0fb1e49002f494dde7067e456c8 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16772 / 0x4184 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 388 / 0x184 + linkedit_bytes_after_signature: 5772 / 0x168c + signature: + superblob_length: 372 / 0x174 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 316 + sha1: c86136679b8fb8b73c260c3f5143eb4787ba7408 + sha256: 319e12d5056d6b83506f2a51858ddfd99a244ed7b1bb261d9f7a1befa55239db + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: bin + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null +- path: Contents/MacOS/lib.dylib + file_size: 22544 + file_sha256: f5bf39926f898f9d8b10749c2c2e02d89e6ca1ab85e5210df86a711afc35f1bd + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16772 / 0x4184 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 388 / 0x184 + linkedit_bytes_after_signature: 5772 / 0x168c + signature: + superblob_length: 372 / 0x174 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 316 + sha1: af401622e3c8ad117ef8e8048542a0f6ce3e0d7c + sha256: df488d463c798ba6e7afbb55d1f86959aefc12753467b49d5a984611e11ec8d0 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: lib + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(0x0) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null +- path: Contents/Resources/non-nested-bin + file_size: 16386 + file_sha256: 4cfaf70bc9fb6827fcf7751deaf65f8b54d46fecb6f39cb2ba8fbcf36912430c + entity: + mach_o: + macho_linkedit_start_offset: null + macho_signature_start_offset: null + macho_signature_end_offset: null + macho_linkedit_end_offset: null + macho_end_offset: 16386 / 0x4002 + linkedit_signature_start_offset: null + linkedit_signature_end_offset: null + linkedit_bytes_after_signature: null + signature: null +- path: Contents/_CodeSignature/CodeResources + file_size: 2882 + file_sha256: 29ca54092b8a1ee42bd378889eae9382dd64f94f6ac99e093d5aff76af6ea2bf + entity: + bundle_code_signature_file: !ResourcesXml + - + - + - + - + - ' files' + - ' ' + - ' Resources/non-nested-bin' + - ' ' + - ' apwGEW+W2ghwpHtZD2rJ1FcX9d8=' + - ' ' + - ' ' + - ' files2' + - ' ' + - ' MacOS/bin' + - ' ' + - ' cdhash' + - ' ' + - ' MZ4S1QVta4NQbypRhY3f2ZokTtc=' + - ' ' + - ' requirement' + - ' cdhash H"319e12d5056d6b83506f2a51858ddfd99a244ed7"' + - ' ' + - ' MacOS/lib.dylib' + - ' ' + - ' cdhash' + - ' ' + - ' 30iNRjx5i6bnr7tV0fhpWa78EnU=' + - ' ' + - ' requirement' + - ' cdhash H"df488d463c798ba6e7afbb55d1f86959aefc1275"' + - ' ' + - ' Resources/non-nested-bin' + - ' ' + - ' hash2' + - ' ' + - ' TPr3C8n7aCf893Ud6vZfi1TUb+y285yyuo+882kSQww=' + - ' ' + - ' ' + - ' ' + - ' rules' + - ' ' + - ' ^Resources/' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^version.plist$' + - ' ' + - ' ' + - ' rules2' + - ' ' + - ' .*/.dSYM($|/)' + - ' ' + - ' weight' + - ' 11' + - ' ' + - ' ^(.*/)?/.DS_Store$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 2000' + - ' ' + - ' ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^.*' + - ' ' + - ' ^Info/.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^PkgInfo$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^[^/]+$' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^embedded/.provisionprofile$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^version/.plist$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ' + - + - + - '' + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-nested-macho-identifier.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-nested-macho-identifier.trycmd new file mode 100644 index 00000000..61a847e8 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-nested-macho-identifier.trycmd @@ -0,0 +1,352 @@ +Binary identifiers in nested Mach-O within bundles are handled correctly. + +``` +$ rcodesign debug-create-macho MyApp.app/Contents/MacOS/MyApp +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/MacOS/MyApp + +$ rcodesign debug-create-info-plist --bundle-name MyApp MyApp.app/Contents/Info.plist +writing MyApp.app/Contents/Info.plist + +$ rcodesign debug-create-macho --architecture x86-64 exe.x86_64 +assuming default minimum version 11.0.0 +writing Mach-O to exe.x86_64 + +$ rcodesign sign --binary-identifier old-bin-x86_64 exe.x86_64 +signing exe.x86_64 in place +signing exe.x86_64 as a Mach-O binary +parsing Mach-O +writing Mach-O to exe.x86_64 + +$ rcodesign debug-create-macho --architecture aarch64 exe.aarch64 +assuming default minimum version 11.0.0 +writing Mach-O to exe.aarch64 + +$ rcodesign macho-universal-create -o old-bin-name exe.x86_64 exe.aarch64 +adding exe.x86_64 +adding exe.aarch64 +writing old-bin-name + +$ rcodesign sign old-bin-name +signing old-bin-name in place +signing old-bin-name as a Mach-O binary +setting binary identifier to old-bin-name +parsing Mach-O +writing Mach-O to old-bin-name + +$ mv old-bin-name MyApp.app/Contents/MacOS/new-bin + +$ rcodesign -v sign MyApp.app MyApp.app.signed +signing MyApp.app to MyApp.app.signed +signing bundle at MyApp.app +signing bundle at MyApp.app into MyApp.app.signed +collecting code resources files +copying file MyApp.app/Contents/Info.plist -> MyApp.app.signed/Contents/Info.plist +sealing nested Mach-O binary: Contents/MacOS/new-bin +signing Mach-O file Contents/MacOS/new-bin +setting binary identifier based on path: new-bin +inferring default signing settings from Mach-O binary +using binary identifier from settings +preserving code signature flags in existing Mach-O signature (CodeSignatureFlags(ADHOC)) +using binary identifier from settings +preserving code signature flags in existing Mach-O signature (CodeSignatureFlags(ADHOC)) +signing Mach-O binary at index 0 +binary targets macOS >= 11.0.0 with SDK 11.0.0 +adding code signature flags from signing settings: CodeSignatureFlags(ADHOC) +creating ad-hoc signature +code directory version: 132096 +total signature size: 280 bytes +signing Mach-O binary at index 1 +binary targets macOS >= 11.0.0 with SDK 11.0.0 +adding code signature flags from signing settings: CodeSignatureFlags(ADHOC) +creating ad-hoc signature +code directory version: 132096 +total signature size: 376 bytes +writing Mach-O to MyApp.app.signed/Contents/MacOS/new-bin +writing sealed resources to MyApp.app.signed/Contents/_CodeSignature/CodeResources +signing main executable Contents/MacOS/MyApp +setting main executable binary identifier to com.example.mybundle (derived from CFBundleIdentifier in Info.plist) +inferring default signing settings from Mach-O binary +signing Mach-O binary at index 0 +binary targets macOS >= 11.0.0 with SDK 11.0.0 +creating ad-hoc signature +code directory version: 132096 +total signature size: 421 bytes +writing signed main executable to MyApp.app.signed/Contents/MacOS/MyApp + +$ rcodesign print-signature-info MyApp.app.signed +- path: Contents/Info.plist + file_size: 576 + file_sha256: 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5 + entity: other +- path: Contents/MacOS/MyApp + file_size: 22544 + file_sha256: e1dfbe5e2a27918a25ccbe0971b0b40e96c8a1a031a332e8b9fb79475fe0345a + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16821 / 0x41b5 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 437 / 0x1b5 + linkedit_bytes_after_signature: 5723 / 0x165b + signature: + superblob_length: 421 / 0x1a5 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 365 + sha1: c826994bd20c58899a48dbca7e237bcc1940096b + sha256: ccbff6200513f074b4299064006b820d714a57ad77d06f44924e34c0a6bff910 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: com.example.mybundle + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + - 'Resources (3): c28145c843d03ba3ddb1c7e5a2029c1e179750bd1be9bfe9ebecb6e7f51922c5' + cms: null +- path: Contents/MacOS/new-bin + file_size: 55312 + file_sha256: 177b1b4ff578e3803cade0b792f7ca4537bf94c7ca6844ae584819c118683011 + sub_path: macho-index:0 + entity: + mach_o: + macho_linkedit_start_offset: 4096 / 0x1000 + macho_signature_start_offset: 4112 / 0x1010 + macho_signature_end_offset: 4392 / 0x1128 + macho_linkedit_end_offset: 10256 / 0x2810 + macho_end_offset: 10256 / 0x2810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 296 / 0x128 + linkedit_bytes_after_signature: 5864 / 0x16e8 + signature: + superblob_length: 280 / 0x118 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 224 + sha1: 95cb29468e76eefe3f75aa4a6847bdf4ca44cd30 + sha256: f677a5c4d4239ef741c96b66a5b1356d3d3d8630f4ca91593f2620f80224a549 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: new-bin + digest_type: sha256 + platform: 0 + signed_entity_size: 4112 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 2 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null +- path: Contents/MacOS/new-bin + file_size: 55312 + file_sha256: 177b1b4ff578e3803cade0b792f7ca4537bf94c7ca6844ae584819c118683011 + sub_path: macho-index:1 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16776 / 0x4188 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 392 / 0x188 + linkedit_bytes_after_signature: 5768 / 0x1688 + signature: + superblob_length: 376 / 0x178 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 320 + sha1: 6399ea612a352a77a5e69020d92ff0c3cafc89b5 + sha256: 7c122679cc9f0796e02496f20a9f428468c9fc3e74045530ed1a938745c8ee27 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: new-bin + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null +- path: Contents/_CodeSignature/CodeResources + file_size: 2483 + file_sha256: c28145c843d03ba3ddb1c7e5a2029c1e179750bd1be9bfe9ebecb6e7f51922c5 + entity: + bundle_code_signature_file: !ResourcesXml + - + - + - + - + - ' files' + - ' ' + - ' files2' + - ' ' + - ' MacOS/new-bin' + - ' ' + - ' cdhash' + - ' ' + - ' 9nelxNQjnvdByWtmpbE1bT09hjA=' + - ' ' + - ' requirement' + - ' (cdhash H"f677a5c4d4239ef741c96b66a5b1356d3d3d8630") or (cdhash H"7c122679cc9f0796e02496f20a9f428468c9fc3e")' + - ' ' + - ' ' + - ' rules' + - ' ' + - ' ^Resources/' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^version.plist$' + - ' ' + - ' ' + - ' rules2' + - ' ' + - ' .*/.dSYM($|/)' + - ' ' + - ' weight' + - ' 11' + - ' ' + - ' ^(.*/)?/.DS_Store$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 2000' + - ' ' + - ' ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^.*' + - ' ' + - ' ^Info/.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^PkgInfo$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^[^/]+$' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^embedded/.provisionprofile$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^version/.plist$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ' + - + - + - '' + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-nested-outside-nested-directory.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-nested-outside-nested-directory.trycmd new file mode 100644 index 00000000..47867ec0 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-nested-outside-nested-directory.trycmd @@ -0,0 +1,1332 @@ +A child bundle can exist outside a directory marked as "nested" in the +CodeResources rules set. In this case, the child bundle is treated like +a regular directory and not signed as a bundle. + +But when signing in non-shallow mode, we need to avoid signing Mach-O +binaries in the child bundle because they would have already been signed +as part of signing the child bundle. + +This test is inspired from #149. + +``` +$ rcodesign debug-create-macho MyApp.app/Contents/MacOS/MyApp +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/MacOS/MyApp + +$ rcodesign debug-create-info-plist --bundle-name MyApp MyApp.app/Contents/Info.plist +writing MyApp.app/Contents/Info.plist + +$ rcodesign debug-create-macho MyApp.app/Contents/lib/foo/exe +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/lib/foo/exe + +$ rcodesign debug-create-macho MyApp.app/Contents/lib/foo/child.app/Contents/MacOS/child +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/lib/foo/child.app/Contents/MacOS/child + +$ rcodesign debug-create-info-plist --bundle-name child MyApp.app/Contents/lib/foo/child.app/Contents/Info.plist +writing MyApp.app/Contents/lib/foo/child.app/Contents/Info.plist + +$ rcodesign debug-create-macho MyApp.app/Contents/lib/foo/child.app/Contents/MacOS/extra-exe +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/lib/foo/child.app/Contents/MacOS/extra-exe + +$ rcodesign sign MyApp.app MyApp.app.signed +signing MyApp.app to MyApp.app.signed +signing bundle at MyApp.app +signing 1 nested bundles in the following order: +Contents/lib/foo/child.app +entering nested bundle Contents/lib/foo/child.app +signing bundle at MyApp.app/Contents/lib/foo/child.app into MyApp.app.signed/Contents/lib/foo/child.app +signing Mach-O file Contents/MacOS/extra-exe +signing main executable Contents/MacOS/child +leaving nested bundle Contents/lib/foo/child.app +signing bundle at MyApp.app into MyApp.app.signed +signing Mach-O file Contents/lib/foo/exe +signing main executable Contents/MacOS/MyApp + +$ rcodesign debug-file-tree MyApp.app.signed +d MyApp.app.signed/ +d MyApp.app.signed/Contents +f 0a5902dc8e47f490d038 MyApp.app.signed/Contents/Info.plist +d MyApp.app.signed/Contents/MacOS +f c106af767c277207a6ad MyApp.app.signed/Contents/MacOS/MyApp +d MyApp.app.signed/Contents/_CodeSignature +f ade78fe52d723803b6cf MyApp.app.signed/Contents/_CodeSignature/CodeResources +d MyApp.app.signed/Contents/lib +d MyApp.app.signed/Contents/lib/foo +d MyApp.app.signed/Contents/lib/foo/child.app +d MyApp.app.signed/Contents/lib/foo/child.app/Contents +f 4c5130ae58d7184aa747 MyApp.app.signed/Contents/lib/foo/child.app/Contents/Info.plist +d MyApp.app.signed/Contents/lib/foo/child.app/Contents/MacOS +f 8d2bb481b183bd66403b MyApp.app.signed/Contents/lib/foo/child.app/Contents/MacOS/child +f d4637e70eed799f72544 MyApp.app.signed/Contents/lib/foo/child.app/Contents/MacOS/extra-exe +d MyApp.app.signed/Contents/lib/foo/child.app/Contents/_CodeSignature +f 164aae2ea1c61b76cd81 MyApp.app.signed/Contents/lib/foo/child.app/Contents/_CodeSignature/CodeResources +f 2adcd25a21eb14fc3f7b MyApp.app.signed/Contents/lib/foo/exe + +$ rcodesign print-signature-info MyApp.app.signed +- path: Contents/Info.plist + file_size: 576 + file_sha256: 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5 + entity: other +- path: Contents/MacOS/MyApp + file_size: 22544 + file_sha256: c106af767c277207a6adf0bac84ba1caa5b510fd11db5f3ec7dcc0830bed66b1 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16821 / 0x41b5 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 437 / 0x1b5 + linkedit_bytes_after_signature: 5723 / 0x165b + signature: + superblob_length: 421 / 0x1a5 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 365 + sha1: 807803de9fec161102ccaa46c27047bfff1d6af1 + sha256: 88b6f97438eb59baad281e3cb35e7621bead272852380b058fc73ea90c78353f + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: com.example.mybundle + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + - 'Resources (3): ade78fe52d723803b6cf8398715169db305938bbaaf08b9d185a64211bd27e6d' + cms: null +- path: Contents/_CodeSignature/CodeResources + file_size: 2824 + file_sha256: ade78fe52d723803b6cf8398715169db305938bbaaf08b9d185a64211bd27e6d + entity: + bundle_code_signature_file: !ResourcesXml + - + - + - + - + - ' files' + - ' ' + - ' files2' + - ' ' + - ' lib/foo/child.app/Contents/Info.plist' + - ' ' + - ' hash2' + - ' ' + - ' TFEwrljXGEqnR7LZD/HiHXVCvWvV3ZQjm+Lphz/9pZ4=' + - ' ' + - ' ' + - ' lib/foo/child.app/Contents/MacOS/child' + - ' ' + - ' hash2' + - ' ' + - ' jSu0gbGDvWZAO21MEqe6C7r9WFbXmTMk+kW/f203LT8=' + - ' ' + - ' ' + - ' lib/foo/child.app/Contents/MacOS/extra-exe' + - ' ' + - ' hash2' + - ' ' + - ' 1GN+cO7XmfclRNEqlCdbwEu89NOQkcykJmFIUAsxj6g=' + - ' ' + - ' ' + - ' lib/foo/exe' + - ' ' + - ' hash2' + - ' ' + - ' KtzSWiHrFPw/e1yk9UZbUV8hk53ZhD3lv32eP3rPqds=' + - ' ' + - ' ' + - ' ' + - ' rules' + - ' ' + - ' ^Resources/' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^version.plist$' + - ' ' + - ' ' + - ' rules2' + - ' ' + - ' .*/.dSYM($|/)' + - ' ' + - ' weight' + - ' 11' + - ' ' + - ' ^(.*/)?/.DS_Store$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 2000' + - ' ' + - ' ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^.*' + - ' ' + - ' ^Info/.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^PkgInfo$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^[^/]+$' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^embedded/.provisionprofile$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^version/.plist$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ' + - + - + - '' +- path: Contents/lib/foo/child.app/Contents/Info.plist + file_size: 576 + file_sha256: 4c5130ae58d7184aa747b2d90ff1e21d7542bd6bd5dd94239be2e9873ffda59e + entity: other +- path: Contents/lib/foo/child.app/Contents/MacOS/child + file_size: 22544 + file_sha256: 8d2bb481b183bd66403b6d4c12a7ba0bbafd5856d7993324fa45bf7f6d372d3f + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16821 / 0x41b5 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 437 / 0x1b5 + linkedit_bytes_after_signature: 5723 / 0x165b + signature: + superblob_length: 421 / 0x1a5 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 365 + sha1: da2d1d20b15073dc7c0ea2b9bdf0265b1c60b9d7 + sha256: 5df5b910c1b35258c22d107a13dccdfad7f747d4c1d6726d72b54c264de1bd40 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: com.example.mybundle + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 4c5130ae58d7184aa747b2d90ff1e21d7542bd6bd5dd94239be2e9873ffda59e' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + - 'Resources (3): 164aae2ea1c61b76cd8120aceaa7ab635c0a01514682e049bdde384d788fb677' + cms: null +- path: Contents/lib/foo/child.app/Contents/MacOS/extra-exe + file_size: 22544 + file_sha256: d4637e70eed799f72544d12a94275bc04bbcf4d39091cca4266148500b318fa8 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16778 / 0x418a + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 394 / 0x18a + linkedit_bytes_after_signature: 5766 / 0x1686 + signature: + superblob_length: 378 / 0x17a + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 322 + sha1: 99068332ebb6b9cc3d9c9534835a7f6c12c434ee + sha256: feb5492886d3537f86aead02ebd705a5a4eec2e656c28357a20fd814a34d8c06 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: extra-exe + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null +- path: Contents/lib/foo/child.app/Contents/_CodeSignature/CodeResources + file_size: 2427 + file_sha256: 164aae2ea1c61b76cd8120aceaa7ab635c0a01514682e049bdde384d788fb677 + entity: + bundle_code_signature_file: !ResourcesXml + - + - + - + - + - ' files' + - ' ' + - ' files2' + - ' ' + - ' MacOS/extra-exe' + - ' ' + - ' cdhash' + - ' ' + - ' /rVJKIbTU3+Grq0C69cFpaTuwuY=' + - ' ' + - ' requirement' + - ' cdhash H"feb5492886d3537f86aead02ebd705a5a4eec2e6"' + - ' ' + - ' ' + - ' rules' + - ' ' + - ' ^Resources/' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^version.plist$' + - ' ' + - ' ' + - ' rules2' + - ' ' + - ' .*/.dSYM($|/)' + - ' ' + - ' weight' + - ' 11' + - ' ' + - ' ^(.*/)?/.DS_Store$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 2000' + - ' ' + - ' ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^.*' + - ' ' + - ' ^Info/.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^PkgInfo$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^[^/]+$' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^embedded/.provisionprofile$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^version/.plist$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ' + - + - + - '' +- path: Contents/lib/foo/exe + file_size: 22544 + file_sha256: 2adcd25a21eb14fc3f7b5ca4f5465b515f21939dd9843de5bf7d9e3f7acfa9db + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16772 / 0x4184 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 388 / 0x184 + linkedit_bytes_after_signature: 5772 / 0x168c + signature: + superblob_length: 372 / 0x174 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 316 + sha1: 4ca6f9ee2bfe2bfac44ab4e9e9c1ef9b6e4fc0de + sha256: 23fc7207e52f23c0f6d2317dbb92cf9eff2aca8fe61ac241900d48be8f46cf5c + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: exe + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null + +$ rcodesign sign --shallow MyApp.app MyApp.app.signed-shallow +signing MyApp.app to MyApp.app.signed-shallow +signing bundle at MyApp.app +1 nested bundles will be copied instead of signed because shallow signing enabled: +Contents/lib/foo/child.app +entering nested bundle Contents/lib/foo/child.app +shallow signing enabled; bundle will be copied instead of signed +leaving nested bundle Contents/lib/foo/child.app +signing bundle at MyApp.app into MyApp.app.signed-shallow +signing main executable Contents/MacOS/MyApp + +$ rcodesign debug-file-tree MyApp.app.signed-shallow +d MyApp.app.signed-shallow/ +d MyApp.app.signed-shallow/Contents +f 0a5902dc8e47f490d038 MyApp.app.signed-shallow/Contents/Info.plist +d MyApp.app.signed-shallow/Contents/MacOS +f ec0736200e9c8dc64954 MyApp.app.signed-shallow/Contents/MacOS/MyApp +d MyApp.app.signed-shallow/Contents/_CodeSignature +f 90443605a8872c4b12b6 MyApp.app.signed-shallow/Contents/_CodeSignature/CodeResources +d MyApp.app.signed-shallow/Contents/lib +d MyApp.app.signed-shallow/Contents/lib/foo +d MyApp.app.signed-shallow/Contents/lib/foo/child.app +d MyApp.app.signed-shallow/Contents/lib/foo/child.app/Contents +f 4c5130ae58d7184aa747 MyApp.app.signed-shallow/Contents/lib/foo/child.app/Contents/Info.plist +d MyApp.app.signed-shallow/Contents/lib/foo/child.app/Contents/MacOS +f 4cfaf70bc9fb6827fcf7 MyApp.app.signed-shallow/Contents/lib/foo/child.app/Contents/MacOS/child +f 4cfaf70bc9fb6827fcf7 MyApp.app.signed-shallow/Contents/lib/foo/child.app/Contents/MacOS/extra-exe +f 4cfaf70bc9fb6827fcf7 MyApp.app.signed-shallow/Contents/lib/foo/exe + +$ rcodesign print-signature-info MyApp.app.signed-shallow +- path: Contents/Info.plist + file_size: 576 + file_sha256: 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5 + entity: other +- path: Contents/MacOS/MyApp + file_size: 22544 + file_sha256: ec0736200e9c8dc64954a17c80f6ed556e5bc4795120a24f2fae5490a6cdc1b4 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16821 / 0x41b5 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 437 / 0x1b5 + linkedit_bytes_after_signature: 5723 / 0x165b + signature: + superblob_length: 421 / 0x1a5 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 365 + sha1: 91fbe54adddc8de02772732e2eed09cde141c39c + sha256: c06b7ef1263aea0fd7b10cadb9f1557ef1d3117662b839499da4a3529d99fcd3 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: com.example.mybundle + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + - 'Resources (3): 90443605a8872c4b12b6b0a86cd50b2e5101b245e156ee3d3a8787686b77aa92' + cms: null +- path: Contents/_CodeSignature/CodeResources + file_size: 2824 + file_sha256: 90443605a8872c4b12b6b0a86cd50b2e5101b245e156ee3d3a8787686b77aa92 + entity: + bundle_code_signature_file: !ResourcesXml + - + - + - + - + - ' files' + - ' ' + - ' files2' + - ' ' + - ' lib/foo/child.app/Contents/Info.plist' + - ' ' + - ' hash2' + - ' ' + - ' TFEwrljXGEqnR7LZD/HiHXVCvWvV3ZQjm+Lphz/9pZ4=' + - ' ' + - ' ' + - ' lib/foo/child.app/Contents/MacOS/child' + - ' ' + - ' hash2' + - ' ' + - ' TPr3C8n7aCf893Ud6vZfi1TUb+y285yyuo+882kSQww=' + - ' ' + - ' ' + - ' lib/foo/child.app/Contents/MacOS/extra-exe' + - ' ' + - ' hash2' + - ' ' + - ' TPr3C8n7aCf893Ud6vZfi1TUb+y285yyuo+882kSQww=' + - ' ' + - ' ' + - ' lib/foo/exe' + - ' ' + - ' hash2' + - ' ' + - ' TPr3C8n7aCf893Ud6vZfi1TUb+y285yyuo+882kSQww=' + - ' ' + - ' ' + - ' ' + - ' rules' + - ' ' + - ' ^Resources/' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^version.plist$' + - ' ' + - ' ' + - ' rules2' + - ' ' + - ' .*/.dSYM($|/)' + - ' ' + - ' weight' + - ' 11' + - ' ' + - ' ^(.*/)?/.DS_Store$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 2000' + - ' ' + - ' ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^.*' + - ' ' + - ' ^Info/.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^PkgInfo$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^[^/]+$' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^embedded/.provisionprofile$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^version/.plist$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ' + - + - + - '' +- path: Contents/lib/foo/child.app/Contents/Info.plist + file_size: 576 + file_sha256: 4c5130ae58d7184aa747b2d90ff1e21d7542bd6bd5dd94239be2e9873ffda59e + entity: other +- path: Contents/lib/foo/child.app/Contents/MacOS/child + file_size: 16386 + file_sha256: 4cfaf70bc9fb6827fcf7751deaf65f8b54d46fecb6f39cb2ba8fbcf36912430c + entity: + mach_o: + macho_linkedit_start_offset: null + macho_signature_start_offset: null + macho_signature_end_offset: null + macho_linkedit_end_offset: null + macho_end_offset: 16386 / 0x4002 + linkedit_signature_start_offset: null + linkedit_signature_end_offset: null + linkedit_bytes_after_signature: null + signature: null +- path: Contents/lib/foo/child.app/Contents/MacOS/extra-exe + file_size: 16386 + file_sha256: 4cfaf70bc9fb6827fcf7751deaf65f8b54d46fecb6f39cb2ba8fbcf36912430c + entity: + mach_o: + macho_linkedit_start_offset: null + macho_signature_start_offset: null + macho_signature_end_offset: null + macho_linkedit_end_offset: null + macho_end_offset: 16386 / 0x4002 + linkedit_signature_start_offset: null + linkedit_signature_end_offset: null + linkedit_bytes_after_signature: null + signature: null +- path: Contents/lib/foo/exe + file_size: 16386 + file_sha256: 4cfaf70bc9fb6827fcf7751deaf65f8b54d46fecb6f39cb2ba8fbcf36912430c + entity: + mach_o: + macho_linkedit_start_offset: null + macho_signature_start_offset: null + macho_signature_end_offset: null + macho_linkedit_end_offset: null + macho_end_offset: 16386 / 0x4002 + linkedit_signature_start_offset: null + linkedit_signature_end_offset: null + linkedit_bytes_after_signature: null + signature: null + +$ rcodesign sign MyApp.app +signing MyApp.app in place +signing bundle at MyApp.app +signing 1 nested bundles in the following order: +Contents/lib/foo/child.app +entering nested bundle Contents/lib/foo/child.app +signing bundle at MyApp.app/Contents/lib/foo/child.app into MyApp.app/Contents/lib/foo/child.app +signing Mach-O file Contents/MacOS/extra-exe +signing main executable Contents/MacOS/child +leaving nested bundle Contents/lib/foo/child.app +signing bundle at MyApp.app into MyApp.app +signing Mach-O file Contents/lib/foo/exe +signing main executable Contents/MacOS/MyApp + +$ rcodesign debug-file-tree MyApp.app +d MyApp.app/ +d MyApp.app/Contents +f 0a5902dc8e47f490d038 MyApp.app/Contents/Info.plist +d MyApp.app/Contents/MacOS +f e07ea4755d1135f15295 MyApp.app/Contents/MacOS/MyApp +d MyApp.app/Contents/_CodeSignature +f 48a72e1777660495d9fc MyApp.app/Contents/_CodeSignature/CodeResources +d MyApp.app/Contents/lib +d MyApp.app/Contents/lib/foo +d MyApp.app/Contents/lib/foo/child.app +d MyApp.app/Contents/lib/foo/child.app/Contents +f 4c5130ae58d7184aa747 MyApp.app/Contents/lib/foo/child.app/Contents/Info.plist +d MyApp.app/Contents/lib/foo/child.app/Contents/MacOS +f 8d2bb481b183bd66403b MyApp.app/Contents/lib/foo/child.app/Contents/MacOS/child +f d4637e70eed799f72544 MyApp.app/Contents/lib/foo/child.app/Contents/MacOS/extra-exe +d MyApp.app/Contents/lib/foo/child.app/Contents/_CodeSignature +f 164aae2ea1c61b76cd81 MyApp.app/Contents/lib/foo/child.app/Contents/_CodeSignature/CodeResources +f 2adcd25a21eb14fc3f7b MyApp.app/Contents/lib/foo/exe + +$ rcodesign print-signature-info MyApp.app +- path: Contents/Info.plist + file_size: 576 + file_sha256: 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5 + entity: other +- path: Contents/MacOS/MyApp + file_size: 22544 + file_sha256: e07ea4755d1135f15295324ef4e2d21506ef74a7a95542f56cd659f46c3f886a + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16821 / 0x41b5 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 437 / 0x1b5 + linkedit_bytes_after_signature: 5723 / 0x165b + signature: + superblob_length: 421 / 0x1a5 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 365 + sha1: 90ae5487cb1d55a65877d973aae4f73ddc8c6c85 + sha256: 2345e491269e624fcc0c1f3aa515702988c5739b69eef97df48cfa7b260fc27b + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: com.example.mybundle + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + - 'Resources (3): 48a72e1777660495d9fce43e650c4f00ffe79977e30e4efeaee227b32eeef620' + cms: null +- path: Contents/_CodeSignature/CodeResources + file_size: 3001 + file_sha256: 48a72e1777660495d9fce43e650c4f00ffe79977e30e4efeaee227b32eeef620 + entity: + bundle_code_signature_file: !ResourcesXml + - + - + - + - + - ' files' + - ' ' + - ' files2' + - ' ' + - ' lib/foo/child.app/Contents/Info.plist' + - ' ' + - ' hash2' + - ' ' + - ' TFEwrljXGEqnR7LZD/HiHXVCvWvV3ZQjm+Lphz/9pZ4=' + - ' ' + - ' ' + - ' lib/foo/child.app/Contents/MacOS/child' + - ' ' + - ' hash2' + - ' ' + - ' jSu0gbGDvWZAO21MEqe6C7r9WFbXmTMk+kW/f203LT8=' + - ' ' + - ' ' + - ' lib/foo/child.app/Contents/MacOS/extra-exe' + - ' ' + - ' hash2' + - ' ' + - ' 1GN+cO7XmfclRNEqlCdbwEu89NOQkcykJmFIUAsxj6g=' + - ' ' + - ' ' + - ' lib/foo/child.app/Contents/_CodeSignature/CodeResources' + - ' ' + - ' hash2' + - ' ' + - ' FkquLqHGG3bNgSCs6qerY1wKAVFGguBJvd44TXiPtnc=' + - ' ' + - ' ' + - ' lib/foo/exe' + - ' ' + - ' hash2' + - ' ' + - ' KtzSWiHrFPw/e1yk9UZbUV8hk53ZhD3lv32eP3rPqds=' + - ' ' + - ' ' + - ' ' + - ' rules' + - ' ' + - ' ^Resources/' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^version.plist$' + - ' ' + - ' ' + - ' rules2' + - ' ' + - ' .*/.dSYM($|/)' + - ' ' + - ' weight' + - ' 11' + - ' ' + - ' ^(.*/)?/.DS_Store$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 2000' + - ' ' + - ' ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^.*' + - ' ' + - ' ^Info/.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^PkgInfo$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^[^/]+$' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^embedded/.provisionprofile$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^version/.plist$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ' + - + - + - '' +- path: Contents/lib/foo/child.app/Contents/Info.plist + file_size: 576 + file_sha256: 4c5130ae58d7184aa747b2d90ff1e21d7542bd6bd5dd94239be2e9873ffda59e + entity: other +- path: Contents/lib/foo/child.app/Contents/MacOS/child + file_size: 22544 + file_sha256: 8d2bb481b183bd66403b6d4c12a7ba0bbafd5856d7993324fa45bf7f6d372d3f + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16821 / 0x41b5 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 437 / 0x1b5 + linkedit_bytes_after_signature: 5723 / 0x165b + signature: + superblob_length: 421 / 0x1a5 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 365 + sha1: da2d1d20b15073dc7c0ea2b9bdf0265b1c60b9d7 + sha256: 5df5b910c1b35258c22d107a13dccdfad7f747d4c1d6726d72b54c264de1bd40 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: com.example.mybundle + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 4c5130ae58d7184aa747b2d90ff1e21d7542bd6bd5dd94239be2e9873ffda59e' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + - 'Resources (3): 164aae2ea1c61b76cd8120aceaa7ab635c0a01514682e049bdde384d788fb677' + cms: null +- path: Contents/lib/foo/child.app/Contents/MacOS/extra-exe + file_size: 22544 + file_sha256: d4637e70eed799f72544d12a94275bc04bbcf4d39091cca4266148500b318fa8 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16778 / 0x418a + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 394 / 0x18a + linkedit_bytes_after_signature: 5766 / 0x1686 + signature: + superblob_length: 378 / 0x17a + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 322 + sha1: 99068332ebb6b9cc3d9c9534835a7f6c12c434ee + sha256: feb5492886d3537f86aead02ebd705a5a4eec2e656c28357a20fd814a34d8c06 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: extra-exe + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null +- path: Contents/lib/foo/child.app/Contents/_CodeSignature/CodeResources + file_size: 2427 + file_sha256: 164aae2ea1c61b76cd8120aceaa7ab635c0a01514682e049bdde384d788fb677 + entity: + bundle_code_signature_file: !ResourcesXml + - + - + - + - + - ' files' + - ' ' + - ' files2' + - ' ' + - ' MacOS/extra-exe' + - ' ' + - ' cdhash' + - ' ' + - ' /rVJKIbTU3+Grq0C69cFpaTuwuY=' + - ' ' + - ' requirement' + - ' cdhash H"feb5492886d3537f86aead02ebd705a5a4eec2e6"' + - ' ' + - ' ' + - ' rules' + - ' ' + - ' ^Resources/' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^version.plist$' + - ' ' + - ' ' + - ' rules2' + - ' ' + - ' .*/.dSYM($|/)' + - ' ' + - ' weight' + - ' 11' + - ' ' + - ' ^(.*/)?/.DS_Store$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 2000' + - ' ' + - ' ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^.*' + - ' ' + - ' ^Info/.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^PkgInfo$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^[^/]+$' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^embedded/.provisionprofile$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^version/.plist$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ' + - + - + - '' +- path: Contents/lib/foo/exe + file_size: 22544 + file_sha256: 2adcd25a21eb14fc3f7b5ca4f5465b515f21939dd9843de5bf7d9e3f7acfa9db + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16772 / 0x4184 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 388 / 0x184 + linkedit_bytes_after_signature: 5772 / 0x168c + signature: + superblob_length: 372 / 0x174 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 316 + sha1: 4ca6f9ee2bfe2bfac44ab4e9e9c1ef9b6e4fc0de + sha256: 23fc7207e52f23c0f6d2317dbb92cf9eff2aca8fe61ac241900d48be8f46cf5c + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: exe + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-nested-symlinks.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-nested-symlinks.trycmd new file mode 100644 index 00000000..fced59ba --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-nested-symlinks.trycmd @@ -0,0 +1,282 @@ +Sign a bundle with symlinks in a nested directory + +``` +$ rcodesign debug-create-macho MyApp.app/Contents/MacOS/MyApp +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/MacOS/MyApp + +$ rcodesign debug-create-info-plist --bundle-name MyApp MyApp.app/Contents/Info.plist +writing MyApp.app/Contents/Info.plist + +$ rcodesign debug-create-macho --file-type dylib MyApp.app/Contents/Frameworks/libssh.4.8.8.dylib +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/Frameworks/libssh.4.8.8.dylib + +$ ln -s libssh.4.8.8.dylib MyApp.app/Contents/Frameworks/libssh.4.dylib +$ ln -s libssh.4.dylib MyApp.app/Contents/Frameworks/libssh.dylib + +$ rcodesign sign MyApp.app MyApp.app.signed +signing MyApp.app to MyApp.app.signed +signing bundle at MyApp.app +signing bundle at MyApp.app into MyApp.app.signed +signing Mach-O file Contents/Frameworks/libssh.4.8.8.dylib +signing main executable Contents/MacOS/MyApp + +$ rcodesign debug-file-tree MyApp.app.signed +d MyApp.app.signed/ +d MyApp.app.signed/Contents +d MyApp.app.signed/Contents/Frameworks +f 40a8b05ac0eac09b1186 MyApp.app.signed/Contents/Frameworks/libssh.4.8.8.dylib +l MyApp.app.signed/Contents/Frameworks/libssh.4.dylib -> libssh.4.8.8.dylib +l MyApp.app.signed/Contents/Frameworks/libssh.dylib -> libssh.4.dylib +f 0a5902dc8e47f490d038 MyApp.app.signed/Contents/Info.plist +d MyApp.app.signed/Contents/MacOS +f 8f330392b30c9cbd593d MyApp.app.signed/Contents/MacOS/MyApp +d MyApp.app.signed/Contents/_CodeSignature +f 273cd2b4970deda577c2 MyApp.app.signed/Contents/_CodeSignature/CodeResources + +$ rcodesign print-signature-info MyApp.app.signed +- path: Contents/Frameworks/libssh.4.8.8.dylib + file_size: 22544 + file_sha256: 40a8b05ac0eac09b118678fb3cadd62a364b5b40c0a04db953b9924d891fc3ef + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16777 / 0x4189 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 393 / 0x189 + linkedit_bytes_after_signature: 5767 / 0x1687 + signature: + superblob_length: 377 / 0x179 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 321 + sha1: 5b19ce6b4bcd9ad15044c2066216829d95ea7ab0 + sha256: 870a3508181d52e36783c1bf86010a76ad8f735165f2440f23a7c42119b4be9f + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: libssh.4 + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(0x0) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null +- path: Contents/Frameworks/libssh.4.dylib + symlink_target: libssh.4.8.8.dylib + entity: other +- path: Contents/Frameworks/libssh.dylib + symlink_target: libssh.4.dylib + entity: other +- path: Contents/Info.plist + file_size: 576 + file_sha256: 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5 + entity: other +- path: Contents/MacOS/MyApp + file_size: 22544 + file_sha256: 8f330392b30c9cbd593dc332979a2a2dfcc3edfe1b3bcfd1c5e2fecb31aeca00 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16821 / 0x41b5 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 437 / 0x1b5 + linkedit_bytes_after_signature: 5723 / 0x165b + signature: + superblob_length: 421 / 0x1a5 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 365 + sha1: 98f382966f6efe2b3158f862123c12c57dc167a9 + sha256: b7256b5560a1bc8c7f1e3f56d1eabc9ed248e4b97fee8e716efd5909681f95be + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: com.example.mybundle + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + - 'Resources (3): 273cd2b4970deda577c29fcd05055752e46144fe483371d48754d273bbccbb85' + cms: null +- path: Contents/_CodeSignature/CodeResources + file_size: 2673 + file_sha256: 273cd2b4970deda577c29fcd05055752e46144fe483371d48754d273bbccbb85 + entity: + bundle_code_signature_file: !ResourcesXml + - + - + - + - + - ' files' + - ' ' + - ' files2' + - ' ' + - ' Frameworks/libssh.4.8.8.dylib' + - ' ' + - ' cdhash' + - ' ' + - ' hwo1CBgdUuNng8G/hgEKdq2Pc1E=' + - ' ' + - ' requirement' + - ' cdhash H"870a3508181d52e36783c1bf86010a76ad8f7351"' + - ' ' + - ' Frameworks/libssh.4.dylib' + - ' ' + - ' symlink' + - ' libssh.4.8.8.dylib' + - ' ' + - ' Frameworks/libssh.dylib' + - ' ' + - ' symlink' + - ' libssh.4.dylib' + - ' ' + - ' ' + - ' rules' + - ' ' + - ' ^Resources/' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^version.plist$' + - ' ' + - ' ' + - ' rules2' + - ' ' + - ' .*/.dSYM($|/)' + - ' ' + - ' weight' + - ' 11' + - ' ' + - ' ^(.*/)?/.DS_Store$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 2000' + - ' ' + - ' ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^.*' + - ' ' + - ' ^Info/.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^PkgInfo$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^[^/]+$' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^embedded/.provisionprofile$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^version/.plist$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ' + - + - + - '' + +``` \ No newline at end of file diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-storybook-bundle.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-storybook-bundle.trycmd new file mode 100644 index 00000000..81d4d675 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-storybook-bundle.trycmd @@ -0,0 +1,259 @@ +When signing a shallow bundle with storybook "bundles," the storybook +bundles should not be signed. + +``` +$ rcodesign debug-create-macho MyApp.app/MyApp +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/MyApp + +$ rcodesign debug-create-info-plist --bundle-name MyApp MyApp.app/Info.plist +writing MyApp.app/Info.plist + +$ mkdir -p MyApp.app/Base.lproj/Main.storyboardc +$ touch MyApp.app/Base.lproj/Main.storyboardc/test.nib + +$ rcodesign debug-create-info-plist --empty --bundle-name ignored MyApp.app/Base.lproj/Main.storyboardc/Info.plist +writing MyApp.app/Base.lproj/Main.storyboardc/Info.plist + +$ cat MyApp.app/Base.lproj/Main.storyboardc/Info.plist + + + + + +$ touch MyApp.app/PkgInfo +$ touch MyApp.app/embedded.mobileprovision + +$ rcodesign sign MyApp.app MyApp.app.signed +signing MyApp.app to MyApp.app.signed +signing bundle at MyApp.app +signing bundle at MyApp.app into MyApp.app.signed +signing main executable MyApp + +$ rcodesign debug-file-tree MyApp.app.signed +d MyApp.app.signed/ +d MyApp.app.signed/Base.lproj +d MyApp.app.signed/Base.lproj/Main.storyboardc +f d0db6a79107b15f10e16 MyApp.app.signed/Base.lproj/Main.storyboardc/Info.plist +f e3b0c44298fc1c149afb MyApp.app.signed/Base.lproj/Main.storyboardc/test.nib +f 0a5902dc8e47f490d038 MyApp.app.signed/Info.plist +f 1d11f3a2cb072f0c9969 MyApp.app.signed/MyApp +f e3b0c44298fc1c149afb MyApp.app.signed/PkgInfo +d MyApp.app.signed/_CodeSignature +f dea493de64a991ac1fd6 MyApp.app.signed/_CodeSignature/CodeResources +f e3b0c44298fc1c149afb MyApp.app.signed/embedded.mobileprovision + +$ rcodesign print-signature-info MyApp.app.signed +- path: Base.lproj/Main.storyboardc/Info.plist + file_size: 180 + file_sha256: d0db6a79107b15f10e169d17bc2ef3395631f5932cd2552a7422e82f31e3f413 + entity: other +- path: Base.lproj/Main.storyboardc/test.nib + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other +- path: Info.plist + file_size: 576 + file_sha256: 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5 + entity: other +- path: MyApp + file_size: 22544 + file_sha256: 1d11f3a2cb072f0c996981756ccb080e9f35a0cce03c46220eb198c0c97406e2 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16821 / 0x41b5 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 437 / 0x1b5 + linkedit_bytes_after_signature: 5723 / 0x165b + signature: + superblob_length: 421 / 0x1a5 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 365 + sha1: ab5c39d99e4374adec9ff318431e0ad8f4e1a132 + sha256: 742baf721b17b851139efc4c6e8aba3089e100ab8b00c7a6db6379078a695d7f + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: com.example.mybundle + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + - 'Resources (3): dea493de64a991ac1fd6e9e9feab78408748cc96130c86bd0bb5989d86d9c39b' + cms: null +- path: PkgInfo + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other +- path: _CodeSignature/CodeResources + file_size: 2631 + file_sha256: dea493de64a991ac1fd6e9e9feab78408748cc96130c86bd0bb5989d86d9c39b + entity: + bundle_code_signature_file: !ResourcesXml + - + - + - + - + - ' files' + - ' ' + - ' Base.lproj/Main.storyboardc/Info.plist' + - ' ' + - ' n9hnu0WwQx9uSvF9Zek1KUmSMvY=' + - ' ' + - ' Base.lproj/Main.storyboardc/test.nib' + - ' ' + - ' 2jmj7l5rSw0yVb/vlWAYkK/YBwk=' + - ' ' + - ' Info.plist' + - ' ' + - ' Zb8cJrxjzN/maIzHh7BviPNDXvA=' + - ' ' + - ' PkgInfo' + - ' ' + - ' 2jmj7l5rSw0yVb/vlWAYkK/YBwk=' + - ' ' + - ' embedded.mobileprovision' + - ' ' + - ' 2jmj7l5rSw0yVb/vlWAYkK/YBwk=' + - ' ' + - ' ' + - ' files2' + - ' ' + - ' Base.lproj/Main.storyboardc/Info.plist' + - ' ' + - ' hash2' + - ' ' + - ' 0NtqeRB7FfEOFp0XvC7zOVYx9ZMs0lUqdCLoLzHj9BM=' + - ' ' + - ' ' + - ' Base.lproj/Main.storyboardc/test.nib' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' ' + - ' embedded.mobileprovision' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' ' + - ' ' + - ' rules' + - ' ' + - ' ^.*' + - ' ' + - ' ^.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^version.plist$' + - ' ' + - ' ' + - ' rules2' + - ' ' + - ' .*/.dSYM($|/)' + - ' ' + - ' weight' + - ' 11' + - ' ' + - ' ^(.*/)?/.DS_Store$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 2000' + - ' ' + - ' ^.*' + - ' ' + - ' ^.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^Info/.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^PkgInfo$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^embedded/.provisionprofile$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^version/.plist$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ' + - + - + - '' +- path: embedded.mobileprovision + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-symlink-overwrite.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-symlink-overwrite.trycmd new file mode 100644 index 00000000..8a2866c2 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-symlink-overwrite.trycmd @@ -0,0 +1,43 @@ +Sign a bundle with symlinks and verify symlink overwrites work + +``` +$ rcodesign debug-create-macho MyApp.app/Contents/MacOS/MyApp +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/MacOS/MyApp + +$ rcodesign debug-create-info-plist --bundle-name MyApp MyApp.app/Contents/Info.plist +writing MyApp.app/Contents/Info.plist + +$ mkdir -p MyApp.app/Contents/Resources +$ touch MyApp.app/Contents/Resources/file-00.txt +$ touch MyApp.app/Contents/Resources/file-01.txt +$ ln -s file-00.txt MyApp.app/Contents/Resources/file.txt + +$ rcodesign sign MyApp.app MyApp.app.signed +signing MyApp.app to MyApp.app.signed +signing bundle at MyApp.app +signing bundle at MyApp.app into MyApp.app.signed +signing main executable Contents/MacOS/MyApp + +$ ln -sf file-01.txt MyApp.app/Contents/Resources/file.txt + +$ rcodesign sign MyApp.app MyApp.app.signed +signing MyApp.app to MyApp.app.signed +signing bundle at MyApp.app +signing bundle at MyApp.app into MyApp.app.signed +signing main executable Contents/MacOS/MyApp + +$ rcodesign debug-file-tree MyApp.app.signed +d MyApp.app.signed/ +d MyApp.app.signed/Contents +f 0a5902dc8e47f490d038 MyApp.app.signed/Contents/Info.plist +d MyApp.app.signed/Contents/MacOS +f 92cc3ed9973cf49d2574 MyApp.app.signed/Contents/MacOS/MyApp +d MyApp.app.signed/Contents/Resources +f e3b0c44298fc1c149afb MyApp.app.signed/Contents/Resources/file-00.txt +f e3b0c44298fc1c149afb MyApp.app.signed/Contents/Resources/file-01.txt +l MyApp.app.signed/Contents/Resources/file.txt -> file-01.txt +d MyApp.app.signed/Contents/_CodeSignature +f bafb4e22a57de8763dc1 MyApp.app.signed/Contents/_CodeSignature/CodeResources + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-with-nested-framework.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-with-nested-framework.trycmd new file mode 100644 index 00000000..eaaf0623 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-with-nested-framework.trycmd @@ -0,0 +1,844 @@ +``` +$ rcodesign debug-create-macho MyApp.app/Contents/MacOS/MyApp +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/MacOS/MyApp + +$ rcodesign debug-create-info-plist --bundle-name MyApp MyApp.app/Contents/Info.plist +writing MyApp.app/Contents/Info.plist + +$ mkdir -p MyApp.app/Contents/Resources +$ touch MyApp.app/Contents/Resources/AppIcon.icns + +$ rcodesign debug-create-macho --file-type dylib MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Sparkle +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Sparkle + +$ rcodesign debug-create-info-plist --bundle-name Sparkle MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Info.plist +writing MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Info.plist + +$ mkdir -p MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Headers +$ mkdir -p MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Modules +$ mkdir -p MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/PrivateHeaders +$ mkdir -p MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/en.lproj + +$ touch MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Headers/Sparkle.h +$ touch MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Modules/module.modulemap +$ touch MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/DarkAqua.css +$ touch MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/en.lproj/Sparkle.strings + +$ rcodesign debug-create-macho MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/MacOS/Autoupdate +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/MacOS/Autoupdate + +$ rcodesign debug-create-info-plist --bundle-name Autoupdate MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Info.plist +writing MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Info.plist + +$ mkdir -p MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/en.lproj +$ touch MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/en.lproj/Sparkle.strings + +$ ln -s A MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/Current +$ ln -s Versions/Current/Headers MyApp.app/Contents/Frameworks/Sparkle.framework/Headers +$ ln -s Versions/Current/Modules MyApp.app/Contents/Frameworks/Sparkle.framework/Modules +$ ln -s Versions/Current/PrivateHeaders MyApp.app/Contents/Frameworks/Sparkle.framework/PrivateHeaders +$ ln -s Versions/Current/Resources MyApp.app/Contents/Frameworks/Sparkle.framework/Resources +$ ln -s Versions/Current/Sparkle MyApp.app/Contents/Frameworks/Sparkle.framework/Sparkle + +$ rcodesign sign MyApp.app MyApp.app.signed +signing MyApp.app to MyApp.app.signed +signing bundle at MyApp.app +signing 3 nested bundles in the following order: +Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app +Contents/Frameworks/Sparkle.framework/Versions/A +Contents/Frameworks/Sparkle.framework +entering nested bundle Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app +signing bundle at MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app into MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app +signing main executable Contents/MacOS/Autoupdate +leaving nested bundle Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app +entering nested bundle Contents/Frameworks/Sparkle.framework/Versions/A +signing bundle at MyApp.app/Contents/Frameworks/Sparkle.framework/Versions/A into MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A +signing main executable Sparkle +leaving nested bundle Contents/Frameworks/Sparkle.framework/Versions/A +entering nested bundle Contents/Frameworks/Sparkle.framework +signing bundle at MyApp.app/Contents/Frameworks/Sparkle.framework into MyApp.app.signed/Contents/Frameworks/Sparkle.framework +leaving nested bundle Contents/Frameworks/Sparkle.framework +signing bundle at MyApp.app into MyApp.app.signed +signing main executable Contents/MacOS/MyApp + +$ rcodesign debug-file-tree MyApp.app.signed +d MyApp.app.signed/ +d MyApp.app.signed/Contents +d MyApp.app.signed/Contents/Frameworks +d MyApp.app.signed/Contents/Frameworks/Sparkle.framework +l MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Headers -> Versions/Current/Headers +l MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Modules -> Versions/Current/Modules +l MyApp.app.signed/Contents/Frameworks/Sparkle.framework/PrivateHeaders -> Versions/Current/PrivateHeaders +l MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Resources -> Versions/Current/Resources +l MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Sparkle -> Versions/Current/Sparkle +d MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions +d MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A +d MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Headers +f e3b0c44298fc1c149afb MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Headers/Sparkle.h +d MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Modules +f e3b0c44298fc1c149afb MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Modules/module.modulemap +d MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Resources +d MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app +d MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents +f 41d88c15e923bda8c225 MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Info.plist +d MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/MacOS +f ccb6e50095f95b7ba447 MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/MacOS/Autoupdate +d MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources +d MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/en.lproj +f e3b0c44298fc1c149afb MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/en.lproj/Sparkle.strings +d MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/_CodeSignature +f 0740079f9cc964f82201 MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/_CodeSignature/CodeResources +f e3b0c44298fc1c149afb MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/DarkAqua.css +f fc10a69db39ae9732767 MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Info.plist +d MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/en.lproj +f e3b0c44298fc1c149afb MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/en.lproj/Sparkle.strings +f 9f41a07525f12e42afbe MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/Sparkle +d MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/_CodeSignature +f e8441b0a5cdcdcea855a MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/A/_CodeSignature/CodeResources +l MyApp.app.signed/Contents/Frameworks/Sparkle.framework/Versions/Current -> A +f 0a5902dc8e47f490d038 MyApp.app.signed/Contents/Info.plist +d MyApp.app.signed/Contents/MacOS +f 90d4920250fbddd56eb3 MyApp.app.signed/Contents/MacOS/MyApp +d MyApp.app.signed/Contents/Resources +f e3b0c44298fc1c149afb MyApp.app.signed/Contents/Resources/AppIcon.icns +d MyApp.app.signed/Contents/_CodeSignature +f 4f5c3a58e8cdc1d972ba MyApp.app.signed/Contents/_CodeSignature/CodeResources + +$ rcodesign print-signature-info MyApp.app.signed +- path: Contents/Frameworks/Sparkle.framework/Headers + symlink_target: Versions/Current/Headers + entity: other +- path: Contents/Frameworks/Sparkle.framework/Modules + symlink_target: Versions/Current/Modules + entity: other +- path: Contents/Frameworks/Sparkle.framework/PrivateHeaders + symlink_target: Versions/Current/PrivateHeaders + entity: other +- path: Contents/Frameworks/Sparkle.framework/Resources + symlink_target: Versions/Current/Resources + entity: other +- path: Contents/Frameworks/Sparkle.framework/Sparkle + symlink_target: Versions/Current/Sparkle + entity: other +- path: Contents/Frameworks/Sparkle.framework/Versions/A/Headers/Sparkle.h + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other +- path: Contents/Frameworks/Sparkle.framework/Versions/A/Modules/module.modulemap + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other +- path: Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Info.plist + file_size: 591 + file_sha256: 41d88c15e923bda8c2256d9ec934b3dd53ef43db06bf73cdb68fc25eff77b78e + entity: other +- path: Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/MacOS/Autoupdate + file_size: 22544 + file_sha256: ccb6e50095f95b7ba44724673fffd70258c3ef86ee4b42c5f7543f9185ef1efe + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16821 / 0x41b5 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 437 / 0x1b5 + linkedit_bytes_after_signature: 5723 / 0x165b + signature: + superblob_length: 421 / 0x1a5 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 365 + sha1: c9fa0ab0fd50466dbdd211d0dddeebc25db2b639 + sha256: a48bb6ceafa9f883b35e0a1acaaabb0a9052765f49eab1c5a4a1caeb03bb5b94 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: com.example.mybundle + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 41d88c15e923bda8c2256d9ec934b3dd53ef43db06bf73cdb68fc25eff77b78e' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + - 'Resources (3): 0740079f9cc964f8220145e3ef8c10590cfa3ae48707b8a649b833f5661f8887' + cms: null +- path: Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/en.lproj/Sparkle.strings + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other +- path: Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/_CodeSignature/CodeResources + file_size: 2579 + file_sha256: 0740079f9cc964f8220145e3ef8c10590cfa3ae48707b8a649b833f5661f8887 + entity: + bundle_code_signature_file: !ResourcesXml + - + - + - + - + - ' files' + - ' ' + - ' Resources/en.lproj/Sparkle.strings' + - ' ' + - ' hash' + - ' ' + - ' 2jmj7l5rSw0yVb/vlWAYkK/YBwk=' + - ' ' + - ' optional' + - ' ' + - ' ' + - ' ' + - ' files2' + - ' ' + - ' Resources/en.lproj/Sparkle.strings' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' optional' + - ' ' + - ' ' + - ' ' + - ' rules' + - ' ' + - ' ^Resources/' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^version.plist$' + - ' ' + - ' ' + - ' rules2' + - ' ' + - ' .*/.dSYM($|/)' + - ' ' + - ' weight' + - ' 11' + - ' ' + - ' ^(.*/)?/.DS_Store$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 2000' + - ' ' + - ' ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^.*' + - ' ' + - ' ^Info/.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^PkgInfo$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^[^/]+$' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^embedded/.provisionprofile$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^version/.plist$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ' + - + - + - '' +- path: Contents/Frameworks/Sparkle.framework/Versions/A/Resources/DarkAqua.css + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other +- path: Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Info.plist + file_size: 582 + file_sha256: fc10a69db39ae97327678df5b093982db866a9a478a77a84e8c47f6333170bdf + entity: other +- path: Contents/Frameworks/Sparkle.framework/Versions/A/Resources/en.lproj/Sparkle.strings + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other +- path: Contents/Frameworks/Sparkle.framework/Versions/A/Sparkle + file_size: 22544 + file_sha256: 9f41a07525f12e42afbe04eca9d3f157fe2ae2a5c774416872b6e3c0ad7459ea + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16821 / 0x41b5 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 437 / 0x1b5 + linkedit_bytes_after_signature: 5723 / 0x165b + signature: + superblob_length: 421 / 0x1a5 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 365 + sha1: 0c78ab52c9ca52cb69b6c81f2a64984909089c66 + sha256: 17c791e1d0abc8bb1231f600176aa39b31dfae66315908007e54997f01b426cb + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: com.example.mybundle + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(0x0) + code_digests_count: 5 + slot_digests: + - 'Info (1): fc10a69db39ae97327678df5b093982db866a9a478a77a84e8c47f6333170bdf' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + - 'Resources (3): e8441b0a5cdcdcea855a05a20135d82d60dc44246219e1a1b16d3c5b839534cf' + cms: null +- path: Contents/Frameworks/Sparkle.framework/Versions/A/_CodeSignature/CodeResources + file_size: 4311 + file_sha256: e8441b0a5cdcdcea855a05a20135d82d60dc44246219e1a1b16d3c5b839534cf + entity: + bundle_code_signature_file: !ResourcesXml + - + - + - + - + - ' files' + - ' ' + - ' Resources/Autoupdate.app/Contents/Info.plist' + - ' ' + - ' JfaPHt3iYsKzZnh6nfeTIpJWVqE=' + - ' ' + - ' Resources/Autoupdate.app/Contents/MacOS/Autoupdate' + - ' ' + - ' apwGEW+W2ghwpHtZD2rJ1FcX9d8=' + - ' ' + - ' Resources/Autoupdate.app/Contents/Resources/en.lproj/Sparkle.strings' + - ' ' + - ' hash' + - ' ' + - ' 2jmj7l5rSw0yVb/vlWAYkK/YBwk=' + - ' ' + - ' optional' + - ' ' + - ' ' + - ' Resources/DarkAqua.css' + - ' ' + - ' 2jmj7l5rSw0yVb/vlWAYkK/YBwk=' + - ' ' + - ' Resources/Info.plist' + - ' ' + - ' lv+5FkNAh6bulcE7JEu8aWkDqmI=' + - ' ' + - ' Resources/en.lproj/Sparkle.strings' + - ' ' + - ' hash' + - ' ' + - ' 2jmj7l5rSw0yVb/vlWAYkK/YBwk=' + - ' ' + - ' optional' + - ' ' + - ' ' + - ' ' + - ' files2' + - ' ' + - ' Headers/Sparkle.h' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' ' + - ' Modules/module.modulemap' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' ' + - ' Resources/Autoupdate.app/Contents/Info.plist' + - ' ' + - ' hash2' + - ' ' + - ' QdiMFekjvajCJW2eyTSz3VPvQ9sGv3PNto/CXv93t44=' + - ' ' + - ' ' + - ' Resources/Autoupdate.app/Contents/MacOS/Autoupdate' + - ' ' + - ' hash2' + - ' ' + - ' zLblAJX5W3ukRyRnP//XAljD74buS0LF91Q/kYXvHv4=' + - ' ' + - ' ' + - ' Resources/Autoupdate.app/Contents/Resources/en.lproj/Sparkle.strings' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' optional' + - ' ' + - ' ' + - ' Resources/DarkAqua.css' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' ' + - ' Resources/Info.plist' + - ' ' + - ' hash2' + - ' ' + - ' /BCmnbOa6XMnZ431sJOYLbhmqaR4p3qE6MR/YzMXC98=' + - ' ' + - ' ' + - ' Resources/en.lproj/Sparkle.strings' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' optional' + - ' ' + - ' ' + - ' ' + - ' rules' + - ' ' + - ' ^Resources/' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^version.plist$' + - ' ' + - ' ' + - ' rules2' + - ' ' + - ' .*/.dSYM($|/)' + - ' ' + - ' weight' + - ' 11' + - ' ' + - ' ^(.*/)?/.DS_Store$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 2000' + - ' ' + - ' ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^.*' + - ' ' + - ' ^Info/.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^PkgInfo$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^[^/]+$' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^embedded/.provisionprofile$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^version/.plist$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ' + - + - + - '' +- path: Contents/Frameworks/Sparkle.framework/Versions/Current + symlink_target: A + entity: other +- path: Contents/Info.plist + file_size: 576 + file_sha256: 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5 + entity: other +- path: Contents/MacOS/MyApp + file_size: 22544 + file_sha256: 90d4920250fbddd56eb3716aa3caf27de25466c2455d7234ee15821f7ce7f088 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16821 / 0x41b5 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 437 / 0x1b5 + linkedit_bytes_after_signature: 5723 / 0x165b + signature: + superblob_length: 421 / 0x1a5 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 365 + sha1: 2ffbdce9b026e11a2fbc88fbb20c096b701faa0c + sha256: 37579b3141daaa06faf53f95ed30d98c913734fd776455d15c96e28bea43b594 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: com.example.mybundle + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + - 'Resources (3): 4f5c3a58e8cdc1d972ba455220cf05e1e579afc3a157bd2a9c50bdfd31433873' + cms: null +- path: Contents/Resources/AppIcon.icns + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other +- path: Contents/_CodeSignature/CodeResources + file_size: 2678 + file_sha256: 4f5c3a58e8cdc1d972ba455220cf05e1e579afc3a157bd2a9c50bdfd31433873 + entity: + bundle_code_signature_file: !ResourcesXml + - + - + - + - + - ' files' + - ' ' + - ' Resources/AppIcon.icns' + - ' ' + - ' 2jmj7l5rSw0yVb/vlWAYkK/YBwk=' + - ' ' + - ' ' + - ' files2' + - ' ' + - ' Frameworks/Sparkle.framework' + - ' ' + - ' cdhash' + - ' ' + - ' F8eR4dCryLsSMfYAF2qjmzHfrmY=' + - ' ' + - ' requirement' + - ' cdhash H"17c791e1d0abc8bb1231f600176aa39b31dfae66"' + - ' ' + - ' Resources/AppIcon.icns' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' ' + - ' ' + - ' rules' + - ' ' + - ' ^Resources/' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^version.plist$' + - ' ' + - ' ' + - ' rules2' + - ' ' + - ' .*/.dSYM($|/)' + - ' ' + - ' weight' + - ' 11' + - ' ' + - ' ^(.*/)?/.DS_Store$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 2000' + - ' ' + - ' ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^.*' + - ' ' + - ' ^Info/.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^PkgInfo$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^[^/]+$' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^embedded/.provisionprofile$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^version/.plist$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ' + - + - + - '' + +$ rcodesign sign --shallow MyApp.app MyApp.app.signed-shallow +? 1 +signing MyApp.app to MyApp.app.signed-shallow +signing bundle at MyApp.app +3 nested bundles will be copied instead of signed because shallow signing enabled: +Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app +Contents/Frameworks/Sparkle.framework/Versions/A +Contents/Frameworks/Sparkle.framework +entering nested bundle Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app +shallow signing enabled; bundle will be copied instead of signed +leaving nested bundle Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app +entering nested bundle Contents/Frameworks/Sparkle.framework/Versions/A +shallow signing enabled; bundle will be copied instead of signed +leaving nested bundle Contents/Frameworks/Sparkle.framework/Versions/A +entering nested bundle Contents/Frameworks/Sparkle.framework +shallow signing enabled; bundle will be copied instead of signed +leaving nested bundle Contents/Frameworks/Sparkle.framework +signing bundle at MyApp.app into MyApp.app.signed-shallow +Error: binary does not have code signature data + +$ rcodesign sign --shallow MyApp.app.signed MyApp.app.signed-shallow +signing MyApp.app.signed to MyApp.app.signed-shallow +signing bundle at MyApp.app.signed +3 nested bundles will be copied instead of signed because shallow signing enabled: +Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app +Contents/Frameworks/Sparkle.framework/Versions/A +Contents/Frameworks/Sparkle.framework +entering nested bundle Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app +shallow signing enabled; bundle will be copied instead of signed +leaving nested bundle Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app +entering nested bundle Contents/Frameworks/Sparkle.framework/Versions/A +shallow signing enabled; bundle will be copied instead of signed +leaving nested bundle Contents/Frameworks/Sparkle.framework/Versions/A +entering nested bundle Contents/Frameworks/Sparkle.framework +shallow signing enabled; bundle will be copied instead of signed +leaving nested bundle Contents/Frameworks/Sparkle.framework +signing bundle at MyApp.app.signed into MyApp.app.signed-shallow +signing main executable Contents/MacOS/MyApp + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle.trycmd new file mode 100644 index 00000000..a2a4e7d8 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle.trycmd @@ -0,0 +1,495 @@ +Sign a simple application bundle + +``` +$ mkdir -p MyApp.app/Contents/MacOS +$ rcodesign debug-create-macho MyApp.app/Contents/MacOS/MyApp +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/MacOS/MyApp + +$ rcodesign sign MyApp.app MyApp.app.signed +? 1 +signing MyApp.app to MyApp.app.signed +signing bundle at MyApp.app +Error: error interfacing with directory-based bundle: Info.plist not found; not a valid bundle + +$ rcodesign debug-create-info-plist --bundle-name MyApp MyApp.app/Contents/Info.plist +writing MyApp.app/Contents/Info.plist + +$ mkdir -p MyApp.app/Resources +$ touch MyApp.app/Resources/file-00.txt +$ touch MyApp.app/Resources/file-01.txt + +$ rcodesign sign MyApp.app MyApp.app.signed +signing MyApp.app to MyApp.app.signed +signing bundle at MyApp.app +signing bundle at MyApp.app into MyApp.app.signed +signing main executable Contents/MacOS/MyApp + +$ rcodesign debug-file-tree MyApp.app.signed +d MyApp.app.signed/ +d MyApp.app.signed/Contents +f 0a5902dc8e47f490d038 MyApp.app.signed/Contents/Info.plist +d MyApp.app.signed/Contents/MacOS +f 0e2027a7c6d687972a35 MyApp.app.signed/Contents/MacOS/MyApp +d MyApp.app.signed/Contents/_CodeSignature +f c844b31db66807774bd8 MyApp.app.signed/Contents/_CodeSignature/CodeResources +d MyApp.app.signed/Resources +f e3b0c44298fc1c149afb MyApp.app.signed/Resources/file-00.txt +f e3b0c44298fc1c149afb MyApp.app.signed/Resources/file-01.txt + +$ rcodesign print-signature-info MyApp.app.signed +- path: Contents/Info.plist + file_size: 576 + file_sha256: 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5 + entity: other +- path: Contents/MacOS/MyApp + file_size: 22544 + file_sha256: 0e2027a7c6d687972a3526c512cc89e3acd5f5654a1e8a639862d6b72ed3d59d + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16821 / 0x41b5 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 437 / 0x1b5 + linkedit_bytes_after_signature: 5723 / 0x165b + signature: + superblob_length: 421 / 0x1a5 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 365 + sha1: ea937121b2d2b4be4dd8d37e1b884e7f1c2201af + sha256: 3dfec63df494ed0e2dfeedf5d13a70b46b957bd73830cd7644a12e0ce6f08c00 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: com.example.mybundle + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + - 'Resources (3): c844b31db66807774bd8ea00afe62cae1254bf6dfed2fa30d1204449d3c7e943' + cms: null +- path: Contents/_CodeSignature/CodeResources + file_size: 2672 + file_sha256: c844b31db66807774bd8ea00afe62cae1254bf6dfed2fa30d1204449d3c7e943 + entity: + bundle_code_signature_file: !ResourcesXml + - + - + - + - + - ' files' + - ' ' + - ' Resources/file-00.txt' + - ' ' + - ' 2jmj7l5rSw0yVb/vlWAYkK/YBwk=' + - ' ' + - ' Resources/file-01.txt' + - ' ' + - ' 2jmj7l5rSw0yVb/vlWAYkK/YBwk=' + - ' ' + - ' ' + - ' files2' + - ' ' + - ' Resources/file-00.txt' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' ' + - ' Resources/file-01.txt' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' ' + - ' ' + - ' rules' + - ' ' + - ' ^Resources/' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^version.plist$' + - ' ' + - ' ' + - ' rules2' + - ' ' + - ' .*/.dSYM($|/)' + - ' ' + - ' weight' + - ' 11' + - ' ' + - ' ^(.*/)?/.DS_Store$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 2000' + - ' ' + - ' ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^.*' + - ' ' + - ' ^Info/.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^PkgInfo$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^[^/]+$' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^embedded/.provisionprofile$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^version/.plist$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ' + - + - + - '' +- path: Resources/file-00.txt + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other +- path: Resources/file-01.txt + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other + +``` + +Signing a bundle with an executable without targeting activates SHA-1 digests + +``` +$ mkdir -p MyApp.app/Contents/MacOS +$ rcodesign debug-create-macho --no-targeting MyApp.app/Contents/MacOS/MyApp +writing Mach-O to MyApp.app/Contents/MacOS/MyApp + +$ rcodesign debug-create-info-plist --bundle-name MyApp MyApp.app/Contents/Info.plist +writing MyApp.app/Contents/Info.plist + +$ rcodesign sign MyApp.app MyApp.app.signed +signing MyApp.app to MyApp.app.signed +signing bundle at MyApp.app +signing bundle at MyApp.app into MyApp.app.signed +signing main executable Contents/MacOS/MyApp + +$ rcodesign print-signature-info MyApp.app.signed +- path: Contents/Info.plist + file_size: 576 + file_sha256: 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5 + entity: other +- path: Contents/MacOS/MyApp + file_size: 23568 + file_sha256: 0e1c406b4bd8ac2a94a79c325db69a2a19876c753971284c4c45468e047505f4 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 17098 / 0x42ca + macho_linkedit_end_offset: 23568 / 0x5c10 + macho_end_offset: 23568 / 0x5c10 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 714 / 0x2ca + linkedit_bytes_after_signature: 6470 / 0x1946 + signature: + superblob_length: 698 / 0x2ba + blob_count: 4 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 269 + sha1: daa7889f0fc39e6920ab2f468b80b06e04f714f5 + sha256: f9530a6c35ec6da7f21a047873953248668b59a63d3879754781c5ff5d8b5038 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: 'CodeDirectory Alternate #0 (4096)' + magic: fade0c02 + length: 365 + sha1: 78b08e2a2b243714a59975d7db86d0c77164a9f3 + sha256: 0210dbf647e161423f7ed74183dca566bc6e6e1b7a045079002b660385c5a26c + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: com.example.mybundle + digest_type: sha1 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 65bf1c26bc63ccdfe6688cc787b06f88f3435ef0' + - 'RequirementSet (2): 3a75f6db058529148e14dd7ea1b4729cc09ec973' + - 'Resources (3): bba07ca7abb366417d2b426b767c25838f5aeb58' + alternative_code_directories: + - - 'CodeDirectory Alternate #0 (4096)' + - version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: com.example.mybundle + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + - 'Resources (3): ba6147622a84edef406a5bc43b6ba041cef593326e34c5c53e662f9f57343263' + cms: null +- path: Contents/_CodeSignature/CodeResources + file_size: 2816 + file_sha256: ba6147622a84edef406a5bc43b6ba041cef593326e34c5c53e662f9f57343263 + entity: + bundle_code_signature_file: !ResourcesXml + - + - + - + - + - ' files' + - ' ' + - ' Resources/file-00.txt' + - ' ' + - ' 2jmj7l5rSw0yVb/vlWAYkK/YBwk=' + - ' ' + - ' Resources/file-01.txt' + - ' ' + - ' 2jmj7l5rSw0yVb/vlWAYkK/YBwk=' + - ' ' + - ' ' + - ' files2' + - ' ' + - ' Resources/file-00.txt' + - ' ' + - ' hash' + - ' ' + - ' 2jmj7l5rSw0yVb/vlWAYkK/YBwk=' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' ' + - ' Resources/file-01.txt' + - ' ' + - ' hash' + - ' ' + - ' 2jmj7l5rSw0yVb/vlWAYkK/YBwk=' + - ' ' + - ' hash2' + - ' ' + - ' 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - ' ' + - ' ' + - ' ' + - ' rules' + - ' ' + - ' ^Resources/' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^version.plist$' + - ' ' + - ' ' + - ' rules2' + - ' ' + - ' .*/.dSYM($|/)' + - ' ' + - ' weight' + - ' 11' + - ' ' + - ' ^(.*/)?/.DS_Store$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 2000' + - ' ' + - ' ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^.*' + - ' ' + - ' ^Info/.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^PkgInfo$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^Resources/.*/.lproj/' + - ' ' + - ' optional' + - ' ' + - ' weight' + - ' 1000' + - ' ' + - ' ^Resources/.*/.lproj/locversion.plist$' + - ' ' + - ' omit' + - ' ' + - ' weight' + - ' 1100' + - ' ' + - ' ^Resources/Base/.lproj/' + - ' ' + - ' weight' + - ' 1010' + - ' ' + - ' ^[^/]+$' + - ' ' + - ' nested' + - ' ' + - ' weight' + - ' 10' + - ' ' + - ' ^embedded/.provisionprofile$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ^version/.plist$' + - ' ' + - ' weight' + - ' 20' + - ' ' + - ' ' + - + - + - '' +- path: Resources/file-00.txt + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other +- path: Resources/file-01.txt + file_size: 0 + file_sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + entity: other + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-cms.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-cms.trycmd new file mode 100644 index 00000000..2f643410 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-cms.trycmd @@ -0,0 +1,815 @@ +``` +$ rcodesign debug-create-macho exe +assuming default minimum version 11.0.0 +writing Mach-O to exe + +$ rcodesign sign --pem-source src/testdata/self-signed-rsa-apple-development.pem --signing-time 2023-11-05T10:00:00Z --timestamp-url none exe exe.apple-development +reading PEM data from src/testdata/self-signed-rsa-apple-development.pem +registering signing key +signing exe to exe.apple-development +signing exe as a Mach-O binary +setting binary identifier to exe +parsing Mach-O +creating cryptographic signature with certificate Apple Development: RSA Apple Development (test) +writing Mach-O to exe.apple-development + +$ rcodesign extract cms-info exe.apple-development +signed content (embedded): None +signed content (external): Some("fade0c020000013c00020400000000000000009c000000580000000200000005000040102002000c")... (316 bytes) +signed content SHA-1: e1c19ec9ec8c13b3940f8385a8f5f9b56309330a +signed content SHA-256: fbd3393f2015c87653f2ccef69a864fb60985e21d38485b3bb1c7deeb76d825c +signed content SHA-384: 25a7989a6cb024c4d037cd56f0bcfcea0ba4b178362dc4d1694a2080e47d7c63bb178b5a85dfddc9bedf8764ac8d380a +signed content SHA-512: 16db5b4b1ea0529186a760790ce2d2325ea31f3d009eb74a182beb97ac26617e5495bf638d4b79746eee8f469aa991ba3a341af41b46d3b8d59079cf6d376950 +certificate count: 1 +certificate #0: subject CN=Apple Development: RSA Apple Development (test); self signed=true +signer count: 1 +signer #0: digest algorithm: Sha256 +signer #0: signature algorithm: RsaSha256 +signer #0: content type: 1.2.840.113549.1.7.1 +signer #0: message digest: fbd3393f2015c87653f2ccef69a864fb60985e21d38485b3bb1c7deeb76d825c +signer #0: signing time: Some(2023-11-05T10:00:00Z) +signer #0: signature content SHA-1: fc8004e694122f8e134b5e39380392165732f648 +signer #0: signature content SHA-256: c07c8319b33e4410d2f6b06ad5344aebbd7a848894f9c5004057cbbe8e17474d +signer #0: signature content SHA-384: 8b53dc40ee48b34eee4c4ba0c2d0fe411929ef4f80e9dbc167cf96c6ef4abd8d633affefffdca0ef6eadc0a055c31827 +signer #0: signature content SHA-512: f4cf4a8f0e4de256e07bbfedff1455d50ae05cdbd2d3fec3a12bbc055a1e77d7746b8f1b2bf55fc6380164fda04e1d12d3fd03085b5842f6fb7c5a00348eec84 +signer #0: signature valid: true +signer #0: time-stamp token present: false + +$ rcodesign extract cms exe.apple-development +SignedData { + digest_algorithms: { + Sha256, + }, + signed_content: None, + certificates: Some( + [ + CapturedX509Certificate { + original: Ber(308203f7308202dfa003020102020101300d06092a864886f70d01010b050030818c31143012060a0992268993f22c6401010c04746573743138303606035504030c2f4170706c6520446576656c6f706d656e743a20525341204170706c6520446576656c6f706d656e7420287465737429310d300b060355040b0c0474657374311e301c060355040a0c15525341204170706c6520446576656c6f706d656e74310b3009060355040613025553301e170d3233313130373130343932385a170d3337303731363130343932385a30818c31143012060a0992268993f22c6401010c04746573743138303606035504030c2f4170706c6520446576656c6f706d656e743a20525341204170706c6520446576656c6f706d656e7420287465737429310d300b060355040b0c0474657374311e301c060355040a0c15525341204170706c6520446576656c6f706d656e74310b300906035504061302555330820122300d06092a864886f70d01010105000382010f003082010a0282010100e6c9e2a15c321fbad0442e10bebb9824c6319fdaa7afd7c227bda4a81e152b9728855a375e34249f85f4127207a658bc091b447bb499f1f0884eecc98187630200f24c4d08e557624c33a4021ab5528581a92b902b4d436c3033cc27f67242f3eff171ed62f316fc0f138eb927e8d0568aecf11fc519487608509037e1382d81998c12f7b5913dd14bf9c26880bcc270c46b872023f83bdd1ba2395b34d6873ca5ff703dffbce5f561b55361a5a3bd01bfeda19f20b1a6bc89aadffb643d3e8faf2329609e11a587732f85405102de6a2cd76c2f95c95e22dd7af7ae9b8f238d2c60abad0595b3fa9c96b52e9ab95ef7af4596112de6fa5862aebc7aa24fe1fb0203010001a3623060300c0603551d130101ff0402300030160603551d250101ff040c300a06082b06010505070303300e0603551d0f0101ff0404030207803013060a2a864886f763640601020101ff040205003013060a2a864886f7636406010c0101ff04020500300d06092a864886f70d01010b0500038201010099563303c646c61728b9758f16c559e6e1be2eae0884c8535834062b81f21c89a94c89c0ffb25b93b886535749d81e94440f6d257438e07cc701a7e69aa8bdeca71c3d2f686357f842c62a27b045c671b487d89fd3e69458aae19d69274e7b2d54f7f736e25196738185cab05ccd71b4d8a180610e1f771cfeee0198047692ec87fd3a4dbac1db4ff8205ddd7445d6184b11e3ca7018d6a495fa4b44bc1325fdd68050b45dddadf3a9a9ea0575a5b6d7a9d636f052f3b3d79729bf475efc9c95db4154f14d2ae598cb0debd424e0f0bfe59f11668f1e80e52412ae3f722bab026439addcf9a07b81cd17dec724afa51128e092038203c137f602cb154397c05a), + inner: X509Certificate( + Certificate { + tbs_certificate: TbsCertificate { + version: Some( + V3, + ), + serial_number: Integer( + b"/x01", + ), + signature: AlgorithmIdentifier { + algorithm: 1.2.840.113549.1.1.11, + parameters: Some( + AlgorithmParameter( + [ 05 00 ], + ), + ), + }, + issuer: RdnSequence( + RdnSequence( + [ + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 0.9.2342.19200300.100.1.1, + value: 0c0474657374, + }, + ], + ), + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 2.5.4.3, + value: 0c2f4170706c6520446576656c6f706d656e743a20525341204170706c6520446576656c6f706d656e7420287465737429, + }, + ], + ), + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 2.5.4.11, + value: 0c0474657374, + }, + ], + ), + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 2.5.4.10, + value: 0c15525341204170706c6520446576656c6f706d656e74, + }, + ], + ), + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 2.5.4.6, + value: 13025553, + }, + ], + ), + ], + ), + ), + validity: Validity { + not_before: UtcTime( + UtcTime( + 2023-11-07T10:49:28Z, + ), + ), + not_after: UtcTime( + UtcTime( + 2037-07-16T10:49:28Z, + ), + ), + }, + subject: RdnSequence( + RdnSequence( + [ + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 0.9.2342.19200300.100.1.1, + value: 0c0474657374, + }, + ], + ), + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 2.5.4.3, + value: 0c2f4170706c6520446576656c6f706d656e743a20525341204170706c6520446576656c6f706d656e7420287465737429, + }, + ], + ), + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 2.5.4.11, + value: 0c0474657374, + }, + ], + ), + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 2.5.4.10, + value: 0c15525341204170706c6520446576656c6f706d656e74, + }, + ], + ), + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 2.5.4.6, + value: 13025553, + }, + ], + ), + ], + ), + ), + subject_public_key_info: SubjectPublicKeyInfo { + algorithm: AlgorithmIdentifier { + algorithm: 1.2.840.113549.1.1.1, + parameters: Some( + AlgorithmParameter( + [ 05 00 ], + ), + ), + }, + subject_public_key: 3082010a0282010100e6c9e2a15c321fbad0442e10bebb9824c6319fdaa7afd7c227bda4a81e152b9728855a375e34249f85f4127207a658bc091b447bb499f1f0884eecc98187630200f24c4d08e557624c33a4021ab5528581a92b902b4d436c3033cc27f67242f3eff171ed62f316fc0f138eb927e8d0568aecf11fc519487608509037e1382d81998c12f7b5913dd14bf9c26880bcc270c46b872023f83bdd1ba2395b34d6873ca5ff703dffbce5f561b55361a5a3bd01bfeda19f20b1a6bc89aadffb643d3e8faf2329609e11a587732f85405102de6a2cd76c2f95c95e22dd7af7ae9b8f238d2c60abad0595b3fa9c96b52e9ab95ef7af4596112de6fa5862aebc7aa24fe1fb0203010001 (unused 0), + }, + issuer_unique_id: None, + subject_unique_id: None, + extensions: Some( + Extensions( + [ + Extension { + id: 2.5.29.19, + critical: Some( + true, + ), + value: 3000, + }, + Extension { + id: 2.5.29.37, + critical: Some( + true, + ), + value: 300a06082b06010505070303, + }, + Extension { + id: 2.5.29.15, + critical: Some( + true, + ), + value: 03020780, + }, + Extension { + id: 1.2.840.113635.100.6.1.2, + critical: Some( + true, + ), + value: 0500, + }, + Extension { + id: 1.2.840.113635.100.6.1.12, + critical: Some( + true, + ), + value: 0500, + }, + ], + ), + ), + raw_data: Some("308202dfa003020102020101300d06092a864886f70d01010b050030818c31143012060a0992268993f22c6401010c04746573743138303606035504030c2f4170706c6520446576656c6f706d656e743a20525341204170706c6520446576656c6f706d656e7420287465737429310d300b060355040b0c0474657374311e301c060355040a0c15525341204170706c6520446576656c6f706d656e74310b3009060355040613025553301e170d3233313130373130343932385a170d3337303731363130343932385a30818c31143012060a0992268993f22c6401010c04746573743138303606035504030c2f4170706c6520446576656c6f706d656e743a20525341204170706c6520446576656c6f706d656e7420287465737429310d300b060355040b0c0474657374311e301c060355040a0c15525341204170706c6520446576656c6f706d656e74310b300906035504061302555330820122300d06092a864886f70d01010105000382010f003082010a0282010100e6c9e2a15c321fbad0442e10bebb9824c6319fdaa7afd7c227bda4a81e152b9728855a375e34249f85f4127207a658bc091b447bb499f1f0884eecc98187630200f24c4d08e557624c33a4021ab5528581a92b902b4d436c3033cc27f67242f3eff171ed62f316fc0f138eb927e8d0568aecf11fc519487608509037e1382d81998c12f7b5913dd14bf9c26880bcc270c46b872023f83bdd1ba2395b34d6873ca5ff703dffbce5f561b55361a5a3bd01bfeda19f20b1a6bc89aadffb643d3e8faf2329609e11a587732f85405102de6a2cd76c2f95c95e22dd7af7ae9b8f238d2c60abad0595b3fa9c96b52e9ab95ef7af4596112de6fa5862aebc7aa24fe1fb0203010001a3623060300c0603551d130101ff0402300030160603551d250101ff040c300a06082b06010505070303300e0603551d0f0101ff0404030207803013060a2a864886f763640601020101ff040205003013060a2a864886f7636406010c0101ff04020500"), + }, + signature_algorithm: AlgorithmIdentifier { + algorithm: 1.2.840.113549.1.1.11, + parameters: Some( + AlgorithmParameter( + [ 05 00 ], + ), + ), + }, + signature: 99563303c646c61728b9758f16c559e6e1be2eae0884c8535834062b81f21c89a94c89c0ffb25b93b886535749d81e94440f6d257438e07cc701a7e69aa8bdeca71c3d2f686357f842c62a27b045c671b487d89fd3e69458aae19d69274e7b2d54f7f736e25196738185cab05ccd71b4d8a180610e1f771cfeee0198047692ec87fd3a4dbac1db4ff8205ddd7445d6184b11e3ca7018d6a495fa4b44bc1325fdd68050b45dddadf3a9a9ea0575a5b6d7a9d636f052f3b3d79729bf475efc9c95db4154f14d2ae598cb0debd424e0f0bfe59f11668f1e80e52412ae3f722bab026439addcf9a07b81cd17dec724afa51128e092038203c137f602cb154397c05a (unused 0), + }, + ), + }, + ], + ), + signers: [ + SignerInfo { + issuer: RdnSequence( + RdnSequence( + [ + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 0.9.2342.19200300.100.1.1, + value: 0c0474657374, + }, + ], + ), + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 2.5.4.3, + value: 0c2f4170706c6520446576656c6f706d656e743a20525341204170706c6520446576656c6f706d656e7420287465737429, + }, + ], + ), + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 2.5.4.11, + value: 0c0474657374, + }, + ], + ), + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 2.5.4.10, + value: 0c15525341204170706c6520446576656c6f706d656e74, + }, + ], + ), + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 2.5.4.6, + value: 13025553, + }, + ], + ), + ], + ), + ), + serial_number: Integer( + b"/x01", + ), + digest_algorithm: Sha256, + signature_algorithm: RsaSha256, + signature: 427dab648570de5bb97d6660434a47057abd7fdbd2598419a96307c2e3b72f8dd3db12e4b540d3976ba30220c49a19ef82193d66b04cefdf5066ab49472b4c6b333cabf9c789167d04c25974b3ca2a3bf9d26a47cf575b209216fa3b6f7f849b026e168d248692db61f68ed974462423bcc69fe152b8db05c58b5b1dae0a6cde4c1f085c51ddba0621935ae4cc5fa764073a9681241dd03db9497844200749b31f1cb53345ab1c1626f4bc41c0171dd24178d14c66bc6392fc0cb1b7b8a27622af16bd52fdc54661939a07d49d0f9aebf833765fbaf4c2f8febc6741643ae4dc133ef35cf01eeb205b309d56ab240ae73ebf013ea80203b9c7dd613355c7585b, + signed_attributes: Some( + SignedAttributes { + content_type: 1.2.840.113549.1.7.1, + message_digest: fbd3393f2015c87653f2ccef69a864fb60985e21d38485b3bb1c7deeb76d825c, + signing_time: Some( + 2023-11-05T10:00:00Z, + ), + }, + ), + digested_signed_attributes_data: Some("318201d4301806092a864886f70d010903310b06092a864886f70d010701301c06092a864886f70d010905310f170d3233313130353130303030305a302f06092a864886f70d01090431220420fbd3393f2015c87653f2ccef69a864fb60985e21d38485b3bb1c7deeb76d825c303c06092a864886f763640902312f302d06096086480165030402010420fbd3393f2015c87653f2ccef69a864fb60985e21d38485b3bb1c7deeb76d825c3082012906092a864886f7636409013182011a048201163c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d38223f3e0a3c21444f435459504520706c697374205055424c494320222d2f2f4170706c652f2f44544420504c49535420312e302f2f454e222022687474703a2f2f7777772e6170706c652e636f6d2f445444732f50726f70657274794c6973742d312e302e647464223e0a3c706c6973742076657273696f6e3d22312e30223e0a3c646963743e0a093c6b65793e63646861736865733c2f6b65793e0a093c61727261793e0a09093c646174613e0a09092b394d355079415679485a5438737a766161686b2b3243595869453d0a09093c2f646174613e0a093c2f61727261793e0a3c2f646963743e0a3c2f706c6973743e0a"), + unsigned_attributes: None, + }, + ], +} + +$ rcodesign print-signature-info exe.apple-development +- path: exe.apple-development + file_size: 22544 + file_sha256: b79b1797e7e4da470e94c4b4881e1a04dab26e515cf3ecdc69e31cb16f48812d + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 18841 / 0x4999 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 2457 / 0x999 + linkedit_bytes_after_signature: 3703 / 0xe77 + signature: + superblob_length: 2441 / 0x989 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 316 + sha1: e1c19ec9ec8c13b3940f8385a8f5f9b56309330a + sha256: fbd3393f2015c87653f2ccef69a864fb60985e21d38485b3bb1c7deeb76d825c + - slot: RequirementSet (2) + magic: fade0c01 + length: 80 + sha1: 4f9d3e687a7622d7209180eeca44e6a4c97a2187 + sha256: f48f861e449222d508463e8342afee0c2241817878cab57b21e38e6aea0c08fa + - slot: CMS Signature (65536) + magic: fade0b01 + length: 2009 + sha1: faa96064b748df76d40c73c847cad6664772324c + sha256: f77bac63ecd33d9a152f4011d5cfefe695682e4dd15da5d89cdb9d8347350404 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(0x0) + identifier: exe + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): f48f861e449222d508463e8342afee0c2241817878cab57b21e38e6aea0c08fa' + code_requirements: + - 'designated(3): 0: (identifier "exe") and (certificate root = H"e1c7216e46533c923b7cfc94e86c7043790b96e9");' + cms: + certificates: + - subject: 'CN=Apple Development: RSA Apple Development (test), OU=test, O=RSA Apple Development, C=US' + issuer: 'CN=Apple Development: RSA Apple Development (test), OU=test, O=RSA Apple Development, C=US' + key_algorithm: RSA + signature_algorithm: SHA-256 with RSA encryption + signed_with_algorithm: SHA-256 with RSA encryption + is_apple_root_ca: false + is_apple_intermediate_ca: false + chains_to_apple_root_ca: false + apple_extended_key_usages: + - Code Signing + apple_code_signing_extensions: + - iPhone Developer + - Mac Developer + apple_certificate_profile: apple-development + apple_team_id: test + signers: + - issuer: 'CN=Apple Development: RSA Apple Development (test), OU=test, O=RSA Apple Development, C=US' + digest_algorithm: SHA-256 + signature_algorithm: SHA-256 with RSA encryption + attributes: + - 1.2.840.113549.1.9.3 + - 1.2.840.113549.1.9.4 + - 1.2.840.113549.1.9.5 + - 1.2.840.113635.100.9.1 + - 1.2.840.113635.100.9.2 + content_type: 1.2.840.113549.1.7.1 + message_digest: fbd3393f2015c87653f2ccef69a864fb60985e21d38485b3bb1c7deeb76d825c + signing_time: 2023-11-05T10:00:00Z + cdhash_plist: + - + - + - ' ' + - ' cdhashes' + - ' ' + - ' ' + - "/t/t+9M5PyAVyHZT8szvaahk+2CYXiE=" + - "/t/t" + - ' ' + - ' ' + - + cdhash_digests: + - - 2.16.840.1.101.3.4.2.1 + - fbd3393f2015c87653f2ccef69a864fb60985e21d38485b3bb1c7deeb76d825c + signature_verifies: true + +$ rcodesign sign --pem-source src/testdata/self-signed-rsa-apple-distribution.pem --signing-time 2023-11-05T10:00:00Z --timestamp-url none exe exe.apple-distribution +reading PEM data from src/testdata/self-signed-rsa-apple-distribution.pem +registering signing key +signing exe to exe.apple-distribution +signing exe as a Mach-O binary +setting binary identifier to exe +parsing Mach-O +creating cryptographic signature with certificate Apple Distribution: RSA Apple Distribution (test) +writing Mach-O to exe.apple-distribution + +$ rcodesign print-signature-info exe.apple-distribution +- path: exe.apple-distribution + file_size: 22544 + file_sha256: 81cbb13602e5aa13afd8ba7a2aa20429e13426043660916191318051644d6820 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 18850 / 0x49a2 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 2466 / 0x9a2 + linkedit_bytes_after_signature: 3694 / 0xe6e + signature: + superblob_length: 2450 / 0x992 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 316 + sha1: 34fce63175115d3946697622c3c9c258b2fdc82e + sha256: 691a7c16a12d28cca6045028c2b1b56d15cf4e1f679bc2c34ecdb57d346bc244 + - slot: RequirementSet (2) + magic: fade0c01 + length: 80 + sha1: b9d926a29a6b0a414a767d932df464b0ba4015bc + sha256: 93cc24502039c0f7c85f1165b021a7f703793b8d07ce1b71cbb3435c7e4c5d93 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 2018 + sha1: d91ee60ca34d3de9169c7cac3fb4f0dbbfbe1d1e + sha256: 3164953bd2408b70af58b988d4b619f6f68d502d668d4c5787a03a5155f6ac59 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(0x0) + identifier: exe + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 93cc24502039c0f7c85f1165b021a7f703793b8d07ce1b71cbb3435c7e4c5d93' + code_requirements: + - 'designated(3): 0: (identifier "exe") and (certificate root = H"0383efdf909250708bf2de4d43753836ccb3d608");' + cms: + certificates: + - subject: 'CN=Apple Distribution: RSA Apple Distribution (test), OU=test, O=RSA Apple Distribution, C=US' + issuer: 'CN=Apple Distribution: RSA Apple Distribution (test), OU=test, O=RSA Apple Distribution, C=US' + key_algorithm: RSA + signature_algorithm: SHA-256 with RSA encryption + signed_with_algorithm: SHA-256 with RSA encryption + is_apple_root_ca: false + is_apple_intermediate_ca: false + chains_to_apple_root_ca: false + apple_extended_key_usages: + - Code Signing + apple_code_signing_extensions: + - Apple Mac App Signing (Development) + - Apple Developer Certificate (Submission) + apple_certificate_profile: apple-distribution + apple_team_id: test + signers: + - issuer: 'CN=Apple Distribution: RSA Apple Distribution (test), OU=test, O=RSA Apple Distribution, C=US' + digest_algorithm: SHA-256 + signature_algorithm: SHA-256 with RSA encryption + attributes: + - 1.2.840.113549.1.9.3 + - 1.2.840.113549.1.9.4 + - 1.2.840.113549.1.9.5 + - 1.2.840.113635.100.9.1 + - 1.2.840.113635.100.9.2 + content_type: 1.2.840.113549.1.7.1 + message_digest: 691a7c16a12d28cca6045028c2b1b56d15cf4e1f679bc2c34ecdb57d346bc244 + signing_time: 2023-11-05T10:00:00Z + cdhash_plist: + - + - + - ' ' + - ' cdhashes' + - ' ' + - ' ' + - "/t/taRp8FqEtKMymBFAowrG1bRXPTh8=" + - "/t/t" + - ' ' + - ' ' + - + cdhash_digests: + - - 2.16.840.1.101.3.4.2.1 + - 691a7c16a12d28cca6045028c2b1b56d15cf4e1f679bc2c34ecdb57d346bc244 + signature_verifies: true + +$ rcodesign sign --pem-source src/testdata/self-signed-rsa-developer-id-application.pem --signing-time 2023-11-05T10:00:00Z --timestamp-url none exe exe.developer-id-application +reading PEM data from src/testdata/self-signed-rsa-developer-id-application.pem +registering signing key +signing exe to exe.developer-id-application +signing exe as a Mach-O binary +setting binary identifier to exe +parsing Mach-O +creating cryptographic signature with certificate Developer ID Application: RSA Developer ID Application (test) +writing Mach-O to exe.developer-id-application + +$ rcodesign print-signature-info exe.developer-id-application +- path: exe.developer-id-application + file_size: 22544 + file_sha256: 864799f8d45af41c80c3b93be270f559ece79f88e9aa944f4356eefe2532203a + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 18883 / 0x49c3 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 2499 / 0x9c3 + linkedit_bytes_after_signature: 3661 / 0xe4d + signature: + superblob_length: 2483 / 0x9b3 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 316 + sha1: e32c3d89be4bd79a707a6028081ad309d230cd6e + sha256: 5dd2eefc1b66bc80fc4b3f278aa620a5493f048a68627ca4d8f90e2d2f3841d7 + - slot: RequirementSet (2) + magic: fade0c01 + length: 80 + sha1: 6eb37ab943110f6b496aa327c4949c8348ba5456 + sha256: 0c0961788fa02751edb6b397dd4f130c78edd4380d0a0ddc397cb69fd1e38efa + - slot: CMS Signature (65536) + magic: fade0b01 + length: 2051 + sha1: 6a6c3b85d34353dcd7d77ad0188611beebf1380b + sha256: ce9fe441659bdb5e3f65d1fba0e81543445b0c7dc47237e300791b3ddbe31fea + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(0x0) + identifier: exe + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 0c0961788fa02751edb6b397dd4f130c78edd4380d0a0ddc397cb69fd1e38efa' + code_requirements: + - 'designated(3): 0: (identifier "exe") and (certificate root = H"3acf1d302fe3a4bba06a3c16aadc908045bc9162");' + cms: + certificates: + - subject: 'CN=Developer ID Application: RSA Developer ID Application (test), OU=test, O=RSA Developer ID Application, C=US' + issuer: 'CN=Developer ID Application: RSA Developer ID Application (test), OU=test, O=RSA Developer ID Application, C=US' + key_algorithm: RSA + signature_algorithm: SHA-256 with RSA encryption + signed_with_algorithm: SHA-256 with RSA encryption + is_apple_root_ca: false + is_apple_intermediate_ca: false + chains_to_apple_root_ca: false + apple_extended_key_usages: + - Code Signing + apple_code_signing_extensions: + - Developer ID Application + apple_certificate_profile: developer-id-application + apple_team_id: test + signers: + - issuer: 'CN=Developer ID Application: RSA Developer ID Application (test), OU=test, O=RSA Developer ID Application, C=US' + digest_algorithm: SHA-256 + signature_algorithm: SHA-256 with RSA encryption + attributes: + - 1.2.840.113549.1.9.3 + - 1.2.840.113549.1.9.4 + - 1.2.840.113549.1.9.5 + - 1.2.840.113635.100.9.1 + - 1.2.840.113635.100.9.2 + content_type: 1.2.840.113549.1.7.1 + message_digest: 5dd2eefc1b66bc80fc4b3f278aa620a5493f048a68627ca4d8f90e2d2f3841d7 + signing_time: 2023-11-05T10:00:00Z + cdhash_plist: + - + - + - ' ' + - ' cdhashes' + - ' ' + - ' ' + - "/t/tXdLu/BtmvID8Sz8niqYgpUk/BIo=" + - "/t/t" + - ' ' + - ' ' + - + cdhash_digests: + - - 2.16.840.1.101.3.4.2.1 + - 5dd2eefc1b66bc80fc4b3f278aa620a5493f048a68627ca4d8f90e2d2f3841d7 + signature_verifies: true + +$ rcodesign sign --pem-source src/testdata/self-signed-rsa-developer-id-installer.pem --signing-time 2023-11-05T10:00:00Z --timestamp-url none exe exe.developer-id-installer +reading PEM data from src/testdata/self-signed-rsa-developer-id-installer.pem +registering signing key +signing exe to exe.developer-id-installer +signing exe as a Mach-O binary +setting binary identifier to exe +parsing Mach-O +creating cryptographic signature with certificate Developer ID Installer: RSA Developer ID Installer (test) +writing Mach-O to exe.developer-id-installer + +$ rcodesign print-signature-info exe.developer-id-installer +- path: exe.developer-id-installer + file_size: 22544 + file_sha256: 21c767ead15a921e3e30c767edad0fa427a1d6d2a40b4f441a6887936f50f3d4 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 18866 / 0x49b2 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 2482 / 0x9b2 + linkedit_bytes_after_signature: 3678 / 0xe5e + signature: + superblob_length: 2466 / 0x9a2 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 316 + sha1: b1d835487867f475abdf906baacba12f8e4083cc + sha256: b69ec4756395b108b381e8a40969b849986b28ff14ce1f8346e49d16278b093b + - slot: RequirementSet (2) + magic: fade0c01 + length: 80 + sha1: bbf8d975b30086c19734ae0c52a2f0a296515ab3 + sha256: 77c190fb1bb2b3add5411d105d9d306cb5eb4bcb54ec1a7771239bebcc03e54c + - slot: CMS Signature (65536) + magic: fade0b01 + length: 2034 + sha1: 106fda991c0624264fe46127d92b85ef33dc8ae9 + sha256: 0806066e436723014fbf803ba8b465bcde2d39aff1951bee92afa78f623c221e + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(0x0) + identifier: exe + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 77c190fb1bb2b3add5411d105d9d306cb5eb4bcb54ec1a7771239bebcc03e54c' + code_requirements: + - 'designated(3): 0: (identifier "exe") and (certificate root = H"5c1314a89e5a486ac7b1da86b38e08777adca4af");' + cms: + certificates: + - subject: 'CN=Developer ID Installer: RSA Developer ID Installer (test), OU=test, O=RSA Developer ID Installer, C=US' + issuer: 'CN=Developer ID Installer: RSA Developer ID Installer (test), OU=test, O=RSA Developer ID Installer, C=US' + key_algorithm: RSA + signature_algorithm: SHA-256 with RSA encryption + signed_with_algorithm: SHA-256 with RSA encryption + is_apple_root_ca: false + is_apple_intermediate_ca: false + chains_to_apple_root_ca: false + apple_extended_key_usages: + - Developer ID Installer + apple_code_signing_extensions: + - Developer ID Installer + apple_certificate_profile: developer-id-installer + apple_team_id: test + signers: + - issuer: 'CN=Developer ID Installer: RSA Developer ID Installer (test), OU=test, O=RSA Developer ID Installer, C=US' + digest_algorithm: SHA-256 + signature_algorithm: SHA-256 with RSA encryption + attributes: + - 1.2.840.113549.1.9.3 + - 1.2.840.113549.1.9.4 + - 1.2.840.113549.1.9.5 + - 1.2.840.113635.100.9.1 + - 1.2.840.113635.100.9.2 + content_type: 1.2.840.113549.1.7.1 + message_digest: b69ec4756395b108b381e8a40969b849986b28ff14ce1f8346e49d16278b093b + signing_time: 2023-11-05T10:00:00Z + cdhash_plist: + - + - + - ' ' + - ' cdhashes' + - ' ' + - ' ' + - "/t/ttp7EdWOVsQizgeikCWm4SZhrKP8=" + - "/t/t" + - ' ' + - ' ' + - + cdhash_digests: + - - 2.16.840.1.101.3.4.2.1 + - b69ec4756395b108b381e8a40969b849986b28ff14ce1f8346e49d16278b093b + signature_verifies: true + +$ rcodesign sign --pem-source src/testdata/self-signed-rsa-mac-installer-distribution.pem --signing-time 2023-11-05T10:00:00Z --timestamp-url none exe exe.mac-installer-distribution +reading PEM data from src/testdata/self-signed-rsa-mac-installer-distribution.pem +registering signing key +signing exe to exe.mac-installer-distribution +signing exe as a Mach-O binary +setting binary identifier to exe +parsing Mach-O +creating cryptographic signature with certificate 3rd Party Mac Developer Installer: RSA Mac Installer Distribution (test) +writing Mach-O to exe.mac-installer-distribution + +$ rcodesign print-signature-info exe.mac-installer-distribution +- path: exe.mac-installer-distribution + file_size: 22544 + file_sha256: d95e33ec011ce293e9fd0f5b5e14cfe6ce42ecca863a72e50bf7260dab4c577f + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 18923 / 0x49eb + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 2539 / 0x9eb + linkedit_bytes_after_signature: 3621 / 0xe25 + signature: + superblob_length: 2523 / 0x9db + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 316 + sha1: 25b36eef594655dc5d3fe2546b8544689dce35a6 + sha256: 573b8c4f66ff2bd848f7af75fdb3daba70f38287b93463dfec1e0586f01ea59d + - slot: RequirementSet (2) + magic: fade0c01 + length: 80 + sha1: 79b9c12819b6a549c8b107dc31a253351ad82e55 + sha256: 7d2395bc79aad815504fb0dcca84e5a009ac109a78843d24e8592c48f54388b7 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 2091 + sha1: 2c14a17b4c055b6a56dff68eae8bf9cfb7bfa36c + sha256: 6a3e036f02b15b5117438fce7931b03f3b7b3b2a48bc557ba45be6bb9e364622 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(0x0) + identifier: exe + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 7d2395bc79aad815504fb0dcca84e5a009ac109a78843d24e8592c48f54388b7' + code_requirements: + - 'designated(3): 0: (identifier "exe") and (certificate root = H"58e39fe0fca55e7af4ca00027bc7c59e566e960a");' + cms: + certificates: + - subject: 'CN=3rd Party Mac Developer Installer: RSA Mac Installer Distribution (test), OU=test, O=RSA Mac Installer Distribution, C=US' + issuer: 'CN=3rd Party Mac Developer Installer: RSA Mac Installer Distribution (test), OU=test, O=RSA Mac Installer Distribution, C=US' + key_algorithm: RSA + signature_algorithm: SHA-256 with RSA encryption + signed_with_algorithm: SHA-256 with RSA encryption + is_apple_root_ca: false + is_apple_intermediate_ca: false + chains_to_apple_root_ca: false + apple_extended_key_usages: + - 3rd Party Mac Developer Installer Packaging Signing + apple_code_signing_extensions: + - Apple Mac App Signing Submission + apple_certificate_profile: mac-installer-distribution + apple_team_id: test + signers: + - issuer: 'CN=3rd Party Mac Developer Installer: RSA Mac Installer Distribution (test), OU=test, O=RSA Mac Installer Distribution, C=US' + digest_algorithm: SHA-256 + signature_algorithm: SHA-256 with RSA encryption + attributes: + - 1.2.840.113549.1.9.3 + - 1.2.840.113549.1.9.4 + - 1.2.840.113549.1.9.5 + - 1.2.840.113635.100.9.1 + - 1.2.840.113635.100.9.2 + content_type: 1.2.840.113549.1.7.1 + message_digest: 573b8c4f66ff2bd848f7af75fdb3daba70f38287b93463dfec1e0586f01ea59d + signing_time: 2023-11-05T10:00:00Z + cdhash_plist: + - + - + - ' ' + - ' cdhashes' + - ' ' + - ' ' + - "/t/tVzuMT2b/K9hI9691/bPaunDzgoc=" + - "/t/t" + - ' ' + - ' ' + - + cdhash_digests: + - - 2.16.840.1.101.3.4.2.1 + - 573b8c4f66ff2bd848f7af75fdb3daba70f38287b93463dfec1e0586f01ea59d + signature_verifies: true + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-code-requirements.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-code-requirements.trycmd new file mode 100644 index 00000000..437155c3 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-code-requirements.trycmd @@ -0,0 +1,66 @@ +``` +$ rcodesign debug-create-macho exe +assuming default minimum version 11.0.0 +writing Mach-O to exe + +$ rcodesign debug-create-code-requirements --code-requirement developer-id-signed reqs +writing code requirements to reqs + +$ rcodesign sign --code-requirements-path reqs exe exe.signed +setting designated code requirements for main signing target: ((anchor apple generic) and (certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */)) and ((certificate leaf[field.1.2.840.113635.100.6.1.14] /* exists */) or (certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */)) +signing exe to exe.signed +signing exe as a Mach-O binary +setting binary identifier to exe +parsing Mach-O +writing Mach-O to exe.signed + +$ rcodesign print-signature-info exe.signed +- path: exe.signed + file_size: 22544 + file_sha256: c08fddbfc311ebf3358bc6dca698de84f1eb89ba4dcc6a5aa7a0e214f766909d + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16892 / 0x41fc + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 508 / 0x1fc + linkedit_bytes_after_signature: 5652 / 0x1614 + signature: + superblob_length: 492 / 0x1ec + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 316 + sha1: 73543dc5f7d53cca5d485adfa9a36aeeb162f5ad + sha256: 0282989f9df116683ae8f98e3b71d389d7a9eaa9b1864904e94cb7bc4a19cb02 + - slot: RequirementSet (2) + magic: fade0c01 + length: 132 + sha1: 2ac6f23e29171f6d5b6a87822e8f5411753e0ec7 + sha256: 362f0cbb74f1847e4b2c7e3159a9d55e63d112fd1bf085e379d1bdfaf2813472 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: exe + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 362f0cbb74f1847e4b2c7e3159a9d55e63d112fd1bf085e379d1bdfaf2813472' + code_requirements: + - 'designated(3): 0: ((anchor apple generic) and (certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */)) and ((certificate leaf[field.1.2.840.113635.100.6.1.14] /* exists */) or (certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */));' + cms: null + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-code-signature-flags.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-code-signature-flags.trycmd new file mode 100644 index 00000000..4a2865bf --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-code-signature-flags.trycmd @@ -0,0 +1,123 @@ +``` +$ rcodesign debug-create-macho exe +assuming default minimum version 11.0.0 +writing Mach-O to exe + +$ rcodesign sign --code-signature-flags host --code-signature-flags hard --code-signature-flags kill --code-signature-flags expires --code-signature-flags runtime --code-signature-flags linker-signed exe exe.signed +adding code signature flag CodeSignatureFlags(HOST) to main signing target +adding code signature flag CodeSignatureFlags(FORCE_HARD) to main signing target +adding code signature flag CodeSignatureFlags(FORCE_KILL) to main signing target +adding code signature flag CodeSignatureFlags(FORCE_EXPIRATION) to main signing target +adding code signature flag CodeSignatureFlags(RUNTIME) to main signing target +adding code signature flag CodeSignatureFlags(LINKER_SIGNED) to main signing target +signing exe to exe.signed +signing exe as a Mach-O binary +setting binary identifier to exe +parsing Mach-O +writing Mach-O to exe.signed + +$ rcodesign print-signature-info exe.signed +- path: exe.signed + file_size: 22544 + file_sha256: 3017c61e6fcc2f51a8f617b6e7d942f7018e25c62fbacf40fe9e355fa04dff18 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16780 / 0x418c + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 396 / 0x18c + linkedit_bytes_after_signature: 5764 / 0x1684 + signature: + superblob_length: 380 / 0x17c + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 324 + sha1: 5387bae11c7dcb3c9e688b7eb64f05d8dac8c992 + sha256: 64128ab1178a720c658230df5e9143daebed711fb6879b72741be13dfdb5d6db + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20500' + flags: CodeSignatureFlags(HOST | ADHOC | FORCE_HARD | FORCE_KILL | FORCE_EXPIRATION | RUNTIME) + identifier: exe + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + runtime_version: 11.0.0 + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null + +$ rcodesign sign --code-signature-flags host exe.signed exe.signed.2 +adding code signature flag CodeSignatureFlags(HOST) to main signing target +signing exe.signed to exe.signed.2 +signing exe.signed as a Mach-O binary +setting binary identifier to exe +parsing Mach-O +writing Mach-O to exe.signed.2 + +$ rcodesign print-signature-info exe.signed.2 +- path: exe.signed.2 + file_size: 22544 + file_sha256: 2f9efe355a16d97141912918e577d4b17aa480d41b977115b75f299d4c32daf5 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16780 / 0x418c + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 396 / 0x18c + linkedit_bytes_after_signature: 5764 / 0x1684 + signature: + superblob_length: 380 / 0x17c + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 324 + sha1: 6d8822da191a8355c8f63147908fdca97497997a + sha256: e94de3a843e9104d499fbd8472fa3ad2cff45e3c98f5867155d534ada84a962a + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20500' + flags: CodeSignatureFlags(HOST | ADHOC) + identifier: exe + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + runtime_version: 11.0.0 + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-constraints.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-constraints.trycmd new file mode 100644 index 00000000..bfe3d342 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-constraints.trycmd @@ -0,0 +1,191 @@ +``` +$ rcodesign debug-create-macho exe +assuming default minimum version 11.0.0 +writing Mach-O to exe + +$ rcodesign debug-create-constraints --team-id self self.plist +writing constraints plist to self.plist + +$ rcodesign debug-create-constraints --team-id parent parent.plist +writing constraints plist to parent.plist + +$ rcodesign debug-create-constraints --team-id responsible responsible.plist +writing constraints plist to responsible.plist + +$ rcodesign debug-create-constraints --team-id library library.plist +writing constraints plist to library.plist + +$ rcodesign -v sign --launch-constraints-self-file self.plist --launch-constraints-parent-file parent.plist --launch-constraints-responsible-file responsible.plist --library-constraints-file library.plist exe exe.signed +setting self launch constraints for main signing target from path self.plist +setting parent process launch constraints for main signing target from path parent.plist +setting responsible process launch constraints for main signing target from path responsible.plist +setting loaded library constraints for main signing target from path library.plist +signing exe to exe.signed +signing exe as a Mach-O binary +inferring default signing settings from Mach-O binary +setting binary identifier to exe +parsing Mach-O +signing Mach-O binary at index 0 +binary targets macOS >= 11.0.0 with SDK 11.0.0 +creating ad-hoc signature +code directory version: 132096 +total signature size: 1072 bytes +writing Mach-O to exe.signed + +$ rcodesign print-signature-info exe.signed +- path: exe.signed + file_size: 22544 + file_sha256: 790c4aa4f95ed99bf7a0c8bc7fdbb5db42d0866d28deb61d321e437074b2e2cb + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 17472 / 0x4440 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 1088 / 0x440 + linkedit_bytes_after_signature: 5072 / 0x13d0 + signature: + superblob_length: 1072 / 0x430 + blob_count: 7 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 604 + sha1: 45ef0fe3d1f42d9b5c0a3261f82bc536626ede6c + sha256: f791189a8fbd8abfc7184baa075d428788e7a1a86fbfe899365069efe3c1b50f + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: DER Launch Constraints on Self (8) + magic: fade8181 + length: 92 + sha1: 2b749fe77b21e8fbd4a0896e011be9ee11d0bfe5 + sha256: cae1ca80e110bff504600a455803bd4260c71a2a3df3ee81f6f3cc3bb39eb94e + - slot: DER Launch Constraints on Parent (9) + magic: fade8181 + length: 94 + sha1: 27ea439e8ad23bbe2951638ccccbe75c2aa138d7 + sha256: 612cc88ec1c1e9c015953a7caae667169f646bf9d4c0fa63091bf55d285c558c + - slot: DER Launch Constraints on Responsible Process (10) + magic: fade8181 + length: 99 + sha1: e33c702c7b73cb54009443ddab5ea6e82574ce58 + sha256: 5410d96426086e9ad39af854d512ba3ac0465aa33e0116e7c3b3241e8ceaa0ac + - slot: DER Launch Constraints on Loaded Libraries (11) + magic: fade8181 + length: 95 + sha1: 7a3ca2070d329bf7eee56303568f81e920c48006 + sha256: 1aa9eb84f1e5332c9a7d818702c574120674c073e03decafe1dba6e3e3f2a7ea + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: exe + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + - 'Resources (3): 0000000000000000000000000000000000000000000000000000000000000000' + - 'Application (4): 0000000000000000000000000000000000000000000000000000000000000000' + - 'Entitlements (5): 0000000000000000000000000000000000000000000000000000000000000000' + - 'Rep Specific (6): 0000000000000000000000000000000000000000000000000000000000000000' + - 'DER Entitlements (7): 0000000000000000000000000000000000000000000000000000000000000000' + - 'DER Launch Constraints on Self (8): cae1ca80e110bff504600a455803bd4260c71a2a3df3ee81f6f3cc3bb39eb94e' + - 'DER Launch Constraints on Parent (9): 612cc88ec1c1e9c015953a7caae667169f646bf9d4c0fa63091bf55d285c558c' + - 'DER Launch Constraints on Responsible Process (10): 5410d96426086e9ad39af854d512ba3ac0465aa33e0116e7c3b3241e8ceaa0ac' + - 'DER Launch Constraints on Loaded Libraries (11): 1aa9eb84f1e5332c9a7d818702c574120674c073e03decafe1dba6e3e3f2a7ea' + launch_constraints_self: + - + - + - ' ' + - ' ccat' + - ' 0' + - ' comp' + - ' 1' + - ' reqs' + - ' ' + - ' $or' + - ' ' + - ' team-identifier' + - ' self' + - ' ' + - ' ' + - ' vers' + - ' 1' + - ' ' + - + launch_constraints_parent: + - + - + - ' ' + - ' ccat' + - ' 0' + - ' comp' + - ' 1' + - ' reqs' + - ' ' + - ' $or' + - ' ' + - ' team-identifier' + - ' parent' + - ' ' + - ' ' + - ' vers' + - ' 1' + - ' ' + - + launch_constraints_responsible: + - + - + - ' ' + - ' ccat' + - ' 0' + - ' comp' + - ' 1' + - ' reqs' + - ' ' + - ' $or' + - ' ' + - ' team-identifier' + - ' responsible' + - ' ' + - ' ' + - ' vers' + - ' 1' + - ' ' + - + library_constraints: + - + - + - ' ' + - ' ccat' + - ' 0' + - ' comp' + - ' 1' + - ' reqs' + - ' ' + - ' $or' + - ' ' + - ' team-identifier' + - ' library' + - ' ' + - ' ' + - ' vers' + - ' 1' + - ' ' + - + cms: null + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-digests.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-digests.trycmd new file mode 100644 index 00000000..1d3b5f5c --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-digests.trycmd @@ -0,0 +1,301 @@ +# We can force the use of specific digests. + +``` +$ rcodesign debug-create-macho exe +assuming default minimum version 11.0.0 +writing Mach-O to exe + +$ rcodesign sign --digest sha1 exe exe.signed +signing exe to exe.signed +signing exe as a Mach-O binary +setting binary identifier to exe +parsing Mach-O +writing Mach-O to exe.signed + +$ rcodesign print-signature-info exe.signed +- path: exe.signed + file_size: 22544 + file_sha256: cdc8997042da0032519411d23d678ca453932182c9544393268da381e0205246 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16688 / 0x4130 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 304 / 0x130 + linkedit_bytes_after_signature: 5856 / 0x16e0 + signature: + superblob_length: 288 / 0x120 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 232 + sha1: 29a1f2cbaf1a20e9326d3a6ebffb436d6531c98f + sha256: 908cc01763cfb3f0479a270998b2b7e349d15d0ef6cf88dfbdf8c7b6f8f61bba + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: exe + digest_type: sha1 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000' + - 'RequirementSet (2): 3a75f6db058529148e14dd7ea1b4729cc09ec973' + cms: null + +``` + +``` +$ rcodesign debug-create-macho exe +assuming default minimum version 11.0.0 +writing Mach-O to exe + +$ rcodesign sign --digest sha1 --digest sha256 exe exe.signed +signing exe to exe.signed +signing exe as a Mach-O binary +setting binary identifier to exe +parsing Mach-O +writing Mach-O to exe.signed + +$ rcodesign print-signature-info exe.signed +- path: exe.signed + file_size: 23568 + file_sha256: 3e0e54e0e236947019d851382ebb65c3c4b7939e1c601dc981b8e88fa0e49ef7 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 17012 / 0x4274 + macho_linkedit_end_offset: 23568 / 0x5c10 + macho_end_offset: 23568 / 0x5c10 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 628 / 0x274 + linkedit_bytes_after_signature: 6556 / 0x199c + signature: + superblob_length: 612 / 0x264 + blob_count: 4 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 232 + sha1: 4f4a745ee8a3dfe4f9de996f2aa1d6e71f8ad5e6 + sha256: 518625e9dc0e38bf4f9be3dfb17070091a091e3643dc89215ae17feeac66069b + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: 'CodeDirectory Alternate #0 (4096)' + magic: fade0c02 + length: 316 + sha1: a222eac2fc2818e7d09eadcfef8800940f50ea4e + sha256: 226de56fa11db31547a694be8ec4ff1e592b3e554949865689fa444924f6a5d4 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: exe + digest_type: sha1 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000' + - 'RequirementSet (2): 3a75f6db058529148e14dd7ea1b4729cc09ec973' + alternative_code_directories: + - - 'CodeDirectory Alternate #0 (4096)' + - version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: exe + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null + +``` + +# Signing a binary supporting old macOS automatically adds SHA-1 digests. + +``` +$ rcodesign debug-create-macho --minimum-os-version 10.11.3 exe +writing Mach-O to exe + +$ rcodesign sign exe exe.signed +signing exe to exe.signed +signing exe as a Mach-O binary +setting binary identifier to exe +parsing Mach-O +writing Mach-O to exe.signed + +$ rcodesign print-signature-info exe.signed +- path: exe.signed + file_size: 23568 + file_sha256: 55c1916f7737031457bd6cf921e72de7a6060e6a5416cb398de373a429df35cd + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 17012 / 0x4274 + macho_linkedit_end_offset: 23568 / 0x5c10 + macho_end_offset: 23568 / 0x5c10 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 628 / 0x274 + linkedit_bytes_after_signature: 6556 / 0x199c + signature: + superblob_length: 612 / 0x264 + blob_count: 4 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 232 + sha1: 924ad4febb532fcc1768161281b840747b312bd5 + sha256: 0e4ae94cde8c28c6d0e1c156618602d99ad13661de603df665262a126987eaf2 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: 'CodeDirectory Alternate #0 (4096)' + magic: fade0c02 + length: 316 + sha1: 3541576a4eb2b0474bc59c614d2e3fe2459aae0b + sha256: aaafdd1ab8ef8ae97c11f8501a5cd923899657424f065be1b4e91941c4b803ba + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: exe + digest_type: sha1 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000' + - 'RequirementSet (2): 3a75f6db058529148e14dd7ea1b4729cc09ec973' + alternative_code_directories: + - - 'CodeDirectory Alternate #0 (4096)' + - version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: exe + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null + +``` + +Signing a binary without Mach-O targeting adds SHA-1 digests + +``` +$ rcodesign debug-create-macho --no-targeting exe +writing Mach-O to exe + +$ rcodesign sign exe exe.signed +signing exe to exe.signed +signing exe as a Mach-O binary +setting binary identifier to exe +parsing Mach-O +writing Mach-O to exe.signed + +$ rcodesign print-signature-info exe.signed +- path: exe.signed + file_size: 23568 + file_sha256: 188bcc6537912c2fa3b7db65d6ccec0053d0d680b35a0a3a18c7cfe0bee56687 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 17012 / 0x4274 + macho_linkedit_end_offset: 23568 / 0x5c10 + macho_end_offset: 23568 / 0x5c10 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 628 / 0x274 + linkedit_bytes_after_signature: 6556 / 0x199c + signature: + superblob_length: 612 / 0x264 + blob_count: 4 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 232 + sha1: 065debcf801fabfb5915636fd16f4a7018da2f40 + sha256: fa9a4ab20228af9d52544f9f021e8d3bd02b9a8bc38ebcd3787b167d41189ffc + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: 'CodeDirectory Alternate #0 (4096)' + magic: fade0c02 + length: 316 + sha1: 7fb8e0032e6368d4456cd1c0fc148a02f030b610 + sha256: 6b30a1e0f8780390d0ca3276cac2e0b3ae498d3b8986cc127cd4565314b07750 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: exe + digest_type: sha1 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000' + - 'RequirementSet (2): 3a75f6db058529148e14dd7ea1b4729cc09ec973' + alternative_code_directories: + - - 'CodeDirectory Alternate #0 (4096)' + - version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: exe + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-entitlements.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-entitlements.trycmd new file mode 100644 index 00000000..c7628d71 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-entitlements.trycmd @@ -0,0 +1,228 @@ +``` +$ rcodesign debug-create-macho exe +assuming default minimum version 11.0.0 +writing Mach-O to exe + +$ rcodesign debug-create-entitlements --get-task-allow entitlements.plist +writing entitlements.plist + +$ cat entitlements.plist + + + + + get-task-allow + + + +$ rcodesign sign --entitlements-xml-path entitlements.plist exe exe.signed +setting entitlements XML for main signing target from path entitlements.plist +signing exe to exe.signed +signing exe as a Mach-O binary +setting binary identifier to exe +parsing Mach-O +writing Mach-O to exe.signed + +$ rcodesign print-signature-info exe.signed +- path: exe.signed + file_size: 22544 + file_sha256: 76362775999fdb91e4ee35c83e8b9b47cf4b30643fb7e1298096cd23e38676d0 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 17215 / 0x433f + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 831 / 0x33f + linkedit_bytes_after_signature: 5329 / 0x14d1 + signature: + superblob_length: 815 / 0x32f + blob_count: 5 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 476 + sha1: b734525ced22c371f94c5db4f24d84db32a020fe + sha256: 6929e7e458738363e16107c68e454ab544ad31e501b5a4223bcf736124a40275 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: Entitlements (5) + magic: fade7171 + length: 231 + sha1: 609a70a1468d84bef2be0146d1f0a5ea1c839948 + sha256: adea2675562421d85cc35e2c909ae27f33846eeb3b2b7c68017abd1b4b02f624 + - slot: DER Entitlements (7) + magic: fade7172 + length: 36 + sha1: 1018e52606e45993b16da1c621ceec945b9d5226 + sha256: 4d9925d24f1357a00429379f31f567cedfaa8101d58442e7864f923bfb708794 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: exe + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY | ALLOW_UNSIGNED) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + - 'Resources (3): 0000000000000000000000000000000000000000000000000000000000000000' + - 'Application (4): 0000000000000000000000000000000000000000000000000000000000000000' + - 'Entitlements (5): adea2675562421d85cc35e2c909ae27f33846eeb3b2b7c68017abd1b4b02f624' + - 'Rep Specific (6): 0000000000000000000000000000000000000000000000000000000000000000' + - 'DER Entitlements (7): 4d9925d24f1357a00429379f31f567cedfaa8101d58442e7864f923bfb708794' + entitlements_plist: + - + - + - + - + - ' get-task-allow' + - ' ' + - + - + entitlements_der_plist: + - + - + - ' ' + - ' get-task-allow' + - ' ' + - ' ' + - + cms: null + +$ rcodesign debug-create-entitlements --get-task-allow --run-unsigned-code --debugger --dynamic-code-signing --skip-library-validation entitlements.plist +writing entitlements.plist + +$ cat entitlements.plist + + + + + get-task-allow + + run-unsigned-code + + com.apple.private.cs.debugger + + dynamic-codesigning + + com.apple.private.skip-library-validation + + + +$ rcodesign sign --entitlements-xml-path entitlements.plist exe exe.signed +setting entitlements XML for main signing target from path entitlements.plist +signing exe to exe.signed +signing exe as a Mach-O binary +setting binary identifier to exe +parsing Mach-O +writing Mach-O to exe.signed + +$ rcodesign print-signature-info exe.signed +- path: exe.signed + file_size: 22544 + file_sha256: 430f7d90ca4ee8032a3449edc4bd653db9be9b25a2134bbfad6372572a45ae49 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 17545 / 0x4489 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 1161 / 0x489 + linkedit_bytes_after_signature: 4999 / 0x1387 + signature: + superblob_length: 1145 / 0x479 + blob_count: 5 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 476 + sha1: 200aa4782ec54d3bee30812ceae2fda9cd9de682 + sha256: 45935fd888a2c65f4fdb2aec0539d97b8d0048c42e1eb50a9087a6bedd6e1e56 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: Entitlements (5) + magic: fade7171 + length: 425 + sha1: 12d9b2e699e7ea852d20b29b6d428363cc919540 + sha256: e49f5c5bbafe414f3e1061dbaa41d3f7bd618138870826ccd586fd3a004f5687 + - slot: DER Entitlements (7) + magic: fade7172 + length: 172 + sha1: add4cf224f06fe52b4b1d6130516e9ef9e557f17 + sha256: 901eb3ecea82e5ee82d092f460b76afea8daefd1b7b8014fe16c979bb62ac4d7 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: exe + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY | ALLOW_UNSIGNED | DEBUGGER | JIT | SKIP_LIBRARY_VALIDATION) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + - 'Resources (3): 0000000000000000000000000000000000000000000000000000000000000000' + - 'Application (4): 0000000000000000000000000000000000000000000000000000000000000000' + - 'Entitlements (5): e49f5c5bbafe414f3e1061dbaa41d3f7bd618138870826ccd586fd3a004f5687' + - 'Rep Specific (6): 0000000000000000000000000000000000000000000000000000000000000000' + - 'DER Entitlements (7): 901eb3ecea82e5ee82d092f460b76afea8daefd1b7b8014fe16c979bb62ac4d7' + entitlements_plist: + - + - + - + - + - ' get-task-allow' + - ' ' + - ' run-unsigned-code' + - ' ' + - ' com.apple.private.cs.debugger' + - ' ' + - ' dynamic-codesigning' + - ' ' + - ' com.apple.private.skip-library-validation' + - ' ' + - + - + entitlements_der_plist: + - + - + - ' ' + - ' com.apple.private.cs.debugger' + - ' ' + - ' com.apple.private.skip-library-validation' + - ' ' + - ' dynamic-codesigning' + - ' ' + - ' get-task-allow' + - ' ' + - ' run-unsigned-code' + - ' ' + - ' ' + - + cms: null + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-for-notarization.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-for-notarization.trycmd new file mode 100644 index 00000000..de0abab3 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-for-notarization.trycmd @@ -0,0 +1,137 @@ +Sign a bundle containing multiple Mach-O binaries. + +``` +$ rcodesign debug-create-macho MyApp.app/Contents/MacOS/MyApp +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/MacOS/MyApp + +$ rcodesign debug-create-macho MyApp.app/Contents/MacOS/bin +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/MacOS/bin + +$ rcodesign debug-create-macho --file-type dylib MyApp.app/Contents/MacOS/lib.dylib +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/MacOS/lib.dylib + +$ rcodesign debug-create-macho MyApp.app/Contents/Resources/non-nested-bin +assuming default minimum version 11.0.0 +writing Mach-O to MyApp.app/Contents/Resources/non-nested-bin + +$ rcodesign debug-create-info-plist --bundle-name MyApp MyApp.app/Contents/Info.plist +writing MyApp.app/Contents/Info.plist + +$ rcodesign sign --for-notarization MyApp.app MyApp.app.signed +? 1 +--for-notarization requires use of a Developer ID signing certificate; no signing certificate was provided +Error: signing settings are not compatible with notarization + +$ rcodesign sign --for-notarization --pem-source src/testdata/self-signed-rsa-apple-development.pem MyApp.app MyApp.app.signed +? 1 +reading PEM data from src/testdata/self-signed-rsa-apple-development.pem +registering signing key +using time-stamp protocol server http://timestamp.apple.com/ts01 +--for-notarization requires use of an Apple-issued signing certificate; current certificate is not signed by Apple +hint: use a signing certificate issued by Apple that is signed by an Apple certificate authority +--for-notarization requires use of a Developer ID signing certificate; current certificate doesn't appear to be such a certificate +hint: use a `Developer ID Application`, `Developer ID Installer`, or `Developer ID Kernel` certificate +Error: signing settings are not compatible with notarization + +$ rcodesign sign --for-notarization --pem-source src/testdata/self-signed-rsa-developer-id-application.pem MyApp.app MyApp.app.signed +? 1 +reading PEM data from src/testdata/self-signed-rsa-developer-id-application.pem +registering signing key +using time-stamp protocol server http://timestamp.apple.com/ts01 +--for-notarization requires use of an Apple-issued signing certificate; current certificate is not signed by Apple +hint: use a signing certificate issued by Apple that is signed by an Apple certificate authority +Error: signing settings are not compatible with notarization + +$ rcodesign sign --for-notarization --pem-source src/testdata/self-signed-rsa-developer-id-application.pem --timestamp-url none MyApp.app MyApp.app.signed +? 1 +reading PEM data from src/testdata/self-signed-rsa-developer-id-application.pem +registering signing key +--for-notarization requires use of an Apple-issued signing certificate; current certificate is not signed by Apple +hint: use a signing certificate issued by Apple that is signed by an Apple certificate authority +--for-notarization requires use of a time-stamp protocol server; none configured +Error: signing settings are not compatible with notarization + +$ rcodesign sign -v --for-notarization --signing-time 2024-01-01T00:00:00Z --pem-source src/testdata/self-signed-rsa-developer-id-application2.pem MyApp.app MyApp.app.signed +reading PEM data from src/testdata/self-signed-rsa-developer-id-application2.pem +adding private key from src/testdata/self-signed-rsa-developer-id-application2.pem +adding certificate from src/testdata/self-signed-rsa-developer-id-application2.pem +registering signing key +using time-stamp protocol server http://timestamp.apple.com/ts01 +signing MyApp.app to MyApp.app.signed +signing bundle at MyApp.app +signing bundle at MyApp.app into MyApp.app.signed +collecting code resources files +copying file MyApp.app/Contents/Info.plist -> MyApp.app.signed/Contents/Info.plist +sealing nested Mach-O binary: Contents/MacOS/bin +signing Mach-O file Contents/MacOS/bin +setting binary identifier based on path: bin +inferring default signing settings from Mach-O binary +signing Mach-O binary at index 0 +deriving code requirements from signing certificate +deriving code requirements from signing certificate +binary targets macOS >= 11.0.0 with SDK 11.0.0 +adding hardened runtime flag because notarization mode enabled +adding code signature flags from signing settings: CodeSignatureFlags(RUNTIME) +using hardened runtime version 11.0.0 derived from SDK version +code directory version: 132352 +creating cryptographic signature with certificate Developer ID Application: John Signer (deadbeef) +Using time-stamp server http://timestamp.apple.com/ts01 +Using signing time 2024-01-01T00:00:00+00:00 +total signature size: [..] bytes +writing Mach-O to MyApp.app.signed/Contents/MacOS/bin +sealing nested Mach-O binary: Contents/MacOS/lib.dylib +signing Mach-O file Contents/MacOS/lib.dylib +setting binary identifier based on path: lib +inferring default signing settings from Mach-O binary +signing Mach-O binary at index 0 +deriving code requirements from signing certificate +deriving code requirements from signing certificate +binary targets macOS >= 11.0.0 with SDK 11.0.0 +adding hardened runtime flag because notarization mode enabled +adding code signature flags from signing settings: CodeSignatureFlags(RUNTIME) +using hardened runtime version 11.0.0 derived from SDK version +code directory version: 132352 +creating cryptographic signature with certificate Developer ID Application: John Signer (deadbeef) +Using time-stamp server http://timestamp.apple.com/ts01 +Using signing time 2024-01-01T00:00:00+00:00 +total signature size: [..] bytes +writing Mach-O to MyApp.app.signed/Contents/MacOS/lib.dylib +non-nested file is a Mach-O binary; signing accordingly Contents/Resources/non-nested-bin +signing Mach-O file Contents/Resources/non-nested-bin +setting binary identifier based on path: non-nested-bin +inferring default signing settings from Mach-O binary +signing Mach-O binary at index 0 +deriving code requirements from signing certificate +deriving code requirements from signing certificate +binary targets macOS >= 11.0.0 with SDK 11.0.0 +adding hardened runtime flag because notarization mode enabled +adding code signature flags from signing settings: CodeSignatureFlags(RUNTIME) +using hardened runtime version 11.0.0 derived from SDK version +code directory version: 132352 +creating cryptographic signature with certificate Developer ID Application: John Signer (deadbeef) +Using time-stamp server http://timestamp.apple.com/ts01 +Using signing time 2024-01-01T00:00:00+00:00 +total signature size: [..] bytes +writing Mach-O to MyApp.app.signed/Contents/Resources/non-nested-bin +writing sealed resources to MyApp.app.signed/Contents/_CodeSignature/CodeResources +signing main executable Contents/MacOS/MyApp +setting main executable binary identifier to com.example.mybundle (derived from CFBundleIdentifier in Info.plist) +inferring default signing settings from Mach-O binary +signing Mach-O binary at index 0 +deriving code requirements from signing certificate +deriving code requirements from signing certificate +binary targets macOS >= 11.0.0 with SDK 11.0.0 +adding hardened runtime flag because notarization mode enabled +adding code signature flags from signing settings: CodeSignatureFlags(RUNTIME) +using hardened runtime version 11.0.0 derived from SDK version +code directory version: 132352 +creating cryptographic signature with certificate Developer ID Application: John Signer (deadbeef) +Using time-stamp server http://timestamp.apple.com/ts01 +Using signing time 2024-01-01T00:00:00+00:00 +total signature size: [..] bytes +writing signed main executable to MyApp.app.signed/Contents/MacOS/MyApp + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-macho-info-plist.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-macho-info-plist.trycmd new file mode 100644 index 00000000..c2a72150 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-macho-info-plist.trycmd @@ -0,0 +1,66 @@ +``` +$ rcodesign debug-create-macho exe +assuming default minimum version 11.0.0 +writing Mach-O to exe + +$ rcodesign debug-create-info-plist --bundle-name MyApp Info.plist +writing Info.plist + +$ rcodesign sign --info-plist-path Info.plist exe exe.signed +signing exe to exe.signed +signing exe as a Mach-O binary +setting binary identifier to exe +parsing Mach-O +writing Mach-O to exe.signed + +$ rcodesign print-signature-info exe.signed +- path: exe.signed + file_size: 22544 + file_sha256: a060c6c3b1bd72a2d0a8aca5e21d5a91ed7ecbcfdc7d0201a87ef53ed1a74077 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16772 / 0x4184 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 388 / 0x184 + linkedit_bytes_after_signature: 5772 / 0x168c + signature: + superblob_length: 372 / 0x174 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 316 + sha1: 7f41a89c7645b669df41150feae14a688103d27f + sha256: af781f6cf2fe6b3460d436deaf800f0481ae67eaae1681a419fc7e9657da69a4 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: exe + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null + +$ hashsum --sha256 --text Info.plist +0a5902dc8e47f490d03889d3593d17bddbf79e6c1f79494e20dd28f9459effa5 Info.plist + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-macho-reconcile-identifier.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-macho-reconcile-identifier.trycmd new file mode 100644 index 00000000..02874dcb --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-macho-reconcile-identifier.trycmd @@ -0,0 +1,253 @@ +When signing a universal Mach-O, disagreeing binary identifiers are +reconciled to the same value. + +``` +$ rcodesign debug-create-macho --architecture x86-64 exe.x86_64 +assuming default minimum version 11.0.0 +writing Mach-O to exe.x86_64 + +$ rcodesign debug-create-macho --architecture aarch64 exe.aarch64 +assuming default minimum version 11.0.0 +writing Mach-O to exe.aarch64 + +$ rcodesign sign --binary-identifier identifier-0 exe.x86_64 +signing exe.x86_64 in place +signing exe.x86_64 as a Mach-O binary +parsing Mach-O +writing Mach-O to exe.x86_64 + +$ rcodesign sign --binary-identifier identifier-1 exe.aarch64 +signing exe.aarch64 in place +signing exe.aarch64 as a Mach-O binary +parsing Mach-O +writing Mach-O to exe.aarch64 + +$ rcodesign macho-universal-create -o exe exe.x86_64 exe.aarch64 +adding exe.x86_64 +adding exe.aarch64 +writing exe + +$ rcodesign -v sign exe exe.signed +signing exe to exe.signed +signing exe as a Mach-O binary +inferring default signing settings from Mach-O binary +preserving existing binary identifier in Mach-O (identifier-0) +preserving code signature flags in existing Mach-O signature (CodeSignatureFlags(ADHOC)) +identifiers within Mach-O do not agree (initial: identifier-0, subsequent: identifier-1); reconciling to identifier-0 +preserving code signature flags in existing Mach-O signature (CodeSignatureFlags(ADHOC)) +setting binary identifier to exe +parsing Mach-O +signing Mach-O binary at index 0 +binary targets macOS >= 11.0.0 with SDK 11.0.0 +adding code signature flags from signing settings: CodeSignatureFlags(ADHOC) +creating ad-hoc signature +code directory version: 132096 +total signature size: 285 bytes +signing Mach-O binary at index 1 +binary targets macOS >= 11.0.0 with SDK 11.0.0 +adding code signature flags from signing settings: CodeSignatureFlags(ADHOC) +creating ad-hoc signature +code directory version: 132096 +total signature size: 381 bytes +writing Mach-O to exe.signed + +$ rcodesign print-signature-info exe.signed +- path: exe.signed + file_size: 55312 + file_sha256: ef5ec5345b8b70820cf5f207dcf57a6e59e26067098416a93feba927aaf71402 + sub_path: macho-index:0 + entity: + mach_o: + macho_linkedit_start_offset: 4096 / 0x1000 + macho_signature_start_offset: 4112 / 0x1010 + macho_signature_end_offset: 4397 / 0x112d + macho_linkedit_end_offset: 10256 / 0x2810 + macho_end_offset: 10256 / 0x2810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 301 / 0x12d + linkedit_bytes_after_signature: 5859 / 0x16e3 + signature: + superblob_length: 285 / 0x11d + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 229 + sha1: 41e8e1f340a05d3f0a349c9dd6ee4ea608f13934 + sha256: f319eb464979c5ac2db1f24f9d73c17731a022e4af7d9530fe89913e9a9401fd + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: identifier-0 + digest_type: sha256 + platform: 0 + signed_entity_size: 4112 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 2 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null +- path: exe.signed + file_size: 55312 + file_sha256: ef5ec5345b8b70820cf5f207dcf57a6e59e26067098416a93feba927aaf71402 + sub_path: macho-index:1 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16781 / 0x418d + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 397 / 0x18d + linkedit_bytes_after_signature: 5763 / 0x1683 + signature: + superblob_length: 381 / 0x17d + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 325 + sha1: c51f0d3272967beff74e25d1f0caedc9b6ed8229 + sha256: 5f7b84f4ce42b6237680fb5a15b7f6a58afe21a5ff73b6e1f1c826835d220d53 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: identifier-0 + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null + +``` + +If we force a different identifier, that is used. + +``` +$ rcodesign sign --binary-identifier identifier-forced exe exe.signed-forced +signing exe to exe.signed-forced +signing exe as a Mach-O binary +parsing Mach-O +writing Mach-O to exe.signed-forced + +$ rcodesign print-signature-info exe.signed-forced +- path: exe.signed-forced + file_size: 55312 + file_sha256: 963cbf89c51423c76cbb06618abbf58a68542780160191dce851592f36a86752 + sub_path: macho-index:0 + entity: + mach_o: + macho_linkedit_start_offset: 4096 / 0x1000 + macho_signature_start_offset: 4112 / 0x1010 + macho_signature_end_offset: 4402 / 0x1132 + macho_linkedit_end_offset: 10256 / 0x2810 + macho_end_offset: 10256 / 0x2810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 306 / 0x132 + linkedit_bytes_after_signature: 5854 / 0x16de + signature: + superblob_length: 290 / 0x122 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 234 + sha1: 654c659c27de82e26ca4554bafe9c909b35b2e6a + sha256: df10f8609f8116bf1fbbef0654a2e3f898323aee07c3d7475a5cb9fd5bee7e4c + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: identifier-forced + digest_type: sha256 + platform: 0 + signed_entity_size: 4112 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 2 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null +- path: exe.signed-forced + file_size: 55312 + file_sha256: 963cbf89c51423c76cbb06618abbf58a68542780160191dce851592f36a86752 + sub_path: macho-index:1 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16786 / 0x4192 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 402 / 0x192 + linkedit_bytes_after_signature: 5758 / 0x167e + signature: + superblob_length: 386 / 0x182 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 330 + sha1: 2758ec0c44f728a4f53dbcf55b8c43cad4bbbd3b + sha256: bc59b9a004b779b671902b1ccefff5d64eccf48cf38eb9aade1ecfdf48dd5a2d + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: identifier-forced + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-macho-text-segment-offset.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-macho-text-segment-offset.trycmd new file mode 100644 index 00000000..0daba2ca --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-macho-text-segment-offset.trycmd @@ -0,0 +1,139 @@ +Signing a Mach-O whose __TEXT segment starts after file start and after +load commands. + +``` +$ rcodesign debug-create-macho --text-segment-start-offset 4096 exe +assuming default minimum version 11.0.0 +writing Mach-O to exe + +$ rcodesign sign exe exe.signed +signing exe to exe.signed +signing exe as a Mach-O binary +setting binary identifier to exe +parsing Mach-O +writing Mach-O to exe.signed + +$ rcodesign extract macho-segments exe.signed +segments count: 5 +segment #0; __PAGEZERO; offsets=0x0-0x0 (0-0); addresses=0x0-0x100000000; vm/file size 4294967296/0; section count 0 +segment #1; __TEXT; offsets=0x1000-0x4000 (4096-16384); addresses=0x100000000-0x100000000; vm/file size 0/12288; section count 2 +segment #1; section #0: __text; offsets=0x4000-0x4000 (16384-16384); addresses=0x100000000-0x100000000; size 0; align=16384; flags=0 +segment #1; section #1: __const; offsets=0x4000-0x4000 (16384-16384); addresses=0x100000000-0x100000000; size 0; align=16384; flags=0 +segment #2; __DATA_CONST; offsets=0x4000-0x4000 (16384-16384); addresses=0x100000000-0x100000000; vm/file size 0/0; section count 1 +segment #2; section #0: __const; offsets=0x4000-0x4000 (16384-16384); addresses=0x100000000-0x100000000; size 0; align=16384; flags=0 +segment #3; __DATA; offsets=0x4000-0x4000 (16384-16384); addresses=0x100000000-0x100000000; vm/file size 0/0; section count 1 +segment #3; section #0: __data; offsets=0x4000-0x4000 (16384-16384); addresses=0x100000000-0x100000000; size 0; align=16384; flags=0 +segment #4; __LINKEDIT; offsets=0x4000-0x5810 (16384-22544); addresses=0x100000000-0x100004000; vm/file size 16384/6160; section count 0 + +$ rcodesign print-signature-info exe.signed +- path: exe.signed + file_size: 22544 + file_sha256: 77b1ad57dcdf3cc1691937f60b19e1d0ddcc9375ef2434a5c3afd0f543f6c8d6 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16772 / 0x4184 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 388 / 0x184 + linkedit_bytes_after_signature: 5772 / 0x168c + signature: + superblob_length: 372 / 0x174 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 316 + sha1: 1f4bd3829ad41fe48fb516b9f22129f680f38f25 + sha256: 3fcf43c77ae4ff699ba01bdbb0e77a934ae1ad09f66adf65fb4b03775e5df2f5 + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: exe + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null + +``` + +Signing a Mach-O whose __TEXT segment doesn't begin at 0x0 and whose +non-zero start is before the end of the load commands. + +``` +$ rcodesign debug-create-macho --text-segment-start-offset 64 exe +assuming default minimum version 11.0.0 +writing Mach-O to exe + +$ rcodesign sign exe exe.signed +signing exe to exe.signed +signing exe as a Mach-O binary +setting binary identifier to exe +parsing Mach-O +writing Mach-O to exe.signed + +$ rcodesign print-signature-info exe.signed +- path: exe.signed + file_size: 22544 + file_sha256: 8c5f46d87038f6028bc60f2a7c62bc842e80a738053a377ac4cb3c93a7114658 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16772 / 0x4184 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 388 / 0x184 + linkedit_bytes_after_signature: 5772 / 0x168c + signature: + superblob_length: 372 / 0x174 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 316 + sha1: 43d43e2056ce1c367bc707f0dbe35550e3d1e44c + sha256: 55bed196e4c5df215500e1587a172a47b0640a7d19307b8bd04165ddb33216ed + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: exe + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-macho-universal.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-macho-universal.trycmd new file mode 100644 index 00000000..14c01c83 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-macho-universal.trycmd @@ -0,0 +1,116 @@ +``` +$ rcodesign debug-create-macho --architecture aarch64 exe.aarch64 +assuming default minimum version 11.0.0 +writing Mach-O to exe.aarch64 + +$ rcodesign debug-create-macho --architecture x86-64 exe.x86-64 +assuming default minimum version 11.0.0 +writing Mach-O to exe.x86-64 + +$ rcodesign macho-universal-create -o exe exe.aarch64 exe.x86-64 +adding exe.aarch64 +adding exe.x86-64 +writing exe + +$ rcodesign sign exe exe.signed +signing exe to exe.signed +signing exe as a Mach-O binary +setting binary identifier to exe +parsing Mach-O +writing Mach-O to exe.signed + +$ rcodesign print-signature-info exe.signed +- path: exe.signed + file_size: 59408 + file_sha256: 70b20ec49527789389cd366af42fbb018551ec96a73d4798fe33d55701695ef3 + sub_path: macho-index:0 + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16772 / 0x4184 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 388 / 0x184 + linkedit_bytes_after_signature: 5772 / 0x168c + signature: + superblob_length: 372 / 0x174 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 316 + sha1: 4ca6f9ee2bfe2bfac44ab4e9e9c1ef9b6e4fc0de + sha256: 23fc7207e52f23c0f6d2317dbb92cf9eff2aca8fe61ac241900d48be8f46cf5c + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: exe + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null +- path: exe.signed + file_size: 59408 + file_sha256: 70b20ec49527789389cd366af42fbb018551ec96a73d4798fe33d55701695ef3 + sub_path: macho-index:1 + entity: + mach_o: + macho_linkedit_start_offset: 4096 / 0x1000 + macho_signature_start_offset: 4112 / 0x1010 + macho_signature_end_offset: 4388 / 0x1124 + macho_linkedit_end_offset: 10256 / 0x2810 + macho_end_offset: 10256 / 0x2810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 292 / 0x124 + linkedit_bytes_after_signature: 5868 / 0x16ec + signature: + superblob_length: 276 / 0x114 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 220 + sha1: a2d752af585c67bf99ffa874a67c87013d27a42b + sha256: c33301d0689076699ca6fcb89113bc376dfd2439d8acbd3ed826ee2ee80ade5e + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: exe + digest_type: sha256 + platform: 0 + signed_entity_size: 4112 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 2 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-p12.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-p12.trycmd new file mode 100644 index 00000000..e46fe3ca --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign-p12.trycmd @@ -0,0 +1,403 @@ +Signing with a PKCS#12 / PFX file works + +``` +$ rcodesign debug-create-macho exe +assuming default minimum version 11.0.0 +writing Mach-O to exe + +$ rcodesign sign --p12-file src/testdata/self-signed-rsa-apple-development.p12 --p12-password password --signing-time 2023-11-05T10:00:00Z --timestamp-url none exe exe.signed +registering signing key +signing exe to exe.signed +signing exe as a Mach-O binary +setting binary identifier to exe +parsing Mach-O +creating cryptographic signature with certificate Apple Development: RSA Apple Development (test) +writing Mach-O to exe.signed + +$ rcodesign extract cms-info exe.signed +signed content (embedded): None +signed content (external): Some("fade0c020000013c00020400000000000000009c000000580000000200000005000040102002000c")... (316 bytes) +signed content SHA-1: e1c19ec9ec8c13b3940f8385a8f5f9b56309330a +signed content SHA-256: fbd3393f2015c87653f2ccef69a864fb60985e21d38485b3bb1c7deeb76d825c +signed content SHA-384: 25a7989a6cb024c4d037cd56f0bcfcea0ba4b178362dc4d1694a2080e47d7c63bb178b5a85dfddc9bedf8764ac8d380a +signed content SHA-512: 16db5b4b1ea0529186a760790ce2d2325ea31f3d009eb74a182beb97ac26617e5495bf638d4b79746eee8f469aa991ba3a341af41b46d3b8d59079cf6d376950 +certificate count: 1 +certificate #0: subject CN=Apple Development: RSA Apple Development (test); self signed=true +signer count: 1 +signer #0: digest algorithm: Sha256 +signer #0: signature algorithm: RsaSha256 +signer #0: content type: 1.2.840.113549.1.7.1 +signer #0: message digest: fbd3393f2015c87653f2ccef69a864fb60985e21d38485b3bb1c7deeb76d825c +signer #0: signing time: Some(2023-11-05T10:00:00Z) +signer #0: signature content SHA-1: fc8004e694122f8e134b5e39380392165732f648 +signer #0: signature content SHA-256: c07c8319b33e4410d2f6b06ad5344aebbd7a848894f9c5004057cbbe8e17474d +signer #0: signature content SHA-384: 8b53dc40ee48b34eee4c4ba0c2d0fe411929ef4f80e9dbc167cf96c6ef4abd8d633affefffdca0ef6eadc0a055c31827 +signer #0: signature content SHA-512: f4cf4a8f0e4de256e07bbfedff1455d50ae05cdbd2d3fec3a12bbc055a1e77d7746b8f1b2bf55fc6380164fda04e1d12d3fd03085b5842f6fb7c5a00348eec84 +signer #0: signature valid: true +signer #0: time-stamp token present: false + +$ rcodesign extract cms exe.signed +SignedData { + digest_algorithms: { + Sha256, + }, + signed_content: None, + certificates: Some( + [ + CapturedX509Certificate { + original: Ber(308203f7308202dfa003020102020101300d06092a864886f70d01010b050030818c31143012060a0992268993f22c6401010c04746573743138303606035504030c2f4170706c6520446576656c6f706d656e743a20525341204170706c6520446576656c6f706d656e7420287465737429310d300b060355040b0c0474657374311e301c060355040a0c15525341204170706c6520446576656c6f706d656e74310b3009060355040613025553301e170d3233313130373130343932385a170d3337303731363130343932385a30818c31143012060a0992268993f22c6401010c04746573743138303606035504030c2f4170706c6520446576656c6f706d656e743a20525341204170706c6520446576656c6f706d656e7420287465737429310d300b060355040b0c0474657374311e301c060355040a0c15525341204170706c6520446576656c6f706d656e74310b300906035504061302555330820122300d06092a864886f70d01010105000382010f003082010a0282010100e6c9e2a15c321fbad0442e10bebb9824c6319fdaa7afd7c227bda4a81e152b9728855a375e34249f85f4127207a658bc091b447bb499f1f0884eecc98187630200f24c4d08e557624c33a4021ab5528581a92b902b4d436c3033cc27f67242f3eff171ed62f316fc0f138eb927e8d0568aecf11fc519487608509037e1382d81998c12f7b5913dd14bf9c26880bcc270c46b872023f83bdd1ba2395b34d6873ca5ff703dffbce5f561b55361a5a3bd01bfeda19f20b1a6bc89aadffb643d3e8faf2329609e11a587732f85405102de6a2cd76c2f95c95e22dd7af7ae9b8f238d2c60abad0595b3fa9c96b52e9ab95ef7af4596112de6fa5862aebc7aa24fe1fb0203010001a3623060300c0603551d130101ff0402300030160603551d250101ff040c300a06082b06010505070303300e0603551d0f0101ff0404030207803013060a2a864886f763640601020101ff040205003013060a2a864886f7636406010c0101ff04020500300d06092a864886f70d01010b0500038201010099563303c646c61728b9758f16c559e6e1be2eae0884c8535834062b81f21c89a94c89c0ffb25b93b886535749d81e94440f6d257438e07cc701a7e69aa8bdeca71c3d2f686357f842c62a27b045c671b487d89fd3e69458aae19d69274e7b2d54f7f736e25196738185cab05ccd71b4d8a180610e1f771cfeee0198047692ec87fd3a4dbac1db4ff8205ddd7445d6184b11e3ca7018d6a495fa4b44bc1325fdd68050b45dddadf3a9a9ea0575a5b6d7a9d636f052f3b3d79729bf475efc9c95db4154f14d2ae598cb0debd424e0f0bfe59f11668f1e80e52412ae3f722bab026439addcf9a07b81cd17dec724afa51128e092038203c137f602cb154397c05a), + inner: X509Certificate( + Certificate { + tbs_certificate: TbsCertificate { + version: Some( + V3, + ), + serial_number: Integer( + b"/x01", + ), + signature: AlgorithmIdentifier { + algorithm: 1.2.840.113549.1.1.11, + parameters: Some( + AlgorithmParameter( + [ 05 00 ], + ), + ), + }, + issuer: RdnSequence( + RdnSequence( + [ + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 0.9.2342.19200300.100.1.1, + value: 0c0474657374, + }, + ], + ), + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 2.5.4.3, + value: 0c2f4170706c6520446576656c6f706d656e743a20525341204170706c6520446576656c6f706d656e7420287465737429, + }, + ], + ), + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 2.5.4.11, + value: 0c0474657374, + }, + ], + ), + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 2.5.4.10, + value: 0c15525341204170706c6520446576656c6f706d656e74, + }, + ], + ), + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 2.5.4.6, + value: 13025553, + }, + ], + ), + ], + ), + ), + validity: Validity { + not_before: UtcTime( + UtcTime( + 2023-11-07T10:49:28Z, + ), + ), + not_after: UtcTime( + UtcTime( + 2037-07-16T10:49:28Z, + ), + ), + }, + subject: RdnSequence( + RdnSequence( + [ + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 0.9.2342.19200300.100.1.1, + value: 0c0474657374, + }, + ], + ), + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 2.5.4.3, + value: 0c2f4170706c6520446576656c6f706d656e743a20525341204170706c6520446576656c6f706d656e7420287465737429, + }, + ], + ), + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 2.5.4.11, + value: 0c0474657374, + }, + ], + ), + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 2.5.4.10, + value: 0c15525341204170706c6520446576656c6f706d656e74, + }, + ], + ), + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 2.5.4.6, + value: 13025553, + }, + ], + ), + ], + ), + ), + subject_public_key_info: SubjectPublicKeyInfo { + algorithm: AlgorithmIdentifier { + algorithm: 1.2.840.113549.1.1.1, + parameters: Some( + AlgorithmParameter( + [ 05 00 ], + ), + ), + }, + subject_public_key: 3082010a0282010100e6c9e2a15c321fbad0442e10bebb9824c6319fdaa7afd7c227bda4a81e152b9728855a375e34249f85f4127207a658bc091b447bb499f1f0884eecc98187630200f24c4d08e557624c33a4021ab5528581a92b902b4d436c3033cc27f67242f3eff171ed62f316fc0f138eb927e8d0568aecf11fc519487608509037e1382d81998c12f7b5913dd14bf9c26880bcc270c46b872023f83bdd1ba2395b34d6873ca5ff703dffbce5f561b55361a5a3bd01bfeda19f20b1a6bc89aadffb643d3e8faf2329609e11a587732f85405102de6a2cd76c2f95c95e22dd7af7ae9b8f238d2c60abad0595b3fa9c96b52e9ab95ef7af4596112de6fa5862aebc7aa24fe1fb0203010001 (unused 0), + }, + issuer_unique_id: None, + subject_unique_id: None, + extensions: Some( + Extensions( + [ + Extension { + id: 2.5.29.19, + critical: Some( + true, + ), + value: 3000, + }, + Extension { + id: 2.5.29.37, + critical: Some( + true, + ), + value: 300a06082b06010505070303, + }, + Extension { + id: 2.5.29.15, + critical: Some( + true, + ), + value: 03020780, + }, + Extension { + id: 1.2.840.113635.100.6.1.2, + critical: Some( + true, + ), + value: 0500, + }, + Extension { + id: 1.2.840.113635.100.6.1.12, + critical: Some( + true, + ), + value: 0500, + }, + ], + ), + ), + raw_data: Some("308202dfa003020102020101300d06092a864886f70d01010b050030818c31143012060a0992268993f22c6401010c04746573743138303606035504030c2f4170706c6520446576656c6f706d656e743a20525341204170706c6520446576656c6f706d656e7420287465737429310d300b060355040b0c0474657374311e301c060355040a0c15525341204170706c6520446576656c6f706d656e74310b3009060355040613025553301e170d3233313130373130343932385a170d3337303731363130343932385a30818c31143012060a0992268993f22c6401010c04746573743138303606035504030c2f4170706c6520446576656c6f706d656e743a20525341204170706c6520446576656c6f706d656e7420287465737429310d300b060355040b0c0474657374311e301c060355040a0c15525341204170706c6520446576656c6f706d656e74310b300906035504061302555330820122300d06092a864886f70d01010105000382010f003082010a0282010100e6c9e2a15c321fbad0442e10bebb9824c6319fdaa7afd7c227bda4a81e152b9728855a375e34249f85f4127207a658bc091b447bb499f1f0884eecc98187630200f24c4d08e557624c33a4021ab5528581a92b902b4d436c3033cc27f67242f3eff171ed62f316fc0f138eb927e8d0568aecf11fc519487608509037e1382d81998c12f7b5913dd14bf9c26880bcc270c46b872023f83bdd1ba2395b34d6873ca5ff703dffbce5f561b55361a5a3bd01bfeda19f20b1a6bc89aadffb643d3e8faf2329609e11a587732f85405102de6a2cd76c2f95c95e22dd7af7ae9b8f238d2c60abad0595b3fa9c96b52e9ab95ef7af4596112de6fa5862aebc7aa24fe1fb0203010001a3623060300c0603551d130101ff0402300030160603551d250101ff040c300a06082b06010505070303300e0603551d0f0101ff0404030207803013060a2a864886f763640601020101ff040205003013060a2a864886f7636406010c0101ff04020500"), + }, + signature_algorithm: AlgorithmIdentifier { + algorithm: 1.2.840.113549.1.1.11, + parameters: Some( + AlgorithmParameter( + [ 05 00 ], + ), + ), + }, + signature: 99563303c646c61728b9758f16c559e6e1be2eae0884c8535834062b81f21c89a94c89c0ffb25b93b886535749d81e94440f6d257438e07cc701a7e69aa8bdeca71c3d2f686357f842c62a27b045c671b487d89fd3e69458aae19d69274e7b2d54f7f736e25196738185cab05ccd71b4d8a180610e1f771cfeee0198047692ec87fd3a4dbac1db4ff8205ddd7445d6184b11e3ca7018d6a495fa4b44bc1325fdd68050b45dddadf3a9a9ea0575a5b6d7a9d636f052f3b3d79729bf475efc9c95db4154f14d2ae598cb0debd424e0f0bfe59f11668f1e80e52412ae3f722bab026439addcf9a07b81cd17dec724afa51128e092038203c137f602cb154397c05a (unused 0), + }, + ), + }, + ], + ), + signers: [ + SignerInfo { + issuer: RdnSequence( + RdnSequence( + [ + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 0.9.2342.19200300.100.1.1, + value: 0c0474657374, + }, + ], + ), + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 2.5.4.3, + value: 0c2f4170706c6520446576656c6f706d656e743a20525341204170706c6520446576656c6f706d656e7420287465737429, + }, + ], + ), + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 2.5.4.11, + value: 0c0474657374, + }, + ], + ), + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 2.5.4.10, + value: 0c15525341204170706c6520446576656c6f706d656e74, + }, + ], + ), + RelativeDistinguishedName( + [ + AttributeTypeAndValue { + type: 2.5.4.6, + value: 13025553, + }, + ], + ), + ], + ), + ), + serial_number: Integer( + b"/x01", + ), + digest_algorithm: Sha256, + signature_algorithm: RsaSha256, + signature: 427dab648570de5bb97d6660434a47057abd7fdbd2598419a96307c2e3b72f8dd3db12e4b540d3976ba30220c49a19ef82193d66b04cefdf5066ab49472b4c6b333cabf9c789167d04c25974b3ca2a3bf9d26a47cf575b209216fa3b6f7f849b026e168d248692db61f68ed974462423bcc69fe152b8db05c58b5b1dae0a6cde4c1f085c51ddba0621935ae4cc5fa764073a9681241dd03db9497844200749b31f1cb53345ab1c1626f4bc41c0171dd24178d14c66bc6392fc0cb1b7b8a27622af16bd52fdc54661939a07d49d0f9aebf833765fbaf4c2f8febc6741643ae4dc133ef35cf01eeb205b309d56ab240ae73ebf013ea80203b9c7dd613355c7585b, + signed_attributes: Some( + SignedAttributes { + content_type: 1.2.840.113549.1.7.1, + message_digest: fbd3393f2015c87653f2ccef69a864fb60985e21d38485b3bb1c7deeb76d825c, + signing_time: Some( + 2023-11-05T10:00:00Z, + ), + }, + ), + digested_signed_attributes_data: Some("318201d4301806092a864886f70d010903310b06092a864886f70d010701301c06092a864886f70d010905310f170d3233313130353130303030305a302f06092a864886f70d01090431220420fbd3393f2015c87653f2ccef69a864fb60985e21d38485b3bb1c7deeb76d825c303c06092a864886f763640902312f302d06096086480165030402010420fbd3393f2015c87653f2ccef69a864fb60985e21d38485b3bb1c7deeb76d825c3082012906092a864886f7636409013182011a048201163c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d38223f3e0a3c21444f435459504520706c697374205055424c494320222d2f2f4170706c652f2f44544420504c49535420312e302f2f454e222022687474703a2f2f7777772e6170706c652e636f6d2f445444732f50726f70657274794c6973742d312e302e647464223e0a3c706c6973742076657273696f6e3d22312e30223e0a3c646963743e0a093c6b65793e63646861736865733c2f6b65793e0a093c61727261793e0a09093c646174613e0a09092b394d355079415679485a5438737a766161686b2b3243595869453d0a09093c2f646174613e0a093c2f61727261793e0a3c2f646963743e0a3c2f706c6973743e0a"), + unsigned_attributes: None, + }, + ], +} + +$ rcodesign print-signature-info exe.signed +- path: exe.signed + file_size: 22544 + file_sha256: b79b1797e7e4da470e94c4b4881e1a04dab26e515cf3ecdc69e31cb16f48812d + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 18841 / 0x4999 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 2457 / 0x999 + linkedit_bytes_after_signature: 3703 / 0xe77 + signature: + superblob_length: 2441 / 0x989 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 316 + sha1: e1c19ec9ec8c13b3940f8385a8f5f9b56309330a + sha256: fbd3393f2015c87653f2ccef69a864fb60985e21d38485b3bb1c7deeb76d825c + - slot: RequirementSet (2) + magic: fade0c01 + length: 80 + sha1: 4f9d3e687a7622d7209180eeca44e6a4c97a2187 + sha256: f48f861e449222d508463e8342afee0c2241817878cab57b21e38e6aea0c08fa + - slot: CMS Signature (65536) + magic: fade0b01 + length: 2009 + sha1: faa96064b748df76d40c73c847cad6664772324c + sha256: f77bac63ecd33d9a152f4011d5cfefe695682e4dd15da5d89cdb9d8347350404 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(0x0) + identifier: exe + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): f48f861e449222d508463e8342afee0c2241817878cab57b21e38e6aea0c08fa' + code_requirements: + - 'designated(3): 0: (identifier "exe") and (certificate root = H"e1c7216e46533c923b7cfc94e86c7043790b96e9");' + cms: + certificates: + - subject: 'CN=Apple Development: RSA Apple Development (test), OU=test, O=RSA Apple Development, C=US' + issuer: 'CN=Apple Development: RSA Apple Development (test), OU=test, O=RSA Apple Development, C=US' + key_algorithm: RSA + signature_algorithm: SHA-256 with RSA encryption + signed_with_algorithm: SHA-256 with RSA encryption + is_apple_root_ca: false + is_apple_intermediate_ca: false + chains_to_apple_root_ca: false + apple_extended_key_usages: + - Code Signing + apple_code_signing_extensions: + - iPhone Developer + - Mac Developer + apple_certificate_profile: apple-development + apple_team_id: test + signers: + - issuer: 'CN=Apple Development: RSA Apple Development (test), OU=test, O=RSA Apple Development, C=US' + digest_algorithm: SHA-256 + signature_algorithm: SHA-256 with RSA encryption + attributes: + - 1.2.840.113549.1.9.3 + - 1.2.840.113549.1.9.4 + - 1.2.840.113549.1.9.5 + - 1.2.840.113635.100.9.1 + - 1.2.840.113635.100.9.2 + content_type: 1.2.840.113549.1.7.1 + message_digest: fbd3393f2015c87653f2ccef69a864fb60985e21d38485b3bb1c7deeb76d825c + signing_time: 2023-11-05T10:00:00Z + cdhash_plist: + - + - + - ' ' + - ' cdhashes' + - ' ' + - ' ' + - "/t/t+9M5PyAVyHZT8szvaahk+2CYXiE=" + - "/t/t" + - ' ' + - ' ' + - + cdhash_digests: + - - 2.16.840.1.101.3.4.2.1 + - fbd3393f2015c87653f2ccef69a864fb60985e21d38485b3bb1c7deeb76d825c + signature_verifies: true + +``` \ No newline at end of file diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/sign.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign.trycmd new file mode 100644 index 00000000..ec22fd6f --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/sign.trycmd @@ -0,0 +1,540 @@ +``` +$ rcodesign help sign +Adds code signatures to a signable entity. + +This command can sign the following entities: + +* A single Mach-O binary (specified by its file path) +* A bundle (specified by its directory path) +* A DMG disk image (specified by its path) +* A XAR archive (commonly a .pkg installer file) + +If the input is Mach-O binary, it can be a single or multiple/fat/universal +Mach-O binary. If a fat binary is given, each Mach-O within that binary will +be signed. + +If the input is a bundle, the bundle will be recursively signed. If the +bundle contains nested bundles or Mach-O binaries, those will be signed +automatically. + +# Settings Scope + +The following signing settings are global and apply to all signed entities: + +* --pem-source +* --team-name +* --timestamp-url + +The following signing settings can be scoped so they only apply to certain +entities: + +* --digest +* --binary-identifier +* --code-requirements-files +* --code-resources-file +* --code-signature-flags +* --entitlements-xml-file +* --info-plist-file + +Scoped settings take the form or :. If the 2nd form +is used, the string before the first colon is parsed as a /"scoping string/". +It can have the following values: + +* `main` - Applies to the main entity being signed and all nested entities. +* `@` - e.g. `@0`. Applies to a Mach-O within a fat binary at the + specified index. 0 means the first Mach-O in a fat binary. +* `@[cpu_type=` - e.g. `@[cpu_type=7]`. Applies to a Mach-O within a fat + binary targeting a numbered CPU architecture (using numeric constants + as defined by Mach-O). +* `@[cpu_type=` - e.g. `@[cpu_type=x86_64]`. Applies to a Mach-O within + a fat binary targeting a CPU architecture identified by a string. See below + for the list of recognized values. +* `` - e.g. `path/to/file`. Applies to content at a given path. This + should be the bundle-relative path to a Mach-O binary, a nested bundle, or + a Mach-O binary within a nested bundle. If a nested bundle is referenced, + settings apply to everything within that bundle. +* `@` - e.g. `path/to/file@0`. Applies to a Mach-O within a + fat binary at the given path. If the path is to a bundle, the setting applies + to all Mach-O binaries in that bundle. +* `@[cpu_type=]` e.g. `Contents/MacOS/binary@[cpu_type=7]` + or `Contents/MacOS/binary@[cpu_type=arm64]`. Applies to a Mach-O within a + fat binary targeting a CPU architecture identified by its integer constant + or string name. If the path is to a bundle, the setting applies to all + Mach-O binaries in that bundle. + +The following named CPU architectures are recognized: + +* arm +* arm64 +* arm64_32 +* x86_64 + +Signing will traverse into nested entities: + +* A fat Mach-O binary will traverse into the multiple Mach-O binaries within. +* A bundle will traverse into nested bundles. +* A bundle will traverse non-code "resource" files and sign their digests. +* A bundle will traverse non-main Mach-O binaries and sign them, adding their + metadata to the signed resources file. + +When signing nested entities, only some signing settings will be copied +automatically: + +* All settings related to the signing certificate/key. +* --timestamp-url +* --signing-time +* --exclude +* --digest +* --runtime-version + +All other settings only apply to the main entity being signed or the +scoped path being annotated. + +# Bundle Signing Overrides Settings + +When signing bundles, some settings specified on the command line will be +ignored. This is to ensure that the produced signing data is correct. The +settings ignored include (but may not be limited to): + +* --binary-identifier for the main executable. The `CFBundleIdentifier` value + from the bundle's `Info.plist` will be used instead. +* --code-resources-path. The code resources data will be computed automatically + as part of signing the bundle. +* --info-plist-path. The `Info.plist` from the bundle will be used instead. +* --digest + +# Designated Code Requirements + +When using Apple issued code signing certificates, we will attempt to apply +an appropriate designated requirement automatically during signing which +matches the behavior of what `codesign` would do. We do not yet support all +signing certificates and signing targets for this, however. So you may +need to provide your own requirements. + +Designated code requirements can be specified via --code-requirements-path. + +This file MUST contain a binary/compiled code requirements expression. We do +not (yet) support parsing the human-friendly code requirements DSL. A +binary/compiled file can be produced via Apple's `csreq` tool. e.g. +`csreq -r '=' -b /output/path`. If code requirements data is +specified, it will be parsed and displayed as part of signing to ensure it +is well-formed. + +# Code Signing Key Pair + +By default, the embedded code signature will only contain digests of the +binary and other important entities (such as entitlements and resources). +This is often referred to as /"ad-hoc/" signing. + +To use a code signing key/certificate to derive a cryptographic signature, +you must specify a source certificate to use. This can be done in the following +ways: + +* The --p12-file denotes the location to a PFX formatted file. These are + often .pfx or .p12 files. A password is required to open these files. + Specify one via --p12-password or --p12-password-file or enter a password + when prompted. +* The --pem-file argument defines paths to files containing PEM encoded + certificate/key data. (e.g. files with /"===== BEGIN CERTIFICATE =====/"). +* The --certificate-der-file argument defines paths to files containing DER + encoded certificate/key data. +* The --keychain-domain and --keychain-fingerprint arguments can be used to + load code signing certificates from macOS keychains. These arguments are + ignored on non-macOS platforms. +* The --windows-store-name and --windows-store-cert-fingerprint arguments can be used to + load code signing certificates from the Windows store. These arguments are + ignored on non-Windows platforms. +* The --smartcard-slot argument defines the name of a slot in a connected + smartcard device to read from. `9c` is common. +* Arguments beginning with --remote activate *remote signing mode* and can + be used to delegate cryptographic signing operations to a separate machine. + It is strongly advised to read the user documentation on remote signing + mode at https://gregoryszorc.com/docs/apple-codesign/main/. + +If you export a code signing certificate from the macOS keychain via the +`Keychain Access` application as a .p12 file, we should be able to read these +files via --p12-file. + +When using --pem-file, certificates and public keys are parsed from +`BEGIN CERTIFICATE` and `BEGIN PRIVATE KEY` sections in the files. + +The way certificate discovery works is that --p12-file is read followed by +all values to --pem-file. The seen signing keys and certificates are +collected. After collection, there must be 0 or 1 signing keys present, or +an error occurs. The first encountered public certificate is assigned +to be paired with the signing key. All remaining certificates are assumed +to constitute the CA issuing chain and will be added to the signature +data to facilitate validation. + +If you are using an Apple-issued code signing certificate, we detect this +and automatically register the Apple CA certificate chain so it is included +in the digital signature. This matches the behavior of the `codesign` tool. + +For best results, put your private key and its corresponding X.509 certificate +in a single file, either a PFX or PEM formatted file. Then add any additional +certificates constituting the signing chain in a separate PEM file. + +When using a code signing key/certificate, a Time-Stamp Protocol server URL +can be specified via --timestamp-url. By default, Apple's server is used. The +special value /"none/" can disable using a timestamp server. + +# Selecting What to Sign + +By default, this command attempts to recursively sign everything in the source +path. This applies to: + +* Bundles. If the specified bundle has nested bundles, those nested bundles + will be signed automatically. + +It is possible to exclude nested items from signing using --exclude. This +argument takes a glob expression that matches *relative paths* from the +source path. Glob expressions can be literal string compares. Or the +following special syntax is recognized: + +* `?` matches any single character. +* `*` matches any (possibly empty) sequence of characters. +* `**` matches the current directory and arbitrary subdirectories. This sequence + must form a single path component, so both **a and b** are invalid and will + result in an error. A sequence of more than two consecutive * characters is + also invalid. +* `[...]` matches any character inside the brackets. Character sequences can also + specify ranges of characters, as ordered by Unicode, so e.g. [0-9] specifies any + character between 0 and 9 inclusive. An unclosed bracket is invalid. +* `[!...]` is the negation of `[...]`, i.e. it matches any characters not in the + brackets. +* The metacharacters `?`, `*`, `[`, `]` can be matched by using brackets (e.g. + `[?]`). When a `]` occurs immediately following `[` or `[!` then it is + interpreted as being part of, rather then ending, the character set, so `]` and + `NOT ]` can be matched by `[]]` and `[!]]` respectively. The `-` character can + be specified inside a character sequence pattern by placing it at the start or + the end, e.g. `[abc-]`. + +Currently, --exclude only applies to the relative path of nested bundles within +the main bundle to sign. e.g. if you sign `MyApp.app` and it has a +`Contents/Frameworks/MyFramework.framework` that you wish to exclude, you would +`--exclude Contents/Frameworks/MyFramework.framework` or even +`--exclude Contents/Frameworks/**` to exclude the entire directory tree. + +Exclusions will still be copied and parents that need to reference exclude +entities will continue to do so. If you wish to make a file or directory +disappear, create a new directory without the file(s) and sign that. + +To exclude all nested bundles from being signed and only sign the main bundle +(the default behavior of ``codesign`` without ``--deep``), use `--exclude '**'`. + +Usage: rcodesign[EXE] sign [OPTIONS] [OUTPUT_PATH] + +Arguments: + + Path to Mach-O binary to sign + + [OUTPUT_PATH] + Path to signed Mach-O binary to write + +Options: + --binary-identifier + Identifier string for binary. The value normally used by CFBundleIdentifier + + -C, --config-file + Explicit configuration file to load. + + If provided, the default configuration files are not loaded, even if they exist. + + Can be specified multiple times. Files are loaded/merged in the order given. + + The special value `/dev/null` can be used to specify an empty/null config file. It can be used to short-circuit loading of default config files. + + --code-requirements-file + Path to a file containing binary code requirements data to be used as designated requirements + + -P, --profile + Configuration profile to load. + + If not specified, the implicit "default" profile is loaded. + + --code-resources-file + Path to an XML plist file containing code resources + + -v, --verbose... + Increase logging verbosity. Can be specified multiple times + + --code-signature-flags + Code signature flags to set. + + Valid values: host, hard, kill, expires, library, runtime, linker-signed + + --digest + Digest algorithms to use. + + This typically doesn't need to be set since the OS targeting information from signed binaries implicitly derives appropriate digests to sign with. + + However, there are special cases where you may want to force use of specific digests. + + The first provided value will become the "primary" digest. Subsequent values will become alternative digests. The "primary" digest should be "older" to ensure compatibility with older clients. + + When targeting older Apple OS versions, SHA-1 should be the primary digest and SHA-256 should also be present for compatibility with newer OS versions. + + When targeting new OS versions, it is sufficient to only provide SHA-256 digests. + + The following values are accepted: none, sha1, sha256, sha384, sha512. + + Important: only "sha1" and "sha256" are widely used and use of other algorithms may cause problems. + + -e, --entitlements-xml-file + Path to a plist file containing entitlements + + --launch-constraints-self-file + Launch constraints on the current executable. + + Specify the path to a plist XML file defining launch constraints. + + --launch-constraints-parent-file + Launch constraints on the parent process. + + Specify the path to a plist XML file defining launch constraints. + + --launch-constraints-responsible-file + Launch constraints on the responsible process. + + Specify the path to a plist XML file defining launch constraints. + + --library-constraints-file + Constraints on loaded libraries. + + Specify the path to a plist XML file defining launch constraints. + + --runtime-version + Hardened runtime version to use (defaults to SDK version used to build binary) + + --info-plist-file + Path to an Info.plist file whose digest to include in Mach-O signature + + --team-name + Team name/identifier to include in code signature + + --signing-time + An RFC 3339 date and time string to be used in signatures. + + e.g. 2023-11-05T10:42:00Z. + + If not specified, the current time will be used. + + Setting is only used when signing with a signing certificate. + + This setting is typically not necessary. It was added to facilitate deterministic signing behavior. + + --timestamp-url + URL of time-stamp server to use to obtain a token of the CMS signature + + Can be set to the special value `none` to disable the generation of time-stamp tokens and use of a time-stamp server. + + [default: http://timestamp.apple.com/ts01] + + --exclude + Glob expression of paths to exclude from signing + + --shallow + Do not traverse into nested entities when signing. + + Some signable entities (like directory bundles) have child/nested entities that can be signed. By default, signing traversed into these entities and signs all entities recursively. + + Activating shallow signing mode using this flag overrides the default behavior. + + The behavior of this flag is subject to change. As currently implemented it will: + + * Prevent signing nested bundles when signing a bundle. e.g. if an app bundle contains a framework, only the app bundle will be signed. Additional Mach-O binaries within a bundle may still be signed with this flag set. + + Activating shallow signing mode can result in signing failures if the skipped nested entities aren't signed. For example, when signing an application bundle containing an unsigned nested bundle/framework, signing will fail with an error about a missing code signature. Always be sure to sign nested entities before their parents when this mode is activated. + + --for-notarization + Indicate that the entity being signed will later be notarized. + + Notarized software is subject to specific requirements, such as enabling the hardened runtime. + + The presence of this flag influences signing settings and engages additional checks to help ensure that signed software can be successfully notarized. + + This flag is best effort. Notarization failures of software signed with this flag may be indicative of bugs in this software. + + The behavior of this flag is subject to change. As currently implemented, it will: + + * Require the use of a "Developer ID" signing certificate issued by Apple. * Require the use of a time-stamp server. * Enable the hardened runtime code signature flag on all Mach-O binaries (equivalent to `--code-signature-flags runtime` for all signed paths). + + --smartcard-slot + Smartcard slot number of signing certificate to use (9c is common) + + --smartcard-pin + Smartcard PIN used to unlock certificate + + If not provided, you will be prompted for a PIN as necessary. + + --smartcard-pin-env + Environment variable holding the smartcard PIN + + --keychain-domain + (macOS only) Keychain domain to operate on + + [possible values: user, system, common, dynamic] + + --keychain-fingerprint + (macOS only) SHA-256 fingerprint of certificate in Keychain to use + + --windows-store-name + (Windows only) Windows Store to operate on + + [possible values: user, machine, service] + + --windows-store-sha1-fingerprint + (Windows only) SHA-1 fingerprint of certificate in Windows Store to use + + --pem-file + Path to file containing PEM encoded certificate/key data + + --p12-file + Path to a .p12/PFX file containing a certificate key pair + + --p12-password + The password to use to open the --p12-file file + + --p12-password-file + Path to file containing password for opening --p12-file file + + --remote-signing-url + URL of a remote code signing server + + --remote-public-key + Base64 encoded public key data describing the signer + + --remote-public-key-pem-file + PEM encoded public key data describing the signer + + --remote-shared-secret + Shared secret used for remote signing + + --remote-shared-secret-env + Environment variable holding the shared secret used for remote signing + + --certificate-der-file + Path to file containing DER encoded certificate data + + -h, --help + Print help (see a summary with '-h') + +``` + +An ad-hoc signature over a minimal Mach-O works. + +``` +$ rcodesign debug-create-macho exe +assuming default minimum version 11.0.0 +writing Mach-O to exe + +$ rcodesign sign exe exe.signed +signing exe to exe.signed +signing exe as a Mach-O binary +setting binary identifier to exe +parsing Mach-O +writing Mach-O to exe.signed + +$ rcodesign print-signature-info exe.signed +- path: exe.signed + file_size: 22544 + file_sha256: 2adcd25a21eb14fc3f7b5ca4f5465b515f21939dd9843de5bf7d9e3f7acfa9db + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16772 / 0x4184 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 388 / 0x184 + linkedit_bytes_after_signature: 5772 / 0x168c + signature: + superblob_length: 372 / 0x174 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 316 + sha1: 4ca6f9ee2bfe2bfac44ab4e9e9c1ef9b6e4fc0de + sha256: 23fc7207e52f23c0f6d2317dbb92cf9eff2aca8fe61ac241900d48be8f46cf5c + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: exe + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null + +$ rcodesign sign exe.signed exe.signed.2 +signing exe.signed to exe.signed.2 +signing exe.signed as a Mach-O binary +setting binary identifier to exe +parsing Mach-O +writing Mach-O to exe.signed.2 + +$ rcodesign diff-signatures exe.signed exe.signed.2 +-- path: exe.signed ++- path: exe.signed.2 + file_size: 22544 + file_sha256: 2adcd25a21eb14fc3f7b5ca4f5465b515f21939dd9843de5bf7d9e3f7acfa9db + entity: + mach_o: + macho_linkedit_start_offset: 16384 / 0x4000 + macho_signature_start_offset: 16400 / 0x4010 + macho_signature_end_offset: 16772 / 0x4184 + macho_linkedit_end_offset: 22544 / 0x5810 + macho_end_offset: 22544 / 0x5810 + linkedit_signature_start_offset: 16 / 0x10 + linkedit_signature_end_offset: 388 / 0x184 + linkedit_bytes_after_signature: 5772 / 0x168c + signature: + superblob_length: 372 / 0x174 + blob_count: 3 + blobs: + - slot: CodeDirectory (0) + magic: fade0c02 + length: 316 + sha1: 4ca6f9ee2bfe2bfac44ab4e9e9c1ef9b6e4fc0de + sha256: 23fc7207e52f23c0f6d2317dbb92cf9eff2aca8fe61ac241900d48be8f46cf5c + - slot: RequirementSet (2) + magic: fade0c01 + length: 12 + sha1: 3a75f6db058529148e14dd7ea1b4729cc09ec973 + sha256: 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986 + - slot: CMS Signature (65536) + magic: fade0b01 + length: 8 + sha1: 2a7254313aa41796079bb0e9d0f044345f69f98b + sha256: e6c83bc98a10348492c7d4d2378a54572ef29e1a5692ccd02b5e29f4b762d6a0 + code_directory: + version: '0x20400' + flags: CodeSignatureFlags(ADHOC) + identifier: exe + digest_type: sha256 + platform: 0 + signed_entity_size: 16400 + executable_segment_flags: ExecutableSegmentFlags(MAIN_BINARY) + code_digests_count: 5 + slot_digests: + - 'Info (1): 0000000000000000000000000000000000000000000000000000000000000000' + - 'RequirementSet (2): 987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986' + cms: null + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/smartcard-generate-key.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/smartcard-generate-key.trycmd new file mode 100644 index 00000000..dac25627 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/smartcard-generate-key.trycmd @@ -0,0 +1,43 @@ +``` +$ rcodesign help smartcard-generate-key +Generate a new private key on a smartcard + +Usage: rcodesign[EXE] smartcard-generate-key [OPTIONS] --smartcard-slot + +Options: + -C, --config-file + Explicit configuration file to load. + + If provided, the default configuration files are not loaded, even if they exist. + + Can be specified multiple times. Files are loaded/merged in the order given. + + The special value `/dev/null` can be used to specify an empty/null config file. It can be used to short-circuit loading of default config files. + + --smartcard-slot + Smartcard slot number to store key in (9c is common) + + -P, --profile + Configuration profile to load. + + If not specified, the implicit "default" profile is loaded. + + --touch-policy + Smartcard touch policy to protect key access + + [default: default] + [possible values: default, always, never, cached] + + --pin-policy + Smartcard pin prompt policy to protect key access + + [default: default] + [possible values: default, never, once, always] + + -v, --verbose... + Increase logging verbosity. Can be specified multiple times + + -h, --help + Print help (see a summary with '-h') + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/smartcard-import.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/smartcard-import.trycmd new file mode 100644 index 00000000..7f47cbb4 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/smartcard-import.trycmd @@ -0,0 +1,103 @@ +``` +$ rcodesign help smartcard-import +Import a code signing certificate and key into a smartcard + +Usage: rcodesign[EXE] smartcard-import [OPTIONS] + +Options: + -C, --config-file + Explicit configuration file to load. + + If provided, the default configuration files are not loaded, even if they exist. + + Can be specified multiple times. Files are loaded/merged in the order given. + + The special value `/dev/null` can be used to specify an empty/null config file. It can be used to short-circuit loading of default config files. + + --existing-key + Re-use the existing private key in the smartcard slot + + --dry-run + Don't actually perform the import + + -P, --profile + Configuration profile to load. + + If not specified, the implicit "default" profile is loaded. + + --smartcard-slot + Smartcard slot number of signing certificate to use (9c is common) + + -v, --verbose... + Increase logging verbosity. Can be specified multiple times + + --smartcard-pin + Smartcard PIN used to unlock certificate + + If not provided, you will be prompted for a PIN as necessary. + + --smartcard-pin-env + Environment variable holding the smartcard PIN + + --keychain-domain + (macOS only) Keychain domain to operate on + + [possible values: user, system, common, dynamic] + + --keychain-fingerprint + (macOS only) SHA-256 fingerprint of certificate in Keychain to use + + --windows-store-name + (Windows only) Windows Store to operate on + + [possible values: user, machine, service] + + --windows-store-sha1-fingerprint + (Windows only) SHA-1 fingerprint of certificate in Windows Store to use + + --pem-file + Path to file containing PEM encoded certificate/key data + + --p12-file + Path to a .p12/PFX file containing a certificate key pair + + --p12-password + The password to use to open the --p12-file file + + --p12-password-file + Path to file containing password for opening --p12-file file + + --remote-signing-url + URL of a remote code signing server + + --remote-public-key + Base64 encoded public key data describing the signer + + --remote-public-key-pem-file + PEM encoded public key data describing the signer + + --remote-shared-secret + Shared secret used for remote signing + + --remote-shared-secret-env + Environment variable holding the shared secret used for remote signing + + --certificate-der-file + Path to file containing DER encoded certificate data + + --touch-policy + Smartcard touch policy to protect key access + + [default: default] + [possible values: default, always, never, cached] + + --pin-policy + Smartcard pin prompt policy to protect key access + + [default: default] + [possible values: default, never, once, always] + + -h, --help + Print help (see a summary with '-h') + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/smartcard-scan.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/smartcard-scan.trycmd new file mode 100644 index 00000000..6fb74cdf --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/smartcard-scan.trycmd @@ -0,0 +1,28 @@ +``` +$ rcodesign help smartcard-scan +Show information about available smartcard (SC) devices + +Usage: rcodesign[EXE] smartcard-scan [OPTIONS] + +Options: + -C, --config-file + Explicit configuration file to load. + + If provided, the default configuration files are not loaded, even if they exist. + + Can be specified multiple times. Files are loaded/merged in the order given. + + The special value `/dev/null` can be used to specify an empty/null config file. It can be used to short-circuit loading of default config files. + + -P, --profile + Configuration profile to load. + + If not specified, the implicit "default" profile is loaded. + + -v, --verbose... + Increase logging verbosity. Can be specified multiple times + + -h, --help + Print help (see a summary with '-h') + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/staple.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/staple.trycmd new file mode 100644 index 00000000..8f4052e9 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/staple.trycmd @@ -0,0 +1,32 @@ +``` +$ rcodesign help staple +Staples a notarization ticket to an entity + +Usage: rcodesign[EXE] staple [OPTIONS] + +Arguments: + + Path to entity to attempt to staple + +Options: + -C, --config-file + Explicit configuration file to load. + + If provided, the default configuration files are not loaded, even if they exist. + + Can be specified multiple times. Files are loaded/merged in the order given. + + The special value `/dev/null` can be used to specify an empty/null config file. It can be used to short-circuit loading of default config files. + + -P, --profile + Configuration profile to load. + + If not specified, the implicit "default" profile is loaded. + + -v, --verbose... + Increase logging verbosity. Can be specified multiple times + + -h, --help + Print help (see a summary with '-h') + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/verify.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/verify.trycmd new file mode 100644 index 00000000..89ed40e8 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/verify.trycmd @@ -0,0 +1,32 @@ +``` +$ rcodesign help verify +Verifies code signature data + +Usage: rcodesign[EXE] verify [OPTIONS] + +Arguments: + + Path of Mach-O binary to examine + +Options: + -C, --config-file + Explicit configuration file to load. + + If provided, the default configuration files are not loaded, even if they exist. + + Can be specified multiple times. Files are loaded/merged in the order given. + + The special value `/dev/null` can be used to specify an empty/null config file. It can be used to short-circuit loading of default config files. + + -P, --profile + Configuration profile to load. + + If not specified, the implicit "default" profile is loaded. + + -v, --verbose... + Increase logging verbosity. Can be specified multiple times + + -h, --help + Print help (see a summary with '-h') + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/windows-store-export-certificate-chain.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/windows-store-export-certificate-chain.trycmd new file mode 100644 index 00000000..be6c712a --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/windows-store-export-certificate-chain.trycmd @@ -0,0 +1,40 @@ +``` +$ rcodesign help windows-store-export-certificate-chain +Export CA certificates from the Windows Store + +Usage: rcodesign[EXE] windows-store-export-certificate-chain [OPTIONS] --thumbprint + +Options: + -C, --config-file + Explicit configuration file to load. + + If provided, the default configuration files are not loaded, even if they exist. + + Can be specified multiple times. Files are loaded/merged in the order given. + + The special value `/dev/null` can be used to specify an empty/null config file. It can be used to short-circuit loading of default config files. + + --windows-store-name + Windows Store to operate on + + [default: user] + [possible values: user, machine, service] + + --no-print-self + Print only the issuing certificate chain, not the subject certificate + + -P, --profile + Configuration profile to load. + + If not specified, the implicit "default" profile is loaded. + + --thumbprint + SHA-1 thumbprint of code signing certificate to find and whose CA chain to export + + -v, --verbose... + Increase logging verbosity. Can be specified multiple times + + -h, --help + Print help (see a summary with '-h') + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/windows-store-print-certificates.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/windows-store-print-certificates.trycmd new file mode 100644 index 00000000..d2b34310 --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/windows-store-print-certificates.trycmd @@ -0,0 +1,34 @@ +``` +$ rcodesign help windows-store-print-certificates +Print information about certificates in the Windows Store + +Usage: rcodesign[EXE] windows-store-print-certificates [OPTIONS] + +Options: + -C, --config-file + Explicit configuration file to load. + + If provided, the default configuration files are not loaded, even if they exist. + + Can be specified multiple times. Files are loaded/merged in the order given. + + The special value `/dev/null` can be used to specify an empty/null config file. It can be used to short-circuit loading of default config files. + + --windows-store-name + Windows Store name to operate on + + [default: user] + [possible values: user, machine, service] + + -P, --profile + Configuration profile to load. + + If not specified, the implicit "default" profile is loaded. + + -v, --verbose... + Increase logging verbosity. Can be specified multiple times + + -h, --help + Print help (see a summary with '-h') + +``` diff --git a/3rdparty/apple-codesign-0.29.0/tests/cmd/x509-oids.trycmd b/3rdparty/apple-codesign-0.29.0/tests/cmd/x509-oids.trycmd new file mode 100644 index 00000000..884d446f --- /dev/null +++ b/3rdparty/apple-codesign-0.29.0/tests/cmd/x509-oids.trycmd @@ -0,0 +1,43 @@ +``` +$ rcodesign x509-oids +# Extended Key Usage (EKU) Extension OIDs + +1.3.6.1.5.5.7.3.3 CodeSigning +1.2.840.113635.100.4.8 SafariDeveloper +1.2.840.113635.100.4.9 ThirdPartyMacDeveloperInstaller +1.2.840.113635.100.4.13 DeveloperIdInstaller + +# Code Signing Certificate Extension OIDs + +1.2.840.113635.100.6.1.1 AppleSigning +1.2.840.113635.100.6.1.2 IPhoneDeveloper +1.2.840.113635.100.6.1.3 IPhoneOsApplicationSigning +1.2.840.113635.100.6.1.4 AppleDeveloperCertificateSubmission +1.2.840.113635.100.6.1.5 SafariDeveloper +1.2.840.113635.100.6.1.6 IPhoneOsVpnSigning +1.2.840.113635.100.6.1.7 AppleMacAppSigningDevelopment +1.2.840.113635.100.6.1.8 AppleMacAppSigningSubmission +1.2.840.113635.100.6.1.9 AppleMacAppStoreCodeSigning +1.2.840.113635.100.6.1.10 AppleMacAppStoreInstallerSigning +1.2.840.113635.100.6.1.12 MacDeveloper +1.2.840.113635.100.6.1.13 DeveloperIdApplication +1.2.840.113635.100.6.1.33 DeveloperIdDate +1.2.840.113635.100.6.1.14 DeveloperIdInstaller +1.2.840.113635.100.6.1.16 ApplePayPassbookSigning +1.2.840.113635.100.6.1.17 WebsitePushNotificationSigning +1.2.840.113635.100.6.1.18 DeveloperIdKernel +1.2.840.113635.100.6.1.25.1 TestFlight + +# Certificate Authority Certificate Extension OIDs + +1.2.840.113635.100.6.2.1 AppleWorldwideDeveloperRelations +1.2.840.113635.100.6.2.3 AppleApplicationIntegration +1.2.840.113635.100.6.2.6 DeveloperId +1.2.840.113635.100.6.2.9 AppleTimestamp +1.2.840.113635.100.6.2.11 DeveloperAuthentication +1.2.840.113635.100.6.2.14 AppleApplicationIntegrationG3 +1.2.840.113635.100.6.2.15 AppleWorldwideDeveloperRelationsG2 +1.2.840.113635.100.6.2.19 AppleSoftwareUpdateCertification +1.2.840.113635.100.6.2.31 AppleApplicationIntegrationG1 + +``` diff --git a/Cargo.lock b/Cargo.lock index 8ed51a19..c67def6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -233,8 +233,6 @@ dependencies = [ [[package]] name = "apple-codesign" version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f24e9ebdb70a2aee3ca1cea217009fb50776955f0d7678c31d22e48c1524667f" dependencies = [ "anyhow", "apple-bundles", diff --git a/Cargo.toml b/Cargo.toml index 6e77b6ff..d96cbedd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,4 +73,7 @@ reqwest = { version = "0.13", default-features = false, features = [ ] } chrono = { version = "0.4", default-features = false, features = ["std", "serde"] } serde = { version = "1", features = ["derive"] } -serde_json = { version = "1" } \ No newline at end of file +serde_json = { version = "1" } + +[patch.crates-io] +apple-codesign = { path = "3rdparty/apple-codesign-0.29.0" } \ No newline at end of file