Avviso di handshake SSL: errore unrecognized_name dall'aggiornamento a Java 1.7.0


225

Oggi sono passato da Java 1.6 a Java 1.7. Da allora si verifica un errore quando provo a stabilire una connessione al mio server web su 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)

Ecco il codice:

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);  
}

È solo un progetto di test ed è per questo che autorizzo e utilizzo certificati non attendibili con il codice:

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);
} 

Ho provato con successo a connettermi a https://google.com . dov'è la mia colpa?

Grazie.

Risposte:


304

Java 7 ha introdotto il supporto SNI che è abilitato di default. Ho scoperto che alcuni server non configurati correttamente inviano un avviso "Nome non riconosciuto" nell'handshake SSL che viene ignorato dalla maggior parte dei client ... tranne Java. Come menzionato da @Bob Kerns , gli ingegneri Oracle si rifiutano di "correggere" questo bug / funzionalità.

Come soluzione alternativa, suggeriscono di impostare la jsse.enableSNIExtensionproprietà. Per consentire ai programmi di funzionare senza ricompilare, eseguire l'app come:

java -Djsse.enableSNIExtension=false yourClass

La proprietà può anche essere impostata nel codice Java, ma deve essere impostata prima di qualsiasi azione SSL . Una volta caricata la libreria SSL, è possibile modificare la proprietà, ma non avrà alcun effetto sullo stato SNI . Per disabilitare SNI in fase di esecuzione (con le limitazioni di cui sopra), utilizzare:

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

Lo svantaggio di impostare questo flag è che SNI è disabilitato ovunque nell'applicazione. Per utilizzare SNI e supportare ancora server non configurati correttamente:

  1. Crea un SSLSocketcon il nome host a cui desideri connetterti. Chiamiamo questo sslsock.
  2. Prova a correre sslsock.startHandshake(). Questo si bloccherà fino a quando non verrà eseguito o genererà un'eccezione in caso di errore. Ogni volta che si è verificato un errore startHandshake(), ottieni il messaggio di eccezione. Se è uguale a handshake alert: unrecognized_name, hai trovato un server configurato male.
  3. Quando hai ricevuto l' unrecognized_nameavviso (fatale in Java), riprova ad aprire a SSLSocket, ma questa volta senza un nome host. Ciò disabilita effettivamente SNI (dopo tutto, l'estensione SNI riguarda l'aggiunta di un nome host al messaggio ClientHello).

Per il proxy SSL Webscarab, questo commit implementa la configurazione fall-back.


5
funziona, grazie! Il client di subversione IDEA IntelliJ ha riscontrato lo stesso errore durante la connessione tramite HTTPS. Devo solo aggiornare il file idea.exe.vmoptions con la riga: -Djsse.enableSNIExtension = false
Dima

2
Per java webstart usa questo: javaws -J-Djsse.enableSNIExtension = false yourClass
Torsten

Ho avuto lo stesso identico problema con il mio client Jersey con il server di riposo https. Ho scoperto che ho usato il tuo trucco e ha funzionato !! Grazie!
shahshi15,

4
Per coloro che si interrogano su Java 8, Oracle non ha ancora modificato il comportamento e richiede ancora al programmatore di catturare server che non supportano (correttamente) SNI: docs.oracle.com/javase/8/docs/technotes/guides/security/jsse /…
Lekensteyn,

2
@Blauhirn Contatta il supporto del tuo host web, dovrebbero sistemare la loro configurazione. Se usano Apache, cerca alcune modifiche che devono essere apportate.
Lekensteyn,

89

Ho avuto quello che credo sia lo stesso problema. Ho scoperto che avevo bisogno di regolare la configurazione di Apache per includere un ServerName o ServerAlias ​​per l'host.

Questo codice non è riuscito:

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();
   }
}

E questo codice ha funzionato:

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 ha rivelato che durante il Hello TSL / SSL l'avviso di avviso (Livello: avviso, descrizione: nome non riconosciuto), il server Hello veniva inviato dal server al client. Era solo un avvertimento, tuttavia, Java 7.1 ha risposto immediatamente con un "Descrizione fatale: messaggio imprevisto", che presumo significhi che alle librerie SSL Java non piace vedere l'avvertimento di un nome non riconosciuto.

