Skip to content

web: fix panic when client_allowed_sans is used with an optional client certificate - #436

Open
mrueg wants to merge 1 commit into
prometheus:masterfrom
mrueg:fix/verifypeercertificate-empty-rawcerts
Open

web: fix panic when client_allowed_sans is used with an optional client certificate#436
mrueg wants to merge 1 commit into
prometheus:masterfrom
mrueg:fix/verifypeercertificate-empty-rawcerts

Conversation

@mrueg

@mrueg mrueg commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

TLSConfig.VerifyPeerCertificate indexes rawCerts[0] without checking the length:

func (t *TLSConfig) VerifyPeerCertificate(rawCerts [][]byte, _ [][]*x509.Certificate) error {
	// sender cert comes first, see https://www.rfc-editor.org/rfc/rfc5246#section-7.4.2
	cert, err := x509.ParseCertificate(rawCerts[0])

crypto/tls invokes VerifyPeerCertificate even when the client sent no certificate, as long as the configured client auth type does not require one. In processCertsFromClient the "client didn't provide a certificate" bail-out is guarded by requiresClientCert(c.config.ClientAuth), and control still reaches the unconditional callback at the end of the function with an empty slice.

So this configuration panics for every client that connects without a certificate:

tls_server_config:
  cert_file: "server.crt"
  key_file: "server.key"
  client_auth_type: "VerifyClientCertIfGiven"   # or RequestClientCert
  client_ca_file: "client_ca.pem"
  client_allowed_sans:
    - "one"

net/http's per-connection recover catches it, so the process survives, but the connection is aborted with a stack trace in the log rather than a clean bad certificate alert.

Changes

  1. Return an error instead of panicking when rawCerts is empty.
  2. Reject the combination at configuration load time. client_allowed_sans is only meaningful when the client is required to present a certificate — and with NoClientCert the callback is never invoked at all, so the configured SANs were silently ignored. That silent-bypass case seems worth failing loudly on, but it is the one behavioural change here, so happy to drop it to just (1) if you'd rather not reject configs that start today.

Docs updated to state the constraint.

Testing

Every existing client_allowed_sans fixture uses RequireAndVerifyClientCert, which is why this went unnoticed. Added a unit test for the nil/empty slice, a real TLS handshake test where the client presents no certificate, and two config fixtures for the rejected combinations. Reverting the guard makes the handshake test fail with index out of range [0] with length 0.

…nt cert

VerifyPeerCertificate indexed rawCerts[0] without checking the length.
crypto/tls calls VerifyPeerCertificate even when the client sent no
certificate, as long as the client auth type does not require one, so
combining client_allowed_sans with RequestClientCert or
VerifyClientCertIfGiven panicked on every certificate-less client. The
panic is recovered by net/http's per-connection handler, so the process
survives, but the connection is aborted with a stack trace in the log
instead of a clean TLS alert.

Return an error for an empty rawCerts slice, and reject the combination at
configuration load time: client_allowed_sans is only meaningful when the
client is required to present a certificate. With NoClientCert the callback
is never invoked at all, so the configured SANs were silently ignored,
which is the more dangerous half of this.

All existing client_allowed_sans test fixtures use RequireAndVerifyClientCert,
which is why this went unnoticed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Manuel Rüger <manuel@rueg.eu>
@mrueg
mrueg force-pushed the fix/verifypeercertificate-empty-rawcerts branch from 2672aae to 0089e78 Compare September 2, 2026 11:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant