5

次のようなコードがあります。

    // configure the SSLContext with a TrustManager
    SSLContext ctx = SSLContext.getInstance("TLS");
    ctx.init(new KeyManager[0], 
             new TrustManager[] {new DefaultTrustManager()}, 
             new SecureRandom());
    SSLContext.setDefault(ctx);

    URL url = new URL(urlString); // https://abc.myhost.com
    HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
    conn.setHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String arg0, SSLSession arg1) {
                System.out.println("verify:" + arg0);
                return true;
            }
        });

    System.out.println("HTTP status: " + conn.getResponseCode());
    Certificate[] certs = conn.getServerCertificates();
    int c=0;
    for (Certificate cert : certs){
        String t = cert.getType();
        System.out.println(String.format("\ncert[%d]: %s",c,t));
        c++;
        if (pi.verbose) {
            System.out.println(cert);
        }
        else if (cert instanceof X509Certificate) {
            X509Certificate x509cert = (X509Certificate) cert;
            System.out.println(x509cert.getSubjectDN().getName());
        }
    }

Java 6 で特定の Web サイトに対してこのコードを実行すると、Java 7 で取得した証明書とは異なる証明書が取得されます。ホスト名が abc.myhost.com であるとします。

Java6で私は得る:

cert[0]: X.509
CN=example.com,OU=Secure Link SSL Pro,O=Company Name Here, 
    STREET=2001 Space Odyssey Dr,L=Weirton,ST=Wv,2.5.4.17=#13053330303034,C=US

Java7 では次のようになります。

cert[0]: X.509
CN=abc.myhost.com,OU=Secure Link SSL Pro,O=Company Name Here, 
    STREET=2001 Space Odyssey Dr,L=Weirton,ST=Wv,2.5.4.17=#13053330303034,C=US

有効な日付を出力すると、それらも異なります。シリアルナンバーもそのまま。これらは異なる証明書です。

Java 7 では正しく見えます。Java 6 では、ホスト名と CN の間に不一致があります。証明書が間違っているようです。

このサーバーがプロキシの背後にある可能性は十分にあります。そのサーバーの所有者 (私が取り組んでいるプロジェクトのパートナー) が最近証明書を変更した可能性もあります。2 つの証明書が存在する可能性があります。1 つはプロキシ サーバー上にあり、もう 1 つはプロキシ サーバーの背後にあるサーバーにあります。私は彼らにこれを調べてもらいました。

私が持っている質問は、Java7 で Java6 と同じ結果が得られないのはなぜですか? Java は で何かを変更しましたHttpsURLConnection.getServerCertificates()か?


好奇心旺盛な人にとっては、これは単なる診断作業です。実際のエラーは次のとおりです。

javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: 
    No subject alternative DNS name matching abc.myhost.com found.

この場合の問題は通常、証明書のホスト名と CN の間の不一致です。私は意見の不一致を確認しましたが、Java 6 でのみです。Java6 と Java7 が異なる理由を理解したいと思います。


EDIT : Python 2.7.1 スクリプトは、Java6 と同じ証明書を返します。 SSLConnection.get_peer_cert()CN が一致しない証明書を表示します。

4

1 に答える 1

10

これは、Java 7 でクライアント側に導入された Server Name Indication サポートによるものと思われます。

SNI を使用すると、クライアントは SSL/TLS 初期要求内でホスト名を指定できます。特に、同じ IP アドレス/ポートで複数のホスト名を個別の証明書 (Apache Httpd が名前ベースの仮想ホストと呼ぶもの) でホストできるようになります。SSL/TLS ハンドシェーク中に要求されたホスト名を知ることで、サーバーは、HTTP トラフィックが使用される前に適切な証明書を提供できます (HTTPHostヘッダーは HTTP レベルで使用されますが、HTTPS には遅すぎます)。

クライアントがそれをサポートしていない場合、サーバーはクライアントが実際に必要とするホスト名を認識せず、通常はデフォルトのホスト値にフォールバックしてデフォルトの証明書を提供します。

(Win XP 上の IE のどのバージョンでも、場合によっては一部のモバイル ブラウザでも同じ問題が発生することに注意してください。)

編集:編集後 ( URL url = new URL(urlString); // https://abc.myhost.com)。

これにより、SNI の問題が確認されたようです。(Wireshark を使用して、TLS クライアントの Hello メッセージにサーバー名拡張子があるかどうかを確認できます。)

Java 7 および SNI をサポートする任意のクライアントを使用すると、 を要求すると、 (サーバーが正しく構成されている場合)https://abc.myhost.comに対して有効な証明書を実際に取得できます。その URL のホスト名で SSL/TLS 接続を開始します。abc.myhost.comHttpsURLConnection

Java 6 および SNI をサポートしていないクライアント (Python 2.7、少なくとも他のライブラリなし) では、その IP アドレスとポートに接続するときにサーバーがデフォルトで提示する証明書を取得します。

またはとは関係ありませHttpsURLConnection.getServerCertificates()SSLConnection.get_peer_cert()。むしろ、一部の古いクライアント/プラットフォームではサポートされていない SNI をサーバーがクライアントにサポートすることを期待しているためです。

Windows XP で Java 6、Python 2.x、Internet Explorer (または既定の MS API を使用するその他のクライアント) をサポートする必要がある場合、SNI を使用することはできません。この場合、サーバー管理者に連絡して、SNI を使用しないように構成を変更する必要があります (これらの複数のホストが必要な場合は、追加の IP アドレスが必要になる場合があります)。

于 2012-11-22T11:05:40.160 に答える