爲高級 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

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

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章