Podpisz kompilacje do opublikowania

Obrazy systemu operacyjnego Android używają podpisów kryptograficznych w 2 miejscach:

  1. Każdy plik .apk w obrazie musi być podpisany. Menedżer pakietów Androida używa podpisu .apk na 2 sposoby:
    • Aby uzyskać dostęp do danych starej aplikacji, musisz podpisać nową aplikację tym samym kluczem co starą aplikację. Dotyczy to zarówno aktualizacji aplikacji użytkownika przez zastąpienie pliku .apk, jak i zastąpienia aplikacji systemowej nowszą wersją zainstalowaną w ramach /data.
    • Jeśli co najmniej 2 aplikacje chcą udostępniać identyfikator użytkownika (aby mogły udostępniać dane itp.), muszą być podpisane tym samym kluczem.
  2. Pakiety aktualizacji OTA muszą być podpisane jednym z kluczy wymaganych przez system, w przeciwnym razie proces instalacji je odrzuci.

Klawisze zwalniające

Drzewo Androida zawiera klucze testowe w folderze build/target/product/security. Kompilowanie obrazu systemu operacyjnego Android za pomocą make spowoduje podpisanie wszystkich plików .apk za pomocą kluczy testowych. Klucze testowe są publicznie znane, więc każdy może podpisać własne pliki .apk tymi samymi kluczami, co może umożliwić zastąpienie lub przejęcie aplikacji systemowych wbudowanych w obraz systemu operacyjnego. Dlatego ważne jest, aby podpisać każdy opublikowany publicznie lub wdrożony obraz systemu operacyjnego Android za pomocą specjalnego zestawu kluczy wersji, do których dostęp ma tylko Ty.

Aby wygenerować własny unikalny zestaw kluczy wersji, uruchom te polecenia z poziomu głównego drzewa Androida:

subject='/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'
mkdir ~/.android-certs
for x in releasekey platform shared media networkstack; do \
    ./development/tools/make_key ~/.android-certs/$x "$subject"; \
  done

$subject należy zmienić, aby odzwierciedlał informacje organizacji. Możesz użyć dowolnego katalogu, ale wybierz lokalizację z kopią zapasową i bezpieczną. Niektórzy dostawcy decydują się na zaszyfrowanie klucza prywatnego za pomocą silnego hasła i przechowywanie zaszyfrowanego klucza w systemie kontroli wersji. Inni przechowują klucze wersji gdzie indziej, na przykład na komputerze z funkcją air-gap.

Aby wygenerować obraz wersji, użyj:

