Reproduzierbare Builds, Signierschlüssel und Binärdatei-Repos

Anfang des Jahres berichteten wir über unsere Fortschritte, was reproduzierbare Builds betrifft. Mittlerweile verwenden sie mehr und mehr Apps; hier findet ihr einige statistische Werte dazu: verglichen mit ungefähr 20 Apps im November 2022, stieg die Zahl der Apps, die reproduzierbar hergestellt werden, fast um den Faktor 10 auf 191 im September 2023. Rund 2 von 3 Apps, die neu zu F-Droid hinzukommen, verwenden dieses Verfahren. Aber was genau bedeutet „reproduzierbarer Build“, in einfachen Worten und ohne den ganzen „Technik-Kram“?

All die Jahre zuvor erstellte F-Droid einen fest zugeordneten Schlüssel für jede einzelne App, um die veröffentlichten APKs zu signieren, nun aber mit den reproduzierbaren Builds liefert F-Droid APKs aus, die von den einspielenden Entwicklern (im Upstream) signiert sind. Was euch beweist, dass die Entwickler bestätigten: „dies ist das, was ich beabsichtigte zu veröffentlichen, dies wurde aus meinem Code hergestellt“. Und dass es von F-Droid vertrieben wird, sagt euch: „F-Droid bestätigt ebenfalls, dass dies das APK ist, das aus ebendiesem Code hergestellt wurde, den die Entwickler vorlegten“. Somit konnte keiner der beiden irgendetwas „hineinschmuggeln“, was das Quellcode-Repository der App nicht enthielt.

Die Überprüfung bei F-Droid, wieder in einfachen Worten, geschieht folgendermaßen: die App wird aus dem Quellcode auf Build-Servern von F-Droid’s hergestellt. Dann wird das von den Entwicklern hergestellte APK als Gegenstück hergenommen und damit verglichen. Die einzigen Unterschiede soltten die Signatur-Dateien sein – da die APKs von den Entwicklern mit deren privaten Schlüsseln signiert wurden, worauf F-Droid keinen Zugriff hat. Wenn dies der Fall ist (d. h. die beiden APKs stimmen überein), wurde nachgewiesen, dass es sich um ein und dieselbe „Binärdatei“ handelt – und F-Droid kann die von den Entwicklern signierte herausgeben.

Ihr findet eine Beschreibung dieses Vorgangs hier und technische Einzelheiten dazu in dieser Dokumentation.

Vorteile und Vorbehalte

Einige Vorteile sind ganz klar: mehr Verlässlichkeit ist das Erste, was einem in den Sinn kommt, da jetzt zwei Parteien (F-Droid und die Entwickler) beide die Integrität des herausgegebenen APK bestätigen können. Aber es gibt da noch mehr. Zum einen, wenn Entwickler irgendein „Notfall-Update“ herausbringen müssen (z. B. um ein Sicherheitsproblem zu lösen oder weil etwas Kritisches kaputt ging), müsst ihr nicht länger einen oder zwei komplette Build-Zyklen abwarten, um das in die Hände zu bekommen. Da das APK, das ihr über F-Droid installiert habt, mit dem privaten Schlüssel der Entwickler signiert wurde, könnt ihr einfach ein APK hernehmen, das sie euch auf anderen Wegen (z. B. über das Repository der App auf Codeberg, GitLab oder Github) direkt bereitstellen und darüber aktualisieren – vorausgesetzt ihr vertraut den Entwicklern genug – was ihr dann müsst, da solche Builds (noch) nicht von F-Droid überprüft wurden.

Gibt es also irgendwelche „Nachteile“? Warum erwähnt die Überschrift „Vorbehalte“? Nun… F-Droid stellt jetzt APKs bereit, die es nicht selbst signiert hat. Was also, wenn ein Quellcode-Repo einer App von einer bösartigen Truppe beschädigt wurde, die dann den Code modifizierte und ihre eigene Version anbot, während der ursprüngliche Autor z. B. im Urlaub, im Krankenhaus oder anderweitig verhindert war? Sie würden natürlich das APK mit ihrem eigenen privaten Schlüsel signieren (da der ursprüngliche Autor seinen privaten Schlüssel hoffentlich sicher aufbewahrte). Aber das muss irgendwie berücksichtigt werden und man muss damit umgehen. Also haltet euch an:

