Architektura podpisywania na urządzeniu

Od Androida 12 moduł środowiska wykonawczego Androida (ART) jest modułem Mainline. Aktualizacja modułu może wymagać ponownego skompilowania artefaktów kompilacji AOT (ahead-of-time) plików JAR bootclasspath i serwera systemowego. Ponieważ te artefakty są wrażliwe na bezpieczeństwo, Android 12 wykorzystuje funkcję podpisywania na urządzeniu, aby zapobiec ich manipulowaniu. Na tej stronie opisujemy architekturę podpisywania na urządzeniu i jej interakcje z innymi funkcjami bezpieczeństwa Androida.

Projekt na wysokim poziomie

Podpisywanie na urządzeniu ma 2 główne komponenty:

  • odrefresh jest częścią modułu ART Mainline. Odpowiada za generowanie artefaktów środowiska wykonawczego. Sprawdza, czy istniejące artefakty są zgodne z zainstalowaną wersją modułu ART, plikami JAR bootclasspath i plikami JAR serwera systemowego, aby określić, czy są aktualne, czy wymagają ponownego wygenerowania. Jeśli wymagają ponownego wygenerowania, odrefresh generuje je i zapisuje.

  • odsign to plik binarny, który jest częścią platformy Android. Jest uruchamiany podczas wczesnego uruchamiania, zaraz po zamontowaniu partycji /data. Jego głównym zadaniem jest wywoływanie odrefresh, aby sprawdzić, czy trzeba wygenerować lub zaktualizować jakieś artefakty. W przypadku wszystkich nowych lub zaktualizowanych artefaktów wygenerowanych przez odrefresh funkcja odsign oblicza funkcję skrótu. Wynik takiego obliczenia skrótu nazywa się skrótem pliku. W przypadku wszystkich artefaktów, które już istnieją, odsign sprawdza, czy skróty istniejących artefaktów są zgodne ze skrótami, które odsign obliczył wcześniej. Dzięki temu artefakty nie zostały naruszone.

W przypadku błędów, np. gdy skrót pliku nie pasuje, odrefresh i odsign odrzucają wszystkie istniejące artefakty w /data i próbują je ponownie wygenerować. Jeśli to się nie uda, system przechodzi w tryb JIT.

odrefresh i odsign są chronione przez dm-verity i stanowią część łańcucha weryfikacji podczas uruchamiania Androida.

Obliczanie skrótów plików za pomocą fs-verity

fs-verity to funkcja jądra systemu Linux, która przeprowadza weryfikację danych pliku na podstawie drzewa Merkle. Włączenie fs-verity w pliku powoduje, że system plików tworzy drzewo Merkle na podstawie danych pliku za pomocą skrótów SHA-256, zapisuje je w ukrytej lokalizacji obok pliku i oznacza plik jako tylko do odczytu. fs-verity automatycznie weryfikuje dane pliku na podstawie drzewa Merkle na żądanie podczas odczytu. fs-verity udostępnia skrót główny drzewa Merkle jako wartość o nazwie skrót pliku fs-verity, a fs-verity zapewnia, że wszystkie dane odczytane z pliku są zgodne z tym skrótem.

odsign używa fs-verity, aby poprawić wydajność uruchamiania przez optymalizację kryptograficznego uwierzytelniania artefaktów skompilowanych na urządzeniu podczas uruchamiania. Gdy artefakt jest generowany, odsign włącza w nim fs-verity. Gdy odsign weryfikuje artefakt, weryfikuje skrót pliku fs-verity zamiast pełnego skrótu pliku. Eliminuje to konieczność odczytywania i tworzenia skrótu pełnych danych artefaktu podczas uruchamiania. Dane artefaktu są zamiast tego tworzone na żądanie przez fs-verity w miarę ich używania, blok po bloku.

Na urządzeniach, których jądro nie obsługuje fs-verity, odsign wraca do obliczania skrótów plików w przestrzeni użytkownika. odsign używa tego samego algorytmu skrótu opartego na drzewie Merkle co fs-verity, więc skróty są w obu przypadkach takie same. fs-verity jest wymagane na wszystkich urządzeniach, które zostały wprowadzone na rynek z Androidem 11 lub nowszym.

