「Java」 HttpsURLConnection で HTTPS 通信する ( サーバ認証あり )

以下のエントリーではサーバ証明書の信頼性をチェックしない HTTPS 通信をしてみましたが、今回は証明書の信頼性をチェックするパターンです。

・「Java」 HttpsURLConnection で HTTPS 通信する ( 信頼性のチェックなし ) - プログラム日記
http://a4dosanddos.hatenablog.com/entry/2015/03/28/160704

コードの内容はほとんど変わらないんですが、違うことは、サーバ証明書を発行したルート証明書をキーストアに登録して、システムプロパティなりで登録したキーストアを使用するよう設定するという感じです。

※ たぶん証明書が「ルート証明書 -> 中間証明書 -> サーバ証明書」みたいない階層になってる場合でも、クライアント側はルート証明書の登録だけでいけるはずです・・・
※ キーストアはこの場合は「トラストストア」と呼んだ方がいいのかな。

今回は Java の話がメインなので、ルート証明書サーバ証明書は既にある前提の内容にしてます。OpenSSL で作ってますが、このあたりの作り方はまた別のエントリーで書こうと思います。


まずは、以下の感じで keytool でルート証明書をキーストアに登録する。
cacert.pem がルート証明書になります。

keytool -importcert -alias ca -file cacert.pem -keystore castore -storepass password

コードは以下です。

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Map;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager;

public class Test {
	public static void main(String[] args) throws Exception {

		URL url = new URL("https://example.com/hello.html");
		HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
		//con.setSSLSocketFactory(factory);

		System.out.println("----- Headers -----");
		Map<String, List<String>> headers = con.getHeaderFields();
		for (String key : headers.keySet()) {
			System.out.println(key + ": " + headers.get(key));
		}
		System.out.println();

		System.out.println("----- Body -----");
		BufferedReader reader = new BufferedReader(new InputStreamReader(
				con.getInputStream()));
		String body;
		while ((body = reader.readLine()) != null) {
			System.out.println(body);
		}

		reader.close();
		con.disconnect();
	}
}

上記のコード実行する際に作成したキーストアが使われるようにします。やり方はいくつかありますが、システムプロパティ使う場合は、javax.net.ssl.trustStore にキーストアのパスを、javax.net.ssl.trustStorePassword にキーストアのパスワードを指定してやります。コード内でやるなら以下の感じです。

System.setProperty("javax.net.ssl.trustStore", "C:\\workspace44_2\\HTTPSClient\\crt\\castore");
System.setProperty("javax.net.ssl.trustStorePassword ", "password");

ちなみに、システムプロパティ以外ではコードで以下の処理を書いてやる方法もあります。HttpsURLConnection で使われる SSLSocketFactory を自分で作ってやるという感じですね。

	- 略 -
	char[] passphrase = "password".toCharArray();
	SSLContext ctx = SSLContext.getInstance("TLS");
	TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
	KeyStore ks = KeyStore.getInstance("JKS");

	ks.load(new FileInputStream("C:\\workspace44_2\\HTTPSClient\\crt\\castore"), passphrase);

	tmf.init(ks);
	ctx.init(null, tmf.getTrustManagers(), null);

	SSLSocketFactory factory = ctx.getSocketFactory();

	URL url = new URL("https://example.com/hello.html");
	HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
	con.setSSLSocketFactory(factory);
	- 略 -

※ 以下の実装例が参考になります。クライアント認証をする実装例なので KeyManagerFactory が使われてますが、要領は一緒です。

・SSLSocketClientWithClientAuth.java
http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/samples/sockets/client/SSLSocketClientWithClientAuth.java

ひとまず、上記でサーバ認証ありの HTTPS 通信ができるはずです。

・JSSE Reference Guide
http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html

・keytool
http://docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html

・HttpsURLConnection (Java Platform SE 7 )
http://docs.oracle.com/javase/jp/7/api/javax/net/ssl/HttpsURLConnection.html

・TrustManagerFactory (Java Platform SE 7 )
http://docs.oracle.com/javase/7/docs/api/javax/net/ssl/TrustManagerFactory.html

以上です。

[ 環境情報 ]
Java SE 8 Update 25