square/okhttp · critical · IOException
Denylisted peer certificate:
Error message
Denylisted peer certificate:
What it means
A custom OkHttp network interceptor (CHECK_HANDSHAKE_INTERCEPTOR) computes CertificatePinner.pin(certificate) for each peer certificate in the established TLS handshake and compares it against a hard-coded denylist (sha256/afwi3RxoMmLkuRW1l7QsPZTJPwDS2jXw8ig=). On a match it throws java.io.IOException("Denylisted peer certificate: " + pin), aborting the call inside the interceptor chain. This is a deliberate security policy (e.g. revoking a specific compromised cert) implemented in user code, not an OkHttp built-in.
Source
Thrown at samples/guide/src/main/java/okhttp3/recipes/CheckHandshake.java:38
import java.util.Collections;
import java.util.Set;
import okhttp3.CertificatePinner;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public final class CheckHandshake {
/** Rejects otherwise-trusted certificates. */
private static final Interceptor CHECK_HANDSHAKE_INTERCEPTOR = new Interceptor() {
final Set<String> denylist = Collections.singleton(
"sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=");
@Override public Response intercept(Chain chain) throws IOException {
for (Certificate certificate : chain.connection().handshake().peerCertificates()) {
String pin = CertificatePinner.pin(certificate);
if (denylist.contains(pin)) {
throw new IOException("Denylisted peer certificate: " + pin);
}
}
return chain.proceed(chain.request());
}
};
private final OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(CHECK_HANDSHAKE_INTERCEPTOR)
.build();
public void run() throws Exception {
Request request = new Request.Builder()
.url("https://publicobject.com/helloworld.txt")
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
View on GitHub (pinned to 4fc0831380)
Solutions
- Confirm whether the denylist entry is still intended — review the sha256 constant against current revocation needs.
- If the cert rotation is legitimate, remove or update the denylisted pin.
- Distinguish this IOException by message prefix in your catch block so denylist rejections are reported as security events, not generic failures.
- Prefer OkHttp's CertificatePinner for allow-listing instead of a hand-rolled denylist where possible.
Example fix
// before
if (denylist.contains(pin)) {
throw new IOException("Denylisted peer certificate: " + pin);
}
// after — typed exception + structured logging
if (denylist.contains(pin)) {
throw new DenylistedCertificateException(pin, chain.connection().route());
}
// caller:
try {
client.newCall(request).execute();
} catch (DenylistedCertificateException e) {
securityLog.warn("Refused denylisted cert pin={} host={}", e.pin, e.route.address().url().host());
} Defensive patterns
Strategy: try-catch
Validate before calling
// Before making the call, confirm the expected peer cert is NOT denylisted
String expectedPin = "sha256/<expected-current-cert-spki>";
if (Set.of("sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=").contains(expectedPin)) {
throw new IllegalStateException("Configured cert matches a denylisted pin; aborting before request");
} Try / catch
try {
Response response = client.newCall(request).execute();
} catch (IOException e) {
if (e.getMessage() != null && e.getMessage().startsWith("Denylisted peer certificate:")) {
String pin = e.getMessage().substring(e.getMessage().indexOf(':') + 1).trim();
securityLogger.warn("Denylisted cert presented pin={} host={}", pin, request.url().host());
throw new SecurityPolicyException("Denylisted certificate", e);
}
throw e;
} Prevention
- Maintain the denylist explicitly; review entries when upstream rotates certs.
- Use a typed exception (subclass of IOException) so callers can distinguish policy rejections from transport errors.
- Log the matched pin and host as a security event.
- Consider allow-listing via CertificatePinner instead of deny-listing when feasible.
When it happens
Trigger: Any client.newCall(...).execute() through this client when the server's leaf certificate SPKI hash equals the denylisted value. Concretely: the server presents the revoked cert, the TLS handshake completes (cert is otherwise trusted), the interceptor runs, computes the pin, matches the denylist, and throws before chain.proceed().
Common situations: Intentionally triggered during a security test that points the client at a server using the revoked cert; accidentally triggered when the upstream legitimately rotated to a cert whose SPKI happens to match the denylisted hash (collision is astronomically unlikely but the denylist is meant to be maintained); deploying this interceptor against a CDN whose cert chain changes per-PoP.
Related errors
AI-assisted analysis of square/okhttp@4fc0831380 (2026-08-04).
Data as JSON: /data/errors/e2590c0f9858a099.json.
Report an issue: GitHub.