Schemat podpisu pliku APK w wersji 2

Schemat podpisu plików APK w wersji 2 to schemat podpisu dla całego pliku, który przyspiesza weryfikację i zwiększa gwarancje integralności, wykrywając wszelkie zmiany w zabezpieczonych częściach pliku APK.

Podpisywanie przy użyciu schematu podpisu plików APK w wersji 2 wstawia blok podpisywania pliku APK do pliku APK bezpośrednio przed sekcją ZIP Central Directory. W bloku podpisywania plików APK podpisy w wersji 2 i informacje o tożsamości podpisujących są przechowywane w bloku schematu podpisu pliku APK w wersji 2.

Plik APK przed i po podpisaniu

Rysunek 1. Plik APK przed i po podpisaniu

Schemat podpisu plików APK w wersji 2 został wprowadzony w Androidzie 7.0 (Nougat). Aby plik APK można było zainstalować na urządzeniach z Androidem 6.0 (Marshmallow) i starszych, należy go podpisać przy użyciu podpisywania pliku JAR, a następnie podpisać według schematu v2.

Blokada podpisywania pliku APK

Aby zachować zgodność wsteczną z formatem plików APK w wersji 1, podpisy plików APK w wersji 2 i nowszych są przechowywane w bloku podpisywania plików APK – nowym kontenerem obsługującym schemat podpisywania plików APK w wersji 2. W pliku APK blok podpisywania APK znajduje się bezpośrednio przed katalogiem głównym ZIP, który znajduje się na końcu pliku.

Blok zawiera pary identyfikator–wartość zapakowane w sposób ułatwiający zlokalizowanie go w pliku APK. Podpis w wersji 2 pliku APK jest przechowywany jako para identyfikator–wartość z identyfikatorem 0x7109871a.

Format

Format bloku podpisywania pliku APK jest następujący (wszystkie pola liczbowe są w porządku little-endian):

  • size of block w bajtach (bez tego pola) (uint64)
  • Sekwencja par identyfikator-wartość z prefiksem o długości uint64:
    • ID (Uint32)
    • value (zmienna długość: długość pary – 4 bajty)
  • size of block w bajtach – to samo co pierwsze pole (uint64)
  • magic „APK Sig Block 42” (16 bajtów)

Plik APK jest analizowany przez znalezienie początku katalogu centralnego ZIP (poprzez znalezienie rekordu ZIP End of Central Directory na końcu pliku, a następnie odczytanie przesunięcia początkowego katalogu centralnego z rekordu). Wartość magic pozwala szybko ustalić, że blok danych poprzedzający katalog główny jest prawdopodobnie blokiem podpisywania pliku APK. Wartość size of block wskazuje na początek bloku w pliku.

Podczas interpretacji bloku pary identyfikator–wartość z nieznanymi identyfikatorami powinny być ignorowane.

Blokada schematu podpisu APK w wersji 2

Plik APK jest podpisany przez co najmniej 1 sygnatariusza lub tożsamość, z których każdy jest reprezentowany przez klucz podpisywania. Te informacje są przechowywane jako blok schematu podpisu APK w wersji 2. W przypadku każdej osoby podpisującej przechowywane są te informacje:

  • (signature algorithm, digest, signature) tuples. Digest jest przechowywany, aby oddzielić weryfikację podpisu od sprawdzania integralności zawartości pliku APK.
  • łańcuch certyfikatów X.509 reprezentujący tożsamość podpisującego.
  • dodatkowe atrybuty w postaci par klucz-wartość.

W przypadku każdego podpisującego plik APK jest weryfikowany przy użyciu obsługiwanego podpisu z podanej listy. Podpisy z nieznanymi algorytmami podpisów są ignorowane. Od każdej implementacji zależy, którego podpisu użyć w przypadku wystąpienia wielu obsługiwanych podpisów. Umożliwi to wprowadzenie w przyszłości mocniejszych metod podpisywania w sposób zgodny z wstecz. Sugerowane podejście to weryfikacja najsilniejszej sygnatury.

Format

Blok schematu podpisu APK w wersji 2 jest przechowywany w bloku podpisywania APK pod identyfikatorem 0x7109871a.

Format bloku schematu podpisu APK w wersji 2 jest następujący (wszystkie wartości liczbowe są wartością little-endian, a wszystkie pola z prefiksami długości mają ciąg uint32 jako długość):

  • sekwencja z przedrostkiem długości zawierająca signer z przedrostkiem długości:
    • signed data z prefiksem długości:
      • sekwencja z przedrostkiem długości zawierająca digests z przedrostkiem długości:
      • sekwencja X.509 z preiksem długości: certificates:
        • X.509 z prefiksem długości certificate (format ASN.1 DER)
      • sekwencja z przedrostkiem długości zawierająca additional attributes z przedrostkiem długości:
        • ID (Uint32)
        • value (długość zmienna: długość dodatkowego atrybutu – 4 bajty)
    • sekwencja z przedrostkiem długości zawierająca signatures z przedrostkiem długości:
      • signature algorithm ID (Uint32)
      • signature z prefiksem długości signed data
    • z prefiksem długości public key (SubjectPublicKeyInfo, format ASN.1 DER)

