用JSSE定制SSL连接

用JSSE定制SSL连接(转)

JSSE(Java Security Socket Extension,Java安全套接字扩展)是Sun为了解决在Internet上的安全通讯而推出的解决方案。它实现了SSL和TSL(传输层安全)协议。在JSSE中包含了数据加密,服务器验证,消息完整性和客户端验证等技术。通过使用JSSE,开发人员可以在客户机和服务器之间通过TCP/IP协议安全地传输数据。这篇文章主要描述如何使用JSSE接口来控制SSL连接。

首先我通过一个简单的客户机/服务器程序来介绍如何利用JSSE进行编程。当建立客户端时,我们需要配置KeyStore和TrustStore文件,这样在程序中我们才可以从客户端的文件系统中加载它们。然后文章将讨论授权和身份验证方面的问题。通过从KeyStore中选择不同的授权,客户端程序可以连接到不同的服务器。

 

运行例子程序

下载例子程序

在运行JSSE程序前,你需要正确安装JSSE。如果你安装了J2SE 1.4,JSSE已经被自动安装并配置好了,如果你使用的是其他版本的Java,你需要从官方站点上下载并安装JSSE,安装过程这里就不再赘述。由于JSSE是在J2SE 1.4中才成为标准的,并且J2SE 1.4中的JSSE和以前的JSSE有一些细微的差别,而且文中的例子都是在J2SE 1.4下调试的,因此推荐你使用J2SE 1.4运行这些例子。

在深入介绍JSSE之前,让我们来一个简单的客户机/服务器程序,程序中包含了两个文件:SimpleSSLServer和SimpleSSLClient。在运行程序之前,你需要配置下面这些KeyStore和TrestStore文件:

· 一个客户端的KeyStore文件,该文件中包含了对Alice和Bob的授权。

· 一个服务器端的KeyStore文件,该文件中包含了对server的授权。

· 一个名为clientTrust的客户端TrustStore文件,该文件中包含了对server的授权。

· 一个名为serverTrust的服务器端TrustStore文件,该文件中包含了对Alice和Bob的授权。

使用keytool可以帮助你创建这些文件(该工具在Java的bin目录下):

· 一个客户端的KeyStore文件,该文件中包含了对Alice和Bob的授权。

在命令窗口中输入下面的命令:

																														
                
																														
                keytool -genkey -alias alice -keystore clientKeys

窗口中会出现下面的提示,根据提示输入相应的信息:

																														
                
																														
                输入keystore密码:  password
您的名字与姓氏是什么?
  [Unknown]:  Alice
您的组织单位名称是什么?
  [Unknown]:  Development
您的组织名称是什么?
  [Unknown]:  DCQ
您所在的城市或区域名称是什么?
  [Unknown]:  ChongQing
您所在的州或省份名称是什么?
  [Unknown]:  ChongQing
该单位的两字母国家代码是什么
  [Unknown]:  CH
CN=Alice, OU=Development, O=DCQ, L=ChongQing, ST=ChongQing, C=CH 正确吗?
  [否]:  是
输入<alice>的主密码
     (如果和 keystore 密码相同,按回车):

通过相同的方式可以建立对Bob的授权。

																														
                
																														
                keytool -genkey -alias bob -keystore clientKeys

注意在名字与姓氏一栏中填写Bob。在完成后可以键入下面的命令来检测是否已经正确完成了授权。

																														
                
																														
                keytool -list -v -keystore clientKeys

· 一个服务器端的KeyStore文件,该文件中包含了对server的授权。

在命令窗口中键入下面的命令:

																														
                
																														
                keytool -genkey -alias server -keystore serverKeys

注意将密码设为password,名字与姓氏设定为Server。完成授权后同样可以通过上面提到的命令来检测。

· 一个名为clientTrust的客户端TrustStore文件,该文件中包含了对server的授权。以及一个名为serverTrust的服务器端TrustStore文件,该文件中包含了对Alice和Bob的授权。

																														
                
																														
                keytool -export -alias server -keystore clientKeys -file server.cer
输入keystore密码:  password
保存在文件中的认证 <server.cer>
keytool -export -alias alice -keystore clientKeys -file alice.cer
输入keystore密码:  password
保存在文件中的认证 <alice.cer>
keytool -export -alias bob -keystore clientKeys -file bob.cer
输入keystore密码:  password
保存在文件中的认证 <bob.cer>

这样keytool就在当前目录下创建了三个授权文件。然后我们将server.cer文件导入到clientTrust文件中;将alice.cer和bob.cer导入到serverTruest文件中:

																														
                
																														
                keytool -import -alias server -keystore clientTrust -file server.cer
keytool -import -alias alice -keystore serverTrust -file alice.cer
keytool -import -alias bob -keystore serverTrust -file bob.cer

到目前为止,在当前目录下包含clientKeys,serverKeys,clientTrust,serverTrust四个文件。完成了KeyStore和TrustStore的设置后就可以运行例子程序了。首先需要运行服务器程序:

																														
                
																														
                java -Djavax.net.ssl.keyStore=serverKeys 
      -Djavax.net.ssl.keyStorePassword=password 
      -Djavax.net.ssl.trustStore=serverTrust 
  -Djavax.net.ssl.trustStorePassword=password SimpleSSLServer

在命令行中我们指定了keyStore属性为serverKeys。由于服务器程序需要获得客户端的授权信息,我们指定trustStore为serverTrust。这样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端口建立SSL连接。同样你可以通过-port参数指定端口号,也可以通过-host参数指定主机名称。当连接成功后,会出现下面的提示信息:

																														
                
																														
                Connected

同时在服务器端会提示用户客户端已经连接成功。

SimpleSSLServer

让我们先来看一下SimpleSSLServer。在main()方法中,程序获得了缺省的SSLServerSocketFactory对象;然后利用SSLServerSocketFactory创建一个SimpleSSLServer对象,最后调用start()方法启动SimpleSSLServer对象。

																														
                
																														
                SSLServerSocketFactory ssf=      
