|
4 | 4 | package msdsn |
5 | 5 |
|
6 | 6 | import ( |
| 7 | + "bytes" |
7 | 8 | "crypto/tls" |
8 | 9 | "crypto/x509" |
| 10 | + "encoding/pem" |
9 | 11 | "fmt" |
10 | 12 | ) |
11 | 13 |
|
@@ -70,54 +72,40 @@ func setupTLSCommonName(config *tls.Config, pem []byte) error { |
70 | 72 | return nil |
71 | 73 | } |
72 | 74 |
|
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). |
84 | 85 |
|
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 |
88 | 93 |
|
89 | 94 | config.InsecureSkipVerify = true |
90 | 95 | config.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { |
91 | 96 | if len(rawCerts) == 0 { |
92 | 97 | return fmt.Errorf("no peer certificates provided") |
93 | 98 | } |
94 | 99 |
|
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] |
100 | 103 |
|
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") |
111 | 106 | } |
112 | 107 |
|
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 |
121 | 109 | } |
122 | 110 | return nil |
123 | 111 | } |
0 commit comments