Golang中如何处理由未知机构签发的Crypto/x509证书

Golang中如何处理由未知机构签发的Crypto/x509证书 在使用SSL连接进行证书验证时,我遇到了以下问题。

已知的意大利SMTP服务器 smtp.tim.it 发送了以下证书数据(页面底部附有 openssl 命令的结果)。

看起来,它的证书链似乎存在某种断裂:

  • USERTrust RSA Certification Authority(已知受信任的CA) => 验证 TI Trust Technologies DV CA
  • TI Trust Technologies OV CA => 验证 smtp.tim.it

但在这个链中,缺少对 TI Trust Technologies OV CA 的验证环节。

运行以下代码会失败,并提示 certificate: x509: certificate signed by unknown authority

	tlsConfig := &tls.Config{
		InsecureSkipVerify: false, // If noCert is true, skip Go's verification entirely
		ServerName:   "smtp.tim.it",
	}

	tlsConn := tls.Client(socket.conn, tlsConfig)
	// Perform the TLS handshake
	err := tlsConn.Handshake()

但这种情况仅发生在 Linux 和 Android 上。 在 MAC 和 iOS 上,此代码可以正常工作并正确验证证书链。尚未在 Windows 上测试,但我推测它也能正常工作。经过一些调试,我发现差异来自以下代码部分:

verify.go

// Use platform verifiers, where available, if Roots is from SystemCertPool.
	if runtime.GOOS == "windows" || runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
		// Don't use the system verifier if the system pool was replaced with a non-system pool,
		// i.e. if SetFallbackRoots was called with x509usefallbackroots=1.
		systemPool := systemRootsPool()
		if opts.Roots == nil && (systemPool == nil || systemPool.systemPool) {
			return c.systemVerify(&opts)
		}
		if opts.Roots != nil && opts.Roots.systemPool {
			platformChains, err := c.systemVerify(&opts)
			// If the platform verifier succeeded, or there are no additional
			// roots, return the platform verifier result. Otherwise, continue
			// with the Go verifier.
			if err == nil || opts.Roots.len() == 0 {
				return platformChains, err
			}
		}
	}

我猜测这段代码导致操作系统处理验证链。我的问题是,对于其他平台(如 Android),我们该如何处理?(我们有许多使用此服务提供商的 Android 用户,并且强制要求证书验证,因此这对我们非常重要。此外,在我们之前的 Java 实现中,这曾经是有效的(不确定是如何实现的,因为那是很久以前开发的,代码也已过时)。)

感谢提供的任何帮助,如果这个问题在其他地方已经解答过,我表示抱歉(我个人未能找到答案),另外我对这些东西的工作原理了解不多。

openssl s_client -starttls smtp -connect smtp.tim.it:587 -showcerts 结果:

Connecting to 34.141.221.156                          CONNECTED(00000003)
depth=0 C=IT, ST=Milano, O=TELECOM ITALIA SPA, CN=smtp.tim.it
verify error:num=20:unable to get local issuer certificate
verify return:1                                       depth=0 C=IT, ST=Milano, O=TELECOM ITALIA SPA, CN=smtp.tim.it                                               verify error:num=21:unable to verify the first certificate                                                  verify return:1
depth=0 C=IT, ST=Milano, O=TELECOM ITALIA SPA, CN=smtp.tim.it
verify return:1
---
Certificate chain
 0 s:C=IT, ST=Milano, O=TELECOM ITALIA SPA, CN=smtp.tim.it
   i:C=IT, ST=Roma, L=Pomezia, O=TI Trust Technologies S.R.L., CN=TI Trust Technologies OV CA
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
   v:NotBefore: May 28 00:00:00 2025 GMT; NotAfter: Jun 27 23:59:59 2026 GMT
