javax.net.ssl.SSLException: Certificate doesn't match any of the subject alternative names

yassam picture yassam · Sep 29, 2016 · Viewed 44.3k times · Source

I recently added LetsEncrypt certificates to my server and my java applet is having problems connecting using TLS.

My applet uses Apache HttpClient.

My web server is Apache 2,4, and I have a few virtual hosts set up as subdomains of my main domain (foo.com - not my real domain name).

When I run my applet on the staging subdomain (e.g. it runs off https://staging.foo.com), I get the following error:

javax.net.ssl.SSLException: Certificate for <staging.foo.com> doesn't match any of the subject alternative names: [developer.foo.com]
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:165)
at org.apache.http.conn.ssl.BrowserCompatHostnameVerifier.verify(BrowserCompatHostnameVerifier.java:61)
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:141)
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:114)
at org.apache.http.conn.ssl.SSLSocketFactory.verifyHostname(SSLSocketFactory.java:580)
at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:554)
at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:412)
at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:179)
at org.apache.http.impl.conn.ManagedClientConnectionImpl.open(ManagedClientConnectionImpl.java:328)
at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:612)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:447)
at org.apache.http.impl.client.AbstractHttpClient.doExecute(AbstractHttpClient.java:884)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:107)
...(cut)
at javax.swing.SwingWorker$1.call(SwingWorker.java:295)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at javax.swing.SwingWorker.run(SwingWorker.java:334)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)

I have no idea what's going on.

First of all, I have no idea how Java knows that developer.foo.bar is one of my virtual hosts (although this virtual host is the first one, alphabetically, that has SSL turned on).

I've looked at the certificate detail for staging.foo.com, and the only name listed under the "Subject Alternative Name" field is staging.foo.com.

So where is it getting developer.foo.com from?

And how do I fix this problem?

I'm using Firefox on OS X El Capitan 10.11.6 with the following Java plugin version info:

Java Plug-in 11.102.2.14 x86_64
Using JRE version 1.8.0_102-b14 Java HotSpot(TM) 64-Bit Server VM

This is the Apache conf file for staging.foo.com:

<IfModule mod_ssl.c>
<VirtualHost *:443>
    ServerName staging.foo.com
    ServerAlias www.staging.foo.com

    # Turn on HTTP Strict Transport Security (HSTS). This tells the
    # client that it should only communicate with this site using
    # HTTPS. See
    # https://raymii.org/s/tutorials/HTTP_Strict_Transport_Security_for_Apache_NGINX_and_Lighttpd.html
    Header always set Strict-Transport-Security "max-age=31536000; includeSubdomains;"

    # The following is used to tunnel websocket requests to daphne, so
    # that Django Channels can do its thing
    ProxyPass "/ws/" "ws://localhost:8001/ws/"
    ProxyPassReverse "/ws/" "ws://localhost:8001/ws/"

    # The following is used during deployment. Every page request is
    # served from one static html file.
    RewriteEngine       on
    RewriteCond         /home/www-mm/staging.foo.com/apache/in_maintenance -f
    RewriteRule .*      /home/www-mm/staging.foo.com/static/maintenance/maintenance.html

    # Use Apache to serve protected (non-static) files. This is so that
    # Apache can deal with ranges
    XSendFile on
    XSendFilePath /home/www-mm/staging.foo.com/user_assets

    # Limit uploads - 200MB
    LimitRequestBody 209715200

    Alias /static/ /home/www-mm/staging.foo.com/serve_static/
    Alias /robots.txt /home/www-mm/staging.foo.com/apache/serve-at-root/robots.txt

    <Directory /home/www-mm/staging.foo.com/serve_static>
        AddOutputFilterByType DEFLATE text/html text/css text/javascript application/javascript application/json
        Order deny,allow
        Require all granted
    </Directory>

    # Videos uploaded via staff to home page should never cache,
    # because they can change at any time (and we don't know if the
    # URLs will change or not). Etags are used and only headers are
    # sent if the files in question aren't modified (we get a 304
    # back)
    <Directory /home/www-mm/staging.foo.com/serve_static/video>
        ExpiresActive On
        # Expire immediately
        ExpiresDefault A0
    </Directory>

    # The following ensures that the maintenance page is never cached.
    <Directory /home/www-mm/staging.foo.com/static/maintenance>
        ExpiresActive On
        # Expire immediately
        ExpiresDefault A0
        Require all granted
    </Directory>

    # Hide uncompressed code from prying eyes. Python needs access to this code for the css compressor
    <Directory /home/www-mm/staging.foo.com/serve_static/js/muso>
        <Files ~ "\.js$">
            Deny from all
        </Files>
        # Order deny,allow
        # Deny from all
    </Directory>

    # Hide uncompressed code from prying eyes. Python needs access to this code for the css compressor
    <DirectoryMatch "/home/www-mm/staging.foo.com/serve_static/js/dist/.*/muso">
        Order deny,allow
        Deny from all
    </DirectoryMatch>

    <Directory /home/www-mm/staging.foo.com/apache>
        <Files django.wsgi>
            Order deny,allow
            Require all granted
        </Files>
    </Directory>

    WSGIScriptAlias / /home/www-mm/staging.foo.com/apache/django.wsgi
    WSGIDaemonProcess staging.foo.com user=www-mm group=www-mm
    WSGIProcessGroup staging.foo.com

    ErrorLog /var/log/apache2/staging.foo.com-error.log
    CustomLog /var/log/apache2/staging.foo.com-access.log combined

    SSLCertificateFile /etc/letsencrypt/live/staging.foo.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/staging.foo.com/privkey.pem
    Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>

The SSL sections were added by certbot, the LetsEncrypt CLI tool.

I should add that accessing each of these subdomains in a modern browser (such as Chrome) is fine.

Answer

Yurii picture Yurii · Feb 15, 2018

If you use HttpClient 4.4 then you need to specify host verifier (NoopHostnameVerifier) to allow accepting certificates from different hosts:

SSLConnectionSocketFactory scsf = SSLConnectionSocketFactory(
     SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build(), 
        NoopHostnameVerifier.INSTANCE)
httpclient = HttpClients.custom().setSSLSocketFactory(scsf).build()