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的验签
在编译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的加载
通过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的调用
从前面的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);