Skip to content

Commit 768525c

Browse files
Copilotshueybubbles
andcommitted
Change certificate validation to byte comparison like Microsoft.Data.SqlClient
Instead of validating certificate chain/expiry/subject, now simply compares the raw bytes of the server certificate with the provided certificate file. This matches the behavior of Microsoft.Data.SqlClient and eliminates the need for complete certificate chains. Co-authored-by: shueybubbles <[email protected]>
1 parent 99b21d1 commit 768525c

File tree

2 files changed

+59
-44
lines changed

2 files changed

+59
-44
lines changed

msdsn/conn_str_go115.go

Lines changed: 25 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
package msdsn
55

66
import (
7+
"bytes"
78
"crypto/tls"
89
"crypto/x509"
10+
"encoding/pem"
911
"fmt"
1012
)
1113

@@ -70,54 +72,40 @@ func setupTLSCommonName(config *tls.Config, pem []byte) error {
7072
return nil
7173
}
7274

73-
// setupTLSCertificateOnly validates the certificate chain without checking the hostname
74-
func setupTLSCertificateOnly(config *tls.Config, pem []byte) error {
75-
// To skip hostname validation while still validating the certificate chain,
76-
// we must use InsecureSkipVerify=true with VerifyPeerCertificate callback.
77-
// VerifyConnection runs AFTER standard verification (including hostname check),
78-
// so it cannot be used to skip hostname validation. VerifyPeerCertificate runs
79-
// when InsecureSkipVerify=true and allows us to perform custom verification.
80-
//
81-
// Security note: This is safe because VerifyPeerCertificate performs full
82-
// certificate chain validation against the user-provided CA. Only hostname
83-
// verification is skipped, which is the intended behavior.
75+
// setupTLSCertificateOnly validates that the server certificate matches the provided certificate
76+
func setupTLSCertificateOnly(config *tls.Config, pemData []byte) error {
77+
// To match the behavior of Microsoft.Data.SqlClient, we simply compare the raw bytes
78+
// of the server's certificate with the provided certificate file. This approach:
79+
// - Does not validate certificate chain, expiry, or subject
80+
// - Only checks that the server's certificate exactly matches the provided certificate
81+
// - Skips hostname validation (which is the intended behavior)
82+
//
83+
// We use InsecureSkipVerify=true with VerifyPeerCertificate callback because
84+
// VerifyConnection runs AFTER standard verification (including hostname check).
8485

85-
// Create a certificate pool with the provided certificate as the root CA
86-
roots := x509.NewCertPool()
87-
roots.AppendCertsFromPEM(pem)
86+
// Parse the expected certificate from the PEM data
87+
block, _ := pem.Decode(pemData)
88+
if block == nil {
89+
return fmt.Errorf("failed to decode PEM certificate")
90+
}
91+
// Store the raw certificate bytes (DER format) for comparison
92+
expectedCertBytes := block.Bytes
8893

8994
config.InsecureSkipVerify = true
9095
config.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
9196
if len(rawCerts) == 0 {
9297
return fmt.Errorf("no peer certificates provided")
9398
}
9499

95-
// Parse the peer certificate
96-
cert, err := x509.ParseCertificate(rawCerts[0])
97-
if err != nil {
98-
return fmt.Errorf("failed to parse certificate: %w", err)
99-
}
100+
// Compare the server's certificate bytes with the expected certificate bytes
101+
// This matches the Microsoft.Data.SqlClient behavior: just compare raw bytes
102+
serverCertBytes := rawCerts[0]
100103

101-
// Build intermediates pool from the peer certificates (excluding the first one which is the server cert)
102-
intermediates := x509.NewCertPool()
103-
if len(rawCerts) > 1 {
104-
for i := 1; i < len(rawCerts); i++ {
105-
intermediateCert, err := x509.ParseCertificate(rawCerts[i])
106-
if err != nil {
107-
return fmt.Errorf("failed to parse intermediate certificate: %w", err)
108-
}
109-
intermediates.AddCert(intermediateCert)
110-
}
104+
if !bytes.Equal(serverCertBytes, expectedCertBytes) {
105+
return fmt.Errorf("server certificate doesn't match the provided certificate")
111106
}
112107

113-
// Verify the certificate chain against the provided root CA
114-
// Note: We do NOT set DNSName here - that's intentional to skip hostname verification
115-
opts := x509.VerifyOptions{
116-
Roots: roots,
117-
Intermediates: intermediates,
118-
}
119-
_, err = cert.Verify(opts)
120-
return err
108+
return nil
121109
}
122110
return nil
123111
}

msdsn/conn_str_go115pre.go

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,46 @@
33

44
package msdsn
55

6-
import "crypto/tls"
6+
import (
7+
"bytes"
8+
"crypto/tls"
9+
"encoding/pem"
10+
"fmt"
11+
)
712

8-
func setupTLSCommonName(config *tls.Config, pem []byte) error {
13+
func setupTLSCommonName(config *tls.Config, pemData []byte) error {
914
// Prior to Go 1.15, the TLS allowed ":" when checking the hostname.
1015
// See https://golang.org/issue/40748 for details.
1116
return skipSetup
1217
}
1318

14-
// setupTLSCertificateOnly validates the certificate chain without checking the hostname
15-
func setupTLSCertificateOnly(config *tls.Config, pem []byte) error {
16-
// Prior to Go 1.15, we don't have VerifyConnection callback.
17-
// We must use InsecureSkipVerify=true to skip hostname validation.
18-
// The certificate will still be verified against RootCAs (set in SetupTLS after this function).
19+
// setupTLSCertificateOnly validates that the server certificate matches the provided certificate
20+
func setupTLSCertificateOnly(config *tls.Config, pemData []byte) error {
21+
// To match the behavior of Microsoft.Data.SqlClient, we simply compare the raw bytes
22+
// of the server's certificate with the provided certificate file.
23+
24+
// Parse the expected certificate from the PEM data
25+
block, _ := pem.Decode(pemData)
26+
if block == nil {
27+
return fmt.Errorf("failed to decode PEM certificate")
28+
}
29+
// Store the raw certificate bytes (DER format) for comparison
30+
expectedCertBytes := block.Bytes
31+
1932
config.InsecureSkipVerify = true
33+
config.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
34+
if len(rawCerts) == 0 {
35+
return fmt.Errorf("no peer certificates provided")
36+
}
37+
38+
// Compare the server's certificate bytes with the expected certificate bytes
39+
serverCertBytes := rawCerts[0]
40+
41+
if !bytes.Equal(serverCertBytes, expectedCertBytes) {
42+
return fmt.Errorf("server certificate doesn't match the provided certificate")
43+
}
44+
45+
return nil
46+
}
2047
return nil
2148
}

0 commit comments

Comments
 (0)