4、镜像签名工具:QTI_SignTool使用、签名脚本编写、多镜像签名策略
说到镜像签名,这可能是安全启动里最容易被忽视、但又最容易出问题的一环。我记得刚接触高通平台那会儿,光签名就折腾了两天——不是证书链搞错,就是签名算法选不对。今天咱们就把这块彻底讲透。
4.1 QTI_SignTool 到底是什么?
QTI_SignTool 是高通官方提供的签名工具。说白了,它就是个命令行工具,负责给你的 bootloader、tz、hyp 等镜像打上签名。没有这个签名,芯片在启动时就会拒绝加载。
你可能会问:为什么不能直接用 OpenSSL 签名?嗯,这里有个关键点——高通有自己的签名格式和证书结构。QTI_SignTool 封装了这些细节,你只需要提供正确的参数就行。
核心功能:
- 支持 RSA 和 ECDSA 签名算法
- 自动生成签名块(Signature Block)
- 支持多级证书链
- 可配置签名属性(如抗回滚版本号)
4.2 工具安装与环境配置
QTI_SignTool 通常集成在高通发布的 LE(Lightning Edition)或 SA(Secure Application)包中。我个人习惯把它单独拎出来,放到一个固定的工具目录里。
# 解压工具包
unzip qti_signtool_*.zip -d /opt/qti_tools/
# 设置环境变量
export QTI_SIGNTOOL_PATH=/opt/qti_tools/signtool
export PATH=$PATH:$QTI_SIGNTOOL_PATH
# 验证安装
python3 $QTI_SIGNTOOL_PATH/QTI_SignTool.py --version
小提示:建议把环境变量写到 ~/.bashrc 里,避免每次都要手动 export。我在项目里吃过这个亏——有一次编译脚本没加载环境变量,签名失败,排查了半天才发现是路径问题。
4.3 签名脚本编写实战
写签名脚本,其实就是在做三件事:准备证书、准备镜像、调用签名工具。我一般会写一个通用的 shell 脚本,然后根据不同镜像传不同的参数。
先看一个基础的签名脚本框架:
#!/bin/bash
# sign_image.sh - 通用镜像签名脚本
IMAGE_PATH=$1
IMAGE_TYPE=$2
OUTPUT_DIR=$3
# 证书路径配置
CERT_DIR="./certs"
ROOT_CERT="$CERT_DIR/root.cer"
ATTEST_CA_CERT="$CERT_DIR/attest_ca.cer"
ATTEST_CERT="$CERT_DIR/attest.cer"
SIGNING_KEY="$CERT_DIR/attest.key"
# 签名参数配置
case $IMAGE_TYPE in
"xbl")
SW_ID=0x00000001
HW_ID=0x00000001
;;
"tz")
SW_ID=0x00000002
HW_ID=0x00000001
;;
"hyp")
SW_ID=0x00000003
HW_ID=0x00000001
;;
*)
echo "Unknown image type: $IMAGE_TYPE"
exit 1
;;
esac
# 执行签名
python3 $QTI_SIGNTOOL_PATH/QTI_SignTool.py sign \
--image $IMAGE_PATH \
--cert $ATTEST_CERT \
--key $SIGNING_KEY \
--root_cert $ROOT_CERT \
--attest_ca_cert $ATTEST_CA_CERT \
--sw_id $SW_ID \
--hw_id $HW_ID \
--out $OUTPUT_DIR/${IMAGE_TYPE}_signed.bin
echo "Signing completed for $IMAGE_TYPE"
这个脚本看起来简单,但我在实际项目中踩过不少坑。比如 SW_ID 和 HW_ID 必须和芯片熔丝配置一致,否则签名验证会失败。我曾经因为 HW_ID 写错了一个 bit,导致整批设备无法启动——那叫一个惨。
4.4 多镜像签名策略
高通平台启动涉及多个镜像:xbl、abl、tz、hyp、devcfg、cmnlib 等等。每个镜像的签名策略其实有细微差别。
| 镜像名称 | 签名算法 | 证书链要求 | 特殊说明 |
|---|---|---|---|
| xbl | RSA 4K | 3级证书链 | 必须包含抗回滚版本号 |
| abl | ECDSA P-384 | 2级证书链 | 可省略根证书 |
| tz | RSA 4K | 3级证书链 | 需要 ELF 格式支持 |
| hyp | ECDSA P-256 | 2级证书链 | 签名块大小固定 |
你想想看,如果每个镜像都单独写一个签名脚本,那维护起来得多痛苦?我建议的做法是:
- 统一证书管理:所有镜像共用一套根证书和 CA 证书,只有终端证书可以不同
- 参数化签名:把 SW_ID、HW_ID、算法类型等作为参数传入
- 批量签名脚本:写一个主脚本,遍历所有镜像并调用通用签名脚本
下面是我在实际项目中用的批量签名脚本:
#!/bin/bash
# batch_sign.sh - 批量签名所有镜像
IMAGES=("xbl" "abl" "tz" "hyp" "devcfg" "cmnlib")
IMAGE_DIR="./build_images"
OUTPUT_DIR="./signed_images"
mkdir -p $OUTPUT_DIR
for img in "${IMAGES[@]}"; do
echo "Signing $img..."
./sign_image.sh $IMAGE_DIR/${img}.bin $img $OUTPUT_DIR
if [ $? -ne 0 ]; then
echo "Failed to sign $img"
exit 1
fi
done
echo "All images signed successfully"
注意:批量签名时,一定要检查每个镜像的签名结果。我曾经遇到过 tz 镜像签名成功但实际验证失败的情况——原因是 ELF 格式的 section 对齐方式不对。所以签名完成后,最好用 QTI_SignTool 的 verify 命令验证一遍。
4.5 签名验证与调试
签名完成后,验证是必不可少的一步。QTI_SignTool 提供了 verify 命令:
python3 $QTI_SIGNTOOL_PATH/QTI_SignTool.py verify \
--image signed_xbl.bin \
--root_cert root.cer \
--attest_ca_cert attest_ca.cer
如果验证失败,常见原因有:
- 证书链不完整或顺序错误
- 签名算法与证书不匹配
- 镜像在签名后被修改过
- 时间戳或抗回滚版本号冲突
嗯,这里要特别提一下——调试签名问题时,我建议先用手动方式验证证书链,再用工具验证签名。这样可以快速定位是证书问题还是签名算法问题。
4.6 避坑指南
做签名开发这几年,我总结了几条血泪教训:
- 证书有效期:高通要求证书有效期至少覆盖产品生命周期。我曾经用了一个快过期的测试证书,结果量产三个月后设备全部无法启动——因为证书过期了。
- 密钥保护:签名私钥一定要放在 HSM(硬件安全模块)或安全服务器里。千万别把私钥放在代码仓库里,哪怕只是测试环境也不行。
- 版本号管理:抗回滚版本号只能递增,不能递减。一旦写错了,就只能换芯片或者重新熔丝。
- 签名顺序:有些镜像之间有依赖关系,比如 xbl 必须在 abl 之前签名。这个顺序搞反了,启动时就会报签名验证失败。
说实话,签名这块看起来简单,但细节特别多。我建议你在开发阶段就建立一套完整的签名流水线,从证书生成到镜像签名再到验证,全部自动化。这样既能提高效率,又能减少人为失误。
好了,关于 QTI_SignTool 的使用和签名脚本编写,今天就聊到这儿。下一章咱们会深入讲解证书链的生成和管理,这可是安全启动的基石,到时候见。