Files
tee_tee_dev_kit/docs/opentrustee-guidelines/opentrustee-ca-ta-develop-guidelines.md
2025-01-16 11:00:07 +08:00

52 KiB
Raw Permalink Blame History

OpenTrustee CA/TA开发指南

CA/TA访问机制

OpenTrustee应用分为CA(客户端应用)和TA(可信应用)。OpenTrustee支持CA访问TA,也支持TA访问TA。TA采用命令响应机制,交互流程如下:

  1. CA调用TEEC_InitializeContext初始化客户端上下文,这个过程并不会访问TA。
  2. CA调用TEEC_OpenSession建立与TA的会话。OpenTrustee系统会把TA加载运行,创建TA实例并调用TA的TA_CreateEntryPoint。然后再创建TA会话并调用TA的TA_OpenSessionEntryPoint接口。客户端可以跟TA建立多个会话,每个会话发起时OpenTrustee都会调用TA_OpenSessionEntryPoint接口。
  3. CA调用TEEC_InvokeCommand向TA发送命令,OpenTrustee系统会调用TA的TA_InvokeCommandEntryPoint接口处理该命令并返回结果。
  4. CA调用TEEC_CloseSession关闭与TA的会话。OpenTrustee系统会调用TA的TA_CloseSessionEntryPoint接口清理资源。在TA最后一个会话被关闭时,OpenTrustee系统会调用TA的TA_DestroyEntryPoint接口清理全局资源。
  5. CA调用TEEC_FinalizeContext,清理上下文。

OpenTrustee的实现遵循GP TEE标准的规定,上述流程可参考GP TEE标准。

CA开发指导

CA即OpenHarmony系统侧应用,比如native程序、SA服务、或HAP应用。

CA接口库

对于用户态CAOpenTrustee 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规范有差异,差异点如下:

  1. TEEC_OpenSession接口的TEEC_Context结构体成员 ta_path支持指定TA的加载路径(限制在/data目录)

    举例如下:

    TEEC_Context context;
    context.ta_path = (uint8_t *)"/data/58dbb3b9-4a0c-42d2-a84d-7c7ab17539fc.sec"
    
  2. TEEC_OpenSession接口入参connectionMethod只支持TEEC_LOGIN_IDENTIFY

    对于TEEC_OpenSession函数中第四个入参connectionMethodGP规范定义了六种Login MethodOpenTrustee拓展了TEEC_LOGIN_IDENTIFY的类型,且只支持该种connectionMethod。

  3. 调用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头文件和安全函数库头文件

开发套件准备

开发者在使用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, &params[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位TAMakefile中不应包含此标记。
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库,接口支持请参考下表。

说明:

  • 不支持文件系统、控制台。
  • 不支持fstatfsyncwritev接口。
  • stdio中的printf函数目前不支持文件系统,文件操作只支持标准输入输出。

表 1 标准C支持列表

模块

函数接口名

pthread

sem_getvalue

sem_init

sem_post

sem_wait

pthread_mutex_destroy

pthread_mutex_init

pthread_mutex_lock

pthread_mutex_trylock

pthread_mutex_unlock

pthread_mutexattr_destroy

pthread_mutexattr_init

pthread_mutexattr_setprotocol

pthread_mutexattr_settype

pthread_spin_destroy

pthread_spin_init

pthread_spin_lock/p>

pthread_spin_trylock

pthread_spin_unlock

pthread_cond_broadcast

pthread_cond_destroy

pthread_cond_init

pthread_cond_signal

pthread_cond_wait

pthread_attr_destroy

pthread_attr_getstack

pthread_attr_getstacksize

pthread_attr_init

pthread_attr_setstack

pthread_attr_setstacksize

pthread_create

pthread_equal

pthread_exit

pthread_getspecific

pthread_join

pthread_key_create

pthread_key_delete

pthread_once

pthread_self

pthread_setschedprio

pthread_setspecific

malloc

aligned_alloc

calloc

malloc

realloc

free

posix_memalign

mman

mmap

munmap

time

gettimeofday

strftime

time

stdio

printf

scanf

snprintf

sprintf

vsnprintf

vsprintf

errno

errno

strerror

exit

abort

unistd

getpid

gettid

locale

setlocale

strcoll

strxfrm

strtod

multibyte

mbrtowc

wcrtomb

wctob

prng

srandom

initstate

setstate

random

string

memchr

memcmp

memcpy

memmove

memset

strchr

strcmp

strcpy

strlen

strncmp

strncpy

strnlen

strrchr

strstr

wcschr

wcslen

wmemchr

ctype

isalpha

isascii

isdigit

islower

isprint

isspace

iswctype

iswdigit

iswlower

iswspace

iswupper

towupper

towlower

math

atan

ceil

ceilf

copysignl

exp

fabs

floor

frexp

frexpl

log

log2

pow

roundf

scalbn

scalbnl

sqrt

stdlib

abs

atof

atoi

atol

atoll

bsearch

div

ecvt

imaxabs

llabs

qsort

strtoul

strtol

wcstod

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