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

225

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

Спасибо.

pvomhoff
источник

Ответы:

304

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

Lekensteyn
источник
5
это работает, спасибо! Клиент Subversion IntelliJ IDEA имел ту же ошибку при подключении через HTTPS. Просто нужно обновить файл idea.exe.vmoptions строкой: -Djsse.enableSNIExtension = false
Дима
2
Для веб-запуска Java используйте это: javaws -J-Djsse.enableSNIExtension = false yourClass
Торстен
У меня была точно такая же проблема с моим клиентом на Джерси с сервером https. Оказывается, я использовал твой трюк, и это сработало !! Спасибо!
shahshi15
4
Для тех, кто интересуется Java 8, Oracle все еще не изменил поведение и все еще требует, чтобы программист ловил серверы, которые (должным образом) не поддерживают SNI: docs.oracle.com/javase/8/docs/technotes/guides/security/jsse /…
Лекенштейн
2
@Blauhirn Обратитесь в службу поддержки веб-хостинга, они должны исправить свою конфигурацию. Если они используют Apache, обратите внимание на некоторые изменения, которые необходимо внести.
Лекенштейн
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 предупреждение с предупреждением (уровень: предупреждение, описание: нераспознанное имя) было отправлено с сервера на клиент. Это было всего лишь предупреждение, однако, Java 7.1 затем немедленно ответила «Фатальным, Описание: Неожиданное сообщение», что, как я предполагаю, означает, что библиотеки Java SSL не любят видеть предупреждение о нераспознанном имени.

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

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

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

<VirtualHost mydomain.com:443>
  ServerName mydomain.com
  ServerAlias www.mydomain.com
Дэвид Маклафлин
источник
3
Это похоже на ошибку в реализации TLS Java.
Пауло Эберманн
1
Я фактически открываю ошибку для этого. Мне присвоили этот номер (пока не видно): bugs.sun.com/bugdatabase/view_bug.do?bug_id=7127374
eckes
1
Спасибо, добавив ServerAliasработали для меня. Я видел такое же предупреждение TLS в Wireshark.
Джаред Бек
2
Это сработало и для меня. (сработало бы лучше, если бы я поверил в это и не пошел бы охотиться по другому пути)
конечный пользователь
10
@JosephShraibman, говорить, что сотрудник Oracle - идиот, неуместно. Когда вы используете два ServerNames, Apache отвечает unrecognized_name предупреждением (которое также может быть фатальным ). RFC 6066 точно говорит об этом по этой теме: « НЕ РЕКОМЕНДУЕТСЯ отправлять предупреждение уровня unrecognized_name (112) уровня предупреждения, потому что поведение клиента в ответ на предупреждения уровня предупреждения непредсказуемо ». Единственная ошибка, допущенная этим сотрудником, заключается в предположении, что это было смертельное предупреждение. Это такая же ошибка в JRE, как и в Apache.
Бруно
36

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

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

оборота
источник
11
Что сделано так:System.setProperty ("jsse.enableSNIExtension", "false");
jn1kk
Я обнаружил, что это не всегда работает. Я не знаю, почему это работает в некоторых случаях, а не в других.
Джозеф Шрайбман
2
У меня работает с -Djsse.enableSNIExtension=false. Но что еще это делает? Это вредно для остальной безопасности Javas?
выступление
2
Нет, это просто означает, что некоторые сайты (особенно веб-серверы), которые используют несколько имен хостов за общим IP, не знают, какой сертификат отправлять. Но если ваше приложение Java не должно подключаться к миллионам веб-сайтов, вам не понадобится эта функция. Если вы столкнетесь с таким сервером, проверка сертификата может завершиться неудачно, и соединение прервется. Таким образом, никаких проблем с безопасностью не ожидается.
Eckes
17

