1. 摘要
本文是《手机安全与可信应用开发指南:TrustZone和OP-TEE技术详解》的读书笔记,需要更详细的内容和源码,请自行购买书籍。第1章 可信执行环境
1.2 TEE如何保护数据安全
为了给移动设备提供一个安全的运行环境,ARM从ARMv6的架构开始引入了TrustZone技术。TrustZone技术将中央处理器(Central Processing Unit, CPU)的工作状态分为了正常世界状态(Normal World Status, NWS)和安全世界状态(Secure World Status, SWS)。支持TrustZone技术的芯片提供了对外围硬件资源的硬件级别的保护和安全隔离。当CPU处于正常世界状态时,任何应用都无法访问安全硬件设备,也无法访问属于安全世界状态下的内存、缓存(Cache)以及其他外围安全硬件设备。
在整个系统的软件层面,一般的操作系统(如Linux、Android、Windows等)以及应用运行在正常世界状态中,TEE运行在安全世界状态中,正常世界状态内的开发资源相对于安全世界状态较为丰富,因此通常称运行在正常世界状态中的环境为丰富执行环境(Rich Execution Environment,REE),而可信任的操作系统以及上层的可信应用(Trusted Application, TA)运行于安全世界状态,运行在安全世界状态中的系统就是前文提到的TEE。
CPU在访问安全设备或者安全内存地址空间时,芯片级别的安全扩展组件会去校验CPU发送的访问请求的安全状态读写信号位(Non-secure bit, NS bit)是0还是1,以此来判定当前CPU发送的资源访问请求是安全请求还是非安全请求。
在真实环境中,可以将用户的敏感数据保存到TEE中,并由可信应用(Trusted Application, TA)使用重要算法和处理逻辑来完成对数据的处理。当需要使用用户的敏感数据做身份验证时,则通过在REE侧定义具体的请求编号(IDentity, ID)从TEE侧获取验证结果。验证的整个过程中用户的敏感数据始终处于TEE中,REE侧无法查看到任何TEE中的数据。对于REE而言,TEE中的TA相当于一个黑盒,只会接受有限且提前定义好的合法调用,而至于这些合法调用到底是什么作用,会使用哪些数据,做哪些操作在REE侧是无法知晓的。如果在REE侧发送的调用请求是非法请求,TEE内的TA是不会有任何的响应或是仅返回错误代码,并不会暴露任何数据给REE侧。
1.3.1 智能手机领域的TEE
Google规定在Android M之后所有的Android设备在使用指纹数据时都需要用TEE来进行保护,否则无法通过Google的CTS认证授权,另外Android也建议使用硬件Keymaster和gatekeeper来强化系统安全性。1.4 为什么选择OP-TEE
OP-TEE是由非营利的开源软件工程公司Linaro开发的,从git上可以获取OP-TEE的所有源代码。
OP-TEE是按照GP规范开发的,支持QEMU、Hikey(Linaro推广的96Board系列平台之一,使用Hisilicon处理器)以及其他通用的ARMv7/ARMv8平台,开发环境搭建方便,便于开发者开发自有的上层可信应用,且OP-TEE提供了完整的软件开发工具包(Software Development Kit, SDK),方便编译TA和CA。OP-TEE遵循GP规范,支持各种加解密和电子签名验签算法以便实现DRM、在线支付、指纹和虹膜识别功能。OP-TEE也支持在芯片中集成第三方的硬件加解密算法。除此之外,在IoT和车载芯片领域也大都使用OP-TEE作为TEE解决方案。
第2章 ARM的TrustZone技术
为提高系统的安全性,ARM早在ARMv6架构中就引入了TrustZone技术,且在ARMv7和ARMv8中得到增强,TrustZone技术能提供芯片级别对硬件资源的保护和隔离。
当处理器核处于安全状态时只能运行TEE侧的代码,且具有REE侧地址空间的访问权限。当处理器核处于非安全状态时只能运行REE侧的代码,且只能通过事先定义好的客户端接口来获取TEE侧中特定的数据和调用特定的功能。
2.1.2 ARMv7架构的TrustZone技术
ARMv7架构中使用了TrustZone技术的系统软件层面的框图如图2-2所示。
当系统在REE侧或者TEE侧运行时,系统执行smc(安全监控模式调用)指令进入Monitor模式,通过判定系统SCR寄存器中对应的值来确定请求来源(REE/TEE)以及发送目标(REE/TEE),相关寄存器中的值只有当系统处于安全态时才可以更改。
2.2 ARM安全扩展组件
TrustZone技术之所以能提高系统的安全性,是因为对外部资源和内存资源的硬件隔离。这些硬件隔离包括中断隔离、片上RAM和ROM的隔离、片外RAM和ROM的隔离、外围设备的硬件隔离、外部RAM和ROM的隔离等。实现硬件层面的各种隔离,需要对整个系统的硬件和处理器核做出相应的扩展。这些扩展包括:
□ 对处理器核的虚拟化,也就是将AMR处理器的运行状态分为安全态和非安全态。
□ 对总线的扩展,增加安全位读写信号线。
□ 对内存管理单元(Memory Management Unit, MMU)的扩展,增加页表的安全位。
□ 对缓存(Cache)的扩展,增加安全位。
□ 对其他外围组件进行了相应的扩展,提供安全操作权限控制和安全操作信号。
2.2.1 AXI总线上安全状态位的扩展
为了支持TrustZone技术,控制处理器在不同状态下对硬件资源访问的权限,ARM对先进可扩展接口(Advanced eXtensible Interface, AXI)系统总线进行了扩展。在原有AXI总线基础上对每一个读写信道增加了一个额外的控制信号位,用来表示当前的读写操作是安全操作还是非安全操作,该信号位称为安全状态位(NS bit)或者非安全状态位(Non-Secure bit)。
2.2.2 AXI-to-APB桥的作用
TrustZone同样能够保护外围设备的安全,例如中断控制、时钟、I/O设备。
AMBA3规范包含了一个低门数、低带宽的外设总线,被称作外设总线(Advanced Peripheral Bus,APB), APB通过AXI-to-APB桥连接到系统总线上。
2.2.3 TrustZone地址空间控制组件
TrustZone地址空间控制组件(TrustZone Address Space Controller, TZASC是AXI总线上的一个主设备,TZASC能够将从设备全部的地址空间分割成一系列的不同地址范围。
在安全状态下,通过编程TZASC能够将这一系列分割后的地址区域设定成安全空间或者是非安全空间。被配置成安全属性的区域将会拒绝非安全的访问请求。
使用TZASC主要是将一个AXI从设备分割成几个安全设备,例如off-Soc、DRAM等。
需要注意的是,TZASC组件只支持存储映射设备对安全和非安全区域的划分与扩展,但不支持对块设备(如EMMC、NANDflash等)的安全和非安全区域的划分与扩展。图2-4所示为使用TZASC组件的例子。
2.3 TrustZone技术对资源隔离的实现
ARM处理器核的虚拟化和资源隔离是TrustZone实现安全需求的根本。支持TrustZone的处理器核具有虚拟化,也即将一个物理核分成安全状态和非安全状态。
2.3.2 片上RAM和片上ROM的隔离
芯片内部存在小容量的RAM或者ROM,以供芯片上电时运行芯片ROM或者存放芯片自身相关的数据。TrustZone架构对该部分也进行了隔离操作。隔离操作通过使用TZMA和TZPC组件来实现。
2.3.4 外围设备的隔离
其他外围设备都会挂载到APB总线上,然后通过AXI-to-APB桥连接到AXI总线上,AXI-to-APB结合TZPC组件的TZPCDECROT的值及访问请求的PROT信号来判定该访问是否有效。当处理器需要访问外围设备时,会将地址和PROT信号发送到AXI总线上。
第4章 OP-TEE运行环境的搭建及编译
4.2 运行CA和TA示例
4.2.3 CA端代码的修改
若读者需添加新的功能,可按照GP规范调用REE侧的相关接口,编辑完CA端的代码后就需要修改host目录下的Makefile文件,将需要编译进CA的文件添加到Makefile中,主要是修改host/Makefile文件中的OBJS变量和BINARY变量,其中OBJS变量存放的是需要编译到CA的目标文件或者库文件,BINARY是编译完成后的可执行文件的名字。注意,在CA的头文件中需要定义UUID和command ID的宏,且定义的内容需要与TA中的UUID和command ID一致,否则执行CA后将会导致调用失败,关于UUID的值并没有特殊的要求,只需按照其格式定义一个唯一的字符串即可。
4.2.4 TA端代码的修改
ta目录中存放的是该TA的源代码、makefile文件和头文件,其中ta目录中必须存在一个user_ta_header.h文件,该文件在编译TA镜像或者是整个工程时会被使用到。在该文件中会定义UUID的宏、该TA运行的堆栈空间的大小以及版本信息。在TA的头文件中需要定义UUID的宏和command ID,且必须与CA中定义的一样,否则CA端将无法调用该TA中对应的操作。修改ta/Makefile文件,将该文件中BINARY变量的值修改成与CA中相同的UUID值。
修改完成后运行build_ta_mytest_qemu.sh脚本就能单独编译CA和TA,如果出现错误,则根据提示进行修改,编译成功后会在ta目录中生成与UUID值一样的elf文件,在host目录中将会生成与host/Makefile文件中BINARY变量的值一样的文件。
第8章 OP-TEE在REE侧的上层软件
8.1 OP-TEE的软件框架
OP-TEE的软件分为REE侧部分和TEE侧部分,分别包括CA、REE侧接口库(libteec)、常驻进程(tee_supplicant)、OP-TEE驱动、OP-TEE OS、TA等部分。使用OP-TEE来实现特定的安全功能需要开发者根据实际需求开发特定的CA和TA程序并集成到OP-TEE中。CA端负责在REE侧实现该新功能在用户空间的对外接口,TA端的代码则是在OP-TEE OS的用户空间负责实现具体的安全功能,例如使用何种算法组合来对数据进行安全处理、对处理后的数据的安全保存、解密加密数据等功能,如图8-1所示为OP-TEE软件的整体框图。8.2 REE侧libteec库提供的接口
CA使用libteec库中提供的接口来实现对TEE侧TA中具体命令的调用。libteec库是OP-TEE提供给用户在Linux用户空间使用的接口的实现,对于该部分每家芯片厂商可能不一样,但对外的接口都遵循GP规范中CA的接口进行定义。本章将以OP-TEE的实现方法为例进行介绍。libteec库的所有源代码存放在optee_client/libteec目录下,OP-TEE提供给Linux端使用的接口源代码的实现存放在optee_client/libteec/ src/tee_client_api.c文件中。
8.2.1 libteec库提供的接口说明
libteec库提供给上层用户使用的API一共有10个,都按照GP标准进行定义,使用这10个API能够满足用户在Linux用户空间的需求,在系统中这部分会被编译成libteec库,保存在REE侧的文件系统中以备上层使用。上述10个函数的功能和实现说明如下:
1.TEEC_InitializeContext
函数原型:
函数作用描述:
初始化一个TEEC_Context变量,该变量用于CA和TEE之间建立联系。其中参数name用来定义TEE的身份,如果该参数为NULL,则CA将会选择默认的TEE方案来建立联系。该API必须是CA调用的第一个libteec库的API,且该API不会触发TA的执行。
参数说明:
name:指向TEE的名字,一般情况下该值设置成NULL,使其选择默认的TEE方案进行连接。
ctx:指向一个类型为TEEC_Context的变量的地址,该变量会用于CA与TA之间的连接和通信。
函数返回值:
TEEC_SUCCESS:初始化操作成功。其他返回值表示初始化失败。
2. TEEC_FinalizeContext
函数原型:
函数作用描述:释放一个已经被初始化的类型为TEEC_Context的变量,关闭CA与TEE之间的连接。在调用该函数之前必须确保打开的session已经被关闭。
参数说明:
ctx:指向一个类型为TEEC_Context的变量,该变量会用于CA与TA之间的连接和通信。
函数返回值:无。
3. TEEC_OpenSession
函数原型:
函数作用描述:
打开一个CA与对应TA之间的一个session,该session用于CA与对应TA之间的联系,CA需要连接的TA是由UUID指定的。session具有不同的打开和连接方式,根据不同的打开和连接方式CA可以在执行打开session时传递数据给TA,以便TA对打开操作进行权限检查。各种打开方式说明如下。
TEEC_LOGIN_PUBLIC:不需要提供,即connectionData的值必须为NULL。
TEEC_LOGIN_USER:提示用户链接,connectionData的值必须为NULL。
TEEC_LOGIN_GROUP: CA以组的方式打开session。connectionData的值必须指向一个类型为uint32_t的数据,其包含某一组的特定信息。在TA端将会对connectionData的数据进行检查,判定CA是否真属于该组。
TEEC_LOGIN_APPLICATION:以application的方式连接,connectionData的值必须为NULL。
TEEC_LOGIN_USER_APPLICATION:以用户程序的方式连接,connectionData的值必须为NULL。
TEEC_LOGIN_GROUP_APPLICATION:以组应用程序的方式连接,其中connectionData需要指向一个uint32_t类型的变量。在TA端将会对connectionData的数据进行权限检查,查看连接是否合法。
参数说明:
context:指向一个类型为TEEC_Context的变量,该变量用于CA与TA之间的连接和通信,调用TEEC_InitializeContext函数进行初始化;
session:存放session内存的变量;
destination:指向存放需要连接TA的UUID的值的变量;
connectionMethod:CA与TA的连接方式,详细可参考函数描述中的说明;
connectionData:指向需要在打开session时传递给TA的数据;
operation:指向TEEC_Operation结构体的变量,变量中包含了一系列用于与TA进行交互使用的buffer或者其他变量。如果在打开session时CA和TA不需要交互数据,则可以将该变量指向NULL;
returnOrigin:用于存放从TA端返回的结果的变量。如果不需要返回值,则可以将该变量指向NULL。
函数返回值:
TEEC_SUCCESS:初始化操作成功;其他返回值表示初始化失败。
4.TEEC_CloseSession
函数原型:
函数作用描述:
关闭已经被初始化的CA与对应TA之间的session,在调用该函数之前需要保证所有的command已经执行完毕。如果session为NULL,则不执行任何操作。
参数说明:
session:指向已经初始化的session结构体变量。
函数返回值:无。
5. TEEC_InvokeCommand
函数原型:
函数作用描述:通过cmd_id和打开的session来通知session对应的TA执行cmd_id指定的操作。
参数说明:
session:指向已经初始化的session结构体变量;
cmd_id: TA中定义的command的ID值,让CA通知TA执行哪条command;
operation:已经初始化的TEEC_Operation类型的变量,该变量中包含CA与TA之间进行交互的buffer、缓存的属性等信息;
error_origin:调用TEEC_InvokeCommand函数时,TA端的返回值。
6. TEEC_RequestCancellation
函数原型:
函数作用描述:
取消某个CA与TA之间的操作,该接口只能由除执行TEEC_OpenSession和TEEC_InvokeCommand的线程之外的其他线程进行调用,而TA端或者TEE OS可以选择并不响应该请求。只有当operation中的started域被设置成0之后,该操作方可有效。
参数说明:
operation:已经初始化的TEEC_Operation类型的变量,该变量中包含CA与TA之间进行交互的buffer、缓存的属性等信息。
7. TEEC_RegisterShareMemory
函数原型:
函数作用描述:
注册一块在CA端的内存作为CA与TA之间的共享内存。
shareMemory结构体中的三个成员如下:
buffer:指向作为共享内存的起始地址;
size:共享内存的大小;
flags:表示CA与TA之间的数据流方向。
参数说明:
ctx:指向一个类型为TEEC_Context的变量,该变量必须已经被初始化;
8. TEEC_RegisterShareMemoryFileDescriptor
函数原型:
函数作用描述:
注册一个在CA与TA之间的共享文件,在CA端会将文件的描述符fd传递给OP-TEE,其内容被存放到shm中。
参数说明:
ctx:指向一个类型为TEEC_Context的变量,该变量必须已经被初始化;
shm:指向共享内存的结构体变量;
fd:共享的文件的描述符号。
9. TEEC_AllocateSharedMemory
函数原型:
函数作用描述:
分配一块共享内存,共享内存是由OP-TEE分配的,OP-TEE分配了共享内存之后将会返回该内存块的fd给CA, CA将fd映射到系统内存,然后将地址保存到shm中。
参数说明:
ctx:指向一个类型为TEEC_Context的变量,该变量必须已经被初始化;
shm:指向共享内存的结构体变量。
10. TEEC_ReleaseSharedMemory
函数原型:
void TEEC_ReleaseSharedMemory(TEEC_SharedMemory *shm)
函数作用描述:释放已经被分配或者注册过的共享内存。
参数说明:
shm:指向共享内存的结构体变量。
8.2.2 CA调用libteec库中接口的流程
CA在使用libteec库中的接口来实现调用TA的操作时,一般过程是需要先建立context,然后建立与需要调用的TA之间的session,再通过执行invoke操作向TA发送command ID来实现具体的操作需求,待TA中command ID的内容执行完成之后,如果后续也不需要再次调用TA时,可以通过closesession和final context来释放资源,完全关闭该CA与TA之间的联系。一次完整的操作过程如图8-2所示。
8.3 REE侧的守护进程—tee_supplicant
tee_supplicant是常驻在Linux内核中的一个进程,主要作用是使OP-TEE能够通过tee_supplicant来访问REE侧的资源。例如加载存放在文件系统中的TA镜像到TEE中,对REE侧数据库的操作,对EMMC中RPMB分区的操作,提供socket通信等。其源代码在optee_client/tee-supplicant目录中。编译之后会生成一个名为tee_supplicant的可执行文件,该可执行文件在REE启动时会作为一个后台守护程序被自动启动。
8.3.1 tee_supplicant编译生成和自启动
系统启动tee_supplicant的过程如图8-3所示。
8.3.2 tee_supplicant入口函数
tee_supplicant作为Linux中的一个守护进程,起到处理RPC请求的服务器端的作用,通过类似于C/S的方式,为OP-TEE提供对REE侧资源进行操作的实现。该可执行文件的入口函数存放在optee_client/tee-supplicant/src/tee_supplicant.c文件中。
8.3.3 tee_supplicant存放RPC请求的结构体
在tee_supplicant中用于接收和发送请求的数据都存放在类型为tee_rpc_invoke的结构体变量中。该结构体内容如下:
8.3.4 tee_supplicant中的无限循环
tee_supplicant启动后最终会进入一个无限循环,调用process_one_request函数来监控、接收、处理、回复OP-TEE的请求。整个处理过程如图8-5所示。
8.3.7 RPC请求的处理
当解析完来自TA的RPC请求,获取到具体参数后,在process_one_request函数中会根据请求的功能ID来决定具体执行什么操作。
这些操作包括:
□ 从文件系统中读取TA的镜像保存在共享内存中;
□ 对文件系统中的节点进行读/写/打开/关闭/移除等操作;
□ 执行RPMB(EMMC中的RPMB分区)相关操作;
□ 分配共享内存;
□ 释放共享内存;
□ 处理gprof请求;
□ 执行网络socket请求。
8.4 各种RPC请求的处理
tee_supplicant获取到远程过程调用(Remote Procedure Call, RPC)请求后会解析出功能ID,然后根据该ID值来命中tee_supplicant提供的具体操作。当请求处理完成后会将处理结果和数据发送给OP-TEE驱动,OP-TEE驱动最终会触发安全监控模式调用(smc)将数据传递给OP-TEE。
8.4.1 加载TA镜像
请求加载TA镜像的功能ID为RPC_CMD_LOAD_TA。执行该功能时,tee_supplicant会到文件系统中将TA镜像的内容读取到共享内存中。该操作是通过调用load_ta函数来实现的,该函数定义在tee_supplicant.c文件中,在REE侧加载TA镜像文件的整体流程如图8-6所示。
当load_ta执行完成并正确读取了TA镜像文件的信息之后,最终会将读取到的数据通过调用write_response函数,将数据发送给OP-TEE驱动,由驱动来完成将数据发送给OP-TEE的操作。OP-TEE会对接收到的TA镜像的合法性进行校验,主要是验证TA镜像文件的电子签名是否合法。
8.4.2 操作REE侧的文件系统
当功能ID为RPC_CMD_FS时,tee_supplicant会根据TA请求调用tee_supp_fs_process函数来完成对文件系统的具体操作,包括常规的文件和目录的打开、关闭、读取、写入、重命名、删除等。
tee_supp_fs_process函数主要是对REE侧文件系统进行操作。如果执行的是open、create操作则会返回文件的操作句柄fd值给OP-TEE;如果是write操作则会将需要写的内容写到具体的文件中。
8.4.3 操作RPMB
当功能ID为RPC_CMD_RPMB时,tee_supplicant会根据TA请求调用process_rpmb函数来完成对EMMC中rmpb分区的操作。EMMC中的RPMB分区,在读写过程中会执行验签和加解密的操作。
8.4.4 分配共享内存
当功能ID为RPC_CMD_SHM_ALLOC时,tee_supplicant会根据TA请求调用process_alloc函数来分配TA与tee_supplicant之间的共享内存。
8.4.5 释放共享内存
当功能ID为RPC_CMD_SHM_FREE时,tee_supplicant会根据TA请求调用process_free函数来释放TA与tee_supplicant之间的共享内存。
8.4.6 记录程序执行效率
当功能ID为RPC_CMD_GPROF时,tee_supplicant会根据TA请求调用gprof_process函数将某个特定的TA执行效率信息记录到文件系统中。
8.4.7 网络套接字操作
当功能ID为OPTEE_MSG_RPC_CMD_SOCKET时,tee_supplicant会根据TA请求调用tee_socket_process函数来完成网络套接字(socket)的相关操作,包括网络套接字的建立、发送、接收和ioctl操作。
第9章 REE侧OP-TEE的驱动
本章介绍了libteec库中的接口和tee_supplicant的调用在驱动中的具体实现,libteec库中的接口主要是发送REE侧的请求到OP-TEE, REE侧与OP-TE之间的数据传递是通过共享内存的方式来实现的,而该共享内存是在挂载驱动时被分配好的。
从tee_supplicant处理来自OP-TEE的请求过程来看主要有三点。
□ 驱动在触发安全监控模式调用后会进入到loop循环中,根据OP-TEE中的返回值来判定该返回是来自OP-TEE的RPC请求还是CA请求的处理结果。如果是RPC请求,也就是需要驱动或者tee_supplicant执行相关操作,驱动将RPC请求保存到OP-TEE驱动的请求消息队列中,然后等待直到收到处理结果;
□ tee_supplicant作为一个常驻进程存在于Linux中,它会不停地尝试从驱动的请求消息队列中获取来自OP-TEE的请求。如果请求消息队列中并没有请求则会一直等待,直到拿到请求才返回,拿到请求之后会对请求进行解析,然后根据请求ID执行具体的操作;
□ tee_supplicant处理完来自OP-TEE的请求后,会调用send操作将处理结果存放到该消息队列的参数区域,并使用complete函数通知OP-TEE驱动该请求已经被处理完毕。OP-TEE驱动block住的地方可以继续往下执行,通过安全监控模式调用将结果返回给OP-TEE。
9.2 REE侧OP-TEE驱动的加载
OP-TEE驱动是REE侧与TEE侧之间进行数据交互的桥梁。tee_supplicant和libteec库中的接口最终都会通过系统调用的方式陷入到Linux内核空间,然后Linux内核根据传递的参数找到OP-TEE驱动,并命中驱动的operation结构体中的具体处理函数来完成实际的操作。对于OP-TEE驱动,一般会触发安全监控模式调用(smc),并带参数进入到ARM核的Monitor模式或EL3中,在Monitor模式或EL3中执行正常世界状态(NWS)与安全世界状态(SWS)之间的切换,待状态切换完成后,会将驱动端带入的参数传递给OP-TEE中的线程进行进一步的处理。OP-TEE驱动的源代码存放在linux/drivers/tee目录中。
OP-TEE驱动的加载过程分为两部分,第一部分是创建class和分配设备号,第二部分是probe过程。在正式介绍OP-TEE具体内容之前,首先需要明白两个Linux内核中加载驱动的宏:subsys_initcall和module_init。OP-TEE驱动的第一部分是调用subsys_initcall宏来实现,而第二部分则是调用module_init宏来实现。整个OP-TEE驱动的初始化流程如图9-1所示。
OP-TEE驱动会创建两个设备,分别为/dev/tee0和/dev/teepriv0,这两个设备分别被libteec库和tee_supplicant使用,用于实现各自的功能,而驱动与TEE侧之间的数据传递是通过共享内存的方式来完成的,即在OP-TEE驱动挂载过程中会创建OP-TEE与TEE之间的专用共享内存空间,在Linux的用户空间需要发送到TEE的数据最终都会被保存在该共享内存中,然后再切换ARM核的状态后,OP-TEE从该共享内存中去获取数据。
9.6 libteec库中的接口在驱动中的实现
驱动挂载完成后,CA程序通过调用libteec库中的接口调用OP-TEE驱动来穿透到OP-TEE中,然后调用对应的TA程序。OP-TEE驱动在挂载完成后会在/dev目录下分别创建两个设备节点,分别为/dev/tee0和/dev/teepriv,对/dev/tee0设备进行相关操作就能够穿透到OP-TEE中实现特定请求的发送。
9.6.4 libteec库中的open session操作
当用户调用libteec库中的TEEC_OpenSession接口时会执行OP-TEE驱动中ioctl函数的TEE_IOC_OPEN_SESSION分支去执行tee_ioctl_open_session函数,该函数只会在打开/dev/tee0设备后才能被使用。整个open session的操作流程如图9-3所示。
调用过程中使用optee_do_call_with_arg函数来完成驱动与OP-TEE之间的交互。
9.6.5 libteec库中的invoke操作
当完成session的打开操作后,用户就可以调用TEEC_InvokeCommand接口来调用对应的TA中特定的操作了,TEEC_InvokeCommand函数最终会调用驱动的tee_ioctl_invoke函数来完成具体的操作。
第19章 OP-TEE中的密码学算法
OP-TEE根据GP规范实现了常用的加解密、签名验签和计算摘要的密码学算法的基础框架。如果芯片厂商需使用硬件的密码学引擎来实现这些算法,则只需替换掉对应的底层算法实现接口即可。对于上层用户而言无需修改任何代码,只需按照GP规范,调用对应的接口组合即可实现对数据的加解密、摘要计算和数据的签名验签操作。
19.1 算法使用示例
OP-TEE根据GP规范支持当前主流的基本算法,包括RAS、AES、HMAC、SHA、RANDOM等。本章将介绍在OP-TEE中添加一个TA和CA来调用上述算法的GP接口,实现对数据的加密、解密、签名、验签、计算哈希值等操作。在xtest中也有上述算法的接口调用示例,但比较零散,并不适合开发者直接引用。例如在xtest中,如果要对数据进行AES加密操作,在xtest中可能需要在TA和CA之间多次传递数据来才可完成。而正常的用户希望能达到的效果是在CA中带入需要被处理的数据,调用接口就能够对数据完成AES加密操作。
19.2 OP-TEE中的SHA算法
SHA算法主要用于计算数据的摘要,该算法的特点是具有不可逆性,外界不可能通过摘要的值计算出原始数据的内容。SHA算法主要包括SHA1、SHA256、SHA244、SHA384、SHA512。OP-TEE使用同一套接口来实现这些算法,只是在调用各接口函数时输入的参数有所不同,各SHA算法执行后输出的数据长度和算法ID如表19-1所示。
19.2.1 TA中使用SHA算法的实现
GP规范定义了一类用于在TEE侧计算摘要的接口函数,一次完整的摘要计算需要在TA中带参数依次调用如下函数:
TEE_AllocateOperation函数会分配一个算法操作句柄,用于规定当前操作是计算摘要操作还是加解密操作或签名验签操作。TEE_DigestUpdate用于将需要计算摘要的数据填充到操作句柄的数据区域中。TEE_DigestDoFinal用于触发最终的计算摘要操作。上述三个接口函数最终都会通过系统调用陷入OP-TEE内核空间,在OP-TEE内核空间调用密码学系统服务提供的接口完成摘要的计算。在调用TEE_AllocateOperation函数时需要带入算法ID和模式。
19.3 OP-TEE中的AES算法
AES算法是对称加解密算法,使用时需要利用AES密钥和初始化向量IV来加解密数据。解密操作时必须使用相同的AES密钥和IV值,否则解密出来的数据是不正确的。
19.3.1 TA中使用AES算法的实现
GP规范中定义了一类用于在TEE侧使用AES算法对数据进行加解密的接口函数,完成一次完整的AES加解密需要在TA中带参数依次调用如下函数:
这些接口的名称以及作用如表19-2所示。
在执行AES操作之前,需要将AES密钥作为一个object填充到操作句柄中,然后填充数据进行初始化,再执行加解密操作,至于是加密还是解密操作由句柄的mode参数决定。
19.4 OP-TEE中的RSA算法
RSA算法是非对称算法,RSA算法支持加密、解密、签名、验签操作,执行上述操作时需要使用RSA私钥或者RSA公钥,操作与密钥类型的对应关系如表19-3所示。
19.4.1 TA中使用RSA算法的实现
GP规范中定义了一类用于在TEE侧使用RSA算法对数据进行加解密以及签名验签操作的接口函数,完成一次完整的RSA加解密需要在TA中带参数调用如下函数:
这些接口的名称以及作用如表19-4所示。
在执行RSA操作之前需要将密钥作为一个object填充到操作句柄中,然后使用对应的函数实现RSA的加密、解密、签名、验签操作。
第20章 OP-TEE的安全存储
20.1 安全存储简介
OP-TEE的安全存储功能是OP-TEE为用户提供的安全存储机制。用户可使用安全存储功能来保存敏感数据、密钥等信息。使用OP-TEE安全存储功能保存数据时,OP-TEE会对需要被保存的数据进行加密,且每次更新安全文件时所用的加密密钥都会使用随机数重新生成,用户只要调用GP标准中定义的安全存储相关接口就能使用OP-TEE的安全存储功能对私有数据进行保护。
需要被保护的数据被OP-TEE加密后会被保存到REE侧的文件系统、EMMC的RPMB分区或数据库中,至于具体需要将加密后的数据保存到哪里则由芯片提供商决定。
20.2 安全存储使用示例
安全存储功能的实现主要是通过对PersistentObject进行操作来完成,将需要被保存的数据填充到PersistentObject的相应位置,并调用对应的接口就能实现对安全文件的创建、打开、读取、写入、重命名、删除等操作。
20.3 安全存储功能使用的密钥
OP-TEE中使用安全存储功能保存的数据都是使用AES算法进行加密的,加密后的文件被保存在文件系统或RPMB分区。使用AES算法进行数据加密或解密时需提供密钥和初始化向量IV值。每个TA在使用安全存储功能保存数据时都会生成一个随机数作为IV值,使用FEK的值作为AES的密钥。FEK的值是OP-TEE对相关数据执行HMAC操作后生成的。FEK值的生成涉及SSK和TSK,本章节将介绍这些密钥的使用和生成过程。相关密钥的关系和生成方式如图20-2所示。
20.3.1 安全存储密钥
安全存储密钥(Secure Storage Key, SSK)在每台设备中的值都不同。OP-TEE启动时会使用芯片ID和HUK经HMAC算法计算来获得该值,并将SSK的值保存在结构体变量tee_fs_ssk的密钥成员中,以备生成其他密钥使用。工厂生产时会将HUK写入到OTP/efuse中,且正常世界状态无法读取到HUK的值,而芯片ID在芯片出厂后就会被写入到芯片中。
OP-TEE启动过程中会执行tee_fs_init_key_manager函数,该函数使用SSK =HMAC(HUK,message)的方式来生成SSK。
20.3.2 可信应用的存储密钥
可信应用的存储密钥(Trusted Applicant Storage Key, TSK)是生成FEK时使用到的密钥。TSK是使用SSK作为密钥对TA的UUID经HMAC计算获得,类似于HMAC(SSK, UUID)的方式生成TSK。在调用tee_fs_fek_crypt函数时会计算TSK的值。TSK最终会被用来生成FEK, FEK会在使用安全存储功能保存数据时被用来加密数据。
20.3.3 文件加密密钥
文件加密密钥(File Encryption Key, FEK)是安全存储功能用于对数据进行加密时使用的AES密钥,该密钥在生成文件时会使用PRNG算法随机产生,产生的FEK会使用TSK进行加密,然后保存到head.enc_fek变量中。TA在每次使用安全存储功能创建一个安全文件时就会生成一个随机数作为FEK,即每个TA中的每个安全文件都有一个FEK用于加密对应文件中的数据。关于FEK的产生可简单理解为如下公式,使用的初始化向量IV值为0:
AES_CBC(in_key, TSK)
OP-TEE通过调用tee_fs_fek_crypt函数来生成一个FEK。
20.4 安全文件、dirf.db文件的数据格式和操作过程
OP-TEE的安全存储功能可满足用户保存敏感数据的需求,需要被保存的数据会被加密保存到文件系统或RPMB分区中。当选择将数据保存到文件系统中时,默认情况下,加密后的数据会被保存在/data/tee目录中。安全存储功能使用二叉树的方式来保存加密后的文件。
当第一次使用安全存储功能创建用于保存敏感数据的安全文件时,OP-TEE将会在/data/tee目录中生成两个文件:dirf.db文件和以数字命名的文件。dirf.db文件保存的是整个安全存储功能管理的所有文件的目录信息和节点信息。当用户使用某个已经存在的安全文件时,OP-TEE首先会读取dirf.db文件中的相关内容,然后根据需要操作的安全文件名字的哈希值在dirf.db文件中找到对应的文件编号,最终按照这个编号实现对文件的打开、关闭、写入、读出、重命名、裁剪等操作。
保存在/data/tee目录以数字命名的文件是被安全存储保护的用户文件。该文件保存的是加密之后的用户数据,加密使用的密钥则是对应的FEK。
20.4.3 安全存储中的文件节点组成
在安全存储中,dirf.db文件和安全文件都是使用二叉树的方式来保存文件编号或数据块。dirf.db文件的数据块区域保存的是dirfile_entry结构体变量(密文保存), dirf.db文件中的节点区域保存的是与保存的数据块相对应的节点信息。通过查找dirf.db文件中的tee_fs_htree_node_image就能找到对应的dirfile_entry数据块的数据。在安全文件中同样也存在这样的对应关系,只不过数据块中保存的不再是dirfile_entry,而是实际需要被保存的数据。二叉树的保存方式如图20-5所示,第一个节点作为dirf.db文件或安全文件的根节点使用。
20.5 安全存储文件的创建
使用安全存储时首先需要创建并初始化该安全文件。如果在创建安全文件之前,/data/tee目录下没有dirf.db文件,则会先创建dirf.db文件并进行初始化。创建的dirf.db文件和安全文件具有相同的格式。所有对/data/tee目录下的文件进行的操作都是通过TEE侧发送RPC请求通知tee_supplicant来完成的。
20.5.1 安全存储软件框架
在OP-TEE中调用GP标准接口使用安全存储功能时,对文件的读写操作最终是由REE侧来完成的。OP-TEE无法直接操作REE侧的文件系统,故需通过发送RPC请求的方式通知tee_supplicant来完成对文件系统的操作,整个安全存储功能的软件框图如图20-7所示。
在TA中调用GP的接口最终会通过系统调用的方式陷入OP-TEE的内核空间,根据实际操作需求组装RPC请求需要的参数,并触发安全监控模式调用(smc)将RPC请求发送给tee_supplicant。tee_supplicant会解析出RPC请求的参数,并根据参数的定义对/data/tee目录下的文件进行具体操作。
20.5.3 安全文件的创建
在TA中调用TEE_CreatePersistentObject接口时会创建安全文件。在创建安全文件时会初始化安全文件的数据区域(初始化数据已加密)。整个安全文件的创建过程如图20-9所示。
安全文件创建完成之后,会将初始化数据加密后写入到安全文件中,然后更新整个安全文件的tee_fs_htree_node_image区域以及保存在文件头的tee_fs_htree_image区域,到此安全文件创建就已完毕。为后续能够通过dirf.db文件找到该安全文件,则还需要更新dirf. db文件的内容,主要是更新dirf.db文件数据区域中的dirfile_entry数据。
20.8 安全文件中数据的加解密
安全存储中的安全文件和dirf.db文件中的数据内容都是按照一定的格式保存的,主要由三部分组成:tee_fs_htree_image、tee_fs_htree_node_image和数据区域块。tee_fs_htree_image和tee_fs_htree_node_image结构体中保存的是安全文件操作时使用到的重要数据的密文数据,tee_fs_htree_image区域中的数据是对元数据经加密重要数据后生成的。而数据区域块和tee_fs_htree_node_image中的数据则是对数据块数据经加密后获得的。
第21章 可信应用及客户端应用的开发
TA的全称是Trust Application,即可信任应用程序。CA的全称是Client Applicant,即客户端应用程序。TA运行在OP-TEE的用户空间,CA运行在REE侧。CA执行时代入特定的UUID和命令ID参数就能实现请求特定TA执行特定操作的需求,并将执行结果返回给CA。通过CA对TA的调用可实现在REE侧对安全设备和安全资源的操作。普通用户无法知道TA的具体实现,例如操作使用了什么算法、操作了哪些资源、获取了哪些数据等,这也就确保了相关资源和数据的安全。
21.1 TA及CA的基本概念
GP规范定义了CA调用TA的所有接口以及相关结构体和变量类型,同时也定义了TEE侧用户空间的所有接口和相关结构体和变量类型。
1. TEE Contexts
TEE上下文(TEE Contexts)用于表示CA与TEE之间的抽象连接,即通过TEE上下文可将REE侧的操作请求发送到TEE侧。需注意的是,在执行打开CA与TA之间的会话之前必须先获取到TEE上下文。一般该值是打开REE侧的TEE驱动设备时返回的句柄,如果在REE侧支持多个TEE驱动,则在调用TEEC_InitializeContext时可指定具体的驱动设备名来获得特定的TEE上下文。
2. Session
会话(Session)是CA与特定TA之间的抽象连接。只有建立了CA与TA之间的会话后,CA才可调用TA中的命令来执行特定的操作。调用TEEC_OpenSession函数后,TEE会将建立的会话内容返回给CA,一个会话包含TEE上下文和会话ID值。
3. Commands
命令(Commands)是CA与TA之间通过会话进行具体操作的基础。在交互过程中,CA通过指定命令ID通知TA执行与命令ID匹配的操作。至于TA中执行什么操作则完全由TA开发者决定,命令ID只是CA与TA约定的某个特殊操作的ID值。
4. Share Memroy
共享内存(Share Memroy)被用于CA与TEE之间进行数据交互,CA可通过注册或分配的方式通知TEE注册或分配CA与TA之间的共享内存,CA和TEE对该块共享内存都具有指定的读写权限。
5. Memory References
Memroy Reference是CA与TEE之间一段固定范围的共享内存,Memory Reference可指定一个完整的共享内存块,也可指定共享内存块中的特定区域。
6. UUID
UUID是一个TA的身份标识ID。当CA需要调用某个TA时,TEE侧通过UUID来决定要加载和运行哪个TA镜像。
21.2 GP标准
GP标准的全称是GlobalPlatform,该标准对TEE的框架和安全需求做出了明确的规定,并对REE侧提供的接口函数、数据类型和数据结构体也做出了明确的定义,并对TEE侧提供给TA开发者使用的接口函数、数据类型、数据结构体做出了明确的规定和定义。关于GP规范与TEE相关的文档,读者可到如下链接中自行查阅和下载:
http://www.globalplatform.org/mediaguidetee.asp
对CA和TA的开发者而言,需要仔细阅读GP对REE侧和TEE侧各种接口函数和数据结构体的定义,只有熟悉了接口函数以及数据结构体的定义后才能正确使用这些接口来开发特定的CA和TA。
21.3 GP标准对TA属性的定义
TA的属性定义了该TA的运行方式、链接方式、堆栈大小、版本等信息。在GP标准中对一个TA所需要具有的属性进行了严格的定义和说明,这些属性的名称、作用、值的内容说明如表21-1所示。
OP-TEE中TA的扩展属性如表21-2所示。
需要被设定的TA属性都在TA源代码的user_ta_headr_defines.h文件中被定义,gpd.ta. appID的值通常被设置成该文件中TA_UUID的值。gpd.ta.singleInstance、gpd.ta.multiSession、gpd.ta.instanceKeepAlive的值通过在该文件中定义TF_FLAGS的值来确定。gpd.ta.dataSize的值由该文件中定义TA_DATA_SIZE的值来确定。gpd.ta.stackSize的值由该文件中定义TA_STACK_SIZE的值来确定。在OP-TEE中gpd.ta.version和gpd.ta.description的值使用默认值。gp.ta.description和gp.ta.version的值由TA_CURRENT_TA_EXT_PROPERTIES宏定义来确定。
21.4 GP标准定义的接口
GP官方网站中名称为TEE_Client_API_Specification-Vx.x_c.pdf的文档给出了这些接口的详细说明,根据发布版本的不同,定义的接口可能也会有所不同。TEE侧定义的接口函数属于内部接口,详细内容查阅GP提供的名称为GPD_TEE_Internal_Core_API_Specification_vx.x.pdf的文档。
21.4.1 GP定义的客户端接口
GP定义的客户端接口包括9个函数和1个宏。
21.4.2 GP定义的内部接口
GP定义的内部接口是供TEE侧的TA或其他功能模块使[插图]。大致可以分为Framwork层API、对数据和密钥操作的API、密码学操作API、时钟API、大整数算法API。
21.5 TA和CA的实现
第4章中提供了一个完整的CA和TA的示例,本节将详细介绍如何完成CA和TA源代码的实现。本节中并不涉及TA中的特定操作的实现,只是介绍如何搭建CA和TA的整体框架和设定相关的参数。
21.5.3 TA代码的实现
TA代码需实现具体功能的所有操作,TA被TEE调用的各种操作的入口函数就是在表21-4部分的API。所以需要在TA中实现这些API,最重要的是对TA_InvokeCommand-EntryPoint函数的实现。该函数需要定义各种命令ID对应的操作,至于每个命令ID需要实现什么功能就由开发者决定,但该命令ID的定义需要与CA中的命令ID的定义保持一致。
TA属性的设定可通过修改user_ta_head_defines.h文件来实现,主要需修改如下的宏定义:
□ TA_UUID:该TA的UUID值;
□ TA_FLAGS:TA的访问属性,具体内容请参阅21.3节和GP规范;
□ TA_STACK_SIZE:指定该TA运行时栈空间的大小;
□ TA_DATA_SIZE:指定该TA运行时堆空间的大小;
□ TA_CURRENT_TA_EXT_PROPERTIES:该TA的扩展属性,主要包括TA名字、版本等。
第23章 终端密钥在线下发系统
在终端设备实际使用过程中,终端设备与服务器端可能会存在通信的情况,为确保通信过程中数据的安全,一般会对通信数据进行加密操作。而在终端设备生产过程中,由于产品的生产批次和后续安全功能的扩展,通信所需要的密钥并不一定会在产品出厂之前就预置到终端设备中,此时就可使用TEE来构建终端密钥的在线下发系统来确保密钥被安全分发到特定的终端设备中,后期就可使用下发的密钥实现终端设备与服务器端进行密文通信。本章将介绍使用OP-TEE搭建的一种密钥在线下发系统。
本章给出了一个使用OP-TEE实现密钥在线下发的系统示例,使用该框架可实现密钥的密文在线下发功能,在下发过程中,所有数据都是以密文的形式存在的,密文生成的算法及加密使用的密钥都只有OP-TEE知道,在REE侧无法知晓数据的解密、加密以及保存方式。读者可根据自身实际需求修改密文组包的格式以及加密算法的类型和密钥的生成规则搭建自己的在线密钥下发系统,例如可将组包数据最后的哈希操作换成RSA算法签名,这样可确保下发数据的完整性和合法性。关于下发的密钥是如何产生的,读者亦可根据实际需求进行修改。
在使用下发的密钥时不可将密钥暴露到REE侧,这就需将使用密钥对数据进行密码学处理的操作也集成到该TA中,这样可以确保密钥的下发、保存、使用都处于一个隔离的安全状态。
23.1 密钥在线下发系统的框架
将下发的密文密钥包发送到OP-TEE中,由OP-TEE来完成对密文数据包的解密以及密钥的保存就能确保下发的密钥的安全性,如果在使用该密钥时也将相关加密操作放在OP-TEE中,这样可以确保密钥在任何时候都不被暴露在REE侧,这样可以构建一个安全的通信密文环境。整个终端密钥在线下发系统的框架图如图23-1所示。
密钥在线下发系统在REE侧会运行一个常驻进程用于接收服务器端发送的密文密钥数据包,该进程在接收到数据包后直接调用下发系统的CA接口,将数据发送给OP-TEE中密钥下发系统的TA,该TA会按照约定的数据格式和密钥解析并解密下发的数据包,从而获得明文的密钥,然后调用OP-TEE的安全存储功能保存该密钥,在使用时同样也通过该TA来获取该密钥。当然读者也可以借助自己的实际环境将密钥保存到希望保存的地方,但最好采取密文的形式保存该密钥。
23.2 密钥在线下发的数据包格式
密钥下发系统的数据包是以密文的形式被发送到终端设备,为确保数据的完整性和合法性,最后会对密钥数据包的内容使用RSA算法进行电子签名,数据包的格式如图23-2所示。
整个数据包分为密文数据区域和哈希区域,密文数据部分包含需要下发的数据经对称加密处理之后数据,而哈希区域则是使用SHA256算法计算的密文区域的哈希值。由于设备厂商在生产设备时会收集每台终端设备的相关硬件信息,所以可使用这些硬件信息作为因子用于生成加密使用的密钥。下发的密钥是通过盐值、密码和重复数经过PBKDF算法计算获得的。
PBKDF2(Password-Based Key Derivation Function)是一个用来导出密钥的函数,常用于生成加密的密码。它的基本原理是通过一个伪随机函数(例如HMAC函数),把明文和一个盐值作为输入参数,然后重复进行运算,并最终产生密钥。如果重复的次数足够大,破解的成本就会变得很高。而盐值的添加也会增加“彩虹表”攻击的难度。
DK = PBKDF2(PRF, Password, Salt, c, dkLen) PRF是一个伪随机函数,例如HASH_HMAC函数,它会输出长度为hLen的结果。 Password是用来生成密钥的原文密码。 Salt是一个加密用的盐值。 c是进行重复计算的次数。 dkLen是期望得到的密钥的长度。 DK是最后产生的密钥。
密文数据区域中包含的信息说明如下:
□ KeyType:需要被下发的密钥类型,读者可以根据自己实际需求进行定义,用于分发不通过类型的密钥;
□ MagicNum:数据包的魔术数,用于校验;
□ DataLength:整个数据包中有效数据的长度;
□ Count:用于生成密钥时使用的重复数;
□ SaltData:存放用于生成密钥时使用的盐值;
□ Length of salt dat:盐值的数据长度;
□ Password Data:存放用于生成密钥时使用的密码;
□ Length of password data:密码的数据长度;
□ Reservet:预留的区域;
□ HASH:下发的数据明文数据的哈希值,用于校验明文数据的完整性。
整个密文会使用AES128算法的CBC模式进行加密。由于在OP-TEE中AES128的CBC模式采取的是无填充的方式进行加密操作,故需要明文数据的长度为八个字节的倍数。
哈希数据区域保存的是密文数据区域的哈希值,在OP-TEE中可使用该值来判定终端接收到的数据包是否完整。
最后使用BASE64算法对密文数据区域和哈希数据区域的数据进行转换,最终生成的数据就是被下发到终端设备的数据包。
第24章 基于OP-TEE的在线支付系统
基于安全考虑,支付系统的最终结算由支付服务提供商与银行完成支付结算,而终端设备只为支付系统的服务器提供支付凭据,让支付系统的服务器端触发支付操作完成与银行间的账务清算。而终端设备的支付凭据就在整个在线支付过程中起到了至关重要的作用。如何确保支付凭据安全且可信的发送到服务器端并被服务器验证通过就尤为重要。本章将简要介绍在线支付系统中,终端设备中的TEE是如何确保数据安全且可信地被支付系统的服务器端使用。
24.1 在线支付系统的基本框架
在线支付系统的支付凭据是由终端产生,该支付凭据包含了产生支付操作所需要的所有信息,支付凭据的组包和加密都是由TEE内核或者运行于TEE中的TA来完成的,至于采取何种方式则需要支付厂商与终端设备厂商协商解决。一个简单的在线支付系统的大致框架如图24-1所示。
在线支付系统的服务器端与银行之间的数据交互协议是由支付厂商与银行之间制定的,而支付系统的服务器端与终端设备之间的数据交互协议则是由支付服务提供商自行定义。一般情况下支付系统提供商会为终端设备提供从应用层到系统层面的支付应用程序、库文件和可执行文件,这些软件能够搭建一个完整的支付请求交互通道。终端设备普遍支持TEE,支付厂商可将数据的组包操作和加密操作都放到TEE中来完成,这样可将交互数据的组包和加密部分与REE侧的系统相互隔离。根据是否在终端设备中预置了密钥在TEE中完成终端设备与支付系统服务器端之间交互数据的组包和加密可大致分为预置密钥的组包方式和未预置密钥的组包方式。
24.2 可信通信通道
可信通信通道是指设备终端与支付服务器之间的通信链路是安全可信的,即设备终端发出去的支付相关数据只有支付服务器才能正确地解析。可信通信通道的建立可以仿照SSL协议来进行建立,SSL协议主要用于网络通信,通过设备终端与支付服务器之间的握手通信来建立两者之间进行密文通信使用的加密密钥。图24-2所示为SLL通信协议的简图。
在确定可信通信通道之前,设备终端与服务器端需要经过三次握手来协商通信时使用的加密密钥。设备终端向服务器第一次发送握手请求时会生成一个随机数A,服务器端会认证该请求的可信性,如果认证通过,服务器会生成一个随机数B,然后将该随机数发送给设备终端。设备终端获取到来自服务器的返回数据之后会进行一系列的可信认证,待认证通过之后会生成一个随机数C,并将该随机数发送给服务器完成整个握手操作。然后客户端和服务器端使用相同的算法结合上述三个随机数生成两者之间进行数据通信的加密密钥。
为防止中间人攻击,在终端设备和服务器建立可信通信通道时,终端设备和服务器需要具有数据的唯一性和可信鉴定机制。这一点可通过在设备生产时提前将认证密钥预置到设备中或在应用程序安装或注册时分发密钥到设备来实现。为确保终端设备与服务器之间的相互隐私,这组密钥一般使用的是非对称算法密钥,其中私钥保存在终端设备中,而公钥则由服务器来保存。
在线支付程序安装或者注册时,支付服务器可给设备下发一对RSA密钥的公钥,该公钥最终会被保存到OP-TEE中。在建立可信通信信道时,终端设备可用该公钥加密握手数据请求。
24.3 数据交互协议
数据交互协议是指终端设备与支付服务器之间进行数据交互时双方发送的数据需按照一定的格式进行组合。组合的数据需要包含数据的用途、内容、发送方、数字签名,且以密文的形式进行传输。在本章节提供的示例中,数据交互协议的内容包括数据头部区域、数据区域电子签名区域。数据头部区域和数据区域的内容在发送之前需要使用密码学算法进行处理,以便数据在传输过程中都是以密文的形式存在。
24.3.1 数据头部区域
数据头部区域包含通信协议的版本信息、数据发送方、数据接收方以及预留区域,以便后续扩展使用。关于数据头部区域的数据格式定义如表24-1所示。
24.3.2 数据区域
数据区域中包含的内容是设备终端与服务器之间进行交互式传输的数据内容,关于数据区域中包含了哪些数据内容则由支付服务提供商自行决定。但该区域中一般都会包含该份数据的用途、数据长度等信息。数据区域的数据格式定义如表24-2所示。
数据区域中的数据是设备终端与服务器端需要进行相关操作的依据,用于产生支付凭据、合法的支付请求以及支付结果的反馈等。支付厂商可以根据自身的实际需求定义该部分的内容。
24.3.3 电子签名区域
电子签名区域用于验证接收到的数据的完整性和唯一性,一般使用RSA算法来实现,当然支付厂商也可以根据实际的需求使用电子证书加RSA签名的方式来实现。本章提供的示例代码中直接使用RSA2048算法来实现,其内容定义如表24-3所示。
在建立可信通信通道过程中,第一次握手时不会带电子签名,如果设备终端没有在生产时将RSA公钥发布给支付厂商,则在第一次握手时会将设备终端的一把RSA公钥发送给服务器端。
24.3.4 交互数据包的格式
一个完整数据包需要包含数据包头、数据区域、电子签名区域。一般在完成通信握手操作之后使用对称加密算法对数据包头和数据区域进行加密。一份完整的数据包的组合方式如图24-3所示。
由于在数据通信过程中并不会在设备终端中固定加密密钥。故在仿照SSL通信协议协商加密密钥的过程中传输的数据一般使用非对称加密的方式对握手操作时的数据进行加密处理。这样可以确保终端设备与服务器端握手操作时数据的安全性。
24.4 在线支付系统示例的实现
24.4.4 支付请求
支付请求命令会生成一个通知支付服务器与银行产生清算操作的支付凭据的数据包。该数据包使用握手时生成的AES密钥进行加密,然后使用设备终端的RSA私钥进行电子签名,以确保数据包的完整性和唯一性。支付请求的数据包内容示意图如图24-7所示。支付请求时设备终端发送给支付服务器的支付凭据,在组包之前需要被发起支付请求的操作者进行相关的身份验证,例如支付密码、指纹验证、短信验证等方式。待身份验证通过后,CA会向OP-TEE发送该命令,让TA生成合法的支付请求数据包。明文支付请求数据会使用握手时生成的AES密钥进行加密,服务器接收到该数据包后会对数据包进行电子签名验证、解密、解析、支付请求合法性验证等操作。
24.4.5 支付反馈
支付请求完成之后,服务器端会向设备终端发送支付反馈数据包,该数据包包含直接结果和其他相关的信息,用于将最终的支付结果反馈给用户。
设备终端接收到支付反馈数据包后会通过调用CA将该数据发送给TA, TA接收到数据后会对数据包进行电子签名验证、AES解密、合法性验证,然后解析出支付结果,并将最终的支付结果信息返回给CA。支付应用可从CA的返回数据中获取支付结果并显示给用户。
24.5 示例的集成
终端设备与支付服务器端之间的交互数据是按照事先约定的通信协议格式进行组包和发送的,为提高数据的安全性、唯一性、完整性、可信性,将数据的组包、加密、签名操作都放在TA中来实现,REE侧只负责数据的接收和发送。整个系统在TEE侧的实现如图24-9所示,本节将介绍如何将示例代码集成到OP-TEE中。
24.5.5 示例支持的命令说明
示例总共支持五个命令,更加详细的信息可参阅示例代码中的README.md文件,其在CA侧的命令说明如下:
onLinePay hsone:让OP-TEE中的TA按照通信协议的规定打包第一次握手请求的数据包,其中包括第一个随机数和终端设备需要发送给服务器端的RSA公钥内容。
onLinePay hstwo:将服务器端发送的第二次握手的数据发送到OP-TEE进行解密、验证并解析,获取到服务器端发送给终端设备的第二个随机数。
onLinePay hsthree:让OP-TEE中的TA按照通信协议打包第三次握手请求的数据包,其中包含第三个随机数,最后将上述三个随机数通过PBKDF2算法进行融合生成终端设备与服务器端进行数据交互时的AES密钥,从而完成可信通信通道的建立。
onLinePay payreq:让OP-TEE中的TA按照通信协议打包支付请求的数据包,其中包含了需要发送给服务器端用于实现支付认证的相关信息。
onLinePay payover:将服务器端发送的支付操作反馈数据包发送到OP-TEE中进行解密、验证并解析,获取最终的支付结果。
24.7 支付系统与生物特征的结合
生物特征数据一般都会用于终端设备使用者身份合法性的鉴定,如果在执行支付操作时嵌入使用者生物特征的匹配检查就能实现支付系统与使用者生物特征数据的结合。即在触发支付操作之前需要使用者提供生物特征数据,例如指纹、虹膜、人脸扫描等。只有当生物特征数据验证通过之后才能触发支付操作,如果生物特征数据匹配失败则取消支付操作。
如要实现生物特征数据与支付系统的强制绑定,可在开通支付系统之前要求用户录入生物特征数据,并将该数据传递到服务器端,由服务器端来完成使用者身份的论证,但该方式往往会牵扯到侵犯用户隐私的问题,故当前一般将生物特征数据的鉴定放在终端设备中来完成。
第25章 TEE可信应用的使用领域
25.1 在线支付
每一台手机在工厂生产过程中都会使用微信提供的工具生成一对RSA密钥,公钥将会被上传到微信的服务器中,生成密钥的操作是由TEE来完成,而且在使用微信时,相关数据的组包、签名都由TEE来完成。微信的在线支付功能在使用时会使用到系统中的keystore模块、keymaster模块、TEE驱动、运行于TEE中的在线支付TA、指纹TA模块。整个过程中所有数据的加密、签名以及生成密钥的过程都是在TEE环境中完成的,这也就能保证在线支付操作的安全。
25.2 数字版权保护
数字版权保护(DRM)是用于对视频资源进行版权保护的解决方案,目前DRM的方案有很多种,例如ChinaDRM、Marlin、WideWine等。TEE用于对视频码流版权的验证和视频资源的解密,并提供安全的播放环境(Secure Video Path, SVP)。当设备需要播放受DRM保护的视频资源时,首先需要对视频码流进行版权验证,待视屏码流被验证通过后,系统再从服务器端获取到加密的视频资源,然后将密文的视频资源交由TEE使用密钥进行解密,解密后的明文视频资源将会被保存到安全内存中,当多媒体单元要播放该视频资源时,多媒体单元可以从SVP中获取到解码后的视频数据。
在ChinaDRM的方案中,TEE主要用于验证和解密视屏资源并提供安全内存的功能。TEE中会运行一个ChinaDRM的TA,该TA将完成对视频资源的版权认证和解密操作,解密使用的算法策略则由ChinaDRM厂商以库文件的方式提供给应用厂商,应用厂商将该算法策略集成到TA中。
25.3 身份验证
对设备使用者的身份验证主要使用密码和生物特征数据来进行判定,最终的验证结果是由转换后的数据与保存数据的对比结果是否一致来决定。根据生物特征数据进行判定是目前最安全的方式,生物传感器主要用于采集用户的生物特征数据,例如指纹数据和虹膜数据。谷歌在Android 7.0之后已强制要求设备厂商使用TEE来保存用户的生物特征数据。对于系统软件中的指纹识别模块,谷歌在Android系统已提供了统一的标准接口。设备厂商只需将指纹传感器配置成安全设备,并在TEE中添加对应的TA就可实现使用TEE对指纹数据进行安全保护。
由于指纹传感器被设置成安全设备,故只有TEE才可以获取到指纹识别传感器的数据。指纹数据的采集操作是由运行于TEE中的TA来完成的,该TA会调用指纹识别传感器厂商提供的第三方库来完成数据的采集。将指纹识别对应的CA接口与Android在REE侧提供的标准接口进行对接就能实现指纹识别相关的认证操作,包括指纹解锁、指纹支付等功能。这些功能的实现需要TEE厂商、支付平台、芯片厂商以及在线支付厂商四方共同开发完成。