返回 2026-05-25
🔒 安全

签名协议:危机时刻才显价值Signing is for the bad days

nesbitt.io·2026-05-24

文章强调 TUF、in-toto 和 Sigstore 这类签名协议的价值只有在安全事件发生时才能体现。作者指出,这些机制平时看似多余,但在系统遭攻击或数据篡改时(如供应链攻击),它们提供的完整性验证和审计追踪才是关键防线。

Andrew Nesbitt

过去一个月里,我大约进行了四到五次类似的对话。我正在解释为什么注册中心应采用 Sigstore,或为何构建流水线应生成 in-toto 断言,而对面的人总会以类似的话回应:我们已经在注册中心使用 TLS,注册中心已经对 tarball 进行哈希校验,锁文件也已固定哈希值,签名又能带来什么?在一切正常的一个周二下午,诚实的回答是:它只是在发布时增加了一点 CPU 开销,工作流中多了一点点 YAML 配置,除此之外你几乎看不出其他变化。

这也是我们不断遭遇供应链事件的原因——锁文件中的哈希只能保证自锁定以来字节未改变,却无法验证这些字节是否最初就是正确的。这类工具的核心目的正是解决第二个问题。直到某天有人入侵构建服务器或替换镜像站点的 tarball 前,这些措施都毫无可见效果;而当这一天到来时,区别在于“客户端拒绝安装”与一篇以“事件”开头的博文。我想借此梳理支撑这些工作的三个项目及其各自防御的对象,因为它们的价值往往难以从常规路径中察觉。

贯穿这三个项目的核心人物是 Santiago Torres-Arias。在研究各项目背后的论文和设计文档时,这个名字频繁出现,于是我深入探究其背景。他在纽约大学师从 Justin Cappos(曾开发 The Update Framework),参与了 TUF 的开发,并在 USENIX Security '19 上发表论文补充了 TUF 的不足,后来成为 Sigstore 的创建者之一。2021 年,他是签署信任根的五个关键签署人之一。

他现执教于普渡大学,领导着一个持续产出此类技术的实验室。该实验室开发的 GUAC 可将 SBOM 和断言转化为图数据库,便于在 CVE 发布时快速查询受影响组件。早在此前,他 2016 年关于 git 元数据篡改的论文已揭示:即使所有提交都经过签名,敌对服务器仍可能重写分支指针,将漏洞代码推送给用户。贯穿所有这些工作的设计假设是:你的基础设施部分必然已被攻破,而工具的目标是让系统在这种状态下仍可存活。

TUF:假设注册中心被攻陷

TUF 保护的最终环节是从仓库到安装机的数据传输。最朴素的方案是“对包签名”(PGP 方案已沿用多年),但一旦签名密钥泄露便形同虚设。若仅保留一个在线密钥供 CI 自动签名,那么攻破 CI 服务器的人便能随意签发签名,所有客户端都会信任。TUF 的原始研究正是源于 2000 年代末 Linux 发行版更新器中暴露的这类故障模式。

TUF 的解决方案是将签名分解为具有独立密钥和独立暴露的角色。一个根角色由离线硬件中每年只接触一次的密钥签署,声明其他角色的哪些密钥有效。目标角色声明哪些包文件存在及其哈希值。快照角色声明当前所有元数据的版本,而时间戳角色(唯一需要在线且热更新的密钥)则签署一条短命的声明,表明快照是新鲜的。热密钥只能声明“您已拥有的元数据仍然有效”;它不能添加包或更改哈希值,也不能为其他角色授权新密钥。

想象攻击者如何突破这一防线:他们攻破注册表的在线基础设施并获取时间戳密钥——这是唯一可访问的密钥。现在他们可以签署新的时间戳,但仅此而已。篡改包需要目标密钥的攻击者并不拥有。回滚攻击(提供旧漏洞版本)被阻止,因为快照版本号只会递增。即使攻击者长期驻留服务器并提供过期的元数据(冻结攻击),一旦时间戳签名过期且客户端发现其不再更新,攻击即失效。若要实际分发恶意软件,攻击者需要离线目标密钥;若要更改受信任的密钥,则需要多个离线根密钥(分散在不同保险箱中的阈值)。

