从那时起,当我尝试通过SSL建立到我的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)
这只是一个测试项目,这就是为什么我允许和使用不信任证书的原因: >
我成功尝试连接到https://google.com。
我的错在哪里?
谢谢。
#1 楼
Java 7引入了默认情况下启用的SNI支持。我发现某些配置错误的服务器会在SSL握手中发送“无法识别的名称”警告,大多数客户端会忽略该警告... Java除外。如@Bob Kerns所述,Oracle工程师拒绝“修复”此错误/功能。作为解决方法,他们建议设置
jsse.enableSNIExtension
属性。要允许您的程序在不重新编译的情况下运行,请按以下方式运行您的应用:java -Djsse.enableSNIExtension=false yourClass
也可以在Java代码中设置该属性,但必须在之前设置任何SSL操作。加载SSL库后,您可以更改属性,但不会对SNI状态产生任何影响。要在运行时禁用SNI(具有上述限制),请使用:
System.setProperty("jsse.enableSNIExtension", "false");
设置此标志的缺点是SNI在应用程序中的任何地方都被禁用。为了使用SNI并仍然支持配置错误的服务器:
使用您要连接的主机名创建一个
SSLSocket
。我们将其命名为sslsock
。尝试运行
sslsock.startHandshake()
。这将阻塞直到完成或在错误时引发异常。每当startHandshake()
中发生错误时,请获取异常消息。如果它等于handshake alert: unrecognized_name
,那么您发现服务器配置错误。当您收到
unrecognized_name
警告(在Java中严重)时,请重试打开SSLSocket
,但这一次没有主机名。这有效地禁用了SNI(毕竟,SNI扩展名是要向ClientHello消息中添加主机名)。对于Webscarab SSL代理,此提交实现了后备设置。
#2 楼
我遇到了同样的问题。我发现我需要调整Apache配置以包含用于主机的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();
}
}
此代码有效:
qq1202078q
Wireshark透露在TSL / SSL期间,您好警告
警报(级别:警告,描述:无法识别的名称),服务器Hello
是从服务器发送到客户端的。
这只是一个警告,但是Java 7.1随后立即回复“致命,说明:“意外消息”,我认为这意味着Java SSL库不希望看到名称无法识别的警告。
112无法识别的名称仅警告TLS;客户端的服务器名称指示符指定了服务器不支持的主机名
,这使我查看了我的Apache配置文件,发现如果我为从客户端/ java发送的名称添加了ServerName或ServerAlias方面,它可以正常工作而没有任何错误。
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();
}
}
评论
这看起来像Java TLS实施中的错误。
–PaŭloEbermann
2011年11月9日,0:18
我实际上为此打开了一个错误。我分配了这个数字(尚不可见):bugs.sun.com/bugdatabase/view_bug.do?bug_id=7127374
–eckes
2012年1月5日在16:19
谢谢,添加ServerAlias对我有用。我在Wireshark中看到了相同的TLS警报。
–Jared Beck
2012年12月4日在2:28
这也对我有用。 (如果我相信这一点,并且不走另一条路,就会更好地工作)
–最终用户
13年1月31日在18:34
@JosephShraibman说Oracle雇员是个白痴是不合适的。当您使用两个ServerName时,Apache会以unrecognized_name警告警报响应(也可能是致命警报)。 RFC 6066在此主题上恰好说明了这一点:“不建议发送警告级别的unrecognized_name(112)警报,因为客户端对警告级别的警报的行为是不可预测的。”该员工唯一犯的错误是假定这是一个致命警报。这既是一个Apache JRE漏洞,又是一个JRE错误。
–布鲁诺
2014年2月24日19:51
#3 楼
您可以使用系统属性jsse.enableSNIExtension = false禁用发送SNI记录。如果可以更改代码,则使用
SSLCocketFactory#createSocket()
(无主机参数或连接的插座)会有所帮助。在这种情况下,它将不会发送server_name指示。评论
这样做是这样的:System.setProperty(“ jsse.enableSNIExtension”,“ false”);
– jn1kk
2012年4月9日18:27
我发现那并不总是有效。我不知道为什么它在某些情况下会起作用,而在另一些情况下却不会。
–约瑟夫·施莱布曼(Joseph Shraibman)
2012年5月9日21:42
对我有用-Djsse.enableSNIExtension = false。但是它还能做什么?这对其余的Java安全性有害吗?
–天花板
13年2月4日在10:33
不,这仅意味着某些在共享IP后面使用多个主机名的站点(尤其是Web服务器)不知道要发送什么证书。但是除非您的Java应用程序必须连接到数百万个网站,否则您将不需要该功能。如果遇到这样的服务器,则证书验证可能会失败并且连接将中止。因此不会出现安全问题。
–eckes
13年2月7日在23:17
#4 楼
我们还在新的Apache服务器版本上遇到了此错误。我们的解决方法是在
ServerAlias
中定义与Java试图连接的主机名相对应的httpd.conf
。我们的ServerName
被设置为内部主机名。我们的SSL证书使用的是外部主机名,但这不足以避免发出警告。为了帮助调试,您可以使用以下ssl命令: />
如果该主机名有问题,那么它将在输出顶部附近打印此消息:
openssl s_client -servername <hostname> -connect <hostname>:443 -state
我还应该注意使用该命令连接到内部主机名时,即使它与SSL证书不匹配,我们也没有收到该错误。
评论
感谢您提供有关如何使用openssl重现问题的示例。这非常有帮助。
–sideshowbarker
15年8月4日在10:22
openssl客户端是否可以通过某种方式查看源自消息SSL3警报的名称差异:警告:无法识别的名称?我看到打印了警报,但是输出并没有帮助我实际看到这种命名差异
–mox601
16年5月25日在10:44
这对我不起作用。已在2013年2月11日使用OpenSSL 1.0.1e-fips,2017年1月26日在OpenSSL 1.0.2k-fips和LibreSSL 2.6.5中进行测试。
– Greg Dubicki
19-10-15在11:20
@GregDubicki尝试使用openssl s_client -showcerts -connect <主机名>:443代替
–艾尔·罗斯(Eyal Roth)
7月13日15:21
#5 楼
您可以定义最后一个使用任意ServerName和通配符ServerAlias的虚拟主机,而不必依赖apache中的默认虚拟主机机制,例如,ServerName catchall.mydomain.com
ServerAlias *.mydomain.com
您可以使用SNI,而apache不会发回SSL警告。
当然,只有当您可以使用通配符语法轻松描述所有域时,此方法才有效。
评论
据我了解,apache仅在客户端使用未配置为ServerName或ServerAlias的名称的情况下发送此警告。因此,如果您具有通配符证书,请指定所有使用的子域,或对ServerAlias使用* .mydomain.com语法。请注意,您可以使用ServerAlias添加多个别名,例如ServerAlias * .mydomain.com mydomain.com * .myotherdomain.com。
–Michael Paesold
16-4-20在14:21
#6 楼
它应该是有用的。要在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);
评论
如果您这样做的话。您如何处理SSLConnectionSocketFactory.createLayeredSocket中的verifyHostname(sslsock,target)?由于目标更改为空字符串,因此verifyHostname不会成功。我在这里错过明显的东西吗?
–好准
17年4月15日在21:07
正是我所寻找的,非常感谢!我找不到其他方法来支持SNI和同时响应此“ unrecognized_name”警告的服务器。
– korpe
17年7月8日在18:50
除非您使用代理服务器,否则此解决方案有效。
– Mrog
18-10-16在23:06
从verifyHostname()开始,通过Apache客户端代码进行了快速调试之后,我看不到如何使用DefaultHostnameVerifier进行工作。我怀疑此解决方案要求禁用主机名验证(例如使用NoopHostnameVerifier),但这不是一个好主意,因为它允许中间人攻击。
–FlyingSheep
19年5月13日在10:45
#7 楼
使用:System.setProperty(“ jsse.enableSNIExtension”,“ false”);
重新启动Tomcat(重要)
#8 楼
使用Spring Boot以及jvm 1.7和1.8遇到了这个问题。在AWS上,我们没有选择更改ServerName和ServerAlias以匹配(它们是不同的)的选项,因此我们执行以下操作:在build.gradle中,我们添加了以下内容:
System.setProperty("jsse.enableSNIExtension", "false")
bootRun.systemProperties = System.properties
这使我们能够绕过带有“无法识别的名称”的问题。
#9 楼
不幸的是,您无法向jarsigner.exe工具提供系统属性。我提交了缺陷7177232,引用了@eckes的缺陷7127374并解释了为什么错误地关闭了它。
我的缺陷特别是对jarsigner工具的影响,但也许会导致他们重新打开另一个缺陷并正确解决问题。
更新:实际上,您可以提供系统Jarsigner工具的属性,它不在帮助消息中。使用
jarsigner -J-Djsse.enableSNIExtension=false
评论
感谢Bob的跟进。可悲的是,Oracle仍然不了解整个事情。 SNI警报不是致命的,它可能是致命的。但是大多数典型的SSL客户端(例如浏览器)都选择忽略它,因为这不是真正的安全问题(因为您仍然需要检查服务器证书并会检测到错误的端点)。对于CA无法设置CA,我们感到非常遗憾。正确配置的https服务器。
–eckes
2012年9月6日在2:26
实际上,事实证明您可以为Jarsigner工具提供系统属性,而只是不在帮助消息中。它包含在文档中。愚蠢的我相信在线文本。当然,他们以此为借口不予解决。
–鲍勃·克恩斯(Bob Kerns)
2012年10月22日,下午1:39
顺便说一句:我目前正在security-dev @ openjdk上讨论此问题,目前我正在评估它,就像Symantec修复了GEOTrust时间戳服务器一样,它现在可以正确接受URL,而不会发出警告。但是我仍然认为应该固定。如果您想看一看,请测试一下客户项目和讨论内容:
–eckes
13年1月21日在4:21
#10 楼
我遇到了同样的问题,结果发现反向DNS设置不正确,它指向IP的主机名错误。纠正反向DNS并重新启动httpd之后,警告消失了。(如果我不纠正反向dns,则添加ServerName也对我有用)
#11 楼
默认情况下,我的VirtualHost
的ServerName
已被注释掉。取消注释后就可以了。评论
同样在这里。服务器名称丢失。
–黑暗之星1
18年8月28日在14:30
#12 楼
如果要使用Resttemplate构建客户端,则只能这样设置端点:https:// IP / path_to_service并设置requestFactory。使用此解决方案,您无需重新启动TOMCAT或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;
}
#13 楼
从Java 1.6_29升级到1.7时,我也遇到了这个问题。在“高级”选项卡中,您可以选中“使用与SSL 2.0兼容的ClientHello格式”。这似乎可以解决问题。
我们在Internet Explorer浏览器中使用Java小程序。
希望有帮助。
#14 楼
通过Eclipse访问时,运行Ubuntu的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
指令。问题消失了(自然重启Apache之后)#15 楼
只是在这里添加解决方案。这可能对LAMP用户有所帮助Options +FollowSymLinks -SymLinksIfOwnerMatch
虚拟主机配置中的上述行是罪魁祸首。
错误时的虚拟主机配置
<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}/ [NC,R=301,L]
</VirtualHost>
工作配置
<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}/ [NC,R=301,L]
</VirtualHost>
#16 楼
这是Appache httpclient 4.5.11的解决方案。我的证书有问题,该证书的主题通配为*.hostname.com
。它返回了相同的异常,但我不使用属性System.setProperty("jsse.enableSNIExtension", "false");
来禁用它,因为它在Google位置客户端中产生了错误。我找到了简单的解决方案(仅修改套接字):
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();
}
}
#17 楼
有一种更简单的方法,您可以使用自己的HostnameVerifier隐式信任某些连接。 Java 1.7出现了问题,其中已添加了SNI扩展,并且您的错误是由于服务器配置错误引起的。您可以使用“ -Djsse.enableSNIExtension = false”在整个JVM上禁用SNI或阅读我的博客,其中解释了如何在URL连接的顶部实现自定义验证程序。
评论
它有效,谢谢!通过HTTPS连接时,IntelliJ IDEA转换客户端存在相同的错误。只需使用以下行更新idea.exe.vmoptions文件:-Djsse.enableSNIExtension = false
–迪马
13年7月20日在23:15
对于Java Webstart,请使用以下命令:javaws -J-Djsse.enableSNIExtension = false yourClass
– Torsten
2014年1月7日10:32
我的https休息服务器的Jersey客户端遇到了完全相同的问题。原来我用了你的把戏,而且有效!!谢谢!
– shahshi15
2014年1月24日,0:27
对于那些对Java 8感到好奇的人,Oracle仍然没有改变其行为,仍然要求程序员捕获不(正确)支持SNI的服务器:docs.oracle.com/javase/8/docs/technotes/guides/security/jsse /…
– Lekensteyn
2014年7月1日在13:23
@Blauhirn联系您的网站托管支持,他们应该修复其配置。如果他们使用Apache,请查看是否需要进行一些更改。
– Lekensteyn
16年4月8日在16:12