-----BEGIN CERTIFICATE-----
MIIHXzCCBkegAwIBAgIQBaf7Bz3GKlRh4r37qfgd/TANBgkqhkiG9w0BAQsFADB7
MQswCQYDVQQGEwJJVDENMAsGA1UECBMEUm9tYTEQMA4GA1UEBxMHUG9tZXppYTEl
MCMGA1UEChMcVEkgVHJ1c3QgVGVjaG5vbG9naWVzIFMuUi5MLjEkMCIGA1UEAxMb
VEkgVHJ1c3QgVGVjaG5vbG9naWVzIE9WIENBMB4XDTI1MDUyODAwMDAwMFoXDTI2
MDYyNzIzNTk1OVowUTELMAkGA1UEBhMCSVQxDzANBgNVBAgTBk1pbGFubzEbMBkG
A1UEChMSVEVMRUNPTSBJVEFMSUEgU1BBMRQwEgYDVQQDEwtzbXRwLnRpbS5pdDCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALeUXDVdSR3vX69HqJcPTK/7
IkZKz5Hu3bVlxt5BVgfq8ikgAFYtGNVSChkm8cC1aqf39HK/lGbjBxmfoXqeqOXU
95yorSHKKS5M65434Gh/qxgZX0rNGOnkPSR9cGxhW5lOitPK755SXWFpXsWEo/A7
ChmuBkJvmWettaKRU4dFfxr9rxCIq/KMDW0pNOx6AVWYw9W22+Fcia7b2nMwI7lJ
NYDlPWEN7Y+v/xhsnR1WoDcjfpHGZFGijAcLKX9EFuzvE+AxNyiKk7JTPDJJvJIS
bN6PBBqLwkTRmZVPUZTZ+EUxIUlndpLeNd3GLUsTrSCxe7o5nsKr20hZeRBR/k8C
AwEAAaOCBAcwggQDMB8GA1UdIwQYMBaAFGPlP/jPJ7ExGSorXM3/LnH7KTjfMB0G
A1UdDgQWBBTt5pcOD+PaovEkJbMeaY3TZJYaNzAOBgNVHQ8BAf8EBAMCBaAwDAYD
VR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwSQYDVR0g
BEIwQDA0BgsrBgEEAbIxAQICSTAlMCMGCCsGAQUFBwIBFhdodHRwczovL3NlY3Rp
Z28uY29tL0NQUzAIBgZngQwBAgIwSwYDVR0fBEQwQjBAoD6gPIY6aHR0cDovL3Rp
VHJ1c3QuY3JsLnNlY3RpZ28uY29tL1RJVHJ1c3RUZWNobm9sb2dpZXNPVkNBLmNy
bDCBgwYIKwYBBQUHAQEEdzB1MEYGCCsGAQUFBzAChjpodHRwOi8vdGlUcnVzdC5j
cnQuc2VjdGlnby5jb20vVElUcnVzdFRlY2hub2xvZ2llc09WQ0EuY3J0MCsGCCsG
AQUFBzABhh9odHRwOi8vdGlUcnVzdC5vY3NwLnNlY3RpZ28uY29tMIIBfwYKKwYB
BAHWeQIEAgSCAW8EggFrAWkAdQCWl2S/VViXrfdDh2g3CEJ36fA61fak8zZuRqQ/
D8qpxgAAAZcV1yCqAAAEAwBGMEQCIFFVWNEsc2UbBPBpkhqnTj0/KUtKHMAfALFM
DK9O9IF5AiBGa2EYjszuEg9BheOqodEayZZ9MHQBsif+hG+d8G+f0QB3ABmG1Mco
qm/+ugNveCpNAZGqzi1yMQ+uzl1wQS0lTMfUAAABlxXXIEsAAAQDAEgwRgIhAMhe
ncRRlLMTkW0OZr3N5JZqBcsjY83cBIQ+CTsJtx4QAiEAllswEQIQlonTy80tV3/A
bKzlbZLJiv3YD/V8TjHXK4MAdwAOV5S8866pPjMbLJkHs/eQ35vCPXEyJd0hqSWs
YcVOIQAAAZcV1yBNAAAEAwBIMEYCIQDj0Th5VH8vK1cToYt7hiHgSBjBa62e1FcX
KbaBkPb8JwIhAPiqdWtqVhyynkgJarD/WKDAvTLd69U7JPkiSeek1tt0MIHiBgNV
HREEgdowgdeCC3NtdHAudGltLml0gg4qLnBvc3RhLnRpbS5pdIIKYm94LnRpbi5p
dIILaW1hcC50aW0uaXSCC2ltYXAudGluLml0ggxpbWFwcy50aW4uaXSCC2luLmFs
aWNlLml0ggttYWlsLnRpbi5pdIIJbXgudGltLml0gglteC50aW4uaXSCDG91dC5h
bGljZS5pdIIKcG9wLnRpbS5pdIIKcG9wLnRpbi5pdIILcG9wcy50aW4uaXSCC3Nt
dHAudGluLml0ggxzbXRwcy50aW4uaXSCBnRpbi5pdDANBgkqhkiG9w0BAQsFAAOC
AQEAYXfyRlm5bU3Mm2fyXHi9OXl90cAZvd57sWb5geXDL1Ubwmmqqk/eCLxDsj8d
3bXmMjWQjTs/ciphG358H1PonrhF7Ct1ZW1njYEK0RZJsOM0uppBMFEhrCg3C77z
6iXNUoZ8Nx5bfUsST3dwR0ieA99OKXV8qRKolO23FGACoH4eIhrZc7X2J0euQeGY
gGNduEVael2IIS2eAoXVmA0t06UuF/FsVbvmrX6WC7W/SjZ04u6zCkNZkIi17I4W
+1TtZ9GR3TiEO1rAl7vsAaaMKLoAG8T5PuiG7iO96KTa/bj5lhXMm5UvyLY0RLxi
++bD6UvAIbf/+wtMFumZwctOXQ==
-----END CERTIFICATE-----
 1 s:C=IT, ST=Roma, L=Pomezia, O=TI Trust Technologies S.R.L., CN=TI Trust Technologies DV CA
   i:C=US, ST=New Jersey, L=Jersey City, O=The USERTRUST Network, CN=USERTrust RSA Certification Authority
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA384
   v:NotBefore: Jul 30 00:00:00 2019 GMT; NotAfter: Jul 29 23:59:59 2029 GMT
