From e145bbe93229c56677a5e59f275a95f20ddcfc90 Mon Sep 17 00:00:00 2001 From: zeuzmakessoftware Date: Sun, 20 Sep 2026 22:56:31 -0700 Subject: [PATCH] ci(ios): bundle the verified generic developer image --- .github/workflows/build_ipa.yml | 10 +++++++ scripts/prepare_cryptex_ddi.py | 53 +++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 scripts/prepare_cryptex_ddi.py diff --git a/.github/workflows/build_ipa.yml b/.github/workflows/build_ipa.yml index e0c08bb..6f25af2 100644 --- a/.github/workflows/build_ipa.yml +++ b/.github/workflows/build_ipa.yml @@ -29,6 +29,15 @@ jobs: with: xcode-version: '26.6' + - name: Test Cryptex image validation + run: swift test + + - name: Prepare checksum-pinned idevice with Cryptex support + run: python3 scripts/prepare_idevice.py + + - name: Prepare verified generic developer image + run: python3 scripts/prepare_cryptex_ddi.py --output build/BundledCryptexDDI + - name: 3. Compile Project Archive run: | xcodebuild clean archive \ @@ -48,6 +57,7 @@ jobs: - name: 4. Package .app into .ipa run: | cp -R build/StikDebug.xcarchive/Products/Applications/StikDebug.app . + cp -R build/BundledCryptexDDI StikDebug.app/BundledCryptexDDI mkdir -p Payload cp -R StikDebug.app Payload/ zip -r StikDebug.ipa Payload diff --git a/scripts/prepare_cryptex_ddi.py b/scripts/prepare_cryptex_ddi.py new file mode 100644 index 0000000..d558250 --- /dev/null +++ b/scripts/prepare_cryptex_ddi.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +"""Bundle the verified generic DDI used in the iPhone19,2 mounting test.""" +import argparse +import hashlib +import plistlib +import shutil +import tempfile +import urllib.request +from pathlib import Path + +REVISION = "59a90f1e3f1e7190b9a429c768568b83c321582e" +BASE = f"https://raw.githubusercontent.com/doronz88/DeveloperDiskImage/{REVISION}/PersonalizedImages/Xcode_iOS_DDI_Cryptex" +PAYLOADS = { + "Cryptex1,GenericDmg": "Image.dmg", + "Cryptex1,GenericTrustCache": "Image.dmg.trustcache", + "Cryptex1,CryptexInfoPlist": "Image.dmg.cryptex_info", + "Cryptex1,GenericVolume": "Image.dmg.root_hash", +} + + +def prepare(output: Path): + if output.exists(): + raise ValueError(f"Output already exists: {output}") + output.parent.mkdir(parents=True, exist_ok=True) + staging = Path(tempfile.mkdtemp(prefix="cryptex-", dir=output.parent)) + try: + def download(name): + with urllib.request.urlopen(f"{BASE}/{name}", timeout=120) as response: + data = response.read() + (staging / name).write_bytes(data) + return data + + manifest = plistlib.loads(download("BuildManifest.plist")) + identity = next(i for i in manifest["BuildIdentities"] + if i.get("Info", {}).get("Variant", "").endswith("Developer Disk Image Cryptex")) + for key, name in PAYLOADS.items(): + entry = identity["Manifest"][key] + if entry["Info"]["Path"] != name: + raise ValueError(f"Unexpected Cryptex path for {key}") + data = download(name) + if not data or hashlib.sha384(data).digest() != entry["Digest"]: + raise ValueError(f"Cryptex payload failed verification: {name}") + staging.rename(output) + print(f"Verified Cryptex DDI {manifest['ProductBuildVersion']} from {REVISION}") + finally: + if staging.exists(): + shutil.rmtree(staging) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--output", type=Path, required=True) + prepare(parser.parse_args().output)