SSL 핸드셰이크 실패 - 잘못된 클라이언트 인증서

Apigee Edge 문서입니다.
Go to the Apigee X documentation.
info

증상

클라이언트 애플리케이션은 API 요청에 대한 응답으로 "Service Unavailable" 메시지와 함께 HTTP 상태 코드 503 을 수신합니다. UI trace에서 실패한 API 요청의 대상 요청 흐름에 error.cause Received fatal alert: bad_certificate임을 확인할 수 있습니다.

메시지 프로세서 로그에 액세스할 수 있는 경우 실패한 API 요청에 대한 오류 메시지가 Received fatal alert: bad_certificate 로 표시됩니다. 이 오류는 메시지 프로세서와 백엔드 서버 간의 SSL 핸드셰이크 프로세스 중에 양방향 TLS 설정에서 발생합니다.

오류 메시지

클라이언트 애플리케이션은 다음 응답 코드를 가져옵니다.

HTTP/1.1 503 Service Unavailable

또한 다음 오류 메시지가 표시될 수 있습니다.

{
 "fault": {
    "faultstring":"The Service is temporarily unavailable",
    "detail":{
        "errorcode":"messaging.adaptors.http.flow.ServiceUnavailable"
    }
 }
}

Private Cloud 사용자는 메시지 프로세서 로그 /opt/apigee/var/log/edge-message-processor/system.log에서 특정 API 요청에 대해 다음 오류를 확인합니다 .

