【问题标题】:SSL handshake alert: unrecognized_name error since upgrade to Java 1.7.0SSL 握手警报:升级到 Java 1.7.0 后出现 unrecognized_name 错误
【发布时间】:2011-11-28 18:16:37
【问题描述】:

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

谢谢。

【问题讨论】:

    标签: java ssl


    【解决方案1】:

    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 消息添加主机名)。

    对于 Webscarab SSL 代理,this commit 实现了后备设置。

    【讨论】:

    • 它有效,谢谢! IntelliJ IDEA subversion 客户端在通过 HTTPS 连接时出现同样的错误。只需要使用以下行更新idea.exe.vmoptions文件:-Djsse.enableSNIExtension=false
    • 对于 java webstart 使用这个:javaws -J-Djsse.enableSNIExtension=false yourClass
    • 我的 Jersey 客户端使用 https 休息服务器时遇到了完全相同的问题。原来我用了你的把戏,它奏效了!!谢谢!
    • 对于那些想知道 Java 8 的人,Oracle 仍然没有改变行为,仍然要求程序员捕获不(正确)支持 SNI 的服务器:docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/…
    • @Blauhirn 联系您的虚拟主机支持,他们应该修复他们的配置。如果他们使用 Apache,请查看需要进行的一些更改。
    【解决方案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 期间,警告 警报(级别:警告,描述:无法识别的名称),服务器您好 正在从服务器发送到客户端。 这只是一个警告,然而,Java 7.1 立即回复了“致命,描述:意外消息”,我认为这意味着 Java SSL 库不喜欢看到无法识别名称的警告。

    来自关于传输层安全性 (TLS) 的 Wiki:

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

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

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

    【讨论】:

    • 这看起来像是 Java 的 TLS 实现中的一个错误。
    • 我实际上为此打开了一个错误。我得到了这个号码(尚未显示):bugs.sun.com/bugdatabase/view_bug.do?bug_id=7127374
    • 谢谢,添加ServerAlias 对我有用。我在 Wireshark 中看到了相同的 TLS 警报。
    • 这也对我有用。 (如果我相信它而不是另辟蹊径,效果会更好)
    • @JosephShraibman,说 Oracle 员工是个白痴是不恰当的。当您使用两个 ServerNames 时,Apache 会返回一个 unrecognized_name warning 警报(也可能是一个 fatal 警报)。 RFC 6066 在此主题上准确地说:“不建议发送警告级别的 unrecognized_name(112) 警报,因为客户端响应警告级别警报的行为是不可预测的。”。该员工犯的唯一错误是认为这是一个致命警报。这既是 JRE 错误,也是 Apache 错误。
    【解决方案3】:

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

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

    【讨论】:

    • 这样做是这样的:System.setProperty ("jsse.enableSNIExtension", "false");
    • 我发现这并不总是有效。我不知道为什么它在某些情况下有效,而在其他情况下无效。
    • -Djsse.enableSNIExtension=false 一起为我工作。但它还有什么作用?它对 Java 的其他安全性有害吗?
    • 不,这只是意味着某些在共享 IP 后面使用多个主机名的站点(尤其是 Web 服务器)不知道要发送什么证书。但除非您的 Java 应用程序必须连接到数百万个网站,否则您将不需要该功能。如果您遇到这样的服务器,您的证书验证可能会失败并且连接会中止。所以预计不会出现安全问题。
    【解决方案4】:

    我们也在新的 Apache 服务器构建中遇到了这个错误。

    我们的解决方法是在httpd.conf 中定义一个ServerAlias,它与Java 尝试连接的主机名相对应。我们的ServerName 设置为内部主机名。我们的 SSL 证书使用的是外部主机名,但这不足以避免警告。

    为了帮助调试,你可以使用这个 ssl 命令:

    openssl s_client -servername &lt;hostname&gt; -connect &lt;hostname&gt;:443 -state

    如果该主机名有问题,那么它将在输出顶部附近打印此消息:

    SSL3 alert read: warning:unrecognized name

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

    【讨论】:

    • 感谢您提供有关如何使用openssl 重现问题的示例。很有帮助。
    • 有没有办法让 openssl 客户端查看源自消息 SSL3 alert read: warning:unrecognized name 的名称差异?我看到打印的警报,但输出并不能帮助我真正看到这种命名差异
    • 这对我不起作用。使用OpenSSL 1.0.1e-fips 11 Feb 2013OpenSSL 1.0.2k-fips 26 Jan 2017LibreSSL 2.6.5 测试。
    • @GregDubicki 试试openssl s_client -showcerts -connect &lt;hostname&gt;:443
    【解决方案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.
    【解决方案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)?由于target 更改为空字符串,verifyHostname 不会成功。我在这里遗漏了一些明显的东西吗?
    • 这正是我想要的,非常感谢!我没有找到任何其他方式来支持 SNI 和同时响应此“unrecognized_name”警告的服务器。
    • 此解决方案有效,除非您使用代理服务器。
    • 刚刚通过 Apache 客户端代码进行了快速调试,从 verifyHostname() 开始,我看不出使用 DefaultHostnameVerifier 是如何工作的。我怀疑这个解决方案需要禁用主机名验证(例如使用 NoopH​​ostnameVerifier),这不是一个好主意,因为它允许中间人攻击。
    【解决方案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 的跟进。可悲的是,甲骨文仍然不了解整个事情。 SNI 警报不是致命的,它可能被视为致命的。但是大多数典型的 SSL 客户端(即浏览器)选择忽略它,因为它不是真正的安全问题(因为您仍然需要检查服务器证书并且会检测到错误的端点)。当然很遗憾 CA 无法设置建立一个正确配置的 https 服务器。
        • 实际上,您可以向 Jarsigner 工具提供系统属性,只是不在帮助消息中。它包含在文档中。愚蠢的我相信网上的文字。当然,他们以此为借口不修复它。
        • 顺便说一句:我目前正在 security-dev@openjdk 上讨论这个问题,目前我正在评估它,看起来赛门铁克修复了 GEOTrust 时间戳服务器,它现在可以正确接受 URL 而没有警告。但我仍然认为应该解决这个问题。如果你想看看,test client 项目和discussion
        【解决方案10】:

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

        【讨论】:

          【解决方案11】:

          我的VirtualHostServerName 默认被注释掉了。取消注释后它起作用了。

          【讨论】:

          • 这里也一样。服务器名称丢失。
          【解决方案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 时也遇到了这个问题。

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

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

            这似乎解决了问题。

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

            希望这会有所帮助。

            【讨论】:

              【解决方案14】:

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

              【讨论】:

                【解决方案15】:

                当通过 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后,自然)

                【讨论】:

                  【解决方案16】:

                  只是在这里添加一个解决方案。这可能对 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>
                  

                  【讨论】:

                    【解决方案17】:

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

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

                    【讨论】:

                      猜你喜欢
                      • 2013-03-21
                      • 1970-01-01
                      • 1970-01-01
                      • 2016-01-26
                      • 1970-01-01
                      • 1970-01-01
                      • 2015-11-19
                      • 2013-07-08
                      相关资源
                      最近更新 更多