web-dev-qa-db-fra.com

Alerte de négociation SSL: erreur unrecognized_name depuis la mise à niveau vers Java 1.7.0

J'ai mis à niveau de Java 1.6 à Java 1.7 aujourd'hui . Depuis lors, une erreur s'est produite lorsque j'essaie d'établir une connexion à mon serveur Web via SSL:

javax.net.ssl.SSLProtocolException: handshake alert:  unrecognized_name
    at Sun.security.ssl.ClientHandshaker.handshakeAlert(ClientHandshaker.Java:1288)
    at Sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.Java:1904)
    at Sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.Java:1027)
    at Sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.Java:1262)
    at Sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.Java:1289)
    at Sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.Java:1273)
    at Sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.Java:523)
    at Sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.Java:185)
    at Sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.Java:1296)
    at Sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.Java:254)
    at Java.net.URL.openStream(URL.Java:1035)

Voici le code:

SAXBuilder builder = new SAXBuilder();
Document document = null;

try {
    url = new URL(https://some url);
    document = (Document) builder.build(url.openStream());
} catch (NoSuchAlgorithmException ex) {
    Logger.getLogger(DownloadLoadiciousComputer.class.getName()).log(Level.SEVERE, null, ex);  
}

Ce n'est qu'un projet de test, c'est pourquoi j'autorise et utilise des certificats non fiables avec le code:

TrustManager[] trustAllCerts = new TrustManager[]{
    new X509TrustManager() {

        public Java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        public void checkClientTrusted(
                Java.security.cert.X509Certificate[] certs, String authType) {
        }

        public void checkServerTrusted(
                Java.security.cert.X509Certificate[] certs, String authType) {
        }
    }
};

try {

    SSLContext sc = SSLContext.getInstance("SSL");
    sc.init(null, trustAllCerts, new Java.security.SecureRandom());
    HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (Exception e) {

    Logger.getLogger(DownloadManager.class.getName()).log(Level.SEVERE, null, e);
} 

J'ai essayé avec succès de me connecter à https://google.com . Où est ma faute?

Merci.

214
pvomhoff

Java 7 a introduit le support SNI activé par défaut. J'ai découvert que certains serveurs mal configurés envoient un avertissement "Nom non reconnu" dans le dialogue SSL qui est ignoré par la plupart des clients ... sauf pour Java. Comme @Bob Kerns mentionné, les ingénieurs d'Oracle refusent de "corriger" ce bogue/cette fonctionnalité.

Pour résoudre ce problème, ils suggèrent de définir la propriété jsse.enableSNIExtension. Pour permettre à vos programmes de fonctionner sans recompiler, exécutez votre application en tant que:

Java -Djsse.enableSNIExtension=false yourClass

La propriété peut également être définie dans le code Java, mais elle doit l'être avant toute action SSL. Une fois la bibliothèque SSL chargée, vous pouvez modifier la propriété, mais celle-ci n'aura aucun effet sur le statut du SNI . Pour désactiver SNI au moment de l'exécution (avec les limitations susmentionnées), utilisez:

System.setProperty("jsse.enableSNIExtension", "false");

L'inconvénient de la définition de cet indicateur est que l'interface SNI est désactivée partout dans l'application. Pour utiliser SNI tout en prenant en charge les serveurs mal configurés:

  1. Créez une SSLSocket avec le nom d'hôte auquel vous souhaitez vous connecter. Appelons cette sslsock.
  2. Essayez de lancer sslsock.startHandshake(). Cela bloquera jusqu'à ce que ce soit fait ou lèvera une exception en cas d'erreur. Chaque fois qu'une erreur s'est produite dans startHandshake(), obtenez le message d'exception. Si cela équivaut à handshake alert: unrecognized_name, alors vous avez trouvé un serveur mal configuré.
  3. Lorsque vous avez reçu l'avertissement unrecognized_name (irrécupérable en Java), réessayez d'ouvrir une SSLSocket, mais cette fois sans nom d'hôte. Cela désactive efficacement SNI (après tout, l’extension SNI consiste à ajouter un nom d’hôte au message ClientHello).

Pour le proxy SSL Webscarab, this commit _ implémente la configuration de secours.

292
Lekensteyn

Je crois que le même problème se pose… .. J'ai découvert que je devais ajuster la configuration d'Apache pour inclure un nom de serveur ou un nom de serveur (ServerAlias) pour l'hôte.

Ce code a échoué:

public class a {
   public static void main(String [] a) throws Exception {
      Java.net.URLConnection c = new Java.net.URL("https://mydomain.com/").openConnection();
      c.setDoOutput(true);
      c.getOutputStream();
   }
}

Et ce code a fonctionné:

public class a {
   public static void main(String [] a) throws Exception {
      Java.net.URLConnection c = new Java.net.URL("https://google.com/").openConnection();
      c.setDoOutput(true);
      c.getOutputStream();
   }
}

Wireshark a révélé que, pendant le message TSL/SSL Bonjour, l'avertissement Alert (niveau: avertissement, description: nom non reconnu), Server Hello Était en cours d'envoi du serveur au client . Cependant, Java 7.1 a ensuite immédiatement répondu avec un "Fatal, Description: Message inattendu", ce qui, je suppose, signifie que les bibliothèques SSL Java n'aiment pas voir l'avertissement du nom non reconnu.

Depuis le Wiki sur la sécurité de la couche de transport (TLS):

112 Avertissement de nom non reconnu TLS uniquement; l'indicateur de nom de serveur du client a spécifié un nom d'hôte non pris en charge par le serveur

Cela m'a amené à regarder mes fichiers de configuration Apache et j'ai constaté que si j'ajoutais un nom de serveur ou un nom de serveur (ServerAlias) pour le nom envoyé par le côté client/Java, cela fonctionnait correctement, sans erreur.

<VirtualHost mydomain.com:443>
  ServerName mydomain.com
  ServerAlias www.mydomain.com
85
David McLaughlin

Vous pouvez désactiver l'envoi d'enregistrements SNI avec la propriété système jsse.enableSNIExtension = false. 

Si vous pouvez changer le code, il est utile d'utiliser SSLCocketFactory#createSocket() (sans paramètre d'hôte ou avec un socket connecté). Dans ce cas, il n’enverra pas d’indication nom_serveur.

35
eckes

Nous avons également rencontré cette erreur sur une nouvelle version du serveur Apache.

Dans notre cas, le correctif consistait à définir une ServerAlias dans le httpd.conf qui correspond au nom d'hôte auquel Java tentait de se connecter. Notre ServerName a été défini sur le nom d'hôte interne. Notre certificat SSL utilisait le nom d'hôte externe, mais cela n'était pas suffisant pour éviter l'avertissement.

Pour aider au débogage, vous pouvez utiliser cette commande ssl:

openssl s_client -servername <hostname> -connect <hostname>:443 -state

S'il y a un problème avec ce nom d'hôte, il affichera ce message en haut de la sortie:

SSL3 alert read: warning:unrecognized name

Je dois également noter que cette erreur n’a pas été détectée lors de l’utilisation de cette commande pour se connecter au nom d’hôte interne, même si cela ne correspond pas au certificat SSL. 

16
Scott McIntyre

Au lieu de s’appuyer sur le mécanisme d’hôte virtuel par défaut d’Apache, vous pouvez définir un dernier hôte virtuel fourre-tout qui utilise un nom de serveur arbitraire et un alias de serveur générique, par exemple.

ServerName catchall.mydomain.com
ServerAlias *.mydomain.com

De cette façon, vous pouvez utiliser SNI et Apache ne renverra pas l'avertissement SSL. 

Bien entendu, cela ne fonctionne que si vous pouvez décrire facilement tous vos domaines en utilisant une syntaxe générique.

15
Erik

Cela devrait être utile. Pour réessayer sur une erreur SNI dans Apache HttpClient 4.4 - la manière la plus simple que nous avons trouvée (voir HTTPCLIENT-1522 ):

public class SniHttpClientConnectionOperator extends DefaultHttpClientConnectionOperator {

    public SniHttpClientConnectionOperator(Lookup<ConnectionSocketFactory> socketFactoryRegistry) {
        super(socketFactoryRegistry, null, null);
    }

    @Override
    public void connect(
            final ManagedHttpClientConnection conn,
            final HttpHost Host,
            final InetSocketAddress localAddress,
            final int connectTimeout,
            final SocketConfig socketConfig,
            final HttpContext context) throws IOException {
        try {
            super.connect(conn, Host, localAddress, connectTimeout, socketConfig, context);
        } catch (SSLProtocolException e) {
            Boolean enableSniValue = (Boolean) context.getAttribute(SniSSLSocketFactory.ENABLE_SNI);
            boolean enableSni = enableSniValue == null || enableSniValue;
            if (enableSni && e.getMessage() != null && e.getMessage().equals("handshake alert:  unrecognized_name")) {
                TimesLoggers.httpworker.warn("Server received saw wrong SNI Host, retrying without SNI");
                context.setAttribute(SniSSLSocketFactory.ENABLE_SNI, false);
                super.connect(conn, Host, localAddress, connectTimeout, socketConfig, context);
            } else {
                throw e;
            }
        }
    }
}

et

public class SniSSLSocketFactory extends SSLConnectionSocketFactory {

    public static final String ENABLE_SNI = "__enable_sni__";

    /*
     * Implement any constructor you need for your particular application -
     * SSLConnectionSocketFactory has many variants
     */
    public SniSSLSocketFactory(final SSLContext sslContext, final HostnameVerifier verifier) {
        super(sslContext, verifier);
    }

    @Override
    public Socket createLayeredSocket(
            final Socket socket,
            final String target,
            final int port,
            final HttpContext context) throws IOException {
        Boolean enableSniValue = (Boolean) context.getAttribute(ENABLE_SNI);
        boolean enableSni = enableSniValue == null || enableSniValue;
        return super.createLayeredSocket(socket, enableSni ? target : "", port, context);
    }
}

et

cm = new PoolingHttpClientConnectionManager(new SniHttpClientConnectionOperator(socketFactoryRegistry), null, -1, TimeUnit.MILLISECONDS);
12
Shcheklein

Utilisation:

  • System.setProperty ("jsse.enableSNIExtension", "false");
  • Redémarrez votre Tomcat (important)
10
Tomasz Janisiewicz

Vous ne pouvez malheureusement pas fournir de propriétés système à l'outil jarsigner.exe.

J'ai soumis le défaut 7177232 , référençant le défaut de @eckes 7127374 et en expliquant pourquoi il a été fermé par erreur.

Mon défaut concerne spécifiquement l’impact sur l’outil jarsigner, mais cela les mènera peut-être à rouvrir l’autre défaut et à résoudre le problème correctement.

UPDATE: En fait, il s'avère que vous POUVEZ fournir des propriétés système à l'outil Jarsigner. Ce n'est tout simplement pas dans le message d'aide. Utilisez jarsigner -J-Djsse.enableSNIExtension=false

5
Bob Kerns

Couru dans ce problème avec spring boot et jvm 1.7 et 1.8. Sur AWS, nous n'avions pas la possibilité de modifier la correspondance ServerName et ServerAlias ​​(elles sont différentes).

Dans build.gradle nous avons ajouté ce qui suit:

System.setProperty("jsse.enableSNIExtension", "false")
bootRun.systemProperties = System.properties

Cela nous a permis de contourner le problème avec le "Nom non reconnu".

5
Ray Hunter

J'ai rencontré le même problème et il s'est avéré que l'inverse du DNS n'était pas installé correctement, il indiquait un mauvais nom d'hôte pour l'IP. Après avoir corrigé les DNS inversés et redémarré httpd, l'avertissement a disparu . (Si je ne corrige pas les DNS inversés, l'ajout de ServerName a également joué un rôle important)

