1

我已经阅读了很多关于这个主题的内容,这似乎是不可能的,但只是为了确保我想要更多的意见。

用例:以编程方式与一个或多个 https 服务连接的 Web 应用程序,这些服务是动态的,并且证书会经常更新。

应用程序应该做的是使用新证书更新 TrustStore 并在不重新启动应用程序的情况下使用它们。重要的是,不应实施任何新代码来进行 https 连接(因此,它应该无缝集成)。

我已经尝试(没有运气)覆盖默认的 Java TrustManager,任何帮助将不胜感激。

编辑:我已经尝试了评论/答案中提出的一些解决方案,但之后我仍然需要重新启动我的 tomcat

4

3 回答 3

2

虽然它发布在其他 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 中:

  1. 通过文件的修改时间戳检查 TrustStore 是否被修改。如果已修改,则使用新的 TrustStore 创建一个新的 TrustManager。
  2. 调用嵌入式 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() 的第一个参数。

于 2019-06-25T02:49:53.940 回答
0

我在这里发布了一个类似的答案:Reloading a java.net.http.HttpClient's SSLContext

基本上,您需要的是一个自定义的信任管理器,它包裹着实际的信任管理器,它能够在需要时交换实际的信任管理器,例如当信任库更新时。

您可以在此处的链接上找到完整的答案,下面是一个小片段,它应该可以为您解决问题,它在幕后使用名为 hot swappable trustmanager 的包装器 trustmanager

SSLFactory sslFactory = SSLFactory.builder()
          .withSwappableTrustMaterial()
          .withTrustMaterial("truststore.jks", "password".toCharArray())
          .build();
          
HttpClient httpClient = HttpClient.newBuilder()
          .sslParameters(sslFactory.getSslParameters())
          .sslContext(sslFactory.getSslContext())
          .build()

// execute https request
HttpResponse<String> response = httpClient.send(aRequest, HttpResponse.BodyHandlers.ofString());

// swap trust materials and reuse existing http client
TrustManagerUtils.swapTrustManager(sslFactory.getTrustManager().get(), anotherTrustManager);

// Cleanup old ssl sessions by invalidating them all. Forces to use new ssl sessions which will be created by the swapped TrustManager
SSLSessionUtils.invalidateCaches(sslFactory.getSslContext());

HttpResponse<String> response = httpClient.send(aRequest, HttpResponse.BodyHandlers.ofString());
于 2022-02-24T23:06:40.563 回答
-3

似乎这个问题已经在这里得到解答Programmatically Import CA trust cert into existing keystore file without using keytool

我认为问题在于,信任库和密钥库实际上是相同的东西,但它们与密钥管理器一起用于私钥(客户端身份验证(通常不使用且没有真正的签名授权))和信任管理器用于服务器身份验证(对于完整的 tls 总是完成)联系)。

在这方面,您仍然以编程方式将密钥库用于信任库。希望我在这方面是正确的。

于 2016-11-02T15:54:52.370 回答