SSL handshake エラー - 不正なクライアント証明書

症状

クライアント アプリケーションが API リクエストに対するレスポンスとして、HTTP ステータス コード 503 とメッセージ "Service Unavailable" を受け取ります。UI トレースでは、失敗した API リクエストのターゲット リクエスト フローに Received fatal alert: bad_certificate という error.cause が示されます。

Message Processor のログにアクセスできる場合、失敗した API リクエストに対して "Received fatal alert: bad_certificate" というエラー メッセージが記録されているはずです。このエラーは、双方向 TLS セットアップでの Message Processor とバックエンド サーバー間の SSL handshake プロセス中に観測されます。

エラー メッセージ

クライアント アプリケーションは、次のレスポンス コードを受け取ります。

HTTP/1.1 503 Service Unavailable

さらに、次のエラー メッセージも確認できます。

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

Private Cloud ユーザーは、Message Processor のログ /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 Cloud と Public Cloud のユーザー
認証局不一致 Message Processor のキーストアに格納されているリーフ証明書(証明書チェーンの最初の証明書)の認証局が、バックエンド サーバーで承認されている認証局のいずれとも一致しません。 Edge Private Cloud と Public Cloud のユーザー

共通の診断手順

  1. Edge UI でトレースを有効にしてから API 呼び出しを実行し、問題を再現します。
  2. UI トレース結果で、各フェーズを確認し、どこでエラーが発生したのか判断します。エラーはターゲット リクエスト フローで発生しているはずです。
  3. エラーが示されているフローを調べると、次のサンプル トレースに示すようなエラーを確認できるはずです。

    alt_text

  4. 上記のスクリーンショットに示されているように、error.cause は "Received fatal alert: bad_certificate" となっています。
  5. Private Cloud ユーザーは、次の手順に従います。
    1. 失敗した API リクエストのメッセージ ID を取得するために、トレース内の AX で示されているフェーズでのエラーヘッダー「X-Apigee.Message-ID」の値を確認します。
    2. このメッセージ ID を Message Processor のログ /opt/apigee/var/log/edge-message-processor/system.log で検索し、エラーに関する詳細情報を見つけられるかどうか確認します。
      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]
          

      Message Processor のログにはエラー Received fatal alert: bad_certificate のスタック トレースが記録されていますが、この問題の原因を示唆する追加情報は含まれていません。

  6. さらに詳しく問題を調査するには、tcpdump ツールを使用して TCP/IP パケットをキャプチャする必要があります。
    1. Private Cloud ユーザーは、バックエンド サーバーまたは Message Processor 上で 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>

      Message Processor 上で TCP/IP パケットをキャプチャする場合は、tcpdump コマンドでバックエンド サーバーのパブリック IP アドレスを使用します。

      バックエンド サーバー / Message Processor に複数の IP アドレスがある場合は、別の tcpdump コマンドを使用する必要があります。このツールの詳細とこのコマンドの他のバリアントについては、tcpdump をご覧ください。

  7. Wireshark ツールまたは同様のツールを使用して TCP/IP パケットを分析します。

Wireshark ツールを使用する場合、サンプル TCP/IP パケットのデータを分析すると、次の出力結果が表示されます。

alt_text

  1. 上記の tcpdump の出力では、メッセージ 4 に、Message Processor(送信元)が「Client Hello」メッセージをバックエンド サーバー(宛先)に送信したことが示されています。
  2. メッセージ 5 には、バックエンド サーバーが Message Processor からの Client Hello メッセージに確認応答したことが示されています。
  3. バックエンド サーバーは自身の証明書とあわせて「Server Hello」メッセージを送信した後、クライアントにクライアント証明書を送信するようリクエストします(メッセージ 7)。
  4. Message Processor が証明書の検証を完了し、バックエンド サーバーの Server Hello メッセージに確認応答します(メッセージ 8)。
  5. Message Processor が自身の証明書をバックエンド サーバーに送信します(メッセージ 9)。
  6. バックエンド サーバーが Message Processor の証明書を受信したことを確認応答します(メッセージ 11)。
  7. ただし、バックエンド サーバーは直ちに Fatal Alert: Bad Certificate を Message Processor に送信します(メッセージ 12)。これは、Message Processor から送信された証明書に問題あり、バックエンド サーバー上での証明書検証が失敗したことを意味します。その結果、SSL handshake は失敗して接続が閉じられます。


    alt_text

  8. メッセージ 9 を調べて、Message Processor から送信された証明書の内容を確認します。


    alt_text

  9. バックエンド サーバーがクライアントから証明書を受け取っていないことがわかります(Certificate Length: 0)。そのため、バックエンド サーバーは「Fatal Alert: Bad Certificate」を送信しています。
  10. 一般にこのエラーが発生するのは、クライアント、つまり Message Processor(Java ベースのプロセス)が次の状態になっている場合です。
    1. クライアントのキーストアにクライアント証明書が格納されていない。
    2. クライアント証明書を送信できない。バックエンド サーバーで承認されている認証局のいずれかから発行された証明書が見つからない場合は、この状態になる可能性があります。つまり、クライアントのリーフ証明書(証明書チェーンの最初の証明書)の認証局が、バックエンド サーバーで承認している認証局のいずれとも一致しなければ、Message Processor は証明書を送信しません。

