UEFI secure boot(2)

您所在的位置:网站首页 boot分区签名验证 UEFI secure boot(2)

UEFI secure boot(2)

2024-07-02 16:55| 来源: 网络整理| 查看: 265

UEFI中Variable的实现

安全启动涉及到的关于启动模式变量以及上述密钥变量的保存,在uefi中,设置开机的启动项,设置开机密码等功能.这部分功能在标准的uefi中都是使用Variable这套机制实现的。而安全启动中的变量的保存也是通过这套机制来实现的。这些变量将分别保存到内存以及flash中。

(1)Variable变量在flash中数据结构

当安全启动禁用时

即在系统中只保存关于一些启动过程中的相关变量时,格式如下:

在flash中的基地址是0x900000001fc01048

typedef struct {

  EFI_GUID  Signature;

  UINT32  Size;

  UINT8   Format;

  UINT8   State;

  UINT16  Reserved;

  UINT32  Reserved1;

} VARIABLE_STORE_HEADER; //28个字节

后面紧跟着启动过程中的相关变量头数据。结构如下:

typedef struct {

  UINT16      StartId; //0x55aa标识有效

  UINT8       State;

  UINT8       Reserved;

  UINT32      Attributes;

  UINT32      NameSize;

  UINT32      DataSize;

  EFI_GUID    VendorGuid;

} VARIABLE_HEADER; //32个字节

然后是variable名称+数据。

当安全启动启动后

在flash中的基地址还是0x900000001fc01048,范围为0x60000。

typedef struct {

  EFI_GUID  Signature;

  UINT32  Size;

  UINT8   Format;

  UINT8   State;

  UINT16  Reserved;

  UINT32  Reserved1;

} VARIABLE_STORE_HEADER; //28个字节

后面紧跟着启动过程中的安全启动变量头数据。结构如下:

typedef struct {

  UINT16      StartId;

  UINT8       State;

  UINT8       Reserved;

  UINT32      Attributes;

  UINT64      MonotonicCount;

  EFI_TIME    TimeStamp;

  UINT32      PubKeyIndex;

  UINT32      NameSize;

  UINT32      DataSize;

  EFI_GUID    VendorGuid;

} AUTHENTICATED_VARIABLE_HEADER;

然后是variable名称+数据。

(2)variable系统中的接口

1)VariableServiceGetVariable调用FindVolatileVariable和FindNonVolatileVariable接口来从flash或者memory中获取变量,获取过程就是解析以上数据结构来得到变量以及数据。后续系统通过gRT->GetVariable来调用。

2) VariableServiceSetVariable将系统中变量保存到memory或flash中,memory可直接进行简单的memcpy即可保存,需要保存到flash中的变量则必须要调用Flash的驱动函数去写才能写入。后续系统通过gST->SetVariable()来更新variable。

 

3) VariableServiceSetVariable函数调用UpdateVariable来实现将变量写到flash或内存中。这个接口分为两种情况,在写变量前首先会调用FindVolatileVariable和FindNonVolatileVariable接口来查找变量,如找到,则更新变量。如未找到,则创建一个新的变量。更新flash中变量分为四步,首先创建Variable的头;第二步更新头的中的State位,让这位变成有效;第三步写Variable的数据;最后再次更新head中的State位为VAR_ADDED。

4)Reclaim接口,由于更新变量时是将Variable头中State位改变(比如删除则置为对应状态),在系统中多次重启后,flash可能被写满,这个时候需要Reclaim接口来重新组织flash中数据。Reclaim接口根据Variable头中State位状态来删除或保留vaiable数据。

5)AuthVariableLibProcessVariable接口是当开启安全启动后,更新variable变量调用的接口

1> 在custom模式下,获取密钥数据(分离数据与时间戳),检测密钥文件的格式是否为PK/KEK/db/dbx格式的变量。通过验证则调用UpdateVariable来添加密钥变量。这个模式下可以任意更新PK、KEK、DB以及DBX,不需要经过安全验证这个验证。

2> 在用户模式下,添加KEK需要经过PK的验证,添加DB/DBX需要经过KEK或者PK的验证。这样一级一级验证,防止恶意添加自己的密钥来通过安全启动的验证过程。

6)时间戳及PKI

私钥不会被别人获取或破解,那么如何传递公钥呢?也就是说,依赖方(需要验证数字签名的B)如何才能确定这个公钥A,确实就是真正的公钥,而不是伪造的呢?为了解决这一问题,引入了公钥基础设施这一概念(Public-Key Infrastructures ,简称PKI)。

在更高的层次上,引入了一个具有极高权威的第三方C,这个第三方在公钥上附加数字签名,以证明该公钥来自于私钥的创建者A创建的公钥。可以将这类数字签名理解为“荣誉证书”,“证书”的颁发者被称为证书授权机构(Certificate Authority,简称CA)。需要数字签名的B会使用ca给的公钥解密证书,获取A的公钥,保证公钥的正确性。

