mTLS 证书 EKU 故障分析:从原理到四种故障场景的完整拆解

7 阅读7分钟

一、什么是 mTLS?

从普通 TLS 说起

最早的互联网通信是明文的,HTTP 裸跑。中间人可以窃听、篡改。所以诞生了 SSL(后来演进为 TLS),核心解决一个问题:客户端怎么确认"我连的真的是这个网站,不是个假的"。

每天用的 HTTPS 就是最典型的单向 TLS——浏览器验证银行的证书,确认是真的银行服务器。但银行不验证你是谁,身份是通过登录页面输入账号密码来确认的。

mTLS 解决什么问题

在企业内部、微服务之间、设备通信这些场景里,没有"登录页面",两个服务之间要直接互信。mTLS(mutual TLS,双向 TLS) 就是在标准 TLS 握手的基础上,加了一步:服务端也要求客户端出示证书。

一句话: 普通 TLS 是"我验你",mTLS 是"你我互验"。

典型使用场景

  • 微服务 / 容器间通信 —— K8s 里各组件(kubelet、apiserver、etcd)
  • 零信任架构 —— "不管在哪,每次连接都要验证身份",mTLS 是落地的核心手段
  • 金融 / 支付系统 —— 银行和第三方对接,双方都需要确认对方身份

二、mTLS 握手流程

下图展示了一次完整的 mTLS 握手。橙色部分是 mTLS 比普通 TLS 多出来的步骤——服务端发 CertificateRequest,要求客户端也出示证书。

图 1:mTLS 握手流程——双方互验证书

注意图中左右两侧的验证环节:客户端验证服务端证书时检查 serverAuth ,服务端验证客户端证书时检查 clientAuth

关键: EKU 不是证书持有者自己看的,而是对端在 TLS 握手时检查的。serverAuth 意思是"我被授权当服务端",但检查这件事的人是客户端;clientAuth 同理,检查的人是服务端。

三、理解 keyUsage 和 extendedKeyUsage

证书里有两个容易混淆的扩展字段:

keyUsage 是"底层能力"——这把钥匙在密码学层面能干什么。比如 digitalSignature(能签名)、keyEncipherment(能加密对称密钥)。纯技术性的,不关心业务场景。

extendedKeyUsage(EKU)  是"业务角色"——这张证书在应用场景中扮演什么角色。serverAuth = "我是服务端",clientAuth = "我是客户端"。

打个比方: keyUsage 相当于"这个人会开车、会做饭",EKU 相当于"这个人是出租车司机 / 是厨师"。能力是基础,角色决定你在具体场景里被信任做什么。

还有一个重要规则:如果证书没有 EKU 字段,TLS 库视为"不受限",任何用途都放行。但只要写了 EKU,就会严格检查。

TLS 握手中的两层校验

TLS 握手时对端验证一张证书,至少做两件事:

  1. CA 签名链验证 → "这张证书是不是我信任的 CA 签发的?" → 检查来源
  2. EKU 检查 → "这张证书被授权用在这个场景吗?" → 检查用途

这不是二选一,是同一次握手中的两个环节,都通过才算验证成功。

四、我们的架构与证书分布

我所在的项目是一个采用中心化管控架构的系统,核心架构是主控节点(Central Node)与受控节点(Agent Node)之间通过 mTLS 进行双向认证通信。

图 2:OP 与 Agent 的证书分布每一方持有自己的证书 + 对方 CA

这里有两个关键的架构妥协需要注意:

妥协一:服务端证书身兼两职

服务端证书的 EKU 同时包含了 serverAuthclientAuth,原因是同一张证书在不同通信方向上被复用——当节点 B 主动连接管控端 A 时,该证书作为服务端证书使用;当管控端 A 向节点 B 推送连接时,该证书又被用作客户端证书。标准做法应该是为不同角色签发独立证书。

妥协二:所有受控节点共享一张客户端证书

客户端证书由管控端统一管理并分发给所有受控节点,这意味着在 TLS 层面无法区分"连接来自哪个节点"。同时这张客户端证书在管控端推送到受控节点时,还被受控节点拿来当作服务端证书使用。

为什么要求服务端和客户端使用不同的 CA?

正因为 EKU 同时包含了两种角色,EKU 层面已经无法区分"这是服务端证书还是客户端证书"。为了防止受控节点拿着客户端证书冒充管控端去连接其他节点,架构设计要求服务端和客户端使用不同的 CA 签发——用 CA 分离来补偿 EKU 区分能力的失效。

五、四种 EKU 缺失的故障场景

理解了上面的架构和原理后,我们可以精确推导出:当任意一张证书缺失某个 EKU 字段时,会在哪个方向、哪个环节、以什么方式失败。

场景 1:服务端证书缺 serverAuth

故障方向: 受控节点→主控节点(受控节点主动连接主控节点)。
机制: 主控节点出示服务端证书,受控节点检查EKU发现没有serverAuth,判定"你没资格当服务端",握手失败。
表现: 受控节点注册失败,显示离线。

场景 2:服务端证书缺 clientAuth

故障方向: 主控节点→受控节点(主控节点主动推送)。
机制: 主控节点变成客户端,拿服务端证书出示给受控节点。受控节点检查EKU发现没有clientAuth,判定"你没资格当客户端",握手被拒。
表现: 主控节点侧推送反复重试,最终超时失败。受控节点侧正常注册所以显示在线。

场景 3:客户端证书缺 clientAuth

故障方向: 受控节点→主控节点(受控节点主动连接主控节点)。
机制: mTLS第二阶段,受控节点出示客户端证书,主控节点检查EKU发现没有clientAuth,判定"你没资格当客户端",握手失败。
表现: 受控节点注册失败,显示离线。(跟场景1表现相似,但根因不同——场景1是受控节点不信任主控节点,这里是主控节点不信任受控节点。)

场景 4:客户端证书缺 serverAuth(最隐蔽)

故障方向:主控节点→受控节点(主控节点主动推送)。
机制: 主控节点连接受控节点时,受控节点变成服务端,拿客户端证书充当服务端证书出示。主控节点检查EKU发现没有 serverAuth,握手被拒。但受控节点→主控节点方向完全正常!clientAuth 还在,心跳、注册都没问题。
表现: 受控节点显示在线,但主控节点侧的主动操作全部失败(连通性测试不通、任务下发超时)。

为什么场景4最危险? 因为Agent状态显示正常,运维只看面板会以为一切没问题。必须从OP侧操作日志才能发现TLS握手失败,很容易被误判为业务层问题。

总结对比

证书缺失字段故障方向表现
服务端证书serverAuth受控节点 → 主控节点受控节点离线
服务端证书clientAuth主控节点 → 受控节点推送超时,受控节点在线
客户端证书clientAuth受控节点 → 主控节点受控节点离线
客户端证书serverAuth主控节点 → 受控节点受控节点在线,但主控节点操作全挂

六、写在最后

证书的 EKU 看起来只是个小字段,但它背后的设计哲学是最小权限原则——一张证书只应该被授权做它该做的事。

我们项目中把 serverAuth 和 clientAuth 都写上是为了简化部署,但这让 EKU 的角色区分能力失效了,只能靠 CA 分离来兜底。理解了这些,下次遇到证书相关的故障就不再是"换一张证书试试",而是能从握手流程出发,精准定位到底是哪个方向、哪个环节、谁在拒绝谁