web-dev-qa-db-ja.com

Java SSLException:証明書のホスト名が一致しませんでした

次のコードを使用して、Googleのサービスの1つに接続しています。このコードは私のローカルマシンでうまくいきました:

HttpClient client=new DefaultHttpClient();
HttpPost post = new HttpPost("https://www.google.com/accounts/ClientLogin");
post.setEntity(new UrlEncodedFormEntity(myData));
HttpResponse response = client.execute(post);

このコードは、Google.comをブロックしていた実稼働環境に配置しました。リクエストに応じて、IP:74.125.236.52-GoogleのIPの1つにアクセスできるようにすることで、Googleサーバーとの通信を許可しました。 hostsファイルを編集して、このエントリも追加しました。

それでもURLにアクセスできませんでしたが、なぜだろうか。したがって、上記のコードを次のように置き換えました。

HttpPost post = new HttpPost("https://74.125.236.52/accounts/ClientLogin");

今、私はこのようなエラーを受け取ります:

javax.net.ssl.SSLException:証明書のホスト名が一致しませんでした:<74.125.236.52>!= <www.google.com>

これは、Googleに複数のIPがあるためだと思います。ネットワーク管理者にこれらすべてのIPへのアクセスを許可するように依頼することはできません。このリスト全体を入手することさえできないかもしれません。

私は今どうすればいい ? Java levelで回避策がありますか?それとも、完全にネットワーク担当者の手にありますか?

39
WinOrWin

Vineet Reynoldsに感謝します。あなたが提供したリンクは、多くのユーザーのコメントを保持していました。私はこのメソッドを追加しました:

// Do not do this in production!!!
HttpsURLConnection.setDefaultHostnameVerifier( new HostnameVerifier(){
    public boolean verify(String string,SSLSession ssls) {
        return true;
    }
});

私はこの解決策が一時的なものであることを知っていますが、これは今私にとっては問題ないようです。私は、ネットワークの人々と協力して、hostsファイルが無視される理由を特定しています。

5
WinOrWin

here の説明に従ってHostnameVerifierを設定することもできます。これはこのエラーを回避するために私のために働いた。

// Do not do this in production!!!
HostnameVerifier hostnameVerifier = org.Apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;

DefaultHttpClient client = new DefaultHttpClient();

SchemeRegistry registry = new SchemeRegistry();
SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory();
socketFactory.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier);
registry.register(new Scheme("https", socketFactory, 443));
SingleClientConnManager mgr = new SingleClientConnManager(client.getParams(), registry);
DefaultHttpClient httpClient = new DefaultHttpClient(mgr, client.getParams());

// Set verifier     
HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);

// Example send http request
final String url = "https://encrypted.google.com/";  
HttpPost httpPost = new HttpPost(url);
HttpResponse response = httpClient.execute(httpPost);
27
H6.

証明書検証プロセスは、サーバーが提示した証明書のDNS名を、クライアントが使用するURLのサーバーのホスト名で常に検証します。

次のコード

HttpPost post = new HttpPost("https://74.125.236.52/accounts/ClientLogin");