2017-10-23 05:28:57,813 org:org-name env:env-name api:apiproxy-name rev:revision-number messageid:message_id NIOThread@0 ERROR HTTP.CLIENT - HTTPClient$Context.handshakeFailed() : SSLClientChannel[C:IP address:port # Remote host:IP address:port #]@65461 useCount=1 bytesRead=0 bytesWritten=0 age=529ms lastIO=529ms handshake failed, message: Received fatal alert: bad_certificate

가능한 원인

이 문제의 가능한 원인은 다음과 같습니다.

원인 설명 다음에 관한 문제 해결 안내
클라이언트 인증서 없음 대상 서버의 대상 엔드포인트에 사용된 키 저장소에 클라이언트 인증서가 없습니다. Edge Private 및 Public Cloud 사용자
인증 기관 불일치 메시지 프로세서의 키 저장소에 있는 리프 인증서 (인증서 체인의 첫 번째 인증서) 의 인증 기관이 백엔드 서버에서 허용하는 인증 기관과 일치하지 않습니다. Edge Private 및 Public Cloud 사용자

일반적인 진단 단계

  1. Edge UI에서 trace를 사용 설정하고 API를 호출하여 문제를 재현합니다.
  2. UI trace 결과에서 각 단계를 탐색하고 오류가 발생한 위치를 확인합니다. 오류는 대상 요청 흐름에서 발생했을 것입니다.
  3. 오류를 표시하는 흐름을 검사합니다. 아래 예시 trace와 같이 오류가 표시됩니다.

    alt_text

  4. 위 스크린샷에서 볼 수 있듯이 error.cause "Received fatal alert: bad_certificate"입니다.
  5. Private Cloud 사용자인 경우 아래 안내를 따르세요.
    1. trace에서 AX로 표시된 단계에서 오류 헤더 "X-Apigee.Message-ID"의 값을 확인하여 실패한 API 요청의 메시지 ID를 가져올 수 있습니다.
    2. 메시지 프로세서 로그 /opt/apigee/var/log/edge-message-processor/system.log에서 이 메시지 ID를 검색하고 오류에 관한 추가 정보를 찾을 수 있는지 확인합니다.
      2017-10-23 05:28:57,813 org:org-name env:env-name api:apiproxy-name
      rev:revision-number messageid:message_id NIOThread@0 ERROR HTTP.CLIENT - HTTPClient$Context.handshakeFailed() :
      SSLClientChannel[C:IP address:port # Remote host:IP address:port #]@65461 useCount=1
      bytesRead=0 bytesWritten=0 age=529ms lastIO=529ms handshake failed, message: Received fatal alert: bad_certificate
      2017-10-23 05:28:57,813 org:org-name env:env-name api:apiproxy-name
      rev:revision-number messageid:message_id NIOThread@0 ERROR HTTP.CLIENT - HTTPClient$Context.handshakeFailed() : SSLInfo:
      KeyStore:java.security.KeyStore@52de60d9 KeyAlias:KeyAlias TrustStore:java.security.KeyStore@6ec45759
      2017-10-23 05:28:57,814 org:org-name env:env-name api:apiproxy-name
      rev:revision-number messageid:message_id NIOThread@0 ERROR ADAPTORS.HTTP.FLOW - RequestWriteListener.onException() :
      RequestWriteListener.onException(HTTPRequest@6071a73d)
      javax.net.ssl.SSLException: Received fatal alert: bad_certificate
      at sun.security.ssl.Alerts.getSSLException(Alerts.java:208) ~[na:1.8.0_101]
      at sun.security.ssl.SSLEngineImpl.fatal(SSLEngineImpl.java:1666) ~[na:1.8.0_101]
      at sun.security.ssl.SSLEngineImpl.fatal(SSLEngineImpl.java:1634) ~[na:1.8.0_101]
      at sun.security.ssl.SSLEngineImpl.recvAlert(SSLEngineImpl.java:1800) ~[na:1.8.0_101]
      at com.apigee.nio.NIOSelector$SelectedIterator.findNext(NIOSelector.java:496) [nio-1.0.0.jar:na]
      at com.apigee.nio.util.NonNullIterator.computeNext(NonNullIterator.java:21) [nio-1.0.0.jar:na]
      at com.apigee.nio.util.AbstractIterator.hasNext(AbstractIterator.java:47) [nio-1.0.0.jar:na]
      at com.apigee.nio.NIOSelector$2.findNext(NIOSelector.java:312) [nio-1.0.0.jar:na]
      at com.apigee.nio.NIOSelector$2.findNext(NIOSelector.java:302) [nio-1.0.0.jar:na]
      at com.apigee.nio.util.NonNullIterator.computeNext(NonNullIterator.java:21) [nio-1.0.0.jar:na]
      at com.apigee.nio.util.AbstractIterator.hasNext(AbstractIterator.java:47) [nio-1.0.0.jar:na]
      at com.apigee.nio.handlers.NIOThread.run(NIOThread.java:59) [nio-1.0.0.jar:na]

      메시지 프로세서 로그에 오류 Received fatal alert: bad_certificate의 스택 트레이스가 있지만 이 문제의 원인을 나타내는 추가 정보는 없습니다.

  6. 이 문제를 추가로 조사하려면 tcpdump 도구를 사용하여 TCP/IP 패킷을 캡처해야 합니다.
    1. Private Cloud 사용자인 경우 백엔드 서버 또는 메시지 프로세서에서 TCP/IP 패킷을 캡처할 수 있습니다. 패킷은 백엔드 서버에서 복호화되므로 백엔드 서버에서 캡처하는 것이 좋습니다.
    2. Public Cloud 사용자인 경우 백엔드 서버에서 TCP/IP 패킷을 캡처합니다.
    3. TCP/IP 패킷을 캡처할 위치를 결정한 후 아래 tcpdump 명령어를 사용하여 TCP/IP 패킷을 캡처합니다.
    4. tcpdump -i any -s 0 host <IP address> -w <File name>

      메시지 프로세서에서 TCP/IP 패킷을 가져오는 경우 `tcpdump`tcpdump 명령어에서 백엔드 서버의 공개 IP 주소를 사용합니다.

      백엔드 서버/메시지 프로세서에 여러 IP 주소가 있는 경우 다른 tcpdump 명령어를 사용해야 합니다. 이 도구 및 이 명령어의 다른 변형에 관한 자세한 내용은 tcpdump 를 참조하세요.

  7. Wireshark 도구 또는 익숙한 유사한 도구를 사용하여 TCP/IP 패킷을 분석합니다.

다음은 Wireshark 도구를 사용하여 샘플 TCP/IP 패킷 데이터를 분석한 결과입니다.

alt_text

  1. 위의 tcpdump의 메시지 #4는 메시지 프로세서 (소스) 가 'Client Hello' 메시지를 백엔드 서버 (대상)에 보냈음을 보여줍니다.
  2. 메시지 #5는 백엔드 서버가 메시지 프로세서의 Client Hello 메시지 를 승인함을 보여줍니다.
  3. 백엔드 서버는 인증서와 함께 "Server Hello" 메시지를 보낸 후, 메시지 #7에서 클라이언트에게 인증서를 보내도록 요청합니다.
  4. 메시지 프로세서는 인증서 확인을 완료하고 메시지 #8에서 백엔드 서버의 ServerHello 메시지를 승인합니다.
  5. 메시지 프로세서는 메시지 #9에서 백엔드 서버로 인증서를 보냅니다.
  6. 백엔드 서버는 메시지 #11에서 메시지 프로세서의 인증서 수신을 승인합니다.
  7. 하지만 즉시 Fatal Alert: Bad Certificate를 메시지 프로세서 (메시지 #12)에 보냅니다. 이는 메시지 프로세서에서 보낸 인증서가 잘못되었으므로 백엔드 서버에서 인증서 확인이 실패했음을 나타냅니다. 따라서 SSL 핸드셰이크가 실패하고 연결 이 닫힙니다.


    alt_text

  8. 이제 메시지 #9를 살펴보고 메시지 프로세서에서 보낸 인증서의 콘텐츠를 확인해 보겠습니다.


    alt_text

  9. 백엔드 서버가 클라이언트로부터 인증서를 가져오지 않았음을 알 수 있습니다 (인증서 길이: 0). 따라서 백엔드 서버는 Fatal Alert: Bad Certificate를 보냅니다.
  10. 일반적으로 이는 클라이언트, 즉 메시지 프로세서 (Java 기반 프로세스)가 다음과 같은 경우에 발생합니다.
    1. 키 저장소에 클라이언트 인증서가 없거나
    2. 클라이언트 인증서를 보낼 수 없습니다. 이는 백엔드 서버의 허용 가능한 인증 기관 중 하나에서 발급한 인증서를 찾을 수 없는 경우에 발생할 수 있습니다. 즉, 클라이언트의 리프 인증서 (체인의 첫 번째 인증서)의 인증 기관이 백엔드 서버의 허용 가능한 인증 기관과 일치하지 않으면 메시지 프로세서가 인증서를 보내지 않습니다.

다음과 같이 이러한 각 원인을 개별적으로 살펴보겠습니다.

원인: 클라이언트 인증서 없음

진단

대상 엔드포인트의 SSL 정보 섹션 또는 대상 엔드포인트에 사용된 대상 서버 에 지정된 키 저장소에 인증서가 없으면 이 오류가 발생합니다.

아래 단계를 따라 이 문제가 원인인지 확인하세요.

  1. 아래 단계를 사용하여 특정 API 프록시의 대상 엔드포인트 또는 대상 서버 에서 사용 중인 키 저장소를 확인합니다.
    1. 대상 엔드포인트 또는 대상 서버의 SSLInfo 섹션에 있는 키 저장소 요소 에서 키 저장소 참조 이름을 가져옵니다.

      대상 엔드포인트 구성의 샘플 SSLInfo 섹션을 살펴보겠습니다.

      <SSLInfo>
        <Enabled>true</Enabled>
        <ClientAuthEnabled>true</ClientAuthEnabled>
        <KeyStore>ref://myKeystoreRef</KeyStore>
        <KeyAlias>myKey</KeyAlias>
        <TrustStore>ref://myTrustStoreRef</TrustStore>
      </SSLInfo>
    2. 위의 예시에서 키 저장소 참조 이름은 "myKeystoreRef"입니다.
    3. Edge UI로 이동하여 API 프록시 -> 환경 구성 을 선택합니다.

      참조 탭을 선택하고 키 저장소 참조 이름을 검색합니다. 특정 키 저장소 참조의 참조 열에 이름을 적어둡니다. 이 이름이 키 저장소 이름이 됩니다.


      alt_text

    4. 위의 예시에서 myKeystoreRef에 'myKeystore'에 대한 참조가 있음을 알 수 있습니다 . 따라서 키 저장소 이름은 myKeystore입니다.
  2. Edge UI 또는 키 저장소 API의 인증서 나열을 사용하여 이 키 저장소에 인증서가 포함되어 있는지 확인합니다.
  3. 키 저장소에 인증서가 포함되어 있으면 원인: 인증 기관 불일치로 이동합니다.
  4. 키 저장소에 인증서가 포함되어 있지 않으면 메시지 프로세서에서 클라이언트 인증서를 보내지 않는 이유입니다.

해결 방법

  1. 올바르고 완전한 클라이언트 인증서 체인이 메시지 프로세서의 특정 키 저장소에 업로드되었는지 확인합니다.

원인: 인증 기관 불일치

일반적으로 서버는 클라이언트에게 인증서를 보내도록 요청할 때 허용되는 발급기관 또는 인증 기관 집합을 나타냅니다. 메시지 프로세서의 키 저장소에 있는 리프 인증서 (인증서 체인의 첫 번째 인증서)의 발급기관/인증 기관이 백엔드 서버에서 허용하는 인증 기관과 일치하지 않으면 메시지 프로세서 (Java 기반 프로세스)가 백엔드 서버로 인증서를 보내지 않습니다.

아래 단계를 사용하여 이 문제가 원인인지 확인합니다.

  1. 키 저장소 API의 인증서 나열.
  2. 키 저장소 API의 인증서 가져오기를 사용하여 위의 1단계에서 가져온 각 인증서의 세부정보를 가져옵니다.
  3. 키 저장소에 저장된 리프 인증서 (인증서 체인의 첫 번째 인증서)의 발급기관을 적어둡니다.

    샘플 리프 인증서

    {
      "certInfo" : [ {
        "basicConstraints" : "CA:FALSE",
        "expiryDate" : 1578889324000,
        "isValid" : "Yes",
        "issuer" : "CN=MyCompany Test SHA2 CA G2, DC=testcore, DC=test, DC=dir, DC=mycompany, DC=com",
        "publicKey" : "RSA Public Key, 2048 bits",
        "serialNumber" : "65:00:00:00:d2:3e:12:d8:56:fa:e2:a9:69:00:06:00:00:00:d2",
        "sigAlgName" : "SHA256withRSA",
        "subject" : "CN=nonprod-api.mycompany.com, OU=ITS, O=MyCompany, L=MELBOURNE, ST=VIC, C=AU",
        "subjectAlternativeNames" : [ ],
        "validFrom" : 1484281324000,
        "version" : 3
      } ],
      "certName" : "nonprod-api.mycompany.com.key.pem-cert"
    }

    위의 예시에서 발급기관/인증 기관은 "CN=MyCompany Test SHA2 CA G2, DC=testcore, DC=test, DC=dir, DC=mycompany, DC=com" 입니다.

  4. 다음 기법 중 하나를 사용하여 백엔드 서버의 허용되는 발급기관 또는 인증 기관 목록을 확인합니다.

    기법 1: 아래 openssl 명령어 사용

    openssl s_client -host <backend server host name> -port <Backend port#> -cert <Client Certificate> -key <Client Private Key>
    

    아래와 같이 이 명령어의 출력에서 "허용 가능한 클라이언트 인증서 CA 이름" 이라는 섹션을 참조하세요.

    Acceptable client certificate CA names
    /C=AU/ST=VIC/L=MELBOURNE/O=MyCompany/OU=ITS/CN=nonprod-api.mycompany.com
    /C=AU/ST=VIC/L=MELBOURNE/O=MyCompany/OU=ITS/CN=nonprod-api.mycompany.com

    기법 2: 백엔드 서버가 클라이언트에게 인증서를 보내도록 요청하는 TCP/IP 패킷에서 Certificate Request 패킷 확인:

    위에 표시된 샘플 TCP/IP 패킷에서 Certificate Request 패킷은 메시지 #7입니다. 백엔드 서버의 허용 가능한 인증 기관이 포함된 "고유 이름" 섹션을 참조하세요.

    alt_text

  5. 3단계에서 가져온 인증 기관이 백엔드 서버의 허용되는 발급기관 또는 인증 기관 목록 과 4단계에서 가져온 백엔드 서버의 허용되는 발급기관 또는 인증 기관 목록과 일치하는지 확인합니다. 일치하지 않으면 메시지 프로세서가 백엔드 서버로 클라이언트 인증서를 보내지 않습니다.

    위의 예시에서 메시지 프로세서의 키 저장소에 있는 클라이언트의 리프 인증서의 발급기관이 백엔드 서버의 허용되는 인증 기관 과 일치하지 않음을 알 수 있습니다. 따라서 메시지 프로세서는 백엔드 서버로 클라이언트 인증서를 보내지 않습니다. 이로 인해 SSL 핸드셰이크가 실패하고 백엔드 서버가 'Fatal alert: bad_certificate' 메시지를 보냅니다.

해결 방법

  1. 클라이언트의 리프 인증서 (체인의 첫 번째 인증서)의 발급기관/인증 기관과 일치하는 발급기관/인증 기관이 있는 인증서가 백엔드 서버의 트러스트 저장소에 저장되어 있는지 확인합니다.
  2. 이 플레이북에 설명된 예시에서 발급기관 "issuer" : "CN=MyCompany Test SHA2 CA G2, DC=testcore, DC=test, DC=dir, DC=mycompany, DC=com" 이 있는 인증서가 백엔드 서버의 트러스트 저장소에 추가되어 문제를 해결했습니다.

문제가 계속되면 진단 정보를 수집해야 함으로 이동합니다.

진단 정보 수집 필요

위 안내를 따른 후에도 문제가 지속되면 다음 진단 정보를 수집하세요. Apigee Edge 지원팀에 문의하고 공유하세요.:

  1. Public Cloud 사용자인 경우 다음 정보를 제공합니다.
    1. 조직 이름
    2. 환경 이름
    3. API 프록시 이름
    4. 오류를 재현하는 완전한 curl 명령어
    5. 오류를 보여주는 trace 파일
    6. 백엔드 서버에서 캡처된 TCP/IP 패킷
  2. Private Cloud 사용자인 경우 다음 정보를 제공합니다.
    1. 확인된 완전한 오류 메시지
    2. API 프록시 번들
    3. 오류를 보여주는 trace 파일
    4. 메시지 프로세서 로그 /opt/apigee/var/log/edge-message-processor/logs/system.log
    5. 백엔드 서버 또는 메시지 프로세서에서 캡처된 TCP/IP 패킷
    6. 키 저장소 API의 인증서 가져오기의 출력
  3. 이 플레이북에서 시도한 섹션과 이 문제의 해결을 신속하게 처리하는 데 도움이 되는 기타 통찰에 관한 세부정보