Firmare le build per la release

Le immagini del sistema operativo Android utilizzano le firme crittografiche in due punti:

  1. Ogni file .apk all'interno dell'immagine deve essere firmato. Gestore pacchetti di Android utilizza una firma .apk in due modi:
    • Quando un'applicazione viene sostituita, deve essere firmata con la stessa chiave della vecchia applicazione per ottenere l'accesso ai dati della vecchia applicazione. Questo vale sia per l'aggiornamento delle app utente tramite la sovrascrittura di .apk sia per la sostituzione di un'app di sistema con una versione più recente installata in /data.
    • Se due o più applicazioni vogliono condividere un ID utente (in modo da poter condividere dati e così via), devono essere firmate con la stessa chiave.
  2. I pacchetti di aggiornamento OTA devono essere firmati con una delle chiavi previste dal sistema, altrimenti il processo di installazione li rifiuterà.

Chiavi di release

L'albero di Android include test-keys in build/target/product/security. La creazione di un'immagine del sistema operativo Android utilizzando make firmerà tutti i file .apk utilizzando le chiavi di test. Poiché le chiavi di test sono di dominio pubblico, chiunque può firmare i propri file .apk con le stesse chiavi, il che potrebbe consentire di sostituire o dirottare le app di sistema integrate nell'immagine del sistema operativo. Per questo motivo, è fondamentale firmare qualsiasi immagine del sistema operativo Android rilasciata o implementata pubblicamente con un insieme speciale di chiavi di release a cui solo tu hai accesso.

Per generare il tuo insieme univoco di chiavi di release, esegui questi comandi dalla radice dell'albero di Android:

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 deve essere modificato per riflettere le informazioni della tua organizzazione. Puoi utilizzare qualsiasi directory, ma fai attenzione a scegliere una posizione di cui viene eseguito il backup e che sia sicura. Alcuni fornitori scelgono di criptare la chiave privata con una passphrase efficace e di archiviare la chiave criptata nel controllo del codice sorgente, mentre altri archiviano le chiavi di release in un'altra posizione, ad esempio su un computer isolato dalla rete.

Per generare un'immagine di release, utilizza:

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

Lo script sign_target_files_apks accetta un file .zip di file di destinazione come input e produce un nuovo file .zip di file di destinazione in cui tutti i file .apk sono stati firmati con nuove chiavi. Le immagini appena firmate sono disponibili in IMAGES/ in signed-target_files.zip.

Firmare i pacchetti OTA

Un file zip di file di destinazione firmato può essere convertito in un file zip di aggiornamento OTA firmato utilizzando la seguente procedura:
ota_from_target_files \
-k  (--package_key) 
signed-target_files.zip \
signed-ota_update.zip

Firme e sideloading

Il sideloading non aggira il normale meccanismo di verifica della firma del pacchetto di recovery: prima di installare un pacchetto, la recovery verifica che sia firmato con una delle chiavi private corrispondenti alle chiavi pubbliche archiviate nella partizione di recovery, proprio come farebbe per un pacchetto distribuito via OTA.

I pacchetti di aggiornamento ricevuti dal sistema principale vengono in genere verificati due volte: una volta dal sistema principale, utilizzando il metodo RecoverySystem.verifyPackage() nell'API Android, e poi di nuovo dalla recovery. L'API RecoverySystem controlla la firma rispetto alle chiavi pubbliche archiviate nel sistema principale, nel file /system/etc/security/otacerts.zip (per impostazione predefinita). La recovery controlla la firma rispetto alle chiavi pubbliche archiviate nel disco RAM della partizione di recovery, nel file /res/keys.

Per impostazione predefinita, il file .zip di file di destinazione prodotto dalla build imposta il certificato OTA in modo che corrisponda alla chiave di test. In un'immagine rilasciata, è necessario utilizzare un certificato diverso in modo che i dispositivi possano verificare l'autenticità del pacchetto di aggiornamento. Il passaggio del flag -o a sign_target_files_apks, come mostrato nella sezione precedente, sostituisce il certificato della chiave di test con il certificato della chiave di release dalla directory dei certificati.

