Chiavi avvolte nell'hardware

Come la maggior parte dei software di crittografia di dischi e file, la crittografia dell'archiviazione di Android si basa tradizionalmente sulla presenza di chiavi di crittografia grezze nella memoria di sistema in modo che la crittografia possa essere eseguita. Anche quando la crittografia viene eseguita da hardware dedicato anziché da software, il software generalmente deve comunque gestire le chiavi di crittografia grezza.

Questo tradizionalmente non è visto come un problema perché le chiavi non saranno presenti durante un attacco offline, che è il tipo principale di attacco contro il quale la crittografia dello storage intende proteggere. Tuttavia, esiste il desiderio di fornire una maggiore protezione contro altri tipi di attacchi, come gli attacchi di avvio a freddo e gli attacchi online in cui un utente malintenzionato potrebbe essere in grado di perdere la memoria di sistema senza compromettere completamente il dispositivo.

Per risolvere questo problema, Android 11 ha introdotto il supporto per le chiavi avvolte nell'hardware, dove è presente il supporto hardware. Le chiavi avvolte nell'hardware sono chiavi di archiviazione note solo in forma grezza all'hardware dedicato; il software vede e funziona solo con queste chiavi in ​​formato avvolto (crittografato). Questo hardware deve essere in grado di generare e importare chiavi di archiviazione, avvolgere chiavi di archiviazione in forme effimere ea lungo termine, derivare sottochiavi, programmare direttamente una sottochiave in un motore crittografico in linea e restituire una sottochiave separata al software.

Nota : un motore di crittografia in linea (o hardware di crittografia in linea ) si riferisce all'hardware che crittografa/decodifica i dati mentre è in viaggio verso/dal dispositivo di archiviazione. Di solito si tratta di un controller host UFS o eMMC che implementa le estensioni crittografiche definite dalla corrispondente specifica JEDEC.

Disegno

Questa sezione presenta la progettazione della funzionalità delle chiavi avvolte nell'hardware, incluso il supporto hardware necessario per essa. Questa discussione si concentra sulla crittografia basata su file (FBE), ma la soluzione si applica anche alla crittografia dei metadati .

Un modo per evitare di aver bisogno delle chiavi di crittografia grezze nella memoria di sistema sarebbe tenerle solo negli slot delle chiavi di un motore crittografico in linea. Tuttavia, questo approccio incontra alcuni problemi:

  • Il numero di chiavi di crittografia può superare il numero di slot di chiavi.
  • I motori di crittografia in linea possono essere utilizzati solo per crittografare/decodificare blocchi interi di dati su disco. Tuttavia, nel caso di FBE, il software deve essere ancora in grado di eseguire altri lavori crittografici come la crittografia dei nomi dei file e la derivazione di identificatori di chiavi. Il software avrebbe comunque bisogno dell'accesso alle chiavi FBE grezze per svolgere quest'altro lavoro.

Per evitare questi problemi, le chiavi di archiviazione vengono invece trasformate in chiavi avvolte nell'hardware , che possono essere scartate e utilizzate solo dall'hardware dedicato. Ciò consente di supportare un numero illimitato di chiavi. Inoltre, la gerarchia delle chiavi viene modificata e parzialmente spostata su questo hardware, il che consente di restituire una sottochiave al software per attività che non possono utilizzare un motore crittografico inline.

Gerarchia delle chiavi

Le chiavi possono essere derivate da altre chiavi utilizzando una KDF (funzione di derivazione delle chiavi) come HKDF , risultando in una gerarchia di chiavi .

Il diagramma seguente illustra una tipica gerarchia di chiavi per FBE quando non vengono utilizzate chiavi avvolte nell'hardware:

Gerarchia delle chiavi FBE (standard)
Figura 1. Gerarchia delle chiavi FBE (standard)

La chiave di classe FBE è la chiave di crittografia grezza che Android passa al kernel Linux per sbloccare un particolare set di directory crittografate, come l'archiviazione crittografata dalle credenziali per un particolare utente Android. (Nel kernel, questa chiave è chiamata chiave master fscrypt .) Da questa chiave, il kernel deriva le seguenti sottochiavi:

  • L'identificatore della chiave. Questo non viene utilizzato per la crittografia, ma piuttosto è un valore utilizzato per identificare la chiave con cui è protetto un particolare file o directory.
  • La chiave di crittografia del contenuto del file
  • La chiave di crittografia dei nomi file

