Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 480 – 应对 PyPI 泄露:软件包端到端签名

作者:
Trishank Karthik Kuppusamy <karthik at trishank.com>,Vladimir Diaz <vladimir.diaz at nyu.edu>,Justin Cappos <jcappos at nyu.edu>,Marina Moore <mm9693 at nyu.edu>
BDFL 代表:
Donald Stufft <donald at stufft.io>
讨论列表:
Discourse 帖子
状态:
草稿
类型:
标准跟踪
主题:
打包
依赖:
458
创建:
2014 年 10 月 8 日

目录

摘要

提议对 PEP 458 进行扩展,以添加对端到端签名和最大安全模型的支持。端到端签名允许 PyPI 和开发者都为客户端下载的分发版进行签名。由 PEP 458 提出的最小安全模型支持分发版的持续交付(因为它们由在线密钥签名),但该模型无法在 PyPI 泄露的情况下保护分发版。在最小安全模型中,攻击者如果获取了存储在 PyPI 基础设施上的签名密钥,则可以为恶意分发版进行签名。本 PEP 中描述的最大安全模型保留了 PEP 458 的优势(例如,上传到 PyPI 的分发版可立即使用),但此外还确保如果 PyPI 泄露,最终用户不会有安装伪造软件的风险。

此 PEP 需要对 PyPI 基础设施进行一些更改,以及对希望参与端到端签名的开发人员的一些建议更改。这些更改包括更新 PEP 458 中的元数据布局以包含对开发者密钥的委托,添加一个将开发者密钥注册到 PyPI 的流程,以及对利用端到端签名的开发人员的上传工作流程进行更改。所有这些更改将在本 PEP 的后面详细描述。希望利用端到端签名的包管理器无需执行除使用 PEP 458 中描述的元数据之外的任何额外工作。

此 PEP 讨论了对 PEP 458 进行的更改,但排除了其信息元素,主要侧重于最大安全模型。例如,此处不涵盖更新框架概述或 PEP 458 中的基本机制。对 PEP 458 的更改包括修改快照流程、密钥泄露分析、审计快照以及在 PyPI 泄露时应采取的步骤。PyPI 可能建议的签名和密钥管理流程进行了讨论,但没有严格定义。如何实施发布流程以管理密钥和元数据留给签名工具的实施者。也就是说,此 PEP 界定了开发人员必须上传到元数据中以支持分发版的端到端验证的预期加密密钥类型和签名格式。

PEP 状态

社区从 2014 年到 2018 年讨论了此 PEP。由于实施此 PEP 需要大量工作,因此讨论被推迟到 PEP 458 中前置步骤批准之后。截至 2020 年年中,PEP 458 已获批准,并且正在实施中,PEP 作者的目标是获得批准,以便他们可以为实施获得适当的资金。

基本原理

PEP 458 提出了如何将 PyPI 与更新框架 (TUF) [2] 集成。它解释了如何使 pip 等现代包管理器更安全,以及如果 PyPI 在服务器端修改为包含 TUF 元数据,可以防止哪些类型的攻击。包管理器可以引用 PyPI 上可用的 TUF 元数据以更安全地下载分发版。

PEP 458 还描述了 PyPI 存储库的元数据布局,并采用了最小安全模型,该模型支持项目的持续交付,并使用在线加密密钥来签署开发人员上传的分发版。尽管最小安全模型可以防御大多数对软件更新程序 [5] [6] 的攻击(例如,混合匹配和额外依赖项攻击),但可以对其进行改进以支持端到端签名,并在 PyPI 泄露时禁止伪造的分发版。

PEP 480PEP 458 的基础上,增加了对开发者签名的支持,并减少了对在线密钥的依赖以防止恶意分发版。 PEP 458 和最小安全模型的主要优势是自动化和简化的发布流程:开发人员可以上传分发版,然后让 PyPI 为其分发版签名。发布流程的很大一部分由在线角色以自动化方式处理,这种方法需要在 PyPI 基础设施上存储加密签名密钥。不幸的是,在线存储的加密密钥容易被盗。本 PEP 中提出的最大安全模型允许开发人员为他们提供给 PyPI 用户的分发版签名,并且如果存储在 PyPI 基础设施上的在线密钥泄露,也不会使最终用户有下载恶意分发版的风险。

威胁模型

