别了,SMTP mTLS
最近在查看一封邮件的原始信头时,其中一段Received:记录引起了我的注意:
| |
注意其中的(Client CN "mx2.freebsd.org", Issuer "YR1" (not verified))。这个(not verified)对我来说有些意外,翻看我自己邮件服务器上的日志发现,对于来自mx2.freebsd.org和其他一些邮件服务的投递,都记录了类似的状态:
| |
我和FreeBSD.org的Postfix配置采取了类似的策略:在接收邮件时,请求对方提供客户端证书(smtpd_tls_ask_ccert = yes),而在发送邮件时,如果远端服务器要求提供客户端证书,则将自己的服务器TLS证书作为客户端证书出示(配置了smtp_tls_cert_file),以此向对方证明身份。这套配置在过去很长一段时间里一直工作良好,日志里记录的是Trusted TLS connection,信头里则是(verified OK)。
所以到底发生了什么呢?为此,我临时提高了Postfix的TLS日志级别,以方便看清OpenSSL握手时发生了什么:
postconf -e smtpd_tls_loglevel=2
service postfix reload然后给自己发送一封邮件,于是在日志中看到了:
| |
此处「YR1」我之前没有见过,第一反应是本地的ca_root_nss是不是漏掉了新的中间证书或根证书。但仔细看日志就会发现:depth=3的ISRG Root X1已经在本地受信根证书中,并且从上往下逐级验证签名时全部返回了verify=1,只是在最开始检查证书用途时,前三级出现了verify=0。
查了一圈,找到了去年Let’s Encrypt的一篇公告:Ending TLS Client Authentication Certificate Support in 2026。
嘛玩意?
mTLS与EKU(Extended Key Usage)
在使用HTTPS访问Web时,TLS认证是单向的:客户端(浏览器)去验证服务端的证书,并据此确认自己连上的是真正的目标网站,而服务端并不在TLS层关心客户端是谁。
但在服务器到服务器(例如两台邮件服务器之间投递、微服务网格、分布式数据库节点互联)的场景中,我们经常会用到双向TLS(mTLS)验证。在mTLS握手中,服务端会向客户端发出CertificateRequest,要求客户端也出示自己的X.509证书,并用对应的私钥签署CertificateVerify,从而在建立加密通道的同时完成双向身份确认。
为了防止证书被挪作他用(例如拿一张仅用于代码签名或邮件加密的证书去冒充Web服务器),X.509 v3引入了扩展密钥用法(Extended Key Usage,简写为EKU)扩展,用来限制证书可以在哪些场合使用,互联网上的用法由RFC 5280第4.2.1.12节定义。和TLS相关的两个OID是:
id-kp-serverAuth(1.3.6.1.5.5.7.3.1):TLS WWW服务端身份认证id-kp-clientAuth(1.3.6.1.5.5.7.3.2):TLS WWW客户端身份认证
在过去十几年里,几乎所有公共CA签发的普通域名证书都默认同时包含这两个EKU。因此系统管理员申请一张标准的服务器证书后,既可以把它作为服务端证书使用,也可以作为客户端证书使用。
不过,从2026年开始,Let’s Encrypt和其他受主流浏览器信任的公共CA陆续停止在公共TLS证书中包含clientAuth EKU。Let’s Encrypt的默认classic profile从2026年2月11日起不再包含它;新启用的Generation Y中间证书(如日志中的YR1)本身就不含clientAuth,也就无法再签发带clientAuth的终端证书。
因此,当mx2.freebsd.org拿着这张只有serverAuth的新证书作为客户端连上来时,尽管证书的签名和信任链完全合法,OpenSSL在按X509_PURPOSE_SSL_CLIENT做用途检查时,仍然会因为缺少clientAuth而报告X509_V_ERR_INVALID_PURPOSE(对应日志里的verify=0),Postfix便会记作Untrusted/(not verified)。
浏览器厂商为什么要砍掉它?
这并不是一两家CA自己的决定,而是来自Google的Chrome Root Program Policy。这份政策从1.6版开始定义「专用于TLS服务端认证的PKI体系」,1.8版则给出了具体日期:从2026年6月15日起,Chrome逐步淘汰不符合要求的PKI体系,此后新披露的中间CA只能包含serverAuth;2027年3月15日及以后签发的终端证书也必须只包含serverAuth。Chrome是最大的根证书库之一,任何想要继续留在里面的公共CA都只能照办。
浏览器厂商这样做,是想缩小公共Web PKI出事时的波及面。在浏览器厂商看来,公共根证书库存在的目的就是保护浏览器访问HTTPS网站。按照CA/Browser Forum的Baseline Requirements 第4.9.1.1节,CA一旦发现签发过程有问题,需要在24小时或5天(120小时)内吊销受影响的证书,严重时整个中间CA都可能被要求停用。
但现实情况是,大量企业和机构为了省事,把公共CA签发的双用途证书广泛部署在内部甚至工业基础设施中。每次发生合规事件需要紧急吊销时,都会有大型企业跳出来抗议,声称「如果在5天内强制吊销这批证书,我们的内网/基础设施就会全面瘫痪」,从而阻碍或延迟吊销。2024年7月DigiCert因CNAME域名验证缺陷 需要吊销八万多张证书时,就有「关键基础设施」客户要求延期,甚至有公司直接去法院申请了临时禁令。
于是浏览器厂商索性釜底抽薪:规定公共TLS证书体系只为服务端认证服务。把非Web服务端的用途踢出公共Web PKI之后,浏览器以后再要求强制吊销时,就不用顾忌会不会顺手把哪家公司的内网mTLS给一锅端掉了。如果往阴谋论的方向想,商业CA或许也乐见其成:免费和廉价DV证书不能再兼作客户端证书之后,企业若想继续用别人托管的mTLS,就只能掏钱去买私有PKI服务了。
人类不配有好东西
单从职责划分来说,浏览器厂商的做法并没有什么问题。而且这样做还顺手消灭了一类常见的低级配置错误:有些管理员在配置nginx或VPN的客户端证书认证时,直接把系统的公共CA bundle设为信任根,却忘了校验客户端证书的Subject CN或SAN,结果任何一个持有有效域名证书的人都能通过其内部认证。
不过,这种「因为有人乱用,所以干脆把桌子掀了」的做法还是让人相当无奈。原本像FreeBSD.org或是我自己的个人邮件服务器,用现成的公网证书就能在MTA之间互相确认身份,几乎不需要额外维护;而现在只能在公共接口上关掉smtpd_tls_ask_ccert,退回Anonymous TLS connection,或是自己维护一套私有CA或公钥指纹白名单。
This is why we can’t have nice things.
别了,SMTP mTLS。
在线阅读本文并参与讨论 delphij's Chaos https://blog.delphij.net/posts/2026/10/byebye-smtp-mtls/