证明:PyPI 上新一代的签名

PyPI 引入了新的安全特性,即 Index-Hosted Digital Attestations,通过 Sigstore 将 Trusted Publishing 和软件包分发联系起来,利用密钥签名将分发包的身份(名称和摘要)与其来源(生成它的 GitHub 仓库或其他来源)进行加密绑定。

过去一年,我们与 Python 包索引 (PyPI) 合作开发了一个新的 Python 生态系统安全功能:索引托管的数字证明,如 PEP 740 中所规定。

这些证明改进了传统的 PGP 签名(该签名已在 PyPI 上禁用)通过提供密钥可用性索引可验证性密码学强度来源属性,使我们朝着为我们的软件供应链提供整体的、密码学上可验证的来源更近了一步。

好消息:如果你已经使用 Trusted Publishing 将软件包发布到 PyPI,你可能不必更改任何内容官方 PyPI 发布工作流程 内置了证明支持,默认情况下已启用,版本为 v1.11.0 及更高版本。换句话说,只要你已经使用(或升级到)pypa/gh-action-pypi-publish@v1.11.0 或更高版本并使用 Trusted Publisher,你的软件包将默认获得构建来源!

默认启用是我们的一个关键设计约束:我们想要一个可以与现有发布身份集成的证明功能,绕过传统的数字签名设计中反复出现的密钥和身份管理挑战。Sigstore 为这些挑战提供了解决方案:它对基于身份的 无密钥签名 的支持提供了 PyPI 对 Trusted Publishing 的支持与软件包来源之间的公开可验证的链接。

查看 官方 PyPI 文档,获取有关如何创建和使用索引托管证明的实用信息,并在此处阅读我们关于这些证明如何工作以及我们认为它们未来发展方向的技术总结!

另请阅读 PyPI 博客上的官方公告

背景:Trusted Publishing

去年,我们PyPI 合作设计并实施了 Trusted Publishing,这是一种新的、更方便、更安全的方式将软件包上传到 PyPI。由于其可用性的优势,我们看到 Trusted Publishing 在过去的 18 个月中取得了 巨大 的成功:超过 19,000 个独立项目已注册 Trusted Publisher,并且这些项目总共使用 Trusted Publishing 将近 50 万个文件发布到 PyPI:

我们有一篇完整的单独的 关于 Trusted Publishing 和 PyPI 的博客文章,但简要概括如下:

  • Trusted Publishing 消除了对手动配置和范围界定的 API Token的需求
  • 项目声明了经批准的 Trusted Publisher(GitHub、GitLab、Google Cloud Build、ActiveState 等)身份,这些身份可以上传新版本。
  • 为了确保来自这些身份的请求的真实性(即,声称是它们的 CI/CD 工作流程),Trusted Publishing 使用通过 OpenID Connect (OIDC) 的公钥加密
  • OIDC 流程允许 Trusted Publisher 自动获取 PyPI API Token,而无需用户干预,从而减少了用户错误的机会,例如凭据泄露和意外的过度范围界定。
  • 通过此 OIDC 流程发出的结果Token是短期的最小范围的,从而减少了攻击者囤积它们以供将来使用或使用单个凭据在不同项目之间进行转移的能力。

Trusted Publishing 在 PyPI 上的成功也引起了其他生态系统的兴趣:RubyGems 在几个月后就实现了它,而 Rust 的 crates.io 有一个开放的 RFC 用于它!

从 Trusted Publishing 到 Sigstore

Trusted Publishing 将 PyPI 托管的项目连接到密码学上可验证的计算机身份(例如 release.yml @ github.com/example/example),这些身份处理发布。

这对于消除手动 API Token流程非常有用,但它为我们提供了更多基本的东西:来源!

特别是,在 GitHub(或 GitLab 等)打包工作流程的上下文中,在 OIDC 凭据中找到的计算机身份为我们提供了一些类似于“发布来源”的东西:一组关于存储库和工作流程状态的声明,这些声明对应于软件包发布到 PyPI 的时间。

