square/okhttp · critical · IOException
Denylisted peer certificate:
Error message
Denylisted peer certificate:
What it means
Thrown by a custom network interceptor in the CheckHandshake recipe: `throw new IOException("Denylisted peer certificate: " + pin)`. It computes the SHA-256 pin of each peer certificate and throws if it matches the hardcoded denylist entry 'sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig='. This is an application-level security control: the certificate is otherwise fully trusted by the JVM, but the app refuses it. Because it is thrown inside a network interceptor, it aborts the call as a transport-level IOException (surfaces via onFailure / execute throws).
Solutions
- Confirm the denylisted pin is intentional; remove or update it if the server legitimately rotated its certificate.
- Load the denylist from configuration rather than a hardcoded constant so it can be updated without recompiling.
- Re-derive the current server pin from a trusted connection and compare it against the denylist to see if it really matches.
- Treat this IOException as a security event: log/alert, do not silently retry against the same host.
Example fix
// before
if (denylist.contains(pin)) {
throw new IOException("Denylisted peer certificate: " + pin);
}
// after
if (denylist.contains(pin)) {
SecurityLog.alert("denylisted cert presented by " + chain.request().url().host() + ": " + pin);
throw new IOException("Denylisted peer certificate: " + pin);
} Defensive patterns
Strategy: try-catch
Validate before calling
// You cannot 'prevent' a denylist hit at call time; the interceptor is the guard. // Pre-check by not putting legitimate current server pins in the denylist. // Validate the denylist is current before building the client.
Type guard
static boolean isDenylisted(String pin, Set<String> denylist) { return denylist.contains(pin); } Try / catch
try {
// call
} catch (IOException e) {
if (e.getMessage() != null && e.getMessage().startsWith("Denylisted peer certificate")) {
// security event: log/alert, do NOT retry the same host blindly
securityMonitor.onDenylistedCert(e.getMessage());
} else {
// other transport/HTTP error
}
} Prevention
- Treat a denylist hit as a security incident, not a transient error.
- Load the denylist from configuration so it can be updated without recompiling.
- Never put a legitimate current server pin in the denylist by mistake.
- Do not silently retry against the same host after a denylist throw.
When it happens
Trigger: The server's leaf certificate pin exactly equals the denylisted value. Happens when an attacker/MITM presents a stolen-but-valid cert that you have explicitly blocklisted, or when the legitimate server rotated to a cert whose pin collides with the denylist (extremely unlikely with SHA-256), or when the denylist entry was added for testing and left in.
Common situations: Hardcoding a sample denylist pin and forgetting to remove it; a CA compromise where you must block a specific cert while trusting the CA; testing the interceptor with a pinned mock server.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
AI-assisted analysis of square/okhttp@91a8b34c6f (2026-08-10).
Data as JSON: /api/errors/e2590c0f9858a099.
Report an issue: GitHub.
Appendix: 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 91a8b34c6f)