Go中实现mTLS必须设ClientAuth为tls.RequireAndVerifyClientCert并配置ClientCAs,否则静默降级为单向;客户端需主动提供证书且RootCAs信任服务端CA,私钥权限须为0600。

ClientAuth 必须设为 tls.RequireAndVerifyClientCert 才真正生效
服务端默认不验证客户端证书,哪怕你配了 ClientCAs 也没用。Go 的 tls.Config.ClientAuth 默认是 tls.NoClientCert,此时客户端不发证书、发错证书、甚至发空证书,连接都照常建立——你完全感知不到失败。
真正起效的只有这一种配置:
-
ClientAuth: tls.RequireAndVerifyClientCert:强制客户端提供证书,并立即执行链式验证(是否由ClientCAs中任一 CA 签发) -
ClientCAs: certPool必须是非 nil 的*x509.CertPool,且内容必须和客户端所用根 CA 完全一致;加载时用AppendCertsFromPEM(),别漏掉-----BEGIN CERTIFICATE-----边界 - 服务端自己的证书走
Certificates字段,和单向 HTTPS 一样,千万别混进ClientCAs
客户端不主动发证书,服务端永远收不到
HTTP 客户端(如 http.DefaultTransport)和 gRPC 客户端(如 grpc.Dial)默认都不携带客户端证书。你配了服务端 mTLS,但客户端没动,连接就会静默降级成单向 TLS。
正确做法是显式构造 tls.Config 并注入传输层:
- 用
tls.LoadX509KeyPair("client.crt", "client.key")加载证书+私钥;私钥文件权限必须是0600,否则 Go 会静默返回空tls.Certificate -
RootCAs填的是服务端 CA 的证书池(用于验证服务端身份),不是客户端自己的 CA - 若服务端证书 SAN 不含访问域名或 IP,
ServerName字段必须显式设置,否则握手直接失败并报x509: certificate is valid for xxx, not yyy
VerifyPeerCertificate 是零信任校验的唯一出口
基础链验证只确认“证书能追溯到 ClientCAs”,但业务上往往需要更细粒度控制:比如只允许 OU=api-clients 的证书、拒绝特定序列号、或要求 SAN 包含指定 DNS 名。这时必须用 VerifyPeerCertificate 回调。
注意两个关键事实:
- 一旦设置了
VerifyPeerCertificate,Go 就不再自动做链验证,你得自己调用roots.Verify()补上这一步,否则任何证书都能过 - 参数
rawCerts是原始 DER 字节,需用x509.ParseCertificate()解析;别直接信任verifiedChains,它在回调里是空的 - 错误要返回非 nil 的
error,否则校验视为通过;返回nil即放行
CRL 吊销检查必须手动实现,标准库不支持
Go 的 crypto/tls 明确不内置 CRL 或 OCSP 支持——这不是 bug,而是设计选择。Adam Langley 等安全专家认为 CRL/OCSP 在实践中存在延迟、可靠性与隐私缺陷,所以把吊销决策权交还应用层。
真要集成 CRL,只能在 HTTP handler 里手动做:
- 从
r.TLS.PeerCertificates[0]取客户端叶证书 - 加载本地 PEM 或 DER 格式的 CRL(用
pem.Decode()+x509.ParseCRL()) - 用签发该客户端证书的 CA 公钥调用
crl.CheckSignatureFrom(caCert)验证 CRL 签名 - 遍历
crl.RevokedCertificates检查客户端证书序列号是否在吊销列表中
最易忽略的一点:CRL 文件本身需要定期更新,且必须确保签名验证通过——如果 CRL 被篡改或过期,整个吊销检查就失去意义。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











