You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Golang TLS双向认证:握手错误时如何导出客户端证书

解决双向TLS服务器握手出错时导出客户端证书的问题

要捕获所有(成功/失败握手场景下的)客户端证书,你可以通过以下两种方式实现:

方法1:手动处理TLS握手,捕获证书与错误

绕过http.ListenAndServeTLS的自动握手流程,手动接受连接并触发握手,这样无论握手成功与否,都能从tls.Conn中提取客户端证书:

package main

import (
	"crypto/tls"
	"log"
	"net/http"
)

func main() {
	tlsCfg := &tls.Config{
		ClientAuth:   tls.RequireAndVerifyClientCert,
		RootCAs:      // 配置你的CA证书池
		Certificates: // 配置服务器证书
	}

	// 创建自定义TLS监听器
	listener, err := tls.Listen("tcp", ":443", tlsCfg)
	if err != nil {
		log.Fatalf("Failed to start TLS listener: %v", err)
	}
	defer listener.Close()

	httpSrv := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.Write([]byte("Authenticated successfully"))
		}),
	}

	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Printf("Accept error: %v", err)
			continue
		}

		tlsConn, ok := conn.(*tls.Conn)
		if !ok {
			log.Println("Received non-TLS connection")
			conn.Close()
			continue
		}

		// 手动触发握手,捕获错误
		handshakeErr := tlsConn.Handshake()

		// 提取客户端证书(无论握手成功与否)
		clientCerts := tlsConn.PeerCertificates()
		if len(clientCerts) > 0 {
			for _, cert := range clientCerts {
				log.Printf("Client certificate subject: %s", cert.Subject)
				// 这里可以将证书导出为PEM格式保存到文件
				// example: pem.Encode(file, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
			}
		} else {
			log.Println("No client certificate provided")
		}

		if handshakeErr != nil {
			log.Printf("Handshake failed: %v", handshakeErr)
			tlsConn.Close()
			continue
		}

		// 握手成功,交给HTTP服务器处理
		go httpSrv.Serve(tlsConn)
	}
}

方法2:使用tls.Config.VerifyPeerCertificate捕获证书

VerifyPeerCertificate会在证书验证阶段被调用——无论最终验证是否通过,都能提前获取客户端证书,适合不需要完全控制握手流程的场景:

package main

import (
	"crypto/tls"
	"crypto/x509"
	"log"
	"net/http"
)

func main() {
	tlsCfg := &tls.Config{
		ClientAuth: tls.RequireAndVerifyClientCert,
		RootCAs:    // 配置你的CA证书池
		Certificates: // 配置服务器证书
		VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
			// 先导出客户端证书
			for _, rawCert := range rawCerts {
				cert, err := x509.ParseCertificate(rawCert)
				if err != nil {
					log.Printf("Failed to parse client certificate: %v", err)
					continue
				}
				log.Printf("Client certificate subject: %s", cert.Subject)
				// 执行证书导出操作
			}

			// 调用默认的证书验证逻辑,确保原有认证规则生效
			defaultVerify := tls.CertificateVerifyFunc(tlsCfg.ClientAuth)
			return defaultVerify(rawCerts, verifiedChains)
		},
	}

	httpSrv := &http.Server{
		Addr:      ":443",
		TLSConfig: tlsCfg,
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.Write([]byte("Authenticated successfully"))
		}),
	}

	log.Fatal(httpSrv.ListenAndServeTLS("server.crt", "server.key"))
}

关键说明

  • tls.Conn.PeerCertificates():只要客户端在握手过程中发送了证书,无论验证是否通过,都能获取到证书内容。
  • VerifyPeerCertificate:在证书验证环节触发,优先于最终的握手结果,适合轻量级的证书捕获需求;注意必须调用默认验证逻辑,否则会跳过客户端证书的合法性校验。

内容的提问来源于stack exchange,提问作者Levap

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 16:39:51