Klucze obudowane sprzętowo

Podobnie jak większość programów do szyfrowania dysków i plików, szyfrowanie pamięci w Androidzie tradycyjnie opiera się na obecności kluczy szyfrowania w pamięci systemowej, aby można było przeprowadzić szyfrowanie. Nawet jeśli szyfrowanie jest wykonywane przez dedykowany sprzęt, a nie przez oprogramowanie, oprogramowanie zwykle nadal musi zarządzać surowymi kluczami szyfrowania.

Zwykle nie jest to problem, ponieważ klucze nie są obecne podczas ataku offline, który jest głównym rodzajem ataku, przed którym ma chronić szyfrowanie pamięci. Istnieje jednak potrzeba zapewnienia większej ochrony przed innymi rodzajami ataków, takimi jak ataki typu cold boot i ataki online, w przypadku których atakujący może wykradać pamięć systemową bez pełnego przejęcia kontroli nad urządzeniem.

Aby rozwiązać ten problem, w Androidzie 11 wprowadzono obsługę kluczy chronionych sprzętowo, w przypadku których dostępna jest obsługa sprzętowa. Klucze opakowane sprzętowo to klucze pamięci, które są znane w formie surowej tylko dedykowanemu sprzętowi. Oprogramowanie widzi te klucze i z nimi pracuje tylko w formie opakowanej (zaszyfrowanej). Ten sprzęt musi mieć możliwość generowania i importowania kluczy pamięci, opakowywania kluczy pamięci w formie tymczasowej i długoterminowej, wyprowadzania kluczy podrzędnych, bezpośredniego programowania jednego klucza podrzędnego w wbudowanym module kryptograficznym oraz zwracania osobnego klucza podrzędnego do oprogramowania.

Uwaga: wbudowany silnik kryptograficzny (lub wbudowany sprzęt do szyfrowania) to sprzęt, który szyfruje i odszyfrowuje dane podczas przesyłania do i z urządzenia pamięci masowej. Zwykle jest to host UFS lub kontroler eMMC, który implementuje rozszerzenia kryptograficzne zdefiniowane w odpowiedniej specyfikacji JEDEC.

Projektowanie

W tej sekcji opisujemy projekt funkcji kluczy zabezpieczonych sprzętowo, w tym wymagania dotyczące obsługi sprzętowej. W tym artykule skupimy się na szyfrowaniu opartym na plikach (FBE), ale to rozwiązanie dotyczy też szyfrowania metadanych.

Jednym ze sposobów na uniknięcie konieczności przechowywania nieprzetworzonych kluczy szyfrowania w pamięci systemowej jest przechowywanie ich tylko w gniazdach kluczy wbudowanego silnika kryptograficznego. Takie podejście ma jednak pewne wady:

  • Liczba kluczy szyfrowania może przekraczać liczbę gniazd kluczy.
  • Wbudowane silniki kryptograficzne zwykle tracą zawartość swoich gniazd kluczy, jeśli kontroler hosta pamięci zostanie zresetowany. Resetowanie kontrolera hosta pamięci to standardowa procedura odzyskiwania po błędach, która jest wykonywana w przypadku wystąpienia określonych rodzajów błędów pamięci. Błędy te mogą wystąpić w dowolnym momencie. Dlatego w przypadku korzystania z kryptografii wbudowanej system operacyjny musi być zawsze gotowy do przeprogramowania gniazd kluczy bez interwencji użytkownika.
  • Wbudowane silniki kryptograficzne mogą być używane tylko do szyfrowania i odszyfrowywania pełnych bloków danych na dysku. W przypadku FBE oprogramowanie musi jednak nadal wykonywać inne operacje kryptograficzne, takie jak szyfrowanie nazw plików i wyprowadzanie identyfikatorów kluczy. Oprogramowanie nadal będzie potrzebować dostępu do surowych kluczy FBE, aby wykonywać inne zadania.

Aby uniknąć tych problemów, klucze pamięci są przekształcane w klucze opakowane w sprzęcie, które mogą być rozpakowywane i używane tylko przez dedykowany sprzęt. Dzięki temu można obsługiwać nieograniczoną liczbę kluczy. Dodatkowo hierarchia kluczy jest modyfikowana i częściowo przenoszona do tego sprzętu, co umożliwia zwracanie oprogramowaniu klucza podrzędnego w przypadku zadań, które nie mogą korzystać z wbudowanego silnika kryptograficznego.

