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

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

Спасибо.

Ответы [ 16 ]

296 голосов
/ 15 февраля 2013

Java 7 представила поддержку SNI, которая включена по умолчанию.Я обнаружил, что некоторые неправильно настроенные серверы посылают предупреждение «Нераспознанное имя» в рукопожатии SSL, которое игнорируется большинством клиентов ... за исключением Java.Как упоминалось @ Боб Кернс , инженеры 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).

Для прокси-сервера Webscarab SSL этот коммит реализуетобратно настройки.

86 голосов
/ 09 ноября 2011

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

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

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

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

<VirtualHost mydomain.com:443>
  ServerName mydomain.com
  ServerAlias www.mydomain.com
36 голосов
/ 07 января 2012

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

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

17 голосов
/ 29 июня 2015

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

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

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

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

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

SSL3 alert read: warning:unrecognized name

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

15 голосов
/ 17 декабря 2012

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

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

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

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

12 голосов
/ 18 февраля 2015

Это должно быть полезно. Повторить попытку ошибки 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);
11 голосов
/ 07 сентября 2015

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

  • System.setProperty ("jsse.enableSNIExtension", "false");
  • Перезагрузите Tomcat (важно)
5 голосов
/ 21 августа 2015

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

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

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

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

5 голосов
/ 15 июня 2012

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

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

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

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

3 голосов
/ 17 октября 2013

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...