當數據在網絡上傳播的時候,經過使用 SSL 對其進行加密和保護,JSSE 爲 Java 應用程序提供了安全的通訊。在本篇有關該技術的高級研究中,Java 中間件開發人員 Ian Parkinson 深刻研究了 JSSE API 較鮮爲人知的方面,爲您演示瞭如何圍繞 SSL 的一些限制進行編程。您將學習如何動態地選擇 KeyStore 和 TrustStore、放寬 JSSE 的密碼匹配要求,以及構建您本身定製的 KeyManager 實現。java
JSSE(Java 安全套接字擴展,Java Secure Socket Extension)使 Java 應用程序可以在因特網上使用 SSL 安全地進行通訊。因爲 developerWorks 已經提供了一篇涵蓋 JSSE 基本用法的教程(請參閱 參考資料),因此本文將集中闡述該技術的更高級用法。本文將演示如何使用 JSSE 接口定製 SSL 鏈接的屬性。算法
首先,咱們將開發一個很是簡單的安全客戶機/服務器聊天應用程序。在咱們構建該程序的客戶機端時,我將演示如何定製 KeyStore 和 TrustStore 文件,以便從客戶機的文件系統裝入它們。接着,咱們將着重說明證書和標識。經過從 KeyStore 選擇不一樣的證書,能夠將客戶機以不一樣的形式提供給不一樣的服務器。若是您的客戶機應用程序須要鏈接到多個對等方,或者甚至它須要冒充不一樣的用戶,這項高級的功能都特別有用。編程
因爲本文着重講述更高級的主題,所以假定您已經具有了 JSSE 的使用經驗。要運行示例,須要一個帶有正確安裝和配置 JSSE 提供程序的 Java SDK。J2SE 1.4 SDK 提供了已安裝和配置的 JSSE。若是您正在使用 J2SE 1.2 或 1.3,則須要獲取一個 JSSE 實現並安裝它。請參閱 參考資料下載 JSSE 擴展。數組
JSSE API 只是 J2SE 1.4 的一項標準,而且早期的 JSSE 實現之間存在略有不一樣的變體。本文的示例基於 1.4 API。必要的時候,我會強調使示例與 J2SE 1.2 和 1.3 的 Sun JSSE 實現協同工做所必需的更改。安全
在咱們深刻研究 JSSE 以前,先讓咱們熟悉一下將要使用的客戶機/服務器應用程序。SimpleSSLServer 和 SimpleSSLClient 是咱們的演示應用程序的兩個組件。爲了運行示例,須要在應用程序的每一端上設置好幾個 KeyStore 和 TrustStore 文件。特別是您將須要:服務器
接下來,下載本文隨附的 jar 文件。這些文件包含客戶機/服務器應用程序的源代碼和已編譯的版本,所以,只要把它們放到 CLASSPATH 中,就可使用了。網絡
回頁首app
要運行 SimpleSSLServer,咱們輸入以下(稍微冗長的)命令:dom
java -Djavax.net.ssl.keyStore=serverKeys -Djavax.net.ssl.keyStorePassword=password -Djavax.net.ssl.trustStore=serverTrust -Djavax.net.ssl.trustStorePassword=password SimpleSSLServer
能夠看到,咱們已指定了 KeyStore,用它來標識服務器,還指定了在 KeyStore 中設置的密碼。因爲服務器將須要客戶機認證,所以咱們也爲它提供了 TrustStore。經過指定 TrustStore,咱們確保 SSLSimpleServer 將信任由 SSLSimpleClient 提供的證書。服務器初始化它本身後,您將獲得下述報告:socket
SimpleSSLServer running on port 49152
以後,服務器將等待來自客戶機的鏈接。若是但願在另外一個端口上運行服務器,在命令的結尾處指定 -port xxx
,用選定的端口代替變量 xxx
。
接下來,設置應用程序的客戶機組件。從另外一個控制檯窗口輸入以下命令:
java -Djavax.net.ssl.keyStore=clientKeys -Djavax.net.ssl.keyStorePassword=password -Djavax.net.ssl.trustStore=clientTrust -Djavax.net.ssl.trustStorePassword=password SimpleSSLClient
缺省狀況下,客戶機將嘗試鏈接到運行在本地主機端口 49152 上的服務器。能夠在命令行上使用 -host
和 -port
參數更改主機。當客戶機已鏈接到服務器時,您會獲得消息:
Connected
與此同時,服務器將報告鏈接請求,並顯示由客戶機提供的區別名,以下所示:
1: New connection request 1: Request from CN=Bob, OU=developerWorks, O=IBM, L=Winchester, ST=Hampshire, C=UK
爲了測試新鏈接,試着向客戶機輸入一些文本,按 Return 鍵,並觀察服務器是否回顯文本。要殺死客戶機,在客戶機控制檯上按 Ctrl-C 鍵。服務器將以下所示記錄斷開鏈接:
1: Client disconnected
無需殺死服務器;在各類練習過程當中咱們只需保持服務器運行。
儘管本文餘下部分主要都是講述客戶機應用程序的,可是,查看一下服務器代碼仍然是很值得的。除了能夠了解服務器應用程序是如何工做外,您還能夠學會如何使用 HandshakeCompletedListener
接口檢索有關 SSL 鏈接的信息。
SimpleSSLServer.java
從三條 import 語句開始,以下所示:
import javax.net.ssl.*; import java.security.cert.*; import java.io.*;
javax.net.ssl
是三條語句中最重要的;它包含大多數核心 JSSE 類,咱們要用它處理任何和 SSL 有關的工做。java.security.cert
在您須要操做單獨的證書(在本文後面咱們將這樣作)時頗有用。java.io
是標準的 Java I/O 包。在本案例中,咱們將使用它來處理經過安全套接字接收和發送的數據。接下來, main()
方法檢查命令行中可選的 -port
參數。而後它獲取缺省的 SSLServerSocketFactory
,構造一個 SimpleSSLServer
對象,把工廠(factory)傳遞給構造器,而且啓動服務器,以下所示:
SSLServerSocketFactory ssf= (SSLServerSocketFactory)SSLServerSocketFactory.getDefault(); SimpleSSLServer server=new SimpleSSLServer(ssf, port); server.start();
因爲服務器是在單獨的線程上運行的,所以只要啓動並運行, main()
就退出。新的線程調用 run()
方法,這樣會建立一個 SSLServerSocket
,而且設置服務器以要求客戶機認證,以下所示:
SSLServerSocket serverSocket= (SSLServerSocket)serverSocketFactory.createServerSocket(port); serverSocket.setNeedClientAuth(true);
將它激活以後, run()
方法進行無限循環,等待來自客戶機的請求。每一個套接字都與 HandshakeCompletedListener
實現相關聯,後者用來顯示來自客戶機證書的區別名(DN)。套接字的 InputStream
封裝在一個 InputDisplayer
中,它做爲另外一個線程運行,而且未來自套接字的數據回顯到 System.out
。SimpleSSLServer 的主循環如清單 1 所示:
while (true) { String ident=String.valueOf(id++); // Wait for a connection request. SSLSocket socket=(SSLSocket)serverSocket.accept(); // We add in a HandshakeCompletedListener, which allows us to // peek at the certificate provided by the client. HandshakeCompletedListener hcl=new SimpleHandshakeListener(ident); socket.addHandshakeCompletedListener(hcl); InputStream in=socket.getInputStream(); new InputDisplayer(ident, in); }
咱們的 HandshakeCompletedListener
― SimpleHandshakeListener
提供了一個 handshakeCompleted()
方法的實現。當 SSL 握手階段完成時,該方法由 JSSE 基礎設施調用,而且傳遞(在 HandshakeCompletedEvent
對象中)有關鏈接的信息。咱們使用這個方法獲取並顯示客戶機的 DN,如清單 2 所示:
class SimpleHandshakeListener implements HandshakeCompletedListener { String ident; /** * Constructs a SimpleHandshakeListener with the given * identifier. * @param ident Used to identify output from this Listener. */ public SimpleHandshakeListener(String ident) { this.ident=ident; } /** Invoked upon SSL handshake completion. */ public void handshakeCompleted(HandshakeCompletedEvent event) { // Display the peer specified in the certificate. try { X509Certificate cert=(X509Certificate)event.getPeerCertificates()[0]; String peer=cert.getSubjectDN().getName(); System.out.println(ident+": Request from "+peer); } catch (SSLPeerUnverifiedException pue) { System.out.println(ident+": Peer unverified"); } } }
用紅色突出顯示的行是很是重要的兩行: getPeerCertificates
返回做爲X509Certificate
對象數組的證書鏈。這些證書對象創建對等方的(即客戶機的)標識。數組中的第一個是客戶機自己的證書;最後一個一般是 CA 證書。一旦咱們擁有了對等方的證書,咱們能夠獲取 DN 並將其顯示到 System.out
。 X509Certificate
是在包java.security.cert
中定義的。
咱們將研究的第一個客戶機應用程序根本不能作什麼。可是,在後面的示例中咱們會擴展它來闡述更高級的功能。設置 SimpleSSLClient 的目的是爲了方便地添加子類。打算覆蓋下面四個方法:
main()
固然是在從命令行運行類時被調用。對於每一個子類, main()
必須構造一個合適類的對象,並調用對象上的 runClient()
和 close()
方法。這些方法是在超類 ― SimpleSSLClient
上提供的,而且不打算被覆蓋。handleCommandLineOption()
和 displayUsage()
容許每一個子類在命令行上添加選項,而無需更新父類。它們都從 runClient()
方法調用。getSSLSocketFactory()
是一個有趣的方法。JSSE 安全套接字始終是從 SSLSocketFactory
對象構造的。經過構造一個定製的套接字工廠,咱們能夠定製 JSSE 的行爲。爲了未來練習的目的,每一個 SimpleSSLClient 子類都實現該方法,並相應定製 SSLSocketFactory
。目前,SimpleSSLClient 僅能理解 -host
和 -port
參數,這容許用戶把客戶機指向服務器。在這第一個基本示例中, getSSLSocketFactory
返回(JVM 範圍的)缺省工廠,以下所示:
protected SSLSocketFactory getSSLSocketFactory() throws IOException, GeneralSecurityException { return (SSLSocketFactory)SSLSocketFactory.getDefault(); }
從子類的 main()
方法調用的 runClient()
方法,負責處理命令行參數,而後從子類獲取 SSLSocketFactory
來使用。而後它使用 connect()
方法鏈接到服務器,而且使用 transmit()
方法在安全通道上開始傳輸數據。
connect()
方法至關簡單。在使用 SSLSocketFactory
鏈接到服務器以後,它調用安全套接字上的 startHandshake
。這迫使 JSSE 完成 SSL 握手階段,並於是觸發服務器端上的 HandshakeCompletedListener
。儘管 JSSE 確實會自動啓動握手,可是僅當數據首次經過套接字發送時它才這樣作。由於用戶在鍵盤上輸入消息以前咱們不會發送任何數據,可是咱們但願服務器當即報告鏈接,因此咱們須要使用startHandshake
強制進行握手。
transmit()
方法一樣至關簡單。它的首要任務把輸入源包裝到適當的 Reader
,以下所示:
BufferedReader reader=new BufferedReader( new InputStreamReader(in));
咱們使用 BufferedReader
,由於它將幫咱們把輸入分割成單獨的行。
接下來, transmit()
方法把輸出流 ― 在本案例中,由安全套接字提供 OutputStream
― 包裝到適當的 Writer
中。服務器但願文本是以 UTF-8 編碼的,所以咱們可讓 OutputStreamWriter
使用下列編碼:
writer=new OutputStreamWriter(socket.getOutputStream(), "UTF-8");
主循環很簡單;正如您在清單 3 中看到的,它看起來更象 SimpleSSLServer 中 InputDisplayer
的主循環:
boolean done=false; while (!done) { String line=reader.readLine(); if (line!=null) { writer.write(line); writer.write('\n'); writer.flush(); } else done=true; }
基本的 JSSE 服務器和客戶機代碼就只有這些。如今,咱們能夠繼續擴展 SimpleSSLClient,而且看看一些其它 getSSLSocketFactory
實現。
還記得咱們是如何運行 SimpleSSLClient 的嗎?命令以下:
java -Djavax.net.ssl.keyStore=clientKeys -Djavax.net.ssl.keyStorePassword=password -Djavax.net.ssl.trustStore=clientTrust -Djavax.net.ssl.trustStorePassword=password SimpleSSLClient
命令簡直太長了!幸運的是,該示例及接下來的示例將爲您演示如何設置一個帶有到 KeyStore 和 TrustStore 的硬編碼路徑的SSLSocketFactory
。除了減小上述命令的長度以外,您將學習的技術將容許您設置多個 SSLSocketFactory
對象,每一個對象都帶有不一樣的 KeyStore 和 TrustStore 設置。若是沒有這種配置,JVM 中的每一個安全鏈接必須使用相同的 KeyStore 和 TrustStore。儘管對於較小的應用程序而言這是能夠接受的,可是較大的應用程序可能須要鏈接到多個表明許多不一樣用戶的對等方。
對於第一個示例,咱們將使用示例應用程序 CustomKeyStoreClient(可在本文的源代碼中找到)來動態定義一個 KeyStore。在研究源代碼以前,讓咱們看看正在使用的 CustomKeyStoreClient。對於這個練習,咱們將指定 TrustStore 而不是 KeyStore。在 CustomKeyStoreClient 命令行上輸入下列參數,咱們將看到出現的結果:
java -Djavax.net.ssl.trustStore=clientTrust -Djavax.net.ssl.trustStorePassword=password CustomKeyStoreClient
假定客戶機鏈接良好,服務器將報告說提供的證書是有效的。鏈接成功,由於 CustomKeyStoreClient.java
已經硬編碼了 KeyStore 的名稱(clientKeys
)和密碼( password
)。若是您爲客戶機 KeyStore 選擇了另外的文件名或密碼,那麼可使用新的命令行選項 -ks
和 -kspass
來指定它們。
研究一下 CustomKeystoreClient.java
的源代碼, getSSLSocketFactory
作的第一件事是調用助手方法 getKeyManagers()
。稍後咱們將考慮這是如何工做的;目前只是註明它返回 KeyManager
對象數組,已經利用必需的 KeyStore 文件和密碼對其進行了設置。
protected SSLSocketFactory getSSLSocketFactory() throws IOException, GeneralSecurityException { // Call getKeyManagers to get suitable key managers KeyManager[] kms=getKeyManagers(); // Now construct a SSLContext using these KeyManagers. We // specify a null TrustManager and SecureRandom, indicating that the // defaults should be used. SSLContext context=SSLContext.getInstance("SSL"); context.init(kms, null, null); // Finally, we get a SocketFactory, and pass it to SimpleSSLClient. SSLSocketFactory ssf=context.getSocketFactory(); return ssf; }
得到 KeyManager
數組以後, getSSLSocketFactory
執行一些對全部 JSSE 定製一般都很重要的設置工做。爲了構造 SSLSocketFactory
,應用程序獲取一個 SSLContext
實例,對其進行初始化,而後使用 SSLContext
生成一個 SSLSocketFactory
。
當獲得 SSLContext
時,咱們指定 "SSL"
的協議;咱們也能夠在這放入特定的 SSL(或 TLS)協議版本,而且強制通訊在特定的級別發生。經過指定 "SSL"
,咱們容許 JSSE 缺省至它能支持的最高級別。
SSLContext.init
的第一個參數是要使用的 KeyManager
數組。第二個參數(這裏保留爲 null)相似於 TrustManager
對象數組,稍後咱們將使用它們。經過讓第二個參數爲 null,咱們告訴 JSSE 使用缺省的 TrustStore,它從 javax.net.ssl.trustStore
和javax.net.ssl.trustStorePassword
系統屬性挑選設置。第三個參數容許咱們覆蓋 JSSE 的隨機數生成器(RNG)。RNG 是 SSL 的一個敏感領域,誤用該參數會導致鏈接變得不安全。咱們讓該參數爲 null,這樣容許 JSSE 使用缺省的 ― 而且安全的!― SecureRandom
對象。
接下來,咱們將研究 getKeyManagers
如何裝入和初始化 KeyManagers
數組。先從清單 5 中的代碼開始,而後咱們將討論正在發生什麼。
protected KeyManager[] getKeyManagers() throws IOException, GeneralSecurityException { // First, get the default KeyManagerFactory. String alg=KeyManagerFactory.getDefaultAlgorithm(); KeyManagerFactory kmFact=KeyManagerFactory.getInstance(alg); // Next, set up the KeyStore to use. We need to load the file into // a KeyStore instance. FileInputStream fis=new FileInputStream(keyStore); KeyStore ks=KeyStore.getInstance("jks"); ks.load(fis, keyStorePassword.toCharArray()); fis.close(); // Now we initialize the TrustManagerFactory with this KeyStore kmFact.init(ks, keyStorePassword.toCharArray()); // And now get the TrustManagers KeyManager[] kms=kmFact.getKeyManagers(); return kms; }
首要工做是獲取 KeyManagerFactory
,可是要這樣作,咱們須要知道將使用哪一種算法。幸運的是,JSSE 使缺省的 KeyManagerFactory
算法可用。可使用 ssl.KeyManagerFactory.algorithm
安全性屬性配置缺省算法。
接下來, getKeyManagers()
方法裝入 KeyStore 文件。這其中包括從文件創建一個 InputStream
、獲取一個 KeyStore
實例,以及從InputStream
裝入 KeyStore
。除了 InputStream
, KeyStore
須要知道流的格式(咱們使用缺省的 "jks"
)和存儲密碼。存儲密碼必須做爲字符數組提供。
要說明的一個可能頗有用的竅門是, KeyStore.load
會獲取任何 InputStream
。您的應用程序能夠從任何地方構建這些流;除了文件,您能夠經過網絡、從移動設備獲取流,或者甚至直接生成流。
裝入 KeyStore
以後,咱們使用它來初始化之前建立的 KeyManagerFactory
。咱們須要再次指定一個密碼,此次是單獨的證書密碼。一般,對於 JSSE 而言,KeyStore 中的每一個證書都須要具有與 KeyStore 自己相同的密碼。本身構造 KeyManagerFactory
能夠克服這個限制。
KeyManagerFactory
初始化以後,它一般使用 getKeyManagers()
方法獲取相應的KeyManager
對象的數組。
對於 CustomKeyStoreClient 而言,咱們已經研究瞭如何從任意的位置(本文使用本地文件系統)裝入 KeyStore,以及如何讓證書和 KeyStore 自己使用不一樣的密碼。稍後咱們將研究如何容許 KeyStore 中的每一個證書擁有不一樣的密碼。儘管在本示例中咱們着重於客戶機端,可是,咱們能夠在服務器端使用相同的技術來構建適當的 SSLServerSocketFactory
對象。
覆蓋 JSSE 的缺省 TrustStore 很是相似於咱們剛纔用 KeyStore 所作的工做,這並不使人驚奇。咱們已經設置了 CustomTrustStoreClient(可在本文的源代碼中找到)來使用硬編碼的 KeyStore 和硬編碼的 TrustStore。開始運行它所需作的所有工做就是輸入命令: java CustomTrustStoreClient
。
CustomTrustStoreClient 但願 KeyStore 將是一個名爲 clientKeys
而且密碼爲 password
的文件。但願 TrustStore 將是一個名爲 clientTrust
,密碼爲 password
的文件。就象使用 CustomKeyStoreClient 同樣,可使用 -ks
、 -kspass
、 -ts
和 -tspass
參數覆蓋這些缺省值。
getSSLSocketFactory()
在許多方面與 CustomKeyStoreClient 中相同方法是同樣的。咱們甚至從 CustomKeyStoreClient 調用getKeyManagers()
方法來獲取與前面示例中相同的定製的 KeyManager
對象數組。可是這時 getSSLSocketFactory
還必須獲取一個定製的TrustManager
對象數組。在清單 6 中,咱們能夠看到 getSSLSocketFactory
如何使用助手方法 getTrustManagers()
獲取定製的TrustManager
對象:
protected SSLSocketFactory getSSLSocketFactory() throws IOException, GeneralSecurityException { // Call getTrustManagers to get suitable trust managers TrustManager[] tms=getTrustManagers(); // Call getKeyManagers (from CustomKeyStoreClient) to get suitable // key managers KeyManager[] kms=getKeyManagers(); // Next construct and initialize a SSLContext with the KeyStore and // the TrustStore. We use the default SecureRandom. SSLContext context=SSLContext.getInstance("SSL"); context.init(kms, tms, null); // Finally, we get a SocketFactory, and pass it to SimpleSSLClient. SSLSocketFactory ssf=context.getSocketFactory(); return ssf; }
這時,當初始化上下文時,咱們覆蓋了 KeyStore 和 TrustStore。可是,咱們仍然讓 JSSE 經過傳遞 null
做爲第三個參數來使用它缺省的SecureRandom
。
getTrustManagers
也很是相似於 CustomKeyStoreClient 的等價物一樣不足爲奇,如清單 7 所示:
protected TrustManager[] getTrustManagers() throws IOException, GeneralSecurityException { // First, get the default TrustManagerFactory. String alg=TrustManagerFactory.getDefaultAlgorithm(); TrustManagerFactory tmFact=TrustManagerFactory.getInstance(alg); // Next, set up the TrustStore to use. We need to load the file into // a KeyStore instance. FileInputStream fis=new FileInputStream(trustStore); KeyStore ks=KeyStore.getInstance("jks"); ks.load(fis, trustStorePassword.toCharArray()); fis.close(); // Now we initialize the TrustManagerFactory with this KeyStore tmFact.init(ks); // And now get the TrustManagers TrustManager[] tms=tmFact.getTrustManagers(); return tms; }
就象之前同樣, getTrustManagers()
方法首先根據缺省算法實例化一個 TrustManagerFactory
。而後將 TrustStore 文件裝入 KeyStore
對象 ― 是的,命名不大恰當 ― 而且初始化 TrustManagerFactory
。
跟 KeyStore 等價物不一樣,請注意,當初始化 TrustManagerFactory
時,無需提供密碼。不象私鑰,可信的證書無需利用單獨的密碼進行保護。
到目前爲止,咱們已經研究瞭如何動態地覆蓋 KeyStore 和 TrustStore。到這兩個示例都完成時,您應該很是清楚如何設置KeyManagerFactory
和 TrustManagerFactory
,並使用這些來「播種」一個 SSLContext
。最後的示例有點煩瑣:咱們將構建本身的KeyManager
實現。
當運行客戶機應用程序的之前版本時,您是否注意到了服務器顯示的是哪一個證書 DN?咱們故意設置客戶機 KeyStore 以得到兩個可接受的證書,一個用於 Alice,另外一個用於 Bob。在這個案例中,JSSE 將選擇任何一個它認爲合適的證書。在個人安裝中,彷佛始終選取 Bob 的證書,可是您的 JSSE 的行爲可能有所不一樣。
咱們的示例應用程序 ― SelectAliasClient 容許您選擇提供哪一個證書。由於咱們在 Keystore 中按照別名 alice
或 bob
命名了每一個證書,因此要選擇 Alice 的證書可輸入命令: java SelectAliasClient -alias alice
。
當客戶機鏈接而且 SSL 握手完成時,服務器將用以下所示進行響應:
1: New connection request 1: Request from CN=Alice, OU=developerWorks, O=IBM, L=Winchester, ST=Hampshire, C=UK
(或者建立 Alice 的證書時所選的任何值)。相似地,若是選擇 Bob 的證書,請輸入: java SelectAliasClient -alias bob
,服務器將報告下述信息:
2: New connection request 2: Request from CN=Bob, OU=developerWorks, O=IBM, L=Winchester, ST=Hampshire, C=UK
爲了強制選擇一個特殊的別名,咱們將編寫一個 X509KeyManager
實現, KeyManager
一般由 JSSE 使用來進行 SSL 通訊。咱們的實現將包含一個真正的 X509KeyManager
,而且簡單地經過它傳遞大多數的調用。它攔截的惟一方法是 chooseClientAlias()
;咱們的實現檢查以便了解所需的別名有效仍是無效,若是有效,則返回它。
在 SSL 握手階段, X509KeyManager
接口使用許多方法來檢索密鑰,而後使用它來標識對等方。在 參考資料部分能夠找到全部方法的參考。下列方法對於本練習很重要:
getClientAliases()
和 getServerAliases()
分別爲使用 SSLSocket
和 SSLServerSocket
提供了有效的別名數組。chooseClientAlias()
和 chooseServerAlias()
返回單個有效的別名。getCertificateChain()
和 getPrivateKey()
每一個都把別名做爲參數,並返回有關已標識證書的信息。控制流的工做以下所示:
chooseClientAlias
以發現要使用的別名。chooseClientAlias
在真實的 X509KeyManager
上調用 getClientAliases
來發現一個有效的別名列表,以便於它能檢查所需的別名是否有效。X509KeyManager
的 getCertificateChain
和 getPrivateKey
,X509KeyManager 讓調用能夠訪問被包裝的 KeyManager。KeyManager AliasForcingKeyManager()
的 chooseClientAlias()
方法實際上須要屢次調用 getClientAliases()
,一次對應一個 JSSE 安裝支持的密鑰類型,如清單 8 所示:
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) { // For each keyType, call getClientAliases on the base KeyManager // to find valid aliases. If our requested alias is found, select it // for return. boolean aliasFound=false; for (int i=0; i<keyType.length && !aliasFound; i++) { String[] validAliases=baseKM.getClientAliases(keyType[i], issuers); if (validAliases!=null) { for (int j=0; j<validAliases.length && !aliasFound; j++) { if (validAliases[j].equals(alias)) aliasFound=true; } } } if (aliasFound) return alias; else return null; }
AliasForcingKeyManager
須要 X509KeyManager
的其它五種方法的實現;這些只是調用它們在 baseKM
上的對應部分。
目前,它仍然使用 AliasForcingKeyManager
,而不是一般的 KeyManager
。這發生在 getSSLSocketFactory
中,它首先從其它示例中調用getKeyManagers
和 getTrustManagers
,可是接着將每一個從 getKeyManagers
返回的 KeyManager
封裝進一個 AliasForcingKeyManager
實例,如清單 9 所示:
protected SSLSocketFactory getSSLSocketFactory() throws IOException, GeneralSecurityException { // Call the superclasses to get suitable trust and key managers KeyManager[] kms=getKeyManagers(); TrustManager[] tms=getTrustManagers(); // If the alias has been specified, wrap recognized KeyManagers // in AliasForcingKeyManager instances. if (alias!=null) { for (int i=0; i<kms.length; i++) { // We can only deal with instances of X509KeyManager if (kms[i] instanceof X509KeyManager) kms[i]=new AliasForcingKeyManager((X509KeyManager)kms[i], alias); } } // Now construct a SSLContext using these (possibly wrapped) // KeyManagers, and the TrustManagers. We still use a null // SecureRandom, indicating that the defaults should be used. SSLContext context=SSLContext.getInstance("SSL"); context.init(kms, tms, null); // Finally, we get a SocketFactory, and pass it to SimpleSSLClient. SSLSocketFactory ssf=context.getSocketFactory(); return ssf; }
可使用本文探討的技術覆蓋 KeyManager
的任何方面。相似地,可使用它們代替TrustManager
,更改 JSSE 的機制以決定是否信任從遠程對等方流出的證書。
本文已經討論了至關多的技巧和技術,所以讓咱們以快速回顧來結束本文。如今您應當基本瞭解如何:
HandshakeCompletedListener
收集有關鏈接的信息SSLContext
獲取 SSLSocketFactory
在適當的地方,我還建議擴展這些技術以用於各類應用程序案例。在您本身的實現中封裝 X509KeyManager
的技巧可用於 JSSE 中的許多其它類,固然,利用 TrustStore
和 KeyStore
能夠作更有趣的事情,而不僅是裝入硬編碼的文件。
無論您如何選擇實現本文演示的高級 JSSE 定製,任何一個都不是隨便就能夠實現的。在調整 SSL 內部機理的時候,請牢記:一個錯誤就會導致您鏈接變得不安全,這很重要。
轉自:http://www.ibm.com/developerworks/cn/java/j-customssl/
http://lyb520320.iteye.com/blog/720478