Golang中如何重写或扩展x509.CertPool的contains函数?

Golang中如何重写或扩展x509.CertPool的contains函数? 你好。有没有办法配置服务器TLS,使得在验证客户端证书时,如果服务器tls.ConfigClientCAs x509.CertPool中找不到该客户端证书,可以调用我自己的函数?

查看Go源代码,我发现它在go/handshake_server.go at master · golang/go · GitHub中执行到了certs[0].Verify(opts),而在Verify函数中,它调用了if opts.Roots.contains(c)。在contains函数里,我需要的是当s.havesum为false时,能够执行一些自定义代码的方法 - go/cert_pool.go at 527ace0ffa81d59698d3a78ac3545de7295ea76b · golang/go · GitHub

我想要实现的是让服务器接受那些使用在我们后端数据库中注册过的自签名证书的客户端。

感谢任何建议。


更多关于Golang中如何重写或扩展x509.CertPool的contains函数?的实战教程也可以访问 https://www.itying.com/category-94-b0.html

6 回复

抱歉,我不太明白这有什么帮助?我希望服务器端能够使用一个函数来验证客户端证书——但我不知道如何从 *tls.ClientHelloInfo 中获取客户端证书?

(在 processCert 被调用的时候,客户端甚至还没有发送它的客户端证书,对吧?)

更多关于Golang中如何重写或扩展x509.CertPool的contains函数?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


谢谢。我通过使用那个 VerifyPeerCertificate 以及将 ClientAuth 设置为 tls.RequireAnyClientCert,让它完成了我的需求:

var tlsConf = &tls.Config{
   VerifyPeerCertificate: validateCert,
   ClientAuth:            tls.RequireAnyClientCert, 
}

通过将 ClientAuth 设置为 tls.RequireAnyClientCert,实际上是在向任何(恶意的)客户端暗示您需要客户端证书,这根据您应用程序的安全要求可能并不理想。 但是通过 GetCertificate 方法来实现,您可以静默地检查证书是否是您所期望的那个。

func main() {
    fmt.Println("hello world")
}

你不需要那样做。 以下是一个如何实现的示例:

package main

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

var tlsConf = &tls.Config{GetCertificate: processCert}

func main() {
	srv := &http.Server{TLSConfig: tlsConf}
	cert, err := tls.LoadX509KeyPair("cert", "privkey") // 用于多个证书导入的情况
	if err != nil {
		log.Panic(err.Error())
	}
	tlsConf.Certificates = append(tlsConf.Certificates, cert)
	err = srv.ListenAndServeTLS("", "") // 也可以在这里直接指定证书

	if err != nil {
		log.Fatal(err)
	}
}

func processCert(hello *tls.ClientHelloInfo) (crt *tls.Certificate, err error) {
	if isValidCert(hello) {
		log.Println("Valid certificate")
		// 执行其他操作
	}
	return
}

func isValidCert(hello *tls.ClientHelloInfo) (supported bool) {
	for _, cert := range tlsConf.Certificates {
		cert := cert
		if err := hello.SupportsCertificate(&cert); err == nil {
			return true
		}
	}

	return
}

只需将自签名证书添加到双方即可。

如果你在 tlsConf 中实现了 VerifyPeerCertificate 方法,那么该方法只会在客户端实际发送证书时接收到证书数据,其他情况则不会。

另一方面,每当收到新的 TLS 握手请求时,都会调用 processCert,这使得你能够在 TLS 握手过程的最初阶段就阻止无效的请求。

但无论如何,如果你需要第一种方式,这里有一个示例:

package main

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

var tlsConf = &tls.Config{VerifyPeerCertificate: validateCert}

func main() {
	srv := &http.Server{TLSConfig: tlsConf}
	cert, err := tls.LoadX509KeyPair("cert", "privkey") // 在需要导入多个证书的情况下
	if err != nil {
		log.Panic(err.Error())
	}
	tlsConf.Certificates = append(tlsConf.Certificates, cert)
	err = srv.ListenAndServeTLS("", "") // 也可以在这里直接指定证书

	if err != nil {
		log.Fatal(err)
	}
}

func validateCert(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
	if verifiedChains == nil {
		return errors.New("client didn't send any certificates")
	}

	for _, chain := range verifiedChains {
		for _, cert := range chain {
			// 对证书实施任何类型的验证
			if time.Now().After(cert.NotAfter) { // 一个检查证书是否过期的示例
				return errors.New("certificate expired")
			}
		}
	}
	return nil
}

可以通过实现自定义的verifyPeerCertificate回调函数来扩展客户端证书验证逻辑。这个回调函数会在标准验证之后执行,允许你添加额外的验证逻辑。

package main

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

// 模拟的后端数据库检查函数
func isCertificateRegisteredInDB(cert *x509.Certificate) bool {
    // 这里实现你的数据库查询逻辑
    // 例如:检查证书指纹是否在数据库中注册
    return true
}

func main() {
    // 创建自定义的验证回调
    verifyPeerCert := func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
        if len(rawCerts) == 0 {
            return errors.New("no client certificate provided")
        }

        // 解析客户端证书
        clientCert, err := x509.ParseCertificate(rawCerts[0])
        if err != nil {
            return err
        }

        // 在这里添加你的自定义验证逻辑
        if !isCertificateRegisteredInDB(clientCert) {
            return errors.New("client certificate not registered in database")
        }

        return nil
    }

    // 配置TLS
    tlsConfig := &tls.Config{
        ClientAuth: tls.RequireAnyClientCert,
        VerifyPeerCertificate: verifyPeerCert,
        // 可以设置ClientCAs为nil,完全依赖自定义验证
        ClientCAs: nil,
    }

    server := &http.Server{
        Addr:      ":8443",
        TLSConfig: tlsConfig,
    }

    // 启动HTTPS服务器
    server.ListenAndServeTLS("server.crt", "server.key")
}

如果你需要更底层的控制,可以创建自定义的CertPool实现:

type CustomCertPool struct {
    *x509.CertPool
    // 添加你的自定义字段
}

func (c *CustomCertPool) contains(cert *x509.Certificate) bool {
    // 先调用原始的contains检查
    if c.CertPool != nil && c.CertPool.Contains(cert) {
        return true
    }
    
    // 添加你的自定义逻辑
    // 例如检查数据库
    return isCertificateRegisteredInDB(cert)
}

// 使用自定义CertPool
func main() {
    customPool := &CustomCertPool{
        CertPool: x509.NewCertPool(),
    }
    
    tlsConfig := &tls.Config{
        ClientAuth: tls.RequireAnyClientCert,
        ClientCAs:  customPool,
    }
    
    // ... 服务器配置
}

注意:第二种方法需要你能够替换contains方法的调用,但在标准库中这是内部方法。更可靠的方法是使用VerifyPeerCertificate回调,这是Go标准库提供的扩展点。

回到顶部