AWS Nitro Enclaves 镜像与认证机制深度解析

本文深入探讨了AWS Nitro Enclaves的镜像构建流程、认证机制及其潜在安全风险,提供了构建安全enclave的实用建议,包括内核验证、PCR检查和安全签名实践等关键要点。

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

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

本文重点关注enclave镜像和认证过程。

构建和签名enclave时的安全建议(TL;DR)

  • 最小化构建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工具执行以下操作:

  • 从Docker镜像和一些预编译文件构建enclave镜像。
    • Docker用于创建enclave用户空间文件的存档。
    • 预编译二进制文件在本文后续描述。
  • 从enclave镜像启动enclave。

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

图1:构建enclave的流程

启动enclave时发生以下操作:

  • 内存和CPU从EC2实例释放并保留给enclave。
  • EIF复制到新保留的内存。
  • EC2实例请求Nitro Hypervisor启动enclave。
    • 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用于与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 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使用版本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架构不同,后者信任在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开头剥离字节并附加到第一个,PCR-0测量不会改变。这是一个滴答作响的管道炸弹,但目前不可利用。无论如何,我们建议尽可能检查PCR-1和PCR-2以及PCR-0。

另一个观察是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:私钥必须存储在本地文件中。

总体而言,我们建议在签名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

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