Skip to content

Commit 407d409

Browse files
katipallyCopilot
andcommitted
fix(release): use Manual signing in archive, drop re-sign pass
AMFI rejected v2.4.0-v2.4.2 with 'Restricted entitlements not validated - No matching profile found (count=3)' for the 3 iCloud entitlements. Root cause: codesign --entitlements <file> in the re-sign step embedded a verbatim entitlements plist. The Developer ID profile authorizes icloud-services as wildcard string "*" while our .entitlements declares it as array ["CloudKit"]. AMFI does a strict format match against the embedded profile and rejected. Fix: archive with CODE_SIGN_STYLE=Manual + Developer ID identity + the DoomCoder Mac DevID profile. Xcode reconciles entitlements properly against the profile at sign time, and signs all nested binaries (Sparkle, XPC services, frameworks) correctly. No re-sign pass needed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent f47af62 commit 407d409

1 file changed

Lines changed: 27 additions & 91 deletions

File tree

.github/workflows/release.yml

Lines changed: 27 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -136,32 +136,28 @@ jobs:
136136
137137
- name: Build Release Archive
138138
run: |
139-
# Archive with Automatic signing + allowProvisioningUpdates.
140-
# The API key lets Xcode auto-create/download the Mac Development
141-
# provisioning profile for iCloud/App Groups entitlements.
142-
# CODE_SIGN_IDENTITY is NOT overridden here — that conflicts with
143-
# Automatic style. The export + re-sign steps apply Developer ID.
139+
# Manual signing with Developer ID identity + our Developer ID
140+
# provisioning profile. Xcode reconciles app entitlements with the
141+
# profile's authorized entitlements at sign time — no re-sign step
142+
# is needed (and re-sign was the root cause of AMFI rejections in
143+
# v2.4.0–v2.4.2: codesign --entitlements embedded a verbatim plist
144+
# whose format/values did not match the embedded profile).
144145
xcodebuild \
145146
-project DoomCoder.xcodeproj \
146147
-scheme DoomCoder \
147148
-configuration Release \
148149
-archivePath build/DoomCoder.xcarchive \
149-
-allowProvisioningUpdates \
150-
-authenticationKeyPath "${{ steps.write-asc-key.outputs.key_path }}" \
151-
-authenticationKeyID "${{ steps.write-asc-key.outputs.key_id }}" \
152-
-authenticationKeyIssuerID "${{ steps.write-asc-key.outputs.issuer_id }}" \
153150
archive \
154-
CODE_SIGN_STYLE=Automatic \
151+
CODE_SIGN_STYLE=Manual \
152+
CODE_SIGN_IDENTITY="Developer ID Application" \
153+
PROVISIONING_PROFILE_SPECIFIER="DoomCoder Mac DevID" \
155154
DEVELOPMENT_TEAM="${{ secrets.APPLE_TEAM_ID }}" \
156155
MARKETING_VERSION="${{ env.VERSION }}" \
157-
CURRENT_PROJECT_VERSION="${{ env.BUILD }}"
156+
CURRENT_PROJECT_VERSION="${{ env.BUILD }}" \
157+
OTHER_CODE_SIGN_FLAGS="--timestamp --options=runtime"
158158
159-
- name: Extract app from archive
159+
- name: Extract signed app from archive
160160
run: |
161-
# Skip xcodebuild -exportArchive — it requires downloading a
162-
# Developer ID provisioning profile via cloud signing (needs Admin
163-
# role). The archive already contains the built .app; we extract it
164-
# directly and the re-sign step will apply the correct Developer ID.
165161
mkdir -p build/export
166162
APP_IN_ARCHIVE="build/DoomCoder.xcarchive/Products/Applications/DoomCoder.app"
167163
if [ ! -d "$APP_IN_ARCHIVE" ]; then
@@ -170,84 +166,24 @@ jobs:
170166
exit 1
171167
fi
172168
cp -R "$APP_IN_ARCHIVE" build/export/DoomCoder.app
173-
echo "✅ Extracted DoomCoder.app from archive"
174-
echo " Size: $(du -sh build/export/DoomCoder.app | cut -f1)"
175-
176-
- name: Embed Developer ID provisioning profile
177-
run: |
178169
APP="build/export/DoomCoder.app"
179-
# Replace the auto-generated Mac Development profile (from the
180-
# archive's Automatic signing) with our Developer ID profile.
181-
# Gatekeeper verifies signing identity is listed in this profile.
182-
cp "${MAC_PROFILE_PATH}" "${APP}/Contents/embedded.provisionprofile"
183-
echo "✅ Embedded Developer ID profile"
184-
security cms -D -i "${APP}/Contents/embedded.provisionprofile" \
170+
echo "✅ Extracted DoomCoder.app from archive"
171+
echo " Size: $(du -sh "$APP" | cut -f1)"
172+
echo ""
173+
echo "=== Embedded provisioning profile ==="
174+
security cms -D -i "$APP/Contents/embedded.provisionprofile" \
185175
| plutil -extract Name xml1 -o - - | grep '<string>'
186-
187-
- name: Re-sign all embedded code (inside-out)
188-
run: |
189-
APP="build/export/DoomCoder.app"
190-
IDENTITY="Developer ID Application"
191-
ENTS="DoomCoder/DoomCoder.Release.entitlements"
192-
193-
# Fail loudly if the app wasn't exported where we expect
194-
if [ ! -d "$APP" ]; then
195-
echo "::error::Exported app not found at $APP — contents of build/export/:"
196-
ls -laR build/export/ || true
197-
exit 1
198-
fi
199-
200-
sign_file() {
201-
echo " → $(basename "$1")"
202-
codesign --force --sign "$IDENTITY" --timestamp --options runtime "$1"
203-
}
204-
205-
# ── Pass 1: every Mach-O binary (deepest path first) ──────────────
206-
# Use process substitution (<) so the while loop runs in the CURRENT
207-
# shell — pipe-to-while runs in a subshell where errors are swallowed.
208-
echo "=== Pass 1: Mach-O binaries ==="
209-
FAIL=0
210-
while IFS= read -r f; do
211-
if file "$f" 2>/dev/null | grep -qE "Mach-O|shared library"; then
212-
sign_file "$f" || FAIL=1
213-
fi
214-
done < <(find "$APP" -type f \
215-
| awk '{ printf "%d\t%s\n", gsub("/","/"), $0 }' \
216-
| sort -rn | cut -f2-)
217-
[ "$FAIL" -eq 0 ] || { echo "::error::One or more Mach-O binaries failed to sign"; exit 1; }
218-
219-
# ── Pass 2: XPC service bundles ────────────────────────────────────
220-
echo "=== Pass 2: XPC service bundles ==="
221-
while IFS= read -r xpc; do
222-
sign_file "$xpc"
223-
done < <(find "$APP" -type d -name "*.xpc" \
224-
| awk '{ printf "%d\t%s\n", gsub("/","/"), $0 }' \
225-
| sort -rn | cut -f2-)
226-
227-
# ── Pass 3: nested .app bundles (e.g. Sparkle's Updater.app) ──────
228-
echo "=== Pass 3: nested .app bundles ==="
229-
while IFS= read -r nested; do
230-
sign_file "$nested"
231-
done < <(find "$APP" -mindepth 2 -type d -name "*.app" \
232-
| awk '{ printf "%d\t%s\n", gsub("/","/"), $0 }' \
233-
| sort -rn | cut -f2-)
234-
235-
# ── Pass 4: frameworks ─────────────────────────────────────────────
236-
echo "=== Pass 4: frameworks ==="
237-
while IFS= read -r fw; do
238-
sign_file "$fw"
239-
done < <(find "$APP" -type d -name "*.framework" \
240-
| awk '{ printf "%d\t%s\n", gsub("/","/"), $0 }' \
241-
| sort -rn | cut -f2-)
242-
243-
# ── Pass 5: main app bundle (with entitlements, always last) ───────
244-
echo "=== Pass 5: main app ==="
245-
codesign --force --sign "$IDENTITY" --timestamp --options runtime \
246-
--entitlements "$ENTS" "$APP"
247-
248-
echo "=== Verify bundle ==="
176+
echo ""
177+
echo "=== Signing identity ==="
178+
codesign -dv "$APP" 2>&1 | grep -E "Authority|TeamIdentifier|Identifier="
179+
echo ""
180+
echo "=== Bundled entitlements ==="
181+
codesign -d --entitlements - --xml "$APP" 2>/dev/null \
182+
| plutil -convert xml1 -o - -
183+
echo ""
184+
echo "=== Deep verify ==="
249185
codesign --verify --deep --strict --verbose=2 "$APP"
250-
echo "✅ Re-sign complete"
186+
echo "✅ Archive signing verified"
251187
252188
- name: Audit all binary signatures
253189
run: |

0 commit comments

Comments
 (0)