OPTEE:TA镜像的签名和加载-上(五)
0、前言
在前面知道了关于TA的基础知识,在书里面还有关于调用、内存、线程等知识,但是因为这次的任务是关于TA动态加载功能的验证,所以就先暂时跳过,我的学习一直都是建立关联。
前面讲了TA的用处,以及和CA的配合使用。这一节就来讲讲TA镜像本身的东西。
使用OP-TEE实现特定功能需求则需要开发一个特定的TA, TA调用GP规范定义的接口实现该功能需求。
(这篇文章为了连续性,就长得比较长,不一定一次性看完,建议慢慢享用,体会这个TA编译签名加载使用的逻辑,我分为两篇讲完)
TA镜像文件会被保存在REE侧的文件系统中并以动态TA的方式运行于OP-TEE中,当用户需要调用该TA的功能时,通过在CA中调用libteec库中的接口,完成创建会话的操作,将REE侧文件系统中的TA镜像文件加载到OP-TEE的用户空间运行。
为防止该TA镜像文件被篡改或被破坏,在加载TA镜像文件的过程中会对该TA镜像文件的合法性进行检查,只有校验通过的TA镜像文件才允许运行于OP-TEE的用户空间。(这就是目的)
编译TA镜像文件过程中会对TA镜像文件做电子签名操作。
接下来就讲讲TA镜像文件的编译、签名以及加载。
1、TA镜像文件的编译和签名
TA镜像文件在OP-TEE工程编译过程中生成,也可通过单独调用TA目录下的脚本来进行编译,但前提是OP-TEE工程被完整编译过。
编译过程会先生成原始的TA镜像文件,然后使用签名脚本对该文件进行电子签名,并最终生成.ta文件,即最终会被加载到OP-TEE中的TA镜像文件。(这里感觉应该知道OPTEE的工程编译会好点~~~嘤嘤嘤,先讲讲什么是电子签名和验签)
电子签名(数字签名):利用公钥加密系统
常用的签名算法有:
+ RSA,基于大整数分解问题
+ DSA,基于离散对数问题
+ ECDSA,属于DSA的一个变种,基于椭圆曲线上的离散对数问题
其中RSA是实现数字签名最简单的公钥加密方法。
RSA加密算法是一种非对称加密算法,所谓非对称,就是指该算法需要一对密钥,使用其中一个加密,则需要用另一个才能解密。这样就可以在不直接传递密钥的情况下,完成解密。这能够确保信息的安全性,避免了直接传递密钥所造成的被破解的风险。是由一对密钥来进行加解密的过程,分别称为公钥和私钥。两者之间有数学相关,该加密算法的原理就是对一极大整数做因数分解的困难性来保证安全性。通常个人保存私钥,公钥是公开的(可能同时多人持有)。
具体的原理可以看看这个峰峰老师的文章:http://www.ruanyifeng.com/blog/2013/07/rsa_algorithm_part_two.html
加密和签名都是为了安全性考虑,但略有不同。
常有人问加密和签名是用私钥还是公钥?其实都是对加密和签名的作用有所混淆。
简单的说,加密是为了防止信息被泄露,而签名是为了防止信息被篡改。
总结:公钥加密、私钥解密。
私钥签名、公钥验签。
举个栗子:
第一个场景:战场上,B要给A传递一条消息,内容为某一指令。
RSA的加密过程如下:
(1)A生成一对密钥(公钥和私钥),私钥不公开,A自己保留。公钥为公开的,任何人可以获取。
(2)A传递自己的公钥给B,B用A的公钥对消息进行加密。
(3)A接收到B加密的消息,利用A自己的私钥对消息进行解密。
在这个过程中,只有2次传递过程,第一次是A传递公钥给B,第二次是B传递加密消息给A,即使都被敌方截获,也没有危险性,因为只有A的私钥才能对消息进行解密,防止了消息内容的泄露。(公钥加密、私钥解密。)
第二个场景:A收到B发的消息后,需要进行回复“收到”。
RSA签名的过程如下:
(1)A生成一对密钥(公钥和私钥),私钥不公开,A自己保留。公钥为公开的,任何人可以获取。
(2)A用自己的私钥对消息加签,形成签名,并将加签的消息和消息本身一起传递给B。
(3)B收到消息后,在获取A的公钥进行验签,如果验签出来的内容与消息本身一致,证明消息是A回复的。
在这个过程中,只有2次传递过程,第一次是A传递加签的消息和消息本身给B,第二次是B获取A的公钥,即使都被敌方截获,也没有危险性,因为只有A的私钥才能对消息进行签名,即使知道了消息内容,也无法伪造带签名的回复给B,防止了消息内容的篡改。
但是,综合两个场景你会发现,第一个场景虽然被截获的消息没有泄露,但是可以利用截获的公钥,将假指令进行加密,然后传递给A。(就是发送假消息)
第二个场景虽然截获的消息不能被篡改,但是消息的内容可以利用公钥验签来获得,并不能防止泄露。(消息的内容暴露了)
实际使用的时候:要根据情况使用,也可以同时使用加密和签名,比如A和B都有一套自己的公钥和私钥,当A要给B发送消息时,先用B的公钥对消息加密,再对加密的消息使用A的私钥加签名,达到既不泄露也不被篡改,更能保证消息的安全性。
下面开始整体
1.2、TA镜像文件的编译
前面知道在TA镜像文件在OP-TEE工程编译过程中生成,也可通过单独调用TA目录下的脚本来进行编译,但前提是OP-TEE工程被完整编译过。
编译过程会先生成原始的TA镜像文件,然后使用签名脚本对该文件进行电子签名,并最终生成.ta文件,即最终会被加载到OP-TEE中的TA镜像文件。
首先TA镜像文件的编译,对某个TA源代码目录中的Makefile文件执行make指令可触发编译生成TA镜像文件的操作,该Makefile文件将会包含optee_os/ta/mk/ta_dev_kit.mk文件,该文件中会定义各种目标依赖关系和Object,
**编译完目标和object后,编译器将会按照optee_os/ta/arch/arm/link.mk文件中的依赖关系将目标和object链接成xxx.ta文件,**其中xxx是该TA UUID的值。
make–>Makefile文件–>optee_os/ta/mk/ta_dev_kit.mk—>optee_os/ta/arch/arm/link.mk–>xxx.ta(UUID的值)
link.mk中的链接依赖关系如下:
(
l
i
n
k
−
o
u
t
−
d
i
r
)
/
(link-out-dir)/
(link−out−dir)/(binary).stripped.elf目标会删除TA镜像文件中的调试信息。
在原始TA镜像文件的头部有一个ta_head段,该段中存放该TA的基本信息以及被调用到的入口地址,该段的内容将会在加载TA镜像到OP-TEE时和调用TA执行特定命令时被使用到。
存放在该段中的内容定义在optee_os/ta/arch/arm/user_ta_header.c文件中,其内容如下:
1.3、对TA镜像文件的签名
生成原始的TA镜像文件后,编译系统会对该镜像文件进行签名生成最终的xxx.ta文件,该文件会被保存在REE侧的文件系统中。
对原始TA镜像文件的签名操作是使用optee_os/scripts/sign.py文件来实现,使用的私钥是optee_os/keys目录下的RSA2048密钥(default_ta.pem)。当该TA需要被正式发布时,应该使用OEM厂商自有的私钥替换掉该密钥。
sign.py文件的内容如下:
签名完成后的TA镜像文件中的内容如图18-1所示。
签名后的TA镜像文件在被加载到OP-TEE内存中之前,会使用签名信息对该TA镜像文件进行合法性检查。(这个过程是我灰常想知道的。)
2、TA镜像的加载
前面讲了TA和CA建立会话,但是对这个TA的加载一笔带过,这里在着重讲一下TA的镜像加载。
我们知道TA分为静态和动态,静态是一开始和TEE一起编译进镜像,动态的话是放在REE侧的文件系统中。当CA第一次调用libteec库中的创建会话操作时,如果被调用的TA是动态TA,则会触发OP-TEE加载该动态TA镜像文件的操作。
在加载过程中,OP-TEE会发送PRC请求通知tee_supplicant从文件系统中将UUID对应的TA镜像文件传递到OP-TEE
如果验证通过则将相关段中的内容保存到OP-TEE用户空间分配的TA内存中。
加载TA镜像的整体流程如图所示。
2.1 REE侧获取TA镜像文件的内容
OP-TEE通过调用rpc_load函数发送PRC请求,将TA镜像文件的内容从REE侧加载到OP-TEE的共享内存中。
该函数会触发两次RPC请求:
-
第一次RPC请求用于获取TA镜像文件的大小
-
第二次RPC请求是将TA镜像文件加载到OP-TEE的共享内存中
触发第二次RPC请求之前,OP-TEE会在用户空间先分配与TA镜像文件的大小相等的共享内存区域,该区域用于存放TA镜像文件的内容。(这里我们知道TA静态在内核态,动态在用户态,都是指的TEE环境中)
rpc_load函数的内容如下:
对TA镜像文件内容的合法性检查,将TA加载到OP-TEE用户空间TA的内存操作都是在共享内存中完成的。(这个共享内存区域就是在REE的内核态创建的。REE和TEE的很多操作都是通过共享内存实现的,所以在ATF中也有调用到copy_from_user这些函数的调用)
2.2 加载TA镜像的RPC请求
加载TA过程中,ta_open函数会调用rpc_load函数,该函数会调用thread_rpc_cmd来发送OPTEE_MSG_RPC_CMD_LOAD_TA的RPC请求,rpc_load函数会组合该类请求的相关数据结构变量,然后通过调用thread_rpc函数向REE发送RPC请求。thread_rpc_cmd函数的内容和介绍如下:
在整个TA的加载过程中会发送两次RPC请求,第一次是用于获取TA镜像文件的大小,第二次RPC请求是通知tee_supplicant将TA镜像文件的内容加载到OP-TEE提供的共享内存中。
2.3 RPC请求的发送
RPC请求的发送是通过触发安全监控模式调用(smc)来实现的,在触发安全监控模式调用(smc)之前会将当前的线程挂起,并保存该线程的运行上下文。该函数以汇编的形式实现内容如下:
当REE处理完RPC请求后,会发送标准安全监控模式调用(std smc)重新进入到OP-TEE中,OP-TEE根据返回的安全监控模式调用(smc)的类型判定当前的安全监控模式调用(smc)是RPC的返回还是普通的安全监控模式调用(smc)。
如果该安全监控模式调用(smc)是返回RPC请求的处理结果,则会进入到thread_resume_from_rpc分支恢复之前被挂起的线程。在thread_rpc函数中已经指定了恢复该线程之后程序执行的入口函数——thread_rpc_return,到此一次完整的RPC请求也就被处理完毕。
(其实你很难第一次就把这些知识吸收掉,尽管前辈的书写的很好很详细,等到你需要在这个方面去做点东西的时候,你再来看,会找到答案。)
2.4 读取TA镜像文件内容到共享内存
rpc_load函数发起第二次RPC请求时才会将TA镜像文件的内容读取到OP-TEE提供的共享内存中。
共享内存的分配是在rpc_load函数中调用thread_rpc_alloc_payload函数来实现的。
分配的共享内存的地址将会被保存到ta_handle变量的nw_ta成员中,读取到的TA镜像文件的内容将会被加载到OP-TEE用户空间TA运行的内存中。(代码内容解释见16.2.1节。《手机安全和可信应用开发指南》)
到这里休息一下啦小少爷,不累的话,那就继续看下一篇吧。