Dal Wiki sulla Transport Layer Security (TLS):

112 Avviso nome non riconosciuto solo TLS; L'indicatore del nome server del client ha specificato un nome host non supportato dal server

Questo mi ha portato a guardare i miei file di configurazione di Apache e ho scoperto che se aggiungevo un ServerName o ServerAlias ​​per il nome inviato dal lato client / java, funzionava correttamente senza errori.

<VirtualHost mydomain.com:443>
  ServerName mydomain.com
  ServerAlias www.mydomain.com

3
Sembra un bug nell'implementazione TLS di Java.
Paŭlo Ebermann,

1
In realtà apro un bug per questo. Mi è stato assegnato questo numero (non ancora visibile): bugs.sun.com/bugdatabase/view_bug.do?bug_id=7127374
diamine

1
Grazie, aggiungendo un ha ServerAliasfunzionato per me. Avevo visto lo stesso avviso TLS a Wireshark.
Jared Beck,

2
Questo ha funzionato anche per me. (avrebbe funzionato meglio se ci avessi creduto e non avessi cercato un altro percorso)
utente finale

10
@JosephShraibman, dire che l'impiegato Oracle è un idiota è inappropriato. Quando si utilizzano due ServerNames, Apache risponde con un unrecognized_name avvertimento avviso (che potrebbe anche essere un fatale avviso). RFC 6066 dice esattamente questo su questo argomento: " NON È RACCOMANDATO di inviare un avviso unrecognized_name (112) a livello di avviso, perché il comportamento del client in risposta a avvisi a livello di avviso è imprevedibile. ". L'unico errore commesso da questo dipendente è presumere che si sia trattato di un allarme fatale . Questo è tanto un bug JRE quanto uno Apache.
Bruno,

36

È possibile disabilitare l'invio di record SNI con la proprietà di sistema jsse.enableSNIExtension = false.

Se è possibile modificare il codice, può essere utile SSLCocketFactory#createSocket()(senza parametri host o con un socket collegato). In questo caso non invierà un'indicazione nome_server.


11
Il che è fatto in questo modo:System.setProperty ("jsse.enableSNIExtension", "false");
jn1kk,

Ho scoperto che non sempre funziona. Non so perché funzioni in alcuni casi e non in altri.
Joseph Shraibman,

2
Funziona con me -Djsse.enableSNIExtension=false. Ma cos'altro fa? È dannoso per il resto della sicurezza di Javas?
ceving

2
No significa solo che alcuni siti (in particolare i server Web) che utilizzano nomi host multipli dietro un IP condiviso non sanno quale certificato inviare. Ma a meno che la tua app Java non debba connettersi a milioni di siti Web, non avrai bisogno di quella funzione. Se si incontra un server di questo tipo, la convalida del certificato potrebbe non riuscire e la connessione verrà interrotta. Quindi non si prevedono problemi di sicurezza.
Controlla il

17

Abbiamo anche riscontrato questo errore su una nuova build del server Apache.

La correzione nel nostro caso era definire un ServerAliasin httpd.confche corrispondesse al nome host a cui Java stava cercando di connettersi. Il nostro ServerNameera impostato sul nome host interno. Il nostro certificato SSL utilizzava il nome host esterno, ma ciò non era sufficiente per evitare l'avviso.

Per aiutare il debug, puoi usare questo comando ssl:

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

Se si verifica un problema con quel nome host, stamperà questo messaggio nella parte superiore dell'output:

SSL3 alert read: warning:unrecognized name

Dovrei anche notare che non abbiamo riscontrato questo errore quando si utilizzava quel comando per connettersi al nome host interno, anche se non corrispondeva al certificato SSL.


6
Grazie per aver incluso l'esempio su come riprodurre il problema utilizzando openssl. È molto utile.
sideshowbarker,

2
C'è un modo per un client openssl di vedere la differenza di nome che origina il messaggio SSL3 alert read: warning:unrecognized name? Vedo l'avviso stampato, ma l'output non mi aiuta a vedere effettivamente questa differenza di denominazione
mox601