威胁模型假设以下几点

  • 脱机密钥是安全的,并安全地存储。
  • 攻击者可以泄露至少一个存储在线的 PyPI 可信密钥,并且可以立即或在一段时间内这样做。
  • 攻击者可以响应客户端请求。
  • 攻击者可以控制客户端不想安装的项目的任意数量的开发者密钥。

如果攻击者能够导致客户端安装(或保留已安装的)客户端正在更新的软件的最新版本以外的其他内容,则认为攻击者已成功。当攻击者阻止安装更新时,攻击者的目标是客户端没有意识到任何问题。

定义

本文档中的关键字“必须”、“不得”、“需要”、“应”、“不应”、“应该”、“不应该”、“推荐”、“可以”和“可选”的解释方式如 RFC 2119 中所述。

此 PEP 侧重于将 TUF 与 PyPI 集成;但是,鼓励读者阅读有关 TUF 设计原则 [2] 的内容。还建议读者熟悉 TUF 规范 [3]PEP 458(此 PEP 正在扩展)。

本 PEP 中使用的以下术语在 Python 打包术语表 [4] 中定义:项目发布分发

本 PEP 中使用的术语定义如下

  • 分发文件:包含 Python 包、模块和其他用于分发发布版的资源文件的版本化归档文件。术语分发文件分发包 [4] 或简称分发在本 PEP 中可以互换使用。
  • 简单索引:包含到分发文件的内部链接的 HTML 页面。
  • 目标文件:根据经验,目标文件是 PyPI 上所有应使用 TUF 保证其完整性的文件。通常,这包括分发文件和 PyPI 元数据(如简单索引)。
  • 角色:TUF 中的角色包含一方被授权执行的操作集,包括他们可以签署哪些元数据以及他们负责哪些包。PyPI 中有一个角色。有多个角色,其职责由角色直接或间接委托给他们。术语“顶级角色”指的是角色和由角色委托的任何角色。每个角色都有一个它被信任提供的单个元数据文件。
  • 元数据:元数据是描述角色、其他元数据和目标文件的文件。
  • 存储库:存储库是由命名元数据和目标文件组成的资源。客户端请求存储在存储库上的元数据和目标文件。
  • 一致的快照:一组 TUF 元数据和目标文件,捕获 PyPI 上所有项目在某个固定时间点存在时的完整状态。
  • 开发者:项目的拥有者或维护者,他们被允许更新 TUF 元数据以及给定项目的发布元数据和文件。
  • 在线密钥:必须存储在 PyPI 服务器基础设施上的私有加密密钥。这通常允许使用密钥进行自动签名。攻击者如果泄露了 PyPI 基础设施,将能够立即读取这些密钥。
  • 脱机密钥:必须独立于 PyPI 服务器基础设施存储的私有加密密钥。这防止使用密钥进行自动签名。攻击者如果泄露了 PyPI 基础设施,将无法立即读取这些密钥。
  • 阈值签名方案:角色可以通过指定至少 t 个中的 n 个密钥需要对其元数据进行签名来提高其对密钥泄露的弹性。泄露 t-1 个密钥不足以泄露角色本身。说角色需要 (t, n) 个密钥表示阈值签名属性。

最大安全模型

最大安全模型允许开发者签署他们的项目并将签名的元数据上传到 PyPI。在本 PEP 中提出的模型中,如果 PyPI 基础设施遭到破坏,攻击者将无法在没有访问该项目开发者密钥的情况下提供恶意版本的声明项目。图 1 描述了对最小安全模型的元数据布局所做的更改,即现在支持开发者角色,并且存在三个新的委派角色:声明最近声明未声明。最小安全模型中的bins角色已重命名为未声明,并且可以包含任何尚未添加到声明中的项目。未声明角色的功能与以前相同(即,如PEP 458中所述,添加到此角色的项目由 PyPI 使用在线密钥签名)。开发者提供的脱机密钥确保了最大安全模型相对于最小模型的强度。尽管最小安全模型支持项目的持续交付,但所有项目都由在线密钥签名。也就是说,攻击者能够在最小安全模型中破坏软件包,但在最大模型中则不能,除非也破坏了开发者的密钥。

../_images/pep-0480-1.png

图 1:最大安全模型中元数据布局的概述。最大安全模型支持持续交付和可恢复密钥泄露。

