La disponibilità di un ambiente di esecuzione attendibile in un sistema su un chip (SoC) offre ai dispositivi Android l'opportunità di fornire servizi di sicurezza efficaci e basati su hardware al sistema operativo Android, ai servizi di piattaforma e persino alle app di terze parti. Gli sviluppatori che cercano le estensioni specifiche per Android devono rivolgersi a android.security.keystore.
Prima di Android 6.0, Android aveva già un'API di servizi di crittografia semplice basata su hardware, fornita dalle versioni 0.2 e 0.3 dell'HAL (Hardware Abstraction Layer) Keymaster. Il keystore forniva operazioni di firma e verifica digitale, oltre alla generazione e all'importazione di coppie di chiavi di firma asimmetrica. Questa funzionalità è già implementata su molti dispositivi, ma esistono molti obiettivi di sicurezza che non possono essere raggiunti facilmente solo con un'API di firma. Keystore in Android 6.0 ha esteso l'API Keystore per fornire una gamma più ampia di funzionalità.
In Android 6.0, Keystore ha aggiunto primitive crittografiche simmetriche, AES e HMAC, nonché un sistema di controllo dell'accesso per le chiavi basate su hardware. I controlli di accesso vengono specificati durante la generazione della chiave e applicati per tutta la sua durata. Le chiavi possono essere limitate in modo da essere utilizzabili solo dopo l'autenticazione dell'utente e solo per finalità specifiche o con parametri criptici specifici. Per ulteriori informazioni, consulta la pagina Tag di autorizzazione.
Oltre ad ampliare la gamma di primitive crittografiche, Keystore in Android 6.0 ha aggiunto quanto segue:
- Uno schema di controllo dell'utilizzo per consentire di limitare l'utilizzo delle chiavi, al fine di ridurre il rischio di compromissione della sicurezza dovuto all'uso improprio delle chiavi
- Uno schema di controllo dell'accesso per consentire la limitazione delle chiavi a utenti, clienti e a un intervallo di tempo specificati
In Android 7.0, Keymaster 2 ha aggiunto il supporto per l'attestazione delle chiavi e il binding della versione. L'attestazione della chiave fornisce certificati di chiavi pubbliche che contengono una descrizione dettagliata della chiave e dei relativi controlli di accesso, per rendere verificabile da remoto l'esistenza della chiave in hardware sicuro e la relativa configurazione.
La associazione alla versione associa le chiavi al sistema operativo e alla versione del livello di patch. In questo modo, un malintenzionato che scopre una vulnerabilità in una versione precedente del sistema o del software TEE non può ripristinare la versione vulnerabile di un dispositivo e utilizzare le chiavi create con la versione più recente. Inoltre, quando una chiave con una determinata versione e un determinato livello patch viene utilizzata su un dispositivo di cui è stato eseguito l'upgrade a una versione o a un livello patch più recenti, viene eseguito l'upgrade della chiave prima che possa essere utilizzata e la versione precedente della chiave viene invalidata. Durante l'upgrade del dispositivo, le chiavi vengono "incluse" insieme al dispositivo, ma qualsiasi ripristino del dispositivo a una release precedente rende le chiavi inutilizzabili.
In Android 8.0, Keymaster 3 è passato dall'HAL (Hardware Abstraction Layer) con struttura C di vecchio stile all'interfaccia HAL C++ generata da una definizione nel nuovo linguaggio HIDL (Hardware Interface Definition Language). Nell'ambito della modifica, molti dei tipi di argomenti sono cambiati, anche se i tipi e i metodi hanno una corrispondenza uno a uno con i vecchi tipi e i metodi della struttura HAL.
Oltre a questa revisione dell'interfaccia, Android 8.0 ha esteso la funzionalità di attestazione di Keymaster 2 per supportare l'attestazione dell'identità. L'attestazione dell'identità fornisce un meccanismo limitato e facoltativo per attestare in modo affidabile gli identificatori hardware, come il numero di serie del dispositivo, il nome del prodotto e l'ID telefono (IMEI / MEID). Per implementare questa aggiunta, Android 8.0 ha modificato lo schema di attestazione ASN.1 per aggiungere l'attestazione dell'identità. Le implementazioni di Keymaster devono trovare un modo sicuro per recuperare gli elementi di dati pertinenti, nonché definire un meccanismo per disattivare la funzionalità in modo sicuro e permanente.
In Android 9, gli aggiornamenti includevano:
- Eseguire l'aggiornamento a Keymaster 4
- Supporto per Secure Element incorporati
- Supporto per l'importazione di chiavi sicure
- Supporto della crittografia 3DES
- Modifiche al binding delle versioni in modo che boot.img e system.img abbiano versioni impostate separatamente per consentire aggiornamenti indipendenti
Glossario
Ecco una breve panoramica dei componenti del Keystore e delle relative relazioni.
AndroidKeystore è l'API e il componente del framework Android utilizzati dalle app per accedere alla funzionalità Keystore. È implementata come estensione delle API Java Cryptography Architecture standard e consiste in codice Java che viene eseguito nello spazio dei processi dell'app. AndroidKeystore
soddisfa le richieste dell'app per il comportamento dell'archivio chiavi inoltrandole al demone dell'archivio chiavi.
Il demone del keystore è un demone di sistema Android che fornisce accesso a tutte le funzionalità del keystore con un'API Binder. È responsabile della memorizzazione dei "blob di chiavi", che contengono il materiale della chiave segreta criptato in modo che Keystore possa memorizzarli, ma non utilizzarli o rivelarli.
keymasterd è un server HIDL che fornisce l'accesso al TA Keymaster. Questo nome non è standardizzato ed è solo a scopo concettuale.
Keymaster TA (app attendibile) è il software in esecuzione in un contesto sicuro, più spesso in TrustZone su un SoC ARM, che fornisce tutte le operazioni di Keystore sicure, ha accesso al materiale della chiave non elaborato, convalida tutte le condizioni di controllo dell'accesso alle chiavi e così via.
LockSettingsService è il componente di sistema Android responsabile dell'autenticazione dell'utente, sia con password che con impronta. Non fa parte del
Keystore, ma è pertinente perché molte operazioni sulle chiavi del Keystore richiedono l' autenticazione
dell'utente. LockSettingsService
interagisce con il TA Gatekeeper e il TA Fingerprint per ottenere token di autenticazione, che fornisce al daemon del keystore e che vengono infine utilizzati dall'app TA Keymaster.
Gatekeeper TA (app attendibile) è un altro componente in esecuzione nel contesto sicuro, responsabile dell'autenticazione delle password utente e della generazione di token di autenticazione utilizzati per dimostrare a Keymaster TA che è stata eseguita un'autenticazione per un determinato utente in un determinato momento.
L'app attendibile (Fingerprint TA) è un altro componente in esecuzione nel contesto sicuro che si occupa di autenticare le impronte degli utenti e generare token di autenticazione utilizzati per dimostrare all'app attendibile Keymaster che è stata eseguita un'autenticazione per un determinato utente in un determinato momento.
Architettura
L'API Android Keystore e l'HAL Keymaster di base forniscono un insieme di primitive di crittografia di base, ma adeguate, per consentire l'implementazione di protocolli che utilizzano chiavi basate su hardware con controllo dell'accesso.
L'HAL Keymaster è una libreria caricabile dinamicamente fornita dall'OEM utilizzata dal servizio Keystore per fornire servizi di crittografia basati su hardware. Per mantenere la sicurezza, le implementazioni HAL non eseguono operazioni sensibili nello spazio utente o nello spazio kernel. Le operazioni sensibili vengono delegate a un processore sicuro raggiunto tramite un'interfaccia del kernel. L'architettura risultante ha il seguente aspetto:

