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"
注意: 每次重启模拟器,TPM内部状态都会重置。如果你在测试PCR扩展、密钥持久化等场景,记得先保存状态文件。

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和上层应用之间的关系。我画图时习惯把通信路径标清楚,这样排查问题能快速定位。

TPM模拟器架构与通信流程 上层应用(tpm2-tools / 固件代码) tpm2-tss 软件栈 TCTI 通信层(socket/unixio) swtpm 模拟器进程 持久化存储(本地文件) 说明:TCTI层通过Unix Socket与swtpm通信,swtpm将TPM状态持久化到本地文件

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模拟器环境应该能正常工作了。接下来就可以基于这个环境,开始做固件度量相关的开发和测试了。