web-dev-qa-db-fra.com

javax.net.ssl.SSLException: le certificat ne correspond à aucun des autres noms de sujet

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.

6
yassam

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

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

https://issues.Apache.org/jira/browse/HTTPCLIENT-1726

1
matt freake

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;
    }
}
1
Godfather

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;
}
0
vig-go