By Toradex Jon Oster
这是我们关于 OTA 软件更新的 7 部分系列文章的第 3 篇。 在第 1 篇中,我们向您介绍了能够远程交付软件更新的所有重要原因,在第 2 部分中,我们介绍了软件更新系统危险的所有原因。 今天,我们将会探讨保护您的软件更新系统的方法,这些方法乍一看似乎是安全的,但实际上并非如此,每个示例都有现实世界中著名的故障。
软件更新系统的组成部分
让我们从最简单的系统开始。 有一个设备,它知道它可能需要更新。 为简单起见,我们还假设,更新是以单个文件的形式进行,并且设备知道它所需文件的名称以及服务器的地址,我们称之为存储库,在这个里面设备能够找到需要的文件。然后设备可以访问存储库,请求文件,下载并以任何可行的方式安装它:
在设计软件更新系统时,我们需要解决的最基本问题是:设备如何确信它收到的更新是可信的? 这是一个非常重要的问题,在物联网中则显得尤其困难,在大多数情况下,我们希望自动完成更新或在用户同意的情况下进行。我们通常不想要设备的用户来判断推送给他的更新是否可信。
让我们看看我们可能会选择的一些常见方法,以及这些方法存在的问题。
选项 1:验证存储库,设置安全通道
攻击者可能具备的一项常见能力就是拦截流量或冒充存储库。因此,只要我们可以确保设备能够验证其正在与之通信的存储库是真实的(并且可以在整个“对话”中持续验证),那么它就可以安全地安装所找到的更新文件。 而且由于我们已经有了一个完善的方法来做到这一点(我们一直依赖 TLS),所以我们达成目的了。
因此,您可以使用证书链,一直追溯到某个受信任的根证书颁发机构,以验证服务器的身份,并使用 TLS 为接下来数据传输设置加密连接。这样问题解决了,因为只要我们与正确的服务器通信并且有加密连接,我们应该没问题,对吧?
不完全是。 有两大问题。 首先,TLS 可能不会被破坏,但 TLS 所依赖的信任体系并不完美。曾多次发现根证书颁发机构发布证书以允许中间人攻击。 例如,中国和法国的国家证书颁发机构都被发现签发了假冒 Google 域名的欺诈性证书。 2019 年的一项学术研究表明,欺诈性 TLS 证书也可以在网络犯罪市场和暗网上随处购买。
其次,存储库本身可能会受到入侵。由于存储库的密钥用于签署每个请求,因此需要将其保存在服务器上,而攻击者可以从中窃取它。即使服务器的密钥存储在硬件安全模块中并且无法被窃取,获取服务器访问权限的攻击者仍然可以在其控制期间对连接的每台设备安装恶意软件。这正是 2013 年 Ruby Gems 存储库所发生的事情,使大部分互联网陷入恐慌。攻击者利用了 rubygems.org 中的漏洞。幸运的是,当时攻击者并没有恶意,只是想引起 RubyGems 团队的注意,以便他们修补漏洞。但是,如果他们愿意,他们可以发布 Rails 等流行的恶意版本,并接管(或窃取密码)任何碰巧在 RubyGems 存储库遭到入侵期间构建的 Ruby on Rails 网站,这包括 Twitter、Airbnb 和 Hulu 等网站。
选项 2:签署您的软件
因此,很明显我们不能仅仅依赖于验证存储库的身份。那我们是否可以在将软件更新放入存储库之前,就为其添加一个准许印? 每个客户端都可以验证它,这样即使存储库被入侵,我们的用户仍能够保持安全。理论上这也是一种使用基于公钥加密的数字签名很容易做到且非常安全的方式。我们生成一个离线密钥,确保我们的客户端拥有公钥,并让我们的软件更新程序拒绝任何没有有效签名的程序。
什么是离线密钥?
离线密钥是未保存在任何连接到 Internet 的计算机上的密钥。它可能是像 Yubikey 这样的硬件设备,也可能是密钥文件存储在 USB 存储设备上并被保存在某个地方,只在需要时才取出。您不能将离线密钥用于 TLS 之类的东西,因为每次建立连接时都需要使用这些密钥。但是您可以将离线密钥用于很少发生的加密任务,例如签署主要软件版本。
这是 Red Hat 和 Debian 等主要 Linux 发行版采用的基本方法。(多年来进行了各种改进,但基本方法保持不变。)当您安装操作系统时,它会提供有一个 GPG 密钥并信任其可以自我更新。这使得更新软件可以安全地分发到世界各地的存储库镜像,而存储库不需要任何密钥。
通过验证存储库的解决了两个问题。我们不再使用复杂的证书认证系统,现在只有一个密钥,我们自己将其分发给客户端。我们还可以防止存储库泄露,如果攻击者控制了存储库并尝试发送恶意更新,我们的更新客户端将拒绝它,因为它没有有效的签名。但这种方法也存在一些重大隐患。
最大的问题是关于密钥分发和信任。如何确保您的客户拥有正确的密钥,如何在紧急情况下更新密钥,以及如何确保离线密钥确保离线,而不会在开发人员的笔记本电脑或编译系统上泄漏? Red Hat (Fedora) Linux 和 Debian Linux 软件更新程序都依赖 GPG 签名(使用预分发的公钥)都曾遇到过问题。 2008 年,Red Hat 发现他们用于签署软件包的服务器遭到入侵。对于 Debian,问题更为严重:原来用于为他们开发人员生成签名密钥的程序存在漏洞,这意味着需要替换大量的密钥。从而在全世界范围内引发一波安全通知和漏洞修复。在这两种情况下,解决方案都是发布全球公告,要求系统管理员采取手动步骤更换密钥,并扫描其系统以查找潜在漏洞。对于有专门的付费系统管理员的系统,这种方法可能是可行的。如果嵌入式系统中存在类似的密钥泄露,则可能需要全球召回产品。
还要注意的是,在 Red Hat 的案例中,导致密钥泄露的仍然是服务器遭到入侵。因此,即使密钥不在软件存储库中,它却仍然存在与自动构建系统上。它并不能抵御存储库遭到入侵,而是将问题移到了整个链条中的上一个环节。
简单软件签名的另一个主要问题是它只能防止恶意软件。软件更新系统,尤其是嵌入式软件更新系统,需要应对更多类型的威胁。例如,经常会发布软件更新来解决安全漏洞;如果攻击者可以令客户安装旧版本的软件,甚至只是确保他们没有及时收到更新,这足以引发更严重的攻击。(这些类型的攻击分别称为回滚和冻结攻击。)
选项 3:采用上面的两种方案
因此,我们现在研究了软件更新系统采用的两种主要方法,以及它们存在的问题。验证存储库并使用加密连接 (TLS) 下载更新很容易受到证书颁发机构和广泛分布信任链的所有问题的影响,并且在服务器遭到入侵的情况下根本无法保护您。使用离线密钥对软件进行签名并禁止未签名的更新可以解决存储库遭入侵的问题并避免证书颁发机构的问题。但在现实世界中,将签名密钥保持离线是不方便的,因此它们通常可能会出现在构建服务器(甚至开发人员的笔记本电脑)上,从而抵消了让密钥“离线”的优势。尽管离线签名避免了证书颁发机构和信任链的一些问题,但它也带来了新的问题,尤其是围绕密钥分发的问题。
如果将两者结合起来会怎样呢?如果你已经看到这里,那么你可能已经有很多思路来解决这些问题,也许我们可以有一个主密钥来签署一个受信任的密钥列表,并且我们可以在多个位置发布密钥。我们甚至可以发布并签署一份不应再被安装的旧软件镜像列表,并让我们的更新客户端检查该列表。您可能会认为,这里列出的问题似乎都不是很难解决的。你是对的!您完全能够自己构建一个系统实现所有的更新服务,该系统整合诸多技术,如用于 GPG 密钥撤销的固定自托管密钥服务器、预先配置的私有 TLS 证书颁发机构以及具有一些检查所有签名、密钥、到期等规则的更新客户端。
但是,如果您采用这种方式,会出现很多复杂情况,并且错误或漏洞可能会更容易出现。(阅读 Microsoft 对允许安装 Flame 恶意软件的安全漏洞的解释,了解即便是数十亿美元公司在尝试将整合这些独立的技术时也可能会忽略一些事情。)您不希望您的更新系统由不同的技术方案简单的拼凑而成,也不希望更新系统中的关键部分如密钥分发有外部系统处理。
试图将一些现有技术组合在一起的解决方案的另一个问题是故障模式可能无法预测,并且人类的天性有时会胜过理想的安全实践。例如,您可能构建了一个使用 TLS 来验证存储库并要求对软件进行签名的系统,但忽略了构建允许您撤销和更换签名密钥的系统。几年后,一位初级开发人员不小心将软件签名密钥推送到了公共 github 存储库。您对召回您的设备进行修复以及更换密钥进行成本效益分析,但从业务角度由于费用过高而被否决。现在您只剩下 TLS 保护,所有上述问题。或者您使用离线密钥签署发行版本,但几个月后决定开始向客户发布 nightly 版本,让他们测试新功能。没有人愿意每晚都手动对构建的软件进行签名。因此您开放让 CI 流程访问私钥,这样它就可以在夜间进行签名。现在该流程成为您整个设备集群的单点故障。
为了避免这些系统性问题,您从一开始就需要有一个能够灵活管理密钥的系统,并将其包含在更新系统中。您的设备应该有办法确保每次检查更新时都拥有关于密钥、存储库位置等的最新信息。正确构建这些东西一件棘手的事情。但幸运的是,有 20 年的学术研究和实践经验可以指导您,包括既定的标准和最佳实践,以及开源、经过时间考验的软件存储库和更新客户端实现。
我很快将撰写有关软件更新规范的文章,以及我们在 Torizon 平台中构建 OTA 更新系统时所做的决策。我们当然面临一些挑战:我们的客户正在医疗设备和工业自动化等安全敏感领域中开发物联网设备。他们有时还拥有异构集群,需要将不同的更新分配给不同的设备,并具有服务器端控制的能力。我们必须构建一个更新系统,确保离线密钥的安全可稳定,以及客户端可控的加密(在高安全性和高度监管的行业中必不可少),同时仍然提供 Toradex 众所周知的易用性和使用体验。请继续关注我们是如何做实现的!