Skip to content

crypto/tls: disable session resumption when VerifyPeerCertificate is in use #77377

@FiloSottile

Description

@FiloSottile

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    LibraryProposalIssues describing a requested change to the Go standard library or x/ libraries, but not to a toolNeedsDecisionFeedback is required from experts, contributors, and/or the community before a change can be made.Security

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions