Pełna weryfikacja przejrzystości plików APK w usługach systemowych Google

Na tej stronie opisujemy różne metody, które pozwalają sprawdzić, czy pakiet APK zainstalowany na urządzeniu z Androidem odpowiada deklaracji złożonej w modelu zgłaszającego. Obejmuje to pobranie z urządzenia pliku APK, sprawdzenie integralności jego kodu i przeprowadzenie dowodu włączenia logu na wyodrębnionym artefakcie.

Proces weryfikacji

Dziennik przejrzystości jest implementowany za pomocą drzewa Merklego składającego się z wartości skrótu. Węzeł liścia zawiera dane, a węzeł nadrzędny zawiera skrót jego węzłów podrzędnych.

W przypadku drzewa Merkle’a wykonuje się 2 obliczenia, aby zweryfikować właściwość dzienników przejrzystości, która polega na wykrywaniu manipulacji: dowód włączenia i dowód spójności. Pierwszy z nich potwierdza, że dziennik zawiera wpis odpowiadający konkretnej wersji pliku APK. Wpis w logu zawiera hash, czyli skrót SHA256 tokena podpisu kodu w formie tokena internetowego JSON (JWT), który można uzyskać z odpowiednich plików APK. To ostatnie potwierdza, że gdy do drzewa dodawane są nowe wpisy, nowy punkt kontrolny jest (kryptograficznie) zgodny z poprzednią wersją drzewa.

Aby zweryfikować objęty ochroną plik APK, przeprowadź test dowodu włączenia na podstawie zaobserwowanego punktu kontrolnego. Planujemy zintegrować ten dziennik z publiczną siecią świadków za pomocą standardowego protokołu świadków. Zapewni to punkt kontrolny, który gwarantuje spójność dziennika.

Jeśli chcesz się upewnić, że pakiet APK na Twoim urządzeniu jest zgodny z deklaracją w modelu zgłaszającego roszczenie, zapoznaj się z poniższym opisem.

Dowód uwzględnienia

Użytkownik Androida może sprawdzić, czy objęty ochroną plik APK na jego urządzeniu znajduje się w logu. W tym celu musi najpierw wyodrębnić plik APK i odpowiednie metadane, a następnie porównać ponownie obliczony hash główny z hashem głównym zawartym w opublikowanym punkcie kontrolnym. Jeśli są zgodne, użytkownik Androida może mieć pewność, że jest chroniony w sposób opisany w modelu zagrożeń.

Jak sprawdzić, czy pakiet APK jest uwzględniony w logu

Jak wspomnieliśmy wcześniej, listę obecnie objętych ochroną plików APK znajdziesz na stronie Przegląd.

Wymagania wstępne dotyczące weryfikacji

Zanim przejdziesz do weryfikacji, czy plik APK wyodrębniony z urządzenia jest zgodny z naszymi deklaracjami, musisz zainstalować na komputerze podłączonym do sieci te narzędzia:

Android Debug Bridge (ADB)

ADB to narzędzie do komunikacji z urządzeniem z Androidem, dostępne na stronie Android SDK Platform Tools.

AAPT2

AAPT2 (Android Asset Packaging Tool) to narzędzie do kompilacji, które służy do kompilowania i pakowania zasobów aplikacji na Androida. Jest on dostępny jako samodzielne narzędzie w pakiecie Android SDK Build Tools w wersji 26.0.2 i nowszej.

bundletool

bundletool to narzędzie służące do tworzenia pakietu aplikacji na Androida (AAB). Można go też używać do konwertowania pakietu AAB na pliki APK, które można zainstalować na urządzeniach. Możesz go pobrać z GitHub.

Weryfikator dowodu włączenia

Jest to moduł Go, który opublikowaliśmy w repozytorium Git w ramach Projektu Android Open Source (AOSP) o nazwie avb. Umożliwia wysyłanie zapytań do dziennika przejrzystości plików APK usług systemowych Google i sprawdzanie, czy pakiet jest w nim uwzględniony. Przykład użycia tej funkcji znajdziesz w dalszej sekcji.

Aby pobrać to narzędzie, musisz najpierw sklonować repozytorium avb.

