Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8299856

SSLHandshakeException is sometimes not set as cause of an IOException

    XMLWordPrintable

Details

    • generic
    • generic

    Description

      ADDITIONAL SYSTEM INFORMATION :
      Windows 10 x64 / Java 17.0.5.8.1 (corretto)

      A DESCRIPTION OF THE PROBLEM :
      Since we are using the new Java 11 HttpClient the cause (SSLHandshakeException) of an IOException is not allways set correctly. This only occurs with requests using a BodyPublisher, otherwise the cause is allways set to the SSLHandshakeException.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      run the provided code

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The SSLHandshakeException should allways be set as root cause of the IOException
      ACTUAL -
      Sometimes the root cause is: Caused by: java.io.IOException: Eine bestehende Verbindung wurde softwaregesteuert durch den Hostcomputer abgebrochen

      This is german for the connection was canceled.

      ---------- BEGIN SOURCE ----------

      import java.io.ByteArrayInputStream;
      import java.io.IOException;
      import java.net.Socket;
      import java.net.URI;
      import java.net.http.HttpClient;
      import java.net.http.HttpRequest;
      import java.net.http.HttpResponse;
      import java.security.KeyStore;
      import java.security.cert.CertificateException;
      import java.security.cert.X509Certificate;
      import java.time.Duration;
      import java.util.Base64;
      import java.util.logging.Level;
      import java.util.logging.Logger;
      import javax.net.ssl.KeyManagerFactory;
      import javax.net.ssl.SSLContext;
      import javax.net.ssl.SSLException;
      import javax.net.ssl.SSLServerSocket;
      import javax.net.ssl.SSLServerSocketFactory;
      import javax.net.ssl.TrustManager;
      import javax.net.ssl.X509TrustManager;

      public class Connection {

          private static final Logger LOG = Logger.getLogger(Connection.class.getName());

          public final static String[] TLS_PROTOCOLS = {"TLSv1.3"};
          public final static String[] TLS_CIPHER_SUITES = {"TLS_AES_256_GCM_SHA384", "TLS_AES_128_GCM_SHA256"};

          public static void main(final String[] args) throws Exception {
              // to keep this dummy code simple
              System.setProperty("jdk.internal.httpclient.disableHostnameVerification", "true");

              // a trust manager to trust in all servers and reject all clients
              TrustManager[] trusts = new TrustManager[]{
                  new X509TrustManager() {

                      @Override
                      public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                          throw new CertificateException("Not trusted.");
                      }

                      @Override
                      public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                          return; // trusted
                      }

                      @Override
                      public X509Certificate[] getAcceptedIssuers() {
                          return new X509Certificate[0];
                      }
                  }
              };

              // start a SSL socket to show the issue
              {
                  // a dummy SSL certificate
                  String cert = "MIACAQMwgAYJKoZIhvcNAQcBoIAkgASCA+gwgDCABgkqhkiG9w0BBwGggASCAxQwggMQMIIDDAYLKoZIhvcNAQwKAQKgggKz"
                          + "MIICrzApBgoqhkiG9w0BDAEDMBsEFOkb3cZjQYPLL8m+Js1LLgyA2vtDAgMAyAAEggKAA/GVS98QC7vDu8LQUe1SYOXNx/Ie26V0"
                          + "taV/CbImGvz8Hrhc7J6Nf0D3GB8WTkI60SRZ7UGnc+7oO1v+3yqzMDRSo0dtRKgqYuISN4sL2q2yyFKkGNVBDyBHN9KQ3K+iprwL"
                          + "/ShuqvxMfK9aVlOJiAmigFr7oysx71u5NkMxCZFJyk9kJZgjvASKO2eddWuIA5B7UDKxvu18abDqBFBZnUBj3Wpmgxz4/j5tCMEa"
                          + "4Wp7dhDBxcN69Mztc9cSsS9SGHt8BAjBmSdummcVS09vx3LEQCdWOT7M7I5bchlsL9fSgyJMiJ9TVZen3exIXc78dprHA3fIF3ze"
                          + "jfge6hzPq5Mua618l4hK4P8HO1oxQ+n81tcc6XWekK5PLp13PMMwrDlNxnlxV+hU9/uhwGE8izGdmfZUeI7n70UsZXnn0DVQHMgt"
                          + "+CZlUEuyYHu2pubLvNRnVURW3Glo8XbD9a7jPW7mhVlj4ZqShxXDgq57GGBgr6Dn12XpXAjrHRYQQUbOCftmn9BgI0JdhuVFErF9"
                          + "u9q0U52FWKYI4DMFVgWaxYa4hr7grck5ipS5Q62D8G3iWLgJFXe2yo9/XXusAkAYzi2IvmCNaHyxmfV3UR9MBTSlblNvpAvdhSAM"
                          + "Xg2znCelZKDaCJ4mQ0v6OBAJ/2ntIyqkwH3BH1Pf78hnJ+vFsUggcby6WPxgb+IpEtI2NyIAX+x6F5/4INeNszxVksBzXLIWI0D9"
                          + "wwSdUKRB33o1HytYvRXk6GBzEymGUg5DIM5OYtJQa8bEvYI4Zw7qaLvNMDxB46UGXoINhQiGOwgLVmZYojirMOQAIIowj0ZAgifj"
                          + "yFSFniSoVy7/wHVuHxjB8zFGMB8GCSqGSIb3DQEJFDESHhAAaQBkAGUAbgB0AGkAdAB5MCMGCSqGSIb3DQEJFTEWBBT76rmH2Qg7"
                          + "P9M2MAzr8XKx9922ggAAAAAwgAYJKoZIhvcNAQcGoIAwgAIBADCABgkqhkiG9w0BBwEwKQYKKoZIhvcNAQwBBjAbBBSb6VFGFYLQ"
                          + "que5J6P4rnITdJkUUwIDAMgAgIICgLeHrleGCloetqri6X72rHaB3aM4ZyuWAIVXr+RZTPTZZPigpvocv4r0PPCwCpMVoUWijmga"
                          + "C1IoascVCmU3qGKt1ORRtaTuMdZskh/84t4lhbyMFUlgsxBPiqNnPPdqoAfUY0wtsk7go4kDBIICH+Y8E4i3vS5ea2Ah1XVnTcp1"
                          + "o68Rj1baX8/WbiiEYajDVMcQ4Y+8W/XdHGCha1+X4jSL52V8uXRX86R8KfTb3SJVDV+NUnC0AJ7YBs7PfDxBSBszyO51ZaF8rQfn"
                          + "2J44aDoQkrU5ALw/4H4sTpnvj86VivMCOYW0F8hjHMkLk1YZsroZQzjmA9HEylc0zb6+Xnv83PHv7y4B+AKSS0kfSiIulGkJZO2A"
                          + "MsJt6cFPW8faHpzMXobu8QLyGbYBNeddJ+lPfuxy2PZrsvewA6Ush8I5wo4sPj/QqQ/fchCmURx7pcyyWRSPQoeuAjVCXW8oTyLQ"
                          + "2dhpYNKVrNl+5mF1eT51H1ULy6oUqfYkcqxbrkNK777AeJTp/JwGRVRr4f4THnBLS3FP4d0ab0unXI0HUygvFNaBAW0v//zHtCF9"
                          + "79b8hOx2vrbnUca6SzJApgVy3w68l/QaW/gFA41YTM0OIhud9GbtBns3U7Qkel0v3v25oDCkHTYzjJ6y7eORXNud7kTCqlICKZYL"
                          + "DTstIqAe+wWsy2Ae80HteWWuzArStsA0lUC1e8hkRhZd8GkOEjXW4ujDPQS0tfVpSASBIM5coao2+Vyk9AJ2KUM5iomKrdAcQdAw"
                          + "QkvvqKs51k4IrlJ3fCj3m7r8VWmELK9rcbWikSDYWPly5QLa+gR5ZXzIQtxbOzRhBSxB64RuTXURZK6jVLFr3YXUAAAAAAAAAAAA"
                          + "AAAAAAAAADA+MCEwCQYFKw4DAhoFAAQUnYwoKFFWZmoSCoSDJRRhGo5jeDUEFAgLJTnMBHs+vhoFesnZBojOCjELAgMBkAAAAA==";
                  char[] password = "dummy".toCharArray();

                  {
                      KeyStore keyStore = KeyStore.getInstance("PKCS12");

                      keyStore.load(new ByteArrayInputStream(Base64.getDecoder().decode(cert)), password);

                      KeyManagerFactory factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());

                      factory.init(keyStore, password);

                      SSLContext sslContext = SSLContext.getInstance("TLS");
                      sslContext.init(factory.getKeyManagers(), trusts, null);

                      SSLServerSocketFactory socketFactory = sslContext.getServerSocketFactory();
                      SSLServerSocket socket;

                      socket = (SSLServerSocket) socketFactory.createServerSocket(4567);
                      socket.setNeedClientAuth(true);
                      socket.setEnabledProtocols(TLS_PROTOCOLS);
                      socket.setEnabledCipherSuites(TLS_CIPHER_SUITES);

                      new Thread(() -> {
                          // try to read the incomming connections (this is doomed, we trust no client)
                          try ( var s = socket) {
                              while (true) {
                                  Socket cnn = s.accept();

                                  try {
                                      cnn.getInputStream().read();

                                      // should not happen, we do not trust the client
                                      System.exit(666);
                                  } catch (SSLException e) {
                                      cnn.close();
                                  }
                              }
                          } catch (IOException ex) {
                              Logger.getLogger(Connection.class.getName()).log(Level.SEVERE, null, ex);
                          }
                      }).start();
                  }
              }

              // start to send requests to the socket
              {
                  SSLContext sslContext = SSLContext.getInstance("TLS");

                  sslContext.init(null, trusts, null);

                  HttpClient client = HttpClient
                          .newBuilder()
                          .sslContext(sslContext)
                          .connectTimeout(Duration.ofSeconds(2))
                          .build();

                  byte[] data = new byte[8 * 1024];

                  // the issue does not occur with out a BodyPublisher set
                  HttpRequest request = HttpRequest
                          .newBuilder(URI.create("https://localhost:4567"))
                          .method("POST", HttpRequest.BodyPublishers.ofInputStream(() -> new ByteArrayInputStream(data)))
                          .timeout(Duration.ofSeconds(4)).build();

                  // start to spin, to provoke the issue
                  for (int i = 0; i < 1000; i++) {
                      try {
                          // send the request
                          client.send(request, HttpResponse.BodyHandlers.ofByteArray());
                      } catch (IOException e) {
                          // we expect a failure here
                          Throwable cause = e.getCause();

                          // get the root cause of the exception
                          if (cause.getCause() != null) {
                              cause = cause.getCause();
                          }

                          // we expect a SSLException, if so continue to spin
                          if (cause instanceof SSLException) {
                              LOG.info(i + ": got the expected " + cause.toString());
                              continue;
                          }

                          // we are sending the same request and the dummy server is stateless
                          // why is there a different exception now?
                          throw new IllegalStateException( i + ": why is there no SSLException as root cause?", e);
                      }
                  }
              }
          }
      }

      ---------- END SOURCE ----------

      FREQUENCY : occasionally


      Attachments

        Activity

          People

            djelinski Daniel Jelinski
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved: