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

Jaikiran Pai jai.forums2013 at gmail.com
Tue Sep 18 01:30:35 UTC 2018


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