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 ricompilazione degli artefatti di compilazione ahead-of-time (AOT) dei file JAR bootclasspath e del server di sistema. Poiché questi artefatti sono sensibili alla sicurezza, Android 12 utilizza una funzionalità chiamata firma sul dispositivo per impedire la manomissione di questi artefatti. Questa pagina descrive l'architettura di firma sul dispositivo e le sue interazioni con altre funzionalità di sicurezza di Android.

Progettazione di alto livello

La firma sul dispositivo ha due componenti principali:

  • odrefresh fa parte del modulo Mainline ART. È responsabile della generazione degli artefatti di runtime. Controlla gli artefatti 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 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 l'avvio iniziale, subito dopo il montaggio della partizione /data. La sua responsabilità principale è richiamare odrefresh per verificare se è necessario generare o aggiornare artefatti. Per tutti gli artefatti nuovi o aggiornati che odrefresh genera, odsign calcola una funzione hash. Il risultato di questo calcolo hash è chiamato digest del file. Per gli artefatti già esistenti, odsign verifica che i digest degli artefatti esistenti corrispondano ai digest calcolati in precedenza da odsign. In questo modo, si garantisce che gli artefatti non siano stati manomessi.

In condizioni di errore, ad esempio quando il digest di un file non corrisponde, odrefresh e odsign eliminano tutti gli artefatti esistenti su /data e tentano di rigenerarli. Se l'operazione 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 crei un albero Merkle sui dati del file utilizzando hash SHA-256, lo memorizzi in una posizione nascosta accanto al file e contrassegni il file come di sola lettura. fs-verity verifica automaticamente i dati del file rispetto all'albero Merkle su richiesta durante la lettura. fs-verity rende disponibile l'hash radice 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 le prestazioni di avvio ottimizzando l'autenticazione crittografica degli artefatti compilati sul dispositivo al momento dell'avvio. Quando viene generato un artefatto, odsign abilita 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 calcolare l'hash di tutti i dati dell'artefatto al momento dell'avvio. I dati degli artefatti vengono invece sottoposti ad hashing su richiesta da fs-verity man mano che vengono utilizzati, blocco per blocco.

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

Archiviazione dei digest dei file

odsign memorizza i digest dei file degli artefatti in un file separato denominato odsign.info. Per assicurarsi che odsign.info non sia manomesso, odsign.info è firmato con una chiave di firma che ha importanti proprietà di sicurezza. In particolare, la chiave può essere generata e utilizzata solo durante l'avvio anticipato, momento in cui viene eseguito solo il codice attendibile. Per maggiori dettagli, consulta Chiavi di firma attendibili.

Verifica dei digest dei file

A ogni avvio, se odrefresh determina che gli artefatti esistenti sono aggiornati, odsign garantisce che i file non siano stati manomessi dalla loro generazione. odsign lo fa 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à di Keystore chiamata chiavi di avvio che risolve i seguenti problemi di sicurezza:

  • Che cosa impedisce a un malintenzionato di utilizzare la nostra chiave di firma per firmare la propria versione di odsign.info?
  • Che 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 di avvio dividono il ciclo di avvio di Android in livelli e collegano crittograficamente la creazione e l'utilizzo di una chiave a un livello specifico. odsign crea la propria chiave di firma a un livello iniziale, quando è in esecuzione solo codice attendibile, protetta 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 è al livello di avvio 10.

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 artefatti, odsign verifica che la chiave sia associata al livello di avvio 30.

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

  • Gli autori degli attacchi non possono utilizzare la chiave generata perché, quando hanno la possibilità di eseguire codice dannoso, il livello di avvio è aumentato oltre 30 e Keystore rifiuta le operazioni che utilizzano la chiave.
  • Gli autori degli attacchi non possono creare una nuova chiave perché, quando hanno la possibilità di eseguire codice dannoso, il livello di avvio è aumentato oltre 30 e Keystore si 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.

Keystore garantisce che il livello di avvio venga applicato correttamente. Le sezioni che seguono descrivono in dettaglio come viene eseguita questa operazione per le diverse versioni di KeyMint (in precedenza Keymaster).

Implementazione di Keymaster 4.0

Versioni diverse di Keymaster gestiscono l'implementazione delle chiavi di avvio in modo diverso. Sui dispositivi con TEE/StrongBox Keymaster 4.0, Keymaster gestisce l'implementazione nel seguente modo:

  1. Al primo avvio, Keystore crea una chiave simmetrica K0 con il tag MAX_USES_PER_BOOT 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 richiamato 10 volte per derivare 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 nel livello di avvio i, 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 decrittografata 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 a quelle 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 di avvio, quando non è in esecuzione codice non attendibile. Ciò 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 per firmare gli artefatti. 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 proprio database su disco. Ciò significa che un malintenzionato che compromette il file system 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 una chiave HMAC aggiuntiva con lo stesso livello di avvio della chiave di firma. Poi, quando viene creata la chiave di firma, odsign utilizza questa chiave HMAC per creare una firma della chiave pubblica e la memorizza su disco. Nei riavvii successivi, quando recupera la chiave pubblica della chiave di firma, utilizza la chiave HMAC per verificare che la firma sul disco corrisponda alla firma della chiave pubblica recuperata. Se corrispondono, la chiave pubblica è attendibile perché la chiave HMAC può essere utilizzata solo nei livelli di avvio iniziali e quindi non può essere stata creata da un malintenzionato.