[java] 치명적 경보 수신 : SSLHandshakeException을 통한 handshake_failure

인증 된 SSL 연결에 문제가 있습니다. 클라이언트 인증 SSL 인증서로 외부 서버에 연결하는 Struts Action을 작성했습니다. 내 작업에서 서버로 인해 다음과 같은 오류가 발생하여 일부 데이터를 은행 서버로 보내려고하지만 운이 없습니다.

error: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

서버로 데이터를 보내는 내 Action 클래스의 내 메소드

//Getting external IP from host
    URL whatismyip = new URL("http://automation.whatismyip.com/n09230945.asp");
    BufferedReader inIP = new BufferedReader(new InputStreamReader(whatismyip.openStream()));

    String IPStr = inIP.readLine(); //IP as a String

    Merchant merchant;

    System.out.println("amount: " + amount + ", currency: " + currency + ", clientIp: " + IPStr + ", description: " + description);

    try {

        merchant = new Merchant(context.getRealPath("/") + "merchant.properties");

    } catch (ConfigurationException e) {

        Logger.getLogger(HomeAction.class.getName()).log(Level.INFO, "message", e);
        System.err.println("error: " + e.getMessage());
        return ERROR;
    }

    String result = merchant.sendTransData(amount, currency, IPStr, description);

    System.out.println("result: " + result);

    return SUCCESS;

내 merchant.properties 파일 :

bank.server.url=https://-servernameandport-/
https.cipher=-cipher-

keystore.file=-key-.jks
keystore.type=JKS
keystore.password=-password-
ecomm.server.version=2.0

encoding.source=UTF-8
encoding.native=UTF-8

처음으로 이것이 인증서 문제라고 생각하여 .pfx에서 .jks로 변환했지만 변경하지 않고 동일한 오류가 발생했습니다.



답변

여러 가지 이유로 핸드 셰이크 실패가 발생했을 수 있습니다.

  • 클라이언트와 서버에서 호환되지 않는 암호 스위트를 사용 중입니다. 이를 위해서는 클라이언트가 서버에서 지원하는 암호 제품군을 사용 (또는 활성화)해야합니다.
  • 호환되지 않는 SSL 버전이 사용 중입니다 (서버는 TLS v1 만 허용하고 클라이언트는 SSL v3 만 사용할 수 있음). 클라이언트는 SSL / TLS 프로토콜의 호환 가능한 버전을 사용해야합니다.
  • 서버 인증서의 불완전한 신뢰 경로. 클라이언트가 서버의 인증서를 신뢰하지 않을 수 있습니다. 일반적으로 더 자세한 오류가 발생하지만 가능합니다. 일반적으로 수정은 서버의 CA 인증서를 클라이언트의 신뢰 저장소로 가져 오는 것입니다.
  • 인증서는 다른 도메인에 대해 발급됩니다. 다시 말하지만, 더 자세한 메시지가 나왔을 것입니다. 그러나 이것이 원인 인 경우 여기에 수정 사항을 설명하겠습니다. 이 경우 해결 방법은 서버가 올바른 인증서를 사용하도록 서버를 얻는 것입니다.

기본 장애를 정확히 파악할 수 없으므로 -Djavax.net.debug=allSSL 연결을 디버깅 할 수 있도록 플래그를 설정 하는 것이 좋습니다 . 디버그가 켜져 있으면 핸드 셰이크의 어떤 활동이 실패했는지를 정확히 알 수 있습니다.

최신 정보

현재 사용 가능한 세부 사항을 기반으로, 문제점은 서버에 발행 된 인증서와 루트 CA 사이의 불완전한 인증서 신뢰 경로로 인한 것으로 보입니다. 대부분의 경우 루트 CA의 인증서가 신뢰 저장소에 없기 때문에 인증서 신뢰 경로가 존재하지 않기 때문입니다. 인증서는 본질적으로 클라이언트에 의해 신뢰되지 않습니다. 브라우저는 경고를 표시하여 사용자가이를 무시할 수 있지만 SSL 클라이언트 ( HttpsURLConnection 클래스 또는 Apache HttpComponents Client 와 같은 HTTP 클라이언트 라이브러리와 같은)의 경우도 마찬가지 입니다.

