用JSSE定制SSL连接

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

  首先我通过一个简单的客户
配置KeyStore和TrustStore文件
论授权和身份验证方面的问题。

机/服务器程序来介绍如何利用JSSE
,这样在程序中我们才可以从客户端
通过从KeyStore中选择不同的授权,

进行编程。当建立客户端时,我们需要
的文件系统中加载它们。然后文章将讨
客户端程序可以连接到不同的服务器。

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


  在深入介绍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 c
lientKeys
  窗口中会出现下面的提示,根据提示输入相应的信息:                                         

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

  (如果和 keystore 密码相同,按回车):                                          
  通过相同的方式可以建立对Bob的授权。                                                   

  keytool -genkey -alias b
ob -keystore clientKeys
  注意在名字与姓氏一栏中填
写Bob。在完成后可以键入下面的命
令来检测是否已经正确完成了授权。
  keytool -list -v -keystore clientKeys                
  · 一个服务器端的KeyStore文件,该文件中包含了对server的授权。             

  在命令窗口中键入下面的命令:                                                             
  keytool -genkey -alias server -keystore
serverKeys
  注意将密码设为password,名字与姓氏设定为

Server。完成授权后同样可以通过上面提到的命令来检测

  · 一个名为clientTrust的
serverTrust的服务器端TrustSt
客户端TrustStore文件,该文件中包
ore文件,该文件中包含了对Alice和
含了对server的授权。以及一个名为
Bob的授权。
  keytool -export -alias s
erver -keystore clientKeys -file
server.cer
  输入keystore密码: password                                            
  保存在文件中的认证

  keytool -export -alias alice -keystore c
lientKeys -file alice.cer
  输入keystore密码: password                                            
  保存在文件中的认证

  keytool -export -alias b
ob -keystore clientKeys -file bo
b.cer
  输入keystore密码: password                                            
  保存在文件中的认证

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


  keytool -import -alias server -keystore
clientTrust -file server.cer
  keytool -import -alias a
lice -keystore serverTrust -file
alice.cer
  keytool -import -alias bob -keystore ser
verTrust -file bob.cer
  到目前为止,在当前目录下
了KeyStore和TrustStore的设置
包含clientKeys,serverKeys,clie
后就可以运行例子程序了。首先需要
ntTrust,serverTrust四个文件。完成
运行服务器程序:
  java -Djavax.net.ssl.keyStore=serverKeys         

   -Djavax.net.ssl.ke
yStorePassword=password
   -Djavax.net.ssl.trustStore=serverTrust
   -Djavax.net.ssl.trustStorePassword=pas
sword SimpleSSLServer
  在命令行中我们指定了keyS
指定trustStore为serverTrust
当服务器程序成功运行后,你会
tore属性为serverKeys。由于服务器
。这样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=pas
sword SimpleSSLClient
  客户端程序会试图向本机的
过-host参数指定主机名称。当
49152端口建立SSL连接。同样你可以
连接成功后,会出现下面的提示信息
通过-port参数指定端口号,也可以通


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

  SimpleSSLServer                                                           

  让我们先来看一下SimpleSS
对象;然后利用SSLServerSocke
SimpleSSLServer对象。
LServer。在main()方法中,程序
tFactory创建一个SimpleSSLServer

获得了缺省的SSLServerSocketFactory
对象,最后调用start()方法启动


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

  SSLServerSocket serverSo
ServerSocket(port);
cket= (SSLServerSocket)serverSo

cketFactory.create

  serverSocket.setNeedClientAuth(true);                
  调用run()方法后,程序
