Golang处理格式错误的TLS客户端证书问题

Golang处理格式错误的TLS客户端证书问题 我有一个基于 crypto/tls 的 TLS 服务器,已经运行了两年多,处理 TLS 会话。但我遇到了一个由 Intune 生成的不良客户端证书问题,其 SAN 中的 URI 格式错误。tls 包在进行 TLS 握手时失败,没有给应用程序任何机会进行干预并忽略这个错误的 URI。作为一个服务,我对证书中可能无关紧要的部分并不挑剔;我只是希望 TLS 握手成功,或许能记录一下证书中的错误字段。

我已经在 x509/parser.go 文件的 parseSANExtension 函数中找到了检查失败的确切位置。我也尝试过“分叉” crypto/tls,但这无法实现,因为它引用了我没有访问权限的“internal”包。

有什么办法能让我在这里取得进展吗?


更多关于Golang处理格式错误的TLS客户端证书问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

更多关于Golang处理格式错误的TLS客户端证书问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


一个可行的方案是使用自定义的 VerifyConnection 回调函数来绕过这个限制。虽然标准的 crypto/tls 包在解析证书时确实会验证 SAN 字段,但你可以通过以下方式处理:

  1. 在 TLS 配置中设置 InsecureSkipVerify: true 来跳过默认的证书验证。
  2. 实现自定义的 VerifyConnection 函数,在其中手动验证证书链,但忽略 SAN 字段中的 URI 格式错误。

示例代码:

package main

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

func main() {
    cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
    if err != nil {
        panic(err)
    }

    config := &tls.Config{
        Certificates: []tls.Certificate{cert},
        ClientAuth:   tls.RequireAnyClientCert,
        // 跳过默认验证
        InsecureSkipVerify: true,
        // 自定义连接验证
        VerifyConnection: func(cs tls.ConnectionState) error {
            // 验证客户端证书(如果有)
            if len(cs.PeerCertificates) > 0 {
                // 创建自定义的验证选项
                opts := x509.VerifyOptions{
                    Roots:         getRootCAs(), // 你的根证书池
                    Intermediates: x509.NewCertPool(),
                    KeyUsages:     []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
                }

                // 添加中间证书
                for _, cert := range cs.PeerCertificates[1:] {
                    opts.Intermediates.AddCert(cert)
                }

                // 尝试验证证书链,但忽略解析错误
                _, err := cs.PeerCertificates[0].Verify(opts)
                if err != nil {
                    // 检查是否是 SAN 解析错误
                    if isSANParseError(err) {
                        // 记录日志并继续
                        fmt.Printf("警告:忽略 SAN 解析错误: %v\n", err)
                        return nil
                    }
                    return err
                }
            }
            return nil
        },
    }

    listener, err := tls.Listen("tcp", ":8443", config)
    if err != nil {
        panic(err)
    }
    defer listener.Close()

    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Printf("接受连接错误: %v\n", err)
            continue
        }
        go handleConnection(conn)
    }
}

func getRootCAs() *x509.CertPool {
    // 加载你的根证书
    pool := x509.NewCertPool()
    // pool.AppendCertsFromPEM(...)
    return pool
}

func isSANParseError(err error) bool {
    // 检查错误是否包含 SAN 解析相关的信息
    errStr := err.Error()
    return contains(errStr, "SAN") || contains(errStr, "URI") || contains(errStr, "parse")
}

func contains(s, substr string) bool {
    return len(s) >= len(substr) && (s == substr || len(s) > len(substr) && (s[:len(substr)] == substr || contains(s[1:], substr)))
}

func handleConnection(conn net.Conn) {
    defer conn.Close()
    // 处理连接
}

另一种更彻底的方法是复制 crypto/x509 包的相关部分并修改解析逻辑。你可以创建一个本地的 x509 包副本,修改 parseSANExtension 函数以跳过格式错误的 URI,然后使用这个修改后的包。

创建本地 x509 包:

// localx509/x509.go
package localx509

import (
    "encoding/asn1"
    "errors"
    "fmt"
    "net/url"
)

// 复制并修改 parseSANExtension 函数
func ParseSANExtension(value []byte) (dnsNames, emailAddresses []string, ipAddresses []net.IP, uris []*url.URL, err error) {
    // ... 复制原始代码 ...
    
    // 修改 URI 解析部分,添加错误处理
    for _, v := range seq {
        switch v.Tag {
        case 6: // URI
            var uriStr string
            if _, err := asn1.Unmarshal(v.Bytes, &uriStr); err != nil {
                // 原始代码会返回错误
                // 修改为:记录错误并继续
                fmt.Printf("警告:无法解析 URI SAN: %v\n", err)
                continue // 跳过这个 URI,而不是返回错误
            }
            
            uri, err := url.Parse(uriStr)
            if err != nil {
                fmt.Printf("警告:URI 格式错误: %v\n", err)
                continue // 跳过这个 URI
            }
            
            if len(uri.Host) > 0 {
                if uri.Port() == "" {
                    uris = append(uris, uri)
                } else {
                    // 处理带端口的 URI
                    uris = append(uris, uri)
                }
            }
        // ... 其他 case ...
        }
    }
    
    return
}

然后在你的 TLS 服务器中使用这个修改后的包来验证证书。这种方法需要更多的工作量,但给你完全的控制权。

这两种方法都能让你在遇到格式错误的 SAN URI 时继续 TLS 握手,同时记录错误信息。

回到顶部