但是,以 OIDC 凭据的形式,此来源对外部用户来说并没有立即的价值:

  • PyPI 无法共享 凭据本身,因为它本质上是秘密材料。即使有适当的控制(过期和固定的受众),PII 泄露和行为不端的 JWT 验证器的风险也太大了,无法冒险将其披露以供外部(意味着非 PyPI)验证。
  • PyPI 可以 披露凭据中的声明,例如通过发布元数据,其效果是“项目 sampleproject 由从 pypa/sampleproject 运行的 GitHub 工作流程 pypi-publish.yml 发布。” 这将导致一个模型,下游用户被迫相信 PyPI 诚实地提供这些声明。

这就是 Sigstore 的用武之地。我们有 另一篇 完整的单独的 关于 Sigstore 及其工作原理的博客文章,但对我们来说,关键部分是 Sigstore 通过免费的、公开可访问的、可审计的证书颁发机构(Fulcio)将短期的签名密钥与机器身份绑定。

Fulcio 接受 OIDC 凭据形式的机器身份,这意味着 PyPI 的 Trusted Publishing 流程与 Sigstore 签名隐式兼容:Trusted Publisher 需要做的就是使用 OIDC 凭据向 Fulcio 提交证书签名请求,并接收用于后续使用的签名证书。

Fulcio 会将 OIDC 凭据中的适当声明嵌入到公共证书中,从而为我们提供公开可验证的来源,而无需披露凭据本身单方面信任 PyPI 来正确提供它!

此过程涉及的步骤可能有点难以理解,因此让我们将其可视化。这是“传统”的 Trusted Publishing 流程,在 Sigstore 参与之前:

然后,在 Sigstore 参与的情况下:

请注意,虽然流程中有一个实体(Sigstore),但从用户的角度来看,没有任何改变:他们所需要的只是他们的一次性 Trusted Publisher 配置,该配置来自原始流程。

从 Sigstore 到证明和来源

Sigstore 通过为我们提供公开可验证的凭据(以 X.509 证书的形式)来缩小 Trusted Publishing 和来源之间的差距,该凭据将临时密钥对绑定到机器身份(例如,发布到 PyPI 的 GitHub 存储库和工作流程)。

但是,还剩下最后一步:Sigstore 颁发的证书绑定到 Trusted Publishing 身份,但它本身不对正在发布的东西(即,实际的 Python 软件包分发)进行签名。

为了涵盖后者,我们需要使用我们的临时密钥对来签署软件包分发的证明,以密码学方式将分发本身的身份(其名称和摘要)绑定到其来源(实际生成它的 GitHub 存储库或其他来源)。

这就是 PEP 740 的用武之地。PEP 740 通过一个固定的证明有效负载将 Sigstore 和 Trusted Publishing 与实际的软件包分发结合在一起,该有效负载本身在 in-toto 证明框架 的范围内定义。

以下是为 sigstore v3.5.1 生成的实际证明的示例:

然后,这些证明由临时密钥对的私有部分签名,该密钥对本身绑定到 X.509 证书,从而完成了分发身份(文件名和摘要)与来源(嵌入到 X.509 证书中的 OIDC 声明)的完整绑定,这种方式可由 PyPI 本身验证(因为 OIDC 声明对应于用户注册的 Trusted Publisher 身份)。

当然,仅仅生成证明是不够的,还需要存储这些证明,以便用户可以自行验证它们!PEP 740 也定义了这一点:使用证明上传的分发在 JSON simple API 中获得一个 provenance 键,并在 PEP 503 索引中获得相应的 data-provenance 属性。

这些字段包含指向 provenance 对象 的 URL,该对象是每个分发的一个或多个 证明对象 的汇总,以及 PyPI 用于验证这些证明的 Trusted Publisher 身份。我们可以浏览这些对象的内部结构,以返回到我们上面的原始有效负载:

这会让我们处于什么位置?