当注册表正常运行时,这一切均不可见:带有 PEP 458 元数据的 `pip install` 与不带元数据的安装看起来完全一致。差异仅在服务器被入侵的场景下显现——这正是几周前威胁模型文章反复强调的情景:注册表作为持有在线凭证的大型目标,应偶尔计划应对其被攻陷的情况。

in-toto:假设管道已被控制

TUF 能确保你收到的文件是注册表意图提供的文件,但无法说明注册表本应是否拥有该文件。例如,`foo-1.2.3` 的 tarball 通过 TLS 传输且匹配签名哈希,看似正常,但谁从什么源、在哪台机器上构建它?编译器到上传过程中是否有任何环节被篡改?在包管理的大部分历史中,答案一直是“仓库字段在清单声明的字符串,由发布者手动输入”。

in-toto 正是针对这一空白提出的解决方案,我认为这是整个领域最重要的理念。项目所有者会编写布局文档:一份签署过的文件,声明流水线步骤(克隆、构建、测试、打包)、每个步骤被授权的密钥,以及各步骤允许从前一阶段消耗的内容和传递给下一阶段的规则。每步执行时,会记录输入输出的哈希值并用自身密钥签署该记录(链接)。远端验证时,最终产物、布局文档及一系列链接会被检查,确保从布局指定的源头到你手中字节之间存在未经破坏的签名链,且每一步均由布局授权的人员完成。

让 xz 通过该模型运行:后门存在于发布 tarball 中,但从未出现在 git 仓库中;攻击者在维护者被信任手动执行的“创建发布归档”步骤期间添加了它。若有一个声明“包步骤的输入必须与标签步骤的输出匹配”的 in-toto 布局,本应验证失败,因为 tarball 包含标签树中没有的文件。

SolarWinds 构建服务器确实是一个功能密钥会被植入受控服务器的构建步骤,但布局可以要求两个独立功能者使用相同源码构建并匹配输出,这正是可重现构建理念以可验证策略而非理想化目标表达的方式。Codecov bash 上传器是在 CI 生成后修改存储桶中的文件;CI 步骤产物与分发步骤材料之间的 MATCH 规则恰好能捕获此类情况。

USENIX 论文详细分析了三十起真实入侵事件,并指出每条链路的哪一环节本应拦截它们——阅读稍显不适,因为几乎所有案例本都能被拦截。正常流程下你完全看不到这些痕迹:CI 会产出附带 JSON 文件的工件,安装客户端校验通过后静默无提示。

Sigstore:假设无人管理密钥

上述两种方案均默认人们拥有签名密钥且妥善保管,但开源领域三十年 PGP 实践表明这不会发生。密钥服务器是项目放弃后过期/撤销密钥的坟场;Maven 签名需求普遍由 2014 年笔记本上生成的一次性密钥满足且再未更新;PyPI 最终彻底移除了 PGP 支持,因几乎无人能验证现有签名。

Sigstore 的贡献在于从开发者生命周期中彻底移除长期密钥。你通过已有 OIDC 身份(如 GitHub Actions/Google)认证,Fulcio 随即绑定该身份的十分钟有效代码签名证书。签署工件后,签名和证书存入公开不可篡改的 Rekor 日志,密钥随即销毁。

验证者不再问“这是否由 0xABCD 密钥签名”,而是问“Rekor 是否存有此哈希值对应、Fulcio 颁发给 github.com/foo/bar/.github/workflows/release.yml@refs/tags/v1.2.3 的证书”。可信对象是身份与日志,维护者无需维护任何凭证。

发布者配置中不存在可被窃取的信息。工作流文件明确列出预期签名身份,这些信息本就是公开的。即使仓库泄露,你仅获知需冒充的身份,但无助于实施攻击。OIDC 令牌仅在运行任务中存在几分钟,窃取意味着已控制该任务执行权限——这与读取秘密存储是不同且更难的问题。而长时效签名密钥或注册表 API 令牌中,秘密存储里的正是凭证本身,外泄即等于完成攻击。

