Repository navigation
signing: sign for distribution with an existing identity - #326
TartanLeGrand wants to merge 9 commits into
Conversation
Add DistributionSigner, which signs an app with a user-supplied .p12 identity and App Store or Ad Hoc provisioning profiles, without any call to Apple (nothing is created or revoked). - Load identities from PKCS#12 archives (including legacy RC2/3DES ones). - Expose the kind of a provisioning profile (development, ad hoc, App Store, enterprise); App Store profiles have no ProvisionedDevices. - Reject development profiles and certificates, expired inputs, team mismatches and profiles that don't include the certificate. - Derive entitlements from the profile, never including get-task-allow, and reject requested entitlements the profile does not allow. - Refuse apps with extensions until the signer can apply per-bundle entitlements (xtool-org#131), rather than produce an invalid signature.
`xtool sign <ipa|app> --p12 ... --profile ...` re-signs an existing build for distribution. `xtool dev build` accepts the same options. The .p12 password comes from an environment variable or a file, never from an argument.
Drop the root-only filter in Signer.sign (xtool-org#131) and let DistributionSigner sign app extensions with their own profile and entitlements. Requires a Zupersign that honours the bundle_path of each entitlements entry (xtool-org/zsign, not released yet): bump the zsign requirement in Package.swift once it is.
Compared with an Xcode-exported IPA, entitlements now match bundle by bundle: get-task-allow is explicitly false, and capabilities are only claimed when the app asks for them (beta-reports-active is always carried over). An app with no entitlements of its own still gets everything its profile grants.
Loading the default and legacy providers on every call raced with PKCS12_parse running on other threads: the parse succeeded without a key or certificate, and parallel tests failed with p12MissingContent.
Points at a branch combining xtool-org/zsign#3, xtool-org#4 and xtool-org#5 until zsign tags a release including them.
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Related physical-device repro while investigating #131/#326:
Does #326's per-bundle signing work cover this |
|
Thanks for the detailed report. Short answer: I don't know whether #326 fixes this, and I have not tested a Safari web extension myself. What I can say:
"Missing bundle ID" while creating the placeholder makes me suspect installd fails to read the extension's bundle metadata rather than its signature, but that is a guess. What would narrow it down:
If you can share the |
Depends on xtool-org/zsign#3, #4 and #5. Until zsign tags a release that includes them, the last commit points
Package.swiftat a branch in my fork that combines the three (by revision) so that CI can build; it will become.upToNextMinor(from: "<tag>").What does this PR do?
Lets xtool sign an app for App Store or Ad Hoc distribution with an identity and profiles the user already has, fully offline: nothing is created or revoked on the developer account.
xtool sign <ipa|app> --p12 <file> --profile <file> [--profile ...] [--entitlements <plist>] [-o <ipa>]re-signs an existing build into a new.ipa, leaving the input untouched.xtool dev buildtakes the same options (--p12,--p12-password-env,--p12-password-file,--profile,--entitlements), also with--ipa.--p12together with--signis rejected..p12password comes fromXTOOL_P12_PASSWORD, another variable named with--p12-password-env, or a file (--p12-password-file). Never from an argument.embedded.mobileprovision, and its own entitlements.Library side (
Sources/XKit/Signer/Distribution/):DistributionIdentityloads the.p12through a small OpenSSL helper (Sources/CXKit/pkcs12_identity.c), which also loads the legacy provider (once) so archives exported by Keychain Access (RC2-40/3DES) work. Wrong password, invalid file and missing key are distinct errors. Development and expired certificates are refused.DistributionProfilerefuses development, expired and non-iOS profiles, a team mismatch, and a certificate missing fromDeveloperCertificates.Mobileprovisionnow decodes App Store profiles (they have noProvisionedDevices, which used to fail decoding) and exposes their kind and platforms.DistributionEntitlementstakes the profile as the source of truth:application-identifierand the team identifier come from the profile,get-task-allowis alwaysfalse,beta-reports-activeis carried over, other capabilities are claimed only if the app asks for them (existing signature or--entitlements), or everything the profile grants if the app asks for nothing. Wildcards are resolved. Asking for something the profile does not allow is an error. This matches what Xcode does on export.DistributionSignervalidates everything, embeds the profiles and calls the existingSigner.This also removes the root-only filter in
Signer.sign(the #131 workaround), so development signing of apps with extensions now uses each bundle's entitlements as well; that part is what needs zsign#3.SignerLayoutTestscovers zsign#5 (a binary with too little room fails without being modified; a binary whose first section is not__textsigns), and the signing tests expect the single SHA-256 CodeDirectory of zsign#4.Docs: a "Distribution" article, linked from the main page.
Out of scope: creating distribution certificates and App Store profiles through the App Store Connect API. A safe version needs a "never revoke" policy that is easier to review separately.
How was it tested?
Linux x86_64, Podman, Swift 6.4,
XTL_CI=1(warnings as errors), against the combined zsign branch:swift build --product xtoolandswift test:Test run with 40 tests in 5 suites passed(28 new): profile parsing and kinds, bundle ID matching, refusal of development profiles and certificates, expired certificate and profile, team mismatch, certificate not in profile, wrong.p12password and garbage input, entitlements derivation, noget-task-allow, wildcard resolution, narrowing, rejection of entitlements the profile does not allow, signer preconditions, signing a fake app with a fake extension using two profiles and reading back each executable's entitlements from its signature, and the two layout tests. Test fixtures are throwaway self-signed identities and hand-made CMS-signed profiles, regenerated byscripts/generate-signing-fixtures.sh.--strict: 0 violations in 296 files..p12loads.aps-environment), and the requirements blob andCodeResourcesmatch. Uploaded to App Store Connect: buildsVALIDin about 2 minutes, then installed through TestFlight on an iPhone with iOS 27. Before zsign#4 the same upload was rejected with 90034.xtool signwith the same identity: alsoVALID.Found while preparing this PR: loading the OpenSSL providers on every call raced with
PKCS12_parseon other threads (parallel tests failed withp12MissingContent); they are now loaded once.Not tested: macOS and iOS builds. The helper includes
openssl/provider.h; it still has to be confirmed that the OpenSSL xcframework used on Apple platforms ships it and the legacy provider (if not, only legacy.p12files would fail, withinvalidP12). Ad Hoc installs were not tried.Related: #324 (development signing) has a similar "does the profile allow this" check; whichever lands second can share it. Fixes #131. Part of #117.
AI tool usage
How much of this PR was AI-assisted? (check one)
Besides my review, the change was validated by running it (tests, real builds, device installs and App Store Connect uploads where stated above). I will answer review questions myself.