2. EST 协议交互流程:CSR 属性请求、证书续期、全量证书链分发

EST 协议,全称是 Enrollment over Secure Transport。说白了,它就是在 TLS 安全通道上跑的一套证书申请流程。我最早接触 EST 是在一个物联网网关项目里,当时设备要批量入网,手动导入证书简直要命。EST 的出现,算是把「自动证书申请」这件事标准化了。

这一章,我们重点拆解 EST 的三个核心交互:CSR 属性请求、证书续期、全量证书链分发。这三个动作,基本覆盖了设备从入网到证书生命期管理的全部场景。

2.1 整体交互流程概览

先看一张总图,把 EST 的完整握手流程串起来。你想想看,一个设备第一次入网,它需要先知道 CA 要求什么属性,然后才能生成正确的 CSR,最后拿到证书链。

核心流程:设备 → EST 服务器 → CA。所有通信都跑在 HTTPS 上,证书验证是双向的。

EST 协议核心交互流程图 EST 协议核心交互流程 EST 客户端(设备) EST 服务器(RA) CA 后端 ① GET /cacerts ② GET /csrattrs ③ POST /simpleenroll ④ POST /simplereenroll 关键说明: • /cacerts 返回全量 CA 证书链 • /csrattrs 返回 CA 要求的 CSR 属性模板 • /simpleenroll 提交 CSR,返回新证书 续期流程: • /simplereenroll 使用旧证书认证,提交新 CSR • 服务器验证旧证书有效后,签发新证书

2.2 CSR 属性请求:设备怎么知道 CA 要什么?

设备第一次入网,它不知道 CA 期望的证书属性是什么。比如,CA 要求 Subject 里必须包含国家代码,或者需要特定的扩展密钥用法。这时候,设备就需要先问一下。

EST 定义了一个专门的端点:/.well-known/est/csrattrs。设备通过 GET 请求这个端点,服务器会返回一个 CSR Attributes 对象,里面包含了 CA 要求的属性模板。

我的经验: 我在一个金融项目里遇到过,CA 要求 CSR 里必须带一个自定义的 OID 扩展,用来标识设备所属的业务线。如果不先请求 csrattrs,生成的 CSR 会被 CA 直接拒绝。这个坑,踩过一次就记住了。

2.2.1 请求与响应格式

请求很简单,就是一个 GET,带上客户端证书做 TLS 双向认证。

GET /.well-known/est/csrattrs HTTP/1.1
Host: est.example.com
Content-Type: application/pkcs10

服务器返回的响应体是一个 CSR Attributes 结构,用 DER 编码的 PKCS#10 格式封装。我拆开来看,里面主要包含两部分:

  • 属性集合(Attributes):CA 要求必须包含的属性,比如 challengePassword、extensionRequest。
  • 扩展集合(Extensions):CA 期望的 X.509 v3 扩展,比如 keyUsage、extKeyUsage、subjectAltName 模板。

举个例子,服务器可能返回这样的属性模板:

Attribute: challengePassword (OID: 1.2.840.113549.1.9.7)
  Value: "optional"  // 可选,但建议提供

Attribute: extensionRequest (OID: 1.2.840.113549.1.9.14)
  Extensions:
    - keyUsage: digitalSignature, keyEncipherment
    - extKeyUsage: serverAuth, clientAuth
    - subjectAltName: dnsName template: *.example.com

注意: 如果设备不请求 csrattrs 直接提交 CSR,CA 可能会拒绝。但有些 CA 实现比较宽松,会使用默认属性。我个人建议:永远先请求 csrattrs,这是最稳妥的做法。

2.3 证书续期:旧证书快过期了怎么办?

证书有生命周期,一般一年或两年。设备上的证书快过期时,需要续期。EST 的续期流程和首次申请很像,但有一个关键区别:续期时,设备用旧证书做认证。

续期端点:/.well-known/est/simplereenroll。设备 POST 一个新的 CSR,但 TLS 握手时用的是即将过期的旧证书。服务器验证旧证书有效后,签发新证书。

2.3.1 续期 vs 首次申请

