JSP和Servlet那些事兒系列--HTTPS

原文出處:http://jackyrong.iteye.com/blog/1606444
java


JSP和Servlet那些事兒 》系列文章旨在闡述Servlet(Struts和Spring的MVC架構基礎)和JSP內部原理以及一些比較容易混淆的概念(好比forward和redirect區別、靜態include和<jsp:include標籤區別等)和使用,本文爲系列文章之啓蒙篇--初探HTTP服務器,基本能從本文中折射出Tomcat和Apache HTTPD等處理靜態文件的原理。算法

前言

  在前一篇博文中闡述了基於普通Socket的簡化版HTTP服務器實現:http://qingkangxu.iteye.com/blog/1562033,在深刻學習JSP和Servlet以前,HTTPS是一個很常見的名詞,也許不少人聽到HTTPS就有點懼怕,至少我本人是比較懼怕的,總以爲他很是的高深;其實就Java而言,若是但願以HTTPS的方式服務,本質就是在TCP鏈接之上加上基於SSL協議的握手,成功握手以後數據的接收和發送都是加密過的。你們熟知Tomcat,根據Tomcat的配置文檔能夠很容易地配置一個基於HTTPS的Connector,咱們在server.xml作以下配置以後,8443這個Socket監聽就必須使用HTTPS才能訪問。瀏覽器

 

    <Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"安全

               maxThreads="150" scheme="https" secure="true"服務器

               keystoreFile="${catalina.home}/conf/keystore" keystorePass="changeit"session

               clientAuth="false" sslProtocol="TLS" />架構

  經過本文的閱讀,但願你能知道爲何須要這樣配置?配置是怎麼被用於底層JDK安全相關API的?dom

一些SSL相關的基礎概念

  對稱加密:就是接收方和發送方都使用相同的密鑰,雙方都必須知道這個密鑰,其存在的問題就是當密鑰被截取以後一切都變得不安全了。socket

  非對稱加密:就是加密和解密使用不一樣的密鑰,缺點是由於算法複雜因此性能比對稱加密低,可是安全性更高jsp

  keystore(證書庫):看到store應該就能明白,其是一個庫文件用於存儲多個證書條目,沒有證書條目有alias(別名)進行標識,通常須要安全的一方(好比服務器端)須要設置keystore配置

  truststore(信任證書庫):通常是客戶端用於標識本身信任的通訊源。

  注意:不管是服務器端或者是客戶端均可以設置本身的keystore和truststore,只不過通常都是客戶端認證服務器端,服務器端不多須要認證客戶端的;就像咱們經過瀏覽器訪問一個HTTPS的地址,有時候會被彈出是否信任證書,而咱們不多提供本身的證書。

  此外HTTPS不是單純的使用對稱加密或者是非對稱加密,HTTPS在握手階段使用非對稱加密方式握手,雙方最後協商出一個對稱加密密鑰,握手以後的數據傳輸使用對稱加密算法。

最簡單的基於SSL的Socket實現
  服務器端

    實現代碼以下,主要經過SSLServerSocketFactory. getDefault()方法獲取到建立Server端的SSLServerSocket工廠類,而後建立相應的SSLServerSocket監聽,接收客戶端的SSL Socket鏈接。接收到來自客戶端的基於SSL的Socket鏈接以後就從Socket中獲取到輸入輸出流用於和客戶端交互。

  package security.ssl;

Java代碼  收藏代碼

  1. import java.io.BufferedReader;  

  2. import java.io.InputStream;  

  3. import java.io.InputStreamReader;  

  4.   

  5. import javax.net.ssl.SSLServerSocket;  

  6. import javax.net.ssl.SSLServerSocketFactory;  

  7. import javax.net.ssl.SSLSocket;  

  8.   

  9. /** 

  10.  *  

  11.  * Execute command: java -Djavax.net.ssl.keyStore=./server.key 

  12.  * -Djavax.net.ssl.keyStorePassword=changeit security.ssl.EchoServer 

  13.  *  

  14.  */  

  15. public class EchoServer {  

  16.   

  17.     public static void main(String[] args) {  

  18.         try {  

  19.             /** 

  20.              * Get the default SSLServerSocketFactory, it will use the default 

  21.              * default key manager(could be configured by javax.net.ssl.keyStore 

  22.              * and javax.net.ssl.keyStorePassword properties) and default trust 

  23.              * manager(could be configured by javax.net.ssl.trustStore and 

  24.              * javax.net.ssl.trustStorePassword properties) 

  25.              */  

  26.             SSLServerSocketFactory sslserversocketfactory = (SSLServerSocketFactory) SSLServerSocketFactory  

  27.                     .getDefault();  

  28.             /** 

  29.              * Create the ServerSocket for receiving connection from client, , 

  30.              * it roughly the same as non-ssl serversocket 

  31.              */  

  32.             SSLServerSocket sslserversocket = (SSLServerSocket) sslserversocketfactory  

  33.                     .createServerSocket(9999);  

  34.   

  35.             System.out.println("Ready to Receive...");  

  36.             SSLSocket sslsocket = (SSLSocket) sslserversocket.accept();  

  37.   

  38.             InputStream inputstream = sslsocket.getInputStream();  

  39.             InputStreamReader inputstreamreader = new InputStreamReader(  

  40.                     inputstream);  

  41.             BufferedReader bufferedreader = new BufferedReader(  

  42.                     inputstreamreader);  

  43.   

  44.             String string = null;  

  45.             while ((string = bufferedreader.readLine()) != null) {  

  46.                 System.out.println(string);  

  47.                 System.out.flush();  

  48.             }  

  49.             System.out.println("End to Receive.");  

  50.         } catch (Exception exception) {  

  51.             exception.printStackTrace();  

  52.         }  

  53.     }  

  54. }  

     客戶端

    客戶端實現代碼以下,主要經過SSLSocketFactory.getDefault()獲取到建立Client端的SSLSocket工廠類,而後直接獲取和Server端的SSLSocket鏈接。客戶端從基於SSL的Socket鏈接獲取到輸入輸出流用於和服務器端交互。

  package security.ssl;

