硬件支持的密钥库

片上系统 (SoC) 中可用的可信执行环境为 Android 设备提供了一个机会,可以为 Android 操作系统、平台服务甚至第三方应用提供硬件支持的强大安全服务。寻求 Android 特定扩展程序的开发者应转到 android.security.keystore

在 Android 6.0 之前,Android 已经拥有一个简单的硬件支持的加密服务 API,由 Keymaster 硬件抽象层 (HAL) 的 0.2 和 0.3 版本提供。密钥库提供数字签名和验证操作,以及非对称签名密钥对的生成和导入。这已经在许多设备上实现,但仅凭签名 API 很难实现许多安全目标。Android 6.0 中的密钥库扩展了 Keystore API,以提供更广泛的功能。

在 Android 6.0 中,密钥库添加了 对称加密原语 AES 和 HMAC,以及硬件支持密钥的访问控制系统。访问控制在密钥生成期间指定,并在密钥的整个生命周期内强制执行。密钥可以限制为仅在用户通过身份验证后才可使用,并且仅用于指定目的或使用指定的加密参数。如需了解详情,请参阅授权标记页面。

除了扩展加密原语的范围外,Android 6.0 中的密钥库还添加了以下功能:

  • 使用控制方案,允许限制密钥的使用,以降低因密钥误用而导致安全泄露的风险
  • 访问控制方案,用于限制密钥给指定用户、客户端和定义的时间范围

在 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 Framework API 和组件,应用使用它来访问密钥库功能。它作为标准 Java Cryptography Architecture API 的扩展实现,由在应用自己的进程空间中运行的 Java 代码组成。AndroidKeystore 通过将应用对密钥库行为的请求转发到密钥库守护程序来满足这些请求。

密钥库守护程序是一个 Android 系统守护程序,它通过 Binder API 提供对所有密钥库功能的访问。它负责存储“密钥 Blob”,其中包含实际的密钥材料,这些材料已加密,以便密钥库可以存储它们,但不能使用或泄露它们。

keymasterd 是一个 HIDL 服务器,它提供对 Keymaster TA 的访问。(此名称未标准化,仅用于概念目的。)

Keymaster TA(可信应用)是在安全环境中运行的软件,最常见的情况是在 ARM SoC 上的 TrustZone 中,它提供所有安全密钥库操作,可以访问原始密钥材料,验证密钥上的所有访问控制条件等。

LockSettingsService 是负责用户身份验证(包括密码和指纹)的 Android 系统组件。它不是密钥库的一部分,但与之相关,因为许多密钥库密钥操作都需要用户身份验证。LockSettingsService 与 Gatekeeper TA 和 Fingerprint TA 交互以获取身份验证令牌,它将这些令牌提供给密钥库守护程序,最终由 Keymaster TA 应用使用。

Gatekeeper TA(可信应用)是在安全环境中运行的另一个组件,它负责验证用户密码并生成身份验证令牌,用于向 Keymaster TA 证明已在特定时间点为特定用户完成身份验证。

Fingerprint TA (可信应用)是在安全环境中运行的另一个组件,它负责验证用户指纹并生成身份验证令牌,用于向 Keymaster TA 证明已在特定时间点为特定用户完成身份验证。

架构

Android Keystore API 和底层 Keymaster HAL 提供了一组基本但足够的加密原语,以允许使用访问受控的硬件支持密钥来实现协议。

Keymaster HAL 是 OEM 提供的动态加载库,密钥库服务使用它来提供硬件支持的加密服务。为了保持安全,HAL 实现不会在用户空间甚至内核空间中执行任何敏感操作。敏感操作委托给通过某些内核接口访问的安全处理器。生成的架构如下所示:

Access to Keymaster

图 1. 访问 Keymaster

在 Android 设备中,Keymaster HAL 的“客户端”由多个层组成(例如,应用、框架、密钥库守护程序),但这在本文档中可以忽略不计。这意味着所描述的 Keymaster HAL API 是低级别的,供平台内部组件使用,并且不向应用开发者公开。更高级别的 API 在Android 开发者网站上进行了描述。