Hierarchia kluczy

Klucze można wyprowadzać z innych kluczy za pomocą funkcji derywacji klucza (KDF), takiej jak HKDF, co tworzy hierarchię kluczy.

Poniższy diagram przedstawia typową hierarchię kluczy w przypadku szyfrowania opartego na plikach, gdy klucze opakowane sprzętowo nie są używane:

Hierarchia kluczy FBE (standardowa)
Rysunek 1. Hierarchia kluczy FBE (standardowa)

Klucz klasy FBE to surowy klucz szyfrowania, który Android przekazuje do jądra systemu Linux, aby odblokować określony zestaw zaszyfrowanych katalogów, takich jak zaszyfrowany za pomocą danych logowania magazyn dla konkretnego użytkownika Androida. (W jądrze ten klucz jest nazywany kluczem głównym fscrypt). Na podstawie tego klucza jądro tworzy te podklucze:

  • Identyfikator klucza. Nie jest on używany do szyfrowania, ale raczej jako wartość służąca do identyfikowania klucza, którym chroniony jest dany plik lub katalog.
  • klucz szyfrowania zawartości pliku,
  • Klucz szyfrowania nazw plików

Na poniższym diagramie przedstawiono hierarchię kluczy w przypadku szyfrowania na poziomie plików, gdy używane są klucze chronione sprzętowo:

Hierarchia kluczy FBE (z kluczem opakowanym sprzętowo)
Rysunek 2. Hierarchia kluczy FBE (z kluczem opakowanym w sprzęcie)

W porównaniu z wcześniejszym przypadkiem do hierarchii kluczy dodano dodatkowy poziom, a klucz szyfrowania zawartości pliku został przeniesiony. Węzeł główny nadal reprezentuje klucz, który Android przekazuje do Linuksa, aby odblokować zestaw zaszyfrowanych katalogów. Teraz jednak klucz jest opakowany w formie efemerycznej i aby można było go użyć, musi zostać przekazany do dedykowanego sprzętu. Ten sprzęt musi implementować 2 interfejsy, które przyjmują klucz opakowany w sposób efemeryczny:

  • Jeden interfejs do wyprowadzania wartości inline_encryption_key i bezpośredniego programowania jej w gnieździe klucza wbudowanego silnika kryptograficznego. Umożliwia to szyfrowanie i odszyfrowywanie zawartości pliku bez dostępu oprogramowania do klucza w formie nieprzetworzonej. W wspólnych jądrach Androida ten interfejs odpowiada operacji blk_crypto_ll_ops::keyslot_program, która musi być zaimplementowana przez sterownik pamięci.
  • Jeden interfejs do uzyskiwania i zwracania sw_secret („software secret” – w niektórych miejscach nazywany też „raw secret”), czyli klucza, którego Linux używa do uzyskiwania kluczy podrzędnych do wszystkiego poza szyfrowaniem zawartości plików. W wspólnych jądrach Androida ten interfejs odpowiada operacji blk_crypto_ll_ops::derive_sw_secret, która musi być zaimplementowana przez sterownik pamięci.

Aby uzyskać klucze inline_encryption_keysw_secret z surowego klucza pamięci, sprzęt musi używać silnej kryptograficznie funkcji KDF. Ten KDF musi być zgodny ze sprawdzonymi metodami kryptograficznymi; musi mieć siłę zabezpieczeń co najmniej 256 bitów, czyli wystarczającą dla każdego algorytmu używanego później. Podczas tworzenia każdego typu klucza podrzędnego musi też używać odrębnej etykiety i kontekstu, aby zagwarantować, że wynikowe klucze podrzędne są kryptograficznie odizolowane, tzn. znajomość jednego z nich nie ujawnia żadnego innego. Nie jest wymagane rozciąganie klucza, ponieważ klucz surowy jest już kluczem losowym o jednolitym rozkładzie.

