SSL 握手警报:升级到 Java 1.7.0 后出现 unrecognized_name 错误
Posted
技术标签:
【中文标题】SSL 握手警报:升级到 Java 1.7.0 后出现 unrecognized_name 错误【英文标题】:SSL handshake alert: unrecognized_name error since upgrade to Java 1.7.0 【发布时间】:2011-11-28 18:16:37 【问题描述】:我今天从 Java 1.6 升级到 Java 1.7。 从那时起,当我尝试通过 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)
代码如下:
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);
它只是一个测试项目,这就是为什么我允许并在代码中使用不受信任的证书:
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。 我的错在哪里?
谢谢。
【问题讨论】:
【参考方案1】:Java 7 引入了默认启用的 SNI 支持。我发现某些配置错误的服务器在 SSL 握手中发送了“无法识别的名称”警告,大多数客户端都忽略了这个警告......除了 Java。正如@Bob Kerns 提到的,Oracle 工程师拒绝“修复”这个错误/功能。
作为解决方法,他们建议设置jsse.enableSNIExtension
属性。要让您的程序无需重新编译即可运行,请将您的应用程序运行为:
java -Djsse.enableSNIExtension=false yourClass
也可以在 Java 代码中设置该属性,但必须在任何 SSL 操作之前设置。加载 SSL 库后,您可以更改属性,但它是 won't have any effect on the SNI status。要在运行时禁用 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 代理,this commit 实现了后备设置。
【讨论】:
它有效,谢谢! IntelliJ IDEA subversion 客户端在通过 HTTPS 连接时出现同样的错误。只需要使用以下行更新idea.exe.vmoptions文件:-Djsse.enableSNIExtension=false 对于 java webstart 使用这个:javaws -J-Djsse.enableSNIExtension=false yourClass 我的 Jersey 客户端使用 https 休息服务器时遇到了完全相同的问题。原来我用了你的把戏,它奏效了!!谢谢! 对于那些想知道 Java 8 的人,Oracle 仍然没有改变行为,仍然要求程序员捕获不(正确)支持 SNI 的服务器:docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/… @Blauhirn 联系您的虚拟主机支持,他们应该修复他们的配置。如果他们使用 Apache,请查看需要进行的一些更改。【参考方案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();
这段代码有效:
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) 的 Wiki:
112 仅 TLS 无法识别名称警告;客户端的服务器名称指示器指定了服务器不支持的主机名
这让我查看了我的 Apache 配置文件,我发现如果我为从客户端/java 端发送的名称添加了 ServerName 或 ServerAlias,它可以正常工作而没有任何错误。
<VirtualHost mydomain.com:443>
ServerName mydomain.com
ServerAlias www.mydomain.com
【讨论】:
这看起来像是 Java 的 TLS 实现中的一个错误。 我实际上为此打开了一个错误。我得到了这个号码(尚未显示):bugs.sun.com/bugdatabase/view_bug.do?bug_id=7127374 谢谢,添加ServerAlias
对我有用。我在 Wireshark 中看到了相同的 TLS 警报。
这也对我有用。 (如果我相信它而不是另辟蹊径,效果会更好)
@JosephShraibman,说 Oracle 员工是个白痴是不恰当的。当您使用两个 ServerName
s 时,Apache 会返回一个 unrecognized_name
warning 警报(也可能是一个 fatal 警报)。 RFC 6066 在此主题上准确地说:“不建议发送警告级别的 unrecognized_name(112) 警报,因为客户端响应警告级别警报的行为是不可预测的。”。该员工犯的唯一错误是认为这是一个致命警报。这既是 JRE 错误,也是 Apache 错误。【参考方案3】:
您可以使用系统属性 jsse.enableSNIExtension=false 禁用发送 SNI 记录。
如果您可以更改代码,则使用SSLCocketFactory#createSocket()
(没有主机参数或连接的套接字)会有所帮助。在这种情况下,它不会发送 server_name 指示。
【讨论】:
这样做是这样的:System.setProperty ("jsse.enableSNIExtension", "false");
我发现这并不总是有效。我不知道为什么它在某些情况下有效,而在其他情况下无效。
与-Djsse.enableSNIExtension=false
一起为我工作。但它还有什么作用?它对 Java 的其他安全性有害吗?
不,这只是意味着某些在共享 IP 后面使用多个主机名的站点(尤其是 Web 服务器)不知道要发送什么证书。但除非您的 Java 应用程序必须连接到数百万个网站,否则您将不需要该功能。如果您遇到这样的服务器,您的证书验证可能会失败并且连接会中止。所以预计不会出现安全问题。【参考方案4】:
我们也在新的 Apache 服务器构建中遇到了这个错误。
我们的解决方法是在httpd.conf
中定义一个ServerAlias
,它与Java 尝试连接的主机名相对应。我们的ServerName
设置为内部主机名。我们的 SSL 证书使用的是外部主机名,但这不足以避免警告。
为了帮助调试,你可以使用这个 ssl 命令:
openssl s_client -servername <hostname> -connect <hostname>:443 -state
如果该主机名有问题,那么它将在输出顶部附近打印此消息:
SSL3 alert read: warning:unrecognized name
我还应该注意,使用该命令连接到内部主机名时我们没有收到该错误,即使它与 SSL 证书不匹配。
【讨论】:
感谢您提供有关如何使用openssl
重现问题的示例。很有帮助。
有没有办法让 openssl 客户端查看源自消息 SSL3 alert read: warning:unrecognized name
的名称差异?我看到打印的警报,但输出并不能帮助我真正看到这种命名差异
这对我不起作用。使用OpenSSL 1.0.1e-fips 11 Feb 2013
、OpenSSL 1.0.2k-fips 26 Jan 2017
和LibreSSL 2.6.5
测试。
@GregDubicki 试试openssl s_client -showcerts -connect <hostname>:443
【参考方案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
.【参考方案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)
?由于target
更改为空字符串,verifyHostname
不会成功。我在这里遗漏了一些明显的东西吗?
这正是我想要的,非常感谢!我没有找到任何其他方式来支持 SNI 和同时响应此“unrecognized_name”警告的服务器。
此解决方案有效,除非您使用代理服务器。
刚刚通过 Apache 客户端代码进行了快速调试,从 verifyHostname() 开始,我看不出使用 DefaultHostnameVerifier 是如何工作的。我怀疑这个解决方案需要禁用主机名验证(例如使用 NoopHostnameVerifier),这不是一个好主意,因为它允许中间人攻击。【参考方案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 的跟进。可悲的是,甲骨文仍然不了解整个事情。 SNI 警报不是致命的,它可能被视为致命的。但是大多数典型的 SSL 客户端(即浏览器)选择忽略它,因为它不是真正的安全问题(因为您仍然需要检查服务器证书并且会检测到错误的端点)。当然很遗憾 CA 无法设置建立一个正确配置的 https 服务器。 实际上,您可以向 Jarsigner 工具提供系统属性,只是不在帮助消息中。它包含在文档中。愚蠢的我相信网上的文字。当然,他们以此为借口不修复它。 顺便说一句:我目前正在 security-dev@openjdk 上讨论这个问题,目前我正在评估它,看起来赛门铁克修复了 GEOTrust 时间戳服务器,它现在可以正确接受 URL 而没有警告。但我仍然认为应该解决这个问题。如果你想看看,test client 项目和discussion:【参考方案10】:我遇到了同样的问题,结果发现反向 dns 设置不正确,它指向 IP 的错误主机名。在我更正反向 dns 并重新启动 httpd 后,警告消失了。 (如果我不更正反向 dns,添加 ServerName 也对我有用)
【讨论】:
【参考方案11】:我的VirtualHost
的ServerName
默认被注释掉了。取消注释后它起作用了。
【讨论】:
这里也一样。服务器名称丢失。【参考方案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 时也遇到了这个问题。
令人担忧的是,我的客户在 Java 控制面板中发现了一个可以解决此问题的设置。
在“高级”选项卡中,您可以选中“使用 SSL 2.0 兼容的 ClientHello 格式”。
这似乎解决了问题。
我们在 Internet Explorer 浏览器中使用 Java 小程序。
希望这会有所帮助。
【讨论】:
【参考方案14】:这里是 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();
【讨论】:
【参考方案15】:当通过 Eclipse 访问运行 subversion 的 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
中的指令一起输入。
删除ports.conf
中的指令后,问题消失了(重启Apache后,自然)
【讨论】:
【参考方案16】:只是在这里添加一个解决方案。这可能对 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/$1 [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/$1 [NC,R=301,L]
</VirtualHost>
【讨论】:
【参考方案17】:有一个easier way,您可以在其中使用自己的 HostnameVerifier 来隐式信任某些连接。这个问题出现在 Java 1.7 中,其中添加了 SNI 扩展,而您的错误是由于服务器配置错误造成的。
您可以使用“-Djsse.enableSNIExtension=false”在整个 JVM 中禁用 SNI,或者阅读我的博客,其中解释了如何在 URL 连接之上实现自定义验证器。
【讨论】:
以上是关于SSL 握手警报:升级到 Java 1.7.0 后出现 unrecognized_name 错误的主要内容,如果未能解决你的问题,请参考以下文章
Paypal Sandbox IPN: 14077410:sslroutines:ssl23_get_server_hello:sslv3 警报握手失败
使用 fsockopen 的警报握手失败 paypal IPN 集成
PayPal IPN OPENSSL 错误:14077410:SSL 例程:SSL23_GET_SERVER_HELLO:sslv3 警报握手失败
SSL 握手错误 javax.net.ssl.SSLHandshakeException 收到致命警报 bad_certificate