I have implemented a JAX-WS client by using ApacheCXF (v3.0.4) and everything works successfully but the problem comes when I want to use a secure connection (SSL/TLS) with
You, or the underlying libs (the WS lib does it) might be using setHostnameVerifier(..)
There is a bug in java8, where if setHostnameVerifier(..) is used the SNI is not done from the client side.
https://bugs.openjdk.java.net/browse/JDK-8072464
Please use JDK version 8u141 and above where this issue has been fixed. Please review the JDK 8u141 Bugs Fixes page for more details
As mentioned, the cause is related to the JDK bug where using setHostnameVerifier() breaks SNI (Extension server_name). https://bugs.openjdk.java.net/browse/JDK-8144566
Our workaround: After testing we found that setting a connection's SSLSocketFactory to just about anything from the default seems to fix the issue.
This does not work:
HttpsURLConnection.setSSLSocketFactory((SSLSocketFactory) SSLSocketFactory.getDefault());
This does work:
HttpsURLConnection.setSSLSocketFactory(new SSLSocketFactoryFacade());
So, to fix it for a JAX-WS client, you could do something like this:
bindingProvider.getRequestContext().put("com.sun.xml.internal.ws.transport.https.client.SSLSocketFactory", new SSLSocketFactoryFacade());
Our SSLSocketFactory facade: (Note that it really doesn't do anything)
public class SSLSocketFactoryFacade extends SSLSocketFactory {
SSLSocketFactory sslsf;
public SSLSocketFactoryFacade() {
sslsf = (SSLSocketFactory) SSLSocketFactory.getDefault();;
}
@Override
public String[] getDefaultCipherSuites() {
return sslsf.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return sslsf.getSupportedCipherSuites();
}
@Override
public Socket createSocket(Socket socket, String s, int i, boolean b) throws IOException {
return sslsf.createSocket(socket, s, i, b);
}
@Override
public Socket createSocket(String s, int i) throws IOException, UnknownHostException {
return sslsf.createSocket(s, i);
}
@Override
public Socket createSocket(String s, int i, InetAddress inetAddress, int i1) throws IOException, UnknownHostException {
return sslsf.createSocket(s, i, inetAddress, i1);
}
@Override
public Socket createSocket(InetAddress inetAddress, int i) throws IOException {
return createSocket(inetAddress, i);
}
@Override
public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress1, int i1) throws IOException {
return createSocket(inetAddress, i, inetAddress1, i1);
}
}
I tried the solution provided by Benjamin Parry, but it did not work for me. After some digging around, I also found this solution which looks very similar, however the SSLSocketFactoryFacade manually inserts the correct SSL header instead of being a pure pass-though. Providing my final code below which is slightly different, but credit to be given to Girish Kamath at javabreaks for the basic idea:
private static class SSLSocketFactoryFacade extends SSLSocketFactory {
private SSLSocketFactory sslsf;
private SSLParameters sslParameters;
public SSLSocketFactoryFacade(String hostName) {
sslParameters = new SSLParameters();
sslParameters.setServerNames(Arrays.asList(new SNIHostName(hostName)));
sslsf = (SSLSocketFactory) SSLSocketFactory.getDefault();;
}
public Socket createSocket() throws IOException {
Socket socket = sslsf.createSocket();
((SSLSocket) socket).setSSLParameters(sslParameters);
return socket;
}
public Socket createSocket(InetAddress arg0, int arg1, InetAddress arg2, int arg3) throws IOException {
Socket socket = sslsf.createSocket(arg0, arg1, arg2, arg3);
((SSLSocket) socket).setSSLParameters(sslParameters);
return socket;
}
public Socket createSocket(InetAddress arg0, int arg1) throws IOException {
Socket socket = sslsf.createSocket(arg0, arg1);
((SSLSocket) socket).setSSLParameters(sslParameters);
return socket;
}
public Socket createSocket(Socket arg0, InputStream arg1, boolean arg2) throws IOException {
Socket socket = sslsf.createSocket(arg0, arg1, arg2);
((SSLSocket) socket).setSSLParameters(sslParameters);
return socket;
}
public Socket createSocket(Socket arg0, String arg1, int arg2, boolean arg3) throws IOException {
Socket socket = sslsf.createSocket(arg0, arg1, arg2, arg3);
((SSLSocket) socket).setSSLParameters(sslParameters);
return socket;
}
public Socket createSocket(String arg0, int arg1, InetAddress arg2, int arg3)
throws IOException, UnknownHostException {
Socket socket = sslsf.createSocket(arg0, arg1, arg2, arg3);
((SSLSocket) socket).setSSLParameters(sslParameters);
return socket;
}
public Socket createSocket(String arg0, int arg1) throws IOException, UnknownHostException {
Socket socket = sslsf.createSocket(arg0, arg1);
((SSLSocket) socket).setSSLParameters(sslParameters);
return socket;
}
public String[] getDefaultCipherSuites() {
return sslsf.getDefaultCipherSuites();
}
public String[] getSupportedCipherSuites() {
return sslsf.getSupportedCipherSuites();
}
}
And then I can call
sslConnection.setSSLSocketFactory(new SSLSocketFactoryFacade(sslConnection.getURL().getHost()));
where sslConnection
is the HttpsURLConnection
.
First of all, this "server_name" stuff is associate with the SNI (Server Name Indication) extension. The Java 8 JSSE documentation talks about it here.
The documentation includes example code that shows how to set the server names that are sent. The code is for Java 8.
However, I can't figure out why (apparently) Java 7 is setting the server name by default, and Java 8 isn't. (The easy way to figure it out would be to use a debugger to figure out how the SSL engine object is created and initialized.)