关于TA的签名、验签、加载以及调用的学习笔记

TA验签加载与调用

TA的签名

以optee-os 3.11版本为例。在optee_os目录下,存放着签名的私钥和签名脚本。
工程目录/optee_os/keys/default_ta.pem
工程目录/optee_os/scripts/sign_encrypt.py
编译optee-os会将TA编译为elf文件。此时执行签名脚本,对elf文件签名并生成.ta文件。此脚本还会放置头部数据,shdr,放在TA镜像的头部。头部放置了magic number,需要和optee-os代码里的magic number匹配。其他的就是签名相关的数据。

/* struct shdr - signed header*/
struct shdr {
	uint32_t magic;
	uint32_t img_type;
	uint32_t image_size;
	uint32_t algo;
	uint16_t hash_size;
	uint16_t sig_size;
};

下图是签名后的TA镜像文件的内容。
关于TA的签名、验签、加载以及调用的学习笔记

TA的验签

在编译optee_os时,pem_to_pub_c.py脚本中将default_ta.pem中解析出der格式rsa_pub公钥,放到了ta_pub_key.c文件的ta_pub_key_modulus数组中,此时等于说公钥是放在.rodata段。此数组在optee-os加载TA时shdr_verify_signature函数会从此数组里获取公钥用于验签TA。验签时只验头部shdr,通过rpc调用到ree侧的tee_supplicant,此时会先加载TA的头shdr,然后调用shdr_verify_signature读取公钥和shdr的签名信息验签。

/* 存放公钥的数组 */
const unit8_t ta_pub_key_modulus[];
/* Validate header signature */
res = shdr_verify_signature(shdr);

TA的加载

动态TA的加载

关于TA的签名、验签、加载以及调用的学习笔记

通过GP标准调用的与TA通信的命令(opensession\invoke\closession)其实都是std smc call,该smc调用后,会进入到TEE中的tee_entry_std中。
tee_entry_std函数中调用就是opensession\invoke\closession的接口。调用到entry_open_session接口。动态TA最终会在tee_ta_init_user_ta_session函数里加载。

ldelf

tee_ta_init_user_ta_session里调用load_ldelf函数。其实现大致为,在代码编译的时候,optee-os下的ldelf目录下编译出ldelf.elf二进制文件,通过gen_ldelf_hex.py脚本将二进制文件生成ldelf_hex.c文件。其二进制数据会被写入数组ldelf_data,还有代码段,数据段的大小。在load_ldelf函数中将其加载到内存中。再通过init_with_ldelf函数调用thread_enter_user_mode函数切换到用户模式el0,运行此ldelf的代码,ldelf其作用为load需要加载的TA。调用ldelf → ta_elf_load_main → load_main加载TA的二进制文件,通过系统调用到optee内核再由内核切到system.PTA,此TA加载动态TA的镜像。invoke到此TA,调用system_open_ta_binary。此函数会遍历注册的TA_STORE,调用open等操作函数。
3.11的optee-os的版本里注册的是下面两个。

TEE_TA_REGISTER_TA_STORE(9) = {
	.description = "REE",
	.open = ree_fs_ta_open,
	.get_size = ree_fs_ta_get_size,
	.get_tag = ree_fs_ta_get_tag,
	.read = ree_fs_ta_read,
	.close = ree_fs_ta_close,
};
TEE_TA_REGISTER_TA_STORE(4) = {
	.description = "Secure Storage TA",
	.open = secstor_ta_open,
	.get_size = secstor_ta_get_size,
	.get_tag = secstor_ta_get_tag,
	.read = secstor_ta_read,
	.close = secstor_ta_close,
};

ree_fs_ta_open函数解析:
先调用rpc_load:通过rpc调用ree侧的tee_supplicant ,传入uuid,ree侧将uuid对应的TA二进制文件加载到共享内存中,在rpc_load中包含两次TA加载的rpc请求,第一次参数里只有UUID,因为此时optee-os不知道加载的TA的image大小,tee supplicant接收到TA加载请求后由于解析出参数的image size为0,所以其通过UUID读取TA文件后计算出ta_size,发现传入大小不足,所以此时没有加载TA 并返回需要传入的size给到optee-os的rpc_load(),其得到size后通过rpc请求size大小的共享内存并得到地址,此时将uuid、共享内存地址、size传入作为第二次rpc加载TA请求,tee-suplicant收到后加载TA到对应的共享内存中。

