標籤:安全 https tomcat httpsurlconnection sslsocket
在前面所講到的一些安全技術手段如:訊息摘要、加解密演算法、數位簽章和資料認證等,一般都不會由開發人員直接地去使用,而是經過了一定的封裝,甚至形成了某些安全性通訊協定,再暴露出一定的介面來供開發人員使用。因為直接使用這些安全手段,對開發人員的學習成本太高,需要深入瞭解底層實現才行,而直接使用封裝後暴露出來的介面就容易多了。
在這些封裝與協議的背後,很多都使用到了SSL/TSL協議,其中最常見的HTTPS就是在HTTP協議的基礎上加入了SSL/TLS協議形成的,來保障Web訪問安全性。SSL/TLS協議包含兩個協議:SSL(Secure Socket Layer,安全通訊端層)和TLS(Transport Layer Security,傳輸層安全)協議。SSL由Netscape公司研發,位於TCP/IP參考模型中的網路傳輸層,作為網路通訊提供安全及資料完完整性的一種安全性通訊協定。TLS是基於SSL協議之上的通用化協議,它同樣位於TCP/IP參考模型中的網路傳輸層,作為SSL協議的繼承者,成為下一代網路安全性與資料完整性安全性通訊協定。
SSL/TLS協議的具體實現與細節肯定是很複雜的,可以百度一下慢慢瞭解,下面主要列舉一下Java中經常遇見的與SSL/TLS有關的情況:
一、Tomcat中HTTPS協議的配置
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" SSLEnabled="true" maxThreads="150" scheme="https" secure="true" clientAuth="true" sslProtocol="TLS" keystoreFile="conf/serverKeyStore.jks" keystorePass="gitblit" truststoreFile="conf/caKeyStore.p12" truststorePass="gitblit" truststoreType="pkcs12"/>
屬性解釋:
port:協議監聽連接埠號碼
protocol: 協議實作類別
maxThreads: 最大線程數
SSLEnabled,scheme,secure,sslProtocol:基本上是固定配置
keystoreFile:伺服器KeyStore檔案路徑
keystorePass:伺服器KeyStore密碼
clientAuth:是否驗證用戶端
truststoreFile:伺服器信任KeyStore檔案路徑
truststorePass:伺服器信任KeyStore密碼
truststoreType:伺服器信任KeyStore類型,如不指定,預設為jks
在伺服器KeyStore檔案中最好只有一個條目,當然條目為KeyEntry類型,因為在該配置中無法配置被用於安全通訊的條目別名,如果有多個條目的話,伺服器會任意選行一個條目,就會造成所使用條目不確定的情況。伺服器信任KeyStore中儲存的條目是CertificateEntry類型,正確情況下裡面只儲存了伺服器信任的認證。一般說來這些認證都是有一根憑證頒發給用戶端使用的,而這個根憑證肯定是伺服器所有的,所以伺服器信任KeyStore中最好只儲存一個為用戶端頒發認證的根憑證,這樣只要信任了該根憑證,也就會信任該根憑證頒發的所有認證,利於添加新的用戶端。當然還有一種更笨的做法就是將根憑證頒發給用戶端使用的認證全部添加到伺服器信任KeyStore檔案中。如果clientAuth配置為false,即單向認證,只認證服務端而不,而不認證用戶端,那麼,truststoreFile,truststorePass,truststoreType這幾個屬性是用不上的,可不配置。
二、SSLSocket的使用
package com.xtayfjpk.security.jsse;import java.io.FileInputStream;import java.io.InputStream;import java.io.OutputStream;import java.security.KeyStore;import javax.net.ssl.KeyManagerFactory;import javax.net.ssl.SSLContext;import javax.net.ssl.SSLServerSocket;import javax.net.ssl.SSLServerSocketFactory;import javax.net.ssl.SSLSocket;import javax.net.ssl.SSLSocketFactory;import javax.net.ssl.TrustManagerFactory;import org.junit.Test;public class SSLSocketTest {@Testpublic void testRunServer() throws Exception {//擷取SSL上下文SSLContext context = SSLContext.getInstance("SSL");String keyStorePassword = "password";//擷取服務端KeyStoreKeyStore serverKeys = getKeyStore("serverKeys", "jks", keyStorePassword);//擷取KeyManagerFactoryKeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());String privateKeyPassword = "password";//初始化KeyManagerFactorykeyManagerFactory.init(serverKeys, privateKeyPassword.toCharArray());//擷取TrustManagerFactoryTrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());String trustStorePassword = "password";//擷取服務端信任KeyStoreKeyStore serverTrustKeys = getKeyStore("serverTrust", "jks", trustStorePassword);//初始化TrustManagerFactorytrustManagerFactory.init(serverTrustKeys);//初始化SSL上下文context.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);//使用SSL上下文擷取SSLServerSocketFactorySSLServerSocketFactory ssf = (SSLServerSocketFactory) context.getServerSocketFactory();//使用SSLServerSocketFactory建立出SSLServerSocket,並監聽指定連接埠SSLServerSocket serverSocket = (SSLServerSocket) ssf.createServerSocket(9999);//設定需要對用戶端進行認證serverSocket.setNeedClientAuth(true);while(true) {try {//等待用戶端串連SSLSocket socket = (SSLSocket) serverSocket.accept();InputStream in = socket.getInputStream();byte[] buf = new byte[1024];int len = in.read(buf);System.out.println(new String(buf, 0, len));in.close();} catch (Exception e) {e.printStackTrace();}}}@Testpublic void testRunClient() throws Exception {SSLContext context = SSLContext.getInstance("SSL");String keyStorePassword = "password";KeyStore clientKeys = SimpleSSLServer.getKeyStore("clientKeys", "jks", keyStorePassword);KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());String privateKeyPassword = "xtayfjpk";keyManagerFactory.init(clientKeys, privateKeyPassword.toCharArray());TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());String trustStorePassword = "password";KeyStore serverTrustKeys = getKeyStore("clientTrust", "jks", trustStorePassword);trustManagerFactory.init(serverTrustKeys);context.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);//使用SSL上下文建立SSLSocketSSLSocketFactory factory = (SSLSocketFactory) context.getSocketFactory();String host = "127.0.0.1";//建立SSLSocketSSLSocket socket = (SSLSocket) factory.createSocket(host, 9999);//與服務端進行通訊OutputStream outputStream = socket.getOutputStream();outputStream.write("xtayfjpk".getBytes());outputStream.flush();outputStream.close();socket.close();}public static KeyStore getKeyStore(String keyStorePath, String type, String keyStorePassword) throws Exception {KeyStore keyStore = KeyStore.getInstance(type);FileInputStream in = new FileInputStream(keyStorePath);keyStore.load(in, keyStorePassword.toCharArray());in.close();return keyStore;}}
能過上面的例子能夠發現,這和Tomcat的https協議的配置是一致的,因為Tomcat底層肯定也是使用SSLSocket來實現https協議的。SSLServerSocketFactory還有個getDefault方法直接返回一個SSLServerSocketFactory執行個體,如果使用此方法不需要建立與初始化SSLContext,有關SSL相關的配置設定在系統屬性中,設定系統屬性的方式有兩種:一是在虛擬機器啟動的時候設定,如下所示:
-Djavax.net.ssl.keyStore=clientKeys
-Djavax.net.ssl.keyStorePassword=password
-Djavax.net.ssl.trustStore=clientTrust
-Djavax.net.ssl.trustStorePassword=password
二是通過System.setProperty設定。
三、HttpsURLConnection的使用
package com.xtayfjpk.security.jsse;import java.io.BufferedReader;import java.io.FileInputStream;import java.io.InputStream;import java.io.InputStreamReader;import java.net.URL;import java.security.KeyStore;import javax.net.ssl.HttpsURLConnection;import javax.net.ssl.KeyManagerFactory;import javax.net.ssl.SSLContext;import javax.net.ssl.SSLSocketFactory;import javax.net.ssl.TrustManagerFactory;import org.junit.Test;public class HttpsUrlConnectionTest {@Testpublic void test() throws Exception {URL url = new URL("https://localhost:8443/");HttpsURLConnection connection = HttpsURLConnection.class.cast(url.openConnection());SSLContext context = SSLContext.getInstance("SSL");String keyStorePassword = "gitblit";KeyStore clientKeys = SimpleSSLServer.getKeyStore("D:\\java-app\\apache-tomcat-6.0.35\\conf\\clientKeyStore.p12", "pkcs12", keyStorePassword);KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());String privateKeyPassword = "gitblit";keyManagerFactory.init(clientKeys, privateKeyPassword.toCharArray());TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());String trustStorePassword = "gitblit";KeyStore serverTrustKeys = getKeyStore("D:\\java-app\\apache-tomcat-6.0.35\\conf\\clientTrustStore.jks", "jks", trustStorePassword);trustManagerFactory.init(serverTrustKeys);context.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);//使用SSL上下文建立SSLSocketSSLSocketFactory factory = (SSLSocketFactory) context.getSocketFactory();//如果服務端沒設定需要認證用戶端的話,可以不用設定SSLSocketFactoryconnection.setSSLSocketFactory(factory);connection.setDoInput(true);connection.setDoOutput(true);InputStream in = connection.getInputStream();String line = null;BufferedReader reader = new BufferedReader(new InputStreamReader(in));while((line=reader.readLine())!=null) {System.out.println(line);}}public static KeyStore getKeyStore(String keyStorePath, String type, String keyStorePassword) throws Exception {KeyStore keyStore = KeyStore.getInstance(type);FileInputStream in = new FileInputStream(keyStorePath);keyStore.load(in, keyStorePassword.toCharArray());in.close();return keyStore;}}
四、使服務端KeyStore支援多條目,並可指定被使用條目別名
在Tomcat中HTTPS協議的配置時說到,伺服器KeyStore最好只有一個條目,否則會造成所使用條目不確定的情況。但有時候你可能會想,該KeyStore中儲存多個條目,在啟動時通過配置條目別名來指定具體的條目,因為Tomcat中沒有提供別名配置支援,所以KeyStore中最好還是只有一個條目。但如果是自己寫SSLSocket程式,可能通過擴充來支援,如下:
package com.xtayfjpk.security;import java.net.Socket;import java.security.Principal;import java.security.PrivateKey;import java.security.cert.X509Certificate;import java.util.Arrays;import javax.net.ssl.SSLEngine;import javax.net.ssl.X509ExtendedKeyManager;import javax.net.ssl.X509KeyManager;public class MyAliasedX509ExtendKeyManager extends X509ExtendedKeyManager {private String keyAlias; private X509KeyManager keyManager; public MyAliasedX509ExtendKeyManager(String keyAlias, X509KeyManager keyManager) { this.keyAlias = keyAlias; this.keyManager = keyManager; } //提供給用戶端使用,用於選擇用戶端Keystore中的一個別名@Overridepublic String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) {String alias = keyAlias==null ? keyManager.chooseClientAlias(keyTypes, issuers, socket) : keyAlias;return alias;}//提供給服務端使用,用於選擇服務端Keystore中的一個別名@Overridepublic String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {String alias = keyAlias==null ? keyManager.chooseServerAlias(keyType, issuers, socket) : keyAlias;return alias;}@Overridepublic X509Certificate[] getCertificateChain(String alias) {return keyManager.getCertificateChain(alias);}@Overridepublic String[] getClientAliases(String keyType, Principal[] issuers) {return keyManager.getClientAliases(keyType, issuers);}@Overridepublic PrivateKey getPrivateKey(String alias) {return keyManager.getPrivateKey(alias);}@Overridepublic String[] getServerAliases(String keyType, Principal[] issuers) {return keyManager.getServerAliases(keyType, issuers);}@Overridepublic String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) {String alias = keyAlias==null ? super.chooseEngineClientAlias(keyType, issuers, engine) : keyAlias;return alias;}@Overridepublic String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) {String alias = keyAlias==null ? super.chooseEngineServerAlias(keyType, issuers, engine) : keyAlias;return alias;}}
通過繼承X509ExtendedKeyManager,自己實現一個KeyManager,別名通過構造方法傳入,然後使用自己的KeyManager實作類別封裝KeyManagerFactory建立的KeyManager即可通過別名達到指定KeyStore中被使用條目的目的。
Java安全之SSL/TLS