截至 10 月 29 日,对于任何通过 GitHub 的 PyPA 发布操作 使用 Trusted Publishing 的人来说,证明都是默认设置。这意味着大约 20,000 个软件包现在可以默认证明其来源,而无需进行任何更改。我们预计随着时间的推移,这个数字也会增加,因为越来越多的项目(尤其是较新的项目)默认选择 Trusted Publishing,因为它既是用户友好的选择,也是更安全的替代手动配置 API Token的选择。

然而,生成证明的软件包总数只是一个角度,并且可以说是不完整的:软件包的证明价值与其“重要性”密切相关,也就是说,依赖于它的用户或下游的数量。PyPI 不知道项目的依赖项,但总下载量是项目中生态系统中相对重要性的有力衡量标准。

为了深入了解后者,我们构建了 Are We PEP 740 Yet?,它跟踪了 PyPI 上 360 个下载量最多的软件包对 PEP 740 证明的采用情况:

到目前为止,360 个下载量最多的软件包中有 5% 上传了证明。但是,有一个混淆因素:自启用证明以来,大约三分之二的下载量最多的软件包根本没有更新,这意味着我们尚不知道一旦他们发布新版本,有多少拥有证明!

我们将从这里走向何方?

所有这些工作中明显缺少的一件事是:下游验证

如所指定的,PEP 740 仅涉及索引本身:它告诉 PyPI 如何接收和验证证明以用于其自身目的,以及如何在公共索引端点上重新分发它们,但它没有强制(甚至定义)安装客户端(如 pipuv)的验证流程。

在实践中,这意味着索引托管证明的短期影响是有限的:它们向 PyPI 中使用的 Trusted Publisher 身份引入了透明度,但下游客户端仍然需要信任 PyPI 本身才能诚实地提供证明。

这不是一个可以接受的最终状态(密码学证明只有在实际验证的情况下才具有防御属性),因此我们正在寻找将验证引入到各个安装客户端的方法。特别是,我们目前正在开发 pip 的插件架构,该架构将使用户能够将 验证逻辑加载 直接加载到他们的 pip install 流程中。

从长远来看,我们可以做得更好:进行“一次性”验证意味着客户端不记得哪些身份应该被信任用于哪些分发。为了解决这个问题,安装工具需要一个“ 首次使用时信任”的签名身份概念,这意味着如果证明身份发生更改(或者软件包在版本之间变为未经证明),后续安装可以由用户停止和检查

如果这听起来像是一个 lockfile 问题,那是因为它确实是!我们正在密切关注 PEP 751,因为它定义了我们需要在其中存储预期分发身份的元数据格式。一旦 Python 生态系统开始采用标准化 lockfile,我们将能够使用它们来存储和验证身份,就像今天使用哈希来验证分发完整性一样。

总而言之,在常见的默认安装流程在底层验证证明之前,我们还有一段路要走。但是,与早期尝试索引托管签名不同,我们对如何实现这一目标有很好的想法。与此同时,确实有一些人群可以尽早利用 PyPI 新托管的证明:

  • 研究人员:PEP 740 证明建立在 Sigstore 之上,并提供了一个关键的可验证的缺失链接,该链接连接了源代码存储库和软件包(因为它们出现在 PyPI 上)。这使它们成为安全和供应链研究的绝佳数据来源!
  • 事件响应者:如果可用,证明会大大缩短和简化事件调查中最烦人且容易出错的部分:将特定工件追溯到其来源,确切地了解何时以及如何生成它等等。
  • 对其构建系统具有完全控制权的用户:如果你维护一个完全控制其 Python 软件包依赖项(即,不使用 pip 或其他工具进行解析和安装)的开源或专业项目,那么你可能可以将证明验证直接集成到你的构建过程中!查看我们的 pypi_attestations 文档 以获取入门指南。
  • 原文链接: blog.trailofbits.com/202...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Trail of Bits
Trail of Bits
https://www.trailofbits.com/