4、Quote与签名机制:TPM2_Quote命令详解、签名算法选择(RSA/ECC)、Nonce机制与重放攻击防护

Quote 是远程证明的核心操作。说白了,就是让 TPM 给你签个名,证明「当前平台状态是真实的」。但签名怎么签、用什么算法签、怎么防止别人拿着你的签名去骗人——这里面的坑,我踩过不少。

4.1 TPM2_Quote 命令到底干了什么?

很多人以为 TPM2_Quote 就是「读取 PCR 值然后签名」。其实没那么简单。我拆开来讲。

TPM2_Quote 的输入有三个关键东西:

  • PCR 选择器:你要对哪些 PCR 做证明?是只做 PCR[0-7],还是全部 24 个?
  • Nonce:一个一次性随机数,由验证方生成。后面会细讲。
  • 签名密钥句柄:用哪个密钥来签名?通常是 AK(Attestation Key)。

命令执行后,TPM 会返回一个 TPMS_ATTEST 结构体,里面包含了 PCR 值的摘要、Nonce、时钟信息等。然后 TPM 用 AK 对这个结构体签名,生成 TPMT_SIGNATURE。

核心要点:Quote 返回的不是 PCR 原始值,而是 PCR 值的哈希摘要。验证方需要自己重新计算 PCR 摘要,然后比对。

我在项目中遇到过有人直接把 PCR 原始值塞进 Quote 结构体,结果 TPM 报错。嗯,TPM 的 Quote 只接受摘要,不接受原始数据。这个设计是为了固定长度,也为了隐私——你不希望把每个 PCR 的原始值都暴露出去。

4.2 签名算法选择:RSA 还是 ECC?

TPM 2.0 支持多种签名算法。但实际项目中,你主要面临两个选择:RSA 和 ECC。我个人的建议是——

对比维度 RSA (RSASSA-PKCS1-v1_5 / RSASSA-PSS) ECC (ECDSA)
签名速度 较慢(尤其密钥长度 2048+) 快(约快 3-5 倍)
验证速度 快 较慢
签名大小 固定 256 字节(2048 位) 可变,通常 64-72 字节
密钥生成 慢,需要大素数 快
硬件支持 几乎所有 TPM 2.0 都支持 较新 TPM 才支持,部分旧设备没有
安全性 成熟,2048 位以上安全 同等安全强度下密钥更短

你想想看,如果你的设备是 IoT 网关,每次证明都要签名,签名次数频繁,ECC 明显更合适。但如果是服务器场景,验证方压力大,RSA 的验证速度快反而是优势。

我的经验:如果 TPM 固件版本较老(比如 2018 年以前的),优先选 RSA。我遇到过一台设备,TPM 声称支持 ECDSA,但实际签名时总是返回 TPM_RC_COMMAND_CODE——固件没实现完整。后来换成 RSA 就稳了。

4.3 Nonce 机制:为什么不能少?

Nonce 是 Number used ONCE 的缩写。在 Quote 里,Nonce 的作用是防止重放攻击。

假设没有 Nonce:

  1. 攻击者截获了一次合法的 Quote 数据(包含 PCR 值和签名)。
  2. 攻击者把设备搞坏了,PCR 值变了。
  3. 攻击者把之前截获的 Quote 数据重新发给验证方。
  4. 验证方验证签名通过,以为设备还是好的。

这就是重放攻击。Nonce 怎么防?

  • 验证方每次发起挑战时,生成一个随机 Nonce。
  • TPM 在签名时,把 Nonce 放进 TPMS_ATTEST 结构体。
  • 验证方收到 Quote 后,先检查 Nonce 是否和自己发出去的一致。
  • 如果不一致,直接拒绝。

Nonce 必须满足两个条件:唯一性和不可预测性。用时间戳当 Nonce 行不行?我见过有人这么干。结果呢?时间戳可以被预测,攻击者可以提前构造合法的 Quote。所以,一定要用密码学安全的随机数生成器。