(SSLServerSocketFactory)SSLServerSocketFactory.getDefault();
SimpleSSLServer server=new SimpleSSLServer(ssf, port);
server.start();

由于服务器是在一个单独的线程中运行的,main()方法启动了服务器之后就退出了。start()方法启动了一个新的线程,该线程执行run()方法中的代码。在run()方法中创建了一个SSLServerSocket对象,然后设定服务器需要进行客户端验证:

																														
                
																														
                SSLServerSocket serverSocket=      
(SSLServerSocket)serverSocketFactory.createServerSocket(port);
serverSocket.setNeedClientAuth(true);

调用run()方法后,程序进入了一个死循环,等待客户端的连接申请。循环中的每个Socket对应一个HandshakeCompletedListener对象(该对象是用来显示客户验证信息中的标识名称[distinguished name]的)。Socket的InputStream对象被包装在一个InputDisplayer对象中,这个InputDisplayer对象运行在另外一个线程中,用来将Socket接收到的数据发送到System.out。下面的代码是SimpleSSLServer中的主循环体:

																														
                
																														
                while (true) {
       String ident=String.valueOf(id++);
       //监听连接请求.
       SSLSocket socket=(SSLSocket)serverSocket.accept();
       //通过使用HandshakeCompletedListener对象,程序进行授权验证.
       HandshakeCompletedListener hcl=new SimpleHandshakeListener(ident);
       socket.addHandshakeCompletedListener(hcl);
       InputStream in=socket.getInputStream();
       new InputDisplayer(ident, in);
}

程序中的SimpleHandshakeListener类实现了HandshakeCompletedListerner接口。在SimpleHandshakeListener类中实现了handshakeCompleted()方法,该方法在SSL握手阶段完成后将被JSSE调用。它将显示出客户端的标识名称:

																														
                
																														
                class SimpleHandshakeListener implements HandshakeCompletedListener
{
  String ident;
      /**
       * 构造函数.
       */
      public SimpleHandshakeListener(String ident)
      {
        this.ident=ident;
      }
      /**当SSL握手过程完成后该方法被激活. */
      public void handshakeCompleted(HandshakeCompletedEvent event)
      {
        //显示授权信息.
        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");
        }
      }
 }

用红色字体表示的两行代码是这段代码的核心:getPeerCertificates()方法返回一个X509Certificated对象的数组。这些X509Certificated对象创建了客户端的身份标识。在数组中的第一个元素是客户端的验证信息,而最后一个通常是CA验证。当我们有了客户端的验证信息后。我们可以得到其中的标识名称,并将它传送到System.out。

SimpleSSLClient

SimpleSSLClient类比较简单,但是在后面的一些比较复杂的例子中的类会继承该类。在getSLLSocketFactory()方法中,程序返回缺省的工厂类:

																														
                
																														
                protected SSLSocketFactory getSSLSocketFactory()
  throws IOException, GeneralSecurityException
{
  return (SSLSocketFactory)SSLSocketFactory.getDefault();
}

在runClient()方法中,程序处理了输入参数后,获得SSLSockFactory对象,调用connect()方法连接到服务器程序。在connect()方法中,程序首先创建一个SSLSocket对象,然后调用SSLSocket对象的startHandshang()方法启动和服务器端的握手过程。当握手过程完成后,会触发一个HandshakeCompletedEvent事件。在服务器端的HandshakeCompletedListener对象会处理这个事件。事实上,JSSE可以自动启动握手过程,但是必须是在第一次有数据通过Socket传输的情况下。由于在例子程序中,直到用户在键盘上输入信息后才会有数据通过Socket传输,而我们希望服务器端及时报告连接情况,因此我们用startShake()方法来手工激活握手过程。

																								
             
																								
             public void connect(SSLSocketFactory sf) throws IOException
{
    socket=(SSLSocket)sf.createSocket(host, port);
    try {
      socket.startHandshake();
    }
    catch (IOException ioe) {
      // 握手失败.关闭连接.
      try {
       socket.close();
    } 
      catch (IOException ioe2) {
        // 忽略该错误.
      }
      socket=null;
      throw ioe;
    }
  }

SimpleSSLClient类中的transmit()方法也很简单。首先程序将输入流包装到一个Reader对象中,然后将输出流包装到一个Writer对象中;最后将数据流输出到Socket:

																								
             
																								
             boolean done=false;
while (!done) {
  String line=reader.readLine();
  if (line!=null) {
        writer.write(line);
    writer.write('\n');
        writer.flush();
  }
  else done=true;
}

 

定制KeyStore和TrustStore

还记得我们是如何运行客户端的吗?我们需要在命令行中指定keyStore, keyStorePasword, trustStore和trustStorePassword参数,以至于整个命令显得过于冗长。事实上你可以在程序中指定KeyStore和TrustStore,后面的例子中将告诉你如何实现这一点。同时在例子中还会演示如何配置多个SSLSocketFactory对象,其中每个SSLSocketFactory对象对应不同的KeyStore和TrustStore设置。如果没有这种技术,在同一个虚拟机上的所有安全连接都只能使用同一个KeyStore和TrustStore。对于比较小的应用程序,这也许不会产生问题;但是对于那些比较大的应用程序来说,这绝对是一个严重的缺陷。

在下面的例子中,我们将使用CustomTrustStoreClient来动态定义KeyStore和TrustStore。首先让我们先运行一下CustomTrustStoreClient:

																								
             
																								
             java CustomTrustStoreClient

