diff --git a/zh-cn/application-dev/tee/Readme-CN.md b/zh-cn/application-dev/tee/Readme-CN.md new file mode 100644 index 0000000000000000000000000000000000000000..66cd2ba1c069708ba606178419a0eadbe6e78468 --- /dev/null +++ b/zh-cn/application-dev/tee/Readme-CN.md @@ -0,0 +1,5 @@ +# TEE Kit(可信执行环境开发套件) + +- [TEE Kit简介](tee-kit-overview.md) +- [CA-TA通信开发指导](ca-ta-development-guideline.md) +- [CA/TA特性开发指导](feature-development-guideline.md) \ No newline at end of file diff --git a/zh-cn/application-dev/tee/ca-ta-development-guideline.md b/zh-cn/application-dev/tee/ca-ta-development-guideline.md new file mode 100644 index 0000000000000000000000000000000000000000..03aecb18ae8cbadcb9ce876ce34f82dc3b0435f2 --- /dev/null +++ b/zh-cn/application-dev/tee/ca-ta-development-guideline.md @@ -0,0 +1,316 @@ +# CA-TA通信开发指导 + +可信应用的设计分为客户端应用和可信端应用,GlobalPlatform API标准定义(GP规范)定义了CA和TA之间的API接口,CA和TA按照规定的接口进行通信。典型客户端应用和可信端应用接口交互过程如下图所示: + +![CA-TA](figures/ca-ta.png) + +## CA-TA通信步骤 + +CA-TA的访问类似C/S架构,CA是client端,TA是server端,具体通信步骤如下: +1. CA首先调用TEEC_InitializeContext初始化TEE环境,建立与TEE的链接; +2. 根据TA的UUID,调用TEEC_OpenSession建立与TA的会话; +3. 调用TEEC_InvokeCommand向TA发送命令,可多次调用; +4. 调用TEEC_CloseSession结束与TA的会话; +5. 调用TEEC_FinalizeContext关闭与TEE的链接。 + +其中TEEC_InitializeContext和TEEC_FinalizeContext,还有TEEC_OpenSession和TEEC_CloseSession必须配对使用,即使在出现异常的情况下也要注意这一点,否则可能导致资源无法释放。 + +CA-TA之间支持同时建立多个session进行并发访问,前提是开发者的CA和TA在设计上都支持并发。如果开发者的CA需要请求多个TA的服务,也是可以的。 + +在调用TEEC_OpenSession或TEEC_InvokeCommand时,CA需要通过operation参数向TA发送数据。Operation参数的类型为结构体TEEC_Operation,定义在“tee_client_type.h”中,其参数格式如下: +```C +typedef struct { + uint32_t started; + uint32_t paramTypes; + TEEC_Parameter params[4]; + TEEC_Session *session; + bool cancel_flag; +} TEEC_Operation; +``` + +* 数据成员uint32_t started:为0表示取消操作,非0表示发送命令。目前仅支持非0,建议赋值为1即可。 +* 数据成员uint32_t paramTypes:该成员与下一个成员TEEC_Parameter params[4]是对应的,我们在一次访问中支持4组数据的传递,这4组数据可以分别是不同的类型,类型取值参考枚举TEEC_ParamType。开发者需要把4组数据的类型组装到paramTypes中,我们提供了宏TEEC_PARAM_TYPES来完成组装。举例如下: + +```C +paramTypes = TEEC_PARAM_TYPES(TEEC_VALUE_INPUT, + TEEC_VALUE_OUTPUT, + TEEC_NONE, + TEEC_NONE); +``` + +该示例表示,第一组数据作为输入,第二组数据作为输出,其数据类型均为TEEC_Value类型,第三组和第四组数据没有使用。 + * 数据成员TEEC_Parameter params[4]:用来传递数据,支持4组数据的传递,这4组数据的类型需要与上个成员paramTypes中定义的类型一致。TEEC_Parameter是一个枚举类型,格式如下: + +```C +typedef union { + TEEC_TempMemoryReference tmpref; + TEEC_RegisteredMemoryReference memref; + TEEC_Value value; + TEEC_IonReference ionref; +} TEEC_Parameter; +``` + +该枚举定义了CA向TA传递数据的几种方式: +1. TEEC_Value:该类型只有两个uint32的空间,用于传递微量数据。 +2. TEEC_TempMemoryReference:CA创建一段临时缓冲区,使用该临时缓冲区传递数据到TEE。 +3. TEEC_RegisteredMemoryReference:CA在发送命令之前申请一段共享内存,通过共享内存将数据传递到TEE,这种方式可以减少内存的拷贝次数,在传递大量数据时可以提高效率。 +4. TEEC_IonReference:使用ION内存,这是一种特殊场景,并不具备普遍适用性。 + +关于TEEC_RegisteredMemoryReference这种方式,我们提供了获取共享内存的接口。获取共享内存的方式有以下两种: +1. 调用接口TEEC_RegisterSharedMemory,注册一块共享内存。这要求CA自己先申请一块内存,然后调用该接口将内存注册到TEE中,调用接口时需传入内存的地址。之后CA在发送命令时便可以使用该内存传递数据。 +2. 调用接口TEEC_AllocateSharedMemory,申请一块共享内存。采用这种方式,CA只需要传入申请内存的大小,而无需自己申请内存,TEE会为CA分配指定大小的内存。之后CA在发送命令时可以使用该内存传递数据。 + +CA在访问结束后,需要释放共享内存,对于上述两种方式,我们提供了同一个内存释放接口TEEC_ReleaseSharedMemory。对于方式1,该接口会解除TEE中的内存注册关系,但是不会释放CA自己申请的内存,CA需要自己释放。对于方式2,该接口会释放共享内存,CA无需自己释放。 + +共享内存相关函数接口定义在“tee_client_api.h”中,在使用前建议先阅读头文件注释。 + +> 注意: +> 1. 在调用TEEC_OpenSession接口时,params[2]和params[3]是预留给系统的,不允许CA使用,CA仅可以使用params[0]和params[1]。在调用TEEC_InvokeCommand接口时,则没有限制。 +> 2. 系统总共可用CA访问TA共享内存4M,无CA并发访问TEE时,限制为:单个TEEC_Parameter中buffer 最大1M,所有buffer总和需小于4M。 +> 3. TEEC_InvokeCommand接口支持传递为NULL的tempref buffer。 +> 4. TA默认加载路径与TA2TA无感知加载TA默认路径为: "/vendor/bin"。 + +## CA开发示例 + +CA开发示例如下: +```C +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tee_client_api.h" +#define VERSION_BUFFER_SIZE 256 +#define OPERATION_START_FLAG 1 +#define OUT_BUFF_INDEX 3 +static const TEEC_UUID g_demoTemplateUuid = { + 0xe3d37f4a, 0xf24c, 0x48d0, { 0x88, 0x84, 0x3b, 0xdd, 0x6c, 0x44, 0xe9, 0x88 } +}; +enum { + CMD_GET_TA_VERSION = 1, +}; +int main(void) +{ + TEEC_Context context = {0}; + TEEC_Session session = {0}; + TEEC_Result result; + TEEC_Operation operation = {0}; + uint32_t origin = 0; + char versionBuf[VERSION_BUFFER_SIZE] = {0}; + unsigned int bufLen = VERSION_BUFFER_SIZE; + /* 1.初始化TEE环境 */ + result = TEEC_InitializeContext(NULL, &context); + if (result != TEEC_SUCCESS) { + printf("teec initial failed"); + goto cleanup_1; + } + operation.started = OPERATION_START_FLAG; + operation.paramTypes = TEEC_PARAM_TYPES( + TEEC_NONE, + TEEC_NONE, + TEEC_NONE, + TEEC_NONE); + /* 2.与TA建立会话 */ + result = TEEC_OpenSession( + &context, &session, &g_demoTemplateUuid, TEEC_LOGIN_IDENTIFY, NULL, &operation, &origin); + if (result != TEEC_SUCCESS) { + printf("teec open session failed"); + goto cleanup_2; + } + operation.started = OPERATION_START_FLAG; + operation.paramTypes = TEEC_PARAM_TYPES( + TEEC_NONE, + TEEC_NONE, + TEEC_NONE, + TEEC_MEMREF_TEMP_OUTPUT); + operation.params[OUT_BUFF_INDEX].tmpref.buffer = versionBuf; + operation.params[OUT_BUFF_INDEX].tmpref.size = bufLen; + /* 3.向TA发送命令 */ + result = TEEC_InvokeCommand(&session, CMD_GET_TA_VERSION, &operation, &origin); + if (result != TEEC_SUCCESS) { + printf("invoke failed, codes=0x%x, origin=0x%x", result, origin); + } else { + printf("Succeed to load TA, TA's version: %s.\n", versionBuf); + } + /* 4.结束与TA会话 */ + TEEC_CloseSession(&session); +cleanup_2: + /* 5.关闭与TEE的链接 */ + TEEC_FinalizeContext(&context); +cleanup_1: + return 0; +} +``` + + +## TA开发示例 + +TA开发示例 +TA入口函数按照GP规范定义如下: +| TA 入口函数 | 函数描述 | +|-----------------------------|--------------------------------------------------------------------------| +| TA_CreateEntryPoint | TA 实例的构造函数,每个 TA 实例的生命周期中只被调用一次。 | +| TA_DestroyEntryPoint | TA 实例的析构函数,TEE 系统在销毁 TA 实例时调用此函数。 | +| TA_OpenSessionEntryPoint | 在 CA 请求创建一个与 TA 的会话时,TEE 系统会调用此函数来创建一个会话。 | +| TA_CloseSessionEntryPoint | 在 CA 发起关闭与 TA 的会话连接时,TEE 系统会调用此函数。 | +| TA_InvokeCommandEntryPoint | 在 CA 发送指令给 TA 时,需要指定之前创建成功的会话,TEE 系统收到请求后会调用此函数。 | + +TA的这些入口函数必须按照如下来定义,否则TEE里加载TA二进制文件会有异常。 +### TA_CreateEntryPoint +TA实例的构造函数,每个TA实例的生命周期中只被调用一次。如果函数执行失败,则表示创建TA实例失败。通常在CA请求创建会话时,如果TA实例不存在,TEE系统会调用此函数创建TA实例。
+ +TEE_Result TA_CreateEntryPoint(void)
+ +参数:无
+ +返回值:
+* TEE_SUCCESS:TA实例创建成功。 +* 其他:TA实例创建失败,具体原因参考错误码说明。 + +### TA_DestroyEntryPoint +TA实例的析构函数,TEE系统在销毁TA实例时调用此函数。通常在所有会话都关闭后TEE系统会调用此函数销毁TA实例。 + +void TA_DestroyEntryPoint(void)
+ +参数:无
+ +返回值:
+ +### TA_OpenSessionEntryPoint +在CA请求创建一个与TA的会话时,TEE系统会调用此函数来创建一个会话。后续CA与TA的通信都需要基于这个创建成功的会话来完成。用于标识一个会话的结构是由TA自己进行申请的,并在函数执行成功后返回给调用者。
+ +TEE_Result TA_OpenSessionEntryPoint(uint32_t paramTypes, TEE_Param params[4], void** sessionContext) + +参数: +* paramTypes:四个参数属性值。 +* params:指向四个参数数组的指针。 +* sessionContext:指向会话的二级指针。 + +返回值: +* TEE_SUCCESS:TA打开session成功。 +* 其他:指令执行失败,具体原因参考错误码说明。 + + +### TA_CloseSessionEntryPoint +在CA发起关闭与TA的会话连接时,TEE系统会调用此函数。TA需要释放用于此会话的资源。 + +void TA_CloseSessionEntryPoint(void* session_context)
+ +参数: +* Session_context:指向会话的指针。
+ +返回值:无 + +### TA_InvokeCommandEntryPoint +在CA发送指令给TA时,需要指定之前创建成功的会话,TEE系统收到请求后会调用此函数。TA根据指令ID和参数信息做相应的处理。 +TEE_Result TA_InvokeCommandEntryPoint(void* session_context, uint32_t cmd_id, uint32_t paramTypes, TEE_Param params[4]) +参数: + * session_context:指向会话的指针。 + * cmd_id:由TA自己定义的指令ID。 + * paramTypes:四个参数属性值。 + * params:指向四个参数数组的指针。 +返回值: + * TEE_SUCCESS:TA执行指令成功。 + * 其他:指令执行失败,具体原因参考错误码说明。 + +TA开发示例如下: + +```c +#include +#include +#include +#define TA_TEMPLATE_VERSION "demo_20200601" +#define PARAM_COUNT 4 +#define OUT_BUFFER_INDEX 3 +enum { + CMD_GET_TA_VERSION = 1, +}; +static TEE_Result get_ta_version(char* buffer, size_t *buf_len) +{ + const char *version = TA_TEMPLATE_VERSION; + if (*buf_len < strlen(version) + 1) { + tloge("buffer is too short for storing result"); + *buf_len = strlen(version) + 1; + return TEE_ERROR_SHORT_BUFFER; + } + errno_t err = strncpy_s(buffer, *buf_len, version, strlen(version) + 1); + if (err != EOK) + return TEE_ERROR_SECURITY; + *buf_len = strlen(version) + 1; + return TEE_SUCCESS; +} +/* TA构造函数 */ +TEE_Result TA_CreateEntryPoint(void) +{ + TEE_Result ret; + tlogd("----- TA entry point ----- "); + tlogd("TA version: %s", TA_TEMPLATE_VERSION); + ret = AddCaller_CA_exec("/vendor/bin/teec_hello", 0); + if (ret == TEE_SUCCESS) { + tlogd("TA entry point: add ca whitelist success"); + } else { + tloge("TA entry point: add ca whitelist failed"); + return TEE_ERROR_GENERIC; + } + return TEE_SUCCESS; +} +/* CA打开与TA会话时,框架会调用此接口 */ +TEE_Result TA_OpenSessionEntryPoint(uint32_t parm_type, + TEE_Param params[PARAM_COUNT], void** session_context) +{ + (void)parm_type; + (void)params; + (void)session_context; + tlogd("---- TA open session -------- "); + return TEE_SUCCESS; +} +/* TEE侧针对CA侧命令字响应函数 */ +TEE_Result TA_InvokeCommandEntryPoint(void* session_context, uint32_t cmd, + uint32_t parm_type, TEE_Param params[PARAM_COUNT]) +{ + TEE_Result ret; + (void)session_context; + tlogd("---- TA invoke command ----------- "); + switch (cmd) { + case CMD_GET_TA_VERSION: + if (!check_param_type(parm_type, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_MEMREF_OUTPUT)) { + tloge("Bad expected parameter types"); + return TEE_ERROR_BAD_PARAMETERS; + } + if (params[OUT_BUFFER_INDEX].memref.buffer == NULL || + params[OUT_BUFFER_INDEX].memref.size == 0) { + tloge("InvokeCommand with bad, cmd is %u", cmd); + return TEE_ERROR_BAD_PARAMETERS; + } + ret = get_ta_version(params[OUT_BUFFER_INDEX].memref.buffer, ¶ms[OUT_BUFFER_INDEX].memref.size); + if (ret != TEE_SUCCESS) { + tloge("InvokeCommand Failed 0x%x. cmd is %u", ret, cmd); + return ret; + } + break; + default: + tloge("Unknown cmd is %u", cmd); + ret = TEE_ERROR_BAD_PARAMETERS; + } + return ret; +} +/* CA关闭与TA会话时,TEE侧关闭会话 */ +void TA_CloseSessionEntryPoint(void* session_context) +{ + (void)session_context; + tlogd("---- close session ----- "); +} +/* TA析构函数 */ +void TA_DestroyEntryPoint(void) +{ + tlogd("---- destroy TA ---- "); +} +``` diff --git a/zh-cn/application-dev/tee/feature-development-guideline.md b/zh-cn/application-dev/tee/feature-development-guideline.md new file mode 100644 index 0000000000000000000000000000000000000000..4f35c69218961a84a5d05874c523a375b0b9bd7b --- /dev/null +++ b/zh-cn/application-dev/tee/feature-development-guideline.md @@ -0,0 +1,814 @@ +# CA/TA特性开发指导 + +- [CA鉴权](#CA鉴权) +- [主动防护度量](#主动防护度量) +- [TA多线程支持](#TA多线程支持) +- [SE(安全器件)](#SE(安全器件)) +- [安全存储](#安全存储) +- [独立服务开发](#独立服务开发) + +## CA鉴权 +由于CA位于REE侧,TA位于TEE侧,因此CA是不可信的。为了防止恶意CA攻击安全OS,需要对访问安全OS的CA进行一系列认证,以识别合法CA,拦截恶意CA,这个过程叫CA鉴权。 + +### TEE hash鉴权 +**添加鉴权方法** +1. 配置ca鉴权文件 +− vendor侧CA:vendor/huaweiplatform/itrustee/itrustee_build_tools/ca_signtools/data/vendor_native_ca.xml +− system侧CA:vendor/huaweiplatform/itrustee/itrustee_build_tools/ca_signtools/data/system_native_ca.xml +2. 准备待签名文件 +− native_packages.xml:从手机上拉取对应的native_packages.xml文件并放到指定工程目录 +* vendor侧CA:/vendor/etc/native_packages.xml -----> out/vendor/etc/ +* system侧CA:/system/etc/native_packages.xml -----> out/system/etc/ +− ca二进制:放到对应目录下,确保与xxx_native_ca.xml文件中path路径配置保证一致 +* vendor侧CA: out/vendor/bin/ +* system侧CA: out/system/bin/ +3. 执行签名 +− vendor侧CA:./vendor/huaweiplatform/itrustee/itrustee_build_tools/ca_signtools/transXmlToBin.py vendor out/ tools/signcenter/NativeCASign.jar +− system侧CA:./vendor/huaweiplatform/itrustee/itrustee_build_tools/ca_signtools/transXmlToBin.py system out/ tools/signcenter/NativeCASign.jar +4. 替换native_packages.xml到手机,并重启: +− vendor侧CA:out/vendor/etc/native_packages.xml——》/vendor/etc/native_packages.xml +− system侧CA:out/system/etc/native_packages.xml——》/system/etc/native_packages.xml + +### TA caller鉴权 +添加CA鉴权方法:在TA源码中的TA_CreateEntryPoint接口内,调用AddCaller_CA接口来添加caller权限。 +AddCaller_CA +TEE_Result AddCaller_CA(const uint8_t *cainfo_hash, uint32_t length); +1. 功能: +在TA里添加CA鉴权白名单。 +2. 参数: +− cainfo_hash:CA info hash值; +− length:CA info hash字节长度,取值需为SHA256_DIGEST_LENGTH(32U); +3. 返回值:TEE_SUCCESS为添加成功,其他值为异常。 +4. CA info hash计算方法: +− 工具路径:itrustee_sdk/tools/ca_auth_hash_tools +− 配置需要计算ca info信息:在ca_caller_info.xml文件里配置添加需要计算的ca info信息,示例如下: +```xml + + + + + + +``` +− 执行脚本:python3 calc_ca_caller_hash.py + +5. Native CA TA_caller鉴权示例: +```c +#define SHA256_DIGEST_LENGTH 32 +static uint8_t g_caller_hash[][SHA256_DIGEST_LENGTH] = { + /* cmdine = "/vendor/bin/teec_hello", uid = 0 */ + { 0xca, 0x9f, 0x5e, 0xd7, 0x6d, 0x07, 0x0d, 0x66, 0xe7, 0xb2, 0xab, 0xb3, 0x55, 0xfc, 0xb0, 0xbf, + 0xc8, 0x16, 0x52, 0x37, 0x5f, 0xfe, 0x99, 0xfc, 0x34, 0x43, 0xf6, 0x5f, 0x0c, 0x70, 0x44, 0x48 }, +}; + +TEE_Result TA_CreateEntryPoint(void) +{ + TEE_Result ret; + for (uint32_t i = 0; i < sizeof(g_caller_hash) / SHA256_DIGEST_LENGTH; i++) { + ret = AddCaller_CA(g_caller_hash[i], SHA256_DIGEST_LENGTH); + if (ret != 0) + return ret; + } + return TEE_SUCCESS; +} +``` + + + + +## 主动防护度量 +### 概述 +主动防护度量是通过软硬件独立运行的MSPC独立核安全系统实时度量保护关键系统组件的运行环境,通过按需度量和周期度量关键资源(只读代码段、rodata)来检测和防御非法入侵: +* 安全OS主动防护分层度量框架:由MSPC对TEEOS关键模块(kernel、sysmgr、filemgr的代码段和rodata)做度量,由安全OS主动防护服务apm service对关键业务TA(TA的代码段和rodata)做运行时度量,保证业务TA运行时安全可信。 +* TA主动防护度量流程:开启主动防护度量的TA在首次加载时apm service会建立该TA的度量基线,TA invoke运行期间apm service会启动度量,度量异常时按对应TA的威胁处理策略做异常处理。 + +### 使用说明 +1. TA使能主动防护度量配置方式 +在TA的manifest.txt文件新增配置gpd.ta.apm_level字段,字段取值说明如下表。 + +| 字段 | 描述 | +|--------------------------|----------------------------------------------------------------------------------------------| +| `gpd.ta.apm_level: 0` | TA关闭主动防护度量。 | +| `gpd.ta.apm_level: 1` | TA开启主动防护度量,度量异常时威胁处理策略为弱策略,弱策略为只打印异常日志不修改TA invoke返回值。 | +| `gpd.ta.apm_level: 2` | CPRO 车机平台:TA开启主动防护度量,度量异常时威胁处理策略为强策略,强策略为打印异常日志且修改TA invoke返回值为`TEE_ERROR_APM`错误码;
CLT 121及后续平台:同 `gpd.ta.apm_level: 1` 功能一致。 | +| `gpd.ta.apm_level: xxx` | 配置为其他值时属于无效值,TA运行会报错。 | +| 无 `gpd.ta.apm_level` 字段 | 默认TA关闭主动防护度量。 | + + +2. 主动防护度量失败错误码 +TEE_ERROR_APM = 0xFFFF9115 +### 限制与约束 +* 平台约束 +该功能仅向Harmony OS Next版本华为内部开发者开放。 +* 使用限制 + + a. 开启主动防护度量的TA数量上限为8个,新增开启主动防护度量的TA需要通知itrustee; + + b. 只支持通过ROM升级的TA使能主动防护度量,不支持单独升级的TA使能主动防护度量(不支持更新TA度量基线)。 + +查询度量报告使用指导: +TEE提供接口支持查询TEEOS对TA的度量报告。 +TEE提供接口支持TA查询MPSC对所有子系统(TEEOS\BL2等)度量报告。 + +权限配置: +查询度量报告接口权限配置方式:TA需要在manifest.txt中增加如下配置,未配置权限时TA查询度量报告会报TEE_MEASURE_ERROR_PERMISSION_DENY(0x00000005)错误码。 +gpd.ta.apm_perm: true + +查询TA度量报告: +开发者可通过调用tee_query_ta_measure_report()查询TA调度报告。 + +查询MSPC度量报告: +开发者可通过调用tee_query_mspc_measure_report()查询MSPC度量报告。 + +## TA多线程支持 + +### 标准Posix接口 +| 序号 | 接口名称 | +|------|-----------------------------| +| 1 | pthread_create | +| 2 | pthread_join | +| 3 | pthread_attr_init | +| 4 | pthread_attr_setstack | +| 5 | pthread_attr_setstacksize | +| 6 | pthread_cond_broadcast | +| 7 | pthread_cond_destroy | +| 8 | pthread_cond_init | +| 9 | pthread_cond_signal | +| 10 | pthread_cond_wait | +| 11 | pthread_mutexattr_destroy | +| 12 | pthread_mutexattr_init | +| 13 | pthread_mutexattr_settype | +| 14 | pthread_mutex_init | +| 15 | pthread_mutex_lock | +| 16 | pthread_mutex_trylock | +| 17 | pthread_mutex_unlock | +| 18 | pthread_mutex_destroy | +| 19 | pthread_key_create | +| 20 | pthread_key_delete | +| 21 | pthread_getspecific | +| 22 | pthread_setspecific | +| 23 | pthread_equal | +| 24 | pthread_once | +| 25 | pthread_self | + +### 扩展接口 +**pthread_attr_settee**
+```int pthread_attr_settee(pthread_attr_t *a, int ca, int task_id, int shadow);``` +1. 功能: +设置线程并行属性。 +2. 参数: + +| 方向 | 类型 | 名称 | 说明 | +|-------|----------------|----------|--------------------------------------------------------------| +| in | int | ca | 默认为 `TEESMP_THREAD_ATTR_CA_INHERIT` | +| in | int | task_id | 默认为 `TEESMP_THREAD_ATTR_TASK_ID_INHERIT` | +| in | int | shadow | 默认为 `TEESMP_THREAD_ATTR_HAS_SHADOW`,可为该线程分配一个新核 | +| out | pthread_attr_t*| a | 线程属性 | +| return| int | - | `EINVAL`:操作失败,a == NULL
`0`:成功 | + +注:在TA调用pthread_create创建线程,建议调用pthread_attr_settee设置影子线程属性,让TEE拉入一个新的核,并行跑创建的线程,用法见1.3.8.2 TA多线程限制。 + +### TA多线程限制 +1. 在TA中创建的thread不能使用所有的agent服务以及TEE_Wait。其中hm native和posix兼容的是可以使用的,但sleep例外(因为其内部是用TEE_wait实现的)。 +2. 创建线程时,只有添加TEESMP_THREAD_ATTR_HAS_SHADOW属性,才会为本线程拉入一个新的核,提供新的计算能力,否则容易出现hang的现象。代码如下: +```c +#define TEESMP_THREAD_ATTR_CA_INHERIT (-1U) +#define TEESMP_THREAD_ATTR_TASK_ID_INHERIT (-1U) +#define TEESMP_THREAD_ATTR_HAS_SHADOW 0x1 +pthread_attr_init(&attr); +pthread_attr_settee(&attr,TEESMP_THREAD_ATTR_CA_INHERIT, TEESMP_THREAD_ATTR_TASK_ID_INHERIT, TEESMP_THREAD_ATTR_HAS_SHADOW); +pthread_create(&thread, &attr, thread_func, i) +``` +### TA shadow线程优先级设置 +int spi_notify_set_shadow_priority(uint32_t priority_ree); +1. 功能:支持TA调用该接口设置shadow线程优先级 +2. 参数: +priority_ree:新的优先级(这个优先级是REE侧的,而非TEE侧的),取值范围20到41,20为normal,41为vip,数值越大优先级越高。 +3. 返回值: +成功返回0 +此接口只用于设置shadow线程优先级,TA如果调用此接口设置非shadow线程优先级,则报错(返回TEE_ERROR_ACCESS_DENIED) +priority_ree入参不在[20,41]范围内,则报错(返回TEE_ERROR_BAD_PARAMETERS ) +其他错误返回-1 +4. 接口所在头文件:tee_notify_set_priority.h +5. 参考代码 +```c +pthread_create(&thread, &attr, thread_func, NULL); +void *thread_func(void *arg) +{ + uint32_t priority_ree = 41u; + int ret = spi_notify_set_shadow_priority(priority_ree); + tlogi("call spi_notify_set_shadow_priority, ret=%d\n", ret); +} +``` + +## SE(安全器件) + +安全器件(SE)连接在TEE上,对于连接到TEE的SE的访问,只能通过TA来进行,由于不通过REE,因此这种TA与SE的通信被认为是可信的。 +### 限制与约束 +目前Harmony OS Next版本支持该特性,具体硬件平台支持情况可咨询iTrustee华为工程师。 +### 开发步骤 +SE API接口主要分为以下四个层级。 +| 类名(Class) | 描述 | +|---------------------|----------------------------------------------------------------------| +| SEService class | SE API 的总入口,要访问 SE,必须首先获取 SEService 的访问权限。 | +| SEReader class | 在 SEService 下获取的 reader handle,通过它才能建立 SE 的连接。 | +| SESession class | 获取 SE 的 ATR,并且打开 SE 通道。 | +| SEChannel class | 通过 channel 向 SE 发送 APDU。 | + + +一个安全器件访问主要开发步骤如下: +1. 首先通过TEE_SEServiceOpen获取一个访问安全元件的Service Handle; +2. 通过TEE_SEServiceGetReaders获取当前安全元件的可用Reader; +3. 通过TEE_SESessionOpenLogicalChannel打开对应session指向的下一个逻辑通道并调用TEE_SEChannelTransmit发送消息; +4. 消息发送成功后,通过TEE_SEChannelClose关闭channel指向的通道; +5. 调用TEE_SESessionCloseChannels关闭session指向连接的所有通道,并通过TEE_SESessionClose关闭session指向的连接; +6. 通过TEE_SEReaderCloseSessions关闭reader所指向的安全元器件建立的所有连接; +7. 最后通过TEE_SEServiceClose关闭已打开的访问安全元件的service。 + +以下为示例代码: +```c +#include "tee_internal_se_api.h" +#include "tee_log.h" +#include +int use_which_se = 0; +int SE_test_demo(void) +{ + TEE_SEServiceHandle service = NULL; + TEE_SEReaderHandle reader[6] = {0}; + uint32_t reader_len = 6; + TEE_SESessionHandle session; + TEE_SEChannelHandle channel; + /* 获取一个访问安全元件的Service Handle */ + if (TEE_SEServiceOpen(&service)) { + tloge("open se service failed\n"); + return -1; + } + /* 获取当前安全元件的可用的reader */ + if (TEE_SEServiceGetReaders(service, reader, &reader_len) != TEE_SUCCESS) + goto clean; + /* 与reader指向的安全元件建立一个连接 */ + if (TEE_SEReaderOpenSession(reader[use_which_se], &session)) { + tloge("open session failed\n"); + goto clean; + } + uint8_t ppse_id[] = {0x32, 0x50, 0x41, 0x59, 0x2E, 0x53, 0x59, 0x53, 0x2E, 0x44, 0x44, 0x46, 0x30, 0x31}; + uint8_t crs_id[] = {0xA0, 0x00, 0x00, 0x01, 0x51, 0x43, 0x52, 0x53, 0x00}; + uint8_t ara_m_id[] = {0xA0, 0x00, 0x00, 0x01, 0x51, 0x41, 0x43, 0x4C, 0x00}; + uint8_t main_s_id[] = {0xA0, 0x00, 0x00, 0x01, 0x51, 0x00, 0x00, 0x00}; + TEE_SEAID aids[] = { + {ppse_id, sizeof(ppse_id)}, + {crs_id, sizeof(crs_id)}, + {ara_m_id, sizeof(ara_m_id)}, + {main_s_id, sizeof(main_s_id)} + }; + unsigned char hex_cmd[] = {0x00, 0xA4, 0x04, 0x00, 0x08, 0xA0, 0x00, 0x00, 0x01, 0x51, 0x00, 0x00, 0x00}; + char rsp[256] = {0}; + unsigned int rsp_len = 256; + char cmd_suc[20] = {0x6F, 0x10, 0x84, 0x08, 0xA0, 0x00, 0x00, 0x01, 0x51, 0x00, 0x00, 0x00, 0xA5, 0x04, + 0x9F, 0x65, 0x01, 0xFF, 0x90, 0x00}; + int i; + for (i = 0; i < 4; i++) { + /* 打开session指向的连接下的一个逻辑通道 */ + if (!TEE_SESessionOpenLogicalChannel(session, &aids[i], &channel)) { + tlogd("open logical channel %d success\n", i); + goto transmit; + } + tloge("open logical channel %d failed\n", i); + } + goto clean; +transmit: + /* 通过channel指向的通道发送命令 */ + if (TEE_SEChannelTransmit(channel, hex_cmd, sizeof(hex_cmd), rsp, &rsp_len)) { + tloge("send test cmd to logical channel failed\n"); + goto clean; + } + if (memcmp(rsp, cmd_suc, sizeof(cmd_suc))) { + tloge("test cmd receive wrong rsp\n"); + goto clean; + } + /* 关闭channel指向的通道 */ + TEE_SEChannelClose(channel); + tlogd("close channel in session\n"); + /* 关闭session指向的连接下的所有通道 */ + TEE_SESessionCloseChannels(session); + tlogd("close channels in session\n"); + /* 关闭session指向的连接 */ + TEE_SESessionClose(session); + + /* 关闭reader指向的安全元件建立的所有连接 */ + if (reader[use_which_se]) + TEE_SEReaderCloseSessions(reader[use_which_se]); + /* 关闭已经打开的访问安全元件的service */ + if (service) + TEE_SEServiceClose(service); + tloge("%s --- \n", __FUNCTION__); + return TEE_SUCCESS; +clean: + if (reader[use_which_se]) + TEE_SEReaderCloseSessions(reader[use_which_se]); + if (service) + TEE_SEServiceClose(service); + return -1; +} +``` + + + +## 安全存储 +安全存储可以用于加密保存以下两种形式的数据: +1. 恢复出厂设置时不会被删除的数据,该类数据一般保存在persistent分区(/sec_storage),该分区需要开发者自行适配; +2. TA运行时生成,关机后还需要保存、但恢复出厂设置会被清除的数据,通常存储在data分区(/data/service/el1/public/sec_storage_data)。 +在指定storageID为TEE_OBJECT_STORAGE_CE时,上述两类数据也可以保存在CE类路径(data/vendor_ce)下。 +对于利用安全存储保存的数据,都需要开发具体的TA才能实现数据的保存。对于数据的来源视方案设计而定,可以是由REE的CA传入给TA,再由TA调用安全存储接口保存;也可以是TA内部生成再保存。对于需要在产线上写入的数据,同样也需要有TA配合单板软件和装备工具一起实现相应功能。 + +### 存储密钥样例 +**实例场景** +* 为特定密钥申请内存空间 +* 使用随机数生成函数来生成密钥信息对称加解密 +* 创建永久Object用于存储密钥 + +**实例步骤** +1. 为AES密钥分配存储空间,AES密钥最长长度为32字节; +2. 调用随机数生成函数生成密钥信息,长度为32字节的随机数组; +3. 将密钥信息存入临时内存空间中,用于密钥存取的验证; +4. 创建永久Object,并将密钥存入物理介质中; +5. 打开存储密钥的Object; +6. 将读取的密钥与之前已存储的密钥进行比较,如果相同则证明存储成功; +7. 关闭并删除存储AES密钥的Object。 + +**实例代码** +```c +#include +#include "tee_defines.h" +#include "tee_log.h" +#include "tee_object_api.h" +#include "tee_trusted_storage_api.h" +#include "tee_mem_mgmt_api.h" +#include "tee_crypto_api.h" +TEE_Result store_key_sample(void) +{ + uint32_t storageID = TEE_OBJECT_STORAGE_PRIVATE; + TEE_ObjectHandle transient_key = NULL; + TEE_ObjectHandle persistent_key = NULL; + TEE_Result ret; + uint32_t w_flags = TEE_DATA_FLAG_ACCESS_WRITE; + uint32_t r_flags = TEE_DATA_FLAG_ACCESS_READ; + void *objectID = "store_key_sample.txt"; + void *aes_key = NULL; + tlogd("store_key_sample start:============\n"); + /* 为密钥分配存储空间 */ + ret = TEE_AllocateTransientObject(TEE_TYPE_AES, 0x00000800, (&transient_key)); + if (ret != TEE_SUCCESS) { + tloge("Failed to execute TEE_AllocateTransientObject:ret = 0x%x\n", ret); + return ret; + } + TEE_GenerateRandom(transient_key->Attribute->content.ref.buffer, transient_key->Attribute->content.ref.length); + /* 将密钥信息存入临时内存空间中 */ + aes_key = TEE_Malloc(transient_key->Attribute->content.ref.length, 0); + if (aes_key == NULL) { + tloge("malloc fail, len:%u", transient_key->Attribute->content.ref.length); + TEE_FreeTransientObject(transient_key); + return TEE_ERROR_OUT_OF_MEMORY; + } + int32_t rc = memmove_s(aes_key, transient_key->Attribute->content.ref.length, + transient_key->Attribute->content.ref.buffer, + transient_key->Attribute->content.ref.length); + if (rc != 0) { + tloge("memmove fail"); + TEE_Free(aes_key); + TEE_FreeTransientObject(transient_key); + return TEE_ERROR_SECURITY; + } + /* 创建永久Object:密钥存储Object persistent_key */ + ret = TEE_CreatePersistentObject(storageID, objectID, strlen(objectID), w_flags, transient_key, NULL, 0, + (&persistent_key)); + if (ret != TEE_SUCCESS) { + tloge("Failed to create object:ret = 0x%x\n", ret); + TEE_Free(aes_key); + TEE_FreeTransientObject(transient_key); + return ret; + } + TEE_CloseObject(persistent_key); + TEE_FreeTransientObject(transient_key); + /* 打开前面创建的存储密钥的Object */ + ret = TEE_OpenPersistentObject(storageID, objectID, strlen(objectID), + r_flags | TEE_DATA_FLAG_ACCESS_WRITE_META, (&persistent_key)); + if (ret != TEE_SUCCESS) { + tloge("Failed to execute TEE_OpenPersistentObject:ret = %x\n", ret); + TEE_Free(aes_key); + return ret; + } + /* 判断读入的密钥是否正确 */ + if (TEE_MemCompare(aes_key, persistent_key->Attribute->content.ref.buffer, + persistent_key->Attribute->content.ref.length)) { + tloge("The persistent_key stored is broken.\n"); + TEE_Free(aes_key); + return TEE_FAIL; + } + /* 删除存储AES密钥的永久Object */ + TEE_CloseAndDeletePersistentObject1(persistent_key); + TEE_Free(aes_key); + return TEE_SUCCESS; +} +``` + +**用到的资源** + +使用安全存储相关接口以及内存申请相关接口时需用到如下头文件:tee_mem_mgmt_api.h、tee_trusted_storage_api.h、tee_crypto_api.h、tee_internal_api.h。 +存储密钥的类型仅支持GlobalPlatform所列类型,如下表。不支持的密钥可做为普通数据存储(存储方法见下节存储文件样例)。 + +| Object Type | Possible Object Sizes | +|---------------------------|----------------------------------------------------------------------------------------| +| TEE_TYPE_AES | 128, 192, or 256 bits | +| TEE_TYPE_DES | Always 56 bits | +| TEE_TYPE_DES3 | 112 or 168 bits | +| TEE_TYPE_HMAC_MD5 | Between 64 and 512 bits, multiple of 8 bits | +| TEE_TYPE_HMAC_SHA1 | Between 80 and 512 bits, multiple of 8 bits | +| TEE_TYPE_HMAC_SHA224 | Between 112 and 512 bits, multiple of 8 bits | +| TEE_TYPE_HMAC_SHA256 | Between 192 and 1024 bits, multiple of 8 bits | +| TEE_TYPE_HMAC_SHA384 | Between 256 and 1024 bits, multiple of 8 bits | +| TEE_TYPE_HMAC_SHA512 | Between 256 and 1024 bits, multiple of 8 bits | +| TEE_TYPE_RSA_PUBLIC_KEY | Object size is the number of bits in the modulus. All key sizes up to 2048 bits must be supported. Support for bigger key sizes is implementation-dependent. Minimum key size is 256 bits. | +| TEE_TYPE_RSA_KEYPAIR | Same as for RSA public key size | +| TEE_TYPE_DSA_PUBLIC_KEY | Between 512 and 1024 bits, multiple of 64 bits | +| TEE_TYPE_DSA_KEYPAIR | | +| TEE_TYPE_DH_KEYPAIR | From 256 to 2048 bits | +| TEE_TYPE_GENERIC_SECRET | Multiple of 8 bits, up to 4096 bits. This type is intended for secret data that is not directly used as a key in a cryptographic operation, but participates in a key derivation. | + + +### 存储文件样例 +**实例场景** +* 创建永久Object,为永久Object分配内存空间。 +* 将数据存入永久Object中,并写入物理介质。 +* 打开永久Object,将写入的数据读出,判断是否写入成功。 + +**实例步骤** +1. 通过TEE_CreatePersistentObject创建永久Object,申请内存空间,并通过TEE_WriteObjectData将数据写入永久Object,即可写入物理介质中; +2. 通过TEE_OpenPersistentObject打开已经创建的文件,为打开的永久Object分配内存空间; +3. 通过TEE_ReadObjectData读取存储的数据,并与之前写入的进行对比; +4. 如果相同,则证明写入成功; +5. 通过TEE_CloseObject关闭并删除已存储的数据文件。 + +**实例代码** + +```c +#include +#include "tee_defines.h" +#include "tee_log.h" +#include "tee_object_api.h" +#include "tee_trusted_storage_api.h" +#include "tee_mem_mgmt_api.h" +TEE_Result store_data_sample(void) +{ + uint32_t storageID = TEE_OBJECT_STORAGE_PRIVATE; + uint32_t r_flags = TEE_DATA_FLAG_ACCESS_READ; + uint32_t w_flags = TEE_DATA_FLAG_ACCESS_WRITE; + void *create_objectID = "store_data_sample.txt"; + TEE_ObjectHandle persistent_data = NULL; + TEE_Result ret; + char *write_buffer = "It is a test for persist object!"; + uint32_t pos = 0; + uint32_t len = 0; + char *read_buffer = NULL; + uint32_t count = 0; + tlogd("store_data_sample start:============\n"); + /* 创建永久Object: flag为写操作(会将initialData和objectinfo和attributes写入物理介质) */ + ret = TEE_CreatePersistentObject(storageID, create_objectID, strlen(create_objectID), w_flags, + TEE_HANDLE_NULL, NULL, 0, (&persistent_data)); + if (ret != TEE_SUCCESS) { + tloge("Failed to create file: ret = 0x%x\n", ret); + return ret; + } + ret = TEE_WriteObjectData(persistent_data, write_buffer, strlen(write_buffer)); + if (ret != TEE_SUCCESS) { + tloge("Failed to write file: ret = 0x%x\n", ret); + /* 打开或创建文件之后异常分支需要关闭文件,否则会产生内存泄漏 */ + TEE_CloseObject(persistent_data); + return ret; + } + TEE_CloseObject(persistent_data); + /* 打开已经创建的文件,进行数据的读取 */ + ret = TEE_OpenPersistentObject(storageID, create_objectID, strlen(create_objectID), + r_flags, (&persistent_data)); + if (ret != TEE_SUCCESS) { + tloge("Failed to open file:ret = 0x%x\n", ret); + return ret; + } + ret = TEE_InfoObjectData(persistent_data, &pos, &len); + if (ret != TEE_SUCCESS) { + tloge("Failed to open file:ret = 0x%x\n", ret); + TEE_CloseObject(persistent_data); + return ret; + } + read_buffer = TEE_Malloc(len + 1, 0); + if (read_buffer == NULL) { + tloge("Failed to open file:ret = 0x%x\n", ret); + TEE_CloseObject(persistent_data); + return ret; + } + /* 读取已存入的数据 */ + ret = TEE_ReadObjectData(persistent_data, read_buffer, len, &count); + if (ret != TEE_SUCCESS) { + TEE_CloseObject(persistent_data); + TEE_Free(read_buffer); + return ret; + } + if (TEE_MemCompare(write_buffer, read_buffer, strlen(write_buffer)) != 0) { + TEE_CloseObject(persistent_data); + TEE_Free(read_buffer); + return TEE_FAIL; + } + /* 关闭永久 object */ + TEE_CloseObject(persistent_data); + TEE_Free(read_buffer); + return TEE_SUCCESS; +} +``` + +## 独立服务开发 +### 概述 +服务开发分为两个部分: +* client:端提供接口给TA,TA可以通过该部分接口和服务进行通信。 +* server:服务的主体,会等待和处理TA的消息完成相应的功能处理。 + +client、server开发所依赖的编译工具链,签名工具等与TA保持一致。 + + +### 开发步骤 + +1. manifest.txt文件配置 + +a. client的manifest文件: +* gpd.ta.appID: e3d37f4a-f24c-48d0-8884-000000000000 +* gpd.ta.service_name: client_demo +* gpd.ta.singleInstance: true +* gpd.ta.multiSession: false +* gpd.ta.instanceKeepAlive: false +* gpd.ta.dataSize: 81920 +* gpd.ta.stackSize: 8192 +* gpd.ta.target_type: 4 + +在编译client时,gpd.ta.service_name,gpd.ta.target_type必须为有效值,gpd.ta.appID填写对应service的uuid, 其他字段不可以缺省,但是填充值不关心。 + +参数要求: +* gpd.ta.service_name长度必须小于32字节,并且名字定义需要保证唯一性。 +* client对应的target type为4。 + +b. server的manifest文件: +* gpd.ta.appID: e3d37f4a-f24c-48d0-8884-3bdd6c44e988 +* gpd.ta.service_name: task_demo +* gpd.ta.singleInstance: true +* gpd.ta.multiSession: false +* gpd.ta.instanceKeepAlive: true +* gpd.ta.dataSize: 81920 +* gpd.ta.stackSize: 8192 +* gpd.ta.target_type: 3 +* gpd.srv.is_need_release_ta_res: false +* gpd.srv.crash_callback: false +* gpd.srv.is_need_create_msg: false +* gpd.srv.is_need_release_msg: false + +server的manifest中通用字段的描述与TA保持一致。 + +针对服务的参数要求: +* gpd.ta.instanceKeepAlive目前仅支持为true。 +* server对应的target type为3。 +* gpd.ta.service_name长度不超过32字节。 +* gpd.srv.is_need_release_ta_res 该字段置为true时,在ta session退出时,gtask会给服务发送一个消息,服务中添加该消息对应的处理函数即可触发调用。该消息的处理函数需要服务自己添加,服务开发者可以按需实现。该场景不需要触发操作则置为false。 +* gpd.srv.crash_callback 该字段置为true时,在服务crash之后会被重新拉起。置为false则不会被重新拉起。需要注意,TEE中存在审计机制,服务crash 3次之后将无法被拉起。 +* gpd.srv.is_need_create_msg 该字段置为true时,在ta加载时,gtask会给服务发送一个消息,服务中添加该消息对应的处理函数即可触发调用。该消息的处理函数需要服务自己添加,服务开发者可以按需实现。该场景不需要触发操作则置为false。 +* gpd.srv.is_need_release_msg 该字段置为true时,在ta退出时,gtask会给服务发送一个消息,服务中添加该消息对应的处理函数即可触发调用。该消息的处理函数需要服务自己添加,服务开发者可以按需实现。该场景不需要触发操作则置为false。 +2. 权限配置 +− 映射TA内存权限 +在client使用新的共享内存申请接口,接口需要指定uuid。TA调用该接口申请共享内存,指定uuid的服务可以直接映射,不需要再单独配置相关权限。 +新的共享内存申请接口如下: + +void *tee_alloc_sharemem_aux(const struct tee_uuid *uuid, uint32_t size) ,定义在tee_sharemem_ops.h。 + +− 获取ta uuid权限 +仍在代码中静态配置,AC_DEFINE_SUBJ_BEG(get_uuid)中增加task的uuid。 + +− TA权限控制 +服务中自己维护白名单控制TA的访问权限 + +3. 独立服务client端开发 +```c +#include "securec.h" +#include "string.h" +#include "tee_defines.h" +#include "tee_service_public.h" +#include "tee_log.h" +#include "tee_sharemem_ops.h" +#define DEMO_TASK_NAME "task_demo" +#define DEMO_UUID \ + { \ + 0xe3d37f4a, 0xf24c, 0x48d0, \ + { \ + 0x88, 0x84, 0x3b, 0xdd, 0x6c, 0x44, 0xe9, 0x88 \ + } \ + } +#define MSG_LEN 10 +enum srv_cmd { + SRV_CMD_DEMO_0 = 0x0, + SRV_CMD_DEMO_1 = 0x1, +}; +TEE_Result tee_srv_demo() +{ + tee_service_ipc_msg msg = {{0}}; + tee_service_ipc_msg_rsp rsp = {0}; + TEE_Result ret; + uint8_t *buffer_local = NULL; + const char *temp = "my_demo"; + TEE_UUID srv_uuid = DEMO_UUID; + buffer_local = tee_alloc_sharemem_aux(&srv_uuid, MSG_LEN); + if (buffer_local == NULL) { + tloge("buffer_local is null"); + return TEE_ERROR_OUT_OF_MEMORY; + } + int rc = memcpy_s(buffer_local, MSG_LEN, temp, strlen(temp)); + if (rc != 0) { + tee_free_sharemem(buffer_local, MSG_LEN); + return TEE_ERROR_SECURITY; + } + rsp.ret = TEE_FAIL; + msg.args_data.arg0 = (uint64_t)(uintptr_t)(buffer_local); + msg.args_data.arg1 = MSG_LEN; + msg.args_data.arg3 = 0x123456; + tee_common_ipc_proc_cmd(DEMO_TASK_NAME, SRV_CMD_DEMO_0, &msg, SRV_CMD_DEMO_0, &rsp); + ret = rsp.ret; + if (ret == TEE_SUCCESS) { + tloge("tee_srv_demo done success!"); + } else { + tloge("tee_srv_demo done fail!"); + } + tee_free_sharemem(buffer_local, MSG_LEN); + return ret; +} +TEE_Result tee_srv_iomap_test() +{ + tee_service_ipc_msg msg = {{0}}; + tee_service_ipc_msg_rsp rsp = {0}; + TEE_Result ret; + rsp.ret = TEE_FAIL; + tee_common_ipc_proc_cmd(DEMO_TASK_NAME, SRV_CMD_DEMO_1, &msg, SRV_CMD_DEMO_1, &rsp); + ret = rsp.ret; + if (ret == TEE_SUCCESS) { + tloge("tee_srv_iomap_test done success!"); + } else { + tloge("tee_srv_iomap_test done fail!"); + } + return ret; +} +``` +4. 独立服务service端开发 +```c +#include "securec.h" +#include "tee_defines.h" +#include "tee_ext_api.h" +#include "tee_log.h" +#include "tee_dynamic_srv.h" +#include "tee_drv_client.h" +static int64_t drv_iomap_test(void) +{ + const char *drv_name = "drv_test_module"; + uint32_t args = (uint32_t)(uintptr_t)(&drv_name); + tloge("%s iomap begin args:0x%x\n", drv_name, args); + int64_t fd = tee_drv_open(drv_name, &args, sizeof(args)); + if (fd <= 0) { + tloge("open fd failed\n"); + return -1; + } + tloge("iomap test ioctl begin args:0x%x fd:%d\n", args, (int32_t)fd); + int64_t ret = tee_drv_ioctl(fd, 0x1a, &args, sizeof(args)); + if (ret != 0) + tloge("iomap test ioctl fd:%d failed\n", (int32_t)fd); + ret |= tee_drv_close(fd); + if (ret != 0) + tloge("===================== iomap test DRV_FAIL ==================\n"); + else + tloge("===================== iomap test DRV_SUCC ==================\n"); + return ret; +} +#define MSG_LEN 10 +static void demo_fn(tee_service_ipc_msg *msg, uint32_t sndr, tee_service_ipc_msg_rsp *rsp) +{ + const char *result = "my_demo"; + char temp[MSG_LEN] = {0}; + if (rsp == NULL) + return; + if (msg == NULL) { + rsp->ret = TEE_ERROR_BAD_PARAMETERS; + return; + } + tloge("!!!!!!!!demo_fn enter!!!!!!!"); + uint32_t vaddr = 0; + uint32_t size = msg->args_data.arg1; + int ret = tee_srv_map_from_task(sndr, msg->args_data.arg0, size, &vaddr); + if (ret != 0) { + rsp->ret = TEE_ERROR_GENERIC; + return; + } + int32_t rc = memcpy_s(temp, sizeof(temp), (uint8_t *)(uintptr_t)vaddr, size); + if (rc != 0) { + tloge("copy fail"); + tee_srv_unmap_from_task(vaddr, size); + rsp->ret = TEE_ERROR_SECURITY; + return; + } + if (memcmp((const char *)temp, result, strlen(result)) != 0) { + tloge("compare fail!"); + tee_srv_unmap_from_task(vaddr, size); + rsp->ret = TEE_ERROR_GENERIC; + return; + } + TEE_UUID uuid; + rsp->ret = tee_srv_get_uuid_by_sender(sndr, &uuid); + if (ret != 0) { + tloge("get uuid fail!"); + tee_srv_unmap_from_task(vaddr, size); + return; + } + tee_srv_unmap_from_task(vaddr, size); + rsp->ret = TEE_SUCCESS; + return; +} +static void demo_io_map_test(tee_service_ipc_msg *msg, uint32_t sndr, tee_service_ipc_msg_rsp *rsp) +{ + (void)sndr; + if (rsp == NULL) + return; + if (msg == NULL) { + rsp->ret = TEE_ERROR_BAD_PARAMETERS; + return; + } + tloge("!!!!!!!!demo_io_map_test enter!!!!!!!"); + int64_t rc = drv_iomap_test(); + if (rc != 0) { + rsp->ret = TEE_ERROR_GENERIC; + tloge("drv_iomap_test fail!"); + return; + } + rsp->ret = TEE_SUCCESS; + return; +} +enum srv_cmd { + SRV_CMD_DEMO_0 = 0x0, + SRV_CMD_DEMO_1 = 0x1, +}; +struct srv_dispatch_t dispatch_fns[] = { + {SRV_CMD_DEMO_0, demo_fn}, + {SRV_CMD_DEMO_1, demo_io_map_test}, +}; +void task_init(void) +{ + tloge("!!!!!!!!task init!!!!!!!!!"); +} +void tee_task_entry(void) +{ + task_init(); + tee_srv_cs_server_loop("task_demo", dispatch_fns, sizeof(dispatch_fns) / sizeof(dispatch_fns[0]), NULL); +} +``` + +5. TA加载服务client库 +```c +#include "dcm_verify_hal.h" +#include +#include +#include +#include + +#define DCM_VERIFIER_SDK_SO_NAME "/tafs/dcm_verifier_sdk.so" + +typedef TEE_Result (*dcm_attn_verifier_verify_func) + (hm_attestation_info_t *attestInfo, const hm_attest_blob_t *cert_chain); + +TEE_Result dcm_cert_verify_hal_demo(hm_attestation_info_t *attestInfo, const hm_attest_blob_t *cert_chain) +{ + TEE_Result ret = TEE_ERROR_GENERIC; + void *dcm_sdk_lib_handle = dlopen(DCM_VERIFIER_SDK_SO_NAME, RTLD_NOW | RTLD_GLOBAL); + if (dcm_sdk_lib_handle == NULL) { + tloge("dlopen failed: %s, %s\n", DCM_VERIFIER_SDK_SO_NAME, dlerror()); + (void)pthread_mutex_unlock(&g_dcm_sdk_lib_lock); + return TEE_ERROR_GENERIC; + } + + dcm_attn_verifier_verify_func dcm_attn_verifier_verify = dlsym(dcm_sdk_lib_handle, "DcmAttnVerifierVerify"); + if (dcm_attn_verifier_verify == NULL) { + tloge("dlsym DcmAttnVerifierVerify failed\n"); + ret = TEE_ERROR_GENERIC; + goto free_handle; + } + + ret = dcm_attn_verifier_verify(attestInfo, cert_chain); + if (ret != TEE_SUCCESS) + tloge("DcmAttnVerifierVerify failed, ret=0x%x\n", ret); + else + tlogi("DcmAttnVerifierVerify success\n"); +free_handle: + dlclose(dcm_sdk_lib_handle); + dcm_sdk_lib_handle = NULL; + return ret; +} +``` +关于dlopen时传入client库路径的/tafs/dcm_verifier_sdk.so的说明:1. /tafs/是服务client库文件在TEE中的挂载路径;2. dcm_verifier_sdk是manifest文件中gpd.ta.service_name \ No newline at end of file diff --git a/zh-cn/application-dev/tee/figures/ca-ta.png b/zh-cn/application-dev/tee/figures/ca-ta.png new file mode 100644 index 0000000000000000000000000000000000000000..a1554ebe6dc9297abe90e8488e225a6c72d1f46b Binary files /dev/null and b/zh-cn/application-dev/tee/figures/ca-ta.png differ diff --git a/zh-cn/application-dev/tee/figures/overview-of-opentrustee.png b/zh-cn/application-dev/tee/figures/overview-of-opentrustee.png new file mode 100644 index 0000000000000000000000000000000000000000..adb98a01a99b4ccbcf98a6a98573f31b50fd24b0 Binary files /dev/null and b/zh-cn/application-dev/tee/figures/overview-of-opentrustee.png differ diff --git a/zh-cn/application-dev/tee/tee-kit-overview.md b/zh-cn/application-dev/tee/tee-kit-overview.md new file mode 100644 index 0000000000000000000000000000000000000000..6cf17e3c3fc758cd4e088f740f835c25c4e948e7 --- /dev/null +++ b/zh-cn/application-dev/tee/tee-kit-overview.md @@ -0,0 +1,79 @@ +# TEE Kit简介 + + + +随着移动通讯和互联网技术的飞速发展,随之带来的安全隐患愈发严重。为了消除、杜绝这类风险,在系统中提供一个可信执行环境,使用户的关键数据或应用在这个可信环境中使用和运行,保护用户的关键数据和应用不被窃取、滥用和篡改。这个可信执行环境就是TEE(Trusted Execution Environment)。
+TEE基于TrustZone技术提供可信执行环境。TrustZone技术将中央处理器的工作状态划分为正常模式和安全模式,富执行环境(Rich Execution Environment,REE)运行在正常模式下,TEE运行在安全模式下。在安全模式下,提供了对外围硬件资源的保护和隔离,包括内存、外设等,保障REE和TEE的隔离,保障TEE的安全性。
+iTrustee是一套完整的TEE解决方案,包含正常模式的客户端应用(Client Application,CA)、安全模式的可信应用(Trusted Application,TA)、安全模式下的可信操作系统。 + + +## 一、能力范围 +iTrustee提供了一个可信执行环境(TEE),运行在受硬件隔离的安全区域中,与常规操作系统如OpenHarmony同时运行但互相隔离,OpenTrustee比常规操作系统拥有更高的安全性,可以为设备上的机密数据提供保护。OpenTrustee是一套完整的TEE解决方案,包含多个部件,系统架构如下图所示: + +![TEE架构](figures/overview-of-opentrustee.png) + +在终端设备越来越智能化的今天,人们正在把越来越多的个人应用和数据放到设备上,安全性成为一个很重要的命题。可信执行环境(TEE)已逐渐成为终端设备必备的安全技术。OpenTrustee具备丰富的安全特性,可以支持开发者灵活部署安全应用,应用场景也非常广泛,例如:移动支付、生物认证、版权保护等,也可以为系统安全提供保护,如安全启动、系统完整性检测等。 + +iTrustee是一套完整的TEE解决方案,包含正常模式的客户端应用(Client Application,CA)、安全模式的可信应用(Trusted Application,TA)、安全模式下的可信操作系统, + +iTrusteeOpenTrustee可以为应用提供以下功能: + +### 可信存储 +iTrusteeOpenTrustee提供对数据的可信存储,并保证数据机密性、完整性和原子性。 +* 机密性:存储的数据为密文,保证数据不被泄露; +* 完整性:存储的数据有完整性保护,保证数据不被篡改; +* 原子性:保证数据的一致性; +* 隔离性:TA间存储的数据互相隔离,不能互相访问; +* 不可复制性:数据与设备进行绑定,复制到其它设备后无法访问。 + +### 加解密 +iTrusteeOpenTrustee支持以下多种加解密算法 +* 摘要算法:SHA1、SHA224、SHA256、SHA384、SHA512; +* MAC: HMAC、AES-CMAC; +* 对称加解密算法:AES、DES、3DES; +* 非对称加解密算法:RSA 。 + +其中,下表中的算法不推荐开发者使用。 + +| 算法及场景 | 算法 | +|----------------|--------------------------------------------------------| +| 对称加密算法 | DES、3DES | +| 哈希算法 | SHA1、MD5、SHA224、HMAC-SHA1、HMAC_SHA224 | +| 非对称加密 | RSA(密钥小于3072,不包含3072) | +| 数字签名 | RSA(密钥小于3072,不包含3072)、ECDSA-SHA1、ECDSA-SHA224、DSA | +| 密钥协商 | ECDH(<256bit) | + + +### 可信时间 +iTrusteee提供可信的基准时间,该时间不能被TA或REE应用修改,因此可以更好的防范恶意软件。 + +### 可信显示与输入 +iTrustee提供安全的显示和输入,确保恶意程序既看不到显示屏上的信息,也无法访问触摸屏。它可以保护密码或PIN输入等功能,且不会将凭证暴露给恶意程序。应用程序还向用户显示安全消息,从而避免应用程序被窥探。 + +### 第三方安全应用开发 +第三方应用开发者可基于iTrusteeOpenTrustee进行TA和服务的开发和调试。 + + +## 二、基本概念 + +### TrustZone +在ARM处理器内核的一个安全扩展,该项技术可以保护安全内存、加密块、键盘和屏幕等外设,从而可确保它们免遭软件攻击。 +### 安全存储 +安全存储,提供了一个安全的存储环境,加密保存用户的私有数据。 +### REE +富执行环境(Rich Execution Environment,REE)运行在正常模式下。 +### TEE +可信执行环境TEE(Trusted Execution Environment,TEE)运行在安全模式下。 +### CA +正常模式的客户端应用(Client Application,CA)。 +### TA +安全模式的可信应用(Trusted Application,TA)。 + +## 三、应用场景 +基于TEE KIT提供的安全应用开发能力,可构建如下安全应用场景。 +| 服务 | 具体应用 | +|----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 内容保护 | 用于 DRM(Digital Rights Management)领域,保证视频在播放过程中的安全性,无法被窃取和监听。 | +| BYOD | 用于 BYOD(Bring Your Own Devices)领域,企业需要移动平台更高的安全性。例如使用安全存储对用户登录的密钥进行保存。iTrustee/OpenTrustee 可保证用户登录密钥的安全,防止操作系统中恶意程序窃取到用户密钥数据。 | +| 移动支付 | 用于移动支付领域,保证输入信息(PIN)的安全性,同时可配合 NFC 进行使用。iTrustee/OpenTrustee 可保护用户输入信息的安全,防止被恶意程序窃取。 | +| 应用逻辑保护 | iTrustee/OpenTrustee 可保护关键应用逻辑不被窃取或篡改。 | \ No newline at end of file