이러한 클라이언트 클래스 / 라이브러리 대부분은 인증서 유효성 검증을 위해 JVM이 사용하는 신뢰 저장소에 의존합니다. 대부분의 경우 이것은 cacertsJRE_HOME / lib / security 디렉토리 의 파일입니다. JVM 시스템 특성을 사용하여 신뢰 저장소의 위치를 ​​지정한 경우 javax.net.ssl.trustStore해당 경로의 저장소는 일반적으로 클라이언트 라이브러리가 사용하는 위치입니다. 확실치 않은 경우 Merchant클래스를 살펴보고 연결에 사용중인 클래스 / 라이브러리를 찾으십시오 .

CA를 발급하는 서버의 인증서를이 신뢰 저장소에 추가하면 문제를 해결해야합니다. 이 목적으로 도구얻는 것에 대한 관련 질문에 대한대답을 참조 할 수 있지만 Java keytool 유틸리티 는이 목적으로 충분합니다.

경고 : 신뢰 저장소는 기본적으로 신뢰하는 모든 CA의 목록입니다. 신뢰할 수없는 CA에 속하지 않는 인증서를 넣은 경우 개인 키를 사용할 수 있으면 해당 엔터티에서 발급 한 인증서가있는 사이트에 대한 SSL / TLS 연결을 해독 할 수 있습니다.

업데이트 # 2 : JSSE 추적의 출력 이해

JVM이 사용하는 키 저장소 및 신뢰 저장소는 일반적으로 다음과 같이 다소 처음에 나열됩니다.

keyStore is :
keyStore type is : jks
keyStore provider is :
init keystore
init keymanager of type SunX509
trustStore is: C:\Java\jdk1.6.0_21\jre\lib\security\cacerts
trustStore type is : jks
trustStore provider is : 

잘못된 신뢰 저장소를 사용하는 경우 서버 인증서를 올바른 인증서로 다시 가져 오거나 나열된 인증서를 사용하도록 서버를 다시 구성해야합니다 (여러 JVM이있는 경우 권장되지 않음). 필요).

신뢰 인증서 목록에 필요한 인증서가 포함되어 있는지 확인하려면 다음과 같이 시작하는 섹션이 있습니다.

adding as trusted cert:
  Subject: CN=blah, O=blah, C=blah
  Issuer:  CN=biggerblah, O=biggerblah, C=biggerblah
  Algorithm: RSA; Serial number: yadda
  Valid from SomeDate until SomeDate

서버의 CA가 주체인지 확인해야합니다.

핸드 셰이크 프로세스에는 몇 가지 중요한 항목이 있습니다 (세부 사항을 이해하려면 SSL을 알아야합니다). 그러나 현재 문제점을 디버깅하기 위해 handshake_failure가 일반적으로 ServerHello에보고되어 있으면 충분합니다.

1. ClientHello

연결이 초기화 될 때 일련의 항목이보고됩니다. SSL / TLS 연결 설정에서 클라이언트가 보낸 첫 번째 메시지는 일반적으로 로그에 다음과 같이보고되는 ClientHello 메시지입니다.

*** ClientHello, TLSv1
RandomCookie:  GMT: 1291302508 bytes = { some byte array }
Session ID:  {}
Cipher Suites: [SSL_RSA_WITH_RC4_128_MD5, SSL_RSA_WITH_RC4_128_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_DES_CBC_SHA, SSL_DHE_RSA_WITH_DES_CBC_SHA, SSL_DHE_DSS_WITH_DES_CBC_SHA, SSL_RSA_EXPORT_WITH_RC4_40_MD5, SSL_RSA_EXPORT_WITH_DES40_CBC_SHA, SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA]
Compression Methods:  { 0 }
***

사용 된 암호 제품군에 유의하십시오. 이 동의해야 할 수 있습니다 동일한 규칙이 은행의 라이브러리에 의해 사용될 수를 들어, merchant.properties 파일의 항목으로. 사용 된 규칙이 다르면 암호 스위트가 호환되지 않는 경우 ServerHello가 상태를 표시하므로 걱정할 필요가 없습니다.