Java代碼  收藏代碼

  1. import java.io.BufferedReader;  

  2. import java.io.BufferedWriter;  

  3. import java.io.InputStream;  

  4. import java.io.InputStreamReader;  

  5. import java.io.OutputStream;  

  6. import java.io.OutputStreamWriter;  

  7.   

  8. import javax.net.ssl.SSLSocket;  

  9. import javax.net.ssl.SSLSocketFactory;  

  10.   

  11. /** 

  12.  *  

  13.  * Execute command: java -Djavax.net.ssl.trustStore=./clientca.key 

  14.  * -Djavax.net.ssl.trustStorePassword=changeit security.ssl.EchoClient 

  15.  * 

  16.  */  

  17. public class EchoClient {  

  18.   

  19.     public static void main(String[] args) {  

  20.         try {  

  21.             /** 

  22.              * Get the default SSLSocketFactory, it will use the default 

  23.              * default key manager(could be configured by javax.net.ssl.keyStore 

  24.              * and javax.net.ssl.keyStorePassword properties) and default trust 

  25.              * manager(could be configured by javax.net.ssl.trustStore and 

  26.              * javax.net.ssl.trustStorePassword properties) 

  27.              */  

  28.             SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory  

  29.                     .getDefault();  

  30.             /** 

  31.              * Create SSLSocket by SSLSocketFactory, it roughly the same as non-ssl socket 

  32.              */  

  33.             SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket(  

  34.                     "localhost"9999);  

  35.   

  36.             InputStream inputstream = System.in;  

  37.             InputStreamReader inputstreamreader = new InputStreamReader(  

  38.                     inputstream);  

  39.             BufferedReader bufferedreader = new BufferedReader(  

  40.                     inputstreamreader);  

  41.   

  42.             OutputStream outputstream = sslsocket.getOutputStream();  

  43.             OutputStreamWriter outputstreamwriter = new OutputStreamWriter(  

  44.                     outputstream);  

  45.             BufferedWriter bufferedwriter = new BufferedWriter(  

  46.                     outputstreamwriter);  

  47.   

  48.             System.out.println("Please input the message...");  

  49.             String string = null;  

  50.             while ((string = bufferedreader.readLine()) != null) {  

  51.                 bufferedwriter.write(string + '\n');  

  52.                 bufferedwriter.flush();  

  53.             }  

  54.         } catch (Exception exception) {  

  55.             exception.printStackTrace();  

  56.         }  

  57.     }  

  58.   

  59. }  

   不論是Server端的實現仍是客戶端的實現,和普通 (SSL) Socket監聽和鏈接並無太大的變化。之因此能夠這麼簡單的實現基於SSLsocket通訊,是由於JDK自己提供了不少的默認行爲,好比對證書庫的管理,底層SSL握手處理等。

  測試以上代碼

    這裏對Keystore 概念再次進行說明

   ***證書庫(Keystore):這個包含了服務器端/客戶端用於存儲本身的私鑰/信任證書的證書庫,可使用JDK提供的keytool工具輔助生成或修改。本例中咱們不考慮服務器端要求驗證客戶端的狀況(實際環境中較少應用),所以咱們只須要生成服務器端用於存儲私鑰的keystore和客戶端用於存儲本身信任證書的keystore文件。

 

   ***服務器端用keystore製做:

     使用如下命令能夠生成一個用於服務器端的證書庫(其保存了服務器的私鑰):

    keytool -genkey -keyalg RSA -alias server -dname "CN=SSLServer, OU=Dev, O=ECS, L=BeiJing, ST=BeiJing, C=CN" -keystore server.key -storepass changeit -keypass changeit

    -genkey就是生成keystore的專用命令,-keyalg是加密算法,由於一個證書庫能夠保存不少個證書條目,所以咱們每生成一個證書條目的時候都須要指定一個-alias用於惟一標識,dname標識證書發行者,-keystore是keystore文件路徑,注意由於測試用的是自簽名證書,所以最好設置相同的-storepass和-keypass。

 

  ***客戶端用證書製做:

    客戶端信任的證書須要從服務器端的keystore作導出工做,如下命令先從服務器端的keystore文件中導出其證書(存儲了服務器端的公鑰),而後把證書導入到客戶端的keystore文件中(此時客戶端的這個keystore只包含信任證書)

  keytool -export -file server.cer -alias server -keystore server.key -storepass changeit

  keytool -import -alias serverKey -file server.cer -keystore clientca.key -storepass changeit -noprompt

 

  以上證書生成以後,使用以下命令運行服務器端和客戶端程序,注意根據實際狀況調整keyStore和trustStore文件路徑。

  java -Djavax.net.ssl.keyStore=./server.key -Djavax.net.ssl.keyStorePassword=changeit security.ssl.EchoServer運行服務器端監聽

 

  java -Djavax.net.ssl.trustStore=./clientca.key -Djavax.net.ssl.trustStorePassword=changeit security.ssl.EchoClient運行客戶端程序試圖與服務器端進行SSL通訊。

 簡單SSLSocket測試程序總結

    以上是簡單的基於SSL通訊的Socket測試程序,而對於Tomcat這樣的Web容器或者是包含EJB容器、JMS服務等大型的JavaEE應用服務器而言,單靠設置系統屬性(javax.net.ssl.keyStore、javax.net.ssl.trustStore等)很難達到安全設置標準,由於每每不一樣的程序或者說不一樣的監聽器須要不一樣的keysotre管理機制,對於此類狀況,參照JDK Documentation中關於SSL Socket相關API依賴關係,能夠很容易去實現不一樣監聽器使用不一樣的keysotre。

   

  上圖爲Java提供的與SSL鏈接相關的類依賴圖,而在本章的最開始給出的例子中知道JDK提供了默認的SSLServerSocketFactory和SSLSocketFactory,所以咱們沒有使用到圖中稍顯複雜的這些類。不過,仔細分析,發現這個類圖依賴關係實際上也很清晰。若是把目光彙集在中間的SSLContext類上就大大簡化了咱們的理解。首先往下是SSLContext能夠建立SocketFactory,往上能夠看出SSLContext可使用本身的KeyManager(可理解爲私鑰證書管理器)和TrustManager(可理解爲信任證書管理器),而相應的Manager均有其工廠類,KeyStoreManagerFactory和TrsutStoreManagerFactory都可傳遞keystore配置(圖中的Key Material就是某一個實際的keystore文件)。

  Tomcat的HTTPS監聽就是使用了上面的API爲每一個Connector單獨配置證書的,若是咱們不但願經過系統屬性,而是更靈活的配置相關的SSLSocket工廠,能夠參照以下

