先简单介绍下optee:TEE是智能手机主处理器中的一块安全区域,保证代码和数据的机密性和完整性;TEE中的数据不会被REE中的程序非法访问;TEE中的 可信应用(TA)在隔离的环境中运行,其 安全性比手机主操作系统(Rich OS,比如Android)高,并且 提供比SE更丰富的功能。optee就是TEE的开源版本,企业可以将其的TEE功能移植到支持trustzone的arm芯片的各种操作系统,包括Android、Linux等其他系统。
目前我所知的OPTEE已经可以支持的设备有如下:
其实还有很多厂商自己移植的,只是不对外公开。optee的官网地址为:Device specific information — OP-TEE documentation documentation
我是使用的qemu方式来验证TEE功能,TEE的理解我这里不详细介绍,网上很多资源可以看到对其的介绍,我主要是讲静态TA的创建方法和调用方法,接下来我的介绍都是在ubuntu上面搭建的optee的qemu环境上进行验证。
首先我大致说下静态TA和动态TA的区别:
动态TA:动态TA就是最后编译生成的.TA文件是在REE测,用户可以在文件系统里面找到对应的.TA文件,在调用TA的时候,TEE测会加载该动态TA,然后验证TA是否合法,最后再执行TA中用户开发的代码功能。重点是需要时才会去加载,而且会验证TA合法性,并且TA是存在REE测
静态TA:静态TA最后编译的静态.TA文件是在TEE测,在系统运行的时候,静态TA会跟随系统一起加载起来在TEE测,在调用TA的时候直接进行调用使用。静态TA的重点是系统运行就会加载,我不确定是否需要验证合法性,并且TA在TEE测,用无法看到静态TA。
我们在qemu的optee_examples里面操作的全是属于动态TA的例子,动态TA的使用方法,网上太多了,大家可以按照optee_examples里面的例子自救去实验即可,我们直接讲如何创建静态TA和怎么调用静态TA。
在例子中我创建的是一个生成随机数的静态TA功能,用户在REE测调用静态TA,可以生成指定长度的随机数。
1、qemu的目录如下
drwxrwxr-x 10 jelly jelly 4096 1月 18 16:56 build
drwxrwxr-x 16 jelly jelly 4096 3月 9 2020 buildroot
drwxrwxr-x 25 jelly jelly 4096 1月 19 15:51 linux
drwxrwxr-x 4 jelly jelly 4096 3月 9 2020 optee_benchmark
drwxrwxr-x 8 jelly jelly 4096 3月 9 2020 optee_client
drwxrwxr-x 11 jelly jelly 4096 1月 18 15:39 optee_examples
drwxrwxr-x 12 jelly jelly 4096 1月 18 17:42 optee_os
drwxrwxr-x 9 jelly jelly 4096 3月 9 2020 optee_test
drwxrwxr-x 3 jelly jelly 4096 3月 9 2020 out
drwxr-xr-x 2 root root 4096 1月 19 15:51 out-br
drwxrwxr-x 47 jelly jelly 12288 1月 19 15:43 qemu
drwxrwxr-x 3 jelly jelly 4096 3月 9 2020 soc_term
drwxrwxr-x 2 jelly jelly 4096 3月 9 2020 toolchains
drwxrwxr-x 19 jelly jelly 4096 3月 9 2020 trusted-firmware-a
drwxrwxr-x 25 jelly jelly 4096 1月 19 15:51 u-boot
我们需要在optee_os文件夹里面编辑静态TA,然后在optee_examples里面写调用静态TA的示例
2、新增静态TA的.c文件
vim /home/jelly/qemu/optee_os/core/pta/jelly_static_ta.c
内容如下:下面的操作就是静态TA的一个测试功能,是生成随机数,需要从REE测传入随机数的长度和生成随机数的种子。
备注一下哈,本来是可以直接调用TA测的生成随机数的函数或者rand函数,但是我试了很久都没调用起来,后来就找到了一个类似随机数生成的源码代替随机数函数。
// SPDX-License-Identifier: BSD-2-Clause
/*
* Copyright (C) 2019, Linaro Limited
*/
/*
* This pseudo TA is used by normal world OS TEE driver to fetch pseudo TA's
* UUIDs which can act as TEE bus devices.
*/
#include
#include
#include
#include
#include
#include
#define STATIC_NAME "static_jelly.pta"
#include
static unsigned long next = 1; //next的初始值为随机数种子
uint32_t jelly_rand(void) //自定义随机数产生函数
{
next = next * 1103515245 + 12345;
return((unsigned)(next/65536) % 32768 % 256);
}
void jelly_srand(unsigned long seed)//通过传不同的参数更改种子
{
next=seed;
}
static TEE_Result random_number_generate(uint32_t param_types,
TEE_Param params[4])
{
uint8_t random_data[256] = {0};
uint32_t exp_param_types =
TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_OUTPUT,
TEE_PARAM_TYPE_MEMREF_INPUT,
TEE_PARAM_TYPE_NONE,
TEE_PARAM_TYPE_NONE);
DMSG("has been called");
if ((param_types != exp_param_types))
return TEE_ERROR_BAD_PARAMETERS;
//IMSG("Generating random data over %u bytes.", params[0].memref.size);
jelly_srand(params[1].memref.size);
for(uint8_t i=0;i
3、修改sub.mk文件,将新的.c文件添加到其中
/home/jelly/qemu/optee_os/core/pta/sub.mk
修改后内容如下:
subdirs-$(CFG_TEE_CORE_EMBED_INTERNAL_TESTS) += tests
srcs-$(CFG_TEE_BENCHMARK) += benchmark.c
srcs-$(CFG_DEVICE_ENUM_PTA) += device.c
srcs-$(CFG_TA_GPROF_SUPPORT) += gprof.c
srcs-$(CFG_SDP_PTA) += sdp.c
ifeq ($(CFG_WITH_USER_TA),y)
srcs-$(CFG_SECSTOR_TA_MGMT_PTA) += secstor_ta_mgmt.c
endif
srcs-$(CFG_WITH_STATS) += stats.c
srcs-$(CFG_SYSTEM_PTA) += system.c
srcs-y += jelly_static_ta.c
subdirs-y += bcm
4、新增.c的头文件
vim /home/jelly/qemu/optee_os/lib/libutee/include/pta_jelly_static_ta.h
内容如下:头文件里面包含了TA的UUID和一些其他参数
/* SPDX-License-Identifier: BSD-2-Clause */
/*
* Copyright (C) 2019, Linaro Limited jelly static test
*/
/*
* Enumerate the pseudo TAs that have the TA_FLAG_DEVICE_ENUM flag enabled.
*/
#ifndef __PTA_DEVICE_H
#define __PTA_DEVICE_H
#define PTA_STATIC_JELLY_UUID { 0xaabbccee, 0xddde, 0x4053, \
{ 0xa5, 0xa9, 0x7b, 0x3c, 0x4d, 0xdf, 0x13, 0xdd } }
/*
* Get device UUIDs
*
* [out] memref[0] Array of device UUIDs
*
* Return codes:
* TEE_SUCCESS - Invoke command success
* TEE_ERROR_BAD_PARAMETERS - Incorrect input param
* TEE_ERROR_SHORT_BUFFER - Output buffer size less than required
*/
#define PTA_CMD_STATIC 0x0
#endif /* __PTA_DEVICE_H */
5、创建静态TA调用示例
在optee_examples创建一个static_test的文件夹
mkdir /home/jelly/qemu/optee_examples/static_test
//里面含有如下文件列表,和optee_examples的其他动态TA的区别是此时没有TA文件夹了,因为静态TA在os文件夹下面已经创建好了
-rw-rw-r-- 1 jelly jelly 472 1月 18 13:37 Android.mk
-rw-rw-r-- 1 jelly jelly 286 1月 18 13:37 CMakeLists.txt
drwxrwxr-x 2 jelly jelly 4096 1月 19 15:48 host
-rw-rw-r-- 1 jelly jelly 311 1月 18 13:36 Makefile
Android.mk内容如下:
###################### optee-random ######################
LOCAL_PATH := $(call my-dir)
OPTEE_CLIENT_EXPORT = $(LOCAL_PATH)/../../optee_client/out/export
include $(CLEAR_VARS)
LOCAL_CFLAGS += -DANDROID_BUILD
LOCAL_CFLAGS += -Wall
LOCAL_SRC_FILES += host/main.c
LOCAL_C_INCLUDES := $(OPTEE_CLIENT_EXPORT)/include
LOCAL_SHARED_LIBRARIES := libteec
LOCAL_MODULE := optee_static_test
LOCAL_VENDOR_MODULE := true
LOCAL_MODULE_TAGS := optional
include $(BUILD_EXECUTABLE)
CMakeLists.txt内容如下:
project (optee_static_test C)
set (SRC host/main.c)
add_executable (${PROJECT_NAME} ${SRC})
target_include_directories(${PROJECT_NAME}
PRIVATE include)
target_link_libraries (${PROJECT_NAME} PRIVATE teec)
install (TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
Makefile内容如下:
export V ?= 0
# If _HOST or _TA specific compilers are not specified, then use CROSS_COMPILE
HOST_CROSS_COMPILE ?= $(CROSS_COMPILE)
TA_CROSS_COMPILE ?= $(CROSS_COMPILE)
.PHONY: all
all:
$(MAKE) -C host CROSS_COMPILE="$(HOST_CROSS_COMPILE)" --no-builtin-variables
.PHONY: clean
clean:
$(MAKE) -C host clean
host文件夹里面就两个文件:main.c和Makefile
host的main.c文件内容如下:
备注:在下面代码可以看到
1)random_uuid是我最后生成随机数的数组,定义的16位,由op.params[0].tmpref.buffer传递给TA
2)sizeof(random_uuid)是随机数长度,由op.params[0].tmpref.size传递给TA
3)datanouse这个没用,只是为了传随机数种子随便定义的字符串数组,这个必须有,不然会报错,我测试出来是这样的,由op.params[1].tmpref.buffer传递给TA(TA那边不会用到这个参数)
4)time(NULL)%1000这个是随机数种子,这个必须取余运算,否则TA会报内存溢出错误,猜测是time(NULL)太大了,大家自己也可以试试,由op.params[1].tmpref.size传递给TA
#include
#include
#include
#include
/* OP-TEE TEE client API (built by optee_client) */
#include
#define TA_RANDOM_CMD_GENERATE 0
#define PTA_STATIC_JELLY_UUID { 0xaabbccee, 0xddde, 0x4053, \
{ 0xa5, 0xa9, 0x7b, 0x3c, 0x4d, 0xdf, 0x13, 0xdd } }
int main(void)
{
TEEC_Result res;
TEEC_Context ctx;
TEEC_Session sess;
TEEC_Operation op = { 0 };
TEEC_UUID uuid = PTA_STATIC_JELLY_UUID;
uint8_t random_uuid[16] = { 0 };
char datanouse[] = "sadadawdw";
uint32_t err_origin;
int i;
/* Initialize a context connecting us to the TEE */
res = TEEC_InitializeContext(NULL, &ctx);
if (res != TEEC_SUCCESS)
errx(1, "TEEC_InitializeContext failed with code 0x%x", res);
printf("#############Begin Static Test#############\n");
printf("TEE init success...\n");
printf("Static UUID = aabbccee-ddde-4053-a5a9-7b3c4ddf13dd\n");
/*
* Open a session to the Random example TA, the TA will print "hello
* world!" in the log when the session is created.
*/
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);
printf("TEE Opensession success...\n");
printf("TEE Static Verify success...\n");
/*
* Execute a function in the TA by invoking it, in this case
* we're incrementing a number.
*
* The value of command ID part and how the parameters are
* interpreted is part of the interface provided by the TA.
*/
/* Clear the TEEC_Operation struct */
memset(&op, 0, sizeof(op));
/*
* Prepare the argument. Pass a value in the first parameter,
* the remaining three parameters are unused.
*/
op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_OUTPUT,
TEEC_MEMREF_TEMP_INPUT,
TEEC_NONE, TEEC_NONE);
op.params[0].tmpref.buffer = random_uuid;
op.params[0].tmpref.size = sizeof(random_uuid);
op.params[1].tmpref.buffer = datanouse;
op.params[1].tmpref.size = time(NULL)%1000; //随机数种子
//printf("op.params[1].tmpref.size = %d\n",op.params[1].tmpref.size);
/*
* TA_EXAMPLE_RANDOM_GENERATE is the actual function in the TA to be
* called.
*/
//printf("Invoking TA to generate random UUID... \n");
res = TEEC_InvokeCommand(&sess, TA_RANDOM_CMD_GENERATE,
&op, &err_origin);
if (res != TEEC_SUCCESS)
errx(1, "TEEC_InvokeCommand failed with code 0x%x origin 0x%x",
res, err_origin);
printf("#############Static Test Gen Random Data #############\n");
printf("Data = ");
for (i = 0; i < 16; i++)
printf("%02x", random_uuid[i]);
printf("\n");
/*
* We're done with the TA, close the session and
* destroy the context.
*
* The TA will print "Goodbye!" in the log when the
* session is closed.
*/
printf("#############End Static Test#############\n");
TEEC_CloseSession(&sess);
TEEC_FinalizeContext(&ctx);
return 0;
}
host的Makefile文件内容如下:
CC ?= $(CROSS_COMPILE)gcc
LD ?= $(CROSS_COMPILE)ld
AR ?= $(CROSS_COMPILE)ar
NM ?= $(CROSS_COMPILE)nm
OBJCOPY ?= $(CROSS_COMPILE)objcopy
OBJDUMP ?= $(CROSS_COMPILE)objdump
READELF ?= $(CROSS_COMPILE)readelf
OBJS = main.o
CFLAGS += -Wall -I./include
CFLAGS += -I$(TEEC_EXPORT)/include
LDADD += -lteec -L$(TEEC_EXPORT)/lib
BINARY = optee_static_test
.PHONY: all
all: $(BINARY)
$(BINARY): $(OBJS)
$(CC) -o $@ $< $(LDADD)
.PHONY: clean
clean:
rm -f $(OBJS) $(BINARY)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
6、切换到编译目录:
cd /home/jelly/qemu/build
然后执行命令:
sudo make run
输入密码,等待编译完成,在界面输入c,回车
然后REE测执行optee_static_test命令就会完成静态TA的调用示例,如下图所示
结尾备注:
在没有研究静态TA的时候,其实是不明白何为静态TA,何为动态TA,初学者只知道在optee_examples文件夹内创建自己的新的TA,我直到研究静态TA之前也是这样的理解程度。最后查了各种资料,包括网上资料和书本资料,最后大致明白静态TA和动态TA的调用其实是和CA那边完全没得任何关系的,optee_examples里面的CA怎么调用的TA就还是按照原来的方法调用,区别在于静态TA的源码是在core里面的,而且在代码尾部必须进行注册pseudo_ta_register,见上面的.c的源代码。
在CA端的host里面的主函数开始调用TA时,CA会根据TA的uuid进行TA的调用,当UUID传到TA端时,TA端的内部函数会拿着这个UUID先在已经存在静态TA里面寻找对应的TA,如果找不到,才会去静态TA寻找,寻找到UUID,如果是在静态TA这边直接进行调用即可;如果在动态TA这边,就先加载动态TA,然后在执行动态TA的功能。CA-TA的调用内部流程如下图所示,该图片摘自《手机安全和可信应用开发指南 TrustZone与OP-TEE技术详解》
从上图中可以看出,当用户调用TA的时候,先从已经创建的TA链表中查找(我的理解是之前调用过的TA就会被放在这个TA链表中,以后调用就直接调用,再也不用去静态TA或者动态TA中找了),然后再去静态TA中寻找,静态TA中找不到再去动态TA中寻找。
好了,差不多了,之后再有新的理解看再来补充。
2021年1月20日下午5点补充:
针对main.c里面的op.paramTypes有了新的理解,之前理解的不够透彻,今天突然理解透了来记录一下。
op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_OUTPUT,
TEEC_MEMREF_TEMP_INPUT,
TEEC_NONE, TEEC_NONE);
op.params[0].tmpref.buffer = random_uuid;
op.params[0].tmpref.size = sizeof(random_uuid);
op.params[1].tmpref.buffer = datanouse;
op.params[1].tmpref.size = time(NULL)%1000;
TEEC_PARAM_TYPES里面有四个参数,分别对应op.params[0],op.params[1],op.params[2],op.params[3]这个四个参数的类型。如上面代码所示:
1)TEEC_MEMREF_TEMP_OUTPUT,对应op.params[0]参数,使用时就是使用tmpref.buffer和tmpref.size参数,不能调用value.a和value.b参数,否则会报内存错
2)TEEC_MEMREF_TEMP_INPUT,对应op.params[1]参数,使用时只能tmpref.buffer和tmpref.size参数,不能调用value.a和value.b参数,否则也会报内存错
3)当时我是想传随机数种子进入TA,由于没理解清楚,始终传不成功,一直报内存错误,后来理解清楚,就大致明白了op.params[N]里面有既有value也有tmpref,根据我测试结果,这两个参数是不能同时用的。如果我当时只想传随机数参数的话,只需要像下面这样设置就可以了
op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_OUTPUT,
TEEC_VALUE_INPUT,
TEEC_NONE, TEEC_NONE);
op.params[0].tmpref.buffer = random_uuid;
op.params[0].tmpref.size = sizeof(random_uuid);
op.params[1].value.a = time(NULL)%1000;
这样设置后,op.params[1].tmpref.buffer也不能用了,否则也会报内存错误。然后在TA那边也设置成这样,随机数种子参数改为value.a就可以了,终于不会报内存错误了,理解透了,在此记录一下。