[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를 사용하고 여전히 잘못 구성된 서버를 지원하려면 다음을 수행하십시오.

  1. SSLSocket연결하려는 호스트 이름으로를 작성하십시오 . 이 이름을 지정합시다 sslsock.
  2. 실행 해보십시오 sslsock.startHandshake(). 완료 될 때까지 차단되거나 오류시 예외가 발생합니다. 에서 오류가 발생할 때마다 startHandshake()예외 메시지가 표시됩니다. 이 (가)이면 handshake alert: unrecognized_name잘못 구성된 서버를 발견 한 것입니다.
  3. 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을 다시 시작하십시오 (중요)