OP-TEE架构分析
基本上,存在两个独立的世界:一个Normal世界,另一个是Secure世界。 每个世界都有自己的操作系统(OS)和用户应用程序,简单地说,正常世界中的用户应用程序和正常操作系统是传统的,而安全世界中的用户应用程序和正常操作系统具有专门的用途(例如数字版权管理,认证等)。
两个世界通过安全Monitor进行通信。
当受信任的OS从其非特权模式加载信任关系时,它首先检查其签名及软件完整性,以查看其是否由正确的方签名。这种完整性检查旨在消除加载被篡改的trustlets的风险。
通过设计,从硬件(例如,eFuse / qFuse或ROM)开始,形成信任链。此链包括引导加载程序、受信任的操作系统和派生的证书或密钥。这种逐步验证程序确保了安全世界的完整性。潜在地,整个系统的安全性和完整性依赖于这种验证。
- TEE subsystem
管理共享内存
提供通用API作为IOCTL - tee-supplicant
TEE辅助程序 - OP-TEE driver
从客户端到OP-TEE的转发命令
从OP-TEE向请求方管理RPC请求
可信应用的设计分为两个部分 :客户端应用和可信端应用。客户端应用和可信端应用通过 uuid 进行识别,只有使用相同的 uuid,双方才能实现交互。下图显示了客户端应用和可信端应用的交互过程。
TA部分
-
TA分为Static TA 和 Dynamic TA两种
静态TA运行在内核模式中,动态TA运行在用户模式中,一般存储在文件系统中,通过TEE_Supplicant被OP-TEE加载。
以下内容帮助对其进行理解:
静态TA只是一个接口,它不是TA本身
静态TA是将特殊服务暴露给动态TAs和客户端的一种方式
静态TA可以通过调用TEE内核中的任何驱动程序代码直接访问硬件。 这意味着您可以使用静态TA扩展内部API提供的服务,而无需添加任何新的系统调用。
一个case必须实现app作为static TA,是因为TA必须在REE准备好之前被启动。 在REE准备好和动态TAs启动完成之后,可以为该静态TA提供服务,这些是需要的对于静态TA想要打开、调用动态TA。
动态TA可以使用Internal Client API与静态TA进行通信
-
MMU L1 Translation tables
保留4GiB大小用于OP-TEE内核,还存在若干32MiB大小的用于TA虚拟内存,并且为每个进程分配这样一个小的tables。
借助OP-TEE来实现特定安全需求时,一次完整的功能调用一般都是起源于CA,TA做具体功能实现并返回数据到CA,而整个过程需要经过OP-TEE的client端接口,OP-TEE在Linux kernel端的驱动,Monitor模式下的SMC处理,OP-TEE OS的thread处理,OP-TEE中的TA程序运行,OP-TEE端底层库或者硬件资源支持等几个阶段。当TA执行完具体请求之后会按照原路径将得到的数据返回给CA。
类似于以下流程:
非安全的应用程序A将数据传递给linux内核,linux内核将用户层数据拷贝到内核层,linux内核向守护进程申请一块共享内存,linux内核又把数据拷贝到共享内存,将数据所在的虚拟地址转为物理地址,将物理地址作为参数放在r1,r2中,产生svc中断传递给OPTEE,OPTEE再将物理地址转为虚拟地址,读取数据,传递给安全应用程序B。
共享内存就是安全世界和非安全世界通信用的,比如安全世界应用调用安全存储API,API函数内部会申请共享内存,然后把加密过得数据存放在共享内存中发送给linux内核,内核再给linux守护进程,linux守护进程在把数据存储到linux文件系统中
-
TA部分的通信与调度
从SMC进入安全世界(FIQ)
-
Command到达 —> 分配可用线程,TA上下文建立并调用
如果需要tee-supplicant,RPC将被启动并挂起
任务完成、RPC 或者IRQ返回Normal
-
TA实现
在TA档案中就是在TEE中要执行的内容,主要使用五个API来控制CA-TA连结与指令操作
- TA_CreateEntryPoint:建立进入点,让TA可以被呼叫
- TA_DestroyEntryPoint:移除进入点,结束TA的功能
- TA_OpenSessionEntryPoint:建立CA呼叫TA的通道,为连接CA-TA的起始点
- TA_CloseSessionEntryPoint:关闭CA-TA的通道
- TA_InvokeCommandEntryPoint:接收CA传送的指令,并在这边执行
TA 进程。每一个可信应用的进程都被分成了两个线程,一个线程专门负责进程间通信,另一个是工作线程,用于处理数据及 IO 任务。这种结构的好处一方面在于当发生中断时,进程不会完全停止 ;另一方面,这种结构也有利于 TA 进程从可信执行环境框架中更好地分离和抽象。
CA部分
典型的CA程序流程遵循GP客户端API
CA就是在REE中用来控制TA的应用程序,主要也是包含五个与TA中对应的api来控制CA-TA连结与指令操作,在制作应用程序时也可以利用呼叫CA或相关的API来控制TA。
-
TEEC_InitializeContext
Connect to the OP-TEE Linux driver
建立context,提供CA连接TA的方式
-
TEEC_OpenSession
Loads the TA
根据UUID建立CA呼叫TA的通道,为连接CA-TA的起始点
-
TEEC_InvokeCommand
Control TA functions
传送指令与需要的参数给TA
-
TEEC_CloseSession
关闭CA-TA的通道
-
TEEC_FinalizeContext
移除建立好的背景
自定义TA_CA实现
TA_代码分析
根据上篇博文的源码进行分析,熟悉项目结构
TEE_Result TA_CreateEntryPoint(void){…}
创建TA的实例时调用。 这是TA的入口
DMSG("has been called");
TEE_Result TA_OpenSessionEntryPoint( uint32_t p1, TEE_Param __maybe_unused p2, void __maybe_unused p3){…}
当TA开启新的会话时被调用。 sess_ctx可以更新一个值,以便能够在后续调用TA中识别此会话。 在此功能中,您通常会对TA进行全局初始化。
DMSG("This is Hiro!\n");
TEE_Result TA_InvokeCommandEntryPoint(void __maybe_unused p1, uint32_t p2, uint32_t p3, TEE_Param p4){…}
TA调用时被call。 sess_ctx(p1)保存由TA_OpenSessionEntryPoint()分配的值。 其余参数来自正常世界。
static TEE_Result inc_value(uint32_t param_types, TEE_Param params[4]){…}
被TA_InvokeCommandEntryPoint中的
inc_value(param_types, params);
调用,实现加1
params[0].value.a++;
DMSG("Got value: … " & "Increase value to:")
void TA_CloseSessionEntryPoint(void __maybe_unused *sess_ctx){…}
在会话关闭时调用,sess_ctx保存由TA_OpenSessionEntryPoint()分配的值。
DMSG("Goodbye!_new_taps\n");
void TA_DestroyEntryPoint(void){…}
如果TA没有被crashed or panicked时,当TA实例被销毁时调用,这是TA最后一个调用。
DMSG("has been called");
CA代码分析
CA部分主程序代码
int main(int argc, char *argv[])
{
TEEC_Result res;
TEEC_Context ctx;
TEEC_Session sess;
TEEC_Operation op;
TEEC_UUID uuid = TA_NEW_TAPS_UUID;
uint32_t err_origin;
/*
* CA第一个调用,初始化上下文连接至TEE
*/
res = TEEC_InitializeContext(NULL, &ctx);
if (res != TEEC_SUCCESS)
errx(1, "TEEC_InitializeContext failed with code 0x%x", res);
/*
* 开启会话,当与TA断会话建立完成,Log输出
* This is Hiro!
*/
res = TEEC_OpenSession(&ctx, &sess, &uuid,
TEEC_LOGIN_PUBLIC, NULL, NULL, &err_origin);
if (res != TEEC_SUCCESS)
errx(1, "TEEC_Opensession failed with code 0x%x origin 0x%x",
res, err_origin);
/*
* 通过调用它来执行TA中的一个功能,在这种情况下我们增加一个数字。
* 命令ID部分的值以及如何解释参数是TA提供的接口的一部分。
*/
/* 明确/组合TEEC_Operation结构体 */
memset(&op, 0, sizeof(op));
/*
* 准备参数。 在第一个参数中传递一个值,剩下的三个参数是未使用的。
*/
op.paramTypes = TEEC_PARAM_TYPES(TEEC_VALUE_INOUT, TEEC_NONE,
TEEC_NONE, TEEC_NONE);
op.params[0].value.a = 42;
/*
* TA_NEW_TAPS_CMD_INC_VALUE是要调用的TA中的实际功能。
*/
printf("Invoking TA to increment %d _NT\n", op.params[0].value.a);
res = TEEC_InvokeCommand(&sess, TA_NEW_TAPS_CMD_INC_VALUE, &op,
&err_origin);
if (res != TEEC_SUCCESS)
errx(1, "TEEC_InvokeCommand failed with code 0x%x origin 0x%x",
res, err_origin);
printf("TA incremented value to %d _NT\n", op.params[0].value.a);
/*
* 结束
*/
TEEC_CloseSession(&sess);
TEEC_FinalizeContext(&ctx);
return 0;
}
完成一次完整的CA请求时在linux 用户需要执行的操作依次如下 :
- 调用TEEC_InitializeContext函数打开op-tee驱动文件,获取到操作句柄并存放到TEE_Context类型的变量中。
- 调用TEEC_OpenSession函数,通过获取到的TEE_Context类型的变量创建一个特定CA与特定TA之间进行通信的通道,如果TA image被存放在file system中,那个在创建session的时候,OP-TEE OS端还会将TA image从file system中加载到OP-TEE。
- 初始化TEEC_Operation类型的变量,并根据实际需要借助TEEC_PARAM_TYPES宏来设定TEEC_Operation类型变量中paramTypes成员的值,该值规定传递到OP-TEE中的最多4个变量缓存或者是数据的作用(作为输入还是输出)。并且还要根据paramTypes的值设定对应的params[x]成员的值或者是指向的地址以及缓存的长度。
- 使用已经创建好的session,TA与CA端规定的command ID以及配置好的TEEC_Operation类型变量作为参数调用TEEC_InvokeCommand函数来真正发起请求。 调用TEEC_InvokeCommand成功之后,剩下的事情就有OP-TEE和TA进行处理并将结果和相关的数据通过TEEC_Operation类型变量中的params成员返回给CA。
- 调用成功之后如果不需要再次调用该TA则需要注销session和释放掉context,这两个操作一次通过调用TEEC_CloseSession函数和TEEC_FinalizeContext函数来实现。
自定义算法实现
本部分实现调用sha1算法和sha256算法分别计算hash值,并将计算结果返回给CA端。
- CA_interface
int main(int argc, char *argv[])
{
if(0 == memcmp(argv[1], "random", 6))
{
TF("Entry get random CA\n");
g_CryptoVerifyCa_Random(atoi(argv[2]), g_RandomOut);
TF("The Respond random from TA just like follow:\n");
g_CA_PrintfBuffer(g_RandomOut, atoi(argv[2]));
}
/* The sha1 result should be:
0x21, 0x9b, 0x5b, 0x8b, 0x25, 0x6f, 0x0e, 0x52, 0xcb, 0x2f, 0xfe, 0xfd, 0x6c, 0x47, 0xd7, 0xb4,
0x44, 0x00, 0x57, 0xc3
*/
if(0 == memcmp(argv[1], "sha1", 4))
{
TF("Entry sha1 CA\n");
g_CryptoVerifyCa_Sha(g_ShaTestBuf, sizeof(g_ShaTestBuf), EN_OP_SHA1, g_ShaOutput, 20);
TF("The Respond hash data from TA just like follow:\n");
g_CA_PrintfBuffer(g_ShaOutput, 20);
}
/* The sha256 result should be:
0xda, 0x52, 0xe9, 0xc2, 0x53, 0xae, 0x03, 0x30, 0xbd, 0x97, 0x3f, 0xa5, 0xf3, 0xea, 0x51, 0x1d,
0x31, 0x0a, 0xdf, 0x1f, 0x0a, 0xc0, 0x0e, 0x62, 0x0f, 0x2d, 0x5e, 0x99, 0xf5, 0xc8, 0x6b, 0x8f
*/
if(0 == memcmp(argv[1], "sha256", 6))
{
TF("Entry sha256 CA\n");
g_CryptoVerifyCa_Sha(g_ShaTestBuf, sizeof(g_ShaTestBuf), EN_OP_SHA256, g_ShaOutput, 32);
TF("The Respond hash data from TA just like follow:\n");
g_CA_PrintfBuffer(g_ShaOutput, 32);
}
if(0 == memcmp(argv[1], "hmac", 4))
{
TF("Entry hmac CA\n");
g_CryptoVerifyCa_hmac(atoi(argv[2]), g_HmacOutput, atoi(argv[3]));
TF("The Respond HMAC data from TA just like follow:\n");
g_CA_PrintfBuffer(g_HmacOutput, atoi(argv[2]));
}
if(0 == memcmp(argv[1], "base64", 6))
{
TF("Entry base64 CA\n");
if(0 == memcmp(argv[2], "enc", 3))
{
g_CryptoVerifyCa_base64(g_Base64Raw, 372, g_Base64out, 500, 1U);
}
else if(0 == memcmp(argv[2], "dec", 3))
{
g_CryptoVerifyCa_base64(g_Base64enced, 496, g_Base64out, 500, 2U);
}
TF("The Respond HMAC data from TA just like follow:\n");
g_CA_PrintfBuffer(g_Base64out, 500);
}
if(0 == memcmp(argv[1], "aes", 3))
{
TF("Entry aes operaton\n");
memset(g_AesOutpUT, 0, 256);
l_Aes_Test(argv[2], argv[3]);
g_CA_PrintfBuffer(g_AesOutpUT, 80);
}
if(0 == memcmp(argv[1], "pbkdf", 5))
{
TF("Entry generate pbkdf CA\n");
g_CryptoVerifyCa_Pbkdf(atoi(argv[2]), g_PbkdfOut);
g_CA_PrintfBuffer(g_PbkdfOut, atoi(argv[2]));
}
if(0 == memcmp(argv[1], "rsa1024", 7))
{
TF("Entry rsa1024 operation\n");
/* rsa1024 + sign/verify/enc/dec + pcks1/nopadding */
l_Rsa_Test(argv[2], EN_KEY_1024, g_Output1024, 128);
if(0 == memcmp(argv[2], "verify", 6))
{
TF("The verify result is: %s\n", g_Output1024);
}
}
if(0 == memcmp(argv[1], "rsa2048", 7))
{
TF("Entry rsa1024 operation\n");
/* rsa1024 + sign/verify/enc/dec + pcks1/nopadding */
l_Rsa2048_Test(argv[2], EN_KEY_2048, g_Output2048, 256);
if(0 == memcmp(argv[2], "verify", 6))
{
TF("The verify result is: %s\n", g_Output2048);
}
}
return 0;
}
- CA_TEEC_InitializeContext_TEEC_OpenSession
static int l_CryptoVerifyCa_TaskInit(void)
{
TEEC_Result result;
int l_RetVal = OK;
/**1) Check if need to do task initialization operation */
if(false == g_TaskInitFlag)
{
result = TEEC_InitializeContext(NULL, &g_TaskContext);
if(result != TEEC_SUCCESS)
{
TF("InitializeContext failed, ReturnCode=0x%x\n", result);
l_RetVal= FAIL;
}
else
{
g_TaskInitFlag = true;
TF("InitializeContext success\n");
l_RetVal = OK;
}
}
return l_RetVal;
}
static int l_CryptoVerifyCa_OpenSession(TEEC_Session* session)
{
TEEC_Result result;
int l_RetVal = FAIL;
uint32_t origin;
result = TEEC_OpenSession(&g_TaskContext, session, &svc_id, TEEC_LOGIN_PUBLIC, NULL, NULL, &origin);
if(result != TEEC_SUCCESS)
{
TF("OpenSession failed, ReturnCode=0x%x, ReturnOrigin=0x%x\n", result, origin);
g_TaskInitFlag = false;
l_RetVal = FAIL;
}
else
{
TF("OpenSession success\n");
l_RetVal = OK;
}
return l_RetVal;
}
- TA_CreateEntryPoint
- TA_OpenSessionEntryPoint
- TEEC_InvokeCommand
static int l_CryptoVerifyCa_SendCommand(TEEC_Operation* operation, TEEC_Session* session, uint32_t commandID)
{
TEEC_Result result;
int l_RetVal = FAIL;
uint32_t origin;
result = TEEC_InvokeCommand(session, commandID, operation, &origin);
if (result != TEEC_SUCCESS)
{
TF("InvokeCommand failed, ReturnCode=0x%x, ReturnOrigin=0x%x\n", result, origin);
l_RetVal = FAIL;
}
else
{
TF("InvokeCommand success\n");
l_RetVal = OK;
}
return l_RetVal;
}
- TA_InvokeCommandEntryPoint
- TEEC_CloseSession & TEEC_FinalizeContext
int g_CryptoVerifyCa_Random(UINT32 len, CHAR* output)
{
TEEC_Session l_session; /* Define the session of TA&CA */
TEEC_Operation l_operation; /* Define the operation for communicating between TA&CA */
int l_RetVal = FAIL; /* Define the return value of function */
/**1) Initialize this task */
l_RetVal = l_CryptoVerifyCa_TaskInit();
if(FAIL == l_RetVal)
{
goto cleanup_1;
}
/**2) Open session */
l_RetVal = l_CryptoVerifyCa_OpenSession(&l_session);
if(FAIL == l_RetVal)
{
goto cleanup_2;
}
/**3) Set the communication context between CA&TA */
memset(&l_operation, 0x0, sizeof(TEEC_Operation));
l_operation.started = 1;
l_operation.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_OUTPUT,TEEC_NONE,
TEEC_NONE, TEEC_NONE);
l_operation.params[0].tmpref.size = len;
l_operation.params[0].tmpref.buffer = output;
/**4) Send command to TA */
l_RetVal = l_CryptoVerifyCa_SendCommand(&l_operation, &l_session, CMD_GEN_RANDOM_OPER);
if(FAIL == l_RetVal)
{
goto cleanup_3;
}
/**5) The clean up operation */
cleanup_3:
TEEC_CloseSession(&l_session);
cleanup_2:
TEEC_FinalizeContext(&g_TaskContext);
cleanup_1:
return l_RetVal;
}
- TA_CloseSessionEntryPoint
- TA_DestroyEntryPoint