对比项 首次申请(/simpleenroll) 续期(/simplereenroll)
认证方式 使用设备初始身份证书(如 IDevID) 使用即将过期的旧证书
CSR 要求 必须包含 csrattrs 要求的属性 可以沿用旧证书的属性,或提交新属性
服务器验证 验证初始身份证书是否受信任 验证旧证书是否有效、未吊销
返回证书 新签发的身份证书 新签发的身份证书(有效期重置)
适用场景 设备首次入网 证书到期前续期

这里有个细节:续期时,设备可以提交和旧证书完全相同的 CSR,也可以提交新的属性。我个人习惯是:如果 CA 策略没变,直接复用旧 CSR,省事。

续期流程示例:

  1. 设备用旧证书建立 TLS 连接。
  2. POST 新 CSR 到 /simplereenroll。
  3. 服务器验证旧证书:检查有效期、吊销状态、签名。
  4. 服务器签发新证书,返回 PKCS#7 包。
  5. 设备安装新证书,旧证书可以删除或保留到过期。

2.4 全量证书链分发:设备需要完整的信任链

设备拿到自己的身份证书后,还需要 CA 证书链来验证其他对端。比如,两个设备互相认证时,需要知道对方的证书是否由同一个 CA 签发。

EST 的 /.well-known/est/cacerts 端点就是干这个的。设备 GET 这个端点,服务器返回一个 PKCS#7 包,里面包含完整的 CA 证书链,从根 CA 到中间 CA。

2.4.1 证书链的结构

一个典型的证书链长这样:

Root CA (自签名)
  └── Intermediate CA 1 (由 Root CA 签发)
       └── Intermediate CA 2 (由 Intermediate CA 1 签发)
            └── 设备证书 (由 Intermediate CA 2 签发)

设备通过 /cacerts 拿到的,是 Root CA + Intermediate CA 1 + Intermediate CA 2 的完整列表。这样设备就能构建出完整的信任路径。

避坑指南: 我曾经在一个项目里发现,设备只缓存了中间 CA,没缓存根 CA。结果根 CA 更新时,设备无法验证新的中间 CA。所以,一定要全量缓存 cacerts 返回的所有证书,不要自作聪明只存一部分。

2.4.2 请求与响应示例

GET /.well-known/est/cacerts HTTP/1.1
Host: est.example.com

HTTP/1.1 200 OK
Content-Type: application/pkcs7-mime; smime-type=certs-only
Content-Transfer-Encoding: base64

MIIFgQYJKoZIhvcNAQcCoIIFcjCCBW4CAQExADALBgkqhkiG9w0BBwGgggU6
... (base64 编码的 PKCS#7 包) ...

设备收到后,需要解码 PKCS#7,提取出所有证书,然后安装到本地信任存储中。

2.5 三个端点的对比总结

端点 HTTP 方法 输入 输出 用途
/cacerts GET 无 PKCS#7 证书链 获取全量 CA 证书链
/csrattrs GET 无 CSR 属性模板 获取 CA 要求的 CSR 属性
/simpleenroll POST PKCS#10 CSR PKCS#7 新证书 首次证书申请
/simplereenroll POST PKCS#10 CSR PKCS#7 新证书 证书续期

2.6 实战中的注意事项

  • TLS 双向认证是前提:EST 的所有交互都跑在 HTTPS 上,客户端和服务器都需要证书。没有双向认证,EST 的安全性就大打折扣。
  • CSR 属性请求不是必须的:有些 CA 实现允许设备跳过 csrattrs,直接提交 CSR。但我不建议这么做。万一 CA 升级了策略,你的设备就全挂了。
  • 续期时注意旧证书的吊销状态:如果旧证书被吊销了,续期会失败。设备需要走首次申请流程,或者管理员手动介入。
  • 证书链的缓存策略:cacerts 返回的证书链可以缓存,但要注意有效期。CA 证书更新时,设备需要重新获取。我一般建议:每次续期时都重新拉一次 cacerts,确保信任链是最新的。

重要提醒: 不要在生产环境中直接使用自签名 CA 证书做 EST 测试。我曾经见过一个团队,测试环境用自签名 CA,上线时忘了换,结果所有设备都无法验证生产 CA 的证书链。血的教训。