nats-io/nats-server · error

missing TLS verified chains

Error message

missing TLS verified chains

What it means

GetOCSPStatus staples/validates an OCSP response from the TLS connection state. It requires the handshake to have produced verified certificate chains; if tls.ConnectionState.VerifiedChains is empty (client cert verification didn't run or failed), this error is returned.

Source

Thrown at internal/ocsp/ocsp.go:232

}

func parsePEM(t *testing.T, pemPath string) *pem.Block {
	t.Helper()
	data, err := os.ReadFile(pemPath)
	if err != nil {
		t.Fatal(err)
	}

	block, _ := pem.Decode(data)
	if block == nil {
		t.Fatalf("failed to decode PEM %s", pemPath)
	}
	return block
}

func GetOCSPStatus(s tls.ConnectionState) (*ocsp.Response, error) {
	if len(s.VerifiedChains) == 0 {
		return nil, fmt.Errorf("missing TLS verified chains")
	}
	chain := s.VerifiedChains[0]

	if got, want := len(chain), 2; got < want {
		return nil, fmt.Errorf("incomplete cert chain, got %d, want at least %d", got, want)
	}
	leaf, issuer := chain[0], chain[1]

	resp, err := ocsp.ParseResponseForCert(s.OCSPResponse, leaf, issuer)
	if err != nil {
		return nil, fmt.Errorf("failed to parse OCSP response: %w", err)
	}
	if err := resp.CheckSignatureFrom(issuer); err != nil {
		return resp, err
	}
	return resp, nil
}

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Configure the TLS listener to request and verify client certs (tls.RequireAndVerifyClientCert or equivalent) so VerifiedChains is populated
  2. Guard the call: check len(connState.VerifiedChains) > 0 before invoking GetOCSPStatus
  3. If constructing ConnectionState manually (tests), populate VerifiedChains with the parsed chain

Example fix

// before
resp, err := GetOCSPStatus(tlsConn.ConnectionState())
// after
cs := tlsConn.ConnectionState()
if len(cs.VerifiedChains) == 0 {
    return nil, errors.New("no verified chains: client cert verification did not run")
}
resp, err := GetOCSPStatus(cs)
Defensive patterns

Strategy: type-guard

Validate before calling

cs := conn.ConnectionState()
if len(cs.VerifiedChains) == 0 {
    return errors.New("no verified chains; ensure client cert verification is enabled")
}

Type guard

func hasVerifiedChains(cs tls.ConnectionState) bool {
    return len(cs.VerifiedChains) > 0
}

Try / catch

resp, err := GetOCSPStatus(cs)
if err != nil {
    if err.Error() == "missing TLS verified chains" {
        return nil, errOCSPSkipped // treat as 'no OCSP data', not fatal
    }
    return err
}

Prevention

When it happens

Trigger: Calling GetOCSPStatus with a ConnectionState where VerifiedChains is empty — e.g. the TLS session did not request/verify client certificates, verification was skipped (InsecureSkipVerify without manual chain building), or the state came from a session without peer certs.

Common situations: Server-side OCSP-stapling code run on connections where ClientAuth is NoClientCert; test harnesses constructing tls.ConnectionState manually without populating VerifiedChains; resumption paths where verification details are absent.

Understand the failure class

Related errors


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/ad35c45c3f252cf3. Report an issue: GitHub.