grpc/grpc-java · error · CertificateException

Peer certificate SAN check failed

Error message

Peer certificate SAN check failed

What it means

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.

Source

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

    if (altNameType == null) {
      throw new CertificateParsingException("Invalid SAN entry: null altNameType");
    }
    switch (altNameType) {
      case ALT_DNS_NAME:
      case ALT_URI_NAME:
      case ALT_IPA_NAME:
        return verifyDnsNameInSanList((String) entry.get(1), verifySanList);
      default:
        return false;
    }
  }

  // logic from Envoy::Extensions::TransportSockets::Tls::ContextImpl::verifySubjectAltName
  private static void verifySubjectAltNameInLeaf(
      X509Certificate cert, List<StringMatcher> verifyList) throws CertificateException {
    Collection<List<?>> names = cert.getSubjectAlternativeNames();
    if (names == null || names.isEmpty()) {
      throw new CertificateException("Peer certificate SAN check failed");
    }
    for (List<?> name : names) {
      if (verifyOneSanInList(name, verifyList)) {
        return;
      }
    }
    // 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) {

View on GitHub (pinned to 64daddc1f3)

Solutions

  1. 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
  2. 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
  3. Verify the correct leaf cert is being presented (peerCertChain[0]) — check the peer's serving/admin cert selection
  4. Inspect the peer cert with `openssl x509 -in cert.pem -text` and confirm an 'X509v3 Subject Alternative Name' section exists

Example fix

// before: certificate generated with only CN
generate-cert --cn mysvc.example.com

// after: include SAN (openssl example)
openssl req -new -x509 -subj "/CN=mysvc.example.com" \
  -addext "subjectAltName=DNS:mysvc.example.com"
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check a peer cert before handshake/config acceptance
static boolean hasSanEntries(X509Certificate cert) throws CertificateParsingException {
  Collection<List<?>> sans = cert.getSubjectAlternativeNames();
  return sans != null && !sans.isEmpty();
}

Type guard

static boolean hasSans(X509Certificate c) {
  try {
    Collection<List<?>> s = c.getSubjectAlternativeNames();
    return s != null && !s.isEmpty();
  } catch (CertificateParsingException e) {
    return false;
  }
}

Try / catch

try {
  tlsHandshake();
} catch (SSLHandshakeException e) {
  if (e.getCause() instanceof CertificateException
      && e.getCause().getMessage().contains("SAN check failed")) {
    log.error("Peer cert has no SANs matching configured matchers; reissue cert or fix match_subject_alt_names");
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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