Alerta de handshake SSL: erro unrecognized_name desde a atualização para o Java 1.7.0

225

Atualizei do Java 1.6 para o Java 1.7 hoje. Desde então, ocorre um erro quando tento estabelecer uma conexão com meu servidor da Web por 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)

Aqui está o código:

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

É apenas um projeto de teste, é por isso que permito e uso certificados não confiáveis ​​com o código:

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

Tentei conectar-me com sucesso a https://google.com . onde está minha culpa?

Obrigado.

pvomhoff
fonte

Respostas:

304

O Java 7 introduziu o suporte SNI, que é ativado por padrão. Descobri que certos servidores mal configurados enviam um aviso "Nome não reconhecido" no handshake SSL, que é ignorado pela maioria dos clientes ... exceto pelo Java. Como @Bob Kerns mencionou, os engenheiros da Oracle se recusam a "consertar" esse bug / recurso.

Como solução alternativa, eles sugerem definir a jsse.enableSNIExtensionpropriedade. Para permitir que seus programas funcionem sem recompilar, execute seu aplicativo como:

java -Djsse.enableSNIExtension=false yourClass

A propriedade também pode ser configurada no código Java, mas deve ser configurada antes de qualquer ação SSL . Depois que a biblioteca SSL for carregada, você poderá alterar a propriedade, mas ela não afetará o status do SNI . Para desativar o SNI no tempo de execução (com as limitações mencionadas), use:

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

A desvantagem de definir esse sinalizador é que o SNI está desativado em qualquer lugar do aplicativo. Para fazer uso do SNI e ainda suportar servidores configurados incorretamente:

  1. Crie um SSLSocketcom o nome do host ao qual você deseja se conectar. Vamos nomear issosslsock .
  2. Tente correr sslsock.startHandshake(). Isso será bloqueado até que seja feito ou gerará uma exceção por erro. Sempre que ocorreu um erro startHandshake(), obtenha a mensagem de exceção. Se for igual ahandshake alert: unrecognized_name , você encontrou um servidor configurado incorretamente.
  3. Quando você receber o unrecognized_nameaviso (fatal em Java), tente abrir a SSLSocket, mas desta vez sem um nome de host. Isso desabilita efetivamente o SNI (afinal, a extensão SNI é sobre a adição de um nome de host à mensagem ClientHello).

Para o proxy SSL Webscarab, esse commit implementa a configuração de fallback .

Lekensteyn
fonte
5
funciona, obrigado! O cliente de subversão do IntelliJ IDEA teve o mesmo erro ao se conectar através do HTTPS. Precisa apenas de arquivo de atualização idea.exe.vmoptions com a linha: -Djsse.enableSNIExtension = false
Dima
2
Para java webstart use isto: javaws -J-Djsse.enableSNIExtension = false YourClass
Torsten
Eu tive exatamente o mesmo problema com meu cliente de Jersey com https rest server. Acontece que eu usei seu truque e funcionou !! obrigado!
precisa saber é o seguinte
4
Para aqueles que se perguntam sobre o Java 8, a Oracle ainda não mudou o comportamento e ainda exige que o programador capture servidores que não suportam (adequadamente) o SNI: docs.oracle.com/javase/8/docs/technotes/guides/security/jsse /…
Lekensteyn
2
@Blauhirn Entre em contato com o suporte do host da web, eles devem corrigir sua configuração. Se eles usam o Apache, procure algumas mudanças que precisam ser feitas.
Lekensteyn
89

Eu tinha o que acredito que seja o mesmo problema. Descobri que precisava ajustar a configuração do Apache para incluir um ServerName ou ServerAlias ​​para o host.

Este código falhou:

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 esse código funcionou:

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

O Wireshark revelou que, durante o TSL / SSL Hello, o alerta de aviso (Nível: Aviso, Descrição: Nome não reconhecido), o Server Hello estava sendo enviado do servidor para o cliente. Foi apenas um aviso, no entanto, o Java 7.1 respondeu imediatamente de volta com uma "Descrição fatal: mensagem inesperada", que suponho que as bibliotecas Java SSL não gostam de ver o aviso de nome não reconhecido.

