SSL 握手警报:升级到 Java 1.7.0 后出现unrecognized_name错误

2022-08-31 05:46:59

我今天从Java 1.6升级到Java 1.7。从那时起,当我尝试通过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)

代码如下:

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 库后,您可以更改该属性,但它不会对 SNI 状态产生任何影响。要在运行时禁用 SNI(具有上述限制),请使用:

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

设置此标志的缺点是 SNI 在应用程序中的所有位置都处于禁用状态。为了利用SNI并且仍然支持配置错误的服务器:

  1. 使用要连接到的主机名创建 。让我们命名这个 .SSLSocketsslsock
  2. 尝试运行 。这将阻塞,直到它完成或在出错时引发异常。每当 中发生错误时,获取异常消息。如果它等于 ,则您发现了配置错误的服务器。sslsock.startHandshake()startHandshake()handshake alert: unrecognized_name
  3. 当您收到警告(在 Java 中为致命)时,请重试打开 ,但这次没有主机名。这有效地禁用了SNI(毕竟,SNI扩展是关于向ClientHello消息添加主机名的)。unrecognized_nameSSLSocket

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

这段代码起作用了:

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警告警报(级别:警告,描述:无法识别的名称)期间,服务器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