So here's my attempt at that code. I've hidden it behind a configuration property we can adjust at deployment time, so hopefully, we can get rid of this wart as soon as the web service server configuration is fixed.
I've not checked any security implications this code has. I've only tested this with my application, and it might not work for you. Other JVM versions might behave differently. Don't hold me responsible if you decided to use this code!
Update 2014-09-01: Unfortunately, this workaround only works if you're connecting to the destination through a HTTPS proxy. If you connect directly, you will get the somewhat nondescript error.
Caused by: java.net.SocketException: Unconnected sockets not implemented
at javax.net.SocketFactory.createSocket(SocketFactory.java:125) ~[na:1.7.0_65]
Since our target server has had it's configuration fixed, I don't need the workaround any more. Adding a working stub createSocket() method is much more complicated, since it needs to be done in HTTPClients' SSLSocketFactory.
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* When a TLS client sends the Server Name Indication extension in the Client Hello, the server might reply with an
* alert that it does not know this particular server name. Usually, this means that the server is misconfigured,
* or that you're using the wrong hostname. But as long as the server does get you to the correct web service or
* site, it's harmless.
* <p/>
* Oracle in it's wisdom has decided that the warning should be treated as an error. This cannot be changed. This
* class overrides all createSocket calls so that the hostname (for TLS purposes) is blanked-out. In the
* implementation in 1.7.0_51-b13, this makes SSL not issue the SNI extension in the hello,
* thereby not triggering the problematic response.
* <p/>
* To use this with Apache HttpComponents' HttpClient, you need to create a ConnectionManager that uses a
* SchemeManager, which in turn uses a custom https SchemeHandler using this socket factory.
*
* <pre>
* {@code
* SchemeRegistry schemeRegistry = new SchemeRegistry();
* schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
* if (disableServerNameIndication) {
* schemeRegistry.register(new Scheme("https", 443, new SSLSocketFactory(new NoSNISSLSocketFactory
* (sslcontext.getSocketFactory()),
* SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)));
* } else {
* schemeRegistry.register(new Scheme("https", 443, new SSLSocketFactory(sslcontext)));
* }
* defaultHttpClient = new DefaultHttpClient(new PoolingClientConnectionManager(schemeRegistry));}
* </pre>
*/
public class NoSNISSLSocketFactory extends SSLSocketFactory {
private final SSLSocketFactory sslSocketFactory;
protected NoSNISSLSocketFactory(SSLSocketFactory socketFactory) {
this.sslSocketFactory = socketFactory;
}
@Override
public String[] getDefaultCipherSuites() {
return sslSocketFactory.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return sslSocketFactory.getSupportedCipherSuites();
}
@Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
return sslSocketFactory.createSocket(socket, "", port, autoClose);
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return createSocket(new Socket(host, port), host, port, true);
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException,
UnknownHostException {
return createSocket(new Socket(host, port, localHost, localPort), host, port, true);
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return sslSocketFactory.createSocket(host, port);
}
@Override
public Socket createSocket(InetAddress host, int port, InetAddress localHost, int localPort) throws IOException {
return createSocket(new Socket(host, port, localHost, localPort), host.getHostName(), port, true);
}
}