错误信息
SSL routines:ssl3_read_bytes:ssl/tls alert bad certificate
也可能输出为
- SSL routines:ssl3_read_bytes:sslv3 alert bad certificate
- error:0A000412:SSL routines::ssl/tls alert bad certificate
OpenSSL 3.3 及以上版本输出“ssl/tls alert bad certificate”,更早的版本输出“sslv3 alert bad certificate”(代码 0A000412;1.1.1 使用的是 14094412),后面跟着“SSL alert number 42”。OpenSSL 客户端自己在同样的拒绝情况下发送的是 unknown_ca(48)或 certificate_expired(45)。
OpenSSL — TLS alert 42 (bad_certificate) received from the peer
含义
对端发送了 TLS 告警 42(bad_certificate):它拒绝了本端出示的证书。Go 的 crypto/tls 对任何验证不通过的服务器证书(CA 未知、已过期、名称错误)都会发送这个告警,因此每当 Go 客户端拒绝基于 OpenSSL 的服务器(nginx、HAProxy、Postfix、s_server)的证书时,这些服务器都会记录它。在双向 TLS 中,服务器拒绝客户端的证书时会发送它。
原因
Go 客户端拒绝了服务器的证书
常见Go 程序(Kubernetes 组件、Terraform、Prometheus、Docker 以及许多 CLI)在验证失败时会发送 bad_certificate。真正的原因显示在 Go 一侧,例如“x509: certificate signed by unknown authority”。
双向 TLS:服务器拒绝了客户端证书
常见客户端证书已过期、不是由服务器接受的 CA 签发,或者因其他原因不被服务器接受。
证书的密钥用法不允许它承担这一角色
偶见如果证书的 extendedKeyUsage 中缺少 serverAuth(对客户端证书而言是 clientAuth),Go 等严格的验证方就会拒绝它。
修复方法
- 1.
找出拒绝证书的客户端及原因
日志行中写有客户端地址;在那个客户端上,错误信息会给出真正的原因。从外部验证证书链,往往也能直接看出原因。
bash # Server log: which client rejected the certificate, and does the chain verify from outside? openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>/dev/null \ | grep -E 'Verify return code' - 2.
Go 客户端:信任该 CA 或修复证书链
提供由受信任 CA 签发的完整证书链,或者把私有 CA 添加到 Go 客户端的 RootCAs 中;切勿用 InsecureSkipVerify 作为修复手段。
go // Go clients: trust the server's CA explicitly (keeps verification on) pool, _ := x509.SystemCertPool() caPEM, _ := os.ReadFile("internal-ca.pem") pool.AppendCertsFromPEM(caPEM) client := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{RootCAs: pool}}} - 3.
双向 TLS:检查客户端证书
客户端证书的有效期、颁发者和密钥用法必须符合服务器的要求。
bash # Mutual TLS: check the client certificate the server rejected openssl x509 -in client.pem -noout -issuer -dates -ext extendedKeyUsage,keyUsage