computer:~$ git clone https://android.googlesource.com/platform/external/avb

Kod źródłowy weryfikatora znajdziesz w tools/transparency/verifyrepozytorium avb.

Tworzenie ładunku na potrzeby weryfikacji

Aby sprawdzić, czy wyodrębniony z urządzenia plik APK jest zgodny z naszymi deklaracjami, musisz utworzyć ładunek logu na podstawie informacji pochodzących z tego pliku.

Zanim zaczniesz, upewnij się, że na Twoim urządzeniu można używać adb. W tym celu włącz debugowanie ADB na urządzeniu.

Następnie znajdź miejsce, w którym plik APK jest zainstalowany na urządzeniu. Na potrzeby tego przewodnika jako przykładu użyjemy pliku APK systemowego weryfikatora kluczy Androida (com.google.android.contactkeys).

computer:~$ adb shell pm list packages -f | grep contactkeys
package:/data/app/~~i5WYSO4PuAAv798-eHdM7A==/com.google.android.contactkeys-PQCKjnn7xDqjeVhcUDibBA==/base.apk=com.google.android.contactkeys

Jeśli na urządzeniu jest zainstalowany plik APK weryfikatora kluczy systemowych Androida, powyższe polecenie zwróci ścieżkę wskazującą miejsce, w którym jest on zainstalowany na urządzeniu. W przeciwnym razie nie zobaczysz żadnych danych wyjściowych.

Następnie pobierz plik APK z urządzenia z Androidem na komputer, na którym pracujesz, za pomocą tego polecenia (pamiętaj, że rzeczywista lokalizacja i nazwa pliku APK na urządzeniu mogą się różnić):

computer:~$ mkdir -p /tmp/testdir && cd /tmp/testdir
computer:/tmp/testdir$ adb pull /data/app/~~i5WYSO4PuAAv798-eHdM7A==/com.google.android.contactkeys-PQCKjnn7xDqjeVhcUDibBA==/base.apk ./contactkeys_candidate.apk

Aby uzyskać i zweryfikować nazwę pakietu APK, który właśnie został pobrany, musisz najpierw rozpakować plik APK, ponieważ jest to specjalny rodzaj pliku ZIP.

computer:/tmp/testdir$ mkdir extracted && unzip contactkeys_candidate.apk -d extracted/

W tym kroku rozpakowywane są wszystkie pliki, z których składa się plik APK. Nazwę pakietu i wersję można znaleźć w manifeście pliku APK, który zwykle znajduje się w pliku o nazwie AndroidManifest.xml.

Uzyskany plik manifestu jest jednak w formie binarnej, która nie jest czytelna dla człowieka. Aby przekonwertować binarny plik XML na format czytelny dla człowieka, używamy narzędzia aapt2 zainstalowanego wcześniej (zgodnie z wymaganiami w sekcji wymagania wstępne).

computer:/tmp/testdir$ aapt2 dump badging ./contactkeys_candidate.apk
package: name='com.google.android.contactkeys' versionCode='7805' versionName='1.219.791156583' platformBuildVersionName='Baklava' platformBuildVersionCode='36' compileSdkVersion='36' compileSdkVersionCodename='Baklava'

Z powyższych danych wyjściowych możemy mieć pewność, że nazwa pakietu tego pliku APK to com.google.android.contactkeys, a numer wersji (versionCode) to 1413.

Teraz wyszukamy w pliku APK podpis przejrzystości kodu. Powinien to być plik o nazwie code_transparency_signed.jwt znajdujący się w folderze META-INF wśród innych wyodrębnionych z pliku APK plików.

computer:/tmp/testdir$ sha256sum extracted/META-INF/code_transparency_signed.jwt
1779a2aee029112c2c9bfc9390b9678f3e5f4595b39705e8528dd522e8042f11  code_transparency_signed.jwt

Dzięki temu ciągowi znaków mamy wszystkie informacje potrzebne do utworzenia ładunku logu zgodnie z formatem opisanym w sekcji Zawartość logu. W tym przykładzie odpowiedni ładunek logu powinien wyglądać tak:

