Trusty,一个可信执行环境
适用于:Jetson Xavier NX 系列、Jetson AGX Xavier 系列和 Jetson TX2 系列设备
Trusty 由一组软件组件组成,用于在移动设备上支持可信执行环境 (TEE)。TEE 提供了一个执行环境,其中包括安全功能,以确保设备上的代码和数据受到保护。
Trusty 是 Android 开源项目 (AOSP) 的一部分。有关 Trusty 和可信执行环境的更多信息,请参阅
Trusty 扩展了 Little Kernel (LK) 开发提供的技术。LK 旨在以简单、轻量级的软件包提供线程和互斥锁等操作系统原语。
有关 Little Kernel 的更多信息,请参阅 LK 文档,网址为
Trusty 基于 ARM® TrustZone 技术。有关 TrustZone 的信息,请参阅白皮书,网址为
http://infocenter.arm.com/help/topic/com.arm.doc.prd29-genc-009492c/PRD29-GENC-009492C_trustzone_security_whitepaper.pdf
NVIDIA® Jetson™ Linux 驱动程序包 (L4T) 中的 Trusty 能够在 NVIDIA® Jetson Xavier™ NX 系列、NVIDIA® Jetson AGX Xavier™ 系列和 NVIDIA® Jetson™ TX2 系列设备上启动可信操作系统。以下部分介绍了如何设置和使用 Trusty。
本主题使用了一些 Trusty 特有的术语
• ATF:ARM 可信固件
• CA:客户端应用程序
• TA:可信应用程序;在 Trusty TEE 中运行的任何应用程序
• TEE:可信执行环境,Trusty 提供的用于运行可信应用程序的安全环境
• TOS:可信操作系统;Trusty 是 L4T 使用的可信操作系统
• Trusty:来自 Android 开源项目的可信操作系统;L4T 中提供的可信操作系统
架构
Trusty 驻留在单独的存储分区中,并作为信任链或安全启动序列的一部分启动。它在具有不同安全模式的设备中创建两个环境
• 非安全环境 (NSE):用于在非安全模式下运行软件组件的环境。此模式被称为“正常世界”。诸如 Linux 之类的富操作系统通常在此环境中运行。
• 可信执行环境 (TEE):一个单独的环境,提供可信操作并在硬件强制执行的安全模式下运行。此模式被称为“安全世界”。Trusty 在此环境中运行。
正常世界操作系统和 Trusty 软件以客户端-服务器关系运行,Trusty 作为服务器。
引导加载程序分配一个专用的划分区域 TZ-DRAM,以运行安全操作系统。所有安全操作都由在非安全环境中运行的客户端应用程序发起。安全世界中的可信应用程序永远不会主动与非安全环境联系。
此图显示了组件之间的关系
执行步骤
1. 当客户端应用程序 (CA) 必须执行安全操作时,它通过调用 Trusty 客户端应用程序库中的函数向可信应用程序 (TA) 发送请求。
2. Trusty 客户端库将请求路由到 Trusty Linux 内核驱动程序。
3. Trusty Linux 驱动程序将客户端应用程序请求路由到 ARM 可信固件 (ATF)。
4. 监视器将请求路由到 Trusty 内核。
L4T 监视器实现基于 ATF。有关 ATF 的更多信息,请参阅
5. Trusty 内核框架确定要由哪个可信应用程序 (TA) 处理请求。
6. Trusty 内核框架将控制权传递给 TA 以处理请求。
7. 完成后,执行控制权沿反向路径返回到客户端应用程序,客户端应用程序接收返回值和任何已处理的数据。
可信应用程序开发
本节提供了有关开发在 Trusty 的可信执行环境 (TEE) 中运行的可信应用程序 (TA) 的说明。
默认可信应用程序
L4T 默认启用三个 TA
• sample/ipc-unittest/main
• sample/ipc-unittest/srv
• nvidia-sample/hwkey-agent
前两个 TA 构成了作为 Android 开源项目 (AOSP) 一部分开发的功能测试框架。
第三个 TA 由 NVIDIA 开发,用作在 Jetson 设备上实现硬件支持的加密密钥 blob 的参考。对于任何将 AES 密钥烧录到熔丝(KEK、SBK 等)的设备,如果要使用包含密钥或其他加密内容的加密 blob 配置设备,请使用基于此参考的 TA。
来自 AOSP 的另外两个 TA 与 Trusty 打包在一起,但默认情况下处于禁用状态。这些 TA 位于 <TRUSTY_TOP>/trusty/app 目录中,其中 <TRUSTY_TOP> 是 Trusty 源程序包的根目录。
可信应用程序的清单
每个可信应用程序都必须有一个对应的清单,用于指定 TA 的配置属性。清单数据结构为
typedef struct {
uuid_t uuid; /* UUID 是 TA 的唯一标识符 */
uint32_t config_options[];
} trusty_app_manifest_t;
清单在以下位置定义
<TRUSTY_TOP>/trusty/lib/include/trusty_app_manifest.h
其中 <TRUSTY_TOP> 是保存所有 Trusty 代码的根目录。
使用清单来指定配置,例如:堆栈大小、堆大小和映射 MMIO 地址。UUID 生成器(
https://www.uuidgenerator.net/)创建 UUID。
这是一个 TA 清单的示例
trusty_app_manifest_t TRUSTY_APP_MANIFEST_ATTRS trusty_app_manifest =
{
.uuid = SERVICE_SAMPLE_UUID,
.config_options =
/* 此处为可选配置选项 */
{
/* 4 页用于堆 */
TRUSTY_APP_CONFIG_MIN_HEAP_SIZE(4 * 4096),
/* 4 页用于堆栈 */
TRUSTY_APP_CONFIG_MIN_STACK_SIZE(4 * 4096),
/* 请求 I/O 映射 */
TRUSTY_APP_CONFIG_MAP_MEM(0,
TEGRA_DEVICE_ADDR, TEGRA_DEVICE_ADDR_LEN),
}
};
访问 MMIO 区域
要访问硬件寄存器,可信应用程序必须将寄存器映射到地址空间中的 MMIO 区域。
要访问硬件寄存器
1. 在 TA 清单文件中指定地址区域。
2. 在清单中为每个映射指定唯一 ID。
3. 使用系统函数 mmap() 和 munmap() 映射地址空间。
这些函数在 trusty_calls.h 中声明
long mmap (void* uaddr, uint32_t size, uint32_t flags, uint32_t handle);
long munmap (void* uaddr, uint32_t size);
直接内存访问操作
Trusty 系统函数 prepare_dma() 和 finish_dma() 执行直接内存访问 (DMA) 操作。flags 参数确定 DMA 和缓存操作的方向。
flags 的值在以下位置定义
<TRUSTY_TOP>/trusty/lk/trusty/include/mm.h
其中 <TRUSTY_TOP> 是保存所有 Trusty 代码的根目录。这些函数在 trusty_syscalls.h 中声明
long prepare_dma (void* uaddr, uint32_t size, uint32_t flags, void* pmem);
long finish_dma (void* uaddr, uint32_t size, uint32_t flags);
如何实现新的可信应用程序
要实现新的可信应用程序,请执行以下步骤
1. 将可信应用程序 (TA) 源文件放置在 <TRUSTY_TOP>/trusty/app/ 内的新应用程序目录中(例如 <TRUSTY_TOP>/trusty/app/my_trusted_app/)。
2. 创建 TA 清单文件。
• 根据需要更新 I/O 映射(可选)。
• 声明最小堆栈和堆大小(可选)。
3. 在 my_trusted_app/ 目录中添加源文件。
简单示例 TA 的源代码位于文件
<TRUSTY_TOP>/trusty/app/sample/skel/skel_app.c
4. 创建 TA makefile (rules.mk),其中指定 TA 的源文件、包含路径和模块依赖项。
简单示例 TA makefile 位于
<TRUSTY_TOP>/trusty/app/sample/skel/rules.mk
5. 将 TA makefile 的路径添加到用户任务列表 TRUSTY_ALL_USER_TASKS,该列表位于
<TRUSTY_TOP>/trusty/device/nvidia/t186/project/t186-l4t.mk
添加行
TRUSTY_ALL_USER_TASKS += <path_to_app>
其中 <path_to_app> 是 TA 的路径名,相对于目录 <TRUSTY_TOP>/trusty/app。
6. 重新构建 Trusty。
Trusty 和 TA 具有组合构建。重新构建 Trusty 还会重新构建 lk.bin 中 TRUSTY_ALL_USER_TASKS 中定义的所有 TA。
注意 | 为了保护可信操作系统二进制文件的完整性和机密性,NVIDIA 强烈建议您使用 安全启动来加密和签署 TOS 映像。 |
如何与其他应用程序通信
TA 使用 TIPC 协议与其他 TA 或在正常世界中运行的应用程序进行通信。该协议在 Android Trusty TEE 文档中进行了描述,网址为
Trusty API 参考
Trusty API 参考可以在以下位置找到
熔丝密钥和用户定义密钥的密钥派生函数
不同的安全功能通常需要不同类型的密钥来加密和解密数据。这些密钥通常是机密和敏感的,也就是说,泄露它们会产生严重的后果。Trusty 使用 加密密钥 blob (EKB) 机制来配置密钥和其他机密数据。
加密密钥 blob
讨论 EKB 功能时使用了一些专用术语
• EKB 或 EKS:加密密钥 blob,一个加密 blob,其中包含开发者定义的内容。
• 密钥槽:Jetson 安全引擎 (SE) 内部的安全存储区域。它可以保护安全密钥免受未经授权的读取和写入。在早期启动期间,Bootrom 将安全密钥从熔丝存储加载到密钥槽,以便稍后 Trusty 应用程序可以使用 SE 从密钥槽派生密钥。
• KEK2 熔丝密钥:一个 128 位 AES 密钥,已烧录到 KEK2 熔丝中。此密钥对软件不可见,但 Trusty 在启动期间通过安全引擎 (SE) 利用它来派生一个名为 KEK2 根密钥 (KEK2_RK) 的密钥。
• KEK2_RK:一个 128 位 AES 密钥,从 SE KEK2 密钥槽派生而来。此密钥不得用于加密用户数据;它仅用于获取 KEK2 派生密钥 (KEK2_DK)。
• KEK2_DK:一个 128 位 AES 密钥,从 KEK2_RK 派生而来。Trusty 使用遵循 NIST-SP-800-108 的密钥派生函数 (KDF) 来派生密钥。
NVIDIA 强烈建议您对安全密钥生成使用相同的 KDF。如果您的应用程序必须以不同的方式处理不同类型的敏感用户数据,请为每个用例生成一个 DK。
• EKB_EK:EKB 加密密钥,一个 128 位 AES 密钥,是 KEK2_DK 之一。它仅用于加密和解密 EKB。
• EKB_AK:EKB 身份验证密钥,一个 128 位 AES 密钥,是 KEK2_DK 之一。它仅用于加密和解密 EKB。
• FV:固定向量,一个固定的 16 字节值,它是 KEK2_RK 密钥派生的一部分。
• SE:Jetson 安全引擎。
• MB2:引导加载程序阶段,将加密的 EKB 内容传递到可信操作系统。引导流程在可信操作系统初始化之前执行 MB2。
注意 | 为了安全起见,这些密钥槽必须在 Trusty 使用后立即清除。 |
加密密钥 blob 概述
可信操作系统的常见用例是管理密钥。此类密钥通常必须以安全的方式在设备上配置,并且可供可信操作系统访问。您可以使用 EKB 来完成此操作。EKB 加密密钥 (EKB_EK) 从硬件支持的密钥派生而来,并且仅对安全世界可见。EKB 内容仅以明文形式对安全世界可见。
加密密钥 blob 格式
加密密钥 blob (EKB) 格式旨在尽可能通用,使您可以完全控制实际的密钥 blob 结构。EKB 二进制文件(.img 文件)有一个 16 字节的 EKB 标头,前缀为 EKB blob 的内容
EKB 二进制文件通常称为 eks.img,它是默认情况下刷写到 EKS 分区的 EKB 二进制文件的名称。
EKB 标头
EKB 标头信息由 MB2 引导加载程序使用。它必须与以下布局匹配
EKB_size(4 字节) | Magic(8 字节) | Reserved(4 字节) |
EKB_size 是 EKB 二进制文件的长度,从 Magic 字段开始。EKB_size 长四个字节,采用小端格式。其值必须比 EKB 二进制文件的字节长度小 4。
Magic 长八个字节,并且必须包含确切的字符串 "NVEKBP\0\0"。
Reserved 包含四个未使用的字节。NVIDIA 建议将这些字节设置为二进制零。
EKB 内容
EKB 内容完全由实现定义。对其没有限制,尽管它旨在保存加密密钥或类似数据。
EKB 内容部分中的任何数据在设备启动期间都可供 Trusty TA 访问。它对正常世界不可见为明文。
EKB 二进制文件大小限制
出于安全原因,EKB 二进制文件必须至少为 1024 字节长。它可能不超过 EKS 分区大小。如果 EKB 二进制文件的大小不在此范围内,则刷写将失败。
如果您只有很少的 EKB 内容,请将 EKB 二进制文件填充到至少 1024 字节的长度。NVIDIA 建议在加密之前使用随机数据填充二进制文件。Trusty 中的 TA 解密整个二进制文件,然后丢弃填充。
加密密钥 blob 生成和设备配置
NVIDIA 建议使用 128 位对称密钥加密 EKB 内容,该密钥是从烧录到设备 KEK2 熔丝中的硬件支持的 KEK2 熔丝密钥派生而来。
后面的章节将更详细地描述公式背后的操作。
1. 定义 EKB 内容的格式,并以明文形式生成 EKB 内容。
2. 生成 128 位对称 KEK2 熔丝密钥。
3. 将 KEK2 熔丝密钥烧录到设备的 KEK2 熔丝中。有关熔丝的更多信息,请参阅您的 Jetson 设备的
熔丝规范应用说明。
4. 将固定向量 (FV) 常量更改为随机生成的值。(此步骤不是必需的,但 NVIDIA 建议这样做。)
默认 FV 为 0xbad66eb4484983684b992fe54a648bb8。
5. 根据公式使用 AES ECB 加密计算 KEK2_RK
KEK2_RK = AES − 128 − ECB(FV, KEK2 熔丝密钥)
6. 遵循 NIST-SP-800-108 KDF 建议来派生 KEK2_DK。KEK2_DK 的此值将用作 EKB_EK。
7. 使用所需的加密算法和 EKB_EK 加密 EKB 内容明文,以获得 EKB 内容密文。
8. 将 EKB 标头附加到 EKB 内容密文,如
加密密钥 blob 格式部分所述。生成的文件是完全生成的 EKB 二进制文件。
9. 将 EKB 二进制文件刷写到 Jetson 设备的 EKS 分区。
此流程图说明了 EKB 生成过程
加密密钥 blob 解密
在启动期间,Trusty 内部的 TA 执行以下步骤
10. 确保 TA 中的 FV 与用于派生 KEK2_RK 的 FV 匹配。
11. 请求安全引擎 (SE) 使用公式派生 KEK2_RK
KEK2_RK = AES − 128 − ECB(FV, KEK2 熔丝密钥)
12. 遵循 NIST-SP-800-108 KDF 派生 KEK2_DK(也称为 EKB_EK)。
13. 将 EKB 内容密文映射到 TA 的内存中。此内存区域不在 TZDRAM 孔径内,因此您必须将内容复制到 TA 的堆中。
14. 使用所选的加密算法和 EKB_EK 解密 EKB 内容,以获得明文。(Trusty 中没有原生加密库,因此您必须选择并导入一个加密库。)
15. 在 TA 中根据需要在 TA 中使用 EKB 内容明文。
下图显示了 EKB 解密的过程。
SE 密钥槽清除
在 Trusty 从 KEK2 密钥槽派生的 KEK2_RK 中派生出 EKB_EK 之后,不再需要在密钥槽中保留 KEK2 熔丝密钥。NVIDIA 强烈建议从密钥槽中擦除密钥,以防止任何组件在设备启动后使用它。
hwkey-agent TA 演示了密钥槽清除过程。NVIDIA 建议您使用 hwkey-agent TA 中提供的 se_clear_aes_keyslots() 函数。此函数清除多个密钥槽作为安全预防措施。
SE 用法
Trusty 中的 TA 必须仅在启动期间使用 SE。在启动后从 Trusty 使用 SE 可能会导致系统崩溃,因为无法保证 SE 时钟已启用。这意味着必须在启动期间派生 EKB_EK 并清除密钥槽。启动后,您必须通过软件加密库使用 EKB_EK。
安全示例
下图概述了两个安全示例应用程序,CA 和 TA。CA 帮助用户使用 TA 中的两种不同类型的密钥加密或解密数据。TA 从 EKB 中提取用户定义的密钥,并从 SE SSK 密钥槽派生另一个密钥,即安全存储派生密钥(SSK 派生密钥)。
SSK 派生密钥在每个 Jetson Xavier NX 系列、Jetson AGX Xavier 系列和 Jetson TX2 系列设备上都是唯一的。一旦数据使用该密钥加密,它就会绑定到该设备。相比之下,用户定义的密钥对于每个设备都是相同的。CA 可以决定在每种情况下使用哪个密钥来执行加密操作。
下图描述了适用于安全示例应用程序的场景。
客户端应用程序:hwkey-app
客户端应用程序 (CA) 是一个命令行程序,说明如何使用可信应用程序 (TA) 中的密钥加密和解密数据。它为用户提供两个选项
• 在 TA 中使用 OpenSSL 库使用存储在 EKB 中的用户定义密钥。
• 使用硬件安全引擎 (SE) 的 SSK 密钥槽中的 SSK。该应用程序使用 tegra-crypto 库通过 SE 执行加密操作。
此选项默认禁用;您必须手动启用它。
CA 有两个主要功能区域
• OPENSSL_CRYPTO:使用 EKB 中用户定义的密钥,在 TA 中使用 OpenSSL 库执行加密操作。
• TEGRA_SE_CRYPTO:使用密钥槽中的 SSK,在 CA 中使用 tegra-crypto 库执行加密操作。
可信应用程序:hwkey-agent
TA 是一种后台服务,在启动时启动,它为两个密钥执行密钥管理:EKB 中的用户定义密钥和 SSK 派生密钥。它使用 OpenSSL 库中的加密服务。
TA 有两个主要功能区域
• 用户定义密钥和 SSK 派生密钥的密钥维护。
TA 从 SSK 密钥槽派生 SSK 派生密钥 (SSK_DK) 的方式与从 KEK2 密钥槽派生 KEK2_DK 的方式相同。这演示了如何从密钥槽派生密钥以实现安全目的。您可以对自己的需求使用相同的过程来派生 SSK_DK。
• OPENSSL_CRYPTO:提供一个接口,可以接收带有参数的 IPC 请求,这些参数指定
• 是否加密或解密
• 包含要加密的明文或要解密的密文的数据缓冲区
TA 默认使用用户定义的密钥。要将 SE 与 SSK 密钥槽一起使用,您必须手动启用它。
密钥维护和 EKB
示例应用程序的一个重要目的是演示从基于硬件的熔丝密钥派生密钥、将它们用于不同目的以及保护 EKB 中用户定义密钥的安全的安全技术。
包含一个密钥的 EKB 如下所示
EKB_header (16 字节) | EKB_cmac (16 字节) | Random_IV (16 字节) | EKB 密文 (16 字节) |
EKB 中的字段为
• EKB 标头:一个 16 字节的 EKB 标头。
• EKB_cmac:基于 AES-CMAC 算法的身份验证代码。这用于验证 Random_IV 和 EKB 密文的 EKB 内容。
• Random_IV:用于 EKB 内容加密和解密的随机初始向量。
• EKB 密文:加密的用户定义密钥。
您可以通过添加额外的(EKB_cmac、Random_IV、EKB 密文)字段集来向 EKB 添加其他密钥。您可以通过扩展脚本(请参阅
EKB 生成工具)来支持其他密钥。然后,EKB 布局如下所示
EKB_header (16 字节) | EKB_cmac_1 (16 字节) | Random_IV_1 (16 字节) | EKB 密文 (16 字节) | |
| | |
| | EKB_cmac_2 (16 字节) | Random_IV_2 (16 字节) | EKB 密文 2 (16 字节) | |
| · · · | |
| | EKB_cmac_n (16 字节) | Random_IV_ n (16 字节) | EKB 密文 n (16 字节) |
在使用中,TA 使用 EKB_AK 对 EKB 进行身份验证。只有在身份验证成功,确认 EKB 未被修改的情况下,它才会解密 EKB 密文。
熔丝密钥的 KDF
熔丝密钥在早期启动阶段加载到密钥槽中,在 Trusty 运行之前。由于软件无法从密钥槽中读取密钥,因此 TA 只能通过 AES-ECB 算法从密钥槽中派生密钥。使用来自 SE 的派生密钥,它被称为根密钥 (RK)。RK 不直接用于加密操作。使用 NIST-SP-800-108 KDF 建议来派生派生密钥 (DK),并将其用于进一步的加密操作。
因此,从熔丝密钥获取 DK 有两个步骤
1. RK = AES-ECB-128(熔丝密钥, FV)
2. DK = NIST-SP-800-108(RK)
示例应用程序使用 NIST-SP-800-108 中描述的计数器模式 KDF 和 CMAC 伪随机函数 (PRF)。
生产应用程序通常有多个需要不同密钥的用例。NVIDIA 强烈建议对不同目的使用不同的密钥,可以使用 KDF 派生多个密钥。
此概要介绍了熔丝密钥的 KDF 生成流程
1. FV(固定向量)。
使用随机数生成器生成 FV。NVIDIA 建议使用 /dev/random 或 /dev/urandom。
此命令生成一个 16 字节的随机数并以十六进制显示
$ openssl rand -rand /dev/urandom -hex 16 > iv_hex_file
示例应用程序需要两个 FV
• FV_for_ekb:用于从 KEK2 密钥槽派生 EKB 的 RK。
• FV_for_ssk_dk:用于从 SSK 密钥槽派生 SSK_DK 的 RK。
2. RK(根密钥):通过公式派生
KEK2_RK_for_ekb = AES-128-ECB(KEK2 密钥槽, FV_for_ekb)
SSK_RK = AES-128-ECB(SSK 密钥槽, FV_for_ssk_dk)
3. DK(派生密钥):通过公式派生
EKB_EK = NIST-SP-800-108(KEK2_RK_for_ekb, ...)
EKB_AK = NIST-SP-800-108(KEK2_RK_for_ekb, ...)
SSK_DK = NIST-SP-800-108(SSK_RK, ...)
NIST-SP-800-108 的伪代码
NIST-SP-800-108(KI, KO, L, context_string, label_string) {
uint8_t count = 0x01;
for (count=0x01; count<=L/128; count++) {
AES-128-CMAC(key=KI, count || label_string || 0x00 || context_string, output=&KO[count*128]);
}
}
其中
• KI 是 128 位输入密钥
• KO 是 128 位输出密钥
• L 为 128,是 KO 的位长度。
• context_string 和 label_string 具有下表所示的值
对于派生密钥 | context_string | label_string |
EKB_EK | "ekb" | "encryption" |
EKB_AK | "ekb" | "authentication" |
SSK_DK | "ssk" | "derivedkey" |
EKB 生成
如下图
密钥维护和 EKB 所示,示例应用程序的 EKB 布局旨在帮助您设计一种机制,该机制足够安全,可以保护 EKB blob 中的私有数据。示例应用程序在 EKB 中存储用户定义的密钥。您可以轻松扩展 EKB 布局,例如,通过为多个密钥添加多个部分。
NVIDIA 强烈建议您使用与示例程序相同的布局,或者将其替换为您知道的更安全的布局。
以下概要显示了创建 EKB 的最符合逻辑的操作顺序。
1. EKB 密文 = AES-128-CBC(IV=Random_IV, Key=EKB_EK, EKB 明文)
• Random_IV 是用于生成新 EKB blob 的初始向量。
• EKB 明文是明文形式的用户定义密钥。
2. EKB_cmac 是身份验证代码,将用于验证消息是否已被更改。
将 EKB_content(中间结果)计算为
EKB_content = Random_IV + EKB_ciphertext
然后将 EKB_cmac 计算为
EKB_cmac = AES-CMAC(Key=EKB_AK, EKB_content)
3. EKB blob = EKB 标头 + EKB_cmac + EKB_content
EKB 提取
以下概要显示了从 EKB 提取信息的最符合逻辑的操作顺序。它本质上是 EKB 生成过程的逆过程。
1. 计算 CMAC
AES-CMAC_verify(EKB_cmac, Key=EKB_AK, EKB_content)
2. 比较计算出的 CMAC 与 EKB 中的 CMAC。 如果它们匹配,则继续。
3. 计算 EKB 明文
EKB_plaintext = AES-128-CBC_decrypt(IV=Random_IV, Key=EKB_EK, EKB_ciphertext)
EKB 生成工具
在生成 EKB blob 之前,请参阅
安全启动主题,了解有关将密钥烧录到熔丝(包括 KEK2 熔丝)中的信息,以及在 Jetson 设备上使用 Trusty 的安全启动要求。
正如
熔丝密钥的 KDF解释的那样,您可以使用运行
openssl 工具的命令行生成固定向量 (FV) 或用户定义的密钥。 您可以分别生成这些项并将它们存储在不同的文件中。
请注意,对于 EKB 提取和 EKB 生成,FV 必须相同。 请务必谨慎,对 FV 保密。
此示例展示了如何运行 EKB 生成工具
$ python3 gen_ekb.py -kek2_key <kek2_fuse_key_file> \
-fv <fv_for_ekb_ek> \
-in_sym_key <sym_key_file> \
-in sym_key2 <sym2_key_file> \
-out <eks_image_file>
其中
• <kek2_fuse_key_file> 是存储在 KEK2 熔丝中的密钥。
• <fv_for_ekb_ek> 是用于从 KEK2 熔丝派生 RK 的固定向量 (FV)。 它必须与 hwkey-agent 中用于派生 KEK2 RK 以进行 EKB 加密和解密的 FV 相同。
• <sym_key_file> 是内核加密密钥。
• <sym2_key_file> 是磁盘加密密钥。
此密钥在两个参考实现中使用。 一个是由 hwkey-agent 和 hwkey-app 实现的安全示例。 此示例使用该密钥进行数据加密和解密。 在另一种情况下,该密钥是磁盘加密参考实现中 LUKS 密钥生成的源密钥。
• <eks_image_file> 是由 EKB 生成工具从加密二进制 Blob (EKB) 文件生成的镜像文件。 输出二进制 blob 文件旨在刷写到设备的 EKS 分区。
EKB 提取示例
有关如何执行 EKB 提取的示例,请参阅 hwkey-agent 可信应用程序的源代码。
硬件随机数生成器功能
许多用例需要安全的随机数源,用于密钥或密码生成、网络协议、密码学过程等。 Jetson 处理器通过 Linux 设备节点 /dev/random(它会阻塞,直到内核积累了足够的熵数据以返回请求的随机数据量)或 /dev/urandom(它不会阻塞)提供基于硬件的随机数源。
Trusty 在安全世界中提供了一个替代源,即 Trusty 随机数生成器 (RNG)。 它符合 NIST-SP 800-90 a/b/c 草案规范。 它可以自我重播种子,并生成真正随机且在结果范围内均匀分布的数字。
TA 可以提供 RNG 服务,CA 或其他 TA 可以与其通信以获取随机数。
此图显示了 Trusty RNG 的架构以及如何从安全世界中提取随机数。
RNG 功能的服务模型是
• 在 TA 初始化期间,hwkey-agent TA 创建 rng-srv 服务,这是一个 IPC 通道,可以接收和返回来自其他应用程序的随机数请求。
• 正常世界或安全世界中的其他应用程序可以通过初始化 IPC 通道从 rng-srv 请求随机数,以从 hwkey-agent TA 提取随机数。
Trusty 源代码包含一个名为 get_random() 的示例函数,该函数返回以这种方式获得的随机数。 请参阅 $TRUSTY_TOP/app/nvidia-sample/hwkey-agent/CA_sample/hwkey-app.c 中的源文件。
rng-srv 使用硬件安全引擎 (SE) RNG1 生成随机数。 这些数字符合 NIST-SP 800-90 a/b/c 草案规范。
RNG IPC 数据包的定义是
#define RNG_SRV_DATA_SIZE 2048
typedef struct rng_srv_msg {
uint32_t rng_size;
uint8_t rng_data[RNG_SRV_DATA_SIZE];
};
RNG 函数可以返回的最大随机数长度为 2048 字节。 如果您需要更长的随机数,请多次查询并将结果连接到您需要的长度。
Trusty OS 中的 AES-256 硬件密钥派生函数
Jetson Linux 可信操作系统 (TOS) 具有基于硬件的密钥派生功能 (KDF),您可以使用它从密钥加密密钥 (ODM KEK) 熔丝派生密钥。
AES-256 硬件 KDF 的流程
此图显示了从 ODM KEK 熔丝派生密钥的流程。
您可以将 ODM KEK 熔丝 KEK0 和 KEK1 作为单独的 128 位熔丝或单个 256 位熔丝寻址和加载,如下表所示
与安全启动相关的 ODM KEK 熔丝 Jetson Xavier NX 系列、Jetson AGX Xavier 系列和 Jetson TX2 系列设备 |
位大小 | 名称 | 密钥槽 | odmfuse.sh 设置的默认值 |
128 | KEK0 | 13 | 四个 32‑位寄存器,名为 KEK00 到 KEK03。 |
128 | KEK1 | 12 | 四个 32‑位寄存器,名为 KEK10 到 KEK13。 |
256 | KEK256 | 13 | 不是不同的熔丝; 将 KEK0 和 KEK1 作为单个 256‑位熔丝寻址。 |
128 | KEK2 | 11 | 四个 32‑位寄存器,名为 KEK20 到 KEK23。 |
BootROM 启动配置表 (BR-BCT) 中的 BctKEKKeySelect 标志确定 BootROM 如何将 KEK0 和 KEK1 加载到安全引擎 (SE) 密钥槽中
• 如果该标志设置为 0,则 BootROM 将 KEK0 和 KEK1 熔丝加载为两个 128 位密钥。
• 如果该标志设置为 1,则 BootROM 将熔丝加载为单个 256 位密钥。
BootROM 从存储在 ${Linux_for_Tegra_folder}/bootloader/t186ref/BCT/ 目录中的配置文件加载 BR BCT。 该文件的名称是
• 对于 Jetson Xavier NX 系列:tegra194-br-bct-qspi.cfg
• 对于 Jetson AGX Xavier 系列:tegra194-br-bct-sdmmc.cfg
• 对于 Jetson TX2 系列:emmc.cfg
要将标志设置为 1,例如
BctKEKKeySelect = 1;
在 BootROM 从 BR BCT 中的设置识别密钥配置源之后,您可以使用基于硬件的 NIST-SP 800-108 KDF 从 ODM KEK 熔丝派生密钥。 KDF 生成派生的密钥。
odmfuse.sh 脚本可以使用选项 --KEK0 和 --KEK1 将 KEK0 和 KEK1 刻录为单独的 128 位密钥,或者使用选项 --KEK256 将它们刻录为单个 256 位密钥。
API 函数
Jetson Linux 支持两种用于基于硬件的随机数生成的 API。
基于硬件的 AES-CMAC 函数与
OpenSSL CMAC 实现中的函数非常相似。 有关详细信息,请参阅
基于硬件的 AES-CMAC 函数。
NIST 800-108 密钥定义函数实现了 NIST-SP 800-108 中定义的计数器模式 KDF。 有关详细信息,请参阅
NIST 800-108 密钥定义函数。