其中根據書上所說,雙向認證跟單向認證的差別在於,雙向認證會讓客戶端和伺服器端互相認證對方,單向認證只有認證伺服器(確認伺服器是預期的那個)而已。
PS. 參考資料中,比較建議參考的對象是 [3,5],實際上最後我成功的方法就是綜合 [3,5] 得到的。
在進行 Java 之前,首先要先在 Tomcat 上面設定 HTTPS 以及 TLS 所需要的簽章。
這裡會引用一些 [5] 的說明。特別提到 [5] 是因為 [3] 的過程其實我都不知道那些動作在幹嘛,直到看了 [5] 以後才慢慢抓到那些動作大概的意義。
我的環境如下。
伺服器:ubuntu 12.04 server + Sun JDK 6 Update 38 + Tomcat 6.0.36
其中我的 JDK 的路徑在 /usr/local/jvm/jdk1.6.0_38/。
1、Tomcat 設定
1.1、為伺服器產生簽章庫
1 |
sudo /usr/local/jvm/jdk1 .6.0_38 /bin/keytool -genkey - v - alias tomcat -keyalg RSA -validity 10000 -keystore . /tomcat .keystore -dname "CN=localhost,OU=my,O=my,L=Taipei,ST=Taiwan,c=TW" -storepass changeit -keypass changeit |
參數 -validity 表示這個簽章的有效期間,數字是以天為單位,因此指定 10000 表示有效期限為 10000 天。
參數 -dname 表示指定簽章中的相關資訊,如果不給這個參數的話,keytool 仍然會一個一個詢問。參數內容的意義轉載自 [4]:
1 2 3 4 5 6 |
CN:Common Name 名字與姓氏 OU:Organization Unit組織單位名稱 O:Organization組織名稱 L:Locality城市或區域名稱 ST:State州或省份名稱 C:Country國家名稱 |
PS. 如果使用的 CN 跟 Java 實際使用的網域不同時,Java 會吐以下的 Exception 而無法成功建立 HTTPS 連線。
1 |
java.security.cert.CertificateException: No subject alternative names present |
1.2、為客戶端產生簽章庫
1 |
sudo /usr/local/jvm/jdk1 .6.0_38 /bin/keytool -genkey - v - alias client -keyalg RSA -storetype PKCS12 -validity 10000 -keystore . /client .p12 -dname "CN=localhost,OU=my,O=my,L=Taipei,ST=Taiwan,c=TW" -storepass changeit -keypass changeit |
其中因為客戶端包含瀏覽器,為了要讓 IE 和 FireFox 可以匯入簽章,簽章必須要使用 PKCS12 格式。
另外客戶端使用的 CN 可以是任意值。
附註:如果想看簽章的內容,可以執行以下的 script,會顯示簽章所使用的演算法資訊。
1 |
openssl pkcs12 - in client.p12 -info -noout |
1.3、讓 Tomcat 伺服器信任產生的客戶端簽章
在這個步驟中,首先因為沒辦法直接將 PKCS12 格式的簽章庫導入,因此要先把簽章庫匯出成 CER 的檔案。
1 |
sudo /usr/local/jvm/jdk1 .6.0_38 /bin/keytool - export - alias client -keystore . /client .p12 -storetype PKCS12 -storepass changeit -rfc - file . /client .cer |
1 |
sudo /usr/local/jvm/jdk1 .6.0_38 /bin/keytool - import - file . /client .cer -keystore . /tomcat .keystore -storepass changeit |
1.4、讓客戶端信任伺服器的簽章
類似的步驟,反過來要讓客戶端把伺服器加入信任的簽章。
同樣的,也是要先把簽章庫匯出成為簽章,然後再用簽章產生一個 TrustStore 的檔案。這裡比較不同的是,使用匯入的指令時,我們是產生另一個檔案,而不是像步驟 1.3 那樣把簽章加入已經產生的 KeyStore。
1 |
sudo /usr/local/jvm/jdk1 .6.0_38 /bin/keytool - export - alias tomcat -keystore . /tomcat .keystore -storepass changeit -rfc - file . /tomcat .cer |
1 |
sudo /usr/local/jvm/jdk1 .6.0_38 /bin/keytool - import - alias tomcat - file . /tomcat .cer -keystore . /client .truststore -storepass changeit |
1.5、設定 Tomcat
簽章都產生完以後,就要設定 Tomcat 了。
首先在 Tomcat 的 server.xml 裡找到以下的片段,將它們的註解刪掉。
1 |
<Connector port= "443" protocol= "HTTP/1.1" SSLEnabled= "true" maxThreads= "150" scheme= "https" secure= "true" clientAuth= "false" sslProtocol= "TLS" /> |
1 2 3 |
<Connector port= "443" protocol= "HTTP/1.1" SSLEnabled= "true" maxThreads= "150" scheme= "https" secure= "true" clientAuth= "false" sslProtocol= "TLS" keystoreFile= "/usr/local/TOMCAT/key/tomcat.keystore" keystorePass= "changeit" truststoreFile= "/usr/local/TOMCAT/key/tomcat.keystore" truststorePass= "changeit" /> |
Java Client
在 Java 端的變動,其實只有在建立連線之前先設定 TLS,然後建立連線時改 cast 成 HttpsURLConnection。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
FileInputStream keyStoreStream = null ; FileInputStream trustStoreStream = null ; try { keyStoreStream = new FileInputStream( new File( "D:/client.p12" )); KeyStore clientStore = KeyStore.getInstance( "PKCS12" ); clientStore.load(keyStoreStream, "changeit" .toCharArray()); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(clientStore, "changeit" .toCharArray()); KeyManager[] kms = kmf.getKeyManagers(); KeyStore trustStore = KeyStore.getInstance( "JKS" ); trustStoreStream = new FileInputStream( new File( "D:/client.truststore" )); trustStore.load(trustStoreStream, "changeit" .toCharArray()); TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(trustStore); TrustManager[] tms = tmf.getTrustManagers(); SSLContext sslContext = SSLContext.getInstance( "TLS" ); sslContext.init(kms, tms, new SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory()); } catch (KeyStoreException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (CertificateException e) { e.printStackTrace(); } catch (UnrecoverableKeyException e) { e.printStackTrace(); } catch (KeyManagementException e) { e.printStackTrace(); } finally { if (keyStoreStream != null ) keyStoreStream.close(); if (trustStoreStream != null ) trustStoreStream.close(); } // Construct the connection. HttpsURLConnection httpConnection = (HttpsURLConnection) url.openConnection(); |
這裡有用到兩個檔案,分別是前面步驟 1.2 和 1.4 產生的 client.p12 和 client.truststore
這兩個檔案必須先從伺服器那邊取得,讓 Java 能夠在建立連線時使用這兩個伺服器產生的簽證檔案。
另外可以注意到最後一行,原本開啟連線時,是把 URLConnection cast 成 HttpURLConnection
但要用 HTTPS 時就要改成 cast 到 HttpsURLConnection。
連線開啟後,後面的使用方法都跟一般 HttpURLConnection 完全一樣了。
2013-03-25 補充:
在查了一些資料後,發現不管是單向認證還是雙向認證,都必須要讓客戶端先具有證書檔
但是現在被要求要像瀏覽器那樣,不需要證書就可以接受伺服器的自我認證。
這時就只能使用 [7] 回應中比較不建議使用的方法了。
在上面 Java Client 部分的程式碼中,4~21 行的部份置換成以下的程式碼,就可以跳過證書認證(即一律信任伺服器的認證)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
sslContext = SSLContext.getInstance( "TLS" ); // Create a trust manager that does not validate certificate chains TrustManager[] trustAllCerts = new TrustManager[] { new javax.net.ssl.X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { return null ; } public void checkClientTrusted(X509Certificate[] certs, String authType) {} public void checkServerTrusted(X509Certificate[] certs, String authType) {} } }; sslContext.init( null , trustAllCerts, new java.security.SecureRandom()); |
參考資料:
1、Tomcat配备SSL单向认证
2、Java SSL: unable to find valid certification path to requested target
3、Java Tomcat SSL 服务端/客户端双向认证(一)
4、JAVA数字证书及TOMCAT SSL支持配置说明
5、Tomcat 6中配置SSL双向认证
6、Java 2-way TLS/SSL (Client Certificates) and PKCS12 vs JKS KeyStores
7、telling java to accept self-signed ssl certificate
沒有留言:
張貼留言