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

原文:http://qingkangxu.iteye.com/blog/1614053

《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"

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

  通过本文的阅读,希望你能知道为什么需要这样配置?配置是怎么被用于底层JDK安全相关API的?

一些SSL相关的基础概念

  对称加密:就是接收方和发送方都使用相同的密钥,双方都必须知道这个密钥,其存在的问题就是当密钥被截取之后一切都变得不安全了。

  非对称加密:就是加密和解密使用不同的密钥,缺点是因为算法复杂所以性能比对称加密低,但是安全性更高

  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监听和连接并没有太大的变化。之所以可以这么简单的实现基于SSL的socket通信,是因为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.                 null, new SecureRandom());  
  6.   
  7. // 用于Server端的ServerSocketFactory获取  
  8. serverSslProxy = context.getServerSocketFactory();  
  9. // 用于Client端的SocketFactory获取  
  10. clientSslProxy = context.getSocketFactory();  

 

  1,  必须通过指定协议(默认TLS)获得一个SSLContext实例

  2,  通过KeyManager和TrustManager初始化SSLContext实例,而KeyManager和TrustManager又是借助于指定的keystore和truststore文件进行初始化

  3,  从SSLContext实例实例中获得用于新建ServerSocket(服务器端)和Socket(客户端)的相关工厂类

  4, 有了以上三步操作,Server端进程和客户端进程就可以使用MyJSSESocketFactory创建ServerSocket和Socket

 

 

   具体的实现类如下,这个类基本就是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.                         null, new 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. }  
posted @ 2017-12-11 15:26  letmedown  阅读(270)  评论(0编辑  收藏  举报