Files
316a11e523 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>
2026-10-15 16:44:32 -04:00
..
2026-09-25 02:11:36 -04:00
2025-05-06 11:01:25 +05:30