Apple: provider-sign the published xcframework (1.7.0) - #13
Merged
Conversation
Xcode records the state of every .xcframework an app links against AS ITS PUBLISHER SHIPPED IT, and writes that into the IPA as Signatures/dart_bridge.xcframework-ios.signature. Ours was unsigned, so that receipt read `signed = false` / `isSecureTimestamp = false` -- which is what Apple's App Store scan reports as ITMS-91065: Missing signature. The app's own signature does not fill this in. Xcode re-signs the embedded copy with the submitting team's identity at embed and again at exportArchive, but the SDK-origin receipt is a separate record and is unaffected. apple/xcframework_signing.sh holds the signing + verification helpers. Signing happens after `xcodebuild -create-xcframework` and before the zip -- the one point where the bundle is complete and still unmutated -- and the signature is verified again after the zip is extracted into a fresh directory, so a packaging bug fails here instead of in a consumer's app. (The symlink flattening fixed in 1.6.1 would have shown up as exactly that.) Verification asserts an outer _CodeSignature/CodeResources, `codesign --verify --strict`, a non-ad-hoc signature, a real Timestamp= (not the self-asserted Signed Time= a --timestamp-less signature carries), the expected authority, and the expected TeamIdentifier. No --deep, no --timestamp=none. CI: the certificate is confined to a new release-only build-apple-signed job in the protected `release-signing` environment. It imports the p12 into a temporary keychain without -A (only codesign and security may reach the key), derives exactly one identity fingerprint and fails if the keychain yields any other count, and deletes the keychain in an always() step. Selecting by display name is ambiguous when a keychain holds more than one match and codesign then picks arbitrarily. build-apple keeps running unprivileged on branches and PRs but is skipped on tags and now uploads its artifact as ...-unsigned, so a release run cannot produce one. publish depends on build-apple-signed and refuses to publish if an unsigned Apple artifact turns up anyway. Local and PR builds without credentials still succeed and produce an unsigned artifact; REQUIRE_XCFRAMEWORK_SIGNATURE=1 (set on the release path) turns every missing credential, missing timestamp, wrong team, or empty tree into a hard failure. dev.flet.dartbridge is unchanged -- it was already a stable, publisher-owned identifier, which is what lets one signature cover every app that embeds it.
An .xcframework's root Info.plist is an XFWK manifest -- AvailableLibraries, CFBundlePackageType, XCFrameworkFormatVersion -- with no CFBundleIdentifier at all. codesign therefore fell back to the bundle's file name and the seal reported a bare `Identifier=dart_bridge` rather than a reverse-DNS one. xcf_signing_identifier reads the identifier off the xcframework's own inner framework Info.plist (flat iOS layout or versioned macOS layout, skipping the Versions/Current symlink) and xcf_sign_one passes it as `-i`. The identifier is already stable and provider-owned in every artifact we publish, so the outer seal and the framework it wraps agree by construction and neither depends on the consuming application -- no mapping table to keep in sync. Verification asserts the two still agree, which catches a re-sign that dropped the flag. It is skipped, not failed, when the inner framework cannot be located by name: serious_python stages Python.xcframework as Python-<platform>.xcframework and the rename is legitimate. Signing fails outright if no inner framework Info.plist carries a CFBundleIdentifier, rather than silently falling back to the file name again.
This was referenced Jul 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Xcode records the state of every
.xcframeworkan app links against as its publisher shipped it, and writes the result into the IPA asSignatures/dart_bridge.xcframework-ios.signature. Ours was unsigned, so that receipt readsigned = false/isSecureTimestamp = false— which is what Apple's App Store scan reports asITMS-91065: Missing signature(flet-dev/flet#6724).The app's own signature does not fill this in. Xcode re-signs the embedded copy with the submitting team's identity at embed and again at
exportArchive, but the SDK-origin receipt is a separate record and is unaffected by it. See Verifying the origin of your XCFrameworks.What changed
apple/xcframework_signing.sh(new) — signing and verification helpers.Signing happens after
xcodebuild -create-xcframeworkand before the zip: the one point where the bundle is complete and still unmutated. The signature is then verified again after the zip is extracted into a fresh directory, so a packaging bug fails here rather than in a consumer's app. (The symlink flattening fixed in 1.6.1 would have surfaced as exactly that.)Verification asserts:
_CodeSignature/CodeResourcescodesign --verify --strictTimestamp=— not the self-assertedSigned Time=a--timestamp-less signature carries, which is whatisSecureTimestampreflectsApple Distribution) andTeamIdentifierNo
--deep, no--timestamp=none.Explicit signing identifier. An
.xcframework's rootInfo.plistis anXFWKmanifest with noCFBundleIdentifier, so codesign would fall back to the bundle's file name and seal it as a bareIdentifier=dart_bridge. The identifier is now read off the xcframework's own inner framework (flat iOS layout or versioned macOS layout) and passed as-i, givingIdentifier=dev.flet.dartbridge. No mapping table to keep in sync, and it cannot drift from the framework it wraps.CI. The certificate is confined to a new release-only
build-apple-signedjob in the protectedrelease-signingenvironment. It imports the p12 into a temporary keychain without-A(onlycodesignandsecuritymay reach the key), derives exactly one identity fingerprint and fails on any other count, and deletes the keychain in analways()step.build-applekeeps running unprivileged on branches and PRs, but is skipped on tags and now uploads its artifact as…-unsigned, so a release run cannot produce one.publishdepends onbuild-apple-signedand refuses to publish if an unsigned Apple artifact turns up anyway.Builds without credentials still succeed and produce an unsigned artifact.
REQUIRE_XCFRAMEWORK_SIGNATURE=1, set on the release path, turns every missing credential, missing timestamp, wrong team, or empty tree into a hard failure.Verified locally
Full run with the real Apple Distribution identity:
Identical
CDHashbefore and after the zip round trip. Also checked that the verifier rejects unsigned and ad-hoc bundles, and that identifier derivation resolves under both the flat iOS and versioned macOS framework layouts.Before merging
release-signingneeds to exist in this repo withAPPLE_DISTRIBUTION_CERT_P12_BASE64+APPLE_DISTRIBUTION_CERT_P12_PASSWORDsecrets and anXCFRAMEWORK_EXPECTED_TEAM_IDvariable (GXXRQJK434), restricted tov*.*.*tags. Without them the next tag push fails at preflight instead of publishing something unsigned — which is the intended failure mode, but worth setting up first.Version
Bumped to 1.7.0. Compiled binaries are identical to 1.6.1; only the packaging of the published artifact changed. Do not re-release over 1.6.1 — consumer caches are version-keyed and hold the unsigned zip.
Part of the three-repo effort;
python-build(signing the Python XCFrameworks) andserious_python(preserving those signatures through staging) follow.