AWS Nitro Enclaves 深度解析:镜像构建与认证机制的安全实践

本文深入探讨AWS Nitro Enclaves的镜像构建流程与认证机制,揭示潜在安全风险并提供实用建议,包括内核验证、PCR检查、签名处理等关键安全实践,帮助开发者规避常见陷阱。

关于AWS Nitro Enclaves的几点说明:镜像与认证

AWS Nitro Enclaves是支持认证的锁定式虚拟机,属于可信执行环境(TEE),类似于Intel SGX,适用于运行高安全关键代码。然而,该平台缺乏全面文档和成熟工具链。因此,我们通过深入研究填补文档空白,重点发现安全陷阱并提供规避建议。

本文聚焦于enclave镜像和认证过程。以下是构建和签名enclave时避免安全陷阱的简要建议:

  • 最小化构建镜像时的隐式信任关系
  • 检查内核版本和哈希值
  • 审查内核配置和启动命令行
  • 验证预编译二进制文件(init可执行文件、NSM驱动和linuxkit工具)
  • 确认使用正确的Docker镜像构建enclave镜像
  • 从可信源获取AWS根证书并验证其哈希
  • 确保系统威胁模型考虑AWS作为集中信任点的事实
  • 除PCR-0外检查PCR-1和PCR-2
  • 注意EIF的元数据部分未经认证
  • 不要解析EIF签名——应重构并验证它们
  • 不要将未保护的私钥推送到EC2实例进行enclave签名
  • 不要对不可信EIF使用nitro-cli describe-eif命令

运行enclave

要运行enclave,需通过SSH连接AWS EC2实例并使用nitro-cli工具执行以下操作:

  1. 从Docker镜像和预编译文件构建enclave镜像
    • Docker用于创建enclave用户空间文件的归档
    • 预编译二进制文件将在后文详述
  2. 从enclave镜像启动enclave

enclave镜像采用enclave镜像文件(EIF)格式的二进制blob。

图1:构建enclave的流程

启动enclave时的底层操作:

  • 从EC2实例释放内存和CPU并保留给enclave
  • EIF复制到新保留的内存
  • EC2实例请求Nitro Hypervisor启动enclave
  • Nitro Hypervisor负责保护enclave(例如在内存返回EC2前清除)
  • enclave附加到父EC2实例且无法在实例间移动

enclave内执行的所有代码均由EIF提供。

EIF格式

EIF格式的最佳"规范"是aws-nitro-enclaves-image-format仓库中的代码。格式简单:头部和节数组,每节包含头部和二进制blob。

图2:EIF的头部和节结构

CRC32校验和基于头部(减去4字节校验和保留位)和所有节(含头部)计算。

EIF节类型包括:

  • Kernel:二进制bzImage文件
  • Cmdline:内核启动命令行字符串
  • Metadata:构建信息的JSON(如内核配置、Cargo和Docker版本)
  • Ramdisk:cpio格式的引导ramfs(含NSM驱动和init文件)和用户空间ramfs(含Docker镜像文件)
  • Signature:CBOR格式的元组向量(证书,签名)

EIF包含运行VM所需全部数据:内核镜像、命令行、引导二进制文件和用户空间文件系统。

信任谁?

在深入细节前,需了解EIF创建时涉及多个隐式信任关系。因此,验证数据如何进入EIF镜像至关重要。

验证EIF数据流需查看nitro-cli使用的enclave_build包。内核镜像(bzImage文件)、init可执行文件和NSM驱动预编译后存储在EC2实例的/usr/share/nitro_enclaves/blobs/文件夹,在安装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构建):

组件 EC2实例 aws-nitro-enclaves-cli aws-nitro-enclaves-sdk-bootstrap
Kernel 127b32…9821c4 127b32…9821c4 4b3719…016c58
Kernel config e9704c…7d9d35 e9704c…7d9d35 9e634d…663f99
Cmdline cefb92…ab0b0f cefb92…ab0b0f N/A
init 7680fd…a435bb e23a90…4272ea 601ec5…d4b25e
NSM driver 2357cb…8192c9 93d1f…657b50 96d0df…4f5306
linuxkit 31ed3c…035664 581ddc…2ee024 N/A

内核源代码安全获取且哈希一致。手动构建内核哈希不同可能因配置差异。可手动验证内核配置和启动命令行,故其哈希不重要。

有趣的是,init和NSM驱动的哈希完全不符。为确保这些可执行文件未被恶意修改,需从源代码构建并调试新构建与预编译版本的差异(使用GDB或Ghidra),或信任预编译文件安全。

接下来是ramdisk节,即存储二进制文件的cpio归档。每个EIF至少有两个ramdisk:

  • 第一个ramdisk含init可执行文件和NSM驱动
    • init安装NSM驱动,chroot到rootfs/目录,并调用.cmd文件的execvpe(使用.env文件的环境变量)
  • 第二个ramdisk从提供给nitro-cli的Docker镜像创建
    • 存储init用于pivot的命令(.cmd文件)、环境变量(.env文件)和Docker镜像的所有文件(rootfs/目录)
    • 命令和环境变量从Dockerfile解析