1779a2aee029112c2c9bfc9390b9678f3e5f4595b39705e8528dd522e8042f11
SHA256(Signed Code Transparency JWT)
com.google.android.contactkeys
1143

Zwróć też uwagę na znak nowego wiersza po wersji pakietu.

Możesz zapisać treść w pliku, np. payload.txt. Przyda się to później podczas testu dowodu włączenia.

Weryfikowanie autentyczności podpisu kodu APK

Teraz musimy zweryfikować autentyczność tokena podpisu kodu osadzonego w pliku APK. W tym celu używamy bundletool i klucza publicznego z pary kluczy, która została użyta do podpisania go w pierwszej kolejności. Są one publikowane w każdej sekcji odpowiednich plików APK. Załóżmy, że certyfikat klucza publicznego (np.dla weryfikatora kluczy systemowych Androida) został zapisany w pliku o nazwie signing_cert_pubkey.pem. Aby przeprowadzić weryfikację podpisu kodu, postępuj zgodnie z poniższymi instrukcjami.

Najpierw utwórz archiwum ZIP i dodaj do niego kandydacki plik APK.

computer:/tmp/testdir$ zip -u test.zip contactkeys_candidate.apk
        zip warning: test.zip not found or empty
  adding: contactkeys_candidate.apk (deflated 58%)

computer:/tmp/testdir$ file test.zip
test.zip: Zip archive data, at least v2.0 to extract, compression method=deflate

Możemy teraz użyć polecenia bundletoolcheck-transparency, aby sprawdzić, czy podpis kodu osadzony w kandydacie na plik APK jest zgodny z opublikowanym podpisem.

computer:/tmp/testdir$ java -jar BUNDLETOOL_INSTALL_PATH/bundletool-all-version.jar check-transparency \
  --mode=apk \
  --apk-zip=test.zip \
  --transparency-key-certificate=signing_cert_pubkey.pem

APK signature is valid. SHA-256 fingerprint of the apk signing key certificate (must be compared with the developer's public key manually): D9 E1 73 5B 2A 39 51 27 3A 87 35 B7 66 9E F1 9E F5 3A F1 C1 27 5C BA 31 39 3C 18 40 8B 03 79 D0
Code transparency signature verified for the provided code transparency key certificate.
Code transparency verified: code related file contents match the code transparency file.

Upewnij się, że wynik powyższego polecenia wskazuje, że zarówno podpis przejrzystości kodu, jak i przejrzystość kodu zostały zweryfikowane. Jeśli tak nie jest, np. widzisz dane wyjściowe takie jak Code transparency verification failed because the provided public key certificate does not match the transparency file, oznacza to, że integralność kodu danego pliku APK może być naruszona i nie należy mu ufać. Pamiętaj, aby sprawdzić, czy weryfikujesz je za pomocą prawidłowego certyfikatu klucza publicznego. Jeśli wszystko inne się zgadza, oznacza to, że autentyczność podpisu kodu została zweryfikowana w przypadku sprawdzanego pliku APK.

Weryfikowanie włączenia pakietu (dowód włączenia)

Korzystając z utworzonego wcześniej ładunku, możesz teraz sprawdzić, czy dany pakiet został uwzględniony w dzienniku przejrzystości.

Narzędzie do weryfikacji włączenia zostało opublikowane w avb repozytorium w ramach Projektu Android Open Source. Aby go uruchomić:

computer:external/avb/tools/transparency/verify$ PAYLOAD_PATH=PATH_TO_PAYLOAD_DIR/payload.txt
computer:external/avb/tools/transparency/verify$ go build cmd/verifier/verifier.go
computer:external/avb/tools/transparency/verify$ ./verifier --payload_path=${PAYLOAD_PATH} --log_type=google_system_apk

Weryfikator używa odpowiedniego punktu kontrolnego i zawartości dziennika (znajdującej się w katalogu kafelków), aby sprawdzić, czy ładunek APK znajduje się w dzienniku przejrzystości, potwierdzając, że został opublikowany przez Google.

Wynik polecenia jest zapisywany w standardowym wyjściu:

  • OK. inclusion check success! jeśli kod pakietu jest uwzględniony w logu;
  • FAILURE jeśli nie jest.