版本绑定

在 Keymaster 1 中,所有 Keymaster 密钥都以加密形式绑定至设备的信任根或启动时验证密钥。在 Keymaster 2 和 3 中,所有密钥也绑定至系统映像的操作系统和补丁程序级别。这样可确保在旧版系统或 TEE 软件中发现漏洞的攻击者无法将设备回滚到易受攻击的版本,也无法使用在较新版本中创建的密钥。此外,在已经升级到更新的版本或补丁级别的设备上使用指定版本和补丁级别的密钥时,需要先升级该密钥才能使用,因为该密钥的旧版本已失效。这样一来,当设备升级时,密钥会随着设备一起“升级”,但是将设备恢复到任何一个旧版本都会导致密钥无法使用。

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

在 Android 9 中,boot 分区、system 分区和 vendor 分区都拥有自己的补丁程序级别。

  • 包含 Android 启动时验证 (AVB) 的设备可以将所有补丁级别和系统版本放在 vbmeta 中,以便引导加载程序能够将这些内容提供给 Keymaster。对于链接的分区,相应分区的版本信息位于链接的 vbmeta 中。通常,版本信息应位于包含指定分区的验证数据(哈希或哈希树)的 vbmeta struct 中。
  • 在没有 AVB 的设备上:
    • 启动时验证的实现需要将版本元数据的哈希提供给引导加载程序,以便引导加载程序能够将其提供给 Keymaster。
    • boot.img 可以继续在标头中存储补丁程序级别
    • system.img 可以继续在只读属性中存储补丁程序级别和操作系统版本
    • vendor.img 在只读属性 ro.vendor.build.version.security_patch 中存储补丁程序级别。
    • 引导加载程序可以将启动时验证功能验证过的所有数据的哈希提供给 Keymaster。
  • 在 Android 9 中,使用以下标记提供对应分区的版本信息:
    • VENDOR_PATCH_LEVELvendor 分区
    • BOOT_PATCH_LEVELboot 分区
    • OS_PATCH_LEVELOS_VERSIONsystem 分区(从 boot.img 标头中移除 OS_VERSION)。
  • 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。

upgradeKey 在调用 keyBlobToUpgrade 后仍然有效,而且从理论上说,如果设备降级,还可以再次使用它。但在实际操作中,Keystore 通常在调用 upgradeKey 后很快就会对 keyBlobToUpgrade Blob 调用 deleteKey。如果 keyBlobToUpgrade 具有 Tag::ROLLBACK_RESISTANT 标记,则 upgradedKeyBlob 也应该包含该标记(并且应该可抗回滚)。

安全配置

如需实现版本绑定,Keymaster TA 需要以一种方式安全接收当前的操作系统版本和补丁级别(版本信息),并确保所接收的信息与运行系统的相关信息严格匹配。

为了确保将版本信息安全传递到 TA,启动映像头文件中添加了 OS_VERSION 字段。启动映像构建脚本会自动填充此字段。原始设备制造商 (OEM) 和 Keymaster TA 实现人员需要相互协作来修改设备引导加载程序,以便从启动映像中提取版本信息,并在非安全系统启动之前将其传递到 TA。这样可确保攻击者无法干扰向 TA 提供版本信息的过程。

此外,您还需要确保系统映像与启动映像具有相同的版本信息。为此,Keymaster HAL 中添加了 configure 方法:

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,所有其他 Keymaster 方法会继续返回 ErrorCode::KEYMASTER_NOT_CONFIGURED。如果信息一致,configure 会返回 ErrorCode::OK,其他 Keymaster 方法开始正常运行。

configure 的后续调用与首次调用返回的值相同,而且不会更改 Keymaster 的状态。请注意,此过程会要求所有 OTA 同时更新系统映像和启动映像;为了使版本信息保持同步,不能单独更新这两种映像。

由于将要调用 configure 的系统的内容需要经过验证,攻击者可以利用这个稍纵即逝的机会破坏系统映像,并强制其提供与启动映像一致的版本信息,但这并不是系统的真实版本信息。不过,对启动映像的验证、对系统映像内容的 dm-verity 验证以及在系统启动早期调用 configure 这三项相结合,应该能够让攻击者难以利用这个机会。