Questo non funziona per me. Testato con OpenSSL 1.0.1e-fips 11 Feb 2013, OpenSSL 1.0.2k-fips 26 Jan 2017e LibreSSL 2.6.5.
Greg Dubicki,

15

Invece di fare affidamento sul meccanismo host virtuale predefinito in apache, è possibile definire un ultimo virtual host catchall che utilizza un ServerName arbitrario e un ServerAlia jolly, ad es.

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

In questo modo puoi usare SNI e apache non rispedirà l'avviso SSL.

Ovviamente, questo funziona solo se puoi descrivere facilmente tutti i tuoi domini usando una sintassi jolly.


A quanto ho capito, Apache invia questo avviso solo nel caso in cui il client utilizzi un nome non configurato come ServerName o ServerAlias. Pertanto, se si dispone di un certificato con caratteri jolly, specificare tutti i sottodomini utilizzati o utilizzare la *.mydomain.comsintassi per ServerAlias. Nota che puoi aggiungere più alias usando ServerAlias, ad es ServerAlias *.mydomain.com mydomain.com *.myotherdomain.com.
Michael Paesold,

13

Dovrebbe essere utile Per riprovare su un errore SNI in Apache HttpClient 4.4 - il modo più semplice in cui siamo venuti (vedi 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;
            }
        }
    }
}

e

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);
    }
}

e

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

Se lo fai in questo modo. Come si fa a trattare con verifyHostname(sslsock, target)in SSLConnectionSocketFactory.createLayeredSocket? Poiché targetviene modificato in stringa vuota, verifyHostnamenon avrà esito positivo. Mi sto perdendo qualcosa di ovvio qui?
Haozhun,

2
Era esattamente quello che stavo cercando, grazie mille! Non ho trovato nessun altro modo per supportare SNI e server che rispondano contemporaneamente a questo avviso "unrecognized_name".
Korpe,

Questa soluzione funziona, a meno che non si stia utilizzando un server proxy.
Mrog

Dopo aver appena eseguito un debug rapido attraverso il codice client Apache, a partire da confirmHostname (), non riesco a vedere come può funzionare utilizzando DefaultHostnameVerifier. Ho il sospetto che questa soluzione richieda la disabilitazione della verifica del nome host (ad es. Utilizzando un NoopHostnameVerifier), che NON è una buona idea in quanto consente attacchi man-in-the-middle.
FlyingSheep


6

Risolto questo problema con avvio a molla e jvm 1.7 e 1.8. Su AWS, non avevamo la possibilità di modificare ServerName e ServerAlias ​​affinché corrispondessero (sono diversi), quindi abbiamo fatto quanto segue:

In build.gradle abbiamo aggiunto quanto segue:

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

Ciò ci ha permesso di aggirare il problema con il "Nome non riconosciuto".


5

Purtroppo non è possibile fornire le proprietà di sistema allo strumento jarsigner.exe.

Ho presentato il difetto 7177232 , facendo riferimento al difetto di @eckes 7127374 e spiegando perché è stato chiuso per errore.

Il mio difetto riguarda in particolare l'impatto sullo strumento jarsigner, ma forse li porterà a riaprire l'altro difetto e ad affrontare correttamente il problema.

AGGIORNAMENTO: In realtà, risulta che PUOI fornire le proprietà di sistema allo strumento Jarsigner, non è solo nel messaggio di aiuto. Usojarsigner -J-Djsse.enableSNIExtension=false


1
Grazie Bob per il seguito. Purtroppo Oracle non capisce ancora tutto. L'allerta SNI non è fatale, può essere trerata fatale. Ma i client SSL più comuni (ovvero i browser) hanno scelto di ignorarlo poiché non è un vero problema di sicurezza (perché è ancora necessario controllare il certificato del server e rilevare endpoint errati). Naturalmente è triste che un'autorità di certificazione non sia in grado di impostare un server https correttamente configurato.
Verifica il

2
In realtà, risulta che PUOI fornire le proprietà di sistema allo strumento Jarsigner, ma non è nel messaggio di aiuto. È coperto nella documentazione. Stupidami per aver creduto al testo online. Naturalmente, hanno usato questo come una scusa per non risolverlo.
Bob Kerns,