-----BEGIN CERTIFICATE-----
MIIGBjCCA+6gAwIBAgIRALMcXS7T8KrJaCn0VoiLO44wDQYJKoZIhvcNAQEMBQAw
gYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpOZXcgSmVyc2V5MRQwEgYDVQQHEwtK
ZXJzZXkgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMS4wLAYD
VQQDEyVVU0VSVHJ1c3QgUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTE5
MDczMDAwMDAwMFoXDTI5MDcyOTIzNTk1OVowezELMAkGA1UEBhMCSVQxDTALBgNV
BAgTBFJvbWExEDAOBgNVBAcTB1BvbWV6aWExJTAjBgNVBAoTHFRJIFRydXN0IFRl
Y2hub2xvZ2llcyBTLlIuTC4xJDAiBgNVBAMTG1RJIFRydXN0IFRlY2hub2xvZ2ll
cyBEViBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKRYvlj4gqxr
SMQPfD5tXRBiK9tifjCoeFZ6BnPwT+G6XthhxODxX1ZVrqXjREjtef6uQc1K9ao/
gXJ5jA4soV72lRLvxyVZ9uHcw83WZft+6oqpQaSOdo8Bvag3HEegoSb3zElSmPhv
NArENCdK6uiHmXvOW6uzPPdXG4uMeqyTdZVlR1u27UVJ5xB2Um6ryNfHqAA4eN22
djopHRIeVAu5sQoeAurLTBOfq4QEpecDm0xa+ySe642X+HVNzQGye7diI0sD/ngf
Ir9x7h1HxyqxC24rXT4XZlK+ZA/d5bpuvoMQMDEV4CKo6T9J7oo+Oq3u+HFwBo94
BClLlXBc9aMCAwEAAaOCAXUwggFxMB8GA1UdIwQYMBaAFFN5v1qqK0rPVIDh2JvA
nfKyA2bLMB0GA1UdDgQWBBTWUeJ5s8ZXr95WY+CywoVtRjQVpjAOBgNVHQ8BAf8E
BAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHSUEFjAUBggrBgEFBQcDAQYI
KwYBBQUHAwIwIgYDVR0gBBswGTANBgsrBgEEAbIxAQICSTAIBgZngQwBAgEwUAYD
VR0fBEkwRzBFoEOgQYY/aHR0cDovL2NybC51c2VydHJ1c3QuY29tL1VTRVJUcnVz
dFJTQUNlcnRpZmljYXRpb25BdXRob3JpdHkuY3JsMHYGCCsGAQUFBwEBBGowaDA/
BggrBgEFBQcwAoYzaHR0cDovL2NydC51c2VydHJ1c3QuY29tL1VTRVJUcnVzdFJT
QUFkZFRydXN0Q0EuY3J0MCUGCCsGAQUFBzABhhlodHRwOi8vb2NzcC51c2VydHJ1
c3QuY29tMA0GCSqGSIb3DQEBDAUAA4ICAQA0MXhgkf6J+NnG0SAwIkNrC0zv27vT
IwtAkVcp3TaIIdnIT11XJ1mVmt7MwidqMC7cYXqe81zlIlVB65+NwZWuhEiM+1ce
vu8MFA6xcaFHFTTFYFIsBGHw7Fgbo54rP5cIHKehyCcCxvFFdQ2tvbMVjGWMubZO
5sD0rAWb1gGkVQ8uNgwR2RJrrfllPDqbNz/PNyxAAJWtY7dpTd1duEvA8u+uJ2wk
0DHKtPX8MvmojNwL89KZMUbXE+Ecg4Dc120SWjk6tcExkkceRP91MQv9oSABGw03
mSO2F02LguHJbfrS3kP1Twx68B33SIzR7OUcP93aAU+eKjCiY+jMqfr/knaqmaY1
U5AEANMHIFLoM7SOYvQhBa0sqNAxiwIZPT2lGxN8/znPAf9wEk7zHOpsJ2Qd+Tb1
Or6F+Y0Q2SsVGCxEB+HiFG9Az/rBk0sZhMuCyvd81XYPO7aTJjf/ppwtiB

