如何在Java(JSSE)中使用默认的KeyStore时提供特定的TrustStore
作者:互联网
概观
JSSE允许用户通过指定javax.net.ssl.*参数来提供默认信任存储和密钥存储.我想为我的应用程序提供一个非默认的TrustManager,同时允许用户像往常一样指定KeyManager,但似乎没有任何方法可以实现这一点.
细节
假设在unix机器上我想允许用户使用pkcs12密钥库进行身份验证,而在OS X上我希望允许用户使用系统密钥链.在OS X上,应用程序可能会按如下方式启动:
java -Djavax.net.ssl.keyStore=NONE -Djavax.net.ssl.keyStoreType=KeychainStore \
-Djavax.net.ssl.keyStorePassword=- -jar MyApplication.jar
这将正常工作:当应用程序访问需要相互身份验证(客户端证书身份验证)的https服务器时,将提示用户允许访问其钥匙串.
问题
现在假设我想将自签名证书颁发机构与我的应用程序捆绑在一起.我可以通过构造一个TrustManagerFactory并传入一个包含我的证书的KeyStore来覆盖默认的信任管理器(javadoc).但是,要使用此非默认信任管理器,我需要创建并初始化SSLContext.这里存在问题.
通过调用init(..)并传递KeyManager和TrustManager来初始化SSLContexts.但是,使用javax.net.ssl.*参数创建KeyManager的逻辑嵌入在默认SSLContexts的实现中 – 我找不到使用默认行为获取KeyManager或KeyManagerFactory的方法,同时还指定了非默认的TrustManager或TrustManagerFactory.因此,似乎不可能使用例如适当的操作系统特定的钥匙串实现,同时还提供用于验证远程服务器的根证书.
解决方法:
听起来你遇到类似于this question的问题,因为在SSLContext.init(…)中使用null for trustmanager参数会恢复为默认的信任管理器,而对于keymanager则不会.
这就是说,使用默认系统属性初始化KeyManager并不困难.这样的东西应该可以工作(代码直接写在这个答案中,所以你可能需要修改一些小东西):
String provider = System.getProperty("javax.net.ssl.keyStoreProvider");
String keystoreType = System.getProperty("javax.net.ssl.keyStoreType", KeyStore.getDefaultType());
KeyStore ks = null;
if (provider != null) {
ks = KeyStore.getInstance(keystoreType, provider);
} else {
ks = KeyStore.getInstance(keystoreType);
}
InputStream ksis = null;
String keystorePath = System.getProperty("javax.net.ssl.keyStore");
String keystorePassword = System.getProperty("javax.net.ssl.keyStorePassword");
if (keystorePath != null && !"NONE".equals(keystorePath)) {
ksis = new FileInputStream(keystorePath);
}
try {
ks.load(ksis, keystorePassword.toCharArray());
} finally {
if (ksis != null) { ksis.close(); }
}
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, keystorePassword.toCharArray());
// Note that there is no property for the key password itself, which may be different.
// We're using the keystore password too.
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), ..., null);
(This utility class也可能是有意义的,更具体地说是getKeyStoreDefaultLoader().)
编辑:(在您的附加评论后)
当您想要仅自定义一半SSLContext时,我担心Oracle和IBM JSSE似乎都没有默认行为.您在Oracle JSSE文档中链接到的部分说:“如果密钥库由javax.net.ssl.keyStore系统属性和适当的javax.net.ssl.keyStorePassword系统属性指定,则由默认SSLContext创建的KeyManager将是用于管理指定密钥库的KeyManager实现.“
这不适用于此,因为您使用的是自定义SSLContext,而不是默认的SSLContext(即使您正在自定义其中的一部分).
无论如何,Oracle JSSE参考指南和IBM JSSE参考指南在这个主题上有所不同. (我不确定这有多少意味着“标准”,原则上是否应该与另一方相符,但事实显然并非如此.)
“创建SSLContext对象”部分几乎完全相同,但它们是不同的.
If the KeyManager[] parameter is null, then an empty KeyManager will
be defined for this context.
If the KeyManager[] paramater is null, the installed security
providers will be searched for the highest-priority implementation of
the KeyManagerFactory, from which an appropriate KeyManager will be
obtained.
遗憾的是,如果您希望在具有不同规范的实现中具有相同的行为,则必须编写一些代码,即使这有效地复制了其中一个实现已经执行的操作.
标签:java,ssl,client-certificates,jsse 来源: https://codeday.me/bug/20190517/1122940.html