硬體支援的金鑰庫

系統單晶片 (SoC) 中可信任執行環境的可用性為 Android 裝置提供了向 Android 作業系統、平台服務甚至第三方應用程式提供硬體支援的強大安全服務的機會。尋求 Android 特定擴充功能的開發人員應該造訪android.security.keystore

在 Android 6.0 之前,Android 已經有了一個簡單的、硬體支援的加密服務 API,由 Keymaster 硬體抽象層 (HAL) 0.2 和 0.3 版本提供。金鑰庫提供數位簽章和驗證操作,以及非對稱簽章金鑰對的產生和匯入。這已經在許多設備上實現,但有許多安全目標無法僅透過簽名 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 分別設定版本以允許獨立更新

詞彙表

以下是密鑰庫元件及其關係的快速概述。

AndroidKeystore是應用程式用來存取金鑰庫功能的 Android 框架 API 和元件。它作為標準 Java 加密體系結構 API 的擴展來實現,並由在應用程式自己的進程空間中運行的 Java 程式碼組成。 AndroidKeystore透過將應用程式轉送到金鑰庫守護程式來滿足應用程式對金鑰庫行為的請求。

密鑰庫守護程序是一個 Android 系統守護程序,它透過Binder API提供對所有密鑰庫功能的存取。它負責儲存“密鑰 blob”,其中包含實際的秘密密鑰材料,並經過加密,以便 Keystore 可以儲存它們,但不能使用或洩露它們。

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

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

LockSettingsService是 Android 系統元件,負責使用者身份驗證,包括密碼和指紋。它不是 Keystore 的一部分,但相關,因為許多 Keystore 金鑰操作需要使用者驗證。 LockSettingsService與 Gatekeeper TA 和 Fingerprint TA 互動以獲得身份驗證令牌,並將其提供給金鑰庫守護程序,並最終由 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函數指標將操作結果回傳給呼叫者。請注意,函數指標呼叫是同步的。呼叫者呼叫generateKeygenerateKey呼叫提供的函數指針,該函數指針執行完成,將控制權返回給generateKey實現,然後該實現返回給呼叫者。

有關詳細範例,請參閱hardware/interfaces/keymaster/3.0/default/KeymasterDevice.cpp中的預設實作。預設實作為具有舊式 keymaster0、keymaster1 或 keymaster2 HALS 的裝置提供向後相容性。

存取控制

Keystore存取控制最基本的規則是每個應用程式都有自己的命名空間。但每條規則都有一個例外。密鑰庫有一些硬編碼映射,允許某些系統元件存取某些其他命名空間。這是一個非常生硬的工具,因為它讓一個元件完全控制另一個名稱空間。然後還有供應商元件作為金鑰庫客戶端的問題。我們目前無法為供應商元件(例如 WPA 請求者)建立命名空間。

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

金鑰庫域

透過金鑰庫域,我們可以將命名空間與 UID 解耦。存取金鑰庫中金鑰的用戶端必須指定他們想要存取的網域、命名空間和別名。根據這個元組和呼叫者的身份,我們可以確定呼叫者想要存取哪個金鑰以及它是否具有適當的權限。

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

  • DOMAIN_APP :應用程式網域涵蓋舊行為。 Java Keystore SPI 預設使用此網域。使用此域時,命名空間參數將被忽略,而使用呼叫者的 UID。對此網域的存取由 SELinux 策略中keystore_key類別的金鑰庫標籤控制。
  • DOMAIN_SELINUX :此網域表示該命名空間在 SELinux 策略中具有標籤。尋找命名空間參數並將其轉換為目標上下文,並對keystore_key類別的呼叫 SELinux 上下文執行權限檢查。當為給定操作建立了權限後,完整元組將用於鍵查找。
  • DOMAIN_GRANT :授權網域表示命名空間參數是授權識別碼。別名參數被忽略。 SELinux 檢查是在建立授權時執行的。進一步的存取控制僅檢查呼叫者 UID 是否與所要求授權的受讓人 UID 相符。
  • DOMAIN_KEY_ID :此域表示命名空間參數是唯一的鍵id。金鑰本身可能是使用DOMAIN_APPDOMAIN_SELINUX建立的。從金鑰資料庫載入網domainnamespace空間後執行權限檢查,就像透過網域、命名空間和別名元組載入 blob 一樣。密鑰 ID 域的基本原理是連續性。當透過別名存取金鑰時,後續呼叫可能會對不同的金鑰進行操作,因為可能已經產生或匯入了新金鑰並綁定到該別名。然而,密鑰 ID 永遠不會改變。因此,當使用別名從 Keystore 資料庫載入一次金鑰後,透過金鑰 id 使用金鑰時,只要金鑰 id 仍然存在,就可以確定它是同一個金鑰。此功能不會向應用程式開發人員公開。相反,它在 Android Keystore SPI 中使用,即使以不安全的方式同時使用,也能提供更一致的體驗。
  • DOMAIN_BLOB :blob 域表示呼叫者自己管理 blob。這用於在掛載資料分區之前需要存取金鑰庫的客戶端。密鑰 blob 包含在密鑰描述符的blob欄位中。

使用 SELinux 網域,我們可以讓供應商元件存取非常特定的金鑰庫命名空間,這些命名空間可以由系統元件(例如設定對話框)共用。

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 SE政策標籤使用者識別碼描述
0蘇鍵不適用超級用戶金鑰。僅用於在 userdebug 和 eng 版本上進行測試。與用戶建置無關。
1外殼鍵不適用可用於 shell 的命名空間。主要用於測試,但也可以從命令列用於使用者建置。
100沃德密鑰不適用供 vold 使用。
101 odsing_key不適用由設備上簽章守護程序使用。
102 wifi_key AID_WIFI(1010)由 Android 的 Wifi 系統系統(包括 wpa_supplicant)使用。
120重新啟動鍵時恢復輔助系統(1000)由 Android 系統伺服器用來支援重啟時恢復。

訪問向量

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

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

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

允許意義
add_auth身份驗證提供者(例如 Gatekeeper 或 BiometricsManager)需要新增身份驗證令牌。
clear_ns此權限以前稱為clear_uid,允許命名空間的非擁有者刪除該命名空間中的所有金鑰。
list系統需要透過各種屬性(例如所有權或身份驗證邊界)來列舉金鑰。列舉自己的命名空間的呼叫者不需要此權限。 get_info權限涵蓋了這一點。
lock此權限允許鎖定金鑰庫,即逐出主金鑰,從而使身份驗證綁定金鑰變得不可用且無法建立。
reset此權限允許將金鑰庫重設為出廠預設值,刪除對 Android 作業系統功能不重要的所有金鑰。
unlock嘗試解鎖身份驗證綁定金鑰的主金鑰需要此權限。