No Wiki sobre Transport Layer Security (TLS):

112 Nome não reconhecido, avisando apenas TLS; O Indicador de Nome do Servidor do cliente especificou um nome de host não suportado pelo servidor

Isso me levou a olhar para os meus arquivos de configuração do Apache e descobri que, se eu adicionasse um ServerName ou ServerAlias ​​para o nome enviado do lado do cliente / java, funcionaria corretamente sem erros.

<VirtualHost mydomain.com:443>
  ServerName mydomain.com
  ServerAlias www.mydomain.com
David McLaughlin
fonte
3
Isso parece um bug na implementação do TLS do Java.
Paŭlo Ebermann 9/11/11
1
Na verdade, eu abro um bug para isso. Eu consegui esse número atribuído (ainda não visível): bugs.sun.com/bugdatabase/view_bug.do?bug_id=7127374
eckes
1
Obrigado, adicionando um ServerAliasfuncionou para mim. Eu tinha visto o mesmo alerta TLS no Wireshark.
Jared Beck
2
Isso funcionou para mim também. (teria funcionado melhor se eu tivesse acreditado e não de caça ido para outro caminho)
o usuário final
10
@ JosephphShraibman, dizendo que o funcionário da Oracle é um idiota é inapropriado. Quando você usa dois ServerNames, o Apache responde com um alerta de unrecognized_name aviso (que também pode ser um alerta fatal ). O RFC 6066 diz precisamente isso sobre este tópico: " NÃO É RECOMENDADO enviar um alerta unrecognized_name (112) no nível do aviso, porque o comportamento do cliente em resposta a alertas no nível do aviso é imprevisível ". O único erro cometido por esse funcionário é assumir que esse foi um alerta fatal . Isso é tanto um bug do JRE quanto um do Apache.
246 Bruno Bruno
36

Você pode desativar o envio de registros SNI com a propriedade System jsse.enableSNIExtension = false.

Se você pode alterar o código, é útil usar SSLCocketFactory#createSocket()(sem parâmetro de host ou com um soquete conectado). Nesse caso, ele não enviará uma indicação server_name.

eckes
fonte
11
O que é feito assim:System.setProperty ("jsse.enableSNIExtension", "false");
jn1kk
Eu descobri que isso nem sempre funciona. Não sei por que funciona em alguns casos e não em outros.
Joseph Shraibman
2
Funciona para mim com -Djsse.enableSNIExtension=false. Mas o que mais isso faz? É prejudicial para o resto da segurança do Javas?
ceving 04/02
2
Não, isso apenas significa que alguns sites (especialmente servidores da Web) que usam nomes de host múltiplos por trás de um IP compartilhado não sabem qual certificado enviar. Mas, a menos que seu aplicativo java precise se conectar a milhões de sites, você não precisará desse recurso. Se você encontrar um servidor desse tipo, a validação do certificado poderá falhar e a conexão será interrompida. Portanto, não há nenhum problema de segurança esperado.
Eckes
17

Também encontramos esse erro em uma nova compilação do servidor Apache.

A correção no nosso caso foi definir um ServerAliasna httpd.confque correspondesse ao nome do host ao qual o Java estava tentando se conectar. Nosso ServerNamefoi definido como o nome do host interno. Nosso certificado SSL estava usando o nome do host externo, mas isso não foi suficiente para evitar o aviso.

Para ajudar na depuração, você pode usar este comando ssl:

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

Se houver um problema com esse nome de host, a mensagem será impressa na parte superior da saída:

SSL3 alert read: warning:unrecognized name

Também devo observar que não recebemos esse erro ao usar esse comando para conectar-se ao nome do host interno, mesmo que não corresponda ao certificado SSL.

