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 的流程分三步:

  1. 创建主密钥(Primary Key):基于 TPM 内部种子生成,作为 AK 的父密钥。
  2. 创建 AK:挂载在主密钥下,指定用途为签名。
  3. 读取 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(随机数)防止重放攻击。

流程是这样的:

  1. 验证方生成一个 Nonce,发给证明方。
  2. 证明方用 TPM 对指定 PCR 和 Nonce 一起签名,生成 Quote。
  3. 证明方把 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 后,做三件事:

  1. 验证签名:用 AK 公钥验证 Quote 签名。
  2. 验证 Nonce:确保 Quote 里的 Nonce 跟自己发出去的一致。
  3. 验证 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 完整流程可视化

下面这张图把整个流程串起来了。你可以看到证明方和验证方各自做了什么,数据流怎么走。

TPM 2.0 远程证明完整流程 证明方(TPM 设备) 验证方(远程服务器) 1. 生成 AK(tpm2_create) 私钥永不离 TPM 2. 读取 PCR 值(tpm2_pcrread) 获取平台度量状态 3. 生成 Quote(tpm2_quote) PCR + Nonce → 签名 4. 发送数据 AK 公钥 + PCR + Quote 5. 生成 Nonce 32 字节随机数 6. 验证签名(tpm2_checkquote) 用 AK 公钥验证 7. 验证 Nonce 防止重放攻击 8. 验证 PCR 值 比对黄金度量值 发送 AK 公钥 发送 Nonce 发送 PCR + Quote 验证通过 → 设备可信

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。这部分内容我们会在后面的章节展开。