SSLProtocolException: handshake alert: unrecognized_name: client-side code workaround?

10,630

Solution 1

You're connecting to a server that has missconfigured SNI. If you have no control over that server and cannot get it fixed, then you will have to disable SNI.

1) If you expect the server to be fixed at some point, use the runtime flag to disable SNI temporarily until the server is fixed. This way you can just remove this flag later without even needing to recompile once the server is fixed.

java -Djsse.enableSNIExtension=false

2) If you don't expect the server will ever be fixed, implement a permanent workaround such as disabling SNI in your application:

System.setProperty("jsse.enableSNIExtension", "false");

The workaround the answer you link mentions ultimately disables SNI anyway in a much more cumbersome way, so unless you need SNI somewhere else in your application I would just go with one of the options above.

Solution 2

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);
  }
}
Share:
10,630
stblassitude
Author by

stblassitude

By day software architect, by night OS hacker and train fancier.

Updated on June 05, 2022

Comments

  • stblassitude
    stblassitude about 2 years

    In a custom HTTP client retrieving XML data, I'm getting the unrecognized_name error. The answers to similar questions unfortunately do not work for me:

    • I cannot switch off SNI support via the system property since my code is running inside an application server, over which I have no control
    • I cannot fix the web service server configuration (to add a ServerName)

    Right now, my code is using commons httpclient 3.1, but I'm open to alternatives. Here's the abridged code I'm using right now:

    HttpClient client = new HttpClient();
    PostMethod method = new PostMethod(getEndpointUrl());
    NameValuePair[] data = {
        new NameValuePair("username", getUsername()),
        new NameValuePair("password", getPassword()),
        new NameValuePair("email", email)
    };
    method.setRequestBody(data);
    client.executeMethod(method);
    return method.getResponseBodyAsStream();
    

    I've looked at HttpClient, and it seems that I can implement a workaround similar to the one mentioned in https://stackoverflow.com/a/14884941/3676401, but seems to be a lot of potentially security-relevant code.

    I'm rather loathe to dump a large amount of code into an otherwise simple client. Any other suggestions?

  • stblassitude
    stblassitude about 10 years
    As I said in my question, I cannot set a system property, because my code runs in an app server I have no control over; setting the property in my code does not appear to help, as the SSLSocket code seems to have been initialized already by the time my code runs. And the workaround that I linked to uses code to make Oracles SSLSocket implementation ignore SNI on a per-case basis, so I think this is what I'd want.