替换 运行 java 应用程序中的证书
Replace certificates in a running java application
我有一个打开 SSLServerSocket 的 java 应用程序。要创建该 SSLServerSocket,它使用从数据库加载的 KeyStore。我们会经常更新数据库中的证书。
应用程序现在的设置方式,它永远保留 SSLServerSocket 并且永远不会开始使用新证书,除非它手动重新启动。
java 是否有办法热替换 SSLServerSocket 使用的证书?如果不是,在这种情况下公认的最佳做法是什么?
这是可能的,但开箱即用不支持。我的一个项目也遇到了同样的挑战。我通过将 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>6.6.1</version>
</dependency>
并使用以下代码段:
import nl.altindag.ssl.SSLFactory;
import nl.altindag.ssl.util.KeyManagerUtils;
import nl.altindag.ssl.util.SSLSessionUtils;
import nl.altindag.ssl.util.TrustManagerUtils;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) throws Exception {
SSLFactory sslFactory = SSLFactory.builder()
.withIdentityMaterial(Paths.get("/path/to/keystore.jks"), "secret".toCharArray())
.withTrustMaterial(Paths.get("/path/to/truststore.jks"), "secret".toCharArray())
.withSwappableIdentityMaterial()
.withSwappableTrustMaterial()
.build();
X509ExtendedKeyManager hotSwappableX509ExtendedKeyManager = sslFactory.getKeyManager().get();
X509ExtendedTrustManager hotSwappableX509ExtendedTrustManager = sslFactory.getTrustManager().get();
SSLServerSocketFactory sslServerSocketFactory = sslFactory.getSslServerSocketFactory();
X509ExtendedKeyManager myNewKeyManager = ...;
X509ExtendedTrustManager myNewTrustManager = ...;
KeyManagerUtils.swapKeyManager(hotSwappableX509ExtendedKeyManager, myNewKeyManager);
TrustManagerUtils.swapTrustManager(hotSwappableX509ExtendedTrustManager, myNewTrustManager);
SSLSessionUtils.invalidateCaches(sslFactory);
}
}
我有一个打开 SSLServerSocket 的 java 应用程序。要创建该 SSLServerSocket,它使用从数据库加载的 KeyStore。我们会经常更新数据库中的证书。
应用程序现在的设置方式,它永远保留 SSLServerSocket 并且永远不会开始使用新证书,除非它手动重新启动。
java 是否有办法热替换 SSLServerSocket 使用的证书?如果不是,在这种情况下公认的最佳做法是什么?
这是可能的,但开箱即用不支持。我的一个项目也遇到了同样的挑战。我通过将 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>6.6.1</version>
</dependency>
并使用以下代码段:
import nl.altindag.ssl.SSLFactory;
import nl.altindag.ssl.util.KeyManagerUtils;
import nl.altindag.ssl.util.SSLSessionUtils;
import nl.altindag.ssl.util.TrustManagerUtils;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) throws Exception {
SSLFactory sslFactory = SSLFactory.builder()
.withIdentityMaterial(Paths.get("/path/to/keystore.jks"), "secret".toCharArray())
.withTrustMaterial(Paths.get("/path/to/truststore.jks"), "secret".toCharArray())
.withSwappableIdentityMaterial()
.withSwappableTrustMaterial()
.build();
X509ExtendedKeyManager hotSwappableX509ExtendedKeyManager = sslFactory.getKeyManager().get();
X509ExtendedTrustManager hotSwappableX509ExtendedTrustManager = sslFactory.getTrustManager().get();
SSLServerSocketFactory sslServerSocketFactory = sslFactory.getSslServerSocketFactory();
X509ExtendedKeyManager myNewKeyManager = ...;
X509ExtendedTrustManager myNewTrustManager = ...;
KeyManagerUtils.swapKeyManager(hotSwappableX509ExtendedKeyManager, myNewKeyManager);
TrustManagerUtils.swapTrustManager(hotSwappableX509ExtendedTrustManager, myNewTrustManager);
SSLSessionUtils.invalidateCaches(sslFactory);
}
}