AllowedAPKSigningKeys

Wann immer ein reproduzierbarer Build bei F-Droid eingeführt wird, wird der Hash des entsprechenden Entwickler-Zertifikats, das zum Signieren ihrer APKs verwendet wird, zusammen mit den anderen Metadaten auf F-Droids Seite gespeichert. Der Schlüsselbegriff in den Build-Metadaten dafür ist AllowedAPKSigningKeys. Wenn also das APK der Entwickler zum Vergleich herangezogen wird, wird die Signatur hiermit verglichen:

apksigner verify --print-certs app-release.apk 2>/dev/null \
 | sed -n 's/^Signer #1 certificate SHA-256 digest: \(.*\)/\1/p'

gibt uns den SHA-256-Hash des beim Signieren des APK genutzten Zertifikats aus. Sollte der nicht passen, wird das APK zurückgewiesen und der Build als „fehlgeschlagen“ angesehen. Das stellt sicher, dass F-Droid wirklich nur die vorgesehenen, mit dem korrekten Schlüssel signierten APKs ausliefert – und die oben genannten potentiell „bösartigen Gruppen“ nicht einfach „Dinge einschleusen“ kann. Ein gutes Sicherheitsmerkmal, das in diesem Zusammenhang verwendet wird, obgleich es ursprünglich für etwas anderes vorgesehen war:

Binäre Repositorys

Was ist das, fragt ihr euch? Nun: F-Droid ist nicht an das eine Repository, das von F-Droid betrieben wird, gebunden. Jeder kann sein eigenes Repository aufstellen. Wie die Apps, die F-Droid vertreibt, ist auch F-Droids Code „free and libre“. Eines der bekanntesten Drittanbieter-Repositorys ist wahrscheinlich das als „IzzyOnDroid Repo“ oder „IzzySoft Repo“ bekannte, das momentan mehr als 1.111 Apps anbietet. Dort wurde AllowedAPKSigningKeys für alle enthaltenen Apps in der ersten Woche im August 2023 eingeführt. Da dieses Repo seine APKs direkt aus den Entwickler-Repositorys holt, anstatt sie aus dem Quellcode herzustellen, ist diese Extramaßnahme besonders hilfreich – aus dem oben beschriebenen Grund: um sicherzustellen, dass alle Updates „echt“ sind (und nicht durch einen bösartigen Akteur ins Repo eingestellt wurde).

Wann immer der Updater ein neues APK aus dem entsprechenden App-Repo bei Codeberg, GitLab, Github usw. abholt, validiert „fdroidserver“, das es wirklich mit dem Schlüssel seines Autors signiert wurde. Wurde es das nicht, wird es niemals in den Repository-Index aufgenommen werden (also wird es nicht an euch ausgeliefert und kann euch somit auch nicht gefährden). Stattdessen wird der Betreiber des Repos eine Warnung erhalten und muss eine Untersuchung einleiten:

2023-09-01 20:56:25,845 WARNING: "com.example.app_123.apk" is signed by a key that is not allowed:
a0fe1234567890abcdefa0fe1234567890abcdefa0fe1234567890abcdef1234

Das bedeutet: Es wird Zeit zu untersuchen, was passiert ist. Da das APK nie den Index erreichen wird, bevor der neue Schlüssel vom Repository-Betreiber bestätigt worden ist, kann dies gründlich ohne Eile erledigt werden.

Ist das wirklich ein häufiges Problem?

Bedauerlicherweise, ja. Während des Aktualisierungsprozess der Metadaten im IzzyOnDroid-Repository, waren 26 der 1105 geprüften Apps von diesem Problem betroffen: die Signierschlüssel wurden seit der Vorstellung der Erstversion geändert. Das sind 2.35% der geprüften Apps. Als ob das nicht schlimm genug wäre, verging nicht eine der nächsten 4 Wochen, ohne dass nicht eine andere App von dem Problem erfasst wurde. Grob überschlagen: das Jahr hat 52 Wochen, das Repo enthält über 1000 Apps – mit gerade einmal einer App pro Woche, sind geschätzt eine von 20 Apps (5%) von diesem Problem mindestens einmal betroffen!

