Java 安全套接字编程以及 keytool 使用最佳实践(四)

2014-11-24 07:42:52 · 作者: · 浏览: 4
定义信任库了,但如果对方的证书不在信任库中,则通信会直接宣告失败。

如果希望能自定义信任库的一些行为 ( 例如:检验对方证书,针对异常做一些处理 ),我们可以使用 X509TrustManager 接口,实现自己的方法。

如清单 5 所示,假定我们要在客户端程序使用 X509TrustManager,那么就可以在 checkServerTrusted() 函数里做一些事情,检测到服务端证书异常的话,就可以做一些自己的处理。CheckClientTrusted() 则是用于服务端检测客户端的证书情况。

将清单 5 的代码替换到清单 4 的 TrustManager[] tms 的生成处,并对代码稍作调整即可。

清单 5. X509TrustManager 的使用
 // 使用自定义的 MyTrustManager 产生信任库
 TrustManager[] tms=new TrustManager[]{new MyTrustManager()}; 
……
……
 class MyTrustManager implements X509TrustManager{ 
  // 相关的 jks 文件及其密码定义
  private final static String TRUST_STORE="D:/test_client_trust.jks"; 
  private final static String TRUST_STORE_PASSWORD="Testpassw0rd"; 
  X509TrustManager xtm;
  
  public MyTrustManager() throws Exception { 
    // 载入 jks 文件
    KeyStore ks = KeyStore.getInstance("JKS"); 
    ks.load(new FileInputStream(TRUST_STORE),TRUST_STORE_PASSWORD.toCharArray()); 
    TrustManagerFactory tmf =TrustManagerFactory.getInstance("SunX509", "SunJSSE"); 
    tmf.init(ks);
    TrustManager[] tms = tmf.getTrustManagers();
    // 筛选出 X509 格式的信任证书
    for (int i = 0; i < tms.length; i++) { 
      if (tms[i] instanceof X509TrustManager) { 
        xtm = (X509TrustManager) tms[i]; 
        return; 
      } 
    } 
  }
  // 服务端检验客户端证书的接口
  public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException{ 
  }
 // 客户端检验服务端证书的接口
  public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException{ 
    try{ 
      xtm.checkServerTrusted(chain, authType); 
    }catch(CertificateException excep){ 
      System.out.println(excep.getMessage()); 
      throw excep; 
    } 
  }
  // 获取可接受的发行者
  public X509Certificate[] getAcceptedIssuers() { 
   //return xtm.getAcceptedIssuers(); 
    return null; 
  } 
 }

注意:

1. 当服务端代码 setNeedClientAuth(False) 时,客户端的 MyTrustManager 实现了 X509TrustManager 后,如果 checkServerTrusted() 方法的实现为空,则无论服务端使用什么证书,客户端都会默认接受;如果要对服务端证书进行检查,还需要像清单 5 中的代码片段那样,捕捉异常并处理。

2.getAcceptedIssuers() 方法通常不需要具体实现,但是当服务端要求检验客户端身份,也即 setNeedClientAuth(True) 时,服务端需也需要具体实现 X509TrustManager,且 getAcceptedIssuers() 方法要如清单 5 中注释部分代码那样实现。

调试 SSL/TSL 程序

打开调试开关观察通信日志

图 2 描述了 SSL/TSL 通信的握手过程。在实际编写程序的时候,可能会在这些环节遇到问题,导致无法通信,排查起来往往令人无从下手。这个时候我们可以将 SSL/TSL 通信的握手日志开关打开,进行观察。

图 2.SSL 通信协议握手过程
图 2.SSL 通信协议握手过程

(注:图片引自 《Java Secure Socket Extension (JSSE) Reference Guide》)

打开日志开关的方法同样是通过设定系统属性,可以从命令行添加虚拟机参数:

-Djavax.net.debug=ssl,handshake

当然也可以使用 System.setProperty() 方法在代码中打开该开关。

打开日志开关后,可以搜索“ClientHello”、“ServerHello”等关键字;或者搜索“*** ”( 三个星号和一个空格 ) ――这是握手阶段每一个步骤日志打印的开始标志。通过阅读日志来定位问题。更详细的开关信息,请参阅 JSSE 指导中的“Debugging Utilities”章节:

选择通信的 Cipher Suites

有的时候为了做实验,我们会选用特定的 Cipher Suites,我们可以使用 SSLServerSocket 或 SSLSocket 的 getEnabledCipherSuites() 观察到默认支持的所有加密套件的信息,然后使用 setEnabledCipherSuites() 进行设置。

清单 6 展示了从服务端过滤掉所有的匿名加密套件的代码。

清单 6. 过滤所有的匿名加密套件
 String enabled[]=serverSocket.getEnabledCipherSuites(); 
 Set
  
    filter = new LinkedHashSet
   
    (); for(int i = 0; i < enabled.length; i++) { if(enabled[i].indexOf("anon")<0){ filter.add(enabled[i]); } } serverSocket.setEnabledCipherSuites(filter.toArray(new String[filter.size()]));
   
  

结束语

通过对本文的学习,我们可以快速了解如何使用 keytool 制作证书库和信任库,并使用 Java 编写安全套接字程序。文中贴出的命令行和代码片段都可以直接用于试验和实际应用中,但是要熟练掌握和理解,还建议按照本文的步骤,亲自动手试一试。