通过 SSLContext 指定证书库和信任库
前文描述的,通过系统参数指定证书库和信任库的方法,虽然简单易用,但是缺点也是显而易见的,整个程序的环境都得使用同样的 jks 文件。如果程序里有不同的 SSL/TSL 通信,则需要使用不同的 jks 文件,该怎么做呢?
可以使用 SSLContext 来指定 jks 文件,只需要把清单 3 的代码片段替换到清单 1 的“SSLServerSocketFactory ssf”生成处;把清单 4 的代码片段替换到清单 2 的“SSLSocketFactory sf”生成处,再稍作代码调整即可。
(注:实际上,在使用 SSLSocketFactory.getDefault() 或者 SSLServerSocketFactory.getDefault() 创建套接字的时候,程序内部已经使用了默认的 context,其参数就是通过系统属性指定的 )
清单 3. SSLContext 指定证书库
// 相关的 jks 文件及其密码定义
private final static String CERT_STORE="D:/test_server_cert.jks";
private final static String CERT_STORE_PASSWORD="Testpassw0rd";
// 载入 jks 文件
FileInputStream f_certStore=new FileInputStream(CERT_STORE);
KeyStore ks=KeyStore.getInstance("jks");
ks.load(f_certStore, CERT_STORE_PASSWORD.toCharArray());
f_certStore.close();
// 创建并初始化证书库工厂
String alg=KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory kmFact=KeyManagerFactory.getInstance(alg);
kmFact.init(ks, CERT_STORE_PASSWORD.toCharArray());
KeyManager[] kms=kmFact.getKeyManagers();
// 创建并初始化 SSLContext 实例
SSLContext context=SSLContext.getInstance("SSL");
context.init(kms, null, null);
SSLServerSocketFactory ssf=(SSLServerSocketFactory)context.getServerSocketFactory();
清单 4. SSLContext 指定信任库
// 相关的 jks 文件及其密码定义
private final static String TRUST_STORE="D:/test_client_trust.jks";
private final static String TRUST_STORE_PASSWORD="Testpassw0rd";
// 载入 jks 文件
FileInputStream f_trustStore=new FileInputStream(TRUST_STORE);
KeyStore ks=KeyStore.getInstance("jks");
ks.load(f_trustStore, TRUST_STORE_PASSWORD.toCharArray());
f_trustStore.close();
// 创建并初始化信任库工厂
String alg=TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmFact=TrustManagerFactory.getInstance(alg);
tmFact.init(ks);
TrustManager[] tms=tmFact.getTrustManagers();
// 创建并初始化 SSLContext 实例
SSLContext context=SSLContext.getInstance("SSL");
context.init(null, tms, null);
SSLSocketFactory sf=context.getSocketFactory();
当然,如果要在服务端或者客户端同时使用证书库和信任库,可将清单 3 和清单 4 的代码用在同一处 context.init() 的地方。
这里需要说明的是,如果 context.init() 的第一个 KeyManager[] 参数为 null,则表示不提供证书;如果第二个 TrustManager[] 参数为 null,则会寻找系统默认提供的信任库 ( 例如:JRE 安装目录下的 lib/security/cacerts)。
回页首
使用 X509 证书信任管理器
X509TrustManager 接口扩展了 TrustManager 接口,使用 TrustManager 接口,我们已经可以在程序中自定义信任库了,但如果对方的证书不在信任库中,则通信会直接宣告失败。
如果希望能自定义信任库的一些行为 ( 例如:检验对方证书,针对异常做一些处理 ),我们可以使用 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; ifilter = new LinkedHashSet();
for(int i = 0; i < enabled.length; i++) {
if(enabled.indexOf("anon")<0){
filter.add(enabled);
}
}
serverSocket.setEnabledCipherSuites(filter.toArray(new String[filter.size()]));
|