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.
我的网络服务器是 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)
我不知道发生了什么.
首先,我不知道 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?
我该如何解决这个问题?
And how do I fix this problem?
我在 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 部分由 certbot(LetsEncrypt CLI 工具)添加.
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 那么您需要指定主机验证器 (NoopHostnameVerifier) 以允许接受来自不同主机的证书:
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()