LoginSignup
7
10

More than 5 years have passed since last update.

HttpClientで異なるホスト名のSSL証明書を許可する方法

Posted at

はじめに

Apache HttpComponentsにより、SSL通信を行う際には当然ですが、ホスト名の検証が行われます。したがって、例えばhttps://google.com/の代わりにhttps://216.58.220.227/に接続した場合、SSLExceptionが発生します。ただし、環境の移行時等にはIPアドレスでアクセスする必要が生じたりするため、SSL証明書を無視することなく接続する方法を調べてみました。

簡単に

ここで、証明書のホスト名を無視するだけであれば、SSLSocketFactoryクラスのsetHostnameVerifierメソッドにALLOW_ALL_HOSTNAME_VERIFIERを渡せば、例外は生じなくなります。次にHttpClientの生成例を示します。

HttpParams params = new BasicHttpParams();
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
SSLSocketFactory sslSocketFactory = SSLSocketFactory.getSocketFactory();
// ホスト名の検証を行わない。
sslSocketFactory.setHostnameVerifier(ALLOW_ALL_HOSTNAME_VERIFIER);
registry.register(new Scheme("https", sslSocketFactory, 443));
ThreadSafeClientConnManager clientConnManager = new ThreadSafeClientConnManager(params, registry);
HttpClient httpClient = new DefaultHttpClient(clientConnManager , params);

安全に

前述の方法によると、ホスト名の検証を行わないので、証明書がルート証明書から正しくつながっていれば、その通信を許可してしまいます。ですから、宛先ホスト以外の任意のドメインに属するSSL証明書を持った人間でも、通信を傍受することが可能になります。

そこで、ホスト名の変換テーブルを検証器に持たせ、IPアドレスで接続したいホストを変換テーブルに登録しておくことにしました。これにより、意図したSSL証明書でのみ、ホスト名の問題を無視して通信を行うことができます。次にこの実装を行った検証器、CustomHostnameVerifierクラスを示します。

CustomHostnameVerifier.java
package net.leak4mk0.util;

import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
import org.apache.http.conn.ssl.X509HostnameVerifier;

import java.io.IOException;
import java.security.cert.X509Certificate;
import java.util.Map;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;

public class CustomHostnameVerifier implements X509HostnameVerifier {
    private BrowserCompatHostnameVerifier mHostnameVerifier;
    private Map<String, String> mHostMap;

    public CustomHostnameVerifier(Map<String, String> hostMap) {
        mHostnameVerifier = new BrowserCompatHostnameVerifier();
        mHostMap = hostMap;
    }

    @Override
    public boolean verify(String host, SSLSession session) {
        return mHostnameVerifier.verify(getCustomHost(host), session);
    }

    @Override
    public void verify(String host, SSLSocket ssl) throws IOException {
        mHostnameVerifier.verify(getCustomHost(host), ssl);
    }

    @Override
    public void verify(String host, X509Certificate cert) throws SSLException {
        mHostnameVerifier.verify(getCustomHost(host), cert);
    }

    @Override
    public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {
        mHostnameVerifier.verify(getCustomHost(host), cns, subjectAlts);
    }

    private String getCustomHost(String originalHost) {
        if (!mHostMap.containsKey(originalHost)) {
            return originalHost;
        }

        return mHostMap.get(originalHost);
    }
}

X509HostnameVerifierを実装したBrowserCompatHostnameVerifierクラスを内部で利用しています。継承するつもりでしたが、verifyメソッドがfinalでしたので、このような形になっています。コードを見ればわかることですが、getCustomHostメソッドで変換テーブルにあれば置換後のホストを返し、検証を続行しています。

前述のsetHostnameVerifierメソッドで変換テーブルを持ったCustomHostnameVerifierのインスタンスを渡せば、検証器を変えることができます。

Map<String, String> HOST_MAP =
        new HashMap<String, String>() {{
            put("216.58.220.227", "google.com");
        }};

HttpParams params = new BasicHttpParams();
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
SSLSocketFactory sslSocketFactory = SSLSocketFactory.getSocketFactory();
// ホスト名の変換テーブルを持った検証器を利用する。
sslSocketFactory.setHostnameVerifier(new CustomHostnameVerifier(HOST_MAP));
registry.register(new Scheme("https", sslSocketFactory, 443));
ThreadSafeClientConnManager clientConnManager = new ThreadSafeClientConnManager(params, registry);
HttpClient httpClient = new DefaultHttpClient(clientConnManager , params);

さいごに

私は、この方法で安全性を確保したまま、サーバー移行時の問題を解決することができました。
この記事で示したコードはご自由に使っていただいて大丈夫です。ただし、一切の責任を取りかねますのでご了承ください。

7
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
10