Scott McIntyre
fonte
6
Obrigado por incluir o exemplo de como reproduzir o problema usando openssl. É muito útil.
sideshowbarker
2
Existe uma maneira de um cliente openssl ver a diferença de nome que origina a mensagem SSL3 alert read: warning:unrecognized name? Eu vejo o alerta impresso, mas a saída não me ajudar a ver, na verdade, essa diferença de nomenclatura
mox601
Isso não funciona para mim. Testado com OpenSSL 1.0.1e-fips 11 Feb 2013, OpenSSL 1.0.2k-fips 26 Jan 2017e LibreSSL 2.6.5.
Greg Dubicki 15/10/19
15

Em vez de confiar no mecanismo de host virtual padrão no apache, você pode definir um último host virtual catchall que usa um ServerName arbitrário e um curinga ServerAlias, por exemplo

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

Dessa forma, você pode usar o SNI e o apache não enviará de volta o aviso SSL.

Obviamente, isso só funciona se você puder descrever todos os seus domínios facilmente usando uma sintaxe curinga.

Erik
fonte
Pelo que entendi, o apache envia apenas esse aviso caso o cliente use um nome não configurado como ServerName ou ServerAlias. Portanto, se você tiver um certificado curinga, especifique todos os subdomínios usados ​​ou use a *.mydomain.comsintaxe para ServerAlias. Observe que você pode adicionar vários aliases usando ServerAlias, por exemplo ServerAlias *.mydomain.com mydomain.com *.myotherdomain.com.
Michael Paesold
13

Deve ser útil. Para tentar novamente um erro SNI no Apache HttpClient 4.4 - a maneira mais fácil de criar (consulte 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);
Shcheklein
fonte
Se você fizer assim. Como você lida com verifyHostname(sslsock, target)em SSLConnectionSocketFactory.createLayeredSocket? Como a targetstring é alterada para vazia, verifyHostnamenão será bem-sucedida. Estou perdendo algo óbvio aqui?
Haozhun
2
Era exatamente isso que eu procurava, muito obrigado! Não encontrei outra maneira de oferecer suporte ao SNI e servidores que respondem com esse aviso "unrecognized_name" ao mesmo tempo.
korpe
Esta solução funciona, a menos que você esteja usando um servidor proxy.
Mr #
Tendo acabado de fazer uma depuração rápida através do código do cliente Apache, começando com confirmHostname (), não consigo ver como isso pode funcionar usando o DefaultHostnameVerifier. Suspeito que esta solução exija que a verificação de nome de host seja desabilitada (por exemplo, usando um NoopHostnameVerifier), o que NÃO é uma boa ideia, pois permite ataques man-in-the-middle.
FlyingSheep 13/05/19
11

Usar:

  • System.setProperty ("jsse.enableSNIExtension", "false");
  • Reinicie seu Tomcat (importante)
Tomasz Janisiewicz
fonte
6

Ocorreu um problema com o boot de primavera e as jvm 1.7 e 1.8. Na AWS, não tivemos a opção de alterar o ServerName e o ServerAlias ​​para corresponder (eles são diferentes); portanto, fizemos o seguinte:

No build.gradle , adicionamos o seguinte:

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

Isso nos permitiu ignorar o problema com o "Nome não reconhecido".

Ray Hunter
fonte
5

Infelizmente, você não pode fornecer propriedades do sistema para a ferramenta jarsigner.exe.

Enviei o defeito 7177232 , referenciando o defeito 7127374 da @eckes e explicando por que o erro foi encerrado.

Meu defeito é especificamente sobre o impacto na ferramenta jarsigner, mas talvez isso os leve a reabrir o outro defeito e resolver o problema adequadamente.

ATUALIZAÇÃO: Na verdade, você PODE fornecer propriedades do sistema à ferramenta Jarsigner, mas não está na mensagem de ajuda. Usarjarsigner -J-Djsse.enableSNIExtension=false

