原文出處: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
對稱加密:就是接收方和發送方都使用相同的密鑰,雙方都必須知道這個密鑰,其存在的問題就是當密鑰被截取以後一切都變得不安全了。socket
非對稱加密:就是加密和解密使用不一樣的密鑰,缺點是由於算法複雜因此性能比對稱加密低,可是安全性更高jsp
keystore(證書庫):看到store應該就能明白,其是一個庫文件用於存儲多個證書條目,沒有證書條目有alias(別名)進行標識,通常須要安全的一方(好比服務器端)須要設置keystore配置
truststore(信任證書庫):通常是客戶端用於標識本身信任的通訊源。
注意:不管是服務器端或者是客戶端均可以設置本身的keystore和truststore,只不過通常都是客戶端認證服務器端,服務器端不多須要認證客戶端的;就像咱們經過瀏覽器訪問一個HTTPS的地址,有時候會被彈出是否信任證書,而咱們不多提供本身的證書。
此外HTTPS不是單純的使用對稱加密或者是非對稱加密,HTTPS在握手階段使用非對稱加密方式握手,雙方最後協商出一個對稱加密密鑰,握手以後的數據傳輸使用對稱加密算法。
實現代碼以下,主要經過SSLServerSocketFactory. getDefault()方法獲取到建立Server端的SSLServerSocket工廠類,而後建立相應的SSLServerSocket監聽,接收客戶端的SSL Socket鏈接。接收到來自客戶端的基於SSL的Socket鏈接以後就從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鏈接。客戶端從基於SSL的Socket鏈接獲取到輸入輸出流用於和服務器端交互。
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監聽和鏈接並無太大的變化。之因此能夠這麼簡單的實現基於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通訊。
以上是簡單的基於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工廠,能夠參照以下
基於對前面章節的掌握,你們應該知道,對於服務器端的Socket操做,主要通javax.net.ssl.SSLServerSocketFactory進行,客戶端主要經過javax.net.ssl.SSLSocketFactory。
如下是擴展了Tomcat用於配置HTTPS Connector的Socket工廠類(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, 經過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;
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導出的證書生成的。
附件包含了測試證書及代碼!
以上完成了簡單的基於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是客戶端發起握手的初始請求。能夠看到其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);
}
該消息是雙方均可能發送的,只要任意一方要求證書認證,那對方均需在握手中傳遞此消息。從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);
}
}