为什么运行CustomTrustStoreClient时不需要指定KeyStore和TrustStore参数呢?这是应为在CustomTrustStoreClient的代码中指定了KeyStore(ClientKeys)和TrustStore(ClientTruts)以及它们的密钥(password)。如果你想使用其他的KeyStore、 TrustStore或密钥,可以使用-ks、-kspass、-ts和-tspass参数来指定。下面让我们来看一下CustomTrustStoreClient的getSSLSocketFactory()方法。该方法通过调用getTrustManager()方法获得一个TurstManager对象数组,通过调用getKeyManagers()方法获得一个KeyManager对象数组。然后利用得到的TurstManager和KeyManager对象数组构造一个SSLContext对象,最后通过SSLContext对象的getSocketFactory()方法来配置JSSE。需要注意的是在调用SSLContext类的init()方法时使用的参数。第一个参数是KeyManager对象数组。第二个参数和第一个参数类似,是TrustManager数组。如果前两个参数被设定为null,程序将使用缺省的KeyManager和TrustStore(缺省的KeyStore来源于系统属性中的javax.net.ssl.keyStore和javax.net.ssl.keyStorePassword属性;缺省的TrustStore来源于系统属性中的javax.net.ssl.trustStore和javax.net.ssl.trustStorePassword属性)。通过设定第三个参数可以指定JSSE中的随机数产生器(Random Number Generate, RNG)。由于在SSL中随机数的产生是一个很敏感的问题,错误使用这个参数会导致安全连接变得不安全,因此我在例子中使用了null。这样程序将使用缺省的并且是安全的SecureRandom对象。

																								
             
																								
             protected SSLSocketFactory getSSLSocketFactory()
  throws IOException, GeneralSecurityException
{
  // 调用getTrustManagers方法获得trust managers
  TrustManager[] tms=getTrustManagers();
  // 调用getKeyManagers方法获得key manager
  KeyManager[] kms=getKeyManagers();
  //利用KeyManagers创建一个SSLContext对象.用获得的KeyStore和
  // TrustStore初始化该SSLContext对象.我们使用缺省的SecureRandom.
  SSLContext context=SSLContext.getInstance("SSL");
  context.init(kms, tms, null);
  //最后获得了SocketFactory对象.
  SSLSocketFactory ssf=context.getSocketFactory();
  return ssf;
}

下面让我们看一看CustomKeyStoreClient类中的getKeyMangers()方法是如何初始化KeyManagers对象数组的:

																								
             
																								
             protected KeyManager[] getKeyManagers()
  throws IOException, GeneralSecurityException
{
  // 获得KeyManagerFactory对象.
  String alg=KeyManagerFactory.getDefaultAlgorithm();
  KeyManagerFactory kmFact=KeyManagerFactory.getInstance(alg);
  // 配置KeyManagerFactory对象使用的KeyStoree.我们通过一个文件加载
  // KeyStore.
  FileInputStream fis=new FileInputStream(keyStore);
  KeyStore ks=KeyStore.getInstance("jks");
  ks.load(fis, keyStorePassword.toCharArray());
  fis.close();
  // 使用获得的KeyStore初始化KeyManagerFactory对象
  kmFact.init(ks, keyStorePassword.toCharArray());
  // 获得KeyManagers对象
  KeyManager[] kms=kmFact.getKeyManagers();
  return kms;
}

首先的任务是获得一个KeyManagerFactory对象,但是你必须知道应该使用哪种算法。JSSE中提供了一个缺省的KeyManagerFactory算法(程序员也可以通过指定ssl.KeyManagerFacotory.algorithm属性指定缺省算法)。获得KeyManagerFactory对象后就可以加载KeyStore文件了,程序中通过一个InputStream对象将信息从文件送入KeyStore对象中。在这个过程之前,KeyStore对象需要知道输入流的格式(例子中我使用的是jks)和密钥。当我们完成了KeyStore的加载后,我们就可以用它来初始化KeyManagerFactory对象了。通常在JSSE中,在KeyStore中的所有证书使用和KeyStore相同的密码,但是通过创建KeyManagerFactory对象你可以突破这个限制。在初始化了KeyManagerFactory对象后,通常使用getKeyManager()方法来获得KeyManager对象数组。程序员通过使用和getKeyMangers()方法类似的流程来初始化TrustManager数组,这里我就不再重复了。

 

实现一个KeyManager类

到目前为止,我们已经知道如何在程序中动态生成KeyStore和TrustStore了。最后一个例子将告诉你如何实现一个KeyManager类。

当运行前几个例子的时候,不知道大家是否注意到服务器端显示的授权的标识名称。在前面我们授权给了两个人:Alice和Bob,在运行程序时JSSE会从中任选一个。在我的计算机上JSSE选择的总是Bob,或许在你的计算机上情况会有所不同。下面让我们来看一看最后一个例子程序:SelectAliasClient。这个例子使你能够在运行客户端时使用指定的授权。例如你需要指定使用Alice的授权,由于Alice的别名是alice,你需要在命令窗口中键入下面的命令:

																								
             
																								
             java SelectAliasClient -alias alice

当客户端和服务器端成功连接后,客户器端会出现下面的信息:

																								
             
																								
             1: New connection request
1: Request from CN=Alice, OU= Development, O=DCQ, L=ChongQing,
ST=ChongQing, C=CH

为了使程序使用指定的授权,我们需要实现X509KeyManager接口(X509KeyManager是JSSE中最常用的KeyManager)。X509KeyManager接口在SSL握手阶段使用了几个方法来获得授权。下面是X509KeyManager接口获得授权的过程:

1.JSSE调用chooseClientAlias()方法获得指定的授权。

2.chooseClientAlias()方法调用X509KeyManager接口的getClientAlaises()方法获得SSLSocket对象使用的所有授权的别名,然后检查指定的授权别名是否有效。

3.JSSE将别名作为参数调用X509KeyManager接口的getCertificateChain()和getPrivateKey()方法,这样就获得了指定授权的相关信息。

在例子程序中,X509KeyManager接口的实现类是AliasForcingKeyManager。在该类中最重要的方法就是就是chooseClientAlias()方法。下面是该方法的源代码:

																								
             
																								
             public String chooseClientAlias(String[] keyType, Principal[] issuers,
                            Socket socket)
{
  //对于每一种类型的授权,都需要调用一次getClientAliases()方法来验
 // 证别名是否有效.
  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;
}

