2016-03-31 9 views
7

私は、異なる証明書とSNIを持つ複数のTLS仮想ホストを実行するApache Webサーバーを持っています。Java 8を使用するSNIクライアント側の謎

私はさまざまなバーチャルホストにカールを使ってアクセスできます(おそらくSNIが動作すると思われます)。また、基本的にopenConnection()をURL上に置く小さなコマンドラインのJavaプログラムを使っても問題なくアクセスできます。

私のTomcatアプリケーションでは、基本的な同じクライアント側コードがクライアントと同じApacheサーバーにアクセスしますが、常に指定された仮想ホストの証明書ではなく、デフォルトのcert(defaulthost.defaultdomain)アクセスしようとするURL (これはSunCertPathBuilderExceptionを生成します - 基本的には証明書への証明書パスを証明することはできませんが、公式ではない証明書なので真実ですが、デフォルトの証明書は使用しないでください)

これは、アプリケーション/ TomcatでSNIがクライアント側で非アクティブ化された場合と同じです。私はなぜそれが私のアプリとコマンドラインの間で異なった振る舞いをするべきか迷っている。同じJDK、同じホストなど

プロパティjsse.enableSNIExtensionが見つかりましたが、どちらの場合もtrueに設定されていることを確認しました。質問:

  1. これらの2つのプログラムが異なる動作をする理由は何ですか?

  2. どのように私はこれをデバッグするでしょうか?

これは86_64、JDK 8u77、Tomcat 8.0.32のArch Linuxです。

+0

ネットワークトレースをキャプチャして、tomcatでSNI指標を送信することができますか? httpsリクエストを作成するコードは何ですか? –

+0

SNIは魔法のようにSSL接続に追加されませんが、SSL接続を確立するコードで使用する必要があります。すべてのHTTPライブラリがそれを行うわけではありません。私の推測では、SNIがあるケースでは使用されるが、他のケースでは使用されないように、小さなテストプログラムとは違って、TomcatでURLアクセスに別のライブラリまたはコードを使用していると思います。 –

+1

コマンドラインで '-Djavax.net.debug = ssl'を使用してJavaのSSLメッセージ(_e.g._)のデバッグを有効にして、Javaクライアントが送受信しているものを正確に確認できます。 – Castaglia

答えて

5

JDKをデバッグしてから数時間後、残念な結果が出ます。これは動作します:

URLConnection c = new URL("https://example.com/").openConnection(); 
InputStream i = c.getInputStream(); 
... 

これは失敗します。カスタムHostnameVerifierを呼び出されることはありませんですがsetHostnameVerifierコールを追加

URLConnection c = new URL("https://example.com/").openConnection(); 
((HttpsURLConnection)c).setHostnameVerifier(new HostnameVerifier() { 
     public boolean verify(String s, SSLSession sess) { 
      return false; // or true, won't matter for this 
     } 
}); 
InputStream i = c.getInputStream(); // Exception thrown here 
... 

は、SNIを無効にする結果となります。

犯人はsun.net.www.protocol.https.HttpsClientでこのコードのようだ:

  if (hv != null) { 
       String canonicalName = hv.getClass().getCanonicalName(); 
       if (canonicalName != null && 
       canonicalName.equalsIgnoreCase(defaultHVCanonicalName)) { 
        isDefaultHostnameVerifier = true; 
       } 
      } else { 
       // Unlikely to happen! As the behavior is the same as the 
       // default hostname verifier, so we prefer to let the 
       // SSLSocket do the spoof checks. 
       isDefaultHostnameVerifier = true; 
      } 
      if (isDefaultHostnameVerifier) { 
       // If the HNV is the default from HttpsURLConnection, we 
       // will do the spoof checks in SSLSocket. 
       SSLParameters paramaters = s.getSSLParameters(); 
       paramaters.setEndpointIdentificationAlgorithm("HTTPS"); 
       s.setSSLParameters(paramaters); 

       needToCheckSpoofing = false; 
      } 

いくつかの明るい心のチェック設定さHostnameVerifierのクラスが呼び出さ、デフォルトのJDKクラスは(、ちょうど私のように、falseを返しているかどうか上のコード)、それに基づいて、SSL接続のパラメータを変更します。これは副作用としてSNIを無効にします。

クラスの名前を確認し、それに依存するロジックを作成することは、今のところ私を逃れさせる良いアイデアです。 ( "ママ!バーチャルメソッドは必要ありません、クラス名をチェックしてディスパッチするだけです!")しかし、悪いことに、SNIはまずHostnameVerifierと何をしなければならないのですか?

同じ明るい心はまた、大文字と小文字を区別しない名前の比較を行うことを決めたので、おそらくこの問題を回避するには、同じ名前を持つカスタムHostnameVerifierが、異なる大文字と小文字を使用することです。

'と述べた。

+2

'HttpsURLConnection'に独自の' SSLSocketFactory'サブクラスを設定して、 'SSLSocket'を返す_before_が' socket.getSSLParameters()。setEndpointIdentificationAlgorithm( "HTTPS") 'を実行するようにして、上記のコード? – Castaglia

+0

あなたは正しいようです。素晴らしい法医学! –

7

この回答は遅れていますが、私たちは問題にぶつかりました(私はそれを信じることはできませんが、非常に大きなバグです)。

すべてが真実だと思われますが、デフォルトのHostnameVerifierではありませんが、トラブルシューティング担当者です。 (ソケットがSSLSocketImplの場合のみ)HttpsClient DO afterConnectが最初setHostを確立しようとすると:あなたはオーバーライドのcreateSocket()(パラメータなしのメソッド)を使用せずに、カスタムのSSLSocketFactoryを使用している場合

SSLSocketFactory factory = sslSocketFactory; 
try { 
    if (!(serverSocket instanceof SSLSocket)) { 
     s = (SSLSocket)factory.createSocket(serverSocket, 
              host, port, true); 
    } else { 
     s = (SSLSocket)serverSocket; 
     if (s instanceof SSLSocketImpl) { 
      ((SSLSocketImpl)s).setHost(host); 
     } 
    } 
} catch (IOException ex) { 
    // If we fail to connect through the tunnel, try it 
    // locally, as a last resort. If this doesn't work, 
    // throw the original exception. 
    try { 
     s = (SSLSocket)factory.createSocket(host, port); 
    } catch (IOException ignored) { 
     throw ex; 
    } 
} 

は、十分にパラメータ化のcreateSocketを使用して、すべてのさ期待どおりに動作します(クライアントsniの拡張機能を使用)。しかし、それが使われているときに、第2の方法(setHostエンSSLSocketImplにしてみてください)のコードが実行される:

// ONLY used by HttpsClient to setup the URI specified hostname 
// 
// Please NOTE that this method MUST be called before calling to 
// SSLSocket.setSSLParameters(). Otherwise, the {@code host} parameter 
// may override SNIHostName in the customized server name indication. 
synchronized public void setHost(String host) { 
    this.host = host; 
    this.serverNames = 
     Utilities.addToSNIServerNameList(this.serverNames, this.host); 
} 

コメントはすべて言います。クライアントハンドシェイクの前にsetSSLParametersを呼び出す必要があります。デフォルトのHostnameVerifierを使用すると、HttpsClientはsetSSLParametersを呼び出します。しかし、逆の方法でsetSSLParametersの実行はありません。この修正はOracleにとって非常に簡単です。

SSLParameters paramaters = s.getSSLParameters(); 
if (isDefaultHostnameVerifier) { 
    // If the HNV is the default from HttpsURLConnection, we 
    // will do the spoof checks in SSLSocket. 
    paramaters.setEndpointIdentificationAlgorithm("HTTPS"); 

    needToCheckSpoofing = false; 
} 
s.setSSLParameters(paramaters); 

Java 9はSNIで期待どおり動作しています。しかし、彼ら(オラクル)は、この問題を解決するために必要ではないようだ。

+0

ありがとう、非常に便利です。 – Cheeso

+1

最後に、Java 1.8.0_141のSNIが修正されています。http://www.oracle.com/technetwork/java/javase/8u141-relnotes-3720385.html – Dawuid

0

これは同じ問題であれば、本当にわかりません。私のJVMは、SNIサーバに接続するためのプライベート署名付き証明書を信頼するように設定されています。証明書はしばらく(数時間)働いてから、SunCertPathBuilderException: unable to find valid certification path to requested targetというエラーで失敗します。エラーが発生すると、Javaアプリケーションは、まったく同じ設定でJVMを再起動しない限り、HTTPSサーバーに接続できなくなります。すべてが再び動作するようになります。これは絶え間なく繰り返されます。

これは上記の問題と同じですか?

ありがとう

関連する問題