Schemat podpisu pliku APK w wersji 2

Schemat podpisu pliku APK w wersji 2 to schemat podpisu całego pliku, który zwiększa szybkość weryfikacji i wzmacnia gwarancje integralności, wykrywając wszelkie zmiany w chronionych częściach pliku APK.

Podpisywanie przy użyciu schematu podpisu APK w wersji 2 powoduje wstawienie bloku podpisywania APK w pliku APK bezpośrednio przed sekcją katalogu głównego ZIP. W bloku podpisywania pliku APK podpisy w wersji 2 i informacje o tożsamości podpisującego 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.

Blok podpisywania pliku APK

Aby zachować zgodność wsteczną z formatem pliku APK w wersji 1, podpisy w wersji 2 i nowszych są przechowywane w bloku podpisywania pliku APK – nowym kontenerze wprowadzonym w celu obsługi schematu podpisu pliku 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 formacie little-endian):

  • size of block bajtów (z wyjątkiem 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 głównego ZIP (poprzez znalezienie rekordu ZIP End of Central Directory na końcu pliku, a następnie odczytanie przesunięcia początkowego katalogu głównego z rekordu). Wartość magic umożliwia szybkie ustalenie, ż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żdego podpisującego 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. W przypadku wykrycia wielu obsługiwanych podpisów wybór podpisu do użycia należy do implementacji. 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ą w postaci little-endian, wszystkie pola z prefiksem długości używają typu uint32):

  • sekwencja z przedrostkiem długości zawierająca signer z przedrostkiem długości:
    • signed data z preiksem 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 nad 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 soli, trailer: 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 z 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 z skrótem SHA2-256

Platforma Android obsługuje wszystkie wymienione wyżej algorytmy podpisów. 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 z ochroną integralności

W celu ochrony zawartości pliku APK składa się on z 4 sekcji:

  1. Treść wpisów ZIP (od przesunięcia 0 do początku bloku podpisywania pliku APK)
  2. Blok podpisywania pliku APK
  3. Katalog centralny ZIP
  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 bloku schematu podpisu plików APK w wersji 2 zawartego w sekcji 2.

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

Informacje skrótowe z sekcji 1, 3 i 4 są obliczane w następujący sposób, podobnie jak w drzewie 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.

Skrót 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. Odsunięcie zmienia się, gdy zmienia się rozmiar bloku podpisywania pliku APK, na przykład gdy dodano nowy podpis. Dlatego podczas obliczania skrótu w ramach zakończenia katalogu centralnego pliku ZIP pole zawierające przesunięcie katalogu centralnego pliku ZIP należy traktować jako zawierające przesunięcie bloku podpisywania pliku 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 zapobiec temu atakowi, pliki APK podpisane w wersji 2, które są również podpisane w wersji 1, muszą zawierać atrybut X-Android-APK-Signed w sekcji głównej plików META-INF/*.SF. Wartość tego atrybutu to zestaw rozdzielonych przecinkami identyfikatorów schematów podpisów plików 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 plików APK preferowanym przez weryfikator z tego zestawu (np. schemat w wersji 2). Ta ochrona opiera się na tym, że zawartość plików META-INF/SF jest chroniona za pomocą sygnatur w wersji 1. Zapoznaj się z sekcją Weryfikacja pliku APK podpisanego za pomocą pliku JAR.

Osoba atakująca może spróbować usunąć silniejsze podpisy z bloku podpisu schematu pliku 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 pliku APK

Rysunek 4. Proces weryfikacji podpisu pliku APK (nowe kroki są oznaczone 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 w wersji 1.
  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. Za pomocą polecenia public key sprawdź, czy odpowiednia wartość signature z poziomu signatures jest zgodna z wartością signed data. (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 usunięciu lub dodaniu podpisu).
    4. Obliczać skrót treści pliku APK za pomocą tego samego algorytmu skrótu co algorytm podpisu.
    5. Sprawdź, czy obliczony digest jest identyczny z odpowiadającym mu digestdigests.
    6. Sprawdź, czy SubjectPublicKeyInfo pierwszego certificate w certificates jest identyczny z public key.
  4. Weryfikacja przebiega pomyślnie, jeśli znaleziono co najmniej 1 wartość signer i w przypadku każdej znalezionej wartości signer udało 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 z podpisem cyfrowym w strukturze SignedData, którego podpis jest weryfikowany w pliku <signer>.SF.
  3. Plik <signer>.SF zawiera całość pliku skrótów 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.