Keymaster HAL 的目的不是实现安全敏感型算法,而只是编组和解组到安全世界的请求。线路格式是实现定义的。

与先前版本的兼容性

Keymaster 1 HAL 与先前发布的 HAL(例如,Keymaster 0.2 和 0.3)完全不兼容。为了方便在运行 Android 5.0 及更早版本且使用旧版 Keymaster HAL 启动的设备上实现互操作性,密钥库提供了一个适配器,该适配器通过调用现有硬件库来实现 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 HAL 的设备提供向后兼容性。

访问控制

密钥库访问控制最基本的规则是每个应用都有自己的命名空间。但每条规则都有例外情况。密钥库有一些硬编码的映射,允许某些系统组件访问某些其他命名空间。这是一种非常钝的工具,因为它使一个组件能够完全控制另一个命名空间。然后,还有供应商组件作为密钥库客户端的问题。我们目前没有办法为供应商组件(例如,WPA supplicant)建立命名空间。

为了适应供应商组件并泛化访问控制,而无需硬编码的例外情况,Key库 2.0 引入了域和 SELinux 命名空间。

密钥库域

借助密钥库域,我们可以将命名空间与 UID 解耦。访问密钥库中密钥的客户端必须指定他们想要访问的域、命名空间和别名。根据此元组和调用方的身份,我们可以确定调用方想要访问哪个密钥以及它是否具有适当的权限。

我们引入了五个域参数,这些参数控制密钥的访问方式。它们控制密钥描述符的命名空间参数的语义以及访问控制的执行方式。

  • DOMAIN_APP:应用域涵盖旧版行为。Java Keystore SPI 默认使用此域。当使用此域时,命名空间参数将被忽略,而是使用调用方的 UID。对此域的访问由 SELinux 策略中 keystore_key 类的密钥库标签控制。
  • DOMAIN_SELINUX:此域表示命名空间在 SELinux 策略中具有标签。命名空间参数会被查找并转换为目标上下文,并对调用 SELinux 上下文执行 keystore_key 类的权限检查。当给定操作的权限已建立时,完整元组将用于密钥查找。
  • DOMAIN_GRANT:授权域表示命名空间参数是授权标识符。别名参数将被忽略。创建授权时会执行 SELinux 检查。进一步的访问控制仅检查调用方 UID 是否与请求授权的被授权者 UID 匹配。
  • DOMAIN_KEY_ID:此域表示命名空间参数是唯一密钥 ID。密钥本身可能是使用 DOMAIN_APPDOMAIN_SELINUX 创建的。权限检查在从密钥数据库加载 domainnamespace 后执行,方式与通过域、命名空间和别名元组加载 Blob 相同。密钥 ID 域的基本原理是连续性。当通过别名访问密钥时,后续调用可以对不同的密钥进行操作,因为可能已生成或导入新密钥并将其绑定到此别名。但是,密钥 ID 永远不会更改。因此,当在通过别名从密钥库数据库加载密钥一次后,通过密钥 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。对于从密钥库加载密钥和将密钥导入密钥库,命名空间 ID 使用 AndroidKeyStoreLoadStoreParameter 指定。例如:

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

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

要在给定命名空间中生成密钥,必须使用 KeyGenParameterSpec.Builder#setNamespace(): 提供命名空间 ID:

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 标签 UID 描述
0 su_key 不适用 超级用户密钥。仅用于 userdebug 和 eng 版本上的测试。与用户版本无关。
1 shell_key 不适用 shell 可用的命名空间。主要用于测试,但也可以从命令行在用户版本中使用。
100 vold_key 不适用 旨在供 vold 使用。
101 odsing_key 不适用 由设备端签名守护程序使用。
102 wifi_key AID_WIFI(1010) 由 Android 的 Wifi 子系统(包括 wpa_supplicant)使用。
120 resume_on_reboot_key AID_SYSTEM(1000) 由 Android 的系统服务器使用,以支持重启后恢复。

访问向量

SELinux 类 keystore_key 已经存在很长时间了,并且某些权限(例如 verifysign)已经失去了它们的意义。以下是密钥库 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 尝试解锁身份验证绑定密钥的主密钥时需要此权限。