234

I upgraded from Java 1.6 to Java 1.7 today. Since then an error occur when I try to establish a connection to my webserver over SSL:

javax.net.ssl.SSLProtocolException: handshake alert:  unrecognized_name
    at sun.security.ssl.ClientHandshaker.handshakeAlert(ClientHandshaker.java:1288)
    at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:1904)
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1027)
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1262)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1289)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1273)
    at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:523)
    at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1296)
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254)
    at java.net.URL.openStream(URL.java:1035)

Here is the code:

SAXBuilder builder = new SAXBuilder();
Document document = null;

try {
    url = new URL(https://some url);
    document = (Document) builder.build(url.openStream());
} catch (NoSuchAlgorithmException ex) {
    Logger.getLogger(DownloadLoadiciousComputer.class.getName()).log(Level.SEVERE, null, ex);  
}

Its only a test project thats why I allow and use untrusted certificates with the code:

TrustManager[] trustAllCerts = new TrustManager[]{
    new X509TrustManager() {

        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        public void checkClientTrusted(
                java.security.cert.X509Certificate[] certs, String authType) {
        }

        public void checkServerTrusted(
                java.security.cert.X509Certificate[] certs, String authType) {
        }
    }
};

try {

    SSLContext sc = SSLContext.getInstance("SSL");
    sc.init(null, trustAllCerts, new java.security.SecureRandom());
    HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (Exception e) {

    Logger.getLogger(DownloadManager.class.getName()).log(Level.SEVERE, null, e);
} 

I sucessfully tried to connect to https://google.com. where is my fault?

Thanks.

soufrk
  • 825
  • 1
  • 10
  • 24
pvomhoff
  • 2,341
  • 2
  • 14
  • 4

17 Answers17

313

Java 7 introduced SNI support which is enabled by default. I have found out that certain misconfigured servers send an "Unrecognized Name" warning in the SSL handshake which is ignored by most clients... except for Java. As @Bob Kerns mentioned, the Oracle engineers refuse to "fix" this bug/feature.

As workaround, they suggest to set the jsse.enableSNIExtension property. To allow your programs to work without re-compiling, run your app as:

java -Djsse.enableSNIExtension=false yourClass

The property can also be set in the Java code, but it must be set before any SSL actions. Once the SSL library has loaded, you can change the property, but it won't have any effect on the SNI status. To disable SNI on runtime (with the aforementioned limitations), use:

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

The disadvantage of setting this flag is that SNI is disabled everywhere in the application. In order to make use of SNI and still support misconfigured servers:

  1. Create a SSLSocket with the host name you want to connect to. Let's name this sslsock.
  2. Try to run sslsock.startHandshake(). This will block until it is done or throw an exception on error. Whenever an error occurred in startHandshake(), get the exception message. If it equals to handshake alert: unrecognized_name, then you have found a misconfigured server.
  3. When you have received the unrecognized_name warning (fatal in Java), retry opening a SSLSocket, but this time without a host name. This effectively disables SNI (after all, the SNI extension is about adding a host name to the ClientHello message).

For the Webscarab SSL proxy, this commit implements the fall-back setup.

Community
  • 1
  • 1
Lekensteyn
  • 64,486
  • 22
  • 159
  • 192
  • 5
    it works, thanks! IntelliJ IDEA subversion client had the same error when connecting through HTTPS. Just need to update idea.exe.vmoptions file with line: -Djsse.enableSNIExtension=false – Dmitry Jul 20 '13 at 23:15
  • 2
    For java webstart use this: javaws -J-Djsse.enableSNIExtension=false yourClass – Torsten Jan 07 '14 at 10:32
  • I had the exact same issue with my Jersey client with https rest server. Turns out I used your trick and it worked!! thanks! – shahshi15 Jan 24 '14 at 00:27
  • 4
    For those wondering about Java 8, Oracle has still not changed the behavior and still require the programmer to catch servers which do not (properly) support SNI: http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#SNIExamples – Lekensteyn Jul 01 '14 at 13:23
  • Thanks! For those on jruby/trinidad, you can add this option to your JRUBY_OPTS environment variable. – Stephen C Feb 25 '15 at 18:28
  • Our product involves customers installing our server, but we don't necessarily know an appropriate FQDN to place in the ServerName directive. A simple solution is providing `ServerAlias *`. The ServerName will then be defined based on the hostname provided by the Java Webstart request. This way, we don't have to define the ServerName FQDN ahead of time -- we simply make use of whatever hostname the user requested. – ChaimKut Apr 22 '15 at 12:13
  • Fun discovery - this then will not work if you need to connect to CloudFlare sites. – Evan Knowles Mar 17 '16 at 14:20
  • You are talking of `misconfigured servers`. I bought an SSL certificate at my hoster, strato.com, and firefox confirms a secure connection on the respective pages. So there is nothing to do for me, really..? – phil294 Apr 07 '16 at 17:10
  • 2
    @Blauhirn Contact your webhost support, they should fix their configuration. If they use Apache, look down for some changes that need to be made. – Lekensteyn Apr 08 '16 at 16:12
  • Hoping this will help somebody else some time: My host told me it's my fault not theirs and that I were probably using an outdated OpenSSL version. I'm using Java 8. Still I might have found a solution: setting `enableSNIExtension` explicitly to `TRUE` – phil294 Jul 27 '16 at 17:13
  • Was receiving this error after updating Confluence to "6.1 (which in turn updated JAVA. This resolved my issue. Thanks! – IT_User May 24 '17 at 16:30
91

I had what I believe the same issue is. I found that I needed to adjust the Apache configuration to include a ServerName or ServerAlias for the host.

This code failed:

public class a {
   public static void main(String [] a) throws Exception {
      java.net.URLConnection c = new java.net.URL("https://mydomain.com/").openConnection();
      c.setDoOutput(true);
      c.getOutputStream();
   }
}

And this code worked:

public class a {
   public static void main(String [] a) throws Exception {
      java.net.URLConnection c = new java.net.URL("https://google.com/").openConnection();
      c.setDoOutput(true);
      c.getOutputStream();
   }
}

Wireshark revealed that during the TSL/SSL Hello the warning Alert (Level: Warning, Description: Unrecognized Name), Server Hello Was being sent from the server to the client. It was only a warning, however, Java 7.1 then responded immediately back with a "Fatal, Description: Unexpected Message", which I assume means the Java SSL libraries don't like to see the warning of unrecognized name.

From the Wiki on Transport Layer Security (TLS):

112 Unrecognized name warning TLS only; client's Server Name Indicator specified a hostname not supported by the server

This led me to look at my Apache config files and I found that if I added a ServerName or ServerAlias for the name sent from the client/java side, it worked correctly without any errors.

<VirtualHost mydomain.com:443>
  ServerName mydomain.com
  ServerAlias www.mydomain.com
  • 3
    This looks like a bug in Java's TLS implementation. – Paŭlo Ebermann Nov 09 '11 at 00:18
  • 1
    I actually openend a Bug for this. I got this number assigned (not yet visible): http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7127374 – eckes Jan 05 '12 at 16:19
  • 1
    Thanks, adding a `ServerAlias` worked for me. I had seen the same TLS alert in Wireshark. – Jared Beck Dec 04 '12 at 02:28
  • 2
    This worked for me, also. (would have worked better if I'd believed it and not gone hunting down another path) – end-user Jan 31 '13 at 18:34
  • 10
    @JosephShraibman, saying the Oracle employee is an idiot is inappropriate. When you use two `ServerName`s, Apache responds with an `unrecognized_name` **warning** alert (which could also be a **fatal** alert). [RFC 6066](https://tools.ietf.org/html/rfc6066#section-3) precisely says this on this topic: "*It is NOT RECOMMENDED to send a warning-level unrecognized_name(112) alert, because the client's behavior in response to warning-level alerts is unpredictable.*". The only mistake made by this employee is to assume that this was a *fatal* alert. This is as much a JRE bug as it is an Apache one. – Bruno Feb 24 '14 at 19:51
  • 1
    Correcting ServerName fixed the issue for my case. – Aykut Kllic Jan 17 '15 at 19:22
37

You can disable sending SNI records with the System property jsse.enableSNIExtension=false.

If you can change the code it helps to use SSLCocketFactory#createSocket() (with no host parameter or with a connected socket). In this case it will not send a server_name indication.

eckes
  • 10,103
  • 1
  • 59
  • 71
  • 11
    Which is done like this: `System.setProperty ("jsse.enableSNIExtension", "false");` – jn1kk Apr 09 '12 at 18:27
  • I've found that that doesn't always work. I don't know why it works in some cases and not others. – Joseph Shraibman May 09 '12 at 21:42
  • 2
    Works for me with `-Djsse.enableSNIExtension=false`. But what else does it do? Is it harmful for the rest of Javas security? – ceving Feb 04 '13 at 10:33
  • 2
    No it just means that some sites (especially web servers) which use mutiple hostnames behind a shared IP do not know what certificate to send. But unless your java app has to connect to millions of web sites you wont need that feature. If you encounter such a server your certificate validation might fail and the connection aborts. So there is no security problem expected. – eckes Feb 07 '13 at 23:17
19

We also ran into this error on a new Apache server build.

The fix in our case was to define a ServerAlias in the httpd.conf that corresponded to the host name that Java was trying to connect to. Our ServerName was set to the internal host name. Our SSL cert was using the external host name, but that was not sufficient to avoid the warning.

To help debug, you can use this ssl command:

openssl s_client -servername <hostname> -connect <hostname>:443 -state

If there is a problem with that hostname, then it will print this message near the top of the output:

SSL3 alert read: warning:unrecognized name

I should also note that we did not get that error when using that command to connect to the internal host name, even though it did not match the SSL cert.

Scott McIntyre
  • 1,365
  • 12
  • 24
  • 6
    Thanks for including the example about how to reproduce the problem using `openssl`. It’s very helpful. – sideshowbarker Aug 04 '15 at 10:22
  • 2
    Is there a way for an openssl client to see the name difference that originates the message `SSL3 alert read: warning:unrecognized name`? I see the alert printed, but the output doesn't help me to actually see this naming difference – mox601 May 25 '16 at 10:44
  • This doesn't work for me. Tested with `OpenSSL 1.0.1e-fips 11 Feb 2013`, `OpenSSL 1.0.2k-fips 26 Jan 2017` and `LibreSSL 2.6.5`. – Greg Dubicki Oct 15 '19 at 11:20
  • @GregDubicki try `openssl s_client -showcerts -connect :443` instead – Eyal Roth Jul 13 '20 at 15:21
15

Instead of relying on the default virtual host mechanism in apache, you can define one last catchall virtualhost that uses an arbitrary ServerName and a wildcard ServerAlias, e.g.

ServerName catchall.mydomain.com
ServerAlias *.mydomain.com

In that way you can use SNI and apache will not send back the SSL warning.

Of course, this only works if you can describe all of your domains easily using a wildcard syntax.

Erik
  • 151
  • 1
  • 2
  • As far as I understand, apache only sends this warning in case the client uses a name not configured as either ServerName or ServerAlias. So if you have a wild card certificate, either specify all used sub domains, or use the `*.mydomain.com` syntax for ServerAlias. **Note** that you can add multiple aliases using `ServerAlias`, e.g. `ServerAlias *.mydomain.com mydomain.com *.myotherdomain.com`. – Michael Paesold Apr 20 '16 at 14:21
13

It should be useful. To retry on a SNI error in Apache HttpClient 4.4 - the easiest way we came up with (see HTTPCLIENT-1522):

public class SniHttpClientConnectionOperator extends DefaultHttpClientConnectionOperator {

    public SniHttpClientConnectionOperator(Lookup<ConnectionSocketFactory> socketFactoryRegistry) {
        super(socketFactoryRegistry, null, null);
    }

    @Override
    public void connect(
            final ManagedHttpClientConnection conn,
            final HttpHost host,
            final InetSocketAddress localAddress,
            final int connectTimeout,
            final SocketConfig socketConfig,
            final HttpContext context) throws IOException {
        try {
            super.connect(conn, host, localAddress, connectTimeout, socketConfig, context);
        } catch (SSLProtocolException e) {
            Boolean enableSniValue = (Boolean) context.getAttribute(SniSSLSocketFactory.ENABLE_SNI);
            boolean enableSni = enableSniValue == null || enableSniValue;
            if (enableSni && e.getMessage() != null && e.getMessage().equals("handshake alert:  unrecognized_name")) {
                TimesLoggers.httpworker.warn("Server received saw wrong SNI host, retrying without SNI");
                context.setAttribute(SniSSLSocketFactory.ENABLE_SNI, false);
                super.connect(conn, host, localAddress, connectTimeout, socketConfig, context);
            } else {
                throw e;
            }
        }
    }
}

and

public class SniSSLSocketFactory extends SSLConnectionSocketFactory {

    public static final String ENABLE_SNI = "__enable_sni__";

    /*
     * Implement any constructor you need for your particular application -
     * SSLConnectionSocketFactory has many variants
     */
    public SniSSLSocketFactory(final SSLContext sslContext, final HostnameVerifier verifier) {
        super(sslContext, verifier);
    }

    @Override
    public Socket createLayeredSocket(
            final Socket socket,
            final String target,
            final int port,
            final HttpContext context) throws IOException {
        Boolean enableSniValue = (Boolean) context.getAttribute(ENABLE_SNI);
        boolean enableSni = enableSniValue == null || enableSniValue;
        return super.createLayeredSocket(socket, enableSni ? target : "", port, context);
    }
}

and

cm = new PoolingHttpClientConnectionManager(new SniHttpClientConnectionOperator(socketFactoryRegistry), null, -1, TimeUnit.MILLISECONDS);
Shcheklein
  • 5,979
  • 7
  • 44
  • 53
  • If you do it this way. How do you deal with `verifyHostname(sslsock, target)` in `SSLConnectionSocketFactory.createLayeredSocket`? Since the `target` is changed to empty string, `verifyHostname` is not going to succeed. Am I missing something obvious here? – Haozhun Apr 15 '17 at 21:07
  • 2
    This was exactly, what I was looking for, thank you so much! I didn't find any other way to support SNI and servers which respond with this "unrecognized_name" warning at the same time. – korpe Jul 08 '17 at 18:50
  • This solution works, unless you're using a proxy server. – mrog Oct 16 '18 at 23:06
  • Having just done a quick debug throught the Apache client code, starting with verifyHostname(), I cannot see how this can work using the DefaultHostnameVerifier. I suspect that this solution requires Hostname verification to be disabled (e.g. using a NoopHostnameVerifier), which is NOT a good idea as it allows man-in-the-middle attacks. – FlyingSheep May 13 '19 at 10:45
11

Use:

  • System.setProperty("jsse.enableSNIExtension", "false");
  • Restart your Tomcat (important)
Tomasz Janisiewicz
  • 639
  • 1
  • 11
  • 9
6

Ran into this issue with spring boot and jvm 1.7 and 1.8. On AWS, we did not have the option to change the ServerName and ServerAlias to match (they are different) so we did the following:

In build.gradle we added the following:

System.setProperty("jsse.enableSNIExtension", "false")
bootRun.systemProperties = System.properties

That allowed us to bypass the issue with the "Unrecognized Name".

Ray Hunter
  • 15,137
  • 5
  • 53
  • 51
5

You cannot supply system properties to the jarsigner.exe tool, unfortunately.

I have submitted defect 7177232, referencing @eckes' defect 7127374 and explaining why it was closed in error.

My defect is specifically about the impact on the jarsigner tool, but perhaps it will lead them to reopening the other defect and addressing the issue properly.

UPDATE: Actually, it turns out that you CAN supply system properties to the Jarsigner tool, it's just not in the help message. Use jarsigner -J-Djsse.enableSNIExtension=false

GreenGiant
  • 4,930
  • 1
  • 46
  • 76
Bob Kerns
  • 1,767
  • 1
  • 16
  • 14
  • 1
    Thanks Bob for the followup. Sadly Oracle still dont understand the whole thing. The SNI alert is not fatal, it may be trerated fatal. But most typical SSL clients (i.e. browsers) chose to ignore it since it is not a real security problem (because you still need to check the server certificate and would detect wrong endpoints).Of course it is sad that a CA is unable to set up a properly configured https server. – eckes Sep 06 '12 at 02:26
  • 2
    Actually, it turns out that you CAN supply system properties to the Jarsigner tool, it's just not in the help message. It is covered in the documentation. Silly me for believing the online text. Of course, they used this as an excuse for not fixing it. – Bob Kerns Oct 22 '12 at 01:39
  • BTW: I am currently discussing this issue on security-dev@openjdk and at the moment I was evaluating it it looks like Symantec fixed the GEOTrust timestamping server, it now correctly accepts the URL with no warning. But I still think that should be fixed. If you want to have a look, a [test client](https://github.com/ecki/JavaCryptoTest) project and the [discussion](http://mail.openjdk.java.net/pipermail/security-dev/2013-January/006437.html): – eckes Jan 21 '13 at 04:21
3

I hit the same problem and it turned out that reverse dns was not setup correct, it pointed to wrong hostname for the IP. After I correct reverse dns and restart httpd, the warning is gone. (if I don't correct reverse dns, adding ServerName did the trick for me as well)

2

My VirtualHost's ServerName was commented out by default. It worked after uncommenting.

Aram Paronikyan
  • 1,598
  • 17
  • 22
2

If you are building a client with Resttemplate, you can only set the endpoint like this: https://IP/path_to_service and set the requestFactory.
With this solution you don't need to RESTART your TOMCAT or Apache:

public static HttpComponentsClientHttpRequestFactory requestFactory(CloseableHttpClient httpClient) {
    TrustStrategy acceptingTrustStrategy = new TrustStrategy() {
        @Override
        public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            return true;
        }
    };

    SSLContext sslContext = null;
    try {
        sslContext = org.apache.http.ssl.SSLContexts.custom()
                .loadTrustMaterial(null, acceptingTrustStrategy)
                .build();
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }   

    HostnameVerifier hostnameVerifier = new HostnameVerifier() {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    };

    final SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext,hostnameVerifier);

    final Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
            .register("http", new PlainConnectionSocketFactory())
            .register("https", csf)
            .build();

    final PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
    cm.setMaxTotal(100);
    httpClient = HttpClients.custom()
            .setSSLSocketFactory(csf)
            .setConnectionManager(cm)
            .build();

    HttpComponentsClientHttpRequestFactory requestFactory =
            new HttpComponentsClientHttpRequestFactory();

    requestFactory.setHttpClient(httpClient);

    return requestFactory;
}
Alfredo
  • 15
  • 2
1

I have also come across this issue whilst upgrading from Java 1.6_29 to 1.7.

Alarmingly, my customer has discovered a setting in the Java control panel which resolves this.

In the Advanced Tab you can check 'Use SSL 2.0 compatible ClientHello format'.

This seems to resolve the issue.

We are using Java applets in an Internet Explorer browser.

Hope this helps.

Allan D
  • 51
  • 3
1

Here is solution for Appache httpclient 4.5.11. I had problem with cert which has subject wildcarded *.hostname.com. It returned me same exception, but I musn't use disabling by property System.setProperty("jsse.enableSNIExtension", "false"); because it made error in Google location client.

I found simple solution (only modifying socket):

import io.micronaut.context.annotation.Bean;
import io.micronaut.context.annotation.Factory;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;

import javax.inject.Named;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.util.List;

@Factory
public class BeanFactory {

    @Bean
    @Named("without_verify")
    public HttpClient provideHttpClient() {
        SSLConnectionSocketFactory connectionSocketFactory = new SSLConnectionSocketFactory(SSLContexts.createDefault(), NoopHostnameVerifier.INSTANCE) {
            @Override
            protected void prepareSocket(SSLSocket socket) throws IOException {
                SSLParameters parameters = socket.getSSLParameters();
                parameters.setServerNames(List.of());
                socket.setSSLParameters(parameters);
                super.prepareSocket(socket);
            }
        };

        return HttpClients.custom()
                .setSSLSocketFactory(connectionSocketFactory)
                .build();
    }


}
Andrew Sneck
  • 724
  • 9
  • 18
0

I had the same problem with an Ubuntu Linux server running subversion when accessed via Eclipse.

It has shown that the problem had to do with a warning when Apache (re)started:

[Mon Jun 30 22:27:10 2014] [warn] NameVirtualHost *:80 has no VirtualHosts

... waiting [Mon Jun 30 22:27:11 2014] [warn] NameVirtualHost *:80 has no VirtualHosts

This has been due to a new entry in ports.conf, where another NameVirtualHost directive was entered alongside the directive in sites-enabled/000-default.

After removing the directive in ports.conf, the problem had vanished (after restarting Apache, naturally)

Kevin Panko
  • 8,356
  • 19
  • 50
  • 61
0

Just to add a solution here. This might help for LAMP users

Options +FollowSymLinks -SymLinksIfOwnerMatch

The above mentioned line in the virtual host configuration was the culprit.

Virtual Host Configuration when error

<VirtualHost *:80>
    DocumentRoot /var/www/html/load/web
    ServerName dev.load.com
    <Directory "/var/www/html/load/web">
        Options +FollowSymLinks -SymLinksIfOwnerMatch
        AllowOverride All
        Require all granted
        Order Allow,Deny
        Allow from All
    </Directory>
     RewriteEngine on
     RewriteCond %{SERVER_PORT} !^443$
     RewriteRule ^/(.*) https://%{HTTP_HOST}/$1 [NC,R=301,L]
</VirtualHost>

Working Configuration

<VirtualHost *:80>
    DocumentRoot /var/www/html/load/web

   ServerName dev.load.com
   <Directory "/var/www/html/load/web">

        AllowOverride All

        Options All

        Order Allow,Deny

        Allow from All

    </Directory>

    # To allow authorization header
    RewriteEngine On
    RewriteCond %{HTTP:Authorization} ^(.*)
    RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]

   # RewriteCond %{SERVER_PORT} !^443$
   # RewriteRule ^/(.*) https://%{HTTP_HOST}/$1 [NC,R=301,L]


</VirtualHost>
Griffin
  • 25
  • 6
-1

There is an easier way where you can just use your own HostnameVerifier to implicitly trust certain connections. The issue comes with Java 1.7 where SNI extensions have been added and your error is due to a server misconfiguration.

You can either use "-Djsse.enableSNIExtension=false" to disable SNI across the whole JVM or read my blog where I explain how to implement a custom verifier on top of a URL connection.

MagicDude4Eva
  • 137
  • 2
  • 7