ここからは、以上の原因のそれぞれを見ていきましょう。

原因: クライアント証明書がない

診断

ターゲット エンドポイントの SSL 情報セクションまたはターゲット エンドポイントで使用されているターゲット サーバーの SSL 情報セクションで指定されているキーストアに証明書が 1 つも格納されていない場合は、それがこのエラーの原因です。

これが原因であるかどうか判断するには、次の手順に従います。

  1. 次の手順に従って、特定の API プロキシのターゲット エンドポイントまたはターゲット サーバーで使用されているキーストアを判別します。
    1. ターゲット エンドポイントまたはターゲット サーバーの SSLInfo セクションで、Keystore 要素に含まれているキーストア参照名を確認します。

      以下はターゲット エンドポイント構成の 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 Proxies] -> [Environment Configurations] を選択します。

      [References] タブを選択し、キーストア参照名を検索します。目的のキーストア参照の [References] 列に示されている値をメモします。これが、使用しているキーストア名です。


      alt_text

    4. 上記の例では、myKeystoreRef が「myKeystore」を参照しています。したがって、キーストア名は myKeystore です。
  2. Edge UI またはこの管理 API を使用して、このキーストアに証明書が格納されているかどうか確認します。
  3. キーストアに 1 つ以上の証明書が格納されている場合は、原因: 認証局不一致に進みます。
  4. キーストアに証明書が 1 つも格納されていない場合は、それが理由で Message Processor から証明書が送信されません。

解決策

  1. Message Processor 内の特定のキーストアに、適切かつ完全な証明書チェーンがアップロードされるようにします。

原因: 認証局不一致

通常、サーバーがクライアントにクライアント証明書を送信するようリクエストするときは、承認済みの一連の発行元または認証局を示します。Message Processor のキーストアに格納されているリーフ証明書(証明書チェーンの最初の証明書)の認証局が、バックエンド サーバーで承認されている認証局のいずれとも一致しない場合、Message Processor(Java ベースのプロセス)はバックエンド サーバーに証明書を送信しません。

この状況が該当するかどうか確認するには、次の手順に従います。

  1. キーストアに格納されている証明書を一覧表示します。
  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>
        

    このコマンドの出力で、次に示す「Acceptable Client Certificate CA names」というセクションを参照します。

    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 です。「Distinguished Names」セクションを参照します。ここに、バックエンド サーバーが許容する認証局が示されています。

    alt_text

  5. ステップ 3 で取得した認証局が、ステップ 4 で取得したリストに含まれる、バックエンド サーバーが許容する発行元または認証局のいずれかと一致するかどうか確認します。一致していなければ、Message Processor はクライアント証明書をバックエンド サーバーに送信しません。

    上記の例では、Message Processor のキーストアに格納されているクライアントのリーフ証明書の発行元は、バックエンド サーバーが許容する認証局のいずれとも一致しません。したがって、Message Processor はクライアント証明書をバックエンド サーバーに送信しません。その結果、SSL handshake が失敗し、バックエンド サーバーが「Fatal alert: bad_certificate」メッセージを送信します。

解決策

  1. クライアントのリーフ証明書(証明書チェーンの最初の証明書)の発行元 / 認証局と一致する発行元 / 認証局によって発行された証明書が、バックエンド サーバーのトラストストアに格納されるようにします。
  2. このプレイブックで説明している例では、問題を解決するために、発行元が "issuer" : "CN=MyCompany Test SHA2 CA G2, DC=testcore, DC=test, DC=dir, DC=mycompany, DC=com" となっている証明書をバックエンド サーバーのトラストストアに追加します。

それでも問題が解決しない場合は、診断情報の収集が必要な場合に進んでください。

診断情報の収集が必要な場合

以上の手順に従っても問題が解決されない場合は、次の診断情報を収集してください。Apigee サポートに連絡して、収集した情報を共有してください。

  1. Public Cloud をご利用の場合は、次の情報を提供してください。
    1. 組織名
    2. 環境名
    3. API プロキシ名
    4. エラーを再現するための完全な curl コマンド
    5. エラーが記録されているトレース ファイル
    6. バックエンド サーバー上でキャプチャした TCP/IP パケット
  2. Private Cloud をご利用の場合は、次の情報を提供してください。
    1. 確認したエラー メッセージ全文
    2. API プロキシ バンドル
    3. エラーが記録されているトレース ファイル
    4. Message Processor のログ /opt/apigee/var/log/edge-message-processor/logs/system.log
    5. バックエンド サーバーまたは Message Processor 上でキャプチャした TCP/IP パケット
    6. キーストアに格納されている証明書を一覧表示する API の出力と、この管理 API を使用して取得した各証明書の詳細
  3. このプレイブックのどのセクションを試したかを含め、その他すべての分析情報を詳しく説明していただけると、迅速な問題解決に役立ちます。