虽然它发布在其他 SO 帖子的评论中,但我想将此方法作为潜在答案提及,因为它也帮助我解决了这个问题。
这篇文章告诉我们如何创建一个新的 SSLContext,它包含围绕标准 X509TrustManager 的包装器 (ReloadableX509TrustManager):https ://jcalcote.wordpress.com/2010/06/22/managing-a-dynamic-java-trust-store/
每当客户端/服务器正在被认证(使用 checkClientTrusted/checkServerTrusted)时,X509ReloadableTrustManager 将调用 X509TrustManager 内部的相关方法。如果失败(抛出 CertificateException),那么它将在再次尝试之前重新加载 TrustStore。每次“重新加载”实际上都将 X509TrustManager 替换为一个新实例,因为我们无法触及其中的证书。
就个人而言,我与这篇文章略有不同。在 ReloadableX509TrustManager 的 checkClientTrusted/checkServerTrusted 中:
- 通过文件的修改时间戳检查 TrustStore 是否被修改。如果已修改,则使用新的 TrustStore 创建一个新的 TrustManager。
- 调用嵌入式 X509TrustManager 的 checkClientTrusted/checkServerTrusted。
为了减少文件 I/O 请求的数量,我跟踪了最后一次检查 TrustStore 的时间戳,以将 TrustStore 上的轮询间隔限制为至少 15 秒。
我相信我的方法稍微好一点,因为它允许使用当前的 TrustStore 进行身份验证,它也可以从中删除证书。原始方法仍然允许应用程序持续信任客户端/服务器,即使相关证书被删除。
编辑:回想起来,我认为重新加载过程应该是线程安全的,因为我找不到任何表明 X509TrustManager 中的 checkClientTrusted() 和 checkServerTrusted() 方法可能在设计时没有考虑线程安全。事实上,默认 X509TrustManagerImpl 类的 checkTrustedInit() 方法有一些同步块——这可能暗示这些函数必须是线程安全的。
编辑 2021/04/10:这是一个示例实现:
package com.test.certificate;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchProviderException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.List;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ReloadableX509TrustManager implements X509TrustManager {
private static final Logger logger = LoggerFactory.getLogger(ReloadableX509TrustManager.class);
private X509TrustManager trustManager;
private static final String KEYSTORE_RUNTIME_FORMAT = "JKS";
private static final String CERTIFICATE_ENTRY_FORMAT = "X.509";
public ReloadableX509TrustManager() throws Exception {
reload();
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
try {
reload();
} catch (Exception ex) {
logger.warn("Failed to reload TrustStore due to " + ex, ex);
}
trustManager.checkClientTrusted(chain, authType);
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
try {
reload();
} catch (Exception ex) {
logger.warn("Failed to reload TrustStore due to " + ex, ex);
}
trustManager.checkServerTrusted(chain, authType);
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return trustManager.getAcceptedIssuers();
}
/**
* Reloads the inner TrustStore.
* For performance, reloading of the TrustStore will only be done if there is a change.
* @throws Exception
*/
public synchronized void reload() throws Exception {
if (!isUpdated())
return;
KeyStore trustStore = KeyStore.getInstance(KEYSTORE_RUNTIME_FORMAT);
trustStore.load(null, null);
List<TrustedCertificate> certs = getCertificates();
CertificateFactory cf = CertificateFactory.getInstance(CERTIFICATE_ENTRY_FORMAT);
for (TrustedCertificate cert : certs) {
InputStream is = new ByteArrayInputStream(cert.getCertificate());
Certificate certEntry;
try {
certEntry = cf.generateCertificate(is);
} catch (CertificateException e) {
logger.error("Failed to generate certificate " + cert.getAliasForKeystore() + " due to: " + e);
continue;
} finally {
is.close();
}
try {
trustStore.setCertificateEntry(cert.getAliasForKeystore(), certEntry);
} catch (KeyStoreException e) {
logger.error("Failed to insert certificate " + cert.getAliasForKeystore() + " due to: " + e);
continue;
}
}
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
// Locate the X509TrustManager and get a reference to it.
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
for (TrustManager tm : trustManagers) {
if (tm instanceof X509TrustManager) {
trustManager = (X509TrustManager)tm;
return;
}
}
throw new NoSuchProviderException("X509TrustManager not available from TrustManagerFactory.");
}
/**
* Indicates whether the TrustStore was updated.
* @return Whether the TrustStore was updated.
*/
private boolean isUpdated() {
// TODO Write your logic to check whether the TrustStore was updated.
// If disk I/O is used, it may be good to limit how often the file is accessed for performance.
return false;
}
/**
* Returns a list of certificates from the TrustStore.
* @return A list of certificates from the TrustStore.
* @throws Exception
*/
private List<TrustedCertificate> getCertificates() throws Exception {
// TODO Write your logic to retrieve all certificates from the TrustStore.
return ;
}
}
然后生成一个使用新 TrustManager 的新 SSLContext:
private SSLContext initContext() throws Exception {
TrustManager[] trustManagers = { getTrustManager() };
//Initialize a new SSLContext, with our custom TrustManager.
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagers, null);
return sslContext;
}
如果您需要可重新加载的 KeyStore,可以执行类似的操作。但不是实现 X509TrustManager,而是该类应实现 X509KeyManager。此自定义 KeyManager 作为数组传递给 sslContext.init() 的第一个参数。