javascript
使用 JSSE 定制 SSL 连接的属性--转载
當(dāng)數(shù)據(jù)在網(wǎng)絡(luò)上傳播的時(shí)候,通過(guò)使用 SSL 對(duì)其進(jìn)行加密和保護(hù),JSSE 為 Java 應(yīng)用程序提供了安全的通信。在本篇有關(guān)該技術(shù)的高級(jí)研究中,Java 中間件開(kāi)發(fā)人員 Ian Parkinson 深入研究了 JSSE API 較不為人知的方面,為您演示了如何圍繞 SSL 的一些限制進(jìn)行編程。您將學(xué)習(xí)如何動(dòng)態(tài)地選擇 KeyStore 和 TrustStore、放寬 JSSE 的密碼匹配要求,以及構(gòu)建您自己定制的 KeyManager 實(shí)現(xiàn)。
JSSE(Java 安全套接字?jǐn)U展,Java Secure Socket Extension)使 Java 應(yīng)用程序能夠在因特網(wǎng)上使用 SSL 安全地進(jìn)行通信。由于 developerWorks 已經(jīng)提供了一篇涵蓋 JSSE 基本用法的教程(請(qǐng)參閱?參考資料),所以本文將集中闡述該技術(shù)的更高級(jí)用法。本文將演示如何使用 JSSE 接口定制 SSL 連接的屬性。
首先,我們將開(kāi)發(fā)一個(gè)非常簡(jiǎn)單的安全客戶(hù)機(jī)/服務(wù)器聊天應(yīng)用程序。在我們構(gòu)建該程序的客戶(hù)機(jī)端時(shí),我將演示如何定制 KeyStore 和 TrustStore 文件,以便從客戶(hù)機(jī)的文件系統(tǒng)裝入它們。接著,我們將著重說(shuō)明證書(shū)和標(biāo)識(shí)。通過(guò)從 KeyStore 選擇不同的證書(shū),可以將客戶(hù)機(jī)以不同的形式提供給不同的服務(wù)器。如果您的客戶(hù)機(jī)應(yīng)用程序需要連接到多個(gè)對(duì)等方,或者甚至它需要冒充不同的用戶(hù),這項(xiàng)高級(jí)的功能都特別有用。
由于本文著重講述更高級(jí)的主題,因此假定您已經(jīng)具備了 JSSE 的使用經(jīng)驗(yàn)。要運(yùn)行示例,需要一個(gè)帶有正確安裝和配置 JSSE 提供程序的 Java SDK。J2SE 1.4 SDK 提供了已安裝和配置的 JSSE。如果您正在使用 J2SE 1.2 或 1.3,則需要獲取一個(gè) JSSE 實(shí)現(xiàn)并安裝它。請(qǐng)參閱?參考資料下載 JSSE 擴(kuò)展。
JSSE API 只是 J2SE 1.4 的一項(xiàng)標(biāo)準(zhǔn),并且早期的 JSSE 實(shí)現(xiàn)之間存在略有不同的變體。本文的示例基于 1.4 API。必要的時(shí)候,我會(huì)強(qiáng)調(diào)使示例與 J2SE 1.2 和 1.3 的 Sun JSSE 實(shí)現(xiàn)協(xié)同工作所必需的更改。
入門(mén):設(shè)置
在我們深入研究 JSSE 之前,先讓我們熟悉一下將要使用的客戶(hù)機(jī)/服務(wù)器應(yīng)用程序。SimpleSSLServer 和 SimpleSSLClient 是我們的演示應(yīng)用程序的兩個(gè)組件。為了運(yùn)行示例,需要在應(yīng)用程序的每一端上設(shè)置好幾個(gè) KeyStore 和 TrustStore 文件。特別是您將需要:
- 稱(chēng)為 clientKeys 的用于客戶(hù)機(jī)的 KeyStore 文件,分別包含用于虛構(gòu)的通信者 Alice 和 Bob 的證書(shū)
- 稱(chēng)為 serverKeys 的用于服務(wù)器的 KeyStore 文件,包含一個(gè)用于服務(wù)器的證書(shū)
- 稱(chēng)為 clientTrust 的用于客戶(hù)機(jī)的 TrustStore 文件,包含服務(wù)器的證書(shū)
- 稱(chēng)為 serverTrust 的用于服務(wù)器的 TrustStore 文件,包含 Alice 和 Bob 的證書(shū)
接下來(lái),下載本文隨附的?jar 文件。這些文件包含客戶(hù)機(jī)/服務(wù)器應(yīng)用程序的源代碼和已編譯的版本,因此,只要把它們放到 CLASSPATH 中,就可以使用了。
回頁(yè)首
建立一個(gè)安全連接
要運(yùn)行 SimpleSSLServer,我們輸入如下(稍微冗長(zhǎng)的)命令:
java -Djavax.net.ssl.keyStore=serverKeys -Djavax.net.ssl.keyStorePassword=password -Djavax.net.ssl.trustStore=serverTrust -Djavax.net.ssl.trustStorePassword=password SimpleSSLServer可以看到,我們已指定了 KeyStore,用它來(lái)標(biāo)識(shí)服務(wù)器,還指定了在 KeyStore 中設(shè)置的密碼。由于服務(wù)器將需要客戶(hù)機(jī)認(rèn)證,因此我們也為它提供了 TrustStore。通過(guò)指定 TrustStore,我們確保 SSLSimpleServer 將信任由 SSLSimpleClient 提供的證書(shū)。服務(wù)器初始化它自己后,您將得到下述報(bào)告:
SimpleSSLServer running on port 49152之后,服務(wù)器將等待來(lái)自客戶(hù)機(jī)的連接。如果希望在另一個(gè)端口上運(yùn)行服務(wù)器,在命令的結(jié)尾處指定?-port xxx?,用選定的端口代替變量?xxx。
接下來(lái),設(shè)置應(yīng)用程序的客戶(hù)機(jī)組件。從另一個(gè)控制臺(tái)窗口輸入如下命令:
java -Djavax.net.ssl.keyStore=clientKeys -Djavax.net.ssl.keyStorePassword=password -Djavax.net.ssl.trustStore=clientTrust -Djavax.net.ssl.trustStorePassword=password SimpleSSLClient缺省情況下,客戶(hù)機(jī)將嘗試連接到運(yùn)行在本地主機(jī)端口 49152 上的服務(wù)器。可以在命令行上使用?-host?和?-port?參數(shù)更改主機(jī)。當(dāng)客戶(hù)機(jī)已連接到服務(wù)器時(shí),您會(huì)得到消息:
Connected與此同時(shí),服務(wù)器將報(bào)告連接請(qǐng)求,并顯示由客戶(hù)機(jī)提供的區(qū)別名,如下所示:
1: New connection request 1: Request from CN=Bob, OU=developerWorks, O=IBM, L=Winchester, ST=Hampshire, C=UK為了測(cè)試新連接,試著向客戶(hù)機(jī)輸入一些文本,按 Return 鍵,并觀察服務(wù)器是否回顯文本。要?dú)⑺揽蛻?hù)機(jī),在客戶(hù)機(jī)控制臺(tái)上按 Ctrl-C 鍵。服務(wù)器將如下所示記錄斷開(kāi)連接:
1: Client disconnected無(wú)需殺死服務(wù)器;在各種練習(xí)過(guò)程中我們只需保持服務(wù)器運(yùn)行。
回頁(yè)首
SimpleSSLServer 內(nèi)幕
盡管本文余下部分主要都是講述客戶(hù)機(jī)應(yīng)用程序的,但是,查看一下服務(wù)器代碼仍然是很值得的。除了可以了解服務(wù)器應(yīng)用程序是如何工作外,您還可以學(xué)會(huì)如何使用?HandshakeCompletedListener?接口檢索有關(guān) SSL 連接的信息。
SimpleSSLServer.java?從三條 import 語(yǔ)句開(kāi)始,如下所示:
import javax.net.ssl.*; import java.security.cert.*; import java.io.*;- javax.net.ssl?是三條語(yǔ)句中最重要的;它包含大多數(shù)核心 JSSE 類(lèi),我們要用它處理任何和 SSL 有關(guān)的工作。
- java.security.cert?在您需要操作單獨(dú)的證書(shū)(在本文后面我們將這樣做)時(shí)很有用。
- java.io?是標(biāo)準(zhǔn)的 Java I/O 包。在本案例中,我們將使用它來(lái)處理通過(guò)安全套接字接收和發(fā)送的數(shù)據(jù)。
接下來(lái),?main()?方法檢查命令行中可選的?-port?參數(shù)。然后它獲取缺省的?SSLServerSocketFactory?,構(gòu)造一個(gè)?SimpleSSLServer?對(duì)象,把工廠(factory)傳遞給構(gòu)造器,并且啟動(dòng)服務(wù)器,如下所示:
SSLServerSocketFactory ssf=(SSLServerSocketFactory)SSLServerSocketFactory.getDefault(); SimpleSSLServer server=new SimpleSSLServer(ssf, port); server.start();由于服務(wù)器是在單獨(dú)的線程上運(yùn)行的,因此只要啟動(dòng)并運(yùn)行,?main()?就退出。新的線程調(diào)用?run()?方法,這樣會(huì)創(chuàng)建一個(gè)?SSLServerSocket,并且設(shè)置服務(wù)器以要求客戶(hù)機(jī)認(rèn)證,如下所示:
SSLServerSocket serverSocket=(SSLServerSocket)serverSocketFactory.createServerSocket(port); serverSocket.setNeedClientAuth(true);HandshakeCompletedListener
將它激活之后,?run()?方法進(jìn)行無(wú)限循環(huán),等待來(lái)自客戶(hù)機(jī)的請(qǐng)求。每個(gè)套接字都與?HandshakeCompletedListener?實(shí)現(xiàn)相關(guān)聯(lián),后者用來(lái)顯示來(lái)自客戶(hù)機(jī)證書(shū)的區(qū)別名(DN)。套接字的?InputStream?封裝在一個(gè)?InputDisplayer?中,它作為另一個(gè)線程運(yùn)行,并且將來(lái)自套接字的數(shù)據(jù)回顯到?System.out?。SimpleSSLServer 的主循環(huán)如清單 1 所示:
清單 1. SimpleSSLServer 主循環(huán)
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?提供了一個(gè)?handshakeCompleted()?方法的實(shí)現(xiàn)。當(dāng) SSL 握手階段完成時(shí),該方法由 JSSE 基礎(chǔ)設(shè)施調(diào)用,并且傳遞(在?HandshakeCompletedEvent?對(duì)象中)有關(guān)連接的信息。我們使用這個(gè)方法獲取并顯示客戶(hù)機(jī)的 DN,如清單 2 所示:
清單 2. SimpleHandshakeListener
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");}} }在 J2SE 1.2 或 1.3 上運(yùn)行服務(wù)器應(yīng)用程序
如果您正在 J2SE 1.2 或 1.3 上運(yùn)行 SimpleSSLServer 應(yīng)用程序,則需要使用一個(gè)略微不同的(并且目前已過(guò)時(shí)的)JSSE API。不是導(dǎo)入?java.security.cert?,而是使用?javax.security.cert?。該包還包含稱(chēng)為X509Certificate?的類(lèi);但是,為了從HandshakeCompletedEvent?獲得證書(shū)對(duì)象數(shù)組,必須使用?getPeerCertificateChain()?方法,而不是getPeerCertificates()?方法。
用紅色突出顯示的行是非常重要的兩行:?getPeerCertificates?返回作為X509Certificate?對(duì)象數(shù)組的證書(shū)鏈。這些證書(shū)對(duì)象建立對(duì)等方的(即客戶(hù)機(jī)的)標(biāo)識(shí)。數(shù)組中的第一個(gè)是客戶(hù)機(jī)本身的證書(shū);最后一個(gè)通常是 CA 證書(shū)。一旦我們擁有了對(duì)等方的證書(shū),我們可以獲取 DN 并將其顯示到?System.out?。?X509Certificate?是在包java.security.cert?中定義的。
回頁(yè)首
SimpleSSLClient 內(nèi)幕
我們將研究的第一個(gè)客戶(hù)機(jī)應(yīng)用程序根本不能做什么。但是,在后面的示例中我們會(huì)擴(kuò)展它來(lái)闡述更高級(jí)的功能。設(shè)置 SimpleSSLClient 的目的是為了方便地添加子類(lèi)。打算覆蓋下面四個(gè)方法:
- main()?當(dāng)然是在從命令行運(yùn)行類(lèi)時(shí)被調(diào)用。對(duì)于每個(gè)子類(lèi),?main()?必須構(gòu)造一個(gè)合適類(lèi)的對(duì)象,并調(diào)用對(duì)象上的?runClient()?和?close()方法。這些方法是在超類(lèi) ―?SimpleSSLClient?上提供的,并且不打算被覆蓋。
- handleCommandLineOption()?和?displayUsage()?允許每個(gè)子類(lèi)在命令行上添加選項(xiàng),而無(wú)需更新父類(lèi)。它們都從?runClient()?方法調(diào)用。
- getSSLSocketFactory()?是一個(gè)有趣的方法。JSSE 安全套接字始終是從?SSLSocketFactory?對(duì)象構(gòu)造的。通過(guò)構(gòu)造一個(gè)定制的套接字工廠,我們可以定制 JSSE 的行為。為了將來(lái)練習(xí)的目的,每個(gè) SimpleSSLClient 子類(lèi)都實(shí)現(xiàn)該方法,并相應(yīng)定制?SSLSocketFactory?。
目前,SimpleSSLClient 僅能理解?-host?和?-port?參數(shù),這允許用戶(hù)把客戶(hù)機(jī)指向服務(wù)器。在這第一個(gè)基本示例中,?getSSLSocketFactory?返回(JVM 范圍的)缺省工廠,如下所示:
protected SSLSocketFactory getSSLSocketFactory()throws IOException, GeneralSecurityException {return (SSLSocketFactory)SSLSocketFactory.getDefault(); }從子類(lèi)的?main()?方法調(diào)用的?runClient()?方法,負(fù)責(zé)處理命令行參數(shù),然后從子類(lèi)獲取?SSLSocketFactory?來(lái)使用。然后它使用?connect()方法連接到服務(wù)器,并且使用?transmit()?方法在安全通道上開(kāi)始傳輸數(shù)據(jù)。
connect()?方法相當(dāng)簡(jiǎn)單。在使用?SSLSocketFactory?連接到服務(wù)器之后,它調(diào)用安全套接字上的?startHandshake?。這迫使 JSSE 完成 SSL 握手階段,并因而觸發(fā)服務(wù)器端上的?HandshakeCompletedListener?。盡管 JSSE 確實(shí)會(huì)自動(dòng)啟動(dòng)握手,但是僅當(dāng)數(shù)據(jù)首次通過(guò)套接字發(fā)送時(shí)它才這樣做。因?yàn)橛脩?hù)在鍵盤(pán)上輸入消息之前我們不會(huì)發(fā)送任何數(shù)據(jù),但是我們希望服務(wù)器立即報(bào)告連接,所以我們需要使用startHandshake?強(qiáng)制進(jìn)行握手。
transmit()?方法同樣相當(dāng)簡(jiǎn)單。它的首要任務(wù)把輸入源包裝到適當(dāng)?shù)?Reader?,如下所示:
BufferedReader reader=new BufferedReader(new InputStreamReader(in));我們使用?BufferedReader?,因?yàn)樗鼘臀覀儼演斎敕指畛蓡为?dú)的行。
接下來(lái),?transmit()?方法把輸出流 ― 在本案例中,由安全套接字提供?OutputStream?― 包裝到適當(dāng)?shù)?Writer?中。服務(wù)器希望文本是以 UTF-8 編碼的,因此我們可以讓?OutputStreamWriter?使用下列編碼:
writer=new OutputStreamWriter(socket.getOutputStream(), "UTF-8");主循環(huán)很簡(jiǎn)單;正如您在清單 3 中看到的,它看起來(lái)更象 SimpleSSLServer 中?InputDisplayer?的主循環(huán):
清單 3. SimpleSSLClient 主循環(huán)
boolean done=false; while (!done) {String line=reader.readLine();if (line!=null) {writer.write(line);writer.write('\n');writer.flush();}else done=true; }基本的 JSSE 服務(wù)器和客戶(hù)機(jī)代碼就只有這些。現(xiàn)在,我們可以繼續(xù)擴(kuò)展 SimpleSSLClient,并且看看一些其它?getSSLSocketFactory?實(shí)現(xiàn)。
回頁(yè)首
自制的 KeyStore
還記得我們是如何運(yùn)行 SimpleSSLClient 的嗎?命令如下:
java -Djavax.net.ssl.keyStore=clientKeys-Djavax.net.ssl.keyStorePassword=password -Djavax.net.ssl.trustStore=clientTrust -Djavax.net.ssl.trustStorePassword=password SimpleSSLClient命令簡(jiǎn)直太長(zhǎng)了!幸運(yùn)的是,該示例及接下來(lái)的示例將為您演示如何設(shè)置一個(gè)帶有到 KeyStore 和 TrustStore 的硬編碼路徑的SSLSocketFactory?。除了減少上述命令的長(zhǎng)度之外,您將學(xué)習(xí)的技術(shù)將允許您設(shè)置多個(gè)?SSLSocketFactory?對(duì)象,每個(gè)對(duì)象都帶有不同的 KeyStore 和 TrustStore 設(shè)置。如果沒(méi)有這種配置,JVM 中的每個(gè)安全連接必須使用相同的 KeyStore 和 TrustStore。盡管對(duì)于較小的應(yīng)用程序而言這是可以接受的,但是較大的應(yīng)用程序可能需要連接到多個(gè)代表許多不同用戶(hù)的對(duì)等方。
介紹 CustomKeyStoreClient
對(duì)于第一個(gè)示例,我們將使用示例應(yīng)用程序 CustomKeyStoreClient(可在本文的源代碼中找到)來(lái)動(dòng)態(tài)定義一個(gè) KeyStore。在研究源代碼之前,讓我們看看正在使用的 CustomKeyStoreClient。對(duì)于這個(gè)練習(xí),我們將指定 TrustStore 而不是 KeyStore。在 CustomKeyStoreClient 命令行上輸入下列參數(shù),我們將看到出現(xiàn)的結(jié)果:
java -Djavax.net.ssl.trustStore=clientTrust-Djavax.net.ssl.trustStorePassword=password CustomKeyStoreClient假定客戶(hù)機(jī)連接良好,服務(wù)器將報(bào)告說(shuō)提供的證書(shū)是有效的。連接成功,因?yàn)?CustomKeyStoreClient.java?已經(jīng)硬編碼了 KeyStore 的名稱(chēng)(clientKeys?)和密碼(?password?)。如果您為客戶(hù)機(jī) KeyStore 選擇了另外的文件名或密碼,那么可以使用新的命令行選項(xiàng)?-ks?和?-kspass來(lái)指定它們。
研究一下?CustomKeystoreClient.java?的源代碼,?getSSLSocketFactory?做的第一件事是調(diào)用助手方法?getKeyManagers()?。稍后我們將考慮這是如何工作的;目前只是注明它返回?KeyManager?對(duì)象數(shù)組,已經(jīng)利用必需的 KeyStore 文件和密碼對(duì)其進(jìn)行了設(shè)置。
清單 4. CustomKeyStoreClient.getSSLSocketFactory
protected SSLSocketFactory getSSLSocketFactory()throws IOException, GeneralSecurityException {// Call getKeyManagers to get suitable key managersKeyManager[] 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?數(shù)組之后,?getSSLSocketFactory?執(zhí)行一些對(duì)所有 JSSE 定制通常都很重要的設(shè)置工作。為了構(gòu)造?SSLSocketFactory?,應(yīng)用程序獲取一個(gè)?SSLContext?實(shí)例,對(duì)其進(jìn)行初始化,然后使用?SSLContext?生成一個(gè)?SSLSocketFactory?。
當(dāng)?shù)玫?SSLContext?時(shí),我們指定?"SSL"?的協(xié)議;我們也可以在這放入特定的 SSL(或 TLS)協(xié)議版本,并且強(qiáng)制通信在特定的級(jí)別發(fā)生。通過(guò)指定?"SSL"?,我們?cè)试S JSSE 缺省至它能支持的最高級(jí)別。
SSLContext.init?的第一個(gè)參數(shù)是要使用的?KeyManager?數(shù)組。第二個(gè)參數(shù)(這里保留為 null)類(lèi)似于?TrustManager?對(duì)象數(shù)組,稍后我們將使用它們。通過(guò)讓第二個(gè)參數(shù)為 null,我們告訴 JSSE 使用缺省的 TrustStore,它從?javax.net.ssl.trustStore?和javax.net.ssl.trustStorePassword?系統(tǒng)屬性挑選設(shè)置。第三個(gè)參數(shù)允許我們覆蓋 JSSE 的隨機(jī)數(shù)生成器(RNG)。RNG 是 SSL 的一個(gè)敏感領(lǐng)域,誤用該參數(shù)會(huì)致使連接變得不安全。我們讓該參數(shù)為 null,這樣允許 JSSE 使用缺省的 ― 并且安全的!―?SecureRandom?對(duì)象。
裝入 KeyStore
接下來(lái),我們將研究?getKeyManagers?如何裝入和初始化?KeyManagers?數(shù)組。先從清單 5 中的代碼開(kāi)始,然后我們將討論正在發(fā)生什么。
清單 5. 裝入和初始化 KeyManagers
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 KeyStorekmFact.init(ks, keyStorePassword.toCharArray());// And now get the TrustManagersKeyManager[] kms=kmFact.getKeyManagers();return kms; }首要工作是獲取?KeyManagerFactory?,但是要這樣做,我們需要知道將使用哪種算法。幸運(yùn)的是,JSSE 使缺省的?KeyManagerFactory?算法可用。可以使用?ssl.KeyManagerFactory.algorithm?安全性屬性配置缺省算法。
接下來(lái),?getKeyManagers()?方法裝入 KeyStore 文件。這其中包括從文件建立一個(gè)?InputStream?、獲取一個(gè)?KeyStore?實(shí)例,以及從InputStream?裝入?KeyStore?。除了?InputStream?,?KeyStore?需要知道流的格式(我們使用缺省的?"jks"?)和存儲(chǔ)密碼。存儲(chǔ)密碼必須作為字符數(shù)組提供。
CustomKeyStoreClient 包導(dǎo)入
為了訪問(wèn)?KeyStore?類(lèi),我們必須導(dǎo)入?javax.net.ssl和?java.security.cert?。其它類(lèi)(如?SSLContext和?KeyManagerFactory?)從 J2SE 1.4 起,是javax.net.ssl?的成員。在 J2SE 1.2 或 1.3 中,這些類(lèi)的位置不是標(biāo)準(zhǔn)的;例如,Sun JSSE 實(shí)現(xiàn)把它們放在com.sun.net.ssl?中。
要說(shuō)明的一個(gè)可能很有用的竅門(mén)是,?KeyStore.load?會(huì)獲取任何?InputStream?。您的應(yīng)用程序可以從任何地方構(gòu)建這些流;除了文件,您可以通過(guò)網(wǎng)絡(luò)、從移動(dòng)設(shè)備獲取流,或者甚至直接生成流。
裝入?KeyStore?之后,我們使用它來(lái)初始化以前創(chuàng)建的?KeyManagerFactory?。我們需要再次指定一個(gè)密碼,這次是單獨(dú)的證書(shū)密碼。通常,對(duì)于 JSSE 而言,KeyStore 中的每個(gè)證書(shū)都需要具備與 KeyStore 本身相同的密碼。自己構(gòu)造?KeyManagerFactory?可以克服這個(gè)限制。
KeyManagerFactory?初始化之后,它通常使用?getKeyManagers()?方法獲取相應(yīng)的KeyManager?對(duì)象的數(shù)組。
對(duì)于 CustomKeyStoreClient 而言,我們已經(jīng)研究了如何從任意的位置(本文使用本地文件系統(tǒng))裝入 KeyStore,以及如何讓證書(shū)和 KeyStore 本身使用不同的密碼。稍后我們將研究如何允許 KeyStore 中的每個(gè)證書(shū)擁有不同的密碼。盡管在本示例中我們著重于客戶(hù)機(jī)端,但是,我們可以在服務(wù)器端使用相同的技術(shù)來(lái)構(gòu)建適當(dāng)?shù)?SSLServerSocketFactory?對(duì)象。
CustomTrustStoreClient 包導(dǎo)入
同樣,本示例中使用的類(lèi)會(huì)出現(xiàn)在不同 JSSE 供應(yīng)商的不同包中。在 J2SE 1.4 中,?TrustManagerFactory?位于?javax.net.ssl?中;在 J2SE 1.2 或 1.3 中,通常它位于?com.sun.net.ssl?中。
回頁(yè)首
使用您自己的 TrustStore
覆蓋 JSSE 的缺省 TrustStore 非常類(lèi)似于我們剛才用 KeyStore 所做的工作,這并不令人驚奇。我們已經(jīng)設(shè)置了 CustomTrustStoreClient(可在本文的源代碼中找到)來(lái)使用硬編碼的 KeyStore 和硬編碼的 TrustStore。開(kāi)始運(yùn)行它所需做的全部工作就是輸入命令:?java CustomTrustStoreClient?。
CustomTrustStoreClient 希望 KeyStore 將是一個(gè)名為?clientKeys?并且密碼為?password?的文件。希望 TrustStore 將是一個(gè)名為?clientTrust,密碼為?password?的文件。就象使用 CustomKeyStoreClient 一樣,可以使用?-ks?、?-kspass?、?-ts?和?-tspass?參數(shù)覆蓋這些缺省值。
getSSLSocketFactory()?在許多方面與 CustomKeyStoreClient 中相同方法是一樣的。我們甚至從 CustomKeyStoreClient 調(diào)用getKeyManagers()?方法來(lái)獲取與前面示例中相同的定制的?KeyManager?對(duì)象數(shù)組。但是這時(shí)?getSSLSocketFactory?還必須獲取一個(gè)定制的TrustManager?對(duì)象數(shù)組。在清單 6 中,我們可以看到?getSSLSocketFactory?如何使用助手方法?getTrustManagers()?獲取定制的TrustManager?對(duì)象:
清單 6. getSSLSocketFactory 如何使用 TrustManagers
protected SSLSocketFactory getSSLSocketFactory()throws IOException, GeneralSecurityException {// Call getTrustManagers to get suitable trust managersTrustManager[] tms=getTrustManagers();// Call getKeyManagers (from CustomKeyStoreClient) to get suitable// key managersKeyManager[] 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; }這時(shí),當(dāng)初始化上下文時(shí),我們覆蓋了 KeyStore 和 TrustStore。但是,我們?nèi)匀蛔?JSSE 通過(guò)傳遞?null?作為第三個(gè)參數(shù)來(lái)使用它缺省的SecureRandom?。
getTrustManagers?也非常類(lèi)似于 CustomKeyStoreClient 的等價(jià)物同樣不足為奇,如清單 7 所示:
清單 7. 裝入和初始化 TrustManagers
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 KeyStoretmFact.init(ks);// And now get the TrustManagersTrustManager[] tms=tmFact.getTrustManagers();return tms; }就象以前一樣,?getTrustManagers()?方法首先根據(jù)缺省算法實(shí)例化一個(gè)?TrustManagerFactory?。然后將 TrustStore 文件裝入?KeyStore?對(duì)象 ― 是的,命名不大恰當(dāng) ― 并且初始化?TrustManagerFactory?。
跟 KeyStore 等價(jià)物不同,請(qǐng)注意,當(dāng)初始化?TrustManagerFactory?時(shí),無(wú)需提供密碼。不象私鑰,可信的證書(shū)無(wú)需利用單獨(dú)的密碼進(jìn)行保護(hù)。
到目前為止,我們已經(jīng)研究了如何動(dòng)態(tài)地覆蓋 KeyStore 和 TrustStore。到這兩個(gè)示例都完成時(shí),您應(yīng)該非常清楚如何設(shè)置KeyManagerFactory?和?TrustManagerFactory?,并使用這些來(lái)“播種”一個(gè)?SSLContext?。最后的示例有點(diǎn)煩瑣:我們將構(gòu)建自己的KeyManager?實(shí)現(xiàn)。
回頁(yè)首
定制 KeyManager 設(shè)置:選擇別名
當(dāng)運(yùn)行客戶(hù)機(jī)應(yīng)用程序的以前版本時(shí),您是否注意到了服務(wù)器顯示的是哪個(gè)證書(shū) DN?我們故意設(shè)置客戶(hù)機(jī) KeyStore 以獲得兩個(gè)可接受的證書(shū),一個(gè)用于 Alice,另一個(gè)用于 Bob。在這個(gè)案例中,JSSE 將選擇任何一個(gè)它認(rèn)為合適的證書(shū)。在我的安裝中,似乎始終選取 Bob 的證書(shū),但是您的 JSSE 的行為可能有所不同。
我們的示例應(yīng)用程序 ― SelectAliasClient 允許您選擇提供哪個(gè)證書(shū)。因?yàn)槲覀冊(cè)?Keystore 中按照別名?alice?或?bob?命名了每個(gè)證書(shū),所以要選擇 Alice 的證書(shū)可輸入命令:?java SelectAliasClient -alias alice?。
當(dāng)客戶(hù)機(jī)連接并且 SSL 握手完成時(shí),服務(wù)器將用如下所示進(jìn)行響應(yīng):
1: New connection request 1: Request from CN=Alice, OU=developerWorks, O=IBM, L=Winchester, ST=Hampshire, C=UK(或者創(chuàng)建 Alice 的證書(shū)時(shí)所選的任何值)。類(lèi)似地,如果選擇 Bob 的證書(shū),請(qǐng)輸入:?java SelectAliasClient -alias bob?,服務(wù)器將報(bào)告下述信息:
2: New connection request 2: Request from CN=Bob, OU=developerWorks, O=IBM, L=Winchester, ST=Hampshire, C=UK回頁(yè)首
定制 KeyManager 實(shí)現(xiàn)
為了強(qiáng)制選擇一個(gè)特殊的別名,我們將編寫(xiě)一個(gè)?X509KeyManager?實(shí)現(xiàn),?KeyManager?通常由 JSSE 使用來(lái)進(jìn)行 SSL 通信。我們的實(shí)現(xiàn)將包含一個(gè)真正的?X509KeyManager?,并且簡(jiǎn)單地通過(guò)它傳遞大多數(shù)的調(diào)用。它攔截的唯一方法是?chooseClientAlias()?;我們的實(shí)現(xiàn)檢查以便了解所需的別名有效還是無(wú)效,如果有效,則返回它。
在 SSL 握手階段,?X509KeyManager?接口使用許多方法來(lái)檢索密鑰,然后使用它來(lái)標(biāo)識(shí)對(duì)等方。在?參考資料部分可以找到所有方法的參考。下列方法對(duì)于本練習(xí)很重要:
- getClientAliases()?和?getServerAliases()?分別為使用?SSLSocket?和?SSLServerSocket?提供了有效的別名數(shù)組。
- chooseClientAlias()?和?chooseServerAlias()?返回單個(gè)有效的別名。
- getCertificateChain()?和?getPrivateKey()?每個(gè)都把別名作為參數(shù),并返回有關(guān)已標(biāo)識(shí)證書(shū)的信息。
定制 KeyManager 中的控制流
控制流的工作如下所示:
KeyManager?AliasForcingKeyManager()?的?chooseClientAlias()?方法實(shí)際上需要多次調(diào)用?getClientAliases()?,一次對(duì)應(yīng)一個(gè) JSSE 安裝支持的密鑰類(lèi)型,如清單 8 所示:
清單 8. 強(qiáng)制別名的選擇
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?的其它五種方法的實(shí)現(xiàn);這些只是調(diào)用它們?cè)?baseKM?上的對(duì)應(yīng)部分。
目前,它仍然使用?AliasForcingKeyManager?,而不是通常的?KeyManager?。這發(fā)生在?getSSLSocketFactory?中,它首先從其它示例中調(diào)用getKeyManagers?和?getTrustManagers?,但是接著將每個(gè)從?getKeyManagers?返回的?KeyManager?封裝進(jìn)一個(gè)?AliasForcingKeyManager?實(shí)例,如清單 9 所示:
清單 9. 封裝 X509KeyManagers
protected SSLSocketFactory getSSLSocketFactory() throws IOException, GeneralSecurityException {// Call the superclasses to get suitable trust and key managersKeyManager[] 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 X509KeyManagerif (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 重新打包
J2SE 1.2 和 1.3 中的?KeyManager?和?X509KeyManager在 J2SE 1.4 中都從供應(yīng)商特定的包中移到了javax.net.ssl?中;當(dāng)接口移動(dòng)時(shí),X509KeyManager?方法說(shuō)明會(huì)略微發(fā)生一點(diǎn)變化。
可以使用本文探討的技術(shù)覆蓋?KeyManager?的任何方面。類(lèi)似地,可以使用它們代替TrustManager?,更改 JSSE 的機(jī)制以決定是否信任從遠(yuǎn)程對(duì)等方流出的證書(shū)。
回頁(yè)首
結(jié)束語(yǔ)
本文已經(jīng)討論了相當(dāng)多的技巧和技術(shù),因此讓我們以快速回顧來(lái)結(jié)束本文。現(xiàn)在您應(yīng)當(dāng)基本了解如何:
- 使用?HandshakeCompletedListener?收集有關(guān)連接的信息
- 從?SSLContext?獲取?SSLSocketFactory
- 使用定制、動(dòng)態(tài)的 TrustStore 或 KeyStore
- 放寬 KeyStore 密碼與單個(gè)證書(shū)密碼必須匹配的 JSSE 限制
- 使用您自己的 KeyManager 強(qiáng)制選擇標(biāo)識(shí)證書(shū)
在適當(dāng)?shù)牡胤?#xff0c;我還建議擴(kuò)展這些技術(shù)以用于各種應(yīng)用程序案例。在您自己的實(shí)現(xiàn)中封裝?X509KeyManager?的技巧可用于 JSSE 中的許多其它類(lèi),當(dāng)然,利用?TrustStore?和?KeyStore?可以做更有趣的事情,而不只是裝入硬編碼的文件。
不管您如何選擇實(shí)現(xiàn)本文演示的高級(jí) JSSE 定制,任何一個(gè)都不是隨便就可以實(shí)現(xiàn)的。在調(diào)整 SSL 內(nèi)部機(jī)理的時(shí)候,請(qǐng)牢記:一個(gè)錯(cuò)誤就會(huì)致使您連接變得不安全,這很重要。
轉(zhuǎn)自:http://www.ibm.com/developerworks/cn/java/j-customssl/
http://lyb520320.iteye.com/blog/720478
?
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/p/3797435.html
總結(jié)
以上是生活随笔為你收集整理的使用 JSSE 定制 SSL 连接的属性--转载的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 如何使用JCA (J2EE 连接器架构)
- 下一篇: gradle idea java ssm