1. 程式人生 > >為高階 JSSE 開發人員定製 SSL(一)

為高階 JSSE 開發人員定製 SSL(一)

當資料在網路上傳播的時候,通過使用 SSL 對其進行加密和保護,JSSE 為 Java 應用程式提供了安全的通訊。在本篇有關該技術的高階研究中,Java 中介軟體開發人員 Ian Parkinson 深入研究了 JSSE API 較不為人知的方面,為您演示瞭如何圍繞 SSL 的一些限制進行程式設計。您將學習如何動態地選擇 KeyStore 和 TrustStore、放寬 JSSE 的密碼匹配要求,以及構建您自己定製的 KeyManager 實現。

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,我們輸入如下(稍微冗長的)命令:

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 提供的證書。伺服器初始化它自己後,您將得到下述報告:

 
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);
}

我們的 HandshakeCompletedListenerSimpleHandshakeListener 提供了一個 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");
    }
  }
}
在 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.outX509Certificate 是在包 java.security.cert 中定義的。