openUBMC CSR/PSR内层ECC远程签名接入实践(SignServer + bingo)

openUBMC CSR/PSR 内层 EEPROM ECC 远程签名接入实践(SignServer + bingo)

社区里关于 SignServer 的帖子,之前主要集中在 HPM 外层 CMS/TSA 链路。
但在 CSR/PSR 场景中,除了外层 HPM 签名,还存在一个经常被忽略的点:

EEPROM/PSR 内层 .bin 也有自己独立的 ECC 签名链路。

这条链路和外层 HPM/CMS 不是一套机制,也不共用证书链。
本文记录我在 bingo 中接入 EEPROM 内层 ECC 远程签名 的实践方式,以及一些容易踩坑的边界。


1. 先说清楚:外层和内层是两条独立链路

在 CSR/PSR 出包里,通常同时存在两类签名:

  1. 外层 HPM 签名

    • 对 HPM 外层文件清单做 CMS 签名

    • 依赖 rootca.der/rootca.crl

    • 使用 hpm_signer/hpm_verify

  2. 内层 EEPROM ECC 签名

    • 对要写入 EEPROM 的原始二进制内容做 ECC 签名

    • 依赖固定公钥 device_desc_pubkey.bin

    • 设备侧和构建侧都围绕这个固定公钥做验签

这两条链路互不替代:

  • 外层 CMS 成功,不代表内层 ECC 正确

  • 内层 ECC 成功,也不代表外层 HPM/CMS 正确

所以建议把它们拆开理解、拆开配置、拆开排查。


2. 设备侧信任模型

内层 ECC 这条链路最关键的不是证书链,而是 固定公钥

设备侧验签使用的是固定公钥文件:

 /opt/bmc/trust/partner/device_desc_pubkey.bin

这意味着:

  • BMC 环境并不是拿 PEM/CRT 文本证书去验 EEPROM

  • 它信任的是一个固定的原始公钥二进制文件

  • 如果你后续切换了签名私钥,但没有同步更新设备侧公钥,升级就会失败

我的建议是:

  • 为同一产品线/同一环境,固定使用一套 ECC 公私钥

  • 公钥固定放到设备侧信任位置

  • 私钥固定导入 SignServer 的 ECC worker

  • 不要每次出一个 CSR/PSR 包就重新生成一套 ECC 密钥


3. ECC Worker 的要求

和外层 HPM/CMS 不同,EEPROM 内层 ECC 不能直接复用 CMS/TSA worker。

它需要单独的 ECC 签名 worker,满足以下要求:

  • 接口方式:POST

  • Content-Type: application/octet-stream

  • 请求体:待签名原始二进制内容

  • 响应体:原始 ECDSA DER 签名字节

  • 算法:P-256 / SHA-256 / DER 编码

也就是说,这条链路不是 multipart 文件上传,不是 CMS 回包,也不是时间戳服务。

如果你之前做过 demo/ecc_sign/sign_server.py 的 simple server,会发现两者有一个关键区别:

  • simple server:接收 files={'file': ...}

  • SignServer ECC:直接接收原始二进制 body

这一点如果搞错,服务端即使收到了请求,也会因为协议不匹配而无法正确签名。


4. 公钥准备方式

如果你只是先做验证,仓库里已经有一个最小 demo:

 cd bingo/demo/ecc_sign
 sh 1_create_sign_ecc.sh

执行后会生成:

  • private_key.pem

  • device_desc_pubkey.bin

这个 demo 的作用很明确:

  • private_key.pem:构建侧或服务端签名使用

  • device_desc_pubkey.bin:设备侧与构建侧验签使用

