版本繫結

在 Keymaster 1 中,所有 Keymaster 金鑰都以密碼編譯方式繫結至裝置的信任根或已驗證啟動金鑰。在 Keymaster 2 和 3 中,所有金鑰也會繫結至系統映像檔的作業系統和修補程式層級。這可確保攻擊者即使發現舊版系統或 TEE 軟體的弱點,也無法將裝置還原至有安全漏洞的版本,並使用新版建立的金鑰。此外,如果裝置升級至較新版本或修補程式等級,當裝置使用特定版本和修補程式等級的金鑰時,系統會先升級金鑰,才能使用金鑰,且金鑰的舊版會失效。這樣一來,裝置升級時,金鑰也會隨之「棘輪」前進,但如果裝置還原為先前的版本,金鑰就會無法使用。

為支援 Treble 的模組化結構,並打破 system.img 與 boot.img 的繫結,Keymaster 4 變更了金鑰版本繫結模型,為每個磁碟分割區設定不同的修補程式層級。這樣一來,每個分區都能獨立更新,同時仍提供復原保護機制。

如要實作這個版本繫結,KeyMint 信任的應用程式 (TA) 必須能安全地接收目前的 OS 版本和修補程式層級,並確保收到的資訊與執行中系統的所有資訊相符。

  • 搭載 Android 驗證開機程序 (AVB) 的裝置可將所有修補程式層級和系統版本放在 vbmeta 中,因此啟動載入程式可將這些資訊提供給 Keymaster。如果是鏈結分區,分區的版本資訊位於鏈結的 vbmeta 中。一般來說,版本資訊應位於包含特定分區驗證資料 (雜湊或雜湊樹狀結構) 的 vbmeta struct 中。
  • 在沒有 AVB 的裝置上:
    • 已驗證開機程序實作項目必須向啟動載入程式提供版本中繼資料的雜湊值,啟動載入程式才能將雜湊值提供給 Keymaster。
    • boot.img 可以繼續在標頭中儲存修補程式等級
    • system.img 可以繼續在唯讀屬性中儲存修補程式層級和 OS 版本
    • vendor.img 會將修補程式層級儲存在唯讀屬性 ro.vendor.build.version.security_patch 中。
    • 開機載入程式可將驗證開機程序驗證的所有資料的雜湊值提供給 Keymaster。
  • 在 Android 9 中,請使用下列標記,為下列分割區提供版本資訊:
    • VENDOR_PATCH_LEVELvendor 分割區
    • BOOT_PATCH_LEVELboot 分割區
    • OS_PATCH_LEVELOS_VERSIONsystem 分割區。(OS_VERSION 會從 boot.img 標頭中移除。
  • Keymaster 實作應獨立處理所有修補程式層級。如果所有版本資訊都符合與金鑰相關聯的值,且 IKeymaster::upgradeDevice() 視需要升級至較高的修補程式層級,即可使用金鑰。

HAL 變更

為支援版本繫結和版本認證,Android 7.1 新增了 Tag::OS_VERSIONTag::OS_PATCHLEVEL 標記,以及 configureupgradeKey 方法。Keymaster 2 以上的實作項目會自動將版本標記新增至所有新產生 (或更新) 的金鑰。此外,如果嘗試使用的金鑰 OS 版本或修補程式等級,與目前系統的 OS 版本或修補程式等級不符,系統會拒絕要求並傳回 ErrorCode::KEY_REQUIRES_UPGRADE

Tag::OS_VERSIONUINT 值,代表 Android 系統版本的主要、次要和副次要部分,格式為 MMmmss,其中 MM 是主要版本,mm 是次要版本,ss 是副次要版本。舉例來說,6.1.2 會以 060102 表示。

Tag::OS_PATCHLEVELUINT 值,代表系統最近一次更新的年份和月份 (YYYYMM),其中 YYYY 是四位數的年份,MM 則是兩位數的月份。舉例來說,2016 年 3 月會表示為 201603。

UpgradeKey

為允許將金鑰升級至系統映像檔的新 OS 版本和修補程式層級,Android 7.1 在 HAL 中新增了 upgradeKey 方法:

Keymaster 3

    upgradeKey(vec keyBlobToUpgrade, vec upgradeParams)
        generates(ErrorCode error, vec upgradedKeyBlob);

Keymaster 2

keymaster_error_t (*upgrade_key)(const struct keymaster2_device* dev,
    const keymaster_key_blob_t* key_to_upgrade,
    const keymaster_key_param_set_t* upgrade_params,
    keymaster_key_blob_t* upgraded_key);
  • dev 是裝置結構
  • keyBlobToUpgrade」是需要升級的金鑰
  • upgradeParams 是升級金鑰所需的參數。這些包括 Tag::APPLICATION_IDTag::APPLICATION_DATA,如果是在產生期間提供,則必須使用這些屬性解密金鑰 Blob。
  • upgradedKeyBlob 是輸出參數,用於傳回新的金鑰 Blob。

如果使用無法剖析或無效的金鑰 blob 呼叫 upgradeKey,則會傳回 ErrorCode::INVALID_KEY_BLOB。如果使用修補程式等級大於目前系統值的鍵呼叫,則會傳回 ErrorCode::INVALID_ARGUMENT。如果呼叫的鍵的 OS 版本大於目前的系統值,且系統值為非零值,則會傳回 ErrorCode::INVALID_ARGUMENT。允許將 OS 版本從非零升級至零。如果與安全世界通訊時發生錯誤,則會傳回適當的錯誤值 (例如 ErrorCode::SECURE_HW_ACCESS_DENIEDErrorCode::SECURE_HW_BUSY)。否則,會傳回 ErrorCode::OK,並在 upgradedKeyBlob 中傳回新的金鑰 Blob。

keyBlobToUpgradeupgradeKey呼叫後仍有效,理論上,如果裝置降級,可以再次使用。實務上,KeyStore 通常會在呼叫 upgradeKey 後不久,對 keyBlobToUpgrade Blob 呼叫 deleteKey。如果 keyBlobToUpgrade 具有標記 Tag::ROLLBACK_RESISTANT,則 upgradedKeyBlob 也應具有該標記 (且應可防止回溯)。

安全設定

如要實作版本繫結,Keymaster TA 必須能安全地接收目前的 OS 版本和修補程式層級 (版本資訊),並確保接收到的資訊與執行中系統的資訊完全相符。

為支援將版本資訊安全地傳送至 TA,啟動映像檔標頭已新增 OS_VERSION 欄位。開機映像檔建構指令碼會自動填入這個欄位。OEM 和 Keymaster TA 實作者必須合作修改裝置開機載入程式,從開機映像檔擷取版本資訊,並在啟動不安全的系統前,將資訊傳遞至 TA。這樣可確保攻擊者無法干擾 TA 的版本資訊佈建作業。

此外,也必須確保系統映像檔與開機映像檔的版本資訊相同。為此,Keymaster HAL 已新增設定方法:

keymaster_error_t (*configure)(const struct keymaster2_device* dev,
  const keymaster_key_param_set_t* params);

params 引數包含 Tag::OS_VERSIONTag::OS_PATCHLEVEL。這個方法是由 keymaster2 用戶端在開啟 HAL 後呼叫,但會在呼叫任何其他方法之前呼叫。如果在設定前呼叫任何其他方法,TA 會傳回 ErrorCode::KEYMASTER_NOT_CONFIGURED

裝置啟動後首次呼叫 configure 時,應驗證所提供的版本資訊是否與開機載入程式提供的資訊相符。如果版本資訊不符,configure 會傳回 ErrorCode::INVALID_ARGUMENT,而所有其他 Keymaster 方法會繼續傳回 ErrorCode::KEYMASTER_NOT_CONFIGURED。如果資訊相符,configure 會傳回 ErrorCode::OK,其他 Keymaster 方法也會開始正常運作。

後續呼叫 configure 會傳回第一次呼叫時傳回的值,且不會變更 Keymaster 的狀態。

由於 configure 是由系統呼叫,且內容會經過驗證,因此攻擊者可利用的機會不多,只能在短時間內入侵系統映像檔,並強制提供與啟動映像檔相符的版本資訊,但這並非系統的實際版本。結合開機映像檔驗證、系統映像檔內容的 dm-verity 驗證,以及 configure 是在系統開機初期呼叫的事實,應該能讓這個可乘之機難以利用。