J'ai récemment ajouté des certificats LetsEncrypt à mon serveur et mon Java rencontre des problèmes de connexion à l'aide de TLS.
Mon applet utilise Apache HttpClient.
Mon serveur Web est Apache 2,4 et j'ai quelques hôtes virtuels configurés comme sous-domaines de mon domaine principal (foo.com - pas mon vrai nom de domaine).
Lorsque j'exécute mon applet sur le sous-domaine de transfert (par exemple, il s'exécute https://staging.foo.com ), j'obtiens l'erreur suivante:
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)
Je ne ai aucune idée de ce qui se passe.
Tout d'abord, je ne sais pas comment Java sait que developer.foo.bar est l'un de mes hôtes virtuels (bien que cet hôte virtuel soit le premier, par ordre alphabétique, avec SSL activé) .
J'ai regardé les détails du certificat pour staging.foo.com, et le seul nom répertorié sous le champ "Subject Alternative Name" est staging.foo.com.
Alors d'où vient-il developer.foo.com?
Et comment résoudre ce problème?
J'utilise Firefox sur OS X El Capitan 10.11.6 avec les informations de version Java plugin 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
Voici le fichier de configuration Apache pour 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>
Les sections SSL ont été ajoutées par certbot, l'outil CLI LetsEncrypt.
Je dois ajouter que l'accès à chacun de ces sous-domaines dans un navigateur moderne (tel que Chrome) est très bien.
Si vous utilisez HttpClient 4.4, vous devez spécifier le vérificateur d'hôte (NoopHostnameVerifier) pour autoriser l'acceptation de certificats provenant de différents hôtes:
SSLConnectionSocketFactory scsf = SSLConnectionSocketFactory(
SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build(),
NoopHostnameVerifier.INSTANCE)
httpclient = HttpClients.custom().setSSLSocketFactory(scsf).build()
Je ne sais pas quelle version de Apache HttpClient vous utilisiez mais les versions 4.4.1 et 4.5.1 avaient un bug où le SNI ne fonctionnait pas correctement. Cela a été corrigé dans 4.5.3
Suite au commentaire de Yurri, il a résolu mon problème en ajoutant NoopHostnameVerifier.INSTANCE lors de l'initialisation de SSLConnectionSocketFactory:
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;
}
}
J'obtenais la même erreur lorsque j'utilisais des méthodes de org.Apache.http. * Pour effectuer mes requêtes http. De votre trace de pile, je suppose que même vous utilisez la même chose.
Cette erreur a disparu lorsque j'ai utilisé Java.net.HttpURLConnection et que j'ai pu me connecter avec succès.
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;
}