This is an automated email from the ASF dual-hosted git repository.

pifta pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/master by this push:
     new 39d035d915 HDDS-8588. Add initialization logic in CertificateClient to 
handle more than one rootCA certificate (#4853)
39d035d915 is described below

commit 39d035d9153c5c0a6e515e9798bbe3ce89fd952f
Author: Galsza <[email protected]>
AuthorDate: Fri Jun 16 09:23:27 2023 +0200

    HDDS-8588. Add initialization logic in CertificateClient to handle more 
than one rootCA certificate (#4853)
---
 .../security/ssl/ReloadingX509TrustManager.java    | 19 +++++-
 .../x509/certificate/client/CertificateClient.java | 21 +++++-
 .../client/DefaultCertificateClient.java           | 76 ++++++++++++++++++----
 .../java/org/apache/hadoop/hdds/utils/HAUtils.java | 17 ++---
 .../ssl/TestReloadingX509TrustManager.java         | 18 +++--
 .../client/CertificateClientTestImpl.java          | 18 ++++-
 .../client/TestDefaultCertificateClient.java       | 22 +++++++
 7 files changed, 157 insertions(+), 34 deletions(-)

diff --git 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/ssl/ReloadingX509TrustManager.java
 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/ssl/ReloadingX509TrustManager.java
index f2ca6a13c6..d2351b3e96 100644
--- 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/ssl/ReloadingX509TrustManager.java
+++ 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/ssl/ReloadingX509TrustManager.java
@@ -29,8 +29,10 @@ import javax.net.ssl.X509TrustManager;
 import java.io.IOException;
 import java.security.GeneralSecurityException;
 import java.security.KeyStore;
+import java.security.KeyStoreException;
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 
 /**
@@ -125,8 +127,8 @@ public final class ReloadingX509TrustManager implements 
X509TrustManager {
   X509TrustManager loadTrustManager(CertificateClient caClient)
       throws GeneralSecurityException, IOException {
     // SCM certificate client sets root CA as CA cert instead of root CA cert
-    X509Certificate rootCACert = caClient.getRootCACertificate() != null ?
-        caClient.getRootCACertificate() : caClient.getCACertificate();
+    X509Certificate rootCACert = caClient.getRootCACertificate() == null ?
+        caClient.getCACertificate() : caClient.getRootCACertificate();
 
     String rootCACertId = rootCACert.getSerialNumber().toString();
     // Certificate keeps the same.
@@ -138,7 +140,10 @@ public final class ReloadingX509TrustManager implements 
X509TrustManager {
     X509TrustManager trustManager = null;
     KeyStore ks = KeyStore.getInstance(type);
     ks.load(null, null);
-    ks.setCertificateEntry(rootCACertId, rootCACert);
+    Set<X509Certificate> caCertsToInsert =
+        caClient.getRootCACertificate() == null ? caClient.getAllCaCerts() :
+            caClient.getAllRootCaCerts();
+    insertCertsToKeystore(caCertsToInsert, ks);
 
     TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
         TrustManagerFactory.getDefaultAlgorithm());
@@ -153,4 +158,12 @@ public final class ReloadingX509TrustManager implements 
X509TrustManager {
     currentRootCACertId = rootCACertId;
     return trustManager;
   }
+
+  private void insertCertsToKeystore(Iterable<X509Certificate> certs,
+      KeyStore ks) throws KeyStoreException {
+    for (X509Certificate certToInsert : certs) {
+      String certId = certToInsert.getSerialNumber().toString();
+      ks.setCertificateEntry(certId, certToInsert);
+    }
+  }
 }
diff --git 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/CertificateClient.java
 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/CertificateClient.java
index 84e03f998d..29b0f999fe 100644
--- 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/CertificateClient.java
+++ 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/CertificateClient.java
@@ -34,6 +34,7 @@ import java.security.cert.CertPath;
 import java.security.cert.X509Certificate;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 import static 
org.apache.hadoop.hdds.security.OzoneSecurityException.ResultCodes.OM_PUBLIC_PRIVATE_KEY_FILE_NOT_EXIST;
 
@@ -102,19 +103,35 @@ public interface CertificateClient extends Closeable {
    * Return all certificates in this component's trust chain,
    * the last one is the root CA certificate.
    */
-  List<X509Certificate> getTrustChain();
+  List<X509Certificate> getTrustChain() throws IOException;
 
   /**
    * Return the latest Root CA certificate known to the client.
+   *
    * @return latest Root CA certificate known to the client.
    */
   X509Certificate getRootCACertificate();
 
   /**
-   * Return the pem encoded CA certificate list.
+   * Return the root ca certs saved in this client's file system.
+   *
+   * @return all the Root CA certificates known to the client
+   */
+  Set<X509Certificate> getAllRootCaCerts();
+
+  /**
+   * Return the subordinate ca certs saved in this client's file system.
    *
+   * @return all the subordinate CA certificates known to the client
+   */
+  Set<X509Certificate> getAllCaCerts();
+
+  /**
+   * Return the pem encoded CA certificate list.
+   * <p>
    * If initialized return list of pem encoded CA certificates, else return
    * null.
+   *
    * @return list of pem encoded CA certificates.
    */
   List<String> getCAList();
diff --git 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java
 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java
index 83c6851836..56b3bbba10 100644
--- 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java
+++ 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java
@@ -43,11 +43,13 @@ import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executors;
@@ -55,6 +57,7 @@ import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 import java.util.stream.Stream;
+import java.util.stream.Collectors;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.util.concurrent.ThreadFactoryBuilder;
@@ -110,6 +113,8 @@ public abstract class DefaultCertificateClient implements 
CertificateClient {
   private PublicKey publicKey;
   private CertPath certPath;
   private Map<String, CertPath> certificateMap;
+  private Set<X509Certificate> rootCaCertificates;
+  private Set<X509Certificate> caCertificates;
   private String certSerialId;
   private String caCertId;
   private String rootCaCertId;
@@ -137,6 +142,8 @@ public abstract class DefaultCertificateClient implements 
CertificateClient {
     this.certIdSaveCallback = saveCertId;
     this.shutdownCallback = shutdown;
     this.notificationReceivers = new HashSet<>();
+    this.rootCaCertificates = ConcurrentHashMap.newKeySet();
+    this.caCertificates = ConcurrentHashMap.newKeySet();
 
     updateCertSerialId(certSerialId);
   }
@@ -180,6 +187,8 @@ public abstract class DefaultCertificateClient implements 
CertificateClient {
         this.certPath = allCertificates;
       }
       certificateMap.putIfAbsent(readCertSerialId, allCertificates);
+      addCertsToSubCaMapIfNeeded(fileName, allCertificates);
+      addCertToRootCaMapIfNeeded(fileName, allCertificates);
 
       updateCachedData(fileName, CAType.SUBORDINATE, 
this::updateCachedSubCAId);
       updateCachedData(fileName, CAType.ROOT, this::updateCachedRootCAId);
@@ -222,6 +231,21 @@ public abstract class DefaultCertificateClient implements 
CertificateClient {
     }
   }
 
+  private void addCertsToSubCaMapIfNeeded(String fileName, CertPath certs) {
+    if (fileName.startsWith(CAType.SUBORDINATE.getFileNamePrefix())) {
+      caCertificates.addAll(
+          certs.getCertificates().stream()
+              .map(x -> (X509Certificate) x)
+              .collect(Collectors.toSet()));
+    }
+  }
+
+  private void addCertToRootCaMapIfNeeded(String fileName, CertPath certs) {
+    if (fileName.startsWith(CAType.ROOT.getFileNamePrefix())) {
+      rootCaCertificates.add(firstCertificateFrom(certs));
+    }
+  }
+
   /**
    * Returns the private key of the specified  if it exists on the local
    * system.
@@ -323,7 +347,8 @@ public abstract class DefaultCertificateClient implements 
CertificateClient {
    * the last one is the root CA certificate.
    */
   @Override
-  public synchronized List<X509Certificate> getTrustChain() {
+  public synchronized List<X509Certificate> getTrustChain()
+      throws IOException {
     CertPath path = getCertPath();
     if (path == null || path.getCertificates() == null) {
       return null;
@@ -337,21 +362,40 @@ public abstract class DefaultCertificateClient implements 
CertificateClient {
       }
     } else {
       // case before certificate bundle is supported
-      chain.add(getCertificate());
-      X509Certificate cert = getCACertificate();
-      if (cert != null) {
-        chain.add(getCACertificate());
-      }
-      cert = getRootCACertificate();
-      if (cert != null) {
-        chain.add(cert);
+      X509Certificate lastInsertedCert = getCertificate();
+      chain.add(lastInsertedCert);
+      List<X509Certificate> caCertList =
+          OzoneSecurityUtil.convertToX509(listCA());
+      while (!getAllRootCaCerts().contains(lastInsertedCert)) {
+        Optional<X509Certificate> issuerOpt =
+            getIssuerForCert(lastInsertedCert, caCertList);
+        if (issuerOpt.isPresent()) {
+          X509Certificate issuer = issuerOpt.get();
+          chain.add(issuer);
+          lastInsertedCert = issuer;
+        } else {
+          throw new CertificateException("No issuer found for certificate: " +
+              lastInsertedCert);
+        }
       }
+      //add root ca to the cert chain at the end
+      chain.add(lastInsertedCert);
     }
-
     return chain;
   }
 
-  private synchronized CertPath getCACertPath() {
+  private Optional<X509Certificate> getIssuerForCert(X509Certificate cert,
+      Iterable<X509Certificate> issuerCerts) {
+    for (X509Certificate issuer : issuerCerts) {
+      if (cert.getIssuerX500Principal().equals(
+          issuer.getSubjectX500Principal())) {
+        return Optional.of(issuer);
+      }
+    }
+    return Optional.empty();
+  }
+
+  public synchronized CertPath getCACertPath() {
     if (caCertId != null) {
       return certificateMap.get(caCertId);
     }
@@ -846,6 +890,16 @@ public abstract class DefaultCertificateClient implements 
CertificateClient {
     return null;
   }
 
+  @Override
+  public Set<X509Certificate> getAllRootCaCerts() {
+    return Collections.unmodifiableSet(rootCaCertificates);
+  }
+
+  @Override
+  public Set<X509Certificate> getAllCaCerts() {
+    return Collections.unmodifiableSet(caCertificates);
+  }
+
   @Override
   public synchronized List<String> getCAList() {
     return pemEncodedCACerts;
diff --git 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/HAUtils.java 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/HAUtils.java
index 1989f25802..8855546a1e 100644
--- 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/HAUtils.java
+++ 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/HAUtils.java
@@ -453,17 +453,16 @@ public final class HAUtils {
   private static List<String> generateCAList(CertificateClient certClient)
       throws IOException {
     List<String> caCertPemList = new ArrayList<>();
-    if (certClient.getRootCACertificate() != null) {
-      caCertPemList.add(CertificateCodec.getPEMEncodedString(
-          certClient.getRootCACertificate()));
+    for (X509Certificate cert : certClient.getAllRootCaCerts()) {
+      caCertPemList.add(CertificateCodec.getPEMEncodedString(cert));
     }
-    if (certClient.getCACertificate() != null) {
-      caCertPemList.add(CertificateCodec.getPEMEncodedString(
-          certClient.getCACertificate()));
+    for (X509Certificate cert : certClient.getAllCaCerts()) {
+      caCertPemList.add(CertificateCodec.getPEMEncodedString(cert));
     }
     return caCertPemList;
   }
 
+
   /**
    * Retry forever until CA list matches expected count.
    * @param task - task to get CA list.
@@ -518,10 +517,8 @@ public final class HAUtils {
       // X509 by buildCAList.
       if (!SCMHAUtils.isSCMHAEnabled(conf)) {
         List<X509Certificate> x509Certificates = new ArrayList<>();
-        if (certClient.getRootCACertificate() != null) {
-          x509Certificates.add(certClient.getRootCACertificate());
-        }
-        x509Certificates.add(certClient.getCACertificate());
+        x509Certificates.addAll(certClient.getAllCaCerts());
+        x509Certificates.addAll(certClient.getAllRootCaCerts());
         return x509Certificates;
       }
     }
diff --git 
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/ssl/TestReloadingX509TrustManager.java
 
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/ssl/TestReloadingX509TrustManager.java
index d2af37db1f..ccaae08923 100644
--- 
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/ssl/TestReloadingX509TrustManager.java
+++ 
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/ssl/TestReloadingX509TrustManager.java
@@ -21,10 +21,11 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import 
org.apache.hadoop.hdds.security.x509.certificate.client.CertificateClientTestImpl;
 import org.apache.ozone.test.GenericTestUtils.LogCapturer;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
-import javax.net.ssl.TrustManager;
 import java.security.cert.X509Certificate;
 
 import static org.junit.Assert.assertEquals;
@@ -48,19 +49,22 @@ public class TestReloadingX509TrustManager {
 
   @Test
   public void testReload() throws Exception {
-    TrustManager tm =
-        caClient.getServerKeyStoresFactory().getTrustManagers()[0];
+    ReloadingX509TrustManager tm =
+        (ReloadingX509TrustManager) caClient.getServerKeyStoresFactory()
+            .getTrustManagers()[0];
     X509Certificate cert1 = caClient.getRootCACertificate();
-    assertEquals(cert1,
-        ((ReloadingX509TrustManager)tm).getAcceptedIssuers()[0]);
+    MatcherAssert.assertThat(tm.getAcceptedIssuers(),
+        Matchers.arrayContaining(cert1));
 
     caClient.renewRootCA();
     caClient.renewKey();
     X509Certificate cert2 = caClient.getRootCACertificate();
     assertNotEquals(cert1, cert2);
 
-    assertEquals(cert2,
-        ((ReloadingX509TrustManager)tm).getAcceptedIssuers()[0]);
+    MatcherAssert.assertThat(tm.getAcceptedIssuers(),
+        Matchers.hasItemInArray(cert1));
+    MatcherAssert.assertThat(tm.getAcceptedIssuers(),
+        Matchers.hasItemInArray(cert2));
 
     assertTrue(reloaderLog.getOutput().contains(
         "ReloadingX509TrustManager is reloaded"));
diff --git 
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/client/CertificateClientTestImpl.java
 
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/client/CertificateClientTestImpl.java
index 534247f0c7..c7215cef4e 100644
--- 
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/client/CertificateClientTestImpl.java
+++ 
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/client/CertificateClientTestImpl.java
@@ -78,6 +78,8 @@ public class CertificateClientTestImpl implements 
CertificateClient {
   private X509Certificate x509Certificate;
   private KeyPair rootKeyPair;
   private X509Certificate rootCert;
+  private Set<X509Certificate> rootCerts;
+
   private HDDSKeyGenerator keyGen;
   private DefaultApprover approver;
   private KeyStoresFactory serverKeyStoresFactory;
@@ -95,6 +97,7 @@ public class CertificateClientTestImpl implements 
CertificateClient {
       throws Exception {
     certificateMap = new ConcurrentHashMap<>();
     securityConfig = new SecurityConfig(conf);
+    rootCerts = new HashSet<>();
     keyGen = new HDDSKeyGenerator(securityConfig.getConfiguration());
     keyPair = keyGen.generateKey();
     rootKeyPair = keyGen.generateKey();
@@ -118,6 +121,7 @@ public class CertificateClientTestImpl implements 
CertificateClient {
     rootCert = new JcaX509CertificateConverter().getCertificate(
         builder.build());
     certificateMap.put(rootCert.getSerialNumber().toString(), rootCert);
+    rootCerts.add(rootCert);
 
     // Generate normal certificate, signed by RootCA certificate
     approver = new DefaultApprover(new DefaultProfile(), securityConfig);
@@ -269,12 +273,23 @@ public class CertificateClientTestImpl implements 
CertificateClient {
     return rootCert;
   }
 
+  @Override
+  public Set<X509Certificate> getAllRootCaCerts() {
+    return rootCerts;
+  }
+
+  @Override
+  public Set<X509Certificate> getAllCaCerts() {
+    return rootCerts;
+  }
+
   @Override
   public List<String> getCAList() {
     return null;
   }
+
   @Override
-  public List<String> listCA() throws IOException  {
+  public List<String> listCA() throws IOException {
     return null;
   }
 
@@ -302,6 +317,7 @@ public class CertificateClientTestImpl implements 
CertificateClient {
     rootCert = new JcaX509CertificateConverter().getCertificate(
         builder.build());
     certificateMap.put(rootCert.getSerialNumber().toString(), rootCert);
+    rootCerts.add(rootCert);
   }
 
   public void renewKey() throws Exception {
diff --git 
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/client/TestDefaultCertificateClient.java
 
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/client/TestDefaultCertificateClient.java
index 5049d2d59e..d2d313e01d 100644
--- 
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/client/TestDefaultCertificateClient.java
+++ 
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/client/TestDefaultCertificateClient.java
@@ -276,6 +276,10 @@ public class TestDefaultCertificateClient {
     X509Certificate cert1 = generateX509Cert(keyPair);
     X509Certificate cert2 = generateX509Cert(keyPair);
     X509Certificate cert3 = generateX509Cert(keyPair);
+    X509Certificate rootCa1 = generateX509Cert(keyPair);
+    X509Certificate rootCa2 = generateX509Cert(keyPair);
+    X509Certificate subCa1 = generateX509Cert(keyPair);
+    X509Certificate subCa2 = generateX509Cert(keyPair);
 
     Path certPath = dnSecurityConfig.getCertificateLocation(DN_COMPONENT);
     CertificateCodec codec = new CertificateCodec(dnSecurityConfig,
@@ -300,6 +304,18 @@ public class TestDefaultCertificateClient {
         getPEMEncodedString(cert2));
     codec.writeCertificate(certPath, "3.crt",
         getPEMEncodedString(cert3));
+    codec.writeCertificate(certPath,
+        CAType.ROOT.getFileNamePrefix() + "1.crt",
+        getPEMEncodedString(rootCa1));
+    codec.writeCertificate(certPath,
+        CAType.ROOT.getFileNamePrefix() + "2.crt",
+        getPEMEncodedString(rootCa2));
+    codec.writeCertificate(certPath,
+        CAType.SUBORDINATE.getFileNamePrefix() + "1.crt",
+        getPEMEncodedString(subCa1));
+    codec.writeCertificate(certPath,
+        CAType.SUBORDINATE.getFileNamePrefix() + "2.crt",
+        getPEMEncodedString(subCa2));
 
     // Re instantiate DN client which will load certificates from filesystem.
     if (dnCertClient != null) {
@@ -315,6 +331,12 @@ public class TestDefaultCertificateClient {
     assertNotNull(dnCertClient.getCertificate(cert3.getSerialNumber()
         .toString()));
 
+    assertEquals(2, dnCertClient.getAllCaCerts().size());
+    assertTrue(dnCertClient.getAllCaCerts().contains(subCa1));
+    assertTrue(dnCertClient.getAllCaCerts().contains(subCa2));
+    assertEquals(2, dnCertClient.getAllRootCaCerts().size());
+    assertTrue(dnCertClient.getAllRootCaCerts().contains(rootCa1));
+    assertTrue(dnCertClient.getAllRootCaCerts().contains(rootCa2));
   }
 
   @Test


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to