构建ramdisk的cpio归档时,nitro-cli使用linuxkit工具(与其他预编译文件一起下载)。AWS使用"稍加修改"的版本(故哈希不匹配)。linuxkit下载Docker镜像并提取文件,尝试制作完全相同的可复制副本。值得注意的是,nitro-cli使用过时的linuxkit 0.8版本。

图4:EIF创建过程示意图

nitro-cli获取用于构建EIF的Docker镜像的方式:

  • 若提供--docker-dir命令行选项,则本地构建镜像
  • 否则检查镜像是否本地可用
  • 若不可用,则使用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架构(信任分割 between Intel(认证密钥所有者)和云提供商)。

Hypervisor签名enclave哈希时,具体签名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开头剥离字节并附加到第一个,PCR-0测量不会改变。这是定时管道炸弹,但目前不可利用。无论如何,建议尽可能除PCR-0外检查PCR-1和PCR-2。

另一观察是EIF的元数据节未经认证。未指定用户如何及何时使用该节,故难以想象此属性的利用场景。只需确保系统安全不依赖该节内容。

在哪里签名?

最后讨论EIF的签名节。该节含CBOR编码的元组向量,每个元组是证书-签名对。签名是CBOR编码的COSE_Sign1结构,含编码有效负载(PCR索引-值对元组)、对有效负载的实际签名和一些元数据。证书为PEM格式。

1
2
3
4
5
6
7
Section = [(certificate, COSE structure), (certificate, COSE structure), ]
COSE structure = COSE_Sign1([(PCR index, PCR value), (PCR index, PCR value), ])
COSE_Sign1(payload) = structure {
    payload = payload
    signature = sign(payload)
    metadata = signing algorithm (etc)
}

当前EIF格式版本中,该节仅含PCR-0签名(整个enclave镜像的哈希)。(但注意可制作含多个签名元素的EIF;Hypervisor仍会运行,但不会验证第一个后的签名。)

签名代码由aws-nitro-enclaves-cose库实现。

PCR-8是EIF文件签名证书的哈希,计算如下:证书先从原始PEM格式解码并编码为DER。

1
PCR-8 = sha384(‘\0’*48 | sha384(SignatureSection[0].certificate))

如何验证签名?文档指示用户从COSE_Sign1对象解密有效负载以获取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实例运行。

1
2
nitro-cli build-enclave --docker-uri hello-world:latest --output-file
hello-signed.eif --private-key key_name.pem --signing-certificate certificate.pem

图7:私钥必须存储在本地文件。

总体建议不遵循AWS文档关于签名EIF的部分。以下是安全签名EIF的几种选项(按推荐顺序):

  1. 将私钥和Docker镜像推送到离线环境并在那里签名EIF
  2. 修改nitro-cli以启用更安全签名(使用HSM、KMS、keyring等)
  3. 等待支持KMS签名EIF的nitro-cli PR合并;这样无需自行修改nitro-cli
  4. 按AWS推荐将私钥推送到EC2实例并在那里签名EIF,但先用密码保护密钥(nitro-cli构建EIF时会询问密码)

如何解析?

既然知道enclave镜像的样子,接下来讨论如何解析。若熟悉文件格式解析器中的安全错误,可能已发现解析过程中的歧义和潜在问题。

有两个EIF解析器:

  • 公共解析器:nitro-cli describe-eif命令

  • 私有解析器:Nitro Hypervisor用于启动enclave

  • CRC32校验和是否验证?是。无效时enclave不启动。

  • EIF可有多于两个ramdisk节?是。所有ramdisk节简单拼接。

  • 可截断(破坏)ramdisk节中的cpio归档?是!Hypervisor忽略某些cpio错误。

  • EIF可有多于一个内核或cmdline节?可能不行,但难确保不可能。

  • 可交换不同类型的节(如将cmdline节放在内核节前)?是。这样做改变PCR-0测量。

  • EIF头部元数据中的节大小是否与节实际头部中的大小验证?是。

  • EIF节间可含数据?是。若含,CRC32校验和也计算该数据。

  • EIF头部的num_sections字段是否与节大小和节偏移数组中的项验证?否。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格式 信任谁? 认证什么? 在哪里签名? 如何解析? 为什么相关? 最近文章 我们构建了MCP始终需要的安全层 利用废弃硬件中的零日漏洞 Inside EthCC[8]:成为智能合约审计员 使用Vendetect大规模检测代码复制 构建安全消息传递很难:对Bitchat安全辩论的 nuanced 看法 © 2025 Trail of Bits. 使用Hugo和Mainroad主题生成。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计