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.

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.

Appendix: 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)