Figura 1. Accesso a Keymaster
All'interno di un dispositivo Android, il "client" dell'HAL Keymaster è costituito da più livelli (ad esempio app, framework, daemon Keystore), ma può essere ignorato ai fini di questo documento. Ciò significa che l'API HAL Keymaster descritta è di basso livello, utilizzata dai componenti interni alla piattaforma e non esposta agli sviluppatori di app. L'API di livello superiore è descritta sul sito Android for Developers.
Lo scopo dell'HAL Keymaster non è implementare gli algoritmi sensibili alla sicurezza, ma solo eseguire il marshalling e lo smarshalling delle richieste nel mondo sicuro. Il formato del cablaggio è definito dall'implementazione.
Compatibilità con le versioni precedenti
L'HAL Keymaster 1 è completamente incompatibile con gli HAL rilasciati in precedenza, ad esempio Keymaster 0.2 e 0.3. Per semplificare l'interoperabilità sui dispositivi con Android 5.0 e versioni precedenti lanciati con le HAL Keymaster precedenti, Keystore fornisce un'opzione che implementa la HAL Keymaster 1 con chiamate alla libreria hardware esistente. Il risultato non può fornire la gamma completa di funzionalità nell'HAL Keymaster 1. In particolare, supporta solo gli algoritmi RSA ed ECDSA e l'applicazione di tutte le autorizzazioni delle chiavi viene eseguita dall'adattatore in un ambiente non sicuro.
Keymaster 2 ha semplificato ulteriormente l'interfaccia HAL rimuovendo i metodi get_supported_*
e consentendo al metodo finish()
di accettare input. Ciò riduce il numero di viaggi di andata e ritorno al TEE nei casi in cui l'input sia disponibile contemporaneamente e semplifica l'implementazione della decrittografia AEAD.
In Android 8.0, Keymaster 3 è passato dall'HAL con struttura C di vecchio stile all'interfaccia HAL C++ generata da una definizione nel nuovo linguaggio HIDL (Hardware Interface Definition Language). Un'implementazione HAL di nuovo stile viene creata creando una sottoclasse della classe IKeymasterDevice
generata e implementando i metodi virtuali puri. Nell'ambito della modifica, molti dei tipi di argomenti sono cambiati, anche se i tipi e i metodi hanno una corrispondenza uno a uno con i tipi precedenti e i metodi della struttura HAL.
Panoramica di HIDL
HIDL (Hardware Interface Definition Language) fornisce un meccanismo di implementazione indipendente dal linguaggio per specificare le interfacce hardware. Lo strumento HIDL supporta attualmente la generazione di interfacce C++ e Java. Prevediamo che la maggior parte degli implementatori di Trusted Execution Environment (TEE) troverà lo strumento C++ più pratico, pertanto in questa pagina viene discussa solo la rappresentazione C++.
Le interfacce HIDL sono costituite da un insieme di metodi, espressi come:
methodName(INPUT ARGUMENTS) generates (RESULT ARGUMENTS);
Esistono vari tipi predefiniti e gli HAL possono definire nuovi tipi enumerati e di struttura. Per ulteriori dettagli su HIDL, consulta la sezione di riferimento.
Un metodo di esempio di Keymaster 3 IKeymasterDevice.hal
è:
generateKey(vec<KeyParameter> keyParams) generates(ErrorCode error, vec<uint8_t> keyBlob, KeyCharacteristics keyCharacteristics);
Questo è l'equivalente del seguente codice dell'HAL keymaster2:
keymaster_error_t (*generate_key)( const struct keymaster2_device* dev, const keymaster_key_param_set_t* params, keymaster_key_blob_t* key_blob, keymaster_key_characteristics_t* characteristics);
Nella versione HIDL, l'argomento dev
viene rimosso perché è implicito. L'argomento params
non è più una struttura contenente un
puntatore che fa riferimento a un array di oggetti key_parameter_t
, ma un
vec
(vettore) contenente oggetti KeyParameter
. I valori di ritorno sono elencati nella clausola "generates
", incluso un vettore di valori uint8_t
per il blob della chiave.
Il metodo virtuale C++ generato dal compilatore HIDL è:
Return<void> generateKey(const hidl_vec<KeyParameter>& keyParams, generateKey_cb _hidl_cb) override;
dove generateKey_cb
è un puntatore di funzione definito come:
std::function<void(ErrorCode error, const hidl_vec<uint8_t>& keyBlob, const KeyCharacteristics& keyCharacteristics)>
In altre parole, generateKey_cb
è una funzione che prende i valori di ritorno elencati nella clausola generate. La classe di implementazione HAL sostituisce questo metodo generateKey
e chiama il puntatore della funzione generateKey_cb
per restituire il risultato dell'operazione all'autore della chiamata. Tieni presente che la chiamata del cursore della funzione è sincrona. Il chiamante chiama generateKey
e generateKey
chiama il puntatore funzione fornito, che viene eseguito fino al completamento, restituendo il controllo all'implementazione di generateKey
, che poi ritorna al chiamante.
Per un esempio dettagliato, consulta l'implementazione predefinita in
hardware/interfaces/keymaster/3.0/default/KeymasterDevice.cpp
.
L'implementazione predefinita fornisce la compatibilità con le versioni precedenti per i dispositivi con HAL keymaster0, keymaster1 o keymaster2 di tipo precedente.
Controllo dell'accesso
La regola di base del controllo dell'accesso al Keystore è che ogni app ha il suo proprio spazio dei nomi. Tuttavia, per ogni regola esiste un'eccezione. Il Keystore contiene alcune mappe predefinite che consentono a determinati componenti di sistema di accedere ad altri spazi dei nomi. Si tratta di uno strumento molto diretto in quanto offre a un componente il pieno controllo su un altro spazio dei nomi. Inoltre, c'è la questione dei componenti del fornitore come client di Keystore. Al momento non abbiamo modo di stabilire un ambito per i componenti del fornitore, ad esempio WPA supplicant.
Per supportare i componenti del fornitore e generalizzare il controllo dell'accesso senza eccezioni hardcoded, Keystore 2.0 introduce domini e spazi dei nomi SELinux.
Domini dell'archivio chiavi
Con i domini del keystore, possiamo disaccoppiare gli spazi dei nomi dagli UID. I client che accedono a una chiave nel Keystore devono specificare il dominio, lo spazio dei nomi e l'alias a cui vogliono accedere. In base a questa tupla e all'identità dell'utente che effettua la chiamata, possiamo determinare a quale chiave vuole accedere e se dispone delle autorizzazioni appropriate.
Vengono introdotti cinque parametri di dominio che regolano il modo in cui è possibile accedere alle chiavi. Controllano la semantica del parametro dello spazio dei nomi del descrittore della chiave e la modalità di esecuzione del controllo dell'accesso.
DOMAIN_APP
: il dominio dell'app copre il comportamento legacy. L'API SPI Java Keystore utilizza questo dominio per impostazione predefinita. Quando viene utilizzato questo dominio, l'argomento dello spazio dei nomi viene ignorato e viene utilizzato l'UID dell'utente chiamante. L'accesso a questo dominio è controllato dall'etichetta del keystore per la classekeystore_key
nel criterio SELinux.DOMAIN_SELINUX
: questo dominio indica che lo spazio dei nomi ha un'etichetta nel criterio SELinux. Il parametro dello spazio dei nomi viene cercato e tradotto in un contesto di destinazione e viene eseguito un controllo delle autorizzazioni per il contesto SELinux chiamante per la classekeystore_key
. Una volta stabilita l'autorizzazione per l'operazione specificata, viene utilizzata la tupla completa per la ricerca della chiave.DOMAIN_GRANT
: il dominio della concessione indica che il parametro dello spazio dei nomi è un identificatore della concessione. Il parametro alias viene ignorato. I controlli SELinux vengono eseguiti al momento della creazione della concessione. Un ulteriore controllo dell'accesso verifica solo se l'UID dell'utente chiamante corrisponde all'UID dei beneficiari della concessione richiesta.DOMAIN_KEY_ID
: questo dominio indica che il parametro namespace è un ID chiave univoco. La chiave stessa potrebbe essere stata creata conDOMAIN_APP
oDOMAIN_SELINUX
. Il controllo delle autorizzazioni viene eseguito dopo chedomain
enamespace
sono stati caricati dal database delle chiavi nello stesso modo in cui il blob viene caricato dalla tupla di dominio, spazio dei nomi e alias. Il motivo del dominio ID chiave è la continuità. Quando accedi a una chiave tramite alias, le chiamate successive possono operare su diverse chiavi, perché potrebbe essere stata generata o importata una nuova chiave e associata a questo alias. L'ID chiave, invece, non cambia mai. Pertanto, quando si utilizza una chiave tramite l'ID chiave dopo che è stata caricata dal database Keystore utilizzando l'alias una volta, si può essere certi che si tratta della stessa chiave finché l'ID chiave esiste ancora. Questa funzionalità non è esposta agli sviluppatori di app. Viene invece utilizzato all'interno dell'API SPI Android Keystore per offrire un'esperienza più coerente anche se utilizzato contemporaneamente in modo non sicuro.DOMAIN_BLOB
: il dominio del blob indica che l'autore della chiamata gestisce il blob autonomamente. Viene utilizzato per i client che devono accedere al Keystore prima del montaggio della partizione di dati. Il blob della chiave è incluso nel campoblob
del descrittore della chiave.
Utilizzando il dominio SELinux, possiamo concedere ai componenti del fornitore l'accesso a spazi dei nomi Keystore molto specifici che possono essere condivisi dai componenti di sistema come la finestra di dialogo delle impostazioni.
Criterio SELinux per keystore_key
Le etichette dello spazio dei nomi vengono configurate utilizzando il file keystore2_key_context
.
Ogni riga di questi file mappa un ID spazio dei nomi numerico a un'etichetta SELinux.
Ad esempio,
# wifi_key is a keystore2_key namespace intended to be used by wpa supplicant and # Settings to share keystore keys. 102 u:object_r:wifi_key:s0
Dopo aver configurato un nuovo spazio dei nomi delle chiavi in questo modo, possiamo concedervi l'accesso aggiungendo un criterio appropriato. Ad esempio, per consentire a wpa_supplicant
di recuperare e utilizzare le chiavi nel nuovo spazio dei nomi, aggiungeremo la seguente riga a wpa_supplicant
:hal_wifi_supplicant.te
allow hal_wifi_supplicant wifi_key:keystore2_key { get, use };
Dopo aver configurato il nuovo spazio dei nomi, AndroidKeyStore può essere utilizzato quasi come al solito. L'unica differenza è che è necessario specificare l'ID dello spazio dei nomi. Per caricare e importare le chiavi da e verso il keystore, l'ID dello spazio dei nomi viene specificato utilizzando AndroidKeyStoreLoadStoreParameter
. Ad esempio,
import android.security.keystore2.AndroidKeyStoreLoadStoreParameter; import java.security.KeyStore; KeyStore keystore = KeyStore.getInstance("AndroidKeyStore"); keystore.load(new AndroidKeyStoreLoadStoreParameter(102));
Per generare una chiave in un determinato spazio dei nomi, l'ID dello spazio dei nomi deve essere fornito utilizzando KeyGenParameterSpec.Builder#setNamespace():
import android.security.keystore.KeyGenParameterSpec; KeyGenParameterSpec.Builder specBuilder = new KeyGenParameterSpec.Builder(); specBuilder.setNamespace(102);
I seguenti file di contesto possono essere utilizzati per configurare gli spazi dei nomi SELinux di Keystore 2.0. Ogni partizione ha un intervallo riservato diverso di 10.000 ID ambito per evitare collisioni.
Partizione | Intervallo | File di configurazione |
---|---|---|
Sistema | 0 ... 9999 | /system/etc/selinux/keystore2_key_contexts, /plat_keystore2_key_contexts |
Sistema esteso | 10.000-19.999 | /system_ext/etc/selinux/system_ext_keystore2_key_contexts, /system_ext_keystore2_key_contexts |
Prodotto | 20.000 ... 29.999 | /product/etc/selinux/product_keystore2_key_contexts, /product_keystore2_key_contexts |
Fornitore | 30.000 ... 39.999 | /vendor/etc/selinux/vendor_keystore2_key_contexts, /vendor_keystore2_key_contexts |
Il client richiede la chiave richiedendo il dominio SELinux e lo spazio dei nomi virtuali preferito, in questo caso "wifi_key"
, tramite il relativo ID numerico.
Al di sopra di questo, sono stati definiti i seguenti spazi dei nomi. Se sostituiscono regole speciali, la tabella seguente indica l'UID a cui corrispondevano in precedenza.
ID spazio dei nomi | SEPolicy Label | UID | Descrizione |
---|---|---|---|
0 | su_key | N/D | Chiave superutente. Utilizzato solo per i test sulle build userdebug ed eng. Non pertinente per le build utente. |
1 | shell_key | N/D | Spazio dei nomi disponibile per la shell. Utilizzato principalmente per i test, ma può essere utilizzato anche per le compilazioni utente dalla riga di comando. |
100 | vold_key | N/D | Da utilizzare da vold. |
101 | odsing_key | N/D | Utilizzato dal daemon di firma on-device. |
102 | wifi_key | AID_WIFI(1010) | Utilizzato dal sottosistema Wi-Fi di Android, incluso wpa_supplicant. |
120 | resume_on_reboot_key | AID_SYSTEM(1000) | Utilizzato dal server di sistema di Android per supportare la ripresa al riavvio. |
Vettori di accesso
La classe SELinux keystore_key
è piuttosto obsoleta e alcune delle autorizzazioni, come verify
o sign
, hanno perso il loro significato. Ecco il nuovo insieme di autorizzazioni, keystore2_key
,
che viene applicato da Keystore 2.0.
Autorizzazione | Significato |
---|---|
delete
|
Selezionata quando si rimuovono le chiavi dall'archivio chiavi. |
get_info
|
Viene selezionato quando vengono richiesti i metadati di una chiave. |
grant
|
L'utente che chiama ha bisogno di questa autorizzazione per creare una concessione alla chiave nel contesto di destinazione. |
manage_blob
|
L'utente che chiama può utilizzare DOMAIN_BLOB nello spazio dei nomi SELinux specificato,
gestendo così i blob autonomamente. Questo è particolarmente utile per
vold. |
rebind
|
Questa autorizzazione controlla se un alias può essere ricollegato a una nuova chiave. Questo è obbligatorio per l'inserimento e implica che la chiave precedentemente associata venga eliminata. Si tratta di un'autorizzazione di inserimento, ma descrive meglio la semantica del keystore. |
req_forced_op
|
I client con questa autorizzazione possono creare operazioni non potabili e la creazione di operazioni non va mai a buon fine, a meno che tutti gli slot di operazioni non siano occupati da operazioni non potabili. |
update
|
Obbligatorio per aggiornare il sottocomponente di una chiave. |
use
|
Viene selezionato quando viene creata un'operazione Keymint che utilizza il materiale della chiave, ad esempio per la firma, la crittografia e la decrittografia. |
use_dev_id
|
Obbligatorio per la generazione di informazioni di identificazione del dispositivo, come l'attestazione dell'ID dispositivo. |
Inoltre, abbiamo suddiviso un insieme di autorizzazioni del keystore non specifiche per le chiavi nella classe di sicurezza SELinux keystore2
:
Autorizzazione | Significato |
---|---|
add_auth
|
Obbligatorio per il provider di autenticazione, ad esempio Gatekeeper o BiometricsManager, per aggiungere token di autenticazione. |
clear_ns
|
In precedenza clear_uid, questa autorizzazione consente a un utente diverso dal proprietario di uno spazio dei nomi di eliminare tutte le chiavi in quello spazio. |
list
|
Obbligatorio per il sistema per l'enumerazione delle chiavi in base a varie proprietà, come la proprietà o la delimitazione dell'autenticazione. Questa autorizzazione non è richiesta dagli utenti che elencano i propri spazi dei nomi. Questa operazione è coperta dall'autorizzazione get_info . |
lock
|
Questa autorizzazione consente di bloccare il Keystore, ovvero di espellere la chiave principale, in modo che le chiavi legate all'autenticazione diventino inutilizzabili e non creabili. |
reset
|
Questa autorizzazione consente di reimpostare il Keystore sui valori predefiniti di fabbrica, eliminando tutte le chiavi non essenziali per il funzionamento del sistema operativo Android. |
unlock
|
Questa autorizzazione è necessaria per tentare di sbloccare la chiave principale per le chiavi legate all'autenticazione. |