我今天从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)


这只是一个测试项目,这就是为什么我允许和使用不信任证书的原因: >
我成功尝试连接到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代理,此提交实现了后备设置。

评论


它有效,谢谢!通过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

#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



谢谢,添加S​​erverAlias对我有用。我在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进行工作。我怀疑此解决方案要求禁用主机名验证(例如使用NoopH​​ostnameVerifier),但这不是一个好主意,因为它允许中间人攻击。

–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 楼

默认情况下,我的VirtualHostServerName已被注释掉。取消注释后就可以了。

评论


同样在这里。服务器名称丢失。

–黑暗之星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连接的顶部实现自定义验证程序。