首次由开发者签署并上传到 PyPI 的项目将添加到最近声明角色中。最近声明角色使用在线密钥,因此首次上传的项目会立即提供给客户端。经过一段时间后,PyPI 管理员可以定期(例如,每月)将最近声明中列出的项目移动到声明角色以获得最大安全性。声明角色使用脱机密钥,因此如果 PyPI 遭到破坏,则无法轻易伪造添加到此角色的项目。

最近声明角色与未声明角色分开是为了可用性和效率,而不是安全性。如果将新的项目委派预置到未声明元数据中,则每次项目获得密钥时都需要重新下载未声明。通过分离新项目,可以减少检索的数据量。从可用性的角度来看,它也使管理员更容易看到哪些项目现在已声明。在将密钥从最近声明移动到声明时需要此信息,这将在“生成一致的快照”部分中详细讨论。

端到端签名

端到端签名允许 PyPI 和开发者都为客户端下载的元数据签名。PyPI 受信任地将上传的项目提供给客户端(PyPI 为此过程的一部分签名元数据),并且开发者签署他们上传到 PyPI 的发行版。

为了将信任委派给项目,开发者需要向 PyPI 提交至少一个公钥。开发者可以为同一个项目提交多个公钥(例如,每个维护者一个密钥)。PyPI 获取项目的所有公钥并将其添加到 PyPI 随后签名的父元数据中。在建立初始信任后,开发者需要使用至少一个公钥对应的私钥签署他们上传到 PyPI 的发行版。开发者上传到 PyPI 的已签署 TUF 元数据包含诸如发行版的文件大小和哈希等信息,软件包管理器使用这些信息来验证下载的发行版。

端到端签名的实际意义是委派项目信任所需的额外管理工作,以及开发者必须与发行版一起上传到 PyPI 的已签名元数据。具体来说,PyPI 预计会通过将项目添加到声明元数据文件并对其进行签名来定期使用脱机密钥对元数据进行签名。相反,在最小安全模型中,项目永远只使用在线密钥签名。端到端签名确实需要手动干预才能委派信任(即,使用脱机密钥对元数据进行签名),但这是一次性成本,此后项目对 PyPI 泄露有更强的保护。

元数据签名、密钥管理和签名分发

本节讨论了 PyPI 可以向签名工具的实现者推荐的工具、签名方案和签名方法。开发者预计将使用这些工具来签署并将发行版上传到 PyPI。为了总结下面各小节中讨论的推荐工具和方案,开发者可以使用某种自动化方式生成加密密钥并对元数据(使用 Ed25519 签名方案)进行签名,其中元数据包含验证发行版真实性所需的信息。然后,开发者将元数据上传到 PyPI,在那里它将供 pip 等软件包管理器下载(即,支持 TUF 元数据的软件包管理器)。整个过程对从 PyPI 下载发行版的最终用户(使用支持 TUF 的软件包管理器)是透明的。

前三个小节(加密签名方案、加密密钥文件和密钥管理)涵盖了开发者发布过程的加密组件。也就是说,PyPI 支持哪种密钥类型、密钥如何存储以及密钥如何生成。接下来的两个小节讨论了应该修改以支持 TUF 元数据的 PyPI 模块。例如,Twine 和 Distutils 是应该修改的两个项目。最后一个小节介绍了为签名工具推荐的自动化密钥管理和签名解决方案。

TUF 的设计在加密密钥类型、签名和签名方法方面具有灵活性。以下各节中讨论的工具、修改和方法是对签名工具实现者的建议。

加密签名方案:Ed25519

与 CPython 一起提供的软件包管理器 (pip) 必须在非 CPython 解释器上工作,并且不能有必须编译的依赖项(即,PyPI+TUF 集成必须不需要编译 C 扩展才能验证加密签名)。签名的验证必须在 Python 中完成,并且由于速度原因,在纯 Python 中验证 RSA [8] 签名可能不切实际。因此,PyPI 可以使用 Ed25519 签名方案。

Ed25519 [9] 是一种使用小型加密签名和密钥的公钥签名系统。 Ed25519 签名方案的纯 Python 实现 可供使用。即使在 Python 中执行,Ed25519 签名的验证速度也很快。

加密密钥文件

实现可以使用 AES-256-CTR 模式加密密钥文件,并使用 PBKDF2-HMAC-SHA256 加强密码(默认情况下为 100K 次迭代,但这可以由开发者覆盖)。TUF 的当前 Python 实现可以使用任何加密库(将来会添加对 PyCA 加密的支持),可以覆盖默认的 PBKDF2 迭代次数,并且可以根据需要调整 KDF。

