{"record":{"id":"e2590c0f9858a099","repo":"square/okhttp","slug":"denylisted-peer-certificate","errorCode":null,"errorMessage":"Denylisted peer certificate: ","messagePattern":"Denylisted peer certificate: ","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"critical","filePath":"samples/guide/src/main/java/okhttp3/recipes/CheckHandshake.java","lineNumber":38,"sourceCode":"import java.util.Collections;\nimport java.util.Set;\nimport okhttp3.CertificatePinner;\nimport okhttp3.Interceptor;\nimport okhttp3.OkHttpClient;\nimport okhttp3.Request;\nimport okhttp3.Response;\n\npublic final class CheckHandshake {\n  /** Rejects otherwise-trusted certificates. */\n  private static final Interceptor CHECK_HANDSHAKE_INTERCEPTOR = new Interceptor() {\n    final Set<String> denylist = Collections.singleton(\n        \"sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=\");\n\n    @Override public Response intercept(Chain chain) throws IOException {\n      for (Certificate certificate : chain.connection().handshake().peerCertificates()) {\n        String pin = CertificatePinner.pin(certificate);\n        if (denylist.contains(pin)) {\n          throw new IOException(\"Denylisted peer certificate: \" + pin);\n        }\n      }\n      return chain.proceed(chain.request());\n    }\n  };\n\n  private final OkHttpClient client = new OkHttpClient.Builder()\n      .addNetworkInterceptor(CHECK_HANDSHAKE_INTERCEPTOR)\n      .build();\n\n  public void run() throws Exception {\n    Request request = new Request.Builder()\n        .url(\"https://publicobject.com/helloworld.txt\")\n        .build();\n\n    try (Response response = client.newCall(request).execute()) {\n      if (!response.isSuccessful()) throw new IOException(\"Unexpected code \" + response);\n","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/square/okhttp/blob/91a8b34c6f44bd28c421364f8edadc9f324dddd9/samples/guide/src/main/java/okhttp3/recipes/CheckHandshake.java#L20-L56","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nif (denylist.contains(pin)) {\n  throw new IOException(\"Denylisted peer certificate: \" + pin);\n}\n\n// after\nif (denylist.contains(pin)) {\n  SecurityLog.alert(\"denylisted cert presented by \" + chain.request().url().host() + \": \" + pin);\n  throw new IOException(\"Denylisted peer certificate: \" + pin);\n}","handlingStrategy":"try-catch","validationCode":"// You cannot 'prevent' a denylist hit at call time; the interceptor is the guard.\n// Pre-check by not putting legitimate current server pins in the denylist.\n// Validate the denylist is current before building the client.","typeGuard":"static boolean isDenylisted(String pin, Set<String> denylist) { return denylist.contains(pin); }","tryCatchPattern":"try {\n  // call\n} catch (IOException e) {\n  if (e.getMessage() != null && e.getMessage().startsWith(\"Denylisted peer certificate\")) {\n    // security event: log/alert, do NOT retry the same host blindly\n    securityMonitor.onDenylistedCert(e.getMessage());\n  } else {\n    // other transport/HTTP error\n  }\n}","preventionTips":["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."],"tags":["okhttp","interceptor","tls","certificate-pinning","security","java"],"backgroundTag":null,"analyzedSha":"91a8b34c6f44bd28c421364f8edadc9f324dddd9","analyzedAt":"2026-08-10T18:39:54.316Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}