下面是一份在 OpenSSL 3.0.11 环境下,针对已有 CMS 签名(CAdES-BES Detached)进行 RFC 3161 时间戳签名的完整操作教程。step.cms:基础 Detached CMS(含 SignedAttributes)
uboot.bin:原始数据signer.pem:固件签名者证书与私钥(PEM 格式,二合一或分离)tsa.cnf:本地 TSA 配置(包含[tsa]和[tsa_configX]区段)ts_signer.pem:TSA 证书与私钥(PEM 格式)
一、生成基础签名(步骤①–⑥)
如果你尚未生成 step.cms,请先执行以下命令:
openssl cms -sign \
-binary \ # 处理二进制数据,无文本封装
-cades \ # 自动添加 CAdES-BES 所需的 signingCertificateV2
-nosmimecap \ # 禁止添加 S/MIME capability 扩展
-signer signer.pem \ # 签名者证书(可与私钥合并)
-inkey signer.pem \ # 对应私钥
-in uboot.bin \ # 原始数据文件
-md sha256 \ # 指定摘要算法为 SHA-256
-out step.cms \ # 输出 CMS 文件
-outform DER # 输出 DER 格式(非 PEM)
- DETACHED:CMS 不包含原文,只存签名和属性,适用于大文件或流式签名;
- CAdES-BES:
-cades会自动添加id_smime_aa_signingCertificateV2,以绑定签名证书; - 这一步完成后,
step.cms已含有以下 SignedAttributes:contentType:标记被签名的内容类型(pkcs7-data)signingTime:签名时间戳messageDigest:原文摘要signingCertificateV2:签名证书标识(ESSCertIDv2)
二、提取签名值(步骤⑦ 前置)
RFC 3161 要对签名值本身(即 SignerInfo.signature),而非原文,再做一次哈希并请求时间戳。
-
定位签名值偏移:
openssl asn1parse -inform DER -in step.cms > asn1.txt grep -n "prim:.*BIT STRING" asn1.txt- 这会列出所有
BIT STRING项。 - 通常会看到两处:一处是证书自身签名,另一处长度 ~513 字节,即
SignerInfo.signature。 - 记下该行前的偏移值(如
1062)。
- 这会列出所有
-
抠出签名值:
openssl asn1parse -inform DER -in step.cms \ -strparse 1062 -out sig.bin -noout-strparse <offset>:从指定偏移提取 ASN.1 对象原始二进制。sig.bin即纯粹的签名 OCTET STRING。
-
确认签名值正确:
openssl dgst -sha256 sig.bin # 输出示例: SHA2-256(sig.bin)= 33045f0076db62...- 也可与后续时间戳校验对照用。
三、生成时间戳请求 TSQ(步骤⑦)
openssl ts -query \
-config tsa.cnf \ # 加载本地 TSA 配置([tsa] 段)
-data sig.bin \ # 待时间戳的二进制数据(签名值)
-sha256 \ # 摘要算法
-cert \ # 请求返回 TSA 证书链
-tspolicy 1.2.3.4.1 \ # 指定策略 OID,对应 tsa.cnf 中 default_policy
-out sig.tsq # 输出时间戳请求
- TSQ(
.tsq)是符合 RFC 3161 格式的时间戳请求。
四、 TSA 签发 TSR(步骤⑧)
本地TSA.cnf签发TSR
openssl ts -reply \
-config tsa.cnf \ # 同样加载 TSA 配置
-inkey ts_signer.pem \ # TSA 私钥
-signer ts_signer.pem \ # TSA 证书
-queryfile sig.tsq \ # 上一步生成的请求
-out sig.tsr # 输出时间戳响应
若出现
Warning: could not open file ./serial...,可执行:echo 01 > serial
signserver TSA模块签发TSR
curl -k -H "Content-Type: application/timestamp-query" --data-binary "@sig.tsq" "https://192.168.13.118/signserver/process?workerId=3" -o sig.tsr
可选验证:
openssl ts -verify \
-in sig.tsr \ # 时间戳响应
-data sig.bin \ # 原始签名值
-sha256 \ # 摘要算法
-CAfile rootca.pem # TSA 根证书
&& echo "TimeStampToken 验证通过"
五、将 TimeStampToken 注入 CMS(步骤⑨)
方法 :C 程序(调用 OpenSSL API,详见下方)
如下是注入CMS的C代码
timestamp_inject.txt (3.5 KB)
gcc -std=c11 timestamp_inject.c -o timestamp_inject \
$(pkg-config --cflags --libs openssl)
./timestamp_inject step.cms sig.tsr uboot.bin.cms
六、验证最终结果
-
主体签名验证(Detached):
openssl cms -verify -binary -inform DER -in uboot.bin.cms -content uboot.bin -certfile signer.pem -CAfile rootca.pem -purpose any -no_attr_verify -out /dev/null && echo "主体签名 OK"
总结
本文档对①-⑨全流程做了深入解释:
- 基础 CAdES-BES Detached 签名
- 提取签名值
- 构建时间戳请求
- 本地 TSA 签发响应
- 将 TimeStampToken 注入 unsignedAttrs
- 验证签名与时间戳