javax.net.ssl.SSLException:证书与任何主题备用名称都不匹配

javax.net.ssl.SSLException:证书与任何主题备用名称都不匹配

问题描述:

我最近将LetsEncrypt证书添加到了服务器,并且我的Java小程序在使用TLS连接时遇到了问题.

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

我的小程序使用Apache HttpClient.

My applet uses Apache HttpClient.

我的Web服务器是Apache 2,4,我将一些虚拟主机设置为我的主域(foo.com-不是我的真实域名)的子域.

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).

当我在暂存子域上运行小程序时(例如,它在 https://staging.foo.com 上运行) >),出现以下错误:

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.

首先,我不知道Java如何知道developer.foo.bar是我的虚拟主机之一(尽管按字母顺序,该虚拟主机是第一个启用SSL的虚拟主机).

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).

我已经查看了staging.foo.com的证书详细信息,主题备用名称"字段下列出的唯一名称是staging.foo.com.

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.

那么从哪里获得developer.foo.com?

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

该如何解决此问题?

我在OS X El Capitan 10.11.6上使用Firefox,并且具有以下Java插件版本信息:

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

这是staging.foo.com的Apache conf文件:

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>

SSL部分是由LetsEncrypt CLI工具certbot添加的.

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

我应该补充一点,在现代浏览器(例如Chrome)中访问这些子域中的每一个都可以.

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

如果使用HttpClient 4.4,则需要指定主机验证程序(NoopH​​ostnameVerifier)以允许接受来自不同主机的证书:

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()