A proposito: sto attualmente discutendo di questo problema su security-dev @ openjdk e al momento in cui lo stavo valutando sembra che Symantec abbia corretto il server di marcatura temporale GEOTrust, ora accetta correttamente l'URL senza preavviso. Ma penso ancora che dovrebbe essere risolto. Se vuoi dare un'occhiata, un progetto del client di prova e la discussione :
Verifica il

3

Ho riscontrato lo stesso problema e si è scoperto che il DNS inverso non era impostato correttamente, indicava un nome host errato per l'IP. Dopo aver corretto DNS inverso e riavviato httpd, l'avviso è sparito. (se non correggo DNS inverso, l'aggiunta di ServerName ha funzionato anche per me)


2

Il mio VirtualHostè ServerNamestato commentato per impostazione predefinita. Ha funzionato dopo il commento.


Anch'io. Nome del server mancante.
Dark Star1,

2

Se si sta creando un client con Resttemplate, è possibile impostare l'endpoint solo in questo modo: https: // IP / path_to_service e impostare requestFactory.
Con questa soluzione non è necessario riavviare TOMCAT o 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;
}

1

Ho anche riscontrato questo problema durante l'aggiornamento da Java 1.6_29 a 1.7.

In modo allarmante, il mio cliente ha scoperto un'impostazione nel pannello di controllo Java che risolve questo problema.

Nella scheda Avanzate è possibile selezionare "Usa formato ClientHello compatibile con SSL 2.0".

Questo sembra risolvere il problema.

Stiamo usando applet Java in un browser Internet Explorer.

Spero che questo ti aiuti.


0

Ho avuto lo stesso problema con un server Ubuntu Linux in esecuzione sovversione quando si accedeva tramite Eclipse.

Ha dimostrato che il problema riguardava un avviso all'avvio di 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

Ciò è dovuto a una nuova entrata ports.conf, in cui è NameVirtualHoststata inserita un'altra direttiva accanto alla direttiva in questione sites-enabled/000-default.

Dopo aver rimosso la direttiva ports.conf, il problema era scomparso (dopo aver riavviato Apache, naturalmente)


0

Solo per aggiungere una soluzione qui. Questo potrebbe aiutare gli utenti LAMP

Options +FollowSymLinks -SymLinksIfOwnerMatch

La linea sopra menzionata nella configurazione dell'host virtuale era il colpevole.

Configurazione dell'host virtuale in caso di errore

<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>

Configurazione di lavoro

<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

Ecco la soluzione per Appache httpclient 4.5.11. Ho avuto problemi con il certificato che ha il carattere jolly soggetto *.hostname.com. Mi ha restituito la stessa eccezione, ma non uso la disabilitazione per proprietà System.setProperty("jsse.enableSNIExtension", "false");perché ha commesso un errore nel client di localizzazione di Google.

Ho trovato una soluzione semplice (solo modificando socket):

import io.micronaut.context.annotation.Bean;
import io.micronaut.context.annotation.Factory;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;

import javax.inject.Named;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.util.List;

@Factory
public class BeanFactory {

    @Bean
    @Named("without_verify")
    public HttpClient provideHttpClient() {
        SSLConnectionSocketFactory connectionSocketFactory = new SSLConnectionSocketFactory(SSLContexts.createDefault(), NoopHostnameVerifier.INSTANCE) {
            @Override
            protected void prepareSocket(SSLSocket socket) throws IOException {
                SSLParameters parameters = socket.getSSLParameters();
                parameters.setServerNames(List.of());
                socket.setSSLParameters(parameters);
                super.prepareSocket(socket);
            }
        };

        return HttpClients.custom()
                .setSSLSocketFactory(connectionSocketFactory)
                .build();
    }


}

-1

C'è un modo più semplice in cui puoi semplicemente usare il tuo HostnameVerifier per fidarti implicitamente di certe connessioni. Il problema si presenta con Java 1.7 in cui sono state aggiunte estensioni SNI e l'errore è dovuto a una configurazione errata del server.

Puoi utilizzare "-Djsse.enableSNIExtension = false" per disabilitare SNI in tutta la JVM o leggere il mio blog in cui spiego come implementare un verificatore personalizzato su una connessione URL.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.