`
qingkangxu
  • 浏览: 43541 次
  • 性别: 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服务器

    在IT行业中,JSP(JavaServer Pages)和Servlet是用于构建动态Web应用程序的两种核心技术。本篇文章将深入探讨这两者以及它们与HTTP服务器之间的关系。首先,我们需要理解HTTP(超文本传输协议)的基础知识,它是...

    Head First Servlet And JSP

    Head First 系列书籍的特点是使用直观的方法教授复杂的概念,通过故事、图形和其他非传统的教学工具来提高学习效率。《Head First Servlet And JSP》一书旨在帮助读者深入理解并熟练掌握 Servlet 和 JSP 的使用技巧...

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

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

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

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

    JSP servlet

    《JSP servlet HEAD FIRST》这本书在IT领域尤其是Java Web开发界引起了广泛的关注和高度的赞誉。本书采用了一种独特而有效的教学方法,旨在帮助读者深入理解JSP(Java Server Pages)和Servlet技术,这两种技术是...

    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应用,如何开发定制标记,以及会话状态、包装器、过滤器、企业设计模式等方面的...

    jsp web开发资源包

    当JSP页面首次被请求时,会被转换成一个Servlet类,并根据需要编译和加载到服务器中。 JSP Web开发资源包通常包括JSP教程、示例代码、API文档、开发工具(如IDE插件)、框架(如Spring MVC、Struts等)以及相关的...

    head_first_servlets_and_jsp_2nd_edition.pdf

    《Head First Servlets and JSP》第二版是一本深入浅出讲解Servlet和JSP技术的书籍,旨在帮助读者理解并掌握Java Web开发的基础知识。该书采用了一种独特的教学方法,通过轻松有趣的风格让学习过程更加高效和愉快。 ...

    《深入浅出Servlets and JSP第二版》.

    对于那些教授Servlet和JSP课程的教育者来说,如Philippe Maquet所述,他们曾经尝试过十多本不同的教科书,但最终发现只有HeadFirst系列的书籍能够满足他们的教学需求。这是因为HeadFirst系列书籍不仅内容全面,而且...

    JavaWeb项目:基于jsp+servlet+mysql实现的旅游管理系统【源码+数据库】

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

    Head First Servlets and JSP 2nd Edition .pdf

    - **Head First Servlets and JSP 2nd Edition**:本书是Head First系列中的一个版本,专注于Servlets和JavaServer Pages (JSP)技术的学习。作为第二版,意味着在第一版的基础上进行了更新和完善,通常会包含更多的...

    servlet 增删改查

    在本项目"Servlet增删改查"中,我们将重点探讨如何利用Servlet实现一个基于MVC(Model-View-Controller)架构的Web应用程序,用于管理几米漫画的故事内容。 首先,让我们了解MVC模式。MVC是一种设计模式,它将应用...

    jsp网络编程从基础到实践

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

    JSP2.0技术手册完整版附源码.rar

    通过这个完整的JSP 2.0技术手册和源码,开发者不仅可以系统地学习JSP 2.0的核心概念和技术,还能通过实践加深理解,提升开发能力。在阅读手册的同时,结合源码分析和动手实践,将是深入掌握JSP 2.0的最佳方式。

Global site tag (gtag.js) - Google Analytics