替换正在运行的java应用程序中的证书

问题描述 投票:0回答:2

我有一个打开 SSLServerSocket 的 java 应用程序。为了创建 SSLServerSocket,它使用从数据库加载的 KeyStore。我们经常会滚动数据库中的证书。

应用程序现在的设置方式,它会永远保留 SSLServerSocket,并且永远不会开始使用新证书,除非手动重新启动。

java有办法热替换SSLServerSocket使用的证书吗?如果不是,在这种情况下公认的最佳实践是什么?

java ssl jks
2个回答
1
投票

这是可能的,但不支持开箱即用。我的一个项目也面临着同样的挑战。我通过将 KeyManager 和 TrustManager 实例包装到委托实例中解决了这个问题,并且添加了一种附加方法来在我想要替换为旧的时设置内部 KeyManager 和 TrustManager。所以我认为你有两个选择,请参阅下面的详细信息:

选项1

仅使用纯java代码并复制下面的KeyManager和TrustManager。

import javax.net.ssl.SSLEngine;
import javax.net.ssl.X509ExtendedKeyManager;
import java.net.Socket;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Objects;

public final class HotSwappableX509ExtendedKeyManager extends X509ExtendedKeyManager {

    private X509ExtendedKeyManager keyManager;

    public HotSwappableX509ExtendedKeyManager(X509ExtendedKeyManager keyManager) {
        this.keyManager = keyManager;
    }

    @Override
    public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
        return keyManager.chooseClientAlias(keyType, issuers, socket);
    }

    @Override
    public String[] getClientAliases(String keyType, Principal[] issuers) {
        return keyManager.getClientAliases(keyType, issuers);
    }

    @Override
    public String chooseEngineClientAlias(String[] keyTypes, Principal[] issuers, SSLEngine sslEngine) {
        return keyManager.chooseEngineClientAlias(keyTypes, issuers, sslEngine);
    }

    @Override
    public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
        return keyManager.chooseServerAlias(keyType, issuers, socket);
    }

    @Override
    public String[] getServerAliases(String keyType, Principal[] issuers) {
        return keyManager.getServerAliases(keyType, issuers);
    }

    @Override
    public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine sslEngine) {
        return keyManager.chooseEngineServerAlias(keyType, issuers, sslEngine);
    }

    @Override
    public PrivateKey getPrivateKey(String alias) {
        return keyManager.getPrivateKey(alias);
    }

    @Override
    public X509Certificate[] getCertificateChain(String alias) {
        return keyManager.getCertificateChain(alias);
    }

    public void setKeyManager(X509ExtendedKeyManager keyManager) {
        this.keyManager = Objects.requireNonNull(keyManager);
    }

}

import javax.net.ssl.SSLEngine;
import javax.net.ssl.X509ExtendedTrustManager;
import java.net.Socket;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Objects;

public class HotSwappableX509ExtendedTrustManager extends X509ExtendedTrustManager {

    private X509ExtendedTrustManager trustManager;

    public HotSwappableX509ExtendedTrustManager(X509ExtendedTrustManager trustManager) {
        this.trustManager = trustManager;
    }

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
        trustManager.checkClientTrusted(chain, authType, socket);
    }

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        trustManager.checkClientTrusted(chain, authType);
    }

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine) throws CertificateException {
        trustManager.checkClientTrusted(chain, authType, sslEngine);
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
        trustManager.checkServerTrusted(chain, authType, socket);
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        trustManager.checkServerTrusted(chain, authType);
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine) throws CertificateException {
        trustManager.checkServerTrusted(chain, authType, sslEngine);
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        X509Certificate[] acceptedIssuers = trustManager.getAcceptedIssuers();
        return Arrays.copyOf(acceptedIssuers, acceptedIssuers.length);
    }

    public void setTrustManager(X509ExtendedTrustManager trustManager) {
        this.trustManager = Objects.requireNonNull(trustManager);
    }

}

并使用上面的包装器,如下面的代码片段:

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Collections;
import java.util.Objects;
import java.security.KeyStore;

public class App {
    
