時々使う割には、かなりの頻度で忘れるのでメモ。
テストなどでSSLの自己署名証明書を使った場合に、よく遭遇する状況
- SSL証明書をマジメに検証しないようにしたい
- 証明書のホスト名と実際のホスト名が異なる時にエラーにならないようにしたい
というのをゴマかそう?というお話。
以下のような例外がスローされるケースですね。
アクセス先が、未知の証明書を返却する場合。
sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
※単に自己署名証明書を通したいだけなら、後述のキーストアに証明書を追加すれば済む話です
証明書に書かれているホスト名と、アクセス先のホスト名が合わない場合。
java.security.cert.CertificateException: No name matching [ホスト名] found
import文は、以下を前提とします。
import java.net.HttpURLConnection; 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.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.X509TrustManager;
また、javax.net.ssl.HttpsURLConnectionを使用した通信としてコード例は記載します。ま、SSLSocketFactoryをどうにかできればいいはずですが。
SSL証明書の検証を行わない
まずは、証明書の検証を行わないケースについて。X509TrustManagerインターフェースを実装したクラスを作成します。
public class LooseTrustManager implements X509TrustManager { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return null; } }
ここでは、各種メソッドが何もしない、もしくはnullを返却しないように実装。
これを、SSLContextへの設定後、HttpsURLConnectionが使用するSSLSocketFactoryとして設定します。
HttpsURLConnection secureConn = ...; // 証明書の検証をしない // sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(null, new X509TrustManager[] { new LooseTrustManager() }, new SecureRandom()); secureConn.setSSLSocketFactory(sslContext.getSocketFactory());
証明書と実際にアクセスするホスト名の検証を行わない
ホスト名の検証をしない場合は、HostnameVerifierインターフェースを実装したクラスを作成します。ここで、verifyメソッドが常にtrueを返却するように実装します。
public class LooseHostnameVerifier implements HostnameVerifier { public boolean verify(String hostname, SSLSession session) { return true; } }
あとは、HttpsURLConnection#setDefaultHostnameVerifier、またはHttpsURLConnection#setHostnameVerifierで作成したHostnameVerifierの実装のインスタンスを設定します。
// ホスト名の検証をしない // java.security.cert.CertificateException: No name matching [ホスト名] found // HttpsURLConnection.setDefaultHostnameVerifier(new LooseHostnameVerifier()); secureConn.setHostnameVerifier(new LooseHostnameVerifier());
繋げると、こんな感じ。
HttpsURLConnection secureConn = ...; // 証明書の検証をしない // sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(null, new X509TrustManager[] { new LooseTrustManager() }, new SecureRandom()); secureConn.setSSLSocketFactory(sslContext.getSocketFactory()); // ホスト名の検証をしない // java.security.cert.CertificateException: No name matching [ホスト名] found // HttpsURLConnection.setDefaultHostnameVerifier(new LooseHostnameVerifier()); secureConn.setHostnameVerifier(new LooseHostnameVerifier());
合わせて覚えたい
システムプロパティに、「-Djavax.net.debug=all」を指定します。
$ java -Djavax.net.debug=all ....
「all」以外にも「ssl:handshake」なども指定可能です。詳しくは、こちらへ。
信頼済みのキーストアを指定する。
システムプロパティに、「-Djavax.net.ssl.trustStore=[信頼済みキーストアのパス]」を指定します。
$ java -Djavax.net.ssl.trustStore=trustStore ...
キーストアに証明書を追加するには、
$ keytool -import -alias [エイリアス] -keystore [キーストアへのパス] -file [追加する証明書へのパス]
ですね。
参考)
SSL/TLS 接続のデバッグ
http://docs.oracle.com/javase/jp/7/technotes/guides/security/jsse/ReadDebug.html
Debugging SSL/TLS Connections
http://docs.oracle.com/javase/jp/8/technotes/guides/security/jsse/ReadDebug.html