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 012ecd3316 HDDS-8178. CertificateClient and KeyStoresFactory support 
multiple Sub-CA certificates in the trust chain (#4442)
012ecd3316 is described below

commit 012ecd331613f5b3b591dcd7573a32caf0cd75c1
Author: Sammi Chen <[email protected]>
AuthorDate: Wed Mar 29 06:24:23 2023 +0800

    HDDS-8178. CertificateClient and KeyStoresFactory support multiple Sub-CA 
certificates in the trust chain (#4442)
---
 .../hdds/security/ssl/ReloadingX509KeyManager.java | 26 +++++++++++------
 .../security/ssl/ReloadingX509TrustManager.java    | 18 +++++++-----
 .../x509/certificate/client/CertificateClient.java |  6 ++++
 .../client/DefaultCertificateClient.java           | 34 ++++++++++++++++++++++
 .../ssl/TestReloadingX509TrustManager.java         |  4 +--
 .../client/CertificateClientTestImpl.java          | 11 ++++++-
 .../hadoop/ozone/TestSecureOzoneCluster.java       |  3 +-
 7 files changed, 82 insertions(+), 20 deletions(-)

diff --git 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/ssl/ReloadingX509KeyManager.java
 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/ssl/ReloadingX509KeyManager.java
index dc78dcb142..103bc462b8 100644
--- 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/ssl/ReloadingX509KeyManager.java
+++ 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/ssl/ReloadingX509KeyManager.java
@@ -33,8 +33,9 @@ import java.security.GeneralSecurityException;
 import java.security.KeyStore;
 import java.security.Principal;
 import java.security.PrivateKey;
-import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Locale;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -64,7 +65,7 @@ public class ReloadingX509KeyManager extends 
X509ExtendedKeyManager {
    * materials are changed.
    */
   private PrivateKey currentPrivateKey;
-  private String currentCertId;
+  private List<String> currentCertIdsList = new ArrayList<>();
 
   /**
    * Construct a <code>Reloading509KeystoreManager</code>.
@@ -150,11 +151,14 @@ public class ReloadingX509KeyManager extends 
X509ExtendedKeyManager {
   private X509ExtendedKeyManager loadKeyManager(CertificateClient caClient)
       throws GeneralSecurityException, IOException {
     PrivateKey privateKey = caClient.getPrivateKey();
-    X509Certificate cert = caClient.getCertificate();
-    String certId = cert.getSerialNumber().toString();
-    // Security materials keep the same
-    if (currentCertId != null && currentPrivateKey != null &&
-        currentCertId.equals(certId) && currentPrivateKey.equals(privateKey)) {
+    List<X509Certificate> newCertList = caClient.getTrustChain();
+    if (currentPrivateKey != null && currentPrivateKey.equals(privateKey) &&
+        currentCertIdsList.size() > 0 &&
+        newCertList.size() == currentCertIdsList.size() &&
+        !newCertList.stream().filter(
+            c -> !currentCertIdsList.contains(c.getSerialNumber().toString()))
+            .findAny().isPresent()) {
+      // Security materials(key and certificates) keep the same.
       return null;
     }
 
@@ -163,7 +167,8 @@ public class ReloadingX509KeyManager extends 
X509ExtendedKeyManager {
     keystore.load(null, null);
 
     keystore.setKeyEntry(caClient.getComponentName() + "_key",
-        privateKey, EMPTY_PASSWORD, new Certificate[]{cert});
+        privateKey, EMPTY_PASSWORD,
+        newCertList.toArray(new X509Certificate[0]));
 
     KeyManagerFactory keyMgrFactory = KeyManagerFactory.getInstance(
         KeyManagerFactory.getDefaultAlgorithm());
@@ -176,7 +181,10 @@ public class ReloadingX509KeyManager extends 
X509ExtendedKeyManager {
     }
 
     currentPrivateKey = privateKey;
-    currentCertId = cert.getSerialNumber().toString();
+    currentCertIdsList.clear();
+    for (X509Certificate cert: newCertList) {
+      currentCertIdsList.add(cert.getSerialNumber().toString());
+    }
     return keyManager;
   }
 }
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 5252c278dd..f2ca6a13c6 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
@@ -51,9 +51,9 @@ public final class ReloadingX509TrustManager implements 
X509TrustManager {
   private final String type;
   private final AtomicReference<X509TrustManager> trustManagerRef;
   /**
-   * Current CA cert in trustManager, to detect if certificate is changed.
+   * Current Root CA cert in trustManager, to detect if certificate is changed.
    */
-  private String currentCACertId = null;
+  private String currentRootCACertId = null;
 
   /**
    * Creates a reloadable trustmanager. The trustmanager reloads itself
@@ -124,17 +124,21 @@ public final class ReloadingX509TrustManager implements 
X509TrustManager {
 
   X509TrustManager loadTrustManager(CertificateClient caClient)
       throws GeneralSecurityException, IOException {
-    X509Certificate cert = caClient.getCACertificate();
-    String certId = cert.getSerialNumber().toString();
+    // SCM certificate client sets root CA as CA cert instead of root CA cert
+    X509Certificate rootCACert = caClient.getRootCACertificate() != null ?
+        caClient.getRootCACertificate() : caClient.getCACertificate();
+
+    String rootCACertId = rootCACert.getSerialNumber().toString();
     // Certificate keeps the same.
-    if (currentCACertId != null && currentCACertId.equals(certId)) {
+    if (currentRootCACertId != null &&
+        currentRootCACertId.equals(rootCACertId)) {
       return null;
     }
 
     X509TrustManager trustManager = null;
     KeyStore ks = KeyStore.getInstance(type);
     ks.load(null, null);
-    ks.setCertificateEntry(certId, cert);
+    ks.setCertificateEntry(rootCACertId, rootCACert);
 
     TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
         TrustManagerFactory.getDefaultAlgorithm());
@@ -146,7 +150,7 @@ public final class ReloadingX509TrustManager implements 
X509TrustManager {
         break;
       }
     }
-    currentCACertId = certId;
+    currentRootCACertId = rootCACertId;
     return trustManager;
   }
 }
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 8c8a2df056..bd70001c9d 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
@@ -106,6 +106,12 @@ public interface CertificateClient extends Closeable {
    */
   CertPath getCACertPath();
 
+  /**
+   * Return all certificates in this component's trust chain,
+   * the last one is the root CA certificate.
+   */
+  List<X509Certificate> getTrustChain();
+
   /**
    * Return the latest Root CA certificate known to the client.
    * @return latest Root CA certificate known to the client.
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 2dfbce9dac..97c07f4e10 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
@@ -42,6 +42,7 @@ import java.time.Duration;
 import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
@@ -331,6 +332,39 @@ public abstract class DefaultCertificateClient implements 
CertificateClient {
     return firstCertificateFrom(caCertPath);
   }
 
+  /**
+   * Return all certificates in this component's trust chain,
+   * the last one is the root CA certificate.
+   */
+  @Override
+  public synchronized List<X509Certificate> getTrustChain() {
+    CertPath path = getCertPath();
+    if (path == null || path.getCertificates() == null) {
+      return null;
+    }
+
+    List<X509Certificate> chain = new ArrayList<>();
+    // certificate bundle case
+    if (path.getCertificates().size() > 1) {
+      for (int i = 0; i < path.getCertificates().size(); i++) {
+        chain.add((X509Certificate) path.getCertificates().get(i));
+      }
+    } 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);
+      }
+    }
+
+    return chain;
+  }
+
   @Override
   public synchronized CertPath getCACertPath() {
     if (caCertId != null) {
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 42f6de3281..d2af37db1f 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
@@ -50,13 +50,13 @@ public class TestReloadingX509TrustManager {
   public void testReload() throws Exception {
     TrustManager tm =
         caClient.getServerKeyStoresFactory().getTrustManagers()[0];
-    X509Certificate cert1 = caClient.getCACertificate();
+    X509Certificate cert1 = caClient.getRootCACertificate();
     assertEquals(cert1,
         ((ReloadingX509TrustManager)tm).getAcceptedIssuers()[0]);
 
     caClient.renewRootCA();
     caClient.renewKey();
-    X509Certificate cert2 = caClient.getCACertificate();
+    X509Certificate cert2 = caClient.getRootCACertificate();
     assertNotEquals(cert1, cert2);
 
     assertEquals(cert2,
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 18136d600e..27aa596bd6 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
@@ -31,6 +31,7 @@ import java.security.cert.X509Certificate;
 import java.time.Duration;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
@@ -207,6 +208,14 @@ public class CertificateClientTestImpl implements 
CertificateClient {
     return null;
   }
 
+  @Override
+  public List<X509Certificate> getTrustChain() {
+    List<X509Certificate> list = new ArrayList<>();
+    list.add(x509Certificate);
+    list.add(rootCert);
+    return list;
+  }
+
   @Override
   public X509Certificate getCACertificate() {
     return rootCert;
@@ -262,7 +271,7 @@ public class CertificateClientTestImpl implements 
CertificateClient {
 
   @Override
   public X509Certificate getRootCACertificate() {
-    return x509Certificate;
+    return rootCert;
   }
 
   @Override
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestSecureOzoneCluster.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestSecureOzoneCluster.java
index 7018e3fb88..1888d40931 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestSecureOzoneCluster.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestSecureOzoneCluster.java
@@ -359,6 +359,7 @@ public final class TestSecureOzoneCluster {
     ScmInfo scmInfo = scm.getClientProtocolServer().getScmInfo();
     assertEquals(clusterId, scmInfo.getClusterId());
     assertEquals(scmId, scmInfo.getScmId());
+    assertEquals(2, scm.getScmCertificateClient().getTrustChain().size());
   }
 
   @Test
@@ -867,6 +868,7 @@ public final class TestSecureOzoneCluster {
       assertNotNull(om.getCertificateClient().getPublicKey());
       assertNotNull(om.getCertificateClient().getPrivateKey());
       assertNotNull(om.getCertificateClient().getCertificate());
+      assertEquals(3, om.getCertificateClient().getTrustChain().size());
       assertTrue(omLogs.getOutput().contains("Init response: GETCERT"));
       assertTrue(omLogs.getOutput().contains("Successfully stored " +
           "SCM signed certificate"));
@@ -903,7 +905,6 @@ public final class TestSecureOzoneCluster {
 
     SecurityConfig securityConfig = new SecurityConfig(conf);
 
-
     // save first cert
     final int certificateLifetime = 20; // seconds
     KeyCodec keyCodec =


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

Reply via email to