@@ -84,16 +84,25 @@ func normalizeHost(host string) string {
8484 return strings .ToLower (host )
8585}
8686
87- // ClientConfig encodes the parameters for a TLS client connection.
87+ // ClientConfig holds configuration parameters used for establishing a TLS client connection.
8888type ClientConfig struct {
89- // The host name for the Server Name Indication (SNI).
89+ // ServerName specifies the hostname sent for Server Name Indication (SNI).
90+ // This is often the same as the dialed hostname but can be overridden using [WithSNI].
9091 ServerName string
91- // The hostname to use for certificate validation.
92- CertificateName string
93- // The protocol id list for protocol negotiation (ALPN).
92+
93+ // NextProtos lists the application-layer protocols (e.g., "h2", "http/1.1")
94+ // supported by the client for Application-Layer Protocol Negotiation (ALPN).
95+ // See [WithALPN].
9496 NextProtos []string
95- // The cache for sessin resumption.
97+
98+ // SessionCache enables TLS session resumption by providing a cache for session tickets.
99+ // If nil, session resumption is disabled. See [WithSessionCache].
96100 SessionCache tls.ClientSessionCache
101+
102+ // CertVerifier specifies a custom verifier for the peer's certificate chain.
103+ // If nil, [StandardCertVerifier] is used by default, validating against the dialed
104+ // server name. See [WithCertVerifier].
105+ CertVerifier CertVerifier
97106}
98107
99108// toStdConfig creates a [tls.Config] based on the configured parameters.
@@ -106,33 +115,25 @@ func (cfg *ClientConfig) toStdConfig() *tls.Config {
106115 // replacing. This will not disable VerifyConnection.
107116 InsecureSkipVerify : true ,
108117 VerifyConnection : func (cs tls.ConnectionState ) error {
109- // This replicates the logic in the standard library verification:
110- // https://cs.opensource.google/go/go/+/master:src/crypto/tls/handshake_client.go;l=982;drc=b5f87b5407916c4049a3158cc944cebfd7a883a9
111- // And the documentation example:
112- // https://pkg.go.dev/crypto/tls#example-Config-VerifyConnection
113- opts := x509.VerifyOptions {
114- DNSName : cfg .CertificateName ,
115- Intermediates : x509 .NewCertPool (),
116- }
117- for _ , cert := range cs .PeerCertificates [1 :] {
118- opts .Intermediates .AddCert (cert )
119- }
120- _ , err := cs .PeerCertificates [0 ].Verify (opts )
121- return err
118+ return cfg .CertVerifier .VerifyCertificate (& CertVerificationContext {
119+ PeerCertificates : cs .PeerCertificates ,
120+ })
122121 },
123122 }
124123}
125124
126- // ClientOption allows configuring the parameters to be used for a client TLS connection.
127- type ClientOption func (serverName string , config * ClientConfig )
128-
129125// WrapConn wraps a [transport.StreamConn] in a TLS connection.
130126func WrapConn (ctx context.Context , conn transport.StreamConn , serverName string , options ... ClientOption ) (transport.StreamConn , error ) {
131- cfg := ClientConfig {ServerName : serverName , CertificateName : serverName }
127+ cfg := ClientConfig {ServerName : serverName }
132128 normName := normalizeHost (serverName )
133129 for _ , option := range options {
134130 option (normName , & cfg )
135131 }
132+ if cfg .CertVerifier == nil {
133+ // If CertVerifier is not provided, use the default verification logic,
134+ // which validates the peer certificate against the provided serverName.
135+ cfg .CertVerifier = & StandardCertVerifier {CertificateName : serverName }
136+ }
136137 tlsConn := tls .Client (conn , cfg .toStdConfig ())
137138 err := tlsConn .HandshakeContext (ctx )
138139 if err != nil {
@@ -181,10 +182,60 @@ func WithSessionCache(sessionCache tls.ClientSessionCache) ClientOption {
181182 }
182183}
183184
184- // WithCertificateName sets the hostname to be used for the certificate cerification.
185- // If absent, defaults to the dialed hostname.
186- func WithCertificateName (hostname string ) ClientOption {
185+ // WithCertVerifier sets the verifier to be used for the certificate verification.
186+ func WithCertVerifier (verifier CertVerifier ) ClientOption {
187187 return func (_ string , config * ClientConfig ) {
188- config .CertificateName = hostname
188+ config .CertVerifier = verifier
189189 }
190190}
191+
192+ // CertVerificationContext provides connection-time context for the certificate verification.
193+ type CertVerificationContext struct {
194+ // PeerCertificates are the parsed certificates sent by the peer, in the
195+ // order in which they were sent. The first element is the leaf certificate
196+ // that the connection is verified against.
197+ //
198+ // On the client side, it can't be empty. On the server side, it can be
199+ // empty if Config.ClientAuth is not RequireAnyClientCert or
200+ // RequireAndVerifyClientCert.
201+ //
202+ // PeerCertificates and its contents should not be modified.
203+ PeerCertificates []* x509.Certificate
204+ }
205+
206+ // CertVerifier verifies peer certificates for TLS connections.
207+ type CertVerifier interface {
208+ // VerifyCertificate verified a peer certificate given the context.
209+ VerifyCertificate (info * CertVerificationContext ) error
210+ }
211+
212+ // StandardCertVerifier implements [CertVerifier] using standard TLS certificate chain verification.
213+ type StandardCertVerifier struct {
214+ // CertificateName specifies the expected DNS name (or IP address) against which
215+ // the peer's leaf certificate is verified.
216+ CertificateName string
217+ // Roots contains the set of trusted root certificate authorities.
218+ // If nil, the host's default root CAs are used for certificate chain validation.
219+ Roots * x509.CertPool
220+ }
221+
222+ // VerifyCertificate implements [CertVerifier].
223+ func (v * StandardCertVerifier ) VerifyCertificate (certContext * CertVerificationContext ) error {
224+ // This replicates the logic in the standard library verification:
225+ // https://cs.opensource.google/go/go/+/master:src/crypto/tls/handshake_client.go;l=982;drc=b5f87b5407916c4049a3158cc944cebfd7a883a9
226+ // And the documentation example:
227+ // https://pkg.go.dev/crypto/tls#example-Config-VerifyConnection
228+ opts := x509.VerifyOptions {
229+ DNSName : v .CertificateName ,
230+ Roots : v .Roots ,
231+ Intermediates : x509 .NewCertPool (),
232+ }
233+ for _ , cert := range certContext .PeerCertificates [1 :] {
234+ opts .Intermediates .AddCert (cert )
235+ }
236+ _ , err := certContext .PeerCertificates [0 ].Verify (opts )
237+ return err
238+ }
239+
240+ // ClientOption allows configuring the parameters to be used for a client TLS connection.
241+ type ClientOption func (serverName string , config * ClientConfig )
0 commit comments