| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| package org.apache.hadoop.hdds.security.x509.certificate.utils; |
| |
| import org.apache.commons.io.IOUtils; |
| import org.apache.hadoop.hdds.security.exception.SCMSecurityException; |
| import org.apache.hadoop.hdds.security.x509.SecurityConfig; |
| import org.bouncycastle.cert.X509CertificateHolder; |
| import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; |
| import org.bouncycastle.openssl.jcajce.JcaPEMWriter; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.StringWriter; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.nio.file.attribute.PosixFilePermission; |
| import java.security.cert.CertificateEncodingException; |
| import java.security.cert.CertificateException; |
| import java.security.cert.CertificateFactory; |
| import java.security.cert.X509Certificate; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| import java.util.stream.Stream; |
| |
| import static java.nio.charset.StandardCharsets.UTF_8; |
| import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE; |
| import static java.nio.file.attribute.PosixFilePermission.OWNER_READ; |
| import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE; |
| import static org.apache.hadoop.hdds.security.exception.SCMSecurityException.ErrorCode.PEM_ENCODE_FAILED; |
| |
| /** |
| * A class used to read and write X.509 certificates PEM encoded Streams. |
| */ |
| public class CertificateCodec { |
| public static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----"; |
| public static final String END_CERT = "-----END CERTIFICATE-----"; |
| |
| private static final Logger LOG = |
| LoggerFactory.getLogger(CertificateCodec.class); |
| private static final JcaX509CertificateConverter CERTIFICATE_CONVERTER |
| = new JcaX509CertificateConverter(); |
| private final SecurityConfig securityConfig; |
| private final Path location; |
| private Set<PosixFilePermission> permissionSet = |
| Stream.of(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE) |
| .collect(Collectors.toSet()); |
| /** |
| * Creates a CertificateCodec with component name. |
| * |
| * @param config - Security Config. |
| * @param component - Component String. |
| */ |
| public CertificateCodec(SecurityConfig config, String component) { |
| this.securityConfig = config; |
| this.location = securityConfig.getCertificateLocation(component); |
| } |
| |
| /** |
| * Returns a X509 Certificate from the Certificate Holder. |
| * |
| * @param holder - Holder |
| * @return X509Certificate. |
| * @throws CertificateException - on Error. |
| */ |
| public static X509Certificate getX509Certificate(X509CertificateHolder holder) |
| throws CertificateException { |
| return CERTIFICATE_CONVERTER.getCertificate(holder); |
| } |
| |
| /** |
| * Returns the Certificate as a PEM encoded String. |
| * |
| * @param x509CertHolder - X.509 Certificate Holder. |
| * @return PEM Encoded Certificate String. |
| * @throws SCMSecurityException - On failure to create a PEM String. |
| */ |
| public static String getPEMEncodedString(X509CertificateHolder x509CertHolder) |
| throws SCMSecurityException { |
| try { |
| return getPEMEncodedString(getX509Certificate(x509CertHolder)); |
| } catch (CertificateException exp) { |
| throw new SCMSecurityException(exp); |
| } |
| } |
| |
| /** |
| * Returns the Certificate as a PEM encoded String. |
| * |
| * @param certificate - X.509 Certificate. |
| * @return PEM Encoded Certificate String. |
| * @throws SCMSecurityException - On failure to create a PEM String. |
| */ |
| public static String getPEMEncodedString(X509Certificate certificate) |
| throws SCMSecurityException { |
| try { |
| StringWriter stringWriter = new StringWriter(); |
| try (JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) { |
| pemWriter.writeObject(certificate); |
| } |
| return stringWriter.toString(); |
| } catch (IOException e) { |
| LOG.error("Error in encoding certificate." + certificate |
| .getSubjectDN().toString(), e); |
| throw new SCMSecurityException("PEM Encoding failed for certificate." + |
| certificate.getSubjectDN().toString(), e, PEM_ENCODE_FAILED); |
| } |
| } |
| |
| /** |
| * 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 getX509Certificate(String pemEncodedString) |
| throws CertificateException, IOException { |
| CertificateFactory fact = CertificateFactory.getInstance("X.509"); |
| try (InputStream input = IOUtils.toInputStream(pemEncodedString, UTF_8)) { |
| return (X509Certificate) fact.generateCertificate(input); |
| } |
| } |
| |
| /** |
| * Get Certificate location. |
| * |
| * @return Path |
| */ |
| 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. |
| */ |
| public void writeCertificate(X509CertificateHolder xCertificate) |
| throws SCMSecurityException, IOException { |
| String pem = getPEMEncodedString(xCertificate); |
| writeCertificate(location.toAbsolutePath(), |
| this.securityConfig.getCertificateFileName(), pem, false); |
| } |
| |
| /** |
| * Write the Certificate to the specific file. |
| * |
| * @param xCertificate - Certificate to write. |
| * @param fileName - file name to write to. |
| * @param overwrite - boolean value, true means overwrite an existing |
| * certificate. |
| * @throws SCMSecurityException - On Error. |
| * @throws IOException - On Error. |
| */ |
| public void writeCertificate(X509CertificateHolder xCertificate, |
| String fileName, boolean overwrite) |
| throws SCMSecurityException, IOException { |
| String pem = getPEMEncodedString(xCertificate); |
| writeCertificate(location.toAbsolutePath(), fileName, pem, overwrite); |
| } |
| |
| /** |
| * Helper function that writes data to the file. |
| * |
| * @param basePath - Base Path where the file needs to written to. |
| * @param fileName - Certificate file name. |
| * @param pemEncodedCertificate - pemEncoded Certificate file. |
| * @param force - Overwrite if the file exists. |
| * @throws IOException - on Error. |
| */ |
| public synchronized void writeCertificate(Path basePath, String fileName, |
| String pemEncodedCertificate, boolean force) |
| throws IOException { |
| File certificateFile = |
| Paths.get(basePath.toString(), fileName).toFile(); |
| if (certificateFile.exists() && !force) { |
| throw new SCMSecurityException("Specified certificate file already " + |
| "exists.Please use force option if you want to overwrite it."); |
| } |
| if (!basePath.toFile().exists()) { |
| if (!basePath.toFile().mkdirs()) { |
| LOG.error("Unable to create file path. Path: {}", basePath); |
| throw new IOException("Creation of the directories failed." |
| + basePath.toString()); |
| } |
| } |
| try (FileOutputStream file = new FileOutputStream(certificateFile)) { |
| IOUtils.write(pemEncodedCertificate, file, UTF_8); |
| } |
| |
| Files.setPosixFilePermissions(certificateFile.toPath(), permissionSet); |
| } |
| |
| /** |
| * Rertuns a default certificate using the default paths for this component. |
| * |
| * @return X509CertificateHolder. |
| * @throws SCMSecurityException - on Error. |
| * @throws CertificateException - on Error. |
| * @throws IOException - on Error. |
| */ |
| public X509CertificateHolder readCertificate() throws |
| CertificateException, IOException { |
| return readCertificate(this.location.toAbsolutePath(), |
| this.securityConfig.getCertificateFileName()); |
| } |
| |
| /** |
| * Returns the certificate from the specific PEM encoded file. |
| * |
| * @param basePath - base path |
| * @param fileName - fileName |
| * @return X%09 Certificate |
| * @throws IOException - on Error. |
| * @throws SCMSecurityException - on Error. |
| * @throws CertificateException - on Error. |
| */ |
| public synchronized X509CertificateHolder readCertificate(Path basePath, |
| String fileName) throws IOException, CertificateException { |
| File certificateFile = Paths.get(basePath.toString(), fileName).toFile(); |
| return getX509CertificateHolder(certificateFile); |
| } |
| |
| /** |
| * Helper function to read certificate. |
| * |
| * @param certificateFile - Full path to certificate file. |
| * @return X509CertificateHolder |
| * @throws IOException - On Error. |
| * @throws CertificateException - On Error. |
| */ |
| private X509CertificateHolder getX509CertificateHolder(File certificateFile) |
| throws IOException, CertificateException { |
| if (!certificateFile.exists()) { |
| throw new IOException("Unable to find the requested certificate. Path: " |
| + certificateFile.toString()); |
| } |
| CertificateFactory fact = CertificateFactory.getInstance("X.509"); |
| try (FileInputStream is = new FileInputStream(certificateFile)) { |
| return getCertificateHolder( |
| (X509Certificate) fact.generateCertificate(is)); |
| } |
| } |
| |
| /** |
| * Returns the Certificate holder from X509Certificate class. |
| * |
| * @param x509cert - Certificate class. |
| * @return X509CertificateHolder |
| * @throws CertificateEncodingException - on Error. |
| * @throws IOException - on Error. |
| */ |
| public static X509CertificateHolder getCertificateHolder( |
| X509Certificate x509cert) |
| throws CertificateEncodingException, IOException { |
| return new X509CertificateHolder(x509cert.getEncoded()); |
| } |
| } |