更多关于Golang中如何处理由未知机构签发的Crypto/x509证书的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中如何处理由未知机构签发的Crypto/x509证书的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go中处理由未知机构签发的x509证书,可以通过自定义证书验证逻辑来实现。以下是几种解决方案:

解决方案1:添加中间证书到信任链

import (
    "crypto/tls"
    "crypto/x509"
)

// 中间证书(TI Trust Technologies OV CA)
const intermediateCert = `-----BEGIN CERTIFICATE-----
MIIF...(完整的中间证书PEM)...Q==
-----END CERTIFICATE-----`

func createTLSConfig() (*tls.Config, error) {
    // 创建证书池
    rootCAs, err := x509.SystemCertPool()
    if err != nil {
        rootCAs = x509.NewCertPool()
    }
    
    // 添加中间证书
    if ok := rootCAs.AppendCertsFromPEM([]byte(intermediateCert)); !ok {
        return nil, fmt.Errorf("failed to append intermediate certificate")
    }
    
    return &tls.Config{
        RootCAs:              rootCAs,
        ServerName:           "smtp.tim.it",
        InsecureSkipVerify:   false,
    }, nil
}

// 使用
tlsConfig, err := createTLSConfig()
if err != nil {
    // 处理错误
}
tlsConn := tls.Client(socket.conn, tlsConfig)
err = tlsConn.Handshake()

解决方案2:完全自定义验证

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

func customVerify(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
    if len(rawCerts) == 0 {
        return errors.New("no certificates provided")
    }
    
    // 解析服务器证书
    serverCert, err := x509.ParseCertificate(rawCerts[0])
    if err != nil {
        return err
    }
    
    // 创建证书池
    certPool := x509.NewCertPool()
    
    // 添加系统根证书
    systemPool, err := x509.SystemCertPool()
    if err == nil && systemPool != nil {
        certPool = systemPool
    }
    
    // 添加中间证书
    intermediateCert := `-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----`
    certPool.AppendCertsFromPEM([]byte(intermediateCert))
    
    // 构建验证选项
    opts := x509.VerifyOptions{
        Roots:         certPool,
        DNSName:       "smtp.tim.it",
        Intermediates: x509.NewCertPool(),
    }
    
    // 添加所有中间证书到中间池
    for i := 1; i < len(rawCerts); i++ {
        intermediate, err := x509.ParseCertificate(rawCerts[i])
        if err != nil {
            continue
        }
        opts.Intermediates.AddCert(intermediate)
    }
    
    // 执行验证
    _, err = serverCert.Verify(opts)
    return err
}

// 使用
tlsConfig := &tls.Config{
    ServerName:         "smtp.tim.it",
    InsecureSkipVerify: true, // 禁用默认验证
    VerifyConnection:   customVerify,
}

tlsConn := tls.Client(socket.conn, tlsConfig)
err := tlsConn.Handshake()

解决方案3:动态获取并缓存中间证书