Technicznie można użyć dowolnej funkcji KDF, która spełnia wymagania dotyczące bezpieczeństwa. Jednak na potrzeby testów vts_kernel_encryption_test implementuje ten sam KDF w oprogramowaniu, aby odtworzyć zaszyfrowany tekst na dysku i sprawdzić, czy jest on prawidłowy. Aby ułatwić testowanie i mieć pewność, że używana jest bezpieczna i sprawdzona funkcja KDF, zalecamy, aby sprzęt implementował domyślną funkcję KDF, którą sprawdza test. W przypadku sprzętu, który używa innego algorytmu KDF, w sekcji Testowanie kluczy opakowanych znajdziesz informacje o tym, jak odpowiednio skonfigurować test.

Opakowywanie klucza

Aby spełnić cele związane z bezpieczeństwem kluczy chronionych sprzętowo, zdefiniowano 2 rodzaje ochrony kluczy:

  • Pakowanie tymczasowe: sprzęt szyfruje klucz surowy za pomocą klucza, który jest generowany losowo przy każdym uruchomieniu i nie jest bezpośrednio udostępniany poza sprzętem.
  • Długoterminowe pakowanie: sprzęt szyfruje klucz w formacie RAW za pomocą unikalnego, trwałego klucza wbudowanego w sprzęt, który nie jest bezpośrednio udostępniany poza nim.

Wszystkie klucze przekazywane do jądra systemu Linux w celu odblokowania pamięci masowej są tymczasowo szyfrowane. Dzięki temu, jeśli atakującemu uda się wyodrębnić z pamięci systemowej używany klucz, nie będzie on mógł go użyć nie tylko poza urządzeniem, ale też na urządzeniu po ponownym uruchomieniu.

Jednocześnie Android musi mieć możliwość przechowywania zaszyfrowanej wersji kluczy na dysku, aby można było je odblokować. Do tego celu można użyć kluczy raw. Warto jednak nigdy nie przechowywać surowych kluczy w pamięci systemowej, aby nie można ich było wyodrębnić i użyć poza urządzeniem, nawet jeśli zostaną wyodrębnione podczas uruchamiania. Z tego powodu zdefiniowano koncepcję długoterminowego opakowania.

Aby obsługiwać zarządzanie kluczami opakowanymi na te 2 sposoby, sprzęt musi implementować te interfejsy:

  • Interfejsy do generowania i importowania kluczy pamięci masowej, które są zwracane w długoterminowej postaci opakowanej. Dostęp do tych interfejsów jest uzyskiwany pośrednio przez KeyMint i odpowiadają one tagowi TAG_STORAGE_KEY KeyMint. Funkcja „generate” jest używana przez vold do generowania nowych kluczy pamięci do użytku przez Androida, a funkcja „import” jest używana przez vold do importowania kluczy testowych.vts_kernel_encryption_test
  • Interfejs do konwertowania długoterminowego klucza pamięci masowej na klucz pamięci masowej opakowany tymczasowo. Odpowiada to metodzie convertStorageKeyToEphemeral KeyMint. Ta metoda jest używana zarówno przez vold, jak i vts_kernel_encryption_test do odblokowywania pamięci.

Algorytm zawijania klucza jest szczegółem implementacji, ale powinien używać silnego algorytmu AEAD, takiego jak AES-256-GCM z losowymi wektorami inicjującymi.

Wymagane zmiany w oprogramowaniu

AOSP ma już podstawowe ramy obsługi kluczy wspieranych sprzętowo. Obejmuje to obsługę w komponentach przestrzeni użytkownika, takich jak vold, a także obsługę w jądrze systemu Linux w blk-crypto, fscryptdm-default-key.

Wymagane są jednak pewne zmiany związane z implementacją.

Zmiany w KeyMint

Implementację KeyMint na urządzeniu należy zmodyfikować, aby obsługiwała TAG_STORAGE_KEY i wdrożyć metodę convertStorageKeyToEphemeral.

W Keymasterze zamiast convertStorageKeyToEphemeral używano exportKey.

Zmiany w jądrze Linuksa

Sterownik jądra Linuksa dla wbudowanego silnika kryptograficznego urządzenia musi zostać zmodyfikowany, aby obsługiwać klucze opakowane sprzętowo.

W przypadku jąder w wersji android14 lub nowszych ustaw BLK_CRYPTO_KEY_TYPE_HW_WRAPPEDblk_crypto_profile::key_types_supported, włącz blk_crypto_ll_ops::keyslot_programblk_crypto_ll_ops::keyslot_evict, aby obsługiwać programowanie i usuwanie kluczy opakowanych w sprzęt, oraz zaimplementuj blk_crypto_ll_ops::derive_sw_secret.