Мы также столкнулись с этой ошибкой при новой сборке сервера 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
Спасибо за включение примера о том, как воспроизвести проблему с помощью openssl. Это очень полезно.
Sideshowbarker
2
Есть ли способ для клиента openssl увидеть разницу в именах, которая создает сообщение SSL3 alert read: warning:unrecognized name? Я вижу напечатанное предупреждение, но вывод не помогает мне увидеть эту разницу в именах
mox601
Это не работает для меня. Проверено с OpenSSL 1.0.1e-fips 11 Feb 2013, OpenSSL 1.0.2k-fips 26 Jan 2017и LibreSSL 2.6.5.
Грег Дубицки
15

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

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

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

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

Erik
источник
Насколько я понимаю, apache отправляет это предупреждение только в том случае, если клиент использует имя, не настроенное как ServerName или ServerAlias. Поэтому, если у вас есть *.mydomain.comподстановочный сертификат, либо укажите все используемые поддомены, либо используйте синтаксис для ServerAlias. Обратите внимание, что вы можете добавить несколько псевдонимов, используя ServerAlias, например ServerAlias *.mydomain.com mydomain.com *.myotherdomain.com.
Майкл Пезольд
13

Это должно быть полезно. Повторить попытку ошибки 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);
Shcheklein
источник
Если вы делаете это таким образом. Как вы имеете дело с verifyHostname(sslsock, target)в SSLConnectionSocketFactory.createLayeredSocket? Поскольку targetпеременная изменена на пустую строку, verifyHostnameона не будет успешной. Я что-то упускаю здесь очевидное?
Хаожун
2
Это было именно то, что я искал, большое спасибо! Я не нашел другого способа поддержки SNI и серверов, которые одновременно отвечают этим предупреждением «unrecognized_name».
корпе
Это решение работает, если вы не используете прокси-сервер.
Мрог
Просто сделав быструю отладку через клиентский код Apache, начиная с verifyHostname (), я не могу понять, как это может работать с помощью DefaultHostnameVerifier. Я подозреваю, что это решение требует отключения проверки имени хоста (например, с помощью NoopHostnameVerifier), что НЕ является хорошей идеей, так как допускает атаки по принципу «человек посередине».
FlyingSheep
11

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

  • System.setProperty ("jsse.enableSNIExtension", "false");
  • Перезагрузите ваш Tomcat (важно)
Томаш Янисевич
источник
6

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

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

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

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

Рэй Хантер
источник
5

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

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

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

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

Боб Кернс
источник
1
Спасибо Бобу за продолжение. К сожалению, Oracle все еще не понимает всего этого. Предупреждение SNI не смертельно, его можно лечить смертельно. Но большинство типичных клиентов SSL (то есть браузеров) решили игнорировать его, поскольку это не является реальной проблемой безопасности (потому что вам все равно нужно проверить сертификат сервера и обнаружить неправильные конечные точки). Конечно, печально, что ЦС не может установить правильно настроенный сервер https.
Eckes
2
На самом деле, оказывается, что вы МОЖЕТЕ предоставить системные свойства инструменту Jarsigner, но его нет в справочном сообщении. Это описано в документации. Глупый я за веру в онлайн текст. Конечно, они использовали это как оправдание для того, чтобы не исправить это.
Боб Кернс
Кстати, я сейчас обсуждаю эту проблему на security-dev @ openjdk, и в тот момент, когда я оценивал ее, похоже, что Symantec исправил сервер меток времени GEOTrust, теперь он правильно принимает URL-адрес без предупреждения. Но я все еще думаю, что это должно быть исправлено. Если вы хотите взглянуть, тестовый клиентский проект и обсуждение :
eckes
3

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

user2888387
источник
2

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

Арам Пароникян
источник
Тоже самое. Имя сервера отсутствует.
Dark Star1
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;
}
Alfredo
источник
1

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

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

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

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

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

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

Аллан Д
источник
0

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

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

Gerhard
источник
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», чтобы отключить SNI во всей JVM, или прочитать мой блог, где я объясняю, как реализовать пользовательский верификатор поверх URL-соединения.

MagicDude4Eva
источник