{"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/4fc083138014aba3d0078f5c26d1ce84815fa984/samples/guide/src/main/java/okhttp3/recipes/CheckHandshake.java#L20-L56","documentation":"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.","triggerScenarios":"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().","commonSituations":"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.","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."],"exampleFix":"// before\nif (denylist.contains(pin)) {\n  throw new IOException(\"Denylisted peer certificate: \" + pin);\n}\n\n// after — typed exception + structured logging\nif (denylist.contains(pin)) {\n  throw new DenylistedCertificateException(pin, chain.connection().route());\n}\n// caller:\ntry {\n  client.newCall(request).execute();\n} catch (DenylistedCertificateException e) {\n  securityLog.warn(\"Refused denylisted cert pin={} host={}\", e.pin, e.route.address().url().host());\n}","handlingStrategy":"try-catch","validationCode":"// Before making the call, confirm the expected peer cert is NOT denylisted\nString expectedPin = \"sha256/<expected-current-cert-spki>\";\nif (Set.of(\"sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=\").contains(expectedPin)) {\n  throw new IllegalStateException(\"Configured cert matches a denylisted pin; aborting before request\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  Response response = client.newCall(request).execute();\n} catch (IOException e) {\n  if (e.getMessage() != null && e.getMessage().startsWith(\"Denylisted peer certificate:\")) {\n    String pin = e.getMessage().substring(e.getMessage().indexOf(':') + 1).trim();\n    securityLogger.warn(\"Denylisted cert presented pin={} host={}\", pin, request.url().host());\n    throw new SecurityPolicyException(\"Denylisted certificate\", e);\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["okhttp","interceptor","tls","certificate-pinning","revocation","security","java"],"analyzedSha":"4fc083138014aba3d0078f5c26d1ce84815fa984","analyzedAt":"2026-08-04T19:09:04.639Z","schemaVersion":2}