通常这些特定的CA是由更高级CA进行签名认证过的。通过这样的职能划分,不但大大减小了分发顶级CA(通常称为信任锚或根CA)的数量,安全性也不减反增。顶级CA对所需的证书进行自签名,也就是说他们签署了用来自证的证书(他们自己证明自己是自己)。从技术方面来说这是不需要的 ,之所以这么做是因为依赖方(依赖于证书的这一方)需要知道这个特定的根CA确实拥有所需的公钥,因此为了方便起见(证书的一致使用),常常使用自签名证书。

由CA颁发的证书是可以被其撤销的。撤销实际上是由CA作出的声明,表明用来产生对应公钥的某个实体(称之为A)的私钥因产生了安全问题而不再受信任:这可能是由于A丢失了他的私钥或是因为它被怀疑已经遭受了入侵或是其他的一些原因。一旦证书被撤销,它就不能再用于验证由相应的私钥做出的签名。正因如此,在数字签名上加盖时间戳就显得很必要:依赖方可以通过时间戳的信息来确定签名是否创建在证书被撤销之前。(由签名时间戳和申明撤销的时间比较,确认签名的有效性)

 

签名过程

1、生成证书

需要三个证书: PK、KEK、DB

1)生成一些自签名的X.509证书:

umask 0077

openssl req -new -x509 -newkey rsa:2048 -subj "/CN=my PK name/" -keyout PK.key -out PK.crt -days 3650 -nodes -sha256

openssl req -new -x509 -newkey rsa:2048 -subj "/CN=my KEK name/" -keyout KEK.key -out KEK.crt -days 3650 -nodes -sha256

openssl req -new -x509 -newkey rsa:2048 -subj "/CN=my db name/" -keyout db.key -out db.crt -days 3650 -nodes -sha256

2)转换格式

cert-to-efi-sig-list /tmp/CA/PK.crt /tmp/CA/PK.esl

cert-to-efi-sig-list /tmp/CA/KEK.crt /tmp/CA/KEK.esl

cert-to-efi-sig-list /tmp/CA/boot0.crt /tmp/CA/db.esl

建立信任链

PK将自签名,KEK将由PK签名,引导证书将由KEK签名

sign-efi-sig-list -k PK.key -c PK.crt PK /tmp/CA/PK.esl /tmp/PK.auth

sign-efi-sig-list -k PK.key -c PK.crt KEK /tmp/CA/KEK.esl /tmp/CA/KEK.auth

guid=$(uuidgen)

sign-efi-sig-list -k KEK.key -c KEK.crt $guid /tmp/CA/db.esl /tmp/CA/db.auth

./cert-to-efi-sig-list dbx.esl

2、签名二进制文件

安装了自定义UEFI签名密钥后,需要对自己的EFI二进制文件进行签名。Linux内核本身可以从3.3版开始构建为可引导的EFI二进制文件。可以签署和引导任何有效的EFI二进制文件。

一种选择是直接对内核映像签名。如果的发行版使用的是二进制内核,那么需要在重新启动系统之前对每个新的内核更新进行签名。如果使用自编译内核,则需要在构建每个内核之后对其进行签名。

Linux sbsigntools包可以从大多数Linux发行版的存储库中获得,是对UEFI二进制文件进行签名时的首选工具。UEFI安全启动二进制文件应该使用X509格式签名。命令是sbsign,它被调用如下:

sbsign --key DB.key --cert DB.crt unsigned.efi --output signed.efi

由于在MIPS下这个工具还没有支持,可以使用osslsigncode工具,它也生成签名。此工具不是专门用于安全引导,但它生成的签名符合所需的规范。使用osslsigntool对二进制文件进行签名的方式与sbsign类似:

osslsigncode -certs DB.crt -key DB.key -h sha256 -in unsigned.efi -out signed.efi

 

3、验证过程

在DxeImageVerificationHandler函数中,将预加载的efi文件进行安全验证。

1、首先获取image类型,目前实验显示UEFI内部efi imgae类型为IMAGE_FROM_FV,这类image在安全引导过程属于不验证文件,即直接加载。而其他类型的image文件则需要通过验证才可以加载。比如BOOTMIPS.EFI文件。然后从flash或者内存中获取SecureBoot变量,如获取到,判断SecureBoot是否为使能状态。禁止状态直接返回,如使能,验证DOS头,获取文件信息,判断是否为有效的PeImage文件。

2、检测如果efi文件没有签名信息,则计算image的hash值,查看hash值如果在DB数据库中且不在DBX中,则通过验证,否则验证失败。

3、检测如果efi文件包含签名信息,则遍历db数据库中签名证书,检测证书类型(如果为hash值,则跳过)。检测efi文件的签名信息不被dbx禁止且可以通过db签名验证。则验证通过,否则,继续检测efi文件(可执行image以及签名信息)的hash值不在dbx且在db中存在。则也可以通过验证。如以上检验全不满足,说明签名信息不符合。验证失败。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3