{"record":{"id":"8fa15741e9dee45b","repo":"grpc/grpc-java","slug":"peer-certificate-s-missing","errorCode":null,"errorMessage":"Peer certificate(s) missing","messagePattern":"Peer certificate\\(s\\) missing","errorType":"validation","errorClass":"CertificateException","httpStatus":null,"severity":"error","filePath":"xds/src/main/java/io/grpc/xds/internal/security/trust/XdsX509TrustManager.java","lineNumber":226,"sourceCode":"    // at this point there's no match\n    throw new CertificateException(\"Peer certificate SAN check failed\");\n  }\n\n  /**\n   * Verifies SANs in the peer cert chain against verify_subject_alt_name in the certContext.\n   * This is called from various check*Trusted methods.\n   */\n  @VisibleForTesting\n  void verifySubjectAltNameInChain(X509Certificate[] peerCertChain,\n      List<StringMatcher> verifyList) throws CertificateException {\n    if (certContext == null) {\n      return;\n    }\n    if (verifyList.isEmpty()) {\n      return;\n    }\n    if (peerCertChain == null || peerCertChain.length < 1) {\n      throw new CertificateException(\"Peer certificate(s) missing\");\n    }\n    // verify SANs only in the top cert (leaf cert)\n    verifySubjectAltNameInLeaf(peerCertChain[0], verifyList);\n  }\n\n  @Override\n  @SuppressWarnings(\"deprecation\") // gRFC A29 predates match_typed_subject_alt_names\n  public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket)\n      throws CertificateException {\n    chooseDelegate(chain).checkClientTrusted(chain, authType, socket);\n    verifySubjectAltNameInChain(chain, certContext != null\n        ? certContext.getMatchSubjectAltNamesList() : new ArrayList<>());\n  }\n\n  @Override\n  @SuppressWarnings(\"deprecation\") // gRFC A29 predates match_typed_subject_alt_names\n  public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine)\n      throws CertificateException {","sourceCodeStart":208,"sourceCodeEnd":244,"githubUrl":"https://github.com/grpc/grpc-java/blob/64daddc1f3d1975670f769f3e97bde8b2ba32d25/xds/src/main/java/io/grpc/xds/internal/security/trust/XdsX509TrustManager.java#L208-L244","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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","On the server, set needClientAuth (not wantClientAuth) so clients without certs are rejected earlier with a clearer error","If the peer legitimately has no certificate, remove match_subject_alt_names from the cert context so SAN checking is skipped","Confirm the matching trust manager (chooseDelegate) and keystore on the peer side are loaded — a missing cert provider yields an empty chain"],"exampleFix":"// before: optional client auth allows empty chains\nsslContext.getDefaultSSLParameters().setWantClientAuth(true);\n\n// after: require a client certificate\nsslContext.getDefaultSSLParameters().setNeedClientAuth(true);","handlingStrategy":"validation","validationCode":"// Reject empty peer chains before trust-manager processing\nstatic void requirePeerChain(X509Certificate[] chain) throws CertificateException {\n  if (chain == null || chain.length == 0) {\n    throw new CertificateException(\"Peer did not present a certificate\");\n  }\n}","typeGuard":"static boolean peerChainPresent(X509Certificate[] chain) {\n  return chain != null && chain.length > 0;\n}","tryCatchPattern":"try {\n  serverCall();\n} catch (SSLHandshakeException e) {\n  if (e.getCause() instanceof CertificateException\n      && e.getCause().getMessage().contains(\"Peer certificate(s) missing\")) {\n    log.error(\"Peer sent no certificate during mTLS handshake; check client keystore/needClientAuth\");\n  }\n  throw e;\n}","preventionTips":["Use setNeedClientAuth(true) on servers requiring mTLS so empty chains fail early","Verify the peer's keystore contains a valid key + cert chain at startup (health check)","Enable javax.net.debug=ssl:handshake to see whether a cert is actually sent"],"tags":["mtls","certificate-validation","xds","empty-certificate-chain"],"backgroundTag":"empty-required-field","analyzedSha":"64daddc1f3d1975670f769f3e97bde8b2ba32d25","analyzedAt":"2026-09-08T06:14:57.704Z","contentChangedAt":"2026-09-08T06:14:57.704Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}