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 index 6e590df04898..517aca2adb4a 100644 --- 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 @@ -20,28 +20,36 @@ package org.apache.hadoop.hdds.security.x509.certificate.utils; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; 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.jcajce.provider.asymmetric.x509.CertificateFactory; import org.bouncycastle.openssl.jcajce.JcaPEMWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.ByteArrayInputStream; 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.charset.Charset; +import java.nio.charset.StandardCharsets; 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.CertPath; +import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -58,6 +66,7 @@ public class CertificateCodec { public static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----"; public static final String END_CERT = "-----END CERTIFICATE-----"; + public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; private static final Logger LOG = LoggerFactory.getLogger(CertificateCodec.class); @@ -65,7 +74,7 @@ public class CertificateCodec { = new JcaX509CertificateConverter(); private final SecurityConfig securityConfig; private final Path location; - private Set permissionSet = + private final Set permissionSet = Stream.of(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE) .collect(Collectors.toSet()); /** @@ -96,6 +105,19 @@ public static X509Certificate getX509Certificate(X509CertificateHolder holder) return CERTIFICATE_CONVERTER.getCertificate(holder); } + /** + * Get a valid pem encoded string for the certification path. + */ + public static String getPEMEncodedString(CertPath certPath) + throws SCMSecurityException { + List certsInPath = certPath.getCertificates(); + ArrayList pemEncodedList = new ArrayList<>(certsInPath.size()); + for (Certificate cert : certsInPath) { + pemEncodedList.add(getPEMEncodedString((X509Certificate) cert)); + } + return StringUtils.join(pemEncodedList, "\n"); + } + /** * Returns the Certificate as a PEM encoded String. * @@ -136,7 +158,9 @@ public static String getPEMEncodedString(X509Certificate certificate) } /** - * Gets the X.509 Certificate from PEM encoded String. + * Get the leading X.509 Certificate from PEM encoded String possibly + * containing multiple certificates. To get all certificates, use + * {@link #getCertPathFromPemEncodedString(String)}. * * @param pemEncodedString - PEM encoded String. * @return X509Certificate - Certificate. @@ -145,12 +169,20 @@ public static String getPEMEncodedString(X509Certificate certificate) */ public static X509Certificate getX509Certificate(String pemEncodedString) throws CertificateException, IOException { - CertificateFactory fact = CertificateFactory.getInstance("X.509"); + CertificateFactory fact = getCertFactory(); try (InputStream input = IOUtils.toInputStream(pemEncodedString, UTF_8)) { - return (X509Certificate) fact.generateCertificate(input); + return (X509Certificate) fact.engineGenerateCertificate(input); } } + public static X509Certificate firstCertificateFrom(CertPath certificatePath) { + return (X509Certificate) certificatePath.getCertificates().get(0); + } + + public static CertificateFactory getCertFactory() { + return new CertificateFactory(); + } + /** * Get Certificate location. * @@ -160,78 +192,58 @@ public Path getLocation() { return location; } - /** - * 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 static X509Certificate getX509Cert(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. + * @throws IOException - on Error. */ public void writeCertificate(X509CertificateHolder xCertificate) throws SCMSecurityException, IOException { String pem = getPEMEncodedString(xCertificate); writeCertificate(location.toAbsolutePath(), - this.securityConfig.getCertificateFileName(), pem, false); + this.securityConfig.getCertificateFileName(), pem); } /** * 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. + * @param fileName - file name to write to. * @throws SCMSecurityException - On Error. * @throws IOException - On Error. */ public void writeCertificate(X509CertificateHolder xCertificate, - String fileName, boolean overwrite) - throws SCMSecurityException, IOException { + String fileName) throws IOException { String pem = getPEMEncodedString(xCertificate); - writeCertificate(location.toAbsolutePath(), fileName, pem, overwrite); + writeCertificate(location.toAbsolutePath(), fileName, pem); + } + + /** + * Write the pem encoded string to the specified file. + */ + public void writeCertificate(String fileName, String pemEncodedCert) + throws IOException { + writeCertificate(location.toAbsolutePath(), fileName, pemEncodedCert); } /** * 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 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) + String pemEncodedCertificate) throws IOException { + checkBasePathDirectory(basePath); 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); } @@ -240,54 +252,43 @@ public synchronized void writeCertificate(Path basePath, String fileName, } /** - * 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. + * Gets a certificate path from the specified pem encoded String. */ - public X509CertificateHolder readCertificate() throws - CertificateException, IOException { - return readCertificate(this.location.toAbsolutePath(), - this.securityConfig.getCertificateFileName()); + public static CertPath getCertPathFromPemEncodedString( + String pemString) throws CertificateException, IOException { + try (InputStream is = + new ByteArrayInputStream(pemString.getBytes(DEFAULT_CHARSET))) { + return generateCertPathFromInputStream(is); + } + } + + private CertPath getCertPath(Path path, String fileName) throws IOException, + CertificateException { + checkBasePathDirectory(path.toAbsolutePath()); + File certFile = + Paths.get(path.toAbsolutePath().toString(), fileName).toFile(); + if (!certFile.exists()) { + throw new IOException("Unable to find the requested certificate file. " + + "Path: " + certFile); + } + try (FileInputStream is = new FileInputStream(certFile)) { + return generateCertPathFromInputStream(is); + } } /** - * 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. + * Get the certificate path stored under the specified filename. */ - public synchronized X509CertificateHolder readCertificate(Path basePath, - String fileName) throws IOException, CertificateException { - File certificateFile = Paths.get(basePath.toString(), fileName).toFile(); - return getX509CertificateHolder(certificateFile); + public CertPath getCertPath(String fileName) + throws IOException, CertificateException { + return getCertPath(location, fileName); } /** - * Helper function to read certificate. - * - * @param certificateFile - Full path to certificate file. - * @return X509CertificateHolder - * @throws IOException - On Error. - * @throws CertificateException - On Error. + * Get the default certificate path for this cert codec. */ - 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)); - } + public CertPath getCertPath() throws CertificateException, IOException { + return getCertPath(this.securityConfig.getCertificateFileName()); } /** @@ -303,4 +304,58 @@ public static X509CertificateHolder getCertificateHolder( throws CertificateEncodingException, IOException { return new X509CertificateHolder(x509cert.getEncoded()); } + + /** + * Helper method that takes in a certificate path and a certificate and + * generates a new certificate path starting with the new certificate + * followed by all certificates in the specified path. + */ + public CertPath prependCertToCertPath(X509CertificateHolder certHolder, + CertPath path) throws CertificateException { + List certificates = path.getCertificates(); + ArrayList updatedList = new ArrayList<>(); + updatedList.add(getX509Certificate(certHolder)); + for (Certificate cert : certificates) { + updatedList.add((X509Certificate) cert); + } + CertificateFactory factory = getCertFactory(); + return factory.engineGenerateCertPath(updatedList); + } + + /** + * Helper method that gets one certificate from the specified location. + * The remaining certificates are ignored. + */ + public X509CertificateHolder getTargetCertHolder(Path path, + String fileName) throws CertificateException, IOException { + CertPath certPath = getCertPath(path, fileName); + X509Certificate certificate = firstCertificateFrom(certPath); + return getCertificateHolder(certificate); + } + + /** + * Helper method that gets one certificate from the default location. + * The remaining certificates are ignored. + */ + public X509CertificateHolder getTargetCertHolder() + throws CertificateException, IOException { + return getTargetCertHolder( + location, securityConfig.getCertificateFileName()); + } + + private static CertPath generateCertPathFromInputStream( + InputStream inputStream) throws CertificateException { + CertificateFactory fact = getCertFactory(); + return fact.engineGenerateCertPath(inputStream, "PEM"); + } + + private void checkBasePathDirectory(Path basePath) throws IOException { + 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); + } + } + } } diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/CAType.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/CAType.java new file mode 100644 index 000000000000..06bea6dbd9a8 --- /dev/null +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/CAType.java @@ -0,0 +1,41 @@ +/* + * 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; + +/** + * Certificate authority type. Root certificate is typically a self-signed + * certificate by the SCM. It can also be specified from an external + * source. + */ +public enum CAType { + NONE(""), + SUBORDINATE("CA-"), + ROOT("ROOTCA-"); + + private final String fileNamePrefix; + + public final String getFileNamePrefix() { + return fileNamePrefix; + } + + CAType(String fileNamePrefix) { + this.fileNamePrefix = fileNamePrefix; + } +} diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/CertificateServer.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/CertificateServer.java index 0af22da2fd5f..69d28ccc6885 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/CertificateServer.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/authority/CertificateServer.java @@ -31,6 +31,7 @@ import java.io.IOException; import java.math.BigInteger; +import java.security.cert.CertPath; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Date; @@ -65,6 +66,19 @@ void init(SecurityConfig securityConfig, CAType type) X509CertificateHolder getCACertificate() throws CertificateException, IOException; + /** + * Gets the certificate bundle for the CA certificate of this server. + * The first element of the list is the CA certificate. The issuer of an + * element of this list is always the next element of the list. The root CA + * certificate is the final element. + * + * @return the certificate bundle starting with the CA certificate. + * @throws CertificateException + * @throws IOException + */ + CertPath getCaCertPath() throws CertificateException, + IOException; + /** * Returns the Certificate corresponding to given certificate serial id if * exist. Return null if it doesn't exist. @@ -87,7 +101,7 @@ X509Certificate getCertificate(String certSerialId) * approved. * @throws SCMSecurityException - on Error. */ - Future requestCertificate( + Future requestCertificate( PKCS10CertificationRequest csr, CertificateApprover.ApprovalType type, NodeType role) throws SCMSecurityException; @@ -96,14 +110,15 @@ Future requestCertificate( /** * Request a Certificate based on Certificate Signing Request. * - * @param csr - Certificate Signing Request as a PEM encoded String. - * @param type - An Enum which says what kind of approval process to follow. + * @param csr - Certificate Signing Request as a PEM encoded String. + * @param type - An Enum which says what kind of approval process to + * follow. * @param nodeType: OM/SCM/DN * @return A future that will have this certificate when this request is * approved. * @throws SCMSecurityException - on Error. */ - Future requestCertificate(String csr, + Future requestCertificate(String csr, ApprovalType type, NodeType nodeType) throws IOException; /** @@ -150,12 +165,4 @@ List listCertificate(NodeType role, * @return latest CRL id. */ long getLatestCrlId(); - - /** - * Make it explicit what type of CertificateServer we are creating here. - */ - enum CAType { - SELF_SIGNED_CA, - INTERMEDIARY_CA - } } 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 a3a2bccaae2c..6fa860cdac53 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 @@ -51,6 +51,7 @@ import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.cert.CertPath; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; @@ -180,12 +181,19 @@ public X509CertificateHolder getCACertificate() throws IOException { CertificateCodec certificateCodec = new CertificateCodec(config, componentName); try { - return certificateCodec.readCertificate(); + return certificateCodec.getTargetCertHolder(); } catch (CertificateException e) { throw new IOException(e); } } + @Override + public CertPath getCaCertPath() + throws CertificateException, IOException { + CertificateCodec codec = new CertificateCodec(config, componentName); + return codec.getCertPath(); + } + /** * Returns the Certificate corresponding to given certificate serial id if * exist. Return null if it doesn't exist. @@ -213,7 +221,7 @@ private KeyPair getCAKeys() throws IOException { } @Override - public Future requestCertificate( + public Future requestCertificate( PKCS10CertificationRequest csr, CertificateApprover.ApprovalType approverType, NodeType role) { LocalDate beginDate = LocalDate.now().atStartOfDay().toLocalDate(); @@ -231,16 +239,20 @@ public Future requestCertificate( CompletableFuture xcertHolder = approver.inspectCSR(csr); + CompletableFuture xCertHolders + = new CompletableFuture<>(); + if (xcertHolder.isCompletedExceptionally()) { // This means that approver told us there are things which it disagrees // with in this Certificate Request. Since the first set of sanity // checks failed, we just return the future object right here. - return xcertHolder; + xCertHolders.completeExceptionally(new SCMSecurityException("Failed to " + + "verify the CSR.")); } try { switch (approverType) { case MANUAL: - xcertHolder.completeExceptionally(new SCMSecurityException("Manual " + + xCertHolders.completeExceptionally(new SCMSecurityException("Manual " + "approval is not yet implemented.")); break; case KERBEROS_TRUSTED: @@ -254,7 +266,10 @@ public Future requestCertificate( LOG.error("Certificate storage failed, retrying one more time.", e); xcert = signAndStoreCertificate(beginDate, endDate, csr, role); } - xcertHolder.complete(xcert); + CertificateCodec codec = new CertificateCodec(config, componentName); + CertPath certPath = codec.getCertPath(); + CertPath updatedCertPath = codec.prependCertToCertPath(xcert, certPath); + xCertHolders.complete(updatedCertPath); break; default: return null; // cannot happen, keeping checkstyle happy. @@ -262,10 +277,10 @@ public Future requestCertificate( } catch (CertificateException | IOException | OperatorCreationException | TimeoutException e) { LOG.error("Unable to issue a certificate.", e); - xcertHolder.completeExceptionally( + xCertHolders.completeExceptionally( new SCMSecurityException(e, UNABLE_TO_ISSUE_CERTIFICATE)); } - return xcertHolder; + return xCertHolders; } private X509CertificateHolder signAndStoreCertificate(LocalDate beginDate, @@ -292,7 +307,7 @@ private X509CertificateHolder signAndStoreCertificate(LocalDate beginDate, } @Override - public Future requestCertificate(String csr, + public Future requestCertificate(String csr, CertificateApprover.ApprovalType type, NodeType nodeType) throws IOException { PKCS10CertificationRequest request = @@ -474,9 +489,9 @@ Consumer processVerificationStatus( }; break; case INITIALIZE: - if (type == CAType.SELF_SIGNED_CA) { + if (type == CAType.ROOT) { consumer = this::initRootCa; - } else if (type == CAType.INTERMEDIARY_CA) { + } else if (type == CAType.SUBORDINATE) { // For sub CA certificates are generated during bootstrap/init. If // both keys/certs are missing, init/bootstrap is missed to be // performed. @@ -602,7 +617,7 @@ private void initWithExternalRootCa(SecurityConfig conf) { throw new IOException("External cert path is not correct: " + extCertPath); } - X509CertificateHolder certHolder = certificateCodec.readCertificate( + X509CertificateHolder certHolder = certificateCodec.getTargetCertHolder( extCertParent, extCertName.toString()); Path extPrivateKeyParent = extPrivateKeyPath.getParent(); Path extPrivateKeyFileName = extPrivateKeyPath.getFileName(); diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/CertificateClient.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/CertificateClient.java index 9f6564b1ad28..73123c34a67c 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/CertificateClient.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/CertificateClient.java @@ -21,6 +21,7 @@ import org.apache.hadoop.hdds.security.OzoneSecurityException; import org.apache.hadoop.hdds.security.ssl.KeyStoresFactory; +import org.apache.hadoop.hdds.security.x509.certificate.authority.CAType; import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateSignRequest; import org.apache.hadoop.hdds.security.x509.crl.CRLInfo; import org.apache.hadoop.hdds.security.x509.exception.CertificateException; @@ -33,6 +34,7 @@ import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.cert.CertPath; import java.security.cert.CertStore; import java.security.cert.X509Certificate; import java.util.List; @@ -73,15 +75,32 @@ X509Certificate getCertificate(String certSerialId) throws CertificateException; /** - * Returns the certificate of the specified component if it exists on the - * local system. + * Returns the full certificate path of the specified component if it + * exists on the local system. * * @return certificate or Null if there is no data. */ + CertPath getCertPath(); + + /** + * Returns the certificate used by the specified component if it exists + * on the local system. + * + * @return the target certificate or null if there is no data. + */ X509Certificate getCertificate(); + /** + * Returns the full certificate path for the CA certificate known to the + * client. + * + * @return latest ca certificate path known to the client + */ + CertPath getCACertPath(); + /** * Return the latest CA certificate known to the client. + * * @return latest ca certificate known to the client. */ X509Certificate getCACertificate(); @@ -184,25 +203,21 @@ String signAndStoreCertificate(PKCS10CertificationRequest request) * Stores the Certificate for this client. Don't use this api to add * trusted certificates of others. * - * @param pemEncodedCert - pem encoded X509 Certificate - * @param force - override any existing file + * @param pemEncodedCert - pem encoded X509 Certificate * @throws CertificateException - on Error. - * */ - void storeCertificate(String pemEncodedCert, boolean force) + void storeCertificate(String pemEncodedCert) throws CertificateException; /** * Stores the Certificate for this client. Don't use this api to add * trusted certificates of others. * - * @param pemEncodedCert - pem encoded X509 Certificate - * @param force - override any existing file - * @param caCert - Is CA certificate. + * @param pemEncodedCert - pem encoded X509 Certificate + * @param caType - Is CA certificate. * @throws CertificateException - on Error. - * */ - void storeCertificate(String pemEncodedCert, boolean force, boolean caCert) + void storeCertificate(String pemEncodedCert, CAType caType) throws CertificateException; /** @@ -272,11 +287,11 @@ enum InitResponse { /** * Store RootCA certificate. + * * @param pemEncodedCert - * @param force * @throws CertificateException */ - void storeRootCACertificate(String pemEncodedCert, boolean force) + void storeRootCACertificate(String pemEncodedCert) throws CertificateException; /** @@ -365,6 +380,7 @@ default void assertValidKeysAndCertificate() throws OzoneSecurityException { /** * Register a receiver that will be called after the certificate renewed. + * * @param receiver */ void registerNotificationReceiver(CertificateNotification receiver); 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 73653e14a00d..6d04d20674e6 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 @@ -22,6 +22,7 @@ import org.apache.hadoop.hdds.protocol.DatanodeDetails; import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos; import org.apache.hadoop.hdds.security.x509.SecurityConfig; +import org.apache.hadoop.hdds.security.x509.certificate.authority.CAType; import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateCodec; import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateSignRequest; import org.apache.hadoop.hdds.security.x509.exception.CertificateException; @@ -106,7 +107,7 @@ public CertificateSignRequest.Builder getCSRBuilder(KeyPair keyPair) @Override public String signAndStoreCertificate(PKCS10CertificationRequest csr, - Path certPath) throws CertificateException { + Path certificatePath) throws CertificateException { try { // TODO: For SCM CA we should fetch certificate from multiple SCMs. SCMSecurityProtocolProtos.SCMGetCertResponseProto response = @@ -117,16 +118,19 @@ public String signAndStoreCertificate(PKCS10CertificationRequest csr, if (response.hasX509CACertificate()) { String pemEncodedCert = response.getX509Certificate(); CertificateCodec certCodec = new CertificateCodec( - getSecurityConfig(), certPath); + getSecurityConfig(), certificatePath); // Certs will be added to cert map after reloadAllCertificate called - storeCertificate(pemEncodedCert, true, false, false, certCodec, false); - storeCertificate(response.getX509CACertificate(), true, true, - false, certCodec, false); + storeCertificate(pemEncodedCert, CAType.NONE, + certCodec, + false); + storeCertificate(response.getX509CACertificate(), + CAType.SUBORDINATE, + certCodec, false); // Store Root CA certificate. if (response.hasX509RootCACertificate()) { - storeCertificate(response.getX509RootCACertificate(), true, false, - true, certCodec, false); + storeCertificate(response.getX509RootCACertificate(), + CAType.ROOT, certCodec, false); } // Return the default certificate ID String dnCertSerialId = getX509Certificate(pemEncodedCert). diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java index ad4ebbfb6d1f..b680b2023c2c 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/DefaultCertificateClient.java @@ -37,6 +37,7 @@ import java.security.SecureRandom; import java.security.Signature; import java.security.SignatureException; +import java.security.cert.CertPath; import java.security.cert.CertStore; import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; @@ -66,6 +67,7 @@ import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.apache.hadoop.hdds.protocolPB.SCMSecurityProtocolClientSideTranslatorPB; import org.apache.hadoop.hdds.security.ssl.KeyStoresFactory; +import org.apache.hadoop.hdds.security.x509.certificate.authority.CAType; import org.apache.hadoop.hdds.security.x509.crl.CRLInfo; import org.apache.hadoop.hdds.security.x509.SecurityConfig; import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateCodec; @@ -97,7 +99,6 @@ import static org.apache.hadoop.hdds.security.x509.exception.CertificateException.ErrorCode.ROLLBACK_ERROR; import static org.apache.hadoop.hdds.utils.HddsServerUtil.getScmSecurityClientWithMaxRetry; -import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.pkcs.PKCS10CertificationRequest; import org.slf4j.Logger; @@ -111,17 +112,15 @@ public abstract class DefaultCertificateClient implements CertificateClient { private static final Random RANDOM = new SecureRandom(); private static final String CERT_FILE_NAME_FORMAT = "%s.crt"; - private static final String CA_CERT_PREFIX = "CA-"; private static final int CA_CERT_PREFIX_LEN = 3; - private static final String ROOT_CA_CERT_PREFIX = "ROOTCA-"; private static final int ROOT_CA_PREFIX_LEN = 7; private final Logger logger; private final SecurityConfig securityConfig; private final KeyCodec keyCodec; private PrivateKey privateKey; private PublicKey publicKey; - private X509Certificate x509Certificate; - private Map certificateMap; + private CertPath certPath; + private Map certificateMap; private String certSerialId; private String caCertId; private String rootCaCertId; @@ -176,11 +175,11 @@ public synchronized void setCertificateId(String certId) { * */ private synchronized void loadAllCertificates() { // See if certs directory exists in file system. - Path certPath = securityConfig.getCertificateLocation(component); - if (Files.exists(certPath) && Files.isDirectory(certPath)) { + Path certificatePath = securityConfig.getCertificateLocation(component); + if (Files.exists(certificatePath) && Files.isDirectory(certificatePath)) { getLogger().info("Loading certificate from location:{}.", - certPath); - File[] certFiles = certPath.toFile().listFiles(); + certificatePath); + File[] certFiles = certificatePath.toFile().listFiles(); if (certFiles != null) { CertificateCodec certificateCodec = @@ -190,17 +189,17 @@ private synchronized void loadAllCertificates() { for (File file : certFiles) { if (file.isFile()) { try { - X509CertificateHolder x509CertificateHolder = certificateCodec - .readCertificate(certPath, file.getName()); - X509Certificate cert = - CertificateCodec.getX509Certificate(x509CertificateHolder); + CertPath allCertificates = + certificateCodec.getCertPath(file.getName()); + X509Certificate cert = firstCertificateFrom(allCertificates); if (cert != null && cert.getSerialNumber() != null) { if (cert.getSerialNumber().toString().equals(certSerialId)) { - x509Certificate = cert; + this.certPath = allCertificates; } certificateMap.putIfAbsent(cert.getSerialNumber().toString(), - cert); - if (file.getName().startsWith(CA_CERT_PREFIX)) { + allCertificates); + if (file.getName().startsWith( + CAType.SUBORDINATE.getFileNamePrefix())) { String certFileName = FilenameUtils.getBaseName( file.getName()); long tmpCaCertSerailId = NumberUtils.toLong( @@ -210,7 +209,8 @@ private synchronized void loadAllCertificates() { } } - if (file.getName().startsWith(ROOT_CA_CERT_PREFIX)) { + if (file.getName().startsWith( + CAType.ROOT.getFileNamePrefix())) { String certFileName = FilenameUtils.getBaseName( file.getName()); long tmpRootCaCertSerailId = NumberUtils.toLong( @@ -238,7 +238,7 @@ private synchronized void loadAllCertificates() { rootCaCertId = Long.toString(latestRootCaCertSerialId); } - if (x509Certificate != null) { + if (certPath != null) { if (executorService == null) { startCertificateMonitor(); } @@ -299,15 +299,24 @@ public PublicKey getPublicKey() { return publicKey; } + @Override + public X509Certificate getCertificate() { + CertPath currentCertPath = getCertPath(); + if (currentCertPath == null || currentCertPath.getCertificates() == null) { + return null; + } + return firstCertificateFrom(currentCertPath); + } + /** * Returns the default certificate of given client if it exists. * * @return certificate or Null if there is no data. */ @Override - public synchronized X509Certificate getCertificate() { - if (x509Certificate != null) { - return x509Certificate; + public synchronized CertPath getCertPath() { + if (certPath != null) { + return certPath; } if (certSerialId == null) { @@ -318,17 +327,27 @@ public synchronized X509Certificate getCertificate() { // Refresh the cache from file system. loadAllCertificates(); if (certificateMap.containsKey(certSerialId)) { - x509Certificate = certificateMap.get(certSerialId); + certPath = certificateMap.get(certSerialId); } - return x509Certificate; + return certPath; } /** * Return the latest CA certificate known to the client. + * * @return latest ca certificate known to the client. */ @Override public synchronized X509Certificate getCACertificate() { + CertPath caCertPath = getCACertPath(); + if (caCertPath == null || caCertPath.getCertificates() == null) { + return null; + } + return firstCertificateFrom(caCertPath); + } + + @Override + public synchronized CertPath getCACertPath() { if (caCertId != null) { return certificateMap.get(caCertId); } @@ -338,16 +357,17 @@ public synchronized X509Certificate getCACertificate() { /** * Returns the certificate with the specified certificate serial id if it * exists else try to get it from SCM. - * @param certId * + * @param certId * @return certificate or Null if there is no data. */ @Override public synchronized X509Certificate getCertificate(String certId) throws CertificateException { // Check if it is in cache. - if (certificateMap.containsKey(certId)) { - return certificateMap.get(certId); + if (certificateMap.containsKey(certId) && + certificateMap.get(certId).getCertificates() != null) { + return firstCertificateFrom(certificateMap.get(certId)); } // Try to get it from SCM. return this.getCertificateFromScm(certId); @@ -388,7 +408,7 @@ private X509Certificate getCertificateFromScm(String certId) certId); try { String pemEncodedCert = getScmSecureClient().getCertificate(certId); - this.storeCertificate(pemEncodedCert, true); + this.storeCertificate(pemEncodedCert); return CertificateCodec.getX509Certificate(pemEncodedCert); } catch (Exception e) { getLogger().error("Error while getting Certificate with " + @@ -601,58 +621,55 @@ public X509Certificate queryCertificate(String query) { * Stores the Certificate for this client. Don't use this api to add trusted * certificates of others. * - * @param pemEncodedCert - pem encoded X509 Certificate - * @param force - override any existing file + * @param pemEncodedCert - pem encoded X509 Certificate * @throws CertificateException - on Error. - * */ @Override - public void storeCertificate(String pemEncodedCert, boolean force) + public void storeCertificate(String pemEncodedCert) throws CertificateException { - this.storeCertificate(pemEncodedCert, force, false); + this.storeCertificate(pemEncodedCert, CAType.NONE); } /** * Stores the Certificate for this client. Don't use this api to add trusted * certificates of others. * - * @param pemEncodedCert - pem encoded X509 Certificate - * @param force - override any existing file - * @param caCert - Is CA certificate. + * @param pemEncodedCert - pem encoded X509 Certificate + * @param caType - Is CA certificate. * @throws CertificateException - on Error. - * */ @Override - public void storeCertificate(String pemEncodedCert, boolean force, - boolean caCert) throws CertificateException { + public void storeCertificate(String pemEncodedCert, + CAType caType) throws CertificateException { CertificateCodec certificateCodec = new CertificateCodec(securityConfig, component); - storeCertificate(pemEncodedCert, force, caCert, false, + storeCertificate(pemEncodedCert, caType, certificateCodec, true); } public synchronized void storeCertificate(String pemEncodedCert, - boolean force, boolean isCaCert, boolean isRootCaCert, - CertificateCodec codec, boolean addToCertMap) + CAType caType, CertificateCodec codec, boolean addToCertMap) throws CertificateException { try { - X509Certificate cert = - CertificateCodec.getX509Certificate(pemEncodedCert); + CertPath certificatePath = + CertificateCodec.getCertPathFromPemEncodedString(pemEncodedCert); + X509Certificate cert = firstCertificateFrom(certificatePath); + String certName = String.format(CERT_FILE_NAME_FORMAT, - cert.getSerialNumber().toString()); + caType.getFileNamePrefix() + cert.getSerialNumber().toString()); - if (isCaCert) { - certName = CA_CERT_PREFIX + certName; + if (caType == CAType.SUBORDINATE) { caCertId = cert.getSerialNumber().toString(); - } else if (isRootCaCert) { - certName = ROOT_CA_CERT_PREFIX + certName; + } + if (caType == CAType.ROOT) { rootCaCertId = cert.getSerialNumber().toString(); } - codec.writeCertificate(codec.getLocation(), certName, - pemEncodedCert, force); + codec.writeCertificate(certName, + pemEncodedCert); if (addToCertMap) { - certificateMap.putIfAbsent(cert.getSerialNumber().toString(), cert); + certificateMap.putIfAbsent( + cert.getSerialNumber().toString(), certificatePath); } } catch (IOException | java.security.cert.CertificateException e) { throw new CertificateException("Error while storing certificate.", e, @@ -800,9 +817,13 @@ public synchronized InitResponse init() throws CertificateException { return handleCase(init); } + private X509Certificate firstCertificateFrom(CertPath certificatePath) { + return CertificateCodec.firstCertificateFrom(certificatePath); + } + /** * Default handling of each {@link InitCase}. - * */ + */ protected InitResponse handleCase(InitCase init) throws CertificateException { switch (init) { @@ -986,18 +1007,17 @@ public String getComponentName() { @Override public synchronized X509Certificate getRootCACertificate() { if (rootCaCertId != null) { - return certificateMap.get(rootCaCertId); + return firstCertificateFrom(certificateMap.get(rootCaCertId)); } return null; } @Override - public void storeRootCACertificate(String pemEncodedCert, boolean force) + public void storeRootCACertificate(String pemEncodedCert) throws CertificateException { CertificateCodec certificateCodec = new CertificateCodec(securityConfig, component); - storeCertificate(pemEncodedCert, force, false, true, - certificateCodec, true); + storeCertificate(pemEncodedCert, CAType.ROOT, certificateCodec, true); } @Override @@ -1053,11 +1073,11 @@ private synchronized boolean removeCertificates(List certIds) { String certName = String.format(CERT_FILE_NAME_FORMAT, certId); if (certId.equals(caCertId)) { - certName = CA_CERT_PREFIX + certName; + certName = CAType.SUBORDINATE.getFileNamePrefix() + certName; } if (certId.equals(rootCaCertId)) { - certName = ROOT_CA_CERT_PREFIX + certName; + certName = CAType.ROOT.getFileNamePrefix() + certName; } FileUtils.deleteQuietly(basePath.resolve(certName).toFile()); @@ -1159,7 +1179,8 @@ public String renewAndStoreKeyAndCertificate(boolean force) if (!force) { synchronized (this) { Preconditions.checkArgument( - timeBeforeExpiryGracePeriod(x509Certificate).isZero()); + timeBeforeExpiryGracePeriod(firstCertificateFrom(certPath)) + .isZero()); } } @@ -1339,7 +1360,7 @@ synchronized void reloadKeyAndCertificate(String newCertId) { // reset current value privateKey = null; publicKey = null; - x509Certificate = null; + certPath = null; certSerialId = null; caCertId = null; rootCaCertId = null; @@ -1358,7 +1379,7 @@ public OzoneConfiguration getConfig() { @Override public abstract String signAndStoreCertificate( - PKCS10CertificationRequest request, Path certPath) + PKCS10CertificationRequest request, Path certificatePath) throws CertificateException; public String signAndStoreCertificate(PKCS10CertificationRequest request) @@ -1393,7 +1414,7 @@ public synchronized void startCertificateMonitor() { // Schedule task to refresh certificate before it expires Duration gracePeriod = securityConfig.getRenewalGracePeriod(); long timeBeforeGracePeriod = - timeBeforeExpiryGracePeriod(x509Certificate).toMillis(); + timeBeforeExpiryGracePeriod(firstCertificateFrom(certPath)).toMillis(); // At least three chances to renew the certificate before it expires long interval = Math.min(gracePeriod.toMillis() / 3, TimeUnit.DAYS.toMillis(1)); diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/ReconCertificateClient.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/ReconCertificateClient.java index bf956f33ed8d..d949b75c3876 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/ReconCertificateClient.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/ReconCertificateClient.java @@ -20,6 +20,7 @@ import org.apache.hadoop.hdds.protocol.proto.HddsProtos; import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos; import org.apache.hadoop.hdds.security.x509.SecurityConfig; +import org.apache.hadoop.hdds.security.x509.certificate.authority.CAType; import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateCodec; import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateSignRequest; import org.apache.hadoop.hdds.security.x509.exception.CertificateException; @@ -96,7 +97,7 @@ public CertificateSignRequest.Builder getCSRBuilder(KeyPair keyPair) @Override public String signAndStoreCertificate(PKCS10CertificationRequest csr, - Path certPath) throws CertificateException { + Path certificatePath) throws CertificateException { try { SCMSecurityProtocolProtos.SCMGetCertResponseProto response; HddsProtos.NodeDetailsProto.Builder reconDetailsProtoBuilder = @@ -113,15 +114,18 @@ public String signAndStoreCertificate(PKCS10CertificationRequest csr, if (response.hasX509CACertificate()) { String pemEncodedCert = response.getX509Certificate(); CertificateCodec certCodec = new CertificateCodec( - getSecurityConfig(), certPath); - storeCertificate(pemEncodedCert, true, false, false, certCodec, false); - storeCertificate(response.getX509CACertificate(), true, true, - false, certCodec, false); + getSecurityConfig(), certificatePath); + storeCertificate(pemEncodedCert, CAType.NONE, + certCodec, + false); + storeCertificate(response.getX509CACertificate(), + CAType.SUBORDINATE, + certCodec, false); // Store Root CA certificate. if (response.hasX509RootCACertificate()) { storeCertificate(response.getX509RootCACertificate(), - true, false, true, certCodec, false); + CAType.ROOT, certCodec, false); } return getX509Certificate(pemEncodedCert).getSerialNumber().toString(); } else { diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/SCMCertificateClient.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/SCMCertificateClient.java index cbf8c921f0e0..655bbba3dbd1 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/SCMCertificateClient.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/certificate/client/SCMCertificateClient.java @@ -146,7 +146,7 @@ public Logger getLogger() { @Override public String signAndStoreCertificate(PKCS10CertificationRequest request, - Path certPath) throws CertificateException { + Path certificatePath) throws CertificateException { return null; } diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/crl/CRLCodec.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/crl/CRLCodec.java index e9a52be2bd3b..bde2d49eb252 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/crl/CRLCodec.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/security/x509/crl/CRLCodec.java @@ -21,8 +21,10 @@ import org.apache.commons.io.IOUtils; 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.bouncycastle.cert.X509CRLHolder; import org.bouncycastle.cert.jcajce.JcaX509CRLConverter; +import org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory; import org.bouncycastle.openssl.jcajce.JcaPEMWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,8 +39,6 @@ import java.nio.file.Paths; import java.nio.file.attribute.PosixFilePermission; import java.security.cert.CRLException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; import java.security.cert.X509CRL; import java.util.Set; import java.util.stream.Collectors; @@ -123,14 +123,13 @@ public static String getPEMEncodedString(X509CRL holder) * @param pemEncodedString - PEM encoded String. * @return X509CRL - Crl. * @throws CRLException - Thrown on Failure. - * @throws CertificateException - Thrown on Failure. - * @throws IOException - Thrown on Failure. + * @throws IOException - Thrown on Failure. */ public static X509CRL getX509CRL(String pemEncodedString) - throws CRLException, CertificateException, IOException { - CertificateFactory fact = CertificateFactory.getInstance("X.509"); + throws CRLException, IOException { + CertificateFactory fact = CertificateCodec.getCertFactory(); try (InputStream input = IOUtils.toInputStream(pemEncodedString, UTF_8)) { - return (X509CRL) fact.generateCRL(input); + return (X509CRL) fact.engineGenerateCRL(input); } } diff --git a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/CertificateClientTest.java b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/CertificateClientTest.java index b892c164783a..87ce8b600012 100644 --- a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/CertificateClientTest.java +++ b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/CertificateClientTest.java @@ -22,20 +22,25 @@ import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.cert.CertPath; import java.security.cert.CertStore; import java.security.cert.X509Certificate; import java.util.Collections; import java.util.List; +import com.google.common.collect.ImmutableList; import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.apache.hadoop.hdds.security.ssl.KeyStoresFactory; +import org.apache.hadoop.hdds.security.x509.certificate.authority.CAType; import org.apache.hadoop.hdds.security.x509.certificate.client.CertificateClient; import org.apache.hadoop.hdds.security.x509.certificate.client.CertificateNotification; +import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateCodec; import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateSignRequest; import org.apache.hadoop.hdds.security.x509.crl.CRLInfo; import org.apache.hadoop.hdds.security.x509.exception.CertificateException; import org.apache.hadoop.security.ssl.KeyStoreTestUtil; +import org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory; import org.bouncycastle.pkcs.PKCS10CertificationRequest; /** @@ -45,15 +50,17 @@ public class CertificateClientTest implements CertificateClient { private KeyPair keyPair; - private X509Certificate x509Certificate; + private CertPath certPath; private SecurityConfig secConfig; public CertificateClientTest(OzoneConfiguration conf) throws Exception { secConfig = new SecurityConfig(conf); keyPair = KeyStoreTestUtil.generateKeyPair("RSA"); - x509Certificate = KeyStoreTestUtil.generateCertificate("CN=OzoneMaster", - keyPair, 30, "SHA256withRSA"); + CertificateFactory fact = CertificateCodec.getCertFactory(); + X509Certificate singleCert = KeyStoreTestUtil + .generateCertificate("CN=OzoneMaster", keyPair, 30, "SHA256withRSA"); + certPath = fact.engineGenerateCertPath(ImmutableList.of(singleCert)); } @Override @@ -75,17 +82,27 @@ public PublicKey getPublicKey() { @Override public X509Certificate getCertificate(String certSerialId) throws CertificateException { - return x509Certificate; + return CertificateCodec.firstCertificateFrom(certPath); + } + + @Override + public CertPath getCertPath() { + return certPath; } @Override public X509Certificate getCertificate() { - return x509Certificate; + return CertificateCodec.firstCertificateFrom(certPath); } @Override public X509Certificate getCACertificate() { - return x509Certificate; + return CertificateCodec.firstCertificateFrom(certPath); + } + + @Override + public CertPath getCACertPath() { + return certPath; } @Override @@ -133,7 +150,7 @@ public CertificateSignRequest.Builder getCSRBuilder() { @Override public String signAndStoreCertificate(PKCS10CertificationRequest request, - Path certPath) throws CertificateException { + Path certificatePath) throws CertificateException { return null; } @@ -149,12 +166,12 @@ public X509Certificate queryCertificate(String query) { } @Override - public void storeCertificate(String cert, boolean force) + public void storeCertificate(String cert) throws CertificateException { } @Override - public void storeCertificate(String cert, boolean force, boolean caCert) + public void storeCertificate(String cert, CAType caType) throws CertificateException { } @@ -196,11 +213,11 @@ public String getComponentName() { @Override public X509Certificate getRootCACertificate() { - return x509Certificate; + return CertificateCodec.firstCertificateFrom(certPath); } @Override - public void storeRootCACertificate(String pemEncodedCert, boolean force) { + public void storeRootCACertificate(String pemEncodedCert) { } @@ -264,7 +281,8 @@ public void renewKey() throws Exception { "CN=OzoneMaster", keyPair, 30, "SHA256withRSA"); keyPair = newKeyPair; - x509Certificate = newCert; + CertificateFactory fact = CertificateCodec.getCertFactory(); + certPath = fact.engineGenerateCertPath(ImmutableList.of(newCert)); } @Override diff --git a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/authority/TestDefaultCAServer.java b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/authority/TestDefaultCAServer.java index 80d1751081aa..d2f2441fede6 100644 --- a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/authority/TestDefaultCAServer.java +++ b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/authority/TestDefaultCAServer.java @@ -19,12 +19,14 @@ package org.apache.hadoop.hdds.security.x509.certificate.authority; +import com.google.common.collect.ImmutableList; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.validator.routines.DomainValidator; import org.apache.hadoop.hdds.HddsConfigKeys; 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.authority.profile.DefaultCAProfile; import org.apache.hadoop.hdds.security.x509.certificate.authority.profile.DefaultProfile; import org.apache.hadoop.hdds.security.x509.certificate.client.CertificateClient; import org.apache.hadoop.hdds.security.x509.certificate.client.SCMCertificateClient; @@ -39,7 +41,7 @@ import org.bouncycastle.asn1.x509.CRLReason; import org.bouncycastle.cert.X509CertificateHolder; -import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory; import org.bouncycastle.pkcs.PKCS10CertificationRequest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -52,6 +54,8 @@ import java.security.KeyPair; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; +import java.security.cert.CertPath; +import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.time.LocalDate; @@ -70,8 +74,6 @@ import static org.apache.hadoop.hdds.HddsConfigKeys.OZONE_METADATA_DIRS; import static org.apache.hadoop.hdds.protocol.proto.HddsProtos.NodeType.OM; import static org.apache.hadoop.hdds.protocol.proto.HddsProtos.NodeType.SCM; -import static org.apache.hadoop.hdds.security.x509.certificate.authority.CertificateServer.CAType.INTERMEDIARY_CA; -import static org.apache.hadoop.hdds.security.x509.certificate.authority.CertificateServer.CAType.SELF_SIGNED_CA; import static org.apache.hadoop.hdds.security.x509.exception.CertificateException.ErrorCode.CSR_ERROR; import static org.apache.hadoop.ozone.OzoneConsts.SCM_CA_CERT_STORAGE_DIR; import static org.apache.hadoop.ozone.OzoneConsts.SCM_CA_PATH; @@ -104,11 +106,11 @@ public void testInit() throws SCMSecurityException, CertificateException, RandomStringUtils.randomAlphabetic(4), caStore, new DefaultProfile(), Paths.get(SCM_CA_CERT_STORAGE_DIR, SCM_CA_PATH).toString()); - testCA.init(securityConfig, SELF_SIGNED_CA); + testCA.init(securityConfig, CAType.ROOT); X509CertificateHolder first = testCA.getCACertificate(); assertNotNull(first); //Init is idempotent. - testCA.init(securityConfig, SELF_SIGNED_CA); + testCA.init(securityConfig, CAType.ROOT); X509CertificateHolder second = testCA.getCACertificate(); assertEquals(first, second); } @@ -123,7 +125,8 @@ public void testMissingCertificate() { Paths.get(SCM_CA_CERT_STORAGE_DIR, SCM_CA_PATH).toString()); Consumer caInitializer = ((DefaultCAServer) testCA).processVerificationStatus( - DefaultCAServer.VerificationStatus.MISSING_CERTIFICATE, SELF_SIGNED_CA); + DefaultCAServer.VerificationStatus.MISSING_CERTIFICATE, + CAType.ROOT); try { caInitializer.accept(securityConfig); @@ -145,7 +148,7 @@ public void testMissingKey() { Paths.get(SCM_CA_CERT_STORAGE_DIR, SCM_CA_PATH).toString()); Consumer caInitializer = ((DefaultCAServer) testCA).processVerificationStatus( - DefaultCAServer.VerificationStatus.MISSING_KEYS, SELF_SIGNED_CA); + DefaultCAServer.VerificationStatus.MISSING_KEYS, CAType.ROOT); try { caInitializer.accept(securityConfig); @@ -170,9 +173,9 @@ public void testMissingKey() { @Test public void testRequestCertificate() throws IOException, ExecutionException, InterruptedException, - NoSuchProviderException, NoSuchAlgorithmException { - String scmId = RandomStringUtils.randomAlphabetic(4); - String clusterId = RandomStringUtils.randomAlphabetic(4); + NoSuchProviderException, NoSuchAlgorithmException, CertificateException { + String scmId = RandomStringUtils.randomAlphabetic(4); + String clusterId = RandomStringUtils.randomAlphabetic(4); KeyPair keyPair = new HDDSKeyGenerator(conf).generateKey(); PKCS10CertificationRequest csr = new CertificateSignRequest.Builder() @@ -195,13 +198,24 @@ public void testRequestCertificate() throws IOException, new DefaultProfile(), Paths.get(SCM_CA_CERT_STORAGE_DIR, SCM_CA_PATH).toString()); testCA.init(new SecurityConfig(conf), - SELF_SIGNED_CA); + CAType.ROOT); - Future holder = testCA.requestCertificate(csrString, - CertificateApprover.ApprovalType.TESTING_AUTOMATIC, SCM); + Future holder = testCA.requestCertificate( + csrString, CertificateApprover.ApprovalType.TESTING_AUTOMATIC, SCM); // Right now our calls are synchronous. Eventually this will have to wait. assertTrue(holder.isDone()); - assertNotNull(holder.get()); + //Test that the cert path returned contains the CA certificate in proper + // place + List certBundle = holder.get().getCertificates(); + X509Certificate caInReturnedBundle = (X509Certificate) certBundle.get(1); + assertEquals(caInReturnedBundle, + CertificateCodec.getX509Certificate(testCA.getCACertificate())); + X509Certificate signedCert = + CertificateCodec.firstCertificateFrom(holder.get()); + //Test that the ca has signed of the returned certificate + assertEquals(caInReturnedBundle.getSubjectX500Principal(), + signedCert.getIssuerX500Principal()); + } /** @@ -239,13 +253,13 @@ public void testRequestCertificateWithInvalidSubject() throws IOException, new DefaultProfile(), Paths.get(SCM_CA_CERT_STORAGE_DIR, SCM_CA_PATH).toString()); testCA.init(new SecurityConfig(conf), - SELF_SIGNED_CA); + CAType.ROOT); - Future holder = testCA.requestCertificate(csrString, - CertificateApprover.ApprovalType.TESTING_AUTOMATIC, OM); + Future holder = testCA.requestCertificate( + csrString, CertificateApprover.ApprovalType.TESTING_AUTOMATIC, OM); // Right now our calls are synchronous. Eventually this will have to wait. assertTrue(holder.isDone()); - assertNotNull(holder.get()); + assertNotNull(CertificateCodec.firstCertificateFrom(holder.get())); } @Test @@ -259,7 +273,7 @@ public void testRevokeCertificates() throws Exception { new DefaultProfile(), Paths.get(SCM_CA_CERT_STORAGE_DIR, SCM_CA_PATH).toString()); testCA.init(new SecurityConfig(conf), - SELF_SIGNED_CA); + CAType.ROOT); KeyPair keyPair = new HDDSKeyGenerator(conf).generateKey(); @@ -275,11 +289,11 @@ public void testRevokeCertificates() throws Exception { // Let us convert this to a string to mimic the common use case. String csrString = CertificateSignRequest.getEncodedString(csr); - Future holder = testCA.requestCertificate(csrString, - CertificateApprover.ApprovalType.TESTING_AUTOMATIC, OM); + Future holder = testCA.requestCertificate( + csrString, CertificateApprover.ApprovalType.TESTING_AUTOMATIC, OM); X509Certificate certificate = - new JcaX509CertificateConverter().getCertificate(holder.get()); + CertificateCodec.firstCertificateFrom(holder.get()); List serialIDs = new ArrayList<>(); serialIDs.add(certificate.getSerialNumber()); Future> revoked = testCA.revokeCertificates(serialIDs, @@ -324,12 +338,12 @@ public void testRequestCertificateWithInvalidSubjectFailure() new DefaultProfile(), Paths.get(SCM_CA_CERT_STORAGE_DIR, SCM_CA_PATH).toString()); testCA.init(new SecurityConfig(conf), - SELF_SIGNED_CA); + CAType.ROOT); LambdaTestUtils.intercept(ExecutionException.class, "ScmId and " + "ClusterId in CSR subject are incorrect", () -> { - Future holder = + Future holder = testCA.requestCertificate(csrString, CertificateApprover.ApprovalType.TESTING_AUTOMATIC, OM); holder.get(); @@ -345,7 +359,7 @@ public void testIntermediaryCAWithEmpty() { new DefaultProfile(), Paths.get("scm").toString()); assertThrows(IllegalStateException.class, - () -> scmCA.init(new SecurityConfig(conf), INTERMEDIARY_CA)); + () -> scmCA.init(new SecurityConfig(conf), CAType.SUBORDINATE)); } @Test @@ -369,7 +383,7 @@ public void testExternalRootCA(@TempDir Path tempDir) throws Exception { scmCertificateClient.getComponentName()); certificateCodec.writeCertificate(tempDir, externalCaCertFileName, - CertificateCodec.getPEMEncodedString(externalCert), true); + CertificateCodec.getPEMEncodedString(externalCert)); CertificateServer testCA = new DefaultCAServer("testCA", RandomStringUtils.randomAlphabetic(4), @@ -377,13 +391,13 @@ public void testExternalRootCA(@TempDir Path tempDir) throws Exception { new DefaultProfile(), Paths.get(SCM_CA_CERT_STORAGE_DIR, SCM_CA_PATH).toString()); //When initializing a CA server with external cert - testCA.init(securityConfig, SELF_SIGNED_CA); + testCA.init(securityConfig, CAType.ROOT); //Then the external cert is set as CA cert for the server. assertEquals(externalCert, testCA.getCACertificate()); } private void setExternalPathsInConfig(Path tempDir, - String externalCaCertFileName) { + String externalCaCertFileName) { String externalCaCertPart = Paths.get(tempDir.toString(), externalCaCertFileName).toString(); String privateKeyPath = Paths.get(tempDir.toString(), @@ -399,6 +413,62 @@ private void setExternalPathsInConfig(Path tempDir, publicKeyPath); } + @Test + public void testInitWithCertChain(@TempDir Path tempDir) throws Exception { + String externalCaCertFileName = "CaCert.pem"; + setExternalPathsInConfig(tempDir, externalCaCertFileName); + SecurityConfig securityConfig = new SecurityConfig(conf); + CertificateApprover approver = new DefaultApprover(new DefaultCAProfile(), + securityConfig); + SCMCertificateClient scmCertificateClient = + new SCMCertificateClient(new SecurityConfig(conf)); + String scmId = RandomStringUtils.randomAlphabetic(4); + String clusterId = RandomStringUtils.randomAlphabetic(4); + KeyPair keyPair = new HDDSKeyGenerator(conf).generateKey(); + KeyCodec keyPEMWriter = new KeyCodec(securityConfig, + scmCertificateClient.getComponentName()); + + keyPEMWriter.writeKey(tempDir, keyPair, true); + LocalDate beginDate = LocalDate.now().atStartOfDay().toLocalDate(); + LocalDate endDate = + LocalDate.from(LocalDate.now().atStartOfDay().plusDays(10)); + PKCS10CertificationRequest csr = new CertificateSignRequest.Builder() + .addDnsName("hadoop.apache.org") + .addIpAddress("8.8.8.8") + .addServiceName("OzoneMarketingCluster002") + .setCA(false) + .setClusterID(clusterId) + .setScmID(scmId) + .setSubject("Ozone Cluster") + .setConfiguration(conf) + .setKey(keyPair) + .build(); + X509CertificateHolder externalCert = generateExternalCert(keyPair); + X509CertificateHolder signedCert = approver.sign(securityConfig, + keyPair.getPrivate(), externalCert, + java.sql.Date.valueOf(beginDate), java.sql.Date.valueOf(endDate), csr, + scmId, clusterId); + CertificateFactory certFactory = new CertificateFactory(); + CertificateCodec certificateCodec = new CertificateCodec(securityConfig, + scmCertificateClient.getComponentName()); + + CertPath certPath = certFactory.engineGenerateCertPath( + ImmutableList.of(CertificateCodec.getX509Certificate(signedCert), + CertificateCodec.getX509Certificate(externalCert))); + certificateCodec.writeCertificate(tempDir, externalCaCertFileName, + CertificateCodec.getPEMEncodedString(certPath)); + + CertificateServer testCA = new DefaultCAServer("testCA", + RandomStringUtils.randomAlphabetic(4), + RandomStringUtils.randomAlphabetic(4), caStore, + new DefaultProfile(), + Paths.get(SCM_CA_CERT_STORAGE_DIR, SCM_CA_PATH).toString()); + //When initializing a CA server with external cert + testCA.init(securityConfig, CAType.ROOT); + //Then the external cert is set as CA cert for the server. + assertEquals(signedCert, testCA.getCACertificate()); + } + @Test public void testIntermediaryCA() throws Exception { @@ -411,7 +481,7 @@ public void testIntermediaryCA() throws Exception { clusterId, scmId, caStore, new DefaultProfile(), Paths.get("scm", "ca").toString()); - rootCA.init(new SecurityConfig(conf), SELF_SIGNED_CA); + rootCA.init(new SecurityConfig(conf), CAType.ROOT); SCMCertificateClient scmCertificateClient = @@ -432,12 +502,13 @@ clusterId, scmId, caStore, new DefaultProfile(), .setKey(keyPair) .build(); - Future holder = rootCA.requestCertificate(csr, + Future holder = rootCA.requestCertificate(csr, CertificateApprover.ApprovalType.TESTING_AUTOMATIC, SCM); assertTrue(holder.isDone()); - - X509CertificateHolder certificateHolder = holder.get(); - + X509Certificate certificate = + CertificateCodec.firstCertificateFrom(holder.get()); + X509CertificateHolder certificateHolder = + CertificateCodec.getCertificateHolder(certificate); assertNotNull(certificateHolder); LocalDate invalidAfterDate = certificateHolder.getNotAfter().toInstant() @@ -449,11 +520,12 @@ clusterId, scmId, caStore, new DefaultProfile(), X509CertificateHolder rootCertHolder = rootCA.getCACertificate(); scmCertificateClient.storeCertificate( - CertificateCodec.getPEMEncodedString(rootCertHolder), true, true); + CertificateCodec.getPEMEncodedString(rootCertHolder), + CAType.SUBORDINATE); // Write to the location where Default CA Server reads from. scmCertificateClient.storeCertificate( - CertificateCodec.getPEMEncodedString(certificateHolder), true); + CertificateCodec.getPEMEncodedString(certificateHolder)); CertificateCodec certCodec = new CertificateCodec(new SecurityConfig(conf), @@ -467,7 +539,7 @@ clusterId, scmId, caStore, new DefaultProfile(), scmCertificateClient.getComponentName()); try { - scmCA.init(new SecurityConfig(conf), INTERMEDIARY_CA); + scmCA.init(new SecurityConfig(conf), CAType.SUBORDINATE); } catch (Exception e) { fail("testIntermediaryCA failed during init"); } diff --git a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/client/TestDefaultCertificateClient.java b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/client/TestDefaultCertificateClient.java index 5de47ca95ef9..109a27e56334 100644 --- a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/client/TestDefaultCertificateClient.java +++ b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/client/TestDefaultCertificateClient.java @@ -22,6 +22,7 @@ import org.apache.hadoop.hdds.protocol.MockDatanodeDetails; import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos; import org.apache.hadoop.hdds.protocolPB.SCMSecurityProtocolClientSideTranslatorPB; +import org.apache.hadoop.hdds.security.x509.certificate.authority.CAType; import org.apache.hadoop.hdds.security.x509.certificate.client.CertificateClient.InitResponse; import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateCodec; import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateSignRequest; @@ -175,7 +176,7 @@ public void testCertificateOps() throws Exception { X509Certificate cert = dnCertClient.getCertificate(); assertNull(cert); dnCertClient.storeCertificate(getPEMEncodedString(x509Certificate), - true); + CAType.SUBORDINATE); cert = dnCertClient.getCertificate( x509Certificate.getSerialNumber().toString()); @@ -307,11 +308,11 @@ public void testCertificateLoadingOnInit() throws Exception { () -> dnCertClient.getCertificate(cert3.getSerialNumber() .toString())); codec.writeCertificate(certPath, "1.crt", - getPEMEncodedString(cert1), true); + getPEMEncodedString(cert1)); codec.writeCertificate(certPath, "2.crt", - getPEMEncodedString(cert2), true); + getPEMEncodedString(cert2)); codec.writeCertificate(certPath, "3.crt", - getPEMEncodedString(cert3), true); + getPEMEncodedString(cert3)); // Re instantiate DN client which will load certificates from filesystem. dnCertClient = new DNCertificateClient(dnSecurityConfig, null, @@ -333,9 +334,9 @@ public void testStoreCertificate() throws Exception { X509Certificate cert2 = generateX509Cert(keyPair); X509Certificate cert3 = generateX509Cert(keyPair); - dnCertClient.storeCertificate(getPEMEncodedString(cert1), true); - dnCertClient.storeCertificate(getPEMEncodedString(cert2), true); - dnCertClient.storeCertificate(getPEMEncodedString(cert3), true); + dnCertClient.storeCertificate(getPEMEncodedString(cert1)); + dnCertClient.storeCertificate(getPEMEncodedString(cert2)); + dnCertClient.storeCertificate(getPEMEncodedString(cert3)); assertNotNull(dnCertClient.getCertificate(cert1.getSerialNumber() .toString())); @@ -450,7 +451,7 @@ public X509Certificate getCertificate() { @Override public String signAndStoreCertificate( - PKCS10CertificationRequest request, Path certPath) + PKCS10CertificationRequest request, Path certificatePath) throws CertificateException { return null; } @@ -473,16 +474,18 @@ public void testTimeBeforeExpiryGracePeriod() throws Exception { Duration gracePeriod = dnSecurityConfig.getRenewalGracePeriod(); X509Certificate cert = KeyStoreTestUtil.generateCertificate("CN=Test", - keyPair, (int)(gracePeriod.toDays()), + keyPair, (int) (gracePeriod.toDays()), dnSecurityConfig.getSignatureAlgo()); - dnCertClient.storeCertificate(getPEMEncodedString(cert), true); + dnCertClient.storeCertificate( + getPEMEncodedString(cert), CAType.SUBORDINATE); Duration duration = dnCertClient.timeBeforeExpiryGracePeriod(cert); Assert.assertTrue(duration.isZero()); cert = KeyStoreTestUtil.generateCertificate("CN=Test", - keyPair, (int)(gracePeriod.toDays() + 1), + keyPair, (int) (gracePeriod.toDays() + 1), dnSecurityConfig.getSignatureAlgo()); - dnCertClient.storeCertificate(getPEMEncodedString(cert), true); + dnCertClient.storeCertificate( + getPEMEncodedString(cert), CAType.SUBORDINATE); duration = dnCertClient.timeBeforeExpiryGracePeriod(cert); Assert.assertTrue(duration.toMillis() < Duration.ofDays(1).toMillis() && duration.toMillis() > Duration.ofHours(23).plusMinutes(59).toMillis()); @@ -551,7 +554,8 @@ public void testRenewAndStoreKeyAndCertificate() throws Exception { "CN=OzoneMaster", keyPair, 30, "SHA256withRSA"); certCodec = new CertificateCodec(dnSecurityConfig, newCertDir.toPath()); - dnCertClient.storeCertificate(getPEMEncodedString(cert), true, false, false, + dnCertClient.storeCertificate(getPEMEncodedString(cert), + CAType.NONE, certCodec, false); // a success renew after auto cleanup new key and cert dir dnCertClient.renewAndStoreKeyAndCertificate(true); diff --git a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestCRLCodec.java b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestCRLCodec.java index 2b020b4b0430..bc22252584c7 100644 --- a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestCRLCodec.java +++ b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestCRLCodec.java @@ -41,7 +41,6 @@ import java.security.PrivateKey; import java.security.cert.CRLException; import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; import java.security.cert.X509CRL; import java.time.LocalDateTime; import java.util.Date; @@ -57,6 +56,7 @@ import org.bouncycastle.cert.X509CRLHolder; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.X509v2CRLBuilder; +import org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.junit.jupiter.api.BeforeEach; @@ -159,8 +159,8 @@ public void testWriteCRLX509() throws IOException, byte[] crlBytes = TMP_CRL_ENTRY.getBytes(UTF_8); try (InputStream inStream = new ByteArrayInputStream(crlBytes)) { - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - X509CRL crl = (X509CRL)cf.generateCRL(inStream); + CertificateFactory cf = CertificateCodec.getCertFactory(); + X509CRL crl = (X509CRL) cf.engineGenerateCRL(inStream); CRLCodec crlCodec = new CRLCodec(securityConfig); crlCodec.writeCRL(crl); @@ -168,7 +168,7 @@ public void testWriteCRLX509() throws IOException, // verify file generated or not File crlFile = Paths.get(crlCodec.getLocation().toString(), - this.securityConfig.getCrlName()).toFile(); + this.securityConfig.getCrlName()).toFile(); assertTrue(crlFile.exists()); } @@ -271,7 +271,7 @@ private void writeTempCert() throws NoSuchProviderException, assertTrue(basePath.mkdirs()); } codec.writeCertificate(basePath.toPath(), TMP_CERT_FILE_NAME, - pemString, false); + pemString); } private X509CertificateHolder readTempCert() @@ -281,7 +281,7 @@ private X509CertificateHolder readTempCert() new CertificateCodec(securityConfig, COMPONENT); X509CertificateHolder x509CertHolder = - codec.readCertificate(basePath.toPath(), TMP_CERT_FILE_NAME); + codec.getTargetCertHolder(basePath.toPath(), TMP_CERT_FILE_NAME); assertNotNull(x509CertHolder); diff --git a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestCertificateCodec.java b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestCertificateCodec.java index 6284dd219e3f..6b7f032f8cce 100644 --- a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestCertificateCodec.java +++ b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestCertificateCodec.java @@ -19,12 +19,14 @@ package org.apache.hadoop.hdds.security.x509.certificate.utils; +import com.google.common.collect.ImmutableList; import org.apache.commons.lang3.RandomStringUtils; 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.keys.HDDSKeyGenerator; import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -33,12 +35,15 @@ import java.nio.file.Path; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; +import java.security.cert.CertPath; +import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.time.LocalDateTime; import static org.apache.hadoop.hdds.HddsConfigKeys.OZONE_METADATA_DIRS; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -46,12 +51,13 @@ * Tests the Certificate codecs. */ public class TestCertificateCodec { - private static OzoneConfiguration conf = new OzoneConfiguration(); + private OzoneConfiguration conf; private static final String COMPONENT = "test"; private SecurityConfig securityConfig; @BeforeEach public void init(@TempDir Path tempDir) { + conf = new OzoneConfiguration(); conf.set(OZONE_METADATA_DIRS, tempDir.toString()); securityConfig = new SecurityConfig(conf); } @@ -72,30 +78,15 @@ public void init(@TempDir Path tempDir) { public void testGetPEMEncodedString() throws NoSuchProviderException, NoSuchAlgorithmException, IOException, SCMSecurityException, CertificateException { - HDDSKeyGenerator keyGenerator = - new HDDSKeyGenerator(conf); - LocalDateTime startDate = LocalDateTime.now(); - LocalDateTime endDate = startDate.plusDays(1); X509CertificateHolder cert = - SelfSignedCertificate.newBuilder() - .setSubject(RandomStringUtils.randomAlphabetic(4)) - .setClusterID(RandomStringUtils.randomAlphabetic(4)) - .setScmID(RandomStringUtils.randomAlphabetic(4)) - .setBeginDate(startDate) - .setEndDate(endDate) - .setConfiguration(keyGenerator.getSecurityConfig() - .getConfiguration()) - .setKey(keyGenerator.generateKey()) - .makeCA() - .build(); - CertificateCodec codec = new CertificateCodec(securityConfig, COMPONENT); - String pemString = codec.getPEMEncodedString(cert); + generateTestCert(); + String pemString = CertificateCodec.getPEMEncodedString(cert); assertTrue(pemString.startsWith(CertificateCodec.BEGIN_CERT)); assertTrue(pemString.endsWith(CertificateCodec.END_CERT + "\n")); // Read back the certificate and verify that all the comparisons pass. - X509CertificateHolder newCert = - codec.getCertificateHolder(codec.getX509Certificate(pemString)); + X509CertificateHolder newCert = CertificateCodec.getCertificateHolder( + CertificateCodec.getX509Certificate(pemString)); assertEquals(cert, newCert); // Just make sure we can decode both these classes to Java Std. lIb classes. @@ -104,6 +95,50 @@ public void testGetPEMEncodedString() assertEquals(firstCert, secondCert); } + /** + * Test when converting a certificate path to pem encoded string and back + * we get back the same certificates. + */ + @Test + public void testGetPemEncodedStringFromCertPath() throws IOException, + NoSuchAlgorithmException, NoSuchProviderException, CertificateException { + X509CertificateHolder certHolder1 = generateTestCert(); + X509CertificateHolder certHolder2 = generateTestCert(); + X509Certificate cert1 = CertificateCodec.getX509Certificate(certHolder1); + X509Certificate cert2 = CertificateCodec.getX509Certificate(certHolder2); + CertificateFactory certificateFactory = new CertificateFactory(); + CertPath pathToEncode = + certificateFactory.engineGenerateCertPath(ImmutableList.of(cert1, + cert2)); + String encodedPath = CertificateCodec.getPEMEncodedString(pathToEncode); + CertPath certPathDecoded = + CertificateCodec.getCertPathFromPemEncodedString(encodedPath); + assertEquals(cert1, certPathDecoded.getCertificates().get(0)); + assertEquals(cert2, certPathDecoded.getCertificates().get(1)); + } + + /** + * Test prepending a new certificate to a cert path. + */ + @Test + public void testPrependCertificateToCertPath() throws IOException, + NoSuchAlgorithmException, NoSuchProviderException, CertificateException { + CertificateCodec codec = new CertificateCodec(securityConfig, COMPONENT); + X509CertificateHolder initialHolder = generateTestCert(); + X509CertificateHolder prependedHolder = generateTestCert(); + X509Certificate initialCert = + CertificateCodec.getX509Certificate(initialHolder); + X509Certificate prependedCert = + CertificateCodec.getX509Certificate(prependedHolder); + codec.writeCertificate(initialHolder); + CertPath initialPath = codec.getCertPath(); + CertPath pathWithPrependedCert = + codec.prependCertToCertPath(prependedHolder, initialPath); + + assertEquals(prependedCert, pathWithPrependedCert.getCertificates().get(0)); + assertEquals(initialCert, pathWithPrependedCert.getCertificates().get(1)); + } + /** * tests writing and reading certificates in PEM encoded form. * @@ -117,28 +152,14 @@ public void testGetPEMEncodedString() public void testWriteCertificate(@TempDir Path basePath) throws NoSuchProviderException, NoSuchAlgorithmException, IOException, SCMSecurityException, CertificateException { - HDDSKeyGenerator keyGenerator = - new HDDSKeyGenerator(conf); - LocalDateTime startDate = LocalDateTime.now(); - LocalDateTime endDate = startDate.plusDays(1); X509CertificateHolder cert = - SelfSignedCertificate.newBuilder() - .setSubject(RandomStringUtils.randomAlphabetic(4)) - .setClusterID(RandomStringUtils.randomAlphabetic(4)) - .setScmID(RandomStringUtils.randomAlphabetic(4)) - .setBeginDate(startDate) - .setEndDate(endDate) - .setConfiguration(keyGenerator.getSecurityConfig() - .getConfiguration()) - .setKey(keyGenerator.generateKey()) - .makeCA() - .build(); + generateTestCert(); CertificateCodec codec = new CertificateCodec(securityConfig, COMPONENT); - String pemString = codec.getPEMEncodedString(cert); + String pemString = CertificateCodec.getPEMEncodedString(cert); codec.writeCertificate(basePath, "pemcertificate.crt", - pemString, false); + pemString); X509CertificateHolder certHolder = - codec.readCertificate(basePath, "pemcertificate.crt"); + codec.getTargetCertHolder(basePath, "pemcertificate.crt"); assertNotNull(certHolder); assertEquals(cert.getSerialNumber(), certHolder.getSerialNumber()); @@ -154,28 +175,13 @@ public void testWriteCertificate(@TempDir Path basePath) * @throws NoSuchAlgorithmException - on Error. */ @Test - public void testwriteCertificateDefault() + public void testWriteCertificateDefault() throws IOException, SCMSecurityException, CertificateException, NoSuchProviderException, NoSuchAlgorithmException { - HDDSKeyGenerator keyGenerator = - new HDDSKeyGenerator(conf); - LocalDateTime startDate = LocalDateTime.now(); - LocalDateTime endDate = startDate.plusDays(1); - X509CertificateHolder cert = - SelfSignedCertificate.newBuilder() - .setSubject(RandomStringUtils.randomAlphabetic(4)) - .setClusterID(RandomStringUtils.randomAlphabetic(4)) - .setScmID(RandomStringUtils.randomAlphabetic(4)) - .setBeginDate(startDate) - .setEndDate(endDate) - .setConfiguration(keyGenerator.getSecurityConfig() - .getConfiguration()) - .setKey(keyGenerator.generateKey()) - .makeCA() - .build(); + X509CertificateHolder cert = generateTestCert(); CertificateCodec codec = new CertificateCodec(securityConfig, COMPONENT); codec.writeCertificate(cert); - X509CertificateHolder certHolder = codec.readCertificate(); + X509CertificateHolder certHolder = codec.getTargetCertHolder(); assertNotNull(certHolder); assertEquals(cert.getSerialNumber(), certHolder.getSerialNumber()); } @@ -192,30 +198,82 @@ public void testwriteCertificateDefault() @Test public void writeCertificate2() throws IOException, SCMSecurityException, NoSuchProviderException, NoSuchAlgorithmException, CertificateException { - HDDSKeyGenerator keyGenerator = - new HDDSKeyGenerator(conf); - LocalDateTime startDate = LocalDateTime.now(); - LocalDateTime endDate = startDate.plusDays(1); - X509CertificateHolder cert = - SelfSignedCertificate.newBuilder() - .setSubject(RandomStringUtils.randomAlphabetic(4)) - .setClusterID(RandomStringUtils.randomAlphabetic(4)) - .setScmID(RandomStringUtils.randomAlphabetic(4)) - .setBeginDate(startDate) - .setEndDate(endDate) - .setConfiguration(keyGenerator.getSecurityConfig() - .getConfiguration()) - .setKey(keyGenerator.generateKey()) - .makeCA() - .build(); + X509CertificateHolder cert = generateTestCert(); CertificateCodec codec = - new CertificateCodec(keyGenerator.getSecurityConfig(), "ca"); - codec.writeCertificate(cert, "newcert.crt", false); + new CertificateCodec(securityConfig, "ca"); + codec.writeCertificate(cert, "newcert.crt"); // Rewrite with force support - codec.writeCertificate(cert, "newcert.crt", true); + codec.writeCertificate(cert, "newcert.crt"); X509CertificateHolder x509CertificateHolder = - codec.readCertificate(codec.getLocation(), "newcert.crt"); + codec.getTargetCertHolder(codec.getLocation(), "newcert.crt"); assertNotNull(x509CertificateHolder); + } + + /** + * Tests writing a certificate path to file and reading back the certificates. + * + * @throws IOException + * @throws NoSuchAlgorithmException + * @throws NoSuchProviderException + * @throws CertificateException + */ + @Test + public void testMultipleCertReadWrite() throws IOException, + NoSuchAlgorithmException, NoSuchProviderException, CertificateException { + //Given a certificate path of one certificate and another certificate + X509CertificateHolder certToPrepend = + generateTestCert(); + + X509CertificateHolder initialCert = + generateTestCert(); + assertNotEquals(certToPrepend, initialCert); + + CertificateFactory certificateFactory = new CertificateFactory(); + CertPath certPath = + certificateFactory.engineGenerateCertPath( + ImmutableList.of(CertificateCodec.getX509Certificate(initialCert))); + //When prepending the second one before the first one and reading them back + CertificateCodec codec = + new CertificateCodec(securityConfig, "ca"); + + CertPath updatedCertPath = + codec.prependCertToCertPath(certToPrepend, certPath); + + String certFileName = "newcert.crt"; + String pemEncodedStrings = + CertificateCodec.getPEMEncodedString(updatedCertPath); + codec.writeCertificate(certFileName, pemEncodedStrings); + + CertPath rereadCertPath = + codec.getCertPath(certFileName); + + //Then the two certificates are the same as before + Certificate rereadPrependedCert = rereadCertPath.getCertificates().get(0); + Certificate rereadSecondCert = rereadCertPath.getCertificates().get(1); + assertEquals(CertificateCodec.getCertificateHolder( + (X509Certificate) rereadPrependedCert), certToPrepend); + assertEquals(CertificateCodec.getCertificateHolder( + (X509Certificate) rereadSecondCert), initialCert); + } + + private X509CertificateHolder generateTestCert() + throws IOException, NoSuchProviderException, NoSuchAlgorithmException { + HDDSKeyGenerator keyGenerator = + new HDDSKeyGenerator(conf); + LocalDateTime startDate = LocalDateTime.now(); + LocalDateTime endDate = startDate.plusDays(1); + return SelfSignedCertificate.newBuilder() + .setSubject(RandomStringUtils.randomAlphabetic(4)) + .setClusterID(RandomStringUtils.randomAlphabetic(4)) + .setScmID(RandomStringUtils.randomAlphabetic(4)) + .setBeginDate(startDate) + .setEndDate(endDate) + .setConfiguration(keyGenerator.getSecurityConfig() + .getConfiguration()) + .setKey(keyGenerator.generateKey()) + .makeCA() + .build(); } + } \ No newline at end of file diff --git a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCertificate.java b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCertificate.java index 73a4077a84d2..7d98e2f2c0eb 100644 --- a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCertificate.java +++ b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/security/x509/certificate/utils/TestRootCertificate.java @@ -185,13 +185,13 @@ public void testCACert(@TempDir Path basePath) certificateHolder.getSerialNumber()); CertificateCodec codec = new CertificateCodec(securityConfig, "scm"); - String pemString = codec.getPEMEncodedString(certificateHolder); + String pemString = CertificateCodec.getPEMEncodedString(certificateHolder); codec.writeCertificate(basePath, "pemcertificate.crt", - pemString, false); + pemString); X509CertificateHolder loadedCert = - codec.readCertificate(basePath, "pemcertificate.crt"); + codec.getTargetCertHolder(basePath, "pemcertificate.crt"); Assertions.assertNotNull(loadedCert); Assertions.assertEquals(certificateHolder.getSerialNumber(), loadedCert.getSerialNumber()); diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/HASecurityUtils.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/HASecurityUtils.java index 5397673594a7..caa48b27edb8 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/HASecurityUtils.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/HASecurityUtils.java @@ -23,6 +23,7 @@ import org.apache.hadoop.hdds.ratis.RatisHelper; import org.apache.hadoop.hdds.scm.server.SCMStorageConfig; import org.apache.hadoop.hdds.security.x509.SecurityConfig; +import org.apache.hadoop.hdds.security.x509.certificate.authority.CAType; import org.apache.hadoop.hdds.security.x509.certificate.authority.CertificateServer; import org.apache.hadoop.hdds.security.x509.certificate.authority.CertificateStore; import org.apache.hadoop.hdds.security.x509.certificate.authority.DefaultCAServer; @@ -52,6 +53,7 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.security.KeyPair; +import java.security.cert.CertPath; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.concurrent.CompletableFuture; @@ -153,15 +155,15 @@ private static void getRootCASignedSCMCert(CertificateClient client, // Store SCM sub CA and root CA certificate. if (response.hasX509CACertificate()) { String pemEncodedRootCert = response.getX509CACertificate(); - client.storeCertificate(pemEncodedRootCert, true, true); - client.storeCertificate(pemEncodedCert, true); + client.storeCertificate( + pemEncodedRootCert, CAType.SUBORDINATE); + client.storeCertificate(pemEncodedCert); + //note: this does exactly the same as store certificate + persistSubCACertificate(config, client, + pemEncodedCert); X509Certificate certificate = CertificateCodec.getX509Certificate(pemEncodedCert); - - persistSubCACertificate(config, client, - CertificateCodec.getCertificateHolder(certificate)); - // Persist scm cert serial ID. scmStorageConfig.setScmCertSerialId(certificate.getSerialNumber() .toString()); @@ -192,24 +194,27 @@ private static void getPrimarySCMSelfSignedCert(CertificateClient client, PKCS10CertificationRequest csr = generateCSR(client, scmStorageConfig, config, scmAddress); - X509CertificateHolder subSCMCertHolder = rootCAServer. + CertPath subSCMCertHolderList = rootCAServer. requestCertificate(csr, KERBEROS_TRUSTED, SCM).get(); - X509CertificateHolder rootCACertificateHolder = - rootCAServer.getCACertificate(); + CertPath rootCACertificatePath = + rootCAServer.getCaCertPath(); String pemEncodedCert = - CertificateCodec.getPEMEncodedString(subSCMCertHolder); + CertificateCodec.getPEMEncodedString(subSCMCertHolderList); String pemEncodedRootCert = - CertificateCodec.getPEMEncodedString(rootCACertificateHolder); - - - client.storeCertificate(pemEncodedRootCert, true, true); - client.storeCertificate(pemEncodedCert, true); - - - persistSubCACertificate(config, client, subSCMCertHolder); + CertificateCodec.getPEMEncodedString(rootCACertificatePath); + + client.storeCertificate( + pemEncodedRootCert, CAType.SUBORDINATE); + client.storeCertificate(pemEncodedCert); + //note: this does exactly the same as store certificate + persistSubCACertificate(config, client, pemEncodedCert); + X509Certificate cert = + (X509Certificate) subSCMCertHolderList.getCertificates().get(0); + X509CertificateHolder subSCMCertHolder = + CertificateCodec.getCertificateHolder(cert); // Persist scm cert serial ID. scmStorageConfig.setScmCertSerialId(subSCMCertHolder.getSerialNumber() @@ -244,8 +249,7 @@ public static CertificateServer initializeRootCertificateServer( scmStorageConfig.getScmId(), scmCertStore, pkiProfile, SCM_ROOT_CA_COMPONENT_NAME); - rootCAServer.init(new SecurityConfig(config), - CertificateServer.CAType.SELF_SIGNED_CA); + rootCAServer.init(new SecurityConfig(config), CAType.ROOT); return rootCAServer; } @@ -283,6 +287,7 @@ private static PKCS10CertificationRequest generateCSR( /** * Persists the sub SCM signed certificate to the location which can be * read by sub CA Certificate server. + * * @param config * @param certificateClient * @param certificateHolder @@ -290,12 +295,15 @@ private static PKCS10CertificationRequest generateCSR( */ private static void persistSubCACertificate(OzoneConfiguration config, CertificateClient certificateClient, - X509CertificateHolder certificateHolder) throws IOException { + String certificateHolder) throws IOException { + SecurityConfig securityConfig = new SecurityConfig(config); CertificateCodec certCodec = - new CertificateCodec(new SecurityConfig(config), + new CertificateCodec(securityConfig, certificateClient.getComponentName()); - certCodec.writeCertificate(certificateHolder); + certCodec.writeCertificate(certCodec.getLocation().toAbsolutePath(), + securityConfig.getCertificateFileName(), + certificateHolder); } /** diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMSecurityProtocolServer.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMSecurityProtocolServer.java index a4171e805adb..f79cd86310a9 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMSecurityProtocolServer.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/SCMSecurityProtocolServer.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.math.BigInteger; import java.net.InetSocketAddress; +import java.security.cert.CertPath; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; @@ -64,7 +65,6 @@ import org.apache.hadoop.security.UserGroupInformation; import org.bouncycastle.asn1.x509.CRLReason; -import org.bouncycastle.cert.X509CertificateHolder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -224,8 +224,7 @@ public String getSCMCertificate(ScmNodeDetailsProto scmNodeDetails, */ private String getEncodedCertToString(String certSignReq, NodeType nodeType) throws IOException { - - Future future; + Future future; if (nodeType == NodeType.SCM) { future = rootCertificateServer.requestCertificate(certSignReq, KERBEROS_TRUSTED, nodeType); @@ -302,7 +301,7 @@ public String getCACertificate() throws IOException { LOGGER.debug("Getting CA certificate."); try { return CertificateCodec.getPEMEncodedString( - scmCertificateServer.getCACertificate()); + scmCertificateServer.getCaCertPath()); } catch (CertificateException e) { throw new SCMSecurityException("getRootCertificate operation failed. ", e, GET_CA_CERT_FAILED); diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/StorageContainerManager.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/StorageContainerManager.java index f5ceeb0295bb..aae54004dec3 100644 --- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/StorageContainerManager.java +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/server/StorageContainerManager.java @@ -72,6 +72,7 @@ import org.apache.hadoop.hdds.scm.pipeline.WritableContainerFactory; import org.apache.hadoop.hdds.security.token.ContainerTokenGenerator; import org.apache.hadoop.hdds.security.token.ContainerTokenSecretManager; +import org.apache.hadoop.hdds.security.x509.certificate.authority.CAType; import org.apache.hadoop.hdds.security.x509.certificate.authority.CertificateStore; import org.apache.hadoop.hdds.security.x509.certificate.authority.profile.DefaultCAProfile; import org.apache.hadoop.hdds.security.x509.certificate.authority.profile.DefaultProfile; @@ -817,7 +818,7 @@ certificateStore, new DefaultProfile(), scmCertificateClient.getComponentName()); // INTERMEDIARY_CA which issues certs to DN and OM. scmCertificateServer.init(new SecurityConfig(configuration), - CertificateServer.CAType.INTERMEDIARY_CA); + CAType.SUBORDINATE); } // If primary SCM node Id is set it means this is a cluster which has diff --git a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/cert/InfoSubcommand.java b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/cert/InfoSubcommand.java index 8ed139d8c697..52710515ac60 100644 --- a/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/cert/InfoSubcommand.java +++ b/hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/cert/InfoSubcommand.java @@ -63,7 +63,7 @@ public void execute(SCMSecurityProtocol client) throws IOException { // Print container report info. LOG.info("Certificate id: {}", serialId); try { - X509Certificate cert = CertificateCodec.getX509Cert(certPemStr); + X509Certificate cert = CertificateCodec.getX509Certificate(certPemStr); LOG.info(cert.toString()); } catch (CertificateException ex) { LOG.error("Failed to get certificate id " + serialId); diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestSecureOzoneCluster.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestSecureOzoneCluster.java index b66cc89bb7c0..f3befc8da26e 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestSecureOzoneCluster.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestSecureOzoneCluster.java @@ -848,7 +848,8 @@ public void testSecureOmInitSuccess() throws Exception { validateCertificate(certificate); String pemEncodedCACert = scm.getSecurityProtocolServer().getCACertificate(); - X509Certificate caCert = CertificateCodec.getX509Cert(pemEncodedCACert); + X509Certificate caCert = + CertificateCodec.getX509Certificate(pemEncodedCACert); X509Certificate caCertStored = om.getCertificateClient() .getCertificate(caCert.getSerialNumber().toString()); assertEquals(caCert, caCertStored); @@ -1262,9 +1263,7 @@ public void validateCertificate(X509Certificate cert) throws Exception { assertTrue(cert.getIssuerDN().toString().contains(clusterId)); // Verify that certificate matches the public key. - String encodedKey1 = cert.getPublicKey().toString(); - String encodedKey2 = om.getCertificateClient().getPublicKey().toString(); - assertEquals(encodedKey1, encodedKey2); + assertEquals(cert.getPublicKey(), om.getCertificateClient().getPublicKey()); } private void initializeOmStorage(OMStorage omStorage) throws IOException { diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/CertificateClientTestImpl.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/CertificateClientTestImpl.java index 2b84fa115356..5a6bd9c48a0f 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/CertificateClientTestImpl.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/CertificateClientTestImpl.java @@ -27,6 +27,7 @@ import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; +import java.security.cert.CertPath; import java.security.cert.CertStore; import java.security.cert.X509Certificate; import java.time.Duration; @@ -47,6 +48,7 @@ import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.apache.hadoop.hdds.security.ssl.KeyStoresFactory; import org.apache.hadoop.hdds.security.x509.SecurityConfig; +import org.apache.hadoop.hdds.security.x509.certificate.authority.CAType; import org.apache.hadoop.hdds.security.x509.certificate.authority.DefaultApprover; import org.apache.hadoop.hdds.security.x509.certificate.authority.profile.DefaultProfile; import org.apache.hadoop.hdds.security.x509.certificate.client.CertificateClient; @@ -195,11 +197,21 @@ public X509Certificate getCertificate(String certSerialId) return certificateMap.get(certSerialId); } + @Override + public CertPath getCertPath() { + return null; + } + @Override public X509Certificate getCertificate() { return x509Certificate; } + @Override + public CertPath getCACertPath() { + return null; + } + @Override public X509Certificate getCACertificate() { return rootCert; @@ -277,12 +289,12 @@ public X509Certificate queryCertificate(String query) { } @Override - public void storeCertificate(String cert, boolean force) + public void storeCertificate(String cert) throws CertificateException { } @Override - public void storeCertificate(String cert, boolean force, boolean caCert) + public void storeCertificate(String cert, CAType caType) throws CertificateException { } @@ -329,7 +341,7 @@ public X509Certificate getRootCACertificate() { } @Override - public void storeRootCACertificate(String pemEncodedCert, boolean force) { + public void storeRootCACertificate(String pemEncodedCert) { } diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/OMCertificateClient.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/OMCertificateClient.java index 6cce39267bee..706918179396 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/OMCertificateClient.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/OMCertificateClient.java @@ -24,6 +24,7 @@ import org.apache.hadoop.hdds.protocol.proto.HddsProtos; import org.apache.hadoop.hdds.protocol.proto.SCMSecurityProtocolProtos.SCMGetCertResponseProto; import org.apache.hadoop.hdds.security.x509.SecurityConfig; +import org.apache.hadoop.hdds.security.x509.certificate.authority.CAType; import org.apache.hadoop.hdds.security.x509.certificate.client.CommonCertificateClient; import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateCodec; import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateSignRequest; @@ -162,26 +163,27 @@ public CertificateSignRequest.Builder getCSRBuilder(KeyPair keyPair) @Override public String signAndStoreCertificate(PKCS10CertificationRequest request, - Path certPath) throws CertificateException { + Path certificatePath) throws CertificateException { try { SCMGetCertResponseProto response = getScmSecureClient() .getOMCertChain(omInfo, getEncodedString(request)); String pemEncodedCert = response.getX509Certificate(); CertificateCodec certCodec = new CertificateCodec( - getSecurityConfig(), certPath); + getSecurityConfig(), certificatePath); // Store SCM CA certificate. if (response.hasX509CACertificate()) { String pemEncodedRootCert = response.getX509CACertificate(); storeCertificate(pemEncodedRootCert, - true, true, false, certCodec, false); - storeCertificate(pemEncodedCert, true, false, false, certCodec, false); + CAType.SUBORDINATE, certCodec, false); + storeCertificate(pemEncodedCert, CAType.NONE, certCodec, + false); // Store Root CA certificate if available. if (response.hasX509RootCACertificate()) { storeCertificate(response.getX509RootCACertificate(), - true, false, true, certCodec, false); + CAType.ROOT, certCodec, false); } return CertificateCodec.getX509Certificate(pemEncodedCert) .getSerialNumber().toString(); diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestOzoneDelegationTokenSecretManager.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestOzoneDelegationTokenSecretManager.java index 0003c73efc3c..887e46917335 100644 --- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestOzoneDelegationTokenSecretManager.java +++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestOzoneDelegationTokenSecretManager.java @@ -24,13 +24,16 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; +import java.security.cert.CertPath; import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.Map; +import com.google.common.collect.ImmutableList; import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.apache.hadoop.hdds.security.x509.SecurityConfig; import org.apache.hadoop.hdds.security.x509.certificate.client.CertificateClient; +import org.apache.hadoop.hdds.security.x509.certificate.utils.CertificateCodec; import org.apache.hadoop.hdds.server.ServerUtils; import org.apache.hadoop.io.Text; import org.apache.hadoop.ozone.OzoneConsts; @@ -51,6 +54,8 @@ import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_RATIS_ENABLE_KEY; import static org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMTokenProto.Type.S3AUTHINFO; + +import org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -135,13 +140,16 @@ private OzoneConfiguration createNewTestPath() throws IOException { * */ private CertificateClient setupCertificateClient() throws Exception { KeyPair keyPair = KeyStoreTestUtil.generateKeyPair("RSA"); - X509Certificate cert = KeyStoreTestUtil + CertificateFactory fact = CertificateCodec.getCertFactory(); + X509Certificate singleCert = KeyStoreTestUtil .generateCertificate("CN=OzoneMaster", keyPair, 30, "SHA256withRSA"); + CertPath certPath = fact.engineGenerateCertPath( + ImmutableList.of(singleCert)); return new OMCertificateClient(securityConfig) { @Override - public X509Certificate getCertificate() { - return cert; + public CertPath getCertPath() { + return certPath; } @Override @@ -156,7 +164,7 @@ public PublicKey getPublicKey() { @Override public X509Certificate getCertificate(String serialId) { - return cert; + return CertificateCodec.firstCertificateFrom(certPath); } }; }