Golang中TLS 1.3 RequireAndVerifyClientCert的实现与问题排查
Golang中TLS 1.3 RequireAndVerifyClientCert的实现与问题排查 服务器端:
pool := x509.NewCertPool()
pool.AddCert(rootCert)
config := &tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequireAndVerifyClientCert,
MinVersion: tls.VersionTLS13,
MaxVersion: tls.VersionTLS13,
ClientCAs: pool,
}
客户端:
pool := x509.NewCertPool()
pool.AddCert(serverCert)
// 配置 TLS 连接
config := &tls.Config{
Certificates: []tls.Certificate{cert},
InsecureSkipVerify: false,
RootCAs: pool,
ServerName: "localhost",
}
我正在执行:
$ openssl req -x509 -nodes -newkey rsa:2048 -out root.pem -subj "/CN=localhost" -addext "subjectAltName = DNS:localhost"
但我收到:
2023/06/10 11:40:58 tls: failed to verify certificate: x509: certificate is not valid for any names, but wanted to match localhost
2023/06/10 11:32:21 tls: failed to verify certificate: x509: cannot validate cer tificate for 127.0.0.1 because it doesn't contain any IP SANs
如何在使用 CA 池的情况下执行握手而不导致这些错误?我无论如何都做不到!
提前感谢。
更多关于Golang中TLS 1.3 RequireAndVerifyClientCert的实现与问题排查的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于Golang中TLS 1.3 RequireAndVerifyClientCert的实现与问题排查的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在TLS 1.3中实现RequireAndVerifyClientCert时,证书验证确实需要特别注意。您遇到的两个错误都是因为证书的SAN(Subject Alternative Name)配置问题。
第一个错误是因为客户端证书缺少localhost的DNS SAN记录。第二个错误是因为服务器证书缺少IP地址的SAN记录。以下是修正后的实现:
服务器端配置:
pool := x509.NewCertPool()
if !pool.AppendCertsFromPEM(rootCertPEM) {
log.Fatal("failed to parse root certificate")
}
config := &tls.Config{
Certificates: []tls.Certificate{serverCert},
ClientAuth: tls.RequireAndVerifyClientCert,
MinVersion: tls.VersionTLS13,
MaxVersion: tls.VersionTLS13,
ClientCAs: pool,
VerifyConnection: func(cs tls.ConnectionState) error {
// 额外的连接验证逻辑
opts := x509.VerifyOptions{
Roots: pool,
CurrentTime: time.Now(),
DNSName: "",
Intermediates: x509.NewCertPool(),
}
for _, cert := range cs.PeerCertificates {
if _, err := cert.Verify(opts); err != nil {
return fmt.Errorf("failed to verify client certificate: %w", err)
}
}
return nil
},
}
客户端配置:
pool := x509.NewCertPool()
if !pool.AppendCertsFromPEM(serverCertPEM) {
log.Fatal("failed to parse server certificate")
}
config := &tls.Config{
Certificates: []tls.Certificate{clientCert},
InsecureSkipVerify: false,
RootCAs: pool,
ServerName: "localhost",
VerifyConnection: func(cs tls.ConnectionState) error {
// 验证服务器证书
opts := x509.VerifyOptions{
Roots: pool,
CurrentTime: time.Now(),
DNSName: "localhost",
Intermediates: x509.NewCertPool(),
}
if _, err := cs.PeerCertificates[0].Verify(opts); err != nil {
return fmt.Errorf("failed to verify server certificate: %w", err)
}
return nil
},
}
生成证书的正确方法:
生成包含正确SAN扩展的证书:
# 生成CA证书
openssl req -x509 -nodes -newkey rsa:2048 \
-keyout ca.key -out ca.pem \
-subj "/CN=MyCA" \
-addext "basicConstraints=critical,CA:TRUE" \
-days 3650
# 生成服务器证书
openssl req -nodes -newkey rsa:2048 \
-keyout server.key -out server.csr \
-subj "/CN=localhost"
openssl x509 -req -in server.csr \
-CA ca.pem -CAkey ca.key -CAcreateserial \
-out server.pem -days 365 \
-extfile <(printf "subjectAltName=DNS:localhost,IP:127.0.0.1\nkeyUsage=digitalSignature,keyEncipherment\nextendedKeyUsage=serverAuth")
# 生成客户端证书
openssl req -nodes -newkey rsa:2048 \
-keyout client.key -out client.csr \
-subj "/CN=client"
openssl x509 -req -in client.csr \
-CA ca.pem -CAkey ca.key -CAcreateserial \
-out client.pem -days 365 \
-extfile <(printf "subjectAltName=DNS:client\nkeyUsage=digitalSignature,keyEncipherment\nextendedKeyUsage=clientAuth")
证书加载示例:
// 加载服务器证书
serverCert, err := tls.LoadX509KeyPair("server.pem", "server.key")
if err != nil {
log.Fatal(err)
}
// 加载客户端证书
clientCert, err := tls.LoadX509KeyPair("client.pem", "client.key")
if err != nil {
log.Fatal(err)
}
// 加载CA证书
caCert, err := os.ReadFile("ca.pem")
if err != nil {
log.Fatal(err)
}
pool := x509.NewCertPool()
if !pool.AppendCertsFromPEM(caCert) {
log.Fatal("failed to parse CA certificate")
}
调试连接:
// 启用TLS调试
os.Setenv("GODEBUG", "tls13=1,x509sha1=1")
// 在客户端添加验证回调
config.VerifyConnection = func(cs tls.ConnectionState) error {
fmt.Printf("Negotiated protocol: %s\n", cs.NegotiatedProtocol)
fmt.Printf("Cipher suite: %x\n", cs.CipherSuite)
for i, cert := range cs.PeerCertificates {
fmt.Printf("Certificate %d:\n", i)
fmt.Printf(" Subject: %s\n", cert.Subject)
fmt.Printf(" DNS Names: %v\n", cert.DNSNames)
fmt.Printf(" IP Addresses: %v\n", cert.IPAddresses)
}
return nil
}
关键点:
- 证书必须包含正确的SAN扩展(DNS:localhost 和 IP:127.0.0.1)
- 使用
VerifyConnection回调进行更细粒度的证书验证 - 确保CA证书正确加载到证书池中
- 服务器和客户端证书都需要由同一个CA签名
这些修改应该能解决您的证书验证问题。

