SSL 핸드 셰이크 경고 : Java 1.7.0으로 업그레이드 한 후 unrecognized_name 오류
오늘 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 에 연결하려고했습니다 . 내 잘못은 어디에 있습니까?
감사.
Java 7에는 기본적으로 활성화 된 SNI 지원이 도입되었습니다. 잘못 구성된 일부 서버는 SSL 핸드 셰이크에서 "인식 할 수없는 이름"경고를 보내며 Java를 제외한 대부분의 클라이언트가 무시합니다. 으로 @Bob 컨스가 언급 한 오라클 엔지니어들은이 버그 / 기능 "수정"을 거부한다.
해결 방법으로 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 메시지에 호스트 이름을 추가하는 것입니다).
Webscarab SSL 프록시의 경우이 커밋 은 대체 설정을 구현합니다.
나는 똑같은 문제가 있다고 생각했다. 호스트의 ServerName 또는 ServerAlias를 포함하도록 Apache 구성을 조정해야한다는 것을 알았습니다.
이 코드는 실패했습니다 :
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 Hello 중에 경고 경고 (수준 : 경고, 설명 : 인식 할 수없는 이름), 서버 Hello가 서버에서 클라이언트로 전송되고 있음을 공개했습니다. 그러나 경고 일 뿐이지 만 Java 7.1은 "치명적, 설명 : 예상치 못한 메시지"로 즉시 응답했습니다. 이는 Java SSL 라이브러리가 인식 할 수없는 이름의 경고를보고 싶지 않다는 의미입니다.
TLS (Transport Layer Security) 위키에서 :
112 인식 할 수없는 이름 경고 TLS 만 해당; 클라이언트의 서버 이름 표시기가 서버에서 지원하지 않는 호스트 이름을 지정했습니다.
이로 인해 Apache 구성 파일을 보았고 클라이언트 / 자바 측에서 보낸 이름으로 ServerName 또는 ServerAlias를 추가하면 오류없이 올바르게 작동한다는 것을 알았습니다.
<VirtualHost mydomain.com:443>
ServerName mydomain.com
ServerAlias www.mydomain.com
시스템 특성 jsse.enableSNIExtension = false를 사용하여 SNI 레코드 전송을 비활성화 할 수 있습니다.
코드를 변경할 수 있으면 사용하는 데 도움이됩니다 SSLCocketFactory#createSocket()
(호스트 매개 변수가 없거나 연결된 소켓이 있음). 이 경우 server_name 표시를 보내지 않습니다.
또한 새로운 Apache 서버 빌드에서이 오류가 발생했습니다.
우리의 경우 수정은 Java가 연결하려고 시도한 호스트 이름에 해당 하는를 정의하는 것 ServerAlias
입니다 httpd.conf
. 우리는 ServerName
내부 호스트 이름으로 설정했다. SSL 인증서가 외부 호스트 이름을 사용하고 있었지만 경고를 피하기에는 충분하지 않았습니다.
디버그를 돕기 위해이 ssl 명령을 사용할 수 있습니다.
openssl s_client -servername <hostname> -connect <hostname>:443 -state
해당 호스트 이름에 문제가 있으면 출력 상단 근처에이 메시지가 인쇄됩니다.
SSL3 alert read: warning:unrecognized name
또한 SSL 인증서와 일치하지 않더라도 해당 명령을 사용하여 내부 호스트 이름에 연결할 때 해당 오류가 발생하지 않았습니다.
아파치에서 기본 가상 호스트 메커니즘에 의존하는 대신 임의의 ServerName과 와일드 카드 ServerAlias를 사용하는 마지막 catchall 가상 호스트를 정의 할 수 있습니다.
ServerName catchall.mydomain.com
ServerAlias *.mydomain.com
이런 식으로 SNI를 사용할 수 있으며 Apache는 SSL 경고를 다시 보내지 않습니다.
물론 이것은 와일드 카드 구문을 사용하여 모든 도메인을 쉽게 설명 할 수있는 경우에만 작동합니다.
유용해야합니다. Apache HttpClient 4.4에서 SNI 오류를 다시 시도하려면 가장 쉬운 방법입니다 ( 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);
사용하다:
- System.setProperty ( "jsse.enableSNIExtension", "false");
- Tomcat을 다시 시작하십시오 (중요)
불행히도 jarsigner.exe 도구에 시스템 특성을 제공 할 수 없습니다.
나는 결함 제출 한 7,177,232 , @eckes '결함 참조 7,127,374을 하고 그것이 오류가 폐쇄 된 이유를 설명합니다.
내 결함은 특히 jarsigner 도구에 미치는 영향에 관한 것이지만 다른 결함을 다시 열어 문제를 올바르게 해결할 수 있습니다.
업데이트 : 실제로 Jarsigner 도구에 시스템 속성을 제공 할 수 있다는 것이 밝혀졌습니다. 도움말 메시지에는 없습니다. 사용하다jarsigner -J-Djsse.enableSNIExtension=false
스프링 부트 와 jvm 1.7 및 1.8 에서이 문제가 발생했습니다 . AWS에서는 ServerName과 ServerAlias를 일치하도록 변경할 수있는 옵션이 없었으므로 다음과 같이했습니다.
에서 build.gradle 우리는 다음을 추가 :
System.setProperty("jsse.enableSNIExtension", "false")
bootRun.systemProperties = System.properties
이를 통해 "인식 할 수없는 이름"문제를 우회 할 수있었습니다.
나는 같은 문제에 부딪 쳤고 역방향 DNS가 올바르게 설정되지 않았으며 IP의 잘못된 호스트 이름을 지적했습니다. 역방향 DNS를 수정하고 httpd를 다시 시작하면 경고가 사라집니다. (역 DNS를 수정하지 않으면 ServerName을 추가해도 나에게 속임수를 썼습니다)
나의 VirtualHost
의는 ServerName
기본적으로 주석했다. 주석 해제 후 작동했습니다.
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;
}
Java 1.6_29에서 1.7로 업그레이드하는 동안이 문제가 발생했습니다.
놀랍게도 내 고객이 Java 제어판에서이를 해결하는 설정을 발견했습니다.
고급 탭에서 'SSL 2.0 호환 ClientHello 형식 사용'을 확인할 수 있습니다.
이것은 문제를 해결하는 것으로 보입니다.
Internet Explorer 브라우저에서 Java 애플릿을 사용하고 있습니다.
도움이 되었기를 바랍니다.
Eclipse를 통해 액세스 할 때 Subversion을 실행하는 Ubuntu Linux 서버와 동일한 문제가있었습니다.
아파치 (재)가 시작될 때 문제가 경고와 관련이있는 것으로 나타났습니다.
[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
문제가 사라졌습니다 (아파치를 다시 시작한 후 자연스럽게).
여기에 솔루션을 추가하십시오. 이것은 램프 사용자에게 도움이 될 수 있습니다
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>
고유 한 HostnameVerifier를 사용하여 특정 연결을 암시 적으로 신뢰할 수 있는 더 쉬운 방법 이 있습니다. 이 문제는 SNI 확장이 추가 된 Java 1.7에서 발생하며 오류는 서버 구성 오류로 인한 것입니다.
"-Djsse.enableSNIExtension = false"를 사용하여 전체 JVM에서 SNI를 비활성화하거나 URL 연결 위에 사용자 지정 검증기를 구현하는 방법을 설명하는 내 블로그를 읽을 수 있습니다.
'Programing' 카테고리의 다른 글
데몬 스레드 설명 (0) | 2020.04.21 |
---|---|
Rails에서 상대 시간은 어떻게합니까? (0) | 2020.04.21 |
트위터 부트 스트랩 2에 버튼을 제출하기 위해 아이콘 추가 (0) | 2020.04.21 |
사용자가 로그인했는지 확인하는 방법 (user.is_authenticated를 올바르게 사용하는 방법)? (0) | 2020.04.21 |
N 분마다 오프셋을 더한 Cron 작업 실행 (0) | 2020.04.21 |