{"record":{"id":"c73758271787c117","repo":"grpc/grpc-java","slug":"peer-certificate-san-check-failed","errorCode":null,"errorMessage":"Peer certificate SAN check failed","messagePattern":"Peer certificate SAN check failed","errorType":"validation","errorClass":"CertificateException","httpStatus":null,"severity":"error","filePath":"xds/src/main/java/io/grpc/xds/internal/security/trust/XdsX509TrustManager.java","lineNumber":201,"sourceCode":"    if (altNameType == null) {\n      throw new CertificateParsingException(\"Invalid SAN entry: null altNameType\");\n    }\n    switch (altNameType) {\n      case ALT_DNS_NAME:\n      case ALT_URI_NAME:\n      case ALT_IPA_NAME:\n        return verifyDnsNameInSanList((String) entry.get(1), verifySanList);\n      default:\n        return false;\n    }\n  }\n\n  // logic from Envoy::Extensions::TransportSockets::Tls::ContextImpl::verifySubjectAltName\n  private static void verifySubjectAltNameInLeaf(\n      X509Certificate cert, List<StringMatcher> verifyList) throws CertificateException {\n    Collection<List<?>> names = cert.getSubjectAlternativeNames();\n    if (names == null || names.isEmpty()) {\n      throw new CertificateException(\"Peer certificate SAN check failed\");\n    }\n    for (List<?> name : names) {\n      if (verifyOneSanInList(name, verifyList)) {\n        return;\n      }\n    }\n    // 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) {","sourceCodeStart":183,"sourceCodeEnd":219,"githubUrl":"https://github.com/grpc/grpc-java/blob/64daddc1f3d1975670f769f3e97bde8b2ba32d25/xds/src/main/java/io/grpc/xds/internal/security/trust/XdsX509TrustManager.java#L183-L219","documentation":"Thrown by XdsX509TrustManager.verifySubjectAltNameInLeaf when the peer's leaf certificate has no Subject Alternative Name extension at all (getSubjectAlternativeNames() returns null or an empty collection), while verify_subject_alt_name matchers are configured in the xDS cert context. The library mirrors Envoy's ContextImpl.verifySubjectAltName logic: when SAN matchers are configured, a peer cert without any SANs cannot be authenticated, so the TLS handshake fails with this CertificateException.","triggerScenarios":"During checkClientTrusted/checkServerTrusted, after the delegate trust manager passes chain validation, verifySubjectAltNameInChain is called with a non-empty match list and the leaf cert (peerCertChain[0]) exposes no SAN entries whatsoever.","commonSituations":"Peer certificates issued by an internal CA or tooling (e.g. old OpenSSL configs, cert-manager without SAN templates) that only use the deprecated CN field instead of SANs; mTLS between gRPC xDS services where one side was issued a legacy cert; certificates regenerated by an upgraded issuer that dropped the SAN extension.","solutions":["Reissue the peer certificate so it includes at least one Subject Alternative Name (DNS, URI, or IP) matching the configured verify_subject_alt_name matchers","If SAN verification is not required, remove the match_subject_alt_names / verify_subject_alt_name entries from the xDS cert context so verifyList is empty","Verify the correct leaf cert is being presented (peerCertChain[0]) — check the peer's serving/admin cert selection","Inspect the peer cert with `openssl x509 -in cert.pem -text` and confirm an 'X509v3 Subject Alternative Name' section exists"],"exampleFix":"// before: certificate generated with only CN\ngenerate-cert --cn mysvc.example.com\n\n// after: include SAN (openssl example)\nopenssl req -new -x509 -subj \"/CN=mysvc.example.com\" \\\n  -addext \"subjectAltName=DNS:mysvc.example.com\"","handlingStrategy":"validation","validationCode":"// Pre-check a peer cert before handshake/config acceptance\nstatic boolean hasSanEntries(X509Certificate cert) throws CertificateParsingException {\n  Collection<List<?>> sans = cert.getSubjectAlternativeNames();\n  return sans != null && !sans.isEmpty();\n}","typeGuard":"static boolean hasSans(X509Certificate c) {\n  try {\n    Collection<List<?>> s = c.getSubjectAlternativeNames();\n    return s != null && !s.isEmpty();\n  } catch (CertificateParsingException e) {\n    return false;\n  }\n}","tryCatchPattern":"try {\n  tlsHandshake();\n} catch (SSLHandshakeException e) {\n  if (e.getCause() instanceof CertificateException\n      && e.getCause().getMessage().contains(\"SAN check failed\")) {\n    log.error(\"Peer cert has no SANs matching configured matchers; reissue cert or fix match_subject_alt_names\");\n  }\n  throw e;\n}","preventionTips":["Always generate certs with subjectAltName; never rely on the deprecated CN field","Add a CI check that fails when issued certs lack SAN entries","Keep match_subject_alt_names in sync with the certs your CA actually issues"],"tags":["mtls","certificate-validation","xds","spiffe","san"],"backgroundTag":"schema-validation-failed","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"}