/* Request TA from tee-supplicant */
res = rpc_load(uuid, &ta, &ta_size, &mobj);

接着调用shdr_alloc_and_copy:通过rpc_load()之后得到的TA加载的共享内存地址,因为TA镜像在签名时增加签名相关的头部文件shdr,在此函数中计算shdr的大小,struct shdr + hash_size + sig_size。然后申请此大小的内存并将TA镜像的shdr拷贝到申请的内存里。用于后面的验签流程。

/* Make secure copy of signed header */
shdr = shdr_alloc_and_copy(ta, ta_size);

接着调用shdr_verify_signature执行验签工作。传入镜像头部文件shdr指针。shdr包括了magic、img_type、img_size、algo等参数。先检验代码中的magic与shdr中的是否一致,从前面提到的ta_pub_key.c中读取ta_pub_key_modulus,提取出pub_key,从shdr中提取hash和sig,使用rsa算法验签此shdr。

/* Validate header signature */
res = shdr_verify_signature(shdr);

然后在ree_fs_ta_open()里还要取出TA镜像里的uuid与传入的uuid比较,并初始化hash_ctx。
ldelf完成后返回到optee-os内核中。

TA的调用

关于TA的签名、验签、加载以及调用的学习笔记

动态TA的调用

从前面的ldelf回到tee_ta_open_session函数。在此函数中调用了user_ta注册的enter_open_session。

res = ctx->ops->enter_open_session(s, param, err);
static const struct tee_ta_ops user_ta_ops __rodata_unpaged = {
	.enter_open_session = user_ta_enter_open_session,
	.enter_invoke_cmd = user_ta_enter_invoke_cmd,
	.enter_close_session = user_ta_enter_close_session,
	.dump_state = user_ta_dump_state,
#ifdef CFG_FTRACE_SUPPORT
	.dump_ftrace = user_ta_dump_ftrace,
#endif
	.destroy = user_ta_ctx_destroy,
	.get_instance_id = user_ta_get_instance_id,
	.handle_svc = user_ta_handle_svc,
};

而不论是open_session、close_session、invoke_cmd等CA调用,到了optee-os内核中都是通过user_ta_enter函数处理的。在此函数中调用thread_enter_user_mode切换到el0,执行TA。TA入口为__ta_entry,根据不同的调用命令,选择不同的执行函数(open、close、invoke),此处为open_session,进入entry_open_session,最后执行到TA_OpenSessionEntryPoint的GP接口。

静态TA的调用

静态TA,也就是PTA,其作为optee内核的一部分被编译进内核。其不需要加载,CA调用的open/invoke/close和动态TA一样,与动态TA不同的是,调用动态TA实际对应的是user_ta_ops 注册的操作函数,也就是上面提到的。而调用静态TA对应的接口变为了下面的pseudo_ta_ops 接口,对应注册PTA的这些接口,open_session_entry_point、invoke_command_entry_point 等。所以CA的调用流程是一样的。

static const struct tee_ta_ops pseudo_ta_ops = {
	.enter_open_session = pseudo_ta_enter_open_session,
	.entry_invoke_cmd = pseudo_ta_enter_invoke_cmd,
	.enter_close_session = pseudo_ta_enter_close_session,
	.destroy = pseudo_ta_destroy,
};
pseudo_ta_register(.uuid = PTA_INVOKE_TEST_UUID, .name = TA_NAME,
		   .flag = PTA_DEFAULT_FLAGS | TA_FLAG_SECURE_DATA_PATH |
				   TA_FLAG_CONCURRENT | TA_FLAG_DEVICE_ENUM,
		   .create_entry_point = create_ta,
		   .destroy_entry_point = destroy_ta,
		   .open_session_entry_point = open_session,
		   .close_session_entry_point = close_session,
		   .invoke_command_entry_point = invoke_command);
上一篇:Python笔记 之 文件open操作


下一篇:Python文件与数据格式化