Extended Server_Name (Sni Extension) Not Sent with Jdk1.8.0 But Send with Jdk1.7.0

Java (JDK1.8) is not setting server_name extension in SSL handshake when hostname has '_'

The answer is no. This wont work in java using the standard JDK libraries with HTTPS.

There virtually no "normal" Java way to work around this problem.
Other ideas:

  • ask the service owner to provide a domain name without the _.
  • dont use SSL/HTTPS. Most Java HTTP clients that are not doing strict checks will work with a hostname with an underscore in it. This is not a great solution if you are on the open internet
  • do use HTTPS but go direct to the IP address and disable HTTPS hostname validation. This is also a bad idea if you are on the open internet
  • introduce a MITM or forward proxy that can provide a HTTP endpoint to your service, but will do HTTPS over the untrusted parts of the network, (SSL onloading?)
  • just shell out to curl and read the resulting string, as described here on Baeldung. Be very careful here, as it's very easy to unwittingly open yourself up security issues like remote code execution
  • use jni to get to a network library that will allow the underscore - libcurl is an obvious candidate, but this will be significantly more work than the System.execute method, also possibly unsafe for other reasons (memory management, instability of badly written jni interop, not great for maintainability)
  • You could possibly try to fork the related URI / URL parsing classes in java.net to remove the check for the underscore. The problem with this is it's a massive hack, possibly of unbounded difficulty, could easily break all manner of things unintentionally and you might need to build your own version of the JDK as I have some recollection of java.net code being special for some reason in that you couldn't just chuck a patched class in ext/lib dir of the JVM (but I could be mixing that up with something else).

SNI Java 1.8 and Jboss SSL Handshake

The WS client we were using in this case is utilizing AXIS2 and is designed to be agnostic to the framework in use. The SNI issue was being caused by an SSL header that was not being set called:

Extension server_name, server_name:

Its posted values should look something like this:

[type=host_name (0), value=some.server.value]

During out initial trials two failed assumption sets that were made 1) that any httpclient could be used i.e. the jboss supplied client or the JAVAX supplied client or even the client supplied by AXIS2 – however this was not the case and lead down many roads which were not conducive to a solution. We knew with a fair amount of certainty that we needed to set the SNI headers via an SSLFactory, what we were unsure of was how to get that factory to produce connectors that would be used by the system. Something that might have been helpful in identifying this issue would have been:

 Class klass = String.class;
URL location = klass.getResource('/' + klass.getName().replace('.', '/') + ".class");

2) Another area that we were unable to procced forward with was the bind solution presented in multiple wikis and howto’s:

bindingProvider.getRequestContext().put("<a href="http://com.sun.xml.internal.ws">com.sun.xml.internal.ws</a>.transport.https.client.SSLSocketFactory", new SSLSocketFactoryFacade());

The Application had its own version of HTTPclient which needed to be used. So a new class called BrokeredEpaasSSLSocketFactory was created that implements SecureProtocolSocketFactory based on the org.apache.commons.httpclient 3.1 supplied by the application.
The key pieces of code are these -
In the client itself:
(to restrict the factories usage we have altered the URL’s of EPAAS to carry epaas instead of HTTPS)
Initially we had thought of using register in the protocol class for https – however that would then mean every application that has a unique URL would need to be tested

Protocol myHTTPS = new Protocol( "https", new MySSLSocketFactory(), 443 );
Protocol.registerProtocol( "https", myHTTPS );

Also an attempt to try and alter the protocol (using epaas to make the request instead of https) resulted in AXIS throwing back an error that wouldn’t allow to proceed forward so we abandoned this idea…

String endPointFinal = endPoint.replaceAll("epaas://", "https://");
bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endPointFinal);
bp.getRequestContext().put("SO_TIMEOUT", new Integer(sTimout));
bp.getRequestContext().put("CONNECTION_TIMEOUT", new Integer(cTimeout));
BrokeredEpaasSSLSocketFactory factory = new BrokeredEpaasSSLSocketFactory();
Protocol authhttps = new Protocol("https", factory, 443);
HttpClient httpclient = new HttpClient();
URL URI = new URL(endPointFinal);
httpclient.getHostConfiguration().setHost(URI.getHost(), 443, authhttps);
GetMethod httpget = new GetMethod("/ecmuser/BrokeredConnectorService");
httpclient.executeMethod(httpget);

The newly created class (BrokeredEpaasSSLSocketFactory) has two methods of particular interest:
This currently is the only createSocket being used by the application (though they have all been altered for future use):

public Socket createSocket(String host, int port, InetAddress localAddress, int localPort, HttpConnectionParams params) throws IOException, UnknownHostException, ConnectTimeoutException {
// TODO Auto-generated method stub
if (params == null) {
throw new IllegalArgumentException("Parameters may not be null");
}
int timeout = params.getConnectionTimeout();
if (timeout == 0) {
return _defaultFactory.createSocket(host, port,localAddress,localPort);
} else {

SSLSocket socket = (SSLSocket) _defaultFactory.createSocket();
setParameters(socket,host);
SocketAddress localaddr = new InetSocketAddress(localAddress, localPort);
SocketAddress remoteaddr = new InetSocketAddress(host, port);
socket.bind(localaddr);
socket.connect(remoteaddr, timeout);
return socket;
}
}

This is the method which actually sets the SNI headers on the SSL outbound request:

private void setParameters(SSLSocket socket, String host)
throws MalformedURLException {
SSLParameters sslParameters = new SSLParameters();
List<SNIServerName> sniHostNames = new ArrayList<SNIServerName>(1);
sniHostNames.add(new SNIHostName(host));
sslParameters.setServerNames(sniHostNames);
socket.setSSLParameters(sslParameters);
}

Java 8 https connection fails on some sites

According to SSL Labs, both sites which aren't working give this interesting result :

This site works only in browsers with SNI support.

This is what I said in a comment, maybe an SNI issue, which seems to be confirmed.
As we see in your debug log, there is no server_name extension in the ClientHello. We would typically find it after the other extensions that we see in the log (elliptic_curves, ec_point_formats, signature_algorithms)

There's one thread looking a similar, talking about a Java bug turning off the SNI functionality when using a HostnameVerifier (which is your case). Your JVM version (8u121) is probably guilty, please upgrade as said in the bug information, to 8u141 minimum.

Apache http client defaults don't work with sni

The issue ended up being that another dependency was not only using a different version of the httpclient, BUT was also packaging it into its jar without listing it as a transitive dependency. (meaning, that it doesn't show up when you go to inspect the pom dependency hierarchy, so it can't be flagged out as the "used version").

The dependency in question was:

<dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-dev</artifactId>
<version>2.7.0</version>
</dependency>

It has the code for apache http included in its jar, and unlisted in its pom.
And the version it references is 4.3.1, which exhibits this problem. (4.3.2 and onward do not).

There are two workarounds:

  1. Include the http client dependency 1st in the list within the
    pom.xml

  2. Remove the gwt-dev dependency from the pom.xml



Related Topics



Leave a reply



Submit