2012年12月9日 星期日

[筆記] 如何解決在HTTPS下開發Web Service 的問題

圖片來源:fotolia


延續上一篇"透過wsimport 去產生 web service client 的程式碼" 如果你看到出現這種錯誤訊息:
SunCertPathBuilderException: unable to find valid certification path to requested target 
那就代表你遇到這個問題了,因為對方的Web Service 連線是使用HTTPS,而且是使用Self-sign SSL Certificate,所以你的jvm不信任它,而且在keystore也沒有找到相對應的key。

在stackoverflow 找了許多討論:
[1] stackoverflow - How to use wsimport when server expects client certificate?
[2] stackoverflow - Mutual SSL - getting the key/truststores in the proper formats
[3] stackoverflow - SOAP with mutual SSL - how to send over credentials?
 
主要分為幾種作法:

1. 關掉驗證的機制 (不推薦)


範例程式:

Disable Certificate Validation (code from Example Depot):
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] {
    new X509TrustManager() {   
        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return null; //這邊就是重點
        }
        public void checkClientTrusted(
            java.security.cert.X509Certificate[] certs, String authType) {
            }
        public void checkServerTrusted(
            java.security.cert.X509Certificate[] certs, String authType) {
        }
    }
};

// Install the all-trusting trust manager
try {
    SSLContext sc = SSLContext.getInstance("SSL");
    sc.init(null, trustAllCerts, new java.security.SecureRandom());
    HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (GeneralSecurityException e) {
}
// Now you can access an https URL without having the certificate in the truststore
try {
    URL url = new URL("https://hostname/index.html");
} catch (MalformedURLException e) {
}

另外可以參考的方法 (用來暫時關閉驗證的方法) 


2. 匯入Self-sign SSL Certificate


匯入的方法有很多種,傳統的方法就是教你利用Keytool去匯入,範例如下:

\bin\keytool -import -v -trustcacerts -alias server-alias -file server.cer -keystore cacerts.jks -keypass changeit -storepass changeit

但是這個方法有一個最大的問題,就是Server.cer和cacerts.jks分別從哪裡來?該怎麼產生?根據sun 官網的描述:
If you are running the client on a different machine, you need to export the tomcat certificate and import into your trustore etc. Use those values while starting your client application.
也就是說,你必須先從跑HTTPS的 Server 匯出Key,然後再手動傳到你client所在的機器,步驟如下:

A. 在Server端匯出你的Certificate

On Windows:
%JAVA_HOME%\bin\keytool -export -alias tomcat -file file.cer

On Linux:
$JAVA_HOME/bin/keytool -export -alias tomcat -file file.cer

B. 匯入剛剛匯出的Certificate 到Client 端的 TrustStore:
 On Windows:
%JAVA_HOME%\bin\keytool -import -alias serverCert -file RootCert.crt -keystore %JAVA_HOME%\jre\lib\security\cacerts

On Linux:
$JAVA_HOME/bin/keytool -import -alias serverCert -file RootCert.crt -keystore $JAVA_HOME/jre/lib/security/cacerts

C. 匯入後 Client 端必須利用 JAVA_OPTS去指定 TrustStore:

On Windows:
JAVA_OPTS = -Djavax.net.ssl.trustStore="%JAVA_HOME%\jre\lib\security\cacerts" -Djavax.net.ssl.trustStorePassword="changeit"

On Linux:
JAVA_OPTS = -Djavax.net.ssl.trustStore="$JAVA_HOME/jre/lib/security/cacerts" -Djavax.net.ssl.trustStorePassword="changeit"

Ps.
1. 通常cacerts檔案是存在以下路徑: %JAVA_HOME%\jre\lib\security\cacerts
2. Windows 和 Linux trustStore password default 是changeit


但是如果這個Server 不是你管的該怎麼辦?所以我覺得這個方法也不好用太麻煩,目前看到最好的就是這篇"Resolve : SunCertPathBuilderException: unable to find valid certification path to requested target ",這篇的教學就是教你利用InstallCert.java這隻程式,去匯入Self-sign SSL Certificate。

也就是透過程式去連線HTTPS的網站,然後自動把它的certificate 下載下來,然後再另外儲存成java可以看的懂得cert檔,最後再匯入client 端的 keystore,所以整個步驟就會簡化成:

下載 InstallCert (Code by Andreas Sterbenz, now available on code.google.com)

java InstallCert [host]:[port]
keytool -exportcert -keystore jssecacerts -storepass changeit -file output.cert
keytool -importcert -keystore [DESTINATION_KEYSTORE] -file output.cert



延伸閱讀: X.509  certificate filename extensions
Common filename extensions for X.509 certificates are:
  • .pem – (Privacy Enhanced Mail) Base64 encoded DER certificate, enclosed between “—–BEGIN CERTIFICATE—–” and “—–END CERTIFICATE—–”
  • .cer, .crt, .der – usually in binary DER form, but Base64-encoded certificates are common too (see .pem above)
  • .p7b, .p7c – PKCS#7 SignedData structure without data, just certificate(s) or CRL(s)
  • .p12 – PKCS#12, may contain certificate(s) (public) and private keys (password protected)
  • .pfx – PFX, predecessor of PKCS#12 (usually contains data in PKCS#12 format, e.g, with PFX files generated in IIS)
  • PKCS#7 is a standard for signing or encrypting (officially called “enveloping”) data. Since the certificate is needed to verify signed data, it is possible to include them in the SignedData structure. A .P7C file is a degenerated SignedData structure, without any data to sign.
  • PKCS#12 evolved from the PFX (Personal inFormation eXchange) standard and is used to exchange public and private objects in a single file.
張貼留言