Alerta de apretón de manos SSL: error de nombre no reconocido desde la actualización a Java 1.7.0

Actualicé de Java 1.6 a Java 1.7 hoy. Desde entonces, se produce un error cuando trato de establecer una conexión con mi servidor web a través de 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) 

Aquí está el 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); } 

Es solo un proyecto de prueba por eso que yo permito y uso certificados que no son de confianza con el 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); } 

Intenté conectarme con éxito a https://google.com . ¿dónde está mi culpa?

Gracias.

Java 7 introdujo el soporte de SNI que está habilitado por defecto. Descubrí que ciertos servidores mal configurados envían una advertencia de “Nombre no reconocido” en el protocolo de enlace SSL, que la mayoría de los clientes ignoran … excepto Java. Como @Bob Kerns mencionó, los ingenieros de Oracle se niegan a “arreglar” este error / característica.

Como solución jsse.enableSNIExtension , sugieren establecer la propiedad jsse.enableSNIExtension . Para permitir que sus progtwigs funcionen sin volver a comstackr, ejecute su aplicación como:

 java -Djsse.enableSNIExtension=false yourClass 

La propiedad también se puede establecer en el código de Java, pero se debe establecer antes de cualquier acción de SSL . Una vez que la biblioteca SSL se haya cargado, puede cambiar la propiedad, pero no tendrá ningún efecto en el estado de SNI . Para desactivar SNI en tiempo de ejecución (con las limitaciones antes mencionadas), use:

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

La desventaja de establecer este indicador es que SNI está deshabilitado en todas partes de la aplicación. Para hacer uso de SNI y aún soportar servidores mal configurados:

  1. Cree un SSLSocket con el nombre de host al que desea conectarse. Vamos a sslsock este sslsock .
  2. Intenta ejecutar sslsock.startHandshake() . Esto bloqueará hasta que se complete o lanzará una excepción por error. Siempre que startHandshake() un error en startHandshake() , obtenga el mensaje de excepción. Si es igual a la handshake alert: unrecognized_name , entonces ha encontrado un servidor malconfigurado.
  3. Cuando haya recibido la advertencia de nombre unrecognized_name (fatal en Java), intente abrir un SSLSocket , pero esta vez sin un nombre de host. Esto efectivamente deshabilita SNI (después de todo, la extensión SNI se trata de agregar un nombre de host al mensaje ClientHello).

Para el proxy SSL de Webscarab, este compromiso implementa la configuración alternativa.

Tuve lo que creo que es el mismo problema. Descubrí que necesitaba ajustar la configuración de Apache para incluir un ServerName o ServerAlias ​​para el host.

Este código falló:

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

Y este código funcionó:

 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 reveló que durante el TSL / SSL Hola, la Alerta de advertencia (Nivel: Advertencia, Descripción: Nombre no reconocido), Se estaba enviando el Servidor Hola desde el servidor al cliente. Era solo una advertencia, sin embargo, Java 7.1 luego respondió de inmediato con un “Fatal, Descripción: Mensaje inesperado”, que supongo que significa que a las bibliotecas de Java SSL no les gusta ver la advertencia de un nombre no reconocido.

Desde Wiki en Transport Layer Security (TLS):

112 Nombre no reconocido que advierte solo TLS; el indicador de nombre del servidor del cliente especificó un nombre de host no admitido por el servidor

Esto me llevó a mirar mis archivos de configuración de Apache y encontré que si añadía un ServerName o ServerAlias ​​para el nombre enviado desde el lado del cliente / java, funcionaba correctamente sin ningún error.

  ServerName mydomain.com ServerAlias www.mydomain.com 

Puede deshabilitar el envío de registros SNI con la propiedad del sistema jsse.enableSNIExtension = false.

Si puede cambiar el código, ayuda a usar SSLCocketFactory#createSocket() (sin parámetro de host o con un socket conectado). En este caso, no enviará una indicación server_name.

También encontramos este error en una nueva comstackción del servidor Apache.

La solución en nuestro caso fue definir un ServerAlias en el httpd.conf que correspondía al nombre de host al que Java intentaba conectarse. Nuestro ServerName se estableció en el nombre de host interno. Nuestro certificado SSL estaba usando el nombre de host externo, pero eso no fue suficiente para evitar la advertencia.

Para ayudar a depurar, puede usar este comando ssl:

openssl s_client -servername -connect :443 -state

Si hay un problema con ese nombre de host, imprimirá este mensaje cerca de la parte superior de la salida:

SSL3 alert read: warning:unrecognized name

