Попередження про рукостискання з 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 . де я винен?

Дякую.

Відповіді:


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).

Для проксі WebScarab SSL, це зобов'язання реалізує установку відкату.


5
це працює, дякую! Підключення клієнта Subliver IntelliJ IDEA мала таку ж помилку під час підключення через HTTPS. Просто потрібно оновити файл idea.exe.vmoptions рядком: -Djsse.enableSNIExtension = false
Діма

2
Для веб-запуску java використовуйте це: javaws -J-Djsse.enableSNIExtension = false yourClass
Torsten

У мене була та сама проблема з клієнтом Джерсі з сервером відпочинку 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
Це виглядає як помилка у впровадженні Java TLS.
Paŭlo Ebermann

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 точно говорить про це на цю тему: " НЕ РЕКОМЕНДОВАНО надсилати попередження про невизнане ім'я (112) на рівні попередження, оскільки поведінка клієнта у відповідь на попередження рівня попередження непередбачувана. " Єдина помилка, яку зробив цей працівник - припустити, що це було смертельним попередженням. Це стільки ж помилок JRE, скільки Apache.
Бруно

36

Ви можете відключити надсилання записів SNI за допомогою властивості системи jsse.enableSNIExtension = false.

Якщо ви можете змінити код, який він допомагає використовувати SSLCocketFactory#createSocket()(без параметра хосту або з підключеним сокетом). У цьому випадку він не надсилатиме вказівку імені_сервера.


11
Що робиться так:System.setProperty ("jsse.enableSNIExtension", "false");
jn1kk

Я виявив, що це не завжди працює. Я не знаю, чому це працює в деяких випадках, а не в інших.
Йосиф Шрайбман

2
Для мене працює -Djsse.enableSNIExtension=false. Але що ще робить? Чи шкідливо це для решти безпеки Javas?
1313

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. Це дуже корисно.
sidehowbarker

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, ви можете визначити один останній віртуальний хост, який використовує довільне ім'я ServerName та підстановку ServerAlias, наприклад

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

Таким чином, ви можете використовувати SNI, і apache не поверне попередження SSL.

Звичайно, це працює лише в тому випадку, якщо ви можете легко описати всі свої домени за допомогою синтаксису підстановки.


Наскільки я розумію, 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);

Якщо ви зробите це так. Як ви маєте справу з verifyHostname(sslsock, target)в SSLConnectionSocketFactory.createLayeredSocket? Оскільки targetзмінено на порожній рядок, verifyHostnameце не вдасться. Я пропускаю тут щось очевидне?
Хаожун

2
Це було саме те, що я шукав, дуже дякую! Я не знайшов іншого способу підтримати SNI та сервери, які реагують одночасно з цим попереднім невідомим іменем.
korpe

Це рішення працює, якщо ви не використовуєте проксі-сервер.
мрог

Щойно зробивши швидку налагодження через клієнтський код Apache, починаючи з verifyHostname (), я не бачу, як це може працювати за допомогою DefaultHostnameVerifier. Я підозрюю, що це рішення вимагає відключення перевірки імені хоста (наприклад, використання NoopHostnameVerifier), що НЕ є гарною ідеєю, оскільки дозволяє атаки людини в середині.
FlyingSheep


6

Потрапив до цього випуску із весняним завантаженням та jvm 1.7 та 1.8. У AWS у нас не було можливості змінювати ServerName та ServerAlias ​​на відповідність (вони різні), тому ми зробили наступне:

У build.gradle ми додали наступне:

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

Це дозволило нам обійти проблему із "Нерозпізнаним іменем".


5

На жаль, ви не можете надати властивості системи інструменту jarsigner.exe, на жаль.

Я подав дефект 7177232 , посилаючись на дефект @eckes 7127374 та пояснюючи, чому його помилково закрили.

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

ОНОВЛЕННЯ: Власне, виявляється, що ви МОЖЕТЕ надавати властивості системи інструменту Jarsigner, це просто не в довідковому повідомленні. Використовуйтеjarsigner -J-Djsse.enableSNIExtension=false


1
Дякую Боб за подальші спостереження. На жаль, Oracle все ще не розуміє всієї справи. Попередження про СНР не є смертельним, воно може бути фатальним. Але більшість типових клієнтів SSL (тобто браузери) вирішили проігнорувати його, оскільки це не є справжньою проблемою безпеки (тому що вам все-таки потрібно перевірити сертифікат сервера і виявити неправильні кінцеві точки). Звичайно, сумно, що КА не може встановити створити правильно налаштований сервер https.
eckes

2
Власне, виявляється, що ви МОЖЕТЕ надати системні властивості інструменту Jarsigner, це просто не в довідковому повідомленні. Це висвітлено в документації. Дурний мені за те, що повірив онлайн-тексту. Звичайно, вони використали це як привід для того, щоб не виправити це.
Боб Кернс

BTW: Я зараз обговорюю це питання на безпеці-dev @ openjdk, і на даний момент я оцінював це, схоже, Symantec зафіксував сервер часових позначок GEOTrust, тепер він правильно приймає URL-адресу без попередження. Але я все ж думаю, що це слід виправити. Якщо ви хочете подивитися, тестовий клієнтський проект та дискусія :
eckes

3

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



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;
}

1

Я також натрапив на цю проблему під час оновлення з Java 1.6_29 до 1.7.

Тривожно, мій клієнт виявив налаштування на панелі управління Java, яке вирішує цю проблему.

На вкладці "Додаткові" ви можете встановити прапорець "Використовувати формат ClientHello, сумісний із SSL 2.0".

Це, здається, вирішило проблему.

Ми використовуємо аплети Java в браузері Internet Explorer.

Сподіваюсь, це допомагає.


0

У мене була така ж проблема з сервером Ubuntu Linux, який працює на підриві, коли отримував доступ через 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)


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. У мене були проблеми з cert, який піддав підстановку *.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-з'єднання.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.