[java] 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을 다시 시작하십시오 (중요)