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 个参数为输出内存引用,其余必须为空,过滤非法调用。
  • 执行流程
  1. 校验输入指针和长度合法性;
  2. 分配临时堆缓冲区并初始化为 0,调用 TEE_GenerateRandom 生成硬件随机数;
  3. 将随机数拷贝到调用方输出缓冲区;
  4. 用 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。
  • 执行流程
  1. 打开系统 PTA(PTA_SYSTEM_UUID)会话;
  2. 传入可选的额外上下文数据(当前密封场景传 NULL);
  3. 调用 PTA_SYSTEM_DERIVE_TA_UNIQUE_KEY 完成派生,输出密钥到调用方缓冲区;
  4. 关闭会话并返回结果。
  • 设计价值:密钥派生在更高特权级的 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
  1. 初始化 blob 头部,reserved 置 0;
  2. 生成 16 字节随机 IV 存入 blob 头部;
  3. 调用 TEE_AEInit 初始化 AE 操作,配置 IV 长度、标签长度;
  4. 调用 TEE_AEEncryptFinal 完成一次性加密并生成认证标签;
  5. 用 ADD_OVERFLOW 宏校验输出总长度,防止整数溢出。
解密侧 huk_ae_decrypt
  1. 用 SUB_OVERFLOW 宏计算密文长度,校验 blob 长度合法性,防止整数下溢;
  2. 从 blob 头部取出 IV,初始化 AE 操作;
  3. 取出认证标签,调用 TEE_AEDecryptFinal 完成解密+完整性校验;
  4. 解密/校验失败统一返回 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;
}
负责完整的密码学操作生命周期:创建操作句柄、派生密钥、加载密钥、执行加解密、资源清理。
  1. 分配 AES-GCM 算法操作句柄,密钥长度对应派生密钥的比特位数(默认 256 位 AES);
  2. 调用 derive_unique_key 派生 TA 专属密钥,暂存于栈上缓冲区;
  3. 创建 AES 临时密钥对象,将派生密钥填充到对象中;
  4. 将密钥对象绑定到 AE 操作句柄;
  5. 根据加解密模式调用对应底层函数;
  6. 强制清理:无论成功失败,最终都会释放密钥对象、操作句柄,并用 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 路由到对应处理函数,不支持的命令返回错误。

四、安全设计亮点总结

  1. 密钥分级隔离:不直接接触 HUK,通过系统 PTA 派生 TA 专属密钥,单 TA 泄露不影响全局安全。
  2. 认证加密保障:使用 AES-GCM AEAD 算法,同时保证密钥的机密性和完整性,防止密文被篡改。
  3. 最小权限访问:仅允许 REE 内核调用,用户态不可达,严格控制攻击入口。
  4. 敏感数据清零:所有栈/堆上的密钥、随机数临时缓冲区,使用后均显式清零,避免内存泄露。
  5. 整数溢出防护:所有长度计算使用安全宏 ADD_OVERFLOW/SUB_OVERFLOW,杜绝整数溢出漏洞。
  6. 输入强校验:所有入口严格校验参数类型、指针、长度、对齐,非法输入直接拦截。
  7. 错误模糊处理:解密失败统一返回通用安全错误,不泄露失败细节,防御侧信道攻击。
  8. 资源全路径释放:所有错误分支都正确释放内存和句柄,无资源泄漏。

五、细节讨论点

  1. IV 长度选择:AES-GCM 官方推荐 96 位(12 字节)IV,性能与安全性最优;此处使用 16 字节 IV,功能安全但会增加一次 GHASH 计算,对密钥密封这种低频操作无实际影响。
  2. 无 AAD 绑定:当前密封未附加认证数据(如密钥用途、版本号),未来可将保留字段作为 AAD 参与认证,进一步增强绑定强度。
  3. 无状态设计:每次调用都重新派生密钥、创建密码操作,无会话状态残留,安全性更高;代价是略有性能开销,但密钥密封属于低频操作,完全可接受。

更多推荐