HDDS-696. Bootstrap genesis SCM(CA) with self-signed certificate. Contributed by Anu Engineer.
Project: http://git-wip-us.apache.org/repos/asf/hadoop/repo Commit: http://git-wip-us.apache.org/repos/asf/hadoop/commit/87f51d23 Tree: http://git-wip-us.apache.org/repos/asf/hadoop/tree/87f51d23 Diff: http://git-wip-us.apache.org/repos/asf/hadoop/diff/87f51d23 Branch: refs/heads/HDDS-4 Commit: 87f51d23d9899e46ac31058b726b272f1a7880ba Parents: 9b9b8e4 Author: Anu Engineer <aengin...@apache.org> Authored: Tue Nov 27 15:02:07 2018 -0800 Committer: Xiaoyu Yao <x...@apache.org> Committed: Thu Nov 29 11:58:54 2018 -0800 ---------------------------------------------------------------------- .../org/apache/hadoop/hdds/HddsConfigKeys.java | 57 ++- .../hdds/security/x509/SecurityConfig.java | 84 ++++- .../authority/CertificateServer.java | 11 +- .../certificate/authority/DefaultCAServer.java | 373 +++++++++++++++++++ .../certificate/client/CertificateClient.java | 18 +- .../certificate/utils/CertificateCodec.java | 280 ++++++++++++++ .../x509/certificate/utils/package-info.java | 22 ++ .../certificates/CertificateSignRequest.java | 245 ------------ .../certificates/SelfSignedCertificate.java | 212 ----------- .../x509/certificates/package-info.java | 22 -- .../utils/CertificateSignRequest.java | 245 ++++++++++++ .../utils/SelfSignedCertificate.java | 238 ++++++++++++ .../x509/certificates/utils/package-info.java | 22 ++ .../security/x509/keys/HDDSKeyGenerator.java | 32 +- .../security/x509/keys/HDDSKeyPEMWriter.java | 255 ------------- .../hdds/security/x509/keys/KeyCodec.java | 337 +++++++++++++++++ .../apache/hadoop/ozone/common/StorageInfo.java | 2 +- .../authority/TestDefaultCAServer.java | 118 ++++++ .../certificate/authority/package-info.java | 22 ++ .../certificate/utils/TestCertificateCodec.java | 218 +++++++++++ .../TestCertificateSignRequest.java | 19 +- .../x509/certificates/TestRootCertificate.java | 46 +-- .../x509/certificates/package-info.java | 4 +- .../x509/keys/TestHDDSKeyPEMWriter.java | 216 ----------- .../hdds/security/x509/keys/TestKeyCodec.java | 216 +++++++++++ .../hadoop/utils/db/TestDBStoreBuilder.java | 5 +- .../hadoop/ozone/TestSecureOzoneCluster.java | 15 +- 27 files changed, 2267 insertions(+), 1067 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/hadoop/blob/87f51d23/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsConfigKeys.java ---------------------------------------------------------------------- diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsConfigKeys.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsConfigKeys.java index 553f4aa..a02152d 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsConfigKeys.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/HddsConfigKeys.java @@ -1,19 +1,18 @@ /** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. */ package org.apache.hadoop.hdds; @@ -25,61 +24,45 @@ import org.apache.hadoop.utils.db.DBProfile; */ public final class HddsConfigKeys { - /** - * Do not instantiate. - */ - private HddsConfigKeys() { - } - public static final String HDDS_HEARTBEAT_INTERVAL = "hdds.heartbeat.interval"; public static final String HDDS_HEARTBEAT_INTERVAL_DEFAULT = "30s"; - public static final String HDDS_NODE_REPORT_INTERVAL = "hdds.node.report.interval"; public static final String HDDS_NODE_REPORT_INTERVAL_DEFAULT = "60s"; - public static final String HDDS_CONTAINER_REPORT_INTERVAL = "hdds.container.report.interval"; public static final String HDDS_CONTAINER_REPORT_INTERVAL_DEFAULT = "60s"; - public static final String HDDS_PIPELINE_REPORT_INTERVAL = "hdds.pipeline.report.interval"; public static final String HDDS_PIPELINE_REPORT_INTERVAL_DEFAULT = "60s"; - public static final String HDDS_COMMAND_STATUS_REPORT_INTERVAL = "hdds.command.status.report.interval"; public static final String HDDS_COMMAND_STATUS_REPORT_INTERVAL_DEFAULT = "60s"; - public static final String HDDS_CONTAINER_ACTION_MAX_LIMIT = "hdds.container.action.max.limit"; public static final int HDDS_CONTAINER_ACTION_MAX_LIMIT_DEFAULT = 20; - public static final String HDDS_PIPELINE_ACTION_MAX_LIMIT = "hdds.pipeline.action.max.limit"; public static final int HDDS_PIPELINE_ACTION_MAX_LIMIT_DEFAULT = 20; - // Configuration to allow volume choosing policy. public static final String HDDS_DATANODE_VOLUME_CHOOSING_POLICY = "hdds.datanode.volume.choosing.policy"; - // DB Profiles used by ROCKDB instances. public static final String HDDS_DB_PROFILE = "hdds.db.profile"; public static final DBProfile HDDS_DEFAULT_DB_PROFILE = DBProfile.DISK; - // Once a container usage crosses this threshold, it is eligible for // closing. public static final String HDDS_CONTAINER_CLOSE_THRESHOLD = "hdds.container.close.threshold"; public static final float HDDS_CONTAINER_CLOSE_THRESHOLD_DEFAULT = 0.9f; - public static final String HDDS_SCM_CHILLMODE_ENABLED = "hdds.scm.chillmode.enabled"; public static final boolean HDDS_SCM_CHILLMODE_ENABLED_DEFAULT = true; @@ -97,11 +80,9 @@ public final class HddsConfigKeys { public static final String HDDS_SCM_CHILLMODE_THRESHOLD_PCT = "hdds.scm.chillmode.threshold.pct"; public static final double HDDS_SCM_CHILLMODE_THRESHOLD_PCT_DEFAULT = 0.99; - public static final String HDDS_LOCK_MAX_CONCURRENCY = "hdds.lock.max.concurrency"; public static final int HDDS_LOCK_MAX_CONCURRENCY_DEFAULT = 100; - // This configuration setting is used as a fallback location by all // Ozone/HDDS services for their metadata. It is useful as a single // config point for test/PoC clusters. @@ -121,7 +102,6 @@ public final class HddsConfigKeys { public static final String HDDS_DEFAULT_SECURITY_PROVIDER = "BC"; public static final String HDDS_KEY_DIR_NAME = "hdds.key.dir.name"; public static final String HDDS_KEY_DIR_NAME_DEFAULT = "keys"; - // TODO : Talk to StorageIO classes and see if they can return a secure // storage location for each node. public static final String HDDS_METADATA_DIR_NAME = "hdds.metadata.dir"; @@ -131,7 +111,6 @@ public final class HddsConfigKeys { public static final String HDDS_PUBLIC_KEY_FILE_NAME = "hdds.public.key.file" + ".name"; public static final String HDDS_PUBLIC_KEY_FILE_NAME_DEFAULT = "public.pem"; - /** * Maximum duration of certificates issued by SCM including Self-Signed Roots. * The formats accepted are based on the ISO-8601 duration format PnDTnHnMn.nS @@ -140,12 +119,20 @@ public final class HddsConfigKeys { public static final String HDDS_X509_MAX_DURATION = "hdds.x509.max.duration"; // Limit Certificate duration to a max value of 5 years. public static final String HDDS_X509_MAX_DURATION_DEFAULT= "P1865D"; - public static final String HDDS_X509_SIGNATURE_ALGO = "hdds.x509.signature.algorithm"; public static final String HDDS_X509_SIGNATURE_ALGO_DEFAULT = "SHA256withRSA"; - - public static final String HDDS_GRPC_BLOCK_TOKEN_ENABLED = "hdds.grpc.block" + - ".token.enabled"; + public static final String HDDS_GRPC_BLOCK_TOKEN_ENABLED = + "hdds.grpc.block.token.enabled"; public static final boolean HDDS_GRPC_BLOCK_TOKEN_ENABLED_DEFAULT = false; + public static final String HDDS_X509_DIR_NAME = "hdds.x509.dir.name"; + public static final String HDDS_X509_DIR_NAME_DEFAULT = "certs"; + public static final String HDDS_X509_FILE_NAME = "hdds.x509.file.name"; + public static final String HDDS_X509_FILE_NAME_DEFAULT = "certificate.crt"; + + /** + * Do not instantiate. + */ + private HddsConfigKeys() { + } } http://git-wip-us.apache.org/repos/asf/hadoop/blob/87f51d23/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/SecurityConfig.java ---------------------------------------------------------------------- diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/SecurityConfig.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/SecurityConfig.java index 2826e55..ee20a21 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/SecurityConfig.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/SecurityConfig.java @@ -46,6 +46,10 @@ import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_PRIVATE_KEY_FILE_NAME_D import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_PUBLIC_KEY_FILE_NAME; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_PUBLIC_KEY_FILE_NAME_DEFAULT; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_SECURITY_PROVIDER; +import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_DIR_NAME; +import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_DIR_NAME_DEFAULT; +import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_FILE_NAME; +import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_FILE_NAME_DEFAULT; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_MAX_DURATION; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_MAX_DURATION_DEFAULT; import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_X509_SIGNATURE_ALGO; @@ -54,7 +58,7 @@ import static org.apache.hadoop.hdds.HddsConfigKeys.OZONE_METADATA_DIRS; /** * A class that deals with all Security related configs in HDDS. - * + * <p> * This class allows security configs to be read and used consistently across * all of security related code base. */ @@ -73,6 +77,8 @@ public class SecurityConfig { private final Duration certDuration; private final String x509SignatureAlgo; private final Boolean grpcBlockTokenEnabled; + private final String certificateDir; + private final String certificateFileName; /** * Constructs a SecurityConfig. @@ -108,6 +114,10 @@ public class SecurityConfig { this.certDuration = Duration.parse(durationString); this.x509SignatureAlgo = this.configuration.get(HDDS_X509_SIGNATURE_ALGO, HDDS_X509_SIGNATURE_ALGO_DEFAULT); + this.certificateDir = this.configuration.get(HDDS_X509_DIR_NAME, + HDDS_X509_DIR_NAME_DEFAULT); + this.certificateFileName = this.configuration.get(HDDS_X509_FILE_NAME, + HDDS_X509_FILE_NAME_DEFAULT); this.grpcBlockTokenEnabled = this.configuration.getBoolean( HDDS_GRPC_BLOCK_TOKEN_ENABLED, @@ -127,8 +137,17 @@ public class SecurityConfig { } /** - * Returns the public key file name, This is used for storing the public - * keys on disk. + * Returns the Standard Certificate file name. + * + * @return String - Name of the Certificate File. + */ + public String getCertificateFileName() { + return certificateFileName; + } + + /** + * Returns the public key file name, This is used for storing the public keys + * on disk. * * @return String, File name used for public keys. */ @@ -137,8 +156,8 @@ public class SecurityConfig { } /** - * Returns the private key file name.This is used for storing the private - * keys on disk. + * Returns the private key file name.This is used for storing the private keys + * on disk. * * @return String, File name used for private keys. */ @@ -149,16 +168,47 @@ public class SecurityConfig { /** * Returns the File path to where keys are stored. * - * @return String Key location. + * @return path Key location. */ public Path getKeyLocation() { return Paths.get(metadatDir, keyDir); } /** + * Returns the File path to where keys are stored with an additional component + * name inserted in between. + * + * @param component - Component Name - String. + * @return Path location. + */ + public Path getKeyLocation(String component) { + return Paths.get(metadatDir, component, keyDir); + } + + /** + * Returns the File path to where keys are stored. + * + * @return path Key location. + */ + public Path getCertificateLocation() { + return Paths.get(metadatDir, certificateDir); + } + + /** + * Returns the File path to where keys are stored with an addition component + * name inserted in between. + * + * @param component - Component Name - String. + * @return Path location. + */ + public Path getCertificateLocation(String component) { + return Paths.get(metadatDir, component, certificateDir); + } + + /** * Gets the Key Size, The default key size is 2048, since the default - * algorithm used is RSA. User can change this by setting the "hdds.key - * .len" in configuration. + * algorithm used is RSA. User can change this by setting the "hdds.key.len" + * in configuration. * * @return key size. */ @@ -177,8 +227,8 @@ public class SecurityConfig { } /** - * Returns the Key generation Algorithm used. User can change this by - * setting the "hdds.key.algo" in configuration. + * Returns the Key generation Algorithm used. User can change this by setting + * the "hdds.key.algo" in configuration. * * @return String Algo. */ @@ -188,8 +238,8 @@ public class SecurityConfig { /** * Returns the X.509 Signature Algorithm used. This can be changed by setting - * "hdds.x509.signature.algorithm" to the new name. The default algorithm - * is SHA256withRSA. + * "hdds.x509.signature.algorithm" to the new name. The default algorithm is + * SHA256withRSA. * * @return String */ @@ -207,11 +257,11 @@ public class SecurityConfig { } /** - * Returns the maximum length a certificate can be valid in SCM. The - * default value is 5 years. This can be changed by setting - * "hdds.x509.max.duration" in configuration. The formats accepted are - * based on the ISO-8601 duration format PnDTnHnMn.nS - * + * Returns the maximum length a certificate can be valid in SCM. The default + * value is 5 years. This can be changed by setting "hdds.x509.max.duration" + * in configuration. The formats accepted are based on the ISO-8601 duration + * format PnDTnHnMn.nS + * <p> * Default value is 5 years and written as P1865D. * * @return Duration. http://git-wip-us.apache.org/repos/asf/hadoop/blob/87f51d23/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/CertificateServer.java ---------------------------------------------------------------------- diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/CertificateServer.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/CertificateServer.java index 5b5deef..ee685c8 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/CertificateServer.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/CertificateServer.java @@ -20,10 +20,12 @@ package org.apache.hadoop.hdds.security.x509.certificate.authority; import org.apache.hadoop.hdds.security.exception.SCMSecurityException; -import org.apache.hadoop.hdds.security.x509.certificates.CertificateSignRequest; import org.apache.hadoop.hdds.security.x509.SecurityConfig; +import org.apache.hadoop.hdds.security.x509.certificates.utils.CertificateSignRequest; import org.bouncycastle.cert.X509CertificateHolder; +import java.io.IOException; +import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.concurrent.Future; @@ -47,16 +49,19 @@ public interface CertificateServer { * Returns the CA Certificate for this CA. * * @return X509CertificateHolder - Certificate for this CA. - * @throws SCMSecurityException -- usually thrown if this CA is not + * @throws CertificateException - usually thrown if this CA is not * initialized. + * @throws IOException - on Error. */ X509CertificateHolder getCACertificate() - throws SCMSecurityException; + throws CertificateException, IOException; /** * Request a Certificate based on Certificate Signing Request. * * @param csr - Certificate Signing Request. + * @param approver - An Enum which says what kind of approval process to + * follow. * @return A future that will have this certificate when this request is * approved. * @throws SCMSecurityException - on Error. http://git-wip-us.apache.org/repos/asf/hadoop/blob/87f51d23/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/DefaultCAServer.java ---------------------------------------------------------------------- diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/DefaultCAServer.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/DefaultCAServer.java new file mode 100644 index 0000000..f6e6e5c --- /dev/null +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/DefaultCAServer.java @@ -0,0 +1,373 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.hadoop.hdds.security.x509.certificate.authority; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +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.CertificateSignRequest; +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.bouncycastle.cert.X509CertificateHolder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.concurrent.Future; +import java.util.function.Consumer; + +/** + * The default CertificateServer used by SCM. This has no dependencies on any + * external system, this allows us to bootstrap a CertificateServer from + * Scratch. + * <p> + * Details ======= + * <p> + * The Default CA server is one of the many possible implementations of an SCM + * Certificate Authority. + * <p> + * A certificate authority needs the Root Certificates and its private key to + * operate. The init function of the DefaultCA Server detects four possible + * states the System can be in. + * <p> + * 1. Success - This means that the expected Certificates and Keys are in + * place, and the CA was able to read those files into memory. + * <p> + * 2. Missing Keys - This means that private keys are missing. This is an error + * state which SCM CA cannot recover from. The cluster might have been + * initialized earlier and for some reason, we are not able to find the private + * keys for the CA. Eventually we will have 2 ways to recover from this state, + * first one is to copy the SCM CA private keys from a backup. Second one is to + * rekey the whole cluster. Both of these are improvements we will support in + * future. + * <p> + * 3. Missing Certificate - Similar to Missing Keys, but the root certificates + * are missing. + * <p> + * 4. Initialize - We don't have keys or certificates. DefaultCA assumes that + * this is a system bootup and will generate the keys and certificates + * automatically. + * <p> + * The init() follows the following logic, + * <p> + * 1. Compute the Verification Status -- Success, Missing Keys, Missing Certs or + * Initialize. + * <p> + * 2. ProcessVerificationStatus - Returns a Lambda, based on the Verification + * Status. + * <p> + * 3. Invoke the Lambda function. + * <p> + * At the end of the init function, we have functional CA. This function can be + * invoked as many times since we will regenerate the keys and certs only if + * both of them are missing. + */ +public class DefaultCAServer implements CertificateServer { + private static final Logger LOG = + LoggerFactory.getLogger(DefaultCAServer.class); + private final String subject; + private final String clusterID; + private final String scmID; + private String componentName = Paths.get("scm", "ca").toString(); + private Path caKeysPath; + private Path caRootX509Path; + private SecurityConfig config; + + /** + * Create an Instance of DefaultCAServer. + * + * @param subject - String Subject + * @param clusterID - String ClusterID + * @param scmID - String SCMID. + */ + public DefaultCAServer(String subject, String clusterID, String scmID) { + this.subject = subject; + this.clusterID = clusterID; + this.scmID = scmID; + } + + @Override + public void init(SecurityConfig securityConfig, CAType type) + throws SCMSecurityException { + caKeysPath = securityConfig.getKeyLocation(componentName); + caRootX509Path = securityConfig.getCertificateLocation(componentName); + this.config = securityConfig; + + /* In future we will spilt this code to have different kind of CAs. + * Right now, we have only self-signed CertificateServer. + */ + + if (type == CAType.SELF_SIGNED_CA) { + VerificationStatus status = verifySelfSignedCA(securityConfig); + Consumer<SecurityConfig> caInitializer = + processVerificationStatus(status); + caInitializer.accept(securityConfig); + return; + } + + LOG.error("We support only Self-Signed CAs for now."); + throw new IllegalStateException("Not implemented functionality requested."); + } + + @Override + public X509CertificateHolder getCACertificate() throws + CertificateException, IOException { + CertificateCodec certificateCodec = + new CertificateCodec(config, componentName); + return certificateCodec.readCertificate(); + } + + @Override + public Future<X509CertificateHolder> requestCertificate( + CertificateSignRequest csr, CertificateApprover approver) + throws SCMSecurityException { + return null; + } + + @Override + public Future<Boolean> revokeCertificate(X509Certificate certificate, + CertificateApprover approver) throws SCMSecurityException { + return null; + } + + /** + * Generates a Self Signed CertificateServer. These are the steps in + * generating a Self-Signed CertificateServer. + * <p> + * 1. Generate a Private/Public Key Pair. 2. Persist to a protected location. + * 3. Generate a SelfSigned Root CertificateServer certificate. + * + * @param securityConfig - Config. + */ + private void generateSelfSignedCA(SecurityConfig securityConfig) throws + NoSuchAlgorithmException, NoSuchProviderException, IOException { + KeyPair keyPair = generateKeys(securityConfig); + generateRootCertificate(securityConfig, keyPair); + } + + /** + * Verify Self-Signed CertificateServer. 1. Check if the Certificate exist. 2. + * Check if the key pair exists. + * + * @param securityConfig -- Config + * @return Verification Status + */ + private VerificationStatus verifySelfSignedCA(SecurityConfig securityConfig) { + /* + The following is the truth table for the States. + True means we have that file False means it is missing. + +--------------+--------+--------+--------------+ + | Certificates | Keys | Result | Function | + +--------------+--------+--------+--------------+ + | True | True | True | Success | + | False | False | True | Initialize | + | True | False | False | Missing Key | + | False | True | False | Missing Cert | + +--------------+--------+--------+--------------+ + + This truth table maps to ~(certs xor keys) or certs == keys + */ + boolean keyStatus = checkIfKeysExist(); + boolean certStatus = checkIfCertificatesExist(); + + if ((certStatus == keyStatus) && (certStatus)) { + return VerificationStatus.SUCCESS; + } + + if ((certStatus == keyStatus) && (!certStatus)) { + return VerificationStatus.INITIALIZE; + } + + // At this point certStatus is not equal to keyStatus. + if (certStatus) { + return VerificationStatus.MISSING_KEYS; + } + + return VerificationStatus.MISSING_CERTIFICATE; + } + + /** + * Returns Keys status. + * + * @return True if the key files exist. + */ + private boolean checkIfKeysExist() { + if (!Files.exists(caKeysPath)) { + return false; + } + + if (!Files.exists(Paths.get(caKeysPath.toString(), + this.config.getPrivateKeyFileName()))) { + return false; + } + return true; + } + + /** + * Returns certificate Status. + * + * @return True if the Certificate files exist. + */ + private boolean checkIfCertificatesExist() { + if (!Files.exists(caRootX509Path)) { + return false; + } + if (!Files.exists(Paths.get(caRootX509Path.toString(), + this.config.getCertificateFileName()))) { + return false; + } + return true; + } + + /** + * Based on the Status of the verification, we return a lambda that gets + * executed by the init function of the CA. + * + * @param status - Verification Status. + */ + @VisibleForTesting + Consumer<SecurityConfig> processVerificationStatus( + VerificationStatus status) { + Consumer<SecurityConfig> consumer = null; + switch (status) { + case SUCCESS: + consumer = (arg) -> LOG.info("CertificateServer validation is " + + "successful"); + break; + case MISSING_KEYS: + consumer = (arg) -> { + LOG.error("We have found the Certificate for this CertificateServer, " + + "but keys used by this CertificateServer is missing. This is a " + + "non-recoverable error. Please restart the system after locating " + + "the Keys used by the CertificateServer."); + LOG.error("Exiting due to unrecoverable CertificateServer error."); + throw new IllegalStateException("Missing Keys, cannot continue."); + }; + break; + case MISSING_CERTIFICATE: + consumer = (arg) -> { + LOG.error("We found the keys, but the root certificate for this " + + "CertificateServer is missing. Please restart SCM after locating " + + "the " + + "Certificates."); + LOG.error("Exiting due to unrecoverable CertificateServer error."); + throw new IllegalStateException("Missing Root Certs, cannot continue."); + }; + break; + case INITIALIZE: + consumer = (arg) -> { + try { + generateSelfSignedCA(arg); + } catch (NoSuchProviderException | NoSuchAlgorithmException + | IOException e) { + LOG.error("Unable to initialize CertificateServer.", e); + } + VerificationStatus newStatus = verifySelfSignedCA(arg); + if (newStatus != VerificationStatus.SUCCESS) { + LOG.error("Unable to initialize CertificateServer, failed in " + + "verification."); + } + }; + break; + default: + /* Make CheckStyle happy */ + break; + } + return consumer; + } + + /** + * Generates a KeyPair for the Certificate. + * + * @param securityConfig - SecurityConfig. + * @return Key Pair. + * @throws NoSuchProviderException - on Error. + * @throws NoSuchAlgorithmException - on Error. + * @throws IOException - on Error. + */ + private KeyPair generateKeys(SecurityConfig securityConfig) + throws NoSuchProviderException, NoSuchAlgorithmException, IOException { + HDDSKeyGenerator keyGenerator = new HDDSKeyGenerator(securityConfig); + KeyPair keys = keyGenerator.generateKey(); + KeyCodec keyPEMWriter = new KeyCodec(securityConfig, + componentName); + keyPEMWriter.writeKey(keys); + return keys; + } + + /** + * Generates a self-signed Root Certificate for CA. + * + * @param securityConfig - SecurityConfig + * @param key - KeyPair. + * @throws IOException - on Error. + * @throws SCMSecurityException - on Error. + */ + private void generateRootCertificate(SecurityConfig securityConfig, + KeyPair key) throws IOException, SCMSecurityException { + Preconditions.checkNotNull(this.config); + LocalDate beginDate = LocalDate.now().atStartOfDay().toLocalDate(); + 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(); + + CertificateCodec certCodec = + new CertificateCodec(config, componentName); + certCodec.writeCertificate(selfSignedCertificate); + } + + /** + * This represents the verification status of the CA. Based on this enum + * appropriate action is taken in the Init. + */ + @VisibleForTesting + enum VerificationStatus { + SUCCESS, /* All artifacts needed by CertificateServer is present */ + MISSING_KEYS, /* Private key is missing, certificate Exists.*/ + MISSING_CERTIFICATE, /* Keys exist, but root certificate missing.*/ + INITIALIZE /* All artifacts are missing, we should init the system. */ + } +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/87f51d23/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/CertificateClient.java ---------------------------------------------------------------------- diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/CertificateClient.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/CertificateClient.java index bfc0576..e33c9b6 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/CertificateClient.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/CertificateClient.java @@ -19,7 +19,7 @@ package org.apache.hadoop.hdds.security.x509.certificate.client; -import org.apache.hadoop.hdds.security.x509.certificates.CertificateSignRequest; +import org.apache.hadoop.hdds.security.x509.certificates.utils.CertificateSignRequest; import org.apache.hadoop.hdds.security.x509.exceptions.CertificateException; import java.io.InputStream; @@ -64,7 +64,7 @@ public interface CertificateClient { /** * Verifies if this certificate is part of a trusted chain. - * + * @param certificate - certificate. * @return true if it trusted, false otherwise. */ boolean verifyCertificate(X509Certificate certificate); @@ -74,7 +74,9 @@ public interface CertificateClient { * key. * * @param stream - Data stream to sign. + * @param component - name of the component. * @return byte array - containing the signature. + * @throws CertificateException - on Error. */ byte[] signDataStream(InputStream stream, String component) throws CertificateException; @@ -82,6 +84,7 @@ public interface CertificateClient { /** * Verifies a digital Signature, given the signature and the certificate of * the signer. + * * @param stream - Data Stream. * @param signature - Byte Array containing the signature. * @param cert - Certificate of the Signer. @@ -123,7 +126,7 @@ public interface CertificateClient { * * @param key - private key * @param component - name of the component. - * @throws CertificateException + * @throws CertificateException - on Error. */ void storePrivateKey(PrivateKey key, String component) throws CertificateException; @@ -132,7 +135,8 @@ public interface CertificateClient { * Stores the public key of a specified component. * * @param key - public key - * @throws CertificateException + * @param component - name of the component. + * @throws CertificateException - on Error. */ void storePublicKey(PublicKey key, String component) throws CertificateException; @@ -142,7 +146,7 @@ public interface CertificateClient { * * @param certificate - X509 Certificate * @param component - Name of the component. - * @throws CertificateException + * @throws CertificateException - on Error. */ void storeCertificate(X509Certificate certificate, String component) throws CertificateException; @@ -152,7 +156,7 @@ public interface CertificateClient { * * @param certStore - Cert Store. * @param component - Trust Chain. - * @throws CertificateException + * @throws CertificateException - on Error. */ void storeTrustChain(CertStore certStore, String component) throws CertificateException; @@ -162,7 +166,7 @@ public interface CertificateClient { * * @param certificates - List of Certificates. * @param component - String component. - * @throws CertificateException + * @throws CertificateException - on Error. */ void storeTrustChain(List<X509Certificate> certificates, String component) throws CertificateException; http://git-wip-us.apache.org/repos/asf/hadoop/blob/87f51d23/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/utils/CertificateCodec.java ---------------------------------------------------------------------- diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/utils/CertificateCodec.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/utils/CertificateCodec.java new file mode 100644 index 0000000..b2a37b5 --- /dev/null +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/utils/CertificateCodec.java @@ -0,0 +1,280 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.hadoop.hdds.security.x509.certificate.utils; + +import com.google.common.base.Preconditions; +import org.apache.commons.io.IOUtils; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hdds.security.exception.SCMSecurityException; +import org.apache.hadoop.hdds.security.x509.SecurityConfig; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.PosixFilePermission; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE; +import static java.nio.file.attribute.PosixFilePermission.OWNER_READ; +import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE; + +/** + * A class used to read and write X.509 certificates PEM encoded Streams. + */ +public class CertificateCodec { + public static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----"; + public static final String END_CERT = "-----END CERTIFICATE-----"; + + private static final Logger LOG = + LoggerFactory.getLogger(CertificateCodec.class); + private static final JcaX509CertificateConverter CERTIFICATE_CONVERTER + = new JcaX509CertificateConverter(); + private final SecurityConfig securityConfig; + private final Path location; + private Set<PosixFilePermission> permissionSet = + Stream.of(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE) + .collect(Collectors.toSet()); + /** + * Creates an CertificateCodec. + * + * @param config - Security Config. + * @param component - Component String. + */ + public CertificateCodec(SecurityConfig config, String component) { + this.securityConfig = config; + this.location = securityConfig.getCertificateLocation(component); + } + + /** + * Creates an CertificateCodec. + * + * @param configuration - Configuration + */ + public CertificateCodec(Configuration configuration) { + Preconditions.checkNotNull(configuration, "Config cannot be null"); + this.securityConfig = new SecurityConfig(configuration); + this.location = securityConfig.getCertificateLocation(); + } + + /** + * Returns a X509 Certificate from the Certificate Holder. + * + * @param holder - Holder + * @return X509Certificate. + * @throws CertificateException - on Error. + */ + public static X509Certificate getX509Certificate(X509CertificateHolder holder) + throws CertificateException { + return CERTIFICATE_CONVERTER.getCertificate(holder); + } + + /** + * Get Certificate location. + * + * @return Path + */ + public Path getLocation() { + return location; + } + + /** + * Returns the Certificate as a PEM encoded String. + * + * @param x509CertHolder - X.509 Certificate Holder. + * @return PEM Encoded Certificate String. + * @throws SCMSecurityException - On failure to create a PEM String. + */ + public String getPEMEncodedString(X509CertificateHolder x509CertHolder) + throws SCMSecurityException { + try { + StringWriter stringWriter = new StringWriter(); + try (JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) { + pemWriter.writeObject(getX509Certificate(x509CertHolder)); + } + return stringWriter.toString(); + } catch (CertificateException | IOException e) { + LOG.error("Error in encoding certificate." + x509CertHolder + .getSubject().toString(), e); + throw new SCMSecurityException("PEM Encoding failed for certificate." + + x509CertHolder.getSubject().toString(), e); + } + } + + /** + * Gets the X.509 Certificate from PEM encoded String. + * + * @param pemEncodedString - PEM encoded String. + * @return X509Certificate - Certificate. + * @throws CertificateException - Thrown on Failure. + * @throws IOException - Thrown on Failure. + */ + public X509Certificate getX509Certificate(String pemEncodedString) + throws CertificateException, IOException { + CertificateFactory fact = CertificateFactory.getInstance("X.509"); + try (InputStream input = IOUtils.toInputStream(pemEncodedString, UTF_8)) { + return (X509Certificate) fact.generateCertificate(input); + } + } + + /** + * Write the Certificate pointed to the location by the configs. + * + * @param xCertificate - Certificate to write. + * @throws SCMSecurityException - on Error. + * @throws IOException - on Error. + */ + public void writeCertificate(X509CertificateHolder xCertificate) + throws SCMSecurityException, IOException { + String pem = getPEMEncodedString(xCertificate); + writeCertificate(location.toAbsolutePath(), + this.securityConfig.getCertificateFileName(), pem, false); + } + + /** + * Write the Certificate to the specific file. + * + * @param xCertificate - Certificate to write. + * @param fileName - file name to write to. + * @param overwrite - boolean value, true means overwrite an existing + * certificate. + * @throws SCMSecurityException - On Error. + * @throws IOException - On Error. + */ + public void writeCertificate(X509CertificateHolder xCertificate, + String fileName, boolean overwrite) + throws SCMSecurityException, IOException { + String pem = getPEMEncodedString(xCertificate); + writeCertificate(location.toAbsolutePath(), fileName, pem, overwrite); + } + + /** + * Helper function that writes data to the file. + * + * @param basePath - Base Path where the file needs to written to. + * @param fileName - Certificate file name. + * @param pemEncodedCertificate - pemEncoded Certificate file. + * @param force - Overwrite if the file exists. + * @throws IOException - on Error. + */ + public synchronized void writeCertificate(Path basePath, String fileName, + String pemEncodedCertificate, boolean force) + throws IOException { + File certificateFile = + Paths.get(basePath.toString(), fileName).toFile(); + if (certificateFile.exists() && !force) { + throw new SCMSecurityException("Specified certificate file already " + + "exists.Please use force option if you want to overwrite it."); + } + if (!basePath.toFile().exists()) { + if (!basePath.toFile().mkdirs()) { + LOG.error("Unable to create file path. Path: {}", basePath); + throw new IOException("Creation of the directories failed." + + basePath.toString()); + } + } + try (FileOutputStream file = new FileOutputStream(certificateFile)) { + IOUtils.write(pemEncodedCertificate, file, UTF_8); + } + + Files.setPosixFilePermissions(certificateFile.toPath(), permissionSet); + } + + /** + * Rertuns a default certificate using the default paths for this component. + * + * @return X509CertificateHolder. + * @throws SCMSecurityException - on Error. + * @throws CertificateException - on Error. + * @throws IOException - on Error. + */ + public X509CertificateHolder readCertificate() throws + CertificateException, IOException { + return readCertificate(this.location.toAbsolutePath(), + this.securityConfig.getCertificateFileName()); + } + + /** + * Returns the certificate from the specific PEM encoded file. + * + * @param basePath - base path + * @param fileName - fileName + * @return X%09 Certificate + * @throws IOException - on Error. + * @throws SCMSecurityException - on Error. + * @throws CertificateException - on Error. + */ + public synchronized X509CertificateHolder readCertificate(Path basePath, + String fileName) throws IOException, CertificateException { + File certificateFile = Paths.get(basePath.toString(), fileName).toFile(); + return getX509CertificateHolder(certificateFile); + } + + /** + * Helper function to read certificate. + * + * @param certificateFile - Full path to certificate file. + * @return X509CertificateHolder + * @throws IOException - On Error. + * @throws CertificateException - On Error. + */ + private X509CertificateHolder getX509CertificateHolder(File certificateFile) + throws IOException, CertificateException { + if (!certificateFile.exists()) { + throw new IOException("Unable to find the requested certificate. Path: " + + certificateFile.toString()); + } + CertificateFactory fact = CertificateFactory.getInstance("X.509"); + try (FileInputStream is = new FileInputStream(certificateFile)) { + return getCertificateHolder( + (X509Certificate) fact.generateCertificate(is)); + } + } + + /** + * Returns the Certificate holder from X509Ceritificate class. + * + * @param x509cert - Certificate class. + * @return X509CertificateHolder + * @throws CertificateEncodingException - on Error. + * @throws IOException - on Error. + */ + public X509CertificateHolder getCertificateHolder(X509Certificate x509cert) + throws CertificateEncodingException, IOException { + return new X509CertificateHolder(x509cert.getEncoded()); + } +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/87f51d23/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/utils/package-info.java ---------------------------------------------------------------------- diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/utils/package-info.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/utils/package-info.java new file mode 100644 index 0000000..4971d4a --- /dev/null +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/utils/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +/** + * Certificate Utils. + */ +package org.apache.hadoop.hdds.security.x509.certificate.utils; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/hadoop/blob/87f51d23/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/CertificateSignRequest.java ---------------------------------------------------------------------- diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/CertificateSignRequest.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/CertificateSignRequest.java deleted file mode 100644 index 0e762a5..0000000 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/CertificateSignRequest.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ -package org.apache.hadoop.hdds.security.x509.certificates; - -import com.google.common.base.Preconditions; -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.hdds.security.exception.SCMSecurityException; -import org.apache.hadoop.hdds.security.x509.SecurityConfig; -import org.apache.hadoop.hdds.security.x509.exceptions.CertificateException; -import org.apache.hadoop.hdds.security.x509.keys.SecurityUtil; -import org.apache.logging.log4j.util.Strings; -import org.bouncycastle.asn1.DEROctetString; -import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; -import org.bouncycastle.asn1.x500.X500Name; -import org.bouncycastle.asn1.x509.BasicConstraints; -import org.bouncycastle.asn1.x509.Extension; -import org.bouncycastle.asn1.x509.Extensions; -import org.bouncycastle.asn1.x509.GeneralName; -import org.bouncycastle.asn1.x509.GeneralNames; -import org.bouncycastle.asn1.x509.KeyUsage; -import org.bouncycastle.operator.ContentSigner; -import org.bouncycastle.operator.OperatorCreationException; -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; -import org.bouncycastle.pkcs.PKCS10CertificationRequest; -import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder; -import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; - -import java.io.IOException; -import java.security.KeyPair; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -/** - * A certificate sign request object that wraps operations to build a - * PKCS10CertificationRequest to CA. - */ -public final class CertificateSignRequest { - private final KeyPair keyPair; - private final SecurityConfig config; - private final Extensions extensions; - private String subject; - private String clusterID; - private String scmID; - - /** - * Private Ctor for CSR. - * - * @param subject - Subject - * @param scmID - SCM ID - * @param clusterID - Cluster ID - * @param keyPair - KeyPair - * @param config - SCM Config - * @param extensions - CSR extensions - */ - private CertificateSignRequest(String subject, String scmID, String clusterID, - KeyPair keyPair, SecurityConfig config, Extensions extensions) { - this.subject = subject; - this.clusterID = clusterID; - this.scmID = scmID; - this.keyPair = keyPair; - this.config = config; - this.extensions = extensions; - } - - private PKCS10CertificationRequest generateCSR() throws - OperatorCreationException { - X500Name dnName = SecurityUtil.getDistinguishedName(subject, scmID, - clusterID); - PKCS10CertificationRequestBuilder p10Builder = - new JcaPKCS10CertificationRequestBuilder(dnName, keyPair.getPublic()); - - ContentSigner contentSigner = - new JcaContentSignerBuilder(config.getSignatureAlgo()) - .setProvider(config.getProvider()) - .build(keyPair.getPrivate()); - - if (extensions != null) { - p10Builder.addAttribute( - PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extensions); - } - return p10Builder.build(contentSigner); - } - - /** - * Builder class for Certificate Sign Request. - */ - public static class Builder { - private String subject; - private String clusterID; - private String scmID; - private KeyPair key; - private SecurityConfig config; - private List<GeneralName> altNames; - private Boolean ca = false; - - public CertificateSignRequest.Builder setConfiguration( - Configuration configuration) { - this.config = new SecurityConfig(configuration); - return this; - } - - public CertificateSignRequest.Builder setKey(KeyPair keyPair) { - this.key = keyPair; - return this; - } - - public CertificateSignRequest.Builder setSubject(String subjectString) { - this.subject = subjectString; - return this; - } - - public CertificateSignRequest.Builder setClusterID(String s) { - this.clusterID = s; - return this; - } - - public CertificateSignRequest.Builder setScmID(String s) { - this.scmID = s; - return this; - } - - // Support SAN extenion with DNS and RFC822 Name - // other name type will be added as needed. - public CertificateSignRequest.Builder addDnsName(String dnsName) { - Preconditions.checkNotNull(dnsName, "dnsName cannot be null"); - this.addAltName(GeneralName.dNSName, dnsName); - return this; - } - - public CertificateSignRequest.Builder addRfc822Name(String name) { - Preconditions.checkNotNull(name, "Rfc822Name cannot be null"); - this.addAltName(GeneralName.rfc822Name, name); - return this; - } - - // IP address is subject to change which is optional for now. - public CertificateSignRequest.Builder addIpAddress(String ip) { - Preconditions.checkNotNull(ip, "Ip address cannot be null"); - this.addAltName(GeneralName.iPAddress, ip); - return this; - } - - private CertificateSignRequest.Builder addAltName(int tag, String name) { - if (altNames == null) { - altNames = new ArrayList<>(); - } - altNames.add(new GeneralName(tag, name)); - return this; - } - - public CertificateSignRequest.Builder setCA(Boolean isCA) { - this.ca = isCA; - return this; - } - - private Extension getKeyUsageExtension() throws IOException { - int keyUsageFlag = KeyUsage.digitalSignature | KeyUsage.keyEncipherment - | KeyUsage.dataEncipherment | KeyUsage.keyAgreement; - - if (ca) { - keyUsageFlag |= KeyUsage.keyCertSign | KeyUsage.cRLSign; - } - KeyUsage keyUsage = new KeyUsage(keyUsageFlag); - return new Extension(Extension.keyUsage, true, - new DEROctetString(keyUsage)); - } - - private Optional<Extension> getSubjectAltNameExtension() throws - IOException { - if (altNames != null) { - return Optional.of(new Extension(Extension.subjectAlternativeName, - true, new DEROctetString(new GeneralNames( - altNames.toArray(new GeneralName[altNames.size()]))))); - } - return Optional.empty(); - } - - private Extension getBasicExtension() throws IOException { - // We don't set pathLenConstraint means no limit is imposed. - return new Extension(Extension.basicConstraints, - true, new DEROctetString(new BasicConstraints(ca))); - } - - private Extensions createExtensions() throws IOException { - List<Extension> extensions = new ArrayList<>(); - - // Add basic extension - extensions.add(getBasicExtension()); - - // Add key usage extension - extensions.add(getKeyUsageExtension()); - - // Add subject alternate name extension - Optional<Extension> san = getSubjectAltNameExtension(); - if (san.isPresent()) { - extensions.add(san.get()); - } - - return new Extensions( - extensions.toArray(new Extension[extensions.size()])); - } - - public PKCS10CertificationRequest build() throws SCMSecurityException { - Preconditions.checkNotNull(key, "KeyPair cannot be null"); - Preconditions.checkArgument(Strings.isNotBlank(subject), "Subject " + - "cannot be blank"); - Preconditions.checkArgument(Strings.isNotBlank(clusterID), "Cluster ID " + - "cannot be blank"); - Preconditions.checkArgument(Strings.isNotBlank(scmID), "SCM ID cannot " + - "be blank"); - - try { - CertificateSignRequest csr = new CertificateSignRequest(subject, scmID, - clusterID, key, config, createExtensions()); - return csr.generateCSR(); - } catch (IOException ioe) { - throw new CertificateException(String.format("Unable to create " + - "extension for certificate sign request for %s.", SecurityUtil - .getDistinguishedName(subject, scmID, clusterID)), ioe.getCause()); - } catch (OperatorCreationException ex) { - throw new CertificateException(String.format("Unable to create " + - "certificate sign request for %s.", SecurityUtil - .getDistinguishedName(subject, scmID, clusterID)), - ex.getCause()); - } - } - } -} http://git-wip-us.apache.org/repos/asf/hadoop/blob/87f51d23/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/SelfSignedCertificate.java ---------------------------------------------------------------------- diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/SelfSignedCertificate.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/SelfSignedCertificate.java deleted file mode 100644 index 85fba9b..0000000 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/SelfSignedCertificate.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package org.apache.hadoop.hdds.security.x509.certificates; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Preconditions; -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.hdds.security.exception.SCMSecurityException; -import org.apache.hadoop.hdds.security.x509.SecurityConfig; -import org.apache.hadoop.hdds.security.x509.exceptions.CertificateException; -import org.apache.hadoop.util.Time; -import org.apache.logging.log4j.util.Strings; -import org.bouncycastle.asn1.x500.X500Name; -import org.bouncycastle.asn1.x509.BasicConstraints; -import org.bouncycastle.asn1.x509.Extension; -import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; -import org.bouncycastle.cert.CertIOException; -import org.bouncycastle.cert.X509CertificateHolder; -import org.bouncycastle.cert.X509v3CertificateBuilder; -import org.bouncycastle.operator.ContentSigner; -import org.bouncycastle.operator.OperatorCreationException; -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; - -import java.math.BigInteger; -import java.security.KeyPair; -import java.time.Duration; -import java.util.Date; - -/** - * A Self Signed Certificate with CA basic constraint can be used to boot-strap - * a certificate infra-structure, if no external certificate is provided. - */ -public final class SelfSignedCertificate { - private static final String NAME_FORMAT = "CN=%s,OU=%s,O=%s"; - private String subject; - private String clusterID; - private String scmID; - private Date beginDate; - private Date endDate; - private KeyPair key; - private SecurityConfig config; - private boolean isCA; - - /** - * 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 ca - isCA? - */ - private SelfSignedCertificate(String subject, String scmID, String clusterID, - Date beginDate, Date endDate, SecurityConfig configuration, - KeyPair keyPair, boolean ca) { - this.subject = subject; - this.clusterID = clusterID; - this.scmID = scmID; - this.beginDate = beginDate; - this.endDate = endDate; - config = configuration; - this.key = keyPair; - this.isCA = ca; - } - - @VisibleForTesting - public static String getNameFormat() { - return NAME_FORMAT; - } - - public static Builder newBuilder() { - return new Builder(); - } - - private X509CertificateHolder generateCertificate() - throws OperatorCreationException, CertIOException { - // For the Root Certificate we form the name from Subject, SCM ID and - // Cluster ID. - String dnName = String.format(getNameFormat(), subject, scmID, clusterID); - X500Name name = new X500Name(dnName); - byte[] encoded = key.getPublic().getEncoded(); - SubjectPublicKeyInfo publicKeyInfo = - SubjectPublicKeyInfo.getInstance(encoded); - - - ContentSigner contentSigner = - new JcaContentSignerBuilder(config.getSignatureAlgo()) - .setProvider(config.getProvider()).build(key.getPrivate()); - - // Please note: Since this is a root certificate we use "ONE" as the - // serial number. Also note that skip enforcing locale or UTC. We are - // trying to operate at the Days level, hence Time zone is also skipped for - // now. - BigInteger serial = BigInteger.ONE; - if (!isCA) { - serial = new BigInteger(Long.toString(Time.monotonicNow())); - } - - X509v3CertificateBuilder builder = new X509v3CertificateBuilder(name, - serial, beginDate, endDate, name, publicKeyInfo); - - if (isCA) { - builder.addExtension(Extension.basicConstraints, true, - new BasicConstraints(true)); - } - return builder.build(contentSigner); - } - - /** - * Builder class for Root Certificates. - */ - public static class Builder { - private String subject; - private String clusterID; - private String scmID; - private Date beginDate; - private Date endDate; - private KeyPair key; - private SecurityConfig config; - private boolean isCA; - - public Builder setConfiguration(Configuration configuration) { - this.config = new SecurityConfig(configuration); - return this; - } - - public Builder setKey(KeyPair keyPair) { - this.key = keyPair; - return this; - } - - public Builder setSubject(String subjectString) { - this.subject = subjectString; - return this; - } - - public Builder setClusterID(String s) { - this.clusterID = s; - return this; - } - - public Builder setScmID(String s) { - this.scmID = s; - return this; - } - - public Builder setBeginDate(Date date) { - this.beginDate = new Date(date.toInstant().toEpochMilli()); - return this; - } - - public Builder setEndDate(Date date) { - this.endDate = new Date(date.toInstant().toEpochMilli()); - return this; - } - - public Builder makeCA() { - isCA = true; - return this; - } - - public X509CertificateHolder build() throws SCMSecurityException { - Preconditions.checkNotNull(key, "Key cannot be null"); - Preconditions.checkArgument(Strings.isNotBlank(subject), "Subject " + - "cannot be blank"); - Preconditions.checkArgument(Strings.isNotBlank(clusterID), "Cluster ID " + - "cannot be blank"); - Preconditions.checkArgument(Strings.isNotBlank(scmID), "SCM ID cannot " + - "be blank"); - - Preconditions.checkArgument(beginDate.before(endDate), "Certificate " + - "begin date should be before end date"); - - Duration certDuration = Duration.between(beginDate.toInstant(), - endDate.toInstant()); - Preconditions.checkArgument( - certDuration.compareTo(config.getMaxCertificateDuration()) < 0, - "Certificate life time cannot be greater than max configured value."); - - - SelfSignedCertificate rootCertificate = - new SelfSignedCertificate(this.subject, - this.scmID, this.clusterID, this.beginDate, this.endDate, - this.config, key, isCA); - try { - return rootCertificate.generateCertificate(); - } catch (OperatorCreationException | CertIOException e) { - throw new CertificateException("Unable to create root certificate.", - e.getCause()); - } - } - } -} http://git-wip-us.apache.org/repos/asf/hadoop/blob/87f51d23/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/package-info.java ---------------------------------------------------------------------- diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/package-info.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/package-info.java deleted file mode 100644 index e88737c..0000000 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/package-info.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ -/** - * Utils for Certificates. - */ -package org.apache.hadoop.hdds.security.x509.certificates; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/hadoop/blob/87f51d23/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/utils/CertificateSignRequest.java ---------------------------------------------------------------------- diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/utils/CertificateSignRequest.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/utils/CertificateSignRequest.java new file mode 100644 index 0000000..3624b32 --- /dev/null +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/utils/CertificateSignRequest.java @@ -0,0 +1,245 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.hadoop.hdds.security.x509.certificates.utils; + +import com.google.common.base.Preconditions; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hdds.security.exception.SCMSecurityException; +import org.apache.hadoop.hdds.security.x509.SecurityConfig; +import org.apache.hadoop.hdds.security.x509.exceptions.CertificateException; +import org.apache.hadoop.hdds.security.x509.keys.SecurityUtil; +import org.apache.logging.log4j.util.Strings; +import org.bouncycastle.asn1.DEROctetString; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.Extensions; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.asn1.x509.KeyUsage; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder; +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; + +import java.io.IOException; +import java.security.KeyPair; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * A certificate sign request object that wraps operations to build a + * PKCS10CertificationRequest to CertificateServer. + */ +public final class CertificateSignRequest { + private final KeyPair keyPair; + private final SecurityConfig config; + private final Extensions extensions; + private String subject; + private String clusterID; + private String scmID; + + /** + * Private Ctor for CSR. + * + * @param subject - Subject + * @param scmID - SCM ID + * @param clusterID - Cluster ID + * @param keyPair - KeyPair + * @param config - SCM Config + * @param extensions - CSR extensions + */ + private CertificateSignRequest(String subject, String scmID, String clusterID, + KeyPair keyPair, SecurityConfig config, Extensions extensions) { + this.subject = subject; + this.clusterID = clusterID; + this.scmID = scmID; + this.keyPair = keyPair; + this.config = config; + this.extensions = extensions; + } + + private PKCS10CertificationRequest generateCSR() throws + OperatorCreationException { + X500Name dnName = SecurityUtil.getDistinguishedName(subject, scmID, + clusterID); + PKCS10CertificationRequestBuilder p10Builder = + new JcaPKCS10CertificationRequestBuilder(dnName, keyPair.getPublic()); + + ContentSigner contentSigner = + new JcaContentSignerBuilder(config.getSignatureAlgo()) + .setProvider(config.getProvider()) + .build(keyPair.getPrivate()); + + if (extensions != null) { + p10Builder.addAttribute( + PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extensions); + } + return p10Builder.build(contentSigner); + } + + /** + * Builder class for Certificate Sign Request. + */ + public static class Builder { + private String subject; + private String clusterID; + private String scmID; + private KeyPair key; + private SecurityConfig config; + private List<GeneralName> altNames; + private Boolean ca = false; + + public CertificateSignRequest.Builder setConfiguration( + Configuration configuration) { + this.config = new SecurityConfig(configuration); + return this; + } + + public CertificateSignRequest.Builder setKey(KeyPair keyPair) { + this.key = keyPair; + return this; + } + + public CertificateSignRequest.Builder setSubject(String subjectString) { + this.subject = subjectString; + return this; + } + + public CertificateSignRequest.Builder setClusterID(String s) { + this.clusterID = s; + return this; + } + + public CertificateSignRequest.Builder setScmID(String s) { + this.scmID = s; + return this; + } + + // Support SAN extenion with DNS and RFC822 Name + // other name type will be added as needed. + public CertificateSignRequest.Builder addDnsName(String dnsName) { + Preconditions.checkNotNull(dnsName, "dnsName cannot be null"); + this.addAltName(GeneralName.dNSName, dnsName); + return this; + } + + public CertificateSignRequest.Builder addRfc822Name(String name) { + Preconditions.checkNotNull(name, "Rfc822Name cannot be null"); + this.addAltName(GeneralName.rfc822Name, name); + return this; + } + + // IP address is subject to change which is optional for now. + public CertificateSignRequest.Builder addIpAddress(String ip) { + Preconditions.checkNotNull(ip, "Ip address cannot be null"); + this.addAltName(GeneralName.iPAddress, ip); + return this; + } + + private CertificateSignRequest.Builder addAltName(int tag, String name) { + if (altNames == null) { + altNames = new ArrayList<>(); + } + altNames.add(new GeneralName(tag, name)); + return this; + } + + public CertificateSignRequest.Builder setCA(Boolean isCA) { + this.ca = isCA; + return this; + } + + private Extension getKeyUsageExtension() throws IOException { + int keyUsageFlag = KeyUsage.digitalSignature | KeyUsage.keyEncipherment + | KeyUsage.dataEncipherment | KeyUsage.keyAgreement; + + if (ca) { + keyUsageFlag |= KeyUsage.keyCertSign | KeyUsage.cRLSign; + } + KeyUsage keyUsage = new KeyUsage(keyUsageFlag); + return new Extension(Extension.keyUsage, true, + new DEROctetString(keyUsage)); + } + + private Optional<Extension> getSubjectAltNameExtension() throws + IOException { + if (altNames != null) { + return Optional.of(new Extension(Extension.subjectAlternativeName, + true, new DEROctetString(new GeneralNames( + altNames.toArray(new GeneralName[altNames.size()]))))); + } + return Optional.empty(); + } + + private Extension getBasicExtension() throws IOException { + // We don't set pathLenConstraint means no limit is imposed. + return new Extension(Extension.basicConstraints, + true, new DEROctetString(new BasicConstraints(ca))); + } + + private Extensions createExtensions() throws IOException { + List<Extension> extensions = new ArrayList<>(); + + // Add basic extension + extensions.add(getBasicExtension()); + + // Add key usage extension + extensions.add(getKeyUsageExtension()); + + // Add subject alternate name extension + Optional<Extension> san = getSubjectAltNameExtension(); + if (san.isPresent()) { + extensions.add(san.get()); + } + + return new Extensions( + extensions.toArray(new Extension[extensions.size()])); + } + + public PKCS10CertificationRequest build() throws SCMSecurityException { + Preconditions.checkNotNull(key, "KeyPair cannot be null"); + Preconditions.checkArgument(Strings.isNotBlank(subject), "Subject " + + "cannot be blank"); + Preconditions.checkArgument(Strings.isNotBlank(clusterID), "Cluster ID " + + "cannot be blank"); + Preconditions.checkArgument(Strings.isNotBlank(scmID), "SCM ID cannot " + + "be blank"); + + try { + CertificateSignRequest csr = new CertificateSignRequest(subject, scmID, + clusterID, key, config, createExtensions()); + return csr.generateCSR(); + } catch (IOException ioe) { + throw new CertificateException(String.format("Unable to create " + + "extension for certificate sign request for %s.", SecurityUtil + .getDistinguishedName(subject, scmID, clusterID)), ioe.getCause()); + } catch (OperatorCreationException ex) { + throw new CertificateException(String.format("Unable to create " + + "certificate sign request for %s.", SecurityUtil + .getDistinguishedName(subject, scmID, clusterID)), + ex.getCause()); + } + } + } +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/87f51d23/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/utils/SelfSignedCertificate.java ---------------------------------------------------------------------- diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/utils/SelfSignedCertificate.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/utils/SelfSignedCertificate.java new file mode 100644 index 0000000..1fd6d7c --- /dev/null +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/utils/SelfSignedCertificate.java @@ -0,0 +1,238 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.hadoop.hdds.security.x509.certificates.utils; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hdds.security.exception.SCMSecurityException; +import org.apache.hadoop.hdds.security.x509.SecurityConfig; +import org.apache.hadoop.hdds.security.x509.exceptions.CertificateException; +import org.apache.hadoop.util.Time; +import org.apache.logging.log4j.util.Strings; +import org.bouncycastle.asn1.DEROctetString; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.KeyUsage; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.cert.CertIOException; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.KeyPair; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.util.Date; + +/** + * A Self Signed Certificate with CertificateServer basic constraint can be used + * to bootstrap a certificate infrastructure, if no external certificate is + * provided. + */ +public final class SelfSignedCertificate { + private static final String NAME_FORMAT = "CN=%s,OU=%s,O=%s"; + private String subject; + private String clusterID; + private String scmID; + private LocalDate beginDate; + private LocalDate endDate; + private KeyPair key; + private SecurityConfig config; + + /** + * 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 + */ + 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; + } + + @VisibleForTesting + public static String getNameFormat() { + return NAME_FORMAT; + } + + public static Builder newBuilder() { + return new Builder(); + } + + private X509CertificateHolder generateCertificate(boolean isCA) + throws OperatorCreationException, IOException { + // For the Root Certificate we form the name from Subject, SCM ID and + // Cluster ID. + String dnName = String.format(getNameFormat(), subject, scmID, clusterID); + X500Name name = new X500Name(dnName); + byte[] encoded = key.getPublic().getEncoded(); + SubjectPublicKeyInfo publicKeyInfo = + SubjectPublicKeyInfo.getInstance(encoded); + + + ContentSigner contentSigner = + new JcaContentSignerBuilder(config.getSignatureAlgo()) + .setProvider(config.getProvider()).build(key.getPrivate()); + + // Please note: Since this is a root certificate we use "ONE" as the + // serial number. Also note that skip enforcing locale or UTC. We are + // trying to operate at the Days level, hence Time zone is also skipped for + // now. + BigInteger serial = BigInteger.ONE; + if (!isCA) { + serial = new BigInteger(Long.toString(Time.monotonicNow())); + } + + ZoneOffset zoneOffset = + beginDate.atStartOfDay(ZoneOffset.systemDefault()).getOffset(); + + // Valid from the Start of the day when we generate this Certificate. + Date validFrom = + Date.from(beginDate.atTime(LocalTime.MIN).toInstant(zoneOffset)); + + // Valid till end day finishes. + Date validTill = + Date.from(endDate.atTime(LocalTime.MAX).toInstant(zoneOffset)); + + X509v3CertificateBuilder builder = new X509v3CertificateBuilder(name, + serial, validFrom, validTill, name, publicKeyInfo); + + if (isCA) { + builder.addExtension(Extension.basicConstraints, true, + new BasicConstraints(true)); + int keyUsageFlag = KeyUsage.keyCertSign | KeyUsage.cRLSign; + KeyUsage keyUsage = new KeyUsage(keyUsageFlag); + builder.addExtension(Extension.keyUsage, false, + new DEROctetString(keyUsage)); + } + return builder.build(contentSigner); + } + + /** + * Builder class for Root Certificates. + */ + public static class Builder { + private String subject; + private String clusterID; + private String scmID; + private LocalDate beginDate; + private LocalDate endDate; + private KeyPair key; + private SecurityConfig config; + private boolean isCA; + + public Builder setConfiguration(Configuration configuration) { + this.config = new SecurityConfig(configuration); + return this; + } + + public Builder setKey(KeyPair keyPair) { + this.key = keyPair; + return this; + } + + public Builder setSubject(String subjectString) { + this.subject = subjectString; + return this; + } + + public Builder setClusterID(String s) { + this.clusterID = s; + return this; + } + + public Builder setScmID(String s) { + this.scmID = s; + return this; + } + + public Builder setBeginDate(LocalDate date) { + this.beginDate = date; + return this; + } + + public Builder setEndDate(LocalDate date) { + this.endDate = date; + return this; + } + + public Builder makeCA() { + isCA = true; + return this; + } + + public X509CertificateHolder build() + throws SCMSecurityException, IOException { + Preconditions.checkNotNull(key, "Key cannot be null"); + Preconditions.checkArgument(Strings.isNotBlank(subject), "Subject " + + "cannot be blank"); + Preconditions.checkArgument(Strings.isNotBlank(clusterID), "Cluster ID " + + "cannot be blank"); + Preconditions.checkArgument(Strings.isNotBlank(scmID), "SCM ID cannot " + + "be blank"); + + Preconditions.checkArgument(beginDate.isBefore(endDate), "Certificate " + + "begin date should be before end date"); + + // We just read the beginDate and EndDate as Start of the Day and + // confirm that we do not violate the maxDuration Config. + Duration certDuration = Duration.between(beginDate.atStartOfDay(), + endDate.atStartOfDay()); + Duration maxDuration = config.getMaxCertificateDuration(); + if (certDuration.compareTo(maxDuration) > 0) { + throw new SCMSecurityException("The cert duration violates the " + + "maximum configured value. Please check the hdds.x509.max" + + ".duration config key. Current Value: " + certDuration + + " config: " + maxDuration); + } + + SelfSignedCertificate rootCertificate = + new SelfSignedCertificate(this.subject, + this.scmID, this.clusterID, this.beginDate, this.endDate, + this.config, key); + try { + return rootCertificate.generateCertificate(isCA); + } catch (OperatorCreationException | CertIOException e) { + throw new CertificateException("Unable to create root certificate.", + e.getCause()); + } + } + } +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/87f51d23/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/utils/package-info.java ---------------------------------------------------------------------- diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/utils/package-info.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/utils/package-info.java new file mode 100644 index 0000000..945adc9 --- /dev/null +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/certificates/utils/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +/** + * Utils for Certificates. + */ +package org.apache.hadoop.hdds.security.x509.certificates.utils; \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: common-commits-unsubscr...@hadoop.apache.org For additional commands, e-mail: common-commits-h...@hadoop.apache.org