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 java 8 (jdk1.8.0_25).
I see the following exception in log (-Djavax.net.debug=all):
main, handling exception: java.net.SocketException: Connection reset
main, SEND TLSv1.2 ALERT: fatal, description = unexpected_message
main, WRITE: TLSv1.2 Alert, length = 2
main, Exception sending alert: java.net.SocketException: Connection reset by peer: socket write error
After a depeer analysis I have observed the problem is caused because the with Java 8 the server_name (SNI) is not sent but with Java 7 it is sent and the web service invocation works successfully.
Java 8 log (-Djavax.net.debug=all): Missing "Extension server_name"
[...]
Compression Methods: { 0 }
Extension elliptic_curves, curve names: {secp256r1, sect163k1, sect163r2, secp192r1, secp224r1, sect233k1, sect233r1, sect283k1, sect283r1, secp384r1, sect409k1, sect409r1, secp521r1, sect571k1, sect571r1, secp160k1, secp160r1, secp160r2, sect163r1, secp192k1, sect193r1, sect193r2, secp224k1, sect239k1, secp256k1}
Extension ec_point_formats, formats: [uncompressed]
Extension signature_algorithms, signature_algorithms: SHA512withECDSA, SHA512withRSA, SHA384withECDSA, SHA384withRSA, SHA256withECDSA, SHA256withRSA, SHA224withECDSA, SHA224withRSA, SHA1withECDSA, SHA1withRSA, SHA1withDSA, MD5withRSA
***
[...]
Java 7 log (-Djavax.net.debug=all) (works): "Extension server_name" is set
[...]
Compression Methods: { 0 }
Extension elliptic_curves, curve names: {secp256r1, sect163k1, sect163r2, secp192r1, secp224r1, sect233k1, sect233r1, sect283k1, sect283r1, secp384r1, sect409k1, sect409r1, secp521r1, sect571k1, sect571r1, secp160k1, secp160r1, secp160r2, sect163r1, secp192k1, sect193r1, sect193r2, secp224k1, sect239k1, secp256k1}
Extension ec_point_formats, formats: [uncompressed]
Extension signature_algorithms, signature_algorithms: SHA512withECDSA, SHA512withRSA, SHA384withECDSA, SHA384withRSA, SHA256withECDSA, SHA256withRSA, SHA224withECDSA, SHA224withRSA, SHA1withECDSA, SHA1withRSA, SHA1withDSA, MD5withRSA
Extension server_name, server_name: [host_name: testeo.hostname.es]
***
[...]
It is observed that with Java 7 the Extension server_name, server_name: [host_name: testeo.hostname.es] is set and then the web service invocation works successfully.
Why didn't Java 8 set the server_name as Java 7 did? Is it a Java configuration issue?
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);
}
}
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.
Please use JDK version 8u141 and above where this issue has been fixed. Please review the JDK 8u141 Bugs Fixes page for more details
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.)
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
.
来源:https://stackoverflow.com/questions/30817934/extended-server-name-sni-extension-not-sent-with-jdk1-8-0-but-send-with-jdk1-7