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


225

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

谢谢。

Answers:


304

Java 7引入了默认情况下启用的SNI支持。我发现某些配置错误的服务器会在SSL握手中发送“无法识别的名称”警告,大多数客户端会忽略此警告... Java除外。正如@Bob Kerns提到的那样,Oracle工程师拒绝“修复”此错误/功能。

解决方法是,他们建议设置jsse.enableSNIExtension属性。要允许您的程序无需重新编译就可以运行,请按以下方式运行您的应用:

java -Djsse.enableSNIExtension=false yourClass

也可以在Java代码中设置该属性,但是必须在任何SSL操作之前设置该属性。加载SSL库后,您可以更改属性,但不会对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消息中添加主机名)。

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


5
有效,谢谢!通过HTTPS连接时,IntelliJ IDEA颠覆客户端具有相同的错误。只需更新以下行的idea.exe.vmoptions文件:-Djsse.enableSNIExtension = false
Dima

2
对于Java Webstart,请使用以下命令:javaws -J-Djsse.enableSNIExtension = false yourClass
Torsten

我的https休息服务器的Jersey客户端遇到了完全相同的问题。原来我用了你的把戏,而且有效!!谢谢!
shahshi15 2014年

4
对于那些对Java 8感到好奇的人,Oracle仍然没有改变其行为,仍然要求程序员捕获不(正确)支持SNI的服务器:docs.oracle.com/javase/8/docs/technotes/guides/security/jsse /…
Lekensteyn

2
@Blauhirn与您的网站托管支持联系,他们应该修复其配置。如果他们使用Apache,请查看是否需要进行一些更改。
Lekensteyn '16

89

我有我认为相同的问题。我发现我需要调整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期间,警告警报(级别:警告,说明:无法识别的名称),Server Hello已从服务器发送到客户端。但这只是一个警告,但是Java 7.1随后立即返回“致命,说明:意外消息”,我认为这意味着Java SSL库不喜欢看到名称无法识别的警告。

从Wiki上的传输层安全性(TLS):

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

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

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

3
这看起来像Java TLS实施中的错误。
圣保罗Ebermann

1
我实际上为此打开了一个错误。我分配了这个数字(尚不可见): bugs.sun.com/bugdatabase/view_bug.do?bug_id=7127374
eckes 2012年

1
谢谢,ServerAlias为我增加了工作。我在Wireshark中看到了相同的TLS警报。
杰里德·贝克

2
这也对我有用。(如果我相信它,并且不会沿着另一条路走下去,效果会更好)
最终用户

10
@JosephShraibman说Oracle员工是个白痴是不合适的。当您使用两个ServerNames时,Apache会发出unrecognized_name 警告警报(也可能是致命警报)。RFC 6066在此主题上恰好说明了这一点:“ 不建议发送警告级别的unrecognized_name(112)警报,因为客户端响应警告级别的警报的行为是不可预测的。 ” 该员工唯一的错误是假设这是致命的警报。这既是一个JRE错误,又是一个Apache错误。
布鲁诺

36

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

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


11
这样做是这样的:System.setProperty ("jsse.enableSNIExtension", "false");
jn1kk

我发现这并不总是有效。我不知道为什么它在某些情况下会起作用,而在另一些情况下却不会。
约瑟夫·施赖布曼

2
为我工作-Djsse.enableSNIExtension=false。但是它还能做什么?它对其余的Java安全性有害吗?
ceving 2013年

2
不,这仅意味着某些在共享IP后面使用多个主机名的站点(尤其是Web服务器)不知道要发送什么证书。但是除非您的Java应用程序必须连接到数百万个网站,否则您将不需要该功能。如果遇到这样的服务器,则证书验证可能会失败并且连接将中止。因此不会出现安全问题。
eckes 2013年

17

我们还在新的Apache服务器上遇到了此错误。

在我们的情况下,修复是定义ServerAliashttpd.conf相应的被主机名Java是试图连接到。我们ServerName被设置为内部主机名。我们的SSL证书使用的是外部主机名,但这不足以避免发出警告。

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

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