密钥管理:miniLock

需要一个易于使用的密钥管理解决方案。一种解决方案是从密码派生私钥,以便开发者不必在多台计算机上管理加密密钥文件。miniLock 是如何做到这一点的一个示例。开发者可以将加密密钥视为辅助密码。miniLock 也适用于像 Ed25519 这样的签名方案,它只需要一个非常小的密钥。

第三方上传工具:Twine

诸如 Twine 之类的第三方工具可以修改(如果他们希望支持包含 TUF 元数据的发行版)以签署并将开发者项目上传到 PyPI。Twine 是一个用于与 PyPI 交互的实用程序,它使用 TLS 上传发行版,并防止对用户名和密码进行中间人攻击。

构建后端

构建后端可以修改以签署元数据并将已签署的发行版上传到 PyPI。

自动签名解决方案

建议开发者使用易于使用的密钥管理解决方案。一种方法是从用户密码生成加密私钥,类似于 miniLock。尽管开发者签名可以保持可选,但由于每个发行版可能具有的大量潜在未签名依赖项,这种方法可能不足。如果这些依赖项中的任何一个未签名,则会抵消项目通过签署自己的发行版获得的任何好处(即,攻击者只需要破坏其中一个未签名的依赖项即可攻击最终用户)。要求开发者手动签署发行版和管理密钥预计会使密钥签名成为一个未使用的功能。

建议为签名工具提供一个默认的、由 PyPI 介导的密钥管理和软件包签名解决方案,该解决方案对开发者是 透明的,并且不需要密钥托管(与 PyPI 共享加密私钥)。此外,签名工具应该避免在每个开发者的多台机器之间共享私钥。这意味着密钥管理解决方案应该支持每个项目的多个密钥。

以下是新开发者可以按照上传发行版到 PyPI 的自动化签名解决方案的概述

  1. 注册 PyPI 项目。
  2. 输入辅助密码(独立于 PyPI 用户帐户密码)。
  3. 可选:从第二台机器(在密码提示后)向开发者的 PyPI 用户帐户添加新身份。
  4. 上传项目。
  5. 可选:与项目相关的其他维护者可以登录并输入辅助密码以将其身份添加到项目中。

步骤 1 是开发者遵循的正常程序,以 注册 PyPI 项目

步骤 2 生成加密密钥文件(私有),将 Ed25519 公钥上传到 PyPI,并签署为发行版生成的 TUF 元数据。

在步骤 3 中,可选地从第二台机器添加新身份,只需输入密码,这也会生成一个加密的私钥文件并将 Ed25519 公钥上传到 PyPI。可以创建单独的身份以允许开发者在多台机器上签署发布。现有的已验证身份(其公钥包含在项目元数据中或已上传到 PyPI)为新身份签名。默认情况下,项目元数据的签名阈值为“1”,其他已验证的身份可以创建新发布以满足阈值。

步骤 4 将发行版文件和 TUF 元数据上传到 PyPI。“快照过程”部分详细讨论了开发者遵循的上传发行版到 PyPI 的过程。

步骤 5 允许其他维护者以类似于步骤 2 的方式生成加密密钥文件。这些密钥应该上传到 PyPI 并添加到 TUF 元数据中。此密钥可以用来上传项目的未来版本。

在默认情况下,加密文件和签名的生成对开发者是透明的:开发者不需要知道软件包是自动签名的。但是,签名工具应该灵活;开发者可能希望生成自己的密钥并自行处理密钥管理。在这种情况下,开发者可以简单地将他们的公钥上传到 PyPI。

存储库开发者 TUF 工具目前支持前面提到的所有建议,除了自动化签名解决方案,该解决方案应该添加到 Distlib、Twine 和其他第三方签名工具中。可用的存储库工具功能调用自动化签名解决方案来签署元数据并生成加密密钥文件。

快照流程

快照过程相当简单,**应该**自动化。快照过程**必须**将最新的工作集(包含roottargets和委派角色)保存在内存中。大约每分钟,快照过程都会对这个最新的工作集进行签名。(回想一下,项目上传会持续以并发安全的方式告知快照过程最新的委派元数据。快照过程实际上会对最新工作集的副本进行签名,而内存中的最新工作集将使用项目事务过程持续通信的信息进行更新。)快照过程**必须**生成并签名新的timestamp元数据,以证明上一步生成的元数据(roottargets和委派角色)的有效性。最后,快照过程**必须**向客户端提供新的timestampsnapshot元数据,表示最新的快照。