我们可以看到在程序中,chooserClientAlias()方法实际上多次调用了getClientAliases()方法,每次都针对不同的授权类型。AliasForingKeyManager还实现了X509KeyManager接口的其他五个方法,在这里就不再一一赘述了。

然后我们就可以在程序中用AliasForingKeyManager对象来替代KeyManager对象了。在getSSLSocketFactory()方法中,我们只需要将通过调用getKeyManagers()方法获得KeyManager对象数组,然后将其强制转化为AliasForcingKeyManager对象就可以了。下面是新的getSSLSocketFactory()方法的代码:

																								
             
																								
             protected SSLSocketFactory getSSLSocketFactory() 
    throws IOException, GeneralSecurityException
    {
      // 调用父类中的方法获得TrustManager和KeyManager
      KeyManager[] kms=getKeyManagers();
      TrustManager[] tms=getTrustManagers();
       // 如果指定了别名,将KeyManagers包装在AliasForcingKeyManager对象中.
      if (alias!=null) {
        for (int i=0; i<kms.length; i++) {
          // 这里只处理了X509KeyManager接口
          if (kms[i] instanceof X509KeyManager)
            kms[i]=new AliasForcingKeyManager((X509KeyManager)kms[i],    alias);
        }
      }
      // 利用TrustManagers和已经被包装的KeyManagers创建一个SSLContext对象.
      SSLContext context=SSLContext.getInstance("SSL");
      context.init(kms, tms, null);
      // 获得SocketFactory对象.
      SSLSocketFactory ssf=context.getSocketFactory();
      return ssf;
    }

我们可以使用同样的方法来替换TrustManager对象,这样我们就可以控制JSSE验证授权的机制。具体的实现就留给读者朋友去解决了。

 

小结

在这篇文章中,我们讲述了使用JSSE的一些小技巧。读完这篇文章后,我相信大家因该知道如何通过编程实现下面的任务:

· 使用HandshagCompletedListerner对象来获得关于连接的信息。

· 从SSLContext对象中获得一个SLLSocketFactory对象。

· 使用动态的TrustStroe或KeyStore。

· 突破在JSSE中KeySotre的密钥的每个授权的密钥必须相同的限制。

· 通过实现自己的KeyManager类来指定JSSE使用的授权。

如果大家有兴趣的话,还可以进一步将这些技术进行扩展。例如你可以在JSSE的其他类中使用X509KeyManager接口,也可以在TrustStore和KeyStore的实现类中从数据库中读取授权信息。但是在使用自己编写的TrustStore,KeyStore,TrustManager和KeyManager的时候,需要非常小心,因为任何一个细微的错误都可能导致SSL连接不再是安全的了。

作者简介:冯睿毕业于美国北伊利诺大学计算机和电气工程系,获工程硕士学位。曾就职于NewMonics公司,进行Java虚拟机部分包的设计和开发和Java底层的性能优化工作。目前负责一些政府和企业级GIS系统的设计和实现。

SimpleSSLServer
/** */ /**
 * SimpleSSLServer.java
 * 该类是一个简单的服务器程序.它的功能是接受SSL连接请求,然后再标准显示设备上
 * 显示通过SSL套接字接收到的数据流.该服务器缺省运行在49152端口上,它需要进行
 * 客户端验证.通过输入Ctrl-C可以终止该程序.
 
*/


import  javax.net.ssl. * ;
import  java.security.cert. * ;
import  java.io. * ;

/** */ /**
 * 该类是一个简单的服务器程序.它的功能是接受SSL连接请求,然后再标准显示设备上
 * 显示通过SSL套接字接收到的数据流.该服务器缺省运行在49152端口上,它需要进行
 * 客户端验证.通过输入Ctrl-C可以终止该程序.
 
*/

