nats-io/nats-server · error

incomplete cert chain, got %d, want at least %d

Error message

incomplete cert chain, got %d, want at least %d

What it means

GetOCSPStatus requires at least a leaf and its issuer in the first verified chain to check the OCSP response signature. If the chain has fewer than 2 certificates, validation cannot proceed and this error reports the actual length found.

Source

Thrown at internal/ocsp/ocsp.go:237

	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
}

func SetOCSPStatus(t *testing.T, ocspURL, certPEM string, status int) {
	t.Helper()

	cert := parseCertPEM(t, certPEM)

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Ensure the client sends (or the server's cert pool supplies) the issuer certificate so the chain has at least leaf + issuer
  2. If using self-signed test certs, build a two-cert chain (root CA + leaf) instead of a single self-signed leaf
  3. Guard with a length check on VerifiedChains[0] before calling GetOCSPStatus

Example fix

// before
chain := [][]*x509.Certificate{{leaf}}
// after
chain := [][]*x509.Certificate{{leaf, caCert}}
Defensive patterns

Strategy: type-guard

Validate before calling

cs := conn.ConnectionState()
if len(cs.VerifiedChains) == 0 || len(cs.VerifiedChains[0]) < 2 {
    return errors.New("chain too short for OCSP check (need leaf + issuer)")
}

Type guard

func hasLeafAndIssuer(cs tls.ConnectionState) bool {
    return len(cs.VerifiedChains) > 0 && len(cs.VerifiedChains[0]) >= 2
}

Try / catch

resp, err := GetOCSPStatus(cs)
if err != nil {
    if strings.HasPrefix(err.Error(), "incomplete cert chain") {
        return nil, errOCSPNotPossible
    }
    return err
}

Prevention

When it happens

Trigger: Calling GetOCSPStatus with a ConnectionState whose VerifiedChains[0] contains only the leaf cert (len < 2) — e.g. a self-signed or directly trusted leaf with no issuer appended.

Common situations: Client presenting a self-signed certificate trusted as a root; minimal test chains built with only one cert; RootCAs pool configured so only the leaf verifies without issuer in chain.

Related errors


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