最近、LetsEncrypt証明書をサーバーに追加しましたが、Java=アプレットがTLSを使用した接続に問題があります。
私のアプレットはApache HttpClientを使用しています。
私のWebサーバーはApache 2,4で、メインドメイン(foo.com-実際のドメイン名ではない)のサブドメインとしていくつかの仮想ホストを設定しています。
ステージングサブドメインでアプレットを実行すると(たとえば、 https://staging.foo.com で実行されます)、次のエラーが発生します。
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が私の仮想ホストの1つであることを知っています(この仮想ホストはアルファベット順に、SSLがオンになっている最初のものですが) 。
Staging.foo.comの証明書の詳細を確認しましたが、[Subject Alternative Name]フィールドの下にリストされている名前はstaging.foo.comだけです。
では、どこからdeveloper.foo.comを取得するのでしょうか。
そして、どうすればこの問題を修正できますか?
OS X El Capitan 10.11.6でFirefoxを使用しており、次のJavaプラグインバージョン情報を使用します:
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ファイルです。
<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によって追加されました。
これらの各サブドメインへのアクセスは、最新のブラウザー(Chromeなど)でも問題ないことを付け加えておきます。
HttpClient 4.4を使用する場合は、ホスト検証(NoopHostnameVerifier)を指定して、さまざまなホストからの証明書を受け入れることができるようにする必要があります。
SSLConnectionSocketFactory scsf = SSLConnectionSocketFactory(
SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build(),
NoopHostnameVerifier.INSTANCE)
httpclient = HttpClients.custom().setSSLSocketFactory(scsf).build()
使用しているApache HttpClientのバージョンはわかりませんが、バージョン4.4.1および4.5.1には、SNIが正しく機能しないバグがありました。これは4.5.3で修正されました
Yurriのコメントに続いて、SSLConnectionSocketFactoryの初期化中にNoopHostnameVerifier.INSTANCEを追加することで問題を解決しました。
import org.Apache.http.HttpHost;
import org.Apache.http.conn.ssl.NoopHostnameVerifier;
import org.Apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.Apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.Apache.http.impl.client.CloseableHttpClient;
import org.Apache.http.impl.client.HttpClientBuilder;
import org.Apache.http.impl.client.HttpClients;
import org.Apache.http.ssl.TrustStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import Java.net.Proxy;
import Java.nio.charset.StandardCharsets;
import Java.security.KeyManagementException;
import Java.security.KeyStoreException;
import Java.security.NoSuchAlgorithmException;
import javax.net.ssl.SSLContext;
/**
* Provide basic Utils for getting HttpHeader and making REST api calls.
*
*/
@Component
public class HttpUtil {
private static final Logger LOG = LoggerFactory.getLogger(HttpUtil.class);
/**
* The default implementation to get basic headers.
* @return HttpHeaders.
*/
public HttpHeaders getHttpHeaders(String userAgent, String Host) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set(HttpHeaders.ACCEPT_CHARSET, StandardCharsets.UTF_8.name());
headers.set(HttpHeaders.USER_AGENT, userAgent);
LOG.info("Host=" + Host);
if (null != Host) {
headers.set(HttpHeaders.Host, Host);
}
return headers;
}
/**
* Default implementation to get RestTemplate
* @return
*/
public RestTemplate getRestTemplate(String proxyHost, int proxyPort)
throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
TrustStrategy acceptingTrustStrategy = new TrustSelfSignedStrategy();
SSLContext sslContext = org.Apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy)
.build();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
if (null != proxyHost && proxyPort > 0) {
LOG.info("PROXY CONFIGURED | proxyHost=" + proxyHost + " | proxyPort=" + proxyPort);
HttpHost proxy = new HttpHost(proxyHost, proxyPort, Proxy.Type.HTTP.name());
httpClient = HttpClients.custom().setSSLSocketFactory(csf)
.setRoutePlanner(new DefaultProxyRoutePlanner(proxy)).build();
}
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
return restTemplate;
}
/**
* Make a rest api call
* @return ResponseEntity
*/
public ResponseEntity<String> getApiResponse(HttpMethod httpMethod, final String URL, final String userAgent,
String proxyHost, int proxyPort, String Host) throws HttpClientErrorException {
ResponseEntity<String> response = null;
HttpEntity<String> httpEntity = new HttpEntity<>(getHttpHeaders(userAgent, Host));
try {
if (null != httpMethod && null != URL) {
RestTemplate request = null;
try {
request = getRestTemplate(proxyHost, proxyPort);
response = request.exchange(URL, httpMethod, httpEntity, String.class);
} catch (KeyManagementException | KeyStoreException | NoSuchAlgorithmException e) {
LOG.error("Error creating Rest Template", e);
}
}
} catch (HttpClientErrorException ex) {
LOG.error("Method = " + httpMethod.toString() + "Request URL = " + URL);
LOG.error("Headers =" + getHttpHeaders(userAgent, Host));
LOG.error("Response Status = " + ex.getStatusText());
LOG.error("Response Body = " + ex.getResponseBodyAsString());
throw ex;
}
return response;
}
}
Httpリクエストを作成するためにorg.Apache.http。*のメソッドを使用していたときにも、同じエラーが発生していました。あなたのスタックトレースから、私もあなたが同じものを使用していると思います。
Java.net.HttpURLConnectionを使用すると、このエラーは消え、正常に接続できました。
import Java.net.HttpURLConnection;
public static HttpURLConnection connectToWeb(String uri) {
HttpURLConnection connection = null;
try {
URL url = new URL(uri);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.connect();
} catch (MalformedURLException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
}
return connection;
}