On-Device-Signaturarchitektur

Ab Android 12 ist das Android Runtime-Modul (ART) ein Mainline-Modul. Beim Aktualisieren des Moduls müssen möglicherweise die AOT-Kompilierungsartefakte (Ahead-of-Time) von Bootclasspath-JARs und des Systemservers neu erstellt werden. Da diese Artefakte sicherheitsrelevant sind, verwendet Android 12 eine Funktion namens On-Device-Signierung, um Manipulationen zu verhindern. Auf dieser Seite wird die Architektur für die Signierung auf dem Gerät und die Interaktion mit anderen Android-Sicherheitsfunktionen beschrieben.

Konzeption

Die On-Device-Signierung hat zwei Kernkomponenten:

  • odrefresh ist Teil des ART Mainline-Moduls. Sie ist für das Generieren der Laufzeitartefakte verantwortlich. Dabei werden vorhandene Artefakte mit der installierten Version des ART-Moduls, den Bootclasspath-Jars und den Systemserver-Jars verglichen, um festzustellen, ob sie auf dem neuesten Stand sind oder neu generiert werden müssen. Wenn sie neu generiert werden müssen, werden sie von odrefresh generiert und gespeichert.

  • odsign ist eine Binärdatei, die Teil der Android-Plattform ist. Es wird während des frühen Starts ausgeführt, direkt nachdem die Partition /data gemountet wurde. Die Hauptaufgabe besteht darin, odrefresh aufzurufen, um zu prüfen, ob Artefakte generiert oder aktualisiert werden müssen. Für alle neuen oder aktualisierten Artefakte, die von odrefresh generiert werden, berechnet odsign eine Hash-Funktion. Das Ergebnis einer solchen Hash-Berechnung wird als Dateidigest bezeichnet. Bei allen Artefakten, die bereits vorhanden sind, wird mit odsign geprüft, ob die Digests der vorhandenen Artefakte mit den Digests übereinstimmen, die odsign zuvor berechnet hat. So wird sichergestellt, dass die Artefakte nicht manipuliert wurden.

Bei Fehlern, z. B. wenn der Digest für eine Datei nicht übereinstimmt, werden mit odrefresh und odsign alle vorhandenen Artefakte auf /data verworfen und es wird versucht, sie neu zu generieren. Wenn das nicht funktioniert, wechselt das System in den JIT-Modus.

odrefresh und odsign sind durch dm-verity geschützt und Teil der Verified Boot-Kette von Android.

Berechnung von Dateidigest mit fs-verity

fs-verity ist eine Funktion des Linux-Kernels, die auf Merkle-Bäumen basierende Überprüfung von Dateidaten durchführt. Wenn Sie „fs-verity“ für eine Datei aktivieren, erstellt das Dateisystem einen Merkle-Baum für die Daten der Datei mit SHA-256-Hashes, speichert ihn an einem verborgenen Ort neben der Datei und markiert die Datei als schreibgeschützt. „fs-verity“ überprüft die Daten der Datei automatisch bei Bedarf anhand des Merkle-Baums, wenn sie gelesen werden. „fs-verity“ macht den Stamm-Hash des Merkle-Baums als Wert namens fs-verity-Dateidigest verfügbar und sorgt dafür, dass alle aus der Datei gelesenen Daten mit diesem Dateidigest übereinstimmen.

odsign verwendet fs-verity, um die Bootleistung zu verbessern, indem die kryptografische Authentifizierung von auf dem Gerät kompilierten Artefakten beim Booten optimiert wird. Wenn ein Artefakt generiert wird, aktiviert odsign fs-verity dafür. Wenn odsign ein Artefakt überprüft, wird der fs-verity-Dateidigest anstelle des vollständigen Dateihashs überprüft. Dadurch entfällt die Notwendigkeit, die vollständigen Daten des Artefakts beim Booten zu lesen und zu hashen. Stattdessen werden Artefaktdaten bei Bedarf von fs-verity gehasht, wenn sie verwendet werden, und zwar blockweise.

Auf Geräten, deren Kernel „fs-verity“ nicht unterstützt, greift odsign auf die Berechnung von Dateidigest im Userspace zurück. odsign verwendet denselben auf Merkle-Bäumen basierenden Hash-Algorithmus wie fs-verity. Die Digests sind also in beiden Fällen gleich. fs-verity ist auf allen Geräten erforderlich, die mit Android 11 und höher auf den Markt gekommen sind.

Speicherung der Datei-Digests