make dist
sign_target_files_apks \
-o \    # explained in the next section
--default_key_mappings ~/.android-certs out/dist/*-target_files-*.zip \
signed-target_files.zip

Skrypt sign_target_files_apks przyjmuje jako dane wejściowe pliki docelowe .zip i tworzy nowe pliki docelowe .zip, w których wszystkie pliki .apk zostały podpisane nowymi kluczami. Nowo podpisane obrazy znajdziesz w sekcji IMAGES/ w signed-target_files.zip.

Podpisywanie pakietów OTA

Podpisany plik ZIP z plikami docelowymi można przekonwertować na podpisany plik ZIP z aktualizacją OTA, wykonując tę procedurę:
ota_from_target_files \
-k  (--package_key) 
signed-target_files.zip \
signed-ota_update.zip

Podpisy i instalowanie aplikacji spoza sklepu

Ładowanie z zewnętrznego źródła nie omija normalnego mechanizmu weryfikacji podpisu pakietu w przywróceniu. Przed zainstalowaniem pakietu funkcja przywracania zweryfikuje, czy jest on podpisany jednym z kluczy prywatnych pasujących do kluczy publicznych zapisanych na partycji odzyskiwania, tak jak w przypadku pakietu dostarczanego przez sieć.

Pakiety aktualizacji otrzymane z głównego systemu są zwykle weryfikowane dwukrotnie: raz przez system główny za pomocą metody RecoverySystem.verifyPackage() w interfejsie API Androida, a potem ponownie przez funkcję odzyskiwania. Interfejs RecoverySystem API porównuje podpis z kluczami publicznymi przechowywanymi w głównym systemie w pliku /system/etc/security/otacerts.zip (domyślnie). Odzyskiwanie sprawdza podpis pod kątem kluczy publicznych przechowywanych na dysku RAM partycji odzyskiwania w pliku /res/keys.

Domyślnie pliki docelowe .zip wygenerowane przez kompilację ustawiają certyfikat OTA tak, aby pasował do klucza testowego. W przypadku opublikowanego obrazu należy użyć innego certyfikatu, aby urządzenia mogły zweryfikować autentyczność pakietu aktualizacji. Przekazywanie flagi -o do sign_target_files_apks (jak pokazano w poprzedniej sekcji) powoduje zastąpienie certyfikatu klucza testowego certyfikatem klucza wersji z Twojego katalogu certyfikatów.

Zwykle obraz systemu i obraz odzyskiwania zawierają ten sam zestaw kluczy publicznych OTA. Dodanie klucza tylko do zestawu kluczy przywracania umożliwia podpisywanie pakietów, które można instalować tylko przez instalowanie z innego urządzenia (przy założeniu, że mechanizm pobierania aktualizacji głównego systemu prawidłowo wykonuje weryfikację pod kątem pliku otacerts.zip). Możesz określić dodatkowe klucze, które będą uwzględniane tylko w procesie odzyskiwania, ustawiając zmienną PRODUCT_EXTRA_RECOVERY_KEYS w definicji produktu:

vendor/yoyodyne/tardis/products/tardis.mk
 [...]

PRODUCT_EXTRA_RECOVERY_KEYS := vendor/yoyodyne/security/tardis/sideload

Obejmuje to klucz publiczny vendor/yoyodyne/security/tardis/sideload.x509.pem w pliku z kluczami odzyskiwania, aby można było instalować pakiety podpisane za jego pomocą. Ten dodatkowy klucz nie jest jednak uwzględniony w pliku otacerts.zip, więc systemy, które prawidłowo weryfikują pobrane pakiety, nie wywołują funkcji odzyskiwania w przypadku pakietów podpisanych tym kluczem.

Certyfikaty i klucze prywatne

Każdy klucz składa się z dwóch plików: certyfikatu z rozszerzeniem .x509.pem oraz klucza prywatnego z rozszerzeniem .pk8. Klucz prywatny należy przechowywać w tajemnicy. Służy on do podpisywania pakietu. Klucz może być chroniony hasłem. Certyfikat zawiera natomiast tylko publiczną połowę klucza, dzięki czemu można go rozpowszechniać wśród wszystkich użytkowników. Służy do sprawdzania, czy pakiet został podpisany odpowiednim kluczem prywatnym.

Standardowa wersja Androida używa 5 kluczy, które znajdują się w folderze build/target/product/security:

testkey
Ogólny klucz domyślny dla pakietów, które nie określają innego klucza.
platform
Klucz testowy do pakietów, które są częścią platformy podstawowej.
udostępniony
Klucz do testowania elementów udostępnianych w domu i kontaktach.
multimedia
Klucz testowy dla pakietów, które są częścią systemu pobierania/multimediów.
networkstack
Klucz testowy dla pakietów, które są częścią systemu sieciowego. Klucz Networktack służy do podpisywania plików binarnych zaprojektowanych jako modułowe komponenty systemu . Jeśli aktualizacje modułów są tworzone osobno i zintegrowane jako gotowe w obrazie urządzenia, generowanie klucza Networktack w drzewie źródłowym Androida może nie być konieczne.

Poszczególne pakiety określają jeden z tych kluczy, ustawiając LOCAL_CERTIFICATE w pliku Android.mk. (Jeśli ta zmienna nie jest ustawiona, używany jest klucz testowy). Możesz też podać zupełnie inny klucz za pomocą ścieżki, np.:

device/yoyodyne/apps/SpecialApp/Android.mk
 [...]

LOCAL_CERTIFICATE := device/yoyodyne/security/special

Teraz kompilacja używa klucza device/yoyodyne/security/special.{x509.pem,pk8} do podpisania pliku SpecialApp.apk. Kompilacja może używać tylko kluczy prywatnych, które nie są chronione hasłem.

Zaawansowane opcje podpisywania

Wymiana klucza podpisywania pliku APK

Skrypt podpisywania sign_target_files_apks działa na plikach docelowych wygenerowanych dla kompilacji. Wszystkie informacje o certyfikatach i kluczach prywatnych używanych podczas kompilacji są zawarte w plikach docelowych. Podczas uruchamiania skryptu podpisywania w celu podpisania wersji można zastąpić klucze podpisywania na podstawie nazwy klucza lub nazwy pliku APK.

Aby określić zastępowanie kluczy na podstawie ich nazw, użyj flag --key_mapping i --default_key_mappings:

  • Flaga --key_mapping src_key=dest_key określa zastąpienie jednego klucza naraz.
  • Flaga --default_key_mappings dir określa katalog z 5 kluczami, które zastępują wszystkie klucze w pliku build/target/product/security. Jest to równoznaczne z 5-krotnym użyciem pliku --key_mapping do określenia mapowań.
build/target/product/security/testkey      = dir/releasekey
build/target/product/security/platform     = dir/platform
build/target/product/security/shared       = dir/shared
build/target/product/security/media        = dir/media
build/target/product/security/networkstack = dir/networkstack

Użyj parametru --extra_apks apk_name1,apk_name2,...=key, aby określić zastąpienia klucza podpisywania na podstawie nazw plików APK. Jeśli pole key jest puste, skrypt traktuje wskazane pliki APK jako podpisane wstępnie.

W przypadku hipotetycznego produktu tardis potrzebujesz 6 kluczy chronionych hasłem: 5 kluczy do zastąpienia 5 kluczy w build/target/product/security i 1 klucza do zastąpienia dodatkowego klucza device/yoyodyne/security/special wymaganego przez aplikację SpecialApp w wymienionym powyżej przykładzie. Jeśli klucze znajdowały się w tych plikach:

vendor/yoyodyne/security/tardis/releasekey.x509.pem
vendor/yoyodyne/security/tardis/releasekey.pk8
vendor/yoyodyne/security/tardis/platform.x509.pem
vendor/yoyodyne/security/tardis/platform.pk8
vendor/yoyodyne/security/tardis/shared.x509.pem
vendor/yoyodyne/security/tardis/shared.pk8
vendor/yoyodyne/security/tardis/media.x509.pem
vendor/yoyodyne/security/tardis/media.pk8
vendor/yoyodyne/security/tardis/networkstack.x509.pem
vendor/yoyodyne/security/tardis/networkstack.pk8
vendor/yoyodyne/security/special.x509.pem
vendor/yoyodyne/security/special.pk8           # NOT password protected
vendor/yoyodyne/security/special-release.x509.pem
vendor/yoyodyne/security/special-release.pk8   # password protected

Następnie podpisz wszystkie aplikacje w ten sposób:

./build/make/tools/releasetools/sign_target_files_apks \
    --default_key_mappings vendor/yoyodyne/security/tardis \
    --key_mapping vendor/yoyodyne/security/special=vendor/yoyodyne/security/special-release \
    --extra_apks PresignedApp= \
    -o tardis-target_files.zip \
    signed-tardis-target_files.zip

W efekcie:

Enter password for vendor/yoyodyne/security/special-release key>
Enter password for vendor/yoyodyne/security/tardis/networkstack key>
Enter password for vendor/yoyodyne/security/tardis/media key>
Enter password for vendor/yoyodyne/security/tardis/platform key>
Enter password for vendor/yoyodyne/security/tardis/releasekey key>
Enter password for vendor/yoyodyne/security/tardis/shared key>
    signing: Phone.apk (vendor/yoyodyne/security/tardis/platform)
    signing: Camera.apk (vendor/yoyodyne/security/tardis/media)
    signing: NetworkStack.apk (vendor/yoyodyne/security/tardis/networkstack)
    signing: Special.apk (vendor/yoyodyne/security/special-release)
    signing: Email.apk (vendor/yoyodyne/security/tardis/releasekey)
        [...]
    signing: ContactsProvider.apk (vendor/yoyodyne/security/tardis/shared)
    signing: Launcher.apk (vendor/yoyodyne/security/tardis/shared)
NOT signing: PresignedApp.apk
        (skipped due to special cert string)
rewriting SYSTEM/build.prop:
  replace:  ro.build.description=tardis-user Eclair ERC91 15449 test-keys
     with:  ro.build.description=tardis-user Eclair ERC91 15449 release-keys
  replace: ro.build.fingerprint=generic/tardis/tardis/tardis:Eclair/ERC91/15449:user/test-keys
     with: ro.build.fingerprint=generic/tardis/tardis/tardis:Eclair/ERC91/15449:user/release-keys
    signing: framework-res.apk (vendor/yoyodyne/security/tardis/platform)
rewriting RECOVERY/RAMDISK/default.prop:
  replace:  ro.build.description=tardis-user Eclair ERC91 15449 test-keys
     with:  ro.build.description=tardis-user Eclair ERC91 15449 release-keys
  replace: ro.build.fingerprint=generic/tardis/tardis/tardis:Eclair/ERC91/15449:user/test-keys
     with: ro.build.fingerprint=generic/tardis/tardis/tardis:Eclair/ERC91/15449:user/release-keys
using:
    vendor/yoyodyne/security/tardis/releasekey.x509.pem
for OTA package verification
done.

Po wyświetleniu użytkownikowi haseł do wszystkich kluczy chronionych hasłem skrypt ponownie podpisuje wszystkie pliki APK w docelniku wejściowym .zip za pomocą kluczy wersji. Przed uruchomieniem polecenia możesz też ustawić zmienną środowiskową ANDROID_PW_FILE jako nazwę pliku tymczasowego. Skrypt wywoła wtedy edytor, aby umożliwić Ci wprowadzenie haseł do wszystkich kluczy (może to być wygodniejszy sposób wprowadzania haseł).

Zastąpienie klucza podpisywania APEX

Android 10 wprowadza format pliku APEX do instalowania niższych modułów systemu. Jak wyjaśniono w artykule Podpisywanie pakietu APEX, każdy plik APEX jest podpisywany 2 kluczami: jednym dla obrazu mini systemu plików w pakiecie APEX i drugim dla całego pakietu APEX.

Podczas podpisywania wersji dwa klucze podpisywania pliku APEX są zastępowane kluczami wersji. Klucz ładunku systemu plików jest określany za pomocą flagi --extra_apex_payload, a klucz podpisywania całego pliku APEX – za pomocą flagi --extra_apks.

W przypadku usługi tardis załóżmy, że masz następującą konfigurację kluczy dla plików APEX com.android.conscrypt.apex, com.android.media.apexcom.android.runtime.release.apex.

name="com.android.conscrypt.apex" public_key="PRESIGNED" private_key="PRESIGNED" container_certificate="PRESIGNED" container_private_key="PRESIGNED"
name="com.android.media.apex" public_key="PRESIGNED" private_key="PRESIGNED" container_certificate="PRESIGNED" container_private_key="PRESIGNED"
name="com.android.runtime.release.apex" public_key="vendor/yoyodyne/security/testkeys/com.android.runtime.avbpubkey" private_key="vendor/yoyodyne/security/testkeys/com.android.runtime.pem" container_certificate="vendor/yoyodyne/security/testkeys/com.google.android.runtime.release_container.x509.pem" container_private_key="vendor/yoyodyne/security/testkeys/com.google.android.runtime.release_container.pk8"

Masz te pliki zawierające klucze wersji:

vendor/yoyodyne/security/runtime_apex_container.x509.pem
vendor/yoyodyne/security/runtime_apex_container.pk8
vendor/yoyodyne/security/runtime_apex_payload.pem

Podane niżej polecenie zastępuje klucze podpisywania com.android.runtime.release.apexcom.android.tzdata.apex podczas podpisywania wersji. W szczególności podpis com.android.runtime.release.apex jest podpisany określonymi kluczami wydania (runtime_apex_container w przypadku pliku APEX i runtime_apex_payload w przypadku ładunku obrazu pliku). Element com.android.tzdata.apex jest traktowany jako wstępnie podpisany. Wszystkie pozostałe pliki APEX są obsługiwane przez domyślną konfigurację wymienioną w plikach docelowych.

./build/make/tools/releasetools/sign_target_files_apks \
    --default_key_mappings   vendor/yoyodyne/security/tardis \
    --extra_apks             com.android.runtime.release.apex=vendor/yoyodyne/security/runtime_apex_container \
    --extra_apex_payload_key com.android.runtime.release.apex=vendor/yoyodyne/security/runtime_apex_payload.pem \
    --extra_apks             com.android.media.apex= \
    --extra_apex_payload_key com.android.media.apex= \
    -o tardis-target_files.zip \
    signed-tardis-target_files.zip

Uruchomienie tego polecenia powoduje wyświetlenie tych dzienników:

        [...]
    signing: com.android.runtime.release.apex                  container (vendor/yoyodyne/security/runtime_apex_container)
           : com.android.runtime.release.apex                  payload   (vendor/yoyodyne/security/runtime_apex_payload.pem)
NOT signing: com.android.conscrypt.apex
        (skipped due to special cert string)
NOT signing: com.android.media.apex
        (skipped due to special cert string)
        [...]

Inne opcje

Skrypt podpisywania sign_target_files_apks zapisuje w plikach właściwości kompilacji nowy opis kompilacji i odcisk palca, aby odzwierciedlić fakt, że kompilacja jest podpisana. Flaga --tag_changes określa, jakie zmiany są wprowadzane w odciskach palców. Uruchom skrypt z opcją -h, aby wyświetlić dokumentację dotyczącą wszystkich flag.

Ręczne generowanie kluczy

Android używa 2048-bitowych kluczy RSA z wykładnikiem publicznym 3. Pary kluczy certyfikatu i klucza prywatnego możesz generować za pomocą narzędzia openssl z openssl.org:

# generate RSA key
openssl genrsa -3 -out temp.pem 2048
Generating RSA private key, 2048 bit long modulus
....+++
.....................+++
e is 3 (0x3)

# create a certificate with the public part of the key
openssl req -new -x509 -key temp.pem -out releasekey.x509.pem -days 10000 -subj '/C=US/ST=California/L=San Narciso/O=Yoyodyne, Inc./OU=Yoyodyne Mobility/CN=Yoyodyne/emailAddress=yoyodyne@example.com'

# create a PKCS#8-formatted version of the private key
openssl pkcs8 -in temp.pem -topk8 -outform DER -out releasekey.pk8 -nocrypt

# securely delete the temp.pem file
shred --remove temp.pem

Podane powyżej polecenie openssl pkcs8 tworzy plik .pk8 bez żadnego hasła, który można używać w systemie kompilacji. Aby utworzyć plik .pk8 zabezpieczony hasłem (co należy zrobić w przypadku wszystkich rzeczywistych kluczy wersji), zastąp argument -nocrypt wartością -passout stdin. Następnie opensl zaszyfruje klucz prywatny przy użyciu hasła odczytanego ze standardowych danych wejściowych. Nie wyświetla się żadna prośba, więc jeśli stdin to terminal, program może się zawiesić, gdy tak naprawdę czeka na wpisanie hasła. Inne wartości mogą być użyte w argumentu-passout do odczytu hasła z innych lokalizacji. Szczegółowe informacje znajdziesz w dokumentacji openssl.

Plik pośredniczący temp.pem zawiera klucz prywatny bez żadnej ochrony hasłem, dlatego podczas generowania kluczy wersji wyrzucaj go z rozwagą. W szczególności narzędzie GNUshred może nie być skuteczne w przypadku systemów plików sieciowych lub dziennika. Podczas generowania kluczy możesz użyć katalogu roboczego znajdującego się na dysku RAM (np. partycji tmpfs), aby mieć pewność, że pośrednie dane nie zostaną przypadkowo ujawnione.

Tworzenie plików z obrazami

Gdy masz signed-target_files.zip, musisz utworzyć obraz, aby móc go umieścić na urządzeniu. Aby utworzyć podpisany obraz z plików docelowych, uruchom to polecenie w katalogu głównym drzewa Androida:

img_from_target_files signed-target_files.zip signed-img.zip
Wynikowy plik signed-img.zip zawiera wszystkie pliki .img. Aby wczytać obraz na urządzenie, użyj trybu szybkiego rozruchu w następujący sposób:
fastboot update signed-img.zip