Reproducibility of iOS apps
See old issue here: #123 (closed)
I dedicated the last couple of days to look into this and I made some progress. I was able to fill in the blanks regarding step 9 in the Telegram Tutorial. So in theory, I have the individual pieces that would allow us to reproduce iOS apps. But I don't have a fully working example just yet. Before I invest even more time, I think it's time we discuss some things because even if I get it to work, the process will be much more involved than for Android apps.
From what it looks right now, these are the requirements:
- A computer running macOS, or a VM running macOS to do the actual build on
- Any iPhone between the iPhone 5s up to and including the iPhone X. This iPhone has to be jailbroken (Because of an exploit that can't be patched, all those iPhones can be jailbroken, iOS version doesn't matter)
- (maybe) An iCloud account to download Xcode and build the app. Not sure if it's possible without one
Setting up a macOS VM on a Mac is easy. But I don't know how easy it is to get a macOS VM running on Windows or Linux.
Jailbreaking the iPhone is pretty straight forward, however, I was not able to jailbreak it using the macOS VM, I had to run the jailbreaking tool on the macOS host (probably because some USB passthrough issue). But the jailbreaking tool should also run on linux, so hopefully jailbreaking shouldn't be a problem.
The process of checking the reproducibility would basically be the same as for android apps:
- Build the iOS app inside the macOS VM, which will produce an .ipa file. Let's call it
local.ipa
- Download the app from the App Store on the jailbroken device. Then decrypt and extract it, which is as simple as connecting the iPhone to the computer and running a python script (if everything is set up correctly). This will give us an .ipa, let's call it
appstore.ipa
- Compare the the
local.ipa
file with theappstore.ipa
In the last thread I thought step 2 would be very hard, but it turns out that it's actually pretty straight forward once you have a jailbroken iPhone.
So, for a proof of concept, I downloaded "AirGap Vault" from the App Store and extracted it from my phone (let's call it appstore.ipa
). I also downloaded the .ipa that we generated on our CI (let's call it ci.ipa
) that we submitted to apple (so in theory, these versions should match exactly). Sadly, this is not the case.
The ci.ipa
was about 120MB, whereas the appstore.ipa
was only around 6MB. I did not look further into this, but I think this is because the ci.ipa
contains additional files / versions for the different architectures available. These will then be stripped out or optimised for the individual platform you download it on. (Similar to AppBundles on Android?)
I extracted both archives (.ipa
files are just .zip
s):
ls -l app-store
-rw-r-xr-x 1 test staff 318832 Jan 10 23:04 App
-rw-r--r-- 1 test staff 6398 Jan 10 23:04 AppIcon60x60@2x.png
-rw-r--r-- 1 test staff 8079 Jan 10 23:04 AppIcon76x76@2x~ipad.png
-rw-r--r-- 1 test staff 57464 Jan 10 23:04 Assets.car
drwxr-xr-x 4 test staff 128 Jan 10 23:04 Base.lproj
drwxr-xr-x 6 test staff 192 Jan 11 00:07 Frameworks
-rw-r--r--@ 1 test staff 4167 Jan 10 23:38 Info.plist
-rw-r--r-- 1 test staff 8 Jan 10 23:04 PkgInfo
drwxr-xr-x 7 test staff 224 Jan 10 23:04 SC_Info
drwxr-xr-x 3 test staff 96 Jan 10 23:04 _CodeSignature
-rw-r--r-- 1 test staff 305 Jan 10 23:04 capacitor.config.json
-rw-r--r-- 1 test staff 1034 Jan 10 23:04 config.xml
drwxr-xr-x 94 test staff 3008 Jan 10 23:04 public
ls -l ci
-rwxr-xr-x@ 1 test staff 1239584 Nov 6 17:53 App
-rw-r--r--@ 1 test staff 6398 Nov 6 17:52 AppIcon60x60@2x.png
-rw-r--r--@ 1 test staff 8079 Nov 6 17:52 AppIcon76x76@2x~ipad.png
-rw-r--r--@ 1 test staff 194319 Nov 6 17:52 Assets.car
drwxr-xr-x@ 4 test staff 128 Nov 6 17:52 Base.lproj
drwxr-xr-x@ 17 test staff 544 Nov 6 17:53 Frameworks
-rw-r--r--@ 1 test staff 3984 Jan 10 23:38 Info.plist
-rw-r--r--@ 1 test staff 8 Nov 6 17:52 PkgInfo
drwxr-xr-x@ 3 test staff 96 Nov 6 17:52 _CodeSignature
-rw-r--r--@ 1 test staff 305 Nov 6 17:52 capacitor.config.json
-rw-r--r--@ 1 test staff 1034 Nov 6 17:52 config.xml
-rw-r--r--@ 1 test staff 7450 Nov 6 17:53 embedded.mobileprovision
drwxr-xr-x@ 94 test staff 3008 Nov 6 17:52 public
As we can see, there are some files that are present in one folder, but not the other. And some (eg. the "App" executable) are very different.
Running a diff gives me the following output:
diff --brief --recursive app-store ci
Files app-store/.DS_Store and ci/.DS_Store differ
Files app-store/App and ci/App differ
Files app-store/Assets.car and ci/Assets.car differ
Files app-store/Frameworks/Capacitor.framework/Capacitor and ci/Frameworks/Capacitor.framework/Capacitor differ
Files app-store/Frameworks/Cordova.framework/Cordova and ci/Frameworks/Cordova.framework/Cordova differ
Files app-store/Frameworks/CordovaPlugins.framework/CordovaPlugins and ci/Frameworks/CordovaPlugins.framework/CordovaPlugins differ
Only in ci/Frameworks: libswiftCore.dylib
Only in ci/Frameworks: libswiftCoreFoundation.dylib
Only in ci/Frameworks: libswiftCoreGraphics.dylib
Only in ci/Frameworks: libswiftCoreImage.dylib
Only in ci/Frameworks: libswiftDarwin.dylib
Only in ci/Frameworks: libswiftDispatch.dylib
Only in ci/Frameworks: libswiftFoundation.dylib
Only in ci/Frameworks: libswiftMetal.dylib
Only in ci/Frameworks: libswiftObjectiveC.dylib
Only in ci/Frameworks: libswiftQuartzCore.dylib
Only in ci/Frameworks: libswiftUIKit.dylib
Only in ci/Frameworks: libswiftos.dylib
Files app-store/Info.plist and ci/Info.plist differ
Only in app-store: SC_Info
Files app-store/_CodeSignature/CodeResources and ci/_CodeSignature/CodeResources differ
Only in ci: embedded.mobileprovision
I am no iOS expert, but with a little bit of optimism, this might be something we can work with. Very important to note: Because our app is a "hybrid" app that runs inside a webview, the vast majority of our code is located inside the "public" folder. And there seems to be no diff (which is to be expected, because we used the same build, but also because our android version is also reproducible).
I hope the differences in App
, Capacitor
, Cordova
and CordovaPlugins
are due to the build target and can be eliminated. If that is true, then the only confusing thing is why there are some .dylib
files in the ci
version, but not in the app store version. Maybe they get bundled into those executables somehow?
Here I would also like to paste a note from the Telegram Tutorial:
In case of a successful comparison, you will get a text along these lines:
IPAs are equal, except for the files that can't currently be checked:
Excluded files that couldn't be checked due to being encrypted:
PlugIns/SiriIntents.appex/SiriIntents
PlugIns/Widget.appex/Widget
PlugIns/NotificationContent.appex/NotificationContent
PlugIns/NotificationService.appex/NotificationService
PlugIns/Share.appex/Share
IPAs contain Watch directory with a Watch app which can't be checked currently.
IPAs contain .car (Asset Catalog) files that are compiled by the App Store and can't currently be checked:
Frameworks/TelegramUI.framework/Assets.car
Assets.car
IPAs contain .nib (compiled Interface Builder) files that are compiled by the App Store and can't currently be checked:
Base.lproj/LaunchScreen.nib
iOS: Notes
You will get a warning if the archive created in Step 7 contains encrypted files. If all these files are in the PlugIns subfolder, they represent various system extensions (e.g. external sharing, Siri, 3D touch). Decrypting such files using existing ways of receiving app archives via Jailbreak is non-trivial (but we're working on resolving this issue). If you do manage to decrypt them, e.g. on iOS 8, they will be matched.
You will be notified if the archive includes an Apple Watch app. The watch app will soon no longer be included in the archive.
Files with the .car extension are app resource archives (images, sounds) which were compiled and processed specifically for the target device. The App Store processes them in non-trivial ways, we're planning on getting rid of them in future versions.
The LaunchScreen.nib file is an empty file containing a description of the interface which is displayed by the system before the app is launched. It is processed by the App Store in a non-trivial way but doesn't contain any code and therefore may be ignored.
I think that because our app is a) a hybrid app and b) is a mainly offline app with a limited feature set, the diff might be relatively small compared to a more complex app. (Eg. we don't use any .nib
files or Push Notification features).
This is where I currently stand. So my questions are:
-
What would be the requirement for a "diff" to pass as reproducible? In the case of
AirGap Vault
, would it be ok if I was able to get rid of theApp
,Capacitor
,Cordova
andCordovaPlugins
diffs? It sounds like theAssets.car
does not contain code but only assets, which should be ok? Besides that, the differences are minimal (extra files in the ci/local build can be ignored, I assume). -
Who, besides me, would be willing to set up the testing environment? I would obviously be able to verify our own app, but it would be better if someone else did it. Because the process is rather straight forward once everything is set up, I could also reproduce other iOS apps, if the projects provided me with clear build instructions. But we have to be clear here: The project setup will never be as easy as with Android, because you can't run it inside docker (as far as I know). But once the build and jailbroken device are set up, the whole process (minus build times) should only take about 5 minutes.
Over the next days I will discuss my findings with a professional iOS developer, I hope he can shed some light on the open questions. But in any case, we need to discuss the above mentioned points.