SSL握手警报:自升级到Java 1.7.0以来unrecognized_name错误

pvo*_*off 222 java ssl

我今天从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)
Run Code Online (Sandbox Code Playgroud)

这是代码:

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);  
}
Run Code Online (Sandbox Code Playgroud)

它只是一个测试项目,这就是为什么我允许和使用不受信任的证书与代码:

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);
} 
Run Code Online (Sandbox Code Playgroud)

我成功尝试连接到https://google.com.我的错在哪里?

谢谢.

Lek*_*eyn 299

Java 7引入了默认启用的SNI支持.我发现某些配置错误的服务器在SSL握手中发出"无法识别的名称"警告,大多数客户端都会忽略该警告......除了Java.正如@Bob Kerns所说,Oracle工程师拒绝"修复"这个错误/功能.

作为解决方法,他们建议设置jsse.enableSNIExtension属性.要允许您的程序无需重新编译即可运行,请将您的应用程序运行为:

java -Djsse.enableSNIExtension=false yourClass
Run Code Online (Sandbox Code Playgroud)

该属性也可以在Java代码中设置,但必须在任何SSL操作之前设置.加载SSL库后,您可以更改属性,但不会对SNI状态产生任何影响.要在运行时禁用SNI(具有上述限制),请使用:

System.setProperty("jsse.enableSNIExtension", "false");
Run Code Online (Sandbox Code Playgroud)

设置此标志的缺点是SNI在应用程序中的任何位置都被禁用.为了利用SNI并仍然支持配置错误的服务器:

  1. SSLSocket使用要连接的主机名创建一个.我们来这个名字吧sslsock.
  2. 试着跑sslsock.startHandshake().这将阻塞直到它完成或在出错时抛出异常.每当发生错误时startHandshake(),都会收到异常消息.如果它等于handshake alert: unrecognized_name,那么您找到了配置错误的服务器.
  3. 当您收到unrecognized_name警告(在Java中致命)时,重试打开a SSLSocket,但这次没有主机名.这有效地禁用了SNI(毕竟,SNI扩展是关于向ClientHello消息添加主机名).

对于Webscarab SSL代理,此提交实现了后备设置.

  • 它有效,谢谢!通过HTTPS连接时,IntelliJ IDEA subversion客户端具有相同的错误.只需要用行更新idea.exe.vmoptions文件:-Djsse.enableSNIExtension = false (5认同)
  • 对于那些对Java 8感到疑惑的人来说,Oracle仍然没有改变行为,仍然要求程序员捕获那些没有(正确)支持SNI的服务器:http://docs.oracle.com/javase/8/docs/technotes/guides /security/jsse/JSSERefGuide.html#SNIExamples (4认同)
  • 对于java webstart,请使用:javaws -J-Djsse.enableSNIExtension = false yourClass (2认同)
  • @Blauhirn联系您的webhost支持,他们应该修复他们的配置.如果他们使用Apache,请查看需要进行的一些更改. (2认同)

小智 87

我有我认为同样的问题.我发现我需要调整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();
   }
}
Run Code Online (Sandbox Code Playgroud)

这段代码有效:

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();
   }
}
Run Code Online (Sandbox Code Playgroud)

Wireshark透露,在TSL/SSL Hello警告警报(级别:警告,描述:无法识别的名称)期间,服务器Hello正从服务器发送到客户端.这只是一个警告,然而,Java 7.1然后立即响应"致命,描述:意外消息",我认为这意味着Java SSL库不喜欢看到无法识别的名称的警告.

来自Wiki on Transport Layer Security(TLS):

112无法识别的名称警告仅限TLS; 客户端的服务器名称指示符指定了服务器不支持的主机名

这让我看看我的Apache配置文件,我发现如果我为客户端/ java端发送的名称添加了ServerName或ServerAlias,它可以正常工作而没有任何错误.

<VirtualHost mydomain.com:443>
  ServerName mydomain.com
  ServerAlias www.mydomain.com
