Предупреждение о рукопожатии SSL: ошибка unrecognized_name с момента обновления до 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.
в чем моя ошибка?
Спасибо.
Переведено автоматически
Ответ 1
В 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 и по-прежнему поддерживать неправильно настроенные серверы.:
- Создайте
SSLSocket
с именем хоста, к которому вы хотите подключиться. Давайте назовем этоsslsock
. - Попробуйте запустить
sslsock.startHandshake()
. Это будет заблокировано до тех пор, пока это не будет сделано, или вызовет исключение при ошибке. Всякий раз, когда вstartHandshake()
возникает ошибка, получайте сообщение об исключении. Если оно равноhandshake alert: unrecognized_name
, значит, вы обнаружили неправильно настроенный сервер. - Когда вы получите
unrecognized_name
предупреждение (фатальное в Java), повторите попытку открытияSSLSocket
, но на этот раз без имени хоста. Это эффективно отключает SNI (в конце концов, расширение SNI предназначено для добавления имени хоста в сообщение ClientHello).
Для SSL-прокси Webscarab, этот коммит реализует резервную настройку.
Ответ 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 предупреждение
Оповещение (Уровень: Предупреждение, Описание: Нераспознанное имя), приветствие сервера
Отправлялось с сервера клиенту.
Однако это было всего лишь предупреждение, однако Java 7.1 немедленно ответила "Фатальным описанием: неожиданное сообщение", которое, как я полагаю, означает, что библиотекам Java SSL не нравится видеть предупреждение о нераспознанном имени.
Из Wiki о безопасности транспортного уровня (TLS):
112 Предупреждение о нераспознанном имени только по протоколу TLS; индикатор имени сервера клиента указывает имя хоста, не поддерживаемое сервером
Это заставило меня просмотреть мои файлы конфигурации Apache, и я обнаружил, что если я добавил ServerName или ServerAlias для имени, отправленного со стороны клиента / java, оно работало корректно, без каких-либо ошибок.
<VirtualHost mydomain.com:443>
ServerName mydomain.com
ServerAlias www.mydomain.com
Ответ 3
Вы можете отключить отправку записей SNI с помощью системного свойства jsse.enableSNIExtension=false.
Если вы можете изменить код, который он помогает использовать SSLCocketFactory#createSocket()
(без параметра host или с подключенным сокетом). В этом случае он не отправит указание server_name.
Ответ 4
Мы также столкнулись с этой ошибкой в новой сборке сервера Apache.
Исправление в нашем случае заключалось в определении ServerAlias
в httpd.conf
, которое соответствовало имени хоста, к которому Java пыталась подключиться. Наше ServerName
было установлено на внутреннее имя хоста. Наш SSL-сертификат использовал имя внешнего хоста, но этого было недостаточно, чтобы избежать предупреждения.
Для облегчения отладки вы можете использовать эту команду ssl:
openssl s_client -servername <hostname> -connect <hostname>:443 -state
Если есть проблема с этим именем хоста, то это сообщение будет напечатано в верхней части выходных данных:
SSL3 alert read: warning:unrecognized name
Я должен также отметить, что мы не получали эту ошибку при использовании этой команды для подключения к внутреннему имени хоста, даже если оно не соответствовало сертификату SSL.