Skip to content

Commit

Permalink
Automatically reload certificate and keys.
Browse files Browse the repository at this point in the history
* Implemented KeyStore that can reload PEM and PKCS#12 / JKS files wihtout
  server restart.
* Use NewSunX509 KeyManager which selects server certificate based on SNI
  in TLS handshake among other selection criterias.

Signed-off-by: Tero Saarni <tero.saarni@est.tech>
  • Loading branch information
tsaarni committed Jun 7, 2022
1 parent e26ff35 commit f74cc76
Show file tree
Hide file tree
Showing 12 changed files with 661 additions and 80 deletions.
40 changes: 34 additions & 6 deletions src/main/java/io/vertx/core/net/KeyStoreOptionsBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
import io.vertx.core.buffer.Buffer;
import io.vertx.core.impl.VertxInternal;
import io.vertx.core.net.impl.KeyStoreHelper;
import io.vertx.core.net.impl.ReloadingKeyStore;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.KeyStoreBuilderParameters;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509KeyManager;
Expand All @@ -40,6 +42,8 @@ public abstract class KeyStoreOptionsBase implements KeyCertOptions, TrustOption
private Buffer value;
private String alias;
private String aliasPassword;
private X509KeyManager km;
private KeyStore.Builder builder;

/**
* Default constructor
Expand Down Expand Up @@ -189,27 +193,51 @@ KeyStoreHelper getHelper(Vertx vertx) throws Exception {
return helper;
}

X509KeyManager getKeyManager(Vertx vertx) throws Exception {
if (km == null) {
VertxInternal v = (VertxInternal) vertx;

if (this.path != null) {
builder = ReloadingKeyStore.Builder.fromKeyStoreFile(v, type, provider,
v.resolveFile(path).getAbsolutePath(), password, alias, aliasPassword);
} else if (this.value != null) {

KeyStore.ProtectionParameter protection = new KeyStore.PasswordProtection(
password != null ? password.toCharArray() : null);

builder = KeyStore.Builder
.newInstance(KeyStoreHelper.loadKeyStore(type, provider, password, this::getValue, alias), protection);
}

KeyManagerFactory kmf = KeyManagerFactory.getInstance("NewSunX509");
kmf.init(new KeyStoreBuilderParameters(builder));
km = (X509KeyManager) kmf.getKeyManagers()[0];
}
return km;
}

/**
* Load and return a Java keystore.
*
* @param vertx the vertx instance
* @return the {@code KeyStore}
*/
public KeyStore loadKeyStore(Vertx vertx) throws Exception {
KeyStoreHelper helper = getHelper(vertx);
return helper != null ? helper.store() : null;
// Ensure that KeyStore is constructed.
getKeyManager(vertx);
return builder.getKeyStore();
}

@Override
public KeyManagerFactory getKeyManagerFactory(Vertx vertx) throws Exception {
KeyStoreHelper helper = getHelper(vertx);
return helper != null ? helper.getKeyMgrFactory() : null;
return new KeyManagerFactoryWrapper(getKeyManager(vertx));
}

@Override
public Function<String, X509KeyManager> keyManagerMapper(Vertx vertx) throws Exception {
KeyStoreHelper helper = getHelper(vertx);
return helper != null ? helper::getKeyMgr : null;
X509KeyManager km = getKeyManager(vertx);
// Key manager will do SNI lookup and mapping from SNI server name to certificate and key alias.
return serverName -> km;
}

@Override
Expand Down
47 changes: 26 additions & 21 deletions src/main/java/io/vertx/core/net/PemKeyCertOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@
import io.vertx.core.impl.VertxInternal;
import io.vertx.core.json.JsonObject;
import io.vertx.core.net.impl.KeyStoreHelper;
import io.vertx.core.net.impl.ReloadingKeyStore;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.KeyStoreBuilderParameters;
import javax.net.ssl.X509KeyManager;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
* Key store options configuring a list of private key and its certificate based on
Expand Down Expand Up @@ -97,7 +100,9 @@
@DataObject(generateConverter = true, publicConverter = false)
public class PemKeyCertOptions implements KeyCertOptions {

private KeyStoreHelper helper;
private X509KeyManager km;
private KeyStore.Builder builder;

private List<String> keyPaths;
private List<Buffer> keyValues;
private List<String> certPaths;
Expand Down Expand Up @@ -390,21 +395,20 @@ public PemKeyCertOptions copy() {
return new PemKeyCertOptions(this);
}

KeyStoreHelper getHelper(Vertx vertx) throws Exception {
if (helper == null) {
List<Buffer> keys = new ArrayList<>();
for (String keyPath : keyPaths) {
keys.add(vertx.fileSystem().readFileBlocking(((VertxInternal)vertx).resolveFile(keyPath).getAbsolutePath()));
}
keys.addAll(keyValues);
List<Buffer> certs = new ArrayList<>();
for (String certPath : certPaths) {
certs.add(vertx.fileSystem().readFileBlocking(((VertxInternal)vertx).resolveFile(certPath).getAbsolutePath()));
}
certs.addAll(certValues);
helper = new KeyStoreHelper(KeyStoreHelper.loadKeyCert(keys, certs), KeyStoreHelper.DUMMY_PASSWORD, null);
X509KeyManager getKeyManager(Vertx vertx) throws Exception {
if (km == null) {
VertxInternal v = (VertxInternal) vertx;
builder = ReloadingKeyStore.Builder.fromPem(v,
certPaths.stream().map(p -> v.resolveFile(p).getAbsolutePath()).collect(Collectors.toList()),
keyPaths.stream().map(p -> v.resolveFile(p).getAbsolutePath()).collect(Collectors.toList()),
certValues, keyValues);

KeyManagerFactory kmf = KeyManagerFactory.getInstance("NewSunX509");
kmf.init(new KeyStoreBuilderParameters(builder));
km = (X509KeyManager) kmf.getKeyManagers()[0];
}
return helper;

return km;
}

/**
Expand All @@ -414,19 +418,20 @@ KeyStoreHelper getHelper(Vertx vertx) throws Exception {
* @return the {@code KeyStore}
*/
public KeyStore loadKeyStore(Vertx vertx) throws Exception {
KeyStoreHelper helper = getHelper(vertx);
return helper != null ? helper.store() : null;
// Ensure that KeyStore is constructed.
getKeyManager(vertx);
return builder.getKeyStore();
}

@Override
public KeyManagerFactory getKeyManagerFactory(Vertx vertx) throws Exception {
KeyStoreHelper helper = getHelper(vertx);
return helper != null ? helper.getKeyMgrFactory() : null;
return new KeyManagerFactoryWrapper(getKeyManager(vertx));
}

@Override
public Function<String, X509KeyManager> keyManagerMapper(Vertx vertx) throws Exception {
KeyStoreHelper helper = getHelper(vertx);
return helper != null ? helper::getKeyMgr : null;
X509KeyManager km = getKeyManager(vertx);
// KeyManager will do SNI lookup and mapping from SNI server name to certificate and key alias.
return serverName -> km;
}
}
Loading

0 comments on commit f74cc76

Please sign in to comment.