I came across your question yesterday and was working on a prototype. I think it might be possible in your case. However I just tried it out locally with a http client and a server which I was able to change the certificates at runtime without the need of restarting these applications or recreating the SSLContext.
Option 1
From your question I can understand that you are reading PEM
files from somewhere and converting it to something else and at the end you are using a SSLContext
. In that case I would assume you are creating a KeyManager and a TrustManager. If thats the case what you need to do is create a custom implementation of the KeyManager and TrustManager as a wrapper class to delegate the method calls to the actual KeyManager and TrustManager within the wrapper class. And also add a setter method to change the internal KeyManager and TrustManager when the certificates get updated.
In your case that would be a file-watcher which gets triggered when the PEM files have been changed. In that case you only need to regenerate the KeyManager and TrustManager with the new certificates and give it to the wrapped class by calling the setter method. Below is an example code snippet what you could use:
HotSwappableX509ExtendedKeyManager
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 = Objects.requireNonNull(keyManager);
}
@Override
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
return keyManager.chooseClientAlias(keyType, issuers, socket);
}
@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 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);
}
@Override
public String[] getClientAliases(String keyType, Principal[] issuers) {
return keyManager.getClientAliases(keyType, issuers);
}
@Override
public String[] getServerAliases(String keyType, Principal[] issuers) {
return keyManager.getServerAliases(keyType, issuers);
}
public void setKeyManager(X509ExtendedKeyManager keyManager) {
this.keyManager = Objects.requireNonNull(keyManager);
}
}
HotSwappableX509ExtendedTrustManager
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 = Objects.requireNonNull(trustManager);
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
trustManager.checkClientTrusted(chain, authType);
}
@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, SSLEngine sslEngine) throws CertificateException {
trustManager.checkClientTrusted(chain, authType, sslEngine);
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
trustManager.checkServerTrusted(chain, authType);
}
@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, 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);
}
}
Usage
// Your key and trust manager created from the pem files
X509ExtendedKeyManager aKeyManager = ...
X509ExtendedTrustManager aTrustManager = ...
// Wrapping it into your hot swappable key and trust manager
HotSwappableX509ExtendedKeyManager swappableKeyManager = new HotSwappableX509ExtendedKeyManager(aKeyManager);
HotSwappableX509ExtendedTrustManager swappableTrustManager = new HotSwappableX509ExtendedTrustManager(aTrustManager);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(new KeyManager[]{ swappableKeyManager }, new TrustManager[]{ swappableTrustManager })
// Give the sslContext instance to your server or client
// After some time change the KeyManager and TrustManager with the following snippet:
X509ExtendedKeyManager anotherKeyManager = ... // Created from the new pem files
X509ExtendedTrustManager anotherTrustManager = ... // Created from the new pem files
// Set your new key and trust manager into your swappable managers
swappableKeyManager.setKeyManager(anotherKeyManager)
swappableTrustManager.setTrustManager(anotherTrustManager)
So even when your SSLContext instance is cached in your server of client you can still swap in and out new keymanager and trustmanager.
The code snippets are available here:
Github - SSLContext Kickstart
Option 2
If you don't want to add the custom (HotSwappableKeyManager and HotSwappableTrustManager) code to your code base you can also use my library:
<dependency>
<groupId>io.github.hakky54</groupId>
<artifactId>sslcontext-kickstart</artifactId>
<version>7.4.5</version>
</dependency>
Usage
SSLFactory baseSslFactory = SSLFactory.builder()
.withDummyIdentityMaterial()
.withDummyTrustMaterial()
.withSwappableIdentityMaterial()
.withSwappableTrustMaterial()
.build();
SSLContext sslContext = sslFactory.getSslContext();
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);
Update #1 - example with pem files
In the comments someone requested an example with pem files, so below is an example of refreshing the ssl configuration with pem files:
First make sure you have the following library:
<dependency>
<groupId>io.github.hakky54</groupId>
<artifactId>sslcontext-kickstart-for-pem</artifactId>
<version>7.4.5</version>
</dependency>
And the code example:
SSLFactory baseSslFactory = SSLFactory.builder()
.withDummyIdentityMaterial()
.withDummyTrustMaterial()
.withSwappableIdentityMaterial()
.withSwappableTrustMaterial()
.build();
SSLContext sslContext = sslFactory.getSslContext();
Runnable sslUpdater = () -> {
X509ExtendedKeyManager keyManager = PemUtils.loadIdentityMaterial(Paths.get("/path/to/your/certificate-chain.pem"), Paths.get("/path/to/your/"private-key.pem"));
X509ExtendedTrustManager trustManager = PemUtils.loadTrustMaterial(Paths.get("/path/to/your/"some-trusted-certificate.pem"));
SSLFactory updatedSslFactory = SSLFactory.builder()
.withIdentityMaterial(keyManager)
.withTrustMaterial(trustManager)
.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);