自定義用於建立基於SSL的socket工廠類

 

  基於對前面章節的掌握,你們應該知道,對於服務器端的Socket操做,主要通javax.net.ssl.SSLServerSocketFactory進行,客戶端主要經過javax.net.ssl.SSLSocketFactory

如下是擴展了Tomcat用於配置HTTPS Connector的Socket工廠類(MyJSSESocketFactory),同時支持服務器端和客戶端的工廠類實現,大致思路是參照了上面的類圖,最重要的代碼就是下面幾句

  // Create and init SSLContext

Java代碼  收藏代碼

  1. SSLContext context = SSLContext.getInstance(protocol);  

  2. context  

  3.         .init(getKeyManagers(keystoreType, keystoreProvider, algorithm,  

  4.                 (String) properties.getProperty(KEY_KEY_ALIAS)),  

  5.                 nullnew SecureRandom());  

  6.   

  7. // 用於Server端的ServerSocketFactory獲取  

  8. serverSslProxy = context.getServerSocketFactory();  

  9. // 用於Client端的SocketFactory獲取  

  10. clientSslProxy = context.getSocketFactory();  

 

  1,  必須經過指定協議(默認TLS)得到一個SSLContext實例

  2,  經過KeyManagerTrustManager初始化SSLContext實例,KeyManagerTrustManager又是藉助於指定的keystoretruststore文件進行初始化

  3,  SSLContext實例實例中得到用於新建ServerSocket(服務器端)Socket(客戶端)的相關工廠類

  4, 有了以上三步操做,Server端進程和客戶端進程就可使用MyJSSESocketFactory建立ServerSocketSocket

 

 

   具體的實現類以下,這個類基本就是Tomcat配置HTTPS相關屬性的縮減版,Tomcat的監聽Socket就是經過該類相似的createSocket方法,由SSLServerSocketFactory工廠所建立。

  package security.ssl;