Przechowywanie skrótów plików

odsign przechowuje skróty plików artefaktów w osobnym pliku o nazwie odsign.info. Aby mieć pewność, że plik odsign.info nie zostanie naruszony, jest on podpisywany kluczem podpisywania, który ma ważne właściwości zabezpieczeń.odsign.info W szczególności klucz można wygenerować i używać tylko podczas wczesnego uruchamiania, gdy działa tylko zaufany kod. Więcej informacji znajdziesz w sekcji Zaufane klucze podpisywania.

Weryfikacja skrótów plików

Podczas każdego uruchamiania, jeśli odrefresh stwierdzi, że istniejące artefakty są aktualne, odsign sprawdza, czy pliki nie zostały naruszone od czasu ich wygenerowania. odsign robi to, weryfikując skróty plików. Najpierw weryfikuje podpis pliku odsign.info. Jeśli podpis jest prawidłowy, to odsign sprawdza, czy skrót każdego pliku jest zgodny z odpowiednim skrótem w odsign.info.

Zaufane klucze podpisywania

Android 12 wprowadza nową funkcję Keystore o nazwie klucze etapu uruchamiania, która rozwiązuje te problemy z bezpieczeństwem:

  • Co uniemożliwia atakującemu użycie naszego klucza podpisywania do podpisania własnej wersji pliku odsign.info?
  • Co uniemożliwia atakującemu wygenerowanie własnego klucza podpisywania i użycie go do podpisania własnej wersji pliku odsign.info?

Klucze etapu uruchamiania dzielą cykl uruchamiania Androida na poziomy i kryptograficznie wiążą tworzenie i używanie klucza z określonym poziomem. odsign tworzy klucz podpisywania na wczesnym etapie, gdy działa tylko zaufany kod, chroniony przez dm-verity.

Poziomy etapu uruchamiania są numerowane od 0 do magicznej liczby 1000000000. Podczas procesu uruchamiania Androida możesz zwiększyć poziom uruchamiania, ustawiając właściwość systemu w init.rc. Na przykład ten kod ustawia poziom uruchamiania na 10:

setprop keystore.boot_level 10

Klienci Keystore mogą tworzyć klucze powiązane z określonym poziomem uruchamiania. Jeśli na przykład utworzysz klucz dla poziomu uruchamiania 10, będzie można go używać tylko wtedy, gdy urządzenie jest na poziomie uruchamiania 10.

odsign używa poziomu uruchamiania 30, a utworzony przez niego klucz podpisywania jest powiązany z tym poziomem. Zanim użyje klucza do podpisania artefaktów, odsign sprawdza, czy klucz jest powiązany z poziomem uruchamiania 30.

Zapobiega to 2 atakom opisanym wcześniej w tej sekcji:

  • Atakujący nie mogą użyć wygenerowanego klucza, ponieważ zanim będą mogli uruchomić złośliwy kod, poziom uruchamiania wzrośnie powyżej 30, a Keystore odmówi operacji, które używają tego klucza.
  • Atakujący nie mogą utworzyć nowego klucza, ponieważ zanim będą mogli uruchomić złośliwy kod, poziom uruchamiania wzrośnie powyżej 30, a Keystore odmówi utworzenia nowego klucza z tym poziomem uruchamiania. Jeśli atakujący utworzy nowy klucz, który nie jest powiązany z poziomem uruchamiania 30, odsign go odrzuci.

Keystore zapewnia prawidłowe egzekwowanie poziomu uruchamiania. Więcej informacji o tym, jak to zrobić w przypadku różnych wersji KeyMint (wcześniej Keymaster), znajdziesz w sekcjach poniżej.

Implementacja Keymaster 4.0