一个claimedrecently-claimed项目需要在其上传到PyPI的事务中,不仅包含目标文件(简单的索引以及发行版),还需要包含TUF元数据。项目**可以**通过上传一个包含两个目录的ZIP文件来实现,/metadata/(包含委派目标元数据文件)和/targets/(包含目标文件,例如项目简单索引和由委派目标元数据签名的发行版)。

每当项目将元数据或目标文件上传到PyPI时,PyPI**应该**至少检查项目TUF元数据的以下属性

  • 由该项目在PyPI上注册的开发人员密钥中的**阈值**数量**必须**已对表示该项目目标“根”的委派目标元数据文件进行签名(例如metadata/targets/ project.txt)。
  • 委派目标元数据文件的签名**必须**有效。
  • 委派目标元数据文件**不得**过期。
  • 委派目标元数据**必须**与目标文件一致。
  • 委托方**不得**委托未被其他委托方委托给自己的目标文件。
  • 受托方**不得**为未被委托方委托给自己的目标文件签名。

如果PyPI选择检查项目TUF元数据,那么PyPI**可以**选择拒绝发布任何不满足这些要求的元数据或目标文件集。

PyPI**必须**通过确保每个项目只能写入其负责的TUF元数据来执行访问控制。它**必须**通过确保项目上传过程写入正确的元数据以及这些元数据中的正确位置来实现。例如,未声明项目的项目上传过程**必须**写入相应委派未声明元数据中项目目标的正确目标路径。

在极少数情况下,PyPI**可能**希望以向后不兼容的方式扩展项目的TUF元数据格式。请注意,PyPI**无法**代表项目自动重写现有的TUF元数据以将其升级到新的向后不兼容格式,因为这会使开发人员密钥签名的元数据签名失效。相反,包管理器**应该**被编写为识别和处理多个不兼容版本的TUF元数据,以便声明和最近声明的项目可以获得合理的时间将其元数据迁移到更新但向后不兼容的格式。TAP 14中描述了一种处理此版本更改的机制。

如果PyPI最终耗尽磁盘空间以生成新的一致快照,那么PyPI**可以**使用类似“标记-清除”的算法删除足够旧的一致快照。也就是说,只删除不再使用的过时的元数据,例如timestampsnapshot。具体来说,为了保留最新的快照,PyPI将遍历对象——从最新一致快照的根(timestamp)开始——标记所有访问过的对象,并删除所有未标记的对象。最后几个一致快照可以以类似的方式保留。删除一致快照将导致客户端除了对已删除一致快照的目标的任何请求返回HTTP 404响应之外,什么也看不到。客户端**应该**然后(像以前一样)使用最新的快照重试他们的请求。

所有支持TUF元数据的包管理器**必须**修改为下载每个元数据和目标文件(除了timestamp元数据),并在文件请求中包含文件名中的文件加密哈希。按照下一小节中**建议**的文件名约定,对filename.ext文件的请求将转换为对digest.filename文件的等效请求。

最后,PyPI**应该**使用事务日志记录项目事务过程和队列,以便在服务器故障后更容易从错误中恢复。

生成一致的快照

PyPI负责根据项目更新claimedrecently-claimedunclaimed元数据以及相关的委派元数据。每个项目**必须**在单个事务中上传其元数据和目标文件集。上传的文件集称为“项目事务”。PyPI如何**可能**验证项目事务中的文件将在后面的章节中讨论。本节重点介绍PyPI将如何响应项目事务。

每个元数据和目标文件**必须**在其文件名中包含其十六进制摘要BLAKE2b-256哈希值,PyPI可以在文件上传后将其添加到文件名之前。对于本PEP,**建议**PyPI采用以下简单的约定:digest.filename,其中filename是原始文件名,不包含哈希值的副本,digest是哈希值的十六进制摘要。

当未声明的项目上传新的事务时,项目事务过程**必须**添加所有新的目标文件和相关的委派未声明元数据。项目上传过程**必须**通知快照过程新的委派未声明元数据。

recently-claimed项目上传新的事务时,项目上传过程**必须**添加所有新的目标文件和项目的委派目标元数据。如果项目是新的,那么项目上传过程**必须**还添加新的recently-claimed元数据,以及项目的公钥(**必须**是事务的一部分)。recently-claimed项目的阈值由上传过程设置为“1”。最后,项目上传过程**必须**通知快照过程新的recently-claimed元数据以及项目当前的委派目标元数据集。

