4. TPM 2.0 远程证明实战:生成 AK、获取 PCR、生成并验证 Quote
好了,咱们进入实战环节。前面讲了那么多理论,说白了,远程证明的核心就三件事:你是谁(AK 身份)、你是什么状态(PCR 值)、你敢不敢现场证明(Quote 签名)。
我个人习惯用 tpm2-tools 这套工具集,它直接跟 TPM 芯片打交道,没有中间商赚差价。你想想看,如果连 TPM 都信不过,那远程证明就无从谈起了。
本章核心目标:在本地模拟一次完整的远程证明流程。你会亲手生成 AK、读取 PCR、创建 Quote,最后验证它。做完这一遍,远程证明的骨架你就摸透了。
4.1 环境准备与工具链
先确认你的环境。我建议用 Ubuntu 22.04 或更新版本,内核自带 TPM 驱动。如果你用的是虚拟机,记得在 BIOS 里开启 TPM 2.0。
# 安装 tpm2-tools
sudo apt update
sudo apt install tpm2-tools
# 确认 TPM 设备存在
ls /dev/tpm*
# 应该看到 /dev/tpm0 和 /dev/tpmrm0
# 检查 TPM 版本
tpm2_getcap properties-fixed | grep -i "TPM2"
嗯,这里要注意:/dev/tpm0 是字符设备,/dev/tpmrm0 是资源管理设备。我建议你用 tpmrm0,它能帮你处理 TPM 内部资源冲突,省心很多。我曾经在项目里直接用 tpm0,结果并发操作时 TPM 报资源不足,排查了半天才发现是没走资源管理器。
4.2 生成 Attestation Key(AK)
AK 是远程证明的身份证。它必须是 TPM 内部生成的,私钥永远不离开芯片。这一点跟普通软件密钥有本质区别。
生成 AK 的流程分三步:
- 创建主密钥(Primary Key):基于 TPM 内部种子生成,作为 AK 的父密钥。
- 创建 AK:挂载在主密钥下,指定用途为签名。
- 读取 AK 公钥:发给验证方,用于后续验证 Quote。
# 第一步:创建主密钥(EK 派生)
tpm2_createprimary -c primary.ctx -C e -P ""
# 第二步:创建 AK(签名密钥)
tpm2_create -C primary.ctx -u ak.pub -r ak.priv -a "fixedtpm|fixedparent|sensitivedataorigin|userwithauth|sign" -g sha256 -G rsa2048
# 第三步:加载 AK 到 TPM
tpm2_load -C primary.ctx -u ak.pub -r ak.priv -c ak.ctx
# 第四步:读取 AK 公钥(输出为 PEM 格式)
tpm2_readpublic -c ak.ctx -o ak.pem
小技巧:-a 参数里的属性标记很重要。fixedtpm 表示这把密钥绑定在这个 TPM 上,sign 表示只能用于签名。我见过有人忘了加 sign,结果生成的密钥不能用于 Quote,白忙活一场。
生成的 ak.pem 就是你的 AK 公钥。你可以用 openssl 看看它的内容:
openssl rsa -pubin -in ak.pem -text -noout
4.3 获取平台配置寄存器(PCR)值
PCR 是 TPM 的“黑匣子”,记录着系统启动以来的度量值。每个 PCR 对应不同的度量内容:
| PCR 索引 | 度量内容 | 典型用途 |
|---|---|---|
| 0-7 | BIOS、Boot Loader、OS 内核 | 静态信任链(SRTM) |
| 8-15 | 操作系统运行时组件 | 动态信任链(DRTM) |
| 16-23 | 用户自定义 | 应用层度量 |
读取 PCR 值很简单:
# 读取所有 PCR 值
tpm2_pcrread
# 读取特定 PCR(例如 PCR 0-7)
tpm2_pcrread sha256:0,1,2,3,4,5,6,7
# 输出为可解析的格式
tpm2_pcrread sha256:0 -o pcr0.bin
xxd pcr0.bin
你想想看,验证方拿到你的 PCR 值后,会跟已知的“黄金度量值”对比。如果一致,说明你的设备启动过程没有被篡改。我在项目中遇到过一种情况:客户说 PCR 值总对不上,后来发现是 BIOS 升级了,度量值变了。所以,黄金度量值也要跟着固件版本走。
4.4 生成 Quote
Quote 是远程证明的核心操作。它用 AK 对一组 PCR 值进行签名,同时附带一个 Nonce(随机数)防止重放攻击。
流程是这样的:
- 验证方生成一个 Nonce,发给证明方。
- 证明方用 TPM 对指定 PCR 和 Nonce 一起签名,生成 Quote。
- 证明方把 Quote 和 PCR 值发给验证方。
# 假设验证方发来的 Nonce 是(实际应由验证方生成)
NONCE="a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0ab"
# 生成 Quote(对 PCR 0-7 签名)
tpm2_quote -c ak.ctx -l sha256:0,1,2,3,4,5,6,7 -q $NONCE -m quote.msg -s quote.sig -o pcr.bin
# 查看 Quote 消息内容
tpm2_print -t TPMS_ATTEST quote.msg
重要提醒:-q 参数传入的 Nonce 必须是十六进制字符串,长度 32 字节(64 个十六进制字符)。我见过有人传了 ASCII 字符串,结果 TPM 直接报参数错误。另外,quote.msg 里包含了 PCR 值、Nonce、时间戳等信息,不要只看签名,消息本身也要验证。
4.5 验证 Quote
验证方拿到 quote.msg、quote.sig、pcr.bin 和 ak.pem 后,做三件事:
- 验证签名:用 AK 公钥验证 Quote 签名。
- 验证 Nonce:确保 Quote 里的 Nonce 跟自己发出去的一致。
- 验证 PCR 值:比对 PCR 值与黄金度量值。
# 第一步:验证签名
tpm2_checkquote -u ak.pem -m quote.msg -s quote.sig -f pcr.bin
# 第二步:解析 Quote 消息,提取 Nonce 和 PCR 值
tpm2_print -t TPMS_ATTEST quote.msg
# 第三步:手动比对 PCR 值(假设你有黄金度量值 golden_pcr.bin)
diff pcr.bin golden_pcr.bin
如果 tpm2_checkquote 没有报错,说明签名验证通过。然后你从 quote.msg 里提取出 Nonce,跟之前发出去的比对。最后比对 PCR 值。
避坑指南:我曾经在验证时忽略了 Nonce 比对,结果被同事指出存在重放攻击风险。虽然 Quote 本身有时间戳,但 Nonce 是验证方生成的,只有验证方知道它是不是新鲜的。所以,Nonce 比对这一步不能省。
4.6 完整流程可视化
下面这张图把整个流程串起来了。你可以看到证明方和验证方各自做了什么,数据流怎么走。
4.7 常见问题与避坑
实战中你可能会遇到这些问题,我帮你列出来:
| 问题 | 原因 | 解决方法 |
|---|---|---|
TPM_RC_NV_DEFINED |
TPM 未初始化或 NV 空间不足 | 执行 tpm2_clear 或 tpm2_startup -c |
TPM_RC_COMMAND_SIZE |
Nonce 长度不对 | 确保 Nonce 是 32 字节十六进制字符串 |
TPM_RC_AUTH_FAIL |
密钥授权密码错误 | 检查创建 AK 时是否设置了密码 |
| PCR 值总对不上 | 固件版本或启动配置不同 | 重新采集黄金度量值 |
安全提醒:AK 私钥虽然不出 TPM,但 AK 公钥的传输通道要保证完整性。我建议在发送 AK 公钥时,用 EK(Endorsement Key)证书做一次签名,证明这把 AK 确实来自这个 TPM。这部分内容我们会在后面的章节展开。