避坑指南:我曾经在一个项目中,验证方没有检查 Nonce 的唯一性。结果攻击者用同一个 Nonce 发起了多次挑战,每次 Quote 都一样。虽然 Nonce 匹配了,但攻击者其实是在重放。正确的做法是:验证方维护一个 Nonce 列表,已经用过的 Nonce 不能再接受。

4.4 重放攻击防护:不止 Nonce

Nonce 是基础,但还不够。Quote 结构体里还有两个字段可以用来增强防护:

  • clock:TPM 内部的单调递增时钟。
  • resetCount:TPM 复位次数。

验证方可以检查 clock 是否比上一次大,resetCount 是否和预期一致。如果 clock 变小了,说明有人可能回滚了 TPM 状态。

我建议的做法是:

  1. 验证方保存每个设备的 lastClock 和 lastResetCount。
  2. 每次收到 Quote,检查 clock > lastClock。
  3. 如果 resetCount 变了,说明设备重启过,需要重新评估信任。

你想想看,如果攻击者把设备断电,然后恢复到一个旧的状态,PCR 值可能回到一个「干净」的状态。但 clock 会暴露这个行为——因为断电后 clock 不会回退,但 resetCount 会增加。

4.5 签名验证的完整流程

说了这么多,我把完整的 Quote 验证流程画出来:

TPM2_Quote 签名验证流程 验证方 (Verifier) 设备 (Prover) ① 生成随机 Nonce TPM 内部 读取 PCR 摘要 组装 TPMS_ATTEST 用 AK 签名 ② 发送挑战 (Nonce + PCR选择) ③ 返回 Quote (TPMS_ATTEST + 签名) 验证方验证 ④ 检查 Nonce 是否匹配 ⑤ 验证 AK 证书链 ⑥ 验证签名 (RSA/ECC) ⑦ 比对 PCR 摘要与预期值 验证通过 / 拒绝 ⑧ 返回验证结果 ⚠ 常见失败原因 • Nonce 不匹配 → 重放攻击 • AK 证书过期 → 信任链断裂 • PCR 摘要不符 → 平台状态异常

4.6 代码示例:TPM2_Quote 调用

下面是一个使用 tpm2-tools 的 Quote 示例。实际项目中你可能用 C 或 Rust 调用 TPM 库,但原理一样。

# 1. 验证方生成 Nonce(16字节随机数)
openssl rand -hex 16 > nonce.bin

# 2. 设备端执行 Quote(使用 AK 句柄 0x81000000,对 PCR[0-7] 做证明)
tpm2_quote \
  --key-context 0x81000000 \
  --pcr-list sha256:0,1,2,3,4,5,6,7 \
  --message quote.msg \
  --signature quote.sig \
  --qualification nonce.bin

# 3. 验证方验证签名(需要 AK 公钥)
tpm2_verifysignature \
  --key-context ak.pub \
  --message quote.msg \
  --signature quote.sig \
  --ticket quote.tkt

# 4. 解析 TPMS_ATTEST,提取 PCR 摘要和 Nonce
tpm2_print --tpm2 quote.msg

注意:--qualification 参数就是 Nonce。有些工具叫 --nonce,但 TPM 规范里叫 qualification data。别搞混了。

4.7 避坑总结

我把 Quote 和签名机制里常见的坑列出来,你对照检查:

坑 后果 解决方案
Nonce 用时间戳 可预测,重放攻击 用密码学安全随机数
不检查 Nonce 唯一性 同一 Nonce 可重复使用 维护已用 Nonce 列表
AK 证书链不验证 伪造 AK 可冒充设备 验证 AK 证书到根 CA
忽略 clock 和 resetCount 状态回滚攻击 保存并比对 clock/resetCount
签名算法选错 性能差或兼容性问题 根据场景选 RSA 或 ECC
PCR 选择器不全 关键 PCR 未被证明 明确需要证明的 PCR 集合

嗯,Quote 和签名机制就讲到这里。记住一句话:Quote 不是简单的签名,而是一个包含 Nonce、PCR 摘要、时钟信息的结构化证明。每个字段都有它的作用,少一个都不行。