声明项目的上传过程略有不同,因为PyPI管理员会定期(**可能**每两周到一个月)将项目从recently-claimed角色迁移到claimed角色。(将项目从recently-claimed迁移到claimed是一个手动过程,因为PyPI管理员必须使用脱机密钥来签署声明项目的发布。)然后,项目上传过程**必须**添加新的recently-claimedclaimed元数据以反映此迁移。与recently-claimed项目一样,项目上传过程**必须**始终添加所有新的目标文件和声明项目的委派目标元数据。最后,项目上传过程**必须**通知一致快照过程新的recently-claimedclaimed元数据以及项目当前的委派目标元数据集。

项目上传过程**应该**自动化,除非PyPI管理员将项目从recently-claimed角色迁移到claimed角色。项目上传过程**必须**也原子地应用:要么所有元数据和目标文件都被添加,要么都不添加。项目事务过程和快照过程**应该**并发工作。最后,项目上传过程**应该**将最新的claimedrecently-claimedunclaimed元数据保存在内存中,以便它们在新的快照中被正确更新。

队列**可以**根据出现顺序并发处理,前提是遵守以下规则

  1. 任何一对项目上传过程**不得**同时处理同一个项目。
  2. 任何一对项目上传过程**不得**同时处理属于同一委派unclaimed角色的unclaimed项目。
  3. 任何一对项目上传过程**不得**同时处理新的recently-claimed项目。
  4. 任何一对项目上传过程**不得**同时处理新的claimed项目。
  5. 当另一个项目上传过程正在处理新的recently-claimed项目时,任何项目上传过程**不得**处理新的claimed项目,反之亦然。

**必须**遵守这些规则以确保元数据不会被不一致地读取或写入。

审计快照

如果恶意方破坏了PyPI,他们可以使用任何在线密钥对任意文件进行签名。具有脱机密钥的角色(即,roottargets)仍然受到保护。为了安全地从存储库破坏中恢复,应审核快照以确保文件仅恢复到受信任的版本。

当检测到存储库遭到破坏时,必须验证三种类型的信息的完整性

  1. 如果存储库的在线密钥遭到破坏,可以通过让targets角色签署委派给新密钥的新元数据来撤销它们。
  2. 如果存储库上的角色元数据已被更改,这将影响由在线密钥签名的元数据。自破坏以来创建的任何角色信息都应丢弃。因此,新项目的开发者需要重新注册他们的项目。
  3. 如果包本身可能已被篡改,可以使用存储在受信任元数据中存在的包的哈希信息进行验证。此外,由claimed角色中的开发人员签名的新的发行版可以安全地保留。但是,应丢弃由recently-claimedunclaimed角色中的开发人员签名的任何发行版。

为了在发生破坏的情况下安全地恢复快照,PyPI**应该**维护少量自己的镜像,以根据某些计划复制PyPI快照。镜像协议可以立即用于此目的。镜像必须是安全的和隔离的,以便它们只负责镜像PyPI。可以相互检查镜像以检测意外或恶意的故障。

另一种方法是定期生成每个snapshot的加密哈希并发布到Twitter。例如,在收到推文后,用户会提供实际的元数据,然后存储库维护者能够验证元数据的加密哈希。或者,PyPI可以定期存档其自己的snapshot版本,而不是依赖外部提供的元数据。在这种情况下,PyPI**应该**获取存储库中每个包的加密哈希,并将此数据存储在脱机设备上。如果任何包哈希已更改,则表示已发生攻击。

提供不同版本元数据或将包的版本冻结在特定版本的攻击可以通过TUF使用隐式密钥撤销和元数据不匹配检测等技术来处理[2]

密钥泄露分析

本 PEP 涵盖了最大安全模型、为支持持续交付发行版而应添加的 TUF 角色、如何生成和签名每个角色的元数据,以及如何支持开发人员签名的发行版。其余部分讨论了 PyPI **应该**如何审计存储库元数据,以及 PyPI 可以用来检测和恢复 PyPI 泄露的方法。

表 1 总结了一些当达到阈值数量的私有加密密钥(属于任何 PyPI 角色)被泄露时可能发生的攻击。最左边的列列出了被泄露的角色(或角色组合),右侧的列显示被泄露的角色是否会使客户端容易受到恶意更新、冻结攻击或元数据不一致攻击的影响。

