【Java】【JAX-WS】 Webサービス / Metro [8] ~ SSL通信を行う (2) / クライアントサイド ~

はじめに

http://blogs.yahoo.co.jp/dk521123/36516080.html
で自前認証局(オレオレ証明書)でも何でも気にせず通すような処理をを実装したが
もう少しマシな実装を考える

サンプル

 * 今回は、決められた証明書以外は、エラーにするような実装を行う

準備

キーストアの作成
http://blogs.yahoo.co.jp/dk521123/36518468.html
より

[1] サーバ側のキーストアを作成する
~~~~
"%JAVA_HOME%\bin\keytool" -genkey -alias tomcat -keyalg RSA -keypass changeit -storepass changeit -keystore server.keystore -validity 3650
~~~~

[2] サーバ側のキーストアに証明書署名要求(CSR)を生成
~~~~
"%JAVA_HOME%\bin\keytool" -certreq -alias tomcat -keystore server.keystore -storepass changeit -file server.csr
~~~~

[3] サーバ側のキーストアから 証明書署名要求をエクスポートする
~~~~
"%JAVA_HOME%\bin\keytool" -export -alias tomcat -keystore server.keystore -storepass changeit -file server.cer
~~~~

[4] クライアント側のキーストアに、サーバ側の証明書署名要求をインポートする
~~~~
"%JAVA_HOME%\bin\keytool" -import -trustcacerts -alias tomcat -storepass changeit -file server.cer -keystore client.keystore
~~~~

Tomcatの設定
http://blogs.yahoo.co.jp/dk521123/36513426.html
より、【server.xml】

<!-- ★ここから★ -->
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
           maxThreads="150" SSLEnabled="true" defaultSSLHostConfigName="localhost">
    <SSLHostConfig hostName="localhost">
        <Certificate
        certificateKeystoreFile="conf/server.keystore"
        certificateKeystorePassword="changeit"
        certificateKeyAlias="tomcat"
        certificateKeystoreProvider="SUN"
        certificateKeystoreType="JKS"
        type="RSA" />
    </SSLHostConfig>
</Connector>
<!-- ★ここまで★ -->

サーバ側

 * 以下の関連記事を参照のこと
http://blogs.yahoo.co.jp/dk521123/36139336.html

クライアント側

http://blogs.yahoo.co.jp/dk521123/36516080.html
がベース
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

public class Main {
  /** 自己署名証明書のサーバへのSSL通信かどうか.
   *  true:自己署名証明書オレオレ証明書)へのSSL通信、false:信頼できる認証局の証明書へのSSL通信
   */
  private static final boolean isSslConnectionWithSelfSignedCertificate = true;

  public static void main(String[] args) {
    System.out.println("Start!");


    try {
      // ★SSL通信用の処理★
      if (isSslConnectionWithSelfSignedCertificate) {
         // オレオレ証明書用処理(正規の証明書は不要)
         SSLContext sslContext = SSLContext.getInstance("TLS");
         char[] keyPass = "changeit".toCharArray();

         try (FileInputStream fileInputStream = new FileInputStream("./etc/client.keystore")) {
            KeyStore keyStore = creatKeyStore(fileInputStream, keyPass);
            KeyManager[] keyManagers = creatKeyManagers(keyStore, keyPass);
            TrustManager[] trustManager = creatTrustManagers(keyStore);
            SecureRandom secureRandom = SecureRandom.getInstanceStrong();
            System.out.println("Algorithm : " + secureRandom.getAlgorithm());
            sslContext.init(keyManagers, trustManager, secureRandom);

            HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
            HostnameVerifier hostnameVerifier = creatHostnameVerifier();
            HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
         }
      }

      URL url = new URL("https://localhost:8443/SampleWebService/services/SampleWebService.ws?wsdl");
      SampleWebServiceService service = new SampleWebServiceService(url);
      SampleWebService proxy = service.getSampleWebServicePort();

      long start = System.currentTimeMillis();
      String result = proxy.sayYourAge("Ken", 11L);
      long end = System.currentTimeMillis();
      System.out.println((end - start) + "ms");

      System.out.println("Result : " + result);
    } catch (Exception ex) {
      ex.printStackTrace();
    }

    System.out.println("Done");
  }

  private static KeyStore creatKeyStore(InputStream inputStream, char[] trustpass)
      throws KeyStoreException, NoSuchAlgorithmException, CertificateException,
             FileNotFoundException, IOException {
    KeyStore keyStore = KeyStore.getInstance("JKS");
    keyStore.load(inputStream, trustpass);

    return keyStore;
  }
  
  private static KeyManager[] creatKeyManagers(KeyStore keyStore, char[] keyPass)
      throws GeneralSecurityException, IOException {
    String algorithm = KeyManagerFactory.getDefaultAlgorithm();
    KeyManagerFactory factory = KeyManagerFactory.getInstance(algorithm);
    factory.init(keyStore, keyPass);
    return factory.getKeyManagers();
  }

