使用全盘加密保护 openSUSE 中的恶意设备
2025年7月18日 | Alberto Planas | CC-BY-SA-3.0
openSUSE现在有多种配置全盘加密 (FDE) 安装的方法。一种非常安全且简单的方法(YaST2)是通过用户空间工具实现,正如我们多次描述的那样(例如此处、此处或此处)。此解决方案基于systemd工具集,如systemd-cryptenroll、systemd-pcrlock和systemd-cryptsetup等,并由内部sdbootutil脚本进行编排。
使用此systemd方法的主要优点之一是能够集成多种身份验证方法。除了在initrd阶段启动时提示的传统密码外,我们现在还可以使用证书、TPM2或FIDO2密钥解锁系统。我们可以混合使用其中一些方法来创建多个LUKS2密钥槽,例如,使用TPM2以无人值守的方式解锁设备,并使用FIDO2密钥作为恢复机制。
坦率地说,TPM2及其TPM2+PIN变体对用户来说是最相关的。如其他帖子所述,TPM2是一个(有时是虚拟的)设备,可以使用称为度量启动的机制证明我们系统的健康状况。
这个问题的简单版本是,启动过程的每个阶段,从固件开始,都将加载并“度量”下一个阶段,然后再将执行委托给它。例如,这意味着在启动过程的最新阶段,UEFI固件会将启动加载程序从磁盘加载到内存中。这可以是shim、systemd-boot或grub2-bls。它将计算一个哈希值(通常是SHA256),并将命令TPM2对其中一个内部寄存器(PCR)执行“扩展”操作。
扩展是一个非常容易计算但无法复制的加密计算。它针对其中一个内部寄存器(PCR)进行,包括计算PCR的旧值与我们正在度量的组件的哈希值的哈希(再次是SHA256)。这个新值将替换当前的PCR值,因为这是更改这些寄存器的唯一方法。安全属性在于,在加密上不可能强制写入所需的值到这些PCR中的一个,但非常容易计算最终值。
因此,这意味着如果启动链过程的所有组件都经过度量(UEFI固件中的所有阶段、固件配置、启动加载程序、命令行、内核甚至initrd),则最终的PCR值可以与我们的预期进行比较,并发现系统是否已使用已知良好的软件和配置启动,从而使我们能够立即知道启动链中的某个组件是否未经我们同意被黑客攻击或修改。
这是一个强大的属性,但更有趣的是,我们可以拥有只有在我们处于这些良好或已识别状态之一时才能打开的秘密。例如,我们可以使用TPM2加密(密封)打开加密磁盘的密钥,并结合一项策略,该策略只有在您使用相同的TPM2且PCR值在预期列表中时,才能解密(解封)相同的密钥。这些策略可能非常复杂,并且可能包含额外的密码、证书或其他检查,这些检查将在TPM2解封密钥之前进行验证。
通过在 openSUSE 中集成这种模型,借助 systemd 工具,如果系统处于健康状态,我们现在可以避免输入密码来解锁加密磁盘。这里的“健康”是指我们通过加密技术保证在启动过程中使用的代码和配置是预期的,并且没有人输入 init=/bin/bash 到我们的内核命令行,或者用易受攻击的内核或 initrd 替换它们。
通过我们在 openSUSE 中整合的这个模型,我们可以对系统进行更新,包括引导加载程序或内核,而 sdbootutil 将透明地生成新的预期 PCR 值预测,这些预测现在被认为是安全的。这意味着 TPM2 策略的更新,这将在下次启动时被考虑,从而使自动解锁成功。如果出现问题且未达到预期的 PCR 值,用户将需要输入存储在不同 LUKS2 密钥槽中的密码来打开设备,以审计系统并验证它。
设计上的缺陷
如前所述,使用 TPM2 显然提高了安全级别,但这并非最终答案。安全始终是渐近逼近。
几年前,Windows BitLocker FDE 解决方案被描述为存在一种物理攻击。BitLocker 也以类似于之前描述的方式使用 TPM2,但没有使用加密会话与设备通信。截取 SPI 总线被证明可以恢复解锁磁盘的密码。systemd 从中吸取了教训,并及早使用了加密会话,但如果用于解封密钥的策略还要求用户输入 PIN 或密码,也可以避免这种攻击。现在,TPM2 只能在 PCR 处于正确状态且提供的密码正确时解封秘密。应该注意的是,据我所知,SPI 嗅探可能适用于 Clevis,参阅此处。
但最近公开的第二次攻击完全影响了最初的提案,并且不需要原始攻击的复杂性。(披露:此攻击也在此前几个月在内部独立描述过,并提早采取了一些反制措施)
该文章描述了如何通过检查initrd中用于挂载加密设备的文件系统UUID来执行此攻击。此信息存储在initrd中的/etc/crypttab中,其内容类似于这样
systemd-cryptsetup attach cr_root /dev/disk/by-uuid/$UUID 'none' 'tpm2-device=auto'
如果在启动过程中使用了预期的固件、配置文件、内核和initrd,那么TPM2的PCR寄存器将具有与解锁设备的策略匹配的值,并且密封的密钥现在可以由TPM2解封,磁盘将被解锁,切换根目录将成功,并且启动过程将在rootfs中继续。
但是,如果原始驱动器被一个具有相同UUID(毕竟这是一个公共信息)且也已加密的驱动器替换了怎么办?那么PCRs 将处于相同的正确状态。请注意,在度量启动中,是前一个阶段在委托执行之前度量下一个阶段。然后systemd-cryptsetup将尝试使用TPM2使用TPM2成功解封的密钥来解锁设备,然后……当然会失败。恶意设备可能在LUKS2头部中有一个TPM2密钥槽,但肯定无法用这个TPM2或秘密密码打开。
在这种情况下,systemd-cryptsetup 将要求输入密码来解锁设备,攻击者可以输入一个密码,这次将打开恶意设备。根目录切换将发生,但现在它将在虚假的rootfs中继续启动过程,存储在那里的程序可以向TPM2提问,而TPM2仍然包含正确的PCR值。其中一个问题可能是使用当前策略解封秘密密钥。而这次(像之前所做的那样),TPM2将同意将秘密交付给恶意程序。游戏结束。
当然,对于这种攻击,有解决方案。
一种方法是再次使用TPM2+PIN而不是TPM2,这与嗅探攻击的解决方案相同。在这种情况下,第一次systemd-cryptsetup调用将失败,并会要求输入密码来解锁设备。但现在恶意程序无法要求TPM2使用当前策略解封设备。PCR值将匹配,但策略也要求输入真实用户已知的秘密PIN或密码,如果没有它,解封将失败,密钥将保持安全。
另一种解决方案是某种程度上使策略失效,在切换根目录之前扩展一些相关的PCR,这样策略在此之后就不能再应用了。这可以通过systemd-cryptsetup在/etc/crypttab中使用measure-pcr=yes选项自动完成。有了这个选项,PCR15将使用卷密钥进行扩展,这是一个只有知道一些设备密钥才能提取的秘密。为了使此解决方案生效,PCR15需要包含在当前策略中,其预期值为0x000..00,即默认值。一旦恶意设备被黑客提供的密码打开,PCR15将自动扩展,并且其值将与0x000..00不同,从而在切换根目录之前使策略失效。
那是一个很好的解决方案,但对我们来说不适用。在日常情况下,用户需要更新系统,并且需要计算新的策略来替换旧的策略(例如,当内核更新时)。因为使用systemd-pcrlock,策略存储在TPM2的非易失性RAM槽(NVIndex)中,我们需要以某种方式保护它,使其不能被其他进程替换。为此,systemd将一个秘密密钥(恢复PIN)存储在另一个NVIndex中,该NVIndex由相同的策略密封!如果密钥无法自动恢复,因为策略不再适用,那么将要求用户提供恢复PIN,如果策略总是失效,这将使更新过程变得有些不愉快。
最后,解决这个问题的另一种方法是,如果检测到设备不是预期的设备,就停止启动过程。我们可以设想在initrd中存在一个新服务,它在切换根目录之前,在最后时刻执行,如果存储rootfs的设备不是预期的设备,它可以停止启动过程(可能是暂停系统)。
为此,PCR15仍然是一个很好的解决方案。它包含一个秘密(卷密钥)的度量,该秘密只有真实用户才知道,并且不能被攻击者复制。理想情况下,我们可以为PCR15创建一个预测,并让此服务将实际值与预期值进行比较,如果它们不同,则可以停止启动过程。
这就是sdbootutil中的measure-pcr-validator服务所做的事情。sdbootutil首先为在initrd期间打开的所有加密设备生成一个预测,并检查/etc/crypttab中是否存在正确的标签。为了能够访问卷密钥,该工具需要root密码,因此此预测仅在真正需要时才更新,例如添加新的加密设备时。此预测由主机中存储的私钥签名,作为额外的安全度量,但由于公钥也存储在ESP中,因此坦率地说并没有增加太多。
一项额外的服务 (measure-pcr-generator) 将对加密设备的打开顺序进行一些排序,因为这个顺序对于生成一个唯一的 PCR15 值至关重要。如果我们只有一个设备,度量顺序并不重要,但如果我们有三个设备(例如 rootfs、/home 和 swap),我们可能会得到六个可能且有效的不同 PCR15 值。
最后一步是,initrd中的dracut-pcr-signature服务将从ESP导入预测、签名和公钥,以便measure-pcr-validator可以检查签名并比较PCR值。
就这样!
这种方法与新的systemd-validatefs在文件系统层面所做的事情也有些相似。