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 >