openssl 替代hpm_signer签名教程

下面是一份在 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),而非原文,再做一次哈希并请求时间戳。

  1. 定位签名值偏移

    openssl asn1parse -inform DER -in step.cms > asn1.txt
    grep -n "prim:.*BIT STRING" asn1.txt
    
    • 这会列出所有 BIT STRING 项。
    • 通常会看到两处:一处是证书自身签名,另一处长度 ~513 字节,即 SignerInfo.signature
    • 记下该行前的偏移值(如 1062)。
  2. 抠出签名值

    openssl asn1parse -inform DER -in step.cms \
      -strparse 1062 -out sig.bin -noout
    
    • -strparse <offset>:从指定偏移提取 ASN.1 对象原始二进制。
    • sig.bin 即纯粹的签名 OCTET STRING。
  3. 确认签名值正确

    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

六、验证最终结果

  1. 主体签名验证(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"
    

总结

本文档对①-⑨全流程做了深入解释:

  1. 基础 CAdES-BES Detached 签名
  2. 提取签名值
  3. 构建时间戳请求
  4. 本地 TSA 签发响应
  5. 将 TimeStampToken 注入 unsignedAttrs
  6. 验证签名与时间戳
5 个赞