6

概述

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 ) 的 KeyStore 来覆盖默认信任管理器。但是,要使用这个非默认信任管理器,我需要创建并初始化一个 SSLContext。问题就在这里。

通过调用init(..)并同时传递 KeyManager 和 TrustManager 来初始化 SSLContext。但是,使用 javax.net.ssl.* 参数创建 KeyManager 的逻辑嵌入在默认 SSLContexts 的实现中——我找不到使用默认行为获取 KeyManager 或 KeyManagerFactory 的方法,同时还指定了非默认 TrustManager 或 TrustManagerFactory。因此,似乎不可能使用例如适当的操作系统特定的钥匙串实现,同时还提供用于验证远程服务器的根证书。

4

2 回答 2

5

听起来您面临与此问题类似的问题,因为使用信任管理器null参数SSLContext.init(...)恢复为默认信任管理器,而密钥管理器则没有。

话虽如此,使用默认系统属性初始化 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()也可能很有趣。)

编辑:(根据您的附加评论)

恐怕当您只想自定义一半的SSLContext. 您在 Oracle JSSE 文档中链接到的部分说:“如果由 javax.net.ssl.keyStore 系统属性和适当的 javax.net.ssl.keyStorePassword 系统属性指定密钥库,则由默认 SSLContext 创建的 KeyManager将是用于管理指定密钥库的 KeyManager 实现。 ” 这在这里并不适用,因为您使用的是 custom SSLContext,而不是默认的(即使您正在自定义其中的一部分)。

无论如何,Oracle JSSE 参考指南和 IBM JSSE 参考指南在这个主题上有所不同。(我不确定其中有多少是“标准”,以及原则上是否应该与另一个兼容,但显然情况并非如此。)

两个“创建SSLContext对象”部分几乎相同,但它们不同。

Oracle JSSE 参考指南说:

如果 KeyManager[] 参数为 null,则将为该上下文定义一个空的 KeyManager。

IBM JSSE 参考指南说:

如果 KeyManager[] 参数为空,将在已安装的安全提供程序中搜索 KeyManagerFactory 的最高优先级实现,从中获取适当的 KeyManager。

不幸的是,如果您希望在具有不同规范的实现之间具有相同的行为,您将不得不编写一些代码,即使这实际上是在复制其中一个实现已经做的事情。

于 2013-03-07T12:13:03.947 回答
3

编写具有默认行为的 KeyManager 并不难。这只是几行代码。令人惊讶的是,SSLContexts 在 KeyManager 中的行为并不像在 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);
于 2013-03-07T12:11:03.723 回答