También debería tener en cuenta que no obtuvimos ese error cuando usamos ese comando para conectarnos al nombre de host interno, aunque no coincidió con el certificado SSL.

En lugar de confiar en el mecanismo de host virtual predeterminado en apache, puede definir un último host virtual catchall que use un ServerName arbitrario y un ServerAlias ​​comodín, por ejemplo

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

De esa forma puede usar SNI y apache no enviará la advertencia SSL.

Por supuesto, esto solo funciona si puedes describir todos tus dominios fácilmente usando una syntax comodín.

Debería ser útil. Volver a intentar un error de SNI en Apache HttpClient 4.4 – la forma más fácil que se nos ocurrió (ver HTTPCLIENT-1522 ):

 public class SniHttpClientConnectionOperator extends DefaultHttpClientConnectionOperator { public SniHttpClientConnectionOperator(Lookup 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; } } } } 

y

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

y

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

Utilizar:

  • System.setProperty (“jsse.enableSNIExtension”, “false”);
  • Reinicia tu Tomcat (importante)

No puede proporcionar propiedades del sistema a la herramienta jarsigner.exe, desafortunadamente.

He enviado el defecto 7177232 , haciendo referencia al defecto 7127374 de @eckes y explicando por qué se cerró por error.

Mi defecto es específicamente sobre el impacto en la herramienta jarsigner, pero tal vez los lleve a reabrir el otro defecto y abordar el problema adecuadamente.

ACTUALIZACIÓN: En realidad, resulta que PUEDE suministrar propiedades del sistema a la herramienta Jarsigner, simplemente no está en el mensaje de ayuda. Utilice jarsigner -J-Djsse.enableSNIExtension=false

Se encontró con este problema con el arranque de spring y jvm 1.7 y 1.8. En AWS, no teníamos la opción de cambiar ServerName y ServerAlias ​​para que coincidieran (son diferentes), así que hicimos lo siguiente:

En build.gradle agregamos lo siguiente:

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

Eso nos permitió omitir el problema con el “Nombre no reconocido”.

Me encontré con el mismo problema y resultó que el dns invertido no estaba configurado correctamente, señalaba un nombre de host incorrecto para el IP. Después de corregir DNS inversos y reiniciar httpd, la advertencia se ha ido. (Si no corrijo los DNS inversos, agregar ServerName también me gustó)

El nombre de VirtualHost mi VirtualHost fue comentado por defecto. Funcionó después de descomentar.

Si está creando un cliente con Resttemplate, solo puede establecer el punto final de esta manera: https: // IP / path_to_service y establecer requestFactory.
Con esta solución, no es necesario reiniciar su 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 registry = RegistryBuilder.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; } 

También me he encontrado con este problema al actualizar de Java 1.6_29 a 1.7.

De forma alarmante, mi cliente descubrió una configuración en el panel de control de Java que resuelve esto.

En la pestaña Avanzado, puede marcar “Usar formato compatible con SSL 2.0 ClientHello”.

Esto parece resolver el problema.

Estamos utilizando applets de Java en un navegador de Internet Explorer.

Espero que esto ayude.

Tuve el mismo problema con un servidor Ubuntu Linux que ejecuta subversión cuando se accede a través de Eclipse.

Se ha demostrado que el problema tenía que ver con una advertencia cuando se iniciaba Apache (re):

 [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 

Esto se debe a una nueva entrada en ports.conf , donde se ingresó otra directiva NameVirtualHost junto con la directiva en sites-enabled/000-default .

Después de eliminar la directiva en ports.conf , el problema se había desvanecido (después de reiniciar Apache, naturalmente)

Solo para agregar una solución aquí. Esto podría ayudar a los usuarios de LAMP

 Options +FollowSymLinks -SymLinksIfOwnerMatch 

La línea antes mencionada en la configuración del host virtual fue la culpable.

Configuración de host virtual cuando hay un error

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

Configuración de trabajo

  DocumentRoot /var/www/html/load/web ServerName dev.load.com  AllowOverride All Options All Order Allow,Deny Allow from All  # 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]  

Hay una manera más fácil en la que puede usar su propio HostnameVerifier para confiar implícitamente en ciertas conexiones. El problema viene con Java 1.7 donde las extensiones de SNI se han agregado y su error se debe a una configuración incorrecta del servidor.

Puede usar “-Djsse.enableSNIExtension = false” para deshabilitar SNI en toda la JVM o leer mi blog donde explico cómo implementar un verificador personalizado sobre una conexión de URL.