Java代碼  收藏代碼

  1. import java.io.File;  

  2. import java.io.FileInputStream;  

  3. import java.io.FileNotFoundException;  

  4. import java.io.IOException;  

  5. import java.io.InputStream;  

  6. import java.net.InetAddress;  

  7. import java.net.ServerSocket;  

  8. import java.net.Socket;  

  9. import java.net.SocketException;  

  10. import java.security.KeyStore;  

  11. import java.security.SecureRandom;  

  12. import java.util.Properties;  

  13.   

  14. import javax.net.ssl.KeyManager;  

  15. import javax.net.ssl.KeyManagerFactory;  

  16. import javax.net.ssl.SSLContext;  

  17. import javax.net.ssl.SSLException;  

  18. import javax.net.ssl.SSLServerSocketFactory;  

  19. import javax.net.ssl.SSLSocket;  

  20. import javax.net.ssl.SSLSocketFactory;  

  21. import javax.net.ssl.TrustManager;  

  22. import javax.net.ssl.TrustManagerFactory;  

  23. import javax.net.ssl.X509KeyManager;  

  24.   

  25. public class MyJSSESocketFactory {  

  26.   

  27.     /** 

  28.      * 用於Server端的ServerSocketFactory代理 

  29.      */  

  30.     protected SSLServerSocketFactory serverSslProxy = null;  

  31.     /** 

  32.      * 用於Client端的SocketFactory代理 

  33.      */  

  34.     protected SSLSocketFactory clientSslProxy = null;  

  35.     protected boolean initialized;  

  36.       

  37.     private String configFile = "ssl.properties";  

  38.   

  39.     static String defaultProtocol = "TLS";  

  40.     static String defaultKeystoreType = "JKS";  

  41.     private static final String defaultKeystoreFile = ".keystore";  

  42.     private static final String defaultTruststoreFile = ".truststore";  

  43.     private static final String defaultKeyPass = "changeit";  

  44.   

  45.     protected static Properties properties = new Properties();  

  46.     private static final String KEY_PREFIX = "ssl.";  

  47.   

  48.     private static final String KEY_PROTOCOL = KEY_PREFIX + "protocol";  

  49.     private static final String KEY_ALGORITHM = KEY_PREFIX + "algorithm";  

  50.   

  51.     private static final String KEY_KEYSTORE = KEY_PREFIX + "keystore";  

  52.     private static final String KEY_KEY_ALIAS = KEY_PREFIX + "keystoreAlias";  

  53.     private static final String KEY_KEYSTORE_TYPE = KEY_PREFIX + "keystoreType";  

  54.     private static final String KEY_KEYSTORE_PROVIDER = KEY_PREFIX  

  55.             + "keystoreProvider";  

  56.     private static final String KEY_KEYPASS = KEY_PREFIX + "keypass";  

  57.     private static final String KEY_KEYSTORE_PASS = KEY_PREFIX + "keystorePass";  

  58.   

  59.     private static final String KEY_TRUSTSTORE = KEY_PREFIX + "trustStore";  

  60.     private static final String KEY_TRUSTSTORE_TYPE = KEY_PREFIX  

  61.             + "truststoreType";  

  62.     private static final String KEY_TRUSTSTORE_PASS = KEY_PREFIX  

  63.             + "truststorePass";  

  64.     private static final String KEY_TRUSTSTORE_PROVIDER = KEY_PREFIX  

  65.             + "truststoreProvider";  

  66.     private static final String KEY_TRUSTSTORE_ALGORITHM = KEY_PREFIX  

  67.             + "truststoreAlgorithm";  

  68.       

  69.     public MyJSSESocketFactory(){  

  70.     }  

  71.       

  72.     public MyJSSESocketFactory(String configFile_){  

  73.         this.configFile = configFile_;  

  74.     }  

  75.   

  76.     /** 

  77.      * 使用代理Factory建立ServerSocket 

  78.      */  

  79.     public ServerSocket createSocket(int port) throws Exception {  

  80.         if (!initialized)  

  81.             init();  

  82.         ServerSocket socket = serverSslProxy.createServerSocket(port);  

  83.         return socket;  

  84.     }  

  85.   

  86.     /** 

  87.      * 使用代理Factory建立ServerSocket 

  88.      */  

  89.     public ServerSocket createSocket(int port, int backlog) throws Exception {  

  90.         if (!initialized)  

  91.             init();  

  92.         ServerSocket socket = serverSslProxy.createServerSocket(port, backlog);  

  93.         return socket;  

  94.     }  

  95.   

  96.     /** 

  97.      * 使用代理Factory建立ServerSocket 

  98.      */  

  99.     public ServerSocket createSocket(int port, int backlog,  

  100.             InetAddress ifAddress) throws Exception {  

  101.         if (!initialized)  

  102.             init();  

  103.         ServerSocket socket = serverSslProxy.createServerSocket(port, backlog,  

  104.                 ifAddress);  

  105.         return socket;  

  106.     }  

  107.   

  108.     public Socket acceptSocket(ServerSocket socket) throws IOException {  

  109.         SSLSocket asock = null;  

  110.         try {  

  111.             asock = (SSLSocket) socket.accept();  

  112.         } catch (SSLException e) {  

  113.             throw new SocketException("SSL handshake error" + e.toString());  

  114.         }  

  115.         return asock;  

  116.     }  

  117.   

  118.     /** 

  119.      * 客戶端Socket創建 

  120.      */  

  121.     public Socket createSocket(String host,int port) throws Exception {  

  122.         if (!initialized)  

  123.             init();  

  124.         Socket socket = clientSslProxy.createSocket(host,port);  

  125.         return socket;  

  126.     }  

  127.   

  128.   

  129.     /** 

  130.      * 初始化: 

  131.      * 1,從configFile文件裏邊獲取keystore相關配置(好比keystore和truststore路徑、密碼等信息) 

  132.      * 2,調用SSLContext.getInstance,使用指定的protocol(默認爲TLS)獲取SSLContext 

  133.      * 3, 構造KeyManager和TrustManager,並使用構造出來的Manager初始化第二步獲取到的SSLContext 

  134.      * 4,從SSLContext獲取基於SSL的SocketFactory 

  135.      */  

  136.     private void init() throws Exception {  

  137.         FileInputStream fileInputStream = null;  

  138.         try {  

  139.             File sslPropertyFile = new File(configFile);  

  140.             fileInputStream = new FileInputStream(sslPropertyFile);  

  141.             properties.load(fileInputStream);  

  142.         } catch (Exception e) {  

  143.             System.out.println("Because no "+ configFile +" config file, server will use default value.");  

  144.         } finally {  

  145.             try {  

  146.                 fileInputStream.close();  

  147.             } catch (Exception e2) {  

  148.             }  

  149.         }  

  150.   

  151.         String protocol = properties.getProperty(KEY_PROTOCOL);  

  152.         if(protocol == null || "".equals(protocol)){  

  153.             protocol = defaultProtocol;  

  154.         }  

  155.   

  156.         // Certificate encoding algorithm (e.g., SunX509)  

  157.         String algorithm = (String) properties.getProperty(KEY_ALGORITHM);  

  158.         if (algorithm == null) {  

  159.             algorithm = KeyManagerFactory.getDefaultAlgorithm();  

  160.         }  

  161.   

  162.         String keystoreType = (String) properties  

  163.                 .getProperty(KEY_KEYSTORE_TYPE);  

  164.         if (keystoreType == null) {  

  165.             keystoreType = defaultKeystoreType;  

  166.         }  

  167.   

  168.         String keystoreProvider = (String) properties  

  169.                 .getProperty(KEY_KEYSTORE_PROVIDER);  

  170.   

  171.         String trustAlgorithm = (String) properties  

  172.                 .getProperty(KEY_TRUSTSTORE_ALGORITHM);  

  173.         if (trustAlgorithm == null) {  

  174.             trustAlgorithm = TrustManagerFactory.getDefaultAlgorithm();  

  175.         }  

  176.   

  177.         // Create and init SSLContext  

  178.         SSLContext context = SSLContext.getInstance(protocol);  

  179.         context  

  180.                 .init(getKeyManagers(keystoreType, keystoreProvider, algorithm,  

  181.                         (String) properties.getProperty(KEY_KEY_ALIAS)),  

  182.                         nullnew SecureRandom());  

  183.           

  184.         // 用於Server端的ServerSocketFactory獲取  

  185.         serverSslProxy = context.getServerSocketFactory();  

  186.         // 用於Client端的SocketFactory獲取  

  187.         clientSslProxy = context.getSocketFactory();  

  188.     }  

  189.   

  190.     /** 

  191.      * 獲取KeyManagers,KeyManagers根據keystore文件進行初始化,以便Socket可以獲取到相應的證書 

  192.      */  

  193.     protected KeyManager[] getKeyManagers(String keystoreType,  

  194.             String keystoreProvider, String algorithm, String keyAlias)  

  195.             throws Exception {  

  196.   

  197.         KeyManager[] kms = null;  

  198.         String keystorePass = getKeystorePassword();  

  199.   

  200.         /** 

  201.          * 先獲取到Keystore對象以後,使用KeyStore對KeyManagerFactory進行初始化, 

  202.          * 而後從KeyManagerFactory獲取KeyManagers 

  203.          */  

  204.         KeyStore ks = getKeystore(keystoreType, keystoreProvider, keystorePass);  

  205.         if (keyAlias != null && !ks.isKeyEntry(keyAlias)) {  

  206.             throw new IOException("No specified keyAlias[" + keyAlias + "]");  

  207.         }  

  208.   

  209.         KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);  

  210.         kmf.init(ks, keystorePass.toCharArray());  

  211.   

  212.         kms = kmf.getKeyManagers();  

  213.         if (keyAlias != null) {  

  214.             if (defaultKeystoreType.equals(keystoreType)) {  

  215.                 keyAlias = keyAlias.toLowerCase();  

  216.             }  

  217.             for (int i = 0; i < kms.length; i++) {  

  218.                 kms[i] = new JSSEKeyManager((X509KeyManager) kms[i], keyAlias);  

  219.             }  

  220.         }  

  221.   

  222.         return kms;  

  223.   

  224.     }  

  225.   

  226.     /** 

  227.      * 獲取TrustManagers,TrustManagers根據truststore文件進行初始化, 

  228.      * 以便Socket可以獲取到相應的信任證書 

  229.      */  

  230.     protected TrustManager[] getTrustManagers(String keystoreType,  

  231.             String keystoreProvider, String algorithm) throws Exception {  

  232.   

  233.         TrustManager[] tms = null;  

  234.   

  235.         /** 

  236.          * 先獲取到Keystore對象以後,使用KeyStore對TrustManagerFactory進行初始化, 

  237.          * 而後從TrustManagerFactory獲取TrustManagers 

  238.          */  

  239.         KeyStore trustStore = getTrustStore(keystoreType, keystoreProvider);  

  240.         if (trustStore != null) {  

  241.             TrustManagerFactory tmf = TrustManagerFactory  

  242.                     .getInstance(algorithm);  

  243.             tmf.init(trustStore);  

  244.             tms = tmf.getTrustManagers();  

  245.         }  

  246.   

  247.         return tms;  

  248.     }  

  249.   

  250.     /** 

  251.      * 主要是調用getStore方法,傳入keystore文件以供getStore方法解析. 

  252.      */  

  253.     protected KeyStore getKeystore(String type, String provider, String pass)  

  254.             throws IOException {  

  255.   

  256.         String keystoreFile = (String) properties.getProperty(KEY_KEYSTORE);  

  257.         if (keystoreFile == null)  

  258.             keystoreFile = defaultKeystoreFile;  

  259.   

  260.         try {  

  261.             return getStore(type, provider, keystoreFile, pass);  

  262.         } catch (FileNotFoundException fnfe) {  

  263.             throw fnfe;  

  264.         } catch (IOException ioe) {  

  265.             throw ioe;  

  266.         }  

  267.     }  

  268.   

  269.     /** 

  270.      * keystore相關的keyPass和storePass密碼. 

  271.      */  

  272.     protected String getKeystorePassword() {  

  273.         String keyPass = (String) properties.get(KEY_KEYPASS);  

  274.         if (keyPass == null) {  

  275.             keyPass = defaultKeyPass;  

  276.         }  

  277.         String keystorePass = (String) properties.get(KEY_KEYSTORE_PASS);  

  278.         if (keystorePass == null) {  

  279.             keystorePass = keyPass;  

  280.         }  

  281.         return keystorePass;  

  282.     }  

  283.   

  284.     /** 

  285.      * 主要是調用getStore方法,傳入truststore文件以供getStore方法解析. 

  286.      */  

  287.     protected KeyStore getTrustStore(String keystoreType,  

  288.             String keystoreProvider) throws IOException {  

  289.         KeyStore trustStore = null;  

  290.   

  291.         /** 

  292.          * truststore文件優先級:指定的KEY_TRUSTSTORE屬性->系統屬性->當前路徑<.truststore>文件 

  293.          */  

  294.         String truststoreFile = (String) properties.getProperty(KEY_TRUSTSTORE);  

  295.         if (truststoreFile == null) {  

  296.             truststoreFile = System.getProperty("javax.net.ssl.trustStore");  

  297.         }  

  298.         if (truststoreFile == null) {  

  299.             truststoreFile = defaultTruststoreFile;  

  300.         }  

  301.           

  302.         /** 

  303.          * truststorePassword設置 

  304.          */  

  305.         String truststorePassword = (String) properties  

  306.                 .getProperty(KEY_TRUSTSTORE_PASS);  

  307.         if (truststorePassword == null) {  

  308.             truststorePassword = System  

  309.                     .getProperty("javax.net.ssl.trustStorePassword");  

  310.         }  

  311.         if (truststorePassword == null) {  

  312.             truststorePassword = getKeystorePassword();  

  313.         }  

  314.   

  315.         /** 

  316.          *  trustStoreType設置優先級:指定的KEY_TRUSTSTORE_TYPE屬性 

  317.          *  ->系統屬性javax.net.ssl.trustStoreType 

  318.          *  -><keystoreType> 

  319.          */  

  320.         String truststoreType = (String) properties  

  321.                 .getProperty(KEY_TRUSTSTORE_TYPE);  

  322.         if (truststoreType == null) {  

  323.             truststoreType = System.getProperty("javax.net.ssl.trustStoreType");  

  324.         }  

  325.         if (truststoreType == null) {  

  326.             truststoreType = keystoreType;  

  327.         }  

  328.   

  329.         /** 

  330.          *  trustStoreType設置優先級:指定的KEY_TRUSTSTORE_PROVIDER屬性 

  331.          *  ->系統屬性javax.net.ssl.trustStoreProvider 

  332.          *  -><keystoreProvider> 

  333.          */  

  334.         String truststoreProvider = (String) properties  

  335.                 .getProperty(KEY_TRUSTSTORE_PROVIDER);  

  336.         if (truststoreProvider == null) {  

  337.             truststoreProvider = System  

  338.                     .getProperty("javax.net.ssl.trustStoreProvider");  

  339.         }  

  340.         if (truststoreProvider == null) {  

  341.             truststoreProvider = keystoreProvider;  

  342.         }  

  343.   

  344.         /** 

  345.          * 經過調用getStore方法獲取到keystore對象(也就是truststore對象) 

  346.          */  

  347.         if (truststoreFile != null) {  

  348.             try {  

  349.                 trustStore = getStore(truststoreType, truststoreProvider,  

  350.                         truststoreFile, truststorePassword);  

  351.             } catch (FileNotFoundException fnfe) {  

  352.                 throw fnfe;  

  353.             } catch (IOException ioe) {  

  354.                 if (truststorePassword != null) {  

  355.                     try {  

  356.                         trustStore = getStore(truststoreType,  

  357.                                 truststoreProvider, truststoreFile, null);  

  358.                         ioe = null;  

  359.                     } catch (IOException ioe2) {  

  360.                         ioe = ioe2;  

  361.                     }  

  362.                 }  

  363.                 if (ioe != null) {  

  364.                     throw ioe;  

  365.                 }  

  366.             }  

  367.         }  

  368.   

  369.         return trustStore;  

  370.     }  

  371.   

  372.     /** 

  373.      * 使用KeyStore的API從指定的keystore文件中構造出KeyStore對象,KeyStore對象用於初始化KeystoreManager和TrustManager. 

  374.      */  

  375.     private KeyStore getStore(String type, String provider, String path,  

  376.             String pass) throws IOException {  

  377.   

  378.         KeyStore ks = null;  

  379.         InputStream istream = null;  

  380.         try {  

  381.             if (provider == null) {  

  382.                 ks = KeyStore.getInstance(type);  

  383.             } else {  

  384.                 ks = KeyStore.getInstance(type, provider);  

  385.             }  

  386.             if (!("PKCS11".equalsIgnoreCase(type) || "".equalsIgnoreCase(path))) {  

  387.                 File keyStoreFile = new File(path);  

  388.                 istream = new FileInputStream(keyStoreFile);  

  389.             }  

  390.   

  391.             char[] storePass = null;  

  392.             if (pass != null && !"".equals(pass)) {  

  393.                 storePass = pass.toCharArray();  

  394.             }  

  395.             ks.load(istream, storePass);  

  396.         } catch (FileNotFoundException fnfe) {  

  397.             throw fnfe;  

  398.         } catch (IOException ioe) {  

  399.             throw ioe;  

  400.         } catch (Exception ex) {  

  401.             throw new IOException(ex);  

  402.         } finally {  

  403.             if (istream != null) {  

  404.                 try {  

  405.                     istream.close();  

  406.                 } catch (IOException ioe) {  

  407.                     // Do nothing  

  408.                 }  

  409.             }  

  410.         }  

  411.   

  412.         return ks;  

  413.     }  

  414. }  

   package security.ssl;

