别了,SMTP mTLS

• 本文约2921字,阅读大致需要6分钟 • Security

最近在查看一封邮件的原始信头时,其中一段Received:记录引起了我的注意:

1
2
3
4
5
6
7
8
Received: from mx2.freebsd.org (mx2.freebsd.org [96.47.72.81])
	(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
	 key-exchange x25519 server-signature RSA-PSS (4096 bits)
	 server-digest SHA256
	 client-signature RSA-PSS (4096 bits) client-digest SHA256)
	(Client CN "mx2.freebsd.org", Issuer "YR1" (not verified))
	by anubis.delphij.net (Postfix) with ESMTPS id B904024CDA
	for <delphij@delphij.net>; Wed, 14 Oct 2026 12:46:42 -0700 (PDT)

注意其中的(Client CN "mx2.freebsd.org", Issuer "YR1" (not verified))。这个(not verified)对我来说有些意外,翻看我自己邮件服务器上的日志发现,对于来自mx2.freebsd.org和其他一些邮件服务的投递,都记录了类似的状态:

1
2
3
4
5
Oct 14 12:46:42 anubis postfix/smtpd[90904]: Untrusted TLS connection
  established from mx2.freebsd.org[96.47.72.81]: TLSv1.3 with cipher
  TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519
  server-signature RSA-PSS (4096 bits) server-digest SHA256
  client-signature RSA-PSS (4096 bits) client-digest SHA256

我和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

然后给自己发送一封邮件,于是在日志中看到了:

1
2
3
4
5
6
7
depth=0 verify=0 subject=/CN=mx2.freebsd.org
depth=1 verify=0 subject=/C=US/O=Let's Encrypt/CN=YR1
depth=2 verify=0 subject=/C=US/O=ISRG/CN=Root YR
depth=3 verify=1 subject=/C=US/O=Internet Security Research Group/CN=ISRG Root X1
depth=2 verify=1 subject=/C=US/O=ISRG/CN=Root YR
depth=1 verify=1 subject=/C=US/O=Let's Encrypt/CN=YR1
depth=0 verify=1 subject=/CN=mx2.freebsd.org

此处「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。