In genere, l'immagine di sistema e l'immagine di recovery memorizzano lo stesso insieme di chiavi pubbliche OTA. Aggiungendo una chiave solo all'insieme di chiavi di recovery, è possibile firmare i pacchetti che possono essere installati solo tramite sideloading (supponendo che il meccanismo di download degli aggiornamenti del sistema principale esegua correttamente la verifica rispetto a otacerts.zip). Puoi specificare chiavi aggiuntive da includere solo nella recovery impostando la variabile PRODUCT_EXTRA_RECOVERY_KEYS nella definizione del prodotto:

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

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

In questo modo, la chiave pubblica vendor/yoyodyne/security/tardis/sideload.x509.pem viene inclusa nel file delle chiavi di recovery, in modo che possa installare i pacchetti firmati con essa. Tuttavia, la chiave aggiuntiva non è inclusa in otacerts.zip, quindi i sistemi che verificano correttamente i pacchetti scaricati non richiamano la recovery per i pacchetti firmati con questa chiave.

Certificati e chiavi private

Ogni chiave è disponibile in due file: il certificato, con estensione .x509.pem, e la chiave privata, con estensione .pk8. La chiave privata deve essere tenuta segreta ed è necessaria per firmare un pacchetto. La chiave stessa può essere protetta da una password. Il certificato, al contrario, contiene solo la metà pubblica della chiave, quindi può essere distribuito ampiamente. Viene utilizzato per verificare che un pacchetto sia stato firmato con la chiave privata corrispondente.

La build standard di Android utilizza cinque chiavi, tutte presenti in build/target/product/security:

testkey
Chiave predefinita generica per i pacchetti che non specificano una chiave.
platform
Chiave di test per i pacchetti che fanno parte della piattaforma principale.
shared
Chiave di test per gli elementi condivisi nel processo di casa/contatti.
media
Chiave di test per i pacchetti che fanno parte del sistema media/download.
networkstack
Chiave di test per i pacchetti che fanno parte del sistema di networking. La chiave networkstack viene utilizzata per firmare i file binari progettati come componenti di sistema modulari . Se gli aggiornamenti dei moduli vengono creati separatamente e integrati come prebuild nell'immagine del dispositivo, potrebbe non essere necessario generare una chiave networkstack nell' albero di origine di Android.

I singoli pacchetti specificano una di queste chiavi impostando LOCAL_CERTIFICATE nel file Android.mk. (Se questa variabile non è impostata, viene utilizzata testkey.) Puoi anche specificare una chiave completamente diversa per percorso, ad esempio:

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

LOCAL_CERTIFICATE := device/yoyodyne/security/special

Ora la build utilizza la chiave device/yoyodyne/security/special.{x509.pem,pk8} per firmare SpecialApp.apk. La build può utilizzare solo le chiavi private che sono non protette da password.

Opzioni di firma avanzate

Sostituzione della chiave di firma dell'APK

Lo script di firma sign_target_files_apks funziona sui file di destinazione generati per una build. Tutte le informazioni sui certificati e sulle chiavi private utilizzate in fase di build sono incluse nei file di destinazione. Quando esegui lo script di firma per firmare per la release, le chiavi di firma possono essere sostituite in base al nome della chiave o al nome dell'APK.

Utilizza i flag --key_mapping e --default_key_mappings per specificare la sostituzione delle chiavi in base ai nomi delle chiavi:

  • Il flag --key_mapping src_key=dest_key specifica la sostituzione di una chiave alla volta.
  • Il flag --default_key_mappings dir specifica una directory con cinque chiavi per sostituire tutte le chiavi in build/target/product/security; è equivalente all'utilizzo di --key_mapping cinque volte per specificare i mapping.
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

Utilizza il --extra_apks apk_name1,apk_name2,...=key flag per specificare le sostituzioni delle chiavi di firma in base ai nomi degli APK. Se key viene lasciato vuoto, lo script considera gli APK specificati come pre-firmati.

Per l'ipotetico prodotto tardis, sono necessarie sei chiavi protette da password: cinque per sostituire le cinque in build/target/product/security e una per sostituire la chiave aggiuntiva device/yoyodyne/security/special richiesta da SpecialApp nell'esempio precedente. Se le chiavi si trovassero nei seguenti file:

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

