it-roy-ru.com

Предупреждение о рукопожатии SSL: ошибка нераспознанного имени после обновления до Java 1.7.0

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

Благодарю.

214
pvomhoff

В 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 и по-прежнему поддерживать неправильно настроенные серверы:

  1. Создайте SSLSocket с именем хоста, к которому вы хотите подключиться. Давайте назовем это sslsock.
  2. Попробуйте запустить sslsock.startHandshake(). Это будет блокировать до тех пор, пока это не будет сделано, или выдать исключение при ошибке Всякий раз, когда в startHandshake() произошла ошибка, получите сообщение об исключении. Если он равен handshake alert: unrecognized_name, значит, вы обнаружили неправильно настроенный сервер.
  3. Когда вы получите предупреждение unrecognized_name (фатально для Java), попробуйте снова открыть SSLSocket, но на этот раз без имени хоста. Это фактически отключает SNI (в конце концов, расширение SNI - это добавление имени хоста в сообщение ClientHello).

Для SSL-прокси Webscarab, this commit реализует запасную настройку.

292
Lekensteyn

У меня было то, что я считаю, что та же проблема . Я обнаружил, что мне нужно настроить конфигурацию 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 предупреждение Alert (Уровень: Предупреждение, Описание: Нераспознанное имя), Server Hello Отправлялось с сервера на клиент . Это было только предупреждение, однако Java 7.1 немедленно ответил «Неустранимое описание: неожиданное сообщение», что, как я предполагаю, означает, что библиотеки Java SSL не любят видеть предупреждение о нераспознанном имени.

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

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

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

<VirtualHost mydomain.com:443>
  ServerName mydomain.com
  ServerAlias www.mydomain.com
85
David McLaughlin

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

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

35
eckes

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

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

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

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

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

SSL3 alert read: warning:unrecognized name

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

16
Scott McIntyre

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

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

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

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

15
Erik

Это должно быть полезно. Повторить попытку ошибки 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);
12
Shcheklein

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

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

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

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

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

UPDATE: На самом деле, оказывается, что вы МОЖЕТЕ предоставить системные свойства инструменту Jarsigner, но его нет в справочном сообщении. Используйте jarsigner -J-Djsse.enableSNIExtension=false

5
Bob Kerns

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

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

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

Это позволило нам обойти проблему с «Нераспознанным именем».

5
Ray Hunter

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

3
user2888387

Моя VirtualHost's ServerName была закомментирована по умолчанию. Сработало после раскомментирования.

2
Aram Paronikyan

Если вы создаете клиент с помощью 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;
}
2
Alfredo

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

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

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

Кажется, это решает проблему.

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

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

1
Allan D

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

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

Он показал, что проблема была связана с предупреждением, когда 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, где наряду с директивой sites-enabled/000-default была введена другая директива NameVirtualHost.

После удаления директивы в ports.conf проблема исчезла (естественно, после перезапуска Apache)

0
Gerhard