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
这里有一个你可以查看的 crypto/tls 库的分支:GitHub - refraction-networking/utls: Go 标准 TLS 库的分支,提供对 ClientHello 的低级访问以用于模仿目的。
更多关于Golang处理格式错误的TLS客户端证书问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
一个可行的方案是使用自定义的 VerifyConnection 回调函数来绕过这个限制。虽然标准的 crypto/tls 包在解析证书时确实会验证 SAN 字段,但你可以通过以下方式处理:
- 在 TLS 配置中设置
InsecureSkipVerify: true来跳过默认的证书验证。 - 实现自定义的
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 握手,同时记录错误信息。