Java代碼  收藏代碼

  1. import java.net.Socket;  

  2. import java.security.Principal;  

  3. import java.security.PrivateKey;  

  4. import java.security.cert.X509Certificate;  

  5. import javax.net.ssl.X509KeyManager;  

  6.   

  7. /** 

  8.  * 本類只做爲一個包裝類:主要是根據指定的別名(alias)獲取證書和私鑰等信息 

  9.  * 

  10.  */  

  11. public final class JSSEKeyManager implements X509KeyManager {  

  12.   

  13.     private X509KeyManager delegate;  

  14.     private String serverKeyAlias;  

  15.   

  16.     public JSSEKeyManager(X509KeyManager mgr, String serverKeyAlias) {  

  17.         this.delegate = mgr;  

  18.         this.serverKeyAlias = serverKeyAlias;  

  19.     }  

  20.   

  21.     public String chooseClientAlias(String[] keyType, Principal[] issuers,  

  22.             Socket socket) {  

  23.         return delegate.chooseClientAlias(keyType, issuers, socket);  

  24.     }  

  25.   

  26.     public String chooseServerAlias(String keyType, Principal[] issuers,  

  27.             Socket socket) {  

  28.         return serverKeyAlias;  

  29.     }  

  30.   

  31.     public X509Certificate[] getCertificateChain(String alias) {  

  32.         return delegate.getCertificateChain(alias);  

  33.     }  

  34.   

  35.     public String[] getClientAliases(String keyType, Principal[] issuers) {  

  36.         return delegate.getClientAliases(keyType, issuers);  

  37.     }  

  38.   

  39.     public String[] getServerAliases(String keyType, Principal[] issuers) {  

  40.         return delegate.getServerAliases(keyType, issuers);  

  41.     }  

  42.   

  43.     public PrivateKey getPrivateKey(String alias) {  

  44.         return delegate.getPrivateKey(alias);  

  45.     }  

  46. }  

 

    前面的EchoServer和EchoClient只須要很小的改動就能使用這個工廠類達到定製keytore克truststore的目的;其中EchoServer類須要把下面註釋的語句刪掉,使用沒有註釋的代碼

  //SSLServerSocketFactory sslserversocketfactory = (SSLServerSocketFactory) SSLServerSocketFactory

