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


Reply via email to