Różne wersje Keymaster inaczej obsługują implementację kluczy etapu uruchamiania. Na urządzeniach z Keymaster 4.0 TEE/StrongBox Keymaster obsługuje implementację w ten sposób:

  1. Podczas pierwszego uruchamiania Keystore tworzy klucz symetryczny K0 z tagiem MAX_USES_PER_BOOT ustawionym na 1. Oznacza to, że klucz można użyć tylko raz podczas uruchamiania.
  2. Podczas uruchamiania, jeśli poziom uruchamiania zostanie zwiększony, można wygenerować nowy klucz dla tego poziomu uruchamiania z K0 za pomocą funkcji HKDF: Ki+i=HKDF(Ki, "some_fixed_string"). Jeśli na przykład przejdziesz z poziomu uruchamiania 0 na poziom uruchamiania 10, funkcja HKDF zostanie wywołana 10 razy, aby wyprowadzić K10 z K0.
  3. Gdy poziom uruchamiania się zmieni, klucz dla poprzedniego poziomu uruchamiania zostanie usunięty z pamięci, a klucze powiązane z poprzednimi poziomami uruchamiania nie będą już dostępne.

    Klucz K0 to klucz MAX_USES_PER_BOOT=1. Oznacza to, że nie można go użyć później podczas uruchamiania, ponieważ zawsze następuje co najmniej 1 przejście poziomu uruchamiania (do końcowego poziomu uruchamiania).

Gdy klient Keystore, taki jak odsign, poprosi o utworzenie klucza na poziomie uruchamiania i, jego blob zostanie zaszyfrowany kluczem Ki. Ponieważ Ki nie jest dostępny po poziomie uruchamiania i, nie można go utworzyć ani odszyfrować na późniejszych etapach uruchamiania systemu.

Implementacja Keymaster 4.1 i KeyMint 1.0

Implementacje Keymaster 4.1 i KeyMint 1.0 są w dużej mierze takie same jak implementacja Keymaster 4.0. Główna różnica polega na tym, że K0 nie jest kluczem MAX_USES_PER_BOOT, ale kluczem EARLY_BOOT_ONLY, który został wprowadzony w Keymaster 4.1. Klucza EARLY_BOOT_ONLY można używać tylko we wczesnych fazach uruchamiania, gdy nie działa żaden niezaufany kod. Zapewnia to dodatkowy poziom ochrony: w implementacji Keymaster 4.0 atakujący, który naruszy system plików i SELinux, może zmodyfikować bazę danych Keystore, aby utworzyć własny klucz MAX_USES_PER_BOOT=1 do podpisywania artefaktów. Taki atak jest niemożliwy w przypadku implementacji Keymaster 4.1 i KeyMint 1.0, ponieważ klucze EARLY_BOOT_ONLY można tworzyć tylko podczas wczesnego uruchamiania.

Publiczny komponent zaufanych kluczy podpisywania

odsign pobiera publiczny komponent klucza podpisywania z Keystore. Keystore nie pobiera jednak tego klucza publicznego z TEE/SE, który zawiera odpowiedni klucz prywatny. Zamiast tego pobiera klucz publiczny z własnej bazy danych na dysku. Oznacza to, że atakujący, który naruszy system plików, może zmodyfikować bazę danych Keystore, aby zawierała klucz publiczny, który jest częścią pary kluczy publicznych i prywatnych pod jego kontrolą.

Aby zapobiec temu atakowi, odsign tworzy dodatkowy klucz HMAC z tym samym poziomem uruchamiania co klucz podpisywania. Następnie podczas tworzenia klucza podpisywania odsign używa tego klucza HMAC do utworzenia podpisu klucza publicznego i zapisuje go na dysku. Podczas kolejnych uruchamiań, gdy pobiera klucz publiczny klucza podpisywania, używa klucza HMAC, aby sprawdzić, czy podpis na dysku jest zgodny z podpisem pobranego klucza publicznego. Jeśli są zgodne, klucz publiczny jest zaufany, ponieważ klucza HMAC można używać tylko na wczesnych poziomach uruchamiania, a więc nie mógł go utworzyć atakujący.