AWS Nitro Enclaves 笔记:镜像与认证机制
AWS Nitro Enclaves 是一种支持认证的锁定虚拟机,属于可信执行环境(TEE),类似于 Intel SGX,适用于运行高度安全关键的代码。然而,该平台缺乏全面的文档和成熟工具。因此,我们进行了深入研究,以填补文档空白,并发现安全陷阱,提供避免建议。
运行 Enclave
要运行 enclave,需通过 SSH 连接到 AWS EC2 实例,并使用 nitro-cli 工具执行以下操作:
- 从 Docker 镜像和一些预编译文件构建 enclave 镜像。
- Docker 用于创建 enclave 用户空间文件的存档。
- 预编译二进制文件在后续章节中描述。
- 从 enclave 镜像启动 enclave。
enclave 镜像是一个二进制 blob,格式为 enclave 镜像文件(EIF)。
图 1:构建 enclave 的流程
启动 enclave 时,底层发生以下操作:
- 内存和 CPU 从 EC2 实例释放并保留给 enclave。
- EIF 复制到新保留的内存。
- EC2 实例请求 Nitro Hypervisor 启动 enclave。
- Nitro Hypervisor 负责保护 enclave(例如,在内存返回 EC2 实例前清除内存)。 enclave 附加到其父 EC2 实例,无法在 EC2 实例间移动。所有在 enclave 内执行的代码都在 EIF 中提供。
EIF 格式
EIF 格式的最佳“规范”是 aws-nitro-enclaves-image-format 仓库中的代码。EIF 格式相当简单:一个头部和一个部分数组。每个部分是一个头部和一个二进制 blob。
图 2:EIF 的头部和部分
CRC32 校验和计算覆盖头部(减去保留给校验和本身的 4 字节)和所有部分(包括头部)。
EIF 部分有五种类型:
- Kernel:二进制,bzImage 文件。
- Cmdline:字符串,内核的启动命令行。
- Metadata:JSON,构建信息,如内核配置和使用的 Cargo 与 Docker 版本。
- Ramdisk:cpio,引导 ramfs,包括 NSM 驱动和 init 文件;用户空间 ramfs,包括来自 Docker 镜像的文件。
- Signature:CBOR,形式为(证书,签名)的元组向量。
因此,EIF 包含运行 VM 所需的所有内容:内核镜像、命令行、引导二进制文件(NSM 驱动和 init 可执行文件)和用户空间文件系统。
你信任谁?
在深入细节前,您应知道,在创建 EIF 时,流入 EIF 的数据涉及多个隐式信任关系。因此,验证数据如何进入 EIF 镜像至关重要。
要验证 EIF 镜像的数据流,我们需要查看 nitro-cli 工具使用的 enclave_build 包。
内核镜像(bzImage 文件)、init 可执行文件和 NSM 驱动是预编译的,存储在 /usr/share/nitro_enclaves/blobs/ 文件夹(在 EC2 实例上)。它们在安装 aws-nitro-enclaves-cli-devel 包时被拉到实例。
图 3:Nitro Enclaves CLI 安装文档的一部分
- 下载并构建内核,使用自定义内核配置。
- 使用 gpg2 工具验证内核签名(信任属于 torvalds@kernel.org 和 gregkh@kernel.org 的密钥)。
- 构建用于引导系统的 init 可执行文件。
- 构建 enclave 用于与 Nitro Hypervisor 通信的 NSM 驱动。
二进制文件也可以在 aws-nitro-enclaves-cli 仓库中找到。我们可以比较来自三个源的预编译二进制文件的 SHA-384 哈希:EC2 实例、aws-nitro-enclaves-cli 仓库和由 aws-nitro-enclaves-sdk-bootstrap 仓库生成的(针对 nitro-cli 版本 1.2.2):
在 EC2 实例中 | 在 aws-nitro-enclaves-cli 中 | 使用 aws-nitro-enclaves-sdk-bootstrap 构建 |
---|---|---|
Kernel: 127b32…9821c4 | Kernel: 127b32…9821c4 | Kernel: 4b3719…016c58 |
Kernel config: e9704c…7d9d35 | Kernel config: e9704c…7d9d35 | Kernel config: 9e634d…663f99 |
Cmdline: cefb92…ab0b0f | Cmdline: cefb92…ab0b0f | N/A |
init: 7680fd…a435bb | init: e23a90…4272ea | init: 601ec5…d4b25e |
NSM driver: 2357cb…8192c9 | NSM driver: 93d1f…657b50 | NSM driver: 96d0df…4f5306 |
linuxkit: 31ed3c…035664 | linuxkit: 581ddc…2ee024 | N/A |
内核源代码安全获取,哈希一致。手动构建的内核哈希与预编译内核不同,可能是因为配置不同。我们可以手动验证内核配置和启动命令行,因此它们的哈希不那么重要。
有趣的是,init 和 NSM 驱动的哈希完全不同。为确保这些可执行文件未被恶意修改,我们必须从源代码构建它们,并调试新构建版本与预编译版本之间的差异(使用 GDB 或 Ghidra 等工具)。或者,我们必须信任预编译文件是安全使用的。
接下来是 ramdisk 部分,它们只是存储二进制文件的 cpio 存档。每个 EIF 中至少有两个 ramdisk:
- 第一个 ramdisk 包含 init 可执行文件和 NSM 驱动。
- init 安装 NSM 驱动,chroot 到 rootfs/ 目录,并调用 execvpe 在 .cmd 文件上,使用来自 .env 文件的环境变量。
- 第二个 ramdisk 从提供给 nitro-cli 命令的 Docker 镜像创建。
- 存储 init 用于 pivot 的命令(在 .cmd 文件中)、环境变量(在 .env 文件中)和来自 Docker 镜像的所有文件(在 rootfs/ 目录中)。
- 命令和环境变量从 Dockerfile 解析。
为构建 ramdisk 的 cpio 存档,nitro-cli 工具使用 linuxkit 工具,该工具与其他预编译文件一起下载。AWS 使用“略微修改”的版本(这就是哈希不匹配的原因)。linuxkit 下载 Docker 镜像并从中提取文件,尝试制作相同、可复制的副本。值得注意的是,nitro-cli 使用版本 0.8 的 linuxkit,该版本已过时。
图 4:EIF 创建方式的描述
nitro-cli 获取用于构建 EIF 的 Docker 镜像的方式如下:
- 如果提供 –docker-dir 命令行选项,nitro-cli 在本地构建镜像。
- 否则,nitro-cli 检查镜像是否在本地可用。
- 如果不可用,则使用 shiplift 库和本地文件中的凭据拉取镜像。 linuxkit 也尝试使用本地可用镜像;如果镜像不可用,则使用通过 docker login 命令获得的凭据从远程注册表拉取。
以可复制、透明且易于审计的方式从 Docker 文件生成 enclave 很棘手——您可以在 Artur Cygan 的“增强 SGX enclave 的信任”博客文章中阅读更多相关内容。在构建 EIF 时,您至少应确保 nitro-cli 使用正确的镜像。为此,请查阅 Docker 构建日志(因为 Docker 镜像和守护进程不存储镜像来源信息)。
你认证什么?
AWS Nitro Enclaves 的主要特性是加密认证。运行中的 enclave 可以请求 Nitro Hypervisor 计算(测量)enclave 代码的哈希,并使用 AWS 的私钥签名,或者更准确地说,使用由证书签名的证书签名……由 AWS 根证书签名。
您可以使用加密认证功能在 enclave 源代码和实际执行的代码之间建立信任。只需确保从可信源获取 AWS 根证书并验证其哈希。
重要的是,AWS 拥有认证密钥和基础设施。这意味着您必须完全信任 AWS。如果 AWS 被入侵或恶意行为,游戏就结束了。这种安全模型与 SGX 架构不同,在 SGX 中,信任在 Intel(认证密钥所有者)和云提供商之间分配。
当 Hypervisor 签名 enclave 的哈希时,它 specifically 签名 aws-nitro-enclaves-nsm-api 仓库中指定的 CBOR 编码文档。文档中有几个项目,但目前我们对平台配置寄存器(PCR)感兴趣,这些是与 enclave 关联的测量(加密哈希)。前三个 PCR 是 enclave 代码的哈希。
图 5:enclave 的前三个 PCR
PCR 0 到 2 只是部分数据的 SHA-384 哈希:
- PCR-0: sha384(‘\0’*48 | sha384(Kernel | Cmdline | Ramdisk[:]))
- PCR-1: sha384(‘\0’*48 | sha384(Kernel | Cmdline | Ramdisk[0]))
- PCR-2: sha384(‘\0’*48 | sha384(Ramdisk[1:]))
如您所见,部分数据之间没有域分离——部分只是连接在一起。此外,PCR 哈希不包括部分头部。这意味着我们可以在相邻部分之间移动字节而不改变 PCR。例如,如果我们从第二个 ramdisk 的开头剥离字节并将其附加到第一个 ramdisk,PCR-0 测量不会改变。这是一个滴答作响的管道炸弹,但目前不可利用。无论如何,我们建议尽可能检查 PCR-1 和 PCR-2,以及 PCR-0。
另一个观察是 EIF 的元数据部分未经认证。未指定用户应如何以及何时使用该部分,因此很难想象此属性的利用场景。只需确保您的系统安全不依赖于该部分的内容。
你在哪里签名?
最后,我们将讨论 EIF 的签名部分。此部分包含 CBOR 编码的元组向量,每个元组是证书-签名对。签名是 CBOR 编码的 COSE_Sign1 结构,包含编码的有效负载(PCR 索引-值对的元组)、对有效负载的实际签名和一些元数据。证书为 PEM 格式。
|
|
在当前版本的 EIF 格式中,该部分仅包含 PCR-0 的签名,即整个 enclave 镜像的哈希。(但请注意,您可以制作具有多个签名元素的 EIF;它仍将由 Hypervisor 运行,但不会验证第一个之后的签名。)
签名代码由 aws-nitro-enclaves-cose 库实现。
PCR-8 是 EIF 文件签名证书的哈希,计算如下。证书首先从其原始 PEM 格式解码并编码为 DER。
|
|
现在,如何验证签名?文档指示用户从 COSE_Sign1 对象解密有效负载以获取 PCR 索引-值对,并将 PCR 值与预期 PCR 比较。我们认为这里存在术语问题,他们意思是验证实际签名,然后从有效负载中提取 PCR 并与预期 PCR 比较。然而,我们建议从预期 PCR 重建 COSE_Sign1 有效负载,并针对该有效负载验证签名。这应避免因无效解析而遇到错误。(我们在下一节讨论此类错误。)
签名 enclave 的官方方式是在 EC2 实例上使用 nitro-cli 工具(图 6)。这迫使您将私钥推送到实例(图 7)。这真的不是处理私钥的理想方式。更糟糕的是,AWS 文档未指示用户使用密码保护密钥……
但没有什么阻止您在 EC2 实例之外运行 nitro-cli,甚至可以在离线环境中运行。毕竟,EIF 只是一堆头部和二进制 blob——构建和签名镜像不需要 Nitro Hypervisor。AWS 仓库甚至有一个在 Docker 容器中构建 EIF 的示例。此外,aws-nitro-enclaves-cli 仓库中有一个待处理的 PR,一旦合并,将启用使用 KMS 签名 EIF。
图 6:AWS 文档声明 nitro-cli 必须在 EC2 实例上运行。
|
|
图 7:私钥必须存储在本地文件中。
总体而言,我们建议在签名 EIF 时不遵循 AWS 文档。相反,以下是确保 EIF 安全签名的几个选项(按推荐顺序):
- 将您的私钥和 Docker 镜像推送到离线环境并在那里签名 EIF。
- 修改 nitro-cli 以启用更安全的签名(使用 HSM、KMS、keyring 等)。
- 等待启用使用 KMS 签名 EIF 的 nitro-cli PR 合并;这样,您就不必自己修改 nitro-cli 来这样做。
- 按照 AWS 推荐将私钥推送到 EC2 实例并在那里签名 EIF,但首先用密码保护密钥。(nitro-cli 在构建 EIF 时会询问密码。)
你如何解析?
现在我们知道 enclave 镜像的样子,我们将讨论如何解析它。如果您熟悉文件格式解析器中的安全错误,您可能已经发现了解析过程中的歧义和潜在问题。
有两个 EIF 解析器:
- 公共的:nitro-cli describe-eif 命令
- 私有的:由 Nitro Hypervisor 用于启动 enclave
我们关心的解析器是私有的——它向 Hypervisor 提供 EIF 的实际视图。然而,它未开源,且没有 EIF 格式的规范,因此我们无法了解私有解析器如何工作。要了解私有解析器的行为,我们必须将其视为黑盒并运行实验。通过修改有效的 EIF 并尝试在 Hypervisor 上运行它们,我对以下问题得出了一些答案,其中一些包含在我提交给 aws-nitro-enclaves-image-format 仓库的问题中:
- CRC32 校验和是否验证?是。如果 CRC32 校验和无效,enclave 不会启动。
- EIF 可以有超过两个 ramdisk 部分吗?是。所有 ramdisk 部分只是连接在一起。
- 您可以截断(损坏)ramdisk 部分中的 cpio 存档吗?是!Hypervisor 忽略一些 cpio 错误。
- EIF 可以有多个内核或 cmdline 部分吗?可能不,但很难确保不可能。
- 您可以交换不同类型的部分(例如,将 cmdline 部分放在内核部分之前)吗?是。这样做会改变 PCR-0 测量。
- EIF 头部元数据中指示的部分大小是否针对部分实际头部中指示的大小进行验证?是。
- EIF 可以在其部分之间包含数据吗?是。如果是,CRC32 校验和也计算在该数据上。
- EIF 头部的 num_sections 字段是否针对 section_sizes 和 section_offsets 中的项目进行验证?不。num_sections 之后的项目被忽略。
- section_sizes 数组中的大小是否包括部分头部?不。数组仅存储数据长度。
- EIF 可以在签名部分中有多个 PCR 索引-值元组吗?不。
- EIF 可以使用空的 PCR 索引-值向量吗?不。
- 您可以签名 PCR-0 以外的 PCR 吗?复杂,但不。PCR 索引可以是任意数据(甚至不是数字),但值必须是 PCR-0 值。
- EIF 可以在签名部分中存储多个证书-签名对吗?是。
- 所有证书-签名对都验证吗?不。仅验证第一对。
如果您将上述发现与 nitro-cli 解析器代码比较,您会发现两个解析器工作方式不同。也许最重要的区别是 nitro-cli 解析器不尊重头部元数据,如 num_sections 和部分偏移。因此,nitro-cli 解析器可能产生与 Hypervisor 解析器不同的测量。我们建议不要使用 nitro-cli describe-eif 命令来了解不可信 EIF 的 PCR。相反,从源构建您的 EIF 或运行它们并使用 nitro-cli describe-enclaves 命令。该命令咨询 Hypervisor 获取测量。
为什么这相关?
我们在 TEE(如 AWS Nitro Enclaves)中运行代码时,该代码是高度安全关键的,因此我们必须正确处理细节。但 AWS Nitro Enclaves 的文档严重缺乏,使得理解这些细节困难。该功能也缺乏成熟工具,并包含几个安全陷阱。因此,如果您要使用 AWS Nitro Enclaves,请务必遵循本文开头提供的清单!如果您需要进一步指导,我们的 AppSec 团队定期举行办公时间。联系我们安排会议,您可以向我们的专家提问任何问题。
要了解更多关于 AWS 的信息,请查看 Scott Arciszewski 的博客文章“云密码学揭秘:Amazon Web Services”和 Joop van de Pol 的关于 TEE 特定问题的博客文章“翻转比特的踪迹”。
如果您喜欢这篇文章,请分享: Twitter LinkedIn GitHub Mastodon Hacker News
页面内容 运行 enclave EIF 格式 你信任谁? 你认证什么? 你在哪里签名? 你如何解析? 为什么这相关? 最近帖子 使用 Deptective 调查您的依赖项 系好安全带,Buttercup,AIxCC 的评分回合正在进行中! 使您的智能合约超越私钥风险 Go 解析器中意外的安全陷阱 我们从审查 Silence Laboratories 的首批 DKLs23 库中学到了什么 © 2025 Trail of Bits。 使用 Hugo 和 Mainroad 主题生成。