{"record":{"id":"cb3eb27094763367","repo":"grpc/grpc-java","slug":"failed-to-extract-spiffe-id-from-peer-leaf-certifi","errorCode":null,"errorMessage":"Failed to extract SPIFFE ID from peer leaf certificate","messagePattern":"Failed to extract SPIFFE ID from peer leaf certificate","errorType":"validation","errorClass":"CertificateException","httpStatus":null,"severity":"error","filePath":"xds/src/main/java/io/grpc/xds/internal/security/trust/XdsX509TrustManager.java","lineNumber":325,"sourceCode":"      if (serverNames != null) {\n        for (SNIServerName serverName : serverNames) {\n          if (serverName instanceof SNIHostName) {\n            SNIHostName sniHostName = (SNIHostName) serverName;\n            String hostName = sniHostName.getAsciiName();\n            sniNamesToMatch.add(StringMatcher.newBuilder().setExact(hostName).build());\n          }\n        }\n      }\n    }\n    return sniNamesToMatch;\n  }\n\n  private X509ExtendedTrustManager chooseDelegate(X509Certificate[] chain)\n      throws CertificateException {\n    if (spiffeTrustMapDelegates != null) {\n      Optional<SpiffeUtil.SpiffeId> spiffeId = SpiffeUtil.extractSpiffeId(chain);\n      if (!spiffeId.isPresent()) {\n        throw new CertificateException(\"Failed to extract SPIFFE ID from peer leaf certificate\");\n      }\n      String trustDomain = spiffeId.get().getTrustDomain();\n      if (!spiffeTrustMapDelegates.containsKey(trustDomain)) {\n        throw new CertificateException(String.format(\"Spiffe Trust Map doesn't contain trust\"\n            + \" domain '%s' from peer leaf certificate\", trustDomain));\n      }\n      return spiffeTrustMapDelegates.get(trustDomain);\n    } else {\n      return delegate;\n    }\n  }\n\n  @Override\n  public X509Certificate[] getAcceptedIssuers() {\n    if (spiffeTrustMapDelegates != null) {\n      Set<X509Certificate> result = new HashSet<>();\n      for (X509ExtendedTrustManager tm: spiffeTrustMapDelegates.values()) {\n        result.addAll(Arrays.asList(tm.getAcceptedIssuers()));","sourceCodeStart":307,"sourceCodeEnd":343,"githubUrl":"https://github.com/grpc/grpc-java/blob/64daddc1f3d1975670f769f3e97bde8b2ba32d25/xds/src/main/java/io/grpc/xds/internal/security/trust/XdsX509TrustManager.java#L307-L343","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Reissue the peer's certificate with a proper SPIFFE URI SAN, e.g. spiffe://<trust-domain>/ns/<namespace>/sa/<service-account>","Verify the URI SAN format parses as a SPIFFE ID (scheme 'spiffe', non-empty trust domain) with `openssl x509 -text`","If SPIFFE trust-map selection is not needed, remove the spiffe trust map configuration so the single default delegate is used","Make sure chain[0] is the actual leaf — peers sending intermediate-first chains may break leaf extraction"],"exampleFix":"// before: cert with only DNS SAN\nsubjectAltName=DNS:mysvc.example.com\n\n// after: include SPIFFE URI SAN\nsubjectAltName=DNS:mysvc.example.com,URI:spiffe://prod-td/ns/default/sa/mysvc","handlingStrategy":"validation","validationCode":"// Confirm the leaf has a parseable SPIFFE URI SAN before trusting trust-map configs\nstatic boolean hasSpiffeUriSan(X509Certificate cert) throws CertificateParsingException {\n  Collection<List<?>> sans = cert.getSubjectAlternativeNames();\n  if (sans == null) return false;\n  for (List<?> san : sans) {\n    if ((Integer) san.get(0) == 6) { // URI\n      String uri = (String) san.get(1);\n      if (uri.startsWith(\"spiffe://\") && uri.length() > \"spiffe://\".length()) return true;\n    }\n  }\n  return false;\n}","typeGuard":null,"tryCatchPattern":"try {\n  mtlsCall();\n} catch (SSLHandshakeException e) {\n  if (e.getCause() instanceof CertificateException\n      && e.getCause().getMessage().contains(\"Failed to extract SPIFFE ID\")) {\n    log.error(\"Peer leaf cert lacks a SPIFFE URI SAN; reissue cert or drop spiffe trust map config\");\n  }\n  throw e;\n}","preventionTips":["Issue all mesh workloads certs with a spiffe:// URI SAN","Validate leaf cert format (URI SAN type 6) when certificates are rotated/reissued","Only enable the SPIFFE trust map when every peer in the mesh uses SPIFFE identities"],"tags":["mtls","spiffe","certificate-validation","xds","trust-domain"],"backgroundTag":"missing-required-argument","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"}