Inside-out signing with per-binary entitlements to restore passkeys (#2905)
The AuthenticationServices WebAuthn / passkey path on Developer ID Mac apps requires com.apple.application-identifier in the codesigned entitlements blob of the calling process. AS does not fall back to the embedded provisioning profile and does not derive the app id from team-identifier + bundle-id; without that key the ceremony fails with AuthorizationError 1004. PR #2727's nightly build signed every binary inside the bundle (main app plus Contents/Resources/bin/cmux and Contents/Resources/bin/ghostty) with the same cmux.entitlements. Injecting application-identifier into that shared file and re-applying it with --deep pushed the main app's app id onto the CLI helpers, whose code identifiers are 'cmux' and 'ghostty'. That mismatch is what amfi on notarized macOS 26 Tahoe rejects with Launchd job spawn failed / errno 163. Local un-notarized Developer-ID builds survived because amfi is less strict on those. Split into two entitlements files and sign inside-out: - cmux-helper.entitlements: minimal hardened-runtime only (disable-library-validation, allow-jit, allow-unsigned-executable-memory). Used for CLI helpers, which never need app-id, WebAuthn, camera, mic, or AppleEvents. - cmux.entitlements: unchanged in the tree (shared base, no app-id). At sign time the workflow copies it and injects application-identifier and team-identifier for the bundle being built (com.cmuxterm.app for release, com.cmuxterm.app.nightly for nightly). Signing order in both workflows: 1. CLI helpers (bin/cmux, bin/ghostty) signed with cmux-helper.entitlements. 2. Main app bundle signed last with the full injected entitlements, WITHOUT --deep. --deep would overwrite the helper signatures and re-propagate the mismatch. release.yml also gains the same embedded provisioning profile step the nightly gained in #2727, so the shipped com.cmuxterm.app build is authorized to use the WebAuthn browser entitlement too. Verified locally on macOS 26.3.1: re-signed the previous broken nightly bundle using this scheme, it launches, passkey ceremony reaches ASAuthorizationController.performRequests without the 1004 error. Co-authored-by: Lawrence Chen <[email protected]>
This commit is contained in:
co-authored by
Lawrence Chen
parent
1085927035
commit
02f741c32a
@@ -432,21 +432,50 @@ jobs:
|
||||
echo "Missing APPLE_SIGNING_IDENTITY secret" >&2
|
||||
exit 1
|
||||
fi
|
||||
ENTITLEMENTS="cmux.entitlements"
|
||||
# Inside-out signing with per-binary entitlements:
|
||||
# - CLI helpers get a minimal hardened-runtime entitlements file
|
||||
# (no application-identifier). If we gave them the main app's
|
||||
# application-identifier, amfi rejects the bundle at launch on
|
||||
# macOS 26 Tahoe because the helper's signing identifier
|
||||
# ("cmux" / "ghostty") doesn't match the claimed app id
|
||||
# (7WLXT3NR37.com.cmuxterm.app.nightly).
|
||||
# - Main app bundle gets the full entitlements including an
|
||||
# injected application-identifier, which AuthenticationServices
|
||||
# requires for passkey / WebAuthn ceremonies
|
||||
# (ASAuthorizationError 1004 otherwise).
|
||||
# - No --deep on the top-level sign: it would overwrite the
|
||||
# helper signatures with the main entitlements and reintroduce
|
||||
# the mismatch.
|
||||
HELPER_ENT="cmux-helper.entitlements"
|
||||
NIGHTLY_APP_ENT="$(mktemp /tmp/cmux-nightly-app-ent.XXXXXX.plist)"
|
||||
trap 'rm -f "$NIGHTLY_APP_ENT"' EXIT
|
||||
cp cmux.entitlements "$NIGHTLY_APP_ENT"
|
||||
/usr/libexec/PlistBuddy -c "Delete :com.apple.application-identifier" "$NIGHTLY_APP_ENT" >/dev/null 2>&1 || true
|
||||
/usr/libexec/PlistBuddy -c "Delete :com.apple.developer.team-identifier" "$NIGHTLY_APP_ENT" >/dev/null 2>&1 || true
|
||||
/usr/libexec/PlistBuddy -c "Add :com.apple.application-identifier string 7WLXT3NR37.com.cmuxterm.app.nightly" "$NIGHTLY_APP_ENT"
|
||||
/usr/libexec/PlistBuddy -c "Add :com.apple.developer.team-identifier string 7WLXT3NR37" "$NIGHTLY_APP_ENT"
|
||||
for APP_PATH in \
|
||||
"build-universal/Build/Products/Release/cmux NIGHTLY.app"
|
||||
do
|
||||
CLI_PATH="$APP_PATH/Contents/Resources/bin/cmux"
|
||||
HELPER_PATH="$APP_PATH/Contents/Resources/bin/ghostty"
|
||||
if [ -f "$CLI_PATH" ]; then
|
||||
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$ENTITLEMENTS" "$CLI_PATH"
|
||||
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$HELPER_ENT" "$CLI_PATH"
|
||||
fi
|
||||
if [ -f "$HELPER_PATH" ]; then
|
||||
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$ENTITLEMENTS" "$HELPER_PATH"
|
||||
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$HELPER_ENT" "$HELPER_PATH"
|
||||
fi
|
||||
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$ENTITLEMENTS" --deep "$APP_PATH"
|
||||
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$NIGHTLY_APP_ENT" "$APP_PATH"
|
||||
/usr/bin/codesign --verify --deep --strict --verbose=2 "$APP_PATH"
|
||||
/usr/bin/codesign -d --entitlements :- "$APP_PATH" 2>&1 | grep -q "com.apple.developer.web-browser.public-key-credential"
|
||||
/usr/bin/codesign -d --entitlements :- "$APP_PATH" 2>&1 | grep -q "7WLXT3NR37.com.cmuxterm.app.nightly"
|
||||
# Assert helpers do NOT carry the main app's application-identifier
|
||||
if [ -f "$CLI_PATH" ]; then
|
||||
/usr/bin/codesign -d --entitlements :- "$CLI_PATH" 2>&1 | grep -q "application-identifier" && {
|
||||
echo "error: CLI helper unexpectedly carries application-identifier" >&2
|
||||
exit 1
|
||||
} || true
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Notarize apps and dmgs
|
||||
|
||||
@@ -247,6 +247,39 @@ jobs:
|
||||
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" build.keychain
|
||||
security list-keychains -d user -s build.keychain
|
||||
|
||||
- name: Embed release provisioning profile
|
||||
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
||||
env:
|
||||
APPLE_RELEASE_PROVISIONING_PROFILE_BASE64: ${{ secrets.APPLE_RELEASE_PROVISIONING_PROFILE_BASE64 }}
|
||||
run: |
|
||||
if [ -z "$APPLE_RELEASE_PROVISIONING_PROFILE_BASE64" ]; then
|
||||
echo "Missing APPLE_RELEASE_PROVISIONING_PROFILE_BASE64 secret" >&2
|
||||
exit 1
|
||||
fi
|
||||
APP_PATH="build-universal/Build/Products/Release/cmux.app"
|
||||
PROFILE_PATH="$APP_PATH/Contents/embedded.provisionprofile"
|
||||
TMP_PROFILE="$(mktemp /tmp/cmux-release-profile.XXXXXX)"
|
||||
TMP_PLIST="$(mktemp /tmp/cmux-release-profile.XXXXXX.plist)"
|
||||
trap 'rm -f "$TMP_PROFILE" "$TMP_PLIST"' EXIT
|
||||
echo "$APPLE_RELEASE_PROVISIONING_PROFILE_BASE64" | base64 --decode > "$TMP_PROFILE"
|
||||
security cms -D -i "$TMP_PROFILE" > "$TMP_PLIST"
|
||||
APP_ID="$(/usr/libexec/PlistBuddy -c "Print :Entitlements:com.apple.application-identifier" "$TMP_PLIST")"
|
||||
if [ "$APP_ID" != "7WLXT3NR37.com.cmuxterm.app" ]; then
|
||||
echo "Release provisioning profile targets unexpected app ID: $APP_ID" >&2
|
||||
exit 1
|
||||
fi
|
||||
WEBAUTHN_ENTITLEMENT="$(/usr/libexec/PlistBuddy -c "Print :Entitlements:com.apple.developer.web-browser.public-key-credential" "$TMP_PLIST")"
|
||||
if [ "$WEBAUTHN_ENTITLEMENT" != "true" ]; then
|
||||
echo "Release provisioning profile missing WebAuthn browser entitlement" >&2
|
||||
exit 1
|
||||
fi
|
||||
PROVISIONS_ALL_DEVICES="$(/usr/libexec/PlistBuddy -c "Print :ProvisionsAllDevices" "$TMP_PLIST")"
|
||||
if [ "$PROVISIONS_ALL_DEVICES" != "true" ]; then
|
||||
echo "Release provisioning profile is not a Developer ID all-devices profile" >&2
|
||||
exit 1
|
||||
fi
|
||||
cp "$TMP_PROFILE" "$PROFILE_PATH"
|
||||
|
||||
- name: Codesign app
|
||||
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
||||
env:
|
||||
@@ -257,17 +290,33 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
APP_PATH="build-universal/Build/Products/Release/cmux.app"
|
||||
ENTITLEMENTS="cmux.entitlements"
|
||||
# Inside-out signing (see docs/signing.md reasoning also in
|
||||
# nightly.yml): CLI helpers get minimal hardened-runtime
|
||||
# entitlements, main app bundle gets the full entitlements with
|
||||
# application-identifier injected so AuthenticationServices can
|
||||
# serve passkey / WebAuthn ceremonies. No --deep on the top
|
||||
# sign: it would overwrite helper signatures and re-introduce
|
||||
# the amfi launch rejection seen in the nightly.
|
||||
HELPER_ENT="cmux-helper.entitlements"
|
||||
RELEASE_APP_ENT="$(mktemp /tmp/cmux-release-app-ent.XXXXXX.plist)"
|
||||
trap 'rm -f "$RELEASE_APP_ENT"' EXIT
|
||||
cp cmux.entitlements "$RELEASE_APP_ENT"
|
||||
/usr/libexec/PlistBuddy -c "Delete :com.apple.application-identifier" "$RELEASE_APP_ENT" >/dev/null 2>&1 || true
|
||||
/usr/libexec/PlistBuddy -c "Delete :com.apple.developer.team-identifier" "$RELEASE_APP_ENT" >/dev/null 2>&1 || true
|
||||
/usr/libexec/PlistBuddy -c "Add :com.apple.application-identifier string 7WLXT3NR37.com.cmuxterm.app" "$RELEASE_APP_ENT"
|
||||
/usr/libexec/PlistBuddy -c "Add :com.apple.developer.team-identifier string 7WLXT3NR37" "$RELEASE_APP_ENT"
|
||||
CLI_PATH="$APP_PATH/Contents/Resources/bin/cmux"
|
||||
HELPER_PATH="$APP_PATH/Contents/Resources/bin/ghostty"
|
||||
if [ -f "$CLI_PATH" ]; then
|
||||
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$ENTITLEMENTS" "$CLI_PATH"
|
||||
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$HELPER_ENT" "$CLI_PATH"
|
||||
fi
|
||||
if [ -f "$HELPER_PATH" ]; then
|
||||
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$ENTITLEMENTS" "$HELPER_PATH"
|
||||
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$HELPER_ENT" "$HELPER_PATH"
|
||||
fi
|
||||
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$ENTITLEMENTS" --deep "$APP_PATH"
|
||||
/usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$RELEASE_APP_ENT" "$APP_PATH"
|
||||
/usr/bin/codesign --verify --deep --strict --verbose=2 "$APP_PATH"
|
||||
/usr/bin/codesign -d --entitlements :- "$APP_PATH" 2>&1 | grep -q "com.apple.developer.web-browser.public-key-credential"
|
||||
/usr/bin/codesign -d --entitlements :- "$APP_PATH" 2>&1 | grep -q "7WLXT3NR37.com.cmuxterm.app"
|
||||
|
||||
- name: Notarize app
|
||||
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
Reference in New Issue
Block a user