Run Code Online (Sandbox Code Playgroud)

  • @JosephShraibman,说甲骨文员工是个白痴是不合适的.当你使用两个`ServerName`时,Apache会以`unrecognized_name`**警告**警告响应(这也可能是**致命**警报).[RFC 6066](https://tools.ietf.org/html/rfc6066#section-3)就此主题准确地说明了这一点:"*建议不要发送警告级别的无法识别的名称(112)警报,因为客户端响应警告级别警报的行为是不可预测的.*".这名员工唯一的错误就是假设这是*致命*警报.这是一个JRE错误,因为它是Apache的错误. (10认同)
  • 这看起来像Java的TLS实现中的一个错误. (3认同)
  • 这对我也有用.(如果我相信它并且没有沿着另一条路走下去,那会更好) (2认同)

eck*_*kes 36

您可以使用System属性jsse.enableSNIExtension = false禁用发送SNI记录.

如果您可以更改它有助于使用的代码SSLCocketFactory#createSocket()(没有主机参数或连接的套接字).在这种情况下,它不会发送server_name指示.

  • 这是这样做的:`System.setProperty("jsse.enableSNIExtension","false");` (11认同)
  • 使用`-Djsse.enableSNIExtension = false`为我工作.但它还能做些什么呢?它对Javas的其他安全性有害吗? (2认同)
  • 不,它只是意味着在共享IP后面使用多个主机名的某些站点(尤其是Web服务器)不知道要发送什么证书.但除非您的Java应用程序必须连接到数百万个网站,否则您将不需要该功能.如果遇到此类服务器,则证书验证可能会失败,连接将中止.所以没有预期的安全问题. (2认同)

Sco*_*yre 17

我们还在新的Apache服务器版本上遇到了这个错误.

我们的例子中的修复是ServerAliashttpd.confJava中尝试连接的主机名中定义一个.我们ServerName设置为内部主机名.我们的SSL证书使用外部主机名,但这不足以避免警告.

要帮助调试,可以使用此ssl命令:

openssl s_client -servername <hostname> -connect <hostname>:443 -state

如果该主机名出现问题,则会在输出顶部附近打印此消息:

SSL3 alert read: warning:unrecognized name

我还应该注意,使用该命令连接到内部主机名时,我们没有收到该错误,即使它与SSL证书不匹配.

  • 感谢您提供有关如何使用`openssl`重现问题的示例.这非常有帮助. (6认同)
  • 有没有办法让openssl客户端看到源自消息的名称差异`SSL3 alert read:warning:unrecognized name`?我看到打印的警告,但输出并没有帮助我实际看到这个命名差异 (2认同)

小智 15

您可以定义使用任意ServerName和通配符ServerAlias的最后一个catchall虚拟主机,而不是依赖于apache中的默认虚拟主机机制,例如

ServerName catchall.mydomain.com
ServerAlias *.mydomain.com
Run Code Online (Sandbox Code Playgroud)

这样你就可以使用SNI而apache不会发回SSL警告.

当然,这只有在您可以使用通配符语法轻松描述所有域时才有效.


Shc*_*ein 12

它应该是有用的.要重试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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

cm = new PoolingHttpClientConnectionManager(new SniHttpClientConnectionOperator(socketFactoryRegistry), null, -1, TimeUnit.MILLISECONDS);
Run Code Online (Sandbox Code Playgroud)

  • 这正是我所寻找的,非常感谢你!我没有找到任何其他方式来支持SNI和同时响应此"unrecognized_name"警告的服务器. (2认同)

小智 11

使用:

  • System.setProperty("jsse.enableSNIExtension","false");
  • 重启Tomcat(重要)


Bob*_*rns 5

遗憾的是,您无法为jarsigner.exe工具提供系统属性.

我提交了缺陷7177232,引用了@eckes的缺陷7127374并解释了为什么它被错误关闭了.

我的缺陷是关于对jarsigner工具的影响,但也许会导致他们重新打开其他缺陷并正确解决问题.

更新:实际上,事实证明,您可以向Jarsigner工具提供系统属性,它不在帮助消息中.使用jarsigner -J-Djsse.enableSNIExtension=false

  • 实际上,事实证明,您可以向Jarsigner工具提供系统属性,它不在帮助消息中.它包含在文档中.傻我相信在线文本.当然,他们以此为借口不修理它. (2认同)

Ray*_*ter 5

使用spring boot和jvm 1.7和1.8 进入这个问题.在AWS上,我们没有选择更改ServerName和ServerAlias以匹配(它们是不同的),因此我们执行了以下操作:

build.gradle中,我们添加了以下内容:

System.setProperty("jsse.enableSNIExtension", "false")
bootRun.systemProperties = System.properties
Run Code Online (Sandbox Code Playgroud)

这使我们能够通过"无法识别的名称"绕过这个问题.