在 Java (JSSE) 中使用默认 KeyStore 时如何提供特定的 TrustStore
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15269419/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I provide a specific TrustStore while using the default KeyStore in Java (JSSE)
提问by Stephen Nelson
Overview
概述
JSSE allows users to provide default trust stores and key stores by specifying javax.net.ssl.* parameters. I would like to provide a non-default TrustManager for my application, while allowing the user to specify the KeyManager as usual, but there doesn't seem to be any way to achieve this.
JSSE 允许用户通过指定 javax.net.ssl.* 参数来提供默认的信任库和密钥库。我想为我的应用程序提供一个非默认的 TrustManager,同时允许用户像往常一样指定 KeyManager,但似乎没有任何方法可以实现这一点。
Details
细节
Suppose on unix machines I want to allow the user to use a pkcs12 key store for authentication, while on OS X I want allow the user to use the system keychain. On OS X the application might be started as follows:
假设在 unix 机器上我希望允许用户使用 pkcs12 密钥库进行身份验证,而在 OS XI 上希望允许用户使用系统钥匙串。在 OS X 上,应用程序可能按如下方式启动:
java -Djavax.net.ssl.keyStore=NONE -Djavax.net.ssl.keyStoreType=KeychainStore \
-Djavax.net.ssl.keyStorePassword=- -jar MyApplication.jar
This will work fine: when the application accesses an https server that requires mutual authentication (client certificate authentication) then the user will be prompted to allow access to their keychain.
这将正常工作:当应用程序访问需要相互身份验证(客户端证书身份验证)的 https 服务器时,将提示用户允许访问他们的钥匙串。
The Problem
问题
Now suppose I want to bundle a self-signed certificate authority with my application. I can override the default trust manager by constructing a TrustManagerFactory and passing in a KeyStore containing my certificate (javadoc). However, to use this non-default trust manager I need to create and initialise an SSLContext. Here-in lies the problem.
现在假设我想将自签名证书颁发机构与我的应用程序捆绑在一起。我可以通过构建 TrustManagerFactory 并传入包含我的证书 ( javadoc)的 KeyStore 来覆盖默认信任管理器。但是,要使用这个非默认信任管理器,我需要创建并初始化一个 SSLContext。问题就在这里。
SSLContexts are initialised by calling init(..)and passing both a KeyManager and a TrustManager. However, logic for creating a KeyManager using the javax.net.ssl.* parameters is embedded in the implementation of the default SSLContexts -- I can't find a way to obtain a KeyManager or a KeyManagerFactory using the default behaviour while also specifying a non-default TrustManager or TrustManagerFactory. Thus, it seems that it is not possible to use, for example, the appropriate operating-system specific keychain implementation while also providing a root certificate for authenticating remote servers.
SSLContexts 通过调用init(..)并传递 KeyManager 和 TrustManager来初始化。但是,使用 javax.net.ssl.* 参数创建 KeyManager 的逻辑嵌入在默认 SSLContexts 的实现中——我找不到使用默认行为同时指定一个 KeyManager 或 KeyManagerFactory 的方法非默认 TrustManager 或 TrustManagerFactory。因此,似乎不可能使用,例如,适当的操作系统特定的钥匙串实现,同时还提供用于验证远程服务器的根证书。
采纳答案by Bruno
It sounds like you're facing a similar problem to this question, in that using null
for the trustmanager parameter in SSLContext.init(...)
reverts to the default trust manager, whereas it doesn't for the keymanager.
听起来您面临着与此问题类似的问题,null
因为在 trustmanager 参数中使用SSLContext.init(...)
会恢复为默认信任管理器,而对于密钥管理器则不会。
This being said, it's not that hard to initialise a KeyManager using the default system properties. Something like this should work (code written directly in this answer, so you might need to fix a few little things):
话虽如此,使用默认系统属性初始化 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 classmay also be of interest, more specifically getKeyStoreDefaultLoader()
.)
(这个实用程序类也可能很有趣,更具体地说getKeyStoreDefaultLoader()
。)
EDIT:(Following your additional comment)
编辑:(按照您的附加评论)
I'm afraid there doesn't seem to be a default behaviour for both the Oracle and the IBM JSSE when you want to customise only half of the SSLContext
. The section you link to in the Oracle JSSE documentation says, "If a keystore is specified by the javax.net.ssl.keyStore system property and an appropriate javax.net.ssl.keyStorePassword system property, then the KeyManager created by the default SSLContext will be a KeyManager implementation for managing the specified keystore."
This wouldn't really apply here, since you're using a custom SSLContext
, not the default one anyway (even if you're customising part of it).
恐怕当您只想自定义SSLContext
. 您在 Oracle JSSE 文档中链接到的部分说,“如果由 javax.net.ssl.keyStore 系统属性和适当的 javax.net.ssl.keyStorePassword 系统属性指定密钥库,则由默认 SSLContext 创建的 KeyManager将是用于管理指定密钥库的 KeyManager 实现。” 这在这里并不真正适用,因为您使用的是 custom SSLContext
,而不是默认的(即使您正在自定义它的一部分)。
Anyway, the Oracle JSSE reference guide and the IBM JSSE reference guide differ on this subject. (I'm not sure how much of this is meant to be "standard" and whether one should in principle be compliant with the other, but this is clearly not the case.)
无论如何,Oracle JSSE 参考指南和 IBM JSSE 参考指南在这个主题上有所不同。(我不确定其中有多少是“标准”的,以及原则上是否应该与另一个兼容,但显然情况并非如此。)
Both "Creating an SSLContext
Object" sections are almostidentical, but they are different.
两个“创建SSLContext
对象”部分几乎相同,但又有所不同。
The Oracle JSSE Reference guidesays:
在甲骨文JSSE参考指南说:
If the KeyManager[] parameter is null, then an empty KeyManager will be defined for this context.
如果 KeyManager[] 参数为空,则将为该上下文定义一个空的 KeyManager。
The IBM JSSE Reference guidesays:
在IBM JSSE参考指南说:
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.
如果 KeyManager[] 参数为空,则将搜索已安装的安全提供程序以查找 KeyManagerFactory 的最高优先级实现,从中将获得适当的 KeyManager。
Unfortunately, if you want the same behaviour across implementations that have different specifications, you'll have to write a bit of code, even if that's effectively duplicating what one of the implementations already does.
不幸的是,如果您希望在具有不同规范的实现之间具有相同的行为,则必须编写一些代码,即使这实际上是在复制其中一个实现已经执行的操作。
回答by user207421
It's not too hard to write a KeyManager that has the default behaviour. It's only a few lines of code. It's surprising that SSLContexts don't all behave like that w.r.t. the KeyManager, as they do w.r.t. the TrustManager. IBM's JSSE does behave like that. But it's not hard to synthesize yourself:
编写具有默认行为的 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);