当前 bingo 适配里,pubkey_bin 接受以下两种格式:

  • 65 字节未压缩公钥点(首字节 0x04

  • 64 字节 X||Y 原始拼接

如果你不是用 demo,而是从已有 ECC 证书或 P12 中导出公钥,请确保最终落到设备侧/构建侧的是 原始公钥二进制,不是 PEM 文本证书本身。


5. 配置入口

和外层 CSR/HPM 签名一样,当前实现读取的是 .bmcgo/config 体系,而不是 manifest.yml

  1. 当前产品目录 .bmcgo/config

  2. ~/.bmcgo/config

  3. /etc/bmcgo.conf

建议优先在当前项目目录下配置。


6. 内层 ECC 配置示例

.bmcgo/config 中加入:

 [eeprom_signserver]
 url = https://<SIGN_SERVER_DOMAIN>/signserver/process?workerId=<ECC_WORKER_ID>
 pubkey_bin = /path/to/device_desc_pubkey.bin
 curl_cafile = /root/ca/ca.pem
 insecure = false

如果只是先联通调试,也可以先这样:

 [eeprom_signserver]
 url = https://<SIGN_SERVER_DOMAIN>/signserver/process?workerId=<ECC_WORKER_ID>
 pubkey_bin = /path/to/device_desc_pubkey.bin
 insecure = true

参数说明:

  • url:ECC worker 接口地址

  • pubkey_bin:构建侧强制验签使用的固定公钥文件

  • curl_cafile:HTTPS 校验用 CA 文件

  • insecure:跳过 HTTPS 校验

说明:

  • pubkey_bin 是必填项

  • 如果 eeprom_signserver section 存在但缺少 pubkey_bin,构建会直接报错

  • 如果同时配置了 eeprom_signserver 和旧的 eeprom_self_sign / eeprom_server_sign,当前实现会优先走 eeprom_signserver


7. 构建阶段发生了什么

当 bingo 进入内层 EEPROM ECC 签名阶段,流程如下:

  1. 根据 eeprom_signserver 选择 remote_ecc 分支

  2. 先生成待签名的原始 EEPROM 二进制内容

  3. 使用 application/octet-stream 将原始字节 POST 给 SignServer

  4. 服务端返回 DER 编码的 ECDSA 签名字节

  5. bingo 立即使用 pubkey_bin 做一次本地强制验签

  6. 验签通过后,才把签名写回 EEPROM 签名区

这一步的意义很大:

  • 不等到设备侧升级时才暴露问题

  • 构建阶段就能发现“私钥/公钥不匹配”或“服务回包格式错误”


8. 关键日志

构建成功时,通常可以看到类似日志:

 eeprom签名使用 remote_ecc 方法
 >> POST SignServer接口
 开始校验eeprom SignServer签名结果...
 >> verify_eeprom_signserver --check-file /path/device_desc_pubkey.bin --data-bytes ... --sig-bytes ...
 eeprom SignServer签名验签成功

如果同时也配置了外层 HPM 签名,你还会看到:

 hpm签名使用 remote_cms 方法

这表示两条链路都已经接通。


9. 常见问题

9.1 为什么明明外层签名正常,设备升级还是失败

因为外层 CMS 和内层 ECC 是两回事。
如果设备升级阶段报 EEPROM/PSR 校验失败,优先看内层 ECC,而不是只盯着外层 HPM。

9.2 为什么这里要用 pubkey_bin,而不是 rootca.der/rootca.crl

因为内层 ECC 不是 CMS 证书链验签模型。
它是“固定公钥 + 原始签名”的模型,所以这里看的是 device_desc_pubkey.bin,不是 CMS 的根证书和吊销列表。

9.3 为什么不能直接复用 simple sign server

因为协议不一样:

  • simple server 用 multipart 上传文件

  • 当前 SignServer ECC 分支要求原始二进制 body

两者接口不兼容。

9.4 如果公钥和私钥不匹配会怎样

当前实现会在构建阶段直接报错,不会继续回包。
这也是为什么 pubkey_bin 必须和服务端 ECC worker 内的私钥严格对应。

9.5 设备侧到底应该放什么

设备侧放的是固定公钥文件:

 /opt/bmc/trust/partner/device_desc_pubkey.bin

不是 PEM 证书链,不是 rootca.der,也不是 CMS 的 CRL 文件。


10. 推荐使用方式

我更推荐下面这种方式:

  1. 先固定一套 ECC 密钥对

  2. 公钥导出为 device_desc_pubkey.bin

  3. 公钥部署到 BMC 固定信任位置

  4. 私钥导入 SignServer ECC worker

  5. 所有 CSR/PSR 内层签名都复用这套固定密钥

这样做的好处是:

  • 设备侧信任关系稳定

  • 不需要每次出包都重新换公钥

  • 排障路径清晰


11. 结语

CSR/PSR 场景里,外层 HPM/CMS 和内层 EEPROM ECC 经常被混在一起讨论。
但从实现和运维角度看,它们最好拆开理解:

  • 外层看 hpm_signer/hpm_verify/rootca.der/rootca.crl

  • 内层看 device_desc_pubkey.bin + raw body + DER signature

只要把这个边界想清楚,内层 ECC 接入 SignServer 并不复杂。
真正要守住的,是固定公钥信任模型不要漂移。


参考资料

1 个赞