Java代碼  收藏代碼

  1. //      .getDefault();  

  2.   

  3. //使用自定義的SocketFactory  

  4. MyJSSESocketFactory myJSSESocketFactory = new MyJSSESocketFactory();  

  5.    

  6. /** 

  7.  * Create the ServerSocket for receiving connection from client, 

  8.  * it roughly the same as non-ssl serversocket 

  9.  */  

  10. //SSLServerSocket sslserversocket = (SSLServerSocket) sslserversocketfactory  

  11. //      .createServerSocket(9999);  

  12. SSLServerSocket sslserversocket = (SSLServerSocket) myJSSESocketFactory.createSocket(9999);  

   EchoClient則一樣作以下調整即可

  //SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory

Java代碼  收藏代碼

  1. //      .getDefault();  

  2. //使用自定義的SocketFactory  

  3. MyJSSESocketFactory myJSSESocketFactory = new MyJSSESocketFactory();  

  4. /** 

  5.  * Create SSLSocket by SSLSocketFactory, it roughly the same as non-ssl socket 

  6.  */  

  7. //SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket(  

  8. //      "localhost", 9999);  

  9. SSLSocket sslsocket = (SSLSocket) myJSSESocketFactory.createSocket("localhost"9999);  

 

  若是須要對修改後的代碼進行測試,保證運行java的當前路徑有《.keystore》和《.truststore》兩個證書文件或者是自定義配置文件來指定keystore和truststore文件路徑、密碼等信息;此外,須要保證.truststore是經過.keystore導出的證書生成的。

  附件包含了測試證書及代碼!

 

