-
Notifications
You must be signed in to change notification settings - Fork 18.8k
Description
In #77217 (comment) we discussed how the combination of the documented behaviors of GetConfigForClient and VerifyPeerCertificate is dangerous.
GetConfigForClient is documented to share session ticket keys with the parent.
// If SessionTicketKey was explicitly set on the returned Config, or if
// SetSessionTicketKeys was called on the returned Config, those keys will
// be used. Otherwise, the original Config keys will be used (and possibly
// rotated if they are automatically managed).
GetConfigForClient func(*ClientHelloInfo) (*Config, error)
VerifyPeerCertificate is documented not to run on resumed sessions.
// This callback is not invoked on resumed connections, as certificates are
// not re-verified on resumption.
VerifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
That makes the following code work unexpectedly.
&tls.Config{
ClientAuth: tls.RequireAnyClientCert,
GetConfigForClient: func(hello *tls.ClientHelloInfo) (*tls.Config, error) {
if someCondition(hello) {
return &tls.Config{
ClientAuth: tls.RequireAnyClientCert,
VerifyPeerCertificate: customStricterVerification,
}, nil
}
return nil, nil
},
}
You would think customStricterVerification applies to all clients matching someCondition, but instead a client can resume a connection that originally didn't match someCondition but now does without passing customStricterVerification.
A similar, but less likely, issue is present on the client side if ClientSessionCache is shared across Configs with different VerifyPeerCertificate logic.
The second #77217 fix solved this for native verification by re-checking the inclusion of the root. VerifyConnection is not affected because it always runs, including on resumed connections.
I propose we fix this by disabling session resumption if VerifyPeerCertificate is set. We can ship this in Go 1.27 with a GODEBUG.
If the application wishes to use resumption, they can switch to VerifyConnection.
I am not sure if we should do this only one the server side, or also on the client side. Arguably reusing a ClientSessionCache (which is not enabled by default, unlike server-side session tickets) is a pretty explicit statement of intent?
A more heavy-handed alternative is to deprecate VerifyPeerCertificate entirely in favor of VerifyConnection. We could also break the VerifyPeerCertificate docs promise and run it also on resumed connections, but it's hard to predict what would break.
/cc @golang/security @marten-seemann @espadolini