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.
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:signature algorithm ID
(uint32)- (z prefiksem długości)
digest
— patrz Treści chronione przed naruszeniem integralności
- sekwencja X.509 z preiksem długości:
certificates
- X.509 z prefiksem długości
certificate
(format ASN.1 DER)
- X.509 z prefiksem długości
- 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
- sekwencja z przedrostkiem długości zawierająca
signatures
z przedrostkiem długości:signature algorithm ID
(uint32)signature
z prefiksem długości nadsigned 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:
- Treść wpisów ZIP (od przesunięcia 0 do początku bloku podpisywania pliku APK)
- Blok podpisywania pliku APK
- Katalog centralny ZIP
- ZIP End of Central Directory
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.
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.
Rysunek 4. Proces weryfikacji podpisu pliku APK (nowe kroki są oznaczone na czerwono)
Weryfikacja schematu podpisu APK w wersji 2
- Odszukaj blok podpisywania pliku APK i sprawdź, czy:
- 2 pola rozmiaru w bloku podpisywania APK zawierają tę samą wartość.
- Po katalogu centralnym ZIP następują dane zakończenia katalogu centralnego ZIP.
- Po zakończeniu centralnego katalogu ZIP nie następuje więcej danych.
- 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.
- W przypadku każdego elementu
signer
w bloku schematu podpisu APK w wersji 2:- Wybierz najsilniejszy obsługiwany
signature algorithm ID
zsignatures
. Kolejność siły zależy od wersji implementacji lub platformy. - Za pomocą polecenia
public key
sprawdź, czy odpowiednia wartośćsignature
z poziomusignatures
jest zgodna z wartościąsigned data
. (Teraz można bezpiecznie przeanalizowaćsigned data
). - Sprawdź, czy uporządkowana lista identyfikatorów algorytmów podpisu w pliku
digests
isignatures
jest identyczna. (ma to zapobiec usunięciu lub dodaniu podpisu). - Obliczać skrót treści pliku APK za pomocą tego samego algorytmu skrótu co algorytm podpisu.
- Sprawdź, czy obliczony digest jest identyczny z odpowiadającym mu
digest
zdigests
. - Sprawdź, czy SubjectPublicKeyInfo pierwszego
certificate
wcertificates
jest identyczny zpublic key
.
- Wybierz najsilniejszy obsługiwany
- Weryfikacja przebiega pomyślnie, jeśli znaleziono co najmniej 1 wartość
signer
i w przypadku każdej znalezionej wartościsigner
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:
- Każdy podpisujący jest reprezentowany przez wpis JAR w katalogu META-INF/<signer>.SF i META-INF/<signer>.(RSA|DSA|EC).
- <signer>.(RSA|DSA|EC) to PKCS #7 CMS ContentInfo z podpisem cyfrowym w strukturze SignedData, którego podpis jest weryfikowany w pliku <signer>.SF.
- 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.
- 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.
- 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.