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

1 回复

更多关于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
}

关键点:

  1. 证书必须包含正确的SAN扩展(DNS:localhost 和 IP:127.0.0.1)
  2. 使用VerifyConnection回调进行更细粒度的证书验证
  3. 确保CA证书正确加载到证书池中
  4. 服务器和客户端证书都需要由同一个CA签名

这些修改应该能解决您的证书验证问题。

回到顶部