编者注:5 月 9 日,太平洋时间 8:22 - 更新如下:(1) 修正动词时态 (2) 澄清了与下游发行版的状况。有关更多详细信息,请参阅 Bug 1549886.
最近,Firefox 发生了一起事件,导致大多数附加组件停止工作。这是由于我们方面的错误:我们让用于签署附加组件的证书之一过期,这导致绝大多数附加组件被禁用。现在,我们已经为大多数用户修复了问题,并且大多数人的附加组件都已恢复,我想详细介绍一下发生的事情,原因以及我们如何修复它。
背景:附加组件和附加组件签名
虽然许多人开箱即用地使用 Firefox,但 Firefox 还支持一个强大的扩展机制,称为“附加组件”。附加组件允许用户向 Firefox 添加第三方功能,以扩展我们默认提供的功能。目前,有超过 15,000 个 Firefox 附加组件,其功能从 阻止广告 到 管理数百个标签 都有。
Firefox 要求所有安装的附加组件都必须进行 数字签名。此要求旨在通过要求 Mozilla 工作人员进行一些最低限度的审查来保护用户免受恶意附加组件的侵害。在我们在 2015 年引入此要求之前,我们遇到了 严重的问题,这些问题与恶意附加组件有关。
附加组件签名的工作方式是 Firefox 配置了一个预安装的“根证书”。该根证书以脱机方式存储在 硬件安全模块 (HSM) 中。每隔几年,它就会用于签署一个新的“中间证书”,该证书在线保存,并用作签名过程的一部分。当提交附加组件进行签名时,我们会生成一个新的临时“最终实体证书”,并使用中间证书对其进行签名。然后,最终实体证书用于签署附加组件本身。从视觉上看,这看起来像这样
请注意,每个证书都有一个“主体”(证书属于谁)和一个“颁发者”(签名者)。对于根证书,这两个实体是相同的,但对于其他证书,证书的颁发者是签署该证书的证书的主体。
这里的一点很重要,即每个附加组件都由它自己的最终实体证书签名,但几乎所有附加组件都共享同一个中间证书 [1]。正是这个证书遇到了问题:每个证书都有一个固定的有效期。在此时间窗口之前或之后,证书将不被接受,而使用该证书签名的附加组件将无法加载到 Firefox 中。不幸的是,我们正在使用的中间证书在 5 月 4 日凌晨 1 点 UTC 之后过期,并且所有使用该证书签名的附加组件立即变得不可验证,并且无法加载到 Firefox 中。
虽然附加组件在午夜左右全部过期,但中断的影响并没有立即显现。造成这种情况的原因是 Firefox 不会持续检查附加组件的有效性。相反,所有附加组件大约每 24 小时检查一次,每个用户的检查时间都不同。结果是有些人立即遇到问题,而有些人则直到很久以后才遇到问题。我们在 Mozilla 首次在 5 月 3 日星期五太平洋时间下午 6 点左右意识到这个问题,并立即组建了一个团队来解决这个问题。
损害限制
一旦我们意识到我们面临的困境,我们就采取了几项措施来避免情况变得更糟。
首先,我们禁用了对新附加组件的签名。当时这样做是明智的,因为我们是用一个已知的过期证书进行签名。回想起来,也许可以继续签名,但这也会干扰我们将在下面讨论的“硬编码日期”缓解措施(尽管最终没有使用),因此我们保留了这个选择是件好事。现在签名已恢复正常。
其次,我们立即推送了一个热修复程序,该修复程序抑制了对附加组件签名的重新验证。这里的想法是避免破坏尚未重新验证的用户。我们是在没有任何其他修复措施之前进行的,并且在修复措施可用后已将其删除。
并行工作
从理论上讲,修复此类问题看起来很简单:创建一个新的有效证书,并使用该证书重新发布每个附加组件。不幸的是,我们很快发现这样做行不通,原因有以下几点
- 附加组件数量非常多(超过 15,000 个),而签名服务没有针对批量签名进行优化,因此仅重新签名每个附加组件都需要花费比我们希望的时间更长。
- 一旦附加组件被签名,用户就需要获取新的附加组件。某些附加组件托管在 Mozilla 的服务器上,Firefox 会在 24 小时内更新这些附加组件,但用户必须手动更新他们从其他来源安装的任何附加组件,这将非常不方便。
相反,我们专注于尝试开发一种修复措施,我们可以将其提供给所有用户,而无需或几乎无需手动干预。
在检查了许多方法后,我们很快收敛到两种主要策略,我们并行地执行了这些策略
- 修补 Firefox 以更改用于验证证书的日期。这将使现有附加组件神奇地再次工作,但需要发布新的 Firefox 构建版本(“点发布”)。
- 生成一个仍然有效的替换证书,并以某种方式说服 Firefox 接受它,而不是现有的过期证书。
我们不确定这两种方法是否都能奏效,因此我们决定并行执行它们,并部署第一个看起来即将奏效的方法。最终,我们最终部署了第二个修复程序,即新的证书,我将在下面更详细地介绍。
替换证书
如上所述,这里有我们必须遵循的两个主要步骤
- 生成一个新的有效证书。
- 将其远程安装在 Firefox 中。
为了理解为什么这有效,你需要了解有关 Firefox 如何验证附加组件的更多信息。附加组件本身作为一个文件包出现,其中包括用于签署它的证书链。结果是,只要你知道根证书(在构建时已配置到 Firefox 中),附加组件就可以独立验证。但是,正如我所说,中间证书已损坏,因此附加组件实际上无法验证。
但是,事实证明,当 Firefox 尝试验证附加组件时,它不仅限于使用附加组件本身的证书。相反,它试图构建一个有效的证书链,从最终实体证书开始,一直到根证书。该算法很复杂,但从高层次上讲,你从最终实体证书开始,然后找到一个主体等于最终实体证书颁发者的证书(即中间证书)。在简单情况下,这仅仅是随附加组件一起提供的中间证书,但它可以是浏览器碰巧知道的任何证书。如果我们可以远程添加一个新的有效证书,那么 Firefox 也会尝试使用它。下图显示了安装新证书之前和之后的情况。
一旦安装了新证书,Firefox 就有两种选择可以验证证书链,使用旧的无效证书(将不起作用)和使用新的有效证书(将起作用)。这里的一个重要特征是,新证书与旧证书具有相同的主题名称和公钥,因此它对最终实体证书的签名是有效的。幸运的是,Firefox 足够聪明,会尝试两者,直到找到一个有效的路径,这样附加组件就再次变得有效。请注意,这与我们用于验证 TLS 证书的逻辑相同,因此这是我们能够利用的比较成熟的代码。[2]
这个修复措施最棒的地方在于它不需要我们更改任何现有的附加组件。只要我们将新证书放入 Firefox,即使带有旧证书的附加组件也会自动验证。然后,棘手的部分就变成了将新证书放入 Firefox,我们需要自动远程完成此操作,然后让 Firefox 重新检查所有可能被禁用的附加组件。
诺曼底和研究系统
具有讽刺意味的是,解决这个问题的方案是一种特殊类型的附加组件,称为系统附加组件 (SAO)。为了让我们进行研究,我们开发了一个名为诺曼底的系统,它允许我们向 Firefox 用户提供 SAO。这些 SAO 会自动在用户的浏览器上执行,虽然它们通常用于运行实验,但它们也可以广泛访问 Firefox 内部 API。对于这种情况很重要的一点是,它们可以将新证书添加到 Firefox 用于验证附加组件的证书数据库中。[3]
因此,这里的修复措施是构建一个执行两件事的 SAO
- 安装我们制作的新证书。
- 强制浏览器重新验证每个附加组件,以便那些被禁用的附加组件变为活动状态。
但是,等等,你说。附加组件无法正常工作,那么我们如何让它运行呢?好吧,我们使用新证书对其进行签名!
将所有内容整合在一起……以及为什么花费了这么长时间?
好的,所以我们现在有了计划:发布一个新的证书来替换旧的证书,构建一个系统附加组件来将其安装在 Firefox 上,并通过诺曼底部署它。从 5 月 3 日星期五太平洋时间下午 6 点左右开始,我们在凌晨 2:44 开始通过诺曼底发布修复程序,或者说不到 9 个小时,然后又花了 6-12 个小时才让大多数用户获得它。从零开始,这实际上已经相当不错了,但我看到 Twitter 上有很多关于为什么我们不能更快完成的问题。有许多步骤很费时。
首先,颁发新的中间证书需要一些时间。正如我上面提到的,根证书存储在离线硬件安全模块中。这是一种良好的安全做法,因为根证书很少使用,因此需要确保其安全,但如果您需要紧急颁发新证书,这显然有些不便。无论如何,我们的工程师之一必须开车前往 HSM 存储的安全地点。然后,我们尝试了几次,但没有颁发完全正确的证书,每次尝试都需要花费一两个小时的测试,才能确定具体的操作步骤。
其次,开发系统插件需要一些时间。从概念上讲,这非常简单,但即使是简单的程序也需要小心,我们真的想确保不会让情况变得更糟。在发布 SAO 之前,我们必须对其进行测试,这需要时间,尤其是因为它需要签名。但签名系统已禁用,因此我们不得不为此找到一些变通方法。
最后,一旦我们准备发布 SAO,部署它仍然需要时间。Firefox 客户端每 6 小时检查一次 Normandy 更新,当然许多客户端处于离线状态,因此修复程序需要一些时间才能在 Firefox 用户群中传播。但是,我们预计大多数人已经收到了更新,或者收到了我们后来发布的更新。
最后步骤
虽然与 Studies 一起部署的 SAO 应该可以解决大多数用户的问题,但并非所有人都能收到更新。特别是,有许多类型的受影响用户需要另一种方法。
- 已禁用遥测或 Studies 的用户。
- 使用 Firefox for Android (Fennec) 的用户,我们没有 Studies。
- Firefox ESR 下游版本的使用者,他们没有选择加入
遥测报告。 - 使用 HTTPS 中间人代理的用户,因为我们的插件安装系统对这些连接强制执行密钥固定,而代理会干扰此过程。
- 使用非常旧版本的 Firefox 的用户,Studies 系统无法访问他们。
对于最后一组用户,我们无能为力——他们应该尽快升级到新版本的 Firefox,因为旧版本通常存在相当严重的未修复安全漏洞。我们知道,有些人一直使用旧版本的 Firefox,因为他们想运行旧式插件,但现在许多插件都可以在较新版本的 Firefox 中运行。对于其他组,我们开发了一个 Firefox 修补程序,以便人们更新后安装新证书。此修补程序作为“点版本”发布,因此人们会通过常规更新渠道获得它——可能已经获得了——如果你有下游版本,你需要等待你的构建维护者更新。
我们承认,这一切都不完美。特别是,在某些情况下,用户丢失了与其插件相关的数据(例如 “多账户容器”插件)。
我们无法开发出避免这种副作用的修复程序,但我们认为,从短期来看,这是大多数用户最好的方法。从长远来看,我们将研究更好的架构方法来处理此类问题。
教训
首先,我要说的是,这里的团队做出了惊人的工作:他们在收到初始报告后不到 12 小时内就构建并发布了修复程序。作为一个参与了会议的人,我可以说,人们在艰难的环境中非常努力地工作,几乎没有浪费时间。
话虽如此,这显然不是理想的情况,而且本来就不应该发生。我们显然需要调整我们的流程,既要降低此类事件及其类似事件发生的可能性,也要使其更容易修复。
我们将在下周进行正式的事后分析,并将发布我们打算进行的更改列表,但在此期间,以下是我对我们需要做些什么的一些初步想法。首先,我们应该有一个更好的方法来跟踪 Firefox 中所有可能成为定时炸弹的事物的状态,并确保我们不会遇到突然出现的情况。我们仍在研究这方面的细节,但至少我们需要对所有这类事物进行清查。
其次,我们需要一种机制,即使在一切其他功能都关闭时——尤其是当一切其他功能都关闭时——也能快速将更新推送给我们的用户。很高兴我们能够使用 Studies 系统,但它也是一个我们投入使用的并不完善的工具,它产生了一些不良副作用。特别是,我们知道,许多用户启用了自动更新,但更愿意不参加 Studies,这是一个合理的偏好(真实故事:我也关掉了它!)但同时,我们也需要能够将更新推送给我们的用户;无论内部技术机制如何,用户应该能够选择加入更新(包括紧急修复),但选择退出其他一切。此外,更新渠道应该比我们今天拥有的更具响应能力。即使在周一,我们也有一些用户还没有收到紧急修复或点版本,这显然不是理想的。这个问题已经有一些工作要做,但这次事件表明它有多么重要。
最后,我们将更全面地查看我们的插件安全架构,以确保它以最小的断裂风险强制执行正确的安全属性。
我们将在下周发布更详细的事后分析结果,但在那之前,我很乐意通过 ekr-blog@mozilla.com 电子邮件回答问题。
[1] 一些非常旧的插件使用不同的中间证书签名。
[2] 熟悉 WebPKI 的读者会认识到,这也就是交叉认证的工作方式。
[3] 技术说明:我们并没有以任何特殊权限添加证书;它通过被根证书签名而获得权威。我们只是将它添加到可以被 Firefox 使用的证书池中。因此,这不像我们在 Firefox 中添加了一个新的特权证书。
关于 Eric Rescorla
Eric 是 Mozilla Firefox 团队的首席技术官。