grpc/grpc-java · critical · CertificateException

${validationResult.getValidationDetails()} (server-provided

Error message

${validationResult.getValidationDetails()} (server-provided validation details)

What it means

When the S2A validates the peer certificate chain and returns a result other than SUCCESS, S2ATrustManager.checkPeerTrusted() throws a CertificateException whose message is the S2A-provided validation details. This is a genuine peer-certificate trust failure reported by the S2A during mTLS.

Source

Thrown at s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2ATrustManager.java:173

    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      throw new CertificateException("Failed to send request to S2A.", e);
    }
    if (resp.hasStatus() && resp.getStatus().getCode() != 0) {
      throw new CertificateException(
          String.format(
              "Error occurred in response from S2A, error code: %d, error message: %s.",
              resp.getStatus().getCode(), resp.getStatus().getDetails()));
    }

    if (!resp.hasValidatePeerCertificateChainResp()) {
      throw new CertificateException("No valid response received from S2A.");
    }

    ValidatePeerCertificateChainResp validationResult = resp.getValidatePeerCertificateChainResp();
    if (validationResult.getValidationResult()
        != ValidatePeerCertificateChainResp.ValidationResult.SUCCESS) {
      throw new CertificateException(validationResult.getValidationDetails());
    }
  }

  private static ImmutableList<ByteString> certificateChainToDerChain(X509Certificate[] chain)
      throws CertificateEncodingException {
    ImmutableList.Builder<ByteString> derChain = ImmutableList.<ByteString>builder();
    for (X509Certificate certificate : chain) {
      derChain.add(ByteString.copyFrom(certificate.getEncoded()));
    }
    return derChain.build();
  }
}

View on GitHub (pinned to 64daddc1f3)

Solutions

  1. Read getValidationDetails() in the exception message to identify the exact validation failure ( expiry, untrusted root, SAN mismatch ).
  2. Ensure the peer's certificate chain is valid and issued by a CA the S2A trusts.
  3. Update the S2A's trust-domain/root configuration if certificates were recently rotated.
  4. Verify both peers are configured with matching S2A trust domains.

Example fix

// before
// peer cert expired -> S2A rejects
// after
// renew peer certificate / propagate new roots to S2A trust config
s2a.updateTrustBundle(newRoots);
Defensive patterns

Strategy: try-catch

Validate before calling

// check peer cert basics before the handshake
for (X509Certificate cert : peerChain) {
  cert.checkValidity(); // fails fast on expired/not-yet-valid certs
}

Try / catch

try {
  trustManager.checkClientTrusted(chain, authType);
} catch (CertificateException e) {
  // message carries S2A validation details ( expired, untrusted root, SAN mismatch )
  logger.warning("Peer cert rejected by S2A: " + e.getMessage());
  throw e; // do not bypass trust validation
}

Prevention

When it happens

Trigger: checkClientTrusted/checkServerTrusted receives ValidatePeerCertificateChainResp whose ValidationResult != SUCCESS — the peer's certificate chain failed S2A-side validation, with getValidationDetails() explaining why.

Common situations: Peer presenting a cert from an untrusted CA or expired certificate; wrong trust domain configuration on the S2A; hostname/SAN mismatch in peer identity; rotated roots not yet propagated to the S2A.

Related errors


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