In odsign werden die Datei-Digests der Artefakte in einer separaten Datei namens odsign.info gespeichert. Damit odsign.info nicht manipuliert wird, wird odsign.info mit einem Signaturschlüssel signiert, der wichtige Sicherheitseigenschaften hat. Der Schlüssel kann insbesondere nur während des frühen Bootvorgangs generiert und verwendet werden, wenn nur vertrauenswürdiger Code ausgeführt wird. Weitere Informationen finden Sie unter Vertrauenswürdige Signaturschlüssel.

Prüfung von Datei-Digests

Bei jedem Start prüft odrefresh, ob die vorhandenen Artefakte aktuell sind. odsign sorgt dafür, dass die Dateien seit ihrer Generierung nicht manipuliert wurden. odsign prüft dazu die Datei-Digests. Zuerst wird die Signatur von odsign.info überprüft. Wenn die Signatur gültig ist, prüft odsign, ob der Digest jeder Datei mit dem entsprechenden Digest in odsign.info übereinstimmt.

Vertrauenswürdige Signaturschlüssel

Mit Android 12 wird eine neue Keystore-Funktion namens „Boot Stage Keys“ eingeführt, die die folgenden Sicherheitsbedenken ausräumt:

  • Was hindert einen Angreifer daran, unseren Signaturschlüssel zum Signieren seiner eigenen Version von odsign.info zu verwenden?
  • Was hindert einen Angreifer daran, einen eigenen Signaturschlüssel zu generieren und damit eine eigene Version von odsign.info zu signieren?

Schlüssel für die Boot-Phase unterteilen den Boot-Zyklus von Android in Ebenen und verknüpfen die Erstellung und Verwendung eines Schlüssels kryptografisch mit einer bestimmten Ebene. odsign erstellt seinen Signierschlüssel auf einer frühen Ebene, wenn nur vertrauenswürdiger Code ausgeführt wird, geschützt durch dm-verity.

Die Boot-Phasen sind von 0 bis zur magischen Zahl 1.000.000.000 nummeriert. Während des Android-Bootvorgangs können Sie die Boot-Ebene erhöhen, indem Sie eine Systemeigenschaft über init.rc festlegen. Mit dem folgenden Code wird die Boot-Ebene beispielsweise auf 10 festgelegt:

setprop keystore.boot_level 10

Keystore-Clients können Schlüssel erstellen, die an ein bestimmtes Boot-Level gebunden sind. Wenn Sie beispielsweise einen Schlüssel für die Boot-Ebene 10 erstellen, kann dieser Schlüssel nur verwendet werden, wenn sich das Gerät auf der Boot-Ebene 10 befindet.

odsign verwendet die Boot-Ebene 30 und der von ihr erstellte Signaturschlüssel ist an diese Boot-Ebene gebunden. Bevor ein Schlüssel zum Signieren von Artefakten verwendet wird, prüft odsign, ob der Schlüssel mit der Boot-Ebene 30 verknüpft ist.

Dadurch werden die beiden Angriffe verhindert, die weiter oben in diesem Abschnitt beschrieben wurden:

  • Angreifer können den generierten Schlüssel nicht verwenden, da die Boot-Ebene bis zu dem Zeitpunkt, zu dem ein Angreifer die Möglichkeit hat, schädlichen Code auszuführen, auf über 30 gestiegen ist und Keystore Vorgänge, die den Schlüssel verwenden, ablehnt.
  • Angreifer können keinen neuen Schlüssel erstellen, da die Boot-Ebene bis zu dem Zeitpunkt, zu dem ein Angreifer die Möglichkeit hat, schädlichen Code auszuführen, über 30 liegt und Keystore die Erstellung eines neuen Schlüssels mit dieser Boot-Ebene verweigert. Wenn ein Angreifer einen neuen Schlüssel erstellt, der nicht an die Boot-Ebene 30 gebunden ist, wird er von odsign abgelehnt.

Der Keystore sorgt dafür, dass die Boot-Ebene richtig erzwungen wird. In den folgenden Abschnitten wird genauer beschrieben, wie dies für verschiedene KeyMint-Versionen (früher Keymaster) erfolgt.

Keymaster 4.0-Implementierung

