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

时间:2011-09-30 20:18:39

标签: java ssl

我今天从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。 我的错在哪里?

感谢。

17 个答案:

答案 0 :(得分:294)

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并仍然支持配置错误的服务器:

  1. 使用您要连接的主机名创建SSLSocket。我们将此命名为sslsock
  2. 尝试运行sslsock.startHandshake()。这将阻塞直到它完成或在出错时抛出异常。每当startHandshake()发生错误时,都会收到异常消息。如果它等于handshake alert: unrecognized_name,那么您找到了配置错误的服务器。
  3. 当您收到unrecognized_name警告(在Java中致命)时,请重试打开SSLSocket,但这次没有主机名。这有效地禁用了SNI(毕竟,SNI扩展是关于向ClientHello消息添加主机名)。
  4. 对于Webscarab SSL代理,this commit实现了后备设置。

答案 1 :(得分:86)

我有我认为相同的问题。 我发现我需要调整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库不喜欢看到无法识别的名称的警告。

来自Wiki on Transport Layer Security(TLS):

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

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

<VirtualHost mydomain.com:443>
  ServerName mydomain.com
  ServerAlias www.mydomain.com

答案 2 :(得分:35)

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

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

答案 3 :(得分:17)

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

我们的案例中的修复是在ServerAlias中定义httpd.conf,该ServerName对应于Java尝试连接的主机名。我们的openssl s_client -servername <hostname> -connect <hostname>:443 -state已设置为内部主机名。我们的SSL证书使用外部主机名,但这不足以避免警告。

为了帮助调试,您可以使用此ssl命令:

SSL3 alert read: warning:unrecognized name

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

function initialize() { var mapOptions = { zoom: 4, center: new google.maps.LatLng(40.09024, -95.712891), }; var map = new google.maps.Map(document.getElementById('map-canvas'),mapOptions); var minPos = new google.maps.LatLng(49.25, -123.1); var maxPos = new google.maps.LatLng(34.052234, -74.005973); for(var i = 0; i < 16;i++) { var lat = getRandomInterval(minPos.lat(),maxPos.lat()); var lng = getRandomInterval(minPos.lng(),maxPos.lng()); var bounds = new google.maps.LatLngBounds( new google.maps.LatLng(lat, lng), new google.maps.LatLng(lat + 1.5, lng + 4.0) ); showRect(map,bounds); } } function showRect(map,bounds){ // Define the rectangle and set its editable property to true. var rectangle = new google.maps.Rectangle({ bounds: bounds, editable: true, draggable: true }); rectangle.setMap(map); return rectangle; } function getRandomInterval(min, max) { return Math.random() * (max - min) + min; } google.maps.event.addDomListener(window, 'load', initialize);

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

答案 4 :(得分:15)

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

ServerName catchall.mydomain.com
ServerAlias *.mydomain.com

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

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

答案 5 :(得分: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;
            }
        }
    }
}

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);

答案 6 :(得分:10)

使用:

  • System.setProperty(“jsse.enableSNIExtension”,“false”);
  • 重新启动Tomcat(重要)

答案 7 :(得分:5)

不幸的是,您无法为jarsigner.exe工具提供系统属性。

我提交了错误7177232,引用了@eckes'缺陷7127374,并解释了错误关闭的原因。

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

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

答案 8 :(得分:5)

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

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

System.setProperty("jsse.enableSNIExtension", "false")
bootRun.systemProperties = System.properties

这使我们能够通过“无法识别的名称”来绕过这个问题。

答案 9 :(得分:3)

我遇到了同样的问题,结果发现反向dns没有设置正确,它指向IP的错误主机名。在我纠正反向dns并重新启动httpd之后,警告消失了。 (如果我没有纠正反向dns,添加ServerName也为我做了伎俩)

答案 10 :(得分:2)

如果要使用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;
}

答案 11 :(得分:2)

默认情况下我的bind VirtualHost被注释掉了。它在取消注释后起作用了。

答案 12 :(得分:1)

在从Java 1.6_29升级到1.7时,我也遇到过这个问题。

很明显,我的客户在Java控制面板中发现了一个解决此问题的设置。

在“高级”标签中,您可以选中“使用SSL 2.0兼容的ClientHello格式”#。

这似乎解决了这个问题。

我们在Internet Explorer浏览器中使用Java小程序。

希望这有帮助。

答案 13 :(得分:0)

只需在此处添加解决方案。这可能有助于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>

答案 14 :(得分:0)

当通过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之后)

答案 15 :(得分:0)

以下是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();
    }


}

答案 16 :(得分:-2)

有一个easier way,您可以使用自己的HostnameVerifier隐式信任某些连接。问题来自Java 1.7,其中添加了SNI扩展,并且您的错误是由于服务器配置错误造成的。

您可以使用“-Djsse.enableSNIExtension = false”在整个JVM中禁用SNI,也可以阅读我的博客,在那里我将解释如何在URL连接之上实现自定义验证程序。