为高级 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 中定义的。

你可能感兴趣的:(为高级 JSSE 开发人员定制 SSL(一))