Read every signer in a rotated app's lineage into signer.sha256
Closes part of #1296: a rotated app's index entry now names every certificate in its lineage, oldest first, so a client can match the one an installed copy reports.
What an app that rotated looks like
Signal 8.26.4 signs with two certificates: 29f34e5f in the v3.0 block for minSdk 24 to 32, and 4be4f6cd in the v3.1 block from 33, tied together by the SigningCertificateLineage in the v3.1 additional attributes.
get_first_signer_certificate() returns the certificate a verifier checks the APK against, and refuses an APK whose blocks disagree. That is right for its callers, and it has no way to say "these differ, and the lineage says so on purpose", so the rotated signer never reached the index. The official client then intersects signer.sha256 with what the device holds, which for an installed rotated app is the pre-rotation certificate, and the update is never offered. Droid-ify compares the current signer instead and shows a signature mismatch on every release.
The change
common.get_lineage_certificates()walks the proof-of-rotation attribute (0x3ba06f8c). Its own function, as @grote suggested, since androguard parses the v3.1 block but keeps those attributes as raw bytes. It needs nothing else from fdroidserver and can move upstream whole.common.get_all_signer_certificates()returns the lineage plus the v3.0 and v3.1 block certificates, oldest first, deduplicated, and falls back to the single-signer path for everything else.update.pyfillssigner.sha256from that list. index-v1's singlesignerandpreferredSignertake entry zero, which is still the certificate an installed copy reports, so nothing there changes.
AllowedAPKSigningKeys checks every entry, as before, so a rotated app pins its whole lineage. @linsui pointed out in review that the list comes from our own parse rather than Android's, and rejecting a correct APK beats accepting a wrong one.
Checked
Measured on lib at the tip of master, against the real Signal 8.26.4 APK: the walk returns 29f34e5f then 4be4f6cd, consumes the lineage buffer exactly, and matches the order apksigner lineage --print-certs prints.
The index side is running in a live repo: https://signal-rotation-test-repo-c17d9d.gitlab.io/fdroid/repo, fingerprint E69E234D842655386254EBB55B846E20DBC077E7FB4632995D360FF1BC6FC7B2, unmodified Signal APK, both certificates listed. @grote installed and updated from it with the official client.
New tests in tests/test_common.py. The lineage case builds a proof-of-rotation attribute by hand rather than shipping a rotated APK fixture, so the bytes under test are readable in the test, and it includes a second attribute the walk has to step over. Breaking the signed-data length prefix makes it fail, which I checked. The single-signer cases assert the new call agrees with get_first_signer_certificate on existing fixtures, none of which have a v3.1 block.
test_common.py, test_update.py and test_index.py show the same failures before and after this branch here (they want an apksigner in the test config), and black --check is clean.
One thing I did not do: verify the lineage signatures. fdroid update already runs apksigner verify on every APK before the index is built, and that rejects a proof-of-rotation record whose signatures do not verify, so the bytes this reads have been checked by then. Is there any path that indexes an APK without running apksigner verify on it first?