实际上包含了两个问题:
- 如何把自定义的二进制文件打包到fip.bin中?
- 如何在secure boot流程中load和认证自定义的二进制文件?
如何打包
证书创建工具和FIP打包工具是通过命令行传参的方式进行证书创建和打包的,如下:
由于我们需要打包自定义得image到fip,但是证书创建工具和FIP打包工具预定义的选项中并没有该选项,所以需要修改cert_create和fip_tool工具。
准备自定义的image
我直接在ATF的platform.mk中生成了一个测试用的dtb作为image:
FDT_SOURCES += ${PLAT_QEMU_PATH}/test.dts
HW_CONFIG := ${BUILD_PLAT}/fdts/test.dtb
修改cert_create
-
在
include/tools_share/tbbr_oid.h
文件中添加自定义的证书拓展域OID,定义如下(需保证与其他OID不同):#define USER_IMAGE_HASH_OID "1.3.6.1.4.1.4128.2100.1401"
-
在
tools/cert_create/include/tbbr/tbb_ext.h
添加一行自定义image的证书拓展域枚举类型:/* TBBR extensions */ enum { TRUSTED_FW_NVCOUNTER_EXT, NON_TRUSTED_FW_NVCOUNTER_EXT, TRUSTED_BOOT_FW_HASH_EXT, TRUSTED_BOOT_FW_CONFIG_HASH_EXT, ................. SCP_FWU_CFG_HASH_EXT, AP_FWU_CFG_HASH_EXT, FWU_HASH_EXT, USER_IMAGE_EXT, /* user-defined image ext */ };
-
我们想要使用
TRUSTED_BOOT_FW_CERT
去认证这个image,所以在tools/cert_create/src/tbbr/tbb_cert.c
文件中TRUSTED_BOOT_FW_CERT
的拓展域添加USER_IMAGE_EXT
枚举类型。/* * Certificates used in the chain of trust * * The order of the certificates must follow the enumeration specified in * tbb_cert.h. All certificates are self-signed, so the issuer certificate * field points to itself. */ static cert_t tbb_certs[] = { [TRUSTED_BOOT_FW_CERT] = { .id = TRUSTED_BOOT_FW_CERT, .opt = "tb-fw-cert", .help_msg = "Trusted Boot FW Certificate (output file)", .fn = NULL, .cn = "Trusted Boot FW Certificate", .key = ROT_KEY, .issuer = TRUSTED_BOOT_FW_CERT, .ext = { TRUSTED_FW_NVCOUNTER_EXT, TRUSTED_BOOT_FW_HASH_EXT, TRUSTED_BOOT_FW_CONFIG_HASH_EXT, HW_CONFIG_HASH_EXT, FW_CONFIG_HASH_EXT, USER_IMAGE_EXT, /* user-defined image ext */ }, .num_ext = 6 /*modify the size from five to six */ },
-
接着,在
tools/cert_create/src/tbbr/tbb_ext.c
中添加该拓展域的内容:static ext_t tbb_ext[] = { [TRUSTED_FW_NVCOUNTER_EXT] = { .oid = TRUSTED_FW_NVCOUNTER_OID, .opt = "tfw-nvctr", .help_msg = "Trusted Firmware Non-Volatile counter value", .sn = "TrustedWorldNVCounter", .ln = "Trusted World Non-Volatile counter", .asn1_type = V_ASN1_INTEGER, .type = EXT_TYPE_NVCOUNTER, .attr.nvctr_type = NVCTR_TYPE_TFW }, .................... /* new added */ [USER_IMAGE_EXT] = { .oid = USER_IMAGE_HASH_OID, /* 自定义的OID */ .opt = "user-img", /*这个会生成cert_create的选项 --user-img */ .help_msg = "User-defined Image file", .sn = "UserImgHash", .ln = "User-defined image HASH (SHA256)", .asn1_type = V_ASN1_OCTET_STRING, .type = EXT_TYPE_HASH, /* 使用hash校验该image */ .optional = 1 } }; REGISTER_EXTENSIONS(tbb_ext);
自此,cert_create可以使用–user-img命令为自定义的image生成hash并将hash值存储在TRUSTED_BOOT_FW_CERT
证书中。
修改fip_tool
image准备好了,证书生成了,该打包进fip了。
-
首先在
include/tools_share/firmware_image_package.h
添加一个标识该image的唯一的UUID号,该UUID将会在加载认证阶段用于识别该image:#define UUID_USER_DEFINED_IMG \ {{0x23, 0x6e, 0xd3, 0x30}, {0x4e, 0xdf}, {0x11, 0xef}, 0x8d, 0xd7, {0x00, 0x15, 0x5d, 0xba, 0x59, 0x68} }
-
在
tools/fiptool/tbbr_config.c
文件中为fip_tool添加一个命令行选项--user-img
。toc_entry_t toc_entries[] = { { .name = "Platform Key Certificate", .uuid = UUID_PLAT_KEY_CERT, .cmdline_name = "plat-key-cert" }, ................................. //添加命令 { .name = "User-defined Image", .uuid = UUID_USER_DEFINED_IMG, .cmdline_name = "user-img" }, { .name = NULL, .uuid = { {0} }, .cmdline_name = NULL, } };
这里的命令行选项需要和cert_create的保持一致。
-
做完以上操作,在使用fip_tool工具时在–user-img后面跟上自定义的image的路径就可以将image打包到fip_tool。
打包image
最后我们在platform.mk中使用一个宏TOOL_ADD_PAYLOAD即可完成:
- 将image的hash记录在
TRUSTED_BOOT_FW_CERT
证书中,用于后续的验签 - 将image打包到fip.bin。
如何加载
可以拆解为以下问题:
- 从哪里加载?(需要修改IO storage)
- 加载到哪里?(定义加载的位置并进行MMU映射)
- 如何加载?怎么验签?(修改信任链)
从哪里加载
-
首先我们在
include/export/common/tbbr/tbbr_img_def_exp.h
定义一个ID唯一标识该image:#define PLAT_USER_IMG_ID U(38) /* Max Images */ #define MAX_IMAGE_IDS U(39)
-
在平台路径
plat/qemu/common/qemu_io_storage.c
中定义自定义image的描述符:static const io_uuid_spec_t user_image_uuid_spec = { .uuid = UUID_USER_DEFINED_IMG, };
-
在平台路径
plat/qemu/common/qemu_io_storage.c
中的plat_io_policy数组中添加以下内容,表示该image从fip文件中根据UUID进行解析提取。static const struct plat_io_policy policies[] = { [FIP_IMAGE_ID] = { &memmap_dev_handle, (uintptr_t)&fip_block_spec, open_memmap }, ..................... [PLAT_USER_IMG_ID] = { &fip_dev_handle, (uintptr_t)&user_image_uuid_spec, open_fip }, };
加载到哪里
-
首先需要在
plat/qemu/qemu/include/platform_def.h
中定义自定义镜像加载的基地址和边界,我这里定义的是在RAM的其实地址,最大为一个PAGE_SIZE(4KB),毕竟设备树很小。#define USER_IMG_BASE BL_RAM_BASE #define USER_IMG_LIMIT (USER_IMG_BASE + PAGE_SIZE)
-
我们计划是在BL2阶段load镜像,所以需要在BL2起来之后进行memory map,不然未经过MMU平坦映射的地址是无法访问的。在
plat/qemu/common/qemu_bl2_setup.c
中添加以下映射,内存属性为普通memory、可读写、secure。#define MAP_BL2_TOTAL MAP_REGION_FLAT( \ bl2_tzram_layout.total_base, \ bl2_tzram_layout.total_size, \ MT_MEMORY | MT_RW | MT_SECURE), \ /* new added */ \ MAP_REGION_FLAT( \ USER_IMG_BASE, \ USER_IMG_LIMIT \ - USER_IMG_BASE, \ MT_MEMORY | MT_RW | MT_SECURE)
如何加载?怎么验签?
-
然后需要告诉BL2阶段需要加载这个自定义的image,在
plat/qemu/common/qemu_bl2_mem_params_desc.c
的bl2_mem_params_descs
数组中添加如下内容。如此,BL2在load_image时就会解析该数组,根据镜像UUID在fip中查找到该镜像,并将镜像load到指定的镜像基地址进行验证。static bl_mem_params_node_t bl2_mem_params_descs[] = { /* Fill BL32 related information */ { .image_id = BL32_IMAGE_ID, SET_STATIC_PARAM_HEAD(ep_info, PARAM_EP, VERSION_2, entry_point_info_t, BL32_EP_ATTRIBS), .ep_info.pc = BL32_BASE, SET_STATIC_PARAM_HEAD(image_info, PARAM_EP, VERSION_2, image_info_t, BL32_IMG_ATTRIBS), .image_info.image_base = BL32_BASE, .image_info.image_max_size = BL32_LIMIT - BL32_BASE, .next_handoff_image_id = BL33_IMAGE_ID, }, ................................... /* new added */ { .image_id = PLAT_USER_IMG_ID, /* 镜像ID */ SET_STATIC_PARAM_HEAD(ep_info, PARAM_IMAGE_BINARY, VERSION_2, entry_point_info_t, SECURE | NON_EXECUTABLE), SET_STATIC_PARAM_HEAD(image_info, PARAM_IMAGE_BINARY, VERSION_2, image_info_t, 0), .image_info.image_base = USER_IMG_BASE, /* 加载的基地址 */ .image_info.image_max_size = USER_IMG_LIMIT - USER_IMG_BASE, /*镜像最大size*/ .next_handoff_image_id = INVALID_IMAGE_ID, }, };
-
然后还需要修改COT(chain of trust),否则BL2不知道如何验证该镜像的完整性。在
drivers/auth/tbbr/tbbr_cot_common.c
中添加如下内容,表明TRUSTED_BOOT_FW_CERT_ID
证书的拓展域存储了自定义image的hash值。line 32: static unsigned char user_img_hash_buf[HASH_DER_LEN]; line 56: auth_param_type_desc_t user_img_hash = AUTH_PARAM_TYPE_DESC( AUTH_PARAM_HASH, USER_IMAGE_HASH_OID); * trusted_boot_fw_cert */ line 63: const auth_img_desc_t trusted_boot_fw_cert = { .img_id = TRUSTED_BOOT_FW_CERT_ID, .img_type = IMG_CERT, .parent = NULL, .img_auth_methods = (const auth_method_desc_t[AUTH_METHOD_NUM]) { ................. }, .authenticated_data = (const auth_param_desc_t[COT_MAX_VERIFIED_PARAMS]) { [0] = { .type_desc = &tb_fw_hash, .data = { .ptr = (void *)tb_fw_hash_buf, .len = (unsigned int)HASH_DER_LEN } }, ................................ /* new added */ [4] = { .type_desc = &user_img_hash, /* hash 校验 */ .data = { .ptr = (void *)user_img_hash_buf, /* 证书拓展域的hash buffer */ .len = (unsigned int)HASH_DER_LEN } }, } };
-
还需要在
drivers/auth/tbbr/tbbr_cot_common.c
文件中增加一个需要认证的节点的描述结构:const auth_img_desc_t user_img = { .img_id = PLAT_USER_IMG_ID, .img_type = IMG_RAW, .parent = &trusted_boot_fw_cert, /* 指定它需要由trusted_boot_fw_cert认证*/ .img_auth_methods = (const auth_method_desc_t[AUTH_METHOD_NUM]) { [0] = { .type = AUTH_METHOD_HASH, /* 认证方法为hash */ .param.hash = { .data = &raw_data, .hash = &user_img_hash } } } };
-
最后将该结构加入到COT当中。在文件
drivers/auth/tbbr/tbbr_cot_bl2.c
增加。这样,在验证该image时,BL2就可以根据COT找到认证该image所需的父节点和方法。static const auth_img_desc_t * const cot_desc[] = { [TRUSTED_BOOT_FW_CERT_ID] = &trusted_boot_fw_cert, [HW_CONFIG_ID] = &hw_config, [TRUSTED_KEY_CERT_ID] = &trusted_key_cert, [SCP_FW_KEY_CERT_ID] = &scp_fw_key_cert, [SCP_FW_CONTENT_CERT_ID] = &scp_fw_content_cert, ................................. [PLAT_USER_IMG_ID] = &user_img, //new added };
Demo
从启动过程中log可以看出:BL2在加载ID为38的镜像(即我们添加的自定义镜像PLAT_USER_IMG_ID
)时,去加载了ID=6的TRUSTED_BOOT_FW_CERT
证书对齐进行认证,认证成功后才开始去解析该image。
文档提供完整patch资源,在ATF2.9.0的qemu平台的代码上实现了将一个自定义的dtb打包到fip中,并且在BL2阶段对该dtb进行加载和验证。