Al contrario, il diagramma seguente illustra la gerarchia delle chiavi per FBE quando vengono utilizzate chiavi avvolte nell'hardware:

Gerarchia delle chiavi FBE (con chiave avvolta nell'hardware)
Figura 2. Gerarchia delle chiavi FBE (con chiave avvolta nell'hardware)

Rispetto al caso precedente, è stato aggiunto un livello aggiuntivo alla gerarchia delle chiavi e la chiave di crittografia del contenuto del file è stata riposizionata. Il nodo radice rappresenta ancora la chiave che Android passa a Linux per sbloccare un insieme di directory crittografate. Tuttavia, ora quella chiave è in forma effimera e per essere utilizzata deve essere passata a un hardware dedicato. Questo hardware deve implementare due interfacce che accettano una chiave con wrapping effimero:

  • Un'interfaccia per derivare inline_encryption_key e programmarla direttamente in un keylot del motore di crittografia inline. Ciò consente di crittografare/decodificare il contenuto dei file senza che il software abbia accesso alla chiave grezza. Nei kernel comuni Android, questa interfaccia corrisponde all'operazione blk_ksm_ll_ops::keyslot_program , che deve essere implementata dal driver di archiviazione.
  • Un'interfaccia per derivare e restituire sw_secret ("segreto software" -- chiamato anche "segreto grezzo" in alcuni punti), che è la chiave che Linux usa per derivare le sottochiavi per qualsiasi cosa diversa dalla crittografia dei contenuti dei file. Nei kernel comuni Android, questa interfaccia corrisponde all'operazione blk_ksm_ll_ops::derive_raw_secret , che deve essere implementata dal driver di archiviazione.

Per derivare inline_encryption_key e sw_secret dalla chiave di archiviazione grezza, l'hardware deve utilizzare un KDF crittograficamente forte. Questo KDF deve seguire le migliori pratiche di crittografia; deve avere una forza di sicurezza di almeno 256 bit, cioè sufficiente per qualsiasi algoritmo utilizzato in seguito. Deve inoltre utilizzare un'etichetta distinta, un contesto e/o una stringa di informazioni specifiche dell'applicazione durante la derivazione di ciascun tipo di sottochiave per garantire che le sottochiavi risultanti siano crittograficamente isolate, ovvero la conoscenza di una di esse non ne rivela nessun'altra. L'estensione della chiave non è richiesta, poiché la chiave di archiviazione grezza è già una chiave uniformemente casuale.

Tecnicamente, è possibile utilizzare qualsiasi KDF che soddisfi i requisiti di sicurezza. Tuttavia, a scopo di test, è necessario implementare nuovamente lo stesso KDF nel codice di test. Attualmente, un KDF è stato rivisto e implementato; può essere trovato nel codice sorgente di vts_kernel_encryption_test . Si consiglia all'hardware di utilizzare questo KDF, che utilizza NIST SP 800-108 "KDF in Counter Mode" con AES-256-CMAC come PRF. Nota che per essere compatibili, tutte le parti dell'algoritmo devono essere identiche, inclusa la scelta dei contesti e delle etichette KDF per ogni sottochiave.

Avvolgimento delle chiavi

Per soddisfare gli obiettivi di sicurezza delle chiavi con wrapping hardware, vengono definiti due tipi di wrapping delle chiavi:

  • Wrapping effimero : l'hardware crittografa la chiave grezza utilizzando una chiave generata casualmente ad ogni avvio e non esposta direttamente all'esterno dell'hardware.
  • Wrapping a lungo termine : l'hardware crittografa la chiave grezza utilizzando una chiave univoca e persistente integrata nell'hardware che non è esposta direttamente all'esterno dell'hardware.

Tutte le chiavi passate al kernel Linux per sbloccare l'archiviazione sono avvolte in modo effimero. Ciò garantisce che se un utente malintenzionato è in grado di estrarre una chiave in uso dalla memoria di sistema, tale chiave sarà inutilizzabile non solo fuori dal dispositivo, ma anche sul dispositivo dopo un riavvio.

Allo stesso tempo, Android deve ancora essere in grado di memorizzare una versione crittografata delle chiavi su disco in modo che possano essere sbloccate in primo luogo. Le chiavi grezze funzionerebbero per questo scopo. Tuttavia, è auspicabile che le chiavi non elaborate non siano mai presenti nella memoria di sistema in modo che non possano mai essere estratte per essere utilizzate fuori dal dispositivo, anche se estratte all'avvio. Per questo motivo viene definito il concetto di avvolgimento a lungo termine.

Per supportare la gestione delle chiavi racchiuse in questi due modi diversi, l'hardware deve implementare le seguenti interfacce:

  • Interfacce per generare e importare chiavi di archiviazione, restituendole in forma di wrapping a lungo termine. Queste interfacce sono accessibili indirettamente tramite KeyMint e corrispondono al tag TAG_STORAGE_KEY KeyMint. L'abilità "genera" viene utilizzata da vold per generare nuove chiavi di archiviazione da utilizzare da Android, mentre l'abilità "importa" viene utilizzata da vts_kernel_encryption_test per importare le chiavi di test.
  • Un'interfaccia per convertire una chiave di archiviazione con wrapping a lungo termine in una chiave di archiviazione con wrapping effimero. Ciò corrisponde al metodo convertStorageKeyToEphemeral KeyMint. Questo metodo viene utilizzato sia da vold che da vts_kernel_encryption_test per sbloccare la memoria.

L'algoritmo di wrapping delle chiavi è un dettaglio di implementazione, ma dovrebbe utilizzare un AEAD forte come AES-256-GCM con IV casuali.

Sono necessarie modifiche al software

AOSP dispone già di un framework di base per supportare le chiavi avvolte nell'hardware. Ciò include il supporto nei componenti dello spazio utente come vold , così come il supporto del kernel Linux in blk-crypto , fscrypt e dm-default-key .

Tuttavia, sono necessarie alcune modifiche specifiche dell'implementazione.

KeyMint cambia

L'implementazione di KeyMint del dispositivo deve essere modificata per supportare TAG_STORAGE_KEY e implementare il metodo convertStorageKeyToEphemeral .

In Keymaster è stato utilizzato exportKey invece di convertStorageKeyToEphemeral .

Modifiche al kernel Linux

Il driver del kernel Linux per il motore crittografico inline del dispositivo deve essere modificato per impostare BLK_CRYPTO_FEATURE_WRAPPED_KEYS , fare in modo che le keyslot_program() e keyslot_evict() supportino la programmazione/eliminazione delle chiavi avvolte nell'hardware e implementano l'operazione derive_raw_secret() .

Test

Sebbene la crittografia con chiavi avvolte nell'hardware sia più difficile da testare rispetto alla crittografia con chiavi standard, è comunque possibile eseguire il test importando una chiave di test e implementando nuovamente la derivazione della chiave eseguita dall'hardware. Questo è implementato in vts_kernel_encryption_test . Per eseguire questo test, eseguire:

atest -v vts_kernel_encryption_test

Leggere il registro del test e verificare che i test case delle chiavi con wrapping hardware (ad es FBEPolicyTest.TestAesInlineCryptOptimizedHwWrappedKeyPolicy e DmDefaultKeyTest.TestHwWrappedKey ) non siano stati ignorati a causa del supporto per le chiavi con wrapping hardware non rilevate, poiché i risultati del test verranno comunque "superati" in questo caso.

Abilitare

Una volta che il supporto della chiave avvolta nell'hardware del dispositivo funziona correttamente, puoi apportare le seguenti modifiche al file fstab del dispositivo per fare in modo che Android lo utilizzi per FBE e la crittografia dei metadati:

  • FBE: aggiungi il flag fileencryption wrappedkey_v0 Ad esempio, usa fileencryption=::inlinecrypt_optimized+wrappedkey_v0 . Per maggiori dettagli, vedere la documentazione FBE .
  • Crittografia dei metadati: aggiungi il flag wrappedkey_v0 al parametro metadata_encryption . Ad esempio, usa metadata_encryption=:wrappedkey_v0 . Per maggiori dettagli, vedere la documentazione sulla crittografia dei metadati .