SSL握手警报:自升级到Java 1.7.0以来的unrecognized_name错误
我今天从Java 1.6升级到Java 1.7。 从那时起,当我尝试通过SSLbuild立到我的web服务器的连接时发生错误:
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)
这里是代码:
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); }
它只是一个testing项目,这就是为什么我允许和使用代码不受信任的证书:
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); }
我成功尝试连接到https://google.com 。 我的错在哪里?
谢谢。
Java 7引入了默认启用的SNI支持。 我发现某些configuration错误的服务器在SSL握手中发送了一个“无法识别的名称”警告,大多数客户端都忽略了这个警告,除了Java。 正如@Bob Kerns所提到的,Oracle工程师拒绝“修复”这个bug /function。
作为解决方法,他们build议设置jsse.enableSNIExtension
属性。 为了让您的程序在不重新编译的情况下运行,请运行您的应用程序:
java -Djsse.enableSNIExtension=false yourClass
该属性也可以在Java代码中设置,但必须在任何SSL操作之前设置。 一旦加载了SSL库,您可以更改该属性,但对SNI状态没有任何影响 。 要在运行时禁用SNI(具有上述限制),请使用:
System.setProperty("jsse.enableSNIExtension", "false");
设置此标志的缺点是在应用程序中处处禁用了SNI。 为了使用SNI,仍然支持错误configuration的服务器:
- 使用要连接的主机名创build一个
SSLSocket
。 我们来命名这个sslsock
。 - 尝试运行
sslsock.startHandshake()
。 这将阻塞,直到完成或抛出exception的错误。 每当startHandshake()
发生错误时,都会得到exception消息。 如果它等于handshake alert: unrecognized_name
,那么你find了一个configuration错误的服务器。 - 当您收到
unrecognized_name
警告(Java中致命)时,请重试打开一个SSLSocket
,但是这次没有主机名。 这有效地禁用SNI(毕竟,SNI扩展是关于将主机名添加到ClientHello消息)。
对于Webscarab SSL代理, 此提交实现了回退设置。
我有我相信同样的问题是。 我发现我需要调整Apacheconfiguration以包含主机的ServerName或ServerAlias。
此代码失败:
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(); } }
而这个代码工作:
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透露,在TSL / SSL期间,警告警报(级别:警告,描述:无法识别的名称),服务器Hello正在从服务器发送到客户端。 然而,这只是一个警告,然后,Java 7.1立即回应了一个“致命的描述:意外消息”,我认为这意味着Java SSL库不希望看到无法识别的名称的警告。
从维基上传输层安全(TLS):
112无法识别的名称警告仅限TLS; 客户端的服务器名称指示器指定了服务器不支持的主机名称
这导致我看看我的Apacheconfiguration文件,我发现如果我从客户端/ Java端发送的名称添加ServerName或ServerAlias,它正常工作没有任何错误。
<VirtualHost mydomain.com:443> ServerName mydomain.com ServerAlias www.mydomain.com
您可以禁用系统属性jsse.enableSNIExtension = false发送SNIlogging。
如果您可以更改代码,则可以使用SSLCocketFactory#createSocket()
(不带主机参数或连接的套接字)。 在这种情况下,它不会发送server_name指示。
您不必依赖apache中的默认虚拟主机机制,而是可以定义最后一个使用任意ServerName和通配符ServerAlias的虚拟主机,例如
ServerName catchall.mydomain.com ServerAlias *.mydomain.com
这样就可以使用SNI,而且apache不会发送SSL警告。
当然,只有使用通配符语法才能很容易地描述所有的域名时,这才起作用。
我们也在新的Apache服务器版本上遇到这个错误。
在我们的例子中,修正是在httpd.conf
中定义一个ServerAlias
,它与Java试图连接的主机名相对应。 我们的ServerName
被设置为内部主机名。 我们的SSL证书使用的是外部主机名,但这不足以避免此警告。
为了帮助debugging,你可以使用这个ssl命令:
openssl s_client -servername <hostname> -connect <hostname>:443 -state
如果该主机名存在问题,则会在输出顶部附近打印此消息:
SSL3 alert read: warning:unrecognized name
我也应该注意到,当使用该命令连接到内部主机名时,即使它不符合SSL证书,我们也没有得到这个错误。
使用:
- System.setProperty(“jsse.enableSNIExtension”,“false”);
- 重新启动你的Tomcat(重要的)
这应该是有用的。 要重试Apache HttpClient 4.4中的SNI错误 – 我们想到的最简单的方法(请参阅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; } } } }
和
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); } }
和
cm = new PoolingHttpClientConnectionManager(new SniHttpClientConnectionOperator(socketFactoryRegistry), null, -1, TimeUnit.MILLISECONDS);
不幸的是,您不能将系统属性提供给jarsigner.exe工具。
我提交了缺陷7177232 ,引用了@eckes的缺陷7127374,并解释了为什么它被错误地closures。
我的缺点是具体关于对jarsigner工具的影响,但也许会导致他们重新开放另一个缺陷并恰当地解决问题。
更新:其实,事实certificate,你可以提供系统属性的Jarsigner工具,它只是不在帮助消息。 使用jarsigner -J-Djsse.enableSNIExtension=false
用spring引导和jvm 1.7和1.8来解决这个问题。 在AWS上,我们没有select更改ServerName和ServerAlias以匹配(它们是不同的),所以我们做了以下操作:
在build.gradle中,我们添加了以下内容:
System.setProperty("jsse.enableSNIExtension", "false") bootRun.systemProperties = System.properties
这让我们绕过“无法识别的名称”的问题。
我遇到了同样的问题,事实certificate,反向DNS设置不正确,它指向IP的错误主机名。 纠正反向dns并重新启动httpd后,警告消失了。 (如果我不纠正反向DNS,添加ServerName也为我做了诡计)
我也遇到这个问题,同时从Java 1.6_29升级到1.7。
令人惊讶的是,我的客户在Java控制面板中发现了一个解决这个问题的设置。
在“高级”选项卡中,您可以选中“使用SSL 2.0兼容的ClientHello格式”。
这似乎解决了这个问题。
我们在Internet Explorer浏览器中使用Java小程序。
希望这可以帮助。
我的VirtualHost
的ServerName
被默认注释掉了。 它取消注释后,工作。
当通过Eclipse访问时,我遇到了与运行颠覆的Ubuntu Linux服务器相同的问题。
它已经表明,当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
这是由于在ports.conf
有一个新的条目,其中另一个NameVirtualHost
指令与sites-enabled/000-default
的指令一起input。
删除ports.conf
的指令后,问题消失了(自然重启Apache之后)
只是在这里添加一个解决scheme。 这可能有助于LAMP用户
Options +FollowSymLinks -SymLinksIfOwnerMatch
上面提到的虚拟主机configuration线是罪魁祸首。
虚拟主机configuration错误时
<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
<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>
有一种更简单的方法 ,您可以使用自己的HostnameVerifier隐式地信任某些连接。 Java 1.7已经添加了SNI扩展,而且错误是由于服务器configuration错误导致的。
您可以使用“-Djsse.enableSNIExtension = false”在整个JVM中禁用SNI,也可以阅读我的博客,在此解释如何在URL连接上实现自定义validation器。