Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.apache.hadoop.hdds.annotation.InterfaceAudience;
import org.apache.hadoop.hdds.annotation.InterfaceStability;
import org.apache.hadoop.hdds.security.x509.certificate.client.CertificateClient;
import org.apache.hadoop.hdds.security.x509.certificate.client.CertificateNotification;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -34,22 +35,20 @@
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicReference;

/**
* An implementation of <code>X509KeyManager</code> that exposes a method,
* {@link #loadFrom(CertificateClient)} to reload its configuration.
* An implementation of <code>X509KeyManager</code> that can be notified of certificate changes.
* Note that it is necessary to implement the
* <code>X509ExtendedKeyManager</code> to properly delegate
* the additional methods, otherwise the SSL handshake will fail.
*/
@InterfaceAudience.Private
@InterfaceStability.Evolving
public class ReloadingX509KeyManager extends X509ExtendedKeyManager {
public class ReloadingX509KeyManager extends X509ExtendedKeyManager implements CertificateNotification {

public static final Logger LOG =
LoggerFactory.getLogger(ReloadingX509KeyManager.class);
Expand All @@ -67,27 +66,30 @@ public class ReloadingX509KeyManager extends X509ExtendedKeyManager {
* materials are changed.
*/
private PrivateKey currentPrivateKey;
private List<String> currentCertIdsList = new ArrayList<>();
private String alias;
private List<X509Certificate> currentTrustChain;
private final String alias;

/**
* Construct a <code>Reloading509KeystoreManager</code>.
*
* @param type type of keystore file, typically 'jks'.
* @param caClient client to get the private key and certificate materials.
* @param type type of keystore file, typically 'jks'.
* @param componentName the name of the component for which the keys are created.
* @param privateKey private key for this key manager.
* @param trustChain list of the trusted certificates.
* @throws IOException
* @throws GeneralSecurityException
*/
public ReloadingX509KeyManager(String type, CertificateClient caClient)
public ReloadingX509KeyManager(String type, String componentName, PrivateKey privateKey,
List<X509Certificate> trustChain)
throws GeneralSecurityException, IOException {
this.type = type;
alias = componentName + "_key";
keyManagerRef = new AtomicReference<>();
keyManagerRef.set(loadKeyManager(caClient));
keyManagerRef.set(init(privateKey, trustChain));
}

@Override
public String chooseEngineClientAlias(String[] strings,
Principal[] principals, SSLEngine sslEngine) {
public String chooseEngineClientAlias(String[] strings, Principal[] principals, SSLEngine sslEngine) {
String ret = keyManagerRef.get()
.chooseEngineClientAlias(strings, principals, sslEngine);

Expand Down Expand Up @@ -184,29 +186,9 @@ public PrivateKey getPrivateKey(String s) {
return keyManagerRef.get().getPrivateKey(s.toLowerCase(Locale.ROOT));
}

public ReloadingX509KeyManager loadFrom(CertificateClient caClient) {
try {
X509ExtendedKeyManager manager = loadKeyManager(caClient);
if (manager != null) {
keyManagerRef.set(manager);
LOG.info("ReloadingX509KeyManager is reloaded");
}
} catch (Exception ex) {
// The Consumer.accept interface forces us to convert to unchecked
throw new RuntimeException(ex);
}
return this;
}

private X509ExtendedKeyManager loadKeyManager(CertificateClient caClient)
private X509ExtendedKeyManager init(PrivateKey newPrivateKey, List<X509Certificate> newTrustChain)
throws GeneralSecurityException, IOException {
PrivateKey privateKey = caClient.getPrivateKey();
List<X509Certificate> newCertList = caClient.getTrustChain();
if (currentPrivateKey != null && currentPrivateKey.equals(privateKey) &&
currentCertIdsList.size() > 0 &&
newCertList.size() == currentCertIdsList.size() &&
newCertList.stream().allMatch(c ->
currentCertIdsList.contains(c.getSerialNumber().toString()))) {
if (isAlreadyUsing(newPrivateKey, newTrustChain)) {
// Security materials(key and certificates) keep the same.
return null;
}
Expand All @@ -215,30 +197,54 @@ private X509ExtendedKeyManager loadKeyManager(CertificateClient caClient)
KeyStore keystore = KeyStore.getInstance(type);
keystore.load(null, null);

alias = caClient.getComponentName() + "_key";
keystore.setKeyEntry(alias, privateKey, EMPTY_PASSWORD,
newCertList.toArray(new X509Certificate[0]));
keystore.setKeyEntry(alias, newPrivateKey, EMPTY_PASSWORD,
newTrustChain.toArray(new X509Certificate[0]));

LOG.info("Key manager is loaded with certificate chain");
for (X509Certificate x509Certificate : newCertList) {
for (X509Certificate x509Certificate : newTrustChain) {
LOG.info(x509Certificate.toString());
}

KeyManagerFactory keyMgrFactory = KeyManagerFactory.getInstance(
KeyManagerFactory.getDefaultAlgorithm());
keyMgrFactory.init(keystore, EMPTY_PASSWORD);
for (KeyManager candidate: keyMgrFactory.getKeyManagers()) {
for (KeyManager candidate : keyMgrFactory.getKeyManagers()) {
if (candidate instanceof X509ExtendedKeyManager) {
keyManager = (X509ExtendedKeyManager)candidate;
keyManager = (X509ExtendedKeyManager) candidate;
break;
}
}

currentPrivateKey = privateKey;
currentCertIdsList.clear();
for (X509Certificate cert: newCertList) {
currentCertIdsList.add(cert.getSerialNumber().toString());
}
currentPrivateKey = newPrivateKey;
currentTrustChain = newTrustChain;
return keyManager;
}

private boolean isAlreadyUsing(PrivateKey privateKey, List<X509Certificate> newTrustChain) {
return currentPrivateKey != null && currentPrivateKey.equals(privateKey) &&
currentTrustChain.size() > 0 &&
newTrustChain.size() == currentTrustChain.size() &&
newTrustChain.stream()
.allMatch(
newCertificate -> (currentTrustChain.stream()
.anyMatch(oldCert -> oldCert.getSerialNumber().equals(newCertificate.getSerialNumber()))
)
);
}

@Override
public synchronized void notifyCertificateRenewed(
CertificateClient certClient, String oldCertId, String newCertId) {
LOG.info("{} notify certificate renewed", certClient.getComponentName());
try {
X509ExtendedKeyManager manager = init(certClient.getPrivateKey(), certClient.getTrustChain());
if (manager != null) {
keyManagerRef.set(manager);
LOG.info("ReloadingX509KeyManager is reloaded");
}
} catch (Exception ex) {
// The Consumer.accept interface forces us to convert to unchecked
throw new RuntimeException(ex);
}
}
}
Loading