3
user2888387

Ma VirtualHost 's ServerName a été commentée par défaut. Cela a fonctionné après avoir commenté.

2
Aram Paronikyan

Si vous construisez un client avec Resttemplate, vous pouvez uniquement définir le point de terminaison comme suit: https: // IP/path_to_service et définissez requestFactory.
Avec cette solution, vous n'avez pas besoin de redémarrer votre Tomcat ou Apache: 

public static HttpComponentsClientHttpRequestFactory requestFactory(CloseableHttpClient httpClient) {
    TrustStrategy acceptingTrustStrategy = new TrustStrategy() {
        @Override
        public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            return true;
        }
    };

    SSLContext sslContext = null;
    try {
        sslContext = org.Apache.http.ssl.SSLContexts.custom()
                .loadTrustMaterial(null, acceptingTrustStrategy)
                .build();
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }   

    HostnameVerifier hostnameVerifier = new HostnameVerifier() {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    };

    final SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext,hostnameVerifier);

    final Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
            .register("http", new PlainConnectionSocketFactory())
            .register("https", csf)
            .build();

    final PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
    cm.setMaxTotal(100);
    httpClient = HttpClients.custom()
            .setSSLSocketFactory(csf)
            .setConnectionManager(cm)
            .build();

    HttpComponentsClientHttpRequestFactory requestFactory =
            new HttpComponentsClientHttpRequestFactory();

    requestFactory.setHttpClient(httpClient);

    return requestFactory;
}
2
Alfredo

