Architettura di firma on-device

A partire da Android 12, il modulo Android Runtime (ART) è un modulo Mainline. L'aggiornamento del modulo potrebbe richiedere la ricostruzione degli elementi di compilazione AOT (ahead-of-time) dei file JAR del bootclasspath e del server di sistema. Poiché questi elementi sono sensibili alla sicurezza, Android 12 utilizza una funzionalità chiamata firma sul dispositivo per impedire la loro manomissione. Questa pagina illustra l'architettura della firma on-device e le sue interazioni con altre funzionalità di sicurezza di Android.

Design di alto livello

La firma sul dispositivo ha due componenti principali:

  • odrefresh fa parte del modulo ART Mainline. È responsabile della generazione degli artefatti di runtime. Controlla gli elementi esistenti rispetto alla versione installata del modulo ART, dei file JAR bootclasspath e dei file JAR del server di sistema per determinare se sono aggiornati o se devono essere rigenerati. Se devono essere rigenerati, odrefresh li genera e li memorizza.

  • odsign è un file binario che fa parte della piattaforma Android. Viene eseguito durante la fase iniziale dell'avvio, subito dopo il montaggio della partizione /data. La sua responsabilità principale è chiamare odrefresh per verificare se è necessario generare o aggiornare eventuali elementi. Per tutti gli elementi nuovi o aggiornati generati da odrefresh, odsign calcola una funzione hash. Il risultato di questo calcolo dell'hash è chiamato digest del file. Per gli elementi esistenti, odsign verifica che i digest degli elementi esistenti corrispondano ai digest calcolati in precedenza da odsign. In questo modo, hai la certezza che gli elementi non siano stati manomessi.

In condizioni di errore, ad esempio quando il digest di un file non corrisponde, odrefresh e odsign eliminano tutti gli elementi esistenti su /data e tentamo di rigenerarli. Se non va a buon fine, il sistema torna alla modalità JIT.

odrefresh e odsign sono protetti da dm-verity e fanno parte della catena di avvio verificato di Android.

Calcolo dei digest dei file con fs-verity

fs-verity è una funzionalità del kernel Linux che esegue la verifica dei dati dei file basata sull'albero Merkle. L'attivazione di fs-verity su un file fa sì che il file system costruisca un albero Merkle sui dati del file utilizzando gli hash SHA-256, li memorizzi in una posizione nascosta accanto al file e contrassegni il file come di sola lettura. fs-verity verifica automaticamente i dati del file in base all'albero Merkle su richiesta durante la lettura. fs-verity rende disponibile l'hash principale dell'albero Merkle come valore chiamato digest del file fs-verity e garantisce che tutti i dati letti dal file siano coerenti con questo digest del file.

odsign utilizza fs-verity per migliorare il rendimento dell'avvio ottimizzando l'autenticazione crittografica degli elementi compilati sul dispositivo all'avvio. Quando viene generato un artefatto, odsign attiva fs-verity. Quando odsign verifica un artefatto, verifica il digest del file fs-verity anziché l'hash completo del file. In questo modo non è necessario leggere e sottoporre ad hashing i dati completi dell'elemento al momento dell'avvio. I dati degli elementi vengono invece sottoposti ad hashing su richiesta da fs-verity man mano che vengono utilizzati, su base blocco per blocco.

Sui dispositivi il cui kernel non supporta fs-verity, odsign ricorre al calcolo dei digest dei file nello spazio utente. odsign utilizza lo stesso algoritmo di hashing basato sull'albero Merkle di fs-verity, pertanto i digest sono gli stessi in entrambi i casi. fs-verity è obbligatorio su tutti i dispositivi lanciati con Android 11 e versioni successive.

Archiviazione dei digest dei file

odsign memorizza i digest dei file degli elementi in un file separato chiamato odsign.info. Per assicurarti che odsign.info non venga manomesso, odsign.info viene firmato con una chiave di firma che ha proprietà di sicurezza importanti. In particolare, la chiave può essere generata e utilizzata solo durante l'avvio iniziale, quando è in esecuzione solo il codice attendibile. Per maggiori dettagli, consulta Chiavi di firma attendibile.

Verifica dei digest dei file

A ogni avvio, se odrefresh stabilisce che gli elementi esistenti sono aggiornati, odsign garantisce che i file non siano stati manomessi dal momento della loro generazione. odsign esegue questa operazione verificando i digest dei file. Innanzitutto, verifica la firma di odsign.info. Se la firma è valida, odsign verifica che il digest di ogni file corrisponda al digest corrispondente in odsign.info.

Chiavi di firma attendibili

Android 12 introduce una nuova funzionalità Keystore chiamata boot stage keys che risolve i seguenti problemi di sicurezza:

  • Cosa impedisce a un malintenzionato di utilizzare la nostra chiave di firma per firmare la sua versione di odsign.info?
  • Cosa impedisce a un malintenzionato di generare la propria chiave di firma e di utilizzarla per firmare la propria versione di odsign.info?

Le chiavi della fase di avvio suddividono il ciclo di avvio di Android in livelli e collegano in modo criptato la creazione e l'utilizzo di una chiave a un livello specificato. odsign crea la sua chiave di firma in una fase iniziale, quando è in esecuzione solo il codice attendibile, protetto tramite dm-verity.

