使用 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

相关文章
相关标签/搜索