Some considerations regarding the APK Signing Block and how F-Droid handles Reproducible Builds.
The signature part of the APK Signing Block can contain more than one signature.
AFAIK android and apksigner (unlike apksigtool) only check the one with the strongest supported signature algorithm ID, not all of them.
So it might be possible to hide some data in a signature that won't be checked normally.
Either way it's possible for the app to behave differently depending on which key it was signed with, though we can't do much about that.
Related: Android App Links.
These opaque Google-specific extensions have their own block types.
There is some documentation, code, and reverse engineering, but in the end we don't really know what's in there.
Another opaque Google-specific extension.
"[...] data is compressed, encrypted by a Google Play signing key [...]"
AFAIK these are simply ignored by apksigner etc.
So a developer could just create their own block type (or abuse an existing one) and put anything they want in there, including code.
Android apps have access to their own APK file (e.g. I can run apksigcopier extract in Termux on Termux' own APK in /data/app/), so AFAIK an app could easily load some kind of code/instructions from the Signing Block, especially from something other than the APK Signature Scheme Block.
I haven't made a PoC for this yet, but it's on my TODO list somewhere.
- Apps signed by F-Droid should be "safe".
- Apps using signatures in metadata could already have put something additional in the
APKSigningBlock, but there is some accountability via git history. - Apps using
Binaries:-- or the server we download the APK from -- could put anything additional in there.
I'd recommend at least "cleaning" and/or "linting" the APK Signing Block as part of our build process / CI, allowing only APK Signature Scheme Blocks (and verity zero padding), and ideally making sure all signatures in there are valid.
The downside to "cleaning" (when it actually removes something) is that it results in an APK file that verifies correctly, but will not have the same checksum as the original since the APK Signing Block differs.
Current apps using signatures in metadata seem to be "clean":
Output
$ for file in $( find -name APKSigningBlock ); do apksigtool clean --block "$file"; done | uniq -c
37 nothing to clean
$ for file in $( find -name APKSigningBlock ); do apksigtool parse --block --json "$file" | jq -r '.pairs[].value._type'; done | sort | uniq -c
72 APKSignatureSchemeBlock
35 VerityPaddingBlockHowever, two apps using Binaries: have a DependencyInfoBlock (encrypted by a Google Play signing key).
Output
$ for file in *.apk; do echo "==> $file"; apksigtool parse --json "$file" | jq -r '.pairs[].value._type'; echo; done
==> androdns.android.leetdreams.ch.androdns_15.apk
Error: No APK Signing Block.
==> androdns.android.leetdreams.ch.androdns_16.apk
Error: No APK Signing Block.
==> ch.admin.bag.covidcertificate.verifier_4040000.apk
APKSignatureSchemeBlock
DependencyInfoBlock
VerityPaddingBlock
==> ch.admin.bag.covidcertificate.verifier_4060000.apk
APKSignatureSchemeBlock
DependencyInfoBlock
VerityPaddingBlock
==> ch.admin.bag.covidcertificate.verifier_4070000.apk
APKSignatureSchemeBlock
DependencyInfoBlock
VerityPaddingBlock
==> ch.admin.bag.covidcertificate.wallet_4040000.apk
APKSignatureSchemeBlock
DependencyInfoBlock
VerityPaddingBlock
==> ch.admin.bag.covidcertificate.wallet_4060000.apk
APKSignatureSchemeBlock
DependencyInfoBlock
VerityPaddingBlock
==> ch.admin.bag.covidcertificate.wallet_4070000.apk
APKSignatureSchemeBlock
DependencyInfoBlock
VerityPaddingBlock
==> com.mishiranu.dashchan_1041.apk
APKSignatureSchemeBlock
VerityPaddingBlock
==> com.mishiranu.dashchan_1042.apk
APKSignatureSchemeBlock
VerityPaddingBlock
==> com.mishiranu.dashchan_1043.apk
APKSignatureSchemeBlock
APKSignatureSchemeBlock
VerityPaddingBlock
==> de.corona.tracing_2240202.apk
APKSignatureSchemeBlock
APKSignatureSchemeBlock
VerityPaddingBlock
==> de.corona.tracing_2250002.apk
APKSignatureSchemeBlock
APKSignatureSchemeBlock
VerityPaddingBlock
==> de.corona.tracing_2260004.apk
APKSignatureSchemeBlock
APKSignatureSchemeBlock
VerityPaddingBlock
==> de.schildbach.oeffi_120025.apk
APKSignatureSchemeBlock
APKSignatureSchemeBlock
VerityPaddingBlock
==> de.schildbach.oeffi_120100.apk
APKSignatureSchemeBlock
APKSignatureSchemeBlock
VerityPaddingBlock
==> de.schildbach.oeffi_120103.apk
APKSignatureSchemeBlock
APKSignatureSchemeBlock
VerityPaddingBlock
==> eu.bubu1.fdroidclassic_1014.apk
Error: No APK Signing Block.
==> eu.bubu1.fdroidclassic_1106.apk
APKSignatureSchemeBlock
APKSignatureSchemeBlock
VerityPaddingBlock
==> eu.bubu1.fdroidclassic_1110.apk
APKSignatureSchemeBlock
APKSignatureSchemeBlock
VerityPaddingBlock
==> info.guardianproject.checkey_101.apk
Error: No APK Signing Block.
==> nya.kitsunyan.foxydroid_2.apk
Error: No APK Signing Block.
==> nya.kitsunyan.foxydroid_3.apk
Error: No APK Signing Block.
==> nya.kitsunyan.foxydroid_4.apk
Error: No APK Signing Block.
==> org.briarproject.briar.android_10410.apk
APKSignatureSchemeBlock
==> org.briarproject.briar.android_10411.apk
APKSignatureSchemeBlock
==> org.briarproject.briar.android_10412.apk
APKSignatureSchemeBlock
==> org.jellyfin.mobile_2040199.apk
APKSignatureSchemeBlock
APKSignatureSchemeBlock
VerityPaddingBlock
==> org.jellyfin.mobile_2040299.apk
APKSignatureSchemeBlock
APKSignatureSchemeBlock
VerityPaddingBlock
==> org.jellyfin.mobile_2040499.apk
APKSignatureSchemeBlock
APKSignatureSchemeBlock
VerityPaddingBlock
==> rs.ltt.android_12.apk
APKSignatureSchemeBlock
APKSignatureSchemeBlock
VerityPaddingBlock
==> rs.ltt.android_13.apk
APKSignatureSchemeBlock
APKSignatureSchemeBlock
VerityPaddingBlock
==> rs.ltt.android_14.apk
APKSignatureSchemeBlock
APKSignatureSchemeBlock
VerityPaddingBlock
==> top.fumiama.copymanga_23.apk
APKSignatureSchemeBlock
APKSignatureSchemeBlock
VerityPaddingBlock
==> uk.co.keepawayfromfire.screens_6.apk
APKSignatureSchemeBlock
==> uk.co.keepawayfromfire.screens_7.apk
APKSignatureSchemeBlock
==> uk.co.keepawayfromfire.screens_8.apk
APKSignatureSchemeBlockRelated: #1055, #974, #1013, #935.
Edit 1: added apksigtool clean & apksigtool parse output for apps using signatures in metadata.
Edit 2: added apksigtool parse output for apps using Binaries: and Dependency Info Block section.
When building your app using AGP 4.0.0 and higher, the plugin includes metadata that describes the library dependencies that are compiled into your app. [...] The data is compressed, encrypted by a Google Play signing key, and stored in the signing block of your release app. [...]
This bit was present in the old link that no longer has the info but isn't in the new one:
However, you can inspect the metadata yourself in the local intermediate build files in the following directory:
<project>/<module>/build/outputs/sdk-dependencies/release/sdkDependency.txt.
android {
dependenciesInfo {
// Disables dependency metadata when building APKs.
includeInApk = false
// Disables dependency metadata when building Android App Bundles.
includeInBundle = false
}
}If I make 2 copies of an unsigned APK and sign each separately with apksigner (using the same key) I get 2 bit-by-bit identical signed APKs.
$ cp -i unsigned.apk signed-1.apk
$ cp -i unsigned.apk signed-2.apk
$ apksigner sign ... signed-1.apk
Signed
$ apksigner sign ... signed-2.apk
Signed
$ shasum signed-*.apk
6e40428963a3e8c56f753848316fd620437126ef signed-1.apk
6e40428963a3e8c56f753848316fd620437126ef signed-2.apkBut if I use Android Studio (or a Gradle signing config) to build a signed APK (from the same commit and using the same signing key), it will add the Dependency Info Block (unless disabled), which is not deterministic, and thus I never get bit-by-bit identical signed APKs.
The only difference is the Dependency Info Block; cleaning the APK with apksigtool clean makes the APKs bit-by-bit identical as expected (and the signature stays valid).
IMO that does count as breaking RB. The signed APKs should be identical and they are not.
Which is at the very least causing confusion for devs trying to make their apps RB, as their builds are not identical and it is not at all obvious to them that it's "only" the Dependency Info Block that's different.
I'll report this as a bug to Google, but I don't really expect them to fix it any time soon.
And even if they do I still want the block gone since it contains encrypted data only Google can read.
v2-signed APKs should be bitwise identical after signature copying (without cleaning), so maybe we should not just verify the signature but also the shasum?
