1. ホーム
  2. java

[解決済み] SSLHandshakeExceptionによるhandshake_failureという致命的なアラートを受信しました。

2022-03-09 19:48:19

質問

オーソライズドSSL接続に問題があります。Client Authorized SSL証明書を使用して外部サーバーに接続するStrutsアクションを作成しました。このアクションで、銀行のサーバーにデータを送信しようとしているのですが、サーバーから次のようなエラーが表示され、うまくいきません。

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=all フラグを使用して、確立された SSL 接続のデバッグを有効にします。デバッグをオンにすると、ハンドシェイクのどのアクティビティが失敗したかをピンポイントで特定することができます。

更新情報

現在公開されている情報によると、この問題は、サーバーに発行された証明書とルートCAとの間の証明書信頼パスが不完全であることが原因であるように思われます。多くの場合、ルート認証局の証明書がトラストストアに存在しないため、証明書のトラストパスが存在せず、証明書がクライアントから本質的に信頼されない状況になります。ブラウザは警告を表示してユーザーがこれを無視できるようにすることができますが、同じことはSSLクライアント(たとえば HttpsURLConnection クラス、または Apache のような HTTP クライアントライブラリ HttpComponents クライアント ).

これらのクライアントクラス/ライブラリのほとんどは、証明書の検証のためにJVMによって使用されるトラストストアに依存することになります。ほとんどの場合、これは cacerts ファイル(JRE_HOME/lib/security ディレクトリ)にあります。もし、トラストストアの場所がJVMシステムプロパティである javax.net.ssl.trustStore そのパスのストアは、通常、クライアント・ライブラリで使用されるものです。もし疑問があれば、あなたの Merchant クラスが接続に使用しているクラス/ライブラリーを特定します。

このトラストストアにサーバーの証明書発行局を追加することで、問題は解決するはずです。私の 関連する質問で、ツールの入手方法について回答しています。 この目的のために Java キートゥールユーティリティ で十分である。

警告 : トラストストアは、基本的に、あなたが信頼するすべての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 を知る必要がありますが、今回の問題をデバッグする目的では、通常 ServerHello で handshake_failure が報告されることを知っていれば十分でしょう)。

1. クライアントハロー

接続が初期化される際に、一連のエントリーが報告されます。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, 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 が発生し、この段階でハンドシェイクは通常完了します (実際には、ハンドシェイクの後続ステージでは通常ハンドシェイクの失敗は発生しません)。どの段階が失敗したかを把握し、適切なメッセージを質問に対する更新として投稿する必要があります(メッセージをすでに理解し、それを解決するために何をすべきかを知っている場合を除く)。