  private static TrustManager[] creatTrustManagers(KeyStore trustStore)
      throws NoSuchAlgorithmException, KeyStoreException {
    String algorithm = KeyManagerFactory.getDefaultAlgorithm();
    TrustManagerFactory factory = TrustManagerFactory.getInstance(algorithm);
    factory.init(trustStore);
    return factory.getTrustManagers();
  }

  private static HostnameVerifier creatHostnameVerifier() {
    return new HostnameVerifier() {
      public boolean verify(String hostname, SSLSession session) {
        System.out.println("verify " + hostname + " " + session.getPeerHost());
        // "localhost"は期待するホスト名を指定
        return "localhost".equals(hostname);
      }
    };
  }
}

出力結果

Start!
Algorithm : Windows-PRNG
74ms
Result : Ken's age is 11.
Done
参考:クライアント側のキーストアに別の証明書署名要求(CSR)をインポートした場合→例外が発生する
Start!
Algorithm : Windows-PRNG
javax.xml.ws.WebServiceException: 次の場所でWSDLへのアクセスに失敗しました: https://localhost:8443/SampleWebService/services/SampleWebService.ws?wsdl。次のメッセージにより失敗しました: 
	sun.security.validator.ValidatorException: No trusted certificate found。
	at com.sun.xml.internal.ws.wsdl.parser.RuntimeWSDLParser.tryWithMex(RuntimeWSDLParser.java:250)
	at com.sun.xml.internal.ws.wsdl.parser.RuntimeWSDLParser.parse(RuntimeWSDLParser.java:231)
	at com.sun.xml.internal.ws.wsdl.parser.RuntimeWSDLParser.parse(RuntimeWSDLParser.java:194)
	at com.sun.xml.internal.ws.wsdl.parser.RuntimeWSDLParser.parse(RuntimeWSDLParser.java:163)
	at com.sun.xml.internal.ws.client.WSServiceDelegate.parseWSDL(WSServiceDelegate.java:348)
	at com.sun.xml.internal.ws.client.WSServiceDelegate.<init>(WSServiceDelegate.java:306)
	at com.sun.xml.internal.ws.client.WSServiceDelegate.<init>(WSServiceDelegate.java:215)
	at com.sun.xml.internal.ws.client.WSServiceDelegate.<init>(WSServiceDelegate.java:196)
	at com.sun.xml.internal.ws.client.WSServiceDelegate.<init>(WSServiceDelegate.java:192)
	at com.sun.xml.internal.ws.spi.ProviderImpl.createServiceDelegate(ProviderImpl.java:99)
	at javax.xml.ws.Service.<init>(Service.java:77)
	at com.sample.webservice.client.stub.SampleWebServiceService.<init>(SampleWebServiceService.java:50)
	at com.sample.webservice.client.stub.Main.main(Main.java:45)
・・・略・・・
Caused by: sun.security.validator.ValidatorException: No trusted certificate found
	at sun.security.validator.SimpleValidator.buildTrustedChain(SimpleValidator.java:384)
	at sun.security.validator.SimpleValidator.engineValidate(SimpleValidator.java:133)
	at sun.security.validator.Validator.validate(Validator.java:260)
	at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324)
	at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:229)
	at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124)
	at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1351)
	... 27 more


関連記事

Webサービス

Webサービス / Metro [1] ~入門編 / サーバサイドの構築 ~
http://blogs.yahoo.co.jp/dk521123/36139336.html
Webサービス / Metro [2] ~入門編 / クライアントサイドの構築 ~
http://blogs.yahoo.co.jp/dk521123/36140561.html
Webサービス / Metro [6] ~ 証明書未チェックでSSL通信を行う (1) / クライアントサイド ~
http://blogs.yahoo.co.jp/dk521123/36516080.html

SSL通信

Java で、SSL通信を行うには
http://blogs.yahoo.co.jp/dk521123/33122920.html
自前認証局(オレオレ証明書) のSSLサーバに接続するには...
http://blogs.yahoo.co.jp/dk521123/35032522.html
Javaアプリにおける SSL通信時のデバッグ方法
http://blogs.yahoo.co.jp/dk521123/35261371.html

キーストア

Javaで、キーストアファイルをロードし、一覧表示する
https://blogs.yahoo.co.jp/dk521123/37020660.html
Keytool コマンド
http://blogs.yahoo.co.jp/dk521123/36518468.html
Ant で、Keystore ファイルを作成するには ~GenKeyタスク~
http://blogs.yahoo.co.jp/dk521123/35066497.html
SSLサイトの証明書をキーストアにインポートする
http://blogs.yahoo.co.jp/dk521123/36535856.html

SSL通信トラブル

* WebサービスにアクセスするJavaクライアントで、Exception: Broken pipeが発生する
http://blogs.yahoo.co.jp/dk521123/36643583.html