概要
这一篇文章主要详述整个安全启动的信任根和信任链。信任根,顾名思义系统的信任根本,这是信任的锚定物,当A被信任根验证通过,A再去对B进行验证,这个过程形成的链路被称为信任链。类似于,你去银行贷款,银行要查看你的工作或者工资流水等等,这里的信任根是你的工作或者工资流水,并不是你本人,当你拿到贷款买房后,这个时候房产证成为新的信用凭证,可以继续抵押办理贷款去买车,由此产生了信用链。
信任根
当前安全启动流程的信任根为一级boot,也就是整个系统信任的起源是一级boot,那么凭什么一级boot可以成为信任根呢?首先,通过硬件机制保证一级boot不能够被篡改,将一级boot所在的flash区域被设置为写保护;其次,在硬件机制保证一级boot不能被修改的前提下,一级boot中固化的公钥是整个信任链的关键,该公钥将会用于验证二级boot,并将信任传递下去。一级boot对二级boot的验证过程为:一级boot读取二级boot代码所在区域的数据,计算其hash值,并用一级boot中的公钥对计算出的hash值进行验签,验签通过后,二级boot的身份就得到了信任。
信任的传递形成信任链
二级boot验证
由于二级boot的身份得到确认,所以二级boot是一个可信任的程序,二级boot中固化的公钥将会成为新的信任凭证,那为什么可以信任该公钥呢?因为二级boot具有这样一个结构体
typedef struct {
uint32_t magic;
uint32_t firmware_len;
uint8_t signature64;
uint8_t sha256_hash32;
} fw_header_t;
该区域在离线签名时会被填充,其中的signature64,就是用一级boot中公钥对应的私钥对二级boot的hash经过计算得到的签名值。如果二级boot被篡改过(比如二级boot中公钥被篡改),由于篡改者拿不到私钥,所以在一级boot验证二级boot签名时,一定不能验证通过。所以此时信任根具有的信任就被传递到了二级boot的公钥,此时二级boot就可以拿着该公钥去验证APP的有效性。因此,验签通过的二级boot就是该信任链的一个新的信任锚定物。
APP验证
同样的APP中也有一个签名区域,结构体定义如下:
typedef struct {
uint32_t magic;
uint32_t length;
uint8_t hash32;
uint32_t version ;
uint8_t reserved20;
} FlashConfig_t;
注意当前这个区域不是由离线签名程序进行填充的,而是在刷写完毕并校验通过后,由二级boot来填充的。整个过程是这样:刷写完毕后,接收到上位机发送来的app的hash值和签名,然后二级boot先计算当前APP代码的hash值,与接收到hash值进行比较,hash值验证通过后,再将该hash值和接收到的签名,与信任的公钥进行验签。验签通过后,APP就是被信任的程序,然后再更新签名区域FlashConfig_t的值。这样做其实会有漏洞 :因为签名值,是上位机在刷写时在线计算的,所以这个时候私钥必然会存储在上位机程序中,这对私钥的保密性造成了严重问题。所以后续可以改成和二级boot一样,进行离线签名。另外,如果没有刷写请求,二级boot会直接检查app的有效性,但是有效性检查只做了hash的检查,而没有做签名的验证,这里也存在一个漏洞。
这里再提一个针对刷写流程的建议:当前刷写程序是在刷写请求之后就开始擦除原来的程序,然后下载新程序,这里如果攻击者发送一个请求,由于不会进行身份验证,那么即使最后攻击者的程序不能够正常运行起来,但是原有的程序也就被擦掉了,导致整个系统不能正常运行。所以,解决方案为:APP先离线签名,在刷写程序正式开始之前,上位机先把APP的签名信息发过来,这个时候先用二级boot中信任的公钥验签,没有问题后,在进行刷写,这样就可以解决身份验证的问题。