Предупреждение о подтверждении SSL: ошибка unrecognized_name с момента обновления до версии 1.7.7

Я сегодня обновился с 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. где моя вина?

Спасибо.

Ответ 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 этот коммит реализует настройку спада.

Ответ 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 затем сразу же ответил "Fatal, Description: Unexpected Message", который, как я полагаю, означает, что библиотеки Java SSL не любят видеть предупреждение о непризнанном имени.

Из Wiki по безопасности транспортного уровня (TLS):

112 Предупреждение о непризнанном имени только TLS; указатель имени сервера клиента указал имя хоста, которое не поддерживается сервером

Это заставило меня посмотреть мои конфигурационные файлы Apache, и я обнаружил, что если бы я добавил имя_сервера или ServerAlias ​​для имени, отправленного со стороны client/java, он работал правильно без каких-либо ошибок.

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

Ответ 3

Вы можете отключить отправку записей SNI с помощью свойства System jsse.enableSNIExtension = false.

Если вы можете изменить код, он помогает использовать SSLCocketFactory#createSocket() (без параметра хоста или с подключенным сокетом). В этом случае он не будет отправлять идентификатор server_name.

Ответ 4

Вместо того, чтобы полагаться на механизм виртуального хоста по умолчанию в apache, вы можете определить один последний виртуальный хост, который использует произвольное имя сервера и подстановочный сервер ServerAlias, например

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

Таким образом вы можете использовать SNI, и apache не отправит предупреждение SSL.

Конечно, это работает только в том случае, если вы можете легко описать все свои домены с помощью подстановочного синтаксиса.

Ответ 5

Мы также столкнулись с этой ошибкой на новой сборке сервера Apache.

Исправление в нашем случае состояло в том, чтобы определить ServerAlias в httpd.conf, который соответствует имени хоста, к которому пытается подключиться Java. Наш ServerName был настроен на внутреннее имя хоста. Наш сертификат SSL использовал имя внешнего хоста, но этого было недостаточно, чтобы избежать предупреждения.

Чтобы отладить, вы можете использовать эту команду ssl:

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

Если с этим именем хоста возникла проблема, он будет печатать это сообщение в верхней части вывода:

SSL3 alert read: warning:unrecognized name

Я также должен отметить, что мы не получили эту ошибку при использовании этой команды для подключения к внутреннему имени хоста, хотя это не соответствовало сертификату SSL.

Ответ 6

Использование:

  • System.setProperty( "jsse.enableSNIExtension", "false" );
  • Перезапустите Tomcat (важно)

Ответ 7

Это должно быть полезно. Чтобы повторить ошибку SNI в Apache HttpClient 4.4 - самый простой способ, с которым мы столкнулись (см. 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);

Ответ 8

К сожалению, вы не можете предоставить системные свойства инструменту jarsigner.exe.

Я отправил дефект 7177232, ссылаясь на дефект @eckes 7127374 и объяснил, почему он был закрыт по ошибке.

Мой дефект в особенности касается воздействия на инструмент jarsigner, но, возможно, это приведет к повторному открытию другого дефекта и правильному решению проблемы.

ОБНОВЛЕНИЕ: На самом деле, оказывается, что вы можете предоставлять системные свойства инструменту Jarsigner, это просто не в справочном сообщении. Используйте jarsigner -J-Djsse.enableSNIExtension=false

Ответ 9

Включите эту проблему с помощью spring загрузки и jvm 1.7 и 1.8. В AWS у нас не было возможности изменить имя ServerName и ServerAlias ​​для соответствия (они разные), поэтому мы сделали следующее:

В build.gradle мы добавили следующее:

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

Это позволило нам обойти проблему с помощью "Нераспознанного имени".

Ответ 10

Я столкнулся с той же проблемой, и оказалось, что обратный dns не был настроен правильно, он указал на неправильное имя хоста для IP. После того, как я исправлю обратные dns и перезапустите httpd, предупреждение исчезнет. (если я не исправляю обратные dns, добавление ServerName тоже помогло мне)

Ответ 11

Я также столкнулся с этой проблемой при обновлении с версии от Java 1.6_29 до 1.7.

Тревожно, мой клиент обнаружил настройку на панели управления Java, которая разрешает это.

На вкладке "Дополнительно" вы можете проверить "Использовать совместимый с SSL 2.0 формат ClientHello".

Это, похоже, разрешает проблему.

Мы используем Java-апплеты в браузере Internet Explorer.

Надеюсь, что это поможет.

Ответ 12

Мой VirtualHost ServerName был отмечен по умолчанию. Он работал после расторжения.

Ответ 13

У меня была та же проблема с сервером Ubuntu Linux, выполняющим подрывную деятельность при доступе через Eclipse.

Он показал, что проблема связана с предупреждением при запуске Apache (re):

[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, естественно)

Ответ 14

Просто добавьте решение здесь. Это может помочь пользователям 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>

Ответ 15

Существует более простой способ, где вы можете просто использовать свой собственный HostnameVerifier для неявного доверия определенным соединениям. Проблема связана с Java 1.7, где добавлены расширения SNI, а ваша ошибка связана с неправильной конфигурацией сервера.

Вы можете использовать "-Djsse.enableSNIExtension = false", чтобы отключить SNI для всей JVM или прочитать мой блог, где я объясню, как реализовать пользовательский верификатор поверх URL-соединения.