public   class  SimpleSSLServer  extends  Thread
{
  
private   static   final   int  DEFAULT_PORT = 49152 ;

  
private  SSLServerSocketFactory serverSocketFactory;
  
private   int  port;

  
/** */ /**
   * 该方法首先处理命令行参数,然后开始监听连接请求.
   
*/

  
public   static   void  main(String args[])
  
{
    
int  port = DEFAULT_PORT;

    
//  解释参数
     boolean  parseFailed = false ;
    
try   {
      
for  ( int  i = 0 ; i < args.length; i ++ {
        String arg
= args[i].trim().toUpperCase();

        
//  目前只支持"-port"参数
         if  (arg.equals( " -PORT " )) port = Integer.parseInt(args[ ++ i]);
        
else  parseFailed = true ;
      }

    }

    
catch (Exception e)  {
      
//  命令行参数解释错误,显示提示信息.
      parseFailed = true ;
    }


    
if  (parseFailed)  {
      displayUsage();
    }

    
else   {
      
//  命令行参数解释成功.
      
//  利用缺省的SSLServerSocketFactory对象创建一个SimpleSSLServer对象,
      
//  然后启动该SimpleSSLServer对象.
      SSLServerSocketFactory ssf =
        (SSLServerSocketFactory)SSLServerSocketFactory.getDefault();
      SimpleSSLServer server
= new  SimpleSSLServer(ssf, port);
      server.start();
    }

  }


  
/** */ /**  显示命令行用法  */
  
private   static   void  displayUsage()
  
{
    System.out.println(
" Options: " );
    System.out.println(
" \t-port\tport of server (default  " + DEFAULT_PORT + " ) " );
  }



  
/** */ /**
   *在指定端口上创建一个SimpleSSLServer对象.
   
*/

  
public  SimpleSSLServer(SSLServerSocketFactory ssf,  int  port)
  
{
    serverSocketFactory
= ssf;
    
this .port = port;
  }


  
/** */ /**
   * SimpleSSLServer对象运行在一个单独的线程中.run()方法中实现了一个死循环,
   * 服务器在该循环中监听客户端的连接申请.用户按下Ctrl-C可以退出该循环.
   
*/

  
public   void  run()
  
{
    System.out.println(
" SimpleSSLServer running on port  " + port);

    
try   {
      
//  首先创建一个安全套接字对象
      SSLServerSocket serverSocket =
        (SSLServerSocket)serverSocketFactory.createServerSocket(port);

      serverSocket.setNeedClientAuth(
true );

      
//  每个连接都有一个ID号,该ID号从1开始
       int  id = 1 ;

      
//  监听连接请求.对于每个请求打开一个新的线程.
       while ( true {
        String ident
= String.valueOf(id ++ );

        
//  监听连接请求.
        SSLSocket socket = (SSLSocket)serverSocket.accept();

        
//  通过使用HandshakeCompletedListener对象,程序可以进行授权验证.
        HandshakeCompletedListener hcl = new  SimpleHandshakeListener(ident);
        socket.addHandshakeCompletedListener(hcl);

        InputStream in
= socket.getInputStream();
        
new  InputDisplayer(ident, in);
      }

    }

    
catch (IOException ioe)  {
      System.out.println(
" SimpleSSLServer failed with following exception: " );
      System.out.println(ioe);
      ioe.printStackTrace();
    }

  }


  
/** */ /**
   * HandshakeCompletedListener类显示客户端传来的授权信息.
   
*/

  
class  SimpleHandshakeListener  implements  HandshakeCompletedListener
  
{
    String ident;

    
/** */ /**
     * 构造函数.
     
*/

    
public  SimpleHandshakeListener(String ident)
    
{
      
this .ident = ident;
    }


    
/** */ /**  当SSL握手过程完成后该方法被激活.  */
    
public   void  handshakeCompleted(HandshakeCompletedEvent event)
    
{
      
//  显示授权信息.
       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 " );
      }

    }

  }

    

  
/** */ /**
   * 该类将套接字接收到的数据流传送到标准输出.
   
*/

  
class  InputDisplayer  extends  Thread  {
    BufferedReader reader;
    String ident;

    
/** */ /**
     * 构造函数.
     
*/

    InputDisplayer(String ident, InputStream is)
    
{
      
this .ident = ident;
      log(
" New connection request " );

      
//  利用InputStream对象创建一个InputStreamReader对象.该InputStreamReader
      
//  对象将数据流中的字节转化为字符,然后利用一个BufferedReader对象将数据流
      
//  按行分解.
       try   {
        reader
= new  BufferedReader( new  InputStreamReader(is,  " UTF-8 " ));
      }

      
catch  (UnsupportedEncodingException uee)  {
        log(
" Warning: JVM cannot support UTF-8. Using default instead " );
        reader
= new  BufferedReader( new  InputStreamReader(is));
      }


      setDaemon(
true );
      start();
    }


    
/** */ /**
     * 将数据按行输出到屏幕.
     
*/

    
public   void  run()
    
{
      
boolean  done = false ;

      
try   {
        
while  ( ! done)  {
          String line
= reader.readLine();
          
if  (line != null ) display(line);
          
else  done = true ;
        }

        log(
" Client disconnected " );
      }

      
catch (IOException ioe)  {
        
//  IO错误,记录错误并退出.
        log(ioe.toString());
        log(
" Closing connection. " );
      }


      
try   {
        reader.close();
      }

      
catch (IOException ioe)  {}
    }


    
/** */ /**
     * 该方法进行日志处理.
     
*/

    
private   void  log(String text)
    
{
      System.out.println(ident
+ " " + text);
    }


    
private   void  display(String text)
    
{
      System.out.println(ident
+ " " + text);
    }

  }

}

-------------------------
SimpleSSLClient
import javax.net.ssl.*;
import java.io.*;

/** *//**
 * 这是一个简单客户端,它向服务器端建立一个SSL连接.运行程序后用户的输入将
 * 被传送到服务器端.缺省情况下使用'localhost'和49152端口, 也可以通过命
 * 令行指定服务器名和端口号.使用Ctrl-C终止该程序.
 
*/

public class SimpleSSLClient
{
  
private static final int DEFAULT_PORT=49152;
  
private static final String DEFAULT_HOST="localhost";

  
private SSLSocket socket;
  
private String host=DEFAULT_HOST;
  
private int port=DEFAULT_PORT;

  
/** *//**
   * main()方法创建一个SimpleSSLClient对象,然后调用runClient()方法
   
*/

  
public static void main(String args[])
  
{
    SimpleSSLClient client
=new SimpleSSLClient();
    client.runClient(args);
    client.close();
  }

  
  
/** *//**
   * 该方法进行参数处理.
   
*/

  
protected int handleCommandLineOption(String[] args, int i)
  
{
    
int out;
    
try {
      String arg
=args[i].trim().toUpperCase();

      
// 该方法目前只支持"-port"和"-host"参数
      if (arg.equals("-PORT")) {
        port
=Integer.parseInt(args[i+1]);
        out
=2;
      }

      
else if (arg.equals("-HOST")) {
        host
=args[i+1];
        out
=2;
      }

      
else out=0;
    }

    
catch(Exception e) {
      
// 参数解析错误
      out=0;
    }


    
return out;
  }


  
/** *//**
   * 提供客户端使用的SSLSocketFactory对象.现在返回的是虚拟机的默认
   * SSLSocketFactory对象.
   
*/

  
protected SSLSocketFactory getSSLSocketFactory()
    
throws IOException, java.security.GeneralSecurityException
  
{
    
return (SSLSocketFactory)SSLSocketFactory.getDefault();
  }


  
/** *//**
   * 在命令窗口中显示程序的使用方法.
   
*/

  
protected void displayUsage()
  
{
    System.out.println(
"Options:");
    System.out.println(
"\t-host\thost of server (default '"+DEFAULT_HOST+"')");
    System.out.println(
"\t-port\tport of server (default "+DEFAULT_PORT+")");
  }
  


  
/** *//**
   * runClient()方法首先处理命令行参数,然后将客户端输入的信息传送到服务器端.
   
*/

  
public void runClient(String args[])
  
{
    
// 解释命令行参数
    boolean parseFailed=false;
    
int i=0;
    
while (i<args.length && !parseFailed) {
      
int handled=handleCommandLineOption(args, i);
      
if (handled==0) parseFailed=true;
      
else i+=handled;
    }

    
    
if (parseFailed) {
      
// 如果命令行参数解时出现错误,显示提示信息
      displayUsage();
    }

    
else {
      
try {
        
// 命令行解释成功.使用SSLSocketFactory对象连接到服务器.
        SSLSocketFactory ssf=getSSLSocketFactory();
        connect(ssf);
        System.out.println(
"Connected");
        
        
// 连接成功,将用户输入发送到服务器端.
        transmit(System.in);
      }

      
catch (IOException ioe) {
        
// 连接失败.
        System.out.println("Connection failed: "+ioe);
      }

      
catch (java.security.GeneralSecurityException gse) {
        
// 连接失败.
        System.out.println("Connection failed: "+gse);
      }
        
    }

  }


  
/** *//**
   * 连接到服务器,当SSL握手过程结束后返回.
   
*/

  
public void connect(SSLSocketFactory sf) throws IOException
  
{
    socket
=(SSLSocket)sf.createSocket(host, port);

    
try {
      socket.startHandshake();
    }

    
catch (IOException ioe) {
      
// 握手失败.关闭连接.
      try {
        socket.close();
      }

      
catch (IOException ioe2) {
        
// 忽略该错误.
      }

      socket
=null;
      
throw ioe;
    }

  }


  
/** *//**
   * 将用户输入传输到服务器端.在调用该方法前必须调用connect()方法.在出现下面
   * 的情况是退出:1.Ctrl-C; 2.数据流尾Ctrl-D; 3.网络错误.
   
*/

  
public void transmit(InputStream in)
  
{
    
try {
      
// 利用InputStream对象中创建创建一个BufferReader对象.
      
// BufferedReader对象可以将输入分解为行.
      BufferedReader reader=new BufferedReader(new InputStreamReader(in));

      
// 创建一个Writer对象向套接字写入数据.
      try {
        writer
=new OutputStreamWriter(socket.getOutputStream(), "UTF-8");
      }

      
catch (UnsupportedEncodingException uee) {    
        System.out.println(
"Warning: JVM cannot support UTF-8. Using default instead");
        writer
=new OutputStreamWriter(socket.getOutputStream());
      }

      
      
// 按行读入数据,然后将数据写入套接字.
      boolean done=false;
      
while (!done) {
        String line
=reader.readLine();
        
if (line!=null{
          writer.write(line);
          writer.write(
'\n');
          writer.flush();
        }

        
else done=true;
      }

    }

    
catch (IOException ioe) {
      
// 登录错误,然后退出
      System.out.println("Error: "+ioe);
    }


    
// 关闭连接,忽略错误.
    try {
      socket.close();
    }

    
catch (IOException ioe) {}
  }


  
/** *//**
   * 断开连接.
   
*/

  
public void close()
  
{
    
try {
      
if (socket!=null) socket.close();
    }

    
catch (IOException ioe) {
      
// 忽略IO错误.
    }

    socket
=null;
  }

}

CustomKeyStoreClient
import javax.net.ssl.*;
import java.io.*;
import java.security.*;

/** *//**
 * 该类演示了如何创建和配置KeyStore.它加载了名为"ClientKeys"的KeyStore. 
 * 也通过命令行参数可以设置加载的KeyStore.
 
*/

class CustomKeyStoreClient extends SimpleSSLClient
{
  
private final String DEFAULT_KEYSTORE="clientKeys";
  
private final String DEFAULT_KEYSTORE_PASSWORD="password";

  
private String keyStore=DEFAULT_KEYSTORE;
  
private String keyStorePassword=DEFAULT_KEYSTORE_PASSWORD;

  
/** *//**
   * 该方法覆盖了SimpleSSLClient类中的main()方法,目的是为了使用
   * CustomKeyStoreClient.
   
*/

  
public static void main(String args[])
  
{
    CustomKeyStoreClient client
=new CustomKeyStoreClient();
    client.runClient(args);
    client.close();
  }


  
/** *//**
   * 该方法覆盖了SimpleSSLClient类中的handleCommandLineOption()方法.
   * 现在该方法可以处理-ks和-kspass参数了.
   
*/

  
protected int handleCommandLineOption(String[] args, int i)
  
{
    
int out;
    
try {
      String arg
=args[i].trim().toUpperCase();

      
// 这里仅仅处理了-ks和-kspass参数,其他参数传递给父类中的方法处理.
      if (arg.equals("-KS")) {
        keyStore
=args[i+1];
        out
=2;
      }

      
else if (arg.equals("-KSPASS")) {
        keyStorePassword
=args[i+1];
        out
=2;
      }

      
else out=super.handleCommandLineOption(args,i);
    }

    
catch(Exception e) {
      
// 解析参数时发生了错误.
      out=0;
    }


    
return out;
  }


  
/** *//**
   * 显示命令行的使用方法.
   
*/

  
protected void displayUsage()
  
{
    
super.displayUsage();
    System.out.println(
"\t-ks\tkeystore (default '"
                       
+DEFAULT_KEYSTORE+"', JKS format)");
    System.out.println(
"\t-kspass\tkeystore password (default '"
                       
+DEFAULT_KEYSTORE_PASSWORD+"')");
  }


  
/** *//**
   * 提供了一个SSLSocketFactory对象.该对象忽略JSSE选择的keystore,
   * 而使用在代码中指定的文件名和密钥,如果命令行中指定了文件名和密钥,则使用
   * 在命令行中指定的信息.
   * 该方法调用getKeyManagers()方法完成大部分的核心工作.在该方法中只需要
   * 创建一个SSLContext对象,然后从该对象中获得SSLSocketFactory对象.
   
*/

  
protected SSLSocketFactory getSSLSocketFactory()
    
throws IOException, GeneralSecurityException
  
{
    
// 调用getKeyManagers方法获得key manager
    KeyManager[] kms=getKeyManagers();

    
// 利用KeyManagers创建一个SSLContext对象. 我们使用空的TrustManager和
    
// SecureRandom对象作为参数初始化SSLContext对象,这样JSSE会使用默认
    
// TrustManager和SecureRandom对象.
    SSLContext context=SSLContext.getInstance("SSL");
    context.init(kms, 
nullnull);

    
// 最后获得了SocketFactory对象.
    SSLSocketFactory ssf=context.getSocketFactory();
    
return ssf;
  }


  
/** *//**
   * 该方法返回一组KeyManagers对象,这些对象使用指定的KeyStore.
   
*/

  
protected KeyManager[] getKeyManagers()
    
throws IOException, GeneralSecurityException
  
{
    
// 获得KeyManagerFactory对象.
    String alg=KeyManagerFactory.getDefaultAlgorithm();
    KeyManagerFactory kmFact
=KeyManagerFactory.getInstance(alg);
    
    
// 配置KeyManagerFactory对象使用的KeyStoree.我们通过一个文件加载
    
// KeyStore.
    FileInputStream fis=new FileInputStream(keyStore);
    KeyStore ks
=KeyStore.getInstance("jks");
    ks.load(fis, keyStorePassword.toCharArray());
    fis.close();

    
// 使用获得的KeyStore初始化KeyManagerFactory对象
    kmFact.init(ks, keyStorePassword.toCharArray());

    
// 获得KeyManagers对象
    KeyManager[] kms=kmFact.getKeyManagers();
    
return kms;
  }

}

CustomTrustStoreClient
import javax.net.ssl.*;
import java.io.*;
import java.security.*;

/** *//**
 * 该类演示了如何创建和配置TrustStore.它加载了名为"ClientKeys"的TrustStore. 
 * 也通过命令行参数可以设置加载的TrustStore.
 
*/

class CustomTrustStoreClient extends CustomKeyStoreClient
{
  
private final String DEFAULT_TRUSTSTORE="clientTrust";
  
private final String DEFAULT_TRUSTSTORE_PASSWORD="password";

  
private String trustStore=DEFAULT_TRUSTSTORE;
  
private String trustStorePassword=DEFAULT_TRUSTSTORE_PASSWORD;

  
/** *//**
   * 该方法覆盖了SimpleSSLClient类中的main()方法,目的是为了使用
   * CustomTrustStoreClient.
   
*/

  
public static void main(String args[])
  
{
    CustomTrustStoreClient client
=new CustomTrustStoreClient();
    client.runClient(args);
    client.close();
  }


  
/** *//**
   * 该方法覆盖了SimpleSSLClient类中的handleCommandLineOption()方法.
   * 现在该方法可以处理-ts和-tspass参数了.
   
*/

  
protected int handleCommandLineOption(String[] args, int i)
  
{
    
int out;
    
try {
      String arg
=args[i].trim().toUpperCase();

      
// 这里仅仅处理了-ts和-tspass参数,其他参数传递给父类中的方法处理.
      if (arg.equals("-TS")) {
        trustStore
=args[i+1];
        out
=2;
      }

      
else if (arg.equals("-TSPASS")) {
        trustStorePassword
=args[i+1];
        out
=2;
      }

      
else out=super.handleCommandLineOption(args,i);
    }

    
catch(Exception e) {
      
// 解析参数时发生了错误.
      out=0;
    }


    
return out;
  }


  
/** *//**
   * 显示命令行的使用方法.
   
*/

  
protected void displayUsage()
  
{
    
super.displayUsage();
    System.out.println(
"\t-ts\ttruststore (default '"
                       
+DEFAULT_TRUSTSTORE+"', JKS format)");
    System.out.println(
"\t-tspass\ttruststore password (default '"
                       
+DEFAULT_TRUSTSTORE_PASSWORD+"')");
  }
  

  
/** *//**
   * 提供了一个SSLSocketFactory对象.该对象忽略JSSE选择的TrustStore,
   * 而使用在代码中指定的文件名和密钥,如果命令行中指定了文件名和密钥,则使用
   * 在命令行中指定的信息.
   * 该方法调用getTrustManagers()方法完成大部分的核心工作.在该方法中只需要
   * 创建一个SSLContext对象,然后从该对象中获得SSLSocketFactory对象.
   
*/

  
protected SSLSocketFactory getSSLSocketFactory()
    
throws IOException, GeneralSecurityException
  
{
    
// 调用getTrustManagers方法获得trust managers
    TrustManager[] tms=getTrustManagers();
    
    
// 调用getKeyManagers方法获得key manager
    KeyManager[] kms=getKeyManagers();

    
// 利用KeyManagers创建一个SSLContext对象.用获得的KeyStore和
    
// TrustStore初始化该SSLContext对象.我们使用缺省的SecureRandom.
    SSLContext context=SSLContext.getInstance("SSL");
    context.init(kms, tms, 
null);

    
// 最后获得了SocketFactory对象.
    SSLSocketFactory ssf=context.getSocketFactory();
    
return ssf;
  }


  
/** *//**
   * 该方法返回一组TrustManagers对象,这些对象使用指定的TrustyStore.
   
*/

  
protected TrustManager[] getTrustManagers()
    
throws IOException, GeneralSecurityException
  
{
    
// 首先获得缺省的TrustManagerFactory对象.
    String alg=TrustManagerFactory.getDefaultAlgorithm();
    TrustManagerFactory tmFact
=TrustManagerFactory.getInstance(alg);
    
    
// 配置KeyManagerFactory对象使用的TrustStoree.我们通过一个文件加载
    
// TrustStore.
    FileInputStream fis=new FileInputStream(trustStore);
    KeyStore ks
=KeyStore.getInstance("jks");
    ks.load(fis, trustStorePassword.toCharArray());
    fis.close();

    
// 使用获得的KeyStore初始化TrustManagerFactory对象
    tmFact.init(ks);

    
// 获得TrustManagers对象
    TrustManager[] tms=tmFact.getTrustManagers();
    
return tms;
  }

}

SelectAliasClient
import javax.net.ssl.*;
import java.net.*;
import java.io.*;
import java.security.*;
import java.security.cert.*;

/** *//**
 * 该类演示了如何在KeyStore中使用指定的别名。该类获取一组KeyManager,
 * 然后将它们包装在一个可配置的KeyManager中。通过指定别名,这个可配置的
 * KeyManager让包装在其中的特定的KeyManager完成处理工作.
 
*/

public class SelectAliasClient extends CustomTrustStoreClient
{
  
private String alias=null;

  
/** *//**
   * 该方法覆盖了SimpleSSLClient类中的main()方法,目的是为了使用
   * SelectAliasClient.
   
*/

  
public static void main(String args[])
  
{
    SelectAliasClient client
=new SelectAliasClient();
    client.runClient(args);
    client.close();
  }


  
/** *//**
   * 该方法覆盖了SimpleSSLClient类中的handleCommandLineOption()方法.
   * 现在该方法可以处理-alias参数了.
   
*/

  
protected int handleCommandLineOption(String[] args, int i)
  
{
    
int out;
    
try {
      String arg
=args[i].trim().toUpperCase();

      
// 这里仅仅处理了-alias参数,其他参数传递给父类中的方法处理.
      if (arg.equals("-ALIAS")) {
        alias
=args[i+1];
        out
=2;
      }

      
else out=0;
    }

    
catch(Exception e) {
      
// 解析参数时发生了错误.
      out=0;
    }


    
return out;
  }


  
/** *//**
   * 显示命令行的使用方法.
   
*/

  
protected void displayUsage()
  
{
    
super.displayUsage();
    System.out.println(
"\t-alias\talias to use");
  }


  
/** *//**
   * 提供了一个SSLSocketFactory对象.该对象忽略JSSE选择的KeyStore,TrustStore
   * 和别名.对于TrustStore和KeyStore,如果在命令行中指定了,使用命令行中提供的信息;
   * 如果没有指定,使用在代码中指定的信息.对于别名,如果在命令行中指定了,使用命令行中
   * 提供的信息; 如果没有指定,使用JSSE的缺省设置.
   * 该方法调用CustomKeyStoreClient类中的getKeyManagers()和CustomTrustStoreClient
   * 中的getTrustManagers()方法加载KeyStore和TrustStore.然后将返回的KeyManagers包装在
   * AliasForcingKeyManager对象中, 该对象确保正确的别名对应的授权被选中.
   
*/

  
protected SSLSocketFactory getSSLSocketFactory()
    
throws IOException, GeneralSecurityException
  
{
    
// 调用父类中的方法获得TrustManager和KeyManager
    KeyManager[] kms=getKeyManagers();
    TrustManager[] tms
=getTrustManagers();

    
// 如果指定了别名,将KeyManagers包装在AliasForcingKeyManager对象中
    if (alias!=null{
      
for (int i=0; i<kms.length; i++{
        
// 这里只处理了X509KeyManager接口
        if (kms[i] instanceof X509KeyManager)
          kms[i]
=new AliasForcingKeyManager((X509KeyManager)kms[i], alias);
      }

    }


    
// 利用TrustManagers和已经被包装的KeyManagers创建一个SSLContext using对象.
    SSLContext context=SSLContext.getInstance("SSL");
    context.init(kms, tms, 
null);

    
// 获得SocketFactory对象.
    SSLSocketFactory ssf=context.getSocketFactory();
    
return ssf;
  }


  
/** *//**
   * AliasForcingKeyManager是X509KeyManager接口的实现类.该类包装了一个
   * 已经存在的X509KeyManager对象并使用指定的别名.如果别名无效,则不建立连接.
   
*/

  
private class AliasForcingKeyManager implements X509KeyManager
  
{
    X509KeyManager baseKM
=null;
    String alias
=null;

    
public AliasForcingKeyManager(X509KeyManager keyManager, String alias)
    
{
      baseKM
=keyManager;
      
this.alias=alias;
    }


    
/** *//**
     * chooseClientAlias选择一个别名来验证客户端的SSL连接.该方法使用了
     * getClientAliases方法来获得有效别名的列表,然后将指定的别名和列表
     * 中的别名进行比较.如果指定的别名是有效的,该方法正常返回,否则返回null.
     
*/

    
public String chooseClientAlias(String[] keyType, Principal[] issuers,
                                    Socket socket)
    
{
      
// 对于每一种类型的授权,都需要调用一次getClientAliases()方法来验
      
// 证别名是否有效.
      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;
    }


    
// 下面的方法调用缺省KeyManager中的对应方法

    
public String chooseServerAlias(String keyType, Principal[] issuers,
                                    Socket socket)
    
{
      
return baseKM.chooseServerAlias(keyType, issuers, socket);
    }


    
public X509Certificate[] getCertificateChain(String alias)
    
{
      
return baseKM.getCertificateChain(alias);
    }


    
public String[] getClientAliases(String keyType, Principal[] issuers)
    
{
      
return baseKM.getClientAliases(keyType, issuers);
    }


    
public PrivateKey getPrivateKey(String alias)
    
{
      
return baseKM.getPrivateKey(alias);
    }


    
public String[] getServerAliases(String keyType, Principal[] issuers)
    
{
      
return baseKM.getServerAliases(keyType, issuers);
    }

  }

}

你可能感兴趣的:(用JSSE定制SSL连接)