Java 11 RC - Handshake failure in certain specific cases throws a different exception than previous versions

Xuelei Fan xuelei.fan at oracle.com
Tue Sep 18 01:59:58 UTC 2018


Hi Jaikiran,

Normally, the thrown exception class can be an implementation choice, 
and may be not reliable from version to version.  We were trying to use 
the same exception, but we may miss the use cases.  I may suggest to 
make the code independent from it.  However, if the impact is 
significant, please feel free file a bug and we will evaluate if there 
is something we can do.

Thanks,
Xuelei

On 9/17/2018 6:30 PM, Jaikiran Pai wrote:
> Just checking back on this one. Is this an expected change? Personally,
> it's not a big issue in the code where this is happening for me. I'll
> probably just change the catch block to a more generic IOException.
> However, for any other code which relied on the previous SocketException
> catch block, they will now have to expect a different exception
> depending on what version of Java runtime it's running against.
> 
> -Jaikiran
> 
> 
> On 12/09/18 9:11 PM, Jaikiran Pai wrote:
>> Please consider the code that's at the end of this mail. It is a simple
>> client/server code where a HTTPS server is created and set to
>> "needClientAuth". The client then uses HttpsURLConnection and (in this
>> case intentionally) doesn't present any certificate, expecting the
>> handshake to fail. In previous versions of Java, the handshake failure
>> in this code would throw a java.net.SocketException as below:
>>
>> Exception in thread "main" java.net.SocketException: Connection reset
>>      at java.net.SocketInputStream.read(SocketInputStream.java:210)
>>      at java.net.SocketInputStream.read(SocketInputStream.java:141)
>>      at sun.security.ssl.InputRecord.readFully(InputRecord.java:465)
>>      at sun.security.ssl.InputRecord.read(InputRecord.java:503)
>>      at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:983)
>>      at sun.security.ssl.SSLSocketImpl.waitForClose(SSLSocketImpl.java:1779)
>>      at
>> sun.security.ssl.HandshakeOutStream.flush(HandshakeOutStream.java:124)
>>      at
>> sun.security.ssl.Handshaker.sendChangeCipherSpec(Handshaker.java:1156)
>>      at
>> sun.security.ssl.ClientHandshaker.sendChangeCipherAndFinish(ClientHandshaker.java:1266)
>>      at
>> sun.security.ssl.ClientHandshaker.serverHelloDone(ClientHandshaker.java:1178)
>>      at
>> sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:348)
>>      at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1052)
>>      at sun.security.ssl.Handshaker.process_record(Handshaker.java:987)
>>      at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1072)
>>      at
>> sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1385)
>>      at
>> sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1413)
>>      at
>> sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1397)
>>      at
>> sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
>>      at
>> sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
>>      at
>> sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1564)
>>      at
>> sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492)
>>      at
>> sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:263)
>>      at ClientCertTest.main(ClientCertTest.java:26)
>>
>>
>> However, in the Java 11 (release candidate) as well as Java 12
>> (upstream), this code now throws a javax.net.ssl.SSLProtocolException
>> with the java.net.SocketException wrapped in it:
>>
>> Exception in thread "main" javax.net.ssl.SSLProtocolException:
>> Connection reset
>>      at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:126)
>>      at
>> java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:321)
>>      at
>> java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:264)
>>      at
>> java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:259)
>>      at
>> java.base/sun.security.ssl.SSLSocketImpl.handleException(SSLSocketImpl.java:1314)
>>      at
>> java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:839)
>>      at
>> java.base/java.io.BufferedInputStream.fill(BufferedInputStream.java:252)
>>      at
>> java.base/java.io.BufferedInputStream.read1(BufferedInputStream.java:292)
>>      at
>> java.base/java.io.BufferedInputStream.read(BufferedInputStream.java:351)
>>      at
>> java.base/sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:746)
>>      at java.base/sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:689)
>>      at java.base/sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:717)
>>      at
>> java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1604)
>>      at
>> java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1509)
>>      at
>> java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:245)
>>      at ClientCertTest.main(ClientCertTest.java:26)
>> Caused by: java.net.SocketException: Connection reset
>>      at java.base/java.net.SocketInputStream.read(SocketInputStream.java:186)
>>      at java.base/java.net.SocketInputStream.read(SocketInputStream.java:140)
>>      at
>> java.base/sun.security.ssl.SSLSocketInputRecord.read(SSLSocketInputRecord.java:448)
>>      at
>> java.base/sun.security.ssl.SSLSocketInputRecord.bytesInCompletePacket(SSLSocketInputRecord.java:68)
>>      at
>> java.base/sun.security.ssl.SSLSocketImpl.readApplicationRecord(SSLSocketImpl.java:1104)
>>      at
>> java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:823)
>>      ... 10 more
>>
>>
>> Is this an intentional and expected change? As far as I could see, there
>> isn't any specific API which says SocketException will be thrown in this
>> case, so maybe the client applications which were catching this specific
>> exception are expected to change their catch block to something more
>> generic like a IOException?
>>
>> Here's the code to reproduce this:
>>
>> import java.io.*;
>> import java.net.*;
>> import javax.net.ssl.*;
>> import java.util.*;
>> import java.security.*;
>> import com.sun.net.httpserver.*;
>> import java.security.cert.*;
>>
>>
>> public class ClientCertTest {
>>      private static final String keyFilename = "keystore";
>>      private static final String keystorePass = "passphrase";
>>
>>      public static void main(final String[] args) throws Exception {
>>          final int port = 12345;
>>          final HttpsServer server = startServer("localhost", port);
>>          try {
>>              final URL targetURL = new URL("https://localhost:" + port +
>> "/");
>>              final SSLContext sslctx = SSLContext.getInstance("TLS");
>>              sslctx.init(null, new TrustManager[] {new TrustAll()}, null);
>>              HttpsURLConnection.setDefaultHostnameVerifier((h, s) ->
>> {return true;});
>>              final HttpsURLConnection conn = (HttpsURLConnection)
>> targetURL.openConnection();
>>              // setup the HTTPS connection to use our SocketFactory which
>> doesn't present
>>              // any cert when asked for (and thus is expected to fail
>> handshake)
>>              conn.setSSLSocketFactory(sslctx.getSocketFactory());
>>              try (final InputStream is = conn.getInputStream()) {
>>                  is.read();
>>              } catch (SocketException se) {
>>                   System.out.println("*** Received the expected
>> SocketException: " + se.getMessage());
>>                   throw se;
>>              }
>>          } finally {
>>                 server.stop(0);
>>              System.out.println("Stopped server");
>>          }
>>      }
>>
>>      private static HttpsServer startServer(final String host, final int
>> port) throws Exception {
>>          final HttpsServer server = HttpsServer.create(new
>> InetSocketAddress(host, port), 0);
>>          final Thread t = new Thread(() -> {
>>              try {
>>                  final SSLContext sslctx = SSLContext.getInstance("TLS");
>>                  final KeyManagerFactory kmf =
>> KeyManagerFactory.getInstance("SunX509");
>>                  final KeyStore ks = KeyStore.getInstance("JKS");
>>                  try (final FileInputStream fis = new
>> FileInputStream(keyFilename)) {
>>                      ks.load(fis, keystorePass.toCharArray());
>>                  }
>>                  kmf.init(ks, keystorePass.toCharArray());
>>                  sslctx.init(kmf.getKeyManagers(), null, null);
>>                  final SSLParameters sslParameters =
>> sslctx.getDefaultSSLParameters();
>>                  // need client auth
>>                  sslParameters.setNeedClientAuth(true);
>>                  server.setHttpsConfigurator(new HttpsConfigurator(sslctx) {
>>                      @Override
>>                      public void configure(final HttpsParameters params) {
>>                          params.setSSLParameters(sslParameters);
>>                      }
>>                  });
>>                  server.start();
>>                  System.out.println("Started server at " +
>> server.getAddress());
>>              } catch(Exception e) {
>>                  throw new RuntimeException(e);
>>              }
>>          });
>>          t.start();
>>          return server;
>>      }
>>
>>      private static class TrustAll implements X509TrustManager {
>>
>>          @Override
>>          public void checkClientTrusted(X509Certificate[] chain, String
>> authType) throws CertificateException {
>>              return;
>>          }
>>
>>          @Override
>>          public void checkServerTrusted(X509Certificate[] chain, String
>> authType) throws CertificateException {
>>              return;
>>          }
>>
>>          @Override
>>          public X509Certificate[] getAcceptedIssuers() {
>>              return new X509Certificate[0];
>>          }
>>      }
>> }
>>
>>
>> P.S: The "keystore" file in this example is the same one that's present
>> in the openjdk repo at test/jdk/javax/net/ssl/etc/keystore, so in order
>> to run the above code, that file can just be placed in the current
>> working dir.
>>
>> -Jaikiran
>>
> 


More information about the security-dev mailing list