サーバーによって発行された証明書の共通名、つまりwww.google.comがホスト名、つまり74.125.236.52と一致するかどうかを検証する証明書検証プロセスになります。明らかに、これは失敗に結びつきます(ブラウザーでhttps://74.125.236.52/accounts/ClientLoginにアクセスして、これを確認し、結果のエラーを自分で確認できます)。

おそらく、セキュリティのために、独自のTrustManagerを書くことをためらっています(そして、安全なものを書く方法を理解しない限り、あなたはデータセンターでDNSレコードを確立することを見てはいけません) www.google.comへのすべてのルックアップが74.125.236.52に解決されることを確認してください。これは、ローカルDNSサーバーまたはOSのhostsファイルで行う必要があります。他のドメインにもエントリを追加する必要がある場合があります。言うまでもなく、これがISPから返されたレコードと一致していることを確認する必要があります。

22
Vineet Reynolds

同様の問題がありました。 AndroidのDefaultHttpClientを使用していました。 HttpsURLConnectionがこの種の例外を処理できることを読みました。そこで、HttpsURLConnectionのベリファイアを使用するカスタムHostnameVerifierを作成しました。また、実装をカスタムHttpClientにラップしました。

public class CustomHttpClient extends DefaultHttpClient {

public CustomHttpClient() {
    super();
    SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory();
    socketFactory.setHostnameVerifier(new CustomHostnameVerifier());
    Scheme scheme = (new Scheme("https", socketFactory, 443));
    getConnectionManager().getSchemeRegistry().register(scheme);
}

CustomHostnameVerifierクラスは次のとおりです。

public class CustomHostnameVerifier implements org.Apache.http.conn.ssl.X509HostnameVerifier {

@Override
public boolean verify(String Host, SSLSession session) {
    HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
    return hv.verify(Host, session);
}

@Override
public void verify(String Host, SSLSocket ssl) throws IOException {
}

@Override
public void verify(String Host, X509Certificate cert) throws SSLException {

}

@Override
public void verify(String Host, String[] cns, String[] subjectAlts) throws SSLException {

}

}

13
granko87

Httpcliet4.3.3のクリーンなアプローチ(テスト環境のみ)は次のとおりです。

SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext,SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
8

Httpclient-4.3.3.jarには、使用する別のHttpClientがあります。

public static void main (String[] args) throws Exception {
    // org.Apache.http.client.HttpClient client = new DefaultHttpClient();
    org.Apache.http.client.HttpClient client = HttpClientBuilder.create().build();
    System.out.println("HttpClient = " + client.getClass().toString());
    org.Apache.http.client.methods.HttpPost post = new HttpPost("https://www.rideforrainbows.org/");
    org.Apache.http.HttpResponse response = client.execute(post);
    Java.io.InputStream is = response.getEntity().getContent();
    Java.io.BufferedReader rd = new Java.io.BufferedReader(new Java.io.InputStreamReader(is));
    String line;
    while ((line = rd.readLine()) != null) { 
        System.out.println(line);
    }
}

このHttpClientBuilder.create()。build()は、org.Apache.http.impl.client.InternalHttpClientを返します。 this 証明書のホスト名が一致しませんでしたの問題を処理できます。

5
oraclesoon

懸念は、ALLOW_ALL_HOSTNAME_VERIFIERを使用しないことです。

独自のホスト名検証を実装するにはどうすればよいですか?

class MyHostnameVerifier implements org.Apache.http.conn.ssl.X509HostnameVerifier
{
    @Override
    public boolean verify(String Host, SSLSession session) {
        String sslHost = session.getPeerHost();
        System.out.println("Host=" + Host);
        System.out.println("SSL Host=" + sslHost);    
        if (Host.equals(sslHost)) {
            return true;
        } else {
            return false;
        }
    }

    @Override
    public void verify(String Host, SSLSocket ssl) throws IOException {
        String sslHost = ssl.getInetAddress().getHostName();
        System.out.println("Host=" + Host);
        System.out.println("SSL Host=" + sslHost);    
        if (Host.equals(sslHost)) {
            return;
        } else {
            throw new IOException("hostname in certificate didn't match: " + Host + " != " + sslHost);
        }
    }

    @Override
    public void verify(String Host, X509Certificate cert) throws SSLException {
        throw new SSLException("Hostname verification 1 not implemented");
    }

    @Override
    public void verify(String Host, String[] cns, String[] subjectAlts) throws SSLException {
        throw new SSLException("Hostname verification 2 not implemented");
    }
}

共有サーバーでホストされている https://www.rideforrainbows.org/ に対してテストしてみましょう。

public static void main (String[] args) throws Exception {
    //org.Apache.http.conn.ssl.SSLSocketFactory sf = org.Apache.http.conn.ssl.SSLSocketFactory.getSocketFactory();
    //sf.setHostnameVerifier(new MyHostnameVerifier());
    //org.Apache.http.conn.scheme.Scheme sch = new Scheme("https", 443, sf);

    org.Apache.http.client.HttpClient client = new DefaultHttpClient();
    //client.getConnectionManager().getSchemeRegistry().register(sch);
    org.Apache.http.client.methods.HttpPost post = new HttpPost("https://www.rideforrainbows.org/");
    org.Apache.http.HttpResponse response = client.execute(post);
    Java.io.InputStream is = response.getEntity().getContent();
    Java.io.BufferedReader rd = new Java.io.BufferedReader(new Java.io.InputStreamReader(is));
    String line;
    while ((line = rd.readLine()) != null) { 
        System.out.println(line);
    }
}

SSLException:

スレッド「メイン」の例外javax.net.ssl.SSLException:証明書のホスト名が一致しませんでした:www.rideforrainbows.org!= stac.rt.sg OR stac.rt.sg = OR www.stac.rt.sg
org.Apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.Java:231)で
...

MyHostnameVerifierで行う:

public static void main (String[] args) throws Exception {
    org.Apache.http.conn.ssl.SSLSocketFactory sf = org.Apache.http.conn.ssl.SSLSocketFactory.getSocketFactory();
    sf.setHostnameVerifier(new MyHostnameVerifier());
    org.Apache.http.conn.scheme.Scheme sch = new Scheme("https", 443, sf);

    org.Apache.http.client.HttpClient client = new DefaultHttpClient();
    client.getConnectionManager().getSchemeRegistry().register(sch);
    org.Apache.http.client.methods.HttpPost post = new HttpPost("https://www.rideforrainbows.org/");
    org.Apache.http.HttpResponse response = client.execute(post);
    Java.io.InputStream is = response.getEntity().getContent();
    Java.io.BufferedReader rd = new Java.io.BufferedReader(new Java.io.InputStreamReader(is));
    String line;
    while ((line = rd.readLine()) != null) { 
        System.out.println(line);
    }
}

ショー:

Host = www.rideforrainbows.org
SSL Host = www.rideforrainbows.org

少なくとも、比較(Host == SSL Host)してtrueを返すロジックがあります。

上記のソースコードは、httpclient-4.2.3.jarおよびhttpclient-4.3.3.jarで機能します。

3
oraclesoon