角色泄露 恶意更新 冻结攻击 元数据不一致攻击
时间戳 否,快照和目标或任何委托的角色不需要协作 是,受最早的根、目标或 bin 元数据过期时间限制 否,快照需要协作
快照 否,时间戳和目标或任何委托的角色不需要协作 否,时间戳需要协作 否,时间戳需要协作
时间戳 *和* 快照 否,目标或任何委托的角色不需要协作 是,受最早的根、目标或 bin 元数据过期时间限制 是,受最早的根、目标或 bin 元数据过期时间限制
目标 *或* **已声明** *或* 最近已声明 *或* 未声明 *或* **项目** 否,时间戳和快照需要协作 不适用,需要时间戳和快照 不适用,需要时间戳和快照
(时间戳 *和* 快照) *和* **项目** 是,受最早的根、目标或 bin 元数据过期时间限制 是,受最早的根、目标或 bin 元数据过期时间限制
(时间戳 *和* 快照) *和* (最近已声明 *或* 未声明) 是,但仅限于未由已声明委托的项目 是,受最早的根、目标、已声明、最近已声明、项目或未声明元数据过期时间限制 是,受最早的根、目标、已声明、最近已声明、项目或未声明元数据过期时间限制
(时间戳 *和* 快照) *和* (目标 *或* **已声明**) 是,受最早的根、目标、已声明、最近已声明、项目或未声明元数据过期时间限制 是,受最早的根、目标、已声明、最近已声明、项目或未声明元数据过期时间限制

表 1:通过泄露某些角色密钥组合可能发生的攻击。在2013 年 9 月,有人展示了当时最新版本的 pip 如何容易受到这些攻击,以及 TUF 如何保护用户免受这些攻击[7]。由离线密钥签名的角色以**粗体**显示。

请注意,泄露目标或任何委托的角色(除了项目目标元数据)不会立即允许攻击者提供恶意更新。攻击者还必须泄露时间戳快照角色(这两个角色都处于在线状态,因此更容易被泄露)。这意味着,为了发起任何攻击,不仅必须能够充当中间人,还必须泄露时间戳密钥(或泄露密钥并签名一个新的时间戳密钥)。为了发起除冻结攻击之外的任何攻击,还必须泄露快照密钥。最后,PyPI 基础设施的泄露**可能**会导致对最近已声明的项目的恶意更新,因为这些角色的密钥在线。

密钥泄露事件

密钥泄露意味着开发人员或 PyPI 上的角色以及 PyPI 基础设施的密钥已达到阈值数量被泄露,并用于在 PyPI 上签名新的元数据。

如果项目的开发人员密钥达到阈值数量被泄露,则该项目**必须**采取以下步骤

  1. 项目元数据和目标**必须**恢复到最后已知的良好一致快照,在该快照中项目尚未知被泄露。开发人员可以使用新密钥重新打包和重新签名所有目标来做到这一点。
  2. 项目的元数据**必须**将其版本号递增、适当延长过期时间并更新签名。

而 PyPI **必须**采取以下步骤

  1. 最近已声明已声明的角色中撤销被泄露的开发人员密钥。这可以通过用新发行的开发人员密钥替换被泄露的开发人员密钥来完成。
  2. **必须**发布新的带时间戳的一致快照。

如果时间戳快照最近已声明未声明密钥达到阈值数量被泄露,则 PyPI **必须**采取以下步骤

  1. 从根角色中撤销时间戳快照目标角色密钥。这可以通过用新发行的密钥替换被泄露的时间戳快照目标密钥来完成。
  2. 通过用新发行的密钥替换其密钥,从目标角色中撤销最近已声明未声明密钥。对新的目标角色元数据进行签名并丢弃新密钥(因为,正如我们之前解释的那样,这提高了目标元数据的安全性)。
  3. 清除最近已声明角色中的所有目标或委托,并删除所有相关的委托目标元数据。最近注册的项目**应该**再次向 PyPI 注册其开发人员密钥。
  4. 所有最近已声明未声明角色的目标**应该**与最后已知的良好一致快照进行比较,在该快照中,尚未知时间戳快照最近已声明未声明密钥中的任何一个被泄露。在被泄露的一致快照中添加、更新或删除的目标,如果与最后已知的良好一致快照不匹配,**应该**恢复到其先前版本。在确保所有未声明目标的完整性后,**必须**重新生成未声明的元数据。
  5. 最近已声明未声明元数据**必须**将其版本号递增、适当延长过期时间并更新签名。
  6. **必须**发布新的带时间戳的一致快照。

