在 Java (JSSE) 中使用默认 KeyStore 时如何提供特定的 TrustStore
Posted
技术标签:
【中文标题】在 Java (JSSE) 中使用默认 KeyStore 时如何提供特定的 TrustStore【英文标题】:How do I provide a specific TrustStore while using the default KeyStore in Java (JSSE) 【发布时间】:2013-02-22 13:07:25 【问题描述】:概述
JSSE 允许用户通过指定 javax.net.ssl.* 参数来提供默认的信任库和密钥库。我想为我的应用程序提供一个非默认的 TrustManager,同时允许用户像往常一样指定 KeyManager,但似乎没有任何方法可以实现这一点。
详情
http://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/JSSERefGuide.html#CustomizingStores
假设在 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 并传入一个包含我的证书 (javadoc) 的 KeyStore 来覆盖默认信任管理器。但是,要使用这个非默认信任管理器,我需要创建和初始化 SSLContext。问题就在这里。
通过调用init(..) 并同时传递 KeyManager 和 TrustManager 来初始化 SSLContext。但是,使用 javax.net.ssl.* 参数创建 KeyManager 的逻辑嵌入在默认 SSLContexts 的实现中——我找不到使用默认行为获取 KeyManager 或 KeyManagerFactory 的方法,同时还指定了非默认 TrustManager 或 TrustManagerFactory。因此,似乎不可能使用例如适当的操作系统特定的钥匙串实现,同时还提供用于验证远程服务器的根证书。
【问题讨论】:
澄清一下,我不想重新实现默认行为(例如,通过复制 sun.security.ssl.SSLContextImpl 或 javax.net.ssl.DefaultSSLContext 的实现)作为默认加载行为在不同平台上可能会有所不同,并且可能会在未来的 JVM 版本中发生变化。我正在专门寻找一种重用默认行为的方法,例如通过从默认 SSLContext 中提取 KeyManager。 【参考方案1】:编写具有默认行为的 KeyManager 并不难。这只是几行代码。令人惊讶的是,SSLContext 的行为并非都像 w.r.t 那样。 KeyManager,就像他们在 w.r.t.信任管理器。 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);
【讨论】:
如您所见,不同的 JSSE 实现有不同的行为,我不想实现自己的 KeyManager,然后在 JVM、体系结构和 JSSE 实现的每种可能组合上对其进行测试。顺便说一句,JDK7 的默认实现只有不到 100 行代码。 @StephenNelson 嗯,我的 21 岁:-| 你的没有 cmets 或处理 PKCS11 :-) 否则它很有用,谢谢。 @StephenNelson 它应该处理 PKCS#11,如果那是指定的 javax.net.ssl.keyStoreType。【参考方案2】:听起来您面临与this question 类似的问题,因为在SSLContext.init(...)
中将null
用于信任管理器参数会恢复为默认信任管理器,而对于密钥管理器则不会。
话虽如此,使用默认系统属性初始化 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 系统属性指定,则 KeyManager 由默认 SSLContext 将是用于管理指定密钥库的 KeyManager 实现。"
这在这里并不适用,因为您使用的是自定义SSLContext
,而不是默认的(即使您正在自定义其中的一部分)。
无论如何,Oracle JSSE 参考指南和 IBM JSSE 参考指南在这个主题上有所不同。 (我不确定其中有多少是“标准”,以及原则上是否应该与另一个兼容,但显然情况并非如此。)
两个“创建SSLContext
对象”部分几乎相同,但它们不同。
Oracle JSSE Reference guide 说:
如果 KeyManager[] 参数为空,那么一个空的 KeyManager 将 为这个上下文定义。
IBM JSSE Reference guide 说:
如果 KeyManager[] 参数为空,则安装的安全 提供者将被搜索最高优先级的实现 KeyManagerFactory,一个合适的 KeyManager 将从那里得到 获得。
不幸的是,如果您希望在具有不同规范的实现之间具有相同的行为,您将不得不编写一些代码,即使这实际上是在复制其中一个实现已经在做的事情。
【讨论】:
不是一个理想的答案,因为它需要重新实现默认行为而不是重用它,但非常有用。感谢您提供有关 jsslutils 的提示。 很遗憾,没有办法重用一半的 SSL 上下文。感谢您的详细回答。以上是关于在 Java (JSSE) 中使用默认 KeyStore 时如何提供特定的 TrustStore的主要内容,如果未能解决你的问题,请参考以下文章
在 WAS 9 上使用 Apache HttpClient 和 IBM Jsse2 的 SSLHandshakeException