之前的文章XXX中介绍了链式镜像加载,验签的基础知识和一些代码数据结构,并且修改代码打开了镜像验签功能。本小节就来实操一下,还是那句:老铁,你的qemu运行ATF代码环境搭建好了?参考:XXX
什么是真正的技术?必须深挖代码,并且给你一份代码,一顿分析,很快就能掌握的能力,就是内功。真实可靠,想改哪里改哪里,完全拿捏。本文不讲套路,show me the code。
1. 整体流程
ATF的安全启动按照TBBR规范要求进行实现,为了防止恶意固件在平台上运行,受信任的板级引导 (TBB) 功能要求验证所有固件镜像(包括普通世界的引导加载程序),这通过使用公钥密码标准 (PKCS) 建立信任链来做到这一点。上图中就是代码中doc文件下的分类介绍:
- GEN和IO:TF-A通用代码和IO框架,负责BL1或者BL2中的镜像启动身份验证过程
- PP:TF-A平台接口,指定信任链,提供信任根ROTPK等
- CM:密码模块,验证数字签名和哈希
- AM:认证模块,描述信任链,跟踪验证的镜像等
- IPM:镜像解析器模块,获取用于镜像验证的参数
- CL:密码算法库,提供哈希、验签和加密算法等实现
- IPL:镜像解析器库,解析镜像参数,如从x509v3证书中提取参数
开源软件的一手资料doc文档
开源软件一般都会有doc目录下的rst文件,不过都是英文的
信任链CoT是一系列经过认证的镜像,信任从信任根开始往后逐级传递。信任根通常由两个组件构成:信任根公钥和BootROM固件。信任根公钥用于验证可信启动证书和可信密钥证书,而BootROM代码确保镜像验证流程必须执行。
信任链的其他组件还包括符合X.509 v3标准的证书,证书的扩展中存储了建立CoT需要的参数。证书是自签名的,因此不需要CA进行验证,这是因为CoT的建立不需要验证颁发者的有效性,只是请求证书扩展中的内容。换句话说就是证书链的安全性是由硬件保证,需要依赖外部CA:BootROM保证证书链必须经过验证,证书链的源头是由信任根公钥保证。
2. BL2加载验签初始化流程
参考BL1中代码分析XXX:
scss
void bl1_main(void)
{
/* Announce our arrival */
NOTICE(FIRMWARE_WELCOME_STR);
NOTICE("hello BL1: %s\n", version_string);
NOTICE("BL1: %s\n", build_message);
INFO("BL1: RAM %p - %p\n", (void *)BL1_RAM_BASE, (void *)BL1_RAM_LIMIT);
bl1_arch_setup(); //设置下一个image的EL级别为aarch64
crypto_mod_init(); //初始化加密库
auth_mod_init(); //初始化安全模块和镜像解析模块
bl1_plat_mboot_init();
bl1_platform_setup();
image_id = bl1_plat_get_next_image_id(); //获取下一级启动镜像的ID。
if (image_id == BL2_IMAGE_ID)
bl1_load_bl2(); //将BL2镜像加载到SRAM中
else
NOTICE("BL1-FWU: *******FWU Process Started*******\n");
bl1_plat_mboot_finish();
bl1_prepare_next_image(image_id); //获取bl2 image的描述信息,包括名字,ID,entry potin info等,并将这些信息保存到bl1_cpu_context的上下文中,为进入下一级镜像执行准备好上下文。
console_flush(); //在退出bl1之前将串口中的数据全部刷新掉
}
我们需要关注:crypto_mod_init、auth_mod_init、bl1_load_bl2三个函数
2.1 加密库初始化
crypto_mod_init()在drivers/auth/crypto_mod.c中定义
csharp
void crypto_mod_init(void)
{
/* Initialize the cryptographic library */
crypto_lib_desc.init();
INFO("Using crypto library '%s'\n", crypto_lib_desc.name);
}
crypto_lib_desc对应drivers/auth/mbedtls/mbedtls_crypto.c如下注册
ini
#define LIB_NAME "mbed TLS"
REGISTER_CRYPTO_LIB(LIB_NAME, init, verify_signature, verify_hash, NULL,
NULL, NULL);
/* Macro to register a cryptographic library */
#define REGISTER_CRYPTO_LIB(_name, _init, _verify_signature, _verify_hash, \
_calc_hash, _auth_decrypt, _convert_pk) \
const crypto_lib_desc_t crypto_lib_desc = { \
.name = _name, \
.init = _init, \
.verify_signature = _verify_signature, \ //签名验证
.verify_hash = _verify_hash, \ //hash验证
.calc_hash = _calc_hash, \
.auth_decrypt = _auth_decrypt, \
.convert_pk = _convert_pk \
}
init()函数会执行
scss
static void init(void)
{
/* Initialize mbed TLS */
mbedtls_init();
}
void mbedtls_init(void)
{
static int ready;
void *heap_addr;
size_t heap_size = 0;
int err;
if (!ready) {
if (atexit(cleanup))
panic();
err = plat_get_mbedtls_heap(&heap_addr, &heap_size);
/* Ensure heap setup is proper */
if (err < 0) {
ERROR("Mbed TLS failed to get a heap\n");
panic();
}
assert(heap_size >= TF_MBEDTLS_HEAP_SIZE);
/* Initialize the mbed TLS heap */
mbedtls_memory_buffer_alloc_init(heap_addr, heap_size);
#ifdef MBEDTLS_PLATFORM_SNPRINTF_ALT
mbedtls_platform_set_snprintf(snprintf);
#endif
ready = 1;
}
}
2.2 IPL镜像解析库初始化
auth_mod_init();--》img_parser_init()
scss
void img_parser_init(void)
{
parser_lib_descs = (img_parser_lib_desc_t *) PARSER_LIB_DESCS_START;
for (index = 0; index < mod_num; index++) {
/* Check that the image parser library descriptor is valid */
validate_desc(&parser_lib_descs[index]);
/* Initialize image parser */
parser_lib_descs[index].init();
/* Keep the index of this hash calculator */
parser_lib_indices[parser_lib_descs[index].img_type] = index;
}
}
parser_lib_descs的定义,drivers/auth/mbedtls/mbedtls_x509_parser.c中
ini
REGISTER_IMG_PARSER_LIB(IMG_CERT, LIB_NAME, init,
check_integrity, get_auth_param);
/* Macro to register an image parser library */
#define REGISTER_IMG_PARSER_LIB(_type, _name, _init, _check_int, _get_param) \
static const img_parser_lib_desc_t __img_parser_lib_desc_##_type \
__section(".img_parser_lib_descs") __used = { \
.img_type = _type, \
.name = _name, \
.init = _init, \
.check_integrity = _check_int, \ //镜像完整性检查
.get_auth_param = _get_param \ //提取镜像验证函数的指针
}
init函数
javascript
static void init(void)
{
mbedtls_init();
}
可见也是使用的mbedtls库。这个之前加密库初始化的时候已经初始化过了。
2.3 bl1_load_bl2
参考XXX中fip加载流程如下图:
但是我们打开镜像验签后,有些函数会多执行,我们重点介绍验签流程,下面再画一个图:
跟之前的框架图对比下,就能对上了:
另外起一章,详细介绍下加密校验的流程
3. 加密校验流程
这里还是用BL1加载BL2进行说明,其他的类似。
3.1 使用加密校验加载
首先就是加载流程的变化:bl1_load_bl2--》load_auth_image_internal--》load_auth_image_recursive
arduino
static int load_auth_image_internal(unsigned int image_id,
image_info_t *image_data)
{
#if TRUSTED_BOARD_BOOT
if (dyn_is_auth_disabled() == 0) {
return load_auth_image_recursive(image_id, image_data, 0);//加密校验加载
}
#endif
return load_image(image_id, image_data);//直接加载
}
load_auth_image_recursive,首先找parent,判断parent是否进行了验证,如果没验证则先验证parent,直到找到根,这样就是可信的
ini
static int load_auth_image_recursive(unsigned int image_id,
image_info_t *image_data,
int is_parent_image)
{
rc = auth_mod_get_parent_id(image_id, &parent_id);
if (rc == 0) {
rc = load_auth_image_recursive(parent_id, image_data, 1);
}
rc = load_image(image_id, image_data);
rc = auth_mod_verify_img(image_id,
(void *)image_data->image_base,
image_data->image_size);
}
3.2 获取parent
BL2的parent是TRUSTED_BOOT_FW_CERT_ID,因为BL2的cot定义如下:
ini
static const auth_img_desc_t bl2_image = {
.img_id = BL2_IMAGE_ID,
.img_type = IMG_RAW,
.parent = &trusted_boot_fw_cert,
下面我们看下代码怎么跟cot的定义联系上的,首先分析auth_mod_get_parent_id
ini
int auth_mod_get_parent_id(unsigned int img_id, unsigned int *parent_id)
{
img_desc = FCONF_GET_PROPERTY(tbbr, cot, img_id);
/* Check if the image has no parent (ROT) */
if (img_desc->parent == NULL) {
*parent_id = 0;
return 1;
}
/* Check if the parent has already been authenticated */
if (auth_img_flags[img_desc->parent->img_id] & IMG_FLAG_AUTHENTICATED) {
*parent_id = 0;
return 1;
}
*parent_id = img_desc->parent->img_id;
return 0;
}
#define FCONF_GET_PROPERTY(a, b, c) a##__##b##_getter(c)
FCONF_GET_PROPERTY(tbbr, cot, img_id)就是tbbr__cot_getter(1)-->cot_desc_ptr-->REGISTER_COT-->cot_desc[],代码倒推过程如下:
ini
#define tbbr__cot_getter(id) __extension__ ({ \
assert((id) < cot_desc_size); \
cot_desc_ptr[id]; \
})
#define REGISTER_COT(_cot) \
const auth_img_desc_t *const *const cot_desc_ptr = (_cot); \
const size_t cot_desc_size = ARRAY_SIZE(_cot); \
unsigned int auth_img_flags[MAX_NUMBER_IDS]
REGISTER_COT(cot_desc);
static const auth_img_desc_t * const cot_desc[] = {
[TRUSTED_BOOT_FW_CERT_ID] = &trusted_boot_fw_cert,
[BL2_IMAGE_ID] = &bl2_image,
[HW_CONFIG_ID] = &hw_config,
[TB_FW_CONFIG_ID] = &tb_fw_config,
[FW_CONFIG_ID] = &fw_config,
[FWU_CERT_ID] = &fwu_cert,
[SCP_BL2U_IMAGE_ID] = &scp_bl2u_image,
[BL2U_IMAGE_ID] = &bl2u_image,
[NS_BL2U_IMAGE_ID] = &ns_bl2u_image
};
拿到bl2的parent后,就会继续递归调用load_auth_image_recursive,再去找trusted_boot_fw_cert的parent,这时为NULL,那就继续执行load_image,这时image的id就变成6了。
load_image()在之前的文章XXX里面有描述,这里不详述了。当把trusted_boot_fw_cert load到内存里面后,就要进行auth_mod_verify_img()校验了。
如果校验失败,程序会停止往下执行,并清空数据,让系统启动不起来:
rust
rc = auth_mod_verify_img(image_id,
(void *)image_data->image_base,
image_data->image_size);
if (rc != 0) {
/* Authentication error, zero memory and flush it right away. */
zero_normalmem((void *)image_data->image_base,
image_data->image_size);
flush_dcache_range(image_data->image_base,
image_data->image_size);
return -EAUTH;
}
3.3 AM校验
auth_mod_verify_img()整个校验过程都在这个函数里面,定义如下:
ini
int auth_mod_verify_img(unsigned int img_id,
void *img_ptr,
unsigned int img_len)
{
/* Get the image descriptor from the chain of trust */
img_desc = FCONF_GET_PROPERTY(tbbr, cot, img_id);
/* Ask the parser to check the image integrity *///完整性检查
rc = img_parser_check_integrity(img_desc->img_type, img_ptr, img_len);
/* Authenticate the image using the methods indicated in the image
* descriptor. *///验证本镜像
for (i = 0 ; i < AUTH_METHOD_NUM ; i++) {
auth_method = &img_desc->img_auth_methods[i];
switch (auth_method->type) {
case AUTH_METHOD_NONE:
rc = 0;
break;
case AUTH_METHOD_HASH://hash验证
rc = auth_hash(&auth_method->param.hash,
img_desc, img_ptr, img_len);
case AUTH_METHOD_SIG://签名验证
rc = auth_signature(&auth_method->param.sig,
img_desc, img_ptr, img_len);
case AUTH_METHOD_NV_CTR://版本号增加验证
nv_ctr_param = &auth_method->param.nv_ctr;
rc = auth_nvctr(nv_ctr_param,
img_desc, img_ptr, img_len,
&cert_nv_ctr, &need_nv_ctr_upgrade);
}
/* Extract the parameters indicated in the image descriptor to
* authenticate the children images. *///验证子镜像用到的数据提取
if (img_desc->authenticated_data != NULL) {
for (i = 0 ; i < COT_MAX_VERIFIED_PARAMS ; i++) {
/* Get the parameter from the image parser module */
rc = img_parser_get_auth_param(img_desc->img_type,
img_desc->authenticated_data[i].type_desc,
img_ptr, img_len, ¶m_ptr, ¶m_len);
/* Copy the parameter for later use */
memcpy((void *)img_desc->authenticated_data[i].data.ptr,
(void *)param_ptr, param_len);
type_desc = img_desc->authenticated_data[i].type_desc;
if (type_desc->type == AUTH_PARAM_PUB_KEY) {
rc = plat_mboot_measure_key(type_desc->cookie,
param_ptr,
param_len);
}
}
}
}
/* Mark image as authenticated */
auth_img_flags[img_desc->img_id] |= IMG_FLAG_AUTHENTICATED;
return 0;
}
由三部分组成
- 根据本镜像格式,验证本镜像完整性--IPM
- 验证本镜像签名--CM
- 提取子镜像验证需要的信息--IPM
3.4 验证镜像完整性
img_parser_check_integrity:检查镜像完整性,这主要是针对证书镜像,需要解析证书,检查是否符合ASN.1结构,并提取相关的数据用于后续认证操作,例如签名算法,公钥信息,扩展信息,签名值等。
IMG_RAW不验证。其他格式需要调用check_integrity()进行验证
arduino
int img_parser_check_integrity(img_type_t img_type,
void *img_ptr, unsigned int img_len)
{
/* No integrity checks on raw images */
if (img_type == IMG_RAW) {
return IMG_PARSER_OK;
}
/* Find the index of the required image parser */
idx = parser_lib_indices[img_type];
assert(idx != INVALID_IDX);
/* Call the function to check the image integrity */
return parser_lib_descs[idx].check_integrity(img_ptr, img_len);
}
根镜像trusted_boot_fw_cert的格式是IMG_CERT,根据parser_lib_indices找到idx是0,这里其实只有一个mbedtls库进行了注册,drivers/auth/mbedtls/mbedtls_x509_parser.c中:
scss
REGISTER_IMG_PARSER_LIB(IMG_CERT, LIB_NAME, init,
check_integrity, get_auth_param);
parser_lib_indices[IMG_CERT]的值在img_parser_init中定义,mod_num值是1,只有一个mbedtls库注册了
ini
for (index = 0; index < mod_num; index++) {
parser_lib_indices[parser_lib_descs[index].img_type] = index;
check_integrity就是x509证书内容的解析了。
3.5 镜像校验
3.5.1 trusted_boot_fw_cert校验auth_signature
校验要用到COT结构体auth_img_desc_t,trusted_boot_fw_cert如下:
- img_id:定义可信启动证书唯一标识符
- img_type:IMG_CERT表示镜像类型是证书
- parent:NULL表示该镜像没有父镜像,需要使用信任根进行认证
- img_auth_methods:镜像认证的方法包括认证签名和NV计数器,用于验签的公钥来自主公钥subject_pk,即信任根公钥ROTPK,而NV计数器的验证是用证书中解析的nv_ctr与平台定义的nv_ctr进行比较,rotpk和plat_nv_ctr通常存储在OTP中
- authenticated_data:定义已经认证过的数据,这些参数通常用于认证子镜像,这里的tb_fw_hash即下面待比较的bl2镜像哈希
可信密钥证书trusted_key_cert定义如下,同BL1可信启动证书一样,认证方法为验签和NV计数器比较,由于没有父镜像,因此该证书验签也由信任根公钥ROTPK进行验证。证书认证通过后,可以从中提取安全和非安全世界公钥trusted_world_pk和non_trusted_world_pk,用于验证后续镜像的密钥证书。
对于trusted_boot_fw_cert使用AUTH_METHOD_SIG 和AUTH_METHOD_NV_CTR放回滚校验
对于使用X509证书方式的镜像解析方式而言,auth_signature函数只会针对于镜像类型为证书IMG_CERT。
认证签名是公钥验证镜像的签名值,需要以下参数:
- 待认证的镜像:被签名的数据,签名值,签名算法
- 父镜像:公钥(或公钥哈希) 如果父镜像只包含公钥的哈希,则需要从待认证的镜像中提取公钥值(即自签名的证书),并计算该公钥的哈希值,与父镜像中获取的哈希值进行比较。如果镜像没有父镜像,则需要用OTP中存储的ROTPK进行验签。
scss
static int auth_signature(const auth_method_param_sig_t *param,
const auth_img_desc_t *img_desc,
void *img, unsigned int img_len)
{
void *data_ptr, *pk_ptr, *cnv_pk_ptr, *pk_plat_ptr, *sig_ptr, *sig_alg_ptr, *pk_oid;
unsigned int data_len, pk_len, cnv_pk_len, pk_plat_len, sig_len, sig_alg_len;
unsigned int flags = 0;
int rc;
/* Get the data to be signed from current image */
rc = img_parser_get_auth_param(img_desc->img_type, param->data,
img, img_len, &data_ptr, &data_len);
/* Get the signature from current image */
rc = img_parser_get_auth_param(img_desc->img_type, param->sig,
img, img_len, &sig_ptr, &sig_len);
/* Get the signature algorithm from current image */
rc = img_parser_get_auth_param(img_desc->img_type, param->alg,
img, img_len, &sig_alg_ptr, &sig_alg_len);
//从镜像中获取了签名sig_ptr、签名算法sig_alg_ptr、
/* Get the public key from the parent. If there is no parent (NULL),
* the certificate has been signed with the ROTPK, so we have to get
* the PK from the platform */
if (img_desc->parent != NULL) {
rc = auth_get_param(param->pk, img_desc->parent,
&pk_ptr, &pk_len);
//从CoT父关系img_desc->parent中获取,公钥pk_ptr
} else {
/*
* Root certificates are signed with the ROTPK, so we have to
* get it from the platform.
*/
rc = plat_get_rotpk_info(param->pk->cookie, &pk_plat_ptr,
&pk_plat_len, &flags);
/* Also retrieve the key from the image. */
rc = img_parser_get_auth_param(img_desc->img_type,
param->pk, img, img_len,
&pk_ptr, &pk_len);
//公钥从ROTPK获取,因为是根签名,父亲是null
/*
* Validate the certificate's key against the platform ROTPK.
*
* Platform may store key in one of the following way -
* 1. Hash of ROTPK
* 2. Hash if prefixed, suffixed or modified ROTPK
* 3. Full ROTPK
*/
if ((flags & ROTPK_NOT_DEPLOYED) != 0U) {
NOTICE("ROTPK is not deployed on platform. "
"Skipping ROTPK verification.\n");
//如果公钥没有部署,就跳过对公钥的验证操作,这一般用用调试场景,如果公钥已经部署并且是存储的哈希值,则需要对公钥进行哈希比较操作crypto_mod_verify_hash,以此来判断公钥是否可信
} else if ((flags & ROTPK_IS_HASH) != 0U) {
/*
* platform may store the hash of a prefixed,
* suffixed or modified pk
*/
rc = crypto_mod_convert_pk(pk_ptr, pk_len, &cnv_pk_ptr, &cnv_pk_len);
//如果公钥为哈希,需要先从镜像中提取公钥值,然后调用密码模块crypto_mod_verify_signature进行验签操作,即调用底层的verify_signature函数,如果公钥就是实际的值,则直接进行验签操作
/*
* The hash of the certificate's public key must match
* the hash of the ROTPK.
*/
rc = crypto_mod_verify_hash(cnv_pk_ptr, cnv_pk_len,
pk_plat_ptr, pk_plat_len);
} else {
/* Platform supports full ROTPK */
if ((pk_len != pk_plat_len) ||
(memcmp(pk_plat_ptr, pk_ptr, pk_len) != 0)) {
ERROR("plat and cert ROTPK len mismatch\n");
return -1;
}
}
/*
* Public key is verified at this stage, notify platform
* to measure and publish it.
*/
rc = plat_mboot_measure_key(pk_oid, pk_ptr, pk_len);
}
/* Ask the crypto module to verify the signature */
rc = crypto_mod_verify_signature(data_ptr, data_len,
sig_ptr, sig_len,
sig_alg_ptr, sig_alg_len,
pk_ptr, pk_len);
return 0;
}
- data_ptr, data_len: 签名数据
- sig_ptr, sig_len: 数字签名
- sig_alg_ptr, sig_alg_len: 数字签名算法
- pk_ptr, pk_len: 公钥
数字签名算法利用公钥对签名数据进行解密,然后跟数字签名对比,是否一致。crypto_mod_verify_signature()函数的深度分析,应该挺有意思,但是也很耗费时间,专业选手肯定是要对代码全部掌握的,加解密的库什么的,作者这里没有太多的时间,这里仅仅是入门,知道原理就可以,就写到这里了。
对于trusted_boot_fw_cert根签名镜像,主要区别就是获取公钥过程复杂,如下:
- img_parser_get_auth_param:获取被签名的数据,签名值以及签名算法
- img_desc->parent:判断镜像是否有父镜像,并从父镜像已经认证过的数据中获取公钥,如果没有父镜像,说明证书使用ROTPK进行签名的,需要从平台如OTP中获取公钥
- ROTPK_IS_HASH:如果公钥为哈希,需要先从镜像中提取公钥值,然后调用密码模块crypto_mod_verify_signature进行验签操作,即调用底层的verify_signature函数,如果公钥就是实际的值,则直接进行验签操作
- ROTPK_NOT_DEPLOYED:如果公钥没有部署,就跳过对公钥的验证操作,这一般用用调试场景,如果公钥已经部署并且是存储的哈希值,则需要对公钥进行哈希比较操作crypto_mod_verify_hash,以此来判断公钥是否可信
3.5.2 trusted_boot_fw_cert校验auth_nvctr
认证NV计数器:
认证NV计数器是系统防回滚设计,这个计数器只能递增并且存储在一次性存储器如OTP中,所有证书中的计数器必须比平台存储的大,即不能升级版本低的固件(防回滚)。auth_nvctr函数主要功能如下:
- img_parser_get_auth_param:获取当前镜像的NV计数器,即从证书中提取DER编码的计数器值,然后进行解析,转换成整型值
- plat_get_nv_ctr:从平台中获取当前的NV计数器值,比与上面计数器值进行比较,如果比他大,则认证通过,返回NV计数器需要更新need_nv_ctr_upgrade
3.5.2 bl2_image校验auth_hash
对于bl2_image使用的hash校验:
- img_id:定义bl2镜像唯一标识符
- img_type:IMG_RAW表示镜像类型是原始镜像数据
- parent:表示bl2镜像的父镜像是可选启动证书trusted_boot_fw_cert,即认证bl2镜像首先需要认证其父镜像trusted_boot_fw_cert
- img_auth_methods:bl2镜像认证的方法为哈希比较,比较的可信哈希是从trusted_boot_fw_cert可信启动证书中提取到的
认证哈希是通过比较哈希值是否一致,一个是计算镜像得到的哈希值,一个是从父镜像提取的可信哈希值(包括哈希算法)。auth_hash函数中的主要流程:
- auth_get_param:从父镜像中获取DER编码的哈希值及哈希算法
- img_parser_get_auth_param:获取当前镜像需要被哈希的数据,对于IMG_RAW类型就是镜像本身
- crypto_mod_verify_hash:请求密码模块验证哈希,会调用底层密码模块verify_hash函数
arduino
static int auth_hash(const auth_method_param_hash_t *param,
const auth_img_desc_t *img_desc,
void *img, unsigned int img_len)
{
/* Get the hash from the parent image. This hash will be DER encoded
* and contain the hash algorithm */
rc = auth_get_param(param->hash, img_desc->parent,
&hash_der_ptr, &hash_der_len);
/* Get the data to be hashed from the current image */
rc = img_parser_get_auth_param(img_desc->img_type, param->data,
img, img_len, &data_ptr, &data_len);
//从父镜像获取hash_der_ptr哈希值和hash算法、数据data_ptr
/* Ask the crypto module to verify this hash */
rc = crypto_mod_verify_hash(data_ptr, data_len,
hash_der_ptr, hash_der_len);
return 0;
}
crypto_mod_verify_hash()函数,利用hash_der_ptr中hash算法计算数据data_ptr的hash值,然后跟hash_der_ptr中hash值对比。
3.6 子镜像校验信息获取
从父镜像提取的子镜像验证参数,需要单独存储 ,因为当加载子镜像的时候会冲掉父镜像占用的内存,需要使用auth_param_data_desc_t中ptr指针存储。
arduino
/*
* Store a pointer to the authentication parameter and its length
*/
typedef struct auth_param_data_desc_s {
void *ptr;
unsigned int len;
} auth_param_data_desc_t;
/*
* Authentication parameter descriptor, including type and value
*/
typedef struct auth_param_desc_s {
auth_param_type_desc_t *type_desc;
auth_param_data_desc_t data;
} auth_param_desc_t;
IPM 获取镜像验证参数的时候填充ptr和len。auth_param_desc_t中保护了这个data。
3.7 mbedtls解码库
这个代码目录没在trusted-firmware-a里面,而是根目录下mbedtls文件夹,但是编译的时候跟随ATF一起进行编译的。
上面认证流程用到的哈希、验签和解密都是基于底层的密码算法库。ATF默认使用的是mbedtls密码算法库。在底层的密码模块中,注册了验签、哈希验证以及认证解密函数。
scss
REGISTER_CRYPTO_LIB(LIB_NAME, init, verify_signature, verify_hash,
auth_decrypt);
另外对于镜像解析模块,也注册了镜像完整性校验和认证参数解析函数。认证需要的参数如镜像哈希值、公钥等都是存储在X509证书中,在使用这些参数之前,需要从证书中解析出来(ASN.1编码的结构)。
scss
REGISTER_IMG_PARSER_LIB(IMG_CERT, LIB_NAME, init, \
check_integrity, get_auth_param);
3.8 其他信任链
BL31的信任链:
BL31的信任链CoT定义如下,包括密钥证书soc_fw_key_cert、内容证书soc_fw_content_cert和镜像bl31_image以及配置soc_fw_config
- 密钥证书soc_fw_key_cert:父镜像是可信密钥证书trusted_key_cert,认证方法包括验签和NV计数器,验签的公钥是安全世界公钥trusted_world_pk,即trusted_key_cert认证过的密钥数据;NV计数器是比较安全固件的计数器trusted_nv_ctr。认证通过后可以提取验证内容证书所用的公钥soc_fw_content_pk
- 内容证书soc_fw_content_cert:父镜像是上面的密钥证书,认证方法同样是验签和NV计数器,验签公钥是上面的soc_fw_content_pk,认证通过后可以提取用于bl31镜像哈希比较的哈希值soc_fw_hash
- bl31镜像bl31_image:父镜像是上面的内容证书,认证方法为哈希比较,比较的可信哈希为上面的soc_fw_hash
ini
static const auth_img_desc_t soc_fw_key_cert = {
.img_id = SOC_FW_KEY_CERT_ID,
.img_type = IMG_CERT,
.parent = &trusted_key_cert,
.img_auth_methods = (const auth_method_desc_t[AUTH_METHOD_NUM]) {
[0] = {
BL33的信任链:
包括密钥证书non_trusted_fw_key_cert、内容证书non_trusted_fw_content_cert和镜像bl33_image。
- 密钥证书non_trusted_fw_key_cert:同BL31类似,只是验签的公钥是非安全世界公钥non_trusted_world_pk,NV计数器是非安全的计数器non_trusted_nv_ctr。认证通过后可以提取内容证书验证公钥nt_fw_content_pk
- 内容证书non_trusted_fw_content_cert:同理,认证通过后可以提取用于镜像bl33比较的镜像哈希值nt_world_bl_hash
- bl33镜像bl33_image:父镜像是上面的内容证书,认证方法为哈希比较,比较的可信哈希为上面的nt_world_bl_hash
ini
static const auth_img_desc_t non_trusted_fw_key_cert = {
.img_id = NON_TRUSTED_FW_KEY_CERT_ID,
.img_type = IMG_CERT,
.parent = &trusted_key_cert,
.img_auth_methods = (const auth_method_desc_t[AUTH_METHOD_NUM]) {
[0] = {
.type = AUTH_METHOD_SIG,
参考:
- 【安全有理--(07)ATF安全启动】blog.csdn.net/qq_16106195...
- 【Arnold Lu@南京--ARM Trusted Firmware分析------镜像签名/加密/生成、解析/解密/验签--www.cnblogs.com/arnoldlu/p/...
后记:
本篇又是干货满满的一篇,但是总有一些核心的技术细节,例如到加解密mbedtls的分析,感觉太耗时了,从入门到放弃,专业选手可以搞一下。这里作为入门足够了,毕竟mbedtls开源库基本不会有人去改里面的代码,也不敢让改啊。
相信跟着这个系列看到这里,对secure boot从代码级别已经有所小成了,但是工作中估计还是远远不够,一入开源代码深似海,路已经铺好,等大家自己去探索了。