Publish Cryptex1 DDI assets, and a script to refresh them - #27
Merged
Conversation
Publishing a new DDI was a manual copy out of `/Library/Developer`. This script does it reproducibly: it locates the platform's `Restore` directory (attaching `CoreDevice/CandidateDDIs/<platform>_DDI.dmg` when Xcode has not expanded one), verifies every payload against the SHA-384 digests its build manifest declares, and republishes it under fixed file names so download URLs stay predictable across releases. Apple's own paths embed a build number, so the published `BuildManifest.plist` is rewritten to declare the fixed names instead. Only `Info.Path` is adjusted; the digests personalization relies on are untouched. It is a PEP 723 script with no third-party dependencies -- run it with `uv run --script update_ddi.py`.
iOS 17+ can have the DeveloperDiskImage installed as a cryptex through `cryptexd` instead of mounted through the image mounter. That path needs four `Cryptex1,*` payloads which exist only inside Xcode's own bundle -- the personalized variant published here ships `PersonalizedDMG`, which has no cryptex info plist or volume root hash -- so hosts without Xcode, notably Windows, cannot install a DDI that way at all today. Publish them as their own variant alongside the personalized one, and add `get_cryptex_disk_image()` to reach it. Written back out under their published names, the result is a directory `cryptexd` clients accept as-is, since the build manifest shipped with them declares those same names. Note `test_cryptex_disk_image` reads the `main` tree, so it only goes green once this is merged -- the same chicken-and-egg the personalized test had.
Refreshed with `update_ddi.py`. The personalized payloads move from 27A5194q, and their `BuildManifest.plist` now declares the published file names. Bump `LATEST_DDI_BUILD_ID` in pymobiledevice3 to 27A5228h to match.
Reading blobs through the REST API cost a recursive tree listing of a repository full of disk images on every `create()`, and burned the anonymous 60-requests-per-hour quota -- which is why the tests are `xfail`ed on `GithubRateLimitExceededError` rather than simply expected to pass. `raw.githubusercontent.com` is not under that quota, needs no token, and serves any ref. Since every payload now lives at a known fixed path, the tree listing bought nothing: resolve paths directly, and treat 404 as "not published". `create()` therefore performs no request at all, and takes a `ref` so a caller can read a revision that is not merged yet. The tests use it to check the revision under test instead of `main`, which they could not see their own payloads from. `DeveloperDiskImageRepository.__init__` no longer takes a tree.
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
iOS 17+ can have the DeveloperDiskImage installed as a cryptex through
cryptexd, rather than mounted through the image mounter. That path needs fourCryptex1,*payloads which exist only inside Xcode's own bundle — the personalized variant published here shipsPersonalizedDMG, which has no cryptex info plist or volume root hash. So today a host without Xcode (notably Windows) cannot install a DDI that way at all.This publishes those assets as their own variant, and adds a script so refreshing every variant is reproducible instead of a manual copy out of
/Library/Developer.What's here
update_ddi.py— a PEP 723 script (no third-party deps,uv run --script update_ddi.py). It locates the platform'sRestoredirectory, attachingCoreDevice/CandidateDDIs/<platform>_DDI.dmgwhen Xcode has not expanded one, verifies every payload against the SHA-384 digests in the build manifest, and removes stale files left by a previous build. Has--dry-run,--platform,--variant.PersonalizedImages/Xcode_iOS_DDI_Cryptex/— the new variant.get_cryptex_disk_image()— returns the four payloads plus the build manifest.raw.githubusercontent.cominstead of the REST API.Predictable names
Every payload is published under a fixed name (
Image.dmg,Image.dmg.trustcache,Image.dmg.cryptex_info,Image.dmg.root_hash), so download URLs are stable across releases. Apple's own paths embed a build number (022-22107-072.dmg), so the publishedBuildManifest.plistis rewritten to declare the fixed names. OnlyInfo.Pathis adjusted — the digests personalization relies on are untouched — which keeps a downloaded cryptex directory usable as a drop-inRestoredirectory.No more rate limiting
Reading blobs through the REST API cost a recursive tree listing of a repository full of disk images on every
create(), and burned the anonymous 60-per-hour quota — which is why the tests werexfailed onGithubRateLimitExceededErrorinstead of simply expected to pass.raw.githubusercontent.comis not under that quota, needs no token, and serves any ref. Since every payload now sits at a known fixed path, the tree listing bought nothing.Consequently
create()performs no request at all and takes aref, so the tests check the revision under test rather thanmain— previously a PR adding payloads could not see its own files and could only go green after merging. API change:DeveloperDiskImageRepository.__init__no longer takes a tree.Verification
The published directory was used to install the DDI on a physical iPhone 11 (iOS 27) end to end, over
cryptexdalone, viapymobiledevice3 cryptex auto-install --restore-dir <this directory>. Digest verification passes for every payload; the script is idempotent (a second run reports zero changes) and each commit is flake8/isort clean.Follow-up
LATEST_DDI_BUILD_IDin pymobiledevice3 needs bumping to27A5228h, andcryptex auto-installcan drop its Xcode requirement once adeveloper_disk_imagerelease carriesget_cryptex_disk_image().