#!/usr/bin/env bash set -euo pipefail # macOS signing identity sanity check for: # - Developer ID (outside Mac App Store) # - Mac App Store (Apple Distribution + 3rd Party Mac Developer Installer) ts() { date +"%H:%M:%S"; } if [[ "$(uname -s)" != "Darwin" ]]; then echo "Error: This script is for macOS only." >&2 exit 1 fi echo "==> [$(ts)] Strawberry macOS signing identity check" echo "==> [$(ts)] Host: $(sw_vers -productName 2>/dev/null || true) $(sw_vers -productVersion 2>/dev/null || true)" echo echo "==> [$(ts)] Keychains searched by 'security' (user)" security list-keychains -d user || true echo echo "==> [$(ts)] Valid code signing identities (must include private key)" codesigning_out="$(security find-identity -p codesigning -v 2>&1 || true)" echo "$codesigning_out" echo echo "==> [$(ts)] Valid installer/pkg identities (must include private key)" basic_out="$(security find-identity -p basic -v 2>&1 || true)" echo "$basic_out" echo echo "==> [$(ts)] Note" cat <<'EOF' - Apple uses multiple certificate types. The "basic" identity list can include certificates that are not usable for signing a Mac App Store upload package. - For App Store Connect uploads via .pkg, you typically need an *Installer* identity (e.g. "3rd Party Mac Developer Installer" or "Mac Installer Distribution") and it must have a private key on this Mac. EOF echo list_cert_labels() { local query="$1" # Extract "labl" lines like: "labl"="Apple Distribution: ..." security find-certificate -a -c "$query" 2>/dev/null \ | sed -n 's/.*"labl"="\(.*\)".*/\1/p' \ | sort -u } check_label_in_identities() { local label="$1" local out="$2" if echo "$out" | grep -Fq "$label"; then echo "YES" else echo "NO" fi } check_label_in_installer_identities() { local label="$1" local out="$2" # Only treat as installer-capable if the cert label itself is an installer cert. case "$label" in *Installer*|*installer*) ;; *) echo "NO"; return 0 ;; esac if echo "$out" | grep -Fq "$label"; then echo "YES" else echo "NO" fi } print_section() { local title="$1" shift local queries=("$@") echo "==> [$(ts)] ${title}" local any=0 local q for q in "${queries[@]}"; do local labels labels="$(list_cert_labels "$q" || true)" if [[ -z "$labels" ]]; then continue fi any=1 while IFS= read -r label; do [[ -z "$label" ]] && continue local in_codesign in_basic in_codesign="$(check_label_in_identities "$label" "$codesigning_out")" in_basic="$(check_label_in_installer_identities "$label" "$basic_out")" printf -- "- %s\n" "$label" printf -- " - codesigning identity: %s\n" "$in_codesign" printf -- " - installer identity: %s\n" "$in_basic" if [[ "$in_codesign" == "NO" && "$in_basic" == "NO" ]]; then printf -- " - note: certificate exists, but it is NOT a usable identity on this Mac (almost always missing private key)\n" fi done <<<"$labels" done if [[ "$any" -eq 0 ]]; then echo "(no matching certificates found)" fi echo } print_section "Expected for Developer ID (outside Mac App Store)" \ "Developer ID Application" \ "Developer ID Installer" print_section "Expected for Mac App Store submissions" \ "Apple Distribution" \ "Mac App Distribution" \ "3rd Party Mac Developer Application" \ "3rd Party Mac Developer Installer" \ "Mac Installer Distribution" echo "==> [$(ts)] Quick interpretation" cat <<'EOF' - If a certificate label appears above, but both: - codesigning identity: NO - installer identity: NO then the certificate is present but NOT usable for signing on this Mac. The most common cause is: the private key is missing. Fix: - Open Keychain Access → login → "My Certificates" - Expand the certificate. You must see a private key underneath it. - If there is no private key: - Recreate the certificate on this Mac via Xcode (Accounts → Manage Certificates), OR - Import a .p12 that includes the private key from the machine where it was created. EOF echo echo "==> [$(ts)] Provisioning profiles (Mac App Store builds require one)" prof_dirs=( "${HOME}/Library/Developer/Xcode/UserData/Provisioning Profiles" "${HOME}/Library/MobileDevice/Provisioning Profiles" ) any_prof=0 for prof_dir in "${prof_dirs[@]}"; do if [[ -d "${prof_dir}" ]]; then any_prof=1 echo " ${prof_dir}" ls -la "${prof_dir}" | head -n 20 echo fi done if [[ "$any_prof" -eq 0 ]]; then echo "(no provisioning profile directories found in common locations)" fi echo "Tip: to pick the right MAS profile for a bundle id, run:" echo " ./build_tools/macos/find_mas_provisioning_profile.sh --bundle-id com.dryark.strawberry" \n\necho\n echo "==> [$(ts)] Recommended SHA-1 values to use (avoids ambiguity when names are duplicated)" cat <<'EOF' When you have multiple identities with the same display name, prefer using the SHA-1 hash in scripts: --codesign-identity "" --installer-identity "" This prevents codesign/productbuild from picking an unexpected identity. EOF echo extract_identities() { local policy="$1" # codesigning | basic # Output: SHA1|LABEL security find-identity -p "$policy" -v 2>/dev/null \ | sed -n 's/^[[:space:]]*[0-9][0-9]*[)] \([0-9A-F]\{40\}\) "\(.*\)"$/\1|\2/p' } print_sha_list() { local title="$1" local policy="$2" local label_match="$3" echo "$title" local matches matches="$(extract_identities "$policy" | grep -F "$label_match" || true)" if [[ -z "$matches" ]]; then echo " (none found)" return 0 fi local first=1 while IFS='|' read -r sha label; do [[ -z "$sha" || -z "$label" ]] && continue if [[ $first -eq 1 ]]; then echo " recommended: $sha ($label)" first=0 else echo " alternative: $sha ($label)" fi done <<<"$matches" } print_sha_list "Mac App Store (app signing) [use with --codesign-identity]:" "codesigning" "Apple Distribution:" print_sha_list "Mac App Store (pkg signing) [use with --installer-identity]:" "basic" "3rd Party Mac Developer Installer:" print_sha_list "Developer ID (app signing) [outside App Store]:" "codesigning" "Developer ID Application:" print_sha_list "Developer ID (pkg signing) [outside App Store]:" "basic" "Developer ID Installer:"