前言
本文主要介绍OPTEE的TA(Trusted Applications),翻译自官方文档:Trusted Applications — OP-TEE documentation documentation (optee.readthedocs.io)
有两种方法可以实现可信应用程序 (TA):伪 TA 和用户模式 TA。用户模式 TA 是 GlobalPlatform API TEE 规范指定的功能齐全的受信任应用程序,这些只是人们在说“受信任的应用程序”时所指的那些,在大多数情况下,这是编写和使用首选的 TA 类型。
一、伪TA(Pseudo Trusted Applications)
伪TA不是受信任应用程序。伪 TA 不是特定实体。伪 TA 是一个接口。它是 OP-TEE Core 向其外部世界公开的接口:用于保护客户端受信任的应用程序和非安全的客户端实体。
这些直接实现到 OP-TEE 核心树中,例如:core/PTA,并与 OP-TEE core blob 一起构建并静态构建到其中。
OP-TEE中包含的伪TA已经是隐藏在“GlobalPlatform TA Client”API后面的OP-TEE安全特权级别服务。这些伪 TA 用于各种目的,例如特定的安全服务或嵌入式测试服务。
伪 TA 无法从 GlobalPlatform TEE 规范指定的 GlobalPlatform Core Internel API 支持中受益。这些 API 作为静态库提供给 TA,每个 TA 应链接(“libutee”),并通过syscalls调用 OP-TEE 核心服务。由于OP-TEE核心不与libutee链接,伪TA只能使用OP-TEE核心内部API和例程。
由于伪 TA 在与 OP-TEE 核心代码本身相同的特权执行级别上运行,因此根据用例的不同,这可能是可取的,也可能是不可取的。
在大多数情况下,非特权(用户模式)TA 是最佳选择,而不是将代码直接添加到 OP-TEE core。但是,如果您决定最好像这样直接在 OP-TEE core中处理您的应用程序,您可以将 core/pta/stats.c 视为模板,并基于此将伪 TA 添加到同一目录中的 sub.mk 中
二、用户模式TA(User Mode Trusted Applications)
用户模式TA由Secure World中的 OP-TEE core加载(映射到内存中),当富执行环境 (REE) 中的某些内容想要与该特定应用程序 UUID 通信时。它们以比 OP-TEE 核心代码更低的 CPU 权限级别运行。在这方面,它们与在 REE 中运行的常规应用程序非常相似,只是它们在Secure World中执行。
TA受益于全球平台 TEE 规范指定的 GlobalPlatform TEE Internel Core API。有几种类型的用户模式 TA,它们的存储方式不同。
三、TA位置
普通 TA(用户模式)可以驻留且从不同位置加载。OP-TEE 目前支持三种方式。
1、Early TA
所谓的Early TA 实际上与 REE FS TA 相同,但它们不是从正常世界文件系统加载的,而是链接到 TEE core blob 中的特殊数据部分。因此,它们甚至在 tee supplicant和 REE filesystem出现之前就可用。请在early TA summit中找到更多详细信息。
2、REE filesystem TA
它们由一个 ELF 文件组成,经过签名和选择性加密,以 TA 的 UUID 和 .ta后缀命名。它们是独立于 OP-TEE core boot-time blob生成的,尽管在生成它们时它们使用相同的生成系统,并使用原始 OP-TEE core blob 生成的密钥进行签名。
由于 TA 已签名并可选择使用脚本/sign_encrypt.py进行加密,因此它们能够存储在不受信任的 REE filesystem中,并且tee supplicant将负责传递它们以由Secure World OP-TEE core检查和加载。
A.REE-FS TA 回滚保护
OP-TEE core在安全存储中维护一个ta_ver.db文件,以检查从 REE-FS 加载的 REE TA 版本,以防止任何 TA 版本降级。TA 版本可以通过 TA build option进行配置:CFG_TA_VERSION=<unsigned integer>。
注意:此处的回滚保护仅在 CFG_RPMB_FS=y 时有效
B.REE-FS TA 格式
REE 文件系统 TA 有三种格式:
自版本 3.7.0 起,构建脚本无法再创建已签名且未加密的旧版 TA。
引导 TA使用原始 OP-TEE core blob 生成的密钥进行签名,未加密。
加密的 TA,先签名再加密后 MAC,当 CFG_ENCRYPT_TA=y 时用 TA_ENC_KEY 加密。在 OP-TEE 运行时,用于解密 TA 的对称密钥必须通过覆盖 API 以平台特定的方式提供:
TEE_Result tee_otp_get_ta_enc_key(uint32_t key_type, uint8_t *buffer,
size_t len);
C.REE-FS TA 头结构
所有 REE 文件系统 TA 都有通用的头,struct shdr,定义为:
enum shdr_img_type {
SHDR_TA = 0,
SHDR_BOOTSTRAP_TA = 1,
SHDR_ENCRYPTED_TA = 2,
};
#define SHDR_MAGIC 0x4f545348
/**
* struct shdr - 签名头
* @magic: magic number必须与SHDR_MAGIC匹配
* @img_type: image type, values 通过enum shdr_img_type定义
* @img_size: 图像大小(以字节为单位)
* @algo: 算法,由 TEE 内部 API 规范形如TEE_ALG_XXX定义的公钥算法
* @hash_size: 签名哈希的大小
* @sig_size: 签名的大小
* @hash: 图像的哈希
* @sig: hash签名
*/
struct shdr {
uint32_t magic;
uint32_t img_type;
uint32_t img_size;
uint32_t algo;
uint16_t hash_size;
uint16_t sig_size;
/*
* 注释掉的元素用于可视化结构体的动态布局部分。
* 哈希通过宏SHDR_GET_HASH访问,签名通过宏SHDR_GET_SIG
*
* uint8_t hash[hash_size];
* uint8_t sig[sig_size];
*/
};
#define SHDR_GET_SIZE(x) (sizeof(struct shdr) + (x)->hash_size + \
(x)->sig_size)
#define SHDR_GET_HASH(x) (uint8_t *)(((struct shdr *)(x)) + 1)
#define SHDR_GET_SIG(x) (SHDR_GET_HASH(x) + (x)->hash_size)
字段img_type为 TA 的类型,如果它是SHDR_TA(0),则它是旧版 TA。如果是SHDR_BOOTSTRAP_TA(1),则为引导 TA。
字段algo为使用的算法。用于对 TA 进行签名的脚本当前使用TEE_ALG_RSASSA_PKCS1_V1_5_SHA256 (0x70004830)。这意味着RSA具有PKCS#1v1.5填充和SHA-256哈希函数。OP-TEE 接受任何 TEE_ALG_RSASSA_PKCS1_* 算法。
对于引导 TA,结构 shdr 后跟一个子标题,结构体shdr_bootstrap_ta定义为:
/**
* struct shdr_bootstrap_ta - bootstrap TA subheader
* @uuid: TA的UUID
* @ta_version: TA的版本
*/
struct shdr_bootstrap_ta {
uint8_t uuid[sizeof(TEE_UUID)];
uint32_t ta_version;
};
字段 uuid 和 ta_version 允许在加载 TA 时执行额外的检查。目前仅检查 uuid 字段。
对于加密的 TA,结构 shdr 后跟一个子标题,结构shdr_bootstrap_ta后跟另一个子标题,结构shdr_encrypted_ta定义为:
/**
* struct shdr_encrypted_ta - encrypted TA header
* @enc_algo: 经过身份验证的加密算法,由 TEE 内部 API 规范中的对称密钥算法 TEE_ALG_* 定义
* @flags: 经过身份验证的加密标志
* @iv_size: 初始化向量的大小
* @tag_size: 身份验证标记的大小
* @iv: 初始化向量
* @tag: 身份验证标记
*/
struct shdr_encrypted_ta {
uint32_t enc_algo;
uint32_t flags;
uint16_t iv_size;
uint16_t tag_size;
/*
* 注释掉的元素用于可视化 struct.iv 的布局动态部分通过宏SHDR_ENC_GET_IV访问,标记通过宏SHDR_ENC_GET_TAG访问
*
* uint8_t iv[iv_size];
* uint8_t tag[tag_size];
*/
};
字段enc_algo告诉所使用的算法。用于加密 TA 的脚本当前使用 TEE_ALG_AES_GCM (0x40000810)。OP-TEE内核也接受TEE_ALG_AES_CCM算法。
字段flags支持单个标志来告知加密密钥类型,该类型定义为:
#define SHDR_ENC_KEY_TYPE_MASK 0x1
enum shdr_enc_key_type {
SHDR_ENC_KEY_DEV_SPECIFIC = 0,
SHDR_ENC_KEY_CLASS_WIDE = 1,
};
D.REE-FS TA 二进制格式
TA 二进制文件遵循 ELF 文件,该文件通常被剥离为附加符号等,加载 TA 时将被忽略。
旧版 TA 二进制文件的格式为
hash = H(<struct shdr> || <stripped ELF>)
signature = RSA-Sign(hash)
legacy_binary = <struct shdr> || <hash> || <signature> || <stripped ELF>
引导 TA 二进制文件的格式为:
hash = H(<struct shdr> || <struct shdr_bootstrap_ta> || <stripped ELF>)
signature = RSA-Sign(<hash>)
bootstrap_binary = <struct shdr> || <hash> || <signature> ||
<struct shdr_bootstrap_ta> || <stripped ELF>
加密的 TA 二进制文件的格式为:
nonce = <unique random value>
ciphertext, tag = AES_GCM(<stripped ELF>)
hash = H(<struct shdr> || <struct shdr_bootstrap_ta> ||
<struct shdr_encrypted_ta> || <nonce> || <tag> || <stripped ELF>)
signature = RSA-Sign(<hash>)
encrypted_binary = <struct shdr> || <hash> || <signature> ||
<struct shdr_bootstrap_ta> ||
<struct shdr_encrypted_ta> || <nonce> || <tag> ||
<ciphertext>
E.使用subkeys进行验证
可以使用subkeys或subkeys链来验证 TA。这允许在不分发根密钥的情况下委派 TA 签名。使用子项签名的 TA 仅限于子项的 UUID-V5 命名空间,以避免 TA UUID 与不同的子项发生冲突。
SHDR_SUBKEY是一种启用公钥链的标头。公钥根密钥用于验证第一个公钥subkey,然后用于验证下一个公钥subkeys,依此类推。
最后使用最后一个子项验证 TA。所有这些标头都添加到 TA 二进制文件的前面,因此验证 TA 所需的所有内容在加载到内存中时都可用。
F.加载REE-FS TA
REE TA 使用将 REE TA 加载到不安全共享内存中的系列或 RPC 加载到共享内存中。有效负载内存通过 TEE 请求方分配,稍后在释放先前分配的非安全共享内存中将 TA 加载到安全内存中时释放。
将 REE TA 加载到不安全的共享内存中
释放以前分配的不安全共享内存
3、Secure Storage TA
这些存储在安全存储中。元数据存储在所有已安装 TA 的数据库中,实际的二进制文件作为不受信任的 REE 文件系统(flash)中的单独文件进行加密和完整性保护。在加载这些 TA 之前,必须先安装它们,这是可以在初始部署期间或稍后阶段完成的操作。
出于测试目的,测试程序xtest可以使用以下命令将TA安装到安全存储中:
$ xtest --install-ta
存储在安全存储中的 TA 保存在 TA 数据库中。TA 数据库由一个名为 dirf 的单个文件组成.db该文件存储在基于 REE filesystem的安全存储或 RPMB 中。该文件与安全存储中的任何其他对象一样加密和完整性保护。TA 本身不存储在 dirf.db 中,而是存储在加密和完整性保护的 REE 文件系统中。其中一个原因是 TA 可能非常大,只有几兆字节,而安全存储旨在仅容纳以千字节为单位的小对象。
DIRF.db结构tadb_entry数组的组成,定义为:
/*
* struct tee_tadb_property
* @uuid: 可信应用程序 (TA) 或安全域 (SD) 的 UUID
* @version: TA 或 SD 的版本
* @custom_size:自定义属性的大小,附加到加密的 TA 二进制文件前面
* @bin_size: 二进制 TA 的大小
*/
struct tee_tadb_property {
TEE_UUID uuid;
uint32_t version;
uint32_t custom_size;
uint32_t bin_size;
};
#define TADB_IV_SIZE TEE_AES_BLOCK_SIZE
#define TADB_TAG_SIZE TEE_AES_BLOCK_SIZE
#define TADB_KEY_SIZE TEE_AES_MAX_KEY_SIZE
/*
* struct tadb_entry - TA database entry
* @prop: TA的属性
* @file_number: 加密的 TA 存储在 <file_number>.ta 中
* @iv: 身份验证加密的初始化向量
* @tag: 用于验证身份验证加密 TA 的标记
* @key: 用于解密 TA 的密钥
*/
struct tadb_entry {
struct tee_tadb_property prop;
uint32_t file_number;
uint8_t iv[TADB_IV_SIZE];
uint8_t tag[TADB_TAG_SIZE];
uint8_t key[TADB_KEY_SIZE];
};
UUID 仅由零组成的条目无效,将被忽略。file_number字段表示存储在 REE 文件系统中的文件的名称。文件名由附加 .ta 的file_number的十进制字符串表示形式组成,或者如果要打印:printf(“%u.ta”, file_number)。
TA 使用 iv 和密钥字段初始化的身份验证加密算法 AES-GCM 进行解密,完成解密时使用标签字段。
通过打开 dirf 并在结构tadb_entry类型元素中扫描 TA .db直到找到匹配的 UUID 来查找 TA。
四、加载和准备 TA 以执行
用户模式 TA 使用用户模式 ELF 加载器 ldelf 以相同的方式加载到最终内存中。不同的 TA 位置具有指向 ldelf 的通用接口,这使得用户模式操作与 TA 的存储方式相同。
TA 将加载到准备 TA 以执行中的安全内存中。
准备 TA 执行
在 ldelf 返回准备执行的 TA 后,如果使用 dlopen() 和朋友,它仍然保留在内存中以服务于 TA。如果 TA 通过中止终止,ldelf 还用于转储堆栈跟踪和详细的内存映射。
Linux 用户空间中客户端应用程序的整个流的高级视图,其中会话被打开到 TA,在打开会话到 TA 中。
向 TA 打开会话
五、TA属性
1、GP属性
标准 TA 属性必须通过宏中的属性标志定义 TA_FLAGS user_ta_header_defines.h
A.单实例
“gpd.ta.singleInstance” 是 TA 的bool属性。此属性定义是必须创建 TA 的一个实例并接收所有打开的会话请求,还是必须为每个传入的打开会话请求创建新的特定 TA 实例。OP-TEE TA 标志TA_FLAG_SINGLE_INSTANCE设置为此属性的配置。如果TA_FLAGS设置位TA_FLAG_SINGLE_INSTANCE,则bool属性设置为 true,否则bool属性设置为 false。
B.多会话
“gpd.ta.multiSession” 是 TA 的bool属性。此属性定义 TA 实例是否可以处理多个会话。如果禁用,则 TA 实例仅支持一个会话。在这种情况下,如果 TA 已经打开了会话,则任何打开的会话请求都将返回繁忙错误状态。
注意:如果 TA 不是单实例 TA,则此属性毫无意义。
OP-TEE TA 标志TA_FLAG_MULTI_SESSION设置为此属性的配置。如果TA_FLAGS设置位TA_FLAG_MULTI_SESSION,则bool属性设置为 true,否则bool属性设置为 false。
C.Keep Alive
“gpd.ta.instanceKeepAlive” 是 TA 的bool属性。此属性定义当关闭朝 TA 打开的所有会话时,是否必须销毁创建的 TA 实例。如果启用了该属性,则 TA 实例一旦创建(在第一个打开会话请求时),将永远不会删除,除非重新启动 TEE 本身(启动/重新启动)。
注意:如果 TA 不是单实例 TA,则此属性毫无意义。
OP-TEE TA 标志TA_FLAG_INSTANCE_KEEP_ALIVE设置为此属性的配置。如果 TA_FLAGS 位设置为 TA_FLAG_INSTANCE_KEEP_ALIVE,则bool属性设置为 true,否则bool属性设置为 false。
D.堆大小
“gpd.ta.dataSize” 是 TA 的 32 位整数属性。此属性定义 TA 分配池的大小(以字节为单位),TEE_Malloc() 和友元在其中分配内存。属性的值必须由 user_ta_header_defines.h 中的宏TA_DATA_SIZE定义。
E.栈大小
“gpd.ta.stackSize” 是 TA 的 32 位整数属性。此属性定义用于 TA 执行的堆栈的大小(以字节为单位)。属性的值必须由 user_ta_header_defines.h 中的宏TA_STACK_SIZE定义。
2、属性扩展
A.安全数据路径标志
TA_FLAG_SECURE_DATA_PATH是TA_FLAGS支持的位标志。此属性标志声明来自 OP-TEE OS 的安全数据支持。请参阅 OP-TEE OS 以获取安全数据路径支持。未在 TA_FLAGS 值中设置TA_FLAG_SECURE_DATA_PATH的 TA 将无法处理与安全数据路径缓冲区相关的内存引用调用参数。
B.缓存维护标志
TA_FLAG_SECURE_DATA_PATH是TA_FLAGS支持的位标志。此属性标志声明来自 OP-TEE OS 的安全数据支持。请参阅 OP-TEE OS 以获取安全数据路径支持。未在 TA_FLAGS 值中设置TA_FLAG_SECURE_DATA_PATH的 TA 将无法处理与安全数据路径缓冲区相关的内存引用调用参数。
C.已弃用的属性标志
旧版本的 OP-TEE 用于定义扩展属性标志,这些标志已弃用且对当前 OP-TEE 毫无意义。这些是TA_FLAG_USER_MODE的,TA_FLAG_EXEC_DDR的和TA_FLAG_REMAP_SUPPORT的。