Identyfikatory algorytmu podpisu

  • 0x0101 – RSASSA-PSS ze skrótem SHA2-256, SHA2-256 MGF1, 32 bajty ciągu zaburzającego, zwiastun: 0xbc
  • 0x0102 – RSASSA-PSS z podpisem SHA2-512, SHA2-512 MGF1, 64 bajty soli, trailer: 0xbc
  • 0x0103 — RSASSA-PKCS1-v1_5 z skrótem SHA2-256. Jest to przeznaczone dla systemów kompilacji, które wymagają podpisów deterministycznych.
  • 0x0104 – RSASSA-PKCS1-v1_5 ze skrótem SHA2-512. Jest to przeznaczone dla systemów kompilacji, które wymagają podpisów deterministycznych.
  • 0x0201 – ECDSA z skrótem SHA2-256
  • 0x0202 – ECDSA ze skrótem SHA2-512
  • 0x0301 – DSA ze skrótem SHA2-256

Wszystkie powyższe algorytmy podpisu są obsługiwane przez platformę Android. Narzędzia do podpisywania mogą obsługiwać podzbiór algorytmów.

Obsługiwane rozmiary kluczy i krzywe EC:

  • RSA: 1024, 2048, 4096, 8192, 16384
  • EC: NIST P-256, P-384, P-521
  • DSA: 1024, 2048, 3072

Treści chronione integralności

W celu ochrony treści plik APK składa się z 4 sekcji:

  1. Zawartość wpisów ZIP (od przesunięcia 0 do początku bloku podpisywania APK)
  2. Blokada podpisywania pliku APK
  3. Katalog ZIP Central
  4. ZIP End of Central Directory

Sekcje pliku APK po podpisaniu

Rysunek 2. Sekcje pliku APK po podpisaniu

Schemat podpisu plików APK w wersji 2 chroni integralność sekcji 1, 3 i 4 oraz bloków signed data w ramach sekcji 2 schematu podpisu plików APK w wersji 2.

Integralność sekcji 1, 3 i 4 jest chroniona przez co najmniej 1 streszczenie treści tych sekcji przechowywanych w blokach signed data, które są z kolei chronione przez co najmniej 1 podpis.

Wyciąg z sekcji 1, 3 i 4 jest obliczany w ten sposób: podobnie jak drzewo Merkla na 2 poziomach. Każda sekcja jest podzielona na kolejne fragmenty o długości 1 MB (220 bajtów). Ostatni fragment każdej sekcji może być krótszy. Digest każdego fragmentu jest obliczany na podstawie ciągu bajtów 0xa5, długości fragmentu w bajtach (uint32 w formacie little-endian) oraz zawartości fragmentu. Skrót na najwyższym poziomie jest obliczany na podstawie ciągu bajtów 0x5a, liczby fragmentów (w systemie little-endian uint32) oraz ciągu skrótów fragmentów w kolejności ich występowania w pliku APK. Informacje są obliczane w kawałkach, aby przyspieszyć obliczenia przez ich równoległe wykonywanie.

Podsumowanie pliku APK

Rysunek 3. Skrót pliku APK

Ochrona sekcji 4 (ZIP End of Central Directory) jest skomplikowana przez sekcję zawierającą przesunięcie katalogu centralnego ZIP. Przesunięcie zmienia się, gdy zmieni się rozmiar bloku podpisywania APK, np. po dodaniu nowego podpisu. Dlatego podczas obliczania skrótu na końcu ZIP na końcu katalogu Central Directory pole zawierające odsunięcie katalogu ZIP Central Directory musi być traktowane jako zawierające przesunięcie bloku podpisywania APK.

Zabezpieczenia przed przywracaniem

