Reproducible Builds
Reproducible Builds ensure that given the same source code and build environment, anyone can recreate bit-by-bit identical build artifacts. This allows anyone to verify that the pre-compiled artifacts that the FMD team publishes (APKs for FMD Android, Go binaries for FMD Server) were infact produced from the claimed source code, and have not secretly been altered.
Reproducible Builds are important for security because end-user devices run compiled binary code, not source code. However, auditing binary code is extremely difficult and time consuming. Reproducible Builds allow reviewers to audit the source code, and use bit-by-bit reproducibility to establish the causal link to the binary that is executed.
The Reproducible Builds project has excellent articles on why reproducibility is important: short version, long version.
Scope
The goal of FMD is for the main release channels to be reproducible. This includes production releases published on F-Droid and on https://packages.fmd-foss.org.
Other release channels (such as the Docker images) are out-of-scope and may have limited reproducibility.
FMD Android
FMD Android 0.13.0 and later is reproducible. Versions 0.12.0 and earlier were nearly reproducible, up to a stray timestamp. For details, see the Reproducibility Status page maintained by F-Droid.
Reproducible Builds with releases signed by the FMD team are planned.
To reproduce the APK yourself:
- Make sure that you have the Android SDK installed.
- Clone the FMD Android git repository:
git clone git@gitlab.com:fmd-foss/fmd-android.git - Check out the version you want to reproduce:
cd fmd-android && git checkout v0.13.0 - Build the APK:
./gradlew assembleProdRelease - This should produce an unsigned APK:
app/build/outputs/apk/prod/release/app-prod-release-unsigned.apk
One way to compare the two APKs is with diffoscope.
The APKs should be nearly bit-by-bit identical.
They will only differ in the signature.
This means, that they will differ in the following files:
META-INF/FOOBAR.SF, META-INF/FOOBAR.RSA, META-INF/MANIFEST.MF.
Alternatively, you can use apksigcopier
to compare the two APKs.
apksigcopier will copy the signing block from the signed APK onto the unsigned APK, and then verify the APK.
If the APKs match, this command returns with exit code 0 and no output.
apksigcopier compare --unsigned ~/Downloads/de.nulide.findmydevice_37.apk app/build/outputs/apk/prod/release/app-prod-release-unsigned.apk
FMD Server
For FMD Server, pre-compiled binaries are published on https://packages.fmd-foss.org/server/.
They are published as a ZIP file that contains binaries for multiple target architectures (amd64, arm, arm64)
as well as supporting files (such as the config.example.yml).
To reproducibly build a given FMD Server release:
- Make sure that you have Docker installed.
- Clone the FMD Server git repository:
git clone git@gitlab.com:fmd-foss/fmd-server.git - Build the ZIP for the release in question:
./scripts/bundle_repro.sh v0.13.0 - This should have produced two files:
fmd-server-v0.13.0.zipandfmd-server-v0.13.0.zip.sha256sum
To verify that the builds are identical, compare the fmd-server-v0.13.0.zip file
that you just built locally with the ZIP file that is published on https://packages.fmd-foss.org/server/.
Additionally, you can verify that the SHA-256 hash matches the ZIP file:
$ sha256sum --check fmd-server-v0.13.0.zip.sha256sum
fmd-server-v0.13.0.zip: OK
Docker image
The pre-built Docker images only have partial reproducibility. Reproducibility of the entire image is out-of-scope. It is possible to reproduce the FMD Server binary that is included in the image. The steps are as follows:
-
Reproduce the ZIP for a given FMD Server version as described above.
-
Extract the FMD Server binary from the Docker image in question. Run the container, and use
docker cpto copy the binary out of the container and in to a local path:docker run --name fmd_server --rm registry.gitlab.com/fmd-foss/fmd-server:0.13.0-debian
# in a different shell
docker cp fmd_server:/opt/fmd-server ./fmd-server-bin -
Verify that the extracted binary matches the binary from the reproduced ZIP.
What if a build is not reproducible?
To find where exactly two artifacts differ, diffoscope is a useful tool.
In most cases, a non-reproducible build has a non-malicious cause. Modern build environments are complex, and it is easy to break reproducibility by accident. For example, Google's Android Gradle Plugin has had regressions that broke reproducibility of APKs. See also the F-Droid docs.
If you find that a build is not reproducible, please open an issue and explain your steps (what are you trying to reproduce, what are you getting, what are you expecting).