DaanHoogland commented on a change in pull request #2239: CLOUDSTACK-9993: 
Securing Agents Communications
URL: https://github.com/apache/cloudstack/pull/2239#discussion_r134691592
 
 

 ##########
 File path: 
plugins/ca/root-ca/src/org/apache/cloudstack/ca/provider/RootCAProvider.java
 ##########
 @@ -0,0 +1,572 @@
+// 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.cloudstack.ca.provider;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.StringReader;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyManagementException;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.SignatureException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.naming.ConfigurationException;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+import org.apache.cloudstack.ca.CAManager;
+import org.apache.cloudstack.framework.ca.CAProvider;
+import org.apache.cloudstack.framework.ca.Certificate;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.Configurable;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.utils.security.CertUtils;
+import org.apache.cloudstack.utils.security.KeyStoreUtils;
+import org.apache.log4j.Logger;
+import org.bouncycastle.jce.PKCS10CertificationRequest;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.util.io.pem.PemObject;
+import org.bouncycastle.util.io.pem.PemReader;
+
+import com.cloud.certificate.dao.CrlDao;
+import com.cloud.utils.PropertiesUtil;
+import com.cloud.utils.component.AdapterBase;
+import com.cloud.utils.db.DbProperties;
+import com.cloud.utils.db.GlobalLock;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.utils.net.NetUtils;
+import com.cloud.utils.nio.Link;
+import com.google.common.base.Strings;
+
+public final class RootCAProvider extends AdapterBase implements CAProvider, 
Configurable {
+    private static final Logger LOG = Logger.getLogger(RootCAProvider.class);
+
+    public static final Integer caValidityYears = 30;
+    public static final String caAlias = "root";
+    public static final String managementAlias = "management";
+
+    private static KeyPair caKeyPair = null;
+    private static X509Certificate caCertificate = null;
+
+    @Inject
+    private ConfigurationDao configDao;
+    @Inject
+    private CrlDao crlDao;
+
+    ////////////////////////////////////////////////////
+    /////////////// Root CA Settings ///////////////////
+    ////////////////////////////////////////////////////
+
+    private static ConfigKey<String> rootCAPrivateKey = new 
ConfigKey<>("Hidden", String.class,
+            "ca.plugin.root.private.key",
+            null,
+            "The ROOT CA private key.", true);
+
+    private static ConfigKey<String> rootCAPublicKey = new 
ConfigKey<>("Hidden", String.class,
+            "ca.plugin.root.public.key",
+            null,
+            "The ROOT CA public key.", true);
+
+    private static ConfigKey<String> rootCACertificate = new 
ConfigKey<>("Hidden", String.class,
+            "ca.plugin.root.ca.certificate",
+            null,
+            "The ROOT CA certificate.", true);
+
+    private static ConfigKey<String> rootCAIssuerDN = new 
ConfigKey<>("Advanced", String.class,
+            "ca.plugin.root.issuer.dn",
+            "CN=ca.cloudstack.apache.org",
+            "The ROOT CA issuer distinguished name.", true);
+
+    protected static ConfigKey<Boolean> rootCAAuthStrictness = new 
ConfigKey<>("Advanced", Boolean.class,
+            "ca.plugin.root.auth.strictness",
+            "false",
+            "Set client authentication strictness, setting to true will 
enforce and require client certificate for authentication in applicable CA 
providers.", true);
+
+    private static ConfigKey<Boolean> rootCAAllowExpiredCert = new 
ConfigKey<>("Advanced", Boolean.class,
+            "ca.plugin.root.allow.expired.cert",
+            "true",
+            "When set to true, it will allow expired client certificate during 
SSL handshake.", true);
+
+
+    ///////////////////////////////////////////////////////////
+    /////////////// Root CA Private Methods ///////////////////
+    ///////////////////////////////////////////////////////////
+
+    private Certificate generateCertificate(final List<String> domainNames, 
final List<String> ipAddresses, final int validityDays) throws 
NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, 
CertificateException, SignatureException, IOException, 
OperatorCreationException {
+        if (domainNames == null || domainNames.size() < 1 || 
Strings.isNullOrEmpty(domainNames.get(0))) {
+            throw new CloudRuntimeException("No domain name is specified, 
cannot generate certificate");
+        }
+        final String subject = "CN=" + domainNames.get(0);
+
+        final KeyPair keyPair = 
CertUtils.generateRandomKeyPair(CAManager.CertKeySize.value());
+        final X509Certificate clientCertificate = 
CertUtils.generateV3Certificate(
+                caCertificate,
+                caKeyPair.getPrivate(),
+                keyPair.getPublic(),
+                subject,
+                CAManager.CertSignatureAlgorithm.value(),
+                validityDays,
+                domainNames,
+                ipAddresses);
+        return new Certificate(clientCertificate, keyPair.getPrivate(), 
Collections.singletonList(caCertificate));
+    }
+
+    private Certificate generateCertificateUsingCsr(final String csr, final 
List<String> domainNames, final List<String> ipAddresses, final int 
validityDays) throws NoSuchAlgorithmException, InvalidKeyException, 
NoSuchProviderException, CertificateException, SignatureException, IOException, 
OperatorCreationException {
+        PemObject pemObject = null;
+
+        try {
+            final PemReader pemReader = new PemReader(new StringReader(csr));
+            pemObject = pemReader.readPemObject();
+        } catch (IOException e) {
+            LOG.error("Failed to read provided CSR string as a PEM object", e);
+        }
+
+        if (pemObject == null) {
+            throw new CloudRuntimeException("Unable to read/process CSR: " + 
csr);
+        }
+
+        final PKCS10CertificationRequest request = new 
PKCS10CertificationRequest(pemObject.getContent());
+
+        final X509Certificate clientCertificate = 
CertUtils.generateV3Certificate(
+                caCertificate, caKeyPair.getPrivate(),
+                request.getPublicKey(),
+                request.getCertificationRequestInfo().getSubject().toString(),
+                CAManager.CertSignatureAlgorithm.value(),
+                validityDays,
+                domainNames,
+                ipAddresses);
+        return new Certificate(clientCertificate, null, 
Collections.singletonList(caCertificate));
+
+    }
+
+    ////////////////////////////////////////////////////////
+    /////////////// Root CA API Handlers ///////////////////
+    ////////////////////////////////////////////////////////
+
+    @Override
+    public boolean canProvisionCertificates() {
+        return true;
+    }
+
+    @Override
+    public List<X509Certificate> getCaCertificate() {
+        return Collections.singletonList(caCertificate);
+    }
+
+    @Override
+    public Certificate issueCertificate(final List<String> domainNames, final 
List<String> ipAddresses, final int validityDays) {
+        try {
+            return generateCertificate(domainNames, ipAddresses, validityDays);
+        } catch (final CertificateException | IOException | SignatureException 
| NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException | 
OperatorCreationException e) {
+            LOG.error("Failed to create client certificate, due to: ", e);
+            throw new CloudRuntimeException("Failed to generate certificate 
due to:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public Certificate issueCertificate(final String csr, final List<String> 
domainNames, final List<String> ipAddresses, final int validityDays) {
+        try {
+            return generateCertificateUsingCsr(csr, domainNames, ipAddresses, 
validityDays);
+        } catch (final CertificateException | IOException | SignatureException 
| NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException | 
OperatorCreationException e) {
+            LOG.error("Failed to generate certificate from CSR: ", e);
+            throw new CloudRuntimeException("Failed to generate certificate 
using CSR due to:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public boolean revokeCertificate(final BigInteger certSerial, final String 
certCn) {
+        return true;
+    }
+
+    ////////////////////////////////////////////////////////////
+    /////////////// Root CA Trust Management ///////////////////
+    ////////////////////////////////////////////////////////////
+
+    public static final class RootCACustomTrustManager implements 
X509TrustManager {
+        private String clientAddress = "Unknown";
+        private boolean authStrictness = true;
+        private boolean allowExpiredCertificate = true;
+        private CrlDao crlDao;
+        private X509Certificate caCertificate;
+        private Map<String, X509Certificate> activeCertMap;
+
+        public RootCACustomTrustManager(final String clientAddress, final 
boolean authStrictness, final boolean allowExpiredCertificate, final 
Map<String, X509Certificate> activeCertMap, final X509Certificate 
caCertificate, final CrlDao crlDao) {
+            if (!Strings.isNullOrEmpty(clientAddress)) {
+                this.clientAddress = clientAddress.replace("/", 
"").split(":")[0];
+            }
+            this.authStrictness = authStrictness;
+            this.allowExpiredCertificate = allowExpiredCertificate;
+            this.activeCertMap = activeCertMap;
+            this.caCertificate = caCertificate;
+            this.crlDao = crlDao;
+        }
+
+        private void printCertificateChain(final X509Certificate[] 
certificates, final String s) throws CertificateException {
+            if (certificates == null) {
+                return;
+            }
+            final StringBuilder builder = new StringBuilder();
+            builder.append("A client/agent attempting connection from 
address=").append(clientAddress).append(" has presented these certificate(s):");
+            int counter = 1;
+            for (final X509Certificate certificate: certificates) {
+                builder.append("\nCertificate [").append(counter++).append("] 
:");
+                builder.append(String.format("\n Serial: %x", 
certificate.getSerialNumber()));
+                builder.append("\n  Not Before:" + certificate.getNotBefore());
+                builder.append("\n  Not After:" + certificate.getNotAfter());
+                builder.append("\n  Signature Algorithm:" + 
certificate.getSigAlgName());
+                builder.append("\n  Version:" + certificate.getVersion());
+                builder.append("\n  Subject DN:" + certificate.getSubjectDN());
+                builder.append("\n  Issuer DN:" + certificate.getIssuerDN());
+                builder.append("\n  Alternative Names:" + 
certificate.getSubjectAlternativeNames());
+            }
+            LOG.debug(builder.toString());
+        }
+
+        @Override
+        public void checkClientTrusted(final X509Certificate[] certificates, 
final String s) throws CertificateException {
+            if (LOG.isDebugEnabled()) {
+                printCertificateChain(certificates, s);
+            }
+            if (!authStrictness) {
 
 Review comment:
   :+1: in the end backwards compatibility is so easy :)
 
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
[email protected]


With regards,
Apache Git Services

Reply via email to