Vertrauen durch Verifizierung: Commit-Signierung in Git

Git bietet Wege, Commits über Signaturen eindeutig Verantwortlichen zuzuordnen. Auf diese Weise die Security zu verbessern, muss nicht aufwendig sein.

In Pocket speichern vorlesen Druckansicht 29 Kommentare lesen
Elektronische Signatur

(Bild: Thapana_Studio/Shutterstock)

Lesezeit: 10 Min.
Von
  • Janosch Deurer
Inhaltsverzeichnis

Man stelle sich vor, Stefan ist ein aktives und geschätztes Mitglied der Open Source Community und trägt regelmäßig Code zu einem Projekt bei. Aber jetzt findet Petra einen Commit unter seinem Namen, der eine Backdoor enthält. Stefans Ruf ist ruiniert, obwohl er den Commit nicht erstellt hat.

Das Beispiel ist fiktiv, allerdings hat mit der XZ-Hintertür im April ein großer Supply-Chain-Angriff für Wirbel gesorgt. Dabei hat zwar vermutlich niemand Commits unter dem Namen existierender Developer erstellt, aber der wirkliche Autor der Codeeinreichung ist bis heute unbekannt.

Sowohl in Open-Source- als auch geschlossenen Projekten ist das erste Glied in der Supply Chain der Commit. Wenn sich dessen Herkunft nicht nachvollziehen lässt, können Entwicklerinnen und Entwickler Schadcode unter unbekanntem oder falschem Namen ins Projekt einschleusen.

Ein Git-Server wie bei GitLab oder GitHub stellt eine Authentifizierung zur Verfügung, sodass nur berechtigte User pushen können. Warum sollte man Commits zusätzlich signieren? Weil Git erlaubt, sie unter beliebigem Namen zu erstellen. Dazu muss man lediglich den Autor und die E-Mail-Adresse in der lokalen Git-Konfiguration anpassen. Abhilfe schafft die Commit-Signatur. Dank ihr ist kryptografisch sicher nachvollziehbar, wer einen Commit erstellt hat.

Da Git dezentral funktioniert, sind lokal beliebige Operationen möglich. Beim Push auf den Server könnte dieser theoretisch verhindern, dass Commits mit einem anderen Autor gepusht werden. Warum passiert das nicht? Zunächst unterscheidet Git zwischen Autor und Committer. Der Autor hat dabei die Codeinhalte erstellt, während der Committer die Änderung eingereicht hat. Meist sind beide identisch. Wenn allerdings jemand Änderungen über Rebase, Cherry Picking oder Patches übernimmt, ersetzt Git bestehende Commits durch neue. Dabei behält das System den ursprünglichen Autor bei und trägt den aktuellen User als Committer ein. GitHub stellt diese Commits wie in Abbildung 1 gezeigt dar.

GitHub zeigt sowohl den Autor als auch den Committer der Änderung an (Abb. 1).

(Bild: Screenshot (Janosch Deurer))

Damit lässt sich unterscheiden, wer den Code geschrieben und wer den Commit erstellt hat. Das ist wichtig, da der Autor nicht allein für den Code verantwortlich ist. Auch beim Rebase oder ähnlichen Operationen können sich Fehler einschleichen.

Der Committer lässt sich allerdings nicht nur in sinnvollen Fällen wie oben beschrieben ändern, sondern auch fälschen: Den Commit in Abbildung 1 habe ich im Namen meines Kollegen ohne sein Zutun erstellt. Dass der Git-Server solche Commits annimmt, ist beispielsweise für den Push eines bestehenden Repository auf einen neuen Server erforderlich. Der Push enthält die vollständige Historie mit den Committern.

Git kennt mit dem Commit-Hash eine Funktion, um die Integrität der Codebasis und der gesamten Commit-Historie kryptografisch sicherzustellen. Das garantiert, dass bei zwei Commits mit demselben Commit-Hash die Historien ebenfalls identisch sind. Signierung stellt zusätzlich sicher, dass die enthaltenen Commits von den angegebenen Autoren stammen.

Bei der Commit-Signierung dient ein privater Schlüssel dazu, den gesamten Commit inklusive Tree, Parent, Autor, Committer und Commit-Nachricht zu signieren. Die digitale Unterschrift betrifft alle Daten, die der Commit-Hash abbildet. Damit ist bei jedem Push sichergestellt, dass der Commit von demjenigen stammt, der ihn signiert hat.

Zum Signieren von Git-Commits stehen prinzipiell drei Varianten zur Verfügung: über einen GPG-Key, über einen SSH-Schlüssel oder per X.509 mit S/MIME. Da SSH-Schlüssel zur Authentifizierung gegenüber dem Git-Server üblich sind, ist es meist am einfachsten, einen bestehenden SSH-Key zum Signieren zu verwenden. GPG-Schlüssel sind etwas aufwendiger zu verwalten, haben dafür aber ein optionales Ablaufdatum und lassen sich anders als SSH-Keys zudem zurückziehen, wenn sie kompromittiert wurden.

Beim Einsatz im Open-Source-Bereich steht außerdem eine Reihe von GPG-Key-Servern bereit, die ein sogenanntes Web of Trust bilden, um die Identität des Schlüsselbesitzers auch ohne physischen Kontakt sicherzustellen. Für große Organisationen kann es sich lohnen, auf S/MIME in Verbindung mit einem X.509-Zertifikat einer Public-Key-Infrastruktur (PKI) zu setzen.

Die folgenden Befehle erzeugen zunächst einen neuen SSH-Key, stellen dann die Commit-Signierung auf SSH um und geben schließlich den SSH-Schlüssel zum Signieren an:

ssh-keygen -t ed25519 -a 100
git config --global gpg.format ssh
git config --global user.signingkey /PFAD/ZUM/SSH/PUBLIC/KEY

Die folgenden drei Befehle erzeugen einen GPG-Key und geben anschließend die Liste der Schlüssel aus. Schließlich nutzt der dritte Befehl die Schlüssel-ID, um den Key in Git als Standardschlüssel zu konfigurieren:

gpg --full-generate-key
gpg --list-secret-keys --keyid-format LONG <e-Mail>
git config --global user.signingkey <Schlüssel-ID>

Der Parameter -S dient dazu, signierte Commits zu erstellen:

git commit -S

Alternativ lässt sich Git so konfigurieren, dass es alle Commits automatisch signiert:

git config --global commit.gpgsign true