这将预先保护所有这些角色,即使可能只有一个角色被泄露。

如果目标已声明密钥达到阈值数量被泄露,那么在没有时间戳快照密钥的情况下,攻击者几乎无法做任何事情。在这种情况下,PyPI **必须**简单地撤销被泄露的目标已声明密钥,方法是在目标角色中分别用新密钥替换它们。

如果时间戳快照已声明密钥达到阈值数量被泄露,则 PyPI **必须**采取以下步骤,此外还必须采取在时间戳快照密钥被泄露时采取的步骤

  1. 从目标角色中撤销已声明角色密钥,并用新发行的密钥替换它们。
  2. 所有已声明角色的项目目标**应该**与最后已知的良好一致快照进行比较,在该快照中,尚未知时间戳快照已声明密钥中的任何一个被泄露。在被泄露的一致快照中添加、更新或删除的目标,如果与最后已知的良好一致快照不匹配,**可能**恢复到其先前版本。在确保所有已声明项目目标的完整性后,**必须**重新生成已声明元数据。
  3. 已声明的元数据**必须**将其版本号递增、适当延长过期时间并更新签名。

遵循这些步骤将预先保护所有这些角色,即使可能只有一个角色被泄露。

如果密钥达到阈值数量被泄露,则 PyPI **必须**采取在目标角色被泄露时采取的步骤。所有密钥也必须被替换。

还**建议** PyPI 使用安全公告充分记录泄露事件。当使用 TUF 的 pip 用户无法安装或更新项目(因为时间戳快照角色的密钥不再有效)时,这些安全公告将提供最有用的信息。然后,用户可以访问 PyPI 网站查阅安全公告,以帮助解释为什么用户无法再安装或更新,然后采取相应的措施。当由于泄露而没有撤销阈值数量的密钥时,可以安全地更新新的元数据,因为阈值数量的现有密钥将用于对新元数据的完整性进行签名。TUF 客户端将能够使用阈值数量的先前已知的密钥验证新元数据的完整性。这将是常见情况。在最坏的情况下,如果由于泄露而撤销了阈值数量的密钥,则最终用户可以选择使用带外机制更新新的元数据。

附录 A:PyPI 构建农场和端到端签名

PyPI 管理员打算支持一个中央构建农场。PyPI 构建农场将为开发人员上传到 PyPI 基础设施和支持平台上的每个发行版自动生成一个Wheel。包管理器可能会通过下载这些 PyPI Wheels(安装速度比源发行版快得多)来安装项目,而不是下载开发人员签名的源发行版。在实施最大安全模型之前,**应该**调查拥有端到端签名的中央构建农场的含义。

中央构建农场和端到端签名的一个问题是,开发人员在 PyPI 基础设施上生成 Wheel 发行版后不太可能对其进行签名。但是,从开发人员签名的源发行版生成 Wheels 仍然是有益的,前提是构建 Wheels 是一个确定性过程。如果确定性构建不可行,开发人员可以将这些 Wheels 的信任委托给 PyPI 角色,该角色使用在线密钥对 Wheels 进行签名。

参考文献

致谢

本材料基于美国国家科学基金会根据赠款号 CNS-1345049 和 CNS-0959138 提供的支持。本材料中表达的任何意见、发现、结论或建议均为作者的观点,不一定反映美国国家科学基金会的观点。

我们感谢 Alyssa Coghlan、Daniel Holth、Donald Stufft、Sumana Harihareswara 和 distutils-sig 社区,感谢他们帮助我们思考如何将 TUF 可用且高效地与 PyPI 集成。

Roger Dingledine、Sebastian Hahn、Nick Mathewson、Martin Peck 和 Justin Samuel 帮助我们从其前身 Tor 项目的 Thandy 设计了 TUF。

我们感谢 Konstantin Andrianov、Geremy Condra、Zane Fisher、Justin Samuel、Tian Tian、Santiago Torres、John Ward 和 Yuyu Zheng 为开发 TUF 所做的努力。


来源:https://github.com/python/peps/blob/main/peps/pep-0480.rst

上次修改时间:2023-10-11 12:05:51 GMT