In jedem Fall wurden die entsprechenden Entwickler erreicht, so konnte der Grund ermittelt und das Problem (hoffentlich) gelöst werden. Ihr wundert euch vielleicht, was es verursacht haben könnte, deshalb hier einige der genannten Gründe. Alle von ihnen wohlgemeint, auf die ein oder andere Weise:

> „Ups, ich hab den Signierschlüssel verloren…“

  • die Festplatte stürzte ab (oder der ganze PC gab den Geist auf)
  • zufällig das Verzeichnis gelöscht, in dem das „wichtige Zeug“ drin war
  • Signieren wurde von einem Teammitglied erledigt, das uns verlassen hat (mit dem Schlüssel)
  • die Entwicklungsumgebung war frisch aufgesetzt oder auf eine neue Maschine umgezogen – und irgendwie wurde der Schlüsselspeicher beim Umzug vergessen (als das Problem entdeckt wurde, war die ursprüngliche Umgebung nicht mehr zugänglich)
  • die Entwickler hatten einen „Debug Key“ verwendet, als die Entwicklung begann (nett, wenn man lokal Dinge für sich selbst entwickelt – aber schlecht, wenn die App verbreitet werden soll), so mussten sie zu einem „Release Key“ wechseln
  • der Originalschlüssel war „zu schwach“ und musste durch einen stärkeren ersetzt werden

In den letzten beiden Fällen, war die Rechtmäßigkeit durch Bereitstellung des neusten APK in zwei Varianten leicht nachzuweisen: ein und derselbe Build, nur einmal mit dem ursprünglichen und einmal mit dem neuen Schlüssel signiert. Somit konnten die beiden APKs miteinander verglichen werden, ähnlich wie in dem für reproduzierbare Builds verwendeten Prozess – vorausgesetzt der Originalschlüssel wurde nicht bereits gelöscht, weil er als „veraltet“ galt.

Gewonnene Erkenntnisse #1: die Repository-Betreiber

Es ist eine gute Maßnahme AllowedAPKSigningKeys in einem Binärdatei-Repo für alle Apps, die es enthält, einzuführen. Das Problem taucht weit öfter auf, als dass es ignoriert werden könnte. Die Apps, die du vertreibst müssen sicher sein: du hast gegenüber den Leuten, die dein Repository nutzen, eine Verantwortung. Während derlei Updates zurückgewiesen würden, wenn die App bereits auf einem Gerät installiert war („inkompatible Signatur“), würde das Problem bei denjenigen, welche die App „frisch“, zum ersten Mal installieren, nicht erkennt werden. Jene müssen genauso geschützt werden.

Die gute Nachricht: in keinem der Fälle, war eine böswillige Aktion die Ursache für die Änderung des Schlüssels. Das heißt nicht, das es nie der Fall sein wird – aber es ist gut zu wissen, dass wir unseren FOSS-Entwicklern ziemlich gut vertrauen können.

Die schlechte Nachricht: die Bedeutung, den Schlüsselspeicher zu schützen, scheint nicht hinreichend selbstverständlich zu sein. Wir müssen das Bewusstsein dafür schärfen.

Gewonnene Erkenntnisse #2: wie soll der Schlüssel geschützt und welche Maßnahmen für den Fall seines Verlustes ergriffen werden?

Das Offensichtliche: Sicherungen machen! Nicht gerade auf der Entwicklungsmaschine. Nutze ein anderes Gerät zur Sicherung abseits – z. B. auf einem verschlüsselten USB-Stick, der an einem sicheren Ort aufbewahrt ist, auf einem vertrauenswürdigen Server/Computer irgendwo (bei einem Freund, einem Familienmitglied oder einem Cloud-Dienst – wiederum, eine verschlüsselte Sicherung). Und vergewissere dich, dass du weißt, wie sie wiederhergestellt wird. Du musst nicht unbedingt eine Backup Ceremony wie diejenige, die das F-Droid-Team veranstaltete durchführen – aber einige Maßgaben sollten erfüllt sein. Und nein: hochladen ins öffentlich zugängliche Git-Repo deiner App ist keinesfalls eine gute Idee, schon gar nicht für eine Sicherung (lacht nicht, auch das passierte schon).

