使用 JSSE 定製 SSL 鏈接的屬性--轉載

當數據在網絡上傳播的時候,經過使用 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 文件。特別是您將須要:服務器

  • 稱爲 clientKeys 的用於客戶機的 KeyStore 文件,分別包含用於虛構的通訊者 Alice 和 Bob 的證書
  • 稱爲 serverKeys 的用於服務器的 KeyStore 文件,包含一個用於服務器的證書
  • 稱爲 clientTrust 的用於客戶機的 TrustStore 文件,包含服務器的證書
  • 稱爲 serverTrust 的用於服務器的 TrustStore 文件,包含 Alice 和 Bob 的證書

接下來,下載本文隨附的 jar 文件。這些文件包含客戶機/服務器應用程序的源代碼和已編譯的版本,所以,只要把它們放到 CLASSPATH 中,就可使用了。網絡

 

創建一個安全鏈接

要運行 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

無需殺死服務器;在各類練習過程當中咱們只需保持服務器運行。

 

SimpleSSLServer 內幕

儘管本文餘下部分主要都是講述客戶機應用程序的,可是,查看一下服務器代碼仍然是很值得的。除了能夠了解服務器應用程序是如何工做外,您還能夠學會如何使用 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);

HandshakeCompletedListener

將它激活以後, run() 方法進行無限循環,等待來自客戶機的請求。每一個套接字都與 HandshakeCompletedListener 實現相關聯,後者用來顯示來自客戶機證書的區別名(DN)。套接字的 InputStream 封裝在一個 InputDisplayer 中,它做爲另外一個線程運行,而且未來自套接字的數據回顯到 System.out 。SimpleSSLServer 的主循環如清單 1 所示:

清單 1. SimpleSSLServer 主循環
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 所示:

清單 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 上運行服務器應用程序

若是您正在 J2SE 1.2 或 1.3 上運行 SimpleSSLServer 應用程序,則須要使用一個略微不一樣的(而且目前已過期的)JSSE API。不是導入 java.security.cert ,而是使用 javax.security.cert 。該包還包含稱爲X509Certificate 的類;可是,爲了從HandshakeCompletedEvent 得到證書對象數組,必須使用 getPeerCertificateChain() 方法,而不是getPeerCertificates() 方法。

用紅色突出顯示的行是很是重要的兩行: getPeerCertificates 返回做爲X509Certificate 對象數組的證書鏈。這些證書對象創建對等方的(即客戶機的)標識。數組中的第一個是客戶機自己的證書;最後一個一般是 CA 證書。一旦咱們擁有了對等方的證書,咱們能夠獲取 DN 並將其顯示到 System.out 。 X509Certificate 是在包java.security.cert 中定義的。

 

SimpleSSLClient 內幕

咱們將研究的第一個客戶機應用程序根本不能作什麼。可是,在後面的示例中咱們會擴展它來闡述更高級的功能。設置 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 的主循環:

清單 3. SimpleSSLClient 主循環
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 實現。

 

自制的 KeyStore

還記得咱們是如何運行 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

對於第一個示例,咱們將使用示例應用程序 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 文件和密碼對其進行了設置。

清單 4. CustomKeyStoreClient.getSSLSocketFactory
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 對象。

裝入 KeyStore

接下來,咱們將研究 getKeyManagers 如何裝入和初始化 KeyManagers 數組。先從清單 5 中的代碼開始,而後咱們將討論正在發生什麼。

清單 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 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" )和存儲密碼。存儲密碼必須做爲字符數組提供。

CustomKeyStoreClient 包導入

爲了訪問 KeyStore 類,咱們必須導入 javax.net.ssl和 java.security.cert 。其它類(如 SSLContext和 KeyManagerFactory )從 J2SE 1.4 起,是javax.net.ssl 的成員。在 J2SE 1.2 或 1.3 中,這些類的位置不是標準的;例如,Sun JSSE 實現把它們放在com.sun.net.ssl 中。

要說明的一個可能頗有用的竅門是, KeyStore.load 會獲取任何 InputStream 。您的應用程序能夠從任何地方構建這些流;除了文件,您能夠經過網絡、從移動設備獲取流,或者甚至直接生成流。

裝入 KeyStore 以後,咱們使用它來初始化之前建立的 KeyManagerFactory 。咱們須要再次指定一個密碼,此次是單獨的證書密碼。一般,對於 JSSE 而言,KeyStore 中的每一個證書都須要具有與 KeyStore 自己相同的密碼。本身構造 KeyManagerFactory 能夠克服這個限制。

