From 643c96b74d3106bb8d2e72fd2183ca2f43a70428 Mon Sep 17 00:00:00 2001 From: Ozgur Ersoy Date: Wed, 16 Apr 2025 13:11:26 +0200 Subject: [PATCH] fix(actions): improve macOS notarization workflow by enhancing signing process with detailed identity verification, refined component signing, and comprehensive error handling --- .gitea/workflows/test-macos-build.yml | 376 +++++++++++--------------- 1 file changed, 151 insertions(+), 225 deletions(-) diff --git a/.gitea/workflows/test-macos-build.yml b/.gitea/workflows/test-macos-build.yml index b497a33b..7e9a531e 100644 --- a/.gitea/workflows/test-macos-build.yml +++ b/.gitea/workflows/test-macos-build.yml @@ -341,134 +341,103 @@ jobs: security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME" security list-keychains security default-keychain - security find-identity -v "$KEYCHAIN_NAME" | grep "Developer ID Application" - # Check if certificate is available - if [[ "$CERTIFICATE_AVAILABLE" == "false" ]]; then - debug_log "No certificate available and fallback disabled. Skipping signing." - echo "SIGNING_RESULT=none" >> $GITHUB_ENV - exit 0 + # Verify certificate exists + IDENTITY_INFO=$(security find-identity -v "$KEYCHAIN_NAME") + EXACT_IDENTITY=$(echo "$IDENTITY_INFO" | grep "Developer ID Application" | head -1 | sed -E 's/.*"(Developer ID Application: .*)"/\1/') + + if [[ -z "$EXACT_IDENTITY" ]]; then + debug_log "ERROR: No Developer ID Application certificate found in keychain" + debug_log "$IDENTITY_INFO" + echo "SIGNING_RESULT=false" >> $GITHUB_ENV + exit 1 fi - # Make sure entitlements file is defined and reset variable name + debug_log "Found signing identity: $EXACT_IDENTITY" + SIGNING_IDENTITY="$EXACT_IDENTITY" + + # Get hash ID if available for direct signing + if [[ "$IDENTITY_INFO" =~ ([0-9A-F]{40}) ]]; then + HASH_ID="${BASH_REMATCH[1]}" + debug_log "Using certificate hash: $HASH_ID" + else + HASH_ID="" + debug_log "No certificate hash found, using identity name" + fi + + # Make sure entitlements file path is set ENTITLEMENTS_PATH="${{ env.ENTITLEMENTS_FILE }}" - - # Sign the app - if [[ "$CERTIFICATE_AVAILABLE" == "true" ]]; then - debug_log "Signing with Developer ID certificate" - debug_log "Using identity: $SIGNING_IDENTITY" - - # First remove existing signatures - debug_log "Removing existing signatures..." - codesign --remove-signature "$APP_PATH" || true - - # Sign all dynamic libraries and frameworks - debug_log "Signing embedded binaries and frameworks..." - - # Check if the directories exist before trying to sign files within them - if [ -d "$APP_PATH/Contents/MacOS" ]; then - find "$APP_PATH/Contents/MacOS" -type f -name "*.dylib" -exec codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" {} \; 2>/dev/null || true - else - debug_log "No MacOS directory found, skipping dylib signing" - fi - - if [ -d "$APP_PATH/Contents/Frameworks" ]; then - debug_log "Signing Frameworks directory contents" - find "$APP_PATH/Contents/Frameworks" -type f -depth 1 -exec codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" {} \; 2>/dev/null || true - find "$APP_PATH/Contents/Frameworks" -name "*.framework" -exec codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" {} \; 2>/dev/null || true - else - debug_log "No Frameworks directory found, skipping frameworks signing" - fi - - # Sign all executables - debug_log "Signing executables..." - if [ -d "$APP_PATH/Contents/MacOS" ]; then - find "$APP_PATH/Contents/MacOS" -type f -exec codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" {} \; 2>/dev/null || true - fi - - # Try with exact hash ID if available - if [[ "$IDENTITY_INFO" =~ ([0-9A-F]{40}) ]]; then - HASH_ID="${BASH_REMATCH[1]}" - debug_log "Trying to sign with hash ID: $HASH_ID" - - # Sign app bundle with hash ID - debug_log "Signing main app bundle with hash ID..." - codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$HASH_ID" "$APP_PATH" - SIGN_RESULT=$? - else - # Sign app bundle - debug_log "Signing main app bundle..." - codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" "$APP_PATH" - SIGN_RESULT=$? - fi - - if [ $SIGN_RESULT -eq 0 ]; then - debug_log "App signed successfully with Developer ID" - echo "SIGNING_RESULT=true" >> $GITHUB_ENV - else - debug_log "App signing failed with Developer ID" - debug_log "Attempting to continue with unsigned app" - echo "SIGNING_RESULT=none" >> $GITHUB_ENV - fi - - elif [[ "$CERTIFICATE_AVAILABLE" == "adhoc" ]]; then - debug_log "Signing with ad-hoc identity (not suitable for distribution)" - - # Remove existing signatures - codesign --remove-signature "$APP_PATH" || true - - # Sign with ad-hoc identity - codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign - "$APP_PATH" - - SIGN_RESULT=$? - if [ $SIGN_RESULT -eq 0 ]; then - debug_log "App signed successfully with ad-hoc identity" - echo "SIGNING_RESULT=ad-hoc" >> $GITHUB_ENV - else - debug_log "App signing failed with ad-hoc identity" - debug_log "Attempting to continue with unsigned app" - echo "SIGNING_RESULT=none" >> $GITHUB_ENV - fi + if [[ ! -f "$ENTITLEMENTS_PATH" ]]; then + debug_log "WARNING: Entitlements file not found at $ENTITLEMENTS_PATH, will use default entitlements" else - debug_log "Unexpected certificate state. Skipping signing." - echo "SIGNING_RESULT=none" >> $GITHUB_ENV + debug_log "Using entitlements file: $ENTITLEMENTS_PATH" fi - # Verify signing - debug_log "Verifying app signature..." - codesign -dvv "$APP_PATH" || debug_log "App verification failed but continuing" - shell: bash - - - name: Verify notarization and stapling - id: verify-notarization - if: env.SIGNING_RESULT == 'true' - run: | - # Debug log helper - function debug_log() { - echo "DEBUG: $1" - if [[ -n "$DEBUG_LOG_PATH" ]]; then - echo "$(date "+%Y-%m-%d %H:%M:%S") - $1" >> "$DEBUG_LOG_PATH" - fi - } + # First remove existing signatures + debug_log "Removing existing signatures..." + codesign --remove-signature "$APP_PATH" || true - debug_log "Verifying app signature and code requirements before notarization" + # Sign embedded components first + debug_log "Signing embedded components..." - # Verify code signature - codesign --verify --verbose "$APP_PATH" - if [ $? -ne 0 ]; then - debug_log "Error: App signature verification failed" - # Don't exit, just log the error + # Sign MacOS directory contents + if [ -d "$APP_PATH/Contents/MacOS" ]; then + debug_log "Signing MacOS executables and libraries..." + # Sign dylibs first + find "$APP_PATH/Contents/MacOS" -type f -name "*.dylib" -exec codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" {} \; 2>/dev/null || true + # Sign all executables + find "$APP_PATH/Contents/MacOS" -type f -perm +111 -exec codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" {} \; 2>/dev/null || true + # Sign any remaining files + find "$APP_PATH/Contents/MacOS" -type f -not -name "*.dylib" -not -perm +111 -exec codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" {} \; 2>/dev/null || true else - debug_log "App signature verification passed" + debug_log "No MacOS directory found" fi - # Check app for code requirements - codesign --display --requirements "$APP_PATH" - if [ $? -ne 0 ]; then - debug_log "Error: App doesn't meet requirements" - # Don't exit, just log the error + # Sign Frameworks if they exist + if [ -d "$APP_PATH/Contents/Frameworks" ]; then + debug_log "Signing Frameworks directory..." + # Sign individual files in Frameworks + find "$APP_PATH/Contents/Frameworks" -type f -not -path "*.framework/*" -exec codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" {} \; 2>/dev/null || true + # Sign framework bundles + find "$APP_PATH/Contents/Frameworks" -type d -name "*.framework" -exec codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" {} \; 2>/dev/null || true else - debug_log "App meets code requirements" + debug_log "No Frameworks directory found" + fi + + # Sign any plugins if they exist + if [ -d "$APP_PATH/Contents/PlugIns" ]; then + debug_log "Signing PlugIns directory..." + find "$APP_PATH/Contents/PlugIns" -type d -name "*.appex" -exec codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" {} \; 2>/dev/null || true + fi + + # Sign main app bundle (use hash ID if available, otherwise use identity name) + debug_log "Signing main app bundle..." + if [[ -n "$HASH_ID" ]]; then + codesign --force --deep --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$HASH_ID" "$APP_PATH" + else + codesign --force --deep --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" "$APP_PATH" + fi + + SIGN_RESULT=$? + if [ $SIGN_RESULT -eq 0 ]; then + debug_log "App signed successfully" + echo "SIGNING_RESULT=true" >> $GITHUB_ENV + + # Verify signature + debug_log "Verifying app signature..." + codesign -dvv "$APP_PATH" + VERIFY_RESULT=$? + + if [ $VERIFY_RESULT -eq 0 ]; then + debug_log "Signature verification successful" + else + debug_log "WARNING: Signature verification failed, app may not be properly signed" + # Continue anyway since the signing appeared to succeed + fi + else + debug_log "ERROR: App signing failed with exit code: $SIGN_RESULT" + echo "SIGNING_RESULT=false" >> $GITHUB_ENV + exit 1 fi shell: bash @@ -495,8 +464,7 @@ jobs: exit 1 fi - # Notarize the app using API key method - debug_log "Notarizing with API key method" + debug_log "ZIP archive created successfully at: $ZIP_PATH" # Submit for notarization debug_log "Submitting app for notarization..." @@ -508,15 +476,18 @@ jobs: cat "$WORK_DIR/notarization_output.txt" | tee -a "$DEBUG_LOG_PATH" + # Check if notarization was successful REQUEST_STATUS=$(grep -o "status: .*" "$WORK_DIR/notarization_output.txt" | cut -d ' ' -f2) if [[ "$REQUEST_STATUS" == "Accepted" ]]; then debug_log "Notarization successful" echo "NOTARIZATION_RESULT=true" >> $GITHUB_ENV else - debug_log "Notarization failed or timed out" + debug_log "ERROR: Notarization failed or timed out" + debug_log "Notarization status: $REQUEST_STATUS" cat "$WORK_DIR/notarization_output.txt" echo "NOTARIZATION_RESULT=false" >> $GITHUB_ENV + exit 1 fi shell: bash @@ -541,24 +512,57 @@ jobs: if [ $STAPLE_RESULT -eq 0 ]; then debug_log "Notarization ticket stapled successfully" echo "STAPLING_RESULT=true" >> $GITHUB_ENV - - # Verify stapling - debug_log "Verifying notarization stapling" - xcrun stapler validate "$APP_PATH" - if [ $? -eq 0 ]; then - debug_log "Stapling validation successful" - else - debug_log "Stapling validation failed, but continuing" - fi else - debug_log "Stapling failed" + debug_log "ERROR: Stapling failed with exit code: $STAPLE_RESULT" echo "STAPLING_RESULT=false" >> $GITHUB_ENV + exit 1 + fi + shell: bash + + - name: Verify notarization and stapling + id: verify-notarization + if: env.SIGNING_RESULT == 'true' && env.NOTARIZATION_RESULT == 'true' && env.STAPLING_RESULT == 'true' + run: | + # Debug log helper + function debug_log() { + echo "DEBUG: $1" + if [[ -n "$DEBUG_LOG_PATH" ]]; then + echo "$(date "+%Y-%m-%d %H:%M:%S") - $1" >> "$DEBUG_LOG_PATH" + fi + } + + debug_log "Verifying notarization and stapling" + + # Verify code signature with staple + debug_log "Verifying code signature..." + codesign --verify --deep --strict --verbose=2 "$APP_PATH" + VERIFY_RESULT=$? + + if [ $VERIFY_RESULT -eq 0 ]; then + debug_log "Code signature verification successful" + else + debug_log "ERROR: Code signature verification failed with exit code: $VERIFY_RESULT" + exit 1 + fi + + # Verify stapling + debug_log "Verifying stapling..." + xcrun stapler validate "$APP_PATH" + STAPLE_VERIFY_RESULT=$? + + if [ $STAPLE_VERIFY_RESULT -eq 0 ]; then + debug_log "Stapling verification successful" + echo "VERIFY_RESULT=true" >> $GITHUB_ENV + else + debug_log "ERROR: Stapling verification failed with exit code: $STAPLE_VERIFY_RESULT" + echo "VERIFY_RESULT=false" >> $GITHUB_ENV + exit 1 fi shell: bash - name: Remove quarantine attribute id: remove-quarantine - if: env.SIGNING_RESULT != 'none' + if: env.SIGNING_RESULT == 'true' && env.NOTARIZATION_RESULT == 'true' && env.STAPLING_RESULT == 'true' run: | # Debug log helper function debug_log() { @@ -578,6 +582,7 @@ jobs: - name: Package signed app id: package-app + if: env.SIGNING_RESULT == 'true' && env.NOTARIZATION_RESULT == 'true' && env.STAPLING_RESULT == 'true' run: | # Debug log helper function debug_log() { @@ -587,7 +592,7 @@ jobs: fi } - debug_log "Packaging the signed app" + debug_log "Packaging the signed and notarized app" # Check if we should use create-dmg if available if command -v create-dmg &> /dev/null; then @@ -638,105 +643,26 @@ jobs: if [ $DMG_CREATE_RESULT -eq 0 ]; then debug_log "DMG package created successfully at: $DMG_PATH" echo "DMG_CREATED=true" >> $GITHUB_ENV - else - debug_log "DMG creation failed" - echo "DMG_CREATED=false" >> $GITHUB_ENV - fi - - # If we have a properly signed app, sign the DMG as well - if [[ "$SIGNING_RESULT" == "true" && "$DMG_CREATED" == "true" ]]; then - debug_log "Signing DMG with Developer ID certificate" - codesign --force --timestamp --sign "$SIGNING_IDENTITY" "$DMG_PATH" - - if [ $? -eq 0 ]; then - debug_log "DMG signed successfully" - else - debug_log "DMG signing failed" - fi - - # If app was notarized, also notarize the DMG - if [[ "$NOTARIZATION_RESULT" == "true" ]]; then - debug_log "Notarizing DMG..." - - # Notarize the DMG using API key method - debug_log "Notarizing DMG with API key method" - - xcrun notarytool submit "$DMG_PATH" \ - --key "$API_KEY_FILE" \ - --key-id "${{ secrets.NOTARY_API_KEY_ID }}" \ - --issuer "${{ secrets.NOTARY_API_KEY_ISSUER_ID }}" \ - --wait > "$WORK_DIR/dmg_notarization_output.txt" 2>&1 - - cat "$WORK_DIR/dmg_notarization_output.txt" | tee -a "$DEBUG_LOG_PATH" - - DMG_REQUEST_STATUS=$(grep -o "status: .*" "$WORK_DIR/dmg_notarization_output.txt" | cut -d ' ' -f2) - - if [[ "$DMG_REQUEST_STATUS" == "Accepted" ]]; then - debug_log "DMG notarization successful" - - # Staple DMG - debug_log "Stapling notarization ticket to DMG" - xcrun stapler staple "$DMG_PATH" - if [ $? -eq 0 ]; then - debug_log "DMG stapling successful" - else - debug_log "DMG stapling failed" - fi - else - debug_log "DMG notarization failed or timed out" - cat "$WORK_DIR/dmg_notarization_output.txt" - fi - fi - fi - - # Final verification of all distribution artifacts - debug_log "Verifying final distribution artifacts" - - # Check ZIP file - if [[ -f "$ZIP_PATH" ]]; then - ZIP_SIZE=$(du -h "$ZIP_PATH" | cut -f1) - debug_log "ZIP package size: $ZIP_SIZE" - - # Verify ZIP integrity - unzip -t "$ZIP_PATH" > /dev/null - if [ $? -eq 0 ]; then - debug_log "ZIP package integrity verified" - else - debug_log "ZIP package may be corrupted" - fi - fi - - # Check DMG file - if [[ -f "$DMG_PATH" ]]; then - DMG_SIZE=$(du -h "$DMG_PATH" | cut -f1) - debug_log "DMG package size: $DMG_SIZE" - - # Verify DMG signature if signed - if [[ "$SIGNING_RESULT" == "true" ]]; then - codesign -vvv "$DMG_PATH" 2>&1 | tee -a "$DEBUG_LOG_PATH" || debug_log "DMG signature verification failed" - fi - fi - - # Set output variables - if [[ "$SIGNING_RESULT" == "true" ]]; then - echo "SIGNED_STATUS=true" >> $GITHUB_ENV - elif [[ "$SIGNING_RESULT" == "ad-hoc" ]]; then - echo "SIGNED_STATUS=ad-hoc" >> $GITHUB_ENV - else - echo "SIGNED_STATUS=none" >> $GITHUB_ENV - fi - - if [[ "$NOTARIZATION_RESULT" == "true" ]]; then - echo "NOTARIZED_STATUS=true" >> $GITHUB_ENV - else - echo "NOTARIZED_STATUS=false" >> $GITHUB_ENV - fi - - if [[ "$DMG_CREATED" == "true" ]]; then echo "PACKAGE_PATH=$DMG_PATH" >> $GITHUB_ENV else + debug_log "ERROR: DMG creation failed with exit code: $DMG_CREATE_RESULT" + debug_log "Falling back to ZIP package" + echo "DMG_CREATED=false" >> $GITHUB_ENV echo "PACKAGE_PATH=$ZIP_PATH" >> $GITHUB_ENV fi + + # Final verification of distribution artifacts + debug_log "Verifying final distribution package" + + FINAL_PACKAGE="${PACKAGE_PATH:-$ZIP_PATH}" + if [[ -f "$FINAL_PACKAGE" ]]; then + PACKAGE_SIZE=$(du -h "$FINAL_PACKAGE" | cut -f1) + debug_log "Distribution package size: $PACKAGE_SIZE" + debug_log "Distribution package ready at: $FINAL_PACKAGE" + else + debug_log "ERROR: Distribution package not found" + exit 1 + fi shell: bash - name: Clean up