Bob Kerns
fonte
1
Obrigado, Bob, pelo acompanhamento. Infelizmente, a Oracle ainda não entende a coisa toda. O alerta SNI não é fatal, pode ser trerrado fatal. Porém, a maioria dos clientes SSL típicos (ou seja, navegadores) optou por ignorá-lo, pois não é um problema de segurança real (porque você ainda precisa verificar o certificado do servidor e detectaria pontos de extremidade incorretos). um servidor https configurado corretamente.
Eckes
2
Na verdade, você PODE fornecer propriedades do sistema para a ferramenta Jarsigner, mas não está na mensagem de ajuda. Está coberto na documentação. Parvo-me por acreditar no texto online. Obviamente, eles usaram isso como desculpa para não consertá-lo.
Bob Kerns
BTW: No momento, estou discutindo esse problema no security-dev @ openjdk e, no momento em que estava avaliando, parece que a Symantec corrigiu o servidor de registro de data e hora GEOTrust, agora aceita corretamente o URL sem aviso. Mas ainda acho que isso deve ser corrigido. Se você quiser dar uma olhada, um projeto de cliente de teste e a discussão :
eckes
3

Eu bati o mesmo problema e verificou-se que o DNS reverso não estava configurado corretamente, apontou para o nome de host errado para o IP. Depois de corrigir o DNS reverso e reiniciar o httpd, o aviso desapareceu. (se eu não corrigir o DNS reverso, a adição do ServerName também funcionou)

user2888387
fonte
2

Meu VirtualHost's ServerNamefoi comentada por padrão. Funcionou depois de descomentar.

Aram Paronikyan
fonte
O mesmo aqui. O nome do servidor estava ausente.
Escuro Star1
2

Se você estiver construindo um cliente com Resttemplate, poderá definir apenas o terminal assim: https: // IP / path_to_service e defina requestFactory.
Com esta solução, você não precisa reiniciar o seu 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;
}
Alfredo
fonte
1

Também deparei com esse problema ao atualizar do Java 1.6_29 para 1.7.

Surpreendentemente, meu cliente descobriu uma configuração no painel de controle Java que resolve isso.

Na guia Avançado, você pode verificar 'Usar o formato ClientHello compatível com SSL 2.0'.

Isso parece resolver o problema.

Estamos usando miniaplicativos Java em um navegador Internet Explorer.

Espero que isto ajude.

Allan D
fonte
0

Eu tive o mesmo problema com um servidor Ubuntu Linux executando o subversion quando acessado via Eclipse.

Ele mostrou que o problema tinha a ver com um aviso quando o Apache (re) iniciou:

[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

Isso ocorreu devido a uma nova entrada ports.conf, onde outra NameVirtualHostdiretiva foi inserida juntamente com a diretiva em sites-enabled/000-default.

Após remover a diretiva ports.conf, o problema desapareceu (após reiniciar o Apache, naturalmente)

Gerhard
fonte
0

Apenas para adicionar uma solução aqui. Isso pode ajudar os usuários do LAMP

Options +FollowSymLinks -SymLinksIfOwnerMatch

A linha acima mencionada na configuração do host virtual foi a culpada.

Configuração de host virtual quando erro

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

Configuração de trabalho

<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>
Griffin
fonte
0

Aqui está a solução para o Appache httpclient 4.5.11. Eu tive um problema com o certificado que tem o curinga sujeito *.hostname.com. Ele me retornou a mesma exceção, mas não devo usar a desativação por propriedade System.setProperty("jsse.enableSNIExtension", "false");porque cometeu um erro no cliente de localização do Google.

Encontrei solução simples (apenas modificando o soquete):

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


}
Andrew Sneck
fonte
-1

Existe uma maneira mais fácil de você usar o seu próprio HostnameVerifier para confiar implicitamente em determinadas conexões. O problema ocorre no Java 1.7, onde as extensões SNI foram adicionadas e o erro ocorreu devido a uma configuração incorreta do servidor.

Você pode usar "-Djsse.enableSNIExtension = false" para desativar o SNI em toda a JVM ou ler meu blog, onde explico como implementar um verificador personalizado em cima de uma conexão de URL.

MagicDude4Eva
fonte