    public static void main(String[] args) throws Exception {
        Path keyStorePath = Paths.get("/path/to/keystore.jks");
        InputStream keyStoreInputStream = Files.newInputStream(keyStorePath, StandardOpenOption.READ);
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(keyStoreInputStream, "secret".toCharArray());

        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        keyManagerFactory.init(keyStore, "secret".toCharArray());
        KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();

        Path trustStorePath = Paths.get("/path/to/truststore.jks");
        InputStream trustStoreInputStream = Files.newInputStream(trustStorePath, StandardOpenOption.READ);
        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        trustStore.load(trustStoreInputStream, "secret".toCharArray());

        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(keyStore);
        TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

        HotSwappableX509ExtendedKeyManager hotSwappableX509ExtendedKeyManager = new HotSwappableX509ExtendedKeyManager((X509ExtendedKeyManager) keyManagers[0]);
        HotSwappableX509ExtendedTrustManager hotSwappableX509ExtendedTrustManager = new HotSwappableX509ExtendedTrustManager((X509ExtendedTrustManager) trustManagers[0]);

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(new KeyManager[]{hotSwappableX509ExtendedKeyManager}, new TrustManager[]{hotSwappableX509ExtendedTrustManager}, null);
        SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();

        // use the sslServerSocketFactory
        // after some time update the key and trust material with the following snippet
        X509ExtendedKeyManager myNewKeyManager = ...;
        X509ExtendedTrustManager myNewTrustManager = ...;
        hotSwappableX509ExtendedKeyManager.setKeyManager(myNewKeyManager);
        hotSwappableX509ExtendedTrustManager.setTrustManager(myNewTrustManager);
        SSLSessionContext sslSessionContext = sslContext.getServerSessionContext()
        Collections.list(sslSessionContext.getIds()).stream()
                .map(sslSessionContext::getSession)
                .filter(Objects::nonNull)
                .forEach(SSLSession::invalidate);
    }
    
}

选项2

我已经在我自己的库中提供了选项 1,请参阅此处了解详细信息:GitHub - SSLContext Kickstart

将以下依赖项添加到您的项目中:

<dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>sslcontext-kickstart</artifactId>
    <version>7.4.4</version>
</dependency>

并使用以下代码片段:

import nl.altindag.ssl.SSLFactory;
import nl.altindag.ssl.util.SSLFactoryUtils;

import javax.net.ssl.SSLServerSocketFactory;
import java.nio.file.Paths;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class App {

    public static void main(String[] args) throws Exception {
        SSLFactory baseSslFactory = SSLFactory.builder()
                .withDummyIdentityMaterial()
                .withDummyTrustMaterial()
                .withSwappableIdentityMaterial()
                .withSwappableTrustMaterial()
                .build();

        SSLServerSocketFactory sslServerSocketFactory = baseSslFactory.getSslServerSocketFactory();

        Runnable sslUpdater = () -> {
            SSLFactory updatedSslFactory = SSLFactory.builder()
                    .withIdentityMaterial(Paths.get("/path/to/your/identity.jks"), "password".toCharArray())
                    .withTrustMaterial(Paths.get("/path/to/your/truststore.jks"), "password".toCharArray())
                    .build();

            SSLFactoryUtils.reload(baseSslFactory, updatedSslFactory);
        };

        // initial update of ssl material to replace the dummies
        sslUpdater.run();

        // update ssl material every hour    
        Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(sslUpdater, 1, 1, TimeUnit.HOURS);
    }
}

0
投票

我认为另一种解决方案是提供自定义的 X509ExtendedKeyManager 并实现“chooseEngineServerAlias”方法,例如:

SSLSession session = engine.getHandshakeSession();
if (session instanceof ExtendedSSLSession sessionEx)
    {
    List<SNIServerName> listNames = sessionEx.getRequestedServerNames();
    if (listNames.size() > 0)
        {
        String sHost = ((SNIHostName) listNames.get(0)).getAsciiName();
        // get a certificate for the host
        }
    }
return f_sServerAlias;
}
© www.soinside.com 2019 - 2024. All rights reserved.