Javaでオレオレ証明書のサーバにアクセスする

オレオレ証明書が入っているサーバに、Android等JavaのコードでHTTPSアクセスする方法です。
# コード
```java
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;
	}
}
```
となります。

通常なら

```java
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
```

でHttpsURLConnectionのオブジェクトを作成する代わりに、ここでは

```java
HttpsURLConnection connection = makeOreOreHttpsURLConnection(url);
```

を呼び出して、証明書の正当性を無視するHttpsURLConnection を作成しています。

# おまけ
やっている内容は`makeOreOreHttpsURLConnection`メソッド内のコメントの通り、

* `HttpsURLConnection#setHostnameVerifier()`でホスト名の検証スキップ
* `HttpsURLConnection#setSSLSocketFactory()`証明書チェーンの検証スキップ

の2つです。

ところで、それぞれの代わりの手段として、

* `HttpsURLConnection#getDefaultHostnameVerifier()`
* `HttpsURLConnection#setDefaultSSLSocketFactory()`

といったメソッドもあります。
こちらは、API仕様には

```
このクラスの新しいインスタンスに継承されるデフォルトの HostnameVerifier を設定します。
```

とあります。
オレオレ証明書を信用するという変則的な挙動をデフォルトとして設定するのは気味が悪いですが、
`HttpsURLConnection`オブジェクトを作成している箇所が多い場合は、どこか1か所でこれらのメソッドを呼んでおけば処理をまとめられるメリットもあります。