2. 公钥基础设施与证书体系

代码签名不是“把私钥和文件一起打包”这么简单。它要回答一个更根本的问题:验证方凭什么相信这把签名私钥属于某个真实主体,并且它有权代表该主体签署代码?这个问题的答案由公钥基础设施(PKI)提供。PKI 是一套由证书、密钥、签发机构、策略与验证规则共同组成的信任体系,而 X.509 证书是这套体系里承载“公钥—身份—权限”绑定的标准载体。

PKI 的核心组件与职责分工

企业代码签名场景中,PKI 至少包含以下角色:

  • CA(证书颁发机构):负责签发、吊销证书,并对下级 CA 或终端实体进行身份核验。企业代码签名通常不会直接使用公共根 CA 签发开发者证书,而是由企业自建 CA 或采购受管 PKI 服务。
  • RA(注册机构):承担身份审核与证书申请受理,可由 CA 兼任,也可独立部署。它决定“谁有资格拿到签名证书”。
  • 终端实体证书:即代码签名证书,绑定开发者或构建系统的公钥与主体信息,私钥由签名方持有。
  • CRL / OCSP 响应器:提供证书吊销状态查询,验证方据此判断证书是否仍然有效。
  • 信任库:操作系统、浏览器或签名验证工具内置的可信根证书集合,是信任链验证的起点。

这些组件的关系可以概括为:RA 审核身份,CA 签发证书,终端实体用私钥签名,验证方从信任库出发逐级验证证书链,并通过 CRL/OCSP 确认吊销状态。

X.509 证书结构:签名验证真正读取的字段

X.509 v3 证书是一段 DER 编码的 ASN.1 结构,展开后主要包含三部分:tbsCertificate(待签名证书主体)、signatureAlgorithm(签名算法标识)、signatureValue(CA 对 tbsCertificate 的签名值)。其中 tbsCertificate 是验证时真正被逐字段检查的部分,关键字段包括:

字段含义代码签名中的关注点
version证书版本,通常为 v3v3 才支持扩展项,代码签名证书必须为 v3
serialNumberCA 分配的唯一序列号吊销查询与审计追踪的索引
signatureCA 使用的签名算法如 sha256WithRSAEncryption、ecdsa-with-SHA256
issuer签发者 DN必须与上级证书 subject 匹配,构成链
validitynotBefore / notAfter签名时间戳必须落在有效期内
subject证书持有者 DN企业名称、组织单位、CN 等
subjectPublicKeyInfo公钥及算法验证签名时使用
extendedKeyUsage扩展密钥用法代码签名必须包含 codeSigning(1.3.6.1.5.5.7.3.3)
keyUsage密钥用途位通常要求 digitalSignature
basicConstraints是否为 CA 及路径长度终端证书 CA=FALSE,中间 CA CA=TRUE
authorityInfoAccessOCSP/CA Issuers 地址验证方获取吊销状态与上级证书
crlDistributionPointsCRL 分发地址离线吊销检查路径

一个常见的排障点:证书看起来“没过期”,但签名仍被拒绝,原因往往是 extendedKeyUsage 缺少 codeSigning,或 keyUsage 未置位 digitalSignature。验证工具不会因为 DN 看起来正确就放行。

信任链:从终端证书到根证书的逐级验证

信任链(certification path)是一条由终端证书出发、经中间 CA、最终到达信任库中根证书的证书序列。验证方执行的核心动作是:用上级证书的公钥验证下级证书的签名,逐级向上,直到命中本地信任库中的根证书。任何一级签名不匹配、有效期越界、basicConstraints 不允许签发、或吊销状态异常,整条链即判定失败。

企业代码签名信任链与验证路径 根证书 Root CA 自签名 · 离线保管 · 预置信任库 中间证书 Issuing CA CA=TRUE · pathLen=0 · 在线签发 代码签名证书 CA=FALSE · EKU=codeSigning 已签名代码 / 安装包 含签名值 + 证书链 + 时间戳 签发 签发 签名 验证方 1. 读取信任库根证书 2. 验证中间证书签名 3. 验证终端证书签名 4. 检查 EKU / KU / 有效期 5. 查询 CRL / OCSP 6. 验证代码签名值 吊销状态服务 CRL 分发点 OCSP 响应器 吊销查询 信任锚 根证书预置在系统/工具信任库

图中体现了两个关键工程事实:第一,根证书通常离线保管,不直接签发终端证书,而是签发中间 CA;第二,验证方并不“信任中间证书”,它信任的是根证书,中间证书只是被根证书授权、可被验证的传递环节。因此企业自建 PKI 时,根 CA 的私钥保护级别、中间 CA 的用途隔离、以及信任库分发方式,直接决定整条链的安全边界。

根证书与中间证书在企业代码签名中的角色

根证书是信任锚。它的公钥被预置在 Windows 证书存储、Java cacerts、签名验证工具或企业终端管理策略中。根证书一旦泄露或需要更换,所有依赖它的证书链都会失效,因此根 CA 私钥应离线保存,仅在签发中间 CA 时短暂使用。企业若使用公共 CA 签发的代码签名证书,根证书由公共 CA 管理;若自建 PKI,则必须自行承担根证书分发与生命周期管理。

中间证书承担日常签发职责,可按用途、环境或团队隔离。例如:Corp-CodeSigning-Issuing-CA 只签发代码签名证书,Corp-TLS-Issuing-CA 只签发内部 TLS 证书。这样即使某个中间 CA 私钥泄露,影响范围也被限制在单一用途内,吊销该中间 CA 即可阻断其下所有证书,而不必更换根证书。中间证书的 basicConstraints 应设置 CA=TRUE,并根据层级设置 pathLenConstraint,例如直接签发终端证书的中间 CA 可设 pathLen=0。

企业代码签名中的典型证书链与验证判断

假设某企业自建 PKI,根证书为 CN=Corp Root CA, O=Example Corp, C=CN,中间证书为 CN=Corp CodeSigning Issuing CA, O=Example Corp, C=CN,终端代码签名证书为 CN=Build Signer 01, OU=Release Engineering, O=Example Corp, C=CN。签名时,构建系统使用终端证书私钥对代码摘要签名,并将终端证书与中间证书一并附在签名块中;根证书不附带,由验证方从信任库获取。

验证方执行判断时,可按以下顺序检查:

  1. 用中间证书公钥验证终端证书签名,确认 issuer 与 subject 匹配。
  2. 用根证书公钥验证中间证书签名,确认根证书在信任库中且未被标记为不受信任。
  3. 检查终端证书 notBefore 与 notAfter,并结合可信时间戳判断签名时刻是否在有效期内。
  4. 检查 extendedKeyUsage 是否包含 codeSigning,keyUsage 是否包含 digitalSignature。
  5. 通过 authorityInfoAccess 中的 OCSP 地址或 crlDistributionPoints 查询吊销状态。
  6. 用终端证书公钥验证代码签名值,确认代码未被篡改。
企业内部分发中间证书时,建议将中间证书与终端证书一起打包进签名块,避免验证方因缺少中间证书而无法构建完整链。根证书则通过终端管理策略统一预置,不应随安装包分发。
常见错误:把根证书私钥放在构建服务器上直接签发代码签名证书。这会让根 CA 暴露在持续在线的环境中,一旦构建机被入侵,整条信任链的信任锚即被攻破。正确做法是根 CA 离线,中间 CA 在线但权限受限,终端签名私钥使用硬件令牌或受管密钥服务保护。