grpc/grpc-java · error · IllegalArgumentException

Certificate can't be parsed. Certificate loading for trust d

Error message

Certificate can't be parsed. Certificate loading for trust domain '%s' failed.

What it means

SpiffeUtil.extractCert decodes the base64 x5c certificate of each JWK and parses it as X.509 via CertificateFactory. If parsing throws CertificateException (corrupt DER, wrong encoding, non-certificate bytes), it is wrapped in this IllegalArgumentException with the trust domain name.

Source

Thrown at core/src/main/java/io/grpc/internal/SpiffeUtil.java:247

      String trustDomainName) {
    List<X509Certificate> result = new ArrayList<>();
    for (Map<String, ?> keyNode : keysNode) {
      checkJwkEntry(keyNode, trustDomainName);
      List<String> rawCerts = JsonUtil.getListOfStrings(keyNode, "x5c");
      if (rawCerts == null || rawCerts.isEmpty()) {
        continue;
      }
      InputStream stream = new ByteArrayInputStream((CERTIFICATE_PREFIX + rawCerts.get(0) + "\n"
          + CERTIFICATE_SUFFIX)
          .getBytes(StandardCharsets.UTF_8));
      try {
        Collection<? extends Certificate> certs = CertificateFactory.getInstance("X509")
            .generateCertificates(stream);
        X509Certificate[] certsArray = certs.toArray(new X509Certificate[0]);
        assert certsArray.length == 1;
        result.add(certsArray[0]);
      } catch (CertificateException e) {
        throw new IllegalArgumentException(String.format("Certificate can't be parsed. Certificate "
            + "loading for trust domain '%s' failed.", trustDomainName), e);
      }
    }
    return result;
  }

  /**
   * Represents a SPIFFE ID as defined in the SPIFFE standard.
   * @see <a href="https://github.com/spiffe/spiffe/blob/master/standards/SPIFFE-ID.md">Standard</a>
   */
  public static class SpiffeId {

    private final String trustDomain;
    private final String path;

    private SpiffeId(String trustDomain, String path) {
      this.trustDomain = trustDomain;
      this.path = path;

View on GitHub (pinned to 64daddc1f3)

Solutions

  1. Ensure x5c contains base64 DER only: strip PEM headers/footers and keep the raw base64 body
  2. Verify the cert independently: openssl x509 -inform DER -in cert.der -text
  3. Regenerate the bundle from SPIRE rather than hand-copying certificates
  4. Confirm the file was not truncated or line-wrapped by editors/transfer tools

Example fix

// before
"x5c": ["-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----"]
// after
"x5c": ["MIIBdzCCAV2gAwIBAgIUP...base64-DER-only..."]
Defensive patterns

Strategy: try-catch

Validate before calling

// Decode and sanity-check x5c before loading
byte[] der = java.util.Base64.getMimeDecoder().decode((String) jwk.get("x5c").get(0));
try {
  new javax.crypto.spec.X509EncodedKeySpec(der); // basic length sanity
  java.security.cert.CertificateFactory.getInstance("X509")
      .generateCertificate(new java.io.ByteArrayInputStream(der));
} catch (Exception e) {
  throw new IllegalStateException("x5c entry is not a parseable X.509 cert", e);
}

Try / catch

try {
  certs = SpiffeUtil.loadTrustBundleFromFile(bundlePath);
} catch (IllegalArgumentException e) {
  log.error("Unparseable certificate in trust bundle: " + e.getMessage(), e.getCause());
  throw new TrustBundleLoadException(e);
}

Prevention

When it happens

Trigger: A trust bundle JWK's x5c entry contains bytes that are not a valid X.509 certificate — truncated base64, a PEM header pasted into x5c, or an unrelated key blob — during loadTrustBundleFromFile.

Common situations: Manually assembled bundles where a PEM string (-----BEGIN CERTIFICATE-----) was placed in x5c instead of the base64 DER body; damaged files transferred via copy/paste or mis-encoded downloads.

Understand the failure class

Related errors


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