Quindi firmeresti tutte le app in questo modo:

./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

Viene visualizzato quanto segue:

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.

Dopo aver richiesto all'utente le password per tutte le chiavi protette da password, lo script firma nuovamente tutti i file APK nel file .zip di destinazione di input con le chiavi di release. Prima di eseguire il comando, puoi anche impostare la variabile di ambiente ANDROID_PW_FILE su un nome file temporaneo; lo script richiama quindi l'editor per consentirti di inserire le password per tutte le chiavi (questo potrebbe essere un modo più pratico per inserire le password).

Sostituzione della chiave di firma APEX

Android 10 introduce il formato di file APEX per l'installazione di moduli di sistema di livello inferiore. Come spiegato in Firma APEX, ogni file APEX viene firmato con due chiavi: una per l'immagine del mini file system all'interno di un APEX e l' altra per l'intera immagine di sistema APEX.

Quando si firma per la release, le due chiavi di firma per un file APEX vengono sostituite con le chiavi di release. La chiave del payload del file system viene specificata con il flag --extra_apex_payload e la chiave di firma dell'intero file APEX viene specificata con il flag --extra_apks.

Per il prodotto tardis, supponi di avere la seguente configurazione delle chiavi per i file APEX com.android.conscrypt.apex, com.android.media.apex e com.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"

E hai i seguenti file che contengono le chiavi di release:

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

Il comando seguente sostituisce le chiavi di firma per com.android.runtime.release.apex e com.android.tzdata.apex durante la firma della release. In particolare, com.android.runtime.release.apex viene firmato con le chiavi di release specificate (runtime_apex_container per il file APEX e runtime_apex_payload per il payload dell'immagine del file). com.android.tzdata.apex viene trattato come pre-firmato. Tutti gli altri file APEX vengono gestiti dalla configurazione predefinita, come indicato nei file di destinazione.

./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

L'esecuzione del comando precedente genera i seguenti log:

        [...]
    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)
        [...]

Altre opzioni

Lo script di firma sign_target_files_apks riscrive la descrizione della build e la fingerprint nei file delle proprietà della build per indicare che la build è una build firmata. Il flag --tag_changes controlla le modifiche apportate alla fingerprint. Esegui lo script con -h per visualizzare la documentazione su tutti i flag.

Generare manualmente le chiavi

Android utilizza chiavi RSA a 2048 bit con esponente pubblico 3. Puoi generare coppie di certificati/chiavi private utilizzando lo strumento openssl da 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

Il comando openssl pkcs8 riportato sopra crea un file .pk8 senza password, adatto all'uso con il sistema di compilazione. Per creare un file .pk8 protetto da password (cosa che devi fare per tutte le chiavi di release effettive), sostituisci l'argomento -nocrypt con -passout stdin; quindi openssl cripta la chiave privata con una password letta dall'input standard. Non viene stampato alcun prompt, quindi se stdin è il terminale, il programma sembrerà bloccarsi quando in realtà sta solo aspettando che tu inserisca una password. È possibile utilizzare altri valori per l'argomento -passout per leggere la password da altre posizioni; per i dettagli, consulta la documentazione di openssl.

Il file intermedio temp.pem contiene la chiave privata senza alcun tipo di protezione con password, quindi eliminalo con attenzione quando generi le chiavi di release. In particolare, l'utilità GNUshred potrebbe non essere efficace sui file system di rete o con journaling. Puoi utilizzare una directory di lavoro che si trova in un disco RAM (ad esempio una partizione tmpfs) quando generi le chiavi per assicurarti che gli intermedi non vengano esposti inavvertitamente.

Creare file immagine

Quando hai signed-target_files.zip, devi creare l'immagine in modo da poterla inserire in un dispositivo. Per creare l'immagine firmata dai file di destinazione, esegui il seguente comando dalla radice dell'albero di Android:

img_from_target_files signed-target_files.zip signed-img.zip
Il file risultante, signed-img.zip, contiene tutti i file .img. Per caricare un'immagine su un dispositivo, utilizza fastboot come segue:
fastboot update signed-img.zip