版本绑定

在 Keymaster 1 中,所有 Keymaster 密钥都以加密方式绑定到设备信任根或“验证启动”密钥。在 Keymaster 2 和 3 中,所有密钥还绑定到系统映像的操作系统和补丁程序级别。这可确保攻击者即使在旧版系统或 TEE 软件中发现漏洞,也无法将设备回滚到易受攻击的版本,并使用使用较新版本创建的密钥。此外,当在已升级到较新版本或补丁程序级别的设备上使用具有给定版本和补丁程序级别的密钥时,该密钥会在使用前升级,并且先前版本的密钥会失效。这样,随着设备的升级,密钥会随设备一起向前“棘轮”,但将设备还原到先前的任何版本都会导致密钥无法使用。

为了支持 Treble 的模块化结构并打破 system.img 与 boot.img 的绑定,Keymaster 4 更改了密钥版本绑定模型,使每个分区都具有单独的补丁程序级别。这样一来,每个分区都可以独立更新,同时仍提供回滚保护。

在 Android 9 中,bootsystemvendor 分区各自具有自己的补丁程序级别。

  • 具有 Android 验证启动 (AVB) 功能的设备可以将所有补丁程序级别和系统版本放入 vbmeta 中,以便引导加载程序可以将它们提供给 Keymaster。对于链接的分区,分区的版本信息位于链接的 vbmeta 中。通常,版本信息应位于包含给定分区的验证数据(哈希或哈希树)的 vbmeta struct 中。
  • 在没有 AVB 的设备上
    • 验证启动实现需要向引导加载程序提供版本元数据的哈希,以便引导加载程序可以将哈希提供给 Keymaster。
    • boot.img 可以继续在标头中存储补丁程序级别
    • system.img 可以继续在只读属性中存储补丁程序级别和操作系统版本
    • vendor.img 在只读属性 ro.vendor.build.version.security_patch 中存储补丁程序级别。
    • 引导加载程序可以向密钥库提供由验证启动验证的所有数据的哈希。
  • 在 Android 9 中,使用以下标记为以下分区提供版本信息
    • VENDOR_PATCH_LEVELvendor 分区
    • BOOT_PATCH_LEVELboot 分区
    • OS_PATCH_LEVELOS_VERSIONsystem 分区。(OS_VERSIONboot.img 标头中移除。
  • Keymaster 实现应独立处理所有补丁程序级别。如果所有版本信息都与密钥关联的值匹配,则密钥可用,并且如果需要,IKeymaster::upgradeDevice() 会滚动到更高的补丁程序级别。

HAL 变更

为了支持版本绑定和版本证明,Android 7.1 添加了标记 Tag::OS_VERSIONTag::OS_PATCHLEVEL 以及方法 configureupgradeKey。Keymaster 2+ 实现会自动将版本标记添加到所有新生成(或更新)的密钥。此外,任何尝试使用不具有与当前系统操作系统版本或补丁程序级别分别匹配的操作系统版本或补丁程序级别的密钥的尝试都会因 ErrorCode::KEY_REQUIRES_UPGRADE 而被拒绝。

Tag::OS_VERSION 是一个 UINT 值,它以 MMmmss 形式表示 Android 系统版本的主要版本、次要版本和次次要版本,其中 MM 是主要版本,mm 是次要版本,ss 是次次要版本。例如,6.1.2 将表示为 060102。

Tag::OS_PATCHLEVEL 是一个 UINT 值,它以 YYYYMM 形式表示系统上次更新的年份和月份,其中 YYYY 是四位数的年份,MM 是两位数的月份。例如,2016 年 3 月将表示为 201603。

UpgradeKey

为了允许将密钥升级到系统映像的新操作系统版本和补丁程序级别,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。如果使用操作系统版本高于当前系统值的密钥调用它,并且系统值为非零值,则它会返回 ErrorCode::INVALID_ARGUMENT。允许从非零值升级到零值的操作系统版本升级。如果与安全世界通信时发生错误,它会返回相应的错误值(例如,ErrorCode::SECURE_HW_ACCESS_DENIEDErrorCode::SECURE_HW_BUSY)。否则,它会返回 ErrorCode::OK 并在 upgradedKeyBlob 中返回新的密钥 blob。

keyBlobToUpgradeupgradeKey 调用后仍然有效,并且如果设备降级,理论上可以再次使用。实际上,密钥库通常会在调用 upgradeKey 后不久在 keyBlobToUpgrade blob 上调用 deleteKey。如果 keyBlobToUpgrade 具有标记 Tag::ROLLBACK_RESISTANT,则 upgradedKeyBlob 也应具有该标记(并且应具有防回滚能力)。

安全配置

为了实现版本绑定,密钥库 TA 需要一种安全接收当前操作系统版本和补丁程序级别(版本信息)的方法,并确保其接收到的信息与有关正在运行的系统的信息高度匹配。

为了支持将版本信息安全地传递到 TA,已将 OS_VERSION 字段添加到启动映像标头。启动映像构建脚本会自动填充此字段。OEM 和密钥库 TA 实现者需要协同工作来修改设备引导加载程序,以从启动映像中提取版本信息,并在非安全系统启动之前将其传递给 TA。这可确保攻击者无法干扰向 TA 预配版本信息。

还需要确保系统映像具有与启动映像相同的版本信息。为此,已将 configure 方法添加到密钥库 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 后但在调用任何其他方法之前调用。如果在 configure 之前调用任何其他方法,TA 将返回 ErrorCode::KEYMASTER_NOT_CONFIGURED

在设备启动后首次调用 configure 时,它应验证提供的版本信息是否与引导加载程序提供的版本信息匹配。如果版本信息不匹配,configure 将返回 ErrorCode::INVALID_ARGUMENT,并且所有其他密钥库方法将继续返回 ErrorCode::KEYMASTER_NOT_CONFIGURED。如果信息匹配,configure 将返回 ErrorCode::OK,并且其他密钥库方法将开始正常运行。

后续对 configure 的调用会返回第一次调用返回的相同值,并且不会更改密钥库的状态。

由于 configure 由旨在验证其内容的系统调用,因此攻击者有一个狭窄的机会窗口来破坏系统映像并强制其提供与启动映像匹配的版本信息,但该信息不是系统的实际版本。启动映像验证、系统映像内容的 dm-verity 验证以及 configure 在系统启动早期调用的事实相结合,应使此机会窗口难以利用。