Closed valerio-castelli closed 2 years ago
are you sure reproducible builds are needed, or even useful? here's some thoughts on the security advantage (or lack thereof) of reproducible builds written much better than I could, and that's before the whole App Store process is taken into account
I strongly disagree with this article. I think it was written about reproducibility of packages and not apps. And if you read the whole article, he does mention the benefits of reproducible builds, but dismisses them because it doesn't cover all potential attack vectors on build systems:
Q. Whether it’s useful for end users or not, it will allow experts to monitor for compromised build servers producing tampered builds.
I think this is true, but there are other attacks against compromised build servers, all of which are more common than producing tampered builds.
More often, attackers want signing keys so they can sign their own binaries, steal proprietary source code, inject malicious code into source code tarballs, or malicious patches into source repositories.
Reproducible builds don’t help with any of those problems.
Of course he is right, but what's his point? They are separate problems and require separate solutions. I don't understand why he suggests that one has to do with the other.
@valerio-castelli Thank you for this comprehensive write up. Have you made any progress in this regard?
I tried to reproduce the telegram app myself, but Step 9 seems to be missing. Do you have any resources on how to obtain a decrypted version of the app?
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Reproducible builds
Table of contents
Context
This issue presents the current status of our investigation into reproducible and verifiable builds. As we mentioned in Technology Description, we're conducting active research on the topic, and there's still work to do to achieve a fully satisfactory solution. However, we'd like to start sharing some of our findings.
To simplify the discussion, we're going to break down this issue in three parts. First, we'll cover how the continuous integration process works, and what measures have been put in place to ensure the integrity of builds. Then, we'll present a methodology to allow contributors to verify App Store builds against the binaries that are published on GitHub by the continuous integration. Finally, we'll discuss our progress towards allowing contributors to make builds that match the ones produced by the continuous integration.
Continuous Integration process
Immuni's continuous integration process has been designed to be transparent and independently verifiable from the start. In this section, we'll go through the most important aspects of its design, explaining which measures have been put in place to make it so.
Every build is characterized by a unique combination of release version and build number - for example, version 1.0.0, build 944. After a build has been uploaded to App Store Connect, it cannot be removed or replaced with a different build sharing the same combination of release version and build number.
In addition, all builds that are released on the App Store are built and uploaded by the continuous integration process, automatically and without human intervention. For this purpose we rely on CircleCI, a robust continuous integration platform that supports both macOS and Docker executors, allowing us to build both the iOS and the Android variants of the app.
Each build is associated with a specific commit, which is checked out by CircleCI at the beginning of the build phase. At the end of each build, a GitHub release containing the resulting binary is created, and the binary is uploaded to App Store Connect for testing and review.
Since the entire process is fully automated and binaries can't be removed from App Store Connect after they've been uploaded, this ensures that builds can't contain any code that hasn't been publicly committed and verified by the community. However, this also implies that ensuring the integrity and transparency of the continuous integration process becomes paramount.
To ensure that integrity and transparency, we implemented several measures that help making the process independently verifiable. Among these, all continuous integration pipelines are publicly documented and maintain the full log of operations that have been performed, as well as the exact CircleCI configuration that was used. In addition, every configuration file, build script, and dependency used by the continuous integration process has been released as open source software.
Finally, we implemented an automatic CircleCI job that periodically verifies the integrity of all the critical files used by the continuous integration process. This helps us ensure that the continuous integration can't be modified to inject malicious code inside the app or to leak sensitive information that can't be made public, for instance the credentials required to upload the binaries to App Store Connect and Google Play Store.
Builds verifiability
In addition to ensuring that builds are performed in a public and verifiable way, we believe it is important to have secondary methods to check that builds only contain legitimate code. This adds an additional layer of security in case the continuous integration is compromised or bypassed.
One way to do this is to ensure that external contributors can independently verify the integrity of builds, so they can check that the binary they download from the App Store has indeed been built from a specific revision of the source code and does not contain undocumented functionalities.
Verifying iOS builds is, unfortunately, quite more complex than verifying Android builds. As explained by Telegram in their post on reproducible builds, there are two main issues that make the process cumbersome:
With the current state of things, a decrypted binary from an iOS device is needed to proceed with build verification. In addition, the following steps assume you have access to a Mac or a macOS virtual machine; some operations may be reproducible on other operating systems as well, but we haven't investigated that yet.
Requirements
In order to proceed with the build verification process, make sure you have Xcode 11.5 installed in your macOS environment. In addition, you'll need to grab the following resources:
main.cpp
andipadiff.py
source filesThe build verification suite is compatible with both Python 2 and Python 3, and should therefore be executable on a standard macOS environment without further dependencies.
Verification process
Once you have the necessary software and resources on your machine, you can proceed with the verification steps.
To simplify the process, we suggest to setup your machine as follows:
ipadiff
and be placed within your home directoryImmuni-AppStore.ipa
Immuni-CI.ipa
At the end of these steps, the ipadiff folder should look like this:
You can now verify the integrity of the App Store build by opening a terminal window and entering the following commands:
Assuming everything works correctly, the output should be similar to this:
How does this work?
Once a package has been uploaded to App Store Connect, it undergoes a process that prepares it for distribution. This process may include several steps, but it can generally be summarized as follows:
ITSDRMScheme
propertyIn general, iOS apps may be compiled either to the target architecture instruction sets or to an intermediate LLVM bitcode representation. If the submitted package has been compiled to bitcode, Apple takes care of compiling it for all supported target architectures, producing optimized binaries through a technique called binary slicing. While compiling to bitcode would lead to shorter build times on the developer side, the continuous integration binary would be fundamentally different from the one distributed through the App Store, making any comparison between the two impossible. For this reason, Immuni is compiled directly to the target architecture instruction sets.
Like all applications published after the introduction of iOS 9, Immuni builds undergo a process of app thinning during the App Store distribution process which creates different optimized packages for different families of iOS devices. As builds are not submitted in bitcode format, the app thinning process is limited to the elimination of unnecessary assets from the distributed package and does not alter the executable code. In addition, the package exported by the continuous integration process is universal and contains the necessary assets for all families of iOS devices. This allows external contributors to verify their decrypted App Store binary against the original package, regardless of which device they obtained it from.
The changes introduced by Apple to the uploaded package mean that a verbatim comparison between the decrypted App Store package and its original continuous integration copy is not possible. However, a successful comparison can be obtained with minor modifications to the packages that do not alter the executable code. To this end, we provide a build verification suite that is heavily based on the work made by Telegram for their own reproducible builds effort.
The first step is to remove from the comparison all files which can't be accurately compared. This means, in particular, ignoring the assets and the nib files, as well as all files and directories related to codesigning. Note that none of these files contain executable code, so their removal does not affect the integrity of the comparison.
The second step is to remove from the Info.plist the additional keys that are injected by the Apple servers upon the upload of the package; this refers in particular to the
ITSDRMScheme
property. Although not strictly necessary for this phase, we also remove theUISupportedDevices
,DTAppStoreToolsBuild
,MinimumOSVersion
,BuildMachineOSBuild
, andCFBundleVersion
properties from the file, as their removal can simplify the process of supporting reproducible builds. Also in this case, none of these modifications involve executable code, so the integrity of the comparison is preserved.At this point, provided that the supplied App Store package has been decrypted, all changes to the binary introduced by Apple's distribution process have been reverted. However, due to the way the decrypted App Store package is obtained, a further step is necessary to successfully compare it to its original counterpart.
As iOS apps are decrypted only when they're loaded into memory at runtime, the process of obtaining a decrypted version of an App Store package implies that the binary must be extracted from a snapshot of the device's RAM. As iOS implements Address Space Layout Randomization (ASLR), some of the addresses and offsets of the obtained binary will not match the ones included in the original binary.
To get around this, the build verification suite includes a program that replaces the affected addresses and offsets with zeros in both binaries. The affected commands and segments of the binary are the
LC_CODE_SIGNATURE
,LC_SEGMENT_64
,__LINKEDIT
,LC_ID_DYLIB
,LC_UUID
andLC_ENCRYPTION_INFO_64
. For a more thorough explanation of these sections, please refer to Apple's implementation of the iOS Mach-O loader.As all differences have now been accounted for, the two packages can now be successfully compared.
Build reproducibility
A further way to ensure that the builds delivered through the App Store accurately reflect the source code published in the official repositories is to allow external contributors to compare them with their own builds.
This introduces additional challenges on top of the build verification process explained above. More specifically:
So far, we've investigated a few mitigations to make build reproducibility a reality. While we haven't got there yet, we have encouraging evidence that it should be possible to achieve it with the current technological stack.
Requirements
In order to attempt a reproducible build, make sure your machine is configured as described in the Requirements section of the Build verifiability process. If it is possible, use a macOS environment equipped with the same version of macOS as the one included in the Xcode 11.5.0 CircleCI image - which, at the time of writing, is macOS 10.15.4.
You'll also need to have an Apple Developer account, register an application with a dedicated bundle identifier, and obtain a valid signing certificate and provisioning profile for it. This is a required step in order to sign and export an ipa package. The account can be a free one, as the ability to export a package for App Store distribution is not needed to perform build verification.
Please note that you won't be able to use the official bundle identifier of Immuni -
it.ministerodellasalute.immuni
. In fact, the bundle identifier must be unique on the App Store, and the provisioning profile can only be emitted for an app belonging to your own developer account. We're investigating the possibility of releasing dummy provisioning profiles and signing certificates for this purpose, but they're not available at this time.In addition, you'll need to make a few modifications to the continuous integration pipeline. These modifications are necessary to mimic the process of making an App Store build without actually making one, as exporting an App Store build would require being enrolled in the paid Apple Developer Program.
To this end, make sure you've checked out the commit that you want to build. Then, create a text file called
validation.plist
within theCI/Export
folder with the following content:In addition, add the following lane to the
fastlane/Fastfile
file:Finally, modify the
project.yml
file so that it uses the development signing identity and entitlements for Release builds:Build process
Once you've modified the project's files as described above, install your signing certificate and provisioning profile through fastlane like so:
You can now attempt to perform a reproducible build by executing the following commands:
If everything works correctly, at the end of the process you should find an ipa package in the Products directory of the git repository. However, the package can't be compared with its App Store variant yet - for that, a couple of modifications are necessary.
Since the code signing identity is going to be different from the one of the App Store binary, it is necessary to strip the code signing from both binaries. To do so, you can apply these instructions both to the decrypted App Store package and to the package of your own build:
Payload
folder back into a zip packageAt this point, the two packages are ready for comparison. At the time of writing, however, the comparison will likely fail, due to differences in the way Immuni is compiled on different machines. We believe the issue to be related to one or more flags of the
project.yml
file and we're currently investigating solutions.