3. TPM模拟器搭建:安装swtpm、配置tpm2-tss库、验证TPM功能
TPM模拟器,说白了就是给你的开发环境装一个“虚拟TPM芯片”。我刚开始做固件安全时,手头没有真硬件,全靠模拟器撑场子。swtpm是目前最成熟的软件TPM实现,配合tpm2-tss库,能模拟出完整的TPM 2.0功能。
3.1 为什么需要TPM模拟器?
你想想看,做固件度量开发,每次都要在真机上烧录、重启、看日志……效率太低了。模拟器能让你在普通Linux桌面或服务器上,直接跑TPM相关的代码。我在项目中遇到过好几次,真机TPM被BIOS锁死或者固件版本不兼容,最后全靠模拟器完成了功能验证。
| 对比项 | 物理TPM | swtpm模拟器 |
|---|---|---|
| 硬件依赖 | 需要主板支持 | 任意x86/ARM机器 |
| 调试灵活性 | 低,重启慢 | 高,可随时重置状态 |
| 持久化存储 | NVRAM物理芯片 | 本地文件模拟 |
| 多实例支持 | 单芯片 | 可同时启动多个 |
3.2 安装swtpm
我建议直接用源码编译安装,这样版本可控。Ubuntu 20.04/22.04都测试过,流程一致。
3.2.1 安装依赖
sudo apt update
sudo apt install -y \
build-essential \
autoconf \
automake \
libtool \
pkg-config \
libssl-dev \
libjson-c-dev \
libfuse-dev \
libglib2.0-dev \
libgmp-dev \
libtasn1-dev \
expect \
socat \
net-tools
嗯,这里要注意:libtasn1-dev 和 libjson-c-dev 是很多人容易漏掉的。我曾经因为缺了 libtasn1-dev,编译到一半报错,排查了半小时才发现。
3.2.2 编译swtpm
git clone https://github.com/stefanberger/swtpm.git
cd swtpm
./autogen.sh
./configure --prefix=/usr \
--with-openssl \
--with-tpm2 \
--with-gnutls
make -j$(nproc)
sudo make install
编译参数里 --with-tpm2 是必须的,默认只编译TPM 1.2。我习惯加上 --prefix=/usr,这样二进制文件直接进系统路径,不用额外配置PATH。
3.2.3 验证安装
swtpm --version
# 输出示例:swtpm 0.8.0
如果能看到版本号,说明swtpm装好了。我个人习惯再跑一个快速启动测试:
mkdir /tmp/myvtpm
swtpm socket \
--tpm2 \
--tpmstate dir=/tmp/myvtpm \
--ctrl type=unixio,path=/tmp/myvtpm/swtpm-sock \
--log level=20 &
sleep 2
ls -la /tmp/myvtpm/
# 应该能看到 swtpm-sock 和 TPM2-*.permall 文件
--tpmstate dir 路径就行。我经常同时开三四个模拟器做不同测试。
3.3 配置tpm2-tss库
tpm2-tss是TPM 2.0的软件栈,负责上层应用和模拟器之间的通信。说白了,你的C程序、Python脚本都是通过它来调用TPM功能的。
3.3.1 编译安装tpm2-tss
git clone https://github.com/tpm2-software/tpm2-tss.git
cd tpm2-tss
./bootstrap
./configure --prefix=/usr \
--with-udevrulesdir=/etc/udev/rules.d \
--with-udevrulesprefix=70-
make -j$(nproc)
sudo make install
sudo ldconfig
这里有个坑:sudo ldconfig 一定要跑。我遇到过好几次,库装好了但程序链接不上,就是因为没更新动态链接器缓存。
3.3.2 配置udev规则(可选)
如果你用的是物理TPM设备,需要让普通用户也能访问:
sudo udevadm control --reload-rules
sudo udevadm trigger
模拟器场景下这一步可以跳过,但建议先配好,方便以后切换真机。
3.4 启动TPM模拟器并建立通信
swtpm和tpm2-tss之间通过socket通信。我习惯用这种启动方式:
# 启动模拟器(后台运行)
swtpm socket \
--tpm2 \
--tpmstate dir=/tmp/tpm_workspace \
--ctrl type=unixio,path=/tmp/tpm_workspace/swtpm-sock \
--log file=/tmp/tpm_workspace/swtpm.log,level=20 &
# 设置环境变量,告诉tpm2-tss库连接哪个socket
export TPM2TOOLS_TCTI="swtpm:path=/tmp/tpm_workspace/swtpm-sock"
3.5 验证TPM功能
装好之后,得跑几个命令确认TPM真的能干活。我一般按这个顺序测:
3.5.1 获取TPM属性
tpm2_getcap properties-fixed
# 关键输出项:
# TPM2_PT_MANUFACTURER: 0x.... (模拟器厂商ID)
# TPM2_PT_FAMILY_INDICATOR: "2.0"
# TPM2_PT_LEVEL: 0
看到 TPM2_PT_FAMILY_INDICATOR: "2.0" 就对了。如果报错 connection refused,检查一下swtpm进程是否活着,socket路径是否匹配。
3.5.2 测试PCR操作
PCR是固件度量的核心,咱们先测读写:
# 读取PCR 0的当前值
tpm2_pcrread sha256:0
# 扩展PCR 0(模拟度量事件)
echo "my_measurement_data" > /tmp/measure.bin
tpm2_pcrextend 0:sha256=/tmp/measure.bin
# 再次读取,看值是否变化
tpm2_pcrread sha256:0
两次读取的值应该不一样。如果一样,说明扩展没生效。我调试时经常用这个来验证度量链是否打通。
3.5.3 测试密钥生成
# 创建主密钥(SRK)
tpm2_createprimary -c primary.ctx
# 用主密钥派生一个签名密钥
tpm2_create -C primary.ctx -u key.pub -r key.priv
# 加载密钥到TPM
tpm2_load -C primary.ctx -u key.pub -r key.priv -c key.ctx
能走到最后一步 tpm2_load 不报错,说明整个TPM栈是通的。我习惯把这几个命令写成一个脚本,每次搭建新环境时一键验证。
3.6 核心架构图
下面这张图展示了swtpm、tpm2-tss和上层应用之间的关系。我画图时习惯把通信路径标清楚,这样排查问题能快速定位。
3.7 常见问题与避坑
- swtpm启动报错 "Could not create socket":检查
/tmp/myvtpm目录是否存在,权限是否够。我习惯用mkdir -p先建好。 - tpm2_getcap 返回 "connection refused":环境变量
TPM2TOOLS_TCTI没设对,或者swtpm进程挂了。用ps aux | grep swtpm确认。 - PCR扩展后值不变:确认扩展时用的是
sha256:前缀,且输入文件非空。我曾经传了一个空文件,折腾半天才发现。 - 编译tpm2-tss时报错 "cannot find -lssl":缺
libssl-dev,装一下就好。
核心要点: 模拟器环境搭建的关键是“三层两通”——swtpm层、tpm2-tss库层、应用层,以及它们之间的socket通信和持久化存储。任何一层不通,上层功能都跑不起来。
好了,到这一步,你的TPM模拟器环境应该能正常工作了。接下来就可以基于这个环境,开始做固件度量相关的开发和测试了。