2. ServerHello

서버는 ServerHello로 응답하여 연결 설정을 진행할 수 있는지 여부를 나타냅니다. 로그의 항목은 일반적으로 다음 유형입니다.

*** ServerHello, TLSv1
RandomCookie:  GMT: 1291302499 bytes = { some byte array}
Cipher Suite: SSL_RSA_WITH_RC4_128_SHA
Compression Method: 0
***

선택한 암호 스위트에 유의하십시오. 이것은 서버와 클라이언트 모두에서 사용할 수있는 최상의 제품군입니다. 오류가있는 경우 일반적으로 암호 스위트가 지정되지 않습니다. 서버의 인증서 (및 선택적으로 전체 체인)는 서버에 의해 전송되며 다음 항목에서 찾을 수 있습니다.

*** Certificate chain
chain [0] = [
[
  Version: V3
  Subject: CN=server, O=server's org, L=server's location, ST =Server's state, C=Server's country
  Signature Algorithm: SHA1withRSA, OID = some identifer

.... the rest of the certificate
***

인증서 확인에 성공하면 다음과 유사한 항목이 표시됩니다.

Found trusted certificate:
[
[
  Version: V1
  Subject: OU=Server's CA, O="Server's CA's company name", C=CA's country
  Signature Algorithm: SHA1withRSA, OID = some identifier

핸드 셰이크는 일반적으로이 단계에서 완료되기 때문에 위 단계 중 하나가 실패하여 handshake_failure가 발생합니다 (실제로는 아니지만 핸드 셰이크의 후속 단계는 일반적으로 핸드 셰이크 실패를 유발하지 않음). 어떤 단계가 실패했는지 파악하고 해당 메시지를 질문에 대한 업데이트로 게시해야합니다 (이미 메시지를 이해하고 해결하기 위해 수행 할 작업을 알고 있지 않은 경우).


답변

자바 암호화 확장 기능 (JCE) 무제한 강도를 설치 ( JDK7에 대한 | JDK8에 대한 )는이 버그를 수정 할 수 있습니다. 파일의 압축을 풀고 추가 정보에 따라 설치하십시오.


답변

핸드 셰이크 실패는 버그가있는 TLSv1 프로토콜 구현 일 수 있습니다.

우리의 경우 이것은 Java 7에 도움이되었습니다.

java -Dhttps.protocols=TLSv1.2,TLSv1.1,TLSv1 

jvm은이 순서대로 협상합니다. 최신 업데이트가있는 서버는 1.2를 수행하고 버그가 많은 서버는 v1로 이동하며 Java 7의 유사한 v1에서 작동합니다.


답변

이는 클라이언트가 인증서를 제시해야 할 때도 발생할 수 있습니다. 서버가 인증서 체인을 나열하면 다음이 발생할 수 있습니다.

3. 인증 요청
서버는 클라이언트로부터 인증 요청을 발행합니다. 요청에는 서버가 수락하는 모든 인증서가 나열됩니다.

*** CertificateRequest
Cert Types: RSA
Cert Authorities:
<CN=blah, OU=blah, O=blah, L=blah, ST=blah, C=blah>
<CN=yadda, DC=yadda, DC=yadda>
<CN=moreblah, OU=moreblah, O=moreblah, C=moreblah>
<CN=moreyada, OU=moreyada, O=moreyada, C=moreyada>
... the rest of the request
*** ServerHelloDone

4. 클라이언트 인증서 체인
클라이언트가 서버로 보내는 인증서입니다.

*** Certificate chain
chain [0] = [
[
  Version: V3
  Subject: EMAILADDRESS=client's email, CN=client, OU=client's ou, O=client's Org, L=client's location, ST=client's state, C=client's Country
  Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5
  ... the rest of the certificate
*** ClientKeyExchange, RSA PreMasterSecret, TLSv1
... key exchange info 

체인에 인증서가없고 서버에 인증서가 필요한 경우 여기에 핸드 셰이크 오류가 발생합니다. 인증서의 경로를 찾을 수 없기 때문일 수 있습니다.

5. 인증서 확인
클라이언트는 서버에 인증서 확인을 요청합니다.

*** CertificateVerify
... payload of verify check

이 단계는 인증서를 보내는 경우에만 발생합니다.

6. 완료
서버가 확인 응답으로 응답합니다.

*** Finished
verify_data:  { 345, ... }


답변

나는 이것이 첫 번째 질문자의 문제를 해결한다고 생각하지 않지만 Google 직원이 답을 얻기 위해 여기에 온다고 생각합니다.


릴리스 노트 페이지에서 볼 수 있듯이 업데이트 51에서 java 1.8은 기본적으로 RC4 암호를 금지했습니다.

버그 수정 : RC4 암호화 제품군 금지

RC4는 이제 손상된 암호로 간주됩니다.

RC4 암호화 제품군은 Oracle JSSE 구현의 클라이언트 및 서버 기본 가능 암호화 제품군 목록에서 제거되었습니다. 이러한 암호화 방식은 여전히 활성화 할 수 있습니다 SSLEngine.setEnabledCipherSuites()SSLSocket.setEnabledCipherSuites()방법. JDK-8077109 (공개 아님)를 참조하십시오.

서버가이 암호를 선호하거나이 암호 만 사용하면 handshake_failureon Java를 트리거 할 수 있습니다 .

RC4 암호를 사용하는 서버에 대한 연결을 테스트 할 수 있습니다 (먼저 enabled인수 없이 시도 하여 트리거가 있는지 확인한 handshake_failure후 설정) enabled.

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.*;

import java.util.Arrays;

/** Establish a SSL connection to a host and port, writes a byte and
 * prints the response. See
 * http://confluence.atlassian.com/display/JIRA/Connecting+to+SSL+services
 */
public class SSLRC4Poke {
    public static void main(String[] args) {
        String[] cyphers;
        if (args.length < 2) {
            System.out.println("Usage: "+SSLRC4Poke.class.getName()+" <host> <port> enable");
            System.exit(1);
        }
        try {
            SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
            SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket(args[0], Integer.parseInt(args[1]));

            cyphers = sslsocketfactory.getSupportedCipherSuites();
            if (args.length ==3){
                sslsocket.setEnabledCipherSuites(new String[]{
                    "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5",
                    "SSL_DH_anon_WITH_RC4_128_MD5",
                    "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
                    "SSL_RSA_WITH_RC4_128_MD5",
                    "SSL_RSA_WITH_RC4_128_SHA",
                    "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
                    "TLS_ECDHE_RSA_WITH_RC4_128_SHA",
                    "TLS_ECDH_ECDSA_WITH_RC4_128_SHA",
                    "TLS_ECDH_RSA_WITH_RC4_128_SHA",
                    "TLS_ECDH_anon_WITH_RC4_128_SHA",
                    "TLS_KRB5_EXPORT_WITH_RC4_40_MD5",
                    "TLS_KRB5_EXPORT_WITH_RC4_40_SHA",
                    "TLS_KRB5_WITH_RC4_128_MD5",
                    "TLS_KRB5_WITH_RC4_128_SHA"
                });
            }

            InputStream in = sslsocket.getInputStream();
            OutputStream out = sslsocket.getOutputStream();

            // Write a test byte to get a reaction :)
            out.write(1);

            while (in.available() > 0) {
                System.out.print(in.read());
            }
            System.out.println("Successfully connected");

        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }
}

1- https : //www.java.com/en/download/faq/release_changes.xml


답변

JDK 1.7을 사용하려고 시도하는 동안이 오류가 발생했습니다. JDK를 jdk1.8.0_66으로 업그레이드하면 모두 제대로 작동하기 시작했습니다.

따라서이 문제에 대한 가장 간단한 해결책은 JDK를 업그레이드 하면 잘 작동하기 시작할 수 있습니다.


답변

내 경우에는 인증서를 가져오고 오류가 남아 있으며 System.setProperty("https.protocols", "TLSv1.2,TLSv1.1,SSLv3");연결 전에 추가 하여이 문제를 해결했습니다.