From 224a8e0e7aae0e23dc2b70ca872b6d505b3a73ba Mon Sep 17 00:00:00 2001 From: Hammed Abass Date: Thu, 30 Jul 2026 21:27:34 +0200 Subject: [PATCH] fix(release): enforce Apple hardened runtime --- Makefile | 3 +- apple/scripts/build-dmg.sh | 6 ++ apple/scripts/sign-exported-app.sh | 42 ++++++++++ apple/scripts/tests/test-sign-exported-app.sh | 78 +++++++++++++++++++ packaging/release/test-release-config.sh | 13 ++++ 5 files changed, 141 insertions(+), 1 deletion(-) create mode 100755 apple/scripts/sign-exported-app.sh create mode 100755 apple/scripts/tests/test-sign-exported-app.sh diff --git a/Makefile b/Makefile index f0464b5..d33d5ef 100644 --- a/Makefile +++ b/Makefile @@ -71,8 +71,9 @@ check-version: ## Validate the canonical version and its platform mappings. cd $(ROOT) && $(GRADLE) verifyVersion $(GRADLE_FLAGS) check-release: ## Validate coordinated release scripts and workflow YAML. - cd $(ROOT) && bash -n apple/scripts/notarize.sh apple/scripts/tests/test-notarize.sh packaging/android/build-release.sh packaging/android/verify-apk-signature.sh packaging/android/tests/test_verify_apk_signature.sh packaging/release/assemble-release.sh packaging/release/test-assemble-release.sh packaging/release/test-release-config.sh + cd $(ROOT) && bash -n apple/scripts/notarize.sh apple/scripts/sign-exported-app.sh apple/scripts/tests/test-notarize.sh apple/scripts/tests/test-sign-exported-app.sh packaging/android/build-release.sh packaging/android/verify-apk-signature.sh packaging/android/tests/test_verify_apk_signature.sh packaging/release/assemble-release.sh packaging/release/test-assemble-release.sh packaging/release/test-release-config.sh cd $(ROOT) && apple/scripts/tests/test-notarize.sh + cd $(ROOT) && apple/scripts/tests/test-sign-exported-app.sh cd $(ROOT) && packaging/android/tests/test_verify_apk_signature.sh cd $(ROOT) && packaging/release/test-assemble-release.sh cd $(ROOT) && packaging/release/test-release-config.sh diff --git a/apple/scripts/build-dmg.sh b/apple/scripts/build-dmg.sh index 0071a0f..c0e4186 100755 --- a/apple/scripts/build-dmg.sh +++ b/apple/scripts/build-dmg.sh @@ -105,6 +105,12 @@ ACTUAL_BUILD="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleVersion' \ exit 1 } +echo "==> Enforcing hardened-runtime signature" +"$SCRIPT_DIR/sign-exported-app.sh" \ + "$APP" \ + "$DEVELOPER_ID_APP" \ + "$APPLE_DIR/VniDrop/Resources/VniDropDirect.entitlements" + # --- Build the DMG ----------------------------------------------------------- DMG="$DIST_DIR/$APP_NAME-$VERSION.dmg" rm -f "$DMG" diff --git a/apple/scripts/sign-exported-app.sh b/apple/scripts/sign-exported-app.sh new file mode 100755 index 0000000..32fcba5 --- /dev/null +++ b/apple/scripts/sign-exported-app.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if [[ $# -ne 3 ]]; then + printf 'Usage: %s \n' "$0" >&2 + exit 2 +fi + +app=$1 +signing_identity=$2 +entitlements=$3 + +[[ -d $app ]] || { + printf 'error: exported app bundle does not exist: %s\n' "$app" >&2 + exit 1 +} +[[ -n $signing_identity ]] || { + printf 'error: signing identity is empty\n' >&2 + exit 1 +} +[[ -f $entitlements ]] || { + printf 'error: entitlements file does not exist: %s\n' "$entitlements" >&2 + exit 1 +} + +codesign \ + --force \ + --sign "$signing_identity" \ + --options runtime \ + --timestamp \ + --entitlements "$entitlements" \ + "$app" +codesign --verify --deep --strict --verbose=2 "$app" + +signature_details="$(codesign --display --verbose=4 "$app" 2>&1)" +printf '%s\n' "$signature_details" +printf '%s\n' "$signature_details" | + grep -Eq 'flags=.*\(runtime([^)]*)?\)' || { + printf 'error: exported app signature does not enable the hardened runtime\n' >&2 + exit 1 + } diff --git a/apple/scripts/tests/test-sign-exported-app.sh b/apple/scripts/tests/test-sign-exported-app.sh new file mode 100755 index 0000000..18fcad9 --- /dev/null +++ b/apple/scripts/tests/test-sign-exported-app.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash + +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +sign_exported_app="$script_dir/../sign-exported-app.sh" +scratch="$(mktemp -d "${TMPDIR:-/tmp}/vnidrop-codesign-test.XXXXXX")" +trap 'rm -rf "$scratch"' EXIT + +mkdir -p "$scratch/bin" "$scratch/VniDrop.app/Contents/MacOS" +app="$scratch/VniDrop.app" +entitlements="$scratch/VniDropDirect.entitlements" +calls="$scratch/calls.txt" +printf '\n' > "$entitlements" +printf 'binary\n' > "$app/Contents/MacOS/VniDrop" + +cat > "$scratch/bin/codesign" <<'SCRIPT' +#!/usr/bin/env bash +set -euo pipefail + +printf '%s\n' "$*" >> "$FAKE_CODESIGN_CALLS" +case " $* " in + *" --display "*) + if [[ ${FAKE_CODESIGN_MODE:-runtime} == missing-runtime ]]; then + printf '%s\n' \ + 'CodeDirectory v=20500 size=123 flags=0x0(none) hashes=1+0 location=embedded' \ + >&2 + else + printf '%s\n' \ + 'CodeDirectory v=20500 size=123 flags=0x10000(runtime) hashes=1+0 location=embedded' \ + >&2 + fi + ;; + *" --verify "*) + if [[ ${FAKE_CODESIGN_MODE:-runtime} == verify-error ]]; then + printf '%s\n' 'invalid signature' >&2 + exit 1 + fi + ;; +esac +SCRIPT +chmod +x "$scratch/bin/codesign" + +PATH="$scratch/bin:$PATH" \ + FAKE_CODESIGN_CALLS="$calls" \ + "$sign_exported_app" \ + "$app" \ + 'Developer ID Application: Example (ABCDEFGHIJ)' \ + "$entitlements" >/dev/null +grep -F -- \ + '--force --sign Developer ID Application: Example (ABCDEFGHIJ) --options runtime --timestamp --entitlements' \ + "$calls" >/dev/null +grep -F -- '--verify --deep --strict --verbose=2' "$calls" >/dev/null +grep -F -- '--display --verbose=4' "$calls" >/dev/null + +if PATH="$scratch/bin:$PATH" \ + FAKE_CODESIGN_CALLS="$calls" \ + FAKE_CODESIGN_MODE=missing-runtime \ + "$sign_exported_app" \ + "$app" \ + 'Developer ID Application: Example (ABCDEFGHIJ)' \ + "$entitlements" >/dev/null 2>&1; then + printf 'A signature without the hardened runtime must fail\n' >&2 + exit 1 +fi + +if PATH="$scratch/bin:$PATH" \ + FAKE_CODESIGN_CALLS="$calls" \ + FAKE_CODESIGN_MODE=verify-error \ + "$sign_exported_app" \ + "$app" \ + 'Developer ID Application: Example (ABCDEFGHIJ)' \ + "$entitlements" >/dev/null 2>&1; then + printf 'Signature verification errors must fail\n' >&2 + exit 1 +fi + +printf 'Exported app signing tests passed.\n' diff --git a/packaging/release/test-release-config.sh b/packaging/release/test-release-config.sh index ce16c5c..45f99ca 100755 --- a/packaging/release/test-release-config.sh +++ b/packaging/release/test-release-config.sh @@ -25,4 +25,17 @@ grep -F 'run: make build-apple-dmg' \ exit 1 } +signing_line="$( + awk '/sign-exported-app\.sh/ {print NR; exit}' \ + "$repo_root/apple/scripts/build-dmg.sh" +)" +dmg_line="$( + awk '/echo "==> Building DMG"/ {print NR; exit}' \ + "$repo_root/apple/scripts/build-dmg.sh" +)" +[[ -n $signing_line && -n $dmg_line && $signing_line -lt $dmg_line ]] || { + printf 'The exported app must enforce hardened-runtime signing before DMG creation\n' >&2 + exit 1 +} + printf 'Release configuration tests passed.\n'