ENGINEERING-NOTIZ

iOS-Signaturrechte auf einem Cloud-Mac vorab prüfen

iOS-Signaturrechte auf einem Cloud-Mac vorab prüfen

Ein Archiv kann in Xcode erfolgreich erstellt werden und beim Upload dennoch an einer nicht passenden Push-Umgebung, an Associated Domains oder an einer abweichenden App-Kennung scheitern. Die Ursache liegt meist nicht beim Kompilieren, sondern darin, dass drei Quellen nicht übereinstimmen: die im Projekt deklarierten Rechte, die vom Provisioning Profile erlaubten Rechte und die tatsächlich in die App-Signatur geschriebenen Rechte. Cloud-Macs werden häufig per Skript wiederverwendet, wobei derselbe Knoten mehrere Branches und Umgebungen verarbeiten kann. Deshalb genügt es nicht, nur die .entitlements-Datei im Repository zu prüfen. Maßgeblich ist die für die Auslieferung vorgesehene .xcarchive.

Zuerst das Prüfobjekt festlegen

Der folgende Ablauf verwendet ein bereits erzeugtes Archiv als Eingabe. Ermitteln Sie zunächst den Pfad zur Haupt-App. Prüfen Sie nicht direkt die temporären Produkte in DerivedData, da deren endgültige Signierung möglicherweise noch nicht abgeschlossen ist.

ARCHIVE_PATH="$HOME/builds/MyApp.xcarchive"
APP_PATH="$ARCHIVE_PATH/Products/Applications/MyApp.app"

test -d "$APP_PATH"
test -f "$APP_PATH/embedded.mobileprovision"
codesign --verify --strict --verbose=2 "$APP_PATH"

Enthält die App Erweiterungen, muss jede .appex separat geprüft werden. Eine erfolgreiche Prüfung der Haupt-App bedeutet nicht, dass Benachrichtigungserweiterungen, Widgets oder Share Extensions kompatible App-Kennungen und Rechte verwenden.

find "$APP_PATH/PlugIns" -type d -name '*.appex' -print0 2>/dev/null |
while IFS= read -r -d '' item; do
  codesign --verify --strict --verbose=2 "$item"
done

Für die Vorabprüfung zählt „das Artefakt, das hochgeladen wird“ und nicht „ein Projekt, dessen Konfiguration korrekt aussieht“. Ersteres ist das tatsächliche Ergebnis, Letzteres lediglich eine Eingabe.

Die beiden tatsächlichen Rechte-Listen extrahieren

Mit codesign lassen sich die Entitlements der endgültigen Signatur auslesen. security cms decodiert das in die App eingebettete Provisioning Profile. Speichern Sie beide Ergebnisse als plist-Dateien, damit der spätere Vergleich nicht von der Xcode-Oberfläche abhängt.

WORK_DIR="$(mktemp -d)"
trap 'rm -rf "$WORK_DIR"' EXIT

codesign -d --entitlements :- "$APP_PATH" \
  > "$WORK_DIR/app-entitlements.plist" 2>/dev/null

security cms -D -i "$APP_PATH/embedded.mobileprovision" \
  > "$WORK_DIR/profile.plist"

plutil -lint "$WORK_DIR/app-entitlements.plist"
plutil -lint "$WORK_DIR/profile.plist"

Vergleichen Sie die Felder auf der obersten Ebene des Provisioning Profiles nicht direkt mit der App. Die erlaubten Rechte befinden sich im Wörterbuch Entitlements. Felder wie Erstellungszeitpunkt oder Name helfen lediglich bei der Zuordnung und geben nicht das Ergebnis der Signierung wieder.

Wichtige Felder automatisch vergleichen

Das folgende Skript prüft fünf häufige Problemklassen. Bei Zeichenketten berücksichtigt es den Platzhalterbereich * aus dem Provisioning Profile. Bei Array-Rechten muss jeder von der App verwendete Eintrag durch das Profil erlaubt sein. Das Skript liefert bei einem Fehler einen Rückgabewert ungleich null und kann daher direkt an den Archivierungsschritt angeschlossen werden.

python3 - "$WORK_DIR/app-entitlements.plist" "$WORK_DIR/profile.plist" <<'PY'
import fnmatch
import plistlib
import sys

with open(sys.argv[1], "rb") as f:
    app = plistlib.load(f)

with open(sys.argv[2], "rb") as f:
    profile = plistlib.load(f)["Entitlements"]

keys = [
    "application-identifier",
    "com.apple.developer.team-identifier",
    "aps-environment",
    "com.apple.developer.associated-domains",
    "get-task-allow",
]

def allowed(actual, expected):
    if isinstance(actual, list) and isinstance(expected, list):
        return all(any(fnmatch.fnmatch(str(v), str(rule)) for rule in expected) for v in actual)
    if isinstance(actual, str) and isinstance(expected, str):
        return fnmatch.fnmatch(actual, expected)
    return actual == expected