KeyManagerFactory 初始化以後,它一般使用 getKeyManagers() 方法獲取相應的KeyManager 對象的數組。

對於 CustomKeyStoreClient 而言,咱們已經研究瞭如何從任意的位置(本文使用本地文件系統)裝入 KeyStore,以及如何讓證書和 KeyStore 自己使用不一樣的密碼。稍後咱們將研究如何容許 KeyStore 中的每一個證書擁有不一樣的密碼。儘管在本示例中咱們着重於客戶機端,可是,咱們能夠在服務器端使用相同的技術來構建適當的 SSLServerSocketFactory 對象。

CustomTrustStoreClient 包導入

一樣,本示例中使用的類會出如今不一樣 JSSE 供應商的不一樣包中。在 J2SE 1.4 中, TrustManagerFactory 位於 javax.net.ssl 中;在 J2SE 1.2 或 1.3 中,一般它位於 com.sun.net.ssl 中。

 

使用您本身的 TrustStore

覆蓋 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 對象:

清單 6. getSSLSocketFactory 如何使用 TrustManagers
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 所示:

清單 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 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 實現。

 

定製 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
 

定製 KeyManager 實現

爲了強制選擇一個特殊的別名,咱們將編寫一個 X509KeyManager 實現, KeyManager 一般由 JSSE 使用來進行 SSL 通訊。咱們的實現將包含一個真正的 X509KeyManager ,而且簡單地經過它傳遞大多數的調用。它攔截的惟一方法是 chooseClientAlias() ;咱們的實現檢查以便了解所需的別名有效仍是無效,若是有效,則返回它。

在 SSL 握手階段, X509KeyManager 接口使用許多方法來檢索密鑰,而後使用它來標識對等方。在 參考資料部分能夠找到全部方法的參考。下列方法對於本練習很重要:

  • getClientAliases() 和 getServerAliases() 分別爲使用 SSLSocket 和 SSLServerSocket 提供了有效的別名數組。
  • chooseClientAlias() 和 chooseServerAlias() 返回單個有效的別名。
  • getCertificateChain() 和 getPrivateKey() 每一個都把別名做爲參數,並返回有關已標識證書的信息。

定製 KeyManager 中的控制流

控制流的工做以下所示:

  1. JSSE 調用 chooseClientAlias 以發現要使用的別名。
  2. chooseClientAlias 在真實的 X509KeyManager 上調用 getClientAliases 來發現一個有效的別名列表,以便於它能檢查所需的別名是否有效。
  3. JSSE 經過指定正確的別名調用 X509KeyManager 的 getCertificateChain 和 getPrivateKey ,X509KeyManager 讓調用能夠訪問被包裝的 KeyManager。

KeyManager AliasForcingKeyManager() 的 chooseClientAlias() 方法實際上須要屢次調用 getClientAliases() ,一次對應一個 JSSE 安裝支持的密鑰類型,如清單 8 所示:

清單 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 所示:

清單 9. 封裝 X509KeyManagers
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 從新打包

J2SE 1.2 和 1.3 中的 KeyManager 和 X509KeyManager在 J2SE 1.4 中都從供應商特定的包中移到了javax.net.ssl 中;當接口移動時,X509KeyManager 方法說明會略微發生一點變化。

可使用本文探討的技術覆蓋 KeyManager 的任何方面。相似地,可使用它們代替TrustManager ,更改 JSSE 的機制以決定是否信任從遠程對等方流出的證書。

 

結束語

本文已經討論了至關多的技巧和技術,所以讓咱們以快速回顧來結束本文。如今您應當基本瞭解如何:

  • 使用 HandshakeCompletedListener 收集有關鏈接的信息
  • 從 SSLContext 獲取 SSLSocketFactory
  • 使用定製、動態的 TrustStore 或 KeyStore
  • 放寬 KeyStore 密碼與單個證書密碼必須匹配的 JSSE 限制
  • 使用您本身的 KeyManager 強制選擇標識證書

在適當的地方,我還建議擴展這些技術以用於各類應用程序案例。在您本身的實現中封裝 X509KeyManager 的技巧可用於 JSSE 中的許多其它類,固然,利用 TrustStore 和 KeyStore 能夠作更有趣的事情,而不僅是裝入硬編碼的文件。

無論您如何選擇實現本文演示的高級 JSSE 定製,任何一個都不是隨便就能夠實現的。在調整 SSL 內部機理的時候,請牢記:一個錯誤就會導致您鏈接變得不安全,這很重要。

轉自:http://www.ibm.com/developerworks/cn/java/j-customssl/

http://lyb520320.iteye.com/blog/720478

相關文章
相關標籤/搜索