HandshakeCompletedListener对
的)。Socket的InputStream对
外一个线程中,用来将Socket接
体:
进入了一个死循环,等待客户端的连
象(该对象是用来显示客户验证信息
象被包装在一个InputDisplayer对象
收到的数据发送到System.out。下面

接申请。循环中的每个Socket对应一个
中的标识名称[distinguished name]
中,这个InputDisplayer对象运行在另
的代码是SimpleSSLServer中的主循环

  while (true) {                                                              
   String ident=String.valueOf(id++);        
   //监听连接请求.                                                          
   SSLSocket socket=
(SSLSocket)serverSocket.accept()
;
   //通过使用Handsha
keCompletedListener对象,程序进行
授权验证.
   HandshakeCompletedListener hcl=ne
w SimpleHandshakeListener(ident);

   socket.addHandsha
keCompletedListener(hcl);

   InputStream in=so
cket.getInputStream();
   new InputDisplayer(ident, in);                
  }                                                                                        
  程序中的SimpleHandshakeListener类实现了H
SimpleHandshakeListener类中实现了handshakeCo
调用。它将显示出客户端的标识名称:
andshakeCompletedListerner接口。在
mpleted()方法,该方法在SSL握手阶段完成后将被JSSE

  class SimpleHandshakeLis
tener implements HandshakeComple
tedListener
  {                                                                                        
   String ident;                                                            
   /**                                                                        
   * 构造函数.                                                              
   */                                                                        
   public SimpleHandshakeListener(Str
ing ident)
   {                                                                            
   this.ident=ident;                                        
   }                                                                            
   /**当SSL握手过程完成后该方法被激活. */                              
   public void handsh
akeCompleted(HandshakeCompletedE
vent event)
   {                                                                            
   //显示授权信息.                                                        
   try {                                                                
   X509Certificate                                       
   cert=(X509Ce
rtificate)event.getPeerCertifica
tes()[0];
   String peer=cert.getSubjectDN(
).getName();
   System.out.pri
ntln(ident+": Request from "+pee
r);
   }                                                                        
   catch (SSLPeerUnverifiedExceptio
n pue) {
   System.out.println(ident+": Pe
er unverified");
   }                                                                        
   }                                                                            
   }                                                                                      
  用红色字体表示的两行代码
X509Certificated对象的数组。
素是客户端的验证信息,而最后
标识名称,并将它传送到System
是这段代码的核心:getPeerCertifi
这些X509Certificated对象创建了客
一个通常是CA验证。当我们有了客户
.out。
cates()方法返回一个
户端的身份标识。在数组中的第一个元
端的验证信息后。我们可以得到其中的


  SimpleSSLClient                                                           

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

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


  public void connect(SSLS
ocketFactory sf) throws IOExcept
ion
  {                                                                                        

   socket=(SSLSocket)sf
.createSocket(host, port);
   try {                                                                        
   socket.startHandshake();                              
   }                                                                                
   catch (IOException ioe) {                                
   // 握手失败.关闭连接.                                                    
   try {                                                                    
   socket.close();                                              
   }                                                                               
   catch (IOException ioe2) {                          
   // 忽略该错误.                                                        
   }                                                                            
   socket=null;                                                      
   throw ioe;                                                          
   }                                                                                
   }                                                                                    
  SimpleSSLClient类中的transmit()方法也
后将输出流包装到一个Writer对象中;最后将数据
很简单。首先程序将输入流包装到一个Reader对象中,然
流输出到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                                                
  还记得我们是如何运行客户端的吗?我们需要
trustStore和trustStorePassword参数,以至于整
KeyStore和TrustStore,后面的例子中将告诉你如
SSLSocketFactory对象,其中每个SSLSocketFacto
这种技术,在同一个虚拟机上的所有安全连接都只
程序,这也许不会产生问题;但是对于那些比较大
在命令行中指定keyStore, keyStorePasword,
个命令显得过于冗长。事实上你可以在程序中指定
何实现这一点。同时在例子中还会演示如何配置多个
ry对象对应不同的KeyStore和TrustStore设置。如果没有
能使用同一个KeyStore和TrustStore。对于比较小的应用
的应用程序来说,这绝对是一个严重的缺陷。

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


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


  protected SSLSocketFacto
ry getSSLSocketFactory()
   throws IOException, GeneralSecurityExc
eption
  {                                                                                        
   // 调用getTrustManagers方法获得trust managers        
   TrustManager[] tms=getTrustManagers();          
   // 调用getKeyManagers方法获得key manager                  
   KeyManager[] kms=getKeyManagers();                  
   //利用KeyManagers创建一个SSLContext对
象.用获得的KeyStore和
   // TrustStore初始化该S
SLContext对象.我们使用缺省的Secu
reRandom.
   SSLContext context=SSLContext.getInsta
nce("SSL");
   context.init(kms, tms, null);                            
   //最后获得了SocketFactory对象.                                        

   SSLSocketFactory ssf=c
ontext.getSocketFactory();
   return ssf;                                                                
  }                                                                                        
  下面让我们看一看CustomKeyStoreClient类中
数组的:
的getKeyMangers()方法是如何初始化KeyManagers对象


  protected KeyManager[] getKeyManagers()            
   throws IOException, GeneralSecurityExc
eption
  {                                                                                        
   // 获得KeyManagerFactory对象.                                    

   String alg=KeyManagerF
actory.getDefaultAlgorithm();
   KeyManagerFactory kmFact=KeyManagerFac
tory.getInstance(alg);
   // 配置KeyManagerFactory对象使用的KeyS
toree.我们通过一个文件加载
   // KeyStore.                                                              

   FileInputStream fis=ne
w FileInputStream(keyStore);
   KeyStore ks=KeyStore.getInstance("jks");      
   ks.load(fis, keyStorePassword.toCharAr
ray());
   fis.close();                                                              
   // 使用获得的KeyStore初始化KeyManagerFactory对象          

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

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


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

  java SelectAliasClient -alias alice                    
  当客户端和服务器端成功连接后,客户器端会出现下面的信息:                                 
  1: New connection request                                        
  1: Request from CN=Alice
, OU= Development, O=DCQ, L=Chon
gQing,
  ST=ChongQing, C=CH                                                      
  为了使程序使用指定的授权
KeyManager)。X509KeyManager
口获得授权的过程:
,我们需要实现X509KeyManager接口
接口在SSL握手阶段使用了几个方法

(X509KeyManager是JSSE中最常用的
来获得授权。下面是X509KeyManager接


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

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

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

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


  public String chooseClie
ntAlias(String[] keyType, Princi
pal[] issuers,
   Socket socket)      
  {                                                                                        
   //对于每一种类型的授权,都需要调用一次getClientAliases()方法来验  
   // 证别名是否有效.                                                                  
   boolean aliasFound=false;                                    
   for (int i=0; i

   String[] validAl
iases=baseKM.getClientAliases(ke
yType[i], issuers);
   if (validAliases!=null) {                                
   for (int j=0; j

   if (validAliases
[j].equals(alias)) aliasFound=tr
ue;
   }                                                                    
   }                                                                                
   }                                                                            
   if (aliasFound) return alias;                            
   else return null;                                                    
  }                                                                                        
  我们可以看到在程序中,chooserClientAlias
每次都针对不同的授权类型。AliasForingKeyMana
里就不再一一赘述了。
()方法实际上多次调用了getClientAliases()方法,
ger还实现了X509KeyManager接口的其他五个方法,在这


  然后我们就可以在程序中用
getSSLSocketFactory()方法
组,然后将其强制转化为AliasF
法的代码:
AliasForingKeyManager对象来替代K
中,我们只需要将通过调用getKeyMa
orcingKeyManager对象就可以了。下

eyManager对象了。在
nagers()方法获得KeyManager对象数
面是新的getSSLSocketFactory()方



  protected SSLSocketFacto
ry getSSLSocketFactory()
   throws IOException, GeneralSecurityE
xception
   {                                                                                
   // 调用父类中的方法获得TrustManager和KeyManager      
   KeyManager[] kms=getKeyManagers();          
   TrustManager[] tms=getTrustManagers();  
   // 如果指定了别名
,将KeyManagers包装在AliasForcin
gKeyManager对象中.
   if (alias!=null) {                                          
   for (int i=0; i

   // 这里只处理了X509KeyManager接口                    

   if (kms[i] ins
tanceof X509KeyManager)
   kms[i]=new A
liasForcingKeyManager((X509KeyMa
nager)kms[i], alias);
   }                                                                        
   }                                                                            
   // 利用TrustManagers和已经被包装的
KeyManagers创建一个SSLContext对象.
   SSLContext context=SSLContext.getI
nstance("SSL");
   context.init(kms, tms, null);                              
   // 获得SocketFactory对象.                                      
   SSLSocketFactory ssf=context.getSo
cketFactory();
   return ssf;                                                        
   }                                                                                
  我们可以使用同样的方法来替换TrustManager
实现就留给读者朋友去解决了。
对象,这样我们就可以控制JSSE验证授权的机制。具体的

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


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

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

  · 使用动态的TrustStroe或KeyStore。                                   

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值