failed = []
for key in keys:
    if key not in app:
        continue
    if key not in profile or not allowed(app[key], profile[key]):
        failed.append(key)

if failed:
    print("Entitlement mismatch:", ", ".join(failed))
    raise SystemExit(1)

print("Entitlement preflight passed")
PY

Das Skript vergleicht nur Felder, die von der App tatsächlich deklariert werden. Schreibt das Projekt bestimmte Pflichtfelder vor – etwa aps-environment für ein Produktionspaket –, sollte zusätzlich eine Liste erforderlicher Felder geprüft werden. Fehlt eines davon, muss die Prüfung ebenfalls fehlschlagen.

Konfigurationsursachen anhand der Symptome eingrenzen

Symptom Zuerst prüfen Häufige Ursache
Push-Funktion fehlerhaft aps-environment Archivierungsumgebung und Verwendungszweck des Provisioning Profiles stimmen nicht überein
Universal Links funktionieren nicht com.apple.developer.associated-domains Der Domaineintrag fehlt in der endgültigen Signatur oder die Erweiterung ist anders konfiguriert
Kennungsfehler beim Upload application-identifier Bundle Identifier, Team-Präfix oder Target-Konfigurationen wurden vermischt
Distributionspaket kann weiterhin debuggt werden get-task-allow Für die Auslieferung wurde eine ungeeignete Build-Konfiguration verwendet
Installation einer Erweiterung schlägt fehl Rechte der Haupt-App und der .appex Erweiterungskennung, App-Gruppen oder Provisioning Profiles sind nicht miteinander kompatibel

Bewahren Sie bei einem Fehler zunächst das Archiv und beide plist-Dateien auf. Prüfen Sie anschließend CODE_SIGN_ENTITLEMENTS, PRODUCT_BUNDLE_IDENTIFIER und die aktuelle Build-Konfiguration. Signieren Sie das Artefakt nicht sofort neu, da dadurch die für die Diagnose besonders wertvollen Abweichungen verloren gehen.

Associated Domains und App-Gruppen sind Arrays. Ihre Reihenfolge ist normalerweise unerheblich, weshalb die Mengeninklusion verglichen werden sollte. Boolesche Felder müssen als echte boolesche Werte gelesen werden; die Zeichenkette "false" darf nicht wie false behandelt werden. Das ist zugleich der Grund, plistlib statt grep zu verwenden.

Die Prüfung in die Cloud-Build-Abnahme integrieren

Speichern Sie das Skript unter Scripts/verify_entitlements.sh und führen Sie es nach einem erfolgreichen xcodebuild archive, aber vor dem Export oder Upload aus. Verwenden Sie für jeden Auftrag ein eigenes Archivverzeichnis. Speichern Sie das Prüfergebnis, die Signaturzusammenfassung der App und die fehlgeschlagenen Felder als Build-Anhänge. Protokollieren Sie nur Feldnamen und anonymisierte Kennungen; Inhalte privater Schlüssel oder vollständige Zugangsdaten dürfen nicht in den Logs erscheinen.

Die Abnahme sollte in vier Schritte unterteilt werden: Paketstruktur prüfen, Signaturrechte extrahieren, Provisioning Profile decodieren und Richtlinienvergleich ausführen. Schlägt einer dieser Schritte fehl, muss die weitere Auslieferung gestoppt werden. Die .xcarchive bleibt für die spätere Analyse erhalten. Bei Projekten mit mehreren Targets ist die Prüfung separat für die Haupt-App und jede Erweiterung auszuführen; das Prüfergebnis der Haupt-App darf nicht wiederverwendet werden.

Führen Sie abschließend eine manuelle Stichprobe durch: Vergewissern Sie sich, dass der aktuelle Auftrag das vorgesehene Scheme und die vorgesehene Configuration verwendet, dass get-task-allow im Produktionsartefakt auf false gesetzt ist, dass die Push-Umgebung zum Auslieferungsziel passt, dass die Associated Domains keine Testadressen enthalten und dass alle Erweiterungen ihre jeweilige Signaturprüfung bestehen. So werden Fehler, die sonst erst beim Upload auftreten, in eine reproduzierbare und nachvollziehbare Build-Prüfung vorverlagert.

Häufig gestellte Fragen

Warum reicht die Entitlements-Datei im Projekt nicht aus?

Sie ist nur eine Eingabe. Build-Einstellungen, Profil und Signatur können das Ergebnis verändern, deshalb muss die archivierte und signierte App geprüft werden.

Welche Berechtigungen sollten zuerst automatisiert geprüft werden?

Priorisieren Sie application-identifier, Teamkennung, aps-environment, zugeordnete Domains und get-task-allow.

Exklusiver physischer Knoten

Führen Sie Ihren nächsten Build auf einem Mac in der Cloud aus.

Vergleichen Sie drei Apple-Silicon-Konfigurationen und wählen Sie aus fünf internationalen Knoten die Region, die zu Ihrem aktuellen Workflow passt.

Mietmodell auswählen