TEE-TA学习轨迹第四篇:OP-TEE可信密钥Trusted KeysTA深度解析
·
OP-TEE 官方的可信密钥(Trusted Keys)可信应用(TA),由 Linaro 维护,为 Linux 内核的 trusted keys 子系统提供安全世界侧的密钥密封/解封能力,是典型的符合 GlobalPlatform TEE 规范的 TA 实现。下面从功能定位、数据结构、核心逻辑、安全设计等维度做完整分析。
一、整体功能与定位
这是一个运行在 OP-TEE 安全世界的用户态 TA,核心能力是基于**硬件唯一密钥(HUK)**实现密钥的安全密封与解封,同时提供安全随机数生成能力。
- 密封(Seal):将明文密钥用 AEC-GCM 认证加密,生成只有当前设备、当前 TA 才能解密的密钥 blob,可安全存储在 REE(富执行环境,即 Linux 内核侧)。
- 解封(Unseal):对密封后的 blob 做解密+完整性校验,还原出明文密钥,仅在安全世界内可见。
- 随机数生成:基于硬件 TRNG 输出安全随机数。
- 访问控制:仅允许 REE 内核(TEE_LOGIN_REE_KERNEL)调用,用户态进程无法直接访问,严格缩小攻击面。
二、常量与核心数据结构
1. 常量定义
|
常量
|
值
|
作用
|
|
IV_SIZE
|
16
|
AES-GCM 初始向量长度,单位字节
|
|
TAG_SIZE
|
16
|
AES-GCM 认证标签长度,128 位强度
|
|
MAX_BUF_SIZE
|
512
|
单次密封/解封的最大缓冲区长度,防止溢出和过大内存分配
|
2. 密钥 Blob 头部结构
struct tk_blob_hdr {
uint8_t reserved; // 保留字段,当前置0,用于未来版本扩展
uint8_t iv[IV_SIZE]; // 本次加密生成的随机IV,随blob一同存储
uint8_t tag[TAG_SIZE]; // AES-GCM认证标签,用于解密完整性校验
uint8_t enc_key[]; // 柔性数组,存放加密后的密文密钥
};
这是密封后输出的二进制格式:IV、认证标签与密文打包在一起,解密时直接从 blob 中读取,无需外部额外传递,是 AEAD 算法的标准封装方式。最终 blob 总长度 = sizeof(struct tk_blob_hdr) + 明文密钥长度。
三、核心函数逐模块分析
1. get_random — 安全随机数生成
static TEE_Result get_random(uint32_t types, TEE_Param params[TEE_NUM_PARAMS])
{
uint8_t *rng_buf = NULL;
DMSG("Invoked TA_CMD_GET_RANDOM");
if (types != TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_OUTPUT,
TEE_PARAM_TYPE_NONE,
TEE_PARAM_TYPE_NONE,
TEE_PARAM_TYPE_NONE))
return TEE_ERROR_BAD_PARAMETERS;
if (!params[0].memref.buffer || !params[0].memref.size)
return TEE_ERROR_BAD_PARAMETERS;
rng_buf = TEE_Malloc(params[0].memref.size, TEE_MALLOC_FILL_ZERO);
if (!rng_buf)
return TEE_ERROR_OUT_OF_MEMORY;
TEE_GenerateRandom(rng_buf, params[0].memref.size);
memcpy(params[0].memref.buffer, rng_buf, params[0].memref.size);
memzero_explicit(rng_buf, params[0].memref.size);
TEE_Free(rng_buf);
return TEE_SUCCESS;
}
对应命令 TA_CMD_GET_RANDOM,向调用方输出硬件真随机数。
- 参数校验:严格校验参数类型,仅允许第 0 个参数为输出内存引用,其余必须为空,过滤非法调用。
- 执行流程:
- 校验输入指针和长度合法性;
- 分配临时堆缓冲区并初始化为 0,调用 TEE_GenerateRandom 生成硬件随机数;
- 将随机数拷贝到调用方输出缓冲区;
- 用 memzero_explicit 显式清零临时缓冲区后释放内存。
- 安全设计:敏感数据用完即清零,避免随机数残留在堆内存中被泄露。
2. derive_unique_key — TA 专属密钥派生
static TEE_Result derive_unique_key(uint8_t *key, uint16_t key_size,
uint8_t *extra, uint16_t extra_size)
{
TEE_TASessionHandle sess = TEE_HANDLE_NULL;
TEE_Param params[TEE_NUM_PARAMS] = { };
TEE_Result res = TEE_ERROR_GENERIC;
uint32_t ret_orig = 0;
uint32_t param_types = TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INPUT,
TEE_PARAM_TYPE_MEMREF_OUTPUT,
TEE_PARAM_TYPE_NONE,
TEE_PARAM_TYPE_NONE);
res = TEE_OpenTASession(&(const TEE_UUID)PTA_SYSTEM_UUID,
TEE_TIMEOUT_INFINITE, 0, NULL, &sess,
&ret_orig);
if (res)
return res;
if (extra && extra_size) {
params[0].memref.buffer = extra;
params[0].memref.size = extra_size;
}
params[1].memref.buffer = key;
params[1].memref.size = key_size;
res = TEE_InvokeTACommand(sess, TEE_TIMEOUT_INFINITE,
PTA_SYSTEM_DERIVE_TA_UNIQUE_KEY,
param_types, params, &ret_orig);
TEE_CloseTASession(sess);
return res;
}
这是整个 TA 的密钥根,不直接接触硬件唯一密钥 HUK,而是通过调用系统内置伪 TA(PTA_SYSTEM)完成密钥派生。
- 原理:系统 PTA 持有不可读出的 HUK,结合当前 TA 的 UUID,通过密钥派生算法生成每个 TA 独有的派生密钥。不同 TA 无法解密彼此的密封数据,单 TA 被攻破也不会泄露根 HUK。
- 执行流程:
- 打开系统 PTA(PTA_SYSTEM_UUID)会话;
- 传入可选的额外上下文数据(当前密封场景传 NULL);
- 调用 PTA_SYSTEM_DERIVE_TA_UNIQUE_KEY 完成派生,输出密钥到调用方缓冲区;
- 关闭会话并返回结果。
- 设计价值:密钥派生在更高特权级的 PTA 中完成,当前 TA 永远拿不到原始 HUK,实现密钥分级隔离,符合最小权限原则。
3. huk_ae_encrypt / huk_ae_decrypt — AES-GCM 加解密封装
static TEE_Result huk_ae_encrypt(TEE_OperationHandle crypto_op, uint8_t *in,
size_t in_sz, uint8_t *out, size_t *out_sz)
{
TEE_Result res = TEE_ERROR_GENERIC;
struct tk_blob_hdr *hdr = (struct tk_blob_hdr *)out;
uint8_t iv[IV_SIZE] = { 0 };
size_t enc_key_len = in_sz;
size_t tag_len = TAG_SIZE;
hdr->reserved = 0;
TEE_GenerateRandom(iv, IV_SIZE);
memcpy(hdr->iv, iv, IV_SIZE);
res = TEE_AEInit(crypto_op, hdr->iv, IV_SIZE, TAG_SIZE * 8, 0, 0);
if (res)
return res;
res = TEE_AEEncryptFinal(crypto_op, in, in_sz, hdr->enc_key,
&enc_key_len, hdr->tag, &tag_len);
if (res || tag_len != TAG_SIZE)
return TEE_ERROR_SECURITY;
if (ADD_OVERFLOW(enc_key_len, sizeof(*hdr), out_sz))
return TEE_ERROR_SECURITY;
return res;
}
static TEE_Result huk_ae_decrypt(TEE_OperationHandle crypto_op, uint8_t *in,
size_t in_sz, uint8_t *out, size_t *out_sz)
{
TEE_Result res = TEE_ERROR_GENERIC;
struct tk_blob_hdr *hdr = (struct tk_blob_hdr *)in;
uint8_t tag[TAG_SIZE] = { 0 };
size_t enc_key_len = 0;
if (SUB_OVERFLOW(in_sz, sizeof(*hdr), &enc_key_len))
return TEE_ERROR_SECURITY;
res = TEE_AEInit(crypto_op, hdr->iv, IV_SIZE, TAG_SIZE * 8, 0, 0);
if (res)
return res;
memcpy(tag, hdr->tag, TAG_SIZE);
res = TEE_AEDecryptFinal(crypto_op, hdr->enc_key, enc_key_len, out,
out_sz, tag, TAG_SIZE);
if (res)
res = TEE_ERROR_SECURITY;
return res;
}
基于 TEE 内部 AE(认证加密)API,封装 AES-GCM 的一次性加解密逻辑。
加密侧 huk_ae_encrypt
- 初始化 blob 头部,reserved 置 0;
- 生成 16 字节随机 IV 存入 blob 头部;
- 调用 TEE_AEInit 初始化 AE 操作,配置 IV 长度、标签长度;
- 调用 TEE_AEEncryptFinal 完成一次性加密并生成认证标签;
- 用 ADD_OVERFLOW 宏校验输出总长度,防止整数溢出。
解密侧 huk_ae_decrypt
- 用 SUB_OVERFLOW 宏计算密文长度,校验 blob 长度合法性,防止整数下溢;
- 从 blob 头部取出 IV,初始化 AE 操作;
- 取出认证标签,调用 TEE_AEDecryptFinal 完成解密+完整性校验;
- 解密/校验失败统一返回 TEE_ERROR_SECURITY,不区分具体失败原因。
- 安全设计:错误信息模糊化,避免攻击者通过错误类型区分“密文错误”和“标签错误”,防御侧信道攻击。
4. huk_crypt — 加解密总入口
tatic TEE_Result huk_crypt(TEE_OperationMode mode, uint8_t *in, size_t in_sz,
uint8_t *out, size_t *out_sz)
{
TEE_Result res = TEE_ERROR_GENERIC;
TEE_OperationHandle crypto_op = TEE_HANDLE_NULL;
TEE_ObjectHandle hkey = TEE_HANDLE_NULL;
uint8_t huk_key[TA_DERIVED_KEY_MAX_SIZE] = { };
TEE_Attribute attr = { };
res = TEE_AllocateOperation(&crypto_op, TEE_ALG_AES_GCM, mode,
sizeof(huk_key) * 8);
if (res)
return res;
res = derive_unique_key(huk_key, sizeof(huk_key), NULL, 0);
if (res) {
EMSG("derive_unique_key failed: returned %#"PRIx32, res);
goto out_op;
}
res = TEE_AllocateTransientObject(TEE_TYPE_AES, sizeof(huk_key) * 8,
&hkey);
if (res)
goto out_op;
attr.attributeID = TEE_ATTR_SECRET_VALUE;
attr.content.ref.buffer = huk_key;
attr.content.ref.length = sizeof(huk_key);
res = TEE_PopulateTransientObject(hkey, &attr, 1);
if (res)
goto out_key;
res = TEE_SetOperationKey(crypto_op, hkey);
if (res)
goto out_key;
if (mode == TEE_MODE_ENCRYPT) {
res = huk_ae_encrypt(crypto_op, in, in_sz, out, out_sz);
if (res)
EMSG("huk_AE_encrypt failed: returned %#"PRIx32, res);
} else if (mode == TEE_MODE_DECRYPT) {
res = huk_ae_decrypt(crypto_op, in, in_sz, out, out_sz);
if (res)
EMSG("huk_AE_decrypt failed: returned %#"PRIx32, res);
} else {
TEE_Panic(0);
}
out_key:
TEE_FreeTransientObject(hkey);
out_op:
TEE_FreeOperation(crypto_op);
memzero_explicit(huk_key, sizeof(huk_key));
return res;
}
负责完整的密码学操作生命周期:创建操作句柄、派生密钥、加载密钥、执行加解密、资源清理。
- 分配 AES-GCM 算法操作句柄,密钥长度对应派生密钥的比特位数(默认 256 位 AES);
- 调用 derive_unique_key 派生 TA 专属密钥,暂存于栈上缓冲区;
- 创建 AES 临时密钥对象,将派生密钥填充到对象中;
- 将密钥对象绑定到 AE 操作句柄;
- 根据加解密模式调用对应底层函数;
- 强制清理:无论成功失败,最终都会释放密钥对象、操作句柄,并用 memzero_explicit 清零栈上的密钥缓冲区,防止敏感数据残留在内存中。
5. seal_trusted_key / unseal_trusted_key — 业务命令入口
static TEE_Result seal_trusted_key(uint32_t types,
TEE_Param params[TEE_NUM_PARAMS])
{
TEE_Result res = TEE_SUCCESS;
uint8_t *in = NULL;
size_t in_sz = 0;
uint8_t *out = NULL;
size_t out_sz = 0;
DMSG("Invoked TA_CMD_SEAL");
if (types != TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INPUT,
TEE_PARAM_TYPE_MEMREF_OUTPUT,
TEE_PARAM_TYPE_NONE,
TEE_PARAM_TYPE_NONE))
return TEE_ERROR_BAD_PARAMETERS;
in = params[0].memref.buffer;
in_sz = params[0].memref.size;
out = params[1].memref.buffer;
out_sz = params[1].memref.size;
if (!in || !in_sz || in_sz > MAX_BUF_SIZE)
return TEE_ERROR_BAD_PARAMETERS;
if ((!out && out_sz) ||
(out && !IS_ALIGNED_WITH_TYPE(out, struct tk_blob_hdr)) ||
out_sz > MAX_BUF_SIZE)
return TEE_ERROR_BAD_PARAMETERS;
if ((in_sz + sizeof(struct tk_blob_hdr)) > out_sz) {
params[1].memref.size = in_sz + sizeof(struct tk_blob_hdr);
return TEE_ERROR_SHORT_BUFFER;
}
res = huk_crypt(TEE_MODE_ENCRYPT, in, in_sz, out, &out_sz);
if (res == TEE_SUCCESS) {
assert(out_sz == in_sz + sizeof(struct tk_blob_hdr));
params[1].memref.size = out_sz;
}
return res;
}
static TEE_Result unseal_trusted_key(uint32_t types,
TEE_Param params[TEE_NUM_PARAMS])
{
TEE_Result res = TEE_SUCCESS;
uint8_t *in = NULL;
size_t in_sz = 0;
uint8_t *out = NULL;
size_t out_sz = 0;
DMSG("Invoked TA_CMD_UNSEAL");
if (types != TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INPUT,
TEE_PARAM_TYPE_MEMREF_OUTPUT,
TEE_PARAM_TYPE_NONE,
TEE_PARAM_TYPE_NONE))
return TEE_ERROR_BAD_PARAMETERS;
in = params[0].memref.buffer;
in_sz = params[0].memref.size;
out = params[1].memref.buffer;
out_sz = params[1].memref.size;
if (!in || !IS_ALIGNED_WITH_TYPE(in, struct tk_blob_hdr) ||
in_sz <= sizeof(struct tk_blob_hdr) || in_sz > MAX_BUF_SIZE)
return TEE_ERROR_BAD_PARAMETERS;
if ((!out && out_sz) || out_sz > MAX_BUF_SIZE)
return TEE_ERROR_BAD_PARAMETERS;
if (in_sz > (out_sz + sizeof(struct tk_blob_hdr))) {
params[1].memref.size = in_sz - sizeof(struct tk_blob_hdr);
return TEE_ERROR_SHORT_BUFFER;
}
res = huk_crypt(TEE_MODE_DECRYPT, in, in_sz, out, &out_sz);
if (res == TEE_SUCCESS) {
assert(out_sz == in_sz - sizeof(struct tk_blob_hdr));
params[1].memref.size = out_sz;
}
return res;
}
对外暴露的密封/解封命令处理函数,负责参数合法性校验,再调用底层加解密能力。
密封 seal_trusted_key
- 强校验参数类型、输入非空、长度不超上限;
- 要求输出缓冲区必须按 struct tk_blob_hdr 对齐,避免非对齐访问异常;
- 缓冲区不足时返回 TEE_ERROR_SHORT_BUFFER,并在参数中返回所需长度,完全符合 GP TEE API 规范。
解封 unseal_trusted_key
- 校验输入 blob 必须对齐、长度必须大于头部大小;
- 同样支持短缓冲区查询模式;
- 解密成功后更新输出的明文实际长度。
6. TA 标准入口点
符合 GlobalPlatform TEE 规范的 5 个标准入口函数:
- TA_CreateEntryPoint / TA_DestroyEntryPoint:TA 加载/卸载时调用,当前为空实现。
- TA_OpenSessionEntryPoint(核心安全控制点) 会话打开时强制做身份校验:枚举客户端身份,仅当登录类型为 TEE_LOGIN_REE_KERNEL(REE 内核身份)时才允许建立会话,否则直接返回访问拒绝。这是非常关键的安全设计:该 TA 仅服务于 Linux 内核的密钥管理子系统,普通用户态进程完全无法连接,极大缩小攻击面。
- TA_CloseSessionEntryPoint:会话关闭,空实现。
- TA_InvokeCommandEntryPoint:命令分发器,根据命令 ID 路由到对应处理函数,不支持的命令返回错误。
四、安全设计亮点总结
- 密钥分级隔离:不直接接触 HUK,通过系统 PTA 派生 TA 专属密钥,单 TA 泄露不影响全局安全。
- 认证加密保障:使用 AES-GCM AEAD 算法,同时保证密钥的机密性和完整性,防止密文被篡改。
- 最小权限访问:仅允许 REE 内核调用,用户态不可达,严格控制攻击入口。
- 敏感数据清零:所有栈/堆上的密钥、随机数临时缓冲区,使用后均显式清零,避免内存泄露。
- 整数溢出防护:所有长度计算使用安全宏 ADD_OVERFLOW/SUB_OVERFLOW,杜绝整数溢出漏洞。
- 输入强校验:所有入口严格校验参数类型、指针、长度、对齐,非法输入直接拦截。
- 错误模糊处理:解密失败统一返回通用安全错误,不泄露失败细节,防御侧信道攻击。
- 资源全路径释放:所有错误分支都正确释放内存和句柄,无资源泄漏。
五、细节讨论点
- IV 长度选择:AES-GCM 官方推荐 96 位(12 字节)IV,性能与安全性最优;此处使用 16 字节 IV,功能安全但会增加一次 GHASH 计算,对密钥密封这种低频操作无实际影响。
- 无 AAD 绑定:当前密封未附加认证数据(如密钥用途、版本号),未来可将保留字段作为 AAD 参与认证,进一步增强绑定强度。
- 无状态设计:每次调用都重新派生密钥、创建密码操作,无会话状态残留,安全性更高;代价是略有性能开销,但密钥密封属于低频操作,完全可接受。
更多推荐


所有评论(0)