オレオレ証明書が入っているサーバに、Android等JavaのコードでHTTPSアクセスする方法です。
コード
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class RequestTest {
public static void main(String[] args) throws KeyManagementException, NoSuchAlgorithmException, IOException {
URL url = new URL("https://example.com/cert");
HttpsURLConnection connection = makeOreOreHttpsURLConnection(url);
connection.setRequestMethod("POST");
connection.setDoOutput(true);
BufferedWriter requestBody = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream()));
requestBody.write("{\"key\":\"value\"}");
requestBody.flush();
requestBody.close();
connection.connect();
BufferedReader responseBody = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String response = "";
while (responseBody.ready()) {
response += responseBody.readLine();
}
System.out.println(response);
}
private static HttpsURLConnection makeOreOreHttpsURLConnection(URL url) throws IOException, NoSuchAlgorithmException, KeyManagementException {
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
// 証明書に書かれているCommon NameとURLのホスト名が一致していることの検証をスキップ
connection.setHostnameVerifier(new HostnameVerifier() {
public boolean verify(String hostname, SSLSession sslSession) {
return true;
}
});
// 証明書チェーンの検証をスキップ
KeyManager[] keyManagers = null;
TrustManager[] transManagers = { new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
} };
SSLContext sslcontext = SSLContext.getInstance("SSL");
sslcontext.init(keyManagers, transManagers, new SecureRandom());
connection.setSSLSocketFactory(sslcontext.getSocketFactory());
return connection;
}
}
となります。
通常なら
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
でHttpsURLConnectionのオブジェクトを作成する代わりに、ここでは
HttpsURLConnection connection = makeOreOreHttpsURLConnection(url);
を呼び出して、証明書の正当性を無視するHttpsURLConnection を作成しています。
おまけ
やっている内容はmakeOreOreHttpsURLConnection
メソッド内のコメントの通り、
-
HttpsURLConnection#setHostnameVerifier()
でホスト名の検証スキップ -
HttpsURLConnection#setSSLSocketFactory()
証明書チェーンの検証スキップ
の2つです。
ところで、それぞれの代わりの手段として、
HttpsURLConnection#getDefaultHostnameVerifier()
HttpsURLConnection#setDefaultSSLSocketFactory()
といったメソッドもあります。
こちらは、API仕様には
このクラスの新しいインスタンスに継承されるデフォルトの HostnameVerifier を設定します。
とあります。
オレオレ証明書を信用するという変則的な挙動をデフォルトとして設定するのは気味が悪いですが、
HttpsURLConnection
オブジェクトを作成している箇所が多い場合は、どこか1か所でこれらのメソッドを呼んでおけば処理をまとめられるメリットもあります。