`
qingkangxu
  • 浏览: 43043 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

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

阅读更多

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连接。接收到来自客户端的基于SSLSocket连接之后就从Socket中获取到输入输出流用于和客户端交互。

  package security.ssl;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;

import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;

/**
 * 
 * Execute command: java -Djavax.net.ssl.keyStore=./server.key
 * -Djavax.net.ssl.keyStorePassword=changeit security.ssl.EchoServer
 * 
 */
public class EchoServer {

	public static void main(String[] args) {
		try {
			/**
			 * Get the default SSLServerSocketFactory, it will use the default
			 * default key manager(could be configured by javax.net.ssl.keyStore
			 * and javax.net.ssl.keyStorePassword properties) and default trust
			 * manager(could be configured by javax.net.ssl.trustStore and
			 * javax.net.ssl.trustStorePassword properties)
			 */
			SSLServerSocketFactory sslserversocketfactory = (SSLServerSocketFactory) SSLServerSocketFactory
					.getDefault();
			/**
			 * Create the ServerSocket for receiving connection from client, ,
			 * it roughly the same as non-ssl serversocket
			 */
			SSLServerSocket sslserversocket = (SSLServerSocket) sslserversocketfactory
					.createServerSocket(9999);

			System.out.println("Ready to Receive...");
			SSLSocket sslsocket = (SSLSocket) sslserversocket.accept();

			InputStream inputstream = sslsocket.getInputStream();
			InputStreamReader inputstreamreader = new InputStreamReader(
					inputstream);
			BufferedReader bufferedreader = new BufferedReader(
					inputstreamreader);

			String string = null;
			while ((string = bufferedreader.readLine()) != null) {
				System.out.println(string);
				System.out.flush();
			}
			System.out.println("End to Receive.");
		} catch (Exception exception) {
			exception.printStackTrace();
		}
	}
}

     客户端

    客户端实现代码如下,主要通过SSLSocketFactory.getDefault()获取到创建Client端的SSLSocket工厂类,然后直接获取和Server端的SSLSocket连接。客户端从基于SSLSocket连接获取到输入输出流用于和服务器端交互。

  package security.ssl;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

/**
 * 
 * Execute command: java -Djavax.net.ssl.trustStore=./clientca.key
 * -Djavax.net.ssl.trustStorePassword=changeit security.ssl.EchoClient
 *
 */
public class EchoClient {

	public static void main(String[] args) {
		try {
			/**
			 * Get the default SSLSocketFactory, it will use the default
			 * default key manager(could be configured by javax.net.ssl.keyStore
			 * and javax.net.ssl.keyStorePassword properties) and default trust
			 * manager(could be configured by javax.net.ssl.trustStore and
			 * javax.net.ssl.trustStorePassword properties)
			 */
			SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory
					.getDefault();
			/**
			 * Create SSLSocket by SSLSocketFactory, it roughly the same as non-ssl socket
			 */
			SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket(
					"localhost", 9999);

			InputStream inputstream = System.in;
			InputStreamReader inputstreamreader = new InputStreamReader(
					inputstream);
			BufferedReader bufferedreader = new BufferedReader(
					inputstreamreader);

			OutputStream outputstream = sslsocket.getOutputStream();
			OutputStreamWriter outputstreamwriter = new OutputStreamWriter(
					outputstream);
			BufferedWriter bufferedwriter = new BufferedWriter(
					outputstreamwriter);

			System.out.println("Please input the message...");
			String string = null;
			while ((string = bufferedreader.readLine()) != null) {
				bufferedwriter.write(string + '\n');
				bufferedwriter.flush();
			}
		} catch (Exception exception) {
			exception.printStackTrace();
		}
	}

}

   不管是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标识证书发行者,-keystorekeystore文件路径,注意因为测试用的是自签名证书,因此最好设置相同的-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

 

  以上证书生成之后,使用如下命令运行服务器端和客户端程序,注意根据实际情况调整keyStoretrustStore文件路径。

  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.keyStorejavax.net.ssl.trustStore)很难达到安全设置标准,因为往往不同的程序或者说不同的监听器需要不同的keysotre管理机制,对于此类情况,参照JDK Documentation中关于SSL Socket相关API依赖关系,可以很容易去实现不同监听器使用不同的keysotre。

   

  上图为Java提供的与SSL连接相关的类依赖图,而在本章的最开始给出的例子中知道JDK提供了默认的SSLServerSocketFactorySSLSocketFactory,因此我们没有使用到图中稍显复杂的这些类。不过,仔细分析,发现这个类图依赖关系实际上也很清晰。如果把目光聚集在中间的SSLContext类上就大大简化了我们的理解。首先往下是SSLContext可以创建SocketFactory,往上可以看出SSLContext可以使用自己的KeyManager(可理解为私钥证书管理器)TrustManager(可理解为信任证书管理器),而相应的Manager均有其工厂类,KeyStoreManagerFactoryTrsutStoreManagerFactory均可传递keystore配置(图中的Key Material就是某一个实际的keystore文件)

  Tomcat的HTTPS监听就是使用了上面的API为每个Connector单独配置证书的,如果我们不希望通过系统属性,而是更灵活的配置相关的SSLSocket工厂,可以参照如下

自定义用于创建基于SSL的socket工厂类

 

  基于对前面章节的掌握,大家应该知道,对于服务器端的Socket操作,主要通javax.net.ssl.SSLServerSocketFactory进行,客户端主要通过javax.net.ssl.SSLSocketFactory

以下是扩展了Tomcat用于配置HTTPS ConnectorSocket工厂类(MyJSSESocketFactory),同时支持服务器端和客户端的工厂类实现,大体思路是参照了上面的类图,最重要的代码就是下面几句

  // Create and init SSLContext

		SSLContext context = SSLContext.getInstance(protocol);
		context
				.init(getKeyManagers(keystoreType, keystoreProvider, algorithm,
						(String) properties.getProperty(KEY_KEY_ALIAS)),
						null, new SecureRandom());
		
		// 用于Server端的ServerSocketFactory获取
		serverSslProxy = context.getServerSocketFactory();
		// 用于Client端的SocketFactory获取
		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;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.Properties;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509KeyManager;

public class MyJSSESocketFactory {

	/**
	 * 用于Server端的ServerSocketFactory代理
	 */
	protected SSLServerSocketFactory serverSslProxy = null;
	/**
	 * 用于Client端的SocketFactory代理
	 */
	protected SSLSocketFactory clientSslProxy = null;
	protected boolean initialized;
	
	private String configFile = "ssl.properties";

	static String defaultProtocol = "TLS";
	static String defaultKeystoreType = "JKS";
	private static final String defaultKeystoreFile = ".keystore";
	private static final String defaultTruststoreFile = ".truststore";
	private static final String defaultKeyPass = "changeit";

	protected static Properties properties = new Properties();
	private static final String KEY_PREFIX = "ssl.";

	private static final String KEY_PROTOCOL = KEY_PREFIX + "protocol";
	private static final String KEY_ALGORITHM = KEY_PREFIX + "algorithm";

	private static final String KEY_KEYSTORE = KEY_PREFIX + "keystore";
	private static final String KEY_KEY_ALIAS = KEY_PREFIX + "keystoreAlias";
	private static final String KEY_KEYSTORE_TYPE = KEY_PREFIX + "keystoreType";
	private static final String KEY_KEYSTORE_PROVIDER = KEY_PREFIX
			+ "keystoreProvider";
	private static final String KEY_KEYPASS = KEY_PREFIX + "keypass";
	private static final String KEY_KEYSTORE_PASS = KEY_PREFIX + "keystorePass";

	private static final String KEY_TRUSTSTORE = KEY_PREFIX + "trustStore";
	private static final String KEY_TRUSTSTORE_TYPE = KEY_PREFIX
			+ "truststoreType";
	private static final String KEY_TRUSTSTORE_PASS = KEY_PREFIX
			+ "truststorePass";
	private static final String KEY_TRUSTSTORE_PROVIDER = KEY_PREFIX
			+ "truststoreProvider";
	private static final String KEY_TRUSTSTORE_ALGORITHM = KEY_PREFIX
			+ "truststoreAlgorithm";
	
	public MyJSSESocketFactory(){
	}
	
	public MyJSSESocketFactory(String configFile_){
		this.configFile = configFile_;
	}

	/**
	 * 使用代理Factory创建ServerSocket
	 */
	public ServerSocket createSocket(int port) throws Exception {
		if (!initialized)
			init();
		ServerSocket socket = serverSslProxy.createServerSocket(port);
		return socket;
	}

	/**
	 * 使用代理Factory创建ServerSocket
	 */
	public ServerSocket createSocket(int port, int backlog) throws Exception {
		if (!initialized)
			init();
		ServerSocket socket = serverSslProxy.createServerSocket(port, backlog);
		return socket;
	}

	/**
	 * 使用代理Factory创建ServerSocket
	 */
	public ServerSocket createSocket(int port, int backlog,
			InetAddress ifAddress) throws Exception {
		if (!initialized)
			init();
		ServerSocket socket = serverSslProxy.createServerSocket(port, backlog,
				ifAddress);
		return socket;
	}

	public Socket acceptSocket(ServerSocket socket) throws IOException {
		SSLSocket asock = null;
		try {
			asock = (SSLSocket) socket.accept();
		} catch (SSLException e) {
			throw new SocketException("SSL handshake error" + e.toString());
		}
		return asock;
	}

	/**
	 * 客户端Socket建立
	 */
	public Socket createSocket(String host,int port) throws Exception {
		if (!initialized)
			init();
		Socket socket = clientSslProxy.createSocket(host,port);
		return socket;
	}


	/**
	 * 初始化:
	 * 1,从configFile文件里边获取keystore相关配置(比如keystore和truststore路径、密码等信息)
	 * 2,调用SSLContext.getInstance,使用指定的protocol(默认为TLS)获取SSLContext
	 * 3, 构造KeyManager和TrustManager,并使用构造出来的Manager初始化第二步获取到的SSLContext
	 * 4,从SSLContext获取基于SSL的SocketFactory
	 */
	private void init() throws Exception {
		FileInputStream fileInputStream = null;
		try {
			File sslPropertyFile = new File(configFile);
			fileInputStream = new FileInputStream(sslPropertyFile);
			properties.load(fileInputStream);
		} catch (Exception e) {
			System.out.println("Because no "+ configFile +" config file, server will use default value.");
		} finally {
			try {
				fileInputStream.close();
			} catch (Exception e2) {
			}
		}

		String protocol = properties.getProperty(KEY_PROTOCOL);
		if(protocol == null || "".equals(protocol)){
			protocol = defaultProtocol;
		}

		// Certificate encoding algorithm (e.g., SunX509)
		String algorithm = (String) properties.getProperty(KEY_ALGORITHM);
		if (algorithm == null) {
			algorithm = KeyManagerFactory.getDefaultAlgorithm();
		}

		String keystoreType = (String) properties
				.getProperty(KEY_KEYSTORE_TYPE);
		if (keystoreType == null) {
			keystoreType = defaultKeystoreType;
		}

		String keystoreProvider = (String) properties
				.getProperty(KEY_KEYSTORE_PROVIDER);

		String trustAlgorithm = (String) properties
				.getProperty(KEY_TRUSTSTORE_ALGORITHM);
		if (trustAlgorithm == null) {
			trustAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
		}

		// Create and init SSLContext
		SSLContext context = SSLContext.getInstance(protocol);
		context
				.init(getKeyManagers(keystoreType, keystoreProvider, algorithm,
						(String) properties.getProperty(KEY_KEY_ALIAS)),
						null, new SecureRandom());
		
		// 用于Server端的ServerSocketFactory获取
		serverSslProxy = context.getServerSocketFactory();
		// 用于Client端的SocketFactory获取
		clientSslProxy = context.getSocketFactory();
	}

	/**
	 * 获取KeyManagers,KeyManagers根据keystore文件进行初始化,以便Socket能够获取到相应的证书
	 */
	protected KeyManager[] getKeyManagers(String keystoreType,
			String keystoreProvider, String algorithm, String keyAlias)
			throws Exception {

		KeyManager[] kms = null;
		String keystorePass = getKeystorePassword();

		/**
		 * 先获取到Keystore对象之后,使用KeyStore对KeyManagerFactory进行初始化,
		 * 然后从KeyManagerFactory获取KeyManagers
		 */
		KeyStore ks = getKeystore(keystoreType, keystoreProvider, keystorePass);
		if (keyAlias != null && !ks.isKeyEntry(keyAlias)) {
			throw new IOException("No specified keyAlias[" + keyAlias + "]");
		}

		KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
		kmf.init(ks, keystorePass.toCharArray());

		kms = kmf.getKeyManagers();
		if (keyAlias != null) {
			if (defaultKeystoreType.equals(keystoreType)) {
				keyAlias = keyAlias.toLowerCase();
			}
			for (int i = 0; i < kms.length; i++) {
				kms[i] = new JSSEKeyManager((X509KeyManager) kms[i], keyAlias);
			}
		}

		return kms;

	}

	/**
	 * 获取TrustManagers,TrustManagers根据truststore文件进行初始化,
	 * 以便Socket能够获取到相应的信任证书
	 */
	protected TrustManager[] getTrustManagers(String keystoreType,
			String keystoreProvider, String algorithm) throws Exception {

		TrustManager[] tms = null;

		/**
		 * 先获取到Keystore对象之后,使用KeyStore对TrustManagerFactory进行初始化,
		 * 然后从TrustManagerFactory获取TrustManagers
		 */
		KeyStore trustStore = getTrustStore(keystoreType, keystoreProvider);
		if (trustStore != null) {
			TrustManagerFactory tmf = TrustManagerFactory
					.getInstance(algorithm);
			tmf.init(trustStore);
			tms = tmf.getTrustManagers();
		}

		return tms;
	}

	/**
	 * 主要是调用getStore方法,传入keystore文件以供getStore方法解析.
	 */
	protected KeyStore getKeystore(String type, String provider, String pass)
			throws IOException {

		String keystoreFile = (String) properties.getProperty(KEY_KEYSTORE);
		if (keystoreFile == null)
			keystoreFile = defaultKeystoreFile;

		try {
			return getStore(type, provider, keystoreFile, pass);
		} catch (FileNotFoundException fnfe) {
			throw fnfe;
		} catch (IOException ioe) {
			throw ioe;
		}
	}

	/**
	 * keystore相关的keyPass和storePass密码.
	 */
	protected String getKeystorePassword() {
		String keyPass = (String) properties.get(KEY_KEYPASS);
		if (keyPass == null) {
			keyPass = defaultKeyPass;
		}
		String keystorePass = (String) properties.get(KEY_KEYSTORE_PASS);
		if (keystorePass == null) {
			keystorePass = keyPass;
		}
		return keystorePass;
	}

	/**
	 * 主要是调用getStore方法,传入truststore文件以供getStore方法解析.
	 */
	protected KeyStore getTrustStore(String keystoreType,
			String keystoreProvider) throws IOException {
		KeyStore trustStore = null;

		/**
		 * truststore文件优先级:指定的KEY_TRUSTSTORE属性->系统属性->当前路径<.truststore>文件
		 */
		String truststoreFile = (String) properties.getProperty(KEY_TRUSTSTORE);
		if (truststoreFile == null) {
			truststoreFile = System.getProperty("javax.net.ssl.trustStore");
		}
		if (truststoreFile == null) {
			truststoreFile = defaultTruststoreFile;
		}
		
		/**
		 * truststorePassword设置
		 */
		String truststorePassword = (String) properties
				.getProperty(KEY_TRUSTSTORE_PASS);
		if (truststorePassword == null) {
			truststorePassword = System
					.getProperty("javax.net.ssl.trustStorePassword");
		}
		if (truststorePassword == null) {
			truststorePassword = getKeystorePassword();
		}

		/**
		 *  trustStoreType设置优先级:指定的KEY_TRUSTSTORE_TYPE属性
		 *  ->系统属性javax.net.ssl.trustStoreType
		 *  -><keystoreType>
		 */
		String truststoreType = (String) properties
				.getProperty(KEY_TRUSTSTORE_TYPE);
		if (truststoreType == null) {
			truststoreType = System.getProperty("javax.net.ssl.trustStoreType");
		}
		if (truststoreType == null) {
			truststoreType = keystoreType;
		}

		/**
		 *  trustStoreType设置优先级:指定的KEY_TRUSTSTORE_PROVIDER属性
		 *  ->系统属性javax.net.ssl.trustStoreProvider
		 *  -><keystoreProvider>
		 */
		String truststoreProvider = (String) properties
				.getProperty(KEY_TRUSTSTORE_PROVIDER);
		if (truststoreProvider == null) {
			truststoreProvider = System
					.getProperty("javax.net.ssl.trustStoreProvider");
		}
		if (truststoreProvider == null) {
			truststoreProvider = keystoreProvider;
		}

		/**
		 * 通过调用getStore方法获取到keystore对象(也就是truststore对象)
		 */
		if (truststoreFile != null) {
			try {
				trustStore = getStore(truststoreType, truststoreProvider,
						truststoreFile, truststorePassword);
			} catch (FileNotFoundException fnfe) {
				throw fnfe;
			} catch (IOException ioe) {
				if (truststorePassword != null) {
					try {
						trustStore = getStore(truststoreType,
								truststoreProvider, truststoreFile, null);
						ioe = null;
					} catch (IOException ioe2) {
						ioe = ioe2;
					}
				}
				if (ioe != null) {
					throw ioe;
				}
			}
		}

		return trustStore;
	}

	/**
	 * 使用KeyStore的API从指定的keystore文件中构造出KeyStore对象,KeyStore对象用于初始化KeystoreManager和TrustManager.
	 */
	private KeyStore getStore(String type, String provider, String path,
			String pass) throws IOException {

		KeyStore ks = null;
		InputStream istream = null;
		try {
			if (provider == null) {
				ks = KeyStore.getInstance(type);
			} else {
				ks = KeyStore.getInstance(type, provider);
			}
			if (!("PKCS11".equalsIgnoreCase(type) || "".equalsIgnoreCase(path))) {
				File keyStoreFile = new File(path);
				istream = new FileInputStream(keyStoreFile);
			}

			char[] storePass = null;
			if (pass != null && !"".equals(pass)) {
				storePass = pass.toCharArray();
			}
			ks.load(istream, storePass);
		} catch (FileNotFoundException fnfe) {
			throw fnfe;
		} catch (IOException ioe) {
			throw ioe;
		} catch (Exception ex) {
			throw new IOException(ex);
		} finally {
			if (istream != null) {
				try {
					istream.close();
				} catch (IOException ioe) {
					// Do nothing
				}
			}
		}

		return ks;
	}
}

   package security.ssl;

import java.net.Socket;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509KeyManager;

/**
 * 本类只作为一个包装类:主要是根据指定的别名(alias)获取证书和私钥等信息
 *
 */
public final class JSSEKeyManager implements X509KeyManager {

	private X509KeyManager delegate;
	private String serverKeyAlias;

	public JSSEKeyManager(X509KeyManager mgr, String serverKeyAlias) {
		this.delegate = mgr;
		this.serverKeyAlias = serverKeyAlias;
	}

	public String chooseClientAlias(String[] keyType, Principal[] issuers,
			Socket socket) {
		return delegate.chooseClientAlias(keyType, issuers, socket);
	}

	public String chooseServerAlias(String keyType, Principal[] issuers,
			Socket socket) {
		return serverKeyAlias;
	}

	public X509Certificate[] getCertificateChain(String alias) {
		return delegate.getCertificateChain(alias);
	}

	public String[] getClientAliases(String keyType, Principal[] issuers) {
		return delegate.getClientAliases(keyType, issuers);
	}

	public String[] getServerAliases(String keyType, Principal[] issuers) {
		return delegate.getServerAliases(keyType, issuers);
	}

	public PrivateKey getPrivateKey(String alias) {
		return delegate.getPrivateKey(alias);
	}
}

 

    前面的EchoServer和EchoClient只需要很小的改动就能使用这个工厂类达到定制keytore克truststore的目的;其中EchoServer类需要把下面注释的语句删掉,使用没有注释的代码

   //SSLServerSocketFactory sslserversocketfactory = (SSLServerSocketFactory) SSLServerSocketFactory

			//		.getDefault();
			
			//使用自定义的SocketFactory
			MyJSSESocketFactory myJSSESocketFactory = new MyJSSESocketFactory();
			 
			/**
			 * Create the ServerSocket for receiving connection from client,
			 * it roughly the same as non-ssl serversocket
			 */
			//SSLServerSocket sslserversocket = (SSLServerSocket) sslserversocketfactory
			//		.createServerSocket(9999);
			SSLServerSocket sslserversocket = (SSLServerSocket) myJSSESocketFactory.createSocket(9999);

   EchoClient则同样做如下调整便可

   //SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory

			//		.getDefault();
			//使用自定义的SocketFactory
			MyJSSESocketFactory myJSSESocketFactory = new MyJSSESocketFactory();
			/**
			 * Create SSLSocket by SSLSocketFactory, it roughly the same as non-ssl socket
			 */
			//SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket(
			//		"localhost", 9999);
			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 {

        protocolVersion = ProtocolVersion.valueOf(s.getInt8(), s.getInt8());
        clnt_random = new RandomCookie(s);
        sessionId = new SessionId(s.getBytes8());
        cipherSuites = new CipherSuiteList(s);
        compression_methods = s.getBytes8();
        if (messageLength() != messageLength) {
            extensions = new HelloExtensions(s);
        }
    }

    void send(HandshakeOutStream s) throws IOException {
        s.putInt8(protocolVersion.major);
        s.putInt8(protocolVersion.minor);
        clnt_random.send(s);
        s.putBytes8(sessionId.getId());
        cipherSuites.send(s);
        s.putBytes8(compression_methods);
        extensions.send(s);
    }

   CertificateMsg

  该消息是双方都可能发送的,只要任意一方要求证书认证,那对方均需在握手中传递此消息。从send方法可看出,此消息包含了发送方所有的证书信息。

   CertificateMsg(HandshakeInStream input) throws IOException {

        int chainLen = input.getInt24();
        List<Certificate> v = new ArrayList<Certificate>(4);

        CertificateFactory cf = null;
        while (chainLen > 0) {
            byte[] cert = input.getBytes24();
            chainLen -= (3 + cert.length);
            try {
                if (cf == null) {
                    cf = CertificateFactory.getInstance("X.509");
                }
                v.add(cf.generateCertificate(new ByteArrayInputStream(cert)));
            } catch (CertificateException e) {
                throw (SSLProtocolException)new SSLProtocolException
                        (e.getMessage()).initCause(e);
            }
        }

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

    void send(HandshakeOutStream s) throws IOException {
        s.putInt24(messageLength() - 3);
        for (byte[] b : encodedChain) {
            s.putBytes24(b);
        }
    }
分享到:
评论

相关推荐

    JSP和Servlet那些事儿系列--初探HTTP服务器

    NULL 博文链接:https://qingkangxu.iteye.com/blog/1562033

    java servlet+jsp+mysql旅游网站系统 旅游景点管理系统 带论文和答辩文档.zip

    此项目为jsp制作的javaweb旅游网站项目,数据库使用mysql5。 技术介绍: 前端主要应用框架技术:jsp+jquery 后端主要应用框架技术:servlet+mysql 开发工具:Myeclipse或Eclipse、jdk1.8、tomcat8、mysql5.6或5.7...

    Head First Servlet JSP(清晰中文版).part2

    《Head First Servlets·JSP》(中文版)结合SCWCD考试大纲讲述了关于如何编写servlets和JSP代码,如何使用JSP表达式语言,如何部署Web应用,如何开发定制标记,以及会话状态、包装器、过滤器、企业设计模式等方面的...

    Head First Servlet JSP(清晰中文版).part3

    《Head First Servlets·JSP》(中文版)结合SCWCD考试大纲讲述了关于如何编写servlets和JSP代码,如何使用JSP表达式语言,如何部署Web应用,如何开发定制标记,以及会话状态、包装器、过滤器、企业设计模式等方面的...

    Head First Servlet JSP(清晰中文版).part1

    《Head First Servlets·JSP》(中文版)结合SCWCD考试大纲讲述了关于如何编写servlets和JSP代码,如何使用JSP表达式语言,如何部署Web应用,如何开发定制标记,以及会话状态、包装器、过滤器、企业设计模式等方面的...

    Head First Servlet JSP(清晰中文版).part4

    《Head First Servlets·JSP》(中文版)结合SCWCD考试大纲讲述了关于如何编写servlets和JSP代码,如何使用JSP表达式语言,如何部署Web应用,如何开发定制标记,以及会话状态、包装器、过滤器、企业设计模式等方面的...

    JavaWeb旅游管理系统(jsp+servlet+mysql)

    servlet,jsp,mysql,面向对象编程 二、系统功能 旅游网站设计主要用于实现旅游景点信息管理,基本功能包括:主界面模块设计,用户注册模块,旅游景点模块,酒店预订模块,后台管理模块等。本系统结构如下: (1)...

    jsp网络编程从基础到实践

    实例61 JSP与Servlet程序的对比分析 实例62 一个简单的servlet 实例63 用servlet获取表单数据 实例64 用servlet读写文件数据 实例65 用servlet访问数据库 实例66 一个简单的struts应用实例——用户登录 第10章...

    韩顺平sevlet,jsp视频教程知识点.txt

    (二) java EE程序员修炼成精的法门(卖油翁的故事) (三) jsp版本的用户管理系统演示 (四) jsp的概述(基本语法) (五) jsp的运行原理(还是hello.jsp) (六) jsp版的计算器 6.1 jsp中如何使用js(javascript)和css技术.. ...

    JSP网络编程从基础到实践

    实例34 随机读取文件程序示例 实例35 故事接龙 实例36 文件上传 实例37 在浏览器中打开文件 实例38 文件下载 实例39 用jspSmartUpload组件实现文件上传 实例40 应用jspSmartUpload组件进行...

    JSP网络编程从基础到实践的实例代码

    实例61 JSP与Servlet程序的对比分析 实例62 一个简单的servlet 实例63 用servlet获取表单数据 实例64 用servlet读写文件数据 实例65 用servlet访问数据库 实例66 一个简单的struts应用实例——用户登录 第10章...

    JSP网络编程从基础到实践 实例代码

    实例61 JSP与Servlet程序的对比分析 实例62 一个简单的servlet 实例63 用servlet获取表单数据 实例64 用servlet读写文件数据 实例65 用servlet访问数据库 实例66 一个简单的struts应用实例——用户登录 第10章...

    Head First Servlets and JSP (中文版) pdf 清晰完整版 part2

    Headfirst系列的书一直以讲述幽默,深入浅出著称,书中插图,小故事很多,把枯燥深奥的计算机知识讲得很趣味,很是风靡啊。这次终于有中文版了!

    Head First Servlets and JSP (中文版) pdf 清晰完整版 part1

    Headfirst系列的书一直以讲述幽默,深入浅出著称,书中插图,小故事很多,把枯燥深奥的计算机知识讲得很趣味,很是风靡啊。这次终于有中文版了!.

    NaturalBeverages:小型 uni 项目,饮料店 java servlet

    部分为德语的文本关于: - 一个虚构的网上商店和“天然”饮料的送货服务:啤酒、果汁、茶、咖啡、葡萄酒、蜂蜜酒- 专注于生物和“本地”特色菜(粗)用户故事: - 作为客户,我想浏览商店的饮料- 作为一个老年人,我...

    Tomcat6.0.45

    java Servlet、JSP、java和java语言表达 WebSocket规范下开发的java社区 过程。 Apache Tomcat软件是一个开放和参与 环境开发和发布下Apache许可证2版。的 Apache Tomcat项目是一个合作的最好的 开发者来自世界各地...

    最新Java面试题视频网盘,Java面试题84集、java面试专属及面试必问课程

    │ Java面试题20.jsp和Servlet的相同点和不同点?.mp4 │ Java面试题21.内置对象和四大作用域和页面传值.mp4 │ Java面试题22.Session和Cookie的区别.mp4 │ Java面试题23.mvc模式和mvc各部分的实现.mp4 │ Java面试...

    Java_Web编程新手自学手册.pdf

    第7~12章是提高篇,讲解了搭建Java Web开发环境,HTML,和XML,JSP指令和内置对象,标签和动作指令,Servlet详解,JavaBean组件技术等内容;第13~15章是数据库篇,讲解了数据库技术,使用JDBC连接数据库,数据库...

Global site tag (gtag.js) - Google Analytics