javassl
JavaでSSL通信あれこれ。
このページの内容に関してはJava6(on WindowsXP)でテストしています。
このページの内容に関してはJava6(on WindowsXP)でテストしています。
通信する
- 最近のJavaなら、基本的に何も考えずに通信できます。
URLConnection connection = new URL("https://hogehoge/").openConnection();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "MS932"));
String buffer;
while ((buffer = reader.readLine()) != null) {
System.out.println(buffer);
}
- しかし、この方法だと、サーバ側の証明書として自作証明書を用いた場合は以下のような例外が発生して正しく通信できません(正確に言うと、Javaに標準でストアされている証明書リストに含まれていない場合、通信できません)。
- Javaの標準の証明書ストアは%JAVA_HOME%\jre\lib\security\cacertsに保管されています。
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
自作証明書を使用する場合1
- これを回避するには、例えば自作のX509TrustManagerを用いて署名エラーを無視するといった方法がある。
URLConnection connection = new URL("https://hogehoge/").openConnection();
HttpsURLConnection httpsconnection = (HttpsURLConnection)connection;
KeyManager[] km = null;
TrustManager[] tm = { new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
};
SSLContext sslcontext= SSLContext.getInstance("SSL");
sslcontext.init(km, tm, new SecureRandom());
httpsconnection.setSSLSocketFactory(sslcontext.getSocketFactory());
- HostnameVerifierが必要と記載されているところもありますが、上記の例では使用していなくても問題なく通信できています。何故??
自作証明書を使用する場合2
- 自作X509TrustManagerを使用するのではなく、証明書をインポートして使用するという選択肢もあります。
- keytool.exeを使用した場合、デフォルトではC:\Documents and Settings\username\.keystoreに格納されますが、-keystoreオプションでストア場所を任意に変更することができます。
- keytool.exeの使用方法の詳細はJavaDocを参照してください。
キーストアの作成
- サーバ証明書をインポートする場合、IEなどでアクセスして証明書をファイルに出力するという方法があります。
- IEで証明書を表示し、「詳細」→「ファイルにコピー」→「DER encoded bynary X509」形式でファイルを保存しておきます。例えば、hogesvr.cerというファイルを保管したとします。
- CA証明書があれば、それも保管しておきます(例えば、hogesvrca.cerとして保管)。
- keytool.exeを用いて上記証明書をインポートしたkeystoreを作成します。なお、keystoreを新規作成する場合、keystoreそのものに対するパスワードの登録が必要となります。
keytool.exe -keystore c:\tmp\cer\.keystore -import -alias hogesvrca -file hogesvrca.cer
keytool.exe -keystore c:\tmp\cer\.keystore -import -alias hogesvr -file hogesvr.cer
自作キーストアの読込
- プログラムからは、以下のような感じで作成したkeystoreを使用することができます。
System.setProperty("javax.net.ssl.trustStore", "C:\\tmp\\cer\\.keystore");
System.setProperty("javax.net.ssl.trustStorePassword", "password");
KeyStore trust_store = KeyStore.getInstance("JKS");
char[] trust_pass = System.getProperty("javax.net.ssl.trustStorePassword").toCharArray();
trust_store.load(new FileInputStream(System.getProperty("javax.net.ssl.trustStore")), trust_pass);
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(trust_store);
SSLContext sslcontext= SSLContext.getInstance("SSL");
sslcontext.init(null, tmf.getTrustManagers(), new SecureRandom());
httpsconnection.setSSLSocketFactory(sslcontext.getSocketFactory());
- KeyStoreのタイプとしてJKSとPKCS12があるが、どういうときにどちらを使用するのかは不明・・・keytoolで作成したものは全てJKS??。
- なお、ここでデフォルト以外のkeystoreを使用すると、通常の信頼済みサイトが「信頼されていない」となってしまいます。
- デフォルトのcacertsファイルをコピーし、それに対して自作証明書をインポートすればオッケーです。ちなみに、デフォルトの証明書ストアのパスワードはchageitとなります。
サーバ証明書の情報を取得する
- 以下のような感じでサクッと証明書を取得可能。ただし、一度でもサーバに実際に接続しなければ情報は取得できません(java.lang.IllegalStateException: connection not yet open という例外が発生します)。
HttpsURLConnection httpsconnection = (HttpsURLConnection)connection;
Certificate[] certificates = httpsconnection.getServerCertificates();
for (Certificate c : certificates) {
X509Certificate x509 = (X509Certificate)c;
System.out.println(x509.getSubjectDN());
}
- 自作証明書だろうが何だろうが、通信できれば情報の取得は可能。
- ちなみに、パッケージjavax.security.cert内のクラスは、旧バージョンの
クライアント証明書を使用して通信する
- クライアント証明書が必要となるエリアへアクセスした場合、証明書をロードしていない場合はjava.net.SocketException: Software caused connection abort: recv failedのような例外が発生する。
- PKCS#12形式のファイルに関しては、OpenSSLで作成したものをそのまま使用可能です。
System.setProperty("javax.net.ssl.trustStore", "C:\\tmp\\cer\\cacerts");
System.setProperty("javax.net.ssl.trustStorePassword", "changeit");
System.setProperty("javax.net.ssl.keyStore", "C:\\tmp\\cer\\testcert.p12");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
// サーバ側の証明書の処理
KeyStore trust_store = KeyStore.getInstance("JKS");
char[] trust_pass = System.getProperty("javax.net.ssl.trustStorePassword").toCharArray();
trust_store.load(new FileInputStream(System.getProperty("javax.net.ssl.trustStore")), trust_pass);
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(trust_store);
// クライアント側の証明書の処理
KeyStore key_store = KeyStore.getInstance("PKCS12");
char[] key_pass = System.getProperty("javax.net.ssl.keyStorePassword").toCharArray();
key_store.load(new FileInputStream(System.getProperty("javax.net.ssl.keyStore")), key_pass);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(key_store, key_pass);
// SSLContextの初期化
SSLContext sslcontext= SSLContext.getInstance("SSL");
sslcontext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
httpsconnection.setSSLSocketFactory(sslcontext.getSocketFactory());
2006年12月09日(土) 12:32:58 Modified by syo1976