W przypadku jąder android12android13 ustaw BLK_CRYPTO_FEATURE_WRAPPED_KEYSblk_keyslot_manager::features, utwórz blk_ksm_ll_ops::keyslot_programblk_ksm_ll_ops::keyslot_evict, obsługuj programowanie/usuwanie kluczy sprzętowych i wdrażaj blk_ksm_ll_ops::derive_raw_secret.

W przypadku android11 jądra ustaw BLK_CRYPTO_FEATURE_WRAPPED_KEYSkeyslot_manager::features, utwórz keyslot_mgmt_ll_ops::keyslot_programkeyslot_mgmt_ll_ops::keyslot_evict, obsługuj programowanie/usuwanie kluczy opakowanych w sprzęt i wdrażaj keyslot_mgmt_ll_ops::derive_raw_secret.

Testowanie kluczy opakowanych

Chociaż szyfrowanie za pomocą kluczy sprzętowych jest trudniejsze do przetestowania niż szyfrowanie za pomocą kluczy surowych, nadal można je przetestować, importując klucz testowy i ponownie wdrażając wyprowadzanie klucza, które wykonuje sprzęt. Jest ona zaimplementowana w funkcji vts_kernel_encryption_test. Aby przeprowadzić ten test, uruchom:

atest -v vts_kernel_encryption_test

Przeczytaj log testu i sprawdź, czy testy kluczy chronionych sprzętowo (np. FBEPolicyTest.TestAesInlineCryptOptimizedHwWrappedKeyPolicyDmDefaultKeyTest.TestHwWrappedKey) nie zostały pominięte z powodu niewykrycia obsługi kluczy chronionych sprzętowo, ponieważ w takim przypadku wyniki testu są nadal „pozytywne”.

Domyślnie vts_kernel_encryption_test zakłada, że sprzęt implementuje funkcję KDF o nazwie kdf1. Ten algorytm KDF należy do rodziny algorytmów KDF w trybie licznika z dokumentu NIST SP 800-108 i używa AES-256-CMAC jako funkcji pseudolosowej. Więcej informacji o CMAC znajdziesz w specyfikacji CMAC. Podczas tworzenia każdego podklucza funkcja KDF używa określonych kontekstów i etykiet. Sprzęt powinien implementować tę funkcję KDF, w tym dokładny wybór kontekstu, etykiety i formatowania stałego ciągu wejściowego podczas wyprowadzania każdego klucza podrzędnego.

vts_kernel_encryption_test implementuje jednak dodatkowe funkcje KDFkdf2 za pomocą kdf4. Są one równie bezpieczne jak kdf1 i różnią się tylko wyborem kontekstów, etykiet i formatowaniem stałego ciągu wejściowego. Istnieją one tylko po to, aby obsługiwać różne urządzenia.

W przypadku urządzeń, które używają innego KDF, ustaw właściwość systemową ro.crypto.hw_wrapped_keys.kdfPRODUCT_VENDOR_PROPERTIES na nazwę KDF zdefiniowaną w kodzie źródłowym testu. W rezultacie vts_kernel_encryption_test będzie sprawdzać ten KDF zamiast kdf1. Aby na przykład wybrać element kdf2, użyj tego kodu:

PRODUCT_VENDOR_PROPERTIES += ro.crypto.hw_wrapped_keys.kdf=kdf2

W przypadku urządzeń, które używają funkcji KDF nieobsługiwanej przez test, dodaj do testu implementację tej funkcji KDF i nadaj jej unikalną nazwę.

Włącz opakowane klucze

Gdy obsługa klucza sprzętowego na urządzeniu działa prawidłowo, wprowadź w pliku fstab urządzenia te zmiany, aby Android używał go do szyfrowania opartego na plikach i metadanych:

  • FBE: dodaj flagę wrappedkey_v0 do parametru fileencryption. Na przykład użyj elementu fileencryption=::inlinecrypt_optimized+wrappedkey_v0. Więcej informacji znajdziesz w dokumentacji FBE.
  • Szyfrowanie metadanych: dodaj flagę wrappedkey_v0 do parametru metadata_encryption. Na przykład użyj elementu metadata_encryption=:wrappedkey_v0. Więcej informacji znajdziesz w dokumentacji dotyczącej szyfrowania metadanych.