Na platformach Androida, które obsługują weryfikację plików APK podpisanych według schematu v2, atakujący może spróbować zweryfikować plik APK podpisany według schematu v2 jako plik APK podpisany według schematu v1. Aby zniwelować ten atak, pliki APK z podpisem v2, które również są podpisane v1, muszą w głównej sekcji plików META-INF/*.SF zawierać atrybut podpisany przez X-Androida-APK. Wartość tego atrybutu to zestaw rozdzielonych przecinkami identyfikatorów schematu podpisu APK (identyfikator tego schematu to 2). Podczas weryfikacji podpisu w wersji 1 weryfikator plików APK musi odrzucać pliki APK, które nie mają podpisu w schemacie podpisu APK preferowanym przez weryfikator z tego zestawu (np. schemat w wersji 2). Ta ochrona polega na tym, że zawartość plików META-INF/SF jest chroniona za pomocą podpisów v1. Zapoznaj się z sekcją Weryfikacja podpisanego pliku APK w formacie JAR.

Osoba przeprowadzająca atak może spróbować usunąć silniejsze podpisy z bloku schematu podpisu APK w wersji 2. Aby zapobiec temu atakowi, lista identyfikatorów algorytmu podpisu, którym podpisano plik APK, jest przechowywana w bloku signed data, który jest chroniony przez każdą sygnaturę.

Weryfikacja

W Androidzie 7.0 i nowszych pliki APK można weryfikować zgodnie ze schematem podpisu APK w wersji 2 lub nowszej albo podpisem JAR (schemat w wersji 1). Starsze platformy ignorują podpisy w wersji 2 i weryfikują tylko podpisy w wersji 1.

Proces weryfikacji podpisu APK

Rysunek 4. Proces weryfikacji podpisu APK (nowe kroki zaznaczone na czerwono)

Weryfikacja schematu podpisu APK w wersji 2

  1. Odszukaj blok podpisywania pliku APK i sprawdź, czy:
    1. 2 pola rozmiaru w bloku podpisywania APK zawierają tę samą wartość.
    2. Po katalogu centralnym ZIP następują dane zakończenia katalogu centralnego ZIP.
    3. Po zakończeniu centralnego katalogu ZIP nie następuje więcej danych.
  2. Znajdź pierwszy blok schematu podpisu APK w wersji 2 w bloku podpisywania pliku APK. Jeśli blokada v2 jest obecna, przejdź do kroku 3. W przeciwnym razie przejdź do weryfikacji pliku APK za pomocą schematu v1.
  3. W przypadku każdego elementu signer w bloku schematu podpisu APK w wersji 2:
    1. Wybierz najsilniejszy obsługiwany signature algorithm ID z signatures. Kolejność siły zależy od wersji implementacji lub platformy.
    2. Zweryfikuj odpowiednie signature z signatures z signed data przy użyciu public key. (Teraz można bezpiecznie przeanalizować signed data).
    3. Sprawdź, czy uporządkowana lista identyfikatorów algorytmów podpisu w pliku digestssignatures jest identyczna. (ma to zapobiec dodaniu lub usunięciu podpisu).
    4. Oblicz podsumowanie zawartości pliku APK za pomocą tego samego algorytmu co algorytm skrótu używany w algorytmie podpisu.
    5. Sprawdź, czy obliczony skrót jest identyczny z odpowiednią wartością digest z digests.
    6. Sprawdź, czy SubjectPublicKeyInfo pierwszego certificate w certificates jest identyczny z public key.
  4. Weryfikacja zakończy się pomyślnie, jeśli znaleziono co najmniej 1 element signer, a dla każdego znalezionego elementu signer uda się wykonać krok 3.

Uwaga: jeśli na kroku 3 lub 4 wystąpi błąd, plik APK nie może być weryfikowany przy użyciu schematu w wersji 1.

weryfikacja pliku APK podpisanego za pomocą pliku JAR (schemat 1);

Plik APK podpisany za pomocą pliku JAR to standardowy plik JAR z podpisem, który musi zawierać dokładnie te same pozycje, które są wymienione w pliku META-INF/MANIFEST.MF. Wszystkie pozycje muszą być podpisane przez ten sam zestaw podpisujących. Integralność obiektu tajnego jest weryfikowana w następujący sposób:

  1. Każdy podpisujący jest reprezentowany przez wpis JAR w katalogu META-INF/<signer>.SF i META-INF/<signer>.(RSA|DSA|EC).
  2. <signer>.(RSA|DSA|EC) to PKCS #7 CMS ContentInfo ze strukturą SignedData, którego podpis jest weryfikowany na podstawie pliku <signer>.SF.
  3. Plik <signer>.SF zawiera skrót całego pliku META-INF/MANIFEST.MF oraz skróty każdej sekcji pliku META-INF/MANIFEST.MF. Sprawdzono całość pliku MANIFEST.MF. Jeśli to się nie uda, zweryfikowany zostanie zamiast tego skrót każdej sekcji pliku MANIFEST.MF.
  4. Plik META-INF/MANIFEST.MF zawiera w przypadku każdego wpisu JAR chronionego integralnością sekcję o odpowiedniej nazwie zawierającą skrót nieskompresowanych treści wpisu. Wszystkie te zbiorcze raporty są zweryfikowane.
  5. Weryfikacja pliku APK kończy się niepowodzeniem, jeśli zawiera on wpisy JAR, których nie ma w pliku MANIFEST.MF i które nie są częścią podpisu JAR.

Łańcuch zabezpieczeń to więc <signer>.(RSA|DSA|EC) -> <signer>.SF -> MANIFEST.MF -> zawartość każdego elementu JAR chronionego za pomocą integralności.