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]