SSLSocket握手分析

  以上完成了簡單的基於SSL的Socket監聽測試程序,以及自定義使用SSL的SocketFactory工廠類(基本雷同Tomcat的作法)。相比通常的Socket實現的HTTP監聽,基於SSL Socket實現的HTTP監聽,在接收到客戶端的鏈接請求到真正交互數據之間有不少次握手動做,以使雙方確認對方的身份。

 

   

  上圖爲SSL握手操做圖解,從Client端發起ClientHello請求以後到第13步握手完整狀態以後雙方纔能進行數據傳輸,固然其中某些步驟是可選的。

  在JDK中完成SSL握手的主要類爲:sun.security.ssl.ServerHandshaker、sun.security.ssl.ClientHandshaker、sun.security.ssl.HandshakeMessage;其中,兩個Handshaker分別是服務器和客戶端使用的,在handshake的過程當中傳遞的消息都是HandshakeMessage類的子類。如下只是象徵性的對兩個Message進行簡單說明。

  ClientHello

     ClientHello是客戶端發起握手的初始請求。能夠看到其send方法發送了其支持的最大和最小SSL協議版本以及支持的加密集(Cipher Suite)等信息。

   ClientHello(HandshakeInStream s, int messageLength) throws IOException {

Java代碼  收藏代碼

  1.     protocolVersion = ProtocolVersion.valueOf(s.getInt8(), s.getInt8());  

  2.     clnt_random = new RandomCookie(s);  

  3.     sessionId = new SessionId(s.getBytes8());  

  4.     cipherSuites = new CipherSuiteList(s);  

  5.     compression_methods = s.getBytes8();  

  6.     if (messageLength() != messageLength) {  

  7.         extensions = new HelloExtensions(s);  

  8.     }  

  9. }  

  10.   

  11. void send(HandshakeOutStream s) throws IOException {  

  12.     s.putInt8(protocolVersion.major);  

  13.     s.putInt8(protocolVersion.minor);  

  14.     clnt_random.send(s);  

  15.     s.putBytes8(sessionId.getId());  

  16.     cipherSuites.send(s);  

  17.     s.putBytes8(compression_methods);  

  18.     extensions.send(s);  

  19. }  

   CertificateMsg

  該消息是雙方均可能發送的,只要任意一方要求證書認證,那對方均需在握手中傳遞此消息。從send方法可看出,此消息包含了發送方全部的證書信息。

   CertificateMsg(HandshakeInStream input) throws IOException {

Java代碼  收藏代碼

  1.     int chainLen = input.getInt24();  

  2.     List<Certificate> v = new ArrayList<Certificate>(4);  

  3.   

  4.     CertificateFactory cf = null;  

  5.     while (chainLen > 0) {  

  6.         byte[] cert = input.getBytes24();  

  7.         chainLen -= (3 + cert.length);  

  8.         try {  

  9.             if (cf == null) {  

  10.                 cf = CertificateFactory.getInstance("X.509");  

  11.             }  

  12.             v.add(cf.generateCertificate(new ByteArrayInputStream(cert)));  

  13.         } catch (CertificateException e) {  

  14.             throw (SSLProtocolException)new SSLProtocolException  

  15.                     (e.getMessage()).initCause(e);  

  16.         }  

  17.     }  

  18.   

  19.     chain = v.toArray(new X509Certificate[v.size()]);  

  20. }  

  21.   

  22. void send(HandshakeOutStream s) throws IOException {  

  23.     s.putInt24(messageLength() - 3);  

  24.     for (byte[] b : encodedChain) {  

  25.         s.putBytes24(b);  

  26.     }  

  27. }  

相關文章
相關標籤/搜索