Verschiedene Versionen von Keymaster verarbeiten die Implementierung von Schlüsseln für die Bootphase unterschiedlich. Auf Geräten mit einem Keymaster 4.0-TEE/StrongBox wird die Implementierung von Keymaster so gehandhabt:

  1. Beim ersten Start erstellt Keystore einen symmetrischen Schlüssel K0, dessen MAX_USES_PER_BOOT-Tag auf 1 gesetzt ist. Das bedeutet, dass der Schlüssel nur einmal pro Startvorgang verwendet werden kann.
  2. Wenn die Boot-Ebene während des Bootvorgangs erhöht wird, kann mit der HKDF-Funktion Ki+i=HKDF(Ki, "some_fixed_string") ein neuer Schlüssel für diese Boot-Ebene aus K0 generiert werden. Wenn Sie beispielsweise von der Boot-Ebene 0 zur Boot-Ebene 10 wechseln, wird HKDF zehnmal aufgerufen, um K10 aus K0 abzuleiten.
  3. Wenn sich die Boot-Ebene ändert, wird der Schlüssel für die vorherige Boot-Ebene aus dem Arbeitsspeicher gelöscht und die Schlüssel, die mit vorherigen Boot-Ebenen verknüpft sind, sind nicht mehr verfügbar.

    Schlüssel K0 ist ein MAX_USES_PER_BOOT=1-Schlüssel. Das bedeutet, dass es auch unmöglich ist, diesen Schlüssel später beim Booten zu verwenden, da immer mindestens ein Übergang zur Boot-Ebene (zur endgültigen Boot-Ebene) erfolgt.

Wenn ein Keystore-Client wie odsign anfordert, dass ein Schlüssel auf der Boot-Ebene i erstellt wird, wird sein Blob mit dem Schlüssel Ki verschlüsselt. Da Ki nach der Boot-Ebene i nicht mehr verfügbar ist, kann dieser Schlüssel in späteren Boot-Phasen nicht erstellt oder entschlüsselt werden.

Implementierung von Keymaster 4.1 und KeyMint 1.0

Die Implementierungen von Keymaster 4.1 und KeyMint 1.0 sind weitgehend identisch mit der Implementierung von Keymaster 4.0. Der Hauptunterschied besteht darin, dass K0 kein MAX_USES_PER_BOOT-Schlüssel, sondern ein EARLY_BOOT_ONLY-Schlüssel ist, der in Keymaster 4.1 eingeführt wurde. Ein EARLY_BOOT_ONLY-Schlüssel kann nur in den frühen Phasen des Bootvorgangs verwendet werden, wenn kein nicht vertrauenswürdiger Code ausgeführt wird. Dies bietet eine zusätzliche Schutzebene: In der Keymaster 4.0-Implementierung kann ein Angreifer, der das Dateisystem und SELinux manipuliert, die Keystore-Datenbank ändern, um einen eigenen MAX_USES_PER_BOOT=1-Schlüssel zum Signieren von Artefakten zu erstellen. Ein solcher Angriff ist mit den Implementierungen von Keymaster 4.1 und KeyMint 1.0 nicht möglich, da EARLY_BOOT_ONLY-Schlüssel nur während des frühen Bootvorgangs erstellt werden können.

Öffentliche Komponente der vertrauenswürdigen Signaturschlüssel

odsign ruft die Komponente des öffentlichen Schlüssels des Signierschlüssels aus dem Keystore ab. Keystore ruft diesen öffentlichen Schlüssel jedoch nicht aus dem TEE/SE ab, in dem sich der entsprechende private Schlüssel befindet. Stattdessen wird der öffentliche Schlüssel aus der eigenen On-Disk-Datenbank abgerufen. Das bedeutet, dass ein Angreifer, der das Dateisystem manipuliert, die Keystore-Datenbank so ändern könnte, dass sie einen öffentlichen Schlüssel enthält, der Teil eines öffentlichen/privaten Schlüsselpaars ist, das er kontrolliert.

Um diesen Angriff zu verhindern, erstellt odsign einen zusätzlichen HMAC-Schlüssel mit derselben Boot-Ebene wie der Signierschlüssel. Beim Erstellen des Signierschlüssels verwendet odsign diesen HMAC-Schlüssel, um eine Signatur des öffentlichen Schlüssels zu erstellen und auf der Festplatte zu speichern. Bei nachfolgenden Starts wird der HMAC-Schlüssel verwendet, um zu prüfen, ob die Signatur auf der Festplatte mit der Signatur des abgerufenen öffentlichen Schlüssels übereinstimmt, wenn der öffentliche Schlüssel des Signaturschlüssels abgerufen wird. Wenn sie übereinstimmen, ist der öffentliche Schlüssel vertrauenswürdig, da der HMAC-Schlüssel nur in frühen Boot-Ebenen verwendet werden kann und daher nicht von einem Angreifer erstellt worden sein kann.