I livelli della fase di avvio sono numerati da 0 al numero magico 1000000000. Durante la procedura di avvio di Android, puoi aumentare il livello di avvio impostando una proprietà di sistema da init.rc. Ad esempio, il seguente codice imposta il livello di avvio su 10:

setprop keystore.boot_level 10

I client di Keystore possono creare chiavi associate a un determinato livello di avvio. Ad esempio, se crei una chiave per il livello di avvio 10, questa può essere utilizzata solo quando il dispositivo è in questo livello.

odsign utilizza il livello di avvio 30 e la chiave di firma che crea è associata a questo livello di avvio. Prima di utilizzare una chiave per firmare gli elementi, odsign verifica che la chiave sia associata al livello di avvio 30.

In questo modo vengono evitati i due attacchi descritti in precedenza in questa sezione:

  • Gli utenti malintenzionati non possono utilizzare la chiave generata, perché nel momento in cui un utente malintenzionato ha la possibilità di eseguire codice dannoso, il livello di avvio è aumentato oltre 30 e Keystore rifiuta le operazioni che utilizzano la chiave.
  • Gli utenti malintenzionati non possono creare una nuova chiave perché, nel momento in cui hanno la possibilità di eseguire codice dannoso, il livello di avvio è aumentato oltre 30 e Keystore rifiuta di creare una nuova chiave con quel livello di avvio. Se un malintenzionato crea una nuova chiave non associata al livello di avvio 30, odsign la rifiuta.

Il Keystore garantisce che il livello di avvio venga applicato correttamente. Le sezioni che seguono descrivono in modo più dettagliato come viene eseguita questa operazione per le diverse versioni di Keymaster.

Implementazione di Keymaster 4.0

Versioni diverse di Keymaster gestiscono l'implementazione delle chiavi della fase di avvio in modo diverso. Sui dispositivi con un TEE/Strongbox Keymaster 4.0, Keymaster gestisce l'implementazione come segue:

  1. Al primo avvio, Keystore crea una chiave simmetrica K0 con il MAX_USES_PER_BOOT tag impostato su 1. Ciò significa che la chiave può essere utilizzata solo una volta per avvio.
  2. Durante l'avvio, se il livello di avvio viene aumentato, è possibile generare una nuova chiave per quel livello di avvio da K0 utilizzando una funzione HKDF: Ki+i=HKDF(Ki, "some_fixed_string"). Ad esempio, se passi dal livello di avvio 0 al livello di avvio 10, HKDF viene invocato 10 volte per ricavare K10 da K0.
  3. Quando il livello di avvio cambia, la chiave per il livello di avvio precedente viene cancellata dalla memoria e le chiavi associate ai livelli di avvio precedenti non sono più disponibili.

    La chiave K0 è una chiave MAX_USES_PER_BOOT=1. Ciò significa che è anche impossibile utilizzare questa chiave in un secondo momento durante l'avvio, perché si verifica sempre almeno una transizione di livello di avvio (al livello di avvio finale).

Quando un client Keystore come odsign richiede la creazione di una chiave a livello di booti, il relativo blob viene criptato con la chiave Ki. Poiché Ki non è disponibile dopo il livello di avvio i, questa chiave non può essere creata o decriptata nelle fasi di avvio successive.

Implementazione di Keymaster 4.1 e KeyMint 1.0

Le implementazioni di Keymaster 4.1 e KeyMint 1.0 sono in gran parte uguali all'implementazione di Keymaster 4.0. La differenza principale è che K0 non è una chiave MAX_USES_PER_BOOT, ma una chiave EARLY_BOOT_ONLY, introdotta in Keymaster 4.1. Una chiave EARLY_BOOT_ONLY può essere utilizzata solo durante le prime fasi dell'avvio, quando non è in esecuzione alcun codice non attendibile. Questo fornisce un ulteriore livello di protezione: nell'implementazione di Keymaster 4.0, un malintenzionato che compromette il file system e SELinux può modificare il database Keystore per creare la propria chiave MAX_USES_PER_BOOT=1 con cui firmare gli elementi. Un attacco di questo tipo è impossibile con le implementazioni di Keymaster 4.1 e KeyMint 1.0, perché le chiavi EARLY_BOOT_ONLY possono essere create solo durante l'avvio iniziale.

Componente pubblico delle chiavi di firma attendibili

odsign recupera il componente della chiave pubblica della chiave di firma dall'archivio chiavi. Tuttavia, Keystore non recupera la chiave pubblica dal TEE/SE che contiene la chiave privata corrispondente. Recupera invece la chiave pubblica dal suo database on-disk. Ciò significa che un malintenzionato che compromette il sistema di file potrebbe modificare il database dell'archivio chiavi in modo che contenga una chiave pubblica che fa parte di una coppia di chiavi pubblica/privata sotto il suo controllo.

Per impedire questo attacco, odsign crea un'altra chiave HMAC con lo stesso livello di avvio della chiave di firma. Poi, quando crea la chiave di firma, odsign utilizza questa chiave HMAC per creare una firma della chiave pubblica e la memorizza sul disco. Agli avvii successivi, quando viene recuperata la chiave pubblica della chiave di firma, viene utilizzata la chiave HMAC per verificare che la firma su disco corrisponda alla firma della chiave pubblica recuperata. Se corrispondono, la chiave pubblica è attendibile, perché la chiave HMAC può essere utilizzata solo nei primi livelli di avvio e quindi non può essere stata creata da un malintenzionato.