Signed-off-by: Lihao Cao <lihaocao@sjtu.edu.cn>
52 KiB
OpenTrustee CA/TA开发指南
CA/TA访问机制
OpenTrustee应用分为CA(客户端应用)和TA(可信应用)。OpenTrustee支持CA访问TA,也支持TA访问TA。TA采用命令响应机制,交互流程如下:
- CA调用TEEC_InitializeContext初始化客户端上下文,这个过程并不会访问TA。
- CA调用TEEC_OpenSession建立与TA的会话。OpenTrustee系统会把TA加载运行,创建TA实例并调用TA的TA_CreateEntryPoint。然后再创建TA会话并调用TA的TA_OpenSessionEntryPoint接口。客户端可以跟TA建立多个会话,每个会话发起时OpenTrustee都会调用TA_OpenSessionEntryPoint接口。
- CA调用TEEC_InvokeCommand向TA发送命令,OpenTrustee系统会调用TA的TA_InvokeCommandEntryPoint接口处理该命令并返回结果。
- CA调用TEEC_CloseSession关闭与TA的会话。OpenTrustee系统会调用TA的TA_CloseSessionEntryPoint接口清理资源。在TA最后一个会话被关闭时,OpenTrustee系统会调用TA的TA_DestroyEntryPoint接口清理全局资源。
- CA调用TEEC_FinalizeContext,清理上下文。
OpenTrustee的实现遵循GP TEE标准的规定,上述流程可参考GP TEE标准。
CA开发指导
CA即OpenHarmony系统侧应用,比如native程序、SA服务、或HAP应用。
CA接口库
对于用户态CA,OpenTrustee TEE client模块为CA提供了访问TEE的接口库和API。
对于system/bin侧的CA,引用TEE头文件和接口库的方式为,在CA的BUILD.gn文件中增加如下引用:
include_dirs = [
"//base/tee/tee_client/interfaces/libteec"
]
deps = [
"//base/tee/tee_client/frameworks/build/standard:libteec"
]
对于vendor/bin侧的CA,引用TEE头文件和接口库的方式为,在CA的BUILD.gn文件中增加如下引用:
include_dirs = [
"//base/tee/tee_client/interfaces/libteec"
]
deps = [
"//base/tee/tee_client/frameworks/build/standard:libteec_vendor"
]
CA API
OpenTrustee提供的CA API基本是符合GP TEE标准规定的,可参考《TEE Client API Specification v1.0 (GPD_SPE_007)》。少量实现与GP TEE规范有差异,差异点如下:
-
TEEC_OpenSession接口的TEEC_Context结构体成员 ta_path支持指定TA的加载路径(限制在/data目录)
举例如下:
TEEC_Context context; context.ta_path = (uint8_t *)"/data/58dbb3b9-4a0c-42d2-a84d-7c7ab17539fc.sec" -
TEEC_OpenSession接口入参connectionMethod只支持TEEC_LOGIN_IDENTIFY
对于TEEC_OpenSession函数中第四个入参connectionMethod,GP规范定义了六种Login Method,OpenTrustee拓展了TEEC_LOGIN_IDENTIFY的类型,且只支持该种connectionMethod。
-
调用TEEC_OpenSession时,TEEC_Operation参数有限制
在调用TEEC_OpenSession接口时,TEEC_Operation中params[2]和params[3]是预留给系统的,不允许CA使用,CA仅可以使用params[0]和params[1]。
- 支持的CA API列表如下:
| 名称 | 描述 |
|---|---|
| TEEC_InitializeContext (const char *name, TEEC_Context *context) | 初始化TEE环境。 |
| TEEC_FinalizeContext (TEEC_Context *context) | 关闭TEE环境。 |
| TEEC_OpenSession (TEEC_Context *context, TEEC_Session *session, const TEEC_UUID *destination, uint32_t connectionMethod, const void *connectionData, TEEC_Operation *operation, uint32_t *returnOrigin) | 打开会话。 |
| TEEC_CloseSession (TEEC_Session *session) | 关闭会话。 |
| TEEC_InvokeCommand (TEEC_Session *session, uint32_t commandID, TEEC_Operation *operation, uint32_t *returnOrigin) | 发送命令。 |
| TEEC_RegisterSharedMemory (TEEC_Context *context, TEEC_SharedMemory *sharedMem) | 注册共享内存。 |
| TEEC_AllocateSharedMemory (TEEC_Context *context, TEEC_SharedMemory *sharedMem) | 申请共享内存。 |
| TEEC_ReleaseSharedMemory (TEEC_SharedMemory *sharedMem) | 释放共享内存。 |
| TEEC_RequestCancellation (TEEC_Operation *operation) | 取消正在运行的操作。 |
需注意,业务CA需自行添加selinux策略,否则会出现调用CA API失败。
TA开发指导
TA安装包
TA安装包不需要跟OpenTrustee镜像打包到一起,可以把TA安装包文件放到非安全侧文件系统下。在TA被访问时,由TEE Client将TA安装包发送到OpenTrustee系统中加载运行。
由于TA安装包放在非安全侧文件系统中,需要对TA安装包做签名,保证安装包的完整性。
TA安装包路径
TA安装包放在OpenHarmony文件系统下,路径有两种选择。
1、将TA安装包命名为uuid.sec(uuid需要替换为TA的真实uuid),放在/vendor/bin目录或者/system/bin目录,OpenTrustee Client会在TA被访问时,自动查找该TA对应的uuid.sec,发送到OpenTrustee系统中加载运行。
2、TA安装包可以自定义文件系统路径,在CA调用TEEC_OpenSession时,通过TEEC_Context的ta_path入参指定该TA安装包的路径,OpenTrustee Client会在指定路径查找该安装包,并发送到OpenTrustee系统中加载运行。
TA安装包格式
TA安装包是以“.sec”为后缀名的包文件,文件格式如下:包含文件头、签名块、数据区三部分
TA签名机制
TA签名:由于TA安装包放在非安全侧文件系统中,需要对TA安装包做签名,确保加载到OpenTrustee中的TA安装包没有被篡改。OpenTrustee提供的SDK开发套件中,包含了TA的签名工具,支持对TA安装包一键签名。需要在OpenTrustee SDK开发套件中预置TA签名的私钥。
TA验签:在TA安装包加载到OpenTrustee操作系统中时,由OpenTrustee TA管理框架对TA安装包做签名验证,验证通过之后才允许该TA加载运行。需要在OpenTrustee操作系统中预置TA验签的公钥。
为了方便社区开发者调试,社区的OpenTrustee SDK开发套件已经预置了TA签名私钥,OpenTrustee操作系统中预置了验签的公钥。开发者在OpenTrustee商用版本中应自行替换该签名私钥和验签公钥。
SDK开发套件
介绍
OpenTrustee提供了SDK开发套件支持独立开发TA,该开发套件集成了TA头文件、TA编译框架、TA签名脚本、demo样例等,目录结构如下:
├── build
│ ├── ld # 生成TA ELF文件的链接脚本
│ ├── mk # TA make编译框架
│ ├── signkey # TA签名用的私钥
│ └── tools # 生成TA安装包并对TA签名的脚本
├── include
│ └── TA # 给TA提供的TEE头文件
├── src
│ └── TA # 放置TA源码和示例
└── helloworld_demo # TA helloworld示例
└── secstorage_demo # 安全存储示例
├── thirdparty
│ └── open_source
│ └── import_open_source_header.sh # 导入TA编译依赖的musl头文件和安全函数库头文件
- 开发语言:C语言
- 代码编辑器:未提供特定编辑器,任意支持C语言开发的编辑器均可
- SDK执行环境:linux操作系统
- SDK套件下载地址:https://gitee.com/openharmony-sig/tee_tee_dev_kit
开发套件准备
开发者在使用OpenTrustee SDK开发套件开发TA之前,需要进行一些准备工作。
配置编译工具链
OpenTrustee使用的编译工具链为llvm,与OpenHarmony一致,开发者需要先下载OpenHarmony编译工具链。
首选下载OpenHarmony build代码仓,
git clone git@gitee.com:openharmony/build.git
然后执行该仓中的下载脚本
./build/prebuilts_download.sh
下载完成后,需要在当前编译环境中声明llvm编译工具链的路径。可通过如下命令声明编译工具链路径:
export PATH=openharmony/prebuilts/clang/ohos/linux-x86_64/15.0.4/llvm/bin:$PATH
该命令仅是示例,开发者需要指定正确的编译工具链路径。
导入第三方头文件
OpenTrustee集成了musl库和安全函数库,TA可以使用这些库。OpenTrustee SDK并没有默认包含musl库和安全函数库的头文件,但是提供了导入的脚本。 开发者需要先下载musl库和安全函数库源码仓:
git clone git@gitee.com:openharmony/third_party_musl.git
git clone git@gitee.com:openharmony/third_party_bounds_checking_function.git
然后执行
./tee_dev_kit/sdk/thirdparty/open_source/import_open_source_header.sh
将musl头文件和安全函数库头文件从源码仓导入到OpenTrustee SDK中。
替换TA签名和验签密钥
OpenTrustee SDK中预置了对TA文件进行签名的私钥,该预置私钥只能用来调试,在商用版本中,开发者需要自行替换该私钥。该私钥路径:tee_dev_kit/sdk/build/signkey/ta_sign_priv_key.pem。同时提供了tee_dev_kit/sdk/build/signkey/ta_sign_algo_config.ini脚本,可以用来对签名算法进行配置。默认的签名算法是RSA,密钥长度4096bit。
如果开发者替换了OpenTrustee SDK中的签名私钥,需要对应替换OpenTrustee操作系统中的验签公钥,验签公钥的路径:tee_os_framework/lib/syslib/libelf_verify_key/src/common/ta_verify_key.c。
工具安装
安装python工具
OpenTrustee SDK中用到了python脚本来完成TA的属性配置文件解析、对TA文件进行签名等操作,因此需要在开发环境上安装python工具。以Ubuntu系统为例,安装命令如下:
1、安装python3
sudo apt-get update
sudo apt-get install python3 python3-pip
2、安装python相关的库,如:
pip install pycryptodome
pip install defusedxml
如果在编译过程中提示缺少其他python库,需要一并安装。
安装openssl工具
OpenTrustee SDK使用openssl工具的签名算法来对TA文件进行签名,需要在开发环境上安装openssl工具。
sudo apt-get install openssl
安装make工具
需要在开发环境上安装make工具来对TA源码进行编译。
sudo apt-get install make
TA开发步骤
开发一个新的TA时,需要在tee_dev_kit/sdk/src/TA目录下创建新的TA源码目录,目录结构可以参考该目录下demo示例代码。以helloworld_demo为例,目录结构如下:
├── helloworld_demo
├── ta_demo.c # TA源码文件
├── configs.xml # TA属性配置文件
├── Makefile # TA编译Makefile
├── build_ta.sh # TA一键生成脚本
TA代码编写
TA代码必须实现如下GP TEE标准规定的入口函数:
| TA入口函数名称 | 函数描述 |
|---|---|
| TA_CreateEntryPoint | TA实例的构造函数,每个TA实例的生命周期中只被调用一次 |
| TA_OpenSessionEntryPoint | 客户端请求创建一个与TA的会话 |
| TA_InvokeCommandEntryPoint | 客户端在创建会话成功后向TA发送指令 |
| TA_CloseSessionEntryPoint | 客户端请求关闭与TA的会话 |
| TA_DestroyEntryPoint | TA示例的析构函数,OpenTrustee在销毁TA实例时调用此函数 |
在客户端访问TA时,OpenTrustee系统会主动调用TA的这些入口函数。详细的参数接口定义请参考TA API章节。代码示例如下:
#include <tee_ext_api.h>
#include <tee_log.h>
#include <securec.h>
#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;
}
/**
* Function TA_CreateEntryPoint
* Description:
* The function TA_CreateEntryPoint is the Trusted Application's constructor,
* which the Framework calls when it creates a new instance of this Trusted Application.
*/
TEE_Result TA_CreateEntryPoint(void)
{
tlogd("----- TA entry point ----- ");
return TEE_SUCCESS;
}
/**
* Function TA_OpenSessionEntryPoint
* Description:
* The Framework calls the function TA_OpenSessionEntryPoint
* when a client requests to open a session with the Trusted Application.
* The open session request may result in a new Trusted Application instance
* being created.
*/
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;
}
/**
* Function TA_InvokeCommandEntryPoint
* Description:
* The Framework calls this function when the client invokes a command
* within the given session.
*/
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;
}
/**
* Function TA_CloseSessionEntryPoint
* Description:
* The Framework calls this function to close a client session.
* During the call to this function the implementation can use
* any session functions.
*/
void TA_CloseSessionEntryPoint(void* session_context)
{
(void)session_context;
tlogd("---- close session ----- ");
}
/**
* Function TA_DestroyEntryPoint
* Description:
* The function TA_DestroyEntryPoint is the Trusted Application's destructor,
* which the Framework calls when the instance is being destroyed.
*/
void TA_DestroyEntryPoint(void)
{
tlogd("---- destroy TA ---- ");
}
TA Makefile编写
TA需要自行编写Makefile文件,可参考SDK中示例代码。有如下要点:
- TA编译生成的目标文件名固定为libcombine.so。
- 对于64位的TA,需要在Makefile头部增加“TARGET_IS_ARM64 = y”标记;对于32位TA,Makefile中不应包含此标记。
TA属性配置
每个TA源码目录下需要包含configs.xml,定义该TA的属性信息。
| 属性名 | 数据类型 | 属性描述 | 系统默认值 |
|---|---|---|---|
| service_name | String | TA名称,字符串长度不超过64字符,仅支持数字、字母,'_'和'-' | 无 |
| uuid | UUID | TA唯一标识 | 无 |
| instance_keep_alive | Bool | 如果为true,表示即使TA所有会话被关闭,TA实例也不会被销毁,全局数据仍然存在,直到TEE运行结束。如果为false,表示若TA所有会话关闭,TA实例会被销毁。 | false |
| stack_size | Integer | TA每个会话的栈空间大小,需要根据TA实际情况评估 | 8192 |
| heap_size | Integer | TA实例占用的堆空间大小,需要根据TA实际情况评估 | 0 |
| multi_session | Bool | TA是否支持同时建立多个会话 | false |
| single_instance | Bool | TA的多个会话是否归属同一个实例(当前只支持singleInstance为true) | true |
示例如下:
<ConfigInfo>
<TA_Basic_Info>
<service_name>demo-ta</service_name>
<uuid>e3d37f4a-f24c-48d0-8884-3bdd6c44e988</uuid>
</TA_Basic_Info>
<TA_Manifest_Info>
<instance_keep_alive>false</instance_keep_alive>
<stack_size>8192</stack_size>
<heap_size>81920</heap_size>
<multi_session>false</multi_session>
<single_instance>true</single_instance>
</TA_Manifest_Info>
</ConfigInfo>
TA编译和签名
OpenTrustee SDK中提供了TA一键生成脚本,将tee_dev_kit/sdk/build/build_ta.sh拷贝到TA源码目录执行,即完成TA编译、属性配置文件解析、签名等操作,在当前目录生成uuid.sec命名的TA安装包文件。
TA规格约束
由于OpenTrustee内存资源有限,因此对TA的资源占用需严格约束。
- TA安装包文件大小,应小于8M,否则会被拒绝加载
- 单个TA最大会话数量上限为8
- TA应优化自己的内存占用,避免占用过多内存,导致OpenTrustee系统内存耗尽
CA/TA鉴权指导
为了保证TEE会话一旦建立就是可靠的,OpenTrustee提供了鉴权机制。 具体而言,先使用tee_get_session_type接口获取会话访问类型(CA访问TA 或者 TA访问TA),然后选择相应的验证策略。
CA访问TA
TA可以在TA_OpenSessionEntryPoint中通过入参params数组获取CA访问者的信息,根据此信息来判断是否创建会话。
对于native ca来说
- params[2]: CA的uid以及uid size,可通过/proc/ca pid/status查询
- params[3]: cmdline以及cmdline size,可通过/proc/ca pid/cmdline查询
对于hap来说
- params[2]: hap证书及证书size
- params[3]: hap包名及包名size
举个例子,代码如下:
#include <tee_ext_api.h>
TEE_Result TA_OpenSessionEntryPoint(uint32_t parm_type, TEE_Param params[PARAM_COUNT], void** session_context)
{
(void)parm_type;
(void)session_context;
tlogd("---- TA open session -------- ");
/* 获取会话类型 */
uint32_t session_type = tee_get_session_type();
/* 只允许CA 访问 此TA,并校验CA信息 */
if (session_type == SESSION_FROM_CA) {
/* 获取uid */
uint32_t uid = *((uint32_t *)params[2].memref.buffer);
/* 获取 CA名称和cmdline长度 */
char *ca_name = (char *)params[3].memref.buffer;
size_t ca_name_size = params[3].memref.size;
/* 预期值 */
uint32_t expected_uid = 0;
const char *expected_ca_name = "/vendor/bin/tee_hello";
if (uid != expected_uid || strncmp(ca_name, expected_ca_name, ca_name_size) != 0) {
tloge("caller has no permission");
return TEE_ERROR_ACCESS_DENIED;
}
} else {
tloge("invalid session type, type = %d", session_type);
return TEE_ERROR_BAD_PARAMETERS;
}
/* 初始化其他逻辑,如果需要 */
return TEE_SUCCESS;
}
TA访问TA
TA可以在TA_OpenSessionEntryPoint中通过tee_ext_get_caller_info来获取访问者TA的唯一身份信息uuid,并进行验证。示例代码如下:
#include <tee_ext_api.h>
#include <tee_mem_mgmt_api.h>
const TEE_UUID expected_caller_uuid = {
0x12345678, 0x1234, 0x1234, {0x12,0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78 }
};
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 -------- ");
/* 获取会话类型 */
uint32_t session_type = tee_get_session_type();
/* 只允许TA 访问 此TA,并校验TA信息 */
if (session_type == SESSION_FROM_TA) {
/* 获取调用者信息 */
caller_info caller_info_data = {0};
TEE_Result res = tee_ext_get_caller_info(&caller_info_data, sizeof(caller_info_data));
if (res != TEE_SUCCESS)
return res;
/* 校验调用者TA的uuid */
if (TEE_MemCompare(&caller_info_data.caller_identity.caller_uuid, &expected_caller_uuid, sizeof(TEE_UUID)) != 0) {
tloge("caller has no permission");
return TEE_ERROR_ACCESS_DENIED;
}
} else {
tloge("invalid session type, type = %d", session_type);
return TEE_ERROR_BAD_PARAMETERS;
}
/* 初始化其他逻辑,如果需要 */
return TEE_SUCCESS;
}
TEE标准C库支持
支持大多数的POSIX接口,具体支持情况请参考:POSIX: https://mirror.math.princeton.edu/pub/oldlinux/download/c953.pdf
支持绝大多数的libc接口。使用musl/libc库,接口支持请参考下表。
- 不支持文件系统、控制台。
- 不支持fstat,fsync,writev接口。
- stdio中的printf函数目前不支持文件系统,文件操作只支持标准输入输出。
表 1 标准C支持列表
TEE支持的安全函数
安全函数库介绍
遵循C11 Annex K (Bounds-checking interfaces)的标准,选取并实现了常见的内存/字符串操作类的函数,如memcpy_s、strcpy_s等函数。
具体可以参考安全函数库
函数清单
- memcpy_s
- memmove_s
- memset_s
- strcpy_s
- strncpy_s
- strcat_s
- strncat_s
- strtok_s
- snprintf_s
- vsnprintf_s

