|
本帖最后由 sairen139 于 2019-10-6 23:50 编辑
UEFI安全启动
来自专栏 UEFI和BIOS探秘
UEFI安全引导(Secure Boot)的核心职能就是利用数字签名来确认EFI驱动程序或者应用程序是否是受信任的。在简要地介绍了数字签名的概念(这是安全引导的基础)之后,我们重点介绍UEFI 安全引导是如何利用数字签名以及其他的加密方式(如Hash函数等)来确保系统引导的安全可靠。我们还会讲到Secure Boot的部署方面以及他在操作系统的安全负载方面所起到的协助作用。
密码学基础
很显然,本篇文章的重点在于安全引导,而想要更好地理解安全引导则需要一定的密码学基础。密码学是一门高深的学科,关于它的论著数不胜数,此处我们只讲大致原理,不讲细节,感兴趣的读者不妨自行寻找完整的资料来深入的理解这门学科。
加密Hash(散列)函数
稍有了解的人都知道:散列函数h是使一个较大域的X的值变为较小范围Y的一种数学函数,同时尽可能的使X均匀投影到Y的取值空间中,这样可以减小哈希冲突(你们还记得吗?敲黑板!用数学语言来解释:存在属于X的x1和x2,有相同的对应关系Y,即h(x1) = h(x2),这种情况就叫哈希冲突)的风险,
加密散列函数f是具有如下属性的散列函数:
1). 很难找到x1和x2,使得f(x1)=f(x2)(这一性质通常被成为抗碰撞性(collision resistant))。
2). 对于已知的y,很难找到一个x使得f(x)=y(这一性质通常被称为抗原像性(pre-image resistance),通俗的讲也就是单向性,即从输出反推输入很难或者耗时很长)。
此外,加密散列函数通常用来在给定x的情况下,快速计算y=f(x)。比较出名的应用有MD5,SHA-1,以及SHA-2系列等等。
加密散列函数在一些需要保护数据完整性的应用中是很有用的,由于其算法的特殊属性,攻击者很难在修改数据(例如某可执行文件)后不被拥有原始Hash值的人发现。同样的,即使攻击者知道原Hash值,他也做不到不改变Hash值的情况下对文件进行替换(pre-image resistance)。
2. 公钥加密法和数字签名
加密,是以某种特殊的算法改变原有的信息数据,使得未授权的用户即使获得了已加密的信息,但因不知道解密的方法,仍然无法了解信息的内容。
人类尝试加密数据的历史悠久,从希波战争到凯撒大帝出处都有它的痕迹,可以单独写一本书,这里按下不表。但在非对称密钥出现之前,一直都是对称密钥,大家都认为,如果想要在双方之间建立机密通信,那么双方就必须约定一个共享的秘密——shared secret,依靠这个共享的密钥进行加密解密。进而如何可靠的传递密钥变得异常重要。
公钥加密法在20世纪70年代末由Whitfield Diffie,Ralph Merkle和Martin Hellman提出。这种方法,一不需要事先约定,二不必事先知道与谁进行安全通信,也正是因为这两点特性,这一技术才会被称为是革命性的,它也是建立在这样一种概念——公钥部分和私钥部分共同组成新形式的密钥——非对称密钥。公钥可以交给任何人,而密钥的私有部分仅被其所有者知道。这就好比是通信双方,有一个人建立了一个保险柜(将保险柜的制造方法理解为私钥),将信息存入其中,并把保险柜的钥匙(公钥)交给另一方,而另一方当然可以拿着这个钥匙获取保险柜中的东西,但他不能,也不必知晓对方究竟是如何制造出了这样一个保险柜。
在受到Hellman,Merkle和Diffie想法的启发后,Ronald Rivest,Adi Shamir和Leon Adelman在几年后发明了RSA密码系统。使用RSA,持有私钥的实体A不仅可以使用该密钥来解密由知道A的公钥的人发送给他的数据,而且A还可以使用他的私钥来对一些数据进行数字签名(给别人配钥匙),如下图所示:
任何持有A的公钥部分并且可以访问签名数据的人,都可以通过数学手段来验证这一点:数据只能被拥有A的私钥的人来签名——也就是说,只有A可以生成该消息——并且它不会被没有私钥的人修改。
许多需要进行加密的程序都应用了这一特性,UEFI安全引导也是其中之一:固件驱动程序的发行者可以对该驱动程序进行数字签名以保证驱动程序的真实性,并且向验证者保证,该驱动程序未被其他人(潜在的攻击者)篡改,如下图所示:
3. 公钥基础设施
如果你在阅读前文的过程中脑袋没有停摆,那么此时的你心里应该有一个大大的问号:虽说这个私钥不会被别人获取或破解,那么如何传递公钥呢?也就是说,依赖方(需要验证数字签名的一方)如何才能确定这个公钥A,确实就是真正的公钥,而不是伪造的呢?为了解决这一问题,我们引入了公钥基础设施这一概念(Public-Key Infrastructures ,简称PKI)。在更高的层次上,引入了一个具有极高权威的第三方,这个第三方在公钥上附加数字签名,以证明该公钥来自于对应的公钥—私钥对的创建者。可以将这类数字签名理解为“荣誉证书”,“证书”的颁发者被称为证书授权机构(Certificate Authority,简称CA)。现在双方之间的信任问题就转化为了如何去信任CA的问题。一个CA可以拥有所有权限,或是有专门的职责,比如,一个CA仅可以为个体发出证书(仅证明该个体Alice拥有公钥A),而另一个CA仅颁发出于代码签名目的的证书(证明签名者S可以使用他的私钥对(且仅对)计算机代码进行数字签名 )。通常这些特定的CA是由更高级CA进行签名认证过的。通过这样的职能划分,不但大大减小了分发顶级CA(通常称为信任锚或根CA)的数量,安全性也不减反增。顶级CA对所需的证书进行自签名,也就是说他们签署了用来自证的证书(他们自己证明自己是自己)。从技术方面来说这是不需要的 ,之所以这么做是因为依赖方(依赖于证书的这一方)需要知道这个特定的根CA确实拥有所需的公钥,因此为了方便起见(证书的一致使用),常常使用自签名证书。
由CA颁发的证书是可以被其撤销的。撤销实际上是由CA作出的声明,表明用来产生对应公钥的某个实体(称之为B)的私钥因产生了安全问题而不再受信任:这可能是由于B丢失了他的私钥或是因为它被怀疑已经遭受了入侵或是其他的一些原因。一旦证书被撤销,它就不能再用于验证由相应的私钥做出的签名。正因如此,在数字签名上加盖时间戳有时就显得很必要:依赖方可以通过时间戳的信息来确定签名是否创建在否证书被撤销之前(时间戳本身则是由一个被称作时间戳机构(Timestamping Authority)的第三方创建,他们也需要被依赖方信任)。
基础构架
在介绍了散列函数和数字签名之后,我们现在可以看看它们是如何应用于UEFI安全启动中的。
1. 信任根root of trust
如前文所述,为了可以信任数字签名,依赖方需要知道所声称的签名者确实拥有用于创建数字签名的私钥。由于CA通常作为公证人——证明某个签名可靠的“受信任的第三方”,因此如何分发CA,对信任关系的建立尤其重要。
在UEFI的安全启动中,有一个构成信任基石的特殊秘钥。这个秘钥就是平台秘钥(Platform Key,简称PK),此密钥的持有人能够修改平台上存在的任何其他信任锚(Trust Anchor)列表。通常,PK由设备制造商拥有,但如果一个公司需要完全掌控公司内的具有安全启动的PC时,他也可以持有PK,从而方便企业IT管理。
2. 其他的信任锚
除PK外,UEFI安全启动还维护两个额外的信任锚数据库:
1). 密钥交换密钥(Key Exchange Key,简称KEK)数据库;
2). 已允许签名(The Allowed signature)数据库。
KEK数据库中包含了拥有修改第二个数据库权限的信任锚们。而已允许签名数据库包含了那些用来验证UEFI固件映像上的签名的信任锚。
实际上还有第三个数据库:被禁止数据库(the Forbidden database).这一数据库包含了那些因为证书被撤销而不再受信任的签名者。
3. 签名固件
在UEFI安全启动中,未明确列入白名单(后面介绍)的固件必须进行数字签名并且要加盖时间戳,以便UEFI引导管理器验证映像签名。UEFI使用PE/COFF格式【1】(Windows标准的执行文件格式)和微软验证标准【2】来对固件驱动文件进行签名。PE/COFF格式中的date/time 域刚好可以用来记录时间戳。
4. 白名单和黑名单
UEFI安全引导不仅采用了使用数字证书这一手段来确认已签名的固件组件是否值得信赖。除了数字证书之外,UEFI安全引导还允许授权实体将特定映像散列识别为可信(或不可信)。这个授权实体是指PK或KEK的拥有者。
该方案比大多数现有PKI具有优点,因为可以允许(列入白名单)或撤销(列入黑名单)某些个别驱动,而不是要求撤销签名者——这种操作具有潜在的造成更大范围(far-reaching)影响的可能,因为所有由该签名者签名的驱动在安全引导系统上都将失效。当来自供应商的特定驱动程序已被发现包含漏洞的情况时,这一考虑的必要性是显而易见的。这个供应商不应该被认为是恶意的,也正是为了防止该供应商的所有签名都被认作无效,我们的操作仅仅是将这一(包含有漏洞的)驱动列入黑名单(通过将其哈希值添加到禁止数据库)。而白名单的实用性体现在类似这样的情景中:当一个新驱动被检测到且未签名,此时,授权的实体可以明确地将驱动驱动的哈希值添加到已允许数据库中,从而允许系统随后引导这个驱动。
5. 已认证变量
从上述的的逻辑中,我们可以达成这样一种共识:想要更新任何数据库(包括KEK,Allowed,Forbidden)的一方必须且只能在有适当的授权下才能这么做。为UEFI安全启动而选取的授权模型(authorization model)也遵循这样的的设计思路,它利用了数字签名本身从而需要强化UEFI变量模型,需要对Caller也就是调用者进行身份验证,这种变量叫做已认证变量(Authenticated Variables)。简而言之,如果要更新已认证变量,调用者要在新的变量值上进行签名,之后受信任的固件在更新变量的值之前通过验证该签名来决定应否更新。
6. 变量的回滚攻击
现在来思考这样一个问题:如果一个旧版的Forbidden数据库更新被攻击者捕获,并在攻击者的操作下重放(replay)原操作,从而将数据库回滚至较老版本(重放攻击),这将会产生什么后果呢?最糟糕的情况是,因为平台的黑名单从不包括攻击者的证书,攻击者将一直能够使他的可疑的驱动得到运行。为了防止这种情况,任何已认证变量的更新都必须加盖时间戳。受信任的UEFI固件必须确保将要变更的变量的时间戳(它是签名的一部分)晚于当前已认证变量相关的时间戳。但是有一个例外:UEFI 2.3.1引入了一个功能,将值扩展到现有的已验证变量上(如添加到Forbidden数据库)。在这种情况下,因为更新不再影响已经存在于数据库中的变量的值,时间戳就显得不那么重要。不过即便在这种情况下,如果时间戳晚于上一次更新的时间戳,那么固件最好也更新这一时间。因为它将作为“最后已知的更新”时间,并且如果稍后还需要进行“写入”(而不是“附加”)更新请求,可能会用到这一信息。
7. 驱动的回滚攻击
与认证变量的回滚防止相关联的是驱动程序自身的回滚防止。如果当前驱动被曝出存在安全漏洞,因而需要发布一个新版本。除非UEFI的实现中已经包含了一些方法来确认驱动的版本(在抽象意义上),否则难以知晓这个新版本的驱动是否实际上是当前安装的驱动程序的较旧版本,而不是真正的新的版本。而敏锐的攻击者极有可能利用这一点,将一个漏洞更多的旧版本伪装成新版本发布,进而获得大量的平台控制权。
为了防止这种情况,符合UEFI 2.3.1规范的平台必须带有固件组件的回滚预防功能。不管是查询来自相关固件的PE/COFF映像头(the PE/COFF image header of the firmware)的TimeDateStamp字段或是通过其他方式比如将版本与驱动程序相关联都可以实现这样一种功能。固件更新根据当前使用的驱动程序的时间(或版本)进行验证,并且仅当发现新驱动比当前使用的“更晚”时才进行更新。
8. 完整性
UEFI安全启动平台一般包含多固件组件,包括:
1). UEFI 启动代码
2). UEFI启动管理器
3). UEFI 驱动
4). UEFI应用程序
5). UEFI 操作系统装载器
当安全引导开始时,平台的初始化模块将会包含一个保存在只读存储器中的RSA公钥。这个公钥是用来验证UEFI启动管理器的。一旦映像验证通过,UEFI的启动管理器就会启动并通过加载和初始化EFI启动顺序变量指定的驱动来继续引导平台。在加载每个UEFI映像之前,启动管理器通过查询允许和禁止签名数据库来判断映像是否被允许加载。只有那些签名存在于允许数据库记录(或是该映像的签名者存在于已允许库中)的而且不存在于被禁止库中的(或是该映像的签名者存在于被禁止库中)才允许被加载和初始化。
那些由于签名认证无效的或是与被禁止库记录有关联而被拒绝的映像的信息储存在一个UEFI表中,以便之后的操作系统来查询consumption(或是可能的补救)。
一旦UEFI引导时和UEFI运行时服务可用,就可以更新KEK,Allowed和Forbidden数据库了(PK可能也会被修改,但这一操作很罕见)。为了防止这些库被有故障(或是有漏洞)的EFI组件被未经授权的更新篡改,最好的方法是只允许通过可信的平台组件来进行更新,从而保证在应用这些更新时,该更新的签名是有效的。
部署
安全的架构没有被正确的部署,安全性会受到极大的影响。在平台的整个生命周期中有很多个步骤和安全平台的部署相关,这里我们只讨论部分内容。
1. 出厂时
在支持安全启动的平台的制造过程中,平台供应商一般都会提供初始的安全引导配置.。简言之,这种配置包括:
1). 生成初始的已允许数据库(Allowed Database)
2). 生成初始的被禁止数据库(Forbidden Database)
3). 生成初始的秘钥交换秘钥数据库(KEK Database)
4). 设置平台秘钥(PK)
当上述的最后一步业已完成时,平台将退出安装模式,并且任何涉及到以上数据库的更新都要授权(即要求包含数字签名)。
如果已允许数据库中还包含了OS Loader(操作系统加载器,)的签名信息,安全引导配置至此已全部完成。如果这个平台的默认启动模式是安全引导,那么平台厂商还需要将安全引导变量(Secure Boot variable)设置为True;以便告知OS Loader安全引导已启用。
2. 现场维护时
我们前文提到过,安全启动设备部署好以后,有时会需要修改一些与安全启动相关的数据库中的内容。比如发现了一个固件映像是容易受到安全威胁的,那么他就不应该再被添加到安全引导的过程中。同样,平台所有者有时候想要添加一个新的KEK签名来允许对Allowed或Forbidden数据库进行更灵活的更新。
我们可以通过在UEFI运行时的一些特殊的变量来进行管理。一如前文所讲,任何涉及到已认证变量的更新都要求签名,签名必须采取PKCS#7的形式,并且必须包含时间戳,以便UEFI运行时Variable服务验证此次更新不是旧版更新的重放(避免重放攻击)。
3. 故障恢复时
现在来考虑另外一种情况:当一个新驱动被发现与平台不兼容或者包含了使其不适合在设备上使用的缺陷。而由于安全引导不允许固件的程序回滚(因为这将使得早期固件驱动版本中的易受攻击的漏洞重新暴露出来),如果不在设计时对这种情况加以预防,当设备真正遇到这种情况时可能就会彻底不可用,造成不可估量的损失。
好在平台和平台的管理员至少有两种方式来避免这种情况的发生:
1). 可以为当前存在用户提供旧有的的比较稳定的固件来替代有问题的固件。
2). 对老的固件映像重新签名,给他加一个新的时间戳以使他通过验证(老酒装新瓶)。
这样一种“恢复”机制的存在,在不损害系统安全的情况下增强了系统的鲁棒性(或健壮性,可以理解为系统的抗干扰和自修复的能力)
OS加载器
UEFI安全引导的用途还包括调用OS加载器(OS Loader)。回想一下,在UEFI中,加载器是一种特殊的UEFI可执行文件。由于操作系统及其加载器的来源通常不同于系统板UEFI固件,加载器安全引导的职能就是将OS供应商的OS加密绑定到极有可能由另一供应商生产的硬件平台上。可能有以下几种情况出现:
1. 本地存储
大多数情况下,操作系统都是存放在本地存储器上的,例如本地的硬盘。UEFI引导管理器首先验证操作系统加载器的签名,如果通过,就将权限移交。在获得控制权后,加载器将加载os,并在此过程中的某一时刻调用 ExitBootServices() 。该调用结束后,只有UEFI运行时服务仍然可用。
由于UEFI变量服务属于运行时服务,因此操作系统仍可能执行与安全引导有关的变量的更新,然而,验证这种更新的有效性的责任仍然在可信固件身上。
2. 可移动介质与网络启动
UEFI也允许从和移动介质加载操作系统。UEFI引导管理器读取引导顺序变量以确定是否优先从可移动介质或者从固定本地存储开始引导。第三种是基于网络的操作系统加载。例如使用PXE下载一个OS Loader。在这过程中,安全引导仍然会验证OS Loader的有效性。
Measured Boot
安全引导与受控引导常常容易引起混淆,在了解了这么多安全引导的知识后,我们不妨对这两个名词作明确的区分。首先来了解一下受控引导
1. 背景
受控引导是指这样一种引导过程:在引导过程期间加载的每个组件都被检测,结果被放置在可信任的环境中,比如TPM。在引导过程期间进行的检测可以当做证明提供给第三方,也就是说由引导测试作出关于设备状态的声明。安全引导和标准引导的组合通常称为受信引导(Trusted Boot)。
2. 互补性关系
安全引导提供了本地的完整性验证,而受控引导提供了平台配置的已验证声明。这些声明可能会被第三方用作平台配置的远程验证,比如用来证明平台确实遵循了某些必不可少的安全策略。
可以看出,UEFI安全启动与标准启动既相互协调,又独立工作。需要特别指出的是,任何与平台配置相关的UEFI变量都应该被包括在受控引导的检测过程中。也正是因为这样 Allowed,Forbidden,KEK,PK四个数据库也必须包含在内。
总结
这篇文章从密码学讲起,详细阐述了公钥加密和数字签名的原理,在此基础上介绍了UEFI安全引导的功能构成,运行原理,安全保障机制,让我们对安全启动有了总体性的了解。
后记
到这里所有部分都介绍完了,安全性最近几年也越来越得到重视,也许有人还意犹未尽。下面几点我们可以回去仔细思考,在网上找找资料,对大家深入了解问题会有很大助益。
1)我们介绍了Secure Boot和大致的Meatured Boot。现在很多Intel平台引入了Boot Guard、BIOS Guard、TXT等等新的安全手段,他们很多都是从硬件本身(当然也需要软件辅助)来提高系统整体的安全性。这些软硬件手段之间的关系是如何的,他们是替代关系还是合作关系?
2)也许大家听说过前几年炒得不可开交的打开安全启动对于Linux圈的巨大影响。我们可以思考一下,现在Linux发行版众多,每种Linux的OS loader都有可能不同(OS loader也要验证Linux image的签名),这些由不同公司或者机构发布的Linux,如何获得签名,从而能在出厂时就打开了安全启动的PC上,得以运行呢? |
|