如果希望能自定义信任库的一些行为 ( 例如:检验对方证书,针对异常做一些处理 ),我们可以使用 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 通信协议握手过程
(注:图片引自 《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(); Setfilter = 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 编写安全套接字程序。文中贴出的命令行和代码片段都可以直接用于试验和实际应用中,但是要熟练掌握和理解,还建议按照本文的步骤,亲自动手试一试。