概观
JSSE允许用户通过指定的javax.net.ssl提供默认的信任存储区和密钥存储。*参数。 我想我的应用程序提供了一个非默认的TrustManager,同时允许用户指定的KeyManager像往常一样,但似乎没有什么办法来实现这一目标。
细节
http://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/JSSERefGuide.html#CustomizingStores
假设UNIX机器上我想允许用户使用PKCS12密钥存储进行身份验证,而在OS XI希望允许用户使用系统钥匙串。 在OS X如下的应用程序可能启动:
java -Djavax.net.ssl.keyStore=NONE -Djavax.net.ssl.keyStoreType=KeychainStore \
-Djavax.net.ssl.keyStorePassword=- -jar MyApplication.jar
这将很好地工作:当应用程序访问需要相互身份验证(客户端证书身份验证),那么用户将被提示允许访问自己的钥匙串HTTPS服务器。
问题
现在假设我要捆绑在我的申请自签名的证书颁发机构。 我可以通过构建的TrustManagerFactory并传递包含我的证书(密钥库覆盖缺省信任管理器的javadoc )。 但是,要使用这种非默认的信任经理,我需要创建和初始化的SSLContext中。 在这里,在这就是问题所在。
SSLContexts通过调用初始化的init(..) ,并通过既有的KeyManager和的TrustManager。 然而,逻辑使用javax.net.ssl中创建的KeyManager *参数嵌入在默认SSLContexts实施 - 我不能找到一种方式来获得的KeyManager或使用默认行为,同时指定一个的KeyManagerFactory非默认的TrustManager或的TrustManagerFactory。 因此,似乎是不可能的使用,例如,适当的操作系统特定钥匙扣实施,同时还提供用于认证远程服务器根证书。
这听起来像你面临着类似的问题这个问题 ,在使用null
在的TrustManager参数SSLContext.init(...)
恢复为默认信任管理器,而它不为的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);
( 此实用工具类也可能是感兴趣,更具体getKeyStoreDefaultLoader()
编辑:(追随你的附加评论)
恐怕似乎没有成为在Oracle和IBM的JSSE默认的行为,当你想自定义的只有一半SSLContext
。 您在Oracle JSSE文档中链接到一节说,“ 如果是由javax.net.ssl.keyStore系统属性和适当的javax.net.ssl.keyStorePassword系统属性,那么通过的KeyManager默认的SSLContext创建指定的密钥库将用于管理指定密钥库的KeyManager实现。“这不会真正适用在这里,因为你正在使用自定义SSLContext
,而不是默认的一个无论如何(即使你自定义的一部分)。
无论如何,在Oracle JSSE参考指南和IBM JSSE参考指南对这个问题有所不同。 (我不知道有多少,这是意味着是“标准”和一个是否原则上应符合对方,但这显然不是这样的。)
这两个“ 创建SSLContext
对象 ”部分几乎是相同的,但它们是不同的。
在甲骨文JSSE参考指南说:
如果的KeyManager []参数为空,则空的KeyManager将这个上下文中定义。
在IBM JSSE参考指南说:
如果的KeyManager [] paramater为空,已安装的安全供应商将要搜索的的KeyManagerFactory,从适当的KeyManager将获得的最高优先级执行。
不幸的是,如果你想在安装了不同规格的实现相同的行为,你必须写一些代码,即使这是有效地复制什么实现的一个已经这样做了。
这不是太难写有默认行为的KeyManager。 这只是几行代码。 这是令人惊讶的是SSLContexts不都那样做WRT的的KeyManager,因为他们做的WRT的TrustManager。 IBM的JSSE不那样做的。 但它不是很难合成自己:
SSLContext context = SSLContext.getInstance("TLS");
String keyStore = System.getProperty("javax.net.ssl.keyStore");
String keyStoreType = System.getProperty("javax.net.ssl.keyStoreType", KeyStore.getDefaultType());
String keyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword","");
KeyManager[] kms = null;
if (keyStore != null)
{
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
KeyStore ks = KeyStore.getInstance(keyStoreType);
if (keyStore != null && !keyStore.equals("NONE")) {
fs = new FileInputStream(keyStore);
ks.load(fs, keyStorePassword.toCharArray());
if (fs != null)
fs.close();
char[] password = null;
if (keyStorePassword.length() > 0)
password = keyStorePassword.toCharArray();
kmf.init(ks,password);
kms = kmf.getKeyManagers();
}
context.init(kms,null,null);
文章来源: How do I provide a specific TrustStore while using the default KeyStore in Java (JSSE)