Das weniger Offensichtliche: sicherstellen, dass jemand/etwas für dich „bürgen“ kann. Wieder aus den betroffenen Fällen:

  • signiere deine Commits (GPG/PGP), idealerweise alle von ihnen und von Anfang an. Und natürlich, schütze deinen GPG/PGP-Schlüssel genauso. Auf diese Weise werden die nach dem Ereignis signierten Commits beweisen, dass du noch die Kontrolle über diesen Schlüssel hast. Es ist ziemlich unwahrscheinlich, dass jemand dein Git-Repo und deinen GPG/PGP-Schlüssel kompromittiert aber nicht deinen Schlüsselspeicher zum Signieren der Apps.
  • biete verschiedene (unabhängige) Arten an, mit dir Kontakt aufzunehmen. Dein beschädigtes Git-Repo könnte bedeuten, dass es über dein kompromittiertes Mail-Konto infiltriert wurde – aber wahrscheinlich nicht über dein XMPP- oder Matrix-Konto. Diese Informationen sollten ebenfalls frühzeitig zur Verfügung stehen: alle Einzelheiten, die erst nachträglich geliefert werden, könnten vom potentiellen „Gefährder“ stammen und würden dein Eigentum nicht belegen.
  • wiederum frühzeitig, beziehe eine Person ein, die dich kennt, die für dich bürgen kann – zum Beispiel, da sie deine Geschichte bestätigen kann, indem sie dich anruft oder dich persönlich trifft und dann eine Aussage dazu macht. Idealerweise ist diese Person ein Mitwirkender an deinem Repo. Aber am wichtigsten ist es, dass die Community wissen muss, dass sie ihr vertrauen kann. Was der Fall ist, weil es entweder eine wohlbekannte Person ist – oder wenigstens jemand, der lange vor dem Zwischenfall eingeführt wurde.

Gewonnene Erkenntnisse #3: was tun, wenn es passiert ist?

Ganz wichtig: nicht einfach „verschleiern“ oder „totschweigen“. Dies ist ein Sicherheitsvorfall; es unter den Teppich zu kehren, führt nur zum völligen Vertrauensverlust. Aktualisierungen werden ohne Deinstallation/Neuinstallation nicht funktionieren, so werden es die Leute ohnehin mitbekommen (außer im Google Play Store, der die APKs neu signiert). Lieber transparent damit umgehen:

  • erwähne es in den Release-Notizen (und im Änderungsprotokoll jeder Version, falls du Fastlane or Triple-T) verwendest.
  • verfasse vielleicht einen kurzen Artikel darüber (z. B. ein öffentliches Issue im Repo deiner App oder einen Blog-Beitrag), der erklärt, was passierte, was du tun musstest und was du daraus gelernt hast (damit andere auch daraus lernen können).
  • wenn du ein paar Hilfsmittel hast, die deine Identität belegen (du hast deine Commits immer signiert, du hast ein paar Leute, die für dich bürgen usw.), erwähne sie in den Release-Notizen/dem Artikel, damit das jeder überprüfen kann. Wenn du noch Zugriff auf deinen Schlüsselspeicher hast, ihn aber aus gewissen Gründen ändern musstest, biete ein zusätzliches APK an, das mit demselben Commit erstellt aber mit dem alten Schlüssel signiert wird, was einen definitiven Beweis darstellen würde: die Methoden zur Verifizierung der reproduzierbaren Builds könnten dann angewandt werden, um die beiden APKs zu vergleichen – die sich dann wiederum nur in der Signatur unterscheiden sollten.
  • niemand wurde jemals „weise geboren“ – wir sind unser ganzes Leben lang Lernende („wer aufhört Schüler zu sein, war niemals Schüler“). Also ja, es ist schlecht und du bist möglicherweise „beschämt“. Aber es zeigt Charakter und schafft Vertrauen, zu seinen Fehlern zu stehen, sie zuzugeben. Auf diese Weise wissen die Leute, dass du sie nicht „betrügen“ wirst und sie dir glauben können.