You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ozone.apache.org by xy...@apache.org on 2020/07/25 00:26:26 UTC

[hadoop-ozone] branch ozone-0.6.0 updated: HDDS-3997. Ozone certificate needs additional flags and SAN extension… (#1235)

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

xyao pushed a commit to branch ozone-0.6.0
in repository https://gitbox.apache.org/repos/asf/hadoop-ozone.git


The following commit(s) were added to refs/heads/ozone-0.6.0 by this push:
     new d3f30fc  HDDS-3997. Ozone certificate needs additional flags and SAN extension… (#1235)
d3f30fc is described below

commit d3f30fc1718f85f8e93aba53d6cc96460a60ac65
Author: Xiaoyu Yao <xy...@apache.org>
AuthorDate: Fri Jul 24 17:07:05 2020 -0700

    HDDS-3997. Ozone certificate needs additional flags and SAN extension… (#1235)
    
    (cherry picked from commit 0bb3e247ae7939ddbe9a18af835dd57ff44f904e)
---
 .../x509/certificate/authority/BaseApprover.java   |   9 ++
 .../certificate/authority/DefaultApprover.java     |  11 +++
 .../certificate/authority/DefaultCAServer.java     |  42 ++++++---
 .../certificate/client/DNCertificateClient.java    |   5 +-
 .../certificates/utils/SelfSignedCertificate.java  | 105 +++++++++++++++++----
 .../x509/certificates/TestRootCertificate.java     |  42 ++++++++-
 .../org/apache/hadoop/ozone/om/OzoneManager.java   |   3 +-
 7 files changed, 180 insertions(+), 37 deletions(-)

diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/BaseApprover.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/BaseApprover.java
index 12ececd..26cb491 100644
--- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/BaseApprover.java
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/BaseApprover.java
@@ -60,6 +60,15 @@ public abstract class BaseApprover implements CertificateApprover {
   }
 
   /**
+   * Returns the PKI policy profile.
+   *
+   * @return PKIProfile
+   */
+  public PKIProfile getProfile() {
+    return profile;
+  }
+
+  /**
    * Returns the Security config.
    *
    * @return SecurityConfig
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/DefaultApprover.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/DefaultApprover.java
index c7f37c1..0098fa5 100644
--- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/DefaultApprover.java
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/DefaultApprover.java
@@ -24,9 +24,12 @@ import org.apache.hadoop.hdds.security.x509.SecurityConfig;
 import org.apache.hadoop.hdds.security.x509.certificate.authority.PKIProfiles.PKIProfile;
 import org.apache.hadoop.hdds.security.x509.keys.SecurityUtil;
 import org.apache.hadoop.util.Time;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.x500.X500Name;
 import org.bouncycastle.asn1.x500.style.BCStyle;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.cert.X509CertificateHolder;
 import org.bouncycastle.cert.X509v3CertificateBuilder;
@@ -136,6 +139,14 @@ public class DefaultApprover extends BaseApprover {
             validTill,
             x500Name, keyInfo);
 
+    Extensions exts = SecurityUtil.getPkcs9Extensions(certificationRequest);
+    for (ASN1ObjectIdentifier extId : getProfile().getSupportedExtensions()) {
+      Extension ext = exts.getExtension(extId);
+      if (ext != null) {
+        certificateGenerator.addExtension(ext);
+      }
+    }
+
     ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId)
         .build(asymmetricKP);
 
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/DefaultCAServer.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/DefaultCAServer.java
index c583c19..2378260 100644
--- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/DefaultCAServer.java
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/DefaultCAServer.java
@@ -21,6 +21,7 @@ package org.apache.hadoop.hdds.security.x509.certificate.authority;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
+import org.apache.commons.validator.routines.DomainValidator;
 import org.apache.hadoop.hdds.security.exception.SCMSecurityException;
 import org.apache.hadoop.hdds.security.x509.SecurityConfig;
 import org.apache.hadoop.hdds.security.x509.certificate.authority.PKIProfiles.DefaultProfile;
@@ -29,6 +30,7 @@ import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateCodec;
 import org.apache.hadoop.hdds.security.x509.certificates.utils.SelfSignedCertificate;
 import org.apache.hadoop.hdds.security.x509.keys.HDDSKeyGenerator;
 import org.apache.hadoop.hdds.security.x509.keys.KeyCodec;
+import org.apache.hadoop.ozone.OzoneSecurityUtil;
 import org.bouncycastle.cert.X509CertificateHolder;
 import org.bouncycastle.operator.OperatorCreationException;
 import org.bouncycastle.pkcs.PKCS10CertificationRequest;
@@ -54,6 +56,7 @@ import java.util.concurrent.Future;
 import java.util.function.Consumer;
 
 import static org.apache.hadoop.hdds.security.x509.certificates.utils.CertificateSignRequest.*;
+import static org.apache.hadoop.hdds.security.x509.exceptions.CertificateException.ErrorCode.CSR_ERROR;
 
 /**
  * The default CertificateServer used by SCM. This has no dependencies on any
@@ -459,18 +462,33 @@ public class DefaultCAServer implements CertificateServer {
     LocalDateTime temp = LocalDateTime.of(beginDate, LocalTime.MIDNIGHT);
     LocalDate endDate =
         temp.plus(securityConfig.getMaxCertificateDuration()).toLocalDate();
-    X509CertificateHolder selfSignedCertificate =
-        SelfSignedCertificate
-            .newBuilder()
-            .setSubject(this.subject)
-            .setScmID(this.scmID)
-            .setClusterID(this.clusterID)
-            .setBeginDate(beginDate)
-            .setEndDate(endDate)
-            .makeCA()
-            .setConfiguration(securityConfig.getConfiguration())
-            .setKey(key)
-            .build();
+    SelfSignedCertificate.Builder builder = SelfSignedCertificate.newBuilder()
+        .setSubject(this.subject)
+        .setScmID(this.scmID)
+        .setClusterID(this.clusterID)
+        .setBeginDate(beginDate)
+        .setEndDate(endDate)
+        .makeCA()
+        .setConfiguration(securityConfig.getConfiguration())
+        .setKey(key);
+
+    try {
+      DomainValidator validator = DomainValidator.getInstance();
+      // Add all valid ips.
+      OzoneSecurityUtil.getValidInetsForCurrentHost().forEach(
+          ip -> {
+            builder.addIpAddress(ip.getHostAddress());
+            if(validator.isValid(ip.getCanonicalHostName())) {
+              builder.addDnsName(ip.getCanonicalHostName());
+            }
+          });
+    } catch (IOException e) {
+      throw new org.apache.hadoop.hdds.security.x509
+          .exceptions.CertificateException(
+              "Error while adding ip to CA self signed certificate", e,
+          CSR_ERROR);
+    }
+    X509CertificateHolder selfSignedCertificate = builder.build();
 
     CertificateCodec certCodec =
         new CertificateCodec(config, componentName);
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DNCertificateClient.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DNCertificateClient.java
index 7698658..e95a4a7 100644
--- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DNCertificateClient.java
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DNCertificateClient.java
@@ -48,6 +48,7 @@ public class DNCertificateClient extends DefaultCertificateClient {
   /**
    * Returns a CSR builder that can be used to creates a Certificate signing
    * request.
+   * The default flag is added to allow basic SSL handshake.
    *
    * @return CertificateSignRequest.Builder
    */
@@ -55,8 +56,8 @@ public class DNCertificateClient extends DefaultCertificateClient {
   public CertificateSignRequest.Builder getCSRBuilder()
       throws CertificateException {
     return super.getCSRBuilder()
-        .setDigitalEncryption(false)
-        .setDigitalSignature(false);
+        .setDigitalEncryption(true)
+        .setDigitalSignature(true);
   }
 
   public Logger getLogger() {
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/utils/SelfSignedCertificate.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/utils/SelfSignedCertificate.java
index 7ecc161..a7edfde 100644
--- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/utils/SelfSignedCertificate.java
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/utils/SelfSignedCertificate.java
@@ -26,7 +26,9 @@ import java.time.Duration;
 import java.time.LocalDate;
 import java.time.LocalTime;
 import java.time.ZoneOffset;
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.List;
 
 import org.apache.hadoop.hdds.conf.ConfigurationSource;
 import org.apache.hadoop.hdds.security.exception.SCMSecurityException;
@@ -37,10 +39,18 @@ import org.apache.hadoop.util.Time;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
 import org.apache.logging.log4j.util.Strings;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.DERUTF8String;
 import org.bouncycastle.asn1.x500.X500Name;
 import org.bouncycastle.asn1.x509.BasicConstraints;
 import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
 import org.bouncycastle.asn1.x509.KeyUsage;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.cert.CertIOException;
@@ -64,28 +74,23 @@ public final class SelfSignedCertificate {
   private LocalDate endDate;
   private KeyPair key;
   private SecurityConfig config;
+  private List<GeneralName> altNames;
 
   /**
    * Private Ctor invoked only via Builder Interface.
    *
-   * @param subject - Subject
-   * @param scmID - SCM ID
-   * @param clusterID - Cluster ID
-   * @param beginDate - NotBefore
-   * @param endDate - Not After
-   * @param configuration - SCM Config
-   * @param keyPair - KeyPair
+   * @param builder - builder
    */
-  private SelfSignedCertificate(String subject, String scmID, String clusterID,
-      LocalDate beginDate, LocalDate endDate, SecurityConfig configuration,
-      KeyPair keyPair) {
-    this.subject = subject;
-    this.clusterID = clusterID;
-    this.scmID = scmID;
-    this.beginDate = beginDate;
-    this.endDate = endDate;
-    config = configuration;
-    this.key = keyPair;
+
+  private SelfSignedCertificate(Builder builder) {
+    this.subject = builder.subject;
+    this.clusterID = builder.clusterID;
+    this.scmID = builder.scmID;
+    this.beginDate = builder.beginDate;
+    this.endDate = builder.endDate;
+    this.config = builder.config;
+    this.key = builder.key;
+    this.altNames = builder.altNames;
   }
 
   @VisibleForTesting
@@ -142,6 +147,11 @@ public final class SelfSignedCertificate {
       KeyUsage keyUsage = new KeyUsage(keyUsageFlag);
       builder.addExtension(Extension.keyUsage, false,
           new DEROctetString(keyUsage));
+      if (altNames != null && altNames.size() >= 1) {
+        builder.addExtension(new Extension(Extension.subjectAlternativeName,
+            false, new GeneralNames(altNames.toArray(
+                new GeneralName[altNames.size()])).getEncoded()));
+      }
     }
     return builder.build(contentSigner);
   }
@@ -158,6 +168,7 @@ public final class SelfSignedCertificate {
     private KeyPair key;
     private SecurityConfig config;
     private boolean isCA;
+    private List<GeneralName> altNames;
 
     public Builder setConfiguration(ConfigurationSource configuration) {
       this.config = new SecurityConfig(configuration);
@@ -199,6 +210,62 @@ public final class SelfSignedCertificate {
       return this;
     }
 
+    // Support SAN extension with DNS and RFC822 Name
+    // other name type will be added as needed.
+    public Builder addDnsName(String dnsName) {
+      Preconditions.checkNotNull(dnsName, "dnsName cannot be null");
+      this.addAltName(GeneralName.dNSName, dnsName);
+      return this;
+    }
+
+    // IP address is subject to change which is optional for now.
+    public Builder addIpAddress(String ip) {
+      Preconditions.checkNotNull(ip, "Ip address cannot be null");
+      this.addAltName(GeneralName.iPAddress, ip);
+      return this;
+    }
+
+    public Builder addServiceName(
+        String serviceName) {
+      Preconditions.checkNotNull(
+          serviceName, "Service Name cannot be null");
+
+      this.addAltName(GeneralName.otherName, serviceName);
+      return this;
+    }
+
+    private Builder addAltName(int tag, String name) {
+      if (altNames == null) {
+        altNames = new ArrayList<>();
+      }
+      if (tag == GeneralName.otherName) {
+        ASN1Object ono = addOtherNameAsn1Object(name);
+
+        altNames.add(new GeneralName(tag, ono));
+      } else {
+        altNames.add(new GeneralName(tag, name));
+      }
+      return this;
+    }
+
+    /**
+     * addOtherNameAsn1Object requires special handling since
+     * Bouncy Castle does not support othername as string.
+     * @param name
+     * @return
+     */
+    private ASN1Object addOtherNameAsn1Object(String name) {
+      // Below oid is copied from this URL:
+      // https://docs.microsoft.com/en-us/windows/win32/adschema/a-middlename
+      final String otherNameOID = "2.16.840.1.113730.3.1.34";
+      ASN1EncodableVector otherName = new ASN1EncodableVector();
+      otherName.add(new ASN1ObjectIdentifier(otherNameOID));
+      otherName.add(new DERTaggedObject(
+          true, GeneralName.otherName, new DERUTF8String(name)));
+      return new DERTaggedObject(
+          false, 0, new DERSequence(otherName));
+    }
+
     public X509CertificateHolder build()
         throws SCMSecurityException, IOException {
       Preconditions.checkNotNull(key, "Key cannot be null");
@@ -225,9 +292,7 @@ public final class SelfSignedCertificate {
       }
 
       SelfSignedCertificate rootCertificate =
-          new SelfSignedCertificate(this.subject,
-              this.scmID, this.clusterID, this.beginDate, this.endDate,
-              this.config, key);
+          new SelfSignedCertificate(this);
       try {
         return rootCertificate.generateCertificate(isCA);
       } catch (OperatorCreationException | CertIOException e) {
diff --git a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificates/TestRootCertificate.java b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificates/TestRootCertificate.java
index 02d0078..1e3a8f4 100644
--- a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificates/TestRootCertificate.java
+++ b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificates/TestRootCertificate.java
@@ -19,11 +19,14 @@
 
 package org.apache.hadoop.hdds.security.x509.certificates;
 
+import org.apache.commons.validator.routines.DomainValidator;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.hdds.security.exception.SCMSecurityException;
 import org.apache.hadoop.hdds.security.x509.SecurityConfig;
+import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateCodec;
 import org.apache.hadoop.hdds.security.x509.certificates.utils.SelfSignedCertificate;
 import org.apache.hadoop.hdds.security.x509.keys.HDDSKeyGenerator;
+import org.apache.hadoop.ozone.OzoneSecurityUtil;
 import org.bouncycastle.asn1.x509.Extension;
 import org.bouncycastle.cert.X509CertificateHolder;
 import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
@@ -33,6 +36,7 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 
+import java.io.File;
 import java.io.IOException;
 import java.math.BigInteger;
 import java.security.InvalidKeyException;
@@ -48,6 +52,9 @@ import java.util.Date;
 import java.util.UUID;
 
 import static org.apache.hadoop.hdds.HddsConfigKeys.OZONE_METADATA_DIRS;
+import static org.apache.hadoop.hdds.security.x509.exceptions.CertificateException.ErrorCode.CSR_ERROR;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 
 /**
  * Test Class for Root Certificate generation.
@@ -131,7 +138,7 @@ public class TestRootCertificate {
   @Test
   public void testCACert()
       throws SCMSecurityException, NoSuchProviderException,
-      NoSuchAlgorithmException, IOException {
+      NoSuchAlgorithmException, IOException, CertificateException {
     LocalDate notBefore = LocalDate.now();
     LocalDate notAfter = notBefore.plus(365, ChronoUnit.DAYS);
     String clusterID = UUID.randomUUID().toString();
@@ -152,6 +159,23 @@ public class TestRootCertificate {
             .setConfiguration(conf)
             .makeCA();
 
+    try {
+      DomainValidator validator = DomainValidator.getInstance();
+      // Add all valid ips.
+      OzoneSecurityUtil.getValidInetsForCurrentHost().forEach(
+          ip -> {
+            builder.addIpAddress(ip.getHostAddress());
+            if(validator.isValid(ip.getCanonicalHostName())) {
+              builder.addDnsName(ip.getCanonicalHostName());
+            }
+          });
+    } catch (IOException e) {
+      throw new org.apache.hadoop.hdds.security.x509
+          .exceptions.CertificateException(
+          "Error while adding ip to CA self signed certificate", e,
+          CSR_ERROR);
+    }
+
     X509CertificateHolder certificateHolder = builder.build();
     // This time we asked for a CertificateServer Certificate, make sure that
     // extension is
@@ -165,6 +189,22 @@ public class TestRootCertificate {
     // Since this code assigns ONE for the root certificate, we check if the
     // serial number is the expected number.
     Assert.assertEquals(certificateHolder.getSerialNumber(), BigInteger.ONE);
+
+    CertificateCodec codec = new CertificateCodec(securityConfig, "scm");
+    String pemString = codec.getPEMEncodedString(certificateHolder);
+
+    File basePath = temporaryFolder.newFolder();
+    if (!basePath.exists()) {
+      Assert.assertTrue(basePath.mkdirs());
+    }
+    codec.writeCertificate(basePath.toPath(), "pemcertificate.crt",
+        pemString, false);
+
+    X509CertificateHolder loadedCert =
+        codec.readCertificate(basePath.toPath(), "pemcertificate.crt");
+    assertNotNull(loadedCert);
+    assertEquals(certificateHolder.getSerialNumber(),
+        loadedCert.getSerialNumber());
   }
 
   @Test
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
index 43ae998..102d447 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
@@ -1349,8 +1349,7 @@ public final class OzoneManager extends ServiceRuntimeInfoImpl
         .setConfiguration(config)
         .setScmID(omStore.getScmId())
         .setClusterID(omStore.getClusterID())
-        .setSubject(subject)
-        .addIpAddress(ip);
+        .setSubject(subject);
 
     OMHANodeDetails haOMHANodeDetails = OMHANodeDetails.loadOMHAConfig(config);
     String serviceName =


---------------------------------------------------------------------
To unsubscribe, e-mail: ozone-commits-unsubscribe@hadoop.apache.org
For additional commands, e-mail: ozone-commits-help@hadoop.apache.org