硬件支持的密鑰庫

片上系統 (SoC) 中可信執行環境的可用性為 Android 設備提供了向 Android 操作系統、平台服務甚至第三方應用程序提供硬件支持的強大安全服務的機會。尋求 Android 特定擴展的開發人員應該去android.security.keystore

在 Android 6.0 之前,Android 已經有一個簡單的、硬件支持的加密服務 API,由 Keymaster 硬件抽象層 (HAL) 的 0.2 和 0.3 版本提供。 Keystore 提供數字簽名和驗證操作,以及非對稱簽名密鑰對的生成和導入。這已經在許多設備上實現,但有許多安全目標無法僅通過簽名 API 輕鬆實現。 Android 6.0 中的 Keystore 擴展了 Keystore API 以提供更廣泛的功能。

在 Android 6.0 中,Keystore 添加了對稱加密原語、AES 和 HMAC,以及硬件支持密鑰的訪問控制系統。訪問控制是在密鑰生成期間指定的,並在密鑰的生命週期內強制執行。可以將密鑰限制為僅在用戶通過身份驗證後才可用,並且只能用於指定目的或具有指定的加密參數。有關詳細信息,請參閱授權標籤功能頁面。

除了擴展加密原語的範圍外,Android 6.0 中的 Keystore 添加了以下內容:

  • 一種允許密鑰使用受到限制的使用控制方案,以減輕由於濫用密鑰而導致安全受損的風險
  • 一種訪問控制方案,可將密鑰限制為指定的用戶、客戶端和定義的時間範圍

在 Android 7.0 中,Keymaster 2 添加了對密鑰證明和版本綁定的支持。密鑰證明提供包含密鑰詳細描述及其訪問控制的公鑰證書,以使密鑰在安全硬件中的存在及其配置可遠程驗證。

版本綁定將密鑰綁定到操作系統和補丁級別版本。這確保了在舊版本系統或 TEE 軟件中發現漏洞的攻擊者無法將設備回滾到易受攻擊的版本並使用使用較新版本創建的密鑰。此外,在升級到更新版本或補丁級別的設備上使用具有給定版本和補丁級別的密鑰時,該密鑰將在可以使用之前進行升級,並且先前版本的密鑰失效。隨著設備的升級,密鑰會隨著設備“棘輪”向前移動,但是任何將設備恢復到先前版本的操作都會導緻密鑰無法使用。

在 Android 8.0 中,Keymaster 3 從舊式 C 結構硬件抽象層 (HAL) 轉換為根據新硬件接口定義語言 (HIDL) 中的定義生成的 C++ HAL 接口。作為更改的一部分,許多參數類型發生了更改,儘管類型和方法與舊類型和 HAL 結構方法具有一一對應的關係。有關詳細信息,請參閱功能頁面。

除了此次接口改版,Android 8.0 還擴展了 Keymaster 2 的認證功能,支持ID 認證。 ID 證明提供了一種有限且可選的機制,用於對硬件標識符進行強證明,例如設備序列號、產品名稱和電話 ID (IMEI / MEID)。為了實現這一添加,Android 8.0 更改了 ASN.1 證明架構以添加 ID 證明。 Keymaster 實現需要找到一些安全的方法來檢索相關數據項,並定義一種機制來安全地永久禁用該功能。

在 Android 9 中,更新包括:

  • 更新到Keymaster 4
  • 支持嵌入式安全元件
  • 支持安全密鑰導入
  • 支持 3DES 加密
  • 更改版本綁定,以便 boot.img 和 system.img 分別設置版本以允許獨立更新

詞彙表

這是 Keystore 組件及其關係的快速概述。

AndroidKeystore是應用程序用來訪問 Keystore 功能的 Android 框架 API 和組件。它是作為標準 Java Cryptography Architecture API 的擴展實現的,由在應用程序自己的進程空間中運行的 Java 代碼組成。 AndroidKeystore通過將它們轉發到 keystore 守護程序來滿足應用程序對 Keystore 行為的請求。

keystore 守護進程是一個 Android 系統守護進程,它通過Binder API提供對所有 Keystore 功能的訪問。它負責存儲“密鑰塊”,其中包含實際的密鑰材料,加密後 Keystore 可以存儲它們,但不能使用或洩露它們。

keymasterd是一個 HIDL 服務器,可提供對 Keymaster TA 的訪問。 (此名稱未標準化,僅用於概念目的。)

Keymaster TA (受信任的應用程序)是在安全環境中運行的軟件,通常在 ARM SoC 上的 TrustZone 中運行,它提供所有安全密鑰庫操作,可以訪問原始密鑰材料,驗證密鑰的所有訪問控制條件, 等等。

LockSettingsService是負責用戶身份驗證的 Android 系統組件,包括密碼和指紋。它不是 Keystore 的一部分,但很重要,因為許多 Keystore 密鑰操作需要用戶身份驗證。 LockSettingsService與 Gatekeeper TA 和 Fingerprint TA 交互以獲得身份驗證令牌,它提供給 keystore 守護程序,最終由 Keymaster TA 應用程序使用。

Gatekeeper TA (受信任的應用程序)是在安全上下文中運行的另一個組件,它負責驗證用戶密碼並生成驗證令牌,用於向 Keymaster TA 證明在特定時間點對特定用戶進行了驗證。

指紋 TA (受信任的應用程序)是在安全上下文中運行的另一個組件,它負責驗證用戶指紋並生成驗證令牌,用於向 Keymaster TA 證明在特定時間點對特定用戶進行了驗證。

建築學

Android Keystore API 和底層 Keymaster HAL 提供了一組基本但足夠的加密原語,以允許使用受訪問控制的硬件支持的密鑰來實現協議。

Keymaster HAL 是 OEM 提供的動態可加載庫,Keystore 服務使用它來提供硬件支持的加密服務。為了保證安全,HAL 實現不會在用戶空間甚至內核空間執行任何敏感操作。敏感操作被委託給通過某些內核接口訪問的安全處理器。生成的架構如下所示:

訪問 Keymaster

圖 1.訪問 Keymaster

在 Android 設備中,Keymaster HAL 的“客戶端”由多個層(例如應用程序、框架、Keystore 守護程序)組成,但在本文檔中可以忽略。這意味著所描述的 Keymaster HAL API 是低級的,由平台內部組件使用,並且不向應用程序開發人員公開。 Android 開發者網站上描述了更高級別的 API。

Keymaster HAL 的目的不是實現對安全敏感的算法,而只是對安全世界的請求進行編組和解組。有線格式是實現定義的。

與以前版本的兼容性

Keymaster 1 HAL 與之前發布的 HAL 完全不兼容,例如 Keymaster 0.2 和 0.3。為了促進運行 Android 5.0 及更早版本並與舊版 Keymaster HAL 一起啟動的設備上的互操作性,Keystore 提供了一個適配器,該適配器通過調用現有硬件庫來實現 Keymaster 1 HAL。結果無法提供 Keymaster 1 HAL 中的全部功能。特別是,它只支持 RSA 和 ECDSA 算法,並且所有的密鑰授權執行都是由適配器在非安全世界中執行的。

Keymaster 2 通過刪除get_supported_*方法並允許finish()方法接受輸入,進一步簡化了 HAL 接口。這減少了在輸入同時可用的情況下到 TEE 的往返次數,並簡化了 AEAD 解密的實現。

在 Android 8.0 中,Keymaster 3 從舊式 C 結構 HAL 轉換為根據新硬件接口定義語言 (HIDL) 中的定義生成的 C++ HAL 接口。通過對生成的IKeymasterDevice類進行子類化並實現純虛方法來創建新型​​ HAL 實現。作為更改的一部分,許多參數類型已更改,儘管類型和方法與舊類型和 HAL 結構方法具有一對一的對應關係。

HIDL 概述

硬件接口定義語言 (HIDL) 提供了一種獨立於實現語言的機制來指定硬件接口。 HIDL 工具目前支持生成 C++ 和 Java 接口。預計大多數可信執行環境 (TEE) 實施者會發現 C++ 工具更方便,因此本文檔僅討論 C++ 表示。

HIDL 接口由一組方法組成,表示為:

  methodName(INPUT ARGUMENTS) generates (RESULT ARGUMENTS);

有多種預定義類型,HAL 可以定義新的枚舉和結構類型。有關 HIDL 的更多詳細信息,請參閱參考部分

Keymaster 3 IKeymasterDevice.hal的示例方法是:

generateKey(vec<KeyParameter> keyParams)
        generates(ErrorCode error, vec<uint8_t> keyBlob,
                  KeyCharacteristics keyCharacteristics);

這相當於 keymaster2 HAL 中的以下內容:

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);

在 HIDL 版本中, dev參數被刪除,因為它是隱式的。 params參數不再是包含引用key_parameter_t對像數組的指針的結構,而是包含KeyParameter對象的vec (向量)。返回值列在“ generates ”子句中,包括鍵 blob 的uint8_t值向量。

HIDL 編譯器生成的 C++ 虛方法為:

Return<void> generateKey(const hidl_vec<KeyParameter>& keyParams,
                         generateKey_cb _hidl_cb) override;

其中generateKey_cb是一個函數指針,定義為:

std::function<void(ErrorCode error, const hidl_vec<uint8_t>& keyBlob,
                   const KeyCharacteristics& keyCharacteristics)>

也就是說, generateKey_cb是一個函數,它採用 generate 子句中列出的返回值。 HAL實現類重寫了這個generateKey方法,調用generateKey_cb函數指針,將操作結果返回給調用者。注意函數指針調用是同步的。調用者調用generateKey並且generateKey調用提供的函數指針,該函數指針執行完成,將控制權返回給generateKey實現,然後返回給調用者。

有關詳細示例,請參閱hardware/interfaces/keymaster/3.0/default/KeymasterDevice.cpp中的默認實現。默認實現為具有舊式 keymaster0、keymaster1 或 keymaster2 HALS 的設備提供向後兼容性。

訪問控制

Keystore 訪問控制最基本的規則是每個應用程序都有自己的命名空間。但是每條規則都有一個例外。 Keystore 有一些硬編碼的映射,允許某些系統組件訪問某些其他命名空間。這是一個非常直接的工具,因為它讓一個組件完全控制另一個命名空間。然後是供應商組件作為 Keystore 的客戶端的問題。我們目前無法為供應商組件(例如 WPA supplicant)建立命名空間。

為了適應供應商組件並在沒有硬編碼異常的情況下通用訪問控制,Keystore 2.0 引入了域和 SELinux 命名空間。

密鑰庫域

使用 Keystore 域,我們可以將命名空間與 UID 分離。訪問 Keystore 中的密鑰的客戶端必須指定他們想要訪問的域、命名空間和別名。根據這個元組和調用者的身份,我們可以確定調用者想要訪問哪個密鑰以及它是否具有適當的權限。

我們引入了五個域參數來控制如何訪問密鑰。它們控制鍵描述符的命名空間參數的語義以及如何執行訪問控制。

  • DOMAIN_APP :應用程序域涵蓋遺留行為。 Java Keystore SPI 默認使用這個域。使用此域時,將忽略命名空間參數,而是使用調用者的 UID。對該域的訪問由 SELinux 策略中的keystore_key類的 Keystore 標籤控制。
  • DOMAIN_SELINUX :該域表示命名空間在 SELinux 策略中具有標籤。查找命名空間參數並將其轉換為目標上下文,並對keystore_key類的調用 SELinux 上下文執行權限檢查。為給定操作建立權限後,將使用完整元組進行鍵查找。
  • DOMAIN_GRANT :授權域指示命名空間參數是授權標識符。別名參數被忽略。 SELinux 檢查是在創建授權時執行的。進一步的訪問控制僅檢查調用者 UID 是否與請求的授權的被授權者 UID 匹配。
  • DOMAIN_KEY_ID :這個域表明命名空間參數是一個唯一的key id。密鑰本身可能是使用DOMAIN_APPDOMAIN_SELINUX創建的。權限檢查是在從密鑰數據庫加載domainnamespace後執行的,就像 blob 由域、命名空間和別名元組加載一樣。關鍵 id 域的基本原理是連續性。通過別名訪問密鑰時,後續調用可能對不同的密鑰進行操作,因為可能已生成或導入新密鑰並綁定到此別名。但是,密鑰 ID 永遠不會改變。因此,在使用別名從 Keystore 數據庫加載一次後,通過 key id 使用 key 時,只要 key id 仍然存在,就可以確定它是同一個 key。此功能不向應用程序開發人員公開。相反,它在 Android Keystore SPI 中使用以提供更一致的體驗,即使以不安全的方式同時使用也是如此。
  • DOMAIN_BLOB :blob 域表示調用者自己管理 blob。這用於需要在掛載數據分區之前訪問 Keystore 的客戶端。密鑰 blob 包含在密鑰描述符的blob字段中。

使用 SELinux 域,我們可以讓供應商組件訪問非常特定的 Keystore 命名空間,這些命名空間可由系統組件(例如設置對話框)共享。

keystore_key 的 SELinux 策略

命名空間標籤是使用keystore2_key_context文件配置的。
這些文件中的每一行都將一個數字命名空間 id 映射到一個 SELinux 標籤。例如,

# 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

以這種方式設置新的密鑰命名空間後,我們可以通過添加適當的策略來授予對它的訪問權限。例如,為了允許wpa_supplicant獲取和使用新命名空間中的密鑰,我們將以下行添加到hal_wifi_supplicant.te

allow hal_wifi_supplicant wifi_key:keystore2_key { get, use };

設置新命名空間後,AndroidKeyStore 幾乎可以像往常一樣使用。唯一的區別是必須指定命名空間 ID。為了在 Keystore 中加載和導入密鑰,命名空間 id 使用AndroidKeyStoreLoadStoreParameter指定。例如,

import android.security.keystore2.AndroidKeyStoreLoadStoreParameter;
import java.security.KeyStore;

KeyStore keystore = KeyStore.getInstance("AndroidKeyStore");
keystore.load(new AndroidKeyStoreLoadStoreParameter(102));

要在給定命名空間中生成密鑰,必須使用KeyGenParameterSpec.Builder#setNamespace():

import android.security.keystore.KeyGenParameterSpec;
KeyGenParameterSpec.Builder specBuilder = new KeyGenParameterSpec.Builder();
specBuilder.setNamespace(102);

以下上下文文件可用於配置 Keystore 2.0 SELinux 命名空間。每個分區都有 10,000 個命名空間 ID 的不同保留範圍,以避免衝突。

劃分範圍配置文件
系統0 ... 9,999
/system/etc/selinux/keystore2_key_contexts, /plat_keystore2_key_contexts
擴展系統10,000 ... 19,999
/system_ext/etc/selinux/system_ext_keystore2_key_contexts, /system_ext_keystore2_key_contexts
產品20,000 ... 29,999
/product/etc/selinux/product_keystore2_key_contexts, /product_keystore2_key_contexts
小販30,000 ... 39,999
/vendor/etc/selinux/vendor_keystore2_key_contexts, /vendor_keystore2_key_contexts

客戶端通過請求 SELinux 域和所需的虛擬命名空間(在本例中為"wifi_key" )通過其數字 id 來請求密鑰。

在此之上,已經定義了以下命名空間。如果他們替換了特殊規則,下表顯示了他們曾經對應的UID。

命名空間 ID SEPolicy 標籤用戶標識符描述
0 su_key不適用超級用戶密鑰。僅用於在 userdebug 和 eng 構建上進行測試。與用戶構建無關。
1 shell_key不適用可用於 shell 的命名空間。主要用於測試,但也可以從命令行用於用戶構建。
100 vold_key不適用供vold使用。
101 odsing_key不適用由設備上簽名守護進程使用。
102 wifi_key AID_WIFI(1010)由 Android 的 Wifi sybsystem 使用,包括 wpa_supplicant。
120 resume_on_reboot_key AID_SYSTEM(1000)由 Android 的系統服務器用於支持重啟時恢復。

訪問向量

SELinux 類keystore_key已經老化了很多,一些權限,例如verifysign已經失去了意義。這是 Keystore 2.0 將強制執行的新權限集keystore2_key

允許意義
delete從密鑰庫中刪除密鑰時檢查。
get_info在請求密鑰的元數據時進行檢查。
grant調用者需要此權限才能在目標上下文中創建對密鑰的授權。
manage_blob調用者可以在給定的 SELinux 命名空間上使用DOMAIN_BLOB ,從而自己管理 blob。這對vold特別有用。
rebind此權限控制是否可以將別名重新綁定到新密鑰。這是插入所必需的,並且意味著將刪除先前綁定的鍵。它基本上是一個插入權限,但它更好地捕獲了 keystore 的語義。
req_forced_op具有此權限的客戶端可能會創建不可修剪的操作,並且操作創建永遠不會失敗,除非所有操作槽都被不可修剪的操作佔用。
update需要更新密鑰的子組件。
use在創建使用密鑰材料的 Keymint 操作時選中,例如,用於簽名、加密/解密。
use_dev_id生成設備標識信息時需要,例如設備 ID 證明。

此外,我們在 SELinux 安全類keystore2中拆分出一組非特定於密鑰的密鑰庫權限:

允許意義
add_auth身份驗證提供程序(例如 Gatekeeper 或 BiometricsManager)需要添加身份驗證令牌。
clear_ns以前為 clear_uid,此權限允許命名空間的非所有者刪除該命名空間中的所有鍵。
list系統需要用於按各種屬性(例如所有權或身份驗證有界性)枚舉密鑰。枚舉自己的命名空間的調用者不需要此權限。這由get_info權限覆蓋。
lock此權限允許鎖定 Keystore,即驅逐主密鑰,從而使身份驗證綁定密鑰變得不可用和不可創建。
reset此權限允許將 Keystore 重置為出廠默認設置,刪除所有對 Android 操作系統運行不重要的密鑰。
unlock嘗試解鎖身份驗證綁定密鑰的主密鑰時需要此權限。