Secure boot入门-4镜像验签代码分析

之前的文章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, &param_ptr, &param_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;
}

由三部分组成

  1. 根据本镜像格式,验证本镜像完整性--IPM
  2. 验证本镜像签名--CM
  3. 提取子镜像验证需要的信息--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_SIGAUTH_METHOD_NV_CTR放回滚校验

对于使用X509证书方式的镜像解析方式而言,auth_signature函数只会针对于镜像类型为证书IMG_CERT。

认证签名是公钥验证镜像的签名值,需要以下参数:

  1. 待认证的镜像:被签名的数据,签名值,签名算法
  2. 父镜像:公钥(或公钥哈希) 如果父镜像只包含公钥的哈希,则需要从待认证的镜像中提取公钥值(即自签名的证书),并计算该公钥的哈希值,与父镜像中获取的哈希值进行比较。如果镜像没有父镜像,则需要用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,

参考:

  1. 【安全有理--(07)ATF安全启动】blog.csdn.net/qq_16106195...
  2. 【Arnold Lu@南京--ARM Trusted Firmware分析------镜像签名/加密/生成、解析/解密/验签--www.cnblogs.com/arnoldlu/p/...

后记:

本篇又是干货满满的一篇,但是总有一些核心的技术细节,例如到加解密mbedtls的分析,感觉太耗时了,从入门到放弃,专业选手可以搞一下。这里作为入门足够了,毕竟mbedtls开源库基本不会有人去改里面的代码,也不敢让改啊。

相信跟着这个系列看到这里,对secure boot从代码级别已经有所小成了,但是工作中估计还是远远不够,一入开源代码深似海,路已经铺好,等大家自己去探索了。

相关推荐
凡人的AI工具箱6 小时前
AI教你学Python 第11天 : 局部变量与全局变量
开发语言·人工智能·后端·python
是店小二呀6 小时前
【C++】C++ STL探索:Priority Queue与仿函数的深入解析
开发语言·c++·后端
canonical_entropy6 小时前
金蝶云苍穹的Extension与Nop平台的Delta的区别
后端·低代码·架构
我叫啥都行7 小时前
计算机基础知识复习9.7
运维·服务器·网络·笔记·后端
无名指的等待7128 小时前
SpringBoot中使用ElasticSearch
java·spring boot·后端
.生产的驴8 小时前
SpringBoot 消息队列RabbitMQ 消费者确认机制 失败重试机制
java·spring boot·分布式·后端·rabbitmq·java-rabbitmq
AskHarries9 小时前
Spring Boot利用dag加速Spring beans初始化
java·spring boot·后端
苹果酱05679 小时前
一文读懂SpringCLoud
java·开发语言·spring boot·后端·中间件
掐指一算乀缺钱10 小时前
SpringBoot 数据库表结构文档生成
java·数据库·spring boot·后端·spring
计算机学姐12 小时前
基于python+django+vue的影视推荐系统
开发语言·vue.js·后端·python·mysql·django·intellij-idea