grpc/grpc-java · error · CertificateException

Peer certificate(s) missing

Error message

Peer certificate(s) missing

What it means

Thrown by XdsX509TrustManager.verifySubjectAltNameInChain when SAN verification is configured (certContext present and the match list is non-empty) but the peer presented an empty certificate chain (null or zero-length X509Certificate[]). This is a guard before extracting the leaf cert: SAN checking cannot proceed without at least one peer certificate.

Source

Thrown at xds/src/main/java/io/grpc/xds/internal/security/trust/XdsX509TrustManager.java:226

    // at this point there's no match
    throw new CertificateException("Peer certificate SAN check failed");
  }

  /**
   * Verifies SANs in the peer cert chain against verify_subject_alt_name in the certContext.
   * This is called from various check*Trusted methods.
   */
  @VisibleForTesting
  void verifySubjectAltNameInChain(X509Certificate[] peerCertChain,
      List<StringMatcher> verifyList) throws CertificateException {
    if (certContext == null) {
      return;
    }
    if (verifyList.isEmpty()) {
      return;
    }
    if (peerCertChain == null || peerCertChain.length < 1) {
      throw new CertificateException("Peer certificate(s) missing");
    }
    // verify SANs only in the top cert (leaf cert)
    verifySubjectAltNameInLeaf(peerCertChain[0], verifyList);
  }

  @Override
  @SuppressWarnings("deprecation") // gRFC A29 predates match_typed_subject_alt_names
  public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket)
      throws CertificateException {
    chooseDelegate(chain).checkClientTrusted(chain, authType, socket);
    verifySubjectAltNameInChain(chain, certContext != null
        ? certContext.getMatchSubjectAltNamesList() : new ArrayList<>());
  }

  @Override
  @SuppressWarnings("deprecation") // gRFC A29 predates match_typed_subject_alt_names
  public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine)
      throws CertificateException {

View on GitHub (pinned to 64daddc1f3)

Solutions

  1. Ensure the peer actually sends a certificate: configure its keystore/keyCertChain and private key correctly so the TLS stack includes a client/server cert in the handshake
  2. On the server, set needClientAuth (not wantClientAuth) so clients without certs are rejected earlier with a clearer error
  3. If the peer legitimately has no certificate, remove match_subject_alt_names from the cert context so SAN checking is skipped
  4. Confirm the matching trust manager (chooseDelegate) and keystore on the peer side are loaded — a missing cert provider yields an empty chain

Example fix

// before: optional client auth allows empty chains
sslContext.getDefaultSSLParameters().setWantClientAuth(true);

// after: require a client certificate
sslContext.getDefaultSSLParameters().setNeedClientAuth(true);
Defensive patterns

Strategy: validation

Validate before calling

// Reject empty peer chains before trust-manager processing
static void requirePeerChain(X509Certificate[] chain) throws CertificateException {
  if (chain == null || chain.length == 0) {
    throw new CertificateException("Peer did not present a certificate");
  }
}

Type guard

static boolean peerChainPresent(X509Certificate[] chain) {
  return chain != null && chain.length > 0;
}

Try / catch

try {
  serverCall();
} catch (SSLHandshakeException e) {
  if (e.getCause() instanceof CertificateException
      && e.getCause().getMessage().contains("Peer certificate(s) missing")) {
    log.error("Peer sent no certificate during mTLS handshake; check client keystore/needClientAuth");
  }
  throw e;
}

Prevention

When it happens

Trigger: checkClientTrusted or checkServerTrusted is invoked by the JDK SSL stack with chain == null or chain.length == 0 while a non-empty match_subject_alt_names list is set in the cert context. In practice this happens when the peer sends no certificate (anonymous or empty client auth) yet SAN validation is expected.

Common situations: Client certificate is optional on the server but the xDS config enables SAN verification; peer's TLS layer failed to select a cert (no matching key/cert in keystore) and sent an empty chain; misconfigured keystores on the peer so it sends no cert during mTLS handshake.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08). Data as JSON: /api/errors/8fa15741e9dee45b. Report an issue: GitHub.