自前認証局のSSLサーバにJavaから接続する
概要
通常ECサイトなどを構築する場合、VeriSign などの「信頼できる認証局」によって発行された証明書を使ってSSLサーバを構築する。
しかし、テスト環境などで自分で発行した証明書を使ったSSLサーバを構築した場合、ブラウザでアクセスしようとすると画面上に警告が表示されたりする。
同様に、Javaプログラムから接続しようとすると エラーが発生する。
証明書のチェックを行わないように設定することで、この問題を回避できる。
java.net.URL を使った方法
問題再現手順
次のように自分で発行した証明書のサーバに接続しようとすると、エラーが発生する。
import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.URL; import java.net.URLConnection; import javax.net.ssl.HttpsURLConnection; public class Test1 { public static void main(String[] args) throws Exception { String href = "https://www.example.com/"; String encoding = "UTF-8"; URLConnection connection = new URL(href) .openConnection(); HttpsURLConnection httpsconnection = (HttpsURLConnection) connection; int responseCode = httpsconnection.getResponseCode(); System.out.println(responseCode); BufferedReader reader = new BufferedReader(new InputStreamReader( httpsconnection.getInputStream(), encoding)); String buffer; while ((buffer = reader.readLine()) != null) { System.out.println(buffer); } } }
発生するエラー
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
エラー回避方法
SSL 証明書検証をせずに SSL サーバに接続するには、次のように修正する。
import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.URL; import java.net.URLConnection; 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.HttpsURLConnection; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; public class Test2 { public static void main(String[] args) throws Exception { String href = "https://www.example.com/"; String encoding = "UTF-8"; URLConnection connection = new URL(href).openConnection(); HttpsURLConnection httpsconnection = (HttpsURLConnection) connection; // SSL 証明書検証をしない。 ignoreValidateCertification(httpsconnection); int responseCode = httpsconnection.getResponseCode(); System.out.println(responseCode); BufferedReader reader = new BufferedReader(new InputStreamReader( httpsconnection.getInputStream(), encoding)); String buffer; while ((buffer = reader.readLine()) != null) { System.out.println(buffer); } } private static void ignoreValidateCertification( HttpsURLConnection httpsconnection) throws NoSuchAlgorithmException, KeyManagementException { 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()); } }
HttpClientを使ったSSLサーバへの接続
Jakarta Commons の HttpClient を使った場合も同様である。
エラー再現
次のように同じエラーが発生する。
import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.methods.GetMethod; public class HttpClientTest1 { public static void main(String[] args) throws Exception { String href = "https://www.example.com/"; HttpClient httpClient = new HttpClient(); GetMethod method = new GetMethod(href); int retCode = httpClient.executeMethod(method); System.out.println(retCode); byte[] buf = method.getResponseBody(); System.out.write(buf); } }
エラー回避
次のような修正で回避できる。MySSLSocketFactory クラスを別途作成する必要がある。
import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.protocol.Protocol; /** * テスト2: 証明書チェックを行わない。 * */ public class HttpClientTest2 { private static void initHttpClient() { // 証明書チェックを行わない。 Protocol.registerProtocol("https", new Protocol("https", new MySSLSocketFactory(), 443)); } public static void main(String[] args) throws Exception { // HttpClient の初期設定。 initHttpClient(); String href = "https://www.example.com/"; HttpClient httpClient = new HttpClient(); GetMethod method = new GetMethod(href); int retCode = httpClient.executeMethod(method); System.out.println(retCode); byte[] buf = method.getResponseBody(); System.out.write(buf); } } import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; import java.net.UnknownHostException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.SocketFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import org.apache.commons.httpclient.ConnectTimeoutException; import org.apache.commons.httpclient.HttpClientError; import org.apache.commons.httpclient.params.HttpConnectionParams; import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory; public class MySSLSocketFactory implements SecureProtocolSocketFactory { private SSLContext sslcontext = null; private static SSLContext createEasySSLContext() { try { SSLContext context = SSLContext.getInstance("SSL"); context.init(null, new TrustManager[] { 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; } } }, null); return context; } catch (Exception e) { throw new HttpClientError(e.toString()); } } private SSLContext getSSLContext() { if (this.sslcontext == null) { this.sslcontext = createEasySSLContext(); } return this.sslcontext; } @Override public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose); } @Override public Socket createSocket(String host, int port) throws IOException, UnknownHostException { return getSSLContext().getSocketFactory().createSocket(host, port); } @Override public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException, UnknownHostException { return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort); } @Override public Socket createSocket(String host, int port, InetAddress localAddress, int localPort, HttpConnectionParams params) throws IOException, UnknownHostException, ConnectTimeoutException { if (params == null) { throw new IllegalArgumentException("Parameters may not be null"); } int timeout = params.getConnectionTimeout(); SocketFactory socketfactory = getSSLContext().getSocketFactory(); if (timeout == 0) { return socketfactory.createSocket(host, port, localAddress, localPort); } else { Socket socket = socketfactory.createSocket(); SocketAddress localaddr = new InetSocketAddress(localAddress, localPort); SocketAddress remoteaddr = new InetSocketAddress(host, port); socket.bind(localaddr); socket.connect(remoteaddr, timeout); return socket; } } }