TEESimulator Patch模式机制详解:真实硬件证明如何被重签为Keybox根(X.509与DER手术全解)
【免费下载链接】TEESimulatorSoftware simulation for Android hardware-backed key pairs with key attestation项目地址: https://gitcode.com/gh_mirrors/te/TEESimulator
TEESimulator 的 Patch 模式是 Android 硬件密钥证明(Key Attestation)模拟方案中最接近真机的一种工作方式:它让真实硬件继续生成密钥,只把证明证书链的根换成你自己提供的 Keybox,并顺手把信任根(Root of Trust)改写为"已锁 + Verified"。本文带你完整看懂这条"重签"流水线背后的 X.509 与 DER 字节级手术。
Patch 模式与 Generation 模式:两种玩法怎么选
TEESimulator 的每个配置档案(profile)都有两种运行模式,定义见 schema.js:
| 对比项 | Patch 模式(默认)🩹 | Generation 模式 |
|---|---|---|
| 密钥由谁生成 | 真实 TEE 硬件 | 进程内软件 KeyMint(kmr-ta) |
| 密钥 blob 是否保留 | ✅ 原样保留,后续操作继续走真实 HAL | ❌ 软件生成,带 TEESIMkm 标记 |
| 证明内容(challenge、版本、TEE 强制授权) | 真实硬件写入,原样保留 | 由 kmr-ta 现场生成 |
| 证书链根 | 重签为 Keybox 根 | 直接由 Keybox 签发 |
| 适用场景 | 硬件证明可用的设备(检测点更少) | 硬件损坏/不可证明时的兜底 |
默认配置里就是"mode": "patch",见 config.default.json。一句话概括:Patch 模式 = 真硬件干活,只换"签名人"和"几个字段"。
重签流水线:generateKey 之后的四步走
路由逻辑在 keymint_router.cpp 中(PatchAttest路径),整体流程如下:
- 转发:命中目标应用的
generateKey请求被原样转发给真实 KeyMint HAL,密钥是货真价实的硬件密钥; - 保留 blob:真实的 key blob 一字不改交还应用,之后
begin/finish等操作依旧转发给硬件(见 keymint/README.md); - 重签叶子:把硬件签出的真实证明叶子(DER 格式 X.509 证书)交给 Rust 端的
teesim_km_patch_attestation,即 resign.rs 里的patch_attestation; - 换根输出:返回
[重签后的叶子, Keybox 证书链]替换掉原有证明链。
若真实 HAL 拒绝或没有返回证明(比如某些解锁后的机型),会自动回退到 Generation 模式,保证目标应用总能拿到一把可用的密钥。
特殊场景兜底
- 已有密钥补签:应用在纳管前已生成的密钥,守护进程会把其真实叶子通过控制通道重新走一遍同样的重签(
teesim_cfg_resign),见 ReAttest.kt; - StrongBox 关卡:设备解锁后 StrongBox 可能无法证明,采集阶段的
g_strongbox_ok决定 StrongBox 级别是走 Patch 还是强制 Generation(keymint_router.cpp); - 证明密钥(ATTEST_KEY):永远走 Generation——因为"签假叶子的钥匙也得是我们的",这样它日后签发的每个叶子都能被 Patch 信任根。
X.509 手术:重写叶子证书的三个字段
patch_attestation用x509-cert库解析真实叶子后,做了三处标准 X.509 层级的改动:
① 换签发者(issuer):新 issuer = Keybox 叶子证书的 subject,即把"谁签的这棵树"从真实 TEE 换成了你的 Keybox;
② 换签名算法:按被证明密钥的算法选择 Keybox 中对应的 RSA(1.2.840.113549.1.1.11)或 EC(1.2.840.10045.4.3.2)SHA-256 签名算法;Keybox 若只有单一算法则自动回退(resign.rs);
③ 重写 KeyMint 证明扩展:定位 OID1.3.6.1.4.1.11129.2.1.17的扩展,对其内部的KeyDescription执行下面的 DER 手术。
改完之后,用 Keybox 私钥对新的tbsCertificate重新签名(复用与生成模式相同的 BoringSSL 后端),重新打包为 DER 叶子。
DER 手术:高编号上下文标签下的字节替换
KeyDescription里的信任根和补丁级别藏在高编号[704]这类上下文标签下,通用证书库无法直接寻址,所以 resign.rs 手写了一小套 DER 读写器来"做手术"。重写对象一目了然:
| 标签 | 字段 | 处理方式 |
|---|---|---|
[704] | RootOfTrust(信任根) | 替换;缺失则插入 |
[705] | OS_VERSION | 替换;缺失则插入 |
[706] | OS_PATCHLEVEL | 替换;缺失则插入 |
[718] | VENDOR_PATCHLEVEL | 替换;缺失则插入 |
[719] | BOOT_PATCHLEVEL | 替换;缺失则插入 |
[710]~[717]、[723] | 设备身份 ID(brand/serial/IMEI…) | 仅替换,绝不插入(应用没请求 ID 就不加,保持与真机行为一致) |
手术规则(patch_key_description,resign.rs)很克制:
- 字段存在 →就地替换(软件/TEE 两个授权列表都查,防止残留真实值);
- 信任根/补丁级别缺失 → 按标签号升序插入 TEE 强制列表,保持列表有序;
- 其余一切字段逐字节原样拷贝——被证明的公钥、challenge、KeyMint 版本、tee-enforced 授权统统不动。
新的信任根由build_root_of_trust(resign.rs)按 AOSPkmr-ta的格式构造:已锁定 + Verified 状态 + 采集自真机的 verified-boot 密钥——"内容是真的,状态是理想的"。补丁级别重写的原因也很实际:老设备的真实补丁级别往往过旧,会拖累 Play Integrity 的 STRONG 评级。
一句话总结 + 源码导读
Patch 模式的精髓:真硬件写内容,Keybox 负责盖章,手写 DER 只动该动的五个信任字段。想动手跟读,按这个顺序来:
| 模块 | 文件 | 看点 |
|---|---|---|
| 路由与模式切换 | keymint_router.cpp | PatchAttest转发+重签、失败回退 |
| 重签核心(X.509) | resign.rs | patch_attestation换根重签 |
| DER 手术核心 | resign.rs | 上下文标签读写、就地替换 |
| Keybox 解析 | attest.rs | RSA/ECDSA 双钥与证书链校验 |
| 模式配置 | config.default.json | "mode": "patch"与补丁级别配置 |
| 架构说明 | rust/teesim-km/README.md | 引擎级设计文档 |
上手只需:把keybox.xml放到/data/adb/teesim/,在 WebUI 里保持默认的 Operation mode = patch 即可(详见根目录 README.md 的 Installation 与 Configuration 章节)。若想从零构建源码,可执行:
git clone --recurse-submodules https://gitcode.com/gh_mirrors/te/TEESimulator cd TEESimulator ./gradlew zipRelease【免费下载链接】TEESimulatorSoftware simulation for Android hardware-backed key pairs with key attestation项目地址: https://gitcode.com/gh_mirrors/te/TEESimulator
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考