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

标签 java ssl client-certificates jsse

概览

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

详情

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

假设在 unix 机器上我想允许用户使用 pkcs12 keystore 进行身份验证,而在 OS X 上我想允许用户使用系统钥匙串(keychain)。在 OS X 上,应用程序可能会按如下方式启动:

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

这会很好地工作:当应用程序访问需要相互身份验证(客户端证书身份验证)的 https 服务器时,系统将提示用户允许访问其钥匙串(keychain)。

问题

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

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

最佳答案

听起来您遇到的问题与 this question 类似,因为在 SSLContext.init(...) 中对 trustmanager 参数使用 null 会恢复到默认的信任管理器,而 key 管理器则不会。

话虽如此,使用默认系统属性初始化 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 文档中链接到的部分说,“如果 keystore 是由 javax.net.ssl.keyStore 系统属性和适当的 javax.net.ssl.keyStorePassword 系统属性指定的,则由创建的 KeyManager默认的 SSLContext 将是用于管理指定 keystore 的 KeyManager 实现。” 这在这里并不适用,因为您使用的是自定义 SSLContext,而不是默认的(即使您正在自定义其中的一部分)。

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

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

Oracle JSSE Reference guide说:

If the KeyManager[] parameter is null, then an empty KeyManager will be defined for this context.

IBM JSSE Reference guide说:

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.

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

关于java - 在 Java (JSSE) 中使用默认 KeyStore 时如何提供特定的 TrustStore,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15269419/

相关文章:

api - 安全的 Web 身份验证和 API 访问

java - 如何从客户端强制使用客户端身份验证证书

java - 使用 if 语句依次执行这两个代码

java - 在 java.util.Date 和 java.time.Instant 之间转换古代日期时出现差异

java - 蛮力数独算法

ssl - NGINX 不会监听 443 端口

Python ssl 登录卡在 Debian 上

java - Firebase 数据未显示在我的 RecyclerView 上

PHP cURL 调用返回错误 56 和 NSS 错误 -12195

c# - 具有 ClientCredentials 属性的 CreateChannel