grpc/grpc-java · error · CertificateException

Failed to extract SPIFFE ID from peer leaf certificate

Error message

Failed to extract SPIFFE ID from peer leaf certificate

What it means

Thrown by XdsX509TrustManager.chooseDelegate when spiffeTrustMapDelegates is configured (a SPIFFE trust map with per-trust-domain trust managers) but SpiffeUtil.extractSpiffeId cannot derive a SPIFFE ID from the peer's leaf certificate — typically because the leaf has no URI SAN of the form spiffe://<trust-domain>/.... Without a SPIFFE ID the manager cannot select which trust-domain delegate should validate the chain, so the handshake fails.

Source

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

      if (serverNames != null) {
        for (SNIServerName serverName : serverNames) {
          if (serverName instanceof SNIHostName) {
            SNIHostName sniHostName = (SNIHostName) serverName;
            String hostName = sniHostName.getAsciiName();
            sniNamesToMatch.add(StringMatcher.newBuilder().setExact(hostName).build());
          }
        }
      }
    }
    return sniNamesToMatch;
  }

  private X509ExtendedTrustManager chooseDelegate(X509Certificate[] chain)
      throws CertificateException {
    if (spiffeTrustMapDelegates != null) {
      Optional<SpiffeUtil.SpiffeId> spiffeId = SpiffeUtil.extractSpiffeId(chain);
      if (!spiffeId.isPresent()) {
        throw new CertificateException("Failed to extract SPIFFE ID from peer leaf certificate");
      }
      String trustDomain = spiffeId.get().getTrustDomain();
      if (!spiffeTrustMapDelegates.containsKey(trustDomain)) {
        throw new CertificateException(String.format("Spiffe Trust Map doesn't contain trust"
            + " domain '%s' from peer leaf certificate", trustDomain));
      }
      return spiffeTrustMapDelegates.get(trustDomain);
    } else {
      return delegate;
    }
  }

  @Override
  public X509Certificate[] getAcceptedIssuers() {
    if (spiffeTrustMapDelegates != null) {
      Set<X509Certificate> result = new HashSet<>();
      for (X509ExtendedTrustManager tm: spiffeTrustMapDelegates.values()) {
        result.addAll(Arrays.asList(tm.getAcceptedIssuers()));

View on GitHub (pinned to 64daddc1f3)

Solutions

  1. Reissue the peer's certificate with a proper SPIFFE URI SAN, e.g. spiffe://<trust-domain>/ns/<namespace>/sa/<service-account>
  2. Verify the URI SAN format parses as a SPIFFE ID (scheme 'spiffe', non-empty trust domain) with `openssl x509 -text`
  3. If SPIFFE trust-map selection is not needed, remove the spiffe trust map configuration so the single default delegate is used
  4. Make sure chain[0] is the actual leaf — peers sending intermediate-first chains may break leaf extraction

Example fix

// before: cert with only DNS SAN
subjectAltName=DNS:mysvc.example.com

// after: include SPIFFE URI SAN
subjectAltName=DNS:mysvc.example.com,URI:spiffe://prod-td/ns/default/sa/mysvc
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the leaf has a parseable SPIFFE URI SAN before trusting trust-map configs
static boolean hasSpiffeUriSan(X509Certificate cert) throws CertificateParsingException {
  Collection<List<?>> sans = cert.getSubjectAlternativeNames();
  if (sans == null) return false;
  for (List<?> san : sans) {
    if ((Integer) san.get(0) == 6) { // URI
      String uri = (String) san.get(1);
      if (uri.startsWith("spiffe://") && uri.length() > "spiffe://".length()) return true;
    }
  }
  return false;
}

Try / catch

try {
  mtlsCall();
} catch (SSLHandshakeException e) {
  if (e.getCause() instanceof CertificateException
      && e.getCause().getMessage().contains("Failed to extract SPIFFE ID")) {
    log.error("Peer leaf cert lacks a SPIFFE URI SAN; reissue cert or drop spiffe trust map config");
  }
  throw e;
}

Prevention

When it happens

Trigger: checkClientTrusted/checkServerTrusted is called while a SPIFFE trust map is configured and the leaf cert (chain[0]) lacks a SPIFFE URI SAN (e.g. only DNS SANs, or a URI SAN not parseable as a SPIFFE ID).

Common situations: Mixed mesh where some workloads use non-SPIFFE certs (enterprise CA, DNS-only certs) but the gRPC client is configured with a spiffe trust map; SPIFFE ID in a non-URI SAN type; peer migrated off SPIFFE (e.g. moved to workload identity federation) while the trust-map config remains.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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