桑切斯共同撰写了CCS‘22论文,阐述了Sigstore的安全模型。他的贡献最显著地体现在Rekor中——这是将学术透明日志文献(CONIKS、Certificate Transparency)应用于软件工件,以及通过TUF根分发Sigstore自身密钥的方式,使得“谁签署签署者”的递归终止于与其他系统相同的离线阈值模型。他实际持有该根的五个硬件密钥之一。他与Cappos多年推动的研究议程是:基于身份短期证书、底层采用TUF的公共日志,如今已打包成无需发布者理解Merkle树即可通过`npm publish --provenance`实现的功能。

透明日志在恶意事件发生时承担主要工作。若维护者的GitHub账户遭钓鱼攻击并用于发布恶意版本,维护者可查看Rekor声明:“我未生成该签名,此处日志条目显示其OIDC身份及来源的运行流程。”

当Ledger Connect Kit从被钓鱼的npm账户推送时,精确追踪发布时间和来源耗费了远超预期的调查时间。一个不可篡改且可被监控的公共日志(能检测“包X是否首次由某身份签署”)可将这类取证混乱转化为简单查询。这也意味着仓库运营者即使想事后也无法替换工件,因为原始哈希值已被记录在不受其控制的日志中。

签名未能阻止的一切

若止步于此则不诚实,因为过去几个月持续证明:仅靠这一层防护远不足够。目前每日接近一次妥协事件,攻击者模式均渗透至CI运行环节,其中OIDC令牌仅是窃取目标之一。

Shai-Hulud蠕虫与本月TanStack浪潮直接从GitHub Actions运行器的进程内存窃取令牌并POST到注册表。Ultralytics则相反:缓存污染导致合法流程构建出有毒工件,PyPI可信发布向已污染的构建颁发了完全有效的令牌。可信发布虽封堵了“泄露的长时效令牌”漏洞,但攻击者转而利用工作流漏洞突破防线。

在这些案例中,签名技术的作用在于快速明确还原事件。PyPI对Ultralytics的分析明确指出:正是Sigstore透明日志帮助确认首个恶意wheel源自真实工作流而非被盗API令牌,从而将调查指向构建缓存。第二轮恶意发布使用从未撤销的旧API令牌,完全缺乏认证凭证,这种缺失本身即提供了关键信号。这虽非“阻止坏发布”,但多层防护并存的价值正体现在此类可观测性上。

堆叠防护的效果

这三层结构组成,而实际部署的正是这种组合方式。in-toto 定义了有效的构建应该是什么样子;Sigstore 则让每个步骤无需人工管理密钥即可签署其链接,并将最终证明绑定到生成它的 CI 身份;TUF 确保策略、证明和制品能安全地传输到客户端,且分发渠道无法对其中任何内容作伪。

npm 的来源声明、PyPI 的证明以及 Homebrew 的构建记录均采用 in-toto 格式,并通过 Sigstore 签名,而这些制品的分发越来越多地依赖 TUF 元数据。SLSA(尽管多数业内人士已有所耳闻)本质上是一系列针对 in-toto 布局的难度等级,如今美国政府要求软件供应商对其作出的承诺与之大致相当——这一距离对于一篇博士论文而言已是合理进展。

当有人问哈希值与 lockfile 中的签名相比有何优势时:lockfile 仅能保证锁定后的字节未被篡改,但无法验证这些字节是否最初正确,或它们由何人及何种输入生成;首次安装前若无 lockfile 则毫无信息,事后也难以追溯影响范围。而上文提到的技术栈能在构建被篡改或仓库提供错误内容时拒绝安装,提供可被发布者审计的公开记录,并借助离线根密钥使攻击者窃取在线密钥后获益甚微。

几乎所有这些机制在绝大多数情况下都不可见,这也正是我持续讨论此话题的原因。Santiago 的工作属于真正意义上的基础设施——只有当问题发生时才会意识到其存在,而“我们启用了签名却未察觉明显变化”正是这类工作的成功标志。问题是极少有人能同时理解整个技术栈,每层防护的漏洞仅在攻击者突破时显现,因此我们必须一次次通过具体事件解释,直到人们真正理解为止。

需要完整排版与评论请前往来源站点阅读。