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

  1. Confirm the denylisted pin is intentional; remove or update it if the server legitimately rotated its certificate.
  2. Load the denylist from configuration rather than a hardcoded constant so it can be updated without recompiling.
  3. Re-derive the current server pin from a trusted connection and compare it against the denylist to see if it really matches.
  4. 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

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

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)