如何在Java(JSSE)中使用默认的KeyStore时提供特定的TrustStore

概观

JSSE允许用户通过指定javax.net.ssl。*参数来提供默认信任存储和密钥存储。 我想为我的应用程序提供一个非默认的TrustManager,同时允许用户像往常一样指定KeyManager,但似乎没有任何方法可以实现这一点。

细节

http://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/JSSERefGuide.html#CustomizingStores

假设在unix机器上我想允许用户使用pkcs12密钥存储区进行身份validation,而在OS XI上则希望允许用户使用系统密钥链。 在OS X上,应用程序可能会按如下方式启动:

java -Djavax.net.ssl.keyStore=NONE -Djavax.net.ssl.keyStoreType=KeychainStore \ -Djavax.net.ssl.keyStorePassword=- -jar MyApplication.jar 

这将正常工作:当应用程序访问需要相互身份validation(客户端证书身份validation)的https服务器时,将提示用户允许访问其钥匙串。

问题

现在假设我想将自签名证书颁发机构与我的应用程序捆绑在一起。 我可以通过构造一个TrustManagerFactory并传入一个包含我的证书( javadoc )的KeyStore来覆盖默认的信任管理器。 但是,要使用此非默认信任管理器,我需要创建并初始化SSLContext。 这里存在问题。

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

听起来你在这个问题上遇到了类似的问题,因为在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); 

( 此实用程序类也可能是有意义的,更具体地说是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对象 ”部分几乎完全相同,但它们是不同的。

Oracle JSSE参考指南说:

如果KeyManager []参数为null,则将为此上下文定义空KeyManager。

IBM JSSE参考指南说:

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

遗憾的是,如果您希望在具有不同规范的实现中使用相同的行为,则必须编写一些代码,即使这有效地复制了其中一个实现已经执行的操作。

编写具有默认行为的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);