Fix Mac codesigning to use correct keychain in CI (#5697)

* Remove --keychain flag from codesign commands

The --keychain flag was causing codesign to look for the private key in
the build keychain, but the matching cert+key is in login.keychain.
Since we now use the unambiguous SHA-1 hash, codesign will find the
correct certificate and key pair in whichever keychain contains them.

This fixes the intermittent errSecInternalComponent failures.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Use build keychain specifically for CI codesigning

In CI, the workflow imports the signing certificate into a temporary
build keychain. Previously, the script searched ALL keychains and
picked the first certificate found, which could be an old certificate
from login.keychain instead of the freshly imported one.

Now when KEYCHAIN_NAME is set (CI environment), the script looks for
certificates only in that specific keychain. For local dev (no
KEYCHAIN_NAME), it still searches all keychains.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-29 21:51:15 -08:00
committed by GitHub
co-authored by Claude Opus 4.5
parent f8e8ca4fab
commit 5c8b324dc8
+25 -19
View File
@@ -5,33 +5,49 @@
# #
# Environment variables: # Environment variables:
# SIGNING_IDENTITY - The signing identity (default: "Developer ID Application") # SIGNING_IDENTITY - The signing identity (default: "Developer ID Application")
# KEYCHAIN_PASSWORD - Password to unlock the build keychain (optional) # KEYCHAIN_PASSWORD - Password to unlock the keychain (optional, CI only)
# KEYCHAIN_NAME - Name of the keychain to unlock and use for signing (default: "build.keychain") # KEYCHAIN_NAME - Name of the keychain containing the signing certificate (optional, CI only)
# When set, looks for certificate in this specific keychain.
# When not set, searches all keychains (local dev mode).
set -euxo pipefail set -euxo pipefail
APP_PATH="$1" APP_PATH="$1"
ENTITLEMENTS_PATH="${2:-}" ENTITLEMENTS_PATH="${2:-}"
SIGNING_IDENTITY="${SIGNING_IDENTITY:-Developer ID Application}" SIGNING_IDENTITY="${SIGNING_IDENTITY:-Developer ID Application}"
KEYCHAIN_NAME="${KEYCHAIN_NAME:-build.keychain}" # KEYCHAIN_NAME is set by CI workflow - don't set a default here so we can detect if we're in CI
if [ ! -d "$APP_PATH" ]; then if [ ! -d "$APP_PATH" ]; then
echo "ERROR: App not found at $APP_PATH" echo "ERROR: App not found at $APP_PATH"
exit 1 exit 1
fi fi
# Unlock keychain if password provided # Unlock keychain if password and keychain name are provided (CI environment)
if [ -n "${KEYCHAIN_PASSWORD:-}" ]; then if [ -n "${KEYCHAIN_PASSWORD:-}" ] && [ -n "${KEYCHAIN_NAME:-}" ]; then
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME" || true security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME" || true
fi fi
# Get the SHA-1 hash of the signing certificate # Get the SHA-1 hash of the signing certificate
# Using the hash avoids "ambiguous" errors when the same identity exists in multiple keychains # Using the hash avoids "ambiguous" errors when the same identity exists in multiple keychains
echo "Available codesigning identities:" # We look in the build keychain specifically if KEYCHAIN_NAME is set (CI environment)
security find-identity -v -p codesigning # Otherwise fall back to searching all keychains (local dev)
if [ -n "${KEYCHAIN_NAME:-}" ]; then
# CI environment: look for certificate in the build keychain specifically
KEYCHAIN_PATH="$HOME/Library/Keychains/${KEYCHAIN_NAME}-db"
echo "Looking for signing identity in build keychain: $KEYCHAIN_PATH"
echo "Available identities in build keychain:"
security find-identity -v -p codesigning "$KEYCHAIN_PATH"
CERT_HASH=$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" | grep -E '^\s+[0-9]+\)' | head -1 | awk '{print $2}')
else
# Local dev: search all keychains
echo "Available codesigning identities:"
security find-identity -v -p codesigning
CERT_HASH=$(security find-identity -v -p codesigning | grep -E '^\s+[0-9]+\)' | head -1 | awk '{print $2}')
fi
# Get the first valid identity hash (the hash is unique and unambiguous)
CERT_HASH=$(security find-identity -v -p codesigning | grep -E '^\s+[0-9]+\)' | head -1 | awk '{print $2}')
if [ -z "$CERT_HASH" ]; then if [ -z "$CERT_HASH" ]; then
echo "ERROR: No valid signing identity found" echo "ERROR: No valid signing identity found"
exit 1 exit 1
@@ -42,13 +58,11 @@ echo "Using certificate hash: $CERT_HASH"
SIGNING_IDENTITY="$CERT_HASH" SIGNING_IDENTITY="$CERT_HASH"
echo "=== Signing nested components first ===" echo "=== Signing nested components first ==="
echo "Using keychain: $KEYCHAIN_NAME"
# Sign all dylibs # Sign all dylibs
find "$APP_PATH" -name "*.dylib" -print0 | while IFS= read -r -d '' item; do find "$APP_PATH" -name "*.dylib" -print0 | while IFS= read -r -d '' item; do
echo "Signing dylib: $item" echo "Signing dylib: $item"
codesign --force --verify --verbose --timestamp --options runtime \ codesign --force --verify --verbose --timestamp --options runtime \
--keychain "$KEYCHAIN_NAME" \
--sign "$SIGNING_IDENTITY" "$item" --sign "$SIGNING_IDENTITY" "$item"
done done
@@ -56,7 +70,6 @@ done
find "$APP_PATH" -name "*.bundle" -print0 | while IFS= read -r -d '' item; do find "$APP_PATH" -name "*.bundle" -print0 | while IFS= read -r -d '' item; do
echo "Signing bundle: $item" echo "Signing bundle: $item"
codesign --force --verify --verbose --timestamp --options runtime \ codesign --force --verify --verbose --timestamp --options runtime \
--keychain "$KEYCHAIN_NAME" \
--sign "$SIGNING_IDENTITY" "$item" --sign "$SIGNING_IDENTITY" "$item"
done done
@@ -68,7 +81,6 @@ find "$APP_PATH" -name "*.xpc" -print0 | while IFS= read -r -d '' item; do
fi fi
echo "Signing XPC service: $item" echo "Signing XPC service: $item"
codesign --force --verify --verbose --timestamp --options runtime \ codesign --force --verify --verbose --timestamp --options runtime \
--keychain "$KEYCHAIN_NAME" \
--sign "$SIGNING_IDENTITY" "$item" --sign "$SIGNING_IDENTITY" "$item"
done done
@@ -80,7 +92,6 @@ find "$APP_PATH" -path "*/Frameworks/*.app" -print0 | while IFS= read -r -d '' i
fi fi
echo "Signing nested app: $item" echo "Signing nested app: $item"
codesign --force --verify --verbose --timestamp --options runtime \ codesign --force --verify --verbose --timestamp --options runtime \
--keychain "$KEYCHAIN_NAME" \
--sign "$SIGNING_IDENTITY" "$item" --sign "$SIGNING_IDENTITY" "$item"
done done
@@ -92,7 +103,6 @@ find "$APP_PATH" -path "*/Frameworks/*/Versions/*/Autoupdate" -type f -print0 |
fi fi
echo "Signing executable: $item" echo "Signing executable: $item"
codesign --force --verify --verbose --timestamp --options runtime \ codesign --force --verify --verbose --timestamp --options runtime \
--keychain "$KEYCHAIN_NAME" \
--sign "$SIGNING_IDENTITY" "$item" --sign "$SIGNING_IDENTITY" "$item"
done done
@@ -102,12 +112,10 @@ find "$APP_PATH" -name "*.framework" -print0 | while IFS= read -r -d '' item; do
if [[ "$item" == *"Sparkle.framework" ]]; then if [[ "$item" == *"Sparkle.framework" ]]; then
echo "Signing Sparkle framework with --deep: $item" echo "Signing Sparkle framework with --deep: $item"
codesign --deep --force --verify --verbose --timestamp --options runtime \ codesign --deep --force --verify --verbose --timestamp --options runtime \
--keychain "$KEYCHAIN_NAME" \
--sign "$SIGNING_IDENTITY" "$item" --sign "$SIGNING_IDENTITY" "$item"
else else
echo "Signing framework: $item" echo "Signing framework: $item"
codesign --force --verify --verbose --timestamp --options runtime \ codesign --force --verify --verbose --timestamp --options runtime \
--keychain "$KEYCHAIN_NAME" \
--sign "$SIGNING_IDENTITY" "$item" --sign "$SIGNING_IDENTITY" "$item"
fi fi
done done
@@ -117,12 +125,10 @@ echo "=== Signing main app bundle ==="
if [ -n "$ENTITLEMENTS_PATH" ] && [ -f "$ENTITLEMENTS_PATH" ]; then if [ -n "$ENTITLEMENTS_PATH" ] && [ -f "$ENTITLEMENTS_PATH" ]; then
echo "Using entitlements: $ENTITLEMENTS_PATH" echo "Using entitlements: $ENTITLEMENTS_PATH"
codesign --force --verify --verbose --timestamp --options runtime \ codesign --force --verify --verbose --timestamp --options runtime \
--keychain "$KEYCHAIN_NAME" \
--entitlements "$ENTITLEMENTS_PATH" \ --entitlements "$ENTITLEMENTS_PATH" \
--sign "$SIGNING_IDENTITY" "$APP_PATH" --sign "$SIGNING_IDENTITY" "$APP_PATH"
else else
codesign --force --verify --verbose --timestamp --options runtime \ codesign --force --verify --verbose --timestamp --options runtime \
--keychain "$KEYCHAIN_NAME" \
--sign "$SIGNING_IDENTITY" "$APP_PATH" --sign "$SIGNING_IDENTITY" "$APP_PATH"
fi fi