From d61762e77616401f56b14bb4f57a6ba49c5504a3 Mon Sep 17 00:00:00 2001 From: C Date: Sat, 19 Sep 2026 08:07:51 -0700 Subject: [PATCH 1/7] try and fix this shit idk --- .cargo/rustc-wrapper.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.cargo/rustc-wrapper.sh b/.cargo/rustc-wrapper.sh index a6265ada..31771407 100755 --- a/.cargo/rustc-wrapper.sh +++ b/.cargo/rustc-wrapper.sh @@ -2,7 +2,7 @@ # 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 9>&1 10>&2 exec >>"$LOG_FILE" 2>&1 set -eu @@ -14,7 +14,7 @@ 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 1>&9 2>&10 exec "$@" fi @@ -49,9 +49,9 @@ if [ -d "$PATCH_DIR" ]; then new_args+=("${arg//$CARGO_MANIFEST_DIR/$PATCHED_SRC}") done - exec 1>&3 2>&4 + exec 1>&9 2>&10 exec "${new_args[@]}" else - exec 1>&3 2>&4 + exec 1>&9 2>&10 exec "$@" fi From 88f60ce51e27fb620beabbf787bbedcb51b08ab1 Mon Sep 17 00:00:00 2001 From: C Date: Sat, 19 Sep 2026 08:28:38 -0700 Subject: [PATCH 2/7] this build system sucks --- .cargo/rustc-wrapper.bat | 2 ++ .github/workflows/release.yml | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 .cargo/rustc-wrapper.bat diff --git a/.cargo/rustc-wrapper.bat b/.cargo/rustc-wrapper.bat new file mode 100644 index 00000000..8f8b751d --- /dev/null +++ b/.cargo/rustc-wrapper.bat @@ -0,0 +1,2 @@ +@echo off +bash "%~dp0rustc-wrapper.sh" %* \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index abcb28fe..fa6e307a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -66,14 +66,25 @@ 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' + env: + CARGO_BUILD_RUSTC_WRAPPER: "./.cargo/rustc-wrapper.bat" + 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: From e3cf94ef3de1e24f5998012dbcc4173918b35678 Mon Sep 17 00:00:00 2001 From: C Date: Sat, 19 Sep 2026 16:07:05 -0700 Subject: [PATCH 3/7] idk --- .cargo/rustc-wrapper.bat | 2 -- .cargo/rustc-wrapper.rs | 13 +++++++++++++ .github/workflows/release.yml | 3 ++- .gitignore | 1 + 4 files changed, 16 insertions(+), 3 deletions(-) delete mode 100644 .cargo/rustc-wrapper.bat create mode 100644 .cargo/rustc-wrapper.rs diff --git a/.cargo/rustc-wrapper.bat b/.cargo/rustc-wrapper.bat deleted file mode 100644 index 8f8b751d..00000000 --- a/.cargo/rustc-wrapper.bat +++ /dev/null @@ -1,2 +0,0 @@ -@echo off -bash "%~dp0rustc-wrapper.sh" %* \ No newline at end of file diff --git a/.cargo/rustc-wrapper.rs b/.cargo/rustc-wrapper.rs new file mode 100644 index 00000000..382f1ff7 --- /dev/null +++ b/.cargo/rustc-wrapper.rs @@ -0,0 +1,13 @@ +use std::env; +use std::process::{exit, Command}; + +fn main() { + let args: Vec = env::args().skip(1).collect(); + let status = Command::new("bash") + .arg("./.cargo/rustc-wrapper.sh") + .args(&args) + .status() + .expect("Failed to execute Git Bash wrapper"); + + exit(status.code().unwrap_or(1)); +} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fa6e307a..2c987613 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -77,9 +77,10 @@ jobs: - name: Build binaries (Windows ARM64 native) if: matrix.arch == 'arm64' env: - CARGO_BUILD_RUSTC_WRAPPER: "./.cargo/rustc-wrapper.bat" + CARGO_BUILD_RUSTC_WRAPPER: "./.cargo/rustc-wrapper.exe" shell: pwsh run: | + rustc ./.cargo/rustc-wrapper.rs -o ./.cargo/rustc-wrapper.exe 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" 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 From 0544ce8d79d92cea934841ccb7f02206babbeaae Mon Sep 17 00:00:00 2001 From: C Date: Sat, 19 Sep 2026 16:09:42 -0700 Subject: [PATCH 4/7] idk2 --- .cargo/rustc-wrapper.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/.cargo/rustc-wrapper.rs b/.cargo/rustc-wrapper.rs index 382f1ff7..61153315 100644 --- a/.cargo/rustc-wrapper.rs +++ b/.cargo/rustc-wrapper.rs @@ -1,9 +1,35 @@ use std::env; +use std::path::PathBuf; use std::process::{exit, Command}; +fn find_git_bash() -> PathBuf { + let candidates = [ + r"C:\Program Files\Git\bin\bash.exe", + r"C:\Program Files\Git\usr\bin\bash.exe", + r"C:\Program Files (x86)\Git\bin\bash.exe", + ]; + + for path in &candidates { + let p = PathBuf::from(path); + if p.exists() { + return p; + } + } + + if let Ok(program_files) = env::var("ProgramFiles") { + let p = PathBuf::from(program_files).join(r"Git\bin\bash.exe"); + if p.exists() { + return p; + } + } + + PathBuf::from("bash") +} + fn main() { + let bash_path = find_git_bash(); let args: Vec = env::args().skip(1).collect(); - let status = Command::new("bash") + let status = Command::new(bash_path) .arg("./.cargo/rustc-wrapper.sh") .args(&args) .status() From 08ebe33e493dbfed453c161984be1794cb2f0457 Mon Sep 17 00:00:00 2001 From: C Date: Sat, 19 Sep 2026 16:15:17 -0700 Subject: [PATCH 5/7] IDK --- .cargo/rustc-wrapper.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.cargo/rustc-wrapper.rs b/.cargo/rustc-wrapper.rs index 61153315..0a602d19 100644 --- a/.cargo/rustc-wrapper.rs +++ b/.cargo/rustc-wrapper.rs @@ -26,11 +26,24 @@ fn find_git_bash() -> PathBuf { PathBuf::from("bash") } +fn get_script_path() -> PathBuf { + if let Ok(mut exe_path) = env::current_exe() { + exe_path.pop(); + let script = exe_path.join("rustc-wrapper.sh"); + if script.exists() { + return script; + } + } + PathBuf::from("./.cargo/rustc-wrapper.sh") +} + fn main() { let bash_path = find_git_bash(); + let script_path = get_script_path(); let args: Vec = env::args().skip(1).collect(); + let status = Command::new(bash_path) - .arg("./.cargo/rustc-wrapper.sh") + .arg(script_path) .args(&args) .status() .expect("Failed to execute Git Bash wrapper"); From e86b6585ca31dcb8d726ab89ed51d68562585c44 Mon Sep 17 00:00:00 2001 From: C Date: Sat, 19 Sep 2026 16:23:01 -0700 Subject: [PATCH 6/7] what am I doing with my life --- .cargo/rustc-wrapper.rs | 116 +++++++++++++++++++++++++++------------- 1 file changed, 80 insertions(+), 36 deletions(-) diff --git a/.cargo/rustc-wrapper.rs b/.cargo/rustc-wrapper.rs index 0a602d19..a0e81a74 100644 --- a/.cargo/rustc-wrapper.rs +++ b/.cargo/rustc-wrapper.rs @@ -1,52 +1,96 @@ use std::env; -use std::path::PathBuf; +use std::fs; +use std::path::{Path, PathBuf}; use std::process::{exit, Command}; -fn find_git_bash() -> PathBuf { - let candidates = [ - r"C:\Program Files\Git\bin\bash.exe", - r"C:\Program Files\Git\usr\bin\bash.exe", - r"C:\Program Files (x86)\Git\bin\bash.exe", - ]; - - for path in &candidates { - let p = PathBuf::from(path); - if p.exists() { - return p; +fn copy_dir_all(src: &Path, dst: &Path) -> std::io::Result<()> { + fs::create_dir_all(dst)?; + for entry in fs::read_dir(src)? { + let entry = entry?; + let ty = entry.file_type()?; + if ty.is_dir() { + copy_dir_all(&entry.path(), &dst.join(entry.file_name()))?; + } else { + fs::copy(entry.path(), dst.join(entry.file_name()))?; } } + Ok(()) +} - if let Ok(program_files) = env::var("ProgramFiles") { - let p = PathBuf::from(program_files).join(r"Git\bin\bash.exe"); - if p.exists() { - return p; - } +fn main() { + let args: Vec = env::args().skip(1).collect(); + if args.is_empty() { + exit(1); } - PathBuf::from("bash") -} + let rustc_bin = &args[0]; + let rustc_args = &args[1..]; + + let cargo_pkg_name = env::var("CARGO_PKG_NAME").ok(); + let cargo_manifest_dir = env::var("CARGO_MANIFEST_DIR").ok(); + + if let (Some(pkg_name), Some(manifest_dir)) = (cargo_pkg_name, cargo_manifest_dir) { + let manifest_path = Path::new(&manifest_dir); + if let Some(dir_name) = manifest_path.file_name().and_then(|s| s.to_str()) { + let root_dir = env::current_exe() + .ok() + .and_then(|p| p.parent().map(|p| p.to_path_buf())) + .and_then(|p| p.parent().map(|p| p.to_path_buf())) + .unwrap_or_else(|| PathBuf::from(".")); + + let patch_dir = root_dir.join("patches").join(dir_name); + + if patch_dir.exists() && patch_dir.is_dir() { + let patched_target = root_dir + .join("target") + .join("patched-crates") + .join(dir_name); + + let _ = fs::create_dir_all(patched_target.parent().unwrap()); + let _ = fs::remove_dir_all(&patched_target); -fn get_script_path() -> PathBuf { - if let Ok(mut exe_path) = env::current_exe() { - exe_path.pop(); - let script = exe_path.join("rustc-wrapper.sh"); - if script.exists() { - return script; + if let Err(e) = copy_dir_all(manifest_path, &patched_target) { + eprintln!("Failed to copy source dir for patching: {e}"); + } + + if let Ok(entries) = fs::read_dir(&patch_dir) { + for entry in entries.flatten() { + let path = entry.path(); + if path.is_file() + && path.extension().and_then(|s| s.to_str()) == Some("patch") + { + println!("Applying patch to {pkg_name}: {}", path.display()); + let _ = Command::new("git") + .arg("apply") + .arg(format!("--directory={}", patched_target.display())) + .arg(&path) + .status(); + } + } + } + + let manifest_str = manifest_path.to_string_lossy().to_string(); + let patched_str = patched_target.to_string_lossy().to_string(); + + let new_args: Vec = rustc_args + .iter() + .map(|arg| arg.replace(&manifest_str, &patched_str)) + .collect(); + + let status = Command::new(rustc_bin) + .args(&new_args) + .status() + .expect("Failed to execute rustc"); + + exit(status.code().unwrap_or(1)); + } } } - PathBuf::from("./.cargo/rustc-wrapper.sh") -} - -fn main() { - let bash_path = find_git_bash(); - let script_path = get_script_path(); - let args: Vec = env::args().skip(1).collect(); - let status = Command::new(bash_path) - .arg(script_path) - .args(&args) + let status = Command::new(rustc_bin) + .args(rustc_args) .status() - .expect("Failed to execute Git Bash wrapper"); + .expect("Failed to execute rustc"); exit(status.code().unwrap_or(1)); } From f1adce44c51ffac3d8fdf9300113bd531b80ddd5 Mon Sep 17 00:00:00 2001 From: C Date: Sat, 19 Sep 2026 16:34:58 -0700 Subject: [PATCH 7/7] idc anymore --- .cargo/config.toml | 3 - .cargo/rustc-wrapper.rs | 96 - .cargo/rustc-wrapper.sh | 57 - .github/workflows/release.yml | 3 - 3rdparty/apple-codesign-0.29.0/.cargo-ok | 1 + .../.cargo_vcs_info.json | 6 + 3rdparty/apple-codesign-0.29.0/CHANGELOG.md | 765 +++ 3rdparty/apple-codesign-0.29.0/Cargo.lock | 4884 +++++++++++++++++ 3rdparty/apple-codesign-0.29.0/Cargo.toml | 365 ++ .../apple-codesign-0.29.0/Cargo.toml.orig | 131 + 3rdparty/apple-codesign-0.29.0/LICENSE | 373 ++ 3rdparty/apple-codesign-0.29.0/README.md | 39 + 3rdparty/apple-codesign-0.29.0/docs/Makefile | 20 + .../docs/apple_codesign.rst | 70 + ...pple_codesign_actions_initiator_output.png | Bin 0 -> 75422 bytes .../apple_codesign_actions_signer_output.png | Bin 0 -> 57410 bytes .../docs/apple_codesign_actions_sjs_join.png | Bin 0 -> 59881 bytes .../apple_codesign_certificate_management.rst | 255 + .../docs/apple_codesign_concepts.rst | 158 + ...le_codesign_custom_assessment_policies.rst | 160 + .../docs/apple_codesign_debugging.rst | 49 + .../docs/apple_codesign_developer_guide.rst | 46 + .../docs/apple_codesign_gatekeeper.rst | 151 + .../docs/apple_codesign_getting_started.rst | 117 + .../docs/apple_codesign_github_actions.rst | 125 + .../docs/apple_codesign_quirks.rst | 157 + .../docs/apple_codesign_rcodesign.rst | 16 + .../apple_codesign_rcodesign_config_files.rst | 435 ++ .../apple_codesign_rcodesign_notarizing.rst | 189 + .../docs/apple_codesign_rcodesign_signing.rst | 55 + .../docs/apple_codesign_remote_signing.rst | 352 ++ .../apple_codesign_remote_signing_design.rst | 195 + ...apple_codesign_remote_signing_protocol.rst | 836 +++ .../docs/apple_codesign_settings_scope.rst | 58 + .../docs/apple_codesign_smartcard.rst | 164 + 3rdparty/apple-codesign-0.29.0/docs/conf.py | 34 + 3rdparty/apple-codesign-0.29.0/docs/index.rst | 8 + .../src/apple-certs/AppleAAI2CA.cer | Bin 0 -> 1052 bytes .../src/apple-certs/AppleAAICA.cer | Bin 0 -> 1489 bytes .../src/apple-certs/AppleAAICAG3.cer | Bin 0 -> 754 bytes .../AppleApplicationIntegrationCA5G1.cer | Bin 0 -> 765 bytes .../AppleApplicationIntegrationCA7G1.cer | Bin 0 -> 800 bytes .../AppleComputerRootCertificate.cer | Bin 0 -> 1470 bytes .../src/apple-certs/AppleISTCA2G1.cer | Bin 0 -> 1092 bytes .../src/apple-certs/AppleISTCA8G1.cer | Bin 0 -> 1216 bytes .../apple-certs/AppleIncRootCertificate.cer | Bin 0 -> 1215 bytes .../src/apple-certs/AppleRootCA-G2.cer | Bin 0 -> 1430 bytes .../src/apple-certs/AppleRootCA-G3.cer | Bin 0 -> 583 bytes ...leSoftwareUpdateCertificationAuthority.cer | Bin 0 -> 1136 bytes .../src/apple-certs/AppleTimestampCA.cer | Bin 0 -> 1456 bytes .../src/apple-certs/AppleWWDRCA.cer | Bin 0 -> 1062 bytes .../src/apple-certs/AppleWWDRCAG2.cer | Bin 0 -> 763 bytes .../src/apple-certs/AppleWWDRCAG3.cer | Bin 0 -> 1109 bytes .../src/apple-certs/AppleWWDRCAG4.cer | Bin 0 -> 1113 bytes .../src/apple-certs/AppleWWDRCAG5.cer | Bin 0 -> 1113 bytes .../src/apple-certs/AppleWWDRCAG6.cer | Bin 0 -> 794 bytes .../src/apple-certs/AppleWWDRCAG7.cer | Bin 0 -> 1113 bytes .../src/apple-certs/AppleWWDRCAG8.cer | Bin 0 -> 1113 bytes .../src/apple-certs/DevAuthCA.cer | Bin 0 -> 1051 bytes .../src/apple-certs/DeveloperIDCA.cer | Bin 0 -> 1032 bytes .../src/apple-certs/DeveloperIDG2CA.cer | Bin 0 -> 1090 bytes .../src/apple-codesign-testuser.p12 | Bin 0 -> 2535 bytes .../src/apple_certificates.rs | 613 +++ .../src/bundle_signing.rs | 786 +++ .../apple-codesign-0.29.0/src/certificate.rs | 1824 ++++++ .../src/cli/certificate_source.rs | 706 +++ .../apple-codesign-0.29.0/src/cli/config.rs | 450 ++ .../src/cli/debug_commands.rs | 406 ++ .../src/cli/extract_commands.rs | 652 +++ 3rdparty/apple-codesign-0.29.0/src/cli/mod.rs | 2547 +++++++++ .../src/code_directory.rs | 798 +++ .../src/code_requirement.rs | 2050 +++++++ .../src/code_resources.rs | 1577 ++++++ .../apple-codesign-0.29.0/src/cryptography.rs | 1027 ++++ 3rdparty/apple-codesign-0.29.0/src/dmg.rs | 418 ++ .../src/embedded_signature.rs | 1537 ++++++ .../src/embedded_signature_builder.rs | 363 ++ .../apple-codesign-0.29.0/src/entitlements.rs | 53 + .../src/environment_constraints.rs | 188 + 3rdparty/apple-codesign-0.29.0/src/error.rs | 403 ++ 3rdparty/apple-codesign-0.29.0/src/lib.rs | 169 + 3rdparty/apple-codesign-0.29.0/src/macho.rs | 858 +++ .../src/macho_builder.rs | 688 +++ .../src/macho_signing.rs | 795 +++ .../src/macho_universal.rs | 129 + 3rdparty/apple-codesign-0.29.0/src/macos.rs | 341 ++ 3rdparty/apple-codesign-0.29.0/src/main.rs | 33 + .../apple-codesign-0.29.0/src/notarization.rs | 443 ++ .../apple-codesign-0.29.0/src/plist_der.rs | 769 +++ 3rdparty/apple-codesign-0.29.0/src/policy.rs | 570 ++ 3rdparty/apple-codesign-0.29.0/src/reader.rs | 1106 ++++ .../src/remote_signing/mod.rs | 1093 ++++ .../src/remote_signing/session_negotiation.rs | 891 +++ 3rdparty/apple-codesign-0.29.0/src/signing.rs | 332 ++ .../src/signing_settings.rs | 1697 ++++++ .../src/specification.rs | 324 ++ .../apple-codesign-0.29.0/src/stapling.rs | 331 ++ .../testdata/apple-signed-3rd-party-mac.cer | Bin 0 -> 1480 bytes .../apple-signed-apple-development.cer | Bin 0 -> 1484 bytes .../apple-signed-apple-distribution.cer | Bin 0 -> 1485 bytes .../apple-signed-developer-id-application.cer | Bin 0 -> 1450 bytes .../apple-signed-developer-id-application.pem | 33 + .../apple-signed-developer-id-installer.cer | Bin 0 -> 1449 bytes .../src/testdata/ed25519.pk8 | Bin 0 -> 48 bytes .../src/testdata/rsa-2048.pk8 | Bin 0 -> 1217 bytes .../src/testdata/secp256r1.pk8 | Bin 0 -> 138 bytes .../self-signed-ed25519-apple-development.p12 | Bin 0 -> 1115 bytes .../self-signed-ed25519-apple-development.pem | 19 + ...self-signed-ed25519-apple-distribution.p12 | Bin 0 -> 1123 bytes ...self-signed-ed25519-apple-distribution.pem | 19 + ...igned-ed25519-developer-id-application.p12 | Bin 0 -> 1139 bytes ...igned-ed25519-developer-id-application.pem | 19 + ...-signed-ed25519-developer-id-installer.p12 | Bin 0 -> 1131 bytes ...-signed-ed25519-developer-id-installer.pem | 19 + ...ned-ed25519-mac-installer-distribution.p12 | Bin 0 -> 1163 bytes ...ned-ed25519-mac-installer-distribution.pem | 20 + .../self-signed-rsa-apple-development.p12 | Bin 0 -> 2702 bytes .../self-signed-rsa-apple-development.pem | 52 + .../self-signed-rsa-apple-distribution.p12 | Bin 0 -> 2710 bytes .../self-signed-rsa-apple-distribution.pem | 52 + ...lf-signed-rsa-developer-id-application.p12 | Bin 0 -> 2726 bytes ...lf-signed-rsa-developer-id-application.pem | 52 + ...f-signed-rsa-developer-id-application2.pem | 51 + ...self-signed-rsa-developer-id-installer.p12 | Bin 0 -> 2710 bytes ...self-signed-rsa-developer-id-installer.pem | 52 + ...-signed-rsa-mac-installer-distribution.p12 | Bin 0 -> 2750 bytes ...-signed-rsa-mac-installer-distribution.pem | 53 + .../src/ticket_lookup.rs | 250 + 3rdparty/apple-codesign-0.29.0/src/verify.rs | 510 ++ 3rdparty/apple-codesign-0.29.0/src/windows.rs | 661 +++ 3rdparty/apple-codesign-0.29.0/src/yubikey.rs | 716 +++ .../apple-codesign-0.29.0/tests/cli_tests.rs | 290 + .../tests/cmd/analyze-certificate.trycmd | 348 ++ .../tests/cmd/compute-code-hashes.trycmd | 48 + .../tests/cmd/debug-create-macho.trycmd | 168 + .../tests/cmd/diff-signatures.trycmd | 35 + .../encode-app-store-connect-api-key.trycmd | 70 + .../tests/cmd/extract.trycmd | 369 ++ .../tests/cmd/generate-csr.trycmd | 88 + .../cmd/generate-self-signed-cert.trycmd | 139 + .../tests/cmd/help.trycmd | 159 + .../keychain-export-certificate-chain.trycmd | 46 + .../cmd/keychain-print-certificates.trycmd | 34 + .../tests/cmd/macho-universal-create.trycmd | 50 + .../tests/cmd/notary-log.trycmd | 41 + .../tests/cmd/notary-submit.trycmd | 74 + .../tests/cmd/notary-wait.trycmd | 46 + .../cmd/parse-code-signing-requirement.trycmd | 46 + .../tests/cmd/print-signature-info.trycmd | 32 + .../tests/cmd/remote-sign.trycmd | 95 + .../tests/cmd/sign-binary-identifier.trycmd | 113 + .../tests/cmd/sign-bundle-dsym.trycmd | 251 + .../tests/cmd/sign-bundle-electron.trycmd | 1186 ++++ .../tests/cmd/sign-bundle-exclude.trycmd | 277 + .../cmd/sign-bundle-framework-shallow.trycmd | 236 + .../tests/cmd/sign-bundle-framework.trycmd | 254 + .../cmd/sign-bundle-macho-universal.trycmd | 438 ++ .../cmd/sign-bundle-multiple-macho.trycmd | 767 +++ ...sign-bundle-nested-macho-identifier.trycmd | 352 ++ ...dle-nested-outside-nested-directory.trycmd | 1332 +++++ .../cmd/sign-bundle-nested-symlinks.trycmd | 282 + .../cmd/sign-bundle-storybook-bundle.trycmd | 259 + .../cmd/sign-bundle-symlink-overwrite.trycmd | 43 + .../sign-bundle-with-nested-framework.trycmd | 844 +++ .../tests/cmd/sign-bundle.trycmd | 495 ++ .../tests/cmd/sign-cms.trycmd | 815 +++ .../tests/cmd/sign-code-requirements.trycmd | 66 + .../cmd/sign-code-signature-flags.trycmd | 123 + .../tests/cmd/sign-constraints.trycmd | 191 + .../tests/cmd/sign-digests.trycmd | 301 + .../tests/cmd/sign-entitlements.trycmd | 228 + .../tests/cmd/sign-for-notarization.trycmd | 137 + .../tests/cmd/sign-macho-info-plist.trycmd | 66 + .../sign-macho-reconcile-identifier.trycmd | 253 + .../cmd/sign-macho-text-segment-offset.trycmd | 139 + .../tests/cmd/sign-macho-universal.trycmd | 116 + .../tests/cmd/sign-p12.trycmd | 403 ++ .../tests/cmd/sign.trycmd | 540 ++ .../tests/cmd/smartcard-generate-key.trycmd | 43 + .../tests/cmd/smartcard-import.trycmd | 103 + .../tests/cmd/smartcard-scan.trycmd | 28 + .../tests/cmd/staple.trycmd | 32 + .../tests/cmd/verify.trycmd | 32 + ...dows-store-export-certificate-chain.trycmd | 40 + .../windows-store-print-certificates.trycmd | 34 + .../tests/cmd/x509-oids.trycmd | 43 + Cargo.lock | 2 - Cargo.toml | 5 +- 188 files changed, 52803 insertions(+), 162 deletions(-) delete mode 100644 .cargo/rustc-wrapper.rs delete mode 100755 .cargo/rustc-wrapper.sh create mode 100644 3rdparty/apple-codesign-0.29.0/.cargo-ok create mode 100644 3rdparty/apple-codesign-0.29.0/.cargo_vcs_info.json create mode 100644 3rdparty/apple-codesign-0.29.0/CHANGELOG.md create mode 100644 3rdparty/apple-codesign-0.29.0/Cargo.lock create mode 100644 3rdparty/apple-codesign-0.29.0/Cargo.toml create mode 100644 3rdparty/apple-codesign-0.29.0/Cargo.toml.orig create mode 100644 3rdparty/apple-codesign-0.29.0/LICENSE create mode 100644 3rdparty/apple-codesign-0.29.0/README.md create mode 100644 3rdparty/apple-codesign-0.29.0/docs/Makefile create mode 100644 3rdparty/apple-codesign-0.29.0/docs/apple_codesign.rst create mode 100755 3rdparty/apple-codesign-0.29.0/docs/apple_codesign_actions_initiator_output.png create mode 100755 3rdparty/apple-codesign-0.29.0/docs/apple_codesign_actions_signer_output.png create mode 100755 3rdparty/apple-codesign-0.29.0/docs/apple_codesign_actions_sjs_join.png create mode 100644 3rdparty/apple-codesign-0.29.0/docs/apple_codesign_certificate_management.rst create mode 100644 3rdparty/apple-codesign-0.29.0/docs/apple_codesign_concepts.rst create mode 100644 3rdparty/apple-codesign-0.29.0/docs/apple_codesign_custom_assessment_policies.rst create mode 100644 3rdparty/apple-codesign-0.29.0/docs/apple_codesign_debugging.rst create mode 100644 3rdparty/apple-codesign-0.29.0/docs/apple_codesign_developer_guide.rst create mode 100644 3rdparty/apple-codesign-0.29.0/docs/apple_codesign_gatekeeper.rst create mode 100644 3rdparty/apple-codesign-0.29.0/docs/apple_codesign_getting_started.rst create mode 100644 3rdparty/apple-codesign-0.29.0/docs/apple_codesign_github_actions.rst create mode 100644 3rdparty/apple-codesign-0.29.0/docs/apple_codesign_quirks.rst create mode 100644 3rdparty/apple-codesign-0.29.0/docs/apple_codesign_rcodesign.rst create mode 100644 3rdparty/apple-codesign-0.29.0/docs/apple_codesign_rcodesign_config_files.rst create mode 100644 3rdparty/apple-codesign-0.29.0/docs/apple_codesign_rcodesign_notarizing.rst create mode 100644 3rdparty/apple-codesign-0.29.0/docs/apple_codesign_rcodesign_signing.rst create mode 100644 3rdparty/apple-codesign-0.29.0/docs/apple_codesign_remote_signing.rst create mode 100644 3rdparty/apple-codesign-0.29.0/docs/apple_codesign_remote_signing_design.rst create mode 100644 3rdparty/apple-codesign-0.29.0/docs/apple_codesign_remote_signing_protocol.rst create mode 100644 3rdparty/apple-codesign-0.29.0/docs/apple_codesign_settings_scope.rst create mode 100644 3rdparty/apple-codesign-0.29.0/docs/apple_codesign_smartcard.rst create mode 100644 3rdparty/apple-codesign-0.29.0/docs/conf.py create mode 100644 3rdparty/apple-codesign-0.29.0/docs/index.rst create mode 100644 3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleAAI2CA.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleAAICA.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleAAICAG3.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleApplicationIntegrationCA5G1.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleApplicationIntegrationCA7G1.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleComputerRootCertificate.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleISTCA2G1.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleISTCA8G1.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleIncRootCertificate.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleRootCA-G2.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleRootCA-G3.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleSoftwareUpdateCertificationAuthority.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleTimestampCA.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCA.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG2.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG3.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG4.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG5.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG6.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG7.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/apple-certs/AppleWWDRCAG8.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/apple-certs/DevAuthCA.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/apple-certs/DeveloperIDCA.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/apple-certs/DeveloperIDG2CA.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/apple-codesign-testuser.p12 create mode 100644 3rdparty/apple-codesign-0.29.0/src/apple_certificates.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/bundle_signing.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/certificate.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/cli/certificate_source.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/cli/config.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/cli/debug_commands.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/cli/extract_commands.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/cli/mod.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/code_directory.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/code_requirement.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/code_resources.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/cryptography.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/dmg.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/embedded_signature.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/embedded_signature_builder.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/entitlements.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/environment_constraints.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/error.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/lib.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/macho.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/macho_builder.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/macho_signing.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/macho_universal.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/macos.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/main.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/notarization.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/plist_der.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/policy.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/reader.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/remote_signing/mod.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/remote_signing/session_negotiation.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/signing.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/signing_settings.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/specification.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/stapling.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/apple-signed-3rd-party-mac.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/apple-signed-apple-development.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/apple-signed-apple-distribution.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/apple-signed-developer-id-application.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/apple-signed-developer-id-application.pem create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/apple-signed-developer-id-installer.cer create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/ed25519.pk8 create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/rsa-2048.pk8 create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/secp256r1.pk8 create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-apple-development.p12 create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-apple-development.pem create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-apple-distribution.p12 create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-apple-distribution.pem create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-developer-id-application.p12 create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-developer-id-application.pem create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-developer-id-installer.p12 create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-developer-id-installer.pem create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-mac-installer-distribution.p12 create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-ed25519-mac-installer-distribution.pem create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-apple-development.p12 create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-apple-development.pem create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-apple-distribution.p12 create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-apple-distribution.pem create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-developer-id-application.p12 create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-developer-id-application.pem create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-developer-id-application2.pem create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-developer-id-installer.p12 create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-developer-id-installer.pem create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-mac-installer-distribution.p12 create mode 100644 3rdparty/apple-codesign-0.29.0/src/testdata/self-signed-rsa-mac-installer-distribution.pem create mode 100644 3rdparty/apple-codesign-0.29.0/src/ticket_lookup.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/verify.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/windows.rs create mode 100644 3rdparty/apple-codesign-0.29.0/src/yubikey.rs create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cli_tests.rs create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/analyze-certificate.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/compute-code-hashes.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/debug-create-macho.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/diff-signatures.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/encode-app-store-connect-api-key.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/extract.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/generate-csr.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/generate-self-signed-cert.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/help.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/keychain-export-certificate-chain.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/keychain-print-certificates.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/macho-universal-create.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/notary-log.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/notary-submit.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/notary-wait.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/parse-code-signing-requirement.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/print-signature-info.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/remote-sign.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-binary-identifier.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-dsym.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-electron.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-exclude.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-framework-shallow.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-framework.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-macho-universal.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-multiple-macho.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-nested-macho-identifier.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-nested-outside-nested-directory.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-nested-symlinks.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-storybook-bundle.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-symlink-overwrite.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle-with-nested-framework.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-bundle.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-cms.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-code-requirements.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-code-signature-flags.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-constraints.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-digests.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-entitlements.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-for-notarization.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-macho-info-plist.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-macho-reconcile-identifier.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-macho-text-segment-offset.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-macho-universal.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign-p12.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/sign.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/smartcard-generate-key.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/smartcard-import.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/smartcard-scan.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/staple.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/verify.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/windows-store-export-certificate-chain.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/windows-store-print-certificates.trycmd create mode 100644 3rdparty/apple-codesign-0.29.0/tests/cmd/x509-oids.trycmd 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.rs b/.cargo/rustc-wrapper.rs deleted file mode 100644 index a0e81a74..00000000 --- a/.cargo/rustc-wrapper.rs +++ /dev/null @@ -1,96 +0,0 @@ -use std::env; -use std::fs; -use std::path::{Path, PathBuf}; -use std::process::{exit, Command}; - -fn copy_dir_all(src: &Path, dst: &Path) -> std::io::Result<()> { - fs::create_dir_all(dst)?; - for entry in fs::read_dir(src)? { - let entry = entry?; - let ty = entry.file_type()?; - if ty.is_dir() { - copy_dir_all(&entry.path(), &dst.join(entry.file_name()))?; - } else { - fs::copy(entry.path(), dst.join(entry.file_name()))?; - } - } - Ok(()) -} - -fn main() { - let args: Vec = env::args().skip(1).collect(); - if args.is_empty() { - exit(1); - } - - let rustc_bin = &args[0]; - let rustc_args = &args[1..]; - - let cargo_pkg_name = env::var("CARGO_PKG_NAME").ok(); - let cargo_manifest_dir = env::var("CARGO_MANIFEST_DIR").ok(); - - if let (Some(pkg_name), Some(manifest_dir)) = (cargo_pkg_name, cargo_manifest_dir) { - let manifest_path = Path::new(&manifest_dir); - if let Some(dir_name) = manifest_path.file_name().and_then(|s| s.to_str()) { - let root_dir = env::current_exe() - .ok() - .and_then(|p| p.parent().map(|p| p.to_path_buf())) - .and_then(|p| p.parent().map(|p| p.to_path_buf())) - .unwrap_or_else(|| PathBuf::from(".")); - - let patch_dir = root_dir.join("patches").join(dir_name); - - if patch_dir.exists() && patch_dir.is_dir() { - let patched_target = root_dir - .join("target") - .join("patched-crates") - .join(dir_name); - - let _ = fs::create_dir_all(patched_target.parent().unwrap()); - let _ = fs::remove_dir_all(&patched_target); - - if let Err(e) = copy_dir_all(manifest_path, &patched_target) { - eprintln!("Failed to copy source dir for patching: {e}"); - } - - if let Ok(entries) = fs::read_dir(&patch_dir) { - for entry in entries.flatten() { - let path = entry.path(); - if path.is_file() - && path.extension().and_then(|s| s.to_str()) == Some("patch") - { - println!("Applying patch to {pkg_name}: {}", path.display()); - let _ = Command::new("git") - .arg("apply") - .arg(format!("--directory={}", patched_target.display())) - .arg(&path) - .status(); - } - } - } - - let manifest_str = manifest_path.to_string_lossy().to_string(); - let patched_str = patched_target.to_string_lossy().to_string(); - - let new_args: Vec = rustc_args - .iter() - .map(|arg| arg.replace(&manifest_str, &patched_str)) - .collect(); - - let status = Command::new(rustc_bin) - .args(&new_args) - .status() - .expect("Failed to execute rustc"); - - exit(status.code().unwrap_or(1)); - } - } - } - - let status = Command::new(rustc_bin) - .args(rustc_args) - .status() - .expect("Failed to execute rustc"); - - exit(status.code().unwrap_or(1)); -} diff --git a/.cargo/rustc-wrapper.sh b/.cargo/rustc-wrapper.sh deleted file mode 100755 index 31771407..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 9>&1 10>&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>&9 2>&10 - 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>&9 2>&10 - exec "${new_args[@]}" -else - exec 1>&9 2>&10 - exec "$@" -fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2c987613..45c987ad 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -76,11 +76,8 @@ jobs: - name: Build binaries (Windows ARM64 native) if: matrix.arch == 'arm64' - env: - CARGO_BUILD_RUSTC_WRAPPER: "./.cargo/rustc-wrapper.exe" shell: pwsh run: | - rustc ./.cargo/rustc-wrapper.rs -o ./.cargo/rustc-wrapper.exe 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" 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 0000000000000000000000000000000000000000..692fd4ecf1a3a94678bf7b97b5322bbf5cfa1a23 GIT binary patch literal 75422 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..eb4ba58c05f966ccf171df69c242edb360812cf5 GIT binary patch literal 57410 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%0DFq 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 LXRKSKV;}rKo$J>2 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..83ac1be7f33ea96f00faf45606a834aef5e16b21 GIT binary patch literal 59881 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�s3)@Y!w{Fj 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0038b604398a87ba272f592d32cce914b57ccb8b GIT binary patch literal 1052 zcmXqLVv#UtVrE#t%*4pV#KE%jcj`p18JPyWY@Awc9&O)w85y}*84QvPxeYkkm_u3E zgqcEv4TTK^K^!h&F2{m`oKywRyktE?H3JopAh)nAM9?|4s3bEjGdZy&Ge1wkv9u&3 zzbLb$(ooDm1f-5xm=~fhC_leM!P(J3PMp`!*ud1t#L(Qt*w{QuoY%+#iEGfzqwvmZ}L-l>Ya`xM*#}51F zN4M|O`Z#&{m7KUumU=8JALhzfud^()XZgJIOZZ%62LGAOaq~a$Fh=a#dhyNmO?8J- zAANjdv+Pga?Xb=7xY#yvd!JnN;iKWVb@xnj4mZRFBv073D>vrAKkLjf)Lrfw3mb$0Eie@_k>8V#37Rw$jx% ztc)5n@6XGrpKZVok``uU{LjK_zzn1eU{eA3clC2<`Z1?IlOvT zbYx443qG24`+fQj_K#0)v~d@fU;fyUfjMWQq5i^)yIwzxF+XtQqBPI*2LeG(OQzI!e@jqG zs9A8p{@~QstB2H+HzbA4e5?B9OvlWbcS2;uSsAxizG!Tc<^ z40Y%{h}Wh}6fZn7)H%06Hibp!6cxoyhR32P2&iKaoYS?CC0pXM=a1+3JijOTJkRq5 z0pd&$AVf3}@Hjj^Qak;@f}3}5{syQKqy;ruHDgp9E-E|>6hl#f6hY7sBWQSy3ZjA7 z(1J`O!!B2;F;VO=q(F1QoDhtnB}_uZ==NAl7K7enaXJ!VucyT6wsi%Nu^tHd|v`g;q zuD>>L4S&?-5q@=feRIZ_y_63-KPh_IhawkGU+ZWels|7fvjydjV-KRUckLQ|R8`x5 z>ttUsZpHSP_U!n9=KEWVc$tA85?ZXoGrn7UlC9ws3_K)f_R`<89un6%N zu*^9Ri`I?&ad*uN!n64ce?GT4tFw2AQ7aisJW)FP{KJ~|oWe`1j>i87!C-*0n7@4B;s@szcP;AX%TJqkzS10>Qa_8jftcyB9{VAs9+Y60g?f|VSdBB znzu_l9@ioU!|(T}=tGf9F*xmviJ3vqa0e5VVsMv&gwVNYVmvVp>h~Q;^6b6U_2*E- zw@o`cVxwapf#}e7Oh|+njC&235<#8_xdDs76Nva=&i@_z;FsdUrc;O~uJ<4Rcm|WR`xa*CEWSkaAP|#G&q0U zy=UGtePi1ObL*l{aRnI(D-X4A>N#@o(p$9`7dMW^&A)xTy56$QHRa86@nCBDYgLWQ zl4gVrKN{pkp}#a2$(Pf2E*HPk^9BX#7E82e+OtgyPsw~d`I8+^@9i^7=3E|nU)4D-R%5;FN^EoO_ ze7Z;{u)AQFS5|J}Da>BGMJw@WiAyTyl)t8j%U^<^ePh)Ew-YOPbBeY?;La~pFSW^Yr7$#Py3-xBRj8mRp!76d) zDNwtWXC*sD44Jb`>7d(1Rv%mF@L9_Yu(muc3q{-&I)%Yq=2nPNt6ZU1FolT2>=BE! zdR?hsz^kZI837>6VT|Ks`K=;9-iKuhaN-NZEn598&X(~4=U=^#F_B>`Kor|#Y7=m)PubfdL xHELA~t5PPj@_ib9mJqP>&H3f1pUlJkHs&Ff$pX8%i5UvN4CUF!KmG z78K;9Dg@={mnb+pD(EV>8yl(_sDLE7d1N7y&Z$KunQ58Hi6xo&c?yoDB^mienI)Bm z!UlpMbzD4L5Otn;$$Ey|2Am*4HesgFU;{aEUPBWDQv)+2V-quDgD7!cBTFQ%L6xDN zfeyqWst_|kB+Mqyypq)PB8aWjcUz=^FdI8KNSGL*VZ+SG&g{g%@s6^Z z)kgNR7Y&Qv->Y8d`=Ixa_vDAhVOP_lP1h-1m|n)Kn_W4~zkUp;RFevq^< zBjbM-Rs&`rWgrg{P-c-Z5Ni+qd`i`dg<}RvVau6v(p}7ytu* zfz|?zHZ`bgl8bVX9E0KtJ)oQcA4nrV$SPneVq`NAVB-P?VB7cP6gDP47A95(6}_rl9`s7oLG{XpQqqh zT9T1plvz?~C~P1IQpd%^1ySdjm#k;VZNLcD=aTocy zh&71p-f(YWgmK5LC0#S`?c4i${jJj~s}0=PIJDUqSy|Z`8Ck3hEDX$Gd;`Wdt&EbA z0xNy}{N&;Sy+ly7=_Tjqg1H99x?q7KU^FHtrW+fWgDjF~0fw%D&H}9k8f|J&&B;YM zNP1B;=>g>oWI-DFSj1RFZi@f&PM=`mJ?YEsb63L~M7aBHPa5!nr1?QUVgaTzHUj}R zE?@w*eNRqdV`64uVr4*1XUrK42B}Pj488fw<}ZBkkPAtjH z&r@(LEy>6)$}Fig6gCh9spI0|f~fP%OV%^wHsAyavI#SV1{=tU^BNf&7#kQG85tTG zm`9288e0Il#s**zWl&>iV4w$ah&sd!5DBx%Gp{5yy$E6}&~4@**BMgLb!7$uZ0z7b zVPa%s)ox^AP-0GEU@2jnbo6%RUgKBYY8*Vvo6q_0oAqy|t?z56Hg)dyvd!Edg5#VY zu$3O~pFeNmZB~|tD*B)P?hEN=^!4+TiwpD; zLD8z0oSzHk8W`(>1&V;toSc|$Y+w$uNS*~4`UW}+v=(TzsX;X-7v&)7MbV@OlrxY8 zY2;%OV-b?yI2J z4x*h(BFD6hwyof0GSGatXu0`11@AznsvU2Z?JT-jvRK!P<+8Mi!yey1C-*)TS4-z) Q$T*zwB~E|pedTox0Qk-Nu>b%7 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..8ccb85c5e90e419865b0ee0fa1879d118972f878 GIT binary patch literal 1470 zcmXqLV%=rX#Ik4sGZP~d6CP z=PUbaB?G1x5_UfVI4{>bu9)%j;;FPbD%pY;7Vmx{t#w#_>bxqRCGOqPSH#SXPaom; zJtvZR@uXzY*I2%*_3^VmsWR^h?e~r4{g`5^Eb1*9DZ6>f_iuCNoe$V`Fd`tmpJR){ z_tR^16!HtU*)tmSYFiGSy|?&P}}Cv)wz?N+O`U-*Rg11Ex849w|xU7HD>CH|}cOXb|xx zip8iWV{^akz0~6W>2axhJa3fEHx@}Y;^HehmRXJQmH%_u1;D7Mnq zFE20GO9Z8Hz2y8{{bWS?O@t+1ePFuJE!N8@$;~lnJPbF3$)ItYRO9BL)SS%3yyR4c z{5*w{jLc#MBqJ1(Disp*DisP6i%Nj3;^NZW)MACiU+kS1-9J#~>6I6pV~4 zo(Ap)t}wmVXU(Q9MQ z=(W$146|OW*T1@6F1)P2-dtCYDdukS$xnql)Ew@M_LjQXSMGf#JM*Elj@{b)eLI)@ z<1>{EOx}3--quNWffbC!T}zZtE$R3u7bEW|rdv0aYt_q6#~IR6cTX$qcaP&yI~r>0 sd(5DFX1d66PWfH#$8RxmwyQWz-7B0R$@TtKJzw70gD%ciURNIj0LeB5MF0Q* literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..46711ce49f99cb012c5141de95fb4a5592c817fb GIT binary patch literal 1092 zcmXqLVsS8NV%Au|%*4pV#LQ$>V!+GBsnzDu_MMlJk(-slz{!x?fRl|ml!Z;0DKywn z%s>Rh;S%O`Pt6Z0DlINi@XSlrGn6)v1c@^X3&F+RbMliCa}=B%4dldm4NVNp49yHp zO^ghTqQrTPj0{YT44_XsMBXPo_fZ~qJ5mygXF*#EdnXVtj2tqZ%ngtI=8 zH#@d2D7$vc3Ckm2(yhKXpOegQ)30-_bC*dEIyIFw_K{g&>wzoj4W{{`ckTYlUi#Z_ z5^&>9nby9?mv24!d*ZbQ(=@^9;)yaZk3?EK-7&hzDSK<#Md!Bvahvw^oKL&W*v+(V zipOV*{=ct`R&cy&n;tkt#m>`nTJVd?g$|7`S$9vK8u;h&-x@*h@AEh|i+YN(|Ga3v zV79l>x2;F+WL^Bj_4eC?qnZm=bQk{PowVm)NPz@b?(w%gzfbD29qwuUbGj|&npvMv zH)s8CNhyn~AEp>o@h|K7%*4#dz__@HQ5F~?k_Pg?(3DkXkuVTz5IImaBcr$T_iEM@ z9>LmGwW&K!o$)h}1u5WT5n~a#Q8mS-W!+8bgVT~8kY}LB#-Yu|$jZvj$jG8(07@EAz5!#K5Q+&YAQKFHK-MU*xEeSc zII?jiw0SVL{cvSuH#0CcFobDkViZ$_YAr7>*Go^$F99WOunUS(i}Op1l2eQIlM9NG zlMpa>0Fw|SgP419e(R#7htIXjc6Uy7Qk%ZVYt5R1U@LpWW?LJX)w3RLjFl;qT42Ld z6u$5BynqKMW?qZ9&h+XWuVzTi*{_F=P0$QooG-G1+lkj|MlM^$yboE6Wtq4ZX)NhI z)aYiHn||w_{l8W|CoQ3STett-A-#OsoAUW@t^`l5`p^F56PL(=z@UxS`U1G6X2$H{ zKDqbp+aGZU!*2W5DK8ORsB=Y{b=DKx6Gg-ZN74JzpG~ku9J%Bd+WEDesNPPe#nTV0L@j)HmB2 XpL{x-FJ0O0BVg0{#P?UoomI;Kn9Gbg literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ecddd53ddf918da7b84ccd9bdc91a9f35a274f3f GIT binary patch literal 1216 zcmZ{iTWs4@7{}{N(>80zwv@I)tTjVr9aWNJyG@!(MYD5pE=?AvxfZl7wNoc)nxwW9 z#{o@i5fdP>33X$DgjCox@dDk7#HJyEMhC12@x)D(>kII}wVEnmoghv-v^{WMj=s<5 z_dkFC&j;p^Fqnf^7Z4bN;T?vM>76TIKk|L|T6uq|=f`h>Uf2BKG55zKD1w05=kaZz zMTbOCodJ%F;T@n|EA)moCRGTFB}v9SdCr8}fK}^nGk`vBA4ULR#qKzO88KHu#F8aB zT@W+X6pxjp6jsRRRNMjvG!!uy|09B8W;!XVm^~%RnaK>7lrx1qMs32fS`%Xk(B0Wq zV=cf2>TA5Sjl^r^5J3*w25sYD0>2mBQ&(;^P{m>{g?Yxpn4Q9gG#&|SCO+W9}% z;0CZ$JE3pYZ}Hln-W~@?0E4cn9u^F3rG`LF2;G1h!^hL-cNl)VZe0DMWy${2OQ{#~ z1D8HIy>fizvsd?6iEA^5e?9-l+I#%^W#-Vv4^0)qxc=c$Y~^P0PjUXfC!J^JpkXiv zS-`HEh1~`S`Uh<16& zbi!d7uDuz>rSMW z;HDb{-u%2B?Lga?ubz!Ja-To0yZqcUUoD*=*5A5FfgX*%AMFAiN89f{G#FzeH+q*| zLoyrCnZMuO8Ts_~8AgN#z{7y~l5AVK^zOX;^YGr-)l)YQlOHK3FZO}OwT*fC*_YpZ zO8n(|i(~b#*GtlkbGjqTFz{BF`wp7l@VJW6_V9AU$`=|GNCZCTFk zSKMsE5eEfkyIo=^HsaFLir?<10Yl*t-XX9tnhrZEzEFaW z1>=H}wB!mTCl!WNn^+>*Ff~aFfv9`T?_?ZQOrY7QK3WlB0oeWgO+mI z6~)7pj~0ZP^i=jhkYyc|639~ydWy=@ZX)Xss1sTH6d9KE2QqxT;*Hx9j-a0(93y=W zdwHm=hIucOmJ2!6I>~xNf%2f3k5>CF31(8NId&E;ikeQ1S_3})^h~lW@uf_K2=!-i znp8sBRIchx2=S`mC-X|0_4#t%Sf%K)dKh=9oW$j1MHm^;8@1&OI54|26!y40b}HlFYQsBg2!4D>>yS-j;I@c+L7YuChh5U7`KHvAiEsOqE5wMI&W5Px=0B&b;#hyADPKr1x`dQTTp(jgCTo z!8UtFgP!fq=lSQ_e%AKXkUH`2+}53ZH{)ckownU-we|}?AHyW>jf!G=C0A{DZzqYZ zUR*fIJvj8>dVR;uKYl+hIQwj|k87R0Pjj_t->jT;Rj-bAq&^<-@B zm%W!-{69S|b&uzbviZg$sSC@eoYZAvW@KPo+{9P~43RPeK43h`@-s62XJG-R8#V)e z5MLO?XEk63QUIB4HrbfL%co zBPgB8DzG#$asX{)0b&Md!c0zKWi)8~WT3^yq0I(NqwGwKVsaTJB?ZM+`ugSN<$8&r zl&P1TpQ{gMB`4||G#-X4W-@5pCe^q(C^aWDF)uk)0hmHdGBS%5lHrLqRUxTTAu+E~ zp&+rS1js5bF3n9XR!B@vPAw>b=t%?WNd@6N1&|%Uq@D!K48=g%l*FPGg_6{wT%d-$ z6ouscyp&8(HYirePg5u@PSruNs30Gx7i1YwCER{crYR^&OfJa;IuB@ONosCtUP-YY za{2^jO7!e*{cX?eJDxY@8r; zW8atJ+3zl;@Sm>qH@UIM?q|jS>=W#7YAu_)gB31Y9ND;kmOoeaf9*e!%UL;V#2vx} zUUwk!R<>!CBEM2a=7;zgw~E zguTASugG_6SFxo3)|+Pa2irq$E}yy6$m#cutA+FG76xsX-aFYzMMzw9>OIdRD+ Xyc@&=R&`yy_2kb5PImJRrKO4hMS!&; literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..739b8141312801fc4e88396bf4366075e2711173 GIT binary patch literal 1430 zcmXqLVx45r#9Xz2nTe5!iG%UM)2+?viys*9vTOAw3^$fWUI6;DJ!c3vT26E!Oh9(9k#s-EKh6V@Moh?3yZ7ptT%{oZtmbE?IX_gbDkv37-N%oe}J zzP5HeiJHX2V)W^R!|$N@`QHwFe7wtbLBLg^?oW5`O>9&C@O4{}9^)$g`tk5`oHG2U-jvVF(JFAtQy>@PC8|Gm+-ur_evqs<>KYls!+pFOkI zyw>maT8D@Bjeoz}a@j5VxYux+X5^hik_|o4CC79w3k0jliufP7(wJt`o^Grz!truT zd5cLk8RGj7?_MQ5Sw1#If5q{d-@GFGL~T_}KFzvQ>X@dqh4C8q`U&%RUM2mQ z9cjbyXqw5BL}ue|2zV0PvXSiivJZh)`o?5)W*}w22NK{1 z39tY&9UF2g175L?oeT7jmWF>&3sQpoaUAA37lOmwM#Iu zvEs(Zt*#Cgt5+O2_q#UPJmcH*-|LSZ_I@L8rY0QHHl=9OE2g*IxleeOO*jx$J&Y#WKm>kMqyI%SzoNwLx|7a?QVA>=u=J?w@!2U}iCt+`i2!(e>BKPs%5fi}#0h8g95*leD~AbVWzkY72)$XN3NX zKK@{S-@NGbnHkUaFWU19&c)8#u)Swc2lF=;T`x`$uV~zj- literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..228bfa39cbd5acfe53fb9d196e3c1bbbd28649f8 GIT binary patch literal 583 zcmXqLVsbWUVm!HknTe5!i9`43pN>mMy{8&*v2kd%d7QIlVP-Z+HbQ8gAnH8xlJyL^4LCu9Y{E>T!3J{TyoM$QCdLMa7KWAvW>Mn2#+FE2`Z=V` zK!A-M?0+UkHdgIM76v8eBnFllFZFF5ik7^ctW?w}EOS?2>c^vt{R;1hh~4CSx{Ot; zJf%9`&*JiK8JDf~U*)$MCB>e6*%Iw<;4c`(@HZlYXX#gd9ba~L;nG{vr%%r}jCrd) zw_3sa#?FwNaWj`#1#%fKb~11^i`@ju(^Y`3K= md8u*U-Ks5L%FuSdyxM%SHtQIk3xk#Br&Bd58zu zL&#n>Xkt`C4irXK2IeM4eg>d87gG}>Bg2*t3Q}8y*M#j?ikIYSi92{yhGiaWdUW3W z*|EzvKi2vEe?payneYnX(rI#)XD>6(4fj7B!NT}9J8#$eC(q9>RWF*dy~M|Nn}yTC zZjap}l{XGW{p~yZAwb5m?9o)=0QW!7v{rPVy)Y}nKIY!!iK!kRe}4^ieYr#7YPj{} zEe3*13v)O^Hr}XQDWiPv{nu-$=U%QaUb|7p*+-ZA;kol-cem@k3FNTQ*ynmP&gbSP zt(v==BrZQXx6A46!;+vLM<>?zZ}#Us-aPYZoT6LEmOn)^eN*TEV!svL^ZZQC!|Kon z%fk0KeV@Ibt>FbvQJ&DoHgB=&>WZnxi*_#9SGp+9g-7bwTqb5l2FArrj7$cN{|)$o z!7VGy$oQXy)qojD8OVbKlvyMU#2Q4jFJ!t;DdYV2CA(M8VcB1&V-aC^-?Fuw+$P zcn!Fr$u~KLh0#C{WVt+xx`C>J@&d&L@@=wE1Cxt#kjw*_p$8N)kOisdV-aH!i46a< z%kuo?h%#s4%_okqt>p;Tb}`@sN%Mm|#{$eeYz6{sT;QydoWjN=%)$iBEvT6Xm`)fO zCS_KzG;efi;<&x>H_xraNA}Wr-&xkRR`ChBafPI4c+{6!?G!cXq{H?Ku+gREX%`w+Em=|L8zFGmdo0y00P`nReW zHuIbn{MlD$T6O2T()o(NLUvm!+}H{O@|vf_FnxEGTkv2KZ-irB!$O_V&q^<~mnYsi Pwk|5ixBufCo28WitgNNX literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..dc0538f00e7491eced50d968b025999917b7c252 GIT binary patch literal 1456 zcmXqLVqrIEVtT)TnTe5!iKEshJbC`-58DlR**LY@JlekVGBR?rG8iNoavN~6F^96S z2{VNT8wwi;f;e2lT#f|=IjIVsdC7W)Y6dDGL2hALh@f+7QAuW6W^!UlW`3T6V`)i7 zeoAC<|amd2B0_>QxhX2 z!{u^`h2Liiq{m#+o42E1IodS5?u z^7h=Z{l<1azfIML^N{ojzKV|CgGxJgDGJX%^{#8)-RkK7i(g(`+*tST|Fa&?C)Qj) z1(p>V@76iu6;;G#8BlUpBd;>PZutt4^I@CAa+bdQaAe)jR|fJx3fk+GXWg z)D2V(lou#2kZ+UCC@Cqh($`Ne%F#;%B|*L9{9OGaU`o;h3K{T$4CDvdzyeIyZ3Y5t zT;SB4oWjP$$-)Fo(5UGf7?q3+W|zL+D-32oseGeicIuwbSEFC=WDi3fzx-bmF=5!skHlW?3}Nsd+uaqS>1gdQu|gU`|~xs$tQBF zLgGI;_X%7mi1?ytcSMaQvEtk6aF~5P6$;)+@ z880ut>r`A?6X@JJD?#ASI`vE0=_id{^H0u@TQIBMS8(#f%>Qq^T|fJsQsN80uEhRS zX-oUdIhS2e=h;8)+wsTkYRt(Et68mb%{3OUa7)b(U6|4FD#-5Smn+YH*)Msb*09wo z``Y2BKaCns7%lR6*vGb5^2z4Y*53CYoZ;KFe#XUS=gdO)V9zAuNTWb^$MkGh$MjG~ zUq=IX=U|99mrJ0llXH2fqoawZlVhNxkwIBRYI2ybM`dt8WU5z5PFX-eSh#ksNm@m5 zR)v{QK#6yDzFAsUO0s8qhJRW>5|^)Ol}mDVKzKl?S5S_5VTnhiS+GG~xKFvEQL<%n zsjsnnL7{(%aZ#9uafzFIS(RaySxBY3p;?%ler2GENg$VjM^tidxudT~sJVB5ud$hF zS(eS-NkLS8-}oQb3kVm6Nlde{QKkxkpHWrJ*xdvT>M2 zXsEwiZmwgXvA0Q{L2gN2Wr&|!l0|N$N1=0Knt?%3wzgY=zIKIAP@12AWu~Wpm}PRN zlXhrkiDy~?SDv4rTcweSzGr&*4t zc4b;fsA*Zget~~rZn{sXS)yf<3zunHc4c~UdS!lgRz_BOVwQVqa8+ehiI1_ii?2(O fcUi8Vy9<{t2)O#WC^)+Yg?PGoIy;8Ag2lK1JdPLN literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d2bb1da64122c864c872d9b711b176d042462748 GIT binary patch literal 1062 zcmXqLVo@?^V&+=F%*4pV#KCxP&k@Vq1p)@VY@Awc9&O)w85vnw84QvPxeYkkm_u3E zgqcEv4TTK^K^!h&F2{m`oKywRyktE?H3JopAh)nAM9?|4s3bEjGdZy&Ge1wkv9u&3 zzbLb$(ooDm1f-5xm=~fhC_leM!P(J3PMp`!*ucoZ+{nZl`k>Qj@$g1r8WvHYTX!VypjWG%{)c-lQVYXRuCJE(pY=;r$WxRF4-br${9`c}_k`!An>Li0EopDe zv*^E}E$UaTdLwGTU-QAd$KGuPNJ^ruh+PumVL-h)reU3&vOc4_NAnbP|3 z#gVzkGL~*w{3pGxU>8%Qce&F<%bj1(KJXzgbzkmy4MuTzafLDk420{ zq(fbtbLBRPgzh)5cYSk@JQ@_Tc)I~VNLrYY@jnZz0W**?kOv7Uvq%_-HHc_m$aJ4l z#`*6{cCVhpvhVJ`^&D{qdLRYzEb0cT2FeQ*7s$8CW|Wi^Sn2C07v<Nhma(=FU z5ipVI0fh|sKiL>2Sz0$ga7&Wk^6MMZpzW` ze<$2-^n%rNMP7I9$xNP|H^ujq>s(2H^mkUSRbjJu1>%3p6qBF+hu?6 zedkq{<@Qo*x8F5!lFt%JA<5kEuT>H4)frt++IqZRKk^h=we)T%!^(BLy$#kqT(EJE zX2Ubi@~8Vu7BQZxzw?Oene~p{Z+0b3{mh!|*mRcPTGnUklH03)o}Bv9|B3H&wV91C z_x#+Vd5N(q?V(=JH^r`_KPnzJuG@ck!rYZ>Kd=95AvG=CKqhc$%$vflrY$-AJfiXd D2`7-6 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b77e1e9eb6eea7cf06c82cb37dc6134902970659 GIT binary patch literal 763 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 LuhX4VPq+dA`9+>| literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..424a70bd3b78bd3ce45167b12673d056db250ef2 GIT binary patch literal 794 zcmXqLViq%KVw$snnTe5!Nkr-3LU*o`;>`EKZ9*rH&9}AqoO9iPi;Y98&EuRc3p2Ap zx}mgzBpY)m3p0<9V?jYqszOkHeu;v!qk^u2yRo5~feJ{Hn@1KR>6}_rl9`s7oLG{X zpQqqhT9T1plvz?~C~P1IQpd%^1ySdjm#k;VZNLc8)CH8ulsEe*gR%AnNH#lQ*T5Nn7T;rT^5Ddm|dsR}NsWvMy&1*t^}L8&=ltBVzI zxddt{HxHA$84=DZGZ0{72Zsg|BO9xBBMXBPa}onf-VrYDd;IG{m!(!-Z@N=l8sz=a zR!Fk*0k7c7d7R(P7hnD`nI(Utb9dXp89$un&i%h>UBCEuwt}VCHtfr{x<0X8sp7fp z0gr!oEaYcp^_SG1D)jn)Bqi*52iw`ki3^paJQg?pGHCp6AO!TVtS}?ve-;h{HXy~s z$Y3B35>sZ8Fc51H*}dW3!U*GzSxdTR-rKkL_4-?DawJD15VD;uzkCs=IQyR6Ov{c@PVZH zL7rj(CPFq~0CNEYxb1s#3L6t63ll2?asp(|U@%B!GH{3!S@oo+YY{V(-5rbX4pHpu zeseBlQ>~uwAb(`##JMk27I$d-&3i1ITN*xFCg$i*CPRkH9)F_NPJM5-(=fQ?Nx&iV m`#SGMLj(PL3?nA&{ocH$^jO7D!QPDxpWJ7tmF-pM$p8TIXaMy9 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..df350fd3575783624fe273f20bef9ccabf5a0497 GIT binary patch literal 1113 zcmXqLVhJ^9Vzynt%*4pVBw`{F@t^S@n| zD}zCjA-4f18*?ZNn=n&ou%WPlAc(^y%;i{6kdvz5nU}0*sAix7666+^g$O#Q7L{bC zWhN(JI z0lCI-u2GahDRwvTAiDwTN^Tw|cXLA*11GRWJk}74!t;xAQpz(^QWacM%Tjal3sQ>| zf>Lw9?kiTn;ar0zMkVCXU}R-rZerwT0E%-lH8C-R`rGt{k^sB;;z&B$GnMiRApjjWMEv}_}-xL zje!s_yk&(M8UM3z7_b2;CPoGWd61Ygi-dt#gNXKpO!p~eod3RL_v$$;`|j>r&k<+f z!p5P^225q_jEpQ624)5(Funm}n?^=SNr9EVetvRsfnFjgt?4D_=YqKg#=2mEB4F}K zPBhR1StHM)ZlG$Qyg+e*e48v(YjRNzl1`9nJ)n?*EJ!^cix`VYtdl^F^zP$9)BRlb z3ajt55-M-jHsAwE^MicC0?Z|B1_Eqc;QWxB!p6kN!UW6`s5u0fCKwrl7ISC{r*7XW z|ICE>?Kg??J+`ORw-&s4lVj}tZKmoJ_IZlN%voP+x%9N}bF6H-=T-mkIkz|K4)qzH zcMtNkU2jZT*EHvd>&I99GD-%I7pW$?bpC6t3j0trsfNcTL}mNdCG*uj82t>Nn^l?P z@Rr|%jm_!*4L-xv+-G`>H`(?dJ;l4u{R*piht115Pb5=vyQZuayun z#x~lyY3Zy}S$O=?gWCPFU(K?Q-do8vt6`&H+@_Se%?HEJ9LkBDx&6zAyN0(su4Og8 z+teLkD)#JoOkq;qhEIWyR^~7#eb_3Lv+&oO~7KSbcPGF08tRWVK=NILqlxL=-D!8PUrRL-p zq!uXzrRIR$SFC`;xdu&)O30zX$jZRn#K_M86z5`UVq|2v;2>~?<7bd!vcFBn_bYRn zn0lHs_H6I1?|AOytMguI>7mYhdwH(=CI;wQJ~1d>dg*G$EaOk_Z)&a8lV{%bW9dgH z?r=S~V;3SW7sP+xWd1%&-fn-V<)O9zzOw#~m}YpQUFWLuuF?%UUso^K^+|!p=e0_5 z=1V1q4}B5Q_fo1mr2~an9*A#>JbVA+^KyU4xxqPO+movnA=muoat zYI<}sX*@dkMn^un?az&Erwb1hGkX7F6%@>2WHyQV)_5pRzD9)ao_)6BY5%X+6Q8v( zPulZbRde3iXr6<&A0M&0Sr)!V-$F$4#P*GgLjB|Y`XnP)-uhc~YXgfSD-$y#1LNYx z_Xdq`41|E;Ei25(_@9NtfDK47F)|p)gT$0sBn-qFM6@qtx=$(N{P!ihSI=SDcX!`< zjyMAsHV$nzU@Bv0WMr{0Ff%ZL@eLT;G%`v`3as??^OK7U^b$d7O)oh=7tA#<)&&a` z0h3R1qJbXB8hI9V162d%1&Ryg+hn0ylZ$eYbb?gt0fh`?LF)Nf#8^bO?%mUHgy*rv zk}uoWDsO)8a&Bi-s{tQKnjhp77GN%6GZ0|o0_TV16gDPC7A9bpK+Pe*G{MN=v-x$E z&HYmPFJR|d z)=fJqzfHX+@bp{om-EWc%K6{y@Xq}4XdaW{&Aywb_9e%~KU|P`?%>%x&&3sq>n&}v zytzzNQ?43pwOM^vu+g&c#eu^S4T1tvf|m~MiTu!Y)3o@&v)m^qYm-FmnwVA!WSUnd z=6`UTVrTkw^_t`hH{v40GcWG&&WpVgFFdEfPgU@aijJJvylGJ^Z(ZM6y-SbnKiszA JtB-t!005Iwl`sGR literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..3d8fb276401a365b90012ec5d9a2d57d95151b9d GIT binary patch literal 1051 zcmXqLVi7lJV*0;;nTe5!i6dRpaqp8$OQj5W**LY@JlekVGBR?rG8iNoavN~6F^96S z2{VNT8wwi;f;e2lT#f|=IjIVsdC7W)Y6dDGL2hALh@f+7QAuW6W^!UlW`3T6V`)i7 zeoTEBc`t0jqG55adwjQnNZx$y^{CiMw!5;GmH@R~5mRA-h3Hw(HfCT(N-krl~Rsm7^aeIqjgvf-v zUFf+vSID%f>UziuZ=*w+Tonv^E^5Y~OA~mo+&tMymFvl!4$DF&W=00a#f{4h8W$VL z0z+Pwk420{#PgdO=MHJ*U(C_lUNtSa87m->&St<5k``uU{LjK_zzn1efJx82@9!P;ai@JfTf${>y1@djO86_nJR{HwMMLBwj zpd_i6oS&;-1WZ|aKp_J@ka~WQ4J^QP&SoIM#syB@$ti41+$>DM1df{CflE}Ks{bjfcIXS|(xUZNx5`ZB42O+udQZuEJGtcekLaNg~Qo=H~Q)w{tL9AED| z?418ORoUy>Lwr@80^) z^2^Orwfkxsc5AJitadWVt%EUC>*kiQ@b}IylU}_3d;J4zAD7)ui64ePj85<{d^=H; zcg5awr&jaR178_5GHt!?HF`e^I`itGP3M~($GiJKTr<12F6^G=M$KEnF&ZJKl-wt! LpXU%x`}Pa~3t@{q literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d3337393b5ba6a1cf74377a707350da14c3d5215 GIT binary patch literal 1032 zcmXqLVqr08VtTWHnTe5!i9@1l<%&bo6nPAI**LY@JlekVGBR?rG8iNoavN~6F^96S z2{VNT8wwi;f;e2lT#f|=IjIVsdC7W)Y6dDGL2hALh@f+7QAuW6W^!UlW`3T6V`)i7 zeo0 zbl-oSljF(%uo;@7&!^1YX}Xx}?3M!$A0>P_^+vP8H8V3IJHBhupLxp9tPUw{HuRU- z^=X?)q|>_%E18%jCbDeXT%l(y-{8vYn!lSbD!Sh4`3;Wi+eeHr^(@8)6k6|SjZgP01 zRV-S)X8EH_KJAewnV1K6f%kseUUfDfdeA7ldyFj2P| z2(WR1lX7wj8xtD~6EHQSCT?ImSiuyb8Jb!xmM3+SuU47Q+4Y1MDAFV_?vyg zB%_^I_FEqeu*`BPS@b4azToMqyWTl6%7wb|7mrTf_ilDqL2A=nvBy#Z;+?uH&i>`8 zi16gms5gGcP$j*kY|XZ7vD zgfB}yma`q&9rZ|F-p#e{$@H)40^1doit>wY9SbNuYOC5GyO`nSe6t=8%l^&QuO>9; zO`VnSrQ+NdKCWQ9MZf%S$gOLc+H15Y%(wGpg~Wq3vz&HSxkO3d%qT^yj_qTOn kM|4=VT-S5o^j^utZ?dN2cw%U$%SrWO_IFKB{k|3e0G_UPUH||9 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..8cbcf6f46ce8dcd0fb6e55441867a4608c032860 GIT binary patch literal 1090 zcmXqLVzD!5Vpdzg%*4pVBvQYH!T#)Y&#KeSzLS=8RTLj;iFG#MW#iOp^Jx3d%gD&h z%3zRW$Zf#M#vIDRCd?EXY$$9X2;y)Fb2%0iSS&nCU+yalX;MxjO;0c zCPpP>Z!@woFgG#sGXTZ8n3@lXWlhc{<6@_ zAeVa$Zs~tCyBS(Lx(wQVee%4v9DmC6_sxzm_*Wacv6#I#)Z>*hPs%}3Q09g3DcH!m}M zzME}-$);}wYkbz|o;?}o+I{{v$FroX4pX<9$DEPod-(p&(x<&A1GYQN=TcU6b?Bdx zUj1OoE6d9jOHD2wI{m=7UT>Su;iqa>RnM*J)hhopPh{Hk2Vdj$KD}68xZHa2OrI*H z#lG(g1g~CeYi#?H7fwq1S7-2zs_8d`#)~n?ko^>O~S}P zBuwq0phvQHX3=yH`M&5=acZK!OOzXoLyRRCDz!!3*x{YIYWcZo(++VRH_f_`f37RD zIVso6^6S55-|LI6bjZK$IKcnor?K1?f$fK41kGK{SvD+jncI`WTU{$#cV_G457+W- zMXMT?mRx?=Uwf(Jh2ioUN9FH7YKmOf(3s#R_GaGBE{9dpS`QQ3xOxisZ+hvx@ma(w zc&)N$X|k%KGE@HK=&108*Ije(pZQ)tKmPXdw<*i>UG(#PZ7V9c!nRmnw>qJnx@hvN w+Yzt&uIxN=z~GOOl<%%dJSvtmnyePgy!ZUclRbQUTGvOdj=AvX_8Ec00Cez?EdT%j literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..cfc99a85ae5042ceb24d8b6c9e6b2a590ea50363 GIT binary patch literal 2535 zcmY+^c{CJ?9tZFl##n|K%P_+T+4shNNf^lE5V$K@#gWnJV16} zF_AMLMC8mqi8Ikekla5NxR?k+ot$Xn~M>#NmCFSJ{YBz>nK+?yMFK5a?Ck*_l?X~x8Tp_ z>ABO^AR#DRkwsy)ru^V{rKZ7FrFm}*&+8jvSY5HlJcdLoI-E{xbE!l|OP(R{H}<_J zeS^9T?4+I{O!FJ>F9hAv?L}?g;cIA$L)I|KVm&P`wJ`c|PeX!P)2J_*Pdi6TG-q2} zi@IsnHiK-C9*i)$Y626=b~%@Y!}|mq?3&(EFU{wL-1Qs*3)8N(Rl(FCRK=r_{3!cuB1iS_3>rp_YPEiMc()*P@3_rsan*Z{sf61SAca-m`!^j zUSb6Orrmr@Hbz5xO}2>Tx1l#CuNdt|J;rXj90~6VJIY+=pEdb3se=$rl#PiG8pU&W zsJI?WZ)z%}Nzy;t*Ij&@UHFsTlSwX0%M-;&7M*I`@6s<~3Wi;r%G2N6BAcfWAI4#X z%aw(UuV>IH*C1Z5^M>J$dA|af9lw6th%)|>eQ|I9=aqG~c|+-}VfWQ7!ZiV`Q}D0) zm-zA33|s_=oT0d_`gOf7Q*=>Y&f^=S$D{2mZW~2*)f!qm&yQj(l3V zbFL9sXGDDOLDF;YmXVkVeLkz=@imB~9b74)4^T+HA+qxgySze&Qc?PMgwP1Ye2`i+1{tvpVqtpj;hKEg?*}yCB>)ARZd?if+ zuNn$dBh9ELsZ{6%+v8(WD=jR1bwMw3;El1-XOI#V;uL}?CmmT>SO!M0^?;SB%jxyn zTrLI2d&^aKQV3(d!|9h<8NdX{PB({)G@?Z&Q^3FKauoW4wU0IkUt=_c72N+AP&C7| zyaK=aHS^PR%NyY!H!I_X%<#eh`*vwq<;^srbc{Z(2N5h{4QAR`zE=#y5APOrkNAdU zlsmRoO-Ku?G#^7lqWdNXwW6^ll?L3t5zpHk4I^XGCf9Siz&Z&JVo#$|8W(lPwuk7~ zY5Y)}G|7Kadsc}wXz_X}6w|+tr)lxjKc>&FRy|R?Y8k8sXf^X0x7xa0vYH^@?4VIc z-Ej@!cTuAWewJr`t&nSo^=*i|f8K$5tU4p<8`4#gB7wngR_AN4Oby_oYepf));^ES zugXzS+OTCbkq!GlAQThX96&_2%O~;0ld%9p{=)+okmbaY^%Fpr|4$9pzttE;|6V%f zH*NU08XzL*syB5M9W*E1fYf@50dBEZ-sBg1NaYpVJ^&s#w;=Z^tNKmun_8X|V=)KQas09CZo6=INj(-& zsdZV|&Xx+#>vF2ytXH)BURP&Bye`#^2Q~g2CK<$F`L>|^)5hgfL37b@Zn-I;kqf2B zJ=}=;ZtJatiWKB~$5s3nYbox#f_9{G;2r-?3ieh0?6c(T*d#A```EE4s}E;U@)4bV zYeArWZYUn_X<1-?-(RCe4fP!H1OQ%(WW1rhVCNO}(msF2DqKT_1n$gD#UM_**DHz- z%&EkBY>e08K2@|R=2tZcgZEX;qWr55CwL5n&OK)Spnf^BWr@_$7=iV&iU*A}Ss3$P ziHd|o9Gi-lsJeNt9x|AR!b>%u-5cbd7hzgg`ra3hMs4yUr*;%%gp>YMacT_u8TnFs zC0CWNUrTPXb#~9z;|#aK3i4KdS3tCWxWSeO*YUI2)k2L#8H>``SoveW0981QGMTQ`l7EM4?y)mfvn$ ziZ4AM%AORck5Wz`2(`3b0S;xV-hrlzA zQ0ZA3+$jY7FwM(g;qr4_f}SR5(0jv)mA$_x` z7sONBDAWL_e)3LCeU{Ioro4u|;?+#D`R#z=HP*ZWA(Q3G9S=}pb8=W@AAndW0nUrj`0mxC|-CW=? zpVn(LWVVg3z3&(k>|Ff_Qi5$TC@!T=p)9h7x172N^zFtP;9mKFdA(X~_%)qI6g4a) zl;lV|7xRh>o7|z4%6Cq4Js=i;2BH4Q+$t=__k&jAvx6&-HMkf*z#iZO2m#;$ z5&(yj?E?r#OZ{^qs3ru0PcZ`WLel!lV-^+dHj*RG. +//! +//! 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 0000000000000000000000000000000000000000..fe5bf80cff3c4e229641ec9994798d60c980a790 GIT binary patch literal 1480 zcmY*ZX;2eq7|w1EA%~ozMZgug3J70+> z?z%0ceEYLV9DVp-9XO{F`_dNRtSKp`Y zPJG_AVsYxr$csLoB^ow9{yMPdVfCgnK^6WY`~D~ox1{iU1$uP;BAwc$=)eA!D_=J5 z+@1Jp-nolUQXblGhHiM1IlUvhcIf$?SEcK?`i~CR&j|0o3|~lEy7uXV;^Aad)9uh- zi?(|lwGZsO+UkC&tLxh4YyTFAj~p{@*0se|uMZpiAl>YzZvN9Rd{4joaC)e8sINr% z+V4&C58Y=!Z(F&gJ?mN9s_JPzvyYH(S*9WWY1q{}kM>_^Kej2mSJCB?f4}Xm{`X4X zl1tIB7!pA>RyYV)VFW07LH)fT$S#3_2uu^u49OJWQ|j$Hd~(v(*R{BOn`cW%7^d9$ z;Kv3)%UmL4kW@MWf+RT5ftgMWVB2&($I%JV(HK97Y!oVtY!pVBqRnQU$&ZP`2?nS~ z!y#MnyKo9)=`qm)1TixmNnj5DZ2hyOsm@McMEPSB~DBkT5441e9f8k=H#7E-I zs<6Jl3!1<03F;Y?Bvifd{%Y!ZFj2U66Y?ZtctR=glF3K*4~EMimywC!w~$k7$5P^* z1c}hP6QyCrq7!CwtlUi-|H&+mxSywf*LeB`9e4JqBI4lV({+_zf31Ef&R$!y!C>s2 zf3bJaqrJY$qFLv8*YlYvp#1Tv+b8SdD>Bk{F`cGH+xoz)_0g7X#|t&e1BtCodvkMr zOLX+al+_J8&d=SoCb6Wezo**mZPh^3J<-bU_MF}A==Gl}Q?iN7T?q<5Y6Q0guHt))Tig1NEBcj!)b-I$+MTI{x~llkUSc|e_bAYzX^*7U@j<1MXQLsuKsg-sr^)YIvoz}4^wzUpp(eY6PozgpFDE_fC`|bW7-+sH_ z0*~|}@JL$rh+znZN3E$SJK<}4*!%gj&0){Enj8=&lTY!^_4bEBkWwlFF3bex$ixK_ zF*1EN!`LZwG0ob^N*hU`CaQw6(+tI;d6XUJY_!9PW>YL@E3px;AUfN{mC>w?tHP8( zE>uw>aIPMU0+9kxAc{cAc@9DwjF3ga1^HlHbeIkU2AvVh)a%l8g*?;^L#z;S#onRBQOLwrvw_jW?D`;xM^rEOO?`W6`KDs%@Sym z$&~hi<^2VQ4AbNwF(sm~m~}c!;eyPJbW9CY0k&apWWGt*15Fqy9V8WrJy3)QcpwGr zfgsV*x-nAc^zy&k&(6A4)V8kWUihjkbM(%fe>Blarb*b-`fVRRaCQ3Kjf0Qhadl zp3;5OV>T7lU+wuzTIcFR?yY3we=zRqD%>*X)Z~h5$zvB@`X%;g+Y3GD${&Yb->~}1 z^6{-p&DrLgzMQQy_n+e?)@)oMx~M!9J7Mi3(({uH?Xa#ta63O4Rqox@yr}!RW6@db z^&2yv!D2`R?eoAHzyliq!V4M~1wlg+7>K}l0Zo*|0ClZO{_puQbpyvq#g@_Snj`{g zdT{6@U}T}mGDs>7fglM9tRN+b0ql)0<2YtoYAV4$Jf{{Ho~V|f%Tp^WNw!q4B`FqQ z!{LxO;dMAoIGGXA0tEV$KoT(U*EUWdg-Sfo7#>9OBWMhQltREg1l*m$vM>zXUn1^S z-UT+?K~N~|K)EuT6BRfG%s`>mDin8Ap$yJ){LJZel~Ya>CkTq+0y!mo4kZi+Dx8A1 zkd=3vliwwAmP9#I4vsRdCo#jEdzAY~Bz>Ot_I&d5b1xERzw z@G%f*JI=PdfMW%U#!`KQ2=pSCx3<>+hn=nU8!!fW6yxx}tNfVf**Jy;GWf1M?dn zVPrXwu3Z9Ot~jCzy<#646wVZmBv}bjXJ*TGcHrCKleWrVRsN|FP zPDWkI{%zGY_56g$#KaeQ`VH}_EKfj!5Gz(RRdRk zFCM+Qdg0;BhPKcZkK`$vti}6lKCSYfc{~1_-O;ud-_84R6GZRUmOixjylEk)z18?1 L%t_5bV`BGzphyBa literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f3a9e317acfd3de23f3789e49836f43f7748d2a3 GIT binary patch literal 1485 zcmY*Zc~BEq9M0PukV~$RB39Yrpa>XV5($Vx{O)KQc*|$u%p&?iWMtbnzk@i?5=4L{@9uQ-uoTj``+(+ zkQI+YR@C1vL;!+#rcAZzM<$e6_I2a3O9(WFq)KqR87I(0qKqgZtptT;5G6*&Oj>9(fuI<3p&5G#(Ks7ZOj2g1Tq}oC zzKUFdY|(3dpcfBh3YoKHvIWz)A|yUYb|zf$no_5Q>vRU~dc7_pDvyJ@c^H`os}5-W zp`S!1ZCO=+a5@YJKp{&@)#=jm($_~tY9nEUle5OzAkj=Sl-XqCIYy&fD58j@%F)bS zB!!{DukzNon8_DaQ$v-Lu5wZ4V#X{8T&Nuo3xq825I`2l5EcMI&(rVtbtYzbkwroFNM~LBC-1Tdl zD=K@XdTk8-;M5dr}?#UfFVMb<$D zC+Kn?0A7d?D1ZSxS}F2_>PnS#?xJtgY#%Ob@g7_ij42NPer5n#CGMdTAQrm;Ai|*u zu61FE)V@*7FxKesaEzNgT4UsARD+SF;dVPt73noNK|$?&IH(Q!Kb*v9>w;(=Lj782 z5{%%kZ3RyX6|umV176(cat4Nr0jzoftG;oSMG#o^nWE|(X9Kg*f)OZbL78GRjq;qF zZXi%oIcl_&qgEruaDOyyDuh+%}4apn|qIRyV%Q2rF0hfJK^Xl@rbQaH*GR4JEW z!BLE~;AW@Y$U@Xc6Jd)QSxGLJZ^lK=_vZ#5wNgfmF>~~|nha4&SUA04^;Dygotv6)K?2RYWL+%lY*sBDF~6{%P_}aiFw$^F;1IW%$9} zJ8$YBT*7aA@M$6;;#LWLB(j(94TehqcfH&|aIen7MUiXKrN8rK;@kxUZ_kt+CsE17 zjt{$(6T7`9;=>ACWA2Y#!FL>~nHm21et(;^TRu9Nagg?F-I(>D+%&nrRp>}Ruh)uaWg6Rd)7y_{c%Jv?8pd;FN-)eK6N8zENRb$n0K zkXO_61+lDARCJ=D?#SiPr$x_y*?a6#bzSDmpLQyT8@Vj1)(zJ+m6RRgFWp w^^b-PKRiC2(=M6vyy|^md6}kQwC_9}`;kUE%cgoXc47Werueh+0MncQ0T`SD-T(jq literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..dca1bfc0dcdb695a68243b560ca34dbe971148a8 GIT binary patch literal 1450 zcmZ`(dr%a09KYY*L%G`n;Z7bVvgiqjJbrf|0@fsn0-+9plo2(YdkdT#x98sCaV5%K zief%^4u^>m81WjZcm!IQJ)EQ|5-5j)1Rtr53}zT_XqSh=AMMOjm6c9v78`68i}asUs*X zA{5&~P_#NxjVoz2sl`Il=wy;o(?+=)l(FU_)Hy__(-8k_CxN!Coe&ky+C^w_AGrux zv6lh~5*q_oIfxZ<=&w*fISlku#4#{{fqpYk9tTZ@@)gigAhgPJ%g%r7r2+sI?ub+< zcEm*m1#XnT05{F*_Ftr%jW(w`7z?9_B$6^>F>jL;j(I#|uanQ|6D{&-52drB#fzAW zb(_KJg;@lA4%ZB9IM57)h#3G*eMx@e-HGY0!uX$49>kAY z3$_ji?fdr_b>|Me%bra zvBqh)7t@DI&PJnUJt>zX$QDa)L00Hr_dV?1I2Y;qBni;HFX#M3rCofuK`f(0+ql2I z^&olB>`@(tj7!1=@u!cB)u0E}yXDzOU5etzN}_(b{QAWakIjLa%FePX$BC`!w+~sL z8|r_FI^=oX;fpP<(_dd2_@LohWIe(K98hjXB+!i5K$JYiU;XH;QOO{O2x?o>l64&c| zlx+9;;AH9ygnr&CV!v7i1DG;6vq3PP8Authj5+9B&*LgSfTk&E`exoMf14GY!=JW4f$cG3O05yoDJhzH)9cex2|cF7aY9GWauOL1!5%Hf%3|7Y z4>K8h=Br9csW6(LQW=6)h2f-DrDg|8kCBO3nx61l6i7!hTs9bE`7}N|emY8t(`rT@ z;M_Pv_KAa% z1qRHvVH$whH;dN~GdO^=BDdQ(QdCqtK6G{aBV}y;8G-Dc=KP)Q%j=@QOKcfxGJ(CS zR~l2z?g4jX-pN^SrnL@cw(V{9%)xeB+Vw7J<$WG)C%Uej-1Trn|2cQR_(>ts(;&T+ zJ&>pSc=BxV?_TSgD&6z$hz-TtO34q(+>5gekkn|ibzFAvZ(hM^yYTCc` T@B6B!|I~=>?ts;QQ(pf88iERQ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6a2de5fcf44582683330a22eeca5e4a51b8afbce GIT binary patch literal 1449 zcmXqLVqI#`#L~NfnTe5!i6i7s#2nQNQPKvyY@Awc9&O)w85y}*84M~7bq%!Hm_u2Z zc~o3d%Tjal3sQ>|JY5u=Q;SM6(=wA2OEUBG6dX%SGV+TuODYZ33{*hMxp`zA3kq^l zajFwG5Co~?;^Bg*^UO=uGvqel1PQVUGld2l$cghB85)=vnHgFd8<`kI0l8*Ct~r=% z7-i5n*-*+rf{lxFl3M5FPdX`#j67Vv-bO~gQ2~|~=7!b=mJoLvp}5;Kuec;JCnvSY zO2NG-H9fzmQX#l1zbIKj1EE*b5b8Q^6cfb^L?BM)MbU}s>Lx}dywP*iB>BAE~mg$9-aw*8>aWgyb&Wn2X@R2g(r?QD1`|V0UL|;<8 zbT#63{QV(_&hJ;S z$-DLU^QTpLx(l-HlNWaSm9QO5Q=c1{^7Q(Tcvn;9^Oc-px{PL5Cq@_6?fD*9`2E_k z)~ye3w;YrzR6A_%EV9CaxnkNX)}&=TzJF&FtPd;U{kHz&=Cd2Pe^|Sg1RmcoMb!A( zs;teclx8oV8n;(L{7y}A(s?FkMh3>kO-v#NO-up?Jiq{z6=r1o&%$KDU?30TDYHlz zh&70Wi@#lT{!Yz|g#r)$>}%K6`Se&k#K3`#Lz|6}m6e^5k;TNo$iM)`H(+d2$tWo) zu+rDhPcAOdO9Z7oz2y8{FxS9XHzla zX>Mw{P6fpQcb+oT`IjQ9(XXFUT^WO1S-?%vVs9m|T(xbRN*mlGNPPypm!)12ecM7{#=p zK~r8{jubKoLsAoA*;n5{927jNpcvsb;D(0`kOK}kJ{B<+5tjp2&(=Bbev+-KcISkh zj@IFGMW`dFBm*c*Ux$ptP)l2h0i6By}xfvK~AP{9BjHG}MoC1Kw z4QlBF%sGq<#syoCMEtq2UO+CXY0?&(6{UTT)_wf;XWO}E=-bMe4?hFZ~eagA{(~Ea%9<@WS*9! zVRvR?WJJGC;*XlDrDuFXYdZd%_?ef-q{=P8b*tl8#D3=j;V1hai0LRu^v@J4{@&8? z_Qmyi>3@DiD6j?0$a}#2@BUO%(Hma7U1fiK-*;5}OVk1LMSQi!J@rQu3|~9!T(fKP YmC{c?cXkDxa9+D9iXr5j$-7ru0Z3^J3IG5A literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b7ffa38037699f2d4cada6d4c9e146e7d4d62d7e GIT binary patch literal 48 zcmV-00MGw0E&>4nFa-t!D`jv5A_O2K#05BG08OZ7M#&`4JHU&LNQUrr!ay9qXGc{0)hbn0Iks+T>UO} zbjf4vC^AYvBwds{&|GcXYn__>&-=A5S0cNc3jHH!iUKJ^@hyD;mUuexkSDK3>~Rev4%pN@KA4XuUj z1OUKXih*u41A44&isnOh@aZFUmWe*G2e`EmjIn+|^~Lp?P?v*#&yyq4%IZq;mbjj` zDV4D;Me`F#Yz-KZcR1wpQ9A;~#OJq>ZQiqTHq@}Fm@?6e0-sV(zZ`>|+1@f1JiXYb zf1Nx!-puQtGKccC3rl>exHkXi*D~BTWk|JT=Y)|~q%OlRdjl9+S&HJ86EK}gV)u|R zz-g0PH!gQG>UgGwpAG{W_WzS?vOWfE3Tp9?WuxX0*RzkIJC%ZF-Im5SaHl zfDWPL2TDJ84vLRMlPCgSjmSa#B%~GoB%QN|6WEF3ZvNlgCwyfEj(E*j`+EPAS1(lF z^oCxup^U13I+inU0bEw-CcBQ|DF%}!G@X0q`nVD3h}C)6G8Y(szg;9E?%b5JE=PqM5L1tG>axSc#xeH`{kSc9 frbsT(aPx>xR+s8tCI%__hB-G12&9q^n@RC~!Ztm) literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a6961708bfd841d597f7b6b2c16d19dc3a1e54cf GIT binary patch literal 138 zcmV;50CoQ`frkPC05B5<2P%e0&OHJF1_&yKNX|V20S5$aFlzz<0R$jf!E=z%#VLSq zjXtD(V{w_iZQU8YnoOtt%4CoTRyUEML<2$q1aDXW%DKf4A74u>RX2E6dp~KVVX&)< szrMRjA6F4Z8O)@CrWa4X`Ee)c3`Q9z{`6aY;k5AvP<)cN=CjXSLH24llmGw# literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..35a55b2e20d612497f7007c1a7225e1cee42d706 GIT binary patch literal 1115 zcmXqLVhLwrWHxAGk!9o5YV&CO&dbQoxS)xJkEMx)3n&cUW24|-E?fS%Ky5X# z4P~DcYh2Q1$!|L694h7b;rVAK?k|t|UvmVnxzAK}*23?~hyTU-YiXUL<;iU&4#|5{v`9V4`I?z5o4s}uJ=W-{5jDkHwwqx{}4ThH5Ks&~Ge z=RKsAwRMH!H;L9Me2b22eyUt1X6nyw+op1PaltadB~PxT%#wFFxkYB?yq^>DqOw=< zv$1`9Y%L*r{_xGz1C3_s*SnGxC#_ofeePtbryk#?8yb4l{0{O7P`$S4dzMcBHqLT& zM|=0B#f&}a@|S+9y}rNVM)HZwM04>e=JnYO=W0UT*Y12V`MpGzLE@3b#V%JwzUQaR zUcJpvZB@H{)!uV=HCrAXdE&ff->TiU+|!yQC*{UOc)V2ouXgR3j;tJ06H~yde>qA$ zdKtAo)#g@9&)F8A(XumGXnuHWapH~GGZpnFAsJ=7E#K}lES>eV{q5!FXZhZyt*Emt@4Er+TIq2<-ADI*t4uIa>{-|&hEupJI>Z*J1k!xw5pXk zY2WLj50hURG=4xz_X`>yu{7Q{XuM<4c$1AAmIJsLnHE$TRG?%EW>BUGPQ3P~D)o0C zC{wUR%*yncoG8H8_EGZAu7>4mHTtGuNBy1pCxl$RS{IpYR%Djh`c-bx)~>RN)#V${ z=lx!0RCkdn0_0{XXy3=sr!-Q8BXr+p6frO`$HamMhds4E+q0;cny< zH56kJ;d43k^u%g+5oLwO!1mycjw`-uR2!%x6p0v0$w@FIGvqU*Fr+f*G88jpGNd!) z0m(dubOS>JMFUPYR&73JCMi}17Lh07dyC(O{`)AgsQi88xmMT7tyllBa6Fji;>*v_ IBMkB!0A94?{{R30 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5c0b0de5ea5e7b9aa755f7688b255b17bf6aad62 GIT binary patch literal 1123 zcmXqLVu@#BWHxAGQDx)QYV&CO&dbQoxS)wejHQW12q?^F(8Mf`kYZcV#LNK{Vqs!r zFlb`>i;!V7kYVG3n!v-zX28e7k=Jfz_e<;BT_z?DhK44l3wJ*UvV5PpXUn_h`S-u6 z^S%+S34d?(__&3bz^`A)S?miAN|K_i>C*;fhR}tI z8%~+bjGJw7Xrs-8JL2UFZ{I0Cn#;d*MfbDPC^p}X4?aCkIj|)}D>i@OgxuZc9h>={ zWK^F$a;PYw|7j=pBaO;*E&;aR`cBKsCCr%fukY^F>^pSm+tLoT3?puD2d2Z*qvpS< z61$n+XIC?W{E0-Z(P5 z%XRY|xMXK`C-SMaNA{2T^G>u(sQD3NzGiOomuqJ>&FC<<)|<$D;NpV!i_L!U#=L0% zy5oe0OjR=1rtp$2YmVNTzwyDZO;3JRwMglI&8QZecmDa8pOSpFH|=_Ym+suRcec9v zQJY_@WJ>l3o%m?)T+*=T&L0P{dl%}mM32;&DHrsbrV5@ocIUyt>qZA3ui7Cnm&42S z&@;Woi+HR&4*R}3Wo+H}!)CGXx~rdqx(arr>@s;TJ%Kv_@_Bum93|1PR(n{sGryR*!U}_+-||mUKcYC zyqI*wt7VC|>2{k5*SA<`b1_TRK9lfQ`6#N@aIWM{*$uHC_4e(5rLsNlZ+`R6z%}FL z%D2W_TR+^Dl~g+0-M6CZ{Lt$)XUEpAMv+Pqx|EwUj~gIkkbEx#z!oT_YE5F7&P8w(K*Qc7f9^ukiu*X?@b@-R(?#{xMgcX=fhdYZhl_;@9l=+>M-~hGHxtZGs|d`_!6eUw-bLTyuZPUvtmAFavdjA`wF=ISGbjhJ1z; zhExV!hGK?HhIEEJAeqOIZeVDjXu!$Fs?EpDB*n_WBJyRMWXnF0efKN<|4%b6E&Fpu TRJWFeBTlWoQTJi)bCBl%6TtR@ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..743543dd28d9811e4c10b5ef1acaf99c41b9873e GIT binary patch literal 1139 zcmXqLV##M>WHxAGF=gY_YV&CO&dbQoxS)wejirf22`DUQ(8R2ckYZcV#4G_65@BLw z08+dN8AbycHZG_MJdA7xd@LMH=d`Ysr>pxjF>x?7G%-EU|LYtkH#_{E{P#)EuNle) z2^-8=X=d9Tv}s4EoWSl_g90ykdBz^;kSSAUbu~_l{J4`NEZ|RV`HS`T|15-jZ9hHO zY_Uy^S&O^F>8+TS=$4Ykya(13bT5?6VLuxv*_ZM7f?(30?QK$b`#I`gvF?8P_R+hA zKZA}e+rHnD`|q8Q+E;5D_~JU>G`_tT;QJ@*?uFaeytnI~2$g?vwPAkylX+b){&%+M zS1e|?E@cx&!Mc&XWOLXPJY>?^3mvh@MgEz1v=8~r`L&o*~(@i zkscMbRKrr@-x8M@kFUM#;Hi1uJjwWtr*Nvvyw1A2iI?TCKa%91QEJ!be>U=mgX|8! z)z=w9{95AkFGhaNUVH0~bi}_~J$u!=xjC(Gdx_{M-R)#8owIU9ul|{ZS~=N^Wvf`D z_AZY~yAt%6>(Br2uLcXd@0=C+czv=}o6VDJmO@|UO}4Ciu;b5SnGH_oyuJsVO?sPp zXX^JCb%%C%EiFF#bk%H)s41ItZRE688hb2%a#3uvv_zwLdyUY2*1ReWxoh%nqC8i= zhit5uZJvCze!1R_d$-en=SSU?%iH)r+y2MWjT?8x?pE>nqu)O3)|D``BMv&D@e_Ic z+P9lsP=8VFTs`G;f!E`EGCg*pCgPFaR;vRZ7Ejrl62y6kk2P%m*X?`jOJgtOPO}c4 z8Q(OQ>6WdH)Af5YIt7pC*Qu>^v`gWftoFZ)?dV^rJi*s}dKT7CzGmMr^_gfHP<*uK zU$? zy(A@-`6b>TzRkhw{{7I}Es_Z;g@?Q+{0^1#=1H~hU%lnZ8D&u`4_ ze1FGA@vv9tfBGxe9y^nEozWofuvwnGbM|rGP5%NuubXQ=$C4+gDEdTm#QhDm?-yFO z&Dw79b;q|^jl1${4(r?BH|*NWdu;c_PtUGcvDz9men83(3mPA>G~PF8ykpRKlZ_je zGq@O;7E~Ejpkx+iP-cl^qbmZriiZeiD zc}@{SDLDy-WQKf(6oym=U4~+YOonuZJRq6JkZxdTplHC!#;VQ7%p}Fiz#_8z$RpW4 hoqL|**W&lJzRR05{ju;A7LKomQZD(7tUo}W0|1w~=)3>` literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..cbfa49b0459170e8a2c7d11c089ffdbe8494cca6 GIT binary patch literal 1131 zcmXqLVo7IWWHxAG(PiV*YV&CO&dbQoxS)wej-`o33MedQ(8Mf{kYZcV#4G?5;$dQB z08*?78AbycHZG_MJdA7xd@LMczPBYbW`;dyV&Y(EXkxmtMAT&A3x-EGW{7uApUb(tR($K#=FK*N8a~;k z-_LHA-ZSkVJJV?+%|#mo&oE@N?F{F5Rm7}Ve@XVP-J@S;-pBf1U9czd=?>0J{&k_b zrZ*N`x!t;F-B-?fAr1fDPY&g*tJf#7o!RkiGm~_xVUB3F7{kL?r|+0v$^16yK&F&n zLQ@61$lFDVKT1>nT%IPQWbc*z#`}rA2!~gPhxYcQNe9+S3$2vL4-&FZ;nf-SPMVt*w=x?%lZ&c4p15DQ-Kn7550;`el5=B;i$pz3&9> z$e!3ErR6{CqH>iV-%D9?ZkymGC7orhZLPZLuRD0(uU@%g##;`{2T=@971b9=v;N(< zaP`yHJ$EHJA0CjYUiG!@Wt;RAzirKI^B&$P<49b~;W5cvK`co>iS3WL>qgN<#`BK& z?ew&W_7**KCyG(x$hYI+VwsL>jH5M{@w@HyeE6k1bvxVZSPrSy4!KF^S8BEQu2lZb z>wip=@pO$*hR&haJmP9h9=~Xmb4>Na z9OV$R#|@v`HQLWSxzmy)6dSVN+-5pD_0O(VURmz2+fw6CHNO?pae1{9i|^X46TH~07r5VR zb(w9x$%Iu?`xd!KWEP%!_2H>raf(J>RoVWt$_f`8O^;UJxo~T4XtsV%&iCu`f5I<6 zRuyq$PDu^Dc9Q=S(5BISx58MS8Qn? zY(o@Y$b>zA-eH!~JV9uYh^bo8iLiM_zY0~^oo!F-ul>6JLPAi%8)$VO`A#?eb%qX*%p|W!y~X{KE~@5sE|%rQ{?S zk{R+DQW#PhbQy{nG8xht@_=L>L%M;XfuaE?8>==SGm{i61B=K*g&s%c@eh0{N0A+gLLI3~& literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..813f45bdd97580bffdbcf6a18d27f7bcf542d509 GIT binary patch literal 1163 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a09ca1a398a6d922600c97d014a7ae23dde0ec9a GIT binary patch literal 2710 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 C{QA8B literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6c4fd78a0dfae32b9655102f2ef946dcdf87c20e GIT binary patch literal 2726 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 Cc>7lX literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c0333930b3682606c0825f26f364901839280fc7 GIT binary patch literal 2750 zcmY+^cQhO99tZFwl9bfmwbck>#AvBat48f2v^H(+(VA@pv9}VVMv_-+)+#MkRcf!I zwOX6l)Cx6PT<Lbwha-+WU^RVz{@W6e}AXBRHHbZmr`^QHLKLzJds1 zqJF@@VOjd&+%?mNDal>BUX&4vz)WY~mH9u_ z&uez8QoPkv_VbwG_6yaG9g4N5#sbR`0-D-qSw{1(s#a9azV-}lri?KpTppv@o>6h( zs_-2)_6Vj>UBc}wS;u>ZBZQQ@(_&bcCPR^8BMA;);vl<$n`*+_X+A&-bE0TmC>Xc| zuf-vsVa0m3J_Nz0Dc?TW+q8ovRlu)_c8|w|C`>!!WJ}ea)>d+3H@#XJ<}-qmVrAWT zXKGbDEBUq}50wK++R_KkL2=qHw+Myb^|34O%ak=G)_t6tBcz>sL%lhLPBAzn9kPoc zeAT*_(yLb7;I^Iubg|WBP$iO^V4r!Ko0}@aA<&-Dr1g|mSrlB%p#4ike*d-lU z<3gx}B=D2*<1+wSe-&f~;+CNRrD>srmcN zRPLEMcKX!4EJR0k9OO!YuSP_{8@86~Y)y%l_1xn~b<=Pz!-+AwP_ zn1o>8ngwr~%?f>ut&ioz^Es$US`D*?wYJ7Kz1$hbFg@-#6DrYHo%)Q~hF2(oUD zMYa9-V^#NZ*9r?5M#ynfiGqzkNKgu0EX$p_JCNPPYe<2Eu%jPfPlLg z-oT>%H3WxAAvcHP5sjpc{mRJE){>$B4?$W~9GSW7=I4s}m+;ZVYqiwqV){(+dX~-o z(D8PI09N~LNdHY!Ej%*`KWja4i`9c?{TGLZ(RgGSm6$Kvv%D`MMXZx^0DbAX#8*<9)#Xu2tyg)MJ@TkA%9+|?A>>+iB%iw%O_>g8j-Im*EE-CAUZ^v0PWWA54*=UL6 zh_0+ZP33tAQv=v=?88JNCnKoSi_wF$m;pjGox!3G&7 zkO9>J<33wR!0?I4cahx$e0y;fOK1=HmO#f1ArE`UfR;P$&jpe>p{wlY>}0~^Ky>~e z^?HS!Rv|@k=@h75xYQ=?qTToBdX!TMIoiu^HzD#DAC{6sYrqnfxEXVzvB$uYdwxGF zpURwArgU9?mT%*}XW-*L_EHBcd^{d&T*Ns0{hYUM?9Pp}kJS8axKu`~Piwip-0=Zp zRnm7Y6!M3$WS!Sivzgi(RH6 zaI<2HibrHyIGNT%F9EmaAUh%}ykxf^a#%|<&PzPVlffU%@CwO>HIp@kd|Kb>^?W;k z?_H|>$%~>+ykTdHVBSbyR3`_E=lPnR2R%=y3N`@tVd70GZ_KnguG7eTh8_8C-moOclB(~hm`eM>LryY+i_|>Re zb8Ar8`rV?%_H|GVeF(2d27T6=4_u599bDVk|455%6#cFv7#$;Iup)*a?nkQk%(`wN zzEKY99deI|X1k>)=SD+DKWRtM)p&Heg#ZFt9_}%o%WwDDBAvWc!I&d}OV%zE1lts^ zAG!N>h(*^z%_Ubc>y)LBl19o6^x7Y<*@QGze#H?r87lZv`&zQ&$ts8eSJ0O^;5{(D zm_5Sii7ujan4@=LuUTK$CEY3%3yz+8;n(S>GlE z5kyvBBxRR|IZLM^wr|OX;vB7yE~CP}*?n*R;&jOwt^V$f9asKwc(wZwk%UJCW zd0f=9?@!k;X1&Ob8d$bODiqWlH8GZj-s(30OFW|AK=xp<`{i1!9n%2xt5*Mww&GR# z*w|$bH0L5vrbbLxG_G^j_`Lo|Cpq!^Tc@XyOf{14!rpf3$>r@ZIa=sEd{Dy zCC$^nx$x+>jf^=)pX_{P$CpvvvTLY5sSPJ^zCJw&|CTy^L0a?3n-)?h$VK1$sa)wR zt_HdtM20%wd(Ax4jlfZ-7zsArc(IU4hxs`#5}z%%A?@#kxagBVy^fNhm>Xv4A!C8L z_+EV5%GGP(_|igjmr*+{K1V3Ao#!4oy{>VN6znqUCj=DzEVxFZV~vQg31JhU`vx7| zzrE)>60v-XpE>17NBcwEvA4vs; uAZRH-91sAQ*?EFh, +} + +/// 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