从 PFR 到 DICE:安全启动链的工业级实现与 TPM2 远程证明体系深度实战
引言:当软件安全的尽头是硬件信任根
现代计算安全的演进有一条清晰的主线——信任不断下沉。从应用层的 TLS/HTTPS,到操作系统层的 Secure Boot/DM-Verity,再到可信执行环境(TEE)的 Intel SGX/AMD SEV-SNP/ARM TrustZone,每一层的安全都依赖于下层提供的信任根基。
而这座信任大厦的最底层,是硬件信任根(Hardware Root of Trust, HRoT)。没有可靠的硬件信任根,上层所有的加密验证、远程证明、可信执行都成了空中楼阁。
然而,构建一套工业级的安全启动链远比"在 UEFI 里签个名"复杂。它需要解决一系列关键问题:
本文将从三个核心技术的深度工程实践出发,解析现代安全启动链的完整实现:PFR(Platform Firmware Resiliency,平台固件弹性)、DICE(Device Identifier Composition Engine,设备标识符组合引擎) 和 TPM2(Trusted Platform Module 2.0,可信平台模块)远程证明体系。我们将结合 TCG 规范、Linux 内核驱动代码和实际工程案例,呈现一条从硅片到云端的完整信任链。
一、DICE:设备身份的密码学基石
1.1 DICE 的核心思想
DICE(Device Identifier Composition Engine)是 TCG(Trusted Computing Group)定义的一种标准化机制,用于在设备启动的最早阶段(通常为 ROM Bootloader)建立密码学身份。它的核心设计哲学可以用一句话概括:每一层软件的度量(Measurement)决定了下一层的唯一密钥。
这种设计带来一个关键特性:如果固件被篡改,设备将自动推导出不同的密钥,从而无法向服务端证明自己的合法性。不需要预先烧录唯一密钥,不需要在 ROM 中存储秘密——"身份"是从代码本身涌现出来的。
1.2 密码学推导链
DICE 的核心操作是 Compound Device Identifier(CDI) 的推导。整个流程如下:
┌──────────────────────────────────────────────────┐
│ 阶段 0:UDS (Unique Device Secret) │
│ ─ 硬件熔丝/PUF 提供的唯一秘密,只在 DICE 层可见 │
│ ─ 长度:256 bit (AES-256) │
└─────────────────┬────────────────────────────────┘
│
▼ HMAC-SHA256(UDS, Stage0_Firmware_Hash)
┌──────────────────────────────────────────────────┐
│ 阶段 1:CDI_0 (Compound Device Identifier 0) │
│ ─ Stage 0 的"复合设备标识符" │
│ ─ 由 UDS 和 Stage 0 固件哈希推导而来 │
│ ─ Stage 0 固件被篡改 → CDI_0 完全不同 │
└─────────────────┬────────────────────────────────┘
│
▼ HMAC-SHA256(CDI_0, Stage1_Firmware_Hash)
┌──────────────────────────────────────────────────┐
│ 阶段 2:CDI_1 (Compound Device Identifier 1) │
│ ─ Stage 1 的复合设备标识符 │
│ ─ 绑定了 Stage 0 和 Stage 1 的双重度量 │
└─────────────────┬────────────────────────────────┘
│
▼ ...继续传递直到 OS 内核
┌──────────────────────────────────────────────────┐
│ 阶段 N:CDI_N(操作系统的复合身份) │
│ ─ 完整启动链的密码学摘要 │
└──────────────────────────────────────────────────┘
1.3 DICE 密钥推导的代码实现
以下是一个简化的 DICE 密钥推导实现,展示了 CDI 的计算逻辑:
/* DICE CDI derivation - simplified reference implementation */
#include <openssl/hmac.h>
#include <stdint.h>
#include <string.h>
#define DICE_DIGEST_SIZE 32 /* SHA-256 */
#define DICE_MAX_COMPONENTS 16
struct dice_config {
uint8_t uds[DICE_DIGEST_SIZE]; /* 硬件唯一密钥 */
uint8_t cdi[DICE_DIGEST_SIZE]; /* 当前 CDI */
uint8_t cdi_private_key[32]; /* Ed25519/X25519 私钥种子 */
int stage_index;
};
/* 核心:用上一层的 CDI 推导下一层 */
int dice_derive_next_cdi(struct dice_config *ctx,
const uint8_t *next_stage_hash,
size_t hash_len)
{
unsigned char new_cdi[DICE_DIGEST_SIZE];
unsigned int new_cdi_len = DICE_DIGEST_SIZE;
/* CDI_{n+1} = HMAC-SHA256(Key=CDI_n, Data=Hash(Firmware_{n+1})) */
if (!HMAC(EVP_sha256(), ctx->cdi, DICE_DIGEST_SIZE,
next_stage_hash, hash_len,
new_cdi, &new_cdi_len)) {
return -1;
}
/* 安全擦除旧 CDI——防止后续代码读取到以前的秘密 */
explicit_bzero(ctx->cdi, DICE_DIGEST_SIZE);
memcpy(ctx->cdi, new_cdi, DICE_DIGEST_SIZE);
/* 同理擦除 HMAC 中间结果 */
explicit_bzero(new_cdi, DICE_DIGEST_SIZE);
ctx->stage_index++;
return 0;
}
/* 从 CDI 派生设备密钥对(X25519 用于 ECDH 密钥交换) */
int dice_derive_device_keypair(struct dice_config *ctx,
uint8_t *public_key,
uint8_t *private_key)
{
/*
* 使用 HKDF 从 CDI 派生确定性密钥:
* PrivateKey = HKDF-Expand(HKDF-Extract(CDI, "DICE-KEY-V1"), 32)
* PublicKey = X25519(PrivateKey, basepoint)
*
* 确定性派生的好处:只要固件不变,设备重启后推导出相同的密钥对
*/
uint8_t seed[32];
/* HKDF-Extract */
HMAC(EVP_sha256(), ctx->cdi, DICE_DIGEST_SIZE,
(const uint8_t *)"DICE-KEY-V1", 11,
seed, NULL);
/* HKDF-Expand 得到 X25519 私钥 */
uint8_t okm[64]; /* X25519 需要扩展 */
HMAC(EVP_sha256(), seed, 32,
(const uint8_t *)"\x01" /* info = version byte */, 1,
okm, NULL);
memcpy(private_key, okm, 32);
/* X25519 标量乘法生成公钥 */
x25519_public_key(private_key, public_key);
explicit_bzero(seed, 32);
explicit_bzero(okm, 64);
return 0;
}
1.4 DICE 的硬件实现要求
DICE 的前提是有一个硬件秘密 UDS。工业级的 UDS 来源有三种:
| UDS 来源 | 实现方式 | 安全等级 | 适用场景 |
|---|---|---|---|
| eFuse 编程 | 制造时烧入 256-bit 随机数 | 中(可能被物理攻击读取) | IoT、消费级 MCU |
| SRAM PUF | 利用 SRAM 上电随机态作为指纹 | 高(电阻式,不可克隆) | 汽车 ECU、工业控制器 |
| 专用安全芯片 | 独立 IC(如 OptiTrust、MAX32520) | 最高(防侧信道、防篡改) | 服务器 BMS、金融终端 |
关键安全约束:
二、PFR:固件韧性的 NIST 工程框架
2.1 从 NIST SP 1800-34 说起
NIST(美国国家标准与技术研究院)发布的 SP 1800-34D 文档定义了平台固件弹性(Platform Firmware Resiliency, PFR)的最佳实践。其核心目标有三:
PFR 为 DICE 提供了运行时的"免疫系统"——它不仅启动时验证,还在运行期间持续监控。
2.2 PFR 的信任链架构
典型的 PFR 架构以可信平台控制器(Platform Controller Hub, PCH/BMC)为信任锚点,监控主 CPU 的 SPI Flash:
┌─────────────────────────────────────────────────────┐
│ 服务器主板 │
│ │
│ ┌──────────┐ SPI Bus ┌──────────────────────┐ │
│ │ BMC/PCH │◄──────────►│ 主 SPI Flash │ │
│ │ (信任锚) │ │ (BIOS/UEFI 固件) │ │
│ └─────┬────┘ └──────────┬───────────┘ │
│ │ │ │
│ │ 实时监控 │ │
│ │ (I2C/SPI/I3C) │ │
│ ┌─────▼────┐ ┌──────▼───────────┐ │
│ │ 备份 Flash │ │ 主 CPU │ │
│ │ (Recovery)│ │ (运行 UEFI) │ │
│ └──────────┘ └──────────────────┘ │
│ │
│ 关键特性: │
│ 1. 双 Flash 镜像(Active + Recovery) │
│ 2. 硬件级 SPI 总线监控(非软件检测) │
│ 3. 看门狗定时器 + 自动回滚 │
└─────────────────────────────────────────────────────┘
2.3 PFR 的硬件监控机制
PFR 的核心创新在于将固件保护下沉到硬件控制器(通常是一个专用的 CPLD 或集成在 BMC 中的模块)。该控制器:
/* PFR 固件监控的硬件状态机 */
enum pfr_state {
PFR_STATE_INIT, /* 上电初始化 */
PFR_STATE_MONITOR, /* 正常运行监控 */
PFR_STATE_DETECTED, /* 检测到篡改 */
PFR_STATE_RECOVERY, /* 执行恢复 */
PFR_STATE_LOCKDOWN /* 锁定(需人工干预) */
};
struct pfr_context {
enum pfr_state state;
uint32_t active_region_base;
uint32_t recovery_region_base;
uint8_t golden_hash[32]; /* SHA-256 of trusted firmware */
uint32_t watchdog_period_ms;
bool auto_recovery_enabled;
};
/* 硬件中断处理:检测到未授权 SPI 写入时触发 */
void pfr_spi_violation_isr(struct pfr_context *ctx,
uint32_t target_addr,
uint32_t len)
{
/* 步骤1:记录攻击日志到 BMC 的 EEPROM */
pfr_log_attack(ctx, target_addr, len, PERR_TYPE_SPI_WRITE);
/* 步骤2:如果攻击目标落在受保护区域 */
if (pfr_is_protected_region(ctx, target_addr)) {
ctx->state = PFR_STATE_DETECTED;
/* 步骤3:触发自动恢复 */
if (ctx->auto_recovery_enabled) {
/* 硬件直接将 SPI Flash 切换至 Recovery 镜像 */
pfr_switch_to_recovery(ctx);
/* 步骤4:通知 BMC 发起系统复位 */
bmc_request_reset("PFR: Firmware recovery initiated");
ctx->state = PFR_STATE_RECOVERY;
} else {
/* 如果自动恢复被禁用,锁定平台 */
ctx->state = PFR_STATE_LOCKDOWN;
pfr_assert_platform_lock(ctx);
}
}
}
2.4 PFR 的回滚保护(Anti-Rollback)
防降级攻击是 PFR 的另一大核心功能。攻击者可能利用旧版本固件中已知的漏洞(如 Logo Fail、BootHole 等),将固件回滚到易受攻击的版本。
PFR 通过以下机制防护:
# 使用 TPM2 单调计数器实现防回滚
# 1. 在 TPM2 上创建计数器
tpm2_nvdefine -C o -s 8 -a "authwrite|authread|platformcreate" \
-t "counter|no_da" 0x01C10100
# 2. 读取当前计数器值(当前固件版本号)
tpm2_nvread -C o -s 8 0x01C10100 | xxd -p
# 输出: 0000000000000014 (版本 20)
# 3. 升级固件时递增计数器
tpm2_nvincrement -C o 0x01C10100
# 4. 启动时验证:固件声明的版本 >= TPM2 计数器值
# 如果固件版本 < 计数器值 → 回滚攻击,拒绝启动
三、TPM2 远程证明体系
3.1 远程证明的核心流程
TPM2(Trusted Platform Module 2.0)是独立的安全芯片,提供密钥生成、加密操作、平台配置寄存器(PCR)和远程证明能力。远程证明的目标是:让远端验证服务确信本地平台运行的是未被篡改的固件和软件。
完整的远程证明流程:
┌──────────┐ ┌──────────────────┐
│ 设备/节点 │ │ 远程验证服务 │
│ (Attestor)│ │ (Verifier/AAA) │
└─────┬────┘ └────────┬─────────┘
│ │
│ 1. Verify 发送 Nonce + 公钥引用 │
│◄──────────────────────────────────────────── │
│ │
│ 2. Attestor 执行 TPM2_Quote: │
│ - 获取 PCR[0..7] 的当前度量值 │
│ - 用 AIK (Attestation Identity Key) 签名 │
│ - 附带 Event Log (TPM2 event log) │
│────────────────────────────────────────────►│
│ Quote = { PCR_values, Nonce, Signature } │
│ │
│ 3. Verify 验证流程: │
│ │
│ (a) 验证 AIK 签名 (证明 Quote 来自真实 TPM) │
│ (b) 验证 Nonce 匹配 (新鲜性,防重放) │
│ (c) 重新计算 PCR 复合哈希 │
│ (d) 与 Event Log 中的预期度量值比对 │
│ (e) 判断:系统状态是否可信? │
│ │
│◄──────────────────────────────────────────── │
│ 4. 返回验证结果 + 访问令牌/解密密钥 │
│ │
3.2 PCR 度量链详解
TPM2 的 PCR(Platform Configuration Register)是整个度量体系的基石。不同的 PCR 索引度量启动链的不同阶段:
| PCR 索引 | 度量阶段 | 度量内容 | 典型值来源 |
|---|---|---|---|
| PCR[0] | CRTM/BIOS | UEFI 固件主体代码 | SEC + PEI 阶段度量 |
| PCR[1] | UEFI 配置 | UEFI 变量、启动选项 | UEFI 设置界面配置 |
| PCR[2] | Option ROM | 外插卡固件 | GPU/RAID/NIC 的 UEFI Driver |
| PCR[3] | Option ROM 配置 | 外插卡配置 | Option ROM 的配置项 |
| PCR[4] | Boot Loader | GRUB/Shim 引导程序 | MOK 列表、grub.cfg |
| PCR[5] | Boot Loader 配置 | 内核命令行参数 | /etc/default/grub |
| PCR[6] | Resume from S3 | 休眠恢复状态 | S3 恢复固件度量 |
| PCR[7] | Secure Boot 状态 | 安全启动策略 | PK/KEK/db/dbx 数据库 |
| PCR[8-15] | OS 内核空间 | 内核、initramfs、模块 | IMA (Integrity Measurement Architecture) |
# 读取 TPM2 PCR 度量值(Linux 内核暴露的接口)
cat /sys/class/tpm/tpm0/pcr-sha256/0
# 输出: 0000000000000000000000000000000000000000000000000000000000000000
# (如果 BIOS 未被度量,全零;正常启动后应呈现固件哈希)
# 读取完整 PCR 寄存器集
for i in $(seq 0 23); do
echo -n "PCR[$i]: "
cat /sys/class/tpm/tpm0/pcr-sha256/$i 2>/dev/null || echo "N/A"
done
# 使用 tpm2-tools 读取更详细的信息
tpm2_pcrread sha256:0,1,2,3,4,5,6,7
3.3 内核层的 IMA 度量
Linux 的 IMA(Integrity Measurement Architecture)子系统将 TPM2 的度量能力延伸到运行时。它通过在文件被读取或执行时计算哈希值,并扩展到 PCR[10] 中,实现了运行时的持续完整性验证。
# 启用 IMA 的内核命令行参数
ima_policy=tcb ima_hash=sha256 ima_template=ima-ng
# 查看 IMA 的运行时度量日志
cat /sys/kernel/security/ima/ascii_runtime_measurements
# 输出示例:
# 10 5c5ec2e7... ima-ng sha256:7d9e3f1a... /usr/bin/nginx
# 10 3a8f2b1c... ima-ng sha256:a1b2c3d4... /usr/lib/libssl.so.3
# 10 9e7d6a5b... ima-ng sha256:e5f6a7b8... /etc/nginx/nginx.conf
IMA 策略可以配置为多种模式:
# ima_policy 的可用选项:
# tcb - 度量所有运行的文件(最严格)
# appraise - 拒绝不符合预期哈希的文件访问
# dont_log - 关闭度量(仅用于调试)
# 设置 IMA appraisal 规则(基于数字签名验证)
echo "appraise fowner=0 func=FILE_CHECK mask=MAY_READ ima_sig" > \
/sys/kernel/security/ima/policy
3.4 远程证明的工程化实现
以下是一个使用 TPM2 进行远程证明的简化 Python 实现,展示了完整的证明-验证流程:
#!/usr/bin/env python3
"""
基于 TPM2 的远程证明客户端实现
依赖: tpm2-pytss, cryptography
"""
import json
import hashlib
from tpm2_pytss import *
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
from cryptography.exceptions import InvalidSignature
class TPMRemoteAttestor:
def __init__(self, tpm_device="/dev/tpmrm0"):
self.ectx = ESAPI(tpm_device)
self._load_aik()
def _load_aik(self):
"""加载 AIK (Attestation Identity Key)"""
# AIK 是 TPM2 内部的持久化密钥句柄
# 典型值: 0x81010001 (配置在 TPM2 制造时写入)
self.aik_handle = TPM2_RH.FROM_TPM(0x81010001)
def quote(self, nonce: bytes, pcr_selection: list) -> dict:
"""
执行 TPM2_Quote 操作
Args:
nonce: 验证方提供的随机数(通常 32 字节)
pcr_selection: 需要引用的 PCR 索引列表,如 [0,1,2,7]
Returns:
Quote 结果对象,包含 PCR 值、签名和元数据
"""
assert len(nonce) >= 16 and len(nonce) <= 64, \
"Nonce must be 16-64 bytes"
# 选择要引用的 PCR
pcr_sess = TPML_PCSFParse(
pcr_selection=[
TPMS_PcrSelection(
hash=TPM2_ALG.SHA256,
sizeofSelect=3,
pcrSelect=pcr_selection
)
]
)
# 执行 TPM2_Quote
quote, signature = self.ectx.quote(
self.aik_handle,
pcr_sess,
nonce
)
return {
"quote": quote, # 包含 PCR digest
"signature": signature, # AIK 签名
"pcr_selection": pcr_selection,
"nonce": nonce.hex()
}
def get_event_log(self) -> dict:
"""获取 TPM2 Event Log(包含所有历史度量记录)"""
# Event Log 在 /sys/kernel/security/tpm0/binary_bios_measurements
with open("/sys/kernel/security/tpm0/binary_bios_measurements", "rb") as f:
return parse_tpm2_event_log(f.read())
class RemoteVerifier:
"""远程验证服务(Verifier)"""
def __init__(self, trusted_pcr_values: dict, aik_public_key: bytes):
"""
Args:
trusted_pcr_values: 预存的可信 PCR 值数据库
格式: {"firmware_v2.1": {"pcr0": "abc...", "pcr1": "def..."}, ...}
aik_public_key: 经过 EK 证书链验证的 AIK 公钥
"""
self.trusted_pcrs = trusted_pcr_values
self.aik_pubkey = Ed25519PublicKey.from_public_bytes(aik_public_key)
def verify_quote(self, attestation_response: dict) -> bool:
"""验证远程证明响应"""
# 步骤1: 验证签名 (证明 Quote 来自真实 TPM)
quote_data = attestation_response["quote"]
signature = attestation_response["signature"]
try:
self.aik_pubkey.verify(signature, quote_data)
except InvalidSignature:
raise SecurityException("AIK 签名无效:可能来自伪造 TPM")
# 步骤2: 验证 Nonce 新鲜性
expected_nonce = self.get_pending_nonce(attestation_response["device_id"])
if attestation_response["nonce"] != expected_nonce.hex():
raise SecurityException("Nonce 不匹配:可能为重放攻击")
# 步骤3: 验证 PCR 值是否在可信集合中
observed_pcr_digest = extract_pcr_digest(quote_data)
trusted = False
for fw_version, expected_pcrs in self.trusted_pcrs.items():
if pcr_digest_matches(observed_pcr_digest, expected_pcrs):
trusted = True
print(f"[+] PCR 匹配可信固件版本: {fw_version}")
break
if not trusted:
raise SecurityException(
f"PCR 值不在可信集合中,设备可能被篡改。"
f"Observed digest: {observed_pcr_digest.hex()[:32]}..."
)
return True
# ============ 完整使用示例 ============
def perform_attestation():
"""执行完整的远程证明流程"""
# 设备侧
attester = TPMRemoteAttestor("/dev/tpmrm0")
# 接收来自验证方的 nonce(通常通过 TLS 通道)
nonce = os.urandom(32) # 实际从 verifier 获取
# 执行 Quote(引用 PCR 0-7,覆盖完整启动链)
quote_result = attester.quote(
nonce=nonce,
pcr_selection=[0, 1, 2, 3, 4, 5, 6, 7]
)
# 获取 Event Log 作为补充证据
event_log = attester.get_event_log()
# 打包发送到验证方
attestation_packet = {
"device_id": "server-node-prod-42",
"quote": base64.b64encode(quote_result["quote"]).decode(),
"signature": base64.b64encode(quote_result["signature"]).decode(),
"event_log": event_log,
"nonce": quote_result["nonce"],
"timestamp": int(time.time())
}
# 发送到验证服务(通过 TLS/mTLS 通道)
response = requests.post(
"https://verifier.ybb.press/api/attest",
json=attestation_packet,
cert=("/certs/client.crt", "/certs/client.key"),
verify="/certs/ca.crt"
)
if response.json().get("trusted"):
# 获取解封的密钥(用于解密磁盘/数据)
decryption_key = response.json()["unsealed_key"]
return decryption_key
else:
raise SecurityException("远程证明失败,访问被拒绝")
四、DICE + PFR + TPM2 三位一体:工业级工程实现
4.1 三种技术的协同关系
在实际的工业级系统中,DICE、PFR 和 TPM2 并非互相替代,而是各司其职、协同工作:
┌────────────────────────────────────────────────────────────────┐
│ 三位一体信任链架构 │
├────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────────┐ ┌────────────┐ │
│ │ DICE │ │ PFR │ │ TPM2 │ │
│ │ (身份根基)│ │ (完整性保护) │ │ (远程证明) │ │
│ └─────┬────┘ └──────┬───────┘ └──────┬─────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────────┐ ┌────────────┐ │
│ │制造时注入 │ │运行时监控 │ │云端验证 │ │
│ │UDS + 度量 │ │防篡改+回滚 │ │信任决策 │ │
│ └──────────┘ └──────────────┘ └────────────┘ │
│ │
│ 职责: 职责: 职责: │
│ · 设备唯一身份 · SPI Flash 监控 · PCR 度量日志 │
│ · 派生密钥链 · 双镜像自动恢复 · AIK 签名证明 │
│ · 固件绑定身份 · 防版本回滚 · Event Log 审计 │
│ · 早期信任锚点 · 攻击检测告警 · 密封数据解封 │
│ │
└────────────────────────────────────────────────────────────────┘
4.2 典型的服务器启动流程
在一个配备 DICE + PFR + TPM2 的现代服务器中,完整启动链如下:
1. 电源上电
│
▼
2. DICE ROM 执行(Stage 0 - 不可变 ROM)
│ · 从 eFuse 读取 UDS(唯一硬件密钥)
│ · 计算下一阶段固件(BMC Stage1)的 SHA-256
│ · CDI_0 = HMAC-SHA256(UDS, Hash(Stage1))
│ · 通过 SPI 总线发送度量事件到 TPM2 → 扩展 PCR[0]
│ · 锁定 UDS 访问通路
│
▼
3. BMC/PCH 固件启动(Stage 1)
│ · DICE: 用 CDI_0 派生 BMC 固件密钥
│ · PFR: 开始监控主 SPI Flash(此时主 CPU 被 Hold 在 RESET)
│ · PFR: 通过 GPIO 通知 TPM2 初始化
│
▼
4. PFR 验证主 CPU 的 SPI Flash
│ · 计算 BIOS Flash 的 SHA-256
│ · 与 Golden Hash 比对
│ · 若匹配 → 释放主 CPU RESET 信号
│ · 若不匹配 → 从 Recovery 镜像启动,通知 BMC
│
▼
5. UEFI BIOS 启动(Stage 2)
│ · DICE: CDI_1 = HMAC-SHA256(CDI_0, Hash(Stage2))
│ · PCR[0] = Extend(PCR[0], Hash(BIOS Code))
│ · PCR[1] = Extend(PCR[1], Hash(UEFI Configuration))
│ · PCR[7] = Extend(PCR[7], Hash(Secure Boot State))
│
▼
6. UEFI 度量 Option ROM 和 Boot Loader
│ · PCR[2] = Extend(PCR[2], Hash(GPU Option ROM))
│ · PCR[4] = Extend(PCR[4], Hash(shim.efi + GRUB))
│
▼
7. Linux 内核启动
│ · PCR[4] = Extend(PCR[4], Hash(vmlinuz))
│ · PCR[5] = Extend(PCR[5], Hash(kernel cmdline))
│ · PCR[9] = Extend(PCR[9], Hash(IMA policy))
│ · 内核从 TPM2 获取 EK 证书链,验证 AIK 身份
│
▼
8. 运行时 IMA 持续度量
│ · 每个被执行的 ELF/bin → PCR[10] Extend
│ · 每个被读取的配置文件 → PCR[10] Extend
│
▼
9. 远程证明(按需触发)
│ · 设备向验证方请求证明
│ · TPM2_Quote(PCR[0-7,10], Nonce) → AIK 签名
│ · 验证方验证签名 + PCR 值 + Event Log
│ · 决策:允许访问 / 拒绝访问 / 降级服务
│
▼
10. 密封数据(Sealing)
· 将解密密钥绑定到特定 PCR 状态
· 只有 PCR 值匹配时 TPM2 才解封
· 典型应用:LUKS 磁盘加密、Kubernetes Secret 解封
4.3 Linux 内核中的 TPM2 驱动栈
Linux 内核提供了完整的 TPM2 驱动栈,从硬件接口到用户态 API 一应俱全:
用户态 API:
/dev/tpm0 (字符设备,旧接口)
/dev/trmrm0 (Resource Manager,推荐)
/sys/class/tpm/ (sysfs 接口)
内核态驱动层:
┌─────────────────────────────┐
│ tpm_crb.c (ACPI CRB 接口)│
│ tpm_tis.c (TIS 1.3 接口) │
│ tpm_i2c_atmel│
│ tpm_spi_st33 │
│ tpm_ftpm_tee │
└──────────┬──────────────────┘
│
┌──────────▼──────────────────┐
│ tpm-chip.c (核心层) │
│ · 命令发送 / 接收 │
│ · 时钟超时管理 │
│ · TPM 2.0 协议封装 │
└──────────┬──────────────────┘
│
┌──────────▼──────────────────┐
│ tpm2-space.c / tpm2-cmd.c │
│ · NV 索引读写 │
│ · 密钥加载 / 操作 │
│ · PCR Extend / Quote │
│ · HMAC / Policy 会话 │
└──────────┬──────────────────┘
│
┌──────────▼──────────────────┐
│ keyring / trusted keys │
│ · "trusted" key type │
│ · TPM 密封的数据加密密钥 │
└─────────────────────────────┘
4.4 密封数据的实战:TPM2 + LUKS 磁盘加密
将 TPM2 用于磁盘加密(LUKS + Clevis + Tang/Nanos),可以在不存储明文密码的前提下实现自动解密:
# 安装工具
apt install clevis clevis-tpm2 clevis-luks tang
# 配置 Tang 服务器(用于网络绑定密封)
# 客户端首次绑定:
clevis luks bind -d /dev/sda2 tpm2 '{"pcr_bank":"sha256","pcr_ids":"7"}'
# 验证绑定
clevis luks list -d /dev/sda2
# 输出: 1: tpm2 '{"pcr_bank":"sha256","pcr_ids":"7"}'
# 添加到 crypttab(开机自动解密)
echo "sda2_crypt UUID=xxxx none luks,clevis" >> /etc/crypttab
这种配置的精妙之处在于:如果攻击者将硬盘拆到另一台机器(PCR[7] 不同),TPM2 将拒绝解密,数据仍然安全。
五、攻击面与防御深度
5.1 现实中的硬件攻击向量
任何安全启动链都不是绝对安全的。以下是一些实际可行的攻击向量及其对应防御:
| 攻击类型 | 攻击手段 | 对应防御机制 |
|---|---|---|
| 固件降级攻击 | 刷回有漏洞的旧版 BIOS | PFR 单调计数器 + TPM2 NV 锁定 |
| Glitch 攻击 | 电压/时钟毛刺跳过安全检查 | DICE + 硬件毛刺检测电路 |
| SPI 嗅探 | 逻辑总线分析仪读取固件 | PFR 实时监控 + SPI 加密 |
| 侧信道分析 | 功耗/电磁辐射恢复密钥 | TPM2 芯片内置抗侧信道防护 |
| 冷启动攻击 | 冷冻内存读取残留密钥 | DICE 密钥不驻留内存(仅在 TPM 内) |
| 恶意 BMC 固件 | 带外管理后门 | PFR 硬件隔离 + BMC 远程证明 |
| 供应链攻击 | 制造阶段植入后门 | DICE UDS 多方生成 + 审计日志 |
5.2 TPM2 侧信道防护的进阶工程
对于高安全级别的场景(金融、政务),甚至需要考虑 TPM2 芯片本身的防护:
/* TPM2 命令的侧信道恒定性验证 */
/* 确保 cryptography 操作的时间与密钥无关 */
/* 常量时间比较 — 防止 timing attack 猜测 PCR 值 */
int secure_compare(const uint8_t *a, const uint8_t *b, size_t len) {
volatile uint8_t result = 0;
for (size_t i = 0; i < len; i++) {
result |= a[i] ^ b[i];
}
return (result == 0) ? 0 : -1;
}
/* TPM2 NV 索引设置 - 配置写保护策略 */
typedef struct {
uint32_t attributes;
uint16_t data_size;
uint8_t auth_policy[32]; /* 绑定到特定 PCR 状态 */
} tpm2_nv_config_t;
/* 配置一个"一旦写入,永远锁定"的 NV 索引(类似 eFuse */
tpm2_nv_config_t write_once_config = {
.attributes = TPMA_NV_PPWRITE /* 仅 Platform 级可写 */
| TPMA_NV_WRITEALL /* 必须写入全部数据 */
| TPMA_NV_WRITEDEFINE /* 定义后锁定 */
| TPMA_NV_POLICYWRITE,
.data_size = 32,
/* 策略:必须 PCR[7] = Secure Boot Enabled 状态才可写 */
.auth_policy = { /* PolicyOR( PCR7=EfiSecureBootModeenabled, ... ) */ }
};
六、总结与工程建议
6.1 技术选型指南
安全启动链的设计没有 silver bullet,需要根据实际场景选择合适的技术组合:
6.2 关键工程原则
在实现安全启动链时,请始终牢记以下原则:
6.3 展望未来
随着机密计算(Confidential Computing)的兴起,安全启动链正在向 TEE 延伸:从 CPU TCB 为锚点开始度量,逐步扩展到 GPU TEE、DPU/智能网卡的 HRoT。NVIDIA Hopper 架构的 Confidential Computing、Intel TDX 的 TEE 可信启动、AMD SEV-SNP 的 Secure Nested Paging,都正在将 DICE 的理念延伸到加速器层面。
在不远的未来,一台服务器的信任链可能从 CPU DICE 起始,经由 BMC PFR 验证,再延伸到 GPU 安全启动、NVMe 控制器的 TCG Opal 自加密,最终由服务端的统一证明平台(如 Keylime、VUnits)做全局的可信状态评估。
本文基于 TCG DICE Architecture v1.1、TCM 2.0 Library Specification Rev 1.58、NIST SP 1800-34D 和 Linux 内核 v6.12 源码撰写。所有代码示例为教学用途的简化实现,生产环境需使用经过安全审计的库(如 tpm2-tss、wolfTPM)。

发表评论 取消回复