如果该主机名存在问题,则它将在输出顶部附近显示此消息:

SSL3 alert read: warning:unrecognized name

我还应注意,即使使用不符合SSL证书的命令,​​使用该命令连接内部主机名时,也不会出现该错误。


6
感谢您提供有关如何使用再现问题的示例openssl。这非常有帮助。
sideshowbarker

2
openssl客户端是否有办法查看源于消息的名称差异SSL3 alert read: warning:unrecognized name?我看到警告已打印,但是输出并没有帮助我真正看到这种命名差异
mox601 '16

这对我不起作用。经测试OpenSSL 1.0.1e-fips 11 Feb 2013OpenSSL 1.0.2k-fips 26 Jan 2017LibreSSL 2.6.5
格雷格·杜比奇

15

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

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

这样,您可以使用SNI,并且apache不会发回SSL警告。

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


据我了解,apache仅在客户端使用未配置为ServerName或ServerAlias的名称的情况下发送此警告。因此,如果您具有通配符证书,请指定所有使用的子域,或*.mydomain.com对ServerAlias 使用语法。请注意,您可以使用添加多个别名ServerAlias,例如ServerAlias *.mydomain.com mydomain.com *.myotherdomain.com
Michael Paesold '16

13

它应该是有用的。要在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);

如果您这样做的话。你如何处理verifyHostname(sslsock, target)SSLConnectionSocketFactory.createLayeredSocket?由于target将更改为空字符串,verifyHostname因此不会成功。我在这里错过明显的东西吗?
Haozhun

2
正是我想要的,非常感谢!我没有找到其他方法来支持SNI和同时响应此“ unrecognized_name”警告的服务器。
korpe

除非您使用代理服务器,否则此解决方案有效。
mrog

从verifyHostname()开始,通过Apache客户端代码完成了快速调试之后,我看不到如何使用DefaultHostnameVerifier进行工作。我怀疑此解决方案要求禁用主机名验证(例如,使用NoopH​​ostnameVerifier),这不是一个好主意,因为它允许中间人攻击。
FlyingSheep


6

使用Spring Boot以及jvm 1.7和1.8 遇到了这个问题。在AWS上,我们没有选择将ServerName和ServerAlias更改为匹配的选项(它们是不同的),因此我们执行以下操作:

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

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

这样我们就可以绕过带有“无法识别的名称”的问题。


5

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

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

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

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


1
感谢Bob的跟进。可悲的是,Oracle仍然不了解整个事情。SNI警报不是致命的,它可能是致命的。但是大多数典型的SSL客户端(例如浏览器)都选择忽略它,因为这不是真正的安全问题(因为您仍然需要检查服务器证书并会检测到错误的端点)。对于CA无法设置CA,可悲的是正确配置的https服务器。
eckes 2012年

2
实际上,事实证明您可以为Jarsigner工具提供系统属性,而只是不在帮助消息中。它涵盖在文档中。愚蠢的我相信在线文本。当然,他们以此为借口不予解决。
鲍勃·科恩斯

顺便说一句:我目前正在security-dev @ openjdk上讨论此问题,目前我正在评估它,就像Symantec修复了GEOTrust时间戳服务器一样,它现在可以正确接受URL,而不会发出警告。但是我仍然认为应该固定。如果您想看一看,请测试一个客户项目并进行讨论
eckes 2013年

3

我遇到了同样的问题,结果发现反向DNS设置不正确,它指向IP的错误主机名。在纠正反向dns并重新启动httpd之后,警告消失了。(如果我不纠正反向DNS,那么添加ServerName也可以解决我的问题)



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

1

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

令人震惊的是,我的客户在Java控制面板中发现了一个可以解决此问题的设置。

在“高级”选项卡中,您可以选中“使用与SSL 2.0兼容的ClientHello格式”。

这似乎解决了问题。

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

希望这可以帮助。


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之后)


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>

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


}

-1

有一种更简单的方法,您可以使用自己的HostnameVerifier隐式信任某些连接。Java 1.7中存在此问题,其中已添加了SNI扩展,并且您的错误是由于服务器配置错误引起的。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.