com.yahoo.security.AutoReloadingX509KeyManager Maven / Gradle / Ivy
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.security;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.X509ExtendedKeyManager;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyStore;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A {@link X509ExtendedKeyManager} that reloads the certificate and private key from file regularly.
*
* @author bjorncs
*/
public class AutoReloadingX509KeyManager extends X509ExtendedKeyManager implements AutoCloseable {
public static final String CERTIFICATE_ALIAS = "default";
private static final Duration UPDATE_PERIOD = Duration.ofHours(1);
private static final Logger log = Logger.getLogger(AutoReloadingX509KeyManager.class.getName());
private final MutableX509KeyManager mutableX509KeyManager;
private final ScheduledExecutorService scheduler;
private final Path privateKeyFile;
private final Path certificatesFile;
private AutoReloadingX509KeyManager(Path privateKeyFile, Path certificatesFile) {
this(privateKeyFile, certificatesFile, createDefaultScheduler());
}
AutoReloadingX509KeyManager(Path privateKeyFile, Path certificatesFile, ScheduledExecutorService scheduler) {
this.privateKeyFile = privateKeyFile;
this.certificatesFile = certificatesFile;
this.scheduler = scheduler;
this.mutableX509KeyManager = new MutableX509KeyManager(createKeystore(privateKeyFile, certificatesFile), new char[0]);
scheduler.scheduleAtFixedRate(
new KeyManagerReloader(), UPDATE_PERIOD.getSeconds()/*initial delay*/, UPDATE_PERIOD.getSeconds(), TimeUnit.SECONDS);
}
public static AutoReloadingX509KeyManager fromPemFiles(Path privateKeyFile, Path certificatesFile) {
return new AutoReloadingX509KeyManager(privateKeyFile, certificatesFile);
}
public X509CertificateWithKey getCurrentCertificateWithKey() {
X509ExtendedKeyManager manager = mutableX509KeyManager.currentManager();
X509Certificate[] certificateChain = manager.getCertificateChain(CERTIFICATE_ALIAS);
PrivateKey privateKey = manager.getPrivateKey(CERTIFICATE_ALIAS);
return new X509CertificateWithKey(List.of(certificateChain), privateKey);
}
private static KeyStore createKeystore(Path privateKey, Path certificateChain) {
try {
return KeyStoreBuilder.withType(KeyStoreType.PKCS12)
.withKeyEntry(
CERTIFICATE_ALIAS,
KeyUtils.fromPemEncodedPrivateKey(Files.readString(privateKey)),
X509CertificateUtils.certificateListFromPem(Files.readString(certificateChain)))
.build();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private static ScheduledExecutorService createDefaultScheduler() {
return Executors.newSingleThreadScheduledExecutor(runnable -> {
Thread thread = new Thread(runnable, "auto-reloading-x509-key-manager");
thread.setDaemon(true);
return thread;
});
}
private class KeyManagerReloader implements Runnable {
@Override
public void run() {
try {
log.log(Level.FINE, () -> String.format("Reloading key and certificate chain (private-key='%s', certificates='%s')", privateKeyFile, certificatesFile));
mutableX509KeyManager.updateKeystore(createKeystore(privateKeyFile, certificatesFile), new char[0]);
} catch (Throwable t) {
log.log(Level.SEVERE,
String.format("Failed to load X509 key manager (private-key='%s', certificates='%s'): %s",
privateKeyFile, certificatesFile, t.getMessage()),
t);
}
}
}
@Override
public void close() {
try {
scheduler.shutdownNow();
scheduler.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//
// Methods from X509ExtendedKeyManager
//
@Override
public String[] getServerAliases(String keyType, Principal[] issuers) {
return mutableX509KeyManager.getServerAliases(keyType, issuers);
}
@Override
public String[] getClientAliases(String keyType, Principal[] issuers) {
return mutableX509KeyManager.getClientAliases(keyType, issuers);
}
@Override
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
return mutableX509KeyManager.chooseServerAlias(keyType, issuers, socket);
}
@Override
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
return mutableX509KeyManager.chooseClientAlias(keyType, issuers, socket);
}
@Override
public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) {
return mutableX509KeyManager.chooseEngineServerAlias(keyType, issuers, engine);
}
@Override
public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) {
return mutableX509KeyManager.chooseEngineClientAlias(keyType, issuers, engine);
}
@Override
public X509Certificate[] getCertificateChain(String alias) {
return mutableX509KeyManager.getCertificateChain(alias);
}
@Override
public PrivateKey getPrivateKey(String alias) {
return mutableX509KeyManager.getPrivateKey(alias);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy