Commit Graph

27 Commits

  • ds: fix profile creation conflicts (#430)
    ## What does this PR do?
    
    Fixes issues with missing devices and duplicate names when creating new
    profiles. Supersedes #427 and #428.
    
    Fixes #425, fixes #426
    
    ## How was it tested?
    
    - [x] Ran locally
    - [x] Confirmed that free accounts also support multiple profiles as
    long as the names are different.
    
    ## AI tool usage
    
    How much of the code in this PR was AI-assisted? (check one)
    
    - [x] **0** - No AI was used to write code
    - [ ] **1** - I was assisted by AI in writing code. I reviewed the
    finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    
    <!-- coderabbit-ai-generated:v1 -->
    
    ## Summary by CodeRabbit
    
    * **Bug Fixes**
    * Profile creation now uses only enabled devices that support iOS apps.
    * Existing profiles with the exact generated profile name are removed
    before a replacement is created, including when multiple matches exist.
      * Profile handling now stops when the operation is canceled.
    
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
    
    Co-authored-by: Valentin Brosseau <c4software@gmail.com>
  • signer: replace zsign with RCodesign (#389)
    ## What does this PR do?
    
    Replaces zsign with the
    [apple-codesign](https://github.com/indygreg/apple-platform-rs/tree/0ebbd2ef3b96d5adccc30b1cad4deea38cd735c4/apple-codesign)
    Rust crate. It's much more usable as a library (while zsign has bare
    prints all over the place + we had to fork it to maintain a public
    interface), it supports macOS-style bundles, and also has other features
    that we would've needed to further patch zsign for.
    
    ## How was it tested?
    
    - [x] Ran an ad-hoc signed app on the simulator (with a subsequent PR)
    - [x] Ran an app with App Groups on device, confirmed that the app group
    is present
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [ ] **0** - No AI was used to write code
    - [x] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    <!-- coderabbit-ai-generated:v1 -->
    
    ## Summary by CodeRabbit
    
    * **Updated Signing**
    * App signing now uses an updated signing engine and supports
    certificate-based and ad hoc signing.
    * Entitlement data, including data provided through entitlement files,
    is passed through during signing.
      * Deep signing is enabled in the development command.
    * **Validation**
    * Signing rejects invalid app or entitlement file paths and duplicate
    entitlement paths.
    * **Compatibility**
      * Signing contexts can be created without selecting a signer.
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • ds: allow one app group on free teams (#284)
    ## What does this PR do?
    
    Resolves the FIXME in `DeveloperServicesCapability`: App Groups are no
    longer filtered out on free teams, and
    `DeveloperServicesAddAppOperation` assigns at most one group per app on
    a free team (the constraint the FIXME mentioned).
    
    Free teams can register and assign app groups through the Xcode
    DeveloperServices API (as AltStore/SideStore do), so a widget and its
    app can share `UserDefaults(suiteName:)` with a free Apple ID.
    
    ## How was it tested?
    
    Built on Linux from `main` (4d0c429). Installed a Flutter app with a
    WidgetKit extension on an iPhone (iOS 27.2) with a free Apple ID: the
    app gets `group.XTL-<team>.<id>` in its entitlements and the widget
    reads the app's data through it. `make lint` not run (no SwiftLint on
    this machine).
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [ ] **0** - No AI was used to write code
    - [x] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    <!-- coderabbit-ai-generated:v1 -->
    
    ## Summary by CodeRabbit
    
    * **Bug Fixes**
      * App Groups are now available to free teams.
    * Apps assigned to multiple App Groups are rejected with a clear error
    message.
    * Invalid app errors now include the app’s URL path, making it easier to
    identify which app could not be added.
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
    
    ---------
    
    Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
    Co-authored-by: Kabir Oberai <oberai.kabir@gmail.com>
  • ds: fix assigning multiple app groups (#403)
    ## The bug
    
    `DeveloperServicesAssignAppGroupsOperation` sends one
    `assignApplicationGroupToAppId` request per app group, concurrently.
    That endpoint replaces the App ID's groups with the ones in the request,
    so an app whose entitlements list two or more groups ends up with only
    one of them on the App ID: whichever request Apple handles last. The
    provisioning profile then carries one group while the signed binary
    claims two, and the device refuses the install with
    `ApplicationVerificationFailed`.
    
    Setting the groups by hand in the developer portal does not help,
    because the next `xtool dev build --sign` sends the per-group requests
    again.
    
    ## The fix
    
    - `DeveloperServicesAssignAppGroupRequest` takes `groupIDs:
    [DeveloperServicesAppGroup.ID]` and sends `applicationGroups` as an
    array. The endpoint accepts an array; it was being given a single string
    before.
    - `DeveloperServicesAssignAppGroupsOperation.perform()` still finds or
    creates each group concurrently, then sends a single assign request
    carrying every group ID, and returns the group IDs in the order the
    entitlements listed them, so the rewritten entitlement is stable between
    builds.
    
    The only caller, `DeveloperServicesAddAppOperation`, is unchanged.
    
    ## Related
    
    #324 would report the resulting mismatch between the entitlements and
    the profile as a clear error at signing time; this change removes the
    cause, so an app with several groups gets a profile that grants all of
    them.
    
    ## Checked
    
    Built on Linux with the Swift 6.4 toolchain and used to sign an iOS app
    whose main target has two app groups and a Share extension. The app's
    `embedded.mobileprovision` now carries both groups and the device
    installs the app. The same app signed with the unpatched build got one
    group in the profile and `ApplicationVerificationFailed` on install.
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    https://claude.ai/code/session_01UKzxgxaLwK7Ap5LMwj9MxH
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    <!-- coderabbit-ai-generated:v1 -->
    
    ## Summary by CodeRabbit
    
    * **New Features**
    * Apps can now be assigned multiple app groups in a single operation.
    The assignment replaces the app’s existing group associations with the
    selected groups, so any groups not included in the new assignment are no
    longer associated with the app.
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
    
    ---------
    
    Co-authored-by: David Reinecke <david.reinecke@gmail.com>
    Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
    Co-authored-by: Kabir Oberai <oberai.kabir@gmail.com>
  • cli: better IO (#375)
    ## What does this PR do?
    
    Adds `Dependency`s for console IO. Also fixes #268 by explicitly
    handling non-pipe input.
    
    ## How was it tested?
    
    Ran on macOS + Linux in Docker. I have a future PR that overrides the
    dependencies in certain cases, which also appears to work correctly.
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [ ] **0** - No AI was used to write code
    - [x] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    Did not scrutinize the tests too much tbh but they look reasonable. Easy
    enough to update/remove them in the future if needed.
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    <!-- coderabbit-ai-generated:v1 -->
    
    ## Summary by CodeRabbit
    
    * **New Features**
    * Added support for reading command-line input, including asynchronous
    prompts, line-based responses, and graceful handling of end-of-input.
      * Added coordinated cleanup when commands finish.
    * **Improvements**
    * Command messages across authentication, device management,
    installation, SDK, and other workflows now use consistent output
    handling; displayed messages and command behavior remain unchanged.
    * Improved handling of complete output when the system temporarily
    cannot accept more data.
    * **Tests**
    * Added coverage for command input and output, including stream
    behavior, cancellation, and shutdown.
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • entitlements: support Sign in with Apple (#377)
    ## What does this PR do?
    
    Hi! xtool didn't know about the `com.apple.developer.applesignin`
    entitlement, so every time I installed my app, the capability sync
    removed Sign in with Apple from the App ID. The new provisioning profile
    didn't have it, and the device refused the app ("A valid provisioning
    profile for this executable was not found").
    
    This maps the entitlement to the `APPLE_ID_AUTH` capability, as a
    primary App ID, so the capability stays and the profile includes it.
    
    ## How was it tested?
    
    I built xtool from this branch on macOS and installed my app (a SwiftPM
    app with `entitlementsPath` and the Sign in with Apple entitlement) on
    an iPhone with a paid team. The App ID kept the capability, the install
    worked, and native Sign in with Apple works in the app. Before the
    change the same install failed every time.
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [ ] **0** - No AI was used to write code
    - [x] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    
    <!-- coderabbit-ai-generated:v1 -->
    
    ## Summary by CodeRabbit
    
    * **New Features**
      * Added support for configuring the Apple Sign In entitlement.
    * Apple Sign In is now available as a supported entitlement type,
    including serialization and configuration support.
      * Configures Apple ID app consent as the primary app consent option.
    
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • auth: clean up Anisette handling (#367)
    ## What does this PR do?
    
    Simplifies the Anisette handling code. Removes some unnecessary fields
    (including hard-coded Mac serial numbers, cf #348) from our http
    requests amongst other things.
    
    ## How was it tested?
    
    Tested on macOS:
    
    - [x] Can log in after resetting provisioning
    - [x] Can run `xtool ds teams`
    - [x] Can run `xtool ds certificates`
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [x] **0** - No AI was used to write code
    - [ ] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    
    ## Summary by CodeRabbit
    
    * **Updates**
    * Anisette-backed requests now use device information provided with
    Anisette data, including an Android 14 device identity.
    * Developer service requests no longer include client-information and
    device-ID headers.
    * The Anisette command now displays fetched details in sorted order and
    uses the configured Anisette provider instead of prompting for
    provisioning data.
    
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • certs: match certificates whose serial starts with 0 (#306)
    ## What does this PR do?
    
    Fixes #305 (thanks @0x53 for the detailed diagnosis).
    
    `Certificate.serialNumber()` stripped leading `0` *characters* from the
    hex string, so a serial like `0337…` became `337…` and never matched the
    `serialNumber` returned by the API. xtool then treated its stored
    certificate as missing: free teams are prompted to revoke a valid
    certificate on every run, and paid teams take the same path and try to
    create another certificate, which @McoreD confirmed fails with `409 …
    You already have a current Development certificate or a pending
    certificate request.`
    
    The character-level `drop` predates b6a40ca, which switched to
    fixed-width `%02hhX` formatting; since then it removes the significant
    zero of any first byte below `0x10`. swift-certificates already strips
    leading zero bytes (including DER sign padding) when parsing, so the
    `drop` isn't needed for padding.
    
    - `serialNumber()` now drops only leading zero *bytes*, matching the API
    and `openssl x509 -serial`.
    - The stored-certificate lookup uses a new
    `Certificate.hasSerialNumber(_:)`, which compares serials numerically
    (ignoring case and leading zeros), so it doesn't depend on Apple's exact
    formatting.
    - Profile creation no longer finds the signing certificate with
    `filter[serialNumber]`. The API lists serials with the leading zero
    (`0E41…`), but the filter only matches them without it (`E41…`), so once
    `serialNumber()` kept the zero the filter matched nothing and profile
    creation failed with a 409
    ([details](https://github.com/xtool-org/xtool/pull/306#issuecomment-6181157860)).
    It now fetches all pages of certificates and matches them with
    `hasSerialNumber(_:)`. Fix by @McoreD, with pagination added so a
    certificate on a later page isn't missed.
    
    ## How was it tested?
    
    - Added `Tests/XToolTests/CertificateTests.swift`: builds a cert with
    the serial from #305, round-trips it through DER, and checks
    `serialNumber()` and `hasSerialNumber(_:)`, plus a high-bit serial so
    DER sign padding doesn't leak into the string. The leading-zero test
    fails on `main` (returns `33742570D6F9684A23311711956A084`) and passes
    with this change.
    - `swift test` (17 tests) and `make lint` pass on macOS (Xcode 27, Swift
    6.4, `XTL_CI=1`).
    - @McoreD tested on Linux (Arch, Swift 6.4.1, Xcode 27 SDK, on top of
    1.22.2) with a paid team whose certificate serial starts with `0`: the
    stored certificate is reused, and provisioning, signing and install to
    an iPhone (iOS 27) succeed end to end (tested before pagination was
    added). `CertificateTests` also pass on Linux.
    - Not tested with a free team.
    
    To generate test certificates, the test target now depends on `Crypto`
    and `X509`, which XKit already uses.
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [ ] **0** - No AI was used to write code
    - [x] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    ## Summary by CodeRabbit
    
    * **Bug Fixes**
    * Certificate serial numbers now preserve leading zero nibbles while
    omitting unnecessary padding.
    * Certificate matching now ignores letter case and leading zeros, and
    profile lookups find matching certificates even when the API’s
    serial-number filter treats leading-zero values differently.
    * Certificate lookups continue to use the existing fallback when no
    matching certificate is found.
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
    
    ---------
    
    Co-authored-by: McoreD <McoreD@users.noreply.github.com>
    Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
  • gsa: update user agent to fix auth (#244)
    Fixes #243. GrandSlam seems to be rejecting clients that self-report as
    really old.
  • Fix capability creation via public API (#220)
    Ref #216: the public API seems to now complain when passing in
    `capabilities`, but the field is still required when using the private
    API.
    
    Co-authored-by: sfusco <slf@stevenfusco.com>
  • Clean up some entitlements code (#165)
    - We already do the `isFree` declarations in
    `DeveloperServiceCapability.swift`. Don't need to duplicate it on the
    entitlement types.
    - Add support for creating capabilities with raw names
  • Add support for App Extensions (#97)
    This pull request adds support for Apple's [App
    Extensions](https://developer.apple.com/app-extensions/).
    
    Closes: #45
    
    ---------
    
    Co-authored-by: ErrorErrorError <16653389+ErrorErrorError@users.noreply.github.com>
    Co-authored-by: Kabir Oberai <oberai.kabir@gmail.com>
  • fix: resolve ASC invalid bearer token (#129)
    This pull request resolves potential clock drift between ASC and bearer
    token.
    
    Ref: https://github.com/xtool-org/xtool/pull/97#discussion_r2203584344
  • Make ALL response enums extensible (#92)
    Another bugfix for #64. Fifth… time's the charm?
  • FetchDeviceOperation: account for pagination (#91)
    - Always try to register the device and just return gracefully if it
    fails (prevents races, pagination issues)
    - Allow `SigningContext` (and therefore `AutoSigner`) to be created
    without a target device: just skip device registration.
  • DeveloperServices: support paginated requests (#89)
    Realized that we need to paginate due to #87
  • Fix duplicate App ID registration (#90)
    We pull the list of App IDs in `upsertApp` to see if we already created
    the App ID. This broke when the user had too many App IDs, since DS
    returned a paginated response and we only looked at the first page.
    
    We could paginate the whole list (as seen in #89) but that's slow.
    Instead, just filter the query to the expected App ID. This is a prefix
    search so we might still receive multiple responses, but it's much less
    likely that the user has like 10(? I'm not sure what the default/max
    pagination limit is) apps registered with the XTL-ABC.com.foo.bar prefix
    (they could have PlugIns but usually just a small number of them.)
    
    This is a tentative fix for #87.
  • Improve OpenAPI error handling (#78)
    - Make some enums non-exhaustive (closes #77)
      - Also a tentative fix for #62 and #64
    - Improve logging on deserialization failure
    
    We now use raw JS to patch the OpenAPI spec instead of openapi-overlay.
    It's a lot more flexible this way.
  • Support for logging OpenAPI responses (#75)
    Allow setting `XTL_DEV_LOG` to a regex (e.g. `export
    XTL_DEV_LOG='profiles_.*'`) to log the responses of all matching
    operations
    
    Also add `BundleIdPlatform.services` as seen
    [here](https://github.com/AvdLee/appstoreconnect-swift-sdk/blob/60bbc198c8d9f9992807bfebef86f45d6173a1f1/Makefile#L9)
  • Improve handling of paid developer accounts (#20)
    * getTeamIsFree -> XcodeAuthData.team().isFree
    
    * Only revoke certificates if on a free team
    
    * Update OpenAPI spec, handle AVP
    
    * Fix App ID capabilities on paid accounts