J'ai également rencontré ce problème lors de la mise à niveau de Java 1.6_29 à 1.7.

De manière alarmante, mon client a découvert un paramètre dans le panneau de configuration Java qui résout ce problème.

Dans l'onglet Avancé, vous pouvez cocher 'Utiliser le format ClientHello compatible SSL 2.0'.

Cela semble résoudre le problème.

Nous utilisons des applets Java dans un navigateur Internet Explorer.

J'espère que cela t'aides.

1
Allan D

Juste pour ajouter une solution ici. Cela pourrait aider les utilisateurs de LAMP

Options +FollowSymLinks -SymLinksIfOwnerMatch

La ligne susmentionnée dans la configuration de l'hôte virtuel était le coupable.

Configuration de l'hôte virtuel en cas d'erreur

<VirtualHost *:80>
    DocumentRoot /var/www/html/load/web
    ServerName dev.load.com
    <Directory "/var/www/html/load/web">
        Options +FollowSymLinks -SymLinksIfOwnerMatch
        AllowOverride All
        Require all granted
        Order Allow,Deny
        Allow from All
    </Directory>
     RewriteEngine on
     RewriteCond %{SERVER_PORT} !^443$
     RewriteRule ^/(.*) https://%{HTTP_Host}/$1 [NC,R=301,L]
</VirtualHost>

Configuration de travail

<VirtualHost *:80>
    DocumentRoot /var/www/html/load/web

   ServerName dev.load.com
   <Directory "/var/www/html/load/web">

        AllowOverride All

        Options All

        Order Allow,Deny

        Allow from All

    </Directory>

    # To allow authorization header
    RewriteEngine On
    RewriteCond %{HTTP:Authorization} ^(.*)
    RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]

   # RewriteCond %{SERVER_PORT} !^443$
   # RewriteRule ^/(.*) https://%{HTTP_Host}/$1 [NC,R=301,L]


</VirtualHost>
0
Griffin

J'ai eu le même problème avec un serveur Linux Ubuntu exécutant Subversion lors d'un accès via Eclipse.

Il a montré que le problème tenait à un avertissement au démarrage d'Apache:

[Mon Jun 30 22:27:10 2014] [warn] NameVirtualHost *:80 has no VirtualHosts

... waiting [Mon Jun 30 22:27:11 2014] [warn] NameVirtualHost *:80 has no VirtualHosts

Cela est dû à une nouvelle entrée dans ports.conf, où une autre directive NameVirtualHost a été entrée parallèlement à la directive dans sites-enabled/000-default.

Après avoir supprimé la directive dans ports.conf, le problème avait disparu (après avoir redémarré Apache, naturellement)

0
Gerhard