设备认证 TEE 规范
设备认证 TEE 规范
1. 背景
设备身份是微信 VOIP 业务能够正常运行的基础,开发者在接入 VOIP 业务时,会通过如下两个流程来进行身份的确定:
- 设备注册。通过 SN + modelId 两个维度来标定这台设备。
- 拿票据。在进行 VOIP 通话前,需要拿到这台设备所对应的票据。
对于没有 TEE 的机器,我们要求设备系统里能够操作 EMMC 存储的 RPMB 区域,并且需要将 RPMB 的归属权给到 VOIP 业务,VOIP 在设备注册时会将与设备对应的唯一密钥写入存储的 KEY 区域,用来做身份校验。
而对于有 TEE 的机器,我们信赖 TEE 的结果,但需要 TEE 里按照规范完成 TA 的开发。
2. TA 开发
不管是 optee,还是 trusty 或 qsee 等,TEE 的使用流程通常如下:
打开 TEE 环境 > 开启一个会话 > 发送命令 > 获取信息 > 结束会话 > 关闭 TEE 环境
这里我们需要定义如下标准:
- 会话名称。
- 命令的功能。
- 命令的交互数据定义。
- TA 里的运算逻辑
设备开发者需要根据规范进行如下开发:
- TA 开发,需要开发者或 tee 提供商按照规范开发 TA。
- HAL 开发,HAL 用于与 TA 的交互,微信的系统服务基于此 HAL。
- 服务集成,将微信发布的 rpmbd_tee 以系统服务方式运行起来。
TA 在逻辑上将存储分为两个区域。需要注意的是,这些数据最终应都存在于 EMMC 或 UFS 的 rpmb 分区,或其它 REE 访问不到的安全器件区域。
- 密钥区:32个字节。TA 代码逻辑里需要将此区域实现成只能写一次,类似于硬件上的 OTP (One Time Programmable) 区域。
- 数据区:单位为 Block,每个 Block 有 256 个字节,最小需要 32 个,开发者可根据实际情况来确定大小,若越界则返回相应错误码即可。Block 的地址从 0 开始。
2.1 会话名称
TA 的名称统一为 ta_devauth
2.2 命令定义
定义三个命令,分别是读数据、写数据、写密钥。
#define TA_DEVAUTH_CMD_READ 0x10
#define TA_DEVAUTH_CMD_WRITE 0x11
#define TA_DEVAUTH_CMD_PROKEY 0x12
详细说明:
命令 | TA_DEVAUTH_CMD_READ 0x10 |
功能 | 读 1 个 Block 的数据 |
参数 | 输入:Block 地址 输入/输出:Block 数据 BUFFER,284字节,见 2.3 数据定义。 输出:签名 BUFFER,=32字节 |
返回 | 0:成功读取,并返回 284 字节数据和 32 字节签名 -1:参数错误。 -2:地址越界。 -3:密钥区还没被写。 -5:其它错误。 |
特别说明 | 若密钥区还没被写(例如裸数据全是0x00,或没有TEE文件系统里的文件?),这种情况认为是一台全新的未激活设备,需要读取错误返回 -3。 |
命令 | TA_DEVAUTH_CMD_WRITE 0x11 |
功能 | 写 1 个 Block 的数据 |
参数 | 输入:Block 地址 输入:Block 数据 BUFFER,=284字节,见 2.3 数据定义。 输入:对应的签名 BUFFER,=32字节 |
返回 | 0:写入成功。 -1:参数错误。 -2:地址越界。 -3:密钥区还没被写。 -4:签名错误。 -5:其它错误。 |
特别说明 | 若密钥区还没被写(例如裸数据全是0x00,或没有TEE文件系统里的文件?),这种情况认为是一台全新的未激活设备,需要返回 -3。 此功能在写数据前,需要计算数据对应的签名,并且与参数传入的签名比对,若比对不成功返回 -4 |
命令 | TA_DEVAUTH_CMD_PROKEY 0x12 |
功能 | 写密钥 |
参数 | 密钥 BUFFER,=32字节 |
返回 | 0:写入成功。 -1:参数错误。 -3:密钥区已有数据。 -5:其它错误。 |
特别说明 | 若密钥区已有数据(例如裸数据不为0x00,或有文件?),则需要返回 -3。 |
2.3 数据定义
与 TA 交互时传入传出 Buffer 的大小为 284 字节,它的定义如下:
struct ta_data {
uint8_t data[256]; // 此 256 字节是 TA 写入到1个Block的内容
uint8_t nonce[16]; // 一般为随机字节,传入与传出的一定要一致。
uint32_t reserve1;
uint16_t reserve2;
uint16_t reserve3;
uint16_t reserve4;
uint16_t reserve5;
};
// sizeof(struct ta_data) = 284
签名 Buffer 大小为 32 字节。
CA 与 TA 交互消息定义:
struct ta_message {
uint32_t cmd; // 命令号
uint32_t block; // 读写地址
struct ta_data data; // 284 字节数据
uint8_t key[32]; // 32 字节 Key
uint8_t hmac[32]; // 32 字节 hmac
int ret; // 返回值
};
CA 侧参考伪代码:
static int tee_send_cmd_req(struct ta_message* ta_msg) {
int rc = 0;
if (ca_handle == 0) {
printf("not connected\n");
return -EINVAL;
}
if (tee_send_msg(ca_handle, ta_msg) < 0) {
return -1;
}
if (tee_resp_msg(ca_handle, ta_msg) < 0) {
return -1;
}
return 0;
}
2.4 运算逻辑
数据签名的算法为 HMAC_SHA256。
读数据时,TA 的流程:
- 若密钥区没数据,返回 -3。
- 读地址参数对应 Block 的 256 字节数据。
- 将读到的 256 字节与输入参数里的 16 字节 noce 以及其它 reserve,一共有 284 个字节。
- 利用密钥区的 32 字节密钥,对 284 字节进行签名,并返回 284 字节以及签名。
写数据时,TA 的流程:
- 若密钥区没数据,返回 -3。
- 得到输入参数里的 284 字节,得到输入参数中的签名1。
- 利用密钥区的 32 字节密钥,对 284 字节进行签名,得到签名2。
- 比较签名1与签名2,如果相等则将 284 字节中的 256 字节写入对应的 Block,如果不等则返回错误 -4。
写密钥时,TA 的流程:
- 若密钥区已有数据,返回 -3。
- 将 32 字节的密钥写入密钥区。
一个签名的数据示例,开发者可以此为基准来验证自己的 hmac_sha256:
char *key = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHH";
uint8_t buffer[284] = {0};
int main(int argc, char **argv) {
uint8_t hmac[32] = {0};
memset(buffer, 0x55, 284);
hmac_sha256(key, 32,
buffer, 284,
hmac, sizeof(hmac)
);
for (int i=0; i < 32; i++) {
printf("%02x ", hmac[i]);
}
return 0;
}
// 以上代码输出:
// 61 16 67 22 a0 93 66 74 bb 75 f8 87 0e 5e d4 59 2c d6 99 c0 14 a6 93 70 bd ff ea 3e 8e 84 52 4e
hmac_sha256 为标准算法,一般 tee 里已有此类算法,如果没有,可参考开源实现。
2.5 注意事项
- TEE 里的密钥区和数据区需保证具有 “永久存储” 特性,并且不应该被 REE 以任何方式直接访问,不会随用户的刷机、升级或其它常规行为而丢失。
- 密钥区域需要实现为 OTP 特性,即仅一次写入。
- 密钥区域无数据时,需要按规范返回错误码。
- 厂商需要将 CA 测试例程和代码给到微信,微信进行验收测试。
3. HAL 开发
按照 HAL 规范完成 HAL 的开发,HAL 里使用 CA 代码与 TA 进行交互,完成 TEE 的使用。
HAL 路径:android/hardware/interface/devauth
3.1 HAL 规范
types.hal
package android.hardware.devauth@1.0;
enum TA_CMD : uint32_t {
TA_DEVAUTH_CMD_READ = 0x10,
TA_DEVAUTH_CMD_WRITE = 0x11,
TA_DEVAUTH_CMD_PROKEY = 0x12,
};
struct ta_data {
uint8_t[256] data; // 此 256 字节是 TA 写入到1个Block的内容
uint8_t[16] nonce; // 一般为随机字节,传入与传出的一定要一致。
uint32_t reserve1;
uint16_t reserve2;
uint16_t reserve3;
uint16_t reserve4;
uint16_t reserve5;
};
struct ta_message {
uint32_t cmd; // 命令号
uint32_t block; // 读写地址
ta_data data; // 284 字节数据
uint8_t[32] key; // 32 字节 Key
HMacBuffer hmac; // 32 字节 hmac
int8_t ret; // 返回值
};
typedef uint8_t[32] HMacBuffer;
typedef uint8_t[32] ProKeyBuffer;
typedef ta_data ta_data_t;
typedef ta_message ta_message_t;
IDevauth.hal
package android.hardware.devauth@1.0;
interface IDevauth {
/**
* 读 Block 数据
*
* @param addr: Block 地址
* @param data: 输入的 struct ta_data
* @return retval: 返回值,返回值说明请见规范
* @return data: 返回的 struct ta_data,284 字节。
* @return hmac: 返回的签名, 32 字节。
*
*/
read_block(uint16_t addr, ta_data data) generates (int8_t retval, vec<uint8_t> data, vec<uint8_t> hmac);
/**
* 写 Block 数据
*
* @param addr: Block 地址
* @param data: 输入输出数据,对应规范里的 struct ta_data,284 字节。
* @param hmac: HAL 写数据时用的签名, 32 字节
*
* @return retval: 返回值说明请见规范
*/
write_block(uint16_t addr, ta_data data, HMacBuffer hmac) generates (int8_t retval);
/**
* 写 Key
*
* @param key: 32 字节key
*
* @return retval: 返回值说明请见规范
*/
program_key(ProKeyBuffer key) generates(int8_t retval);
};
3.2 开发参考
hardware/interfaces/devauth/1.0/
下放置 IDevauth.hal
、types.hal
,内容如上。
然后使用如下方式来生成代码:
LOC=hardware/interfaces/devauth/1.0/default
PACKAGE=android.hardware.devauth@1.0
hidl-gen -o $LOC -Lc++-impl -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport ${PACKAGE}
hidl-gen -o $LOC -Landroidbp-impl -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport ${PACKAGE}
./hardware/interfaces/update-makefiles.sh
此时 hal 目录应该如下:
root~/android> tree hardware/interfaces/devauth/
hardware/interfaces/devauth/
└── 1.0
├── Android.bp
├── default
│ ├── Android.bp
│ ├── Devauth.cpp
│ └── Devauth.h
├── IDevauth.hal
└── types.hal
2 directories, 6 files
再按照 HAL 接口定义,在相应的接口函数里完成 CA 代码的开发即可。
4. 验收
4.1 提交资料
- ✓ 芯片平台,存储类型。例:MTK81xx、EMMC 64GB
- ✓ REE 操作系统详细信息。例:Android 7 64位
- ✓ TEE 系统详细信息。例:基于 optee 的自研 tee,提供商为 xxx
- ✓ TEE 里数据存储位置。例:EMMC 里的 RPMB 分区
- ✓ TEE 侧的 TA 代码。例:ta_devauth 模块代码
- ✓ REE 侧的 CA 代码。例:测试用例代码包及相应 TEE 功能 so。
- ✓ REE 侧的 HAL 代码。例:hardware/interface/devauth 下的代码。
- ✓ 能 adb root 的样机。
4.2 测试用例
开发者完成 TEE 的 TA 开发后,应该进行测试用例开发,以验证 TA 的功能与逻辑。 前置条件:ta_deauth 所管理的区域无任何数据,再按顺序进行以下测试项。
- 读数据测试,预期返回 -3
- 写数据测试,预期返回 -3
- 写密钥测试,预期返回 0
- 写密钥测试,预期返回 -3
- 读数据测试,预期返回 0,且数据全是 0x00 且有签名。
- 用正确的 HMAC 写数据测试,预期返回 0
- 用错误的 HMAC 写数据测试,预期返回 -4
- 读数据测试,预期返回 0,并且返回正确的数据和签名。
可在官方测试用例代码的基础上,加上自己的 CA 实现,以快速验证 TA。
开发者完成 HAL 开发后,可用测试用例进行测试,
也可以直接下载已编译好的 tee_hal_test 进行测试,测试方法如下
tee_hal_test a
进行一次全新的测试,需要一个 ta_devauth 所管理的区域无任何数据。tee_hal_test
不带任何参数,可在 1. 后运行。
4.3 集成
4.2 中的测试用例通过后,下载 rpmbd_tee 并集成到系统中以服务方式运行起来即可,参考:
service rpmbd_tee /system/bin/rpmbd_tee
class main
user root
group root system
tee_hal_test 和 rpmbd_tee 可从此处下载
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