import (
    "crypto/tls"
    "crypto/x509"
    "sync"
)

var (
    certCache     = make(map[string]*x509.Certificate)
    certCacheLock sync.RWMutex
)

func getIntermediateCert(serverName string) (*x509.Certificate, error) {
    certCacheLock.RLock()
    if cert, ok := certCache[serverName]; ok {
        certCacheLock.RUnlock()
        return cert, nil
    }
    certCacheLock.RUnlock()
    
    // 这里可以实现从服务器获取中间证书的逻辑
    // 或者从预定义的映射中获取
    intermediatePEM := getPredefinedCert(serverName)
    
    cert, err := x509.ParseCertificate([]byte(intermediatePEM))
    if err != nil {
        return nil, err
    }
    
    certCacheLock.Lock()
    certCache[serverName] = cert
    certCacheLock.Unlock()
    
    return cert, nil
}

func createDynamicTLSConfig(serverName string) (*tls.Config, error) {
    intermediateCert, err := getIntermediateCert(serverName)
    if err != nil {
        return nil, err
    }
    
    rootCAs, _ := x509.SystemCertPool()
    if rootCAs == nil {
        rootCAs = x509.NewCertPool()
    }
    
    // 将中间证书添加到池中
    rootCAs.AddCert(intermediateCert)
    
    return &tls.Config{
        RootCAs:            rootCAs,
        ServerName:         serverName,
        InsecureSkipVerify: false,
    }, nil
}

解决方案4:针对Android平台的特定处理

// +build android

package main

import (
    "crypto/tls"
    "crypto/x509"
)

func createAndroidTLSConfig() *tls.Config {
    // Android系统证书池可能不完整,手动添加所需证书
    certPool := x509.NewCertPool()
    
    // 添加USERTrust RSA根证书
    certPool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIF...USERTrust根证书...Q==
-----END CERTIFICATE-----`))
    
    // 添加TI Trust Technologies中间证书
    certPool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIF...TI Trust OV CA证书...Q==
-----END CERTIFICATE-----`))
    
    return &tls.Config{
        RootCAs:            certPool,
        ServerName:         "smtp.tim.it",
        InsecureSkipVerify: false,
    }
}

完整示例:处理smtp.tim.it证书

package main

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

func main() {
    // 连接到SMTP服务器
    conn, err := net.Dial("tcp", "smtp.tim.it:587")
    if err != nil {
        panic(err)
    }
    defer conn.Close()
    
    // 创建TLS配置
    tlsConfig := &tls.Config{
        ServerName: "smtp.tim.it",
        RootCAs:    createCertPool(),
    }
    
    // 执行TLS握手
    tlsConn := tls.Client(conn, tlsConfig)
    if err := tlsConn.Handshake(); err != nil {
        fmt.Printf("TLS握手失败: %v\n", err)
        // 尝试使用自定义验证
        tlsConfig.InsecureSkipVerify = true
        tlsConfig.VerifyConnection = createCustomVerifier()
        tlsConn = tls.Client(conn, tlsConfig)
        if err := tlsConn.Handshake(); err != nil {
            panic(err)
        }
    }
    
    fmt.Println("TLS连接成功建立")
}

func createCertPool() *x509.CertPool {
    pool := x509.NewCertPool()
    
    // 添加TI Trust Technologies OV CA证书
    ovCert := `-----BEGIN CERTIFICATE-----
MIIF...(实际的TI Trust Technologies OV CA证书)...Q==
-----END CERTIFICATE-----`
    
    if ok := pool.AppendCertsFromPEM([]byte(ovCert)); !ok {
        fmt.Println("警告: 无法添加中间证书")
    }
    
    return pool
}

func createCustomVerifier() func(tls.ConnectionState) error {
    return func(cs tls.ConnectionState) error {
        opts := x509.VerifyOptions{
            DNSName:       "smtp.tim.it",
            Intermediates: x509.NewCertPool(),
            Roots:         createCertPool(),
        }
        
        for _, cert := range cs.PeerCertificates[1:] {
            opts.Intermediates.AddCert(cert)
        }
        
        _, err := cs.PeerCertificates[0].Verify(opts)
        return err
    }
}

这些解决方案允许你在Android和其他平台上正确处理缺失中间证书的情况。建议使用解决方案1或2,它们提供了最灵活和安全的证书验证方式。

回到顶部